ES6,即ECMAScript 2015,是JavaScript的一个重要更新版本。它引入了许多新特性和改进,使得JavaScript编程更加现代化和高效。以下是ES6的一些主要新特性:
1. let
和const
命令
let
和const
是ES6引入的变量声明方式,用于替代传统的var
。
let
:用于声明块级作用域的变量。const
:用于声明常量,变量值不能被重新赋值。
// 示例
if (true) {
let x = 10;
const y = 20;
console.log(x); // 10
console.log(y); // 20
}
// console.log(x); // ReferenceError: x is not defined
// console.log(y); // ReferenceError: y is not defined
2. ES6 的模板字符串
模板字符串允许嵌入表达式和多行字符串,使用反引号 `
包围。
示例:
let name = "Alice";
let greeting = `Hello, ${name}!`;
console.log(greeting); // 输出 "Hello, Alice!"
let multiLine = `
This is a
multi-line
string.
`;
console.log(multiLine);
3. 增强的函数
ES6(ECMAScript 2015)引入了一些增强的函数特性,这些特性使得函数的定义和使用更加灵活和简洁。下面是一些重要的增强功能,包括箭头函数、默认参数、剩余参数和展开运算符等。
3.1 箭头函数
箭头函数提供了一种更简洁的函数定义语法,并且不会重新绑定 this
值。在某些情况下,使用箭头函数可以避免由于 this
的不同上下文而造成的错误。
基本语法
const add = (a, b) => a + b;
console.log(add(5, 10)); // 输出: 15
使用注意
-
不绑定
this
:箭头函数不会创建自己的this
,而是从调用它的上下文中继承this
。function Timer() { this.seconds = 0; setInterval(() => { this.seconds++; console.log(this.seconds); // 输出正确的 `this.seconds` }, 1000); } new Timer();
-
没有
arguments
对象:箭头函数没有arguments
对象。可以使用剩余参数(下文)来代替。
3.2 默认参数
ES6 允许为函数参数设置默认值,当参数未传入或为 undefined
时,使用这些默认值。
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5)); // 输出: 5,因为 b 默认为 1
console.log(multiply(5, 2)); // 输出: 10
3.3 剩余参数
剩余参数(rest parameters)允许将不确定数量的参数作为数组传递给函数。在函数定义中使用 ...
语法。
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 输出: 10
3.4 展开运算符(扩展运算符)
展开运算符(spread operator)同样使用 ...
语法,可用于将数组或对象展开为单独的元素,适用于函数参数、数组、对象等场合。
数组用法
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // 输出: [1, 2, 3, 4, 5, 6]
对象用法
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // 输出: { a: 1, b: 3, c: 4 }
3.5 类似函数的方法定义
在对象字面量中,可以使用简写语法定义方法,增强了对象方法的可读性。
const obj = {
value: 42,
getValue() {
return this.value; // `this` 引用 obj
}
};
console.log(obj.getValue()); // 输出: 42
3.6 其他增强的函数特性
-
bind
方法的简化:对于箭头函数而言,通常不需要使用bind
来绑定上下文,因为箭头函数会自动捕获外部上下文的this
值。 -
forEach
、map
、filter
等高阶函数:在 ES6 中引入的数组方法功能也使得函数式编程变得更加简单和高效。
4. 扩展的字符串、对象、数组功能
ES6 为字符串、对象和数组添加了许多新的方法。
4.1 字符串扩展
a. includes()
检查一个字符串是否包含另一个字符串,返回布尔值。
const str = "Hello, world!";
console.log(str.includes("Hello")); // true
console.log(str.includes("world")); // true
console.log(str.includes("test")); // false
b. startsWith()
和 endsWith()
检查一个字符串是否以另一个字符串开头或结尾。
const str = "Hello, world!";
console.log(str.startsWith("Hello")); // true
console.log(str.endsWith("world!")); // true
c. repeat()
返回一个新字符串,表示将原字符串重复指定次数的结果。
const str = "abc";
console.log(str.repeat(3)); // "abcabcabc"
d. padStart()
和 padEnd()
在字符串开头或结尾填充指定的字符,以达到指定的长度。
const str = "5";
console.log(str.padStart(2, '0')); // "05"
console.log(str.padEnd(2, '0')); // "50"
4.2 对象扩展
a. 对象字面量中的简写
可以简化对象字面量的语法。
const x = 1, y = 2;
const obj = { x, y }; // 等同于 { x: x, y: y }
console.log(obj); // { x: 1, y: 2 }
b. 对象属性名的计算
属性名可以使用表达式来计算。
const key = 'name';
const obj = {
[key]: 'Alice',
age: 25
};
console.log(obj); // { name: 'Alice', age: 25 }
c. Object.assign()
用于将一个或多个源对象的可枚举属性复制到目标对象。
const target = { a: 1 };
const source = { b: 2, c: 3 };
Object.assign(target, source);
console.log(target); // { a: 1, b: 2, c: 3 }
d. Object.entries()
和 Object.values()
Object.entries()
返回一个包含对象自身可枚举属性的键值对数组;Object.values()
返回一个包含对象自身所有属性值的数组。
const obj = { a: 1, b: 2 };
console.log(Object.entries(obj)); // [['a', 1], ['b', 2]]
console.log(Object.values(obj)); // [1, 2]
4.3 数组扩展
a. 扩展运算符 (...
)
可以方便地复制数组或在数组中插入元素。
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
console.log(arr2);
b. Array.from()
从类似数组的对象或可迭代对象创建一个新的数组实例。
const str = "Hello";
const arr = Array.from(str); // ['H', 'e', 'l', 'l', 'o']
console.log(arr);
c. Array.of()
创建一个新的数组实例,其元素为传入的参数。
const arr = Array.of(1, 2, 3); // [1, 2, 3]
console.log(arr);
d. Array.prototype.find()
和 Array.prototype.findIndex()
find()
方法返回数组中满足提供的测试函数的第一个元素的值,findIndex()
返回第一个满足提供的测试函数的元素的索引。
const arr = [5, 12, 8, 130, 44];
const found = arr.find(element => element > 10); // 12
const index = arr.findIndex(element => element > 10); // 1
console.log(found, index);
e. Array.prototype.fill()
用一个静态值填充数组中的所有元素,返回修改后的数组。
const arr = new Array(3).fill(0); // [0, 0, 0]
console.log(arr);
5. 解构赋值
解构赋值可以从数组或对象中提取值并赋给变量。
5.1 数组解构
数组解构允许我们从数组中提取值,并将它们赋给变量。
基本语法
const arr = [1, 2, 3];
const [a, b] = arr;
console.log(a); // 1
console.log(b); // 2
跳过元素
可以跳过数组中的某些元素。
const arr = [1, 2, 3, 4];
const [a, , c] = arr;
console.log(a); // 1
console.log(c); // 3
剩余参数
使用 ...
运算符可以将剩余的元素放入一个数组。
const arr = [1, 2, 3, 4, 5];
const [a, ...rest] = arr;
console.log(a); // 1
console.log(rest); // [2, 3, 4, 5]
解构嵌套数组
可以解构嵌套的数组。
const arr = [1, [2, 3], 4];
const [a, [b, c], d] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // 4
5.2 对象解构
对象解构允许我们从对象中提取值,并将它们赋给变量。
基本语法
const obj = { x: 1, y: 2 };
const { x, y } = obj;
console.log(x); // 1
console.log(y); // 2
指定变量名
可以将对象的属性赋值给不同名称的变量。
const obj = { x: 1, y: 2 };
const { x: a, y: b } = obj;
console.log(a); // 1
console.log(b); // 2
默认值
可以为解构赋值的变量提供默认值。
const obj = { x: 1 };
const { x, y = 2 } = obj;
console.log(x); // 1
console.log(y); // 2
嵌套对象解构
可以解构嵌套的对象。
const obj = { a: 1, b: { c: 2, d: 3 } };
const { a, b: { c, d } } = obj;
console.log(a); // 1
console.log(c); // 2
console.log(d); // 3
剩余参数
使用 ...
可以将剩余的属性放入一个对象。
const obj = { x: 1, y: 2, z: 3 };
const { x, ...rest } = obj;
console.log(x); // 1
console.log(rest); // { y: 2, z: 3 }
5.3 函数参数解构
解构赋值还可以用在函数参数中,使得获取参数更加简洁。
数组参数解构
function sum([a, b]) {
return a + b;
}
console.log(sum([1, 2])); // 3
对象参数解构
function display({ name, age }) {
console.log(`Name: ${name}, Age: ${age}`);
}
display({ name: "Alice", age: 25 }); // Name: Alice, Age: 25
6. Symbol
Symbol
是 ECMAScript 6 (ES6) 中引入的一种基本数据类型,它代表独一无二的值。Symbol
主要用于为对象的属性提供唯一标识,避免属性名的冲突。以下是关于 Symbol
的详细介绍,包括它的创建、特点和应用场景。
6.1 创建 Symbol
使用 Symbol
函数可以创建一个新的 Symbol 值。每次调用 Symbol()
时都会返回一个全新的、唯一的 Symbol。
const sym1 = Symbol();
const sym2 = Symbol("description"); // 可以传入一个描述字符串,用于调试
console.log(sym1); // Symbol()
console.log(sym2); // Symbol(description)
// 确保每个 Symbol 都是唯一的
console.log(sym1 === sym2); // false
6.2 Symbol 的特点
a. 唯一性
每个 Symbol 值都是唯一的,即使它们有相同的描述。
const symA = Symbol("test");
const symB = Symbol("test");
console.log(symA === symB); // false
b. 不可转换为字符串
Symbol 是不可转换为字符串的类型,并且不能与其他类型比较。
const sym = Symbol("test");
// 下面的代码会抛出错误
console.log(String(sym)); // "Symbol(test)"
c. 用作对象的属性名
Symbol 可以用作对象的属性键,因此不会被意外覆盖,也不会与其他字符串或数字属性冲突。
const sym = Symbol("id");
const obj = {
[sym]: 123,
name: "Alice"
};
console.log(obj[sym]); // 123
console.log(obj.name); // "Alice"
6.3 Symbol 的使用场景
a. 定义私有属性
虽然 JavaScript 中没有真正的私有属性,但可以使用 Symbol 创建不易被访问的属性。
const privateKey = Symbol("private");
const obj = {
[privateKey]: "This is private",
public: "This is public"
};
console.log(obj[privateKey]); // "This is private"
console.log(obj.public); // "This is public"
b. 避免属性名冲突
在大型项目或库中,使用 Symbol 作为属性键可以避免不同模块之间的命名冲突。
const moduleA = {
[Symbol("unique")]: "Module A",
};
const moduleB = {
[Symbol("unique")]: "Module B",
};
console.log(moduleA); // { [Symbol(unique)]: 'Module A' }
console.log(moduleB); // { [Symbol(unique)]: 'Module B' }
c. 实现迭代器
ES6 的一些内建接口如 for...of
、Array.from()
等使用 Symbols 来定义默认迭代器。
const myArray = {
data: [1, 2, 3],
[Symbol.iterator]: function() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
for (const value of myArray) {
console.log(value); // 1, 2, 3
}
6.4 Global Symbol Registry
ES6 提供了 Symbol.for()
和 Symbol.keyFor()
方法来管理全局 Symbol 注册表。
a. Symbol.for()
在全局注册 Symbol,如果该 Symbol 已经存在,则返回已有的 Symbol。
const globalSym = Symbol.for("global");
const anotherSym = Symbol.for("global");
console.log(globalSym === anotherSym); // true
b. Symbol.keyFor()
获取为特定 Symbol 注册的键。
const sym = Symbol.for("example");
console.log(Symbol.keyFor(sym)); // "example"
6.5 常用的内建 Symbol
JavaScript 提供了一些内建的 Symbol 值,可以用来修改对象的某些行为,例如:
Symbol.iterator
: 定义自定义迭代器。Symbol.toStringTag
: 定义对象的默认字符串描述。Symbol.hasInstance
: 定义instanceof
行为的自定义逻辑。
// 示例:使用 Symbol.toStringTag
class MyClass {}
console.log(Object.prototype.toString.call(new MyClass())); // [object Object]
MyClass[Symbol.toStringTag] = "MyCustomClass";
console.log(Object.prototype.toString.call(new MyClass())); // [object MyCustomClass]
7. Map 和 Set
ES6 引入了两种新的数据结构:Map
和 Set
。它们提供了更灵活和高效的方式来处理集合数据。以下是对 Map
和 Set
的详细介绍。
7.1 Map
Map
是一种键值对的集合,其中键和值可以是任何类型的数据。与对象(Object)不同,Map
的键可以是任意类型,包括对象、函数、基本数据类型等。
基本用法
创建 Map
const map = new Map();
添加键值对
map.set('name', 'Alice');
map.set('age', 25);
map.set({}, 'an empty object');
console.log(map); // Map { 'name' => 'Alice', 'age' => 25, {} => 'an empty object' }
获取值
console.log(map.get('name')); // 'Alice'
console.log(map.get('age')); // 25
console.log(map.get({})); // undefined
检查键是否存在
console.log(map.has('name')); // true
console.log(map.has('gender')); // false
删除键值对
map.delete('age');
console.log(map); // Map { 'name' => 'Alice', {} => 'an empty object' }
清空 Map
map.clear();
console.log(map); // Map {}
获取 Map 的大小
console.log(map.size); // 0
遍历 Map
可以使用 for...of
循环或 forEach
方法遍历 Map
。
const map = new Map([
['name', 'Alice'],
['age', 25],
[{}, 'an empty object']
]);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
7.2 Set
Set
是一种集合数据结构,它存储唯一的值,不允许重复。Set
中的值可以是任何类型的数据。
基本用法
创建 Set
const set = new Set();
添加值
set.add(1);
set.add(2);
set.add(2); // 重复的值不会被添加
set.add('hello');
set.add({});
console.log(set); // Set { 1, 2, 'hello', {} }
检查值是否存在
console.log(set.has(1)); // true
console.log(set.has(3)); // false
删除值
set.delete(2);
console.log(set); // Set { 1, 'hello', {} }
清空 Set
set.clear();
console.log(set); // Set {}
获取 Set 的大小
console.log(set.size); // 0
遍历 Set
可以使用 for...of
循环或 forEach
方法遍历 Set
。
const set = new Set([1, 2, 3, 4]);
for (const value of set) {
console.log(value);
}
set.forEach((value) => {
console.log(value);
});
7.3 Map 和 Set 的比较
相同点
- 都是集合数据结构,可以存储任意类型的值。
- 都支持
size
属性来获取集合的大小。 - 都支持
clear()
方法来清空集合。 - 都支持
for...of
循环和forEach
方法进行遍历。
不同点
Map
存储键值对,而Set
只存储值。Map
的键可以是任意类型,而Set
的值必须是唯一的。Map
支持set()
、get()
、has()
、delete()
等方法,而Set
支持add()
、has()
、delete()
等方法。
7.4 应用场景
Map
- 存储配置信息:使用
Map
可以方便地存储和获取配置信息,键可以是字符串、数字或其他对象。 - 缓存数据:使用
Map
可以实现简单的缓存机制,通过键来存储和获取缓存数据。 - 复杂数据结构:在需要存储复杂数据结构时,
Map
提供了更灵活的键值对存储方式。
Set
- 去重:
Set
可以方便地去除数组中的重复元素。 - 集合操作:
Set
可以用于集合的并集、交集、差集等操作。 - 唯一标识符:在需要存储唯一标识符时,
Set
可以确保每个值都是唯一的。
7.5 总结
Map
和 Set
是 ES6 中非常有用的数据结构,它们提供了更灵活和高效的方式来处理集合数据。Map
适用于需要键值对存储的场景,而 Set
适用于需要存储唯一值的场景。通过合理使用 Map
和 Set
,可以提高代码的可读性和性能。
8. 迭代器和生成器
迭代器(Iterator)
迭代器是一种对象,它包含了访问集合中各个元素的方法。迭代器通常实现了一个 next()
方法,该方法返回一个对象,这个对象包含了两个属性:
value
:表示当前访问的元素。done
:一个布尔值,表示迭代是否结束。如果还有更多的元素可迭代,done
为false
;如果没有更多元素可迭代,则为true
。
示例:
const myArray = [1, 2, 3];
const myIterator = myArray[Symbol.iterator]();
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }
生成器(Generator)
生成器是ES6中引入的一种特别的函数,能够返回一个迭代器。当调用生成器函数时,它并不会立即执行,而是返回一个生成器对象。每当生成器函数被调用其 yield
表达式时,函数的执行会“暂停”,并返回 yield
的值。稍后可以通过调用生成器的 next()
方法再次恢复执行。
生成器的语法使用 function*
来定义,并通过 yield
关键字来生成值。
示例:
function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
const generator = generateNumbers();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
总结
- 迭代器是一种提供访问集合中元素的方法的对象;
- 生成器是特殊的函数,使用
yield
关键字可以逐步生成值,并返回一个迭代器。
9. Promise 对象
Promise
是 ES6 引入的一种用于处理异步操作的对象。它代表了一个可能在未来某个时刻完成或失败的操作,并可以用于链式调用。这使得对异步操作的管理变得更加简单、清晰。
9.1 Promise 的基本概念
一个 Promise
对象有三种状态:
- Pending(待定):初始状态,既不是成功,也不是失败。
- Fulfilled(已兑现):操作成功完成。
- Rejected(已拒绝):操作失败。
9.2 创建 Promise
要创建一个 Promise
,可以使用 new Promise()
构造函数,接受一个执行器(executor)作为参数。执行器是一个函数,带有两个参数:resolve
和 reject
。当异步操作成功时调用 resolve
,失败时调用 reject
。
const myPromise = new Promise((resolve, reject) => {
// 异步操作
const success = true; // 模拟成功或失败
if (success) {
resolve('操作成功!');
} else {
reject('操作失败!');
}
});
9.3 使用 Promise
创建 Promise
后,可以通过 .then()
、.catch()
和 .finally()
方法来处理结果。
9.3.1 .then()
用于处理 Promise
成功的结果。
myPromise
.then(result => {
console.log(result); // 处理成功结果
})
.catch(error => {
console.error(error); // 处理失败原因
});
9.3.2 .catch()
用于处理 Promise
被拒绝的情况。
myPromise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(`捕获错误: ${error}`);
});
9.3.3 .finally()
不论 Promise
最终是成功还是失败,.finally()
方法都会被调用,常用于清理工作。
myPromise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(`捕获错误: ${error}`);
})
.finally(() => {
console.log('无论成功或失败,都会执行这句话。');
});
9.4 Promise 链式调用
Promise
支持链式调用。这使得结果可以被组合和处理,使代码更易读。
myPromise
.then(result => {
console.log(result);
return new Promise((resolve) => resolve('链式调用成功!'));
})
.then(result2 => {
console.log(result2);
})
.catch(error => {
console.error(`捕获错误: ${error}`);
});
9.5 Promise.all
Promise.all()
方法接收一个可迭代对象(如数组)作为参数,返回一个新的 Promise
,该 Promise
在所有输入的 Promise
都成功时返回一个包含所有结果的数组,如果任意一个输入 Promise
被拒绝,返回的 Promise
也会被拒绝。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
const promise3 = 42;
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [3, 'foo', 42]
})
.catch(error => {
console.error('有一个 Promise 被拒绝:', error);
});
9.6 Promise.race
Promise.race()
方法返回一个新的 Promise
,该 Promise
在第一个输入的 Promise
决议(兑现或拒绝)时解决。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, '第一个');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, '第二个');
});
Promise.race([promise1, promise2])
.then(value => {
console.log(value); // '第二个'
});
9.7 Promise.allSettled
Promise.allSettled()
方法返回一个新的 Promise
,该 Promise
在所有输入的 Promise
都已完成时解决,无论结果是成功还是失败,返回一个对象数组,每个对象包含 status
和 value
或 reason
。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
setTimeout(reject, 100, '出错了');
});
Promise.allSettled([promise1, promise2])
.then(results => {
results.forEach((result) => {
if (result.status === 'fulfilled') {
console.log(`成功: ${result.value}`);
} else {
console.error(`失败: ${result.reason}`);
}
});
});
9.8 Promise.any
Promise.any()
方法接受一个可迭代对象,返回一个 Promise
,该 Promise
在输入的 Promise
至少有一个成功时解决,如果所有输入的 Promise
都失败,则返回一个拒绝状态的 Promise
。
const promise1 = Promise.reject('错误1');
const promise2 = Promise.reject('错误2');
const promise3 = Promise.resolve('成功');
Promise.any([promise1, promise2, promise3])
.then(result => {
console.log(result); // '成功'
})
.catch(error => {
console.error('所有 Promise 都失败:', error);
});
9.9 总结
Promise
对象为处理异步操作提供了一种更清晰、更易于维护的方式。通过使用 Promise
,开发者可以使用链式调用、处理多个异步操作的结果,及更好地处理错误。随着对 Promise
的熟悉,开发者可以写出更高效、更易读的异步代码。
10. Proxy 对象
Proxy
对象是 ES6 引入的一项强大的功能,用于定义对象的基本操作的自定义行为。通过使用 Proxy
,开发者可以在对象被访问、修改、删除等操作发生时,执行自定义的逻辑,而不是执行默认的 JavaScript 行为。这为元编程(meta-programming)提供了强有力的支持。
10.1 基本概念
Proxy
对象用于在目标对象之上创建一个 “拦截器”,你可以拦截并重新定义对象的各种操作行为。
10.2 创建 Proxy
要创建一个 Proxy
,可以使用 new Proxy(target, handler)
构造函数,其中:
- target:目标对象,即被代理的对象。
- handler:处理程序对象,包含一系列捕获器(trap),用于定义代理的行为。
const target = {
message1: "hello",
message2: "world"
};
const handler = {
// 捕获器 (trap)
get(target, prop, receiver) {
if (prop in target) {
return target[prop];
} else {
return '未定义';
}
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message1); // "hello"
console.log(proxy.message3); // "未定义"
10.3 常用捕获器(Traps)
Proxy
提供了多种捕获器,用于拦截对象的各种操作。以下是一些常用的捕获器:
10.3.1 get(target, prop, receiver)
拦截对象属性的读取操作。
const handler = {
get(target, prop, receiver) {
console.log(`读取属性 ${prop}`);
return Reflect.get(target, prop, receiver);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message1); // "读取属性 message1",然后输出 "hello"
10.3.2 set(target, prop, value, receiver)
拦截对象属性的赋值操作。
const handler = {
set(target, prop, value, receiver) {
console.log(`设置属性 ${prop} 为 ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.message1 = "你好"; // "设置属性 message1 为 你好"
10.3.3 has(target, prop)
拦截 in
操作符,判断对象是否包含某个属性。
const handler = {
has(target, prop) {
console.log(`检查属性 ${prop}`);
return prop in target;
}
};
const proxy = new Proxy(target, handler);
console.log('message1' in proxy); // "检查属性 message1",然后输出 true
10.3.4 deleteProperty(target, prop)
拦截 delete
操作符,删除对象的属性。
const handler = {
deleteProperty(target, prop) {
console.log(`删除属性 ${prop}`);
delete target[prop];
return true;
}
};
const proxy = new Proxy(target, handler);
delete proxy.message1; // "删除属性 message1"
10.3.5 apply(target, thisArg, args)
拦截函数的调用操作。
const targetFunction = (a, b) => a + b;
const handler = {
apply(target, thisArg, args) {
console.log(`调用函数 ${target.name} 传入参数 ${args}`);
return target.apply(thisArg, args);
}
};
const proxy = new Proxy(targetFunction, handler);
console.log(proxy(1, 2)); // "调用函数 undefined 传入参数 1,2",然后输出 3
10.3.6 construct(target, args)
拦截 new
操作符,用于构造函数。
class TargetClass {
constructor(name) {
this.name = name;
}
}
const handler = {
construct(target, args) {
console.log(`调用构造函数 ${target.name} 传入参数 ${args}`);
return new target(...args);
}
};
const ProxyClass = new Proxy(TargetClass, handler);
const instance = new ProxyClass('Alice'); // "调用构造函数 TargetClass 传入参数 Alice"
10.4 使用场景
Proxy
对象在以下场景中非常有用:
- 数据验证:在设置属性值时进行数据验证。
- 日志记录:记录对象的访问和修改操作。
- 性能监控:跟踪和分析对象的操作性能。
- 安全控制:控制对象的访问权限。
- 调试:在开发过程中方便调试和监控对象状态。
10.5 总结
Proxy
对象为 JavaScript 提供了一种强大的机制,允许开发者拦截和自定义对象的基本操作。通过使用 Proxy
,开发者可以实现更灵活、更强大的功能,例如数据验证、安全控制、性能监控等。掌握 Proxy
的使用,可以帮助你更好地管理复杂的对象操作和行为。
11. async 的用法
async
是 ES6 引入的一个关键字,用于简化异步编程。它通常与 await
关键字一起使用,使得异步代码的编写和阅读更加直观和简洁。async
函数本质上是一个返回 Promise
的函数,但它的语法和结构使得异步代码看起来更像同步代码。
11.1 基本概念
async
函数:使用async
关键字声明的函数称为async
函数。async
函数总是返回一个Promise
,即使函数体内没有显式返回Promise
。await
关键字:await
关键字只能在async
函数内部使用,用于等待一个Promise
的解析结果。await
会暂停函数的执行,直到Promise
被解析或拒绝。
11.2 基本用法
11.2.1 定义 async
函数
async function fetchData() {
return "数据已获取";
}
fetchData().then(data => {
console.log(data); // "数据已获取"
});
在这个例子中,fetchData
是一个 async
函数,它返回一个 Promise
,解析值为 "数据已获取"
。
11.2.2 使用 await
等待 Promise
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
fetchData().then(data => {
console.log(data); // 输出从 API 获取的数据
});
在这个例子中,await
关键字用于等待 fetch
请求和 json
解析的完成。await
使得异步操作看起来像是同步操作,代码更加简洁易读。
11.3 错误处理
在 async
函数中,可以使用 try...catch
结构来捕获和处理异步操作中的错误。
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error("请求失败:", error);
throw error; // 可以选择重新抛出错误
}
}
fetchData().then(data => {
console.log(data);
}).catch(error => {
console.error("捕获到错误:", error);
});
在这个例子中,try...catch
块用于捕获 fetch
请求或 json
解析过程中可能发生的错误。如果发生错误,catch
块会捕获并处理它。
11.4 并行执行
如果多个异步操作可以并行执行,可以使用 Promise.all
来同时启动多个异步操作,并使用 await
等待所有操作完成。
async function fetchAllData() {
const [data1, data2] = await Promise.all([
fetch('https://api.example.com/data1').then(response => response.json()),
fetch('https://api.example.com/data2').then(response => response.json())
]);
return { data1, data2 };
}
fetchAllData().then(data => {
console.log(data); // 输出两个 API 的数据
});
在这个例子中,Promise.all
用于并行启动两个 fetch
请求,并等待它们都完成。await
等待 Promise.all
的结果,返回一个包含所有数据的对象。
11.5 总结
async
和 await
是 ES6 中用于简化异步编程的重要工具。它们使得异步代码的编写和阅读更加直观和简洁,避免了回调地狱(callback hell)和复杂的 Promise
链。通过结合 try...catch
和 Promise.all
,可以更灵活地处理异步操作中的错误和并行执行。
12. 类 class
ES6 引入了 class
关键字,以更清晰和简洁的方式创建 JavaScript 的对象和继承。虽然 JavaScript 使用原型继承,但 class
提供了一种类似于其他面向对象编程语言(如 Java 和 C#)的语法,使得定义对象和继承的过程更加友好。
12.1 定义一个类
通过使用 class
关键字,你可以定义一个类。以下是一个基本示例:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`你好,我是 ${this.name},今年 ${this.age} 岁。`);
}
}
const alice = new Person('Alice', 30);
alice.greet(); // 输出: 你好,我是 Alice,今年 30 岁。
在这个示例中,Person
是一个类,它具有一个构造函数 constructor
和一个方法 greet
。
12.2 构造函数
constructor
是一个特殊的方法,用于创建和初始化对象的属性。每当使用 new
操作符创建类的实例时,会调用该构造函数。
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
const rect = new Rectangle(5, 10);
console.log(rect.area()); // 输出: 50
12.3 继承
ES6 类支持继承,可以通过 extends
关键字实现子类对父类的扩展。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} 发出了声音`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} 汪汪叫`);
}
}
const dog = new Dog('Buddy');
dog.speak(); // 输出: Buddy 汪汪叫
在这个例子中,Dog
类继承自 Animal
类,并重写了 speak
方法。
12.4 静态方法
使用 static
关键字可以定义静态方法,静态方法属于类本身,而不属于类的实例。
class MathUtils {
static add(a, b) {
return a + b;
}
}
console.log(MathUtils.add(5, 10)); // 输出: 15
静态方法通常用于辅助函数或工具函数,不需要实例化对象即可调用。
12.5 访问修饰符
虽然 JavaScript ES6 类本身没有提供访问修饰符(例如 public
, protected
, private
),但私有方法和属性的概念在 ES2022(ES13)中得到了引入,可以通过在属性名前加 #
实现。
class Counter {
#count = 0; // 私有属性
increment() {
this.#count++;
}
getCount() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
console.log(counter.getCount()); // 输出: 1
// console.log(counter.#count); // 语法错误:#count 是私有属性
12.6 获取和设置属性
可以使用 get
和 set
关键字定义访问器属性,来读取和写入对象的属性。
class Person {
constructor(name) {
this._name = name; // 以 _ 开头表示这是一个内部属性
}
get name() {
return this._name;
}
set name(newName) {
this._name = newName;
}
}
const person = new Person('Alice');
console.log(person.name); // 输出: Alice
person.name = 'Bob';
console.log(person.name); // 输出: Bob
12.7 总结
ES6 的 class
提供了一种更加结构化和清晰的方式来定义对象和实现继承。使用 class
,你可以:
- 更便捷地创建对象。
- 通过构造函数初始化对象的属性。
- 使用继承来扩展其他类的功能。
- 定义静态方法。
- 使用访问修饰符(私有属性)来封装属性。
- 使用访问器属性(getter 和 setter)来管理属性的读写。
13. 模块化实现
ES6 引入了模块化(Module)的概念,允许开发者将代码分割成多个模块文件,并通过 import
和 export
关键字来管理模块之间的依赖关系。模块化使得代码更加组织化、可维护性更高,并且有助于重用代码。
13.1 模块的基本概念
- 模块:一个模块就是一个文件,模块内部定义的变量、函数、类等,默认情况下对外部不可见。
- 导出(
export
):用于将模块内部的变量、函数、类等导出,以便其他模块可以通过import
关键字引入。 - 导入(
import
):用于从其他模块引入导出的内容。
13.2 导出(export
)
13.2.1 命名导出
你可以使用 export
关键字导出模块中的变量、函数、类等。命名导出可以在一个模块中导出多个内容。
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
constructor() {}
multiply(a, b) {
return a * b;
}
}
13.2.2 默认导出
你也可以使用 export default
关键字导出一个默认值。每个模块只能有一个默认导出。
// utils.js
export default function greet(name) {
return `你好,${name}!`;
}
13.3 导入(import
)
13.3.1 导入命名导出
你可以使用 import
关键字导入其他模块的命名导出内容。导入时,可以重命名导入的内容(使用 as
)。
// main.js
import { PI, add, Calculator } from './math.js';
console.log(PI); // 输出: 3.14159
console.log(add(5, 10)); // 输出: 15
const calculator = new Calculator();
console.log(calculator.multiply(3, 4)); // 输出: 12
13.3.2 导入默认导出
默认导出可以直接导入,并且可以为其指定任意名称。
// main.js
import greet from './utils.js';
console.log(greet('Alice')); // 输出: 你好,Alice!
13.3.3 导入所有命名导出
你可以使用 * as
语法导入所有命名导出内容到一个对象中。
// main.js
import * as math from './math.js';
console.log(math.PI); // 输出: 3.14159
console.log(math.add(5, 10)); // 输出: 15
const calculator = new math.Calculator();
console.log(calculator.multiply(3, 4)); // 输出: 12
13.3.4 混合导入
你可以在一个 import
语句中同时导入默认导出和命名导出。
// main.js
import greet, { PI, add } from './math.js';
console.log(greet('Bob')); // 输出: 你好,Bob!
console.log(PI); // 输出: 3.14159
console.log(add(5, 10)); // 输出: 15
13.4 模块的使用场景
13.4.1 代码组织
模块化有助于将代码分割成多个文件,每个文件专注于一个功能模块,使得代码更易于维护和管理。
13.4.2 代码复用
通过导出和导入,你可以在不同的模块中复用相同的代码,减少重复代码。
13.4.3 依赖管理
模块化使得依赖关系更加明确,编译器或运行时可以更方便地处理模块之间的依赖关系。
13.5 模块的加载方式
ES6 模块的加载有两种方式:
- 浏览器环境:可以通过
<script type="module">
标签加载模块。 - Node.js 环境:在 Node.js 中,你可以使用
.mjs
文件扩展名,或者在package.json
中设置"type": "module"
来启用 ES 模块加载。
<!-- 在浏览器中加载模块 -->
<script type="module">
import { add } from './math.js';
console.log(add(5, 10)); // 输出: 15
</script>
13.6 总结
ES6 模块化提供了一种强大的方式来组织和管理 JavaScript 代码。通过 export
和 import
关键字,你可以:
- 将代码分割成多个模块文件,便于维护和管理。
- 明确模块之间的依赖关系,便于代码复用。
- 在浏览器和 Node.js 环境中使用模块化加载方式。
拓展:
一、var
、let
和 const
之间的区别与联系
以下是 var
、let
和 const
的具体示例,说明它们之间的区别与联系。
1. 声明作用域
var 示例
function exampleVar() {
if (true) {
var x = 10; // x 在函数内有效
}
console.log(x); // 输出:10
}
exampleVar();
在这个例子中,x
是通过 var
声明的,虽然它是在 if
块内赋值的,但它在整个函数内都是有效的。
let 示例
function exampleLet() {
if (true) {
let y = 20; // y 在块内有效
console.log(y); // 输出:20
}
console.log(y); // 报错:ReferenceError: y is not defined
}
exampleLet();
在这个例子中,y
是通过 let
声明的,只在 if
块内有效。外部无法访问该变量。
2. 重复声明
var 示例
var a = 5;
var a = 10; // 允许重复声明
console.log(a); // 输出:10
var
允许在同一作用域内重复声明同一变量,后面的声明会覆盖前面的值。
let 和 const 示例
let b = 15;
// let b = 20; // 报错:SyntaxError: Identifier 'b' has already been declared
const c = 25;
// const c = 30; // 报错:SyntaxError: Identifier 'c' has already been declared
let
和 const
都不允许在同一作用域内重复声明同一变量,会导致错误。
3. 可变性
let 示例
let d = 30;
d = 40; // 值可以被修改
console.log(d); // 输出:40
let
声明的变量可以随时修改其值。
const 示例
const e = 50;
// e = 60; // 报错:TypeError: Assignment to constant variable.
const f = [1, 2, 3];
f.push(4); // 可以修改内容
console.log(f); // 输出:[1, 2, 3, 4]
const
声明的变量不能重新赋值,但如果是对象或数组,内容是可以修改的。
总结
var
使用函数作用域,允许重复声明,且可以被提升。let
使用块作用域,允许修改变量,不能重复声明。const
使用块作用域,声明常量,不能重复声明,也不能 reassigned 但可以修改对象的内容。
现代 JavaScript 编程中一般优先使用 let
和 const
,以便保持代码的清晰性和可维护性。
二、拓展运算符
ES6(ECMAScript 2015)引入了一个非常有用的特性,即扩展运算符(Spread Operator)。扩展运算符用于展开数组、对象或函数参数列表中的元素。它由三个点 (...
) 组成。
1. 数组中的扩展运算符
扩展运算符可以用于数组中,将数组的元素展开。
示例 1:合并数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArray = [...arr1, ...arr2];
console.log(mergedArray); // 输出: [1, 2, 3, 4, 5, 6]
示例 2:复制数组
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];
console.log(copiedArray); // 输出: [1, 2, 3]
2. 对象中的扩展运算符
扩展运算符也可以用于对象中,将对象的属性展开。
示例 1:合并对象
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObject = { ...obj1, ...obj2 };
console.log(mergedObject); // 输出: { a: 1, b: 3, c: 4 }
在这个例子中,b
属性在 obj2
中覆盖了 obj1
中的 b
属性。
示例 2:复制对象
const originalObject = { a: 1, b: 2 };
const copiedObject = { ...originalObject };
console.log(copiedObject); // 输出: { a: 1, b: 2 }
3. 函数参数中的扩展运算符
扩展运算符还可以用于函数的参数列表中,将数组或对象的元素展开作为函数的参数。
示例 1:展开数组作为函数参数
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
const result = sum(...numbers);
console.log(result); // 输出: 6
示例 2:展开对象作为函数参数
function displayInfo({ name, age }) {
console.log(`Name: ${name}, Age: ${age}`);
}
const person = { name: 'Alice', age: 30 };
displayInfo({ ...person }); // 输出: Name: Alice, Age: 30