天天看點

我的Javascript之旅——對象的原型鍊之由來

本人是Javascript菜鳥,下面是前幾天學習Javascript的旅程心得,希望對和我一樣的入門者有點用,也希望高手批評指正。

以問題開始:

function
    Base(){}

   var
    base 
   =
    
   new
    Base()
        

上面兩行代碼會建立幾個對象(object)?

要回答這個問題,先明确一下Javascript裡object的概念。

Objects

在Javascript裡,幾乎一切都是object(Arrays、Functions、Numbers、Objects……),而沒有C#裡的class的概念。object的本質是一個name-value pairs的集合,其中name是string類型的,可以把它叫做“property”,value包括各種objects(string,number,boolean,array,function…),指的是property的值。

typeof

既然object包含Arrays、Functions、Numbers、Objects……,那怎麼區分這些呢?答案是typeof。 typeof傳回一個字元串,如typeof(Null) = “object”,typeof(false) = “Boolean”, typeof(1) = “number”。既然總是傳回字元串,那麼對于typeof (typeof x),不管x是什麼,總是傳回”string”。

    

我的Javascript之旅——對象的原型鍊之由來

Constructor

JS裡沒有class,也就沒有class裡的構造函數,那麼object是怎麼被建立的呢?用構造器:constructor。constructor其實就是Function,是以本身也是object。開頭的function Base(){}就是一個構造器,var b = new Base()就是用這個構造器(通過關鍵字new)建立了一個叫b的object。至此我們可以得出結論,開頭的兩行代碼至少建立了2個object:一個是Base,類型為function的object,一個是base,類型為object的object。

Function()和Object()

這是兩個重要的預定義好的構造器。一切function(比如開頭的Base())都是由Function()構造出來的;而Object的prototype将會被所有object繼承,下面會講到。

    

我的Javascript之旅——對象的原型鍊之由來

Function的建立過程

當執行function Base(){this.a = 1}時,相當于var Base = new Function(“this.a = 1”),也就是說,這行代碼本身,将使用預定義好的Function() constructor,來構造一個function型object(即Base)出來。在這個建立過程中,js将做哪些事呢?

  1, 首先當然會建立一個object起來,Base指向這個object。typeof 這個object = “function”

     

我的Javascript之旅——對象的原型鍊之由來

  2, 給Base附上__proto__屬性,讓它等于Function這個構造器的prototype(也是預定義好的)。這是很重要的一步,也是規律性的一步。(規律:)在執行任意類似varx = new X()時,都會把X的prototype賦給x的__proto__,也就是說,x.__proto__和X.prototype此時會指向同一個對象。

     

我的Javascript之旅——對象的原型鍊之由來

  3, 為Base建立call屬性,該屬性是個function。是以我們可以這樣寫:Base.Call()

     

我的Javascript之旅——對象的原型鍊之由來

  4, 為Base建立Construct屬性,該屬性也是個function。在執行var base = new Base()時,即會調用這個Construct屬性。

  5, 為Base建立Scope,Length等屬性,略。

  6, 為Base建立prototype屬性:先用new Object()建立一個對象,為這個對象建立一個屬性叫constructor,該屬性值設定為Base。再把Base的prototype設定為這個新建立的對象。僞代碼如下:

var
    x 
   =
    
   new
    Object();
x.constructor 
   =
    Base;
Base.prototype 
   =
    x;
        

先把關注點放到2和6。

__proto__和prototype

從2可以看出來,任意一個用構造器構造出來的object(包括Objects和Functions),都會有__proto__屬性,指向該構造器的prototype屬性。注意__proto__是個私有屬性,在IE上是看不到的,我用的是chrome,可以看到。

從6可以看出,任意一個用new Function()構造出來的functions,都會有prototype屬性,該屬性是用new Object()建構出來的,初始公開屬性隻有一個constructor。

    

我的Javascript之旅——對象的原型鍊之由來

原型鍊

再來分析下第6步的僞代碼,也就是為function建立prototype的這一步:

var
    x 
   =
    
   new
    Object();  
   //
     參見2中的規律,會有x.__proto__= Object.prototype。
   

   x.constructor 
   =
    Base;
Base.prototype 
   =
    x;
        

此時我們用Base()構造一個對象出來:

