Vue で双方向データ バインディングを実装するにはどうすればよいですか? Vue.jsのデータ双方向バインディングの実装方法は以下の記事で紹介していますので、参考になれば幸いです!
vue を使用すると、データが変更されるとインターフェースも更新されますが、これは当然のことではありません。 ? データは変更されますが、データが変更されたときに Vue はどのようにインターフェイスを更新しますか?
データを変更すると、vue は es5 の Object.defineProperty
メソッドを通じてデータの変更を監視します。データが変更されると、パブリッシュおよびサブスクライブ モード
を使用します。統計サブスクライバ インターフェイスが更新されました。これは設計パターンです。 [学習ビデオ共有: vue ビデオ チュートリアル 、web フロントエンド ビデオ ]
以下に示すように、新しい Vue から開始して、Vue インスタンス、el、データを作成します。 , データが渡されます オブザーバー オブジェクトが渡され、Object.definproperty
を使用してデータ内のデータをデータ ハイジャック用のゲッター/セッターに変換します。データ内の各プロパティは Dep インスタンスを作成しますウォッチャー インスタンスを保存する
El がコンパイルに渡され、命令がコンパイルで解析されます。data 内のデータが el に解析されると、ゲッターがトリガーされ、それによってウォッチャーが依存関係に追加されます。データが変更されると、セッターがトリガーされて依存関係通知を発行し、ウォッチャーに通知します。通知を受信した後、ウォッチャーはビューに通知を送信し、ビューを更新させます
#データ ハイジャックHTML 部分は、span タグと input タグを含む id アプリを使用して div タグを作成します。span タグは補間式を使用し、input タグは v-model
<div class="container" id="app"> <span>内容:{{content}}</span> <input type="text" v-model="content"> </div>
const vm=new Vue({ el:'#app', data:{ content:'请输入开机密码' } })
function を使用して定義されていますここでは ES6 の class を使用してこの Vue インスタンス
を作成し、次にconstructor を設定し、新しい Vue インスタンスの作成時に渡されるオブジェクトとして仮パラメータを obj_instance に設定し、渡されたオブジェクト内のデータをインスタンス
## の $data 属性に割り当てます。 #In JavaScript オブジェクトのプロパティが変更された場合、変更されたプロパティを dom ノードに更新できるように通知する必要があるため、インスタンスの初期化時に listen 関数を呼び出しとして定義し、呼び出し時に関数を渡します監視する必要があるデータ内
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、記述子)
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(){},//修改该属性的时候触发 }) }) }
In
Object.defineProperty属性に対応する値を保存し、それを get 関数で返す必要があります。そうでない場合は、get 関数に到達した後、属性はなくなり、属性に返される値は未定義になります<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">let value=data_instance[key]
Object.defineProperty(data_instance,key,{
enumerable:true,
configurable:true,
get(){
console.log(key,value);
return value
},
set(){}
})</pre><div class="contentsignin">ログイン後にコピー</div></div>
Click $data の属性名をクリックすると get 関数がトリガーされます
#次に、set 関数を設定し、set の仮パラメータを設定します。この仮パラメータは新しく渡された属性値を表し、この新しい属性値を変数値に割り当てます。何も返す必要はなく、変更するだけです。 get にアクセスすると return が返されますが、変更後は get は最新の値変数 value
set(newValue){ console.log(key,value,newValue); value = newValue }
にもアクセスしますが、現在は get と set のみが最初の層の属性に設定されています$data.
obj:{ a:'a', b:'b' }
のような深い層がある場合、get と set は設定されません。層ごとに属性へのデータ ハイジャックを実行する必要があるため、
recursion を使用しますトラバースする前に自分自身を再度監視し、条件付きの判断を行うためです。サブプロパティがない場合、またはオブジェクトが検出されない場合は、再帰を終了します 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和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 中国語 Web サイトの他の関連記事を参照してください。