这个记录(或说笔记),参照一名【合格】前端工程师的自检清单这篇文章,根据里面所提的某些问题,进行归纳和回答。
EventLoop
这部分来自stackoveflow一个问题的回答。我感觉总结的很好,我做了下简单的翻译,拿来和大家一起分享:
EventLoop是JavaScript中异步模型的基础,主要作用是执行代码、收集和处理事件以及执行子任务。事件循环过程的模型如下:
当前正在调用的栈区为空时,执行下面的步骤:
-
在任务队列中选择最早的任务(任务A)
-
如果任务A为null(意味着任务队列为空),则执行步骤6
-
将"当前正在执行的任务"设置为任务A
-
执行任务A(意味着执行回调函数)
-
将"当前正在执行的任务"设置为null,并从任务队列中移除A
-
执行微任务队列
-
(a)在微任务队列中选择最早的任务(task x)
-
(b)如果任务x是null,则接着步骤(g)
-
(c)将"当前正在执行任务"设置为"task x"
-
(d)运行任务x
-
(e)将"当前正在执行任务"设置为null,并移除"task x"
-
(f)在微任务队列中,选择下一个 ”最早任务“,并接着从(b)开始
-
(g)结束微任务队列
-
-
继续执行步骤1
注意:
-
当一个任务(在宏任务队列中)正在执行时,可能会注册新的事件,因此可能会创建新的任务。下面的两种方式将会创建新的任务:
-
promiseA.then() 的回调是一个任务
-
promiseA是完成状态(resolved/rejected),该任务将在当前事件循环中被推送到微任务队列
-
promiseA是pending状态,该任务将在未来的事件循环中(可能是下一轮),被推入微任务队列
-
-
setTimeout(callback,n)的callback是一个任务,并且将会推入宏任务队列。
-
-
微任务队列中任务将在本轮执行,而宏任务队列中的任务必须等待下一轮事件循环
-
click,scroll,ajax,setTimeout的回调是任务,脚本代码在 script tag 中 作为一个整体,也是一个任务(宏任务)。
分析下面代码的输出并解释为什么
Promise.resolve()
.then(() => { // 标记为 1-1
console.log('孙婧-1');
Promise.reject().then(() => {
// 这个代码不会走到这里的
}, () => { // 标记为 2-1
console.log('孙婧-2');
})
})
.then(() => { // 标记为 1-2
console.log('李运华-1');
})
// 上段代码等价于
var a = Promise.resolve()
.then(() => { // 1-1
console.log('孙婧-1');
Promise.reject().then(() => {
// 这个代码不会走到这里的
}, () => { // 标记为 2-1
console.log('孙婧-2');
})
})
a.then(() => { // 1-2
console.log('李运华-1');
})
// 输出:
'孙静-1'
'孙婧-2'
'李运华-1'
下面是对这段代码的分析:
由于Promise.resolve()返回一个fulfilled的Promise,所以执行「1-1」then方法后,「1-1」then方法回调被推入「本轮事件循环」的微任务队列;「1-1」方法执行之后,返回的是一个pending状态的Promise(大家可以打个断点看看),那么「1-2」then方法执行之后,它的回调被推入「下轮事件循环」的微任务队列。
此时,任务队列(宏任务队列)已经没有别的任务,那么开始执行本轮的微任务队列,首先执行的是「1-1」then方法的回调:(1)执行log方法输出“孙静-1”(2)遇到fulfilled的Promise,执行「2-1」then方法,并将其回调加入本轮事件循环的微任务队列,至此「1-1」then的回调执行结束。微任务队列中剩下 「2-1」,弹出执行,输出“孙静-2”;至此,本轮事件循环结束。
开始执行下一轮的事件循环,「1-2」then方法回调被推入微任务队列,由于没有别的任务执行,并且此时微任务队列中只有「1-2」then方法的回调,弹出执行,输出“李运华-1”。至此,执行结束。
为何try里面放return,finally还会执行,理解其内部机制
function foo() {
try {
return console.log('1111');
}catch(error) {
console.log(`error: ${error}`);
}finally {
console.log('finally');
}
}
var value = foo();
console.log(value);
// 输出:
'1111'
'finally'
undefined
分析下上述代码:
由上面代码的输出结果,先输出的是「1111」,也就是说return 后面的语句已经执行,但是并没有返回结果,因为如果返回了结果,那么必然不会输出「finally」。那么由此可以得出:return 会先执行后面的表达式,并将结果存储,待到「finally块」执行完毕,再返回之前存储的结果。(有错误望大家指出来)
JavaScript如何实现异步编程,可以详细描述EventLoop机制
实现异步编程的方式
-
回调函数
回调函数,是实现JavaScript异步编程最基础的方式,我们平常的ajax请求以及一些事件的处理,采用的都是这种方式。
-
Promise
Promise是一个容器,可以存储将来发生的任务。
function promiseTest() { return new Promise((resolve, reject) => { setTimeout(resolve, 1000, '孙婧'); }); } promiseTest().then((res) => { console.log('res:', res); }) // 1秒后(预计)输出 res: 孙婧上面我们定义了一个方法promiseTest:返回一个Promise实例。实例中存储了一个定时器,返回一个字符串「孙婧」。
-
异步函数
异步函数是ES8中增加的规范,用于简化异步编程。它内部的实现,是对Promise的封装。详细信息,可以去MDN的[async function](developer.mozilla.org/zh-CN/docs/…去了解。
宏任务和微任务分别有哪些?
宏任务:setTimeout, setInterval, I/O, requireAnimationFrame, UI rendering等
微任务:process.nextTick, Promise, queueMicrotask, MultationObserver等
使用Promise实现串行
我这里就列一种方法(有错误,请提出来😊):
-
使用async/await
async function execPromiseQueue(array) { // array 是 待执行的Promise数组 console.log(array); for(let i = 0; i < array.length; i++) { try { var result = await Promise.resolve(array[i]); console.log('result', result); } catch(error) { console.log('error', error) } } } var arr = [ Promise.resolve('1212'), Promise.reject('12'), ] execPromiseQueue(arr);