众所周知,vue2使用Object.defineProperty()劫持数据的get/set,并在数据初始化的时候递归data属性,该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
感兴趣可以继续看原理解析:
VUE原理(二):依赖收集:Dep属性订阅器、Watcher订阅者
VUE原理(三):Compile模板解析指令和vue原理概括
笔者今天的目标就是记录下这个方法是如何驱动视图的。首先看一下这个方法:
语法: Object.defineProperty(obj, prop, descriptor)
第一个参数是要定义的对象;第二个参数是属性名,第三个参数是一个对象,可以在里面写属性。
一、一个简单的例子
首先,先用个简单的例子介绍一下Object.defineProperty()
let person = {
name:'zoie',
age:'18',
address:'江苏'
}
Object.defineProperty(person,'sex',{
value: 'f'
})
此时输出person,笔者截图如下:
这样就给person添加了新的属性sex,且值为f,但是这个值是不可枚举,不可修改,不可删除的,可以使用enumerable来设置为可枚举,使用writable设置为可修改,使用configurable设置为可删除,这里笔者就不去一个个尝试了,感兴趣的可以自己试一下。
let person = {
name:'zoie',
age:'18',
address:'江苏'
}
Object.defineProperty(person,'sex',{
value: 'f',
enumerable:true,
writable:true,
configurable:true
})
还是这个例子,这次我们把sex的值通过get返回,且这个值用变量存起来:
let person = {
name:'zoie',
age:'18',
address:'江苏'
}
let sex = 'f'
Object.defineProperty(person,'sex',{
get(){
return sex
}
})
此时输出person,sex的值是通过get取到的:
既然如此,不妨修改一下sex值为'm':
可以看到sex值已经变了。
与get相同,该方法还有一个存取描述符:set,两者默认值都为undefined。set接收一个值,我们可以把这个值当做新值,那么就像上面的例子一样,只是把手动给sex赋值为'm'这件事通过set来做而已。
let person = {
name:'zoie',
age:'18',
address:'江苏'
}
let sex = 'f'
Object.defineProperty(person,'sex',{
get(){
return sex
},
set(v){
sex = v;
}
})
看一下输出的person:
触发set:
二、Object.defineProperty()操作DOM
通过上面的例子,不难解决这个问题,只需要修改某个属性,然后在set中修改dom内容,在get中返回dom内容即可:
let obj = {};
Object.defineProperty(obj,'name',{
get(){
return document.querySelector('#root').innerText;
},
set(v) {
document.querySelector('#root').innerText = v;
}
});
obj.name = 'zoie'
当执行了obj.name赋值语句,就触发了set,由此将页面上id为root的元素内容改写。
三、Vue中的应用(简洁版)
首先来看一个vue的组件写法:
const app = new vue({
el:'#root',
data:{
name:'zoie',
favourite:{
food:'meat'
}
},
created(){
},
methods:{
}
});
给vue类传入一个对象,这里叫它options好了,option对象挂载了几个属性,这里监听的是data里面的内容,因此,给data的所有属性都进行数据劫持,这样每次修改都会触发各自的set,从而被感知。
class vue {
constructor(options){
this.$options = options;
this.$data = options.data;
this.observe(this.$data);
}
observe(obj){
if(!obj || typeof obj !== 'object'){
return
}
Object.keys(obj).forEach((val) => {
this.defineReactive(obj,val,obj[val]);
})
}
defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
get(){
return val
},
set(v) {
if(v === val){
return;
}
val = v;
console.log('更新:'+ JSON.stringify(v))
}
});
}
}
经过一次数据劫持后,此时修改data下的属性,可以在控制台看到有更新:
app.$data.name = 'zzz';
app.$data.favourite = {food:'apple'};
但是!当修改app.$data.favourite.food = 'orange'时,却没有触发更新,这是因为food在favourite对象下,因此,vue需要层层递归,给每一个属性都加上数据劫持:
defineReactive(obj,key,val){
this.observe(val);
//...
}
现在,修改app.$data.favourite.food = 'orange',也可以在控制台看到更新了。
总结
本篇主要介绍了Object.defineProperty()方法的使用和在vue中的简洁版使用,从中可以看出,vue的原理是采用数据劫持结合观察者模式,在observe()中,对data中所有属性进行劫持,又递归对所有属性是对象的再次进行数据劫持,确保每一个属性更新后都能触发set。