老生常谈的一个问题,也是面试中的经典问题了。既如此,真人今天就写下一篇通俗易懂的事件循环,让大多数同学,能获此神技。
筑基(基础)
建议熟读并背诵
js是单线程
如题,js是单线程的,这也就是为啥数据一出错,页面就停止了渲染,测试就把问题抛过来了。此时的我真想掏出我的50m大刀。别问我为啥不做数据容错,任性,就是不干!!!。言归正传,js的单线程是因为js的诞生是为了操作页面的。不可能出现,程序a正在输入这个input,程序b正好把这个input节点个删除了吧。
执行栈
栈,大家应该都明白,先进后出。就如同弹夹一样。js的执行顺序也是这样的,但是呢,我们不能把每一行,每一个语句理解为压入栈中的事件。而应该将函数当作一个事件。这样一讲,似乎又要讲到作用域了。我们可以将整个script当作一个函数,就如同c语言里面的main一样。最先进入栈的是main.
这里参考MDN上的一个例子
function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42
如下图,可以很清晰的看到整个js的运行顺序,当调用 bar 时,创建了第一个帧 ,帧中包含了 bar 的参数和局部变量。当 bar 调用 foo 时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了 foo 的参数和局部变量。当 foo 返回时,最上层的帧就被弹出栈(剩下 bar 函数的调用帧 )。
任务队列
这里我暂时只是介绍下基本概念。队列,先进先出,就跟排队一样。
- 宏任务(Macrotask)
script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,具体可见MDN)、I/O、UI Rendering。
这里其实你可以只记住setTimeout,毕竟真人在这里谈论的只限于浏览器环境
- 微任务(Microtask)
Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver
记住Promise
大致的一个图形化
元婴(核心)
介绍了那么多,该告诉你们真正的技术了。狂风绝息斩!
- 检测宏任务是否为空,不为空就一直执行,直到为空;为空就到2
- 检测微任务是否为空,不为空就执行,直到为空;为空就到3
- 更新视图,重回1
可能看完还是不太了解,那么就可以看看看下面的两张图
onclick 由浏览器的内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中
ajax 由浏览器的内核的 network 模块来处理, 在网络请求完成返回之后,才将回调添加到任务队列中
分神(实战)
闭关了那么久,是不是应该找个对手练下熟练度呢? 看招,正义降临!
console.log('start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('end')
飞升(小结)
个人理解:主栈 > 微任务(promise) > 宏任务(setTimeout) 把每一个队列的任务当作一个函数,里面的任务是其局部变量,没到它的时候,它就是不存在的。其实,完全可以将主栈也当作一个宏任务,就是一个最大,最基础的宏任务。有的人会说了,怎么你一下说宏任务大于微任务,一下说微任务大于宏任务啊。其实啊,这个就要看你怎么来划分组了。个人建议,这个任务它从哪里诞生的,那么它就属于哪里的下一级。可以参照树形结构来辅助记忆。 真人不知所云,如有错误,请及时指正。
参考秘籍
各位大佬如果对您有帮助,欢迎关注真人的公众号