javascript执行机制是基于事件循环的并发式的,事件循环负责处理代码,收集和处理事件以及执行队列中的子任务
可视化描述
栈(stack)
js运行时形成一个执行栈,
function foo(b) {
let a = 10;
return a + b + 11;
}
function bar(x) {
let y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42
当调用 bar 时,第一个帧被创建并压入栈中,帧中包含了 bar 的参数和局部变量。 当 bar 调用 foo 时,第二个帧被创建并被压入栈中,放在第一个帧之上,帧中包含 foo 的参数和局部变量。当 foo 执行完毕然后返回时,第二个帧就被弹出栈(剩下 bar 函数的调用帧 )。当 bar 也执行完毕然后返回时,第一个帧也被弹出,栈就被清空了。
堆(heap)
对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。
队列(queue)
一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。
setTimeout(function(){console.log('时间一到,请执行改回调函数')},1000)
在 事件循环 期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。
事件循环(event loop)
queue.waitForMessage() 会同步地等待消息到达(如果当前没有任何消息等待被处理)
while (queue.waitForMessage()) {
queue.processNextMessage();
}
特点
- 单线程
每一个消息完整地执行后,其它消息才会被执行
缺点:当一个消息需要太长时间才能处理完毕时,Web应用程序就无法处理与用户的交互,例如点击或滚动
2.永不阻塞
JavaScript的事件循环模型与许多其他语言不同的一个非常有趣的特性是,它永不阻塞。 处理 I/O 通常通过事件和回调来执行,所以当一个应用正等待一个 IndexedDB 查询返回或者一个 XHR 请求返回时,它仍然可以处理其它事情,比如用户输入。
总结一下事件循环的机制:
(1) 所有任务在执行栈上执行,
(2) 绑定事件和异步事件(消息)放置于任务队列
(3) 执行栈执行完毕,一直询问任务队列是否有消息需要执行,如果有就将关联的回调函数放置于执行栈,准备执行
宏任务(macrotask)和微任务(mircrotask)
异步任务细分为宏任务和微任务,当一个宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完
宏任务 -> 微任务 -> GUI渲染 -> 宏任务 -> ...
宏任务:
- 主代码块
- setTimeout
- setTimeInterval
- setImmediate()-node
- requestAnimationFrame -浏览器
- postMessage
- I/O
- UI交互事件
微任务: - process.nextTick ()-Node
- Promise.then()
- catch
- finally
- Object.observe
- MutationObserver
注意点
- 浏览器会先执行一个宏任务,紧接着执行当前执行栈产生的微任务,再进行渲染,然后再执行下一个宏任务
- 微任务和宏任务不在一个任务队列
总结
*执行主线程,遇到异步置入任务队列,并在任务队列根据宏任务和微任务进行区分
*执行栈完成,查看任务队列,首先查看宏任务队列有没有要执行的任务,没有就过,有就执行
- 每个宏任务执行完都要查看微任务队列,有没有要执行的任务,没有就过,有就执行,直到微任务队列为空
测试:
function test() {
console.log(1)
setTimeout(function () { // timer1
console.log(2)
}, 1000)
}
test();
setTimeout(function () { // timer2
console.log(3)
})
new Promise(function (resolve) {
console.log(4)
setTimeout(function () { // timer3
console.log(5)
}, 100)
resolve()
}).then(function () {
setTimeout(function () { // timer4
console.log(6)
}, 0)
console.log(7)
})
console.log(8)
结合我们上述的JS运行机制再来看这道题就简单明了的多了
JS是顺序从上而下执行
执行到test(),test方法为同步,直接执行,console.log(1)打印1
test方法中setTimeout为异步宏任务,回调我们把它记做timer1放入宏任务队列
接着执行,test方法下面有一个setTimeout为异步宏任务,回调我们把它记做timer2放入宏任务队列
接着执行promise,new Promise是同步任务,直接执行,打印4
new Promise里面的setTimeout是异步宏任务,回调我们记做timer3放到宏任务队列
Promise.then是微任务,放到微任务队列
console.log(8)是同步任务,直接执行,打印8
主线程任务执行完毕,检查微任务队列中有Promise.then
开始执行微任务,发现有setTimeout是异步宏任务,记做timer4放到宏任务队列
微任务队列中的console.log(7)是同步任务,直接执行,打印7
微任务执行完毕,第一次循环结束
检查宏任务队列,里面有timer1、timer2、timer3、timer4,四个定时器宏任务,按照定时器延迟时间得到可以执行的顺序,即Event Queue:timer2、timer4、timer3、timer1,依次拿出放入执行栈末尾执行 (插播一条:浏览器 event loop 的 Macrotask queue,就是宏任务队列在每次循环中只会读取一个任务)
执行timer2,console.log(3)为同步任务,直接执行,打印3
检查没有微任务,第二次Event Loop结束
执行timer4,console.log(6)为同步任务,直接执行,打印6
检查没有微任务,第三次Event Loop结束
执行timer3,console.log(5)同步任务,直接执行,打印5
检查没有微任务,第四次Event Loop结束
执行timer1,console.log(2)同步任务,直接执行,打印2
检查没有微任务,也没有宏任务,第五次Event Loop结束
结果:1,4,8,7,3,6,5,2
console.log('start')
setTimeout(function(){
console.log('宏任务1号')
})
Promise.resolve().then(function(){
console.log('微任务0号')
})
console.log('执行栈执行中')
setTimeout(function(){
console.log('宏任务2号')
Promise.resolve().then(function(){
console.log('微任务1号')
})
},500)
setTimeout(function(){
console.log('宏任务3号')
setTimeout(function(){
console.log('宏任务4号')
Promise.resolve().then(function(){
console.log('微任务2号')
})
},500)
Promise.resolve().then(function(){
console.log('微任务3号')
})
},600)
console.log('end')
网友评论