【问题标题】:Intercepting JavaScript array accessor拦截 JavaScript 数组访问器
【发布时间】:2017-10-21 04:28:02
【问题描述】:

我想将一些副作用与每个数组访问器相关联,例如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 数组的索引的某些变体超出范围异常。)我会检查当前访问的值是否等于 @ 987654329@,并抛出异常,否则直接返回原值。

【问题讨论】:

标签: javascript arrays metaprogramming intercept


【解决方案1】:

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

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

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

附言在您的编辑之后,为了检查未定义的值和引发异常,您可以在 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

【讨论】:

  • 感谢您的回复。如果我想要实现的目标不可行,我将不得不使用您建议的get 方法。我还稍微更新了问题,澄清了我最关心的异常类型。
  • 您可以更改 get 方法以检查它之前是否未定义并引发异常,但在这种情况下我的建议是另一种。我将它添加到答案中,编写代码比在 cmets 中更容易
  • 改变了它在最后添加你的具体案例
  • 我不知道===== 之间的区别undefined。这很有用!我实际上并不想从数组中删除任何东西,只是跟踪它是否被越界访问。默认情况下,JS在那种情况下不会给出任何异常,但我想知道它什么时候发生。
  • 也许我知道发生在你身上的事情 :) 你正在开发一个由其他人编写的应用程序,相当大,他们定义了很多数组但他们没有管理未定义的案例,所以你有到处都有很多错误。所以现在您想要一种直接更改原型的方法,这样您就不必重构所有代码。不是吗? :D
【解决方案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);

【讨论】:

    猜你喜欢
    • 2023-04-03
    • 2013-10-17
    • 2021-12-28
    • 2013-06-04
    • 2012-04-18
    • 2019-02-11
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    相关资源
    最近更新 更多