一、、同步任务(synchronous Tasks)、异步任务(Asynchronous Tasks)(宏任务与微任务)
前言
js 是一门单线程语言,所以任务都是按顺序执行的,只有当上一个任务结束后,才会执行下一个任务,不可同事执行多个任务。
javascript语言将任务的执行模式分为两种:同步任务和异步任务(宏任务和微任务都属于异步任务)
1、同步任务
所谓同步任务,就是非耗时任务,在主线程当中直接执行的任务
(1)变量赋值和运算:
let a = 5;
let b = a * 2;
console.log(b); // 输出 10
(2)直接调用的函数:
function greet() {
console.log('Hello, world!');
}
greet(); // 输出 "Hello, world!"
(3)循环和条件语句:
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 输出 0, 1, 2, 3, 4
(4)对象和方法调用:
const obj = {
name: 'Alice',
sayHello: function() {
console.log(`Hello, my name is ${this.name}!`);
}
};
obj.sayHello(); // 输出 "Hello, my name is Alice!"
(5)数组和字符串操作:
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((acc, val) => acc + val, 0);
console.log(sum); // 输出 15
const str = 'Hello';
const upperStr = str.toUpperCase();
console.log(upperStr); // 输出 "HELLO"
(6)当async/await的任务当中,并没有返回值时候
Promise.resolve().then(() => {
console.log(1);
});
let a = async () => {
await console.log(2);
return console.log(20);
};
a();
//输出结果:
2
1
20
注意:返回值是异步任务
2、异步任务
宏任务(macro-task)
script(理解为外层同步代码)、setTimeout/setInterval 、postMessage、requestAnimationFrame、MessageChannel、UI rending/UI事件、setmmediate与I/O(Node.js环境)
微任务(micro-task)
Promise.then()、await后面的代码、MutaionObserver(html5新特性)、proxy、process.nextTick(Node.js)
执行顺序
1、同步代码
2、所有微任务
3、第一个宏任务
4、所有新添加的微任务
5、下一个宏任务
核心要点
1、JavaScript引擎总是会先执行同步代码,然后再执行异步代码(同步先行,异步靠后)
2、微任务的优先级高于宏任务
3、微任务可以在Event Loop中插队
JS的执行机制是:
首先,判断JS是同步还是异步,同步进入主线程,异步进入Event table
其次,异步任务在Event table中注册函数,当满足特定的条件,被推入Event queue
最后,同步任务进入主线程后一直执行,直到主线程空闲后,才会去Event queue中查看是否有可执行的异步任务,如果有就推入主线程中执行。
循环以上三步执行,这就是Event loop。
执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
二、输出顺序问题
1、
console.log(1)
setTimeout(() => {
console.log(4)
}, 0);
new Promise(resolve=>{
console.log(5)
resolve(3)
}).then(res=>{
console.log(res)
})
console.log(2)
// 输出结果: 1 5 2 3 4
这里只有一个需要注意的地方就是Promise,.then方法才是微任务,console.log(5)是同步任务
2、
console.log(1);
setTimeout(() => {
console.log(4);
}, 0);
new Promise((resolve) => {
console.log(5);
resolve(3);
new Promise((resolve) => {
console.log(6);
resolve(7);
}).then((res) => {
console.log(res);
});
}).then((res) => {
console.log(res);
});
console.log(2);
//输出结果: 1 5 6 2 7 3 4
同步任务 1562 微任务:73 宏任务:4
这里需要注意的地方是Promise.then()这个微任务放到事件队列的顺序,并不是执行到resolve()方法就会将这个微任务加入到事件队列,而是要执行到.then方法的才会被加入到事件队列,所以 6 在 7 前面。(要被执行的微任务会被加入到事件队列,先进先出)
构造函数式同步执行的,.then是异步执行
3、
setTimeout(() => {
console.log(1);
}, 0);
async function test1() {
console.log(2)
}
async function test2() {
await test1()
console.log(3)
}
test2()
new Promise((resolve) => {
console.log(4);
resolve(5);
new Promise((resolve) => {
console.log(6);
resolve(7);
}).then((res) => {
console.log(res);
});
}).then((res) => {
console.log(res);
});
//输出结果:2 4 6 3 7 5 1
同步任务:246 微任务:357 宏任务:1
注意:await ,await后面一般跟一个Promise对象,如果后面不是Primise对象会被转成Promise对象,await会暂停当前的async function的执行,等待await表达式后面的Promise处理完成后才会继续执行。
3、
document.onclick = function() {
console.log("1");
};
console.log(2);
// 3秒之后才会提交到任务队列中
setTimeout(function() {
console.log(3);
}, 3000);
setTimeout(() => {
console.log(4);
}, 0);
同步任务:2 宏任务:3 4
(1)没有点击: 输出结果: 2 4 3
(2)3秒内点击:输出结果:2 4 1 3
(3)3秒后点击:输出结果 2 4 3 1
三、常见问题
1、为什么说script是一个宏任务?
script是脚本,这被当作是一个宏函数。
<!-- 脚本 1 -->
<script>
console.log('1-1')
setTimeout(() => console.log('1-2'), 0)
new Promise((resolve, reject) => {
console.log('1-3')
resolve('1-4')
}).then((res) => {
console.log(res)
})
console.log('1-5')
</script>
<!-- 脚本 2 -->
<script>
console.log('2-1')
setTimeout(() => console.log('2-2'), 0)
new Promise((resolve, reject) => {
console.log('2-3')
resolve('2-4')
}).then((res) => {
console.log(res)
})
console.log('2-5')
</script>
//输出结果 :1-1、1-3、1-5、1-4、2-1、2-3、2-5、2-4、1-2、2-2
2、宏任务与微任务的理解和差异
微任务宏任务差异表
宏任务 | 微任务 | |
---|---|---|
是否重新渲染页面 | 会 | 不会 |
是否需要其他异步线程的支持 | 需要 | 不需要 |
宏任务与微任务的发起者 | 宿主(node、浏览器) | js引擎 |
具体事件 | script、setTimeout/setInterval、postMessage、MessageChannel、requestAnimaionFrame、UI rending/UI事件、setImmediate与I/O(Node.js环境) | Promise.then()、await 后面的代码、MutaionObserver(html5新特性)、proxy、process.nextTick(node.js) |
3、宏任务与微任务产生的误差
console.log(1)
new Promise((resolve,reject)=>{
console.log(2)
resolve(3)
}).then(res=>{
console.log(res)
const targetTime = new Date().getTime()+3000
let isEnd = false
// 延时三秒
while(!isEnd){
const currentTime = new Date().getTime()
if (targetTime <= currentTime){
isEnd = true
}
}
})
setTimeout(() => console.log(4), 0)
console.log(5)
//执行结果:1 2 5 3 4
注意 console.log(4)是在3秒后被输出
同步任务:1 2 5 微任务:3 宏任务:4
因为在执行promise微任务耗费了3秒, setTimeout的第二个参数仅仅表示最少延迟时间,而非确切的时间。
其他具有回调方法的宏任务也基本是这样。 即使是在异步任务中的做费时等的延迟操作,也会影响到同为异步任务的宏任务,在代码开发中一定要注意比较耗时的代码所产生的影响!
4、通过一个例子加深了解
<body>
<div class="demo"></div>
<script>
window.onload = function() {
const div = document.querySelector(".demo");
Promise.resolve().then(() => {
alert("promise0");
});
alert('同步0')
// 宏任务1
setTimeout(() => {
Promise.resolve().then(() => {
alert("promise1");
});
div.textContent = "元素div1";
alert("settimeout1");
}, 0);
// 宏任务2
setTimeout(() => {
div.textContent = "元素div2";
alert("settimeout2");
}, 0);
};
</script>
</body>
//输出结果:
(1)alert('同步0') (同步任务)
(2)alert("promise0");(微任务)
(3)alert("settimeout1");(settimeout1宏任务)
(4)div.textContent = "元素div1";(页面渲染:页面中的'元素div1'被渲染展示)
(5)alert("promise1");(settimeout1宏任务中的微任务)
(6)alert("settimeout2");(settimeout2宏任务)
(7)div.textContent = "元素div2";(页面渲染:页面中的文字'元素div1'=>'元素div2')
之所以用alert,是因为alert会阻塞线程,方便我们观察页面渲染,这个例子主要想表达:
每个宏任务执行完毕并清空里面的微任务后,会进行一次页面渲染
参考:
https://zhuanlan.zhihu.com/p/618003341
https://baijiahao.baidu.com/s?id=1761241446490028512&wfr=spider&for=pc
https://www.cnblogs.com/sunupo/p/15566501.html
https://blog.csdn.net/qq_41131745/article/details/127004662
https://www.cnblogs.com/guoyeqiang/p/8317582.html
https://cloud.tencent.com/developer/article/2336215