手写实现继承
区别总结
除了通过原型链的方式实现 JavaScript 继承,
JavaScript 中实现继承的方式还包括经典继承(盗用构造函数)、组合继承、原型式继承、寄生式继承,等等。
原型链继承方式中 引用类型的属性被所有实例共享,无法做到实例私有;
经典继承方式可以实现实例属性私有,但要求类型只能通过构造函数来定义;
组合继承融合原型链继承和构造函数的优点,它的实现如下:
function Parent(name) {
// 私有属性,不共享
this.name = name;
}
// 需要复用、共享的方法定义在父类原型上
Parent.prototype.speak = function () {
console.log("hello");
};
function Child(name) {
Parent.call(this, name);
}
// 继承方法
Child.prototype = new Parent();
原型链继承
- 例子
function Parent() {
this.name = '11'
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child() {}
Child.prototype = new Parent()
var c1 = new Child()
console.log(c1.getName()) // 11
- 问题1 引用类型的属性被所有的实力共享
function Parent() {
this.names = ['11', '22']
}
function Child() {}
Child.prototype = new Parent()
var child1 = new Child()
child1.names.push('33')
console.log(child1.names) // ["11", "22", "33"]
var child2 = new Child()
console.log(child2.names) // ["11", "22", "33"]
- 问题2 在创建 Child 的实例时,不能向 Parent 传参数
借用构造函数(经典继承)
function Parent() {
this.names = ['11', '22']
}
function Child() {
Parent.call(this)
}
var child1 = new Child()
child1.names.push('333')
console.log(child1.names) // ['11', '22', '33']
var child2 = new Child()
console.log(child2.names) // ['11', '22']
优点
避免引用类型属性被所有实例共享
可以在 Child 中向 Parent 传参
缺点
- 方法都在构造函数中定义,每次创建实例都会创建一遍方法
组合继承
原型链继承和经典继承双剑合璧。
- 融合了原型链继承和构造函数继承的优点,是 js 中最常用的继承模式
function Parent(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
var child1 = new Child('shiba', 18)
child1.colors.push('black')
console.log(child1.name) // shiba
console.log(child1.age) // 18
console.log(child1.colors) // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20')
console.log(child2.name) // daisy
console.log(child2.age) // 20
console.log(child2.colors) // ["red", "blue", "green"]
原型式继承
使用 ES5
Object.create
的模拟实现,将传入的对象作为创建对象的原型。缺点:包含引用类型的属性值,始终会共享相应的值,这一点和原型链继承一样
function createObj(o) {
function F() {}
F.prototype = o
return new F()
}
var person = {
name: 'shiba',
arrs: ['11', '22']
}
var person1 = createObj(person)
var person2 = createObj(person)
// 这里是给 person1 添加了 name 的值
person1.name = 'p1'
console.log(person2.name) // shiba
person1.arrs.push('33')
console.log(person2.arrs) // ['11', '22', '33']
注意
修改了 person1.name
的值,person2.name
的值并未发生变化, 并不是因为 person1
和 person2
有独立的 name
值,而是因为 person1.name = 'p1'
, 给 person1
添加了 name
值,并非修改了原型上的 name
值
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来做增强对象,最后返回对象
function createObj(o) {
var cloe = Object.create(o)
cloe.sayName = function () {
console.log('hi')
}
return cloe
}
- 缺点:和借用构造函数模式一样,每次创建对象都会创建一遍方法
寄生组合式继承
重复下组合继承的代码
function Parent(name) {
this.name = name
this.arrs = ['11', '22', '33']
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = new Parent()
var child1 = new Child('hj', 18)
console.log(child1) // { name: 'hj', age: 18, arrs: ['11', '22', '33'] }
- 组合式继承最大的缺点是调用了两次父构造函数:
Parent
一次在设置子类型实例的原型的时候:
Child.prototype = new Parent()
一次是在创建子类型实例的时候
var child1 = new Child('hj', 18)
回想下 new 的模拟实现,其实上面的语句中,我们执行了:
Parent.call(this, name)
在这里,我们会调用一车 Parent 构造函数。
如果不适用 Child.prototype = new Parent()
,而是直接的让 Child.prototype
访问到 Parent.prototype
呢?
看看如何实现:寄生组合式继承
function Parent(name) {
this.name = name
this.color = ['red', 'blue', 'green']
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Chid(name, age) {
Parent.call(this, name)
this.age = age
}
// 关键的三步
var F = function () {}
F.prototype = Parent.prototype
Chid.prototype = new F()
var child1 = new Chid('shiba', 18)
console.log(child1)
提醒
需要一个空函数使用 new 操作符 做中转。
不然修改 Chid.prototype
的时候,会直接修改 Parent.prototype
var F = function () {}
F.prototype = Parent.prototype
Chid.prototype = new F()
封装下继承方法
function obj(parent) {
function F() {}
F.prototype = parent
return new F()
}
function inherit(child, parent) {
var proto = obj(parent.prototype)
proto.constructor = child
child.prototype = proto
}
// 使用
inherit(Chid, Parent)
引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:
这种方式的高效率体现它只调用了一次
Parent
构造函数, 并且因此避免了在Parent.prototype
上面创建不必要的、多余的属性。与此同时 ,原型链还能保持不变;因此,还能够正常使用instanceof
和isPrototypeOf
。 开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。