JavaScript 代理的替代方案
Alternatives of JavaScript Proxy
我想在名为 ObservableList
的自定义 class 上使用 Proxy
,其中包含一个 Array
。由于Proxy
是ES6之后才有的,不知是否有替代实现
我的要求是在 ObservableList
发生变化时为观察者更新(而不是引起注意),以便观察者始终与具有某些过滤或映射方法的可观察者保持一致。
var activities = new ObservableList(['reading', 'swimming']);
var sAct = activities.filter(function(v) {
return v[0] === 's';
});
// expect sAct.list to be ['swimming']
var meAct = activities.map(function(v) {
return 'I am ' + v;
});
// expect meAct.list to be ['I am reading', 'I am swimming']
activities.list.push('smiling');
console.log(sAct.list, meAct.list);
// expect sAct.list to be ['swimming', 'smiling']
// expect meAct.list to be ['I am reading', 'I am swimming', 'I am smiling']
activities.list[1] = 'snoopying';
console.log(sAct.list, meAct.list);
// expect sAct.list to be ['swimming', 'snoopying']
// expect meAct.list to be ['I am reading', 'I am snoopying', 'I am smiling']
我的 Proxy 实现可在 https://jsfiddle.net/ovilia/tLmbptr0/3/
获得
用过 defineProperty
.
不完全符合您的要求。我刚刚实现了 "reactive array"。但我认为它可能会解决您的问题。
不好的部分:
- 在目标上定义了 getters/setters 吨。
- 访问未定义的索引器将不会反应。
update()
待优化
好的部分:
- ES5 友好。
- 如果不需要索引器,使用
set(i, val)/get(i)
将是反应式的。
使用代理是硬性要求吗?我不会推荐代理
一般的编程任务,因为您最终可能会遇到不可预知的和
难以发现的副作用。
如果你一直对数据和函数进行转换,避免可变
尽可能说明,我想你最终会得到更简单的代码
更容易维护。
var activities = ['reading', 'swimming'];
var sfilter = function(activities){
return activities.filter(function(v){
return v[0] === 's';
});
};
console.log(sfilter(activities));
var memap = function(activities){
return activities.map(function(v){
return 'I am ' + v;
});
};
console.log(memap(activities));
activities.push('smiling');
console.log(sfilter(activities));
console.log(memap(activities));
// Yes, I know this doesn't work in quite the same way,
// but you're asking for trouble here since in your
// code you're appending to one list, but overwriting
// an element in the other.
activities[1] = 'snoopying';
console.log(sfilter(activities));
console.log(memap(activities));
坚持单一事实来源并观察这一点。对于每个副本,您都会增加状态的复杂性。这将使调试、测试和扩展代码变得困难。
如问题中所述,我只需要 ObservableList
来 包含 一个 Array
,而不是 扩展 它,正如吉姆在他的 复杂 回答中所做的那样。令人惊讶的是,我发现这可以通过包装原始 Array
操作 .
轻松实现
一个限制是 索引操作在我的实现中不是反应性的,因为我未能找到捕获索引操作的正确方法。如果你有更好的想法,欢迎告诉我! XD
这是完整的实现。
export class ObservableList {
list: Array<any>;
private _observer: Array<ObserverList>;
constructor(list?: Array<any>) {
this.list = list || [];
this._initList();
this._initMethods();
this._observer = [];
}
notify(): void {
for (let o of this._observer) {
o.update();
}
}
private _initList(): void {
var that = this;
var operations = ['push', 'pop', 'shift', 'unshift', 'splice',
'sort', 'reverse'];
for (let operation of operations) {
this.list[operation] = function() {
var res = Array.prototype[operation].apply(that.list, arguments);
that.notify();
return res;
}
}
}
private _initMethods(): void {
var that = this;
var methods = ['filter', 'map'];
for (let method of methods) {
this[method] = (formatter: Function): ObserverList => {
var observer = new ObserverList(that, formatter, method);
this._observer.push(observer);
return observer;
}
}
}
}
export class ObserverList {
public list: Array<any>;
constructor(public observable: ObservableList,
public formatter: Function,
public method: string) {
this.list = [];
this.update();
}
update(): void {
var list = [];
var master = this.observable.list;
for (var i = 0, len = master.length; i < len; ++i) {
if (this.method === 'filter') {
if (this.formatter(master[i])) {
list.push(master[i]);
}
} else if (this.method === 'map') {
list.push(this.formatter(master[i]));
} else {
console.error('Illegal method ' + this.method + '.');
}
}
this.list = list;
}
}
我想在名为 ObservableList
的自定义 class 上使用 Proxy
,其中包含一个 Array
。由于Proxy
是ES6之后才有的,不知是否有替代实现
我的要求是在 ObservableList
发生变化时为观察者更新(而不是引起注意),以便观察者始终与具有某些过滤或映射方法的可观察者保持一致。
var activities = new ObservableList(['reading', 'swimming']);
var sAct = activities.filter(function(v) {
return v[0] === 's';
});
// expect sAct.list to be ['swimming']
var meAct = activities.map(function(v) {
return 'I am ' + v;
});
// expect meAct.list to be ['I am reading', 'I am swimming']
activities.list.push('smiling');
console.log(sAct.list, meAct.list);
// expect sAct.list to be ['swimming', 'smiling']
// expect meAct.list to be ['I am reading', 'I am swimming', 'I am smiling']
activities.list[1] = 'snoopying';
console.log(sAct.list, meAct.list);
// expect sAct.list to be ['swimming', 'snoopying']
// expect meAct.list to be ['I am reading', 'I am snoopying', 'I am smiling']
我的 Proxy 实现可在 https://jsfiddle.net/ovilia/tLmbptr0/3/
获得用过 defineProperty
.
不完全符合您的要求。我刚刚实现了 "reactive array"。但我认为它可能会解决您的问题。
不好的部分:
- 在目标上定义了 getters/setters 吨。
- 访问未定义的索引器将不会反应。
update()
待优化
好的部分:
- ES5 友好。
- 如果不需要索引器,使用
set(i, val)/get(i)
将是反应式的。
使用代理是硬性要求吗?我不会推荐代理 一般的编程任务,因为您最终可能会遇到不可预知的和 难以发现的副作用。
如果你一直对数据和函数进行转换,避免可变 尽可能说明,我想你最终会得到更简单的代码 更容易维护。
var activities = ['reading', 'swimming'];
var sfilter = function(activities){
return activities.filter(function(v){
return v[0] === 's';
});
};
console.log(sfilter(activities));
var memap = function(activities){
return activities.map(function(v){
return 'I am ' + v;
});
};
console.log(memap(activities));
activities.push('smiling');
console.log(sfilter(activities));
console.log(memap(activities));
// Yes, I know this doesn't work in quite the same way,
// but you're asking for trouble here since in your
// code you're appending to one list, but overwriting
// an element in the other.
activities[1] = 'snoopying';
console.log(sfilter(activities));
console.log(memap(activities));
坚持单一事实来源并观察这一点。对于每个副本,您都会增加状态的复杂性。这将使调试、测试和扩展代码变得困难。
如问题中所述,我只需要 ObservableList
来 包含 一个 Array
,而不是 扩展 它,正如吉姆在他的 复杂 回答中所做的那样。令人惊讶的是,我发现这可以通过包装原始 Array
操作 .
一个限制是 索引操作在我的实现中不是反应性的,因为我未能找到捕获索引操作的正确方法。如果你有更好的想法,欢迎告诉我! XD
这是完整的实现。
export class ObservableList {
list: Array<any>;
private _observer: Array<ObserverList>;
constructor(list?: Array<any>) {
this.list = list || [];
this._initList();
this._initMethods();
this._observer = [];
}
notify(): void {
for (let o of this._observer) {
o.update();
}
}
private _initList(): void {
var that = this;
var operations = ['push', 'pop', 'shift', 'unshift', 'splice',
'sort', 'reverse'];
for (let operation of operations) {
this.list[operation] = function() {
var res = Array.prototype[operation].apply(that.list, arguments);
that.notify();
return res;
}
}
}
private _initMethods(): void {
var that = this;
var methods = ['filter', 'map'];
for (let method of methods) {
this[method] = (formatter: Function): ObserverList => {
var observer = new ObserverList(that, formatter, method);
this._observer.push(observer);
return observer;
}
}
}
}
export class ObserverList {
public list: Array<any>;
constructor(public observable: ObservableList,
public formatter: Function,
public method: string) {
this.list = [];
this.update();
}
update(): void {
var list = [];
var master = this.observable.list;
for (var i = 0, len = master.length; i < len; ++i) {
if (this.method === 'filter') {
if (this.formatter(master[i])) {
list.push(master[i]);
}
} else if (this.method === 'map') {
list.push(this.formatter(master[i]));
} else {
console.error('Illegal method ' + this.method + '.');
}
}
this.list = list;
}
}