当前位置: 首页 > news >正文

ES6 新特性全面总结

ES6 新特性全面总结

ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多强大的新特性,极大地提升了JavaScript的开发体验和能力。以下是ES6主要新增知识点的详细总结:

(一)、ES6变量声明:let 和 const 详解

一、let 和 const 的基本概念

ES6引入了两种新的变量声明方式:letconst,它们与传统的var声明有显著区别。

1. let 声明

let用于声明块级作用域的变量:

let x = 10;
if (true) {
  let x = 20; // 不同的变量
  console.log(x); // 20
}
console.log(x); // 10
2. const 声明

const用于声明常量,声明后不能重新赋值:

const PI = 3.1415;
// PI = 3; // TypeError: Assignment to constant variable

二、与 var 的关键区别

特性varletconst
作用域函数作用域或全局作用域块级作用域块级作用域
变量提升是(但存在TDZ)是(但存在TDZ)
重复声明允许不允许不允许
初始值可不初始化可不初始化必须初始化
重新赋值允许允许不允许

三、块级作用域详解

1. 什么是块级作用域

块级作用域是指由{}包围的代码块形成的作用域:

{
  let a = 1;
  var b = 2;
}
console.log(a); // ReferenceError: a is not defined
console.log(b); // 2
2. 常见块级作用域场景

• if语句
• for循环
• while循环
• switch语句
• 单独的{}

四、暂时性死区(TDZ)

1. 概念

在声明前访问letconst变量会触发暂时性死区错误:

console.log(a); // undefined
var a = 1;

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
2. 原理

虽然letconst也会提升,但在声明前处于"暂时性死区",访问会报错。

五、const 的特殊说明

1. 必须初始化
const a; // SyntaxError: Missing initializer in const declaration
2. 对象和数组的特殊性

const只保证变量名绑定的内存地址不变,不保证内部数据不变:

const obj = {a: 1};
obj.a = 2; // 允许
// obj = {}; // 不允许

const arr = [1, 2];
arr.push(3); // 允许
// arr = []; // 不允许

六、最佳实践建议

  1. 默认使用const:除非需要重新赋值,否则优先使用const
  2. 需要重新赋值时用let:当变量需要改变时使用let
  3. 避免使用var:除非有特殊需求,否则不使用var
  4. 声明位置:尽量在作用域顶部声明变量
  5. 命名规范const常量可以使用全大写命名(如MAX_SIZE

七、常见使用场景

1. for循环中的let
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 0, 1, 2
}
2. 块级作用域变量
function processData(data) {
  {
    let temp = transformData(data);
    // 处理temp...
  }
  // temp在这里不可访问
}
3. 模块中的常量
// config.js
export const API_URL = 'https://api.example.com';
export const MAX_RETRIES = 3;

八、常见问题解答

Q1: 什么时候用let,什么时候用const?
A: 优先使用const,只有确定变量需要重新赋值时才使用let。

Q2: const声明的对象属性可以修改吗?
A: 可以修改对象属性,但不能重新赋值整个对象。

Q3: let和const能替代var吗?
A: 在大多数情况下可以完全替代,但要注意作用域差异。

Q4: 为什么会有暂时性死区?
A: 这是为了更早发现编程错误,避免变量提升带来的混淆。

Q5: 全局作用域下let和var有什么区别?
A: 全局作用域下,var声明的变量会成为window对象的属性,而let不会。

(二)、ES6箭头函数(Arrow Functions)全面解析

一、基本语法

箭头函数是ES6引入的一种更简洁的函数写法,使用=>符号定义。

1. 基础语法形式
// 传统函数写法
function sum(a, b) {
  return a + b;
}

// 箭头函数写法
const sum = (a, b) => a + b;
2. 不同形式的箭头函数
情况示例等价传统函数
单个参数x => x * 2function(x) { return x * 2; }
多个参数(x, y) => x + yfunction(x, y) { return x + y; }
无参数() => 42function() { return 42; }
多行函数体(a, b) => {<br> const c = a + b;<br> return c * 2;<br>}function(a, b) {<br> const c = a + b;<br> return c * 2;<br>}
返回对象() => ({ a: 1 })function() { return { a: 1 }; }

二、箭头函数的特性

1. 没有自己的this

箭头函数没有自己的this,它会捕获所在上下文的this值。

const obj = {
  name: 'Alice', // 定义一个对象属性 name,值为 'Alice'
  sayName: function() {
    console.log(this.name); // 输出当前对象的 name 属性值,即 'Alice'

    // 使用箭头函数作为 setTimeout 的回调函数
    setTimeout(() => {
      console.log(this.name); // 输出当前对象的 name 属性值,即 'Alice'
      // 箭头函数不会创建自己的 this 上下文,而是捕获外层函数的 this
      // 因此这里的 this 指向 obj 对象
    }, 100);
  },
  sayNameError: function() {
    // 使用普通函数作为 setTimeout 的回调函数
    setTimeout(function() {
      console.log(this.name); // 输出 undefined
      // 普通函数有自己的 this 上下文,默认指向全局对象(浏览器中是 window)
      // 因此这里的 this 不指向 obj 对象,而是 window 对象
      // 由于 window 对象中没有 name 属性,所以输出 undefined
    }, 100);
  }
};
2. 没有arguments对象

