javascript数组上的getter / setter?

时间:2010-03-15 17:39:15

标签: javascript arrays setter getter

有没有办法在数组上获取get / set行为?我想象这样的事情:

var arr = ['one', 'two', 'three'];
var _arr = new Array();

for (var i = 0; i < arr.length; i++) {
    arr[i].__defineGetter__('value',
        function (index) {
            //Do something
            return _arr[index];
        });
    arr[i].__defineSetter__('value',
        function (index, val) {
            //Do something
            _arr[index] = val;
        });
}

10 个答案:

答案 0 :(得分:36)

使用Proxies,您可以获得所需的行为:

&#13;
&#13;
var _arr = ['one', 'two', 'three'];

var accessCount = 0;
function doSomething() {
  accessCount++;
}

var arr = new Proxy(_arr, {
  get: function(target, name) {
    doSomething();
    return target[name];
  }
});

function print(value) {
  document.querySelector('pre').textContent += value + '\n';
}

print(accessCount);      // 0
print(arr[0]);           // 'one'
print(arr[1]);           // 'two'
print(accessCount);      // 2
print(arr.length);       // 3
print(accessCount);      // 3
print(arr.constructor);  // 'function Array() { [native code] }'
&#13;
<pre></pre>
&#13;
&#13;
&#13;

Proxy构造函数将创建一个包装我们的Array的对象,并使用名为traps的函数来覆盖基本行为。在返回值之前,将为任何属性查找调用get函数,并调用doSomething()

代理是ES6功能,IE11或更低版本不支持。见browser compatibility list.

答案 1 :(得分:17)

数组访问与普通属性访问没有区别。 array[0]表示array['0'],因此您可以定义名称为'0'的属性,并通过该属性拦截对第一个数组项的访问权。

然而,这确实使得除了短的,或多或少固定长度的数组之外的所有数据都不实用。您不能一次性为“所有恰好是整数的名称”定义属性。

答案 2 :(得分:5)

我查阅了John Resig的文章JavaScript Getters And Setters,但他的原型示例对我不起作用。在尝试了一些替代方案之后,我找到了一个似乎有用的方法。您可以通过以下方式使用Array.prototype.__defineGetter__

Array.prototype.__defineGetter__("sum", function sum(){
var r = 0, a = this, i = a.length - 1;
do {
    r += a[i];
    i -= 1;
} while (i >= 0);
return r;
});
var asdf = [1, 2, 3, 4];
asdf.sum; //returns 10

在Chrome和Firefox中为我工作。

答案 3 :(得分:1)

我希望它有所帮助。

Object.extend(Array.prototype, {
    _each: function(iterator) {
                    for (var i = 0; i < this.length; i++)
                    iterator(this[i]);
                },
    clear: function() {
                    this.length = 0;
                    return this;
                },
    first: function() {
                    return this[0];
                },
    last: function() {
                return this[this.length - 1];
                },
    compact: function() {
        return this.select(function(value) {
                                                return value != undefined || value != null;
                                                }
                                            );
        },
    flatten: function() {
            return this.inject([], function(array, value) {
                    return array.concat(value.constructor == Array ?
                        value.flatten() : [value]);
                    }
            );
        },
    without: function() {
        var values = $A(arguments);
                return this.select(function(value) {
                        return !values.include(value);
                }
            );
    },
    indexOf: function(object) {
        for (var i = 0; i < this.length; i++)
        if (this[i] == object) return i;
        return -1;
    },
    reverse: function(inline) {
            return (inline !== false ? this : this.toArray())._reverse();
        },
    shift: function() {
        var result = this[0];
        for (var i = 0; i < this.length - 1; i++)
        this[i] = this[i + 1];
        this.length--;
        return result;
    },
    inspect: function() {
            return '[' + this.map(Object.inspect).join(', ') + ']';
        }
    }
);

答案 4 :(得分:1)

可以为JavaScript数组定义Getters和Setter。但是你不能同时拥有访问者和值。请参阅Mozilla documentation

  

不可能同时将getter绑定到属​​性并使该属性实际保存值

因此,如果为数组定义访问器,则需要为实际值设置第二个数组。以下example说明了这一点。

