ES6新特性

/ 前端

块级作用域(Block Scope)

随着ES6的引入,JavaScript增加了对块级作用域的支持,这主要是通过letconst关键字实现的。块级作用域限制了变量的作用范围到最近的一对大括号{}内,比如在一个if语句、for循环或简单的代码块中。

1
2
3
4
5
6
7
8
9
if (true) {
let blockScopedVar = "I'm scoped to this block";
const anotherBlockScopedVar = "Also scoped to this block";
console.log(blockScopedVar); // 正常工作
console.log(anotherBlockScopedVar); // 正常工作
}

console.log(blockScopedVar); // 报错:blockScopedVar is not defined
console.log(anotherBlockScopedVar); // 报错:anotherBlockScopedVar is not defined

暂时性死区(Temporal Dead Zone)

暂时性死区(Temporal Dead Zone,简称TDZ)是ECMAScript 6(ES6或ES2015)引入的一个概念,主要与使用letconst关键字声明的变量相关。它定义了一个区域,在这个区域内尝试访问尚未声明的变量会导致运行时错误。

本质

当控制流进入一个新的作用域(如一个代码块),在这个作用域内用letconst声明的变量会被创建,但在此之前,这些变量不能被访问或使用。如果试图在声明之前访问它们,JavaScript引擎将抛出ReferenceError。换句话说,即使变量已经存在于作用域中,但在其声明之前访问它们是非法的,并且会导致错误。

示例

考虑以下代码:

1
2
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 2;

这里,在let a = 2;语句执行前,任何对a的访问都会导致错误。这是因为从进入作用域开始直到let声明语句的位置,构成了a的暂时性死区。

再来看一个稍微复杂一点的例子:

1
2
3
4
5
if (true) {
// TDZ starts at the beginning of this block
console.log(tmp); // ReferenceError
let tmp = 3; // TDZ ends here
}

在这个例子中,从if语句的开始到let tmp = 3;这一行之间,构成了tmp的暂时性死区。在这一区域内尝试访问tmp会导致ReferenceError

暂时性死区的影响

为什么需要暂时性死区?

暂时性死区的设计是为了减少编程错误。通过强制要求变量必须先声明后使用,可以避免一些由于变量提升带来的意外行为,尤其是在使用var关键字时可能出现的情况。

在JavaScript中,arguments对象和剩余参数(Rest Parameters)都是用于处理函数调用时传入的参数,但它们之间存在一些关键的区别。下面我将详细解释两者,并结合实际应用示例来说明它们的使用方法。

函数剩余参数(Rest Parameters)

函数剩余参数(Rest Parameters)是ES6(ECMAScript 2015)引入的一种语法特性,它提供了一种更简洁的方式来处理传递给函数的不定数量的参数。通过使用三个点 ... 前缀,剩余参数允许我们将一个不定数量的实参表示为一个数组。

基本概念

例如:

1
2
3
4
5
6
function sum(...theArgs) {
return theArgs.reduce((previous, current) => previous + current);
}

console.log(sum(1, 2, 3)); // 输出: 6
console.log(sum(4, 5, 6, 7, 8)); // 输出: 30

在这个例子中,sum 函数使用了剩余参数 theArgs 来接收任意数量的参数,并利用数组的 reduce 方法来计算这些参数的总和。

特性与优点

注意事项

实际应用示例

下面的例子展示了如何使用剩余参数实现一个简单的加法器函数,它可以对任意数量的数字进行求和:

1
2
3
4
5
6
7
8
9
10
11
function sum(first, ...rest) {
let result = first;
for (let number of rest) {
result += number;
}
return result;
}

console.log(sum(1, 2, 3)); // 输出: 6
console.log(sum(4, 5, 6, 7, 8)); // 输出: 30
console.log(sum()); // 输出: NaN,因为没有提供第一个参数

展开运算符(Spread Operator)

展开运算符(Spread Operator)是ES6(ECMAScript 2015)引入的一种语法特性,它允许数组、字符串或对象的元素被“展开”为独立的元素。展开运算符使用三个连续的点号 ... 来表示,并且可以应用于多种场合,包括函数调用、数组字面量构造、对象字面量构造等。

在函数调用时展开数组元素

当你需要将一个数组作为参数传递给一个函数时,可以使用展开运算符来代替 apply() 方法:

1
2
3
4
5
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 输出: 6

在数组字面量中合并多个数组

展开运算符可以用来轻松地合并两个或更多的数组:

1
2
3
4
const fruits = ['apple', 'banana'];
const moreFruits = ['orange', 'grape'];
const allFruits = [...fruits, ...moreFruits];
console.log(allFruits); // 输出: ['apple', 'banana', 'orange', 'grape']

