const 和 let

ES6 推荐使用 let 声明局部变量,相比之前的 var(无论声明在何处,都会被视为声明在函数的最顶部),let 具有块级作用域(即在使用 let 的那一对大括号 {}内起作用),可以避免变量提升和污染全局作用域的问题。

const 则用来声明常量,即一旦赋值就不能再改变。const 也具有块级作用域,并且必须在声明时就初始化。const 适合用来定义不会改变的值,如 PI、URL 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用var
var a = 1;
if (true) {
var a = 2; // 这里会覆盖上面的a
}
console.log(a); // 输出2

// 使用let
let b = 1;
if (true) {
let b = 2; // 这里不会影响外面的b
}
console.log(b); // 输出1

// 使用const
const PI = 3.14;
PI = 3.15; // 报错:Assignment to constant variable.

const URL = "https://www.bing.com";
URL = "https://www.google.com"; // 报错:Assignment to constant variable.

解构赋值

解构赋值是一种从数组或对象中提取数据并赋值给变量的简洁写法。它可以减少代码量,提高可读性,并且支持默认值、嵌套结构、别名等特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 数组解构:按照数组元素位置对应赋值给变量
let [f, g, h] = [1, 2, 3];
console.log(f); // 1
console.log(g); // 2
console.log(h); // 3

// 对象解构:按照对象属性名对应赋值给同名变量(也可以使用别名)
let { name, age } = { name: "Alice", age: 18 };
console.log(name); // Alice
console.log(age); // 18

let { name: i, age: j } = { name: "Bob", age: 19 };
console.log(i); // Bob
console.log(j); //19

// 默认值:如果没有匹配到相应的数据,则使用默认值(如果有)
let [k, l = 0] = [1];
console.log(k); //1
console.log(l); //0

let { m = 0, n = 0 } = { m: 1 };
console.log(m); //1
console.log(n); //0
// 嵌套结构:可以解构嵌套的数组或对象
let [o, [p, q]] = [1, [2, 3]];
console.log(o); // 1
console.log(p); // 2
console.log(q); // 3

let {
r: { s, t },
} = { r: { s: 4, t: 5 } };
console.log(s); // 4
console.log(t); // 5

// 解构赋值的应用场景:交换变量、函数参数、返回值等
let u = 6;
let v = 7;
[u, v] = [v, u]; // 不需要使用临时变量来交换u和v的值
console.log(u); // 7
console.log(v); // 6

function foo([x, y]) {
return x + y;
}
console.log(foo([8, 9])); //17

function bar() {
return [10, 11];
}
let [w, z] = bar();
console.log(w); //10
console.log(z); //11

箭头函数

箭头函数是一种使用 => 符号定义函数的简洁写法 。它可以省略 function 关键字、参数括号、返回值括号等,使得代码更加简洁和清晰。箭头函数还有一个重要的特性,就是它不会改变 this 的指向,即箭头函数内部的 this 始终指向定义时所在的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 普通函数和箭头函数的对比
function add(x, y) {
return x + y;
}

let add = (x, y) => x + y; // 省略了 function 关键字、参数括号、返回值括号

// 如果只有一个参数,可以省略参数括号;如果没有参数,必须使用空括号
let square = (x) => x * x;

let hello = () => console.log("Hello");

// 如果有多条语句,需要使用大括号包裹,并且显式返回(如果需要)
let max = (x, y) => {
if (x > y) {
return x;
} else {
return y;
}
};

// 箭头函数不会改变 this 的指向,即箭头函数内部的 this 始终指向定义时所在的对象
let obj = {
name: "Alice",
sayHi: function () {
console.log(this.name); // Alice
setTimeout(function () {
console.log(this.name); // undefined (因为setTimeout中的this指向window)
}, 1000);
setTimeout(() => {
console.log(this.name); // Alice (因为箭头函数中的this指向obj)
}, 1000);
},
};
obj.sayHi();

//
var obj = {
name: "John",
sayHello: function () {
var that = this; //用它就可以拿到obj
setTimeout(function () {
console.log("Hello, " + that.name);
}, 1000);
},
};
obj.sayHello();

模板字符串

模板字符串是一种使用反引号 `` 包裹字符串,并且支持插入变量或表达式的新语法 。它可以避免使用 + 号连接字符串和变量,并且支持多行字符串和标签模板等特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 使用模板字符串插入变量或表达式,用 ${} 包裹即可(注意是反引号而不是单引号)
let name = "Bob";
let age = 19;
let message = `Hello ${name}, you are ${age} years old.`;
console.log(message);

