JavaScript的继承方式
1、原型链继承
核心:将父类的实例对象作为子类的原型对象,及继承了父类原型的方法也继承了父类实例的属性。
// 父类
function SuperType() {
this.property = true
this.colors = ['red','blue']
}
// 父类原型的方法
SuperType.prototype.getSuperValue = function () {
return this.property
}
// 子类
function SubType() {
this.subproperty = false
}
// 子类的原型对象为 父类的实例对象
SubType.prototype = new SuperType()
// 给子类原型添加方法 一定要注意顺序
SubType.prototype.getSubValue = function () {
return this.subproperty
}
let sub1 = new SubType()
let sub2 = new SubType()
// 修改父类的引用类型的数据 ,会影响其他实例对象
sub1.property = false
sub1.colors.push('yellow')
console.log(sub2);
console.log(sub1.getSuperValue());
console.log(sub2.getSuperValue());
缺点
- 父类的所有
引用类型的属性
会被所有子类共享,一个子类修改,所有子类都发生改变。 - 不能传递参数
2、构造函数继承
核心:在子类构造函数中调用父类构造函数,可以在子类构造函数中使用call()
和apply()
方法,改变this
指向。
// 父类
function SuperType() {
this.property = true
}
// 父类原型的方法
SuperType.prototype.getSuperValue = function () {
return this.property
}
// 子类
function SubType() {
// 通过改变this指向从而继承父类构造函数的属性
// 绑定在实例身上
SuperType.call(this)
}
let sub1 = new SubType()
let sub2 = new SubType()
sub1.property = false
console.log(sub1.property); //false
console.log(sub2.property); //true
// console.log(sub1.getSuperValue());//sub1.getSuperValue is not a function
由于执行SuperType.call(this)
,所以创建子类实例时,会调用SuperType
构造函数,于是SubType
的实例都会将SuperType
中的属性复制一份。
缺点
- 无法继承父类的原型对象的属性和方法
3、组合继承
核心: 使用原型链继承父类原型上的方法和属性,使用构造函数继承父类实例的方法和属性。
// 父类
function SuperType(name) {
this.name = name
}
SuperType.prototype.sayName = function () {
return this.name
}
// 子类
function SubType(name, age) {
//第二次调用
// 继承父类的实例属性 name
SuperType.call(this, name)
this.age = age
}
// 继承父类原型上的方法和属性
SubType.prototype = new SuperType()
// 因为改变了子类的原型对象
//第一次调用
// 所以重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType
// 给子类原型添加方法 注意这里的位置,要在继承之后
SubType.prototype.sayAge = function () {
return this.age
}
let sub1 = new SubType('hwm1', 21)
let sub2 = new SubType('hwm2', 22)
console.log(sub1.sayName()); //hwm1
console.log(sub2.sayName()); //hwm2
console.log(sub1.sayAge()); //21
console.log(sub2.sayAge()); //22
SuperType()
被调用两次
- 第一次会给
SubType.prototype
写入SuperType
实例的属性name
- 第二次会给
SubType
的实例对象,写入SuperType
实例的属性name
实例对象上的属性会屏蔽其原型对象上的同名属性。
缺点
- 在创建子类实例对象时,会存在两份相同的属性和方法。
4、原型式继承
核心:利用一个空构造函数作为中介,将需要继承的对象浅复制给这个空构造函数的原型,通过构造函数创建一个实例。
let obj = {
name: 'hwm',
colors: ['red', 'yellow', 'blue'],
sayName(){
return this.name
}
}
// 将obj浅复制
function object(obj) {
// 空的构造函数
function F() { }
// 继承了obj对象的属性和方法
F.prototype = obj
return new F()
}
let sub1 = object(obj)
let sub2 = object(obj)
sub1.name = 'sub1'
sub2.name = 'sub2'
sub1.colors.push('pink')
console.log(sub2.colors); //['red', 'yellow', 'blue', 'pink']
console.log(sub1.sayName()); //sub1
console.log(sub2.sayName());//sub2
可以直接使用ES5的Object.create()
方法。创建一个原型为obj
的对象。
let sub3 = Object.create(obj)
let sub4 = Object.create(obj)
//直接在对象上添加新属性,而不是修改父类的属性
sub3.name = 'sub3'
sub4.name = 'sub4'
sub3.colors.push('pink')
console.log(sub4.colors); //['red', 'yellow', 'blue', 'pink']
console.log(sub3.sayName());
console.log(sub4.sayName());
缺点
- 因为是浅复制,继承的属性如果是引用类型,存在篡改的可能。
- 无法传递参数
5、寄生式继承
核心:在原型式继承的基础上,给创建的对象添加属性和方法。
let obj = {
name: 'hwm',
colors: ['red', 'yellow', 'blue']
}
function SubType(obj) {
// 浅复制对象
let clone = Object.create(obj)
clone.sayName = function () {
return this.name
}
return clone
}
let sub1 = SubType(obj)
let sub2 = SubType(obj)
sub1.name = 'sub1'
sub2.name = 'sub2'
console.log(sub1.sayName());
console.log(sub2.sayName());
sub1.colors.push('pink')
console.log(sub2.colors); //['red', 'yellow', 'blue', 'pink']
console.log(sub1);
console.log(sub2);
函数SubType
的作用就是为构造函数新增属性和方法。
缺点
- 因为是浅复制,继承的属性如果是引用类型,存在篡改的可能。
- 无法传递参数
6、寄生组合式继承
结合构造函数继承
与寄生式继承
,这是最成熟的方法,也是现在库实现的方法
// 父类
function SuperType(name) {
this.name = name
this.colors = ['red', 'yellow']
}
SuperType.prototype.sayName = function () {
return this.name
}
// 子类
function SubType(name, age) {
// 继承父类实例属性
SuperType.call(this, name)
this.age = age
}
// 继承父类原型上的属性
function inheritPrototype(subType, superType) {
// 浅复制父类原型对象
let prototype = Object.create(superType.prototype)
// 添加复制对象的constructor 属性指向子类
prototype.constructor = subType
subType.prototype = prototype
}
inheritPrototype(SubType, SuperType)
// 给子类原型添加方法
SubType.prototype.sayAge = function () {
return this.age
}
let sub1 = new SubType('sub1', 21)
let sub2 = new SubType('sub2', 22)
console.log(sub1);
sub1.colors.push('1')
sub2.colors.push('2')
console.log(sub1.colors);//['red', 'yellow', '1']
console.log(sub2.colors);//['red', 'yellow', '2']
这个方式比组合式继承
高效,因为SuperType
函数只调用了一次。此方法没有使用原型链继承
的方式继承父类的原型,而是通过创建一个原型为父类原型的对象,并将该对象的constructor
指向子类,并将该对象赋值给子类原型。
7、混入方式继承多个对象
核心:在寄生组合式继承
的基础上,通过Object.assign
方式将其他父类的原型对象拷贝到子类原型上
// 父类
function SuperType(name) {
this.name = name
this.colors = ['red', 'yellow']
}
SuperType.prototype.sayName = function(){
return this.name
}
// 父类
function OtherSuper(age) {
this.age = age
}
OtherSuper.prototype.sayAge = function(){
return this.age
}
// 子类
function SubType(name, age) {
// 继承父类实例属性
SuperType.call(this, name)
OtherSuper.call(this, age)
}
// 子类的原型 浅复制父原型的属性和方法
SubType.prototype = Object.create(SuperType.prototype)
// 混入其他父类 将OtherSuper.prototype的方法和属性 混入SubType.prototype
Object.assign(SubType.prototype, OtherSuper.prototype)
// 重新指定constructor
SubType.prototype.constructor = SubType
// 添加原型方法
SubType.prototype.sayHi = function () {
console.log('hi');
}
let sub1 = new SubType('sub1', 21)
let sub2 = new SubType('sub2', 22)
sub1.colors.push('1')
sub2.colors.push('2')
console.log(sub1.colors);
console.log(sub2.colors);
console.log(sub1);
8、ES6类继承extends
// 父类
class SuperType {
colors = ['red', 'yellow']
constructor(name) {
this.name = name
}
}
SuperType.prototype.sayName = function () {
return this.name
}
// 子类继承
class SubType extends SuperType {
constructor(name, age) {
// 继承父类的属性
super(name)
this.age = age
}
}
SubType.prototype.sayAge = function () {
return this.age
}
let sub1 = new SubType('sub1', 21)
let sub2 = new SubType('sub2', 22)
console.log(sub1.sayName());
sub1.colors.push('1')
console.log(sub2.colors);//['red', 'yellow']