一、继承的概念
继承
,是面向对象语言的一个重要概念。
通常有这两种继承方式:接口继承
和实现继承
。接口继承只继承方法签名,而实现继承则继承实际的方法。
《JS高程》里提到:“由于函数没有签名,在‘ECMAScript’中无法实现接口继承。”
等等,函数签名是什么东西?据MDN文档定义如下:
函数签名(类型签名、方法签名)定义了函数或方法的输入与输出。
签名可包含以下内容:
- 参数 及参数的 类型
- 一个的返回值及其类型
- 可能会抛出或传回的异常
- 该方法在 面向对象程序中的可用性方面的信息(如public、static或prototype)。
看起来好复杂啊,我们换成强类型的语言的角度会不会更好理解?
譬如,C的函数签名就是我们熟悉的函数声明:
int func(double d);
此时:参数名为d
,参数类型为double
,返回值为func(d)
,返回值类型为int
。
再如,Java的函数签名:
public static void main(String[] args)
此时:参数名为args
,参数类型为String []
,返回值类型为void
所以该方法没有返回值,访问修饰符public
表示该方法是公有方法,static
表示该方法是一个类方法而非实例方法……
现在,我们知道函数签名是怎么回事了。那么,接口继承又是什么东西?相信大家会遥想起Java中的interface
,implements
等……在此就不班门弄斧了。
我们知道,JavaScript是类型松散
的语言,不像Java它们有严格的变量类型检查。所以,JS的函数才没有签名,才无法实现接口继承。
那么,JS的实现继承是怎么回事呢?
二、JS的继承
-
原型链的基本模式
理解:通过创建SuperType
的实例,并将该实例赋给SubType.prototype
,来实现SubType
继承SuperType
。instance
的原型指向SubType
,SubType
的原型指向SuperType
,SuperType
的原型指向Object
,如此构成了原型链。
缺点:对象实例共享所有继承的属性和方法,不适宜单独适用function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } //SubType继承SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
于是,有下一个招式,来解决包含引用类型值的原型属性会被所有实例共享的弱点。
-
借用构造函数
理解:“借用”超类型构造方法,在新的子类型对象上执行超类型函数定义的所有对象初始化代码
适用:解决超类型的引用类型值被所有子类型对象实例共享,而且子类型可向超类型传参
缺点:不能做到函数复用,从而降低效率,不适宜单独适用function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; //引用类型值 } function SubType() { SuperType.call(this, "A"); //继承SuperType “借用”超类型的构造函数 并传参 this.age = 20; //实例属性 } var instance1 = new SubType(); //instance1.name: "A", instance1.age: 20 instance1.colors.push("black"); //instance1.colors: "red, blue, green, black" var instance2 = new SubType(); //instance2.colors: "red, blue, green"
接下来,组合技能出大招!
-
组合继承
理解:将原型链和借用构造函数组合到一起,通过原型链来继承共享的原型属性和方法,通过借用构造函数来继承实例属性。
适用:最常用的继承模式。
缺点:调用两次超类型构造函数,在SubType上创建了多余的属性,造成超类型对象的实例属性的重写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); //继承属性 第二次调用超类型构造函数 新实例得到两个实例属性name,colors this.age = age; } SubType.prototype = new SuperType(); //继承方法 第一次调用超类型构造函数 SubType.prototype得到两个实例属性name,colors SubType.prototype.sayAge = function() { alert(this.age); }; var instance1 = new SubType("妹妹", 18); instance1.colors.push("black"); alert(instance1.colors); //"red, blue, green, black" instance1.sayName(); //"妹妹" instance1.sayAge(); //18 var instance2 = new SubType("弟弟", 20); alert(instance2.colors); //"red, blue, green" instance2.sayName(); //"弟弟" instance2.sayAge(); //20
这是打boss的大招,那我怎么对付小怪?
-
原型式继承
理解:本质是对给定对象的浅复制
适用:不必预先定义构造函数来实现继承,只想让一个对象与另一个对象保持类似
缺点:引用类型值的属性被共享,如同原型模式一样function object(o) { //对o进行浅复制 function F() {} //创建一个临时性的构造函数 F.prototype = o; //将传入的对象o作为F的原型 return new F(); //返回F的新实例 } var person = { name: "A", friends: ["B", "C", "D"] }; var anotherPerson = object(person); // Object.create(person) anotherPerson.name = "E"; anotherPerson.friends.push("F"); var yetAnotherPerson = object(person); //Object.create(person) yetAnotherPerson.name = "G"; yetAnotherPerson.friends.push("H"); alert(person.friends); //"B, C, D, F, H" 引用类型值的属性被共享啦
注意:
Object.create()
方法的第二个参数,新对象的额外属性的对象,会覆盖原型对象上的同名属性。var anotherPerson = Object.create(person, { name: { value: "E" } }); alert(anotherPerson.name); // "E"
打了这么久,能不能让小招也升级(封装)一下啊?
-
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
理解:继承的工作是通过调用函数实现的,所以是“寄生”,将继承工作寄托给别人做,自己只是做增强工作。
适用:基于某个对象或某些信息来创建对象,而不考虑自定义类型和构造函数。
缺点:不能做到函数复用,从而降低效率function createAnother(original) { var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function() { //以某种方式来增强这个对象 alert("hi"); } return clone; } var person = { name: "A", friends: ["B", "C", "D"] }; var anotherPerson = createAnother(person); //不仅有person所有属性方法,还有自己的sayHi方法 anotherPerson.sayHi(); //"hi"
经验攒足,我要把之前打boos的组合大招升到满级!
-
寄生组合继承
理解:通过借用构造函数来继承属性,通过原型链的混用形式来继承方法。用寄生式继承来继承超类型的原型,再将增强后的结果指定给子类型的原型。
适用:引用类型最理想的继承模式,高效,只调用了一个SuperType构造函数function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); //创建对象 超类型原型的副本 prototype.constructor = subType; //增强对象 弥补因重写原型而失去默认的constructor属性 subType.prototype = prototype; //指定对象 } 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; } inheritPrototype(SubType, SuperType); //继承方法 SubType.prototype.sayAge = function() { alert(this.age); };
三、ES6的继承
extends
关键字用来创建一个普通类或者内建对象的子类。
class A {
...
}
class B extends A {
...
}
其中,
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
因为extends
实现了:
Object.setPrototypeOf(B.prototype, A.prototype); //B.prototype.__proto__ = A.prototype;
Object.setPrototypeOf(B, A); //B.__proto__ = A
extends
更具体的实现方法参见面试官问:JS的继承,在此就不班门弄斧了~
完~若有不足,请多指教,不胜感激!
以上代码借鉴于《JS高级程序设计》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。