Subclassing Javascript Arrays. TypeError: Array.prototype.toString is not generic

后端 未结 7 1222
独厮守ぢ
独厮守ぢ 2020-11-28 06:30

Is it possible to subclass and inherit from javascript Arrays?

I\'d like to have my own custom Array object that has all the features of an Array, but contains addit

相关标签:
7条回答
  • 2020-11-28 07:01

    Here's a full example that should work on ie9 and greater. For <=ie8 you'd have to implement alternatives to Array.from, Array.isArray, etc. This example:

    • Puts the Array subclass in its own closure (or Namespace) to avoid conflicts and namespace pollution.
    • Inherits all prototypes and properties from the native Array class.
    • Shows how to define additional properties and prototype methods.

    If you can use ES6, you should use the class SubArray extends Array method laggingreflex posted.

    Here is the essentials to subclass and inherit from Arrays. Below this excerpt is the full example.

    ///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);
    

    Full example:

    ///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);
    

    Here are some usage examples and tests.

    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
    
    0 讨论(0)
  • 2020-11-28 07:02

    I've tried to do this sort of thing before; generally, it just doesn't happen. You can probably fake it, though, by applying Array.prototype methods internally. This CustomArray class, though only tested in Chrome, implements both the standard push and custom method last. (Somehow this methodology never actually occurred to me at the time 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
    

    Any Array method you intended to pull into your custom implementation would have to be implemented manually, though you could probably just be clever and use loops, since what happens inside our custom push is pretty generic.

    0 讨论(0)
  • 2020-11-28 07:04

    Checkout this. It works as it should in all browsers which support '__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
    
    0 讨论(0)
  • 2020-11-28 07:07

    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
    

    Original Answer: (Not recommended, may cause performance issues)

    Copy-pasting from article mentioned in the accepted answer for more visibility

    Using __proto__

    function SubArray() {
      var arr = [ ];
      arr.push.apply(arr, arguments);
      arr.__proto__ = SubArray.prototype;
      return arr;
    }
    SubArray.prototype = new Array;
    

    Now you can add your methods to SubArray

    SubArray.prototype.last = function() {
      return this[this.length - 1];
    };
    

    Initialize like normal Arrays

    var sub = new SubArray(1, 2, 3);
    

    Behaves like normal Arrays

    sub instanceof SubArray; // true
    sub instanceof Array; // true
    
    0 讨论(0)
  • 2020-11-28 07:16

    Juriy Zaytsev (@kangax) just today released a really good article on the subject.

    He explores various alternatives like the Dean Edwards iframe borrowing technique, direct object extension, prototype extension and the usage of ECMAScript 5 accessor properties.

    At the end there is no perfect implementation, each one has its own benefits and drawbacks.

    Definitely a really good read:

    • How ECMAScript 5 still does not allow to subclass an array
    0 讨论(0)
  • 2020-11-28 07:17

    I've created a simple NPM module that solves this - inherit-array. It basically does the following:

    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;
      };
    };
    

    After writing your own SubArray class you can make it inherit Array as follows:

    var SubArrayFactory = toArraySubClassFactory(SubArray);
    
    var mySubArrayInstance = SubArrayFactory(/*whatever SubArray constructor takes*/)
    
    0 讨论(0)
提交回复
热议问题