javascript的组合继承

上一篇博客讲的是原型跟原型链,接在这后面的就是继承了,这一篇博客讲的是组合继承。

需要先说明一点,这篇博客是基于高程来写的,这里只是加上自己的理解而已。

那么在上一篇将原型的时候,我们知道对于一些所有实例都有的属性比如说方法来讲,我们可以将其设置到原型对象中,这样当我们每次使用构造函数的时候,就可以避免重复为这个方法分配内存,达到提高性能以及可复用性的目的。
这样看似很完美,其实不然,当原型对象中有一个引用类型数据的时候,比如说数组。那么当我们的一个实例改变了这个数组中的某个元素的时候,由于另外一个实例也是共享这个数组的,那么这个实例在使用这个数组的时候就会受到之前那个实例对数组改动时的影响。我们可以看下图这个例子:

function SuperType() {
    this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function() {
    alert(this.name)
}

function SubType() {}

SubType.prototype = new SuperType()

var instance1 = new SubType()
instance1.colors.push('black')
alert(instance1.colors)            // ['red', 'blue', 'green', 'black']

var instance2 = new SubType()
alert(instance2.colors)            // ['red', 'blue', 'green', 'black']

这段代码是高程上的例子,如果有翻过书的读者应该是有印象的,对于没有读过的读者,我就在这里解释一下这段代码说了什么。
首先我们定义了一个SuperType的构造函数,并且有个属性colors,是一个数组类型的对象。之后通过

SuperType.prototype.sayName = function() {
    alert(this.name)
}

给SuperType的原型对象绑定了一个sayName方法。之后又定义了一个SubType的构造函数。接下来这一行代码很重要

SubType.prototype = new SuperType()

这里让SubType的原型对象作为SuperType的实例,也就是说SubType的原型对象成为了SuperType的实例对象,并且这个原型对象拥有了colors这个属性。还有一点,上一篇博客讲到,只要某个对象是一个构造函数的实例,那么这个对象就会拥有一个指向其构造函数的原型对象的指针,所以这个SubType.prototype原型对象会拥有一个__proto__指针指向SuperType.prototype,
SuperType.prototype有一个sayName方法,因此SubType可以通过原型链使用这个sayName方法。

所以当我们下面利用构造函数SubType创建实例的时候,这个实例对象才可以使用colors这个属性。
然后看到注释,发现当我们只对instance1这个实例去修改colors的时候,却发现instance2的colors也被改变了。这显然不太合适,因为在写代码的时候,我们希望这两个实例可以拥有自己的属性,并且我们发现,在进行实例化的时候,我们没有办法对父类进行传参,也就是没办法对SuperType这个构造函数传递参数,那么我们怎么样做呢?

前人搞了一个借用构造函数的方法,具体怎么用,我们还是看高程里面的例子,我也依然会进行分析。
 

function SuperType(name) {
    this.name = name
    this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function() {
    alert(this.name)
}

function SubType(name, age) {
    SuperType.call(this, name)
    this.age = age
}

SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {
    alert(this.age)
}

var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
alert(instance1.colors)
instance1.sayName()
instance1.sayAge()

var instance2 = new SubType('Greg', 27)
alert(instance2.colors)
instance2.sayName()
instance2.sayAge()

我们看一下这个代码,其中需要注意的是SubType中的代码,里面写了SuperType.call(this, name)这样一句代码,这是干嘛呢?没错,这个东西就是上面提到的借用构造函数。有js基础的应该知道call是用来做什么的,这里在SubType里面通过SuperType.call(this, name),这个地方实际上就相当于SubType调用了父类的构造函数,并且让this指向SubType,而SuperType有一个colors属性,间接的SubType的实例也会拥有这个colors属性。那么这种方式跟前一种方式有什么不同呢,其实区别是很明显的,当我们通过SubType创建实例的时候,都会调用SuperType获得一个新的colors,方法跟原型对象是不同的,原型对象的属性只有一份,而方法中的属性每次调用都会生成一个新的属性,因此,通过这样的方式,我们也就做到了每个实例都拥有了自己的属性colors,这个时候再去改变这个colors就不会影响到另外一个实例的colors属性了。

我们还看到SuperType.call(this, name)里面我们还传入了一个name参数。因此我们通过借用构造函数的方法实现了在创建子类对象的时候对父类对象进行传参的操作了。

通过借用构造函数确实很好的解决了原型链带来的问题,可是如果这样的话,当我们每个实例都需要有自己的属性的时候,我们不就都得将方法或者属性写到父类里面然后借用构造函数了吗,接着我们每次创建实例的时候又每次都要分配内存,原型的出现就是为了解决复用性的,结果这样一搞复用性就没了。显然这是借用构造函数这个方法存在的一个问题。

那么重头戏来了,我们发现不管是单独使用原型链还是借用构造函数都没办法很好的解决问题,因此前人提出了组合继承,也就是将原型链和借用构造函数两种方法组合在一起。

还是上面那个例子。其实组合继承很容易理解,对于属性来说,用借用构造方法,对于一些同用的方法,就设置到原型对象上。
因为有些属性必须要每个实例各自一份,对于这种属性就只能通过调用父类的构造函数来解决,对于上面的代码,比如
sayName方法,就没必要了,因为这种是所有实例通用的,就放到原型对象上。

其实感觉自己表达的很粗糙,但会慢慢加油的。如果有错误的地方,多谢指正,非常感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值