前端面试必会的手撕代码题,四天带你学完(第一天)

128 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

  • 手写 Object.create
  • 手写 instanceof 方法
  • 手写 new 操作符
  • 手写 Promise
  • 手写 Promise.then

1. 手写 Object.create

思路:将传入的对象作为原型

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

2. 手写 instanceof 方法

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

实现步骤:

  1. 首先获取类型的原型
  2. 然后获得对象的原型
  3. 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null

具体实现:

 function myInstanceof(left, right) {
   let proto = Object.getPrototypeOf(left), // 获取对象的原型
       prototype = right.prototype; // 获取构造函数的 prototype 对象
 ​
   // 判断构造函数的 prototype 对象是否在对象的原型链上
   while (true) {
     if (!proto) return false;
     if (proto === prototype) return true;
 ​
     proto = Object.getPrototypeOf(proto);
   }
 }

3. 手写 new 操作符

在调用 new 的过程中会发生以上四件事情:

(1)首先创建了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

 function objectFactory() {
   let newObject = null;
   let constructor = Array.prototype.shift.call(arguments);
   let result = null;
   // 判断参数是否是一个函数
   if (typeof constructor !== "function") {
     console.error("type error");
     return;
   }
   // 新建一个空对象,对象的原型为构造函数的 prototype 对象
   newObject = Object.create(constructor.prototype);
   // 将 this 指向新建对象,并执行函数
   result = constructor.apply(newObject, arguments);
   // 判断返回对象
   let flag = result && (typeof result === "object" || typeof result === "function");
   // 判断返回结果
   return flag ? result : newObject;
 }
 // 使用方法
 objectFactory(构造函数, 初始化参数);

4. 手写 Promise

 const PENDING = "pending";
 const RESOLVED = "resolved";
 const REJECTED = "rejected";
 ​
 function MyPromise(fn) {
   // 保存初始化状态
   var self = this;
 ​
   // 初始化状态
   this.state = PENDING;
 ​
   // 用于保存 resolve 或者 rejected 传入的值
   this.value = null;
 ​
   // 用于保存 resolve 的回调函数
   this.resolvedCallbacks = [];
 ​
   // 用于保存 reject 的回调函数
   this.rejectedCallbacks = [];
 ​
   // 状态转变为 resolved 方法
   function resolve(value) {
     // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
     if (value instanceof MyPromise) {
       return value.then(resolve, reject);
     }
 ​
     // 保证代码的执行顺序为本轮事件循环的末尾
     setTimeout(() => {
       // 只有状态为 pending 时才能转变,
       if (self.state === PENDING) {
         // 修改状态
         self.state = RESOLVED;
 ​
         // 设置传入的值
         self.value = value;
 ​
         // 执行回调函数
         self.resolvedCallbacks.forEach(callback => {
           callback(value);
         });
       }
     }, 0);
   }
 ​
   // 状态转变为 rejected 方法
   function reject(value) {
     // 保证代码的执行顺序为本轮事件循环的末尾
     setTimeout(() => {
       // 只有状态为 pending 时才能转变
       if (self.state === PENDING) {
         // 修改状态
         self.state = REJECTED;
 ​
         // 设置传入的值
         self.value = value;
 ​
         // 执行回调函数
         self.rejectedCallbacks.forEach(callback => {
           callback(value);
         });
       }
     }, 0);
   }
 ​
   // 将两个方法传入函数执行
   try {
     fn(resolve, reject);
   } catch (e) {
     // 遇到错误时,捕获错误,执行 reject 函数
     reject(e);
   }
 }
 ​
 MyPromise.prototype.then = function(onResolved, onRejected) {
   // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
   onResolved =
     typeof onResolved === "function"
       ? onResolved
       : function(value) {
           return value;
         };
 ​
   onRejected =
     typeof onRejected === "function"
       ? onRejected
       : function(error) {
           throw error;
         };
 ​
   // 如果是等待状态,则将函数加入对应列表中
   if (this.state === PENDING) {
     this.resolvedCallbacks.push(onResolved);
     this.rejectedCallbacks.push(onRejected);
   }
 ​
   // 如果状态已经凝固,则直接执行对应状态的函数
 ​
   if (this.state === RESOLVED) {
     onResolved(this.value);
   }
 ​
   if (this.state === REJECTED) {
     onRejected(this.value);
   }
 };

5. 手写 Promise.then

then 方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。

那么,怎么保证后一个 **then** 里的方法在前一个 **then** (可能是异步)结束之后再执行呢?

我们可以将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的效果:

  • 承前:当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
  • 启后:上一步中,当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promiseresolve,让其状态变更,这又会依次调用新 promisecallbacks 数组里的方法,循环往复。。如果返回的结果是个 promise,则需要等它完成之后再触发新 promiseresolve,所以可以在其结果的 then 里调用新 promiseresolve
 then(onFulfilled, onReject){
     // 保存前一个promise的this
     const self = this; 
     return new MyPromise((resolve, reject) => {
       // 封装前一个promise成功时执行的函数
       let fulfilled = () => {
         try{
           const result = onFulfilled(self.value); // 承前
           return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
         }catch(err){
           reject(err)
         }
       }
       // 封装前一个promise失败时执行的函数
       let rejected = () => {
         try{
           const result = onReject(self.reason);
           return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
         }catch(err){
           reject(err)
         }
       }
       switch(self.status){
         case PENDING: 
           self.onFulfilledCallbacks.push(fulfilled);
           self.onRejectedCallbacks.push(rejected);
           break;
         case FULFILLED:
           fulfilled();
           break;
         case REJECT:
           rejected();
           break;
       }
     })
    }

注意:

  • 连续多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考上面的例子和图)
  • 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 callbacks 数组中提前注册的回调