如何观察通过 setter-getter 或 Proxy 公开的对象数组 属性 内容的变化
How to observe changes to contents of an object's array property exposed through setter-getter or Proxy
使用getter/setter
我正在创建如下所示的 IIFE。它 returns getters 和 setters 到内部存储的数组变量。我希望拦截对该数组所做的更改 - console.log
旨在表明在下面的 setter 中。
const a = (function() {
let arr = [];
return {
get arr() {return arr},
set arr(v) {
console.log("new arr", v);
arr = v;
},
}
})();
如果我完全重新分配 arr
,这会很好用,例如a.arr = [1, 2]
。
但它不会拦截对数组内容所做的更改,例如a.arr.push(3)
或 a.arr.shift()
.
寻找有关如何拦截这些内容更改的任何想法。
使用代理
这是使用新代理对象的替代尝试:
a = (function() {
let details = {
arr: []
}
function onChangeProxy(object, onChange) {
const handler = {
apply: function (target, thisArg, argumentsList) {
onChange(thisArg, argumentsList);
return thisArg[target].apply(this, argumentsList);
},
defineProperty: function (target, property, descriptor) {
Reflect.defineProperty(target, property, descriptor);
onChange(property, descriptor);
return true;
},
deleteProperty: function(target, property) {
Reflect.deleteProperty(target, property);
onChange(property, descriptor);
return;
}
};
return new Proxy(object, handler);
};
return onChangeProxy(details, (p, d) => console.log(p, d));
})();
问题依旧。仍然无法观察到使用从 a.arr[0] = 1
到 a.push(3)
.
的任何内容对 a.arr
的内容所做的更改
更新:优雅的解决方案(由 Kris Pruden 和 Sindre Sorhus 提供)
解决方案基于this commit by Kris on Sindre's 'on-change' library。
Explanation of the solution,克里斯:
In the set
trap, my goal is to determine if the value provided is a Proxy
produced by a previous call to the get
trap. If it is such a Proxy
, any property access will be intercepted by our own get
trap. So, when I access value[proxyTarget]
our get
trap will be invoked, which is coded to return target
when property === proxyTarget
(line 46). If the value passed to set
is not an on-change-created Proxy, then value[proxyTarget]
is undefined
.
解决方案的完整代码:
(object, onChange) => {
let inApply = false;
let changed = false;
function handleChange() {
if (!inApply) {
onChange();
} else if (!changed) {
changed = true;
}
}
const handler = {
get(target, property, receiver) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
const value = Reflect.get(target, property, receiver);
// Preserve invariants
if (descriptor && !descriptor.configurable) {
if (descriptor.set && !descriptor.get) {
return undefined;
}
if (descriptor.writable === false) {
return value;
}
}
try {
return new Proxy(value, handler);
} catch (_) {
return value;
}
},
set(target, property, value) {
const result = Reflect.set(target, property, value);
handleChange();
return result;
},
defineProperty(target, property, descriptor) {
const result = Reflect.defineProperty(target, property, descriptor);
handleChange();
return result;
},
deleteProperty(target, property) {
const result = Reflect.deleteProperty(target, property);
handleChange();
return result;
},
apply(target, thisArg, argumentsList) {
if (!inApply) {
inApply = true;
const result = Reflect.apply(target, thisArg, argumentsList);
if (changed) {
onChange();
}
inApply = false;
changed = false;
return result;
}
return Reflect.apply(target, thisArg, argumentsList);
}
};
return new Proxy(object, handler);
};
这解决了我的问题,而无需求助于检查数组修改方法的技巧。
原解:
在 David Walsh's post here 的帮助下,我现在已经对它进行了排序。它仍然很难看,但现在可以用了。
使用递归式 get
陷阱更新了 onChanged
代理制造商。
get: function (target, property, receiver) {
let retval;
try {
retval = new Proxy(target[property], handler);
} catch (err) {
retval = Reflect.get(target, property, receiver);
}
if (mutators.includes(property))
onChange(target, property, receiver);
return retval;
},
还添加了一个函数列表来检查 get 陷阱(丑陋的、hacky 位):
const mutators = [
"push",
"pop",
"shift",
"unshift",
"splice",
"reverse",
"fill",
"sort"
]
到目前为止,这似乎在我的测试中有效。
感谢您指出正确的方向。
使用getter/setter
我正在创建如下所示的 IIFE。它 returns getters 和 setters 到内部存储的数组变量。我希望拦截对该数组所做的更改 - console.log
旨在表明在下面的 setter 中。
const a = (function() {
let arr = [];
return {
get arr() {return arr},
set arr(v) {
console.log("new arr", v);
arr = v;
},
}
})();
如果我完全重新分配 arr
,这会很好用,例如a.arr = [1, 2]
。
但它不会拦截对数组内容所做的更改,例如a.arr.push(3)
或 a.arr.shift()
.
寻找有关如何拦截这些内容更改的任何想法。
使用代理
这是使用新代理对象的替代尝试:
a = (function() {
let details = {
arr: []
}
function onChangeProxy(object, onChange) {
const handler = {
apply: function (target, thisArg, argumentsList) {
onChange(thisArg, argumentsList);
return thisArg[target].apply(this, argumentsList);
},
defineProperty: function (target, property, descriptor) {
Reflect.defineProperty(target, property, descriptor);
onChange(property, descriptor);
return true;
},
deleteProperty: function(target, property) {
Reflect.deleteProperty(target, property);
onChange(property, descriptor);
return;
}
};
return new Proxy(object, handler);
};
return onChangeProxy(details, (p, d) => console.log(p, d));
})();
问题依旧。仍然无法观察到使用从 a.arr[0] = 1
到 a.push(3)
.
a.arr
的内容所做的更改
更新:优雅的解决方案(由 Kris Pruden 和 Sindre Sorhus 提供)
解决方案基于this commit by Kris on Sindre's 'on-change' library。
Explanation of the solution,克里斯:
In the
set
trap, my goal is to determine if the value provided is aProxy
produced by a previous call to theget
trap. If it is such aProxy
, any property access will be intercepted by our ownget
trap. So, when I accessvalue[proxyTarget]
ourget
trap will be invoked, which is coded to returntarget
whenproperty === proxyTarget
(line 46). If the value passed toset
is not an on-change-created Proxy, thenvalue[proxyTarget]
isundefined
.
解决方案的完整代码:
(object, onChange) => {
let inApply = false;
let changed = false;
function handleChange() {
if (!inApply) {
onChange();
} else if (!changed) {
changed = true;
}
}
const handler = {
get(target, property, receiver) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
const value = Reflect.get(target, property, receiver);
// Preserve invariants
if (descriptor && !descriptor.configurable) {
if (descriptor.set && !descriptor.get) {
return undefined;
}
if (descriptor.writable === false) {
return value;
}
}
try {
return new Proxy(value, handler);
} catch (_) {
return value;
}
},
set(target, property, value) {
const result = Reflect.set(target, property, value);
handleChange();
return result;
},
defineProperty(target, property, descriptor) {
const result = Reflect.defineProperty(target, property, descriptor);
handleChange();
return result;
},
deleteProperty(target, property) {
const result = Reflect.deleteProperty(target, property);
handleChange();
return result;
},
apply(target, thisArg, argumentsList) {
if (!inApply) {
inApply = true;
const result = Reflect.apply(target, thisArg, argumentsList);
if (changed) {
onChange();
}
inApply = false;
changed = false;
return result;
}
return Reflect.apply(target, thisArg, argumentsList);
}
};
return new Proxy(object, handler);
};
这解决了我的问题,而无需求助于检查数组修改方法的技巧。
原解:
在 David Walsh's post here 的帮助下,我现在已经对它进行了排序。它仍然很难看,但现在可以用了。
使用递归式 get
陷阱更新了 onChanged
代理制造商。
get: function (target, property, receiver) {
let retval;
try {
retval = new Proxy(target[property], handler);
} catch (err) {
retval = Reflect.get(target, property, receiver);
}
if (mutators.includes(property))
onChange(target, property, receiver);
return retval;
},
还添加了一个函数列表来检查 get 陷阱(丑陋的、hacky 位):
const mutators = [
"push",
"pop",
"shift",
"unshift",
"splice",
"reverse",
"fill",
"sort"
]
到目前为止,这似乎在我的测试中有效。
感谢您指出正确的方向。