在Vue中,实现双向数据绑定的原理是通过数据劫持和发布-订阅模式来实现的。
首先,Vue会对data中的所有属性进行劫持,即通过Object.defineProperty()方法来劫持属性的getter和setter方法。当获取属性值时,会触发getter方法,而当修改属性值时,会触发setter方法。
然后,Vue会为每个属性创建一个依赖收集器(Dep),依赖收集器用来存储当前属性的所有订阅者(Watcher)。
当模板中使用了属性的时候,会创建一个Watcher,Watcher会将自身添加到属性的依赖收集器中,并将依赖收集器添加到自身的订阅者列表中。
当属性的值发生改变时,会触发属性的setter方法,setter方法会通知依赖收集器中的所有订阅者,然后订阅者会更新视图。
以下是一个简单的示例代码:
HTML:
<div id="app">
<input v-model="message" />
<p>{{ message }}</p>
</div>
JavaScript:
// 创建Vue实例
let vm = new Vue({
el: '#app',
data: {
message: ''
}
})
// 数据劫持及订阅-发布模式的实现
class Dep {
constructor() {
this.subscribers = []
}
addSubscriber(subscriber) {
this.subscribers.push(subscriber)
}
notify() {
this.subscribers.forEach(subscriber => subscriber.update())
}
}
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm
this.key = key
this.updateFn = updateFn
Dep.target = this
this.vm[this.key]
Dep.target = null
}
update() {
this.updateFn.call(this.vm, this.vm[this.key])
}
}
function defineReactive(obj, key, val) {
let dep = new Dep()
Object.defineProperty(obj, key, {
get() {
dep.addSubscriber(Dep.target)
return val
},
set(newVal) {
if (newVal !== val) {
val = newVal
dep.notify()
}
}
})
}
function observe(obj) {
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
observe(vm)
// 单向绑定的实现
let input = document.querySelector('input')
let p = document.querySelector('p')
// 更新model
input.addEventListener('input', function() {
vm.message = this.value
})
// 更新视图
new Watcher(vm, 'message', function(value) {
p.innerText = value
})
在上述示例中,我们通过Object.defineProperty()方法对data中的message属性进行劫持,并实现了依赖收集器Dep和订阅者Watcher。当input的值发生变化时,会通过setter方法触发依赖收集器的notify()方法,进而更新订阅者的视图。
这样,当input中的值发生变化时,p标签中的内容也会实时更新。这就实现了双向数据绑定。