前端面试复习3---异步与 promise

异步与 Promise

计算机原理:进程与线程

  • 进程:CPU 资源分配的最小单位
  • 线程:CPU 调度的最小单位
  • 解读:每一辆车代表我们日常用的每个程序,洗车场代表电脑的内存空间。每个停车位就代表了每个进程,小轿车占用小车位,大巴车占用大车位,等同于每个应用程序也会根基自身的需要的运行内存,分配不一样的进程空间。每个洗车工人代表一个线程,一个洗车位可以用多个洗车工人在干活,例如:洗车外面的,清理内饰的同时在进行;等同于一个应用程序的进程空间可以多个线程在同时干活,例如:网易云音乐,在播发音乐的同时,你还可以发表评论。同一个洗车位内的工人可以共用这个洗车位内的所以工具资源,同一个进程内的线程共用这个进程内的资源空间。一个车位至少一位洗车工,一个进程至少一个线程。

[外链图片转存中…(img-Bgpf0KIE-1692330469647)]

面试题:浏览器新开一个窗口,是进程还是线程?

  1. 答案:进程,从区别概念回答
  2. 发散 1:窗口(进程间)通信怎么通讯?storage、session、cookie => storage、cookie 的区别? => 怎么样操作 cookie,前端操作 cookie 有什么风险?=> cookie 的应用场景?
  3. 发散 2:浏览器原理?

浏览器原理-网页

  1. GUI 渲染线程:
    1. 解析 HTML CSS 构建 DOM 树->布局->绘制
    2. 与 JS 引擎线程互斥,当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列空闲时,主线程才回去执行 GUI
  2. JS 引擎线程:
    1. 处理 JS,解析执行脚本
    2. 分配、处理、执行了待执行脚本同时,处理待执行事件,维护事件队列
    3. 阻塞 GUI 渲染。js 为何会阻塞 GUI 渲染:因为网页是单线程的,js 执行的时候,渲染会被挂起。
  3. 定时器触发线程:
    1. 异步定时器的处理和执行 - setTimeout / setInterval
    2. 接收 JS 引擎分配的定时器任务,并执行
    3. 处理完成后交于事件触发线程
  4. 异步 HTTP 请求线程:
    1. 异步执行请求类操作
    2. 接收 JS 引起线程异步请求操作
    3. 监听回调,交给事件触发线程做处理
  5. 事件触发线程:
    1. 接收所有来源的事件
    2. 将回调的事件依次加入到任务队列的队尾,交给 JS 引擎执行

event loop

事件循环(Event Loop)是 JavaScript 的执行机制之一,用于管理和调度异步任务的执行顺序。

事件循环的核心思想是基于一个事件队列(Event Queue)和一个调用栈(Call Stack)来实现。当 JavaScript 引擎执行代码时,同步任务会被直接放入调用栈中执行,而异步任务则会被放入事件队列中等待执行。

事件队列中的任务分为宏任务(macrotask)和微任务(microtask)两种类型。

当调用栈为空时,事件循环开始执行。首先,事件循环会将队列中的微任务一次性地全部取出并执行完毕,然后检查浏览器是否需要进行页面渲染。接下来,事件循环会从宏任务队列中取出一个任务,并放入调用栈中执行。当宏任务执行完毕后,再次执行微任务,然后重新渲染页面(如果需要),继续循环这个过程。

通过事件循环的机制,JavaScript 可以实现非阻塞的异步编程,使得程序能够同时处理多个任务,并提高了程序的响应性和性能。同时,了解事件循环的原理也有助于我们更好地理解 JavaScript 的执行过程,并编写出更高效的异步代码。

微任务与宏任务

宏任务(macro-task)和微任务(micro-task)是指在 JavaScript 执行过程中,不同类型的任务分类。

宏任务通常包括以下几种类型:

  • 渲染事件(如页面加载、重新渲染)
  • 用户交互事件(如点击、滚动)
  • 定时器事件(如 setTimeout、setInterval)
  • 网络请求完成、文件读写完成等 I/O 操作的回调
  • 执行整体的 JavaScript 代码(同步代码)

而微任务则包括以下几种类型:

  • Promise 的 resolve 回调函数
  • MutationObserver 的回调函数

在事件循环中,当一个宏任务执行完毕后,事件循环会检查微任务队列,并将其中的所有微任务连续执行,直到微任务队列为空。这意味着,微任务会在当前宏任务执行结束后立即执行,而不会等待下一个宏任务。

具体来说,当浏览器遇到一个宏任务时,它会进入宏任务队列,并等待执行。而当浏览器遇到一个微任务时,它会立即执行,并将所有产生的新的微任务加入微任务队列中。

通过合理利用微任务,我们可以在宏任务的执行间隙执行一些高优先级的任务,从而提高页面的响应性和交互性。常见的应用场景是使用 Promise 进行异步操作,并在操作完成后立即执行相关的处理逻辑。

总结来说,宏任务表示较为宏观的、需要较长时间才能执行完毕的任务,而微任务则表示较小粒度、需要快速执行的任务。掌握宏任务和微任务的执行顺序和使用场景,可以帮助我们更好地处理异步代码和优化程序性能。

event loop 基本过程

  1. 初始化阶段:事件循环从全局上下文开始,并执行同步代码。任何遇到的异步任务将被放入相应的任务队列中。
  2. 宏任务阶段:执行异步任务队列中的微任务。
  3. 渲染阶段:如果 DOM 被改变,渲染 DOM。
  4. 宏任务阶段:在微任务阶段结束后,事件循环会从宏任务队列中选择一个任务。这个任务将被推送到调用栈中执行,直到任务执行完毕。
  5. 重复循环:重复进行微任务阶段、宏任务阶段和渲染阶段,直到没有更多的任务。

