Getter / setter en la matriz de javascript?


¿Hay alguna manera de obtener un comportamiento get/set en una matriz? Me imagino algo como esto:

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;
        });
}
Author: Adam, 2010-03-15

10 answers

El acceso al array no es diferente al acceso normal a la propiedad. array[0] significa array['0'], por lo que puede definir una propiedad con nombre '0' e interceptar el acceso al primer elemento de matriz a través de eso.

Sin embargo, eso lo hace poco práctico para todos los arrays menos cortos, de longitud más o menos fija. No puede definir una propiedad para "todos los nombres que resultan ser enteros" de una sola vez.

 16
Author: bobince,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-15 19:06:37

Usando Proxies , puede obtener el comportamiento deseado:

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>

El constructor Proxy creará un objeto envolviendo nuestro Array y usará funciones llamadas traps para anular comportamientos básicos. La función get será llamada para cualquier búsqueda de propiedades, y doSomething() antes de devolver el valor.

Los proxies son una característica ES6 y no son compatibles con IE11 o versiones anteriores. Consulte la lista de compatibilidad del navegador .

 26
Author: acbabis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-03 20:32:59

Busqué en el artículo de John Resig Getters Y Setters de JavaScript, pero su ejemplo prototipo no funcionó para mí. Después de probar algunas alternativas, encontré una que parecía funcionar. Puedes usar Array.prototype.__defineGetter__ de la siguiente manera:

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

Funcionó para mí en Chrome y Firefox.

 4
Author: rolandog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-25 16:15:50

Espero que ayude.

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(', ') + ']';
        }
    }
);
 1
Author: Martinez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-19 09:58:58

Es posible definir Getters y Setters para arrays JavaScript. Pero no puedes tener accesores y valores al mismo tiempo. Ver la documentación de Mozilla :

No es posible tener simultáneamente un getter vinculado a una propiedad y que esa propiedad realmente tenga un valor

Así que si define los accesores para una matriz, necesita tener una segunda matriz para el valor real. El siguiente ejemplo lo ilustra.

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

El el código utiliza tres matrices:

  1. uno para los valores reales,
  2. uno para los valores codificados JSON
  3. y uno para los accesores.

El array con los accesores se devuelve al llamante. Cuando se llama a set asignando un valor al elemento array, se actualizan los arrays que contienen los valores simples y codificados. Cuando se llama a get, devuelve solo el valor normal. Y toString devuelve toda la consulta que contiene los valores codificados.

Pero como otros ya han dicho: esto solo tiene sentido, cuando el tamaño de la matriz es constante. Puede modificar los elementos existentes de la matriz, pero no puede agregar elementos adicionales.

 1
Author: ceving,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-17 11:19:21

¿Por qué no crear una nueva clase para los objetos internos?

var a = new Car();

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

Y luego,

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

Creo que entiendes la idea.

 0
Author: jpabluz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-15 17:53:06

Es posible crear setters para cada elemento de una matriz, pero hay una limitación: no sería capaz de establecer directamente elementos de matriz para índices que están fuera de la región inicializada (por ejemplo, myArray[2] = ... // wouldn't work if myArray.length < 2) Utilizando la Matriz.las funciones del prototipo funcionarán. (por ejemplo, push, pop, splice, shift, unshift.) Doy un ejemplo de cómo lograr esto aquí.

 0
Author: Constablebrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-28 17:11:04

Puede agregar los métodos que desee a un Array, agregándolos a Array.prototype. Aquí hay un ejemplo que agrega un getter y setter

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

Array.prototype.set = function(index, value) {
  this[index] = value;
}
 0
Author: Dónal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-10 18:03:19

Esta es la forma en que hago las cosas. Usted tendrá que ajustar la creación del prototipo (he eliminado un poco de mi Versión). Pero esto le dará el comportamiento predeterminado de getter / setter al que estoy acostumbrado en otros idiomas basados en Clases. Definir un Getter y ningún Setter significa que escribir en el elemento será ignorado...

Espero que esto ayude.

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

Esto me da el comportamiento esperado de:

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

Retomando el criticismo de dmvaldman: Escribir ahora debería ser imposible. Yo he reescrito el código para 1) no usar elementos depracados (__ defineGetter__) y 2) no aceptar ninguna escritura (es decir: escritura incontrolada) al elemento levels. Se incluye un setter de ejemplo. (Tuve que agregar espaciado a __ defineGetter debido a markdown)

De dmvaldmans petición:

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]
 0
Author: LeTigre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-11 16:48:09

Esta respuesta es solo una extensión de la solución basada en Proxy. Ver la solución con proxy, en la que solo se menciona get pero también podemos utilizar establecer como estoy mostrando aquí.

Aviso: el 3er argumento en set puede llevar el valor...

El código se explica por sí mismo.

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>
 0
Author: Tibin Thomas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-01 07:54:30