[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