40

你真的了解 JavaScript 执行顺序么?

 4 years ago
source link: https://mp.weixin.qq.com/s/2ilx-5kDvszjF66iAvYQlg?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

大家都知道,Javascript是单线程、顺序执行的,通过事件循环来处理异步。而且稍有开发经验的同学也知道,利用 setTimeout 、  setInterval 以及  Promise 可以延时代码的执行。如果在Node.js中,大家会用  process.nextTick 来让代码在下一个周期执行;或者在Vue中,会利用  Vue.nextTick 保证DOM全部更新完毕后再执行回调函数。但是,如果他们都放在一起呢?执行顺序又会是怎么样的?

来个样例

聪明的你知道下面代码的执行顺序么?

看到这个问题,有经验的同学会脱口而出:太简单了,会输出如下内容:

因为 setTimeout 会加入到队列,延时执行。的确没错。那我们再看看下面的例子呢?

呃,这个问题好像难住了一部分同学,因为他们会有这样的想法:

setTimeout 和  Promise 到底谁先执行呢?听说  Promise 是异步的,但是  setTimeout 也是异步的,而且延时为0, 这可怎么好?

想不明白?没关系,先执行下看看结果:

结果是不是蛮有意思的?为啥 Promise 会先执行呢?我尝试着解释下,如果解释的不对,希望各位大牛多多指导。

大家知道,Javascript是基于事件循环(event loop)来处理事件的,用户的一些操作会放到事件队列里面,Javascript引擎会在合适的时候执行队列里面的操作。注意我们这里用到了“合适的时候“这个限定词,是因为Javascript单线程的,如果某段Javascript执行时间过长,那么它会阻塞主线程的执行。所以 setTimeout 也并不说是一定会精确的执行。

在Javascript引擎里面,队列还分为 Task 队列(也有人叫做  MacroTask )和  MicroTask 队列,  MicroTask 会优先于  Task 执行。比如常见的点击事件、  setImmediate 、  setTimeout 、  MessageChannel 等会放入  Task 队列,但是  Promise 以及  MutationObserver 会放到  Microtask 队列。同时,Javascript引擎在执行  Microtask 队列的时候,如果期间又加入了新的  Microtask ,则该  Microtask 会加入到之前的  Microtask 队列的尾部,保证  Microtask 先于  Task 队列执行。

这样,大家就清楚了为啥 Promise 先执行吧,因为它是一个  Microtask 呀!优先级高,真是没办法 :-)。大家也许会问,优先级高,会高到什么程度呢?我们可以简单量度下:

我在Chrome的console里面执行多次,会输出:

当然,如果这个结果不是固定的,测试多次, setTimeout 执行大慨在4ms左右,  Promise 大慨在1ms左右。哈哈,其实就快了3ms,前端同学为了争取这3ms真是不懈努力而且煞费苦心呀,不过真的为他们爱专研的态度点赞!!!

值得说明的是, Vue中 Vue.nextTick 也利用了该原理来保证在下次DOM更新循环结束之后执行延迟回调。如Vue 2.5.2里面就有这样的代码逻辑:

在Vue,用 MacroTask 就是我们上文说的  Task 。可见执行的时机是:

Task(MacroTask) 队列中:  setImmediateMessageChannelsetTimeout MicroTask 队列中: 直接用了  Promise ,新版本中弃用了  MutationObserver ,因为其兼容性不好

扯了这么多,大家应该知道原因了吧?

再来个样例

为了巩固大家对 MicroTask 的列举,我们再看一个例子

如果我们点击 inneer区域, 输出内容为什么呢? 如果你理解了上文的内容,就会知道输出结果为:

好,今天就分享在这里。下篇文章,我们聊聊Node.js里面的事件。比如上文我们还没提到 setImmediate 呢?这个东西只在IE里面支持,但是在Node.js里面是支持的,而且Node.js里面还有一个  Process.nextTick 。下次我们再聊聊。

参考资料

  1. 什么是微任务与宏任务

  2. Vue.nextTick源码阅读

  3. Vue 中如何使用 MutationObserver 做批量处理?

  4. Node.js Event Loop 的理解 Timers,process.nextTick()

  5. what-is-the-event-loop

  6. Process.nextTick 和 setImmediate 的区别?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK