Aprendizaje de código fuente de Vue: principio de respuesta a datos

prefacio

El núcleo de Vue.Js incluye un "sistema receptivo". "Sensible" significa que cuando los datos cambian, Vue notificará al código que usa los datos. Por ejemplo, los datos se utilizan en la representación de vistas y la vista se actualiza automáticamente cuando cambian los datos.

1. Búsqueda de entrada

La capacidad de respuesta de datos de vue2.X se realiza utilizando para interceptar la adquisición y configuración de propiedades Object.defineProperty()mediante la definición de propiedades de objetos . ¿Cómo lograrlo? En primer lugar, debe considerar por dónde empezar a ver el código fuente. En el artículo anterior Aprendizaje del código fuente de Vue: ¿qué hizo la nueva inicialización de Vue? Hablando de un método llamado: , la explicación que se le dio en ese momento fue: inicializar accesorios, métodos, datos, computar, observar, incluido el procesamiento receptivo.getter/setter

initState()

// src/core/instance/init.ts

export function initMixin(Vue: typeof Component) {
    
    
// 在原型上添加 _init 方法
  Vue.prototype._init = function (options?: Record<string, any>) {
    
    
    const vm: Component = this
    
    //...
    vm._self = vm
    initLifecycle(vm) // 初始化实例的属性、数据:$parent, $children, $refs, $root, _watcher...等
    initEvents(vm)  // 初始化事件:$on, $off, $emit, $once
    initRender(vm)  // 初始化渲染: render, mixin
    callHook(vm, 'beforeCreate', undefined, false) // // 调用生命周期的钩子函数,在这里就能看出一个组件在创建之前和之后分别做了哪些初始化
    initInjections(vm) // 初始化 inject
    initState(vm)  // 对props,methods,data,computed,watch进行初始化,包括响应式的处理
    initProvide(vm) // 初始化 provide
    callHook(vm, 'created') // created 初始化完成,可以执行挂载了
    
    //...
    
  }
}

La inicialización llama a muchos métodos aquí, y cada método hace cosas diferentes, y lo principal sobre la capacidad de respuesta son los datos en el componente props、data. El contenido de esta pieza está en initState()este método, así que ingrese el código fuente de este método para echar un vistazo.

2. Inicialización

initState()

//  src/core/instance/state.ts

// 数据响应式的入口
export function initState(vm: Component) {
    
    
  const opts = vm.$options
   // 初始化 props
  if (opts.props) initProps(vm, opts.props)
  // 初始化 methods
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化 data
  if (opts.data) {
    
    
    initData(vm)
  } else {
    
    
   // 没有 data 的话就默认赋值为空对象,并监听
    const ob = observe((vm._data = {
    
    }))
    ob && ob.vmCount++
  }
   // 初始化 computed
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化 watch 
  if (opts.watch && opts.watch !== nativeWatch) {
    
    
    initWatch(vm, opts.watch)
  }
}

Esto también es un montón de cosas de inicialización. Vayamos directamente al tema y relacionémonos con los datos receptivos, es decir initProps(), , initData(), observe(), y luego miremos el código fuente uno por uno.

initProps()

Estas son las principales cosas que hacer:

  • Recorra la lista pasada por el componente padre props.
  • Verifique el nombre, tipo, atributo predeterminado, etc. de cada atributo. Si no hay problema, llame y defineReactiveconfigúrelo para que responda.
  • Luego, use para proxy()enviar la propiedad a la instancia actual, como cambiar vm._props.xxa vm.xx, puede acceder a ella.
//  src/core/instance/state.ts

function initProps(vm: Component, propsOptions: Object) {
    
    
  // 父组件传入子组件的 props
  const propsData = vm.$options.propsData || {
    
    }
  // 经过转换后最终的 props
  const props = (vm._props = shallowReactive({
    
    }))
  // 存放 props 的数组
  const keys: string[] = (vm.$options._propKeys = [])
  const isRoot = !vm.$parent
  // 转换非根实例的 props
  if (!isRoot) {
    
    
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    
    
    keys.push(key)
    // 校验 props 类型、default 属性等
    const value = validateProp(key, propsOptions, propsData, vm)
    // 非生产环境下
    if (__DEV__) {
    
    
      const hyphenatedKey = hyphenate(key)
      if (
        isReservedAttribute(hyphenatedKey) ||
        config.isReservedAttr(hyphenatedKey)
      ) {
    
    
        warn(
          `"${
      
      hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      // 把 props 设置成响应式的
      defineReactive(props, key, value, () => {
    
    
       // 如果用户修改子组件中的 props 发出警告
        if (!isRoot && !isUpdatingChildComponent) {
    
    
          warn( `xxx警告`,vm)
        }
      })
    } else {
    
    
      // 把 props 设置成响应式的
      defineReactive(props, key, value)
    }
    // 把不在默认 vm 上的属性,代理到实例上
    // 可以让 vm._props.xx 通过 vm.xx 访问
    if (!(key in vm)) {
    
    
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

 - List item

initData()

Estas son las principales cosas que hacer:

  • Inicialice un dato y obtenga la colección de claves.
  • Recorra la colección de claves para determinar si hay un nombre idéntico al nombre de la propiedad en accesorios o al nombre del método en métodos.
  • Si no hay ningún problema, use proxy() para enviar todos los atributos de los datos a la instancia actual, y luego puede this.xxacceder a ellos a través de .
  • Finalmente, llame para observemonitorear todos los datos.
//  src/core/instance/state.ts

function initData(vm: Component) {
    
    
 // 获取当前实例的 data 
  let data: any = vm.$options.data
   // 判断 data 类型
  data = vm._data = isFunction(data) ? getData(data, vm) : data || {
    
    }
  if (!isPlainObject(data)) {
    
    
    data = {
    
    }
    __DEV__ && warn('数据函数应该返回一个对象',vm)
  }
  // 获取当前实例的 data 属性名集合
  const keys = Object.keys(data)
  // 获取当前实例的 props 
  const props = vm.$options.props
  // 获取当前实例的 methods 对象
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    
    
    const key = keys[i]
    // 非生产环境下判断 methods 里的方法是否存在于 props 中
    if (__DEV__) {
    
    
      if (methods && hasOwn(methods, key)) {
    
    
        warn(`Method方法不能重复声明`, vm)
      }
    }
    // 非生产环境下判断 data 里的属性是否存在于 props 中
    if (props && hasOwn(props, key)) {
    
    
      __DEV__ &&
        warn(`属性不能重复声明`,vm)
    } else if (!isReserved(key)) {
    
    
      // 都不重名的情况下,代理到 vm 上
      // 可以让 vm._data.xx 通过 vm.xx 访问
      proxy(vm, `_data`, key)
    }
  }
  // observe 监听 data
  const ob = observe(data)
  ob && ob.vmCount++
}

observe() - Guardia del observador

Este método se utiliza principalmente para agregar oyentes a los datos.
Estrictamente hablando, el método observe() debe considerarse como el guardián del Observer, que es una detección de cumplimiento antes de que el Observer esté a punto de comenzar.

Estas son las principales cosas que hacer:

  • Si el valor ya se ha procesado de forma reactiva (ya se ha observado), devuelve el observador existente.
  • De lo contrario, se agrega una nueva instancia de observador para el valor que no tiene observador agregado.
// src/core/observer/index.ts

export function observe(
  value: any,
  shallow?: boolean,
  ssrMockReactivity?: boolean
): Observer | void {
    
    

 // 首先'__ob__'的值其实就是一个'Observer'实例
 // 所以下面的判断其实就是:如果已经做过了响应式处理(已经被观察过了),则直接返回'ob',也就是'Observer'实例
  if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    
    
    return value.__ob__
  }
  // 如果是初始化的时候,则没有'Observer'的实例,因此需要创建一个'Observer'实例
  if (
    shouldObserve &&
    (ssrMockReactivity || !isServerRendering()) &&
    (isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value.__v_skip /* ReactiveFlags.SKIP */ &&
    !isRef(value) &&
    !(value instanceof VNode)
  ) {
    
    
    return new Observer(value, shallow, ssrMockReactivity)
  }
}

Observador

Esta es una clase cuyo rol es convertir un dato normal en un dato observable.

Estas son las principales cosas que hacer:

  • Marque el valor actual como un atributo de respuesta para evitar operaciones repetidas.
  • Luego juzgue el tipo de datos
    1) En caso afirmativo 数组, recorra la matriz y llame observe()para monitorear cada elemento.
    2) En caso afirmativo 对象, recorra el objeto y llame para defineReactive()crear un objeto receptivo.
// src/core/observer/index.ts

export class Observer {
    
    
  dep: Dep
  vmCount: number 
  constructor(public value: any, public shallow = false, public mock = false) {
    
    
    // 实例化一个dep
    // 正常来说,遍历一个对象的属性时,都是一个属性创建一个dep,为什么此处要给当前对象额外创建一个dep?
    // 其目的在于如果使用Vue.set/delete添加或删除属性,这个dep负责通知更新。
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    // 给 value 添加 __ob__ 属性,值为value的 Observe 实例
    // 表示已经变成响应式了,目的是对象遍历时就直接跳过,避免重复操作
    def(value, '__ob__', this)
    // 类型判断
    if (isArray(value)) {
    
    
      if (!mock) {
    
    
        // 判断数组是否有__proto__
        if (hasProto) {
    
    
         // 如果有就重写数组的方法
          ;(value as any).__proto__ = arrayMethods
        } else {
    
    
        // 没有就通过 def,也就是Object.defineProperty 去定义属性值
          for (let i = 0, l = arrayKeys.length; i < l; i++) {
    
    
            const key = arrayKeys[i]
            def(value, key, arrayMethods[key])
          }
        }
      }
      if (!shallow) {
    
    
        this.observeArray(value)
      }
    } else {
    
    
      // 对象响应式处理方法
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) {
    
    
        const key = keys[i]
        defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }
  
 observeArray(value: any[]) {
    
    
    // 遍历数组,为数组的每一项设置观察,处理数组元素为对象的情况
    for (let i = 0, l = value.length; i < l; i++) {
    
    
      observe(value[i], false, this.mock)
    }
  }

definirReactivo()

El rol es definir objetos sensibles.

Estas son las principales cosas que hacer:

  • Cree uno para cada atributo primero dep 实例.
  • Si es 对象, observe()llámelo y escuche recursivamente para asegurarse de que no importa cuán profunda esté anidada la estructura, las propiedades internas pueden convertirse en objetos receptivos.
  • Luego llame Object.defineProperty()al getter y setter del objeto secuestrado.
  • Si se obtiene, el captador de activación llamará dep.depend()para watcher(观察者) enviar a la matriz dependiente subs.
  • Si se actualiza, activar el setter hará lo siguiente:
    1) El nuevo valor no cambia o salta directamente sin la propiedad setter.
    2) Si el nuevo valor es un objeto, llame observe()al oyente recursivo.
    3) Luego llame a dep.notify()la actualización de despacho.
// src/core/observer/index.ts

export function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: boolean
) {
    
    
  // 给每个响应式数据的 属性 都对应着一个 Dep 实例(重要)
  const dep = new Dep()
  // 拿到对象的属性描述符
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    
    
    return
  }

  // 获取自定义的 getter 和 setter
  const getter = property && property.get
  const setter = property && property.set
  if (
    (!getter || setter) &&
    (val === NO_INITIAL_VALUE || arguments.length === 2)
  ) {
    
    
    val = obj[key]
  }
  // 如果 val 是对象的话就递归监听
  // 递归调用 observe 就可以保证不管对象结构嵌套有多深,都能变成响应式对象
  let childOb = !shallow && observe(val, false, mock)
  // 截持对象属性的 getter 和 setter
  Object.defineProperty(obj, key, {
    
    
    enumerable: true,
    configurable: true,
    // 拦截 getter,当取值时会触发该函数
    get: function reactiveGetter() {
    
    
      const value = getter ? getter.call(obj) : val
      // 进行依赖收集
      // 初始化渲染 watcher 时访问到需要双向绑定的对象,从而触发 get 函数
      if (Dep.target) {
    
    
        if (__DEV__) {
    
    
          dep.depend({
    
    
            target: obj,
            type: TrackOpTypes.GET,
            key
          })
        } else {
    
    
          dep.depend()
        }
        if (childOb) {
    
    
          childOb.dep.depend()
          if (isArray(value)) {
    
    
            dependArray(value)
          }
        }
      }
      return isRef(value) && !shallow ? value.value : value
    },
    // 拦截 setter,当值改变时会触发该函数
    set: function reactiveSetter(newVal) {
    
    
      const value = getter ? getter.call(obj) : val
      // 判断新值是否发生变化
      if (!hasChanged(value, newVal)) {
    
    
        return
      }
      if (__DEV__ && customSetter) {
    
    
        customSetter()
      }
      if (setter) {
    
    
        setter.call(obj, newVal)
      } else if (getter) {
    
    
        return
      } else if (!shallow && isRef(value) && !isRef(newVal)) {
    
    
        value.value = newVal
        return
      } else {
    
    
        val = newVal
      }
      // 如果新值是对象的话递归监听,也做响应式处理
      childOb = !shallow && observe(newVal, false, mock)
      if (__DEV__) {
    
    
       // 派发更新
        dep.notify({
    
    
          type: TriggerOpTypes.SET,
          target: obj,
          key,
          newValue: newVal,
          oldValue: value
        })
      } else {
    
    
       // 派发更新
        dep.notify()
      }
    }
  })

  return dep
}

El código fuente anterior presenta dep.dependla recopilación de dependencias y dep.notify()la distribución de actualizaciones a través de .
Se puede decir que el papel de Dep en la capacidad de respuesta de datos es 数据的依赖收集y 变更通知.

3. Confíe en la colección

El núcleo de la colección de dependencias es Dep, y también es Watcherinseparable de . A continuación se explicará a través de ejemplos y código fuente.

Watcher y Dep obtienen una idea de conceptos a través de ejemplos

1) ¿Qué es el Vigilante?

Por favor, vea el código a continuación:

// 例子代码,与本章代码无关

<div>{
    
    {
    
     name }}</div>

data() {
    
    
    return {
    
    
       name: '铁锤妹妹'
    }
 },
 computed: {
    
    
    info () {
    
    
       return this.name
    }
  },
 watch: {
    
    
    name(newVal) {
    
    
       console.log(newVal)
    }
 }

Del código anterior, podemos ver que namelas variables dependen de tres lugares, que son html里, computed里y watch里. Siempre que namecambie el valor del atributo, se volverá a representar en html, se recalculará en computado y se volverá a ejecutar en observación. Entonces, ¿quién va a notificar a estos tres lugares namede modificación? Eso es Watchertodo.

2) ¿Cuáles son los tipos de Vigilante?

Los tres lugares mencionados anteriormente solo representan tres tipos Watcher, a saber:

  • Rendering Watcher : Cuando se modifica la variable, se encarga de notificar la re-renderización en HTML.
  • Computed Watcher : Cuando se modifica una variable, se encarga de notificar los cambios de las variables de atributos computados en computados que dependen de esta variable.
  • usuario Watcher : Cuando se modifica una variable, se encarga de notificar la ejecución de la función de la variable correspondiente en el atributo watch.

3) ¿Qué es Dep?

¿Qué es Dep? Todavía el código de ejemplo anterior:

<div>{
    
    {
    
     name }}</div>

data() {
    
    
    return {
    
    
       name: '铁锤妹妹'
    }
 },
 computed: {
    
    
    info () {
    
    
       return this.name
    }
  },
 watch: {
    
    
    name(newVal) {
    
    
       console.log(newVal)
    }
 }

Las variables aquí namedependen de tres lugares, y los tres lugares representan tres tipos Watcher, entonces, ¿ namemanejará estos tres directamente Watcher? La respuesta es no, namese creará una instancia de un Dep para administrarlos por mí mismo Wacther, similar a un ama de llaves, cuando namese realicen cambios, se notificará al dep, y el dep tomará la orden del maestro para notificarlos Wactherpara completar lo que debe hacer. .

Después de entender el ejemplo anterior, veamos el código fuente de Dep y Watcher a continuación .

Código fuente de la dependencia

Esta es una clase que realmente Watcheradministra el archivo .

Primero inicialice una subsmatriz para almacenar dependencias, es decir, observadores; quien dependa de estos datos está en esta matriz; luego defina varios métodos para agregar, eliminar y notificar actualizaciones a las dependencias .

Además, Dep también tiene un atributo estático target, que es global Watcher, lo que también significa que solo puede existir uno global a la vez Watcher.

  • Debido a que involucra la parte de profundidad, solo eche un vistazo a esto, primero comprenda el proceso aproximadamente, y hablaré de eso en detalle a continuación, que tiene algo que ver con eso.
  • Cada propiedad monitoreada tendrá una Depinstancia correspondiente. En el getter de la propiedad, el Watcher que se está ejecutando actualmente se agregará a la lista de dependencias del Dep. Cuando la propiedad cambie, notifytodos los Vigilantes dependientes serán notificados para actualizar a través del método de Dep.
  • Durante el proceso de actualización, el método setter del atributo se activará primero, y luego la instancia Dep correspondiente al atributo notificará a todos los objetos Watcher dependientes para que se actualicen. Estos objetos de Watcher se agregarán a 调度器队列, específicamente llamando al updatemétodo de Watcher para agregar Watcher a 调度器队列. Finalmente, Vue ejecutará la operación de actualización del programador en el momento adecuado, sacará el Watcher de la cola del programador y ejecutará su lógica de actualización.
// src/core/observer/dep.ts

// Dep在数据响应式中扮演的角色就是数据的 依赖收集 和 变更通知
// 在获取数据的时候知道自己(Dep)依赖的watcher都有谁,同时在数据变更的时候通知自己(Dep)依赖的这些watcher去执行他们(watcher)的update 
export default class Dep {
    
    
  static target?: DepTarget | null
  id: number
  subs: Array<DepTarget | null>
  _pending = false

  constructor() {
    
    
    this.id = uid++
    // 用来存储 Watcher 的数组
    this.subs = []
  }
  // 在 dep 中添加 观察者
  addSub(sub: DepTarget) {
    
    
    this.subs.push(sub)
  }
  // 移除观察者
  removeSub(sub: DepTarget) {
    
    
    this.subs[this.subs.indexOf(sub)] = null
    if (!this._pending) {
    
    
      this._pending = true
      pendingCleanupDeps.push(this)
    }
  }

  depend(info?: DebuggerEventExtraInfo) {
    
    
    if (Dep.target) {
    
    
     // 调用 watcher 的 addDep 函数
      Dep.target.addDep(this)
      if (__DEV__ && info && Dep.target.onTrack) {
    
    
        Dep.target.onTrack({
    
    
          effect: Dep.target,
          ...info
        })
      }
    }
  }
  // 遍历 dep 中所有的 Watcher,通知相关的 Watcher 执行 update 派发更新
  notify(info?: DebuggerEventExtraInfo) {
    
    
    const subs = this.subs.filter(s => s) as DepTarget[]
    if (__DEV__ && !config.async) {
    
    
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
    
    
      const sub = subs[i]
      if (__DEV__ && info) {
    
    
        sub.onTrigger &&
          sub.onTrigger({
    
    
            effect: subs[i],
            ...info
          })
      }
      sub.update()
    }
  }
}
// 同一时间只有一个观察者使用,赋值观察者
Dep.target = null
const targetStack: Array<DepTarget | null | undefined> = []
// 开始收集的时候 设置:Dep.target = watcher
export function pushTarget(target?: DepTarget | null) {
    
    
  targetStack.push(target)
  Dep.target = target
}
// 结束收集的时候 设置:Dep.target = null
export function popTarget() {
    
    
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

Código fuente del observador

Watcher también es una clase, también llamado observador (suscriptor), el trabajo aquí es bastante complicado y también está conectado en 模板编译serie 渲染.

En un componente, cada propiedad monitoreada corresponde a una Watcherinstancia. Cuando una propiedad cambia, Watcherse agregará la correspondiente 调度器队列(también conocida como la cola de procesamiento).

// src/core/observer/watcher.ts

export default class Watcher implements DepTarget {
    
    
 // ...
  constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null,
    isRenderWatcher?: boolean
  ) {
    
    
    // ...
    if ((this.vm = vm) && isRenderWatcher) {
    
    
      vm._watcher = this
    }
    if (options) {
    
    
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
      if (__DEV__) {
    
    
        this.onTrack = options.onTrack
        this.onTrigger = options.onTrigger
      }
    } else {
    
    
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid 
    this.active = true
    this.post = false
    this.dirty = this.lazy 
    // Watcher 实例持有的 Dep 实例的数组
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = __DEV__ ? expOrFn.toString() : ''

    if (isFunction(expOrFn)) {
    
    
      this.getter = expOrFn
    } else {
    
    
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
    
    
        this.getter = noop
        __DEV__ &&
          warn(
            `Failed watching path: "${
      
      expOrFn}" ` +
              'Watcher only accepts simple dot-delimited paths. ' +
              'For full control, use a function instead.',
            vm
          )
      }
    }
    this.value = this.lazy ? undefined : this.get()
  }

  get() {
    
    
    // 该函数用于缓存 Watcher
    // 因为在组件含有嵌套组件的情况下,需要恢复父组件的 Watcher
    pushTarget(this)
    let value
    const vm = this.vm
    try {
    
    
     // 调用回调函数,也就是upcateComponent,对需要双向绑定的对象求值,从而触发依赖收集
      value = this.getter.call(vm, vm)
    } catch (e: any) {
    
    
      if (this.user) {
    
    
        handleError(e, vm, `getter for watcher "${
      
      this.expression}"`)
      } else {
    
    
        throw e
      }
    } finally {
    
    
      // 深度监听
      if (this.deep) {
    
    
        traverse(value)
      }
      // 恢复Watcher
      popTarget()
      // 清理不需要了的依赖
      this.cleanupDeps()
    }
    return value
  }

  // 添加依赖
  addDep(dep: Dep) {
    
    
    const id = dep.id
    if (!this.newDepIds.has(id)) {
    
    
      // watcher添加它和dep的关系
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
    
    
       // 和上面的反过来,dep添加它和watcher的关系
       // 把当前 Watcher push 进 subs 数组
        dep.addSub(this)
      }
    }
  }
  // 清理不需要的依赖
  cleanupDeps() {
    
     }

  // 派发更新时调用
  update() {
    
    
    // 如果是懒执行走这里,比如:computed
    if (this.lazy) {
    
    
      this.dirty = true
    // 如果是同步执行 则执行run函数
    } else if (this.sync) {
    
    
      this.run()
    // 将watcher放到watcher队列中
    } else {
    
    
      queueWatcher(this)
    }
  }

  // 执行 watcher 的回调
  run() {
    
    
    if (this.active) {
    
    
      // 调用get方法
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
    
    
         // 更换旧值为新值
        const oldValue = this.value
        this.value = value
        if (this.user) {
    
    
          const info = `callback for watcher "${
      
      this.expression}"`
          invokeWithErrorHandling(
            this.cb,
            this.vm,
            [value, oldValue],
            this.vm,
            info
          )
        } else {
    
    
          // 渲染watcher
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

 // 懒执行的watcher会调用该方法 比如:computed
  evaluate() {
    
    
    this.value = this.get()
    // computed的缓存原理
    // this.dirty设置为false 则页面渲染时只会执行一次computed的回调
    // 数据更新以后 会在update中重新设置为true
    this.dirty = false
  }

  depend() {
    
    
    let i = this.deps.length
    while (i--) {
    
    
      this.deps[i].depend()
    }
  }
}

Reponer:

  1. ¿Por qué el control del reloj escrito en nuestros propios componentes puede obtener automáticamente el valor nuevo y el valor anterior? De hecho, Watcher.run()ejecutará y soltará la función, y pasará el valor nuevo y el valor anterior.

Proceso de recopilación de dependencias

Al renderizar y montar por primera vez, habrá una lógica de código de este tipo.

// src/core/instance/lifecycle.ts

export function mountComponent(...): Component {
    
    
   // 调用生命周期钩子函数
  callHook(vm, 'beforeMount')

  let updateComponent
  // 创建一个更新渲染函数; 调用 _update 对 render 返回的虚拟 DOM 进行 patch(也就是 Diff )到真实DOM,这里是首次渲染
    updateComponent = () => {
    
    
      vm._update(vm._render(), hydrating)
    }
    
  // 当触发更新的时候,会在更新之前调用
  const watcherOptions: WatcherOptions = {
    
    
    before() {
    
    
     // 判断 DOM 是否是挂载状态,就是说首次渲染和卸载的时候不会执行
      if (vm._isMounted && !vm._isDestroyed) {
    
    
       // 调用生命周期钩子函数
        callHook(vm, 'beforeUpdate')
      }
    }
  }

 // 生成一个渲染 watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染
  new Watcher(
    vm,
    updateComponent,
    noop,
    watcherOptions,
    true 
  )
 
 // 没有老的 vnode,说明是首次渲染
  if (vm.$vnode == null) {
    
    
    vm._isMounted = true
    // 渲染真实 dom 结束后调用 mounted 生命周期
    callHook(vm, 'mounted')
  }
  return vm
}

Aquí está la preparación para el montaje 真实dom, se crea el observador de representación y se llama al método updateComponent dentro del observador de representación.

Recopilación de dependencias (solo tenga una comprensión general del proceso):

  • Se creará una instancia de representación antes del montaje watcher, y el método watcherse ejecutará al ingresar al constructorthis.get()
  • Luego se ejecutará pushTarget(this), que es asignar Dep.targetel valor a la representación actual watchery empujarlo a la pila (para recuperación)
  • Luego ejecute this.getter.call(vm, vm), es decir, la updateComponent()función anterior, que se ejecutavm._update(vm._render(), hydrating)
  • Luego, la ejecución vm._render()generará la representación vnode, a la que se accederá durante este proceso vm 上的数据, y se activará el objeto de datos.getter
  • Hay un getter para cada valor de objeto dep, y cuando se activa el getter dep.depend(), el método se llamará y ejecutará.Dep.target.addDep(this)
  • Luego, se realizarán algunos juicios aquí para garantizar que los mismos datos no se agregarán varias veces, y luego se agregarán los datos calificados push, subsy esto se hace 完成了依赖的收集, pero aún no se ha ejecutado, si es un objeto, es activará 递归对象el captador de todos los subelementos, también restaurará el estado de Dep.target

4. Distribuir actualizaciones

Cuando se cambian los datos de respuesta definidos, activará Object.definePropertyel método set para cambiar directamente los datos en la capa de datos, pero el problema es que los datos se modifican, entonces, ¿cómo actualizar la vista? En este momento depserá útil, depactivará notifyel método para notificar la representación Watcherpara actualizar la vista.

notificar()

Cuando se active el setter, se llamará para dep.notify()notificar a todos los suscriptores que distribuyan actualizaciones.

// src/core/observer/dep.ts

  notify(info?: DebuggerEventExtraInfo) {
    
    
    const subs = this.subs.filter(s => s) as DepTarget[]
    if (__DEV__ && !config.async) {
    
    
      // 如果不是异步,需要排序以确保正确触发
      subs.sort((a, b) => a.id - b.id)
    }
    // 遍历dep的所有 watcher 然后执行他们的 update 
    for (let i = 0, l = subs.length; i < l; i++) {
    
    
      const sub = subs[i]
      if (__DEV__ && info) {
    
    
        sub.onTrigger &&
          sub.onTrigger({
    
    
            effect: subs[i],
            ...info
          })
      }
      // 触发更新
      sub.update()
    }
  }

actualizar()

Se llama cuando se envía una actualización.

// src/core/observer/watcher.ts

 update() {
    
    
    if (this.lazy) {
    
    
      this.dirty = true
    } else if (this.sync) {
    
    
      this.run()
    } else {
    
    
     // 将 Watcher 对象添加到调度器(scheduler)队列中,以便在适当的时机执行其更新操作。
      queueWatcher(this)
    }
  }

queueWatcher()

Esta es una cola, y también es 派发更新un punto de optimización cuando Vue lo está haciendo. Es decir, la devolución de llamada no se activa cada vez que cambian los datos watcher, sino que estos se watcheragregan a una cola, se watcherejecutan 去重y luego nextTickse ejecutan en la tarea asíncrona.

Estas son las principales cosas que hacer:

  • Primero use el objeto has para encontrar la identificación para asegurarse de que el mismo observador solo sea empujado una vez.
  • de lo contrario, si se inserta un nuevo observador durante la ejecución del observador, vendrá aquí y luego buscará de atrás hacia adelante, encontrará la primera posición donde la identificación que se insertará es mayor que la identificación en la cola actual, y insértelo en la cola, de modo que la longitud de la cola haya cambiado.
  • Finalmente, mediante la espera, se garantiza nextTickque sólo se llamará una vez.
// src/core/observer/scheduler.ts

export function queueWatcher(watcher: Watcher) {
    
    
 // 获得 watcher 的 id
  const id = watcher.id
 // 判断当前 id 的 watcher,是否在观察者队列中,已经存在的话return出去
  if (has[id] != null) {
    
    
    return
  }

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

  has[id] = true
  if (!flushing) {
    
    
    // 最开始会进入这里
    queue.push(watcher)
  } else {
    
    
   // 如果在执行 watcher 期间又有新的 watcher 插入进来就会到这里,插入新的 watcher 
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
    
    
      i--
    }
    queue.splice(i + 1, 0, watcher)
  }
  // 最开始会进入这里
  if (!waiting) {
    
    
    waiting = true

    if (__DEV__ && !config.async) {
    
    
      flushSchedulerQueue()
      return
    }
    // 因为每次派发更新都会引起渲染,所以把所有 watcher 都放到 nextTick 里调用
    nextTick(flushSchedulerQueue)
  }
}

vaciarSchedulerQueue()

La función flushSchedulerQueue() generalmente se ejecuta de forma asíncrona en el siguiente bucle de eventos para garantizar el procesamiento por lotes después de que se complete la operación de actualización interna de Vue.js. Esto evita la activación prematura de actualizaciones de DOM, lo que garantiza que las actualizaciones de DOM solo se activen una vez en la misma tarea asincrónica.

Estas son las principales cosas que hacer:

  • Primero clasifique la cola del observador, hay tres condiciones de clasificación, vea los comentarios.
  • Luego 遍历ejecute la cola del observador correspondiente watcher.run(); la longitud de la cola se evaluará cada vez que se cruce, porque después de la ejecución, es probable que se agregue un nuevo observador, y luego se ejecutará nuevamente el queueWatcher()método anterior.
// src/core/observer/scheduler.ts

function flushSchedulerQueue() {
    
    
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // 根据 id 排序,有如下条件
  // 1.组件更新需要按从父到子的顺序,因为创建过程中也是先父后子
  // 2.组件内我们自己写的 watcher 优先于渲染 watcher
  // 3.如果某组件在父组件的 watcher 运行期间销毁了,就跳过这个 watcher
  queue.sort(sortCompareFn)

  // 不要缓存队列长度,因为遍历过程中可能队列的长度发生变化
  for (index = 0; index < queue.length; index++) {
    
    
    watcher = queue[index]
    // 执行 beforeUpdate 生命周期钩子函数
    if (watcher.before) {
    
    
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    // 执行组件内我们自己写的 watch 的回调函数并渲染组件
    watcher.run()
    // 检查并停止循环更新,比如在 watcher 的过程中又重新给对象赋值了,就会进入无限循环
    if (__DEV__ && has[id] != null) {
    
    
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
    
    
        warn('无限循环了', watcher.vm)
        break
      }
    }
  }

  // 重置状态之前,先保留一份队列备份
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // 调用组件激活的钩子  activated
  callActivatedHooks(activatedQueue)
  // 调用组件更新的钩子  updated
  callUpdatedHooks(updatedQueue)
  cleanupDeps()
}

actualizado()

Finalmente se puede actualizar, la actualización es familiar para todos, es la función de gancho del ciclo de vida.

// src/core/observer/scheduler.ts

function callUpdatedHooks(queue: Watcher[]) {
    
    
  let i = queue.length
  while (i--) {
    
    
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm && vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
    
    
      callHook(vm, 'updated')
    }
  }
}

