JS在ES6标准下的改进以及容易混淆的地方
Table of Contents
以下是 JavaScript ES6(ECMAScript 2015)的一些重要改进,并附带了在使用这些新特性时,可能与旧版语法混淆的情况或示例。
1. let
和 const
替代 var
改进:
let
与var
区别在于前者是块级作用域(block scope),后者是函数作用域(function scope)。const
与let
类似,但它定义的是只读常量,一旦声明就不能重新赋值。
容易混淆的场景:
- 旧版
var
在循环或任何块级作用域内声明的变量,会“提升”到函数顶端,容易造成逻辑错误;使用let
则能严格限制变量的可见范围。 - 许多人在使用
const
时,以为“常量”就必须在编译期决定,或不能被修改对象属性,但实际上const
仅限定不能重新赋值其引用,若是复合数据类型(对象或数组)的属性值仍然可以修改。
示例:
// var 的怪异行为
function testVar() {
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 输出 3, 3, 3
}
function testLet() {
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 输出 0, 1, 2
}
2. 箭头函数(Arrow Functions)
改进:
- 语法更简洁:
() => {...}
- 箭头函数不绑定自己的
this
、arguments
、super
或new.target
,其this
是在词法层面上决定的。
容易混淆的场景:
- 旧的普通函数在函数体内部
this
绑定到调用该函数的对象;而箭头函数继承了外层上下文的this
。 - 若在 ES5 或更早的代码中使用
function
关键字编写回调函数时,往往会用that = this
或bind()
来保留外部this
;在箭头函数中就不需要了,可能导致新旧代码混着写时出现作用域、this
指向混乱的问题。
示例:
const obj = {
value: 42,
regularFunction: function() {
setTimeout(function() {
// 这里的 this 指向 setTimeout 自身的上下文(浏览器环境中通常是 window)
console.log("regularFunction this.value:", this.value); // undefined
}, 0);
},
arrowFunction: function() {
setTimeout(() => {
// 箭头函数的 this 指向 obj
console.log("arrowFunction this.value:", this.value); // 42
}, 0);
}
};
obj.regularFunction();
obj.arrowFunction();
3. 模板字符串(Template Literals)
改进:
- 可以使用反引号(```)来创建多行字符串,并通过
${变量}
进行插值。 - 大幅提高了字符串拼接的可读性。
容易混淆的场景:
- 以前拼接字符串常用
+
或String.concat()
,如果在传统写法和新写法混杂使用,容易遗漏特殊字符或加号。 - 一旦在模板字符串里少打了 ``` 或
${}
,可能会直接被当作普通字符串处理,引发错误。
示例:
let name = "Alice";
let oldWay = "Hello, " + name + "! How are you?";
let newWay = `Hello, ${name}! How are you?`;
4. 参数默认值(Default Parameters)
改进:
- 在函数形参定义位置就可以直接给出默认值,不再需要在函数体内编写
if
判断或三元运算。
容易混淆的场景:
- 旧的写法一般会先检查参数是否
undefined
,然后手动设置一个默认值;在 ES6 的默认参数写法里,依然可以调用arguments
,或者传入undefined
来触发默认值,但和旧写法同时使用时需要注意逻辑重复或覆盖。
示例:
function greet(name = "stranger") {
console.log(`Hello, ${name}`);
}
greet(); // Hello, stranger
greet("Bob"); // Hello, Bob
greet(undefined); // Hello, stranger
5. 解构赋值(Destructuring)
改进:
- 可以更方便地从对象和数组中提取数据,写法更简洁。
容易混淆的场景:
- 需要注意解构赋值的模式必须与被解构的结构相匹配,否则可能得到
undefined
。 - 在解构赋值和旧的“逐个变量赋值”写法混用时,容易打乱变量引用顺序或命名。
示例:
// 对象解构
const user = { name: "Alice", age: 25, location: "Earth" };
const { name, age, location } = user;
// 旧写法需要 user.name, user.age... 逐个赋值
// 数组解构
const arr = [1, 2, 3];
const [first, second, third] = arr;
6. 扩展运算符(Spread Operator ...
)
改进:
- 可以在函数调用、数组构造或对象合并时快速“展开”一个可迭代对象。
容易混淆的场景:
- 旧版中往往使用
apply
或者concat
等方法来合并数组、传递数组参数;将新旧写法混在一起时,可能会重复展开或遗漏展开。 - ES6 的扩展运算符和剩余参数语法形似(都使用
...
),但用途不同:剩余参数是收集多余参数,扩展运算符是展开可迭代对象。
示例:
// 数组合并
const arr1 = [1, 2];
const arr2 = [3, 4];
const newArr = [...arr1, ...arr2]; // [1, 2, 3, 4]
// 旧写法可能是:arr1.concat(arr2)
7. 模块化(Modules)
改进:
- ES6 原生支持
import
/export
关键字,实现模块化代码组织,不再依赖于传统的全局变量或第三方模块方案(如 CommonJS 的require
)。
容易混淆的场景:
- 旧版或 Node.js 环境里常用
require
/module.exports
;ES6 模块引入后,如果同一个项目里同时存在这两种模块系统,容易出现无法直接混用的问题,需要借助打包工具或特殊配置(如 Babel、Webpack)才能兼容。 - 在浏览器直接使用 ES6 模块时,需要确保
<script type="module">
标签以及正确的同源策略等。
示例:
// old.js (CommonJS 写法)
const fs = require('fs');
module.exports = {};
// new.js (ES6 模块写法)
import fs from 'fs';
export default {};
8. Class 语法糖
改进:
- 引入
class
、constructor
、extends
关键字,让面向对象的写法更直观。
容易混淆的场景:
- 在 ES5 或更早版本,类通常通过构造函数 + 原型链实现;ES6
class
本质上还是基于原型继承,但看上去更像其他面向对象语言的写法。混用时要注意方法定义和原型继承机制不要冲突。 class
定义的方法都是放在原型上,而不是在构造函数中定义。
示例:
// ES5 写法
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + " makes a noise.");
};
// ES6 Class 写法
class AnimalClass {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
9. Symbol 类型
改进:
Symbol
是一种新的原始数据类型,用于创建独一无二的标识,通常用作对象属性的 key。
容易混淆的场景:
- 旧版里没有等价概念,若在调试时或混用时不小心把
Symbol
当做字符串处理,可能出现无法枚举或找不到属性的情况。 typeof
得到的结果是"symbol"
,初学者若习惯处理字符串、数字等类型时,需要注意区分。
示例:
const sym = Symbol("description");
const obj = {};
obj[sym] = "value";
console.log(obj[sym]); // "value"
console.log(obj["sym"]); // undefined
10. Generator 函数
改进:
- 使用
function*
和yield
来实现一个可中断的函数,使异步流程或迭代器实现更加灵活。
容易混淆的场景:
- 旧版语法中没有
function*
写法,新手容易把它和普通函数写混。 - 同时在项目里使用回调、Promise、Generator 或
async/await
(ES8)时,可能造成异步逻辑分散难以统一。
示例:
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: undefined, done: true }
总结
- ES6 带来了许多语法糖(如模板字符串、类语法)与新的特性(如
let
/const
、解构、箭头函数、模块化支持等),让 JavaScript 更强大、易读、易维护。 - 在与旧版语法混用或从旧版向 ES6 迁移时,常见的混淆点包括:
var
与let
的作用域差异、箭头函数的this
、require
与import
的模块化冲突等。 - 在大型项目中,更建议选用较新的 ES6+ 特性并通过打包工具(如 Webpack、Rollup 等)统一编译,以减少语法混用带来的困扰。