箭头函数没有自己的arguments对象,但可以访问外围函数的arguments

function outer(a, b) {
  const inner = () => {
    // arguments 是一个特殊的类数组对象,它包含了函数调用时传入的所有参数。
    console.log(arguments); // 访问outer的arguments
  };
  inner();
}
outer(1, 2); // [1, 2]
关键点
  • arguments 的作用域
    • 在普通函数中,arguments 是一个类数组对象,包含了函数调用时传入的所有参数。
    • 在箭头函数中,arguments 不会被自动绑定,而是继承自外层函数的 arguments
  • 箭头函数的特性
    • 箭头函数不会创建自己的 thisarguments,而是继承自外层函数的上下文。
3. 不能作为构造函数

箭头函数不能使用new调用,没有prototype属性。

const Foo = () => {};
// new Foo(); // TypeError: Foo is not a constructor
4. 没有super和new.target

箭头函数没有supernew.target绑定。

三、箭头函数的适用场景

1. 回调函数
// 数组方法
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);

// 事件处理
button.addEventListener('click', () => {
  console.log('Button clicked');
});
2. 需要保持this上下文的场景
class Counter {
  constructor() {
    this.count = 0;
    // 使用箭头函数保持this指向
    this.increment = () => {
      this.count++;
    };
  }
}
3. 简洁的单行函数
const isEven = n => n % 2 === 0;
const greet = name => `Hello, ${name}!`;

四、箭头函数的不适用场景

1. 对象方法
const obj = {
  value: 0,
  // 错误:箭头函数不会绑定this到obj
  increment: () => {
    this.value++; // this指向window/undefined
  }
};
2. 需要动态this的场景
// 错误:无法通过call/apply/bind改变this
const greet = () => console.log(this.name);
const alice = { name: 'Alice' };
greet.call(alice); // 无效
3. 需要arguments对象的函数
// 错误:无法访问自己的arguments
const sum = () => {
  console.log(arguments); // 引用外层arguments或报错
};

五、箭头函数与普通函数的对比

特性箭头函数普通函数
this绑定词法作用域动态绑定
arguments
构造函数不能
prototype
yield不能用作生成器可以
简洁性
适用场景回调、需要固定this方法、构造函数

六、常见问题解答

Q1: 什么时候应该使用箭头函数?
A: 适合需要保持this一致性的回调函数、简单的单行函数、不需要arguments的场景。

Q2: 箭头函数能替代所有普通函数吗?
A: 不能。对象方法、构造函数、需要动态thisarguments的场景仍需使用普通函数。

Q3: 箭头函数有prototype属性吗?
A: 没有,这也是它不能用作构造函数的原因之一。

Q4: 如何给箭头函数添加默认参数?
A: 和普通函数一样,直接在参数中指定:

const greet = (name = 'Guest') => `Hello, ${name}!`;

Q5: 箭头函数可以有name属性吗?
A: 可以,当箭头函数被赋值给变量时,会使用变量名作为函数名:

const foo = () => {};
console.log(foo.name); // "foo"

七、高级用法

1. 立即执行箭头函数(IIFE)
((name) => {
  console.log(`Hello, ${name}!`);
})('Alice');
2. 链式调用
const operations = {
  value: 1,
  add: (n) => {
    operations.value += n;
    return operations;
  },
  multiply: (n) => {
    operations.value *= n;
    return operations;
  }
};

operations.add(2).multiply(3).add(1); // value = 10
3. 配合解构使用
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];

const names = users.map(({ name }) => name); // ['Alice', 'Bob']

箭头函数是ES6中最受欢迎的特性之一,正确理解和使用它可以使代码更简洁、更可读,同时避免许多this绑定的陷阱。

(三)、ES6模板字符串(Template Literals)深度解析

一、基本概念与语法

