首先,JavaScript是单线程语言
1. 进程和线程的关系
一个程序至少有一个进程,而一个进程至少有一个线程
线程是进程的实体,是CPU调度和分派的基本单位,可以看成实际在干活运算的是线程,而进程只是一个或多个线程的资源分配和调度的系统单位
进程如果是一个工厂,线程则是工厂里干活的工人
工人共享一个劳动空间,共享劳动工具
就是说进程里,有一个或多个线程,各线程共享同个内存空间和数据
可以看看阮一峰的《进程与线程的一个简单解释》
2. 浏览器是多线程
- GUI渲染线程
- JS引擎线程
- 事件触发线程
- 定时触发线程
- 异步http请求线程
其中,GUI线程和JS线程,是互斥!也就是某些页面在做JS运算时,会导致DOM刷新卡住的根源。
所以当JS线程在执行运算时,GUI线程是挂起的
早期通过在代码增加setTimeout
来解决渲染卡顿问题
3. Javascript是单线程
JavaScript可以同时执行多个JS文件代码,看似多线程同时执行,其实还是单线程,只不过是分隔成多个任务,在不同任务跳转进行运算
Javascript的执行机制称为Event Loop
任务又分为同步和异步
同步任务都在主线程上执行,遇到异步任务,则丢到任务队列,接着继续执行同步任务,等到同步任务执行完毕,主线程空闲了,才会回过头去查看任务队列
(图片搜自谷歌)
这也就是 setTimeout(() => {}, 0)
并不会立马执行,而且延迟时间并不准确的原因,因为主线程还没空闲去执行任务队列
1 | setTimeout(() => { |
执行时,并不是说放到任务队列,1000ms后执行,而是,由定时器线程1000ms后才丢到队列!
常用到的 setInterval
, 也是一样的逻辑
4. 宏任务、微任务
宏任务 macrotask,包括主线程、setTimeout、setInterval、requestAnimationFrame
微任务 microtask,包括Promise、process.nextTick、MutationObserver,还有已废弃的Object.observe
前面说了通过在代码增加setTimeout
来解决渲染卡顿问题
因为JS线程和GUI线程胡扯,所以执行的时候其实是这样
JS任务 => 渲染DOM => JS任务 => 渲染DOM
所以通过setTimeout
把长运算代码切片处理,防止JS执行时间太长造成页面假死
而微任务则是在 渲染 之前执行的,简单说,在主代码执行完后,执行渲染,然后再去瞧任务队列
但是如果有微任务,则是执行主代码后,先执行微任务,然后渲染,再是任务队列
1 | console.log('begin'); |
执行结果
1 | begin |
可以看出Promise.resolve
是在setTimeout
之前执行,网上有很多示例代码,套了很多层,实际工作中并用不到