为什么js是单线程
JavaScript
的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript
的主要用途是与用户互动,以及操作DOM
。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript
同时有两个线程,一个线程在某个DOM
节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript
就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker
标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM
。所以,这个新标准并没有改变JavaScript
单线程的本质。
任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
在CPU过于忙碌的情况下,比如IO
设备很慢,这时候不得不等IO结束,才能继续下一个任务。
预算,所有任务分为了两种,一种是同步任务
,另一种是异步任务
。
同步任务指的是在主线程上排队执行任务,前一个任务执行完才能进行下一个任务;
异步任务则不一样,不进到主线程,而且进入任务队列
的任务,只有任务队列通知主线程,异步任务才可以执行,该任务才会进入到主线程执行。
异步执行的运行机制如下:
- 所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,存在一个
任务队列
。只要异步任务有个运行结果,就在任务队列
中放置一个事件 - 一旦执行栈中的所有同步任务执行完毕,系统就会读取
任务队列
,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行 - 主线程不断重复上面的第三步
事件和回调函数
任务队列
是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在任务队列
中添加一个事件,表示相关的异步任务可以进入执行栈
了。主线程读取任务队列
,就是读取里面有哪些事件。
任务队列
中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入任务队列
,等待主线程读取。
所谓回调函数
,就是那些会被主线程挂起来的代码。异步任务必须指定回调函数
,当主线程开始执行异步任务
,就是执行对应的回调函数
。
事件循环
主线程从任务队列
中读取事件,这个过程是循环不断的,这种运行机制成为事件循环
Nodejs也是单线程的事件循环
,但机制不同于浏览器环境
- v8引擎解析js脚本
- 解析后的代码,调用Node API
-
libuv
库负责Node API的执行,将不同的任务分配给不同的线程,形成一个事件循环
,以异步的方式将任务的执行结果返回给v8引擎 - v8引擎再将结果返回给用户
宏任务和微任务
每个线程都有自己的事件循环
,所有都能独立运行,然而同源窗口共享一个事件循环
以同步通信。事件循环一直运行,来执行进入任务队列
中的宏任务。
宏任务
浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。
微任务
微任务通常来说就是需要在当前 task 执行结束后立即执行的任务,比如对一系列动作做出反馈,或或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。
解析如下例子:
setTimeout(() => {
console.log('5')
}, 0)
console.log('1');
new Promise((resolve) => {
console.log('2');
resolve()
}).then(() => {
console.log('3');
}).then(()=>{
console.log('4')
})
- 首先浏览器执行js进入第一个宏任务进入到主线程,遇到setTimeout分发到宏任务队列中
- 遇到console.log()直接执行, 输出1, 实例化的Promise方法里面的console.log也可直接执行, 输出2
- 判断是否有可执行的微任务,当前微任务队列有两个then里面的回调方法,先后打印3, 4
- 微任务全部执行完了,开始新的一个宏任务,而这个宏任务就是一开始加入到宏任务队列中的任务,放入到主线程执行,打印出5
网友评论