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)```javascriptconsole.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 startscript endpromise1promise2setTimeout
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”);
输出结果为:```javascript134652
- 初始化状态,执行栈为空;
- 首先执行
<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”);
输出结果为:```javascriptstart012345endPromise实例成功回调执行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);
输出结果为:
123before timeoutalso before timeout4
同时有两个微任务执行:
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);
输出结果为:
123before timeout4also before timeout5
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);
输出结果为:
123before timeout14before timeout2also before timeout1also 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");
输出结果为:
endtimer_1new promisepromise thentimer_2timer_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 queuemicro task 1macro task 3macro 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 queuemicro task 1macro task 2macro 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 1create macro task 2create micro task 2======== main task end ========micro task 1 callbackmicro task 2 callbackmacro task 2 callbackcreate micro task 3create macro task 4micro task 3 callbackmacro task 3 callbackmacro 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 startasync2create micro taskscript startasync1 endmicro 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");
输出结果为:
achbgdfe
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 startasync1 startasync2promise1script endasync1 endpromise2setTimeout
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// 微任务5console.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// 微任务6console.log(7);console.log(8);}t1();new Promise(function (resolve) {console.log("promise1");resolve();}).then(function () {console.log("promise2"); // 微任务3});t2();console.log("end");
输出结果为:
12promise3bpromise156endpromise4t1ppromise2t2p34promise578promise6setTimeout
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();// 加入微任务队列2console.log("b1");}async function c() {console.log("c");await new Promise(function (resolve, reject) {console.log("promise1");resolve();}).then(() => {console.log("promise1-1"); // 微任务2});// 加入微任务队列1setTimeout(() => {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"); // 微任务1setTimeout(() => {console.log("settimeout2");new Promise(function (resolve, reject) {resolve();}).then(function () {console.log("settimeout2-promise");});});}).catch(() => {console.log("promise-reject");});b();console.log("200");
输出结果为:
promise2promise2-1bcpromise1200promise2-2promise1-1c1b1settimeout2settimeout2-promisesettimeout1
执行步骤:
- 从上自下执行,
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语句后面的全部代码加入到微任务行列;
- 在等待

