Skip to content

原型与原型链 - ES6 Class的底层实现原理 #22

@logan70

Description

@logan70

ES6 Class的底层实现原理

ES6 中的类Class,仅仅只是基于现有的原型继承的一种语法糖,我们一起来看一下Class的底层实现原理。

Class的底层实现要素

  1. 只能使用new操作符调用Class
  2. Class可定义实例属性方法和静态属性方法;
  3. Class的实例可继承父Class上的实例属性方法、子Class可继承父Class上的静态属性方法。

只能使用new操作符调用Class

实现思路:�使用instanceof操作符检测实例是否为指定类的实例。

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError('Cannot call a class as a function')
  }
}

定义实例属性方法和静态属性方法

实现思路

  • 在构造函数的原型上定义属性方法,即为实例属性方法;
  • 在构造函数本身定义属性方法,即为静态属性方法。
function _createClass(Constructor, protoProps = [], staticProps = []) {
  // 在构造函数的原型上定义实例属性方法
  _defineProperties(Constructor.prototype, protoProps)
  // 在构造函数本身定义静态属性方法
  _defineProperties(Constructor, staticProps)
}

// 实现公用的批量给对象添加属性方法的方法
function _defineProperties(target, props) {
  props.forEach(prop => {
    Object.defineProperty(target, prop.key, prop)
  })
}

继承实例属性方法和静态属性方法

实现思路:借用原型链继承实现。

function _inherits(subClass, superClass) {
  // 子类实例继承父类的实例属性方法
  subClass.prototype = Object.create(superClass.prototype)
  // 修正constructor属性
  subClass.prototype.constructor = subClass

  // 子类继承父类的静态属性方法
  Object.setPrototypeOf(subClass, superClass)
}

模拟编译

了解了Class的底层实现要素,我们就来将Class模拟编译为使用原型继承实现的代码。

源代码

class Person {
  constructor(options) {
    this.name = options.name
    this.age = options.age
  }
  eat() {
    return 'eating'
  }
  static isPerson(instance) {
    return instance instanceof Person
  }
}

class Student extends Person {
  constructor(options) {
    super(options)
    this.grade = options.grade
  }
  study() {
    return 'studying'
  }
  static isStudent(instance) {
    return instance instanceof Student
  }
}

编译后代码

var Person = (function() {
  function Person(options) {
    // 确保使用new调用
    _classCallCheck(this, Person)
    this.name = options.name
    this.age = options.age
  }

  _createClass(
    Person,
    // 实例属性方法
    [{
      key: 'eat',
      value: function eat() {
        return 'eating'
      }
    }],
    // 静态属性方法
    [{
      key: 'isPerson',
      value: function isPerson(instance) {
        return instance instanceof Person
      }
    }]
  )
  return Person
})();
var Student = (function (_Person) {
  // 继承父类实例属性方法和静态属性方法
  _inherits(Student, _Person)

  function Student(options) {
    // 确保使用new调用
    _classCallCheck(this, Student)

    // 执行父类构造函数
    _Person.call(this, options)

    this.grade = options.grade
  }

  _createClass(Student,
    // 实例属性方法
    [{
      key: 'study',
      value: function study() {
        return 'studying'
      }
    }],
    // 静态属性方法
    [{
      key: 'isStudent',
      value: function isStudent(instance) {
        return instance instanceof Student
      }
    }]
  )

  return Student
})(Person);

测试代码

const person = new Person({ name: 'Logan', age: 18 })
const student = new Student({ name: 'Logan', age: 18, grade: 9 })

expect(person.eat()).toBe('eating')
expect(student.eat()).toBe('eating') // 继承实例方法
expect(student.study()).toBe('studying')

expect(Student.isStudent(student)).toBe(true)
expect(Person.isPerson(person)).toBe(true)
expect(Student.isStudent(person)).toBe(false)
expect(Student.isPerson(student)).toBe(true) // 继承静态方法

公有字段和私有字段(提案中)

公有(public)和私有(private)字段声明目前在JavaScript标准委员会TC39的 试验性功能 (第3阶段),下面进行模拟实现。

静态公有字段

使用

class ClassWithStaticField {
  static staticField1 = 'static field' // 设定初始值
  static staticField2 // 不设定初始值
}

polyfill

function ClassWithStaticField() {}
// @babel/plugin-proposal-class-properties 中使用Object.defineProperty实现,此处简便起见直接赋值
ClassWithStaticField.staticField1 = 'static field' // 设定初始值
ClassWithStaticField.staticField2 = undefined // 不设定初始值

公有实例字段

使用

class ClassWithInstanceField {
  instanceField1 = 'instance field' // 设定初始值
  instanceField2 // 不设定初始值
}

polyfill

function ClassWithInstanceField() {
  // @babel/plugin-proposal-class-properties 中使用Object.defineProperty实现,此处简便起见直接赋值
  this.instanceField1 = 'instance field' // 设定初始值
  this.instanceField2 = undefined // 不设定初始值
}

静态私有字段

静态私有字段只能在静态方法内访问,且只能通过类的属性进行访问,不能通过this进行访问。

使用

class ClassWithPrivateStaticField {
  static #PRIVATE_STATIC_FIELD

  static publicStaticMethod() {
    ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD = 42
    return ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD
  }
}

polyfill

通过闭包实现静态私有字段

var ClassWithPrivateStaticField = (function() {
  var _PRIVATE_STATIC_FIELD
  function ClassWithPrivateStaticField() {}

  ClassWithPrivateStaticField.publicStaticMethod = function() {
    _PRIVATE_STATIC_FIELD = 42
    return _PRIVATE_STATIC_FIELD
  }
  return ClassWithPrivateStaticField
})();

私有实例字段

使用

class ClassWithPrivateField {
  #privateField;
  
  constructor() {
    this.#privateField = 42;
    console.log(this.$privateField)
  }
}

polyfill

通过WeakMap结合实例本身为key实现

var ClassWithPrivateField = (function() {
  var _privateField = new WeakMap()
  function ClassWithPrivateField() {
    _privateField.set(this, undefined)
    _privateField.set(this, 42)
    console.log(_privateField.get(this))
  }
})();

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions