面试常见的11种手撕代码,赶紧收藏起来!!

426 阅读5分钟

promise

  • Promise 是一个类,类的构造函数需要传入一个执行器 executor
  • executor 两个参数: resolve reject
  • 默认创建一个 promise 状态由三个:pending fulfilled rejected
  • 调用成功或失败时,需要传递一个成功原因或失败原因
  • 如果状态转变成 resolved 或者 rejected 状态后,状态不能再次变化
  • Promise的实例都有一个 then 方法, 可以调用多次,返回一个 Promise 对象
  • 抛出异常按照失败处理

promise A+实现

const status = {
    PENDING: 'PENDING',
    FULFILLED: 'FULFILLED',
    REJECTED: 'REJECTED'
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { // 避免循环引用
        return reject(new TypeError('Error'));
    }
    // 判断x的类型,如果x是对象或者函数,说明x有可能是一个promise,否则就不可能是promise
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        let called = false; // promise的实现可能有多个,但都要遵循promise a+规范,我们自己写的这个promise用不上called,但是为了遵循规范才加上这个控制的,因为别人写的promise可能会有多次调用的情况。
        try {
            // 因为then方法有可能是getter来定义的, 取then时有风险,所以要放在try...catch...中
			// 别人写的promise可能是这样的
			// Object.defineProperty(promise, 'then', {
			// 	get() {
			// 		throw new Error();
			// 	}
			// })
            let then = x.then;
            if (typeof then === 'function') {
                // x.then(()=>{}, ()=>{}); 不要这么写,以防以下写法造成报错, 而且也可以防止多次取值
				// let obj = {
				// 	a: 1,
				// 	get then() {
				// 		if (this.a++ == 2) {
				// 			throw new Error();
				// 		}
				// 		console.log(1);
				// 	}
				// }
				// obj.then;
				// obj.then
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject); // 当前promise解析出来的结果可能还是一个promise, 直到解析到他是一个普通值
                }, e => {
                    if (called) return;
                    called = true;
                    reject(e);
                });
            } else {
                resolve(x); // 普通对象直接 resolve
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        } 
    } else {
        resolve(x); // 基本类型直接 resolve
    }
}
class Promise {
    constructor(executor) {
        this.status = status.PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = []; // 存放成功回调
        this.onRejectedCallbacks = []; // 存放失败回调
        const resolve = (value) => {
            if (value instanceof Promise) {
                return value.then(resolve, reject); // 如果 value 是个 Promise,递归执行
            }
            if (this.status === status.PENDING) {
                this.status = status.FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        const reject = (reason) => {
            if (this.status === status.PENDING) {
                this.status = status.REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }
    then(onFulfilled, onRejected) {
        // onFulfilled onRejected 为可选参数
        // 参数透传 Promise.resolve(1).then().then((value) => console.log(value))
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data;
        onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }; 
        let promise2 = new Promise((resolve, reject) => { // then 必须返回一个新的 promise
            if (this.status === status.FULFILLED) {
                setTimeout(() => { // 保证 onFulfilled、onRejected异步执行,同时能拿到 promise2
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0)
            }
            if (this.status === status.REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0)
            } 
            if (this.status === status.PENDING) { // 处理异步
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                })
            }
        })
        return promise2;
    }
    
}

promise 相关api实现

all

static all(promises) {
    return new Promise((resolve, reject) => {
        let res = [];
        let count = 0;
        const len = promises.length;
        for (let i = 0; i < len; i++) {
            const promise = promises[i];
            promise.then(value => {
                res[i] = value;
                if (++count === len) {
                    resolve(res);
                }
            }, reject)
        }
    });
}

race

static race(promises) {
    return new Promise((resolve, reject) => {
        promises.forEach(promise => {
            promise.then(resolve, reject)
        })
    });
}

allSettled

static allSettled(promises) {
    return new Promise(resolve => {
        let count = 0;
        let res = [];
        const callback = (value, index) => {
            res[index] = value;
            if (++count === promises.length) {
                resolve(res);
            }
        }
        promises.forEach((promise, index) => {
            promise.then(value => {
                callback(value, index);
            }, reason => {
                callback(reason, index);
            })
        });
    });
}

resolve

static resolve(value) {
    return new Promise(resolve => resolve(value));
}

reject

static reject(reason) {
    return new Promise((resolve, reject) => reject(reason));
}

catch

catch (errCallback) { 
    return this.then(null, errCallback);
}

Event发布订阅实现

