一、js单线程、非阻塞
JavaScript的主要用途是与用户互动,以及操作DOM。如果它是多线程的会有很多复杂的问题要处理,比如有两个线程同时操作DOM,一个线程删除了当前的DOM节点,一个线程是要操作当前的DOM阶段,最后以哪个线程的操作为准?为了避免这种,所以JS是单线程的。即使H5提出了web worker标准,它有很多限制,受主线程控制,是主线程的子线程。
非阻塞:通过 event loop
实现。
二、代码执行机制
2.1 同步任务和异步任务
- 同步任务 即可以立即执行的任务,例如
console.log()
打印一条日志、声明一个变量或者执行一次加法操作等。- 异步任务 相反不会立即执行的事件任务。异步任务包括宏任务和微任务(后面会进行解释~)。
- 常见的异步操作:
- Ajax
- DOM的事件操作
- setTimeout
- Promise的then方法
- Node的读取文件
任务队列: 异步代码的执行,遇到异步任务不会等待它返回结果,而是将这个任务挂起,继续执行执行栈中的其他任务。当异步任务返回结果,将它放到任务队列中,被放入任务队列不会立刻执行起回调,而是等待当前执行栈中所有任务都执行完毕,主线程空闲状态,主线程会去查找任务队列中是否有任务,如果有,则取出排在第一位的任务,并把这个任务对应的回调放到执行栈中,然后执行其中的同步代码。
2.2 宏任务(Macrotasks)和微任务(Microtasks)
宏任务:
- script(整体代码)
- setTimeout()
- setInterval()
- postMessage
- I/O
- UI交互事件
微任务:
- new Promise().then(回调)
- MutationObserver(html5 新特性)
Event Loop
异步任务中的宏任务和微任务,还没有弄明白。我们可以先顺一遍执行机制:
- 从全局任务
script
开始,任务依次进入栈中,被主线程执行,执行完后出栈。 - 遇到异步任务,交给异步处理模块处理,对应的异步处理线程处理异步任务需要的操作,例如定时器的计数和异步请求监听状态的变更。
- 当异步任务达到可执行状态时,事件触发线程将回调函数加入任务队列,等待栈为空时,依次进入栈中执行。
到这问题就来了,当异步任务进入栈执行时,是宏任务还是微任务呢?
- 由于执行代码入口都是全局任务
script
,而全局任务属于宏任务,所以当栈为空,同步任务任务执行完毕时,会先执行微任务队列里的任务。 - 微任务队列里的任务全部执行完毕后,会读取宏任务队列中拍最前的任务。
- 执行宏任务的过程中,遇到微任务,依次加入微任务队列。
- 栈空后,再次读取微任务队列里的任务,依次类推。
实例解析
console.log('start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('end')
心得: 同一阶段,同步代码执行完毕,当执行栈为空,处理异步任务先微后宏。
三、经典题目分析
①
Promise.resolve()
.then(function () {
console.log("promise0");
})
.then(function () {
console.log("promise5");
});
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function () {
console.log("promise2");
});
Promise.resolve().then(function () {
console.log("promise4");
});
}, 0);
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(function () {
console.log("promise3");
});
}, 0);
Promise.resolve().then(function () {
console.log("promise1");
});
console.log("start");
// 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3
script第一个宏任务,同级微任务一次全部执行完注意入队顺序
,宏任务一个一个执行。
②
console.log("script start");
async function async1() {
await async2(); // await 隐式返回promise
console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
}
async function async2() {
console.log("async2 end"); // 这里是同步代码
}
async1();
setTimeout(function() {
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 => setTimeout
async隐式返回Promise,会产生一个微任务,await后面的代码是在微任务时执行