ES6 新特性全面总结

06-01 1594阅读

ES6 新特性全面总结

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

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

一、let 和 const 的基本概念

ES6引入了两种新的变量声明方式:let和const,它们与传统的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. 概念

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

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

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

五、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  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。

ES6 新特性全面总结
(图片来源网络,侵删)

Q2: const声明的对象属性可以修改吗?

A: 可以修改对象属性,但不能重新赋值整个对象。

ES6 新特性全面总结
(图片来源网络,侵删)

Q3: let和const能替代var吗?

A: 在大多数情况下可以完全替代,但要注意作用域差异。

ES6 新特性全面总结
(图片来源网络,侵删)

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) => {
const c = a + b;
return c * 2;
}
function(a, b) {
const c = a + b;
return c * 2;
}
返回对象() => ({ 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。
    • 箭头函数的特性:
      • 箭头函数不会创建自己的 this 和 arguments,而是继承自外层函数的上下文。
        3. 不能作为构造函数

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

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

        箭头函数没有super和new.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: 不能。对象方法、构造函数、需要动态this或arguments的场景仍需使用普通函数。

        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 = `
          

        ${title}

        ${content}

        `; // 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  替换为 >
                .replace(/"/g, '"')   // 将 " 替换为 "
                .replace(/'/g, ''');   // 将 ' 替换为 '
            }
          }
          return result; // 返回最终生成的 HTML 内容
        }
        // 定义一个可能包含恶意脚本的用户输入
        const userInput = 'alert("XSS")';
        // 使用 safeHtml 标签函数处理模板字符串
        const safeOutput = safeHtml`${userInput}`;
        // 输出结果
        console.log(safeOutput); // <script>alert("XSS")</script>
        
        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  
        

        五、特殊字符处理

        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  
        

        六、性能考量

        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 `
              
                

          Content

          `.trim(); }
        4. 安全注意事项:

          • 不要直接将用户输入插入HTML/URL/SQL

          • 使用专用转义库或标签模板处理危险内容

        八、浏览器兼容性

        现代浏览器均支持模板字符串特性,对于旧版浏览器需要通过Babel等工具转译:

        • ES6转ES5:将模板字符串转换为普通字符串拼接

        • 标签模板:转换为函数调用形式

        九、扩展应用

        1. 配合React等框架
        const name = 'Alice';
        const element = Hello, {name}!;
        // 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. 优先使用扩展运算符:替代 concat、apply 等传统方法

        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 
              yield i;
            }
          }
        }
        const range = new Range(1, 5);
        [...range]; // [1, 2, 3, 4, 5]
        }
        class Child extends Parent {}
        console.log(Child.prototype instanceof Parent); // true
        console.log(Parent.prototype.isPrototypeOf(Child.prototype)); // true
        
          return x * x;
        }
         PI, square } from './math.js';
        console.log(square(PI)); // 9.86902225
        
          [sym1]: 'value'
        };
        console.log(obj[sym1]); // value
        
          [Symbol.iterator]() {
            let step = 0;
            return {
              next() {
                step++;
                if (step 
                  return { value: step, done: false };
                }
                return { value: undefined, done: true };
              }
            };
          }
        };
        for (const value of iterable) {
          console.log(value); // 1, 2, 3
        }
        
          let id = 1;
          while (true) {
            yield id++;
          }
        }
        const gen = idGenerator();
        console.log(gen.next().value); // 1
        console.log(gen.next().value); // 2
        };
        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
         a: 1 };
        console.log(Reflect.get(obj, 'a')); // 1
        Reflect.set(obj, 'b', 2);
        console.log(obj.b); // 2
        
        h3(十四)、新的数据类型/h3 h41. TypedArray/h4 pre class="brush:python;toolbar:false"const buffer = new ArrayBuffer(16); const int32View = new Int32Array(buffer); /pre h42. DataView/h4 pre class="brush:python;toolbar:false"const view = new DataView(buffer); view.setInt32(0, 42); console.log(view.getInt32(0)); // 42 /pre h3(十五)、字符串和数组新增方法/h3 h41. 字符串方法/h4 pre class="brush:python;toolbar:false"'hello'.startsWith('he'); // true 'hello'.endsWith('lo'); // true 'hello'.includes('ell'); // true 'abc'.repeat(3); // 'abcabcabc' /pre h42. 数组方法/h4 pre class="brush:python;toolbar:false"[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开发的基石。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码