拦截 JavaScript 数组访问器

Intercepting JavaScript array accessor

我想将一些副作用与每个数组访问器相关联,例如 a[i]。例如,如果副作用是向控制台写入消息,则以下程序:

var array = [1, 2, 3]
var total = 0;
for (var i in array) {
  total += array[i]
}
console.log(total);
应该 return 输出像:

1 // access a[0]
2 // access a[1]
3 // access a[2]
6 // print original total

如果我对拦截数组方法感兴趣 push,我会使用此博客 post 中的技术并提供一个拦截器:

var _push = Array.prototype.push;
Array.prototype.push = function( item ) {
    console.log( 'pushing ', item, ' into ', this );
    _push.apply( this, arguments );
}

是否可以将相同的技巧应用于数组访问器?或者什么是解决这个问题的更好方法?一个重要的注意事项是我不想修改程序的原始代码。因此,使用 JS proxies 拦截 getter 和 setter 似乎不是解决我的问题的有效选项。

我想介绍的一个特殊的副作用是在访问的值未定义的情况下引发异常(JS 数组的索引越界异常的一些变体。)我会检查当前访问的值是否相等到 undefined,并抛出异常,在这种情况下,否则只是 return 原始值。

您不能覆盖数组的访问器。 这里有一个例子:

Object.defineProperty(Array.prototype, 0, {
  get: function () { return "my get on 0"; }
});
var a = [1,2,3];
console.log(a[0]); // output: 1

但是如果你尝试对数组中并不存在的 属性 做同样的事情,你将实现它:

Object.defineProperty(Array.prototype, 5, {
  get: function () { return "my get on 5"; }
});
var a = [1,2,3];
console.log(a[5]); // output: my get on 5

您可以做的是通过数组的 get 方法访问元素的一些变通方法。

Array.prototype.get = function(i) { 
  console.log('my print'); 
  console.log(this[i]); 
  return "this is!"; 
};
var a = [1,2,3];
console.log(a.get(0)); // output: my print 1 this is!

所以,回到你的问题,你可以像 push 那样做,但是使用 get,避免代理:

Array.prototype.get = function (i) {
  console.log('Accessing element: ' + this[i]);
  console.log(this);
  return this[i];
};
var array = [1, 2, 3];
var total = 0;
// be careful that now you cannot do anymore 
// for (var i in array), because inside the array there is also the property get defined and it will cycle also on that
// if you want to cycle again in that way, you need the check through hasOwnProperty method
/*
for(var i in array) {
  if (array.hasOwnProperty(i)){
    console.log(i);
    total += array.get(i);
  } 
}
*/
for(var i = 0; i < array.length; i++) {
  total += array.get(i);
}
console.log(total);

只是为了完成答案,你想做的事情可以用数组的reduce方法在一行中完成:

var array = [1, 2, 3];
var result = array.reduce(function (accumulator, actual) {
  return accumulator + actual;
}, 0);
console.log(result);

我强烈建议您避免覆盖这些访问器。您将更改代码的基础,这样第三方人员就不可能在不阅读所有代码的情况下理解发生了什么。此外,您将失去很多内置的有用方法。 希望对您有所帮助

p.s。在您的编辑之后,为了检查未定义的值并引发异常,您可以在 get 方法的覆盖范围内添加检查。 但我的建议只是过滤数组,检测未定义的值并删除它们。 请注意,我使用的是双等号。因为 undefined == nullundefined !== null。 通过这种方式,您将删除未定义值和空值。如果只想删除未定义的,请将其更改为 if (typeof element === 'undefined').

所以像这样,只使用一个循环和数组 filter 方法:

var data = [1, 2, undefined, 3, 4, undefined, 5];

data = data.filter(function( element, index ) {
   // note that I am using the double equal. because undefined == null but undefined !== null. 
   // in this way you will remove both undefined and null values
   // if you want to remove only undefined, change it to if (typeof element === 'undefined')
   if (element == null) {
     console.log('found and undefined null value at index: ' + index);
   }
   return element != null;
});
console.log(data); // array without undefined and null values

不修改代码是不行的:

  • 您不能重写数组文字构造函数。参见 Overwriting the Array constructor does not affect [], right?

  • 您也不能覆盖 for ... in 循环使用的迭代器的行为。参见 https://tc39.github.io/ecma262/#sec-enumerate-object-properties

  • 虽然您可以在 Array.prototype 上添加具有截取 getter 函数的属性,但当数组实例中存在同名属性时,将不会访问这些原型属性你的例子就是这种情况。

因此您需要修改代码、预处理代码或修改 JavaScript 引擎 运行 代码。

在前两种情况下,我建议用对 Array 构造函数的显式调用替换数组文字,可以 覆盖它:

// Override default array constructor:
Array = (function(Array) {
  function LoggingArray(...args) {
    return new Proxy(Array(...args), {
      get: function(target, property) {
        console.log(target[property]);
        return Reflect.get(target, property);
      }
    });
  }
  Object.setPrototypeOf(LoggingArray, Array);
  LoggingArray.prototype = Array.prototype;
  return LoggingArray;
})(Array);

// Original code without array literal:
var array = Array(1, 2, 3);
var total = 0;
for (var i in array) {
  total += array[i]
}
console.log(total);