matriz - Implementar el comportamiento similar a Array en JavaScript sin usar Array
slice javascript (9)
Es esto lo que estás buscando?
Thing = function() {};
Thing.prototype.__defineGetter__(''length'', function() {
var count = 0;
for(property in this) count++;
return count - 1; // don''t count ''length'' itself!
});
instance = new Thing;
console.log(instance.length); // => 0
instance[0] = {};
console.log(instance.length); // => 1
instance[1] = {};
instance[2] = {};
console.log(instance.length); // => 3
instance[5] = {};
instance.property = {};
instance.property.property = {}; // this shouldn''t count
console.log(instance.length); // => 5
El único inconveniente es que la ''longitud'' se repetirá en los bucles for..in como si fuera una propiedad. Es una lástima que no haya una manera de establecer atributos de propiedad (esto es algo que realmente me gustaría poder hacer).
¿Hay alguna forma de crear un objeto parecido a una matriz en JavaScript, sin usar la matriz incorporada? Me preocupa específicamente un comportamiento como este:
var sup = new Array(5);
//sup.length here is 0
sup[0] = ''z3ero'';
//sup.length here is 1
sup[1] = ''o3ne'';
//sup.length here is 2
sup[4] = ''f3our'';
//sup.length here is 5
El comportamiento particular que estoy viendo aquí es que sup.length cambia sin que se invoque ningún método. A partir de esta pregunta entiendo que el operador [] está sobrecargado en el caso de las matrices, y esto explica este comportamiento. ¿Hay alguna forma de javascript pura para duplicar este comportamiento, o el lenguaje no es lo suficientemente flexible para eso?
De acuerdo con los documentos de Mozilla , los valores devueltos por expresiones regulares también hacen cosas originales con este índice. ¿Es esto posible con javascript simple?
La respuesta es: no hay forma a partir de ahora. El comportamiento de la matriz se define en ECMA-262 como comportándose de esta manera, y tiene algoritmos explícitos sobre cómo tratar con la obtención y configuración de las propiedades de la matriz (y no de las propiedades genéricas de los objetos). Esto me consterna un tanto = (.
Claro, puedes replicar casi cualquier estructura de datos en JavaScript, todos los bloques básicos están ahí. Sin embargo, lo que terminará será más lento y menos intuitivo.
Pero ¿por qué no usar push / pop?
En general, no necesita un tamaño de índice predefinido para las matrices en javascript, simplemente puede hacer:
var sup = []; //Shorthand for an empty array
//sup.length is 0
sup.push(1); //Adds an item to the array (You don''t need to keep track of index-sizes)
//sup.length is 1
sup.push(2);
//sup.length is 2
sup.push(4);
//sup.length is 3
//sup is [1, 2, 4]
Si te preocupa el rendimiento de tu matriz dispersa (aunque probablemente no deberías) y quieres asegurarte de que la estructura sea tan larga como la de los elementos que le entregaste, puedes hacer esto:
var sup = [];
sup[''0''] = ''z3ero'';
sup[''1''] = ''o3ne'';
sup[''4''] = ''f3our'';
//sup now contains 3 entries
Una vez más, vale la pena señalar que probablemente no verá ningún aumento de rendimiento al hacer esto. Sospecho que Javascript ya maneja arreglos dispersos muy bien, muchas gracias.
[] operador es la forma nativa de acceder a las propiedades del objeto. No está disponible en el idioma para anular para cambiar su comportamiento.
Si lo que desea es devolver los valores calculados en el operador [], no puede hacer eso en JavaScript dado que el idioma no admite el concepto de propiedad computada. La única solución es usar un método que funcione igual que el operador [].
MyClass.prototype.getItem = function(index)
{
return {
name: ''Item'' + index,
value: 2 * index
};
}
Si lo que desea es tener el mismo comportamiento que una matriz nativa en su clase, siempre es posible utilizar métodos de matriz nativa directamente en su clase. Internamente, su clase almacenará datos al igual que una matriz nativa, pero mantendrá su estado de clase. jQuery hace eso para hacer que la clase jQuery tenga un comportamiento de matriz mientras conserva sus métodos.
MyClass.prototype.addItem = function(item)
{
// Will add "item" in "this" as if it was a native array
// it will then be accessible using the [] operator
Array.prototype.push.call(this, item);
}
También puedes crear tu propio método de longitud como:
Array.prototype.mylength = function() {
var result = 0;
for (var i = 0; i < this.length; i++) {
if (this[i] !== undefined) {
result++;
}
}
return result;
}
Sí, puede subclasificar una matriz en un objeto de arrays fácilmente en JavaScript:
var ArrayLike = function() {};
ArrayLike.prototype = [];
ArrayLike.prototype.shuffle = // ... and so on ...
A continuación, puede instanciar una nueva matriz como objetos:
var cards = new Arraylike;
cards.push(''ace of spades'', ''two of spades'', ''three of spades'', ...
cards.shuffle();
Desafortunadamente, esto no funciona en MSIE. No realiza un seguimiento de la propiedad de length
. Lo cual más bien desinfla todo.
El problema está en más detalle en Cómo hacer una subclase de Dean Edwards El objeto de matriz de JavaScript . Más tarde resultó que su solución no era segura ya que algunos bloqueadores de ventanas emergentes lo evitarían.
Actualización: Vale la pena mencionar la publicación absolutamente épica de Juriy "kangax" Zaytsev sobre el tema. Cubre prácticamente todos los aspectos de este problema.
Ahora tenemos ECMAScript 2015 (ECMA-262 6ª Edición; ES6), tenemos objetos proxy , y nos permiten implementar el comportamiento de Array
en el lenguaje en sí, algo así como:
function FakeArray() {
const target = {};
Object.defineProperties(target, {
"length": {
value: 0,
writable: true
},
[Symbol.iterator]: {
// http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype-@@iterator
value: () => {
let index = 0;
return {
next: () => ({
done: index === target.length,
value: target[index++]
})
};
}
}
});
const isArrayIndex = function(p) {
/* an array index is a property such that
ToString(ToUint32(p)) === p and ToUint(p) !== 2^32 - 1 */
const uint = p >>> 0;
const s = uint + "";
return p === s && uint !== 0xffffffff;
};
const p = new Proxy(target, {
set: function(target, property, value, receiver) {
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-exotic-objects-defineownproperty-p-desc
if (property === "length") {
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-arraysetlength
const newLen = value >>> 0;
const numberLen = +value;
if (newLen !== numberLen) {
throw RangeError();
}
const oldLen = target.length;
if (newLen >= oldLen) {
target.length = newLen;
return true;
} else {
// this case gets more complex, so it''s left as an exercise to the reader
return false; // should be changed when implemented!
}
} else if (isArrayIndex(property)) {
const oldLenDesc = Object.getOwnPropertyDescriptor(target, "length");
const oldLen = oldLenDesc.value;
const index = property >>> 0;
if (index > oldLen && oldLenDesc.writable === false) {
return false;
}
target[property] = value;
if (index > oldLen) {
target.length = index + 1;
}
return true;
} else {
target.property = value;
return true;
}
}
});
return p;
}
No puedo garantizar que esto sea realmente correcto, y no maneja el caso en el que se modifica la longitud para que sea más pequeño que su valor anterior (el comportamiento es un poco complejo para hacerlo bien, aproximadamente elimina las propiedades para que la length
propiedad invariante tiene), pero da un esquema aproximado de cómo puede implementarlo. Tampoco imita el comportamiento de [[Call]] y [[Construct]] en Array
, que es otra cosa que no podías hacer antes de ES6: no era posible tener un comportamiento divergente entre los dos dentro del código ES , aunque nada de eso es difícil.
Esto implementa la propiedad de length
de la misma manera que la especificación lo define como funcional: intercepta las asignaciones a las propiedades en el objeto y altera la propiedad de length
si es un "índice de matriz".
A diferencia de lo que se puede hacer con ES5 y getters, esto permite obtener length
en tiempo constante (obviamente, esto todavía depende del acceso a la propiedad subyacente en la VM que es tiempo constante), y el único caso en el que proporciona tiempo no constante el rendimiento es el caso no implementado cuando se newLen - oldLen
propiedades newLen - oldLen
(¡y la eliminación es lenta en la mayoría de las máquinas virtuales!).