Secuestro de datos en el núcleo de Vue

Secuestro de datos en el núcleo de Vue

Angular, Regular, Vue, React, etc. pueden implementar el enlace de datos, ya no es necesario realizar operaciones DOM manualmente, el principio de su implementación es básicamente 脏检查OR 数据劫持. Este artículo comienza con el marco Vue y aprende Object.defineProperty para realizar el secuestro de datos.

Introducción al uso de Object.defineProperty (obj, prop, descriptor):

  • parámetro

    obj: objeto de destino

    prop: el nombre de la propiedad o método que debe definirse

    descriptor: las características del atributo objetivo

  • Lista de características disponibles para su definición

    valor: el valor del atributo

    escribible: si es falso, el valor del atributo no se puede reescribir.

    get: Una vez que se accede al atributo de destino, se volverá a llamar a este método y el resultado de este método se devolverá al usuario.

    set: Una vez que se asigna el atributo de destino, se volverá a llamar a este método.

    configurable: si es falso, se invalidará cualquier intento de eliminar el atributo de destino o modificar los siguientes atributos (modificables, configurables, enumerables).

    enumerable: si se puede recorrer en el bucle for ... in o enumerar en Object.keys

¿Qué es el secuestro de datos?

A través de la introducción a Object.defineProperty anterior, no es difícil encontrar que cuando accedemos o establecemos las propiedades de un objeto, se activará la función correspondiente, y luego el valor de la propiedad será devuelto o establecido en esta función. Siendo ese el caso, ciertamente podemos hacer algo que queramos hacer cuando activamos la función, que es la operación de "secuestro".

En realidad está en Vue 通过Object.defineProperty来劫持对象属性的setter和getter操作,并“种下”一个监听器,当数据发生变化的时候发出通知. Dejame darte un ejemplo simple:

var data = {
    name:'lhl'
}

Object.keys(data).forEach(function(key){
    Object.defineProperty(data,key,{
        enumerable:true, // 是否能在for...in循环中遍历出来或在Object.keys中列举出来。
        configurable:true, // false,不可修改、删除目标属性或修改属性性以下特性
        get:function(){
            console.log('get');
        },
        set:function(){
            console.log('监听到数据发生了变化');
        }
    })
});
data.name //控制台会打印出 “get”
data.name = 'hxx' //控制台会打印出 "监听到数据发生了变化"

Inserte la descripción de la imagen aquí
Como se puede ver en el ejemplo anterior, podemos controlar completamente la configuración y lectura de las propiedades del objeto.
En Vue, el autor ha utilizado inteligentemente el método Object.defineProperty en muchos lugares.

Principio de Vue:

1. Supervisar los cambios en las propiedades del objeto

Esta debería ser la puerta de entrada para que Vue toque la vinculación de datos. Se suma al depósito de suscriptores al observar las propiedades de cada objeto y envía un aviso cuando los datos cambian. El código fuente relevante es el siguiente: (El autor usa el flujo ES6 + para escribir, y el código está en el módulo src / core / observer / index.js).

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  const dep = new Dep()//创建订阅对象

  const property = Object.getOwnPropertyDescriptor(obj, key)//获取obj对象的key属性的描述
  //属性的描述特性里面如果configurable为false则属性的任何修改将无效
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  let childOb = observe(val)//创建一个观察者对象
  Object.defineProperty(obj, key, {
    enumerable: true,//可枚举
    configurable: true,//可修改
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val//先调用默认的get方法取值
      //这里就劫持了get方法,也是作者一个巧妙设计,在创建watcher实例的时候,通过调用对象的get方法往订阅器dep上添加这个创建的watcher实例
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          dependArray(value)
        }
      }
      return value//返回属性值
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val//先取旧值
      if (newVal === value) {
        return
      }
      //这个是用来判断生产环境的,可以无视
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)//继续监听新的属性值
      dep.notify()//这个是真正劫持的目的,要对订阅者发通知了
    }
  })
}

Lo anterior es que Vue monitorea los cambios en las propiedades del objeto, entonces el problema es que cuando a menudo pasamos datos, a menudo no es un objeto, probablemente es una matriz, entonces no hay forma, la respuesta obviamente es otra. Luego, echemos un vistazo a cómo el autor monitorea los cambios de la matriz:

Supervisar cambios en la matriz

Primero echemos un vistazo a este código fuente:

const arrayProto = Array.prototype//原生Array的原型
export const arrayMethods = Object.create(arrayProto)

;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  const original = arrayProto[method]//缓存元素数组原型
  //这里重写了数组的几个原型方法
  def(arrayMethods, method, function mutator () {
    //这里备份一份参数应该是从性能方面的考虑
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    const result = original.apply(this, args)//原始方法求值
    const ob = this.__ob__//这里this.__ob__指向的是数据的Observer
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

...
//定义属性
function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}

El código anterior hereda principalmente el método prototipo de Array y luego realiza una modificación de secuestro, que puede ser notificada. En la etapa de datos del observador, Vue determinará si se trata de una matriz y modificará el prototipo de la matriz. En este caso, cualquier operación posterior en la matriz se puede controlar durante el proceso de secuestro. Combinando las ideas de Vue, simplemente escribimos una pequeña demostración para una mejor comprensión:

var arrayMethod = Object.create(Array.prototype);
['push','shift'].forEach(function(method){
    Object.defineProperty(arrayMethod,method,{
        value:function(){
            var i = arguments.length
            var args = new Array(i)
            while (i--) {
              args[i] = arguments[i]
            }
            var original = Array.prototype[method];
            var result = original.apply(this,args);
            console.log("已经控制了,哈哈");
            return result;
        },
        enumerable: true,
        writable: true,
        configurable: true
    })
})
var bar = [1,2];
bar.__proto__ = arrayMethod;
bar.push(3);//控制台会打印出 “已经控制了,哈哈”;并且bar里面已经成功的添加了成员 ‘3’ 

Todo el proceso parece no ser un problema. Parece que Vue ha sido perfecto. De hecho, Vue todavía no puede detectar cambios en elementos de datos y longitudes de matriz. Por ejemplo, la siguiente llamada:

vm.items[index] = "xxx";
vm.items.length = 100;

Intentamos evitar este método de llamada. Si realmente lo necesitamos, el autor también nos ayuda a implementar una operación $ set, que no se presentará aquí.

Implementar proxy de atributo de objeto

En circunstancias normales, instanciamos un objeto Vue como este:

var VM = new Vue({
    data:{
        name:'lhl'
    },
    el:'#id'
})

Es lógico pensar que cuando manipulamos datos, VM.data.name = 'hxx' es correcto, pero el autor siente que esto no es lo suficientemente conciso, por lo que la posibilidad de VM.name = 'hxx' se realiza mediante proxy. El código relevante es el siguiente:

function proxy (vm, key) {
  if (!isReserved(key)) {
    Object.defineProperty(vm, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter () {
        return vm._data[key]
      },
      set: function proxySetter (val) {
        vm._data[key] = val;
      }
    });
  }
}

En la superficie, parece que estamos operando VM.name, pero de hecho se logra secuestrando los métodos get y set en Object.defineProperty ().

Resumen El
marco Vue hace un buen uso del método Object.defineProperty () para realizar el enlace bidireccional de datos, y también logra un buen desacoplamiento entre módulos

Solo como un registro de nota de estudio, consulte el artículo: https://my.oschina.net/pengpengpengone/blog/1837257

Supongo que te gusta

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