JS里的继承有很多种,此篇文章只举了三种普通常见的继承类型,希望能为读者日常的学习提供一定的帮助。
原型链继承
我们通过一个例子来了解什么是原型链继承
function SuperType() { // 超类
this.superproerty = true
}
SuperType.prototype.getSuperValue = function () {
return this.superproerty
}
function SubType() { // 子类
this.subproperty = false
}
SubType.prototype.getSuperValue = function () {
return this.subproperty
}
SubType.prototype = new SuperType()
var instance = new SubType()
console.log(instance.getSuperValue());
复制代码
问:最后输出是什么?true 还是 false ?
答案: 输出 true。
原因:SubType.prototype = new SuperType()
这一行代码用 SuperType 类型的一个实例重写了 SubType 的原型对象,使得SubType 的原型对象找到了 SuperType 的原型对象。
这个例子中, SubType 继承了 SuperType 。SuperType 我们称之为超类,继承的方式就是通过
new SuperType()
创建 SuperType 实例,且将该实例赋值给 SubType 的原型来实现的。而实现的本质,是重写子类型的原型对象。原型链:instance -> SubType的原型 -> SuperType的原型 -> Object的原型
这就是原型链继承,让子类的原型等于超类的实例,实现原型链继承。
缺点:
- 超类构造函数中定义的引用类型的实例属性,会在子类的原型上变成属性,被所有子类的实例所共享。
- 在创建子类型的实例时,不能向超类的构造函数中传递参数。
构造函数继承
构造函数可以很好的解决原型链继承中的缺点
看一个例子
function SuperType() {
this.colors = ['red', 'green', 'blue']
}
function SubType() {
SuperType.call(this)
}
var instance = new SubType()
instance.colors.push('pink')
console.log(instance.colors); // [ 'red', 'green', 'blue', 'pink' ]
复制代码
首先解释一下 call( ) 的用处:
利用
call()
能够使用属于另一个对象的方法。
我们再看上面的例子,SuperType.call(this)
就是将 SuperType 自己的作用域搬到 SubType 里面去了,等同于在 SubType 里面复制了一份 SuperType 的作用域,并不会影响 SuperType 自己的作用域。
function SuperType(color) {
this.colors = ['red', 'green', 'blue']
this.color = color
}
function SubType(color) {
SuperType.call(this, color)
}
var instance = new SubType()
var instance4 = new SubType('yellow')
var instance2 = new SuperType() // 作用域没有被影响
var instance3 = new SubType()
instance.colors.push('pink')
console.log(instance.colors); // [ 'red', 'green', 'blue', 'pink' ]
console.log(instance2.colors); // [ 'red', 'green', 'blue' ]
console.log(instance2.color); // undefined 实例instance4并未影响超类作用域
console.log(instance3.colors); // [ 'red', 'green', 'blue' ]
console.log(instance4.color); // yellow
复制代码
可以看到这四个实例中,作用域并不是同一个,且传参不会混淆。这就很好的解决了原型链的两个缺点。
但它也有缺点
缺点:
- 子类的实例对象上可能会存在非必要的属性。
- 无法继承到超类原型上的属性。
组合继承
原型链继承 + 构造函数继承
直接上例子
function SuperType(name) {
this.name = name
this.colors = ['red', 'green', 'blue']
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType(name, age) {
SuperType.call(this, name) // 构造函数继承
this.age = age
}
SubType.prototype = new SuperType() // 原型链继承,用SuperType类型的一个实例来重写SubType类型的原型对象
SubType.prototype.sayAge = function () {
console.log(this.age);
}
var instance1 = new SubType('老鱼', 18)
instance1.colors.push('pink')
console.log(instance1.colors); // ['red', 'green', 'blue', 'pink']
instance1.sayName() // 老鱼
instance1.sayAge() // 18
var instance2 = new SubType('虾米', 20)
console.log(instance2.colors); // [ 'red', 'green', 'blue' ]
instance2.sayName() // 虾米
instance2.sayAge() // 20
复制代码
上述代码将原型链继承和构造函数继承的优点都利用了,使其规避了两种继承的缺点。
注意:SubType.prototype = new SuperType()
一定要在SubType.prototype.sayAge = function () {console.log(this.age);}
这行代码之前,如果次序反了会导致sayAge()
被覆盖,从而导致sayAge()
无法调用。
缺点:
- 无论在什么情况下,都会调用两次超类构造函数。