模板字符串是ES6引入的一种新型字符串表示法,使用反引号(`)包裹内容,相比传统字符串具有更强大的功能。

1. 基础语法对比
// 传统字符串
const name = 'Alice';
const greeting = 'Hello, ' + name + '!';

// 模板字符串
const greeting = `Hello, ${name}!`;
2. 核心特性

多行字符串:直接支持换行
字符串插值:使用${expression}嵌入变量和表达式
标签模板:可以自定义字符串处理函数

二、多行字符串处理

1. 传统方式的痛点
// ES5实现多行字符串
var message = '第一行\n' +
              '第二行\n' +
              '第三行';
2. 模板字符串解决方案
const message = `第一行
第二行
第三行`;
3. 实际应用场景
// HTML模板
const html = `
<div class="container">
  <h1>${title}</h1>
  <p>${content}</p>
</div>
`;

// SQL查询
const query = `
SELECT * FROM users
WHERE id = ${userId}
ORDER BY name DESC
`;

三、字符串插值详解

1. 基本插值
const name = 'Alice';
const age = 25;
console.log(`Name: ${name}, Age: ${age}`); // "Name: Alice, Age: 25"
2. 表达式计算
const a = 10;
const b = 20;
console.log(`Sum: ${a + b}`); // "Sum: 30"
3. 函数调用
function getAge() {
  return 25;
}
console.log(`Age: ${getAge()}`); // "Age: 25"
4. 嵌套模板
const isMember = true;
console.log(`Status: ${
  isMember ? `Member since ${2020}` : 'Not a member'
}`); // "Status: Member since 2020"

四、标签模板(Tagged Templates)

1. 基本概念

标签模板允许使用函数解析模板字符串,第一个参数是字符串数组,后续参数是插值表达式。

function tag(strings, ...values) {
  console.log(strings); // ["Hello ", "!"]
  console.log(values); // ["Alice"]
  return 'Processed string';
}

const name = 'Alice';
const result = tag`Hello ${name}!`;
// 当调用 tag 函数时:
// strings 参数接收模板字符串中的文本部分。
// values 参数接收模板字符串中的嵌入值。
2. 实际应用案例
a) 安全HTML转义
// 定义一个标签函数 safeHtml,用于处理模板字符串并防止 XSS 攻击
function safeHtml(strings, ...values) {
  let result = ''; // 初始化一个空字符串,用于存储最终生成的 HTML 内容

  // 遍历 strings 数组,它包含了模板字符串中的所有文本部分
  for (let i = 0; i < strings.length; i++) {
    result += strings[i]; // 将当前的文本部分添加到 result 中

    // 检查是否存在对应的嵌入值
    if (i < values.length) {
      // 将嵌入值转换为字符串,并进行 HTML 转义
      result += String(values[i])
        .replace(/&/g, '&amp;')    // 将 & 替换为 &amp;
        .replace(/</g, '&lt;')     // 将 < 替换为 &lt;
        .replace(/>/g, '&gt;')     // 将 > 替换为 &gt;
        .replace(/"/g, '&quot;')   // 将 " 替换为 &quot;
        .replace(/'/g, '&#39;');   // 将 ' 替换为 &#39;
    }
  }

  return result; // 返回最终生成的 HTML 内容
}

// 定义一个可能包含恶意脚本的用户输入
const userInput = '<script>alert("XSS")</script>';

// 使用 safeHtml 标签函数处理模板字符串
const safeOutput = safeHtml`<div>${userInput}</div>`;

// 输出结果
console.log(safeOutput); // <div>&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</div>
b) 国际化处理
function i18n(strings, ...values) {
  const translations = {
    'Hello': '你好',
    'Welcome': '欢迎'
  };
  let translated = '';
  strings.forEach((str, i) => {
    translated += translations[str.trim()] || str;
    if (values[i]) translated += values[i];
  });
  return translated;
}

const name = 'Alice';
console.log(i18n`Hello ${name}!`); // "你好 Alice!"
c) SQL查询构建
function sqlQuery(strings, ...values) {
  // 实际应用中应该使用数据库驱动提供的参数化查询
  let query = strings[0];
  for (let i = 0; i < values.length; i++) {
    query += `$${i + 1}` + strings[i + 1];
  }
  return {
    text: query,
    values: values
  };
}

const userId = 123;
const query = sqlQuery`SELECT * FROM users WHERE id = ${userId}`;
// {
//   text: "SELECT * FROM users WHERE id = $1",
//   values: [123]
// }

五、特殊字符处理

1. 转义字符
console.log(`反引号: \` 美元符号: \${`); // "反引号: ` 美元符号: ${"
2. 原始字符串

使用String.raw标签获取原始字符串(不处理转义字符):

const path = String.raw`C:\Development\project\files`;
console.log(path); // "C:\Development\project\files"

// 等同于
function raw(strings, ...values) {
  let result = strings.raw[0];
  for (let i = 0; i < values.length; i++) {
    result += values[i] + strings.raw[i + 1];
  }
  return result;
}

六、性能考量

  1. 静态字符串:对于纯静态字符串,模板字符串与普通字符串性能相当
  2. 动态插值:频繁变化的插值内容可能影响性能,在极端性能敏感场景需测试
  3. 标签模板:自定义处理会增加开销,但通常可忽略不计

