javascript原型的那些事兒

聲明:

        發帖是對自己學過知識的一個總結,也是分享知識成果的一種有效途徑。由於個人經驗有限,難免會有一些錯誤的地方,還請大家批評,指正。

        也可以致電  [email protected] 告訴我錯誤,在此表示感激。

        若需轉載,請註明出處。

一.有感而發

        Javascript(以下簡稱js)是每個web開發人員都會接觸到的一門動態腳本語言,我想很多人最先會用到它就是利用它來做web頁面上的表單檢驗,而其實,js的用處遠不止如此。爲了提供更加強大的處理,現在流行的組件庫,如Jquery,Extjs,Dojo等封裝了很多的特性,使得我們利用js可以比較輕鬆做很多複雜的事情。這些組件庫大多都是很龐大和複雜的,特別是每個庫都有自己的設計理念,但不管怎樣,都會用到js的prototype這個特性,它可是js中非常非常重要的特性之一。所以今天單獨來聊聊這一個特性。我相信每一個想要學好js的人都應該好好掌握js中原型這個概念,特別是在看別人的代碼時,如果不懂原型,你可能會常常覺得一頭霧水。


二.閒聊

        我們知道,js中兩個大的概念就是函數和對象,用Function創建出來的就是函數,用Object創建出來的就是對象,但其實函數也是對象來的。因爲對象其實就是鍵值對的一個集合,是一種封裝和聚集信息的載體,而函數因爲也可以有方法和屬性,所以稱它爲對象也是很合理的。

        要創建一個對象,非常簡單,var obj= {}或者var obj=new Object()這樣就可以了,咋看這時候obj好像空空如也,但其實它有一個隱藏的屬性叫__proto__,有何用呢?它其實是一個對象來的,指向的是創建該對象的構造函數的prototype屬性。以var obj=new Object()爲例,obj有一個屬性叫__proto__,由於它的構造函數是Object這個函數,而函數會有一個屬性叫prototype,所以最終的結果就是  obj.__proto__ ===Object.prototype。另外一個例子如下:

var  add = function(p , q){

    return p + q;

};

var instance = new add();

alert(instance.__proto__ === add.prototype);//打印出true

        如果你接觸js比較久了,可能會聽說過原型鏈這個東西,而祕密就在於此,後邊會慢慢展開討論。

        現在我們再來看一個函數,拿我們上面的add函數來講,其實,每個函數創建出來之後,會有兩個隱藏的屬性,一個之前說過了,就是prototype,但同時函數也會有__proto__這個屬性的哦。那prototype和__proto__對於函數來講,又分別指向什麼呢?對於prototype屬性來說,它指向一個對象,而這個對象有一個constructor屬性,指向了函數自己,暈嗎?看下邊

var sayHello= function(){

    alert("hello");

};

alert(sayHello.prototype.constructor === sayHello);//打印出true

        所以想要以sayHello爲構造函數創建一個對象可以這樣 var ins = new sayHello.prototype.constructor(),嫌麻煩(我也是),一般我們都用var ins = new sayHello()來創建。而對於__proto__這個屬性,它跟對象的__proto__的作用是差不多的。在這裏,我們還要明白,我們上邊的sayHello函數是有另外一種寫法的  var sayHello = new Function("alert('hello')");那麼現在你知道函數的__proto__是什麼了嗎?它就是構造了sayHello這個函數的構造函數Function的prototype屬性,也就是sayHello.__proto__===Function.prototype。在這裏,我想你不難猜,還有Function.prototype.constructor === Function,所以想要創建一個函數還可以

var sayHello =new Function.prototype.constructor("alert('sayHello.')");。說了這麼多,會不會更暈了,畫個圖看看吧。

        這裏對上圖作一些說明:

        注意,通過var hello = new sayHello()方式創建出來的對象實例是沒有直接constructor屬性的,但是如果我們執行alert(hello.constructor);卻是有值的,這關係到了原型鏈,後邊再說明。

       看看上圖的三個注意點,先看1,Function.__proto__===Function.prototype,如果應用我上邊的規則,那麼則可以看出其實Function函數是由Function構造函數構造出來的,這有點自構造的味道,但從結果來看確實有這種效果。注意點2,Object.__proto__===Function.prototype,說明了Object()這個函數也是由Function()構造出來的。最後一點,Object.prototype.__proto__這個是比較特殊的,它的值默認爲null。

       好了,如果到了這裏,你對上面的東西還有些疑惑,我建議你再多看幾遍。下面正式講原型鏈相關的東西。

 

