总所周知 JS 运行在浏览器中,以单线程方式运行,每一个window一个JS线程。那么浏览器是如何处理js中的I/O读取、用户点击、setTimeout等异步事件,并使其余js代码不被阻塞的呢?html
浏览器中的事件循环就是其解决方式。简单来讲浏览器中的事件循环的机制是将产生的异步事件产生的回调暂时存储在事件队列中,等到合适的时机再去执行队列中的异步事件的回调。web
要了解浏览器中的事件循环,须要先弄明白两个重要的运行时概念。segmentfault
执行栈,函数调用时造成一个调用帧,并压入栈中,当函数返回时,则帧弹栈。api
任务队列,每个任务都包含一个处理该任务的函数,当任务产生时,任务及其处理函数会被做为一个总体推入任务队列中(例如:一个setTimeout,到达时间时,setTimeOut及其回调函数会做为一个任务被推入任务队列中)。任务队列按照先进先出的顺序执行。当任务队列里的任务须要被处理的时候(即调用任务的处理函数时),将会被移出队列,调用其处理函数,此时造成一个调用帧,并压入执行栈。promise
此时执行栈中的调用帧,直到执行栈为空,而后再去处理队列中的另外一个任务。浏览器
浏览器中的任务分为两种:task(macroTask 宏任务)和microtask(微任务)。不一样的任务按照不一样的规则执行。并发
一个事件循环里有多个task queue,其中的包含多个任务,每一个任务严格的按照先进先出的顺序执行。在一个task执行结束后下一个task执行以前,浏览器可对页面进行从新渲染。 task queue中包含:app
一个事件循环中包含一个microTask queue。 microTask queue包含:webapp
一个任务完整的执行后,其余任务才会被执行。 即:执行栈中的调用帧,直到执行栈为空,而后再去处理队列中的另外一个任务。异步
在浏览器中,当事件发生而且该事件绑定了事件监听时,该事件发生后的任务才会被添加至队列。
例如:为一个DOM元素button绑定onclick一个处理事件,只有当button元素上的click事件发生时,该事件发生后的任务会被添加至队列。
再例如: setTimeout 接受两个参数:待加入队列的任务和一个延迟。延迟表明了任务被添加至任务队列的时间,只有通过了延迟的时间,该任务才会被加入队列。添加至队列之后是否被处理,取决于队列里是否有其余任务。所以延迟的时间表示最少延迟时间,而非确切的等待时间。
事件循环进程模型 步骤以下:
在事件循环中,首先从task queue中选择最早进入的task执行,每执行完一个task都会检查microtask queue是否为空,若不为空则执行完microtsk queue中的全部任务。而后再选择task queue中最早进入的task执行,以此循环。
总结上述步骤为流程图:
console.log('这是开始');
setTimeout(function cb() {
console.log('这是来自第一个回调的消息');
}, 100);
console.log('这是一条消息');
setTimeout(function cb1() {
console.log('这是来自第二个回调的消息');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('这是结束');
复制代码
输出结果为:
这是开始
这是一条消息
这是结束
promise1
promise2
这是来自第二个回调的消息
这是来自第一个回调的消息
复制代码
输出:
这是开始
这是一条消息
这是结束
复制代码
进入microtask检查点;从microtask queue中拿出promise1 then执行;将promise2 then推入microtask queue;执行完成后从microtask queue中删除promise1 then任务;
输出:
这是开始
这是一条消息
这是结束
promise1
复制代码
查看microtask queue中是否还有任务;有则从microtask queue中拿出promise2 then执行;执行完成后从microtask queue中删除promise2 then任务;
输出:
这是开始
这是一条消息
这是结束
promise1
promise2
复制代码
输出:
这是开始
这是一条消息
这是结束
promise1
promise2
这是来自第二个回调的消息
复制代码
检查microtask检查点,microtask queue为空,继续取出task queue中的任务setTimeout1 callback执行,执行完成后从task queue中删除setTimeout1 callback任务;
输出:
这是开始
这是一条消息
这是结束
promise1
promise2
这是来自第二个回调的消息
这是来自第一个回调的消息
复制代码
console.log('script start')
async function async1() {
await async2();
console.log('async1 end');
setTimeout(function() {
console.log('async1 setTimeout')
}, 0);
}
async function async2() {
console.log('async2 end');
setTimeout(function() {
console.log('async2 setTimeout')
}, 0);
}
async1();
setTimeout(function() {
Promise.resolve().then(function() {
console.log('setTimeout promise');
})
console.log('setTimeout');
}, 0);
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
复制代码
输出结果为:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
async2 setTimeout
setTimeout
setTimeout promise
async1 setTimeout
复制代码
async函数是promise的一个语法糖,简单理解为:await中的语句至关于在promise.resolve()中;await后面的语句至关于.then中的语句
输出:
script start
复制代码
-- 2.1 执行到调用async1()语句,在async1中执行await async2,async2中的语句直接执行,async2 setTimeout callback被推入task queue中;async1中的await async2后面的语句至关于promise then被推入microtask queue中; - task queue:async2 setTimeout callback; - microtask queue:async1中的await async2后面的语句;
输出:
script start
async2 end
复制代码
-- 2.2. 执行到setTimeout将setTimeout callback 推入 task queue中; - task queue:async2 setTimeout callback、setTimeout callback; - microtask queue:async1中的await async2后面的语句; -- 2.3. 执行到promise,执行resolve,将promise then1推入microtask queue; - task queue:async2 setTimeout callback、setTimeout callback; - microtask queue:async1中的await async2后面的语句、promise then1;
输出:
script start
async2 end
Promise
复制代码
-- 2.4. 执行输出script end;
输出:
script start
async2 end
Promise
script end
复制代码
【microtask 阶段】执行async1中的await async2后面的语句,输出内容,并将async1 setTimeout callback推入task queue中
输出:
script start
async2 end
Promise
script end
async1 end
复制代码
输出:
script start
async2 end
Promise
script end
async1 end
promise1
复制代码
输出:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
复制代码
输出:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
async2 setTimeout
复制代码
输出:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
async2 setTimeout
setTimeout
复制代码
输出:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
async2 setTimeout
setTimeout
setTimeout promise
复制代码
输出:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
async2 setTimeout
setTimeout
setTimeout promise
async1 setTimeout
复制代码