ES6继承

逃离我推掉我的手 2021-09-03 08:58 559阅读 0赞

class通过extends关键字继承

写法如下

  1. calss People {
  2. }
  3. class Man extends People{
  4. constructor(name,age,eat){
  5. super(name,age)
  6. this.eat = eat
  7. }
  8. showEat(){
  9. return this.eat + ' ' + super.toString()
  10. //调用父类的toString方法
  11. }
  12. }
  13. 复制代码

上面代码定义了一个Man类,该类通过extends关键字,继承了People类的所有属性和方法。

super关键字,表示父类的构造函数,用来新建父类的this对象,子类必须在constructor方法中调用super方法,否则新建实例时会报错,因为子类自己的this对象,必须通过父类的构造函数才能塑造,得到与父类相同的实例属性和方法,然后在进行加工,加上子类自己的实例属性和方法,如果不调用super方法,子类就得不到this对象

  1. class People {
  2. }
  3. class Man extends People{
  4. constructor(){
  5. }
  6. }
  7. let m1 = new Man()
  8. 复制代码

45aabeb161e9701dbbdde0382efccf2b.png

如果子类没有定义constructor方法,这个方法会被默认添加

  1. class ColorPoint extends Point {
  2. }
  3. // 等同于
  4. class ColorPoint extends Point {
  5. constructor(...args) {
  6. super(...args);
  7. }
  8. }
  9. 复制代码

在子类的构造函数中,调用super之后,才可以使用this关键字,

  1. class People {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. }
  6. class ColorPoint extends Point {
  7. constructor(name,age) {
  8. this.age = age; // ReferenceError
  9. super(name);
  10. this.age = age; // 正确
  11. }
  12. }
  13. 复制代码

生成实例后

  1. let m1 = new Man('yr', 18);
  2. m1 instanceof Man // true
  3. m1 instanceof People // true
  4. 复制代码

m1同时是Man和People两个类的实例,与ES5的一致 父类的静态方法,也会被子类继承

  1. class A {
  2. static hello() {
  3. console.log('hello world');
  4. }
  5. }
  6. class B extends A {
  7. }
  8. B.hello() // hello world
  9. 复制代码

Object.getPrototypeOf

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

  1. Object.getPrototypeOf(ColorPoint) === Point
  2. 复制代码

可以使用这个方法判断,一个类是否继承了另一个类。

super关键字

super关键字,可以当函数使用,也可以当对象使用 第一种当函数调用时,代表父类的构造函数,ES6要求,子类的构造函数中必须执行一次super函数

  1. class A {}
  2. class B extends A {
  3. constructor() {
  4. super();
  5. }
  6. }
  7. 复制代码

子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错

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

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

第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

  1. class A {
  2. p() {
  3. return 2;
  4. }
  5. }
  6. class B extends A {
  7. constructor() {
  8. super();
  9. console.log(super.p()); // 2
  10. }
  11. }
  12. let b = new B();
  13. 复制代码

子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()

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

  1. class A {
  2. constructor() {
  3. this.p = 2;
  4. }
  5. }
  6. class B extends A {
  7. get m() {
  8. return super.p;
  9. }
  10. }
  11. let b = new B();
  12. b.m // undefined
  13. 复制代码

p是父类A实例的属性,super.p就引用不到它,如果属性定义在父类的原型对象上,super就可以取到。

  1. class A {}
  2. A.prototype.x = 2;
  3. class B extends A {
  4. constructor() {
  5. super();
  6. console.log(super.x) // 2
  7. }
  8. }
  9. let b = new B();
  10. 复制代码

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

  1. class A {
  2. constructor() {
  3. this.x = 1;
  4. }
  5. print() {
  6. console.log(this.x);
  7. }
  8. }
  9. class B extends A {
  10. constructor() {
  11. super();
  12. this.x = 2;
  13. }
  14. m() {
  15. super.print();
  16. }
  17. }
  18. let b = new B();
  19. b.m() // 2
  20. 复制代码

super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)

