2024年最全Vue3侦听器和异步任务调度, 其中有个神秘角色_vue3监听异步函数,今日头条前端面试题

框架相关

原生JS虽能实现绝大部分功能,但要么就是过于繁琐,要么就是存在缺陷,故绝大多数开发者都会首选框架开发方案。现阶段较热门是React、Vue两大框架,两者工作原理上存在共通点,也存在一些不同点,对于校招来说,不需要两个框架都学得特别熟,一般面试官会针对你简历中写的框架进行提问。

在框架方面,生命周期、钩子函数、虚拟DOM这些基本知识是必须要掌握的,在学习的过程可以结合框架的官方文档

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

Vue框架

知识要点:
1. vue-cli工程
2. vue核心知识点
3. vue-router
4. vuex
5. http请求
6. UI样式
7. 常用功能
8. MVVM设计模式

React框架

知识要点:
1. 基本知识
2. React 组件
3. React Redux
4. React 路由

在理解任务调度之前我们先来了解一些重要的概念:

  • JS是单线程的,所有的JS代码执行在JS引擎线程中;
  • 浏览器是多线程的,除了JS引擎线程还有UI渲染线程,网络IO线程等;
  • 由于JS执行在一个线程中,所以一次只能执行一个任务,如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务;
  • 同步任务 是指主进程中一个个按顺序执行的任务,如果某个任务执行时间久,那后面的任务就等待执行;
  • 异步任务 先不进入主线程,而先进入任务队列,只有主线程空闲了,且异步任务可以执行了,这些任务才会进入主线程,也是按照先后顺序执行;
  • JS操作DOM是同步任务,浏览器渲染DOM是异步任务(因为js引擎线程GUI渲染线程线程间是互斥的);
  • 异步任务分为 微任务宏任务, 微任务优先执行,所有的微任务执行完成后再执行宏任务
  • 微任务promise等,宏任务setTimeout等;
触发组件渲染的入口逻辑
const effect = new ReactiveEffect(
  componentUpdateFn,
  () => queueJob(instance.update),
  instance.scope
)

const update = (instance.update = effect.run.bind(effect) as SchedulerJob)

前面提到过 组件的副作用渲染函数 是基于ReactiveEffect
组件模板的数据变化后,会触发() => queueJob(instance.update)函数(本质就是ReactiveEffectrun方法),然后最终会调用componentUpdateFn函数执行组件的挂载或者更新,从而更新DOM。

  • queueJob 的逻辑
<!-- scheduler.ts -->
// 组件渲染任务数组
const queue: SchedulerJob[] = []

// 组件渲染任务队列执行函数
export function queueJob(job: SchedulerJob) {
  // 省略其他
  if (job.id == null) {
    queue.push(job)
  } else {
    queue.splice(findInsertionIndex(job.id), 0, job)
  }
  queueFlush()
}


Vue 维护了一个queue队列,用于保存需要执行的 副作用渲染函数ReactiveEffectrun方法。
queueJob 就是将 副作用渲染函数 添加到队列中合适的位置,然后执行 queueFlush方法。

queueJob

queueFlush方法我们先忽略,我们回到侦听器的相关逻辑中。

侦听器的侦测的数据变化后的的逻辑

我们继续看上面提到的一段代码。

let scheduler: EffectScheduler
if (flush === 'sync') {
  scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
  scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
  // default: 'pre'
  scheduler = () => {
    if (!instance || instance.isMounted) {
      queuePreFlushCb(job)
    } else {
      // with 'pre' option, the first call must happen before
      // the component is mounted so it is called synchronously.
      job()
    }
  }
}

我们可以通过flush参数来指定侦听器的执行顺序,有sync,postpre(默认) 这三种方式。同步很好理解我们不讨论,我们主要来研究queuePostRenderEffectqueuePreFlushCb这两个方法。

  • queuePostRenderEffect在大多数情况下等同于queuePostFlushCb函数:
