2010-03-15 18 views
23

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

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

@Spudley Por favor, no perpetúen el mito de que JS no es OO. Definitivamente es así. Afirmar lo contrario revela poca comprensión de OO, JS o ambos. – KaptajnKold

Respuesta

15

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

Sin embargo, eso hace que sea poco práctico para todas las matrices, excepto las cortas, de longitud fija o más corta. No puede definir una propiedad para "todos los nombres que pasen a ser enteros", todo de una vez.

+10

Estrategia interesante.Todavía estoy esperando JavaScript para introducir un mecanismo getter/setter catch-all y seré un pato feliz. – devios1

+0

Vale la pena mencionar que ser una clave Array se define en la especificación como que puede convertir las claves a un entero sin signo de 32 bits. Buena respuesta. –

+0

Implementé la sugerencia de @ bobince [aquí] (http://michaelgeekbrewer.blogspot.com/2013/09/capturing-array-element-edits.html). Funciona bien para todo, excepto si se accede a la matriz fuera de los límites de la matriz establecida. – Constablebrew

2

Usted puede agregar cualquier método a su gusto de un Array, añadiéndolos a Array.prototype. Aquí hay un ejemplo que agrega un getter y un setter

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

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

Suena muy funcional y compatible, pero si quieres ajustar una matriz para siempre, entonces ya no quieres una matriz, pero una clase como @jpabluz dijo –

+3

Pero lo que quiero es que la matriz funcione aparentemente como antes, entonces Puedo hacer arr [0] = "value" y no arr.set() etc. y todavía ejecutar algún código cuando eso esté hecho. Esta es la forma en que los captadores/ajustadores funcionan para las propiedades normales. –

0

¿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 se entiende la idea.

+1

'nueva matriz [...]' no es sintácticamente correcta. En su lugar, debe usar parens: 'new Array (...)'. O simplemente omita la 'nueva matriz' y simplemente use la notación literal' [...] '. –

1

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

Por favor, explique su código. – hims056

+1

Esto es realmente una gran idea, y se debe explicar correctamente. El código está creando una subclase de Array. La clave es extender 'Array.prototype'. Una respuesta más relevante sería 'var SubArrayClass = {}; SubArrayClass.prototype = Object.extend (Array.prototype, {get: ..., set: ...}); ' – 00500005

+1

Bueno, una gran idea que deseé funcionó. Actualmente no hay forma de interceptar 'Object [property]', que es realmente lo que se necesita para anular. Esto solo proporciona una matriz como Objeto, que realmente no es lo que el póster está pidiendo. – 00500005

4

Miré hacia arriba en el artículo de John Resig JavaScript Getters And Setters, pero su ejemplo prototipo no funcionó para mí. Después de probar algunas alternativas, encontré una que parecía funcionar. Puede utilizar 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 

trabajado para mí en Chrome y Firefox.

0

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

0

esta es la forma en que hago las cosas. Tendrás que modificar la creación del prototipo (eliminé un poco de mi versión). Pero esto le dará el comportamiento de getter/setter predeterminado al que estoy acostumbrado en otros lenguajes basados ​​en clases. Definir un Getter y no Setter significa que se ignorará la escritura en el elemento ...

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 critizism de dmvaldman: La escritura debe ser ahora imposible. Reescribí el código para 1) no usar elementos depracados (__ defineGetter __) y 2) no aceptar escritura (es decir, escritura no controlada) en el elemento de niveles. Un setter de ejemplo está incluido.(He tenido que añadir espaciado a __ defineGetter debido a la reducción del precio)

De dmvaldmans solicitud:

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

g.levels [0] = [2,3,4] no serán interceptados por el colocador. Esta es la funcionalidad que el cartel original está pidiendo. – dmvaldman

+0

@dmvaldman. Bueno, la pregunta es acerca de getters y setters en general. Y tu crítica también explicaría la mayoría de las otras respuestas, así que ... bueno, no importa. Rehice el código para que no acepte escribir en los niveles. Espero que te guste más. Crítica constructiva bienvenida. Thx .. – LeTigre

+0

Nuevamente, no. Hay una razón por la cual esta respuesta está downvoted: es un malentendido de la pregunta. Después de g.levels [0] = [2,3,4] el póster espera [[2,3,4], [2,3,4], [4,5,6]] – dmvaldman

23

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 nuestra matriz y usará funciones llamadas trampas para anular b comportamientos asics. Se llamará a la función get para cualquier búsqueda de propiedad y doSomething() antes de devolver el valor.

Los apoderados son una característica de ES6 y no son compatibles con IE11 o versiones anteriores. Ver browser compatibility list.

+0

"Tenga en cuenta, sin embargo, que Proxy devuelve un objeto y no una matriz". Pruebe esto: 'print (arr.constructor);' y verá que todavía es una matriz. Btw 'typeof (new Array())' devuelve ''objeto'' también. – xoxox

+0

@xoxox Gracias. Eliminé esa parte – acbabis

1

Es posible definir Getters y Setters para matrices de JavaScript. Pero no puede tener accesadores y valores al mismo tiempo. Ver el Mozilla documentation:

No es posible tener al mismo tiempo un captador unido a una propiedad y tienen esa propiedad en realidad posee un valor

Así que si se define descriptores de acceso para una matriz es necesario tener una segunda matriz para el valor real. La siguiente example 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 código utiliza tres matrices:

  1. uno para los valores reales,
  2. uno de los valores JSON codificados
  3. y uno para los descriptores de acceso.

La matriz con los accesorios se devuelve a la persona que llama. Cuando se llama a set asignando un valor al elemento de la matriz, las matrices que contienen los valores simples y codificados se actualizan. Cuando se llama a get, devuelve solo el valor simple. Y toString devuelve la consulta completa que contiene los valores codificados.

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