1.什么是双向数据绑定?
单向绑定非常简单,就是把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。有单向数据绑定就有双向数据绑定。那什么是双向数据绑定呢?如果用户更新了View,Model的数据也自动被更新了,这种情况就是双向数据绑定,如图:
2.双向数据绑定一般应用在什么场景?
双向数据绑定一般应用在表单中,用来提交数据。其他基本上是使用单向绑定就可以了
3.双向数据绑定是如何实现的?
在Vue中双向数据绑定是通过数据劫持结合发布者-订阅者模式来实现的。这个时候问题又来了,具体是如何实现的呢?
双向数据绑定关键点在于我们如何知道数据发生变化,当我们知道数据何时发生改变,只要数据数据发生改变,我们就去通知更新视图就可以了
在JS中为我们提供了Object.defineProperty方法,在Object.defineProperty方法,为我们提供了get和set方法,通过get方法我们可以监听到数据赋值给了谁,通过set方法我们可以监听到数据修改。
当我们通过Object.defineProperty方法知道数据什么时候发生改变,那么当数据发生改变我们去通知视图就可以了。但是问题又来了,视图那么大,我们该通知谁去改变呢?
当a用到了b,那么b发生改变的时候我们需要通知a。“你依赖的b发生了改变,你需要更新了”这个过程就叫依赖收集
那么我们什么时候需要依赖收集,什么时候需要依赖更新呢?
在Object.defineProperty方法中提供了get,set方法。get可以监听到数据赋值给了谁,set监听到数据的修改,那么我们就可以在get方法中收集依赖,set方法中通知依赖更新
当我们知道在get方法中收集依赖,那么我们又该如何收集依赖呢?
这个时候时候依赖管理器Dep就产生了, Dep类的源码如下:
export default class Dep {
constructor () {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
// 删除一个依赖
removeSub (sub) {
remove(this.subs, sub)
}
// 添加一个依赖
depend () {
if (window.target) {
this.addSub(window.target)
}
}
// 通知所有依赖更新
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
在Dep类中实例了几个方法对依赖进行添加,删除和通知依赖更新的操作,我们有了依赖管理器后,我们就可以在getter中收集依赖,在setter中通知依赖更新了
现在我们知道可以知道了什么是依赖,什么时候收集依赖,何时通知依赖更新,那么这个依赖到底是谁呢,代码中如何描述呢?
这个时候Vue中为我们提供了Watcher类,如下:
export default class Watcher {
constructor (vm,expOrFn,cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn)
this.value = this.get()
}
get () {
window.target = this;
const vm = this.vm
let value = this.getter.call(vm, vm)
window.target = undefined;
return value
}
update () {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
什么是Watcher类呢,Watcher类的实例就是上面说的那个谁了,谁用到了数据,谁就是依赖,我们就为谁创建一个Watcher类
收集依赖的代码如下:
function defineReactive (obj,key,val) {
if (arguments.length === 2) {
val = obj[key]
}
if(typeof val === 'object'){
new Observer(val)
}
const dep = new Dep() //实例化一个依赖管理器,生成一个依赖管理数组dep
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get(){
dep.depend() // 在getter中收集依赖
return val;
},
set(newVal){
if(val === newVal){
return
}
val = newVal;
dep.notify() // 在setter中通知依赖更新
}
})
}
4.不足之处
虽然我们通过Object.defineProperty实现了双向数据绑定,但是这个方法只能观测到Object数据的取值和设置值。
当然vue也为我们提供了两个全局的方法Vue.set和Vue.delete来解决问题