  • Event是前端组件通信的依赖手段之一,主要有三个核心方法
  1. on: 传入需要监听的函数 fn 及事件触发 key
  2. off: 传入需要移除的 keyfn
  3. emit: 传入需要触发的事件 key 及参数,调用监听的函数
class EventEmitter {
    constructor() {
        this._events = new Map();
    }
    on(eventName, fn) {
        let handler = this._events.get(eventName);
        if (!handler) {
            this._events.set(eventName, [fn])
        } else {
            handler.push(fn);
        }
        return this;
    }
    off(eventName, fn) {
        let handler = this._events.get(eventName);
        if (Array.isArray(handler)) {
            if (fn) {
                let index = handler.indexOf(fn);
                if (index !== -1) {
                    handler.splice(index, 1);
                }
            } else {
                handler.length = 0;
            }
        }
        return this;
    }
    emit(eventName, ...args) {
        let handler = this._events.get(eventName);
        if (Array.isArray(handler)) {
            handler.forEach(fn => {
                fn(...args);
            })
        }
        return this;
    }
}

防抖节流

函数防抖

  • 原理: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时
  • 场景: 输入框输入内容查询、下载资源按钮
const debounce = (func, wait = 500, immediate = false) => {
    let timer = null; 
    return function(...args) {
        if (!timer && immediate) {
            func.apply(this, args);
        }
        clearTimeout(timer);
        timer = setTimeout(() => {
            timer = null;
            if (!immediate) {
                func.apply(this, args)
            }
        }, wait);
    }
}

函数节流

  • 原理: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
  • 场景: 常用于监听滚动条
const throttle = (func, wait = 500) => {
    let timer = null;
    let prevTime = 0;
    return function(...args) {
        let now = new Date();
        let remaining = wait - (now - prevTime);
        if (remaining <= 0) {
            // 两次间隔时间超过频率
            timer = null;
            prevTime = now;
            func.apply(this, args);
        } else if (!timer) {
            timer = setTimeout(() => {
                func.apply(this, args);
                clearTimeout(timer);
                timer = null;
                prevTime = new Date();
            }, remaining);
        }
    }
}

深拷贝

详情请点击我的博客深入了解浅拷贝与深拷贝,里面有非常详细的解释。

const arrayTag = '[object Array]'
const objectTag = '[object Object]'
const mapTag = '[object Map]'
const setTag = '[object Set]'
const functionTag = '[object Function]';
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const numberTag = '[object Number]'
const regexpTag = '[object RegExp]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'

function cloneArray(array) {
    const { length } = array;
    const result = new array.constructor(length);
  
    if (length && typeof array[0] === 'string' && hasOwnProperty.call(array, 'index')) {
        result.index = array.index;
        result.input = array.input;
    }
    return result;
}

function cloneSymbol(symbol) {
    return Object(Symbol.prototype.valueOf.call(symbol));
}

function cloneRegExp(regexp) {
    const reFlags = /\w*$/;
    const result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
    result.lastIndex = regexp.lastIndex;
    return result;
}

function initCloneTargetByTag(target, tag) {
    const Ctor = target.constructor;
    switch (tag) {
        case boolTag:
        case dateTag:
            return new Ctor(+target);

        case numberTag:
        case stringTag:
        case errorTag:
            return new Ctor(target);

        case objectTag:
        case mapTag:
        case setTag:
            return new Ctor();

        case arrayTag:
            return cloneArray(target);

        case symbolTag:
            return cloneSymbol(target);

        case regexpTag:
            return cloneRegExp(target);
    }
}

function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}

function deepClone(target, cache = new WeakSet()) {
    if (!isObject(target)) return target; // 拷贝基本类型值

    if (cache.has(target)) return target;

    cache.add(target);

    const tag = Object.prototype.toString.call(target);
    let cloneTarget = initCloneTargetByTag(target, tag); // 使用拷贝对象的构造方法创建对应类型的数据

    if (tag === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, deepClone(value, map));
        });
        return cloneTarget;
    }

    if (tag === setTag) {
        target.forEach(value => {
            cloneTarget.add(deepClone(value, map));
        });
        return cloneTarget;
    }

    if (tag === functionTag) {
        return target;
    }

    Reflect.ownKeys(target).forEach(key => {
        cloneTarget[key] = deepClone(target[key], cache); // 递归拷贝属性
    });

    return cloneTarget;
}

