2010-03-24 16 views
14

जॉन रेसिग (jQuery प्रसिद्धि का) Simple JavaScript Inheritance का संक्षिप्त कार्यान्वयन प्रदान करता है। उनके दृष्टिकोण ने चीजों को और भी बेहतर बनाने के अपने प्रयास को प्रेरित किया। मैं Resig के मूल Class.extend समारोह फिर से लिख दिया निम्न लाभ शामिल करने के लिए:सरल जावास्क्रिप्ट विरासत में सुधार

  • प्रदर्शन - वर्ग परिभाषा, वस्तु निर्माण के दौरान कम भूमि के ऊपर, और आधार वर्ग प्रणाली को बुलाती है

  • लचीलापन - के लिए अनुकूलित नए ईसीएमएस्क्रिप्ट 5-संगत ब्राउज़र (जैसे क्रोम), लेकिन पुराने ब्राउज़र (जैसे आईई 6) के लिए समकक्ष "शिम" प्रदान करता है

  • संगतता - सख्त मोड में मान्य है और बेहतर उपकरण संगतता प्रदान करता है (उदा। VSDoc/JSDoc टिप्पणी, विजुअल स्टूडियो IntelliSense, आदि)

  • सादगी - आप एक "निंजा" स्रोत कोड को समझने के लिए होने की जरूरत नहीं है (और यह और भी आसान है कि अगर आप ECMAScript 5 सुविधाओं को खो)

  • सशक्तता - गुजरता है और अधिक "कोने मामले" इकाई परीक्षण (आईई में toString अधिभावी जैसे)

क्योंकि यह लगभग सच होना बहुत अच्छा लगता है, मैं यह सुनिश्चित करने के लिए अपने तर्क नहीं है चाहता हूँ कोई मौलिक खामियां या बी है जीएस, और देखें कि कोई भी सुधार का सुझाव दे सकता है या कोड को खारिज कर सकता है। कि के साथ, मैं classify समारोह मौजूद:

function classify(base, properties) 
{ 
    /// <summary>Creates a type (i.e. class) that supports prototype-chaining (i.e. inheritance).</summary> 
    /// <param name="base" type="Function" optional="true">The base class to extend.</param> 
    /// <param name="properties" type="Object" optional="true">The properties of the class, including its constructor and members.</param> 
    /// <returns type="Function">The class.</returns> 

    // quick-and-dirty method overloading 
    properties = (typeof(base) === "object") ? base : properties || {}; 
    base = (typeof(base) === "function") ? base : Object; 

    var basePrototype = base.prototype; 
    var derivedPrototype; 

    if (Object.create) 
    { 
     // allow newer browsers to leverage ECMAScript 5 features 
     var propertyNames = Object.getOwnPropertyNames(properties); 
     var propertyDescriptors = {}; 

     for (var i = 0, p; p = propertyNames[i]; i++) 
      propertyDescriptors[p] = Object.getOwnPropertyDescriptor(properties, p); 

     derivedPrototype = Object.create(basePrototype, propertyDescriptors); 
    } 
    else 
    { 
     // provide "shim" for older browsers 
     var baseType = function() {}; 
     baseType.prototype = basePrototype; 
     derivedPrototype = new baseType; 

     // add enumerable properties 
     for (var p in properties) 
      if (properties.hasOwnProperty(p)) 
       derivedPrototype[p] = properties[p]; 

     // add non-enumerable properties (see https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute) 
     if (!{ constructor: true }.propertyIsEnumerable("constructor")) 
      for (var i = 0, a = [ "constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf" ], p; p = a[i]; i++) 
       if (properties.hasOwnProperty(p)) 
        derivedPrototype[p] = properties[p]; 
    } 

    // build the class 
    var derived = properties.hasOwnProperty("constructor") ? properties.constructor : function() { base.apply(this, arguments); }; 
    derived.prototype = derivedPrototype; 
    derived.prototype.constructor = derived; 
    derived.prototype.base = derived.base = basePrototype; 

    return derived; 
} 

और उपयोग Resig के लिए लगभग समान निर्माता का नाम (constructor बनाम init) और आधार वर्ग विधि कॉल के लिए वाक्य रचना के अलावा है।

equals(m instanceof Object && m instanceof Minimal && m.constructor === Minimal, true); 
equals(c instanceof Object && c instanceof Class && c.constructor === Class, true); 
equals(s instanceof Object && s instanceof Class && s instanceof SpanishClass && s.constructor === SpanishClass, true); 
equals(p instanceof Object && p instanceof Person && p.constructor === Person, true); 
equals(n instanceof Object && n instanceof Person && n instanceof Ninja && n.constructor === Ninja, true); 
equals(e instanceof Object && e instanceof Person && e instanceof Ninja && e instanceof ExtremeNinja && e.constructor === ExtremeNinja, true); 

equals(c.count(), "John: One. Two. Three."); 
equals(s.count(), "Juan: Uno. Dos. Tres."); 

equals(p.isQuiet, false); 
equals(p.canSing(), true); 
equals(p.sing(), "Figaro!"); 

equals(n.isQuiet, true); 
equals(n.skillLevel, 100); 
equals(n.canSing(), false); 
equals(n.sing(), "Shh!"); 
equals(n.attack(), "Chop!"); 

equals(e.isQuiet, true); 
equals(e.skillLevel, 200); 
equals(e.canSing(), false); 
equals(e.sing(), "Shh!"); 
equals(e.attack(), "Chop! Chop!"); 
equals(e.backflip(), "Woosh!"); 
equals(e.skillLevel, 201); 
equals(e.canSing(), true); 
equals(e.sing(), "Figaro!"); 
equals(e.toString(), "Hello, World!"); 

किसी को भी कुछ भी मेरे दृष्टिकोण बनाम जॉन Resig के original approach गलत दिखाई देता है:

/* Example 1: Define a minimal class */ 
var Minimal = classify(); 

/* Example 2a: Define a "plain old" class (without using the classify function) */ 
var Class = function() 
{ 
    this.name = "John"; 
}; 

Class.prototype.count = function() 
{ 
    return this.name + ": One. Two. Three."; 
}; 

/* Example 2b: Define a derived class that extends a "plain old" base class */ 
var SpanishClass = classify(Class, 
{ 
    constructor: function() 
    { 
     this.name = "Juan"; 
    }, 
    count: function() 
    { 
     return this.name + ": Uno. Dos. Tres."; 
    } 
}); 

/* Example 3: Define a Person class that extends Object by default */ 
var Person = classify(
{ 
    constructor: function(name, isQuiet) 
    { 
     this.name = name; 
     this.isQuiet = isQuiet; 
    }, 
    canSing: function() 
    { 
     return !this.isQuiet; 
    }, 
    sing: function() 
    { 
     return this.canSing() ? "Figaro!" : "Shh!"; 
    }, 
    toString: function() 
    { 
     return "Hello, " + this.name + "!"; 
    } 
}); 

/* Example 4: Define a Ninja class that extends Person */ 
var Ninja = classify(Person, 
{ 
    constructor: function(name, skillLevel) 
    { 
     Ninja.base.constructor.call(this, name, true); 
     this.skillLevel = skillLevel; 
    }, 
    canSing: function() 
    { 
     return Ninja.base.canSing.call(this) || this.skillLevel > 200; 
    }, 
    attack: function() 
    { 
     return "Chop!"; 
    } 
}); 

/* Example 4: Define an ExtremeNinja class that extends Ninja that extends Person */ 
var ExtremeNinja = classify(Ninja, 
{ 
    attack: function() 
    { 
     return "Chop! Chop!"; 
    }, 
    backflip: function() 
    { 
     this.skillLevel++; 
     return "Woosh!"; 
    } 
}); 

var m = new Minimal(); 
var c = new Class(); 
var s = new SpanishClass(); 
var p = new Person("Mary", false); 
var n = new Ninja("John", 100); 
var e = new ExtremeNinja("World", 200); 

और यहाँ मेरी QUnit परीक्षण है जो सभी पास कर रहे हैं? सुझाव और प्रतिक्रिया का स्वागत है!

नोट: उपरोक्त कोड को प्रारंभ में संशोधित किया गया है क्योंकि मैंने शुरुआत में इस प्रश्न को पोस्ट किया था। उपर्युक्त नवीनतम संस्करण का प्रतिनिधित्व करता है। यह देखने के लिए कि यह कैसे विकसित हुआ है, कृपया संशोधन इतिहास देखें।

+0

