类-继承

159 阅读4分钟

一、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.prototypeAnimal实例化后,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);

这种继承有个问题:无法继承父类原型上的属性和方法,如果单独使用,所有要继承的属性和方法都要写在父类的构造函数中。特别是实例共享的属性和方法也写在构造函数里,那么这样会浪费内存,所以很少单独使用。

组合继承

原型链式继承和Call()/Apply()方式继承,利用原型链继承共有的属性和方法,利用Call/Apply来初始化自己的但是和父类型同名的属性或方法。
    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入门