JavaScript中的多种继承——最完美继承方式是什么?
本文主要基于阮一峰老师《JavaScript教程》中对象的继承一节,以及查阅网上其他资料整理总结,参考文章链接见文末。(主要感觉阮一峰老师这部分没有单独讲基于原型链和基于构造函数的继承,而是直接介绍了组合继承的方式。因此本人尝试每种继承方式都整理一遍。)
大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现。
上面先引用阮一峰老师的一段话。原型链是Js的一大特色,Js中实现继承也依赖于此。但除去基于原型继承,还可以只借用构造函数来实现继承。但由于单独使用这两种方式来实现继承的缺陷太大,我们在实战中普遍使用的是公认完美的组合继承。让我们先从较为简单的构造函数继承看起。
通过构造函数继承
通过在子类的构造函数中借助call
或者apply
调用父类的构造函数,将父类的属性复制一份到子类中,这种方式完全通过构造函数完成,与原型链没有任何关系。
function Animal(name){
this.name = name;
}
function Dog(name, age){
Animal.call(this, name);
this.age = age;
}
var dog1 = new Dog('dog1',3);
这种继承方式非常简单,但是由于JavaScript构造函数的本身机制或者说缺陷,单纯通过构造函数来继承有一个很大的缺点,那就是资源的浪费。
在JavaScript中,我们可以通过new
关键字调用构造函数来创建实例,在构造函数内部完成定义属性和方法。但是这有一个缺点,基于构造函数创建的多个实例之间无法共享属性/方法。每次创建实例的时候,内部的属性和方法都会完整的创建一次。而很多时候方法的行为和目的是一样的,重复创建会造成资源的浪费。通过以下代码来理解这个概念:
function Dog(name){
this.name = name;
this.bark = function(){
console.log('汪!')}
}
var dog1 = new Dog('dog1');
var dog2 = new Dog('dog2');
dog1.bark === dog2.bark //false
以上代码可以看出,bark行为并没有任何差异,但依旧在每次初始化实例时创建了一个新的,没有必要。
而正是由于这个缺陷的存在,单纯基于构造函数的继承无法避免的也会造成空间浪费。父类中某个希望能被通用的方法依然会多次被复制,消耗资源。
function Animal(name){
this.name = name,
this.who = function() {
console.log(name)}; //这个方法被所有实例共享更合适,且节省空间
}
function Dog(name, age){
Animal.call(this, name);
this.age = age;
}
var dog1 =