2013-05-20 12 views
19

का उपयोग करके सरल जावास्क्रिप्ट विरासत मैंने कुछ वर्षों से सोचा है कि लोग मॉड्यूल-पैटर्न-एस्क्यू कन्स्ट्रक्टर पैटर्न और सामान्य प्रोटोटाइप विरासत के बिना विरासत करने के बारे में सोचते हैं। गैर-सिंगलटन जेएस कक्षाओं के लिए प्रोग्रामर मॉड्यूल पैटर्न का उपयोग क्यों नहीं करते हैं? मेरे लिए लाभ हैं:

  • बहुत स्पष्ट सार्वजनिक और निजी गुंजाइश (आसान कोड और एपीआई को समझने के लिए) में
  • नहीं $ .proxy के माध्यम से 'इस' सूचक ट्रैक करने के लिए की आवश्यकता होगी, (एफ एन, इस) कॉलबैक
  • इवेंट हैंडलर इत्यादि के साथ कोई और var = this, आदि। जब भी मुझे 'यह' दिखाई देता है, मुझे पता है कि यह संदर्भ है जिसे कॉलबैक में पारित किया जा रहा है, यह ऐसा कुछ नहीं है जिसे मैं जानना चाहता हूं ऑब्जेक्ट उदाहरण

नुकसान:

  • छोटे पर्फ़ गिरावट
  • जोखिम संभव डौग Crockford से "उंगली की वाग"?

इस (बस किसी भी js कंसोल में चलाने)

var Animal = function() { 
    var publicApi = { 
     Name: 'Generic', 
     IsAnimal: true, 
     AnimalHello: animalHello, 
     GetHelloCount:getHelloCount 
    }; 

    var helloCount = 0; 

    function animalHello() { 
     helloCount++; 
     console.log(publicApi.Name + ' says hello (animalHello)'); 
    } 

    function getHelloCount(callback) { 
     callback.call(helloCount); 
    } 

    return publicApi; 
}; 

var Sheep = function (name) { 
    var publicApi = { 
     Name: name || 'Woolie', 
     IsSheep: true, 
     SheepHello: sheepHello 
    }; 

    function sheepHello() { 
     publicApi.AnimalHello(); 
     publicApi.GetHelloCount(function() { 
      console.log('i (' + publicApi.Name + ') have said hello ' + this + ' times (sheepHello anon callback)'); 
     }); 
    } 

    publicApi = $.extend(new Animal(), publicApi); 
    return publicApi; 
}; 

var sheepie = new Sheep('Sheepie'); 
var lambie = new Sheep('Lambie'); 

sheepie.AnimalHello(); 
sheepie.SheepHello(); 
lambie.SheepHello(); 

मेरा प्रश्न है इस दृष्टिकोण है कि मैं नहीं दिखाई दे रहा करने के लिए कमियां क्या हैं पर विचार करें? क्या यह एक अच्छा दृष्टिकोण है?

धन्यवाद!

[अद्यतन]

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

उत्तर

35

मैं इसे प्रदर्शन की मुद्दा करने पर निर्भर करता है। आपने बताया कि छोटे प्रदर्शन में गिरावट आई है, लेकिन यह वास्तव में आवेदन के पैमाने पर निर्भर करता है (2 भेड़ बनाम 1000 भेड़)। मूलरूप विरासत नजरअंदाज नहीं किया जाना चाहिए और हम एक प्रभावी मॉड्यूल पैटर्न कार्यात्मक और मूलरूप विरासत के मिश्रण का उपयोग कर बना सकते हैं।

पद JS - Why use Prototype?, प्रोटोटाइप की सुंदरता में से एक में उल्लेख किया है कि आप केवल केवल एक बार मूलरूप सदस्यों प्रारंभ करने की आवश्यकता है, जबकि निर्माता के भीतर सदस्यों को एक उदाहरण के के लिए बनाई गई हैं। वास्तव में, आप एक नई वस्तु बनाये बिना सीधे प्रोटोटाइप तक पहुंच सकते हैं।

Array.prototype.reverse.call([1,2,3,4]); 
//=> [4,3,2,1] 

function add() { 
    //convert arguments into array 
    var arr = Array.prototype.slice.call(arguments), 
     sum = 0; 
    for(var i = 0; i < arr.length; i++) { 
     sum += arr[i]; 
    } 

    return sum; 
} 

add(1,2,3,4,5); 
//=> 15 

