【问题标题】:Alternatives of JavaScript ProxyJavaScript 代理的替代方案
【发布时间】:2016-09-30 06:15:14
【问题描述】:

我想在一个名为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']

我的 Proxy 实现可在 https://jsfiddle.net/ovilia/tLmbptr0/3/ 获得

【问题讨论】:

  • activities.push 还是activities.list.push
  • 你的可观察列表需要在哪些使用场景下工作?到目前为止,您只展示了调用 push 方法并分配给已经存在的索引。你还需要什么?请具体。
  • @Bergi activities.list.push。我需要观看pushshiftsplice 等所有数组操作。
  • 如果仅此而已,您甚至不需要代理 - 只需继承 Array 并使用调用 super 然后通知观察者的更新方法覆盖每个更新方法。
  • @Bergi 数组索引器和length 很难用这种方法打补丁。 @LiuJi-Jim 的答案在许多框架中被广泛采用。这令人沮丧,但不知何故有效。或者脏检查,比如 Angular,无聊又丑陋……

标签: javascript ecmascript-6 observer-pattern es6-proxy


【解决方案1】:

使用defineProperty

不完全符合您的要求。我刚刚实现了一个“反应阵列”。但我认为它可能适用于您的问题。

坏的部分:

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

好的部分:

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

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

【讨论】:

    【解决方案2】:

    如问题中所述,我只需要 ObservableList包含 Array,而不是像 Jim 在他的 complicated 中所做的那样扩展 回答。令人惊讶的是,我发现包装原始的Array 操作可以轻松实现这一点。

    一个限制是 索引操作在我的实现中不是被动的,因为我未能找到捕获索引操作的正确方法。如果你有更好的想法,欢迎告诉我!呵呵

    这是完整的实现。

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

    【讨论】:

    • 等一下,这是 TypeScript,不是吗?
    • @Leo 是的,但我认为这没什么区别,对吧?
    【解决方案3】:

    使用代理是硬性要求吗?我不会推荐代理 一般的编程任务,因为您最终可能会遇到不可预测且 难以发现的副作用。

    如果你保持数据和函数来转换它,避免可变 尽可能说明,我认为您最终会得到更简单的代码 更容易维护。

    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 兼容的 reactive 编程范式。这就是他/她想要摆脱 Proxy 的原因,这是一个完美的用例(但仅在 ES6 之后可用)。 IMO 这不会也不应该是一些业务逻辑。这更像是一个框架级别的要求。
    • @Leo 当然。我在评论 OP 更新镜像数据而不是观察的想法;我没说清楚。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-05
    • 2014-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多