js的单线程
从我们第一天接触js的时候我们就知道js是单线程的,且js是异步的,首先来看一下基本概念
什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。(百度百科)
举个最简单的例子 , 在我们电脑的任务管理器中你打开一个应用程序的时候 就会多一个进程,他代表了cpu能处理的单个任务。
线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也可以开启多个线程,比如你打开了一个vscode编辑器,里边你能做的操作会有很多比如cmd node 等等
浏览器的进程
js是运行在浏览器的,是由js引擎解析和执行的,那么我们先了解一下浏览器都有哪些线程
首先要说明的是 浏览器是多线程的
Browser进程:浏览器的主进程
第三方插件进程
GPU进程
浏览器渲染进程
对于前端来讲这些进程中最重要的是浏览器渲染进程,也被称之为是浏览器内核,因为我们的页面渲染,事件等等都在这个进程中进行的
浏览器的渲染进程里边有一些常驻的线程比如说
GUI渲染线程
- 负责解析html css 构建dom树 编译 RenderObject树,计算布局渲染等等
- 在页面重回或者dom回流的时候这个线程就会被执行
- 页面在第一次渲染的时候肯定会触发这个线程
Js引擎线程
- 主要负责处理javascript的脚本
- 也称之为js内核,一个tab页中只有一个js线程在运行js程序
需要注意的是Gui渲染线程和js引擎线程是冲突的,他们不能同时执行
所以说如果我们页面在一开始js文件需要计算或者操作的时间比较长的时候,会出现大段时间的白屏,
因为js引擎在执行的时候会使页面渲染堵塞,如果dom发生更新的时候,gui更新会被存在一个队列里边,等到js引擎空闲的时候才会被执行
一个tab页里边无论如何都只有一个js线程,就算是后来的webworker ,他也是只属于js引擎的一个子类,并且它不能操作dom
事件触发线程
- 常用的事件有很多比如点击事件 ajax请求事件等等他们会在对应的条件触发的时候被添加到事件线程中
- 这些事件会在js引擎空闲的时候去执行
定时触发器线程
- 定时器与计时器的线程,在满足条件之后会被添加到事件队列里边,等待js引擎空闲的时候去执行
http请求
- 在发送http请求的时候,这个线程会被执行
- 再有回调函数的时候,这个线程会把回调的事件放入到事件队列中去,等待js引擎执行
突然觉得js引擎好累,什么都是要他去做,为什么不设计成多线程,从网上其他地方看了的答案,大同小异,讲的是多线程操作dom有可能会同时操作一个dom发生错误云云
说完了上边的一些概念,你也应该大体的了解了js的单线程这个问题
下边从js引擎的一些运行机制说一说js的异步
刚才已经讲了,浏览器中不仅有js引擎线程还有其他,异步的话主要会用到事件触发线程和定是触发器线程的概念
在js中氛围同步任务和异步任务 ,所有的同步任务都会在主线程上执行,形成一个执行栈(先进后出)
在主线程之外还有一个异步的事件队列
在执行栈执行完毕之后,也就是同步任务执行完毕之后,js引擎线程就会去轮询事件队列,看有没有需要执行的事件,有的话就会执行事件队列的事件
我记得我最开始的时候使用ajax 或者数据库查询的时候遇到过这么一个情况,在做完操作之后就天真的认为可以用返回的值,结果当时我被异步搞得里焦外嫩,甚至我以为在发送完请求之后多做一些操作等一会请求,请求值就会返回来,结果。。。
其实不然, 在同一个事件循环内部,无论做多少操作,你的异步操作只会在执行栈执行完毕之后才会被执行
同时异步也是有优先级的,在事件循环里边js的任务类型分为两种
- 宏任务
- 微任务
宏任务就是说执行栈里的每一个被执行的代码就是一个宏任务,包括一个事件产生的回调执行
宏任务会在执行完毕一段代码之后先对dom进行一次渲染,然后再执行下一个宏任务
微任务是再宏任务执行完毕之后立即执行的,他在dom重新渲染之前,
所以微任务的相应速度比宏任务是要快的
像 定时器,延时器 主代码块等等就是一个宏任务,promise nextTick等就是微任务
正常在延时器打印和在promise里边打印,promise的打印是会比延时器的先打印的
所以总结一下js的运行机制是
- 执行栈执行宏任务
- 执行栈没有任务就去轮询事件队列
- 如果执行期间遇到微任务,就添加到微任务队列
- 一个宏任务执行完毕后会立即执行当前微任务队列的任务
- 宏任务执行完毕后开始渲染
- 然后开启下一轮宏任务
以上有说的不对或者不足之处,请批评指正