強大的原型和原型鏈

前兩次總結了JavaScript中的基本數據類型(值類型<引用類型>,引用類型<複雜值>)以及他們在內存中的存儲,對內存空間有了一個簡單的瞭解,以及第二次總結了this深刻淺出的用法,咱們知道了this的用法取決於函數四種調用的方式。html

這一次咱們來對JavaScript中原型以及原型鏈作一個深刻淺出的理解。編程

JavaScript深刻淺出系列

1)複雜值vs原始值&&內存空間 - JavaScript深刻淺出(一)數組

2)this的用法 – JavaScript深刻淺出(二)編程語言

待續......關於原型的話題都會變得嚴肅。函數

實際上,原型只是一個被稱爲"原型"的空對象屬性,它是由JavaScript在後臺建立(固然咱們知道了它的原理,能夠手動完成這項工做);oop

當你建立一個函數時,這個函數都會有一個prototype屬性(無論你是不把它當作一個構造函數使用)。post

βヾ(,,・∇・,,川←那麼咱們具體來看一下吧!!!性能

原型鏈概要

prototype屬性是JavaScript爲每一個Function實例建立的一個對象。this

具體的說:"它將經過new關鍵字建立的<對象實例>連接回建立它們的<構造函數>" 。就這樣,咱們能夠共享或繼承通用的方法和屬性。當咱們在屬性查找時,就會不自覺的開啓了咱們的原型鏈之旅url

讓咱們經過一個簡單的例子開啓咱們的原型鏈查詢之旅:咱們使用Array構造函數建立一個數組,而後調用join方法

我想上面的例子對於js入門者是很是簡單的,那麼可是咱們再來仔細瞭解一下,你發現join方法並無定義爲myArray對象實例的屬性,可是咱們建立的數組卻能夠訪問join()方法,就好像咱們原本就能夠訪問似的。

join()是在哪一個地方定義的呢?

事實上,咱們常用的join(),slice(),push()...等這些內建的方法,都被定義爲了Array()構造函數的prototype屬性的屬性。因爲在咱們建立的myArray數組中沒有找到join(),所以JavaScript會在原型鏈中查找join()方法

其實這樣作咱們很容易就聯想到了效率和重用,經過把該屬性添加到原型中去,咱們全部的數組都有充分利用了相同的join()函數,而不須要爲每個數組實例都建立函數的新實例。

原型在全部的function()實例上都是標準的

咱們知道建立函數兩種方法

一、調用Function構造函數法:

二、使用字面量法:

其實,即便不直接使用Function構造函數,而是使用字面量表示法,全部的函數也都是由Function()構造函數建立的

咱們用字面量方法建立了一個函數,發現它的prototype和Function()構造函數同樣,都指向了object(),這也就證明了咱們上所說的.

 默認的prototype屬性是object()對象

上面我已經談到,實際上,原型只是一個被稱爲'原型'的空對象屬性,它在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()方法,這就提升了效率和重用性。

 寫在後面

到這裏咱們的函數原型屬性的深刻淺出系列已經介紹完畢了,這篇博文但願能夠幫助初學者--記住原型鏈層次結構的工做原理、對於易混淆的原型繼承屬性有一個分類,解決初學者心中的原型困惑

 

喜歡的話,關注一下吧,你的關注和支持就是給我最大的動力

相關文章
相關標籤/搜索