Análisis de código fuente de Vue: el principio de enlace de datos bidireccional (receptivo)

¿Cuál es el principio de enlace de datos bidireccional (receptivo) de vue? Esto todavía tiene que comenzar desde el código fuente de vue:

Sabemos que una característica importante de vue es 数据驱动视图cómo entender las seis palabras de la vista basada en datos.

Datos: estado;

Drive: función de renderizado;

Ver: interfaz de usuario de la página;

De esta manera, de hecho, podemos obtener una fórmula de este tipo: UI = render (estado), el cambio de estado conduce directamente al cambio de UI, y la constante es render, y vue desempeña el papel de render. Entonces, ¿cómo sabe Vue el cambio de estado? Hay una palabra llamada 变化侦测, porque se menciona en las pilas de tecnología convencionales actuales, vue, react y angular, es decir 状态追宗, una vez que cambian los datos, actualiza la vista. Comencemos con la detección de cambios del objeto.

La llamada detección de cambios significa que podemos saber cuándo se han leído los datos o cuándo se han reescrito.

Detección de datos

1. Realice la detección de objetos (observable)

La detección de objetos se realiza mediante Object.defineProperty()este método.

Primero defina un animal objeto:


let animal = {
    name: 'dog',
    age: 3

Entonces, ¿cómo sabemos cuándo se modifican las propiedades de este objeto animal? Veamos las siguientes operaciones:


let name = 'cat'
Object.defineProperty(animal, 'name', {
    configrable: true,    //    描述属性是否配置,以及可否删除
    enumerable: true,     //    描述属性是否会出现在for in 或者 Object.keys()的遍历中
    writable: true,       //    属性的值是否可以被重写
    get () {
        //    这里读取了name的值
        return name
    },
    set (value) {
        //    这里设置了name的值
        name = value
    }

De esta forma, cuando el valor de name se cambie a cat, será observable y detectable. Pero esto solo es posible para que se detecte un determinado atributo del objeto, pero el atributo del objeto a menudo no es uno. ¿Qué debo hacer cuando hay más de un atributo? De hecho, es muy simple, sí, sí, es la recursividad de la que quieres hablar. Directamente en el código:

//    源码位置:src/core/observer/index.js
export class Observer {
    constructor (value) {
        this.value = value
        def(value,'__ob__',this)
        if (Array.isArray(value)) {
              // 当value为数组时的逻辑
        } else {
              this.walk(value)
        }
    }
 
    walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
              defineReactive(obj, keys[i])
        }
    }
}
function defineReactive (obj,key,val) {
      if (arguments.length === 2) {
            val = obj[key]
      }
      if(typeof val === 'object'){
          new Observer(val)
      }
      Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get(){
              console.log(`${key}属性被读取了`);
              return val;
          },
          set(newVal){
              if(val === newVal){
                  return
              }
              console.log(`${key}属性被修改了`);
              val = newVal;
          }
      })
}

Declaramos una clase Observer, que se usa para convertir todos los atributos del objeto en detectables, y necesitamos agregar una instancia de Observer con el atributo __ob__ y el valor de cada objeto detectable, que es equivalente a Yu ha establecido una bandera para cada valor, lo que significa que el valor responde. Luego, cuando el valor es un objeto, obtenemos todas sus claves, recorremos el método de llamar a Object.defineProperty (), llamamos al método get / set para detectar, cuando se encuentra que el objeto entrante es un Object, pasamos In a de forma recursiva, el Objeto se instancia a través de la clase Observer nuevamente. De esta forma, hemos logrado la detección de objetos.

2. Realice la detección de matrices (observable)

Hablamos sobre la detección de objetos arriba, ahora hablemos sobre la detección de matrices.

Lo observable de la matriz es diferente de lo observable del objeto, porque la matriz no se puede observar con Object.defineProperty (). Entonces, ¿cómo hacer que la matriz sea observable, de hecho, lo mismo, la matriz se 拦截器realiza a través, entonces, qué es el interceptor sagrado?

Sabemos que hay solo unos pocos métodos nativos para manipular matrices en JavaScript, push / pop / shift / unshift / splice / sort / reverse. Se accede a todos estos métodos a través Array.prototype, por lo que si redefinimos un método en Array.prototype para implementar un método nativo, como newPush para lograr el mismo efecto que push, ¿es perfecto? El método nativo no se modifica y la operación correspondiente se puede realizar antes de llamar al método nativo.

let arr = [1,2,3]
//    arr.push(4) 原生方法调用
Array.prototype.newPush = function(val){
      console.log('arr被修改了')
      this.push(val)
}
arr.newPush(4)

Por lo tanto,数组的拦截器就是在数组实例和Array.prototype之间重写了操作数组的方法 . De esta forma, cuando se llama al método procesado, se llama al método reescrito en lugar del método nativo. Mira la implementación del interceptor:

// 源码位置:/src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  Object.defineProperty(arrayMethods, method, {
    enumerable: false,
    configurable: true,
    writable: true,
    value:function mutator(...args){
      const result = original.apply(this, args)
      return result
    }
  })
})

Esta es la implementación del interceptor.Cuando se llama al método de la matriz, lo que realmente se llama es mutator函数, por lo que podemos hacer algunas operaciones en esta función. Pero esto no es suficiente 只有将拦截器挂载在数组实例和Array.prototype之间才能生效,也就是将数据的__proto__属性设置为拦截器arrayMethods即可.

augment (valor, arrayMethods, arrayKeys)

// 源码位置:/src/core/observer/index.js
export class Observer {
  constructor (value) {
    this.value = value
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }
}
// 能力检测:判断__proto__是否可用,因为有的浏览器不支持该属性
export const hasProto = '__proto__' in {}
 
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
 
/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object, keys: any) {
  target.__proto__ = src
}
 
/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
 

Cuando el interceptor entra en vigor, si la matriz cambia, se puede detectar.

Lo anterior es cómo hacer que los cambios en los objetos y matrices sean detectables. Está lejos de ser suficiente para ser detectado. Hablemos de confiar en la colección.

Recopilación dependiente de datos

1. Colección de objetos dependientes

Recopilación dependiente : qué datos se utilizan en la vista, la vista depende de qué datos y los datos modificados se recopilan. Este proceso es recopilación dependiente.

Dónde recopilar dependencias : los datos observables getterse obtienen en, por lo que getter es un lugar para recopilar dependencias.

Cuándo notificar actualizaciones dependientes : los cambios de datos setterse pueden observar en el, naturalmente, las actualizaciones dependientes de la notificación están en el establecedor.

¿Dónde se deben recopilar las dependencias? Y mira hacia abajo:

Debido a que no podemos controlar el tamaño de los datos actualizados, necesitamos un administrador de dependencias para la colección de dependencias:

// 源码位置:src/core/observer/dep.js
export default class Dep {
   constructor () {
       this.subs = []
   }

   addSub (sub) {
       this.subs.push(sub)
   }
   // 删除一个依赖
   removeSub (sub) {
       remove(this.subs, sub)
   }
   // 添加一个依赖
   depend () {
       if (window.target) {
           this.addSub(window.target)
       }
   }
   // 通知所有依赖更新
   notify () {
       const subs = this.subs.slice()
       for (let i = 0, l = subs.length; i < l; i++) {
           subs[i].update()
       }
   }
}

/**
* Remove an item from an array
*/
export function remove (arr, item) {
   if (arr.length) {
       const index = arr.indexOf(item)
       if (index > -1) {
           return arr.splice(index, 1)
       }
   }
}

Con el administrador de dependencias, podemos recopilar dependencias en el captador y notificar la actualización de la dependencia en el configurador.

function defineReactive (obj,key,val) {
    if (arguments.length === 2) {
         val = obj[key]
    }
    if(typeof val === 'object'){
        new Observer(val)
    }
    const dep = new Dep()  //实例化一个依赖管理器,生成一个依赖管理数组dep
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get(){
            dep.depend()    // 在getter中收集依赖
            return val;
        },
        set(newVal){
            if(val === newVal){
            return
        }
        val = newVal;
        dep.notify()   // 在setter中通知依赖更新
     }
  })
}

El método dep.depend () se llama en el captador para recopilar dependencias, y el método dep.notify () se llama en el definidor para notificar todas las actualizaciones de dependencias.

De hecho, una clase de observador que depende de los datos para cada cambio se implementa en Vue. Cuando los datos cambian más tarde, no notificamos directamente a la actualización dependiente, sino que notificamos a la instancia de Watch dependiente, y la instancia de Watcher notificará la vista real.

export default class Watcher {
  constructor (vm,expOrFn,cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn)
    this.value = this.get()
  }
  get () {
    window.target = this;
    const vm = this.vm
    let value = this.getter.call(vm, vm)
    window.target = undefined;
    return value
  }
  update () {
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}
 
/**
 * Parse simple path.
 * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
 * 例如:
 * data = {a:{b:{c:2}}}
 * parsePath('a.b.c')(data)  // 2
 */
const bailRE = /[^\w.$]/
export function parsePath (path) {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}
 

