最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 面试官:什么是 EventLoop。你:一脸蒙蔽。看完这篇文章就懂了

    正文概述 掘金(人生代码)   2021-01-18   400

    面试官:什么是 EventLoop。你:一脸蒙蔽。看完这篇文章就懂了

    文章翻译自:

    https://javascript.info/event-loop

    在这片文章,我们要带着两个问题去学习

    • EventLoop 概念是什么
    • 为什么需要 EventLoop

    事件循环

    浏览器 js 以及 Nodejs 都是基于事件循环,了解事件循环对于代码优化非常重要。 在本章中,我们首先介绍有关事物如何工作的理论细节,然后介绍该知识的实际应用。

    就是有一个无限循环机制:JavaScript 引擎等待任务,执行任务,然后休眠,等待更多任务。

    引擎的一般算法

    • 有任务时:
      • 从最早的任务开始执行它们。
    • 休眠直到出现任务,然后转到有任务时

    这是浏览页面时看到的形式化信息。JavaScript 引擎大部分时间不执行任何操作,仅在脚本/处理程序/事件激活时运行。

    任务示例

    • <script src="...">加载外部脚本时,任务是执行它
    • 用户移动鼠标时,任务是调度 mousemove 事件并执行处理程序
    • 当计划好的时间到了 setTimeout,任务是运行其回调。
    • ... 等等

    设置任务-引擎处理它们-然后等待更多任务(在睡眠时消耗接近零的CPU)。

    引擎繁忙时可能会发生任务,然后将其排入队列。

    任务形成一个队列,即所谓的“宏任务队列”(v8术语):

    面试官:什么是 EventLoop。你:一脸蒙蔽。看完这篇文章就懂了

    例如,当引擎正忙于执行 script,用户可能会移动鼠标 mousemove,这 setTimeout 可能是由于任务到期而导致的,等等,这些任务形成了一个队列,如上图所示。

    队列中的任务按“先到先得”的原则处理。引擎浏览器用完成后 script,它将处理 mousemove 事件,然后 setTimeout 处理程序,依此类推。

    到目前为止,很简单,对吧?

    另外两个细节:

    • 引擎执行任务时永远不会进行渲染。任务是否花费很长时间都没关系。仅在任务完成后才绘制对 DOM 的更改。
    • 如果一项任务花费的时间太长,浏览器将无法执行其他任务,例如处理用户事件。因此,过了一会儿,它会发出“页面无响应”之类的警报,建议终止整个页面的任务。当存在大量复杂的计算或导致无限循环的编程错误时,就会发生这种情况。

    用例1:分割 CPU 任务

    假设我们有一个需要 CPU 的任务。

    例如,语法高亮(用于着色此页面上的代码示例)相当占用 CPU 资源。为了突出显示代码,它执行分析,创建许多彩色元素,然后将它们添加到文档中-花费大量时间编写大量文本。

    当引擎忙于语法高亮显示时,它无法执行其他与 DOM 相关的工作,处理用户事件等。它甚至可能导致浏览器“打ic”甚至“挂起”一小段时间,这是不可接受的。

    通过将大任务分成多个部分,我们可以避免问题。突出显示前100行,然后为后100行计划 setTimeout(零延迟),依此类推。

    为了证明这种方法,为简单起见,而不是文本的高亮显示,让我们一个函数,计算从1到1000000000。

    如果您运行下面的代码,引擎将“挂起”一段时间。对于明显可见的服务器端JS,如果您正在浏览器中运行它,则尝试单击页面上的其他按钮–您会发现在计数结束之前不会处理其他事件。

    let i = 0;

    let start = Date.now();

    function count() {

      // do a heavy job
      for (let j = 0; j < 1e9; j++) {
        i++;
      }

      alert("Done in " + (Date.now() - start) + 'ms');
    }

    count();

    浏览器甚至可能显示“脚本花费太长时间”的警告。

    让我们使用嵌套 setTimeout 调用拆分作业:

    let i = 0;

    let start = Date.now();

    function count() {

      // do a piece of the heavy job (*)
      do {
        i++;
      } while (i % 1e6 != 0);

      if (i == 1e9) {
        alert("Done in " + (Date.now() - start) + 'ms');
      } else {
        setTimeout(count); // schedule the new call (**)
      }

    }

    count();

    现在,浏览器界面在“计数”过程中可以正常使用。

    一次运行 count 完成一部分工作,然后根据需要重新计划自身:

    • 首次运行计数:i=1...1000000。
    • 第二次运行计数:i=1000001..2000000。
    • …等等。

    现在,如果 onclick 在引擎正在忙于执行第1部分时出现新的辅助任务(例如事件),则将其排队,然后在第1部分完成时在下一部分之前执行。count 执行之间定期返回事件循环为 JavaScript 引擎提供了足够的“空气”来执行其他操作,以对其他用户操作做出反应。

    值得注意的是,两种变体(无论是否分配工作)setTimeout在速度上都是可比的。总体计数时间没有太大差异。

    为了使它们更接近,让我们进行改进。

    我们将排程移至的开头 count()

    let i = 0;

    let start = Date.now();

    function count() {

      // move the scheduling to the beginning
      if (i < 1e9 - 1e6) {
        setTimeout(count); // schedule the new call
      }

      do {
        i++;
      } while (i % 1e6 != 0);

      if (i == 1e9) {
        alert("Done in " + (Date.now() - start) + 'ms');
      }

    }

    count();

    现在,当我们开始 count() 发现需要做 count() 更多的事情时,我们会立即安排工作时间,然后再进行这项工作。

    如果您运行它,很容易注意到它花费的时间大大减少。

    为什么?

    这很简单:您记得,许多嵌套 setTimeout 调用在浏览器中的最小延迟为4ms 。即使我们设置了0,它4ms(或者更多)。因此,我们计划得越早–运行速度越快。

    最后,我们将需要大量 CPU 的任务分成了几个部分–现在它不会阻塞用户界面。而且它的整体执行时间不会更长。

    用例2:进度指示

    为浏览器脚本分配繁重任务的另一个好处是,我们可以显示进度指示。

    如前所述,仅在当前运行的任务完成后才绘制对DOM的更改,而不管它花费多长时间。

    一方面,这很棒,因为我们的函数可能创建许多元素,将它们一个接一个地添加到文档中并更改其样式-访问者将看不到任何“中间”未完成的状态。重要的是吧?

    这是演示,在i功能完成之前不会显示对的更改,因此我们将仅看到最后一个值:

    <div id="progress"></div>

    <script>

      function count() {
        for (let i = 0; i < 1e6; i++) {
          i++;
          progress.innerHTML = i;
        }
      }

      count();
    </script>

    …但是我们也可能希望在任务执行过程中显示一些东西,例如进度条。

    如果我们使用来将繁重的任务分成几部分 setTimeout,那么更改将被绘制在它们之间。

    这看起来更漂亮:

    <div id="progress"></div>

    <script>
      let i = 0;

      function count() {

        // do a piece of the heavy job (*)
        do {
          i++;
          progress.innerHTML = i;
        } while (i % 1e3 != 0);

        if (i < 1e7) {
          setTimeout(count);
        }

      }

      count();
    </script>

    现在,<div>显示的是的增加值 i,这是一种进度条。

    用例3:在事件发生后采取措施

    在事件处理程序中,我们可能会决定推迟一些操作,直到事件冒泡并在所有级别上得到处理。我们可以通过将代码包装为零延迟来实现 setTimeout

    在分派自定义事件一章中,我们看到了一个示例:自定义事件 menu-open 是在中分派的 setTimeout ,因此它在完全处理“ click”事件之后发生。

    menu.onclick = function() {
      // ...

      // create a custom event with the clicked menu item data
      let customEvent = new CustomEvent("menu-open", {
        bubbles: true
      });

      // dispatch the custom event asynchronously
      setTimeout(() => menu.dispatchEvent(customEvent));
    };

    宏任务和微任务

    随着宏任务,在本章中所描述的,有 microtasks,在章节中提到 Microtasks

    微任务仅来自我们的代码。它们通常是由.then/catch/finallyPromise创建的:处理程序的执行成为微任务。微任务也被“秘密使用” await,因为它是承诺处理的另一种形式。

    还有一个特殊功能queueMicrotask(func)func 可以在微任务队列中排队等待执行。

    每一个后立即宏任务时,引擎执行所有任务 microtask 队列运行任何其他宏任务或渲染或其他任何东西之前,。

    例如,看一下:

    setTimeout(() => alert("timeout"));

    Promise.resolve()
      .then(() => alert("promise"));

    alert("code");

    这将是什么顺序?

    • code 首先显示,因为它是常规的同步调用。
    • promise显示第二个,因为它.then通过微任务队列,并在当前代码之后运行。
    • timeout 最后显示,因为它是一个宏任务。

    更丰富的事件循环图片如下所示(顺序是从上到下,即:首先是脚本,然后是微任务,渲染,等等):

    面试官:什么是 EventLoop。你:一脸蒙蔽。看完这篇文章就懂了

    在执行任何其他事件处理或呈现或执行任何其他宏任务之前,所有微任务都已完成。

    这很重要,因为它可以确保微任务之间的应用程序环境基本相同(没有鼠标坐标更改,没有新的网络数据等)。

    如果我们想异步执行一个函数(在当前代码之后),但是在呈现更改或处理新事件之前,可以使用进行调度queueMicrotask。

    这是一个带有“计数进度条”的示例,与之前显示的示例相似,但queueMicrotask用于代替setTimeout。您可以看到它在最后渲染。就像同步代码一样:

    <div id="progress"></div>

    <script>
      let i = 0;

      function count() {

        // do a piece of the heavy job (*)
        do {
          i++;
          progress.innerHTML = i;
        } while (i % 1e3 != 0);

        if (i < 1e6) {
          queueMicrotask(count);
        }

      }

      count();
    </script>

    概要

    更详细的事件循环算法(尽管与规范相比仍简化了):

    • 1从宏任务队列中出队并运行最早的任务(例如“脚本”)。
    • 2执行所有微任务: - 当微任务队列不为空时: - 出队并运行最旧的微任务。
    • 3渲染更改(如果有)。
    • 4如果宏任务队列为空,请等待直到出现宏任务。
    • 5转到步骤1。

    要安排新的宏任务:

    • 使用零延迟setTimeout(f)。

    这可用于将繁重的计算任务分解为多个部分,以使浏览器能够对用户事件做出反应并显示它们之间的进度。

    另外,在事件处理程序中用于安排事件完全处理(冒泡完成)后的操作。

    安排新的微任务

    • 使用queueMicrotask(f)。
    • Promise处理程序还会通过微任务队列。

    微任务之间没有 UI 或网络事件处理:它们立即接连运行。

    因此,您可能想queueMicrotask 异步执行功能,但要在环境状态下执行。


    起源地下载网 » 面试官:什么是 EventLoop。你:一脸蒙蔽。看完这篇文章就懂了

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元