个性化阅读
专注于IT技术分析

JS ES6新特性 – JavaScript高级教程

上一章JavaScript教程请查看:JS AJAX请求

在本教程中,你将了解最新版本的JavaScript的新特性。

什么是ECMAScript 6(或ES6)?

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

ES6给JavaScript语言带来了重大变化。它引入了几个新特性,比如块作用域的变量、在数组和对象上迭代的新循环、模板文本,以及许多其他增强,使JavaScript编程更简单、更有趣。在本章中,我们将讨论一些最好的ES6特性,你可以在日常的JavaScript编码中使用它们。

let关键字

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

var和let之间有两个关键的区别。使用var关键字声明的变量是函数作用域的,并在其作用域的顶部悬挂,而使用let关键字声明的变量是块作用域的({}),它们不会悬挂。

块作用域仅仅意味着在一对花括号(即{})之间创建一个新的作用域。因此,如果你在循环中声明一个带有let关键字的变量,那么它并不存在于循环之外,如下面的示例所示:

// ES6 语法
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// ES6 语法

正如你在上面的示例中所看到的,第一个块中的变量i在for循环之外是不可访问的。这还使我们能够多次重用同一个变量名,因为它的作用域被限制在block({})中,从而减少了变量声明,并使代码更加清晰。

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

// 改变数组元素
const COLORS = ["red", "green", "blue"];
console.log(COLORS[0]); // red
COLORS[0] = "yellow";
console.log(COLORS[0]); // yellow

for…of循环

新的for…of允许我们很容易地遍历数组或其他可迭代对象。此外,循环内的代码针对iterable对象的每个元素执行。这里有一个例子:

// 遍历数组
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); 

而在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); 

函数参数的默认值

现在,在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中另一个有趣的特性,它通过选择函数并返回关键字,为编写函数表达式提供了更简洁的语法。

箭头函数使用新的语法定义,即fat箭头(=>)符号。让我们看看它是什么样子的:

// 函数表达式
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

你可以看到没有函数和返回关键字在箭头函数声明。

如果只有一个参数,你也可以跳过圆括号,例如(),但是当你有零个或多个参数时,总是需要使用它。

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

有几种不同的方法可以编写箭头函数。以下是最常用的:

// 单参数,单语句
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!

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

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

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(); 

使用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', 'UK');
let printInfo = p.getInfo();
printInfo(); 

正如你可以清楚地看到的,上面示例中的this关键字指的是包含箭头函数的函数的上下文,该箭头函数是Person对象(第9行),与前面的示例不同,它指的是全局对象窗口(第9行)。

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

在ES6中,可以使用new class关键字后跟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()方法需要访问这个关键字。

注意: 与函数声明不同,类声明不会被挂起。类声明驻留在临时死区(TDZ)中,直到执行到达类声明点,类似于let和const声明。因此,你需要在访问类之前声明它,否则将发生ReferenceError。

模块

在ES6之前,JavaScript中没有对模块的本机支持。JavaScript应用程序中的所有内容(例如跨不同JavaScript文件的变量)都共享相同的作用域。

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

让我们创建一个模块,即一个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文件,还要注意script标签上的type=”module”。

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

rest参数

ES6引入了rest参数,允许我们以数组的形式将任意数量的参数传递给函数。当你希望将参数传递给函数,但又不知道需要多少参数时,这种方法尤其有用。

一个rest参数是通过在一个指定的参数前面加上rest操作符(…)即三个点来指定的。Rest参数只能是参数列表中的最后一个,并且只能有一个Rest参数。请看下面的例子,看看它是如何工作的:

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

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

当rest参数是函数中的惟一参数时,它将获取传递给函数的所有参数,否则将获取超过指定参数数量的其余参数。

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参数和rest(具象状态转移),这与RESTful web服务无关。

扩展操作符

spread扩展操作符(也用(…)表示)执行与rest操作符完全相反的功能,spread操作符展开(即分割)一个数组,并将值传递给指定的函数,如下例所示:

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

let numbers = [5, 12, 8];

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

// ES6 spread操作符
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

析构赋值

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

析构赋值表达式有两种:数组析构赋值表达式和对象析构赋值表达式。好吧,让我们看看它们是如何工作的:

数组析构分配

在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

你也可以在数组的析构赋值中使用rest操作符,如下图所示:

// 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

我们上面讨论的大多数特性都在最新版本的主流浏览器中得到了支持,比如谷歌Chrome、Mozilla Firefox、Microsoft Edge、Safari等。

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

赞(0)
未经允许不得转载:srcmini » JS ES6新特性 – JavaScript高级教程

评论 抢沙发

评论前必须登录!