Vue에서 양방향 데이터 바인딩을 구현하는 방법은 무엇입니까? 다음 글에서는 Vue.js 데이터 양방향 바인딩 구현 방법을 소개하겠습니다. 도움이 되셨으면 좋겠습니다!
vue를 사용하면 데이터가 변경되면 인터페이스도 업데이트되지만 이는 당연하지 않습니다. 데이터를 수정할 때 vue는 데이터 변경을 어떻게 모니터링합니까? Vue는 인터페이스를 새로 고치나요?
데이터를 수정할 때 vue는 es5의 Object.defineProperty
메서드를 통해 데이터 변경 사항을 모니터링하며 publish-subscribe 모드
통계 구독을 사용합니다. 디자인 패턴인 사용자 인터페이스가 새로워졌습니다. [학습 동영상 공유: vue 동영상 튜토리얼Object.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
를 사용하여 데이터를 getter/setter로 변환합니다. 데이터의 속성은 Dep 인스턴스를 사용하여 감시자 인스턴스를 저장하고 el이 컴파일로 전달되고 명령이 컴파일에서 구문 분석될 때 getter가 트리거됩니다. 따라서 감시자를 종속성에 추가합니다. 데이터가 변경되면 setter가 종속성 알림을 발행하고 감시자에게 알리도록 트리거됩니다. 알림을 받은 후 감시자는 뷰에 알림을 보내고 뷰가 업데이트되도록 합니다
html 이 부분은 app의 ID로 div 태그를 생성하는데, 여기에는span 태그와 입력 태그가 포함되어 있으며, 입력 태그는 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를 사용합니다. 양방향 데이터 바인딩을 구현하기 위한 vue.js 파일이 여기에 작성되었습니다. 그런 다음 Vue 인스턴스 vm을 만들고 div 태그에 데이터를 마운트합니다.
set(newValue){ console.log(key,value,newValue); value = newValue }새로운 Vue 인스턴스, 생성자가 필요하다는 것이 분명합니다. 클래스는
function 을 사용하여 vue의 소스 코드에서 정의됩니다. 여기서는 ES6의 class
을 사용하여 이 Vue 인스턴스 를 생성한 다음생성자
를 설정하고 형식 매개변수가 설정됩니다. 새로운 Vue 인스턴스를 생성할 때 전달된 객체로 obj_instance에 전달하고 전달된 객체의 데이터를 인스턴스의 $data 속성에 할당합니다🎜🎜JavaScript에서 객체의 속성이 변경되면 이를 알려줘야 합니다. 변경된 속성을 dom 노드에 업데이트할 수 있으므로 인스턴스를 초기화합니다. 호출 시 청취 기능을 정의하고 모니터링해야 하는 데이터를 전달하세요🎜obj:{ a:'a', b:'b' }🎜이 인스턴스를 인쇄하세요. vm🎜🎜🎜🎜인스턴스가 생성되었지만 여전히 $data의 각 속성에 대해 모니터링해야 합니다. 이는 데이터 모니터링을 구현하는 데 사용됩니다.
Object.defineProperty
는 개체의 기존 속성을 수정할 수 있습니다. 구문 형식은 🎜Object.defineProperty(obj, prop, descriptor)🎜🎜Object.defineProperty
이전에 각 속성에 대해 Object.defineProperty를 사용하여 데이터🎜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 } }) }) }🎜를 모니터링합니다. 속성에 해당하는 값을 저장한 다음 get 함수에 반환해야 합니다. 그렇지 않으면 get 함수에 도달한 후입니다. , 속성 값은 사라지고 속성에 반환된 값은 정의되지 않습니다🎜
set(newValue){ console.log(key,value,newValue); value = newValue Observer(newValue) }🎜$data에서 속성 이름을 클릭하면 get 함수가 트리거됩니다🎜🎜🎜🎜🎜 그런 다음 set 함수를 설정하고 이 형식 매개변수는 새로 전달된 속성 값을 나타내며 이 새 속성 값을 변수 값에 할당합니다. 수정 후 get에 액세스하면 반환이 반환됩니다. , get은 최신 값 변수 value🎜
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); }🎜🎜🎜🎜에도 액세스합니다. 하지만 현재는 $data의 첫 번째 레이어 속성에 대해 get 및 set만 설정되어 있습니다. 🎜
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元素里面 }🎜와 같은 더 깊은 레이어에는 get 및 set이 없습니다. set set. 속성의 데이터를 레이어별로 하이재킹해야 하므로 🎜recursion🎜을 사용하여 다시 모니터링하고 순회하기 전에 조건부 판단을 내립니다. 하위 속성이 감지되지 않거나 객체가 감지되지 않으면 재귀가 종료됩니다🎜
class Dependency{ constructor(){ this.subscribers=[] //存放订阅者的信息 } addSub(sub){ this.subscribers.push(sub) //将订阅者添加到这个数组里 } notify(){ this.subscribers.forEach(sub=>sub.update()) //遍历订阅者的数组,调用自身的update函数进行更新 } }🎜또 다른 세부 사항이 있습니다. $data의 콘텐츠 속성을 문자열에서 객체로 다시 쓰면 이 새 객체에는 get 및 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 })
위 내용은 Vue에서 양방향 데이터 바인딩을 구현하는 방법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!