天天看點

關于JS中的constructor與prototype的總結

prototype 屬性使您有能力向對象添加屬性和方法

constructor 屬性傳回對建立此對象的數組函數的引用

在js中有一個function的東西。一般人們叫它函數

在javascript語言中,constructor屬性是專門為function而設計的,它存在于每一個function的prototype屬性中。這個constructor儲存了指向function的一個引用

javascript内部會執行如下幾個動作:

為該函數添加一個原形屬性(即prototype對象).

為prototype對象額外添加一個constructor屬性,并且該屬性儲存指向函數f的一個引用

這樣當我們把函數f作為自定義構造函數來建立對象的時候,對象執行個體内部會自動儲存一個指向其構造函數(這裡就是我們的自定義構造函數f)的prototype對象的一個屬性__proto__,是以我們在每一個對象執行個體中就可以通路構造函數的prototype所有擁有的全部屬性和方法,就好像它們是執行個體自己的一樣

當然該執行個體也有一個constructor屬性了(從prototype那裡獲得的),這時候constructor的作用就很明顯了,因為這時每一個對象執行個體都可以通過constrcutor對象通路它的構造函數,請看下面代碼:

我們可以利用這個特性來完成下面的事情: 對象類型判斷

其實constructor的出現原本就是用來進行對象類型判斷的,但是constructor屬性易變,不可信賴。

我們有一種更加安全可靠的判定方法:instanceof 操作符。

下面代碼仍然傳回true

原型鍊繼承,由于constructor存在于prototype對象上,是以我們可以結合constructor沿着原型鍊找到最原始的構造函數,如下面代碼:

按照javascript的說法,function定義的這個person就是一個object(對象),而且還是一個很特殊的對象,這個使用function定義的對象與使用new操作符生成的對象之間有一個重要的差別:function定義的對象有一個prototype屬性,使用new生成的對象就沒有這個prototype屬性

上面的例子可以看出函數的prototype 屬性又指向了一個對象,這個對象就是prototype對象

a.prototype 包含了2個屬性,一個是constructor ,另外一個是__proto__。這個constructor 就是我們的構造函數a,那麼__proto__ 是什麼呢?

這個就涉及到了原型鍊的概念:每個對象都會在其内部初始化一個屬性,就是__proto__,當我們通路一個對象的屬性時,如果這個對象内部不存在這個屬性,那麼他就會去__proto__裡找這個屬性,這個__proto__又會有自己的__proto__,于是就這樣 一直找下去

在這裡我們來分析出new 運算符做了這些事情

我們将這個例子改造一些,變得複雜一點

我們來分析下這個過程:

由 <code>var t = new obj('test');</code> 我們可以得到 <code>t.__proto__ = obj.prototype</code>,但是上面指定<code>obj.prototype =new a('test');</code> 可以這樣來看下

那麼<code>obj.prototype.__proto__ = a.prototype</code>,由 <code>t.__proto__ = obj.prototype</code> 可以得出 <code>t.__proto__.__proto__ = a.prototype</code>,是以對象t先去找本身的prototype 是否有test函數,發現沒有,結果再往上級找,即 <code>t.__proto__</code> ,亦即<code>obj.prototype</code> 尋找test函數 ,但是<code>obj.prototype</code> 也沒有這個函數,然後再往上找。即<code>t.__proto__.__proto__</code> 找,由于<code>t.__proto__.__proto__ = a.prototype</code> 在 <code>a.prototype</code> 中找到了這個方法,輸出了alert('test')

從這裡可以分析得出一個結論,js中原形鍊的本質在于 proto

根據上面講到的__proto__ 我們來分析下,首先obj是沒有constructor 這個屬性的,但是 obj.__proto__ = a.prototype;就從

a.prototype中尋找,而 a.prototype.constructor 是就a,所有兩者的結果是一一樣的

接着看繼承是如何實作的

更實際點的意義在于:一個子類對象可以獲得其父類的所有屬性和方法,稱之為繼承。繼承的實作很簡單,隻需要把子類的prototype設定為父類的一個對象即可。注意這裡說的可是對象哦

之前提到constructor易變,那是因為函數的prototype屬性容易被更改,我們用時下很流行的編碼方式來說明問題:

初看這種方式并無問題,但是你會發現下面的代碼失效了:

怎麼回事?f不是執行個體對象f的構造函數了嗎?

當然是!隻不過構造函數f的原型被開發者重寫了,這種方式将原有的prototype對象用一個對象的字面量{}來代替,而建立的對象{}隻是object的一個執行個體,系統(或者說浏覽器)在解析的時候并不會在{}上自動添加一個constructor屬性,因為這是function建立時的專屬操作,僅當你聲明函數的時候解析器才會做此動作。

然而你會發現constructor并不是不存在的,下面代碼可以測試它的存在性:

既然存在,那這個constructor是從哪兒冒出來的呢?

我們要回頭分析這個對象字面量{}。

因為{}是建立對象的一種簡寫,是以{}相當于是new object()。

那既然{}是object的執行個體,自然而然他獲得一個指向構造函數object()的prototype屬性的一個引用__proto__,又因為object.prototype上有一個指向object本身的constructor屬性。是以可以看出這個constructor其實就是object.prototype的constructor,下面代碼可以驗證其結論:

alert(f.constructor === object.prototype.constructor);//output true

alert(f.constructor === object);// also output true

一個解決辦法就是手動恢複他的constructor,下面代碼非常好地解決了這個問題:

之後一切恢複正常,constructor重新獲得構造函數的引用,我們可以再一次測試上面的代碼,這次傳回true

解惑:構造函數上怎麼還有一個constructor?它又是哪兒來的?

像javascript内建的構造函數,如array, regexp, string, number, object, function等等自己也有一個constructor:

經過測試發現,此物非彼物它和prototype上constructor不是同一個對象,他們是共存的:

不過這件事情也是好了解的,因為構造函數也是函數。是函數說明它就是function構造函數的執行個體對象,自然他内部也有一個指向function.prototype的内部引用__proto__。是以我們很容易得出結論,這個constructor(構造函數上的constructor不是prototype上的)其實就是function構造函數的引用:

繼續閱讀