简体   繁体   English

javascript 数组上的 getter/setter?

[英]Getter/setter on javascript array?

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

Using Proxies , you can get the desired behavior: 使用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] }' 
 <pre></pre> 

The Proxy constructor will create an object wrapping our Array and use functions called traps to override basic behaviors. Proxy构造函数将创建一个包装我们的Array的对象,并使用名为traps的函数来覆盖基本行为。 The get function will be called for any property lookup, and doSomething() before returning the value. 在返回值之前,将为任何属性查找调用get函数,并调用doSomething()

Proxies are an ES6 feature and are not supported in IE11 or lower. 代理是ES6功能,IE11或更低版本不支持。 See browser compatibility list. 请参阅浏览器兼容列表

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. array[0]表示array['0'] ,因此您可以定义名为'0'的属性,并通过它拦截对第一个数组项的访问。

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. 您无法一次性定义“所有恰好是整数的名称”的属性。

I looked up in John Resig's article JavaScript Getters And Setters , but his prototype example didn't work for me. 我查阅了John Resig的文章JavaScript Getters And Setters ,但他的原型示例并不适用于我。 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__

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. 在Chrome和Firefox中为我工作。

It is possible to define Getters and Setters for JavaScript arrays. 可以为JavaScript数组定义Getters和Setter。 But you can not have accessors and values at the same time. 但是你不能同时拥有访问者和值。 See the Mozilla documentation : 请参阅Mozilla 文档

It is not possible to simultaneously have a getter bound to a property and have that property actually hold a value 不可能同时将getter绑定到属​​性并使该属性实际保存值

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 一个用于JSON编码值
  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. 通过为数组元素赋值来调用set ,将更新包含普通值和编码值的数组。 When get gets called, it returns just the plain value. get被调用时,它只返回普通值。 And toString returns the whole query containing the encoded values. toString返回包含编码值的整个查询。

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. 您可以修改数组的现有元素,但不能添加其他元素。

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

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 (eg myArray[2] = ... // wouldn't work if myArray.length < 2 ) Using the Array.prototype functions will work. 可以为数组的每个元素创建setter,但是有一个限制:您将无法直接为初始化区域之外的索引设置数组元素(例如myArray[2] = ... // wouldn't work if myArray.length < 2使用Array.prototype函数将起作用。 (eg push, pop, splice, shift, unshift.) I give an example of how to accomplish this here . (例如,推,弹,拼接,移位,不移位。)我举一个如何在这里完成这个的例子。

You can add whatever methods you like to an Array , by adding them to Array.prototype . 您可以将任何您喜欢的方法添加到Array ,方法是将它们添加到Array.prototype Here's an example that adds a getter and setter 这是一个添加getter和setter的示例

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

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

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. 我想你应该已经明白了。

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. 但是这将为您提供我在其他基于类的语言中习惯的默认getter / setter行为。 Defining a Getter and no Setter means that writing to the element will be ignored... 定义一个Getter而没有Setter意味着将忽略对该元素的写入...

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. 接受dmvaldman的批评:写作现在应该是不可能的。 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. 我重写了代码1)不使用depracated元素(__ defineGetter __)和2)不接受任何写入(即:不受控制的写入)到levels元素。 An example setter is included. 包括示例setter。 (I had to add spacing to __ defineGetter because of markdown) (由于降价,我不得不为__ defineGetter添加间距)

From dmvaldmans request: 来自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 answer is just an extension to the solution based on Proxy. 这个答案只是基于Proxy的解决方案的扩展。 See the solution with proxy, in that only get is mentioned but we can also use set as I am showing here. 请参阅带代理的解决方案,因为只提到了get,但我们也可以使用set,因为我在这里展示。

Notice: 3rd argument in set can carry the value... 注意:set中的第三个参数可以携带值...

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

<pre></pre>

in typescript i made this在 typescript 我做了这个

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.这只是想法,您可以添加任何需要的功能。

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.话虽这么说,我只在下面发布基于自定义 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]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM