之前的这篇文章
[http://blog.csdn.net/sunq1982/article/details/79354755]
其实那个监听方式有个大bug,当不停的update 监听对象以后就会导致内存泄漏。因为每次在调用get的时候都会生成一个新的proxy,后来才知道是多傻。
经过我一天时间的思考,用了一个递归的方法去解决嵌套对象的监听问题。
下面是代码:
//传递两个参数,一个是object, 一个是proxy的handler
//如果是不是嵌套的object,直接加上proxy返回,如果是嵌套的object,那么进入addSubProxy进行递归。
function toDeepProxy(object, handler) {
if (!isPureObject(object)) addSubProxy(object, handler);
return new Proxy(object, handler);
//这是一个递归函数,目的是遍历object的所有属性,如果不是pure object,那么就继续遍历object的属性的属性,如果是pure object那么就加上proxy
function addSubProxy(object, handler) {
for (let prop in object) {
if ( typeof object[prop] == 'object') {
if (!isPureObject(object[prop])) addSubProxy(object[prop], handler);
object[prop] = new Proxy(object[prop], handler);
}
}
object = new Proxy(object, handler)
}
//是不是一个pure object,意思就是object里面没有再嵌套object了
function isPureObject(object) {
if (typeof object!== 'object') {
return false;
} else {
for (let prop in object) {
if (typeof object[prop] == 'object') {
return false;
}
}
}
return true;
}
}
这个函数的关键点就在于,你是从嵌套对象的第一个属性进行判断加proxy还是从嵌套对象的最末端先进行判断加proxy, 此函数是后者。
前者我也调试过,行不通,就不赘述了。而且前者向后递归有个问题,在初始化的时候会触发不必要的get/set 指令。
一个有趣的事情是,在js中array也是object, 所以这个方法也是可以监听array的, 衍生开去,这个方法可以监听整个JSON数据结构。
但是这个函数有个缺点,就是会直接污染 需要监听的target, 如果你不想target被污染,那么这个方法就不可行了。
下面是用法:
//这是一个嵌套了对象和数组的对象
let object = {
name: {
first: {
four: 5,
second: {
third: 'ssss'
}
}
},
class: 5,
arr: [1, 2, {arr1:10}],
age: {
age1: 10
}
}
//这是一个嵌套了对象和数组的数组
let objectArr = [{name:{first:'ss'}, arr1:[1,2]}, 2, 3, 4, 5, 6]
//这是proxy的handler
let handler = {
get(target, property) {
console.log('get:' + property)
return Reflect.get(target, property);
},
set(target, property, value) {
console.log('set:' + property + '=' + value);
return Reflect.set(target, property, value);
}
}
//变成监听对象
object = toDeepProxy(object, handler);
objectArr = toDeepProxy(objectArr, handler);
//进行一系列操作
console.time('pro')
objectArr.length
objectArr[3];
objectArr[2]=10
objectArr[0].name.first = 'ss'
objectArr[0].arr1[0]
object.name.first.second.third = 'yyyyy'
object.class = 6;
object.name.first.four
object.arr[2].arr1
object.age.age1 = 20;
console.timeEnd('pro')
下面是结果:
可以看到,每次操作,不管对象,嵌套对象,数组,嵌套数组都有被调用到,而且在3ms左右完成,没有内存泄漏的问题。
接下来封装一下,用两种方法封装,一种是类,一种是函数
类:
class Watch {
constructor(obj) {
this.emit = dispatchEvent.bind(document);
this.listen = addEventListener.bind(document);
let customEventData = { detail: obj };
this.eventUpdated = new CustomEvent('updated', customEventData);
this.eventRead = new CustomEvent('read', customEventData);
this.eventChanged = new CustomEvent('changed', customEventData);
this.targetObj = obj;
}
createProxy() {
let _this = this;
let handler = {
get(target, property) {
_this.emit(_this.eventRead);
return Reflect.get(target, property);
},
set(target, property, value) {
_this.emit(_this.eventUpdated);
if (target[property] != value) _this.emit(_this.eventChanged);
return Reflect.set(target, property, value);
}
}
return toDeepProxy(_this.targetObj, handler);
function toDeepProxy(object, handler) {
if (!isPureObject(object)) addSubProxy(object, handler);
return new Proxy(object, handler);
function addSubProxy(object, handler) {
for (let prop in object) {
if (typeof object[prop] == 'object') {
if (!isPureObject(object[prop])) addSubProxy(object[prop], handler);
object[prop] = new Proxy(object[prop], handler);
}
}
object = new Proxy(object, handler)
}
function isPureObject(object) {
if (typeof object !== 'object') {
return false;
} else {
for (let prop in object) {
if (typeof object[prop] == 'object') {
return false;
}
}
}
return true;
}
}
}
on(eventStr, callback) {
let _this = this;
if (!/,+/.test(eventStr)) {
this.listen(eventStr, (e) => {
if (e.detail == _this.targetObj) callback();
});
} else {
let eventStrArr = eventStr.split(',');
for (let i = 0, len = eventStrArr.length; i < len; i++) {
this.listen(eventStrArr[i].trim(), (e) => {
if (e.detail == _this.targetObj) callback();
callback()
});
}
}
}
}
用法:
let obj1 = {
name: 'ss',
age: 10
}
let obj2 = {
class: 'ff'
}
let watcher = new Watch(obj1);
let watcher2 = new Watch(obj2);
obj1 = watcher.createProxy();
obj2 = watcher2.createProxy();
watcher.on('read, updated', function () {
console.log(obj1)
});
watcher2.on('read, updated', function () {
console.log(obj2)
});
obj1.name
obj2.class
这是用了addEventListener来监听对象的变化,但是问题是要调用全局变量document
下面用函数的方法:
function watchOut(obj, opts) {
let handler = {
get(target, property) {
opts.beforeRead(target, property);
let result = Reflect.get(target, property);
opts.read(target, property);
return result;
},
set(target, property, value) {
opts.beforeUpdated(value, property, value);
if(target[property] != value) opts.beforeChanged(value, property, value);
let result = Reflect.set(target, property, value);
opts.updated(value, property, value);
opts.changed(value, property, value);
return result;
}
}
return toDeepProxy(obj, handler);
function toDeepProxy(object, handler) {
if (!isPureObject(object)) addSubProxy(object, handler);
return new Proxy(object, handler);
function addSubProxy(object, handler) {
for (let prop in object) {
if (typeof object[prop] == 'object') {
if (!isPureObject(object[prop])) addSubProxy(object[prop], handler);
object[prop] = new Proxy(object[prop], handler);
}
}
object = new Proxy(object, handler)
}
function isPureObject(object) {
if (typeof object !== 'object') {
return false;
} else {
for (let prop in object) {
if (typeof object[prop] == 'object') {
return false;
}
}
}
return true;
}
}
}
调用方法:
let obj3 = watchOut(
{ name: { second: '99' } },
{
beforeRead(target,property){
console.log(target, property, 'beforeRead');
},
read(target, property) {
console.log('afterRead');
},
beforeUpdated(target, property, value) {
console.log('beforeUpdated');
},
beforeChanged(target, property, value) {
console.log('beforeChanged')
},
updated(target, property, value) {
console.log('updated');
},
changed(target, property, value) {
console.log('changed')
}
});
obj3.name.second
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
经过我的测试发现,如果用上面的方法对一个嵌套对像进行toDeepProxy以后,如果对此proxy进行赋值,而且这个值是个对象时,那么还是不能对赋的新值进行监听,因为我们没有在handler的set里面对新值进行代理。
所以我们改下handler。
let handler = {
get(target, property) {
opts.beforeRead(target, property);
let result = Reflect.get(target, property);
opts.read(target, property);
return result;
},
set(target, property, value) {
opts.beforeUpdated(value, property, value);
if (target[property] != value) opts.beforeChanged(value, property, value);
let result = Reflect.set(target, property, value);
opts.updated(value, property, value);
opts.changed(value, property, value);
if(typeof value == 'object') {
target[property] = toDeepProxy(target[property], handler); //加了这么一句,当value为一个对象时,对此对象也进行深度代理
}
return result;
}
}