अपने कार्यों में, वहाँ अतिरिक्त भूमि के ऊपर पूरी तरह से नए पशु हर बार एक निर्माता शुरू हो जाती है बना सकते हैं और भेड़ के लिए है। Animal.name जैसे कुछ सदस्य प्रत्येक उदाहरण के साथ बनाए जाते हैं, लेकिन हम जानते हैं कि Animal.name स्थिर है इसलिए इसे एक बार तुरंत चालू करना बेहतर होगा। चूंकि आपका कोड इंगित करता है कि Animal.name सभी जानवरों में समान होना चाहिए, Animal.prototype.name को अपडेट करके Animal.name को सभी उदाहरणों के लिए अपडेट करना आसान है यदि हम प्रोटोटाइप पर ले जाते हैं।

इस

var animals = []; 
for(var i = 0; i < 1000; i++) { 
    animals.push(new Animal()); 
} 

कार्यात्मक विरासत/मॉड्यूल पैटर्न पर विचार करें

function Animal() { 

    return { 
     name : 'Generic', 
     updateName : function(name) { 
      this.name = name; 
     } 
    } 

} 


//update all animal names which should be the same 
for(var i = 0;i < animals.length; i++) { 
    animals[i].updateName('NewName'); //1000 invocations ! 
} 

बनाम प्रोटोटाइप

