2013-05-20 42 views
19

我想了好幾年現在人們想到用模塊式模式構造器模式繼承,而沒有正常的原型繼承。爲什麼程序員不爲非單例式js類使用模塊模式?對我來說,優點是:

  • 很清楚的公共和私人範圍(易於理解的代碼和API)
  • 沒有需要追蹤通過$ .proxy的「這個」指針(FN,這一點)在回調
  • 沒有更多的var = this,等等事件處理程序等。每當我看到一個'this'時,我知道它是傳遞給回調的上下文,它不是我追蹤的知道我的對象實例。

缺點:

  • 小PERF降解
  • 風險可能由道格·克羅克福德的 「手指搖」?

考慮這個(以防任何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用於訪問這些成員,他們應該需要達到相同的模塊化方法。 特權會員是提供訪問私有變量的接口的公共成員。最後添加通用成員到原型。

當然要走這條路,你需要跟蹤這個。 的確,在您的實現有

  • 沒有需要通過$ .proxy跟蹤this指針(FN,這一點)在回調中
  • 沒有更多的變種是=這等。事件處理程序等等。每當我看到一個'this'時,我知道這是傳遞給回調的上下文,但它不是我正在追蹤的知道我的對象實例的東西。

,但你每次都將相較於使用一些原型繼承佔用更多的內存時間創建一個非常大的對象。

事件代表團作爲比喻

類比通過使用原型性能獲得通過操縱DOM在使用事件代表團提高了性能。Event Delegation in Javascript

比方說你有一個大的雜貨店list.Yum。

<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');'不修改所有動物 - 它只修改所有它們繼承的'Generic'名稱。順便說一句,你的組合腳本包含一些不好的做法,在所有情況下都不起作用,並且仍然使用'Sheep'的功能/寄生繼承。它沒有顯示OP的一個優點... – Bergi

+0

是的,你對'Animal.prototype.updateName('NewName')'只更新'Generic' name'是正確的,但這是預期的效果。所有羊都有一個通用名稱(因爲它們仍然是動物)和他們自己的名字。 – AnthonyS

+0

隨時更新我的​​代碼。我的觀點是,當前使用$ .extend會重新生成諸如「Animal.Name」或「Animal.IsAnimal」之類的常用屬性,並且某些屬性(如Name)會在稍後被覆蓋。爲了充分利用$ .extend,我認爲應該使用代碼,以便擴展只發生一次以構建基本的Sheep對象,然後應該使用構造函數/函數來初始化值(例如'makeSheep'函數描述),而不是每次都進行冗餘擴展。 – AnthonyS

2

一個模塊的模式式的構造圖案

這被稱爲寄生繼承官能繼承

對我的優點是:

  • 非常明確的公共和私有範圍(容易理解這些代碼和API)

這同樣適用於經典的構造真模式。順便說一下,在你現在的代碼中,animalHellogetHelloCount是否應該是私人的並不是很清楚。在導出的對象文本中定義它們可能會更好,如果你關心的話。

  • 沒有需要追蹤通過$ .proxy的「這個」指針(FN,這一點)在回調中
  • 沒有更多的變種,與事件處理程序,等等。每當我看到這個=等'this',我知道它是傳遞給回調的上下文,它不是我追蹤的知道我的對象實例的東西。

這基本上是一樣的。您可以使用that解除引用綁定來解決此問題。我認爲這不是一個巨大的缺點,因爲直接將對象「方法」用作回調的情況非常罕見 - 除了上下文之外,您通常還需要提供其他參數。順便說一句,你在代碼中也使用了that引用,它在那裏被稱爲publicApi

爲什麼程序員不使用非單身人士js類的模塊模式?

現在,您已經自己命名了一些缺點。此外,你正在失去原型繼承 - 具有它的所有優點(簡單,活力,instanceof,...)。當然,有些情況下他們不適用,並且您的工廠功能非常好。這確實用於這些情況。

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

代碼的這些部分是有點混亂,以及。你在這裏用不同的對象覆蓋變量,它發生在你的代碼的中間到尾部。最好把它看作是一個「聲明」(你繼承了這裏的父屬性!),並將它放在函數的頂部 - 正如var publicApi = $.extend(Animal(), {…});

另外,你不應該在這裏使用new關鍵字。您不使用這些函數作爲構造函數,並且您不希望創建從Animal.prototype繼承的實例(這會減慢執行速度)。此外,它將可能期望原型繼承的人從該構造函數調用中弄混到new。爲了清楚起見,您甚至可以將函數重命名爲makeAnimalmakeSheep

在nodejs中對此的一種可比較的方法是什麼?

這種設計模式完全與環境無關。它將在Node.js中工作,就像在客戶端和其他每個EcmaScript實現中一樣。它的某些方面甚至是語言無關的。

+0

你的意思是'instanceof'你有'typeof'嗎?有或沒有原型繼承,'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

當然你可以覆蓋方法:'Bar(){var that = Foo(),superValue = that.value; that.value = function(){return superValue()+「覆蓋」; };回報; }' – Bergi