Day01_ES6+ 专业指南:从基础到实战的现代JavaScript开发(上)

本文系统讲解 Day01 核心内容:let/const、块级作用域、解构赋值、模板字符串、字符串与数值新特性。理论结合 MDNECMA-262 规范,并附 完整可运行 HTML 示例 与知识点归纳,适合作为现代 JavaScript 入门与复习手册。

目录

  • [1. ES6+ 概述与发展历程](#1. ES6+ 概述与发展历程)
    • [1.5 Day01 知识脉络与学习路线](#1.5 Day01 知识脉络与学习路线)
  • [2. let 和 const 关键字:块级作用域的革命](#2. let 和 const 关键字:块级作用域的革命)
    • [2.0 ES5 严格模式:ES6 的前置基础](#2.0 ES5 严格模式:ES6 的前置基础)
    • [2.1 let 关键字详解](#2.1 let 关键字详解)
    • [2.2 const 关键字详解](#2.2 const 关键字详解)
    • [2.3 块级作用域深度解析](#2.3 块级作用域深度解析)
    • [2.2.5 const + Object.freeze():打造真正的不可变对象](#2.2.5 const + Object.freeze():打造真正的不可变对象)
    • [2.4 完整可运行示例:let、const 与块级作用域](#2.4 完整可运行示例:let、const 与块级作用域)
  • [3. 解构赋值:优雅的数据提取方式](#3. 解构赋值:优雅的数据提取方式)
    • [3.1 数组解构赋值](#3.1 数组解构赋值)
    • [3.2 对象解构赋值](#3.2 对象解构赋值)
    • [3.3 完整可运行示例:数组与对象解构](#3.3 完整可运行示例:数组与对象解构)
  • [4. 字符串新增特性:更强大的文本处理](#4. 字符串新增特性:更强大的文本处理)
    • [4.1 模板字符串](#4.1 模板字符串)
    • [4.2 字符串实例新增方法](#4.2 字符串实例新增方法)
    • [4.3 完整可运行示例:模板字符串与字符串方法](#4.3 完整可运行示例:模板字符串与字符串方法)
  • [5. 数值新增特性:更精确的数字计算](#5. 数值新增特性:更精确的数字计算)
    • [5.1 新增的二进制和八进制表示方式](#5.1 新增的二进制和八进制表示方式)
    • [5.2 Number 构造函数新增方法和属性](#5.2 Number 构造函数新增方法和属性)
    • [5.3 Math 新增方法](#5.3 Math 新增方法)
    • [5.4 指数运算符 **](#5.4 指数运算符 **)
    • [5.5 新增原始数据类型 BigInt](#5.5 新增原始数据类型 BigInt)
    • [5.6 数字分隔符](#5.6 数字分隔符)
    • [5.7 可选链 ?. 与 空值合并 ??](#5.7 可选链 ?. 与 空值合并 ??)
    • [5.7.3 逻辑赋值运算符 ??= / ||= / &&=(ES2021)](#5.7.3 逻辑赋值运算符 ??= / ||= / &&=(ES2021))
  • [6. 实战应用场景与最佳实践](#6. 实战应用场景与最佳实践)
  • [7. 知识点总结与对比](#7. 知识点总结与对比)
  • [8. Day01 知识点速查与归纳](#8. Day01 知识点速查与归纳)

1. ES6+ 概述与发展历程

1.1 什么是ES6+

名词解释:

  • ES6(ECMAScript 2015):JavaScript语言的第六个版本,是一次重大更新
  • ES6+:指ES6及其后续版本(ES2016、ES2017、ES2018、ES2019、ES2020、ES2021等)的总称
  • TC39:ECMA国际的TC39委员会负责JavaScript标准的制定和维护

1.2 ES版本发布时间线

#mermaid-svg-UbsrMDfK0C9hoewy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UbsrMDfK0C9hoewy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UbsrMDfK0C9hoewy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UbsrMDfK0C9hoewy .error-icon{fill:#552222;}#mermaid-svg-UbsrMDfK0C9hoewy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UbsrMDfK0C9hoewy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UbsrMDfK0C9hoewy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UbsrMDfK0C9hoewy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UbsrMDfK0C9hoewy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UbsrMDfK0C9hoewy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UbsrMDfK0C9hoewy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UbsrMDfK0C9hoewy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UbsrMDfK0C9hoewy .marker.cross{stroke:#333333;}#mermaid-svg-UbsrMDfK0C9hoewy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UbsrMDfK0C9hoewy p{margin:0;}#mermaid-svg-UbsrMDfK0C9hoewy .edge{stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .section--1 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section--1 path,#mermaid-svg-UbsrMDfK0C9hoewy .section--1 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section--1 text{fill:#ffffff;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth--1{stroke-width:17;}#mermaid-svg-UbsrMDfK0C9hoewy .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:#ffffff;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-0 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-0 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-0 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-0 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-0{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-0{stroke-width:14;}#mermaid-svg-UbsrMDfK0C9hoewy .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-1 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-1 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-1 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-1 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-1{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-1{stroke-width:11;}#mermaid-svg-UbsrMDfK0C9hoewy .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-2 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-2 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-2 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-2 text{fill:#ffffff;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-2{stroke-width:8;}#mermaid-svg-UbsrMDfK0C9hoewy .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:#ffffff;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-3 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-3 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-3 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-3 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-3{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-3{stroke-width:5;}#mermaid-svg-UbsrMDfK0C9hoewy .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-4 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-4 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-4 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-4 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-4{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-4{stroke-width:2;}#mermaid-svg-UbsrMDfK0C9hoewy .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-5 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-5 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-5 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-5 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-5{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-5{stroke-width:-1;}#mermaid-svg-UbsrMDfK0C9hoewy .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-6 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-6 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-6 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-6 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-6{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-6{stroke-width:-4;}#mermaid-svg-UbsrMDfK0C9hoewy .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-7 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-7 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-7 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-7 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-7{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-7{stroke-width:-7;}#mermaid-svg-UbsrMDfK0C9hoewy .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-8 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-8 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-8 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-8 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-8{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-8{stroke-width:-10;}#mermaid-svg-UbsrMDfK0C9hoewy .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-9 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-9 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-9 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-9 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-9{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-9{stroke-width:-13;}#mermaid-svg-UbsrMDfK0C9hoewy .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-10 rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-10 path,#mermaid-svg-UbsrMDfK0C9hoewy .section-10 circle,#mermaid-svg-UbsrMDfK0C9hoewy .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-10 text{fill:black;}#mermaid-svg-UbsrMDfK0C9hoewy .node-icon-10{font-size:40px;color:black;}#mermaid-svg-UbsrMDfK0C9hoewy .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .edge-depth-10{stroke-width:-16;}#mermaid-svg-UbsrMDfK0C9hoewy .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-UbsrMDfK0C9hoewy .lineWrapper line{stroke:black;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled,#mermaid-svg-UbsrMDfK0C9hoewy .disabled circle,#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:lightgray;}#mermaid-svg-UbsrMDfK0C9hoewy .disabled text{fill:#efefef;}#mermaid-svg-UbsrMDfK0C9hoewy .section-root rect,#mermaid-svg-UbsrMDfK0C9hoewy .section-root path,#mermaid-svg-UbsrMDfK0C9hoewy .section-root circle{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-UbsrMDfK0C9hoewy .section-root text{fill:#ffffff;}#mermaid-svg-UbsrMDfK0C9hoewy .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-UbsrMDfK0C9hoewy .edge{fill:none;}#mermaid-svg-UbsrMDfK0C9hoewy .eventWrapper{filter:brightness(120%);}#mermaid-svg-UbsrMDfK0C9hoewy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 早期版本 ES5 (2009) 严格模式 JSON支持 数组方法增强 现代JavaScript ES6/ES2015 let/const 箭头函数 类/模块 Promise ES2016 指数运算符 Array.prototype.includes ES2017 async/await Object.values 字符串填充 ES2018 对象展开 异步迭代 Promise.finally ES2019 Array.flat Object.fromEntries 字符串修剪 ES2020 BigInt 可选链 空值合并 ES2021 数字分隔符 String.replaceAll Promise.any JavaScript ES版本发布时间线

【代码注释】时间线图按年份串联各 ES 版本标志性语法(let/const、async、可选链等)。学习 Day01 时不必死记年份,重点建立「ES6 是分水岭、后续每年小步增补」的全景认知,便于查 MDN 时知道特性属于哪一代。

1.3 为什么要学习ES6+

实际应用场景:

  • 就业市场需求:现代前端开发岗位的必备技能
  • 框架基础:React、Vue、Angular等框架都基于ES6+特性
  • 代码质量:提供更优雅、更安全的编程方式
  • 性能提升:新增API和方法提升代码执行效率

业界采用情况:

  • GitHub:大部分现代JavaScript项目都使用ES6+语法
  • npm包:主流开源库都已迁移到ES6+
  • 浏览器支持:现代浏览器完全支持ES6+特性

1.5 Day01 知识脉络与学习路线

名词解释:

  • ECMA-262 :定义 JavaScript 语法的国际标准,每年由 TC39 推进新版本
  • Babel:将 ES6+ 源码转译为旧环境可执行代码的编译器
  • Polyfill:在旧运行时上补齐缺失 API 的垫片脚本

官方与权威资料(建议对照阅读):

#mermaid-svg-neQAWXEhnJ86UEVn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-neQAWXEhnJ86UEVn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-neQAWXEhnJ86UEVn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-neQAWXEhnJ86UEVn .error-icon{fill:#552222;}#mermaid-svg-neQAWXEhnJ86UEVn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-neQAWXEhnJ86UEVn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-neQAWXEhnJ86UEVn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-neQAWXEhnJ86UEVn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-neQAWXEhnJ86UEVn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-neQAWXEhnJ86UEVn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-neQAWXEhnJ86UEVn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-neQAWXEhnJ86UEVn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-neQAWXEhnJ86UEVn .marker.cross{stroke:#333333;}#mermaid-svg-neQAWXEhnJ86UEVn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-neQAWXEhnJ86UEVn p{margin:0;}#mermaid-svg-neQAWXEhnJ86UEVn .edge{stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .section--1 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section--1 path,#mermaid-svg-neQAWXEhnJ86UEVn .section--1 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section--1 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section--1 text{fill:#ffffff;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth--1{stroke-width:17;}#mermaid-svg-neQAWXEhnJ86UEVn .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-0 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-0 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-0 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-0 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-0 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-0{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-0{stroke-width:14;}#mermaid-svg-neQAWXEhnJ86UEVn .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-1 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-1 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-1 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-1 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-1 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-1{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-1{stroke-width:11;}#mermaid-svg-neQAWXEhnJ86UEVn .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-2 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-2 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-2 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-2 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-2 text{fill:#ffffff;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-2{stroke-width:8;}#mermaid-svg-neQAWXEhnJ86UEVn .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-3 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-3 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-3 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-3 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-3 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-3{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-3{stroke-width:5;}#mermaid-svg-neQAWXEhnJ86UEVn .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-4 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-4 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-4 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-4 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-4 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-4{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-4{stroke-width:2;}#mermaid-svg-neQAWXEhnJ86UEVn .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-5 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-5 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-5 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-5 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-5 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-5{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-5{stroke-width:-1;}#mermaid-svg-neQAWXEhnJ86UEVn .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-6 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-6 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-6 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-6 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-6 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-6{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-6{stroke-width:-4;}#mermaid-svg-neQAWXEhnJ86UEVn .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-7 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-7 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-7 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-7 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-7 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-7{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-7{stroke-width:-7;}#mermaid-svg-neQAWXEhnJ86UEVn .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-8 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-8 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-8 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-8 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-8 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-8{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-8{stroke-width:-10;}#mermaid-svg-neQAWXEhnJ86UEVn .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-9 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-9 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-9 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-9 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-9 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-9{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-9{stroke-width:-13;}#mermaid-svg-neQAWXEhnJ86UEVn .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-10 rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-10 path,#mermaid-svg-neQAWXEhnJ86UEVn .section-10 circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-10 polygon,#mermaid-svg-neQAWXEhnJ86UEVn .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-10 text{fill:black;}#mermaid-svg-neQAWXEhnJ86UEVn .node-icon-10{font-size:40px;color:black;}#mermaid-svg-neQAWXEhnJ86UEVn .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .edge-depth-10{stroke-width:-16;}#mermaid-svg-neQAWXEhnJ86UEVn .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled,#mermaid-svg-neQAWXEhnJ86UEVn .disabled circle,#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:lightgray;}#mermaid-svg-neQAWXEhnJ86UEVn .disabled text{fill:#efefef;}#mermaid-svg-neQAWXEhnJ86UEVn .section-root rect,#mermaid-svg-neQAWXEhnJ86UEVn .section-root path,#mermaid-svg-neQAWXEhnJ86UEVn .section-root circle,#mermaid-svg-neQAWXEhnJ86UEVn .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-neQAWXEhnJ86UEVn .section-root text{fill:#ffffff;}#mermaid-svg-neQAWXEhnJ86UEVn .section-root span{color:#ffffff;}#mermaid-svg-neQAWXEhnJ86UEVn .section-2 span{color:#ffffff;}#mermaid-svg-neQAWXEhnJ86UEVn .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-neQAWXEhnJ86UEVn .edge{fill:none;}#mermaid-svg-neQAWXEhnJ86UEVn .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-neQAWXEhnJ86UEVn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Day01 ES6+
变量与作用域
ES5 严格模式
let 四大特性
TDZ 暂时性死区机制
const 常量语义
块级作用域
解构赋值
数组按索引匹配
对象按属性名匹配
默认值与嵌套
伪数组解构
for...of + 解构组合
字符串
模板字符串
标签模板/String.raw
includes/startsWith/endsWith
repeat/pad/trim/replaceAll
String.at 负索引
数值
0b 0o 字面量
Number/Math 新方法
Number.EPSILON 浮点精度
指数运算符
BigInt
数字分隔符
ES2020/ES2021 运算符
可选链 ?.
空值合并 ??
逻辑赋值 ??= ||= &&=
不可变数据
const 绑定不变
Object.freeze 浅冻结
deepFreeze 深度不可变
标签模板进阶
styled-components CSS-in-JS
graphql-tag AST 解析
lit-html 高效 DOM
sql 参数化防注入

【代码注释】思维导图将 Day01 拆为变量作用域 → 解构 → 字符串 → 数值四条主线;TDZ、解构默认值、Number.isNaN?? 等易混点已标在对应分支。建议严格按「严格模式 → let/const → 解构 → 字符串 → 数值」顺序推进,避免跳章导致闭包与 TDZ 概念断层。

Day01 学习顺序建议: 严格模式 → let/const/块级作用域 → 解构赋值 → 字符串新特性 → 数值新特性 → 综合实战。


2. let 和 const 关键字:块级作用域的革命

2.0 ES5 严格模式:ES6 的前置基础

在掌握 letconst 之前,需要理解 严格模式(Strict Mode)------ES5 引入、ES6 在此基础上继续收紧语法的一套运行约束。

名词解析:

  • 严格模式 :在脚本或函数顶部写入 'use strict'; 后,引擎对未声明变量赋值、八进制字面量、删除不可删属性等行为会 直接抛错,而不是静默失败
  • 非严格模式(Sloppy Mode):历史默认行为,许多错误只产生副作用而不中断执行

与 ES6 的关系: 严格模式是 ES6 模块、类、let/const 等特性的语义基础;现代工程普遍默认开启严格模式(打包工具、<script type="module"> 会自动进入严格模式)。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>ES5 严格模式演示</title>
</head>
<body>
  <script>
    // 【代码注释】'use strict' 必须写在脚本或函数体最顶部(注释之后、其它语句之前)
    // 作用:禁止隐式全局变量、禁止删除不可删属性、禁止八进制字面量 012 等
    'use strict';

    // 【代码注释】严格模式禁止无前缀八进制 012(ES5 遗留写法,语义模糊)
    // var num = 012; // SyntaxError: Octal literals are not allowed in strict mode

    // 【代码注释】应使用带前缀的字面量:0x 十六进制、0b 二进制、0o 八进制
    var num = 0x12;   // 0x12 = 1×16 + 2 = 18(十进制)
    console.log(num); // 18

    function func() {
      // 【代码注释】非严格模式:未声明的赋值会隐式挂到全局对象(浏览器为 window)
      // 严格模式:对未声明标识符赋值 → ReferenceError,在开发期暴露拼写错误
      username = 100;
    }
    // func(); // 取消注释运行,观察控制台 ReferenceError
  </script>
</body>
</html>

【代码注释】严格模式是 ES6 块级作用域与模块的语义基础:<script type="module">、打包后的 ESM、以及函数/脚本顶部的 'use strict' 都会进入严格模式。本示例演示两类典型报错------旧式八进制 012(SyntaxError)与隐式全局赋值 username = 100(ReferenceError);0x12 为合法十六进制,值为 18。现代工程应默认严格模式,避免 var 泄漏与静默错误叠加。

经典应用场景:

  • 遗留项目逐步加固:在单个模块顶部加 'use strict'
  • 与 ES6 块级作用域配合:避免 var 泄漏与静默错误叠加
  • 团队规范:Lint 规则(如 eslint:recommended)等价于在编码阶段强制执行严格语义

2.1 let 关键字详解

2.1.1 什么是let关键字

名词解析:

  • let:ES6新增的变量声明关键字,用于声明块级作用域的局部变量
  • 变量提升(Hoisting):var声明的变量会被提升到函数或全局作用域的顶部
  • 暂时性死区(Temporal Dead Zone, TDZ):从块开始到let声明之间的区域,变量无法访问
2.1.2 let与var的核心区别

#mermaid-svg-4seNxLMUREJ6BOaE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4seNxLMUREJ6BOaE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4seNxLMUREJ6BOaE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4seNxLMUREJ6BOaE .error-icon{fill:#552222;}#mermaid-svg-4seNxLMUREJ6BOaE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4seNxLMUREJ6BOaE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4seNxLMUREJ6BOaE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4seNxLMUREJ6BOaE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4seNxLMUREJ6BOaE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4seNxLMUREJ6BOaE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4seNxLMUREJ6BOaE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4seNxLMUREJ6BOaE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4seNxLMUREJ6BOaE .marker.cross{stroke:#333333;}#mermaid-svg-4seNxLMUREJ6BOaE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4seNxLMUREJ6BOaE p{margin:0;}#mermaid-svg-4seNxLMUREJ6BOaE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4seNxLMUREJ6BOaE .cluster-label text{fill:#333;}#mermaid-svg-4seNxLMUREJ6BOaE .cluster-label span{color:#333;}#mermaid-svg-4seNxLMUREJ6BOaE .cluster-label span p{background-color:transparent;}#mermaid-svg-4seNxLMUREJ6BOaE .label text,#mermaid-svg-4seNxLMUREJ6BOaE span{fill:#333;color:#333;}#mermaid-svg-4seNxLMUREJ6BOaE .node rect,#mermaid-svg-4seNxLMUREJ6BOaE .node circle,#mermaid-svg-4seNxLMUREJ6BOaE .node ellipse,#mermaid-svg-4seNxLMUREJ6BOaE .node polygon,#mermaid-svg-4seNxLMUREJ6BOaE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4seNxLMUREJ6BOaE .rough-node .label text,#mermaid-svg-4seNxLMUREJ6BOaE .node .label text,#mermaid-svg-4seNxLMUREJ6BOaE .image-shape .label,#mermaid-svg-4seNxLMUREJ6BOaE .icon-shape .label{text-anchor:middle;}#mermaid-svg-4seNxLMUREJ6BOaE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4seNxLMUREJ6BOaE .rough-node .label,#mermaid-svg-4seNxLMUREJ6BOaE .node .label,#mermaid-svg-4seNxLMUREJ6BOaE .image-shape .label,#mermaid-svg-4seNxLMUREJ6BOaE .icon-shape .label{text-align:center;}#mermaid-svg-4seNxLMUREJ6BOaE .node.clickable{cursor:pointer;}#mermaid-svg-4seNxLMUREJ6BOaE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4seNxLMUREJ6BOaE .arrowheadPath{fill:#333333;}#mermaid-svg-4seNxLMUREJ6BOaE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4seNxLMUREJ6BOaE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4seNxLMUREJ6BOaE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4seNxLMUREJ6BOaE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4seNxLMUREJ6BOaE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4seNxLMUREJ6BOaE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4seNxLMUREJ6BOaE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4seNxLMUREJ6BOaE .cluster text{fill:#333;}#mermaid-svg-4seNxLMUREJ6BOaE .cluster span{color:#333;}#mermaid-svg-4seNxLMUREJ6BOaE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4seNxLMUREJ6BOaE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4seNxLMUREJ6BOaE rect.text{fill:none;stroke-width:0;}#mermaid-svg-4seNxLMUREJ6BOaE .icon-shape,#mermaid-svg-4seNxLMUREJ6BOaE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4seNxLMUREJ6BOaE .icon-shape p,#mermaid-svg-4seNxLMUREJ6BOaE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4seNxLMUREJ6BOaE .icon-shape .label rect,#mermaid-svg-4seNxLMUREJ6BOaE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4seNxLMUREJ6BOaE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4seNxLMUREJ6BOaE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4seNxLMUREJ6BOaE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 变量声明方式
var关键字
let关键字
函数作用域
变量提升
可重复声明
全局变量是window属性
块级作用域
暂时性死区
不可重复声明
全局变量不是window属性

【代码注释】流程图从作用域、提升、重复声明、全局对象属性四个维度对比 var 与 let。面试与 Code Review 常考:var 的 typeof 在 TDZ 前为 undefined,let 在 TDZ 内连 typeof 也抛 ReferenceError;全局 let 不挂 window,有利于减少第三方脚本命名冲突。

2.1.3 let关键字的四大特性

特性一:不能重复声明

javascript 复制代码
// 【代码注释】**重复声明**:同一词法环境内第二次 `let userName` 在解析阶段即 SyntaxError,早于运行。内层 `if` 块可再声明同名变量(新词法环境)。对比 `var age` 可重复声明且后者覆盖前者------大型项目里这是隐蔽 bug 来源。ESLint `no-redeclare` 对 let/const 会提前拦截。
let userName = '张三';
// let userName = '李四'; // SyntaxError: Identifier 'userName' has already been declared

// 但可以在不同作用域中声明
if (true) {
    let userName = '王五'; // 合法,不同作用域
    console.log(userName); // 输出: 王五
}

// var关键字可以重复声明(这是其设计缺陷)
var age = 25;
var age = 30; // 合法但容易导致bug

【代码注释】重复声明 :同一词法环境内第二次 let userName 在解析阶段即 SyntaxError,早于运行。内层 if 块可再声明同名变量(新词法环境)。对比 var age 可重复声明且后者覆盖前者------大型项目里这是隐蔽 bug 来源。ESLint no-redeclare 对 let/const 会提前拦截。

实际应用:防止变量污染

javascript 复制代码
// 【代码注释】实战:for 循环体内用 let 限定 item 作用域;重复 let item 会在解析期 SyntaxError
function processUserData(userData) {
    let result = []; // 函数级 let,整个 processUserData 内可见
    
    for (let i = 0; i < userData.length; i++) {
        // 【代码注释】每次迭代进入循环体块;let item 仅在本轮循环块内有效
        let item = userData[i];
        // let item = transform(item); // SyntaxError:同一循环体内不可重复 let item
        result.push(item);
    }
    // console.log(item); // ReferenceError:item 未逃出循环块
    
    return result;
}

【代码注释】大型项目中,for 循环体是变量污染高发区:用 let item 将处理单元限制在单次迭代块内;若误写 let item = ... 两次,编译器立即报错,避免「后一次声明覆盖前一次」导致的静默逻辑错误。配合 let i 作为循环索引,迭代结束后 i 同样不可在循环外访问。

特性二:不存在变量提升

javascript 复制代码
// 【代码注释】**提升语义**:`notDeclared` 用 `typeof` 得 `"undefined"`(真正未绑定);`letVar` 在声明前访问抛 ReferenceError(TDZ)。`varVar` 提升后值为 `undefined`,易让人误以为已初始化。工程习惯:所有 `let` 写在块顶部,配合 `eslint no-use-before-define` 强制先声明后使用。
console.log(typeof notDeclared); // "undefined"
// console.log(letVar); // ReferenceError: Cannot access 'letVar' before initialization
let letVar = '我是let变量';

console.log(typeof varVar); // "undefined"(已提升但值为undefined)
var varVar = '我是var变量';

// 实际应用场景:强制开发者先声明后使用,减少bug
function calculateTotal(price, quantity) {
    // let total = price * quantity; // 必须先声明
    let total;
    
    if (price > 0 && quantity > 0) {
        total = price * quantity;
    }
    
    return total || 0;
}

【代码注释】提升语义notDeclaredtypeof"undefined"(真正未绑定);letVar 在声明前访问抛 ReferenceError(TDZ)。varVar 提升后值为 undefined,易让人误以为已初始化。工程习惯:所有 let 写在块顶部,配合 eslint no-use-before-define 强制先声明后使用。

2.1.2.1 暂时性死区(TDZ)底层机制深度解析

理论原理:

let/const 并非真的"不提升"------JavaScript 引擎同样会在编译阶段把它们登记到当前词法环境(LexicalEnvironment)中,但与 var 不同,它们的初始状态是 <uninitialized>(未初始化) ,而不是 undefined。从块作用域顶端到声明语句之间的区间,引擎强制要求任何读写操作都抛出 ReferenceError,这段区间就是 暂时性死区(Temporal Dead Zone,TDZ)

javascript 复制代码
// 【代码注释】TDZ 演示:编译期 x 已入词法环境,状态为 <uninitialized>;块顶到 let 之间访问(含 typeof)一律 ReferenceError
// JS 引擎处理以下代码时,块内编译阶段已登记 x,但标记为 <uninitialized>
{
    // ← TDZ 开始:x 已存在于词法环境,但不可访问
    console.log(typeof x); // ReferenceError(注意:即使用 typeof 也会报错)
    let x = 10;            // ← TDZ 结束:x 完成初始化,值为 10
    console.log(x);        // 10
}

// 与 var 的对比:var 进入词法环境时直接标记为 undefined
console.log(typeof varVar); // "undefined"(不报错,已提升并初始化为 undefined)
var varVar = 'hello';

// 【注意】typeof 对 let/const 的 TDZ 变量不再安全
// ES5 时代 typeof 被视为永远不报错的安全检测,ES6 后此假设被打破

【代码注释】TDZ 并非"不提升",而是"提升后禁止访问"。var 提升时初始化为 undefinedlet/const 提升后保持 <uninitialized>,访问即报错。这是 ES6 从语言设计层面强制先声明后使用的核心机制。

javascript 复制代码
// 【代码注释】TDZ 陷阱:形参默认值从左到右求值;x 默认值里引用 y 时,y 尚未完成绑定,处于 TDZ → ReferenceError
// 参数从左到右按顺序求值,后面的默认值不能引用前面还未求值完的参数
function badDefault(x = y, y = 1) { // ReferenceError:y 在 x 求值时处于 TDZ
    return [x, y];
}

function goodDefault(x = 1, y = x) { // 合法:x 已求值完毕
    return [x, y];
}

console.log(goodDefault()); // [1, 1]
console.log(goodDefault(2)); // [2, 2]

// 【代码注释】派生类 constructor 中,super() 调用前 this 未绑定,处于 TDZ;必须先 super() 再访问 this(与实例字段初始化顺序相关)
class Base { constructor() { this.val = 1; } }
class Child extends Base {
    constructor() {
        // console.log(this); // ReferenceError:super() 之前 this 不可访问
        super();             // super() 完成后 TDZ 解除
        console.log(this.val); // 1
    }
}

【代码注释】TDZ 不只影响变量声明,还影响函数默认参数(从左到右求值)和类继承中 super() 前的 this。理解 TDZ 是排查"为什么 ReferenceError"的关键。

特性三:块级作用域

javascript 复制代码
// 【代码注释】**块级作用域**:`block` 仅在 `if` 的 `{}` 内可见,块外 ReferenceError。`for (var i)` + `setTimeout` 全部打印 5,因共享同一 `i`;`for (let j)` 每次迭代新建绑定,异步回调分别捕获 0~4。这是修复「循环绑定事件/定时器」闭包问题的标准写法,优于 IIFE 包一层。
function demoScope() {
    let global = '全局变量';
    
    if (true) {
        let block = '块级变量';
        console.log(global); // 可以访问外层变量
        console.log(block);  // 输出: 块级变量
    }
    
    console.log(global); // 输出: 全局变量
    // console.log(block); // ReferenceError: block is not defined
}

// 经典应用:循环中的闭包问题
// 使用var的旧方式(问题版本)
for (var i = 0; i < 5; i++) {
    setTimeout(() => console.log('var版本:', i), 100); // 都输出5
}

// 使用let的新方式(正确版本)
for (let j = 0; j < 5; j++) {
    setTimeout(() => console.log('let版本:', j), 100); // 输出0,1,2,3,4
}

【代码注释】块级作用域block 仅在 if{} 内可见,块外 ReferenceError。for (var i) + setTimeout 全部打印 5,因共享同一 ifor (let j) 每次迭代新建绑定,异步回调分别捕获 0~4。这是修复「循环绑定事件/定时器」闭包问题的标准写法,优于 IIFE 包一层。

特性四:不属于全局对象属性

javascript 复制代码
// 【代码注释】脚本顶层 `let globalLet` 属于**全局词法环境**,不是 `window` 的属性(`window.globalLet === undefined`),而 `var globalVar` 会挂到 `window`。好处:减少与 CDN 库、浏览器插件的全局命名冲突;在 Node 中同理不污染 `global` 同名属性。配置对象仍建议放模块作用域或命名空间,而非依赖「挂不挂 window」。
let globalLet = 'let声明的全局变量';
var globalVar = 'var声明的全局变量';

console.log(window.globalVar); // 输出: var声明的全局变量
console.log(window.globalLet); // 输出: undefined

// 实际应用:避免污染全局命名空间
let appConfig = {
    apiBaseUrl: 'https://api.example.com',
    version: '1.0.0'
};

// 在浏览器环境中,不会污染window对象
// console.log(window.appConfig); // undefined

【代码注释】脚本顶层 let globalLet 属于全局词法环境 ,不是 window 的属性(window.globalLet === undefined),而 var globalVar 会挂到 window。好处:减少与 CDN 库、浏览器插件的全局命名冲突;在 Node 中同理不污染 global 同名属性。配置对象仍建议放模块作用域或命名空间,而非依赖「挂不挂 window」。

2.1.4 let的实际应用场景

场景一:DOM操作中的事件处理

javascript 复制代码
// 【代码注释】`forEach` 里 `let currentIndex = index` 每次迭代产生新绑定,点击回调闭包捕获的是**当次**索引。若用 `var`,所有按钮共享最终 index。现代写法可直接 `for (let index = 0; ...)` 或 `buttons.forEach((btn, index) => { btn.addEventListener(...) })`,原理相同:块级绑定 + 闭包。
document.querySelectorAll('.button').forEach((button, index) => {
    let currentIndex = index; // 每次循环创建新的绑定
    
    button.addEventListener('click', function() {
        console.log(`点击了第 ${currentIndex + 1} 个按钮`);
        // 如果使用var,所有按钮都会显示最后一个索引
    });
});

【代码注释】forEachlet currentIndex = index 每次迭代产生新绑定,点击回调闭包捕获的是当次 索引。若用 var,所有按钮共享最终 index。现代写法可直接 for (let index = 0; ...)buttons.forEach((btn, index) => { btn.addEventListener(...) }),原理相同:块级绑定 + 闭包。

场景二:配置管理

javascript 复制代码
// 【代码注释】构造函数内 `let config` 被闭包中的 `getConfig`/`setConfig` 引用,外部无法直接改对象,只能通过方法合并更新(浅拷贝 `{...config, ...newConfig}`)。这是**模块私有状态**的简易模式;生产环境常改用 class 私有字段 `#config` 或闭包返回 API 对象。
class ConfigManager {
    constructor() {
        let config = {
            theme: 'light',
            language: 'zh-CN'
        };
        
        this.getConfig = () => ({ ...config });
        this.setConfig = (newConfig) => {
            config = { ...config, ...newConfig };
        };
    }
}

const manager = new ConfigManager();
console.log(manager.getConfig()); // { theme: 'light', language: 'zh-CN' }
manager.setConfig({ theme: 'dark' });

【代码注释】构造函数内 let config 被闭包中的 getConfig/setConfig 引用,外部无法直接改对象,只能通过方法合并更新(浅拷贝 {...config, ...newConfig})。这是模块私有状态 的简易模式;生产环境常改用 class 私有字段 #config 或闭包返回 API 对象。

2.2 const 关键字详解

2.2.1 什么是const关键字

名词解析:

  • const:声明一个只读常量,一旦声明,常量的值就不能改变
  • 常量:值不能被重新赋值的变量
  • 引用类型:对象、数组等类型,const声明的是引用地址不可变,内容可变
2.2.2 const与let的区别

#mermaid-svg-XjOt3FRMFJ4AyGaX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XjOt3FRMFJ4AyGaX .error-icon{fill:#552222;}#mermaid-svg-XjOt3FRMFJ4AyGaX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XjOt3FRMFJ4AyGaX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .marker.cross{stroke:#333333;}#mermaid-svg-XjOt3FRMFJ4AyGaX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XjOt3FRMFJ4AyGaX p{margin:0;}#mermaid-svg-XjOt3FRMFJ4AyGaX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .cluster-label text{fill:#333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .cluster-label span{color:#333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .cluster-label span p{background-color:transparent;}#mermaid-svg-XjOt3FRMFJ4AyGaX .label text,#mermaid-svg-XjOt3FRMFJ4AyGaX span{fill:#333;color:#333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .node rect,#mermaid-svg-XjOt3FRMFJ4AyGaX .node circle,#mermaid-svg-XjOt3FRMFJ4AyGaX .node ellipse,#mermaid-svg-XjOt3FRMFJ4AyGaX .node polygon,#mermaid-svg-XjOt3FRMFJ4AyGaX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XjOt3FRMFJ4AyGaX .rough-node .label text,#mermaid-svg-XjOt3FRMFJ4AyGaX .node .label text,#mermaid-svg-XjOt3FRMFJ4AyGaX .image-shape .label,#mermaid-svg-XjOt3FRMFJ4AyGaX .icon-shape .label{text-anchor:middle;}#mermaid-svg-XjOt3FRMFJ4AyGaX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XjOt3FRMFJ4AyGaX .rough-node .label,#mermaid-svg-XjOt3FRMFJ4AyGaX .node .label,#mermaid-svg-XjOt3FRMFJ4AyGaX .image-shape .label,#mermaid-svg-XjOt3FRMFJ4AyGaX .icon-shape .label{text-align:center;}#mermaid-svg-XjOt3FRMFJ4AyGaX .node.clickable{cursor:pointer;}#mermaid-svg-XjOt3FRMFJ4AyGaX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .arrowheadPath{fill:#333333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XjOt3FRMFJ4AyGaX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XjOt3FRMFJ4AyGaX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XjOt3FRMFJ4AyGaX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XjOt3FRMFJ4AyGaX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XjOt3FRMFJ4AyGaX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XjOt3FRMFJ4AyGaX .cluster text{fill:#333;}#mermaid-svg-XjOt3FRMFJ4AyGaX .cluster span{color:#333;}#mermaid-svg-XjOt3FRMFJ4AyGaX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XjOt3FRMFJ4AyGaX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XjOt3FRMFJ4AyGaX rect.text{fill:none;stroke-width:0;}#mermaid-svg-XjOt3FRMFJ4AyGaX .icon-shape,#mermaid-svg-XjOt3FRMFJ4AyGaX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XjOt3FRMFJ4AyGaX .icon-shape p,#mermaid-svg-XjOt3FRMFJ4AyGaX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XjOt3FRMFJ4AyGaX .icon-shape .label rect,#mermaid-svg-XjOt3FRMFJ4AyGaX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XjOt3FRMFJ4AyGaX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XjOt3FRMFJ4AyGaX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XjOt3FRMFJ4AyGaX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 声明方式
let
const
可重新赋值
声明时可不初始化
不可重新赋值
声明时必须初始化
适用场景: 变化的数据
适用场景: 固定配置/引用

【代码注释】let 允许重新赋值、声明时可暂不初始化(但 TDZ 仍适用);const 必须初始化且不能改绑定,对象/数组内容仍可变。二者都是块级作用域、不可重复声明、不进 TDZ 的「安全子集」。选型:循环计数、临时累加用 let;API 地址、枚举、模块导入默认 const。

2.2.3 const的使用特性

特性一:声明必须初始化

javascript 复制代码
// 【代码注释】`const MIN_SIZE;` 缺初始化会 SyntaxError------引擎要求声明时即确定绑定。习惯用 `const CONFIG = { ... }` 集中配置;若需延迟赋值,先用 let 再在逻辑分支里赋值一次,或拆成函数返回配置对象。
const MAX_SIZE = 100; // 合法
// const MIN_SIZE; // SyntaxError: Missing initializer in const declaration

// 实际应用:配置文件中的固定值
const API_CONFIG = {
    BASE_URL: 'https://api.example.com',
    TIMEOUT: 5000,
    RETRY_TIMES: 3
};

【代码注释】const MIN_SIZE; 缺初始化会 SyntaxError------引擎要求声明时即确定绑定。习惯用 const CONFIG = { ... } 集中配置;若需延迟赋值,先用 let 再在逻辑分支里赋值一次,或拆成函数返回配置对象。

特性二:值不可重新赋值

javascript 复制代码
// 【代码注释】对基本类型,`PI = 3.14` 为 TypeError(改的是绑定)。`const user = {}` 可改 `user.name`(改的是堆上对象),不能 `user = {}`(换绑定)。深冻结需 `Object.freeze`;只读视图用 `Object.seal`/`defineProperty`。面试常问:const 保证的是**引用不变**,不是深度不可变。
const PI = 3.14159265359;
// PI = 3.14; // TypeError: Assignment to constant variable

// 但对象和数组的内容可以修改
const user = { name: '张三', age: 25 };
user.name = '李四'; // 合法,修改对象属性
user.age = 26;      // 合法
// user = {};       // 非法,不能重新赋值整个对象

// 数组同理
const numbers = [1, 2, 3];
numbers.push(4);    // 合法
numbers[0] = 10;    // 合法
// numbers = [];    // 非法

【代码注释】对基本类型,PI = 3.14 为 TypeError(改的是绑定)。const user = {} 可改 user.name(改的是堆上对象),不能 user = {}(换绑定)。深冻结需 Object.freeze;只读视图用 Object.seal/defineProperty。面试常问:const 保证的是引用不变 ,不是深度不可变。

特性三:块级作用域

javascript 复制代码
// 【代码注释】`CONSTANT` 在函数内可见;`ANOTHER_CONSTANT` 仅在 `if` 块内,块外 ReferenceError。与 let 相同的 TDZ、不可重复声明规则。`for (const i = 0; i < n; i++)` 非法------循环变量会递增,应使用 let。
function demo() {
    const CONSTANT = '常量';
    
    if (true) {
        const ANOTHER_CONSTANT = '另一个常量';
        console.log(CONSTANT); // 可以访问外层常量
    }
    
    console.log(CONSTANT);
    // console.log(ANOTHER_CONSTANT); // ReferenceError
}

【代码注释】CONSTANT 在函数内可见;ANOTHER_CONSTANT 仅在 if 块内,块外 ReferenceError。与 let 相同的 TDZ、不可重复声明规则。for (const i = 0; i < n; i++) 非法------循环变量会递增,应使用 let。

2.2.4 const的实际应用场景

场景一:配置文件管理

javascript 复制代码
// 【代码注释】嵌套 `CONFIG` 用 const 防止整体被重新赋值;`fetchUserInfo` 里用模板字符串拼 URL。注意:`process.env` 在浏览器需构建工具注入。团队规范:配置对象全大写键名 + const,运行时只读引用,避免散落魔法字符串。
const CONFIG = {
    // API配置
    API: {
        BASE_URL: process.env.API_BASE_URL || 'https://api.example.com',
        TIMEOUT: 5000,
        ENDPOINTS: {
            USER: '/user',
            PRODUCTS: '/products'
        }
    },
    
    // 应用配置
    APP: {
        NAME: 'MyApp',
        VERSION: '1.0.0',
        DEBUG: process.env.NODE_ENV === 'development'
    },
    
    // 业务常量
    BUSINESS: {
        MAX_RETRY_TIMES: 3,
        DEFAULT_PAGE_SIZE: 20,
        CACHE_DURATION: 3600000 // 1小时
    }
};

// 使用配置
async function fetchUserInfo(userId) {
    const url = `${CONFIG.API.BASE_URL}${CONFIG.API.ENDPOINTS.USER}/${userId}`;
    const response = await fetch(url, {
        timeout: CONFIG.API.TIMEOUT
    });
    return response.json();
}

【代码注释】嵌套 CONFIG 用 const 防止整体被重新赋值;fetchUserInfo 里用模板字符串拼 URL。注意:process.env 在浏览器需构建工具注入。团队规范:配置对象全大写键名 + const,运行时只读引用,避免散落魔法字符串。

场景二:导入模块

javascript 复制代码
// 【代码注释】默认导入与命名导入用 const,因模块导出绑定在运行时是只读的(live binding)。函数组件内 `useState` 返回的 setter 可改 state,但组件标识 `UserProfile` 不应被重新赋值。Tree-shaking 依赖静态 `import`,与 require 动态加载不同。
import React from 'react';
import { useState, useEffect } from 'react';
import axios from 'axios';

// 声明常量组件
const UserProfile = ({ userId }) => {
    const [user, setUser] = useState(null);
    
    useEffect(() => {
        axios.get(`/api/users/${userId}`)
            .then(response => setUser(response.data));
    }, [userId]);
    
    return user ? <div>{user.name}</div> : null;
};

【代码注释】默认导入与命名导入用 const,因模块导出绑定在运行时是只读的(live binding)。函数组件内 useState 返回的 setter 可改 state,但组件标识 UserProfile 不应被重新赋值。Tree-shaking 依赖静态 import,与 require 动态加载不同。

场景三:数学和物理常量

javascript 复制代码
// 【代码注释】将 `Math.PI` 等挂到 `MATHEMATICS` 对象便于业务代码语义化;物理常数用科学计数法字面量。注意:JS number 仍是 IEEE 754 双精度,极高精度物理量 eventually 需 BigInt 或外部 decimal 库。
const MATHEMATICS = {
    PI: Math.PI,
    E: Math.E,
    LN2: Math.LN2,
    LN10: Math.LN10,
    LOG2E: Math.LOG2E,
    LOG10E: Math.LOG10E,
    SQRT2: Math.SQRT2,
    SQRT1_2: Math.SQRT1_2
};

const PHYSICS = {
    SPEED_OF_LIGHT: 299792458,      // m/s
    GRAVITATIONAL_CONSTANT: 6.674e-11, // m³/(kg·s²)
    PLANCK_CONSTANT: 6.626e-34,      // J·s
    BOLTZMANN_CONSTANT: 1.381e-23    // J/K
};

// 计算圆的面积
function calculateCircleArea(radius) {
    return MATHEMATICS.PI * radius * radius;
}

【代码注释】将 Math.PI 等挂到 MATHEMATICS 对象便于业务代码语义化;物理常数用科学计数法字面量。注意:JS number 仍是 IEEE 754 双精度,极高精度物理量 eventually 需 BigInt 或外部 decimal 库。

2.2.5 const + Object.freeze():打造真正的不可变对象

const 只保证变量绑定 不可重新赋值,对象内部属性依然可以修改。若需要真正不可变 的数据结构,需配合 Object.freeze()

理论背景:

  • const 保证:引用地址不变
  • Object.freeze() 保证:对象顶层属性不可增/删/改(浅冻结)
  • 深冻结(Deep Freeze):递归冻结所有嵌套层级,实现完全不可变
javascript 复制代码
// 【代码注释】const 只锁引用,属性仍可改;Object.freeze 锁顶层属性但不递归

// ① const 的限制:引用不变,内容可变
const config = { debug: false, version: '1.0' };
config.debug = true;   // ✅ 合法:修改属性,不改变引用
// config = {};         // ❌ TypeError:不能重新赋值整个对象

// ② Object.freeze():浅冻结顶层属性
const frozenConfig = Object.freeze({
    debug: false,
    version: '1.0',
    database: { host: 'localhost', port: 5432 } // 嵌套对象不会被冻结!
});

// 严格模式下修改冻结属性会 TypeError,非严格模式静默失败
frozenConfig.debug = true;          // TypeError(严格模式)或静默忽略
frozenConfig.newProp = 'test';      // 同上,不能添加新属性
delete frozenConfig.version;        // 同上,不能删除属性

// ③ 关键陷阱:嵌套对象未被冻结
frozenConfig.database.host = '192.168.1.1'; // ✅ 嵌套对象仍可修改!
console.log(Object.isFrozen(frozenConfig.database)); // false

// 【代码注释】Object.isFrozen() 检查冻结状态
console.log(Object.isFrozen(frozenConfig)); // true(顶层已冻结)

【代码注释】浅冻结只保护第一层属性;嵌套对象需要单独冻结或使用深冻结工具函数。面试常考:const obj = Object.freeze({a:{b:1}}); obj.a.b = 2 不会报错,因为 obj.a 指向的嵌套对象未被冻结。

javascript 复制代码
// 【代码注释】deepFreeze:递归冻结所有层级,适合配置常量与枚举对象

function deepFreeze(obj) {
    // Reflect.ownKeys 获取所有自身属性(含 Symbol),比 Object.keys 更全
    const propNames = Reflect.ownKeys(obj);

    for (const name of propNames) {
        const value = obj[name];
        // 仅对非 null 的对象类型属性递归冻结,防止处理 Date/RegExp 等内置对象
        if (value && typeof value === 'object' && !Object.isFrozen(value)) {
            deepFreeze(value);
        }
    }
    return Object.freeze(obj);
}

// 应用:项目级配置常量(深冻结后任何层级都无法修改)
const APP_CONFIG = deepFreeze({
    api: {
        baseUrl: 'https://api.example.com',
        timeout: 5_000,
        endpoints: { user: '/users', product: '/products' }
    },
    features: { darkMode: true, analytics: false },
    version: '2.0.0'
});

// APP_CONFIG.api.baseUrl = 'hacked'; // TypeError:已深冻结

// 【代码注释】枚举常量:deepFreeze 防止运行时被意外覆盖,替代 TypeScript enum 的纯 JS 方案
const Direction = deepFreeze({ UP: 'UP', DOWN: 'DOWN', LEFT: 'LEFT', RIGHT: 'RIGHT' });
const HttpStatus = deepFreeze({ OK: 200, NOT_FOUND: 404, SERVER_ERROR: 500 });

function handleResponse(status) {
    switch (status) {
        case HttpStatus.OK:         return '请求成功';
        case HttpStatus.NOT_FOUND:  return '资源不存在';
        case HttpStatus.SERVER_ERROR: return '服务器错误';
        default:                    return `未知状态码: ${status}`;
    }
}

【代码注释】deepFreeze 与 TypeScript as const 等价(均在各自环境提供深度只读语义)。生产实践建议:项目级配置枚举常量主题令牌 均可用 const + deepFreeze;框架侧(Immer、Redux Toolkit)内部已封装不可变逻辑,日常业务数据无需手动冻结。

方式 引用不变 顶层属性不变 深层属性不变 TypeScript 支持
const 变量类型推断
Object.freeze() ❌(浅) Readonly<T>
deepFreeze() DeepReadonly<T>
as const(TS) 字面量类型推断

2.3 块级作用域深度解析

2.3.1 什么是块级作用域

名词解析:

  • 块级作用域(Block Scope):变量在代码块({})内有效,外部无法访问
  • 代码块:一对大括号{}包裹的代码区域
  • 作用域链:内部作用域可以访问外部作用域的变量,反之不行
2.3.2 产生块级作用域的情况

#mermaid-svg-yPTtYHXM8B0WIJNZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yPTtYHXM8B0WIJNZ .error-icon{fill:#552222;}#mermaid-svg-yPTtYHXM8B0WIJNZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yPTtYHXM8B0WIJNZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .marker.cross{stroke:#333333;}#mermaid-svg-yPTtYHXM8B0WIJNZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yPTtYHXM8B0WIJNZ p{margin:0;}#mermaid-svg-yPTtYHXM8B0WIJNZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .cluster-label text{fill:#333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .cluster-label span{color:#333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .cluster-label span p{background-color:transparent;}#mermaid-svg-yPTtYHXM8B0WIJNZ .label text,#mermaid-svg-yPTtYHXM8B0WIJNZ span{fill:#333;color:#333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .node rect,#mermaid-svg-yPTtYHXM8B0WIJNZ .node circle,#mermaid-svg-yPTtYHXM8B0WIJNZ .node ellipse,#mermaid-svg-yPTtYHXM8B0WIJNZ .node polygon,#mermaid-svg-yPTtYHXM8B0WIJNZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yPTtYHXM8B0WIJNZ .rough-node .label text,#mermaid-svg-yPTtYHXM8B0WIJNZ .node .label text,#mermaid-svg-yPTtYHXM8B0WIJNZ .image-shape .label,#mermaid-svg-yPTtYHXM8B0WIJNZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-yPTtYHXM8B0WIJNZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yPTtYHXM8B0WIJNZ .rough-node .label,#mermaid-svg-yPTtYHXM8B0WIJNZ .node .label,#mermaid-svg-yPTtYHXM8B0WIJNZ .image-shape .label,#mermaid-svg-yPTtYHXM8B0WIJNZ .icon-shape .label{text-align:center;}#mermaid-svg-yPTtYHXM8B0WIJNZ .node.clickable{cursor:pointer;}#mermaid-svg-yPTtYHXM8B0WIJNZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .arrowheadPath{fill:#333333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yPTtYHXM8B0WIJNZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yPTtYHXM8B0WIJNZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yPTtYHXM8B0WIJNZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yPTtYHXM8B0WIJNZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yPTtYHXM8B0WIJNZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yPTtYHXM8B0WIJNZ .cluster text{fill:#333;}#mermaid-svg-yPTtYHXM8B0WIJNZ .cluster span{color:#333;}#mermaid-svg-yPTtYHXM8B0WIJNZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yPTtYHXM8B0WIJNZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yPTtYHXM8B0WIJNZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-yPTtYHXM8B0WIJNZ .icon-shape,#mermaid-svg-yPTtYHXM8B0WIJNZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yPTtYHXM8B0WIJNZ .icon-shape p,#mermaid-svg-yPTtYHXM8B0WIJNZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yPTtYHXM8B0WIJNZ .icon-shape .label rect,#mermaid-svg-yPTtYHXM8B0WIJNZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yPTtYHXM8B0WIJNZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yPTtYHXM8B0WIJNZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yPTtYHXM8B0WIJNZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 块级作用域产生情况
大括号中的let/const声明
分支结构 if/switch
循环结构 for/while
try-catch语句
对象字面量不产生作用域
if语句块
switch语句块
for循环块
while循环块

【代码注释】仅 let/const/class{} 内会创建词法环境;{ var x = 1 } 中 var 仍提升到外层函数。switchcase 建议加 {} 避免多个 let 同名冲突(同一 switch 块共享作用域)。对象字面量 {} 是表达式,不是块级作用域边界。

2.3.3 不同类型的块级作用域

类型一:函数内部

javascript 复制代码
// 【代码注释】`for` 循环里的 `i`、`squared` 在循环块结束即销毁,函数末尾访问会 ReferenceError。对比 `var` 版 `i` 会泄漏到函数级。写算法时应用 let 限定临时变量生命周期,减少误用外层同名变量。
function processArray(numbers) {
    let result = [];
    
    // for循环块
    for (let i = 0; i < numbers.length; i++) {
        let squared = numbers[i] * numbers[i];
        result.push(squared);
    }
    
    // console.log(i); // ReferenceError: i is not defined
    // console.log(squared); // ReferenceError: squared is not defined
    
    return result;
}

【代码注释】for 循环里的 isquared 在循环块结束即销毁,函数末尾访问会 ReferenceError。对比 vari 会泄漏到函数级。写算法时应用 let 限定临时变量生命周期,减少误用外层同名变量。

类型二:条件语句块

javascript 复制代码
// 【代码注释】内层 `let role` 遮蔽外层 `role`,形成独立绑定;`admin` 分支再次遮蔽。`console.log` 打印的是**当前块**可见的 role。这是刻意用块隔离权限变量的写法;若需统一角色,应只声明一次并赋值,避免多层同名 let 造成阅读负担。
function getUserRole(user) {
    let role = 'guest';
    
    if (user.isLoggedIn) {
        let role = 'user'; // 新的块级变量
        let permissions = ['read', 'write'];
        
        if (user.isAdmin) {
            let role = 'admin'; // 又一个新的块级变量
            permissions.push('delete');
            console.log('内部:', role); // admin
        }
        
        console.log('中间:', role); // user
        return { role, permissions };
    }
    
    console.log('外部:', role); // guest
    return { role };
}

【代码注释】内层 let role 遮蔽外层 role,形成独立绑定;admin 分支再次遮蔽。console.log 打印的是当前块 可见的 role。这是刻意用块隔离权限变量的写法;若需统一角色,应只声明一次并赋值,避免多层同名 let 造成阅读负担。

类型三:switch语句

javascript 复制代码
// 【代码注释】每个 `case` 外包 `{}` 后,`studentDiscount` 等只在对应分支存在,避免 switch 整体共享一个块导致重复 `let` 报错。`discount` 在外层声明,分支内赋值后 `break`,是处理多档折扣的常见结构。
function getDiscount(type) {
    let discount = 0;
    
    switch (type) {
        case 'student': {
            let studentDiscount = 0.8;
            discount = studentDiscount;
            break;
        }
        case 'senior': {
            let seniorDiscount = 0.85;
            discount = seniorDiscount;
            break;
        }
        default: {
            let defaultDiscount = 1.0;
            discount = defaultDiscount;
        }
    }
    
    // console.log(studentDiscount); // ReferenceError
    return discount;
}

【代码注释】每个 case 外包 {} 后,studentDiscount 等只在对应分支存在,避免 switch 整体共享一个块导致重复 let 报错。discount 在外层声明,分支内赋值后 break,是处理多档折扣的常见结构。

类型四:try-catch语句

javascript 复制代码
// 【代码注释】`try` 内 `riskyVariable` 在 `catch` 中不可见;`error` 仅在 catch 块有效。`finally` 中同样访问不到 try 块内的 let。清理资源时把需跨块共享的句柄声明在 try 之前。
try {
    let riskyVariable = '危险操作';
    throw new Error('发生错误');
} catch (error) {
    let errorMessage = error.message;
    console.log(errorMessage);
    // console.log(riskyVariable); // ReferenceError
}

【代码注释】tryriskyVariablecatch 中不可见;error 仅在 catch 块有效。finally 中同样访问不到 try 块内的 let。清理资源时把需跨块共享的句柄声明在 try 之前。

2.3.4 块级作用域的实际应用

应用一:防止变量污染

javascript 复制代码
// 【代码注释】用无名称的 `{}` 块把 `config` 与 `userState` 隔离开,初始化结束后块外无法访问,降低全局/函数级变量名冲突。适合初始化脚本、一次性 bootstrap;长期状态仍应归入模块或状态管理 store。
function initApplication() {
    // 配置块
    {
        const config = {
            apiUrl: 'https://api.example.com',
            timeout: 5000
        };
        
        // 使用配置初始化应用
        initializeApi(config);
    }
    
    // 用户状态块
    {
        let userState = {
            isLoggedIn: false,
            currentUser: null
        };
        
        // 设置用户状态监听
        setupUserListeners(userState);
    }
    
    // 配置和用户状态变量互不干扰
    // console.log(config); // ReferenceError
    // console.log(userState); // ReferenceError
}

【代码注释】用无名称的 {} 块把 configuserState 隔离开,初始化结束后块外无法访问,降低全局/函数级变量名冲突。适合初始化脚本、一次性 bootstrap;长期状态仍应归入模块或状态管理 store。

应用二:循环中的异步操作

javascript 复制代码
// 【代码注释】`for (let i)` 每次迭代新作用域,`taskIndex` 捕获当前 i;`setTimeout` 延迟与 i 无关。`Promise.all` 等待全部完成。等价于经典 IIFE `(function(j){ ... })(i)`,但 let 写法更短、更少出错。
// 创建多个Promise,每个都有独立的索引
function createAsyncTasks() {
    const promises = [];
    
    for (let i = 0; i < 5; i++) {
        // 每次循环创建新的块级作用域
        const taskIndex = i; // 使用const也可以
        
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                console.log(`任务 ${taskIndex} 完成`);
                resolve(taskIndex);
            }, 100 * (i + 1));
        });
        
        promises.push(promise);
    }
    
    return Promise.all(promises);
}

【代码注释】for (let i) 每次迭代新作用域,taskIndex 捕获当前 i;setTimeout 延迟与 i 无关。Promise.all 等待全部完成。等价于经典 IIFE (function(j){ ... })(i),但 let 写法更短、更少出错。

2.3.5 作用域链与闭包
javascript 复制代码
// 【代码注释】`createCounter` 返回对象方法共享闭包中的 `count`;外层 `let count` 不被外部直接修改,只能通过 increment/decrement 接口变更。这是模块模式与 React hooks 闭包状态的雏形;注意箭头函数不绑定自己的 `this`,此处无 this 问题。
function createCounter() {
    let count = 0; // 外层作用域变量
    
    return {
        increment: () => {
            count++; // 内层函数访问外层变量
            return count;
        },
        decrement: () => {
            count--;
            return count;
        },
        getCount: () => count
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2

【代码注释】createCounter 返回对象方法共享闭包中的 count;外层 let count 不被外部直接修改,只能通过 increment/decrement 接口变更。这是模块模式与 React hooks 闭包状态的雏形;注意箭头函数不绑定自己的 this,此处无 this 问题。

2.4 完整可运行示例:let、const 与块级作用域

以下示例可直接保存为 .html 在浏览器中运行,覆盖 let 四大特性、块级作用域、const 常量语义、列表点击索引 等核心演练。

2.4.1 let 关键字:提升、重复声明与全局对象
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>let 关键字演示</title>
</head>
<body>
  <script>
    // 【代码注释】特性二 TDZ:var age 已提升,声明前访问为 undefined;let address 在声明前访问会 ReferenceError
    console.log(age);        // undefined(var 提升,尚未执行 var age = 100)
    // console.log(address); // ReferenceError: Cannot access 'address' before initialization
    console.log('');

    let address = '上海';    // 全局词法绑定,不挂 window
    var age = 100;           // 挂到 window.age
    let func = function () {}; // 函数表达式同样受 TDZ 约束

    // 【代码注释】特性一:let 不可重复声明;var 可重复声明且后者覆盖前者
    // let address = '北京'; // SyntaxError: Identifier 'address' has already been declared
    var age = 200;          // 合法,window.age 变为 200
    // let age = 300;        // SyntaxError(若取消注释)

    // 【代码注释】特性四:全局 let 不是 window 的可枚举属性
    console.log(window.age);      // 200
    console.log(window.address);  // undefined(address 在全局词法环境中,不在 window 上)
  </script>
</body>
</html>

【代码注释】本页串联 let 四大特性: 重复声明报 SyntaxError; TDZ 内访问 ReferenceError(对比 var 打印 undefined); 块级作用域见 2.4.2; window.address === undefinedwindow.age 有值。建议按注释顺序取消注释 address 与重复 let,在控制台对照报错信息加深记忆。

2.4.2 块级作用域:大括号、分支、循环与 DOM 事件
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>块级作用域演示</title>
</head>
<body>
  <ul>
    <li>列表项 1</li>
    <li>列表项 2</li>
    <li>列表项 3</li>
    <li>列表项 4</li>
    <li>列表项 5</li>
  </ul>
  <script>
    // 【代码注释】脚本顶层 let:整个 script 块内可见(浏览器中为全局词法环境)
    let workAddress = '广州';

    {
      console.log('块区域:');
      // 【代码注释】var 穿透块边界,提升到外层(此处为全局/script 顶层)
      var homeAddress = '上海';
      // 【代码注释】let 仅在本 {} 块内有效,块外 ReferenceError
      let schoolAddress = '北京';
      console.log(homeAddress, schoolAddress, workAddress);
    }
    console.log(homeAddress, workAddress); // homeAddress 仍可访问(var 泄漏)
    // console.log(schoolAddress); // ReferenceError: schoolAddress is not defined

    if (true) {
      var age01 = 100;  // 泄漏到外层,if 结束后仍可访问
      let age02 = 200;  // 仅 if 块内
    }
    console.log(age01); // 100
    // console.log(age02); // ReferenceError

    // 【代码注释】for (let i) 每次迭代创建新的 i 绑定,循环外不可访问
    for (let i = 0; i < 5; i++) {
      console.log('循环内 i:', i);
    }
    // console.log(i); // ReferenceError

    // 【代码注释】经典闭包修复:for (let i) + 事件回调,每个 li 捕获当次索引 0~4
    const liBoxs = document.querySelectorAll('li');
    for (let i = 0; i < liBoxs.length; i++) {
      liBoxs[i].onclick = function () {
        console.log('点击索引:', i); // 若改为 var i,五次点击都打印 4
      };
    }
  </script>
</body>
</html>

【代码注释】本页对比 var 泄漏let 块级隔离homeAddress/age01 逃出 {} 仍可访问,schoolAddress/age02/循环 i 不可。列表点击演示 for (let i) 每次迭代新绑定------与 setTimeout 闭包陷阱同源,是 DOM 批量绑定的标准写法;可尝试将 let i 改为 var i 观察五次点击均输出 4。

2.4.3 const 关键字:常量与引用类型
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>const 关键字演示</title>
</head>
<body>
  <script>
    let homeAddress = '上海';       // let:绑定可重新赋值
    const schoolAddress = '北京';   // const:绑定不可改,引用类型内容仍可变
    const AGE = 100;                // 约定:全大写表示「语义上的常量」

    homeAddress = '广州';           // 合法,修改 let 绑定的值
    // schoolAddress = '深圳';     // TypeError: Assignment to constant variable

    let info01;                     // let 可先声明后赋值
    // const info02;               // SyntaxError: Missing initializer in const declaration

    // 【代码注释】循环计数器会递增,不能用 const;配置对象、DOM 节点列表引用可用 const
    // for (const i = 0; i <= 5; i++) {} // 非法:i 在循环头被重新赋值
  </script>
</body>
</html>

【代码注释】constlet 共享块级作用域、TDZ、不可重复声明;差异在于 绑定不可重新赋值TypeError)且 必须初始化 (否则 SyntaxError)。const obj = {} 后仍可 obj.key = 1,但不能 obj = {};循环变量、累加器用 let,API 配置、import、DOM 集合引用默认 const

2.4.4 let / const / var 对比归纳
维度 var let const
作用域 函数/全局 块级 块级
变量提升 有(undefined) 无(TDZ) 无(TDZ)
重复声明 允许 禁止 禁止
重新赋值 允许 允许 禁止
全局对象属性
典型场景 遗留代码 可变局部变量 配置/引用不变

3. 解构赋值:优雅的数据提取方式

3.1 数组解构赋值

3.1.1 什么是解构赋值

名词解析:

  • 解构赋值(Destructuring Assignment):按照一定模式,从数组和对象中提取值,对变量进行赋值
  • 模式匹配:等号两边的数据结构进行匹配,对应位置赋值
  • 伪数组:具有length属性和索引属性的对象,如arguments、NodeList等
3.1.2 数组解构的基本用法
javascript 复制代码
// 【代码注释】数组解构按**索引位置**一一对应:`[a,b,c]` 与 `[1,2,3]` 对齐;逗号占位跳过元素;`...tail` 收集剩余项为**新数组**(浅拷贝剩余部分)。右侧必须是可迭代结构;解构失败项为 `undefined`,可用默认值补齐(见下一节)。
const numbers = [1, 2, 3, 4, 5];

// 完全解构
let [a, b, c, d, e] = numbers;
console.log(a, b, c, d, e); // 1 2 3 4 5

// 部分解构
let [first, second] = numbers;
console.log(first, second); // 1 2

// 忽略某些值
let [num1, , num3] = numbers;
console.log(num1, num3); // 1 3

// 使用扩展运算符
let [head, ...tail] = numbers;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

【代码注释】数组解构按索引位置 一一对应:[a,b,c][1,2,3] 对齐;逗号占位跳过元素;...tail 收集剩余项为新数组 (浅拷贝剩余部分)。右侧必须是可迭代结构;解构失败项为 undefined,可用默认值补齐(见下一节)。

3.1.3 数组解构的高级用法

用法一:设置默认值

javascript 复制代码
// 【代码注释】缺项取默认值:`[x,y,z]=[10,20]` 得 z=3。`b = a + 1` 中默认值表达式在**该变量未赋值**时求值,可引用已解构的 a。函数形参 `function greet([name='访客']=[])` 对无参/空数组调用仍安全,是表单、配置对象的常用模式。
let [x = 1, y = 2, z = 3] = [10, 20];
console.log(x, y, z); // 10 20 3

// 默认值可以是表达式
let [a = 1, b = a + 1] = [10];
console.log(a, b); // 10 11

// 实际应用:函数参数默认值
function greet([name = '访客', age = 18] = []) {
    console.log(`欢迎 ${name},年龄 ${age}`);
}

greet(['张三', 25]); // 欢迎 张三,年龄 25
greet(); // 欢迎 访客,年龄 18

【代码注释】缺项取默认值:[x,y,z]=[10,20] 得 z=3。b = a + 1 中默认值表达式在该变量未赋值 时求值,可引用已解构的 a。函数形参 function greet([name='访客']=[]) 对无参/空数组调用仍安全,是表单、配置对象的常用模式。

用法二:交换变量值

javascript 复制代码
// 【代码注释】`[m,n]=[n,m]` 先求值右侧元组再赋值,可交换任意可解构对。冒泡排序里交换 `arr[j]` 与 `arr[j+1]` 比传统 tmp 更简洁。注意:右侧表达式一次性求值,不要在一行内读写同一变量造成困惑。
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2 1

// 实际应用:排序算法中的交换
function bubbleSort(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; // 优雅的交换
            }
        }
    }
    return arr;
}

console.log(bubbleSort([3, 1, 4, 1, 5, 9, 2, 6]));

【代码注释】[m,n]=[n,m] 先求值右侧元组再赋值,可交换任意可解构对。冒泡排序里交换 arr[j]arr[j+1] 比传统 tmp 更简洁。注意:右侧表达式一次性求值,不要在一行内读写同一变量造成困惑。

用法三:嵌套数组解构

javascript 复制代码
// 【代码注释】模式 `[a,[b,c],[d,[e,f]]]` 与嵌套数组结构同形匹配;部分解构可跳过中间层。`parseMatrix` 一次取出三行三列,适合棋盘、图像卷积核等小规模矩阵;深层嵌套建议拆步或改用对象解构提高可读性。
const complexArray = [
    1,
    [2, 3],
    [
        4,
        [5, 6]
    ]
];

// 完全解构
let [a, [b, c], [d, [e, f]]] = complexArray;
console.log(a, b, c, d, e, f); // 1 2 3 4 5 6

// 部分解构
let [first, , [second]] = complexArray;
console.log(first, second); // 1 [4, [5, 6]]

// 实际应用:解析复杂的数据结构
function parseMatrix(matrix) {
    let [
        [a1, a2, a3],
        [b1, b2, b3],
        [c1, c2, c3]
    ] = matrix;
    
    return {
        diagonal: [a1, b2, c3],
        firstRow: [a1, a2, a3],
        lastColumn: [a3, b3, c3]
    };
}

const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

console.log(parseMatrix(matrix));

【代码注释】模式 [a,[b,c],[d,[e,f]]] 与嵌套数组结构同形匹配;部分解构可跳过中间层。parseMatrix 一次取出三行三列,适合棋盘、图像卷积核等小规模矩阵;深层嵌套建议拆步或改用对象解构提高可读性。

用法四:伪数组解构

javascript 复制代码
// 【代码注释】字符串、NodeList、`arguments` 均有 length 与索引,可按数组解构。`querySelectorAll` 解构便于取前几个按钮;`arguments` 解构在剩余参数 `...args` 普及后较少用。迭代器对象(Map)需 `for...of` 或 `Array.from` 后再解构。
// 字符串解构
const str = 'Hello';
let [s1, s2, s3, s4, s5] = str;
console.log(s1, s2, s3, s4, s5); // H e l l o

// NodeList解构
const buttons = document.querySelectorAll('button');
let [firstBtn, secondBtn, thirdBtn] = buttons;
console.log(firstBtn, secondBtn, thirdBtn);

// arguments解构
function sum() {
    let [a, b, c] = arguments;
    return a + b + c;
}

console.log(sum(1, 2, 3)); // 6

// 实际应用:DOM操作
function setupPagination(paginationElement) {
    const buttons = paginationElement.querySelectorAll('button');
    let [first, prev, , next, last] = buttons;
    
    first.addEventListener('click', () => goToPage(1));
    prev.addEventListener('click', () => goToPage(currentPage - 1));
    next.addEventListener('click', () => goToPage(currentPage + 1));
    last.addEventListener('click', () => goToPage(totalPages));
}

【代码注释】字符串、NodeList、arguments 均有 length 与索引,可按数组解构。querySelectorAll 解构便于取前几个按钮;arguments 解构在剩余参数 ...args 普及后较少用。迭代器对象(Map)需 for...ofArray.from 后再解构。

3.1.4 数组解构的实际应用场景

场景一:函数返回多个值

javascript 复制代码
// 【代码注释】多返回值用数组解构比 `const r=get(); const name=r[0]` 清晰。异步示例中 `await response.json()` 若返回三元组,一次解构 product/related/reviews。约定:返回数组时文档应固定顺序;超过 3 项建议改返回对象以免记错下标。
function getUserStats(userId) {
    // 模拟API调用
    return [
        '张三',    // 姓名
        25,       // 年龄
        '北京',   // 城市
        100       // 积分
    ];
}

let [name, age, city, points] = getUserStats(123);
console.log(`用户${name}来自${city},今年${age}岁,积分${points}`);

// 实际应用:处理API返回的数据
async function fetchProductDetails(productId) {
    const response = await fetch(`/api/products/${productId}`);
    const [product, relatedProducts, reviews] = await response.json();
    
    return {
        product: {
            name: product.name,
            price: product.price,
            description: product.description
        },
        relatedProducts: relatedProducts.slice(0, 4),
        averageRating: calculateAverageRating(reviews)
    };
}

【代码注释】多返回值用数组解构比 const r=get(); const name=r[0] 清晰。异步示例中 await response.json() 若返回三元组,一次解构 product/related/reviews。约定:返回数组时文档应固定顺序;超过 3 项建议改返回对象以免记错下标。

场景二:处理CSV数据

javascript 复制代码
// 【代码注释】`split(',')` + `map(trim)` 得到字段数组再解构;批量处理时用 `map(([name,age,...])=>({...}))` 转对象。真实 CSV 需处理引号内逗号,应使用专用解析库;此处演示解构与数据清洗的组合思路。
function parseCSVLine(line) {
    return line.split(',').map(field => field.trim());
}

const csvLine = '张三,25,北京,工程师';
let [name, age, city, profession] = parseCSVLine(csvLine);
console.log(name, age, city, profession); // 张三 25 北京 工程师

// 实际应用:批量处理数据
function processEmployeeData(csvData) {
    return csvData.split('\n')
        .map(line => parseCSVLine(line))
        .filter(fields => fields.length === 4)
        .map(([name, age, department, position]) => ({
            name,
            age: parseInt(age),
            department,
            position,
            hireDate: new Date() // 添加默认入职日期
        }));
}

【代码注释】split(',') + map(trim) 得到字段数组再解构;批量处理时用 map(([name,age,...])=>({...})) 转对象。真实 CSV 需处理引号内逗号,应使用专用解析库;此处演示解构与数据清洗的组合思路。

场景三:状态管理

javascript 复制代码
// 【代码注释】示例用数组解构模拟「取四个字段再写回 state」;真实 React 会用 `setState(prev=>({...prev,...updates}))` 或 useReducer。要点:解构让「从 state 取一组字段」与「合并更新」的字段列表保持同步,减少漏改某一属性。
class FormComponent {
    constructor() {
        this.state = {
            username: '',
            email: '',
            age: 18,
            city: '北京'
        };
    }
    
    updateState(updates) {
        // 部分更新状态
        const [username, email, age, city] = [
            updates.username || this.state.username,
            updates.email || this.state.email,
            updates.age || this.state.age,
            updates.city || this.state.city
        ];
        
        this.state = { username, email, age, city };
        this.render();
    }
    
    render() {
        const [username, email, age, city] = [
            this.state.username,
            this.state.email,
            this.state.age,
            this.state.city
        ];
        
        console.log(`渲染表单: ${username}, ${email}, ${age}岁, ${city}`);
    }
}

【代码注释】示例用数组解构模拟「取四个字段再写回 state」;真实 React 会用 setState(prev=>({...prev,...updates})) 或 useReducer。要点:解构让「从 state 取一组字段」与「合并更新」的字段列表保持同步,减少漏改某一属性。

3.2 对象解构赋值

3.2.1 对象解构的基本语法

名词解析:

  • 对象解构:按照属性名进行匹配,将对象属性的值赋给变量
  • 属性简写:当属性名和变量名相同时,可以省略冒号
  • 计算属性名:使用表达式作为属性名
javascript 复制代码
// 【代码注释】对象按**属性名**匹配:`{name: userName}` 把 `user.name` 赋给 `userName`;`{name,age}` 为属性名与变量名相同的简写。不存在属性为 `undefined`,`email = '未设置'` 提供默认值。与数组解构「按位置」对比记忆。
const user = {
    name: '张三',
    age: 25,
    city: '北京'
};

// 完整写法
let { name: userName, age: userAge, city: userCity } = user;
console.log(userName, userAge, userCity); // 张三 25 北京

// 简写形式(属性名与变量名相同)
let { name, age, city } = user;
console.log(name, age, city); // 张三 25 北京

// 解构不存在的属性
let { name, email = '未设置' } = user;
console.log(email); // 未设置

【代码注释】对象按属性名 匹配:{name: userName}user.name 赋给 userName{name,age} 为属性名与变量名相同的简写。不存在属性为 undefinedemail = '未设置' 提供默认值。与数组解构「按位置」对比记忆。

3.2.2 对象解构的高级用法

用法一:嵌套对象解构

javascript 复制代码
// 【代码注释】模式 `{ personalInfo: { name, address: { city } } }` 逐层进入;重命名与默认值可写在任意层。API 字段深层嵌套时,解构可减少 `user.personalInfo.address.city` 链式书写;过深时建议 flatMap 或 selector 函数。
const complexUser = {
    personalInfo: {
        name: '李四',
        age: 30,
        address: {
            city: '上海',
            district: '浦东新区'
        }
    },
    workInfo: {
        company: '科技公司',
        position: '工程师',
        salary: 15000
    }
};

// 解构嵌套属性
let {
    personalInfo: {
        name,
        address: { city }
    },
    workInfo: { company }
} = complexUser;

console.log(name, city, company); // 李四 上海 科技公司

// 实际应用:处理复杂的API响应
function processUserProfile(apiResponse) {
    const {
        data: {
            user: {
                profile: { name, avatar },
                settings: { theme, language },
                stats: { followers, following }
            },
            metadata: { lastLogin, accountLevel }
        }
    } = apiResponse;
    
    return {
        userInfo: { name, avatar },
        preferences: { theme, language },
        statistics: { followers, following },
        accountInfo: { lastLogin, level: accountLevel }
    };
}

【代码注释】模式 { personalInfo: { name, address: { city } } } 逐层进入;重命名与默认值可写在任意层。API 字段深层嵌套时,解构可减少 user.personalInfo.address.city 链式书写;过深时建议 flatMap 或 selector 函数。

用法二:对象解构用于函数参数

javascript 复制代码
// 【代码注释】形参 `{ name, age = 18 }` 在调用时自动从传入对象取值;可再嵌套、设默认值。配合剩余属性 `...rest` 收集未列出字段。REST 接口 body 校验常与解构 + 默认值一起使用。
function createUser({ name, age = 18, city = '北京' }) {
    console.log(`创建用户: ${name}, ${age}岁, 来自${city}`);
    return { name, age, city };
}

createUser({ name: '王五', age: 25 }); // 创建用户: 王五, 25岁, 来自北京
createUser({ name: '赵六' }); // 创建用户: 赵六, 18岁, 来自北京

// 实际应用:配置选项函数
function fetchApiData({
    url,
    method = 'GET',
    headers = {},
    body = null,
    timeout = 5000
} = {}) {
    const options = {
        method,
        headers: {
            'Content-Type': 'application/json',
            ...headers
        },
        body: body ? JSON.stringify(body) : null
    };
    
    return fetch(url, options)
        .then(response => response.json())
        .catch(error => console.error('请求失败:', error));
}

// 使用不同的配置
fetchApiData({
    url: '/api/users',
    method: 'POST',
    body: { name: '张三' },
    timeout: 10000
});

【代码注释】形参 { name, age = 18 } 在调用时自动从传入对象取值;可再嵌套、设默认值。配合剩余属性 ...rest 收集未列出字段。REST 接口 body 校验常与解构 + 默认值一起使用。

用法三:解构动态属性名

javascript 复制代码
// 【代码注释】左侧 `[propName]` 为计算属性名,运行时决定取哪个键;适合配置驱动、动态表单字段。右侧对象需存在对应键,否则为 `undefined`。与 `obj[key]` 访问等价,但一次绑定多个动态键时解构更清晰。
const user = {
    name: '小明',
    age: 20,
    'user-email': 'xiaoming@example.com',
    ['user_' + 'id']: 1001
};

const prop = 'name';
let { [prop]: userName } = user;
console.log(userName); // 小明

// 解构特殊属性名
let { 'user-email': email } = user;
console.log(email); // xiaoming@example.com

// 实际应用:动态解构配置
function getConfig(config, key) {
    const { [key]: value } = config;
    return value;
}

const appConfig = {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    debug: true
};

console.log(getConfig(appConfig, 'apiUrl')); // https://api.example.com
console.log(getConfig(appConfig, 'timeout')); // 5000

【代码注释】左侧 [propName] 为计算属性名,运行时决定取哪个键;适合配置驱动、动态表单字段。右侧对象需存在对应键,否则为 undefined。与 obj[key] 访问等价,但一次绑定多个动态键时解构更清晰。

用法四:对象解构的剩余属性

javascript 复制代码
// 【代码注释】`{ name, age, ...rest }` 把除 name/age 外属性浅拷贝到 rest(新对象)。用于剔除敏感字段、透传未知配置。注意:rest 必须是解构列表最后一个,且每轮解构只产生一层浅拷贝。
const user = {
    name: '小红',
    age: 22,
    city: '广州',
    profession: '设计师',
    hobby: '绘画'
};

let { name, age, ...restInfo } = user;
console.log(name, age); // 小红 22
console.log(restInfo); // { city: '广州', profession: '设计师', hobby: '绘画' }

// 实际应用:分离常用和不常用属性
function processUserUpdate(user, updates) {
    const { id, createdAt, ...updatableFields } = user;
    
    // 只更新允许修改的字段
    const updatedUser = {
        id,
        createdAt,
        ...updatableFields,
        ...updates
    };
    
    return updatedUser;
}

const originalUser = {
    id: 1,
    createdAt: '2023-01-01',
    name: '小李',
    email: 'xiaoli@example.com'
};

const updates = { name: '李四', email: 'lisi@example.com' };
console.log(processUserUpdate(originalUser, updates));

【代码注释】{ name, age, ...rest } 把除 name/age 外属性浅拷贝到 rest(新对象)。用于剔除敏感字段、透传未知配置。注意:rest 必须是解构列表最后一个,且每轮解构只产生一层浅拷贝。

3.2.3 对象解构的实际应用场景

场景一:模块导入

javascript 复制代码
// 【代码注释】命名导入 `import { debounce, throttle }` 只引入用到的绑定,利于 tree-shaking。重命名 `formatDate as fmt` 避免与本地函数冲突。默认导出与命名导出不要混用同一模块的多种风格,团队应统一规范。
// import { Component, useState, useEffect } from 'react';

// 实际应用:工具库导入
import { debounce, throttle, deepClone } from 'lodash-es';
import { formatCurrency, formatDate } from './formatters';

// 只导入需要的功能,减少bundle大小
function setupEventHandlers() {
    const handleClick = debounce((event) => {
        console.log('处理点击事件:', event);
    }, 300);
    
    const handleScroll = throttle((event) => {
        console.log('处理滚动事件:', event);
    }, 100);
    
    document.addEventListener('click', handleClick);
    window.addEventListener('scroll', handleScroll);
}

【代码注释】命名导入 import { debounce, throttle } 只引入用到的绑定,利于 tree-shaking。重命名 formatDate as fmt 避免与本地函数冲突。默认导出与命名导出不要混用同一模块的多种风格,团队应统一规范。

场景二:数据处理和转换

javascript 复制代码
// 【代码注释】`normalizeData` 按数据源 switch,分别解构不同字段名再映射为统一 `{name,email,age}`。这是 BFF/适配器层的典型写法:对外 API 形状不一,对内领域模型一致。
function transformApiResponse(apiUser) {
    const {
        id: userId,
        attributes: {
            name: fullName,
            'email-address': email,
            contact: {
                phone: phoneNumber
            }
        },
        meta: {
            created_at: joinedDate
        }
    } = apiUser;
    
    return {
        userId,
        fullName,
        email,
        phoneNumber,
        joinedDate,
        // 添加计算属性
        displayName: fullName.split(' ')[0],
        accountAge: calculateAge(joinedDate)
    };
}

// 实际应用:处理不同数据源的数据
function normalizeData(source, data) {
    switch (source) {
        case 'database':
            const { user_name, user_email, user_age } = data;
            return { name: user_name, email: user_email, age: user_age };
            
        case 'api':
            const { username, emailaddress, age: userage } = data;
            return { name: username, email: emailaddress, age: userage };
            
        case 'csv':
            const [name, email, age] = data.split(',');
            return { name, email, age: parseInt(age) };
            
        default:
            throw new Error('未知的数据源');
    }
}

【代码注释】normalizeData 按数据源 switch,分别解构不同字段名再映射为统一 {name,email,age}。这是 BFF/适配器层的典型写法:对外 API 形状不一,对内领域模型一致。

场景三:React组件开发

javascript 复制代码
// 【代码注释】props 解构 `{ user, onUpdate }`;`user` 再解构 `stats` 避免重复 `user.`。子组件只依赖所需字段,配合 PropTypes/TypeScript 接口更清晰。注意解构默认值对 `undefined` 生效,对 `null` 需用 `??`。
function UserProfile({ user, onUpdate, onDelete }) {
    const { name, email, avatar, stats } = user;
    const { followers, following, posts } = stats;
    
    const handleUpdate = (newData) => {
        onUpdate(user.id, newData);
    };
    
    const handleDelete = () => {
        onDelete(user.id);
    };
    
    return (
        <div className="user-profile">
            <img src={avatar} alt={name} />
            <h2>{name}</h2>
            <p>{email}</p>
            <div className="stats">
                <span>粉丝: {followers}</span>
                <span>关注: {following}</span>
                <span>帖子: {posts}</span>
            </div>
            <div className="actions">
                <button onClick={() => handleUpdate({ name })}>编辑</button>
                <button onClick={handleDelete}>删除</button>
            </div>
        </div>
    );
}

【代码注释】props 解构 { user, onUpdate }user 再解构 stats 避免重复 user.。子组件只依赖所需字段,配合 PropTypes/TypeScript 接口更清晰。注意解构默认值对 undefined 生效,对 null 需用 ??

3.2.3.1 for...of 与解构的黄金组合

for...of 内置了对迭代协议(Iterator Protocol)的支持,与解构配合是处理集合数据最常见的惯用写法。

javascript 复制代码
// 【代码注释】for...of + 数组解构:遍历二维数组(如矩阵、坐标集)
const points = [[1, 2], [3, 4], [5, 6]];
for (const [x, y] of points) {
    console.log(`(${x}, ${y})`); // (1, 2)  (3, 4)  (5, 6)
}

// 【代码注释】for...of + Object.entries():遍历对象键值对
const scores = { 语文: 92, 数学: 88, 英语: 95 };
for (const [subject, score] of Object.entries(scores)) {
    console.log(`${subject}: ${score}分`);
}
// 语文: 92分  数学: 88分  英语: 95分

// 【代码注释】for...of + Map:Map 天然支持 [key, value] 迭代
const userMap = new Map([
    ['alice', { age: 25, role: 'admin' }],
    ['bob',   { age: 30, role: 'user' }]
]);
for (const [username, { age, role }] of userMap) {
    console.log(`${username} (${age}岁) - ${role}`);
}

// 【代码注释】for...of + entries():同时获取索引和值
const fruits = ['苹果', '香蕉', '橙子'];
for (const [index, fruit] of fruits.entries()) {
    console.log(`${index + 1}. ${fruit}`);
}
// 1. 苹果  2. 香蕉  3. 橙子

// 【代码注释】实战:for...of 遍历对象数组 + 对象解构 + 内层 scores 数组解构,一次取出 id/name/三科成绩
const apiResponse = [
    { id: 1, name: '张三', scores: [90, 85, 92] },
    { id: 2, name: '李四', scores: [78, 88, 95] }
];

for (const { id, name, scores: [chinese, math, english] } of apiResponse) {
    const avg = ((chinese + math + english) / 3).toFixed(1);
    console.log(`ID:${id} ${name} 平均分:${avg}`);
}

【代码注释】for...of + 解构 是 ES6 最优雅的数据遍历组合:二维坐标for (const [x,y] of points)普通对象Object.entries() 得到 [key,value]Map 迭代器天然是键值对;需要下标fruits.entries()。API 列表示例展示「对象字段 + 嵌套数组」一次拆完,比手写 item.scores[0] 更清晰,且与 React/Vue 列表 map 解构参数同构。

3.2.4 混合解构(数组+对象)
javascript 复制代码
// 【代码注释】`[{ name: firstName }, , { name: thirdName }]` 跳过中间元素;`rows.map(({ id, data: [name, age, city] }) => ...)` 表格行内嵌数组字段一次拆完。列表项为「对象包数组」的接口(如 id + cells)时尤其常用。
const users = [
    { name: '张三', age: 25 },
    { name: '李四', age: 30 },
    { name: '王五', age: 35 }
];

let [{ name: firstName }, , { name: thirdName }] = users;
console.log(firstName, thirdName); // 张三 王五

// 实际应用:处理表格数据
function processTableData(rows) {
    return rows.map(({ id, data: [name, age, city] }) => ({
        id,
        name,
        age: parseInt(age),
        city
    }));
}

const tableRows = [
    { id: 1, data: ['张三', '25', '北京'] },
    { id: 2, data: ['李四', '30', '上海'] },
    { id: 3, data: ['王五', '35', '广州'] }
];

console.log(processTableData(tableRows));

【代码注释】[{ name: firstName }, , { name: thirdName }] 跳过中间元素;rows.map(({ id, data: [name, age, city] }) => ...) 表格行内嵌数组字段一次拆完。列表项为「对象包数组」的接口(如 id + cells)时尤其常用。

3.3 完整可运行示例:数组与对象解构

解构赋值核心规则(归纳):

  1. 右边是 可迭代结构 (数组)或 对象 ,左边是 对应的模式
  2. 数组按 索引位置 匹配;对象按 属性名 匹配
  3. 结构不一致时,多余项被忽略,缺少项为 undefined,可用 默认值 补齐
  4. 字符串、NodeListarguments伪数组 同样可解构
3.3.1 数组解构:声明、交换、传参、嵌套与伪数组
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>数组解构赋值演示</title>
</head>
<body>
  <div class="btns">
    <button>按钮01</button>
    <button>按钮02</button>
    <button>按钮03</button>
  </div>
  <script>
    // 【代码注释】声明时解构:右侧可混合任意类型,左侧按索引一一对应
    const num = Math.random();
    let [v1, v2, v3, v4, v5] = [100, 200, { name: '小乐', age: 100 }, function () { alert('ok'); }, num];

    const data = ['司马姥姥', '欧阳姥姥', '东方姥姥', '西门姥姥'];
    [v1, v2, v3, v4] = data; // 赋值语句左侧也可写解构模式(需先有 let/const 声明的变量)

    [v1, v2] = [v2, v1]; // 交换:右侧先求值成临时数组,再赋给左侧

    // 【代码注释】函数形参解构 = 调用时自动对实参数组解构
    function func([name1, name2, name3]) {
      console.log(name1 + '和' + name2 + '以及' + name3 + '是好朋友!');
    }
    func(['小明', '小刚', '小红']);
    func(data); // 实参元素不足时对应变量为 undefined

    let [a1, a2, a3] = [100, 200, 300, 400, 500]; // 右侧多余项被忽略
    [a1, a2, a3] = ['刘姥姥', '马姥姥'];           // 缺少第三项 → a3 为 undefined
    console.log(a1, a2, a3); // 刘姥姥 马姥姥 undefined

    const [c1, c2, c3 = 250] = [100]; // 仅 c1=100;c2 缺省为 undefined;c3 用默认值 250
    console.log(c1, c2, c3); // 100 undefined 250

    // 【代码注释】嵌套解构:模式形状须与右侧数组结构一致
    const arr = [100, ['高小乐', 199], [100, [10, 20]]];
    const [a, [b, c], [d, [e, f]]] = arr;

    // 【代码注释】伪数组:字符串按 UTF-16 码元、NodeList 按索引解构
    const msg = 'Hello 高小乐';
    const btns = document.querySelectorAll('.btns button');
    const [s1, s2, s3, s4] = msg;
    const [btn1, btn2, btn3] = btns;
    console.log(s1, s2, s3, s4, btn1, btn2, btn3);
  </script>
</body>
</html>

【代码注释】本页覆盖数组解构全流程:声明/赋值解构变量交换形参解构缺项与默认值嵌套模式字符串/NodeList 伪数组 。要点:右侧多余元素被丢弃;缺项为 undefined 才触发默认值;func(data) 只有 4 项时第三个形参为 undefined。与 3.1 节理论对照,建议逐段取消注释观察控制台输出。

3.3.2 对象解构:重命名、简写、默认值与「一切皆对象」
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>对象解构赋值演示</title>
</head>
<body>
  <script>
    // 【代码注释】重命名:{ name: username } 把属性 name 的值赋给变量 username
    let { name: username, address, num: age } = { name: '高小乐', address: [10, 20, 30, 40], num: { name: '老乐', age: 20 } };

    // 【代码注释】简写:属性名与变量名相同时可只写一次
    const { num01, num02, num03 } = { num01: 1000, num02: 2000, num03: 3000 };

    // 【代码注释】形参对象解构;未传入的属性为 undefined(如 length、duration)
    function func({ content, length, delay }) {
      console.log(content, length, delay);
    }
    func({ content: 'box', delay: 2000, duration: 3000 });

    // 【代码注释】默认值:n2 缺省用 250;n3 有值 300;n4 不存在 → undefined
    const { n1: n1, n2: n2 = 250, n3 = 350, n4 } = { n1: 100, n3: 300 };

    const obj = {
      email: 'xiaole@qq.com',
      nums: [100, 200],
      prop: { content: 'Hello ES6' }
    };
    // 【代码注释】混合:对象属性 + 嵌套数组解构 + 嵌套对象解构
    const { email, nums: [nu01, nu02], prop: { content } } = obj;

    // 【代码注释】「一切皆对象」:数组、字符串、Math 都按属性名解构
    const { length, push, map } = [10, 20, 30, 40, 50];       // length=5,拿到数组原型方法
    const { length: len, indexOf, forEach } = 'Hello 小乐';    // 字符串包装对象上的属性
    const { PI } = Math;
    console.log(username, num01, email, nu01, content, length, len, PI);
  </script>
</body>
</html>

【代码注释】对象解构按 属性名 匹配(与数组按索引不同)。{ name: username } 是重命名;{ num01 } 是简写;nums: [nu01, nu02] 在解构时继续拆嵌套数组。数组/字符串解构拿到的是对象自有或原型上的属性(如 lengthmap),这是理解「包装对象」的好例子。形参解构适合 REST 请求体、组件 props 配置对象。

3.3.3 经典场景:接口数据与列表渲染(模拟业务数据)
javascript 复制代码
// 【代码注释】业务场景:接口字段常为缩写(nm/sc/wish),map 回调形参直接解构,避免 movie.nm 重复
const movies = [
  { id: 1, nm: '灌篮高手', sc: 9.4, wish: 1155183, img: 'https://example.com/poster1.jpg' },
  { id: 2, nm: '超级马力欧兄弟大电影', sc: 9.3, wish: 53107, img: 'https://example.com/poster2.jpg' }
];

// 【代码注释】解构 + 模板字符串 + join:生成 HTML 片段;生产环境应对 img/nm 做 XSS 转义
const cards = movies.map(({ nm, sc, wish, img }) => `
  <article class="movie-card">
    <img src="${img}" alt="${nm}">
    <h3>${nm}</h3>
    <p>评分 ${sc} · 想看 ${wish}</p>
  </article>
`).join(''); // join('') 得到连续 HTML,可赋给 innerHTML 或框架 dangerouslySetInnerHTML

【代码注释】这是 Day01 解构 + 模板字符串 的综合实战:({ nm, sc, wish, img }) 只取展示所需字段,省略 id 等不参与渲染的属性。与 Vue v-for、React list.map(({ title }) => ...) 同构;表格行、下拉 options、卡片列表均可用此模式,配合可选链 movie?.img ?? placeholder 更安全。
#mermaid-svg-jQc9zeocC9ENF7zw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jQc9zeocC9ENF7zw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jQc9zeocC9ENF7zw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jQc9zeocC9ENF7zw .error-icon{fill:#552222;}#mermaid-svg-jQc9zeocC9ENF7zw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jQc9zeocC9ENF7zw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jQc9zeocC9ENF7zw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jQc9zeocC9ENF7zw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jQc9zeocC9ENF7zw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jQc9zeocC9ENF7zw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jQc9zeocC9ENF7zw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jQc9zeocC9ENF7zw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jQc9zeocC9ENF7zw .marker.cross{stroke:#333333;}#mermaid-svg-jQc9zeocC9ENF7zw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jQc9zeocC9ENF7zw p{margin:0;}#mermaid-svg-jQc9zeocC9ENF7zw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jQc9zeocC9ENF7zw .cluster-label text{fill:#333;}#mermaid-svg-jQc9zeocC9ENF7zw .cluster-label span{color:#333;}#mermaid-svg-jQc9zeocC9ENF7zw .cluster-label span p{background-color:transparent;}#mermaid-svg-jQc9zeocC9ENF7zw .label text,#mermaid-svg-jQc9zeocC9ENF7zw span{fill:#333;color:#333;}#mermaid-svg-jQc9zeocC9ENF7zw .node rect,#mermaid-svg-jQc9zeocC9ENF7zw .node circle,#mermaid-svg-jQc9zeocC9ENF7zw .node ellipse,#mermaid-svg-jQc9zeocC9ENF7zw .node polygon,#mermaid-svg-jQc9zeocC9ENF7zw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jQc9zeocC9ENF7zw .rough-node .label text,#mermaid-svg-jQc9zeocC9ENF7zw .node .label text,#mermaid-svg-jQc9zeocC9ENF7zw .image-shape .label,#mermaid-svg-jQc9zeocC9ENF7zw .icon-shape .label{text-anchor:middle;}#mermaid-svg-jQc9zeocC9ENF7zw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jQc9zeocC9ENF7zw .rough-node .label,#mermaid-svg-jQc9zeocC9ENF7zw .node .label,#mermaid-svg-jQc9zeocC9ENF7zw .image-shape .label,#mermaid-svg-jQc9zeocC9ENF7zw .icon-shape .label{text-align:center;}#mermaid-svg-jQc9zeocC9ENF7zw .node.clickable{cursor:pointer;}#mermaid-svg-jQc9zeocC9ENF7zw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jQc9zeocC9ENF7zw .arrowheadPath{fill:#333333;}#mermaid-svg-jQc9zeocC9ENF7zw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jQc9zeocC9ENF7zw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jQc9zeocC9ENF7zw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jQc9zeocC9ENF7zw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jQc9zeocC9ENF7zw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jQc9zeocC9ENF7zw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jQc9zeocC9ENF7zw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jQc9zeocC9ENF7zw .cluster text{fill:#333;}#mermaid-svg-jQc9zeocC9ENF7zw .cluster span{color:#333;}#mermaid-svg-jQc9zeocC9ENF7zw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jQc9zeocC9ENF7zw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jQc9zeocC9ENF7zw rect.text{fill:none;stroke-width:0;}#mermaid-svg-jQc9zeocC9ENF7zw .icon-shape,#mermaid-svg-jQc9zeocC9ENF7zw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jQc9zeocC9ENF7zw .icon-shape p,#mermaid-svg-jQc9zeocC9ENF7zw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jQc9zeocC9ENF7zw .icon-shape .label rect,#mermaid-svg-jQc9zeocC9ENF7zw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jQc9zeocC9ENF7zw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jQc9zeocC9ENF7zw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jQc9zeocC9ENF7zw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数组/可迭代
对象
右侧数据源
类型?
按索引解构
按属性名解构
变量赋值 / 函数形参 / 交换
重命名 / 默认值 / 嵌套
渲染 / 配置 / 算法

【代码注释】流程图:右侧数据源 → 判数组/对象 → 选索引或属性名模式 → 赋给变量/形参 → 用于渲染或配置。与 3.3 节 HTML 示例、影片列表 map 解构同源,是 Day01 最高频实战模式之一。

4. 字符串新增特性:更强大的文本处理

4.1 模板字符串

4.1.1 什么是模板字符串

名词解析:

  • 模板字符串(Template String):使用反引号(`)包围的字符串字面量
  • 占位符(Placeholder):${}语法,用于插入表达式
  • 标签模板(Tagged Template):函数后紧跟模板字符串,用于高级字符串处理
4.1.2 模板字符串的基本特性

渲染错误: Mermaid 渲染失败: Parse error on line 8: ...+n] C --> C1使用${}语法 D --> D1[支 ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'DIAMOND_START'

【代码注释】模板字符串支持多行文本、${} 插值与标签模板,是替代字符串 + 拼接的首选写法。

4.1.3 模板字符串的具体用法

用法一:多行字符串

javascript 复制代码
// 【代码注释】反引号内换行会保留在字符串中(含换行符),无需 `\n` 与 `+` 拼接。`createCard`/`generateEmailTemplate` 展示 HTML/邮件场景:注意插入变量仍需转义防 XSS,或配合 `safeHtml` 标签模板。
// 传统方式(需要使用+n)
const oldWay = '第一行\n' +
               '第二行\n' +
               '第三行';

// 使用模板字符串
const newWay = `第一行
第二行
第三行`;

console.log(oldWay);
console.log(newWay);

// 实际应用:HTML模板
function createCard(title, content, author) {
    return `
        <div class="card">
            <h2 class="card-title">${title}</h2>
            <div class="card-content">
                ${content}
            </div>
            <div class="card-author">
                作者:${author}
            </div>
        </div>
    `;
}

// 生成邮件模板
function generateEmailTemplate(userName, verificationCode) {
    return `
        <div style="font-family: Arial, sans-serif;">
            <h2>欢迎注册我们的服务</h2>
            <p>亲爱的 ${userName}:</p>
            <p>您的验证码是:<strong>${verificationCode}</strong></p>
            <p>该验证码将在10分钟后失效。</p>
            <p>如果这不是您本人的操作,请忽略此邮件。</p>
        </div>
    `;
}

【代码注释】反引号内换行会保留在字符串中(含换行符),无需 \n+ 拼接。createCard/generateEmailTemplate 展示 HTML/邮件场景:注意插入变量仍需转义防 XSS,或配合 safeHtml 标签模板。

用法二:变量和表达式插值

javascript 复制代码
// 【代码注释】`${}` 内可为任意表达式:算术、`getGreeting()`、三元运算。`buildUserQuery` 动态拼 SQL 时务必用参数化查询防注入,模板字符串只负责拼接结构。`buildUrl` 用 `encodeURIComponent` 编码查询参数是正确示范。
const name = '张三';
const age = 25;
const city = '北京';

// 变量插值
const message = `你好,我是${name},今年${age}岁,来自${city}`;
console.log(message); // 你好,我是张三,今年25岁,来自北京

// 表达式插值
const nextYear = `明年我将${age + 1}岁`;
console.log(nextYear); // 明年我将26岁

// 函数调用插值
function getGreeting(time) {
    const hour = new Date(time).getHours();
    if (hour < 12) return '上午好';
    if (hour < 18) return '下午好';
    return '晚上好';
}

const greeting = `${getGreeting(Date.now())},${name}!`;
console.log(greeting); // 根据当前时间显示不同的问候

// 实际应用:动态SQL查询构建
function buildUserQuery(filters) {
    const { name, minAge, maxAge, city } = filters;
    let conditions = [];
    let params = [];
    
    if (name) {
        conditions.push('name LIKE ?');
        params.push(`%${name}%`);
    }
    
    if (minAge) {
        conditions.push('age >= ?');
        params.push(minAge);
    }
    
    if (maxAge) {
        conditions.push('age <= ?');
        params.push(maxAge);
    }
    
    if (city) {
        conditions.push('city = ?');
        params.push(city);
    }
    
    const whereClause = conditions.length > 0 
        ? `WHERE ${conditions.join(' AND ')}` 
        : '';
    
    return `SELECT * FROM users ${whereClause}`;
}

// 构建动态URL
function buildUrl(baseUrl, path, params) {
    const queryString = Object.keys(params)
        .map(key => `${key}=${encodeURIComponent(params[key])}`)
        .join('&');
    
    return `${baseUrl}/${path}?${queryString}`;
}

const url = buildUrl('https://api.example.com', 'users', {
    page: 1,
    limit: 20,
    sort: 'name'
});
console.log(url);

【代码注释】${} 内可为任意表达式:算术、getGreeting()、三元运算。buildUserQuery 动态拼 SQL 时务必用参数化查询防注入,模板字符串只负责拼接结构。buildUrlencodeURIComponent 编码查询参数是正确示范。

用法三:标签模板

javascript 复制代码
// 【代码注释】标签函数接收 `(strings, ...values)`:strings 为字面量片段数组,values 为插值求值结果。`highlight` 演示自定义 HTML 包装;`safeHtml` 对插值转义防 XSS。`String.raw` 为内置标签,保留反斜杠不转义(见字符串章节)。
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        const value = values[i] ? `<strong>${values[i]}</strong>` : '';
        return result + str + value;
    }, '');
}

const name = '张三';
const age = 25;
const highlighted = highlight`用户${name},年龄${age}岁`;
console.log(highlighted); // 用户<strong>张三</strong>,年龄<strong>25</strong>岁

// 实际应用:HTML转义防止XSS攻击
function safeHtml(strings, ...values) {
    return strings.reduce((result, str, i) => {
        const value = values[i] != null 
            ? String(values[i])
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#039;')
            : '';
        return result + str + value;
    }, '');
}

const userInput = '<script>alert("XSS")</script>';
const safeContent = safeHtml`用户输入:${userInput}`;
console.log(safeContent); // 用户输入:&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// 实际应用:国际化翻译
function i18n(strings, ...values) {
    const translationKey = strings.join('${}');
    const template = translations[translationKey] || strings.join('${}');
    
    return values.reduce((result, value, i) => {
        return result.replace(`\${${i}}`, value);
    }, template);
}

const translations = {
    'Welcome, ${}': '欢迎,${}',
    'You have ${} new messages': '您有 ${} 条新消息'
};

const message1 = i18n`Welcome, ${userName}`;
const message2 = i18n`You have ${5} new messages`;

【代码注释】标签函数接收 (strings, ...values):strings 为字面量片段数组,values 为插值求值结果。highlight 演示自定义 HTML 包装;safeHtml 对插值转义防 XSS。String.raw 为内置标签,保留反斜杠不转义(见字符串章节)。

用法四:业界知名库的标签模板用法

标签模板不仅是语言特性,更是许多主流库的核心 DSL 设计基础。

javascript 复制代码
// ──────────────────────────────────────────────
// 【代码注释】styled-components(React CSS-in-JS 库)
// 标签模板让 CSS 写法与组件绑定,插值可接受 props 动态计算样式
// ──────────────────────────────────────────────
import styled from 'styled-components';

// styled.div 是标签函数,接收 CSS 模板 + props 插值
const Button = styled.button`
    background: ${props => props.primary ? '#0066cc' : '#fff'};
    color: ${props => props.primary ? '#fff' : '#0066cc'};
    font-size: 1em;
    padding: 0.5em 1.5em;
    border: 2px solid #0066cc;
    border-radius: 4px;
    cursor: pointer;

    &:hover {
        opacity: 0.8;
    }
`;

// 使用:<Button primary>主要按钮</Button>
// 使用:<Button>次要按钮</Button>

// 【代码注释】动态主题:插值可引用全局 theme 对象
const Card = styled.div`
    background: ${({ theme }) => theme.colors.surface};
    color: ${({ theme }) => theme.colors.onSurface};
    border-radius: ${({ theme }) => theme.borderRadius.md};
    padding: ${({ theme }) => theme.spacing[4]};
    box-shadow: ${({ elevation = 1 }) => `0 ${elevation * 2}px ${elevation * 4}px rgba(0,0,0,.1)`};
`;

// ──────────────────────────────────────────────
// 【代码注释】graphql-tag(GQL 查询语言标签)
// gql`` 标签函数将字符串解析为 AST Document 对象,供 Apollo Client 使用
// ──────────────────────────────────────────────
import { gql } from '@apollo/client';

// gql 标签将 GraphQL 查询字符串转换为 DocumentNode AST
const GET_USER = gql`
    query GetUser($id: ID!) {
        user(id: $id) {
            id
            name
            email
            profile {
                avatar
                bio
            }
        }
    }
`;

// 片段复用:gql 标签支持 ${FRAGMENT} 插值组合片段
const USER_FIELDS = gql`
    fragment UserFields on User {
        id
        name
        email
    }
`;

const GET_USERS = gql`
    query GetUsers {
        users {
            ...UserFields
            createdAt
        }
    }
    ${USER_FIELDS}
`;

// ──────────────────────────────────────────────
// 【代码注释】lit-html(Web Components 轻量模板引擎)
// html`` 标签解析出静态部分与动态插值,首次渲染后仅更新变化的节点
// ──────────────────────────────────────────────
import { html, render } from 'lit';

// html`` 返回 TemplateResult,仅在插值变化时局部 DOM 更新(高效)
function userCard(user) {
    return html`
        <div class="card">
            <img src="${user.avatar}" alt="${user.name}">
            <h2>${user.name}</h2>
            <p>${user.bio ?? '暂无简介'}</p>
            <button @click="${() => editUser(user.id)}">编辑</button>
        </div>
    `;
}

render(userCard(currentUser), document.getElementById('app'));

// ──────────────────────────────────────────────
// 【代码注释】sql 标签函数(防 SQL 注入的参数化查询,如 postgres 库)
// 标签函数将插值提取为参数数组,驱动层自动用占位符替换
// ──────────────────────────────────────────────
import { sql } from 'postgres';

// sql`` 自动将插值提取为参数,生成参数化查询,防止 SQL 注入
const userId = req.params.id;
const result = await sql`
    SELECT id, name, email
    FROM users
    WHERE id = ${userId}          -- 插值被安全地参数化为 $1
      AND status = ${'active'}    -- 不拼字符串,防注入
    LIMIT 1
`;

【代码注释】标签模板在业界的核心价值:将特定领域语法(DSL)嵌入 JavaScript ,保留 IDE 高亮/补全的同时支持动态插值。四种经典库的使用模式:styled-components 动态 CSS;graphql-tag 解析查询 AST;lit-html 高效 DOM 更新;postgres sql 参数化防注入。掌握标签模板原理,理解这些库时会豁然开朗。

4.1.4 模板字符串的实际应用场景

场景一:前端组件开发

javascript 复制代码
// 【代码注释】类名、内联 style、aria 文案可用模板字符串动态生成;注意 React 中 `className` 与 `dangerouslySetInnerHTML` 的安全边界。条件 class 更推荐 `clsx`/`classnames` 库,模板字符串适合简单拼接。
function ProductCard({ product }) {
    const { name, price, description, image, rating } = product;
    const stars = '★'.repeat(Math.floor(rating)) + '☆'.repeat(5 - Math.floor(rating));
    
    return `
        <div class="product-card">
            <img src="${image}" alt="${name}" class="product-image">
            <div class="product-info">
                <h3 class="product-name">${name}</h3>
                <div class="product-rating">${stars}</div>
                <p class="product-price">¥${price.toFixed(2)}</p>
                <p class="product-description">${description}</p>
                <button class="add-to-cart" data-id="${product.id}">
                    添加到购物车
                </button>
            </div>
        </div>
    `;
}

// 渲染商品列表
function renderProductList(products) {
    return `
        <div class="product-grid">
            ${products.map(product => ProductCard({ product })).join('')}
        </div>
    `;
}

【代码注释】类名、内联 style、aria 文案可用模板字符串动态生成;注意 React 中 classNamedangerouslySetInnerHTML 的安全边界。条件 class 更推荐 clsx/classnames 库,模板字符串适合简单拼接。

场景二:日志记录

javascript 复制代码
// 【代码注释】日志行用 `${level} ${timestamp} ${message}` 统一格式,便于 grep/日志平台解析。生产环境应对对象字段 `JSON.stringify` 并控制长度,避免循环引用抛错。
class Logger {
    constructor(context) {
        this.context = context;
    }
    
    log(level, message, data = {}) {
        const timestamp = new Date().toISOString();
        const logEntry = {
            timestamp,
            level,
            context: this.context,
            message,
            data
        };
        
        // 根据级别格式化输出
        const formattedMessage = `
[${timestamp}] [${level}] [${this.context}]
${message}
${Object.keys(data).length > 0 ? JSON.stringify(data, null, 2) : ''}
        `.trim();
        
        console.log(formattedMessage);
        
        // 在生产环境中可以发送到日志服务
        if (level === 'ERROR') {
            this.sendToLogService(logEntry);
        }
    }
    
    sendToLogService(logEntry) {
        // 实现日志服务发送逻辑
        fetch('/api/logs', {
            method: 'POST',
            body: JSON.stringify(logEntry)
        }).catch(error => console.error('日志发送失败:', error));
    }
}

// 使用示例
const userLogger = new Logger('UserService');
userLogger.log('INFO', '用户登录成功', { userId: 123, ip: '192.168.1.1' });

【代码注释】日志行用 ${level} ${timestamp} ${message} 统一格式,便于 grep/日志平台解析。生产环境应对对象字段 JSON.stringify 并控制长度,避免循环引用抛错。

场景三:代码生成

javascript 复制代码
// 【代码注释】代码生成/脚手架用模板输出多行文件内容;注意缩进与 `${}` 转义。生成的代码应再经 Prettier/ESLint;勿把用户输入直接拼进生成代码,防止注入。
function generateComponentCode(componentName, props) {
    return `
import React from 'react';

interface ${componentName}Props {
    ${props.map(prop => `    ${prop.name}: ${prop.type};`).join('\n    ')}

}

export const ${componentName}: React.FC<${componentName}Props> = ({
    ${props.map(prop => prop.name).join(', ')}
}) => {
    return (
        <div className="${componentName.toLowerCase()}">
            {/* 组件内容 */}
        </div>
    );
};

export default ${componentName};
    `.trim();
}

const componentSpec = {
    name: 'UserProfile',
    props: [
        { name: 'userId', type: 'number' },
        { name: 'userName', type: 'string' },
        { name: 'showAvatar', type: 'boolean' }
    ]
};

console.log(generateComponentCode(componentSpec.name, componentSpec.props));

【代码注释】代码生成/脚手架用模板输出多行文件内容;注意缩进与 ${} 转义。生成的代码应再经 Prettier/ESLint;勿把用户输入直接拼进生成代码,防止注入。

4.2 字符串实例新增方法

4.2.1 字符串查找方法

方法一:includes()

javascript 复制代码
// 【代码注释】`includes` 返回 boolean,语义优于 `indexOf !== -1`;第二参数 `position` 从该下标开始搜索。区分大小写;需忽略大小写先 `toLowerCase()` 再比。空字符串 `includes('')` 为 true。
const sentence = 'The quick brown fox jumps over the lazy dog';

// 基本用法
console.log(sentence.includes('fox')); // true
console.log(sentence.includes('cat')); // false

// 指定起始位置
console.log(sentence.includes('fox', 20)); // false,从第20个字符开始查找

// 实际应用:搜索功能
function searchUsers(users, searchTerm) {
    const term = searchTerm.toLowerCase();
    return users.filter(user => 
        user.name.toLowerCase().includes(term) ||
        user.email.toLowerCase().includes(term) ||
        (user.bio && user.bio.toLowerCase().includes(term))
    );
}

const users = [
    { name: '张三', email: 'zhangsan@example.com', bio: '前端工程师' },
    { name: '李四', email: 'lisi@example.com', bio: '后端工程师' },
    { name: '王五', email: 'wangwu@example.com', bio: '全栈工程师' }
];

console.log(searchUsers(users, '前端'));

【代码注释】includes 返回 boolean,语义优于 indexOf !== -1;第二参数 position 从该下标开始搜索。区分大小写;需忽略大小写先 toLowerCase() 再比。空字符串 includes('') 为 true。

方法二:startsWith()

javascript 复制代码
// 【代码注释】`startsWith(search, position)` 从 position 起判断前缀,适合路由、MIME、协议判断。比 `slice(0,n)===prefix` 可读且略省边界计算。
const url = 'https://www.example.com/path';

// 基本用法
console.log(url.startsWith('https://')); // true
console.log(url.startsWith('http://')); // false

// 指定起始位置
console.log(url.startsWith('www.', 8)); // true

// 实际应用:URL处理
function processUrl(url) {
    if (url.startsWith('https://')) {
        return { secure: true, protocol: 'https', path: url.slice(8) };
    } else if (url.startsWith('http://')) {
        return { secure: false, protocol: 'http', path: url.slice(7) };
    } else if (url.startsWith('/')) {
        return { secure: null, protocol: 'relative', path: url.slice(1) };
    } else {
        return { secure: null, protocol: 'unknown', path: url };
    }
}

// 文件类型检查
function checkFileType(filename) {
    if (filename.startsWith('image_')) return 'image';
    if (filename.startsWith('document_')) return 'document';
    if (filename.startsWith('video_')) return 'video';
    return 'unknown';
}

【代码注释】startsWith(search, position) 从 position 起判断前缀,适合路由、MIME、协议判断。比 slice(0,n)===prefix 可读且略省边界计算。

方法三:endsWith()

javascript 复制代码
// 【代码注释】`endsWith` 第二参数为「结束位置」前的子串判断,可用于截取前 n 个字符再判后缀。文件扩展名更稳妥用 `lastIndexOf('.')` + slice,避免 `endsWith('.json')` 误匹配 `a.json.bak`。
const filename = 'document.pdf';

// 基本用法
console.log(filename.endsWith('.pdf')); // true
console.log(filename.endsWith('.doc')); // false

// 指定长度
console.log(filename.endsWith('ment.pdf', 12)); // true

// 实际应用:文件类型检测
function getFileType(filename) {
    const extensions = {
        '.jpg': 'image',
        '.jpeg': 'image',
        '.png': 'image',
        '.gif': 'image',
        '.pdf': 'document',
        '.doc': 'document',
        '.docx': 'document',
        '.txt': 'text',
        '.mp4': 'video',
        '.mp3': 'audio'
    };
    
    for (const [ext, type] of Object.entries(extensions)) {
        if (filename.toLowerCase().endsWith(ext)) {
            return type;
        }
    }
    
    return 'unknown';
}

// 验证域名
function isValidDomain(domain) {
    return domain.endsWith('.com') ||
           domain.endsWith('.org') ||
           domain.endsWith('.net') ||
           domain.endsWith('.cn');
}

【代码注释】endsWith 第二参数为「结束位置」前的子串判断,可用于截取前 n 个字符再判后缀。文件扩展名更稳妥用 lastIndexOf('.') + slice,避免 endsWith('.json') 误匹配 a.json.bak

4.2.2 字符串重复和填充方法

方法一:repeat()

javascript 复制代码
// 【代码注释】`repeat(n)` 要求 n 为非负整数且有限;`n` 过大可能抛 RangeError 或占内存。用于生成分隔线、缩进占位;负数和 `Infinity` 非法。
const star = '*';
console.log(star.repeat(5)); // *****

// 实际应用:创建分隔线
function createSeparator(char = '-', length = 50) {
    return char.repeat(length);
}

console.log(createSeparator()); // --------------------------------------------------
console.log(createSeparator('=', 30)); // ==============================

// 创建缩进
function createIndent(level, spacesPerLevel = 4) {
    return ' '.repeat(level * spacesPerLevel);
}

function formatJson(obj, indent = 0) {
    const indentStr = createIndent(indent);
    let result = '{\n';
    
    for (const [key, value] of Object.entries(obj)) {
        result += `${indentStr}    "${key}": `;
        
        if (typeof value === 'object' && value !== null) {
            result += formatJson(value, indent + 1);
        } else {
            result += JSON.stringify(value);
        }
        
        result += ',\n';
    }
    
    result += `${indentStr}}`;
    return result;
}

// 创建进度条
function createProgressBar(current, total, width = 20) {
    const percentage = Math.floor((current / total) * 100);
    const filled = Math.floor((current / total) * width);
    const empty = width - filled;
    
    return `[${'='.repeat(filled)}${' '.repeat(empty)}] ${percentage}%`;
}

console.log(createProgressBar(15, 20)); // [=================== ] 75%

【代码注释】repeat(n) 要求 n 为非负整数且有限;n 过大可能抛 RangeError 或占内存。用于生成分隔线、缩进占位;负数和 Infinity 非法。

方法二:padStart()和padEnd()

javascript 复制代码
// 【代码注释】`padStart(targetLength, padString)` 从左侧填充至目标长度,常用于订单号左补零;`padEnd` 右对齐。默认填充空格;`padString` 过长会截断重复模式。不改变原字符串,返回新串。
// padStart:从开头填充
console.log('5'.padStart(2, '0')); // '05'
console.log('123'.padStart(5, '0')); // '00123'

// padEnd:从结尾填充
console.log('ID:'.padEnd(10, '-')); // 'ID:-------'
console.log('5'.padEnd(2, '0')); // '50'

// 实际应用:格式化数字
function formatNumber(num, digits = 2) {
    return String(num).padStart(digits, '0');
}

function formatTime(hours, minutes, seconds) {
    return `${formatNumber(hours)}:${formatNumber(minutes)}:${formatNumber(seconds)}`;
}

console.log(formatTime(9, 5, 3)); // '09:05:03'
console.log(formatTime(12, 30, 45)); // '12:30:45'

// 格式化表格数据
function formatTableRow(data, widths) {
    return data.map((item, index) => {
        const width = widths[index];
        if (index === data.length - 1) {
            return item.padEnd(width);
        }
        return item.padEnd(width);
    }).join(' | ');
}

const tableData = [
    ['姓名', '年龄', '城市'],
    ['张三', '25', '北京'],
    ['李四', '30', '上海'],
    ['王五', '28', '广州']
];

const columnWidths = [10, 6, 10];
console.log(tableData.map(row => formatTableRow(row, columnWidths)).join('\n'));

// 生成固定宽度的日志
function formatLogMessage(level, message, timestamp) {
    const timestampStr = timestamp.toISOString();
    return `${level.padEnd(5)} | ${timestampStr.padEnd(24)} | ${message}`;
}

console.log(formatLogMessage('INFO', '用户登录成功', new Date()));
console.log(formatLogMessage('ERROR', '数据库连接失败', new Date()));

【代码注释】padStart(targetLength, padString) 从左侧填充至目标长度,常用于订单号左补零;padEnd 右对齐。默认填充空格;padString 过长会截断重复模式。不改变原字符串,返回新串。

4.2.3 字符串修剪方法

方法:trim()、trimStart()、trimEnd()

javascript 复制代码
// 【代码注释】`trim` 去除首尾 Unicode 空白(含全角空格等);`trimStart`/`trimEnd` 单侧。表单提交前必做;中间空格保留。与 `replace(/^\s+|\s+$/g)` 相比语义更明确。
const messyString = '   Hello World   ';

console.log(messyString.trim()); // 'Hello World'
console.log(messyString.trimStart()); // 'Hello World   '
console.log(messyString.trimEnd()); // '   Hello World'

// 实际应用:表单数据处理
function processFormData(formData) {
    const processed = {};
    
    for (const [key, value] of Object.entries(formData)) {
        if (typeof value === 'string') {
            processed[key] = value.trim();
        } else {
            processed[key] = value;
        }
    }
    
    return processed;
}

const userInput = {
    username: '  zhangsan  ',
    email: ' zhangsan@example.com ',
    age: 25
};

console.log(processFormData(userInput));

// 处理用户输入的命令
function parseCommand(command) {
    const trimmed = command.trim();
    const parts = trimmed.split(/\s+/);
    
    return {
        command: parts[0],
        args: parts.slice(1).map(arg => arg.trim())
    };
}

console.log(parseCommand('  search   javascript tutorial  '));

【代码注释】trim 去除首尾 Unicode 空白(含全角空格等);trimStart/trimEnd 单侧。表单提交前必做;中间空格保留。与 replace(/^\s+|\s+$/g) 相比语义更明确。

4.2.4 字符串替换方法

方法:replaceAll()

javascript 复制代码
// 【代码注释】ES2021 `replaceAll` 全局替换,无需 `/g` 正则;`replace` 仅替换首个匹配。正则作为第一参数时仍需 `g` 标志。敏感词过滤、模板占位符批量替换首选 `replaceAll`。
const text = 'Hello World, Hello Universe, Hello Everyone';

// 使用replace(只替换第一个)
console.log(text.replace('Hello', 'Hi')); // 'Hi World, Hello Universe, Hello Everyone'

// 使用replaceAll(替换所有)
console.log(text.replaceAll('Hello', 'Hi')); // 'Hi World, Hi Universe, Hi Everyone'

// 实际应用:模板替换
function processTemplate(template, data) {
    let result = template;
    
    for (const [key, value] of Object.entries(data)) {
        const placeholder = `{{${key}}}`;
        result = result.replaceAll(placeholder, value);
    }
    
    return result;
}

const emailTemplate = `
亲爱的 {{name}}:

感谢您注册我们的服务!

您的账号信息:
- 用户名:{{username}}
- 邮箱:{{email}}
- 注册日期:{{date}}

祝您使用愉快!
`;

const userData = {
    name: '张三',
    username: 'zhangsan',
    email: 'zhangsan@example.com',
    date: '2025-01-01'
};

console.log(processTemplate(emailTemplate, userData));

// 清理用户输入
function sanitizeInput(input) {
    return input
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll("'", '&#039;');
}

【代码注释】ES2021 replaceAll 全局替换,无需 /g 正则;replace 仅替换首个匹配。正则作为第一参数时仍需 g 标志。敏感词过滤、模板占位符批量替换首选 replaceAll

4.2.4.1 String.at() 与 String.raw ------ ES2021/ES2022 补充

方法:at()(ES2022)

javascript 复制代码
// 【代码注释】at() 支持负索引,解决 str[str.length - 1] 的冗长写法
const str = 'Hello, World!';

// 传统写法
console.log(str[str.length - 1]);    // '!'
console.log(str[str.length - 2]);    // 'd'

// ES2022 at() 写法(支持负索引)
console.log(str.at(-1));   // '!'   ← 最后一个字符
console.log(str.at(-2));   // 'd'   ← 倒数第二个字符
console.log(str.at(0));    // 'H'   ← 正数索引与 str[0] 相同

// 实际应用:文件扩展名提取(无需 split('.').pop())
function getExtension(filename) {
    const dotIndex = filename.lastIndexOf('.');
    if (dotIndex === -1) return '';
    return filename.slice(dotIndex + 1).toLowerCase();
}

// 用 at() 判断路径分隔符
function isAbsolutePath(path) {
    return path.at(0) === '/' || path.at(1) === ':'; // Unix 或 Windows
}

// 实际应用:验证码末位校验
function maskPhone(phone) {
    return phone.slice(0, 3) + '****' + phone.at(-4) + phone.at(-3) + phone.at(-2) + phone.at(-1);
}
console.log(maskPhone('13812345678')); // '138****5678'

【代码注释】at()[] 最大区别是支持负数索引,消除了 arr[arr.length - 1] 的冗余。数组也同样支持 Array.prototype.at(-1)

标签函数:String.raw

javascript 复制代码
// 【代码注释】String.raw 是内置标签函数,返回原始字符串(不处理转义序列)
// 用途:Windows 路径、正则字符串、LaTeX 公式等含大量 \ 的场景
console.log(String.raw`C:\Users\admin\Documents\file.txt`);
// 输出:C:\Users\admin\Documents\file.txt(\U \a \D \f 均不被解释为转义)

// 对比普通模板字符串
console.log(`C:\Users\admin`);  // C:\Users\admin (但 \U \a 会被尝试解析)
console.log(`\n`);               // 换行符(1个字符)
console.log(String.raw`\n`);    // '\n' 字面量(2个字符:\ 和 n)

// 实际应用:动态构建正则表达式字符串
function buildRegexStr(pattern) {
    return String.raw`^\d{${pattern}}-[A-Z]+$`;
}
console.log(buildRegexStr(4)); // ^\d{4}-[A-Z]+$

// 实际应用:生成 CSS 内容中的特殊字符
const cssContent = String.raw`
    .icon::before {
        content: '\e001';  /* 图标字体编码,\ 不被转义 */
        font-family: 'IconFont';
    }
`;

【代码注释】String.raw 是最常用的内置标签函数,适合 Windows 路径(避免手写 \\)、正则字符串字面量、CSS content 属性中的 Unicode 转义码。

相关推荐
weixin_BYSJ19871 小时前
基于Django的非物质文化遗产管理系统设计与实现(源码 + 文档)98950
java·javascript·spring boot·python·django·flask·php
এ慕ོ冬℘゜1 小时前
从零封装企业级通用确认弹窗组件|高复用、低耦合、适配全场景
开发语言·前端·javascript
Yvonne爱编码1 小时前
数据库---Day10 索引
数据库·sql·mysql
流星白龙1 小时前
【MySQL高阶】8.MySQL系统库
android·mysql·adb
weixin_BYSJ19871 小时前
springboot鹿邑县旅游网站99312(源码+文档)
java·javascript·spring boot·python·django·flask·php
晓得迷路了2 小时前
栗子前端技术周刊第 131 期 - pnpm 11.3、npm 11.16.0、Astro 6.4...
前端·javascript·css
我是一颗柠檬10 小时前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
身如柳絮随风扬10 小时前
数据库读写分离:从原理到实战,构建高并发系统
数据库·mysql
swipe11 小时前
DeepAgents 实战:用多 Agent 架构搭一个深度调研助手
javascript·面试·llm