为什么把微任务宏任务和变量声明var、let放一起,因为正好在研究循环这里,同时看到var和let在循环中的区别,就放在一起了
一、微任务宏任务
首先微任务和宏任务是JavaScript循环事件异步调用中的定义,并在ES6中分别定义为jobs和task
什么是宏任务?
宏任务就是由宿主发起的任务
什么是微任务?
微任务是由JavaScript引擎线程发起的任务
那什么是宿主?
宿主即为JavaScript的运行环境,浏览器和node
这里顺便提一嘴,node环境在版本11以下和浏览器是有区别的,11以上没细研究。相关内知识可以参考:https://blog.csdn.net/Fundebug/article/details/86487117
接下来上张流程图让我们理解下循环事件中,微任务和宏任务的执行顺序(图片来源:https://www.cnblogs.com/wangziye/p/9566454.html)
接着,我们了解下哪些是宏任务,哪些是微任务
上 一段 代码 及 控制台输出
for (var index = 0; index < 2; index++) {
setTimeout(() => {
console.log('宏任务')
new Promise((resolve, reject) => {
console.log('宏任务->微任务')
}).then(() => {
console.log('end')
})
}, 0)
Promise.resolve().then(() => {
console.log('微任务')
setTimeout(() => {
console.log('微任务->宏任务')
}, 0)
})
}
(错误理解)本来按照我对图的理解,输出是这样的:(错误:一个循环一次,先微后宏)
微任务,微任务->宏任务,宏任务->微任务,宏任务,微任务,微任务->宏任务,宏任务->微任务,宏任务
实际上是这样的
为什么会出现这么大的偏差呢?
那是因为不清楚JS的执行机制,不清楚宏任务和微任务对栈的处理机制是不同的。
JS的执行是栈结构,先进后出原则
宏任务和微任务都是栈,宏任务是可以存在多个栈的,但微任务只有一个栈,所以宏任务是一个一个执行的,微任务是一队一队执行的
接下来我们根据现有的知识再梳理下
首先我们要判定微任务宏任务执行顺序,我们需要理清整体代码的执行顺序
为了有助于理解,我们将for循环拆分,整体写一下方便理解:(并利用代码缩减法一步步理解,不正规哈~)
//第一次
setTimeout(() => {
console.log('宏任务')
new Promise((resolve, reject) => {
console.log('宏任务->微任务')
}).then(() => {
console.log('end')
})
}, 0)
Promise.resolve().then(() => {
console.log('微任务')
setTimeout(() => {
console.log('微任务->宏任务')
}, 0)
})
//第二次
setTimeout(() => {
console.log('宏任务')
new Promise((resolve, reject) => {
console.log('宏任务->微任务')
}).then(() => {
console.log('end')
})
}, 0)
Promise.resolve().then(() => {
console.log('微任务')
setTimeout(() => {
console.log('微任务->宏任务')
}, 0)
})
1.将整体代码的两个微任务执行,打印"微任务,微任务"
2.此时可以将代码缩减,如下图。无微任务,执行第一个宏任务,打印宏任务。
//第一次
setTimeout(() => {
console.log('宏任务')
new Promise((resolve, reject) => {
console.log('宏任务->微任务')
}).then(() => {
console.log('end')
})
}, 0)
setTimeout(() => {
console.log('微任务->宏任务')
}, 0)
//第二次
setTimeout(() => {
console.log('宏任务')
new Promise((resolve, reject) => {
console.log('宏任务->微任务')
}).then(() => {
console.log('end')
})
}, 0)
setTimeout(() => {
console.log('微任务->宏任务')
}, 0)
3此时剩余代码如图,出现微任务,执行微任务,打印'宏任务->微任务'。紧接着执行下一个宏任务,打印'微任务->宏任务'。以此类推,不难推出打印结果
//第一次
new Promise((resolve, reject) => {
console.log('宏任务->微任务')
}).then(() => {
console.log('end')
})
setTimeout(() => {
console.log('微任务->宏任务')
}, 0)
//第二次
setTimeout(() => {
console.log('宏任务')
new Promise((resolve, reject) => {
console.log('宏任务->微任务')
}).then(() => {
console.log('end')
})
}, 0)
setTimeout(() => {
console.log('微任务->宏任务')
}, 0)
二、var和let
直接上代码,代码比较简单。然后在解释其中的原理
for (let index = 0; index < 2; index++) {
setTimeout(() => {
console.log('index', index)//打印0,1
}, 0)
}
console.log('index', index)//报错ReferenceError: index is not defined
for (var index = 0; index < 2; index++) {
setTimeout(() => {
console.log('index', index)//打印2,2
}, 0)
}
console.log('index', index)//打印2
根据打印结果,有三个个问题。
1.为什么setTimeout会等循环结束执行?(会写个单章,这里涉及的是异步的运行机制,以及JS的运行机制)
setTimeout是异步函数;js是单线程的,异步会等主线程内的同步函数执行完毕才会执行异步函数,异步函数被扔到主线程外的任务中
2.var声明的index在循坏外不报错?
var存在声明提升,导致迭代变量渗透到循环外部
3.为什么let声明的和var声明的index在setTimeout中的打印结果不同
let存在块级作用域,每个迭代变量都是个新对象