Skip to content

将 ES6 特性转译到 ES5

BabelJS - 将 ES2015 (ES6) 特性转译到 ES5

Section titled “BabelJS - 将 ES2015 (ES6) 特性转译到 ES5”

ECMAScript 2015 (ES6) 是 JavaScript 的一个重大更新,引入了大量新的语法和特性,显著提高了开发效率和代码组织能力。BabelJS,主要通过 @babel/preset-env,在允许开发者使用这些新特性的同时,保持与只支持 ES5 的旧环境兼容方面发挥着关键作用。

本章将介绍一些关键的 ES2015 特性以及 Babel 如何转译它们。假设你有一个基本的 Babel 设置,并在 babel.config.json 中配置了 @babel/preset-env:

{
"presets": ["@babel/preset-env"]
}

并且你使用类似 npx babel input.js --out-file output.js 的命令进行转译。

let 允许声明块级作用域(block-scoped)的变量,而 const 声明的块级作用域变量的值不能被重新赋值。

let a = 1;
const b = 10;
if (true) {
let a = 2; // Different 'a' (block-scoped)
// b = 20; // This would cause an error: Assignment to constant variable.
console.log('Inside if, a:', a); // 2
}
console.log('Outside if, a:', a); // 1
console.log('Constant b:', b); // 10
"use strict";
var a = 1;
var b = 10;
if (true) {
var _a = 2; // Babel renames to avoid conflict, simulating block scope
// b = 20;
console.log('Inside if, a:', _a);
}
console.log('Outside if, a:', a);
console.log('Constant b:', b);

Babel 将 let 和 const 转换为 var。对于块内的 let,它通常会重命名变量以模拟块级作用域。const 的重新赋值保护是 Babel 在严格模式下或通过 Linter 进行的编译时检查;在 ES5 中,它只是一个 var。

箭头函数提供了编写函数的简洁语法,并词法绑定(lexically bind)this 值(它们没有自己的 this)。

const add = (x, y) => x + y;
console.log(add(5, 3)); // 8
const numbers = [1, 2, 3];
const squares = numbers.map(n => n * n);
console.log(squares); // [1, 4, 9]
"use strict";
var add = function add(x, y) {
return x + y;
};
console.log(add(5, 3));
var numbers = [1, 2, 3];
var squares = numbers.map(function (n) {
return n * n;
});
console.log(squares);

Babel 将箭头函数转换为传统的函数表达式。词法 this 的行为由 Babel 保留,确保如果在箭头函数内部使用了 this,会捕获到正确的 this。

ES2015 类是 JavaScript 现有基于原型的继承(prototype-based inheritance)之上的语法糖(syntactic sugar)。

class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
const person1 = new Person("Alice");
console.log(person1.greet()); // Hello, Alice!
"use strict";
function _classCallCheck(instance, Constructor) { /* ... */ }
function _defineProperties(target, props) { /* ... */ }
function _createClass(Constructor, protoProps, staticProps) { /* ... */ }
var Person = /*#__PURE__*/function () {
function Person(name) {
_classCallCheck(this, Person);
this.name = name;
}
_createClass(Person, [{
key: "greet",
value: function greet() {
return "Hello, ".concat(this.name, "!");
}
}]);
return Person;
}();
var person1 = new Person("Alice");
console.log(person1.greet());

Babel 将类语法转换为构造函数和附加到原型(prototype)上的方法,以及确保正确行为的辅助函数。

Promises 提供了一种处理异步操作的方式。Babel 本身不会转译 Promise 对象本身的存在或其方法;它只转译可能使用 Promises 的语法(如 async/await)。要在不支持 Promises 的环境中使它们工作,你需要一个 polyfill(垫片)。

const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise resolved!");
}, 100);
});
myPromise.then(message => console.log(message));

转译后的 ES5 代码 (语法保持相似)

Section titled “转译后的 ES5 代码 (语法保持相似)”
"use strict";
var myPromise = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Promise resolved!");
}, 100);
});
myPromise.then(function (message) {
return console.log(message);
});