七、最佳实践

  1. 优先使用模板字符串:替代所有字符串拼接场景

  2. 复杂逻辑处理:对于复杂插值逻辑,考虑提前计算表达式

    // 不推荐
    console.log(`Result: ${calculateA() + calculateB() * complexCalculation()}`);
    
    // 推荐
    const result = calculateA() + calculateB() * complexCalculation();
    console.log(`Result: ${result}`);
    
  3. 多行缩进处理:使用.trim()消除不必要的缩进

    function getHtml() {
      return `
        <div>
          <p>Content</p>
        </div>
      `.trim();
    }
    
  4. 安全注意事项
    • 不要直接将用户输入插入HTML/URL/SQL
    • 使用专用转义库或标签模板处理危险内容

八、浏览器兼容性

现代浏览器均支持模板字符串特性,对于旧版浏览器需要通过Babel等工具转译:
• ES6转ES5:将模板字符串转换为普通字符串拼接
• 标签模板:转换为函数调用形式

九、扩展应用

1. 配合React等框架
const name = 'Alice';
const element = <div>Hello, {name}!</div>;

// JSX本质上也是一种模板字符串的扩展应用
2. 生成动态CSS
const primaryColor = '#3498db';
const css = `
  .button {
    background: ${primaryColor};
    padding: 10px 20px;
  }
`;
3. 创建DSL(领域特定语言)
function createRoute(strings, ...values) {
  return {
    path: strings.join('').replace(/\s+/g, ''),
    params: values
  };
}

const id = 123;
const route = createRoute`/users/ ${id} /profile`;
// { path: "/users/123/profile", params: [123] }

模板字符串彻底改变了JavaScript处理字符串的方式,使代码更简洁、更可读,同时通过标签模板提供了强大的扩展能力。正确使用这一特性可以显著提升开发效率和代码质量。

(四)、ES6解构赋值(Destructuring Assignment)全面解析

一、基本概念

解构赋值是ES6引入的一种语法,允许按照一定模式从数组或对象中提取值,然后对变量进行赋值。这种语法可以极大简化数据提取的代码。

二、数组解构

1. 基本用法
const arr = [1, 2, 3];

// 传统方式
const a = arr[0];
const b = arr[1];
const c = arr[2];

// 解构赋值
const [a, b, c] = arr; // a=1, b=2, c=3
2. 嵌套解构
const arr = [1, [2, 3], 4];
const [a, [b, c], d] = arr; // a=1, b=2, c=3, d=4
3. 默认值
const [a=1, b=2] = []; // a=1, b=2
const [a, b=2] = [5]; // a=5, b=2
4. 跳过元素
const [a, , b] = [1, 2, 3]; // a=1, b=3 (跳过第二个元素)
5. 剩余模式
const [a, ...rest] = [1, 2, 3]; // a=1, rest=[2, 3]

三、对象解构

1. 基本用法
const obj = { x: 1, y: 2 };

// 传统方式
const x = obj.x;
const y = obj.y;

// 解构赋值
const { x, y } = obj; // x=1, y=2
2. 别名赋值
const { x: a, y: b } = { x: 1, y: 2 }; // a=1, b=2
3. 默认值
const { a=1, b=2 } = {}; // a=1, b=2
const { a: x=1, b: y=2 } = { b: 3 }; // x=1, y=3
4. 嵌套解构
const obj = { a: { b: 1, c: 2 }, d: 3 };
const { a: { b, c }, d } = obj; // b=1, c=2, d=3
5. 剩余模式
const { a, ...rest } = { a: 1, b: 2, c: 3 }; // a=1, rest={b:2, c:3}

四、混合解构

可以混合使用数组和对象解构:

const props = {
  arr: [1, { b: 2, c: 3 }]
};
const { arr: [a, { b, c }] } = props; // a=1, b=2, c=3

五、函数参数解构

1. 对象参数解构
function draw({ x=0, y=0, radius=1 }) {
  console.log(x, y, radius);
}
draw({ x: 10, y: 20 }); // 10 20 1
2. 数组参数解构
function sum([a=0, b=0]) {
  return a + b;
}
sum([1, 2]); // 3
3. 复杂参数解构
function process({ 
  id, 
  name: firstName, 
  address: { city } = {} 
}) {
  console.log(id, firstName, city);
}
process({ id: 1, name: 'Alice', address: { city: 'Beijing' } });

六、特殊应用场景

1. 交换变量值
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1
2. 函数返回多个值
function getData() {
  return [1, 2, 3];
}
const [a, b, c] = getData();
3. 正则表达式匹配
const url = 'https://example.com/path';
const { 1: protocol, 2: host } = url.match(/(\w+):\/\/([^/]+)/);
4. 模块导入
import { Component, useState } from 'react';
5. 配置对象处理
function init({ 
  width = 100, 
  height = 200, 
  color = 'red' 
} = {}) {
  // 使用解构参数并设置默认值
}

