Skip to content
On this page

promise 篇

如何判断是否时 promise

什么是 promise?

只要里面有 then 函数,那么就是 promise,传统做法:value instanceof Promise,但 这样会失去互操作性

function isPromiseLike(value){
    if(typeof value === 'object' || typeof value === 'function'){
        return typeof value.then === 'function';
    }
    return false;
}

实现 promise

分析一下基本原理:

  1. Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
  2. Promise 会有三种状态
    • Pending 等待
    • Fulfilled 完成
    • Rejected 失败
  3. 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便 不可二次修改;
  4. Promise 中使用 resolve 和 reject 两个函数来更改状态;
  5. then 方法内部做的事情就是状态判断
    • 如果状态是成功,调用成功回调函数
    • 如果状态是失败,调用失败回调函数

实现代码

// MyPromise.js

// 先定义三个常量表示状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";


// 新建 MyPromise 类
class MyPromise {
    constructor(executor) {
        // executor 是一个执行器,进入会立即执行
        // 并传入resolve和reject方法
        executor(this.resolve, this.reject);
    }

    // 储存状态的变量,初始值是 pending
    status = PENDING;

    // resolve和reject为什么要用箭头函数?
    // 如果直接调用的话,普通函数this指向的是window或者undefined
    // 用箭头函数就可以让this指向当前实例对象
    // 成功之后的值
    value = null;
    // 失败之后的原因
    reason = null;

    // 存储成功回调函数
    // onFulfilledCallback = null;
    onFulfilledCallbacks = [];
    // 存储失败回调函数
    // onRejectedCallback = null;
    onRejectedCallbacks = [];

    // 更改成功后的状态
    resolve = (value) => {
        // 只有状态是等待,才执行状态修改
        if (this.status === PENDING) {
            // 状态修改为成功
            this.status = FULFILLED;
            // 保存成功之后的值
            this.value = value;
            // ==== 新增 ====
            // resolve里面将所有成功的回调拿出来执行
            while (this.onFulfilledCallbacks.length) {
                // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
                this.onFulfilledCallbacks.shift()(value);
            }
        }
    };

    // 更改失败后的状态
    // 更改失败后的状态
    reject = (reason) => {
        // 只有状态是等待,才执行状态修改
        if (this.status === PENDING) {
            // 状态成功为失败
            this.status = REJECTED;
            // 保存失败后的原因
            this.reason = reason;
            // ==== 新增 ====
            // resolve里面将所有失败的回调拿出来执行
            while (this.onRejectedCallbacks.length) {
                this.onRejectedCallbacks.shift()(reason)
            }
        }
    }


    then(onFulfilled, onRejected) {
        // 判断状态
        if (this.status === FULFILLED) {
            // 调用成功回调,并且把值返回
            onFulfilled(this.value);
        } else if (this.status === REJECTED) {
            // 调用失败回调,并且把原因返回
            onRejected(this.reason);
        } else if (this.status === PENDING) {
            // ==== 新增 ====
            // 因为不知道后面状态的变化,这里先将成功回调和失败回调存储起来
            // 等待后续调用
            this.onFulfilledCallbacks.push(onFulfilled);
            this.onRejectedCallbacks.push(onRejected);
        }
    }
}

module.exports = MyPromise;

实现结果

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 2000);
})

promise.then(value => {
    console.log(1)
    console.log('resolve', value)
})

promise.then(value => {
    console.log(2)
    console.log('resolve', value)
})

promise.then(value => {
    console.log(3)
    console.log('resolve', value)
})

// 1
// resolve success
// 2
// resolve success
// 3
// resolve success

实现 promise.all

思路:

  1. 判断传入的是数组类型
  2. 定义数组保存每一个 promise 的返回结果,同时开始计数,当数量等于传入的数组长度 ,则执行完成
  3. 如果遇到 reject,直接返回 reject 的失败原因,如果全部成功,则返回结果数组

实现方法

function PromiseAll(promises) {
    return new Promise((resolve, reject) => {
        // 判断传入的必须是数组
        if (!Array.isArray(promises)) {
            throw new TypeError("promises must be an array")
        }
        let result = [] // 保存结果
        let count = 0  // 用于判断是否全部执行完
        promises.forEach((promise, index) => {
            promise.then((res) => {
                console.log("res", res);
                result[index] = res
                count++
                count === promises.length && resolve(result)  //全部执行完后输出结果
            }, (err) => {
                reject(err)
            })
        })
    })
}

module.exports = PromiseAll;

实现结果

const PromiseAll = require('./promiseAll')

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success1')
    }, 5000);
})

const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success2')
    }, 1000);
})

