• 浏览器事件环(EventLoop)


    1. 基础知识

    1. js语言特点

    1. js语言是单线程语言,主线程是单线程。如UI渲染,脚本加载是主线程任务。

    2. js语言采用事件循环(EventLoop)机制。

    2. 同步任务:

    不被引擎挂起,在主线程等待执行的, 按照顺序执行的任务。主线程任务,也算宏任务。

    如: UI渲染,js执行。同一时间只能执行一个。

    3. 异步任务:

    一般比较消耗时间,被引擎挂起,进入任务队列,等待主线程全部执行完才能执行的任务。

    异步任务分两种:

    1. 微任务:Promise的then方法是微任务;微任务在主线程同步任务结束后执行。

    2.宏任务: setTimeout, setInterval, ajax, click事件等。异步任务的宏任务在下一个事件循环开始执行。

    上面的任务都会开辟新的线程(非主线程,单独的线程)

    5. 执行上下文和作用域

    作用域:

    js中是静态作用域。

    分为全局作用域,函数作用域,块级作用域。

    作用域一般在定义和声明声明阶段就已经确定了。

    变量查找会根据作用域链依次查找。

    执行上下文:

    执行上下文是代码执行的时候,产生的执行环境,执行栈。也叫做调用栈。

    当同步任务运行时,进入执行栈,运行完成销毁调用栈。

    对于递归调用的函数来说,最后被调用的是最内层函数,那么它在调用栈的最后位置。

    最先执行的也是最内层函数,执行完后,销毁调用栈所处的上下文环境。

    6. 栈和队列

    : 栈是一种“后入先出”的数据结构。

    同步任务(主线程任务)进入执行栈,产生执行上下文,运行完销毁运行时所在的执行环境。

    队列:队列是一种“先入先出”的数据结构。

    js执行过程过程中,可能会出现诸如setTimeout等WebAPI产生的异步任务。

    它们满足条件后的回调函数会进入队列。

    2.  事件循环机制

    主线程代码执行,同步任务进入执行栈,异步任务进入任务队列。

    其中主执行栈中的任务其实也是宏任务。

    有一个特殊函数window.requestAnimationFrame的回调函数在宏任务和微任务之间运行。

    执行顺序

    1. 先清空主执行栈中的任务。

    2.js引擎查看微任务队列有没有微任务,有的话,清空微任务队列。

    3. 查看宏任务队列有没有任务,如果有,取出第一个,将其加入执行栈。

    执行完成后,查看微任务队列是否有任务,有的话,清空微任务队列。

    4. 重复3,直到清空所有的任务。

    示例:

    function a(){
      console.log('a');
      function b() {
        console.log('b');
      }
      b();
    }
    a();
    setTimeout(function() {
      console.log('setTimeout1');
      Promise.resolve().then(() => {
        console.log('then1')
      }).then(() => {
        console.log('then2')
      }).then(() => {
        console.log('then3')
      })
    },1000)
    setTimeout(function() {
      console.log('setTimeout2')
    },500)
    console.log('end');
    // 运行结果如下
    a // 主执行栈
    b // 主执行栈
    end // 清空主执行栈
    // 500ms后setTimeout2加入宏任务队列,主执行栈为空,将其加入主执行栈
    setTimeout2 // 清空主执行栈
    // 1000ms后setTimeout1加入宏任务队列,主执行栈为空,将其加入主执行栈
    setTimeout1 // 清空主执行栈
    // 将then1加入微任务队列
    then1// 清空微任务队列
    // 将then2加入微任务队列
    then2 // 清空微任务队列
    // 将then3加入微任务队列
    then3 // 清空微任务队列

    3. 异步操作(不等于异步任务)

    异步操作只是手动的实现改变函数的调用顺序。不会进入任务队列。

    常见的异步操作是回调函数/事件监听模式/发布订阅模式;

    异步操作又可以分为串行异步/并行异步/串并行结合异步;

    1. 回调函数

    普通回调函数只是改变了函数的执行顺序,并不会异步任务,也不会进入任务队列。

    function a(callback){
        callback(); // 回调函数
    }
    function b(){}
    a(b); // b是a的回调函数

    2. 事件监听

    事件监听函数也是指定任务的执行顺序,不会进入任务队列。

      <button>click</button>
      <script>
        function a() {
          let button = document.querySelector('button');
          button.click(); //函数a触发监听事件
        }
        function b() {// 在a执行触发后,再执行
          console.log("triggered by a");
    // 最后需要取消监听 } document.addEventListener(
    'click', b); </script>

    3.发布订阅

    任务执行后向“消息中心”发布(publish)消息, 其他任务订阅(subscribe)消息。

    // 伪代码
    function a() {
       XXX.publish('done'); //  发布消息;XXX表示消息中心
    }
    function b() {}
    //  a发布后引发b执行
    XXX.subscribe('done', b);//  b订阅消息
    
    // 如果b执行完,需要取消订阅!!!
    XXX.unSubscribe('done', f2);

    4. 串行异步--多个异步操作

    有两种实现方式:

    1)回调函数--逐级嵌套

    下次执行等待上次执行结束,彼此之间有耦合。setTimeout是异步任务,会进入任务队列。

    示例: 执行6次回调后执行final函数

        <script>
          function asyncFn(arg, callback) {
            console.log("参数:", arg);
            setTimeout(function(){
              callback(arg*2);
            }, 1000);
          }
          function final(value) {
            console.log("the final value equals ", value)
          }
          asyncFn(1, function(result) {
            asyncFn(result, function(result1) {
              asyncFn(result1, function(result2) {
                asyncFn(result2, function(result3){
                  asyncFn(result3, function(result4){
                    asyncFn(result4, function(result5){
                      final(result5);
                    })
                  })
                })
              })
            })
          })
          console.log('00')
        </script>
    callback

    执行结果如下:

    参数:1 
    00 
    参数:2
    参数:4
    参数:8
    参数:16
    参数:32
    the final value equals  64

    2)自定义一个函数(递归函数)

    <script>
          let items = [...new Array(6)].fill(1);
          function asyncFn(arg, callback) {
            console.log("参数:", arg);
            setTimeout(function(){
              callback(arg*2);
            }, 1000);
          }
          function final(value) {
            console.log("the final value equals ", value)
          }
          // 串行执行异步操作
          var paramValue = 1; // 初始化参数
          function serious(items) {
            if (items.shift()) {
              asyncFn(paramValue, function(result) {
                paramValue = result;
                serious(items);
              })          
            } else {
              final(paramValue)
            }
          }
          serious(items);
          console.log('00')
        </script>
    serious

    执行结果和上面一样。

    两种方式的执行结果都是6秒。

    5. 并行执行--多个异步

    并行执行的异步函数,彼此之间不存在执行先后顺序问题,没有耦合。

    几个异步函数同时执行,等待6个异步操作都执行完成后,执行final。

        <script>
          let items = [...new Array(6)].fill(1);
          function asyncFn(arg, callback) {
            console.log("参数:", arg);
            setTimeout(function(){
              callback(arg*2);
            }, 1000);
          }
          function final(value) {
            console.log("the final value equals ", value)
          }
          var result=[];
          items.forEach((item, index) => [
            asyncFn(index+1, function(res) {
              result.push(res);
              if (result.length === 6) { // 判断6个异步都执行完成
                final(res);
              }
            })
          ])
        </script>
    View Code

    执行结果如下:

    参数:1
    参数:2
    参数:3
    参数:4
    参数:5
    参数:6
    the final value equals  12

    并行执行完成耗时1秒

    6. 串并行结合

    串行异步操作耗时较多;并行效率,但是占用资源多,如果并行操作过多,很容易耗尽系统资源。

    可以通过限制并行的个数来提高效率的同时,避免过分占用资源。

    示例: 保证每次同时执行的异步任务是3个,等6个异步操作执行完成后执行final

    <script>
          function asyncFn(arg, callback) {
            console.log("参数:", arg);
            setTimeout(function(){
              callback(arg*2);
            }, 1000);
          }
          function final(value) {
            console.log("the final value equals ", value)
          }
          let items = Array.from(new Array(6), (_,index)=> index+1);
          let result=[];
          let limit = 3; //最大3个
          let running = 0; // 正在执行的个数
          function both() {
            while(running < limit && items.length > 0){
              let item = items.shift();
              asyncFn(item, function(result) {
                running--;
                if (items.length > 0) {
                  both();
                } else if (running ===0 ) { // 执行完毕
                  final(result)
                }
              })
              running++;
            }        
          }
          both();
        </script>
    both

    执行结果同上。 

    耗时2秒

  • 相关阅读:
    UIPickerView-一.01-
    闭包-01-Swift
    Swift 入门-01-概述
    git使用命令行-01-自己操作的
    FetchedResultsController-03-CoreData相关
    SQLite-05-增删改查
    Sqlite函数总结-04
    List<string>转xml
    比较两个List<T>是否相同
    获取DataTable前几条数据
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11734159.html
Copyright © 2020-2023  润新知