要在旧浏览器中使其工作:配置 @babel/preset-env,设置 "useBuiltIns": "usage" 和 "corejs": 3,并安装 core-js。然后,如果你的目标环境需要,Babel 将自动包含 Promise 的 polyfill。请参阅 Polyfills 一章。

生成器函数(function*)可以暂停和恢复。转译生成器需要一个运行时辅助工具(runtime helper),通常是 regenerator-runtime。

function* idMaker() {
let index = 0;
while (true) {
yield index++;
}
}
const gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1

转译后的 ES5 代码 (简化版,包含 regeneratorRuntime)

Section titled “转译后的 ES5 代码 (简化版,包含 regeneratorRuntime)”
"use strict";
var _marked = /*#__PURE__*/regeneratorRuntime.mark(idMaker);
function idMaker() {
var index;
return regeneratorRuntime.wrap(function idMaker$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
index = 0;
case 1:
if (!true) {
_context.next = 7;
break;
}
_context.next = 4;
return index++;
case 4:
_context.next = 1;
break;
case 7:
case "end":
return _context.stop();
}
}
}, _marked);
}
var gen = idMaker();
console.log(gen.next().value);
console.log(gen.next().value);

Babel 将生成器函数转换为状态机(state machines)。为了使它工作,@babel/preset-env(在适当地配置了 core-js 和 useBuiltIns,或者你显式地导入了 regenerator-runtime/runtime 时)将确保 regeneratorRuntime 可用。

6. 解构赋值 (Destructuring Assignment)

Section titled “6. 解构赋值 (Destructuring Assignment)”

解构允许从数组中解包值或从对象中解包属性到不同的变量中。

// Array destructuring
const [first, second] = [1, 2, 3];
console.log(first, second); // 1 2
// Object destructuring
const { name, age } = { name: "Bob", age: 42 };
console.log(name, age); // Bob 42
"use strict";
// Array destructuring
var _ref = [1, 2, 3],
first = _ref[0],
second = _ref[1];
console.log(first, second);
// Object destructuring
var _ref2 = {
name: "Bob",
age: 42
},
name = _ref2.name,
age = _ref2.age;
console.log(name, age);

for...of 循环遍历可迭代对象(iterable objects)。迭代器(Iterators)本身使用 Symbol.iterator 协议。

const arr = ['a', 'b', 'c'];
for (const val of arr) {
console.log(val);
}
// Output: a, b, c (each on a new line)
"use strict";
var arr = ['a', 'b', 'c'];
for (var _i = 0; _i < arr.length; _i++) {
var val = arr[_i];
console.log(val);
}
// Note: For generic iterables, the transform is more complex and involves iterator protocol checks.

直接使用 Symbol.iterator 或在非数组的可迭代对象上使用 for...of 可能需要 Symbol 和特定迭代器方法的 polyfill,如果目标环境非常旧的话。core-js(通过 @babel/preset-env)会处理这一点。

测试 Symbol.iterator (旧浏览器需要 polyfill)

Section titled “测试 Symbol.iterator (旧浏览器需要 polyfill)”

原始代码:

let numbers = [4, 7, 3, 10];
let iterator = numbers[Symbol.iterator]();
console.log(iterator.next()); // {value: 4, done: false}

转译后的代码将保留 Symbol.iterator。如果你的目标环境不支持 Symbol,你需要确保通过 core-js 提供了 Symbol 的 polyfill。以下是一个用于测试 polyfill 的 HTML 示例:

<!-- Assuming bundle.js is created by a bundler after Babel and core-js processing -->
<html>
<head>
<title>Iterator Test</title>
</head>
<body>
<h1>Iterators</h1>
<script src="bundle.js"></script>
</body>
</html>

在浏览器(即使是旧版本,只要提供了 polyfill)的控制台中将显示 {value: 4, done: false}。

模板字面量允许嵌入表达式和多行字符串。

const personName = "Charlie";
const greeting = `Hello, ${personName}!`;
console.log(greeting); // Hello, Charlie!
"use strict";
var personName = "Charlie";
var greeting = "Hello, " + personName + "!"; // or .concat()
console.log(greeting);

