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)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 

图解-执行顺序
在这里插入图片描述
示例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

在这里插入图片描述
示例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

在这里插入图片描述

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

在这里插入图片描述
示例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
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值