对于面向对象类的语言而言,继承是基础实现之一,也是编程过程中讨论的较多的话题。常见的继承方式分为两种:接口继承和实现继承。前者继承方法签名,后者继承实际方法。由于在JavaScript
中没有签名,实现继承称为JavaScript
中的唯一继承方法,而JavaScript
中的继承也是通过原型链来实现的。更多有关原型与原型链的知识,请阅读《JS 的原型与原型链》,这里就不在重复赘述。
原型与继承
原型与实例的关系可以通过两种方式来确定。第一种方式是通过instanceof
操作符,若一个实例的原型链中出现对应的构造函数,则instanceof
返回true
。如下实例所示:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType() {
this.subproperty = false;
}
// 通过将SubType的 prototype指向SuperType实例,从而实现继承SuperType的属性和方法
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
}
let instance = new SubType();
console.log(instance.getSuperValue()); // true
原型式继承
通过原型来实现JS的继承,但这种方式存在的缺点就是多个实例共用属性的问题,如下为示例:
function Person(name, age) {
this.name = [name]
this.age = age
}
Person.prototype.fun = function () {
console.log(this.name);
}
function Son(sex) {
this.sex = sex
}
Son.prototype = new Person('zs', 14);//这样设置导致s1和s2都是一样的name和age,肯定不合理
//不写也不影响,但是原型链有个规则,构造函数的原型.constructor指向构造函数,如果不写会发现Son.prototype.constructor;//Person
Son.prototype.constructor = Son;
let s1 = new Son('男')
let s2 = new Son('女')
s1.name.push('科比')//子类改变父类的引用类型
//导致了s2一起改变
console.log(s1.name);// ['zs', '科比']
console.log(s2.name);// ['zs', '科比']
s1.fun()// ['zs', '科比']
s2.fun()// ['zs', '科比']
// 所以这样的继承,有缺点,不实用
盗用构造函数
盗用构造函数又被称之为对象伪装或者是经典继承,是一种使用call
或者bind
方式调用父类构造函数,从而避免prototype
继承导致的prototype
属性在多个实例之间共有的问题。其实现如下:
function Person () {
this.name = {
firstName: 'San',
lastName: 'Zhang'
};
this.age = 20;
this.sayName = function () {
console.log(this.name.lastName, this.name.firstName);
}
}
function Student () {
this.school = 'Tsinghua University';
Person.call(this);
}
let stu1 = new Student();
let stu2 = new Student();
stu1.name.lastName = 'Li'; //改变了stu1对象实例的name属性
console.log(stu1.name, stu1.age, stu1.school);
console.log(stu2.name, stu2.age, stu2.school);
// { firstName: 'San', lastName: 'Li' } 20 Tsinghua University
// { firstName: 'San', lastName: 'Zhang' } 20 Tsinghua University。stu2的name属性并没有改变!
组合继承
组合继承就是将原型链继承和盗用构造函数继承集成在一起,其示例代码如下:
function Person(name, age) {
this.name = [name]
this.age = age
}
Person.prototype.fun = function () {
console.log(this.name);
}
function Son(name, age, sex) {
// 通过在子类Son中调用父类构造函数,实现给每个子类单独设置属性
Person.call(this, name, age)
this.sex = sex
}
//通过原型让子类型继承父类型中的方法
Son.prototype = new Person();
Son.prototype.constructor = Son
let s1 = new Son('哈登', 30, '男')
let s2 = new Son('小哈', 21, '男')
s1.fun()//['哈登']
s2.fun()//['小哈']
寄生式继承
是一种基于原型式的继承方式,它通过创建一个仅用于封装继承过程的函数,该函数在内部调用原型式继承创建一个对象,然后增强该对象,最后返回这个对象。其实就是原型式样继承和工厂模式的实现组合。
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){
//以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
寄生式组合继承
即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。 不必为了指定子类型的原型而调用父类的构造函数,我们所需要的无非就是父类原型的一个副本而已。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function inheritPrototype(subType, superType){
var prototype = Object.create(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);
}
var instance1 = new SubType("jack", 23);
var instance2 = new SubType("rose", 20);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]