// 使用模板字符串可以直接换行,不需要使用 \n 或者 + 号连接多行字符串
let poem = `Do not go gentle into that good night,
Old age should burn and rave at close of day;
Rage, rage against the dying of the light.`;
console.log(poem);

// 使用标签模板可以自定义模板字符串的处理方式(标签是一个函数名)
function tag(strings, ...values) {
let result = "";
for (let i = 0; i < strings.length; i++) {
result += strings[i];
if (i < values.length) {
result += values[i].toUpperCase(); // 将变量转为大写
}
}
return result;
}

let name = "Alice";
let message = tag`Hello ${name}, how are you?`; // 使用tag函数处理模板字符串
console.log(message); // Hello ALICE, how are you?

默认参数、剩余参数和展开运算符

ES6 提供了一些新的语法,可以让函数的参数更加灵活和方便。默认参数可以让函数在没有传入参数或传入 undefined 时,使用预设的默认值。剩余参数可以让函数接收任意数量的参数,并将它们存储在一个数组中。展开运算符可以将一个数组或对象展开为多个元素或属性,用于函数调用、数组合并、对象复制等场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 默认参数:在函数定义时,给参数赋予默认值(如果没有传入或传入undefined)
function add(x = 0, y = 0) {
return x + y;
}
console.log(add()); // 0
console.log(add(1)); // 1
console.log(add(1, 2)); // 3
console.log(add(1, undefined)); // 1

// 剩余参数:在函数定义时,使用 ... 符号表示剩余的所有参数,并将它们存储在一个数组中(必须是最后一个参数)
function sum(...numbers) {
let result = 0;
for (let number of numbers) {
result += number;
}
return result;
}
console.log(sum()); // 0
console.log(sum(1)); // 1
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3)); // 6

// 展开运算符:在函数调用时,使用 ... 符号将一个数组或对象展开为多个元素或属性(相当于逐个传入)
let arr = [4, 5, 6];
console.log(sum(...arr)); // 15 (相当于sum(4,5,6))

// 展开运算符也可以用于数组合并、对象复制等场景
let arr1 = [1, 2];
let arr2 = [3, ...arr]; // [3,4,5,6]
let obj1 = { name: "Alice" };
let obj2 = { ...obj1 }; // {name: "Alice"}

类和继承

ES6 提供了一种新的语法,可以让 JavaScript 支持类和继承这两个面向对象编程的重要概念。类是一种定义对象属性和方法的模板,可以通过 new 关键字创建类的实例。继承是一种让子类拥有父类属性和方法的机制,可以通过 extends 和 super 关键字实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 定义一个类:使用 class 关键字,并且提供一个 constructor 方法作为构造函数(初始化实例属性)
class Person {
constructor(name, age) {
this.name = name; // this 指向实例对象
this.age = age;
}

// 定义类的方法:直接在类中写函数名和函数体(不需要使用 function 关键字)
sayHi() {
console.log(`Hello ${this.name}, you are ${this.age} years old.`);
}
}

// 创建类的实例:使用 new 关键字,并且传入构造函数所需的参数
let alice = new Person("Alice", 18);
alice.sayHi(); // Hello Alice, you are 18 years old.

// 定义一个子类:使用 extends 关键字继承父类,并且可以重写或新增属性和方法
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 使用 super 关键字调用父类的构造函数(必须在子类构造函数中第一行执行)
this.grade = grade; // 子类可以新增自己的属性
}

// 子类可以重写或新增父类的方法
sayHi() {
console.log(
`Hello ${this.name}, you are ${this.age} years old and in grade ${this.grade}.`
);
}

study() {
console.log(`${this.name} is studying hard.`);
}
}

// 创建子类的实例:使用 new 关键字,并且传入构造函数所需的参数(包括父类和子类的参数)
let bob = new Student("Bob", 19, 12);
bob.sayHi(); // Hello Bob, you are 19 years old and in grade 12.
bob.study(); // Bob is studying hard.

Promise 和 async/await

Promise 是一种用于处理异步操作的对象,它表示一个未来可能完成或失败的事件。Promise 有三种状态:pending(等待)、fulfilled(成功)、rejected(失败)。Promise 可以通过 then 方法添加成功或失败时执行的回调函数,也可以通过 catch 方法添加失败时执行的回调函数。Promise 还可以通过 all 和 race 方法组合多个 Promise 对象。

