在初次入门学习和使用 JavaScript 的过程中,相信遇到过许多程序执行顺序及结果与预期不一致的问题,在查阅资料的过程中了解到原来是程序的执行有同步与异步之分;与此同时也会看到许多有关概念,例如回调函数、执行栈、任务队列、事件循环机制(Event Loop)、宏任务、微任务、Promise(ES6)等等。此时对于一个刚入门不久的小白来说,要理解消化这些概念真的不容易。对于入门不久的我来说也一样,所以写一篇博客记录一下,有关 JavaScript 的运行机制,以及上述的这些概念为什么会出现,又解决了什么问题。
我们知道多线程是可以并行执行程序的,能提高程序运行效率。但是 JS 是一门单线程语言,同一时间内做一件事。
最初作为服务于浏览器的脚本语言,很多时候都是在与用户交互,这个过程涉及了许多 DOM 的操作,倘若使用多线程,那么就容易出现几个线程同时操作一个 DOM 的问题,那么浏览器此时要以哪一个线程为主呢?这样一来无疑增加了复杂性,所以 JS 成为了单线程。虽然说多线程处理起来也很高效,但对于当时直接服务于浏览器用户的 JS 来说,尽可能避免过度复杂,能更简单的处理相对好点吧。
虽然单线程降低了复杂性,但是也有了新的问题。单线程是顺序执行程序,每一个任务要等待上一个任务执行完毕才执行,如果遇到执行时间太长或者出现了别的问题,那么就会一直卡在那,导致整个程序无法顺利执行完毕。为了解决问题,语言设计者希望在程序执行时,将一些耗时、有延迟的任务先挂起,让能快速执行完毕的任务先执行;按照这样的方式执行完整个程序后,在返回去执行那些被挂起的任务。因此有了同步任务与异步任务之分;在执行过程中,当前执行程序的线程称为主线程,同步任务直接在主线程立即执行,而那些异步任务,先给它挂在一边放着,等到主线程执行完了所有同步任务,再回来读取挂在一旁的异步任务,并且执行他们。
任务队列是一系列事件组成的一个队列,也就是上面说到的异步任务挂起的地方。程序执行时会将定义的异步任务送入任务队列,或者用户点击鼠标触发的异步任务送入队列。等待主线程来执行它们。例如常见的各种事件(鼠标点击、键盘敲击、滚动等等)、又或者是 Ajax 那样等待响应的异步任务。
实际上,任务队列不止一种,因为处理的异步任务种类可能不同
回调函数往往就是异步任务所定义的代码。主线程执行完同步任务,就会回来开始读取任务队列中的异步任务并执行这些代码,同时也称为回调函数。
异步任务又可以看为两种,通常由宿主环境(浏览器、node)提供的为宏任务,由语言标准提供的为微任务。 JavaScript 可能会在不同的宿主环境下运行,所以宏任务来自于宿主环境,而微任务作为语言标准,在任何环境下都可以使用。
主线程执行程序时会将定义的异步任务放入任务队列中,宏任务会放在宏任务队列,微任务放在微任务队列,当触发 UI 事件时,也会把相应任务放入队列。为了确保事件处理正常进行,主线程不阻塞。所以有了解决方案 Event Loop,事件循环线程是独立于主线程的,并且一直存在直到整个脚本环境被关闭。无论是主线程执行时添加的异步任务,还是 UI 交互触发后添加的异步任务,事件循环机制都会按一定规则循环读取并且执行。
那么该循环机制如何运行呢?
console.log('1-1');
Promise.resolve().then(() => console.log('微任务 1-1'));
new Promise((resolve) => {
console.log('1-2');
resolve();
}).then(() => {
console.log('微任务 1-2')
});
setTimeout(() => console.log('宏任务 1-1'), 100);
console.log('1-3');
//1-1
//1-2
//1-3
//微任务 1-1
//微任务 1-2
//宏任务 1-1
到这里其实会发现,微任务都会紧跟在当前执行栈执行同步任务后执行,而存好的宏任务被放在下次执行,好似重新开始一样。
按个人总结来就是(不一定对),主线程的执行栈是专门用来执行代码的;当事件循环线程读取到一个宏任务时,将其放入执行栈执行,主线程会执行其中定义的同步任务,将遇到的宏任务和微任务存起来,在本次同步任务执行完之后立即执行微任务。而此次存好的宏任务又会按照相同的方式在下一次循环中进行。因为事件循环机制一次循环只读取执行一个宏任务。
由此看来其实整个程序也可以看成是一个宏任务,而首次添加的宏任务和微任务是按照上面的方式一层层刨开,按照一次执行一个宏任务和里面所有微任务的规则进行
console.log('开始执行主线程');
console.log('0-1');
Promise.resolve().then(() => console.log('微任务 0-1\n-----'));
setTimeout(() => {//宏任务 1
console.log('第一个宏任务');
console.log('宏任务 1-1');
Promise.resolve().then(() => console.log('微任务 1-1'));
Promise.resolve().then(() => console.log('微任务 1-2\n-----'));
setTimeout(() => {//宏任务3
console.log('第三个宏任务');
console.log('宏任务 3-1')
Promise.resolve().then(() => console.log('微任务 3-1\n-----'))
},10);
},100);
setTimeout(() => {//宏任务2
console.log('第二个宏任务');
console.log('宏任务2-1');
Promise.resolve().then(() => console.log('微任务 2-1\n-----'));
},100);
console.log('0-2');
***************************
执行结果
开始执行主线程
0-1
0-2
微任务 0-1
-----
第一个宏任务
宏任务 1-1
微任务 1-1
微任务 1-2
-----
第二个宏任务
宏任务 2-1
微任务 2-1
-----
第三个宏任务
宏任务 3-1
微任务 3-1
-----
大概流程图
提示,虽然说是一次循环只读取一个宏任务,但是他没说要等当前宏任务执行完才进行下一次循环哦!!,事件循环读取到队列中的任务并且让它开始执行后,就可以开始下次循环,不需要等待
console.log(1);
Promise.resolve().then(() => console.log(2));
setTimeout(() => {
console.log(3);
Promise.resolve().then(() => console.log(4));
setTimeout(() => {
console.log(5);
},10);
},200);
setTimeout(() => {
console.log(6);
Promise.resolve().then(() => console.log(7));
setTimeout(() => {
console.log(8)
}, 300);
},100);
console.log(9);
自己在纸上写了一下,将代码在浏览器上运行之后对比,发现完全正确。你也可以自己写一下哦。
有一种情况,那就是 then() 之后接着 then() ,那么此时的顺序呢?
console.log('1-1');
Promise.resolve().then(() => console.log('微任务 1-1')).then(() => console.log('微任务 1-3'));
new Promise((resolve) => {
console.log('1-2');
resolve();
}).then(() => {
console.log('微任务 1-2')
}).then(() => console.log('微任务 1-4'));
setTimeout(() => console.log('宏任务 1-1'), 100);
//1-1
//1-2
//1-3
//微任务 1-1
//微任务 1-2
//微任务 1-3
//微任务 1-4
//宏任务 1-1
可以看到他的运行顺序,说明在 then() 执行之后,如果后面还接着 then() 那么按照同样的方式添加到微任务队列,等到之前添加的第一层 then() 都执行完后,在到微任务队列里面读取后面添加的 then(),运行方式如上。并且只有当微任务队列为空时,事件循环机制才会进行到下一轮并读取新的宏任务。
阮一峰的网络日志 JavaScript 运行机制详解:再谈Event Loop
知乎作者:tigerHee js中的宏任务与微任务
博客园作者:daisy,gogogo JavaScipt 中的事件循环 event loop,以及微任务和宏任务的概念
国外作者写的一篇文章 Tasks, microtasks, queues and schedules
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有