详解 Babel 如何将 Class 语法糖转换为 ES5 语法

作者:键盘侠

时间:2026-10-31

浏览:160

本文深入解析 Babel 编译 Class 至 ES5 的核心机制。详细阐述私有字段通过 WeakMap/WeakSet 存储、私有方法通过 WeakSet 拦截调用的实现方式;分析静态与公开成员在构造函数与原型上的挂载逻辑;并拆解子类继承中 prototype 与 __proto__ 的重定向过程,揭示 Babel 如何利用辅助函数实现 ES5 兼容的面向对象编程。

文章目录

前期准备

  • 在项目根目录建立 index.js 与 .babelrc 配置文件
class Parent {
    static type = 'Parent';
    #salary = 0;
    name = '';
    static showType() {
        console.log(this.type)
    }
    #seeSalary() {
        console.log(this.#salary)
    }
    speakName() {
        this.#seeSalary()
        console.log(this.name)
    }
}

class Child extends Parent {
    constructor(name) {
        super();
        this.name = name;
    }
    speakName() {
        super.speakName();
    }
}
{
    "presets": [
        "@babel/preset-env"
    ]
}
npm install @babel/core @babel/cli @babel/preset-env --save-dev
npx babel index.js -o dist.js

编译生成的私有变量及方法存储机制解析

var _salary = new WeakMap();
var _seeSalary = new WeakSet();
var Parent = function () {
  function Parent() {
    _classPrivateMethodInitSpec(this, _seeSalary);
    _classPrivateFieldInitSpec(this, _salary, {
      writable: true,
      value: 0
    });
  }
}
function _seeSalary2() {
  console.log(_classPrivateFieldGet(this, _salary));
}

function _classPrivateMethodInitSpec(obj, privateSet) {
  privateSet.add(obj); 
}
function _classPrivateFieldInitSpec(obj, privateMap, value) {
  privateMap.set(obj, value);
}

  • 代码逻辑归纳:私有数据依托 WeakMap 存储,以实例为键、数值为值;私有行为依托 WeakSet 存储,以实例为键。因方法执行需依赖函数体,故函数体定义于类外部。
  • 疑问点:此处调用的是seeSalary而非seeSalary2。需明确私有行为仅限类内部访问,外部无法直接调用。因此在类内部调用seeSalary时,系统会拦截该操作并转而执行seeSalary2。
  • 另一疑问:Babel 转换至 ES5 后为何仍出现weakMap与weakSet?Babel 架构涵盖编译与 polyfill 两大模块。

编译后静态或公开变量及方法的存储机制解析

var Parent = function() {
  function Parent() {
    ...
    _defineProperty(this, "name", '');
  }
  // 第一个参数是public方法,第二个参数是static方法
  _createClass(Parent, [{
    key: "speakName",
    value: function speakName() {
      _classPrivateMethodGet(this, _seeSalary, _seeSalary2).call(this);
      console.log(this.name);
    }
  }], [{
    key: "showType",
    value: function showType() {
      console.log(this.type);
    }
  }]);
  return Parent;
}
_defineProperty(Parent, "type", 'Parent');

function _createClass(Constructor, protoProps, staticProps) { 
  if (protoProps) _defineProperties(Constructor.prototype, protoProps); 
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor; 
}
function _classPrivateMethodGet(receiver, privateSet, fn) { 
  if (!privateSet.has(receiver)) { 
    throw new TypeError("attempted to get private field on non-instance"); 
  } 
  return fn; 
}
  • 代码逻辑归纳:公开属性挂载于实例自身;公开方法挂载于构造器的prototype上;静态属性与静态方法均挂载于类自身。

继承机制的实现原理探讨

var Child = function(_Parent2) {
  _inherits(Child, _Parent2);
  function Child(name) {
    var _this;
    _this = _callSuper(this, Child);
    _this.name = name;
    return _this;
  }
  _createClass(Child, [{
    key: "speakName",
    value: function speakName() {
      _get(_getPrototypeOf(Child.prototype), "speakName", this).call(this);
    }
  }]);
  return Child; 
}(Parent);

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } });
  Object.defineProperty(subClass, "prototype", { writable: false });
  if (superClass) _setPrototypeOf(subClass, superClass); 
}
function _setPrototypeOf(o, p) { 
  _setPrototypeOf = Object.setPrototypeOf ?
    Object.setPrototypeOf.bind() :
    function _setPrototypeOf(o, p) { o.__proto__ = p; return o; };
  return _setPrototypeOf(o, p);
}
function _callSuper(t, o, e) {
  return o = 
    _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? 
      Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) :
      o.apply(t, e)); 
}

  • 代码逻辑归纳:继承通过修改子类prototype指向新对象实现,该新对象prototype指向父类prototype,且新对象constructor维持子类constructor不变并允许外部修改。随后禁止子类prototype被赋值为其他对象。最终使子类__proto__指向父类。
  • 疑问点:为何创建新对象而非令子类prototype直接指向父类prototype?旨在避免此前向子类prototype添加公开方法时影响父类。
  • 疑问点:为何需__proto__指向父类?为确保静态属性与方法可被子类访问父类成员,前文已述静态成员挂载于类自身。

延伸知识:原型链机制

  • 所有对象(含数组、函数等)均具备__proto__属性,借由该属性指向其他对象以构建原型链
  • 函数除具备__proto__外还拥有prototype,但原型链查找不经过prototype,除非使用 new 实例化类,因 new 关键字将类prototype赋给实例__proto__。

实例演示

  • 案例一
function P() {}
P.prototype.x = 'x'

function C() {}
C.prototype = P.prototype
console.log(C.x) // undefined
  • 案例二
function P() {}
P.prototype.x = 'x'
P.x = 'xxx'

function C() {}
C.__proto__ = P;
console.log(C.x)  // xxx

上述内容详尽解析了 Babel 将 class 语法糖转换为 ES5 语法的流程,欲获取更多 babel class 转 ES5 资讯,敬请留意本站其他相关博文!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章