继承
很多面向对象语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ECMAScritpt中无法实现接口继承。ECMAScript只支持实现继承,而且实现继承主要依靠原型链来实现。
原型链继承
原型链继承主要是让新实例的原型等于父类的实例
优点:实例可继承的属性包括实例构造函数的属性、父类构造函数的属性、父类原型的属性,但不会继承父类实例的属性
缺点:1. 新实例无法向父类构造函数传参,因为与父类构造函数的关系已经断开
2. 继承单一,不能实现多继承
3. 所有新实例都会共享父类实例的属性(原型上的属性是共享的,构造函数中的属性不共享,因为一个实例修改了原型属性, 另一个实例的原型属性也会被修改),这是原型链继承带来的主要问题,也有办法解决。
//定义父类
function SuperType(){
this.value = 'super';
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
//原型链继承
function SubType(){
this.value = 'sub';
this.subproperty = false;
}
SubType.prototype = new SuperType(); //SubType的原型对象等于SuperType的实例
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); //true
console.log(instance instanceof SuperType); //true
借用构造函数继承
借用构造函数也叫伪造对象或经典继承。思路很简单,即在子类型构造函数的内部调用超类型构造函数。通过apply()和call()方法可以在新创建的对象上执行构造函数。这种方法解决了原型链继承中的缺点1(无法向父类构造函数传参)、2、3
优点:1. 只继承了父类构造函数的属性,没有继承父类原型的属性
2. 在子实例中可向父实例传参
3. 可以继承多个构造函数属性(call多个)
缺点: 1. 只能继承父类构造函数的属性
2. 无法实现构造函数的复用
3. 每个新实例都有父类构造函数的副本,内存消耗大
function SuperType(property){
this.property = property;
this.colors = ['red','green','blue'];
}
function subType(){
//继承了SuperType,同时还传递参数
SuperType.call(this, true); //传给property
}
let instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); //'red','green','blue','black'
let instance2 = new SubType();
console.log(instacne2.colors); //'red','green','blue'
console.log(instance2.property); //true
如果借用构造函数,无法避免构造函数模式存在的问题—方法都在构造函数中定义,因此无法实现函数复用。这种方式很少用
组合继承(常用)
组合继承也叫伪经典继承,是将原型链和借用构造函数组合到一块。其技术原理是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
优点:1. 可以继承父类原型上的属性,可以传参,也可以复用
2. 每个新实例引入的构造函数属性是私有的
缺点: 调用了两次父类构造函数(耗内存)(一次在创建子类型原型的时候,一次在子类型构造函数内部),子类构造函数会代替原型上的那个父类构造函数
function SuperType(name){
this.name = name;
this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this,name); //第二次调用SuperType()
this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); 第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
};
let instance1 = new SubType('Simon', 24);
instance1.colors.push('black');
console.log(instance1.colors); //'red','blue','green','black'
instance1.sayName(); //simon
instance1.sayAge(); //24
console.log(instance1 instanceof SuperType); //true
console.log(instance1 instanceof SubType); //true
let instance2 = new SubType('simoner', 23);
console.log(instance2.colors); //'red','blue','green'
instance2.sayName(); //simoner
instance2.sayAge(); //23
console.log(instance2 instanceof SuperType); //true
console.log(instance2 instanceof SubType); //true
组合继承避免了原型链和借用构造函数的缺陷,是JavaScript最常用的继承模式。
原型式继承
用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了一个可以随意添加属性的实例或对象。
优点: 1. 相当于复制了一个对象
缺点: 1. 所有实例都会继承原型上的属性
2. 无法实现复用
//原型式继承
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
let Person = {
name: 'simon',
friends: ['bob','jack','tom'],
};
let anotherPerson1 = object(Person);
anotherPerson1.name = 'simoner';
anotherPerson1.friends.push('jerry');
let anotherPerson2 = object(Person);
anotherPerson2.name = 'alice';
anotherPerson2.friends.push('jan');
console.log(Person.friends); //"bob", "jack", "tom", "jerry", "jan"
ECMAScript5通过新增Object.create()方法来规范化了原型式继承。也就是封装了object()方法
let Person = {
name: 'simon',
friends: ['bob','jack','tom'],
};
let anotherPerson1 = Object.create(Person); //使用Object.create方法
anotherPerson1.name = 'simoner';
anotherPerson1.friends.push('jerry');
let anotherPerson2 = Object.create(Person);
anotherPerson2.name = 'alice';
anotherPerson2.friends.push('jan');
console.log(Person.friends); //"bob", "jack", "tom", "jerry", "jan"
寄生式继承
寄生式继承是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。这种继承方式与原型式继承紧密相关,相当于给原型式继承外面套了一个函数
优点:没有创建自定义类型
缺点:没有用到原型,无法复用
//寄生式继承
function createAnother(originalObj){
let clone = Object.create(originalObj); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强对象
console.log('Hi');
};
return clone; //返回这个对象
}
let Person = {
name: 'simon',
friends: ['bob','jack','tom'],
};
let anotherPerson = createAnother(Person);
anotherPerson.sayHi(); //Hi
寄生组合式继承—常用
寄生组合式继承是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
//寄生组合式继承
function inheritProperty(subType, superType){
let prototype = Object.create(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象,也可以增加其他属性和方法
subType.prototype = prototype; //指定对象
}
function SuperType(name) {
this.name = name;
this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
inheritProperty(SubType,SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
};
let instance = new SubType('simon', 24);
instance.sayName();
instance.sayAge();
ES6中的继承
通过class关键字来创建类,并使用extend关键字来实现类继承
//ES6中的继承
class Person {
// 在constructor()传参
constructor(name,age){
this.name = name;
this.age = age;
}
/*下面的写法就等同于把方法挂在原型上*/
static sayHi(){// 加了static表示静态方法,只给类用的方法
console.log('Hi');
}//方法和方法之间不用加逗号
sayName(){//动态方法,给实例使用的方法
console.log(this.name);
}
}
let person = new Person('simon',24);
person.sayName();
class Simon extends Person {
//一般把子类属性放在前面,父类属性放后面并用...arg代替,这样就直接继承父类的所有属性
constructor(job, ...arg){
super(...arg); //等同于调用父类,把多余的参数(和父类一样的属性)放到super中,达到继承父类属性的目的
//在继承里,写constructor必须写super
//super下面才能使用this,super有暂存死区(super上面不能使用this,用了就报错)
this.job = job;
}
running(){
console.log('I am running');
}
sayJob(){
console.log(this,job);
}
}
let simon = new Simon('FontEnd','simoner',24);
simon.sayName(); //simoner
simon.running(); // I am running
simon.sayJob(); //FontEnd