JavaScript原型和繼承

來源:互聯網
上載者:User

一、函數建立過程

在瞭解原型鏈之前我們先來看看一個函數在建立過程中做了哪些事情,舉一個空函數的例子:

1 function A() {};

當我們在代碼裡面聲明這麼一個空函數,js解析的本質是(膚淺理解有待深入):

1、建立一個對象(有constructor屬性及[[Prototype]]屬性),根據ECMA,其中[[Prototype]]屬性不可見、不可枚舉

2、建立一個函數(有name、prototype屬性),再通過prototype屬性 引用 剛才建立的對象

3、建立變數A,同時把函數的 引用 賦值給變數A

如所示:

 

 

(注意圖中都是“ 引用 ”類型)


 

每個函數的建立都經曆上述過程。
 
二、建構函式
那麼什麼是建構函式呢?
按照ECMA的定義
Constructor is a function that creates and initializes the newly created object.
建構函式是用來建立同時初始化一個新對象的函數。
什麼樣的函數可以用來建立同時初始化新對象呢?答案是:任何一個函數,包括空函數。
所以,結論是:任何一個函數都可以是建構函式。
 
三、原型
根據前面空函數的建立圖示,我們知道每個函數在建立的時候都自動添加了prototype屬性,這就是函數的原型,可知其實質就是對一個對象的引用(這個對象暫且取名原型對象)。
我們可以對函數的原型對象進行操作,和普通的對象無異!一起來證實一下。
圍繞剛才建立的空函數,這次給空函數增加一些代碼:
 function A() {
 this.width = 10;
 this.data = [1,2,3];
 this.key = "this is A";
 }
 A._objectNum = 0;//定義A的屬性
 A.prototype.say = function(){//給A的原型對象添加屬性
 alert("hello world")
 }
第7~9行代碼就是給函數的原型對象增加一個say屬性並引用一個匿名函數,根據“函數建立”過程,圖解如下:

 




  

(灰色背景就是在空函數基礎上增加的屬性)
簡單說原型就是函數的一個屬性,在函數的建立過程中由js編譯器自動添加。
那麼原型有什麼用呢?
先瞭解下new運算子,如下:
1
2 var a1 = new A;
 var a2 = new A;
這是通過建構函式來建立對象的方式,那麼建立對象為什麼要這樣建立而不是直接var a1 = {};呢?這就涉及new的具體步驟了,這裡的new操作可以分成三步(以a1的建立為例):
1、建立一個對象並賦值給變數a1:var a1 = {};
2、把這個對象的[[Prototype]]屬性指向函數A的原型對象:a1.[[Prototype]] = A.prototype
3、調用函數A,同時把this指向1中建立的對象a1,對對象進行初始化:A.apply(a1,arguments)
其結構圖示如下:

 


 

看到,無論是對象a1還是a2,都有一個屬性儲存了對函數A的原型對象的引用,對於這些對象來說,一些公用的方法可以在函數的原型中找到,節省了記憶體空間。
 
四、原型鏈
瞭解了new運算子以及原型的作用之後,一起來看看什麼是[[Prototype]]?以及對象如何沿著這個引用來進行屬性的尋找?
在js的世界裡,每個對象預設都有一個[[Prototype]]屬性,其儲存著的地址就構成了對象的原型鏈,它是由js編譯器在對象 被建立 的時候自動添加的,其取值由new運算子的右側參數決定:當我們var object1 = {};的時候,object1的[[Prototype]]就指向Object建構函式的原型對象,因為var object1 = {};實質上等於var object = new Object();(原因可參照上述對new A的分析過程)。
對象在尋找某個屬性的時候,會首先遍曆自身的屬性,如果沒有則會繼續尋找[[Prototype]]引用的對象,如果再沒有則繼續尋找[[Prototype]].[[Prototype]]引用的對象,依次類推,直到[[Prototype]].….[[Prototype]]為undefined(Object的[[Prototype]]就是undefined)
如所示:
 //我們想要擷取a1.fGetName
  alert(a1.fGetName);//輸出undefined
  //1、遍曆a1對象本身 www.2cto.com
  //結果a1對象本身沒有fGetName屬性
  //2、找到a1的[[Prototype]],也就是其對應的對象A.prototype,同時進行遍曆
  //結果A.prototype也沒有這個屬性
  //3、找到A.prototype對象的[[Prototype]],指向其對應的對象Object.prototype
  //結果Object.prototype也沒有fGetName
  //4、試圖尋找Object.prototype的[[Prototype]]屬性,結果返回undefined,這就是a1.fGetName的值