मैं 'ऑब्जेक्ट.क्रेट' और [traitsjs] (http://traitsjs.org/) की अनुशंसा करता हूं। विरासत जावास्क्रिप्ट में कोई अच्छा नहीं है, ऑब्जेक्ट संरचना – Raynos

+0

का उपयोग करें शायद मैं अभी तक इसका उपयोग नहीं कर रहा हूं, लेकिन लक्षणों का वाक्यविन्यास मेरे सिर को स्पिन बनाता है। मुझे लगता है कि जब तक यह निम्नलिखित प्राप्त नहीं कर लेता है तब तक मैं गुजरता हूं ... – Will

उत्तर

4

इतना तेज़ नहीं है। यह सिर्फ काम नहीं करता है।

पर विचार करें:

var p = new Person(true); 
alert("p.dance()? " + p.dance()); => true 

var n = new Ninja(); 
alert("n.dance()? " + n.dance()); => false 
n.dancing = true; 
alert("n.dance()? " + n.dance()); => false 

base सिर्फ एक और वस्तु डिफ़ॉल्ट सदस्यों को बताया कि बनाया आप यह काम करता है लगता है के साथ प्रारंभ है।

संपादित करें: रिकॉर्ड के लिए, यहां जावास्क्रिप्ट में विरासत की तरह जावा का कार्यान्वयन मेरा है (2006 में जब मैंने Dean Edward's Base.js से प्रेरित किया था) (और he says John's version is just a rewrite of his Base.js पर उससे सहमत हूं)। You can see it in action (and step debug it in Firebug) here

var Base = Class.define(
{ 
    initialize: function(value) // Java constructor equivalent 
    { 
    this.property = value; 
    }, 

    property: undefined, // member variable 

    getProperty: function() // member variable accessor 
    { 
    return this.property; 
    }, 

    foo: function() 
    { 
    alert('inside Base.foo'); 
    // do something 
    }, 

    bar: function() 
    { 
    alert('inside Base.bar'); 
    // do something else 
    } 
}, 
{ 
    initialize: function() // Java static initializer equivalent 
    { 
    this.property = 'Base'; 
    }, 

    property: undefined, // static member variables can have the same 
           // name as non static member variables 

    getProperty: function() // static member functions can have the same 
    {         // name as non static member functions 
    return this.property; 
    } 
}); 

var Derived = Base.extend(
{ 
    initialize: function() 
    { 
    this.base('derived'); // chain with parent class's constructor 
    }, 

    property: undefined, 

    getProperty: function() 
    { 
    return this.property; 
    }, 

    foo: function() // override foo 
    { 
    alert('inside Derived.foo'); 
    this.base(); // call parent class implementation of foo 
    // do some more treatments 
    } 
}, 
{ 
    initialize: function() 
    { 
    this.property = 'Derived'; 
    }, 

    property: undefined, 

    getProperty: function() 
    { 
    return this.property; 
    } 
}); 

var b = new Base('base'); 
alert('b instanceof Base returned: ' + (b instanceof Base)); 
alert('b.getProperty() returned: ' + b.getProperty()); 
alert('Base.getProperty() returned: ' + Base.getProperty()); 

b.foo(); 
b.bar(); 

var d = new Derived('derived'); 
alert('d instanceof Base returned: ' + (d instanceof Base)); 
alert('d instanceof Derived returned: ' + (d instanceof Derived)); 
alert('d.getProperty() returned: ' + d.getProperty()); 
alert('Derived.getProperty() returned: ' + Derived.getProperty()); 

d.foo(); 
d.bar(); 
+0

डर्न, उस बिंदु को इंगित करने के लिए धन्यवाद। मैं इसमें एक और स्टैब ले जाऊंगा, लेकिन मैं शायद जॉन रेजिग के मूल कार्य में सही हो जाऊंगा। – Will

+0

निश्चित रूप से जॉन का कार्य बिल्कुल ठीक है (फिर भी उसे डीन एडवर्ड्स को श्रेय देना चाहिए था)। वैसे भी, सिर जाओ, उस पर एक और स्टैब लें जैसे मैंने वापस किया था: यह मजेदार का हिस्सा है और भाषा के इन आंतरिक कार्यों को समझने से आपको एक बेहतर प्रोग्रामर (महसूस) कर देगा। दिलचस्प बात यह है कि मैंने कभी भी अपने कार्यान्वयन का उपयोग नहीं किया था, यह सिर्फ इसके लिए था :) इसके अलावा मुझे वास्तव में अधिकतम मात्रा में तर्क को कम से कम कोड में कम करने की कोशिश करने का बिंदु नहीं दिख रहा है: सुनिश्चित करें कि मेरा संस्करण वर्बोज़ है, लेकिन जब भी मैं इसे वापस पढ़ता हूं मैं समझता हूं कि क्या हो रहा है। –

+0

