相信很多人都很困惑于javascript的原型系統,,在我剛開始接觸javascript的原型系統的時候,我也非常困惑于它的結構,因為我以前都沒有接觸過這種基于原型的語言。javascript是一個基于原型的語言,雖然是以函數為第一等公民的語言,但也可以實現面向對象,那就是基于它的原型系統。
javascript原型之函數
現在我們來寫一個函數
function A(){
}
這是一個內容為空的函數,但它真的內容為空嗎?讓我們來看下面一段代碼
function A(){
}
console.log(A.toString());;//輸出為function A() {}
我們知道,javascript里面的一切都是對象,函數也是,既然函數是對象那么就可以調用函數對象的方法,所以我試著調用了toString 方法,它輸出了一個字符串,證明toString方法是存在的。那么toString 方法到底是存在在那里呢,不存在于函數體里,那么存在一個地方必然有toString的函數體且對象function A以某種方式獲得了toString方法的調用權。。。
我百度了一下基于原型的語言的特征,基于原型的語言,必然有一個或著多個最初的對象,然后以后的對象都是由這些最初對象克隆過來的,也就是說,基于原型的語言中對象的生成是根據存在的對象來復制的。
好,那我們開始下一步的實驗
function A(){
}
console.log(A.__proto__);//Function
console.log(A.prototype);//{}
我輸出了Javascript對象所擁有的兩個屬性,這是javascript語言規定的兩個屬性_proto_屬性指向對象構造函數的原型(不是很理解),prototype屬性指向對象的原型。從結果看A函數構造函數的原型是Function,A函數自己的原型是{ }(同樣不是很理解)
于是我又做了下面這個實驗
Function.prototype.getName = function(){
return "FunctionTest";
}
function A(){
}
console.log(A.__proto__ === Function.prototype);//true
console.log(A.getName());//FunctionTest
function B(){
}
console.log(B.__proto__ === Function.prototype);//true
console.log(B.getName());//FunctionTest
console.log(A.__proto__ === A.constructor.prototype);//true
//即函數作為對象它的構造函數為Function
我從另外的地方得知javascript里面有內建的Function和Object對象,于是我想著Function對象是否和function A 有些關聯呢,當我看到第一條console.log語句返回true的時候,我知道我是正確的,于是我擴展了Function.prototype 給其中添加了getName方法,然后我在用函數A調用了這個方法返回FunctionTest,我又新建了函數B,也調用了這個方法,返回FunctionTest。
至此我知道了函數_proto屬性的指向,指向其構造函數的原型,當對象A調用getName函數的時候,由于A對象沒有getName函數,javascript會尋找對象A的_proto屬性所對應對象,有則調用,沒有則繼續向上找。
函數的prototype屬性我一開始始終沒有找到與之對應的對象
console.log(A.prototype === Function.__proto__);//false
console.log(A.prototype === Function.prototype);//false
console.log(A.prototype === Object.__proto__);//false
console.log(A.prototype === Object.prototype);//false
后來我換了一種思考方式終于找到了
console.log(A.prototype.__proto__ === Object.prototype);//true
console.log(A.prototype.prototype);//undefined
而之后我又實驗了
console.log(Function.prototype.__proto__ === Object.prototype);
所以最終所有的一切對象的內置函數比如toString都是在Object.prototype里的
然后我又有一個猜測
所有一切函數對象的內置函數比如call,apply都是Function.prototype里的,可以很容易的就驗證,普通對象是不能調用call,apply的。
javascript原型之對象
我實驗了如下的代碼
function A(){
this.getText = function(){
return "Text";
}
}
A.prototype.getName = function(){
return "my god";
}
var i = new A();
console.log(i.getText());//Text
console.log(i.getName());//my god
然后我改了一下程序
function A(){
this.getName = function(){
return "Text";
}
}
A.prototype.getName = function(){
return "my god";
}
var i = new A();
console.log(i.getName());//Text
console.log(i.__proto__ === A.prototype);//true
console.log(i.prototype);//undefined
由上面的實驗可知,由函數A作為構造函數,所克隆出來的普通對象i的_proto_屬性指向函數A的原型(也就是prototype屬性),且普通對象i的prototype屬性是沒有定義的。
當i調用getName函數時,由于i是由函數A克隆出來(大家還沒忘記原型語言的特征吧,就是新的對象是由另一個已存在的對象克隆的)的,里面只用getText函數沒有getName函數,于是javascript就會尋找i的_proto_屬性,而i的proto屬性所指向的其實就是A.prototype,所以javascript就在A.prototype里面找getName函數,找到了就調用。所以第二段代碼中優先調用返回Text的那個getName函數。
javascript里面還有一種對象就是對象字面量,對象字面量的是否擁有Function.prototype或者Object.prototype的函數呢
請看實驗
var a = {
getName:function(){
return "a";
}
}
console.log(a.__proto__);//{}
console.log(a.prototype);//undefined
console.log(a.__proto__ === Object.prototype);//true
由于對象字面量不是由函數對象克隆的,所以沒有Function.prototype里面的方法,因為a._proto_指向的是Object.prototype,這就說明javascript只能在a本身和Object.prototype里面找調用的函數。
總結
由上面以一些內容我們可以得出:
1.javascript函數調用順序是查找對象的原型(即,對象的_proto_屬性),一層一層的往上找,直到遇到該函數或者undefined才停止。
2.函數作為特殊的對象,它的原型是Function.prototype,能夠調用Function.prototype里面的方法,而Function.prototype的原型又是Object.prototype,故而函數對象既能調用Function.prototype里面的方法,還能調用Object.prototype里面的方法,這就說函數對象即是“函數”又是對象。
3.普通對象的原型是其構造函數的原型(即,A.prototype),而A.prototype的原型是Object.prototype,所以,普通對象只能調用它本身和Object.prototype內的函數
4.對象字面量的原型是Object.prototype。