En este punto, se analiza básicamente el código fuente del proceso principal de respuesta a datos de Vue2.X.A continuación, presentaré los defectos y métodos de procesamiento de Object.defineProperty.

Cinco, Object.defineProperty defectos y métodos de procesamiento

Como se puede ver en lo anterior, Object.definePropertylos defectos al secuestrar objetos y matrices:

  • Indetectable o 对象属性._ _添加删除
  • Múltiples propiedades del objeto detector requieren 遍历el objeto.
  • Es imposible detectar 数组el cambio de elementos, y el método de matriz debe reescribirse.
  • No se puede detectar la modificación de la longitud de la matriz.

Para estos problemas, existen soluciones correspondientes en Vue2.X.

  • Necesidad de usar this.$sety this.$deleteactivar la capacidad de respuesta de agregar y eliminar propiedades de objetos.
  • Anule 7 métodos que cambiarán la matriz original y luego ob.dep.notify()distribuirá manualmente las actualizaciones.

Anulando métodos de matriz

Estas son las principales cosas que hacer:

  • Contiene una lista de métodos que mutan la matriz ('push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse').
  • Luego reescriba esos métodos nativos en la matriz, primero obtenga la matriz __ob__, es decir, es Observer 对象, si hay un nuevo valor, llame para observeArraycontinuar observando el cambio del nuevo valor (es decir, cambie la matriz a través de target__proto__==arrayMethods Instance tipo), y luego llame manualmente notify, notifique la representación watchery ejecute update.
