简介
我把 JavaScript 在浏览器中运行主要分为以下几种类型的任务:
- 同步任务(MainTask):同步任务是指 JavaScript 按照正常顺序执行的代码,比如:函数调用,数值运算等等,只要是执行后立即能够得到结果的就是同步任务。
- 宏任务(MacroTask):setTimeout、setInterval、I/O、UI 渲染
- 微任务(MicroTask):Promise、Object.obsever、MutationObsever
- 用户交互事件(User Interaction Event):点击事件 onclick、键盘事件 onkeywodn、鼠标事件 onmouseover 等等
执行顺序
具体流程:
- 执行完主逻辑中的同步任务
- 取出微任务队列(MicroTask Queue)中的任务执行,直到队列被完全清空
- 取出宏任务队列(MacroTask Queue)中的一个任务执行。
- 取出微任务队列(MicroTask Queue)中的任务执行,直到队列被完全清空
- 重复 3 和 4,直到宏任务队列(MacroTask Queue)被清空。
demo1:宏任务 (MacroTask) 和微任务 (MicroTask) 执行顺序
demo1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo1:宏任务 (MacroTask) 和微任务 (MicroTask) 执行顺序 </title>
</head>
<body>
<script type="text/javascript">
console.log('同步任务 1 start');
setTimeout(function () {console.log('宏任务 1:setTimeout...');
}, 0);
Promise.resolve().then(function () {console.log('微任务 1 Promise.then() 1')
}).then(function () {console.log('微任务 2 Promise.then() 2')
});
setTimeout(function () {console.log('宏任务 2:setTimeout...');
Promise.resolve().then(function () {console.log('宏任务 2:setTimeout => 微任务 Promise.then()')
});
}, 0);
setTimeout(function () {console.log('宏任务 3:setTimeout...');
}, 0);
Promise.resolve().then(function () {console.log('微任务 3 Promise.then() 1')
}).then(function () {console.log('微任务 3 Promise.then() 2')
})
console.log('同步任务 2 end');
</script>
</body>
</html>
运行结果:
以上代码详细的运行步骤队列图,我已经写成了 PPT,大家可以下载打开看效果,可以详细了解每一段代码在队列中的样子:
https://github.com/Jameswain/…
demo2:setInterval —— setTimeout 的语法糖
setInterval 其实可以说是 setTimeout 的语法糖,因为 setInterval 能够实现的功能,setTimeout 也能实现,下面通过一个小例子实现使用 setTimeout 实现 setInterval 的定时调度功能:
function logic() {console.log(Date.now());
setTimeout(logic, 1000);
}
logic();
demo3:setInterval 宏任务和微任务深入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo1:setTimeout 与 setInterval</title>
</head>
<body>
<div class="demo">demo</div>
<script type="text/javascript">
console.log('同步任务 1 start');
setInterval(() => {console.log('宏任务 1:setInterval...');
Promise.resolve().then(function () {console.log('宏任务 1:setInterval => 微任务 1 Promise.then()')
});
Promise.resolve().then(function () {console.log('宏任务 1:setInterval => 微任务 2 Promise.then()')
});
Promise.resolve().then(function () {console.log('宏任务 1:setInterval => 微任务 3 Promise.then()')
});
}, 3000);
setTimeout(function () {console.log('宏任务 2:setTimeout...');
}, 0);
// 微任务:监听 DOM 属性变化,当属性发生变化时触发回调函数
const demo = document.querySelector('.demo');
new MutationObserver(() => {console.log('MutationObserver Callback...');
}).observe(demo, { attributes: true});
Promise.resolve().then(function () {console.log('微任务 1 Promise.then() 1')
Promise.resolve().then(() => {console.log('微任务 1 -1 Promise.then() 1')
});
Promise.resolve().then(() => {console.log('微任务 1 -2 Promise.then() 2')
Promise.resolve().then(() => {console.log('微任务 1 -2-1 Promise.then() 1')
});
});
});
// 修改 DOM 元素属性,将回调变化回调函数放入微任务队列中
demo.setAttribute('data-random', Math.random());
console.log('同步任务 2 end');
</script>
</body>
</html>
运行结果:
从运行结果可以发现,JavaScript 的代码在浏览器中的执行顺序是【同步任务】=>【清空微任务队列】=>【宏任务】=>【清空微任务队列】,如果在执行微任务时,又发现了微任务,它会把这个微任务放入到微任务队列的末尾。宏任务也一样,如果在执行宏任务的时候发现了宏任务,它也会把这个宏任务放入宏任务队列的末尾。
上代码详细的运行步骤队列图,我已经写成了 PPT,大家可以下载打开看效果,可以详细了解每一段代码在队列中的样子:
https://github.com/Jameswain/…
参考文档:
Tasks, microtasks, queues 和 schedules
Tasks, microtasks, queues and schedules
浏览器和 Node 不同的事件循环(Event Loop)