前言:
本文主要探讨重写原型时,未指定
constructor而产生的constructor指向问题,以及如何避免此类问题(1.修改原型而不要重写原型. 2.指定constructor)
零.原型与原型链
在阅读下文前,请自行了解原型与原型链的相关知识(附图可参考)。
prototype 是函数对象特有的属性,当定义一个函数时,JavaScript 会为这个函数创建一个 prototype。constructor 是对象特有的属性,指向创建它的函数。
可以看图中的最右侧一栏:Foo.prototype 的 constructor 指向了 function Foo(),Object.prototype 的 constructor 指向了 function Object(),Function.prototype 的 constructor 指向了 function Function()。
一. 原型修改
先看这个例子
function Person(name) {
this.name = name
}
var p = new Person('River')
// 修改原型
Person.prototype.getName = function(){
console.log('你好,我是',this.name)
}
执行p.getName(),控制台打印:
此时,p 的 __proto__ 指向其构造函数的原型 Person.prototype,没问题。
console.log(p.__proto__ === Person.prototype) // true
p 的 constructor 是 Person。
console.log(p.constructor === Person) // true
这里需要解释一下,当我们访问一个对象的属性时,会沿着原型链一级一级向上查找。实例 p 本身没有 constructor 属性,但是它的原型 Person.prototype 是有 constructor 并且指向 Person 的。即:
p.__proto__ === Person.prototype
Person.prototype.constructor === Person
二. 原型重写
原型重写,结果可能和我们预想的不一样
值得注意的 constructor 属性
1. 例子1
function Person(name) {
this.name = name
}
// 原型重写
Person.prototype = {
getName: function() {
console.log('你好,我是',this.name)
}
}
var p = new Person('River')
执行p.getName(),控制台打印:
此时,p 的 __proto__ 依旧指向 Person 的 prototype,但 p 的 constructor 却指向 Object。
console.log(p.constructor === Object) // true
console.log(p.constructor === Person) // false
console.log(p.__proto__ === Object.prototype) // false
console.log(p.__proto__ === Person.prototype) // true
2. 为什么?
注意,在我们重写原型 prototype 的时候,使用对象字面量的方式创建了一个新的对象,这个新的 prototype 相当于一个 Object 的实例,是没有 constructor 的。
和我们上文中所说的一样,此时的 Person.prototype 上查找不到 constructor,会沿着原型链继续向上查找,找到 Object.prototype 的 constructor,也就是 Obejct。
所以p.constructor === Object
注意
重写原型,改的是 Person.prototype,而 Person 的隐藏属性 [[prototype]] 从始至终都没有变过,其始终指向 Function.prototype,其 constructor 也始终为 Function。
重写原型改变的是构造函数 Person 的 prototype,使它的 constructor 缺失,从而沿着原型链向上查找。
Person | Person.prototype | |
|---|---|---|
__proto__ | 指向 Function.prototype | 指向 Object.prootype |
constructor | Function | Object |
3. 例子2:实例在前,重写在后
为了更加彻底的了解重写原型,我们再来举一个例子,先声明实例 p,再重写 Person:
function Person(name) {
this.name = name
}
var p = new Person('River')
Person.prototype = {
getName: function() {
console.log('你好,我是',this.name)
}
}
执行 p.getName(),控制台打印:
可以看到,p 的 __proto__ 指向的是重写前的 Person.prototype,是调用不到重写后的 getName 的
自然,constructor 也不会改变
避免 constructor 的指向问题
1. 修改 prototype,而不是重写
2. 自己给 constructor 赋值
还是拿上面的例子来说,可以在重写的时候手动给 constructor 赋值,重定指向
function Person(name) {
this.name = name
}
Person.prototype = {
constructor: Person,
getName: function() {
console.log('你好,我是',this.name)
}
}
var p = new Person('River')
三. 总结
在重写原型时,会产生 constructor 丢失的现象,使新建的 实例.constructor === Object。我们在重写原型链的时候应当注意到这个问题,可以通过手动给 constructor 赋值来重定指向。