写一个MyPromise类(手撕)

在 JavaScript 中,类主体(所有方法定义的地方)和构造函数都默认在严格模式下运行。所以,类中的所有代码,包括构造函数,都受到严格模式的限制和规则

class MyClass {
  // "use strict"  默认开启
  
  // 类的主体,在严格模式下运行
  myMethod() {
    console.log(this);
  }

  // 构造函数,也在严格模式下运行
  constructor() {
    console.log(this);
  }
}

const myInstance = new MyClass();

在类的构造函数和方法中,this 不会指向顶层对象(如全局对象)。在严格模式下,类的 this 会保持 undefined。这是为了避免意外的行为和提高代码的安全性

还有一点,在类主体里定义函数

  1. 以普通函数形式定义:放到类的原型对象上 (节省内存)
  2. 以箭头函数形式定义:放在类自身

JS中class类的详解

一、怎么定义一个类

class MyPromise{} 相当于 const MyPromise = class {}
以下代码是根据8个步骤写的残缺版Promise:

const PROMISE_STATE = {
  PENDING:0,
  FULFILLED:1,
  REJECTED:2
}

class MyPromise{
  // 创建一个变量存储调用resolve || reject 时的传递进来的实参
  #result    
  // 创建一个变量记录MyPromise的状态,0代表pending,1代表fulfilled,2代表rejected(初始化为0)
  #state = PROMISE_STATE.PENDING   
  // 创建一个变量存储回调函数
  #callback = []
  
  // 构造函数
  constructor(executor){
    executor(this.#resolve,this.#reject)
  }

  // 用于存储成功的数据   这是私有方法,只能在类里面调用,不能在类外面调用
  #resolve = (value) => {
    // 禁止值被重复修改——如果#state != 0,说明值已经被修改,函数直接返回,下面所有代码都不执行
    if(this.#state !== PROMISE_STATE.PENDING) return 
    this.#result = value
    this.#state = PROMISE_STATE.FULFILLED
    // 当外部调用resolve时,说明数据已经进来了
    // this.#callback && this.#callback(this.#result)
    queueMicrotask(()=>{
      // this.#callback && this.#callback(this.#result)
      this.#callback.forEach((item)=>{
        item() //item() 就是在调用箭头函数,调用箭头函数相当于调用onFulfilled
      })
    })
  }

  // 用于存储失败的数据
  #reject = (reason) => {
    // 禁止值被重复修改——如果#state != 0,说明值已经被修改,函数直接返回,下面所有代码都不执行
    if(this.#state !== PROMISE_STATE.PENDING) return 
    this.#result = reason
    this.#state = PROMISE_STATE.PENDING
  }

  // 添加一个用于读取数据的then方法
  then(onFulfilled,onRejected){

     return new MyPromise((resolve,reject)=>{
       
        if(this.#state === PROMISE_STATE.PENDING){
          // 数据还未进入MyPromise
          // this.#callback = onFulfilled
          // this.#callback.push(onFulfilled)
          this.#callback.push(()=>{
            // onFulfilled(this.#result)
            /*
              then的回调函数onFulfilled 的返回值将成为链式调用里下一个then的#state
              (而该#state由resolve调用时传递的参数决定)
            */
           resolve(onFulfilled(this.#result))  
            /*
               调用onFulfilled,#result被传递出去,
               外面定义onFulfilled的地方return xxx,这里的resolve就将xxx存到#result里,
                                                   同时将#state变成fulfilled
               外面定义onFulfilled的地方没有return,这里的resolve就将undefined存到#state里,
                                                   同时将#state变成fulfilled
               外面定义onFulfilled的地方return MyPromise.resolve(...),
                                                   这里resolve会将实例对象的值存在#result里
            */
            /*
              	这里也能解释,为什么将onFulfilled包装到箭头函数里,而不是直接push进数组:
                因为我需要得到onFulfilled(this.#result)的值a,
                再将这个值传递给链式调用下一个then( 即resolve(a) )
            */
          })
       }else if(this.#state === PROMISE_STATE.FULFILLED){
          // onFulfilled(this.#result)
         queueMicrotask(()=>{
            resolve(onFulfilled(this.#result))
           })
       }else if(this.#state === PROMISE_STATE.REJECTED){
         // onRejected(this.#result)
        queueMicrotask(()=>{
          onRejected(this.#result)
          })
        }
     })   
  }


}

const mp = new MyPromise((resolve,reject)=>{
  resolve("孙悟空")
})

mp.then(
  (result)=>{
    console.log(result)
  },
  (error)=>{
    
  })

1. 调用MyPromise类创建mp实例对象

实际上是在调用构造函数constructor创建mp实例对象(构造函数内的this指向类的实例对象),构造函数接收一个参数executor(构造器函数)
executor就是new Promise时传入的函数,executor接收两个参数resolve、reject

   class MyPromise{
     constructor(executor){
        executor(参数1,参数2)
        // executor(this.#resolve,this.#reject)
     }
   }
   
 const mp = new MyPromise((resolve,reject)=>{
     resolve("孙悟空")
   })

2. 在类里定义私有(实例)方法resolve和reject

  #resolve(value){}用于存储成功的数据
  #reject(reason){}用于存储失败的数据

数据分别用value和reason代替
调用resolve向MyPromise存数据时,再在类里定义一个私有属性#result存储数据
(存数据会出现问题,详见下列代码里的注释)

 #resolve(value){
    this.#result = value  
}
 /* 
   this为undefined
   根据resolve的调用方式,可见resolve函数内的this指向顶层对象global,
   但是类中的方法是自动开启局部严格模式,所以this为undefined
 */

// 怎么将value赋值给#result呢??
// 方法1:在类主体中定义箭头函数。在类主体中定义的箭头函数,this指向类本身
 #resolve = (value)=>{
   this.#result = value  // this指向类自身,类自身可以访问到里面的所有属性和方法
 }

