JavaScript必知必会手写题要点解析(附在线链接)

1,277

一、面向对象编程

1、实现new关键字

实现思路:

  • 要点1:创建新对象,作为要返回的对象
  • 要点2:把新对象的原型指针__proto__指向构造函数的原型
  • 要点3:执行构造函数,并把this指针指向当前对象,
  • 要点4:构造函数的结果是非空对象的话就返回这个结果,否则返回新创建的对象
function myNew(contor, ...args) {
    let obj = Object.create(contor.prototype);
    const rst = contor.call(obj, ...args);
    return typeof rst === "object" ? rst || obj : obj;
}

用例 在线链接

2、实现寄生组合继承

寄生组合继承的思想是借用构造函数继承父类属性,然后通过原型链的混成形式来继承方法。

实现思路:

  • 要点1:子类的构造函数执行父类的构造函数
  • 要点2:子类的原型赋值为父类的原型。 在线链接
function Parent(name) {
  this.name = name
}
Parent.prototype.sayHello = (name) => {
  console.log('hello', name)
}

// 要点1:子类的构造函数执行父类的构造函数
// 要点2:子类的原型赋值为父类的原型。
function Child(name,subName) {
  Parent.call(this,name)
  // 这里要调用父类的方法之后再调用。
  this.subName = subName
}
Child.prototype = Object.create(Parent.prototype, {
  constructor: Child
})

Child.prototype.saySubName= function saySubName ()  {
  // 这里用箭头函数就获取不到this
  console.log('mySubName is', this.subName)
}

// 测试用例
const child = new Child('Jack','wang')
console.log(child.name)
console.log(child.subName)
console.log(child.subName)
console.log(child.sayHello('lili'))
console.log(child.saySubName())

3、实现instanceof

`instanceof `是判断一个对象是否是另一个对象的实例的方法。

实例对象有个原型指针__proto__,执行该实例的构造函数的原型prototype

也就是instance.__proto__ = constro.prototyoe

obj.__proto__,可以用Object.getPrototypeOf 这个兼容写法

要点在于:

  • 通过原型链向上搜索,直到原型链的终点,看__proto__ 是否相等。 在线链接
function myInstanceof(left, right) {
  const prototype = right.prototype;
  while (left !== null) {
    const  proto = Object.getPrototypeOf(left);
    if (proto === prototype) {
      console.log('====',proto,prototype)
      return true;
    }
    left = proto;
  }
  return false;
}

测试用例:

class Father {
  constructor(tall) {
    this.height = tall
  }
}
const jack = new Father("180");
console.log('用例1:',myInstanceof(jack, Father)); // true
console.log('用例2:',myInstanceof([], Array)); // false
console.log('用例3',myInstanceof(123, Father)); // false
console.log('用例4',myInstanceof(123,[] )); // false

4、实现Object.create

`Object.create`是使用现有对象来提供新对象的原型指针`__proto__`,来创建一个新对象。
并且可以指定新对象的属性值。这个过程类似于寄生式继承的步骤:

1、新建一个对象,将新对象的原型设置成指定的原型

2、寄生式继承来为对象添加属性。

在线链接

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

//寄生式继承
function myCreate(proto, propertiesObject) {
  const clone = create()
  // 增强对象
  if (propertiesObject && typeof propertiesObject === "object") {
    for (let key in propertiesObject) {
     clone[key] = propertiesObject[key];
    }
  }
  return clone;
}

二、异步编程

1、实现简易的promise

Promise是个构造函数,接收一个处理器函数executor作为参数。
  • 要点1:有三种状态 pendingfulfilledrejected

  • 要点2:executor立即执行。

  • 要点3:接收resolvereject两个函数作为参数。所以需要定义两个函数。 异步任务成功返回结果时,调用resolve,异步任务失败且返回失败结果或者executor执行出错时执行reject

  • 要点4:当状态为 pending 时, onFulfilled 函数由一个队列维护。当状态为 fulfilled 时, onFulfilled 函数直接执行, rejected同理。所以需要有个数组来存回调函数,和一个存结果的状态。

    总的来说,就是定义一个类和一个成功回调和失败回调。 接受一个立即函数,成功了执行成功回调,失败了执行失败回调。