簡單說就是通過對象的[[Prototype]]儲存對另一個對象的引用,通過這個引用往上進行屬性的尋找,這就是原型鏈。
 
五、繼承
有了原型鏈的概念,就可以進行繼承。
1 function B() {};
這個時候產生了B的原型B.prototype
原型本身就是一個Object對象,我們可以看看裡面放著哪些資料
B.prototype 實際上就是 {constructor : B , [[Prototype]] : Object.prototype}
因為prototype本身是一個Object對象的執行個體,所以其原型鏈指向的是Object的原型
 B.prototype = A.prototype;//相當於把B的prototype指向了A的prototype;這樣只是繼承了A的prototype方法,A中的自訂方法則不繼承
  B.prototype.thisisb = "this is constructor B";//這樣也會改變a的prototype
但是我們只想把B的原型鏈指向A,如何??
第一種是通過改變原型鏈引用地址
1 B.prototype.__proto__ = A.prototype;
ECMA中並沒有__proto__這個方法,這個是ff、chrome等js解譯器添加的,等同於EMCA的[[Prototype]],這不是標準方法,那麼如何運用標準方法呢?
我們知道new操作的時候,實際上只是把執行個體對象的原型鏈指向了建構函式的prototype地址塊,那麼我們可以這樣操作
1 B.prototype = new A();
這樣產生的結果是:
產生一個A的執行個體,同時賦值給B的原型,也即B.prototype 相當於對象 {width :10 , data : [1,2,3] , key : "this is A" , [[Prototype]] : A.prototype}
這樣就把A的原型通過B.prototype.[[Prototype]]這個對象屬性儲存起來,構成了原型的連結
但是注意,這樣B產生的對象的建構函式發生了改變,因為在B中沒有constructor屬性,只能從原型鏈找到A.prototype,讀出constructor:A
 var b = new B;
  console.log(b.constructor);//output A
所以我們還要人為設回B本身
B.prototype.constructor = B;
  //現在B的原型就變成了{width :10 , data : [1,2,3] , key : "this is A" , [[Prototype]] : A.prototype , constructor : B}
  console.log(b.constructor);//output B
  //同時B直接通過原型繼承了A的自訂屬性width和name
  console.log(b.data);//output [1,2,3]
  //這樣的壞處就是
  b.data.push(4);//直接改變了prototype的data數組(引用)
  var c = new B;
  alert(c.data);//output [1,2,3,4]
  //其實我們想要的只是原型鏈,A的自訂屬性我們想在B中進行定義(而不是在prototype)
  //該如何進行繼承?
  //既然我們不想要A中自訂的屬性,那麼可以想辦法把其過濾掉
  //可以建立一個空函數
  function F(){}
  //把空函數的原型指向建構函式A的原型
  F.prototype = A.prototype;
  //這個時候再通過new操作把B.prototype的原型鏈指向F的原型
  B.prototype = new F;
  //這個時候B的原型變成了{[[Prototype]] : F.prototype}
  //這裡F.prototype其實只是一個地址的引用
  //但是由B建立的執行個體其constructor指向了A,所以這裡要顯示設定一下B.prototype的constructor屬性
  B.prototype.constructor = B;
  //這個時候B的原型變成了{constructor : B , [[Prototype]] : F.prototype}
  //這樣就實現了B對A的原型繼承
圖示如下,其中紅色部分代表原型鏈:

 

作為初學者淺陋的理解,本文目的在於更具象地去理解js的物件導向,疏漏之處請指正。

 
 

摘自 網易郵箱前端技術中心
 



 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.