Implementación del enlace bidireccional de datos Vue.js

prefacio

Cuando usamos vue, cuando los datos cambian, la interfaz también se actualizará, pero esto no es algo normal. ? de?

Cuando modificamos los datos, vue monitorea los cambios de datos a través del Object.definePropertymétodo en es5. Cuando los datos cambian 发布订阅模式, se actualiza a través de la interfaz de suscriptor estadístico. Este es un patrón de diseño.

Como se muestra en la figura a continuación, a partir de un nuevo Vue para crear una instancia de Vue, se pasarán el y los datos, y los datos se pasarán a un objeto observador, y Object.definpropertylos datos en los datos se convertirán en getter/setter para el secuestro de datos. , y se creará cada atributo en los datos. Se usa una instancia de Dep para guardar la instancia del observador.

Y se pasa el a compilar, y la instrucción se analiza en compilar.Cuando se analizan los datos en los datos utilizados en el, nuestro captador se activará, de modo que nuestro observador se agregará a la dependencia. Cuando los datos cambien, nuestro setter emitirá una notificación de dependencia, notificará al observador y el observador enviará una notificación a la vista después de recibir la notificación y permitirá que la vista se actualice.

secuestro de datos

La parte html crea una etiqueta div con una identificación de aplicación, que contiene etiquetas de intervalo y de entrada. La etiqueta de intervalo usa expresiones de interpolación, y la etiqueta de entrada usa v-model

<div class="container" id="app"><span>内容:{
   
   {content}}</span><input type="text" v-model="content">
</div> 

La parte js presenta un archivo vue.js, donde se escribe el código para realizar el enlace bidireccional de datos, y luego crea una instancia de Vue vm y monta los datos en la etiqueta div.

const vm=new Vue({el:'#app',data:{content:'请输入开机密码'}
}) 

Una nueva instancia de Vue obviamente necesita usar un constructor. La clase de definición en el código fuente de Vue está definida por función . Aquí uso la clase ES6 para crear esta instancia de Vue.

Luego establezca constructor, el parámetro formal se establece en obj_instance, como el objeto pasado cuando se pasa una nueva instancia de Vue, y los datos en el objeto pasado se asignan al atributo $data en la instancia

Las propiedades del objeto en javascript han cambiado, debemos informarnos, podemos actualizar las propiedades modificadas al nodo dom, por lo que al inicializar la instancia, defina una función de escucha como una llamada y pase los datos para ser monitoreados cuando vocación