9. 增强对象字面量 (Enhanced Object Literals)

Section titled “9. 增强对象字面量 (Enhanced Object Literals)”

属性名和方法定义的简写,以及计算属性名。

const x = 1, y = 2;
const propName = "dynamicProp";
const obj = {
x, // Shorthand property
y,
greet() { // Shorthand method
return "Hi!";
},
[propName]: "value" // Computed property name
};
console.log(obj.x, obj.greet(), obj.dynamicProp); // 1 Hi! value
"use strict";
function _defineProperty(obj, key, value) { /* ... */ return obj; }
var x = 1,
y = 2;
var propName = "dynamicProp";
var obj = _defineProperty({
x: x,
y: y,
greet: function greet() {
return "Hi!";
}
}, propName, "value");
console.log(obj.x, obj.greet(), obj.dynamicProp);

10. 默认参数、剩余参数和展开语法 (Default, Rest & Spread Parameters/Properties)

Section titled “10. 默认参数、剩余参数和展开语法 (Default, Rest & Spread Parameters/Properties)”
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5, 2)); // 10
console.log(multiply(5)); // 5

转译后:

"use strict";
function multiply(a) {
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return a * b;
}
console.log(multiply(5, 2));
console.log(multiply(5));
function sum(...numbers) {
return numbers.reduce((acc, current) => acc + current, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

转译后 (简化版):

"use strict";
function sum() {
for (var _len = arguments.length, numbers = new Array(_len), _key = 0; _key < _len; _key++) {
numbers[_key] = arguments[_key];
}
return numbers.reduce(function (acc, current) {
return acc + current;
}, 0);
}
console.log(sum(1, 2, 3));
console.log(sum(1, 2, 3, 4, 5));

展开语法 (Spread Syntax) (数组为 ES2015,对象为 ES2018)

Section titled “展开语法 (Spread Syntax) (数组为 ES2015,对象为 ES2018)”

用于数组:

const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // [1, 2, 3, 4]

转译后:

"use strict";
function _toConsumableArray(arr) { /* ... helper ... */ }
var arr1 = [1, 2];
var arr2 = [].concat(_toConsumableArray(arr1), [3, 4]);
console.log(arr2);

代理 (Proxies) (ES2015) 允许创建一个对象,该对象可以用来代替另一个对象,但可以重新定义诸如获取、设置和定义属性等基本对象操作。转译完整的 Proxy 行为非常复杂且有局限性。

const target = { message1: "hello", message2: "everyone" };
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : "property does not exist";
}
};
const p = new Proxy(target, handler);
console.log(p.message1); // hello
console.log(p.nonExistent); // property does not exist

转译后的 ES5 代码 (语法保持,需要 Polyfill)

Section titled “转译后的 ES5 代码 (语法保持,需要 Polyfill)”
"use strict";
var target = { message1: "hello", message2: "everyone" };
var handler = {
get: function get(obj, prop) {
return prop in obj ? obj[prop] : "property does not exist";
}
};
var p = new Proxy(target, handler);
console.log(p.message1);
console.log(p.nonExistent);

Babel 不会(也通常无法)将 Proxy 对象本身转译成完全等效的 ES5 代码,因为 Proxy 具有反射特性。要在不支持 Proxy 的环境中使它们工作,需要一个 polyfill(如来自 core-js 或独立的 Proxy polyfill)。然而,Proxy polyfill 通常有局限性,无法完美复制所有 Proxy 行为,特别是对于某些陷阱(traps)。通常建议仅在目标环境原生支持 Proxy 时使用它们,或者在清楚了解 polyfill 局限性的前提下使用。

此概述展示了 Babel 如何处理许多常见的 ES2015 特性,将语法转换为与 ES5 兼容。对于涉及新的内置对象或方法的特性(如 Promises、Symbols、新的 Array 方法或 Proxies),为了在旧环境中获得运行时支持,polyfill 是必不可少的,这通常通过配置带有 core-js 的 @babel/preset-env 来管理。