前端代码分析题(选择题、分析题)——JS事件循环分析
Promise其实也不难-CSDN博客
Promise 的执行顺序分析
Promise 对象的执行是异步的,但其执行器函数内部的代码是立即执行的,而 then方法注册的回调函数则是在 Promise 状态改变后执行的。
const myPromise = new Promise((resolve, reject) => {
console.log('A');
console.log('B');
});
myPromise.then(() => {
console.log('C');
});
console.log('D');
A-》B-》D
const newPromise1 = new Promise((resolve, reject) => {
console.log('A');
resolve('B');
});
const newPromise2 = newPromise1.then(res => {
console.log(res);
});
console.log('C', newPromise1);
console.log('D', newPromise2);
A-》C Promise{<fulfilled>:'B'}->D Promise{<pending>}->B
const newPromise = new Promise((resolve, reject) => {
console.log('A');
setTimeout(() => {
console.log("timer start");
resolve("succeed");
console.log("timer end");
}, 0);
console.log('B');
});
newPromise.then((result) => {
console.log(result);
});
console.log('C');
事件循环
A-》B-》C-》timer starter->timer end ->succeed
流程解读
- 当执行到
new Promise
时,会立即执行Promise构造函数中的执行器函数(executor function),即传递给Promise
构造函数的箭头函数。- 在执行器函数内部,首先打印出
'A'
。- 然后,
setTimeout
被调用,它设置了一个延时为0毫秒的定时器回调。由于setTimeout
的回调被放入了宏任务队列(macrotask queue),它不会立即执行,而是等待当前执行栈清空并且微任务队列(microtask queue)也为空时,才会被执行。- 紧接着,打印出
'B'
。- 此时,Promise的执行器函数执行完毕,但Promise对象
newPromise
的状态仍然是pending,因为resolve
函数是在setTimeout
的回调中调用的,而这个回调还没有执行。- 紧接着,打印出
'C'
。- 事件循环和微任务队列:到目前为止,执行栈已经清空,继续执行微任务队列、宏任务队列中的任务,执行setTimeout()内回调函数。
- resolve方法会触发在微异步任务队列then()方法,因此先输出栈中数据timer end,再检查队列中待执行的任务resolve
事件循环(Event Loop):事件循环是JavaScript运行时环境中的一个循环机制,它不断地检查调栈用和任务队列。当调用栈为空时,事件循环会首先检查微任务队列,并执行其中的所有任务。只有当微任务队列为空时,事件循环才会检查任务队列,并执行其中的任务。(调用栈-》微任务队列-》宏任务队列)
- 微任务队列:它专门用于处理如
Promise
的resolve
或reject
回调、async/await
等微任务。微任务的优先级高于宏任务。- 宏任务队列:用来存储准备好执行的回调函数,比如
setTimeout
和setInterval
的回调- 调用栈
Promise.resolve().then(() => {
console.log('outerPromise');
const innerTimer = setTimeout(() => {
console.log('innerTimer')
}, 0)
});
const timer1 = setTimeout(() => {
console.log('outerTimer')
Promise.resolve().then(() => {
console.log('innerPromise')
})
}, 0)
console.log('run');
run-》outerPromise-》outerTimer-》innerPromise-》innerTimer
then方法里面的return语句讲状态改为fulfilled!
Promise.resolve().then(() => {
return new Error('error')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
//打印出then: Error: error
TypeError: Chaining cycle detected for promise
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
//输出TypeError: Chaining cycle detected for promise
promise异常处理
.catch(err=>{console.log(err)}) | .then(res)=>{console.log(res)},(err)=>{console.log(err)}
两个都可以用于处理,那个在先就先处理错误!!!!
Promise.reject('error')
.then((res) => {
console.log('succeed', res)
}, (err) => {
console.log('innerError', err)
}).catch(err => {
console.log('catch', err)
})
innerError error
Promise.reject('error')
:创建一个被拒绝的Promise
,拒绝原因为'error'
。
.then((res) => { console.log('succeed', res) }, (err) => { console.log('innerError', err) })
:
- 第一个回调函数(处理成功情况的)不会被调用,因为这个
Promise
已经被拒绝。- 第二个回调函数(处理拒绝情况的)会被调用,因为它专门用于处理
Promise
被拒绝的情况。因此,会执行console.log('innerError', err)
,其中err
的值为'error'
。
.catch(err => { console.log('catch', err) })
:
.catch()
方法是.then(null, rejectionHandler)
的语法糖,专门用于捕获Promise
链中未被处理的拒绝情况。- 然而,在这个例子中,
.then()
方法已经包含了一个处理拒绝的回调函数,因此.catch()
方法不会被调用。
console.log('catch', err)
也不会被执行,.then()
方法中已经包含了处理拒绝的逻辑。
promise .finally()用法
被解决(fulfilled)或拒绝(rejected)之后,执行一些不依赖于
Promise
结果的代码。这个方法返回在JavaScript中,Promise.finally()
方法是一个非常有用的工具,它允许你在一个Promise
对象一个新的Promise
,该Promise
的解决值(fulfilled value)或拒绝原因(rejection reason)与原始Promise
相同。let promise = new Promise((resolve, reject) => { // 模拟一个异步操作 setTimeout(() => { resolve('操作成功'); // 如果改为 reject('操作失败'); 则会触发 catch 回调 }, 1000); }); promise .then(result => { console.log(result); // 操作成功 }) .catch(error => { console.error(error); // 如果Promise被拒绝,则会打印错误信息 }) .finally(() => { console.log('清理资源或执行其他不依赖于结果的代码'); // 这里可以执行一些清理工作,比如关闭数据库连接、清除缓存等 });
Promise.resolve('A')
.then(res => {
console.log('promise1', res)
})
.finally(() => {
console.log('finally1')
})
Promise.resolve('B')
.finally(() => {
console.log('finally2')
return 'result'
})
.then(res => {
console.log('promise2', res)
})
//打印promise1 A、finally1、finally2、promise2 B
promise.all()方法(&&)
Promise.all()
是 JavaScript 中一个非常有用的方法,它接受一个包含多个Promise
对象(或可迭代对象,如数组)的可迭代对象,并返回一个新的Promise
。这个新的Promise
会在所有给定的Promise
对象都成功解决(fulfilled)后解决,其解决值(fulfilled value)是一个数组,包含了所有原始Promise
对象的解决值(按相同的顺序)。如果任何一个Promise
对象被拒绝(rejected),则新的Promise
会立即被拒绝,其拒绝原因(rejection reason)是第一个被拒绝的Promise
的拒绝原因。
Promise.all([promise1, promise2, promise3])
.then((values) => {
// 所有 Promise 都成功解决后执行这里的代码
// “!!!values 是一个数组”,包含了 promise1, promise2, promise3 的解决值
})
.catch((error) => {
// 如果有任何一个 Promise 被拒绝,则执行这里的代码
// error 是第一个被拒绝的 Promise 的拒绝原因
});
function runAsync(num) {
return new Promise((resolve) => setTimeout(
() => resolve(num, console.log(num)), 1000)
);
}
function runReject(num) {
return new Promise((resolve, reject) => setTimeout(
() => reject(`Error: ${num}`, console.log(num)), 1000 * num)
);
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then((res) => console.log(res))
.catch((err) => console.log(err));
- 1s后 runAsync(1)、runAsync(3)输出1、3
- 2s号 runReject(2)输出2,状态为reject,promise.all()捕获错误,执行.catch() Error 2
- 4s后runAsync(4)输出4
promise.race()方法[谁早听谁的)]
Promise.race
是 JavaScript 中Promise
对象的一个静态方法。它接收一个可迭代对象(比如数组)作为参数,该可迭代对象包含多个Promise
对象(或者非Promise
值,这些值会被隐式地转换为已解决的Promise
)。Promise.race
方法会返回一个新的Promise
,这个新的Promise
的状态(解决或拒绝)是由第一个解决(resolve)或拒绝(reject)的传入的Promise
决定的。
function runAsync(num) {
return new Promise(
(resolve) => setTimeout(() => resolve(num, console.log(num)), 1000)
);
}
function runReject(num) {
return new Promise(
(resolve, reject) =>
setTimeout(() => reject(`Error: ${num}`, console.log(num)), 1000 * num),
);
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
.then((res) => console.log('res: ', res))
.catch((err) => console.log(err));
function runAsync(num) {
return new Promise(
(resolve) => setTimeout(
() => resolve(num, console.log(num)), 1000
)
);
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then((res) => console.log('res: ', res))
.catch((err) => console.log(err));
async与await
async
和 await
是 JavaScript 中用于处理异步操作的关键字,它们提供了一种更直观、更易于理解的异步编程方式。在底层,async
和 await
是基于 Promise 实现的,它们让异步代码看起来像是同步代码一样。
async
和 await
底层详解
async
函数:- 当一个函数被声明为
async
时,它会隐式地返回一个 Promise。 async
函数内部可以使用await
关键字来等待其他 Promise 的解决。- 如果
async
函数内部没有显式地返回一个 Promise,那么它会返回一个已经解决的 Promise,其值为undefined
。 - 如果
async
函数内部抛出了一个异常,那么这个异常会被封装成一个被拒绝的 Promise。
- 当一个函数被声明为
await
关键字:await
只能在async
函数内部使用。await
会暂停async
函数的执行,直到它等待的 Promise 被解决或拒绝。- 如果
await
的 Promise 被解决,那么await
表达式的结果就是 Promise 的解决值。 - 如果
await
的 Promise 被拒绝,那么async
函数会立即抛出一个异常,这个异常可以被try...catch
语句捕获。 await
不会阻塞整个事件循环,它只是暂停了async
函数的执行,允许其他代码继续运行。
async function runAsync() {
console.log("runAsync start");
await asyncFunc();
console.log("runAsync end");
}
async function asyncFunc() {
console.log("do something");
}
runAsync();
console.log('start')
- runAsync start
- do something
- start
- runAsync end
同步代码的执行(start执行时机分析)
- 在
runAsync
被调用的同时(由于它是异步的),同步代码console.log('start')
也被立即执行,打印"start"
。- 重要的是要注意,虽然
runAsync
包含了异步操作,但它被调用后,控制权立即返回到事件循环,允许同步代码(如console.log('start')
)在runAsync
的异步操作完成之前执行。
await仅阻塞当前函数不阻塞其他函数
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
setTimeout(() => {
console.log('async1 timer')
}, 0)
}
async function async2() {
console.log("async2 start");
setTimeout(() => {
console.log('async2 timer')
}, 0)
console.log("async2 end");
}
async1();
setTimeout(() => {
console.log('outer timer')
}, 0)
console.log("run")
- async1 start
- async2 start
- async2 end
- run
- async1 end
- async2 timer
- outer timer
- async1 timer
await堵塞当前函数,等promise被resolve或reject才执行后续代码
async function runAsync () {
console.log('async start');
await new Promise(resolve => {
console.log('promise')
})
console.log('async end');
return 'async result'
}
console.log('main start')
runAsync().then(res => console.log(res))
console.log('main end')
- main start
- async start
- promise
- main end
此await后promise未被resolve或reject,故runAsync后续函数不会被执行
await只关心它直接等待的那个 Promise 的状态
await
表达式并不关心.then()
返回的新 Promise 的状态;它只关心它直接等待的那个 Promise 的状态。
async function runAsync () {
console.log('async start');
await new Promise(resolve => {
console.log('promise')
resolve('promise resolve')
}).then(res => console.log(res))
console.log('async end');
return 'async result'
}
console.log('main start')
runAsync().then(res => console.log(res))
console.log('main end')
- main start
- async start
- promise
- main end
- promise resolve
- async end
- async result
await后Promise被reject需处理错误
如果
await
的 Promise 被拒绝,那么async
函数会立即抛出一个异常,这个异常可以被try...catch
语句捕获,未捕获直接报错(UnhandledPromiseRejectionWarning:error)
async function runAsync () {
await promiseFunc();
console.log('async');
return 'async result'
}
async function promiseFunc () {
return new Promise((resolve, reject) => {
console.log('promise')
reject('error')
})
}
runAsync().then(res => console.log(res))
仅仅输出promise,未捕获直接报错
- process.nextTick()事件循环时是加入微任务队列
- promise中resolve()函数后代码也需执行
- setTimeout也需考虑可能题目中不同函数的延迟时间不同
作用域分析
区分严格模式和非严格模式,this不同,严格模式this为undefined
箭头函数的this不可用apply|call等方法更改
var num = 10
var obj1 = {
num: 20,
print: () => {
console.log(this.num)
}
}
obj1.print()
var obj2 = { num: 30 }
obj1.print.apply(obj2)
//输出
//10
//10
浏览器环境调用function.call(null),this指向全局对象window
function test() {
console.log(this);
}
test.call(null);
new关键字
var obj = {
user: 'yupi',
print: function(){
console.log(this.user);
}
}
obj.print()
new obj.print()
- yupi
- undefined(非严格模式),
TypeError
严格模式
new obj.print()
:这里的代码尝试使用new
关键字来调用obj
对象的new
关键字用于构造对象实例,它期望后面的调用是一个构造函数。
- 创建新对象:当
new
关键字执行时,它会创建一个新的空对象,并将这个新对象的原型设置为构造函数的prototype
属性。- 设置原型链:JavaScript引擎会将新创建的对象的内部原型(
__proto__
)设置为构造函数的prototype
属性绑定
this
并执行构造函数:然后,JavaScript引擎会将构造函数作为普通函数调用,但此时this
关键字的值会被设置为新创建的对象。这意味着在构造函数内部,你可以通过this
来引用新对象,并向其添加属性和方法。构造函数中的代码会执行,可能会初始化对象的属性、调用其他方法等。- 返回新对象:构造函数执行完毕后,如果它没有显式地返回对象(非原始值),则
new
表达式会默认返回这个新创建的对象。然而,
obj.print
并不是一个构造函数,而是一个普通的方法。它并没有设计为返回一个对象(实际上它返回的是undefined
,因为它没有return
语句)。此外,它也没有一个prototype
属性(这是构造函数才有的)。因此,使用new
来调用它并不是一个好的做法,并且可能会导致意外的行为或错误。在严格模式下,尝试使用
new
来调用一个非构造函数会导致TypeError
。但在非严格模式下,JavaScript 引擎会尝试执行这个操作,但结果通常不是你所期望的。
**与此类似,箭头函数不能做构造函数**
没有
this
绑定:
箭头函数不绑定自己的this
,它会捕获其所在上下文的this
值作为自己的this
值,并且这个绑定在函数创建时就已经确定了,之后不会改变。这意味着在箭头函数内部使用this
时,它总是引用函数被定义时的上下文中的this
,而不是函数被调用时的上下文。这与构造函数的行为不符,因为构造函数需要在被new
调用时能够正确地绑定到新创建的对象上。没有
prototype
属性:
构造函数有一个特殊的prototype
属性,这个属性是一个对象,它包含了可以由构造函数的所有实例共享的属性和方法。当你使用new
关键字调用构造函数时,新创建的对象会继承这个prototype
对象。然而,箭头函数没有prototype
属性,因此它们不能被用作构造函数来创建具有共享原型方法和属性的对象实例。不能使用
new
关键字调用:
由于箭头函数没有prototype
属性,并且它们的this
绑定是固定的,因此尝试使用new
关键字来调用箭头函数会导致错误。JavaScript引擎会抛出一个TypeError
,指出箭头函数不是构造函数
this指向
var obj = {
print: function() {
var test = () => {
console.log("yupi", this);
}
test();
},
rap: {
doRap:() => {
console.log(this);
}
}
}
var copyPrint = obj.print;
copyPrint();
obj.print();
obj.rap.doRap();
原型链