मेरा मानना ​​है कि यह सब अब काम करता है। मैंने "base.method.call (this)" वाक्यविन्यास का उपयोग करने के लिए बेस विधि कॉल में मामूली परिवर्तन किया है जो आपके द्वारा रिपोर्ट की गई समस्या को हल करता है। क्या आप कार्यान्वयन के साथ कोई अन्य समस्याएं देखते हैं? मुझे यकीन नहीं है कि यह एक व्यर्थ अभ्यास है। मेरा मानना ​​है कि अधिकतर डेवलपर्स जावास्क्रिप्ट विरासत से दूर भागने वाले कारणों में से एक है "ब्लैक जादू" जो कार्यान्वयन को समझने में शामिल है, या बदसूरत विरासत वाक्यविन्यास में उन्हें मजबूर किया जाता है। मेरा मानना ​​है कि इससे दोनों चिंताओं को दूर करने में मदद मिलती है (बशर्ते यह सही है)। – Will

1

इस बारे में के रूप में सरल रूप में आप प्राप्त कर सकते है:

/** 
* A function that does nothing: to be used when resetting callback handlers. 
* @final 
*/ 
EMPTY_FUNCTION = function() 
{ 
    // does nothing. 
} 

var Class = 
{ 
    /** 
    * Defines a new class from the specified instance prototype and class 
    * prototype. 
    * 
    * @param {Object} instancePrototype the object literal used to define the 
    * member variables and member functions of the instances of the class 
    * being defined. 
    * @param {Object} classPrototype the object literal used to define the 
    * static member variables and member functions of the class being 
    * defined. 
    * 
    * @return {Function} the newly defined class. 
    */ 
    define: function(instancePrototype, classPrototype) 
    { 
    /* This is the constructor function for the class being defined */ 
    var base = function() 
    { 
     if (!this.__prototype_chaining 
      && base.prototype.initialize instanceof Function) 
     base.prototype.initialize.apply(this, arguments); 
    } 

    base.prototype = instancePrototype || {}; 

    if (!base.prototype.initialize) 
     base.prototype.initialize = EMPTY_FUNCTION; 

    for (var property in classPrototype) 
    { 
     if (property == 'initialize') 
     continue; 

     base[property] = classPrototype[property]; 
    } 

    if (classPrototype && (classPrototype.initialize instanceof Function)) 
     classPrototype.initialize.apply(base); 

    function augment(method, derivedPrototype, basePrototype) 
    { 
     if ( (method == 'initialize') 
      &&(basePrototype[method].length == 0)) 
     { 
     return function() 
     { 
      basePrototype[method].apply(this); 
      derivedPrototype[method].apply(this, arguments); 
     } 
     } 

     return function() 
     { 
     this.base = function() 
        { 
         return basePrototype[method].apply(this, arguments); 
        }; 

     return derivedPrototype[method].apply(this, arguments); 
     delete this.base; 
     } 
    } 

    /** 
    * Provides the definition of a new class that extends the specified 
    * <code>parent</code> class. 
    * 
    * @param {Function} parent the class to be extended. 
    * @param {Object} instancePrototype the object literal used to define 
    * the member variables and member functions of the instances of the 
    * class being defined. 
    * @param {Object} classPrototype the object literal used to define the 
    * static member variables and member functions of the class being 
    * defined. 
    * 
    * @return {Function} the newly defined class. 
    */ 
    function extend(parent, instancePrototype, classPrototype) 
    { 
     var derived = function() 
     { 
     if (!this.__prototype_chaining 
      && derived.prototype.initialize instanceof Function) 
      derived.prototype.initialize.apply(this, arguments); 
     } 

     parent.prototype.__prototype_chaining = true; 

     derived.prototype = new parent(); 

     delete parent.prototype.__prototype_chaining; 

     for (var property in instancePrototype) 
     { 
     if ( (instancePrototype[property] instanceof Function) 
      &&(parent.prototype[property] instanceof Function)) 
     { 
      derived.prototype[property] = augment(property, instancePrototype, parent.prototype); 
     } 
     else 
      derived.prototype[property] = instancePrototype[property]; 
     } 

     derived.extend = function(instancePrototype, classPrototype) 
         { 
          return extend(derived, instancePrototype, classPrototype); 
         } 

     for (var property in classPrototype) 
     { 
     if (property == 'initialize') 
      continue; 

     derived[property] = classPrototype[property]; 
     } 

     if (classPrototype && (classPrototype.initialize instanceof Function)) 
     classPrototype.initialize.apply(derived); 

     return derived; 
    } 

    base.extend = function(instancePrototype, classPrototype) 
        { 
        return extend(base, instancePrototype, classPrototype); 
        } 
    return base; 
    } 
} 

और यह कैसे आप इसका इस्तेमाल होता है। इसे http://www.sitepoint.com/javascript-inheritance/ से लिया गया था।