七、注意事项

  1. 解构失败:如果解构不成功,变量的值等于undefined

    const [a] = []; // a=undefined
    const { b } = {}; // b=undefined
    
  2. 模式匹配:解构赋值的左边是模式,不是变量

    const { a: b } = { a: 1 }; // 模式是a,变量是b
    
  3. 不可迭代值:对非迭代值使用数组解构会报错

    const [a] = null; // TypeError
    
  4. 已声明变量:已声明变量解构需要用括号包裹

    let a;
    ({ a } = { a: 1 }); // 必须加括号
    
  5. 默认值生效条件:只有当解构的值严格等于undefined时,默认值才会生效

    const { a = 1 } = { a: null }; // a=null
    

八、最佳实践

  1. 合理使用默认值:为可能不存在的属性设置默认值
  2. 避免过度嵌套:过深的解构会降低代码可读性
  3. 明确变量名:使用别名时选择有意义的名称
  4. 处理错误情况:考虑解构失败时的处理方式
  5. 文档注释:对复杂解构添加注释说明结构

九、浏览器兼容性

现代浏览器均支持解构赋值,对于旧版浏览器需要通过Babel等工具转译:
• 对象解构转换为Object.assign()或逐个属性赋值
• 数组解构转换为下标访问

解构赋值是ES6中最实用的特性之一,合理使用可以显著提高代码的简洁性和可读性,特别是在处理复杂数据结构时。

(五)、函数参数默认值

function sayHello(name = 'Guest') {
  console.log(`Hello, ${name}!`);
}
sayHello(); // Hello, Guest!

(六)、ES6 扩展运算符(Spread Operator)深度解析

扩展运算符(...)是 ES6 引入的一个重要特性,它允许将可迭代对象(如数组、字符串、Map、Set 等)"展开"为单独的元素。

一、基本语法与概念

扩展运算符使用三个点(...)表示,主要功能是将一个可迭代对象展开为多个元素。

const arr = [1, 2, 3];
console.log(...arr); // 1 2 3

二、数组中的应用

1. 数组复制(浅拷贝)
const original = [1, 2, 3];
const copy = [...original]; // 创建新数组
2. 数组合并
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
3. 数组解构
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]
4. 插入元素
const numbers = [1, 2, 3];
const newNumbers = [0, ...numbers, 4]; // [0, 1, 2, 3, 4]
5. 替代 apply 方法
// ES5
Math.max.apply(null, [1, 2, 3]);

// ES6
Math.max(...[1, 2, 3]);

三、对象中的应用(ES2018+)

1. 对象复制(浅拷贝)
const obj = { a: 1, b: 2 };
const copy = { ...obj }; // { a: 1, b: 2 }
2. 对象合并
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2 }
3. 属性覆盖
const defaults = { color: 'red', size: 'medium' };
const settings = { ...defaults, color: 'blue' };
// { color: 'blue', size: 'medium' }
4. 添加新属性
const person = { name: 'Alice' };
const withAge = { ...person, age: 25 }; // { name: 'Alice', age: 25 }

四、函数参数中的应用

1. 收集剩余参数
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
2. 传递数组参数
const numbers = [1, 2, 3];
sum(...numbers); // 等同于 sum(1, 2, 3)

五、其他可迭代对象中的应用

1. 字符串展开
const str = 'hello';
const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']
2. Set 展开
const set = new Set([1, 2, 3]);
const arr = [...set]; // [1, 2, 3]
3. Map 展开
const map = new Map([['a', 1], ['b', 2]]);
const entries = [...map]; // [['a', 1], ['b', 2]]

六、高级用法

1. 实现数组扁平化
function flatten(arr) {
  return [].concat(...arr);
}
flatten([[1], [2, 3], [4]]); // [1, 2, 3, 4]
2. 实现深度克隆(仅适用于特定情况)
const deepClone = obj => JSON.parse(JSON.stringify(obj));
const cloned = deepClone({ a: [1, 2], b: { c: 3 } });
3. 条件展开
const condition = true;
const obj = {
  ...(condition && { a: 1 }),
  b: 2
}; // { a: 1, b: 2 } 或 { b: 2 }

七、注意事项

  1. 浅拷贝问题:扩展运算符只进行浅拷贝

    const obj = { a: { b: 1 } };
    const copy = { ...obj };
    copy.a.b = 2;
    console.log(obj.a.b); // 2 (被修改)
    
  2. 性能考虑:对于大型数组/对象,扩展运算符可能不是最高效的选择

  3. 浏览器兼容性:对象展开是 ES2018 特性,旧环境可能需要 Babel 转译

  4. 不可迭代对象:不能展开普通对象(在数组上下文中)

    const obj = { a: 1, b: 2 };
    // [...obj]; // TypeError: obj is not iterable
    

八、最佳实践

  1. 优先使用扩展运算符:替代 concatapply 等传统方法

  2. 合理使用解构:结合解构赋值处理复杂数据结构

  3. 注意不可变性:在 React/Redux 等需要不可变数据的场景中特别有用

  4. 命名清晰:使用有意义的变量名提高可读性

  5. 文档注释:对复杂展开逻辑添加注释说明

