一、ES5继承
原型继承
JS里的继承主要依靠是的原型链。让原型对象(每一个构造函数都有一个原型对象)的值,等于另一个类型的实例,即实现了继承;另外一个类型的原型再指向第三个类型的实例,以此类推,也就形成了一个原型链。
function Animal(newName,newAge){
this.name = newName;
this.age = newAge; }
Animal.prototype.eat = function(str){
console.log(this.name + "吃" + str);
}
function Person(newId){
this.id = newId;
}
//new和构造函数创建了一个实例化对象,该实例对象的内部属性_proto_,指向该构造函数的原型对象Animal.prototype.
因此,Person.prototype将Animal实例化后,Person.prototype的_proto_就指向了Animal.prototype原型对象。
Person.prototype=new Animal("老王",18)
let p1 = new Person("007");
console.log(p1.name,p1.age,p1.id);
call和apply的继承
function Person(newId,newName){
this.id = newId;
this.name = newName;
}
function Student(newId,newName,newScore){
//借用构造方法(this改变指向, 改变为由 this指定的新对象即Student的对象)
Person.call(this,newId,newName);
this.score = newScore;}
let student = new Student("007","老王",99);
console.log(student.id,student.name,student.score);
这种继承有个问题:无法继承父类原型上的属性和方法,如果单独使用,所有要继承的属性和方法都要写在父类的构造函数中。特别是实例共享的属性和方法也写在构造函数里,那么这样会浪费内存,所以很少单独使用。
组合继承
function Person(newId,newName){
this.id = newId; this.name = newName;
}
Person.prototype.eat = function(){
console.log("Person eat"); }
function Student(newId,newName,newScore){
Person.call(this,newId,newName);
this.score = newScore; }
Student.prototype = new Person();
let student = new Student("007","老王",99);
console.log(student.id,student.name,student.score);
student.eat();
二、ES6继承(calss继承)
class Person{
constructor(newId,newName) {
this.id = newId;
this.name = newName;
}
showValue(){
console.log(this.id,this.name);
}
}
class Student extends Person{
constructor(newId,newName,newScore){
//该行代码必须写在子类构造方法的第一行;调用父构造函数(仅在 constructor 函数中)
super(newId,newName); //super()负责初始化this(改变this指向,代表父类).就相当于ES5中的call和apply方法。
this.score = newScore;
}
eat(){
console.log("student eat");
}
showValue(){
super.showValue();
console.log(s.score);
}
}
let s = new Student(1,"老王",99);
s.showValue();
s.eat();
ES6使用class关键字来写类。子类Student继承使用extends关键词,来继承父类Person的所有属性和方法。
ES5 的继承,实质是先创造子类的实例对象
this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
另一个需要注意的地方是,在子类的构造函数中,只有调用
super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
super关键字
super关键字。既可以当函数使用,也可以对象使用。
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class A {}
class B extends A {
constructor() {
super();
}
}
子类B中的构造函数之中的super(),代表父类的构造函数。注意。super虽然代表了父类A的构造函数。但是返回的是子类B的实例,即super内部的this指向B的实例。因此。super在这里相当于A.prototype.constructor.call(this)。
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
第二种情况。super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p())
}
}
let b = new B();
上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。super在普通方法中,指向A.prototype。所以super.p()相当于A.prototype.p()。
由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
ES6 规定,在子类普通方法中通过**super**调用父类的方法时,方法内部的**this**指向当前的子类实例。
如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
上面代码中,super在静态方法中指向父类。在普通方法中指向父类的原型对象。
另外,在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
上面代码中,静态方法B.m()中,super.print指向父类的的静态方法。这个方法里面this指向的是B,而不是B的实例。
参考资料1:原型继承
参考资料2:阮一峰es6入门