├── .DS_Store ├── IMGS ├── .DS_Store ├── bom_location.png ├── bom_screen.png ├── ec_stack.png ├── execution_stack.gif ├── extends.png ├── flowOfEvents.jpeg ├── indexDB_examples.jpg ├── js_life_circles.png ├── location.jpeg ├── nodeTree.png ├── page_renders.jpeg ├── part_10_14.jpeg ├── part_10_15.jpeg ├── part_10_16.jpeg ├── part_10_17.jpeg ├── part_2_1.png ├── part_2_2.jpg ├── part_3_10.jpeg ├── part_3_8.jpeg ├── part_3_9.jpeg ├── part_6_1.jpeg ├── part_6_2.jpeg ├── part_6_3.jpeg ├── part_6_4.jpeg ├── part_6_5.jpeg ├── part_6_6.jpeg ├── part_6_8.png ├── post_message.png ├── prototype-chain.png ├── rAF.gif ├── rect.jpeg ├── scope_word.png ├── useragent.jpg └── 任务队列.png ├── README.md ├── 思维导图 ├── .DS_Store ├── DOM树.xmind ├── 任务队列.xmind ├── 渲染进程.xmind └── 运行生命周期.xmind ├── 示例代码 ├── .DS_Store ├── Basics │ ├── main.js │ └── 数组.js ├── BrowserCaches │ ├── 01_Cookie │ │ ├── Cookies.js │ │ ├── index.html │ │ └── index.js │ ├── 02_localStorage │ │ ├── index.html │ │ └── index.js │ └── 03_indexDB │ │ ├── index.html │ │ ├── index.js │ │ └── utils.js ├── DebounceAndThrottle │ └── index.html ├── DesignPatterns │ ├── 01.工厂模式.js │ ├── 02.单例模式.js │ ├── 03.观察者模式.js │ └── 04.发布订阅者模式.js ├── ESNext │ ├── .DS_Store │ ├── 01.basic │ │ └── index.js │ ├── 02.Proxy │ │ └── index.js │ ├── 03.异步操作 │ │ ├── async-await.js │ │ └── generator.js │ └── modules │ │ ├── index.html │ │ └── main.js ├── Event │ ├── index.html │ └── index.js ├── Eventloops │ └── index.js ├── JSONs │ ├── .vscode │ │ └── settings.json │ ├── index.html │ └── index.js ├── Object │ ├── index.html │ └── index.js ├── PostMessage │ ├── index │ │ ├── .vscode │ │ │ └── settings.json │ │ └── index.html │ └── other │ │ └── index.html ├── Promise │ └── 01.基础用法 │ │ └── index.js ├── doms │ ├── index.html │ └── js │ │ ├── index.js │ │ └── lib.js └── requestAnimationFrame │ ├── index.css │ ├── index.html │ └── index.js ├── 第01章 基础知识.md ├── 第02章 程序结构.md ├── 第03章 数值.md ├── 第04章 字符串.md ├── 第05章 数组.md ├── 第06章 函数.md ├── 第07章 对象.md ├── 第08章 内置对象.md ├── 第09章 DOM.md ├── 第10章 事件.md ├── 第11章 BOM.md ├── 第12章 定时器与延迟函数.md ├── 第13章 浏览器缓存.md ├── 第14章 正则表达式.md ├── 第14章 浏览器持久化.md ├── 第15章 异常处理.md ├── 第16章 事件循环.md ├── 第17章 requestAnimationFrame.md ├── 第18章 设计模式.md └── 第19章 ESNext.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/.DS_Store -------------------------------------------------------------------------------- /IMGS/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/.DS_Store -------------------------------------------------------------------------------- /IMGS/bom_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/bom_location.png -------------------------------------------------------------------------------- /IMGS/bom_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/bom_screen.png -------------------------------------------------------------------------------- /IMGS/ec_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/ec_stack.png -------------------------------------------------------------------------------- /IMGS/execution_stack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/execution_stack.gif -------------------------------------------------------------------------------- /IMGS/extends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/extends.png -------------------------------------------------------------------------------- /IMGS/flowOfEvents.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/flowOfEvents.jpeg -------------------------------------------------------------------------------- /IMGS/indexDB_examples.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/indexDB_examples.jpg -------------------------------------------------------------------------------- /IMGS/js_life_circles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/js_life_circles.png -------------------------------------------------------------------------------- /IMGS/location.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/location.jpeg -------------------------------------------------------------------------------- /IMGS/nodeTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/nodeTree.png -------------------------------------------------------------------------------- /IMGS/page_renders.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/page_renders.jpeg -------------------------------------------------------------------------------- /IMGS/part_10_14.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_10_14.jpeg -------------------------------------------------------------------------------- /IMGS/part_10_15.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_10_15.jpeg -------------------------------------------------------------------------------- /IMGS/part_10_16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_10_16.jpeg -------------------------------------------------------------------------------- /IMGS/part_10_17.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_10_17.jpeg -------------------------------------------------------------------------------- /IMGS/part_2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_2_1.png -------------------------------------------------------------------------------- /IMGS/part_2_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_2_2.jpg -------------------------------------------------------------------------------- /IMGS/part_3_10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_3_10.jpeg -------------------------------------------------------------------------------- /IMGS/part_3_8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_3_8.jpeg -------------------------------------------------------------------------------- /IMGS/part_3_9.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_3_9.jpeg -------------------------------------------------------------------------------- /IMGS/part_6_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_6_1.jpeg -------------------------------------------------------------------------------- /IMGS/part_6_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_6_2.jpeg -------------------------------------------------------------------------------- /IMGS/part_6_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_6_3.jpeg -------------------------------------------------------------------------------- /IMGS/part_6_4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_6_4.jpeg -------------------------------------------------------------------------------- /IMGS/part_6_5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_6_5.jpeg -------------------------------------------------------------------------------- /IMGS/part_6_6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_6_6.jpeg -------------------------------------------------------------------------------- /IMGS/part_6_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/part_6_8.png -------------------------------------------------------------------------------- /IMGS/post_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/post_message.png -------------------------------------------------------------------------------- /IMGS/prototype-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/prototype-chain.png -------------------------------------------------------------------------------- /IMGS/rAF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/rAF.gif -------------------------------------------------------------------------------- /IMGS/rect.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/rect.jpeg -------------------------------------------------------------------------------- /IMGS/scope_word.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/scope_word.png -------------------------------------------------------------------------------- /IMGS/useragent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/useragent.jpg -------------------------------------------------------------------------------- /IMGS/任务队列.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/IMGS/任务队列.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | 前端三大基础(又称 **三大马车**): 4 | 5 | - **HTML(HyperText Markup Language)** —— **结构层**:用于定义网页的内容和结构(标签)。 6 | - **CSS(Cascading Style Sheets)** —— **渲染层**:用于控制网页的样式和布局。 7 | - **JavaScript** —— **行为层**:用于实现网页的动态交互。 8 | 9 | > **补充**:HTML 负责网页的骨架,CSS 负责美化和布局,而 JavaScript 赋予网页交互性,它们共同构成了现代 Web 开发的核心技术。 10 | 11 | JavaScript是一种高级编程语言,用于Web开发和创建交互式用户界面。它是一种动态类型语言,意味着变量类型在运行时确定。JavaScript可以在浏览器中运行,也可以在服务器端使用Node.js运行。它具有广泛的应用,包括网页开发、游戏开发、移动应用程序开发等。JavaScript具有易学性和灵活性,是Web开发中必不可少的一部分。 12 | 13 | # 为什么学习 JavaScript 14 | 15 | JavaScript 是目前**最流行**的编程语言之一,被广泛应用于**网页开发、游戏开发、移动应用、后端开发(Node.js)、桌面应用(Electron)**等多个领域。 16 | 17 | 学习 JavaScript 的优势: 18 | 19 | 1. **前端开发的核心** —— 现代网页的交互和动态效果几乎都依赖 JavaScript。 20 | 2. **生态丰富,前景广阔** —— 拥有 React、Vue、Angular 等强大的前端框架,以及 Node.js 让 JavaScript 也能用于后端开发。 21 | 3. **全栈开发** —— 既能编写前端,也能处理后端逻辑,实现一站式开发。 22 | 4. **高效提升开发效率** —— 通过现代前端工具(如 Webpack、Vite)和 TypeScript,可以大幅提高开发效率和代码质量。 23 | 5. **强大的社区支持** —— JavaScript 拥有庞大的开发者社区和丰富的学习资源,新手学习更容易。 24 | 25 | 因此,学习 JavaScript 不仅可以帮助我们**更高效地开发网页**,还能拓展到更广泛的领域(如全栈、移动端、小程序等),提升职业竞争力,是前端开发者的必修技能。 26 | 27 | # JavaScript 历史 28 | 29 | JavaScript 由 **Brendan Eich** 在 **1995 年**为 **Netscape** 公司的浏览器 **Netscape Navigator** 开发,并在短短 **10 天**内完成了第一版。 30 | 31 | **发展历程:** 32 | 33 | - **1995 年** —— **最初命名为 LiveScript**,但由于当时 Java 语言的流行,Netscape 与 Sun Microsystems 合作,将其更名为 **JavaScript**,实际上与 Java 关系不大。 34 | - **1996 年** —— **微软推出 JScript**,用于 IE 浏览器,与 JavaScript 类似,但存在兼容性问题。 35 | - **1997 年** —— **ECMAScript 标准诞生(ES1)**,JavaScript 由 ECMA(欧洲计算机制造商协会)标准化,成为 ECMAScript(ES) 36 | - **2009 年** —— **ES5 发布**,新增 JSON 支持、strict mode 等特性。 37 | - **2015 年** —— **ES6(ES2015)发布**,引入 let、const、箭头函数、类、模块化(import/export) 等现代特性。 38 | - **2016 年至今** —— **JavaScript 进入快速发展期**,ES 每年发布新版本(如 ES7、ES8…),同时现代前端框架(React、Vue、Angular)和后端技术(Node.js)让 JavaScript 生态更加成熟。 39 | 40 | > **总结**:JavaScript 诞生于 **Web 时代**,最初用于网页交互,如今已成为 **全栈开发** 的核心语言,应用于**前端、后端、桌面应用、移动端、游戏开发**等多个领域,仍在持续进化。 41 | 42 | # JavaScript 与 ECMAScript 43 | 44 | JavaScript 和 ECMAScript 是同一个东西吗?**不完全是。** 45 | 46 | - **ECMAScript(ES)** 是 JavaScript 的**核心标准**,由 **ECMA 国际**(European Computer Manufacturers Association)制定,规定了 JavaScript 语言的语法和基本功能。 47 | - **JavaScript** 是 ECMAScript 的**实现**之一,并在 ECMAScript 标准的基础上,添加了 **Web API**(如 DOM、BOM)、异步操作(如 setTimeout、fetch)等功能,使其更适用于 Web 开发。 48 | 49 | **关系总结**: 50 | 51 | - **ECMAScript** 规定了 JavaScript 语言的基本语法,如变量、作用域、对象、函数等。 52 | - **avaScript** 遵循 ECMAScript 标准,并扩展了更多的功能(如 DOM 操作、事件处理)。 53 | - **可以认为 JavaScript 是 ECMAScript 的超集**,但并不完全等同于 ECMAScript。 54 | 55 | 举例: 56 | 57 | 以下是 ECMAScript 规范内的代码: 58 | 59 | ```js 60 | let name = "JavaScript"; // 变量 61 | const greet = () => `Hello, ${name}`; // 箭头函数 62 | console.log(greet()); // 输出: Hello, JavaScript 63 | ``` 64 | 65 | 但 **DOM 操作** 并不属于 ECMAScript,而是 JavaScript 在浏览器环境中的扩展: 66 | 67 | ```js 68 | document.getElementById("app").innerText = "Hello, JavaScript!"; 69 | ``` 70 | 71 | > **总结**:ECMAScript **定义了 JavaScript 的核心规则**,而 JavaScript 在此基础上发展出了 **更多的功能**,尤其是在 Web 开发中(如 DOM 操作、事件处理)。 72 | 73 | # ECMAScript 的历史 74 | 75 | **ECMAScript(ES)** 是由 **Ecma 国际**(前身为欧洲计算机制造商协会)制定的**脚本语言标准**,用于规范 JavaScript 语法和功能。 76 | 77 | 起源: 78 | 79 | - **1995 年**,Brendan Eich 在 Netscape 开发了一种脚本语言,最初命名为 **LiveScript**。 80 | - 为了借助 Java 的热度,Netscape 将其更名为 **JavaScript**。 81 | - **1997 年**,Netscape 将 JavaScript 提交给 **Ecma 国际** 进行标准化,并发布了 **ECMAScript 1.0**(ES1)。 82 | 83 | ECMAScript 发展历程: 84 | 85 | | **版本** | **发布时间** | **主要特性** | 86 | | ----------------- | ------------ | ------------------------------------------------------------ | 87 | | **ES1** | 1997 | ECMAScript 诞生,定义 JavaScript 基础语法 | 88 | | **ES2** | 1998 | 小幅改进,与 ISO/IEC 16262 规范对齐 | 89 | | **ES3** | 1999 | 引入 `try/catch` 异常处理、正则表达式、`JSON` 支持等 | 90 | | **ES4 (取消)** | - | 由于意见不统一,ES4 计划被废弃 | 91 | | **ES5** | 2009 | 引入 `strict mode`(严格模式)、`JSON`、`Object.defineProperty()`、`Array.prototype.map()` 等 | 92 | | **ES6 (ES2015)** | 2015 | 大量更新,包括 `let`、`const`、箭头函数、类(`class`)、`Promise`、`import/export` 模块等 | 93 | | **ES7 (ES2016)** | 2016 | `Array.prototype.includes()`、指数运算符 (`**`) | 94 | | **ES8 (ES2017)** | 2017 | `async/await`、`Object.entries()`、`Object.values()` | 95 | | **ES9 (ES2018)** | 2018 | `Promise.finally()`、`Rest/Spread` 语法扩展 | 96 | | **ES10 (ES2019)** | 2019 | `flat()`、`flatMap()`、`Object.fromEntries()` | 97 | | **ES11 (ES2020)** | 2020 | 可选链(`?.`)、空值合并(`??`)、动态 `import()` | 98 | | **ES12 (ES2021)** | 2021 | `String.prototype.replaceAll()`、逻辑赋值运算符 (`&&=`, `||=`, `??=`) | 99 | | **ES13 (ES2022)** | 2022 | `class` 私有字段(`#`)、`top-level await` | 100 | 101 | > **总结**:ECMAScript 由 **JavaScript 标准化**而来,每年都会推出新版本,为 JavaScript 语言带来更强大的功能,使其在**前端、后端、移动端**等领域不断发展。 102 | 103 | # JavaScript 与 Java 的关系 104 | 105 | JavaScript 和 Java 是 **完全不同** 的编程语言,它们在 **语法、用途、运行环境** 上都有明显区别。 106 | 107 | - **JavaScript** 是一种 **动态** 的 **脚本语言**,主要用于 **网页前端开发**,在浏览器中运行,并由 **HTML、CSS** 共同构建交互式页面。 108 | - **Java** 是一种 **静态** 的 **面向对象编程语言**,广泛用于 **后端开发、桌面应用、移动开发(Android)**,需要 **JVM(Java 虚拟机)** 执行。 109 | 110 | 为什么 JavaScript 里有 “Java”? 111 | 112 | JavaScript 最初在 1995 年由 Netscape 公司推出时,名称是 **LiveScript**。由于当时 Java 语言非常流行,Netscape 出于 **营销目的**,与 Sun Microsystems(Java 的开发公司)合作,将其更名为 **JavaScript**,但两者本质上并无关系。 113 | 114 | # JavaScript 引入方式 115 | 116 | 在网页开发中,可以通过多种方式引入 JavaScript 代码,主要有以下几种: 117 | 118 | ## 内联(行内) 119 | 120 | 直接在 HTML 标签的 onclick、onmouseover 等事件属性中写 JavaScript 代码。 121 | 122 | ```html 123 | 124 | ``` 125 | 126 | ✅ **优点**:适用于简单的交互事件。 127 | 128 | ❌ **缺点**:代码可读性差,不利于维护,不推荐使用。 129 | 130 | ## 内部(嵌入式) 131 | 132 | 在 \ 145 | 146 | 147 | 148 | 149 | 150 | ``` 151 | 152 | ✅ **优点**:适用于小型项目,代码较为集中。 153 | 154 | ❌ **缺点**:HTML 和 JavaScript 耦合,影响代码的模块化。 155 | 156 | ## 外部(引入外部文件) 157 | 158 | 将 JavaScript 代码写在 **独立的 .js 文件** 中,并在 HTML 通过 \ 169 | 170 | 171 | 172 | 173 | 174 | ``` 175 | 176 | 📂 **文件结构** 177 | 178 | ``` 179 | /project 180 | │── index.html 181 | │── script.js 182 | ``` 183 | 184 | 📜 **script.js** 185 | 186 | ```js 187 | function showMessage() { 188 | alert('Hello, 外部 JavaScript!'); 189 | } 190 | ``` 191 | 192 | ✅ **优点**: 193 | 194 | - 代码复用性高,适合大型项目 195 | - HTML 结构与 JavaScript 分离,提升可读性和维护性 196 | 197 | ## defer 与 async 加载 198 | 199 | 默认情况下,\ 205 | ``` 206 | 207 | ✅ **特点**: 208 | 209 | 1. **不会阻塞 HTML 解析**,等 HTML 解析完成后再执行 210 | 2. 按照 **代码顺序** 执行多个 defer 资源 211 | 3. 适用于 **依赖 DOM 结构的 JavaScript**(推荐) 212 | 213 | **(2)async(异步加载)** 214 | 215 | ```html 216 | 217 | ``` 218 | 219 | ✅ **特点**: 220 | 221 | 1. **不会阻塞 HTML 解析**,但**下载完成后立即执行** 222 | 2. 多个 async 资源 **执行顺序不确定**,适合 **不依赖 DOM** 的代码(如广告、统计脚本) 223 | 224 | ## 最佳实践 225 | 226 | 1. **推荐使用外部 JavaScript**,保持 HTML 结构清晰 227 | 2. **推荐使用 defer**,确保 JavaScript 在 HTML 解析后执行 228 | 3. **避免行内 JavaScript**,提升代码可维护性 229 | 230 | # 拓展 231 | 232 | ## 数据结构 *(了解)* 233 | 234 | 这里只是简单介绍一些数据结构及其特点: 235 | 236 | ### 栈(Stack) 237 | 238 | 栈是一种**先进后出**(Last In First Out, LIFO)的数据结构。它的特点是: 239 | 240 | - **入栈(Push)**:将元素加入栈顶。 241 | - **出栈(Pop)**:从栈顶移除元素。 242 | - 只有栈顶的元素可以被访问。 243 | 244 | **应用场景**: 245 | 246 | 1. 浏览器历史记录的回退操作 247 | 2. 表达式求值(如括号匹配) 248 | 3. 递归算法的模拟 249 | 250 | ```js 251 | let stack = []; 252 | stack.push(1); // 入栈 253 | stack.push(2); 254 | console.log(stack.pop()); // 出栈,输出 2 255 | ``` 256 | 257 | ### 堆(Heap) 258 | 259 | 堆是一种特殊的树形数据结构,通常用于实现**优先队列**。它的特点是: 260 | 261 | - 元素是**部分顺序**的,通常是按照**完全二叉树**来储存。 262 | - **最大堆**:父节点的值大于或等于子节点的值。 263 | - **最小堆**:父节点的值小于或等于子节点的值。 264 | - 不是像栈和队列那样的顺序存储,可以通过“key”快速定位。 265 | 266 | **应用场景**: 267 | 268 | 1. 优先队列(如 Dijkstra 最短路径算法) 269 | 2. 动态求解最大值或最小值 270 | 271 | ```js 272 | // 使用 JavaScript 的优先队列示例(通过堆实现) 273 | let heap = []; 274 | heap.push(3); // 插入元素 275 | heap.push(1); 276 | heap.push(2); 277 | console.log(heap); // 堆结构显示 278 | ``` 279 | 280 | ### 队列(Queue) 281 | 282 | 队列是一种**先进先出**(First In First Out, FIFO)的数据结构。它的特点是: 283 | 284 | - **入队(Enqueue)**:将元素加入队尾。 285 | - **出队(Dequeue)**:从队头移除元素。 286 | - 队列中的元素按顺序排队,最先进入的元素最先被处理。 287 | 288 | **应用场景**: 289 | 290 | 1. 任务调度(如操作系统的进程调度) 291 | 2. 打印任务的排队 292 | 3. 异步编程中的事件队列 293 | 294 | ```js 295 | let queue = []; 296 | queue.push(1); // 入队 297 | queue.push(2); 298 | console.log(queue.shift()); // 出队,输出 1 299 | ``` 300 | 301 | ## 执行上下文(环境) 302 | 303 | JavaScript 执行上下文是指在代码执行期间,JavaScript 引擎创建的一个**内部数据结构**,用于存储代码执行期间的**变量、函数、作用域链**等信息。每个执行上下文都有一个**变量对象(Variable Object, VO)**,用于存储当前执行环境中的所有变量和函数声明。 304 | 305 | JavaScript 代码的执行上下文主要分为三种类型: 306 | 307 | 1. **全局执行上下文(Global Execution Context)** 308 | 2. **函数执行上下文(Function Execution Context)** 309 | 3. **Eval 执行上下文(Eval Execution Context)**(不推荐使用) 310 | 311 | ### 全局执行上下文 312 | 313 | 当 JavaScript 代码开始运行时,JS 引擎会首先创建一个**全局执行上下文**,该上下文会执行所有**不在函数内部**的代码,并包含以下内容: 314 | 315 | 1. **创建全局对象**(浏览器环境下是 window,Node.js 环境下是 global) 316 | 2. **创建全局变量对象(VO)** 317 | 3. **将 this 绑定到全局对象** 318 | 319 | ```js 320 | console.log(this === window); // true (浏览器环境) 321 | var a = 10; 322 | function test() { 323 | console.log(a); 324 | } 325 | test(); 326 | ``` 327 | 328 | 在全局上下文中,this 指向全局对象 window。 329 | 330 | ### 函数执行上下文 331 | 332 | 每当一个函数被调用时,都会为该函数**创建一个新的执行上下文**,并包含: 333 | 334 | 1. **函数的变量对象(VO)**:存储函数内部声明的变量、参数和内部函数 335 | 2. **作用域链**(Scope Chain):用于查找变量 336 | 3. **this 绑定** 337 | 338 | ```js 339 | function foo(x) { 340 | var y = 2; 341 | function bar() { 342 | console.log(x, y); 343 | } 344 | bar(); 345 | } 346 | 347 | foo(5); // 输出: 5 2 348 | ``` 349 | 350 | 在 foo 执行时,它的执行上下文会被创建,bar 也会继承 foo 的作用域链。 351 | 352 | ### Eval 执行上下文 353 | 354 | eval() 函数可以在运行时执行 JavaScript 代码,并会创建自己的执行上下文(但不推荐使用,因为它影响性能和安全性)。 355 | 356 | ```js 357 | eval("var x = 10;"); 358 | console.log(x); // 10 359 | ``` 360 | 361 | 这里 eval 代码在执行时会创建自己的执行上下文,但它仍然会修改全局变量。 362 | 363 | ## 执行上下文的生命周期 364 | 365 | 执行上下文的生命周期包括三个阶段:**创建阶段 → 执行阶段 → 销毁阶段**,本文重点介绍创建阶段。 366 | 367 | **① 创建阶段** 368 | 369 | 在创建阶段,js 引擎会创建一个执行上下文对象,并进行以下操作: 370 | - 创建变量对象(Variable Object,VO):包括函数声明、函数参数、变量声明等。 371 | - 确定 this 的指向。 372 | - 创建作用域链(Scope Chain):用于解析变量和函数的作用域。 373 | - 初始化变量的值:将变量初始化为 undefined。 374 | 375 | **② 执行阶段** 376 | 377 | 在执行阶段,js 引擎会按照代码的顺序执行,将变量赋值、函数调用等操作加入执行栈(Execution Stack)中,直到执行栈为空。 378 | 379 | **③ 回收阶段** 380 | 381 | 在销毁阶段,JavaScript 引擎会进行以下操作: 382 | - 执行垃圾回收(Garbage Collection):回收不再使用的内存空间。 383 | - 销毁变量对象和作用域链。 384 | 385 | ## 执行栈 386 | 387 | 执行栈,即 “调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。 388 | 389 | js代码执行时首先会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。 390 | 391 | ![](./IMGS/execution_stack.gif) 392 | 393 | 从上面的流程图,我们需要记住几个关键点: 394 | 395 | - js执行在单线程上,所有的代码都是排队执行。 396 | - 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。 397 | - 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。 398 | - 浏览器的 js 执行引擎总是访问栈顶的执行上下文。 399 | - 全局上下文只有唯一的一个,它在浏览器关闭时出栈。 400 | 401 | 我们再来看个例子: 402 | 403 | ```javascript 404 | var color = 'blue'; 405 | function changeColor() { 406 | var anotherColor = 'red'; 407 | function swapColors() { 408 | var tempColor = anotherColor; 409 | anotherColor = color; 410 | color = tempColor; 411 | } 412 | swapColors(); 413 | } 414 | changeColor(); 415 | ``` 416 | 417 | 上述代码运行按照如下步骤: 418 | 419 | - 当上述代码在浏览器中加载时,JavaScript 引擎会创建一个全局执行上下文并且将它推入当前的执行栈 420 | - 调用 `changeColor` 函数时,此时 `changeColor` 函数内部代码还未执行,js 执行引擎立即创建一个 changeColor 的执行上下文(简称 EC),然后把这执行上下文压入到执行栈(简称 ECStack)中。 421 | - 执行 changeColor 函数过程中,调用 `swapColors` 函数,同样地,swapColors 函数执行之前也创建了一个 swapColors 的执行上下文,并压入到执行栈中。 422 | - swapColors 函数执行完成,swapColors 函数的执行上下文出栈,并且被销毁。 423 | - changeColor 函数执行完成,changeColor 函数的执行上下文出栈,并且被销毁。 424 | 425 | ![](./IMGS/ec_stack.png) 426 | 427 | ## 运行生命周期 428 | 429 | js运行的生命周期,参照下图: 430 | 431 | ![](./IMGS/js_life_circles.png) 432 | 433 | js的运行生命周期包括以下几个阶段: 434 | 1. **解析阶段**:js代码在执行之前需要被解析,解析器会对代码进行词法分析和语法分析,生成抽象语法树(AST)。 435 | 2. **编译阶段**:在编译阶段,js引擎会对AST进行优化和编译,生成可执行代码。 436 | 3. **执行阶段**:在执行阶段,js引擎会按照编译阶段生成的可执行代码,逐行执行js代码,执行过程中会涉及到变量的声明、赋值、函数调用等操作。在执行过程中,js引擎还会进行垃圾回收等操作,确保内存的有效使用。 437 | 438 | ## 编译原理 439 | 440 | js编译原理是将js代码转换为可执行的机器代码的过程,它包括三个主要阶段:解析、转换和代码生成。 441 | 442 | 1. 在解析阶段,编译器将js代码转换为抽象语法树(AST)。 443 | 2. 在转换阶段,编译器将AST转换为中间表示(IR),并对其进行优化。 444 | 3. 在代码生成阶段,编译器将IR转换为机器代码,并生成可执行文件。 445 | 446 | js编译器还包括一些其他的步骤,如词法分析、语法分析、类型检查和错误处理。这些步骤都是为了确保js代码能够正确地编译和执行。 447 | 448 | -------------------------------------------------------------------------------- /思维导图/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/思维导图/.DS_Store -------------------------------------------------------------------------------- /思维导图/DOM树.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/思维导图/DOM树.xmind -------------------------------------------------------------------------------- /思维导图/任务队列.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/思维导图/任务队列.xmind -------------------------------------------------------------------------------- /思维导图/渲染进程.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/思维导图/渲染进程.xmind -------------------------------------------------------------------------------- /思维导图/运行生命周期.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/思维导图/运行生命周期.xmind -------------------------------------------------------------------------------- /示例代码/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/示例代码/.DS_Store -------------------------------------------------------------------------------- /示例代码/Basics/main.js: -------------------------------------------------------------------------------- 1 | for (let i = 0; i < 5; i++) { 2 | if (i === 2) { 3 | continue; 4 | } 5 | console.log(`i = ${i}`); 6 | } 7 | 8 | if (true) { 9 | var y = 20; 10 | } 11 | console.log(y); 12 | 13 | console.log(typeof []); 14 | console.log(typeof {}); 15 | console.log(typeof null); 16 | 17 | 18 | -------------------------------------------------------------------------------- /示例代码/Basics/数组.js: -------------------------------------------------------------------------------- 1 | const heros = ["阿珂", "李白", "貂蝉"]; 2 | 3 | heros.map((item, index, arr) => { 4 | console.log(`item:${item}, index:${index}, arr: ${arr}`); 5 | }); 6 | 7 | /* 8 | item:阿珂, index:0, arr: 阿珂,李白,貂蝉 9 | item:李白, index:1, arr: 阿珂,李白,貂蝉 10 | item:貂蝉, index:2, arr: 阿珂,李白,貂蝉 11 | */ 12 | 13 | 14 | const flatArray = [1, 2, 3, [4, [1, 2], 6]]; 15 | console.log(flatArray.flat(Infinity)); 16 | 17 | const names = ["张三", "李四", "王五"]; 18 | console.log(names.join()); 19 | 20 | 21 | 22 | 23 | const date = new Date(); 24 | date.setMilliseconds 25 | date.setSeconds 26 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/01_Cookie/Cookies.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-12-05 09:52:11 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-04 14:41:12 6 | * @Description: 7 | */ 8 | 9 | /*\ 10 | |*| 11 | |*| :: cookies.js :: 12 | |*| 13 | |*| A complete cookies reader/writer framework with full unicode support. 14 | |*| 15 | |*| https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie 16 | |*| 17 | |*| This framework is released under the GNU Public License, version 3 or later. 18 | |*| http://www.gnu.org/licenses/gpl-3.0-standalone.html 19 | |*| 20 | |*| Syntaxes: 21 | |*| 22 | |*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]]) 23 | |*| * docCookies.getItem(name) 24 | |*| * docCookies.removeItem(name[, path], domain) 25 | |*| * docCookies.hasItem(name) 26 | |*| * docCookies.keys() 27 | |*| 28 | \*/ 29 | 30 | class DocCookies { 31 | /** 32 | * 得到Cookie 33 | * 读取一个 cookie。如果 cookie 不存在返回null。 34 | * @param {string} name 读取的 cookie 名。 35 | * @returns 36 | */ 37 | static getItem(name) { 38 | return ( 39 | decodeURIComponent( 40 | document.cookie.replace( 41 | new RegExp( 42 | '(?:(?:^|.*;)\\s*' + 43 | encodeURIComponent(name).replace(/[-.+*]/g, '\\$&') + 44 | '\\s*\\=\\s*([^;]*).*$)|^.*$' 45 | ), 46 | '$1' 47 | ) 48 | ) || null 49 | ); 50 | } 51 | 52 | /** 53 | * 写入Cookie 54 | * 创建或覆盖一个 cookie 55 | * @param {string} name 要创建或覆盖的 cookie 的名字 56 | * @param {any} value cookie 的值 57 | * @param {any} expires 过期时间,例如一年为 60*60*24*365(可选) 58 | * @param {string} path 存储路径,默认为当前文档位置的路径(可选) 59 | * @param {string} domain 存储域名,默认为当前文档位置的路径的域名部分(可选) 60 | * @param {boolean} secure cookie 只会被 https 传输(可选) 61 | * @returns 62 | */ 63 | static setItem(name, value, expires, path, domain, secure) { 64 | if (!name || /^(?:expires|max\-age|path|domain|secure)$/i.test(name)) { 65 | return false; 66 | } 67 | var sExpires = ''; 68 | if (expires) { 69 | switch (expires.constructor) { 70 | case Number: 71 | sExpires = 72 | expires === Infinity 73 | ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' 74 | : '; max-age=' + expires; 75 | break; 76 | case String: 77 | sExpires = '; expires=' + expires; 78 | break; 79 | case Date: 80 | sExpires = '; expires=' + expires.toUTCString(); 81 | break; 82 | } 83 | } 84 | document.cookie = 85 | encodeURIComponent(name) + 86 | '=' + 87 | encodeURIComponent(value) + 88 | sExpires + 89 | (domain ? '; domain=' + domain : '') + 90 | (path ? '; path=' + path : '') + 91 | (secure ? '; secure' : ''); 92 | return true; 93 | } 94 | 95 | /** 96 | * 移除Cookie 97 | * 删除一个 cookie 98 | * @param {string} name 要移除的 cookie 名 99 | * @param {string} path 存储路径,默认为当前文档位置的路径(可选) 100 | * @param {string} domain 存储域名,默认为当前文档位置的路径的域名部分(可选) 101 | * @returns 102 | */ 103 | static removeItem(name, path, domain) { 104 | if (!name || !this.hasItem(name)) { 105 | return false; 106 | } 107 | document.cookie = 108 | encodeURIComponent(name) + 109 | '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + 110 | (domain ? '; domain=' + domain : '') + 111 | (path ? '; path=' + path : ''); 112 | return true; 113 | } 114 | 115 | /** 116 | * 检测 Cookie 117 | * 检查一个 cookie 是否存在 118 | * @param {string} name 要检查的 cookie 名 119 | * @returns 120 | */ 121 | static hasItem(name) { 122 | return new RegExp( 123 | '(?:^|;\\s*)' + 124 | encodeURIComponent(name).replace(/[-.+*]/g, '\\$&') + 125 | '\\s*\\=' 126 | ).test(document.cookie); 127 | } 128 | 129 | /** 130 | * 得到所有 cookie 的列表 131 | * @returns 132 | */ 133 | static keys() { 134 | var aKeys = document.cookie 135 | .replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, '') 136 | .split(/\s*(?:\=[^;]*)?;\s*/); 137 | for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { 138 | aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); 139 | } 140 | return aKeys; 141 | } 142 | } 143 | 144 | window.docCookies = DocCookies; 145 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/01_Cookie/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Cookie 16 | 17 | 18 | 19 |