Animal.prototype = { 
name: 'Generic', 
updateName : function(name) { 
    this.name = name 
}; 
//update all animal names which should be the same 
Animal.prototype.updateName('NewName'); //executed only once :) 

अपने वर्तमान मॉड्यूल पैटर्न के साथ ऊपर दिखाए गए के रूप में हम को अद्यतन करने के गुण में दक्षता खोना यह सभी सदस्यों के लिए आम होना चाहिए।

आप के बारे में दृश्यता concered रहे हैं, तो मैं एक ही मॉड्यूलर विधि आप वर्तमान में निजी सदस्यों को संपुटित करने के लिए, लेकिन यह भी इन सदस्यों वे पहुँचा जा करने की जरूरत है चाहिए तक पहुँचने के लिए priviledged members का उपयोग का उपयोग कर रहे प्रयोग करेंगे। Priviledged सदस्य सार्वजनिक सदस्य हैं जो निजी चरों तक पहुंचने के लिए एक इंटरफ़ेस प्रदान करते हैं। अंत में प्रोटोटाइप में आम सदस्यों को जोड़ें।

बेशक इस मार्ग पर जाने के लिए, आपको इसका ट्रैक रखने की आवश्यकता होगी। यह सच अपने कार्यान्वयन में है कि वहाँ

  • नहीं $ .proxy के माध्यम से 'इस' सूचक ट्रैक करने के लिए की आवश्यकता होगी, (एफ एन, इस) कॉलबैक में
  • कोई और अधिक वर है कि इस, आदि = है इवेंट हैंडलर इत्यादि के साथ। जब भी मैं 'यह' देखता हूं, मुझे पता है कि यह संदर्भ है जिसे कॉलबैक में पारित किया जा रहा है, यह ऐसा कुछ नहीं है जिसे मैं अपने ऑब्जेक्ट उदाहरण को जानने के लिए ट्रैकिंग कर रहा हूं।

, लेकिन आप एक बहुत बड़ी वस्तु हर बार जो कुछ मूलरूप वंशानुक्रम का उपयोग की तुलना में अधिक स्मृति की खपत होगी पैदा कर रहे होते हैं।

घटना प्रतिनिधिमंडल सादृश्य

एक सादृश्य प्रोटोटाइप का उपयोग करके प्रदर्शन प्राप्त करने के लिए के रूप में घटना प्रतिनिधिमंडल का उपयोग करते समय डोम में चालाकी करके प्रदर्शन में सुधार किया है।Event Delegation in Javascript

मान लें कि आपके पास बड़ी किराने की सूची है। हाँ।

<ul ="grocery-list"> 
    <li>Broccoli</li> 
    <li>Milk</li> 
    <li>Cheese</li> 
    <li>Oreos</li> 
    <li>Carrots</li> 
    <li>Beef</li> 
    <li>Chicken</li> 
    <li>Ice Cream</li> 
    <li>Pizza</li> 
    <li>Apple Pie</li> 
</ul> 

मान लें कि आप जिस आइटम पर क्लिक करते हैं उसे लॉग करना चाहते हैं। एक कार्यान्वयन किया जाएगा हर आइटम (बुरा) के लिए एक ईवेंट हैंडलर संलग्न करने के लिए है, लेकिन अगर हमारी सूची बहुत लंबी है वहाँ की घटनाओं का एक बहुत का प्रबंधन करने के लिए किया जाएगा।

var list = document.getElementById('grocery-list'), 
groceries = list.getElementsByTagName('LI'); 
//bad esp. when there are too many list elements 
for(var i = 0; i < groceries.length; i++) { 
    groceries[i].onclick = function() { 
     console.log(this.innerHTML); 
    } 
} 

एक और कार्यान्वयन हो माता पिता (अच्छा) को एक ईवेंट हैंडलर देते हैं और उस एक माता पिता होने वाले सभी क्लिक संभाल करने के लिए होगा। आप देख सकते हैं इस आम कार्यक्षमता के लिए एक प्रोटोटाइप का उपयोग कर के समान है और काफी बेहतर बनाता प्रदर्शन

कार्यात्मक का संयोजन का उपयोग कर
//one event handler to manage child elements 
list.onclick = function(e) { 
    var target = e.target || e.srcElement; 
    if(target.tagName = 'LI') { 
     console.log(target.innerHTML); 
    } 
} 

पुनर्लेखन/मूलरूप विरासत

मुझे लगता है कि कार्यात्मक/मूलरूप विरासत के संयोजन लिखा जा सकता है एक आसान तरीके से समझा जा सकता। मैं ऊपर वर्णित तकनीकों का उपयोग कर अपने कोड फिर से लिखा है।

var Animal = function() { 

    var helloCount = 0; 
    var self = this; 
    //priviledge methods 
    this.AnimalHello = function() { 
     helloCount++; 
     console.log(self.Name + ' says hello (animalHello)'); 
    }; 

    this.GetHelloCount = function (callback) { 
     callback.call(null, helloCount); 
    } 

}; 

Animal.prototype = { 
    Name: 'Generic', 
    IsAnimal: true 
}; 

var Sheep = function (name) { 

    var sheep = new Animal(); 
    //use parasitic inheritance to extend sheep 
    //http://www.crockford.com/javascript/inheritance.html 
    sheep.Name = name || 'Woolie' 
    sheep.SheepHello = function() { 
     this.AnimalHello(); 
     var self = this; 
     this.GetHelloCount(function(count) { 
      console.log('i (' + self.Name + ') have said hello ' + count + ' times (sheepHello anon callback)'); 
     }); 
    } 

    return sheep; 

}; 

Sheep.prototype = new Animal(); 
Sheep.prototype.isSheep = true; 

var sheepie = new Sheep('Sheepie'); 
var lambie = new Sheep('Lambie'); 

sheepie.AnimalHello(); 
sheepie.SheepHello(); 
lambie.SheepHello(); 

निष्कर्ष

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

+0

आपका 'Animal.prototype.updateName (' NewName ');' सभी जानवरों को संशोधित नहीं करता है - यह केवल उन सभी के द्वारा प्राप्त 'जेनेरिक' नाम को संशोधित करता है। बीटीडब्ल्यू, आपकी संयोजन लिपि में कुछ बुरे प्रथाएं हैं, सभी मामलों में काम नहीं करती हैं और अभी भी 'भेड़' के लिए कार्यात्मक/परजीवी विरासत का उपयोग करती हैं। यह ओपी के किसी भी फायदे को प्रदर्शित नहीं करता है ... – Bergi

+0

हां आप 'Animal.prototype.updateName ('NewName') 'के बारे में सही हैं, केवल' जेनेरिक 'नाम अपडेट कर रहे हैं, लेकिन यह इच्छित प्रभाव है। सभी भेड़ों के पास एक सामान्य नाम होता है (क्योंकि वे अभी भी जानवर हैं) और उनका अपना नाम है। – AnthonyS

+0

मेरे कोड को अपडेट करने के लिए स्वतंत्र महसूस करें। मेरा मुद्दा यह है कि $ .extend का वर्तमान उपयोग 'Animal.Name' या 'Animal.IsAnimal' जैसी सामान्य गुणों को फिर से परिभाषित करता है और नाम जैसे कुछ गुण बाद में ओवरराइट किए जाते हैं। $ .extend की पूर्ण क्षमता लेने के लिए मुझे लगता है कि कोड का उपयोग किया जाना चाहिए ताकि विस्तार केवल बेस भेड़ ऑब्जेक्ट बनाने के लिए होता है, और फिर कन्स्ट्रक्टर/फ़ंक्शन का उपयोग मानों को init करने के लिए किया जाना चाहिए (जैसे 'मेकशीप' फ़ंक्शन वर्णित) हर बार अनावश्यक एक्सटेंशन करने के बजाय। – AnthonyS

2

एक मॉड्यूल पैटर्न-esque निर्माता पैटर्न

यह परजीवी विरासत या कार्यात्मक विरासत के रूप में जाना जाता है।

मेरे लिए लाभ हैं:

  • बहुत स्पष्ट सार्वजनिक और निजी गुंजाइश (आसान इस कोड और एपीआई को समझने के लिए)

ही शास्त्रीय निर्माता के लिए सच है पैटर्न। बीटीडब्ल्यू, आपके वर्तमान कोड में यह स्पष्ट नहीं है कि animalHello और getHelloCount निजी या नहीं माना जाता है। अगर आप उस परवाह करते हैं तो निर्यातित ऑब्जेक्ट में सही ढंग से परिभाषित करना बेहतर होगा।कॉलबैक में (एफ एन, इस)

  • नहीं $ .proxy के माध्यम से 'इस' सूचक ट्रैक करने के लिए की आवश्यकता होगी,
  • कोई और अधिक वर कि ईवेंट हैंडलर्स, आदि के साथ इस, आदि = जब भी मैं एक को देखने के 'यह', मुझे पता है कि यह संदर्भ है जिसे कॉलबैक में पारित किया जा रहा है, यह ऐसा कुछ नहीं है जिसे मैं अपने ऑब्जेक्ट उदाहरण को जानने के लिए ट्रैकिंग कर रहा हूं।

यह मूल रूप से वही है। आप या तो इस समस्या को हल करने के लिए that dereference या बाध्यकारी का उपयोग करें। और मैं इसे एक बड़ा नुकसान के रूप में नहीं देखता, क्योंकि स्थिति जहां आप ऑब्जेक्ट "विधियों" का उपयोग सीधे कॉलबैक के रूप में करते हैं, बहुत दुर्लभ हैं - और संदर्भ के अलावा आप अक्सर अतिरिक्त तर्कों को खिलाना चाहते हैं। बीटीडब्ल्यू, आप अपने कोड में that संदर्भ का भी उपयोग कर रहे हैं, इसे publicApi कहा जाता है।

प्रोग्रामर गैर-सिंगलटन जेएस कक्षाओं के लिए मॉड्यूल पैटर्न का उपयोग क्यों नहीं करते हैं?

अब, आपने पहले से ही कुछ नुकसान का नाम दिया है। इसके अतिरिक्त, आप प्रोटोटाइपिकल विरासत खो रहे हैं - इसके सभी फायदे (सादगी, गतिशीलता, instanceof, ...) के साथ। बेशक, ऐसे मामले हैं जहां वे आवेदन नहीं करते हैं, और आपका कारखाना कार्य पूरी तरह ठीक है। यह वास्तव में इन मामलों में प्रयोग किया जाता है।

publicApi = $.extend(new Animal(), publicApi); 
… 
… new Sheep('Sheepie'); 

अपने कोड के इन भागों एक छोटे से रूप में अच्छी तरह भ्रमित कर रहे हैं। आप चर को एक अलग वस्तु के साथ ओवरराइट कर रहे हैं, और यह आपके कोड के मध्य-अंत में होता है। यह एक "घोषणा" के रूप में देखते बेहतर होगा (आप यहाँ माता पिता के गुण विरासत में कर रहे हैं!) और अपने समारोह के शीर्ष पर डाल दिया - अधिकार के रूप में var publicApi = $.extend(Animal(), {…});

इसके अलावा, आप यहाँ new कीवर्ड का उपयोग नहीं करना चाहिए । आप कंस्ट्रक्टर्स के रूप में कार्य का उपयोग नहीं करते हैं, और आप उदाहरणों कि Animal.prototype से विरासत (जो आपके निष्पादन धीमा) बनाने के लिए नहीं करना चाहती। साथ ही, यह उन लोगों को भ्रमित करता है जो new के साथ उस कन्स्ट्रक्टर आमंत्रण से प्रोटोटाइपिकल विरासत की अपेक्षा कर सकते हैं। स्पष्टता के लिए, आप makeAnimal और makeSheep पर फ़ंक्शंस का नाम भी बदल सकते हैं।

नोडज में इसका तुलनात्मक दृष्टिकोण क्या होगा?

इस डिजाइन पैटर्न पूरी तरह से पर्यावरण के स्वतंत्र है। यह सिर्फ ग्राहक के रूप में की तरह के रूप में और हर दूसरे ECMAScript कार्यान्वयन में Node.js में काम करेंगे। इसके कुछ पहलू भाषा-स्वतंत्र भी हैं।

+0

क्या आपका मतलब 'exampleof' है जहां आपके पास' typeof' है? प्रोटोटाइप विरासत के साथ या बिना, 'टाइपोफ़' केवल आपको 'ऑब्जेक्ट "या' "फ़ंक्शन" देने के लिए जा रहा है, कभी भी "पशु" या' पशु 'नहीं। – gilly3

+0

@ gilly3: ओह, हां बिल्कुल। चौकस पढ़ने के लिए धन्यवाद :-) – Bergi