// 方法2:用bind将#resolve函数内的this绑定到类的实例(bind不会调用函数)
constructor(executor){
    executor(this.#resolve.bind(this),this.#reject)
}
#resolve(value){
    this.#result = value //使用bind绑定后,this指向实例对象,实例对象可以访问私有(实例)属性 
}
  1. 定义在类主体中的箭头函数(this指向类本身 || 所属类)
  2. 定义在类方法中的箭头函数(this指向类的实例对象)

3. 在resolve和reject中禁止值被重复修改

如果我在外部调用2次resolve(),如果是Pormise,不能发生数据覆盖,因为[[PromiseState]]已经变成了fulfilled。因此再定义一个变量#state存储MyPromise的状态

 #resolve = (value) => {
    // 禁止值被重复修改——如果#state != 0,说明值已经被修改,函数直接返回,下面所有代码都不执行
    if(this.#state !== 0) return 
    this.#result = value
    this.#state = 1
}

4. 添加一个读取数据的then方法

 在then里面添加2个参数,onFulfilled和onRejected,它俩是外面调用then时传递进来的函数
 (外面定义onFulfilled和onRejected,里边调用——就能将#resulte传递到外面)
// 添加一个用于读取数据的then方法
then(onFulfilled,onRejected){
  if(this.#state === 1){
    onFulfilled(this.#result)
  }else if(this.#state === 2){
    onRejected(this.#result)
  }
}

以上的步骤,只完成了Promise的雏形,不能存储异步的数据
(用setTimeout存数据,这时调用then读不到数据)
(在原生Promise里,当[[PromiseState]]由pending变为fulfilled时,then里的回调函数被放入微任务队列)


5. 先整一个对象存储状态

(尽量少使用自然量,最好用变量代替)

const PROMISE_STATE = {
  PENDING:0,
  FULFILLED:1,
  REJECTED:2
}

6. 怎么判断数据已经进来了?(数据进来后,再调用onFulfilled)

  • 首先定义一个变量#callback,存储onFulfilled或者onRejected(#callback初始化为空)
  • 其次,在then方法里判断,如果状态为pending,给#callback赋值onFulfilled
  • 最后,回到#resolve方法里,只要外部调用了resolve,里边的#state就由pending变为fulfilled了,于是我们可以在#resolve接收到数据的代码下面调用onFulfilled
if(this.#callback){
   this.#callback(this.#result)
}

// 或者
this.#callback && this.#callback(this.#result)

此时,代码存在的问题:我们应该仿照Promise的then的写法,状态改变时,不是直接调用onFulfilled(this.#result),而是将onFulfilled放入微任务队列
onFulfilled(this.#result)换成queueMicrotask(()=>{onFulfilled(this.#result)})

7. then可以被反复调用

mp.then((re)=>{
  console.log("第一次读取数据",re)   //这个回调函数赋给了#callback
})

mp.then((re)=>{
  console.log("第二次读取数据",re)  //这个回调函数也赋给了#callback,即将前面的覆盖了
})

// 但是只会打印 第二次读取数据

解决办法:
将#callback变成一个数组,调用一次then,就往数组里存一个onFullfilled,再在#resolve里将数组里每一个onFulfilled调用一次(forEach)
(相当于,外面调用n次then,就往#calback数组里添加n个元素)

  • 向数组直接添加onFulfilledthis.#callback.push(onFulfilled)
  • 将onFulfilled包装成箭头函数,再添加到数组里(并且,我不直接传递onFulfilled函数,我让它执行一下)this.#callback.push(()=>{onFulfilled(this.#result)})

(#callback数组的每一个元素都是一个箭头函数,调用箭头函数,就相当于调用了onFulfilled)

8. then的链式调用:then方法返回一个新的Promise实例对象

新Promise实例对象的[[PromiseState]][[PromiseResult]]由上一个then方法返回什么决定

  • 返回一个promise实例对象:状态和结果与返回的promise一样
  • 返回非promise || 什么都不返回:[[PromiseState]]为fulfilled
  1. 在then里return new MyPromise((reslove,reject)=>{})(将原先then里的代码全部挪到{}里面),这时调用then就会返回一个新的实例对象
  2. 由于回调函数onFulfilled || onRejected的返回值会成为新实例对象的#state,因此调用resolve(),将数据存进去

二、整理:写一个实现then、resolve、reject方法的MyPromise类

更完整的请参考源码

1. 始终明白:构造函数的this指向实例对象

(实例对象可以调用实例方法,不能调用静态方法)
在这里插入图片描述

2. 私有(实例)方法 ≠ 静态方法

在这里插入图片描述在这里插入图片描述

3. 私有(实例)方法的定义方式

  • 以普通函数形式定义#resolve(){},this指向undefined,且该私有方法是定义在类自身
  • 以箭头函数形式定义#resolve = ()=>{},this指向类自身,且该私有方法是定义在类的原型对象上

4. 使用try…catch捕获异常

在 try 语句块中的代码执行时,如果有异常抛出,控制流将跳转到匹配的 catch 语句块。异常对象将作为参数传递给 catch 语句中的标识符(这里是 error),然后在 catch 语句块中执行相应的处理逻辑。

try {
  // 可能会抛出异常的代码
} catch (error) {
  // 异常被捕获后的处理逻辑
}

5. 代码如下

const PROMISE_STATE = {
  PENDING:0,
  FULFILLED:1,
  REJECTED:2
}

class MyPromise {
  #state = PROMISE_STATE.PENDING;; // 私有属性,表示 Promise 的状态,初始状态为 pending
  #result = undefined; // 私有属性,用于存储成功的值
  #reason = undefined; // 私有属性,用于存储拒绝的原因
  #onFulfilledCallbacks = []; 
  //此数组存储 MyPromise 状态由 pending 变为 fulfilled 时的回调函数onFulfilled
  #onRejectedCallbacks = [];
  //此数组存储 MyPromise 状态由 pending 变为 rejected 时的回调函数onRejected
  
  // 构造函数
  constructor(executor) {
    // 构造函数内的this指向 实例对象 
    // 
    try {
      // this指向实例对象,在类内部访问私有方法
      executor(this.#resolve.bind(this), this.#reject.bind(this));
    } catch (error) {
      this.#reject(error);
    }
  }

  #resolve = (value) => {
     // 私有实例方法用箭头函数形式定义,因此this指向类自身
      if (this.#state === PROMISE_STATE.PENDING) {
        // 状态为pending才能修改值,否则不可以修改(resolve同时修改值、状态)
        this.#result = value;
        this.#state = PROMISE_STATE.FULFILLED;
        // 当状态变化时,将所有onFulfilled回调放到微任务队列里(便于then的重复调用)
        queueMicrotack(()=>{
          this.onFulfilledCallbacks.forEach(callback => callback(this.value));
        })
      }
    };

  #reject = (reason) => {
      if (this.#state === PROMISE_STATE.PENDING) {
        // 状态为pending才能修改值,否则不可以修改(reject同时修改值、状态)
        this.#reason = reason;
        this.#state = PROMISE_STATE.REJECTED;
        // 当状态变化时,将所有onFulfilled回调放到微任务队列里(便于then的重复调用)
        queueMicrotack(()=>{
           this.onRejectedCallbacks.forEach(callback => callback(this.reason));
        })       
      }
    };
  
  then(onFulfilled, onRejected) {
    // 判断外面定义的是否为函数,
    // 若是函数,直接用,若不是,就使onFulfilled = (#result)=>{return  #result}
    // onRejected同理
    // onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : #result => #result;
    // onRejected = typeof onRejected === 'function' ? onRejected : #reason => { throw reason };

     // then方法返回一个实例对象
    return new MyPromise((resolve,reject)=>{
       if (this.#state === PROMISE_STATE.PENDING){
        // 如果当前 Promise 还是 pending 状态,将 onFulfilled 和 onRejected 存入回调数组
        // 等待状态修改时,立即将所有onFulfilled和 onRejected 放入微任务队列
        this.onFulfilledCallbacks.push(value => {
            // resolve(onFulfilled(value));
            const result = onFulfilled(value);  //得到then的回调函数onFulfilled调用的结果
            resolve(result); //将结果存到新实例对象的#result里,同时修改状态为fulfilled
        });

        this.onRejectedCallbacks.push(reason => {
          try {
            const result = onRejected(reason); //得到then的回调函数onRejected调用的结果
            resolve(result);  //将结果存到新实例对象的#reason里,同时修改状态为rejected
          } catch (error) {
            reject(error);   
            // reject是exxcutor的第二个参数,不是静态方法或者实例方法
            // try 里的resolve是第一个参数,是回调函数
          }
         })
         
       }
       
       else if(this.#state === PROMISE_STATE.FULFILLED){
          // 调用then时,如果状态是fulfilled,将回调函数onFulfilled放入微任务队
         // 同时将onFulfilled的结果放进resolve回调函数内,作为新实例对象的状态和值
         queueMicrotask(()=>{
            const result = onFulfilled(value);  
            resolve(result); 
           })
       }
       
       else if(this.#state === PROMISE_STATE.REIECTED){
         // 调用then时,如果状态是rejected,将回调函数onRejected放入微任务队列
         queueMicrotask(()=>{
            const result = onRejected(reason);
            resolve(result); 
           })
       }
      
    })
  }

  // 静态方法resolve
  static resolve(value) {
    return new MyPromise(resolve => resolve(value));
  }

  // 静态方法reject
  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason));
  }
  
}

// 示例用法
const mp = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    // resolve('Success!');
    reject('Error!');
  }, 1000);
});

mp
  .then((value) => {
    console.log('Fulfilled:', value);
    return 'New Value';
  })
  .then((newValue) => {
    console.log('Chained:', newValue);
  })
  .catch((reason) => {
    console.error('Rejected:', reason);
  });
  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值