# JS的原型链和继承

7 min read
Table of Contents

JavaScript 的原型链和相关继承方式。

原型 prototype

我们创建的每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象,该对象包含了所有实例共享的属性和方法。

我们需要明白对象原型(Object.getPrototypeOf(obj),或者通过已弃用的 __proto__ 属性)和构造函数的 prototype 属性之间有区别。

constructor

constructor 属性始终指向创建当前对象的构建函数。在默认情况下,所有原型对象都会自动获得一个 constructor 属性,这个属性包含一个指向 prototype 属性所在函数的指针。

function Foo(name) {
this.name = name
}
const f1 = new Foo('bar')
const f2 = Object.create(f1)
console.log(Object.getPrototypeOf(f1))
// { constructor: function Foo(name) }
console.log(Object.getPrototypeOf(f2))
// { name: 'bar' }
console.log(f1.constructor, f2.constructor)
// function Foo(name), function Foo(name)

可以看出 f2.prototype 原型指向 f1。f2 的 constructor 指向的是构造函数。将构建函数的 prototype 进行扩展

Foo.prototype.age = 11
Foo.prototype.dosomething = function () {
// ...
}
Foo: 构造函数
Foo Prototype: 原型对象
f1, f2: 实例对象
┌───────────────────────────────────────────────┐
▼ │
┌──────────────────────┐ ┌─────────────────────────┐ │
│ Foo │ ┌──►│ Foo Prototype │ │
├──────────┬───────────┤ │ ├────────────┬────────────┤ │
│prototype │ ├───┤ │constructor │ ├─┘
├──────────┼───────────┤ │ ├────────────┼────────────┤
│name │(undefined)│ │ │age │11 │
└──────────┴───────────┘ │ ├────────────┼────────────┤
│ │dosomething │(function) │
┌─────────────────────────┐ │ └────────────┴────────────┘
┌►│ f1 │ │
│ ├─────────────────────────┤ │
│ │Object.getPrototypeOf(f1)├───┘
│ ├─────────────┬───────────┤
│ │name │'bar' │
│ └─────────────┴───────────┘
│
│ ┌─────────────────────────┐
│ │ f2 │
│ ├─────────────────────────┤
└─┤Object.getPrototypeOf(f2)│
└─────────────────────────┘

我们可以发现实例对象 f1, f2 和构造函数没有直接关系。f1,f2 的 constructor 指向构造函数而已。

继承方式

JavaScript 主要有如下几种继承方式:

  • 原型链
  • 借用构造函数
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承

原型链

实现原型链继承,就是让一个对象的原型对象指向另一个对象的实例,实现的本质就是重写原型对象。

function Person() {
this.name = 'human'
}
function Student() {}
// 重写 Student 的原型,继承 Person 实例
Student.prototype = new Person()
var instance = new Student()
console.log(instance.name)

重写原型对象的时候,默认自带的 constructor 会被覆盖,这个实例的 constructor 会指向构造函数 Person。

注意要点:

  • 重写原型链上的方法会屏蔽原来的方法
  • 不能使用对象字面量创建原型方法

问题:

  • 包含引用类型值的原型属性会被所有实例共享。修改了该属性,所有实例的属性也会改变
  • 在创建子类型的实例时,不能向超类型的构造函数中传递参数

借用构造函数

借用构造函数方法,也叫伪造对象或经典继承,基本思想是在子类型构造函数的内部调用超类型构造函数。

通过使用 apply 和 call 方法可以在新创建的对象上执行构造函数

function Person() {
this.name = 'human'
}
function Student() {
Person.call(this)
}
var instance = new Student()
console.log(instance.name)

传递参数

function Sup(name) {
this.name = name
}
function Sub() {
Sup.call(this, 'foo')
this.age = 18
}
var instance = new Sub()
console.log(instance.name) // foo

方法在构造函数中定义,函数的复用无法实现。

组合继承(最常用)

也叫伪经典继承,就是将原型链和借用构造函数组合一起。

其实通过上面的两个继承方式,我们发现有意思的地方。原型链能够很好地复用方法,但是会无法传递参数,而借用构造函数可以传递参数,却无法复用方法。所以我们将他们组合到一起。

基本思想是,通过借用构造函数来实现对实例属性的继承,通过在原型链上定义方法实现了函数复用。

function Sup(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Sup.prototype.sayName = funtion() {
console.log(this.name);
}
// 构造函数实现实例属性的继承
function Sub(name, age) {
Sup.call(this, name);
this.age = age;
}
// 原型链复用方法
Sub.prototype = new Sup();
Sub.prototype.constructor = Sub;
const instance1 = new Sub("foo", 18);
instance1.colors.push('black'); // ['red', 'blue', 'black']
instance1.sayName(); // 'foo'
const instance2 = new Sub('bar', 20);
console.log(instance2.colors); // ['red', 'blue']
instance2.sayName(); // 'bar'

让两个不同的 Sub 实例既拥有自己的属性 — 包括 colors 属性,又可以使用相同的方法。

原型式继承

借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。

function object(o) {
function F() {}
F.prototype = o
return new F()
}

ES5 通过 Object.create() 方法规范化了原型式继承。

注:包含引用类型值的原型属性会被所有实例共享。就像使用原型模式一样。

寄生式继承

寄生式继承的思路与工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

function createAnother(original) {
var clone = object(original) // 创建一个新对象,其他方式返回新对象也可
clone.say = function () {
// ...
}
return clone
}

寄生组合式继承

function inheritPrototype(sub, sup) {
var prototype = Object(sup.prototype) // 创建对象
prototype.constructor = sub
sub.prototype = prototype
}

参考资料


More Posts

# 清除浮动

2 min read

当未设置容器高度时,同时容器内有浮动的元素时,容器的高度不能自动伸长以适应内容的高度,使得内容溢出到容器外面而影响布局的现象,这个现象叫浮动溢出。为了防止这种现象出现而进行的 CSS 处理,就叫清除浮动。

# DOM

7 min read

DOM(Document Object Model) 文档对象模型。每个 Windows 对象有一个 document 属性引用了 Document 对象。Document 对象表示窗口的内容。