ES6 Class 底层实现原理与 Babel 编译机制详解
本文深入解析 ES6 中 Class 语法的底层实现机制,指出其本质仍是基于原型的构造函数。通过 Babel 编译后的 ES5 代码,详细剖析 _createClass、_classCallCheck 等辅助函数的作用,涵盖构造函数、静态方法、getter/setter 及不可枚举属性的具体实现逻辑,帮助开发者理解 Class 语法糖背后的 JavaScript 核心概念。
ES6 Class 底层实现原理与 Babel 编译机制详解
序言
本文旨在解答前端面试中常见的问题:ES6 中 Class 语法的具体实现方式。
ECMAScript 6 引入了 Class 特性,这并非全新的语言机制,而是一种语法糖。它让 JavaScript 的代码结构更清晰,更符合面向对象编程的习惯,同时也降低了传统面向对象开发者学习 JavaScript 的门槛。此外,这种语法使得 IDE、静态类型检查器和代码风格检查工具能更便捷地进行语法检测和静态分析。
ES6 Class 的 ES5 代码实现
ES6 Class 的 ES5 代码实现
JavaScript 传统上通过构造函数来定义并生成对象,这种写法与传统的面向对象语言存在较大差异。为此,ES6 引入了 Class 概念作为对象的模板,以改善这一状况。
constructor 构造方法
constructor
在 ES6 中,创建 Class 时会默认包含 constructor 方法,当使用 new 关键字调用时,该方法会被自动执行。
ES5:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.toString = function () {
return '(' + this.name + ',' + this.age + ')';
}
var p = new Person('Mia', 18);
console.log(p);// Person { name: 'Mia', age: 18 }
ES6:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
toString() {
return '(' + this.name + ',' + this.age + ')';
}
}
var p = new Person('Mia', 18);
console.log(p);// Person { name: 'Mia', age: 18 }
ES6 Class 中的 constructor 对应 ES5 中的构造函数 Person,其中的 this 关键字指向实例对象。
Class 可以视为构造函数的另一种表现形式。通过 typeof Person === 'function' 为 true 以及 Person === Person.prototype.constructor 为 true 可以验证,Class 的数据类型本质上是函数,Class 本身指向构造函数,其底层依然是 function 构造函数。
类的公共方法均定义在类的 prototype 属性上。可以使用 Object.assign 一次性向类添加多个方法。
需要注意的是,Class 内部定义的方法默认是不可枚举的(non-enumerable),这与 ES5 的行为有所不同。
ES5:
Object.keys(Person.prototype); // ['toString']
ES6:
Object.keys(Person.prototype); // Person {}
关于不可枚举特性的具体代码实现,将在后文通过 Babel 转码 ES6 代码后进行解析。
new 调用机制
new 调用
Class 必须通过 new 关键字调用,否则将抛出错误。
ES5:
Person()// undefined
ES6:
Person() // TypeError: Class constructor Person cannot be invoked without 'new'
实例属性与静态方法
实例属性
实例属性应在 this 对象上显式定义,否则属性将定义在原型上。所有 Class 实例共享同一个原型对象,这一点与 ES5 的行为保持一致。
ES5:
function Person() {
this.grade = {
count: 0
};
}
ES6:
class Person {
constructor() {
this.grade = {
count: 0
};
}
}
此外,还需关注新提案中关于实例属性和静态属性的新写法,目前 Babel 已支持这种语法。
静态方法
Class 相当于其实例的原型,在类中定义的方法会被实例继承。若在方法前加上 static 关键字,则表明该方法不会被实例继承,而是直接通过类本身调用,这被称为“静态方法”。
注意:如果静态方法中包含 this 关键字,它指向的是类本身。
ES5:
function Person() { }
Person.toSay = function () {
return 'I love JavaScript.';
};
Person.toSay(); // I love JavaScript.
ES6:
class Person {
static toSay() {
return 'I love JavaScript.';
}
}
Person.toSay(); // I love JavaScript.
getter 和 setter
getter 和 setter
ES6 提供了 get 和 set 关键字,用于为属性设置存值函数和取值函数,从而拦截该属性的存取行为,这与 ES5 的行为一致。
ES5:
function Person(name) {}
Person.prototype = {
get name() {
return 'mia';
},
set name(newName) {
console.log('new name:' + newName);
}
}
ES6:
class Person {
get name() {
return 'mia';
}
set name(newName) {
console.log('new name:' + newName);
}
}
ES6 Class 底层实现原理
ES6 Class 底层实现原理
下文将主要使用 Babel 转码器对 Class 中的几个主要方法进行转码,并分析其 ES5 实现方式。
Babel 是如何编译 Class 的
Babel 是如何编译 Class 的
将以下代码使用 Babel 转码器转换为 ES5 代码,并按照代码结构和功能分块进行讲解。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
toString() {
return '(' + this.name + ',' + this.age + ')';
}
}
var p = new Person('Mia', 18);
运行模式
"use strict";Class 默认开启严格模式。
私有函数:
JavaScript 开发者通常在变量名或函数名前缀加下划线,以此表示私有。
前缀加下划线仅表示私有,这只是一个约定俗成的习惯,用于澄清意图,并未做其他处理。由于 ECMAScript 草案中并未定义私有变量的方法,因此在此限制下,仍可以在函数外或作用域外访问该函数或变量。
_instanceof 和 _classCallCheck 的作用
用于检查声明的 Class 是否通过 new 方式调用,否则将报错。
function _instanceof(left, right) {
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
return right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
_createClass 和 _defineProperties 的作用
_createClass 函数接收三个参数:Constructor 是传入的构造函数 Person,protoProps 是要添加到原型上的函数数组,staticProps 是要添加到构造函数本身的函数(即静态方法)。第二和第三个参数可以省略,_createClass 函数体内会进行判断。
_createClass 函数的作用是收集公有函数和静态方法,将这些方法添加到构造函数或构造函数的原型中,并返回构造函数。
defineProperties 是将方法添加到构造函数或原型中的主要逻辑。它遍历函数数组,分别声明其描述符。若 enumerable 未定义为 true,则默认为 false,并设置 configurable 为 true。这两个布尔值用于限制 Object.keys() 等方法遍历到这些属性。如果存在 value,则为 descriptor 添加 value 和 writable 属性;如果不存在,则直接使用 get 和 set 属性。
最后,使用 Object.defineProperty 方法为构造函数添加属性。
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
Class 类实现
var Person =
/*#__PURE__*/
function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [{
key: "toString",
value: function toString() {
return '(' + this.name + ',' + this.age + ')';
}
}]);
return Person;
}();
var p = new Person('Mia', 18);
解析:
当不使用 new 调用时,this 指向 window,因此 instance instanceof Constructor 为 false,从而抛出异常。
通过调用 _createClass 函数遍历函数数组。key 为方法名,若有 value 说明有具体的 function 声明,若无 value 说明使用了 get 或 set 方法。
结尾
阅读至此,相信大家对 Class 的实现有了更深的理解。笔者在整理这道阿里前端面试题解析的同时也在忙于毕业设计,欢迎在评论区讨论 Class 实现相关问题。另外,Class 中的 extend 实现也很有趣,将在下一篇文章中解析 Class 继承的实现。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
