js单线程理解

77 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1、进程和线程 进程:CPU进行资源分配的基本单位(进程上下文切换比线程块上下文切换慢)

线程:CPU调度的最小单位,建立在进程的基础上运行的单位,共享进程的内存空间

多进程:一边听歌一边写代码,进程之间互不影响

多线程:线程是一个车间的工人,多个工人协作完成一个任务

2、浏览器的多进程 浏览器的多进程:

浏览器进程:浏览器的主进程

负责浏览器界面的展示,与用户交互 负责各个页面的管理、销毁和创建 将Render进程得到的Bitmap绘制到界面上 网络资源的管理 GPU进程:3D绘制和硬件加速

浏览器渲染进程:浏览器内核就是浏览器渲染进程,从接收下载文件后再到呈现整个页面的过程,由浏览器渲染进程负责,主要流程如下:

解析HTML文件和CSS文件,加载图片等资源文件,渲染成用户看到的页面 执行解析js文件脚本代码 浏览器渲染进程的多线程:GUI渲染线程、JS引擎线程——解析执行js脚本、事件触发线程、定时器出发线程、异步HTTP请求线程 由于js是单线程(一个Tab页内中无论什么时候都只有一个JS线程在运行JS程序),我们通过任务队列来实现js代码的异步,js引擎会一直等待着任务队列中任务的到来,然后加以处理。 注意:JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合

3、js单线程 这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。

举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变

PS:浏览器是多进程的,不同类型的标签页会开启一个新的进程,相同类型的标签页会合并到一个进程

4、同步任务和异步任务(广义) 同步任务:那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务

异步任务:那些被引擎放在一边,不进入主线程、而进入任务队列的任务。

5、异步实现:任务队列和事件循环 任务队列 JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)

首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。

JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。维基百科的定义是:“事件循环是一个程序结构,用于等待和发送消息和事件”。

事件循环 js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。

被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

宏任务(macro-task)、微任务(micro-task)(异步任务细分) macro-task包括:script, setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。

在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取一个macrotask任务,取所有的微任务,取一个宏任务,取所有微任务,周而复始,直至两个队列的任务都取完。

宏任务队列可以有多个,微任务队列只有一个 宏任务按顺序执行,且浏览器在每个宏任务之间渲染页面 所有微任务也按顺序执行,且在以下场景会立即执行所有微任务 每个回调之后且js执行栈中为空。每个宏任务结束后。