js运行机制详解(Event Loop)

1、基础知识

  • 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 异步任务:不进入主线程、而进入"任务队列"(event queue)的任务,只有"任务队列"通知主线程某个异步任务可以执行了,该任务才会进入主线程执行。js中的异步任务有:setTimeout、setInterval、ajax、promise 等,需要注意的是promise本身是同步的,但其 .then()、.catch()、.finally() 却都是异步的。
  • 所有同步任务都在主线程上执行,形成一个执行栈。js引擎存在monitoring process进程,会持续不断的检查执行栈是否为空,一旦为空,就会去 task queue(任务队列) 检查是否有等待被调用的函数。这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环机制)。
  • setTimeout(fn,0)
    setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。需要注意的是,setTimeout()只是将事件插入了"任务队列"的尾部,因此要等到同步任务和"任务队列"现有的事件都处理完,事件才会得到执行。但是同步任务或"任务队列"现有的事件有可能耗时很长,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。

2、几个问题

2.1、JS为什么是单线程的?

  • js作为浏览器脚本语言,它的主要用途是操作DOM,这也就决定js必须是单线程(避免几个线程同时操作同一个DOM);单线程就意味着,所有任务需要排队,前一个任务结束才能执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
  • 为了利用多核CPU的计算能力,H5的Web Worker实现的“多线程”实际上指的是“多子线程”,完全受控于主线程,且不允许操作DOM;所以这个新标准并没有改变JavaScript单线程的本质。

2.2、JS为什么需要异步?
  如果 js 中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。 对于用户而言,阻塞就以为着“卡死”,这样就导致了很差的用户体验。比如在进行ajax请求的时候如果没有返回数据后面的代码就没办法执行。

2.3、js 单线程如何实现的异步?
  js 中的异步以及多线程都可以理解成为一种“假象”,就拿h5的WebWorker来说,子线程有诸多限制,不能控制DOM,不能修改全局对象等等,通常只用来做数据处理。JS异步的执行机制其实就是事件循环(event loop)。

3、Event Loop事件循环

  • 宏任务(macro-task):整体代码script、setTimeout、setInterval
  • 微任务(mincro-task):Promise.then(catch、finally)、process.nextTick()、async

  微任务中 process.nextTick() 是 node.js 中的,作用是在下一次 Event Loop(主线程读取"任务队列")之前触发 process 指定的回调函数(本次 Event Loop 不会执行)。

①事件循环图
事件循环过程图(1)、整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;
(2)、同步任务会直接进入主线程依次执行;
(3)、异步任务会再分为宏任务(进入宏任务队列) 和 微任务(进入微任务队列)。
(4)、当主线程内的任务执行完毕(主线程为空时),会检查微任务的任务队列,如果有任务,就进入主线程全部执行,如果没有就从宏任务队列读取下一个宏任务执行;
(5)、每执行完一个宏任务就清空一次微任务队列,此过程会不断重复,这就是Event Loop;

4、代码示例

4.1 同步任务

console.log(1);
console.log(2);
console.log(3);
//执行结果:1、2、3 ,按顺序一步步执行

4.2、同步任务混合异步任务

console.log(1);
setTimeout(function() {
    console.log(2);
},1000)
setTimeout(function() {
    console.log(3);
},0)
console.log(4);
/*
	执行结果:1、4、3、2
    分析:
        同步任务按照顺序一步一步执行,所以先输出1、4
        异步任务:当执行到异步任务(如上的 setTimeout 宏任务)时,先将其加入 Event
    Table(事件表格) 中,如果 Event Table 中的任务满足某种条件(如达到定时器的时间设定)
    后就将其加入 Event Queue(任务队列) 中去(谁先满足条件谁先加入;队列特点:先入先出)。
    当同步任务完成后,便从Event Queue 中读取事件到主线程执行,并按顺序输出3、2。
*/
for (var i = 1;i < 6;i ++) {
  setTimeout(function fn() {
      console.log(i)
  },i * 1000)
}
/*
	打印5个6,因为 for 循环作为同步任务先执行完毕,此时 i 为 6,再按顺序执行5个加入
Event Queue 的异步 setTimeout(),所以打印5个6。将var改为let或使用闭包,可以输出正
常值。
*/

4.3、微任务和宏任务练习1

console.log(1);
setTimeout(function() {
	console.log(2)
},1000);
			
new Promise(function(resolve) {
	console.log(3);
	resolve();
}).then(function() {
	 console.log(4)
});
console.log(5);
/*
	执行结果是:1、3、5、4、2
	1、3、5是同步任务,所以先输出,.then()是微任务所以加入微任务队列,setTimeout()
	是宏任务所以加入宏任务队列,主线程清空后先执行微任务在执行宏任务,所以输出4、2
*/

4.4、微任务与宏任务练习2

console.log(1);
			
setTimeout(function() {
	console.log(2);
	setTimeout(function() {
		console.log(3);
		new Promise(function(resolve) {
			console.log(4);
			resolve();
		}).then(function() {
			console.log(5);
		});
	}, 1000)
	new Promise(function(resolve) {
		console.log(6);
		resolve();
	}).then(function() {
		console.log(7);
	});
}, 1000)
			
setTimeout(function() {
	console.log(8);
}, 1000)
			
console.log(9);
//执行结果:1、9、2、6、7、8、3、4、5

4.5、微任务和宏任务练习3 (这个比较绕 !可以用笔写一下 )

new Promise(resolve => {
	console.log(1);
	setTimeout(() => {//①
		resolve(); //②
		Promise.resolve().then(() => {//③
			console.log(2);
			setTimeout(() => console.log(3));//④
			Promise.resolve().then(() => console.log(4));//⑤
		});
	});
	Promise.resolve().then(() => console.log(5));//⑥
}).then(() => { //⑦
	console.log(6);
	Promise.resolve().then(() => console.log(7));//⑧
	setTimeout(() => console.log(8));//⑨
});
console.log(9);
//执行结果:1、9、5、6、2、7、4、8、3

参考与:
JavaScript执行机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值