九、常见问题解答

Q1: 扩展运算符和剩余参数有什么区别?
A: 语法相同但使用场景不同:
• 扩展运算符用于展开元素
• 剩余参数用于收集元素

Q2: 如何实现深度克隆?
A: 扩展运算符只能浅拷贝,深度克隆需要递归或使用 JSON.parse(JSON.stringify())(有局限性)

Q3: 可以展开 Generator 吗?
A: 可以,Generator 是可迭代对象:

function* gen() { yield 1; yield 2; }
[...gen()]; // [1, 2]

Q4: 为什么对象展开是 ES2018 特性?
A: 数组展开在 ES6 引入,对象展开稍晚标准化

Q5: 扩展运算符会影响原对象吗?
A: 不会,但浅拷贝的属性引用相同

扩展运算符极大简化了 JavaScript 中对数组和对象的操作,是现代 JavaScript 开发中不可或缺的特性。合理使用可以使代码更加简洁、可读性更强。

(七)、ES6 Promise 深度解析

一、Promise 基本概念

Promise 是 ES6 引入的异步编程解决方案,用于处理异步操作。它代表一个尚未完成但预期将来会完成的操作及其结果值。

1. 三种状态

pending(待定):初始状态
fulfilled(已兑现):操作成功完成
rejected(已拒绝):操作失败

状态转换是不可逆的:pending → fulfilled 或 pending → rejected

2. 基本语法
const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 成功 */) {
    resolve(value); // 状态变为fulfilled
  } else {
    reject(error); // 状态变为rejected
  }
});

二、Promise 实例方法

1. then() 方法
promise.then(
  value => { /* 成功处理 */ },
  error => { /* 失败处理 */ }
);
2. catch() 方法
promise.catch(
  error => { /* 失败处理 */ }
);
3. finally() 方法
promise.finally(
  () => { /* 无论成功失败都会执行 */ }
);

三、Promise 静态方法

1. Promise.resolve()
Promise.resolve('success')
  .then(val => console.log(val)); // 'success'
2. Promise.reject()
Promise.reject('error')
  .catch(err => console.log(err)); // 'error'
3. Promise.all()
Promise.all([promise1, promise2])
  .then(values => { /* 所有promise都成功 */ })
  .catch(error => { /* 任一promise失败 */ });
4. Promise.race()
Promise.race([promise1, promise2])
  .then(value => { /* 第一个完成的promise */ });
5. Promise.allSettled()
Promise.allSettled([promise1, promise2])
  .then(results => { /* 所有promise都完成 */ });
6. Promise.any()
Promise.any([promise1, promise2])
  .then(value => { /* 第一个成功的promise */ })
  .catch(errors => { /* 所有promise都失败 */ });

四、Promise 链式调用

Promise 的 then() 方法返回一个新的 Promise,可以实现链式调用:

doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .then(finalResult => console.log(finalResult))
  .catch(failureCallback);

五、Promise 错误处理

1. 使用 catch()
promise
  .then(handleSuccess)
  .catch(handleError);
2. then() 的第二个参数
promise
  .then(handleSuccess, handleError);
3. 区别

.then(success, error):只能捕获当前 then 之前的错误
.catch(error):可以捕获整个链中的错误

六、Promise 最佳实践

  1. 总是返回 Promise:在 then() 回调中返回 Promise 或值
  2. 避免嵌套:使用链式调用而非嵌套
  3. 总是捕获错误:使用 catch() 处理错误
  4. 命名 Promise:给 Promise 变量有意义的名称
  5. 避免冗余代码:合理使用 Promise 静态方法

七、Promise 实现示例

1. 封装 setTimeout
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

delay(1000).then(() => console.log('1秒后执行'));

2. 封装 XMLHttpRequest

function getJSON(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

3. 封装 fetch

function fetchData(url) {
  return fetch(url)
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    });
}

八、Promise 与 async/await

async/await 是建立在 Promise 之上的语法糖:

async function fetchData() {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
}

九、常见问题解答

Q1: Promise 和回调函数有什么区别?
A: Promise 提供了更清晰的链式调用和错误处理,避免了回调地狱。

Q2: 如何取消一个 Promise?
A: 原生 Promise 无法取消,但可以使用 AbortController 或第三方库实现类似功能。

Q3: Promise 是微任务吗?
A: 是的,Promise 的回调会作为微任务执行,比 setTimeout 等宏任务优先级高。

Q4: 如何实现 Promise 重试机制?
A: 可以封装一个重试函数:

function retry(fn, times) {
  return new Promise((resolve, reject) => {
    function attempt() {
      fn().then(resolve).catch(err => {
        if (times-- > 0) attempt();
        else reject(err);
      });
    }
    attempt();
  });
}

