常规来说 JS 是单线程的, 我们写的代码应该都是一步一步往下走的, 但是 JS 是放在客户端执行的, 如果仅仅是这样, 那很多时候我们页面会卡住, 所以 JS 里就有很多能打破这种执行方式的方法, 比如 setTimeout,setInterval,Promise.then catch finally.node 里面还有 setImmediate,process.nextTick,I/O 操作. es6 又新增了 requestAnimationFrame, 当这些方法和我们正常执行的代码或者这些代码互相包含的时候, 谁先执行, 谁后执行就是一个让人很头大的问题比如这种代码
- console.log(1);
- setTimeout(() => {
- console.log(2);
- }, 0);
- new Promise((resolve, reject) => {
- console.log(3);
- resolve();
- }).then(data => {
- console.log(4);
- setTimeout(() => {
- console.log(5);
- }, 0);
- });
如果我们清楚这些方法的一些背后知识, 那就会很清晰了.
要理解它背后的知识, 借助我前面说的函数执行栈来理解就会稍微容易一些, 前面我说过, JS 里面需要执行的函数都是用栈来管理的, 但是上面我们说的那些方法他们一开始是不入执行栈的, 而是跑到各自对应的宏任务队列和微任务队列去, 如果有上面那些方法时候 JS 引擎处理的方式是这样的: 先让正常的同步任务入栈执行, 然后发现一个有定时器的宏任务, 先不管它, 继续往下走发现一个有回调的微任务, 把这个微任务扔进微任务队列里去, 此时宏任务的定时器到了, 然后把宏任务扔到宏任务队列里去, 继续往下走发现正常的同步任务已经都执行完了, 这时去微任务队列里把里面的所有微任务都推到栈里执行, 执行完后去宏任务队列里去依次把宏任务推到执行栈里执行. 按照这个思路, 理解一下上面的代码
console.log(1) 是正常的同步任务直接执行, 往下走碰到 setTimeout 这个是有定时器的宏任务, 但是时间是 0 直接扔到宏任务队列里去, 继续走碰到 Promise 这个是正常同步任务直接执行, 继续往下走碰到 Promise.then 这个是微任务放到微任务队列里去, 继续往下走又碰到一个 setTimeout 是个宏任务放进宏任务队列里去, 再往下发现正常的同步任务都执行完了, 到微任务队列里把 Promise.then 拿出来执行, 微任务执行完后再去宏任务队列依次把 setTimeout 里的回调执行. 最后打印结果就是 1,3,4,2,5.
按照上面说的思路来分析这种代码, 不管怎么嵌套, 我们都能准确的分析出代码执行顺序, 我们只需要知道哪些是宏任务, 哪些是微任务.
宏任务: JS 里面的 setTimeout,setInterval,requestAnimationFrame,node 里面还有 setImmediate,I/O
微任务: JS 里面的 Promise.then catch finally,node 里面的 process.nextTick
上面一直说执行微任务, 执行宏任务等等, 具体什么时候执行宏任务, 什么时候执行微任务是有一个机制不断的轮询的, 这个机制就是 Event Loop, 具体这个机制怎么运行的, 没深入研究过.
来源: http://www.qdfuns.com/article/11403/5e38a4775f91e2b62ac0e1e536b2740a.html