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

若柳暗花明、若拨云见日、若抽丝剥茧。总之,看完了这篇文章,整个人唯一的感觉就是茅塞顿开。通篇的描述都可以说是深入浅出、面面俱到。强力推荐这篇文章!
掘金_「硬核JS」一次搞懂JS运行机制javascript|「硬核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的异步。
javascript|「硬核JS」一次搞懂JS运行机制 - 读后感
文章图片

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渲染 -> 宏任务 -> ...

javascript|「硬核JS」一次搞懂JS运行机制 - 读后感
文章图片

渲染时,在一个GUI线程中,会将所有UI改动优化合并。
相反,如果对于一个div,在两此宏任务中对其样式进行更改,就比如背景颜色,那我们就可能看到闪烁。
3. 结合“同步任务”、“异步任务”、“宏任务”、“微任务”分析事件循环
【javascript|「硬核JS」一次搞懂JS运行机制 - 读后感】javascript|「硬核JS」一次搞懂JS运行机制 - 读后感
文章图片

  • 首先,“微任务” 的事件回调在 “微任务队列” 中,每个 “宏任务” 都有一个 “微任务队列”
  • 是最大的 “宏任务”,也是一个 “同步任务”,在主线程(JS引擎线程)中执行
  • 执行时,若遇到 “异步任务”,判断 “异步任务” 是 “微任务” 还是 “宏任务”
  • 若是 “宏任务”,判断这个 “宏任务” 是否拥有自己的 “专属执行线程”
  • 有,就把该任务交给 “专属线程”,专属线程完成对应事件后,将事件对应的回调转交给 “事件触发线程”,事件触发线程再将回调推入 “事件队列”
  • 没有时,直接将任务交给 “事件表”(event table),“事件表” 完成任务的事件后,也会将事件的回调交给 “事件触发线程”,依旧由 “事件触发线程” 将回调推入 “事件队列”
  • “微任务” 则相对简单一些,直接交给属于微任务的 “事件表”,在 “事件表” 完成对应事件后,再由同 “事件触发线程” 将回调推入 “微任务队列”
  • “主线程” 执行完后,开始执行 “全局执行上下文” 管理的 “微任务队列”
  • “微任务队列” 执行完,再开始执行下一个 “宏任务”,一直这样循环下去,便是事件循环Event Loop了。

    推荐阅读