export default class MyPromise {
    constructor(executor) {
        if (typeof executor !== "function") {
           throw new Error('Promise resolver 1 is not a function')
        }
        if (this instanceof MyPromise) {
           throw new Error(`${this} is not a promise`)
        }
        // 要点1
        this.status = "pending";
        this.result = "";
        this.errorRst = "";
        // 要点4
        this.onFullfilledCallback = [];
        this.onRejectedCallback = [];
        let self = this;
        // 要点3:定义成功回调
        function onFulfilled(value) {
            if (self.status === "pending") {
                self.status = "fullfilled";
                self.result = value;
                if (self.onRejectedCallback.length) {
                    self.onFullfilledCallback.forEach((cb) => {
                        typeof cb === "function" && cb(value);
                    });
                }
              }

         }
        // 要点3:定义失败回调
        function onRejected(error) {
            if (self.status === "pending") {
                self.status = "rejected";
                self.result = error;
                if (self.onRejectedCallback.length) {
                    self.onRejectedCallback.forEach((cb) => {
                         typeof cb === "function" && cb(error);
                    });
                  }
              }
         }
        // 要点2、要点3
        try {
          executor(onFulfilled, onReject);
        } catch (e) {
          onReject(e);
        }
}

2、实现Promise.prototype.then

then()方法是Promise原型链的方法,返回一个Promise。它最多接受2个参数,成功回调和失败回调。

实现思路如下:

