Implementação da ligação bidirecional de dados Vue.js

prefácio

Quando usamos o vue, quando os dados mudam, a interface também será atualizada, mas isso não é uma questão de curso. Quando modificamos os dados, como o vue monitora a mudança de dados e como o vue atualiza a interface quando os dados mudam ? de?

Quando modificamos os dados, o vue monitora as mudanças de dados por meio do Object.definePropertymétodo em es5. Quando os dados mudam 发布订阅模式, eles são atualizados por meio da interface estatística do assinante. Este é um padrão de design

Conforme mostrado na figura abaixo, começando do novo Vue para criar uma instância do Vue, el e data serão passados, e os dados serão passados ​​para um objeto observador, e Object.definpropertyos dados em data serão convertidos em getter/setter para sequestro de dados , e cada atributo em data será criado Uma instância Dep é usada para salvar a instância do observador

E el é passado para compilar, e a instrução é analisada na compilação.Quando os dados em data usados ​​em el são analisados, nosso getter será acionado, para que nosso observador seja adicionado à dependência. Quando os dados forem alterados, ele acionará nosso configurador para emitir uma notificação de dependência, notificar o observador e o observador enviará uma notificação para a visualização após receber a notificação e deixará a visualização atualizar

sequestro de dados

A parte html cria uma tag div com um id de app, que contém as tags span e input. A tag span usa expressões de interpolação e a tag input usa v-model

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

A parte js introduz um arquivo vue.js, onde o código para realizar a ligação bidirecional de dados é escrito e, em seguida, cria uma instância Vue vm e monta os dados na tag div

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

Uma nova instância do Vue obviamente precisa usar um construtor. A classe de definição no código fonte do Vue é definida pela função .Aqui eu uso a classe ES6 para criar esta instância do Vue.

Em seguida, set constructor, o parâmetro formal é definido como obj_instance, como o objeto passado quando uma nova instância Vue é passada, e os dados no objeto passado são atribuídos ao atributo $data na instância

As propriedades do objeto em javascript foram alteradas, precisamos nos informar, podemos atualizar as propriedades alteradas para o nó dom, portanto, ao inicializar a instância, defina uma função de escuta como uma chamada e passe os dados a serem monitorados quando chamando

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

Imprimir esta instância vm

A instância foi criada, mas ainda precisa monitorar cada propriedade em $data. Ela é usada para implementar o monitoramento de dados Object.defineProperty. Object.definePropertyVocê pode modificar as propriedades existentes do objeto. O formato da sintaxe é Object.defineProperty(obj, prop, descriptor)

  • obj: o objeto cujas propriedades devem ser definidas
  • prop: o nome da propriedade para definir ou modificar
  • descritor: O descritor de atributo a ser definido ou modificado

Para monitorar cada propriedade no objeto, usamos Object.keys e foreach para percorrer cada propriedade no objeto e usar Object.defineProperty para monitoramento de dados para cada propriedade

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 disso, você precisa salvar o valor correspondente ao atributo e devolvê-lo na função get, caso contrário, o valor do atributo desaparecerá após a função get e o valor retornado ao atributo ficará indefinido

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

Clicar no nome do atributo em $data acionará a função get

Em seguida, defina a função set e defina o parâmetro formal para o conjunto. Esse parâmetro formal representa o valor do atributo recém-passado e, em seguida, atribua o novo valor do atributo ao valor da variável. Não há necessidade de retornar nada, apenas modifique-o e o return é para acessar o get. Retornado no tempo, get também acessará o valor da variável de valor mais recente após a modificação

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

Mas atualmente apenas get e set são definidos para o atributo da primeira camada de $data, se houver uma camada mais profunda, como

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

Esse tipo de atributo não define obter e definir. Precisamos sequestrar dados camada por camada no atributo, então usamos recursão para nos monitorar novamente e fazer um julgamento condicional antes de percorrer. Se não houver subatributo ou nenhum objeto for detectado, ele encerrará a recursão

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}})})
} 

Tem outro detalhe, se reescrevermos a propriedade content de $data de uma string para um objeto, esse novo objeto não tem get e set

Como não definimos get e set durante a modificação, precisamos chamar a função de escuta em set

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

análise de modelo

Depois de sequestrar os dados, o aplicativo de dados na instância Vue deve ser trazido para a página e uma área de memória temporária deve ser adicionada para atualizar todos os dados antes de renderizar a página para reduzir as operações DOM

Crie uma função de análise, defina dois parâmetros, um é o elemento montado na instância Vue e o outro é a instância Vue, obtenha o elemento na função e salve-o em $el da instância e coloque-o na memória temporária depois de obter o elemento. Necessário para [createDocumentFragment]criar um novo fragmento de documento em branco

Em seguida, adicione os nós filhos de $el à variável de fragmento um por um, a página não possui conteúdo e o conteúdo é armazenado temporariamente no 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);
} 

