原型链继承
基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法,也就是将父类的实例作为子类的原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Super() { this.superproperty = true }
Super.prototype.getSuperValue = function () { console.log(this.superproperty) }
function Sub() { this.subproperty = false }
Sub.prototype = new Super()
Sub.prototype.getSubValue = function () { console.log(this.subproperty) }
|
可以看到,这种方式实现的继承本质上是重写原型对象,上面的代码中,继承是通过创建Super
的实例,并将该实例赋给Sub.prototype
,因此,原本存在于Super
实例中的所有属性和方法,现在也存在于Sub.prototype
中了
1 2 3 4 5 6 7 8 9
| new Sub() ├── subproperty: false └── __proto__ ├── getSubValue: f() ├── superproperty: true └── __proto__ ├── getSuperValue: f() ├── constructor: f Super() └── __proto__: Object.prototype
|
很明显,getSuperValue()
方法依然还在Super.prototype
中,但superproperty
则位于Sub.prototype
中,这是因为superproperty
是一个实例属性,而getSuperValue()
是原型方法
同时,obj.constructor
现在指向Super
,这是由于Sub.prototype
被重写了的缘故
总结:在通过原型链实现继承的情况下,实例是子类的实例也是父类的实例,父类新增的原型方法和属性,子类都能够访问,并且原型链继承简单易于实现,缺点是来自原型对象的所有属性被所有实例共享,无法向父类构造函数传参
构造继承
基本思想:在子类构造函数的内部调用超类的构造函数
1 2 3 4 5 6 7 8 9 10 11
| function Super() { this.superproperty = 'hello' this.showValue = function () { console.log(this.superproperty) } }
function Sub(property) { Super.call(this) this.subproperty = property }
|
这种方式实际上是在将要创建的Sub
实例的环境下调用Super
构造函数,于是会在Sub
实例中执行Super
定义的所有对象初始化代码,因此,每个Sub
实例会有一个来自Super
的属性和方法的副本
1 2 3 4 5
| let obj = new Sub('world') let obj1 = new Sub('world') console.log(obj.subproperty) obj.showValue() console.log(obj.showValue === obj1.showValue)
|
1 2 3 4 5 6 7
| new Sub() ├── showValue: f() ├── subproperty: 'world' ├── superproperty: 'hello' └── __proto__ ├── constructor: f Sub() └── __proto__: Object.prototype
|
如果只使用构造继承,那么不可避免的会遇到构造函数模式存在的问题,即公有属性和方法无法复用,而且,在超类原型中定义的方法,子类也无法使用
1 2 3 4 5 6
| Super.prototype.getSuperValue = function () { console.log(this.superproperty) }
obj.getSuperValue()
|
组合继承
基本思想:将原型链和构造继承结合在一起,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person(name, age) { this.name = name this.age = age }
Person.prototype.sayName = function () { console.log(this.name) }
function Programmer(skill, name, age) { this.skill = skill Person.call(this, name, age) }
Programmer.prototype = new Person() Programmer.prototype.constructor = Programmer Programmer.prototype.saySkill = function () { console.log(this.skill) }
|
使用这种模式既能通过在原型上定义方法实现函数的复用,同时能保证每个实例有自己的属性
1 2 3 4 5 6 7
| let p1 = new Programmer('js', 'Mike', 22) p1.sayName() p1.saySkill() console.log(p1 instanceof Programmer)
let p2 = new Programmer('java', 'Tom', 26) console.log(p1.sayName === p2.sayName)
|
1 2 3 4 5 6 7 8 9 10
| p1: new Programmer() ├── age: 22 ├── name: 'mike' ├── skill: 'js' └── __proto__ ├── age: undefined ├── constructor: f Programmer() ├── name: undefined ├── saySkill: f() └── __proto__: Object.prototype
|
组合式继承的缺点在于必须调用两次父类的构造函数,生成两份实例
原型式继承(Object.create)
基本思想:借助原型,基于已有的对象创建新对象
1 2 3 4 5
| function inherit(obj) { function F() {} F.prototype = obj return new F() }
|
在inherit
函数内部,先创建一个临时的构造函数,然后将传入的对象作为构造函数的原型,最后返回这个构造函数的一个实例
本质上,inherit
对传入其中的对象执行了一次浅复制
1 2 3 4 5 6 7 8 9 10 11 12 13
| var person = { name: 'Mike', age: 22 }
var p1 = inherit(person) p1.name = 'Tom' p1.age = '26' p1.sayName = function () { console.log(this.name) }
p1.sayName()
|
1 2 3 4 5 6 7 8
| p1 ├── age: 22 ├── name: 'Tom' ├── sayName: f() └── __proto__ ├── age: 22 ├── name: 'Mike' └── __proto__: Object.prototype
|
原型式继承要求必须有一个对象可以作为另一个对象的基础,然后再根据具体需求对得到的对象加以修改
寄生式继承
基本思路:与原型式继承紧密相关,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| function inherit(obj) { function F() {} F.prototype = obj return new F() }
function createAnother(obj) { let clone = inherit(obj) clone.sayHi = function () { console.log('hi') } return clone }
|
1 2 3 4 5 6 7 8
| var person = { name: 'Mike', age: 22 }
var p1 = createAnother(person)
p1.sayHi()
|
1 2 3 4 5 6
| p1 ├── sayHi: f() └── __proto__ ├── age: 22 ├── name: 'Mike' └── __proto__: Object.prototype
|
寄生组合继承
基本思想:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
不必为了指定子类型的原型而调用超类的构造函数,我们需要的只是超类型原型的一个副本,本质上,就是使用寄生式继承来继承超类的原型,然后再将结果指定给子类型的原型
1 2 3 4 5 6 7 8 9 10 11
| function inherit(obj) { function F() {} F.prototype = obj return new F() }
function inheritPrototype(subType, superType) { let prototype = inherit(superType.prototype) prototype.constructor = subType subType.prototype = prototype }
|
举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Person(name, age) { this.name = name this.age = age }
Person.prototype.sayName = function () { console.log(this.name) }
function Programmer(skill, name, age) { this.skill = skill Person.call(this, name, age) }
inheritPrototype(Programmer, Person)
Programmer.prototype.sayAge = function () { console.log(this.age) }
let p1 = new Programmer('java', 'Jack', 30) p1.sayName() p1.sayAge()
|
1 2 3 4 5 6 7 8
| p1 ├── age: 30 ├── name: 'Jack' ├── skill: 'java' └── __proto__ ├── constructor: f Programmer() ├── sayAge: f() └── __proto__: Object.prototype
|
可以看到,通过这种方法只需要调用一次父类的构造函数