Подклассы массивов Javascript. TypeError: Array.prototype.toString не является общим
Возможно ли подкласс и наследование из массивов javascript?
Я хотел бы иметь свой собственный объект Array, который имеет все функции массива, но содержит дополнительные свойства. Я использовал бы myobj instanceof CustomArray
для выполнения определенных операций, если экземпляр - это мой CustomArray.
После попытки подкласса и столкновения с некоторыми проблемами я обнаружил, что эта статья Dean Edwards, которая указывает, что это делается с объектами Array, не работает правильно, Оказывается, Internet Explorer не справляется с этим должным образом. Но я также обнаружил другие проблемы (только проверенные в Chrome до сих пор).
Вот пример кода:
/**
* 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);
Ввод следующего содержания в консоли Chrome дает следующий результат:
> 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]
Очевидно, что объекты не ведут себя одинаково. Должен ли я отказаться от этого подхода или есть какой-то способ выполнить мою задачу myobj instanceof CustomArray
?
Ответы
Ответ 1
Юрий Зайцев (@kangax) только что выпустил действительно хорошую статью по этому вопросу.
Он исследует различные альтернативы, такие как метод заимствования iframe Дин Эдвардс, прямое расширение объекта, расширение прототипа и использование свойств доступа ECMAScript 5.
В конце нет идеальной реализации, каждый из которых имеет свои преимущества и недостатки.
Определенно очень хорошее чтение:
Ответ 2
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
Исходный ответ: (Не рекомендуется, может вызывать проблемы )
Скопировать вставку из статьи, упомянутой в принятом ответе для большей видимости
Использование __proto__
function SubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
arr.__proto__ = SubArray.prototype;
return arr;
}
SubArray.prototype = new Array;
Теперь вы можете добавить свои методы в SubArray
SubArray.prototype.last = function() {
return this[this.length - 1];
};
Инициализировать как обычные массивы
var sub = new SubArray(1, 2, 3);
Ведет себя как обычные массивы
sub instanceof SubArray; // true
sub instanceof Array; // true
Ответ 3
Я пытался это сделать раньше; вообще, этого просто не бывает. Возможно, вы, возможно, подделываете его, применяя методы Array.prototype
внутренне. Этот класс CustomArray
, хотя он протестирован только в Chrome, реализует как стандартный push
, так и настраиваемый метод last
. (Как-то эта методология никогда не приходила мне в голову в момент 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
Любой метод Array, который вы собираетесь использовать в своей пользовательской реализации, должен быть реализован вручную, хотя вы, вероятно, могли бы быть умными и использовать циклы, поскольку то, что происходит внутри нашего пользовательского push
, довольно типично.
Ответ 4
Оформить заказ. Он работает так, как и во всех браузерах, которые поддерживают " __ прото __".
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
Ответ 5
Я создал простой модуль NPM, который решает это - inherit-array. Он в основном делает следующее:
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;
};
};
После написания собственного класса SubArray
вы можете заставить его наследовать массив следующим образом:
var SubArrayFactory = toArraySubClassFactory(SubArray);
var mySubArrayInstance = SubArrayFactory(/*whatever SubArray constructor takes*/)
Ответ 6
Вот полный пример, который должен работать на ie9 и выше. Для <= ie8 вам придется реализовать альтернативы Array.from, Array.isArray и т.д. Этот пример:
- Помещает подкласс Array в собственное закрытие (или пространство имен), чтобы избежать конфликтов и загрязнения пространства имен.
- Наследует все прототипы и свойства из собственного класса Array.
- Показывает, как определить дополнительные свойства и методы прототипа.
Если вы можете использовать ES6, вы должны использовать метод class SubArray extends Array
laggingreflex.
Вот основные требования к подклассу и наследование из массивов. Ниже этот отрывок является полным примером.
///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);
Полный пример:
///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);
Вот несколько примеров использования и тестов.
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