ES6新特性

ES6,即ECMAScript 2015,是JavaScript的一个重要更新版本。它引入了许多新特性和改进,使得JavaScript编程更加现代化和高效。以下是ES6的一些主要新特性:

1. letconst命令

letconst是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 值。

  • forEachmapfilter 等高阶函数:在 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...ofArray.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 类本身没有提供访问修饰符(例如 publicprotectedprivate),但私有方法和属性的概念在 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 环境中使用模块化加载方式。

拓展:

 一、varlet 和 const 之间的区别与联系

以下是 varlet 和 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值