javascript 面向?qū)ο笕吕砭氈當(dāng)?shù)據(jù)的封裝
更新時(shí)間:2009年12月03日 19:15:49 作者:
JavaScript 是一種非常靈活的面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言,它與傳統(tǒng)的強(qiáng)類型的面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言(如 C++,Java,C# 等)有很大不同,所以要實(shí)現(xiàn)如 C++、java、C# 當(dāng)中的一些特性就需要換一種思考方式來解決。
今天主要討論如何在 JavaScript 腳本中實(shí)現(xiàn)數(shù)據(jù)的封裝(encapsulation)。
數(shù)據(jù)封裝說的簡(jiǎn)單點(diǎn)就是把不希望調(diào)用者看見的內(nèi)容隱藏起來。它是面向?qū)ο蟪绦蛟O(shè)計(jì)的三要素之首,其它兩個(gè)是繼承和多態(tài),關(guān)于它們的內(nèi)容在后面再討論。
關(guān)于數(shù)據(jù)封裝的實(shí)現(xiàn),在 C++、Java、C# 等語(yǔ)言中是通過 public、private、static 等關(guān)鍵字實(shí)現(xiàn)的。在 JavaScript 則采用了另外一種截然不同的形式。在討論如何具體實(shí)現(xiàn)某種方式的數(shù)據(jù)封裝前,我們先說幾個(gè)簡(jiǎn)單的,大家所熟知卻又容易忽略的 JavaScript 的概念。
1 幾個(gè)基本概念
1.1 變量定義
在 JavaScript 語(yǔ)言中,是通過 var 關(guān)鍵字來定義變量的。
但是如果我們直接給一個(gè)沒有使用 var 定義的變量賦值,那么這個(gè)變量就會(huì)成為全局變量。
一般情況下,我們應(yīng)該避免使用沒有用 var 定義的變量,主要原因是它會(huì)影響程序的執(zhí)行效率,因?yàn)榇嫒∪肿兞克俣缺染植孔兞恳枚唷?
但是這種用法可以保證我們的變量一定是全局變量。
另外,為了保證速度,我們?cè)谑褂萌肿兞繒r(shí),可以通過 var 定義一個(gè)局部變量,然后將全局變量賦予之,由此可以得到一個(gè)全局變量的局部引用。
1.2 變量類型
沒有定義的變量,類型為 undefined。
變量的值可以是函數(shù)。
函數(shù)在 JavaScript 中可以充當(dāng)類的角色。
1.3 變量作用域
變量作用域是指變量生存周期的有效范圍。
單純用 { } 創(chuàng)建的塊不能創(chuàng)建作用域。
with 將它包含的對(duì)象作用域添加到當(dāng)前作用域鏈中,但 with 不創(chuàng)建新的作用域。with 塊結(jié)束后,會(huì)將對(duì)象作用域從當(dāng)前作用域鏈中刪除。
try-catch 中,catch 的錯(cuò)誤對(duì)象只在 catch 塊中有效,但 catch 塊中定義的變量屬于當(dāng)前作用域。
其它如 if、for、for-in、while、do-while、switch 等控制語(yǔ)句創(chuàng)建的塊不能創(chuàng)建作用域。
用 function 創(chuàng)建的函數(shù),會(huì)創(chuàng)建一個(gè)新的作用域添加到當(dāng)前作用域中。
2 封裝
下面我們就來討論具體的封裝。首先說一下大家最熟悉的幾種封裝:私有實(shí)例成員、公有實(shí)例成員和公有靜態(tài)成員。最后會(huì)討論一下大家所不熟悉的私有靜態(tài)成員和靜態(tài)類的封裝辦法。因?yàn)橄旅嬉懻摰氖敲嫦驅(qū)ο缶幊?,所有?dāng)函數(shù)作為類來定義和使用時(shí),我們暫且將其成為類。
2.1 私有實(shí)例成員
私有實(shí)例成員在 JavaScript 中實(shí)際上可以用函數(shù)內(nèi)的局部變量來實(shí)現(xiàn),它相當(dāng)于類的私有實(shí)例成員。例如:
class1 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// constructor
{
method1();
method2();
}
}
var o = new class1();
// error
alert(o.m_first);
o.method1();
這里 m_first 和 m_second 是 class1 的兩個(gè)私有實(shí)例字段,method1 和 method2 是兩個(gè)私有實(shí)例方法。他們只能在該類的對(duì)象內(nèi)部被使用,在對(duì)象外無法使用。
這里大家會(huì)發(fā)現(xiàn)創(chuàng)建私有方法有兩種方式,一種是直接在類中定義方法,另一種是先定義一個(gè)局部變量(私有實(shí)例字段),然后定義一個(gè)匿名方法賦值給它。
直接在類中定義方法,則該方法的作用域就是這個(gè)類,因此這個(gè)方法在此類外不能夠被訪問,而它又可以存取類中所有的私有實(shí)例字段,這就保證了這是個(gè)私有實(shí)例方法。
第二種創(chuàng)建私有實(shí)例方法的方式跟第一種方式的效果是一樣的,但是第二種方式更靈活一些。
你應(yīng)該還會(huì)注意到,class1 中把構(gòu)造器代碼用 { } 括起來了,這樣做雖然沒有必要,但是代碼看上去更加清晰。
關(guān)于這段構(gòu)造器代碼,還有兩點(diǎn)需要說明的地方:
1、構(gòu)造器代碼必須放在整個(gè)類定義的最后,這樣做是為了保證在它當(dāng)中被調(diào)用的方法都已經(jīng)被定義了。因?yàn)?JavaScript 是解釋型語(yǔ)言,所以,它會(huì)按照從上到下的順序執(zhí)行,因此,如果構(gòu)造器代碼放在其它方法定義的前面,則執(zhí)行到調(diào)用語(yǔ)句時(shí)找不到要調(diào)用的方法,就會(huì)出錯(cuò)。
2、我們已經(jīng)知道 { } 創(chuàng)建的塊不會(huì)改變作用域,因此如果在這樣的構(gòu)造器代碼中創(chuàng)建局部變量,實(shí)際上是在整個(gè)類中創(chuàng)建私有實(shí)例成員,所以,如果需要用到局部變量,應(yīng)當(dāng)定義一個(gè)私有實(shí)例方法,例如可以命名為 constructor(),在 constructor() 這個(gè)私有實(shí)例方法中定義局部變量和原來 { } 構(gòu)造器中要執(zhí)行的代碼,然后在類的最后直接調(diào)用它就可以了。所以更好的寫法是這樣的:
class1 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function constructor() {
method1();
method2();
}
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
constructor();
}
var o = new class1();
// error
alert(o.m_first);
o.method1();
最后,你可能還會(huì)發(fā)現(xiàn) class1 的定義我們沒有用 var,這樣做我們就可以保證它是個(gè)全局的類了。
2.2 公有實(shí)例成員
公有實(shí)例成員可以通過兩種方式來創(chuàng)建,我們先來看下面這個(gè)例子:
class2 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// public fields
this.first = "first";
this.second = ['s','e','c','o','n','d'];
// public methods
this.method1 = method2;
this.method2 = function() {
alert(this.second);
}
// constructor
{
method1();
method2();
}
}
// public method
class1.prototype.method3 = function() {
alert(this.first);
}
var o = new class2();
o.method1();
o.method2();
o.method3();
alert(o.first);
我們發(fā)現(xiàn)這個(gè)例子是在 class1 的例子上做了一些補(bǔ)充。給它添加了公有實(shí)例字段和公有實(shí)例方法,我們把它們通稱為公有實(shí)例成員。
我們應(yīng)該已經(jīng)發(fā)現(xiàn),創(chuàng)建公有實(shí)例成員其實(shí)很簡(jiǎn)單,一種方式是通過在類中給 this.memberName 來賦值,如果值是函數(shù)之外的類型,那就是個(gè)公有實(shí)例字段,如果值是函數(shù)類型,那就是公有實(shí)例方法。另外一種方式則是通過給 className.prototype.memberName 賦值,可賦值的類型跟 this.memberName 是相同的。
到底是通過 this 方式定義好呢,還是通過 prototype 方式定義好呢?
其實(shí)它們各有各的用途,它們之間不是誰(shuí)比誰(shuí)更好的關(guān)系。在某些情況下,我們只能用其中特定的一種方式來定義公有實(shí)例成員,而不能夠使用另一種方式。原因在于它們實(shí)際上是有區(qū)別的:
1、prototype 方式只應(yīng)該在類外定義。this 方式只能在類中定義。
2、prototype 方式如果在類中定義時(shí),則存取私有實(shí)例成員時(shí),總是存取最后一個(gè)對(duì)象實(shí)例中的私有實(shí)例成員。
3、prototype 方式定義的公有實(shí)例成員是創(chuàng)建在類的原型之上的成員。this 方式定義的公有實(shí)例成員,是直接創(chuàng)建在類的實(shí)例對(duì)象上的成員。
基于前兩點(diǎn)區(qū)別,我們可以得到這樣的結(jié)論:如果要在公有實(shí)例方法中存取私有實(shí)例成員,那么必須用 this 方式定義。
關(guān)于第三點(diǎn)區(qū)別,我們后面在討論繼承時(shí)再對(duì)它進(jìn)行更深入的剖析。這里只要知道有這個(gè)區(qū)別就可以了。
我們還會(huì)發(fā)現(xiàn),公有實(shí)例成員和私有實(shí)例成員名字是可以相同的,這樣不會(huì)有沖突嗎?
當(dāng)然不會(huì)。原因在于它們的存取方式不同,公有實(shí)例成員在類中存取時(shí),必須要用 this. 前綴來引用。而私有實(shí)例成員在類中存取時(shí),不使用也不能夠使用 this. 前綴來存取。而在類外存取時(shí),只有公有成員是可以通過類的實(shí)例對(duì)象存取的,私有成員無法存取。
2.3 公有靜態(tài)成員
公有靜態(tài)成員的定義很簡(jiǎn)單,例如:
class3 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// constructor
{
method1();
method2();
}
}
// public static field
class3.field1 = 1;
// public static method
class3.method1 = function() {
alert(class3.field1);
}
class3.method1();
這個(gè)例子的 class3 跟 class1 很像。不同的是 class3 的外面,我們又給 class3 定義了一個(gè)靜態(tài)字段和靜態(tài)方法。
定義的方式就是給 className.memberName 直接賦值。
這里定義的靜態(tài)字段和靜態(tài)方法都是可以被直接通過類名引用來存取的,而不需要?jiǎng)?chuàng)建對(duì)象。因此它們是公有靜態(tài)成員。
不過有點(diǎn)要記住,一定不要將公有靜態(tài)成員定義在它所在的類的內(nèi)部,否則你會(huì)得到非你所期望的結(jié)果。我們可以看下面這個(gè)例子:
class4 = function() {
// private fields
var m_first = 1;
var m_second = 2;
var s_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
class4.method1 = function() {
s_second++;
}
class4.method2 = function() {
alert(s_second);
}
}
var o1 = new class4();
class4.method2(); // 2
class4.method1();
class4.method2(); // 3
var o2 = new class4();
class4.method2(); // 2
class4.method1();
class4.method2(); // 3
這個(gè)例子中,我們期望 s_second 能夠扮演一個(gè)私有靜態(tài)成員的角色,但是輸出結(jié)果卻不是我們所期望的。我們會(huì)發(fā)現(xiàn) s_second 實(shí)際上是 class4 的一個(gè)私有實(shí)例成員,而不是私有靜態(tài)成員。而 class4 的 method1 和 method2 所存取的私有成員總是類的最后一個(gè)實(shí)例對(duì)象中的這個(gè)私有實(shí)例成員。
問題出在哪兒呢?
問題出在每次通過 new class4() 創(chuàng)建一個(gè)對(duì)象實(shí)例時(shí),class4 中的所有語(yǔ)句都會(huì)重新執(zhí)行,因此,s_second 被重置,并成為新對(duì)象中的一個(gè)私有實(shí)例成員。而 class4.method1 和 class4.method2 也被重新定義了,而這個(gè)定義也將它們的變量作用域切換到了最后一個(gè)對(duì)象上來。這與把通過 prototype 方式創(chuàng)建的公有實(shí)例方法定義在類的內(nèi)部而產(chǎn)生的錯(cuò)誤是一樣的。
所以,一定不要將公有靜態(tài)成員定義在它所在的類的內(nèi)部!也不要把通過 prototype 方式創(chuàng)建的公有實(shí)例方法定義在類的內(nèi)部!
那如何定義一個(gè)私有靜態(tài)成員呢?
2.4 私有靜態(tài)成員
前面在基本概念里我們已經(jīng)清楚了,只有用 function 創(chuàng)建函數(shù),才能創(chuàng)建一個(gè)新的作用域,而要?jiǎng)?chuàng)建私有成員(不論是靜態(tài)成員,還是實(shí)例成員),都需要通過創(chuàng)建新的作用域才能夠起到數(shù)據(jù)隱藏的目的。下面所采用的方法就是基于這一點(diǎn)來實(shí)現(xiàn)的。
實(shí)現(xiàn)私有靜態(tài)成員是通過創(chuàng)建一個(gè)匿名函數(shù)函數(shù)來創(chuàng)建一個(gè)新的作用域來實(shí)現(xiàn)的。
通常我們使用匿名函數(shù)時(shí)都是將它賦值給一個(gè)變量,然后通過這個(gè)變量引用該匿名函數(shù)。這種情況下,該匿名函數(shù)可以被反復(fù)調(diào)用或者作為類去創(chuàng)建對(duì)象。而這里,我們創(chuàng)建的匿名函數(shù)不賦值給任何變量,在它創(chuàng)建后立即執(zhí)行,或者立即實(shí)例化為一個(gè)對(duì)象,并且該對(duì)象也不賦值給任何變量,這種情況下,該函數(shù)本身或者它實(shí)例化后的對(duì)象都不能夠被再次存取,因此它唯一的作用就是創(chuàng)建了一個(gè)新的作用域,并隔離了它內(nèi)部的所有局部變量和函數(shù)。因此,這些局部變量和函數(shù)就成了我們所需要的私有靜態(tài)成員。而這個(gè)立即執(zhí)行的匿名函數(shù)或者立即實(shí)例化的匿名函數(shù)我們稱它為靜態(tài)封裝環(huán)境。
下面我們先來看通過直接調(diào)用匿名函數(shù)方式來創(chuàng)建帶有私有靜態(tài)成員的類的例子:
class5 = (function() {
// private static fields
var s_first = 1;
var s_second = 2;
// private static methods
function s_method1() {
s_first++;
}
var s_second = 2;
function constructor() {
// private fields
var m_first = 1;
javascript面向?qū)ο笕吕砭殻ǘ?
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// public fields
this.first = "first";
this.second = ['s','e','c','o','n','d'];
// public methods
this.method1 = function() {
s_second--;
}
this.method2 = function() {
alert(this.second);
}
// constructor
{
s_method1();
this.method1();
}
}
// public static methods
constructor.method1 = function() {
s_first++;
alert(s_first);
}
constructor.method2 = function() {
alert(s_second);
}
return constructor;
})();
var o1 = new class5();
class5.method1();
class5.method2();
o1.method2();
var o2 = new class5();
class5.method1();
class5.method2();
o2.method2();
這個(gè)例子中,通過
(function() {
...
function contructor () {
...
}
return constructor;
})();
來創(chuàng)建了一個(gè)靜態(tài)封裝環(huán)境,實(shí)際的類是在這個(gè)環(huán)境中定義的,并且在最后通過 return 語(yǔ)句將最后的類返回給我們的全局變量 class5,然后我們就可以通過 class5 來引用這個(gè)帶有靜態(tài)私有成員的類了。
為了區(qū)分私有靜態(tài)成員和私有實(shí)例成員,我們?cè)谒接徐o態(tài)成員前面用了 s_ 前綴,在私有實(shí)例成員前面加了 m_ 前綴,這樣避免了重名,因此在對(duì)象中總是可以存取私有靜態(tài)成員的。
但是這種命名方式不是必須的,只是推薦的,私有靜態(tài)成員可以跟私有實(shí)例成員同名,在重名的情況下,在類構(gòu)造器和在類中定義的實(shí)例方法中存取的都是私有實(shí)例成員,在靜態(tài)方法(不論是公有靜態(tài)方法還是私有靜態(tài)方法)中存取的都是私有靜態(tài)成員。
在類外并且在靜態(tài)封裝環(huán)境中通過 prototype 方式定義的公有實(shí)例方法存取的是私有靜態(tài)成員。
在靜態(tài)封裝環(huán)境外定義的公有靜態(tài)方法和通過 prototype 方式定義的公有實(shí)例方法無法直接存取私有靜態(tài)成員。
另外一種方式通過直接實(shí)例化匿名函數(shù)方式來創(chuàng)建帶有私有靜態(tài)成員的類的例子跟上面的例子很相似:
new function() {
// private static fields
var s_first = 1;
var s_second = 2;
// private static methods
function s_method1() {
s_first++;
}
var s_second = 2;
class6 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// public fields
this.first = "first";
this.second = ['s','e','c','o','n','d'];
// public methods
this.method1 = function() {
s_second--;
}
this.method2 = function() {
alert(this.second);
}
// constructor
{
s_method1();
this.method1();
}
}
// public static methods
class6.method1 = function() {
s_first++;
alert(s_first);
}
class6.method2 = function() {
alert(s_second);
}
};
var o1 = new class6();
class6.method1();
class6.method2();
o1.method2();
var o2 = new class6();
class6.method1();
class6.method2();
o2.method2();
這個(gè)例子的結(jié)果跟通過第一種方式創(chuàng)建的例子是相同的。只不過它的靜態(tài)封裝環(huán)境是這樣的:
new function() {
...
};
在這里,該函數(shù)沒有返回值,并且對(duì)于 class5 的定義是直接在靜態(tài)封裝環(huán)境內(nèi)部通過給一個(gè)沒有用 var 定義的變量賦值的方式實(shí)現(xiàn)的。
當(dāng)然,也完全可以在
(function() {
...
})();
這種方式中,不給該函數(shù)定義返回值,而直接在靜態(tài)封裝環(huán)境內(nèi)部通過給一個(gè)沒有用 var 定義的變量賦值的方式來實(shí)現(xiàn)帶有私有靜態(tài)成員的類的定義。
這兩種方式在這里是等價(jià)的。
2.5 靜態(tài)類
所謂的靜態(tài)類,是一種不能夠被實(shí)例化,并且只包含有靜態(tài)成員的類。
在 JavaScript 中我們通過直接實(shí)例化一個(gè)匿名函數(shù)的對(duì)象,就可以實(shí)現(xiàn)靜態(tài)類了。例如:
class7 = new function() {
// private static fields
var s_first = 1;
var s_second = 2;
// private static method
function method1() {
alert(s_first);
}
// public static method
this.method1 = function() {
method1();
alert(s_second);
}
}
class7.method1();
大家會(huì)發(fā)現(xiàn),class7 其實(shí)就是個(gè)對(duì)象,只不過這個(gè)對(duì)象所屬的是匿名類,該類在創(chuàng)建完 class7 這個(gè)對(duì)象后,就不能再被使用了。而 class7 不是一個(gè) function,所以不能夠作為一個(gè)類被實(shí)例化,因此,這里它就相當(dāng)于一個(gè)靜態(tài)類了。
數(shù)據(jù)封裝說的簡(jiǎn)單點(diǎn)就是把不希望調(diào)用者看見的內(nèi)容隱藏起來。它是面向?qū)ο蟪绦蛟O(shè)計(jì)的三要素之首,其它兩個(gè)是繼承和多態(tài),關(guān)于它們的內(nèi)容在后面再討論。
關(guān)于數(shù)據(jù)封裝的實(shí)現(xiàn),在 C++、Java、C# 等語(yǔ)言中是通過 public、private、static 等關(guān)鍵字實(shí)現(xiàn)的。在 JavaScript 則采用了另外一種截然不同的形式。在討論如何具體實(shí)現(xiàn)某種方式的數(shù)據(jù)封裝前,我們先說幾個(gè)簡(jiǎn)單的,大家所熟知卻又容易忽略的 JavaScript 的概念。
1 幾個(gè)基本概念
1.1 變量定義
在 JavaScript 語(yǔ)言中,是通過 var 關(guān)鍵字來定義變量的。
但是如果我們直接給一個(gè)沒有使用 var 定義的變量賦值,那么這個(gè)變量就會(huì)成為全局變量。
一般情況下,我們應(yīng)該避免使用沒有用 var 定義的變量,主要原因是它會(huì)影響程序的執(zhí)行效率,因?yàn)榇嫒∪肿兞克俣缺染植孔兞恳枚唷?
但是這種用法可以保證我們的變量一定是全局變量。
另外,為了保證速度,我們?cè)谑褂萌肿兞繒r(shí),可以通過 var 定義一個(gè)局部變量,然后將全局變量賦予之,由此可以得到一個(gè)全局變量的局部引用。
1.2 變量類型
沒有定義的變量,類型為 undefined。
變量的值可以是函數(shù)。
函數(shù)在 JavaScript 中可以充當(dāng)類的角色。
1.3 變量作用域
變量作用域是指變量生存周期的有效范圍。
單純用 { } 創(chuàng)建的塊不能創(chuàng)建作用域。
with 將它包含的對(duì)象作用域添加到當(dāng)前作用域鏈中,但 with 不創(chuàng)建新的作用域。with 塊結(jié)束后,會(huì)將對(duì)象作用域從當(dāng)前作用域鏈中刪除。
try-catch 中,catch 的錯(cuò)誤對(duì)象只在 catch 塊中有效,但 catch 塊中定義的變量屬于當(dāng)前作用域。
其它如 if、for、for-in、while、do-while、switch 等控制語(yǔ)句創(chuàng)建的塊不能創(chuàng)建作用域。
用 function 創(chuàng)建的函數(shù),會(huì)創(chuàng)建一個(gè)新的作用域添加到當(dāng)前作用域中。
2 封裝
下面我們就來討論具體的封裝。首先說一下大家最熟悉的幾種封裝:私有實(shí)例成員、公有實(shí)例成員和公有靜態(tài)成員。最后會(huì)討論一下大家所不熟悉的私有靜態(tài)成員和靜態(tài)類的封裝辦法。因?yàn)橄旅嬉懻摰氖敲嫦驅(qū)ο缶幊?,所有?dāng)函數(shù)作為類來定義和使用時(shí),我們暫且將其成為類。
2.1 私有實(shí)例成員
私有實(shí)例成員在 JavaScript 中實(shí)際上可以用函數(shù)內(nèi)的局部變量來實(shí)現(xiàn),它相當(dāng)于類的私有實(shí)例成員。例如:
復(fù)制代碼 代碼如下:
class1 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// constructor
{
method1();
method2();
}
}
var o = new class1();
// error
alert(o.m_first);
o.method1();
這里 m_first 和 m_second 是 class1 的兩個(gè)私有實(shí)例字段,method1 和 method2 是兩個(gè)私有實(shí)例方法。他們只能在該類的對(duì)象內(nèi)部被使用,在對(duì)象外無法使用。
這里大家會(huì)發(fā)現(xiàn)創(chuàng)建私有方法有兩種方式,一種是直接在類中定義方法,另一種是先定義一個(gè)局部變量(私有實(shí)例字段),然后定義一個(gè)匿名方法賦值給它。
直接在類中定義方法,則該方法的作用域就是這個(gè)類,因此這個(gè)方法在此類外不能夠被訪問,而它又可以存取類中所有的私有實(shí)例字段,這就保證了這是個(gè)私有實(shí)例方法。
第二種創(chuàng)建私有實(shí)例方法的方式跟第一種方式的效果是一樣的,但是第二種方式更靈活一些。
你應(yīng)該還會(huì)注意到,class1 中把構(gòu)造器代碼用 { } 括起來了,這樣做雖然沒有必要,但是代碼看上去更加清晰。
關(guān)于這段構(gòu)造器代碼,還有兩點(diǎn)需要說明的地方:
1、構(gòu)造器代碼必須放在整個(gè)類定義的最后,這樣做是為了保證在它當(dāng)中被調(diào)用的方法都已經(jīng)被定義了。因?yàn)?JavaScript 是解釋型語(yǔ)言,所以,它會(huì)按照從上到下的順序執(zhí)行,因此,如果構(gòu)造器代碼放在其它方法定義的前面,則執(zhí)行到調(diào)用語(yǔ)句時(shí)找不到要調(diào)用的方法,就會(huì)出錯(cuò)。
2、我們已經(jīng)知道 { } 創(chuàng)建的塊不會(huì)改變作用域,因此如果在這樣的構(gòu)造器代碼中創(chuàng)建局部變量,實(shí)際上是在整個(gè)類中創(chuàng)建私有實(shí)例成員,所以,如果需要用到局部變量,應(yīng)當(dāng)定義一個(gè)私有實(shí)例方法,例如可以命名為 constructor(),在 constructor() 這個(gè)私有實(shí)例方法中定義局部變量和原來 { } 構(gòu)造器中要執(zhí)行的代碼,然后在類的最后直接調(diào)用它就可以了。所以更好的寫法是這樣的:
復(fù)制代碼 代碼如下:
class1 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function constructor() {
method1();
method2();
}
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
constructor();
}
var o = new class1();
// error
alert(o.m_first);
o.method1();
最后,你可能還會(huì)發(fā)現(xiàn) class1 的定義我們沒有用 var,這樣做我們就可以保證它是個(gè)全局的類了。
2.2 公有實(shí)例成員
公有實(shí)例成員可以通過兩種方式來創(chuàng)建,我們先來看下面這個(gè)例子:
復(fù)制代碼 代碼如下:
class2 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// public fields
this.first = "first";
this.second = ['s','e','c','o','n','d'];
// public methods
this.method1 = method2;
this.method2 = function() {
alert(this.second);
}
// constructor
{
method1();
method2();
}
}
// public method
class1.prototype.method3 = function() {
alert(this.first);
}
var o = new class2();
o.method1();
o.method2();
o.method3();
alert(o.first);
我們發(fā)現(xiàn)這個(gè)例子是在 class1 的例子上做了一些補(bǔ)充。給它添加了公有實(shí)例字段和公有實(shí)例方法,我們把它們通稱為公有實(shí)例成員。
我們應(yīng)該已經(jīng)發(fā)現(xiàn),創(chuàng)建公有實(shí)例成員其實(shí)很簡(jiǎn)單,一種方式是通過在類中給 this.memberName 來賦值,如果值是函數(shù)之外的類型,那就是個(gè)公有實(shí)例字段,如果值是函數(shù)類型,那就是公有實(shí)例方法。另外一種方式則是通過給 className.prototype.memberName 賦值,可賦值的類型跟 this.memberName 是相同的。
到底是通過 this 方式定義好呢,還是通過 prototype 方式定義好呢?
其實(shí)它們各有各的用途,它們之間不是誰(shuí)比誰(shuí)更好的關(guān)系。在某些情況下,我們只能用其中特定的一種方式來定義公有實(shí)例成員,而不能夠使用另一種方式。原因在于它們實(shí)際上是有區(qū)別的:
1、prototype 方式只應(yīng)該在類外定義。this 方式只能在類中定義。
2、prototype 方式如果在類中定義時(shí),則存取私有實(shí)例成員時(shí),總是存取最后一個(gè)對(duì)象實(shí)例中的私有實(shí)例成員。
3、prototype 方式定義的公有實(shí)例成員是創(chuàng)建在類的原型之上的成員。this 方式定義的公有實(shí)例成員,是直接創(chuàng)建在類的實(shí)例對(duì)象上的成員。
基于前兩點(diǎn)區(qū)別,我們可以得到這樣的結(jié)論:如果要在公有實(shí)例方法中存取私有實(shí)例成員,那么必須用 this 方式定義。
關(guān)于第三點(diǎn)區(qū)別,我們后面在討論繼承時(shí)再對(duì)它進(jìn)行更深入的剖析。這里只要知道有這個(gè)區(qū)別就可以了。
我們還會(huì)發(fā)現(xiàn),公有實(shí)例成員和私有實(shí)例成員名字是可以相同的,這樣不會(huì)有沖突嗎?
當(dāng)然不會(huì)。原因在于它們的存取方式不同,公有實(shí)例成員在類中存取時(shí),必須要用 this. 前綴來引用。而私有實(shí)例成員在類中存取時(shí),不使用也不能夠使用 this. 前綴來存取。而在類外存取時(shí),只有公有成員是可以通過類的實(shí)例對(duì)象存取的,私有成員無法存取。
2.3 公有靜態(tài)成員
公有靜態(tài)成員的定義很簡(jiǎn)單,例如:
復(fù)制代碼 代碼如下:
class3 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// constructor
{
method1();
method2();
}
}
// public static field
class3.field1 = 1;
// public static method
class3.method1 = function() {
alert(class3.field1);
}
class3.method1();
這個(gè)例子的 class3 跟 class1 很像。不同的是 class3 的外面,我們又給 class3 定義了一個(gè)靜態(tài)字段和靜態(tài)方法。
定義的方式就是給 className.memberName 直接賦值。
這里定義的靜態(tài)字段和靜態(tài)方法都是可以被直接通過類名引用來存取的,而不需要?jiǎng)?chuàng)建對(duì)象。因此它們是公有靜態(tài)成員。
不過有點(diǎn)要記住,一定不要將公有靜態(tài)成員定義在它所在的類的內(nèi)部,否則你會(huì)得到非你所期望的結(jié)果。我們可以看下面這個(gè)例子:
復(fù)制代碼 代碼如下:
class4 = function() {
// private fields
var m_first = 1;
var m_second = 2;
var s_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
class4.method1 = function() {
s_second++;
}
class4.method2 = function() {
alert(s_second);
}
}
var o1 = new class4();
class4.method2(); // 2
class4.method1();
class4.method2(); // 3
var o2 = new class4();
class4.method2(); // 2
class4.method1();
class4.method2(); // 3
這個(gè)例子中,我們期望 s_second 能夠扮演一個(gè)私有靜態(tài)成員的角色,但是輸出結(jié)果卻不是我們所期望的。我們會(huì)發(fā)現(xiàn) s_second 實(shí)際上是 class4 的一個(gè)私有實(shí)例成員,而不是私有靜態(tài)成員。而 class4 的 method1 和 method2 所存取的私有成員總是類的最后一個(gè)實(shí)例對(duì)象中的這個(gè)私有實(shí)例成員。
問題出在哪兒呢?
問題出在每次通過 new class4() 創(chuàng)建一個(gè)對(duì)象實(shí)例時(shí),class4 中的所有語(yǔ)句都會(huì)重新執(zhí)行,因此,s_second 被重置,并成為新對(duì)象中的一個(gè)私有實(shí)例成員。而 class4.method1 和 class4.method2 也被重新定義了,而這個(gè)定義也將它們的變量作用域切換到了最后一個(gè)對(duì)象上來。這與把通過 prototype 方式創(chuàng)建的公有實(shí)例方法定義在類的內(nèi)部而產(chǎn)生的錯(cuò)誤是一樣的。
所以,一定不要將公有靜態(tài)成員定義在它所在的類的內(nèi)部!也不要把通過 prototype 方式創(chuàng)建的公有實(shí)例方法定義在類的內(nèi)部!
那如何定義一個(gè)私有靜態(tài)成員呢?
2.4 私有靜態(tài)成員
前面在基本概念里我們已經(jīng)清楚了,只有用 function 創(chuàng)建函數(shù),才能創(chuàng)建一個(gè)新的作用域,而要?jiǎng)?chuàng)建私有成員(不論是靜態(tài)成員,還是實(shí)例成員),都需要通過創(chuàng)建新的作用域才能夠起到數(shù)據(jù)隱藏的目的。下面所采用的方法就是基于這一點(diǎn)來實(shí)現(xiàn)的。
實(shí)現(xiàn)私有靜態(tài)成員是通過創(chuàng)建一個(gè)匿名函數(shù)函數(shù)來創(chuàng)建一個(gè)新的作用域來實(shí)現(xiàn)的。
通常我們使用匿名函數(shù)時(shí)都是將它賦值給一個(gè)變量,然后通過這個(gè)變量引用該匿名函數(shù)。這種情況下,該匿名函數(shù)可以被反復(fù)調(diào)用或者作為類去創(chuàng)建對(duì)象。而這里,我們創(chuàng)建的匿名函數(shù)不賦值給任何變量,在它創(chuàng)建后立即執(zhí)行,或者立即實(shí)例化為一個(gè)對(duì)象,并且該對(duì)象也不賦值給任何變量,這種情況下,該函數(shù)本身或者它實(shí)例化后的對(duì)象都不能夠被再次存取,因此它唯一的作用就是創(chuàng)建了一個(gè)新的作用域,并隔離了它內(nèi)部的所有局部變量和函數(shù)。因此,這些局部變量和函數(shù)就成了我們所需要的私有靜態(tài)成員。而這個(gè)立即執(zhí)行的匿名函數(shù)或者立即實(shí)例化的匿名函數(shù)我們稱它為靜態(tài)封裝環(huán)境。
下面我們先來看通過直接調(diào)用匿名函數(shù)方式來創(chuàng)建帶有私有靜態(tài)成員的類的例子:
復(fù)制代碼 代碼如下:
class5 = (function() {
// private static fields
var s_first = 1;
var s_second = 2;
// private static methods
function s_method1() {
s_first++;
}
var s_second = 2;
function constructor() {
// private fields
var m_first = 1;
javascript面向?qū)ο笕吕砭殻ǘ?
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// public fields
this.first = "first";
this.second = ['s','e','c','o','n','d'];
// public methods
this.method1 = function() {
s_second--;
}
this.method2 = function() {
alert(this.second);
}
// constructor
{
s_method1();
this.method1();
}
}
// public static methods
constructor.method1 = function() {
s_first++;
alert(s_first);
}
constructor.method2 = function() {
alert(s_second);
}
return constructor;
})();
var o1 = new class5();
class5.method1();
class5.method2();
o1.method2();
var o2 = new class5();
class5.method1();
class5.method2();
o2.method2();
這個(gè)例子中,通過
(function() {
...
function contructor () {
...
}
return constructor;
})();
來創(chuàng)建了一個(gè)靜態(tài)封裝環(huán)境,實(shí)際的類是在這個(gè)環(huán)境中定義的,并且在最后通過 return 語(yǔ)句將最后的類返回給我們的全局變量 class5,然后我們就可以通過 class5 來引用這個(gè)帶有靜態(tài)私有成員的類了。
為了區(qū)分私有靜態(tài)成員和私有實(shí)例成員,我們?cè)谒接徐o態(tài)成員前面用了 s_ 前綴,在私有實(shí)例成員前面加了 m_ 前綴,這樣避免了重名,因此在對(duì)象中總是可以存取私有靜態(tài)成員的。
但是這種命名方式不是必須的,只是推薦的,私有靜態(tài)成員可以跟私有實(shí)例成員同名,在重名的情況下,在類構(gòu)造器和在類中定義的實(shí)例方法中存取的都是私有實(shí)例成員,在靜態(tài)方法(不論是公有靜態(tài)方法還是私有靜態(tài)方法)中存取的都是私有靜態(tài)成員。
在類外并且在靜態(tài)封裝環(huán)境中通過 prototype 方式定義的公有實(shí)例方法存取的是私有靜態(tài)成員。
在靜態(tài)封裝環(huán)境外定義的公有靜態(tài)方法和通過 prototype 方式定義的公有實(shí)例方法無法直接存取私有靜態(tài)成員。
另外一種方式通過直接實(shí)例化匿名函數(shù)方式來創(chuàng)建帶有私有靜態(tài)成員的類的例子跟上面的例子很相似:
復(fù)制代碼 代碼如下:
new function() {
// private static fields
var s_first = 1;
var s_second = 2;
// private static methods
function s_method1() {
s_first++;
}
var s_second = 2;
class6 = function() {
// private fields
var m_first = 1;
var m_second = 2;
// private methods
function method1() {
alert(m_first);
}
var method2 = function() {
alert(m_second);
}
// public fields
this.first = "first";
this.second = ['s','e','c','o','n','d'];
// public methods
this.method1 = function() {
s_second--;
}
this.method2 = function() {
alert(this.second);
}
// constructor
{
s_method1();
this.method1();
}
}
// public static methods
class6.method1 = function() {
s_first++;
alert(s_first);
}
class6.method2 = function() {
alert(s_second);
}
};
var o1 = new class6();
class6.method1();
class6.method2();
o1.method2();
var o2 = new class6();
class6.method1();
class6.method2();
o2.method2();
這個(gè)例子的結(jié)果跟通過第一種方式創(chuàng)建的例子是相同的。只不過它的靜態(tài)封裝環(huán)境是這樣的:
new function() {
...
};
在這里,該函數(shù)沒有返回值,并且對(duì)于 class5 的定義是直接在靜態(tài)封裝環(huán)境內(nèi)部通過給一個(gè)沒有用 var 定義的變量賦值的方式實(shí)現(xiàn)的。
當(dāng)然,也完全可以在
(function() {
...
})();
這種方式中,不給該函數(shù)定義返回值,而直接在靜態(tài)封裝環(huán)境內(nèi)部通過給一個(gè)沒有用 var 定義的變量賦值的方式來實(shí)現(xiàn)帶有私有靜態(tài)成員的類的定義。
這兩種方式在這里是等價(jià)的。
2.5 靜態(tài)類
所謂的靜態(tài)類,是一種不能夠被實(shí)例化,并且只包含有靜態(tài)成員的類。
在 JavaScript 中我們通過直接實(shí)例化一個(gè)匿名函數(shù)的對(duì)象,就可以實(shí)現(xiàn)靜態(tài)類了。例如:
復(fù)制代碼 代碼如下:
class7 = new function() {
// private static fields
var s_first = 1;
var s_second = 2;
// private static method
function method1() {
alert(s_first);
}
// public static method
this.method1 = function() {
method1();
alert(s_second);
}
}
class7.method1();
大家會(huì)發(fā)現(xiàn),class7 其實(shí)就是個(gè)對(duì)象,只不過這個(gè)對(duì)象所屬的是匿名類,該類在創(chuàng)建完 class7 這個(gè)對(duì)象后,就不能再被使用了。而 class7 不是一個(gè) function,所以不能夠作為一個(gè)類被實(shí)例化,因此,這里它就相當(dāng)于一個(gè)靜態(tài)類了。
您可能感興趣的文章:
- JavaScript面向?qū)ο笕齻€(gè)基本特征實(shí)例詳解【封裝、繼承與多態(tài)】
- JS面向?qū)ο蠡A(chǔ)講解(工廠模式、構(gòu)造函數(shù)模式、原型模式、混合模式、動(dòng)態(tài)原型模式)
- Javascript 面向?qū)ο螅ㄒ唬?共有方法,私有方法,特權(quán)方法)
- 面向?qū)ο蟮腏avascript之二(接口實(shí)現(xiàn)介紹)
- js面向?qū)ο笾R妱?chuàng)建對(duì)象的幾種方式(工廠模式、構(gòu)造函數(shù)模式、原型模式)
- javascript 面向?qū)ο缶幊袒A(chǔ) 多態(tài)
- 徹底理解js面向?qū)ο笾^承
- JavaScript面向?qū)ο缶幊倘腴T教程
- JS 面向?qū)ο蟮?鐘寫法
- js面向?qū)ο笾o態(tài)方法和靜態(tài)屬性實(shí)例分析
- JavaScript面向?qū)ο笾叽蠡驹瓌t實(shí)例詳解
相關(guān)文章
從面試題學(xué)習(xí)Javascript 面向?qū)ο螅▌?chuàng)建對(duì)象)
從面試題學(xué)習(xí)Javascript 面向?qū)ο螅▌?chuàng)建對(duì)象),學(xué)習(xí)js的朋友可以參考下2012-03-03javascript面向?qū)ο蟮姆绞綄?shí)現(xiàn)的彈出層效果代碼
由于本人以前是.net程序員,所以即使現(xiàn)在在做前端,也習(xí)慣于用面向?qū)ο蟮姆绞骄帉慾s腳本,我想如果你以前也是或者現(xiàn)在還是名第三代程序員的話,應(yīng)該對(duì)此并不陌生。2010-01-01不錯(cuò)的JavaScript面向?qū)ο蟮暮?jiǎn)單入門介紹
JavaScript是一門OOP,而有些人說,JavaScript是基于對(duì)象的。2008-07-07JavaScript 設(shè)計(jì)模式學(xué)習(xí) Singleton
JavaScript設(shè)計(jì)模式學(xué)習(xí) Singleton2009-07-07JavaScript接口實(shí)現(xiàn)代碼 (Interfaces In JavaScript)
接口是面向?qū)ο缶幊汤锏闹匾匦裕z憾的是JavaScript并沒有提供對(duì)接口的支持!怎么實(shí)現(xiàn)接口呢?2010-06-06javascript中的對(duì)象創(chuàng)建 實(shí)例附注釋
為了讓你的js代碼更加的專業(yè)與代碼的條理性,很多情況下都是定義成對(duì)象的方式來書寫代碼,想深入的朋友可以參考下。2011-02-02