Vue.js 的双向数据绑定是通过数据劫持结合发布者-订阅者模式实现的。
具体思路是:
- 通过Object.defineProperty()来劫持各个属性的setter,getter。
- 实现一个Dep类来存放订阅者,并在getter中收集订阅者,在setter中通知订阅者更新视图。
- 视图渲染时在对应的指令中添加订阅者,即Watcher对象。
- 数据变动时通过发布者-订阅者模式通知各个订阅者,订阅者再响应更新视图。
整个流程可以概括为:数据改变 -> 发布者通知 -> 订阅者响应 -> 视图更新。
例子:
<div id="app">
<p>{{ message }}</p>
<input v-model="message">
</div>
var data = {
message: 'Hello'
}
var vm = new Vue({
el: '#app',
data: data
})
当用户输入值更新 message 时,视图会自动渲染。实现原理如下:
- 初始化时,将message属性用Object.defineProperty()劫持:
var data = {
message: 'Hello'
}
var vm = new Vue({
el: '#app',
data: data
})
Object.defineProperty(data, 'message', {
get: function() {
console.log('get')
Dep.target && dep.addSub(Dep.target)
return message
},
set: function(newValue) {
console.log('set')
message = newValue
dep.notify()
}
})
- 订阅者Watcher被添加到订阅器dep中:
var watcher = new Watcher(vm, 'message', function(val) {
element.value = val
})
- 当message发生变化时,通知订阅器dep,然后订阅器dep调用订阅者的update()方法进行视图渲染:
set: function(newValue) {
console.log('set')
message = newValue
dep.notify() // 通知订阅器
}
// 订阅器调用更新方法
dep.notify() {
subs.forEach(sub => {
sub.update() // 更新视图
})
}
Vue 的双向数据绑定的原理在于通过数据劫持和发布-订阅模式实现数据和视图的同步更新。