Skip to content

前言

前端三大基石:原型、作用域、闭包。可谓是你在js路上的必须要了解清楚地事情,无论是面试还是平时代码开发过程中,无处不在。

这次的灵感主要是由有位伙伴发了个这篇文章js constructor问题?,问了我一个问题跟楼主一样的问题?我看了下面评论大佬的解释,感觉还是有点模糊。

自己还是琢磨琢磨,把本次学习梳理一下。

简单例子

js
function A() {
    this.name = "周星星"
}

function B() {
    this.age = 20
    A.call(this)
}

B.prototype = Object.create(A.prototype);

const bb = new B()

console.log(bb);
console.log(bb.constructor);
function A() {
    this.name = "周星星"
}

function B() {
    this.age = 20
    A.call(this)
}

B.prototype = Object.create(A.prototype);

const bb = new B()

console.log(bb);
console.log(bb.constructor);

打印结果:

由上面可以得知,打印bb.constructor变成了function A。这是为什么呢,按道理来说应该是指向function B

首先来看一张图(来自红宝书):

图中展示了 Person 构造函数、Person 的原型对象和 Person 现有两个实例之间的关系。注意,Person.prototype 指向原型对象,而 Person.prototype.contructor 指回 Person 构造函数。原型对象包含 constructor 属性和其他后来添加的属性。Person 的两个实例 person1person2 都只有一个内部属性指回 Person.prototype,而且两者都与构造函数没有直接联系。

再来看看这行代码:

js
B.prototype = Object.create(A.prototype);
B.prototype = Object.create(A.prototype);

本来这里想用 new 操作符来创建对象的的,纯属因为卷的原因,我就用了ES6创建对象的另一种方式:Object.create()

来看一下MDN对Object.create()的解释:

咋一看,跟new操作符差不多呀

也就是说(Object.create(A.prototype)).__proto__ === A.prototype

注意:

  1. _proto_是所有对象(包括函数)都有的,它才叫做对象的原型,原型链就是靠它形成的。
  2. prototype只有函数(准确地说是构造函数)才有的。它跟原型链没有关系。它的作用是:构造函数Object.create对象的时候,告诉构造函数新创建的对象的原型是谁。

用一张图来总结:

image.png

由图可知:B.prototype.constructor将会沿着Object.create(A.prototype)的原型链向下查找constructorObject.create(A.prototype)没有constructor就去它的__proto__找,因为(Object.create(A.prototype)).__proto__ === A.prototype,而A.prototype.constructor == function A(),所以 B.prototype.constructor == A.prototype.constructor(即function A())

所以,当我们var bb = new A() 时 ,bb.constructor = A.prototype.constructor,bb.constructor指向function A()。

添加 B.prototype.constructor = B

js
function A() {
    this.name = "周星星"
}

function B() {
    this.age = 20
    A.call(this)
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor  = B

const bb = new B()

console.log(bb);
console.log(bb.constructor);
function A() {
    this.name = "周星星"
}

function B() {
    this.age = 20
    A.call(this)
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor  = B

const bb = new B()

console.log(bb);
console.log(bb.constructor);

打印结果:

显而易见,bb.constructor = B.prototype.constructor = function B(); 那么我们为什么要加上B.prototype.constructor = B呢?

答案其实也没那么重要:

constructor属性不影响任何JavaScript的内部属性。constructor其实没有什么用处,只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。

目前看到的作用之一,通过实例的构造函数给闭包中的函数增加属性方法。

js
var a,b;
(function(){
  function A (arg1,arg2) {
    this.a = 1;
    this.b=2; 
  }

  A.prototype.log = function () {
    console.log(this.a);
  }
  a = new A();
  b = new A();
})()
a.log();
// 1
b.log();
// 1
var a,b;
(function(){
  function A (arg1,arg2) {
    this.a = 1;
    this.b=2; 
  }

  A.prototype.log = function () {
    console.log(this.a);
  }
  a = new A();
  b = new A();
})()
a.log();
// 1
b.log();
// 1

通过以上代码我们可以得到两个对象,a,b,他们同为类A的实例。因为A在闭包里,所以现在我们是不能直接访问A的,那如果我想给类A增加新方法怎么办?

js
// a.constructor.prototype 在chrome,firefox中可以通过 a.__proto__ 直接访问
a.constructor.prototype.log2 = function () {
  console.log(this.b)
}

a.log2();
// 2
b.log2();
// 2
// a.constructor.prototype 在chrome,firefox中可以通过 a.__proto__ 直接访问
a.constructor.prototype.log2 = function () {
  console.log(this.b)
}

a.log2();
// 2
b.log2();
// 2

通过访问constructor就可以了。

这位老哥的解释也就是上面的代码的一个应用了。

不过你拿到 prototype 也不见得能拿到 正确的 constructor,JS没有提供方式确保你拿到“正确”constructor。我们只能根据惯例相信对象上的 constructor 应该是“正确”的 constructor。

这也是我们上面做constructor修复的一个原因了。

总结

出于代码习惯,最好加上

参考文章:

拓展: