Promise面试题引起的JavaScript事件循环机制复习

·  阅读 1067
Promise面试题引起的JavaScript事件循环机制复习

朋友去面试了,发来个题,典型的JavaScript事件循环机制考察,默默复习了下,哈哈哈哈,一起看看吧!

console.log('script start');

setTimeout(async function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

这个是查考JavaScript事件循环机制,你的答案是什么?

正确答案是:script start, script end, promise1, promise2, setTimeout

你答对了吗?

我们来看看为什么

  • 整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start
  • 遇到 setTimeout,其回调函数被分发到宏任务的任务队列(Event Queue)中
  • 遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,将其分到微任务 Event Queue 中,记为 then2
  • 遇到 console.log,输出 script end
  • 至此,任务队列(Event Queue)中存在三个任务,如下表:
宏任务微任务
setTimeoutthen1
then2

执行微任务,首先执行then1,输出 promise1, 然后执行 then2,输出 promise2,这样就清空了所有微任务 执行 setTimeout 任务,输出 setTimeout 至此,输出的顺序是:script start, script end, promise1, promise2, setTimeout

看了答案之后,默默去复习了下,然后总结如下,小笔记。。。。

想好好了解的话,去看看文末的连接就好了,下面就可以不看了。。。我是给自己记得小笔记,哈哈哈哈哈!

“进程” 和 “线程”

什么是进程?

对操作系统来说执行一个任务就是一个进程。

什么是线程?

在一个进程上同时需要执行多个“子任务”。例:在使用word时,同时可以写字,拼写检查,打印等事情,在一个进程内部同时要干多件事,每件事都是一个线程。

  • 一个进行至少有一个线程。
  • 线程和进程都是交替执行任务。

JS语言的特性:一门单线程非阻塞的语言

单线程:是由于js最初的用途来决定的,与浏览器交互单线程意味着JS代码在执行的任何时候都是由主线程来处理所有任务
非阻塞:JS在执行异步任务的时候无法立刻返回结果,需要花时间等待,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定的规则去执行回调函数。

如果JS是多线程,同时对一个DOM进行不同操作的时候会不知道处理的优先级而造成混乱。所以单线程保证了执行的顺序,但是限制了JS执行的效率

浏览器下JS引擎的事件循环机制

JS在初次执行代码时:会将不同的变量存于内存中的不同位置。

JS内存存放位置:
1:堆(heap):存放一些对象
2:栈(stack):存放基础数据类型以及堆中对象的指针,注:“执行栈”区别于这个“栈”

JS调用一个方法的过程:

当调用一个方法时,js会生成与这个方法对应的执行环境(context),又叫做上下文,在执行环境中存在这个方法的私有作用域,上层作用域的指向(this对象),方法的参数。

JS中“同步代码”是如何执行的?

(1)、什么是“执行栈”?——用于方法排队等候的“暂存地”

由于JS是单线程,同一时间只能执行一个方法,其余方法需要排队等候依次调用,所以这个排队等候的地方就是“执行栈”

(2)、关于执行栈中的排队顺序

当JS脚本第一次执行的时候,JS引擎会解析代码并将其中的“同步代码”顺序加入到执行栈,然后从头开始执行,如果当前执行的是一个方法,就会在执行栈中添加这个方法的执行环境,当方法执行完毕会退出执行环境并销毁,回到上一个方法的执行环境,这个过程反复执行,直到执行栈中的代码完全执行完毕。
在一个方法的执行环境中,还可以调用别的方法,甚至可调用自己本身,其结果就是在执行环境中再添加一个执行环境,这个过程是可以无限循环下去的,除非发生“栈溢出”(即超过了使用内存的最大值,造成内存泄露)

js中“异步代码”是如何执行的?

(1)、事件队列(task queue)—— 存放异步事件的地方

JS引擎遇到一个异步事件的时候,并不会一直等待结果,而会将这个事件挂起,继续执行“执行栈”中的其他任务,当这个异步事件返回结果后,js会将这个事件加入到另外一个队列中(即事件队列)
异步事件放入到事件队列中不会立即执行,而是等待执行栈中所有的任务都执行完成,主线程处于闲置状态的时候,主线程会去查找事件队列中是否有任务,如果有,按照顺序依次调用
主线程取出第一位事件,并把事件对应的回调函数放到执行栈中,然后执行同步代码,依次反复,这个过程就叫做“事件循环”。

(2)、异步任务的类别:宏任务和微任务

宏任务macrotask 主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务microtask 主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)

宏任务和微任务的执行顺序: 主线程会先查看微任务队列是否有事件存在,如果有,先逐一执行,直到微任务全部执行完毕然后再去执行宏任务,如果没有,再去宏任务中取出事件放入到执行栈中执行。

任务队列

所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列( Event Queue )的机制来进行协调。具体的可以用下面的图来大致说明一下:

image.png

Event Loop (事件循环机制)

同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue 。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。这个概念一定要背下来

在事件循环中,每进行一次循环操作称为tick,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:

1.在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次)
2.检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue
3.更新 render
4.主线程重复执行上述步骤

可以用一张图来说明下流程:

image.png

然后,再看那个题,就理解啦啦啦啦啦啦

引用

深入浅出理解JS事件循环机制

深入理解JavaScript事件循环机制

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改