JS是单线程的
avaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
------阮老师
JS运行机制
要了解事件循环和任务队列,弄明白JS的运行机制就明白了
- 所有的同步任务都在“主线程”上执行,形成一个“执行栈”。
- 在主线程之外,还有一个叫“任务队列”的东西,只要有异步任务执行完毕,就会把运行结果放在“任务队列”中“排队”。
- 当“执行栈”中的所有任务都执行完了,系统就会看看“任务队列”中有没有正在排队的事件,如果有就开始按照排队的顺序处理这些事件。
主线程: JS是单线程的,这个线程就被成为主线程,执行代码用的。主线程会先执行执行栈中的东西,当执行栈为空时,才会看任务队列中是否存在需要被执行的代码,如果有,就把任务队列中的东西按顺序拿到执行栈中执行
执行栈: 执行栈是一个用来存放等待执行的同步任务的地方,当我们调用一个方法(<script><function>等)时,JS会生成一个与这个方法相对应的执行环境,也叫执行上下文(执行环境不是执行栈),执行环境中存在着这个方法的私有参数、作用域等东西,因为JS是单线程的,所以当很多方法被依次调用时,JS会先 解析 这些方法,把同步任务放在“执行栈”中排队
任务队列: 任务队列也叫消息队列,回调队列,异步操作会被放在任务队列中等待执行
事件循环
在“JS运行机制”中,主线程每次清空一次“执行栈”后,就算是执行了一次事件循环。
主线程每次清空栈后,就去任务队列中检查是否有任务,如果有,就每次取出一个放在执行战中执行,一直循环,这个过程被称为“事件循环”
经典老图.jpg
事件循环和宏任务、微任务的关系
经典老图2.jpg
宏任务和微任务
宏任务: 每一次执行栈执行的代码就是一个宏任务
常见宏任务
- script(整体代码)
- setTimeout
- setInterval
微任务: 在每一次宏任务执行结束后执行的任务(在下一个宏任务之前,也在渲染之前)
常见微任务
- Promise.then
- async await
执行顺序
执行栈 | 宏任务(执行栈) | 微任务(如果有的话) | 宏任务 | 微任务 | 宏任务 | 微任务 | … | |
---|---|---|---|---|---|---|---|---|
备注 | 最初js代码 | 任务队列中的宏任务放在执行栈中执行 |
例子
async function ac() {
console.log('ac-start')
await ac2();
console.log('ac-end')
}
async function ac2 () {
console.log('ac2')
}
setTimeout(() => {
console.log('setTimeout')
new Promise(resolve => {
console.log('promise-body')
resolve()
}).then(() => {
console.log('promise-then')
})
}, 0)
setTimeout(() => { // setTimeout2
console.log('setTimeout2')
})
ac()
console.log('window')
- 首先是第一遍事件循环
- 从第一行开始往下走,首先创建函数ac和函数ac2,没有调用,所以没有执行,
- 往下走 看到了setTimeout,setTimeout的第二个形参为"0",也就是说延时0秒执行,所以将它在0秒后排到任务队列中(0秒或者为空都不会立即执行,而是放在任务队列中)
- 再往下走,又是一个setTimeout,我们暂时叫他setTimeout2,同样当做宏任务放在任务队列中
- 接下来是ac(),调用ac函数,
- 在ac函数中,首先输出log‘ac-start’,接下来是ac2(),开始执行ac2
- ac2输出log‘ac2’,没有返回值,返回undefined,回到ac函数中
- 由于ac函数时async函数,在await后的内容放在微任务队列中
- ac()调用完毕后继续往下走,输出log‘window’
- 主进程走完后,看一下是否有微任务,有的话按顺序执行微任务,于是开始继续执行ac函数,输出log‘ac-end’,然后ac函数返回undefined
- 第一遍事件循环结束,现在输出ac-start — ac2 — window — ac-end
- 第二遍事件循环,从任务队列中取第一个,也就是setTimeout中的函数,开始执行
- 首先输出log‘setTimeout’
- 往下走看见promise函数,执行函数中的内容
- 第一行输出log‘promise-body’
- 第二行promise函数返回resolve,把then中的内容放在微任务队列中
- setTimeout执行结束,开始查看有没有微任务,执行微任务
- 执行then中的函数,输出log‘promise-then’
- 第二遍事件循环结束,现在输出ac-start — ac2 — window — ac-end — setTimeout — promise-body — promise-then
- 第三遍事件循环,从任务队列中取出第一个(也是最后一个),setTimeout2中的函数,开始执行
- 输出log‘setTimeout2’
- 在之后没有宏任务额微任务了,执行结束
- 最终输出 ac-start — ac2 — window — ac-end — setTimeout — promise-body — promise-then — setTimeout2