web-dev-qa-db-ja.com

Javascript配列のサブクラス化。 TypeError:Array.prototype.toStringはジェネリックではありません

Javascript配列からサブクラス化して継承することは可能ですか?

配列のすべての機能を備えているが、追加のプロパティを含む独自のカスタム配列オブジェクトが欲しいのですが。インスタンスがCustomArrayの場合は、myobj instanceof CustomArrayを使用して特定の操作を実行します。

サブクラス化を試みていくつかの問題が発生した後、私はこれを見つけました Dean Edwards Arrayオブジェクトでこれを行うことが正しく機能しないことを示す記事。 InternetExplorerが適切に処理していないことが判明しました。しかし、私は他の問題も見つけています(これまでのところ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の目標を達成する方法はありますか?

35
Tauren

Juriy Zaytsev( @ kangax )は本日、このテーマに関する非常に優れた記事をリリースしました。

彼は、Dean Edwardsiframeの借用手法、直接オブジェクト拡張、プロトタイプ拡張、ECMAScript5アクセサープロパティの使用法などのさまざまな代替案を検討しています。

結局、完璧な実装はありません。それぞれに独自の長所と短所があります。

間違いなく本当に良い読み物:

35
CMS

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
23
laggingreflex

私は以前にこの種のことをやろうとしました。一般的に、それは起こりません。ただし、内部で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内で行われることはかなり一般的であるため、おそらく賢くてループを使用することもできます。

1
Matchu

これは、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
1
Derek Ziemba

これをチェックしてください。 '__ 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
1
termi

これを解決する簡単な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*/)
0
Amit Portnoy