1. JavaScript 是一门单线程语言
JavaScript 是一门单线程语言,所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
虽然JavaScript是单线程运行的,但是还是存在其他线程的;例如:处理Ajax请求的线程、定时器的线程、读写文件的线程(node.js
中)等。
2. 同步任务和异步任务
"同步任务"就是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务。
"异步任务"是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务。
3. Event Loop(事件循环)
-
所有任务都在主线程上执行,形成一个执行栈。
-
主线程发现有异步任务,就在“任务队列”之中加入一个任务事件。
-
一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”(先进先出原则)。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
-
主线程不断重复上面的第三步,这样的一个循环称为事件循环。
4. 宏任务与微任务
如果任务队列中有多个异步任务,那么先执行哪个任务呢?
于是在异步任务中,也进行了等级划分,分为宏任务(macrotask)和微任务(microtask)。
不同的API注册的任务会依次进入自身对应的队列中,然后等待事件循环将它们依次压入执行栈中执行。
4.1 宏任务包括:
整体的JavaScript代码
setTimeout
,setInterval
,setImmediate
,I/O
UI rendering
4.2 微任务包括:
process.nextTick
Promise
就先记住这个!!Object.observe
(已废弃)MutationObserver
(html5新特性)
4.3 运行机制
在事件循环中关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)。
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中。
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行,一轮事件循环结束)。
- 开始下一个宏任务。
5. 例题
console.log('start')
setTimeout(function() {
console.log('timeout');
}, 0)
new Promise(function(resolve) {
console.log('promise');
//注意这边调用resolve
//不然then方法不会执行
resolve()
}).then(function() {
console.log('then');
})
console.log('end');
- 刚开始打印
start
- 遇到
setTimeout
,放入宏任务中,等待执行 - 遇到
new Promise
的回调函数,同步执行,打印promise
- 当
resolve
后,then
方法会放入微任务,等待执行 - 打印
end
,这时整个执行栈清空了,宏任务和微任务队列各有一个回调方法 - 先执行微任务队列,打印
then
- 执行
宏任务
,打印timeout
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
- 发现
setTimeout
,放入宏任务1 - 打印
async1 start
- 打印
async2
- 把
await async2
函数后面的回调放入微任务1 - 打印
promise1
把
then中的函数放入微任务2
- 打印
script end
- 调用栈清空,开始执行微任务1,打印
async1 end
- 执行微任务2,打印
promise2
- 微任务执行完,第一轮循环结束
- 开始宏任务1,打印
setTimeOut
- 结束。