JAVASCRIPT 基础教程
JAVASCRIPT & DOM
JAVASCRIPT & BOM
JAVASCRIPT 高级教程
JAVASCRIPT 示例
JAVASCRIPT 参考

JavaScript ES6 JavaScript ES6 功能

在本教程中,您将了解最新版本 JavaScript 中的新功能。

什么是 ECMAScript 6(或 ES6)

ECMAScript 2015(或 ES6)是 ECMAScript 语言规范标准的第六版和主要版本。 它定义了 JavaScript 实现的标准。

ES6 为 JavaScript 语言带来了重大变化。 它引入了一些新功能,例如块范围变量、用于迭代数组和对象的新循环、模板字面量以及许多其他增强功能,以使 JavaScript 编程更容易和更有趣。 在本章中,我们将讨论一些可以在日常 JavaScript 编码中使用的最佳 ES6 特性。

let 关键字

ES6 引入了新的 let 关键字来声明变量。 在 ES6 之前,在 JavaScript 中声明变量的唯一方法是 var 关键字。 让我们看看它们之间有什么区别。

varlet 之间有两个关键区别。 使用 var 关键字声明的变量在其作用域的顶部是 function-scopedhoisted,而使用 let 关键字声明的变量是块作用域({})并且它们不会被提升。

块范围仅意味着在一对大括号之间创建一个新作用域,即 {}。 因此,如果您在循环内使用 let 关键字声明变量,则它不存在于循环外,如下例所示:

// ES6 syntax
for(let i = 0; i < 5; i++) {
    console.log(i); // 0,1,2,3,4
}
console.log(i); // undefined


// ES5 语法
for(var i = 0; i < 5; i++) {
    console.log(i); // 0,1,2,3,4
}
console.log(i); // 5

正如您在上面的示例中看到的那样,第一个块中的变量 ifor 循环之外是不可访问的。 这也使我们能够多次重用同一个变量名,因为它的作用域仅限于块 ({}),这会导致更少的变量声明和更简洁的代码。


const 关键字

新的 const 关键字可以定义常量。 常量是只读的,您不能为它们重新分配新值。 它们也像 let 一样是块范围的。

const PI = 3.14;
console.log(PI); // 3.14

PI = 10; // error

但是,您仍然可以更改对象属性或数组元素:

// 更改对象属性值
const PERSON = {name: "Peter", age: 28};
console.log(PERSON.age); // 28
PERSON.age = 30;
console.log(PERSON.age); // 30

// Changing array element
const COLORS = ["red", "green", "blue"];
console.log(COLORS[0]); // red
COLORS[0] = "yellow";
console.log(COLORS[0]); // yellow

for...of 循环

新的 for...of 循环允许我们非常轻松地迭代数组或其他可迭代对象。 此外,循环内的代码针对可迭代对象的每个元素执行。 这是一个例子:

// Iterating over array
let letters = ["a", "b", "c", "d", "e", "f"];

for(let letter of letters) {
    console.log(letter); // a,b,c,d,e,f
}

// 遍历字符串
let greet = "Hello World!";

for(let character of greet) {
    console.log(character); // H,e,l,l,o, ,W,o,r,l,d,!
}

for...of 循环不适用于对象,因为它们不可迭代。 如果要遍历对象的属性,可以使用 for-in 循环。


模板文字

模板文字提供了一种简单而干净的方式来创建多行字符串并执行字符串插值。 现在我们可以在任何位置将变量或表达式嵌入到字符串中,而无需任何麻烦。

模板文字是使用反引号 (` `)(重音)字符而不是通常的双引号或单引号创建的。 可以使用 ${...} 语法将变量或表达式放在字符串中。 比较以下示例,看看它有多大用处:

// 简单的多行字符串
let str = `The quick brown fox
    jumps over the lazy dog.`;

// 带有嵌入变量和表达式的字符串
let a = 10;
let b = 20;
let result = `The sum of ${a} and ${b} is ${a+b}.`;
console.log(result); // 10 和 20 的和是 30。

在 ES5 中,为了达到同样的效果,我们必须编写如下代码:

// 多行字符串
var str = 'The quick brown fox\n\t'
    + 'jumps over the lazy dog.';

// 使用变量和表达式创建字符串
var a = 10;
var b = 20;
var result = 'The sum of ' + a + ' and ' + b + ' is ' + (a+b) + '.';
console.log(result); // 10 和 20 的和是 30。

函数参数的默认值

现在,在 ES6 中,您可以为 函数参数 指定默认值。 这意味着如果在调用函数时没有为函数提供参数,则将使用这些默认参数值。 这是 JavaScript 中最受期待的特性之一。 这是一个例子:

function sayHello(name='World') {
    return `Hello ${name}!`;
}

console.log(sayHello()); // Hello World!
console.log(sayHello('John')); // Hello John!

在 ES5 中,为了达到同样的效果,我们必须编写如下代码:

function sayHello(name) {
    var name = name || 'World'; 
    return 'Hello ' +  name + '!';
}

console.log(sayHello()); // Hello World!
console.log(sayHello('John')); // Hello John!

箭头函数