  • 要点1:then返回的是个 Promise
  • 要点2:如果当前状态是pending状态,需要把回调存进pending队列里面
  • 要点3:then里面的方法是异步执行的,这里是用setTimeout来模拟
  • 要点4:回调函数onResovled返回的是promise1,那么这个结果promis1e的执行resovle时候,整个then Promise才是resovled状态。
  • 要点5:回调函数onResovled是一个常量的话,发生值穿透。把onResoved变成一个返回value的函数就好了。 链接🔗
MyPromise.prototype.then = function (onResovled, onRejected) {
    const self = this;
    
    //要点5:值穿透
    onResovled = typeof onResovled === "function" ? onResovled : (value) => value;
    onResovled = typeof onResovled === "function" ? onResovled : (value) => value;
    
    // 要点1:返回一个Promise1
    return new MyPromise((resolve, reject) => {
        function _handle(callback) {
            try {
                const rst = callback(self.result);
                // 要点4:回调返回一个Promise
                if (rst instanceof MyPromise) {
                    rst.then((value) => {
                      resolve(value);
                    })
                    .catch((err) => {
                        reject(err);
                    });
                } else {
                  resolve(rst);
                }
            } catch (e) {
                reject(e);
            }
        }
        switch (self.status) {
        // 要点2:pending时存进队列
        case "pending":
            self.onFullfilledCallback.push(() => {
                _handle(onResovled);
            });
            self.onRejectedCallback.push(() => {
                _handle(onRejected);
            });
            break;
        case "fullfilled":
            // 要点3:异步执行
            setTimeout(() => {
            _handle(onResovled);
            }, 0);
            break;
        case "rejected":
            setTimeout(() => {
                _handle(onRejected);
            });
            break;
        default:
            break;
        }

});

3、实现Promise.prototype.resolve

Promise.resolve返回一个新的Promise。如果参数本身就是一个Promise对象,则直接返回这个Promise对象。
MyPromise.prototype.resolve = function (value) {
    return new Promise((resolve, reject) => {
        if (value instanceof Promise) {
            value.then(
                (value) => {
                    resolve(value);
                },
                (err) => {
                    reject(err);
                }
            );
        } else {
            resolve(value);
        }
        });
};

4、实现Promise.prototype.finally

Promise.finally无论Promise成功或失败都会执行。无法知道promis的终态,不接受任何参数。
  • 要点1:返回一个Promise
  • 要点2:返回原来的值,把值穿透到下一个。 链接🔗
MyPromise.prototype.finally = function (callback) {
    let P = this.constructor();
    return this.then(
        // 如果callback 里面是一个Promise,需要等待它的结果
        (value) => P.resolve(callback()).then(() => value),
        (err) => P.resolve(callback()).then(() => {
            throw err;
    })
    );
};

5、实现Promise.prototype.all

Promise.all接收一个promise的iterable类型,返回一个Promise实例。

  • resovle回调的结果是一个数组
  • reject 的是第一个抛出的错误信息 链接🔗
MyPromise.prototype.all = function (promiseArr) {
    return new Promise((resolve, reject) => {
        let promiseRst = [];
        let index = 0;
        promiseArr.forEach((p, i) => {
            // 如果p一个Promise,需要他的结果
            Promise.resolve(p)
                .then((rst) => {
                    promiseRst[i] = rst;
                    if (++index === promiseArr?.length) {
                        resolve(promiseRst);
                    }
                })
                .catch((err) => reject(err));
            });
    });

};

6、实现Promise.prototype.allSettled

    Promise.allSettled返回一个Promise,Promise的结果是带有对象的数组,每个对象表示对于的Promise结果。
    区别于Promise.all的异常会短路且不关心每个promise状态的结果,

链接🔗

MyPromise.allSettled = function (promiseArr) {
    let index = 0;
    const rst = [];
    let len = promiseArr.length;
    if (!len) {
        return Promise.resolve(rst);
    }
    return new Promise((resolve) => {
        promiseArr.forEach((p, i) => {
            Promise.resolve(p)
                .then((value) => {
                    rst[i] = { status: "fulfilled", value };
                    if (++index === len) {
                        resolve(rst);
                    }
                })
                .catch((err) => {
                    rst[i] = { status: "rejected", value: err };
                if (++index === len) {
                    resolve(rst);
                }
        });
    });
    });
};

7、实现Promise.prototype.race

Promise.race接收一个promise的iterable类型,返回一个新的promise,
一旦迭代器中某个promise解决和拒绝,这个promise就会返回第一个结果。
MyPromise.race = function (promiseArr) {
  return new Promise((resolve, reject) => {
      promiseArr.forEach((promise) => {
          // 为了把arr的所有值都转成promise
          Promise.resolve(promise)
              .then((value) => {
                  resolve(value);
              })
              .catch((err) => {
                  reject(err);
              });
      });
  });
};

8、实现Promise.prototype.any

    Promise.any区别于Promise.race,返回的是第一个成功的Promise。
    都失败时,才reject

链接🔗

MyPromise.prototype.any = function (promiseArr) {
    let index = 0;
    const len = promiseArr.length;
    if (!len) {
        return Promise.resolve();
    }
    return new Promise((resolve, reject) => {
        promiseArr.forEach((p) => {
            Promise.resolve(p)
                .then((rst) => {
                    resolve(rst);
                })
                .catch(() => {
                // 判断是否都失败了
                if (++index === len) {
                    // new AggregateError
                    reject(new Error("All promises were rejected"));
                }
                });
        });
    });
};

拓展:Promise.any的面试题

9、实现Promisify

    把同步的回调函数改成promise

首先了解,nodeCallback的规范:

  • 回调函数在主函数参数的位置是最后一个
  • 回调函数的第一个参数是error 例如:
function main(err, b, c, callback) {
    let data = b+c
    callback(err,data)
}

所以,实现思路是把结果由原先的放在callback中返回,改成放在Promise中返回。 最终用法如下:

var func1 = function (a, b, c, callback) {
    let rst = a + b + c;
    callback(null, rst);
};
var func2 = promisify(func1);

func2(1, 2, 3).then((rst) => {
    console.log("rst", rst);
});

可以知道,Promisify返回的是一个高阶函数,这个函数返回一个promise。 我们可以执行传入的函数,改写回调函数callback,在原本的callback里面resolve/reject promise的结果。

所以,我们可以如下实现:

const promisify = (fnc) => (...args) => {
    return new Promise((resolve, reject) => {
        fnc.call(this, ...args, function (err, data) {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
};

其他promise面试题拓展:

1、限制异步并发请求图片数🔗

2、实现红绿灯交替重复亮🔗

10、实现可中断的promise

    单个promise一开始,就无法中断。但是多个promise.all和promise.race的有异常短路的特点,
    可以中断promise。
    所以可以将目标promise和中断的promise放在一起,利用promise.race竞速的特点,
    中断这组promise,达到中断单个promise的目的。

实现思路:

  • 把abort方法赋值为成拒绝结果的promise的reject。一旦被调用,race的promise直接返回。 链接🔗
function abortWrapper1(promise) {
    let abort;
    // 注意这里,abort方法没有被调用时,reject不会被调用。
    // 也就是说,这个p2的promise没有返回。
    let p2 = new Promise((_, reject) => (abort = reject));
    let p = Promise.race([promise, p2]);
    p.abort = abort;
    return p;
}

使用例子:

const request = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve("收到服务端数据");
    }, 3000);
});

const req = abortWrapper1(request);
req.then((res) => console.log(res)).catch((e) => console.log(e));
setTimeout(() => req.abort("用户手动终止请求1"), 2000); // 这里可以是用户主动点击