• 【ECMAScript6】类Class


    一、类Class的概念

    1.1 基本概念

    Class是ES6中的一个语法糖,底层依然是构造函数,故它所实现的大部分功能ES5都能完成,但是Class可以使对象原型的写法更加清晰,更像面向对象编程的语法。

    下面是通过构造函数和class创建实例的对比写法:

            // 构造函数的方式
            function Phone(brand, price) {
                this.brand = brand 
                this.price = price 
            }
    
            Phone.prototype.call = function() {
                console.log('call me')
            }
    
            let huawei = new Phone('华为', 2999)
            huawei.call()
            console.log(huawei)
    
            // class的方式
            class CPhone {
                constructor(brand, price) {
                    this.brand = brand
                    this.price = price 
                }
    
                call() {
                    console.log('call me')
                }
            }
            
            let onePlus = new CPhone('1+', 1234)
            onePlus.call()
            console.log(onePlus)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    class中的constructor就是构造方法,其中的this指向类的实例对象。
    类的数据类型是函数,类本身指向构造函数。

    类的所有方法都定义在类的prototype属性上面:

            class A {
                constructor() {}
                toString() {}
                toValue() {}
            }
    
            // 上面的代码等同于下面的代码
            function A () {
                // constructor
            };
            A.prototype.toString = function() {};
            A.prototype.toValue = function() {};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在类的实例上调用方法相当于调用类原型上的方法:

            let a = new A();
            a.constructor === A.prototype.constructor
    
    • 1
    • 2

    1.2 class中的constructor

    constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

            class A {
            }
            
            // 等同于
            class A {
              constructor() {}
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

            class A {
              constructor() {
                  return Object.create(null);
              }
            }
            
            console.log((new A()) instanceof A);
            // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:

    • 实例的属性除非显示定义在本身(即this对象上),否则都定义在原型上(即class上)
    • class声明不存在变量提升
    • 类的方法内部如果有this,它默认指向类的实例

    二、Class中的静态成员

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。 如果在一个方法前,加上 static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态成员"。

    构造函数与class中静态成员的写法差异:

            // 构造函数中的静态成员
            function Phone() {
    
            }
            Phone.name = 'phone'
            Phone.change = function() {
                console.log('ccccc')
            }
            Phone.prototype.size = '5.8inch'
            let nokia = new Phone()
            console.log(nokia.name) // undefined
            // nokia.change() // erorr
            console.log(nokia.size)  // 5.8inch
    
            // class中的静态成员
            class CPhone {
                static name = 'phone'
                static change() {
                    console.log('ccccc')
                }
                size = '5.8inch'
            }
            let nokia1 = new CPhone()
            console.log(nokia1.name) // undefined
            // nokia.change() // erorr
            console.log(nokia1.size)  // 5.8inch
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    如果静态方法包含this关键字,这个this指的是类,而不是实例。

            class A {
                static classMethod() {
                  this.baz();
                }
                static baz() {
                  console.log('hello');
                }
                baz() {
                  console.log('world');
                }
            }
            A.classMethod();
            // hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    静态方法classMethod调用了this.baz,这里的this指的是A类,而不是A的实例,等同于调用A.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。

    父类的静态方法,可以被子类继承。

            class A {
                static classMethod() {
                    console.log('hello');
                }
            }
            
            class B extends A {}
            
            B.classMethod() // 'hello'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    三、Class中的继承

    下面代码通过构造函数和class的方式实现继承:

            // 构造函数的方式
            function Phone(brand, price) {
                this.brand = brand 
                this.price = price 
            }
            Phone.prototype.call = function() {
                console.log('打电话')
            }
    
            function SmartPhone(brand, price, color, size) {
                Phone.call(this, brand, price)
                this.color = color 
                this.size = size 
            }
    
            SmartPhone.prototype = new Phone()
            SmartPhone.prototype.constructor = SmartPhone
    
            SmartPhone.prototype.photo = function() {
                console.log('拍照')
            }
            SmartPhone.prototype.playGame = function() {
                console.log('玩游戏')
            }
    
            const cuizi = new SmartPhone('崔子', 2222, 'black', '6.0inch')
            console.log(cuizi)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
            // class中的继承
            class Phone{
                constructor(brand, price) {
                    this.brand = brand
                    this.price = price 
                }
                call() {
                    console.log('打电话')
                }
            }
    
            class SmartPhone extends Phone {
                constructor(brand, price, color, size) {
                    super(brand, price)
                    this.color = color 
                    this.size = size
                }
    
                photo() {
                    console.log('拍照')
                }
    
                playGame() {
                    console.log('打游戏')
                }
            }
            const xiaomi = new SmartPhone('小米', 799, 'white', '5.8inch')
            console.log(xiaomi)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    值得注意的是,子类必须在constructor中调用super方法,否则新建的实例就会报错,这是因为子类自己的this对象必须通过父类构造函数来塑造,从而得到和父类相同的实例属性和方法,然后再对其进行加工(加上子类独有的实例属性和方法),如果不调用super子类就得不到this对象。

            class Animal { /* ... */ }
            
            class Cat extends Animal {
              constructor() {
              }
            }
            
            let cp = new Cat();
            // ReferenceError
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    另一个需要注意的是,es5构造函数在调用父构造函数前可以访问this,但是es6的构造函数在调用父类构造函数(super)前不能访问this对象。

            class A {
              constructor(x, y) {
                this.x = x;
                this.y = y;
              }
            }
            
            class B extends A {
              constructor(x, y, name) {
                this.name = name; // ReferenceError
                super(x, y);
                this.name = name; // 正确
              }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如果子类没有显式定义constructor方法,该方法会被默认添加。

            class Cat extends Animal {
            
            }
            // 等同于
            
            class Cat extends Animal {
                constructor(...args) {
                    super(...args);
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

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

    四、Class中的super

    super在class中既可以当作函数调用,又可以当作对象使用。

    4.1 super作为函数调用

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

            class A {}
            
            class B extends A {
              constructor() {
                super();
              }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

            class A {
              constructor() {
                // new.target 指向正在执行的函数
                console.log(new.target.name);
              }
            }
            class B extends A {
              constructor() {
                super();
              }
            }
            new A() // A
            new B() // B
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B。

    4.2 super作为对象调用

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

    4.2.1 super对象在普通函数中调用

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

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

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

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

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

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

    4.2.2 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
    
            const child = new Child();
            child.myMethod(2); // instance 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

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

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

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

    五、子类对父类方法的重写

            class Phone{
                constructor(brand, price) {
                    this.brand = brand
                    this.price = price 
                }
                call() {
                    console.log('打电话')
                }
            }
    
            class SmartPhone extends Phone {
                constructor(brand, price, color, size) {
                    super(brand, price)
                    this.color = color 
                    this.size = size
                }
    
                call() {
                    console.log('视频电话')
                }
            }
            const xiaomi = new SmartPhone('小米', 799, 'white', '5.8inch')
            xiaomi.call()  // 视频通话
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    六、Class中的getter和setter

            class Phone{
                get price() {
                    console.log('price属性被读取了')
                    return '$30'
                }
    
                set price(newPrice) {
                    console.log('price属性被修改了')
                }
            }
            let s = new Phone()
            console.log(s.price) // price属性被读取了 $30
            s.price = 'free'   // price属性被修改了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    Spring-FirstDay
    JSON和全局异常处理
    接上一篇:分布式调用链追踪系统设计
    在emacs中,设置latex的主文档
    Sublime text的使用及技巧积累
    Rust 从 PyTorch 到 Burn
    前端启动项目将http协议改为https协议
    QT第三方库加载pro解读
    【2】c++11新特性(稳定性和兼容性)—>超长整型 long long
    Win10重启后总是自动打开上次未关闭的程序怎么办
  • 原文地址:https://blog.csdn.net/qq_41481731/article/details/125538597