1.问题
vue2.x用Object.defineProperty来劫持data数据的getter和setter操作。用这种方式实现了数据的变化侦测响应的功能,不过,Object.defineProperty存在一些天然的缺陷,包括:
1.Object.defineProperty的getter和setter只能侦测一个数据是否被修改,无法跟踪属性的删除和新增,就导致了对象里新增和删除属性无法被侦测到,(用proxy可以解决,Proxy的handler中有十三种劫持方式,比如deleteProperty可以劫持删除的操作)
2.上文 Object变化侦测中的 Observer实现中:
for(let i =0 ;i<keys.length;i++){ defineReactive(obj,keys[i],obj[keys[i]]) }
通过遍历为对象的每一个属性设置getter/setter方式有些。。。(感觉不对,但是缺少形容词)。
3.Object.defineProperty存在先天的缺陷,无法监听数组变化,vue2.x使用了 用自己的方法覆盖数组原型的方式实现,于是我们可以捕捉到 push,pop,slice等操作,但是依然存在问题,比如: gplArray[0]=1,这样的操作,无法对数据变化捕获。
2.proxy
概述: Proxy用于修改某些操作的默认行为,等同于语言层面做出修改,所以属于一种”元编程“,即对编程语言进行编程。 使用方式,比如:
var obj = new Proxy({}, { get: function (target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); } }); obj.gpl = 1 // setting gpl! ++obj.gpl // getting gpl! // setting gpl! // 2
这个函数defineReactive用来对Object.defineProperty封装,并定义了一个响应式的数据。封装好后,每当从data的key读取数据,get函数被触发,每当往data的key中设置数据时,set函数被触发。
3.用proxy尝试重新实现上面代码
综上所述,以及Object变化侦测中用Object.defineProperty实现的老代码,尝试对Object变化侦测用proxy的方式进行重写。
//Observer export class Observer { 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]]) } } } //Object.defineProperty 的封装 function defineReactive(data,key,val) { if(typeof val === 'object'){ new Observer(val) } let dep = new Dep(); Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ dep.depend() return val; }, set:function(newVal){ if(val === newVal){ return; } val = newVal; dep.notify(); } }) } //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;i<subs.length;i++){ subs[i].update() } } } function remove(arr,item) { if(arr.length){ const index = arr.indexOf(item); if(index > -1){ return arr.splice(index,1) } } }
改写:
由于proxy可以直接监听对象每一个属性,所以dep我改成了Map(【key:[wather,...]】,...)的形式
export default class Dep { constructor(){ this.subs = new Map(); } addSub(key,watcher){ let watcherList = this.subs.get(key)||[]; if(!watcherList.includes(watcher)){ watcherList.push(watcher); this.subs.set(key,watcher); } } removeSub(key){ this.subs.delete(key) } depend(key){ if(window.target){ this.addSub(key,window.target) } } notify(key){ let watcherList = this.subs.get(key); for(let watcher of watcherList){ watcher.update() } } } export class Observer { constructor (value) { this.value = this.defineReactive(value) } static defineReactive(data){ let dep = new Dep(); return new Proxy(data,{ get(obj, key) { dep.depend(key) return Reflect.get(obj, key); }, set(obj, key, newVal) { Reflect.set(obj, key, newVal); dep.notify(key) } }) } } //山寨超低配Vue class Vue { constructor({data}) { this.$data = Observer.defineReactive(data()); } } let vm = new Vue({ data: () => ( { xxx:'gpl' }), });```