• 高级前端进阶(五)


    详解JavaScript中的事件循环机制!!!

    一、简单讲解

    这个大家应该或多或少都知道的

    for (var i = 0; i < 10; i++) {
      setTimeout(() => {
        console.log(i); // 输出10个10
      });
    }
    

    解析:先执行for循环,循环叠加i,然后再执行setTimeout 10遍,所以会输出10个10
    变体:

    for (let i = 0; i < 10; i++) {
      setTimeout(() => {
        console.log(i); // 依次输出0-9
      });
    }
    

    我们知道var跟let本质区别就是作用域的问题,所以会联想到let导致的,但在这一点上,并不是作用域问题,因为从事件循环机制来解释是有问题的
    以上的代码等价于

    for (var i = 0; i < 10; i++) {
      (function (i) {
        setTimeout(() => {
          console.log(i);
        });
      })(i); // 立即执行函数,以上的let应该是将代码特殊处理了下
    }
    

    立即执行函数:函数声明后立即执行。
    从事件循环的角度来说,应该先执行for循环将i叠加到10,执行完后再去执行for循环体(setTimeout),此时的i变成了10,所以每次输出都是10,
    这里使用let,应该是特殊处理了一下的。

    二、深入讲解

    我们知道JavaScript语言是单线程的,至于为啥是单线程?
    假设有两个线程,一个在页面上新增一个div,另一个线程在页面上删除div,那最终听谁的?
    那JavaScript怎么实现异步的呢?
    在JavaScript中,有两类任务:同步任务和异步任务。
    同步任务:普通的任务,依次从上往下执行。
    异步任务:又分为宏任务、微任务。

    宏任务:setTimeout跟setInterval
    微任务:Promise().then() 这里要注意一下,Promise方法里面的是同步任务,then里面的才是微任务
    执行顺序:先执行同步任务,遇到异步任务,将其放入到宏任务或者微任务队列中,然后优先执行微任务,接下来再去执行宏任务。

    简单的例子:

    setTimeout(function() {
        console.log(1)
    }, 0);
    
    new Promise(function(resolve, reject) {
        console.log(2); // 这里是同步任务
        resolve();
    }).then((res) => {
        console.log(3);
    })
    console.log(4);
    // 输出结果是 2  4  3  1
    

    解析:从上往下执行,setTimeout是宏任务,放到宏任务队列中,
    Promise里面的是同步任务,所以先执行,输出2,then里面的是微任务,放到微任务队列中,
    最后一个是同步任务,执行,输出4,
    然后执行微任务,输出3
    最后执行宏任务,输出1

    来点难度的:

    console.log(1);
    
    setTimeout(function () {
      console.log(2);
    }, 0);
    
    setTimeout(function () {
      console.log(3);
    
      setTimeout(function () {
        console.log(4);
      }, 0);
    }, 0);
    
    new Promise(function (resolve, reject) {
      console.log(5);
      resolve();
    }).then((res) => {
      console.log(6);
      new Promise(function (resolve, reject) {
        console.log(7);
        resolve();
      }).then((res) => {
        console.log(8);
      });
    });
    
    new Promise(function (resolve, reject) {
      console.log(9);
      resolve();
    })
      .then((res) => {
        console.log(10);
      })
      .then((res) => {
        console.log(11);
      });
    
    console.log(12);
    // 输出 1  5   9   12
    //      6  7  10   8   11
    //      2  3  4
    

    解析:原理跟上面一样,不过需要注意的是,8跟11的顺序:,在这里then的层级,将各个Promise里面第一层的then放在一起,第二层的then放在一起,依此类推,
    然后依次执行第一层的,执行完第一层,接下来执行完第二层,依此类推。
    第一层(6,7,10)
    第二层(8,11)
    所以先输出8, 再输出11的。

    再来一个(也最可能出错或者无法理解的):

    console.log(1);
    async function fn() {
      console.log(2);
      await console.log(3);
      console.log(4); // 这一步,你应该会有问题
    }
    setTimeout(() => {
      console.log(5);
    }, 0);
    fn();
    new Promise((resolve) => {
      console.log(6);
      resolve();
    }).then(() => {
      console.log(7);
    });
    console.log(8);
    // 输出:1  2  3   6   8   4  7  5
    

    解析:async await是Promise的语法糖,只是针对写法上的优化
    将async await翻译成Promise:

    async function fn() {
      console.log(2);
      await console.log(3);
      console.log(4); // 这一步,你应该会有问题
    }
    // 等价于
    function fn() {
      return new Promise((resolve, reject) => {
        console.log(2);
        resolve(
          (() => {
            console.log(3);
          })() // 立即执行函数
        );
      }).then(() => {
        console.log(4);
      });
    }
    

    这样,你应该能够明白,为啥输出3之后不是立即输出4了吧!

    再来一道加深巩固的:

    console.log(1);
    async function fn() {
      console.log(2);
      await console.log(3);
      await console.log(4);
      console.log(5);
    }
    setTimeout(() => {
      console.log(6);
    }, 2000);
    setTimeout(() => {
      console.log(7);
    }, 1000);
    fn();
    new Promise((resolve) => {
      console.log(8);
      new Promise((resolve) => {
        console.log(9);
        resolve();
      }).then(() => {
        console.log(10);
      });
      resolve();
    }).then(() => {
      console.log(11);
      new Promise((resolve) => {
        console.log(12);
        resolve();
      }).then(() => {
        console.log(13);
      });
    });
    console.log(14);
    // 输出 1  2  3  8  9  14
    //      4  10 11 12 5  13
    //      7  6 
    

    我相信,你现在应该理解掌握了!

    只要钻的深,铁杵都能给你磨成针!!!
  • 相关阅读:
    转: jsp之c标签
    win10下乌龟git安装和使用
    使用Node.js+Socket.IO搭建WebSocket实时应用
    前端优化
    pyqt5环境搭建
    pandas分页读取excel
    PySpark之RDD操作
    PySpark环境搭建
    Django与Celery最佳实践
    Elasticsearch的安装与简单使用
  • 原文地址:https://www.cnblogs.com/ywjbokeyuan/p/16011512.html
Copyright © 2020-2023  润新知