一次搞懂原型修改与重写

2,455 阅读3分钟

前言:

本文主要探讨重写原型时,未指定 constructor 而产生的 constructor指向问题,以及如何避免此类问题(1.修改原型而不要重写原型. 2.指定 constructor

零.原型与原型链

在阅读下文前,请自行了解原型与原型链的相关知识(附图可参考)。

prototype 是函数对象特有的属性,当定义一个函数时,JavaScript 会为这个函数创建一个 prototypeconstructor 是对象特有的属性,指向创建它的函数。

可以看图中的最右侧一栏:Foo.prototypeconstructor 指向了 function Foo()Object.prototypeconstructor 指向了 function Object()Function.prototypeconstructor 指向了 function Function()

image.png

一. 原型修改

先看这个例子

function Person(name) {
    this.name = name
}

var p = new Person('River')

// 修改原型
Person.prototype.getName = function(){
    console.log('你好,我是',this.name)
}

执行p.getName(),控制台打印:

image.png

此时,p__proto__ 指向其构造函数的原型 Person.prototype,没问题。

console.log(p.__proto__ === Person.prototype) // true

pconstructorPerson

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(),控制台打印:

image.png

此时,p__proto__ 依旧指向 Personprototype,但 pconstructor 却指向 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 的。

image.pngimage.png

和我们上文中所说的一样,此时的 Person.prototype 上查找不到 constructor,会沿着原型链继续向上查找,找到 Object.prototypeconstructor,也就是 Obejct

所以p.constructor === Object

注意 重写原型,改的是 Person.prototype,而 Person 的隐藏属性 [[prototype]] 从始至终都没有变过,其始终指向 Function.prototype,其 constructor 也始终为 Function

重写原型改变的是构造函数 Personprototype,使它的 constructor 缺失,从而沿着原型链向上查找。

PersonPerson.prototype
__proto__指向 Function.prototype指向 Object.prootype
constructorFunctionObject

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(),控制台打印:

image.png

可以看到,p__proto__ 指向的是重写前的 Person.prototype,是调用不到重写后的 getName

image.png

自然,constructor 也不会改变

image.png

避免 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')

image.png

三. 总结

在重写原型时,会产生 constructor 丢失的现象,使新建的 实例.constructor === Object。我们在重写原型链的时候应当注意到这个问题,可以通过手动给 constructor 赋值来重定指向。