JS运行机制
进程和线程
- 进程:是系统进行资源分配和调度的基本单位,是一个具有一定独立功能的程序的一次运行活动,是程序的执行实例,包括程序 计数器,寄存器和变量的当前值;
- 多进程:启动多个进程,而每启动一个进程,都要分配给他独立的地址空间,建立众多的数据表来维护,这样会造成很多浪费。
- 线程:进程内一个相对独立的,可调度的执行单元
- 多线程:运行于一个进程中的多个线程,它们之间使用相同的地址空间,不需要像多进程一样进行进程间通信了。通过调度可以 执行多任务
浏览器内核就是多线程的,它们在内核控制下相互配合以保持同步。一个浏览器通常由以下线程组成:GUI渲染线程,JavaScript引擎线程,浏览器事件触发线程,定时器触发器线程,异步http请求线程
- GUI渲染线程:负责渲染浏览器界面 HTML 元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就 会执行。在 Javascript 引擎运行脚本期间, GUI 渲染线程都是处于挂起状态的,也就是说被”冻结”。即 GUI 渲染线程与 JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。
- javascript 引擎线程:也可以称为 JS 内核,主要负责处理 Javascript 脚本程序,例如 V8 引擎。Javascript 引擎 线程理所当然是负责解析 Javascript 脚本,运行代码。浏览器无论什么时候都只有一个 JS 线程在运行 JS 程序
- 浏览器事件触发线程:当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理。这些事件可以是当 前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于JS的单线程关系所有这些 事件都得排队等待 JS 引擎处理。
- 定时触发器线程:浏览器定时计数器并不是由 JavaScript 引擎计数的, 因为 javaScript 引擎是单线程的, 如果处于阻 塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案
- 异步 http 请求线程:在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有 回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理 如上,浏览器中每个进程都有多个线程,而JS只能是单线程而不能是多线程,故如上多个线程是在内核控制下线程一个一个的执行,只有当一个线程执行完另外的线程才能继续执行,而不能在同一时间执行多个线程。
同步和异步
同步:能马上逐行执行代码,会对后续代码造成阻塞,直到执行完后才能执行后面的代码。 异步:如果在代码执行后,还不能得到预期结果,需要进一步的触发或者执行,并且此时不会阻塞后续的代码执行,即叫异步
为什么JS线程要求是单线程而不是多线程的呢?
在了解同步和异步之前,先理解Javascript为什么一定是单线程执行的呢?也就是说CPU在同一时间只能处理一个事件,多个事件需要按顺序,一个一个处理而不是像多线程一样同时处理多个事件呢? Js的主要用途是与用户交互,以及会对DOM元素增删改查。,这就决定了它只能是单线程,因为当多个线程同时操作DOM,其中一个线程要增加DOM节点,而另外一个线程则需要减少DOM节点,这样就会很容易造成分歧,使得浏览器不知道到底是删除还是增加该节点。 正是因为这个特性,当JS代码开始执行时,此时JS引擎线程开始执行,当碰到其他的线程时,是先保存到该线程中不执行,等到JS引擎线程执行完后,再根据内核控制去依次执行其他线程。这些其他线程的代码因为不会阻塞JS引擎线程的执行,故会被JS引擎线程认为是异步
事件循环机制

宏任务队列和微任务队列
如上图,是整个事件轮询的一个过程,这其中,有一个重要的概念:宏任务队列和微任务队列
- 宏任务队列包含:javascript 引擎线程,浏览器事件触发线程,定时触发器线程,异步 http 请求线程,其中javascript 引擎线程是从js代码一开始解析就开始执行,即认为该线程执行的是同步代码(而其他线程会在其执行完后,依据代码而决定是否开启,并在该线程执行完后再执行,不会阻塞该线程代码的执行,故其他线程执行代码会认为是异步执行)。
- 微任务队列包含的方法:promise的方法,如then等.process.nextTick(Node.js 比promise先执行)等方法