一尘不染

如何观察阵列变化?

javascript

在Javascript中,当使用基于推,弹出,移位或基于索引的分配修改数组时,是否有通知方法?我想要能够触发我可以处理的事件的东西。

我知道watch()SpiderMonkey 的功能,但是仅当整个变量设置为其他值时,该功能才起作用。


阅读 240

收藏
2020-05-01

共1个答案

一尘不染

有一些选择…

1.覆盖推送方法

走快速而肮脏的路线,您可以覆盖push()数组1的方法:

Object.defineProperty(myArray, "push", {
  enumerable: false, // hide from for...in
  configurable: false, // prevent further meddling...
  writable: false, // see above ^
  value: function () {
    for (var i = 0, n = this.length, l = arguments.length; i < l; i++, n++) {          
      RaiseMyEvent(this, n, this[n] = arguments[i]); // assign/raise your event
    }
    return n;
  }
});

1或者,如果要定位 所有
数组,则可以覆盖Array.prototype.push()。但是要小心;您环境中的其他代码可能不喜欢或期望这种修改。不过,如果所有内容听起来都很吸引人,请替换myArrayArray.prototype

现在,这只是一种方法,并且有很多更改数组内容的方法。我们可能需要更全面的信息…

2.创建一个自定义的可观察数组

您可以创建自己的可观察数组,而不是覆盖方法。此特定实现拷贝的阵列到一个新的数组状物体并提供定制push()pop()shift()unshift()slice(),和splice()的方法
以及 定制索引访问器(条件是数组大小仅通过上述方法或一种修饰的length属性)。

function ObservableArray(items) {

  var _self = this,

    _array = [],

    _handlers = {

      itemadded: [],

      itemremoved: [],

      itemset: []

    };



  function defineIndexProperty(index) {

    if (!(index in _self)) {

      Object.defineProperty(_self, index, {

        configurable: true,

        enumerable: true,

        get: function() {

          return _array[index];

        },

        set: function(v) {

          _array[index] = v;

          raiseEvent({

            type: "itemset",

            index: index,

            item: v

          });

        }

      });

    }

  }



  function raiseEvent(event) {

    _handlers[event.type].forEach(function(h) {

      h.call(_self, event);

    });

  }



  Object.defineProperty(_self, "addEventListener", {

    configurable: false,

    enumerable: false,

    writable: false,

    value: function(eventName, handler) {

      eventName = ("" + eventName).toLowerCase();

      if (!(eventName in _handlers)) throw new Error("Invalid event name.");

      if (typeof handler !== "function") throw new Error("Invalid handler.");

      _handlers[eventName].push(handler);

    }

  });



  Object.defineProperty(_self, "removeEventListener", {

    configurable: false,

    enumerable: false,

    writable: false,

    value: function(eventName, handler) {

      eventName = ("" + eventName).toLowerCase();

      if (!(eventName in _handlers)) throw new Error("Invalid event name.");

      if (typeof handler !== "function") throw new Error("Invalid handler.");

      var h = _handlers[eventName];

      var ln = h.length;

      while (--ln >= 0) {

        if (h[ln] === handler) {

          h.splice(ln, 1);

        }

      }

    }

  });



  Object.defineProperty(_self, "push", {

    configurable: false,

    enumerable: false,

    writable: false,

    value: function() {

      var index;

      for (var i = 0, ln = arguments.length; i < ln; i++) {

        index = _array.length;

        _array.push(arguments[i]);

        defineIndexProperty(index);

        raiseEvent({

          type: "itemadded",

          index: index,

          item: arguments[i]

        });

      }

      return _array.length;

    }

  });



  Object.defineProperty(_self, "pop", {

    configurable: false,

    enumerable: false,

    writable: false,

    value: function() {

      if (_array.length > -1) {

        var index = _array.length - 1,

          item = _array.pop();

        delete _self[index];

        raiseEvent({

          type: "itemremoved",

          index: index,

          item: item

        });

        return item;

      }

    }

  });



  Object.defineProperty(_self, "unshift", {

    configurable: false,

    enumerable: false,

    writable: false,

    value: function() {

      for (var i = 0, ln = arguments.length; i < ln; i++) {

        _array.splice(i, 0, arguments[i]);

        defineIndexProperty(_array.length - 1);

        raiseEvent({

          type: "itemadded",

          index: i,

          item: arguments[i]

        });

      }

      for (; i < _array.length; i++) {

        raiseEvent({

          type: "itemset",

          index: i,

          item: _array[i]

        });

      }

      return _array.length;

    }

  });



  Object.defineProperty(_self, "shift", {

    configurable: false,

    enumerable: false,

    writable: false,

    value: function() {

      if (_array.length > -1) {

        var item = _array.shift();

        delete _self[_array.length];

        raiseEvent({

          type: "itemremoved",

          index: 0,

          item: item

        });

        return item;

      }

    }

  });



  Object.defineProperty(_self, "splice", {

    configurable: false,

    enumerable: false,

    writable: false,

    value: function(index, howMany /*, element1, element2, ... */ ) {

      var removed = [],

          item,

          pos;



      index = index == null ? 0 : index < 0 ? _array.length + index : index;



      howMany = howMany == null ? _array.length - index : howMany > 0 ? howMany : 0;



      while (howMany--) {

        item = _array.splice(index, 1)[0];

        removed.push(item);

        delete _self[_array.length];

        raiseEvent({

          type: "itemremoved",

          index: index + removed.length - 1,

          item: item

        });

      }



      for (var i = 2, ln = arguments.length; i < ln; i++) {

        _array.splice(index, 0, arguments[i]);

        defineIndexProperty(_array.length - 1);

        raiseEvent({

          type: "itemadded",

          index: index,

          item: arguments[i]

        });

        index++;

      }



      return removed;

    }

  });



  Object.defineProperty(_self, "length", {

    configurable: false,

    enumerable: false,

    get: function() {

      return _array.length;

    },

    set: function(value) {

      var n = Number(value);

      var length = _array.length;

      if (n % 1 === 0 && n >= 0) {

        if (n < length) {

          _self.splice(n);

        } else if (n > length) {

          _self.push.apply(_self, new Array(n - length));

        }

      } else {

        throw new RangeError("Invalid array length");

      }

      _array.length = n;

      return value;

    }

  });



  Object.getOwnPropertyNames(Array.prototype).forEach(function(name) {

    if (!(name in _self)) {

      Object.defineProperty(_self, name, {

        configurable: false,

        enumerable: false,

        writable: false,

        value: Array.prototype[name]

      });

    }

  });



  if (items instanceof Array) {

    _self.push.apply(_self, items);

  }

}



