Class的继承

Class的继承

简介

Class可以通过extends关键字实现继承,这比es5通过修改原型链实现继承,要清晰和方便许多

class Point {
}

class ColorPoint extends Point {
}	//继承了Point类的所有属性和方法
//由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}
//两个方法中都出现了super关键字,在这里表示父类的构造函数,用来新建父类的this对象

子类必须在constructor方法中调用super方法,否则新建实例的时候会报错

这是因为子类自己的this对象,必须通过父类的构造函数完成塑造,得到于父类同样的实例属性和方法,然后对其进行加工,加上子类自己的实例属性和方法

如果不调用super方法,子类就不能得到this对象

class Point { /* ... */ }
class ColorPoint extends Point {
  constructor() {
  }
}

let cp = new ColorPoint(); // ReferenceError
//子类没有调用super方法,导致新建实例时报错

ES5和ES6继承方式的不同

ES5:先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this)

ES6:先将父亲实例对象的属性和方法,添加到this上面(所以必须先调用super方法),然后再用子类构造函数修改this

如果子类没有定义constructor方法,这个方法会被默认添加,不管有没有显示定义,任何一个子类都有constructor方法

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}

注意:只有调用了super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}

父类的静态方法,也会被子类继承

class A {
  static hello() {
    console.log('hello world');
  }
}

class B extends A {
}

B.hello()  // hello world
Object.getPrototypeOf( )

Object.getPrototypeOf方法可以用来从子类上获取父类

判断一个类是否继承了另一个类

Object.getPrototypeOf(ColorPoint) === Point
// true
super关键字

super这个关键字,可以当作函数使用,也可以当作对象使用

第一种情况:函数

super当作函数调用时,代表父类的构造函数

ES6规定,子类的构造函数必须执行一次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() // Anew B() // B//在super执行时,它指向的是子类B的构造函数,而不是父类A的构造函数//也就是说,super内部的this指向的是B

作为函数时,super只能用在子类的构造函数之中,用在其他地方就会报错

class A {}class B extends A {  m() {    super(); // 报错,用在了B子类的m方法中  }}
第二种情况:对象

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

class A {  p() {    return 2;  }}class B extends A {  constructor() {    super();    console.log(super.p()); // 2    //此处的super.p()就是将super当成一个对象使用,在普通方法中,就指向A.prototype,所以super.p()就相当于A.prototype.p()  }}let b = new B();

注意:由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的

class A {  constructor() {    this.p = 2;		//此处p的父类A实例的属性  }}class B extends A {  get m() {    return super.p;		//引用不到A中实例的属性  }}let b = new B();b.m // undefined

如果属性定义在父类的原型对象上,super就可以取到

class A {}A.prototype.x = 2;	//x是定义在A.prototype上面的class B extends A {  constructor() {    super();    console.log(super.x) // 2  }}let b = new B();

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

class A {  constructor() {    this.x = 1;  }  print() {    console.log(this.x);  }}class B extends A {  constructor() {    super();    this.x = 2;  }  m() {    super.print();	//调用了父类的方法,也就是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例  }}let b = new B();b.m() // 2

由于this指向子类实例,所以如果通过super对某个属性赋值,这时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调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例

注意:在使用super时,必须显式指定时作为函数还是对象,否则会报错

类的prototype属性和__proto__属性

大多数浏览器的ES5实现之中,每一个对象都有__proto__属性,之相对应的构造函数的prototype属性

Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类
  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
class A {}class B extends A {}B.__proto__ === A // true	B.prototype.__proto__ === A.prototype // true

这两条继承链可以这样理解:作为一个对象,子类(B)的原型(__proto__属性)是父类(A),作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例

B.prototype = Object.create(A.prototype);// 等同于B.prototype.__proto__ = A.prototype;
原型链,prototype和__proto__的区别

JavaScript中所有的对象都是Object的实例,并继承了Object.prototype的属性和方法,也就是说,Object.prototype是所有对象的爸爸

在对象创建时,就会有一些预定义的属性,其中定义函数的时候,这个预定义属性就是prototype,这个prototype是一个普通的对象

定义普通的对象的时候,就会生成一个__proto__,这个__proto__指向的是这个对象的构造函数的prototype

原型链:对象都有一个toString方法,实例化对象b也可以toString

而实例化对象b本身并没有toString的方法,那他就会沿着它的__proto__向他的构造函数B的prototype对象去找,而这里也没有,那他就会 继续沿着B.prototype.__proto__向上找。而B.prototype.__proto__指向的就是Object.prototype

extends关键字后面可以跟多种类型的值,只要是一个prototype属性的函数,就能被继承,由于函数都有prototype属性(除了Function.prototype函数),所以可以是任意函数

两种情况:

  • 子类继承Object类

    class A extends Object {}A.__proto__ === Object // trueA.prototype.__proto__ === Object.prototype // true
    
  • 不存在任何继承

    class A {}A.__proto__ === Function.prototype // trueA.prototype.__proto__ === Object.prototype // true
    
实例的__proto__属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性

也就是说,子类的原型的原型,是父类的原型

var p1 = new Point(2, 3);var p2 = new ColorPoint(2, 3, 'red');p2.__proto__ === p1.__proto__ // falsep2.__proto__.__proto__ === p1.__proto__ // true

通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为

p2.__proto__.__proto__.printName = function () {  console.log('Ha');};p1.printName() // "Ha"
原生构造函数的继承

ES6允许继承原生构造定义子类,因为ES6是新建父类实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承

class MyArray extends Array {  constructor(...args) {    super(...args);  }}var arr = new MyArray();arr[0] = 12;arr.length // 1arr.length = 0;arr[0] // undefined
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值