700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 当面试官问 promise 的时候 他们希望听到什么(二)

当面试官问 promise 的时候 他们希望听到什么(二)

时间:2020-01-04 06:46:32

相关推荐

当面试官问 promise 的时候 他们希望听到什么(二)

目录

前言

一、前提知识

1、JS单线程机制

2、JS 任务队列与事件循环

3、Promise 回顾

二、题目实战

1、开头提到的题目

2、稍有难度

3、挑战升级

前言

上一篇文章,当面试官问 promise 的时候,他们希望听到什么(一)_czjl6886的博客-CSDN博客笔者介绍了有关 promise 的理解和基本使用相关的面试问题,但是,只会这些,是不能帮助我们通过面试的。接下来,笔者再来讲解在面试中 promise 常见的编程题目。

先来一道面试题试试水:

setTimeout(() => {console.log(1)},0)Promise.resolve().then(()=>{console.log(2)})Promise.resolve().then(()=>{console.log(3)})console.log(4)

上面的代码的在浏览器的控制台的输出顺序是什么呢?不妨自己试试看。

注意:本篇文章中,我所说的打印顺序都是基于浏览器的。

之前学过 promise 了,肯定一部分人会说小意思,但是,只会 promise ,是远远不够滴!

要成功做对上面的题目,还需要掌握以下知识点。

一、前提知识

1、JS单线程机制

作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM 。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

为了避免复杂性,从一诞生,JavaScript 就是单线程,也就是同一个时间内只能做一件事情。HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM 。所以,这个新标准并没有改变 JavaScript 单线程的本质。

2、JS 任务队列与事件循环

由于 js 是单线程,如果一个任务执行时间过长,那么,它的下一个任务就会等待很久,如,浏览器加载图片、视频等资源需要很长时间,这个时候,浏览器页面如果是一直卡着,直到资源加载完成,无疑会降低用户体验。

因此,js 将任务分为两种:同步任务( synchronous )和异步任务( asynchronous )

同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

除了广义的同步任务和异步任务,对任务有更精细的定义:

macro-task (宏任务) :整体script 代码、setTimeout,setInterval 等

micro-task (微任务) :Promise,async / await,process.nextTick 等

总结:

所有的同步任务都在主线程上执行,行成一个执行栈。当主线程的同步任务执行完毕,再执行任务队列中的异步任务。遇到宏任务,先将宏任务放入到”宏任务队列“,遇到微任务,将微任务放到”微任务队列“。判断”微任务队列“是否为空,不为空的话,将”微任务队列“执行完毕后,再执行”宏任务队列“。这个不断重复的过程,就是事件循环( Event Loop )

3、Promise 回顾

Promise 构造函数会立即执行,Promise.then() 内部的代码在当次事件循环的结尾立即执行(微任务)。promise 的状态一旦由 pending 变为 fulfilled 或rejected ,当前 promise 就被标记为完成,后面不会再次改变该状态。resolve()函数和 reject() 函数都将当前 Promise 状态改为完成,并将异步结果 value 或错误结果 reason 当做参数返回。promise 对象的构造函数只会调用一次,then 方法和 catch 方法都能多次调用,但一旦有了确定的结果,再次调用就会直接返回结果。

二、题目实战

以下会详解各个题目的执行顺序,建议大家先自己推敲结果,再来查看答案。

1、开头提到的题目

setTimeout(() => {console.log(1)},0)Promise.resolve().then(()=>{console.log(2)})Promise.resolve().then(()=>{console.log(3)})console.log(4)

在浏览器的控制台的输出结果如下:

思路讲解:

①这段script代码作为宏任务,进入主线程,先遇到setTimeout,异步任务,将其添加到宏任务队列,记为 事件setTimeout1

②接下来遇到Promise.resolve().then,异步任务,将其添加到微任务队列,记为事件then1

③接下来遇到Promise.resolve().then,异步任务,将其添加到微任务队列,记为事件then2

④接下来遇到console.log( ),是同步任务,立即执行该行代码,输出结果 4 。

⑤整体script代码作为第一个宏任务执行结束,看看有哪些微任务、宏任务?

⑥按照先入先出的方式,执行微任务队列中的任务:执行then1,打印输出 2 ,执行then2,打印输出 3 。OK,微任务队列执行完毕,然后开始执行宏任务队列中的任务:setTimeout1,打印输出 1。所以,最终的输出顺序为: 4 2 3 1

第一轮事件循环结束(当然,这个例子只有一轮)。

2、稍有难度

console.log('script start');setTimeout(function () {console.log('setTimeout');}, 0)new Promise(function (resolve) {console.log('promise1');resolve();}).then(function () {console.log('promise2');});console.log('script end');