(function testing() {



  var x = new ObservableArray(["a", "b", "c", "d"]);



  console.log("original array: %o", x.slice());



  x.addEventListener("itemadded", function(e) {

    console.log("Added %o at index %d.", e.item, e.index);

  });



  x.addEventListener("itemset", function(e) {

    console.log("Set index %d to %o.", e.index, e.item);

  });



  x.addEventListener("itemremoved", function(e) {

    console.log("Removed %o at index %d.", e.item, e.index);

  });



  console.log("popping and unshifting...");

  x.unshift(x.pop());



  console.log("updated array: %o", x.slice());



  console.log("reversing array...");

  console.log("updated array: %o", x.reverse().slice());



  console.log("splicing...");

  x.splice(1, 2, "x");

  console.log("setting index 2...");

  x[2] = "foo";



  console.log("setting length to 10...");

  x.length = 10;

  console.log("updated array: %o", x.slice());



  console.log("setting length to 2...");

  x.length = 2;



  console.log("extracting first element via shift()");

  x.shift();



  console.log("updated array: %o", x.slice());



})();

请参阅以供参考。Object.[defineProperty()](https://developer.mozilla.org/en- US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)

这使我们更加接近,但仍不是防弹措施……这使我们能够:

3.代理

代理提供了另一种解决方案…允许您拦截方法调用,访问器等。最重要的是,您甚至不需要提供明确的属性名称就可以执行此操作…这将允许您测试任意的,基于索引的访问/分配。您甚至可以拦截属性删除。代理可以有效地让您
决定允许更改 之前 检查更改…除了在事后处理更改之外。

这是精简的示例:

(function() {



  if (!("Proxy" in window)) {

    console.warn("Your browser doesn't support Proxies.");

    return;

  }



  // our backing array

  var array = ["a", "b", "c", "d"];



  // a proxy for our array

  var proxy = new Proxy(array, {

    apply: function(target, thisArg, argumentsList) {

      return thisArg[target].apply(this, argumentList);

    },

    deleteProperty: function(target, property) {

      console.log("Deleted %s", property);

      return true;

    },

    set: function(target, property, value, receiver) {

      target[property] = value;

      console.log("Set %s to %o", property, value);

      return true;

    }

  });



  console.log("Set a specific index..");

  proxy[0] = "x";



  console.log("Add via push()...");

  proxy.push("z");



  console.log("Add/remove via splice()...");

  proxy.splice(1, 3, "y");



  console.log("Current state of array: %o", array);



})();
2020-05-01