Vue: detección de cambio de objeto

Vue: detección de cambio de objeto

1. ¿Qué es la detección de cambios?

Vue.js detectará automáticamente el estado y generará el DOM, y luego lo enviará a la página. Este proceso se llama renderizado. Este proceso de renderizado es declarativo. Usamos plantillas para describir la relación de mapeo entre el estado y el DOM.

En circunstancias normales, nuestra página actualiza constantemente el estado. En este momento, la página se vuelve a renderizar de acuerdo con el estado. Si desea detectar este proceso, implica la detección de cambios .

Hay dos tipos de detección de cambios: "empujar" y "tirar". Angular y React en los tres principales frameworks domésticos usan "pull", mientras que Vue usa "push" .

La ventaja de "empujar" es que cuando cambia el estado, Vue puede conocer inmediatamente y enviar la información a todas las dependencias relacionadas. Por lo tanto, cuanta más información se conozca, se pueden realizar actualizaciones más detalladas.

Cuanto más fina sea la granularidad, mayor será la sobrecarga de memoria consumida por el seguimiento de las dependencias. Vue presenta DOM virtual y ajusta la granularidad a una granularidad media. La dependencia vinculada a un estado ya no es un nodo DOM específico, sino un componente. El componente luego notifica al DOM, lo que reduce en gran medida el número de dependencias y reduce el consumo de memoria.

2. Seguimiento de cambios y recopilación de la implementación de dependencias

2.1 cambios de seguimiento de objetos en JS

En JS, hay dos métodos principales para detectar cambios de objetos: Object.definePropertyy Proxy.

Vamos a Object.definePropertyencapsularlo para que responda:

function defineReactive(data,key,val){
    
    
    Object.defineProperty(data,key,{
    
    
        enumerable: true,
        configurable: true,
        get: function(){
    
    
            return val;
        },
        set: function(newVal){
    
    
            if(val === newVal){
    
    
                return ;
            }
            val = newVal;
        }
    })
}

2.2 Recopilar dependencias

La recopilación de dependencias es guardar los datos de destino en la plantilla primero y, cuando los datos cambian, desencadenar el ciclo de dependencia recopilado anteriormente para que se procese una vez.

Para resumir en una oración, el captador recopila las dependencias y el definidor activa las dependencias .

Encapsulemos el código que recopila dependencias en clases y mejoremos el contenido de encapsulación anterior para permitirle recopilar dependencias de activación.

class Dep{
    
    
    constructor(){
    
    
        this.subs = [];
    }
    
    addSub(sub){
    
    
        this.subs.push(sub);
    }
    
    removeSub(sub){
    
    
        let index = this.subs.indexOf(sub);
        if(index > -1){
    
    
            this.subs = this.subs.splice(index,1);
        }
    }
    depend(){
    
    
        if(window.target){
    
    
            this.addSub(window.target);
        }
    }
    notify(){
    
    
        let subs = this.subs.splice();
        for(let i=0;i<this.subs.length;i++){
    
    
            subs[i].update() 	// 这里的update方法在后续定义
        }
    }
}

function defineReactive(data,key,val){
    
    
    let dependencies = new Dep(); // 依赖
    Object.defineProperty(data,key,{
    
    
        enumerable: true,
        configurable: true,
        get:function(){
    
    
            dependencies.depend();
            return val;
        },
        set: function(newVal){
    
    
            if(newVal === val){
    
    
                return ;
            }
            val = newVal;
            dependencies.notify();
        }
    })
}
  • En el código anterior, la dependencia que recopilamos se llama window.target, de hecho, tiene un nombre abstracto: Watcher .

2.3 Papel del vigilante

Watcher desempeña el papel de intermediario en Vue, notificándole cuando hay un cambio de datos y luego notificándolo a otros lugares.

En Vue, cómo usar Watcher:

vm.$watch("name",function(newVal,oldVal){
	// do something
})

Para implementar un observador, simplemente agregue la instancia del observador al atributo data.name y señale window.target. Cuando los datos cambien, puede notificar al observador, y el observador puede ejecutar la función de devolución de llamada en el parámetro.