2
अपने दृष्टिकोण के साथ

, तो आप ऐसा आसानी से कार्यों को ओवरराइड और सुपर समारोह कॉल करने के लिए सकेंगे।

function foo() 
{ 
} 

foo.prototype.GetValue = function() 
{ 
     return 1; 
} 


function Bar() 
{ 
} 

Bar.prototype = new foo(); 
Bar.prototype.GetValue = function() 
{ 
    return 2 + foo.prototype.GetValue.apply(this, arguments); 
} 

इसके अलावा, प्रोटोटाइप दृष्टिकोण में, आप ऑब्जेक्ट के सभी उदाहरणों के बीच डेटा साझा कर सकते हैं।

function foo() 
{ 
} 
//shared data object is shared among all instance of foo. 
foo.prototype.sharedData = { 
} 

var a = new foo(); 
var b = new foo(); 
console.log(a.sharedData === b.sharedData); //returns true 
a.sharedData.value = 1; 
console.log(b.sharedData.value); //returns 1 

प्रोटोटाइप दृष्टिकोण का एक और लाभ स्मृति को बचाने के लिए होगा।

function foo() 
{ 
} 

foo.prototype.GetValue = function() 
{ 
    return 1; 
} 

var a = new foo(); 
var b = new foo(); 
console.log(a.GetValue === b.GetValue); //returns true 

