将 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 的命令进行转译。
1. let 和 const
Section titled “1. let 和 const”let 允许声明块级作用域(block-scoped)的变量,而 const 声明的块级作用域变量的值不能被重新赋值。
ES2015 代码
Section titled “ES2015 代码”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); // 1console.log('Constant b:', b); // 10转译后的 ES5 代码 (简化版)
Section titled “转译后的 ES5 代码 (简化版)”"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。
2. 箭头函数
Section titled “2. 箭头函数”箭头函数提供了编写函数的简洁语法,并词法绑定(lexically bind)this 值(它们没有自己的 this)。
ES2015 代码
Section titled “ES2015 代码”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]转译后的 ES5 代码
Section titled “转译后的 ES5 代码”"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。
3. 类 (Classes)
Section titled “3. 类 (Classes)”ES2015 类是 JavaScript 现有基于原型的继承(prototype-based inheritance)之上的语法糖(syntactic sugar)。
ES2015 代码
Section titled “ES2015 代码”class Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; }}const person1 = new Person("Alice");console.log(person1.greet()); // Hello, Alice!转译后的 ES5 代码 (简化版)
Section titled “转译后的 ES5 代码 (简化版)”"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)上的方法,以及确保正确行为的辅助函数。
4. Promises
Section titled “4. Promises”Promises 提供了一种处理异步操作的方式。Babel 本身不会转译 Promise 对象本身的存在或其方法;它只转译可能使用 Promises 的语法(如 async/await)。要在不支持 Promises 的环境中使它们工作,你需要一个 polyfill(垫片)。
ES2015 代码
Section titled “ES2015 代码”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 一章。
5. 生成器 (Generators)
Section titled “5. 生成器 (Generators)”生成器函数(function*)可以暂停和恢复。转译生成器需要一个运行时辅助工具(runtime helper),通常是 regenerator-runtime。
ES2015 代码
Section titled “ES2015 代码”function* idMaker() { let index = 0; while (true) { yield index++; }}const gen = idMaker();console.log(gen.next().value); // 0console.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)”解构允许从数组中解包值或从对象中解包属性到不同的变量中。
ES2015 代码
Section titled “ES2015 代码”// Array destructuringconst [first, second] = [1, 2, 3];console.log(first, second); // 1 2
// Object destructuringconst { name, age } = { name: "Bob", age: 42 };console.log(name, age); // Bob 42转译后的 ES5 代码
Section titled “转译后的 ES5 代码”"use strict";
// Array destructuringvar _ref = [1, 2, 3], first = _ref[0], second = _ref[1];console.log(first, second);
// Object destructuringvar _ref2 = { name: "Bob", age: 42}, name = _ref2.name, age = _ref2.age;console.log(name, age);7. 迭代器和 for...of 循环
Section titled “7. 迭代器和 for...of 循环”for...of 循环遍历可迭代对象(iterable objects)。迭代器(Iterators)本身使用 Symbol.iterator 协议。
ES2015 代码
Section titled “ES2015 代码”const arr = ['a', 'b', 'c'];for (const val of arr) { console.log(val);}// Output: a, b, c (each on a new line)转译后的 ES5 代码 (数组简化版)
Section titled “转译后的 ES5 代码 (数组简化版)”"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}。
8. 模板字面量 (Template Literals)
Section titled “8. 模板字面量 (Template Literals)”模板字面量允许嵌入表达式和多行字符串。
ES2015 代码
Section titled “ES2015 代码”const personName = "Charlie";const greeting = `Hello, ${personName}!`;console.log(greeting); // Hello, Charlie!转译后的 ES5 代码
Section titled “转译后的 ES5 代码”"use strict";
var personName = "Charlie";var greeting = "Hello, " + personName + "!"; // or .concat()console.log(greeting);9. 增强对象字面量 (Enhanced Object Literals)
Section titled “9. 增强对象字面量 (Enhanced Object Literals)”属性名和方法定义的简写,以及计算属性名。
ES2015 代码
Section titled “ES2015 代码”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转译后的 ES5 代码 (简化版)
Section titled “转译后的 ES5 代码 (简化版)”"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)”默认参数 (ES2015)
Section titled “默认参数 (ES2015)”function multiply(a, b = 1) { return a * b;}console.log(multiply(5, 2)); // 10console.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));剩余参数 (Rest Parameters) (ES2015)
Section titled “剩余参数 (Rest Parameters) (ES2015)”function sum(...numbers) { return numbers.reduce((acc, current) => acc + current, 0);}console.log(sum(1, 2, 3)); // 6console.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);11. 代理 (Proxies)
Section titled “11. 代理 (Proxies)”代理 (Proxies) (ES2015) 允许创建一个对象,该对象可以用来代替另一个对象,但可以重新定义诸如获取、设置和定义属性等基本对象操作。转译完整的 Proxy 行为非常复杂且有局限性。
ES2015 代码
Section titled “ES2015 代码”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); // helloconsole.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 来管理。