class Watcher{
    
    
    // vm,属性或函数,callback
    constructor(vm,expOrFn,cb){
    
    
        this.vm = vm;
        this.getter = parsePath(expOrFn);	// 解析字符串路径
        this.cb = cb;
        this.value = this.get();
    }
    get(){
    
    
        window.target = this;
        let value = this.getter.call(this.vm,this.vm);
        window.target = undefined;
        return value;
    }
    update(){
    
    
        const oldVal = this.value;
        this.value = this.get();
        //触发回调函数
        this.cb.call(this.vm,this.value,oldVal);
    }
}
  • En el método get de Watcher, apuntamos window.target a esto, y en el proceso de obtener el valor dependiente actual (como data.name), activamos la función getter;

  • Una vez que se activa el getter, el dependiente this se almacenará en la instancia de Dep, y luego, cuando cambie el data.name, se activará el método de notificación de Dep, y todos los métodos de actualización dependientes se activarán en un bucle para realizar el "push " proceso.

  • El método de analizar la ruta de la cadena en el código se implementa de la siguiente manera:

  • const exp = /[^\w.$]/;
    function parsePath(path){
          
          
        if(exp.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;
        }
    }
    
  • Lea los datos a través del bucle capa por capa, y el resultado final son los datos de destino.

2.4 Cómo detectar todas las claves

En el código anterior, solo podemos detectar un atributo en los datos a la vez, pero nuestro propósito es detectar todos los atributos (incluidos los sub-atributos), por lo que podemos encapsular una clase y establecer todos los atributos en los datos Se convierte en un clave que puede detectar cambios.

class Observer{
    
    
    constructor(value){
    
    
        this.value = value;
    	// 当值不为数组时,将其转化为可侦测的	
        if(!Array.isArray(value)){
    
    
            this.walk(value);
        }
    }
    
    walk(value){
    
    
        const keys = Object.keys(value);
        for(let i=0;i<keys.length;i++){
    
    
            defineReactive(obj,keys[i],obj[keys[i]]);
        }
    }
}

function defineReactive(data,key,val){
    
    
    if(typeof val === 'object'){
    
    
        new Observer(val)
    }
    let dep = new Dep();
    Object.defineProperty(data,key,{
    
    
        enumerable: true,
        configurable: true,
        get:function(){
    
    
            dep.depend();
            return val;
        },
        set:function(newVal){
    
    
            if(newVal === val){
    
    
                return ;
            }
            val = newVal;
            dep.notify();
        }
    })
}

2.5 Todo el proceso de detección de cambios

Primero, los datos primero convierten todos los datos en datos detectables a través de Observer;
cuando el mundo exterior lee los datos, Watcher activará el captador, y el Watcher de los datos se recopilará en Dep (dependencia de recopilación);
cuando los datos cambian, el configurador se activará, El colocador notificará a Dep, y Dep notificará a Watcher;
después de recibir la notificación, Watcher envía una notificación al mundo exterior, y el mundo exterior realiza las operaciones correspondientes de acuerdo con los cambios en los datos.

3. Resumen

  • La detección de cambios es para detectar cambios en los datos.
  • El objeto se puede Object.definePropertyutilizar para convertir propiedades en modo detectable. El captador se activa cuando se obtienen los datos y el definidor se activa cuando se modifican los datos.
  • Recopile dependencias a través de captadores y notifique cambios de datos dependientes a través de configuradores.
  • Para recopilar dependencias, necesitamos una forma de almacenar dependencias, por lo que se encapsula una clase Dep para agregar, eliminar y notificar dependencias.
  • La llamada dependencia es Watcher, siempre que Watcher active el getter, se recopilará en Dep. Cuando los datos cambien, la lista dependerá circularmente y se notificará uno por uno.
  • El principio de Watcher es establecerse en el objeto globalmente único, luego leer los datos, activar el captador, el captador obtendrá el objeto global, es decir, el Watcher actual, y lo agregará al Dep. Watcher se suscribe activamente a cualquier cambio de datos de esta manera.
  • Para detectar cambios en todos los datos del objeto, encapsulamos la clase Observer y convertimos todos los datos (incluidos los subdatos) del objeto en datos detectables mediante defineReactive.
  • Antes de ES6, JS no proporcionaba capacidades de metaprogramación, por lo que era imposible rastrear los cambios de las propiedades nuevas y eliminadas de los objetos.

Supongo que te gusta

Origin blog.csdn.net/yivisir/article/details/114379919
Recomendado
Clasificación