一.写这篇的原因
自己在学Vue的时候一直觉得Vue的双向绑定很有趣,在看了《深入浅出Vue.js》这本书后算是对Vue双向绑定原理有了一个初步的认识,在学习过程中,由于水平有限,理解过程中有一些地方很挣扎,所以分享出来,希望对看到这篇文章的人有所帮助
二.读文章之前你需要了解
首先你需要使用过Vue,至少使用过Vue做过项目或者Demo。如果你不清楚Object.defineProperty的使用以及发布订阅模式的话,接下来我会尽可能的将清楚它的使用
三.数据劫持Object.defineProperty与发布订阅
Object.defineProperty(obj,prop,descriptor)的三个参数分别是
obj:被劫持的对象
prop:要定义或者修改属性的名称
descriptor:属性的描述符
详细的介绍可以参考MDN
虽然有缺陷,但是这个属性确实能侦测到一个对象的属性变化
以Vue文档上的例子作为参考

let data={
message:''
}
var val
Object.defineProperty(data,'message',{
enumerable:true, //可枚举
configurable:true, //可配置
get:()=>{
return val;
},
set:(newVal)=>{
if(newVal!==val) val=newVal
console.log('message被改变')
}
})
data.message='helloworld'
既然这样,我们可以对Object.defineProperty进行包装
function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get:function(){
return val
},
set:function (newVal) {
if(val===newVal) return
val=newVal
}
})
}
通过对Object.defineProperty进行包装我们可以监听到对象属性的变化,但是这似乎并没有什么作用,所以还需要Dep类和Watcher实现发布订阅的设计模式来配合
Vue2.0中采用中的粒度大小,每一个组件对应一个Watcher,每一个组件也会对应一个虚拟Dom,而每一个组件创建后就会被收集在Dep类中。而Watcher是主动将自己添加到Dep中被收集的,这也是体现发布订阅思想的地方。具体看下面代码
//Dep类中,
addSub(sub){
if(Dep.target){
this.subs.push(sub)
}
}
//Watcher类中
Dep.target=this
每生成一个Watcher实例,Watcher会将自己(this)设置到Dep.target中,从而触发Dep的逻辑从而让Dep收集自己。
总的来说,当组件创建后,会触发get从而被Dep中的数组收集,当数据改变的时候,会触发set通知到Watcher,Watcher通过虚拟Dom和diff算法来对比哪里有所改变,进而更改视图。
以上就是双向绑定的原理,读完是否能有所了解呢。觉得不错点个关注吧
下面是具体的各个类的方法,可以供给读者参考
function defineReactive(obj,key,val){
if(typeof obj==='object'){
new Obersver(obj)
}
let dep=new Dep();
Dep.target=undefined;
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get:function(){
dep.depend();
return val
},
set:function (newVal) {
if(val===newVal) return
val=newVal
dep.notify()
}
})
}
//Dep类用于收集到哪些地方用到了Vue.data中的属性,
//实际上每一个使用到属性的地方都是一个Watcher类
class Dep {
constructor() {
this.subs=[];
}
addSub(sub){
this.subs.push(sub)
}
removeSub(sub){
remove(this.subs,sub)
}
depend(){
//收集依赖
if(Dep.target){
this.addSub(Dep.target)
}
}
//在实际上Dep类存的每一项都是一个Watcher的实例,所以能使用update方法
//发布订阅的设计模式是在这一点上体现的
//notify会让subs数组中存的watcher调用他的update方法使用回调函数来重新计算值
notify(){
//对subs数组进行深拷贝
const subsCopy=this.subs.slice();
for(let i=0;i<subsCopy.length;i++){
subsCopy[i].update()
}
}
}
function remove(arr,item) {
if(arr.length){
let index=arr.indexOf(item);
if(index>-1){
return arr.splice(index,1)
}
}
}
class Watcher{
constructor(vm,exp,cb) {
this.vm=vm
this.getter=exp();
this.cb=cb;
this.value=this.get();
}
get(){
Dep.target=this;
const value=this.getter.call(this.vm,this.vm);
Dep.target=undefined;
return value
}
update(){
const oldValue=this.value;
this.value=this.get();
this.cb.call(this.vm,this.value,oldValue)
}
}
//以上只能侦测到一个属性,所以需要一个Observer类。Obersver类是将一个对象(obj)的所有子属性都能够被侦测到,代码逻辑比较简单,看一下就能够理解
class Obersver {
constructor(value) {
this.value=value
if(!Array.isArray(value)){
this.walk(value)
}
}
walk(obj){
const keys=Object.keys(obj)
for(let i=0;i<keys.length;i++){
defineReactive(obj,keys[i],obj[keys[i]])
}
}
}