class Vue{//创建Vue实例constructor(obj_instance){this.$data=obj_instance.dataObserver(this.$data)}
}
function Observer(data_instance){//监听函数} 

Imprimir esta instancia vm

La instancia se ha creado, pero aún necesita monitorear cada propiedad en $data. Se usa para implementar el monitoreo de datos Object.defineProperty. Object.definePropertyPuede modificar las propiedades existentes del objeto. El formato de sintaxis es Object.defineProperty(obj, prop, descriptor)

  • obj: el objeto cuyas propiedades se van a definir
  • prop: el nombre de la propiedad a definir o modificar
  • descriptor: el descriptor del atributo que se definirá o modificará

Para monitorear cada propiedad en el objeto, usamos Object.keys y foreach para recorrer cada propiedad en el objeto y usamos Object.defineProperty para monitorear datos para cada propiedad

function Observer(data_instance){Object.keys(data_instance).forEach(key=>{Object.defineProperty(data_instance,key,{enumerable:true,//设置为true表示属性可以枚举configurable:true,//设置为true表示属性描述符可以被改变get(){},//访问该属性的时候触发,get和set函数就是数据监听的核心set(){},//修改该属性的时候触发})})
} 

AntesObject.defineProperty de eso, debe guardar el valor correspondiente al atributo y devolverlo en la función de obtención; de lo contrario, el valor del atributo desaparecerá después de la función de obtención y el valor devuelto al atributo quedará indefinido.

let value=data_instance[key]
Object.defineProperty(data_instance,key,{enumerable:true,configurable:true,get(){console.log(key,value);return value},set(){}
}) 

Al hacer clic en el nombre del atributo en $datos, se activará la función de obtención

Luego configure la función set y configure el parámetro formal para el conjunto. Este parámetro formal representa el valor del atributo recién pasado, y luego asigne el nuevo valor del atributo al valor de la variable. No es necesario devolver nada, solo modifíquelo, y el return es para acceder a get.Devuelto a la vez, get también accederá al valor de la variable de valor más reciente después de la modificación

set(newValue){console.log(key,value,newValue);value = newValue
} 

Pero actualmente solo se establecen get y set para el atributo de la primera capa de $data, si hay una capa más profunda como

obj:{a:'a',b:'b'
} 

Este tipo de uno no establece get y set. Necesitamos secuestrar datos capa por capa en el atributo, por lo que usamos la recursión para monitorearnos nuevamente y hacemos un juicio condicional antes de atravesar. Si no hay un subatributo o ningún objeto es detectado, terminará la recursividad

function Observer(data_instance){//递归出口if(!data_instance || typeof data_instance != 'object') returnObject.keys(data_instance).forEach(key=>{let value=data_instance[key]Observer(value)//递归-子属性的劫持Object.defineProperty(data_instance,key,{enumerable:true,configurable:true,get(){console.log(key,value);return value},set(newValue){console.log(key,value,newValue);value = newValue}})})
} 

Hay otro detalle, si reescribimos la propiedad de contenido de $data de una cadena a un objeto, este nuevo objeto no tiene get ni set

Debido a que no configuramos get y set en absoluto al modificar, debemos llamar a la función de escucha en set

set(newValue){console.log(key,value,newValue);value = newValueObserver(newValue)
} 

análisis de plantillas

Después de secuestrar los datos, la aplicación de datos en la instancia de Vue debe llevarse a la página y debe agregarse un área de memoria temporal para actualizar todos los datos antes de representar la página para reducir las operaciones DOM.

Cree una función de análisis, establezca dos parámetros, uno es el elemento montado en la instancia de Vue y el otro es la instancia de Vue, obtenga el elemento en la función y guárdelo en $el de la instancia, y colóquelo en la memoria temporal después de obtener el elemento. Requerido para [createDocumentFragment]crear un nuevo fragmento de documento en blanco

Luego agregue los nodos secundarios de $el a la variable de fragmento uno por uno, la página no tiene contenido y el contenido se almacena temporalmente en el fragmento

class Vue{constructor(obj_instance){this.$data=obj_instance.dataObserver(this.$data)Compile(obj_instance.el,this)}
}
function Compile(ele,vm){vm.$el=document.querySelector(ele)const fragment=document.createDocumentFragment()let child;while (child=vm.$el.firstChild){fragment.append(child)}console.log(fragment);console.log(fragment.childNodes);
} 

Ahora aplique directamente el contenido que debe modificarse al fragmento del documento y vuelva a renderizar después de la aplicación. Solo necesita modificar el nodo de texto del nodo secundario childNodes del fragmento. El tipo de nodo de texto es 3. Puede crea una función y llámala para modificar el contenido en el fragmento

Puede haber nodos en el nodo, por lo que se determina si el tipo de nodo es 3, si no, llame a esta función de análisis recursivamente

Si el tipo de nodo es 3, modifique la operación, pero no es posible modificar el texto de todo el nodo. Solo necesita modificar el contenido de la expresión de interpolación, por lo que debe usar la coincidencia de expresiones regulares y guardar el resultado de la coincidencia. en una variable. El resultado coincidente es una matriz, y el elemento con índice 1 es el elemento que necesitamos extraer. Este elemento es la cadena obtenida al eliminar { {}} y espacios, y luego puede usar directamente la instancia de Vue para acceder al valor del atributo correspondiente Después de la modificación Después del retorno, salir y terminar la recursión

function Compile(ele,vm){vm.$el=document.querySelector(ele) //获取元素保存在实例了的$el里const fragment=document.createDocumentFragment() //创建文档碎片let child;while (child=vm.$el.firstChild){//循环将子节点添加到文档碎片里fragment.append(child)}fragment_compile(fragment)function fragment_compile(node){ //修改文本节点内容const pattern = /\{\{\s*(\S*)\s*\}\}/ //检索字符串中正则表达式的匹配,用于匹配插值表达式if(node.nodeType===3){const result = pattern.exec(node.nodeValue)if(result){console.log('result[1]')const value=result[1].split('.').reduce(//split将对象里的属性分布在数组里,链式地进行排列;reduce进行累加,层层递进获取$data的值(total,current)=>total[current],vm.$data)node.nodeValue=node.nodeValue.replace(pattern,value) //replace函数将插值表达式替换成$data里的属性的值}return }node.childNodes.forEach(child=>fragment_compile(child))}vm.$el.appendChild(fragment) //将文档碎片应用到对应的dom元素里面
} 

El contenido de la página vuelve a aparecer y la expresión de interpolación se reemplaza por los datos en la instancia de vm

Suscribirse al patrón de editor

Aunque se ha llevado a cabo el secuestro de datos y los datos se aplican a la página, los datos no se pueden actualizar a tiempo cuando los datos cambian y se debe implementar el modo de publicación por suscripción.

Primero cree una clase para recopilar y notificar a los suscriptores. Al generar una instancia, necesita una matriz para almacenar la información de los suscriptores, un método para agregar suscriptores a esta matriz y un método para notificar a los suscriptores. Llame a este método y devuelva Recorra la matriz de suscriptores y deje que los suscriptores llamen a su propio método de actualización para actualizar

class Dependency{constructor(){this.subscribers=[] //存放订阅者的信息}addSub(sub){this.subscribers.push(sub) //将订阅者添加到这个数组里}notify(){this.subscribers.forEach(sub=>sub.update()) //遍历订阅者的数组,调用自身的update函数进行更新}
} 

Para configurar la clase de suscriptor, debe usar las propiedades en la instancia de Vue, necesita la instancia de Vue y las propiedades correspondientes de la instancia de Vue y una función de devolución de llamada como parámetros, y asignar los parámetros a la instancia

Luego puede crear la función de actualización del suscriptor y llamar a la función de devolución de llamada pasada en la función

class Watcher{constructor(vm,key,callback){//将参数都赋值给Watcher实例this.vm=vmthis.key=keythis.callback=callback}update(){this.callback() }
} 

Al reemplazar el contenido del fragmento del documento, debe indicarle al suscriptor cómo actualizarlo, por lo que la instancia del suscriptor se crea cuando la plantilla analiza el valor del nodo para reemplazar el contenido y se pasa la instancia de vm. El valor de índice 1 y el La función de devolución de llamada después de la coincidencia exitosa de exec reemplazará el texto. La declaración de ejecución se copia en la función de devolución de llamada, y se llama a la función de devolución de llamada cuando se notifica al suscriptor de la actualización.

El nodeValue en la función de devolución de llamada debe guardarse con anticipación; de lo contrario, el contenido reemplazado no es una expresión de interpolación sino contenido reemplazado

Luego tenemos que encontrar una manera de almacenar los suscriptores en la matriz de instancias de Dependencia Podemos guardar las instancias en la matriz de suscriptores al construir la instancia de Watcher.

Dependency.temp=this //设置一个临时属性temp 

Agregue un nuevo suscriptor a la matriz de suscriptores y realice la misma operación en todos los suscriptores, luego puede agregar el suscriptor a la matriz de suscriptores al activar get, para activar correctamente la propiedad get correspondiente, debe usar el método reduce para realizar la misma operación en la llave

Puede ver que la consola imprime instancias de Wathcer, y cada instancia es diferente, correspondiente a diferentes valores de atributo

La clase de Dependencia aún no ha creado una instancia, y la matriz de suscriptor interna no existe, así que primero cree una instancia y luego agregue el suscriptor a la matriz de suscriptor.

Al modificar datos, notifique al suscriptor que actualice, llame al método de notificación de dependencia en el conjunto, el método de notificación pasará por la matriz y el suscriptor ejecuta su propio método de actualización para actualizar los datos

Sin embargo, la función de devolución de llamada de actualización carece de configuración de parámetros formales, y los métodos de división y reducción todavía se usan para obtener valores de atributos.

update(){const value =this.key.split('.').reduce((total,current)=>total[current],this.vm.$data)this.callback(value)
} 

La modificación del valor del atributo en la consola se ha modificado correctamente y la página se actualiza automáticamente

Después de completar el enlace del texto, puede enlazar el cuadro de entrada. En vue, puede enlazar a través del modelo v, por lo que debe determinar qué nodo tiene un modelo v. El tipo del nodo del elemento es 1. Usted puede usar nodeName para hacer coincidir el elemento de entrada. Haga un nuevo juicio directamente debajo del nodo de texto del juicio

if(node.nodeType===1&&node.nodeName==='INPUT'){const attr=Array.from(node.attributes)console.log(attr);
} 

El nombre del nodo nodeName es v-model, nodeValue es el nombre, que es el nombre del atributo en los datos

Por lo tanto, la matriz se atraviesa y el modelo v se compara para encontrar el valor del atributo correspondiente de acuerdo con el valor del nodo, y el valor del atributo se asigna al nodo.Al mismo tiempo, para que el suscriptor sepa actualizarse a sí mismo después de actualizar los datos, también se agrega una nueva instancia de Watcher al nodo INPUT.

attr.forEach(i=>{if(i.nodeName==='v-model'){const value=i.nodeValue.split('.').reduce((total,current)=>total[current],vm.$data)node.value=valuenew Watcher(vm,i.nodeValue,newValue=>{node.value=newValue})}
}) 

Modifique el valor del atributo, y la página también se modifica

Lo último que queda es usar la vista para cambiar los datos, solo use addEventListener para agregar el evento de escucha de entrada en el nodo v-model

node.addEventListener('input',e=>{const arr1=i.nodeValue.split('.')const arr2=arr1.slice(0,arr1.length - 1)const final=arr2.reduce((total,current)=>total[current],vm.$data)final[arr1[arr1.length - 1]]=e.target.value
}) 

Por fin

Organicé 75 preguntas de entrevistas de alta frecuencia de JS y brindé respuestas y análisis, lo que básicamente puede garantizar que pueda hacer frente a las preguntas del entrevistador sobre JS.



Amigos necesitados, puede hacer clic en la tarjeta a continuación para recibir y compartir gratis

Supongo que te gusta

Origin blog.csdn.net/web22050702/article/details/128705627
Recomendado
Clasificación