歡迎您光臨本站 註冊首頁

由淺到深了解JavaScript類

←手機掃碼閱讀     火星人 @ 2014-03-09 , reply:0

  類是什麼?

  許多剛接觸編程的朋友都可能理解不了類,其實類是對我們這個現實世界的模擬,把它說成「類別」或者「類型」可能會更容易理解一些.比如「人」這種動物就是一個類,而具體某一個人就是「人」這個類的一個實例,「人」可以有許多實例(地球人超過六十億了),但「人」這個類只有一個.你或許會說那男人和女人不也是人么?怎麼只能有一個?其實這裡要談到一個繼承的東西,後邊才講,請繼續看下去.

  如何建立一個類?

  在C 中是以class來聲明一個類的,JavaScript與C 不同,它使用了與函數一樣的function來聲明,這就讓許多學Jscript的朋友把類與函數混在一起了,在Jscript中函數與類確實有些混,但使用久了自然而然會理解,這篇文章是針對想進攻面向對象編程的朋友而寫,就不打算一下子討論得太深了.

  請看下邊這個類的定義:

  function WuYouUser()

  {

  this.Name; //名字

  }

  上邊的代碼定義了一個WuYouUser(無憂用戶)類,它有個屬性:Name(名字).Name就是WuYouUser類的一個屬性.

  一個類有固定的屬性,但類的實例卻有不同的屬性值,就像我是屬於「人」這個類的,性別是男,而我有一個女同學,她也屬於「人」類,但她的性別屬性值卻為女.

  那麼如何聲明某個類的一個實例呢?非常簡單:

  var Wo = new WuYouUser(); //實例一:「我」

  var Biyuan = new WuYouUser(); //實例二:「碧原」(Biyuan哥,不好意思...嘿嘿)

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  類的屬性

  這個Wo(我)就是WuYouUser類的一個實例,它擁有WuYouUser給它的一切:Name屬性、Sex屬性以及Age屬性,我們可以這樣子來設置它的屬性:

  Wo.Name = "泣紅亭";

  很簡單是不是?試著運行

  window.document.write(Wo.Name);

  看看,是不是輸出了我的名字:泣紅亭?

  同樣設置一下碧原兄的屬性

  Biyuan.Name = "碧原";

  運行

  window.document.write(Biyuan.Name);

  可以看到輸出了"碧原",也就說明了Biyuan與Wo同樣是WuYouUser類的實例,但卻是不同的實體,具有不同的屬性值.

  屬性是可以設置默認值的,無憂里都有記錄大家各自發了多少貼子,我們也同樣給WuYouUser類添加一個發貼數量的屬性ArticleCount

  function WuYouUser()

  {

  this.Name;

  this.ArticleCount = 0;

  }

  一個無憂新用戶剛註冊完之後他的發貼數量為0,在上邊的代碼中可以看到直接給屬性ArticleCount設置值為0.

  可以運行一下這樣的代碼:

  var Wo = new WuYouUser();

  window.document.write(Wo.ArticleCount);

  可以看到輸出了0,說明ArticleCount屬性被我們成功設置默認值為0

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  類的方法

  方法這個詞不大好理解,我覺得說成行為會更容易理解.一個人具有許多共同的行為,比如睡覺、吃飯、走路等等,現在我們給WuYouUser類添加一個發貼的方法.

  function WuYouUser()

  {

  this.Name;

  this.ArticleCount = 0;

  this.NewArticle = function()

  {

  /*

  *

  * 具體如何發貼我們大家都知道,不就是打打字,加加圖片再按一下保存之類的按鈕么?

  * 關於具體如何發貼的代碼沒有必要在這裡寫出來,我們要了解的僅僅是方法的定義與使用

  * 我們在這裡實現一個最簡單的功能,也是很重要的功能:給我們的發貼數量加上1!

  * 注意:恐龍等級就是這樣加出來的,因此呀……大家狂發貼吧...

  */

  this.ArticleCount ;

  }

  }

  既然定義好了這個方法,我們來試試效果如何:

  var Wo = new WuYouUser();

  Wo.NewArticle();

  document.write(Wo.ArticleCount);

  可以看到輸出了1,說明我們發貼成功了!真是有歷史紀念意義的一刻,離恐龍等級又近一步了.

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  靜態屬性

  靜態屬性又稱公共屬性,它不屬於某個類的實例,而是直接屬於某個類.

  比如說無憂用戶有一個屬性:註冊用戶的數量,它是屬於整個無憂用戶的,而不是屬於泣紅亭或者誰的

  靜態屬性的聲明方法是:

  類名.prototype.屬性名 = 屬性值;

  比如給WuYouUser類定義一個註冊用戶的數量Count:

  WuYouUser.prototype.Count = 0;

  那麼如何讀取它呢?有兩種方法:

  1. 直接用 WuYouUser.prototype.Count

  2. 使用Wo.Count

  這兩者沒有區別,都是得到0

  雖然讀取方法可以有兩種,但在改變它的時候卻得特別小心了,請看下邊代碼

  var Biyuan = new WuYouUser();

  WuYouUser.prototype.Count ;

  document.write(Wo.Count);

  document.write(Biyuan.Count);

  你會發現兩者的Count屬性都是1,也就是說WuYouUser.prototype.Count改變了會影響到各個實例的相應屬性,其實原理就是Wo、Biyuan的Count屬性與WuYouUser.prototype.Count根本就是同一個!

  現在來看另外一段代碼:

  var Biyuan = new WuYouUser();

  Biyuan.Count ; //特別注意一下這裡,這是直接改變Biyuan的Count屬性

  document.write(Biyuan.Count); // 輸出 1

  document.write(WuYouUser.prototype.Count); //輸出 0

  document.write(Wo.Count); //同樣輸出0,為什麼?

  可以看到如果直接修改實例的靜態屬性值,那麼會出現其它實例甚至類的靜態屬性與它不同步了?這是因為直接修改的時候,該實例會生成一個屬於該實例的屬性Count,這個時候Biyuan.Count不再與WuYouUser.prototype.Count是同一個了,也不與Wo.Count是同一個,這個Count屬性是屬於Biyuan自己所有的,以後改變了它也只是影響它自己而已.

  因此如果不是特別的需要,建議不管在讀取還是賦值的時候,都統一使用WuYouUser.prototype.Count這樣的方式,以做到萬無一失!

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  靜態方法

  與靜態屬性相似,它也有個另稱:公共方法,同樣屬於類本身的.

  靜態方法的定義方式是:

  類名.方法名 = function(參數1,參數2...參數n)

  {

  //方法代碼

  }

  我們現在就來定義一個無憂用戶類的註冊新用戶靜態方法:

  WuYouUser.prototype.AddOne = function()

  {

  //*** 同樣具體代碼不寫出來,給靜態屬性Count增加1,表示註冊用戶數量又多一個

  WuYouUser.prototype.Count ;

  }

  現在我們來看看如何用它,同樣有兩種方法:

  1.直接使用WuYouUser.prototype.AddOne()

  2.使用某實例的AddOne()

  這兩種方法沒有什麼不同:

  var Wo = new WuYouUser();

  var Biyuan = new WuYouUser();

  document.write(WuYouUser.prototype.Count); // 0

  Wo.AddOne();

  document.write(WuYouUser.prototype.Count); // 1

  document.write(Wo.Count); // 1

  document.write(Biyuan.Count); // 1

  WuYouUser.prototype.AddOne();

  document.write(WuYouUser.prototype.Count); // 2

  document.write(Wo.Count); // 2

  document.write(Biyuan.Count); // 2

  可以看出不管是使用Wo.AddOne()還是WuYouUser.prototype.AddOne()效果都是一樣的,都是給WuYouUser.prototype.Count加上1

  現在再看一段代碼:

  function NewClass() //由於上邊的WuYouUser類不合適當這個例子的代碼,我聲明了一個新類NewClass

  {

  this.Name = "泣紅亭"; //這裡默認值為我的名字

  }

  NewClass.prototype.ChangeName = function(NewName)

  {

  this.Name = NewName;

  }

  var Wo = new NewClass();

  Wo.ChangeName("鄭運濤"); //我的真名

  可以看到Wo.Name確實已經變成了"鄭運濤",這個方法似乎是可以用的,但裡邊是不是內有天機呢?

  再看下邊的代碼,類的定義以及ChangeName的定義我們照樣,但改變一下下邊的代碼:

  NewClass.prototype.ChangeName("鄭運濤");

  document.write(NewClass.Name); //undefined,即未定義

  document.write(NewClass.prototype.Name); //鄭運濤

  var Wo = new NewClass();

  document.write(Wo.Name); //泣紅亭

  可以看到我們並沒有定義NewClass.prototype.Name這個靜態屬性,但編譯器給我們自己加了一個.

  可是再看下邊輸出Wo.Name,它並不是為"鄭運濤",而是原來的默認值"泣紅亭",說明了什麼?

  其實很簡單,看一下NewClass的定義里已經有Name這個屬性,因此Wo也有自己的Name屬性,它跟NewClass.prototype.Name並不是同一個的,因此就還是那樣子.

  那為什麼前一個例子運行了Wo.ChangeName("鄭運濤")卻能夠實現改變Wo.Name屬性呢?其實在這裡跟改變Wo.Count的值是同一個道理,編譯器自動給Wo增加了一個方法ChangeName,這個方法代碼與NewClass.prototype.ChangeName一樣,但Wo.ChangeName是Wo這個實例所特有的,而非NewClass.prototype.ChangeName!

  分析可知道在靜態方法里盡量不要使用this這樣的關鍵字來引用實例本身的屬性,除非你有特別的目的,能夠清楚地明白這裡邊的運行機制!

  如果真的需要在靜態方法里使用this,可以直接把this當作參數傳進去:

  NewClass.ChangeName = function(This,NewName) //注意這裡是This,不是this

  {

  This.Name = NewName;

  }

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  構造函數

  一個類在初始化的時候其實也是一個函數的執行過程,這個函數就是構造函數,我們看一下下邊的代碼:

  function WuYouUser()

  {

  this.Name = "泣紅亭"; //默認定義為泣紅亭

  alert(this.Name);

  }

  var Wo = new WuYouUser();//可以看到出現一個窗口顯示泣紅亭三個字

  可以看出類的定義不僅僅是定義了它的屬性與方法,還同時可以加入一些代碼,而這些代碼就是該類的構造函數的代碼,在實例聲明過程中被執行!

  其實說起來,類的屬性與類的方法都是在構造函數里執行定義的,看下邊的代碼:

  function WuYouUser()

  {

  this.Name = "泣紅亭";

  return;

  this.Sex = "男";

  }

  var Wo = new WuYouUser();

  document.write(Wo.Name); //泣紅亭

  document.write(Wo.Sex); //undefined,即未定義

  看得出什麼?Sex屬性是在return;之後的,而WuYouUser類的構造函數遇到return即停止運行,換句話說this.Sex = "男";這一行是沒有被執行,即Sex屬性根本沒有被定義!

  構造函數可以有參數,參數值在聲明實例的時候被傳入:

  function WuYouUser(Name)

  {

  this.Name = Name;

  }

  var Wo = new WuYouUser("泣紅亭");

  document.write(Wo.Name); //泣紅亭

  構造函數不需要返回值,但如果你設置了返回值,可以把它當成一個函數來使用.

  function Sum(a, b)

  {

  this.a = a;

  this.b = b;

  return this.a this.b;

  }

  document.write(Sum(12, 23)); //輸出的是12與23的和35

  var Obj = new Sum(12,23);

  document.write(Obj.a) // 12

  document.write(Obj.b) // 23

  感覺挺奇妙,對吧?我寫這文章寫著寫著也覺得挺奇妙的,呵呵!

  但強烈建議不要把一個類當成一個函數來使用!如果你需要的是一個函數,請直接寫成函數而不要寫成類,以免搞混了.

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  繼承

  繼承這個詞在面向對象的編程里是非常重要的,雖然JavaScript並不是真正面向對象的語言,而是跟VB一樣是基於對象的語言,它同樣提供了繼承機制.

  文章開頭時談到了男人與女人,這也同樣是兩個不同的類,但卻具有相同的一些屬性以及方法,而這些相同的特性是來自「人」這個類的,換句話說男人與女人繼承了「人」的所有特性!但是男人與女人卻有其不同的地方,編程語言里的繼承也一樣,一個類A繼承了另一個類B,那麼類B就是類A的父類,類A就是類B的派生類,也稱為子類.比如男人就是人的派生類,而人就是男人的父類.最高一級的類稱為基類,想象一下就可以明白,男人繼承自人,男孩繼承自男人,人就是男孩的基類,男人就是男孩的父類.

  >>>>>>>>>>>>>>>>>>>>

  題外:多重繼承

  這裡再涉及一個多重繼承的話題,但如果你僅僅是學JavaScript的話就沒有必要看下去,因為JavaScript不提供多重繼承,準確一點說沒有一種簡單而標準的方法來實現多重繼承(其實是有辦法實現的,只不過麻煩了一點,確實沒有必要).

  在C 中是有多重繼承的概念的,這裡是討論JavaScript,因此不打算講,只是說說它的一點點思想以供參考.

  在上邊男孩的繼承問題中,男孩其實不僅僅是繼承自男人,還繼承自孩子(有男孩子,也有女孩子)這個類,因此,它同時繼承了兩個類:男人與男孩,這就是所謂的多重繼承.

  好,這個問題打住,我們還是回歸主題.

  >>>>>>>>>>>>>>>>>>>>

  先看第一個類的定義:

  function A()

  {

  this.Name = "泣紅亭";

  alert(this.Name);

  }

  這個類定義了一個屬性Name,默認值為"泣紅亭"

  現在看第二個類的定義:

  function B()

  {

  this.Sex = "男";

  alert(this.Sex);

  }

  定義了一個屬性Sex,默認值為"男"

  繼承的方式就是 子類.prototype = new 父類();

  現在我們來讓B類繼承A類:

  B.prototype = new A();

  運行這一段代碼:

  var Obj = new B(); //打開警告窗口顯示"泣紅亭",再顯示"男"

  可以從上邊的結果看出B類繼承了A類,擁有了A類的屬性Name,並且執行了A類的構造函數,A類的構造函數在B類的構造函數執行之前執行.因此我們利用這個可以實現重寫父類的方法以及重設置父類某屬性的默認值:

  function A()

  {

  this.Name = "泣紅亭";

  this.Show = function()

  {

  alert("這是A類的Show方法");

  }

  alert(this.Name);

  }

  function B()

  {

  this.Name = "鄭運濤";

  this.Show = function()

  {

  alert("這是B類的Show方法");

  }

  alert(this.Name);

  }

  var Obj = new B();

  Obj.Show();

  結果出現了三次警告窗口,第一個內容為泣紅亭,是執行A類的構造函數里的alert(this.Name),那時候Name屬性值還為"泣紅亭",因為B類的構造函數還沒執行,第二次內容為"鄭運濤",這是B類里的alert(this.Name),因為B類的構造函數里給Name重賦值為"鄭運濤".是調用了Obj.Show(),執行了不是A類的Show方法里的Show(顯示"這是A類的Show方法"),而是執行了B類的Show(顯示"這是B類的Show方法"),很明顯Show方法被重寫了.

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  類作為一個對象時的屬性與方法(不知道如何簡潔地表達,因此用了這麼長的題目)

  不知道在這裡談這個話題是否有點混人耳目,但又覺得不談這篇文章就不算完整,因為文章目的就是要讓人搞清楚類的方方面面.

  看了這一小節的題目,或許你會覺得奇怪,類就是類,怎麼會「作為一個對象」呢?在JavaScript里,一切都是對象,包括類!對象可以有屬性,可以有方法,類也同樣可以有,但這個非常容易跟前邊說到的靜態屬性與靜態方法搞混了,因此要仔細看清楚兩者的分別!

  定義一個類:

  function WuYouUser()

  {

  this.Name = "泣紅亭";

  }

  定義類作為一個對象時的屬性:

  WuYouUser.Url = "http://www.51js.com"; //靜態屬性的定義是:WuYouUser.prototype.Url = "http://www.51js.com";

  var Wo = new WuYouUser();

  document.write(WuYouUser.Url); //http://www.51js.com

  document.write(Wo.Url); //undefined,即未定義!注意這裡的未定義

  從這裡可以看出Url這個屬性是WuYouUser自個所有,改變了它與其它類以及它的子類完全無關!

  引用類的屬性只有一個辦法,就是類名.屬性名,改變它也一樣.

  定義類作為一個對象時的方法:

  WuYouUser.ChangeUrl = function()

  {

  this.Url = "http://51js.com";

  }

  你或許會覺得奇怪,這裡的this是什麼?因為ChangeUrl這個方法是屬於對象WuYouUser的,因此this指的就是WuYouUser本身!

  可以運行下邊的代碼試試:

  document.write(WuYouUser.Url); // http://www.51js.com

  WuYouUser.ChangeUrl();

  document.write(WuYouUser.Url); // http://51js.com

  明顯ChangeUrl直接修改了WuYouUser.Url的值,因此後邊才能輸出http://51js.com

  如果你這一節看不明白,也不要著急,編程嘛,許多東東都只能意會不能言傳,我又沒口才,說不清楚,只要以後多寫寫代碼,多用用類自然而然會體會到這一些,還有可以去看看JSVM的代碼,裡邊幾乎每個類都有用到類作為一個對象時的屬性與方法.

  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  後言

  感謝你能夠有耐心看到這裡,我也沒想到寫了這麼多才能夠寫得像樣一點,請別介意.

  不管是哪種語言,只要是支持類的,類都在這種語言中佔了非常重要的地位,但不是誰都能夠掌握它,為了讓無憂還沒學過類以及對類這個東東還搞不清楚的網友能夠清楚一點了解類的概念以及用法,也為了對無憂做點貢獻,我寫了這篇文章,希望大家能夠喜歡.

  我們最近常談javascript的OO,但請大家要格外記住,javascript 不是「面向對象」的語言,最多只能說是「基於對象」.

  「面向對象」和「基於對象」之間的差別,我一時很難找到 簡單且又準確 的辭彙來描述.

  談到 「面向對象」,我們可能會想到 c ,其次是 java,後來再就是 dot net ,(其他的偏門語言我們這裡不多討論)

  其實 c 在貫徹 「面向對象」思想方面 是不及 java 的,因為它還存在大量 「過程」型的東西. java 放棄了多重繼承,重載運算元等「繁索不實用」的東西,把設計重點放在interface(介面)上,不僅簡化的編程者的工作繁瑣度,讓整個框架看上去更加清晰.最重要的是 java 中所有的東西都是以類的形式存在的,沒有第二種形式.至於後來dotnet 中的 c# ,看起來就像是 ms 牌子的 java.

  扯遠了,回到 javascript 上來

  說Javascript不是面向對象 不僅僅是 說 它沒有真正意義上實現:抽象對象、 繼承、重載等等面向對象的功能

  而是說 javascript中的「類」 並不是真正廣義上「類」的概念.類原本是只是一個抽象定義,而javascript中通過「Function」 定義的類,本質上卻是一個「對象」!

  javascript的語法域並不是整個 IE 進程,而是以 Window對象為單位的.

  不同Window對象下相同的Function定義,並不是同一個「類」.

  比如:

  a.htm 中你定義了一個 A 類 function A(){} , b.htm 中你也相同定義了這個 A 類 function A(){}

  在 a.htm 中你創建了一個實例: var a = new A();

  你在 b.htm 中得到了 a.htm 的句柄 winAhandle

  然後你得到 a.htm 中 a 實例的引用

  var a = winAhandle.a;

  你會發現 a instanceof A 是 false,換成 a instanceof winAhandle.A 便是 true 了

  原因很簡單,b.htm 中的A 類並不等同於 a.htm 中的A類,這種「類」的語法域只限於 一個相同的 Window 對象下(同一個Window對象並不僅是指同一個頁面)

  這顯然是有悖於 類是一個廣義上的抽象定義 這種概念了

  會 VB 的人,也應該了解:VB4之後,VB.NET之前的 VB(包括VBS)中的類,也是這種情況,雖然它是通過 Class 的方式定義的.

  比如你 new A 放到 session 里,下一次從 session 中取出來,便不是原先那個對象了.

  其實就是因為語法域不同,前一次定義的類,並不能保留到這一次,解析器不知道他是什麼東西,故不能還原了

  順便提一下 javascript 中繼承方式是採用的 原型(prototype)繼承,詳細的介紹,大家可以去找這本書看看

  《Design Patterns Elements of Reusable Object Oriented Soffware》

  中文版好像叫 《可復用面向對象的設計模式》

  一本好書!!!

  javascript中沒有多重繼承.多重繼承在面向對象中並不是必須的.雖然多重繼承的重用性更好,但會導致類之間的關係過於複雜.

  一般來說,一樣事物,我們通常可以認為它主要是某一類事物的衍生物,單一繼承就夠用了,至於其他的特性,我們可以藉助介面來定義.

  javascript中也沒有介面概念,javascript不需要介面,因為它是一門解釋型的語言,不對實參的類型做預校驗.作為一個參數對象,有沒有某個方法,載入過程中並不去檢查,直到運行時,有則調用,無則異常.不需要強制性聲明繼承了哪個介面才能做為參數調用.

  但實際中,我們還是需要設計一些介面,主要是出於view的考慮,整個框架容易被讀懂!


[火星人 ] 由淺到深了解JavaScript類已經有628次圍觀

http://coctec.com/docs/java/show-post-60539.html