El principio de la actualización asíncrona de la lectura del código fuente de vue

Como todos sabemos, Vue se basa en vistas basadas en datos, y los cambios de datos activarán datala setterfunción de la notificación watcherpara actualizar. El proceso de actualización también necesita pasar por muchas operaciones, como compilación de plantillas, diff dom, renderizado, etc. Si se realizan actualizaciones frecuentes, el rendimiento será deficiente. Entonces, ¿cómo se hace en Vue? Antes de entender, primero comprendamos el mecanismo de operación de js.

js mecanismo de ejecución

Como todos sabemos, JS es un lenguaje de subproceso único basado en bucle de eventos. Los pasos generales del bucle de eventos son los siguientes.

✅(1) Todas las tareas de sincronización se ejecutan en el subproceso principal, formando una pila de contexto de ejecución.

✅(2) Además del hilo principal, también hay una "cola de tareas". Tan pronto como la tarea asincrónica tiene un resultado en ejecución, se coloca un evento (devolución de llamada) en la "cola de tareas".

✅(3) Una vez que se ejecutan todas las tareas de sincronización en la "pila de ejecución", el sistema leerá la "cola de tareas" para ver qué eventos hay en ella. Esas tareas asincrónicas correspondientes finalizan el estado de espera, ingresan a la pila de ejecución y comienzan la ejecución.

✅(4) El hilo principal sigue repitiendo el tercer paso anterior.

Las tareas en la cola de tareas se dividen en dos categorías, a saber, macro tareas y micro tareas.

Tarea macro: en el proceso de un nuevo bucle de eventos, cuando se encuentra una tarea macro, la tarea macro se agregará a la cola de tareas, pero no se ejecutará hasta el siguiente bucle de eventos. Las tareas de macro comunes sonsetTimeout、setInterval、setImmediate、requestAnimationFrame

Microtarea: después de ejecutar la tarea de sincronización del bucle de eventos actual, las tareas en la cola de tareas se ejecutarán en secuencia. Durante la ejecución, si se encuentra una microtarea, la microtarea se agrega a la cola de microtareas del bucle de eventos actual para su ejecución hasta que se vacía. En pocas palabras, siempre que haya microtareas en el bucle de eventos actual, se ejecutarán hasta que se vacíen, en lugar de ejecutarse en el siguiente bucle de eventos. Las microtareas comunes sonMutationObserver、Promise.then、process.nextTick。

Mecanismo de actualización de datos de Vue

只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环 tick 中,Vue 刷新队列并执行实际 (已去重的) 工作。

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环 tick 中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用 “数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。例如:

// html
<div id="example">{{message}}</div>

// javascript
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
// dom 没有立即更新
vm.$el.textContent === 'new message' // false
// 在nextTick中 获取到更新后的dom
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})
复制代码

为什么nextTick可以访问更新后的dom?

// 当一个 Data 更新时,会执行以下代码
// 1. 触发 setter
// 2. setter 中调用 dep.notify
// 3. Dep notify 会遍历所有相关的 Watcher 执行 update 方法
class Watcher {
  // 4. 执行更新操作
  update() {
      queueWatcher(this)
  }
}

const queue = [];

function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) { // 避免添加同一 watcher
    has[id] = true
    if (!flushing) { // 处理 Watcher 渲染时,可能产生的新 Watcher  eg:v-if 触发新watcher
      // 5. 将当前 Watcher 添加到异步队列
      queue.push(watcher)
    } else {
      // 新watcher 添加到指定位置
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // 保证此次watcher都在同一tick 并且不会重复执行
    if (!waiting) {
      waiting = true
      // 6. 执行异步队列,并传入更新视图回调
      nextTick(flushSchedulerQueue)
    }
  }
}

// 更新视图的具体方法
function flushSchedulerQueue() {
  let watcher, id;
  // 排序,先渲染父节点,再渲染子节点
  // 这样可以避免不必要的子节点渲染,如:父节点中 v-if 为 false 的子节点,就不用渲染了
  queue.sort((a, b) => a.id - b.id);
  // 遍历所有 Watcher 进行批量更新。
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    // 更新 DOM
    watcher.run();
  }
}
复制代码

nextTick

可以看到watcher的update并没有直接更新视图,而是将更新视图的方法传给了nextTick方法,接下来看下nextTick源码

// src\core\util\next-tick.js

const callbacks = [];
let pending = false;
let timerFunc;

export function nextTick (cb?: Function, ctx?: Object) {
  // 1.将更新视图方法添加 callbacks中
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } 
  })
  // 2.pending用来保障同一时间 只会执行一个timerFunc()
  if (!pending) {
    pending = true
    timerFunc()
  }
}
复制代码
let timerFunc;

// 判断是否支持 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
   
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} 
// 判断是否支持 MutationObserver
else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} 
// 判断是否支持 setImmediate
else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} 
// 最不济 setTimeout
else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
复制代码

可以看出timerFunc 其实是根据浏览器兼容创建的异步方法,优雅降级,最终调用flushCallbacks执行flushSchedulerQueue方法更新dom。

vm.nextTick可以访问到更新后的dom,是因为异步队列也遵循先来后到,修改 Data 触发的更新异步队列会先执行,执行完成 dom 更新,此时调用 nextTick 的回调可以访问到更新后的dom。

this.$nextTick

this.$nextTick原理就是nextTick方法

// src\core\instance\render.js
Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
}
复制代码

总结

vue异步更新原理就是触发data的setter最终调用watcher的update方法,update调用queueWatcher将本身推入一个全局的queue队列,并将更新视图的方法传给nextTick,最终通过nextTick方法异步更新视图。

Supongo que te gusta

Origin juejin.im/post/7086354741935996958
Recomendado
Clasificación