// src/core/observer/array.ts

// 获取数组的原型
const arrayProto = Array.prototype
// 创建一个新对象并继承了数组原型的属性和方法,将其原型指向 Array.prototype
// 为什么要克隆一份呢?因为如果直接更改数组的原型,那么将来所有的数组都会被我改了。
export const arrayMethods = Object.create(arrayProto)
// 会改变原数组的方法列表;为什么只有7个方法呢?因为只有这7个方法改变了原数组
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

// 重写数组事件
methodsToPatch.forEach(function (method) {
    
    
  // 保存原本的事件
  const original = arrayProto[method]
  // 创建响应式对象
  def(arrayMethods, method, function mutator(...args) {
    
    
   // 首先 先执行原始行为,以前咋滴现在就咋滴
    const result = original.apply(this, args)
     // 然后 再做变更通知,如何变更的呢?
    // 1.获取ob实例
    const ob = this.__ob__
    // 2.如果是新增元素的操作,比如push、unshift或者增加元素的splice操作
    let inserted
    switch (method) {
    
    
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 3.新加入的元素需要做响应式处理
    if (inserted) ob.observeArray(inserted)
    // 4.让内部的dep派发更新
    if (__DEV__) {
    
    
      ob.dep.notify({
    
    
        type: TriggerOpTypes.ARRAY_MUTATION,
        target: this,
        key: method
      })
    } else {
    
    
      // 派发更新
      ob.dep.notify()
    }
    // 返回原生数组方法的执行结果
    return result
  })
})

La razón por la cual los atributos agregados más tarde en Vue2.x no responden

...
 const keys = Object.keys(value) //获取data中的所有属性名
 for (let i = 0; i < keys.length; i++) {
    
    
    const key = keys[i]
    defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
 } 
      
...

Object.defineProperty(obj, key, {
    
    
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    
     ... },
    set: function reactiveSetter (newVal) {
    
     ... }
})
...

