javascript是单线程的,可以理解为同一个时间只能做一件事情。
原因:。js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要移动这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,保证程序执行的一致性。JavaScript就被设计为单线程。
线程和进程:
进程(process)和线程(thread)是操作系统的基本概念,
进程:CPU进行资源分配的基本单位
线程:CPU调度的最小单位,是建立在进程的基础上运行的单位,共享进程的内存空间
进程特征:
- 进程依赖于程序运行而存在,进程是动态的,程序是静态的;
- 进程是操作系统进行资源分配和调度的一个独立单位(CPU除外,线程是处理器任务调度和执行的基本单位);
- 每个进程拥有独立的地址空间,地址空间包括代码区、数据区和堆栈区,进程之间的地址空间是隔离的,互不影响。
线程特性:
- 原子性
- 可见行
- 有序性
进程VS线程
1.区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。
2.从属关系不同:进程中包含了线程,线程属于进程。
3.开销不同:进程的创建、销毁和切换的开销都远大于线程。
4.拥有资源不同:每个进程有自己的内存和资源,一个进程中的线程会共享这些内存和资源。
5.控制和影响能力不同:子进程无法影响父进程,而子线程可以影响父线程,如果主线程发生异 常会影响其所在进程和子线程。
6.CPU利用率不同:进程的CPU利用率较低,因为上下文切换开销较大,而线程的CPU的利用率较高,上下文的切换速度快。
7.操纵者不同:进程的操纵者一般是操作系统,线程的操纵者一般是编程人员。
浏览器进程与线程
1.Browser进程:浏览器的主进程(负责协调、主控),只有一个。
主要作用:
- 负责浏览器界面显示,与用户交互。如前进,后退等
- 负责各个页面的管理,创建和销毁其他进程
- 将渲染(Renderer)进程得到的内存中的Bitmap(位图),绘制到用户界面上
- 网络资源的管理,下载等
2、第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
3、GPU进程:最多一个,用于3D绘制等
4、浏览器渲染进程(即通常所说的浏览器内核)(Renderer进程,内部是多线程的):主要作用为页面渲染,脚本执行,事件处理等
5、网络进程:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立处理,成为单独一个进程。
浏览器渲染进程又包含五个线程:
(1)GUI渲染线程
当浏览器收到响应的html后,该线程开始解析HTML文档构建DOM树,解析CSS文件构建CSSOM,合并构成渲染树,并计算布局样式,绘制在页面上(HTML解析规则,CSS解析规则,渲染流程细节)
当界面样式被修改的时候可能会触发reflow和repaint,该线程就会重新计算,重新绘制,是前端开发需要着重优化的点。
注意:GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
(2)JS引擎线程
JS引擎线程也称为JS内核,负责处理Javascript脚本程序,解析Javascript脚本,运行代码;
JS引擎线程一直等待着任务队列中任务的到来,然后加以处理,一个Tab页中无论什么时候都只有一个JS引擎线程在运行JS程序;
注意:GUI渲染线程与JS引擎线程的互斥关系,所以如果JS执行的时间过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。
(3)事件触发线程
归属于渲染进程而不是JS引擎,用来控制事件轮询(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
当JS引擎执行代码块如鼠标点击、AJAX异步请求等,会将对应任务添加到事件触发线程中
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理任务队列的队尾,等待JS引擎的处理
注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
(4)定时器触发进程
即setInterval与setTimeout所在线程;
浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;
因此使用单独线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行,所以定时器中的任务在设定的时间点不一定能够准时执行,定时器只是在指定时间点将任务添加到事件队列中;
注意:W3C在HTML标准中规定,定时器的定时时间不能小于4ms,如果是小于4ms,则默认为4ms。
(5)异步http请求线程
XMLHttpRequest连接后通过浏览器新开一个线程请求;
检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行;
event loop 事件循环
因为js单线程的特点,如果某段程序需要等待一会再执行,后面的程序都会被阻塞,这样也就带来了一些问题。 为了解决这个问题,js把任务分为了两种:同步任务、异步任务。两种任务的差异就在于执行的优先级不同。event loop
就是对任务的执行顺序做了详细的规范。
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,后一个任务才会执行;
异步任务:不进入主线程、而进入任务队列的任务,只有当主线程上的所有同步任务执行完毕之后,主线程才会读取任务队列,开始执行异步任务。异步任务又分为宏任务、微任务。
执行顺序: 同步任务 -> 微任务 -> 宏任务
区别:异步不会阻塞程序的执行,同步会阻塞程序的执行
什么是 event loop
事件循环(event loop)就是 任务在主线程不断进栈出栈的一个循环过程。任务会在将要执行时进入主线程,在执行完毕后会退出主线程。
下面就是这个循环的步骤:
1.把同步任务队列 或者 微任务队列 或者 宏任务队列中的任务放入主线程。
2.同步任务 或者 微任务 或者 宏任务在执行完毕后会全部退出主线程。
在实际场景下大概是这么一个顺序:
1.把同步任务相继加入同步任务队列。
2.把同步任务队列的任务相继加入主线程。
3.待主线程的任务相继执行完毕后,把主线程队列清空。
4.把微任务相继加入微任务队列。
5.把微任务队列的任务相继加入主线程。
6.待主线程的任务相继执行完毕后,把主线程队列清空。
7.把宏任务相继加入宏任务队列。无time的先加入,像网络请求。有time的后加入,像setTimeout(()=>{},time),在他们中time短的先加入。
8.把宏任务队列的任务相继加入主线程。
9.待主线程的任务相继执行完毕后,把主线程队列清空。
宏任务:
setTimeout setInterval Ajax DOM事件 script(整体代码) I/OUI交互事件 postMessageMessage Channel setImmediate(Node.js 环境)
微任务:
Promise async/await Object.observe MutationObserver process.nextTick(Node.js 环境)
优先级排行:process.nextTick>promise.then>setTimeout>setImmediate
new Promise中的代码会立即执行,then函数分发到微任务队列,process.nextTick分发到微任务队列Event Queue