20 | 键: 21 | 22 |

23 |

24 | 值: 25 | 26 |

27 | 30 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/01_Cookie/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-12-05 09:25:40 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2022-12-05 10:56:03 6 | * @Description: 7 | */ 8 | 9 | var key = ''; 10 | var value = ''; 11 | 12 | function onKeyChange(event) { 13 | key = event.target.value; 14 | } 15 | function onValueChange(event) { 16 | value = event.target.value; 17 | } 18 | function onGetCookies() { 19 | console.log(docCookies.getItem(key)); 20 | } 21 | function onSetCookies() { 22 | if (!key || !value) return; 23 | console.log(docCookies.setItem(key, value)); 24 | } 25 | function onRemoveCookies() { 26 | console.log(docCookies.removeItem(key)); 27 | } 28 | 29 | function onGetKeys() { 30 | console.log(docCookies.keys()); 31 | } 32 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/02_localStorage/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Web Storage 17 | 18 | 19 | 20 |

21 | 键: 22 | 23 |

24 |

25 | 值: 26 | 27 |

28 | 31 | 34 | 37 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/02_localStorage/index.js: -------------------------------------------------------------------------------- 1 | var key = ''; 2 | var value = ''; 3 | 4 | function onKeyChange(event) { 5 | key = event.target.value; 6 | } 7 | function onValueChange(event) { 8 | value = event.target.value; 9 | } 10 | 11 | function onSetItem() { 12 | console.log(localStorage.setItem(key, value)); 13 | } 14 | 15 | function onGetItem() { 16 | console.log(localStorage.getItem(key)); 17 | } 18 | 19 | function onRemoveItem() { 20 | console.log(localStorage.removeItem(key)); 21 | } 22 | 23 | function onClear() { 24 | console.log(localStorage.clear()); 25 | } 26 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/03_indexDB/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | IndexDB 8 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/03_indexDB/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // 数据库版本号 5 | const version = 1; 6 | 7 | // 初始化数据库 8 | const db = new DB("db-learns", version, { students: "id" }); 9 | 10 | // 新增数据 11 | async function onAdd() { 12 | if (!_name.value || !_age.value || !_job.value || !_city.value) { 13 | _tips.textContent = "温馨提示:请完善信息"; 14 | return; 15 | } 16 | try { 17 | const id = await db.add("students", { 18 | id: Date.now().toString(), 19 | name: _name.value, 20 | age: +_age.value, 21 | job: _job.value, 22 | city: _city.value, 23 | }); 24 | console.log(`新增成功,ID:${id}`); 25 | _tips.textContent = "新增成功!"; 26 | } catch (error) { 27 | console.error("新增失败:", error); 28 | _tips.textContent = "新增失败!"; 29 | } 30 | } 31 | 32 | // 删除数据 33 | async function onDelete() { 34 | if (!_flag.value) { 35 | _tips.textContent = "温馨提示:请填写操作标识(ID)"; 36 | return; 37 | } 38 | try { 39 | const resp = await db.delete("students", _flag.value); 40 | console.log(`删除结果:${resp}`); 41 | _tips.textContent = "删除成功!"; 42 | } catch (error) { 43 | console.error("删除失败:", error); 44 | _tips.textContent = "删除失败!"; 45 | } 46 | } 47 | 48 | // 更新数据 49 | async function onPut() { 50 | if (!_name.value || !_age.value || !_job.value || !_city.value || !_flag.value) { 51 | _tips.textContent = "温馨提示:请完善信息"; 52 | return; 53 | } 54 | try { 55 | const id = await db.put("students", { 56 | id: _flag.value, 57 | name: _name.value, 58 | age: +_age.value, 59 | job: _job.value, 60 | city: _city.value, 61 | }); 62 | console.log(`更新成功,ID:${id}`); 63 | _tips.textContent = "更新成功!"; 64 | } catch (error) { 65 | console.error("更新失败:", error); 66 | _tips.textContent = "更新失败!"; 67 | } 68 | } 69 | 70 | // 查询数据 71 | async function onGet() { 72 | if (!_flag.value) { 73 | _tips.textContent = "温馨提示:请填写操作标识(ID)"; 74 | return; 75 | } 76 | try { 77 | const resp = await db.get("students", _flag.value); 78 | console.log("查询结果:", resp); 79 | _tips.textContent = resp ? `查询成功:${JSON.stringify(resp)}` : "无此记录!"; 80 | } catch (error) { 81 | console.error("查询失败:", error); 82 | _tips.textContent = "查询失败!"; 83 | } 84 | } 85 | 86 | // 遍历数据 87 | async function onEach() { 88 | try { 89 | const resp = await db.each("students"); 90 | console.log("所有数据:", resp); 91 | _tips.textContent = "遍历成功,查看控制台日志!"; 92 | } catch (error) { 93 | console.error("遍历失败:", error); 94 | _tips.textContent = "遍历失败!"; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /示例代码/BrowserCaches/03_indexDB/utils.js: -------------------------------------------------------------------------------- 1 | class DB { 2 | /** 3 | * 构造器 4 | * @param {string} databaseName 数据库名 5 | * @param {number} version 数据库版本号(仅支持整数) 6 | * @param {object} storeOptions 配置项 { 表名:主键 } 7 | */ 8 | constructor(databaseName, version, storeOptions) { 9 | // 缓存数据库 { [name + version]:database } 10 | this.dbs = {}; 11 | this.databaseName = databaseName; 12 | this.open(databaseName, version, storeOptions); 13 | } 14 | 15 | /** 16 | * 打开数据库 17 | * @param {string} databaseName 数据库名 18 | * @param {number} version 数据库版本号(仅支持整数) 19 | * @param {object} storeOptions 配置项 { 表名:主键 } 20 | * @returns {Promise} 返回数据库实例 21 | */ 22 | open(databaseName, version, storeOptions) { 23 | return new Promise((resolve, reject) => { 24 | 25 | // 检查缓存是否存在 26 | const dbKey = `${databaseName}_${version}`; 27 | if (this.dbs[dbKey]) { 28 | resolve(this.dbs[dbKey]); 29 | return; 30 | } 31 | 32 | // 尝试打开数据库 33 | const request = indexedDB.open(databaseName, version); 34 | 35 | // 数据库版本更新时触发,通常用于创建或更新表结构 36 | request.onupgradeneeded = (event) => { 37 | console.log("【Indexed-DB】:数据库升级中..."); 38 | const database = event.target.result; 39 | // 遍历配置项,创建新的数据表(objectStore) 40 | for (const storeName in storeOptions) { 41 | if (!database.objectStoreNames.contains(storeName)) { 42 | const keyPath = storeOptions[storeName] || "id"; // 默认主键为 "id" 43 | database.createObjectStore(storeName, { keyPath }); 44 | } 45 | } 46 | }; 47 | 48 | // 数据库打开成功 49 | request.onsuccess = (event) => { 50 | console.log("【Indexed-DB】:数据库打开成功"); 51 | const database = event.target.result; 52 | this.dbs[dbKey] = database; // 缓存数据库实例 53 | resolve(database); 54 | }; 55 | 56 | // 数据库打开失败 57 | request.onerror = (event) => { 58 | console.error("【Indexed-DB】:数据库打开失败", event.target.error); 59 | reject(new Error(`数据库打开失败:${event.target.error.message}`)); 60 | }; 61 | }); 62 | } 63 | 64 | /** 65 | * 获取事务 66 | * @param {string} storeName 表名 67 | * @param {string} mode 事务模式(默认只读 "readonly",可选 "readwrite") 68 | * @returns {Promise} 返回事务实例 69 | */ 70 | async _getTransaction(storeName, mode = "readonly") { 71 | const dbKey = `${this.databaseName}_${this._version}`; 72 | const db = this.dbs[dbKey] || (await this.open(this.databaseName, this._version)); 73 | return db.transaction([storeName], mode); 74 | } 75 | 76 | /** 77 | * 获取数据表对象 78 | * @param {string} storeName 表名 79 | * @param {string} mode 事务模式(默认只读 "readonly",可选 "readwrite") 80 | * @returns {Promise} 返回数据表实例 81 | */ 82 | async _getObjectStore(storeName, mode = "readonly") { 83 | const transaction = await this._getTransaction(storeName, mode); 84 | return transaction.objectStore(storeName); 85 | } 86 | 87 | /** 88 | * 执行指定操作的通用方法 89 | * @param {string} storeName 表名 90 | * @param {string} action 操作类型(如 "get", "add", "put", "delete") 91 | * @param {...any} args 其他参数 92 | * @returns {Promise} 返回操作结果 93 | */ 94 | async _executeRequest(storeName, action, ...args) { 95 | const objectStore = await this._getObjectStore(storeName, action === "get" ? "readonly" : "readwrite"); 96 | return new Promise((resolve, reject) => { 97 | const request = objectStore[action](...args); 98 | request.onsuccess = (event) => resolve(event.target.result); 99 | request.onerror = (event) => { 100 | console.error(`【Indexed-DB】:${action} 操作失败`, event.target.error); 101 | reject(event.target.error); 102 | }; 103 | }); 104 | } 105 | 106 | /** 107 | * 添加数据 108 | * @param {string} storeName 表名 109 | * @param {object} data 数据对象 110 | * @returns {Promise} 返回新增数据的主键值 111 | */ 112 | async add(storeName, data) { 113 | return this._executeRequest(storeName, "add", data); 114 | } 115 | 116 | /** 117 | * 获取数据 118 | * @param {string} storeName 表名 119 | * @param {string | number} id 主键值 120 | * @returns {Promise} 返回数据对象 121 | */ 122 | async get(storeName, id) { 123 | return this._executeRequest(storeName, "get", id); 124 | } 125 | 126 | /** 127 | * 更新数据 128 | * @param {string} storeName 表名 129 | * @param {object} data 数据对象 130 | * @returns {Promise} 返回更新数据的主键值 131 | */ 132 | async put(storeName, data) { 133 | return this._executeRequest(storeName, "put", data); 134 | } 135 | 136 | /** 137 | * 删除数据 138 | * @param {string} storeName 表名 139 | * @param {string | number} id 主键值 140 | * @returns {Promise} 返回操作结果 141 | */ 142 | async delete(storeName, id) { 143 | await this._executeRequest(storeName, "delete", id); 144 | return true; 145 | } 146 | 147 | /** 148 | * 清空数据表 149 | * @param {string} storeName 表名 150 | * @returns {Promise} 返回操作结果 151 | */ 152 | async clear(storeName) { 153 | await this._executeRequest(storeName, "clear"); 154 | } 155 | 156 | /** 157 | * 获取所有数据 158 | * @param {string} storeName 表名 159 | * @returns {Promise>} 返回数据数组 160 | */ 161 | async getAll(storeName) { 162 | const objectStore = await this._getObjectStore(storeName); 163 | return new Promise((resolve, reject) => { 164 | const request = objectStore.getAll(); 165 | request.onsuccess = (event) => resolve(event.target.result); 166 | request.onerror = (event) => reject(event.target.error); 167 | }); 168 | } 169 | 170 | /** 171 | * 遍历数据 172 | * @param {string} storeName 表名 173 | * @param {function} callback 遍历回调函数 (cursor) => void 174 | * @returns {Promise} 175 | */ 176 | async each(storeName, callback) { 177 | const objectStore = await this._getObjectStore(storeName); 178 | return new Promise((resolve, reject) => { 179 | const request = objectStore.openCursor(); 180 | request.onsuccess = (event) => { 181 | const cursor = event.target.result; 182 | if (cursor) { 183 | callback(cursor.value); 184 | cursor.continue(); 185 | } else { 186 | resolve(); 187 | } 188 | }; 189 | request.onerror = (event) => reject(event.target.error); 190 | }); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /示例代码/DebounceAndThrottle/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 函数防抖与节流 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /示例代码/DesignPatterns/01.工厂模式.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-06 16:59:30 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-06 16:59:35 6 | * @Description: 7 | */ 8 | function createPerson(name, age) { 9 | return { 10 | name: name, 11 | age: age, 12 | sayHello: function () { 13 | console.log( 14 | `Hello, my name is ${this.name} and I'm ${this.age} years old.` 15 | ); 16 | }, 17 | }; 18 | } 19 | 20 | const person1 = createPerson('John', 30); 21 | const person2 = createPerson('Jane', 25); 22 | 23 | person1.sayHello(); // 输出:Hello, my name is John and I'm 30 years old. 24 | person2.sayHello(); // 输出:Hello, my name is Jane and I'm 25 years old. 25 | -------------------------------------------------------------------------------- /示例代码/DesignPatterns/02.单例模式.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-06 17:02:57 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-06 17:09:31 6 | * @Description: 7 | */ 8 | 9 | // const Singleton = (function () { 10 | // let instance; 11 | 12 | // function createInstance() { 13 | // const object = new Object({ name: 'John' }); 14 | // return object; 15 | // } 16 | 17 | // return { 18 | // getInstance: function () { 19 | // if (!instance) { 20 | // instance = createInstance(); 21 | // } 22 | // return instance; 23 | // }, 24 | // }; 25 | // })(); 26 | 27 | // const instance1 = Singleton.getInstance(); 28 | // const instance2 = Singleton.getInstance(); 29 | 30 | // console.log(instance1 === instance2); // true 31 | 32 | class Singleton { 33 | constructor() { 34 | if (Singleton.instance) { 35 | return Singleton.instance; 36 | } 37 | Singleton.instance = this; 38 | } 39 | 40 | static getInstance() { 41 | if (Singleton.instance) { 42 | return Singleton.instance; 43 | } 44 | return new Singleton(); 45 | } 46 | } 47 | 48 | const instance1 = new Singleton(); 49 | const instance2 = new Singleton(); 50 | const instance3 = Singleton.getInstance(); 51 | const instance4 = Singleton.getInstance(); 52 | console.log(instance1 === instance2); // true 53 | console.log(instance3 === instance4); // true 54 | -------------------------------------------------------------------------------- /示例代码/DesignPatterns/03.观察者模式.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-06 17:17:18 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-06 17:24:29 6 | * @Description: 7 | */ 8 | 9 | // 1.创建一个被观察者(Subject)对象,它包含一个观察者(Observer)列表和一些方法来添加、删除和通知观察者。 10 | class Subject { 11 | constructor() { 12 | this.observers = []; 13 | } 14 | 15 | addObserver(observer) { 16 | this.observers.push(observer); 17 | } 18 | removeObserver(observer) { 19 | const index = this.observers.indexOf(observer); 20 | if (index !== -1) { 21 | this.observers.splice(index, 1); 22 | } 23 | } 24 | notifyObservers(data) { 25 | for (const observer of this.observers) { 26 | observer.update(data); 27 | } 28 | } 29 | } 30 | 31 | // 2.创建一个观察者对象,它包含一个 update 方法,用于接收来自被观察者的通知。 32 | class Observer { 33 | constructor() {} 34 | update(data) { 35 | console.log(`Received data:${data}`); 36 | } 37 | } 38 | 39 | // 3.创建一个被观察者实例和多个观察者实例,并将观察者添加到被观察者的观察者列表中。 40 | const subject = new Subject(); 41 | const observer1 = new Observer(); 42 | const observer2 = new Observer(); 43 | 44 | subject.addObserver(observer1); 45 | subject.addObserver(observer2); 46 | 47 | // 4.调用被观察者的 notifyObservers 方法,通知所有观察者更新。 48 | subject.notifyObservers(JSON.stringify({ message: 'Hello' })); 49 | -------------------------------------------------------------------------------- /示例代码/DesignPatterns/04.发布订阅者模式.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-06 17:40:30 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-06 18:04:25 6 | * @Description: 7 | */ 8 | class EventBus { 9 | constructor() { 10 | this.listeners = {}; 11 | } 12 | 13 | on(event, listener) { 14 | if (!this.listeners[event]) { 15 | this.listeners[event] = []; 16 | } 17 | this.listeners[event].push(listener); 18 | } 19 | 20 | emit(event, ...args) { 21 | const listeners = this.listeners[event]; 22 | if (listeners) { 23 | listeners.forEach((listener) => { 24 | listener(...args); 25 | }); 26 | } 27 | } 28 | 29 | removeListener(event, listenerToRemove) { 30 | const listeners = this.listeners[event]; 31 | if (listeners) { 32 | this.listeners[event] = listeners.filter( 33 | (listener) => listener !== listenerToRemove 34 | ); 35 | } 36 | } 37 | 38 | removeAllListeners(event) { 39 | delete this.listeners[event]; 40 | } 41 | } 42 | 43 | const bus = new EventBus(); 44 | 45 | const listenerFunction = (args) => { 46 | console.log(args); 47 | }; 48 | 49 | // 1. 添加一个监听器 50 | bus.on('click', listenerFunction); 51 | // 2. 触发事件 52 | bus.emit('click', 'Hello'); 53 | // 3. 删除监听器 54 | bus.removeListener('click', listenerFunction); 55 | // 4. 删除所有监听器 56 | bus.removeAllListeners('click'); 57 | -------------------------------------------------------------------------------- /示例代码/ESNext/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lihongyao/JavaScript/5c71708fcb744d50717a7b36f97584e848849eee/示例代码/ESNext/.DS_Store -------------------------------------------------------------------------------- /示例代码/ESNext/01.basic/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-30 10:54:59 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-30 11:32:40 6 | * @Description: 7 | */ 8 | 9 | // -- 解构 10 | // 1. 交换值 11 | let x = 10, y = 20; 12 | [x, y] = [y, x]; 13 | console.log(x, y); 14 | 15 | 16 | // -- Map 17 | 18 | -------------------------------------------------------------------------------- /示例代码/ESNext/02.Proxy/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-30 16:31:23 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-30 16:45:14 6 | * @Description: 7 | */ 8 | 9 | // const object = { name: '张三', job: '前端工程师' }; 10 | // const p = new Proxy(object, { 11 | // get(target, property, receiver) { 12 | // const value = target[property]; 13 | // if (value) { 14 | // return value; 15 | // } else { 16 | // throw new ReferenceError(`Property ${property} does not exist.`); 17 | // } 18 | // }, 19 | // }); 20 | 21 | // console.log(p.name); // 张三 22 | // console.log(p.major); // ReferenceError: Property major does not exist. 23 | 24 | // const car = { 25 | // _brand: '东风本田', 26 | // color: '珍珠白', 27 | // }; 28 | // const p = new Proxy(car, { 29 | // get(target, property, receiver) { 30 | // // 实现私有属性的保护 31 | // if (/^_/.test(property)) { 32 | // throw new Error(`私有属性 ${property} 不可访问.`); 33 | // } 34 | // return Reflect.get(target, property, receiver); 35 | // }, 36 | // }); 37 | 38 | // console.log(p.color); // 珍珠白 39 | // console.log(p._brand); // Error: 私有属性 _brand 不可访问. 40 | 41 | const api = { 42 | _appsecret: '5732e4c9db7ff9f7', 43 | appID: 'wx1695393264bf7d', 44 | wx: 'gh_133b3cd88m3a', 45 | }; 46 | 47 | const p = new Proxy(api, { 48 | get(target, property, receiver) { 49 | if (/^_/.test(property)) { 50 | console.log(`私有属性 ${property} 不支持访问.`); 51 | return null; 52 | } 53 | return Reflect.get(target, property, receiver); 54 | }, 55 | set(target, property, value, receiver) { 56 | if (/^_/.test(property)) { 57 | console.log(`私有属性 ${property} 不支持赋值.`); 58 | return null; 59 | } 60 | return Reflect.set(target, property, value, receiver); 61 | }, 62 | }); 63 | 64 | console.log(p.appID); // - 5732e4c9db7ff9f7 65 | console.log(p._appsecret); // - 有属性 _appsecret 不支持访问. 66 | -------------------------------------------------------------------------------- /示例代码/ESNext/03.异步操作/async-await.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-03-07 20:31:44 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-03-07 20:39:25 6 | * @Description: 7 | */ 8 | 9 | // -- 线程休眠 10 | /*function sleep(seconds) { 11 | return new Promise((resolve) => setTimeout(() => resolve(seconds), seconds)); 12 | } 13 | 14 | async function test() { 15 | console.log(1); 16 | await sleep(2000); 17 | console.log(2); 18 | } 19 | test();*/ 20 | 21 | // -- async & await 是否可以取代Promise --- 不可以 22 | function sleep(seconds) { 23 | return new Promise((resolve) => { 24 | setTimeout(() => { 25 | console.log(Math.random()); 26 | resolve(); 27 | }, seconds); 28 | }); 29 | } 30 | 31 | // -- 串行执行 32 | // -- 省市区 33 | async function test() { 34 | await sleep(1000); 35 | await sleep(1000); 36 | await sleep(1000); 37 | } 38 | 39 | // -- 并发执行 -- 还是得用Promise.all 40 | async function test() { 41 | const tasks = []; 42 | for (let i = 0; i < 3; i++) { 43 | tasks.push(sleep(1000)); 44 | } 45 | await Promise.all(tasks); 46 | } 47 | 48 | test(); 49 | -------------------------------------------------------------------------------- /示例代码/ESNext/03.异步操作/generator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-30 17:47:18 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-30 17:54:56 6 | * @Description: 7 | */ 8 | 9 | function* generator1() { 10 | yield 'Hello'; 11 | yield 'World'; 12 | } 13 | 14 | function* generator2() { 15 | yield* generator1(); 16 | yield '!'; 17 | } 18 | 19 | // 遍历组合的生成器 20 | const iterator = generator2(); 21 | for (const item of iterator) { 22 | console.log(item); // Hello, World, ! 23 | } 24 | -------------------------------------------------------------------------------- /示例代码/ESNext/modules/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 模块化 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /示例代码/ESNext/modules/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-08 18:21:03 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-08 18:21:59 6 | * @Description: 7 | */ 8 | console.log('模块化'); 9 | -------------------------------------------------------------------------------- /示例代码/Event/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Document 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /示例代码/Event/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-12-01 16:05:40 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2022-12-01 17:04:32 6 | * @Description: 7 | */ 8 | var button = document.querySelector('.button'); 9 | 10 | // -- 添加事件监听 11 | button.addEventListener('click', handler, false); 12 | // -- 移除事件监听 13 | button.removeEventListener('click', handler, false); 14 | 15 | function handler() { 16 | console.log('Hello'); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /示例代码/Eventloops/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-06 16:14:03 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-06 16:33:09 6 | * @Description: 7 | */ 8 | 9 | 10 | 11 | // → 示例1 12 | // console.log('start'); 13 | // setTimeout(function cb() { 14 | // console.log('setTimeout1'); 15 | // }); 16 | 17 | // console.log('message'); 18 | 19 | // setTimeout(function cb1() { 20 | // console.log('setTimeout2'); 21 | // }, 0); 22 | 23 | // console.log('end'); 24 | 25 | 26 | 27 | 28 | // → 示例2 29 | // console.log('start'); 30 | 31 | // setTimeout(() => { 32 | // console.log('setTimeout'); 33 | // }, 0); 34 | 35 | // Promise.resolve() 36 | // .then(() => { 37 | // console.log('promise1'); 38 | // }) 39 | // .then(() => { 40 | // console.log('promise2'); 41 | // }); 42 | 43 | // console.log('end'); 44 | 45 | // → 示例3 46 | // console.log('start'); // 同步代码,立即执行 47 | 48 | // setTimeout(function () { 49 | // console.log('timeout1'); // 宏任务 50 | // }, 5); 51 | 52 | // new Promise((resolve) => { 53 | // console.log('promise1'); // 同步代码,立即执行 54 | // resolve(); 55 | // setTimeout(() => console.log('timeout2'), 5); // 宏任务 56 | // }).then(function () { 57 | // console.log('then1'); // 微任务 58 | // }); 59 | 60 | // console.log('end'); // 同步代码,立即执行 61 | 62 | // start → promise1 → end → then1 → timeout1 → timeout2 63 | 64 | 65 | -------------------------------------------------------------------------------- /示例代码/JSONs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /示例代码/JSONs/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | JSON - testing 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /示例代码/JSONs/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-11-29 13:46:56 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2022-11-29 14:11:15 6 | * @Description: 7 | */ 8 | 9 | /// 语法形式:JSON.stringify(value[, replacer[, space]]) 10 | 11 | /// -- 转换 12 | 13 | var person = { 14 | name: '张三', 15 | age: 30, 16 | sex: '男', 17 | job: '前端工程师', 18 | }; 19 | 20 | // 1. 转换对象 21 | console.log(JSON.stringify(person)); 22 | // → {"name":"张三","age":30,"sex":"男","job":"前端工程师"} 23 | 24 | // 2. 转换普通值 25 | console.log(JSON.stringify('成都')); // → "成都" 26 | console.log(JSON.stringify(1)); // → "1" 27 | console.log(JSON.stringify(true)); // → "true" 28 | console.log(JSON.stringify(null)); // → "null" 29 | 30 | // 3. 指定replacer参数为:函数 31 | console.log( 32 | JSON.stringify(person, (key, value) => { 33 | // -- 过滤属性值为 number 类型的 key-value 对 34 | return typeof value === 'number' ? undefined : value; 35 | }) 36 | ); 37 | // → {"name":"张三","sex":"男","job":"前端工程师"} 38 | 39 | // 4. 指定replacer参数为:数组 40 | console.log(JSON.stringify(person, ['name', 'age'])); 41 | // → {"name":"张三","age":30} 42 | 43 | // 5. 指定space(美化输出) 44 | console.log(JSON.stringify(person, null, 2)); 45 | 46 | /*{ 47 | "name": "张三", 48 | "age": 30, 49 | "sex": "男", 50 | "job": "前端工程师" 51 | }*/ 52 | 53 | // -- 特性1 54 | console.log(JSON.stringify(() => {})); 55 | console.log(JSON.stringify(Symbol('Tag'))); 56 | console.log(JSON.stringify(undefined)); 57 | 58 | // -- 特性2 59 | console.log( 60 | JSON.stringify({ 61 | name: new String('张三'), 62 | age: new Number(30), 63 | checked: new Boolean(false), 64 | }) 65 | ); 66 | // → {"name":"张三","age":30,"checked":false} 67 | 68 | // -- 特性五 69 | console.log( 70 | JSON.stringify({ 71 | name: '张三', 72 | job: '前端工程师', 73 | toJSON: function () { 74 | return `${this.name} - ${this.job}`; 75 | }, 76 | }) 77 | ); 78 | 79 | // → 张三 - 前端工程师 -------------------------------------------------------------------------------- /示例代码/Object/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Objects 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /示例代码/Object/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-11-29 10:44:31 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2022-11-29 11:43:40 6 | * @Description: 7 | */ 8 | 9 | var o1 = { name: '张三' }; 10 | var o2 = { major: '软件技术', name: '李四' }; 11 | 12 | var result = Object.assign(o1, o2); 13 | 14 | console.log(result); // {name: '李四', major: '软件技术'} 15 | console.log(result === o1); 16 | -------------------------------------------------------------------------------- /示例代码/PostMessage/index/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /示例代码/PostMessage/index/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | INDEX PAGE 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 | 28 | 29 | 30 | 45 | 46 | -------------------------------------------------------------------------------- /示例代码/PostMessage/other/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | OTHER PAGE 17 | 18 | 19 | 20 |
Sub window recived message is:
21 |
22 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /示例代码/Promise/01.基础用法/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2023-05-08 19:42:43 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2023-05-08 20:25:11 6 | * @Description: 7 | */ 8 | 9 | // 1. 基础用法 10 | const login = (username, password) => { 11 | return new Promise((resolve, reject) => { 12 | /** 模拟异步操作 */ 13 | setTimeout(() => { 14 | if (username === 'admin' && password === '123') { 15 | resolve(); 16 | } else { 17 | reject(); 18 | } 19 | }, 2000); 20 | }); 21 | }; 22 | login('admin', '123').then( 23 | () => { 24 | console.log('成功'); 25 | }, 26 | (error) => { 27 | console.log('失败'); 28 | } 29 | ); 30 | 31 | // 2. then 32 | 33 | // -- 改变值 34 | const promise = new Promise((resolve) => resolve('ACDB')); 35 | promise 36 | .then((resp) => { 37 | console.log(resp); // → ACDB 38 | return resp.split(''); 39 | }) 40 | .then((letters) => { 41 | console.log(letters); // → [ 'A', 'C', 'D', 'B' ] 42 | return letters.sort(); 43 | }) 44 | .then((sorted) => { 45 | console.log(sorted); // → [ 'A', 'B', 'C', 'D' ] 46 | return sorted.join(''); 47 | }) 48 | .then((result) => { 49 | console.log(result); // → ABCD 50 | }); 51 | 52 | // 3. catch 53 | 54 | login() 55 | .then(() => { 56 | console.log('Login success.'); 57 | }) 58 | .catch((error) => { 59 | console.log('Login failure.'); 60 | }); 61 | 62 | // 4. Promise.all 63 | // const promise1 = Promise.resolve(3); 64 | // const promise2 = 42; 65 | // const promise3 = new Promise((resolve, reject) => { 66 | // setTimeout(() => { 67 | // resolve('foo'); 68 | // }, 100); 69 | // }); 70 | 71 | // Promise.all([promise1, promise2, promise3]).then((values) => { 72 | // console.log(values); // 输出:[ 3, 42, 'foo' ] 73 | // }); 74 | 75 | // 5. Promise.race 76 | const promise1 = new Promise((resolve, reject) => { 77 | setTimeout(resolve, 500, 'one'); 78 | }); 79 | 80 | const promise2 = new Promise((resolve, reject) => { 81 | setTimeout(resolve, 100, 'two'); 82 | }); 83 | 84 | Promise.race([promise1, promise2]).then((value) => { 85 | console.log(value); 86 | // Both resolve, but promise2 is faster 87 | }); 88 | // Expected output: "two" 89 | -------------------------------------------------------------------------------- /示例代码/doms/index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | DOMs 14 | 15 | 16 |
成都哈戳戳科技有限公司
17 |
    18 |
  • 教学部
  • 19 |
  • 教务部
  • 20 |
  • 市场部
  • 21 |
  • 咨询部
  • 22 |
  • 行政部
  • 23 |
  • 财务部
  • 24 |
25 |
成都市高新区新川科技园A区
26 |
DOMs
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /示例代码/doms/js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-03-29 17:12:53 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2022-11-30 10:44:25 6 | */ 7 | 8 | // -- 查找DOM 9 | // 1. 根据ID查找 10 | console.log(document.getElementById('company')); 11 | // 2. 根据name属性查找 12 | console.log(document.getElementsByName('address')); 13 | // 3. 根据类名查找 14 | console.log(document.getElementsByClassName('departments')); 15 | // 4. 根据标签名查找 16 | console.log(document.getElementsByTagName('li')); 17 | // 5. 根据CSS选择器查找 18 | console.log(document.querySelector('#company')); 19 | console.log(document.querySelectorAll('.departments li')); 20 | 21 | var list = document.querySelector('.departments'); 22 | console.log(list.previousElementSibling); 23 | console.log(list.previousSibling); 24 | console.log(list.nextElementSibling); 25 | console.log(list.nextSibling); 26 | console.log(list.parentElement); 27 | console.log(list.parentNode); 28 | console.log(list.children); 29 | console.log(list.childElementCount); 30 | console.log(list.childNodes); 31 | console.log(list.firstElementChild); 32 | console.log(list.lastElementChild); 33 | 34 | // -- 节点操作 35 | // 1. 创建节点 36 | const el = document.createElement('div'); 37 | const attr = document.createAttribute('class'); 38 | const fragment = document.createDocumentFragment(); 39 | 40 | const li = document.createElement('li'); 41 | li.textContent = '纪检部'; 42 | // list.appendChild(li); 43 | // list.append(li); 44 | list.prepend(li); 45 | 46 | 47 | -------------------------------------------------------------------------------- /示例代码/doms/js/lib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-03-29 17:22:47 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2022-11-29 15:31:10 6 | */ 7 | 8 | function showDomInfos(sel) { 9 | const el = document.querySelector(sel); 10 | console.table({ 11 | nodeName: el.nodeName, 12 | nodeValue: el.nodeValue, 13 | nodeType: el.nodeType, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /示例代码/requestAnimationFrame/index.css: -------------------------------------------------------------------------------- 1 | .progress { 2 | width: 300px; 3 | height: 10px; 4 | border-radius: 12px; 5 | background: #eee; 6 | margin-bottom: 16px; 7 | } 8 | .v { 9 | height: 100%; 10 | background: cornflowerblue; 11 | border-radius: 12px; 12 | } -------------------------------------------------------------------------------- /示例代码/requestAnimationFrame/index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | requestAnimationFrame 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /示例代码/requestAnimationFrame/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Lee 3 | * @Date: 2022-12-07 17:03:47 4 | * @LastEditors: Lee 5 | * @LastEditTime: 2022-12-12 11:42:35 6 | * @Description: 7 | */ 8 | 9 | /**************** 10 | * 1.页面可见性:visibilitychange 11 | */ 12 | window.addEventListener( 13 | 'visibilitychange', 14 | () => { 15 | console.log('document.hidden = ' + document.hidden); 16 | }, 17 | false 18 | ); 19 | 20 | /**************** 21 | * 2.使用·动画 22 | */ 23 | // -- 获取DOM元素 24 | let progress = document.querySelector('.v'); 25 | // -- 记录步长 26 | let step; 27 | // -- 记录回调函数 28 | let handlerId; 29 | // -- 动画帧回调函数 30 | function render() { 31 | step++; 32 | progress.style.width = `${step}%`; 33 | if (step < 100) { 34 | handlerId = window.requestAnimationFrame(render); 35 | } 36 | } 37 | // -- 点击Loading按钮时触发,调用动画效果 38 | function onLoading() { 39 | step = 0; 40 | progress.style.width = '0%'; 41 | render(); 42 | } 43 | // -- 点击Stop按钮时触发,停止动画 44 | function onStop() { 45 | cancelAnimationFrame(handlerId); 46 | } 47 | 48 | /**************** 49 | * 3.使用·函数节流 50 | */ 51 | /*window.onmousemove = ({ clientX, clientY }) => { 52 | requestAnimationFrame(() => { 53 | console.log(clientX, clientY); 54 | }); 55 | };*/ 56 | 57 | /**************** 58 | * 4.使用·分帧初始化 59 | */ 60 | /*class A {} 61 | class B {} 62 | class C {} 63 | class D {} 64 | var lazyLoadList = [A, B, C, D]; 65 | lazyLoadList.forEach((module) => { 66 | window.requestAnimationFrame(() => { 67 | new module(); 68 | }); 69 | });*/ 70 | 71 | -------------------------------------------------------------------------------- /第02章 程序结构.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | 程序的基本结构包括**顺序结构**、**分支结构**(选择结构)和**循环结构**,它们可构成所有复杂程序。JavaScript 提供多种语句来实现这些结构。 4 | 5 | # 顺序结构 6 | 7 | 顺序结构指程序按代码顺序从上到下依次执行,每条语句依次执行,不发生跳转。 8 | 9 | # 分支结构 * 10 | 11 | 分支结构用于根据条件执行不同的代码,主要包括 **条件语句**(if 语句)和**分支语句**(switch 语句)。 12 | 13 | ## 条件语句 14 | 15 | ### if 语句 16 | 17 | if 语句用于判断条件是否为 true,若成立,则执行对应的代码块。。其语法形式为: 18 | 19 | ```javascript 20 | if (condition) { 21 | // code... 22 | }; 23 | ``` 24 | 25 | ```javascript 26 | var age = 26; 27 | if(age >= 18) { 28 | console.log("成年"); 29 | } 30 | ``` 31 | 32 | 我们再来看一个复杂一点的关于求闰年的练习: 33 | 34 | ```javascript 35 | var year = 2019; 36 | if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) { 37 | console.log(year + '是闰年'); 38 | } 39 | ``` 40 | 41 | > 提示:闰年是能被4整除但不能被100整除,或者能被400整除的年份,因此,我们可以得出判断条件: 42 | > 43 | > 1. *`year % 4 == 0 && year % 100 != 0`* 44 | > 45 | > 2. *`year % 400 == 0`* 46 | > 47 | >只要这个条件为真,则是闰年,否则为平年。 48 | 49 | ### if-else 语句 50 | 51 | if-else 语句用于根据条件判断执行不同的代码块。如果条件为 true,执行第一个代码块;如果条件为 false,执行第二个代码块。其语法形式如下: 52 | 53 | ```javascript 54 | if(condition) { 55 | // 代码块1 56 | }else { 57 | // 代码块2 58 | } 59 | ``` 60 | 61 | 我们来看一个例子,判断用户是否登录成功,如果用户登录成功,则弹框提示‘恭喜您,登录成功!’,否则弹框提示‘对不起,登录失败!’。 62 | 63 | ```html 64 | 65 | 66 |
67 | 68 |
69 | 70 |

