有没有办法在数组上获取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;
});
}
答案 0 :(得分:36)
使用Proxies,您可以获得所需的行为:
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;
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;
}
}
代码使用三个数组:
带有访问者的数组将返回给调用者。通过为数组元素赋值来调用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)
希望这有帮助。
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>