const promise3 = new Promise((resolve, reject) => {
    reject('error')
})

PromiseAll([promise1, promise2, promise3]).then((res) => {
    console.log("全部执行完成", res);
}).catch((error) => {
    console.log("执行异常", error)
})

//执行结果,如果没有 reject,输出 ['success1','success1'],reject 输出 error

实现 promise.finally

实现代码

// 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,
// 它仅用于无论最终结果如何都要执行的情况
// 并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then

Promise.prototype.myFinally = function (cb) {
    // 此处的 this 是一个 pending状态的 promise,resolve(cb())会执行 finally 的代码
    // promise后有可能还是一个promise,finally需要原封不动的返回 return结果
    return this.then((value) => {
        return Promise.resolve(cb()).then(function () {
            return value
        })
    }, (err) => {
        return Promise.resolve(cb()).then(function () {
            throw err
        })
    })
}

const promise = new Promise((resolve, reject) => {
    resolve('success')
})

promise.then((res) => {
    return new Promise((resolve) => {
        resolve('success1')
    })
}).myFinally(() => {
    console.log("执行finally");
}).then((res) => {
    console.log("finally后,原封不动返回值", res);
})

// 输出:执行finally finally后,原封不动返回值 success1

实现 promise.allSettled

实现代码

function allSettled(promises) {
    if (promises.length === 0) return Promise.resolve([])

    const _promises = promises.map(
        item => item instanceof Promise ? item : Promise.resolve(item)
    )
    return new Promise((resolve, reject) => {
        const result = []
        let unSettledPromiseCount = _promises.length

        _promises.forEach((promise, index) => {
            promise.then((value) => {
                result[index] = {
                    status: 'fulfilled',
                    value
                }

                unSettledPromiseCount -= 1
                // resolve after all are settled
                if (unSettledPromiseCount === 0) {
                    resolve(result)
                }
            }, (reason) => {
                result[index] = {
                    status: 'rejected',
                    reason
                }

                unSettledPromiseCount -= 1
                // resolve after all are settled
                if (unSettledPromiseCount === 0) {
                    resolve(result)
                }
            })
        })

    })
}

module.exports = allSettled;

// 思路:
// 当所有的 promise 都执行完了把结果组成数组,包含状态以及 value。
// 首先判断传入的 promise 数组长度,为0直接返回空数组
// 如果 promise 中没有 reslove 或 reject 的,我们只处理 reslove 或 reject 的,所以没有会直接跳出不执行,停止
// 通过计数 -1 ,以及包装结果,返回全部正常结束的最终结果

实现结果

const promiseAllSettled = require('./promiseAllSettled')

const promise1 = new Promise((resolve, reject) => {
    resolve('success1')
})

const promise2 = new Promise((resolve, reject) => {
    // resolve('success2')
    console.log("sdf");
})

const promise3 = new Promise((resolve, reject) => {
    reject('error')

})

promiseAllSettled([promise1, promise2, promise3]).then((res) => {
    console.log("AllSettled全部执行完成", res);
})

// 执行结果
//  全部执行完成 [
//     { status: 'fulfilled', value: 'success1' },
//     { status: 'fulfilled', value: 'success2' },
//     { status: 'rejected', reason: 'error' }
//   ]

实现 promise.any

实现方法

function promiseAny(promiseArr) {
    let index = 0
    return new Promise((resolve, reject) => {
        if (promiseArr.length === 0) return
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                resolve(val)

            }, err => {
                index++
                if (index === promiseArr.length) {
                    reject(new AggregateError('All promises were rejected'))
                }
            })
        })
    })
}

module.exports = promiseAny;

// 思路:
// 计数,当遇到成功状态,就返回这个结果,当全部都失败,就返回失败信息

实现结果

// 执行 promise 数组,当遇到一个 成功状态,就返回这个成功结果,如果全部失败,就返回 失败提示// 执行所有的 promise,返回所有的 promise 状态与结果,reject不影响过程

const promiseAny = require('./promiseAny')

const promise1 = new Promise((resolve, reject) => {
    resolve('success1')
})

const promise2 = new Promise((resolve, reject) => {
    resolve('success2')
})

const promise3 = new Promise((resolve, reject) => {
    reject('error')
})

promiseAny([promise1, promise2, promise3]).then((res) => {
    console.log("promiseAny结果", res);
})

// 执行结果
// promiseAny结果 success1

array 数组篇

实现数组去重

//ES6 提供的 Set 去重
function unique(arr) {
    const result = new Set(arr);
    return [...result];
    // return Array.from(result); //不兼容ie
    //使用扩展运算符将Set数据结构转为数组
}