async/await 是一种基于 Promise 的新语法,可以让异步操作更加简洁和清晰。async 是一个修饰符,用于声明一个异步函数,该函数返回一个 Promise 对象。await 是一个运算符,用于等待一个 Promise 对象的结果,只能在异步函数中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 创建一个Promise对象:使用 new 关键字,并且传入一个执行器函数(该函数接收两个参数:resolve和reject)
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟一个异步操作
let num = Math.random(); // 随机生成一个0到1之间的数
if (num > 0.5) {
resolve(num); // 如果大于0.5,则表示成功,调用resolve并传入结果
} else {
reject(num); // 如果小于等于0.5,则表示失败,调用reject并传入结果
}
}, 1000);
});

// 使用 then 方法添加成功或失败时执行的回调函数(可以链式调用)
promise
.then((value) => {
console.log(`Success: ${value}`); // 如果Promise状态变为fulfilled,打印成功的结果
return value * 2; // 可以返回一个新的值,传递给下一个then
})
.then((value) => {
console.log(`Double: ${value}`); // 打印上一个then返回的值乘以2
})
.catch((reason) => {
console.log(`Fail: ${reason}`); // 如果Promise状态变为rejected,打印失败的结果
});

// 使用 Promise.all 方法组合多个Promise对象,返回一个新的Promise对象,该对象在所有Promise都成功时成功,否则失败
let promise1 = Promise.resolve(1); // 创建一个立即成功的Promise对象
let promise2 = Promise.resolve(2);
let promise3 = Promise.resolve(3);

let promise4 = Promise.all([promise1, promise2, promise3]); // 组合三个Promise对象

promise4
.then((values) => {
console.log(values); // [1,2,3] (如果所有Promise都成功,打印一个包含所有结果的数组)
})
.catch((reason) => {
console.log(reason); // 如果有任何一个Promise失败,打印失败的结果
});

// 使用 Promise.race 方法组合多个Promise对象,返回一个新的Promise对象,该对象在任何一个Promise完成时完成(无论成功或失败)
let promise5 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(5);
}, 500); // 0.5秒后成功
});
let promise6 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(6);
}, 1000); // 1秒后失败
});

let promise7 = Promise.race([promise5, promise6]); // 组合两个Promise对象

promise7
.then((value) => {
console.log(value); // 5 (如果有任何一个Promise先成功,打印成功的结果)
})
.catch((reason) => {
console.log(reason); // 如果有任何一个Promise先失败,打印失败的结果
});

// 使用 async/await 语法简化异步操作(需要在函数前加上 async 关键字,并且在等待的地方加上 await 关键字)
async function test() {
try {
let value = await promise; // 等待promise对象的结果(如果成功,赋值给value;如果失败,抛出异常)
console.log(`Success: ${value}`); // 如果成功,打印结果
} catch (error) {
console.log(`Fail: ${error}`); // 如果失败,打印错误
}
}

test(); // 调用异步函数

模块化

模块化是一种将代码分割为不同的文件或模块的方法,可以提高代码的可读性、可维护性和复用性。ES6 提供了一种原生的模块化语法,可以让 JavaScript 支持导入和导出模块。导入模块使用 import 关键字,导出模块使用 export 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 创建一个名为 math.js 的模块文件,并且导出两个函数:add 和 multiply
export function add(x, y) {
return x + y;
}

export function multiply(x, y) {
return x * y;
}

// 在另一个文件中,使用 import 关键字导入 math.js 模块,并且使用它们
import { add, multiply } from "./math.js"; // 导入指定的函数(需要使用花括号)
console.log(add(1, 2)); // 3
console.log(multiply(2, 3)); // 6

// 可以使用 as 关键字给导入或导出的函数起别名
import { add as plus, multiply as times } from "./math.js"; // 导入并重命名函数
console.log(plus(1, 2)); // 3
console.log(times(2, 3)); // 6

export { add as plus, multiply as times }; // 导出并重命名函数

// 可以使用 * 符号导入或导出所有的函数(需要起一个别名)
import * as math from "./math.js"; // 导入所有函数并起一个别名为math
console.log(math.add(1, 2)); // 3
console.log(math.multiply(2, 3)); // 6

export * from "./math.js"; // 导出所有函数

// 可以使用 default 关键字指定一个默认的导出(只能有一个,默认导出不需要花括号)
export default function subtract(x, y) {
return x - y;
}

import subtract from "./math.js"; // 导入默认导出(不需要花括号)
console.log(subtract(5, 4)); // 1

迭代器和生成器

