Создание подклассов массивов Javascript.Ошибка типа: Array.prototype.toString не является универсальной - PullRequest
33 голосов
/ 16 июля 2010

Можно ли создать подкласс и наследовать от массивов javascript?

Я хотел бы иметь свой собственный объект Array, который имеет все функции массива, но содержит дополнительные свойства.Я бы использовал myobj instanceof CustomArray для выполнения определенных операций, если экземпляр является моим CustomArray.

После попытки создать подкласс и столкнуться с некоторыми проблемами, я нашел эту статью Дин Эдвардс , которая указывает на выполнениес объектами 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?

Ответы [ 6 ]

33 голосов
/ 16 июля 2010

Юрий Зайцев ( @ kangax ) только сегодня выпустил действительно хорошую статью на эту тему.

Он исследует различные альтернативы, такие как метод декана Эдвардса заимствование фрейма , прямое расширение объекта, расширение прототипа и использование свойств доступа ECMAScript 5.

В конце нет идеальной реализации, у каждого есть свои преимущества и недостатки.

Определенно действительно хорошее чтение:

23 голосов
/ 22 мая 2015

ES6

class SubArray extends Array {
    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
1 голос
/ 27 октября 2016

Вот полный пример, который должен работать на ie9 и выше. Для <= ie8 вам нужно реализовать альтернативы Array.from, Array.isArray и т. Д. Этот пример: </p>

  • Помещает подкласс 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
1 голос
/ 16 июля 2010

Я пытался делать подобные вещи раньше; как правило, этого просто не происходит. Вы, вероятно, можете подделать это, применяя методы 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, довольно общее.

0 голосов
/ 08 апреля 2015

Я создал простой модуль 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 класса вы можете сделать так, чтобы он наследовал Array следующим образом:

var SubArrayFactory = toArraySubClassFactory(SubArray);

var mySubArrayInstance = SubArrayFactory(/*whatever SubArray constructor takes*/)
0 голосов
/ 31 июля 2014

Оформить заказ. Работает так, как должно во всех браузерах, которые поддерживают ' __ 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...