通过前几篇大致了解了js执行的一些机制,包括解析,执行,内存的分配等.
众所周知js是单线程,所以只有一个执行栈,那么一些异步操作,dom操作是怎么实现的?
V8引擎简单看包括内存分配和执行栈,可以看出,异步或dom操作等不归v8管.实际上浏览器有各个单独的线程处理异步(ajax,setTimout),dom事件等.
js执行和页面渲染是互斥的,所以事情都由v8处理会造成卡顿,浏览器多线程和js执行栈完美避免了这种情况.
异步经过其他线程处理结束后,都会返回cb回调函数,js拿到cb,入栈执行即可,称为runtime.

如上图所示,js执行遇到异步,会交给webapi处理,其回调会放在callback queue(回调队列),通过event loop(事件循环),将队列中的callback再次入栈(call stack)执行.
Event loop机制
Event loop实际上就是一个job,用来检测call stack和callback queue,一旦call stack空闲,就将queue中cb入栈执行.
callback queue中任务遵循先进先出
1 2 3 4 5
| console.log('start') setTimeout(function(){console.log('time')},0) console.log('end')
|
首先,在执行栈中依次执行,start,setTimeout,到异步时交给settimeout线程处理,不会阻塞执行,继续end,此时执行栈空,event loop检测到,将callback queue中cb入栈.
宏任务(macrotask)&微任务(microtasks)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| console.log('script start');
setTimeout(function () { console.log('setTimeout'); }, 0);
Promise.resolve().then(function () { console.log('promise1'); }).then(function () { console.log('promise2'); });
console.log('script end');
|
同是异步,为什么promise先于settimeout?
- 宏任务: setInterval setTimeout script setImmediate I/O UI rendering
- 微任务: promise process.netTick Object.observe MutationObserver
微任务优先级高于宏任务
一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。 setTimeout/Promise 等 API 便是任务源,而进入任务队列的是他们指定的具体执行任务。

Callback Queue(Task Queue)里的回调事件称为宏任务(macrotask)
微任务(microtasks)是指异步事件结束后,回调函数不会放到 Callback Queue,而是放到一个微任务队列里(Microtasks Queue),在 Call Stack 为空时,Event Loop 会先查看微任务队列里是否有任务,如果有就会先执行微任务队列里的回调事件;如果没有微任务,才会到 Callback Queue 执行回到事件。
整个 Event Loop 的执行顺序如下:
- 执行一个宏任务(script开始执行是第一个宏任务,栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前微任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取,也就是 callbacke queue)
- 一个宏任务及其产生的微任务执行完,GUI渲染,视为一个循环

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| console.log('script start');
setTimeout(function () { console.log('setTimeout'); }, 0);
new Promise(resolve => { console.log('Promise'); resolve(); }).then(function () { setTimeout(function () { console.log('setTimeout in promise1'); }, 0); console.log('promise1'); }).then(function () { console.log('promise2'); });
console.log('script end');
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| async function async1() { console.log('async1 start'); await async2(); setTimeout(function() { console.log('setTimeout1') },0) } async function async2() { setTimeout(function() { console.log('setTimeout2') },0) } console.log('script start');
setTimeout(function() { console.log('setTimeout3'); }, 0) async1();
new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| async function a1 () { console.log('a1 start') await a2() console.log('a1 end') } async function a2 () { console.log('a2') }
console.log('script start')
setTimeout(() => { console.log('setTimeout') }, 0)
Promise.resolve().then(() => { console.log('promise1') })
a1()
let promise2 = new Promise((resolve) => { resolve('promise2.then') console.log('promise2') })
promise2.then((res) => { console.log(res) Promise.resolve().then(() => { console.log('promise3') }) }) console.log('script end')
|