这就是事件循环的基本过程,它通过不断地处理微任务和宏任务,以及渲染阶段的操作,使得 JavaScript 能够处理异步任务并保持页面的响应性。注意,这只是一个简化的描述,实际的事件循环机制可能会有更多的细节和特殊情况。

存放
存放
拥有
拥有
拥有
拥有
拥有
拥有
执行
执行
执行
所有微任务执行完后执行
event loop
event loop
同步代码
微任务队列
宏任务队列
微任务 1
微任务 2
微任务..
宏任务 1
宏任务2
宏任务..
微任务队列被全部执行
DOM如果被改变就重新渲染

上面整个流程图就是 event loop 的流程,在执行宏任务 1时会继续产生一个这样的 event loop 流程,直至宏任务 1被执行完,然后执行宏任务 2再产生这样的 event loop 流程,直到所有宏任务执行结束

执行栈/调用栈示例 demo

function fn2() {
   
  throw new Error('抛出一个错误')
}
function fn1() {
   
  fn2()
}
function run() {
   
  fn1()
  console.log('fn2抛出错误后,这里已经不执行了')
}
run()

// 执行栈后进先出
// 执行栈:run->fn1->fn2
// 执行完成顺序:fn2->fn1->run
// 先执行完成fn2再往下执行fn1,执行完成fn1再往下执行run,因为fn2报错了,所以fn1和run都无法往下执行完成

面试题 1:死循环

js 堆栈执行顺序与堆栈溢出/ 爆栈 / 性能卡顿 / => js 性能优化

死循环例子:

function fn() {
   
  fn()
}
fn()

正确情况下不会写出这样的死循环,如果在 vue 的 computed 中, 对某个和 computed 有关系的的变量进行复制就会造成这样的死循环。
例如:

{
   
  data() {
   
    return {
   
      bbb: 222,
      ccc: 333,
    }
  },
  computed: {
   
    aaa() {
   
      this.bbb = 111
      return this.bbb + this.ccc
    }
  }
}

面试题 2:执行顺序

setTimeout(() => {
   
  console.log('setTimeout') // 5. 宏任务2
})

new Promise((resolve, reject) => {
   
  console.log('new Promise') // 1. 属于同步进入主线程 宏任务1
  resolve()
})
  .then(() => {
   
    console.log('Promise then') // 3. 微任务1
  })
  .then(() => {
   
    console.log('Promise then then') // 4. 微任务2
  })

console.log('hi') // 2. 同步代码 宏任务1

解题思路:任务维度

promise 有哪些状态?

  1. pending/待处理的, fulfilled/满足的, rejected/被驳回的
  2. executor: new Promise() 的时候立即执行,接收两个参数 resolve, reject

promise 的默认状态是什么?状态是如何流转的?

  1. 默认:pending
  2. 状态流转:pending => fulfilled | rejected

手写 Promise

Promise 规范

  1. Promise 实例有三个状态: pending fulfilled rejected,默认状态为 pending
  2. 状态只能从 pending 向 fulfilled | rejected 流转
  3. Promise(executor) 接收一个参数 executor
  4. executor(resolve, reject) 是同步执行的,同时接收两个两个参数 resolve reject
  5. resolve(value) 成功的回调,接收一个任何类型的参数作为成功的返回值,执行 resolve(value) 会把实例的状态变为 fulfilled
  6. reject(reason) 失败的回调,接收一个参数通常是字符串作为失败的原因,执行 reject(reason) 会把实例的状态变为 rejected
  7. 实例内部需要有一个 result 属性,用来存储 resolve(value) reject(reason) 传进来的的 value 或 reason
  8. Promise 的 then(onResolved, onRejected) 方法接收两个参数 onResolved, onRejected,目的是为了可以拿到内部的 result
  9. 当状态为 fulfilled 时执行 onResolved,result 为 resolve(value) 传进来的 value
  10. 当状态为 rejected 时执行 onRejected,result 为 reject(reason) 传进来的 reason
  11. then 返回的是一个 Promise 实例,这个实例的状态是由 onResolved onRejected 的返回结果决定的,onResolved onRejected 的返回结果不是一个 Promise 实例时,then 方法都会返回一个状态为 fulfilled 的 Promise 实例, onResolved onRejected 的返回结果是一个 Promise 实例时,这个 实例的状态就是 then 方法返回实例的状态
  12. Promise 的 catch(onRejected) 方法接收一个参数 onRejected
  13. catch 返回的同样是一个 Promise 实例,这个实例的状态是由 onRejected 的返回结果决定的

1. 基本框架

class Promise {
   
  // 用静态属性定义三个状态,方便后面使用
  static PENDING = 'pending'
  static FULFILLED = 'fulfilled'
  static REJECTED = 'rejected'

  constructor(executor) {
   
    this.result = undefined // 存储 resolve(value) reject(reason) 传进来的的 value 或 reason
    this.state = Promise.PENDING // 默认状态为 pending

    const resolve = (value) => {
   
      if (this.state === Promise.PENDING) {
   
        this.state = Promise.FULFILLED
        this.result = value
      }
    }

    const reject = (reason) => {
   
      if (this.state === Promise.PENDING) {
   
        this.state = Promise.REJECTED
        this.result = reason
      }
    }

    // 这里用 try catch 是为了解决像 p3 的情况,在 executor 实参的函数体中有报错
    try {
   
      executor(resolve, reject)
    } catch (error) {
   
      reject(error)
    }
  }
}

const p1 = new Promise((resolve, reject) => {
   
  resolve('test')
})
console.log(p1)

const p2 = new Promise((resolve, reject) => {
   
  reject('test')
})
console.log(p2)

const p3 = new 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值