71 | 72 |
73 | ``` 74 | 75 | ```javascript 76 | // index.js 77 | 78 | // 获取元素 79 | let accountIpt = document.getElementById('account'); 80 | let passwordIpt = document.getElementById('password'); 81 | let loginBtn = document.getElementById('loginBtn'); 82 | 83 | // 为loginBtn添加点击事件 84 | loginBtn.onclick = function() { 85 | // 获取账号密码值 86 | var accountText = accountIpt.value; 87 | var passwordText = passwordIpt.value; 88 | 89 | // 判断用户是否登录成功,正确账号:Admin 密码:123 90 | if ((accountText == "Admin") && (passwordText == "123")) { 91 | alert('恭喜您,登录成功!'); 92 | } else { 93 | alert('对不起,登录失败!'); 94 | } 95 | } 96 | ``` 97 | 98 | 上述登录按钮点击事件触发函数中,通过 `if` 语句判断,在判断条件中,由于登录成功需要账号密码同时正确才满足条件,因此我使用了 `&&` 逻辑运算符,程序首先会去判断 `&&` 运算符前后的两个表达式的值,如果两个表达式的值同时为true,则会执行第一个代码块,否则会执行第二个代码块的内容。 99 | 100 | > Tips:一般情况下,*else* 匹配的是排除if条件之外的所有情况。对于 *if-else* 语句,我们可以转换为三目运算符(*a ? b : c*) 来表示。 101 | 102 | ### if-else if-else 语句 103 | 104 | `if` 语句还有更复杂的使用方法,即 `if-else if-else` 语句,前两种形式的if语句一般都用于两个分支的情况。当有多个分支选择时,可采用 `if-else if-else` 语句。我们来看一个示例: 105 | 106 | ```javascript 107 | var score = 92; 108 | if(score >= 90 && score <= 100) { 109 | console.log('优秀!'); 110 | }else if(score >= 70 && score < 90) { 111 | console.log('良好!'); 112 | }else if(score >= 60 && score < 70) { 113 | console.log('及格!'); 114 | }else if(score >= 0 && score < 60) { 115 | console.log('不及格!'); 116 | }else { 117 | console.log('成绩有误!'); 118 | } 119 | // 注意:else 如果后面没有跟上if,则表示排除以上所有情况之外的情况,在这里当成绩小于0或成绩大于100时执行; 120 | ``` 121 | 122 | `if-else if-else` 语句,依次判断表达式的值,当出现某个值为真时,则执行其对应的语句,然后跳到整个if语句之外继续执行程序。如果所有的表达式均为假,则执行 *else* 后的代码块,然后继续执行后续程序。 123 | 124 | > tips:在使用 *if* 语句中还应注意以下问题: 125 | > 126 | > - 在三种形式的if语句中,`if` 关键字之后均为表达式,该表达式通常是逻辑表达式或关系表达式,但也可以是其他表达式,如赋值表达式等,甚至也可以是一个变量。 127 | > - 在 `if` 语句中,条件判断表达式必须用括号括起来,在语句之后必须加分号。 128 | > - 嵌套使用 `if` 语句应该注意,`else` 总是与它前面最近的 `if` 配对。 129 | 130 | ## switch语句 131 | 132 | `switch` 语句的开头是一个被称作分支值的变量,每个case表示一个条件,当条件的值和这个变量的值匹配时,它后面的代码就会被执行,下面是switch语句的语法: 133 | 134 | ```javascript 135 | switch (表达式) { 136 | case 常量表达式1: 语句1;break; 137 | case 常量表达式2: 语句2;break; 138 | case 常量表达式3: 语句3;break; 139 | ... 140 | case 常量表达式n: 语句n;break; 141 | default: 语句n+1; 142 | } 143 | ``` 144 | 145 | > 上述表达式的语义是:计算表达式的值,并逐个与其后的常量表达式值比较。当表达式的值与某个常量表达式的值相等时,即执行其后的语句,然后不再进行判断,继续执行后面所有case后的语句。如表达式的值与所有case的常量表达式均不相同时,则执行 `default` 后的语句。语句后的 `break` 用于跳出switch,执行switch后面的内容。 146 | 147 | ```javascript 148 | var week = 2; 149 | switch (week) { 150 | case 1: { console.log('星期一'); } break; 151 | case 2: { console.log('星期二'); } break; // 输出星期二 152 | case 3: { console.log('星期三'); } break; 153 | case 4: { console.log('星期四'); } break; 154 | case 5: { console.log('星期五'); } break; 155 | case 6: { console.log('星期六'); } break; 156 | case 7: { console.log('星期天'); } break; 157 | default: { console.log("异常!") } 158 | } 159 | ``` 160 | 161 | 上述示例中,判断 `week` 的值,与每一个case值匹配,匹配到值之后执行相应的代码块,然后执行 `break` 跳出switch语句,因此上述示例输出 “*星期二* ”。如果没有添加 `break` 关键字,程序会执行从匹配到的case分支语句表达式及其之后的所有case分支语句表达式。 162 | 163 | ```javascript 164 | var cooking = '川菜'; 165 | switch (cooking) { 166 | case '川菜': { 167 | console.log('麻婆豆腐,回锅肉,宫保鸡丁。'); 168 | } break; 169 | case '粤菜': { 170 | console.log('梅菜扣肉,清蒸鲈鱼。'); 171 | } break; 172 | case '湘菜': { 173 | console.log('剁椒鱼头,霸王别姬。'); 174 | } break; 175 | case '鲁菜': { 176 | console.log('葱爆羊肉, 红烧海螺,锅塌豆腐。'); 177 | }break; 178 | 179 | default: { 180 | console.log('本店没有该菜系!'); 181 | } 182 | } 183 | // "麻婆豆腐,回锅肉,宫保鸡丁。" 184 | ``` 185 | 186 | `switch` 语句匹配区间: 187 | 188 | ```javascript 189 | var score = 60; 190 | switch(true) { 191 | case score >= 80 && score <= 100: { 192 | console.log("优秀"); 193 | } break; 194 | 195 | case score >= 60 && score < 80: { 196 | console.log("良好"); 197 | } break; 198 | 199 | case score >= 0 && score < 60: { 200 | console.log("不及格"); 201 | }break; 202 | 203 | default:{ 204 | console.log("成绩有误!"); 205 | } 206 | } 207 | ``` 208 | 209 | > tips: 210 | > 211 | > - switch语句括号中的表达式以及case分支后的表达式可以是数字或字符串或布尔值; 212 | > - 在case后的各常量表达式的值不能相同,否则会出现错误; 213 | > - 在case后,允许有多个语句,可以不用`{}`括起来,一般不建议这样做; 214 | > - 各case和default子句的先后顺序可以变动,而不会影响程序执行结果; 215 | > - default子句可以省略不用; 216 | 217 | *if-else* 语句与 *switch* 语句比较: 218 | 219 | 1. **适用场景**: 220 | - **if-else**:适用于多种复杂条件判断,条件可以是多种类型的表达式(如比较运算、逻辑运算等)。 221 | - **switch**:适用于判断多个固定值的场景,通常用于针对同一个变量或表达式进行不同值的处理。 222 | 2. **代码结构**: 223 | - **if-else**:结构灵活,可以有多个条件判断,适合复杂的逻辑。 224 | - **switch**:结构较为简洁,针对单一条件值进行多分支判断,适合处理多个相同类型的值。 225 | 3. **性能**: 226 | - **if-else**:在处理较少条件时效率较高,但在条件较多时,性能可能不如 switch。 227 | - **switch**:在处理较多相等判断时效率更高,尤其当有多个 case 时,switch 可以通过跳转表来提高性能。 228 | 4. **可读性**: 229 | - **if-else**:在复杂逻辑中可读性较好,但过多的嵌套可能会导致代码不易阅读。 230 | - **switch**:适合多个条件相等判断时,代码清晰易懂。 231 | 232 | # 循环结构 233 | 234 | 循环结构是程序中一种非常重要的控制结构,其特点是在满足给定条件时,反复执行程序中的某一段代码,直到条件不再成立。给定的条件称为**循环条件**,反复执行的代码段称为**循环体**。JavaScript 提供了三种常见的循环语句,可以构建不同类型的循环结构。以下是三种循环的对比: 235 | 236 | | 循环类型 | 说明 | 237 | | ------------ | ------------------------------------------------------------ | 238 | | **for** | 用于需要执行 **特定次数** 的情况。常常通过计数器来控制循环次数。 | 239 | | **while** | 用于循环 **次数不确定** 的情况。循环会一直执行,直到条件为 `false`。 | 240 | | **do while** | 与 `while` 循环类似,唯一的区别是:无论条件是否为 `true`,循环体至少执行一次。 | 241 | 242 | ## for * 243 | 244 | `for` 循环的语法结构: 245 | 246 | ```javascript 247 | for (初始化语句; 循环条件; 迭代增量) { 248 | 循环体 249 | } 250 | ``` 251 | 252 | > 语法解读: 253 | > 254 | > - **初始化**:创建一个变量,然后赋值为0。这个变量通常被命名为`i`,它起到计数器的作用; 255 | > - **循环条件**:循环会一直重复运行下去,直到计数器达到特定的数值; 256 | > - **迭代增量**:每次循环执行完花括号内部的语句之后,计数器会加1; 257 | 258 | 我们来看一个示例: 259 | 260 | ```javascript 261 | for (var i = 0; i < 5; i++) { 262 | console.log("Hello, world!"); 263 | } 264 | ``` 265 | 266 | 上述示例中,程序会去执行初始化语句,此时`i` 的值为 `0`,然后去判断循环条件,如果 `i < 5` 为 `true`,则执行循环体的内容,打印 *“Hello, world!”*,然后再执行 `i++`,此时 `i` 的值为 `2`,到这里一次循环结束;第1次循环结束之后,初始化语句不再执行,初始化语句只执行一次,第2次循环直接继续判断循环条件,`2 < 5` 为 `true`,那么会执行循环体,再打印一次 *“Hello, world!”*,然后再执行迭代增量 `i++`,此时第2次循环结束。以此类推,当 `i` 的值为 `5` 的时候,判断循环条件 `5 < 5` 为 `false`,则跳出循环,故控制台会打印**5**次 *“Hello, world!”*。 267 | 268 | > tips: 269 | > 270 | > - for循环中,**初始化**、**循环条件**、**迭代增量**都是选择项,即可缺省,但 `;` 不能省略; 271 | > 272 | > - 省略 **初始化语句**,表示不对循环控制变量赋初始值; 273 | > 274 | > - 省略 **循环条件**,则不做其他处理时会进入死循环; 275 | > 276 | > - 省略 **迭代增量**,则不对循环控制变量进行操作,这时可在语句体中加入修改循环控制变量的语句,比如在循环体中,做增量操作; 277 | > 278 | > - 三个表达式都可以省略: 279 | > 280 | > ```javascript 281 | > for (; ;) {} 282 | > ``` 283 | > 284 | > - **初始化**可以是设置循环变量的初值赋值表达式,也可以是其他表达式: 285 | > 286 | > ```javascript 287 | > // 形式1 288 | > let sum = 0, i = 0; 289 | > for (; i < 100; i++) { 290 | > sum += i; 291 | > } 292 | > // 形式2 293 | > let sum, i; 294 | > for(i = 0, sum = 0; i < 100; i++) { 295 | > sum += i; 296 | > } 297 | > ``` 298 | > 299 | > - **初始化**语句和**迭代增量**可以是一个简单表达式,也可以是一个逗号表达式; 300 | > 301 | > - **循环条件**一般是关系表达式或逻辑表达式,但也可以是数值表达式或字符表达式,只要其值为true,就执行循环体; 302 | 303 | ## while 304 | 305 | `while` 循环语法结构: 306 | 307 | ```javascript 308 | 初始化语句 309 | while(循环条件) { 310 | 循环体 311 | 迭代增量 312 | } 313 | ``` 314 | 315 | > 语法解读:`while` 循环先去判断循环条件,如果结果为 `true`,则执行循环体内容,否则结束循环。 316 | 317 | 我们来看一个示例,计算0~100的和: 318 | 319 | ```javascript 320 | var sum = 0, i = 0; 321 | while (i <= 100) { 322 | sum += i; 323 | i++; 324 | } 325 | console.log('sum is ' + sum); 326 | ``` 327 | 328 | 上述示例输出语句中为字符串的拼接,在控制台中,*sum* 变量会呈现其实际值,故而控制台呈现*结果为:5050*。 329 | 330 | ## do…while 331 | 332 | `do…while` 循环语法结构: 333 | 334 | ```javascript 335 | do { 336 | 循环体 337 | }while(循环条件) 338 | ``` 339 | 340 | > 语法解读:*do...while* 循环会先去执行循环体的内容,再判断循环条件,如果循环条件为结果 `true`,再执行循环体内容,如果循环条件结果为 `false`,则跳出循环。*do...while* 与 *while* 的区别在于,*do...while* 循环先执行循环体,再执行循环条件;`while` 循环,先判断循环条件,再执行循环体;*do...whilte* 循环至少执行一次循环体。 341 | 342 | 同样地,我们来看一个使用 `do…while` 循环实现计算0~100的和的示例: 343 | 344 | ```javascript 345 | var sum = 0, i = 0; 346 | do { 347 | sum += i; 348 | i++ 349 | } while (i <= 100); 350 | console.log('sum is ' + sum); 351 | ``` 352 | 353 | ## 循环嵌套 354 | 355 | 两个结论: 356 | 357 | ```javascript 358 | var i, j, str; 359 | // 外层循环 360 | for(i = 0; i < 2; i++) { 361 | // 内层循环 362 | for(j = 0; j < 3; j++) { 363 | str = ''; 364 | str += 'i = ' + i + ' '; 365 | str += 'j = ' + j; 366 | console.log(str); 367 | } 368 | } 369 | // 结论:外层循环执行一次,内层循环执行n次。 370 | ``` 371 | 372 | ```javascript 373 | var i, j, str; 374 | for(i = 0; i < 4; i++) { 375 | for(j = 0; j < 5; j ++) { 376 | str += '*'; 377 | } 378 | str += '\n'; 379 | } 380 | console.log(str); 381 | // 结论:外层循环控制行数,内层循环控制列数。 382 | 383 | // ***** 384 | // ***** 385 | // ***** 386 | // ***** 387 | ``` 388 | 389 | 循环语句通过嵌套可以解决一些有规律的复杂性的重复性工作,如我们在小学期间就学习过的“九九乘法表”。 390 | 391 | ```javascript 392 | var str = ''; 393 | for(let i = 1; i <= 9; i++) { 394 | str = ''; 395 | for(let j = 1; j <= i; j++) { 396 | str += j + '*' + i + '=' + j * i + ' '; 397 | } 398 | console.log(str); 399 | } 400 | ``` 401 | 402 |   输出结果为: 403 | 404 | ![图片](IMGS/part_2_1.png) 405 | 406 | 如果不了解循环运作的原理,一开始看到这样的结果必然不理解,实际在理解上,嵌套循环和在一个函数里再嵌套一个函数非常类似。可以把循环看成一个主流程上的点,主循环(外层的循环)形成一个环状的流程,循环的开始点和结束点和主流程相交,而子循环(内层的循环)又单独形成一个环,子循环的开始点和结束点又和外层循环上的某个点想交。每次子循环结束的时候,又会回到子循环内,进行新一轮的条件循环,当主循环的条件再次得到的值为真的时候,又会根据条件判断语句(如果有的话)是否决定再次进入子循环,以此类推。 407 | 408 | ![图片](IMGS/part_2_2.jpg) 409 | 410 | > Tips:外层循环执行一次,内层循环执行n次;外层循环决定行数,内层循环决定列数; 411 | 412 | 接下来我们看几个利用循环嵌套打印星星的示例: 413 | 414 | ```javascript 415 | // 示例1 416 | /* 417 | * i = 1 space = 3 star = 1 418 | *** i = 2 space = 2 star = 3 419 | ***** i = 3 space = 1 star = 5 420 | ******* i = 4 space = 0 star = 7 421 | row = 4 422 | space = row - i; star = 2 * i - 1 423 | */ 424 | 425 | let row = 4; 426 | let spaces, stars, str = ``; 427 | for(let i = 1; i <= row; i++) { 428 | spaces = row - i; 429 | stars = 2 * i - 1; 430 | // 打印空格 431 | for(let j = 1; j <= spaces; j++) { 432 | str += ` `; 433 | } 434 | // 打印星星 435 | for(let k = 1; k <= stars; k++) { 436 | str += `*`; 437 | } 438 | str += `\n`; 439 | } 440 | console.log(str); 441 | ``` 442 | 443 | ```javascript 444 | // 示例2 445 | /* 446 | * i = 1 space = 2 4 star = 1 447 | *** i = 2 space = 1 2 star = 3 448 | ***** i = 3 space = 0 0 star = 5 449 | *** i = 4 space = 1 2 star = 3 450 | * i = 5 space = 2 4 star = 1 451 | 452 | 提示: 453 | Math.ceil() 向上取整 454 | Math.abs() 取绝对值 455 | 456 | row = 5 457 | space = Math.abs(Math.ceil(row/2) - i); 458 | star = row - 2 * space 459 | */ 460 | 461 | let row = 5; 462 | let spaces, stars, str = ``; 463 | for(let i = 1; i <= row; i++) { 464 | spaces = Math.abs(Math.ceil(row/2) - i); 465 | stars = row - 2 * spaces; 466 | // 打印空格 467 | for(let j = 1; j <= spaces; j++) { 468 | str += ` `; 469 | } 470 | // 打印星星 471 | for(let k = 1; k <= stars; k++) { 472 | str += `*`; 473 | } 474 | str += `\n`; 475 | } 476 | console.log(str); 477 | ``` 478 | 479 | # 流程控制 * 480 | 481 | ## break 482 | 483 | `break` 关键字通常用在循环语句和分支语句中。当break用于分支语句switch中时,可以使程序跳出switch而执行switch以后的语句。如果没有break关键字,则将成为一个死循环而无法退出。break在switch中的用法已在前面的介绍到,这里不再举例。 484 | 485 | 当 `break` 关键字用于do...while、for、while循环语句中时,可以使程序终止循环而执行循环后面的语句,通常break语句总是与if语句联在一起,即满足条件时退出。 486 | 487 | ```javascript 488 | // 示例1 489 | for (var i = 0; i < 5; i++) { 490 | if (i > 3) { 491 | break; 492 | }else { 493 | console.log('idx = ' + i); 494 | } 495 | } 496 | // 输出: 497 | // idx = 0 498 | // idx = 1 499 | // idx = 2 500 | // idx = 3 501 | ``` 502 | 503 | ```javascript 504 | // 示例2 505 | var citys = ["北京", "上海", "深圳", "重庆", "成都", "昆明", "西安"]; 506 | for (var i = 0; i < citys.length; i++) { 507 | if (citys[i] === "成都") { 508 | console.log("已经找到" + citys[i] + ",其在数组中的下标值为:" + i); 509 | break; 510 | } 511 | } 512 | // 输出:已经找到成都,其在数组中的下标值为:4 513 | ``` 514 | 515 | > tips:在多层循环中,一个break关键字只向外跳出一层循环; 516 | 517 | ## continue 518 | 519 | `continue` 关键字的作用是跳过循环体中剩余的语句而执行下一次循环。continue语句只用在for、while、do...while循环体中,常与if条件语句一起使用,用来加速循环。 520 | 521 | ```javascript 522 | window.onload = function() { 523 | for (var i = 0; i < 5; i++) { 524 | if (i = 3) { // 如果i的值为3,则跳出这一次循环,执行下一次循环 525 | continue; 526 | } 527 | console.log(i); 528 | } 529 | } 530 | ``` 531 | 532 | 上述示例中,当i的值为3时,跳出本次循环,不再打印,继续执行下一次循环,故而控制台输出:*0,1,2,4*。 533 | 534 | # 课后练习 535 | 536 | ```js 537 | 1. 大家都知道,男大当婚,女大当嫁。那么女方家长要嫁女儿,当然要提出一定的条件(if|switch) 538 | 高:180cm以上; 富:1000万以上; 帅:500以上; 539 | 如果这三个条件同时满足,则:'我一定要嫁给他' 540 | 如果三个条件有为真的情况,则:'嫁吧,比上不足,比下有余。' 541 | 如果三个条件都不满足,则:'不嫁!' 542 | 543 | 2. 从键盘输入小明的期末成绩(if|switch) 544 | 当成绩为100时,'奖励一辆BMW' 545 | 当成绩为[80-99]时,'奖励一台iphone15s' 546 | 当成绩为[60-80]时,'奖励一本参考书' 547 | 其他时,什么奖励也没有 548 | 549 | 3. 从键盘输入电影类型(if|switch) 550 | 当类型为"恐怖"时,返回你所知道的恐怖电影 551 | 当类型为"喜剧"时,返回你所知道的喜剧电影 552 | 当类型为"科幻"时,返回你所知道的科幻电影 553 | 当类型为"动作"时,返回你所知道的动作电影 554 | 555 | 4. 打印输出 "9*9" 乘法表(for) 556 | 557 | 5. 计算 "50~100" 的和(for|do...while|while) 558 | 559 | 6. 输入月、日,计算是今年的第几天,2月统一为28天(switch) 560 | ``` 561 | -------------------------------------------------------------------------------- /第03章 数值.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | 在 JavaScript 中,[Number](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number) 是一种**基本数据类型**,在程序中无处不在,广泛应用于逻辑实现、条件判断等方面。比如数组操作、循环控制等都离不开数值类型。没有数值型数据,许多程序逻辑都无法实现。 4 | 5 | 在实际项目中,特别是金融类应用,如果没有对 Number 数据类型的深入理解,最终的产品质量难以保障,数据的准确性也可能受到质疑。随着互联网软件对质量要求的提高,缺乏对数值类型的精确处理,产品难以满足上线标准。 6 | 7 | 根据 IEEE 754 标准,JavaScript 中的所有数值都以 **64位双精度浮点数** 形式存储。即使看起来是整数,例如 1 和 1.0,实际上它们都表示相同的浮点数值。 8 | 9 | # 数值型的精度问题 10 | 11 | 在进行浮点数计算时,由于计算机对浮点数的存储和运算存在精度限制,常常会遇到舍入误差和精度丢失等问题,示例如下: 12 | 13 | ```javascript 14 | 0.1 + 0.2 === 0.3; // false 15 | 16 | 0.1 + 0.2; // 0.30000000000000004 17 | 0.3 / 0.1; // 2.9999999999999996 18 | 0.3 - 0.2; // 0.09999999999999998 19 | ``` 20 | 21 | 因此,在进行浮点数计算时,需要特别注意这些精度问题,并尽量使用合适的处理方法来提高计算精度。 22 | 23 | 根据 **IEEE 754** 标准,JavaScript 能精确表示的数值范围为:$$ -(2^{53} - 1) $$ 至 $$ 2^{53} - 1 $$ 之间。 24 | 25 | ```js 26 | console.log(Math.pow(2, 53) - 1); // 9007199254740991 27 | console.log(Math.pow(2, 53)); // 9007199254740992 28 | console.log(Math.pow(2, 53) + 1); // 9007199254740992 29 | console.log(Math.pow(2, 53) + 2); // 9007199254740994 30 | console.log(Math.pow(2, 53) + 3); // 9007199254740996 31 | console.log(Math.pow(2, 53) + 4); // 9007199254740996 32 | console.log(Math.pow(2, 53) + 5); // 9007199254740996 33 | ``` 34 | 35 | 从上述示例中可以看出,当数值大于 $$ 2^{53} $$ 时,整数运算开始出现精度丢失问题。大于等于 $$ 2^{53} $$ 的数值无法保持精度,并且会自动省略掉。 36 | 37 | 此外,JavaScript 还支持使用科学计数法表示数值,即在数字后面添加 “e” 或 “E” 表示指数: 38 | 39 | ```javascript 40 | 123e2 // 12300 41 | -123e2 // -12300 42 | 43 | 0.0314e2 // 3.14 44 | -0.0314e2 // -3.14 45 | ``` 46 | 47 | JavaScript 会在以下两种情况下自动将数值转换为科学计数法: 48 | 49 | 1. 数点前面的数字超过 21 位。 50 | 2. 小数点后面连续的 “0” 超过 5 位。 51 | 52 | 示例: 53 | 54 | ```js 55 | // 小数点前的整数小于等于21位不会被转换 56 | console.log(123456789012345678901); // 123456789012345680000 57 | // 小数点前的整数大于21位会被转换 58 | console.log(1234567890123456789012); // 1.2345678901234568e+21 59 | // 小数点后0的位数小于等于5位不会被转换 60 | console.log(0.00000123); // 0.00000123 61 | // 小数点后0的位数大于5位会被转换 62 | console.log(0.000000123); // 1.23e-7 63 | ``` 64 | 65 | # [NaN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN) * 66 | 67 | ## 含义 68 | 69 | 在数值型数据中,有一个特殊值 `NaN`,它表示的是 **非数字**(**N**ot-**A**-**N**umber),主要出现在将字符串解析成数字出错的场合。 70 | 71 | ```javascript 72 | Number("x"); 73 | NaN 74 | ``` 75 | 76 | 上面代码运行时,会自动将字符串*x*转为数值,但是由于*x*不是数值,所以最后得到结果为 `NaN`,表示它是非数字。 77 | 78 | 另外,一些数学函数及运算操作的结果会出现 `NaN`。 79 | 80 | ```js 81 | console.log(Math.acos(2)); // NaN 82 | console.log(Math.log(-1)); // NaN 83 | console.log(Math.sqrt(-1)); // NaN 84 | console.log(0/0); // NaN 85 | ``` 86 | 87 | 需要注意的是,`NaN` 不是一种独立的数据类型,而是一种特殊数值,它的数据类型依然属于 `number`,使用 `typeof` 运算符可以查看数据类型。 88 | 89 | ```javascript 90 | console.log(typeof NaN); // number 91 | ``` 92 | 93 | ## 运算规则* 94 | 95 | > 1. `NaN` 不等于任何值,包括它本身。 96 | 97 | ```javascript 98 | NaN === NaN; // false 99 | ``` 100 | 101 | > 2. `NaN` 在布尔运算时被当作 `false`。 102 | 103 | ```javascript 104 | Boolean(NaN); // false 105 | ``` 106 | 107 | > 3. `NaN` 与任何数(包括它自己)的运算,得到的都是 `NaN`。 108 | 109 | ```javascript 110 | NaN + 32; // NaN 111 | NaN - 32; // NaN 112 | NaN * 32; // NaN 113 | NaN / 32; // NaN 114 | ``` 115 | 116 | ## 判断NaN的方法 117 | 118 | 通过 `isNaN()` 可以判断某个值是否为NaN: 119 | 120 | ```js 121 | console.log(isNaN('Li-HONHGYAO')); // true 122 | console.log(isNaN(123)); // false 123 | console.log(isNaN(3.14)); // false 124 | console.log(isNaN([1, 2, 3])); // true 125 | console.log(isNaN({ x: 0 })); // true 126 | console.log(isNaN(function () {})); // true 127 | console.log(isNaN(true)); // false 128 | console.log(isNaN(false)); // false 129 | ``` 130 | 131 | 可以发现,其实直接对值类型进行判断的情况下,除了数值型以外,返回的结果都是true,当然也包括它自身 `isNaN(NaN)`,得出的结果同样是 true。NaN产生的条件是:当参与**算术运算**中的某个值不为数值型的值,那返回的结果就会为NaN。但 `+` 运算符比较特殊,因为有的时候它是可以作为字符串拼接运算符的。观察控制台中输出的结果: 132 | 133 | ```js 134 | console.log(3 + 6); // 9 135 | console.log(3 + 'times'); // '3times' 136 | console.log(3 - 'times'); // NaN 137 | console.log(3 * '9'); // 27 138 | console.log(3 / '9'); // 0.3333333333333333 139 | console.log(3 + '9'); // '39' 140 | console.log(3 * 'times'); // NaN 141 | console.log((3 + 6) + 'times'); // 9times 142 | ``` 143 | 144 | # 数值转换 * 145 | 146 | 在实际的数值获取中,很多时候取到的数值并非是以数值存在的。如:字符串 *"3"*,像素单位 *"30px"*,单个数组元素 “[30]” 等,这些类型的值在参与和数值型算术运算时会产生意外的结果,如,一个数值在和一个字符串、像素单位值或单个数组进行 “`+`” 运算时,起到的是一个 “**字符串拼接**”的作用,而在执行“`-`”、“`*`” 或 “`/`” 运算时又是进行基本算术运算。如例: 147 | 148 | ```js 149 | console.log('6' + 3); // '63' 150 | console.log(6 * 3); // 18 151 | console.log(6 / 3); // 2 152 | console.log(6 - 3); // 3 153 | ``` 154 | 155 | 当然,上面这些 “非数值” 的内容都还是一个数值内容,能通过程序自动转换的情况。倘若取到的值是一个带单位的,情况又有所不同了: 156 | 157 | ```js 158 | console.log(100 + "28px"); // '10028px' 159 | console.log(100 * '28px'); // NaN 160 | console.log(100 - '28px'); // NaN 161 | console.log(100 / '28px'); // NaN 162 | ``` 163 | 164 | 在数值转换中,除了自动转换,还包括下面几个转换方法: 165 | 166 | ## parseInt() 167 | 168 | > 作用:**转换结果为整数** 169 | 170 | > 转换规则:**从左往右依次转换,直到遇到非数字字符为止。** 171 | 172 | ```js 173 | console.log(parseInt('1')); // 1 174 | console.log(parseInt(['1'])); // 1 175 | console.log(parseInt('8px')); // 8 176 | console.log(parseInt('80%')); // 80 177 | console.log(parseInt(' 1')); // 1 178 | 179 | // 转换过程中遇到非数字符号,即终止转换,返回已转换部分 180 | console.log(parseInt('1x1')); // 1 181 | 182 | // 首字符不能转换为数字返回 NaN,正负号除外 183 | console.log(parseInt(.1)); // 0 184 | console.log(parseInt('.1')); // NaN 185 | console.log(parseInt('+')); // NaN 186 | console.log(parseInt('+1')); // 1 187 | 188 | // 以 0x 开头,以16进制解析 189 | console.log(parseInt('0x10')); // 16 190 | // 以 0 开头,以10进制解析 191 | console.log(parseInt('010')); // 10 192 | ``` 193 | 194 | > 提示: 195 | > 196 | > 1. parseInt 转换结果不是**十进制数** 就是**NaN**。 197 | > 2. parseInt 能转换数组首元素 198 | 199 | `parseInt` 不能转换科学计数法,在转换科学计数法的数字时,会省略`e`后面的内容: 200 | 201 | ```javascript 202 | parseInt("314e-2") // 314 203 | parseInt("21e3") // 21 204 | ``` 205 | 206 | ## parseFloat() 207 | 208 | > 作用:**转换结果为浮点数** 209 | 210 | > 转换规则:**从左往右依次转换,直到遇到非数字字符为止。** 211 | 212 | ```javascript 213 | parseFloat('20.25') // 20.25 214 | parseFloat(['20.25']) // 20.25 215 | parseFloat('20.25e2') // 2025 216 | parseFloat('1234e-2') // 12.34 217 | parseFloat('3.14abc') // 3.14 218 | parseFloat(' 3.14') // 3.14 219 | 220 | parseFloat('A2') // NaN 221 | parseFloat('[]') // NaN 222 | parseFloat(' ') // NaN 223 | ``` 224 | 225 | ## Number() 226 | 227 | > 作用:**将转换对象转换为数字(自动转换采用该方式)** 228 | 229 | > 转换规则:**整体转换,只要转换对象中包含非数字字符则结果为`NaN`。** 230 | 231 | ```js 232 | Number("1"); // 1 233 | Number("3.14"); // 3.14 234 | Number("12e2"); // 1200 235 | Number("3x14"); // NaN 236 | ``` 237 | 238 | # 数值进制 239 | 240 | 作为一门计算机语言,JavaScript自然也支持四种基本的进制表示法,它们分别是:十进制、二进制、八进制和十六进制。 241 | 242 | ## 十进制 243 | 244 | 日常生活中的数字 245 | 246 | ## 二进制 247 | 248 | 需要在数值之前加上**前导数**:“`0b`”或“`0B`”,且前导数后方的数值不能大于`1`,或是其它内容,否则浏览器会报错,如: 249 | 250 | ![案例图片](IMGS/part_3_8.jpeg) 251 | 252 | ## 八进制 253 | 254 | 需要在数值之前加上前导数:“`0o`”或“`0O`”,且前导数后方的数值不能大于`7`,或是其它内容,否则浏览器会报错容,如: 255 | 256 | ![案例图片](IMGS/part_3_9.jpeg) 257 | 258 | ## 十六进制 259 | 260 | 需要在数值之前加上前导数:“`0x`”或“`0X`”,且前导数后方的数值不能大于`f`或`F`(相当于十进制内的15),或是其它内容,否则浏览器会报错,如: 261 | 262 | ![ 案例图片](IMGS/part_3_10.jpeg) 263 | 264 | # 进制转换 265 | 266 | 通过 `parseInt` 方法,可以将任意进制数转换为十进制数。但当需要转换的进制数为一个字符串时,该方法只能对十六进制的数进行正确的转换。 267 | 268 | ```javascript 269 | parseInt(0b1010) // 10 270 | parseInt(0o12) // 10 271 | parseInt(0xA) // 10 272 | 273 | parseInt("0b1010") // 异常 ‘0’ 274 | parseInt("0o12") // 异常 ‘0’ 275 | parseInt("0xA") // 10 276 | ``` 277 | 278 | 但我们在实际的项目操作中获取到的进制数大多是以字符串的形式存在的,这里我们可以使用 *parseInt* 的第二个参数,该参数可以告诉该方法需要转换的字符是何种进制的,并将其转换为十进制的数。需要注 意的是,如果配置了第二个参数,那第一个参数就不能包含前导数了,否则除了十六进制会出现异常。 279 | 280 | ```js 281 | parseInt("1010", 2); 282 | 10 283 | parseInt("0b1010", 2); 284 | 0 // 异常 285 | parseInt("12", 8); 286 | 10 287 | parseInt("0o12", 8); 288 | 0 // 异常 289 | parseInt("A", 16); 290 | 10 291 | parseInt("0xA", 16); 292 | 10 293 | ``` 294 | 295 | `parseInt` 方法的第二个参数除了上述几种形式之外还可以为其他进制数,它的范围是2~36之间的数值,只有在这个范围内才会返回有意义的数值,如果超出这个范围则会返回`NaN`。 296 | 297 | ```javascript 298 | parseInt("11", 2) // 二进制 3 299 | parseInt("11", 3) // 三进制 4 300 | parseInt("11", 4) // 四进制 5 301 | parseInt("11", 36) // 三十六进制 37 302 | 303 | parseInt("11", 1) // 一进制,异常 304 | parseInt("11", 37) // 三十七进制,异常 305 | ``` 306 | 307 | 上面提到的,是将各种进制数转换为十进制数,有时你可能想将一个十进制数转换为其他进制数,这时我们可以使用 `toString()` 方法,括号里面的参数为转换到的进制,如下所示: 308 | 309 | ```javascript 310 | (10).toString(2) // "1010" 311 | (10).toString(8) // "12" 312 | (10).toString(16) // "a" 313 | ``` 314 | 315 | # 数值对象 316 | 317 | ## 简介 318 | 319 | **Number** 对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用。 320 | 321 | ```javascript 322 | // 1、构造函数使用,生成数值对象 323 | var n = new Number(1); // 1 324 | typeof n; // "object" 325 | 326 | // 2、工具函数使用,类型转换 327 | Number('3.14'); // 3.14 328 | ``` 329 | 330 | ## 方法 * 331 | 332 | - [`Number.isInteger()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger):判断给定的参数是否为整数 333 | - [`Number.isNaN()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN):判断传入的值是否为NaN 334 | - [`Number.parseFloat()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat):转换浮点数 335 | - [`Number.parseInt()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt):转换整数 336 | - [`Number.prototype.toString([radix])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toString):数值转为字符串 337 | - [`Number.prototype.toFixed(len)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed):格式化数值(保留指定位数) 338 | - [`Number.prototype.toExponential()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential):科学计数法 339 | - [`Number.prototype.toPrecision()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision):精度 340 | 341 | ## 属性 342 | 343 | - [Number.MAX_SAFE_INTEGER](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER):最大安全数 344 | - [Number.MIN_SAFE_INTEGER](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER):最小安全数 345 | - [Number.MAX_VALUE](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_VALUE):最大值 346 | - [Number.MIN_VALUE](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_VALUE):最小值 347 | - [Number.NaN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/NaN):非数字 348 | - ... 349 | 350 | -------------------------------------------------------------------------------- /第04章 字符串.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | [字符串 >>](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String) 是被 **双引号** 或 **单引号** 包含的内容,可以包括数字、运算符、各国语言、特殊编码字符,甚至 HTML 标签。简单来说,任何符合在引号内的内容都可以被称为字符串。ES6 引入了模板字符串,使用反引号(**`**)进行标识。 4 | 5 | **字符串引号使用规则** * 6 | 7 | 在使用字符串的引号时,需要注意如果一个字符串已经使用了某种引号,再在字符串内部使用引号时,需要使用不同的引号类型。例如,双引号内使用单引号,单引号内使用双引号,或者使用转义符 \ 对相同的引号进行转义。在多层引号嵌套的情况下,该规则依然适用: 8 | 9 | ```javascript 10 | var str1 = "Hello, I'm Petter!"; 11 | var str2 = 'How do you think about "JavaScript"?'; 12 | var str3 = "设置字体的'颜色'代码是:

