js属于单线程,也就是说对于一段js代码片段,只会顺序依次执行,都是同步执行的。而实际使用过程中有些任务执行时间长,有些任务执行时间短,这会导致页面加载时间过长并且出现空白页面的现象,非常影响用户体验。为了解决这个问题产生了宏任务和微任务。
广义上来说
宏任务包含:script(整体代码块)、setTimeOut、setInterval、setImmediate、I/O、UI rendering
微任务包含:promise、Object.observe、MutationObserver
也没有什么定义,只是将上面这些代码块分为宏任务和微任务。对于宏任务和微任务JS又做了不同的处理方式。
先从两张图片了解一下宏任务和微任务的处理方式。
这两张图片简要介绍了js任务执行流程以及宏任务和微任务执行流程。从这张图的理解来说:宏任务的优先级大于微任务的优先级,也就是说宏任务先执行,然后再执行微任务之后以此往复。
但是在有些视屏教程中介绍js的宏任务和微任务时,讲到微任务的优先级大于宏任务。
我的理解是:这两种说法都对,只是从看的起点角度不同罢了。对于第一种介绍来说。是将起始script代码块看做宏任务,这样子代码执行一开始就是宏任务。宏任务执行结束了再执行微任务,以此往复;对于第二种介绍。其实是拋过了起始script代码块,起始就是同步任务块,顺序执行。因此这样来说微任务的优先级就大于了宏任务。
哪个观点更好:我认为两个观点都好。第一种更严谨。从宏任务和微任务的分类来说。script整体代码块就是宏任务;但是对于学者来说,很容易忽略主线程scrpit也代表的是一个宏任务。所以第二种说法更易懂。
接下来从示例代码中更进一步了解宏任务和微任务的执行顺序。
图例解释:js代码是依次同步执行的,如上图所示,当执行过程中遇到宏任务,宏任务不会立刻执行,先放在宏任务执行队列;遇到微任务,也不会立即执行,会放入微任务队列;只有遇到js的主线代码才会立即执行。当主线代码执行结束,会依次将微任务调用到主线js代码执行,微任务调用结束之后,再去宏任务队列依次调用宏任务到主线代码执行。直到宏任务执行结束。
<script type="text/javascript">
setTimeout(function() {
console.log("宏任务1")
}, 0);
Promise.resolve()
.then(() => {
console.log("微任务1")
});
console.log("js主线代码1")
setTimeout(function() {
console.log("宏任务2")
}, 0);
Promise.resolve()
.then(() => {
console.log("微任务2")
});
console.log("js主线代码2")
</script>
<script type="text/javascript">
setTimeout(function() {
console.log("宏任务1")
}, 0);
new Promise(resolve => {
console.log("js主线任务3")
resolve();
})
.then(() => {
console.log("微任务1")
});
console.log("js主线代码1")
setTimeout(function() {
console.log("宏任务2")
}, 0);
new Promise(resolve => {
console.log("js主线任务4")
resolve();
})
.then(() => {
console.log("微任务2")
});
console.log("js主线代码2")
</script>
结果:
分析: 这两段代码的区别在与promise一个是new,一个直接调用方法。这里有一个误区:会以为new Promise对象的执行代码也放在微任务。事实上是,new对象是相当于主js线程立即执行的。只有then的代码表示异步(微任务)。这样也就解释了执行结果为什么是这样子的。
<script type="text/javascript">
setTimeout(function() {//代码段1
console.log("宏任务1")
Promise.resolve() //代码段2
.then(() => {
console.log("微任务2")
});
}, 0);
Promise.resolve() //代码段3
.then(() => {
console.log("微任务1")
setTimeout(function() { //代码段4
console.log("宏任务2")
}, 0);
});
console.log("js主线代码1") //代码段5
</script>
分析: 这段代码就有点复杂了。这一块就解释了图例中说的是一个层级的流程是这个样子的。 可结合文章第二张图片,理解执行顺序更简单。 先简单的分析执行流程:
<script type="text/javascript">
setTimeout(function() {//代码段1
console.log("宏任务1")
Promise.resolve()//代码段2
.then(() => {
console.log("微任务2")
});
setTimeout(function() {//代码段3
console.log("宏任务3")
},0);
}, 0);
Promise.resolve()//代码段4
.then(() => {
console.log("微任务1")
setTimeout(function() {//代码段5
console.log("宏任务2")
}, 0);
Promise.resolve()//代码段6
.then(() => {
console.log("微任务3")
});
});
console.log("js主线代码1")
</script>
分析: 这个例子更复杂,可结合文章第二张图片,理解执行顺序更简单。 执行步骤如下:
答案是否定的。定时器的执行不一定是等待一定时间后执行的。在图例解释中的图片可看出,setTimeOut是宏任务,当执行到setTimeOut时,先把setTimeOut交给定时器定时,当时间一到,放入宏任务队列去执行。 因此如果主线程执行时间过长,那么定时器是不会准时的。首先得等到主线程执行结束。如果主线程执行时间特别短,那么setTimeOut时间一到即立刻执行。
例如下代码块:
<script type="text/javascript">
setTimeout(function() {
console.log("定时器1")
}, 2000);
for (var i = 0; i < 1000000; i++) {
console.log(" ")
}
setTimeout(function() {
console.log("定时器2")
}, 3000);
</script>
这里执行结果是:当循环结束时立刻执行定时器1,然后执行定时器2。
这里也可以看出:js代码也不是说代码解析到哪里就开始执行到哪里,也许是先解析代码片段,然后依次解析到两个定时器,一个同步代码片段,将定时器放在定时器队列中,然后立刻执行同步片段,结束之后执行定时器。,如果说边解析边执行的话,那么会在循环结束时立刻执行定时器1,等待3秒之后执行定时器2,然而结果并非如此。