使用ES5实现ES6的Class

654 阅读4分钟

ES5的寄生组合式继承

  function animals (animal) {
      this.animal = animal
  }

  animals.prototype.say = function () {
      console.log(this.animal)
  }

  function sub (animal, value) {
      animals.call(this, animal)
      this.value = value
  }

  sub.prototype = Object.create(animals.prototype, {
      constructor: {
          value: sub,
          enumerablefalse, //是否可枚举的
          writabletrue,    //是否可写的
          configurabletrue //是否可配置
      }
  })

ES6的Class

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。但是它们还是有区别的。

区别:

  • 类必须使用new调用,否则会报错。ES5的构造函数是可以当成普通函数使用的
  • 类的内部所有定义的方法,都是不可枚举的。(包括内部定义的静态方法)
  • 类的静态方法也可以被子类继承
  • 可以继承原生构造函数
    • ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。
    • ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承

使用ES5模拟实现ES6的class

根据上面的区别,我们一步步的看。

1. new操作符检查函数

解决问题:

  • 类必须使用new调用,否则会报错。ES的构造函数是可以当成普通函数使用的
  function _checkType (obj, constructor) {
      if (!(obj instanceof constructor)) {
          throw new TypeError('Cannot call a class as a function')
      }
  }

2. 内部方法不可枚举

解决问题:

  • 类的内部所有定义的方法,都是不可枚举的。(包括内部定义的静态方法)
  // 修改构造函数描述符
  function defineProperties (target, descriptors) {
      for (let descriptor of descriptors) {
          descriptor.enumerable = descriptor.enumerable || false

          descriptor.configurable = true
          if ('value' in descriptor) {
              descriptor.writable = true
          }

          Object.defineProperty(target, descriptor.key, descriptor)
      }
  }

  // 构造class
  // constructor 表示类对应的constructor对象
  // protoDesc 表示class内部定义的方法
  // staticDesc 表示class内部定义的静态方法
  function _createClass (constructor, protoDesc, staticDesc) {
      protoDesc && defineProperties(constructor.prototype, protoDesc)
      staticDesc && defineProperties(constructor, staticDesc)
      return constructor
  }

3. 真正的创建class

  const Foo = function () {
      function Foo(name) {
          _checkType(thisFoo// 先检查是不是new调用的

          this.name = name
      }
      _createClass (Foo, [ // 表示在class内部定义的方法
          {
              key'say',
              valuefunction () {
                  console.log(this.name)
              }
          }
      ], [ // 表示在class内部定义的静态方法
          {
              key'say',
              valuefunction () {
                  console.log('static say')
                  console.log(this.name)
              }
          }
      ])
      return Foo
  }()

到这里class实现完成,验证一下。

  • 先直接调用一下Foo(),结果为:

pic1.png

  • 使用new操作符,生成一个对象
const foo = new Foo('aaa')

pic2.jpg

  • 打印一下在原型链上定义的方法

pic3.jpg

可见say方法是不可枚举的。

  • 打印一下静态方法

pic4.jpg

可见静态方法say是不可枚举的。

4. 实现原型链继承和静态方法继承,并考虑到可以继承null的情况

解决问题:

  • 类的静态方法也可以被子类继承
function _inherits(subClass, superClass) {
  if (typeof superClass !== 'function' && superClass !== null) {
     throw new TypeError('Super expression must either be null or a function, not' + typeof superClass)
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
          value: subClass,
          enumerablefalse,
          writabletrue,
          configurabletrue
      }
  })
  if (superClass) {  //将superClass设为subClass的原型
    Object.setPrototypeOf ? 
        Object.setPrototypeOf(subClass, superClass) 
        : subClass.__proto__ = superClass
    }
}

5. 使用父类的实例对象this

解决的问题:

  • 可以继承原生构造函数
    • ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。
    • ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承
  // 返回父类的this;若为null,则返回自身
  function _possibleConstructorReturn(self, call) {
      if (!self) {
          throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
      }
      return call && (typeof call === 'object' || typeof call === 'function') ? call : self
  }

6. 创建子类class

const Child = function (_Parent) {
    _inherits(Child, _Parent) // 继承父类原型上的属性及静态方法的继承
    function Child(name, age) {
      _checkType(thisChild)
      // 先使用父类实例对象this,再返回
      const _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name))
      _this.age = age
      return _this
    }
    return Child
}(Foo)

子类class实现完成。验证一下。

  • 打印一下Child.say()

pic5.jpg

Child并没有在自身定义静态方法,但是它的父类有定义。继承成功。

  • 构造一个继承原生构造函数的子类
const Child = function (_Parent) {
    _inherits(Child, _Parent)
    function Child(name, age) {
      _checkType(thisChild)
      const _this = _possibleConstructorReturn(this, (
          Child.__proto__ 
          || Object.getPrototypeOf(Child)).call(this, name)
        )
        _this.age = age
        return _this
    }
    return Child
}(Array)
const c = new Child('bbb'12)

pic6.jpg

继承成功。基本实现用ES5的function达成ES6的class的效果。