三.原型鏈(prototype)

        經過第二部分的閒聊,我想對於對象和函數一些內幕大家都有所瞭解了,那麼__proto__和prototype這些屬性,到底是用來作什麼的,有什麼指導意義呢?

        學過面向對象的朋友都知道在面向對象世界中,類有封裝,繼承和多態等主要特性,但在js中是沒有class的概念的,有的只是object,所以js中沒有顯示的這些特性,但原型特性使得我們也可以在js中做到面向對象的一些主要特性,如繼承。

         我們先從代碼切入:

var sayHello =function(){

    alert("hello");

};

var hello = new sayHello();

alert(hello.constructor);//非null

hello.constructor===sayHello.prototype.constructor;//true

        從第二部分的圖中我們知道,hello這個對象是沒有constructor這個屬性的,但如果執行alert(hello.constructor);卻發現它又是有值的。原來,在執行hello.constructor的過程當中,js運行時先查看hello這個對象有沒有constructor這個屬性,結果發現沒有,然後它就去hello.__proto__指向的sayHello函數的prototype去查看,結果發現它有constructor這個屬性,所以就返回了,結果就有了hello.constructor===sayHello.prototype.constructor。

        再看另外一個例子:

function Car(){  

};

function Bus(){

};

Car.prototype.name ="Car";

Bus.prototype = new Car();

var bus = new Bus();

//bus.run undefined

alert(bus.name);//”Car”

         首先查找bus的name屬性,結果找不到,去找bus的構造函數Bus.prototype對象,發現爲new Car()這個對象,它也沒有name屬性,結果再去找new Car()對象的構造函數Car的prototype對象,結果發現有name屬性了,所以打印出”Car”來。

        最後說明一個注意事項,原型鏈只有在查找屬性時纔會用到,在更新時是不會有影響的。如下所示:

function Animal(){

};

Animal.prototype.name = "animal";

var dog = new Animal();

alert(dog.name);//打印出"animal"

dog.name = "dog";

alert(Animal.prototype.name);//打印出"animal",不會受dog.name= "dog";影響

alert(dog.name)//打印出"dog"

delete dog.name;

alert(dog.name)//打印出"animal"

//因爲dog本身的name屬性刪除掉了,所以從原型鏈中找

 

        其實,js的原型鏈要說難也不是很難,但如果不夠全面地瞭解,你很容易就陷進去,並很難說出個所以然來,希望到這裏大家對原型鏈有一個更好的瞭解。


四.跟原型相關的一些東西

        1.  hasOwnProperty 判斷一個屬性是否爲一個對象的原有屬性(即不要從構造函數的prototype中去找)

        看如下代碼:

var sayHello = function(){};

sayHello.prototype.message = "hello world!";

var hello = new sayHello();

hello.name = "hello";

alert(hello.hasOwnProperty("message"));//false  message屬性是在原型鏈中才有的

alert(hello.hasOwnProperty("name"));//true  name屬性在hello對象本身中能找到

        2. isPrototypeOf 判斷一個對象是否在某個對象的原型鏈上(就好像判斷一個對象是否爲另外一個對象的父類一樣)

        看如下代碼:

function Book(){};

function Novel(){};

var book = new Book();

Novel.prototype = book;

var novel = new Novel();

book.isPrototypeOf(novel);//true    

 

        3.其它

        如果我們想對原有的js類進行一些擴展,比如給所有的String添加一個trim方法,則可以這樣寫

String.prototype.trim = function(){

//實現

}; 
那麼對於任何一個String ,如"test".trim();將是合法的。

 

五.結束語

        其實,js原型這個東西,我也是一步一步瞭解的,當你剛開始看的時候,也許會很暈,甚至會疑惑這個東西是不是真正的有用處,但當你代碼看得多了,多實踐,多思考,你會發現,原型這個東西真的是js的一大特色來的。好吧,大家加油吧。



發佈了40 篇原創文章 · 獲贊 174 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章