अपने दृष्टिकोण की में जबकि,

var a = new Animal(); 
var b = new Animal(); 
console.log(a.AnimalHello === b.AnimalHello) //returns false 

यह प्रत्येक नए उद्देश्य के साथ का मतलब है, कार्यों का एक नया उदाहरण बनाई गई हैं, जहां के रूप में यह प्रोटोटाइप दृष्टिकोण के बैठाना सभी वस्तुओं के बीच साझा किया जाता है। यह अभ्यस्त कुछ उदाहरणों के बैठाना ज्यादा फर्क लेकिन जब उदाहरणों में से बड़ी संख्या में बनाई गई हैं, यह काफी फर्क दिखा सकती है।

इसके अलावा, प्रोटोटाइप की एक और शक्तिशाली विशेषता एक बार सभी ऑब्जेक्ट्स बनने के बाद होगी, फिर भी आप एक ही समय में सभी वस्तुओं के बीच गुणों को बदल सकते हैं (केवल अगर वे ऑब्जेक्ट सृजन के बाद परिवर्तित नहीं होते हैं)।

function foo() 
{ 
} 
foo.prototype.name = "world"; 

var a = new foo(); 
var b = new foo(); 
var c = new foo(); 
c.name = "bar"; 

foo.prototype.name = "hello"; 

console.log(a.name); //returns 'hello' 
console.log(b.name); //returns 'hello' 
console.log(c.name); //returns 'bar' since has been altered after object creation 

निष्कर्ष: प्रोटोटाइप दृष्टिकोण का ऊपर उल्लेख किया फायदे आपके आवेदन के लिए तो उपयोगी नहीं हैं, तो अपने दृष्टिकोण बेहतर होगा।

+0

बेशक आप विधियों को ओवरराइट कर सकते हैं: 'फ़ंक्शन बार() {var that = Foo(), superValue = that.value; that.value = function() {वापसी सुपरवैल्यू() + "अधिलेखित"; }; वापस लौटें; } ' – Bergi

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