Javascript 踩坑记——继承和原型链
前言
¶和其它面向对象的语言不同——继承只存在于两个不同的类之间——,Javascript 没有真正的类的概念。它采用一种原型链的机制,通过原型对象的连接关系来表达继承:通过某个属性(__proto__
)将原型对象连接成树形结构,则所谓的继承即为该树中节点与其祖先节点的血缘关系。在访问某个对象的属性时,会顺着原型对象树往上寻找目标属性,并返回第一个含有此属性的节点的对应属性值。这种继承策略带来的副产品是,可以轻易地通过修改原型对象上的属性使得所有继承它的对象都拥有此新增属性[1]。
__proto__
虽然被纳入了 ECMA 标准中,但目前它是不被推荐使用的,请使用
Reflect.getPrototypeOf() 和 Reflect.setPrototypeOf()
代替。
出于方便叙述考虑,本文仍采用 __proto__
进行演示。
原型链
¶在 ES6 标准里,所有的对象都有两个内置属性 constructor
和 __proto__
,其中:
constructor
: 指向创建它的构造函数。__proto__
: 指向创建它的构造函数的原型对象。
构造函数还有一个内置属性 prototype
指向它的原型对象;而构造函数的原型对象的
constructor
又指向此构造函数。有点绕,可以看下面的代码示例[2]:
123456789101112function Person(name) { this.name = name}
const alice = new Person('Alice')
alice.constructor === Person // => truealice.__proto__ === Person.prototype // => true
Person.constructor === Function // => truePerson.__proto__ === Function.prototype // => truePerson.prototype.constructor === Person // => true
继承
¶前面提到过,Javascript 通过原型对象的连接关系来表达继承,因此我们可以修改连接关系来实现继承。比如下面的代码中,想让 Student
继承自 Person
,只需要将
Student
的原型对象的 __proto__
指向 Person
的原型对象就好了(仅用于演示,请不要投入到生产中):
1234567891011121314151617181920function Person(name) { this.name = name }Person.prototype.great = function () { return `Hello, ${this.name}!`}
function Student(grade) { this.grade = grade }Student.prototype.showGrade = function () { return `Grade: ${this.grade}`}
Student.prototype.__proto__ = Person.prototype
const alice = new Student(2)alice instanceof Student // => truealice instanceof Person // => true
alice.great() // => 'Hello, undefined!'alice.name = 'Alice'alice.showGrade() // => 'Grade: 2'alice.great() // => 'Hello, Alice!'
上面的修改看起来很直观,但直接操作 __proto__
是不被推荐的,你可以使用
Reflect.setPrototypeOf() 来做修改。
此外,原型对象之间的连接还可以通过 constructor
和 constructor.prototype
来表达,(前文提到过,对象的 constructor
指向其构造函数)如最开始的例子中:
123alice.__proto__ === Student.prototype // => trueStudent.__proto__ === Person.prototype // => truePerson.__proto__ === Function.prototype // => true
等价于:
12alice.constructor.prototype === Person.prototype // => truePerson.constructor.prototype === Function.prototype // => true
因此我们可以利用 Object.create
来创建一个以 Person.prototype
为原型对象的对象,然后将 Student.prototype
指向此对象:
123456789function inherit(Child, Parent) { const prototype = Object.create(Parent.prototype) for (const key of Object.getOwnPropertyNames(Child.prototype)) { if (prototype.hasOwnProperty(key)) continue prototype[key] = Child.prototype[key] } prototype.constructor = Child Child.prototype = prototype}
注意上面代码高亮部分,将 Child.prototype
的值复制到了新的原型对象中,否则调用此继承函数后,原先定义在 Child.prototype
上的属性会丢失。
创建对象
¶在 Javascript
中创建对象有多种方式:
new
:new Person()
创建并返回一个以Person.constructor
作为原型对象的对象,因此返回的对象继承自Person
Object.create
:Object.create(__proto__)
: 接受一个对象__proto__
,创建并返回一个以此对象作为原型对象的对象,因此使用Object.create(null)
可以创建一个不继承自任何对象的对象。const alice = { name: 'alice' }
: 等价于const alice = new Object({ name: 'alice' })
Related
¶