análisis:

  • Cuando se crea una instancia del reloj, el constructor se ejecuta primero;
  • Llame al método de instancia this.get () en el constructor;
  • En el método get (), primero asigne la instancia a un objeto único global window.target by window.target = this, y luego obtenga el objeto dependiente let value = this.getter.call (vm, vm) Data, el El propósito de obtener datos dependientes es activar el captador en los datos. Como dijimos anteriormente, se llama a dep.depend () para recopilar dependencias en el captador, y window.target se recupera de dep.depend (). Y almacenarlo en la matriz de dependencia y suelte window.target al final del método get ().
  • Cuando los datos cambian, se activa el definidor de datos. El método dep.notify () se llama en el definidor. En el método dep.notify (), se recorren todas las dependencias (es decir, instancias de observadores) y se realiza el método update () dependiente Es decir, el método de instancia update () en la clase Watcher llama a la función de devolución de llamada de actualización del cambio de datos en el método update () para actualizar la vista.

Un breve resumen es:

Watcher primero se fija a sí mismo en la ubicación designada globalmente única (window.target) y luego lee los datos. Debido a que se leen los datos, se activará el captador de estos datos. Luego, en el getter, el Watcher que actualmente está leyendo datos se leerá desde la posición global única, y este observador se recopilará en Dep. Después de la recopilación, cuando los datos cambien, se enviará una notificación a cada Watcher en Dep. De esta manera, Watcher puede suscribirse activamente a cualquier cambio de datos. Para facilitar la comprensión, hemos elaborado el diagrama de flujo de relaciones, como se muestra a continuación:

Inserte la descripción de la imagen aquí
Arriba, todas las operaciones como la detección de datos de objetos, la recopilación de dependencias y la actualización de dependencias se completan por completo.

2. Colección de dependencias de la matriz

Para las matrices, de hecho, existe un método similar a los objetos de la colección dependiente.

La colección de la dependencia de la matriz está en la clase de observador:

// 源码位置:/src/core/observer/index.js
export class Observer {
  constructor (value) {
    this.value = value
    this.dep = new Dep()    // 实例化一个依赖管理器,用来收集数组依赖
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }
}

De esta forma, también hemos implementado un administrador de dependencias en el observador.

Entonces podemos ver cómo se recopilan las dependencias de la matriz e ir directamente al código:

function defineReactive (obj,key,val) {
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get(){
      if (childOb) {
        childOb.dep.depend()
      }
      return val;
    },
    set(newVal){
      if(val === newVal){
        return
      }
      val = newVal;
      dep.notify()   // 在setter中通知依赖更新
    }
  })
}
 
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 尝试为value创建一个0bserver实例,如果创建成功,直接返回新创建的Observer实例。
 * 如果 Value 已经存在一个Observer实例,则直接返回它
 */
export function observe (value, asRootData){
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob
}

Dentro de la función de observación, primero determine si hay un atributo __ob__ en los datos entrantes actuales, porque como se mencionó en el artículo anterior, si los datos tienen un atributo __ob__, significa que se ha transformado en un tipo reactivo. significa que los datos aún no responden, luego llame al nuevo Observer (valor) para convertirlo en receptivo y devuelva la instancia de Observer correspondiente a los datos. En la función defineReactive, primero obtenga la instancia de Observer childOb correspondiente a los datos y luego llame al administrador de dependencias en la instancia de Observer en el captador para recopilar las dependencias.

Esto hace que sea muy fácil confiar en las notificaciones para confiar en las actualizaciones.

methodsToPatch.forEach(function (method) {
 const original = arrayProto[method]
 def(arrayMethods, method, function mutator (...args) {
   const result = original.apply(this, args)
   const ob = this.__ob__
   // notify change
   ob.dep.notify()
   return result
 })
})

En este punto, la colección de dependencias de la matriz se logra básicamente, pero esto aún no ha terminado. La matriz no es una capa, tal vez varias capas, por lo que se necesita una inspección profunda:

export class Observer {
  value: any;
  dep: Dep;
 
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)   // 将数组中的所有元素都转化为可被侦测的响应式
    } else {
      this.walk(value)
    }
  }
 
  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
 
export function observe (value, asRootData){
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob
}

Detección de nuevos elementos en la matriz.

methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args   // 如果是push或unshift方法,那么传入参数就是新增的元素
        break
      case 'splice':
        inserted = args.slice(2) // 如果是splice方法,那么传入参数列表中下标为2的就是新增的元素
        break
    }
    if (inserted) ob.observeArray(inserted) // 调用observe函数将新增的元素转化成响应式
    // notify change
    ob.dep.notify()
    return result
  })
})

El principio de respuesta general de los datos vue es así. Los objetos no se pueden monitorear, las propiedades de los objetos aumentan o disminuyen, y los arreglos no se pueden monitorear para detectar cambios en los subíndices de los arreglos. Se han agregado los métodos Vue.set () y Vue.delete a vue para manejarlos.

Todo lo anterior es el principio reactivo, o el principio de enlace de datos bidireccional.

Solo como un registro de aprendizaje, consulte el artículo: https://blog.csdn.net/leelxp/article/details/106936827

Supongo que te gusta

Origin blog.csdn.net/weixin_44433499/article/details/114259116
Recomendado
Clasificación