Constructor同学你到底是谁?

众所周知,Javascript里面有一系列非常“码农不友好”的东西:
constructor、prototype、原型链、匿名函数、闭包……
更可怕的是,这些东西竟然有时候还会混到一起!
今天先讲讲constructor这东西到底是什么

入门

我们经常会看到类似这样的构造器函数:

1
2
3
4
5
6
7
8
function a(){
this.value = 1;
}

var b = new a();

b.constructor === a; //true
b.value === 1; //true

a是一个构造器函数,所以b对象的constructor自然是a。


但是我们也可以这样写:

1
2
3
4
b={};
b.value = 1;

b.constructor === Object // true

这个时候b对象的构建是以“直接赋值”的方式,所以自然地,b此时的构造器变成了原生的Object。

简单易懂,每个人都很开心。


Constructor是如何被访问的

首先要提到JS的原型链这个概念,所谓的原型链,就是__proto__、prototype这些东西。

还是以构造器的方式来构造对象:

1
2
3
4
5
6
7
8
9
10
function a(){
this.value = 1;
}

a.prototype.proto_value = "proto_value"; //给构造器原型添加了一个属性

var b = new a();

console.log( b.proto_value ); //"proto_value",通过__proto__链访问到此属性
b.__proto__ === a.prototype //true

我们可以看出:

  • 任何对象都有一个隐藏的__proto__属性,可以直接访问到构造器的prototype。
  • 构造器的prototype也是一个对象。
  • 如果在对象中没有查找到相应的属性值,那么会沿着__proto__链一直向上查找,直到找到为止。

为何我要加粗第三句话呢?

因为要引入下面的结论:

1、大多数对象(除Function的prototype对象外)创建的时候是没有constructor这个属性的,所以当我们访问对象的constructor属性时,其实是沿着__proto__链在向上查找,直到找到constructor这个属性。

2、Function的prototype对象中,有constructor这个属性,是指向自己的。

什么意思呢?

我们还是用构造器a来生成b对象

1
2
3
4
5
function a(){
this.value = 1;
}

var b = new a();

这个时候你可以访问到 b.constructor

1
console.log(b.constructor);

但实际上是这样访问的:

1
2
3
console.log(b.__proto__.constructor);   //等价于 a.prototype.constructor

b.__proto__ === a.prototype //true

实际上发生的事情是:
b的属性中没有constructor,于是乎,沿着b的__proto__链,我们找到了a.prototype,里面找到了constructor这个属性,值为a本身。


一个小实验

所以我们可以做一下这个实验:

1
2
3
function a(){
this.value = 1;
}

然后把a.prototype直接赋值成一个对象

1
2
3
a.prototype = {
proto_value : "proto_value";
}

我们来看看b会怎么样

1
2
3
4
5
6

var b = new a();

console.log( b.proto_value ); //沿着proto链找到了"proto_value"

console.log( b.constructor ); // Object()

这里我们用一个对象直接覆盖了a.prototype,这个对象是由Object函数构造出来的,而且a.prototype里没有constructor这个属性了。

所以当我们访问b.constructor时,发现b.__proto__(即a.prototype)里没有constructor,于是再向上查找,我们找到了b.__proto__.__proto__(即Object.prototype),里面有一个constructor属性,指向Object(),于是返回了Object()