原理:vue.js采用的是数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
一:数据劫持
在ES5中有Object.defineProperty()方法,它能监听各个属性的set和get方法。
let data ={name:'px'}
Object.defineProperty(data,'name',{
set: function(newValue) {
console.log('更新了data的name:' + newValue);
},
get: function() {
console.log('获取data数据name');
}
})
data.name="sj";//更新了data的name:sj
data.name;//获取data数据name
当执行"data.name='sj'"触发set方法,执行"data.name"触发get方法。
vue就是采用了此方法,对data的属性进行劫持。
模拟实现其劫持的过程如下:
let data ={name:'px',age:'20'}
function observe(data){
//获取所有的data数据对象中的所有属性进行遍历
//Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组
const keys = Object.keys(data)
for (let i = 0; i < keys.length; i++) {
let val = data[keys[I]];
defineReactive(data, keys[i],val)//为每个属性增加监听
}
}
function defineReactive(obj,key,val){
Object.defineProperty(obj, key, {
enumerable: true,//可枚举
configurable: true,//可配置
get: function reactiveGetter () {
//模拟get劫持
console.log("get劫持");
return val;
},
set: function reactiveSetter (newVal) {
//模拟set劫持
console.log("set劫持,新值:"+newVal);
val = newVal;
}
})
}
observe(data);
data.name="sj";//set劫持,新值:sj
console.log(data.name);//get劫持,sj
data模拟vue.data对象,observer中对data的属性进行遍历,调用defineReactive方法对每个属性的get和set方法进行劫持。
那么监听数据变化后如何通知呢,请看下面。
二:发布与订阅
vue在双向绑定的设计中,采用的是观察者-订阅者模式,前面所讲的数据劫持,其实就是为属性创建了一个观察者对象,监听数据的变化。
属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。