Heim > Artikel > Web-Frontend > Lassen Sie uns darüber sprechen, wie Sie die bidirektionale Datenbindung in Vue implementieren
Wie implementiert man eine bidirektionale Datenbindung in
Vue? Der folgende Artikel stellt Ihnen die Implementierungsmethode der bidirektionalen Datenbindung von Vue.j vor. Ich hoffe, er wird Ihnen hilfreich sein!
Wenn wir Vue verwenden und sich die Daten ändern, wird die Schnittstelle ebenfalls aktualisiert. Dies ist jedoch nicht selbstverständlich. Wenn wir die Daten ändern, wie überwacht Vue die Datenänderungen? Aktualisiert Vue die Benutzeroberfläche?
Wenn wir die Daten ändern, überwacht vue die Datenänderungen über die Methode Object.defineProperty
in es5. Wenn sich die Daten ändern, verwendet es das Statistikabonnement Publish-Subscribe-Modus
Die Benutzeroberfläche wurde aktualisiert, was einem Designmuster entspricht. [Teilen von Lernvideos: vue Video-TutorialObject.defineProperty
方法来监听数据的改变的,当数据发生了改变通过发布订阅模式
统计订阅者界面发生了刷新,这是一种设计模式。【学习视频分享:vue视频教程、web前端视频】
下图,从new Vue开始创建Vue实例,会传入el和data,data会被传入一个观察者对象,利用Object.definproperty
将data里数据转化成getter/setter进行数据劫持,data里的每个属性都会创建一个Dep实例用来保存watcher实例
而el则传入compile,在compile里进行指令的解析,当解析到el中使用到data里的数据会触发我们的getter,从而将我们的watcher添加到依赖当中。当数据发生改变的时候会触发我们的setter发出依赖通知,通知watcher,watcher接受到通知后去向view发出通知,让view去更新
html部分创建一个id为app的div标签,里面有span和input标签,span标签使用了插值表达式,input标签使用了v-model
<div class="container" id="app"> <span>内容:{{content}}</span> <input type="text" v-model="content"> </div>
js部分引入了一个vue.js文件,实现数据双向绑定的代码就写在这里面,然后创建Vue实例vm,把数据挂载到div标签上
const vm=new Vue({ el:'#app', data:{ content:'请输入开机密码' } })
new了一个Vue实例很明显需要用到构造函数,在vue的源码里定义类是使用了function来定义的,这里我使用ES6的class来创建这个Vue实例
然后设置constructor
,形参设为obj_instance,作为new一个Vue实例的时候传入的对象,并把传进来的对象里的data赋值给实例里的$data属性
在javascript里对象的属性发生了变化,需要告诉我们,我们就可以把更改后的属性更新到dom节点里,因此初始化实例的时候定义一个监听函数作为调用,调用的时候传入需要监听的数据
class Vue{//创建Vue实例 constructor(obj_instance){ this.$data=obj_instance.data Observer(this.$data) } } function Observer(data_instance){//监听函数 }
打印一下这个实例vm
实例已经创建出来了但是还需要为$data里的每一个属性进行监听,要实现数据监听就用到了Object.defineProperty
,Object.defineProperty
可以修改对象的现有属性,语法格式为Object.defineProperty(obj, prop, descriptor)
监听对象里的每一个属性,我们使用Object.keys和foreach遍历对象里的每一个属性并且对每一个属性使用Object.defineProperty进行数据监听
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(){},//修改该属性的时候触发 }) }) }
在Object.defineProperty
, web front-end video
Object.definproperty
, um die Daten in Getter/Setter für die Datenentführung zu konvertieren Die Eigenschaft in data erstellt eine Dep-Instanz, die zum Speichern der Watcher-Instanz verwendet wird, und el wird an die Kompilierung übergeben, und die Anweisungen werden in der Kompilierung analysiert. Wenn die Daten in den Daten in el analysiert werden, wird unser Getter ausgelöst. Dadurch wird unser Beobachter zur Abhängigkeit hinzugefügt. Wenn sich die Daten ändern, löst dies unseren Setter aus, um eine Abhängigkeitsbenachrichtigung auszugeben und den Beobachter zu benachrichtigen. Nach Erhalt der Benachrichtigung sendet der Beobachter eine Benachrichtigung an die Ansicht und lässt die Ansicht aktualisieren
html Der Teil erstellt ein div-Tag mit der ID der App, das Span- und Eingabe-Tags enthält, und das Eingabe-Tag verwendet v-model
let value=data_instance[key] Object.defineProperty(data_instance,key,{ enumerable:true, configurable:true, get(){ console.log(key,value); return value }, set(){} })
js Hier wird eine vue.js-Datei zum Implementieren der bidirektionalen Datenbindung geschrieben. Erstellen Sie dann eine Vue-Instanz-VM und hängen Sie die Daten an das div-Tag ein.
set(newValue){ console.log(key,value,newValue); value = newValue }Neue Vue-Instanz. Es ist offensichtlich, dass ein Konstruktor benötigt wird. und die Klasse wird im Quellcode von Vue mithilfe der
Funktion
zum Definieren definiert. Hier verwende ich dieKlasse von ES6, um diese Vue-Instanz zu erstellen
und stelle dann denKonstruktor
ein, der formale Parameter ist festgelegt zu obj_instance, als beim Erstellen einer neuen Vue-Instanz übergebenes Objekt, und Weisen Sie die Daten im übergebenen Objekt dem $data-Attribut in der Instanz zu.Wenn sich die Attribute des Objekts in JavaScript ändern, müssen wir uns dies mitteilen dass wir die geänderten Attribute auf den Dom-Knoten aktualisieren können, also die Instanz initialisieren. Definieren Sie eine Abhörfunktion als Aufruf. Übergeben Sie beim Aufruf die Daten, die überwacht werden müssen🎜obj:{ a:'a', b:'b' }🎜Drucken Sie diese Instanz vm🎜🎜🎜🎜Die Instanz wurde erstellt, funktioniert aber immer noch muss für jedes Attribut in $data überwacht werden. Dies wird verwendet, um die Datenüberwachung zu implementieren.
Object.defineProperty
kann die vorhandenen Eigenschaften des Objekts ändern Das Syntaxformat ist 🎜Object.defineProperty(obj, prop, descriptor)🎜🎜function Observer(data_instance){ //递归出口 if(!data_instance || typeof data_instance != 'object') return Object.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 } }) }) }🎜before
Object.defineProperty
Sie müssen den dem Attribut entsprechenden Wert speichern und ihn dann in der Get-Funktion zurückgeben, andernfalls nach Erreichen der Get-Funktion , der Wert des Attributs geht verloren und der an das Attribut zurückgegebene Wert wird undefiniert🎜set(newValue){ console.log(key,value,newValue); value = newValue Observer(newValue) }🎜Klicken Sie auf den Attributnamen in $data. Die Get-Funktion wird ausgelöst. Legen Sie dann die Set-Funktion fest und legen Sie fest Formale Parameter für set. Dieser formale Parameter stellt den neu übergebenen Attributwert dar und weist dann diesen neuen Attributwert dem Variablenwert zu. Es ist nicht erforderlich, etwas zurückzugeben. Nach der Änderung wird die Rückgabe zurückgegeben, wenn auf get zugegriffen wird , get greift auch auf den neuesten Wert der Variablen value zu 🎜
class Vue{ constructor(obj_instance){ this.$data=obj_instance.data Observer(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); }🎜🎜🎜🎜Aber derzeit sind nur get und set für das Attribut der ersten Ebene von $data festgelegt. Die tieferen Ebenen wie 🎜
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元素里面 }🎜 verfügen nicht über get und set Wir müssen die Daten in den Attributen Schicht für Schicht kapern, also verwenden wir 🎜Rekursion🎜, um uns selbst erneut zu überwachen und vor dem Durchlaufen bedingte Urteile zu fällen. Die Rekursion wird beendet, wenn die Untereigenschaft oder kein Objekt erkannt wird 🎜Es gibt noch ein weiteres Detail: Wenn wir das Inhaltsattribut von $data von einer Zeichenfolge in ein Objekt umschreiben, verfügt dieses neue Objekt nicht über get und set🎜
因为我们在修改的时候根本没有设置get和set,因此在set里要调用监听函数
set(newValue){ console.log(key,value,newValue); value = newValue Observer(newValue) }
劫持数据后就要把Vue实例里的数据应用带页面上,得要加一个临时内存区域,将所有数据都更新后再渲染页面以此减少dom操作
创建一个解析函数,设置2个参数,一个是Vue实例里挂载的元素,另一个是Vue实例,在函数里获取获取元素保存在实例了的$el里,获取元素后放入临时内存里,需要用到[createDocumentFragment]
创建一个新的空白的文档片段
然后把$el的子节点一个一个加到fragment变量里,页面已经没有内容了,内容都被临时存在fragment里了
class Vue{ constructor(obj_instance){ this.$data=obj_instance.data Observer(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); }
现在直接把需要修改的内容应用到文档碎片里面,应用后重新渲染,只需修改了fragment的childNodes子节点的文本节点,文本节点的类型是3,可以创建一个函数并调用来修改fragment里的内容
节点里面可能还会有节点,因此判定节点类型是否为3,不是就递归调用这个解析函数
节点类型为3就进行修改操作,但也不行把整个节点的文本都修改,只需修改插值表达式的内容,因此要使用正则表达式匹配,将匹配的结果保存到变量里,匹配的结果是一个数组,而索引为1的元素才是我们需要提取出来的元素,这个元素就是去除了{{}}和空格得到的字符串,然后就可以直接用Vue实例来访问对应属性的值,修改完后return出去结束递归
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元素里面 }
页面的内容又出来了,插值表达式替换成了vm实例里的数据
虽然进行了数据劫持,和将数据应用到页面上,但是数据发生变动还不能及时更新,还需要实现订阅发布者模式
首先创建一个类用来收集和通知订阅者,生成实例的时候需要有一个数组存放订阅者的信息,一个将订阅者添加到这个数组里的方法和一个通知订阅者的方法,调用这个方法就回去遍历订阅者的数组,让订阅者调用自身的update方法进行更新
class Dependency{ constructor(){ this.subscribers=[] //存放订阅者的信息 } addSub(sub){ this.subscribers.push(sub) //将订阅者添加到这个数组里 } notify(){ this.subscribers.forEach(sub=>sub.update()) //遍历订阅者的数组,调用自身的update函数进行更新 } }
设置订阅者类,需要用到Vue实例上的属性,需要Vue实例和Vue实例对应的属性和一个回调函数作为参数,将参数都赋值给实例
然后就可以创建订阅者的update函数,在函数里调用传进来的回调函数
class Watcher{ constructor(vm,key,callback){//将参数都赋值给Watcher实例 this.vm=vm this.key=key this.callback=callback } update(){ this.callback() } }
替换文档碎片内容的时候需要告诉订阅者如何更新,所以订阅者实例在模板解析把节点值替换内容的时候创建,传入vm实例,exec匹配成功后的索引值1和回调函数,将替换文本的执行语句复制到回调函数里,通知订阅者更新的时候就调用这个回调函数
回调函数里的nodeValue要提前保存,不然替换的内容就不是插值表达式而是替换过的内容
然后就要想办法将订阅者存储到Dependency实例的数组里,我们可以在构造Watcher实例的时候保存实例到订阅者数组里
Dependency.temp=this //设置一个临时属性temp
将新的订阅者添加到订阅者数组里且还要将所有的订阅者都进行同样的操作,那么就可以在触发get的时候将订阅者添加到订阅者数组里,为了正确触发对应的属性get,需要用reduce方法对key进行同样的操作
可以看到控制台打印出了Wathcer实例,每个实例都不同,都对应不同的属性值
Dependency类还没创建实例,里面的订阅者数组是不存在的,所以要先创建实例再将订阅者添加到订阅者数组里
修改数据的时候通知订阅者来进行更新,在set里调用dependency的通知方法,通知方法就会去遍数组,订阅者执行自己的update方法进行数据更新
但是update调用回调函数缺少设定形参,依旧使用split和reduce方法获取属性值
update(){ const value =this.key.split('.').reduce( (total,current)=>total[current],this.vm.$data ) this.callback(value) }
在控制台修改属性值都修改成功了,页面也自动更新了
完成了文本的绑定就可以绑定输入框了,在vue里通过v-model进行绑定,因此要判断哪个节点有v-model,元素节点的类型是1,可以使用nodeName来匹配input元素,直接在判断文本节点下面进行新的判断
if(node.nodeType===1&&node.nodeName==='INPUT'){ const attr=Array.from(node.attributes) console.log(attr); }
节点名字nodeName为v-model,nodeValue为name,就是数据里的属性名
因此对这个数组进行遍历,匹配到了v-model根据nodeValue找到对应的属性值,把属性值赋值到节点上,同时为了在数据更新后订阅者知道更新自己,也要在INPUT节点里新增Watcher实例
attr.forEach(i=>{ if(i.nodeName==='v-model'){ const value=i.nodeValue.split('.').reduce( (total,current)=>total[current],vm.$data ) node.value=value new Watcher(vm,i.nodeValue,newValue=>{ node.value=newValue }) } })
修改属性值,页面也作出修改
最后剩下用视图改变数据,在v-model的节点上使用addEventListener增加input监听事件就行了
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 })
Das obige ist der detaillierte Inhalt vonLassen Sie uns darüber sprechen, wie Sie die bidirektionale Datenbindung in Vue implementieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!