Q5: Promise.all 和 Promise.allSettled 有什么区别?
A: all 在任一 promise 失败时立即拒绝,allSettled 会等待所有 promise 完成。

Promise 是现代 JavaScript 异步编程的核心,理解其原理和用法对于编写高质量的异步代码至关重要。

(八)、ES6 Class 类全面解析

一、Class 基本概念

ES6 引入的 class 关键字实质上是 JavaScript 基于原型的继承的语法糖,它提供了更接近传统面向对象语言的写法。

1. 基本语法
class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    console.log(`Hello, ${this.name}!`);
  }
}

const alice = new Person('Alice');
alice.sayHello(); // "Hello, Alice!"

二、Class 核心特性

1. 构造方法 (constructor)
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
2. 实例方法
class Person {
  // ...
  
  greet() {
    console.log(`Hi, I'm ${this.name}`);
  }
}
3. 静态方法 (static)
class MathUtils {
  static sum(a, b) {
    return a + b;
  }
}

MathUtils.sum(1, 2); // 3
4. 静态属性
class Config {
  static apiUrl = 'https://api.example.com';
}

console.log(Config.apiUrl);
5. 私有字段 (ES2022)
class Counter {
  #count = 0; // 私有字段
  
  increment() {
    this.#count++;
  }
  
  getCount() {
    return this.#count;
  }
}

三、Class 继承

1. extends 继承
class Student extends Person {
  constructor(name, grade) {
    super(name); // 调用父类构造函数
    this.grade = grade;
  }
  
  study() {
    console.log(`${this.name} is studying`);
  }
}
2. super 关键字

super() 调用父类构造函数
super.method() 调用父类方法

3. 方法重写
class Student extends Person {
  sayHello() {
    super.sayHello(); // 调用父类方法
    console.log("I'm a student");
  }
}
4. 继承内置类
class MyArray extends Array {
  first() {
    return this[0];
  }
  
  last() {
    return this[this.length - 1];
  }
}

四、Getter 和 Setter

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }
  
  get fahrenheit() {
    return this.celsius * 1.8 + 32;
  }
  
  set fahrenheit(value) {
    this.celsius = (value - 32) / 1.8;
  }
}

const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 86;
console.log(temp.celsius); // 30

五、Class 表达式

1. 命名类表达式
const Person = class NamedPerson {
  constructor(name) {
    this.name = name;
  }
  
  sayName() {
    console.log(this.name);
  }
};
2. 匿名类表达式
const Person = class {
  // ...
};

六、Class 与原型的关系

Class 本质上是构造函数的语法糖:

typeof Person; // "function"
Person.prototype.constructor === Person; // true

七、Class 与函数声明的重要区别

  1. 提升(hoisting):类声明不会被提升
  2. 严格模式:类声明和类表达式默认在严格模式下执行
  3. 调用方式:类必须使用 new 调用
  4. 方法枚举:类方法不可枚举

八、高级用法

1. Mixin 模式
function mixin(...mixins) {
  class Mix {
    constructor() {
      for (let mixin of mixins) {
        copyProperties(this, new mixin());
      }
    }
  }
  
  for (let mixin of mixins) {
    copyProperties(Mix, mixin);
    copyProperties(Mix.prototype, mixin.prototype);
  }
  
  return Mix;
}

class DistributedEdit extends mixin(Loggable, Serializable) {
  // ...
}
2. 抽象基类
class Abstract {
  constructor() {
    if (new.target === Abstract) {
      throw new Error('Cannot instantiate abstract class');
    }
  }
}

class Concrete extends Abstract {}
3. Symbol.iterator 实现
class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  
  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
}

const range = new Range(1, 5);
[...range]; // [1, 2, 3, 4, 5]

九、最佳实践

  1. 单一职责原则:每个类应该只有一个职责
  2. 开放封闭原则:对扩展开放,对修改封闭
  3. 优先组合而非继承:减少复杂的继承层次
  4. 合理使用私有字段:保护内部状态
  5. 文档注释:使用 JSDoc 注释类和方法

十、常见问题解答

Q1: Class 与构造函数有什么区别?
A: Class 本质上是构造函数的语法糖,但提供了更清晰的语法和更好的错误检查。

Q2: 如何实现接口?
A: JavaScript 没有接口,可以通过抽象类或 TypeScript 实现。

Q3: 如何实现多重继承?
A: JavaScript 不支持多重继承,可以通过 Mixin 模式模拟。

