如何为嵌套对象使用 javascript 代理

我在 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",
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",然后它工作得很好。

我不明白为什么它不工作递归。

Http://jsbin.com/dinerotiwe/edit?html,js,console

24351 次浏览

您可以添加一个 get陷阱并返回一个以 validator作为处理程序的新代理:

var validator = {
get(target, key) {
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], validator)
} else {
return target[key];
}
},
set (target, key, value) {
console.log(target);
console.log(key);
console.log(value);
return true
}
}




var person = {
firstName: "alfred",
lastName: "john",
inner: {
salary: 8250,
Proffesion: ".NET Developer"
}
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'

我发表了一个 GitHub 上的图书馆,它也可以做到这一点。它还将向回调函数报告在其完整路径中进行了哪些修改。

Michal 的回答是好的,但它创建了一个新的 Proxy 每次都是嵌套对象被访问。根据您的使用情况,这可能导致非常大的内存开销。

我还创建了一个库类型函数,用于观察深度嵌套代理对象的更新(我创建它是为了用作单向绑定数据模型)。与 Elliot 的库相比,少于100行的代码更容易理解。此外,我认为 Elliot 对新的代理对象的担心是一个过早的优化,所以我保留了这个特性,以便更简单地推断代码的功能。

observable-model.js

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};
})();

然后,创建一个可观察的模型并注册观察者就很简单了:

app.js

// 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: []};

希望这个有用!

Micha Per akowski对这个示例进行了一个小小的修改,该方法的好处是嵌套代理只创建一次,而不是每次访问一个值。

如果正在访问的代理的属性是对象或数组,则该属性的值将替换为另一个代理。Getter 中的 isProxy属性用于检测当前访问的对象是否是代理。您可能希望更改 isProxy的名称,以避免与存储对象的属性发生命名冲突。

注意: 嵌套代理是在 getter 中定义的,而不是在 setter 中定义的,因此只有当数据实际上在某处使用时才会创建它。这可能适合也可能不适合您的用例。

const handler = {
get(target, key) {
if (key == 'isProxy')
return true;


const prop = target[key];


// return if property not found
if (typeof prop == 'undefined')
return;


// set value as proxy if object
if (!prop.isProxy && typeof prop === 'object')
target[key] = new Proxy(prop, handler);


return target[key];
},
set(target, key, value) {
console.log('Setting', target, `.${key} to equal`, value);


// todo : call callback


target[key] = value;
return true;
}
};


const test = {
string: "data",
number: 231321,
object: {
string: "data",
number: 32434
},
array: [
1, 2, 3, 4, 5
],
};


const proxy = new Proxy(test, handler);


console.log(proxy);
console.log(proxy.string); // "data"


proxy.string = "Hello";


console.log(proxy.string); // "Hello"


console.log(proxy.object); // { "string": "data", "number": 32434 }


proxy.object.string = "World";


console.log(proxy.object.string); // "World"

我写了一个基于 Micha Per akowski代码的函数。我在 set/get 函数中添加了对属性路径的访问。还有,我添加了类型。

    const createHander = <T>(path: string[] = []) => ({
get: (target: T, key: keyof T): any => {
if (key == 'isProxy') return true;
if (typeof target[key] === 'object' && target[key] != null)
return new Proxy(
target[key],
createHander<any>([...path, key as string])
);
return target[key];
},
set: (target: T, key: keyof T, value: any) =>  {
console.log(`Setting ${[...path, key]} to: `, value);
target[key] = value;
return true;
}
});
    

const proxy = new Proxy(obj ,createHander<ObjectType>());