Javascript 中的類和閉包
更新時(shí)間:2010年01月08日 00:37:06 作者:
首先說類,要聲明的是,至少到目前為止Javascript中沒有類,所謂的“類”其實(shí)不是真正的類,它只是表現(xiàn)的像其他面向?qū)ο蟮恼Z(yǔ)言中的類而已,它的本質(zhì)是函數(shù)+原型對(duì)象(prototype)。
有人說javascript也是面向?qū)ο蟮?,只是它是prototype based,當(dāng)然這只是概念上的區(qū)別,我不想討論js是不是面向?qū)ο蟮?,關(guān)鍵是想說明雖然javascript的類表現(xiàn)得很像其他語(yǔ)言中的類,但是內(nèi)部的實(shí)現(xiàn)機(jī)理確不太一致,如果一味的把javascript中的類類比作其他語(yǔ)言中的類,有時(shí)候腦子會(huì)犯混。
先看一段簡(jiǎn)單的代碼,一般教材上介紹如何新建一個(gè)類的時(shí)候都是這樣的(當(dāng)然還有更復(fù)雜的方法,不過本質(zhì)上是一樣的):
function MyClass(x) {
this.x = x;
}
var obj = new MyClass('Hello class');
alert(obj.x);
毫無疑問,此時(shí)obj具有一個(gè)x屬性,現(xiàn)在的值是 Hello class. 但是,obj到底是什么?MyClass僅僅是一個(gè)函數(shù)而已,我們稱之為構(gòu)造函數(shù)。在其他OO的語(yǔ)言中,構(gòu)造函數(shù)是要放在class關(guān)鍵字內(nèi)部的,也就是先要聲明一個(gè)類。另外,函數(shù)體內(nèi)的this又是什么?其他OO語(yǔ)言中,this的概念是很明確的,就是當(dāng)前對(duì)象,因?yàn)樗跇?gòu)造函數(shù)執(zhí)行之前已經(jīng)聲明了類,類的內(nèi)部的一些字段都是已經(jīng)定義好的。
先解釋下,在javascript的函數(shù)中,this關(guān)鍵字表示的是調(diào)用該函數(shù)的作用域(scope),作用域的概念也不是太好理解,下面再解釋。不過可以簡(jiǎn)單的認(rèn)為它是調(diào)用函數(shù)的對(duì)象。再看MyClass函數(shù),它內(nèi)部的this是什么呢?
如果我們把代碼改成:
var obj = MyClass('Hello class');
這是完全合乎語(yǔ)法的。如果這段代碼是在瀏覽器中運(yùn)行的,調(diào)試一下可以發(fā)現(xiàn),this是window對(duì)象。而和obj沒有任何關(guān)系,obj還是undefined,alert也不會(huì)有結(jié)果。原來的代碼之所以可以工作,都是new關(guān)鍵字的功勞。new關(guān)鍵字把一個(gè)普通的函數(shù)變成了構(gòu)造函數(shù)。也就是說,MyClass還是一個(gè)普通的函數(shù),它之所以能構(gòu)造出一個(gè)obj,基本上是new的功勞。當(dāng)函數(shù)之前有new關(guān)鍵字的時(shí)候,javascript會(huì)創(chuàng)造一個(gè)匿名對(duì)象,并且把當(dāng)前函數(shù)的作用域設(shè)置為這個(gè)匿名對(duì)象。然后在那個(gè)函數(shù)內(nèi)部引用this的話就是引用的這個(gè)匿名對(duì)象,最后,即使這個(gè)函數(shù)沒有return,它也會(huì)把這個(gè)匿名對(duì)象返回出去。那么obj自然就具有了x屬性。
現(xiàn)在這個(gè)MyClass已經(jīng)有點(diǎn)像一個(gè)類了。但是,這并不是new的工作的全部。Javascript同樣可以方便的實(shí)現(xiàn)繼承——依靠是prototype.prototype也是一個(gè)對(duì)象,畢竟除了原始類型,所有的東西都是對(duì)象,包括函數(shù)。更為重要的是,前面提到j(luò)avascript是prototype based,它的含義就是在javascript中沒有類的概念,類是不存在的,一個(gè)函數(shù),它之所以表現(xiàn)的像類,就是靠的prototype. prototype可以有各種屬性,也包括函數(shù)。上一段說的new在構(gòu)造對(duì)象的過程中,在最終返回那個(gè)匿名對(duì)象之前,還會(huì)把那個(gè)函數(shù)的prototype中的屬性一一復(fù)制給這個(gè)對(duì)象。這里的復(fù)制是復(fù)制的引用,而不是新建的一個(gè)對(duì)象,把內(nèi)容復(fù)制過來,在其內(nèi)部,相當(dāng)于保留了一個(gè)構(gòu)造它的函數(shù)的prototype的引用。有些教材含糊的說所有的“所有對(duì)象都有一個(gè)prototype屬性”,這種說法是不確切的,雖然它內(nèi)部確實(shí)有一個(gè)prototype屬性,但是對(duì)外是不可見的。只有函數(shù)對(duì)象是有prototype屬性的,函數(shù)對(duì)象的prototype默認(rèn)有一個(gè)constructor屬性。
看如下的代碼:
function MyClass(x) {
this.x = x;
}
var proObj = new MyClass('x');
InheritClass.prototype = proObj;
MyClass.prototype.protox = 'xxx';
function InheritClass(y) {
this.y = y;
}
var obj = new InheritClass('Hello class');
MyClass.prototype.protox = 'changed';
proObj.x = 'Changed Too';
alert(obj.protox);
alert(obj.x);
輸出的結(jié)果是changed和Changed Too。此代碼說明了對(duì)象內(nèi)部保留的是構(gòu)造函數(shù)的prototype的引用,要注意的是,proObj中也是保留的它的構(gòu)造函數(shù)的prototype的引用。如果把代碼改成:
var obj = new InheritClass('Hello class');
proObj.protox = 'I am winner';
MyClass.prototype.protox = 'changed';
proObj.x = 'Changed Too';
alert(obj.protox);
alert(obj.x);
輸出的就是 I am winner 和 Changed Too了。事實(shí)上,這些prototype逐層引用,構(gòu)成了一個(gè)prototype鏈。當(dāng)讀取一個(gè)對(duì)象的屬性的時(shí)候,首先尋找自己定義的屬性,如果沒有,就逐層向內(nèi)部隱含的prototype屬性尋找。但是在寫屬性的時(shí)候,就會(huì)把它的引用覆蓋掉,是不會(huì)影響prototype的值的。
再介紹閉包,首先說明下,這里的閉包(closure)和離散數(shù)學(xué)中關(guān)系的傳遞閉包中的不是一個(gè)概念,我曾以為他們之間有關(guān)聯(lián),后來仔細(xì)想想,似乎并無什么關(guān)聯(lián),恰好名字一樣而已。先看定義:
Closure
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
要完全理解閉包需要對(duì)Javascript函數(shù)的機(jī)理有比較透徹的理解,而這個(gè)機(jī)理有點(diǎn)復(fù)雜,并不是三言兩語(yǔ)能講清的,有興趣的朋友可以看這里 Javascript clousures. 即使是這篇文章,也只是大概講了下原理。大意就是任何一個(gè)函數(shù)調(diào)用都在一個(gè)運(yùn)行上下文(Execution Context)中執(zhí)行的,這個(gè)上下文中有一個(gè)作用域?qū)ο?,其中包括了這個(gè)函數(shù)的局部變量、參數(shù)等。另外,如果一個(gè)函數(shù)是一個(gè)內(nèi)部函數(shù),它的作用域中含有它外部函數(shù)的作用域。在內(nèi)部函數(shù)遇到一個(gè)變量名的時(shí)候,它是從內(nèi)部的作用域找起,不斷往外層的作用域找。因此,如果內(nèi)部函數(shù)作為一個(gè)對(duì)象返回出外部函數(shù)的時(shí)候,即使外部函數(shù)已經(jīng)執(zhí)行完畢,但是由于其內(nèi)部函數(shù)仍有引用指向它,內(nèi)部函數(shù)不會(huì)被釋放,因?yàn)閮?nèi)部函數(shù)有外部函數(shù)的作用域,因此外部函數(shù)的局部變量也不會(huì)被釋放。這就構(gòu)成了閉包。
先看一段簡(jiǎn)單的代碼,一般教材上介紹如何新建一個(gè)類的時(shí)候都是這樣的(當(dāng)然還有更復(fù)雜的方法,不過本質(zhì)上是一樣的):
復(fù)制代碼 代碼如下:
function MyClass(x) {
this.x = x;
}
var obj = new MyClass('Hello class');
alert(obj.x);
毫無疑問,此時(shí)obj具有一個(gè)x屬性,現(xiàn)在的值是 Hello class. 但是,obj到底是什么?MyClass僅僅是一個(gè)函數(shù)而已,我們稱之為構(gòu)造函數(shù)。在其他OO的語(yǔ)言中,構(gòu)造函數(shù)是要放在class關(guān)鍵字內(nèi)部的,也就是先要聲明一個(gè)類。另外,函數(shù)體內(nèi)的this又是什么?其他OO語(yǔ)言中,this的概念是很明確的,就是當(dāng)前對(duì)象,因?yàn)樗跇?gòu)造函數(shù)執(zhí)行之前已經(jīng)聲明了類,類的內(nèi)部的一些字段都是已經(jīng)定義好的。
先解釋下,在javascript的函數(shù)中,this關(guān)鍵字表示的是調(diào)用該函數(shù)的作用域(scope),作用域的概念也不是太好理解,下面再解釋。不過可以簡(jiǎn)單的認(rèn)為它是調(diào)用函數(shù)的對(duì)象。再看MyClass函數(shù),它內(nèi)部的this是什么呢?
如果我們把代碼改成:
復(fù)制代碼 代碼如下:
var obj = MyClass('Hello class');
這是完全合乎語(yǔ)法的。如果這段代碼是在瀏覽器中運(yùn)行的,調(diào)試一下可以發(fā)現(xiàn),this是window對(duì)象。而和obj沒有任何關(guān)系,obj還是undefined,alert也不會(huì)有結(jié)果。原來的代碼之所以可以工作,都是new關(guān)鍵字的功勞。new關(guān)鍵字把一個(gè)普通的函數(shù)變成了構(gòu)造函數(shù)。也就是說,MyClass還是一個(gè)普通的函數(shù),它之所以能構(gòu)造出一個(gè)obj,基本上是new的功勞。當(dāng)函數(shù)之前有new關(guān)鍵字的時(shí)候,javascript會(huì)創(chuàng)造一個(gè)匿名對(duì)象,并且把當(dāng)前函數(shù)的作用域設(shè)置為這個(gè)匿名對(duì)象。然后在那個(gè)函數(shù)內(nèi)部引用this的話就是引用的這個(gè)匿名對(duì)象,最后,即使這個(gè)函數(shù)沒有return,它也會(huì)把這個(gè)匿名對(duì)象返回出去。那么obj自然就具有了x屬性。
現(xiàn)在這個(gè)MyClass已經(jīng)有點(diǎn)像一個(gè)類了。但是,這并不是new的工作的全部。Javascript同樣可以方便的實(shí)現(xiàn)繼承——依靠是prototype.prototype也是一個(gè)對(duì)象,畢竟除了原始類型,所有的東西都是對(duì)象,包括函數(shù)。更為重要的是,前面提到j(luò)avascript是prototype based,它的含義就是在javascript中沒有類的概念,類是不存在的,一個(gè)函數(shù),它之所以表現(xiàn)的像類,就是靠的prototype. prototype可以有各種屬性,也包括函數(shù)。上一段說的new在構(gòu)造對(duì)象的過程中,在最終返回那個(gè)匿名對(duì)象之前,還會(huì)把那個(gè)函數(shù)的prototype中的屬性一一復(fù)制給這個(gè)對(duì)象。這里的復(fù)制是復(fù)制的引用,而不是新建的一個(gè)對(duì)象,把內(nèi)容復(fù)制過來,在其內(nèi)部,相當(dāng)于保留了一個(gè)構(gòu)造它的函數(shù)的prototype的引用。有些教材含糊的說所有的“所有對(duì)象都有一個(gè)prototype屬性”,這種說法是不確切的,雖然它內(nèi)部確實(shí)有一個(gè)prototype屬性,但是對(duì)外是不可見的。只有函數(shù)對(duì)象是有prototype屬性的,函數(shù)對(duì)象的prototype默認(rèn)有一個(gè)constructor屬性。
看如下的代碼:
復(fù)制代碼 代碼如下:
function MyClass(x) {
this.x = x;
}
var proObj = new MyClass('x');
InheritClass.prototype = proObj;
MyClass.prototype.protox = 'xxx';
function InheritClass(y) {
this.y = y;
}
var obj = new InheritClass('Hello class');
MyClass.prototype.protox = 'changed';
proObj.x = 'Changed Too';
alert(obj.protox);
alert(obj.x);
輸出的結(jié)果是changed和Changed Too。此代碼說明了對(duì)象內(nèi)部保留的是構(gòu)造函數(shù)的prototype的引用,要注意的是,proObj中也是保留的它的構(gòu)造函數(shù)的prototype的引用。如果把代碼改成:
復(fù)制代碼 代碼如下:
var obj = new InheritClass('Hello class');
proObj.protox = 'I am winner';
MyClass.prototype.protox = 'changed';
proObj.x = 'Changed Too';
alert(obj.protox);
alert(obj.x);
輸出的就是 I am winner 和 Changed Too了。事實(shí)上,這些prototype逐層引用,構(gòu)成了一個(gè)prototype鏈。當(dāng)讀取一個(gè)對(duì)象的屬性的時(shí)候,首先尋找自己定義的屬性,如果沒有,就逐層向內(nèi)部隱含的prototype屬性尋找。但是在寫屬性的時(shí)候,就會(huì)把它的引用覆蓋掉,是不會(huì)影響prototype的值的。
再介紹閉包,首先說明下,這里的閉包(closure)和離散數(shù)學(xué)中關(guān)系的傳遞閉包中的不是一個(gè)概念,我曾以為他們之間有關(guān)聯(lián),后來仔細(xì)想想,似乎并無什么關(guān)聯(lián),恰好名字一樣而已。先看定義:
Closure
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
要完全理解閉包需要對(duì)Javascript函數(shù)的機(jī)理有比較透徹的理解,而這個(gè)機(jī)理有點(diǎn)復(fù)雜,并不是三言兩語(yǔ)能講清的,有興趣的朋友可以看這里 Javascript clousures. 即使是這篇文章,也只是大概講了下原理。大意就是任何一個(gè)函數(shù)調(diào)用都在一個(gè)運(yùn)行上下文(Execution Context)中執(zhí)行的,這個(gè)上下文中有一個(gè)作用域?qū)ο?,其中包括了這個(gè)函數(shù)的局部變量、參數(shù)等。另外,如果一個(gè)函數(shù)是一個(gè)內(nèi)部函數(shù),它的作用域中含有它外部函數(shù)的作用域。在內(nèi)部函數(shù)遇到一個(gè)變量名的時(shí)候,它是從內(nèi)部的作用域找起,不斷往外層的作用域找。因此,如果內(nèi)部函數(shù)作為一個(gè)對(duì)象返回出外部函數(shù)的時(shí)候,即使外部函數(shù)已經(jīng)執(zhí)行完畢,但是由于其內(nèi)部函數(shù)仍有引用指向它,內(nèi)部函數(shù)不會(huì)被釋放,因?yàn)閮?nèi)部函數(shù)有外部函數(shù)的作用域,因此外部函數(shù)的局部變量也不會(huì)被釋放。這就構(gòu)成了閉包。
相關(guān)文章
JavaScript面向?qū)ο?極簡(jiǎn)主義法minimalist approach)
荷蘭程序員 Gabor de Mooij 提出了一種比 Object.create ()更好的新方法,他稱這種方法為極簡(jiǎn)主義法(minimalist approach)。這也是我推薦的方法2012-07-07
javascript 面向?qū)ο蟮慕?jīng)典實(shí)例代碼
這里的面向?qū)ο笾饕鞘褂胮rototype屬性,大家可以參考下。2009-12-12
Javascript 面向?qū)ο缶幊?coolshell)
Javascript是一個(gè)類C的語(yǔ)言,他的面向?qū)ο蟮臇|西相對(duì)于C++/Java比較奇怪,但是其的確相當(dāng)?shù)膹?qiáng)大,在 Todd 同學(xué)的“對(duì)象的消息模型”一文中我們已經(jīng)可以看到一些端倪了2012-03-03
javascript 面向?qū)ο缶幊?萬物皆對(duì)象
javascript幾乎成了如今web開發(fā)人員必學(xué)必會(huì)的一門語(yǔ)言,但很多人卻只停在了一些表單驗(yàn)證等基礎(chǔ)操作層面上,在面向?qū)ο笳Z(yǔ)言大行其道的當(dāng)下,我們需要去學(xué)習(xí)javascript的面向?qū)ο蟮闹R(shí),以便更好的掌握javascript、為深入理解各種腳本框架打好基礎(chǔ)。2009-09-09
手把手教你自己寫一個(gè)js表單驗(yàn)證框架的方法
其實(shí)我自己也就能簡(jiǎn)單用用js而已,但是呢,相對(duì)很多初學(xué)者來說多懂了點(diǎn)Know How所以斗膽孟浪一下,將一些所得記錄下來,以供更多的初學(xué)者能夠知道一個(gè)東西的實(shí)現(xiàn)過程,省去在源碼里摸索的過程。2010-09-09
javascript 對(duì)象定義方法 簡(jiǎn)單易學(xué)
工廠模式 初級(jí)開發(fā)者可能會(huì)這樣定義對(duì)象2009-03-03

