拦截JavaScript数组访问器

时间:2017-05-21 13:02:31

标签: javascript arrays metaprogramming intercept

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



var array = [1, 2, 3]
var total = 0;
for (var i in array) {
  total += array[i]
}
console.log(total);



 应该返回输出,如:



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数组的索引超出范围异常的一些变化。)我将检查当前访问的值是否等于{ {1}},并在这种情况下抛出异常,否则只返回原始值。

2 个答案:

答案 0 :(得分:3)

您无法覆盖Arrays的访问者。 这是一个例子:



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);
&#13;
&#13;
&#13;

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

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

我强烈建议您避免覆盖这些访问者。您将更改代码的基础,以便第三方人无法在不阅读所有代码的情况下了解正在进行的操作。此外,您将失去许多内置的有用方法。 我希望这有帮助

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

就像这样,只使用一个带有数组filter方法的循环:

&#13;
&#13;
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
&#13;
&#13;
&#13;

答案 1 :(得分:2)

如果不修改代码,则无法实现:

因此,您需要修改代码,预处理代码或修改运行代码的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);