//
// Poor man's prepare for querySelector.
//
// Example:
//   var query = prepare ('#modeler table[data-id=?] tr[data-id=?]');
//   query[0] = entity;
//   query[1] = attribute;
//   var src = document.querySelector(query);
//
var prepare;
{
  let r = /^([^?]+)\?(.+)$/; // Regular expression to split the query

  prepare = function (query, base)
  {
    if (!base) base = document;
    var q  = []; // List of query fragments
    var qi = 0;  // Query fragment index
    var v  = []; // List of values
    var vi = 0;  // Value index
    var a  = []; // Array containing setters and getters
    var m;       // Regular expression match
    while (query) {
      m = r.exec (query);
      if (m && m[2]) {
        q[qi++] = m[1];
        query   = m[2];
        (function (qi, vi) {
          Object.defineProperty (a, vi, {
            get: function() { return v[vi]; },
            set: function(val) { v[vi] = val; q[qi] = JSON.stringify(val); }});
        })(qi++, vi++);
      } else {
        q[qi++] = query;
        query   = null;
      }
    }
    a.toString = function () { return q.join(''); }
    return a;
  }
}

代码使用三个数组:

  1. 一个用于实际值,
  2. 一个用于JSON编码值
  3. 和一个用于访问者。
  4. 带有访问者的数组将返回给调用者。通过为数组元素赋值来调用set时,将更新包含普通值和编码值的数组。调用get时,它只返回普通值。 toString返回包含编码值的整个查询。

    但正如其他人已经说过的那样:当数组的大小不变时,这才有意义。您可以修改数组的现有元素,但不能添加其他元素。

答案 5 :(得分:0)

您可以将Array中添加的任何方法添加到Array.prototype。这是一个添加getter和setter

的示例
Array.prototype.get = function(index) {
  return this[index];
}

Array.prototype.set = function(index, value) {
  this[index] = value;
}

答案 6 :(得分:0)

为什么不为内部对象创建一个新类?

var a = new Car();

function Car()
{
   // here create the setters or getters necessary
}

然后,

arr = new Array[a, new Car()]

我想你明白了。

答案 7 :(得分:0)

可以为数组的每个元素创建setter,但是有一个限制:您将无法直接为初始化区域之外的索引设置数组元素(例如myArray[2] = ... // wouldn't work if myArray.length < 2)使用Array.prototype函数将起作用。 (例如,推,弹,拼接,移位,不移位。)我举一个如何完成这个here的例子。

答案 8 :(得分:0)

这是我做事的方式。你将不得不调整原型创建(我从我的版本中删除了一点)。但是这将为您提供我在其他基于类的语言中习惯的默认getter / setter行为。 定义一个Getter而不是Setter意味着将忽略对该元素的写入......

希望这有帮助。

function Game () {
  var that = this;
  this._levels = [[1,2,3],[2,3,4],[4,5,6]];

  var self = {
    levels: [],
    get levels () {
        return that._levels;
    },
    setLevels: function(what) {
        that._levels = what;
        // do stuff here with
        // that._levels
    }
  };
  Object.freeze(self.levels);
  return self;
}

这给了我预期的行为:

var g = new Game()
g.levels
/// --> [[1,2,3],[2,3,4],[4,5,6]]
g.levels[0]
/// --> [1,2,3]

接受dmvaldman的批评:写作现在应该是不可能的。 我重写了代码1)不使用depracated元素(__ defineGetter __)和2)不接受任何写入(即:不受控制的写入)到levels元素。包括示例setter。 (由于降价,我不得不为__ defineGetter添加间距)

来自dmvaldmans请求:

g.levels[0] = [2,3,4];
g.levels;
/// --> [[1,2,3],[2,3,4],[4,5,6]]

//using setter
g.setLevels([g.levels, g.levels, 1,2,3,[9]]);
g.levels;
/// --> [[[1,2,3],[2,3,4],[4,5,6]],[[1,2,3],[2,3,4],[4,5,6]], ....]

//using setLevels
g.setLevels([2,3,4]);
g.levels;
/// --> [2,3,4]

答案 9 :(得分:0)

此答案只是对基于代理的解决方案的扩展。  查看带有代理的解决方案,因为只提到了get,但我们也可以使用  设置为我在此处显示的位置。

注意:set中的第三个参数可以带有值...

该代码不言自明。

var _arr = ['one', 'two', 'three'];

var accessCount = 0;
function doSomething() {
accessCount++;
}

var arr = new Proxy(_arr, {
  get: function(target, name) {
  doSomething();
  return target[name];
},
set: function(target, name, val) { doSomething(); target[name] = val; }
});

function print(value) {
document.querySelector('pre').textContent += value + '\n';
}

print(accessCount);      // 0
print(arr[0]);           // 'one'
print(accessCount);      // 1
arr[1] = 10;
print(accessCount);      // 2
print(arr[1]);           // 10

<pre></pre>