Javascript: 從prototype漫談到繼承(1)

來源:互聯網
上載者:User
文章目錄
  • 關於原型的繼承

本文同時發布在另一獨立部落格 Javascript: 從prototype漫談到繼承(1)

javasscript的prototype原型鏈一直是一個痛點,這篇文章是對自己這段時期學習的一個總結,在這裡不談ECMAScript標準,也不會用UML圖畫出各種關係(結合這兩方面談的文章非常的多,但大部分都相當晦澀,比如湯姆大叔),只力求最淺顯易懂,深入淺出,供以後自己和各位參考。

javascript的function一種對象(object),他們有方法和屬性,方法比如call/apply,而prototype則是function的一個屬性。

一旦你定義了一個函數,它即內建了一個prototype屬性

function t(){};
typeof t.prototype // "object";

 

你可能已經知道使用函數作為一個建構函式,來生產一系列對象。比如

function Some(name, color){
this.name = name;
this.color = color;
this.method =function(){}
}

var a1 =new Some("Lee","black");//執行個體化一個對象

 

上面的Some類的屬性和方法也可以放在prototype對象中,比如

function Some(){}
Some.prototype.name ="Lee"//形式一

Some.prototype ={ //形式二 name:"lee", color:"black", method:function(){}
}
var a1 =newSome("Lee","black");//執行個體化一個對象

 

雖然形式不同,但至少現在使用起來的效果是一致的。當你使用a.Lee或者a.method時,結果是一樣的,現在還看不出分別

Ok,那麼第一點要注意的是,prototype是活著(live)的屬性! 

function Some(){}
var a =newSome();
a.method // undefined

Some.prototype.method = function(){ console.log("hello");
}a.method // function () {console.log("Hello")}

 

上面的代碼想說明的是,在產生執行個體a時,建構函式沒有method方法,所以a也沒有,可以理解;但是之後建構函式在prototype屬性裡又添加上去了,雖然是在a產生之後添加的,但是a仍然照樣擁有,與建構函式添加的時間無關。

第二個問題來了,如果這個對象內部和prototype都定義了相同的欄位怎麼辦,比如

function Some(){
this.color ="yellow";
}
Some.prototype.color ="black";
var a =newSome();
a.color //?

上面的代碼中,我在對象的內部和prototype上分別都定義了color,當我從執行個體中訪問的時候,應該顯示的是哪一個顏色?

要注意的是第二點,javascript引擎首先會檢查a的屬性裡有沒有color,如果沒有的話去它的建構函式的prototype(a.constructor.prototype)裡有沒有該屬性

 

讓我們再看的遠一點,任何一個對象都應該有自己的建構函式,函數的prototype屬性也是個對象,那它的建構函式是什嗎?

functionSome(){
this.color ="yellow";
}
var a =newSome();
a.constructor.prototype.constructor // function Some() {this.color = "yellow";}a.constructor.prototype.constructor.prototype // Some {}

 

上面的原型鏈可以無限的追溯下去,通過原型鏈,可以追溯到最終的建構函式Object(),這也就解釋了,為什麼即使我們沒有在函數上定義toString()函數,a.toString()的方法也是存在的,因為它最終調用的追溯到的Object的toString方法。

 

新的問題是,如何區分自己的property和原型鏈上的屬性,並且你能保證所有的屬性都是可以訪問的嗎?

眾所周知,用for...in迴圈就可以解決這個問題,關於這個問題,只需要記住三點

  • 雖然在迴圈中對象自己的屬性和原型鏈屬性都會被列舉出來,但並非所有屬性都會被列舉,比如一個數組的length和.splice之類的方法就不一定會被列舉出來,可以列舉出來的屬性都是可枚舉的(enumerable)
  • 如何區分對象自己的屬性還是原型鏈的屬性?使用hasOwnProperty()方法
  • 注意propertyIsEnumerable()方法,雖然該方法名字是“可枚舉的屬性”,但是原型鏈中所有的屬性都會反悔false,即使是可枚舉的

還有一個對象的屬性叫做__prop__,個人認為用處不大,只推薦在調試的時候使用,具體用法google去吧

關於原型的繼承

如何寫一個好的繼承方法?這是一個逐漸演化過程,先從最簡單的繼承談起

function Parent(){
this.deep ="Hello";
}
function Child(){
this.shallow ="World";
}
Child.prototype =new Parent();
var c =newChild();
console.log(c.deep);

 

當我們要訪問c的deep屬性時

  • 首先去c對象下查看有沒有deep屬性,沒有
  • 再去c.construct.prototype對象的屬性裡尋找,Parent的執行個體裡尋找,有

但是上面的代碼有一個問題,當你不斷執行個體化Child時,Parent也不會被執行個體化,都會產生一個deep載入記憶體中,如果這個deep是共用的話,不如把deep放在prototype中

function Parent(){}
Parent.prototype.deep ="Hello";

function Child(){
this.shallow ="World";
}
Child.prototype =new Parent();
var c =newChild();
console.log(c.deep)
 

當我們要訪問c的deep屬性時

  • 首先去c對象下查看有沒有deep屬性,沒有
  • 再去c.construct.prototype對象的屬性裡尋找,Parent的執行個體parent裡尋找,沒有
  • 再去parent.construct.prototype尋找deep,有

這麼做的弊端之一就是在尋找某個屬性的時候可能會多尋找一輪

讓我們繼續改進,我們發現我們需要的deep只在Parent的prototype上,那麼其實我只需要Parent的prototype而不是Parent的執行個體

function Parent(){}
Parent.prototype.deep ="Hello";

function Child(){
this.shallow ="World";
}

Child.prototype = Parent.prototype;
var c =new Child();
console.log(c.deep);

 

這樣既避免了Parent的執行個體化,又避免了上一個例子中多一步的尋找。但是有一個副作用,因為是對對象直接的引用,所以當Child.prototype.deep被修改時,Parent.prototype.deep也會被修改。那我們繼續最佳化的目標就很明確了,要阻止這種對父類prototype的直接引用。

於是我們決定使用一個中間變數

function Parent(){}
Parent.prototype.deep ="Hello";// 注意,來了var F =function(){};F.prototype = Parent.prototype;

function Child(){
this.shallow ="World";
}
Child.prototype = new F();
var c = new Child();
console.log(c.deep)

我們用F來作為一個中間變數,來阻止child對deep的修改可能影響parent

當我們要訪問c的deep屬性時

  • 首先去c對象下查看有沒有deep屬性,沒有
  • 再去c.construct.prototype對象的屬性裡尋找,F的執行個體裡尋找,沒有
  • 再去F.construct.prototype尋找deep,有

讓我們來捋一捋為什麼對Child.prototype的修改不會影響Parent.prototype

  • 在上一個例子中,我們對Child.prototype的操作就是對Parent.prototype的操作,無論讀還是寫,用的是別人的
  • 在這個例子中,Child.prototype不是對Parent的直接引用,而是一個新的Null 物件。在沒有deep而我們需要deep時,被迫去Child.prototype的建構函式上去找,追溯到了Parent.protoype,而當我們需要寫時,操縱的其實是Child.prototype = {}這個Null 物件。

於是我們把最後一個程式碼片段抽象為一個方法

function extend(Child,Parent){
var F =function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
// 一旦重設了函數的prototype,需要重新賦值prototype.constructor,
// 忽略這方面的介紹

Child.prototype.constructor = Child;

// 保留對父類的引用,
// 忽略對這方面的介紹
Child.uber =Parent.prototype;
}

 

相關文章

聯繫我們

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