众所周知,JavaScript 是 one-threaded,也就意味着在执行 JavaScript 的过程中,是 One thing at a time,而这样的特性,正是由一个叫 Call Stack 的东西决定的(有且仅有一个)。
既然是栈,就满足 FILO 的原则。故 Call Stack 在函数运行时的表现为:
当有函数执行时,该函数被 push 到 Call Stack
当函数执行结束时,该函数从 Call Stack 内被 pop 出
如果函数内有调用到其他函数(执行结束前),则将其他函数再 push 到 Call Stack 中,等到调用结束时 pop 出
前段时间在网上陆续看了很多关于 Event loop 的文章,看完也就混个眼熟,可能内心深处对这种偏原理的知识有一些抵触心情,看完后也都没有去深入理解。最近在看 Vue 的源码,在读到关于 nextTick 的实现时,总有一种似曾相识的感觉,于是去网上查了下资料,原来 nextTick 的实现正是基于 Event loop 机制(引起重视了)。
Call Stack
众所周知,JavaScript 是 one-threaded,也就意味着在执行 JavaScript 的过程中,是 One thing at a time,而这样的特性,正是由一个叫 Call Stack 的东西决定的(有且仅有一个)。
既然是栈,就满足 FILO 的原则。故 Call Stack 在函数运行时的表现为:
由此可见,如果一个函数定义如下:
const dead = () => { return dead(); } 复制代码
那么当其被执行时,就会向 Call Stack 中不断的 push 同一个函数(dead),导致整个页面挂掉。
When Call Stack Meets Sync Request
众所又周知了,在 jQuery 提供的 Ajax 函数中,可供开发者选择请求是 sync 还是 async,我们先讨论 Call Stack 遇到 sync 请求时会发生什么。
const name = $.ajaxSync(URL_1); const info = $.ajaxSync(URL_2); const work = $.ajaxSync(URL_3); console.log(name); console.log(info); console.log(work); 复制代码
屋漏偏逢连阴雨,此时的网络状态又极差,每一个网络请求从发出到成功要经历五秒,想象一下上面这段代码如果跑起来了,会发生什么?
这是一件让人绝望的事情:
随着程序的推进,ajaxSync 函数会先后三次被 push 并 pop 出 Call Stack,而每一次从 push 到 pop 的过程需要耗费五秒钟的时间。
无论从工程效率还是用户体验的角度来说,这都是不被允许的一件事情。
Async & Callback
为了杜绝上面的问题,浏览器提供给了开发者一个叫做异步 + Callback 的解决方案。先看一段代码:
console.log('kyrieliu'); setTimeout(function(){ console.log('about Event Loop'); }, 5000); console.log(' is writing an article '); 复制代码
运行结果显而易见。
ok,那么这段代码在 Call Stack 中的表现又是怎样的呢?
基于上面文章所述,我推测:
首先,第一行代码入栈,执行完毕后出栈;紧接着,setTimeout 入栈,然后emmm,事情有点不对劲了:如果 setTimeout 入栈执行后立刻出栈,那么它内部的 console 为什么五秒后才打印出来?
Task Queue
问题的关键,是一个叫做 Task Queue 的东西。
紧接着刚才的步骤:setTimeout入栈后执行并触发了一个五秒的 timer,这个 timer 由 Web api 维护,至此,setTimeout执行完毕并出栈,第三个 console 入栈执行并出栈。五秒后,timer 结束计时,将回调函数 callback 下放到 task queue 中。
但 callback 还未执行,它什么时候执行呢?Call Stack 为空的时候。
此时的 call stack 已经为空,所以 callback 被 push 进栈执行并 pop 出,这样一来就解释得通了。 至此,正式引出 Event Loop 的概念。
Event Loop
If the call stack is clear and there's something in the task queue, push the first thing on the queue onto the stack.
setTimeout(callback, 0)
在最开始接触 JavaScript 的时候,看到上面这行代码的我是懵蔽的,0ms 后执行 callback, WTF?
在了解了 Event Loop 的运行机制后,再回过头来尝试解释一下这行代码,即:在 setTimeout 入栈执行时,内部的 callback 会立即被下放到 task queue 中,但它无法执行,因为此时的 call stack 不为空,等到 call stack 为空时,callback 才得以执行。
Thanks
广而告之