迭代器是一种遵循迭代协议的对象,可以按照一定的顺序访问一个集合中的元素。迭代器有一个 next 方法,每次调用返回一个包含 value 和 done 属性的对象,value 表示当前元素的值,done 表示是否还有更多元素。ES6 提供了一种新的语法 for…of 循环,可以方便地遍历迭代器。

生成器是一种特殊的函数,可以返回一个迭代器对象,并且可以在函数体内使用 yield 关键字暂停和恢复执行。生成器使用 function* 关键字声明,并且可以接收参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 创建一个迭代器对象:使用 Symbol.iterator 符号作为属性名,并且返回一个具有 next 方法的对象
let iterator = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
if (i < 5) {
return { value: i++, done: false }; // 返回当前元素的值和状态
} else {
return { done: true }; // 返回结束状态
}
},
};
},
};

// 使用 for...of 循环遍历迭代器对象(不需要调用 next 方法)
for (let value of iterator) {
console.log(value); // 0 1 2 3 4
}

// 创建一个生成器函数:使用 function* 关键字,并且在函数体内使用 yield 关键字暂停和恢复执行
function* generator(n) {
for (let i = 0; i < n; i++) {
yield i; // 每次遇到 yield 关键字,返回当前值并暂停执行,直到下一次调用 next 方法
}
}

// 调用生成器函数返回一个迭代器对象(可以传入参数)
let iter = generator(5);

// 使用 for...of 循环遍历迭代器对象(不需要调用 next 方法)
for (let value of iter) {
console.log(value); // 0 1 2 3 4
}

Map 和 Set

Map 和 Set 是两种新的数据结构,可以提供更高效和灵活的存储和操作方式。Map 是一种类似于对象的集合,但是它可以使用任意类型的值作为键(而不仅仅是字符串)。Set 是一种类似于数组的集合,但是它只存储唯一的值(不会出现重复)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 创建一个Map对象:使用 new 关键字,并且可以传入一个可迭代的数组作为初始值(每个元素是一个键值对数组)
let map = new Map([
["name", "Alice"],
["age", 18],
]);

// 使用 set 方法添加或修改键值对(可以使用任意类型的值作为键)
map.set("gender", "female");
map.set(true, "yes");
map.set([1, 2], "array");

// 使用 get 方法根据键获取对应的值(如果不存在,返回 undefined)
console.log(map.get("name")); // Alice
console.log(map.get(true)); // yes
console.log(map.get([1, 2])); // undefined (因为数组是引用类型,不相等)

// 使用 has 方法判断是否存在某个键
console.log(map.has("age")); // true
console.log(map.has("grade")); // false

// 使用 delete 方法删除某个键值对(返回一个布尔值表示是否删除成功)
console.log(map.delete("age")); // true
console.log(map.delete("age")); // false

// 使用 size 属性获取Map中的元素个数
console.log(map.size); // 4

// 使用 clear 方法清空Map中的所有元素
map.clear();
console.log(map.size); // 0

// 创建一个Set对象:使用 new 关键字,并且可以传入一个可迭代的数组作为初始值(重复的元素会被忽略)
let set = new Set([1, 2, 3, 4, 4]);

// 使用 add 方法添加元素(如果已经存在,不会重复添加)
set.add(5);
set.add(4);

// 使用 has 方法判断是否存在某个元素
console.log(set.has(3)); // true
console.log(set.has(6)); // false

// 使用 delete 方法删除某个元素(返回一个布尔值表示是否删除成功)
console.log(set.delete(2)); // true
console.log(set.delete(2)); // false

// 使用 size 属性获取Set中的元素个数
console.log(set.size); // 5
// 使用 clear 方法清空Set中的所有元素
set.clear();
console.log(set.size); // 0

// Map和Set都是可迭代的对象,可以使用 for...of 循环或扩展运算符遍历它们
let map = new Map([
["name", "Alice"],
["age", 18],
]);
let set = new Set([1, 2, 3]);

// 使用 for...of 循环遍历Map或Set(Map的每个元素是一个键值对数组,Set的每个元素是一个值)
for (let [key, value] of map) {
console.log(`${key}: ${value}`); // name: Alice age: 18
}

for (let value of set) {
console.log(value); // 1 2 3
}

// 使用扩展运算符将Map或Set转换为数组(Map的每个元素是一个键值对数组,Set的每个元素是一个值)
let mapArr = [...map];
console.log(mapArr); // [["name","Alice"],["age",18]]

let setArr = [...set];
console.log(setArr); // [1,2,3]