本文系统讲解 Day01 核心内容:
let/const、块级作用域、解构赋值、模板字符串、字符串与数值新特性。理论结合 MDN 与 ECMA-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 的前置基础
在掌握 let 与 const 之前,需要理解 严格模式(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;
}
【代码注释】提升语义 :notDeclared 用 typeof 得 "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 提升时初始化为 undefined;let/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,因共享同一 i;for (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,所有按钮都会显示最后一个索引
});
});
【代码注释】forEach 里 let 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 仍提升到外层函数。switch 各 case 建议加 {} 避免多个 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 循环里的 i、squared 在循环块结束即销毁,函数末尾访问会 ReferenceError。对比 var 版 i 会泄漏到函数级。写算法时应用 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
}
【代码注释】try 内 riskyVariable 在 catch 中不可见;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
}
【代码注释】用无名称的 {} 块把 config 与 userState 隔离开,初始化结束后块外无法访问,降低全局/函数级变量名冲突。适合初始化脚本、一次性 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 === undefined 而 window.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>
【代码注释】const 与 let 共享块级作用域、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...of 或 Array.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} 为属性名与变量名相同的简写。不存在属性为 undefined,email = '未设置' 提供默认值。与数组解构「按位置」对比记忆。
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 完整可运行示例:数组与对象解构
解构赋值核心规则(归纳):
- 右边是 可迭代结构 (数组)或 对象 ,左边是 对应的模式
- 数组按 索引位置 匹配;对象按 属性名 匹配
- 结构不一致时,多余项被忽略,缺少项为
undefined,可用 默认值 补齐 - 字符串、
NodeList、arguments等 伪数组 同样可解构
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] 在解构时继续拆嵌套数组。数组/字符串解构拿到的是对象自有或原型上的属性(如 length、map),这是理解「包装对象」的好例子。形参解构适合 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 时务必用参数化查询防注入,模板字符串只负责拼接结构。buildUrl 用 encodeURIComponent 编码查询参数是正确示范。
用法三:标签模板
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
: '';
return result + str + value;
}, '');
}
const userInput = '<script>alert("XSS")</script>';
const safeContent = safeHtml`用户输入:${userInput}`;
console.log(safeContent); // 用户输入:<script>alert("XSS")</script>
// 实际应用:国际化翻译
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 中 className 与 dangerouslySetInnerHTML 的安全边界。条件 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('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
【代码注释】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 转义码。