探討在 JavaScript 實作類別的方式在 javascript 中有很多方式來建立對象,所以建立對象的方式使用起來非常靈活。那麼,到底哪一種方式是最恰當的對象建立方式呢?構造 模式,原型模式還是對象原意模式(Object literal)呢? 但這些模式具體是怎麼回事呢? 在開始講解之前,讓我們先清楚地介紹一下關於 javascript 基本知識。 有沒有可能在 javascript 中實現物件導向編程的方式呢? 答案是可能的,javascript 是可以建立對象的!這種對象可以包含資料及能夠操作資料的方法,甚至可以包含其他對象。它沒有類但擁有構 造函數;它沒有類繼承機制,但是可以通過原型(prototype)實現繼承。 現在看起來,我們已經瞭解了在 javascript 中建立對象及實現基於對象編程時所必須的組成部分。 我們都知道 javascript 擁有私人變數。一個通過“var”關鍵字定義的變數,只能在函數體中被訪問,而不能在函數外被訪問。那麼,如果 我們不通過使用“var”關鍵字來定義變數會怎樣呢?我們現在不對這個問題進行深入探討,可能是通過“this”進行訪問的,我會在另外的 時間來詳細講述這個問題。 現在回到之前的問題。到底哪一種方式是最恰當的對象建立方式呢?讓我們用已經知曉的知識,通過建立Person的對象是來實驗一下。 [javascript] var Person = { firstName : 'John', lastName : 'Cody', fullName : '', message : '', createFullName : function () { fullName = this.firstName + ' ' + this.lastName; }, changeMessage : function (msg) { this.message = msg; }, getMessage : function () { this.createFullName(); return this.message + ' ' + fullName; } } Person.firstName = 'Eli'; Person.lastName = 'Flowers' Person.changeMessage('welcome'); var message = Person.getMessage(); // welcome Eli Flowers alert(message); 這是對象原意模式(literal pattern)。這非常接近我們常建立對象的方式。如果你不需要關心私人/封裝的成員,並且你知道不將建立這個對 象的執行個體。那麼,這種方式將會很適合你。公有的成員可以做所有私人成員的事情,不是嗎?但是,這不是一個類,而是一個對象而已,不能 被建立執行個體並且不能被繼承。 讓我們嘗試下其他的方面: [javascript] var Person = function() { this.firstName = 'John'; this.lastName = 'Cody'; var fullName = ''; this.message = ''; var _that = this; var createFullName = function () { fullName = _that.firstName + ' ' + _that.lastName; } this.changeMessage = function (msg) { this.message = msg; } this.getMessage = function () { createFullName(); return this.message + ' ' + fullName; } } var person1 = new Person(); person1.firstName = 'Eli'; person1.lastName = 'Flowers' person1.changeMessage('welcome'); var message = person1.getMessage(); // welcome Eli Flowers alert(message); 這是一種構造模式的執行個體(Constructor Pattern)。那麼,這是類還是對象呢?應該 兩種都算是吧。我們能夠在當請求時把它當做對象Person 來使用。它畢竟也只是一個函數而已。然而,它可以通過使用“new”關鍵字來實現建立新的執行個體功能。 在使用這種方式時,我們需要時刻記住如下要點: 1. 無論什麼時候這個函數被調用時,它擁有一個特別的變數叫做“this”並且可以在全域範圍內使用。全域範圍依賴於這個函數自身的作用 範圍。 2. 無論什麼時候通過“new”關鍵字建立這個函數的執行個體,“this”變數指向這個函數本身,並且這個“new”操作將會影響到函數體中的代 碼被執行。這也正是構造模式。 3. 任何附加到“this”變數下的變數都會成為公有屬性並且任何通過“var”關鍵字定義的變數都將是屬於私人屬性。 4. 一個附加到“this”下的函數叫做特權函數,它可以訪問所有的私人變數以及被附加到“this”下的函數及變數。 5. 私人函數可以訪問到其他私人變數及私人函數。 6. 私人函數不能直接存取被附加到“this”變數和函數。我們可以通過建立一個私人變數“_that”並且將它賦值為“this”的方式實現。 7. 任何私人變數及函數對於其他私人函數及其他被附加到“this”的函數是可用的。這完全是可能的再javascript的作用範圍下。 8. 一個變數:不是通過“var”關鍵字,也不是附加到“this”變數上以獲得全域作用範圍的。例如,對於一個自訂函數的作用範圍。需要 再一次地瞭解範圍及叢集的知識。 這已經實現了我們想要的大部分要求了,但是,有時候“this”和“that”這兩個入口變數很容易造成給人們帶來疑惑。尤其對於那些一直堅 持要求純粹私人的人來說,更容易迷惑。 讓我們再稍微修改下試試吧。 [javascript] var Person = function () { //private var firstName = 'John'; var lastName = 'Cody'; var fullName = ''; var message = ''; var createFullName = function () { fullName = firstName + ' ' + lastName; } //public setters var setMessage = function (msg) { message = msg; } var setFirstName = function (fName) { firstName = fName; } var setLastName = function (lName) { lastName = lName; } var getMessage = function () { createFullName(); return message + ' ' + fullName; } //functions exposed public return { setFirstName: setFirstName, setLastName: setLastName, setMessage: setMessage, getMessage: getMessage }; }; var person1 = new Person(); person1.setFirstName('Eli'); person1.setLastName('Flowers'); person1.setMessage('welcome'); var message = person1.getMessage(); // welcome Eli Flowers alert(message); 這是一個顯示模式(Revealing Pattern)。非常感謝 Christian Heilmann。使用這種模式的方式就是把請求的"getters" 和 "setters" 當 作屬性使用。我們很多都是從傳統的Java編程中找到這樣的身影並且很明顯地知道實現它其實並不複雜。這同樣是一種類似於當類繼承自一個 介面的情況。 這種模式大部分方面都實現得很好,僅僅只有一個很微小的問題。每一次當一個類的執行個體被建立時。這個新建立的對象獲得了一份變數和函數 的拷貝。現在,拷貝變數是沒有問題的,我們希望對於每一個對象的資料都是屬於對象自身的,那麼,成員函數呢?他們僅僅是操作資料而已 。那麼,為什麼需要拷貝他們呢? 這正是原型模式(Prototype)的優勢所在。在所有執行個體中,所有東西都是被建立成一個原型,並且能夠相互分享。我們僅僅需要做的就是依 據原型建立共有函數。 [javascript] var Person = function () { //private var welcomeMessage = 'welcome'; var fullName = ''; var firstName = ''; var lastName = ""; var createFullName = function () { Person.prototype.setFirstName('asdsad'); fullName = firstName + ' ' + lastName; }; //constructor var Person = function () { }; //will be created evrytime //public Person.prototype = { getFullName: function () { createFullName(); return welcomeMessage + ' ' + fullName; }, setFirstName: function (fName) { firstName = fName; }, setLastName: function (lName) { lastName = lName; }, ChangeMessage: function (mesg) { welcomeMessage = mesg; } } return new Person(); // Person; //new Person(); }; var person1 = new Person(); person1.setFirstName ('Eli'); person1.setLastName('Flowers'); person1.ChangeMessage('welcome'); var message = person1.getFullName(); // welcome asdsad Flowers alert(message); 原型模式存在的一個問題是它不能訪問私人變數及私人函數,正因為這個問題,我們才會介紹閉包以及始終組織好建立類中存在的代碼以使得 它在全域範圍內不會變得很混亂。所有都是屬於 Person 類的作用範圍內。 另外一個問題是每一次執行個體被建立時,全部的代碼都被執行一遍,包括原型的綁定。對於我們中的一部分人來說,這僅僅只是一個效率問題。 處理好這個問題的一種方式是僅僅在期望共有函數停用情況下綁定這個原型。 這樣將會使得綁定原型操作只會在第一個執行個體被建立時執行,並且在那之後所有其他的執行個體都將只會進行檢查操作。不幸的是,這樣還是不能 解決我們在上面例子中提到的問題,因為我們只有重新再來一次建立的函數用於產生一個閉包來達到這個類的效果。這樣的話,至少我們減少 了一部分記憶體的使用。 等等,還有另外一個問題是私人函數不能直接存取原型函數。 為什麼你們一定得需要私人函數和私人變數呢?我知道你一定是想實作類別的封裝性,想確保類中的屬性或者內部的資料不會被突然地修改了或 者被內部的其他程式所修改,或者任何其他的操作…… 你應該記住你是不能將 javascript 代碼編譯成二進位的,對於這種情況,你在一定程度上很惱火吧,這樣代碼始終都是可用的。所以,如果 任何人想攪亂代碼的話,不管你真正實現私人或者沒有實現私人,不管你將代碼給團隊中的其他成員或者賣出去,他們都可以攪亂代碼。實現 私人化可能有那麼一點點協助吧。 另一個其他編程者使用的技術是使用約定命名,使用底線 “_”給所有你想設成私人任何的東西加上首碼以規定它成為私人。 [javascript] (function () { var Person = function () { this._fullName = ''; this.welcomeMessage = ''; this.firstName = ''; this.lastName = ""; _that = this; this._createFullName = function () { this.ChangeMessage('Namaste'); this._fullName = this.firstName + ' ' + this.lastName; }; } //Shared Functions for Code optimization Person.prototype = { constructor: Person, getFullName: function () { this._createFullName(); return this.welcomeMessage + ' ' + this._fullName; }, ChangeMessage: function (mesg) { this.welcomeMessage = mesg; } } this.Person = Person; })(); var person1 = new Person(); person1.firstName = 'Eli'; person1.lastName = 'Flowers'; person1.ChangeMessage('Welcome'); var message = person1.getFullName(); // Namaste Eli Flowers alert(message); 我不是說你不應該考慮 “private” 或者類似的知識。你是代碼的設計者,所以你將知道怎麼來管理並且知道怎麼做才是最好的。根據你的 需求,你可以使用任何一種設計模式或者多個設計模式組合一起使用。 無論你決定採用哪種設計模式,始終記住做盡量少的事情,不要在全域作用範圍內實現閉包,盡量減少記憶體泄露,以及最佳化代碼,並且組織好 代碼。所以,盡量多瞭解些範圍,閉包以及 “this” 的表現行為。 最後,祝編程愉快!譯後感經常使用 javascript,對於它的印象一直都是直接拷貝過來就可以用的。最近使用 extjs,它的類架構非常好用。從這樣文章也明白在 javascript 中實作類別的各種方式,以及在文章最後討論了類中私人成員的實現情況。