Se puede ver a partir de los parámetros que necesita encontrar de keyacuerdo con lo específico para interceptar y procesar, por lo que es necesario cumplir con una condición previa, debe saber qué hay al principio, por lo que debe atravesar cada uno y definir getter , setter, por lo que los atributos agregados más tarde no responden. Además, el documento oficial de vue también decía:keys keys [i]keykey

Vue no puede detectar la adición o eliminación de propiedades. Dado que Vue realiza la conversión getter/setter en la propiedad al inicializar la instancia , la propiedad debe existir en el objeto de datos para que Vue la convierta en reactiva.

Seis, respuesta de datos vue2.X para hacer un resumen

1 primero

El núcleo del principio de respuesta a datos de Vue es interceptar Object.definePropertyla adquisición y configuración de datos.

2) Siguiente

Los datos receptivos de Vue se dividen en dos categorías: 对象y 数组.

2.1) Objetos

getterItera a través de todas las propiedades del objeto y establece la suma para cada propiedad setter, de modo que se pueda obtener y establecer en el futuro. Si el valor de la propiedad también es un objeto, se llamará varias veces para establecer observe() 递归遍历la suma para cada clave en el valor de la propiedad .gettersetter

Al buscar datos: depagregue relevante en watcher.
Al configurar datos: luego depnotifique a los relevantes watcherpara actualizar.

