7分钟了解浏览器事件循环

490 阅读5分钟

老生常谈的一个问题,也是面试中的经典问题了。既如此,真人今天就写下一篇通俗易懂的事件循环,让大多数同学,能获此神技。

筑基(基础)

建议熟读并背诵

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 函数的调用帧 )。

如上,bar弹出后,继续执行,如果没有了,就代表整个脚本当前运行完毕了。

任务队列

这里我暂时只是介绍下基本概念。队列,先进先出,就跟排队一样。

  1. 宏任务(Macrotask)

script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,具体可见MDN)、I/O、UI Rendering。 这里其实你可以只记住setTimeout,毕竟真人在这里谈论的只限于浏览器环境

  1. 微任务(Microtask)

Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver 记住Promise

大致的一个图形化

每一次的用户互动事件,XHR,等等都会加入到任务队列中去

元婴(核心)

介绍了那么多,该告诉你们真正的技术了。狂风绝息斩!

大致就是这么一个过程

  1. 检测宏任务是否为空,不为空就一直执行,直到为空;为空就到2
  2. 检测微任务是否为空,不为空就执行,直到为空;为空就到3
  3. 更新视图,重回1

可能看完还是不太了解,那么就可以看看看下面的两张图

这个可能会让你有一个更加全面的,更加完整的认识。

可能你看完还会疑惑一点,我怎么知道我的事件什么时候加入到队列去呢?js不是单线程的吗,你还是没有说清楚异步啊,你个垃圾,退钱!!! 好吧,其实我也是事后才看到一篇文章,猛然发现,这篇没讲清楚I/O,事件处理,以及ajax的异步。注意! js是单线程的,没错,但是浏览器是多线程的啊。多核的 当用户触发事件,ajax等异步操作的时候,js会将这些任务交给浏览器的其它模块来处理,处理完成之后,就会添加到任务队列中,等待执行。 这些异步的操作是由浏览器的内核webcore来执行的

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')

看到这里,相信,你对Event loop已经有了一个清晰的认识了。那么让我们来打条男爵试试。 算了,不打了。再讲的话,就是一些细枝末节的东西,对于学习没有太多的好处,感兴趣的可以观看参考秘籍第三条

飞升(小结)

个人理解:主栈 > 微任务(promise) > 宏任务(setTimeout) 把每一个队列的任务当作一个函数,里面的任务是其局部变量,没到它的时候,它就是不存在的。其实,完全可以将主栈也当作一个宏任务,就是一个最大,最基础的宏任务。有的人会说了,怎么你一下说宏任务大于微任务,一下说微任务大于宏任务啊。其实啊,这个就要看你怎么来划分组了。个人建议,这个任务它从哪里诞生的,那么它就属于哪里的下一级。可以参照树形结构来辅助记忆。 真人不知所云,如有错误,请及时指正。

参考秘籍

各位大佬如果对您有帮助,欢迎关注真人的公众号