在 JavaScript 中,类主体(所有方法定义的地方)和构造函数都默认在严格模式下运行。所以,类中的所有代码,包括构造函数,都受到严格模式的限制和规则
class MyClass {
// "use strict" 默认开启
// 类的主体,在严格模式下运行
myMethod() {
console.log(this);
}
// 构造函数,也在严格模式下运行
constructor() {
console.log(this);
}
}
const myInstance = new MyClass();
在类的构造函数和方法中,this 不会指向顶层对象(如全局对象)。在严格模式下,类的 this 会保持 undefined。这是为了避免意外的行为和提高代码的安全性
还有一点,在类主体里定义函数
- 以普通函数形式定义:放到类的原型对象上 (节省内存)
- 以箭头函数形式定义:放在类自身
一、怎么定义一个类
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指向实例对象,实例对象可以访问私有(实例)属性
}
- 定义在类主体中的箭头函数(this指向类本身 || 所属类)
- 定义在类方法中的箭头函数(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个元素)
- 向数组直接添加onFulfilled
this.#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
- 在then里
return new MyPromise((reslove,reject)=>{})
(将原先then里的代码全部挪到{}
里面),这时调用then就会返回一个新的实例对象 - 由于回调函数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);
});