JS进阶系列文章
- 函数式编程之纯函数
- 函数式编程之柯里化
- 函数式编程之组合函数
- 详解ES6中的Class
- ES6知识点解析
- ES6新增的数据结构
- ES7知识点解析
- Proxy代理对象
- Reflect映射对象
- ES10知识点解析
- 详解ES6中的Promise(上)
- 详解ES6中的Promise(下)
什么是进程和线程
- 进程(
process)计算机已经运行的程序,是操作系统管理程序的一种方式 - 线程(
thread)操作系统能够运行运算调度的最小单位,通常情况下它包含在进程中
在JavaScript是单线程的,但JavaScript的线程有自己的容器进程 浏览器 / node
- 浏览器是多线程的,每个
tab会开启一个新的进程,每个进程里又有很多个线程,这包括执行JavaScript代码 JavaScript是在一个线程中执行的,只能同时处理一件事情- 如果这个事情是非常耗时的,当前进程就会被阻塞
- 浏览器每个进程是多线程的,其他线程可以完成这个耗时的操作
在浏览器中,异步函数都会被js引擎放进一个事件队列中,当同步代码执行完毕之后js引擎会将这个队列中的函数一一取出执行。
下面这段代码,setTimeout会在1秒后,将这个异步函数放到浏览器的一个事件队列。当同步代码执行完毕后,在执行setTimeout中的异步函数。
注意,同步函数不会被放进事件队列中,直接能够被执行的代码一般被称之为 main script
console.log('script start');
// 业务代码
setTimeout(()=>{
console.log('_island');
},1000)
console.log('script end');
宏任务微任务
宏任务(
macro task)事件包括有:setTimeout、setInterval、I/O、postMessage、MessageChanel、setImmediate、UI rendering微任务(
micro task)事件包括有:Promise.then、MutationnObserve、Object.observe、process.nextTick
规范:JavaScript引擎在执行任何的宏任务之前,都需要先保证微任务队列已经被清空
执行机制
- 优先级
main script代码先执行- 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微队列中是否有任务需要要执行,如果有,那么执行微任务队列中的微任务(回调)
根据上面的介绍,我们可以得出一张事件循环图
下面,我们来看两道题,看看它们的输出顺序是怎么样的
console.log("script start");
function foo() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("setTimeOut1");
});
resolve(0);
}).then((res) => {
console.log(res);
});
}
foo();
setTimeout(() => {
console.log("setTimeOut2");
});
console.log("script end");
解析上面这一段代码的执行顺序
-
首先会执行这段代码中的同步代码,先打印出
script start -
执行
foo函数,返回的是Promise对象,先将它放到微任务对象中 -
打印输出
script end -
执行微任务中的
Promise对象,在这里将setTimeout函数存放到宏任务中,res(0)之后调用then方法,打印出0,执行完毕 -
再次遇到
setTimeout函数,同样存放到宏任务中。 -
此时,微任务已经执行完毕了,接下来执行宏任务队列中的任务
-
根据存放的顺序,打印出
setTimeOut1、setTimeOut2 -
最终打印出来的是
script start script end 0 setTimeOut1 setTimeOut2
在看另外一道题,多出async、await函数时,它的执行顺序会是怎么样的?
console.log("script start");
async function foo() {
await new Promise((resolve) => {
console.log("Promise1");
resolve('Promise1 then')
}).then((res)=>{
console.log(res);
})
}
async function bar() {
console.log('bar1');
await console.log(000);
console.log('bar2')
}
foo();
bar()
console.log("script end");
解析上面这一段代码的执行顺序
在解析这道题之前,我们需要搞懂async/await函数的作用
-
首先会执行这段代码中的同步代码,先打印出
script start -
执行
foo函数,执行Promise中的函数体,打印出Promise1,将then中的事件放到微任务队列中 -
执行
bar函数,打印出bar1,000,接下来会将bar2放到微任务队列中(在await等价于then,后续的任务都会被放到微任务中) -
打印
script end,调用微任务对象中的任务,打印Promise1 then、bar2 -
最终打印出来的是
script start Promise1 bar1 000 script end Promise1 then bar2
总结
JavaScript是单线程的,采用单线程的事件循环方式管理异步任务,通过任务队列来处理异步任务。为了用户的体验微任务会被优先执行。