1、参考文章:
2、为什么JavaScript是单线程的?
单线程意思就是说同一个时间只能做一件事。那这样的话效率不是很低?也没有啦,其实javascript的单线程特点是跟他的用途有关的。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。假如不是单线程的话,在一个线程当我们在给某个DOM节点增加内容的时候,另一个线程正在删除这个DOM节点的内容,那还得了,那不是乱套了吗。所以javascript只能是单线程。
虽然javascript是单线程,但是javascript中有同步和异步的概念,解决了js阻塞的问题。avascript就可以进行同步任务和异步任务。把读文件这种操作,ajax请求这些需要耗时的任务放到任务队列中,我还是能够一步步的继续下面的任务。
3、js中的事件循环(Event Loop)
Event Loop
是指在js
执行环境中存在主执行线程和任务队列(Task Queue
),其中所有同步任务都在主执行线程中形成一个执行栈,所有异步任务都会放到任务队列中。Event Loop
会经历如下过程:
- 主线程执行同步任务,在主线程执行过程中,不断形成堆栈并执行出栈入栈的操作
- 主线程任务是否执行完毕,如否,继续循环第1步,如是,则执行下一步
- 系统读取任务队列里的任务,进入执行栈,开始执行
- 不断循环执行前三步
4、浏览器环境
异步任务分为task(宏任务,也可称为macroTask)和microtask(微任务)两类。 当满足执行条件时,task和microtask会被放入各自的队列中等待放入主线程执行,我们把这两个队列称为Task Queue(也叫Macrotask Queue)和Microtask Queue。
- task:script中代码、setTimeout、setInterval、I/O、UI render。
- microtask: promise、Object.observe、MutationObserver。
执行过程:
- 执行完主执行线程中的任务。
- 取出Microtask Queue中任务执行直到清空。
- 取出Macrotask Queue中一个任务执行。
- 取出Microtask Queue中任务执行直到清空。
- 重复3和4。
即为同步完成,一个宏任务,所有微任务,一个宏任务,所有微任务......
5、node环境
各个阶段执行的任务如下:
timers
阶段: 这个阶段执行setTimeout
和setInterval
预定的callback
;- I/O callbacks 阶段: 执行除了
close
事件的callbacks
、被timers
设定的callbacks
、setImmediate()
设定的callbacks
这些之外的callbacks
; idle, prepare
阶段: 仅node
内部使用;poll
阶段: 获取新的I/O
事件, 适当的条件下node
将阻塞在这里;check
阶段: 执行setImmediate()
设定的callbacks
;close callbacks
阶段: 执行socket.on('close', ...)
这些callback
6、测试代码
function sleep(time) {
let startTime = new Date()
while (new Date() - startTime < time) {}
console.log('1s over')
}
setTimeout(() => {
console.log('setTimeout - 1')
setTimeout(() => {
console.log('setTimeout - 1 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then - then')
})
})
sleep(1000)
})
setTimeout(() => {
console.log('setTimeout - 2')
setTimeout(() => {
console.log('setTimeout - 2 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then - then')
})
})
sleep(1000)
})
- 浏览器输出
setTimeout - 1 //1为单个task
1s over
setTimeout - 1 - then
setTimeout - 1 - then - then
setTimeout - 2 //2为单个task
1s over
setTimeout - 2 - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over
- node输出
setTimeout - 1
1s over
setTimeout - 2 //1、2为单阶段task
1s over
setTimeout - 1 - then
setTimeout - 2 - then
setTimeout - 1 - then - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over