var
    base
   =
    
   new
    Base(); 
   //
    參見2中的規律,會有base.__proto__ = Base.prototype,也就是 = x。
  
  
    
                         // 是以有base.__proto__.__proto__ = x.__proto__
  
  
                         // 而x.__proto__ = Object.prototype(見上一個代碼片段)  
  
  
                         // 是以,base.__proto__.__proto__ = Object.prototype.
        

__proto__.__proto__,這就是傳說中JS對象的原型鍊!由于用Function()建立構造器時的關鍵的第6步,保證了所有object的原型鍊的頂端,最終都指向了Object.prototype。

    

我的Javascript之旅——對象的原型鍊之由來

Property Lookup

而我們如果要讀某個object的某個屬性,JS會怎麼做呢?

比如有個object叫xxx,我們執行alert(xxx.a),也就是讀取xxx的a屬性,那麼JS首先會到xxx本身去找a屬性,如果沒找到,則到xxx.__proto__裡去找a屬性,由此沿着原型鍊往上,找到即傳回(沒找到,則傳回undefined)。可以來看個例子:

    

我的Javascript之旅——對象的原型鍊之由來

上圖得知:base本身是沒有constructor屬性的,但是base.constructor确實能傳回Base這個函數,原因就在于base.__proto__有這個屬性。(base.__proto__是啥?就是Base.prototype,上面建構Function的第6步的僞代碼裡,為Base.prototype.constructor指派為Base本身。)

Object作為“基類”

另外,由于任意object的原型鍊的頂端都是Object.prototype。是以,Object.prototype裡定義的屬性,就會通過原型鍊,被所有的object繼承下來。這樣,預定義好的Object,就成了所有對象的“基類”。這就是原型鍊的繼承。

    

我的Javascript之旅——對象的原型鍊之由來

看上圖,Object.prototype已經預定義好了一些屬性,我們再追加一條屬性叫propertyA,那麼這個屬性和預定義屬性一樣,都可以從base上讀到。

原型繼承

已經得知,

對于 var xxx =new Object(); 有xxx.__proto__= Object.prototype;

對于 var xxx =new Base(); 有xxx.__proto__.__proto__= Object.prototype;

看上去很像什麼呢?從c#角度看,很像Base是Object的子類,也就是說,由Base()構造出來的object,比由Object()構造出來的object,在原型鍊上更低一個層級。這是通過把Base.prototype指向由Object()建立的對象來做到的。那麼自然而然,如果我想定義一個繼承自Base的構造器,隻需把改構造器的prototype指向一個Base()構造出來的對象。

function
    Derived(){}

   var
    base 
   =
    
   new
    Base();
Derived.prototype 
   =
    base;

   var
    d 
   =
    newDerived();  
   //
   很容易推算出:d.__proto__.__proto__.__proto__ = Object.prototype.
        

推算過程:d.__proto__指向Derived.prototype,也就是base;則__proto__.__proto__指向base.__proto__,也就是Base.prototype,也就是某個new object()建立出來的東東,假設是o;則__proto__.__proto__.__proto__指向o.__proto__,也就是Object.prototype。

回答開頭的問題,以及幾個新的問題

那兩行代碼至少建立了三個對象:Base、base、Base.prototype。順便說說,base是沒有prototype屬性的,隻有function類型的object,在被建構時才會被建立prototype屬性。

    

我的Javascript之旅——對象的原型鍊之由來

d.constructor會傳回什麼呢?

構造器Base()和Derived()裡都是空的,如果有代碼,将會怎麼被執行呢?

先看張對老手不新鮮但對菜鳥很有趣的圖:

    

我的Javascript之旅——對象的原型鍊之由來

What the heck is that? 簡直是luan lun。

new

抛開上面的圖,先看看上篇文章留下的第二個問題,讓我們在構造器的函數體内加點東西,看會發生什麼。

function
    A(){
   this
   .p 
   =
    
   1
   }

   var
    a 
   =
    
   new
    A()
        

會得到如下結果:

    

我的Javascript之旅——對象的原型鍊之由來

