「硬核JS」一次搞懂JS运行机制 - 读后感

若柳暗花明、若拨云见日、若抽丝剥茧。总之,看完了这篇文章,整个人唯一的感觉就是茅塞顿开。通篇的描述都可以说是深入浅出、面面俱到。强力推荐这篇文章!

掘金_「硬核JS」一次搞懂JS运行机制https://juejin.cn/post/6844904050543034376#heading-15

一、行进与线程

1. 进程:

  • “CPU资源分配的最小单位”
  • 一个进程 = 该程序 + 该程序使用的内存 + 该程序使用的系统资源
  • 一个CPU在一个时间点只能运行一个进程,借助 “时间片轮转调度算法” 实现多个进程之间的切换,以达到同时运行多个进行的目的

2. 线程:

  • “CPU调度的最小单位”
  • 不同的线程表示一个进程中不同的执行路线
  • 一个进行可以有多个线程

3. 进程与线程的区别:

  • 进程之间相互独立,但同一进程下各个线程之间共享程序的内存以及程序级资源

4. 多进程与多线程:

  • 多线程意味着在一个程序中,同一时间可以同时执行多个任务
  • 多进程意味着可以在同一时间运行多个程序

5. 单线程的JS:

  • 尽管借助Web Worker可以为JS创建多个线程,但这些线程都受到主线程的控制,因此本质上依旧是单线程

二、浏览器

1. 浏览器是多进程的

一个Tab页就是一个进程,首页也很消耗CPU

2. 浏览器都有哪些进程?

  • Browser进程

    • 浏览器的主进程(负责界面显示、交互、管理,网络资源的管理等),该进程只有一个
  • 第三方插件进程

    • 每种类型的插件对应一个进程,使用该插件时才创建
  • GPU进程

    • 该进程也只有一个,用于3D绘制等等
  • 渲染进程(重)

    • 也就是浏览器内核(Renderer进程,内部是多线程)
    • 每个Tab页面都有一个渲染进程,互不影响,负责页面渲染,脚本执行,事件处理等

3. 为什么浏览器是多线程?

  • 避免被一不小心挂掉的插件或Tab页把整个浏览器给堵死了

三、渲染进程(Renderer)

渲染进程的主要线程:

  • GUI渲染线程
    • 解析HTML、CSS生成DOM Tree、CSSOM,再组装成Render Tree
    • 重绘、回流时,对页面的绘制
    • GUI渲染线程与JS引擎线程是互斥的
      • 当JS引擎执行时,GUI线程会被挂起(相当于冻结了)。GUI更新会被保存在一个队列中,等到JS引擎空闲时立即被执行
  • JS引擎线程
    • 也就是JS内核(例如Chrome的V8引擎),负责解析JavaScript脚本,管理着一个 “执行栈”
  • 事件触发线程
    • 控制 “事件循环”,管理着一个 “事件队列”(event queue)
    • 当JS执行时,如果遇到 “异步操作”,“事件触发线程” 将事件添加到对应的线程中(比如定时器操作,便把定时器事件添加到定时器线程),等异步事件有了结果,便把它们的回调添加到 “事件队列”,等待JS引擎线程空闲时处理
  • 定时触发器线程(setTimeout、setInterval)
    • 执行到定时器时,先在 “定时器线程” 进行 “定时” 与 “计时”,计时完毕后,将回调函数交给 “事件触发线程”,再由 “事件触发线程” 将回调函数添加到 “任务队列” 中,等待JS引擎线程空闲后执行
    • W3C规定,setTimeout中小于4ms的事件间隔算为4ms
  • 异步http请求线程
    • 执行到一个http请求时,先把 “异步请求事件” 添加到 “异步请求线程” 中,请求成功并收到响应后,把回调交给 “事件触发线程”,由 “事件触发线程” 将回调函数添加到 “任务队列

我们会发现,浏览器上所有线程的工作都很单一且独立,非常符合 “单一原则”。

“定时触发线程” 只管理定时器时只关注定时,不关心结果,定时结束就把回调扔给“事件触发线程”;

“异步http请求线程” 只管理http请求,同样不关心结果,请求结束就把回调扔给 “事件触发线程”;

“事件触发线程” 只关心将异步回调推入任务队列。

四、事件循环(Event Loop)

1. 从“同步”、“异步”的角度看事件循环

script分为 “同步任务” 与 “异步任务”,“同步任务” 在 “执行栈” 中执行,“异步任务” 有了运行结果,就将其回调放入 “任务队列” 中。“执行栈” 中的 “同步任务” 全部执行完毕,就开始读取 “任务队列”,依次添加到 “执行栈” 中执行。

注意,await以前的代码,相当于new Promise的同步代码,await以后的代码相当与Promise.then的异步。

「硬核JS」一次搞懂JS运行机制 - 读后感_第1张图片

2. 从“宏任务”、“微任务”角度看事件循环

1)宏任务(macrotask)

宏任务的每次每次执行都是从头到尾地执行,不会在中途停止下来去执行其他任务。

常见的宏任务:

  • script代码
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame

requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:

  • requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
  • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

2)微任务(microtask

“微任务” 一般是 “宏任务” 执行过后的任务。

常见微任务

  • process.nextTick()
  • Promise.then()
  • catch
  • finally
  • Object.observe
  • MutationObserver

3)Event Loop流程

当一个 “宏任务” 执行完,会在渲染前,将执行期间所产生的所有 “微任务” 都执行完。然后执行下一个 “宏任务”,如此循环往复。

宏任务 -> 微任务 -> GUI渲染 -> 宏任务 -> ...

「硬核JS」一次搞懂JS运行机制 - 读后感_第2张图片

渲染时,在一个GUI线程中,会将所有UI改动优化合并。 

相反,如果对于一个div,在两此宏任务中对其样式进行更改,就比如背景颜色,那我们就可能看到闪烁。

3. 结合“同步任务”、“异步任务”、“宏任务”、“微任务”分析事件循环

「硬核JS」一次搞懂JS运行机制 - 读后感_第3张图片

  • 首先,“微任务” 的事件回调在 “微任务队列” 中,每个 “宏任务” 都有一个 “微任务队列”