Q4: 私有方法和属性有哪些实现方式?
A:
• 使用 ES2022 私有字段 #field
• 使用 WeakMap 或 Symbol 模拟
• 命名约定(如 _privateMethod

Q5: 如何检测一个类是否是另一个类的子类?
A: 使用 instanceofObject.prototype.isPrototypeOf()

class Parent {}
class Child extends Parent {}

console.log(Child.prototype instanceof Parent); // true
console.log(Parent.prototype.isPrototypeOf(Child.prototype)); // true

ES6 Class 为 JavaScript 提供了更优雅的面向对象编程方式,虽然它本质上是基于原型的继承的语法糖,但这种语法更符合大多数程序员的习惯,也使代码更易于理解和维护。

(九)、模块系统

1. 导出

// math.js
export const PI = 3.1415;
export function square(x) {
  return x * x;
}

2. 导入

import { PI, square } from './math.js';
console.log(square(PI)); // 9.86902225

(十)、Symbol

const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false

const obj = {
  [sym1]: 'value'
};
console.log(obj[sym1]); // value

(十一)、迭代器和生成器

1. 迭代器

const iterable = {
  [Symbol.iterator]() {
    let step = 0;
    return {
      next() {
        step++;
        if (step <= 3) {
          return { value: step, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (const value of iterable) {
  console.log(value); // 1, 2, 3
}

2. 生成器

function* idGenerator() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

(十二)、Set和Map

1. Set

const set = new Set([1, 2, 3, 3, 4]);
console.log(set.size); // 4
console.log([...set]); // [1, 2, 3, 4]

2. Map

const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
console.log(map.get('name')); // Alice

(十三)、Proxy和Reflect

1. Proxy

const target = {};
const handler = {
  get(target, prop) {
    return prop in target ? target[prop] : 37;
  }
};

const proxy = new Proxy(target, handler);
proxy.a = 1;
console.log(proxy.a); // 1
console.log(proxy.b); // 37

2. Reflect

const obj = { a: 1 };
console.log(Reflect.get(obj, 'a')); // 1
Reflect.set(obj, 'b', 2);
console.log(obj.b); // 2

(十四)、新的数据类型

1. TypedArray

const buffer = new ArrayBuffer(16);
const int32View = new Int32Array(buffer);

2. DataView

const view = new DataView(buffer);
view.setInt32(0, 42);
console.log(view.getInt32(0)); // 42

(十五)、字符串和数组新增方法

1. 字符串方法

'hello'.startsWith('he'); // true
'hello'.endsWith('lo'); // true
'hello'.includes('ell'); // true
'abc'.repeat(3); // 'abcabcabc'

2. 数组方法

[1, 2, 3].find(x => x > 1); // 2
[1, 2, 3].findIndex(x => x > 1); // 1
[1, 2, 3].fill(4); // [4, 4, 4]
Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
Array.of(1, 2, 3); // [1, 2, 3]

(十六)、尾调用优化

function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total); // 尾调用优化
}

(十七)、二进制和八进制字面量

const binary = 0b1010; // 10
const octal = 0o12; // 10

(十八_、Object新增方法

Object.assign({}, {a: 1}, {b: 2}); // {a: 1, b: 2}
Object.is(NaN, NaN); // true
Object.setPrototypeOf(obj, prototype);
Object.getOwnPropertySymbols(obj);

总结

ES6的这些新特性极大地丰富了JavaScript的功能,使代码更加简洁、可读性更强,同时也提高了开发效率。掌握这些特性对于现代JavaScript开发至关重要。随着JavaScript的不断发展,这些特性已经成为现代Web开发的基石。

相关文章:

  • 26考研|高等代数:线性空间
  • 关于CodeJava的学习笔记——10
  • 医院信息系统与AI赋能的介绍
  • 【Easylive】获取request对象的两种方式
  • windows 下 通过虚拟化拦截对一个text.txt文件的访问 如果要打开的文件名为 text.txt 提示无权限
  • MySQL in和exists的取舍
  • 批量清空或者删除 PDF 文档中作者、创建程序、修改时间等元数据
  • 【Easylive】application.yml文件中都是什么作用
  • Day78 | 灵神 | 反转链表 两两交换链表中的节点
  • 每天学一个 Linux 命令(9):useradd/userdel
  • EDI传输中的OFTP AS2
  • 【模拟CMOS集成电路设计】电荷泵(Charge bump)设计与仿真(示例:栅极开关CP+轨到轨输入运放+基于运放CP)
  • CentOS 7 安装 Kubernetes 1.28.2 集群
  • 企业如何构建风控合规体系?
  • LLM - 推理大语言模型 DeepSeek-R1 论文简读
  • python接口自动化测试:接口数据依赖
  • 蓝桥杯备考---》贪心算法之矩阵消除游戏
  • 理解Java数据库编程之JDBC
  • 用户模块——业务校验工具AssertUtil
  • 10-SpringBoot3入门-整合TX(事务)
  • 做网站microsoft/2022最近十大的新闻热点
  • 360网站提交/搜索引擎优化网站的网址
  • 网站在线支付接口申请/微信营销软件有哪些
  • 长沙3合1网站建设价格/网店推广分为哪几种类型
  • 建立网站该怎样做/域名查询访问
  • 爱旅游网站制作/青岛网站运营