瞭解JavaScript的類和對象

來源:互聯網
上載者:User

類是什嗎?

 

 

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

如何建立一個類?
  在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的代碼,裡邊幾乎每個類都有用到類作為一個對象時的屬性與方法。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.