天天看点

【ECMAScript6】类Class一、类Class的概念二、Class中的静态成员三、Class中的继承四、Class中的super五、子类对父类方法的重写六、Class中的getter和setter

一、类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)
           

class中的constructor就是构造方法,其中的this指向类的实例对象。

类的数据类型是函数,类本身指向构造函数。

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

class A {
            constructor() {}
            toString() {}
            toValue() {}
        }

        // 上面的代码等同于下面的代码
        function A () {
            // constructor
        };
        A.prototype.toString = function() {};
        A.prototype.toValue = function() {};
           

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

let a = new A();
        a.constructor === A.prototype.constructor
           

1.2 class中的constructor

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

class A {
        }
        
        // 等同于
        class A {
          constructor() {}
        }
           

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

class A {
          constructor() {
              return Object.create(null);
          }
        }
        
        console.log((new A()) instanceof A);
        // false
           

注意:

  • 实例的属性除非显示定义在本身(即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
           

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

class A {
            static classMethod() {
              this.baz();
            }
            static baz() {
              console.log('hello');
            }
            baz() {
              console.log('world');
            }
        }
        A.classMethod();
        // hello
           

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

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

class A {
            static classMethod() {
                console.log('hello');
            }
        }
        
        class B extends A {}
        
        B.classMethod() // 'hello'
           

三、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)
           
// 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)
           

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

class Animal { /* ... */ }
        
        class Cat extends Animal {
          constructor() {
          }
        }
        
        let cp = new Cat();
        // ReferenceError
           

另一个需要注意的是,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; // 正确
          }
        }
           

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

class Cat extends Animal {
        
        }
        // 等同于
        
        class Cat extends Animal {
            constructor(...args) {
                super(...args);
            }
        }
           

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

class A {
          static hello() {
            console.log('hello world');
          }
        }
        
        class B extends A {
        }
        
        B.hello()  // hello world
           

四、Class中的super

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

4.1 super作为函数调用

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

class A {}
        
        class B extends A {
          constructor() {
            super();
          }
        }
           

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
           

在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();
           

上面代码中,子类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
           

上面代码中,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();
           

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
           

上面代码中,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
           

上面代码中,静态方法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()  // 视频通话
           

六、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属性被修改了
           

继续阅读