Class 的继承

本文详细介绍了ES6中类的使用和继承机制,对比ES5的原型链继承,ES6的class和extends提供了更简洁的方式。文章深入解析super关键字的不同用途,以及在构造函数和普通方法中的使用规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面的话

es5之前的继承大多以原型链继承为主,但原型链继承的缺点很大。es6引入的类的继承方便的了不少。

es5模拟继承

es5 实现继承需要多个步骤:

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function () {
    return this.length * this.width;
}
function Square(length) {
    Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value: Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});
var square = new Square(3);
console.log(square.getArea());// 9
console.log(square instanceof Square);// true
console.log(square instanceof Rectangle);// true

Square 继承了Rectangle,为了这样做,必须用一个Object.create()创建一个原型为Rectangle.prototype 的对象来重写Square.prototype,并且要调用Rectangle.call()方法,改变this的指向。

super关键字

es6中的类让我们很轻松的实现继承的功能,使用熟悉的extends关键字可以指定类继承的函数。原型会自动调整,通过调用super()方法即可访问基类的构造函数。

super关键字既可以当作函数使用,也可以当作对象使用。这两种情况下,它的用法完全不同。

[作为函数调用]

作为函数调用时代表父类的构造函数。

    class Rectangle {
        constructor (length, width) {
            this.length = length;
            this.width = width;
        }
        getArea() {
            return this.length * this.width;
        }
    }
    class Square extends Rectangle {
        constructor (length) {
            // 与Rectangle.call(this, length, length)相同
            super(length, length);
        }
    }
    var square = new Square(3);
    console.log(square instanceof Square);// true
    console.log(square instanceof Rectangle);// true

Square类通过extends关键字继承Rectangle类,在Square构造函数中通过super()调用Rectangle构造函数,并传入相应的参数。

这里子类Square的构造函数中的super()代表调用父类的构造函数,但super内部的this指向的是Square。 因此super()在这里相当于 Square.prototype.constructor.call(this).

[注意]

  • 继承自其他类的派生类(子类)在constructor方法中必须调用super()方法,否则新建实例会报错。

  • 在子类的构造函数中,只有调用了super之后才可以使用this。

    class Rectangle {
    constructor (length, width) {
        this.length = length;
        this.width = width;
    }
    getArea () {
        return this.length * this.width;
    }
    }
    class Square extends Rectangle {
    constructor(length){
       
        // this.length = length;  // 报错
        super(length,length); 
        this.length = length; 
    
    }
    }
    let square = new Square(5);
    console.log(square.length);// 5
    console.log(square.getArea());// 25
    

    原因是因为子类没有自己的this对象,而是继承的父类的this对象,然后对其加工。如果不调用super()方法, 子类就得不到this对象。

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

        class Square extends Rectangle {
            // 没有构造器
        }
        // 等价于
        class Square extends Rectangle {
            constructor(...args) {
                super(...args)
            }
        }
    

[作为对象]

  • 在普通方法中指向父类的原型对象
  • 在静态方法中指向父类

在普通方法中:

class A {       
        constructor() {
            this.x =1;
        }
      
        p() {
            return 2;
        }
        print() {
            console.log(this.x);
        }
    }
   A.prototype.y = 3;
    class B extends A {
        constructor() {
            super();
            this.x = 4; 
            console.log(super.p()) ;             
        }
        get m() {
            return super.x;
        }
        get n() {
            return super.y;
        }
        G() {
            super.print();
        }
    }
    let b = new B(); // 2
    console.log(b.m);// undefined
    console.log(b.n);// 3
    b.G();// 4

在创建b实例时,constructor方法内执行super.p(),这里的super为对象,指向A.prototype,相当于A.prototype.p(),所以会输出2。

在调用b.m时,执行super.x,相当于A.prototype.x,而A.prototype没有x,只有实例的x,所有定义在父类实例上的方法与属性是无法通过super调用的。

在调用b.n时,此时A.prototype.y = 3,所以输出3。

在调用b.G()时,返回的是B类实例上的x,说明super调用父类方法时,还是会绑定子类的this。所以输出4。

在静态方法中:

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
    let child = new Child();
    child.myMethod(2);// instance 2

super在静态方法中指向父类。

在执行Child.myMethod(1)时,执行的是子类的静态方法,在静态方法中,super指向的是父类super.myMethod(1),相当于parent.myMethod(1),即执行父类的静态方法,所以输出 ‘static 1’。

在执行child.myMethod(2)时,访问的是子类原型上的方法myMethod,这个方法是一个普通函数,super指向父类的原型,相当于执行父类原型上的myMethod方法,所以输出‘instance 2’。

类的Prototype属性与__proto__属性

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

  • 子类的__proto__属性表示构造函数的继承,总是执行父类
  • 子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype属性
     class Parent {
         constructor(name) {
             this.name = name;
         }
     }
     class Child extends Parent{
         constructor(name, age) {
             super(name);
             this.age = age;
         }
         getName() {
             console.log(this.name);
         }

     }
     let parent = new Parent('xiao')
     let child = new Child('xiaoqi', 20);
     console.log(Child.prototype.__proto__ === Parent.prototype);// true
     console.log(Child.__proto__ === Parent );// true
     console.log(child.__proto__ === Child.prototype);// true
     console.log(child.__proto__.__proto__ === parent.__proto__ );// true

这两条继承链可以这样理解:

  • 作为对象,子类的原型(__proto__属性)是父类

  • 作为构造函数,子类的原型(prototype属性)是父类的实例

       Object.create(Parent.prototype);
       // 等同于
       Child.prototype.__proto__ = Parent.prototype;
    

另外,子类实例的__proto__的__proto__属性指向父类实例的__proto__属性,即子类的原型的原型是父类的原型

原生构造函数的继承

使用es5定义一个继承Array的函数:

    function MyArray() {
        Array.apply(this, arguments);
    }
    MyArray.prototype  = Object.create(Array.prototype, {
        constructor: {
            value: MyArray,
            writable: true,
            configurable: true,
            enumerable: true
        }
    })
    var colors = new MyArray();
    colors[0] = 'red';
    console.log(colors.length);// 0
    colors.length = 0;
    console.log(colors[0]);// 'red'

之所以发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。

在es5中 ,先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。

es6允许继承原生构造函数,因为es6先新建父类的实例对象this,然后在用子类的构造函数修饰this,使得父类的所有行为都得到继承。

    class MyArray extends Array{
        constructor(...args){
            super(...args);
        }
    }
    var arr = new MyArray();
    arr[0] = 12;
    console.log(arr.length);// 1
    arr.length = 0;
    console.log(arr[0]);// undefined
}
获取父类

Object.getPrototypeOf():

使用该方法可以用来从子类上获取父类,即可以判断一个类是否继承了另一个类。

  class Parent {

   }
   class Child extends Parent {

   }
   console.log(Object.getPrototypeOf(Child) === Parent);// true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

crazy的蓝色梦想

如果对你有帮助,就鼓励我一下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值