為什麼用new關鍵字構造出來的a,會獲得p這個屬性?new A()這行代碼做了什麼事情?根據上篇文章中Function的建立過程第4步,A這個對象會有一個Construct屬性(注意不是constructor,Consturct是ECMAScript标準裡的屬性,好像對外不可見),該屬性的值是個函數,new A()即會調用A的這個Construct函數。那麼這個Construct函數會做些啥呢?

  1, 建立一個object,假設叫x。

  2, 如果A.prototype是個object(一般都是),則把A.prototype賦給x.__proto__;否則(不常見),請大老闆Object出馬,把Object.prototype賦給x.__proto__。

  3, 調用A.call(x),第一個參數傳入我們剛剛建立的x。這就妥了,A的函數體裡this.p = 1,這個this,就成了x。是以x就有了p這個屬性,并且x.p = 1。

  4, 一般情況下,就傳回x了,這時a就是x了。但也有特殊情況,如果A的函數體裡傳回的東西,它的類型(typeof)是個object。那麼a就不是指向x了,而是指向A函數傳回的東西。

僞代碼如下:

var
    x 
   =
    
   new
    Object(); 
   //
   事實上不一定用new來建立,我也不清楚。
   

   x.__proto__ 
   =
    A.prototype 

   var
    result 
   =
    A.call(x)

   if
    (
   typeof
   (result) 
   ==
    
   "
   object
   "
   ){
    
   return
    result;
}

   return
    x;

        

在我們的例子裡,A函數傳回undefined(因為沒有return字眼),是以a就是x。但我們舉個例子,驗證下上面第4步裡的特殊情況:

    

我的Javascript之旅——對象的原型鍊之由來

果然。

對象的constructor屬性

再看看上篇文章留下的第一個問題

function
    Base(){}
Base.prototype.a 
   =
    
   1
   

   var
    base 
   =
    
   new
    Base();
 

   function
    Derived(){}
Derived.prototype 
   =
    base;

   var
    d 
   =
    
   new
    Derived()
        

執行完上面的代碼,mybase.constructor很容易猜到是Base,那麼d.constructor呢?是Derived嗎?

     

我的Javascript之旅——對象的原型鍊之由來

不對,也是Base,怎麼回事?很簡單,複習下上篇的内容就知道:由于d本身沒有constructor屬性,是以會到d.__proto__上去找,d.__proto__就是Derived.prototype,也就是base這個對象,base也沒constructor屬性,于是再往上,到base.__proto__上找,也就是Base.prototype。它是有constructor屬性的,就是Base本身。事實上,就我目前所知,隻有構造器(function類型的object)的prototype,才真正自己擁有constructor屬性的對象,且“構造器.prototype.constructor === 構造器”。

Instanceof

那麼,instanceof怎麼樣?

    

我的Javascript之旅——對象的原型鍊之由來

從圖中可以看出,d是Base、Derived和Object的執行個體。很合理,但這是怎麼判斷的呢?是這樣的:對于x instanceof constructor的表達式,如果constructor.prototype在x的原型(__proto__)鍊裡,那麼就傳回true。很顯然,d的__proto__鍊往上依次是:Derived.prototype, Base.prototype, Object.prototype,得到圖中結果就毫無疑問了。是以,instanceof跟對象的constructor屬性無關。

Function and Object

最後解答一下文章開頭的圖。

Function和Object本身也是function類型的對象,是以可以說都是Function()構造出來的東西(自己構造自己,我不知道具體是不是這樣,但就這麼認為,挺合理的。)

也就是說,可以設想如下代碼:

var
    Function 
   =
    
   new
    Function()

   var
    Object 
   =
    
   new
    Function()  
        

根據上篇文章的規律,會有Function.__proto__ === Function.prototype,以及Object.__proto__ === Function.prototype,驗證一下:

    

我的Javascript之旅——對象的原型鍊之由來

Function instanceof Object,這是顯然為true的,萬物歸Object管,Function的__proto__鍊依次指向:Function.prototype,Object.prototype。

Object instanceof Function,因為Function.prototype在Object的__proto__鍊中,是以也為true。

【update】

5樓的評論引用的圖檔,被chrome報有威脅,是以我移到這裡來了。

#5樓   2010-08-25 19:57  趙弟棟        Object 是對象的祖先

Function 是函數的祖先

函數可以做構造器

對象是函數new出來的

構造器的prototype是對象

對象的__proto__指向構造器的prototype     

我的Javascript之旅——對象的原型鍊之由來

這樣就都串起來了

繼續閱讀