// filter 配合 indexOf,
//使用 indexOf 获取当前内容的下标,这个下标必须跟当前 index 相等,不想等说明出现在别的地方,是重复的需过滤
function unique(arr) {
    return arr.filter(function (item, index, arr) {
        return arr.indexOf(item) === index;
    })
}

// reduce
let arr = [1, 2, 2, 4, null, null].reduce((accumulator, current) => {
    return accumulator.includes(current) ? accumulator : accumulator.concat(current);
}, []);

foreach

Array.prototype.myForEach = function (callbackFn) {
    // 判断this是否合法
    if (this === null || this === undefined) {
        throw new TypeError("Cannot read property 'myForEach' of null");
    }
    // 判断callbackFn是否合法
    if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {
        throw new TypeError(callbackFn + ' is not a function')
    }
    // 取到执行方法的数组对象和传入的this对象
    var _arr = this, thisArg = arguments[1] || window;
    for (var i = 0; i < _arr.length; i++) {
        // 执行回调函数
        callbackFn.call(thisArg, _arr[i], i, _arr);
    }
}

reduce

先理解 reduce 的用法:

array.reduce(function(accumulator, currentValue, currentIndex, array), initialValue)

接受四个参数:

  • accumulator 累加器,当前执行的结果
  • currentValue 当前执行的值
  • currentIndex 当前执行的 index
  • array 正在被执行的数组
  • initialValue 初始值

必须要 retuen,这样后续才能获取之前的值,继续进行操作

看例子:

// 计数
// 无初始值
let total = [1, 2, 3, 4].reduce((accumulator, current) => accumulator += current); // 10

// 有初始值
let total = [1, 2, 3, 4].reduce((accumulator, current) => accumulator += current, 90); // 100

// 数组去重
let arr = [1, 2, 2, 4, null, null].reduce((accumulator, current) => {
    return accumulator.includes(current) ? accumulator : accumulator.concat(current);
}, []);

实现代码

Array.prototype.myReduce = function (callbackFn) {
    var _arr = this, accumulator = arguments[1];
    var i = 0;
    // 判断是否传入初始值
    if (accumulator === undefined) {
        // 没有初始值的空数组调用reduce会报错
        if (_arr.length === 0) {
            throw new Error('initVal and Array.length>0 need one')
        }
        // 初始值赋值为数组第一个元素
        accumulator = _arr[i];
        i++;
    }
    for (; i < _arr.length; i++) {
        // 计算结果赋值给初始值
        accumulator = callbackFn(accumulator, _arr[i], i, _arr)
    }
    return accumulator;
}

// 思路:
// 判断是否传入初始值,如果没有传入初始值并且,第一个数组还是空的,就返回异常,否则将数组的第一项作为初始值
// 开始循环数组,迭代执行传入的方法,传入四个值
// 接受四个参数:
// - accumulator 累加器,当前执行的结果
// - currentValue 当前执行的值
// - currentIndex 当前执行的index
// - array 正在被执行的数组
// - initialValue 初始值

节流防抖

节流: 多次触发同一个函数,同一段时间内只执行一次,所以节流会稀释函数的执行频率

可以看出节流的主要原理就是利用时间差(当前和上次执行)来过滤中间过程触发的函数 执行。控制是否在开始时会立即触发一次,及最后一次触发是否执行,添加取消的功能

// 节流: 多次触发同一个函数,同一段时间内只执行一次,所以节流会稀释函数的执行频率。触发后只在乎时间,时间到了才执行
// 如果事件不停的触发,它会在规定的时间内一直触发

// fn:执行的方法,wait:等待的时间,immediate:第一次进入是否执行

// 设置上次执行毫秒数初始值 0 ,设置 当前毫秒数 now,通过 now - previous 是否大于传入的等待毫秒数控制方法执行

// 每次点击进入
function throttle(fn, wait, immediate = false) {
    let timer; // 定时器
    let previous = 0; // 上次执行毫秒数初始化 0

    const throttled = function (...args) {
        // args 为执行函数传入的参数

        // 清除上一个 timer
        if (timer) clearTimeout(timer);

        const now = +new Date();
        // 如果第一次进来:previous === 0 并且不需要立即执行 immediate === false
        // 设置当当前毫秒数等于上次执行毫秒数,相减等于0,肯定小于等待时间 wait,所以不能立即执行。
        if (immediate === false && !previous) previous = now; // 控制不能立即执行

        // now - previous 是否大于传入的等待毫秒数控制方法执行
        if (now - previous > wait) {
            fn.apply(this, args);
            previous = now; // 执行完将上次执行毫秒数设置为当前毫秒数
        }
    }

    // 提供马上停止的方法
    throttled.cancel = () => {
        clearTimeout(timer);
        timer = null;
        previous = 0;
    }

    return throttled;
}

