[TOC]
# 揭開 constructor
> 在 Javascript 語言中,`constructor` 屬性是專門為 function 而設計的,它存在于每一個 function 的`prototype` 屬性中。這個 `constructor` 保存了指向 function 的一個引用。
> 在定義一個函數(代碼如下所示)時,
~~~
function F() {
// some code
}
~~~
JavaScript 內部會執行如下幾個動作:
> 1. 為該函數添加一個原型(`prototype`)屬性
> 2. 為 `prototype` 對象額外添加一個 `constructor` 屬性,并且該屬性保存指向函數 F 的一個引用
這樣當我們把函數 F 作為自定義構造函數來創建對象的時候,對象實例內部會自動保存一個指向其構造函數(這里就是我們的自定義構造函數 F)的 `prototype` 對象的一個屬性`__proto__`,
所以我們在每一個對象實例中就可以訪問構造函數的 `prototype` 所有擁有的全部屬性和方法,就好像它們是實例自己的一樣。當然該實例也有一個 `constructor`屬性了(從 `prototype` 那里獲得的),每一個對象實例都可以通過 `constrcutor` 對象訪問它的構造函數,請看下面代碼:
~~~
var f = new F();
alert(f.constructor === F);// output true
alert(f.constructor === F.prototype.constructor);// output true
~~~
我們可以利用這個特性來完成下面的事情:
對象類型判斷,如:
~~~
if(f.constructor === F) {
// do sth with F
}
~~~
其實 `constructor` 的出現原本就是用來進行對象類型判斷的,但是**constructor 屬性易變,不可信賴**。我們有一種更加安全可靠的判定方法:`instanceof` 操作符。下面代碼,仍然返回 `true`:
~~~js
if(f instanceof F) {
// do sth with F
}
~~~
原型鏈繼承,由于 `constructor` 存在于 `prototype` 對象上,因此我們可以結合`constructor`沿著原型鏈找到最原始的構造函數,如下面代碼:
~~~js
function Base() {}
// Sub1 inherited from Base through prototype chain
function Sub1(){}
Sub1.prototype = new Base();
Sub1.prototype.constructor = Sub1;
Sub1.superclass = Base.prototype;
// Sub2 inherited from Sub1 through prototype chain
function Sub2(){}
Sub2.prototype = new Sub1();
Sub2.prototype.constructor = Sub2;
Sub2.superclass = Sub1.prototype;
// Test prototype chain
alert(Sub2.prototype.constructor);// function Sub2(){}
alert(Sub2.superclass.constructor);// function Sub1(){}
alert(Sub2.superclass.constructor.superclass.constructor);// function Base(){}
~~~
上面的例子只是為了說明`constructor`在原型鏈中的作用,更實際一點的意義在于:**一個子類對象可以獲得其父類的所有屬性和方法,稱之為繼承。**
之前提到`constructor 易變`,那是因為函數的 `prototype` 屬性容易被更改,我們用時下很流行的編碼方式來說明問題,請看下面的示例代碼:
~~~
function F() {}
F.prototype = {
_name: 'Eric',
getName: function() {
return this._name;
}
};
// 初看這種方式并無問題,但是你會發現下面的代碼失效了
var f = new F();
console.log(f.constructor === F); // output false
~~~
怎么回事?F 不是實例對象 f 的構造函數了嗎?當然是!只不過構造函數 F 的原型被開發者重寫了,這種方式將原有的 `prototype` 對象用一個對象的字面量`{}`來代替。而新建的對象`{}`只是 Object 的一個實例,系統(或者說瀏覽器)在解析的時候并不會在`{}`上自動添加一個 `constructor` 屬性,因為這是 `function` 創建時的專屬操作,僅當你聲明函數的時候解析器才會做此動作。然而你會發現 `constructor` 是存在的,可以 測試它的存在性:
~~~
alert(typeof f.constructor == 'undefined');// output false
~~~
既然存在,那這個 `constructor` 是從哪兒冒出來的呢?
因為`{}`是創建對象的一種簡寫,所以`{}`相當于是 `new Object()`。
那既然`{}`是 `Object` 的實例,自然而然他獲得一個指向構造函數 `Object()`的 `prototype` 屬性的一個引用`__proto__`,又因為 `Object.prototype` 上有一個指向 `Object` 本身的 `constructor`屬性。所以可以看出這個`constructor`其實就是`Object.prototype`的`constructor`, 下面代碼可以驗證其結論:
```js
console.log(f.constructor === Object.prototype.constructor);//output true
console.log(f.constructor === Object);// also output true
```
一個解決辦法就是手動恢復它的 `constructor`,下面代碼非常好地解決了這個問題:
```js
function F() {}
F.prototype = {
constructor: F, /* reset constructor */
_name: 'Eric',
getName: function() {
return this._name;
}
};
```
之后,`constructor` 重新獲得的構造函數的引用,測試上面的代碼
```js
var f = new F();
alert(f.constructor === F); // output true this time ^^
```
# **解惑:構造函數上怎么還有 constructor ?是哪兒來的?**
細心的會發現,像 JavaScript 內建的構造函數,如 Array, RegExp, String,Number, Object, Function 等等居然自己也有一個 constructor:
```js
alert(typeof Array.constructor != 'undefined');// output true
```
經過測試發現,此物非彼物它和 `prototype` 上 `constructor` 不是同一個對象,他們是共存的:
```js
alert(typeof Array.constructor != 'undefined');// output true
alert(typeof Array.prototype.constructor === Array); // output true
```
不過這件事情也是好理解的,因為 構造函數也是函數。說明它就是 Function 構造函數的實例對象,自然它內部也有一個指向 `Function.prototype` 的內部引用`__proto__`啦。因此我們很容易得出結論,這個 `constructor`(構造函數上的`constructor` 不是 `prototype` 上的)其實就是**Function 構造函數的引用**:
```js
console.log(Array.constructor === Function);// output true
console.log(Function.constructor === Function); // output true
```
# 問?
## 任何對象都有`constructor`屬性,不僅僅存在于function 的prototype 屬性中?
* **回復:** 你要知道其他對象的`constructor`屬性是從哪里來的,就比如
```js
var a = {};
a.constructor === Object`
```
這里面 `a` 的 `constructor` 屬性是從哪里來的,是 js 默認添加的嗎,顯然不是,`a = {}`可以看成是通過`a = new Object()`,實例化之后`a`會有一個`__proto__`屬性,它指向它的構造函數的`prototype`,`a`本身是沒有 `constructor` 這個屬性的,它就會去它的構造函數的`prototype`中去找,而 `Object` 的 `prototype` 中有`constructor`這個屬性,且指向本身,所有你才可以看到`a.constructor === Object`
## **為什么要設置prototype.constructor?**
每次實現JS的繼承時,代碼里必有如下兩句:
~~~javascript
// 用于設置原型
Employee.prototype = new Person()
// 設置原型的構造器
Employee.prototype.constructor=Employee
~~~
實現原型鏈就靠第一句話,但第二句話有什么用呢?
* **回復:** 即使不做這樣的修正也不會有什么影響,它**主要防止一種情況下出錯,就是你顯式地去使用構造函數**。
比如,我并不知道`woman`是由哪個函數實例化出來的,但是我想 clone 一個,這時就可以這樣:
~~~
var woman = new Woman();
...
var woman1 = woman.constructor();
~~~
# 參考
[為什么要做A.prototype.constructor=A這樣的修正?](https://www.cnblogs.com/SheilaSun/p/4397918.html)
http://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor
- 步入JavaScript的世界
- 二進制運算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的產生與發展
- DOM事件處理
- js的并行加載與順序執行
- 正則表達式
- 當遇上this時
- Javascript中apply、call、bind
- JavaScript的編譯過程與運行機制
- 執行上下文(Execution Context)
- javascript 作用域
- 分組中的函數表達式
- JS之constructor屬性
- Javascript 按位取反運算符 (~)
- EvenLoop 事件循環
- 異步編程
- JavaScript的九個思維導圖
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得關注的庫===
- ==文章==
- JavaScript框架
- Angular 1.x
- 啟動引導過程
- $scope作用域
- $q與promise
- ngRoute 和 ui-router
- 雙向數據綁定
- 規范和性能優化
- 自定義指令
- Angular 事件
- lodash
- Test