【JS】JavaScript运行机制浅析

我心飞翔 分类:javascript

进程和线程

JavaScript的单线程指的是,代码执行的时候一个进程里只有一个线程

进程和线程的区别:

  • 进程是CPU资源分配的最小单位;线程是CPU调度的最小单位
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 一个进程的内存空间可以给其中的线程共享

进程类似于工厂,有很多的资源需要加工;而线程就类似于其中的工人,每个人利用资源加工完成一个个的产品

多进程就好比你在电脑前既能敲着代码也能同时听着歌,这就是多进程

多线程一个程序中可以同时运行多个不同的线程来执行不同的任务

JavaScript单线程的原因是,JS可以用来操作DOM,如果是多线程的话,加入一个线程要修改DOM-A,一个线程又要删除DOM-A,那么浏览器就不知道该已谁为准了。

执行栈和任务队列

JavaScript将所有任务分为同步任务和异步任务

执行栈相当于一个存储函数调用的栈结构,遵循先进后出的原则

  • 所有同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,还存在任务队列。异步任务有了运行结果,就在任务队列之中放置一个
  • 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列
  • 主线程不断重复执行3

其中异步任务分为:

  • 宏任务: script(整体代码), setTimeout, setInterval, setImmediate(Nodejs), I/O, UI rendering
  • 微任务: process.nextTick(Nodejs), Promises, Object.observe, MutationObserver

注:setTimeout(fn,t)真正的含义是指,t秒之后,将fn放入任务队列,当主线程执行完执行栈上的任务后,从任务队列中取出fn执行。所以setTimeout第二个参数的意义是不低于t秒后执行fn

事件循环

主线程从任务队列中读取事件,这个过程是循环不断的,这种运行机制就叫EventLoop(事件循环)

1683877ba9aab056.png

浏览器下JavaScript运行机制:

  1. JavaScript代码运行,此时执行栈空,微任务队列空,宏任务队列里有且只有一个script脚本(整体代码)
  2. 全局上下文(script标签)被推入执行栈,同步代码执行,同时会产生新的微任务与宏任务
  3. 当全局上下文(script标签)中同步任务执行完毕后,会去清空此宏任务产生的微任务队列
  4. 执行渲染操作,更新界面
  5. 检查是否存在Web worker任务,如果有,则对其进行处理
  6. 然后再去宏任务队列处理下一个宏任务,不断重复3-6(Event Loop)

Node中Event Loop

Node.js采用V8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv,libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,事件循环机制也是它里面的实现

Node运行机制

  • V8引擎解析JavaScript脚本,解析后的代码,调用Node API(libuv库)
  • libuv库将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎

6个阶段

16841bd9860c1ee9.png

外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段...

  • poll阶段获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check阶段执行setImmediate()的回调
  • closecallbacks 阶段执行socket的close事件回调
  • timers阶段这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks阶段处理一些上一轮循环中的少数未执行的 I/O 回调
  • idle, prepare阶段仅node内部使用

注:process.nextTick独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数

Node端事件循环中的异步队列也是这两种:宏任务队列和微任务队列

  • 常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作等
  • 常见的 micro-task 比如: process.nextTick、new Promise().then(回调)等

Node与浏览器的 Event Loop 差异

16841bad1cda741f.png

  • 浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行
  • 在Node中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务
setTimeout(()=>{
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(()=>{
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
 

参考

  • 浏览器与Node的事件循环(Event Loop)有何区别
  • 这一次,彻底弄懂 JavaScript 执行机制
  • JavaScript 运行机制详解:再谈Event Loop
  • 彻底搞懂浏览器Event-loop

回复

我来回复
  • 暂无回复内容