由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

  1. class A {
  2. constructor() {
  3. this.x = 1;
  4. }
  5. }
  6. class B extends A {
  7. constructor() {
  8. super();
  9. this.x = 2;
  10. super.x = 3;
  11. console.log(super.x); // undefined
  12. console.log(this.x); // 3
  13. }
  14. }
  15. let b = new B();
  16. 复制代码

super.x赋值为3,等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined

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

  1. class Parent {
  2. static myMethod(msg) {
  3. console.log('static', msg);
  4. }
  5. myMethod(msg) {
  6. console.log('instance', msg);
  7. }
  8. }
  9. class Child extends Parent {
  10. static myMethod(msg) {
  11. super.myMethod(msg);
  12. }
  13. myMethod(msg) {
  14. super.myMethod(msg);
  15. }
  16. }
  17. Child.myMethod(1); // static 1
  18. var child = new Child();
  19. child.myMethod(2); // instance 2
  20. 复制代码

super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。

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

  1. class A {
  2. constructor() {
  3. this.x = 1;
  4. }
  5. static print() {
  6. console.log(this.x);
  7. }
  8. }
  9. class B extends A {
  10. constructor() {
  11. super();
  12. this.x = 2;
  13. }
  14. static m() {
  15. super.print();
  16. }
  17. }
  18. B.x = 3;
  19. B.m() // 3
  20. 复制代码

静态方法B.m里面,super.print指向父类的静态方法。这个方法里面的this指向的是B,而不是B的实例。

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

  1. console.log(super); // 报错
  2. 复制代码

类的prototype属性和__proto__属性

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

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类 (2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

  1. class A {
  2. }
  3. class B extends A {
  4. }
  5. B.__proto__ === A // true
  6. B.prototype.__proto__ === A.prototype // true
  7. 复制代码

子类B__proto__属性指向父类A,子类Bprototype属性的__proto__属性指向父类A的prototype属性。

类的继承是按照下面的模式实现的

  1. class A {
  2. }
  3. class B {
  4. }
  5. // B 的实例继承 A 的实例
  6. Object.setPrototypeOf(B.prototype, A.prototype);
  7. // B 继承 A 的静态属性
  8. Object.setPrototypeOf(B, A);
  9. const b = new B();
  10. 复制代码

Object.setPrototypeOf

  1. Object.setPrototypeOf = function (obj, proto) {
  2. obj.__proto__ = proto;
  3. return obj;
  4. }
  5. 复制代码

得到

  1. Object.setPrototypeOf(B.prototype, A.prototype);
  2. // 等同于
  3. B.prototype.__proto__ = A.prototype;
  4. Object.setPrototypeOf(B, A);
  5. // 等同于
  6. B.__proto__ = A;
  7. 复制代码

作为一个对象,子类(B)的原型__proto__属性是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。

第一种,子类继承Object类。

  1. class A extends Object {
  2. }
  3. A.__proto__ === Object // true
  4. A.prototype.__proto__ === Object.prototype // true
  5. 复制代码

A其实就是构造函数Object的复制,A的实例就是Object的实例

第二种情况,不存在任何继承

  1. class A {
  2. }
  3. A.__proto__ === Function.prototype // true
  4. A.prototype.__proto__ === Object.prototype // true
  5. 复制代码

A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承 Function.prototype但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数Objectprototype属性

实例的__proto__属性

子类实例的_proto__性的_proto__性,指向父类实例的_proto__性。也就是说,子类的原型的原型,是父类的原型。

  1. var m1 = new People('yr');
  2. var m2 = new Man('yr',18);
  3. m2.__proto__ === m1.__proto__ // false
  4. m2.__proto__.__proto__ === m1.__proto__ // true
  5. 复制代码

ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上

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

发表评论

表情:
评论列表 (有 0 条评论,559人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ES5和ES6中的继承

    Javascript中的继承一直是个比较麻烦的问题,prototype、constructor、\_\_proto\_\_在构造函数,实例和原型之间有的复杂的关系,不仔细捋下很

    相关 ES5和ES6继承

    ES5常用七种继承方案和ES6的类继承,有八种继承方案。 ES5: 原型链继承、借用构造函数继承、组合继承、 原型式继承、寄生式继承、寄生组合式继承、 混入方式继承