JavaScript Proxy 的替代方案

8

我希望在一个名为ObservableList的自定义类中使用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']

使用代理的实现可以在 https://jsfiddle.net/ovilia/tLmbptr0/3/ 上找到。


activities.push 还是 activities.list.push - Bergi
你的可观察列表需要在哪些使用场景下工作?到目前为止,你只展示了调用 push 方法和分配给已经存在的索引。你还需要什么?请具体说明。 - Bergi
@Bergi activities.list.push。我需要所有数组操作,比如pushshiftsplice等都被监视。 - Ovilia
1
如果只是这样,你甚至不需要代理 - 只需子类化 Array 并覆盖每个更新方法,使其调用 super 然后通知观察者即可。 - Bergi
@Bergi 这种方法很难修补数组索引器和“length”。@LiuJi-Jim的答案在许多框架中被广泛采用。这很令人沮丧,但不知何故有效。或者像Angular一样进行脏检查,这很无聊且丑陋... - Leo
3个回答

2

使用了defineProperty

和你想要的不完全一样,我只是实现了一个“响应式数组”。但我认为它可能适用于你的问题。

不足之处:

  1. 在目标对象上定义了大量的getter/setter。
  2. 访问未定义的索引器将不会是响应式的。
  3. update() 待优化。

优点:

  1. ES5友好。
  2. 如果不需要索引器,则使用 set(i, val)/get(i) 将是响应式的。

https://jsfiddle.net/jimnox/jrtq40p7/2/


0

使用代理是否是硬性要求?对于一般的编程任务,我不建议使用代理,因为您可能会遇到不可预测和难以发现的副作用。

如果您坚持使用数据和函数进行转换,并尽可能避免使用可变状态,那么我认为您最终将得到更简单、更易于维护的代码。

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

坚持一个真实数据源并遵守它。每次复制都会增加状态复杂性。这将使调试、测试和扩展代码变得困难。


这完全偏离了轨道。OP想要实现与ES5兼容的响应式编程范例,这就是为什么他/她想要摆脱Proxy的原因(但只有在ES6之后才可用)。在我看来,这不应该是业务逻辑,也不应该是框架级别的需求。 - Leo
@Leo 当然。我是在评论原帖者更新镜像数据而不是观察的想法,这一点我没有表达清楚。 - 1983

0

如问题所述,我只需要ObservableList包含一个Array,而不是像Jim在他的复杂答案中所做的那样去扩展它。令人惊讶的是,我发现这可以通过包装原始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;
  }

}

2
等一下,这是 TypeScript,不是吗? - Leo
@Leo 是的,但我认为这没有任何区别,对吧? - Ovilia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接