继承

call 继承

call继承:子类只能拿到父类的属性, 不能拿到原型上的方法

function Parent(name) {
    this.parent = name;
}
Parent.prototype.say = function() {
    console.log('this.parent :>> ', this.parent);
}
function Child(name, parent) {
    Parent.call(this, parent); // 继承父类属性
    this.child = name;
}

原型链继承

子类实例会公用同一个父类原型对象,会互相影响

function Parent(name) {
    this.parent = name;
}
Parent.prototype.say = function() {
    console.log('this.parent :>> ', this.parent);
}
function Child(name, parent) {
    this.child = name;
}
Child.prototype = new Parent();

组合继承

call 继承和原型链继承结合起来,缺点是会执行两次 Parent 构造函数

function Parent(name) {
    this.parent = name;
}
Parent.prototype.say = function() {
    console.log('this.parent :>> ', this.parent);
}
function Child(name, parent) {
    Parent.call(this, parent); // 继承父类属性
    this.child = name;
}
Child.prototype = new Parent();

寄生组合继承

没有上面的这些缺点

function Parent(name) {
    this.parent = name;
}
Parent.prototype.say = function() {
    console.log('this.parent :>> ', this.parent);
}
function Child(name, parent) {
    Parent.call(this, parent); // 继承父类属性
    this.child = name;
}

Child.prototype = Object.create(Parent.prototype); // Object.create创建了父类原型的副本,与父类原型完全隔离
Child.prototype.say = function() {
    console.log('this.child :>> ', this.child);
}
Child.prototype.constructor = Child;

new

详情请点击我的博客趣谈new

function selfNew(Ctor, ...args) {
    let instance = Object.create(Ctor.prototype);
    let res = Ctor.apply(instance, args);
    if (/^(object|function)$/.test(typeof res)) return res;
    return instance;
}

bind

详情请点击我的博客趣谈bind

Function.prototype.selfBind = function(context, ...bindArgs) {
    if (typeof this !== 'function') {
        throw new TypeError('参数类型错误');
    }
    const self = this;
    let fBound = function(...args) {
        return self.apply(this instanceof self ? this : context, bindArgs.concat(args));
    }
    fBound.prototype = Object.create(this.prototype);
    return fBound;
}

call

call 做了什么:

  • 接收一个对象,将函数设为对象的属性
  • 执行&删除这个函数
  • 指定 this 到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
Function.prototype.selfCall = function (context, ...args) {
    context = context || window;
    const key = Symbol('key');
    context[key] = this;
    let res = context[key](...args);
    delete context[key];
    return res;
}

apply

bind 方法基本一样,只不过接收的参数类型不同

Function.prototype.selfApply = function(context, args) {
    context = context || window;
    const key = Symbol('key');
    context[key] = this;
    let res = context[key](...args);
    delete context[key];
    return res;
}

instanceof

instanceof 运算符是用来检测某个实例对象的原型链上是否存在构造函数的 prototype 属性。

  1. 先取得当前类的原型,当前实例对象的原型链
  2. 一直循环(执行原型链的查找机制)
  3. 取得当前实例对象原型链的原型链(proto = proto.__proto__,沿着原型链一直向上查找)
  4. 如果 当前实例的原型链__proto__上找到了当前类的原型prototype,则返回 true
  5. 如果 一直找到Object.prototype.__proto__ == nullObject的基类(null)上面都没找到,则返回 false
function selfInstanceof(left, right) {
    let proto = Object.getPrototypeOf(left);
    while (true) {
        if (proto === right.prototype) {
            return true;
        }
        if (proto === null) {
            return false;
        }
        proto = Object.getPrototypeOf(proto);
    }
}

Object.create

Object.create方法的实质是新建一个空的构造函数F,然后让F.prototype属性指向参数对象proto,最后返回一个F的实例,从而实现让该实例继承proto的属性。

Object.prototype.create = function(proto) {
    function F(){};
    F.prototype = proto;
    return new F();
}