JavaScript中的线程
js为什么是单线程
JS单线程:同一个时间点只能执行一个任务(任务分类:宏任务和微任务);
JavaScript最初设计的执行环境是在浏览器端,需要操作DOM的变动。这一设计决定了JS要使用单线程。如果是多线程,一个线程正在操作DOM,另一个线程却正在删除DOM,就会造成冲突,浏览器无法决定该怎么执行。
如果执行同步问题使用多线程,执行任务造成非常繁琐。
⚠️注意:HTML5标准规定允许Javascript脚本创建多个线程,但是子线程完全受主线程操控,且不得操作DOM。
单线程的阻塞问题
单线程会出现阻塞问题,只有前一个任务完成之后才能执行下一个任务。这样会导致页面出现卡死状态,直接影响用户体验,出现了同步任务和异步任务的解决方案。
- 同步任务:在主线程排队的任务,只能前一个执行完毕,才执行下一个任务
- 异步任务:不进入主线程,而是进入“任务队列”的任务,只有“任务队列”通知主线程,该任务才会进入主线程执行。
JS如何实现异步编程
JavaScript异步任务执行,运行机制如下:
- 所有同步任务都在主线程执行,形成一个执行栈;
- 主线程之外,还存在一个“任务队列”,碰到了异步任务,就把该异步任务放置在“任务队列”中。
- 执行栈中的所有同步任务执行完毕,系统就会读取“任务队列”。“任务队列”里面对应的异步任务结束等待状态,进入执行栈,开始执行;
- 主线程会重复执行1,2,3步骤。
主线程读取任务队列的中事件,按照“先进先出”的数据结构,排在后面的事件,优先被主线程读取。执行栈只要为空,任务队列的第一个事件进入到主线程。如果存在定时任务,主线程需要定期检查定时器执行时间,到了时间才能进入主线程执行。
EventLoop事件循环机制
在深入事件循环机制之前,需要弄懂一下几个概念:
- 执行上下文(
Execution context
) - 执行栈(
Execution stack
) - 微任务(
micro-task
) -
执行上下文
执行上下文是对代码执行环境的一层抽象。js的执行上下文分三种:全局执行上下文,函数执行上下文,Eval执行上下文。
全局执行上下文:默认的基础上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事,创建一个全局对象(浏览器环境Window,node环境global);设置this的值等于这个全局对象。一个程序中只能有一个全局执行上下文。
- 函数执行上下文:每当一个函数被调用时,都会给该函数创建一个新的上下文。每个函数都有它独有的执行上下文,切记-函数执行上下文只是在函数被调用时创建。函数执行上下文可以有任意多个。
- Eval函数执行上下文:执行在eval函数内部的代码,有属于它自己的执行上下文。
执行栈
执行栈是栈类型的数据结构,具有先进后出的特性。栈的特性使得代码在执行的时候,遇到执行上下文就将其依次压入执行栈。
执行顺序,先执行栈顶的执行上下文中的代码,当栈顶的执行上下文代码执行完毕就会出栈,继续执行下一个位于栈顶的执行上下文。 ```javascript function foo() { console.log(“a”); bar(); console.log(“b”); }
function bar() { console.log(“c”); }
foo();
- 初始化执行栈为空;
- foo函数执行并进入执行栈,输出a,遇到函数bar;
- 执行bar再进入执行栈,开始执行bar函数,输出c
- bar函数执行完毕出栈,继续执行此时位于栈顶的函数foo,输出b;
- foo执行完毕出栈,所有执行栈内任务执行完毕;
<a name="XBg68"></a>
### 事件循环机制
<br />
- 同步和异步任务分别进入不同场所,同步任务进入call stack执行栈,异步进入task queue队列。
- 当主执行栈的代码执行完毕,call stack的任务为空时,会先执行Microtask queue(微任务后边详细介绍)队列中的任务,执行结束后会执行task queue中的事件,执行对应的回调函数。
- 如果循环形成js的事件循环机制(Event Loop)
<a name="xc360"></a>
## 宏任务(Macro)和微任务(Micro)
```javascript
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 500);
Promise.resolve()
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("script end");
输出结果:
script start
script end
promise1
promise2
setTimeout
JS 中分两种任务类型:macrotask
(宏任务) 和 microtask
(微任务),在 ECMAScript
中,microtask
称为 jobs
,macrotask
可称为 task;
宏任务(macrotask)
每次执行栈执行的任务就是宏任务(包括每次从任务队列中获取的事件回调并放到执行栈中执行);
- 每个宏任务会从头到尾将这个任务执行完毕。
- 浏览器为了能个让js内部宏任务和DOM任务有序执行,会在一个宏任务执行完毕后,在下一个宏任务执行开始前,对页面进行重新渲染reflow(宏任务 => 渲染 => 宏任务 => …)
微任务(microtask)
当前宏任务执行完毕立刻执行的任务。
- 当前宏任务结束后,下一个宏任务执行之前,在渲染之前执行。
- 响应速度比setTimeout(setTimeout是宏任务)快,无需等待渲染;
- 一个宏任务执行完毕后,就会将它执行期间产生的所有微任务都执行完毕。
什么样的场景会形成宏任务和微任务?
- macrotask
- 主代码块
- setTimeout
- setInterval
- … …
- microtask
- process.nextTick
- promise.then
- catch
- finally
- … …
在node环境下,process.nextTick的优先级高于Promise,可以理解为:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,然后才执行微任务中的Promise部分。
运行机制
以下任务执行顺序都是靠函数调用栈来模拟
- 事件循环机制从script标签内开始,整个script标签作为一个宏任务处理。
- 在代码执行的过程中,遇到宏任务,如setTimeout,就会将当前任务分发到对应的执行队列中
- 执行过程中遇到微任务,如promise,在创建Promise实例对象时,代码同步执行,如果到了then操作,该任务就会被分发到微任务队列中
- script标签内的代码执行完毕,同时执行过程中所涉及到的宏任务和微任务被分配到相应的队列。
- 第一个宏任务执行完毕,检查微任务队列执行所有存在的微任务;
- 微任务执行完毕,第一轮消息循环执行完毕,页面进行一次渲染;
- 然后开始第二轮消息队列循环,从宏任务队列中取出任务执行;
- 如果两个任务队列没有任务可执行,此时所有的任务执行完毕。
宏-微 任务执行顺序
用代码演示宏任务和微任务的概念。
题目一 ```javascript console.log(“1”);
setTimeout(() => { console.log(“2”); }, 1000);
new Promise((resolve, reject) => { console.log(“3”); resolve(); console.log(“4”); }).then(() => { console.log(“5”); });
console.log(“6”);
输出结果为:
```javascript
1
3
4
6
5
2
- 初始化状态,执行栈为空;
- 首先执行
<script>
标签内的同步代码,此时全局的代码进入执行栈中,同步顺序执行代码,输出 1; - 执行过程中遇到异步代码
setTimeout
(宏任务),将其分配到宏任务异步队列中; - 同步代码继续执行,遇到一个
promise
异步代码(微任务),但是构造函数中的代码为同步代码,依次输出3、4,则then
之后的任务加入到微任务队列中去; - 最后执行同步代码,输出 6;
- 因为
script
内的代码作为宏任务处理,所以此次循环进行到处理微任务队列中的所有异步任务,直达微任务队列中的所有任务执行完成为止,微任务队列中只有一个微任务,所以输出 5; - 此时页面要进行一次页面渲染,渲染完成之后,进行下一次循环;
- 在宏任务队列中取出一个宏任务,也就是之前的
setTimeout
,最后输出 2; - 此时任务队列为空,执行栈中为空,整个程序执行完毕;
主线程上添加宏任务与微任务
执行顺序:主线程 => 主线程上创建的微任务 => 主线程上创建的宏任务 ```javascript console.log(“start”);
setTimeout(() => { console.log(“setTimeout”); // 将回调代码放入另一个宏任务队列 }, 0);
new Promise((resolve, reject) => { for (let i = 0; i < 5; i++) { console.log(i); } resolve(); }).then(() => { console.log(“Promise实例成功回调执行”); // 将回调代码放入微任务队列 });
console.log(“end”);
输出结果为:
```javascript
start
0
1
2
3
4
5
end
Promise实例成功回调执行
setTimeout
微任务中创建微任务
执行顺序:主线程 => 主线程上创建的微任务 => 微任务上创建的微任务 => 主线程上创建的宏任务
setTimeout(_ => console.log(4)); // 宏任务
new Promise(resolve => {
resolve();
console.log(1); // 同步
}).then(_ => {
console.log(3); // 微任务
Promise.resolve()
.then(_ => {
console.log("before timeout"); // 微任务中创建微任务,在宏任务之前执行
})
.then(_ => {
Promise.resolve().then(_ => {
console.log("also before timeout");
});
});
});
console.log(2);
输出结果为:
1
2
3
before timeout
also before timeout
4
同时有两个微任务执行:
setTimeout(_ => console.log(5)); // 宏任务
new Promise(resolve => {
resolve();
console.log(1); // 同步
})
.then(_ => {
console.log(3); // 微任务
Promise.resolve()
.then(_ => {
console.log("before timeout"); // 微任务中创建微任务,在宏任务之前执行
})
.then(_ => {
Promise.resolve().then(_ => {
console.log("also before timeout");
});
});
})
.then(_ => {
console.log(4); // 微任务
});
console.log(2);
输出结果为:
1
2
3
before timeout
4
also before timeout
5
setTimeout(_ => console.log(5)); // 宏任务
new Promise(resolve => {
resolve();
console.log(1); // 同步
})
.then(_ => {
console.log(3); // 微任务
Promise.resolve()
.then(_ => {
console.log("before timeout1"); // 微任务中创建微任务,在宏任务之前执行
})
.then(_ => {
Promise.resolve().then(_ => {
console.log("also before timeout1");
});
});
})
.then(_ => {
console.log(4); // 微任务
Promise.resolve()
.then(_ => {
console.log("before timeout2"); // 微任务中创建微任务,在宏任务之前执行
})
.then(_ => {
Promise.resolve().then(_ => {
console.log("also before timeout2");
});
});
});
console.log(2);
输出结果为:
1
2
3
before timeout1
4
before timeout2
also before timeout1
also before timeout2
宏任务中创建微任务
执行顺序:主线程 => 主线程上创建的微任务 => 主线程上的宏任务 => 宏任务队列中创建的微任务
setTimeout(() => {
console.log("timer_1");
setTimeout(() => {
console.log("timer_3");
}, 500);
new Promise(resolve => {
resolve();
console.log("new promise");
}).then(() => {
console.log("promise then");
});
}, 500);
setTimeout(() => {
console.log("timer_2");
}, 500);
console.log("end");
输出结果为:
end
timer_1
new promise
promise then
timer_2
timer_3
微任务中创建的宏任务
执行顺序:主线程 => 主线程上创建的微任务 => 主线程上创建的宏任务 => 微任务中创建的宏任务
异步宏任务队列只有一个,当在微任务中创建一个宏任务之后,他会被追加到异步宏任务队列上(跟主线程创建的异步宏任务队列是同一个队列)
new Promise(resolve => {
console.log("new Promise(macro task 1)");
resolve();
}).then(() => {
console.log("micro task 1");
setTimeout(() => {
console.log("macro task 3");
}, 500);
});
setTimeout(() => {
console.log("macro task 2");
}, 1000);
console.log("Task queue");
输出结果为:
new Promise(macro task 1)
Task queue
micro task 1
macro task 3
macro task 2
new Promise(resolve => {
console.log("new Promise(macro task 1)");
resolve();
}).then(() => {
console.log("micro task 1");
setTimeout(() => {
console.log("macro task 3");
}, 1000);
});
setTimeout(() => {
console.log("macro task 2");
}, 1000);
console.log("Task queue");
输出结果为:
new Promise(macro task 1)
Task queue
micro task 1
macro task 2
macro task 3
【注意】:如果把 setTimeout(() => { // 宏任务2 console.log('macro task 2'); }, 1000)
改为立即执行setTimeout(() => { // 宏任务2 console.log('macro task 2'); }, 500)
那么它会在 macro task 3
之前执行,因为定时器是过多少毫秒之后才会加到事件队列里。
console.log("======== main task start ========");
new Promise(resolve => {
console.log("create micro task 1");
resolve();
}).then(() => {
console.log("micro task 1 callback");
setTimeout(() => {
console.log("macro task 3 callback");
}, 0);
});
console.log("create macro task 2");
setTimeout(() => {
console.log("macro task 2 callback");
new Promise(resolve => {
console.log("create micro task 3");
resolve();
}).then(() => {
console.log("micro task 3 callback");
});
console.log("create macro task 4");
setTimeout(() => {
console.log("macro task 4 callback");
}, 0);
}, 0);
new Promise(resolve => {
console.log("create micro task 2");
resolve();
}).then(() => {
console.log("micro task 2 callback");
});
console.log("======== main task end ========");
输出结果为:
======== main task start ========
create micro task 1
create macro task 2
create micro task 2
======== main task end ========
micro task 1 callback
micro task 2 callback
macro task 2 callback
create micro task 3
create macro task 4
micro task 3 callback
macro task 3 callback
macro task 4 callback
包含 async/await
demo1
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end"); // await 语句后面的加入到微任务队列中
}
async function async2() {
console.log("async2");
}
async1();
new Promise(resolve => {
console.log("create micro task");
resolve();
}).then(() => {
console.log("micro task callback");
});
console.log("script start");
输出结果为:
async1 start
async2
create micro task
script start
async1 end
micro task callback
demo2
async function job1() {
console.log("a");
await job2();
console.log("b"); // 添加到微任务队列
}
async function job2() {
console.log("c");
}
setTimeout(function () {
new Promise(function (resolve) {
console.log("d");
resolve();
}).then(function () {
console.log("e");
});
console.log("f");
});
job1();
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("g");
});
console.log("h");
输出结果为:
a
c
h
b
g
d
f
e
demo3
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
输出结果为:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
demo4
async function t1() {
console.log(1);
console.log(2);
new Promise(function (resolve) {
console.log("promise3");
resolve();
}).then(function () {
console.log("promise4"); // 微任务1
});
await new Promise(function (resolve) {
console.log("b");
resolve();
}).then(function () {
console.log("t1p"); // 微任务2
});
// await 语句后的加入微任务队列中1
// 微任务5
console.log(3);
console.log(4);
new Promise(function (resolve) {
console.log("promise5");
resolve();
}).then(function () {
console.log("promise6");
});
}
setTimeout(function () {
console.log("setTimeout");
}, 0);
async function t2() {
console.log(5);
console.log(6);
await Promise.resolve().then(() => console.log("t2p")); // 微任务4
// await 语句后的加入微任务队列中2
// 微任务6
console.log(7);
console.log(8);
}
t1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2"); // 微任务3
});
t2();
console.log("end");
输出结果为:
1
2
promise3
b
promise1
5
6
end
promise4
t1p
promise2
t2p
3
4
promise5
7
8
promise6
setTimeout
await
之后的代码必须等 await
语句执行完成后(包括微任务完成),才能执行后面的,也就是说,只有运行完 await
语句,才把 await
语句后面的全部代码加入到微任务行列,所以,在遇到 await promise
时,必须等 await promise
函数执行完毕才能对 await
语句后面的全部代码加入到微任务中。
所以,在等待 await Promise.then
微任务时:
- 运行其他同步代码;
- 等到同步代码运行完,开始运行
await promise.then
微任务; await promise.then
微任务完成后,把await
语句后面的全部代码加入到微任务行列;
await
语句是同步的,await
语句后面全部代码才是异步的微任务。
demo5
async function b() {
console.log("b");
await c();
// 加入微任务队列2
console.log("b1");
}
async function c() {
console.log("c");
await new Promise(function (resolve, reject) {
console.log("promise1");
resolve();
}).then(() => {
console.log("promise1-1"); // 微任务2
});
// 加入微任务队列1
setTimeout(() => {
console.log("settimeout1");
});
console.log("c1");
}
new Promise(function (resolve, reject) {
console.log("promise2");
resolve();
console.log("promise2-1");
reject();
})
.then(() => {
console.log("promise2-2"); // 微任务1
setTimeout(() => {
console.log("settimeout2");
new Promise(function (resolve, reject) {
resolve();
}).then(function () {
console.log("settimeout2-promise");
});
});
})
.catch(() => {
console.log("promise-reject");
});
b();
console.log("200");
输出结果为:
promise2
promise2-1
b
c
promise1
200
promise2-2
promise1-1
c1
b1
settimeout2
settimeout2-promise
settimeout1
执行步骤:
- 从上自下执行,
new Promise
函数立即执行 => 打印 :promise2
resolve()
将promise2-2
放入微任务队列中settimeout2
放入宏任务队列- 继续向下执行 => 打印 :
promise2-1
- 执行
b()
函数 => 打印 :b
- 执行
await c()
函数 => 打印:c
- 进入
await c()
函数中new Promise
函数 => 打印 :promise1
resolve()
将promise1-1
放入微任务队列中settimeout1
放入宏任务队列- 继续执行、无立即执行 => 打印 :
200
- 暂无执行任务,去微任务中执行第一个进入微任务的待执行动作 => 打印:
promise2-2
- 继续执行微任务中序列中待执行动作 => 打印:
promise1-1
- 微任务中暂无执行动作,继续执行
c()
函数中的待执行代码 => 打印:c1
c()
执行完毕,继续执行b()
函数中待执行代码 => 打印:b1
- 至此,立即执行以微任务执行完毕,执行宏任务队列中第一个进入的待执行任务 => 打印 :
settimeout2
- 宏任务一中,执行
new Promise
函数resolve()
将settimeout2-promise
放入微任务中,立即执行微任务 => 打印:settimeout2-promise
- 继续执行宏任务中待执行动作 => 打印:
settimeout1
总结:
- 微任务队列优先于宏任务队列执行;
- 微任务队列上创建的宏任务会被后添加到当前宏任务队列的尾端;
- 微任务队列中创建的微任务会被添加到微任务队列的尾端;
- 只要微任务队列中还有任务,宏任务队列就只会等待微任务队列执行完毕后再执行;
- 只有运行完
await
语句,才把await
语句后面的全部代码加入到微任务行列; - 在遇到
await promise
时,必须等await promise
函数执行完毕才能对await
语句后面的全部代码加入到微任务中;- 在等待
await Promise.then
微任务时:- 运行其他同步代码;
- 等到同步代码运行完,开始运行
await promise.then
微任务; await promise.then
微任务完成后,把await
语句后面的全部代码加入到微任务行列;
- 在等待