【JS核心概念】Class实现继承

一、简单使用

  • Class通过extends关键字实现继承,其实质是先创造出父类的this对象,然后用子类的构造函数修改this
  • 子类的构造方法中必须调用super方法,且只有在调用了super()之后才能使用this,因为子类的this对象是继承父类的this对象,然后对其进行加工,而super方法表示的是父类的构造函数,用来新建父类的this对象
    class Animal {
        constructor(kind) {
            this.kind = kind
        }
        getKind() {
            return this.kind
        }
    }
    
    // 继承Animal
    class Cat extends Animal {
        constructor(name) {
            // 子类的构造方法中必须先调用super方法
            super('cat');
            this.name = name;
        }
        getCatInfo() {
            console.log(this.name + ':' + super.getKind())
        }
    }
    
    const cat1 = new Cat('xiaohei');
    cat1.getCatInfo(); // xiaohei:cat

二、super关键字

从上面的例子中发现,super关键字不仅可以作为函数使用,还可以作为对象使用。在这两种情况下,super的用处也是不一样的。

2.1 super函数

super作为函数使用时,表示父类的构造函数。

不过要注意的是,super虽然代表父类的构造函数,但是返回的是子类的实例。这里的super相当于父类.prototype.constructor.call(子类this);

    class Animal {
        constructor(kind) {
            this.kind = kind
            console.log(this)
        }
    }
    
    class Cat extends Animal {
        constructor(name) {
            super('cat');
            this.name = name;
        }
    }
    
    // 调用super()函数,相当于:
    // Animal.prototype.constrctor.call(this)
    // 最后再返回this
    const cat1 = new Cat('xiaohei'); // Cat { kind: 'cat' }

注意:super作为函数时,只能在子类的构造函数中调用,在其他地方调用会报错。

2.2 super对象
  • 在普通方法中指向父类的原型对象
    class Animal {
        constructor(kind) {
            this.kind = kind
            console.log(this)
        }
        getKind() {
            return this.kind
        }
    }
    Animal.prototype.color = 'black'
    
    class Cat extends Animal {
        constructor(name) {
            super('cat');
            this.name = name;
        }
        getCatInfo() {
            // super在普通方法中表示的是Animal.prototype:
            // super.color相当于Animal.prototype.color
            console.log(super.color); // black
            // super.getKind()相当于Animal.prototype.getKind()
            console.log(this.name + ':' + super.getKind())
        }
    }
    
    const cat1 = new Cat('xiaohei'); // Cat { kind: 'cat' }
    cat1.getCatInfo(); // xiaohei:cat

注意:

1)由于super表示的是父类的原型,因此在父类实例上的属性和方法都无法通过super调用

    getCatInfo() {
        // kind是Animal的实例属性,因此无法通过super访问
        console.log(super.kind); // undefined
        console.log(this.name + ':' + super.getKind()); // xiaohei:cat
    }

2)ES6规定,通过super调用父类的方法时,super会绑定子类的this

    class Foo {
        constructor() {
            this.num = 1;
        }
        print() {
            console.log(this.num);
        }
    }
    class Bar extends Foo {
        constructor() {
            super();
            this.num = 2;
        }
        write() {
            // super.print()相当于Foo.prototype.print.call(this)
            super.print();
        }
    }
    
    const bar = new Bar();
    bar.write(); // 2

3)通过super对某个属性赋值,相当于在子类上添加了一个实例属性,因此super会绑定子类的this

    class Foo {
        constructor() {
            this.num = 1;
        }
    }
    
    class Bar extends Foo {
        constructor() {
            super();
            this.num = 2;
            console.log(this.num); // 2
            // 相当于this.num = 3
            super.num = 3;
            console.log(this.num); // 3
            // 通过super.num读取值,相当于Foo.prototype.num
            console.log(super.num); // undefined
        }
    }
    
    const bar = new Bar();
  • 在静态方法中指向父类
    class Foo {
        static print() {
            console.log('static method');
        }
        write() {
            console.log('normal method');
        }
    }
    class Bar extends Foo {
        static print() {
            super.print()
        }
        write() {
            super.write()
        }
    }
    
    const bar = new Bar();
    Bar.print(); // static method
    bar.write(); // normal method

三、类的prototype属性和_proto_属性

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

  • 子类的_proto_属性表示构造函数的继承,指向父类 => 在ES5中,对象的_proto_属性指向其原型对象,在Class中的理解是一样的,子类的_proto_属性也是指向其原型对象,因此指向其父类。
  • 子类的prototype属性的_proto_属性表示方法的继承,指向父类的prototype属性 => 在ES5中,只有函数对象才有prototype属性,ES6中的Class实际上可以看作是一个语法糖,Class的数据类型是函数,也具有prototype属性,在子类继承父类的时候,子类的prototype也继承了父类的prototype属性,因此子类的prototype属性的_proto_属性指向父类的prototype。

    用代码表示上面的描述就是:
    class Foo {}
    class Bar {}
    
    Object.setPrototypeOf(Bar, Foo)
    Object.setPrototypeOf(Bar.prototype, Foo.prototype)
    
    // 子类Bar的_proto_属性指向父类Foo
    console.log(Bar._proto_ === Foo) // true
    console.log(Object.getPrototypeOf(Bar) === Foo) // true
    
    // 子类Bar的prototype属性的_proto_属性指向父类Foo的prototype属性
    console.log(Bar.prototype._proto_ === Foo.prototype) // true
    console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype) // true
    class Foo {}
    
    class Bar extends Foo {}
    
    // 子类Bar的_proto_属性指向父类Foo
    console.log(Bar._proto_ === Foo) // true
    console.log(Object.getPrototypeOf(Bar) === Foo) // true
    
    // 子类Bar的prototype属性的_proto_属性指向父类Foo的prototype属性
    console.log(Bar.prototype._proto_ === Foo.prototype) // true
    console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype) // true

  • 子类实例的_proto_属性的_proto_属性指向父类实例的_proto_属性,也就是说子类的原型的原型是父类的原型
    class Foo {}
    
    class Bar extends Foo {}
    
    const foo = new Foo()
    const bar = new Bar()
    
    console.log(Object.getPrototypeOf(Object.getPrototypeOf(bar)) === Object.getPrototypeOf(foo)) // true
    
    console.log(bar._proto_._proto_ === foo._proto_) // true

因此可以通过子类的原型的原型来修改父类的原型,蜜汁操作,不建议

    // bar._proto_.proto_.print = function() {
    //   console.log('haha')
    // }
    
    Object.getPrototypeOf(Object.getPrototypeOf(bar)).print = function() {
      console.log('haha')
    }
    bar.print() // haha
    foo.print() // haha

四、extends的继承目标

目标:含有prototype属性的对象,因此可以是任意函数(除了Function.prototype,不要忘记了Function.prototype也是函数,typeof Function.prototype === 'function')。

  • 通过Class继承原生构造函数
    ECMAScript中的原生构造函数大致有以下这些:
    • Boolean()
    • Number()
    • String()
    • Object()
    • Array()
    • Function()
    • Date()
    • RegExp()
    • Error()



原文地址:https://www.cnblogs.com/jiafifteen/p/12201355.html