"; 13 | ``` 14 | 15 | 需要了解的一点是,字符串不能直接分成多行写,否则浏览器会报错。如果字符串过长,想要分行写以便视觉上更清晰,可以使用以下方式: 16 | 17 | ```javascript 18 | var str = "这是一个" + 19 | "分行写的" + 20 | "字符串"; 21 | console.log(str); // 输出:这是一个分行写的字符串 22 | ``` 23 | 24 | # 转义符 25 | 26 | 反斜杠 `\` 在字符串中有特殊用途,用来表示一些特殊的字符,所以又称 **转义操作符**(简称:转义符),以下是一些常用转义符的表示法:(小括号中的值表示法为 **Unicode**) 27 | 28 | ``` 29 | \n(或:\u000A)用于表示:换行符 30 | 31 | \t(或:\u0009)用于表示:制表符 32 | 33 | \'(或:\u0027)用于表示:单引号 34 | 35 | \"(或:\u0022)用于表示:双引号 36 | 37 | \\(或:\u005C)用于表示:反斜杠 38 | ``` 39 | 40 | 当然,转义符远不止上面列出的这些。你需要注意的是,在非特殊字符前面加上转义符 `\`,那 `\` 会被省略掉,如果需要输出 `\`,那就需要写成双斜杠 `\ \` 的形式。 41 | 42 | # UNICODE 43 | 44 | ## 字符的 Unicode 表示法 45 | 46 | JavaScript 允许使用 \uxxxx 形式表示一个字符,其中 xxxx 是字符的 Unicode 码点。每个 Unicode 码点对应一个字符,例如 \u00A9 是版权符号“©”。 47 | 48 | 在 JavaScript 中,所有字符都使用 Unicode 编码表示,且不仅仅以 Unicode 储存字符,还允许直接使用 Unicode 编号来表示字符。当解析代码时,JavaScript 会自动识别字符是以字面形式表示还是 Unicode 形式表示。输出给用户时,所有字符会被转成字面形式。换句话说,任何 JavaScript 中允许的字符都可以通过 Unicode 来表示。 49 | 50 | ```javascript 51 | "\u738b\u8005\u8363\u8000" 52 | "王者荣耀" 53 | ``` 54 | 55 | ## **UTF-16 储存格式** 56 | 57 | 每个字符在 JavaScript 内部是以 16 位(即 2 字节)的 UTF-16 格式储存。因此,\uxxxx 表示法只适用于 Unicode 码点在 \u0000 ~ \uFFFF 范围内的字符。对于超出该范围的字符,需要使用两个双字节形式表示: 58 | 59 | ```javascript 60 | "\u20BB7" // "₻7" 61 | "\uD842\uDFB7" // "𠮷" 62 | ``` 63 | 64 | 当直接在 \u 后面跟上大于 0xFFFF 的数值(例如 \u20BB7)时,JavaScript 会将其理解为 \u20BB + 7。因为 \u20BB 是一个不可打印字符,输出时只会显示一个空格,后面跟着数字 7。但 ES6 对此进行了改进,只要将码点放入大括号中,就能正确解读该字符: 65 | 66 | ```javascript 67 | "\u{20BB7}" // "𠮷" 68 | ``` 69 | 70 | ## str.charCodeAt(idx)    71 | 72 | 在 ES5 中,如果想将字符转换为十六进制 Unicode 编码,可以通过 `str.charCodeAt(idx)` 方法获取字符的数字编码值。然后通过 `toString(16)` 方法将其转换为十六进制编码,并拼接 `\u` 前缀,得到 Unicode 十六进制表示。 73 | 74 | ```javascript 75 | /** 76 | * 函数封装:将字符串转为Unicode编码 77 | */ 78 | Object.prototype.toUnicodeString = function(str) { 79 | var s = str || this.valueOf() ; 80 | var res = ""; 81 | for (var i = 0; i < s.length; i++) { 82 | res += "\\u" + s.charCodeAt(i).toString(16); 83 | } 84 | return res; 85 | } 86 | "王者荣耀".toUnicodeString(); // "\u738b\u8005\u8363\u8000" 87 | ``` 88 | 89 | 需要注意的是,JavaScript 中字符以 UTF-16 格式储存,每个字符为 2 字节。对于需要 4 字节储存的字符(即 Unicode 码点大于 0xFFFF 的字符),JavaScript 会将其当作两个字符处理: 90 | 91 | ```javascript 92 | var s = "𠮷"; 93 | 94 | s.length // 2 95 | 96 | s.charAt(0) // "�" 97 | s.charAt(1) // "�" 98 | 99 | s.charCodeAt(0) // 55362 100 | s.charCodeAt(1) // 57271 101 | ``` 102 | 103 | 在这个例子中,字符“𠮷”(Unicode 码点为 0x20BB7)需要 4 字节储存,其 UTF-16 编码为 0xD842 0xDFB7。JavaScript 无法正确处理 4 字节字符,字符串长度被误判为 2,且 charAt 和 charCodeAt 方法无法返回完整字符。 104 | 105 | ## String.fromCharCode() 106 | 107 | 在 ES5 中,可以通过 `String.fromCharCode(numCode)` 方法将数字编码转换为字符。numCode 是字符的数字编码值。 108 | 109 | ```javascript 110 | var str = "帅"; 111 | var numCode = str.charCodeAt(0); // 24069 112 | var oriStr = String.fromCharCode(numCode); // "帅" 113 | ``` 114 | 115 | 该方法无法识别 32 位 UTF-16 字符(即 Unicode 编号大于 0xFFFF 的字符)。 116 | 117 | ```javascript 118 | String.fromCharCode(0x20BB7) // // "ஷ" 119 | ``` 120 | 121 | > **提示**:这一小节是为了帮助理解字符的 Unicode 表示法,尤其是在 JavaScript 中的应用。 122 | 123 | # 包装对象 * 124 | 125 | 在 JavaScript 中,“**一切皆对象**”这个概念指的是,基本数据类型(如数值、字符串、布尔值)在某些情况下会自动转化为对象形式。具体来说,JavaScript 中的 **包装对象**(Wrapper Object)是指 **用于包装原始数据类型的对象**,它们使原始类型能够拥有一些对象的方法和属性。 126 | 127 | JavaScript 中有三种原始数据类型的包装对象,分别是: 128 | 129 | 1. Number:用于包装数值类型 130 | 2. String:用于包装字符串类型 131 | 3. Boolean:用于包装布尔值类型 132 | 133 | ## 创建包装对象 134 | 135 | JavaScript 允许使用 new 关键字创建包装对象。这样可以将原始数据类型的值包装成对象。例如: 136 | 137 | ```js 138 | var v1 = new Number(10); 139 | var v2 = new String("Hi"); 140 | var v3 = new Boolean("true"); 141 | ``` 142 | 143 | 这将分别创建数值、字符串和布尔类型的包装对象。 144 | 145 | 注意,typeof 运算符返回的结果是 "object",即使 v1、v2 和 v3 实际上是包装原始值的对象。 146 | 147 | ```js 148 | typeof v1 // "object" 149 | typeof v2 // "object" 150 | typeof v3 // "object" 151 | ``` 152 | 153 | ## 包装对象与原始对象的区别 154 | 155 | 虽然这些包装对象(Number、String、Boolean)看起来像普通的对象,但它们实际上与原始数据类型的值不同。包装对象与原始数据类型的主要区别在于: 156 | 157 | 1. **包装对象** 是通过 new 关键字创建的,可以调用一些对象的方法。 158 | 2. **原始数据类型** 是不可变的,无法直接拥有方法。 159 | 160 | JavaScript 会在需要时自动将原始数据类型转换为相应的包装对象。例如: 161 | 162 | ```js 163 | var str = "hello"; 164 | console.log(str.length); // 5 165 | ``` 166 | 167 | 上面代码会输出 【5】,虽然 "hello" 是一个原始字符串,但 JavaScript 会将其临时转为 String 对象来调用 `.length`。 168 | 169 | ## `valueOf()` 方法 170 | 171 | 每个包装对象都继承自 Object 类,因此也包含 `valueOf()` 方法。该方法用于返回该对象对应的原始值,即还原原始数据类型: 172 | 173 | ```js 174 | v1.valueOf(); // 返回 10 175 | v2.valueOf(); // 返回 "Hi" 176 | v3.valueOf(); // 返回 true 177 | ``` 178 | 179 | 这可以将包装对象还原为原始类型。 180 | 181 | ## 如何判断一个对象是内置对象还是包装对象? 182 | 183 | 在 JavaScript 中,所有的对象都可以使用 typeof 来判断。对于包装对象(如 Number、String、Boolean),typeof 会返回 "object",这与其他普通的对象(如数组、普通对象)一样。因此,仅凭 typeof 很难区分包装对象和内置对象。 184 | 185 | 一种常见的区分方法是通过检查对象的构造函数或通过特定方法来判断: 186 | 187 | ```js 188 | typeof v1 // "object" 189 | typeof v2 // "object" 190 | typeof v3 // "object" 191 | ``` 192 | 193 | 如果通过 typeof 判断是 "object",但它的构造函数是 Number、String 或 Boolean,那么它就是一个包装对象。 194 | 195 | # 字符串对象 * 196 | 197 | ## 构造函数 198 | 199 | ```javascript 200 | var str = "Hello, world!"; 201 | var str = 'Hello, world!'; 202 | var str = `Hello, world!`; // ES6 203 | 204 | var str = new String("Hi!"); 205 | var str = new Object("Hi!"); 206 | ``` 207 | 208 | ## 属性 209 | 210 | - [length](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/length):字符串长度 211 | 212 | ## 方法 213 | 214 | ### 查询相关 215 | 216 | - [`.at(index)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/at):查询指定下标位置的字符 217 | - [`.charAt(index)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/charAt):查询指定下标位置的字符 218 | - [`.indexof(searchValue, position)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf):查询下标 219 | - [`.lastIndexOf(searchValue[, fromIndex])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf):查询下标 220 | - [`.match(regexp)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/match):检索返回一个字符串匹配正则表达式的结果 221 | - [`.matchAll(regexp)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll):检索返回一个字符串匹配正则表达式的 **所有** 结果 222 | - [`.search()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/search):搜索字符 223 | - [`.startsWith(searchString[, position])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith):查询头 224 | - [`.endsWith(searchString[, length])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith):查询尾 225 | - [`.includes(searchString[, position])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/includes):是否包含 226 | 227 | ### 拼接 228 | 229 | - `+`:运算法加号 230 | - [`.concat(str2, [, ...strN])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/concat) 231 | 232 | ### 截取 233 | 234 | - [`.slice(beginIndex[, endIndex])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/slice) 235 | - [`.substring(indexStart, [indexEnd])`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/substring) 236 | 237 | **`slice` 🆚 `substring`** 238 | 239 | | 特性 | `slice()` | `substring()` | 240 | | --------------------- | -------------------- | ----------------------------------- | 241 | | 支持负数参数 | 是 | 否 | 242 | | `start` 大于 `end` 时 | 返回空字符串 | 交换 `start` 和 `end`,提取子字符串 | 243 | | 参数是负数时 | 从字符串末尾开始计算 | 被当作 0 处理 | 244 | 245 | ### 去除空格 246 | 247 | - [`.trim()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/trim):去除两端空格 248 | 249 | - [`.trimStart()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart):去除左侧空格 250 | - [`.trimEnd()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd):去除右侧空格 251 | 252 | ### 大小写转换 253 | 254 | - [`.toLowerCase()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase):转小写 255 | 256 | - [`.toUpperCase()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase):转大写 257 | 258 | ### 比较 259 | 260 | - [`.localeCompare()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare):字符串比较 261 | 262 | ### 替换 263 | 264 | 语法形式为: 265 | 266 | ```js 267 | str.replace(regexp|substr, newSubStr|function) 268 | ``` 269 | 270 | - `regexp`:正则表达式(匹配条件) 271 | - `substr`:字符串(匹配条件) 272 | - `newSubStr`:替换值 273 | - `function`:替换值(函数类型) 274 | 275 | 示例: 276 | 277 | ```javascript 278 | var str = "Hello, world!"; 279 | str.replace("world", "china"); // "Hello, china!" 280 | ``` 281 | 282 | `replace`只会替换第一次匹配到的字段,不能完全替换: 283 | 284 | ```js 285 | var str = "#fffff#"; 286 | str.replace("f", "o"); // "#offff#" 287 | ``` 288 | 289 | 上述例子中只会替换第一个f,如果想要全局匹配替换,我们可以使用正则表达式,如下所示: 290 | 291 | ```js 292 | // g → global:全局模式 293 | str.replace(/f/g, "o"); // "#ooooo#" 294 | ``` 295 | 296 | > **提示**:你也可以通过 [`.replaceAll()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll) 方法实现全局匹配替换。 297 | 298 | `replace` 方法的第2个参数也可以是一个函数,如下所示: 299 | 300 | ```javascript 301 | "Tel:15888888888;".replace(/(\d{3})(\d{4})(\d{4})/, (match, $1, $2, $3, offset, string) => { 302 | console.log(match, $1, $2, $3, offset, string); 303 | return `${$1} ${$2} ${$3}`; 304 | }) 305 | // 15888888888 158 8888 8888 4 Tel:15888888888; 306 | // Tel:158 8888 8888; 307 | ``` 308 | 309 | 可以看到,当参数为函数时,其中各参数表示: 310 | 311 | - `match`:匹配的字符串(`15888888888`)。 312 | - `$1`:如果 `replace` 方法的第1个参数是正则表达式,则 `$1` 表示第1个括号(组匹配)匹配的字符串,以此类推,`$2` / `$3` 表示第2 / 3 个括号匹配字符串。 313 | - `offset`:偏移量,表示被匹配到的字符串在原始字符串中的位置。 314 | - `string`:被匹配的原始字符串。 315 | 316 | 替换字符串可以插入下面的特殊变量名: 317 | 318 | | 变量名 | 代表的值 | 319 | | -------- | ------------------------------------------------------------ | 320 | | $$ | 插入一个 "$"。 | 321 | | $& | 插入匹配的子串。 | 322 | | $` | 插入当前匹配的子串左边的内容。 | 323 | | $' | 插入当前匹配的子串右边的内容。 | 324 | | $*n* | 假如第一个参数是 [`RegExp`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp)对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始。如果不存在第 n个分组,那么将会把匹配到到内容替换为字面量。比如不存在第3个分组,就会用“$3”替换匹配到的内容。 | 325 | | $\ | 这里 *`Name`* 是一个分组名称。如果在正则表达式中并不存在分组(或者没有匹配),这个变量将被处理为空字符串。只有在支持命名分组捕获的浏览器中才能使用。 | 326 | 327 | 我们简单应用下: 328 | 329 | ```javascript 330 | // 1. 加密手机 331 | "15888888888".replace(/(\d{3})(\d{4})(\d{4})/, "$1 *** $3") 332 | // -> '158 *** 8888' 333 | 334 | // 2. 交换两个单词 335 | var re = /(\w+)\s(\w+)/; 336 | var str = "John Smith"; 337 | var newstr = str.replace(re, "$2, $1"); 338 | // Smith, John 339 | console.log(newstr); 340 | ``` 341 | 342 | ### 填充 343 | 344 | - [`.padStart(targetLength, padString)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/padStart):用另一个字符串填充当前字符串(如果需要会重复填充),直到达到给定的长度。填充是从当前字符串的 **开头** 开始的。 345 | - [`.padEnd(targetLength, padString)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd):用另一个字符串填充当前字符串(如果需要会重复填充),直到达到给定的长度。填充是从当前字符串的 **末尾** 开始的。 346 | 347 | 代码示例: 348 | 349 | ```javascript 350 | var month = "1"; 351 | month < 10 ? month.padStart(2, '0') : month; // '01' 352 | ``` 353 | 354 | ### 切割数组 355 | 356 | - [`.split()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/split) 357 | 358 | ## 重复 359 | 360 | - [`.repeat()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/repeat) 361 | 362 | # 编码 363 | 364 | ## Base64转码 365 | 366 | **Base64** 是一种编码方法,用于将任意字符(如二进制数据、文本等)转化为可打印字符。最初的目的是确保数据能在系统中安全地传输,避免一些特殊字符(例如空格、回车符等)导致问题。Base64 编码可以用来在传输过程中将二进制数据转换为字符串形式,常用于电子邮件和 URL 编码。 367 | 368 | JavaScript 提供了两个常用方法来进行 Base64 编码和解码: 369 | 370 | - [btoa(stringToEncode)](https://developer.mozilla.org/zh-CN/docs/Web/API/btoa):将字符串转换为 Base64 编码 371 | 372 | - [atob(encodedData)](https://developer.mozilla.org/zh-CN/docs/Web/API/atob):将 Base64 编码的字符串解码为原始字符串(解码) 373 | 374 | **示例代码**: 375 | 376 | ```js 377 | // 1. 编码字符串 378 | const encodedData = btoa('Hello, world!'); 379 | console.log(encodedData); // → SGVsbG8sIHdvcmxkIQ== 380 | 381 | // 2. 解码字符串 382 | const decodedData = atob(encodedData); 383 | console.log(decodedData); // → Hello, world! 384 | ``` 385 | 386 | ### **Base64 编码限制** 387 | 388 | `btoa()` 只能处理 **ASCII 字符**,如果传入的字符串中包含需要超过一个字节表示的字符(如中文、表情符号等),则会抛出错误。例如: 389 | 390 | ```js 391 | const ok = "a"; 392 | console.log(ok.codePointAt(0).toString(16)); // 61:占用 < 1 byte 393 | 394 | const notOK = "✓"; 395 | console.log(notOK.codePointAt(0).toString(16)); // 2713:占用 > 1 byte 396 | 397 | console.log(btoa(ok)); // YQ== 398 | console.log(btoa(notOK)); // error 399 | ``` 400 | 401 | #### **解决 Unicode 编码问题** 402 | 403 | Base64 仅将二进制数据作为其输入。而在 js 字符串中,这意味着每个字符只能使用一个字节表示。所以,如果你将一个字符串传递给 `btoa()`,而其中包含了需要使用超过一个字节才能表示的字符,你就会得到一个错误,因为这个字符串不能被看作是二进制数据: 404 | 405 | **方法 1:将 Unicode 字符转为字节(不推荐)** 406 | 407 | 通过将字符串的每个 Unicode 单元转换为字节,可以解决 Unicode 编码问题: 408 | 409 | ```js 410 | function b64Encode(str) { 411 | function toBinary(string) { 412 | const codeUnits = new Uint16Array(string.length); 413 | for (let i = 0; i < codeUnits.length; i++) { 414 | codeUnits[i] = string.charCodeAt(i); 415 | } 416 | const charCodes = new Uint8Array(codeUnits.buffer); 417 | let result = ''; 418 | for (let i = 0; i < charCodes.byteLength; i++) { 419 | result += String.fromCharCode(charCodes[i]); 420 | } 421 | return result; 422 | } 423 | return btoa(toBinary(str)); 424 | } 425 | 426 | function b64Decode(b64String) { 427 | function fromBinary(binary) { 428 | const bytes = new Uint8Array(binary.length); 429 | for (let i = 0; i < bytes.length; i++) { 430 | bytes[i] = binary.charCodeAt(i); 431 | } 432 | const charCodes = new Uint16Array(bytes.buffer); 433 | let result = ''; 434 | for (let i = 0; i < charCodes.length; i++) { 435 | result += String.fromCharCode(charCodes[i]); 436 | } 437 | return result; 438 | } 439 | return fromBinary(atob(b64String)); 440 | } 441 | 442 | const b64String = b64Encode('中国!!!'); 443 | console.log(b64String); // → LU79ViEAIQAhAA== 444 | console.log(b64Decode(b64String)); // → 中国!!! 445 | ``` 446 | 447 | **方法 2:使用 encodeURIComponent(推荐)** 448 | 449 | 使用 encodeURIComponent 和 decodeURIComponent 函数处理 Unicode 字符串: 450 | 451 | ```js 452 | function b64Encode(str) { 453 | return btoa(encodeURIComponent(str)); 454 | } 455 | 456 | function b64Decode(b64String) { 457 | return decodeURIComponent(atob(b64String)); 458 | } 459 | 460 | const b64String = b64Encode('中国!!!'); 461 | console.log(b64String); // → JUU0JUI4JUFEJUU1JTlCJUJEISEh 462 | console.log(b64Decode(b64String)); // → 中国!!! 463 | ``` 464 | 465 | ## encodeURI / encodeURIComponent 466 | 467 | encodeURI 和 encodeURIComponent 都是用于对 URI 进行编码,但它们的行为有所不同: 468 | 469 | - [encodeURI](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/encodeURI):编码,不可以编码特殊字符(`#`、`/`、`&` 等) 470 | - [decodeURI](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/decodeURI):解码,不可以解码特殊字符(`#`、`/`、`&` 等) 471 | 472 | - [encodeURIComponent](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent):编码,可以编码特殊字符(`#`、`/`、`&` 等) 473 | 474 | - [decodeURIComponent](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent):解码,可以解码特殊字符(`#`、`/`、`&` 等) 475 | 476 | 代码示例: 477 | 478 | ```js 479 | const uri = 'http://lee-coding.cn#top?id=1&k=small'; 480 | console.log(encodeURI(uri)); // → http://lee-coding.cn#top?id=1&k=small 481 | console.log(encodeURIComponent(uri)); // → http%3A%2F%2Flee-coding.cn%23top%3Fid%3D1%26k%3Dsmall 482 | ``` 483 | 484 | # 七、课后练习 485 | 486 | ```markdown 487 | 1. 将字符串 “hello” 逆序输出为 “olleh” 488 | 489 | 2. 删除字符串 “01i13P47h2o39n32e09” 中的所有数字,输出结果 490 | 491 | 3. 定义一个字符串 ”CHINESE“,将其输出为 “Chinese“ 492 | 493 | 4. 统计 ”warriors” 单词中,“r“字母出现的次数 494 | 495 | 5. 将字符串 ”-_-” 中的 “_“ 替换成 ”$” 496 | 497 | 6. 将字符串”border-bottom-color” 转换成驼峰命名”borderBottomColor” 498 | 499 | 7. 输入身份证号,点击按钮,显示出生年月,输出格式为:"出生年月:xxxx年xx月xx日" 500 | > 提示: 501 | - 获取元素:document.querySelector(CSS选择器); 502 | - 点击事件:el.onclick = function(){}; 503 | - 获取输入框的值:el.value; 504 | - 设置元素显示内容:el.textContent = ""; 505 | ``` 506 | 507 | -------------------------------------------------------------------------------- /第08章 内置对象.md: -------------------------------------------------------------------------------- 1 | # Date 2 | 3 | [Date](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date) 是 JavaScript 提供的日期和时间操作接口。它表示的时间范围是 1970年1月1日00:00:00 前后各 1 亿天(单位:毫秒)。 4 | 5 | > **注意**:Date 作为普通函数调用时,即使带有参数,返回的始终是当前时间的字符串,而不会解析参数。 6 | 7 | ```javascript 8 | Date(); 9 | // 'Sat Apr 29 2023 01:47:52 GMT+0800 (中国标准时间)' 10 | 11 | Date('2023-05-01'); 12 | // 'Sat Apr 29 2023 01:47:52 GMT+0800 (中国标准时间)' 13 | ``` 14 | 15 | ## 创建日期对象 * 16 | 17 | ```javascript 18 | a. new Date(); * 19 | b. new Date(value); 20 | c. new Date(dateString); * 21 | d. new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]); 22 | ``` 23 | 24 | 语法解读: 25 | 26 | a:根据当前时间创建日期对象 27 | 28 | b:参数为相距 1970年1月1日00:00:00 的毫秒数 29 | 30 | c:参数为日期格式字符串,如:2019, Aug 20 19:20:352019/08/20 19:20:35 31 | 32 | d:参数分别为 → 年、月、日、时、分、秒、毫秒,年、月不能省略,月份从0开始计算。 33 | 34 | 语法说明: 35 | 36 | - year, monthIndex 必填,monthIndex 从 0 开始(0 代表 1 月)。 37 | - day, hours, minutes, seconds, milliseconds 可选,默认值为 0。 38 | 39 | ## 日期运算 * 40 | 41 | Date 实例转换为数值时,等于对应的毫秒数;转换为字符串时,则是日期字符串。 42 | 43 | 因此,当两个日期对象进行减法运算时,返回的就是它们间隔的毫秒数;进行加法运算时,返回的就是连接后的两个字符串。 44 | 45 | ```javascript 46 | var d1 = new Date("2023/10/01"); 47 | var d2 = new Date("2023/10/02"); 48 | 49 | console.log(d2 - d1); 50 | // 86400000(1 天的毫秒数) 51 | 52 | console.log(d2 + d1); 53 | // 'Mon Oct 02 2023 00:00:00 GMT+0800 (中国标准时间)Sun Oct 01 2023 00:00:00 GMT+0800 (中国标准时间)' 54 | ``` 55 | 56 | ## 静态方法 57 | 58 | - [`Date.now()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/now) 59 | 60 | 返回当前时间的 Unix 时间戳(单位:毫秒)。 61 | 62 | ```javascript 63 | Date.now(); // 1682704428973 64 | ``` 65 | 66 | **更高精度的时间**:window.performance.now(),返回从页面加载到当前代码执行的精确时间(单位:毫秒,小数部分为微秒级)。 67 | 68 | ```javascript 69 | window.performance.now(); // 5496790.340000001 70 | ``` 71 | 72 | - [`Date.parse()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) 73 | 74 | 解析日期字符串,返回从 1970-01-01 00:00:00 UTC 开始的毫秒数。 75 | 76 | ```javascript 77 | Date.parse('2023/10/01'); // 1696089600000 78 | Date.parse('xxx'); // NaN(无法解析) 79 | ``` 80 | 81 | ## 实例方法 82 | 83 | ```js 84 | const date = new Date(); 85 | ``` 86 | 87 | - **日期格式转换(to 类)** 88 | 89 | ```js 90 | date.toString(); // 'Fri Feb 07 2025 22:14:42 GMT+0800 (中国标准时间)' 91 | date.toISOString(); // '2025-02-07T14:14:42.644Z' 92 | date.toUTCString(); // 'Fri, 07 Feb 2025 14:14:42 GMT' 93 | date.toJSON(); // '2025-02-07T14:14:42.644Z' 94 | date.toDateString(); // 'Fri Feb 07 2025' 95 | date.toTimeString(); // '22:14:42 GMT+0800 (中国标准时间)' 96 | date.toLocaleDateString(); // '2025/2/7' 97 | date.toLocaleTimeString(); // '22:14:42' 98 | date.toLocaleString(); // '2025/2/7 22:14:42' 99 | ``` 100 | 101 | - **获取日期信息(get 类)** 102 | 103 | ```js 104 | date.getFullYear(); // 2025(四位年份) 105 | date.getMonth(); // 1(0 表示 1 月) 106 | date.getDate(); // 7(几号) 107 | date.getDay(); // 5(星期几,0 表示星期日) 108 | date.getHours(); // 22(小时,0-23) 109 | date.getMinutes(); // 14(分钟,0-59) 110 | date.getSeconds(); // 42(秒,0-59) 111 | date.getMilliseconds(); // 644(毫秒,0-999) 112 | date.getTime(); // 1738937682644 (距离 1970年1月1日00:00:00 的毫秒数) 113 | ``` 114 | 115 | - **设置日期信息(set 类)** 116 | 117 | ```typescript 118 | setFullYear(year: number, month?: number, date?: number): number; 119 | setMonth(month: number, date?: number): number; 120 | setDate(date: number): number; 121 | setHours(hours: number, min?: number, sec?: number, ms?: number): number; 122 | setMinutes(min: number, sec?: number, ms?: number): number; 123 | setSeconds(sec: number, ms?: number): number; 124 | setMilliseconds(ms: number): number; 125 | setTime(time: number): number; 126 | ``` 127 | 128 | # Math(数学对象) 129 | 130 | [Math](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math) 提供数学常数和方法。它是一个静态对象,不能实例化。 131 | 132 | ```javascript 133 | new Math(); // TypeError: object is not a function 134 | ``` 135 | 136 | ## 常数(只读) 137 | 138 | | 属性名 | 描述 | 近似值 | 139 | | ------------------------------------------------------------ | -------------------------- | --------- | 140 | | [`Math.E`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/E) | 自然对数的底数(欧拉常数) | 2.718 | 141 | | [`Math.LN2`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/LN2) | 2的自然对数, | 0.693 | 142 | | [`Math.LN10`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/LN10) | 10的自然对数 | 2.303 | 143 | | [`Math.LOG2E`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/LOG2E) | 以2为底E的对数 | 1.443 | 144 | | [`Math.LOG10E`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/LOG10E) | 以10为底E的对数 | 0.434 | 145 | | [`Math.PI`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/PI) | 圆周率 | 3.14159 * | 146 | | [`Math.SQRT1_2`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/SQRT1_2) | 1/2的平方根, | 0.707 | 147 | | [`Math.SQRT2`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/SQRT2) | 2的平方根 | 1.414 | 148 | 149 | ## 方法 * 150 | 151 | | 属性名 | 描述 | 152 | | ------------------------------------------------------------ | ------------------- | 153 | | [`Math.abs(x)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/abs) | 绝对值 | 154 | | [`Math.ceil(x)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil) | 向上取整 | 155 | | [`Math.floor(x)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/floor) | 向下取整 | 156 | | [`Math.round(x)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/round) | 四舍五入 | 157 | | [`Math.pow(x,y)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/pow) | x 的 y 次幂 | 158 | | [`Math.sqrt(x)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt) | 平方根 | 159 | | [`Math.random()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/random) | 0 到 1 之间的随机数 | 160 | | [`Math.max(...nums)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/max) | 取最大值 | 161 | | [`Math.min(...nums)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/min) | 取最小值 | 162 | 163 | ## 随机数封装 * 164 | 165 | **生成指定范围的随机小数** 166 | 167 | ```javascript 168 | // 100 ~ 200 的随机数 169 | Math.random() * 100 + 100 170 | // 函数式封装 171 | function randomDecimals(min, max) { 172 | if (isNaN(min) || isNaN(max)) return -1; 173 | return Math.random() * (max - min) + min; 174 | } 175 | ``` 176 | 177 | **生成指定范围的随机整数** 178 | 179 | ```javascript 180 | // 100 ~ 200 的随机数 181 | Math.floor(Math.random() * 101 + 100); 182 | // 函数式封装 183 | function randomInteger(min, max) { 184 | if (isNaN(min) || isNaN(max)) return -1; 185 | return Math.floor(Math.random() * (max - min + 1)) + min; 186 | } 187 | ``` 188 | 189 | **生成随机字符串** 190 | 191 | ```javascript 192 | function randomString(length) { 193 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 194 | return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); 195 | } 196 | 197 | randomString(6); // "NdQKOr" 198 | ``` 199 | 200 | # JSON * 201 | 202 | [JSON](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON) 是一种数据交换格式,可存储对象、数组、数值、字符串、布尔值和 null。 203 | 204 | 示例: 205 | 206 | ```json 207 | {"name":"木子李", "age":31, "major":"WEB前端", "tags": ["老司机", "实力唱将"]} 208 | ``` 209 | 210 | > 提示: 211 | > 212 | > 1. `JSON` 可以看做是严格模式下的对象,key 值必须加上 **双引号**。 213 | > 2. `JSON` 最外层通常是一个数组或对象 214 | 215 | ## API 216 | 217 | ### [`JSON.stringify()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) 218 | 219 | #### 基础语法 220 | 221 | 将对象转换为 `JSON` 字符串,语法形式如下: 222 | 223 | ```javascript 224 | JSON.stringify(value[, replacer[, space]]) 225 | ``` 226 | > 参数解读: 227 | 228 | - `value`:将要序列化成 一个 `JSON` 字符串的值。 229 | - `replacer`: 230 | - 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理; 231 | - 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 `JSON` 字符串中; 232 | - 如果该参数为 `null` 或者未提供,则对象所有的属性都会被序列化。 233 | - `space`: 234 | - 指定缩进用的空白字符串,用于美化输出; 235 | - 如果参数是个数字,它代表有多少的空格,上限为`10`。 236 | - 该值若小于1,则意味着没有空格; 237 | - 如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格; 238 | - 如果该参数没有提供(或者为 `null`),将没有空格。 239 | 240 | > 返回值:一个标识给定值的 `JSON` 字符串 241 | 242 | > 异常: 243 | 244 | - 当在循环引用时会抛出异常 `TypeError` ("`cyclic object value`")(循环对象值) 245 | - 当尝试去转换 `BigInt` 类型的值会抛出`TypeError` ("`BigInt value can't be serialized in JSON`")(`BigInt`值不能`JSON`序列化). 246 | 247 | #### 基本使用 248 | 249 | > 注意: 250 | > 251 | > 1)`JSON.stringify` 可以转换对象或者值(平常用的更多的是转换对象); 252 | > 253 | > 2)可以指定 `replacer` 为函数选择性的地替换; 254 | > 255 | > 3)也可以指定 `replacer` 为数组,可转换指定的属性; 256 | 257 | ```javascript 258 | var person = { 259 | name: '张三', 260 | age: 30, 261 | sex: '男', 262 | job: '前端工程师', 263 | }; 264 | 265 | // 1. 转换对象 266 | console.log(JSON.stringify(person)); 267 | // → {"name":"张三","age":30,"sex":"男","job":"前端工程师"} 268 | 269 | // 2. 转换普通值 270 | console.log(JSON.stringify('成都')); // → "成都" 271 | console.log(JSON.stringify(1)); // → "1" 272 | console.log(JSON.stringify(true)); // → "true" 273 | console.log(JSON.stringify(null)); // → "null" 274 | 275 | // 3. 指定replacer函数 276 | console.log( 277 | JSON.stringify(person, (key, value) => { 278 | // -- 过滤属性值为 number 类型的 key-value 对 279 | return typeof value === 'number' ? undefined : value; 280 | }) 281 | ); 282 | // → {"name":"张三","sex":"男","job":"前端工程师"} 283 | 284 | // 4. 指定数组 285 | console.log( 286 | JSON.stringify(person, ['name', 'age']) 287 | ); 288 | // → {"name":"张三","age":30} 289 | 290 | // 5. 指定space(美化输出) 291 | 292 | console.log( 293 | JSON.stringify(person, null, 2) 294 | ); 295 | 296 | /*{ 297 | "name": "张三", 298 | "age": 30, 299 | "sex": "男", 300 | "job": "前端工程师" 301 | }*/ 302 | ``` 303 | 304 | #### 九大特性 305 | 306 | 以前仅仅是使用了这个方法,却没有详细了解他的转换规则,居然有9个之多。 307 | 308 | **1)特性一** 309 | 310 | - `undefined`、`任意的函数` 以及 `symbol值`,出现在 **非数组对象** 的属性值中时在序列化过程中会被忽略 311 | 312 | - `undefined`、`任意的函数` 以及 `symbol值`,出现在 `数组` 中时会被转换成 `null`。 313 | 314 | - `undefined`、`任意的函数` 以及 `symbol值`,被 `单独转换` 时,会返回 `undefined` 315 | 316 | ```javascript 317 | // 1. 【对象】中存在 undefined/Function/Symbol 会被忽略 318 | console.log( 319 | JSON.stringify({ 320 | job: undefined, 321 | sayHello: () => {}, 322 | symbolName: Symbol('Tag'), 323 | }) 324 | ); 325 | // → {} 326 | 327 | // 2. 【数组】中存在 undefined/Function/Symbol 被转化为null 328 | console.log(JSON.stringify([undefined, () => {}, Symbol('Tag')])); 329 | // → [null, null, null] 330 | 331 | // 3. 单独转换会返回 undefined 332 | console.log(JSON.stringify(() => {})); 333 | console.log(JSON.stringify(Symbol("Tag"))); 334 | console.log(JSON.stringify(undefined)); 335 | ``` 336 | 337 | **2)特性二** 338 | 339 | `布尔值`、`数字`、`字符串`的包装对象在序列化过程中会自动转换成对应的原始值。 340 | 341 | ```javascript 342 | console.log(JSON.stringify({ 343 | name: new String("张三"), 344 | age: new Number(30), 345 | checked: new Boolean(false) 346 | })); 347 | // → {"name":"张三","age":30,"checked":false} 348 | ``` 349 | 350 | **3)特性三** 351 | 352 | 所有以 `symbol` 为属性键的属性都会被完全忽略掉,即便 `replacer` 参数中强制指定包含了它们。 353 | 354 | ```javascript 355 | console.log(JSON.stringify({ name: Symbol('张三') })); 356 | // '{}' 357 | 358 | console.log( 359 | JSON.stringify({ [Symbol('称呼')]: '张三' }, (key, value) => { 360 | if (typeof key === 'symbol') { 361 | return value; 362 | } 363 | }) 364 | ); 365 | // undefined 366 | ``` 367 | 368 | **4)特性四** 369 | 370 | `NaN` 和 `Infinity` 格式的数值及 `null` 都会被当做 `null`。 371 | 372 | ```javascript 373 | console.log( 374 | JSON.stringify({ 375 | age: NaN, 376 | num: Infinity, 377 | name: null, 378 | }) 379 | ); 380 | // → {"age":null,"num":null,"name":null} 381 | ``` 382 | 383 | **5)特性五** 384 | 385 | 转换值如果有 `toJSON()` 方法,该方法定义什么值将被序列化。 386 | 387 | ```javascript 388 | console.log( 389 | JSON.stringify({ 390 | name: '张三', 391 | job: '前端工程师', 392 | toJSON: function () { 393 | return `${this.name} - ${this.job}`; 394 | }, 395 | }) 396 | ); 397 | 398 | // → 张三 - 前端工程师 399 | ``` 400 | 401 | **6)特性六** 402 | 403 | Date 日期调用了 `toJSON()` 将其转换为了 `string` 字符串(同 `Date.toISOString()` ),因此会被当做字符串处理。 404 | 405 | ```javascript 406 | const d = new Date() 407 | 408 | console.log(d.toJSON()) // 2021-12-20T02:36:12.099Z 409 | console.log(JSON.stringify(d)) // "2021-12-20T02:36:12.099Z" 410 | ``` 411 | 412 | **7)特性七** 413 | 414 | 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。 415 | 416 | ```javascript 417 | let cyclicObj = { name: '张三' } 418 | cyclicObj.obj = cyclicObj 419 | console.log(JSON.stringify(cyclicObj)) 420 | 421 | // TypeError: Converting circular structure to JSON 422 | ``` 423 | 424 | **8)特性八** 425 | 426 | 其他类型的对象,包括 `Map/Set/WeakMap/WeakSet`,仅会序列化可枚举的属性 427 | 428 | ```javascript 429 | let enumerableObj = {}; 430 | Object.defineProperties(enumerableObj, { 431 | name: { 432 | value: '张三', 433 | enumerable: true, 434 | }, 435 | job: { 436 | value: '前端工程师', 437 | enumerable: false, 438 | }, 439 | }); 440 | 441 | console.log(JSON.stringify(enumerableObj)); 442 | // '{"name":"耀哥"}' 443 | ``` 444 | 445 | **9)特性九** 446 | 447 | 当尝试去转换 `BigInt` 类型的值会抛出错误、 448 | 449 | ```javascript 450 | const alsoHuge = BigInt(9007199254740991) 451 | 452 | console.log(JSON.stringify(alsoHuge)) 453 | // TypeError: Do not know how to serialize a BigInt 454 | ``` 455 | 456 | ### 1.2. [`JSON.parse()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) 457 | 458 | 将 `JSON` 字符串转换为对象,语法形式如下: 459 | 460 | ```javascript 461 | JSON.parse(text[, reviver]) 462 | ``` 463 | 代码示例: 464 | ```js 465 | var json = '{"name":"木子李","age":31,"major":"WEB前端","tags":["老司机","实力唱将"]}'; 466 | JSON.parse(json); 467 | 468 | // { name: '木子李', age: 31, major: 'WEB前端', tags: [ '老司机', '实力唱将' ] } 469 | ``` 470 | 471 | ## 异常处理 472 | 473 | 解决 `JSON.stringify` 函数和 `undefined` 丢失问题: 474 | 475 | ```typescript 476 | function JSONStringify(data: T) { 477 | return JSON.stringify(data, (k, v) => { 478 | // -- 处理函数丢失问题 479 | if (typeof v === 'function') { 480 | return `${v}`; 481 | } 482 | // -- 处理undefined丢失问题 483 | if (typeof v === 'undefined') { 484 | return 'undefined'; 485 | } 486 | return v; 487 | }); 488 | } 489 | ``` 490 | 491 | ```typescript 492 | function JSONParse(objStr: string) { 493 | return JSON.parse(objStr, (k, v) => { 494 | if (typeof v === 'string' && v.indexOf && v.indexOf('function') > -1) { 495 | // eval可能在eslint中报错,需要加入下行注释 496 | // eslint-disable-next-line 497 | return eval(`(function(){return ${v}})()`); 498 | } 499 | if (typeof v === 'string' && v === 'undefined') { 500 | return undefined; 501 | } 502 | return v; 503 | }); 504 | } 505 | ``` 506 | 507 | > Tips:其他问题可举一反三~ 508 | 509 | ## 校验 510 | 511 | 512 | 513 | 通过校验工具,可以检测JSON语法是否正确。 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | -------------------------------------------------------------------------------- /第09章 DOM.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | [DOM](https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model)(**D**ocument **O**bject **M**odel,即 文档对象模型)**是网页的编程接口**,它将 HTML / XML 解析为 **结构化的对象**,从而允许 JavaScript 对网页进行操作(如增删改查)。 4 | 5 | 浏览器根据 DOM 规范,将文档解析成 **树状结构(DOM 树)**,其中每个部分(标签、属性、文本等)都是 **节点(Node)**。所有节点都遵循统一的 API,可供 JavaScript 操作。 6 | 7 | > **注意**:DOM 并不属于 JavaScript,但 JS 是最常用于操作 DOM 的语言。 8 | 9 | # 节点 10 | 11 | ## 概念 12 | 13 | DOM 的最小组成单位叫做 **节点**(`Node`)。文档的树形结构(DOM树),就是由各种不同类型的节点组成。 14 | 15 | 节点的类型有七种: 16 | 17 | - `Document`:**文档节点**,代表整个网页(DOM 树的根节点) 18 | - `DocumentType`:**文档类型节点类型** :如 \ 19 | - `Element`:**元素节点**,网页的 HTML 标签,如 \、\。 20 | - `Attribute`:**属性节点**,元素的属性,如 class="right"(现代 JS 主要通过 element.getAttribute() 操作)。 21 | - `Text`:**文本节点** ,元素或属性中的文本内容 22 | - `Comment`:**注释节点** ,TML 中的 \ 23 | - `DocumentFragment`:轻量级的 DOM 结构,常用于批量插入节点以优化性能。 24 | 25 | > 提示:这七种节点都属于浏览器原生提供的节点对象的派生对象,具有一些共同的属性和方法。 26 | 27 | 我们可通过 `nodeName` 和 `nodeType` 属性判断对应节点的名称和类型。 28 | 29 | | 类型 | 描述 | `nodeName` | `nodeType` | 30 | | ------------------------ | -------- | ---------------------------- | ---------- | 31 | | `ELEMENT_NODE` * | 元素节点 | 大写的HTML标签名 | 1 | 32 | | `ATTRIBUTE_NODE` * | 属性节点 | 等同于 `Attr.name`(属性名) | 2 | 33 | | `TEXT_NODE` * | 文本节点 | `#text` | 3 | 34 | | `COMMENT_NODE` | 注释节点 | `#comment` | 8 | 35 | | `DOCUMENT_NODE` | 文档节点 | `#document` | 9 | 36 | | `DOCUMENT_FRAGMENT_NODE` | | `#document-fragment` | 11 | 37 | | `DOCUMENT_TYPE_NODE` | | 等同于 `DocumentType.name` | 10 | 38 | 39 | > 提示:本节知识点作为了解即可。 40 | 41 | ## 节点树 42 | 43 | 一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构,这种树状结构就是 **DOM树**。 44 | 45 | 最顶层的节点就是 `document` 节点,它代表了整个文档。文档里面最高一层的 `HTML` 标签,一般是 ``,它构成树结构的根节点,其他HTML标签节点都是它的下级。 46 | 47 | 除了根节点以外,其他节点对于周围的节点都存在两种关系: 48 | 49 | - 父子关系 50 | - 兄弟关系 51 | 52 | 示例: 53 | 54 | ```html 55 | 56 | 57 | 58 | DOM Tutorial 59 | 60 | 61 |