箭头函数是 ES6 中另一个有趣的特性。 通过选择退出 functionreturn 关键字,它为编写 函数表达式 提供了更简洁的语法。

箭头函数使用新语法定义,即粗箭头 (=>) 表示法。 让我们看看它的外观:

// 函数表达式
var sum = function(a, b) {
    return a + b;
}
console.log(sum(2, 3)); // 5

// 箭头函数
var sum = (a, b) => a + b;
console.log(sum(2, 3)); // 5

如您所见,箭头函数声明中没有 functionreturn 关键字。

您也可以跳过括号,即 () 以防只有一个参数,但当您有零个或多个参数时,您总是需要使用它。

此外,如果函数体中有多个表达式,则需要将其包裹起来 ({})。 在这种情况下,您还需要使用 return 语句来返回一个值。

如何编写箭头函数有多种变体。 以下是最常用的:

// Single parameter, single statement
var greet = name => alert("Hi " + name + "!");
greet("Peter"); // Hi Peter!

// 多个参数,单个语句
var multiply = (x, y) => x * y;
alert(multiply(2, 3)); // 6


// 单参数,多语句
var test = age => {
    if(age > 18) {
        alert("Adult");
    } else {
        alert("Teenager");
    }
}
test(21); // Adult

// 多参数,多语句
var divide = (x, y) => {
    if(y != 0) {
        return x / y;
    }
}
alert(divide(10, 2)); // 5

// 无参数,单语句
var hello = () => alert('Hello World!');
hello(); // Hello World!

常规函数和箭头函数之间有一个重要的区别。 与普通函数不同,箭头函数没有自己的 this,它从定义它的外部函数中获取 this。 在 JavaScript 中,this 是函数的当前执行上下文。

为了清楚地理解这一点,让我们看看下面的例子:

function Person(nickname, country) {
    this.nickname = nickname;
    this.country = country;
    
    this.getInfo = function() {
        // 外部函数上下文(Person 对象)
        return function() {
            // 内部函数上下文(全局对象"Window")
            alert(this.constructor.name); // Window
            alert("Hi, I'm " + this.nickname + " from " + this.country);
        };
    }
}

var p = new Person('Rick', 'Argentina');
var printInfo = p.getInfo();
printInfo(); // Hi, I'm undefined from undefined

使用 ES6 模板文字和箭头函数重写相同的示例:

function Person(nickname, country) {
    this.nickname = nickname;
    this.country = country;
    
    this.getInfo = function() {
        // 外部函数上下文(Person 对象)
        return () => {
            // 内部函数上下文(Person 对象)
            alert(this.constructor.name); // Person
            alert(`Hi, I'm ${this.nickname} from ${this.country}`);
        };
    }
}

let p = new Person('Rick', 'Argentina');
let printInfo = p.getInfo();
printInfo(); // Hi, I'm Rick from Argentina

如您所见,上例中的 this 关键字指的是包含箭头函数的函数的上下文,即 Person 对象(第9行),与前面的示例不同,它指的是 全局对象 Window第9行)。


在 ECMAScript 5 及更早的版本中,JavaScript 中从未存在类。 类是在 ES6 中引入的,它看起来与其他面向对象语言(如 Java、PHP 等)中的类相似,但它们的工作方式并不完全相同。 ES6 类使创建对象、使用 extends 关键字实现继承以及重用代码变得更加容易。

在 ES6 中,您可以使用新的 class 关键字后跟类名来声明一个类。 按照惯例,类名以 TitleCase 书写(即每个单词的首字母大写)。

class Rectangle {
    // 类构造函数
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
    
    // 类方法
    getArea() {
        return this.length * this.width;
    }
}

// Square 类继承自 Rectangle 类
class Square extends Rectangle {
    // 子类构造函数
    constructor(length) {
        // 调用父级的构造函数
        super(length, length);
    }
    
    // 子类方法
    getPerimeter() {
        return 2 * (this.length + this.width);
    }
}

let rectangle = new Rectangle(5, 10);
alert(rectangle.getArea()); // 50

let square = new Square(5);
alert(square.getArea()); // 25
alert(square.getPerimeter()); // 20

alert(square instanceof Square); // true
alert(square instanceof Rectangle); // true
alert(rectangle instanceof Square); // false

在上面的示例中,Square 类使用 extends 关键字从 Rectangle 继承。 从其他类继承的类称为派生类或子类。

此外,在访问上下文 (this) 之前,您必须在子类构造函数中调用 super()。 例如,如果省略 super() 并在 square 对象上调用 getArea() 方法,则会导致错误,因为 getArea() 方法需要访问 this 关键字。

注意: 与函数声明不同,类声明不会提升。 类声明驻留在临时死区 (TDZ) 中,直到执行到达类声明点,类似于 letconst 声明。 因此,你需要在访问它之前声明你的类,否则会出现ReferenceError。


模块

在 ES6 之前,JavaScript 中没有对模块的原生支持。 JavaScript 应用程序中的所有内容,例如不同 JavaScript 文件中的变量,共享相同的范围。

