ES6的学习笔记(十三)Class的继承。。。

147 阅读5分钟

Class的继承

1.简介

Class通过extends关键字实现继承

class Shape{}
class Rect extends Shape{}
// Rect 类继承了Shape类的所有属性和方法。
class Animal {
    constructor(age) {
        this.age = age
    }
    toString(){
        return `Animal`
    }
}
class Duck extends Animal {
    constructor(age, color) {
        super(age)
        this.color = color
    }
    toString(){
        return super.toString()+' duck'
    }
}
// super:表示父类的构造函数,用来新建父类的this对象。
// 子类必须在constructor方法中调用super方法,否则新建实例时会报错。
/*
原因:子类自己的this对象,必须先通过父类的构造函数完成塑造,
得到与父类同样的实例和方法,然后再加工,加上子类自己的实例属性和方法。
不调用super方法,子类就得不到this对象。
*/
// 如果子类没有定义constructor方法,constructor方法则会默认添加。
// 在子类的constructor函数中,只有调用了super之后,才可以使用this关键字。
class Animal {
    constructor(age,weight) {
        this.age = age
        this.weight = weight
    }
    toString(){
        return `Animal`
    }
}
class Duck extends Animal {
    constructor(age,weight, color) {
        this.color = color
        super(age,weight)
    }
    toString(){
        return super.toString()+' duck'
    }
}
// let whiteDuck = new Duck(3,20,'white')  //报错。

// 子类可以继承父类的静态方法。
class Person {
    static sayName(){
        console.log("person")
    }
}
class Student extends Person {

}
Student.sayName() // person

2.Object.getPrototypeOf()

此方法可以用来获取一个子类的父类。

Object.getPrototypeOf(Student) // ....

3.super关键字

super关键字可以当函数使用,也可以当对象使用。

super作为函数调用

此时,代表父类的构造函数,子类的构造函数必须执行一次super函数。

class A {}
class B extends A{
    constructor(){
        super()
    }
}

super代表父类A的构造函数,但返回的是子类B的实例,即super内部的this指向的是B的实例,故在此处,super()相当于A.prototype.constructor.call(this)

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B
// new.target指向当前正在执行的构造函数。

作为函数时,super()只能用在子类的构造函数之中。

class A { }
class B extends A {
    m() {
        super() // 报错
    }
}

super作为对象调用

作为对象时,在普通方法中使用,指向父类的原型对象;在静态方法中使用,指向父类。

class Person {
    say(name){
        console.log("hello "+name)
    }
}
class Student extends Person{
    constructor(name){
        super()
        super.say(name)
    }
}
let tony = new Student('Tony') // hello Tony
// 此时,super.say(),就相当于A.prototype.say()
// 此时,定义在父类实例上的方法和属性是无法通过super调用的。

ES6规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前子类实例。

class Person {
    constructor(){
        this.name = 'unknown'
    }
    printName(){
        console.log(this.name)
    }
}
class Student extends Person {
    constructor(){
        super()
        this.name = 'student'
    }
    pri(){
        super.printName()
    }
}
let tony = new Student()
tony.pri() // student
// super.printName(),调用的是Person.prototype.printName(),但是其内部的this指向子类的实例,输出student。相当于super.printName.call(this)

// 因this指向子类实例,所以通过super对某个属性赋值时,这时super就是this,赋值的属性会变成子类实例的属性。
class A {
    constructor(){
        this.x =1
    }
}
class B extends A {
    constructor(){
        super()
        this.x = 2
        super.x = 3
        console.log(super.x) // undefined
        console.log(this.x) // 3
    }
}
let c = new B() 

super作为对象,用在静态方法中,此时super将指向父类,而不是父类的原型对象。

 class Parent {
    static parentMethod(){
        console.log("static")
    }
    parentMethod(){
        console.log("no static")
    }
}
class Son extends Parent {
    static sonMethod(){
        super.parentMethod()
    }
    sonMethod(){
        super.parentMethod()
    }
}
Son.sonMethod() // static
let son = new Son()
son.sonMethod() // no static 

在子类的静态方法中通过super调用父类的方法时,父类方法内部的this指向当前的子类,而不是子类的实例。

class A {
   constructor(){
       this.x = 1
   }
   static pri(){
       console.log(this.x)
   }
}
class B extends A {
   constructor(){
       super()
       this.x = 444
    }
    static pri (){
        super.pri()
    }
}
B.x = 2
B.pri() // 2
// 在上面代码中,B的静态方法pri中,super.pri指向父类的静态方法。此方法中的this指向类B,而不是B的实例。

使用super的时候,必须显示指定是作为函数还是对象使用,否则会报错。

class Person {
           
}
class Student extends Person {
   constructor(){
       super()
       console.log(super) // 报错。
   }
}

对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。

let obj ={
	toString(){
    	return "MyObject"+ super.toString()
    }
}
obj.toString()

4.类的prototype属性和__proto__属性

大多数浏览器的ES5实现时,每个对象都有一个__proto__属性,指向对应的构造函数的prototype属性。
而Class作为构造函数的语法糖,同时有prototype和__proto__两个属性,故同时存在两条继承链。

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类
  • 子类的prototype属性的__proto__属性,表示方法的继承,指向父类的prototype属性。
class Person {}
class Student extends Person {
   constructor(){
       super()
   }
}
console.log(Student.__proto__) // class Person {}
console.log(Student.prototype.__proto__ === Person.prototype) // true

extends 关键字后可以跟多种类型的值。

class B extends A {}
// A只要是一个具有prototype属性的函数,都可以被B继承。函数都具有prototype属性(Function.prototype函数除外),故A可以是任意函数。
// 讨论两种情况
// 1.子类继承Object类
class A extends Object{}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
// 这种情况,A就是构造函数Object的复制,A的实例就是Object的实例。

// 2.不存在任何继承
class A {}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
// 这种情况下,A作为一个基类(不存在任何继承),就是一个普通函数,即直接继承Function.prototype。但是,A调用后返回一个空对象(即Object实例),故A.prototype.__proto__指向构造函数(Object)的prototype属性。

实例的__proto__属性

子类实例的__proto__属性的__proto__属性指向父类市里的__proto__属性。

class  Point{
 constructor(x,y){
  this.x = x
  this.y =y
 }
}
class ColorPoint extends Point{
 constructor(x,y,color){
     super(x,y)
     this.color = color
 }
}
let p1 = new Point(2,3)
let p2 = new ColorPoint(2,4,'blue')
//    ColorPoint.prototype.__proto__ === Point.prototype
console.log(p2.__proto__.__proto__ === p1.__proto__) // true

// 故,通过子类实例,可以修改父类实例的属性和方法。
p2.__proto__.__proto__.pri = () => {
   console.log("object")
}
p1.pri() // object