// copyPrototype is used to do a form of inheritance. See http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/# 
// Example: 
// function Bug() { this.legs = 6; } 
// Insect.prototype.getInfo = function() { return "a general insect"; } 
// Insect.prototype.report = function() { return "I have " + this.legs + " legs"; } 
// function Millipede() { this.legs = "a lot of"; } 
// copyPrototype(Millipede, Bug); /* Copy the prototype functions from Bug into Millipede */ 
// Millipede.prototype.getInfo = function() { return "please don't confuse me with a centipede"; } /* ''Override" getInfo() */ 
function copyPrototype(descendant, parent) { 
    var sConstructor = parent.toString(); 
    var aMatch = sConstructor.match(/\s*function (.*)\(/); 
    if (aMatch != null) { descendant.prototype[aMatch[1]] = parent; } 
    for (var m in parent.prototype) { 

    descendant.prototype[m] = parent.prototype[m]; 
    } 
}; 
+0

यह सब ठीक है, लेकिन उपयोगी नहीं है (कोई आधार/सुपर एक्सेस नहीं), न ही सुंदर आईएमओ। – Will

+0

@Will: आप मूल तरीकों तक पहुंच सकते हैं। अधिक स्पष्टीकरण के लिए लिंक देखें। –

+0

@ जॉनफिशर मुझे लगता है कि आपका मतलब था 'बग।कोड टिप्पणियों में 'Insert.prototype' के बजाय प्रोटोटाइप'। –

5

कुछ समय पहले, मैं जे एस के लिए कई वस्तु सिस्टम को देखा और यहां तक ​​कि, मेरे अपने के कुछ ही लागू किया जैसे class.js (ES5 version) और proto.js

कारण मैंने कभी उनका उपयोग नहीं किया: आप उसी मात्रा में कोड लिखना समाप्त कर देंगे। बिंदु में मामला:

var Person = Class.extend({ 
    init: function(isDancing) { 
     this.dancing = isDancing; 
    }, 

    dance: function() { 
     return this.dancing; 
    } 
}); 

var Ninja = Person.extend({ 
    init: function() { 
     this._super(false); 
    }, 

    swingSword: function() { 
     return true; 
    } 
}); 

19 लाइनों, 264 बाइट्स: Resig की निंजा-उदाहरण (केवल कुछ खाली स्थान के) जोड़ा। Object.create() साथ

स्टैंडर्ड जे एस (एक ECMAScript 5 समारोह है जो, लेकिन हमारे प्रयोजनों के लिए एक कस्टम ES3 clone() कार्यान्वयन द्वारा बदला जा सकता है):

function Person(isDancing) { 
    this.dancing = isDancing; 
} 

Person.prototype.dance = function() { 
    return this.dancing; 
}; 

function Ninja() { 
    Person.call(this, false); 
} 

Ninja.prototype = Object.create(Person.prototype); 

Ninja.prototype.swingSword = function() { 
    return true; 
}; 

17 लाइनों, 282 बाइट्स। इमो, अतिरिक्त बाइट वास्तव में एक अलग ऑब्जेक्ट सिस्टम की अतिरिक्त जटिलता को कम नहीं कर रहे हैं। कुछ कस्टम फ़ंक्शंस जोड़कर मानक उदाहरण को कम करना आसान है, लेकिन फिर से: यह वास्तव में इसके लायक नहीं है।

+0

+1, – Alsciende

+1

का बहुत दिलचस्प बिंदु मैं आपके जैसा ही सोचता था, लेकिन अब मुझे असहमत होना है। असल में, सभी जॉन रेसिग और मैंने किया है एक एकल विधि ("विस्तार") बनाना जो आपके उपर्युक्त उदाहरण में सटीक ऑब्जेक्ट/प्रोटोटाइप व्यवहार को तारित करता है (यानी यह एक नई ऑब्जेक्ट सिस्टम नहीं है)। एकमात्र अंतर यह है कि विस्तार विधि का उपयोग करते समय वाक्यविन्यास छोटा, कड़ा और कम त्रुटि प्रवण होता है। – Will

+0

इस पर प्रतिबिंबित करने के बाद, मैं मानता हूं कि मानक वायर-अप सिंटैक्स का उपयोग विस्तारित वाक्यविन्यास का उपयोग करने जितना छोटा है। मुझे अभी भी लगता है कि उत्तरार्द्ध बहुत साफ है, कम त्रुटि प्रवण है, और "कॉन्फ़िगरेशन पर सम्मेलन" दृष्टिकोण से अधिक है, अगर यह समझ में आता है। – Will

संबंधित मुद्दे