• JS的事件循环(Event Loop)


    JS的事件循环

    本文是我自己对事件循环的理解和总结,不会有太多的理论知识,已经有太多文章写过了,自己搜索下就能找到很多;
    同时,文章中的观点仅是本人自己理解,比较白话,不用太较真啊!

    事件循环是什么?

    事件是什么?

    事件是特定情况下触发的操作。这里的操作是用函数封装起来的,所以,事件就是在特定情况下触发的函数
    例如:

    • dom元素相关事件,在用户单击页面上的按钮或页面加载完成时触发;
    • 定时器中,在特定时间间隔后触发其绑定函数;
    • ajax请求中,在请求响应时,才会触发回调函数;
    • promise中,在其内代码有了结果后,再触发 then 函数;

    上面的这些情况的回调函数,都是在特定情况下触发的函数,所以都可以称为事件

    特定情况触发的函数

    注意,这里指的是“特定情况”,而不是按代码顺序执行时触发的,也就不是同步触发的,而是异步触发的。
    所以,事件是异步触发的。

    事件循环是什么?

    事物循环,顾名思义,就是事件的循环触发。没错,事件循环的核心就是“事件的循环触发”;
    同时事件循环还包括,这些异步操作的触发,回调函数的收集以及回调函数的触发等一系列操作。这些操作其实贯穿整个JS代码的运行
    综上所述:事件循环就是JS代码的运行机制
    另:
    异步操作的触发, 其实是 同步执行的。
    而回调函数的触发,是异步执行的

    事件循环是如何实现的?

    JS是单线程的

    JS是单线程的,所以浏览器只会分配一个线程用来解析执行JS代码,同时所有的JS代码也只能在这一个线程中执行。此线程为JS引擎线程。
    因为是单线程的,所以代码是同步执行的,也就是按代码顺序执行的,只有前一个任务执行完成,才能执行下一个任务。
    那么如果实现在特定情况下触发函数呢?这就需要用于事件循环了

    JS如何实现异步的

    1)JS是解释型语言。所以JS是需要在宿主环境中才能运行的。而宿主环境是多线程的。
    2)JS是单线程语言。所以宿主环境分配一个线程来解析运行JS代码。
    3)宿主环境再利用其他线程来辅助完成其他的一些异步功能。同时创建相关任务队列,用来存储当异步功能完成后要调用的回调函数。
    4)最后再循环将这些回调函数放入到JS引擎中的执行栈中,进行调用。

    所以:JS的异步功能,是宿主环境的各线程相关联合实现的。而这整个过程可以称为事件循环

    不同的宿主环境对JS的事件循环的实现是不同的

    JS引擎运行JS代码涉及的几个概念

    • 执行栈
    • 执行上下文

    宿主环境实现事件循环涉及的几个概念

    • 事件注册表 event table
    • 任务队列 task queue
      • 微任务队列
      • 宏任务队列

    任务分类

    • 同步任务
    • 异步任务
      • 宏任务(在ES5版本中,异步任务只有宏任务,都放在任务队列中)
      • 微任务(在ES6版本后,异步任务中添加了微任务,此后异步任务就有了两种)

    JS中的任务指的是什么?
    JS中的任务通常指的是需要通过代码完成的操作或功能。这些任务可以是简单的计划操作,也可以是复杂的功能;可以是一行代码,也可以是一个函。

    浏览器环境中的“事件循环”实现

    异步任务中如何产生宏任务/微任务?

    • 宏任务
      • 全局作用域(script整体代码) :可以将整个script代码看成一个宏任务。算默认宏任务,在执行此任务时,再触发其他异步任务,并注册事件信息。
      • setTimeout
      • setInterval
    • 微任务
      • promise 的 then 方法
      • async 方法中,await 语句行后面的代码
      • window.queueMicroTask(fn) 的 fn 方法 中
      • new window.MutationObserve(fn) 的方法中

    个人总结的执行顺序图解法:

    图解法中的几条规则:

    • JS一开始是执行全局同步代码,后面再开始异步代码执行;但在“事件循环”中,可以将全局同步代码看作成一个默认执行的宏任务。所以在执行图中,同步代码作为宏任务列表的第一个宏任务。
    • 所有的宏任务都是独立,隔离的,不存在上下级关系,只有进入宏任务列表的先后关系。所以构建了一个宏任务列表,宏任务队列为事件循环的第一层循环。
    • 宏任务中的同步任务和微任务之间存在上下级关系,根据这些关系可以构建一棵以宏任务为根节点的任务树。不需要关系宏任务,它会添加到宏任务列表中

    图解法评分分两个步骤:

    1. 构建执行图
      • 创建一个宏任务列表;此列表为事件循环的第一层循环
        • “script全文宏任务”作为宏任务队列的第一个任务,也是默认执行的宏任务(其实就是最外层的同步代码),可命名为 h0 。
        • 在执行同步代码时,若遇到宏任务,则在列表的后面假加任务,可依次命名为 h1, h2, … , hn。
      • 创建宏任务的下级树;将宏任务中的同步任务和微任务以上下级关系构建一棵任务树。
        • 以宏任务为任务树的根节点。
        • 将宏任务中所有的同步任务的输出写在此节点上,可用 t 标识了点。如:t : 1,3,4
        • 若遇到微任务,则新建一个微任务节点,以“w+次序“标识节点名;
          • 若微任务中只有同步任务或只有一个微任务,则可将输出直接写上,如:w0 : 5,6
          • 若同时存在同步/微任务,可分别创建节点,并写上输出。
          • 若微任务中还有下级微任务,则可继续向下创建节点。
      • 若有多个宏任务,则重复上一步骤。(注意:在之前代码的执行过程中,可添加宏任务)

    2)根据任务节点图得出最终输出;

    • 根据宏任务列表自上而上,从默认任务开始;
    • 从宏根节点开始,向下级记录输出,每层节点中自上向下记录输出。
    • 一棵宏任务树记录完成后,再向后开始新的宏任务树的输出记录
    • 最终得出整段代码的执行顺序输出。

    先上几个示例-用图解法

    一、示例1

    	// 默认宏任务 :h0 ----- 同步任务
    	console.log("0");
    	setTimeout(function() {  // 宏任务1 : h1
    	  console.log("1");     
    	  new Promise(function(resolve, reject) {
    	    console.log("2");  
    	    resolve();
    	  }).then(() => {
    	    console.log("3");  
    	  });
    	}, 0);
    	
    	new Promise(function(resolve, reject) {
    	  console.log("4");  // 同步任务
    	  resolve();
    	}).then(() => {
    	  console.log("5");  // 微任务
    	});
    	console.log("6");   // 同步任务
    	// 输出:0  4  6  5  1  2  3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    图解
    在这里插入图片描述
    图解说明:
    1)h0, h1 组成宏任务列表,若还有宏任务,则在下面添加节点
    2)以 “t:” 为前缀的节点中,冒号后面的为同步任务输出。
    3)以 “w:” 为前缀的节点中,冒号后面的为微任务输出。这里只有一个任务,所以就没有加序号了。
    4)最后输出:先算出每个宏任务输出,然后再将每个宏任务输出按先后累加起来。

    示例2

      // h0 - 最外层同步任务
      console.log(0)   
          
      // h1   
      setTimeout(() => {      
      	// h3     
        setTimeout(()=>{console.log(6)},0)    
        console.log(1) //
        var p2 = new Promise((n1, n2) => {
          n1(1000)
        })
        p2.then(()=>{console.log(7)}) //
      }, 0)
      
      // h2
      setTimeout(() => {
      	// h4
        setTimeout(() => {console.log(2)}, 200) //
        var p3 = new Promise((n1, n2) => {
          n1(1000)
        })
        p3.then(()=>{console.log(8)})//
        console.log(2)//
      }, 0)
    
      var p1 = new Promise((n1, n2) => {
        n1(1000)
      })
    
      p1.then(() => {console.log(3)})             //
    
      console.log(5)       //
      // 输出:4  5  3  1  7  2  8  6  2 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    图解-执行顺序
    在这里插入图片描述
    示例3

        setTimeout(() => {    // ----------- h1
    		console.log(0);
    	});
    	new Promise(resolve => {
    		console.log(1);
    		setTimeout(() => {   // ----------- h2
    			resolve(); //这里!!!!!!!
    			var p1=new Promise((n1,n2)=>{n1(20)})
    			p1.then(() => console.log(2));
    			console.log(3);
    			setTimeout(()=>{console.log(9)},0)   // ----------- h3
    		});
    		new Promise((n1,n2)=>{n1(20)}).then(() => console.log(4));
    	}).then(() => { //这里的then函数要等resolve()执行后才能执行  一点注意!!!
    		console.log(5);
    		var p2=new Promise((n1,n2)=>{n1(20)})
    		p2.then(() => console.log(8));
    		setTimeout(() => console.log(6));   // ----------- h4
    	});
    	console.log(7);
    	// 输出: 1  7  4  0  3  5  2  8  9  6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    示例4

      console.log(1) 
      setTimeout(() => { 
        setTimeout(()=>{console.log(2)},0) 
        console.log(3) 
        var p2 = new Promise((n1, n2) => {
          n1(1000)
        })
        p2.then(()=>{console.log(4)}) 
      }, 0)
      setTimeout(() => {
        setTimeout(() => {console.log(5)}, 200) 
        var p3 = new Promise((n1, n2) => {
          n1(1000)
        })
        p3.then(()=>{console.log(6)})
        console.log(7)
      }, 0)
      var p1 = new Promise((n1, n2) => {
        n1(1000)
      })
      p1.then(() => {console.log(8)})
      console.log(9) 
      // 输出 : 1  9  8  3  4  7  6  2  5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    Nodejs环境中的“事件循环”实现

    异步任务中如何产生宏任务/微任务?

    • 宏任务
      • script全文。
      • setTimeout
      • setInterval
      • setImmediate,浏览器中没有
    • 微任务
      • promise 的 then 方法
      • async 方法中,await 语句行后面的代码
      • queueMicroTask(fn) 的 fn 方法 中
      • new MutationObserve(fn) 的方法中
      • process.nextTick,浏览器中没有

    执行顺序:

    1. 执行默认宏任务(script全文代码) - 在此过程中添加其下级任务到队列中
    2. 循环执行 nextTick微任务队列
    3. 循环执行 其他微任务列表
    4. 再重复上面1,2,3 这3个步骤。不过不是执行默认宏任务,则是新添加的宏任务。
    5. 当所有其他宏任务执行完毕后,再循环执行 setImmediate 队列中的宏任务。

    示例1(包括 setTimeout宏任务、nextTick微任务,普通微任务)

    	console.log('1');
    	async function async1() {
    	    console.log('2');
    	    await async2();
    	    console.log('3');
    	}
    	async function async2() {
    	    console.log('4');
    	}
    	
    	process.nextTick(function() {
    	    console.log('5');
    	})
    	
    	setTimeout(function() {
    	    console.log('6');
    	    process.nextTick(function() {
    	        console.log('7');
    	    })
    	    new Promise(function(resolve) {
    	        console.log('8');
    	        resolve();
    	    }).then(function() {
    	        console.log('9')
    	    })
    	})
    	
    	async1();
    	
    	new Promise(function(resolve) {
    	    console.log('10');
    	    resolve();
    	}).then(function() {
    	    console.log('11');
    	});
    	console.log('12');
    	// 输出 : 1  2  4  10  12  5  3  11  6  8  7  9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述
    示例2
    包括:setTimeout宏任务,setImmediate宏任务,process.nextTick微任务,promise.then微任务

    console.log('1');   // 同1
    
    setTimeout(function () { // 宏1
        console.log('2');    // 宏1-同1
        process.nextTick(function () {  // 宏1-n微1
            console.log('3');
        })
    
        new Promise(function (resolve) {
            console.log('4');    // 宏1-同2
            resolve();
        }).then(function () {
            console.log('5')  // 宏1-微1
        })
    })
    
    new Promise(function (resolve) {
        console.log('6');   // 同2
        resolve();
    }).then(function () {   // 微1
        console.log('7')
    })
    
    process.nextTick(function () {  // n微1
        console.log('8'); // 
    })
    
    setImmediate(() => {   // 宏2
        console.info('9')  // 主线程和事件队伍的函数执行完成之后立即执行  和setTimeOut(fn,0)差不多
    })
    
    new Promise(function (resolve) {
            console.log('10');   // 同3
            resolve();
        }).then(function () {  // 微2
        console.log('11')
    
    })
    
    setTimeout(function () {   // 宏3
        console.log('12');
        setImmediate(() => {
            console.info('13')
        })
    
        process.nextTick(function () {
            console.log('14')
        })
    
        new Promise(function (resolve) {
            console.log('15');
            resolve();
        }).then(function () {
            console.log('16')
        })
    })
    
    process.nextTick(function () {  // n微2
        console.log('17');
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    在这里插入图片描述

  • 相关阅读:
    upload-labs文件上传1-5关
    Python整理本机Mp3音乐文件
    rhel配置DNS
    【面试经典150 | 哈希表】同构字符串
    Python sort面试题目
    南卡火力全开,发布骨传导百元旗舰标杆,升级蓝牙5.3,内置4G存储,开启运动新体验!
    CentOS7安装Xrdp以便Windows远程桌面连接
    记录一下Kubernetes的快速入门
    Python获取Window系统注册表安装的软件,再卸载软件
    动态 SQL
  • 原文地址:https://blog.csdn.net/xufang461010923/article/details/133126881