separado - Subclases de arreglos de Javascript. TypeError: Array.prototype.toString no es genérico
eliminar un elemento especifico de un array javascript (6)
¿Es posible crear una subclase y heredar de matrices de JavaScript?
Me gustaría tener mi propio objeto Array personalizado que tenga todas las características de una matriz, pero que contenga propiedades adicionales. myobj instanceof CustomArray
de myobj instanceof CustomArray
para realizar operaciones específicas si la instancia es mi CustomArray.
Después de intentar crear una subclase y encontrarme con algunos problemas, encontré este artículo de Dean Edwards que indica que hacer esto con objetos Array no funciona bien. Resulta que Internet Explorer no lo maneja adecuadamente. Pero también estoy encontrando otros problemas (solo probados en Chrome hasta ahora).
Aquí hay un código de muestra:
/**
* Inherit the prototype methods from one constructor into another
* Borrowed from Google Closure Library
*/
function inherits(childCtor, parentCtor) {
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
},
// Custom class that extends Array class
function CustomArray() {
Array.apply(this, arguments);
}
inherits(CustomArray,Array);
array = new Array(1,2,3);
custom = new CustomArray(1,2,3);
Si ingresas lo siguiente en la consola de Chrome obtendrás este resultado:
> custom
[]
> array
[1, 2, 3]
> custom.toString()
TypeError: Array.prototype.toString is not generic
> array.toString()
"1,2,3"
> custom.slice(1)
[]
> array.slice(1)
[2, 3]
> custom.push(1)
1
> custom.toString()
TypeError: Array.prototype.toString is not generic
> custom
[1]
Obviamente, los objetos no se comportan igual. ¿Debo abandonar este enfoque, o hay alguna forma de lograr mi objetivo de myobj instanceof CustomArray
?
ES6
class SubArray extends Array {
constructor(...args) {
super(...args);
}
last() {
return this[this.length - 1];
}
}
var sub = new SubArray(1, 2, 3);
sub // [1, 2, 3]
sub instanceof SubArray; // true
sub instanceof Array; // true
Respuesta original: (No recomendado, puede causar problemas de rendimiento )
Copiar y pegar del artículo mencionado en la respuesta aceptada para mayor visibilidad
Usando __proto__
function SubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
arr.__proto__ = SubArray.prototype;
return arr;
}
SubArray.prototype = new Array;
Ahora puede agregar sus métodos a SubArray
SubArray.prototype.last = function() {
return this[this.length - 1];
};
Inicializar como matrices normales
var sub = new SubArray(1, 2, 3);
Se comporta como matrices normales
sub instanceof SubArray; // true
sub instanceof Array; // true
Aquí hay un ejemplo completo que debería funcionar en ie9 y superior. Para <= ie8 tendrías que implementar alternativas a Array.from, Array.isArray, etc. Este ejemplo:
- Pone la subclase Array en su propio cierre (o Namespace) para evitar conflictos y contaminación del espacio de nombres.
- Hereda todos los prototipos y propiedades de la clase Array nativa.
- Muestra cómo definir propiedades adicionales y métodos prototipo.
Si puede usar ES6, debe usar la class SubArray extends Array
método class SubArray extends Array
laggingreflex publicado.
Aquí está lo esencial para subclasificar y heredar de Arrays. Debajo de este extracto está el ejemplo completo.
///Collections functions as a namespace.
///_NativeArray to prevent naming conflicts. All references to Array in this closure are to the Array function declared inside.
var Collections = (function (_NativeArray) {
//__proto__ is deprecated but Object.xxxPrototypeOf isn''t as widely supported. ''
var setProtoOf = (Object.setPrototypeOf || function (ob, proto) { ob.__proto__ = proto; return ob; });
var getProtoOf = (Object.getPrototypeOf || function (ob) { return ob.__proto__; });
function Array() {
var arr = new (Function.prototype.bind.apply(_NativeArray, [null].concat([].slice.call(arguments))))();
setProtoOf(arr, getProtoOf(this));
return arr;
}
Array.prototype = Object.create(_NativeArray.prototype, { constructor: { value: Array } });
Array.from = _NativeArray.from;
Array.of = _NativeArray.of;
Array.isArray = _NativeArray.isArray;
return { //Methods to expose externally.
Array: Array
};
})(Array);
Ejemplo completo:
///Collections functions as a namespace.
///_NativeArray to prevent naming conflicts. All references to Array in this closure are to the Array function declared inside.
var Collections = (function (_NativeArray) {
//__proto__ is deprecated but Object.xxxPrototypeOf isn''t as widely supported. ''
var setProtoOf = (Object.setPrototypeOf || function (ob, proto) { ob.__proto__ = proto; return ob; });
var getProtoOf = (Object.getPrototypeOf || function (ob) { return ob.__proto__; });
function Array() {
var arr = new (Function.prototype.bind.apply(_NativeArray, [null].concat([].slice.call(arguments))))();
setProtoOf(arr, getProtoOf(this));//For any prototypes defined on this subclass such as ''last''
return arr;
}
//Restores inherited prototypes of ''arr'' that were wiped out by ''setProtoOf(arr, getProtoOf(this))'' as well as add static functions.
Array.prototype = Object.create(_NativeArray.prototype, { constructor: { value: Array } });
Array.from = _NativeArray.from;
Array.of = _NativeArray.of;
Array.isArray = _NativeArray.isArray;
//Add some convenient properties.
Object.defineProperty(Array.prototype, "count", { get: function () { return this.length - 1; } });
Object.defineProperty(Array.prototype, "last", { get: function () { return this[this.count]; }, set: function (value) { return this[this.count] = value; } });
//Add some convenient Methods.
Array.prototype.insert = function (idx) {
this.splice.apply(this, [idx, 0].concat(Array.prototype.slice.call(arguments, 1)));
return this;
};
Array.prototype.insertArr = function (idx) {
idx = Math.min(idx, this.length);
arguments.length > 1 && this.splice.apply(this, [idx, 0].concat([].pop.call(arguments))) && this.insert.apply(this, arguments);
return this;
};
Array.prototype.removeAt = function (idx) {
var args = Array.from(arguments);
for (var i = 0; i < args.length; i++) { this.splice(+args[i], 1); }
return this;
};
Array.prototype.remove = function (items) {
var args = Array.from(arguments);
for (var i = 0; i < args.length; i++) {
var idx = this.indexOf(args[i]);
while (idx !== -1) {
this.splice(idx, 1);
idx = this.indexOf(args[i]);
}
}
return this;
};
return { //Methods to expose externally.
Array: Array
};
})(Array);
Aquí hay algunos ejemplos de uso y pruebas.
var colarr = new Collections.Array("foo", "bar", "baz", "lorem", "ipsum", "lol", "cat");
var colfrom = Collections.Array.from(colarr.reverse().concat(["yo", "bro", "dog", "rofl", "heyyyy", "pepe"]));
var colmoded = Collections.Array.from(colfrom).insertArr(0, ["tryin", "it", "out"]).insert(0, "Just").insert(4, "seems", 2, "work.").remove(''cat'',''baz'',''ipsum'',''lorem'',''bar'',''foo'');
colmoded; //["Just", "tryin", "it", "out", "seems", 2, "work.", "lol", "yo", "bro", "dog", "rofl", "heyyyy", "pepe"]
colmoded instanceof Array; //true
Creé un módulo simple de NPM que resuelve esto - inherit-array . Básicamente hace lo siguiente:
function toArraySubClassFactory(ArraySubClass) {
ArraySubClass.prototype = Object.assign(Object.create(Array.prototype),
ArraySubClass.prototype);
return function () {
var arr = [ ];
arr.__proto__ = ArraySubClass.prototype;
ArraySubClass.apply(arr, arguments);
return arr;
};
};
Después de escribir su propia clase SubArray
, puede hacer que herede Array de la siguiente manera:
var SubArrayFactory = toArraySubClassFactory(SubArray);
var mySubArrayInstance = SubArrayFactory(/*whatever SubArray constructor takes*/)
Intenté hacer este tipo de cosas antes; en general, simplemente no sucede. Sin embargo, probablemente puedas Array.prototype
aplicando métodos Array.prototype
interna. Esta clase CustomArray
, aunque solo se ha probado en Chrome, implementa tanto el método estándar de push
como el last
. (De alguna manera esta metodología nunca se me ocurrió en ese momento xD)
function CustomArray() {
this.push = function () {
Array.prototype.push.apply(this, arguments);
}
this.last = function () {
return this[this.length - 1];
}
this.push.apply(this, arguments); // implement "new CustomArray(1,2,3)"
}
a = new CustomArray(1,2,3);
alert(a.last()); // 3
a.push(4);
alert(a.last()); // 4
Cualquier método Array que intentara implementar en su implementación personalizada tendría que implementarse manualmente, aunque probablemente podría ser inteligente y usar bucles, ya que lo que sucede dentro de nuestra push
personalizada es bastante genérico.
Juriy Zaytsev ( @kangax ) acaba de publicar un muy buen artículo sobre el tema.
Explora diversas alternativas, como la técnica de endeudamiento del iframe Dean Edwards, la extensión directa del objeto, la extensión del prototipo y el uso de las propiedades de acceso ECMAScript 5.
Al final no hay una implementación perfecta, cada uno tiene sus propios beneficios y desventajas.
Definitivamente una muy buena lectura:
Mira esto. Funciona como debería en todos los navegadores compatibles con '' __proto__ ''.
var getPrototypeOf = Object.getPrototypeOf || function(o){
return o.__proto__;
};
var setPrototypeOf = Object.setPrototypeOf || function(o, p){
o.__proto__ = p;
return o;
};
var CustomArray = function CustomArray() {
var array;
var isNew = this instanceof CustomArray;
var proto = isNew ? getPrototypeOf(this) : CustomArray.prototype;
switch ( arguments.length ) {
case 0: array = []; break;
case 1: array = isNew ? new Array(arguments[0]) : Array(arguments[0]); break;
case 2: array = [arguments[0], arguments[1]]; break;
case 3: array = [arguments[0], arguments[1], arguments[2]]; break;
default: array = new (Array.bind.apply(Array, [null].concat([].slice.call(arguments))));
}
return setPrototypeOf(array, proto);
};
CustomArray.prototype = Object.create(Array.prototype, { constructor: { value: CustomArray } });
CustomArray.prototype.append = function(var_args) {
var_args = this.concat.apply([], arguments);
this.push.apply(this, var_args);
return this;
};
CustomArray.prototype.prepend = function(var_args) {
var_args = this.concat.apply([], arguments);
this.unshift.apply(this, var_args);
return this;
};
["concat", "reverse", "slice", "splice", "sort", "filter", "map"].forEach(function(name) {
var _Array_func = this[name];
CustomArray.prototype[name] = function() {
var result = _Array_func.apply(this, arguments);
return setPrototypeOf(result, getPrototypeOf(this));
}
}, Array.prototype);
var array = new CustomArray(1, 2, 3);
console.log(array.length, array[2]);//3, 3
array.length = 2;
console.log(array.length, array[2]);//2, undefined
array[9] = ''qwe'';
console.log(array.length, array[9]);//10, ''qwe''
console.log(array+"", array instanceof Array, array instanceof CustomArray);//''1,2,,,,,,,,qwe'', true, true
array.append(4);
console.log(array.join(""), array.length);//''12qwe4'', 11