2.2) Matrices

Vuelva a escribir esos métodos nativos en el arreglo, primero obtenga el arreglo (la lista de 7 métodos que cambian los elementos del arreglo), es decir, __ob__su Observerobjeto, si hay un nuevo valor, llame para observeArrayseguir observando el cambio del nuevo valor (Eso es para target.__proto__= arrayMethodscambiar el tipo de la instancia de la matriz a través de), y luego llamar manualmente notify, notificar la representación watchery ejecutar update.

Al agregar nuevos datos: es necesario realizar 数据响应式el procesamiento y luego llamar a ob.dep.notify()Notificar watcherpara actualizar
Al eliminar datos: también actualizar mediante ob.dep.notify()Notificarwatcher

3) El papel de dep

Al igual que el ama de llaves, cuando los datos cambian, el dep llamará notify()al método y luego notificará al observador que actualice y ejecute update.

El papel que desempeña Dep en la fórmula de respuesta a datos es 数据的依赖收集sum 变更通知.

Sepa de quiénes son los observadores de los que depende (Dep) cuando obtiene los datos y notifique a los observadores de los que depende (Dep) que ejecuten su actualización (de observador) cuando cambien los datos.

4) Resumen del proceso de respuesta a datos de Vue2.X

  1. Compilación de plantillas
    Vue primero compilará la plantilla y <template></template>la convertirá en renderuna función de representación.
  2. Crear instancias de componentes e inicializar datos
    Durante el proceso de inicialización de los componentes de Vue, se inicializarán los datos. Vue obtendrá el objeto 遍历de la instancia del componente datay usará Object.defineProperty()el método para convertir el atributo de datos a gettery setter, dándose cuenta de la capacidad de respuesta.
  3. Secuestro de datos
    Vue utiliza el método de [secuestro de datos] para realizar la observación de datos. getterEspecíficamente, Vue define y en cada propiedad del objeto de datos setter, y cuando se accede o modifica la propiedad, se activan los métodos gettery correspondientes.setter
  4. Recopilación de dependencias
    Al realizar el secuestro de datos, Vue creará un Watcherobjeto y establecerá su relación de dependencia con el componente actual. Al acceder al método de datos receptivos , Watcher se agregará al objeto gettercorrespondiente a la relación de dependencia que se está procesando actualmente . DepEl objeto Dep es responsable de administrar estas dependencias y activar el Watcher correspondiente cuando se requiere una actualización.
  5. Ejecute la función de representación
    Cuando se procesan los datos, renderse ejecutará la función de representación generada en el primer paso. La función de representación se generará de acuerdo con el estado y los datos del componente 虚拟DOM树, y luego elpatch( elem,vnode)
  6. Realizar actualizaciones
    Cuando los datos reactivos cambien, setterse llamará a su método. WatcherEn este momento, Vue notificará al objeto relacionado con los datos para realizar updatela operación de actualización.
  7. Programación de actualizaciones
    Vue utiliza 调度器队列para administrar las actualizaciones que deben realizarse Watcher. Cuando los datos cambien, el Observador se agregará a la cola del programador. La combinación de 多个las operaciones de actualización de Watcher en 一个actualizaciones por lotes asincrónicas puede reducir el rediseño y la representación innecesarios y mejorar el rendimiento.
  8. Activar la actualización de dependencia (Watcher)
    en un momento apropiado (generalmente el siguiente tick o microtarea), se ejecutará la operación de actualización del programador. Tome los correos electrónicos de la cola Watcheruno por uno y ejecute su método de actualización. Estos métodos de actualización se activan 组件para 重新渲染mantener la vista sincronizada con los datos.

A través del proceso anterior, Vue realiza la actualización receptiva de datos. WatcherCuando los datos cambien, se rastreará y actualizará lo relevante y, en última instancia, afectará 组件的渲染los resultados.

Reponer:

1) Al principio, no podía distinguir la diferencia entre la cola del observador y la cola del programador. Pensé que era una cosa, así que registrémosla aquí.

  • Las colas de observadores y las colas de programadores son conceptos diferentes en Vue.
  • La cola de observadores es una estrategia utilizada por Vue cuando se actualizan los datos para administrar los observadores que deben actualizarse. Cuando los datos reactivos cambien, los observadores relacionados con los datos se agregarán a la cola de observadores, a la espera de más operaciones de actualización. La cola de observadores es responsable de administrar y programar actualizaciones para estos objetos de observadores.
  • La cola del programador es una cola utilizada para programar objetos Watcher en Vue. Cuando sea necesario actualizar varios Watchers, Vue procesará las operaciones de actualización de estos Watchers en lotes a través de la cola del programador. La cola del programador puede evitar la ejecución de una gran cantidad de métodos de actualización de Watcher a la vez en el mismo ciclo de eventos, lo que mejora el rendimiento y evita redibujar y renderizar innecesariamente.
  • En resumen, 观察者队列为了管理观察者对象的更新顺序, y 调度器队列为了批量处理 Watcher 对象的更新操作. Son diferentes en la implementación, pero todos son para garantizar la actualización receptiva de los datos y la sincronización de las vistas.