复制数组

使用展开运算符可以创建一个现有数组的浅拷贝:

1
2
const arr = [1, 2, 3];
const arrCopy = [...arr]; // 创建arr的一个浅拷贝

需要注意的是,这种复制方式只适用于数组的第一层,对于嵌套的对象或数组,展开运算符只会复制引用而不是深层的内容。

在对象字面量中合并对象

在ES7及以后版本中,可以使用展开运算符来合并对象:

1
2
3
4
const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // 输出: { foo: 'baz', x: 42, y: 13 }

如果存在相同的键名,后出现的对象属性会覆盖前面的对象属性。

使用展开运算符代替 apply 方法

展开运算符还可以用于简化某些原本需要用 apply 方法实现的操作,比如求一组数中的最大值:

1
2
const numbers = [9, 3, 2];
const maxNumNew = Math.max(...numbers); // 使用展开运算符的写法

注意事项

箭头函数(Arrow Functions)

箭头函数(Arrow Functions)是ECMAScript 2015(ES6)引入的一种新的函数定义方式,它提供了一种更加简洁的语法来编写匿名函数。

箭头函数的基本语法

箭头函数的基本结构如下:

1
(param1, param2, ..., paramN) => { statements }

如果只有一个参数,可以省略括号:

1
param => { statements }

对于单个表达式返回值的情况,可以进一步简化为:

1
(param1, param2, ..., paramN) => expression

例如:

1
2
3
4
5
6
7
// 普通函数写法
var add = function(x, y) {
return x + y;
};

// 使用箭头函数简化
const add = (x, y) => x + y;

this 关键字的行为

箭头函数与普通函数的一个重要区别在于this关键字的行为。在普通函数中,this的值取决于函数是如何被调用的;而在箭头函数中,this是在函数创建时就确定了,并且总是指向其外层作用域中的this

1
2
3
4
5
6
7
8
9
const obj = {
method: function() {
// 这里的 'this' 指向 obj 对象
setTimeout(() => {
console.log(this); // 同样指向 obj 对象
}, 100);
}
};
obj.method(); // 输出 obj 对象两次

arguments 和剩余参数

箭头函数没有自己的arguments对象,取而代之的是可以通过剩余参数(Rest Parameters)来获取传入的所有参数。

1
2
3
4
5
6
7
8
9
10
function regularFunction() {
console.log(arguments);
}

const arrowFunction = (...args) => {
console.log(args);
};

regularFunction(1, 2, 3); // 输出 Arguments 对象
arrowFunction(1, 2, 3); // 输出 [1, 2, 3]

不能作为构造器

由于箭头函数没有自己的thisprototype属性,所以它们不能通过new关键字来实例化对象。

1
2
const ArrowFunc = () => {};
// new ArrowFunc(); // 抛出错误

其他特性

使用场景

箭头函数非常适合用于那些不需要独立上下文的小型回调函数,比如数组方法中的回调(如mapfilter等)、事件处理器等。

1
[1, 2, 3].map(n => n * 2); // 返回 [2, 4, 6]

总之,箭头函数以其简洁的语法和固定的this绑定机制,为JavaScript开发者提供了更加强大和灵活的工具。不过,在需要动态this或者需要使用arguments对象的情况下,还是应该选择普通函数。

数组解构(Array Destructuring)

数组解构是ES6(ECMAScript 2015)引入的一种语法特性,它提供了一种简洁的方式来从数组中提取数据,并将其赋值给变量。这种特性不仅使代码更加直观和易读,而且也提高了开发效率。

基本用法

最基本的数组解构形式是从一个已知结构的数组中提取元素并赋值给对应的变量。例如:

1
2
3
let [a, b] = [1, 2];
console.log(a); // 输出: 1
console.log(b); // 输出: 2

这里,[1, 2]是一个数组,而[a, b]是解构模式,用于将数组中的第一个元素赋值给变量a,第二个元素赋值给变量b

跳过元素

在解构过程中,如果不需要某些元素,可以通过跳过它们来实现:

1
2
3
let [a,,b] = [1, 2, 3];
console.log(a); // 输出: 1
console.log(b); // 输出: 3

这里,通过使用两个逗号,我们跳过了数组中的第二个元素。

使用默认值

当数组中的元素不存在或为undefined时,可以指定默认值:

1
2
3
let [a = 1, b = 2] = [undefined, 3];
console.log(a); // 输出: 1 (因为第一个元素是undefined,所以使用了默认值)
console.log(b); // 输出: 3

解构剩余部分

使用...操作符,我们可以将数组剩下的部分收集到一个新的数组中:

