关于前端:知识订正浏览器工作原理与事件循环

4次阅读

共计 3039 个字符,预计需要花费 8 分钟才能阅读完成。

  古代浏览器的复杂程度如同操作系统,只有日益完善的机制能力应答现今越来越简单的网页交互。笔者前文曾述 JS 单线程引起的思考,现在看来错漏百出,常识内容早已过期。基于当初的常识积攒,现在再发一文作为勘误,心愿能加深印象,有所收货。
  如同上文的“JS 单线程”,笔者之前所学还是全面的常识,JS 的单线程在哪个过程之内,交互操作,代码执行浏览器线程更偏重谁都是只知其一; 不知其二。当初从新零碎学习了一遍常识后,有了新的了解。
  首先从浏览器的工作原理来一步步解释起因。

一. 浏览器的过程模型

1. 过程与线程:

  程序运⾏须要有它⾃⼰专属的内存空间,能够把这块内存空间简略的了解为过程。每个应⽤⾄少有⼀个过程,过程之间互相独⽴,即便要通信,也须要双⽅批准。⼀个过程⾄少有⼀个线程,所以在过程开启后会⾃动创立⼀个线程来运⾏代码,该线程称之为主线程。如果程序须要同时执⾏多块代码,主线程就会启动更多的线程来执⾏代码,所以⼀个过程中能够蕴含多个线程。为了防止相互影响,为了缩小连环解体的⼏率,当启动浏览器后,它会⾃动启动多个过程。
  其中,最次要的过程有:

  1. 浏览器过程
    次要负责界⾯显示(非网页页面显示,如标签页样子,后退后退刷新按钮,导航栏等)、⽤户交互(如点击按钮,滚动滚动条等)、⼦过程治理等。浏览器过程外部会启动多个线程解决不同的工作。
  2. ⽹络过程
    负责加载⽹络资源。⽹络过程外部会启动多个线程来解决不同的⽹络工作。
  3. 渲染过程(重点)
    渲染过程启动后,会开启⼀个渲染主线程 ,主线程负责执⾏ HTML、CSS、JS 代码。 默认状况下,浏览器会为每个标签⻚开启⼀个新的渲染过程,以保障不同的标签⻚之间不相互影响。

    2. 渲染主过程

      渲染主线程须要解决的工作包含但不限于:

    * 解析 HTML
    * 解析 CSS
    * 计算款式
    * 布局
    * 解决图层
    * 每秒把⻚⾯画 60 次
    * 执⾏全局 JS 代码
    * 执⾏事件处理函数
    * 执⾏计时器的回调函数
    * .....

  渲染主线程须要解决诸如此类如此多的工作,为了确保稳固的运行,就须要一种机制来做任务调度,因而,音讯队列 / 事件队列 应运而生。

  在最开始的时候,渲染主线程会进入一个有限循环。
  每一次循环会查看音讯队列中是否有工作存在。如果有,就取出第一个工作执行,执行完一个后进入下一次循环;如果没有,则进入休眠状态。
  其余所有线程(包含其余过程的线程)能够随时向音讯队列增加工作。新工作会加到音讯队列的开端。在增加新工作时,如果主线程是休眠状态,则会将其唤醒以持续循环拿取工作。这样一来,就能够让每个工作井井有条的、继续的进行上来了。
  整个过程,被称之为 事件循环 event loop 音讯循环 message loop)。

  在理论的代码运行中,次要会遇到三种异步操作,如:

  • 计时实现后须要执行的工作 —— setTimeout、setInterval(计时线程)
  • 网络通信实现后须要执行的工作 – XHR、Fetch(网络线程)
  • 用户操作后须要执行的工作 – addEventListener(交互线程)

  这时,浏览器通过 异步 形式来解决上述三种形式可能导致的阻塞问题。如下图所示:

  当存在 setTimeout 时,将其代码放入“计时线程”内开始计时,渲染主线程持续运行同步代码,若渲染主线程没有工作时,则从音讯队列(事件队列)内拿取工作来运行,而计时完结后,其回调函数会放入音讯队列(事件队列)尾端,期待主线程拿取。
注:当遇到同步延时操作时,浏览器无奈像异步操作那样调用其余线程避免阻塞,只能期待渲染主线程运行完代码后继续执行下一步操作,这段时间会造成阻塞导致页面卡死。

  • 一道面试问答题
