前端之事件机制!!!

几乎每次面试都会本问到浏览器的事件机制,今天来整理一下!本文包含进程与线程、事件循环、宏任务与微任务的梳理,文字偏多,请稍微多点耐心,是经过本人学习了一些资料后的一点小总结,如果有说明不准确甚至错误的地方,欢迎路过的大神们指正!

首先要知道的以下几个概念:

  1. 进程:进程是CPU资源的最小分配单位
  2. 线程:线程是CPU调度的最小单位
  3. 线程依赖于进程
  4. 一个进程至少有一个线程
  • 而咱们的浏览器就是多进程的,浏览器之所以可以工作,是因为CPU给它分配了资源。就拿Chrome来说。它的每个页签都是一个独立的进程,验证方法可以看下电脑的资源管理器,或者思考下,如果不是多进程的,那一个页签down了,那其他的岂不是也都凉了?而实际情况也不是这样的,所以进一步地认识到浏览器是多进程的。

  • 浏览器中有个非常重要的进程是浏览器渲染进程,关于页面渲染、JavaScript脚本执行、事件循环都是在其中完成的!

  • 浏览器的渲染进程是多线程的,比如上面提到的GUI渲染线程、JavaScript引擎线程等。GUI渲染线程负责渲染浏览器的界面,解析HTML、CSS,生成DOM Tree 和 Render Tree;JavaScript线程就是用来执行JavaScript的脚本咯~!需要注意的是二者是一个互斥的关系,所以当HTML页面中含有script脚本的话,页面渲染就会被脚本阻断,直至加载执行完脚本之后才会接着渲染页面(defer、async另说),这也是要把script脚本尽量放到body的最后的原因啦!

  • 众所周知,JavaScript是一门单线程的语言,那为什么它是单线程呢?这是因为JavaScript可以操作DOM,而渲染线程又要渲染DOM,而且两段JavaScript也不能同时执行,所以它必须是一个单线程的。

  • 那么问题来了,JavaScript是一个单线程的,也就是说一次只能做一件事情咯,那空闲的时候岂不是浪费资源了???别急,这时候就该咱们的“异步”大哥上场啦!遇到异步的时候就不用管它啦,主线程该干嘛还是干嘛,主线程中为空之后,再去看看有没有异步任务,有并且满足执行条件的话,就把这个任务放到主线程的执行栈中,然后主线程去执行,那么怎么知道还有没有其他的异步任务呢??事件轮询解决了这个问题,事件轮询会轮询执行“异步队列”中的异步任务,依次将其放到主执行栈中。

  • 那是不是及时 JavaScript 提供了事件循环呢?不是的!**与其说是 JavaScript 提供了事件循环,倒不如说是嵌⼊ JavaScript 的 user agent 需要通过事件循环来与多种事件源交互。**以上定义参考这个链接

JavaScript的事件

  1. 内部事件:JavaScript内部可以执行的各种事件,比如说 Promise 的 then 和 catch、MutationObserver。那么存放这些事件的叫内部队列,标准文档将其称之为Microtask Queue,也就是常说的“微任务”。
  2. 外部任务:JavaScript调用浏览器的各种事件(Web API),比如用户交互(键盘、鼠标)、网络请求(Ajax等)、定时器(setTimeout)、DOM操作等。同样的,存放这些事件的叫外部队列,标准文档将其称之为Task Queue,也是常说的“宏任务”。
    那各种事件总该有个先来后到鸭,那就得排队!执行机制如下图:
    前端之事件机制!!!_第1张图片
    说了那么半天,都是 JavaScript 引擎线程,那GUI渲染线程哪儿去了?从不能一直执行JavaScript脚本,而不更新页面吧,来鸟~~

前端之事件机制!!!_第2张图片
即:

  1. 执行同步任务,至主执行栈为空
  2. 从宏任务队列(外部任务)中取出一个宏任务,放到主执行栈中,执行。
  3. 从微任务队列(内部任务)中取出所有微任务(清空内部队列),将它们放到主执行栈中,执行。
  4. 浏览器渲染
  5. 重复 2 ~ 4

以上都是浏览器的事件循环,下面来说下Node的事件机制:
Node中也有内部队列和外部队列:

内部队列:Promise的 then 和 catch
外部队列:和浏览器比起来,Node 中没有外设,但是有文件的 I / O,还有定时器相关的。

  • 在node v10之前,允许执行多次外部事件再切换到内部事件中。
  • 在v10之后,与浏览器保持一致,先执行一次外部任务,再清空内部任务。其中值得注意的是,node的外部队列中也是有顺序的,相当于外部队列中也有自己的内部任务和外部任务,其中setTimeout是外部任务,setInterval是内部任务。

理论,大概也就这么多了,来结合一些题目来康康掌握了多少昂!!
浏览器环境下:

console.log('1');
setTimeout(function() {
	console.log('2');
}, 0);
Promise.resolve().then(function() {
	console.log('3');
}).then(function() {
    console.log('4');
});
console.log('5');
1
5
3
4
2
// 你答对了没有呢??

console.log('1');
setTimeout(function() {
    console.log('2');
 	Promise.resolve().then(function() {
     	console.log('3');
 	});
}, 0);
Promise.resolve().then(function() {
    console.log('4');
}).then(function() {
    console.log('5');
});
console.log('6');
1
6
4
5
2
3
// 你答对了没有呢??

Node 最新环境下:

setTimeout(()=>{
     console.log('1');
     Promise.resolve().then(function() {
     	console.log('2');
     });
});
setTimeout(()=>{
     console.log('3');
     Promise.resolve().then(function() {
        console.log('4');
     });
});
1
3
2
4
// 你答对了没有呢??

欢迎交流和指正 [email protected]

你可能感兴趣的:(前端,-,JavaScript)