<!-- render.ts -->
export const queuePostRenderEffect = __FEATURE_SUSPENSE__
  ? queueEffectWithSuspense
  : queuePostFlushCb

所以我们来看看queuePostFlushCbqueuePreFlushCb的逻辑:

<!-- scheduler.ts -->
// 更新DOM前的两个callback队列
const pendingPreFlushCbs: SchedulerJob[] = []
let activePreFlushCbs: SchedulerJob[] | null = null

// 更新DOM前的两个callback队列
const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null

export function queuePreFlushCb(cb: SchedulerJob) {
  queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex)
}

export function queuePostFlushCb(cb: SchedulerJobs) {
  queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex)
}


function queueCb(
  cb: SchedulerJobs,
  activeQueue: SchedulerJob[] | null,
  pendingQueue: SchedulerJob[],
  index: number
) {
  // 省略其他
  pendingQueue.push(cb)
  queueFlush()
}

  1. Vue 维护了DOM更新前需要执行的回调函数执行队列pendingPreFlushCbsactivePreFlushCbs;
  2. Vue 维护了DOM更新后需要执行的回调函数执行队列pendingPostFlushCbsactivePostFlushCbs;
  3. queuePostFlushCbqueuePreFlushCb分别是吧对应的回调方法加到pendingPostFlushCbspendingPreFlushCbs队列中;
  4. 然后执行queueFlush函数。

和

queueFlush执行异步调用

不管是DOM更新还是监听器的监听到数据后的回调都是进入了queueFlush,我们来看看它的实现逻辑。

const resolvedPromise: Promise<any> = Promise.resolve()

function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}

queueFlush 使用了微任务的Promise执行异步执行flushJobs。且用isFlushPending控制flushJobs的执行时机。

flushJobs清空所有任务
function flushJobs(seen?: CountMap) {

  isFlushPending = false
  isFlushing = true
    
  // 1. 依次执行所有的所有的回调函数
  flushPreFlushCbs(seen)

  // 2. 对副作用渲染函数排序,然后依次执行所有的副作用渲染函数
  queue.sort((a, b) => getId(a) - getId(b))

  try {
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex]
      callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
    }
  } finally {
    // 3. 依次执行所有的所有的回调函数
    flushPostFlushCbs(seen)

    isFlushing = false
    currentFlushPromise = null
    
    // 4. 如果有新的回调函数添加进来,继续一个 1,2,3 的执行流程
    if (
      queue.length ||
      pendingPreFlushCbs.length ||
      pendingPostFlushCbs.length
    ) {
      flushJobs(seen)
    }
  }
}

flushJobs 的作用是清空所有任务:

  1. flushPreFlushCbs依次清空所有DOM更新前的回调函数:1). 先将pendingPreFlushCbs中的所有数据拷贝到activePreFlushCbs中,pendingPreFlushCbs置空等待新的回调函数加入;2). 依次执行activePreFlushCbs中的回调函数;
  2. callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)依次清空所有更新DOM的副作用渲染函数;
  3. flushPostFlushCbs依次清空所有DOM更新后的回调函数:1). 先将pendingPostFlushCbs中的所有数据拷贝到activePostFlushCbs中,pendingPostFlushCbs置空等待新的回调函数加入;2).依次执行activePostFlushCbs中的回调函数;

flushJobs

神秘的nextTick

nextTick异常神秘,遇到DOM的操作问题可能就想到它了。其实非常简单

export function nextTick<T = void>(
  this: T,
  fn?: (this: T) => void
): Promise<void> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}

异常简单,其实就是对Promise调用then方法。

Promise.resolve().then(() => {
  // 清空任务(包括更新DOM)
}).then(() => {
  // 是不是可以获取到更新后的DOM了??
})

不那么神秘的forceUpdate

最后

由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

新后的DOM了??
})


##### 不那么神秘的`forceUpdate`





### 最后

由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

[外链图片转存中...(img-bU9DFG0W-1715657375517)]

[外链图片转存中...(img-4lNpFXHM-1715657375518)]



  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值