本人是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”。
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繼承,下面會講到。
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”
2, 給Base附上__proto__屬性,讓它等于Function這個構造器的prototype(也是預定義好的)。這是很重要的一步,也是規律性的一步。(規律:)在執行任意類似varx = new X()時,都會把X的prototype賦給x的__proto__,也就是說,x.__proto__和X.prototype此時會指向同一個對象。
3, 為Base建立call屬性,該屬性是個function。是以我們可以這樣寫:Base.Call()
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。
原型鍊
再來分析下第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。
Property Lookup
而我們如果要讀某個object的某個屬性,JS會怎麼做呢?
比如有個object叫xxx,我們執行alert(xxx.a),也就是讀取xxx的a屬性,那麼JS首先會到xxx本身去找a屬性,如果沒找到,則到xxx.__proto__裡去找a屬性,由此沿着原型鍊往上,找到即傳回(沒找到,則傳回undefined)。可以來看個例子:
上圖得知: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,就成了所有對象的“基類”。這就是原型鍊的繼承。
看上圖,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屬性。
d.constructor會傳回什麼呢?
構造器Base()和Derived()裡都是空的,如果有代碼,将會怎麼被執行呢?
先看張對老手不新鮮但對菜鳥很有趣的圖:
What the heck is that? 簡直是luan lun。
new
抛開上面的圖,先看看上篇文章留下的第二個問題,讓我們在構造器的函數體内加點東西,看會發生什麼。
function
A(){
this
.p
=
1
}
var
a
=
new
A()
會得到如下結果:
為什麼用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步裡的特殊情況:
果然。
對象的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嗎?
不對,也是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怎麼樣?
從圖中可以看出,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,驗證一下:
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
這樣就都串起來了