Javascript中的执行机制——Event Loop

9次阅读

共计 2320 个字符,预计需要花费 6 分钟才能阅读完成。

众所周知,Javascript 是单线程语言,这就意味着,所有的任务都必须按照顺序执行,只有等前面的一个任务执行完毕了,下一个任务才能执行。如果前面一个任务耗时很长,后一个任务就得一直等着,因此,为了实现主线程的不阻塞,就有了 Event Loop。
1、javascript 事件循环
首先,我们先了解一下同步任务和异步任务,同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入 ” 任务队列 ”(task queue)的任务,只有 ” 任务队列 ” 通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
为了更好的了解执行机制,看下图

以上图说明主线程在执行的时候产生堆(内存分配)和堆栈(执行上下文),JavaScript 是单线程的,意味着当执行环境的堆栈中的一个任务(task)在执行的时候,其它的任务都要处于等待状态。当主进程执行到异步操作的时候就会将异步操作对应的 task 放到 event table, 指定的事情完成时,Event Table 会将这个函数移入 Event Queue。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的函数,进入主线程执行,因为这个过程是不断重复的,所以称为 Event Loop(事件循环),接下来,我们用几个例子进行分析 eg1:
console.log(1);
setTimeout(function () {
console.log(2);
})
console.log(3);
// 执行结果:1、3、2

我们来分析一下这段代码,首先,根据执行上下文可知,执行环境栈中就有了一个 task——console.log(1),输出 1。接着往下执行,因为 setTimeout 是异步函数,所以将 setTimeout 进入 event table,注册了一个回调函数在 event queue,我们暂且称为 fun1,此时的流程如下图:

接着往下执行,执行环境栈中会创建一个 console.log(3) 的 task,并执行它,输出 3,此时,执行环境已经没有任务了,则去 Event Queue 读取对应的函数,fun1 被发现,进入主线程输出 2,整个过程已经完成,所以输出的结果是 1、3、2。eg2:
setTimeout(function () {
console.log(1)
}, 3)
setTimeout(function () {
console.log(2)
})
输出 2,1
我们再来简单的分析一下这个列子,我们暂且称第一个 setTimeout 为 Time1, 第二个为 Time2。由于两个都是异步函数,按照执行顺序,先将 Time 放到 event Table,接着将 Time 移到 event Table,因为 Time 在 event Table 指定要 3 秒后才执行,所以 Time2 先于 Time1 到注册回调函数到 event queue,所以输出的结果是 2,1。
2、macro-task(宏任务)、micro-task(微任务)
MacroTask: script(整体代码), setTimeout, setInterval, setImmediate(node 独有), I/O, UI rendering
MicroTask: process.nextTick(node 独有), Promises, Object.observe(废弃), MutationObserver
任务又分为宏任务和微任务两种,在同一个上下文中,总的执行顺序为“同步代码—>microTask—>macroTask”,根据上面 event loop 的流程图,我们用列子来做进一步的了解:eg1:
setTimeout(function () {
console.log(1);
},0);
console.log(2);
process.nextTick(() => {
console.log(3);
});
new Promise(function (resolve, rejected) {
console.log(4);
resolve()
}).then(res=>{
console.log(5);
})
setImmediate(function () {
console.log(6)
})
console.log(‘end’);
// 输出 2、4、end、3、5、1、6
本例参考《JavaScript 中的执行机制》,里面有详细的解释,大家可以参考下。
3、优先级
我们将上面的例子稍微改一下,将 process.nextTick 移到 promise 的后面,看下面的代码:
setTimeout(function () {
console.log(1);
},0);
console.log(2);
new Promise(function (resolve, rejected) {
console.log(4);
resolve()
}).then(res=>{
console.log(5);
})
process.nextTick(() => {
console.log(3);
});
setImmediate(function () {
console.log(6)
})
console.log(‘end’);
按照前面的分析,先执行同步代码,先输出“2,4,end”;然后是微任务 promise 输出 5,process.nextTick 输出 3;最后的宏任务输出 1,6。所以结果为 2,4,end,5,3,1,6,然后事实并非如此,结果还是输出 2、4、end、3、5、1、6,这是因为 process.nextTick 注册的函数优先级高于 Promise**。关于 Event Loop 的其他特殊情况,大家可参考文章一篇文章教会你 Event loop——浏览器和 Node,里面有更详细的介绍。

正文完
 0