標籤:添加 它的 物件導向 pos 這一 beta 資料 二次 兩種方法
前兩次總結了JavaScript中的基礎資料型別 (Elementary Data Type)(實值型別<參考型別>,參考型別<複雜值>)以及他們在記憶體中的儲存,對記憶體空間有了一個簡單的瞭解,以及第二次總結了this深入淺出的用法,我們知道了this的用法取決於函數四種調用的方式。
這一次我們來對JavaScript中原型以及原型鏈做一個深入淺出的理解。
JavaScript深入淺出系列
1)複雜值vs原始值&&記憶體空間 - JavaScript深入淺出(一)
2)this的用法 – JavaScript深入淺出(二)
3)函數原型屬性-JavaScript深入淺出(三)
待續......關於原型的話題都會變得嚴肅。
實際上,原型只是一個被稱為"原型"的Null 物件屬性,它是由JavaScript在後台建立(當然我們知道了它的原理,可以手動完成這項工作);
當你建立一個函數時,這個函數都會有一個prototype屬性(不管你是不把它當做一個建構函式使用)。
βヾ(,,?∇?,,川←那麼我們具體來看一下吧!!!
原型鏈概要
prototype屬性是JavaScript為每個Function執行個體建立的一個對象。
具體的說:"它將通過new關鍵字建立的<對象執行個體>連結回建立它們的<建構函式>" 。就這樣,我們可以共用或繼承通用的方法和屬性。當我們在屬性尋找時,就會不自覺的開啟了我們的原型鏈之旅
讓我們通過一個簡單的例子開啟我們的原型鏈查詢之旅:我們使用Array建構函式建立一個數組,然後調用join方法
我想上面的例子對於js入門者是非常簡單的,那麼但是我們再來仔細瞭解一下,你發現join方法並沒有定義為myArray對象執行個體的屬性,但是我們建立的數組卻可以訪問join()方法,就好像我們本來就可以訪問似的。
join()是在哪個地方定義的呢?
事實上,我們經常使用的join(),slice(),push()...等這些內建的方法,都被定義為了Array()建構函式的prototype屬性的屬性。由於在我們建立的myArray數組中沒有找到join(),因此JavaScript會在原型鏈中尋找join()方法;
其實這樣做我們很容易就聯想到了效率和重用,通過把該屬性添加到原型中去,我們所有的數組都有充分利用了相同的join()函數,而不需要為每一個數組執行個體都建立函數的新執行個體。
原型在所有的function()執行個體上都是標準的
我們知道建立函數兩種方法
1、調用Function建構函式法:
2、使用字面量法:
其實,即使不直接使用Function建構函式,而是使用字面量標記法,所有的函數也都是由Function()建構函式建立的
我們用字面量方法建立了一個函數,發現它的prototype和Function()建構函式一樣,都指向了object(),這也就證實了我們上所說的.
預設的prototype屬性是object()對象
上面我已經談到,實際上,原型只是一個被稱為‘原型‘的Null 物件屬性,它在JavaScript的後台已經建立,並且通過Function()建構函式來使用。
我們可以手動完成這項在後面完成的工作,以便瞭解它的機制。
上面的代碼非常簡單,實際上也非常好用,它實質上複製了JavaScript在後面已經完成的工作。
將建構函式建立的執行個體連結至建構函式的prototype屬性
將建構函式所建立的執行個體連結至建構函式的prototype屬性,讓我們開始這條神秘的_proto_連結
上面我們所原型只是一個對象,但是它是特殊的,因為原型鏈將每個執行個體都連結至其建構函式的prototype屬性。
建立的對象執行個體和建立對象的建構函式的prototype屬性
當然,我們除了使用_proto_連結,還可以使用建構函式屬性:
事實上,_proto_ === constructor.prototype
這樣我們就不難理解,下面可以達到同樣的效果:
上面的例子中我寫到直接使用鏈也是可以的,下面會介紹它的查詢順序。雖然我相信對於入門者都是使用的鏈查詢,但是我們有必然要知道它背後的那些機制。
其實有只看不見的手,在協助著我們的程式碼完成任務
原型鏈的最後是Object.prototype
那麼就讓我們來看一下它的原型鏈查詢吧。
由於prototype屬性是一個對象,因此原型鏈或查詢的最後一站是Object.prototype。
我想上面的代碼,對於我們來說是絲毫不費力氣的,但就借這個簡單的例子,最後一個簡單的undefined結果,卻經曆了一段不為我們所見的原型鏈查詢;
我們建立了一個myArray空數組,然後我們試圖訪問未定義的myArray屬性時,並不會直接返回undefined,而是要經曆一段原型鏈查詢。
①在myArray對象中尋找foo屬性;
如果沒有找到
②則在Array.prototype中尋找該屬性;
但它在哪裡也沒有定義,
③最後尋找的地方就是Object.prototype
三個對象中都沒有定義,最後才給我們了一個undefined的回饋。所以請好好對待你的undefined吧,因為它的出現一波三折,還真不容易啊。。哈哈
用新對象替換prototype屬性會刪除預設的建構函式屬性
我們可以用一個新值來替換prototype屬性的預設值,但是需要特別注意的是:這麼做會刪除在"預製"原型對象中找到的預設的constructor屬性,除非我們手動指定一個 ;
所以當你想要替換JavaScript設定的預設的prototype屬性(與一些js oop模式類似),應該重新串連引用該建構函式的建構函式屬性。
下面我們簡單的改一下上面的代碼,以便建構函式屬效能夠再次為適當的建構函式提供引用
繼承原型屬性的執行個體總是能夠獲得最新值
其實prototype是動態繼承原型的屬性的執行個體總是能夠獲得最新值,
這一點比較簡單,不管是使用原型對象還是自己的對象覆蓋它,繼承原型屬性的執行個體總是能夠獲得新值。
但是我們需要注意下面的一點:
丨
丨
丨
用新對象替換prototype屬性不會更新以前的執行個體
當你想用一個新對象完全替換prototype屬性時,覺得所有的執行個體都會被更新,那麼就即將要走向一條尋錯的道路,可能會得到意想不到的結果。
建立一個執行個體時,該執行個體將在執行個體化時綁定至"剛完成"的原型,提供一個新對象作為prototype屬性不會更新已建立的執行個體和原型之間的串連
這裡的重點是,一旦開始建立執行個體,就不應用一個新對象那個來替換對象的原型,這樣將會導致執行個體有一個指向不同原型的連結
自訂建構函式實現原型繼承
當我們在自訂建構函式時,同樣可以實現原型繼承:
上面我們寫的例子,很好的利用原型鏈,來建立一個建構函式。如果我們不提供參數的話,建構函式則可以繼承legs和arms屬性。如果傳入參數,就遮蓋繼承的屬性
建立繼承鏈
我們自訂的建構函式實現了原型繼承,設計原型繼承的目的是要在傳統的物件導向程式設計語言中找到模仿繼承模式的繼承鏈。繼承只是一個對象可以訪問另一個對象的屬性。
接下來我們來建立一個簡單的繼承鏈:
事實上,上述代碼我做的僅僅是利用一個已有的原生對象。
Person()和prototype屬性的預設的object()值沒有什麼不同,這也正是一個prototype屬性包含預設空object()值所發生的事情,尋找用於建立對象的建構函式的原型(即object.prototype),以便尋找所繼承的屬性。
::我們
為什麼要關注prototype屬性呢?
(希望下面可以給<JavaScript入門者>一個瞭解prototype的理由)
你可能不喜歡原型繼承,而是更多的喜歡採用另一種模式的對象繼承。但是:
①原生建構函式(如Ocject(),Array(),Function()...)都使用了prototype屬性,以便讓你的執行個體可以繼承屬性和方法。
②如果想要更好的理解JavaScript,我們需要瞭解JavaScript本身是如何使用prototype對象的
③當你自訂一個建構函式時,可以像JavaScript原生對象那樣使用繼承,就必須要知道他是如何工作的
④通過使用原型繼承,我們可以建立有效對象執行個體。因為並非所有的數組對象都需要他們自己的join()方法<我想這就需要我們做些工作了>,但所有的執行個體都可以利用相同的join()方法,這就提高了效率和重用性。
寫在後面
到這裡我們的函數原型屬性的深入淺出系列已經介紹完畢了,這篇博文希望可以協助初學者--記住原型鏈階層的工作原理、對於易混淆的原型繼承屬性有一個分類,解決初學者心中的原型困惑
喜歡的話,關注一下吧,前輩們的關注和支援就是給我最大的動力
函數原型屬性-JavaScript深入淺出(三)