本文是要实现对象使用Object.defineProperty重写对象,例
//例把下面对象obj let obj={a:1}; //修改成以下写法 let aValue=1; Object.defineProperty(obj,a,{ get(){ return aValue; }, set(newValue){ if(aValue===newValue) return; aValue=newValue; } })
这样在对对象属性进行获取、修改时,都能在get、set中监测到对象属性变化,但不能对对象属性值为数组进行监测,需要重写数组方法,下一篇
首先介绍Object.defineProperty:
作用是给对象的属性设置get、set、value、writable(可重写)、configurable(可配置、删除)、enumerable(可遍历)等
其中writable不能和get、set一起出现,get、set不能和value一块出现
Object.defineProperty(obj,key,{ value,//属性值 writable,//boolean configurable,//boolean enumerable,//boolean get(){},//不能和writable、value一块配置 set(newValue){}//不能和writable、value一块配置 })
******************************正文开始********************************
在使用vue时
//在这里只对vue的data进行处理 let vm = new Vue({ data:function(){ return { a:1, b:{c:2} } } })
可以把以上改成
let options={
data:function(){
return {
a:1,
b:{c:2}
}
}
}
let vm = new Vue(options)
模仿以上,创建一个函数命名为Vue,
function Vue(options){ }
问题:Vue如何将options里的data转成Object.defineProperty的写法
解剖问题:
1、在new Vue()时即完成了到Object.defineProperty的转换
解决:在new Vue()时执行了一个方法,命名为_init
function Vue(options){ this._init(options); } Vue.prototype._init=function(options){ console.log("new的时候执行了_init") }
解析 new Vue()会将所有的属性实例化,所以this_init()也会被执行,执行的时候会在对象的原型链中查找同名方法,所以会找到Vue.prototype._init方法,我们可以在_init中对options进行处理
开始正题
1、封装Object.defineProperty
function defineReactive(obj,key,value){ Object.defineProperty(obj,key,{ get(){ return value; }, set(newValue){ if(newValue===value) return; value=newValue; }, configurable:true, enumerable:true }) }
2、封装判读当前变量是不是需要被观察
function observe(data){ if(typeof data!=="object"||data===null){return} walk(data); }
3、遍历对象并设置Object.defineProperty
function walk(data){ let keys=Object.keys(data); for(let i=0;i<keys.length;i++){ defineReactive(data,keys[i],data[keys[i]]) } }
以上会有一个问题,如果是{a:1}这种则没问题,如果{a:1,b:{c:2}},则不能遍历到底层,所以需要在设置get前对设置的值进行判断当前值需不需要被观察,如果需要被观察,先观察,不需要直接给当前属性赋值
function defineReactive(obj,key,value){ observe(value);//判断当前值需不需要被观察,需要观察,先观察再进行下面的处理 Object.defineProperty(obj,key,{ get(){ return value; }, set(newValue){ if(newValue===value) return; value=newValue; }, configurable:true, enumerable:true }) }
到此还有一个问题,就是给对象的某一个属性设置对象时,应当对设置的值进行观察,看是不是需要把值设计成 Object.defineProperty
function defineReactive(obj,key,value){ observe(value);//判断当前值需不需要被观察,需要观察,先观察再进行下面的处理 Object.defineProperty(obj,key,{ get(){ return value; }, set(newValue){ if(newValue===value) return; value=newValue; observe(newValue); }, configurable:true, enumerable:true }) }
至此初步设计完成,当然还有一个问题,就是对数组处理会有问题
obj ={ a:[2,3] } obj.a.push(4);
//并不能监测数组属性的改变,需要重写数组方法,下一章介绍
还需要解决一个问题,现在访问上面的数据是根据vm._data.key这样去访问,但是在使用vue时,是通过vm.key去访问数据的
//因为我们给data里每一层属性都添加了Object.defineProperty data(){ return{ a:1, b:{ c:2 } } } //a\b\c三个均设置了Object.defineProperty,在访问属性c的时候,会首先触发b接着才会访问c,所以只需要在最外层设置代理即可 //实现访问vm.a等同于访问vm._data.a proxy(vm,source,key){ Object.defineProperty(vm,key,{ get(){ return vm[source][key] }, set(newValue){ vm[source][key]=newValue } }) } 修改initData函数 function initData(vm){ let data=vm.$options.data; data=vm._data=typeof data==="function"?data.call(vm):data||{}; for(let key in data){ proxy(vm,"_data",key); } observe(data); }