zoukankan      html  css  js  c++  java
  • JS事件循环(Event Loop)机制

    前言

    众所周知,为了与浏览器进行交互,Javascript是一门非阻塞单线程脚本语言。
    
    1. 为何单线程? 因为如果在DOM操作中,有两个线程一个添加节点,一个删除节点,浏览器并不知道以哪个为准,所以只能选择一个主线程来执行代码,以防止冲突。虽然如今添加了webworker等新技术,但其依然只是主线程的子线程,并不能执行诸如I/O类的操作。长期来看,JS将一直是单线程。

    2. 为何非阻塞?因为单线程意味着任务需要排队,任务按顺序执行,如果一个任务很耗时,下一个任务不得不等待。所以为了避免这种阻塞,我们需要一种非阻塞机制。这种非阻塞机制是一种异步机制,即需要等待的任务不会阻塞主执行栈中同步任务的执行。这种机制是如下运行的:

      • 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
      • 等待任务的回调结果进入一种任务队列(task queue)
      • 当主执行栈中的同步任务执行完毕后才会读取任务队列,任务队列中的异步任务(即之前等待任务的回调结果)会塞入主执行栈,
      • 异步任务执行完毕后会再次进入下一个循环。此即为今天文章的主角事件循环(Event Loop)

      用一张图展示这个过程:


       
      Markdown

    正文

    1.macro task与micro task

    在实际情况中,上述的任务队列(task queue)中的异步任务分为两种:微任务(micro task)宏任务(macro task)

    • micro task事件:Promises(浏览器实现的原生Promise)MutationObserverprocess.nextTick
      <br />
    • macro task事件:setTimeoutsetIntervalsetImmediateI/OUI rendering
      这里注意:script(整体代码)即一开始在主执行栈中的同步代码本质上也属于macrotask,属于第一个执行的task

    microtask和macotask执行规则:

    • macrotask按顺序执行,浏览器的ui绘制会插在每个macrotask之间
    • microtask按顺序执行,会在如下情况下执行:
      • 每个callback之后,只要没有其他的JS在主执行栈中
      • 每个macrotask结束时

    下面来个简单例子:

    console.log(1);
    
    setTimeout(function() {
      console.log(2);
    }, 0);
    
    new Promise(function(resolve,reject){
        console.log(3)
        resolve()
    }).then(function() {
      console.log(4);
    }).then(function() {
      console.log(5);
    });
    
    console.log(6);

    一步一步分析如下:

    • 1.同步代码作为第一个macrotask,按顺序输出:1 3 6
    • 2.microtask按顺序执行:4 5
    • 3.microtask清空后执行下一个macrotask:2

    再来一个复杂的例子:

    // Let's get hold of those elements
    var outer = document.querySelector('.outer');
    var inner = document.querySelector('.inner');
    
    // Let's listen for attribute changes on the
    // outer element
    new MutationObserver(function() {
      console.log('mutate');
    }).observe(outer, {
      attributes: true
    });
    
    // Here's a click listener…
    function onClick() {
      console.log('click');
    
      setTimeout(function() {
        console.log('timeout');
      }, 0);
    
      Promise.resolve().then(function() {
        console.log('promise');
      });
    
      outer.setAttribute('data-random', Math.random());
    }
    
    // …which we'll attach to both elements
    inner.addEventListener('click', onClick);
    outer.addEventListener('click', onClick);

    假设我们创建一个有里外两部分的正方形盒子,里外都绑定了点击事件,此时点击内部,代码会如何执行?一步一步分析如下:

    • 1.触发内部click事件,同步输出:click
    • 2.将setTimeout回调结果放入macrotask队列
    • 3.将promise回调结果放入microtask
    • 4.将Mutation observers放入microtask队列,主执行栈中onclick事件结束,主执行栈清空
    • 5.依序执行microtask队列中任务,输出:promise mutate
    • 6.注意此时事件冒泡,外部元素再次触发onclick回调,所以按照前5步再次输出:click promise mutate(我们可以注意到事件冒泡甚至会在microtask中的任务执行之后,microtask优先级非常高)
    • 7.macrotask中第一个任务执行完毕,依次执行macrotask中剩下的任务输出:timeout timeout
  • 相关阅读:
    一步一步教你elasticsearch在windows下的安装
    Query DSL for elasticsearch Query
    [转] webpack之前端性能优化(史上最全,不断更新中。。。)
    [转] Javascript模块化编程(一):模块的写法
    [转] 2016 JavaScript 发展现状大调查
    [转] 前端性能的几个基础指标
    [转] 视频直播前端方案
    [转] Web前端开发工程师常用技术网站整理
    [转] getBoundingClientRect判断元素是否可见
    [转] js前端解决跨域问题的8种方案(最新最全)
  • 原文地址:https://www.cnblogs.com/planetwithpig/p/11681071.html
Copyright © 2011-2022 走看看