JavaScript 数组上的 getter/setter?

发布于 2024-08-25 04:52:15 字数 437 浏览 8 评论 0 原文

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

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

Is there a way to get a get/set behaviour on an array? I imagine something like this:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

追星践月 2024-09-01 04:52:15

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

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] }'
<pre></pre>

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

代理是 ES6 的一项功能,IE11 或更低版本不支持。请参阅浏览器兼容性列表

Using Proxies, you can get the desired behavior:

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] }'
<pre></pre>

The Proxy constructor will create an object wrapping our Array and use functions called traps to override basic behaviors. The get function will be called for any property lookup, and doSomething() before returning the value.

Proxies are an ES6 feature and are not supported in IE11 or lower. See browser compatibility list.

如此安好 2024-09-01 04:52:15

数组访问与普通属性访问没有什么不同。 array[0] 表示 array['0'],因此您可以定义一个名为 '0' 的属性并拦截对第一个属性的访问数组项通过它。

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

Array access is no different to normal property access. array[0] means array['0'], so you can define a property with name '0' and intercept access to the first array item through that.

However, that does make it impractical for all but short, more-or-less-fixed-length Arrays. You can't define a property for “all names that happen to be integers” all in one go.

无力看清 2024-09-01 04:52:15

我查阅了 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 中为我工作。

I looked up in John Resig's article JavaScript Getters And Setters, but his prototype example didn't work for me. After trying out some alternatives, I found one that seemed to work. You can use Array.prototype.__defineGetter__ in the following way:

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

Worked for me in Chrome and Firefox.

┈┾☆殇 2024-09-01 04:52:15

我希望它有帮助。

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(', ') + ']';
        }
    }
);

I hope it helps.

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(', ') + ']';
        }
    }
);
雾里花 2024-09-01 04:52:15

可以为 JavaScript 数组定义 Getters 和 Setters。但你不能同时拥有访问器和值。请参阅 Mozilla 文档

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

,因此,如果为数组定义访问器,则需要有第二个数组来存储实际值。以下示例对此进行了说明。

//
// 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. ,一个用于访问器。

带有访问器的数组将返回给调用者。当通过向数组元素分配值来调用 set 时,包含明文值和编码值的数组将被更新。当 get 被调用时,它只返回普通值。 toString 返回包含编码值的整个查询。

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

It is possible to define Getters and Setters for JavaScript arrays. But you can not have accessors and values at the same time. See the Mozilla documentation:

It is not possible to simultaneously have a getter bound to a property and have that property actually hold a value

So if you define accessors for an array you need to have a second array for the actual value. The following example illustrates it.

//
// 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;
  }
}

The code uses three arrays:

  1. one for the actual values,
  2. one for the JSON encoded values
  3. and one for the accessors.

The array with the accessors is returned to the caller. When a set is called by assigning a value to the array element, the arrays containing the plain and encoded values are updated. When get gets called, it returns just the plain value. And toString returns the whole query containing the encoded values.

But as others have stated already: this makes only sense, when the size of the array is constant. You can modify the existing elements of the array but you can not add additional elements.

话少情深 2024-09-01 04:52:15

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

var a = new Car();

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

然后,

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

我想你明白了。

Why not create a new class for the inner objects?

var a = new Car();

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

And then,

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

I think you get the idea.

醉城メ夜风 2024-09-01 04:52:15

可以为数组的每个元素创建 setter,但有一个限制:您无法直接为初始化区域之外的索引设置数组元素(例如 myArray[2] = ... // 如果 myArray.length < 2 则不起作用)使用 Array.prototype 函数将起作用。 (例如推、弹出、拼接、移位、取消移位。)我给出了一个如何完成此操作的示例 此处

It is possible to create setters for each element of an array, but there is one limitation: you would not be able to directly set array elements for indexes that are outside the initialized region (e.g. myArray[2] = ... // wouldn't work if myArray.length < 2) Using the Array.prototype functions will work. (e.g. push, pop, splice, shift, unshift.) I give an example of how to accomplish this here.

行至春深 2024-09-01 04:52:15

这就是我做事的方式。您将必须调整原型创建(我从我的版本中删除了一些)。但这将为您提供我在其他基于类的语言中习惯的默认 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]

接受德姆瓦尔德曼的批评:现在写作应该是不可能的。
我将代码重写为 1) 不使用废弃的元素 (__ DefineGetter __) 和 2) 不接受对级别元素的任何写入(即:不受控制的写入)。包含一个设置器示例。 (由于降价,我不得不向 __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]

this is the way I do things. You will have to tweak the Prototype Creation (I removed a bit from my Version). But this will give you the default getter / setter behavior I am used to in other Class-Based Languages.
Defining a Getter and no Setter means that writing to the element will be ignored...

Hope this helps.

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

This gives me the expected behavior of:

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

Taking up the critizism from dmvaldman: Writing should now be impossible.
I rewrote the code to 1)not use depracated elements (__ defineGetter __) and 2) not accept any writing (that is: uncontrolled writing) to the levels element. An example setter is included. (I had to add spacing to __ defineGetter because of markdown)

From dmvaldmans request:

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]
骑趴 2024-09-01 04:52:15

这个答案只是基于Proxy的解决方案的扩展。
请参阅使用代理的解决方案,其中只提到了 get,但我们也可以使用
设置为我在这里展示的。

注意:集合中的第三个参数可以携带值...

代码是不言自明的。

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

This answer is just an extension to the solution based on Proxy.
See the solution with proxy, in that only get is mentioned but we can also use
set as I am showing here.

Notice: 3rd argument in set can carry the value...

The code is self explanatory.

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
无力看清 2024-09-01 04:52:15

在打字稿中,我做了这种

export class ArraySetter<T = any> extends Array<T>{
    set add(val: T) {
        this.push(val);
    }    
    get sum() {
        //use this to access array 
        return 'anything you want'
    }
}

用法

let tasks = new ArraySetter();
tasks.add = Task1();
tasks.add = Task2();
tasks.add = Task3();

,这只是想法,您可以添加所需的任何功能。

in typescript i made this

export class ArraySetter<T = any> extends Array<T>{
    set add(val: T) {
        this.push(val);
    }    
    get sum() {
        //use this to access array 
        return 'anything you want'
    }
}

usage

let tasks = new ArraySetter();
tasks.add = Task1();
tasks.add = Task2();
tasks.add = Task3();

this is just the idea, you can add whatever functionality needed.

乖乖兔^ω^ 2024-09-01 04:52:15

正确且现代的方法是 代理 作为接受的答案的数组已经注意到。

话虽这么说,我只发布了下面的替代方案,基于自定义 getter/setter,用于某些向后兼容性的情况并作为概念证明。

function observeArray(array, notify)
{
    var methodInterceptor = function(array, notify) {
        var interceptor = function(array, method, notify) {
            return function() {
                var initialLength = array.length;
                var result = Array.prototype[method].apply(array, arguments);
                if ('push' === method || 'unshift' === method || 'splice' === method)
                {
                    itemInterceptor(array, initialLength, array.length, notify);
                }
                notify(null, null, method);
                return result;
            };
        };
        ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(function(method) {
            array[method] = interceptor(array, method, notify);
        });
        return array;
    };

    var itemInterceptor = function(array, start, stop, notify) {
        var interceptor = function(array, index) {
            var key = String(index), val = array[index];
            Object.defineProperty(array, key, {
                get() {
                    return val;
                },
                set(value) {
                    if (val !== value)
                    {
                        val = value;
                        notify(val, index);
                    }
                },
                enumerable: true
            });
        };
        for (var index=start; index<stop; ++index)
        {
            interceptor(array, index);
        }
        return array;
    };
    return itemInterceptor(methodInterceptor(array, notify), 0, array.length, notify);
}


var a = observeArray([1,2,3], function(item, index, method){console.log(method ? 'array modified by method '+method : 'item at '+index+' ('+item+') has been modified');});

console.log(JSON.stringify(a));
a[0] = 4;
console.log(JSON.stringify(a));
a.push(5);
console.log(JSON.stringify(a));
a.unshift(0);
console.log(JSON.stringify(a));
a[0] = 1;
console.log(JSON.stringify(a));
console.log(a[0]);

The correct and modern way would be to proxy the array as accepted answers have already noted.

That being said, I only post the alternative below, based on custom getters/setters, for some cases of backwards compatibility and as a proof of concept.

function observeArray(array, notify)
{
    var methodInterceptor = function(array, notify) {
        var interceptor = function(array, method, notify) {
            return function() {
                var initialLength = array.length;
                var result = Array.prototype[method].apply(array, arguments);
                if ('push' === method || 'unshift' === method || 'splice' === method)
                {
                    itemInterceptor(array, initialLength, array.length, notify);
                }
                notify(null, null, method);
                return result;
            };
        };
        ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(function(method) {
            array[method] = interceptor(array, method, notify);
        });
        return array;
    };

    var itemInterceptor = function(array, start, stop, notify) {
        var interceptor = function(array, index) {
            var key = String(index), val = array[index];
            Object.defineProperty(array, key, {
                get() {
                    return val;
                },
                set(value) {
                    if (val !== value)
                    {
                        val = value;
                        notify(val, index);
                    }
                },
                enumerable: true
            });
        };
        for (var index=start; index<stop; ++index)
        {
            interceptor(array, index);
        }
        return array;
    };
    return itemInterceptor(methodInterceptor(array, notify), 0, array.length, notify);
}


var a = observeArray([1,2,3], function(item, index, method){console.log(method ? 'array modified by method '+method : 'item at '+index+' ('+item+') has been modified');});

console.log(JSON.stringify(a));
a[0] = 4;
console.log(JSON.stringify(a));
a.push(5);
console.log(JSON.stringify(a));
a.unshift(0);
console.log(JSON.stringify(a));
a[0] = 1;
console.log(JSON.stringify(a));
console.log(a[0]);

怼怹恏 2024-09-01 04:52:15

您可以将任何您喜欢的方法添加到 Array 中,方法是将它们添加到 Array.prototype 中。这是添加 getter 和 setter 的示例

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

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

You can add whatever methods you like to an Array, by adding them to Array.prototype. Here's an example that adds a getter and setter

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

Array.prototype.set = function(index, value) {
  this[index] = value;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文