2010-07-16 8 views
31

जावास्क्रिप्ट Arrays से subclass और वारिस संभव है?जावास्क्रिप्ट Arrays subclassing। TypeError: Array.prototype.toString जेनेरिक

मैं अपनी खुद की कस्टम ऐरे ऑब्जेक्ट रखना चाहता हूं जिसमें एक ऐरे की सभी विशेषताएं हैं, लेकिन इसमें अतिरिक्त गुण हैं। यदि उदाहरण मेरा कस्टमएरे है तो मैं विशिष्ट संचालन करने के लिए myobj instanceof CustomArray का उपयोग करूंगा।

कुछ समस्याओं में उपclass और चलाने की कोशिश करने के बाद, मुझे यह Dean Edwards आलेख मिला जो इंगित करता है कि यह ऐरे ऑब्जेक्ट्स के साथ ऐसा करने से सही काम नहीं करता है। यह पता चला है कि इंटरनेट एक्सप्लोरर इसे ठीक से संभाल नहीं करता है। लेकिन मुझे अन्य मुद्दों को भी मिल रहा है (केवल क्रोम में परीक्षण किया गया है)।

यहां कुछ नमूना कोड है:

/** 
* 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 के अपने लक्ष्य को पूरा करने का कोई तरीका है?

+0

एक रैपर एक स्वीकार्य समाधान बना रहा है? उपरोक्त में समस्या यह है कि 'Array.apply' 'इस' संदर्भ को अनदेखा कर एक नई वस्तु को वापस कर रहा है। – Anurag

+0

@Anurag: रैपर द्वारा, क्या आपका मतलब कुछ ऐसा करने का मतलब है? Http: //perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/#wrappers_prototype_chain_injection या आपने किया मन में कुछ और है? – Tauren

उत्तर

31

जुरी ज़ैत्सेव (@kangax) अभी इस विषय पर वास्तव में एक अच्छा लेख जारी किया गया है।

वह डीन एडवर्ड्स आईफ्रेम उधार तकनीक, प्रत्यक्ष ऑब्जेक्ट एक्सटेंशन, प्रोटोटाइप एक्सटेंशन और ईसीएमएस्क्रिप्ट 5 एक्सेसर गुणों के उपयोग जैसे विभिन्न विकल्पों की पड़ताल करता है।

अंत में कोई सही कार्यान्वयन नहीं है, प्रत्येक के अपने फायदे और कमियां हैं।

निश्चित रूप से एक बहुत अच्छी पढ़ने:

+1

ग्रेट आलेख और सही समय! धन्यवाद। – Tauren

+0

लेख भी वास्तव में कठिन है, हमें केवल एक लिंक के साथ जवाब देने से बचना चाहिए। @laggingreflex उत्तर ने आलेख से स्टैक ओवरव्लो में जवाब लाने का काम किया। –

1

मैंने पहले इस तरह की चीज करने की कोशिश की है; आम तौर पर, यह नहीं होता है। हालांकि, आप संभवतः नकली कर सकते हैं, Array.prototype आंतरिक रूप से तरीकों को लागू करके। यह CustomArray वर्ग, हालांकि केवल क्रोम में परीक्षण किया गया है, मानक 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 

कोई सरणी विधि आप अपने कस्टम कार्यान्वयन में खींचने के लिए मैन्युअल रूप से लागू करना होगा, हालांकि आप शायद सिर्फ चालाक हो सकता है और छोरों इस्तेमाल कर सकते हैं करना, चूंकि हमारे कस्टम push के अंदर क्या होता है वह बहुत सामान्य है।

+0

धन्यवाद, लेकिन यह समाधान वास्तव में एक ऑब्जेक्ट नहीं बनाता है जो एक सरणी की तरह करता है। आप इसे 'पुश' के साथ किए गए तरीकों को जोड़ सकते हैं, लेकिन यह प्रत्यक्ष इंडेक्स मैनिपुलेशन को संभाल नहीं करता है। उदाहरण के लिए, 'एक [5] = 6' करने से लंबाई में कोई बदलाव नहीं आएगा जैसा कि यह वास्तविक ऐरे में होगा।@ सीएमएस उत्तर में जुड़ा आलेख सभी संभावित समाधानों पर जाता है और त्रुटियों को इंगित करता है। – Tauren

+0

@Tauren - आह, मुझे पता था कि कुछ स्पष्ट था जो मैं याद कर रहा था :) साफ लेख - शर्म की बात मुझे पहले नहीं मिला था! – Matchu

0

चेकआउट इस। यह काम करता है क्योंकि यह उन सभी ब्राउज़रों में होना चाहिए जो '__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

इस कस्टमएरे के लिए jsperf यहां: 1. [इंडेक्स द्वारा लिखें/पढ़ें] (http://jsperf.com/custom-array-vs-array/2) 2. [के लिए, प्रत्येक के लिए, मानचित्र] (http://jsperf.com/sdngjkn/12) – termi

0

मैंने एक साधारण एनपीएम मॉड्यूल बनाया है जो इसे हल करता है - 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*/) 
17

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 

मूल उत्तर: (अनुशंसित नहीं है, performance issues का कारण हो सकता है)

से ०५३६९१३६३२१०

कॉपी-पेस्ट और अधिक दृश्यता

के लिए स्वीकार किए जाते हैं जवाब में बताया गया __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 
+0

यह पूरी तरह से काम करता है और शुरुआती स्टैंड प्वाइंट बनाता है। – antihero989

+0

लेकिन क्या Array.isArray सच है? (मैंने चेक किया, और यह करता है) – Sam

+0

अच्छा समाधान, मैं सिर्फ एक भाग को संशोधित करता हूं। बाहर प्रोटोटाइप को परिभाषित करने के बजाय, आप इसे 'this.prototype = newArray' के साथ कन्स्ट्रक्टर के अंदर कर सकते हैं। यह सुनिश्चित नहीं है कि – yosefrow

0

की तरह यहाँ एक पूर्ण उदाहरण है कि IE9 और अधिक से अधिक पर काम करना चाहिए है। संघर्ष और नाम स्थान प्रदूषण से बचने के

  • अपनी ही बंद (या नाम स्थान) में सरणी उपवर्ग रखता है: < = IE8 के लिए आप Array.from, Array.isArray, आदि यह उदाहरण के लिए विकल्प लागू करने के लिए होगा।
  • देशी ऐरे वर्ग से सभी प्रोटोटाइप और गुणों को शामिल करता है।
  • दिखाता है कि अतिरिक्त गुणों और प्रोटोटाइप विधियों को कैसे परिभाषित किया जाए।

यदि आप ES6 का उपयोग कर सकते हैं, तो आपको class SubArray extends Array विधि laggingreflex पोस्ट करना चाहिए।

अरवे से उपclass और विरासत के लिए आवश्यक है। इस अंश के नीचे पूरा उदाहरण है।

///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 
संबंधित मुद्दे