1
2
3
let [a, ...rest] = [1, 2, 3, 4];
console.log(a); // 输出: 1
console.log(rest); // 输出: [2, 3, 4]

函数返回值解构

函数也可以返回数组,然后你可以直接对返回值进行解构:

1
2
3
4
5
function returnArray() {
return [1, 2, 3];
}
let [a, b, c] = returnArray();
console.log(a, b, c); // 输出: 1 2 3

交换变量值

解构赋值使得交换两个变量的值变得非常简单:

1
2
3
4
5
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a); // 输出: 2
console.log(b); // 输出: 1

复杂场景

对于更复杂的数据结构,比如嵌套数组,也可以使用解构赋值:

1
2
3
4
5
let nested = [1, [2, 3]];
let [a, [b, c]] = nested;
console.log(a); // 输出: 1
console.log(b); // 输出: 2
console.log(c); // 输出: 3

对象解构(Object Destructuring)

对象解构是ES6(ECMAScript 2015)引入的一种特性,它允许我们从对象中提取属性并将其赋值给变量。这种语法不仅让代码更加简洁和易读,而且提高了开发效率。

基本用法

对象解构的基本形式是从一个对象中提取属性,并将这些属性的值赋给同名的变量:

1
2
3
4
5
let person = { name: "Sarah", country: "Nigeria", job: "Developer" };
let { name, country, job } = person;
console.log(name); // 输出: Sarah
console.log(country); // 输出: Nigeria
console.log(job); // 输出: Developer

这里,{ name, country, job }是解构模式,用于从person对象中提取相应的属性并赋值给同名的变量。

设置别名

有时我们可能想要为提取的属性设置不同的变量名。这可以通过在解构模式中指定属性名后跟冒号和新的变量名来实现:

1
2
3
let { name: personName, country: personCountry } = person;
console.log(personName); // 输出: Sarah
console.log(personCountry); // 输出: Nigeria

在这个例子中,namecountry属性被分别赋值给了personNamepersonCountry变量。

默认值

当对象中没有某个属性或属性值为undefined时,可以提供默认值:

1
2
3
let { name = "Unknown", age = 30 } = {};
console.log(name); // 输出: Unknown
console.log(age); // 输出: 30

解构嵌套对象

对于包含嵌套结构的对象,也可以使用解构赋值:

1
2
3
4
5
6
7
8
9
10
11
let employee = {
name: "John",
address: {
city: "New York",
zipCode: "10001"
}
};
let { name, address: { city, zipCode } } = employee;
console.log(name); // 输出: John
console.log(city); // 输出: New York
console.log(zipCode); // 输出: 10001

函数参数解构

解构赋值同样可以应用于函数参数,从而简化函数签名和内部逻辑:

1
2
3
4
5
function printDetails({ name, job }) {
console.log(`${name} works as a ${job}`);
}

printDetails(person); // 输出: Sarah works as a Developer

使用场景

对象解构在许多情况下都非常有用,比如:

class关键字

ES6(ECMAScript 2015)引入了class关键字,为JavaScript带来了更接近传统面向对象编程语言的语法。尽管这种新的语法看起来像是引入了一种全新的机制来定义类和创建对象,但实际上它只是基于原型继承的一种“语法糖”,并没有改变JavaScript原有的原型继承的本质。

基本语法

在ES6中,你可以使用class关键字来声明一个类,并通过构造函数constructor来初始化实例对象。下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}

在这个例子中,Point类有一个构造方法constructor用于接收参数并初始化对象属性,还有一个名为toString的方法用于返回点的位置信息。

类的继承

ES6还引入了extends关键字来实现类的继承,使得子类可以继承父类的所有属性和方法。例如:

1
2
3
4
5
6
7
8
9
10
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的构造函数
this.color = color;
}

toString() {
return super.toString() + ' in ' + this.color;
}
}

这里,ColorPoint类继承自Point类,并添加了一个额外的属性color。它重写了toString方法,并通过super关键字调用了父类的同名方法。

静态方法

你还可以在类中定义静态方法,这些方法不会被实例化,而是直接通过类本身来调用:

1
2
3
4
5
6
7
class MyClass {
static myStaticMethod() {
return 'Hello World';
}
}

console.log(MyClass.myStaticMethod()); // 输出 "Hello World"

Getter 和 Setter

ES6中的类支持getter和setter方法,它们允许你控制对对象属性的访问和修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}

get fahrenheit() {
return this.celsius * 9 / 5 + 32;
}

set fahrenheit(value) {
this.celsius = (value - 32) * 5 / 9;
}
}

以上代码展示了如何定义获取和设置华氏温度的getter和setter方法。

注意事项

其他

模块(Modules)

Promises

Sets 和 Maps

Proxy 和 Reflect