Análisis del código fuente de Vue2 nextTick y principio de implementación detallado

Referencias

Código fuente de Vue2

github

Descripción del problema

La razón por la que escribo este blog es porque mi amigo me preguntó sobre el principio de implementación de nextTick en vue. Probablemente sé cuál es la situación, pero todavía está lejos de ser clara para los demás, así que leí el código fuente de la parte nextTick en vue2 .

Que aquí Xiaowei debe estudiar mucho hhhhh

nextTick código fuente

Primero, adjunte el código fuente de la parte nextTick en vue (v2.7.7) (elimine el comentario)

/* globals MutationObserver */

import {
    
     noop } from 'shared/util'
import {
    
     handleError } from './error'
import {
    
     isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

const callbacks: Array<Function> = []
let pending = false

function flushCallbacks() {
    
    
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    
    
    copies[i]()
  }
}

let timerFunc


if (typeof Promise !== 'undefined' && isNative(Promise)) {
    
    
  const p = Promise.resolve()
  timerFunc = () => {
    
    
    p.then(flushCallbacks)

    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (
  !isIE &&
  typeof MutationObserver !== 'undefined' &&
  (isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
    
    
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  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
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    
    

  timerFunc = () => {
    
    
    setImmediate(flushCallbacks)
  }
} else {
    
    
  // Fallback to setTimeout.
  timerFunc = () => {
    
    
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick(): Promise<void>
export function nextTick<T>(this: T, cb: (this: T, ...args: any[]) => any): void
export function nextTick<T>(cb: (this: T, ...args: any[]) => any, ctx: T): void
/**
 * @internal
 */
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
    
    
  let _resolve
  callbacks.push(() => {
    
    
    if (cb) {
    
    
      try {
    
    
        cb.call(ctx)
      } catch (e: any) {
    
    
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
    
    
      _resolve(ctx)
    }
  })
  if (!pending) {
    
    
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    
    
    return new Promise(resolve => {
    
    
      _resolve = resolve
    })
  }
}

Analizar código fuente

export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
    
    
  let _resolve
  callbacks.push(() => {
    
    
    if (cb) {
    
    
      try {
    
    
        cb.call(ctx)
      } catch (e: any) {
    
    
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
    
    
      _resolve(ctx)
    }
  })
  if (!pending) {
    
    
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    
    
    return new Promise(resolve => {
    
    
      _resolve = resolve
    })
  }
}

El código fuente de nextTick es realmente muy simple y finalmente expuso un método nextTick al mundo exterior. En este método, cb es la función de devolución de llamada que se pasa y ctx es el objeto al que apunta this. Cuando se pasa una función de devolución de llamada cb, inserte la función de devolución de llamada en la matriz que almacena la función de devolución de llamada, y todas las funciones de devolución de llamada se insertarán en esta matriz, y se llamarán y ejecutarán de manera uniforme.

const callbacks: Array<Function> = []

Después de ingresar a la matriz, primero juzgue el estado pendiente (bloqueo asíncrono), el estado inicial es falso, luego establezca el estado pendiente en verdadero después de ingresar y llame a la función timerFunc. Cuando nextTick no pasa una función de devolución de llamada, devuelva una llamada basada en promesa.

Se ha hecho mucho en nextTick, echemos un vistazo a lo que hace la función timerFunc

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
    
    
  const p = Promise.resolve()
  timerFunc = () => {
    
    
    p.then(flushCallbacks)

    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (
  !isIE &&
  typeof MutationObserver !== 'undefined' &&
  (isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
    
    
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  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
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    
    

  timerFunc = () => {
    
    
    setImmediate(flushCallbacks)
  }
} else {
    
    
  // Fallback to setTimeout.
  timerFunc = () => {
    
    
    setTimeout(flushCallbacks, 0)
  }
}

Aunque esta función parece hacer muchas cosas, en realidad juzga el entorno actual. Cuando existen promesas en el entorno actual, las promesas se utilizan como una solución de empaquetado asíncrono. Cuando no haya ninguna promesa, use MutationObserver como una solución de empaquetado asincrónico. Cuando no haya MutationObserver, use setImmediate. Cuando setImmediate no exista, use el setTimeout más seguro.

Envuélvalo de forma asíncrona y llame a la función flushCallbacks

function flushCallbacks() {
    
    
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    
    
    copies[i]()
  }
}

flushCallbacks es para restablecer el bloqueo asíncrono y luego llamar a la función de devolución de llamada insertada en las devoluciones de llamada en un bucle.

En resumen, nextTick en realidad empuja la función de devolución de llamada entrante a una matriz y luego coloca la llamada de la función de devolución de llamada en esta matriz en una tarea asíncrona.

Después de analizar el código fuente, analicemos el principio de implementación de nextTick

Preparación

Para averiguar cómo funciona nextTick, primero debemos averiguar algunas cosas.
1. ¿Para qué sirve nextTick
? Sabemos que en Vue, no se recomienda demasiado operar el dom, pero ¿y si debemos operar el dom? Entonces podría haber un problema. Es decir, hemos operado el dom antes de que se haya actualizado el dom, en este caso, es posible que no obtengamos el resultado que queremos. En este momento, colocamos nextTick en el exterior del dom, lo que a menudo puede resolver el problema.

this.$nextTick(_ => {
    
    
	document.getElementById('box').innerHtml
});

2. ¿Qué es el bucle de eventos js (bucle de eventos)
Todos sabemos que js es de un solo subproceso En cuanto a por qué js es de un solo subproceso, es porque el propósito de inventar este lenguaje es servir como un lenguaje de secuencias de comandos para los navegadores. El objetivo principal de js es interactuar con los usuarios y operar dom, lo que determina que js solo puede ser de un solo subproceso, de lo contrario traerá problemas de sincronización muy complicados.
Luego, dado que js es de subproceso único, la siguiente tarea solo se ejecutará después de que se complete la tarea anterior de js. Pero si una de las tareas lleva mucho tiempo (por ejemplo, hay un temporizador de 10 s), entonces la última tarea tendrá que esperar a que se ejecute la tarea anterior antes de ejecutarla, lo que obviamente no es científico.
En este caso, js divide las tareas en tareas sincrónicas y tareas asincrónicas. Cuando js se ejecuta de arriba a abajo, al encontrar tareas asincrónicas, colocará las tareas asincrónicas en la cola de ejecución asincrónica en secuencia y luego esperará después del hilo principal. (tarea síncrona) se ejecuta, volverá a ejecutar las tareas en la cola de ejecución asíncrona. En principio, la orden de ejecución se ejecuta en el orden de las primeras entradas, las primeras salidas. El subproceso principal lee eventos de la cola de tareas.Este proceso es continuamente cíclico, por lo que este mecanismo operativo se denomina bucle de eventos.


3. Después de comprender el ciclo de eventos anterior para macrotareas asíncronas y microtareas asíncronas, puede ver que en el orden de ejecución, escribí que, en principio, se ejecutan en el orden de primero en entrar, primero en salir, así es la ejecución orden en la cola de tareas asincrónicas todavía De hecho, las hay, porque las tareas asíncronas se dividen en microtareas asíncronas y macrotareas asíncronas. Las microtareas asíncronas deben ejecutarse antes que las macrotareas asíncronas. Por supuesto, cuando se ejecutan microtareas asíncronas, también deben entrar en ejecución asíncrona según microtareas asíncronas. El orden en se ejecuta la cola. Lo mismo es cierto para las macro tareas asíncronas, entonces, ¿qué son las micro tareas asíncronas y las macro tareas asíncronas?

Los más comunes son:
Microtarea: promesa.entonces MutationObserver promesa.nextTick
Macrotarea: setInterval setTimeout postMessage setImmediate

análisis del problema

Después de asegurarnos de que hemos entendido los tres puntos anteriores, analicemos formalmente el principio de implementación de nextTick.

Para comprender el principio de implementación de nextTick, no es más que entender por qué nextTick lo ejecutará después de que se actualice el dom. Pensemos en ello, en realidad escribimos un código de este tipo en el código.

this.$nextTick(_ => {
    
    });

Pero, ¿por qué esta cosa es tan mágica, debe ejecutarse después de actualizar el dom? ¿Cómo sabe que la actualización de dom está completa? ¿Estás estupefacto y curioso? No se preocupe, para entender esto, tenemos que entender el mecanismo de representación de vue.

En primer lugar, sabemos que al crear un objeto vue, se realizará una conversión de getter/setter en los datos entrantes (el principio de la capacidad de respuesta de vue), lo marcaremos como datos y luego, cuando se represente un componente, el componente correspondiente El observador (observador) registrará los datos en los datos utilizados como dependencia dependiente, y luego, cuando los datos en los datos cambien, verá qué observadores registran la parte modificada de las dependencias y luego notificará al observador para informarle si los datos bajo su mando ha cambiado, actualice la vista para mí rápidamente. Pero si un observador se activa varias veces, ¿se activará para actualizar la vista cada vez que se active? La respuesta es no, porque consume mucho rendimiento. Si un observador se activa varias veces, solo actualizará el resultado del último activador.

Y el observador llama al método de actualización para actualizar la vista Abrimos el código fuente de Vue para encontrar el código de la parte de actualización del observador.

/**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update() {
    
    
    /* istanbul ignore else */
    if (this.lazy) {
    
    
      this.dirty = true
    } else if (this.sync) {
    
    
      this.run()
		/*同步则执行run直接渲染视图*/
      // 基本不会用到sync
    } else {
    
    
      queueWatcher(this)
    }
  }

Desde el código fuente, podemos ver el método de actualización del observador. Porque cuando los datos de vue cambian, la página no se actualizará de inmediato, pero debe realizar la deduplicación y otras cosas (los múltiples observadores de activación mencionados anteriormente solo se actualizarán la última vez, por lo que debe deduplicarse), por lo que esta actualización utiliza el método queueWatcher.

Luego encontramos el código fuente de queueWatcher.

/**
 * Push a watcher into the watcher queue.
 * Jobs with duplicate IDs will be skipped unless it's
 * pushed when the queue is being flushed.
 */
export function queueWatcher(watcher: Watcher) {
    
    
  const id = watcher.id
  if (has[id] != null) {
    
    
    return
  }

  if (watcher === Dep.target && watcher.noRecurse) {
    
    
    return
  }

  has[id] = true
  if (!flushing) {
    
    
    queue.push(watcher)
  } else {
    
    
    // if already flushing, splice the watcher based on its id
    // if already past its id, it will be run next immediately.
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
    
    
      i--
    }
    queue.splice(i + 1, 0, watcher)
  }
  // queue the flush
  if (!waiting) {
    
    
    waiting = true

    if (__DEV__ && !config.async) {
    
    
      flushSchedulerQueue()
      return
    }
    nextTick(flushSchedulerQueue)
  }
}

Independientemente del código anterior, podemos ver el queueWatcher. Finalmente, se llama al método nextTick para actualizar la vista.

En este punto, probablemente podamos conocer el principio de funcionamiento de nextTick.

  1. En primer lugar, los datos se actualizan. Después de escuchar el cambio de datos, el observador activará inmediatamente el método de actualización (esto se ejecutará antes del siguiente Tick que escribimos en el código), luego activará el queueWatcher y finalmente llamará al método nextTick Después de que nextTick ajusta de forma asíncrona la llamada a la función, se coloca en la cola de ejecución asíncrona.

  2. Una vez completado el paso anterior, la llamada de la función de devolución de llamada nextTick que escribimos en el código se ajustará de forma asíncrona y se colocará en la cola de ejecución asíncrona, combinada con el orden de ejecución de las tareas asíncronas del bucle de eventos mencionadas anteriormente. Porque sus métodos de empaquetado asíncrono son los mismos, y el método de actualización de vista entra primero en la cola. Por lo tanto, la ejecución de la devolución de llamada nextTick escrita en nuestro código siempre se ejecutará después de que se actualice el dom.

En resumen, es el principio de implementación de nextTick.

Supongo que te gusta

Origin blog.csdn.net/yangxbai/article/details/125931148
Recomendado
Clasificación