如何了解 JS 的异步?JS 是一门单线程的语言,这是因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。而渲染主线程承当着诸多的工作,渲染页面、执行 JS 都在其中运行。如果应用同步的形式,就极有可能导致主线程产生阻塞,从而导致音讯队列中的很多其余工作无奈失去执行。这样一来,一方面会导致忙碌的主线程白白的耗费工夫,另一方面导致页面无奈及时更新,给用户造成卡死景象。所以浏览器采纳异步的形式来防止。具体做法是当某些工作产生时,比方计时器、网络、事件监听,主线程将工作交给其余线程去解决,本身立刻结束任务的执行,转而执行后续代码。当其余线程实现时,将当时传递的回调函数包装成工作,退出到音讯队列的开端排队,期待主线程调度执行。在这种异步模式下,浏览器永不阻塞,从而最大限度的保障了单线程的晦涩运行。

  上述异步操作会产生工作,工作是没有优先级的,在音讯队列(事件队列)中先进先出。然而音讯队列有优先级。

  • 每个工作都有⼀个工作类型,同⼀个类型的工作必须在⼀个队列,不同类型的工作能够分属于不同的队列。在⼀次事件循环中,浏览器能够依据理论状况从不同的队列中取出工作执⾏。
  • 浏览器必须筹备好⼀个微队列,微队列中的工作优先所有其余工作执⾏。

  在⽬前 chrome 的实现中,⾄少蕴含了下⾯的队列:

  • 延时队列 :⽤于寄存计时器达到后的回调工作,优先级「」。
  • 交互队列 :⽤于寄存⽤户操作后产⽣的事件处理工作,优先级「」。
  • 微(工作)队列 :⽤户寄存须要最快执⾏的工作,优先级「 最⾼」。

注:增加工作到微队列的次要⽅式次要是使⽤ PromiseMutationObserver

  因而,没有宏(工作)队列,曾经没有宏(工作)概念。做了更清晰的划分。当上述代码产生的工作会被放入对应的队列内被渲染主线程按优先级高下顺次取出,直至队列内没有工作,且渲染主线程也没有工作为止,渲染主线程会进入休眠主题,增加新工作时,渲染主线程会被唤醒持续循环拿取工作来执行。

  • 几道面试题:

1. 常见的输入题

function a() {
  //fn1
  console.log(1);
  Promise.resolve().then(() => {
      // fn2
    console.log(2);
  });
}
setTimeout(() => {
  // fn3
  console.log(3);
  Promise.resolve().then(a);
}, 0);
Promise.resolve().then(() => {
  // fn4
  console.log(4);
});

console.log(5);

// 执行后果输入:5 4 3 1 2

2. 论述⼀下 JS 的事件循环

    事件循环⼜叫做音讯循环,是浏览器渲染主线程的⼯作⽅式。在 Chrome 的源码中,它开启⼀个不会完结的 for 循环,每次循环从音讯
队列中取出第⼀个工作执⾏,⽽其余线程只须要在适合的时候将工作加⼊到队列
开端即可。过来把音讯队列简略分为宏队列和微队列,这种说法⽬前已⽆法满⾜简单的
浏览器环境,取⽽代之的是⼀种更加灵便多变的解决⽅式。依据 W3C 官⽅的解释,每个工作有不同的类型,同类型的工作必须在同⼀
个队列,不同的工作能够属于不同的队列。不同工作队列有不同的优先级,在
⼀次事件循环中,由浏览器⾃⾏决定取哪⼀个队列的工作。但浏览器必须有⼀个
微队列,微队列的工作⼀定具备最⾼的优先级,必须优先调度执⾏。

3.JS 中的计时器能做到准确计时吗?为什么?

答:不⾏,因为:1. 计算机硬件没有原⼦钟,⽆法做到准确计时。2. 操作系统的计时函数自身就有大量偏差,因为 JS 的计时器最终调⽤的
是操作系统的函数,也就携带了这些偏差。3. 依照 W3C 的规范,浏览器实现计时器时,如果嵌套层级超过 5 层,则会带有 4 毫秒的起码工夫,这样在计时工夫少于 4 毫秒时⼜带来
了偏差。4. 受事件循环的影响,计时器的回调函数只能在主线程闲暇时运⾏,因而
⼜带来了偏差。
正文完
 0