防抖: 按最后一次算。比如说“停止输入 5s 后才发送请求”,防止多次点击 (比较常用)

可以看出 debounce 函数的实现原理就是通过计时器延迟函数执行,短时间内再次触发时 重置并添加新计时器。

// 防抖: 按最后一次算。比如说“停止输入5s后才发送请求”,防止多次点击  (比较常用)
// 你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行
// 总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行。

// 关键点就是,进来清除上一次的定时器,重新设置定时器

// 可以看出debounce函数的实现原理就是通过计时器延迟函数执行,短时间内再次触发时重置并添加新计时器。

//第一次触发可以立即执行,并且有取消功能
function debounce(fn, wait = 1000, immediate = false) {
    let timer = null;

    function debounced(...args) {
        // 每次进来都重置计时器,当没有触发了,才真正执行,
        // 或者是 延迟时间 比 触发间隔短 执行
        timer && clearTimeout(timer);

        // 首次立即执行
        if (immediate && !timer) {
            fn.apply(this, ...args);
            // 立即执行完,再过 wait 就不执行了
            timer = setTimeout(() => {
                timer = null;
            }, wait);
            return;
        }

        // 新计时器
        timer = setTimeout(() => {
            fn.apply(this, ...args);
            timer = null;
        }, wait);
    }

    debounced.cancel = () => {
        clearTimeout(timer);
        timer = null;
    };

    return debounced;
}

深拷贝

深拷贝,只拷贝内容,不拷贝地址,不会造成互相影响

简单易懂版本

//递归的形式
function copyObj( obj ){
	if(  Array.isArray(obj)  ){
		var newObj = [];
	}else{
		var newObj = {};
	}
	for( var key in obj ){
		if( typeof obj[key] == 'object' ){
			newObj[key] = copyObj(obj[key]);
		}else{
			newObj[key] = obj[key];
		}
	}
	return newObj;
}
console.log(  copyObj(obj5)  );
const deepClone = (target) => {
    // 判断是否是对象并且不为null
    if (typeof target === 'object' && target !== null) {
        const cloneTarget = Array.isArray(target) ? [] : {};
        // 递归克隆
        for (let prop in target) {
            if (target.hasOwnProperty(prop)) {
                cloneTarget[prop] = deepClone(target[prop]);
            }
        }
        return cloneTarget;
    } else {
        return target;
    }
}

const object1 = {
    a: 1,
    b: {
        c: 2
    },
    d: ['1', '2']
}

const object2 = deepClone(object1) // 这里两个的a为1 和 3
// const object2 = object1 // 这里两个的a都将输出3

object2.a = 3
console.log('object1', object1);
console.log('object2', object2);

实现 new

function createObject(Con) {
    // 创建新对象obj
    // var obj = {};也可以
    var obj = Object.create(null);

    // 将obj.__proto__ -> 构造函数原型
    // (不推荐)obj.__proto__ = Con.prototype
    Object.setPrototypeOf(obj, Con.prototype);

    // 执行构造函数,并接受构造函数返回值
    const ret = Con.apply(obj, [].slice.call(arguments, 1));

    // 若构造函数返回值为对象,直接返回该对象
    // 否则返回obj
    return typeof(ret) === 'object' ? ret: obj;
}

Function 篇

Call

Function.prototype.myCall = function (thisArg) {
    thisArg = thisArg || window;
    thisArg.func = this;
    const args = []
    for (let i = 1; i<arguments.length; i++) {
        args.push('arguments['+ i + ']')
    }
    const result = eval('thisArg.func(' + args +')')
    delete thisArg.func;
    return result;
}

Apply

Function.prototype.myApply = function (thisArg, arr) {
    thisArg = thisArg || window;
    thisArg.func = this;
    const args = []
    for (let i = 0; i<arr.length; i++) {
        args.push('arr['+ i + ']')
    }
    const result = eval('thisArg.func(' + args +')')
    delete thisArg.func;
    return result;
}

Bind

Function.prototype.sx_bind = function (obj, ...args) {
    obj = obj || window

    const fn = Symbol()
    obj[fn] = this
    const _this = this

    const res = function (...innerArgs) {
        console.log(this, _this)
        if (this instanceof _this) {
            this[fn] = _this
            this[fn](...[...args, ...innerArgs])
            delete this[fn]
        } else {
            obj[fn](...[...args, ...innerArgs])
            delete obj[fn]
        }
    }
    res.prototype = Object.create(this.prototype)
    return res
}