JS中没有类的概念,所有的类本质都是function。ES5和ES6的继承机制本质上都是依靠原型链,只是ES5要显式地声明,ES6使用语法糖class来表示。
ES5继承方式
要研究OOP中的继承,我们首先要从原型链这一概念讲起。通过原型链,我们可以是实现一定的继承,但是并不完美,因为原型链有两个问题。
- 当原型链中包含引用类型值的原型时,该引用类型的值会被所有实例共享。
- 当创建子类的实例时,我们无法向父类的构造函数中传递参数。
借用构造函数
为了解决这个问题,我们可以使用借助构造函数方法,它的实现原理是在子类的构造函数中调用父类的构造函数。
function Parent(){
this.numbers = [1,2,3];
}
function Child(){
Parent.call(this);
}
var child1 = new Child();
child1.numbers.push(4);
console.log(child1.numbers) // 1,2,3,4
var child2 = new Child();
console.log(child2.numbers) // 1,2,3
显然,借助父类的构造函数我们一举解决了上述两个问题:一是保证了原型链中引用类型的值的独立,二是子类在创建时也能给父类传递参数。但是这种方法也带来了问题,那就是方法是在构造函数中定义的,那么函数复用将变得不可能,父类定义的方法子类无法访问。为了解决这个问题,经典的ES5继承方式组合继承出现了。
组合继承
组合继承的基本思路: 使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承.
// ES5继承 组合继承(应用最广)
function Parent(value){
this.value = value;
}
Parent.prototype.getValue = function(){
console.log(this.value);
}
function Child(value){
Parent.call(this, value); // 继承实例属性,调用父类构造函数
}
Child.prototype = new Parent(); // 继承父类方法,再次调用父类构造函数
这是经典的继承写法,融合了借助构造函数和原型链的优点,但是存在一个问题,那就是Parent类的构造函数被调用了两次。有没有什么办法避免这样呢?答案肯定是有的。
寄生组合式继承
寄生组合式继承就是为了减少调用父类构造函数的开销而出现的。其原理在于不必为了指定子类型的原型而调用超类型的构造函数。
function extend(Child,Parent){
var prototype = object(Parent.prototype);//创建对象
prototype.constructor = Child;//增强对象
Child.prototype = prototype;//指定对象
}
ES6继承方式
ES6继承的实现方法只有一种标准模式,这比ES5更加清晰明了。首先,我们需要为父类和子类添加class关键字,虽然这只是一个语法糖。另外,extends关键字表明了父类和子类之间的继承关系。另一个特殊点就是constructor和super,子类必须在其构造函数中使用super方法。因为子类没有自己的this对象, 子类通过super方法获得父类的this对象。
class Animal{
constructor(props){
this.name = props.name || 'unknown';
}
eat(){
console.log(this.name + " will eat pests");
}
}
class Bird extends Animal{
constructor(props, myProps){
super(props);
this.type = props.type || 'unknown';
this.attr = myProps;
}
fly(){
console.log(this.name + " can fly!");
}
show(){
console.log(this.type + "..." + this.attr);
}
}
var bird = new Bird({
name: "Jack",
type: "小麻雀"
}, "I am a bird.");
bird.eat();
bird.fly();
bird.show();
// Jack will eat pests
// Jack can fly!
// 小麻雀...I am a bird