我还创建了一个库类型函数,用于观察深层嵌套代理对象的更新(我创建它是为了用作单向绑定数据模型)。与 Elliot 的库相比,它在 < 100 行时更容易理解。此外,我认为 Elliot 对生成新 Proxy 对象的担忧是一种过早的优化,因此我保留了该功能以简化对代码功能的推理。
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: []};
希望这是有用的!