话不多说,先上代码
<div id="app" class="app">
hello
</div>
<script>
// 定义 MutationObserver
var targetNode = document.getElementById('app');
var config = { attributes: true, childList: true, subtree: true };
var callback = function(mutationsList) {
for(var mutation of mutationsList) {
if (mutation.type == 'childList') {
console.log('Mutaition childlist');
} else if (mutation.type == 'attributes') {
console.log('Mutaition attributes');
}
}
};
var observer = new MutationObserver(callback);
observer.observe(targetNode, config);
// 定义 async函数
async function async1 () {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2 () {
console.log('async2 start');
}
/********* 开始输出 **********/
console.log('start');
// 更新dom,进入Mutation 回调函数中
targetNode.innerHTML = 'world';
// 执行async1函数
async1();
// 执行promise函数
new Promise(function (resolve) {
console.log('promise');
resolve();
}).then(function () {
console.log('promise then');
});
// 再次更新dom
targetNode.className = 'abc';
// 执行setTimeout函数
setTimeout(function () {
console.log('setTimeout');
},0);
/********* 结束输出 **********/
console.log('end');
</script>
下面是输出结果,看看和大家想的是否一样。
// start
// async1 start
// async2 start
// promise
// end
// Mutaition childlist
// Mutaition attributes
// async1 end
// promise then
// setTimeout
1. 宏任务和微任务
JS分为同步和异步,异步又分为宏任务和微任务。
简单理解执行过程:一条主线程,两个异步栈(宏任务栈,微任务栈)。主线程(同步任务)开始执行,遇到异步任务,放到对应的异步栈中,等主线程执行完毕,先清空微任务栈,再清空宏任务栈,都是先进先出原则。如果执行过程中再遇见异步任务,就重复这个流程,直到全部执行完毕。
清楚了JS执行过程,再去看执行结果,就容易了,我们只需要搞清楚一件事就行了,哪些是宏任务,哪些是微任务。
2. 梳理一下上面的各种异步方案。
- MutationObserver - 微任务,创建一个对DOM变化的监听对象
- setTimeout - 宏任务
- Promise - 微任务
- async 也相当于对Promise的一种封装,使异步代码可以不用嵌套。他本身是同步代码,但是遇到await就相当于开启了一个Promise,变成了一个微任务。
现在清楚了各种方法的分类,对上面的输出结果应该大部分都没有疑问了,唯一一点是 Mutaition childlist、Mutaition attributes 这两个输出,第二个修改属性的动作明明是在Promise之后的,为什么却跑到了它前面输出了呢?之后我把第一个修改DOM的操作移除,再执行一次
// start
// async1 start
// async2 start
// promise
// end
// async1 end
// promise then
// Mutaition attributes
// setTimeout
结果就完全跟我们想的一样了,所以这里可以理解成,在微任务栈中,MutationObserver 的callback 会放到一起存储,当执行到这里时,就会依次优先执行。
总结整个流程:
- 开始执行 打印
start - 更新DOM,MutationObserver callback放入微任务栈
- 执行
async1(),打印async1 start,再执行async2(),打印async2 start console.log('async1 end');相当于在then的回调里,放入微任务栈- 执行Promise,打印
promise console.log('promise then');放入微任务栈- 再次更新DOM,MutationObserver callback再次放入微任务栈,并插入到上一个callback后
- 执行setTimeout,回调放入宏任务栈
- 打印
end,主线程执行完成 - 开始依次执行微任务栈,打印
Mutaition childlist,Mutaition attributes,async1 end,promise then - 开始依次执行宏任务栈,打印
setTimeout,全部执行完成
参考文章
developer.mozilla.org/zh-CN/docs/…
xieyufei.com/2019/12/30/…