第二十三期:一道面试题,三个知识点

·  阅读 85
第二十三期:一道面试题,三个知识点

这里记录工作中遇到的技术点,以及自己对生活的一些思考,周三或周五发布。

封面图

受疫情的影响,上周末北京西站几乎没什么人

一道面试题

代码如下:

let a;

const b = new Promise((resolve,reject)=>{
  console.log('promise')
  resolve()
}).then(()=>{
  console.log('promise2')
}).then(()=>{
  console.log('promise3')
})..then(()=>{
  console.log('promise4')
})

a = new Promise((resolve,reject)=>{
  console.log(a)
  await b;
  console.log('after1')
  await a;
  resolve(true)
  console.log('after2')
})

console.log('end')

复制代码

问: 最终打印出什么结果?

我原本想的答案是下面的结果:

//1. end
//2. promise
//3. type error
复制代码

理由是,这能看出来道题其实考察的是下面的知识点:

  • 变量声明
  • promise 消息队列或者叫(微任务和宏任务)
  • async 和 await 的用法

let 声明的变量存在TMD暂时性死区的问题,所以已声明但未被赋值的变量如果直接使用,会报未定义的错。

我把这个代码执行了一遍,确实报错了,但是报的是这个:

Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
复制代码

原来是我少写了个async,真实的问题应该是这样:

let a;

const b = new Promise((resolve,reject)=>{
  console.log('promise')
  resolve()
}).then(()=>{
  console.log('promise2')
}).then(()=>{
  console.log('promise3')
})..then(()=>{
  console.log('promise4')
})

a = new Promise(async (resolve,reject)=>{
  console.log(a)
  await b;
  console.log('after1')
  await a;
  resolve(true)
  console.log('after2')
})

console.log('end')
复制代码

这次的结果是:

// promise
// undefined
// end
// promise2
// promise3
// promise4
// after1
复制代码

思考一下什么为会出现这种结果? 其实还是上面说的那三个知识点。但是要是真正理解上面的三个知识点,又需要理解下面的知识点:

JS的并发模型和事件循环

JavaScript 有个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务,这个模型与其他语言的模型截然不同。

上图中有三个部分:栈内存堆内存,和消息队列。这是js的一个基本模型。

栈内存有两个作用:1,存放基本类型数据。2.提供代码运行环境。

队内存的作用:主要是存储引用类型的数据。

消息队列:一个JavaScript运行时包含了一个带处理消息的消息队列。每个消息都关联一个用于处理这个消息的回调函数。

在事件循环期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移除队列,并作为输入参数来调用与之关联的函数。

函数的处理会一直进行到执行栈再次为空为止,然后事件循环队列会处理队列中的下一个消息。

这里有个问题,消息是什么?个人理解消息就是事件的回调函数。

在浏览器里,每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。如果没有事件监听器,这个事件将会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。

函数 setTimeout 接受两个参数:待加入队列的消息和一个时间值(可选,默认为 0)。这个时间值代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息并且栈为空,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其它消息,setTimeout 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。比如:

const s = new Date().getSeconds();

setTimeout(function() {
  // 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行
  console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
}, 500);

while(true) {
  if(new Date().getSeconds() - s >= 2) {
    console.log("Good, looped for 2 seconds");
    break;
  }
}
// Good, looped for 2 seconds
//  Ran after 2 seconds
复制代码

可以看到 执行代码后先输出了 Good, looped for 2 seconds,然后输出Ran after 2 seconds

因为代码执行到setTimeout发现它是一个消息,将它加入到了消息队列中,等到栈清空以后,才又接着处理这个消息。

宏任务和微任务的概念

Here “platform code” means engine, environment, and promise implementation code.

下面这个表格可以很清楚的描述宏任务和微任务的概念:

宏任务微任务
谁发起宿主环境(Node,浏览器)平台引擎
具体事件1. script (可以理解为外层同步代码)/n2. setTimeout/setInterval、3. UI rendering/UI事件,4. postMessage,MessageChannel,5. setImmediate,I/O(Node.js)promise,MutationObserver,process.nextTick
运行顺序在后在前
触发新一轮tick不会

async 和 await

async 关键字加到函数申明中,可以告诉我们返回的是 promise,而不是直接返回值。 以往我们写promise的时候,需要在then的返回值中才能捕获我们想要的结果。

但是await可以直接捕获我们想要的结果。

简单来说:await 关键字使JavaScript运行时暂停于此行,允许其他代码在此期间执行,直到异步函数调用返回其结果。一旦完成,我们的代码将继续从下一行开始执行。

比如:

async getDataList=()=>{
  const data = await getOtherList() 
  return data.blob()
}
复制代码

解析器会在此行上暂停,直到当服务器返回的响应变得可用时。此时 getOtherList() 返回的 promise 将会完成(fullfilled),返回的 response 会被赋值给 response 变量。一旦服务器返回的响应可用,解析器就会移动到下一行,从而创建一个Blob。Blob这行也调用基于异步promise的方法,因此我们也在此处使用await。当操作结果返回时,我们将它从getDataList()函数中返回。

那么又出现一个问题

Blob是什么?

最后

微信搜索《javascript高级程序设计》欢迎关注

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