Agora aplique diretamente o conteúdo que precisa ser modificado ao fragmento do documento e renderize novamente após a aplicação. Você só precisa modificar o nó de texto do nó filho childNodes do fragmento. O tipo do nó de texto é 3. Você pode crie uma função e chame-a para modificar o conteúdo no fragmento

Pode haver nós no nó, então é determinado se o tipo de nó é 3, caso contrário, chame esta função de análise recursivamente

Se o tipo de nó for 3, modifique a operação, mas não é possível modificar o texto de todo o nó. Você só precisa modificar o conteúdo da expressão de interpolação, então você precisa usar a correspondência de expressão regular e salvar o resultado correspondente em uma variável. O resultado correspondente é um array, e o elemento com índice 1 é o elemento que precisamos extrair. Este elemento é a string obtida removendo { {}} e espaços, e então você pode usar diretamente a instância Vue para acessar o valor do atributo correspondente. Após modificação Após retornar, saia e finalize a recursão

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元素里面
} 

O conteúdo da página sai novamente e a expressão de interpolação é substituída pelos dados na instância vm

Inscrever-se no padrão do editor

Embora o sequestro de dados tenha sido realizado e os dados sejam aplicados à página, os dados não podem ser atualizados no momento em que os dados são alterados e o modo de editor de assinatura precisa ser implementado

Primeiro crie uma classe para coletar e notificar os assinantes. Ao gerar uma instância, você precisa de um array para armazenar as informações do assinante, um método para adicionar assinantes a esse array e um método para notificar os assinantes. Chame esse método e retorne Percorra o array de assinantes e deixe os assinantes chamarem seu próprio método de atualização para atualizar

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

Para definir a classe de assinante, você precisa usar as propriedades na instância Vue, você precisa da instância Vue e as propriedades correspondentes da instância Vue e uma função de retorno de chamada como parâmetros e atribuir os parâmetros à instância

Em seguida, você pode criar a função de atualização do assinante e chamar a função de retorno de chamada passada na função

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

Ao substituir o conteúdo do fragmento do documento, você precisa informar ao assinante como atualizá-lo, para que a instância do assinante seja criada quando o modelo analisa o valor do nó para substituir o conteúdo e a instância vm é passada. função de retorno de chamada após a correspondência de exec bem-sucedida substituirá o texto A instrução de execução é copiada para a função de retorno de chamada e a função de retorno de chamada é chamada quando o assinante é notificado da atualização

O nodeValue na função de retorno de chamada deve ser salvo com antecedência, caso contrário, o conteúdo substituído não é uma expressão de interpolação, mas um conteúdo substituído

Então, temos que encontrar uma maneira de armazenar os assinantes no array de instâncias de Dependency.Podemos salvar as instâncias no array de assinantes ao construir a instância do Watcher.

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

Adicione um novo assinante ao array de assinantes e execute a mesma operação em todos os assinantes, então você pode adicionar o assinante ao array de assinantes ao acionar get, para acionar corretamente a propriedade correspondente get , você precisa usar o método de redução para executar a mesma operação na chave

Você pode ver que o console imprime instâncias do Wathcer, e cada instância é diferente, correspondendo a diferentes valores de atributos

A classe Dependency ainda não criou uma instância e o array de assinantes dentro dele não existe, então crie uma instância primeiro e depois adicione o assinante ao array de assinantes

Ao modificar dados, notifique o assinante para atualizar, chame o método de notificação de dependência no conjunto, o método de notificação passará pela matriz e o assinante executará seu próprio método de atualização para atualizar os dados

No entanto, a função de retorno de chamada de atualização carece de definição de parâmetros formais, e os métodos de divisão e redução ainda são usados ​​para obter valores de atributo

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

A modificação do valor do atributo no console foi modificada com sucesso e a página é atualizada automaticamente

Depois de concluir a vinculação do texto, você pode vincular a caixa de entrada. Em vue, você pode vincular por meio do modelo v, portanto, é necessário determinar qual nó possui um modelo v. O tipo do nó do elemento é 1. Você pode usar nodeName para corresponder ao elemento de entrada. Faça um novo julgamento diretamente sob o nó de texto de julgamento

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

O nome do nó nodeName é v-model, nodeValue é o nome, que é o nome do atributo nos dados

Portanto, a matriz é percorrida e o v-model é combinado para encontrar o valor do atributo correspondente de acordo com o nodeValue, e o valor do atributo é atribuído ao nó. Ao mesmo tempo, para que o assinante saiba atualizar-se depois que os dados são atualizados, uma nova instância do Watcher também é adicionada ao nó 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 o valor do atributo e a página também é modificada

A última coisa que resta é usar a view para alterar os dados, basta usar addEventListener para adicionar o evento de escuta de entrada no nó 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
}) 

Finalmente

Organizou 75 perguntas de entrevista de alta frequência sobre JS e forneceu respostas e análises, o que basicamente pode garantir que você possa lidar com as perguntas do entrevistador sobre JS.



Amigos necessitados, você pode clicar no cartão abaixo para receber e compartilhar gratuitamente

Acho que você gosta

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