ES6 引入了基于文件的模块,其中每个模块由一个单独的 .js 文件表示。 现在,您可以在模块中使用 exportimport 语句将变量、函数、类或任何其他实体导出或导入其他模块或文件。

让我们创建一个模块,即一个 JavaScript 文件"main.js"并将以下代码放入其中:

let greet = "Hello World!";
const PI = 3.14; 

function multiplyNumbers(a, b) {
    return a * b;
}

// 导出变量和函数
export { greet, PI, multiplyNumbers };

现在使用以下代码创建另一个 JavaScript 文件"app.js":

import { greet, PI, multiplyNumbers } from './main.js';

alert(greet); // Hello World!
alert(PI); // 3.14
alert(multiplyNumbers(6, 15)); // 90

最后使用以下代码创建一个 HTML 文件 "test.html" 并使用 HTTP 协议(或使用 localhost)在浏览器中打开此 HTML 文件。 还要注意脚本标签上的 type="module"

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>ES6 Module Demo</title>
</head>
<body>
    <script type="module" src="app.js"></script>
</body>
</html>

剩余参数

ES6 引入了剩余参数,允许我们以数组的形式将任意数量的参数传递给函数。 这在您想将参数传递给函数但不知道需要多少的情况下特别有用。

剩余参数是通过在命名参数前面加上剩余运算符 (...) 来指定的,即三个点。 剩余参数只能是参数列表中的最后一个,并且只能有一个剩余参数。 看看下面的例子,看看它是如何工作的:

function sortNames(...names) {
    return names.sort();
}

alert(sortNames("Sarah", "Harry", "Peter")); // Harry,Peter,Sarah
alert(sortNames("Tony", "Ben", "Rick", "Jos")); // John,Jos,Rick,Tony

当剩余参数是函数中的唯一参数时,它获取传递给函数的所有参数,否则它获取超过命名参数数量的其余参数。

function myFunction(a, b, ...args) {
    return args;
}

alert(myFunction(1, 2, 3, 4, 5)); // 3,4,5
alert(myFunction(-7, 5, 0, -2, 4.5, 1, 3)); // 0,-2,4.5,1,3

注意:不要将术语剩余参数与 REST(REpresentational State Transfer)混淆。 这与 RESTful Web 服务无关。


扩展运算符

扩展运算符(也由 (...) 表示)执行与剩余运算符完全相反的功能。 扩展运算符展开(即拆分)一个数组并将值传递给指定的函数,如以下示例所示:

function addNumbers(a, b, c) {
    return a + b + c;
}

let numbers = [5, 12, 8];

// ES5 将数组作为函数参数传递的方式
alert(addNumbers.apply(null, numbers)); // 25

// ES6 扩展运算符
alert(addNumbers(...numbers)); // 25

扩展运算符也可用于将数组的元素插入另一个数组,而无需使用 push(), unshift() concat() 等数组方法。

let pets = ["Cat", "Dog", "Parrot"];
let bugs = ["Ant", "Bee"];

// 通过插入其他数组中的元素来创建数组
let animals = [...pets, "Tiger", "Wolf", "Zebra", ...bugs];

alert(animals); // Cat,Dog,Parrot,Tiger,Wolf,Zebra,Ant,Bee

解构赋值

解构赋值是一个表达式,通过提供更短的语法,可以轻松地将数组中的值或对象中的属性提取到不同的变量中。

解构赋值表达式有两种—arrayobject 解构赋值。 好吧,让我们看看它们是如何工作的:

数组解构赋值

在 ES6 之前,要获取数组的单个值,我们需要编写如下内容:

// ES5 语法
var fruits = ["Apple", "Banana"];

var a = fruits[0];
var b = fruits[1];
alert(a); // Apple
alert(b); // Banana

在 ES6 中,我们可以使用数组解构赋值在一行中做同样的事情:

// ES6 语法
let fruits = ["Apple", "Banana"];

let [a, b] = fruits; // 数组解构赋值

alert(a); // Apple
alert(b); // Banana

您还可以在数组解构赋值中使用 剩余运算符,如下所示:

// ES6 语法
let fruits = ["Apple", "Banana", "Mango"];

let [a, ...r] = fruits;

alert(a); // Apple
alert(r); // Banana,Mango
alert(Array.isArray(r)); // true

对象解构赋值

在 ES5 中,要提取对象的属性值,我们需要编写如下内容:

// ES5 语法
var person = {name: "Peter", age: 28};

var name = person.name;
var age = person.age;

alert(name); // Peter
alert(age); // 28

但是在 ES6 中,您可以提取对象的属性值并将它们分配给变量,如下所示:

// ES6 语法
let person = {name: "Peter", age: 28};

let {name, age} = person; // 对象解构赋值

alert(name); // Peter
alert(age); // 28

我们上面讨论的大多数功能都支持最新版本的主要网络浏览器,例如 Google Chrome、Mozilla Firefox、Microsoft Edge、Safari 等。

或者,您可以免费使用 Babel 之类的在线转译器(源到源编译器)将您当前的 ES6 代码转译为 ES5,以获得更好的浏览器兼容性,而不会忽略 ES6 增强语法和功能的好处。

Advertisements