DOMs

62 | 主页 63 | 64 | 65 | ``` 66 | 67 | 上述代码的节点树模型: 68 | 69 | ![](IMGS/nodeTree.png) 70 | 71 | ## 节点集合 72 | 73 | 节点都是单个对象,有时会需要一种数据结构,能够容纳多个节点。DOM提供两种集合对象,用于实现这种节点的集合: 74 | 75 | - `NodeList` 76 | - `HTMLCollection` 77 | 78 | 访问集合成员可通过 `下标` 或 `.item(index)` 获取。 79 | 80 | > 提示:`NodeList` / `HTMLCollection` 属于 **类似数组** 对象,不能直接使用数组方法,如果要通过数组方法来遍历节点集合,你需要将它们转换为真正的数组。 81 | 82 | # 节点查询 83 | 84 | 为了便于演示后面的示例,我们现在在 ``标签中插入以下内容: 85 | 86 | ```html 87 |
成都哈戳戳科技有限公司
88 |
    89 |
  • 教学部
  • 90 |
  • 教务部
  • 91 |
  • 市场部
  • 92 |
  • 咨询部
  • 93 |
  • 行政部
  • 94 |
  • 财务部
  • 95 |
96 |
成都市高新区新川科技园A区
97 | ``` 98 | 99 | ## 直接查找 * 100 | 101 | ```js 102 | // 1. 根据ID查找 103 | document.getElementById("company"); 104 | // 2. 根据name属性查找 105 | document.getElementsByName("address"); 106 | // 3. 根据类名查找 107 | document.getElementsByClassName("departments"); 108 | // 4. 根据标签名查找 109 | document.getElementsByTagName("li"); 110 | // 5. 根据CSS选择器查找 111 | document.querySelector("#company"); * 112 | document.querySelectorAll(".departments li"); * 113 | ``` 114 | 115 | ## 间接查找 * 116 | 117 | 通过已找到的其他标签来查找。 118 | 119 | ```js 120 | // 获取参照节点 121 | p 122 | // 1. 获取上一个兄弟节点 123 | list.previousElementSibling; 124 | list.previousSibling 125 | // 2. 获取下一个兄弟节点 126 | list.nextElementSibling 127 | list.nextSibling 128 | // 3. 获取父节点 129 | list.parentElement 130 | list.parentNode 131 | // 4. 获取所有子节点 132 | list.children // → 获取所有元素子节点 133 | list.firstElementChild // → 获取第一个元素子节点 134 | list.lastElementChild) // → 获取最后一个元素子节点 135 | list.childElementCount // → 获取子节点的个数 136 | // 5. 获取第一个子节点 137 | list.firstElementChild 138 | // 5. 获取最后一个子节点 139 | list.lastElementChild 140 | ``` 141 | 142 | ## 其他查询 143 | 144 | ```js 145 | // 01. 查找文档类型 146 | document.doctype 147 | // 02. 查找根节点 148 | document.documentElement 149 | // 03. 查找Window对象 150 | document.defaultView 151 | // 04. 查找head元素 152 | document.head 153 | // 05. 查找body元素 154 | document.body 155 | // 06. 查询页面中正在获取焦点的元素 156 | document.activeElement 157 | // 07. 获取页面所有的 158 | document.links 159 | // 08. 获取页面所有的
160 | document.forms 161 | // 09. 获取页面所有的 162 | document.images 163 | // 10. 获取页面所有的