Appearance
前言
前端三大基石:原型、作用域、闭包。可谓是你在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
的两个实例 person1
和 person2
都只有一个内部属性指回 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
注意:
- _proto_是所有对象(包括函数)都有的,它才叫做对象的原型,原型链就是靠它形成的。
- prototype只有函数(准确地说是构造函数)才有的。它跟原型链没有关系。它的作用是:构造函数Object.create对象的时候,告诉构造函数新创建的对象的原型是谁。
用一张图来总结:
由图可知:B.prototype.constructor
将会沿着Object.create(A.prototype)
的原型链向下查找constructor
,Object.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修复的一个原因了。
总结
出于代码习惯,最好加上
参考文章:
- JavaScript 中对象的 constructor 属性的作用是什么?
- 红宝书(JavaScript高级程序设计)
拓展: