原型链继承的问题
如果单独只使用原型链继承主要有以下两个问题。
1)包含引用类型值的原型属性会被所有的实例共享
下面中父类有一个 colors 属性是一个引用类型,每个子类实例对它的修改,其它子类的实例会跟着修改。
// 定义父类
function SuperClass () {
this.colors = ['red, black']
}
// 定义子类
function SubClass () {
}
// 将父类的实例作为子类的原型对象
SubClass.prototype = new SuperClass()
// 声明子类的一个实例o1
o1 = new SubClass()
// 声明子类的另一个实例o2
o2 = new SubClass()
// 在 o1 中改变 colors 的值
// 注意不能这样去 o1.colors = ['red', 'yellow'] 修改数组的值,如果这样修改了,o1.colors就不再指向SuperClass中的colors了,而是指向了一个全新的数组。
// 要在原有数组上修改要使用数组提供的API,而不是新建一个数组再进行赋值
o1.splice(1, 1, 'yellow');
console.log(o1.colors) // ['red', 'yellow']
// 这样修改后 o2 实例也会被修改
console.log(o2.colors) // ['red', 'yellow']
2)无法在不影响其它实例的前提下向父类传递参数
在只使用原型链的前提下传递参数,主要就是就是在将创建的父类对象赋给子类的原型对象时,将参数传递进去SubClass.prototype = new SuperClass(['red'])
,这种方式没有办法做到给每个子类的实例单独设置各自的属性。
// 定义父类
function SuperClass (color) {
this.color=color
}
// 定义子类
function SubClass () {
}
// 将父类的实例作为子类的原型对象
SubClass.prototype = new SuperClass(['red'])
// 创建实例对象o1并传递参数
var o1 = new SubClass()
// 创建实例对象o2并传递参数
var o2 = new SubClass()
// 打印o1和o2的color
console.log(o1.color)
console.log(o2.__proto__.color)
可见,每个实例并不能够传递各自的参数。
借用构造函数
上面的每个实例是不能传递各自的参数的,如果我们让每个实例在自己作用域下调用父类的构造函数声明出来的属性就是各个实例中的了,每个实例都各自有自己的属性。
按照这个思路在子类的构造函数中中使用 SuperClass.call(this, color)
在子类实例的作用域下调用父类的构造函数,这样每个创建出来的子类的实例就都有自己的属性了。
// 定义父类
function SuperClass (color) {
this.color=color;
this.say = function () {
alert('hello');
}
}
// 定义子类
function SubClass (color) {
SuperClass.call(this, color)
}
// 创建实例对象o1并传递参数
var o1 = new SubClass(['red'])
// 创建实例对象o2并传递参数
var o2 = new SubClass(['yellow'])
// 打印o1和o2的color
console.log(o1.color) // ['red']
console.log(o2.color) // ['yellow']
可以看到每个实例都可以设置各自的属性。
我们知道实例的属性每个实例应该有各自的,但是父类的方法每个实例应该是能够共享的。但是上面这样写,父类的方法 say 在每个子类的实例都有,对于方法来说没有必要(子类的实例不会去更改方法,而且每个子类的实例都有这个方法会耗费内存),应该让每个子类的实例共享父类的方法。
在分析仅使用原型链时,其中第一个弊端就是父类的所有属性和方法都被子类的所有实例共享。那么在上面既然我们借用构造函数解决了实例属性共享的问题,方法的共享何不用原型链来解决呢?
接下来就出现了广泛使用的组合模式。
组合模式
之前在父类中直接定义方法,这样子类的实例无法共享父类的方法。现在将父类的方法定义在父类的 prototype 中(9行),然后将父类的实例对象赋给子类的原型对象(19行),这样就可以在子类的实例中共享父类的方法了。
// 定义父类
function SuperClass (color) {
this.color=color;
- this.say = function () {
- alert('hello');
- }
}
// 定义父类的方法
+ SuperClass.prototype.say = function () {
+ alert('hello');
+ }
// 定义子类
function SubClass (color) {
SuperClass.call(this, color)
}
// 继承父类的方法
+ SubClass.protype = new SuperClass()
// 创建实例对象o1并传递参数
var o1 = new SubClass(['red'])
// 创建实例对象o2并传递参数
var o2 = new SubClass(['yellow'])
// 打印o1和o2的color
console.log(o1.color) // ['red']
console.log(o2.color) // ['yellow']
o1.say()
ES6 中利用 class 的方法实际上就是利用了组合模式,接下来用 ES6 中的 class 重写上面的代码。
class SuperClass {
constructor (color) {
this.color = color;
}
say () {
alert('hello')
}
}
class SubClass extends SuperClass {
constructor (color) {
super(color)
}
}
const o1 = new SubClass(['red'])
console.log(o1.color) // ['red']
o1.say()
可以看到两者的结果是一致的。