2) Si hay varias propiedades en un componente, si cada propiedad se actualiza, ¿ingresan los datos en una cola del programador?

  • En Vue, las operaciones de actualización de múltiples propiedades en un componente ingresarán a la misma cola del programador. Esta cola del programador se denomina "cola de procesamiento" o "cola de actualización asíncrona".
  • Cuando los datos de una propiedad cambian, el objeto Watcher relacionado con la propiedad se agregará a la cola del programador. Este proceso continuará 不断重复hasta que se hayan agregado a la cola todas las propiedades que deben actualizarse.
  • En el momento adecuado, Vue ejecutará la operación de actualización del programador, atravesará los objetos Watcher en la cola de procesamiento y ejecutará su lógica de actualización a su vez. Esto puede garantizar que las operaciones de actualización de múltiples atributos se realicen en el orden correcto y que las actualizaciones por lotes se realicen de manera eficiente.
  • Por lo tanto, no importa cuántas propiedades se actualicen en un componente, todas ingresarán a la misma cola del programador y el programador administrará y activará la operación de actualización de manera unificada. Este mecanismo asegura el orden y la optimización del rendimiento de las actualizaciones de datos.

Puede consultar:
Análisis del código fuente del principio de respuesta Vue3.2 y la diferencia entre Vue2.x y
Vue2
.

Supongo que te gusta

Origin blog.csdn.net/weixin_45811256/article/details/131713560
Recomendado
Clasificación