JS是单线程语言
JavaScript执行机制是Event Loop(事件循环)
利用alert弹窗会阻碍dom渲染代码执行,来验证微任务与宏任务的执行是在dom渲染前或后
为什么了解执行的顺序和时机? 可以判定使用什么函数去处理代码;
比如:你需要再dom渲染之前做好逻辑处理,等待dom渲染完毕后,展示于页面,此时必用微任务方式处理,必正确;反之需要获取dom的操作就需要采用宏任务的相关代码去包裹处理函数,才不至于获取不到元素而报错!!!
首先说明下哪些是宏任务,哪些是微任务?(关键的分类)
/****宏任务*****/
// setTimeout、setInterval、AJAX、DOM事件
/****微任务*****/
// Promise、async/await
Event Loop也叫做事件循环,是指浏览器或Node环境的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是实现异步的原理。作为一种单线程语言,javascript本身是没有异步这一说法的,是由其宿主环境提供的。
注意:Event Loop 并不是在 ECMAScript 标准中定义的,而是在 HTML 标准中定义的;
javascript代码运行时,任务被分为两种,宏任务(MacroTask/Task)和微任务(MircoTask);Event Loop在执行和协调各种任务时也将任务队列分为Task Queue和MircoTask Queue分别对应管理宏任务(MacroTask/Task)和微任务(MircoTask);作为队列,Task Queue和MircoTak Queue也具备队列特性:先进先出(FIFO—first in first out)。
流程描述:宏任务和微任务是相对而言的,根据代码执时循环的先后,将代码执行分层理解,在每一层(一次)的事件循环中,首先整体代码块看作一个宏任务,宏任务中的 Promise(then、catch、finally)、MutationObserver、Process.nextTick就是该宏任务层的微任务;宏任务中的同步代码进入主线程中立即执行的,宏任务中的非微任务异步执行代码将作为下一次循环的宏任务时进入调用栈等待执行的;此时,调用栈中等待执行的队列分为两种,优先级较高先执行的本层循环微任务队列(MicroTask Queue),和优先级低的下层循环执行的宏任务队列(MacroTask Queue)!
注意:每一次/层循环,都是首先从宏任务开始,微任务结束;
微任务(MircoTask):
1. Promise中的then、catch、finally;
2.
MutationObserver(监视 DOM 变动的API) ;参考MDN
3. Process.nextTick(Node环境,通常也被认为是微任务);
宏任务(MacroTask/Task):
1. script中全部代码(整体);
2. DOM操作;
3. 用户交互操作;
4. 所有的网路请求;
5. 定时器相关的 setTimeout、setInterval 等;
🙂所有代码执行顺序如下,其中异步操作即:宏任务与微任务
🙂异步中的执行顺序:宏任务与微任务
细节说明:await 等价于 promise.then
// async函数内部不一定非得加await标识符,如果需要异步操作时才需要加的
// 调用带有async标识符的函数里面的代码可以直接执行(同步代码),异步函数属微任务队列稍后执行!!
async function tack1() {
throw Error('123')
}
async function tack2() {
return 100
}
// 这里的await加异步函数 等价于 promise.then
// await一行后面的函数全部相当于在callback里面的函数,即当做(微任务)异步函数处理,
// 如果函数报错,怎不会打印值。否则直接返回函数对应的值
let return1 = await tack1() // 报错,无返回值
let return2 = await tack2() // 未报错,返回对应的值 100
关键点举例说明:
async function aa () {
let res = await tack1()
console.log('await后的代码') // await后面的所有类型代码直接扔到微任务队列中,稍后执行
}
执行顺序与dom渲染对比
/****宏任务*****/
// 浏览器规定的
// DOM渲染后触发
/****微任务*****/
// ES6规定的
// DOM渲染前触发
采用原生js的方式渲染和jq的方式渲染,动图如下
可以直观看到属于
微任务的Promise执行在前,且在dom渲染之前
宏任务的setTimeout执行在后,且在dom渲染之后
1. 执行顺序: 微任务 > dom渲染 > 宏任务
2. 微任务比宏任务执行早的原因:
微任务:
ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
宏任务:
ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
jquery的方式去填充页面元素
原生js的方式去填充页面元素
附上我测试的代码,为了加深记忆,可自行去验证!!!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
// 微任务比红任务执行事件早!!!
// 实例说明:两种方式验证均为微任务promise先于DOM渲染
/*********原生**********/
const container = document.querySelector('#container');
container.innerHTML = 111;
Promise.resolve().then(() => {
console.log("Promise", container.childNodes);
alert("Promise.resolve()元素渲染了么", container.childNodes);
})
setTimeout(() => {
console.log("setTimeout", container.childNodes);
alert("setTimeout元素渲染了么", container.childNodes);
});
/****************/
/*******jquery*********/
// const container = $('#container');
// container.append("<p>123456</p>");
// Promise.resolve().then(() => {
// console.log("Promise", container.children.length);
// alert("Promise.resolve()元素渲染了么", container.children.length);
// })
// setTimeout(() => {
// console.log("setTimeout", container.children.length);
// alert("setTimeout元素渲染了么", container.children.length);
// });
/****************/
</script>
</body>
</html>
示例代码,提供答案(可自行copy测试)
示例代码一:
-
在Promise中,一旦状态从pending变为fulfilled或rejected,就不可再次改变。因此,即使在setTimeout中调用resolve,由于Promise已经在resolve('p1')时变为fulfilled状态,所以后续的resolve('setTimeout1')并不会执行。
在这种情况下,setTimeout中的resolve('setTimeout1')将被忽略,因为Promise的状态已经在resolve('p1')时确定了。setTimeout中的代码会被放到事件队列中,等待主线程执行完毕后才会被执行。
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve, reject) => {
console.log(2)
resolve('p1')
new Promise((resolve, reject) => {
console.log(3)
setTimeout(() => {
resolve('setTimeout2')
console.log(4)
}, 0)
resolve('p2')
}).then(data => {
console.log(data)
})
setTimeout(() => {
resolve('setTimeout1')
console.log(5)
}, 0)
}).then(data => {
console.log(data)
})
console.log(6)
result: 2 3 6 p2 p1 1 4 5
示例代码二:
- 由于外层promise返回了一个新的Promise,事件循环会先处理这个内层Promise的微任务,因此 "2" 会先于 "1" 打印出来。
new Promise((resolve, reject) => {
resolve(1)
new Promise((resolve, reject) => {
resolve(2)
}).then(data => {
console.log(data)
})
}).then(data => {
console.log(data)
})
console.log(3)
result:3 2 1
遇到合适的例子继续补充。。。。。