javascript的类——class

347 阅读4分钟

预备知识:原型链

虽然ECMAScript6类表面上看起来可以支持面向对象编程,但实际上它背后使用仍然是原型和构造函数的概念
-----------------------------------------------------------------------《javascript高级程序设计》 javascript中的class关键字更加像是一种语法糖,但是其背后本质依旧是js的老办法,因此在学习class之前,最好将原型链的知识理一理。这里我给出我觉得必要的模型。

在该图例中,使用了Person构造函数创建了空对象实例new Person,它继承了Person的原型对象,同时Person的原型对象作为Object的一个实例,继续继承了Object的原型对象。

当我们在创建类,我们在创建什么?

看下列代码:

  class Person {
      constructor() {
          // 定义在实例上的属性和方法
          this.locate = () => { console.log('instance') }
          this.name = 'wtk';
      }
      // 定义在原型上的方法
      locate() {
          console.log('prototype');
      }
      ['new' + 'locate']() {
          console.log('newlocate')
      }
      // 定义getter和setter
      get name_() {
          return this.name;
      }
      set name_(val) {
          this.name = val;
      }

      // 定义在类上的方法
      static getName() {
          return this.name;
      }


      // 实例工厂 创建出实例
      static create() {
          return new Person();
      }
  }
  
  Person.hobby = '跳绳';
  Person.prototype.sex = 'male';
  
  console.dir(new Person())

我们分别在类中的构造器、类中、类中加static关键字之后定义函数,然后打印观察
Person 实例

image.png

Person原型对象

image.png

Person class

image.png
综合看来,可以得出下列的关系图

在图中,我们清晰地发现开头的js的class的背后依然是原型的结论被验证了,虽然使用了class,但我们还是得到了原型链,只不过构造函数被换成了一个class!

class关键字是一个语法糖,它只是对class中不同地方定义的方法和属性进行了再分配

  • constructor{} ——————>实例中的方法属性。在构造函数中定义的属性和方法,会被分配到实例中去。
  • class{}内定义的函数 —————>原型对象中的方法 。在class中不带static关键字中定义的方法,会被分配到原型对象中去
  • class{}内+static定义的函数 —————>原型对象中的方法。在class中加上static关键字定义的方法,会被分配到class中去,变为类本身的一个方法。
  • 注意:可以给class定义迭代器和生成器,有兴趣可以试试。
  • 注意:不推荐在类中直接给原型对象和class定义属性,如果需要添加可以选择在类的定义后直接手动添加,见代码。

class的继承及super关键字

extends

通过使用extends关键字,我们可以实现继承。之前我们已经知道了class的本质是原型链,那么继承是如何实现为原型链的呢。

实现以下代码再打印:

    class Person{};
    class Man extends Person{};
    console.log(new Man());

根据打印结果,得到关系图:

在图中可发现,犹如两个链条互相夹合,如果Man类继承了Person类:

  • Person的实例会成为Man实例的原型对象。
  • class Person会成为class Man的原型对象。

这就是extends的本质行为,这一举动就通过原型链将两个类给关联起来了。

super

super关键字用于访问和调用一个对象的父对象上的函数。 ——mdn

关于super有很多麻烦的限制,但是我觉得与其把视线关注在什么不应该做上,不如把视线关注在应该怎么做上:

  • 如果要在派生类(子类)中使用super,那么就应该在派生类构造器的一开始就调用super()
  • super()可以理解为是super.constructor()
  • super.fun()等同于Object.getPrototypeOf(this).fun.call(this),这里的this指向的改变至关重要。

super,它和this一样具有薛定谔性质,只有这个函数被调用时,你才会知道这个super到底指的是什么。令人喜悦的是,我们有再分配原则,由此谁来调用这些函数一开始就确定了。

看以下代码:

 class Vehicle {
        constructor() {
            this.tire = 4;
        }
        identifyPrototype(id) {
            console.log(id, this)
        }

        static identifyClass(id) {
            console.log(id, this)
        }
    };



    // 技巧:super 只能访问其原型对象上的方法和属性
    class Bus extends Vehicle {
        constructor() {
            // 为bus实例 super为new Vehicle()
            super();
            console.log('bus构造 访问class:', super.identifyClass);
            console.log('bus构造 访问prototype:', super.identifyPrototype);
        }

        identifyPrototype(id) {
            // 为bus的原型对象 super为Vehicle.prototype
            console.log('busprototype:');
            super.identifyPrototype(id);
            // super.identifyClass(id)
        }



        static identifyClass(id) {
            // 为Bus的原型对象 class Vehicle
            console.log('busClass:');
            super.identifyClass(id);
        }
    };
    
    console.dir(new Bus());
    const bus = new Bus();
    // 在Vehicle的prototype上
    bus.identifyPrototype('bus');
    // Bus的__proto__指向Vehicle
    Bus.identifyClass('bus')
  • 创建Bus实例时,调用constructor(),可以预知到Bus实例的原型是Vehicle的实例,又因为Vehicle的实例可以根据原型链找到Vehicle.prototype,因此Vehicle上.prototype的contructor和 identifyPrototype方法均可以在Bus的中constructor调用
  • Bus的普通函数,也主要给Bus的实例调用,而Bus的实例的__proto__Vehicle实例,同上。
  • static定义的函数,主要由Bus类直接调用,因此super主要为Vehicle,super可以调用Vehicle类上的静态方法。