如何为嵌套对象使用javascript代理
我在js bin中有以下代码:如何为嵌套对象使用javascript代理,javascript,proxy,ecmascript-6,es6-proxy,Javascript,Proxy,Ecmascript 6,Es6 Proxy,我在js bin中有以下代码: var validator = { set (target, key, value) { console.log(target); console.log(key); console.log(value); if(isObject(target[key])){ } return true } } var person = { firstName: "alfred", lastNa
var validator = {
set (target, key, value) {
console.log(target);
console.log(key);
console.log(value);
if(isObject(target[key])){
}
return true
}
}
var person = {
firstName: "alfred",
lastName: "john",
inner: {
salary: 8250,
Proffesion: ".NET Developer"
}
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'
如果我做了proxy.inner.salary=555代码>它不工作
但是,如果我使用proxy.firstName=“Anne”
,那么它的效果会非常好
我不明白为什么它不能递归地工作
您可以添加一个
get
陷阱,并返回一个新的代理,其中验证程序作为处理程序:
var验证程序={
获取(目标、关键){
if(目标[键]==='object'&&target[键]!==null的类型){
返回新代理(目标[key],验证程序)
}否则{
返回目标[键];
}
},
设置(目标、键、值){
控制台日志(目标);
控制台日志(键);
console.log(值);
返回真值
}
}
个人变量={
名字:“阿尔弗雷德”,
姓:“约翰”,
内部:{
工资:8250,
声明:“.NET开发者”
}
}
var proxy=新代理(人员、验证人)
proxy.inner.salary='foo'
我发布了一个也可以这样做的。它还将向回调函数报告所做的修改及其完整路径
Michal的答案很好,但每次访问嵌套对象时,它都会创建一个新的代理。根据您的使用情况,这可能会导致非常大的内存开销。我还创建了一个库类型函数,用于观察深度嵌套代理对象的更新(我创建它是为了用作单向绑定数据模型)。与Elliot的库相比,在<100行时更容易理解。此外,我认为Elliot对正在生成的新代理对象的担心是一种过早的优化,因此我保留了该功能以简化对代码功能的推理
observable model.js
希望这是有用的 对示例稍作修改,这种方法的好处是只创建一次嵌套代理,而不是每次访问一个值
如果要访问的代理的属性是对象或数组,则该属性的值将替换为另一个代理。getter中的isProxy
属性用于检测当前访问的对象是否为代理。您可能需要更改isProxy的名称,以避免与存储对象的属性发生命名冲突
注意:嵌套代理是在getter中定义的,而不是在setter中定义的,因此仅当数据实际在某个地方使用时才创建它。这可能适合您的用例,也可能不适合您的用例
const处理程序={
获取(目标、关键){
如果(键=='isProxy')
返回true;
常量属性=目标[键];
//如果未找到属性,则返回
if(typeof prop==“未定义”)
返回;
//将值设置为代理(如果是对象)
如果(!prop.isProxy&&typeof prop==='object')
target[key]=新代理(prop,handler);
返回目标[键];
},
设置(目标、键、值){
log('Setting',target,'.${key}等于',value);
//todo:呼叫回调
目标[键]=值;
返回true;
}
};
常数测试={
字符串:“数据”,
电话:231321,
对象:{
字符串:“数据”,
电话:32434
},
数组:[
1, 2, 3, 4, 5
],
};
const proxy=新代理(测试,处理程序);
console.log(代理);
console.log(proxy.string);//“数据”
proxy.string=“Hello”;
console.log(proxy.string);//“你好”
console.log(proxy.object);//{“字符串”:“数据”,“数字”:32434}
proxy.object.string=“World”;
console.log(proxy.object.string);//“World”
Nested表示“多个对象”,这意味着您需要多个代理来检测每个对象上的所有属性访问,而不仅仅是根对象。谢谢,如果target[key]是一个对象数组呢?我想我们可以映射validator?@robertking数组也是一个对象,所以它是对象中的一个对象,这段代码应该适用于深度嵌套的对象。日期和数组对我来说不太合适,可能是因为angular ngFor和datePipes使用了原型属性。我已经在下面发布了我的修改后的解决方案,看起来很有效,谢谢。但这样每次它都会返回一个新的代理实例。如果创建了同一个代理实例,是否还可以返回它?如果我执行了const-inner=person.inner,这是否有效;内部薪酬=1000代码>?(编辑:哦,我明白了。代理将从第一个get now返回,因此inner将是一个代理。很酷。这是否还有其他缺点或漏洞,或者它是否可以100%用于任何数量的深度嵌套对象?),.. 我解决这个问题的方法是跟踪WeakMap
中的代理。除非您硬编码所有对象路径,否则这个库实际上不会进行深度复制。@HDog-Hmm,您指的是哪个库?Observable Slim的目的不是深度复制对象——它的目的是观察对象上的更改以及任何深度嵌套子对象上的更改。我尝试访问proxy.inner.salary 1亿次,没有看到任何内存增加。我认为这个答案是不正确的,这让迈克尔的答案名声扫地。垃圾收集似乎在这种情况下起作用。@KilianHertel看一下Michal回答中的第一个if
语句。如果访问的属性是非空的对象
,它将创建一个新的代理。因此,当然,根据您的使用情况,创建一组新的代理对象很可能会增加内存使用。根据垃圾收集情况,您的里程数会有所不同。詹姆斯提供的另一个答案也解决了这个问题。为什么我的回答会给迈克尔的回答加上“坏名声”?我说他的答案很好,甚至我自己也投了更高的票……我相信。iBindingProxy应该是iProxy?如果您使用的是节点v10+,您也可以使用iProxy,而不是手动“设置”iProxy
let ObservableModel = (function () {
/*
* observableValidation: This is a validation handler for the observable model construct.
* It allows objects to be created with deeply nested object hierarchies, each of which
* is a proxy implementing the observable validator. It uses markers to track the path an update to the object takes
* <path> is an array of values representing the breadcrumb trail of object properties up until the final get/set action
* <rootTarget> the earliest property in this <path> which contained an observers array *
*/
let observableValidation = {
get(target, prop) {
this.updateMarkers(target, prop);
if (target[prop] && typeof target[prop] === 'object') {
target[prop] = new Proxy(target[prop], observableValidation);
return new Proxy(target[prop], observableValidation);
} else {
return target[prop];
}
},
set(target, prop, value) {
this.updateMarkers(target, prop);
// user is attempting to update an entire observable field
// so maintain the observers array
target[prop] = this.path.length === 1 && prop !== 'length'
? Object.assign(value, { observers: target[prop].observers })
: value;
// don't send events on observer changes / magic length changes
if(!this.path.includes('observers') && prop !== 'length') {
this.rootTarget.observers.forEach(o => o.onEvent(this.path, value));
}
// reset the markers
this.rootTarget = undefined;
this.path.length = 0;
return true;
},
updateMarkers(target, prop) {
this.path.push(prop);
this.rootTarget = this.path.length === 1 && prop !== 'length'
? target[prop]
: target;
},
path: [],
set rootTarget(target) {
if(typeof target === 'undefined') {
this._rootTarget = undefined;
}
else if(!this._rootTarget && target.hasOwnProperty('observers')) {
this._rootTarget = Object.assign({}, target);
}
},
get rootTarget() {
return this._rootTarget;
}
};
/*
* create: Creates an object with keys governed by the fields array
* The value at each key is an object with an observers array
*/
function create(fields) {
let observableModel = {};
fields.forEach(f => observableModel[f] = { observers: [] });
return new Proxy(observableModel, observableValidation);
}
return {create: create};
})();
// give the create function a list of fields to convert into observables
let model = ObservableModel.create([
'profile',
'availableGames'
]);
// define the observer handler. it must have an onEvent function
// to handle events sent by the model
let profileObserver = {
onEvent(field, newValue) {
console.log(
'handling profile event: \n\tfield: %s\n\tnewValue: %s',
JSON.stringify(field),
JSON.stringify(newValue));
}
};
// register the observer on the profile field of the model
model.profile.observers.push(profileObserver);
// make a change to profile - the observer prints:
// handling profile event:
// field: ["profile"]
// newValue: {"name":{"first":"foo","last":"bar"},"observers":[{}
// ]}
model.profile = {name: {first: 'foo', last: 'bar'}};
// make a change to available games - no listeners are registered, so all
// it does is change the model, nothing else
model.availableGames['1234'] = {players: []};