ECMAScript新特性全解析
1. ECMAScript概述
ECMAScript是脚本语言的标准,其语法在多种语言中得以实现,其中最流行的实现便是JavaScript。1997年发布了ECMAScript规范的第一版,2015年第六版正式确定,这一版也被称为ES6或ES2015。与之前的ES5相比,ES6引入了许多新特性,本文主要探讨的也是ES6的语法。2016年和2017年分别确定了ES7和ES8,但它们并未引入太多新的语法元素,不过我们会在文末介绍ES8的async/await语法。
目前,大多数浏览器都完全支持ES6规范,你可以访问 ECMAScript兼容性网站 查看ES6的支持情况。即便用户使用的是旧版浏览器,你也可以使用ES6/7/8进行开发,然后通过Traceur、Babel或TypeScript等转译器将代码转换为ES5版本。
2. 代码示例运行方法
本文的代码示例以.js扩展名的JavaScript文件形式呈现。通常,代码示例会在控制台输出结果,因此你需要打开浏览器控制台查看输出。你可以创建一个简单的HTML文件,并使用 <script> 标签引入特定的.js文件。
另一种选择是使用 CodePen ,该网站允许你快速编写、测试和分享使用HTML、CSS和JavaScript的应用程序。为了节省你的输入时间,我们会为大多数代码示例提供CodePen链接,你只需点击链接即可查看代码示例的运行效果,并可根据需要进行修改。如果代码示例在控制台输出结果,只需点击CodePen窗口底部的“Console”即可查看。
3. 变量作用域与this
ES5的作用域机制较为复杂。无论使用 var 关键字在何处声明变量,该声明都会被提升到执行上下文(如函数)的顶部,这被称为“提升”(可查看 更多关于提升的内容 )。而且, this 关键字的使用也不像Java或C#那样直观。
ES6通过引入 let 关键字消除了提升带来的困惑,并通过箭头函数解决了 this 的问题。下面我们详细探讨提升和 this 的问题。
3.1 变量声明的提升
在JavaScript中,使用 var 关键字声明的所有变量,即使是在代码块内部声明的,其声明也会被提升到执行上下文的顶部。例如:
function foo() {
for (var i = 0; i < 10; i++) {
}
console.log("i=" + i);
}
foo();
运行这段代码会输出 i=10 。尽管变量 i 似乎只应在循环内部使用,但它在循环外部仍然可用,这是因为JavaScript会自动将变量声明提升到顶部。
在上述示例中,提升并未造成问题,因为只有一个名为 i 的变量。但如果在函数内外声明了两个同名变量,可能会导致混淆。例如:
var customer = "Joe";
(function () {
console.log("The name of the customer inside the function is " + customer);
/*
if (true) {
var customer = "Mary";
} */
})();
console.log("The name of the customer outside the function is " + customer);
全局变量 customer 在函数内外均可访问,运行这段代码会输出:
The name of the customer inside the function is Joe
The name of the customer outside the function is Joe
如果取消注释 if 语句,此时有两个同名变量,一个在全局作用域,另一个在函数作用域,控制台输出会变为:
The name of the customer inside the function is undefined
The name of the customer outside the function is Joe
这是因为在ES5中,变量声明会被提升到作用域的顶部,但变量的初始化不会。当变量被创建时,其初始值为 undefined 。第二个 customer 变量的声明被提升到顶部, console.log() 打印的是函数内部声明的变量的值,该值会覆盖全局变量 customer 的值。
函数声明也会被提升,因此我们可以在函数声明之前调用它:
doSomething();
function doSomething() {
console.log("I'm doing something");
}
然而,函数表达式被视为变量初始化,不会被提升。以下代码会输出 undefined :
doSomething();
var doSomething = function() {
console.log("I'm doing something");
}
3.2 let和const的块级作用域
使用ES6的 let 关键字声明变量可以实现块级作用域。例如:
let customer = "Joe";
(function () {
console.log("The name of the customer inside the function is " + customer);
if (true) {
let customer = "Mary";
console.log("The name of the customer inside the block is " + customer);
}
})();
console.log("The name of the customer in the global scope is " + customer);
这段代码会输出:
The name of the customer inside the function is Joe
The name of the customer inside the block is Mary
The name of the customer in the global scope is Joe
简单来说,在开发新应用时,建议使用 let 而非 var 。 let 允许你多次为变量赋值和重新赋值,但在 for 循环中使用 let 声明循环变量可能会导致性能问题。
如果你想声明一个初始化后值不会改变的变量,可以使用 const 关键字。常量也支持块级作用域, let 和 const 的唯一区别在于, const 不允许改变已赋值的变量。最佳实践是先使用 const 声明变量,如果发现需要改变其值,再将 const 替换为 let 。
4. 模板字面量
ES6引入了一种处理字符串字面量的新语法,允许在字符串中嵌入表达式,这一特性被称为字符串插值。
在ES5中,我们使用字符串拼接来创建包含变量值的字符串:
const customerName = "John Smith";
console.log("Hello" + customerName);
在ES6中,模板字面量使用反引号(`)包围,我们可以通过在花括号内放置表达式,并在前面加上美元符号($)来嵌入表达式。例如:
const customerName = "John Smith";
console.log(`Hello ${customerName}`);
function getCustomer() {
return "Allan Lou";
}
console.log(`Hello ${getCustomer()}`);
这段代码会输出:
Hello John Smith
Hello Allan Lou
我们可以在花括号内使用任何有效的JavaScript表达式。
4.1 多行字符串
使用反引号,我们可以编写多行字符串,而无需进行拼接或使用反斜杠字符:
const message = `Please enter a password that
has at least 8 characters and
includes a capital letter`;
console.log(message);
输出结果会保留所有空格:
Please enter a password that
has at least 8 characters and
includes a capital letter
5. 可选参数和默认值
在ES6中,我们可以为函数参数指定默认值,当函数调用时未提供该参数的值时,将使用默认值。例如,我们编写一个计算税收的函数,该函数接受两个参数:年收入和居住州。如果未提供州的值,我们希望默认使用佛罗里达州。
在ES5中,我们需要在函数体开始时检查州的值是否提供,否则使用佛罗里达州:
function calcTaxES5(income, state) {
state = state || "Florida";
console.log("ES5. Calculating tax for the resident of " + state + " with the income " + income);
}
calcTaxES5(50000);
这段代码会输出:
ES5. Calculating tax for the resident of Florida with the income 50000
在ES6中,我们可以直接在函数签名中指定默认值:
function calcTaxES6(income, state = "Florida") {
console.log("ES6. Calculating tax for the resident of " + state + " with the income " + income);
}
calcTaxES6(50000);
输出结果类似:
ES6. Calculating tax for the resident of Florida with the income 50000
我们还可以调用一个返回默认值的函数来提供可选参数的默认值:
function calcTaxES6(income, state = getDefaultState()) {
console.log("ES6. Calculating tax for the resident of " + state + " with the income " + income);
};
function getDefaultState() {
return "Florida";
}
calcTaxES6(50000);
需要注意的是,每次调用 calcTaxES6() 时都会调用 getDefaultState() 函数,这可能会对性能产生影响。这种新的可选参数语法使代码更简洁、易读。
6. 箭头函数表达式、this和that
ES6引入了箭头函数表达式,它为匿名函数提供了更简洁的表示法,并为 this 变量添加了词法作用域。在其他一些编程语言(如C#和Java)中,类似的语法被称为lambda表达式。
箭头函数表达式的语法由参数、胖箭头符号( => )和函数体组成。如果函数体只有一个表达式,甚至不需要花括号。如果单表达式函数返回一个值,则无需编写 return 语句,结果会隐式返回:
let sum = (arg1, arg2) => arg1 + arg2;
多行箭头函数表达式的函数体需要用花括号包围,并使用显式的 return 语句:
(arg1, arg2) => {
// do something
return someResult;
}
如果箭头函数没有参数,使用空括号:
() => {
// do something
return someResult;
}
如果函数只有一个参数,括号不是必需的:
arg1 => {
// do something
}
以下代码示例展示了如何将箭头函数表达式作为参数传递给数组的 reduce() 方法来计算总和,以及传递给 filter() 方法来打印偶数:
const myArray = [1, 2, 3, 4, 5];
console.log( "The sum of myArray elements is " + myArray.reduce((a,b) => a + b));
// 输出15
console.log( "The even numbers in myArray are " + myArray.filter( value => value % 2 === 0));
// 输出2 4
在ES5中,确定 this 关键字所引用的对象并非易事。搜索“JavaScript this and that”,你会发现很多人抱怨 this 指向了“错误”的对象。 this 引用的值取决于函数的调用方式以及是否使用了严格模式(可查看 Mozilla开发者网络上关于严格模式的文档 )。我们先通过一个示例说明问题,再展示ES6提供的解决方案。
考虑以下代码,它每秒调用一次匿名函数,该函数会打印 StockQuoteGenerator 构造函数提供的股票代码的随机生成价格:
function StockQuoteGenerator(symbol) {
// this.symbol = symbol;
const that = this;
that.symbol = symbol;
setInterval( function () {
console.log("The price of " + that.symbol + " is " + Math.random());
}, 1000);
}
const stockQuoteGenerator = new StockQuoteGenerator("IBM");
注释掉的那行代码展示了在匿名函数中错误使用 this 的方式。如果我们没有将 this 的值保存到 that 中,而是在匿名函数中使用 this.symbol ,它会打印 undefined 而非 IBM 。不仅在 setInterval() 中调用函数会出现这种情况,在任何回调函数中调用也会如此。在回调函数内部, this 会指向全局对象,这与 StockQuoteGenerator() 构造函数中定义的 this 不同。
另一种确保函数在特定 this 对象中运行的方法是使用JavaScript的 call() 、 apply() 或 bind() 函数。
以下是使用箭头函数解决该问题的示例:
function StockQuoteGenerator(symbol) {
this.symbol = symbol;
setInterval( () => {
console.log("The price of " + this.symbol + " is " + Math.random());
}, 1000);
}
const stockQuoteGenerator = new StockQuoteGenerator("IBM");
这段代码能够正确解析 this 引用,传递给 setInterval() 的箭头函数使用了其封闭上下文的 this 值,因此它会将 IBM 识别为 this.symbol 的值。
7. 剩余参数运算符
在ES5中,编写具有可变数量参数的函数需要使用特殊的 arguments 对象,该对象类似于数组,包含传递给函数的参数值。隐式的 arguments 变量可被视为任何函数中的局部变量。
ES6的剩余参数运算符用三个点( ... )表示,它允许函数接受可变数量的参数,并将这些参数作为数组处理。剩余参数运算符必须是参数列表中的最后一个。例如:
function processCustomers(...customers) {
// 函数实现代码
}
在函数内部,我们可以像处理任何数组一样处理 customers 数据。假设我们需要编写一个计算税收的函数,该函数的第一个参数是收入,后面可以跟任意数量的代表客户姓名的参数。以下是使用ES5和ES6语法处理可变数量参数的示例:
// ES5和arguments对象
function calcTaxES5() {
console.log("ES5. Calculating tax for customers with the income ", arguments[0]);
// 收入是第一个元素
// 从第二个元素开始提取数组
var customers = [].slice.call(arguments, 1);
customers.forEach(function (customer) {
console.log("Processing ", customer);
});
}
calcTaxES5(50000, "Smith", "Johnson", "McDonald");
calcTaxES5(750000, "Olson", "Clinton");
// ES6和剩余参数运算符
function calcTaxES6(income, ...customers) {
console.log(`ES6. Calculating tax for customers with the income ${income}`);
customers.forEach( (customer) => console.log(`Processing ${customer}`));
}
calcTaxES6(50000, "Smith", "Johnson", "McDonald");
calcTaxES6(750000, "Olson", "Clinton");
两个函数的输出结果相同:
ES5. Calculating tax for customers with the income 50000
Processing Smith
Processing Johnson
Processing McDonald
ES5. Calculating tax for customers with the income 750000
Processing Olson
Processing Clinton
ES6. Calculating tax for customers with the income 50000
Processing Smith
Processing Johnson
Processing McDonald
ES6. Calculating tax for customers with the income 750000
Processing Olson
Processing Clinton
但在处理客户数据时存在差异。由于 arguments 对象不是真正的数组,在ES5版本中,我们需要使用 slice() 和 call() 方法从 arguments 的第二个元素开始提取客户姓名。而ES6版本则无需这些技巧,因为剩余参数运算符会直接提供一个常规的客户数组,使代码更简单、易读。
8. 扩展运算符
ES6的扩展运算符同样用三个点( ... )表示,但与剩余参数运算符不同,它可以将数组转换为值列表或函数参数。
提示:如果在等号右侧看到三个点,那就是扩展运算符;如果在等号左侧看到三个点,则表示剩余参数运算符。
假设我们有两个数组,需要将第二个数组的元素添加到第一个数组的末尾,使用扩展运算符只需一行代码:
let array1 = [...array2];
这里,扩展运算符会提取 myArray 的每个元素并添加到新数组中(方括号表示“创建一个新数组”)。我们还可以通过以下方式创建数组的副本:
array1.push(...array2);
综上所述,ES6引入的这些新特性极大地提升了JavaScript的开发效率和代码可读性,让开发者能够更轻松地编写高质量的代码。希望本文能帮助你更好地理解和应用这些新特性。
ECMAScript新特性全解析
9. 总结与对比
为了更清晰地展示ES5和ES6在各个特性上的差异,下面通过表格进行对比:
| 特性 | ES5 | ES6 |
| — | — | — |
| 变量声明与作用域 | 使用 var ,存在变量提升问题,作用域不清晰 | 使用 let 和 const 实现块级作用域,避免提升带来的困惑 |
| 字符串处理 | 使用字符串拼接 | 引入模板字面量,支持字符串插值和多行字符串 |
| 函数参数 | 需要在函数内部检查参数是否提供,手动设置默认值 | 可以直接在函数签名中指定默认值 |
| 匿名函数 | 传统匿名函数语法, this 指向问题复杂 | 箭头函数表达式,简化语法, this 指向封闭上下文 |
| 可变参数处理 | 使用 arguments 对象,处理复杂 | 剩余参数运算符将可变参数转换为数组,处理简单 |
| 数组操作 | 操作数组元素较繁琐 | 扩展运算符可轻松实现数组元素的添加和复制 |
通过这个表格,我们可以更直观地看到ES6在各个方面对ES5的改进,这些改进使得代码更加简洁、易读和可维护。
10. 实际应用场景分析
10.1 前端开发中的应用
在前端开发中,ES6的新特性可以显著提升开发效率和代码质量。例如,在处理用户界面交互时,经常需要动态更新页面内容,使用模板字面量可以更方便地生成包含变量的HTML字符串。
const userName = "Alice";
const userAge = 25;
const userInfo = `
<div>
<p>Name: ${userName}</p>
<p>Age: ${userAge}</p>
</div>
`;
document.getElementById('user-info').innerHTML = userInfo;
在处理事件监听时,箭头函数可以避免 this 指向问题,使代码更加清晰。
const button = document.getElementById('my-button');
button.addEventListener('click', () => {
console.log('Button clicked');
});
10.2 后端开发中的应用
在后端开发中,如使用Node.js,ES6的新特性同样具有重要价值。例如,在处理路由和中间件时,剩余参数运算符可以方便地处理可变数量的参数。
function handleRequest(req, res, ...middlewares) {
middlewares.forEach(middleware => middleware(req, res));
// 处理请求逻辑
res.send('Request handled');
}
function middleware1(req, res) {
console.log('Middleware 1 executed');
}
function middleware2(req, res) {
console.log('Middleware 2 executed');
}
handleRequest({}, {}, middleware1, middleware2);
11. 注意事项与最佳实践
11.1 注意事项
-
let和const的使用 :在for循环中使用let声明循环变量时,要注意可能的性能问题。如果循环体内部没有复杂的操作,使用var可能更合适。 - 箭头函数的限制 :箭头函数没有自己的
this、arguments、super或new.target,因此不能使用arguments对象,也不能使用yield关键字。如果需要这些特性,应使用传统的函数声明。 - 扩展运算符的性能 :在处理大型数组时,扩展运算符可能会导致性能问题,因为它需要创建新的数组副本。在这种情况下,可以考虑使用更高效的方法。
11.2 最佳实践
- 变量声明 :优先使用
const声明变量,只有在需要改变变量值时才使用let。这样可以提高代码的可读性和可维护性。 - 模板字面量 :当需要拼接包含变量的字符串时,优先使用模板字面量,避免使用传统的字符串拼接方法。
- 箭头函数 :在处理简单的回调函数时,使用箭头函数可以简化代码。但在需要使用
this指向当前对象的情况下,使用传统函数声明。
12. 未来发展趋势
随着Web技术的不断发展,ECMAScript也在持续演进。未来,我们可以期待更多的新特性和改进。例如,ESNext可能会引入更强大的异步编程模型、更高效的数据处理方法等。作为开发者,我们应该保持学习的热情,及时了解和掌握这些新特性,以便在实际项目中更好地应用。
同时,浏览器和Node.js等运行环境对ECMAScript新特性的支持也会不断完善。这意味着我们可以更放心地使用新特性进行开发,而不用担心兼容性问题。
13. 总结
ECMAScript 6(ES6)为JavaScript带来了许多强大的新特性,这些特性在变量声明、字符串处理、函数参数、匿名函数、可变参数处理和数组操作等方面都有显著的改进。通过使用 let 和 const 实现块级作用域,避免了变量提升带来的困惑;模板字面量使字符串处理更加灵活;箭头函数简化了匿名函数的语法,解决了 this 指向问题;剩余参数运算符和扩展运算符则为处理可变参数和数组操作提供了更便捷的方式。
在实际开发中,我们应该根据具体的应用场景合理使用这些新特性,遵循最佳实践,注意可能出现的问题。同时,关注ECMAScript的未来发展趋势,不断提升自己的技术水平,以适应不断变化的开发需求。希望本文能够帮助你更好地理解和应用ES6的新特性,在JavaScript开发中取得更好的成果。
下面是一个简单的mermaid流程图,展示了在开发中选择使用 let 和 const 的决策过程:
graph TD;
A[开始声明变量] --> B{变量值是否会改变?};
B -- 否 --> C[使用const];
B -- 是 --> D[使用let];
这个流程图清晰地展示了在声明变量时,根据变量值是否会改变来选择使用 let 还是 const 的决策过程,遵循这个流程可以帮助我们更好地使用ES6的变量声明特性。
超级会员免费看
1339

被折叠的 条评论
为什么被折叠?



