实现千位分隔符
// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
复制代码
function parseToMoney(num) {
num = parseFloat(num.toFixed(3));
let [integer, decimal] = String.prototype.split.call(num, '.');
integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
return integer + '.' + (decimal ? decimal : '');
}
复制代码
正则表达式(运用了正则的前向声明和反前向声明):
function parseToMoney(str){
// 仅仅对位置进行匹配
let re = /(?=(?!\b)(\d{3})+$)/g;
return str.replace(re,',');
}
复制代码
实现 filter 方法
Array.prototype.myFilter=function(callback, context=window){
let len = this.length
newArr = [],
i=0
for(; i < len; i++){
if(callback.apply(context, [this[i], i , this])){
newArr.push(this[i]);
}
}
return newArr;
}
复制代码
实现节流函数(throttle)
节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的时间才会执行回调函数。总结起来就是: 事件,按照一段时间的间隔来进行触发 。
像 dom 的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多
手写简版
使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行
时间戳方式:
// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 50) => {
// 上一次执行该函数的时间
let lastTime = 0
return function(...args) {
// 当前时间
let now = +new Date()
// 将当前时间和上一次执行函数时间对比
// 如果差值大于设置的等待时间就执行函数
if (now - lastTime > wait) {
lastTime = now
func.apply(this, args)
}
}
}
setInterval(
throttle(() => {
console.log(1)
}, 500),
1
)
复制代码
定时器方式:
使用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最后一次停止触发后,还会再执行一次函数
function throttle(func, delay){
var timer = null;
returnfunction(){
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
func.apply(context, args);
timer = null;
},delay);
}
}
}
复制代码
适用场景:
总结
实现 instanceOf
思路:
步骤 1:先取得当前类的原型,当前实例对象的原型链
步骤 2:一直循环(执行原型链的查找机制)
取得当前实例对象原型链的原型链(proto = proto.__proto__
,沿着原型链一直向上查找)
如果 当前实例的原型链__proto__
上找到了当前类的原型prototype
,则返回 true
如果 一直找到Object.prototype.__proto__ == null
,Object
的基类(null
)上面都没找到,则返回 false
// 实例.__ptoto__ === 类.prototype
function _instanceof(example, classFunc) {
// 由于instance要检测的是某对象,需要有一个前置判断条件
//基本数据类型直接返回false
if(typeof example !== 'object' || example === null) return false;
let proto = Object.getPrototypeOf(example);
while(true) {
if(proto == null) return false;
// 在当前实例对象的原型链上,找到了当前类
if(proto == classFunc.prototype) return true;
// 沿着原型链__ptoto__一层一层向上查
proto = Object.getPrototypeof(proto); // 等于proto.__ptoto__
}
}
console.log('test', _instanceof(null, Array)) // false
console.log('test', _instanceof([], Array)) // true
console.log('test', _instanceof('', Array)) // false
console.log('test', _instanceof({}, Object)) // true
复制代码
实现 bind 方法
bind
的实现对比其他两个函数略微地复杂了一点,涉及到参数合并(类似函数柯里化),因为 bind
需要返回一个函数,需要判断一些边界问题,以下是 bind
的实现
bind
返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new
的方式,我们先来说直接调用的方式
对于直接调用来说,这里选择了 apply
的方式实现,但是对于参数需要注意以下情况:因为 bind
可以实现类似这样的代码 f.bind(obj, 1)(2)
,所以我们需要将两边的参数拼接起来
最后来说通过 new
的方式,对于 new
的情况来说,不会被任何方式改变 this
,所以对于这种情况我们需要忽略传入的 this
简洁版本
Function.prototype.myBind = function(context = window, ...args) {
// this表示调用bind的函数
let self = this;
//返回了一个函数,...innerArgs为实际调用时传入的参数
let fBound = function(...innerArgs) {
//this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)
// 当作为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 true,可以让实例获得来自绑定函数的值
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return self.apply(
this instanceof fBound ? this : context,
args.concat(innerArgs)
);
}
// 如果绑定的是构造函数,那么需要继承构造函数原型属性和方法:保证原函数的原型对象上的属性不丢失
// 实现继承的方式: 使用Object.create
fBound.prototype = Object.create(this.prototype);
return fBound;
}
复制代码
// 测试用例
function Person(name, age) {
console.log('Person name:', name);
console.log('Person age:', age);
console.log('Person this:', this); // 构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function() {
console.log('person say');
}
// 普通函数
function normalFun(name, age) {
console.log('普通函数 name:', name);
console.log('普通函数 age:', age);
console.log('普通函数 this:', this); // 普通函数this指向绑定bind的第一个参数 也就是例子中的obj
}
var obj = {
name: 'poetries',
age: 18
}
// 先测试作为构造函数调用
var bindFun = Person.myBind(obj, 'poetry1') // undefined
var a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}
a.say() // person say
// 再测试作为普通函数调用
var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefined
bindNormalFun(12) // 普通函数name: poetry2 普通函数 age: 12 普通函数 this: {name: 'poetries', age: 18}
复制代码
注意: bind
之后不能再次修改this
的指向,bind
多次后执行,函数this
还是指向第一次bind
的对象
实现 JSONP 方法
利用<script>
标签不受跨域限制的特点,缺点是只能支持 get
请求
function jsonp({url,params,callback}) {
return new Promise((resolve,reject)=>{
let script = document.createElement('script')
window[callback] = function (data) {
resolve(data)
document.body.removeChild(script)
}
var arr = []
for(var key in params) {
arr.push(`${key}=${params[key]}`)
}
script.type = 'text/javascript'
script.src = `${url}?callback=${callback}&${arr.join('&')}`
document.body.appendChild(script)
})
}
复制代码
// 测试用例
jsonp({
url: 'http://suggest.taobao.com/sug',
callback: 'getData',
params: {
q: 'iphone手机',
code: 'utf-8'
},
}).then(data=>{console.log(data)})
复制代码
实现 Promise 相关方法
实现 Promise 的 resolve
实现 resolve 静态方法有三个要点:
Promise.resolve = (param) => {
if(param instanceof Promise) return param;
return new Promise((resolve, reject) => {
if(param && param.then && typeof param.then === 'function') {
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject);
}else {
resolve(param);
}
})
}
复制代码
实现 Promise.reject
Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传, 实现如下:
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
复制代码
实现 Promise.prototype.finally
前面的promise
不管成功还是失败,都会走到finally
中,并且finally
之后,还可以继续then
(说明它还是一个 then 方法是关键),并且会将初始的promise
值原封不动的传递给后面的then
.
Promise.prototype.finally 最大的作用
Promise.prototype.finally 六大情况用法
// 情况1
Promise.resolve(123).finally((data) => { // 这里传入的函数,无论如何都会执行
console.log(data); // undefined
})
// 情况2 (这里,finally方法相当于做了中间处理,起一个过渡的作用)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
}).then(data => {
console.log(data); // 123
})
// 情况3 (这里只要reject,都会走到下一个then的err中)
Promise.reject(123).finally((data) => {
console.log(data); // undefined
}).then(data => {
console.log(data);
}, err => {
console.log(err, 'err'); // 123 err
})
// 情况4 (一开始就成功之后,会等待finally里的promise执行完毕后,再把前面的data传递到下一个then中)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 3000)
})
}).then(data => {
console.log(data, 'success'); // 123 success
}, err => {
console.log(err, 'err');
})
// 情况5 (虽然一开始成功,但是只要finally函数中的promise失败了,就会把其失败的值传递到下一个then的err中)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('rejected');
}, 3000)
})
}).then(data => {
console.log(data, 'success');
}, err => {
console.log(err, 'err'); // rejected err
})
// 情况6 (虽然一开始失败,但是也要等finally中的promise执行完,才能把一开始的err传递到err的回调中)
Promise.reject(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
}, 3000)
})
}).then(data => {
console.log(data, 'success');
}, err => {
console.log(err, 'err'); // 123 err
})
复制代码
源码实现
Promise.prototype.finally = function (callback) {
return this.then((data) => {
// 让函数执行 内部会调用方法,如果方法是promise,需要等待它完成
// 如果当前promise执行时失败了,会把err传递到,err的回调函数中
return Promise.resolve(callback()).then(() => data); // data 上一个promise的成功态
}, err => {
return Promise.resolve(callback()).then(() => {
throw err; // 把之前的失败的err,抛出去
});
})
}
复制代码
实现 Promise.all
对于 all 方法而言,需要完成下面的核心功能:
传入参数为一个空的可迭代对象,则直接进行resolve
。
如果参数中有一个promise
失败,那么Promise.all
返回的promise
对象失败。
在任何情况下,Promise.all
返回的 promise
的完成状态的结果都是一个数组
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let result = [];
let index = 0;
let len = promises.length;
if(len === 0) {
resolve(result);
return;
}
for(let i = 0; i < len; i++) {
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
Promise.resolve(promise[i]).then(data => {
result[i] = data;
index++;
if(index === len) resolve(result);
}).catch(err => {
reject(err);
})
}
})
}
复制代码
实现 promise.allsettle
MDN: Promise.allSettled()
方法返回一个在所有给定的promise
都已经
fulfilled或
rejected后的
promise,并带有一个对象数组,每个对象表示对应的
promise`结果
当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise
的结果时,通常使用它。
【译】Promise.allSettled
跟 Promise.all
类似, 其参数接受一个Promise
的数组, 返回一个新的Promise
, 唯一的不同在于, 其不会进行短路, 也就是说当 Promise 全部处理完成后我们可以拿到每个Promise
的状态, 而不管其是否处理成功。
用法 | 测试用例
let fs = require('fs').promises;
let getName = fs.readFile('./name.txt', 'utf8'); // 读取文件成功
let getAge = fs.readFile('./age.txt', 'utf8');
Promise.allSettled([1, getName, getAge, 2]).then(data => {
console.log(data);
});
// 输出结果
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'fulfilled', value: 'zf' },
{ status: 'fulfilled', value: '11' },
{ status: 'fulfilled', value: 2 }
]
*/
let getName = fs.readFile('./name123.txt', 'utf8'); // 读取文件失败
let getAge = fs.readFile('./age.txt', 'utf8');
// 输出结果
/*
[
{ status: 'fulfilled', value: 1 },
{
status: 'rejected',
value: [Error: ENOENT: no such file or directory, open './name123.txt'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: './name123.txt'
}
},
{ status: 'fulfilled', value: '11' },
{ status: 'fulfilled', value: 2 }
]
*/
复制代码
实现
function isPromise (val) {
return typeof val.then === 'function'; // (123).then => undefined
}
Promise.allSettled = function(promises) {
return new Promise((resolve, reject) => {
let arr = [];
let times = 0;
const setData = (index, data) => {
arr[index] = data;
if (++times === promises.length) {
resolve(arr);
}
console.log('times', times)
}
for (let i = 0; i < promises.length; i++) {
let current = promises[i];
if (isPromise(current)) {
current.then((data) => {
setData(i, { status: 'fulfilled', value: data });
}, err => {
setData(i, { status: 'rejected', value: err })
})
} else {
setData(i, { status: 'fulfilled', value: current })
}
}
})
}
复制代码
实现 Promise.race
race 的实现相比之下就简单一些,只要有一个 promise 执行完,直接 resolve 并停止执行
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
let len = promises.length;
if(len === 0) return;
for(let i = 0; i < len; i++) {
Promise.resolve(promise[i]).then(data => {
resolve(data);
return;
}).catch(err => {
reject(err);
return;
})
}
})
}
复制代码
实现一个简版 Promise
// 使用
var promise = new Promise((resolve,reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
},function (value) {
// failure
})
复制代码
function myPromise(constructor) {
let self = this;
self.status = "pending" // 定义状态改变前的初始状态
self.value = undefined; // 定义状态为resolved的时候的状态
self.reason = undefined; // 定义状态为rejected的时候的状态
function resolve(value) {
if(self.status === "pending") {
self.value = value;
self.status = "resolved";
}
}
function reject(reason) {
if(self.status === "pending") {
self.reason = reason;
self.status = "rejected";
}
}
// 捕获构造异常
try {
constructor(resolve,reject);
} catch(e) {
reject(e);
}
}
复制代码
// 添加 then 方法
myPromise.prototype.then = function(onFullfilled,onRejected) {
let self = this;
switch(self.status) {
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
var p = new myPromise(function(resolve,reject) {
resolve(1)
});
p.then(function(x) {
console.log(x) // 1
})
复制代码
使用 class 实现
class MyPromise {
constructor(fn) {
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
this.state = 'PENDING';
this.value = '';
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(value) {
if (this.state === 'PENDING') {
this.state = 'RESOLVED';
this.value = value;
this.resolvedCallbacks.map(cb => cb(value));
}
}
reject(value) {
if (this.state === 'PENDING') {
this.state = 'REJECTED';
this.value = value;
this.rejectedCallbacks.map(cb => cb(value));
}
}
then(onFulfilled, onRejected) {
if (this.state === 'PENDING') {
this.resolvedCallbacks.push(onFulfilled);
this.rejectedCallbacks.push(onRejected);
}
if (this.state === 'RESOLVED') {
onFulfilled(this.value);
}
if (this.state === 'REJECTED') {
onRejected(this.value);
}
}
}
复制代码
Promise 实现-详细
可以把 Promise
看成一个状态机。初始是 pending
状态,可以通过函数 resolve
和 reject
,将状态转变为 resolved
或者 rejected
状态,状态一旦改变就不能再次变化。
then
函数会返回一个 Promise
实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise
规范规定除了 pending
状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then
调用就失去意义了。
对于 then
来说,本质上可以把它看成是 flatMap
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function MyPromise(fn) {
let _this = this;
_this.currentState = PENDING;
_this.value = undefined;
// 用于保存 then 中的回调,只有当 promise
// 状态为 pending 时才会缓存,并且每个实例至多缓存一个
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
_this.resolve = function (value) {
if (value instanceof MyPromise) {
// 如果 value 是个 Promise,递归执行
return value.then(_this.resolve, _this.reject)
}
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = RESOLVED;
_this.value = value;
_this.resolvedCallbacks.forEach(cb => cb());
}
})
};
_this.reject = function (reason) {
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = REJECTED;
_this.value = reason;
_this.rejectedCallbacks.forEach(cb => cb());
}
})
}
// 用于解决以下问题
// new Promise(() => throw Error('error))
try {
fn(_this.resolve, _this.reject);
} catch (e) {
_this.reject(e);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this;
// 规范 2.2.7,then 必须返回一个新的 promise
var promise2;
// 规范 2.2.onResolved 和 onRejected 都为可选参数
// 如果类型不是函数需要忽略,同时也实现了透传
// Promise.resolve(4).then().then((value) => console.log(value))
onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;
if (self.currentState === RESOLVED) {
return (promise2 = new MyPromise(function (resolve, reject) {
// 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
// 所以用了 setTimeout 包裹下
setTimeout(function () {
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === REJECTED) {
return (promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
// 异步执行onRejected
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === PENDING) {
return (promise2 = new MyPromise(function (resolve, reject) {
self.resolvedCallbacks.push(function () {
// 考虑到可能会有报错,所以使用 try/catch 包裹
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
self.rejectedCallbacks.push(function () {
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
// 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
if (promise2 === x) {
return reject(new TypeError("Error"));
}
// 规范 2.3.2
// 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof MyPromise) {
if (x.currentState === PENDING) {
x.then(function (value) {
// 再次调用该函数是为了确认 x resolve 的
// 参数是什么类型,如果是基本类型就再次 resolve
// 把值传给下个 then
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 规范 2.3.3.3.3
// reject 或者 resolve 其中一个执行过得话,忽略其他的
let called = false;
// 规范 2.3.3,判断 x 是否为对象或者函数
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 规范 2.3.3.2,如果不能取出 then,就 reject
try {
// 规范 2.3.3.1
let then = x.then;
// 如果 then 是函数,调用 x.then
if (typeof then === "function") {
// 规范 2.3.3.3
then.call(
x,
y => {
if (called) return;
called = true;
// 规范 2.3.3.3.1
resolutionProcedure(promise2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
reject(e);
}
);
} else {
// 规范 2.3.3.4
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 规范 2.3.4,x 为基本类型
resolve(x);
}
}
复制代码
实现 Promisify
const fs = require('fs')
const path = require('path')
// node中使用
// const fs = require('fs').promises 12.18版
// const promisify = require('util').promisify
// 包装node api promise化 典型的高级函数
const promisify = fn=>{
return (...args)=>{
return new Promise((resolve,reject)=>{
fn(...args, (err,data)=>{
if(err) {
reject(err)
}
resolve(data)
})
})
}
}
// const read = promisify(fs.readFile)
// read(path.join(__dirname, './promise.js'), 'utf8').then(d=>{
// console.log(d)
// })
// promise化node所有api
const promisifyAll = target=>{
Reflect.ownKeys(target).forEach(key=>{
if(typeof target[key] === 'function') {
target[key+'Async'] = promisify(target[key])
}
})
return target
}
// promise化fs下的函数
const promisifyNew = promisifyAll(fs)
promisifyNew.readFileAsync(path.join(__dirname, './promise.js'), 'utf8').then(d=>{
console.log(d)
})
module.exports = {
promisify,
promisifyAll
}
复制代码
完整实现 Promises/A+规范
/**
* Promises/A+规范 实现一个promise
* https://promisesaplus.com/
*/
const EMUM = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED'
}
// x 返回值
// promise2 then的时候new的promise
// promise2的resolve, reject
const resolvePromise = (x, promise2, resolve, reject)=>{
// 解析promise的值解析promise2是成功还是失败 传递到下层then
if(x === promise2) {
reject(new TypeError('类型错误'))
}
// 这里的x如果是一个promise的话 可能是其他的promise,可能调用了成功 又调用了失败
// 防止resolve的时候 又throw err抛出异常到reject了
let called
// 如果x是promise 那么就采用他的状态
// 有then方法是promise
if(typeof x === 'object' && typeof x!== null || typeof x === 'function') {
// x是对象或函数
try {
let then = x.then // 缓存,不用多次取值
if(typeof then === 'function') {
// 是promise,调用then方法里面有this,需要传入this为x才能取到then方法里面的值this.value
then.call(x, y=>{// 成功
// y值可能也是一个promise 如resolve(new Promise()) 此时的y==new Promise()
// 递归解析y,直到拿到普通的值resolve(x出去)
if(called) return;
called = true;
resolvePromise(y, promise2, resolve, reject)
},r=>{// 一旦失败直接失败
if(called) return;
called = true;
reject(r)
})
} else {
// 普通对象不是promise
resolve(x)
}
} catch (e) {
// 对象取值可能报错,用defineProperty定义get 抛出异常
if(called) return;
called = true;
reject(e)
}
} else {
// x是普通值
resolve(x) // 直接成功
}
}
class myPromise {
constructor(executor) {
this.status = EMUM.PENDING // 当前状态
this.value = undefined // resolve接收值
this.reason = undefined // reject失败返回值
/**
* 同一个promise可以then多次(发布订阅模式)
* 调用then时 当前状态是等待态,需要将当前成功或失败的回调存放起来(订阅)
* 调用resolve时 将订阅函数进行执行(发布)
*/
// 成功队列
this.onResolvedCallbacks = []
// 失败队列
this.onRejectedCallbacks = []
const resolve = value =>{
// 如果value是一个promise,需要递归解析
// 如 myPromise.resolve(new myPromise()) 需要解析value
if(value instanceof myPromise) {
// 不停的解析 直到值不是promise
return value.then(resolve,reject)
}
if(this.status === EMUM.PENDING) {
this.status = EMUM.FULFILLED
this.value = value
this.onResolvedCallbacks.forEach(fn=>fn())
}
}
const reject = reason =>{
if(this.status === EMUM.PENDING) {
this.status = EMUM.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn=>fn())
}
}
try {
executor(resolve,reject)
} catch(e) {
reject(e)
}
}
then(onFulFilled, onRejected) {
// 透传 处理默认不传的情况
// new Promise((resolve,reject)=>{
// resolve(1)
// }).then().then().then(d=>{})
// new Promise((resolve,reject)=>{
// resolve(1)
// }).then(v=>v).then(v=>v).then(d=>{})
// new Promise((resolve,reject)=>{
// reject(1)
// }).then().then().then(null, e=>{console.log(e)})
// new Promise((resolve,reject)=>{
// reject(1)
// }).then(null,e=>{throw e}).then(null,e=>{throw e}).then(null,e=>{console.log(e)})
onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}
// 调用then 创建一个新的promise
let promise2 = new myPromise((resolve,reject)=>{
// 根据value判断是resolve 还是reject value也可能是promise
if(this.status === EMUM.FULFILLED) {
setTimeout(() => {
try {
// 成功回调结果
let x = onFulFilled(this.value)
// 解析promise
resolvePromise(x, promise2,resolve,reject)
} catch (error) {
reject(error)
}
}, 0);
}
if(this.status === EMUM.REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
// 解析promise
resolvePromise(x, promise2,resolve,reject)
} catch (error) {
reject(error)
}
}, 0);
}
// 用户还未调用resolve或reject方法
if(this.status === EMUM.PENDING) {
this.onResolvedCallbacks.push(()=>{
try {
let x = onFulFilled(this.value)
// 解析promise
resolvePromise(x, promise2,resolve,reject)
} catch (error) {
reject(error)
}
})
this.onRejectedCallbacks.push(()=>{
try {
let x = onRejected(this.reason)
// 解析promise
resolvePromise(x, promise2,resolve,reject)
} catch (error) {
reject(error)
}
})
}
})
return promise2
}
catch(errCallback) {
// 等同于没有成功,把失败放进去而已
return this.then(null, errCallback)
}
// myPromise.resolve 具备等待功能的 如果参数的promise会等待promise解析完毕在向下执行
static resolve(val) {
return new myPromise((resolve,reject)=>{
resolve(val)
})
}
// myPromise.reject 直接将值返回
static reject(reason) {
return new myPromise((resolve,reject)=>{
reject(reason)
})
}
// finally传入的函数 无论成功或失败都执行
// Promise.reject(100).finally(()=>{console.log(1)}).then(d=>console.log('success',d)).catch(er=>console.log('faild',er))
// Promise.reject(100).finally(()=>new Promise()).then(d=>console.log(d)).catch(er=>)
finally(callback) {
return this.then((val)=>{
return myPromise.resolve(callback()).then(()=>val)
},(err)=>{
return myPromise.resolve(callback()).then(()=>{throw err})
})
}
// Promise.all
static all(values) {
return new myPromise((resolve,reject)=>{
let resultArr = []
let orderIndex = 0
const processResultByKey = (value,index)=>{
resultArr[index] = value
// 处理完全部
if(++orderIndex === values.length) {
resolve(resultArr) // 处理完成的结果返回去
}
}
for (let i = 0; i < values.length; i++) {
const value = values[i];
// 是promise
if(value && typeof value.then === 'function') {
value.then((val)=>{
processResultByKey(val,i)
},reject)
} else {
// 不是promise情况
processResultByKey(value,i)
}
}
})
}
static race(promises) {
// 采用最新成功或失败的作为结果
return new myPromise((resolve,reject)=>{
for (let i = 0; i < promises.length; i++) {
let val = promises[i]
if(val && typeof val.then === 'function') {
// 任何一个promise先调用resolve或reject就返回结果了 也就是返回执行最快的那个promise的结果
val.then(resolve,reject)
}else{
// 普通值
resolve(val)
}
}
})
}
}
/**
* =====测试用例-====
*/
// let promise1 = new myPromise((resolve,reject)=>{
// setTimeout(() => {
// resolve('成功')
// }, 900);
// })
// promise1.then(val=>{
// console.log('success', val)
// },reason=>{
// console.log('fail', reason)
// })
/**
* then的使用方式 普通值意味不是promise
*
* 1、then中的回调有两个方法 成功或失败 他们的结果返回(普通值)会传递给外层的下一个then中
* 2、可以在成功或失败中抛出异常,走到下一次then的失败中
* 3、返回的是一个promsie,那么会用这个promise的状态作为结果,会用promise的结果向下传递
* 4、错误处理,会默认先找离自己最新的错误处理,找不到就向下查找,找打了就执行
*/
// read('./name.txt').then(data=>{
// return '123'
// }).then(data=>{
// }).then(null,err=>{
// })
// // .catch(err=>{ // catch就是没有成功的promise
// // })
/**
* promise.then实现原理:通过每次返回一个新的promise来实现(promise一旦成功就不能失败,失败就不能成功)
*
*/
// function read(data) {
// return new myPromise((resolve,reject)=>{
// setTimeout(() => {
// resolve(new myPromise((resolve,reject)=>resolve(data)))
// }, 1000);
// })
// }
// let promise2 = read({name: 'poetry'}).then(data=>{
// return data
// }).then().then().then(data=>{
// console.log(data,'-data-')
// },(err)=>{
// console.log(err,'-err-')
// })
// finally测试
// myPromise
// .resolve(100)
// .finally(()=>{
// return new myPromise((resolve,reject)=>setTimeout(() => {
// resolve(100)
// }, 100))
// })
// .then(d=>console.log('finally success',d))
// .catch(er=>console.log(er, 'finally err'))
/**
* promise.all 测试
*
* myPromise.all 解决并发问题 多个异步并发获取最终的结果
*/
// myPromise.all([1,2,3,4,new myPromise((resolve,reject)=>{
// setTimeout(() => {
// resolve('ok1')
// }, 1000);
// }),new myPromise((resolve,reject)=>{
// setTimeout(() => {
// resolve('ok2')
// }, 1000);
// })]).then(d=>{
// console.log(d,'myPromise.all.resolve')
// }).catch(err=>{
// console.log(err,'myPromise.all.reject')
// })
// 实现promise中断请求
let promise = new Promise((resolve,reject)=>{
setTimeout(() => {
// 模拟接口调用 ajax调用超时
resolve('成功')
}, 10000);
})
function promiseWrap(promise) {
// 包装一个promise 可以控制原来的promise是成功 还是失败
let abort
let newPromsie = new myPromise((resolve,reject)=>{
abort = reject
})
// 只要控制newPromsie失败,就可以控制被包装的promise走向失败
// Promise.race 任何一个先成功或者失败 就可以获得结果
let p = myPromise.race([promise, newPromsie])
p.abort = abort
return p
}
let newPromise = promiseWrap(promise)
setTimeout(() => {
// 超过3秒超时
newPromise.abort('请求超时')
}, 3000);
newPromise.then(d=>{
console.log('d',d)
}).catch(err=>{
console.log('err',err)
})
// 使用promises-aplus-tests 测试写的promise是否规范
// 全局安装 cnpm i -g promises-aplus-tests
// 命令行执行 promises-aplus-tests promise.js
// 测试入口 产生延迟对象
myPromise.defer = myPromise.deferred = function () {
let dfd = {}
dfd.promise = new myPromise((resolve,reject)=>{
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// 延迟对象用户
// ![](http://img-repo.poetries.top/images/20210509172817.png)
// promise解决嵌套问题
// function readData(url) {
// let dfd = myPromise.defer()
// fs.readFile(url, 'utf8', function (err,data) {
// if(err) {
// dfd.reject()
// }
// dfd.resolve(data)
// })
// return dfd.promise
// }
// readData().then(d=>{
// return d
// })
module.exports = myPromise
复制代码
实现事件总线结合 Vue 应用
Event Bus
(Vue、Flutter 等前端框架中有出镜)和 Event Emitter
(Node 中有出镜)出场的“剧组”不同,但是它们都对应一个共同的角色—— 全局事件总线 。
全局事件总线,严格来说不能说是观察者模式,而是发布-订阅模式。它在我们日常的业务开发中应用非常广。
如果只能选一道题,那这道题一定是 Event Bus/Event Emitter
的代码实现——我都说这么清楚了,这个知识点到底要不要掌握、需要掌握到什么程度,就看各位自己的了。
在 Vue 中使用 Event Bus 来实现组件间的通讯
Event Bus/Event Emitter
作为全局事件总线,它起到的是一个沟通桥梁的作用。我们可以把它理解为一个事件中心,我们所有事件的订阅/发布都不能由订阅方和发布方“私下沟通”,必须要委托这个事件中心帮我们实现。
在 Vue 中,有时候 A 组件和 B 组件中间隔了很远,看似没什么关系,但我们希望它们之间能够通信。这种情况下除了求助于 Vuex
之外,我们还可以通过 Event Bus
来实现我们的需求。
创建一个 Event Bus
(本质上也是 Vue 实例)并导出:
const EventBus = new Vue()
export default EventBus
复制代码
在主文件里引入EventBus
,并挂载到全局:
import bus from 'EventBus的文件路径'
Vue.prototype.bus = bus
复制代码
订阅事件:
// 这里func指someEvent这个事件的监听函数
this.bus.$on('someEvent', func)
复制代码
发布(触发)事件:
// 这里params指someEvent这个事件被触发时回调函数接收的入参
this.bus.$emit('someEvent', params)
复制代码
大家会发现,整个调用过程中,没有出现具体的发布者和订阅者(比如上面的PrdPublisher
和DeveloperObserver
),全程只有bus
这个东西一个人在疯狂刷存在感。这就是全局事件总线的特点——所有事件的发布/订阅操作,必须经由事件中心,禁止一切“私下交易”!
下面,我们就一起来实现一个Event Bus
(注意看注释里的解析):
class EventEmitter {
constructor() {
// handlers是一个map,用于存储事件与回调之间的对应关系
this.handlers = {}
}
// on方法用于安装事件监听器,它接受目标事件名和回调函数作为参数
on(eventName, cb) {
// 先检查一下目标事件名有没有对应的监听函数队列
if (!this.handlers[eventName]) {
// 如果没有,那么首先初始化一个监听函数队列
this.handlers[eventName] = []
}
// 把回调函数推入目标事件的监听函数队列里去
this.handlers[eventName].push(cb)
}
// emit方法用于触发目标事件,它接受事件名和监听函数入参作为参数
emit(eventName, ...args) {
// 检查目标事件是否有监听函数队列
if (this.handlers[eventName]) {
// 如果有,则逐个调用队列里的回调函数
this.handlers[eventName].forEach((callback) => {
callback(...args)
})
}
}
// 移除某个事件回调队列里的指定回调函数
off(eventName, cb) {
const callbacks = this.handlers[eventName]
const index = callbacks.indexOf(cb)
if (index !== -1) {
callbacks.splice(index, 1)
}
}
// 为事件注册单次监听器
once(eventName, cb) {
// 对回调函数进行包装,使其执行完毕自动被移除
const wrapper = (...args) => {
cb.apply(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}
复制代码
在日常的开发中,大家用到EventBus/EventEmitter
往往提供比这五个方法多的多的多的方法。但在面试过程中,如果大家能够完整地实现出这五个方法,已经非常可以说明问题了,因此楼上这个EventBus
希望大家可以熟练掌握。学有余力的同学
实现 Object.is
Object.is
不会转换被比较的两个值的类型,这点和===
更为相似,他们之间也存在一些区别
Object.is = function (x, y) {
if (x === y) {
// 当前情况下,只有一种情况是特殊的,即 +0 -0
// 如果 x !== 0,则返回true
// 如果 x === 0,则需要判断+0和-0,则可以直接使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
return x !== 0 || 1 / x === 1 / y;
}
// x !== y 的情况下,只需要判断是否为NaN,如果x!==x,则说明x是NaN,同理y也一样
// x和y同时为NaN时,返回true
return x !== x && y !== y;
};
复制代码
参考:前端手写面试题详细解答
实现一个 compose 函数
组合多个函数,从右到左,比如:compose(f, g, h)
最终得到这个结果 (...args) => f(g(h(...args))).
题目描述:实现一个 compose
函数
// 用法如下:
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11
复制代码
实现代码如下
function compose(...funcs) {
if (!funcs.length) return (v) => v;
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => {
return (...args) => a(b(...args)))
}
}
复制代码
compose
创建了一个从右向左执行的数据流。如果要实现从左到右的数据流,可以直接更改compose
的部分代码即可实现
实现 every 方法
Array.prototype.myEvery=function(callback, context = window){
var len=this.length,
flag=true,
i = 0;
for(;i < len; i++){
if(!callback.apply(context,[this[i], i , this])){
flag=false;
break;
}
}
return flag;
}
// var obj = {num: 1}
// var aa=arr.myEvery(function(v,index,arr){
// return v.num>=12;
// },obj)
// console.log(aa)
复制代码
实现 Ajax
步骤
了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。
实现一个双向绑定
defineProperty 版本
// 数据
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
// 数据变化 --> 修改视图
set(newVal) {
input.value = newVal;
span.innerHTML = newVal;
}
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
data.text = e.target.value;
});
复制代码
proxy 版本
// 数据
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {
set(target, key, value) {
target[key] = value;
// 数据变化 --> 修改视图
input.value = value;
span.innerHTML = value;
return value;
}
};
const proxy = new Proxy(data, handler);
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
proxy.text = e.target.value;
});
复制代码
实现 redux-thunk
redux-thunk
可以利用 redux
中间件让 redux
支持异步的 action
// 如果 action 是个函数,就调用这个函数
// 如果 action 不是函数,就传给下一个中间件
// 发现 action 是函数就调用
const thunk = ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
export default thunk
复制代码
实现迭代器生成函数
我们说迭代器对象全凭迭代器生成函数帮我们生成。在ES6
中,实现一个迭代器生成函数并不是什么难事儿,因为 ES6 早帮我们考虑好了全套的解决方案,内置了贴心的 生成器 (Generator
)供我们使用:
// 编写一个迭代器生成函数
function *iteratorGenerator() {
yield '1号选手'
yield '2号选手'
yield '3号选手'
}
const iterator = iteratorGenerator()
iterator.next()
iterator.next()
iterator.next()
复制代码
丢进控制台,不负众望:
写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背后的实现逻辑更感兴趣。下面我们要做的,不仅仅是写一个迭代器对象,而是用ES5
去写一个能够生成迭代器对象的迭代器生成函数(解析在注释里):
// 定义生成器函数,入参是任意集合
function iteratorGenerator(list) {
// idx记录当前访问的索引
var idx = 0
// len记录传入集合的长度
var len = list.length
return {
// 自定义next方法
next: function() {
// 如果索引还没有超出集合长度,done为false
var done = idx >= len
// 如果done为false,则可以继续取值
var value = !done ? list[idx++] : undefined
// 将当前值与遍历是否完毕(done)返回
return {
done: done,
value: value
}
}
}
}
var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])
iterator.next()
iterator.next()
iterator.next()
复制代码
此处为了记录每次遍历的位置,我们实现了一个闭包,借助自由变量来做我们的迭代过程中的“游标”。
运行一下我们自定义的迭代器,结果符合预期:
实现 call 方法
call 做了什么:
将函数设为对象的属性
执行和删除这个函数
指定this
到函数并传入给定参数执行函数
如果不传入参数,默认指向为 window
// 模拟 call bar.mycall(null);
//实现一个call方法:
// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {
if (typeof this !== "function") {
throw new Error('type error')
}
// this-->func context--> obj args--> 传递过来的参数
// 在context上加一个唯一值不影响context上的属性
let key = Symbol('key')
context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法
// let args = [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组
// 绑定参数 并执行函数
let result = context[key](...args);
// 清除定义的this 不删除会导致context属性越来越多
delete context[key];
// 返回结果
return result;
};
复制代码
//用法:f.call(obj,arg1)
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:1
}
f.myCall(obj,1,2) //否则this指向window
复制代码
实现 ES6 的 extends
function B(name){
this.name = name;
};
function A(name,age){
//1.将A的原型指向B
Object.setPrototypeOf(A,B);
//2.用A的实例作为this调用B,得到继承B之后的实例,这一步相当于调用super
Object.getPrototypeOf(A).call(this, name)
//3.将A原有的属性添加到新实例上
this.age = age;
//4.返回新实例对象
return this;
};
var a = new A('poetry',22);
console.log(a);
复制代码
实现 apply 方法
思路: 利用this
的上下文特性。apply
其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) {
// this-->func context--> obj args--> 传递过来的参数
// 在context上加一个唯一值不影响context上的属性
let key = Symbol('key')
context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法
// let args = [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组
let result = context[key](...args); // 这里和call传参不一样
// 清除定义的this 不删除会导致context属性越来越多
delete context[key];
// 返回结果
return result;
}
复制代码
// 使用
function f(a,b){
console.log(a,b)
console.log(this.name)
}
let obj={
name:'张三'
}
f.myApply(obj,[1,2]) //arguments[1]
复制代码
实现一个迭代器生成函数
ES6 对迭代器的实现
JS 原生的集合类型数据结构,只有Array
(数组)和Object
(对象);而ES6
中,又新增了Map
和Set
。四种数据结构各自有着自己特别的内部实现,但我们仍期待以同样的一套规则去遍历它们,所以ES6
在推出新数据结构的同时也推出了一套 统一的接口机制 ——迭代器(Iterator
)。
ES6
约定,任何数据结构只要具备Symbol.iterator
属性(这个属性就是Iterator
的具体实现,它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历——准确地说,是被for...of...
循环和迭代器的 next 方法遍历。 事实上,for...of...
的背后正是对next
方法的反复调用。
在 ES6 中,针对Array
、Map
、Set
、String
、TypedArray
、函数的 arguments
对象、NodeList
对象这些原生的数据结构都可以通过for...of...
进行遍历。原理都是一样的,此处我们拿最简单的数组进行举例,当我们用for...of...
遍历数组时:
const arr = [1, 2, 3]
const len = arr.length
for(item of arr) {
console.log(`当前元素是${item}`)
}
复制代码
之所以能够按顺序一次一次地拿到数组里的每一个成员,是因为我们借助数组的Symbol.iterator
生成了它对应的迭代器对象,通过反复调用迭代器对象的next
方法访问了数组成员,像这样:
const arr = [1, 2, 3]
// 通过调用iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()
// 对迭代器对象执行next,就能逐个访问集合的成员
iterator.next()
iterator.next()
iterator.next()
复制代码
丢进控制台,我们可以看到next
每次会按顺序帮我们访问一个集合成员:
而for...of...
做的事情,基本等价于下面这通操作:
// 通过调用iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()
// 初始化一个迭代结果
let now = { done: false }
// 循环往外迭代成员
while(!now.done) {
now = iterator.next()
if(!now.done) {
console.log(`现在遍历到了${now.value}`)
}
}
复制代码
可以看出,for...of...
其实就是iterator
循环调用换了种写法。在 ES6 中我们之所以能够开心地用for...of...
遍历各种各种的集合,全靠迭代器模式在背后给力。
ps:此处推荐阅读迭代协议 (opens new window),相信大家读过后会对迭代器在 ES6 中的实现有更深的理解。
数组去重方法汇总
首先:我知道多少种去重方式
1. 双层 for 循环
function distinct(arr) {
for (let i=0, len=arr.length; i<len; i++) {
for (let j=i+1; j<len; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
// splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一
len--;
j--;
}
}
}
return arr;
}
复制代码
思想: 双重 for
循环是比较笨拙的方法,它实现的原理很简单:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) {
let arr = a.concat(b);
return arr.filter((item, index)=> {
//return arr.indexOf(item) === index
return arr.includes(item)
})
}
复制代码
思想: 利用indexOf
检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素
3. ES6 中的 Set 去重
function distinct(array) {
return Array.from(new Set(array));
}
复制代码
思想: ES6 提供了新的数据结构 Set,Set 结构的一个特性就是成员值都是唯一的,没有重复的值。
4. reduce 实现对象数组去重复
var resources = [
{ name: "张三", age: "18" },
{ name: "张三", age: "19" },
{ name: "张三", age: "20" },
{ name: "李四", age: "19" },
{ name: "王五", age: "20" },
{ name: "赵六", age: "21" }
]
var temp = {};
resources = resources.reduce((prev, curv) => {
// 如果临时对象中有这个名字,什么都不做
if (temp[curv.name]) {
}else {
// 如果临时对象没有就把这个名字加进去,同时把当前的这个对象加入到prev中
temp[curv.name] = true;
prev.push(curv);
}
return prev
}, []);
console.log("结果", resources);
复制代码
这种方法是利用高阶函数 reduce
进行去重, 这里只需要注意initialValue
得放一个空数组[],不然没法push
评论