yliu

时来天地皆同力,运去英雄不自由

Babel to Class之编译(1)


前言

这是《Babel to Class》系列的第一篇

这个系列主要讲 Class 的普通写法、继承、私有属性和原生构造函数继承 babel 是如何实现这一过程的。

注:文章顺序存在关联,请按照顺序阅读。

前置知识点

Object.defineProperty

Object.defineProperty(obj, prop, descriptor) 用于定义对象属性,它有三个参数分为别:对象,对象 key,以及属性描述符。

属性描述符默认不传递具体的值则为 false

const a = {};
Object.defineProperty(a, 'd', {});
Object.getOwnPropertyDescriptor(a, 'd');
// {value: undefined, writable: false, enumerable: false, configurable: false}

有两种调用形式

var o = {};

// 1.
Object.defineProperty(o, 'a', {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true,
});

// 2.
var bValue = 38;
Object.defineProperty(o, 'b', {
  get() {
    return bValue;
  },
  set(newValue) {
    bValue = newValue;
  },
  enumerable: true,
  configurable: true,
});

更多内容,以及属性描述符包含那些,请参考文档

Class

  • Class必须通过 new 调用,否则抛出 Uncaught TypeError: Class constructor A cannot be invoked without 'new'
  • Classprototype 属性默认不可遍历

示例文件

class Test {
  constructor() {
    this.name = '张三';
  }

  _age = 18;

  get age() {
    return this._age;
  }

  set age(value) {
    this._age = value;
  }

  getName() {
    return this.name;
  }

  static getClassName() {
    return this.name;
  }

  static name = 'TestClass';
}

上面就是准备好的示例文件,结构很简单,我们看下 babel 会将代码转码成什么样

babel

为了方便讲解,下面通过注释的形式来解读,阅读部分请从 const Test = /* #__PURE__ */ (function () { 这段开始。

'use strict';

function _classCallCheck(instance, Constructor) {
  /*
   * Class必须通过new调用,这是判断调用的this跟Test进行instanceof对比,如果不符合抛出错误
   */
  if (!(instance instanceof Constructor)) {
    throw new TypeError('Cannot call a class as a function');
  }
}

/**
 * 用于给对象定义属性
 * 注意它判断了value属性是否存在,这是因为存在两种写法
 * 一种是传递value的普通
 * 另外一种是传递get和set的形式
 */
function _defineProperties(target, props) {
  for (let i = 0; i < props.length; i++) {
    const descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ('value' in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

/**
 * 初始化Class属性
 * 先从函数的prototype属性来进行定义
 * 后面定义Constructor本身的属性
 */
function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, 'prototype', { writable: false });
  return Constructor;
}

/**
 * 用于给Test实例和本身定义属性
 * 之所以进行一个 in 判定是为了覆盖本身已经有的属性
 * 例如class.name属性,这个本身已经存在 {value: 'A', writable: false, enumerable: false, configurable: true}
 * 但是我们从新定义就要属性描述符全部为true
 */
function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });
  } else {
    obj[key] = value;
  }
  return obj;
}

const Test = /* #__PURE__ */ (function () {
  function Test() {
    // 初始进行new调用判断
    _classCallCheck(this, Test);
    // 实例属性写法
    _defineProperty(this, '_age', 18);

    this.name = '张三';
  }

  // 初始化,后面分为为prototype属性以及静态属性
  _createClass(
    Test,
    [
      {
        key: 'age',
        get: function get() {
          return this._age;
        },
        set: function set(value) {
          this._age = value;
        },
      },
      {
        key: 'getName',
        value: function getName() {
          return this.name;
        },
      },
    ],
    [
      {
        key: 'getClassName',
        value: function getClassName() {
          return this.name;
        },
      },
    ],
  );

  return Test;
})();

_defineProperty(Test, 'name', 'TestClass');

概况一下上面流程:

  1. 初始化 Class 上的 prototype 属性以及 static 属性
  2. new Class 的时候,对 Class 的调用形式进行判断
  3. new 过程中赋值实例属性
  4. 执行...

最后

如果有什么不正确或者书写错误欢迎指出。