在浏览器的控制台的输出结果如下:

思路讲解:

① 这段script代码作为宏任务,进入主线程,先遇到console.log( ),同步任务,立即执行该行代码,输出结果:script start

② 接下来遇到setTimeout,异步任务,将其添加到宏任务队列,记为事件:setTimeout1

③ 接下来遇到newPromise,在Promise 构造函数中,遇到console.log( )同步任务,立即执行该行代码,输出结果promise1 ,resolve()函数执行,改变 promise 实例对象的状态;

④ 接下来遇到Promise.then,异步任务,将其添加到微任务队列,记为事件then1

⑤ 接下来遇到console.log( ),是同步任务,立即执行该行代码,输出结果:script end ;

⑥ 整体script代码作为第一个宏任务执行结束,看看有哪些微任务、宏任务?

⑦ 按照先入先出的方式,执行微任务队列中的任务:执行then1,打印输出:promise2 ;OK,微任务队列执行完毕,然后开始执行宏任务队列中的任务:setTimeout1,打印输出setTimeout

所以,最终的输出顺序为:script start、promise1、script end、promise2、setTimeout

3、挑战升级

setTimeout(() => {console.log("0")}, 0)new Promise((resolve, reject) => {console.log("1")resolve()}).then(() => {console.log("2")new Promise((resolve, reject) => {console.log("3")resolve()}).then(() => {console.log("4")}).then(() => {console.log("5")})}).then(() => {console.log("6")})new Promise((resolve, reject) => {console.log("7")resolve()}).then(() => {console.log("8")})

① 这段script代码作为宏任务,进入主线程,先遇到setTimeout1,异步任务,将其添加到宏任务队列,记为事件setTimeout1

② 接下来遇到newPromise,在Promise 构造函数中,遇到console.log("1" )同步任务,立即执行该行代码,输出结果 1,resolve()函数执行,改变 promise 实例对象的状态;

③ 接下来遇到Promise.then,异步任务,将其添加到微任务队列,记为事件then1;接下来遇到Promise.then,异步任务,但是事件then1 还没有执行完毕,所以这个不会被放到微任务队列中;

④ 接下来遇到newPromise,在Promise 构造函数中,遇到console.log( "7")同步任务,立即执行该行代码,输出结果 7,resolve()函数执行,改变 promise 实例对象的状态;

⑤接下来遇到Promise.then,异步任务,将其添加到微任务队列,记为事件then2

⑥ 整体script代码作为第一个宏任务执行结束,看看有哪些微任务、宏任务?

⑦ 按照先入先出的方式,执行微任务队列中的任务:

执行then1,遇到console.log( "2" ),同步任务,立即执行该行代码,打印输出 2,接下来遇到newPromise,在Promise 构造函数中,遇到console.log( "3")同步任务,立即执行该行代码,输出结果 3,resolve()函数执行,改变 promise 实例对象的状态;

⑧ 接下来遇到Promise.then,异步任务,将其添加到微任务队列,记为事件then3

这里需要注意:这是难点,也是易错点!!!

then 内部的同步代码同步执行,异步代码添加到对应的异步队列即可,运行完毕就算执行完毕了,如果后面接了 then ,就要将后面 then 的调用放入微队列then 的链式调用依赖上个 then 的调用完成,被依赖的 then 未被调用,依赖的 then 此刻就为 undefined ,被依赖的 then 被调用了,就能将依赖的 then 的代码加入到微队列

⑨ 接下来,遇到代码第 30 行 的Promise.then,异步任务,将其添加到微任务队列,记为事件then4

现在的任务队列如下:

执行then2,遇到console.log( "8"),同步任务,立即执行该行代码,打印输出 8,

执行then3,遇到console.log( "4"),同步任务,立即执行该行代码,打印输出 4,

⑩ 接下来遇到Promise.then,异步任务,将其添加到微任务队列,记为事件then5

现在的任务队列如下:

执行then4,遇到console.log( "6"),同步任务,立即执行该行代码,打印输出 6;

执行then5,遇到console.log( "5"),同步任务,立即执行该行代码,打印输出 5;

OK,微任务队列执行完毕,然后开始执行宏任务队列中的任务:setTimeout1,打印输出 0

最终的顺序是:1 7 2 3 8 4 6 5 0

这个题目,最容易出错的地方是 6 和 5 的顺序,大家一定要仔细。

网上还有很多 promise 经典面试题,大家可以去运用我上面讲解的思路,看看自己是否掌握了。

参考资料:

JavaScript 运行机制详解:再谈Event Loop - 阮一峰的网络日志

这一次,彻底弄懂 JavaScript 执行机制 - 掘金

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。