zoukankan      html  css  js  c++  java
  • 【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()



  • 相关阅读:
    关于随机数生成
    全文搜索基本原理(倒排索引、搜索结果排序)
    Log-Structured Merge Tree (LSM Tree)
    Spring Cloud组件使用/配置小记
    容错框架之Hystrix小记
    (转)调试程序时设置断点的原理
    字符串匹配算法
    信息论小记
    Java 函数式编程(Lambda表达式)与Stream API
    (转)自动控制的故事
  • 原文地址:https://www.cnblogs.com/jiafifteen/p/12201355.html
Copyright © 2011-2022 走看看