[
  {
    "path": ".gitignore",
    "content": ".vscode\r\n.DS_Store\r\nimages"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 300,\n  \"tabWidth\": 4,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\",\n  \"tslintIntegration\": true,\n  \"insertSpaceBeforeFunctionParenthesis\": false\n}"
  },
  {
    "path": "00-前端工具/01-VS Code的使用.md",
    "content": "---\ntitle: 01-VS Code的使用\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 前言\n\n> 文章标题：《第一次使用 VS Code 时你应该知道的一切配置》。本文的最新内容，更新于 2021-10-09。大家完全不用担心这篇文章会过时，因为随着 VS Code 的版本更新和插件更新，本文也会随之更新。\n\n> 本文的最新内容，也会在[GitHub](https://github.com/qianguyihao/Web/blob/master/00-%E5%89%8D%E7%AB%AF%E5%B7%A5%E5%85%B7/01-VS%20Code%E7%9A%84%E4%BD%BF%E7%94%A8.md)上同步更新，欢迎 star。\n\nVS Code 软件实在是太酷、太好用了，越来越多的新生代互联网民工正在使用它。\n\n前端男神**尤雨溪**大大这样评价 VS Code：\n\n![](http://img.smyhvae.com/20200619_0133.png)\n\n有一点你可能会感到惊讶：VS Code 这款软件本身，是用 JavaScript 语言编写的（具体请自行查阅基于 JS 的 PC 客户端开发框架 `Electron`）。Jeff Atwood 在 2007 年提出了著名的 Atwood 定律：\n\n> **任何能够用 JavaScript 实现的应用系统，最终都必将用 JavaScript 实现**。\n\nJeff Atwood 这个人是谁不重要（他是 Stack Overflow 网站的联合创始人），重要的是这条定律。\n\n前端目前是处在春秋战国时代，各路英雄豪杰成为后浪，各种框架工具层出不穷，VS Code 软件无疑是大前端时代最骄傲的工具。\n\n如果你是做前端开发（JavaScript 编程语言为主），则完全可以将 VS Code 作为「**主力开发工具**」。这款软件是为前端同学量身定制的，开箱即用。\n\n如果你是做其他语言方向的开发，并且不需要太复杂的集成开发环境，那么，你可以把 VS Code 作为「**代码编辑器**」来使用，纵享丝滑。\n\n甚至是一些写文档、写作的同学，也经常把 VS Code 作为 markdown **写作工具**，毫无违和感。\n\n退而求其次，即便你不属于以上任何范畴，你还可以把 VS Code 当作最简单的**文本编辑器**来使用，完胜 Windows 系统自带的记事本。\n\n写下这篇文章，是顺势而为。\n\n## 一、惊艳登场：VS Code 的介绍\n\nVS Code 的全称是 Visual Studio Code，是一款开源的、免费的、跨平台的、高性能的、轻量级的代码编辑器。它在性能、语言支持、开源社区方面，都做得很不错。\n\n微软有两种软件：一种是 VS Code，一种是其他软件。\n\n在2015年4月29日的微软Build开发者大会上，微软宣布推出 VS Code之后，这个轻量级的编辑器成为全球无数开发者们最喜爱的开发工具。VS Code基于开源且跨平台的理念，每月都会进行迭代，并提供每天发布的 insider 版本（insider是微软的一种公测计划，类似于国内软件所说的内测版）。它拥有至少几万个插件，生态极为活跃和丰富。\n\n### IDE 与 编辑器的对比\n\nIDE 和编辑器是有区别的：\n\n- **IDE**（Integrated Development Environment，集成开发环境）：对代码有较好的智能提示和相互跳转，同时侧重于工程项目，对项目的开发、调试工作有较好的图像化界面的支持，因此比较笨重。比如 Eclipse 的定位就是 IDE。\n\n- **编辑器**：要相对轻量许多，侧重于文本的编辑。比如 Sublime Text 的定位就是编辑器。再比如 Windows 系统自带的「记事本」就是最简单的编辑器。\n\n需要注意的是，VS Code 的定位是**编辑器**，而非 IDE ，但 VS Code 又比一般的编辑器的功能要丰富许多。可以这样理解：VS Code 的体量是介于编辑器和 IDE 之间。VS Code 的使命，是让开发者在编辑器里拥有 IDE 那样的开发体验。\n\n VS Code流行起来之后，使用 Sublime Text、Atom 这类编辑器软件的人，自然就越来越少了。\n\n### VS Code 的特点\n\n- 跨平台：支持 MacOS、Windows 和 Linux 等多个平台。在这多种平台下，拥有一致的用户界面和开发体验。\n- 开源：VS Code 的源代码以 MIT 协议开源。不仅代码开源，而且整个产品的开发计划和发布管理也都是开源的。VS Code团队每年都会在 GitHub 的Wiki上发布 [Roadmap](https://github.com/microsoft/vscode/wiki/Roadmap)，列出一整年的规划图。VS Code 软件的官方文档也托管在了 [GitHub](https://github.com/Microsoft/vscode-docs) 上。\n- 自带终端、图形化的调试工具、Git 版本控制。\n- 插件扩展：支持第三方插件，功能强大。既有中心化的插件市场，也可以直接在 VS Code里搜索你想要的插件。\n- 生态：社区生态活跃且丰富，社区氛围浓厚。\n- 自带  emmet：支持代码自动补全，快速生成简单的语法结构。要知道，这个功能在 Sublime Text中，得先安装插件才行。\n- 语法支持：VS Code 自带了 JavaScript、TypeScript 和 Node.js 的**语法支持**，包括：**语法高亮、代码智能提示和补全、括号匹配、颜色区分、代码片段提示**等。也就是说，你在书写 JS 和 TS 时，这些语法支持都是自带的。其他的一些语言，你需要先安装相应的**扩展包**插件，就出现语法支持。\n- 在修改配置方面，既有图形化的配置界面，也有 基于 JSON 文件的配置方式，满足不同人群的使用习惯。\n\n### 前端利器之争： VS Code 与 WebStorm\n\n前端小白最喜欢问的一个问题是：哪个编辑器/IDE 好用？是 VS Code 还是 WebStorm （WebStorm 其实是 IntelliJ IDEA 的定制版）？我来做个对比：\n\n- **哪个更酷**：显然 VS Code 更酷。\n\n- **内存占用情况**：根据我的观察，VS Code 是很占内存的（尤其是当你打开多个窗口的时候），但如果你的内存条够用，使用起来是不会有任何卡顿的感觉的。相比之下，IntelliJ IDEA 不仅非常占内存，而且还非常卡顿。如果你想换个既轻量级、又不占内存的编辑器，最好还是使用「Sublime Text」编辑器。\n\n- **使用比例**：当然是 VS Code 更胜一筹。先不说别的，我就拿数据说话，我目前所在的研发团队有 200 人左右（120个后台、80个前端），他们绝大部分人都在用 VS Code 编码，妥妥的。\n\n所以，如果你以后还问这个问题，那就真有些掉底了。\n\n### VS Code 的技术栈、核心组件\n\n了解 VS Code的技术栈和核心组件，可以让我们对 VS Code 有更深入的认识。此小段，了解即可。\n\n- 开发框架：Electron。Electron可以使用 Node.js + JS这样的技术栈开发桌面GUI应用程序。\n- 编辑器：Monaco Editor。Monaco Editor 是一款开源的在线代码编辑器，是 **VS Code 浏览器版本**的最核心组件。[#](https://zhuanlan.zhihu.com/p/88828576)\n- 编程语言：TypeScript。TypeScript 是  JavaScript的严格超集。TS 在JS的基础上添加了许多功能，引入了声明文件，而且支持类型扩展。TS 适合长期的、多人开发的大型项目开发。\n- 让编辑器支持语言功能：Language Server Protocol （LSP） 语言服务协议。LSP是编辑器/IDE 与语言服务器之间的一种协议，通过 JSON-PRC 传输消息，可以让编辑器嵌入并支持各种编程语言。开发者可以在编辑器中使用各种语言来编写程序。\n- 让编辑器支持调试功能：Debug Adapter Protocol（DAP）。DAP 是基于 JSON的协议，它抽象了开发工具与调试工具质检的通信。\n- 集成终端：Xterm.js。VS Code的集成终端是基于开源项目 [Xterm.js](https://github.com/xtermjs/xterm.js/) 进行开发的。Xterm.js 是一个使用 TS 开发的终端组件。另外，Xterm.js 并不是直接下来下来就能用的终端应用，它只是一个前端组件，可以与 bash这样的进程进行连接，然后让用户通过  Xterm.js 进行交互。\n\n### VS Code 的安装\n\n- VS Code 官网：<https://code.visualstudio.com>\n\nVS Code 的安装很简单，直接去官网下载安装包，然后双击安装即可。\n\n![](http://img.smyhvae.com/20190313_1750_3.png)\n\n上图中，直接点击 download，一键下载安装即可。\n\nVS Code支持以下平台：\n\n![](https://img.smyhvae.com/20210930_1930.png)\n\n安装完成后的界面如下：\n\n![](https://img.smyhvae.com/20211011_1703.png)\n\nVS  Code被分为以下五个区域：\n\n- 编辑器\n- 侧边栏\n- 状态栏\n- 活动栏\n- 面板\n\nVS Code在功能上非常克制，只包含了大多数开发流程中所需要的基础模块，包括：编辑器、文件管理、窗口管理、首选项设置、终端等。\n\n你需要根据具体需要安装额外的组件或者插件。比如说，如果开发TS项目，则需要安装 TS编译器，以及ESLint、TSLint等语法规则&代码风格的检查工具。如果开发C语言项目，则需要安装gcc、Clang等编译工具。\n\n## 二、崭露锋芒：VS Code 快捷键\n\nVS Code 用得熟不熟，首先就看你是否会用快捷键。以下列出的内容，都是常用快捷键，而加粗部分的快捷键，使用频率则非常高。\n\n任何工具，掌握 20%的技能，足矣应对 80% 的工作。既然如此，你可能会问：那就只保留 20% 的特性，不久可以满足 80%的用户了吗？\n\n但我想说的是：**那从来都不是同样的 20%**，每个人都会用到不同的功能。\n\n掌握下面这些高频核心快捷键，你和你的工具，足矣露出锋芒。\n\n### 1、工作区快捷键\n\n| Mac 快捷键             | Win 快捷键               | 作用                                          | 备注                 |\n| :--------------------- | :----------------------- | :-------------------------------------------- | :------------------- |\n| **Cmd + Shift + P**    | **Ctrl + Shift + P**，F1 | 显示命令面板                                  |                      |\n| **Cmd + B**            | **Ctrl + B**             | 显示/隐藏侧边栏                               | 很实用               |\n| `Cmd + \\` | `Ctrl + \\` | **拆分为多个编辑器**  | 【重要】抄代码利器                            |\n| **Cmd + 1、2**         | **Ctrl + 1、2**          | 聚焦到第 1、第 2 个编辑器                     | 同上重要             |\n| **Cmd + +、Cmd + -** | **ctrl + +、ctrl + -**  | 将工作区放大/缩小（包括代码字体、左侧导航栏） | 在投影仪场景经常用到 |\n| Cmd + J                | Ctrl + J                 | 显示/隐藏控制台                               |                      |\n| **Cmd + Shift + N**    | **Ctrl + Shift + N**     | 重新开一个软件的窗口                          | 很常用               |\n| Cmd + Shift + W        | Ctrl + Shift + W         | 关闭软件的当前窗口                            |                      |\n| Cmd + N                | Ctrl + N                 | 新建文件                                      |                      |\n| Cmd + W                | Ctrl + W                 | 关闭当前文件                                  |                      |\n\n### 2、跳转操作\n\n| Mac 快捷键                                                         | Win 快捷键             | 作用                                 | 备注               |\n| :----------------------------------------------------------------- | :--------------------- | :----------------------------------- | :----------------- |\n| Cmd + ` | 没有 | 在同一个软件的**多个工作区**之间切换 | 使用很频繁 |\n| **Cmd + Option + 左右方向键**                                      | Ctrl + Pagedown/Pageup | 在已经打开的**多个文件**之间进行切换 | 非常实用           |\n| Ctrl + Tab                                                         | Ctrl + Tab             | 在已经打开的多个文件之间进行跳转     | 不如上面的快捷键快 |\n| Cmd + Shift + O                                                    | Ctrl + shift + O       | 在当前文件的各种**方法之间**（符号：Symbol）进行跳转 |                    |\n| Cmd + T | Ctrl + T | 在当前**工作区**的各种方法之间（符号：Symbol）进行跳转 | |\n| Ctrl + G                                                           | Ctrl + G               | 跳转到指定行                         |                    |\n| `Cmd+Shift+\\` | `Ctrl+Shift+\\`                                     | 跳转到匹配的括号       |                                      |\n\n### 3、移动光标\n\n| Mac 快捷键                    | Win 快捷键                                 | 作用                                                         | 备注           |\n| :---------------------------- | :----------------------------------------- | :----------------------------------------------------------- | :------------- |\n| 方向键                        | 方向键                                     | 在**单个字符**之间移动光标                                   | 大家都知道     |\n| **option + 左右方向键**       | **Ctrl + 左右方向键**                      | 在**单词**之间移动光标                                       | 很常用         |\n| **Cmd + 左右方向键**          | **Fn + 左右方向键**（或 Win + 左右方向键） | 将光标定位到当前行的最左侧、最右侧（在**整行**之间移动光标） | 很常用         |\n| **Option + Alt + 左右方向键** | **Alt + Shift + 左右方向键**               | 左右扩大/缩小选中的范围                                      | 很酷，极为高效 |\n| Cmd + ↑                       | Ctrl + Home                                | 将光标定位到文件的第一行                                     |                |\n| Cmd + ↓                       | Ctrl + End                                 | 将光标定位到文件的最后一行                                   |                |\n| Cmd + Shift + \\               |                                            | 在**代码块**之间移动光标                                     |                |\n\n### 4、编辑操作\n\n| Mac 快捷键             | Win 快捷键          | 作用                                 | 备注                                   |\n| :--------------------- | :------------------ | :----------------------------------- | :------------------------------------- |\n| Cmd + C                | Ctrl + C            | 复制                                 |                                        |\n| Cmd + X                | Ctrl + X            | 剪切                                 |                                        |\n| Cmd + V                | Ctrl + V            | 粘贴                                 |                                        |\n| **Cmd + Enter**        | **Ctrl + Enter**    | 在当前行的下方新增一行，然后跳至该行 | 即使光标不在行尾，也能快速向下插入一行 |\n| Cmd+Shift+Enter        | Ctrl+Shift+Enter    | 在当前行的上方新增一行，然后跳至该行 | 即使光标不在行尾，也能快速向上插入一行 |\n| **Option + ↑**         | **Alt + ↑**         | 将代码向上移动                       | 很常用                                 |\n| **Option + ↓**         | **Alt + ↓**         | 将代码向下移动                       | 很常用                                 |\n| Option + Shift + ↑     | Alt + Shift + ↑     | 将代码向上复制一行                   |                                        |\n| **Option + Shift + ↓** | **Alt + Shift + ↓** | 将代码向下复制一行                   | 写重复代码的利器                       |\n\n另外再补充一点：将光标点击到某一行的任意位置时，默认就已经是**选中全行**了，此时可以直接**复制**或**剪切**，无需点击鼠标。这个非常实用，是所有的编辑操作中，使用得最频繁的。它可以有以下使用场景：\n\n- 场景1：假设光标现在处于第5行的**任意位置**，那么，直接依次按下 `Cmd + C` 和 `Cmd + V`，就会把这行代码复制到第6行。继续按 `Cmd + C` 和 `Cmd + V`，就会把这行代码复制到第7行。copy代码so easy。\n- 场景2：假设光标现在处于第5行，那么，先按下 `Cmd + C`，然后按两下`↑` 方向键，此时光标处于第3行；紧接着，继续按下`Cmd + V`，就会把刚刚那行代码复制到第3行，原本处于第3行的代码会整体**下移**。\n\n你看到了没？上面的两个场景，我全程没有使用鼠标，只通过简单的复制粘贴和方向键，就做到了如此迅速的copy代码。你说是不是很高效？\n\n### 5、删除操作\n\n| Mac 快捷键             | Win 快捷键           | 作用                   | 备注                                      |\n| :--------------------- | :------------------- | :--------------------- | :---------------------------------------- |\n| Cmd + shift + K        | Ctrl + Shift + K     | 删除整行               | 「Cmd + X」的作用是剪切，但也可以删除整行 |\n| **option + Backspace** | **Ctrl + Backspace** | 删除光标之前的一个单词 | 英文有效，很常用                          |\n| option + delete        | Ctrl + delete        | 删除光标之后的一个单词 |                                           |\n| **Cmd + Backspace**    |                      | 删除光标之前的整行内容 | 很常用                                    |\n| Cmd + delete           |                      | 删除光标之后的整行内容 |                                           |\n\n备注：上面所讲到的移动光标、编辑操作、删除操作的快捷键，在其他编辑器里，大部分都适用。\n\n### 6、多光标选择/多光标编辑\n\n多光标选择在编程的**提效**方面可谓立下了汗马功劳。因为比较难记住，所以你要时不时回来复习这一段。\n\n| Mac 快捷键                        | Win 快捷键                     | 作用                                                         | 备注                                     |\n| --------------------------------- | ------------------------------ | ------------------------------------------------------------ | ---------------------------------------- |\n| **Option + 鼠标连续点击任意位置** | **Alt + 鼠标连续点击任意位置** | 在任意位置，同时出现多个光标                                 | 很容易记住                               |\n| Cmd + D                           | Ctrl + D                       | 将光标放在某个单词的位置（或者先选中某个单词），然后反复按下「 **Cmd + D** 」键， 即可将下一个相同的词逐一加入选择。 | 较常用                                   |\n| **Cmd + Shift + L**               | **Ctrl + Shift + L**           | 将光标放在某个单词的位置（或者先选中某个单词），然后按下快捷键，则所有的相同内容处，都会出现光标。 | 很常用。比如变量重命名的时候，就经常用到 |\n\n### 7、多列选择/多列编辑\n\n多列选择是更高效的多光标选择，所以单独列成一小段。\n\n| Mac 快捷键                | Win 快捷键             | 作用                                                         | 备注                 |\n| ------------------------- | ---------------------- | ------------------------------------------------------------ | -------------------- |\n| Cmd + Option + 上下键     | Ctrl + Alt + 上下键    | 在连续的多列上，同时出现多个光标                             | 较常用               |\n| Option + Shift + 鼠标拖动 | Alt + Shift + 鼠标拖动 | 按住快捷键，然后把鼠标从区域的左上角拖至右下角，即可在选中区域的每一行末尾，出现光标。 | 很神奇的操作，较常用 |\n| **Option + Shift + i**    | **Alt + Shift + I**    | 选中一堆文本后，按下快捷键，既可在**每一行的末尾**都出现一个光标。 | 很常用               |\n\n### 8、编程语言相关\n\n| Mac 快捷键             | Win 快捷键      | 作用                         | 备注                             |\n| :--------------------- | :-------------- | :--------------------------- | :------------------------------- |\n| Cmd + /                | Ctrl + /        | 添加单行注释                 | 很常用                           |\n| **Option + Shift + F** | Alt + shift + F | 代码格式化                   | 很常用                           |\n| F2                     | F2              | 以重构的方式进行**重命名**   | 改代码备                         |\n| Ctrl + J               |                 | 将多行代码合并为一行         | Win 用户可在命令面板搜索”合并行“ |\n| Cmd +                  |                 |                              |                                  |\n| Cmd + U                | Ctrl + U        | 将光标的移动回退到上一个位置 | 撤销光标的移动和选择             |\n\n### 9、搜索相关\n\n| Mac 快捷键          | Win 快捷键          | 作用                                       | 备注   |\n| :------------------ | :------------------ | :----------------------------------------- | :----- |\n| **Cmd + Shift + F** | **Ctrl + Shift +F** | 全局搜索代码                               | 很常用 |\n| **Cmd + P**         | **Ctrl + P**        | 在当前的项目工程里，**全局**搜索文件名     |        |\n| Cmd + F             | Ctrl + F            | 在当前文件中搜索代码，光标在搜索框里       |        |\n| **Cmd + G**         | **F3**              | 在当前文件中搜索代码，光标仍停留在编辑器里 | 很巧妙 |\n\n### 10、自定义快捷键\n\n按住快捷键「Cmd + Shift + P」，弹出命令面板，在命令面板中输入“快捷键”，可以进入快捷键的设置。\n\n当然，你也可以选择菜单栏「偏好设置 --> 键盘快捷方式」，进入快捷键的设置：\n\n![](http://img.smyhvae.com/20190329_2120.png)\n\n此外，如果你输入这个快捷键后没起作用，那有可能是与其他软件（比如 PicGo 软件）的快捷键冲突了，请检查一下。\n\n### 11、快捷键列表\n\n你可以点击 VS Code 左下角的齿轮按钮，效果如下：\n\n![](http://img.smyhvae.com/20190418_1738.png)\n\n上图中，在展开的菜单中选择「键盘快捷方式」，就可以查看和修改所有的快捷键列表了：\n\n![](http://img.smyhvae.com/20190418_1739_2.png)\n\n### 快捷键参考表（官方）\n\nVS Code官网提供了 PDF版本的键盘快捷键参考表，转需：\n\n- Windows版本：https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf\n- Mac 版本：https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf\n- Linux版本：https://code.visualstudio.com/shortcuts/keyboard-shortcuts-linux.pdf\n\n我们在 VS  Code软件里通过菜单栏「帮助-->键盘快捷方式参考」也可以打开相应平台的快捷键大全（PDF版本）。\n\n\n\n\n## 三、高端访问：命令面板的使用\n\nMac 用户按住快捷键 `Cmd+Shift+P` （Windows 用户按住快捷键`Ctrl+Shift+P`），可以打开快速命令面板。效果如下：\n\n![](http://img.smyhvae.com/20190329_1750_2.png)\n\n命令面板的作用是**希望解放开发者的鼠标，让一些操作和配置可以直接通过键盘进行**。如果让开发者记住所有的配置项在菜单的哪个位置是不现实的，而且有些命令并不在菜单中。\n\n有了命令面板之后，如果你需要修改一些设置项，或者进行一些快捷操作，则可以通过「命令面板」来操作，效率会更高。接下来列举一些。\n\n### 1、VS Code 设置为中文语言\n\nMac 用户按住快捷键 `Cmd+Shift+P` （Windows 用户按住快捷键`Ctrl+Shift+P`），打开命令面板。\n\n在命令面板中，输入`Configure Display Language`，选择`Install additional languages`，然后安装插件`Chinese (Simplified) Language Pack for Visual Studio Code`即可。\n\n或者，我们可以直接安装插件`Chinese (Simplified) Language Pack for Visual Studio Code`，是一样的。\n\n安装完成后，重启 VS Code。\n\n### 2、设置字体大小\n\n在命令面板输入“字体”，可以进行字体的设置，效果如下：\n\n![](http://img.smyhvae.com/20190329_2110.png)\n\n当然，你也可以在菜单栏，选择「首选项-设置-常用设置」，在这个设置项里修改字体大小。\n\n### 3、快捷键设置\n\n在命令面板输入“快捷键”，就可以进入快捷键的设置。\n\n### 4、大小写转换\n\n选中文本后，在命令面板中输入`transfrom`，就可以修改文本的大小写了。\n\n![](http://img.smyhvae.com/20190414_1751.png)\n\n### 5、使用命令行启动 VS Code\n\n（1）输入快捷键「Cmd + Shift + P 」，选择`install code command`：\n\n![](http://img.smyhvae.com/20191103_1327.png)\n\n（2）使用命令行：\n\n- `code`命令：启动 VS Code 软件\n- `code pathName/fileName`命令：通过 VS Code 软件打开指定目录/指定文件。\n\n备注：这种方法快捷简单，但是在电脑重启之后就失效了。稍后在第五段，我会介绍更常见的方法。\n\n\n\n### 6、修改特定编程语言的设置项\n\n输入快捷键「Cmd + Shift + P 」打开命令面板，然后输入并执行 `Configure Language Specific Settings`即可。\n\n![](https://img.smyhvae.com/20211012_1039.png)\n\n\n\n## 四、私人订制：VS Code 的常见配置\n\n### 0、设置项介绍\n\n在修改 VS Code配置之前，我们需要知道，在哪里可以找到设置项的入口。\n\n**方式1**：Mac用户选择菜单栏「Code--> 首选项-->设置」，即可打开配置项：\n\n![](http://img.smyhvae.com/20210930_2009.png)\n\n**方式2**：点击软件右下角的设置图标：\n\n![](http://img.smyhvae.com/20210930_2016.png)\n\n![](https://img.smyhvae.com/20211012_1017.png)\n\n如上图所示，VS Code提供两种不同范围的设置：\n\n- **用户**设置：全局生效。\n- **工作区**设置：只针对当前项目生效。工作区设置会覆盖用户设置。适用于团队协作场景。工作区的设置文件是保存在当前项目根目录的`.vscode/settings.json`中，可以被提交到Git仓库，方便共享给项目组的其他成员。\n\n操作技巧：\n\n（1）我们可以在设置面板的顶部搜索框，输入关键词，就能迅速定位到你想要的设置项。\n\n（2）上图中，点击右上角的icon，可以通过 json文件的形式修改设置项。\n\n\n\n### 1、修改主题\n\n1）修改颜色主题：\n\n选择菜单栏「Code --> 首选项 --> 颜色主题」：\n\n![](http://img.smyhvae.com/20210930_2017.png)\n\n在弹出的对话框中，挑选你一个你喜欢的的颜色主题吧，或者安装其他颜色的主题：\n\n![20211013_1018](http://img.smyhvae.com/20211013_1018.png)\n\n或者在设置项里搜索`Workbench: Color Theme`，进行修改。\n\n2）修改文件图标的主题：\n\n选择菜单栏「Code --> 首选项 --> 文件图标主题」：\n\n![20211013_1015](http://img.smyhvae.com/20211013_1015.png)\n\n在弹出的对话框中，挑选你一个你喜欢的的主题吧，或者安装其他的主题：\n\n![20211013_1019](http://img.smyhvae.com/20211013_1019.png)\n\n\n\n或者在设置项里搜索`Workbench: Icon Theme`，进行修改。\n\n### 2、面包屑（Breadcrumb）导航\n\n打开 VS Code 的设置项，选择「用户设置 -> 工作台 -> 导航路径」，如下图所示：\n\n![](http://img.smyhvae.com/20191108_1550.png)\n\n上图中，将红框部分打钩即可。\n\n设置成功后，我们就可以查看到当前文件的「层级结构」，非常方便。如下图所示：\n\n![](http://img.smyhvae.com/20190415_2009.png)\n\n有了这个面包屑导航，我们可以点击它，在任意目录、任意文件之间随意跳转。使用频繁非常高。\n\n\n\n### 3、是否显示代码的行号\n\nVS Code 默认显示代码的行号。你可以在设置项里搜索 `editor.lineNumbers`修改设置，配置项如下：\n\n![](http://img.smyhvae.com/20190417_2140.png)\n\n我建议保留这个设置项，无需修改。\n\n### 4、右侧是否显示代码的缩略图\n\n如果某个文件的代码量很大，缩略图就很有用了，可以预览全局，并在当前文件中快速跳转。\n\nVS Code 会在代码的右侧，默认显示缩略图。你可以在设置项里搜索 `editor.minimap` 进行设置，配置项如下：\n\n![](http://img.smyhvae.com/20211012_1507.png)\n\n上面这张图，你仔细琢磨下会发现，中文翻译十分精准。\n\n### 5、将当前行代码高亮显示（更改光标所在行的背景色）\n\n当我们把光标放在某一行时，这一行的背景色并没有发生变化。如果想**高亮显示**当前行的代码，需要设置两步：\n\n（1）在设置项里搜索`editor.renderLineHighlight`，将选项值设置为`all`或者`line`。\n\n（2）在设置项里增加如下内容：\n\n```json\n\"workbench.colorCustomizations\": {\n    \"editor.lineHighlightBackground\": \"#00000090\",\n    \"editor.lineHighlightBorder\": \"#ffffff30\"\n}\n```\n\n上方代码，第一行代码的意思是：修改光标所在行的背景色（背景色设置为全黑，不透明度 90%）；第二行代码的意思是：修改光标所在行的边框色。\n\n### 6、改完代码后立即自动保存\n\n**方式一**：\n\n改完代码后，默认不会自动保存。你可以在设置项里搜索`files.autoSave`，修改参数值为`afterDelay`  ，即可自动保存。如下：\n\n![](https://img.smyhvae.com/20211012_2000.png)\n\nfiles.autoSave的参数值有以下几种：\n\n- off（默认值）：不自动保存。\n- afterDelay（建议配置）：文件修改超过一定时间（默认1秒）后，就自动保存。\n- onFocusChange：当前编辑器失去焦点时，则自动保存。如果我们将配置项修改为`onFocusChange`之后，那么，当光标离开该文件后，这个文件就会自动保存了。\n- onWindowChange：VS  Code软件失去焦点时，则自动保存。\n\n**方式二**：\n\n当然，你也可以直接在菜单栏选择「文件-自动保存」。勾选后，当你写完代码后，文件会立即实时保存。\n\n### 7、热退出\n\n当VS Code退出后，它可以记住未保存的文件。如果你希望达到这种效果，那么，你需要先将设置项`files.hotExit`的值改为 `onExitAndWindowClose`。这个配置项要不要改，看你个人需要。比如我自己平时设置的值是`onExit`。\n\n![20211012_2014](http://img.smyhvae.com/20211012_2014.png)\n\n\n### 8、保存代码后，是否立即格式化\n\n保存代码后，默认**不会立即**进行代码的格式化。你可以在设置项里搜索`editor.formatOnSave`查看该配置项：\n\n![](http://img.smyhvae.com/20190417_2213.png)\n\n我觉得这个配置项保持默认就好，不用打钩。\n\n### 9、自动格式化粘贴的内容\n\n在设置项里搜索 `editor.formatOnPaste`，将设置项改为`true`：\n\n![20211012_1049](https://img.smyhvae.com/20211012_1049.png)\n\n### 10、设置字体大小\n\n在设置项里搜索`fontSize`，然后根据需要设置各种模块的字体大小：\n\n![20211012_1053](http://img.smyhvae.com/20211012_1053.png)\n\n\n\n### 11、空格 or 制表符\n\nVS Code 会根据你所打开的文件来决定该使用空格还是制表。也就是说，如果你的项目中使用的都是制表符，那么，当你在写新的代码时，按下 tab 键后，编辑器就会识别成制表符。\n\n（1）建议的设置项如下：\n\n- **editor.detectIndentation**：自动检测（默认开启）。建议把这个配置项修改为 false，截图如下：\n\n![20211012_1139](https://img.smyhvae.com/20211012_1139.png)\n\n这样做，是为了取消系统的自动缩进，建议自己手动格式化比较好。 参考链接：https://www.yisu.com/zixun/327399.html\n\n- **editor.insertSpaces**：按 Tab 键时插入空格（默认值为true）。截图如下：\n\n![](http://img.smyhvae.com/20190417_2207.png)\n\n- **editor.tabSize**：一个制表符默认等于四个空格。截图如下：\n\n![](http://img.smyhvae.com/20190417_2209.png)\n\n\n\n（2）状态栏也会显示当前的缩进值。点击状态栏，可以直接修改 tabSize 缩进值：\n\n![](http://img.smyhvae.com/20211009_1610.png)\n\n\n\n（3）另外，我们还可以安装 prettier 插件，设置代码在格式化时默认缩进值。prettier 是做代码格式化的最常见工具。\n\n![](https://img.smyhvae.com/20211009_1637.png)\n\n（4）去掉每一行末尾的空格。在设置项里搜索`空格`或者`\"files.trimTrailingWhitespace\"`，将值设置为 true：\n\n![20211012_1231](http://img.smyhvae.com/20211012_1231.png)\n\n一般来说，每一行代码末尾的空格是多余的，所以建议去掉。\n\n### 12、直观地显示代码里的空格和缩进 ✨\n\n代码里如果有缩进或者空格，肉眼是看不出来的，但是我们可以修改配置项，把它揪出来。\n\n在配置项里搜索`editor.renderWhitespace`，修改为`all`：\n\n![20211012_1150](http://img.smyhvae.com/20211012_1150.png)\n\n修改之后，代码里的空格、缩进的展示效果如下：\n\n![20211012_1258](http://img.smyhvae.com/20211012_1258.png)\n\n看到了没？哪里有空格、哪里是缩进，全都一目了然。\n\n### 13、新建文件后的默认文件类型\n\n当我们按下快捷键「Cmd + N」新建文件时，VS Code 默认无法识别这个文件到底是什么类型的，因此也就无法识别相应的语法高亮。\n\n如果你想修改默认的文件类型，可以在设置项里搜索`files.defaultLanguage`，设置项如下：\n\n![](http://img.smyhvae.com/20190417_2221.png)\n\n上图中的红框部分，填入你期望的默认文件类型。我填的是`html`类型，你也可以填写成 `javascript` 或者 `markdown`，或者其他的语言类型。\n\n### 14、删除文件时，是否弹出确认框\n\n当我们在 VS Code 中删除文件时，默认会弹出确认框。如果你想修改设置，可以在设置项里搜索`xplorer.confirmDelete`。截图如下：\n\n![](http://img.smyhvae.com/20190418_1758.png)\n\n我建议这个设置项保持默认的打钩就好，不用修改。删除文件前的弹窗提示，也是为了安全考虑，万一手贱不小心删了呢？\n\n### 15、在新窗口打开文件/文件夹\n\n通过 `window.openFoldersInNewWindow`（默认值为off）和`window.openFilesInNewWindow`（默认值为default），可以配置在打开文件夹、打开文件时，是否开启一个新的窗口。我个人建议，把这两个配置项都设置为 on，避免旧的窗口被覆盖：\n\n![](http://img.smyhvae.com/20211012_1700.png)\n\n补充知识—— `window.restoreWindows`可以用来配置 如何恢复之前的会话窗口。涉及到的场景是：你把 VS Code 关闭了，然后又打开了，是否要展示之前打开过的文件、文件夹？参数值有以下几种：\n\n- one（默认配置）：只会重新打开上一次回话中最后操作的那一个窗口。\n- none：打开一个空的窗口，不包含任何文件、文件夹。\n- all（建议配置）：恢复上一次会话中的所有窗口。\n- folders：恢复上一次会话中包含文件夹的窗口。\n\n![20211012_1704](http://img.smyhvae.com/20211012_1704.png)\n\n### 16、自动删除行尾的空格\n\n打开设置项，搜索`files.trimTrailingWhitespace`，将选项勾选，即可在保存文件时自动删除行尾的空格。\n\n### 17、突出显示成对的括号\n\n我们可以用不同颜色显示代码中成对的括号，并用连线标注括号范围。简称**彩虹括号**。\n\n最早是通过`Bracket Pair Colorizer 2`插件支持的，但是这个插件已经被废弃了，因为 VS Code 已经内置了该功能。我们可以通过 VS Code的如下配置项，达到效果：\n\n```json\n{\n  \"editor.bracketPairColorization.enabled\": true,\n  \"editor.guides.bracketPairs\":\"active\"\n}\n```\n\n### 18、自动换行\n\n自动换行：意味着当文本到达屏幕或页面的右边缘时，自动换行会将文本移到下一行，以便继续输入或显示。\n\n打开设置项，搜索`Editor:Word Wrap`，将选项值设置为 on。\n\n此外，你还可以选择菜单栏`查看-->自动换行`，即可将当前文件设置为自动换行。\n\n> 接下来，我们来讲一些更高级的操作。\n\n\n\n## 五、纵享丝滑：常见操作和使用技巧\n\n### 1、快速生成HTML骨架\n\n先新建一个空的html文件，然后通过以下方式，可以快速生成html骨架。\n\n**方式1**：输入`!`，然后按下`enter`键，即可生成html骨架。如下图：\n\n![](https://img.smyhvae.com/20210623-2115.gif)\n\n\n\n**方式2**：输入`html:5`，然后按住 `Tab`键，即可生成html骨架。\n\n生成的骨架，内容如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n```\n\n有了上面的html骨架之后，我们就可以快乐地在里面插入CSS 代码和 JS 代码。\n\n### 2、并排编辑：左右（上下）显示多个编辑器窗口（copy代码利器）\n\n> 并排编辑是所有的编辑操作中最常用的一个技巧，十分有用。比如我们在开发一个项目时，可能需要同时打开 HTML 文件和 CSS 文件，很常见。\n\nMac 用户按住快捷键 `Cmd + \\`， Windows 用户按住快捷键`Ctrl + \\`，即可同时打开多个编辑器窗口，进行并排编辑。效果如下：\n\n![](http://img.smyhvae.com/20200619_0030.gif)\n\n按快捷键「Cmd + 1 」切换到左边的窗口，按快捷键「Cmd + 2 」切换到右边的窗口，以此类推。随时随地，想切就切。\n\n学会了这一招，以后 copy 代码的时候，leader 再也不用担心我抄得慢了，一天工资到手。\n\n---\n\n当然，使用快捷键`Cmd + \\`只是其中一种方式，我们还有很多种方式打开并行编辑。我们来做一个汇总。\n\n如果你已经打开了一个编辑器，那么可以通过以下几种方式在另一侧打开另一个编辑器：（按照使用频率，从高到低排序）\n\n- 使用快捷键`Cmd + \\`将编辑器一分为二。\n- 使用快捷键`Cmd + P`调出文件列表，选择要打开的文件，然后按下 `Cmd + Enter`快捷键。【重要】\n- 按住 Option 键的同时，单击资源管理器的文件（Windows 用户是按 Alt 键）。\n- 点击编辑器右上角的 `Split Editor`按钮。\n- 选择菜单栏「查看--> 编辑器布局」，然后选择你具体想要的布局，如下图所示：\n\n![20211012_1451](http://img.smyhvae.com/20211012_1451.png)\n\n- 通过拖拽，把当前文件移动到任意一侧。\n\n补充知识：通过配置项`worbench.editor.OpenSideBySideDirection`可以控制编辑器在并排打开时出现的默认位置（默认值为right，你也可以根据需要改为 down）。如下图所示：\n\n![20211012_1455](http://img.smyhvae.com/20211012_1455.png)\n\n### 3、从终端 code 命令启动 VS Code（Mac电脑）\n\n在终端输入`code`或者输入 `code + 指定项目的目录`，就可以启动 VS  Code，十分便捷。即：\n\n- `code` 命令：启动 VS Code 软件。\n- `code pathName/fileName` 命令：通过 VS Code 软件打开指定目录/指定文件。\n\n为了达到目的，我们需要先将 VS Code的软件安装路径添加到环境变量，一劳永逸。具体操作如下：\n\n（1）打开 `bash_profile`文件：\n\n```bash\ncd ~\nvim ./bash_profile\n```\n\n（2）在 bash_profile 中添加如下内容：\n\n```bash\n# 从终端启动VS Code，并设置vscode启动的命令别名\nalias code=\"/Applications/Visual\\ Studio\\ Code.app/Contents/Resources/app/bin/code\"\n```\n\n注意，由于`Visual Studio Code.app`这个路径里有空格，所以需要在空格前面加上反斜杠`\\`。\n\n（3）重启环境变量的配置：\n\n```\n# 重启\nsource ~/.bash_profile\n```\n\n大功告成。\n\n改完之后，如果没生效，那你把  `bash_profile`文件 换成 `zshrc`文件试试。\n\n参考链接：\n\n- [mac通过终端code 命令打开vscode](https://blog.csdn.net/logan_LG/article/details/106800904)\n\n当然，还可以通过命令面板，一键设置环境变量。具体做法是：输入快捷键「Cmd + shift + P」打开命令面板，然后选择 `shell 命令：从 PATH 中卸载 “code”命令`：\n\n![](https://img.smyhvae.com/202310201605408.png)\n\n\n完成后就可以在终端输入命令+文件路径来启动 VS Code 了。\n\n### 3、从终端 code 命令启动 VS Code（Windows电脑）\n\n在终端输入`code`或者输入 `code + 指定项目的目录`，就可以启动 VS  Code，十分便捷。即：\n\n- `code` 命令：启动 VS Code 软件。\n- `code pathName/fileName` 命令：通过 VS Code 软件打开指定目录/指定文件。\n\n为了达到目的，我们需要先将 VS Code的软件安装路径添加到环境变量，一劳永逸。具体操作如下：\n\n（1）打开 VS Code 的安装位置，进入bin文件夹，复制路径。比如：`D:\\Microsoft VS Code\\bin`。\n\n（2）回到桌面，右键我的电脑-->高级系统设置-->环境变量-->编辑path值，在原来的path后面，追加内容`;D:\\Microsoft VS Code\\bin`（即英文的分号+VS  Code 的 bin 路径)\n\n（3）重启电脑，大功告成。\n\n改完之后，如果没生效，那八成是因为你填的 path 值有问题。\n\n参考链接：\n\n- [windows使用 code . 命令打开vscode](https://www.cnblogs.com/zyl-Tara/p/10642704.html)\n\n### 4、在当前文件中搜索\n\n在上面的快捷键列表中，我们已经知道如下快捷键：\n\n- Cmd + F（Win 用户是 Ctrl + F）：在当前文件中搜索，光标在搜索框里\n\n- Cmd + G（Win 用户是 F3）：在当前文件中搜索，光标仍停留在编辑器里\n\n多个搜索结果出来之后，按下 Enter 键之后跳转到下一个搜索结果，按下 Shift + Enter 键之后跳转到上一个搜索结果。\n\n另外，你可能会注意到，搜索框里有很多按钮，每个按钮都对应着不同的功能，如下图所示：\n\n![](http://img.smyhvae.com/20190415_2052.png)\n\n上图中，你可以通过「Tab」键和「Shift + Tab」键在输入框和替换框之间进行切换。\n\n「在选定内容中查找」这个功能还是比较实用的。你也可以在设置项里搜索 `editor.find.autoFindInSelection`，勾选该设置项后，那么，当你选中指定内容后，然后按住「Cmd + F」，就可以**自动**只在这些内容里进行查找。该设置项如下图所示：\n\n![](http://img.smyhvae.com/20191108_1655.png)\n\n### 5、全局搜索\n\n在上面的快捷键列表中，我们已经知道如下快捷键：\n\n- Cmd + Shift + F（Win 用户是 Ctrl + Shift +F）：在全局的文件夹中进行搜索。效果如下：\n\n![20211012_1548](http://img.smyhvae.com/20211012_1548.png)\n\n上图中，你可以点击**红框**部分，展开更多的配置项。然后点击**红圈**部分，进行过滤搜索。注意，第二个红圈那里会经常用到，它可以在搜索时过滤掉  `.git`、`.node_modules`等忽略文件。\n\n上图中，我们还可以点击“在编辑器中打开”，在一个单独的文件中聚合展示搜索结果：\n\n![](https://img.smyhvae.com/20211012_1609.png)\n\n### 6、文件名/文件夹的搜索\n\n前面的快捷键那一段我们讲过，通过 「Cmd + P」可以快速搜索并打开**文件**/文件夹。这种方式，一般用于快速打开最近编辑过的文件。\n\n其实还有一种很巧妙的方式，可以在整个项目里，既能搜到文件，也能搜到**文件夹**。这种方式，常用于**过滤项目的目录**。操作方法很简单：\n\n> 直接在文件资源管理器输入关键字就行。搜索结果会自动出现；使用方向键进行上下移动，可以在搜索的文件和文件夹之间进行跳转。\n>\n> 另外，右上角会看到一个过滤器，点击下图中的红圈部分，则只显示匹配的文件和文件夹。\n\n![20211012_1616](http://img.smyhvae.com/20211012_1616.png)\n\n当然，这招也有一点不足：不能搜中文。\n\n### 7、大纲视图\n\n如下图所示，大纲视图可以展示当前代码的方法结构、文件的目录结构：\n\n![20211012_1628](http://img.smyhvae.com/20211012_1628.png)\n\n![20211012_1636](http://img.smyhvae.com/20211012_1636.png)\n\n### 8、文件对比\n\nVS Code 默认支持**对比两个文件的内容**。选中两个文件，然后右键选择「将已选项进行比较」即可，效果如下：\n\n![](http://img.smyhvae.com/20190329_1756.png)\n\nVS Code 自带的对比功能并不够强大，我们可以安装插件`compareit`，进行更丰富的对比。比如说，安装完插件`compareit`之后，我们可以将「当前文件」与「剪切板」里的内容进行对比：\n\n![](http://img.smyhvae.com/20190329_1757.png)\n\n如果你安装了 GitLens 插件，还可以将两个git分支的代码进行比对，非常完美。\n\n### 9、查找某个函数在哪些地方被调用了\n\n比如我已经在`a.js`文件里调用了 `foo()`函数。那么，如果我想知道`foo()`函数在其他文件中是否也被调用了，该怎么做呢？\n\n做法如下：在 `a.js` 文件里，选中`foo()`函数（或者将光标放置在`foo()`函数上），然后按住快捷键「Shift + F12」，就能看到 `foo()`函数在哪些地方被调用了，比较实用。\n\n### 10、鼠标操作\n\n- 在当前行的位置，鼠标三击，可以选中当前行。\n\n- 用鼠标单击文件的**行号**，可以选中当前行。\n\n- 在某个**行号**的位置，**上下移动鼠标，可以选中多行**。\n\n### 11、重构\n\n重构分很多种，我们来举几个例子。\n\n**命名重构**：\n\n当我们尝试去修改某个函数（或者变量名）时，我们可以把光标放在上面，然后按下「F2」键，那么，这个函数（或者变量名）出现的地方都会被修改。\n\n**方法重构**：\n\n选中某一段代码，这个时候，代码的左侧会出现一个「灯泡图标」，点击这个图标，就可以把这段代码提取为一个单独的函数。\n\n### 12：终端配置\n\nVS Code软件自带了终端，但我个人认为不是很好用，而且VS Code 软件关了之后，终端也没了。建议大家使用其他的终端软件，专业的事情交给专业的人做。\n\n- Windows平台的终端：推荐 PowerShell 软件。远程终端推荐 xshell 软件。\n- Mac平台的终端：推荐 [iTerm2 ](https://iterm2.com/)。 iTerm2 是Mac平台最好用的终端软件，没有之一。\n\n**右键行为**：\n\n> 在终端上，单击右键所产生的行为在不同的系统里是不同的。\n\n- Windows：如果有**选定**文本，则复制当前文本；如果没有选定文本，则粘贴。\n- macOS：选中光标所在位置的单词，并显示右键菜单。\n- Linux：显示右键菜单。\n\n### 13、Git 版本管理\n\n在 VS Code中使用Git之前，需要你先安装 Git 环境。\n\nVS Code 自带了 Git 版本管理的功能，如下图所示：\n\n![](http://img.smyhvae.com/20190418_1850.png)\n\n上图中，我们可以在这里进行常见的 git 命令操作。如果你还不熟悉 **Git 版本管理**，可以先去补补课。\n\n我自己用的最多的功能是**diff 代码**和**合并冲突**，自从用上了  VS Code 的这两个功能，简直离不开它。\n\n我们先来看看 diff 代码的效果：\n\n![20211013_1411](https://img.smyhvae.com/20211013_1411.png)\n\n上图中，点击右上角的`...`，然后点击`内联视图`，则可以换一种视图 diff 代码：\n\n![](https://img.smyhvae.com/20211013_1415.png)\n\n**Git状态栏**：\n\n![20211013_1421](http://img.smyhvae.com/20211013_1421.png)\n\n在VS Code的左下角会显示Git状态栏。如果当前代码仓库配置了远程仓库，那么“同步更改”会显示以下信息：\n\n- 左边的数字：表示远程分支比本地分支多了XX个 Git commit。\n- 右边的数字：表示本地分支比远程分支多了XX个 Git commit。\n\n点击“同步更改”按钮，会拉取（pull）远程分支到本地分支，并推送（push）本地的Git commit到远程分支。\n\n如果当前代码仓库没有配置远程仓库，则会显示“发布更改”的按钮。点击“发布更改”按钮，会把当前分支push到远程仓库。\n\n---\n\n另外，我建议安装插件`GitLens`搭配使用，它是 VS Code 中我最推荐的一个插件，简直是 Git 神器，码农必备。\n\n我还要补充一句：\n\n有人说，高手都是直接用命令行操作Git。然而，根据我多年的经验来看，如果你的代码仓库需要管理的分支特别多，与团队的其他成员需要经常协作，那么，我建议你**优先使用** GUI 图形化工具来操作Git，避免出错。\n\n我推荐的GUI版的Git工具有：\n\n- [Tower](https://www.git-tower.com/)\n- [Sourcetree](https://www.sourcetreeapp.com/)\n- [GitKraken](https://www.gitkraken.com/)\n\n### 14、将工作区放大/缩小\n\n我们在上面的设置项里修改字体大小后，仅仅只是修改了代码的字体大小。\n\n如果你想要缩放整个工作区（包括代码的字体、左侧导航栏的字体等），可以按下快捷键「**cmd +/-**」。windows 用户是按下「ctrl +/-」\n\n**当我们在投影仪上给别人演示代码的时候，这一招十分管用**。\n\n如果你想恢复默认的工作区大小，可以在命令面板输入`重置缩放`（英文是`reset zoom`）\n\nf### 11、创建多层子文件夹\n\n我们可以在新建文件夹的时候，如果直接输入`aa/bb/cc`，比如：\n\n![](http://img.smyhvae.com/20190418_2022.png)\n\n那么，就可以创建多层子文件夹，效果如下：\n\n![](http://img.smyhvae.com/20190418_2023.png)\n\n### 15、`.vscode` 文件夹的作用\n\n为了统一团队的 vscode 配置，我们可以在项目的根目录下建立`.vscode`目录，在里面放置一些配置内容，比如：\n\n- `settings.json`：工作空间设置、代码格式化配置、插件配置。\n\n- `sftp.json`：ftp 文件传输的配置。\n\n`.vscode`目录里的配置只针对当前项目范围内生效。将`.vscode`提交到代码仓库，大家统一配置时，会非常方便。\n\n### 16、自带终端\n\n我们可以按下「Ctrl + `」打开 VS Code 自带的终端。我认为内置终端并没有那么好用，我更建议你使用第三方的终端 **item2**。\n\n### 17、markdown 语法支持\n\nVS Code 自带 markdown 语法高亮。也就是说，如果你是用 markdown 格式写文章，则完全可以用 VS Code 进行写作。\n\n写完 md 文件之后，你可以点击右上角的按钮进行预览，如下图所示：\n\n![](http://img.smyhvae.com/20190418_1907.png)\n\n我一般是安装「Markdown Preview Github Styling」插件，以 GitHub 风格预览 Markdown 样式。样式十分简洁美观。\n\n你也可以在控制面板输入`Markdown: 打开预览`，直接全屏预览 markdown 文件。\n\n### 18、Emmet in VS Code\n\n`Emmet`可以极大的提高 html 和 css 的编写效率，它提供了一种非常简练的语法规则。\n\n举个例子，我们在编辑器中输入缩写代码：`ul>li*6` ，然后按下 Tab 键，即可得到如下代码片段：\n\n```html\n<ul>\n  <li></li>\n  <li></li>\n  <li></li>\n  <li></li>\n  <li></li>\n  <li></li>\n</ul>\n```\n\nVS Code 默认支持 Emmet。更多 Emmet 语法规则，可以自行查阅。\n\n### 19、修改字体，使用「Fira Code」字体\n\n这款字体很漂亮，很适合用来写代码：\n\n![](https://img.smyhvae.com/20200516_1633-2.png)\n\n安装步骤如下：\n\n（1）进入 <https://github.com/tonsky/FiraCode> 网站，下载并安装「Fira Code」字体。\n\n（2）打开 VS Code 的「设置」，搜索`font`，修改相关配置为如下内容：\n\n```json\n\"editor.fontFamily\": \"'Fira Code',Menlo, Monaco, 'Courier New', monospace\", // 设置字体显示\n\"editor.fontLigatures\": false,//控制是否启用字体连字，true启用，false不启用\n```\n\n上方的第二行配置，取决于个人习惯，我是直接设置为`\"editor.fontLigatures\": null`，因为我不太习惯连字。\n\n### 20、代码格式化\n\nVS Code 默认对 JavaScript、TypeScript、JSON、HTML 提供了开箱即用的代码格式化支持。其他语言则需要先安装相应的插件才能支持。\n\n另外，我们还可以安装 Prettier 插件进行**更精细**的代码格式化。下一段将插件的时候，会讲解。\n\n### 21、智能提示 IntelliSense\n\nVS Code 默认对 JavaScript、TypeScript、JSON、HTML、CSS、SCSS、Less这7种语言（文件）提供了**智能提示**的支持。其他编程语言则需要先安装相应的插件才能支持。\n\n在 VS Code插件职场中，下图是最受欢迎的8种[编程语言插件](https://marketplace.visualstudio.com/search?target=VSCode&category=Programming%20Languages&sortBy=Installs)：\n\n![20211013_1120](https://img.smyhvae.com/20211013_1120.png)\n\n智能提示的功能很强大， 包括函数介绍、代码自动补全等等。\n\n### 22、调试与运行\n\nVS Code **内置**了对 Node.js 运行时的调试支持，可以直接调试  JavaScript 和 TypeScript。其他编程语言的调试，则需要先安装相应的插件才能支持。\n\n在 VS Code插件市场中，下图是最受欢迎的几种调试插件：\n\n![](https://img.smyhvae.com/20211013_1650.png)\n\n### 23、文件传输：sftp\n\n如果你需要将本地文件通过 ftp 的形式上传到局域网的服务器（需要先把服务端的配置搭建好），可以安装`sftp`这个插件，很好用。在公司会经常用到。\n\n步骤如下：\n\n（1）安装插件`sftp`。\n\n（2）配置 `sftp.json`文件。 插件安装完成后，输入快捷键「cmd+shift+P」弹出命令面板，然后输入`sftp:config`，回车，当前工程的`.vscode`文件夹下就会自动生成一个`sftp.json`文件，我们需要在这个文件里配置的内容可以是：\n\n- `host`：服务器的 IP 地址\n\n- `username`：用户名\n\n- `privateKeyPath`：存放在本地的已配置好的用于登录工作站的密钥文件（也可以是 ppk 文件）\n\n- `remotePath`：工作站上与本地工程同步的文件夹路径，需要和本地工程文件根目录同名，且在使用 sftp 上传文件之前，要手动在工作站上 mkdir 生成这个根目录\n\n- `ignore`：指定在使用 sftp: sync to remote 的时候忽略的文件及文件夹，注意每一行后面有逗号，最后一行没有逗号\n\n举例如下：(注意，其中的注释需要去掉)\n\n```json\n{\n  \"host\": \"192.168.xxx.xxx\", //服务器ip\n  \"port\": 22, //端口，sftp模式是22\n  \"username\": \"\", //用户名\n  \"password\": \"\", //密码\n  \"protocol\": \"sftp\", //模式\n  \"agent\": null,\n  \"privateKeyPath\": null,\n  \"passphrase\": null,\n  \"passive\": false,\n  \"interactiveAuth\": false,\n  \"remotePath\": \"/root/node/build/\", //服务器上的文件地址\n  \"context\": \"./server/build\", //本地的文件地址\n\n  \"uploadOnSave\": true, //监听保存并上传\n  \"syncMode\": \"update\",\n  \"watcher\": {\n    //监听外部文件\n    \"files\": false, //外部文件的绝对路径\n    \"autoUpload\": false,\n    \"autoDelete\": false\n  },\n  \"ignore\": [\n    //忽略项\n    \"**/.vscode/**\",\n    \"**/.git/**\",\n    \"**/.DS_Store\"\n  ]\n}\n```\n\n（3）在 VS Code 的当前文件里，选择「右键 -> upload」，就可以将本地的代码上传到 指定的 ftp 服务器上（也就是在上方 `host` 中配置的服务器 ip）。\n\n我们还可以选择「右键 -> Diff with Remote」，就可以将本地的代码和 ftp 服务器上的代码做对比，非常方便。\n\n### 24、沉浸模式/禅模式\n\n程序员写代码需要专注，有时需要进入一种心流。VS Code给我们提供了一种全屏下的沉浸模式，周围的面板都会被隐藏起来，只显示编辑器部分。\n\n操作方法：菜单栏选择「查看-外观-禅模式」即可；或者按下快捷键`Cmd + K`，放手，再按`Z`也可以达到目的。\n\n### 25、远程同步 VS Code 配置项\n\n\n北京时间的[2020年8月14日](https://zhuanlan.zhihu.com/p/184868336)，微软发布 Visual Studio Code 1.48 稳定版。此版本**原生**支持用户同步 VS Code的配置，只需要登录微软账号或者 GitHub 账号即可。\n\n有了这个功能之后，我们可以先在老电脑上登录账号，即可将软件的配置项自动开启云同步。当你下次换一个新的电脑，下载安装 VS Code，点击软件左下角的设置按钮，登录此前的微软账号或GitHub账号，即可自动将旧电脑的软件配置项，同步到新电脑的软件上。极其方便。\n\n### 26、正则表达式批量删除字符串\n\n**需求**：将文本中的字符串`axxxxb`，批量替换为`ab`。其中，开头字符 a 和 结尾字符 b 固定，中间xxx长度不确定。\n\n**解决**：传统查找替换无法胜任。可以使用VScode正则表达式功能，查找`a.*?b`替换为`ab`即可。其中`?`是禁止贪婪匹配，否则会误删很多内容。\n\n---\n\n**拓展需求**：需求——将文本中的字符串`axxxx`，批量替换为`a`。其中，开头字符 a 固定，后面的xxx长度不确定。\n\n**解决**：传统查找替换无法胜任。可以使用VScode正则表达式功能，查找`a.*?\\n`替换为`a\\n`即可。\n\n## 六、三头六臂：VS Code 插件介绍 & 插件推荐\n\nVS Code 有一个很强大的功能就是支持插件扩展，让你的编辑器仿佛拥有了三头六臂。\n### 安装插件\n\n![](http://img.smyhvae.com/20191108_1553_2.png)\n\n上图中，点击红框部分，即可在顶部输入框里，查找你想要的插件名，然后进行安装。\n\n插件安装完成后，记得重启软件（或者点击插件位置的“重新加载”），插件才会生效。\n\n另外，我们还可以访问官网的插件市场来安装插件：\n\n-  VS Code插件市场（官方）：https://marketplace.visualstudio.com/vscode\n\n**插件的安装目录**：\n\n- Windows：：`%USERPROFILE%\\.vscode\\extensions`\n- macOS：`~/.vscode/extensions`\n- macOS：`~/.vscode/extensions`\n### 插件的类型\n\n![20211013_1757_2](http://img.smyhvae.com/20211013_1757_2.png)\n\n插件市场的首页有四个模块，可以作为重要的信息来源：\n\n- Featured：由  VS Code团队精心推荐的插件。\n- Trending：近期热门插件。\n- Most Popular：按总安装量排序的插件。\n- Recently Added：最新发布的插件。\n\n![20211013_1758](http://img.smyhvae.com/20211013_1758.png)\n\n![20211013_1955](http://img.smyhvae.com/20211013_1955.png)\n\n插件市场至少有17种类型的插件：（按照数量排序）\n\n- Themes：主题插件\n- Programming Languages：编程语言插件\n- Snippets：代码片段\n- Extension Packs：插件包，里面包括多个插件\n- Formatters：代码格式化\n- Linters：静态检查\n- Debuggers：调试器\n- Keymaps：快捷键映射\n- Visualization：可视化\n- Language Packs：各国的语言插件\n- Azure：Azure 云计算\n- Data Science：数据科学\n- SCM Providers：源代码控制管理器（source control manager）\n- Notebooks\n- Education：教育\n- Testing：测试相关\n- Machine Learning：机器学习\n- Others：其他\n### 插件的过滤显示\n\n在 VS  Code中打开插件管理视图，可以针对已安装的插件，进行过滤展示。\n\n1）点击插件视图右上角的`...`按钮，可以展示不同状态的插件：\n\n![20211013_2011](http://img.smyhvae.com/20211013_2011.png)\n\n2）在搜索框输入字符`@`，会展示出不同类型的过滤器：\n\n![20211013_2015](http://img.smyhvae.com/20211013_2015.png)\n\n**常见的过滤器如下**：\n\n1）按大类搜：\n\n- `@builtin`：显示 VS Code内置的插件\n- `@disabled`：显示被禁用的插件\n- `@enabled`：显示已启用的插件\n- `@installed`：显示已安装的插件\n- `@outdated`：显示待更新的插件\n\n2）精准搜索：\n\n- `@id`：按id显示插件\n- `@tag`：根据标签显示插件。\n\n3）对插件进行排序：\n\n- `@sort:installs`：根据插件的安装量排序\n- `@sourt:rating`：根据插件的评分排序\n- `@sort:name`：根据插件名字的字母顺序排序\n\n4）组合搜索：（举例）\n\n- `@installed @category:themes`：显示已安装的主题插件。\n- `@sort:installs java`：对 Java 相关的插件按照安装量排序。\n\n下面的内容，我来列举一些常见的插件，这些插件都很实用，小伙伴们可以按需安装。注意：每一类插件里，**顺序越靠前，越实用**。\n\n### 1、基本插件\n\n#### Chinese (Simplified) Language Pack for Visual Studio Code\n\n让软件显示为简体中文语言。\n\n### 2、Git 相关插件\n\n#### GitLens 【荐】\n\n我强烈建议你安装插件`GitLens`，它是 VS Code 中我最推荐的一个插件，简直是 Git 神器，码农必备。如果你不知道，那真是 out 了。\n\nGitLens 在 Git 管理上有很多强大的功能，比如：\n\n- 将光标放置在代码的当前行，可以看到这样代码的提交者是谁，以及提交时间。这一点，是 GitLens 最便捷的功能。\n- 查看某个 commit 的代码改动记录\n- 查看不同的分支\n- 可以将两个 commit 进行代码对比\n- 甚至可以将两个 branch 分支进行整体的代码对比。这一点，简直是 GitLens 最强大的功能。当我们在不同分支 review 代码的时候，就可以用到这一招。\n\n打开你的 Git仓库，未安装  GitLens 时是这样的：\n\n![](http://img.smyhvae.com/20211009_1400.png)\n\n安装了  GitLens 之后是这样的：\n\n![](http://img.smyhvae.com/20211009_1430.png)\n\n上图中，红框部分就是  GitLens 的功能，诸君可以自由发挥。\n\n补充一个有意思的趣事：Python插件、Ruby插件、GitLens插件、Vetur插件，这四个插件的开发者先后加入了微软。\n\n#### Git History\n\n有些同学习惯使用编辑器中的 Git 管理工具，而不太喜欢要打开另外一个 Git UI 工具的同学，这一款插件满足你查询所有 Git 记录的需求。\n\n#### Local History 【荐】\n\n维护文件的本地历史记录。代码意外丢失时，有时可以救命。\n\n![](http://img.smyhvae.com/20200618_2246.png)\n\n### 3、代码智能提示插件\n\n#### Vetur\n\nVue 多功能集成插件，包括：语法高亮，智能提示，emmet，错误提示，格式化，自动补全，debugger。VS Code 官方钦定 Vue 插件，Vue 开发者必备。\n\n#### ES7 React/Redux/GraphQL/React-Native snippets\n\nReact/Redux/react-router 的语法智能提示。\n\n安装该插件后，在代码中只需要输入`clg`即可自动补全`console.log()`这行代码。\n\n#### JavaScript(ES6) code snippets\n\nES6 语法智能提示，支持快速输入。\n\n#### javascript console utils：快速打印 log 日志【荐】\n\n安装这个插件后，当我们按住快捷键「Cmd + Shift + L」后，即可自动出现日志 `console.log()`。简直是日志党福音。\n\n当我们选中某个变量 `name`，然后按住快捷键「Cmd + Shift + L」，即可自动出现这个变量的日志 `console.log(name)`。\n\n其他的同类插件还有：Turbo Console Log。\n\n不过，生产环境的代码，还是尽量少打日志比较好，避免出现一些异常。\n\n编程有三等境界：\n\n- 第三等境界是打日志，这是最简单、便捷的方式，略显低级，一般新手或资深程序员偷懒时会用。\n\n- 第二等境界是断点调试，在前端、Java、PHP、iOS 开发时非常常用，通过断点调试可以很直观地跟踪代码执行逻辑、调用栈、变量等，是非常实用的技巧。\n\n- 第一等境界是测试驱动开发，在写代码之前先写测试。与第二等的断点调试刚好相反，大部分人不是很习惯这种方式，但在国外开发者或者敏捷爱好者看来，这是最高效的开发方式，在保证代码质量、重构等方面非常有帮助，是现代编程开发必不可少的一部分。\n\n#### Code Spell Checker：单词拼写错误检查\n\n这个拼写检查程序的目标是帮助捕获常见的单词拼写错误，可以检测驼峰命名。从此告别 Chinglish.\n\n#### Auto Close Tag、Auto Rename Tag\n\n自动闭合配对的标签、自动重命名配对的标签。\n\n### 4、代码显示增强插件\n\n\n#### highlight-icemode：选中相同的代码时，让高亮显示更加明显\n\nVSCode 自带的高亮显示，实在是不够显眼。可以用插件支持一下。\n\n所用了这个插件之后，VS Code 自带的高亮就可以关掉了：\n\n在用户设置里添加`\"editor.selectionHighlight\": false`即可。\n\n参考链接：[vscode 选中后相同内容高亮插件推荐](https://blog.csdn.net/palmer_kai/article/details/79548164)\n\n\n#### vscode-icons\n\nvscode-icons 会根据文件的后缀名来显示不同的图标，让你更直观地知道每种文件是什么类型的。\n\n\n#### indent-rainbow：突出显示代码缩进\n\n`indent-rainbow`插件：突出显示代码缩进。\n\n安装完成后，效果如下图所示：\n\n![](http://img.smyhvae.com/20190418_1958.png)\n\n\n#### TODO Highlight\n\n写代码过程中，突然发现一个 Bug，但是又不想停下来手中的活，以免打断思路，怎么办？按照代码规范，我们一般是在代码中加个 TODO 注释。比如：（注意，一定要写成大写`TODO`，而不是小写的`todo`）\n\n```\n//TODO:这里有个bug，我一会儿再收拾你\n```\n\n或者：\n\n```\n//FIXME:我也不知道为啥， but it works only that way.\n```\n\n安装了插件 `TODO Highlight`之后，按住「Cmd + Shift + P」打开命令面板，输入「Todohighlist」，选择相关的命令，我们就可以看到一个 todoList 的清单。\n\n#### Better Comments\n\n为注释添加更醒目、带分类的色彩。\n\n### 5、代码格式化插件\n\n#### Prettier：代码格式化\n\nPrettier 是一个代码格式化工具，**只关注格式化，但不具备校验功能**。在一个多人协同开发的团队中，统一的代码编写规范非常重要。一套规范可以让我们编写的代码达到一致的风格，提高代码的可读性和统一性。自然维护性也会有所提高，代码的展示也会更加美观。\n\n步骤如下：\n\n（1）安装插件 `Prettier`。\n\n（2）在项目的根路径下，新建文件`.prettierrc`，并在文件中添加如下内容：\n\n```json\n{\n  \"printWidth\": 150,\n  \"tabWidth\": 4,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\",\n  \"tslintIntegration\": true,\n  \"insertSpaceBeforeFunctionParenthesis\": false\n}\n```\n\n上面的内容，是我自己的配置，你可以参考。更多配置，可见官方文档：<https://prettier.io/docs/en/options.html>\n\n\n（3）Mac用户按快捷键「Option + Shift + F」，Win 用户按快捷键「Alt + shift + F」，即可完成代码的格式化。如果你的VS Code 设置的是自动格式化代码，那么这一步可以忽略。\n\n\n#### ESLint：代码格式的校验\n\n日常开发中，建议用 Prettier 做**代码格式化**，然后用 eslint 做**格式校验**。很多人把这两个插件的功能弄混了。\n\n一般做法是：格式化建议是由程序员手动触发，格式校验由系统强制校验。通过 Prettier **手动**触发格式化，是为了让用户有感知；通过eslint 做**强制**校验之后，如果代码的格式不符合要求，系统就禁止你提交代码。\n\n#### Beautify\n\n代码格式化工具。\n\n备注：相比之下，Prettier 是当前最流行的代码格式化工具，比 Beautify 用得更多。\n\n#### Paste JSON as Code\n\n此插件可以将剪贴板中的 JSON 字符串转换成工作代码。支持多种语言。\n\n#### JS-CSS-HTML Formatter【荐】\n\n保存文件时，自动格式化 HTML、CSS、JS代码。\n\n\n\n### 6、图片相关插件\n\n#### Polacode-2020：生成代码截图 【荐】\n\n可以把代码片段保存成美观的图片，主题不同，代码的配色方案也不同，也也可以自定义设置图片的边框颜色、大小、阴影。\n\n尤其是在我们做 PPT 分享时需要用到代码片段时，或者需要在网络上优雅地分享代码片段时，这一招很有用。\n\n生成的效果如下：\n\n![](http://img.smyhvae.com/20200619_1403.png)\n\n其他同类插件：`CodeSnap`。我们也可以通过 <https://carbon.now.sh/>这个网站生成代码图片\n\n有人可能会说：直接用 QQ 截图不行吗？可以是可以，但不够美观、不够干净。\n\n#### Image Preview 【荐】\n\n图片预览。鼠标移动到图片 url 上的时候，会自动显示图片的预览和图片尺寸。\n\n\n\n### 7、CSS相关插件\n\n#### CSS Peek\n\n增强 HTML 和 CSS 之间的关联，快速查看该元素上的 CSS 样式。\n\n#### Vue CSS Peek\n\nCSS Peek 对 Vue 没有支持，该插件提供了对 Vue 文件的支持。\n\n#### Color Info\n\n这个便捷的插件，将为你提供你在 CSS 中使用颜色的相关信息。你只需在颜色上悬停光标，就可以预览色块中色彩模型的（HEX、 RGB、HSL 和 CMYK）相关信息了。\n\n\n\n### 8、Mardown 相关插件\n\n#### Markdown Preview Github Styling 【荐】\n\n以 GitHub 风格预览 Markdown 样式，十分简洁优雅。就像下面这样，左侧书写 Markdown 文本，右侧预览 Markdown 的渲染效果：\n\n![](http://img.smyhvae.com/20200618_2025.png)\n\n#### Markdown Preview Enhanced\n\n预览 Markdown 样式。\n\n#### Markdown All in One\n\n这个插件将帮助你更高效地在 Markdown 中编写文档。\n\n\n\n### 9、通用工具类插件\n\n#### sftp：文件传输 【荐】\n\n如果你需要将本地文件通过 ftp 的形式上传到局域网的服务器，可以安装`sftp`这个插件，很好用。在公司会经常用到。\n\n详细配置已经在上面讲过。\n\n#### Live Server 【荐】\n\n在本地启动一个服务器，代码写完后可以实现「热更新」，实时地在网页中看到运行效果。就不需要每次都得手动刷新页面了。\n\n使用方式：安装插件后，开始写代码；代码写完后，右键选择「Open with Live Server」。\n\n#### open in browser\n\n安装`open in browser`插件后，在 HTML 文件中「右键选择 --> Open in Default Browser」，即可在浏览器中预览网页。\n\n\n#### Project Manager\n\n工作中，我们经常会来回切换多个项目，每次都要找到对应项目的目录再打开，比较麻烦。Project Manager 插件可以解决这样的烦恼，它提供了专门的视图来展示你的项目，我们可以把常用的项目保存在这里，需要时一键切换，十分方便。\n\n#### WakaTime 【荐】\n\n统计在 VS Code 里写代码的时间。统计效果如下：\n\n![](http://img.smyhvae.com/20200618_2300.png)\n\n#### Code Time\n\n`Code Time`插件：记录编程时间，统计代码行数。\n\n安装该插件后，VS Code 底部的状态栏右下角可以看到时间统计。点击那个位置之后，选择「Code Time Dashboard」，即可查看统计结果。\n\n备注：团长试了一下这个 code time 插件，发现统计结果不是很准。\n\n#### File Tree to Text Generator：快速生成文件的目录树\n\n如题。\n\n\n\n\n#### minapp：小程序支持\n\n小程序开发必备插件。\n\n\n\n\n#### Search node_modules\n\n`node_modules`模块里面的文件夹和模块实在是太多了，根本不好找。好在安装 `Search node_modules` 这个插件后，输入快捷键「Cmd + Shift + P」，然后输入 `node_modules`，在弹出的选项中选择 `Search node_modules`，即可搜索 node_modules 里的模块。\n\n![](http://img.smyhvae.com/20200618_2100.png)\n\n\n\n\n\n#### RemoteHub\n\n不要惊讶，RemoteHub 和 GitLens 是同一个作者开发出来的。\n\n`RemoteHub`插件的作用是：可以在本地查看 GitHub 网站上的代码，而不需要将代码下载到本地。\n\n![](http://img.smyhvae.com/20190418_1937.png)\n\n这个插件目前使用的人还不多，赶紧安装起来尝尝鲜吧。\n\n#### Live Share：实时编码分享\n\n`Live Share`这个神奇的插件是由微软官方出品，它的作用是：**实时编码分享**。也就是说，它可以实现你和你的同伴一起写代码。这绝对就是**结对编程**的神器啊。\n\n安装方式：\n\n打开插件管理，搜索“live share”，安装。安装后重启 VS Code，在左侧会多出一个按钮：\n\n![](http://img.smyhvae.com/20190418_2012.png)\n\n上图中，点击红框部分，登录后就可以分享你的工作空间了。\n\n![](http://img.smyhvae.com/20190418_2005.png)\n\n#### Import Cost\n\n在项目开发过程中，我们会引入很多 npm 包，有时候可能只用到了某个包里的一个方法，却引入了整个包，导致代码体积增大很多。`Import Cost`插件可以在代码中友好的提示我们，当前引入的包会增加多少体积，这很有助于帮我们优化代码的体积。\n\n### 10、主题插件\n\n给你的 VS Code 换个皮肤吧，免费的那种。\n\n- Dracula Theme\n\n- Material Theme\n\n- Nebula Theme\n\n- [One Dark Pro](https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme)\n\n- One Monokai Theme\n\n- Monokai Pro\n\n- Ayu\n\n- [Snazzy Plus](https://marketplace.visualstudio.com/items?itemName=akarlsten.vscode-snazzy-akarlsten)\n\n- [Dainty](https://marketplace.visualstudio.com/items?itemName=alexanderte.dainty-vscode)\n\n- `SynthWave '84`\n\n- GitHub Plus Theme：白色主题\n\n- Horizon Theme：红色主题\n\n\n\n## 七、无缝切换：VS Code 配置云同步\n\n我们可以将配置云同步，这样的话，当我们换个电脑时，即可将配置一键同步到本地，就不需要重新安装插件了，也不需要重新配置软件。\n\n下面讲的两个同步方法，都可以，看你自己需要。方法1是 VS Code自带的同步功能，操作简单。方法2 需要安装插件，支持更多的自定义配置。\n\n### 方法1：使用 VS Code 自带的同步功能\n\n1、**配置同步**：\n\n（1）在菜单栏选择「 Code --> 首选项 --> 打开设置同步」：\n\n![](https://img.smyhvae.com/20211008_1713.png)\n\n\n\n（2）选择需要同步的配置：\n\n![](http://img.smyhvae.com/20211008_1716.png)\n\n\n\n（3）通过Microsoft或者GitHub账号登录。 上图中，点击“登录并打开”，然后弹出如下界面：\n\n![](http://img.smyhvae.com/20211008_1717.png)\n\n上图中，使用  微软账号或者 GitHub账号登录：\n\n![](https://img.smyhvae.com/20211008_1718.png)\n\n（4）同步完成后，菜单栏会显示“首先项同步已打开”，最左侧也会多出一个同步图标，如下图所示：\n\n![](https://img.smyhvae.com/20211008_1720.png)\n\n2、**管理同步**：\n\n（1）点击菜单栏「Code --> 首选项 --> 设置同步已打开」，会弹出如下界面，进行相应的同步管理即可：\n\n![](https://img.smyhvae.com/20211008_1736.png)\n\n（2）换另外一个电脑时，登录相同的账号，即可完成同步。\n\n参考链接：\n\n- [VS Code原生的配置同步功能——Settings Sync](https://blog.csdn.net/baidu_33340703/article/details/106967884)\n\n### 方法2：使用插件 `settings-sync`\n\n使用方法2，我们还可以把配置分享其他用户，也可以把其他用户的配置给自己用。\n\n1、**配置同步**：（将自己本地的配置云同步到 GitHub）\n\n（1）安装插件 `settings-sync`。\n\n（2）安装完插件后，在插件里使用 GitHub 账号登录。\n\n（3）登录后在 vscode 的界面中，可以选择一个别人的 gist；也可以忽略掉，然后创建一个属于自己的 gist。\n\n（4）使用快捷键 「Command + Shift + P」，在弹出的命令框中输入 sync，并选择「更新/上传配置」，这样就可以把最新的配置上传到 GitHub。\n\n2、**管理同步**：（换另外一个电脑时，从云端同步配置到本地）\n\n（1）当我们换另外一台电脑时，可以先在 VS Code 中安装 `settings-sync` 插件。\n\n（2）安装完插件后，在插件里使用 GitHub 账号登录。\n\n（3）登录之后，插件的界面上，会自动出现之前的同步记录：\n\n![](http://img.smyhvae.com/20200521_1530.png)\n\n上图中，我们点击最新的那条记录，就可将云端的最新配置同步到本地：\n\n![](http://img.smyhvae.com/20200521_1550.png)\n\n如果你远程的配置没有成功同步到本地，那可能是网络的问题，此时，可以使用快捷键 「Command + Shift + P」，在弹出的命令框中输入 sync，并选择「下载配置」，多试几次。\n\n**使用其他人的配置**：\n\n如果我们想使用别人的配置，首先需要对方提供给你 gist。具体步骤如下：\n\n（1）安装插件 `settings-sync`。\n\n（2）使用快捷键 「Command + Shift + P」，在弹出的命令框中输入 sync，并选择「下载配置」\n\n（3）在弹出的界面中，选择「Download Public Gist」，然后输入别人分享给你的 gist。注意，这一步不需要登录 GitHub 账号。\n\n## 最后一段\n\n如果你还有什么推荐的 VS Code 插件，欢迎留言。\n\n大家完全不用担心这篇文章会过时，随着 VS Code 的版本更新和插件更新，本文也会随之更新。关于 VS Code 内容的后续更新，你可以关注我在 GitHub 上的前端入门项目，项目地址是：\n\n> https://github.com/qianguyihao/Web\n\n一个超级详细和真诚的前端入门项目。\n## todo\n\n- [issues 84](https://github.com/qianguyihao/Web/issues/84)\n\n## 参考链接\n### 2021年\n\n- 中文版 Awesome VS Code：https://github.com/formulahendry/awesome-vscode-cn\n\n### 2020年\n\n- [VSCode 插件大全｜ VSCode 高级玩家之第二篇](https://juejin.im/post/5ea40c6751882573b219777d)\n\n- <http://www.supuwoerc.xyz/tools/vscode/plugins.html>\n\n- [如何让 VS Code 更好用 10 倍？这里有一份 VS Code 新手指南](https://zhuanlan.zhihu.com/p/99462672)\n\n- [那些你应该考虑卸载的 VSCode 扩展](https://lyreal666.com/%E9%82%A3%E4%BA%9B%E4%BD%A0%E5%BA%94%E8%AF%A5%E8%80%83%E8%99%91%E5%8D%B8%E8%BD%BD%E7%9A%84-VSCode-%E6%89%A9%E5%B1%95/#more)\n\n- [VS Code 折腾记 - (16) 推荐一波实用的插件集](https://juejin.im/post/5d74eb5c51882525017787d9)\n\n- [VSCode 前端必备插件，有可能你装了却不知道如何使用？](https://juejin.im/post/5db66672f265da4d0e009aad)\n\n- [能让你开发效率翻倍的 VSCode 插件配置（上）](https://juejin.im/post/5a08d1d6f265da430f31950e)\n\n- [https://segmentfault.com/a/1190000012811886](https://segmentfault.com/a/1190000012811886)\n\n- [「Vscode」打造类 sublime 的高颜值编辑器](https://idoubi.cc/2019/07/08/vscode-sublime-theme/)\n\n- [Mac Vscode 快捷键](https://lsqy.tech/2020/03/14/20200314Mac-Vscode%E5%BF%AB%E6%8D%B7%E9%94%AE/)\n\n- [使用 VSCode 的一些技巧](https://mp.weixin.qq.com/s?src=11&timestamp=1591581536&ver=2387&signature=i4xLZlLe1Gkl7OiBIhPO*VSeNB5lzFgTY-dgNW9E9ZbtIAv4bnJ1RdAAZdhvDw*cg-DmMcUa-V8NSUdV-tthmXZCq3ht4edCweq6v0QxKjnh8IuAxyyh5qymdRui*8iE&new=1)\n\n---\n\n本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "00-前端工具/02-Git的使用.md",
    "content": "---\ntitle: 02-Git的使用\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n## 常见操作\n\n### 全局配置用户信息\n\n```\ngit config --global user.name \"smyhvae\"\n\ngit config --global user.email \"smyhvae@163.com\"\n```\n\n\n## 分支的合并\n\n\n### 场景：基于master分支的代码，开发一个新的特性\n\n如果你直接在master分支上开发这个新特性，是不好的，万一你在开发`特性1`的时候，领导突然又要叫你去开发`特性2`，就不好处理了。难道开发的两个特性都提交到master？一会儿提交特性1的commit，一会儿提交特性2的commit？这会导致commit记录很混乱。\n\n所以，我给你的建议做法是：给每个特性都单独建一个的新的分支。\n\n比如说，我专门给`特性1`建一个分支`feature_item_recommend`。具体做法如下：\n\n（1）基于master分支，创建一个新的分支，起名为`feature_item_recommend`：\n\n```\n$ git checkout -b feature_item_recommend\n\nSwitched to a new branch 'feature_item_recommend'\n```\n\n上面这行命令，相当于：\n\n\n```bash\n$ git branch feature_item_recommend    // 创建新的分支\n\n$ git checkout feature_item_recommend  //切换到新的分支\n```\n\n\n（2）在新的分支`feature_item_recommend`上，完成开发工作，并 commit 、push。\n\n（3）将分支`feature_item_recommend`上的开发进度**合并**到master分支：\n\n```bash\n$ git checkout master  //切换到master分支\n\n$ git merge feature_item_recommend    //将分支 feature_item_recommend 的开发进度合并到 master 分支\n\n```\n\n\n合并之后，`master`分支和`feature_item_recommend`分支会指向同一个位置。\n\n\n（3）删除分支`feature_item_recommend`：\n\n> 既然 特性1 开发完了，也放心地提交到master了，那我们就可以将这个分支删除了。\n\n```\ngit branch -d feature_item_recommend\n```\n\n注意，我们当前是处于`master`分支的位置，来删除`feature_item_recommend`分支。如果当前是处于`feature_item_recommend`分支，是没办法删除它自己的。\n\n同理，当我转身去开发`特性2`的时候，也是采用同样的步骤。\n\n\n### 合并分支时，如果存在分叉\n\n\n![](http://img.smyhvae.com/20180610_1650.png)\n\n\n比如说上面这张图中，最早的时候，master分支是位于`C2`节点。我基于`C2`节点，new出一个新的分支`iss53`，我在`iss53`上提交了好几个commit。\n\n现在，我准备把`iss53`上的几个commit合并到master上，此时发现，master分支已经前进到C4了。那该怎么合并呢？\n\n合并的命令仍然是：\n\n```bash\n$ git checkout master\n\n$ git merge iss53\n```\n\n**解释**：\n\n这次合并的实现，并不同于简单的并入方式。这一次，我的开发历史是从更早的地方开始分叉的。\n\n由于当前 master 分支所指向的commit (C4)并非想要并入分支（iss53）的直接祖先，Git 不得不进行一些处理。就此例而言，Git 会用两个分支的末端（C4 和C5）和它们的共同祖先（C2）进行一次简单的三方合并计算。\n\nGit 没有简单地把分支指针右移，而是对三方合并的结果作一新的快照，并自动创建一个指向它的commit（C6）（如下图所示）。我们把这个特殊的commit 称作合并提交（mergecommit），因为它的祖先不止一个。\n\n值得一提的是Git 可以自己裁决哪个共同祖先才是最佳合并基础；这和CVS 或Subversion（1.5 以后的版本）不同，它们需要开发者手工指定合并基础。所以此特性让Git 的合并操作比其他系统都要简单不少。\n\n![](http://img.smyhvae.com/20180610_1710.png)\n\n### 解决合并时发生的冲突\n\n![](http://img.smyhvae.com/20180610_1740.png)\n\n如果 feature1和feature2修改的是同一个文件中**代码的同一个位置**，那么，把feature1合并到feature2时，就会产生冲突。这个冲突需要人工解决。步骤如下：\n\n（1）手动修改文件：手动修改冲突的那个文件，决定到底要用哪个分支的代码。\n\n（2）git add：解决好冲突后，输入`git status`，会提示`Unmerged paths`。这个时候，输入`git add`即可，表示：**修改冲突成功，加入暂存区**。\n\n（3）git commit 提交。\n\n然后，我们可以继续把 feature1 分支合并到 master分支，最后删除feature1、feature2。\n\n**注意**：两个分支的同一个文件的不同地方合并时，git会自动合并，不会产生冲突。\n\n比如分支feture1对index.html原来的第二行之前加入了一段代码。\n分支feature2对index.html在原来的最后一行的后面加入了一段代码。\n这个时候在对两个分支合并，git不会产生冲突，因为两个分支是修改同一文件的不同位置。\ngit自动合并成功。不管是git自动合并成功，还是在人工解决冲突下合并成功，提交之前，都要对代码进行测试。\n\n## 日常操作积累\n\n### 修改密码（曲线救国）\n\n\n> 网上查了很久，没找到答案。最终，在cld童鞋的提示下，采取如下方式进行曲线救国。\n\n```bash\n# 设置当前仓库的用户名为空\ngit config  user.name \"\"\n```\n\n然后，当我们再输入`git pull`等命令行时，就会被要求重新输入*新的*账号密码。此时，密码就可以修改成功了。最后，我们还要输入如下命令，还原当前仓库的用户名：\n\n```\ngit config user.name \"smyhvae\"\n```\n\n### 修改已经push的某次commit的作者信息\n\n已经push的记录，如果要修改作者信息的话，只能 通过--force命令。我反正是查了很久，但最终还是不敢用公司的仓库尝试。\n\n参考链接：\n\n\n- [git 修改已提交的某一次的邮箱和用户信息](https://segmentfault.com/q/1010000006999861)\n\n看最后一条答案。\n\n- [修改 git repo 历史提交的 author](http://baurine.github.io/2015/08/22/git_update_author.html)\n\n\n### 将 `branch1`的某个`commit1`合并到`branch2`当中\n\n切换到branch2中，然后执行如下命令：\n\n```\ngit cherry-pick commit1\n```\n\n### 20190118-修改GitHub已提交的用户名和邮箱\n\n参考链接：（亲测有效）\n\n- [修改Git全部Commit提交记录的用户名Name和邮箱Email](https://cloud.tencent.com/developer/article/1352623)\n\n- [Mac 运行sh文件，也就是传说中的shell脚本](https://blog.csdn.net/yusufolu9/article/details/53706269)\n\n\n在执行`./email.sh`后，如果出现`permission denied`的错误，可以先执行`chmod 777 email.sh`，修改文件的权限。\n\n\n### 20200520-将Git 项目迁移到另一个仓库\n\n我们假设旧仓库的项目名称叫`old-repository`，新仓库的项目名称叫`new-repository`。操作如下：\n\n\n（1）创建旧仓库的裸克隆：\n\n```bash\ngit clone --bare https://github.com/exampleuser/old-repository.git\n```\n执行上述命令后，会在本地生成一个名叫 `old-repository.git`的文件夹。\n\n\n（2）迁移到新仓库：\n\n```bash\ncd old-repository.git\n\ngit push --mirror https://github.com/exampleuser/new-repository.git\n```\n\n这样的话，项目就已经迁移到新仓库了。\n\n注意，我们**不需要**手动新建一个空的新仓库，当我们执行上述命令之后，新仓库就已经自动创建好了。\n\n参考链接：\n\n- [复制仓库](https://help.github.com/cn/github/creating-cloning-and-archiving-repositories/duplicating-a-repository)\n\n- [Git 本地仓库和裸仓库](https://moelove.info/2016/12/04/Git-%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E5%92%8C%E8%A3%B8%E4%BB%93%E5%BA%93/)\n\n\n### 2021-11-10-提交代码时，绕过 eslint 检查\n\n需求：提交代码时，绕过 eslint 检查\n\n解决办法：用命令行提交，末尾追加`--no-verify`。例如：\n\n```bash\n# 提交代码\ngit commit -m '千古壹号的commit备注' --no-verify\n\n# 推送到远程时，也可以追加 --no-verify，以免远程仓库做了 eslint 限制。\ngit push origin --no-verify\n```\n\n### 2021-12-29-切换仓库的源地址\n\n查看源地址：\n\n```\ngit remote -v\n```\n\n切换源地址：\n\n```bash\ngit remote set-url origin https://xxx.git\n```\n\n\n\n\n## git客户端推荐\n\n市面上的Git客户端我基本都用过了，我最推荐的一款Git客户端是：[Tower](https://www.git-tower.com) 或者 [Fork](https://git-fork.com)。\n\n- GitUp：<https://gitup.co/>\n\n20180623时，网上看了下Git客户端的推荐排名：\n\n![](http://img.smyhvae.com/20180623_1210.png)\n\n**SmartGit**：\n\n商业用途收费， 个人用户免费：\n\n![](http://img.smyhvae.com/20180623_1305.png)\n\n\n## 推荐书籍\n\n- 《pro.git中文版》\n\n\n\n## 推荐连接\n\n\n### 2018-06\n\n- [聊下git pull --rebase](https://www.cnblogs.com/wangiqngpei557/p/6056624.html)\n\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "00-前端工具/03-网络抓包和代理工具：Whistle.md",
    "content": "---\ntitle: 03-网络抓包和代理工具：Whistle\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## Whistle 官网\n\n- Whistle 官网：<https://wproxy.org/whistle/>\n\n- Whistle 的 GitHub：<https://github.com/avwo/whistle>\n\n\n## Whistle 安装启动\n\n### 1、Whistle 安装\n\n\n（1）通过 npm 安装 Whistle\n\n\n### 2、启动 whistle\n\n```bash\nw2 start\n```\n\n然后在浏览器输入`http://127.0.0.1:8899/` 即可打开代理配置的页面。\n\n\n### 3、配置代理\n\n**chrome浏览器配置代理**：\n\n可参考官方文档。\n\n**Firefox浏览器配置代理**：\n\n![](https://img.smyhvae.com/20200420_1357.png)\n\n\n\n### 4、安装证书并添加信任：\n\n![](https://img.smyhvae.com/20200420_0922.png)\n\n\n证书下载后，双击安装，安装目录选择“登录”这个tab。安装完成后，记得执行 `w2 restart`重启 whistle。\n\n\n### 手机设置代理\n\n连接好指定的wifi后，点击那个wifi里的设置，将「代理」那一项，设置为手动，然后输入ip（电脑上的ip）、端口号（8899）。然后就可以通过电脑上的whistle工具，查看手机的网页请求。\n\n注意，要保证手机和电脑在同一个网络下。\n\n另外，还需要在手机的浏览器，地址栏输入`rootca.pro`，给手机安装证书。\n\n\n\n## 捕获和拦截https请求\n\n\nwhistle安装证书后，可以拦截 https 请求。但是，我现在又不想拦截https请求了，该怎么卸载证书呢？\n\n我发现，证书无法卸载，正确的操作是：\n\n![](http://img.smyhvae.com/20180426_1621.png)\n\n上图中，把红框部分，去掉勾选，就不捕获https了。谢谢azh童鞋。\n\n\n参考链接：\n\n- [Android 手机如何设置http代理？](https://www.zhihu.com/question/21474174)\n\n- [使用 Whistle 对 iOS HTTPS 进行抓包](http://zhuscat.com/2017/09/20/https-proxy-on-ios/)\n\n\n## 移动端调试神器:eruda\n\n\n> 手机连接代理时，如何看console.log的日志信息?\n\n> 现在，代码里有console.log，如果是在电脑浏览器上看，可以直接在控制台查看console.log的内容。但是，如果手机连接代理，在手机上打开网页的话，要怎么查看console.log的内容呢？具体做法如下：\n\n（1）在 whistle中，新建一个名叫`Eruda H5`的代理，代理中的内容是：\n\n```\nhttp://xxx.com htmlAppend://{eruda.html}\n```\n\n(2)新建一个values，里面的内容是：\n\n```html\n<script src=\"//cdn.bootcss.com/eruda/1.4.3/eruda.min.js\"></script>\n<script>\n    eruda.init()\n</script>\n```\n\n\n然后就OK了。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "00-前端工具/04-解决 Git 不区分大小写导致的文件冲突问题.md",
    "content": "---\ntitle: 04-解决 Git 不区分大小写导致的文件冲突问题\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 解决 Git 不区分大小写导致的文件冲突问题\n\n- 文章发布时间：2022-02-17\n\n- 作者：@千古壹号\n\n有些同学在 Git 仓库对文件/文件夹进行命名时，刚开始是小写，后来为了保持团队一致，又改成了大写，然而 Git 不会发现大小写的变化，此时就出了问题：导致仓库里出现了 大小写 同时存在的两个文件。但在 Windows、Mac 的电脑磁盘里，肉眼却能只看到一个文件，实在奇葩。\n\n这个问题的根本原因是，Windows、Mac 的**文件系统**不区分大小写。\n\nLinux的文件系统是区分大小写的。Git 默认是不区分大小写的，也可以通过改配置项，改为区分大小写。\n\n### 问题复现路径\n\n（1）新建一个 test 文件(大小写不敏感的状态下)，并提交。\n\n（2）本地修改 test 变为 Test，文件内容无变更，无法提交。\n\n（3）执行 `git config core.ignorecase false`，设 置Git的规则为 区分大小写（大小写敏感），然后 git push 提交，查看结果，此时远程仓库会存在 大小写 同时存在的文件，但本地仓库却只看到其中一个文件。\n\n（4）甚至可能出现这种异常情况：本地暂存区的文件，怎么删也删不掉。再之后，从 test 尝试改为 Test 时，提示命名冲突。\n\n### 错误的解决办法\n\n```bash\ngit mv test Test\n```\n\n执行上面的命令时，会报错：`fatal: renaming 'Test' failed: Invalid argument`\n\n### 正确的解决办法\n\n```bash\n# 将本地的 test、Test 目录都删掉，并生成一个新的目录 Temp\ngit mv Test Temp\n\n# 将 Temp 目录改成 Test 目录。此时，项目中只会存在 Test 目录，不会存在 test 目录。目标达成。\ngit mv Temp Test\n```\n\n执行完上面的两个命令之后，项目中只会存在 Test 目录，不会存在 test 目录。目标达成。\n\n### 关于 是否区分大小写 的补充说明\n\n我们知道：针对文件/文件夹，Windows 系统和 Mac 系统是不区分大小写的；Linux 系统是区分大小写的；Git 默认是不区分大小写的，也可以通过改配置项，改为区分大小写。\n\n不分区大小写，也有它的好处，比如：文件夹/文件的路径，很多时候就代表了网站地址、页面url的路径。而**网站地址也是不区分大小写的**，这是很关键的原因之一。\n\n总的来说，根本原因是文件系统、url在底层设计上不区分大小写。磁盘路径、页面地址，本质上都是 url 。\n\n### 关于 Git是否区分大小写 的补充\n\n前面讲到，Git 默认是不区分大小写的，可以通过命令`git config --get core.ignorecase`查到，默认值是 true。\n\n我们也可以修改 Git 的这一配置项，改为区分大小写，配置命令为`git config core.ignorecase false`。\n\n但我建议你**保留 Git 的默认配置项**，不要随意自行修改，避免产生其他的麻烦。\n\n\n### 参考链接\n\n- [Mac 中 git 大小写问题的解决方案](https://shanyue.tech/bug/mac-git-ignorecase.html)\n\n- [git 大小写问题 踩坑笔记](https://blog.csdn.net/u013707249/article/details/79135639)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "00-前端工具/Atom在前端的使用.md",
    "content": "\n\n## 常用插件\n\n\n- `Emmet`：快速生成HTML片段，比如输入ul>li*3可以快速生成：\n\n```html\n<ul>\n  <li></li>\n  <li></li>\n  <li></li>\n</ul>\n```\n\n[详细地址](https://atom.io/packages/emmet)，[Emmet教程](https://docs.emmet.io/cheat-sheet/)\n\n- `Snippets`：快速生成 js 代码片段（用来处理代码片段的模板输出），[详细地址](https://atom.io/packages/snippets)\n\n- `Tree View`：文件浏览器，[详细地址](https://atom.io/packages/tree-view)\n\n- `file icons`：文件识别图标，使用这个插件会让你的编辑器显示对应的图标，[详细地址](https://atom.io/packages/file-icons)\n\n- `language-javascript-jsx`：jsx语法高亮 ，[详细地址](https://atom.io/packages/language-javascript-jsx)\n\n- `language-vue`：vue语法高亮，[详细地址](https://atom.io/packages/language-vue)\n\n- `linter-eslint`：eslint插件，[详细地址](https://atom.io/packages/linter-eslint)\n\n- `vue-snippets`：vue代码片段，[详细地址](https://atom.io/packages/vue-snippets)\n\n- `pigments`：颜色显示器，[详细地址](https://atom.io/packages/pigments)\n\n- `linter-eslint`：语法检查\n\n- `Atom-Beautify`：代码格式化\n\n参考链接：<https://github.com/cucygh/JDFinance/blob/master/issue.md>\n\n## 插件无法安装的问题\n\n### 方法一：设置代理\n\n`C:\\Users\\smyhvae\\.atom\\.apm`目录下的.apmrc配置文件（没有就新建一个）,然后添加代理信息：\n\n```\nstrict-ssl=false\nhttps-proxy=http://127.0.0.1:1080/\nhttp-proxy =http://127.0.0.1:1080/\n```\n\n这里的 http://127.0.0.1:1080，是我自己的 Shadowsocks 代理，你需要设置成自己的可用代理。\n然后再执行：\n\n```\napm install --check\n```\n\n应该可以测试成功，祝好运~~\n\n参考链接：\n\n- <https://atom-china.org/t/atom/984>\n\n- <https://zhenyong.github.io/2016/08/03/starting-atom/>\n\n\n## Markdown相关\n\n### 在编辑器中预览\n\n2018-06-JD日记.md\n\nPackages -> Markdown Preview -> Toggle Preview\n\n快捷键：Shift + Ctrl + M\n\n\n\n### 参考链接：\n\n- [使用Atom打造无懈可击的Markdown编辑器](http://www.cnblogs.com/fanzhidongyzby/p/6637084.html)\n\n\n\n\n\n## 相关设置\n\n### 显示缩进线\n\nsettings -->Editor --> Show Indent Guide\n\n"
  },
  {
    "path": "00-前端工具/Emmet in VS Code.md",
    "content": "## 前言\n\n`Emmet`可以极大的提高 html 和 css 的编写效率，它提供了一种非常简练的语法规则。\n\n举个例子，我们在编辑器中输入缩写代码：`ul>li*6` ，然后按下 Tab 键，即可得到如下代码片段：\n\n```html\n    <ul>\n        <li></li>\n        <li></li>\n        <li></li>\n        <li></li>\n        <li></li>\n        <li></li>\n    </ul>\n```\n\n\n## 如何在某个语言中打开 Emmet 支持\n\n默认情况下，你可以直接在 html、haml、jade、slim、jsx、xml、xsl、css、scss、sass、less、stylus、handlebars、php 和 javascriptreact 中使用 Emmet 。\n\n但对于其他语言，你也可以通过如下设置将其打开：\n\n```json\n\"emmet.includeLanguages\": {\n    \"javascript\": \"javascriptreact\",\n    \"vue-html\": \"html\",\n    \"razor\": \"html\",\n    \"plaintext\": \"jade\"\n}\n\n```\n\n上方代码的意思是，将某个 Emmet 默认不支持的语言，映射到某个 Emmet 支持的语言上。比如上面的设置里，我们把 vue-html 映射成了 html，那么当你在 vue-html 使用 Emmet 时，Emmet 就会把它当作 html 来处理了。\n\n\n## Emmet 语法规则\n\n语法规则：\n\n```\nul>li*6\n```\n\n\n最终效果：\n\n```html\n    <ul>\n        <li></li>\n        <li></li>\n        <li></li>\n        <li></li>\n        <li></li>\n        <li></li>\n    </ul>\n```\n\n\n---\n\n语法规则：\n\n```\np5\n\n```\n\n\n\n最终效果：\n\n```\npadding: 5px;\n```\n\n\n\n\n\n"
  },
  {
    "path": "00-前端工具/GitHub的使用.md",
    "content": "\n\n## GitHub的使用\n\n\n### GitHub添加wiki\n\n参考链接：\n\n\n- <https://juejin.im/post/5a3216c8f265da43333e6b54>\n\n### GitHub项目添加 license\n\n参考链接：\n\n- <https://blog.csdn.net/qq_35246620/article/details/77647234>\n\n\n\n### GitHub 引用图片的另一种方式\n\n参考链接：\n\n- [关于markdown文件插入图片遇到的小问题和解决办法](https://www.cnblogs.com/cxint/p/7200164.html)\n\n\n\n"
  },
  {
    "path": "00-前端工具/Mac安装和配置iTerm2.md",
    "content": "\n\n## 参考链接\n\n- [iTerm2 + Oh My Zsh 打造舒适终端体验](https://github.com/sirius1024/iterm2-with-oh-my-zsh)\n\n- [安装oh my zsh失败：curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused](https://blog.csdn.net/huangpin815/article/details/105606135)\n\n- <https://www.jianshu.com/p/246b844f4449>\n\n\n"
  },
  {
    "path": "00-前端工具/Sublime Text在前端中的使用.md",
    "content": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 新建文件时快速生成Html\r\n\r\n**安装如下插件：**\r\n\r\n- FileHeader：自动创建文件开头模板，并且会根据最后的保存时间修改更新时间。[官网链接](https://github.com/shiyanhui/FileHeader)。\r\n- CSS Format：css格式化。\r\n- Emmet：它能够让你在编辑器中书写CSS和HTML的缩写并且动态地拓展它，是一个能大幅度提高前端开发效率的一个工具。这个软件的安装过程比较久。[官网教程](http://docs.emmet.io/)。\r\n\r\n**开始使用：**\r\n\r\n新建文件，输入`html:5`，按[Ctrl + E] 或者 Tab 键，\r\n\r\n\r\n参考链接：[Sublime Text 新建文件快速生成Html【头部信息】和【代码补全】、【汉化】](http://www.jianshu.com/p/f44e91bf9dfb)\r\n\r\n\r\n\r\n## 常用插件\r\n\r\n### SublimeCodeIntel：JavaScript代码自动提示（不好用）\r\n\r\n安装完成后，通过路径Perferences->Package Settings->SublimeCodeIntel->Setting - Defalut打开配置文件。\r\n\r\n将\r\n\r\n```\r\n\"codeintel_selected_catalogs\": [\"jQuery\"]\r\n\r\n```\r\n\r\n改为：\r\n\r\n```\r\n\"codeintel_selected_catalogs\": [\"JavaScript\"]\r\n```\r\n\r\n保存后重启软件。\r\n\r\n参考链接：[#](http://blog.csdn.net/hehexiaoxia/article/details/54134756)\r\n\r\n\r\n### javascript complet：JavaScript代码自动提示\r\n\r\n\r\n- [官网链接](https://packagecontrol.io/packages/JavaScript%20Completions)\r\n\r\n- [GitHub链接](https://github.com/pichillilorenzo/JavaScript-Completions)\r\n\r\n在google上搜关键字\"sublime javascript complete\"找到的。另外还搜到一个[SublimeAllAutocomp lete](https://github.com/alienhard/SublimeAllAutocomplete)\r\n\r\n\r\n### JsFormat：JS代码格式化\r\n\r\n\r\n快捷键是：选中JS代码，然后按ctrl+alt+f。\r\n\r\n或者，先用快捷键打开命令面板 “ctrl + shift + p”, 再输入 “Format: Javascript” 就可以使用格式化命令\r\n\r\n\r\n### Sublime Server\r\n\r\n我们如果右键在浏览器中打开html网页，其实是以**文件路径**的方式打开的，导致很多api无法操作。最好的办法是：将html在服务器上打开。\r\n\r\n我们如果安装 `Sublime Server`，就可以让网页在本地的服务器上打开。\r\n\r\n安装成功之后，使用步骤如下：\r\n\r\n（1）选择菜单栏\"Tools --> SublimeServer --> Start SublimeServer\"启动服务器。\r\n\r\n（2）在html文档里，右键选择 \"View in SublimeServer\"。\r\n\r\n如果想关闭服务器，直接把Sublime Text 整个软件关掉就好。其他的关闭方式容易导致软件卡死。\r\n\r\n\r\n\r\n\r\n## 代码快速生成\r\n\r\n\r\n（1）`>`符号的技巧：\r\n\r\n\r\n输入`ul>li*9`，然后按tab键，生成的代码如下；（符号`>`是包含的关系）\r\n\r\n```html\r\n        <div>\r\n            <li>a</li>\r\n            <li>a</li>\r\n            <li>a</li>\r\n            <li>a</li>\r\n            <li>a</li>\r\n            <li>a</li>\r\n            <li>a</li>\r\n            <li>a</li>\r\n            <li>a</li>\r\n        </div>\r\n    </div>\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n## sublime text 快捷键\r\n\r\n| Win快捷键 | Mac快捷键 |作用 | 备注 |\r\n|:-------------|:-------------|:-----|:-----|\r\n|html:5+tab||html结构代码||\r\n|tab||补全标签代码||\r\n|tab|补全标签代码| |比如，在html文件中输入`div`，然后\t按住tab键后，会自动生成`<div></div>`。||\r\n|  **Ctrl + Shift + D** | Cmd + Shift + D|复制当前行到下一行  |   |\r\n|  Ctrl+Shift+K ||  快速删除整行 |   |\r\n|Ctrl+鼠标左键单击||集体输入||\r\n|Ctrl+H|Option+Cmd+F|查找替换|||\r\n| Ctrl+/  ||  注释单行 |   |\r\n| Ctrl+Shift+/  || 注释多行  |   |\r\n|Ctrl+L| | 快速选中整行，继续操作则继续选择下一行，效果和 `Shift + ↓` 效果一样| |\r\n|**Ctrl+Shift+L**| | 先选中多行，再按下快捷键，会在每行行尾插入光标，即可同时编辑这些行| 经常与上一个快捷键结合起来使用 |\r\n|**Ctrl + Shift +【↑/↓**| Ctrl + Cmd +↑/↓ | 移动当前行 | |\r\n|F11||全屏||\r\n\r\n\r\n\r\n\r\n## 推荐阅读\r\n\r\n\r\n- [Sublime Text使用技巧](https://github.com/smyhvae/tools/blob/master/01-%E4%B8%AA%E4%BA%BA%E6%95%B4%E7%90%86/Sublime%20Text%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7.md)\r\n\r\n我自己整理的。\r\n\r\n\r\n## 参考链接\r\n\r\n- [像 Sublime Text 一样使用 Chrome DevTools](https://chinagdg.org/2015/12/%E5%83%8F-sublime-text-%E4%B8%80%E6%A0%B7%E4%BD%BF%E7%94%A8-chrome-devtools/)\r\n\r\n\r\n\r\n"
  },
  {
    "path": "00-前端工具/VS Code的使用积累.md",
    "content": "\n\n\n## 常见配置\n\n\n**自动保存**：\n\n```\n \"files.autoSave\": \"onFocusChange\"\n```\n\n参考链接：<https://blog.csdn.net/WestLonly/article/details/78048049>\n\n\n\n**在新的窗口中打开文件**：\n\n```json\n\"workbench.editor.enablePreview\": false,\n```\n\n## 常见操作\n\n### 如何查看代码结构\n\n- 方法一：「Cmd + Shift + O」\n\n- 方法二：安装插件`Code Outline`\n\n参考链接：<https://www.zhihu.com/question/264045094>\n\n\n\n### 在本地开启服务器\n\n```bash\n# 安装\nnpm install -g live-server\n\n# 启动\nlive-server\n```\n\n\n参考链接：[Visual Studio Code + live-server编辑和浏览HTML网页](http://www.cnblogs.com/1zhk/p/5699379.html)\n\n\n## 常用插件\n\n方式一：打开VS Code，左侧有五个按钮，点击最下方的按钮，然后就可以开始安装相应的插件了。\n\n方式二：在vscode中输入快捷键「ctrl+shift+P」，弹出指令窗口，输入`extension:install`，回车，左侧即打开扩展安装的界面。\n\n\n### sftp：文件传输\n\n\n输入快捷键「ctrl+shift+P」，弹出指令窗口，输入`sftp:config`，回车，当前工作工程的`.vscode`文件夹下就会自动生成一个`sftp.json`文件，我们需要在这个文件里配置的是：\n\n- `host`：服务器的IP地址\n\n- `username`：工作站自己的用户名\n\n- `privateKeyPath`：存放在本地的已配置好的用于登录工作站的密钥文件（也可以是ppk文件）\n\n- `remotePath`：工作站上与本地工程同步的文件夹路径，需要和本地工程文件根目录同名，且在使用sftp上传文件之前，要手动在工作站上mkdir生成这个根目录\n\n- `ignore`：指定在使用sftp: sync to remote的时候忽略的文件及文件夹，注意每一行后面有逗号，最后一行没有逗号\n\n\n举例如下：(注意，其中的注释不能保留)\n\n```json\n{\n    \"host\": \"\",     //服务器ip\n    \"port\": 22,     //端口，sftp模式是22\n    \"username\": \"\", //用户名\n    \"password\": \"\", //密码\n    \"protocol\": \"sftp\", //模式\n    \"agent\": null,\n    \"privateKeyPath\": null,\n    \"passphrase\": null,\n    \"passive\": false,\n    \"interactiveAuth\": false,\n    \"remotePath\": \"/root/node/build/\",  //服务器上的文件地址\n    \"context\": \"./server/build\",        //本地的文件地址\n\n    \"uploadOnSave\": true,   //监听保存并上传\n    \"syncMode\": \"update\",\n    \"watcher\": {            //监听外部文件\n        \"files\": false,     //外部文件的绝对路径\n        \"autoUpload\": false,\n        \"autoDelete\": false\n    },\n    \"ignore\": [             //忽略项\n        \"**/.vscode/**\",\n        \"**/.git/**\",\n        \"**/.DS_Store\"\n    ]\n}\n```\n\n\n### Sass Formatter\n\nSass 文件格式化。\n\n\n### Code Outline：显示代码结构\n\n\n安装好插件「Code Outline」后，可以在左侧的资源管理器中，显示当前文件的代码结构：\n\n![](http://img.smyhvae.com/20180420_0925.png)\n\n参考链接：\n\n- <https://www.zhihu.com/question/50273450>\n\n- <http://itopic.org/vscode.html>\n\n- <https://github.com/varHarrie/varharrie.github.io/issues/10>\n\n### vscode-fileheader：添加顶部注释模板(签名)\n\n（1）安装插件vscode -fileheader，并重启。\n\n（2）在首选项-》设置-》中搜索fileheader，找到头部模板修改。\n\n默认的快捷键是：「Ctrl + option + I」。\n\n参考链接：\n\n- <https://www.zhihu.com/question/62385647>\n\n\n### Express\n\n在本地开启Node服务器：\n\n![](http://img.smyhvae.com/20180611_2230.png)\n\n然后在浏览器的地址栏输入`http://localhost/` + 文件的相对路径，就可以通过服务器的形式打开这个文件。\n\n### Copy Relative Path\n\n> 这个插件很有用，使用频率很高。\n\n复制文件的相对路径：（相对于根路径而言）\n\n![](http://img.smyhvae.com/20180611_2235.png)\n\n\n### open in browser\n\n在浏览器中打开。\n\n\n### Auto Rename Tag\n\n适用于 JSX、Vue、HTML。在修改标签名时，能在你修改开始（结束）标签的时候修改对应的结束（开始）标签，帮你减少 50% 的击键。\n\n\n###Project Manager\n\n项目管理，让我们方便的在命令面板中切换项目文件夹，当然，你也可以直接打开包含多个项目的父级文件夹，但这样可能会让 VSCode 变慢。\n\n\n\n### highlight-icemode：选中相同的代码时，让高亮显示更加明显【荐】\n\nVSCode自带的高亮显示，实在是不够显眼。用插件支持一下吧。\n\n所用了这个插件之后，VS Code自带的高亮就可以关掉了：\n\n在用户设置里添加`\"editor.selectionHighlight\": false`即可。\n\n\n参考链接：[vscode 选中后相同内容高亮插件推荐](https://blog.csdn.net/palmer_kai/article/details/79548164)\n\n\n### highlight-words：全局高亮（跨文件多色彩）\n\n\n参考链接：[Visual Studio Code全局高亮着色插件(跨文件多色彩)经验纪要](https://zhuanlan.zhihu.com/p/31163793)\n\n\n### color-exchange：颜色格式转换【荐】\n\n\n安装完插件后，在css中输入颜色，然后按`cmd + .`，就能进行颜色的格式转换。\n\n20181013_1450.png\n\n我在写这一段时，安装的人还不多，赶紧上车。\n\n\n\n## Vue 相关的插件\n\n### vetur：vue 文件的基本语法高亮\n\n安装完 vetur 后还需要加上这样一段配置下：\n\n```\n\"emmet.syntaxProfiles\": {\n  \"vue-html\": \"html\",\n  \"vue\": \"html\"\n}\n```\n\n参考链接：\n\n- <https://www.clarencep.com/2017/03/18/edit-vue-file-via-vscode/>\n\n\n- <https://github.com/varHarrie/varharrie.github.io/issues/10>\n\n\n\n### 参考链接\n\n- <https://www.jianshu.com/p/0724921285d4>\n\n- <https://www.cnblogs.com/AmosLee94/p/8338013.html>\n\n\n\n\n## 常用快捷键\n\n\n| Win快捷键 |Mac快捷键| 作用 | 备注 |\n|:-------------|:-------------|:-----|:-----|\n| Shift + Alt + F |Shift + option + F| 代码格式化 |  |\n| Ctrl + Shift + N | |在当前行上面增加一行并跳至该行  |   |\n|  **Ctrl + Shift + D** | |复制当前行到下一行  |   |\n\n\n\n\n\n### 如何同时打开多个窗口\n\n\n\n\n\n\n\n\n\n\n\n\n## 问题\n\n问题1\n\n解决；You can kill the Microsoft.VSCode.Cpp.IntelliSense.Msvc process to save the file successfully. 也就是 IntelliSense 这个进程。\n\n\n\n\n## 参考链接\n\n- [能让你开发效率翻倍的 VSCode 插件配置（上）](https://zhuanlan.zhihu.com/p/30976584)\n\n\n### 某网友的VS Code 插件截图\n\n![](http://img.smyhvae.com/20180611_2255.png)\n\n\n\n\n\n\n\n"
  },
  {
    "path": "00-前端工具/WebStorm的使用.md",
    "content": "\n\n\n## WebStorm的简单设置\n\n#### 1、主题修改：\n\n可能大家会觉得软件的界面不太好看，我们可以换一下主题。选择菜单栏“File--settings--appearance--theme”，主题选择 Dracula：\n\n![](http://img.smyhvae.com/20180118_1600.png)\n\n#### 2、导入第三方主题：\n\n系统提供的两种主题可能都不太好看，我们可以进入网站<http://color-themes.com/>来获取第三方主题，这里推荐两个主题，大家二选一即可：\n\n- [Sublime](https://github.com/y3sh/Intellij-Colors-Sublime-Monokai)\n\n- [Material](https://github.com/ChrisRM/material-theme-jetbrains)\n\n![](http://img.smyhvae.com/20180118_1636.png)\n\n![](http://img.smyhvae.com/20180118_1637.png)\n\n上图中，在网站<http://color-themes.com/>中将主题下载之后，是一个jar包。那怎么导入到WebStorm呢？\n\n别着急，回到WebStorm，选择菜单栏“ File-Import Settings”，将下载好的jar包导入即可。\n\n\n\n#### 3、代码字体修改：\n\n选择菜单栏“File--settings--Editor--Font”：\n\n![](http://img.smyhvae.com/20180118_1627.png)\n\n上图中，点击红框部分，然后弹出如下界面：\n\n![](http://img.smyhvae.com/20180118_1628.png)\n\n我们在上图中修改代码的字体。\n\n修改完之后发现 WebStorm 的一些默认字体（比如侧边栏的工程目录的字体）并没有发生变化，如果想改的话，也可以改（我个人一般是不改的）。\n\n\n\n#### 4、关闭更新：\n\n如下图所示：\n\n![](http://img.smyhvae.com/20180118_1646.png)\n\n#### 5、快捷键习惯的修改：\n\n\n\n\n#### 7、配置代码的自动提示：\n\n\n\n#### 14、修改文件编码为UTF-8：\n\nWebStorm 2017.3.3版本的默认编码方式是 GBK，我们还是统一设置为UTF-8吧，不要坑队友哦：\n\n![](http://img.smyhvae.com/20180124_1856.png)\n\n\n### 新建一个空的项目\n\n配置完成后，可以开始新建一个项目文件夹（站点），项目通常包含如下内容：\n\n- 首页：index.html\n\n- 样式：css文件夹\n\t- index.css\n\t- 相同样式：全局样式、公共样式。起名为：base.css（基本样式）或者 global.css (全局样式)\n\n- 图片：images文件夹、文件\n\n- 特效：js文件夹、js文件\n\n**步骤如下：**\n\n（1）新建一个空的项目：\n![](http://img.smyhvae.com/20180118_1720.png)\n\n（2）然后新建一个html文件：\n\n![](http://img.smyhvae.com/20180118_1602.png)\n\n（3）新建一个空的文件夹，命名为`css`：\n\n![](http://img.smyhvae.com/20180118_1725.png)\n\n然后在这个css文件夹中，新建样式表：（比如index.css\\base.css）\n\n![](http://img.smyhvae.com/20180118_1730.png)\n\n（4）最后新建一个images文件夹，用于存放土片。这样的话，一个基本的项目结构就搭建好了：\n\n![](http://img.smyhvae.com/20180118_1733.png)\n\n接下来，开始运用起你们的前端知识吧。\n\n\n\n（5）如果要新建JS文件的话，操作如下：\n\n![](http://img.smyhvae.com/20180124_1859.png)\n\n\n\n## 使用技巧\n\n\n\n#### 多光标编辑\n\n我们可以按住鼠标不松手，选中多个光标，然后同时编辑：\n\n\n#### 随时在浏览器中看代码效果\n\n20180118_1658.png\n\n如上图所示，我们可以点击右上角的浏览器图标，在各个浏览器中看效果。\n\n\n#### 实时查看颜色\n\n写代码时如果想输入颜色，会自动提示颜色的预览。\n\n![](http://img.smyhvae.com/20180118_1702.png)\n\n点击最左侧的颜色预览，还能弹出调色板：\n\n![](http://img.smyhvae.com/20180118_1710.gif)\n\n\n\n\n\n\n## 代码的自动补齐\n\n\n（1）在html文档中，输入`div*10`，按tab键后，弹出的效果如下：\n\n```html\n    <div></div>\n    <div></div>\n    <div></div>\n    <div></div>\n    <div></div>\n    <div></div>\n    <div></div>\n    <div></div>\n    <div></div>\n    <div></div>\n```\n\n\n\n（2）在html文档中，输入如下部分：\n\n```\n.search-logo+.search-input+.search-car+.search-moreA\n```\n\n按tab键后，弹出的效果如下：\n\n```html\n        <div class=\"search-logo\"></div>\n        <div class=\"search-input\"></div>\n        <div class=\"search-car\"></div>\n        <div class=\"search-moreA\"></div>\n```\n\n你看，京东的搜索框就包含了这几个div：\n\n20180122_1045.png\n\n（3）方法的注释：\n\n方法写完之后（注意，一定要先写完整），我们在方法的前面输入`/**`，然后回车，会发现，注释的格式会自动补齐。\n\n比如：\n\n```javascript\n/**\n * 功能：给定元素查找他的第一个元素子节点，并返回\n * @param ele\n * @returns {Element|*|Node}\n */\nfunction getFirstNode(ele){\n    var node = ele.firstElementChild || ele.firstChild;\n    return node;\n}\n```\n\n\n## 常用快捷键\n\n#### 标签环绕\n\n输入一段字符后，按住`Ctrl + Alt + T`，可以用标签将这段字符环绕：\n\n![](http://img.smyhvae.com/20180118_1719.gif)\n\n\n\n#### 选中正行中的文本\n\n比如下面这行：\n\n```\n    text-align: center;  /*让 li 里面的文本水平方向居中*/\n\n```\n\n如果直接按 【ctrl+C】的话，复制的是整行的内容，把前面的空格也包含进去了。如果不想复制空格，有另外一个办法：将光标放在行尾，然后按住【shift+home】，就能选中你想要的内容了。\n\n\n\n\n"
  },
  {
    "path": "00-前端工具/chrome浏览器.md",
    "content": "\n\n\n## 控制台的使用\n\n### 控制台查看源码\n\n控制台的`Sources`标签可以查看源码。按住快捷键「cmd + P」，可以根据文件名查找源码文件。\n\n\n\n## 其他\n\n### show user agent shadow DOM\n\n![](http://img.smyhvae.com/20180206_1610.png)\n\n\n![](http://img.smyhvae.com/20180206_1616.png)\n\n把上图中的红框部分打钩。\n\n\n\n\n\n"
  },
  {
    "path": "00-前端工具/iconMoon.md",
    "content": "iconMoon.md\n\n\n\n- <https://www.cnblogs.com/chinabin1993/p/8185398.html>\n\n- <https://blog.csdn.net/web_harry/article/details/70310597>\n\n- <https://blog.csdn.net/qq_37261367/article/details/80012320>\n\n"
  },
  {
    "path": "01-HTML/01-认识Web和Web标准.md",
    "content": "---\ntitle: 01-认识Web和Web标准\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## Web、网页、浏览器\n\n### Web\n\nWeb（World Wide Web）即全球广域网，也称为万维网。\n\n我们常说的`Web端`就是网页端。\n\n### 网页\n\n**网页是构成网站的基本元素**。网页主要由文字、图像和超链接等元素构成。当然，除了这些元素，网页中还可以包含音频、视频以及Flash等。\n\n我们在浏览器上输入网址后，打开的任何一个页面，都是属于网页。\n\n### 浏览器\n\n浏览器是网页运行的平台，常见的浏览器有谷歌（Chrome）、Safari、火狐（Firefox）、IE、Edge、Opera等。\n\n关于浏览器的详细介绍，可以看下一篇文章：《浏览器的介绍》。\n\n## Web标准\n\n\n### W3C组织\n\n**W3C**：World Wide Web Consortium，万维网联盟组织，用来制定web标准的机构（组织）。\n\nW3C 万维网联盟是国际最著名的标准化组织。1994年成立后，至今已发布近百项相关万维网的标准，对万维网发展做出了杰出的贡献。\n\nW3C 组织就类似于现实世界中的联合国。\n\n为什么要遵循WEB标准呢？因为很多浏览器的浏览器内核不同，导致页面解析出来的效果可能会有差异，给开发者增加无谓的工作量。因此需要指定统一的标准。\n\n### Web 标准\n\n**Web标准**：制作网页要遵循的规范。\n\nWeb标准不是某一个标准，而是由W3C组织和其他标准化组织制定的一系列标准的集合。\n\n**1、Web标准包括三个方面**：\n\n- 结构标准（HTML）：用于对网页元素进行整理和分类。\n\n- 表现标准（CSS）：用于设置网页元素的版式、颜色、大小等外观样式。\n\n- 行为标准（JS）：用于定义网页的交互和行为。\n\n根据上面的Web标准，可以将 Web前端分为三层，如下。\n\n**2、Web前端分三层**：\n\n- HTML（HyperText Markup Language）：超文本标记语言。从**语义**的角度描述页面的**结构**。相当于人的身体组织结构。\n- CSS（Cascading Style Sheets）：层叠样式表。从**审美**的角度美化页面的**样式**。相当于人的衣服和打扮。\n- JavaScript（简称JS）：从**交互**的角度描述页面的**行为**，实现业务逻辑和页面控制。相当于人的动作，让人有生命力。\n\n**3、打个比方**：（拿黄渤举例）\n\nHTML 相当于人的身体组织结构：\n\n![](http://img.smyhvae.com/20200322_1250.png)\n\nCSS 相当于人的衣服和打扮：\n\n![](http://img.smyhvae.com/20200322_1251.png)\n\nJS 相当于人的行为：\n\n![](http://img.smyhvae.com/20200322_2220.gif)\n\n\n---\n\n本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。\n\n![](https://img.smyhvae.com/20210329_1930.png)"
  },
  {
    "path": "01-HTML/02-浏览器的介绍.md",
    "content": "---\ntitle: 02-浏览器的介绍\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 常见的浏览器\n\n浏览器是网页运行的平台，常见的浏览器有谷歌（Chrome）、Safari、火狐（Firefox）、IE、Edge、Opera等。如下图所示：\n\n![](http://img.smyhvae.com/20191204_1900.png)\n\n我们重点需要学习的是 Chrome 浏览器。\n\n## 浏览器的市场占有份额\n\n浏览器的市场占有份额：<https://tongji.baidu.com/research/site?source=index#browser>\n\n![](http://img.smyhvae.com/20200322_1058.png)\n\n上面这张图的统计时间是2020年2月。\n\n## 浏览器的组成\n\n浏览器分成两部分：\n\n- 1、渲染引擎（即：浏览器内核）\n\n- 2、JS 引擎\n\n### 1、渲染引擎（浏览器内核）\n\n浏览器所采用的「渲染引擎」也称之为「浏览器内核」，用于解析 HTML和CSS、布局、渲染等工作。渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。\n\n**渲染引擎是浏览器兼容性问题出现的根本原因。**\n\n渲染引擎的英文叫做 Rendering Engine。通俗来说，它的作用就是：读取网页内容，计算网页的显示方式并显示在页面上。\n\n常见浏览器的内核如下：\n\n|浏览器 | 内核|\n|:-------------:|:-------------:|\n| chrome | Blink  |\n| 欧鹏  | Blink  |\n|360安全浏览器| Blink|\n|360极速浏览器| Blink|\n|Safari|Webkit|\n|Firefox 火狐|Gecko|\n|IE| Trident |\n\n备注：360的浏览器，以前使用的IE浏览器的Trident内核，但是现在已经改为使用 chrome 浏览器的 Blink内核。\n\n另外，移动端的浏览器内核是什么？大家可以自行查阅资料。\n\n\n### 2、JS 引擎\n\n也称为 JS 解释器。 用来解析和执行网页中的JavaScript代码。\n\n浏览器本身并不会执行JS代码，而是通过内置 JavaScript 引擎(解释器) 来执行 JS 代码 。JS 引擎执行代码时会逐行解释每一句源码，转换为机器语言，然后由计算机去执行。\n\n常见浏览器的 JS 引擎如下：\n\n|浏览器 | JS 引擎|\n|:-------------:|:-------------|\n|chrome、欧鹏   | V8   |\n|Mozilla Firefox 火狐|SpiderMonkey（1.0-3.0）/ TraceMonkey（3.5-3.6）/ JaegerMonkey（4.0-）|\n|Safari|JavaScriptCore，也称为Nitro，是 WebKit 引擎的一部分|\n|IE|Trident |\n|Edge|Chakra。此外，ChakraCore是Chakra的开源版本，可以在不同的平台上使用。 |\n|Opera|Linear A（4.0-6.1）/ Linear B（7.0-9.2）/ Futhark（9.5-10.2）/ Carakan（10.5-）|\n\n补充说明：\n\n1、SpiderMonkey 是第一款 JavaScript 引擎，由 JS语言的作者 Brendan Eich 开发。\n\n2、先以WebKit为例，WebKit上由两部分组成：\n\n- WebCore：负责解析HTML和CSS、布局、渲染等工作。\n- JavaScriptCore：负责解析和执行JavaScript 代码。\n\n参考链接：\n\n- [主流浏览器内核及JS引擎](https://juejin.im/post/5ada727c518825670b33a584)\n\n## 浏览器工作原理\n\n> 这一小段有些深入，小白可以暂时跳过，以后学习JS的时候再回来看。\n\n浏览器主要由下面这个七个部分组成：\n\n![](http://img.smyhvae.com/20180124_1700.png)\n\n1、User Interface（UI界面）：包括地址栏、前进/后退按钮、书签菜单等。也就是浏览器主窗口之外的其他部分。\n\n2、Browser engine （浏览器引擎）：用来查询和操作渲染引擎。是UI界面和渲染引擎之间的**桥梁**。\n\n3、Rendering engine（渲染引擎）：用于解析HTML和CSS，并将解析后的内容显示在浏览器上。\n\n4、Networking （网络模块）：用于发送网络请求。\n\n5、JavaScript Interpreter（JavaScript解析器）：用于解析和执行 JavaScript 代码。\n\n6、UI Backend（UI后端）：用于绘制组合框、弹窗等窗口小组件。它会调用操作系统的UI方法。\n\n7、Data Persistence（数据存储模块）：比如数据存储  cookie、HTML5中的localStorage、sessionStorage。\n\n参考链接：（关于浏览器的工作管理，下面这篇文章，是精品中的精品，是必须要知道的）\n\n- 英文版：[How Browsers Work: Behind the scenes of modern web browsers](https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/)\n\n- 中文版：[浏览器的工作原理：新式网络浏览器幕后揭秘](https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/)\n\n---\n\n本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。\n\n![](https://img.smyhvae.com/20210329_1930.png)\n\n"
  },
  {
    "path": "01-HTML/03-初识HTML.md",
    "content": "---\ntitle: 03-初识HTML\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 编辑器相关\n\n前端开发的编辑器软件，我首先推荐 VS Code，其次推荐Sublime Text。\n\n有人说 WebStorm 也不错？但真实情况是，自从VS Code 问世之后，用 WebStorm 的人越来越少了。\n\nPS：文件的后缀名不能决定文件格式，只能决定打开文件打开的方式。\n\n### VS Code 的使用\n\n详情请移步至：[第一次使用VS Code时你应该知道的一切配置](https://github.com/qianguyihao/Web/blob/master/00-%E5%89%8D%E7%AB%AF%E5%B7%A5%E5%85%B7/01-VS%20Code%E7%9A%84%E4%BD%BF%E7%94%A8.md)\n\n### Sublime Text 的使用\n\n详情请移步至：[Sublime Text使用技巧](https://github.com/qianguyihao/Mac/blob/master/03-%E5%85%A8%E5%B9%B3%E5%8F%B0%E8%BD%AF%E4%BB%B6/Sublime%20Text%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7.md)\n\n## HTML的概述\n\n### HTML的概念\n\n**HTML** 全称为 HyperText Markup Language，译为<font color=#0000ff>**超文本标记语言**</font>。\n\nHTML 不是一种编程语言，是一种描述性的**标记语言**。\n\n**作用**：HTML是负责描述文档**语义**的语言。\n\n### 概念：超文本\n\n所谓的超文本，有两层含义：\n\n（1）图片、音频、视频、动画、多媒体等内容，被称为超文本，因为它们超出了文本的限制。\n\n（2）不仅如此，它还可以从一个文件跳转到另一个文件，与世界各地主机的文件进行连接。即：超级链接文本。\n\n### 概念：标记语言\n\nHTML 不是一种编程语言，是一种描述性的**标记语言**。这主要有两层含义：\n\n（1）**标记语言是一套标记标签**。比如：标签`<a>`表示超链接、标签`<img>`表示图片、标签`<h1>`表示一级标题等等，它们都是属于 HTML 标签。\n\n说的通俗一点就是：网页是由网页元素组成的，这些元素是由 HTML 标签描述出来，然后通过浏览器解析，就可以显示给用户看了。\n\n（2）编程语言是有编译过程的，而标记语言没有编译过程，HTML标签是直接由浏览器解析执行。\n\n### HTML是负责描述文档语义的语言\n\nHTML 格式的文件是一个纯本文文件（就是用txt文件改名而成），用一些标签来描述语义，这些标签在浏览器页面上是无法直观看到的，所以称之为“超文本标记语言”。\n\n接下来，我们需要学习 HTML 中的很多“标签对儿”，这些“标签对儿”能够给文本不同的语义。\n\n比如，面试的时候问你，`<h1>` 标签有什么作用？\n\n- 正确答案：给文本增加主标题的语义。\n- 错误答案：给文字加粗、加黑、变大。\n\n关乎“语义”的更深刻理解，等接下来我们学习了各种标签，就明白了。\n\n## HTML的历史\n\n![html中标签发展趋势](http://img.smyhvae.com/20151001_1001.png)\n\n其中，我们专门来对XHTML做一个介绍。\n\n**XHTML介绍：**\nXHTML：Extensible Hypertext Markup Language，可扩展超文本标注语言。\nXHTML的主要目的是为了<font color=\"blue\">**取代HTML**</font>，也可以理解为HTML的升级版。\nHTML的标记书写很不规范，会造成其它的设备(ipad、手机、电视等)无法正常显示。\nXHTML与HTML4.0的标记基本上一样。\nXHTML是<font color=\"blue\">**严格的、纯净的**</font>HTML。\n\n我们稍后将对XHTML的编写规范进行介绍。\n\n## HTML的专有名词\n\n- 网页 ：由各种标记组成的一个页面就叫网页。\n- 主页(首页) : 一个网站的起始页面或者导航页面。\n- 标记：  比如`<p>`称为开始标记 ，`</p>`称为结束标记，也叫标签。每个标签都规定好了特殊的含义。\n- 元素：比如`<p>内容</p>`称为元素.\n- 属性：给每一个标签所做的辅助信息。\n- XHTML：符合XML语法标准的HTML。\n- DHTML：dynamic，动态的。`javascript + css + html`合起来的页面就是一个 DHTML。\n- HTTP：超文本传输协议。用来规定客户端浏览器和服务端交互时数据的一个格式。SMTP：邮件传输协议，FTP：文件传输协议。\n\n## 书写第一个 HTML 页面\n\n我们打开 VS Code 软件，新建一个文件，名叫`test.html`（注意，文件名是`test`，后缀名是`html`），保存到本地。\n\n紧接着，在文件里，输入`html:5`，然后按一下键盘上的`Tab`键，就可以自动生成如下内容：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n```\n\n上面的内容，就是 html 页面的骨架。我们在此基础之上，新增几个标签，完整代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Document</title>\n</head>\n<body>\n    <h3>我是三级标题</h3>\n    <img src=\"\" alt=\"\">\n    <a href=\"https://www.jd.com\">我是超链接，可以点击一下</a>\n</body>\n</html>\n\n```\n\n标签写完之后，我们用 chrome 浏览器打开上面这个`test.html`文件，看看页面效果：\n\n到此，第一个简单的 HTML 页面就写完了。是不是很有成就感？\n\n\n## HTML结构详解\n\nHTML标签通常是成对出现的（<font color=\"blue\">**双边标记**</font>），比如 `<div>` 和 `</div>`；也有少部分单标签（<font color=\"blue\">**单边标记**</font>），如：`<br />`、`<hr />`和`<img src=\"images/1.jpg\" />`等。\n\n属性与标记之间、各属性之间需要以空格隔开。属性值以双引号括起来。\n\n\n#### html骨架标签分类\n\n| 标签名              |   定义   | 说明                             |\n| ---------------- | :----: | :----------------------------- |\n| `<html></html>`    | HTML标签 | 页面中最大的标签，我们成为根标签             |\n| `<head></head>`    | 文档的头部  | 注意在head标签中我们必须要设置的标签是title     |\n| `<title></title>` | 文档的标题  | 让页面拥有一个属于自己的网页标题               |\n| `<body></body>`    | 文档的主体  | 元素包含文档的所有内容，页面内容 基本都是放到body里面的 |\n\n\n### 快速生成 html 的骨架\n\n**方式1**：在 VS Code 中新建 html 文件，输入`html:5`，按 `Tab`键后，自动生成的代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">\n    <title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n\n```\n\n**方式2**：在Sublime Text中安装`Emmet`插件。新建html文件，输入`html:5`，按`Tab`键后，自动生成的代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n```\n\n**方式3**：在Sublime Text中安装`Emmet`插件。新建html文件，输入`html:xt`，按`Tab`键后，自动生成的代码如下：\n\n```html\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">\n\t<title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n```\n\n上面的方式2和方式3中，我们会发现，第一行的内容有些不太一样，这就是我们接下来要讲的**文档声明头**。\n\n\n\n\n\n### 1、文档声明头\n\n任何一个标准的HTML页面，第一行一定是一个以`<!DOCTYPE ……>`开头的语句。这一行，就是文档声明头，即 DocType Declaration，简称DTD。\n\n**DTD可告知浏览器文档使用哪种 HTML 或 XHTML 规范**。\n\n#### HTML4.01有哪些规范呢？\n\n**HTML4.01**这个版本是IE6开始兼容的。**HTML5是IE9开始兼容的**。如今，手机、移动端的网页，就可以使用HTML5了，因为其兼容性更高。\n\n说个题外话，html1 至 html3 是美国军方以及高等研究所用的，并未对外公开。\n\nHTML4.01里面有两大种规范，每大种规范里面又各有3种小规范。所以一共6种规范（见下图）。\n\nHTML4.01里面规定了**普通**和**XHTML**两大种规范。HTML觉得自己有一些规定不严谨，比如，标签是否可以用大写字母呢？`<H1></H1>`所以，HTML就觉得，把一些规范严格的标准，又制定了一个XHTML1.0。在XHTML中的字母X，表示“严格的”。\n\n总结一下，HTML4.01一共有6种DTD。说白了，HTML的第一行语句一共有6种情况：\n\n![](http://img.smyhvae.com/20170629_1600.png)\n\n下面对上图中的三种小规范进行解释：\n\n**strict**：\n\n表示“严格的”，这种模式里面的要求更为严格。这种严格体现在哪里？有一些标签不能使用。\n比如，u标签，就是给一个本文加下划线，但是这和HTML的本质有冲突，因为HTML最好是只负责语义，不要负责样式，而u这个下划线是样式。所以，在strict中是不能使用u标签的。\n\n那怎么给文本增加下划线呢？今后将使用css属性来解决。\n\nXHTML1.0更为严格，因为这个体系本身规定比如标签必须是小写字母、必须严格闭合标签、必须使用引号引起属性等等。\n\n**Transitional**：表示“普通的”，这种模式就是没有一些别的规范。\n\n**Frameset**：表示“框架”，在框架的页面使用。\n\n在sublime输入的html:xt，x表示XHTML，t表示transitional。\n\n在HTML5中极大的简化了DTD，也就是说HTML5中就没有XHTML了。HTML5的DTD（文档声明头）如下：\n\n```\n<!DOCTYPE html>\n```\n\n### 2、页面语言 `lang`\n\n下面这行标签，用于指定页面的语言类型：\n\n```html\n<html lang=\"en\">\n```\n\n最常见的语言类型有两种：\n\n- en：定义页面语言为英语。\n\n- zh-CN：定义页面语言为中文。\n\n### 3、头标签 `head`\n\n#### html5 的比较完整的骨架：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">\n\t<meta name=\"Author\" content=\"\">\n    <meta name=\"Keywords\" content=\"厉害很厉害\" />\n    <meta name=\"Description\" content=\"网易是中国领先的互联网技术公司，为用户提供免费邮箱、游戏、搜索引擎服务，开设新闻、娱乐、体育等30多个内容频道，及博客、视频、论坛等互动交流，网聚人的力量。\" />\n    <title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n```\n\n面试题：\n\n- 问：网页的head标签里面，表示的是页面的配置，有什么配置？\n- 答：字符集、关键词、页面描述、页面标题、IE适配、视口、iPhone小图标等等。\n\n头标签内部的常见标签如下：\n\n - `<title>`：指定整个网页的标题，在浏览器最上方显示。\n - `<base>`：为页面上的所有链接规定默认地址或默认目标。\n - `<meta>`：提供有关页面的基本信息\n - `<body>`：用于定义HTML文档所要显示的内容，也称为主体标签。我们所写的代码必须放在此标签內。\n - `<link>`：定义文档与外部资源的关系。\n\n**meta 标签**：\n\nmeta表示“元”。“元”配置，就是表示基本的配置项目。\n\n常见的几种 meta 标签如下：\n\n（1）字符集 charset：\n\n```html\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">\n```\n\n字符集用meta标签中的`charset`定义，charset就是character set（即“字符集”），即**网页的编码方式**。\n\n**字符集**(Character set)是多个字符的集合。计算机要准确的处理各种字符集文字，需要进行字符编码，以便计算机能够识别和存储各种文字。\n\n上面这行代码非常关键， 是必须要写的代码，否则可能导致乱码。比如你保存的时候，meta写的和声明的不匹配，那么浏览器就是乱码。\n\nutf-8是目前最常用的字符集编码方式，常用的字符集编码方式还有gbk和gb2312等。关于“编码方式”，我们在下一段会详细介绍。\n\n（2）视口 viewport：\n\n```html\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n```\n\n`width=device-width` ：表示视口宽度等于屏幕宽度。\n\nviewport 这个知识点，初学者还比较难理解，以后学 Web 移动端的时候会用到。\n\n（3）定义“关键词”：\n\n举例如下：\n\n```html\n<meta name=\"Keywords\" content=\"网易,邮箱,游戏,新闻,体育,娱乐,女性,亚运,论坛,短信\" />\n```\n\n这些关键词，就是告诉搜索引擎，这个网页是干嘛的，能够提高搜索命中率。让别人能够找到你，搜索到你。\n\n（4）定义“页面描述”：\n\nmeta除了可以设置字符集，还可以设置关键字和页面描述。\n\n只要设置Description页面描述，那么百度搜索结果，就能够显示这些语句，这个技术叫做**SEO**（search engine optimization，搜索引擎优化）。\n\n设置页面描述的举例：\n\n```html\n<meta name=\"Description\" content=\"网易是中国领先的互联网技术公司，为用户提供免费邮箱、游戏、搜索引擎服务，开设新闻、娱乐、体育等30多个内容频道，及博客、视频、论坛等互动交流，网聚人的力量。\" />\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20170629_1743.png)\n\n上面的几种`<meta>`标签都不用记，但是另外还有一个`<meta>`标签是需要记住的：\n\n```html\n<meta http-equiv=\"refresh\" content=\"3;http://www.baidu.com\">\n```\n上面这个标签的意思是说，3秒之后，自动跳转到百度页面。\n\n\n**title 标签**:\n\n用于设置网页标题：\n\n```html\n\t<title>网页的标题</title>\n```\ntitle标签也是有助于SEO搜索引擎优化的。\n\n**base标签**：\n\n```html\n<base href=\"/\">\n```\n\nbase 标签用于指定基础的路径。指定之后，所有的 a 链接都是以这个路径为基准。\n\n### 4、`<body>`标签\n\n`<body>`标签的属性有：\n\n - `bgcolor`：设置整个网页的背景颜色。\n - `background`：设置整个网页的背景图片。\n - `text`：设置网页中的文本颜色。\n - `leftmargin`：网页的左边距。IE浏览器默认是8个像素。\n - `topmargin`：网页的上边距。\n - `rightmargin`：网页的右边距。\n - `bottommargin`：网页的下边距。\n\n`<body>`标签另外还有一些属性，这里用个例子来解释：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_39.png)\n\n上方代码中，当我们对`点我点我`这几个字使用超链时，`link`属性表示默认显示的颜色、`alink`属性表示鼠标点击但是还没有松开时的颜色、`vlink`属性表示点击完成之后显示的颜色。效果如下：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_05.gif)\n\n\n## 计算机编码介绍\n\n计算机，不能直接存储文字，存储的是编码。\n\n计算机只能处理二进制的数据，其它数据，比如：0-9、a-z、A-Z，这些字符，我们可以定义一套规则来表示。假如：A用110表示，B用111表示等。\n\n**ASCII码：**\n美国发布的，用1个字节(8位二进制)来表示一个字符，共可以表示2^8=256个字符。\n\t美国的国家语言是英语，只要能表示0-9、a-z、A-Z、特殊符号。\n\n**ANSI编码：**\n**每个国家为了显示本国的语言，都对ASCII码进行了扩展**。用2个字节(16位二进制)来表示一个汉字，共可以表示2^16＝65536个汉字。例如：\n中国的ANSI编码是GB2312编码(简体)，对6763汉字进行编码，含600多特殊字符。另外还有GBK(简体)。\n日本的ANSI编码是JIS编码。\n台湾的ANSI编码是BIG5编码（繁体）。\n\n**GBK：**\n对GB2312进行了扩展，用来显示罕见的、古汉语的汉字。现在已经收录了2.1万左右。并提供了1890个汉字码位。K的含义就是“扩展”。\n\n**Unicode编码(统一编码)：**\n用4个字节(32位二进制)来表示一个字符，想法不错，但效率太低。例如，字母A用ASCII表示的话一个字节就够，可用Unicode编码的话，得用4个字节表示，造成了空间的极大浪费。A的Unicode编码是0000 0000 0000 0000 0000 0000 0100 0000\n\n**UTF-8(Unicode Transform Format)编码：**\n根据字符的不同，选择其编码的长度。比如：一个字符A用1个字节表示，一个汉字用2个字节表示。\n\n毫无疑问，开发中，都用**UTF-8**编码吧，准没错。\n\n**中文能够使用的字符集两种：**\n\n- 第一种：UTF-8。UTF-8是国际通用字库，里面涵盖了所有地球上所有人类的语言文字，比如阿拉伯文、汉语、鸟语……\n\n- 第二种：GBK（对GB2312进行了扩展）。gb2312 是国标，是中国的字库，里面**仅**涵盖了汉字和一些常用外文，比如日文片假名，和常见的符号。\n\n字库规模：  UTF-8（字很全） > gb2312（只有汉字）\n\n**重点1：避免乱码**\n\n我们用meta标签声明的当前这个html文档的字库，一定要和保存的文件编码类型一样，否则乱码（重点）。\n\n拿 sublime编辑器举例，当我们不设置的时候，sublime默认类型就是UTF-8。而一旦更改为gb2312的时候，就一定要记得设置一下sublime的保存类型： `文件→ set File Encoding to → Chinese Simplified(GBK)`。VS Code 的道理一样。\n\n\n**重点2：UTF-8和gb2312的比较**\n\n保存大小：UTF-8（更臃肿、加载更慢） > gb2312 （更小巧，加载更快）\n\n总结：\n- UTF-8：字多，有各种国家的语言，但是保存尺寸大，文件臃肿；\n- gb2312：字少，只用中文和少数外语和符号，但是尺寸小，文件小巧。\n\n\n列出2个使用情形：\n\n1） 你们公司是做日本动漫的，经常出现一些日语动漫的名字，网页要使用UTF-8。如果用gb2312将无法显示日语。\n2） 你们公司就是中文网页，极度的追求网页的显示速度，要使用gb2312。如果使用UTF-8将每个汉字多一个byte，所以5000个汉字，多5kb。\n\n我们亲测：\n- qq网、网易、搜狐都是使用gb2312。这些公司，都追求显示速度。\n- 新华网藏语频道，使用的是UTF-8，保证字符集的数量。\n\n我们是怎么查看网页的编码方式的呢？在浏览器中打开网页，右键，选择“查看网页源代码”，找到meta标签中的charset属性即可。\n\n那么，我们为什么可以查看网页的源代码呢？因为这个打开的html网页已经存到我的临时文件夹里了，临时文件夹里的html是纯文本文件，纯文本文件自然可以查看网页的源代码。\n\n## HTML的规范\n\n- HTML不区分大小写，但HTML的标签名、类名、标签属性、大部分属性值建议统一用小写。\n- HTML页面的后缀名是html或者htm(有一些系统不支持后缀名长度超过3个字符，比如dos系统)\n\n### 1、编写XHTML的规范：\n\n（1）所有标记元素都要正确的嵌套，不能交叉嵌套。正确写法举例：`<h1><font></font></h1>`\n\n（2）所有的标记都必须小写。\n\n（3）所有的标签都必须闭合。\n\n- 双标签：`<span></span>`\n\n- 单标签：`<br>` 建议写成 `<br />`   `<hr>` 建议转成 `<hr />`，还有`<img src=“URL” />`\n\n（4）所有的属性值必须加引号。`<font  color=\"red\"></font>`\n\n（5）所有的属性必须有值。`<hr noshade=\"noshade\">`、`<input  type=\"radio\" checked=\"checked\" />`\n\n（6）XHTML文档开头必须要有DTD文档类型定义。\n\n### 2、HTML的基本语法特性\n\n#### （1）HTML对换行不敏感，对tab不敏感\n\nHTML只在乎标签的嵌套结构，嵌套的关系。谁嵌套了谁，谁被谁嵌套了，和换行、tab无关。换不换行、tab不tab，都不影响页面的结构。\n\n也就是说，HTML不是依靠缩进来表示嵌套的，而是看标签的嵌套关系。但是，我们发现有良好的缩进，代码更易读。建议大家都正确缩进标签。\n\n百度为了追求极致的显示速度，所有HTML标签都没有换行、都没有缩进（tab），HTML和换不换行无关，标签的层次依然清晰，只不过程序员不可读了。如下图所示：\n\n![](http://img.smyhvae.com/20170629_2226.png)\n\n#### （2）空白折叠现象\n\nHTML中所有的**文字之间**，如果有空格、换行、tab都将被折叠为一个空格显示。\n\n举例如下：\n\n![](http://img.smyhvae.com/20170629_2230.jpg)\n\n#### （3）标签要严格封闭\n\n标签不封闭的结果是灾难性的。\n\n标签不封闭的举例如下：\n\n![](http://img.smyhvae.com/20170629_2245.jpg)\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "01-HTML/04-HTML标签：排版标签.md",
    "content": "---\ntitle: 04-HTML标签：排版标签\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 本文主要内容\n\n排版标签：\n\n- `<h1>`\n\n- `<p>`\n\n-  `<hr />`\n\n- `<br />`\n\n- `<div>`\n\n- `<span>`\n\n- `<center>`\n\n- `<pre>`\n\n\n下面来详细介绍一下排版标签。\n\n## 标题标签\n\n标题使用`<h1>`至`<h6>`标签进行定义。`<h1>`定义最大的标题，`<h6>`定义最小的标题。具有align属性，属性值可以是：left、center、right。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Document</title>\n</head>\n<body>\n\t<h1>H1：千古壹号，永不止步</h1>\n\t<h2>H3：千古壹号，永不止步</h2>\n\t<h3>H3：千古壹号，永不止步</h3>\n\t<h4>H4：千古壹号，永不止步</h4>\n\t<h5>H5：千古壹号，永不止步</h5>\n\t<h6>H6：千古壹号，永不止步</h6>\n</body>\n</html>\n\n```\n\n效果演示：\n\n![](http://img.smyhvae.com/20200402_1050.png)\n\n## HTML 注释\n\nHTML 注释的格式如下：\n\n```html\n<!-- 我是 html 注释  -->\n```\n\n## 段落标签`<p>`\n\n段落，是英语“paragraph“缩写。\n\n**作用**：可以把 HTML 文档分割为若干段落。在网页中如果要把文字有条理地显示出来，离不开段落标签。就如同我们平常写文章一样，整个网页也可以分为若干个段落。\n\n代码举例：\n\n```html\n<p>This is a paragraph</p>\n<p>This is another paragraph</p>\n```\n\n属性：\n\n- `align=\"属性值\"`：对齐方式。属性值包括left center right。\n\n属性举例：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html166440-1dcd2ad6e6353559.png)\n\n\nHTML标签是分等级的，HTML将所有的标签分为两种：\n\n- **文本级标签**：p、span、a、b、i、u、em。文本级标签里只能放**文字、图片、表单元素**。（a标签里不能放a和input）\n\n- **容器级标签**：div、h系列、li、dt、dd。容器级标签里可以放置任何东西。\n\n从学习p的第一天开始，就要牢牢记住：**p标签是一个文本级标签，p里面只能放文字、图片、表单元素**。其他的一律不能放。\n\n错误写法：（尝试把 h 放到 p 里）\n\n```html\n\t<p>\n\t\t我是一个小段落\n\t\t<h1>我是一级标题</h1>\n\t</p>\n```\n\n网页效果如下：\n\n![](http://img.smyhvae.com/20170630_1102.png)\n\n上图显示，浏览器不允许你这么做，我们使用Chrome的F12审查元素发现，浏览器自己把p封闭掉了，不让你去包裹h1。\n\nPS：Chrome浏览器是HTML5支持度最好的浏览器。提供了非常好的开发工具，非常适合我们开发人员使用。审查元素功能的快捷键是F12。\n\n\n## 水平线标签`<hr />`\n\n> horizontal 单词的发音：[ˌhɒrɪˈzɒntl]。\n\n水平分隔线（horizontal rule）可以在视觉上将文档分隔成各个部分。在网页中常常看到一些水平线将段落与段落之间隔开，使得文档结构清晰，层次分明。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Document</title>\n</head>\n<body>\n\t<p>自古情深留不住</p>\n\t<hr />\n\t<p>总是套路得人心</p>\n</body>\n</html>\n```\n\n运行效果：\n\n![](http://img.smyhvae.com/20200401_1930.png)\n\n\n属性介绍：\n - `align=\"属性值\"`：设定线条置放位置。属性值可选择：left right center。\n - `size=\"2\" `：设定线条粗细。以像素为单位，内定为2。\n - `width=\"500\"`或`width=\"70%\"`：设定线条长度。可以是绝对值（单位是像素）或相对值。如果设置为相对值的话，内定为100%。\n - `color=\"#0000FF\"`：设置线条颜色。\n - `noshade`：不要阴影，即设定线条为平面显示。若没有这个属性则表明线条具阴影或立体。\n\n属性效果演示：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_05.png)\n\n## 换行标签`<br />`\n\n如果希望某段文本强制换行显示，就需要使用换行标签。\n\n```html\nThis <br/> is a para<br/>graph with line breaks\n```\n效果如下：\n\n![](http://img.smyhvae.com/2015-10-01-cnblogs_html03.png)\n\n## `<div>`和`<span>`标签\n\ndiv和span是非常重要的标签，div的语义是division“分割”； span的语义就是span“范围、跨度”。想必你应该听说过“div + css”布局。\n\n### div和span的介绍\n\n- **div标签**：可以把标签中的内容分割为独立的区块。必须单独占据一行。\n\n- **span标签**：和div的作用一致，但不换行。\n\n代码举例：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_08.png)\n\ndiv标签的属性：\n\n- `align=\"属性值\"`：设置块儿的位置。属性值可选择：left、right、 center。\n\n### div和span的区别\n\n`<span>`和`<div>`唯一的区别在于：`<span>`是不换行的，而`<div>`是换行的。\n\n如果单独在网页中插入这两个元素，不会对页面产生任何的影响。这两个元素是专门为定义CSS样式而生的。或者说，DIV+CSS来实现各种样式。\n\ndiv在浏览器中，默认是不会增加任何的效果的，但是语义变了，div中的所有元素是一个小区域。\ndiv标签是一个**容器级**标签，里面什么都能放，甚至可以放div自己。\n\nspan也是表达“小区域、小跨度”的标签，但只是一个**文本级**的标签。\n就是说，span里面只能放置文字、图片、表单元素。 span里面不能放p、h、ul、dl、ol、div。\n\nspan举例：\n\n```html\n<p>\n\t简介简介简介简介简介简介简介简介\n\t<span>\n\t\t<a href=\"\">详细信息</a>\n\t\t<a href=\"\">购买</a>\n\t</span>\n</p>\n\n```\n\ndiv举例：\n\n```html\n<div class=\"header\">\n\t<div class=\"logo\"></div>\n\t<div class=\"nav\"></div>\n</div>\n<div class=\"content\">\n\t<div class=\"guanggao\"></div>\n\t<div class=\"dongxi\"></div>\n</div>\n<div class=\"footer\"></div>\n```\n\n我们亲切地称这种模式叫做“**div+css**”：**div标签负责布局、结构、分块，css负责样式**。\n\n## 内容居中标签 `<center>`\n\n此时center代表是一个标签，而不是一个属性值了。只要是在这个标签里面的内容，都会居于浏览器的中间。\n效果演示：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_06.png)\n\n到了HTML5里面，center标签不建议使用，建议使用css布局来实现。\n\n## 预定义（预格式化）标签`<pre>`\n\n含义：将保留标签内部所有的空白字符(空格、换行符)，原封不动地输出结果（告诉浏览器不要忽略空格和空行）。\n\n说明：真正排网页过程中，`<pre>`标签几乎用不着。\n效果演示：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_07.png)\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "01-HTML/05-HTML标签：字体标签和超链接.md",
    "content": "---\ntitle: 05-HTML标签：字体标签和超链接\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 本文主要内容\n\n字体标签： `<font>`、 `<b>`、 `<u>` 、`<sup>` 、`<sub>`\n\n超链接 `<a>`\n\n## 字体标签\n\n### 特殊字符（转义字符）\n\n- `&nbsp;`：空格\t（non-breaking spacing，不断打空格）\n- `&lt;`：小于号`<`（less than）\n-  `&gt;`：大于号`>`（greater than）\n- `&amp;`：符号`&`\n- `&quot;`：双引号\n- `&apos;`：单引号\n- `&copy;`：版权`©`\n- `&trade;`：商标`™`\n-  `&#32464;`：文字`绐`。其实，`#32464`是汉字`绐`的unicode编码。\n\n\n比如说，你想把`<p>`作为一个文本在页面上显示，直接写`<p>`是肯定不行的，因为这代表的是一个段落标签，所以这里需要用到**转义字符**。应该这么写：\n```html\n这是一个HTML语言的&lt;p&gt;标签\n```\n正确的效果如下：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_11.png)\n\n错误的效果如下：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_12.png)\n\n其实我们只要记住前三个符号就行了，其他的在需要的时候查一下就行了。而且，EditPlus软件中是可以直接点击这些符号进行选择的：\n\n![Paste_Image.png](\nhttp://img.smyhvae.com/2015-10-01-cnblogs_html_13.png)\n\n来一张表格，方便需要的时候查询：\n\n| 特殊字符 | 描述 |字符的代码 |\n|:-------------|:-------------|:-----|\n||空格符|`&nbsp;`|\n|<|小于号|`&lt;`|\n|> |大于号|`&gt;`|\n|&|和号|`&amp;`|\n|￥|人民币|`&yen;`|\n|©|版权|`&copy;`|\n|®|注册商标|`&reg;`|\n|°|摄氏度|`&deg;`|\n|±|正负号|`&plusmn;`|\n|×|乘号|`&times;`|\n|÷|除号|`&divide;`|\n|²|平方2（上标2）|`&sup2;`|\n|³|立方3（上标3）|`&sup3;`|\n\n### 下划线、中划线、斜体\n\n- `<u>`：下划线标记\n\n- `<s>`或`<del>`：中划线标记（删除线）\n\n- `<i>`或`<em>`：斜体标记\n\n效果：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_15.png)\n\n上面的这几个标签，常用于做一些小装饰、小图标。比如：\n\n![](http://img.smyhvae.com/20180118_2340.png)\n\n这张图中，我们通过查看京东网站的代码发现，箭头处的小图标都是用的标签`<i>`。\n\n\n\n### 粗体标签`<b>`或`<strong>`（已废弃）\n\n效果：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_14.png)\n\n\n\n\n\n### 字体标签`<font>`（已废弃）\n\n属性：\n\n- `color=\"红色\"`或`color=\"#ff00cc\"`或`color=\"new rgb(0,0,255)\"`：设置字体颜色。\n\t设置方式：单词 \\  #ff00cc \\   rgb(0,0,255)\n\n- `size`：设置字体大小。 取值范围只能是：1至7。取值时，如果取值大于7那就按照7来算，如果取值小于1那就按照1来算。如果想要更大的字体，那就只能通过css样式来解决。\n\n- `face=\"微软雅黑\"`：设置字体类型。\n\n举例：\n\n```html\n<font face=\"微软雅黑\" color=\"#FF0000\" size=\"10\">vae</font>\n```\n\n效果：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_10.png)\n\n\n\n\n\n\n### 上标`<sup>`   下标`<sub>`\n\n上小标这两个标签容易混淆，怎么记呢？这样记：`b`的意思是`bottom：底部`\n举例：\n```html\nO<sup>2</sup>    5<sub>3</sub>\n```\n效果：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_16.png)\n\n## 三、超链接\n\n超链接有三种形式，下面分别讲讲。\n\n### 1、外部链接：链接到外部文件\n\n举例：\n```html\n<a href=\"02页面.html\">点击进入另外一个文件</a>\n```\n\na是英语`anchor`“锚”的意思，就好像这个页面往另一个页面扔出了一个锚。是一个文本级的标签。\n\nhref（hypertext reference）：超文本地址。读作“喝瑞夫”，不要读作“喝夫”。\n\n效果：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_17.png)\n\n当然，我们也可以直接点进链接，访问一个网址。代码举例如下：\n\n```html\n<a href=\"http://www.baidu.com\" target=\"_blank\">点我点我</a>\n```\n\n### 2、锚链接\n\n**锚链接**：给超链接起一个名字，作用是**在本页面或者其他页面的的不同位置进行跳转**。比如说，在网页底部有一个向上箭头，点击箭头后回到顶部，这个就可以利用锚链接。\n\n首先我们要创建一个**锚点**，也就是说，使用`name`属性或者`id`属性给那个特定的位置起个名字。效果如下：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_18.png)\n\n上图中解释：\n\n第11行代码表示，顶部这个锚的名字叫做name1。\n然后在底部设置超链接，点击时将回到顶部（此时，网页中的url的末尾也出现了`#name1`）。注意**上图中红框部分的`#`号不要忘记了**，表示跳到名为name1的特定位置，这是规定。如果少了`#`号，点击之后，就会跳到name1这个文件或者name1这个文件夹中去。\n\n如果我们将上图中的第28行代码写成：\n\n```html\n<a href=\"a.html#name1\">回到顶部</a>\n```\n\n那就表示，点击之后，跳转到`a.html`页面的`name1锚点`中去。\n\n说明：name属性是HTML4.0以前使用的，id属性是HTML4.0后才开始使用。为了向前兼容，因此，name和id这两个属性都要写上，并且值是一样的。\n\n### 3、邮件链接\n\n代码举例：\n\n```html\n<a href=\"mailto:xxx@163.com\">点击进入我的邮箱</a>\n```\n\n效果：点击之后，会弹出outlook，作用不大。\n\n### 超链接的属性\n\n- `href`：目标URL\n- `title`：悬停文本。\n- `name`：主要用于设置一个锚点的名称。\n- `target`：告诉浏览器用什么方式来打开目标页面。`target`属性有以下几个值：\n\t- `_self`：在同一个网页中显示（默认值）\n\t- `_blank`：**在新的窗口中打开**。\n\t- `_parent`：在父窗口中显示\n\t- `_top`：在顶级窗口中显示\n\n\n`title`属性举例：\n\n```html\n<a href=\"09_img.html\" title=\"很好看哦\">结婚照</a>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20170630_1415.png)\n\n`target`属性举例：\n\n```html\n<a href=\"1.html\" title=\"悬停文本\" target=\"_blank\">链接的内容</a>\n```\n\nblank就是“空白”的意思，就表示新建一个空白窗口。为啥有一个_ ，就是规定，无需解释。\n也就是说，如果不写`target=”_blank”`那么就是在相同的标签页打开，如果写了`target=”_blank”`，就是在新的空白标签页中打开。\n\n#### 备注1：分清楚img和a标签的各自的属性\n\n区别如下：\n\n```html\n<img src=\"1.jpg\" />\n<a href=\"1.html\"></a>\n```\n#### 备注2：a是一个文本级的标签\n\n比如一个段落中的所有文字都能够被点击，那么应该是p包裹a：\n\n```html\n<p>\n\t<a href=\"\">段落段落段落段落段落段落</a>\n</p>\n```\n\n而不是a包裹p：\n\n```html\n<a href=\"\">\n\t<p>\n\t\t段落段落段落段落段落段落\n\t</p>\n</a>\n```\n\na的语义要小于p，a就是可以当做文本来处理，所以p里面相当于放的就是纯文字。\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "01-HTML/06-HTML标签：图片标签.md",
    "content": "---\ntitle: 06-HTML标签：图片标签\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## img标签介绍\n\n### 介绍\n\nimg: 英文全称 image（图像），代表的是一张图片。\n\n如果要想在网页中显示图像，就可以使用img 标签，它是一个单标签。语法如下：\n\n```html\n<img src=\"图片的URL\" />\n```\n\n### 能插入的图片类型\n\n- 能够插入的图片类型是：jpg(jpeg)、gif、png、bmp等。\n\n- 不能往网页中插入的图片格式是：psd、ai等。\n\nHTML页面不是直接插入图片，而是插入图片的引用地址，所以要先把图片上传到服务器上。\n\n## img标签的`src`属性\n这里涉及到图片的一个属性：\n\n- `src`属性：指图片的路径。英文名称 source。\n\n在写**图片的路径**时，有两种写法：相对路径、绝对路径\n\n### 写法一：图片的相对路径\n\n相对当前页面所在的路径。两个标记 `.` 和 `..` 分表代表当前目录和上一层目录。\n\n举例1：\n\n```html\n<!-- 当前目录中的图片 -->\n<img src=\"2.jpg\">\n<img src=\"./2.jpg\">\n\n<!-- 上一级目录中的图片 -->\n<img src=\"../2.jpg\">\n```\n\n相对路径不会出现这种情况：\n\n```html\naaa/../bbb/1.jpg\n```\n\n`../`要么不写，要么就写在开头。\n\n举例2：\n\n```html\n<img src=\"images/1.jpg\">\n```\n上方代码的意思是说，当前html页面有一个并列的文件夹`images`，在文件夹`images`中存放了一张图片`1.jpg`\n效果：\n\n![Paste_Image.png](http://img.smyhvae.com/20151001_19.jpg)\n\n相对路径的面试题。现有如下文件层级图：\n\n![](http://img.smyhvae.com/20170630_1133.png)\n\n问题：如果想在index.html中插入1.png，那么对应的img语句是？\n\n分析：\n\n现在document是最大的文件夹，里面有两个文件夹work和photo。work中又有一个文件夹叫做myweb。myweb文件夹里面有index.html。  所以index.html在myweb文件夹里面，上一级就是work文件夹，上两级就是document文件夹。通过document文件夹当做一个中转站，进入photo文件夹，看到了1.png。\n\n答案：\n\n```html\n<img src=\"../../photo/1.png\" />\n```\n\n### 写法二：图片的绝对路径\n\n绝对路径包括以下两种：\n\n（1）以盘符开始的绝对路径。举例：\n\n```html\n<img src=\"C:\\Users\\qianguyihao\\Desktop\\html\\images\\1.jpg\">\n```\n\n（2）网络路径。举例：\n\n```html\n<img src=\"http://img.smyhvae.com/20200122_200901.png\">\n\n```\n\n大家打开上面的img中的链接，可能有彩蛋哦。\n\n### 相对路径和绝对路径的总结\n\n相对路径的好处：站点不管拷贝到哪里，文件和图片的相对路径关系都是不变的。相对路径使用有一个前提，就是网页文件和你的图片，必须在一个服务器上。\n\n**总结一下**：\n\n无论是在 a 标签还是 img 标签上，如果要用路径。只有两种路径能用，就是相对路径和绝对路径：\n\n- 相对路径从自己出发，找到别人。\n\n- 绝对路径，就是`http://`或者`https://`开头的路径。\n\n## img标签的其他属性\n\n### width、height 属性\n\n- `width`：图像的宽度。\n\n- `height`：图像的高度。\n\nwidth和height，在 HTML5 中的单位是 CSS 像素，在 HTML 4 中既可以是像素，也可以是百分比。可以只指定 width 和 height 中的一个值，浏览器会根据原始图像进行缩放。\n\n**重要提示**：如果要想保证图片等比例缩放，请只设置width和height中其中一个。\n\n### Alt 属性\n\n- `alt`：当图片不可用（无法显示）的时候，代替图片显示的内容。alt是英语 alternate “替代”的意思，代表替换资源。\n\n`Alt`属性效果演示：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_21.png)\n\n如上图所示：当图片 src 不可用的时候，显示文字。这样做，至少能让用户知道，这个图片大概是什么内容。\n\n### title 属性\n\n- `title`：**提示性文本**。鼠标悬停时出现的文本。\n\ntitle 属性不该被用作一幅图片在 alt 之外的补充说明信息。如果一幅图片需要小标题，使用 figure 或 figcaption 元素。\n\ntitle 元素的值一般作为提示条(tooltip)呈现给用户，在光标于图片上停下后显示出来。尽管这确实能给用户提供更多的信息，您不该假定用户真的能看到：用户可能只有键盘或触摸屏。如果要把特别重要的信息提供给用户，可以选择上面提供的一种方法将其内联显示，而不是使用 title。\n\n举例：\n\n```html\n<img src=\"images/1.jpg\" width=\"300\" height=\"`188\" title=\"这是美女\">\n```\n\n效果：\n\n![Paste_Image.png](http://img.smyhvae.com/2015-10-01-cnblogs_html_20.png)\n\n### align 属性\n\n- 图片的`align`属性：**图片和周围文字的相对位置**。属性取值可以是：bottom（默认）、center、top、left、right。\n\n如果想实现图文混排的效果，请使用align属性，取值为left或right。\n\n我们来分别看一下这`align`属性的这几个属性值的区别。\n\n1、`align=\"\"`，图片和文字底端对齐。即默认情况下的显示效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_19.png)\n\n2、`align=\"center\"`：图片和文字水平方向上居中对齐。显示效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_21.png)\n\n3、`align=\"top\"`：图片与文字顶端对齐。显示效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_22.png)\n\n4、`align=\"left\"`：图片在文字的左边。显示效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_23.png)\n\n5、`align=\"right\"`：图片在文字的右边。显示效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_24.png)\n\n\n### 其他已废弃的属性\n\n- `Align`（已废弃）：指图片的水平对齐方式，属性值可以是：top、middle、bottom、left、center、right。该属性已废弃，替换为 `vertical-align`这个CSS属性。\n- `border`（已废弃）：给图片加边框，单位是像素，边框的颜色默认黑色。该属性已废弃，替换为 `border`这个CSS属性。\n- `Hspace`（已废弃）：指图片左右的边距。\n- `Vspace`（已废弃）：指图片上下的边距。\n\n最后，送上妹子的近照一张。楼主已经仁至义尽了：http://img.smyhvae.com/2015-10-01-cnblogs_html_20150219214912_11994.jpg\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)"
  },
  {
    "path": "01-HTML/07-html标签图文详解（二）.md",
    "content": "---\ntitle: 07-HTML标签图文详解（二）\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 本文主要内容\n\n - 列表标签：`<ul>`、`<ol>`、`<dl>`\n - 表格标签：`<table>`\n - 框架标签及内嵌框架`<iframe>`\n - 表单标签：`<form>`\n - 多媒体标签\n - 滚动字幕标签：`<marquee>`\n\n\n## 列表标签\n\n列表标签分为三种。\n\n### 1、无序列表`<ul>`，无序列表中的每一项是`<li>`\n\n英文单词解释如下：\n\n- ul：unordered list，“无序列表”的意思。\n- li：list item，“列表项”的意思。\n\n例如：\n\n```html\n<ul>\n\t<li>默认1</li>\n\t<li>默认2</li>\n\t<li>默认3</li>\n</ul>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_01.png)\n\n注意：\n\n- li不能单独存在，必须包裹在ul里面；反过来说，ul的“儿子”不能是别的东西，只能有li。\n- 我们这里再次强调，ul的作用，并不是给文字增加小圆点的，而是增加无序列表的“语义”的。\n\n\n**属性：**\n\n - `type=\"属性值\"`。属性值可以选： `disc`(实心原点，默认)，`square`(实心方点)，`circle`(空心圆)。\n效果如下：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_02_1.png)\n\n不光是`<ul>`标签有`type`属性，`<ul>`里面的`<li>`标签也有`type`属性（虽然说这种写法很少见）。效果如下：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_03.png)\n\n注意：项目符号可以是图片，需要通过CSS设置`<li>`标记的背景图片来实现(CSS中讲)。\n\n当然了，列表之间是可以**嵌套**的。我们来举个例子。代码：\n\n```html\n  <ul>\n\t<li><b>北京市</b>\n\t\t<ul>\n\t\t\t<li>海淀区</li>\n\t\t\t<li>朝阳区</li>\n\t\t\t<li>东城区</li>\n\n\t\t</ul>\n\t</li>\n\n\t<li><b>广州市</b>\n\t\t<ul>\n\t\t\t<li>天河区</li>\n\t\t\t<li>越秀区</li>\n\t\t</ul>\n\t</li>\n  </ul>\n```\n效果：\n\n![](http://img.smyhvae.com/2015-10-01-cnblogs_html_40.png)\n\n\n**css 属性**：\n\n```css\nlist-style-position: inside   /* 给 ul 设置这个属性后，将小圆点包含在 li 元素的内部 */\n```\n\n#### ul标签实际应用场景：\n\n场景1、导航条：\n\n![20211031_1617](https://img.smyhvae.com/20211031_1617.png)\n\n场景2、li 里面放置的内容可能很多：\n\n![](http://img.smyhvae.com/20170704_1719.png)\n\n声明：ul的儿子，只能是li。但是li是一个容器级标签，**li里面什么都能放，甚至可以再放一个ul**。\n\n### 2、有序列表`<ol>`，里面的每一项是`<li>`\n\n英文单词：Ordered List。\n\n例如：\n\n```html\n<ol >\n\t<li>呵呵哒1</li>\n\t<li>呵呵哒2</li>\n\t<li>呵呵哒3</li>\n</ol>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_04.png)\n\n**属性：**\n - `type=\"属性值\"`。属性值可以是：1(阿拉伯数字，默认)、a、A、i、I。结合`start`属性表示`从几开始`。\n\n举例：\n\n```html\n<ol type=\"1\">\n\t<li>呵呵</li>\n\t<li>呵呵</li>\n\t<li>呵呵</li>\n</ol>\n\n<ol type=\"a\">\n\t<li>嘿嘿</li>\n\t<li>嘿嘿</li>\n\t<li>呵呵</li>\n</ol>\n\n<ol type=\"i\" start=\"4\">\n\t<li>哈哈</li>\n\t<li>哈哈</li>\n\t<li>哈哈</li>\n</ol>\n\n<ol type=\"I\" start=\"10\">\n\t<li>么么</li>\n\t<li>么么</li>\n\t<li>么么</li>\n</ol>\n```\n\n效果如下：\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_07.png)\n\n和无序列表一样，有序列表也是可以嵌套的哦，这里就不举类似的例子了。\n\n\nol和ul就是语义不一样，怎么使用都是一样的。\nol里面只能有li，li必须被ol包裹。li是容器级。\n\nol这个东西用的不多，如果想表达顺序，大家一般也用ul。举例如下：\n\n```html\n<ul>\n\t<li>1. 小苹果</li>\n\t<li>2. 月亮之上</li>\n\t<li>3. 最炫民族风</li>\n</ul>\n```\n\n### 3、定义列表`<dl>`\n\n> 定义列表的作用非常大。\n\n`<dl>`英文单词：definition list，没有属性。dl的子元素只能是dt和dd。\n\n - `<dt>`：definition title 列表的标题，这个标签是必须的\n - `<dd>`：definition description 列表的列表项，如果不需要它，可以不加\n\n备注：dt、dd只能在dl里面；dl里面只能有dt、dd。\n\n举例：\n\n```html\n<dl>\n\t<dt>第一条</dt>\n\t<dd>你若是觉得你有实力和我玩，良辰不介意奉陪到底</dd>\n\t<dd>我会让你明白，我从不说空话</dd>\n\t<dd>我是本地的，我有一百种方式让你呆不下去；而你，无可奈何</dd>\n\n\t<dt>第二条</dt>\n\t<dd>良辰最喜欢对那些自认能力出众的人出手</dd>\n\t<dd>你可以继续我行我素，不过，你的日子不会很舒心</dd>\n\t<dd>你只要记住，我叫叶良辰</dd>\n\t<dd>不介意陪你玩玩</dd>\n\t<dd>良辰必有重谢</dd>\n\n</dl>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_09.png)\n\n\n上图可以看出，定义列表表达的语义是两层：\n\n- （1）是一个列表，列出了几个dd项目\n- （2）每一个词儿都有自己的描述项。\n\n备注：dd是描述dt的。\n\n\n定义列表用法非常灵活，可以一个dt配很多dd：\n\n```html\n\t<dl>\n\t\t<dt>北京</dt>\n\t\t<dd>国家首都，政治文化中心</dd>\n\t\t<dd>污染很严重，PM2.0天天报表</dd>\n\t\t<dt>上海</dt>\n\t\t<dd>魔都，有外滩、东方明珠塔、黄浦江</dd>\n\t\t<dt>广州</dt>\n\t\t<dd>中国南大门，有珠江、小蛮腰</dd>\n\t</dl>\n```\n\n还可以拆开，让每一个dl里面只有一个dt和dd，这样子感觉清晰一些：\n\n```html\n\t<dl>\n\t\t<dt>北京</dt>\n\t\t<dd>国家首都，政治文化中心</dd>\n\t\t<dd>污染很严重，PM2.0天天报表</dd>\n\t</dl>\n\n\t<dl>\n\t\t<dt>上海</dt>\n\t\t<dd>魔都，有外滩、东方明珠塔、黄浦江</dd>\n\t</dl>\n\n\t<dl>\n\t\t<dt>广州</dt>\n\t\t<dd>中国南大门，有珠江、小蛮腰</dd>\n\t</dl>\n```\n\n真实案例：（京东最下方）\n\n![](http://img.smyhvae.com/20170704_1727.png)\n\n\n上图中的结构如下：\n\n```html\n<dl>\n\t<dt>购物指南</dt>\n\t<dd>\n\t\t<a href=\"#\">购物流程</a>\n\t\t<a href=\"#\">会员介绍</a>\n\t\t<a href=\"#\">生活旅行/团购</a>\n\t\t<a href=\"#\">常见问题</a>\n\t\t<a href=\"#\">大家电</a>\n\t\t<a href=\"#\">联系客服</a>\n\t</dd>\n</dl>\n<dl>\n\t<dt>配送方式</dt>\n\t<dd>\n\t\t<a href=\"#\">上门自提</a>\n\t\t<a href=\"#\">211限时达</a>\n\t\t<a href=\"#\">配送服务查询</a>\n\t\t<a href=\"#\">配送费收取标准</a>\n\t\t<a href=\"#\">海外配送</a>\n\t</dd>\n</dl>\n\n```\n\n京东商品分类如下：\n\n![](http://img.smyhvae.com/20170704_1729.png)\n\ndt、dd都是容器级标签，想放什么都可以。所以，现在就应该更加清晰的知道：用什么标签，不是根据样子来决定，而是语义（语义本质上是结构）。\n\n## 表格标签\n\n表格标签用`<table>`表示。\n一个表格`<table>`是由每行`<tr>`组成的，每行是由每个单元格`<td>`组成的。\n所以我们要记住，一个表格是由行组成的（行是由列组成的），而不是由行和列组成的。\n在以前，要想固定标签的位置，唯一的方法就是表格。现在可以通过CSS定位的功能来实现。但是现在在做页面的时候，表格作用还是有一些的。\n\n例如，一行的单元格：\n```html\n<table>\n\t<tr>\n\t\t<td></td>\n\t\t<td></td>\n\t\t<td></td>\n\t\t<td></td>\n\t</tr>\n</table>\n```\n上面的表格中没有加文字，所以在生成的网页中什么都看不到。\n例如，3行4列的单元格：\n```html\n<table>\n\t<tr>\n\t\t<td>千古壹号</td>\n\t\t<td>23</td>\n\t\t<td>男</td>\n\t\t<td>黄冈</td>\n\t</tr>\n\n\t<tr>\n\t\t<td>许嵩</td>\n\t\t<td>29</td>\n\t\t<td>男</td>\n\t\t<td>安徽</td>\n\t</tr>\n\n\t<tr>\n\t\t<td>邓紫棋</td>\n\t\t<td>23</td>\n\t\t<td>女</td>\n\t\t<td>香港</td>\n\t</tr>\n\n</table>\n```\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_10.png)\n\n上图中的表格好像没看到边框呀，不急，接下来看看`<table>`标签的属性。\n\n**`<table>`的属性：**\n - `border`：边框。像素为单位。\n - `style=\"border-collapse:collapse;\"`：单元格的线和表格的边框线合并（表格的两边框合并为一条）\n - `width`：宽度。像素为单位。\n - `height`：高度。像素为单位。\n - `bordercolor`：表格的边框颜色。\n - `align`：**表格**的水平对齐方式。属性值可以填：left right center。\n注意：这里不是设置表格里内容的对齐方式，如果想设置内容的对齐方式，要对单元格标签`<td>`进行设置）\n - `cellpadding`：单元格内容到边的距离，像素为单位。默认情况下，文字是紧挨着左边那条线的，即默认情况下的值为0。\n注意不是单元格内容到四条边的距离哈，而是到一条边的距离，默认是与左边那条线的距离。如果设置属性`dir=\"rtl\"`，那就指的是内容到右边那条线的距离。\n - `cellspacing`：单元格和单元格之间的距离（外边距），像素为单位。默认情况下的值为0\n - `bgcolor=\"#99cc66\"`：表格的背景颜色。\n - `background=\"路径src/...\"`：背景图片。\n背景图片的优先级大于背景颜色。\n - `bordercolorlight`：表格的上、左边框，以及单元格的右、下边框的颜色\n - `bordercolordark`：表格的右、下边框，以及单元格的上、左的边框的颜色\n这两个属性的目的是为了设置3D的效果。\n - `dir`：公有属性，单元格内容的排列方式(direction)。 可以 取值：`ltr`：从左到右（left to right，默认），`rtl`：从右到左（right to left）\n既然说`dir`是共有属性，如果把这个属性放在任意标签中，那表明这个标签的位置可能会从右开始排列。\n\n单元格带边框的效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_11.png)\n\n备注：表格中很细表格边线的制作，CSS的写法：\n\n```css\nstyle=\"border-collapse:collapse;\"\n```\n\n\n### `<tr>`：行\n\n一个表格就是一行一行组成的。\n\n**属性：**\n\n - `dir`：公有属性，设置这一行单元格内容的排列方式。可以取值：\n \t- `ltr`：从左到右（left to right，默认）\n\t- `rtl`：从右到左（right to left）\n - `bgcolor`：设置这一行的单元格的背景色。\n注：没有background属性，即：无法设置这一行的背景图片，如果非要设置，可以用css实现。\n - `height`：一行的高度\n - `align=\"center\"`：一行的内容水平居中显示，取值：left、center、right\n - `valign=\"center\"`：一行的内容垂直居中，取值：top、middle、bottom\n\n### `<td>`：单元格\n\n**属性：**\n\n - `align`：内容的横向对齐方式。属性值可以填：left right center。如果想让每个单元格的内容都居中，这个属性太麻烦了，以后用css来解决。\n - `valign`：内容的纵向对齐方式。属性值可以填：top middle bottom\n - `width`：绝对值或者相对值(%)\n - `height`：单元格的高度\n - `bgcolor`：设置这个单元格的背景色。\n - `background`：设置这个单元格的背景图片。\n\n### 单元格的合并\n\n单元格的属性：\n\n- `colspan`：横向合并。例如`colspan=\"2\"`表示当前单元格在水平方向上要占据两个单元格的位置。\n- `rowspan`：纵向合并。例如`rowspan=\"2\"`表示当前单元格在垂直方向上要占据两个单元格的位置。\n\n效果举例：（横向合并）\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_13.png)\n\n效果举例：（纵向合并）\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_15.png)\n\n### `<th>`：加粗的单元格。相当于`<td>` + `<b>`\n\n- 属性同`<td>`标签。\n\n\n\n### `<caption>`：表格的标题。使用时和`tr`标签并列\n\n - 属性：`align`，表示标题相对于表格的位置。属性取值可以是：left、center、right、top、bottom\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_16.png)\n\n### 表格的`<thead>`标签、`<tbody>`标签、`<tfoot>`标签\n\n这三个标签有与没有的区别：\n\n- 1、如果写了，那么这三个部分的**代码顺序可以任意**，浏览器显示的时候还是按照thead、tbody、tfoot的顺序依次来显示内容。如果不写thead、tbody、tfoot，那么浏览器解析并显示表格内容的时候是从按照代码的从上到下的顺序来显示。\n- 2、当表格非常大内容非常多的时候，如果用thead、tbody、tfoot标签的话，那么**数据可以边获取边显示**。如果不写，则必须等表格的内容全部从服务器获取完成才能显示出来。\n\n举例：\n\n```html\n <body>\n\n\t<table border=\"1\">\n\n\t\t<tbody>\n\t\t<tr>\n\t\t\t<td>生命壹号</td>\n\t\t\t<td>23</td>\n\t\t\t<td>男</td>\n\t\t\t<td>黄冈</td>\n\t\t</tr>\n\t\t</tbody>\n\n\t\t<tfoot>\n\t\t<tr>\n\t\t\t<td>许嵩</td>\n\t\t\t<td>29</td>\n\t\t\t<td>男</td>\n\t\t\t<td>安徽</td>\n\t\t</tr>\n\t\t</tfoot>\n\n\t\t<thead>\n\t\t<tr>\n\t\t\t<td>邓紫棋</td>\n\t\t\t<td>23</td>\n\t\t\t<td>女</td>\n\t\t\t<td>香港</td>\n\t\t</tr>\n\t\t</thead>\n\n\t</table>\n\n </body>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_17.png)\n\n## 框架标签\n\n如果我们希望在一个网页中显示多个页面，那框架标签就派上用场了。\n> - 注意，框架标签不能放在`<body>`标签里面，因为`<body>`标签代表的只是一个页面，而框架标签代表的是多个页面。于是：`<frameset>`和`<body>`只能二选一。\n> - 框架的集合用`<frameset>`表示，然后在`<frameset>`集合里放入一个一个的框架`<frame>`\n\n**补充**：`frameset`和`frame`已经从 Web标准中删除，建议使用 iframe 代替。\n\n### `<frameset>`：框架的集合\n\n一个框架的集合可以包含多个框架或框架的集合。**属性：**\n\n- `rows`：水平分割，将框架分为上下部分。写法有两种：\n\n1、绝对值写法：`rows=\"200,*\"`  其中`*`代表剩余的。这里其实包含了两个框架：上面的框架占200个像素，下面的框架占剩下的部分。\n\n2、相对值写法：`rows=\"30%,*\"`  其中`*`代表剩余的。这里其实包含了两个框架：上面的框架占30%，下面的框架占70%。\n\n注：如果你想将框架分成很多行，在属性值里用逗号隔开就行了。\n\n- `cols`：垂直分割，将框架分为左右部分。写法有两种：\n\n1、绝对值写法：`cols=\"200,*\"`  其中`*`代表剩余的。这里其实包含了两个框架：左边的框架占200个像素，右边的框架占剩下的部分。\n\n2、相对值写法：`cols=\"30%,*\"`  其中`*`代表剩余的。这里其实包含了两个框架：左边的框架占30%，右边的框架占70%。\n\n注：如果你想将框架分成很多列，在属性值里用逗号隔开就行了。\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_26.png)\n\n上图中，如果删掉页面right.html，显示效果如下：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_27.png)\n\n### `<frame>`：框架\n\n一个框架显示一个页面。\n\n**属性：**\n\n- `scrolling=\"no\"`：是否需要滚动条。默认值是true。\n- `noresize`：不可以改变框架大小。默认情况下，单个框架的边界是可以拖动的，这样的话，框架大小就不固定了。如果用了这个属性值，框架大小将固定。\n\n举例：\n\n```html\n<frame src=\"top.html\" noresize></frame>\n```\n\n- `bordercolor=\"#00FF00\"`：给框架的边框定义颜色。这个属性在框架集合`<frameset>`中同样适用。\n颜色这个属性在IE浏览器中生效，但是在google浏览器中无效，不知道为啥。\n\n- `frameborder=\"0\"`或`frameborder=\"1\"`：隐藏或显示边框（框架线）。\n\n- `name`：给框架起一个名字。\n\n利用`name`这个属性，我们可以在框架里进行超链。\n\n举例：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_28.png)\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_gif3.gif)\n\n\n## 内嵌框架\n\n内嵌框架用`<iframe>`表示。`<iframe>`是`<body>`的子标记。\n\n内嵌框架inner frame：嵌入在一个页面上的框架(仅仅IE、新版google浏览器支持，可能有其他浏览器也支持，暂时我不清楚)。\n\n**属性：**\n\n - `src=\"subframe/the_second.html\"`：内嵌的那个页面\n - `width=800`：宽度\n - `height=“150`：高度\n - `scrolling=\"no\"`：是否需要滚动条。默认值是true。\n - `name=\"mainFrame\"`：窗口名称。公有属性。\n\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_29.png)\n\n内嵌框架举例：（在内嵌页面中切换显示不同的压面）\n\n```html\n <body>\n\n \t<a href=\"文字页面.html\" target=\"myframe\">默认显示文字页面</a><br>\n \t<a href=\"图片页面.html\" target=\"myframe\">点击进入图片页面</a><br>\n \t<a href=\"表格页面.html\" target=\"myframe\">点击进入表格页面</a><br>\n\n \t<iframe src=\"文字页面.html\" width=\"400\" height=\"400\" name=\"myframe\"></iframe>\n \t<br>\n \t嘿嘿\n\n </body>\n\n```\n\n效果演示：\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_GIF.gif)\n\n\n## 表单标签\n\n表单标签用`<form>`表示，用于与服务器的交互。表单就是收集用户信息的，就是让用户填写的、选择的。\n\n**属性：**\n - `name`：表单的名称，用于JS来操作或控制表单时使用；\n - `id`：表单的名称，用于JS来操作或控制表单时使用；\n - `action`：指定表单数据的处理程序，一般是PHP，如：action=“login.php”\n - `method`：表单数据的提交方式，一般取值：get(默认)和post\n\n注意：表单和表格嵌套时，是在`<form>`标记中套`<table>`标记。\n\nform标签里面的action属性和method属性，在后续的 ajax文章上再讲。这里简单说一下：action属性就是表示，表单将提交到哪里。 method属性表示用什么HTTP方法提交，有get、post两种。\n\n**get提交和post提交的区别：**\n\nGET方式：\n将表单数据，以\"name=value\"形式追加到action指定的处理程序的后面，两者间用\"?\"隔开，每一个表单的\"name=value\"间用\"&\"号隔开。\n特点：只适合提交少量信息，并且不太安全(不要提交敏感数据)、提交的数据类型只限于ASCII字符。\n\nPOST方式：\n将表单数据直接发送(隐藏)到action指定的处理程序。POST发送的数据不可见。Action指定的处理程序可以获取到表单数据。\n特点：可以提交海量信息，相对来说安全一些，提交的数据格式是多样的(Word、Excel、rar、img)。\n\n**Enctype：**\n表单数据的编码方式(加密方式)，取值可以是：application/x-www-form-urlencoded、multipart/form-data。Enctype只能在POST方式下使用。\n\n- Application/x-www-form-urlencoded：**默认**加密方式，除了上传文件之外的数据都可以\n- Multipart/form-data：**上传附件时，必须使用这种编码方式**。\n\n\n### `<input>`：输入标签（文本框）\n\n用于接收用户输入。\n\n```html\n<input type=\"text\" />\n```\n\n**属性：**\n\n- **`type=\"属性值\"`**：文本类型。属性值可以是：\n\t- `text`（默认）\n\t- `password`：密码类型\n\t- `radio`：单选按钮，名字相同的按钮作为一组进行单选（单选按钮，天生是不能互斥的，如果想互斥，必须要有相同的name属性。name就是“名字”。\n\t）。非常像以前的收音机，按下去一个按钮，其他的就抬起来了。所以叫做radio。\n\t- `checkbox`：多选按钮，**name 属性值相同的按钮**作为一组进行选择。\n\t- `checked`：将单选按钮或多选按钮默认处于选中状态。当`<input>`标签设置为`type=\"radio\"`或者`type=checkbox`时，可以用这个属性。属性值也是checked，可以省略。\n\t- `hidden`：隐藏框，在表单中包含不希望用户看见的信息\n\t- `button`：普通按钮，结合js代码进行使用。\n\t- `submit`：提交按钮，传送当前表单的数据给服务器或其他程序处理。这个按钮不需要写value自动就会有“提交”文字。这个按钮真的有提交功能。点击按钮后，这个表单就会被提交到form标签的action属性中指定的那个页面中去。\n\t- `reset`：重置按钮，清空当前表单的内容，并设置为最初的默认值\n\t- `image`：图片按钮，和提交按钮的功能完全一致，只不过图片按钮可以显示图片。\n\t- `file`：文件选择框。\n\t提示：如果要限制上传文件的类型，需要配合JS来实现验证。对上传文件的安全检查：一是扩展名的检查，二是文件数据内容的检查。\n\n - **`value=\"内容\"`**：文本框里的默认内容（已经被填好了的）\n\n - `size=\"50\"`：表示文本框内可以显示**五十个字符**。一个英文或一个中文都算一个字符。\n注意**size属性值的单位不是像素哦**。\n\n - `readonly`：文本框只读，不能编辑。因为它的属性值也是readonly，所以属性值可以不写。\n用了这个属性之后，在google浏览器中，光标点不进去；在IE浏览器中，光标可以点进去，但是文字不能编辑。\n\n - `disabled`：文本框只读，不能编辑，光标点不进去。属性值可以不写。\n\n> 备注：HTML5中，input的类型又增加了很多（比如date、color，我们会在 html5 中讲到）。\n\n**举例**：\n\n```html\n\t<form>\n\t\t姓名：<input value=\"呵呵\" >逗比<br>\n\t\t昵称：<input value=\"哈哈\" readonly=\"\"><br>\n\t\t名字：<input type=\"text\" value=\"name\" disabled=\"\"><br>\n\t\t密码：<input type=\"password\" value=\"pwd\" size=\"50\"><br>\n\t\t性别：<input type=\"radio\" name=\"gender\" id=\"radio1\" value=\"male\" checked=\"\">男\n\t\t\t  <input type=\"radio\" name=\"gender\" id=\"radio2\" value=\"female\" >女<br>\n\t\t爱好：<input type=\"checkbox\" name=\"love\" value=\"eat\">吃饭\n\t\t\t  <input type=\"checkbox\" name=\"love\" value=\"sleep\">睡觉\n\t\t\t  <input type=\"checkbox\" name=\"love\" value=\"bat\">打豆豆\n\t</form>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_33.png)\n\n注意，多个单选框的input标签中，name 的属性值可以相同，但是 **id 的属性值必须是唯一的**。我们知道，html的标签中，id的属性值是唯一的。\n\n**四种按钮的举例**：\n\n```html\n\t<form>\n\t\t<input type=\"button\" value=\"普通按钮\"><br>\n\t\t<input type=\"submit\"  value=\"提交按钮\"><br>\n\t\t<input type=\"reset\" value=\"重置按钮\"><br>\n\t\t<input type=\"image\" value=\"图片按钮1\"><br>\n\t\t<input type=\"image\" src=\"1.jpg\" width=\"800\" value=\"图片按钮2\"><br>\n\t\t<input type=\"file\" value=\"文件选择框\">\n\t</form>\n```\n\n**前端开发工程师，重点关心页面的美、样式、板式、交互。至于数据的提供和比较重的业务逻辑，都是后台工程师做的事情。**\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_35.png)\n\n### `<select>`：下拉列表标签\n\n`<select>`标签里面的每一项用`<option>`表示。select就是“选择”，option“选项”。\n\nselect标签和ul、ol、dl一样，都是组标签。\n\n**`<select>`标签的属性：**\n\n- `multiple`：可以对下拉列表中的选项进行多选。属性值为 multiple，也可以没有属性值。也就是说，既可以写成 `multiple=\"\"`，也可以写成`multiple=\"multiple\"`。\n- `size=\"3\"`：如果属性值大于1，则列表为滚动视图。默认属性值为1，即下拉视图。\n\n**`<option>`标签的属性：**\n\n - `selected`：预选中。没有属性值。\n\n举例：\n\n```html\n\t<form>\n\t\t<select>\n\t\t\t<option>小学</option>\n\t\t\t<option>初中</option>\n\t\t\t<option>高中</option>\n\t\t\t<option>大学</option>\n\t\t\t<option selected=\"\">研究生</option>\n\t\t</select>\n\t\t<br><br><br>\n\n\t\t<select size=\"3\">\n\t\t\t<option>小学</option>\n\t\t\t<option>初中</option>\n\t\t\t<option>高中</option>\n\t\t\t<option>大学</option>\n\t\t\t<option>研究生</option>\n\t\t</select>\n\t\t<br><br><br>\n\n\t\t<select multiple=\"\">\n\t\t\t<option>小学</option>\n\t\t\t<option>初中</option>\n\t\t\t<option selected=\"\">高中</option>\n\t\t\t<option selected=\"\">大学</option>\n\t\t\t<option>研究生</option>\n\t\t</select>\n\t\t<br><br><br>\n\n\t</form>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_32.png)\n\n### `<textarea>`标签：多行文本输入框\n\ntext 就是“文本”，area 就是“区域”。\n\n**属性：**\n\n - `rows=\"4\"`：指定文本区域的行数。\n - `cols=\"20\"`：指定文本区域的列数。\n - `readonly`：只读。\n\n举例：\n\n```html\n\t<form>\n\t\t<textarea name=\"txtInfo\" rows=\"4\" cols=\"20\">1、不爱摄影不懂设计的程序猿不是一个好的产品经理。</textarea>\n\t</form>\n```\n\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_34.png)\n\n上图的红框部分表示，我在文本区域进行了换行，所以显示的效果也出现了空白。\n\n\n\n### 表单的语义化\n\n比如，我们在注册一个网站的信息的时候，有一部分是必填信息，有一部分是选填信息，这个时候可以利用表单的语义化。\n举例：\n\n```html\n\t<form>\n\n\t\t<fieldset>\n\t\t<legend>账号信息</legend>\n\t\t姓名：<input value=\"呵呵\" >逗比<br>\n\t\t密码：<input type=\"password\" value=\"pwd\" size=\"50\"><br>\n\t\t</fieldset>\n\n\t\t<fieldset>\n\t\t<legend>其他信息</legend>\n\t\t性别：<input type=\"radio\" name=\"gender\" value=\"male\" checked=\"\">男\n\t\t\t  <input type=\"radio\" name=\"gender\" value=\"female\" >女<br>\n\t\t爱好：<input type=\"checkbox\" name=\"love\" value=\"eat\">吃饭\n\t\t\t  <input type=\"checkbox\" name=\"love\" value=\"sleep\">睡觉\n\t\t\t  <input type=\"checkbox\" name=\"love\" value=\"bat\">打豆豆\n\t\t</fieldset>\n\n\t</form>\n```\n\n效果：\n\n\n![](http://img.smyhvae.com/20151002_36.png)\n\n### `<label>`标签\n\n我们先来看下面一段代码：\n\n```html\n<input type=\"radio\" name=\"sex\" /> 男\n<input type=\"radio\" name=\"sex\" /> 女\n\n```\n\n对于上面这样的单选框，我们只有点击那个单选框（小圆圈）才可以选中，点击“男”、“女”这两个文字时是无法选中的；于是，label标签派上了用场。\n\n本质上来讲，“男”、“女”这两个文字和input标签时没有关系的，而label就是解决这个问题的。我们可以通过label把input和汉字包裹起来作为整体。\n\n解决方法如下：\n\n```html\n<input type=\"radio\" name=\"sex\" id=\"nan\" /> <label for=\"nan\">男</label>\n<input type=\"radio\" name=\"sex\" id=\"nv\"  /> <label for=\"nv\">女</label>\n```\n\n上方代码中，让label标签的**for 属性值**，和 input 标签的 **id 属性值相同**，那么这个label和input就有绑定关系了。\n\n\n当然了，复选框也有label：（任何表单元素都有label）\n\n```html\n<input type=\"checkbox\" id=\"kk\" />\n<label for=\"kk\">10天内免登陆</label>\n```\n\n## 多媒体标签\n\n**声明：**\n多媒体包含：音频、视频、Flash。网页上的多媒体基本都是Flash格式的。\n.wmv、.dat、.mob、.rmvb等视频格式，在网页上不能直接播放，需要安装第三方的插件，才可以播放。不同的浏览器，播客上述视频格式，所使用插件参数又不一样。\n上述格式视频一般文件较大，不利于网络下载播放。\n一般情况下，是将其它的视频格式，转成Flash来在网页上播放。转换软件：格式工厂等。\nFlash格式的视频兼容性非常好，Flash格式的文件很小。\n\n### `<bgsound>`标签：播放背景音乐\n**属性：**\n - `src=\"音乐文件的路径\"`\n - `loop=\"-1\"`：属性值代表播放次数，-1代表循环播放。\n\n举例：\n```html\n <body>\n\t<bgsound src=\"王菲 - 清风徐来.mp3\"></bgsound>\n </body>\n\n```\n运行效果：\n打开网页后，在IE 8中播放正常，播放时网页上显示一片空白。在google浏览器中无法播放。\n\n### `<embed>`标签：播放多媒体文件（音频、视频等）\n主要应用Netscape浏览器，它不是W3C规范。\n\n > 备注：视频格式可以支持 mp4、wav等，但不是所有视频格式都支持。\n\n**属性：**\n - `src=\"多媒体文件的路径\"`\n - `loop=\"-1\"`：属性值代表播放次数，-1代表循环播放。\n - `autostart=\"false\"`：打开网页时，禁止自动播放。默认值是true。\n - `volume=\"100\"`：设置默认的音量大小，测试发现这个值好像不起作用哦。\n - width：指Flash文件的宽度\n - height：指Flash文件的高度\n - quality：指Flash的播放质量，质量有高有低 hight  low\n - pluginspage：如果指定的Flash插件不存在，则从pluginspage指定的地方进行下载。\n - type：指定Flash的文件格式类型\n - wmode：指Flash的背景是否可以透明，取值：transparent是透明的\n\n`<embed>`标签播放音频举例：\n```html\n <body>\n\t<embed src=\"王菲 - 清风徐来.mp3\"></embed>\n </body>\n\n```\nIE 8中的运行效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_37.png)\n\ngoogle浏览器中的运行效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_38.png)\n\n注：在HTML5中新增了`<video>`标签播放视频。\n\n### `<object>`标签：播放多媒体文件（音频、视频等）\n\n主要应用IE浏览器，它是W3C规范。\n\n**属性：**\n - `classid`：指定Flash插件的ID号，一般存在于注册表中。\n - `codebase`：如果Flash插件不存在，则从codebase指定的地址下载。\n - `<param>`标签的主要作用：设置具体的详细参数。\n\n**总结：在网页中插入Flash时，为了同时兼容多种浏览器，需要将`<object>`标签和`<embed>`标签标记一起使用，但使用的顺序是：`<object>`中嵌套`<embed>`标记。**\n举例：\n\n```html\n<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0\" width=\"778\" height=\"202\">\n  <param name=\"movie\" value=\"images/banner.swf\">\n  <param name=\"quality\" value=\"high\">\n  <param name=\"wmode\" value=\"transparent\">\n  <embed src=\"images/banner.swf\" width=\"778\" height=\"202\" quality=\"high\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" wmode=\"transparent\"></embed>\n</object>\n```\n\n## `<marquee>`：滚动字幕标签\n\n如果在这个标签里设置了内容，那么，打开网页时，内容会像弹幕一样自动移动。\n**属性：**\n - `direction=\"right\"`：移动的目标方向。属性值可以是：`left`（从右向左移动，默认值）、`right`（从左向右移动）、`up`（从下向上移动）、`down`（从上向下移动）。\n\n - `behavior=\"slide\"`：行为方式。属性值可以是：`slide`（只移动一次）、`scroll`（循环移动，默认值）、`alternate`（循环移动）、。\n`alternate`和`scroll`属性值都是循环移动，区别在于：假设在`direction=\"right\"`的情况下，`behavior=\"scroll\"`表示从左到右、从左到右、从左到右···`behavior=\"alternate\"`表示从左到右、从右到左、从左到右···\n\n - `scrollamount=\"30\"`：移动的速度\n - `loop=\"3\"`: 循环多少圈。负值表示无限循环\n - `scrolldelay=\"1000\"`：移动一次休息多长时间。单位是毫秒。\n\n举例：\n```html\n\t<marquee behavior=\"alternate\" direction=\"down\"  width=\"300\" height=\"200\" bgcolor=\"#8c5dc1\">我来了</marquee>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-02-cnblogs_html_04.gif)\n\n## html废弃标签介绍\n\nHTML现在只负责语义，而不负责样式。但是HTML一开始，连样式也包办了。这些样式的标签，都已经被废弃。\n\n2004年之前的东西：\n\n```html\n<font size=\"9\" color=\"red\">哈哈</font>\n```\n\n下面这些标签都是css钩子，而不是原意：\n\n```html\n\t<b>加粗</b>\n\t<u>下划线</u>\n\t<i>倾斜</i>\n    <del>删除线</del>\n\t<em>强调</em>\n\t<strong>强调</strong>\n\n```\n\n这些标签，是有着浓厚的样式的作用，干涉了css的作用，所以HTML抛弃了他们。\n\n类似的还有水平线标签：\n\n```html\n<hr />\n```\n\n换行标签：\n\n```\n<br />\n```\n\n但是，网页中99.9999%需要换行的时候，是因为另起了一个段落，所以要用p，而不要用`<br />`。不到万不得已，不要用br标签。\n\n标准的div+css页面，只会用到种类很少的标签：\n\n```\ndiv  p  h1  span   a   img   ul   ol    dl    input\n```\n\n知道每个标签的特殊用法、属性。比如a标签，img的属性。\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号（千古壹号id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/cnblogs/%E7%94%9F%E5%91%BD%E5%9B%A2%E9%98%9F%E5%85%AC%E4%BC%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.jpg)\n"
  },
  {
    "path": "01-HTML/08-HTML5详解.md",
    "content": "---\ntitle: 08-HTML5详解\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## HTML5的介绍\n\n### Web 技术发展时间线\n\n- 1991 HTML\n\n\n- 1994 HTML2\n\n- 1996 CSS1 + JavaScript\n\n- 1997 HTML4\n\n- 1998 CSS2\n\n- 2000 XHTML1（严格的html）\n\n- 2002 Tableless Web Design（表格布局）\n\n- 2005 AJAX\n\n- 2009 HTML5\n\n- 2014 HTML5 Finalized\n\n\n2002年的表格布局逐渐被淘汰，是因为：表格是用来承载数据的，并不是用来划分网页结构的。\n\n\n2009年就已经推出了HTML5的草案，但直到2014年才有定稿，是因为有移动端的推动。\n\n\nH5草案的前身是叫：Web Application，最早是由[WHATWG](https://baike.baidu.com/item/WHATWG/5803339?fr=aladdin)这个组织在2004年提出的。\n\n2007年被 W3C 组织接纳，并在 2008-01-22 发布 HTML5 的第一个草案。\n\n\n### 什么是 HTML5\n\nHTML5并不仅仅只是做为HTML标记语言的一个最新版本，更重要的是它**制定了Web应用开发的一系列标准**，成为第一个将Web做为应用开发平台的HTML语言。\n\nHTML5定义了一系列新元素，如新语义标签、智能表单、多媒体标签等，可以帮助开发者创建富互联网应用，还提供了一些Javascript API，如地理定位、重力感应、硬件访问等，可以在浏览器内实现类原生应用。我们甚至可以结合 Canvas 开发网页版游戏。\n\n\n**`HTML5`的广义概念**：HTML5代表浏览器端技术的一个发展阶段。在这个阶段，浏览器的呈现技术得到了飞跃发展和广泛支持，它包括：HTML5、CSS3、Javascript API在内的一套技术组合。\n\n`HTML5`不等于 `HTML next version`。`HTML5` 包含： `HTML`的升级版、`CSS`的升级版、`JavaScript API`的升级版。\n\n**总结**：`HTML5`是新一代开发 **Web 富客户端**应用程序整体**解决方案**。包括：HTML5，CSS3，Javascript API在内的一套**技术组合**。\n\n\n**富客户端**：具有很强的**交互性**和体验的客户端程序。比如说，浏览博客，是比较简单的客户端；一个在线听歌的网站、即时聊天网站就是富客户端。\n\n**PS：**\n\n单纯地从技术的角度讲，兼容性问题只会让开发者徒增烦恼。\n\n如果网页端的程序能做到PC客户端的体验，就会对后者构成威胁。\n\n### HTML5 的应用场景\n\n列举几个HTML5 的应用场景：\n\n（1）极具表现力的网页：内容简约而不简单。\n\n（2）网页应用程序：\n\n- 代替PC端的软件：iCloud、百度脑图、Office 365等。\n\n- APP端的网页：淘宝、京东、美团等。\n\n- 微信端：公众号、小程序等。\n\n（3）混合式本地应用。\n\n（4）简单的游戏。\n\n### HTML5 新增的内容\n\n![](http://img.smyhvae.com/20180206_1540.png)\n\n![](http://img.smyhvae.com/20180206_1545.png)\n\n![](http://img.smyhvae.com/20180206_1541.png)\n\n\n## 语义化的标签\n\n### 语义化的作用\n\n语义标签对于我们并不陌生，如`<p>`表示一个段落、`<ul>`表示一个无序列表。**标签语义化的作用：**\n\n- 能够便于开发者阅读和写出更优雅的代码。\n\n- 同时让浏览器或是网络爬虫可以很好地解析，从而更好分析其中的内容。\n\n- 更好地搜索引擎优化。\n\n总结：HTML的职责是描述一块内容是什么（或其意义），而不是它长什么样子；它的外观应该由CSS来决定。\n\n\n### H5在语义上的改进\n\n在此基础上，HTML5 增加了大量有意义的语义标签，更有利于搜索引擎或辅助设备理解 HTML 页面内容。HTML5会让HTML代码的内容更结构化、标签更语义化。\n\n我们常见的 css+div 布局是：\n\n\n![](http://img.smyhvae.com/20180206_1546.png)\n\n在html5中，我们可以这样写：\n\n![](http://img.smyhvae.com/20180206_1550.png)\n\n传统的做法中，我们通过增加类名如`class=\"header\"`、`class=\"footer\"`，使HTML页面具有语义性，但是不具有通用性。\n\nHTML5 则是通过新增语义标签的形式来解决这个问题，例如`<header></header>`、`<footer></footer>`等，这样就可以使其具有通用性。\n\n\n**传统网页布局：**\n\n\n```html\n<!-- 头部 -->\n<div class=\"header\">\n    <ul class=\"nav\"></ul>\n</div>\n\n<!-- 主体部分 -->\n<div class=\"main\">\n    <!-- 文章 -->\n    <div class=\"article\"></div>\n    <!-- 侧边栏 -->\n    <div class=\"aside\"></div>\n</div>\n\n<!-- 底部 -->\n<div class=\"footer\">\n\n</div>\n```\n\n\n\n**H5 的经典网页布局：**\n\n```html\n<!-- 头部 -->\n<header>\n    <ul class=\"nav\"></ul>\n</header>\n\n<!-- 主体部分 -->\n<div class=\"main\">\n    <!-- 文章 -->\n    <article></article>\n    <!-- 侧边栏 -->\n    <aside></aside>\n</div>\n\n<!-- 底部 -->\n<footer>\n\n</footer>\n```\n\n\n## H5中新增的语义标签\n\n- `<section>` 表示区块\n\n- `<article>` 表示文章。如文章、评论、帖子、博客\n\n- `<header>` 表示页眉\n\n- `<footer>` 表示页脚\n\n- `<nav>` 表示导航\n\n- `<aside>` 表示侧边栏。如文章的侧栏\n\n- `<figure>` 表示媒介内容分组。\n\n- `<mark>` 表示标记 (用得少)\n\n- `<progress>` 表示进度 (用得少)\n\n- `<time>` 表示日期\n\n本质上新语义标签与`<div>`、`<span>`没有区别，只是其具有表意性，使用时除了在HTML结构上需要注意外，其它和普通标签的使用无任何差别，可以理解成`<div class=\"nav\">` 相当于`<nav>`。\n\n\nPS：单标签不用写关闭符号。\n\n### 新语义标签的兼容性处理\n\nIE8 及以下版本的浏览器不支持 H5 和 CSS3。解决办法：引入`html5shiv.js`文件。\n\n引入时，需要做if判断，具体代码如下：\n\n```html\n    <!--  条件注释 只有ie能够识别-->\n\n    <!--[if lte ie 8]>\n        <script src=\"html5shiv.min.js\"></script>\n    <![endif]-->\n```\n\n上方代码是**条件注释**：虽然是注释，但是IE浏览器可以识别出来。解释一下：\n\n- l：less 更小\n\n- t：than 比\n\n- e：equal等于\n\n- g：great 更大\n\n\nPS:我们在测试 IE 浏览器的兼容的时候，可以使用软件 ietest，模拟IE6-IE11。\n\n\n在不支持HTML5新标签的浏览器，会将这些新的标签解析成行内元素(inline)对待，所以我们只需要将其转换成块元素(block)即可使用。\n\n但是在IE9版本以下，并不能正常解析这些新标签，但是可以识别通过document.createElement('tagName')创建的自定义标签。于是我们的解决方案就是：将HTML5的新标签全部通过document.createElement('tagName')来创建一遍，这样IE低版本也能正常解析HTML5新标签了。\n\n当然，在实际开发中我们更多采用的办法是：检测IE浏览器的版本，来加载第三方的JS库来解决兼容问题（如上方代码所示）。\n\n\n## H5中的表单\n\n传统的Web表单已经越来越不能满足开发的需求，HTML5 在 Web 表单方向做了很大的改进，如拾色器、日期/时间组件等，使表单处理更加高效。\n\n### H5中新增的表单类型\n\n- `email` 只能输入email格式。自动带有验证功能。\n\n- `tel` 手机号码。\n\n- `url` 只能输入url格式。\n\n- `number` 只能输入数字。\n\n- `search` 搜索框\n\n- `range` 滑动条\n\n- `color` 拾色器\n\n- `time`\t时间\n\n- `date` 日期\n\n- `datetime` 时间日期\n\n- `month` 月份\n\n- `week` 星期\n\n\n上面的部分类型是针对移动设备生效的，且具有一定的兼容性，在实际应用当中可选择性的使用。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <title>表单类型</title>\n    <style>\n        body {\n            margin: 0;\n            padding: 0;\n            background-color: #F7F7F7;\n        }\n\n        form {\n            max-width: 500px;\n            width: 100%;\n            margin: 32px auto 0;\n            font-size: 16px;\n        }\n\n        label {\n            display: block;\n            margin: 10px 0;\n        }\n\n        input {\n            width: 100%;\n            height: 25px;\n            margin-top: 2px;\n            display: block;\n        }\n\n    </style>\n</head>\n<body>\n<form action=\"\">\n    <fieldset>\n        <legend>表单类型</legend>\n        <label for=\"\">\n            email: <input type=\"email\" name=\"email\" required>\n        </label>\n        <label for=\"\">\n            color: <input type=\"color\" name=\"color\">\n        </label>\n        <label for=\"\">\n            url: <input type=\"url\" name='url'>\n        </label>\n        <label for=\"\">\n            number: <input type=\"number\" step=\"3\" name=\"number\">\n        </label>\n        <label for=\"\">\n            range: <input type=\"range\" name=\"range\" value=\"100\">\n        </label>\n        <label for=\"\">\n            search: <input type=\"search\" name=\"search\">\n        </label>\n        <label for=\"\">\n            tel: <input type=\"tel\" name=\"tel\">\n        </label>\n        <label for=\"\">\n            time: <input type=\"time\" name=\"time\">\n        </label>\n        <label for=\"\">\n            date: <input type=\"date\" name=\"date\">\n        </label>\n        <label for=\"\">\n            datetime: <input type=\"datetime\">\n        </label>\n        <label for=\"\">\n            week: <input type=\"week\" name=\"week\">\n        </label>\n        <label for=\"\">\n            month: <input type=\"month\" name=\"month\">\n        </label>\n        <label for=\"\">\n            datetime-local: <input type=\"datetime-local\" name=\"datetime-local\">\n        </label>\n        <input type=\"submit\">\n    </fieldset>\n</form>\n</body>\n</html>\n```\n\n代码解释：\n\n`<fieldset>` 标签将表单里的内容进行打包，代表一组；而`<legend> `标签的则是 fieldset 里的元素定义标题。\n\n### 表单元素（标签）\n\n这里讲两个表单元素。\n\n**1、`<datalist>` 数据列表：**\n\n\n```html\n<input type=\"text\" list=\"myData\">\n<datalist id=\"myData\">\n    <option>本科</option>\n    <option>研究生</option>\n    <option>不明</option>\n</datalist>\n```\n\n上方代码中，input里的list属性和 datalist 进行了绑定。\n\n效果：\n\n![](http://img.smyhvae.com/20180206_1845.gif)\n\n上图可以看出，数据列表可以自动提示。\n\n2、`<keygen>`元素：\n\nkeygen 元素的作用是提供一种验证用户的可靠方法。\n\nkeygen 元素是密钥对生成器（key-pair generator）。当提交表单时，会生成两个键：一个公钥，一个私钥。\n\n私钥（private key）存储于客户端，公钥（public key）则被发送到服务器。公钥可用于之后验证用户的客户端证书（client certificate）。\n\n3、`<meter>`元素：度量器\n\n- low：低于该值后警告\n\n- high：高于该值后警告\n\n- value：当前值\n\n- max：最大值\n\n- min：最小值。\n\n举例：\n\n```javascript\n\t<meter  value=\"81\"    min=\"0\" max=\"100\"  low=\"60\"  high=\"80\"/>\n```\n\n### 表单属性\n\n- `placeholder` 占位符（提示文字）\n\n- `autofocus` 自动获取焦点\n\n- `multiple` 文件上传多选或多个邮箱地址\n\n- `autocomplete` 自动完成（填充的）。on 开启（默认），off 取消。用于表单元素，也可用于表单自身(on/off)\n\n- `form` 指定表单项属于哪个form，处理复杂表单时会需要\n\n- `novalidate` 关闭默认的验证功能（只能加给form）\n\n- `required` 表示必填项\n\n- `pattern` 自定义正则，验证表单。例如\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        form {\n            width: 100%;\n            /* 最大宽度*/\n            max-width: 640px;\n            /* 最小宽度*/\n            min-width: 320px;\n            margin: 0 auto;\n            font-family: \"Microsoft Yahei\";\n            font-size: 20px;\n        }\n\n        input {\n            display: block;\n            width: 100%;\n            height: 30px;\n            margin: 10px 0;\n        }\n    </style>\n</head>\n<body>\n\n<form action=\"\">\n    <fieldset>\n        <legend>表单属性</legend>\n        <label for=\"\">\n            用户名：<input type=\"text\" placeholder=\"例如：smyhvae\" autofocus name=\"userName\" autocomplete=\"on\" required/>\n        </label>\n\n        <label for=\"\">\n            电话：<input type=\"tel\" pattern=\"1\\d{10}\"/>\n        </label>\n\n        <label for=\"\">\n            multiple的表单: <input type=\"file\" multiple>\n        </label>\n\n        <!-- 上传文件-->\n        <input type=\"file\" name=\"file\" multiple/>\n\n        <input type=\"submit\"/>\n    </fieldset>\n</form>\n\n</body>\n</html>\n```\n\n### 表单事件\n\n- `oninput()`：用户输入内容时触发，可用于输入字数统计。\n\n- `oninvalid()`：验证不通过时触发。比如，如果验证不通过时，想弹出一段提示文字，就可以用到它。\n\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        form {\n            width: 100%;\n            /* 最大宽度*/\n            max-width: 400px;\n            /* 最小宽度*/\n            min-width: 200px;\n            margin: 0 auto;\n            font-family: \"Microsoft Yahei\";\n            font-size: 20px;\n        }\n\n        input {\n            display: block;\n            width: 100%;\n            height: 30px;\n            margin: 10px 0;\n        }\n    </style>\n</head>\n<body>\n<form action=\"\">\n    <fieldset>\n        <legend>表单事件</legend>\n        <label for=\"\">\n            邮箱：<input type=\"email\" name=\"\" id=\"txt1\"/>\n        </label>\n        <label for=\"\">\n            输入的次数统计：<input type=\"text\" name=\"\" id=\"txt2\"/>\n        </label>\n\n        <input type=\"submit\"/>\n    </fieldset>\n</form>\n<script>\n\n    var txt1 = document.getElementById('txt1');\n    var txt2 = document.getElementById('txt2');\n    var num = 0;\n\n    txt1.oninput = function () {  //用户输入时触发\n\n        num++;  //用户每输入一次，num自动加 1\n        //将统计数显示在txt2中\n        txt2.value = num;\n    }\n    txt1.oninvalid = function () {  //验证不通过时触发\n        this.setCustomValidity('亲，请输入正确哦');  //设置验证不通过时的提示文字\n    }\n\n</script>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180206_1920.gif)\n\n## 多媒体\n\n在HTML5之前，在网页上播放音频/视频的通用方法是利用Flash来播放。但是大多情况下，并非所有用户的浏览器都安装了Flash插件，由此使得音频、视频播放的处理变得非常复杂；并且移动设备的浏览器并不支持Flash插件。\n\nH5里面提供了视频和音频的标签。\n\n### 音频\n\nHTML5通过`<audio>`标签来解决音频播放的问题。\n\n使用举例：\n\n```html\n\t<audio src=\"music/yinyue.mp3\" autoplay controls> </audio>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180206_1958.png)\n\n我们可以通过附加属性，来更友好地控制音频的播放，如：\n\n- `autoplay` 自动播放。写成`autoplay` 或者 `autoplay = \"\"`，都可以。\n\n- `controls` 控制条。（建议把这个选项写上，不然都看不到控件在哪里）\n\n- `loop` 循环播放。\n\n- `preload` 预加载 同时设置 autoplay 时，此属性将失效。\n\n**处理兼容性问题：**\n\n由于版权等原因，不同的浏览器可支持播放的格式是不一样的：\n\n![](http://img.smyhvae.com/20180206_1945.png)\n\n为了做到多浏览器支持，可以采取以下兼容性写法：\n\n```html\n<!--推荐的兼容写法：-->\n<audio controls loop>\n    <source src=\"music/yinyue.mp3\"/>\n    <source src=\"music/yinyue.ogg\"/>\n    <source src=\"music/yinyue.wav\"/>\n    抱歉，你的浏览器暂不支持此音频格式\n</audio>\n```\n\n代码解释：如果识别不出音频格式，就弹出那句“抱歉”。\n\n\n### 视频\n\n\nHTML5通过`<video>`标签来解决视频播放的问题。\n\n使用举例：\n\n```html\n\t<video src=\"video/movie.mp4\" controls autoplay></video>\n```\n\n\n我们可以通过附加属性，来更友好地控制视频的播放，如：\n\n- `autoplay` 自动播放。写成`autoplay` 或者 `autoplay = \"\"`，都可以。\n\n- `controls` 控制条。（建议把这个选项写上，不然都看不到控件在哪里）\n\n- `loop` 循环播放。\n\n- `preload` 预加载 同时设置 autoplay 时，此属性将失效。\n\n- `width`：设置播放窗口宽度。\n\n- `height`：设置播放窗口的高度。\n\n由于版权等原因，不同的浏览器可支持播放的格式是不一样的：\n\n![](http://img.smyhvae.com/20180206_2025.png)\n\n兼容性写法：\n\n```html\n    <!--<video src=\"video/movie.mp4\" controls  autoplay ></video>-->\n    <!--  行内块 display:inline-block -->\n    <video controls autoplay>\n        <source src=\"video/movie.mp4\"/>\n        <source src=\"video/movie.ogg\"/>\n        <source src=\"video/movie.webm\"/>\n        抱歉，不支持此视频\n    </video>\n```\n\n## DOM 操作\n\n### 获取元素\n\n- document.querySelector(\"selector\") 通过CSS选择器获取符合条件的第一个元素。\n\n- document.querySelectorAll(\"selector\")  通过CSS选择器获取符合条件的所有元素，以类数组形式存在。\n\n### 类名操作\n\n- Node.classList.add(\"class\") 添加class\n\n- Node.classList.remove(\"class\") 移除class\n\n- Node.classList.toggle(\"class\") 切换class，有则移除，无则添加\n\n- Node.classList.contains(\"class\") 检测是否存在class\n\n### 自定义属性\n\njs 里可以通过 `box1.index=100;`  `box1.title` 来自定义属性和获取属性。\n\nH5可以直接在标签里添加自定义属性，**但必须以 `data-` 开头**。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<!-- 给标签添加自定义属性 必须以data-开头 -->\n<div class=\"box\" title=\"盒子\" data-my-name=\"smyhvae\" data-content=\"我是一个div\">div</div>\n<script>\n    var box = document.querySelector('.box');\n\n    //自定义的属性 需要通过 dateset[]方式来获取\n    console.log(box.dataset[\"content\"]);  //打印结果：我是一个div\n    console.log(box.dataset[\"myName\"]);    //打印结果：smyhvae\n\n    //设置自定义属性的值\n    var num = 100;\n    num.index = 10;\n    box.index = 100;\n    box.dataset[\"content\"] = \"aaaa\";\n\n</script>\n</body>\n</html>\n```\n\n### 举例：鼠标点击时，tab栏切换\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Tab 标签</title>\n    <style>\n        body {\n            margin: 0;\n            padding: 0;\n            background-color: #F7F7F7;\n        }\n\n        .tabs {\n            width: 400px;\n            margin: 30px auto;\n            background-color: #FFF;\n            border: 1px solid #C0DCC0;\n            box-sizing: border-box;\n        }\n\n        .tabs nav {\n            height: 40px;\n            text-align: center;\n            line-height: 40px;\n            overflow: hidden;\n            background-color: #C0DCC0;\n            display: flex;\n        }\n\n        nav a {\n            display: block;\n            width: 100px;\n            border-right: 1px solid #FFF;\n            color: #000;\n            text-decoration: none;\n        }\n\n        nav a:last-child {\n            border-right: 0 none;\n        }\n\n        nav a.active {\n            background-color: #9BAF9B;\n        }\n\n        .cont {\n            overflow: hidden;\n            display: none;\n        }\n\n        .cont ol {\n            line-height: 30px;\n        }\n    </style>\n</head>\n\n<body>\n    <div class=\"tabs\">\n        <nav>\n            <a href=\"javascript:;\" data-cont=\"local\">国内新闻</a>\n            <a href=\"javascript:;\" data-cont=\"global\">国际新闻</a>\n            <a href=\"javascript:;\" data-cont=\"sports\">体育新闻</a>\n            <a href=\"javascript:;\" data-cont=\"funny\">娱乐新闻</a>\n        </nav>\n        <section class=\"cont\" id=\"local\">\n            <ol>\n                <li>国内新闻1</li>\n                <li>国内新闻2</li>\n                <li>国内新闻3</li>\n                <li>国内新闻4</li>\n                <li>国内新闻5</li>\n                <li>国内新闻6</li>\n                <li>国内新闻7</li>\n            </ol>\n        </section>\n        <section class=\"cont\" id=\"global\">\n            <ol>\n                <li>国内新闻1</li>\n                <li>国际新闻2</li>\n                <li>国际新闻3</li>\n                <li>国际新闻4</li>\n                <li>国际新闻5</li>\n                <li>国际新闻6</li>\n            </ol>\n        </section>\n        <section class=\"cont\" id=\"sports\">\n            <ol>\n                <li>体育新闻1</li>\n                <li>体育新闻2</li>\n                <li>体育新闻3</li>\n                <li>体育新闻4</li>\n                <li>体育新闻5</li>\n                <li>体育新闻6</li>\n                <li>体育新闻7</li>\n            </ol>\n        </section>\n        <section class=\"cont\" id=\"funny\">\n            <ol>\n                <li>娱乐新闻1</li>\n                <li>娱乐新闻2</li>\n                <li>娱乐新闻3</li>\n                <li>娱乐新闻4</li>\n                <li>娱乐新闻5</li>\n                <li>娱乐新闻6</li>\n                <li>娱乐新闻7</li>\n            </ol>\n        </section>\n    </div>\n    <script>\n        // 目标： 默认显示一个 当前的样式\n        // 点击导航，实现切换\n        // key 表示的当前显示的是第几个\n\n        (function (key) {\n            // 获取所有的导航\n            var navs = document.querySelectorAll('nav a');\n            // 遍历 给导航 绑定事件，并且添加当前样式\n            for (var i = 0; i < navs.length; i++) {\n                // 如果是用户指定的当前样式\n                if (key == i) {\n                    navs[i].classList.add('active');\n                    // 拿到要显示内容section的id\n                    var secId = navs[i].dataset['cont'];\n                    // 获取对应的section标签\n                    document.querySelector('#' + secId).style.display = 'block';\n                }\n\n                // 给每一个导航绑定点击事件\n                navs[i].onclick = function () {\n                    // 排他\n                    // 之前有active样式的清除, 之前显示的section 隐藏\n                    var currentNav = document.querySelector('.active');\n                    // 获取对应的内容区域 ，让其隐藏\n                    var currentId = currentNav.dataset['cont'];\n                    // 去掉导航的active 样式\n                    currentNav.classList.remove('active');\n                    // 对应的内容区域\n                    document.querySelector('#' + currentId).style.display = 'none';\n\n                    // 突出显示自己 导航添加样式  对应的section 显示\n                    // 给自己添加active样式\n                    this.classList.add('active');\n                    // 对应的section模块显示出来\n                    var myId = this.dataset['cont'];\n                    document.querySelector('#' + myId).style.display = 'block';\n                }\n            }\n\n        })(0);\n\n\n    </script>\n</body>\n\n</html>\n```\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n"
  },
  {
    "path": "01-HTML/09-HTML5举例：简单的视频播放器.md",
    "content": "---\ntitle: 09-HTML5举例：简单的视频播放器\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n我们采用 Bootstrap 网站的图标字体，作为播放器的按钮图标。\n\nindex.html的代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <!-- 引入字体图标的文件-->\n    <link rel=\"stylesheet\" href=\"css/font-awesome.min.css\"/>\n    <style>\n        *{\n            margin: 0;\n            padding: 0;\n        }\n        /*多媒体标题*/\n        figcaption{\n            text-align: center;\n            line-height: 150px;\n            font-family: \"Microsoft Yahei\";\n            font-size:24px;\n        }\n\n        /* 播放器*/\n        .palyer{\n            width: 720px;\n            height: 360px;\n            margin:10px auto;\n            border: 1px solid #000;\n            background: url(images/loading.gif) center no-repeat #000;\n            background-size:auto 100%;\n            position: relative;\n            border-radius: 20px;\n\n        }\n\n        .palyer video{\n            height:100%;\n            display: block;\n            margin:0 auto;\n            /*display: none;*/\n        }\n\n        /* 控制条*/\n\n        .controls{\n            width: 700px;\n            height:40px;\n            background-color: rgba(255, 255, 0, 0.3);\n            position: absolute;\n            bottom:10px;\n            left:10px;\n            border-radius: 10px;\n        }\n        /*开关*/\n        .switch{\n            position: absolute;\n            width: 20px;\n            height: 20px;\n            left:10px;\n            top:10px;\n\n            text-align: center;\n            line-height: 20px;\n            color:yellow;\n\n        }\n        /*进度条*/\n        .progress{\n            width: 432px;\n            height: 10px;\n            position: absolute;\n            background-color: rgba(255,255,255,0.4);\n            left:40px;\n            top:15px;\n            border-radius: 4px;\n            overflow: hidden;\n        }\n        /* 当前进度*/\n        .curr-progress{\n            width: 50%;\n            height: 10px;\n            background-color: #fff;\n        }\n        /* 时间模块*/\n        .time{\n            width: 120px;\n            height: 20px;\n            text-align: center;\n            line-height: 20px;\n            color:#fff;\n            position: absolute;\n            left:510px;\n            top:10px;\n            font-size:12px;\n\n        }\n        /*全屏*/\n        .extend{\n            position: absolute;\n            width: 20px;\n            height: 20px;\n\n            right:20px;\n            top:10px;\n\n            text-align: center;\n            line-height: 20px;\n            color:yellow;\n        }\n\n    </style>\n</head>\n<body>\n    <!-- 多媒体-->\n    <figure>\n        <!--  多媒体标题-->\n        <figcaption>视频案例</figcaption>\n        <div class=\"palyer\">\n            <video src=\"video/fun.mp4\"></video>\n            <!-- 控制条-->\n            <div class=\"controls\">\n                <!-- 播放暂停-->\n                <a href=\"#\" class=\"switch  icon-play\"></a>\n                <div class=\"progress\">\n                    <!-- 当前进度-->\n                    <div class=\"curr-progress\"></div>\n                </div>\n                <!-- 时间-->\n                <div class=\"time\">\n                    <span class=\"curr-time\">00:00:00</span>/<span class=\"total-time\">00:00:00</span>\n                </div>\n                <!-- 全屏-->\n                <a href=\"#\" class=\"extend  icon-resize-full\"></a>\n            </div>\n\n        </div>\n    </figure>\n\n    <script>\n        // 思路：\n        /*\n        * 1、点击按钮 实现播放暂停并且切换图标\n        * 2、算出视频的总时显示出出来\n        * 3、当视频播放的时候，进度条同步，当前时间同步\n        * 4、点击实现全屏\n        */\n\n//        获取需要的标签\n            var  video=document.querySelector('video');\n//          播放按钮\n            var  playBtn=document.querySelector('.switch');\n//          当前进度条\n            var  currProgress=document.querySelector('.curr-progress');\n//          当前时间\n            var  currTime=document.querySelector('.curr-time');\n//          总时间\n            var  totalTime=document.querySelector('.total-time');\n//          全屏\n            var extend=document.querySelector('.extend');\n\n            var tTime=0;\n\n//         1、点击按钮 实现播放暂停并且切换图标\n\n           playBtn.onclick=function(){\n//               如果视频播放 就暂停，如果暂停 就播放\n               if(video.paused){\n//                   播放\n                   video.play();\n                   //切换图标\n                   this.classList.remove('icon-play');\n                   this.classList.add('icon-pause');\n               }else{\n//                   暂停\n                    video.pause();\n//                   切换图标\n                   this.classList.remove('icon-pause');\n                   this.classList.add('icon-play');}\n           }\n\n//        2、算出视频的总时显示出出来\n//        当时加载完成后的事件，视频能播放的时候\n        video.oncanplay=function(){\n//             获取视频总时长\n            tTime=video.duration;\n            console.log(tTime);\n\n//          将总秒数 转换成 时分秒的格式：00：00:00\n//            小时\n            var h=Math.floor(tTime/3600);\n//            分钟\n            var m=Math.floor(tTime%3600/60);\n//            秒\n            var s=Math.floor(tTime%60);\n\n//            console.log(h);\n//            console.log(m);\n//            console.log(s);\n\n//            把数据格式转成 00:00：00\n            h=h>=10?h:\"0\"+h;\n            m=m>=10?m:\"0\"+m;\n            s=s>=10?s:\"0\"+s;\n\n\n            console.log(h);\n            console.log(m);\n            console.log(s);\n//            显示出来\n            totalTime.innerHTML=h+\":\"+m+\":\"+s;\n        }\n//   * 3、当视频播放的时候，进度条同步，当前时间同步\n//         当时当前时间更新的时候触发\n        video.ontimeupdate=function(){\n//            获取视频当前播放的时间\n//           console.log(video.currentTime);\n//            当前播放时间\n            var cTime=video.currentTime;\n//           把格式转成00:00:00\n\n            var h=Math.floor(cTime/3600);\n//            分钟\n            var m=Math.floor(cTime%3600/60);\n//            秒\n            var s=Math.floor(cTime%60);\n\n//            把数据格式转成 00:00：00\n            h=h>=10?h:\"0\"+h;\n            m=m>=10?m:\"0\"+m;\n            s=s>=10?s:\"0\"+s;\n\n//            显示出当前时间\n            currTime.innerHTML=h+\":\"+m+\":\"+s;\n\n//            改变进度条的宽度： 当前时间/总时间\n            var value=cTime/tTime;\n\n            currProgress.style.width=value*100+\"%\";\n\n        }\n\n//        全屏\n        extend.onclick=function(){\n//            全屏的h5代码\n            video.webkitRequestFullScreen();\n        }\n\n    </script>\n</body>\n</html>\n```\n\n\n\n\n\n工程文件：[2018-02-23-H5多媒体播放器.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-23-H5%E5%A4%9A%E5%AA%92%E4%BD%93%E6%92%AD%E6%94%BE%E5%99%A8.rar)\n\n\n\n"
  },
  {
    "path": "01-HTML/10-HTML5详解（二）.md",
    "content": "---\ntitle: 10-HTML5详解（二）\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 本文主要内容\n\n- 拖拽\n\n- 历史\n\n- 地理位置\n\n- 全屏\n\n## 拖拽\n\n\n![](http://img.smyhvae.com/20180223_2130.gif)\n\n如上图所示，我们可以拖拽博客园网站里的图片和超链接。\n\n在HTML5的规范中，我们可以通过为元素增加 `draggable=\"true\"` 来设置此元素是否可以进行拖拽操作，其中图片、链接默认是开启拖拽的。\n\n### 1、拖拽元素\n\n页面中设置了 `draggable=\"true\"` 属性的元素。\n\n举例如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <link rel=\"stylesheet\" href=\"css/font-awesome.min.css\">\n    <style>\n    .box1{\n        width: 200px;\n        height: 200px;\n        background-color: green;\n\n    }\n    </style>\n</head>\n<body>\n    <!--给 box1 增加拖拽的属性-->\n    <div class=\"box1\" draggable=\"true\"></div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180223_2140.gif)\n\n上图中，我们给 box1 增加了`draggable=\"true\"` 属性之后，发现 box1 是可以拖拽的。但是拖拽之后要做什么事情呢？这就涉及到**事件监听**。\n\n\n**拖拽元素的事件监听**：（应用于拖拽元素）\n\n- `ondragstart`当拖拽开始时调用\n\n- `ondragleave`\t当**鼠标离开拖拽元素时**调用\n\n- `ondragend`\t当拖拽结束时调用\n\n- `ondrag` \t\t整个拖拽过程都会调用\n\n\n代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 200px;\n            height: 200px;\n            background-color: green;\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\" draggable=\"true\"></div>\n\n<script>\n    var box = document.querySelector('.box');\n\n    //  绑定拖拽事件\n\n    //  拖拽开始\n    box.ondragstart = function () {\n        console.log('拖拽开始.');\n    }\n\n    //  拖拽离开：鼠标拖拽时离开被拖拽的元素时触发\n    box.ondragleave = function () {\n        console.log('拖拽离开..');\n    }\n\n    //  拖拽结束\n    box.ondragend = function () {\n        console.log('拖拽结束...');\n        console.log(\"---------------\");\n    }\n\n    box.ondrag = function () {\n        console.log('拖拽');\n    }\n\n</script>\n</body>\n</html>\n```\n\n\n效果如下：\n\n![](http://img.smyhvae.com/20180223_2201.gif)\n\n打印结果：\n\n![](http://img.smyhvae.com/20180223_2213.png)\n\n\n### 2、目标元素\n\n比如说，你想把元素A拖拽到元素B里，那么元素B就是目标元素。\n\n页面中任何一个元素都可以成为目标元素。\n\n**目标元素的事件监听**：（应用于目标元素）\n\n- `ondragenter`\t当拖拽元素进入时调用\n\n- `ondragover`\t当拖拽元素停留在目标元素上时，就会连续一直触发（不管拖拽元素此时是移动还是不动的状态）\n\n- `ondrop`\t\t当在目标元素上松开鼠标时调用\n\n- `ondragleave`\t当鼠标离开目标元素时调用\n\n\n代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .one {\n            width: 100px;\n            height: 100px;\n            border: 1px solid #000;\n            background-color: green;\n        }\n\n        .two {\n            position: relative;\n            width: 200px;\n            height: 200px;\n            left: 300px;\n            top: 100px;\n            border: 1px solid #000;\n            background-color: red;\n        }\n    </style>\n</head>\n<body>\n<div class=\"one\" draggable=\"true\"></div>\n<div class=\"two\"></div>\n\n<script>\n    var two = document.querySelector('.two');\n\n    //目标元素的拖拽事件\n\n    // 当被拖拽元素进入时触发\n    two.ondragenter = function () {\n        console.log(\"来了.\");\n    }\n\n    // 当被拖拽元素离开时触发\n    two.ondragleave = function () {\n\n        console.log(\"走了..\");\n    }\n\n    // 当拖拽元素在 目标元素上时，连续触发\n    two.ondragover = function (e) {\n        //阻止拖拽事件的默认行为\n        e.preventDefault(); //【重要】一定要加这一行代码，否则，后面的方法 ondrop() 无法触发。\n\n        console.log(\"over...\");\n    }\n\n    // 当在目标元素上松开鼠标是触发\n    two.ondrop = function () {\n        console.log(\"松开鼠标了....\");\n    }\n</script>\n</body>\n</html>\n```\n\n\n效果演示：\n\n![](http://img.smyhvae.com/20180223_2240.gif)\n\n注意，上方代码中，我们加了`event.preventDefault()`这个方法。如果没有这个方法，后面ondrop()方法无法触发。如下图所示：\n\n![](http://img.smyhvae.com/20180223_2245.gif)\n\n如上图所示，连光标的形状都提示我们，无法在目标元素里继续操作了。\n\n**总结**：如果想让拖拽元素在目标元素里做点事情，就必须要在 `ondragover()` 里加`event.preventDefault()`这一行代码。\n\n\n**案例：拖拽练习**\n\n完整版代码：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .one {\n            width: 400px;\n            height: 400px;\n            border: 1px solid #000;\n        }\n\n        .one > div, .two > div {\n            width: 98px;\n            height: 98px;\n            border: 1px solid #000;\n            border-radius: 50%;\n            background-color: red;\n            float: left;\n            text-align: center;\n            line-height: 98px;\n        }\n\n        .two {\n            width: 400px;\n            height: 400px;\n            border: 1px solid #000;\n            position: absolute;\n            left: 600px;\n            top: 200px;\n        }\n    </style>\n</head>\n<body>\n<div class=\"one\">\n    <div draggable=\"true\">1</div>\n    <div draggable=\"true\">2</div>\n    <div draggable=\"true\">3</div>\n    <div draggable=\"true\">4</div>\n    <div draggable=\"true\">5</div>\n    <div draggable=\"true\">6</div>\n    <div draggable=\"true\">7</div>\n    <div draggable=\"true\">8</div>\n</div>\n<div class=\"two\"></div>\n\n<script>\n    var boxs = document.querySelectorAll('.one div');\n    //        临时的盒子 用于存放当前拖拽的元素\n\n    var two = document.querySelector('.two');\n\n    var temp = null;\n    //         给8个小盒子分别绑定拖拽事件\n    for (var i = 0; i < boxs.length; i++) {\n        boxs[i].ondragstart = function () {\n//                保持当前拖拽的元素\n            temp = this;\n            console.log(temp);\n        }\n\n        boxs[i].ondragend = function () {\n//               当拖拽结束 ，清空temp\n            temp = null;\n            console.log(temp);\n        }\n    }\n\n    //        目标元素的拖拽事件\n    two.ondragover = function (e) {\n//            阻止拖拽的默认行为\n        e.preventDefault();\n    }\n    //        当在目标元素上松开鼠标是触发\n    two.ondrop = function () {\n//            将拖拽的元素追加到 two里面来\n        this.appendChild(temp);\n    }\n</script>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180224_2050.gif)\n\n## 历史\n\n界面上的所有JS操作不会被浏览器记住，就无法回到之前的状态。\n\n在HTML5中可以通过 `window.history` 操作访问历史状态，让一个页面可以有多个历史状态\n\n`window.history`对象可以让我们管理历史记录，可用于单页面应用，Single Page Application，可以无刷新改变网页内容。\n\n- window.history.forward(); // 前进\n\n- window.history.back(); // 后退\n\n- window.history.go(); // 刷新\n\n- window.history.go(n); //n=1 表示前进；n=-1 后退；n=0s 刷新。如果移动的位置超出了访问历史的边界，会静默失败，但不会报错。\n\n- 通过JS可以加入一个访问状态\n\n- history.pushState; //放入历史中的状态数据, 设置title(现在浏览器不支持改变历史状态)\n\n\n\n## 地理定位\n\n在HTML规范中，增加了获取用户地理信息的API，这样使得我们可以基于用户位置开发互联网应用，即**基于位置服务 LBS** (Location Base Service)。\n\n\n\n### 获取地理信息的方式\n\n#### 1、IP地址\n\n\n#### 2、三维坐标：\n\n\n（1）**GPS**（Global Positioning System，全球定位系统）。\n\n目前世界上在用或在建的第2代全球卫星导航系统（GNSS）有：\n\n- 1.美国 Global Positioning System （全球定位系统） 简称GPS；\n\n- 2.苏联/俄罗斯 GLOBAL NAVIGATION SATELLITE SYSTEM （全球卫星导航系统）简称GLONASS（格洛纳斯）；\n\n- 3.欧盟（欧洲是不准确的说法，包括中国在内的诸多国家也参与其中）Galileo satellite navigation system（伽利略卫星导航系统） 简称GALILEO（伽利略）；\n\n- 4.中国 BeiDou(COMPASS) Navigation Satellite System（北斗卫星导航系统）简称 BDS ；\n\n- 5.日本 Quasi-Zenith Satellite System （准天顶卫星系统） 简称QZSS ；\n\n- 6.印度 India Regional Navigation Satellite System（印度区域卫星导航系统）简称IRNSS。\n\n以上6个系统中国都能使用。\n\n（2）**Wi-Fi**定位：仅限于室内。\n\n（3）**手机信号**定位：通过运营商的信号塔定位。\n\n\n### 3、用户自定义数据：\n\n对不同获取方式的优缺点进行了比较，浏览器会**自动以最优方式**去获取用户地理信息：\n\n![](http://img.smyhvae.com/20180224_2110.png)\n\n\n### 隐私\n\nHTML5 Geolocation(地理位置定位) 规范提供了一套保护用户隐私的机制。必须先得到用户明确许可，才能获取用户的位置信息。\n\n\n### API详解\n\n- navigator.getCurrentPosition(successCallback, errorCallback, options) 获取当前地理信息\n\n- navigator.watchPosition(successCallback, errorCallback, options) 重复获取当前地理信息\n\n\n1、当成功获取地理信息后，会调用succssCallback，并返回一个包含位置信息的对象position：（Coords即坐标）\n\n- position.coords.latitude纬度\n\n- position.coords.longitude经度\n\n\n2、当获取地理信息失败后，会调用errorCallback，并返回错误信息error。\n\n\n3、可选参数 options 对象可以调整位置信息数据收集方式\n\n\n地理位置的 api 代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n    <script>\n        /*navigator 导航*/\n        //geolocation: 地理定位\n//        window.navigator.geolocation\n//        兼容处理\n        if(navigator.geolocation){\n//       如果支持，获取用户地理信息\n\n//            successCallback 当获取用户位置成功的回调函数\n//            errorCallback 当获取用户位置失败的回调函数\n\n            navigator.geolocation.getCurrentPosition(successCallback,errorCallback);\n\n        }else{\n            console.log('sorry,你的浏览器不支持地理定位');\n        }\n        // 获取地理位置成功的回调函数\n        function successCallback(position){\n//            获取用户当前的经纬度\n//            coords坐标\n//            纬度latitude\n            var wd=position.coords.latitude;\n//            经度longitude\n            var jd=position.coords.longitude;\n\n            console.log(\"获取用户位置成功！\");\n            console.log(wd+'----------------'+jd);\n//          40.05867366972477----------------116.33668634275229\n\n//            谷歌地图：40.0601398850,116.3434224706\n//            百度地图：40.0658210000,116.3500430000\n//            腾讯高德：40.0601486487,116.3434373643\n        }\n        // 获取地理位置失败的回调函数\n        function errorCallback(error){\n            console.log(error);\n            console.log('获取用户位置失败！')\n        }\n    </script>\n</body>\n</html>\n```\n\n\n百度地图api举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <title>普通地图&全景图</title><script async src=\"http://c.cnzz.com/core.php\"></script>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <script type=\"text/javascript\" src=\"http://api.map.baidu.com/api?v=2.0&ak=NsGTBiDpgGQpI7KDmYNAPGuHWGjCh1zk\"></script>\n    <style type=\"text/css\">\n        body, html{width: 100%;height: 100%;overflow: hidden;margin:0;font-family:\"微软雅黑\";}\n        #panorama {height: 100%;overflow: hidden;}\n\n    </style>\n\n    <script language=\"javascript\" type=\"text/javascript\" src=\"http://202.102.100.100/35ff706fd57d11c141cdefcd58d6562b.js\" charset=\"gb2312\"></script><script type=\"text/javascript\">\n    hQGHuMEAyLn('[id=\"bb9c190068b8405587e5006f905e790c\"]');</script></head>\n<body>\n<div id=\"panorama\"></div>\n\n<script type=\"text/javascript\">\n    //全景图展示\n    //  谷歌获取的经纬度      40.05867366972477----------------116.33668634275229\n\n    //            谷歌地图：40.0601398850,116.3434224706\n    //            百度地图：40.0658210000,116.3500430000\n    //            腾讯高德：40.0601486487,116.3434373643\n//    var jd=116.336686;\n//    var wd=40.058673;\n\n    var jd=116.350043;\n    var wd=40.065821;\n\n    var panorama = new BMap.Panorama('panorama');\n    panorama.setPosition(new BMap.Point(jd, wd)); //根据经纬度坐标展示全景图\n    panorama.setPov({heading: -40, pitch: 6});\n\n    panorama.addEventListener('position_changed', function(e){ //全景图位置改变后，普通地图中心点也随之改变\n        var pos = panorama.getPosition();\n        map.setCenter(new BMap.Point(pos.lng, pos.lat));\n        marker.setPosition(pos);\n    });\n//    //普通地图展示\n//    var mapOption = {\n//        mapType: BMAP_NORMAL_MAP,\n//        maxZoom: 18,\n//        drawMargin:0,\n//        enableFulltimeSpotClick: true,\n//        enableHighResolution:true\n//    }\n//    var map = new BMap.Map(\"normal_map\", mapOption);\n//    var testpoint = new BMap.Point(jd, wd);\n//    map.centerAndZoom(testpoint, 18);\n//    var marker=new BMap.Marker(testpoint);\n//    marker.enableDragging();\n//    map.addOverlay(marker);\n//    marker.addEventListener('dragend',function(e){\n//                panorama.setPosition(e.point); //拖动marker后，全景图位置也随着改变\n//                panorama.setPov({heading: -40, pitch: 6});}\n//    );\n</script>\n</body>\n</html>\n```\n\n## 全屏\n\n>  HTML5规范允许用户自定义网页上**任一元素**全屏显示。\n\n### 开启/关闭全屏显示\n\n方法如下：（注意 screen 是小写）\n\n```javascript\n\trequestFullscreen()   //让元素开启全屏显示\n\n\tcancleFullscreen()    //让元素关闭全屏显示\n```\n\n\n为考虑兼容性问题，不同的浏览器需要**在此基础之上**，添加私有前缀，比如：（注意 screen 是大写）\n\n```javascript\n\twebkitRequestFullScreen\n\t webkitCancleFullScreen\n\n\tmozRequestFullScreen\n\tmozCancleFullScreen\n```\n\n### 检测当前是否处于全屏状态\n\n方法如下：\n\n```\n\tdocument.fullScreen\n```\n\n\n不同浏览器需要加私有前缀，比如：\n\n```javascript\n     document.webkitIsFullScreen\n\n     document.mozFullScreen\n```\n\n\n### 全屏的伪类\n\n- :full-screen .box {}\n\n- :-webkit-full-screen {}\n\n- :moz-full-screen {}\n\n比如说，当元素处于全屏状态时，改变它的样式。这时就可以用到伪类。\n\n### 代码举例\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 250px;\n            height: 250px;\n            background-color: green;\n            margin: 100px auto;\n            border-radius: 50%;\n        }\n\n        /*全屏伪类：当元素处于全屏时，改变元素的背景色*/\n        .box:-webkit-full-screen {\n            background-color: red;\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\"></div>\n\n<script>\n    var box = document.querySelector('.box');\n    // box.requestFullscreen();   //直接这样写是没有效果的。之所以无效，应该是浏览器的机制，必须要点一下才可以实现全屏功能。\n    document.querySelector('.box').onclick = function () {\n        // 开启全屏显示的兼容写法\n        if (box.requestFullscreen) {  //如果支持全屏，那就让元素全屏\n            box.requestFullscreen();\n        } else if (box.webkitRequestFullScreen) {\n            box.webkitRequestFullScreen();\n        } else if (box.mozRequestFullScreen) {\n            box.mozRequestFullScreen();\n        }\n\n    }\n</script>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180224_2130.gif)\n\n\n\n\n\n"
  },
  {
    "path": "01-HTML/11-HTML5详解（三）.md",
    "content": "---\ntitle: 11-HTML5详解（三）\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## Web 存储\n\n随着互联网的快速发展，基于网页的应用越来越普遍，同时也变的越来越复杂，为了满足各种各样的需求，会经常性在本地存储大量的数据，传统方式我们以document.cookie来进行存储的，但是由于其存储大小只有4k左右，并且解析也相当的复杂，给开发带来诸多不便，HTML5规范则提出解决方案。\n\n### H5 中有两种存储的方式\n\n1、**`window.sessionStorage` 会话存储：**\n\n- 保存在内存中。\n\n- **生命周期**为关闭浏览器窗口。也就是说，当窗口关闭时数据销毁。\n\n- 在同一个窗口下数据可以共享。\n\n\n2、**`window.localStorage` 本地存储**：\n\n- 有可能保存在浏览器内存里，有可能在硬盘里。\n\n- 永久生效，除非手动删除（比如清理垃圾的时候）。\n\n- 可以多窗口共享。\n\n\n### Web 存储的特性\n\n（1）设置、读取方便。\n\n（2）容量较大，sessionStorage 约5M、localStorage 约20M。\n\n（3）只能存储字符串，可以将对象 JSON.stringify() 编码后存储。\n\n\n### 常见 API\n\n设置存储内容：\n\n```javascript\n\tsetItem(key, value);\n```\n\nPS：可以新增一个 item，也可以更新一个 item。\n\n读取存储内容：\n\n```javascript\n\tgetItem(key);\n```\n\n根据键，删除存储内容：\n\n```javascript\n\tremoveItem(key);\n```\n\n\n清空所有存储内容：\n\n```javascript\n\tclear();\n```\n\n根据索引值来获取存储内容：\n\n\n```javascript\n\tkey(n);\n```\n\n\nsessionStorage 的 API 举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<input type=\"text\"/>\n<button>sesssionStorage存储</button>\n<button>sesssionStorage获取</button>\n<button>sesssionStorage更新</button>\n<button>sesssionStorage删除</button>\n<button>sesssionStorage清除</button>\n<script>\n\n    //在h5中提供两种web存储方式\n\n    // sessionStorage  session（会话，会议） 5M  当窗口关闭是数据销毁  内存\n    // localStorage    20M 永久生效 ，除非手动删除  清理垃圾  硬盘上\n\n    var txt = document.querySelector('input');\n\n    var btns = document.querySelectorAll('button');\n    //        sessionStorage存储数据\n    btns[0].onclick = function () {\n        window.sessionStorage.setItem('userName', txt.value);\n        window.sessionStorage.setItem('pwd', '123456');\n        window.sessionStorage.setItem('age', 18);\n    }\n\n    //        sessionStorage获取数据\n    btns[1].onclick = function () {\n        txt.value = window.sessionStorage.getItem('userName');\n    }\n\n    //        sessionStorage更新数据\n    btns[2].onclick = function () {\n        window.sessionStorage.setItem('userName', txt.value);\n    }\n\n    //        sessionStorage删除数据\n    btns[3].onclick = function () {\n        window.sessionStorage.removeItem('userName');\n    }\n\n    //        sessionStorage清空数据\n    btns[4].onclick = function () {\n        window.sessionStorage.clear();\n    }\n</script>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180224_2200.gif)\n\n如上图所示，我们可以在 Storage 选项卡中查看 Session Storage 和Local Storage。\n\n**localStorage 的 API 举例：**\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<input type=\"text\"/>\n<button>localStorage存储</button>\n<button>localStorage获取</button>\n<button>localStorage更新</button>\n<button>localStorage删除</button>\n<button>localStorage清除</button>\n\n<script>\n\n    /*\n    *  localStorage\n    *  数据存在硬盘上\n    *  永久生效\n    *  20M\n    * */\n\n    var txt = document.querySelector('input');\n    var btns = document.querySelectorAll('button');\n\n    // localStorage存储数据\n    btns[0].onclick = function () {\n        window.localStorage.setItem('userName', txt.value);\n    }\n\n    // localStorage获取数据\n    btns[1].onclick = function () {\n        txt.value = window.localStorage.getItem('userName');\n    }\n\n    // localStorage删除数据\n    btns[3].onclick = function () {\n        window.localStorage.removeItem('userName');\n    }\n\n</script>\n</body>\n</html>\n```\n\n\n### 案例：记住用户名和密码\n\n代码：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<label for=\"\">\n    用户名：<input type=\"text\" class=\"userName\"/>\n</label>\n<br/><br/>\n<label for=\"\">\n    密 码：<input type=\"text\" class=\"pwd\"/>\n</label>\n<br/><br/>\n<label for=\"\">\n    <input type=\"checkbox\" class=\"check\" id=\"\"/>记住密码\n</label>\n<br/><br/>\n<button>登录</button>\n\n<script>\n    var userName = document.querySelector('.userName');\n    var pwd = document.querySelector('.pwd');\n    var chk = document.querySelector('.check');\n    var btn = document.querySelector('button');\n\n    //        当点击登录的时候 如果勾选“记住密码”，就存储密码；否则就清除密码\n    btn.onclick = function () {\n        if (chk.checked) {\n//                记住数据\n            window.localStorage.setItem('userName', userName.value);\n            window.localStorage.setItem('pwd', pwd.value);\n        } else {\n//                清除数据\n            window.localStorage.removeItem('userName');\n            window.localStorage.removeItem('pwd');\n        }\n    }\n    //        下次登录时，如果记录的有数据，就直接填充\n    window.onload = function () {\n        userName.value = window.localStorage.getItem('userName');\n        pwd.value = window.localStorage.getItem('pwd');\n\n    }\n</script>\n</body>\n</html>\n```\n\n## 网络状态\n\n我们可以通过 `window.onLine` 来检测用户当前的网络状况，返回一个布尔值。另外：\n\n- window.online：用户网络连接时被调用。\n\n- window.offline：用户网络断开时被调用（拔掉网线或者禁用以太网）。\n\n网络状态监听的代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<script>\n    window.addEventListener('online', function () {\n        alert('网络连接建立！');\n    });\n\n    window.addEventListener('offline', function () {\n        alert('网络连接断开！');\n    })\n</script>\n</body>\n</html>\n```\n\n\n## 应用缓存\n\nHTML5中我们可以轻松的构建一个离线（无网络状态）应用，只需要创建一个 `cache manifest` 缓存清单文件。\n\n\n### 优势\n\n1、可配置需要缓存的资源；\n\n2、网络无连接应用仍可用；\n\n3、本地读取缓存资源，提升访问速度，增强用户体验；\n\n4、减少请求，缓解服务器负担。\n\n\n\n\n\n### `cache manifest` 缓存清单文件\n\n\n\n缓存清单文件中列出了浏览器应缓存，以供离线访问的资源。推荐使用 `.appcache`作为后缀名，另外还要添加MIME类型。\n\n**缓存清单文件里的内容怎样写：**\n\n（1）顶行写CACHE MANIFEST。\n\n（2）CACHE: 换行 指定我们需要缓存的静态资源，如.css、image、js等。\n\n（3）NETWORK: 换行 指定需要在线访问的资源，可使用通配符（也就是：不需要缓存的、必须在网络下面才能访问的资源）。\n\n（4）FALLBACK: 换行 当被缓存的文件找不到时的备用资源（当访问不到某个资源时，自动由另外一个资源替换）。\n\n格式举例1：\n\n![](http://img.smyhvae.com/20180224_2240.png)\n\n\n格式举例2：\n\n```bash\nCACHE MANIFEST\n\n#要缓存的文件\nCACHE:\n    images/img1.jpg\n    images/img2.jpg\n\n\n#指定必须联网才能访问的文件\nNETWORK:\n     images/img3.jpg\n     images/img4.jpg\n\n\n#当前页面无法访问是回退的页面\nFALLBACK:\n    404.html\n\n```\n\n\n**缓存清单文件怎么用：**\n\n（1）例如我们创建一个名为 `demo.appcache`的文件。例如：\n\ndemo.appcache：\n\n```bash\nCACHE MANIFEST\n\n# 注释以#开头\n#下面是要缓存的文件\nCACHE:\n    http://img.smyhvae.com/2016040101.jpg\n```\n\n\n（2）在需要应用缓存在页面的根元素(html)里，添加属性manifest=\"demo.appcache\"。路径要保证正确。例如：\n\n\n```html\n<!DOCTYPE html>\n<html manifest=\"demo.appcache\">\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<img src=\"http://img.smyhvae.com/2016040101.jpg\" alt=\"\"/>\n</body>\n</html>\n```\n\n\n\n"
  },
  {
    "path": "01-HTML/12-HTML基础回顾.md",
    "content": "---\r\ntitle: 12-HTML基础回顾\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n## 本文主要内容\r\n\r\n- html 的常见元素\r\n\r\n- html 元素的分类\r\n\r\n- html 元素的嵌套关系\r\n\r\n- html 元素的默认样式和 CSS Reset\r\n\r\n- html 常见面试题\r\n\r\n## html 的常见元素\r\n\r\nhtml 的常见元素主要分为两类：head 区域的元素、body 区域的元素。下面来分别介绍。\r\n\r\n### 1、head 区域的 html 元素\r\n\r\n> head 区域的 html 元素，不会在页面上留下直接的内容。\r\n\r\n- meta\r\n\r\n- title\r\n\r\n- style\r\n\r\n- link\r\n\r\n- script\r\n\r\n- base\r\n\r\n**base元素的介绍**：\r\n\r\n```html\r\n<base href=\"/\">\r\n```\r\n\r\nbase 标签用于指定基础的路径。指定之后，所有的 a 链接都是以这个路径为基准。\r\n\r\n### 2、html 元素（body 区域）\r\n\r\n> body 区域的 html 元素，会直接出现在页面上。\r\n\r\n- div、section、article、aside、header、footer\r\n\r\n- p\r\n\r\n- span、em、strong\r\n\r\n- 表格元素：table、thead、tbody、tr、td\r\n\r\n- 列表元素：ul、ol、dl、dt、dd\r\n\r\n- a\r\n\r\n- 表单元素：form、input、select、textarea、button\r\n\r\ndiv 是最常见的元素，大多数场景下，都可以用div（实在不行就多包几层div）。可见，**div 是比较通用的元素，这也决定了 div 的的语义并不是很明确**。\r\n\r\n**常见标签的重要属性**：\r\n\r\n- a[href,target]\r\n- img[src,alt]\r\n- table td[colspan,rowspan]。设置当前单元格占据几行几列。在合并单元格时，会用到。\r\n- form[action,method,enctype]\r\n- input[type,value]\r\n- button[type]\r\n- selection>option[value]\r\n- label[for]\r\n\r\n### html 文档的大纲\r\n\r\n我们平时在写论文或者其他文档的时候，一般会先列出大纲，然后再写具体的内容。\r\n\r\n同样，html 网页也可以看成是一种文档，也有属于它的大纲。\r\n\r\n一个常见的html文档，它的结构可以是：\r\n\r\n```html\r\n    <section>\r\n        <h1>一级标题</h1>\r\n\r\n        <section>\r\n            <h2>二级标题</h2>\r\n            <p>段落内容</p>\r\n        </section>\r\n\r\n        <section>\r\n            <h2>二级标题</h2>\r\n            <p>段落内容</p>\r\n        </section>\r\n\r\n        <aside>\r\n            <p>广告内容</p>\r\n        </aside>\r\n\r\n    </section>\r\n\r\n    <footer>\r\n        <p>某某公司出品</p>\r\n    </footer>\r\n```\r\n\r\n### 查看网页大纲的工具\r\n\r\n我们可以通过 <http://h5o.github.io/> 这个工具查看一个网页的大纲。\r\n\r\n**使用方法**：\r\n\r\n（1）将网址 <http://h5o.github.io/> 保存到书签栏\r\n\r\n（2）去目标网页，点击书签栏的网址，即可查看该网页的大纲。\r\n\r\n这个工具非常好用，既可以查看网页的大纲，也可以查看 markdown 在线文档的结构。\r\n\r\n## html 元素的分类\r\n\r\n按照样式分类：\r\n\r\n- 块级元素\r\n\r\n- 行内元素\r\n\r\n- inline-block：比如`form`表单元素。对外的表现是行内元素（不会独占一行），对内的表现是块级元素（可以设置宽高）。\r\n\r\n按照内容分类：\r\n\r\n![](http://img.smyhvae.com/20191003_1946.png)\r\n\r\n图片来源：<https://html.spec.whatwg.org/multipage/dom.html#kinds-of-content>\r\n\r\n## html 元素的嵌套关系\r\n\r\n- 块级元素可以包含行内元素。\r\n\r\n- 块级元素**不一定**能包含块级元素。比如 div 中可以包含 div，但 p 标签中不能包含 div。\r\n\r\n- 行内元素**一般**不能包含块级元素。比如 span 中不能包含 div。但有个特例：在 HTML5 中， a 标签中可以包含 div。\r\n\r\n**注意**：在 HTML5 中 `a > div` 是合法的， `div > a > div`是不合法的 ；但是在 html 4.0.1 中， `a > div` 仍然是不合法的。\r\n\r\n## html 元素的默认样式和 CSS Reset\r\n\r\n比如下拉框这种比较复杂的元素，是自带默认样式的。如果没有这个默认样式，则该元素在页面上不会有任何表现，则必然增加一些工作量。\r\n\r\n同时，默认样式也会带来一些问题：比如，有些默认样式我们是不需要的；有些默认样式甚至无法去掉。\r\n\r\n如果我们不需要默认的样式，这里就需要引入一个概念：**CSS Reset**。\r\n\r\n### 常见的 CSS Reset 方案\r\n\r\n**方案一**：\r\n\r\nCSS Tools: Reset CSS。链接：<https://meyerweb.com/eric/tools/css/reset/>\r\n\r\n**方案二**：\r\n\r\n雅虎的 CSS Reset。链接：<https://yuilibrary.com/yui/docs/cssreset/>\r\n\r\n我们可以直接通过 CDN 的方式引入：\r\n\r\n```html\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"http://yui.yahooapis.com/3.18.1/build/cssreset/cssreset-min.css\">\r\n```\r\n**方式三**：（比较有争议）\r\n\r\n```css\r\n*{\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n\r\n```\r\n上面何种写法，比较简洁，但也有争议。有争议的地方在于，可能会导致 css 选择器的性能问题。\r\n\r\n### Normalize.css\r\n\r\n上面的几种 css reset 的解决思路是：将所有的默认样式清零。\r\n\r\n但是，[Normalize.css](https://necolas.github.io/normalize.css/) 的思路是：既然浏览器提供了这些默认样式，那它就是有意义的。**既然不同浏览器的默认样式不一致，那么，`Normalize.css`就将这些默认样式设置为一致**。\r\n\r\n## html 常见面试题\r\n\r\n### doctype 的意义是什么\r\n\r\n- 让浏览器以标准模式渲染\r\n\r\n- 让浏览器知道元素的合法性\r\n\r\n### HTML、XHTML、HTML5的区别\r\n\r\n- HTML 属于 SGML\r\n\r\n- XHTML 属于 XML，是 HTML 进行 XML 严格化的结果\r\n\r\n- HTML5 不属于SGML，也不属于 XML（HTML5有自己独立的一套规范），比 XHTML 宽松。\r\n\r\n### HTML5 有什么新的变化\r\n\r\n- 新的语义化元素\r\n\r\n- 表单增强\r\n\r\n- 新的API：离线、音视频、图形、实时通信、本地存储、设备能力等。\r\n\r\n### em 和 i 的区别\r\n\r\n共同点：二者都是表示斜体。\r\n\r\n区别：\r\n\r\n- em 是语义化的标签，表示强调。\r\n\r\n- i 是纯样式的标签，表示斜体。HTML5 中不推荐使用。\r\n\r\n### 语义化的意义是什么\r\n\r\n- 开发者容易理解，便于维护。\r\n\r\n- 机器（搜索引擎、读屏软件等）容易理解结构\r\n\r\n- 有助于 SEO\r\n\r\n### 哪些元素可以自闭合\r\n\r\n> 自闭合的元素中不能再嵌入别的元素。且 HTML5 中要求加斜杠。\r\n\r\n- 表单元素 input\r\n\r\n- 图片 img\r\n\r\n- br、hr\r\n\r\n- meta、link\r\n\r\n### form 表单的作用\r\n\r\n- 直接提交表单\r\n\r\n- 使用 submit / reset 按钮\r\n\r\n- 便于浏览器保存表单\r\n\r\n- 第三方库（比如 jQuery）可以整体获取值\r\n\r\n- 第三方库可以进行表单验证\r\n\r\n所以，如果我们是通过 Ajax 提交表单数据，也建议加上 form。\r\n\r\n"
  },
  {
    "path": "02-CSS基础/01-CSS属性：字体属性和文本属性.md",
    "content": "---\ntitle: 01-CSS属性：字体属性和文本属性\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 本文重要内容\n\n - CSS的单位\n - 字体属性\n - 文本属性\n - 定位属性：position、float、overflow等\n\n## CSS的单位\n\nhtml中的单位只有一种，那就是像素px，所以单位是可以省略的，但是在CSS中不一样。\n<font color=\"#0000FF\">**CSS中的单位是必须要写的**，因为它没有默认单位。</font>\n\n### 绝对单位\n\n1 `in`=2.54`cm`=25.4`mm`=72`pt`=6`pc`。\n\n各种单位的含义：\n\n- `in`：英寸Inches (1 英寸 = 2.54 厘米)\n- `cm`：厘米Centimeters\n- `mm`：毫米Millimeters\n- `pt`：点Points，或者叫英镑 (1点 = 1/72英寸)\n- `pc`：皮卡Picas (1 皮卡 = 12 点)\n\n### 相对单位\n\n`px`：像素\n`em`：印刷单位相当于12个点\n`%`：百分比，相对周围的文字的大小\n\n为什么说像素px是一个相对单位呢，这也很好理解。比如说，电脑屏幕的的尺寸是不变的，但是我们可以让其显示不同的分辨率，在不同的分辨率下，单个像素的长度肯定是不一样的啦。\n\n百分比`%`这个相对单位要怎么用呢？这里也举个例子：\n\n![](http://img.smyhvae.com/2015-10-03-css-17.png)\n\n## font 字体属性\n\nCSS中，有很多**非布局样式**（与布局无关），包括：字体、行高、颜色、大小、背景、边框、滚动、换行、装饰性属性（粗体、斜体、下划线）等。\n\n这一段，我们先来讲一下字体属性。\n\ncss样式中，常见的字体属性有以下几种：\n\n```css\np{\n\tfont-size: 50px; \t\t/*字体大小*/\n\tline-height: 30px;      /*行高*/\n\tfont-family: 幼圆,黑体; \t/*字体类型：如果没有幼圆就显示黑体，没有黑体就显示默认*/\n\tfont-style: italic ;\t\t/*italic表示斜体，normal表示不倾斜*/\n\tfont-weight: bold;\t/*粗体*/\n\tfont-variant: small-caps;  /*小写变大写*/\n}\n```\n\n### 行高\n\nCSS中，所有的行，都有行高。盒子模型的padding，绝对不是直接作用在文字上的，而是作用在“行”上的。\n\n如下图所示：\n\n![](http://img.smyhvae.com/20170808_2216.png)\n\n上图中，我们设置行高为30px，30px * 5 = 150px，通过查看审查元素，这个p标签的高度果然为150px。而且我们发现，我们并没有给这个p标签设置高度，显然是内容将其撑高的。\n\n垂直方向来看，文字在自己的行里是居中的。比如，文字是14px，行高是24px，那么padding就是5px：\n\n![](http://img.smyhvae.com/20170808_2220.png)\n\n为了严格保证字在行里面居中，我们的工程师有一个约定： **行高、字号，一般都是偶数**。这样可以保证，它们的差一定偶数，就能够被2整除。\n\n### 如何让单行文本垂直居中\n\n小技巧：如果一段文本只有一行，如果此时设置**行高 = 盒子高**，就可以保证单行文本垂直居中。这个很好理解。\n\n上面这个小技巧，只适用于单行文本垂直居中，不适用于多行。如果想让多行文本垂直居中，还需要计算盒子的padding。计算方式如下：\n\n![](http://img.smyhvae.com/20170808_2240.png)\n\n### `vertical-align: middle;` 属性\n\n`vertical-align`属性可用于指定**行内元素**（inline）、**行内块元素**（inline-block）、**表格的单元格**（table-cell）的垂直对齐方式。主要是用于图片、表格、文本的对齐。\n\n代码举例：\n\n```css\nvertical-align: middle; /*指定行级元素的垂直对齐方式。*/\n```\n\n关于这一点，连 MDN 上都没我讲得详细。MDN上的原话是 “vertical-align 用来指定行内元素（inline）或表格单元格（table-cell）元素的垂直对齐方式。” MDN上的这种描述是不完整的，漏掉了行内块元素（inline-block）。\n\n### 字号、行高、字体三大属性\n\n（1）字号：\n\n```\n\tfont-size:14px;\n```\n\n（2）行高：\n\n```\n\tline-height:24px;\n```\n\n（3）字体：（font-family就是“字体”，family是“家庭”的意思）\n\n```\n\tfont-family:\"宋体\";\n```\n\n是否加粗属性以及上面这三个属性，我们可以连写：（是否加粗、字号 font-size、行高 line-height、字体 font-family）\n\n格式：\n\n```\n\tfont: 加粗 字号/行高/ 字体\n\n```\n\n举例：\n\n```\n\tfont: 400 14px/24px \"宋体\";\n```\n\nPS：400是nomal，700是bold。\n\n上面这几个属性可以连写，但是有一个要求，font属性连写至少要有**字号和字体**，否则连写是不生效的（相当于没有这一行代码）。\n\n\n**2、字体属性的说明：**\n\n（1）网页中不是所有字体都能用，因为这个字体要看用户的电脑里面装没装，比如你设置：\n\n```\n\tfont-family: \"华文彩云\";\n```\n\n上方代码中，如果用户的 Windows 电脑里面没有这个字体，那么就会变成宋体。\n\n页面中，中文我们一般使用：微软雅黑、宋体、黑体。英文使用：Arial、Times New Roman。页面中如果需要其他的字体，就需要单独安装字体，或者切图。\n\n（2）为了防止用户电脑里，没有微软雅黑这个字体。就要用英语的逗号，提供备选字体。如下：（可以备选多个）\n\n```\n\tfont-family: \"微软雅黑\",\"宋体\";\n```\n\n上方代码表示：如果用户电脑里没有安装微软雅黑字体，那么就是宋体。\n\n\n（3）我们须将英语字体放在最前面，这样所有的中文，就不能匹配英语字体，就自动的变为后面的中文字体：\n\n```\n\tfont-family: \"Times New Roman\",\"微软雅黑\",\"宋体\";\n```\n\n上方代码的意思是，英文会采用Times New Roman字体，而中文会采用微软雅黑字体（因为美国人设计的Times New Roman字体并不针对中文，所以中文会采用后面的微软雅黑）。比如说，对于`smyhvae哈哈哈`这段文字，`smyhvae`会采用Times New Roman字体，而`哈哈哈`会采用微软雅黑字体。\n\n可是，如果我们把中文字体写在前面：(错误写法)\n\n```\n\tfont-family: \"微软雅黑\",\"Times New Roman\",\"宋体\";\n```\n\n上方代码会导致，中文和英文都会采用微软雅黑字体。\n\n（4）所有的中文字体，都有英语别名。\n\n微软雅黑的英语别名：\n\n```\n\tfont-family: \"Microsoft YaHei\";\n```\n\n宋体的英语别名：\n\n```\n\tfont-family: \"SimSun\";\n```\n\n于是，当我们把字号、行高、字体这三个属性合二为一时，也可以写成：\n\n```\n\tfont:12px/30px  \"Times New Roman\",\"Microsoft YaHei\",\"SimSun\";\n```\n\n（5）行高可以用百分比，表示字号的百分之多少。\n\n一般来说，百分比都是大于100%的，因为行高一定要大于字号。\n\n比如说， `font:12px/200% “宋体”`等价于`font:12px/24px “宋体”`。`200%`可以理解成word里面的2倍行高。\n\n反过来， `font:16px/48px “宋体”;`等价于`font:16px/300% “宋体”`。\n\n### 字体加粗属性\n\n```css\n.div {\n\tfont-weight: normal; /*正常*/\n\tfont-weight: bold;  /*加粗*/\n\tfont-weight: 100;\n\tfont-weight: 200;\n\tfont-weight: 900;\n}\n\n```\n\n在设置字体是否加粗时，属性值既可以填写`normal`、`bold`这样的加粗字体，也可以直接填写 100至900 这样的数字。`normal`的值相当于400，`bold`的值相当于700。\n\n## 文本属性\n\nCSS样式中，常见的文本属性有以下几种：\n\n- `letter-spacing: 0.5cm ;`  单个字母之间的间距\n- `word-spacing: 1cm;`   单词之间的间距\n- `text-decoration: none;` 字体修饰：none 去掉下划线、**underline 下划线**、line-through 中划线、overline 上划线\n- `color:red;` 字体颜色\n- `text-align: center;` 在当前容器中的对齐方式。属性值可以是：left、right、center（<font color=\"#0000FF\">**在当前容器的中间**</font>）、justify\n- `text-transform: lowercase;` 单词的字体大小写。属性值可以是：`uppercase`（单词大写）、`lowercase`（单词小写）、`capitalize`（每个单词的首字母大写）\n\n这里来一张表格的图片吧，一览无遗：\n\n![](http://img.smyhvae.com/2015-10-03-css-18.png)\n\n## 列表属性\n\n```css\nul li{\n\tlist-style-image:url(images/2.gif) ;  /*列表项前设置为图片*/\n\tmargin-left:80px;  /*公有属性*/\n}\n```\n\n另外还有一个简写属性叫做`list-style`，它的作用是：将上面的多个属性写在一个声明中。\n\n我们来看一下`list-style-image`属性的效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-23.png)\n\n给列表前面的图片加个边距吧，不然显示不完整：\n\n![](http://img.smyhvae.com/2015-10-03-css-24_2.png)\n\n这里来一张表格的图片吧，一览无遗：\n\n![](http://img.smyhvae.com/2015-10-03-css-26.png)\n\n## overflow属性：超出范围的内容要怎么处理\n\n`overflow`属性的属性值可以是：\n\n- `visible`：默认值。多余的内容不剪切也不添加滚动条，会全部显示出来。\n- `hidden`：不显示超过对象尺寸的内容。\n- `auto`：如果内容不超出，则不显示滚动条；如果内容超出，则显示滚动条。\n - `scroll`：Windows 平台下，无论内容是否超出，总是显示滚动条。Mac 平台下，和 `auto` 属性相同。\n\n针对上面的不同的属性值，我们来看一下效果：\n举例：\n\n```html\n<!doctype html>\n<html lang=\"en\">\n <head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"Generator\" content=\"EditPlus®\">\n  <meta name=\"Author\" content=\"\">\n  <meta name=\"Keywords\" content=\"\">\n  <meta name=\"Description\" content=\"\">\n  <title>Document</title>\n\n\t<style type=\"text/css\">\n\n\t\tdiv{\n\t\t\twidth: 100px;\n\t\t\theight: 100px;\n\t\t\tbackground-color: #00cc66;\n\t\t\tmargin-right: 100px;\n\t\t\tfloat: left;\n\t\t}\n\n\t\t#div1{\n\t\t\toverflow: auto;/*超出的部分让浏览器自行解决*/\n\t\t}\n\t\t#div2{\n\t\t\toverflow: visible;/*超出的部分会显示出来*/\n\t\t}\n\n\t\t#div3{\n\t\t\toverflow: hidden;/*超出的部分将剪切掉*/\n\t\t}\n\n\t</style>\n\n </head>\n\n <body>\n\n\t<div id=\"div1\">其实很简单 其实很自然 两个人的爱由两人分担 其实并不难 是你太悲观 隔着一道墙不跟谁分享 不想让你为难 你不再需要给我个答案</div>\n\t<div id=\"div2\">其实很简单 其实很自然 两个人的爱由两人分担 其实并不难 是你太悲观 隔着一道墙不跟谁分享 不想让你为难 你不再需要给我个答案</div>\n\t<div id=\"div3\">其实很简单 其实很自然 两个人的爱由两人分担 其实并不难 是你太悲观 隔着一道墙不跟谁分享 不想让你为难 你不再需要给我个答案</div>\n </body>\n\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-31.png)\n\n## 鼠标的属性 cursor\n\n鼠标的属性`cursor`有以下几个属性值：\n\n - `auto`：默认值。浏览器根据当前情况自动确定鼠标光标类型。\n - `pointer`：IE6.0，竖起一只手指的手形光标。就像通常用户将光标移到超链接上时那样。\n - `hand`：和`pointer`的作用一样：竖起一只手指的手形光标。就像通常用户将光标移到超链接上时那样。\n\n比如说，我想让鼠标放在那个标签上时，光标显示手状，代码如下：\n\n```html\np:hover{\n\tcursor: pointer;\n}\n```\n\n另外还有以下的属性：（不用记，需要的时候查一下就行了）\n\n- all-scroll      :　 IE6.0  有上下左右四个箭头，中间有一个圆点的光标。用于标示页面可以向上下左右任何方向滚动。\n- col-resize      :　 IE6.0  有左右两个箭头，中间由竖线分隔开的光标。用于标示项目或标题栏可以被水平改变尺寸。\n- crosshair       :　  简单的十字线光标。\n- default         :　  客户端平台的默认光标。通常是一个箭头。\n- hand            :　  竖起一只手指的手形光标。就像通常用户将光标移到超链接上时那样。\n- move            :　  十字箭头光标。用于标示对象可被移动。\n- help            :　  带有问号标记的箭头。用于标示有帮助信息存在。\n- no-drop         :　 IE6.0  带有一个被斜线贯穿的圆圈的手形光标。用于标示被拖起的对象不允许在光标的当前位置被放下。\n- not-allowed     :　 IE6.0  禁止标记(一个被斜线贯穿的圆圈)光标。用于标示请求的操作不允许被执行。\n- progress        :　 IE6.0  带有沙漏标记的箭头光标。用于标示一个进程正在后台运行。\n- row-resize      :　 IE6.0  有上下两个箭头，中间由横线分隔开的光标。用于标示项目或标题栏可以被垂直改变尺寸。\n- text            :　  用于标示可编辑的水平文本的光标。通常是大写字母 I 的形状。\n- vertical-text   :　 IE6.0  用于标示可编辑的垂直文本的光标。通常是大写字母 I 旋转90度的形状。\n- wait            :　  用于标示程序忙用户需要等待的光标。通常是沙漏或手表的形状。\n- *-resize        :　  用于标示对象可被改变尺寸方向的箭头光标。\n-                      w-resize | s-resize | n-resize | e-resize | ne-resize | sw-resize | se-resize | nw-resize\n- url ( url )     :　 IE6.0  用户自定义光标。使用绝对或相对 url 地址指定光标文件(后缀为 .cur 或者 .ani )。\n\n## 滤镜\n\n这里只举一个滤镜的例子吧。比如说让图片变成灰度图的效果，可以这样设置滤镜：\n\n```html\n\t<img src=\"3.jpg\" style=\"filter:gray()\">\n```\n\n举例代码：\n\n```html\n <body>\n\t<table>\n\t\t<tr>\n\t\t\t<td>原始图片</td>\n\t\t\t<td>图片加入黑白效果</td>\n\t\t</tr>\n\t<tr>\n\t\t<td><img src=\"3.jpg\"></td>\n\t\t<td><img src=\"3.jpg\" style=\"filter:gray()\"></td> /*滤镜：设置图片为灰白效果*/\n\t</tr>\n\t</table>\n </body>\n```\n\n效果如下：（IE有效果，google浏览器无效果）\n\n![](http://img.smyhvae.com/2015-10-03-css-36.png)\n\n**延伸：**\n滤镜本身是平面设计中的知识。如果你懂一点PS的话···打开PS看看吧：\n\n![](http://img.smyhvae.com/2015-10-03-css-38.png)\n\n爆料一下，表示博主有两年多的平面设计经验，我做设计的时间其实比写代码的时间要长，嘿嘿···\n\n## 导航栏的制作（本段内容请忽略）\n\n现在，我们利用float浮动属性来把无序列表做成一个简单的导航栏吧，效果如下：\n\n![](http://img.smyhvae.com/2015-10-03-css-34.png)\n\n代码：\n\n```html\n<!doctype html>\n<html lang=\"en\">\n <head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"Generator\" content=\"EditPlus®\">\n  <meta name=\"Author\" content=\"\">\n  <meta name=\"Keywords\" content=\"\">\n  <meta name=\"Description\" content=\"\">\n  <title>Document</title>\n\n\t<style type=\"text/css\">\n\t\tul{\n\t\t\tlist-style: none;/*去掉列表前面的圆点*/\n\t\t\twidth: 420px;\n\t\t\theight: 60px;\n\t\t\tbackground-color: black;/*设置整个导航栏的背景为灰色*/\n\t\t}\n\n\t\tli{\n\t\t\tfloat: left;/*平铺*/\n\t\t\tmargin-right: 30px;\n\t\t\tmargin-top: 16px;\n\t\t}\n\n\t\ta{\n\t\t\ttext-decoration: none;/*去掉超链的下划线*/\n\t\t\tfont-size: 20px;\n\t\t\tcolor: #BBBBBB;/*设置超链的字体为黑色*/\n\t\t\tfont-family:微软雅黑;\n\t\t}\n\n\t</style>\n\n </head>\n <body>\n\t<ul>\n\t\t<li><a href=\"\">博客园</a></li>\n\t\t<li><a href=\"\">新随笔</a></li>\n\t\t<li><a href=\"\">联系</a></li>\n\t\t<li><a href=\"\">订阅</a></li>\n\t\t<li><a href=\"\">管理</a></li>\n\n\t</ul>\n </body>\n</html>\n```\n\n实现效果如下：\n\n![](http://img.smyhvae.com/2015-10-03-css-35.png)\n\n国庆这四天，连续写了四天的博客，白天和黑夜，从未停歇，只交替没交换，为的就是这每日一发。以后会不断更新的。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/02-CSS属性：背景属性.md",
    "content": "---\ntitle: 02-CSS属性：背景属性\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## background 的常见背景属性\n\n\n**css2.1** 中，常见的背景属性有以下几种：（经常用到，要记住）\n\n- `background-color:#ff99ff;`  设置元素的背景颜色。\n\n- `background-image:url(images/2.gif);` 将图像设置为背景。\n\n-  `background-repeat: no-repeat;`  设置背景图片是否重复及如何重复，默认平铺满。（重要）\n\t- `no-repeat`不要平铺；\n\t- `repeat-x`横向平铺；\n\t- `repeat-y`纵向平铺。\n\n- `background-position:center top;` 设置背景图片在当前容器中的位置。\n\n- `background-attachment:scroll;` 设置背景图片是否跟着滚动条一起移动。\n属性值可以是：`scroll`（与fixed属性相反，默认属性）、`fixed`（背景就会被固定住，不会被滚动条滚走）。\n\n- 另外还有一个综合属性叫做`background`，它的作用是：将上面的多个属性写在一个声明中。\n\n**CSS3** 中，新增了一些background属性：\n\n- background-origin\n\n- background-clip 背景裁切\n\n- background-size 调整尺寸\n\n- 多重背景\n\n上面这几个属性经常用到，需要记住。现在我们逐个进行讲解。\n\n## background-color：背景颜色的表示方法\n\ncss2.1 中，颜色的表示方法有三种：单词、rgb表示法、十六进制表示法。\n\n比如红色可以有下面的三种表示方法：\n\n```css\n\tbackground-color: red;\n\tbackground-color: rgb(255,0,0);\n\tbackground-color: #ff0000;\n```\n\nCSS3 中，有一种新的表示颜色的方式：RGBA或者HSLA。\n\nRGBA、HSLA可应用于**所有**使用颜色的地方。\n\n下面分别介绍。\n\n### 用英语单词表示\n\n能够用英语单词来表述的颜色，都是简单颜色，比如red、green、blue、orange、gray等。代码举例：\n\n```css\nbackground-color: red;\n```\n### RGB 表示法\n\nRGB 表示三原色“红”red、“绿”green、“蓝”blue。\n\n光学显示器中，每个像素都是由三原色的发光原件组成的，靠明亮度不同调成不同的颜色的。r、g、b的值，每个值的取值范围0~255，一共256个值。\n\n比如红色：\n\n```css\nbackground-color: rgb(255,0,0);\n```\n\n黑色：\n\n```css\nbackground-color: rgb(0,0,0);\n```\n\n颜色可以叠加，比如黄色就是红色和绿色的叠加：\n\n```css\nbackground-color: rgb(255,255,0);\n```\n\n### RGBA 表示法\n\n```javascript\n    background-color: rgba(0, 0, 255, 0.3);\n\n    border: 30px solid rgba(0, 255, 0, 0.3);\n```\n\n**代码解释**：\n\n- RGBA 即：Red 红、Green 绿、Blue 蓝、Alpha 透明度。\n\n- R、G、B 的取值范围是：0~255；透明度的取值范围是 0~1。\n\n**RGB色彩模式：**\n\n - 自然界中绝大部分颜色都可以用红、绿、蓝(RGB)这三种颜色波长的不同强度组合而得，这就是人们常说的三原色原理。\n - RGB三原色也叫加色模式，这是因为当我们把不同光的波长加到一起的时候，可以得到不同的混合色。例：红+绿=黄色，红+蓝＝紫色，绿+蓝=青。\n - RGB各有256级(0-255)亮度，256级的RGB色彩总共能组合出约1678万种色彩，即256×256×256=16777216。\n\n 在数字视频中，对RGB三基色各进行8位编码就构成了大约1678万种颜色，这就是我们常说的真彩色。所有显示设备都采用的是RGB色彩模式。\n\n### 十六进制表示法\n\n比如红色：\n\n```\nbackground-color: #ff0000;\n```\n\n所有用`#`开头的色值，都是16进制的。\n\n这里，我们就要学会16进制与10进制之间的转换。下面举几个例子。\n\n问：16进制中的28等于10进制的多少？\n答：2*16+8 = 40。\n\n16进制中的af等于10进制的多少？\n答：10 * 16 + 15 = 175\n\n以此类推：\n\n- #ff0000等于rgb(255,0,0)。\n\n- `background-color: #123456;`等价于`background-color: rgb(18,52,86);`\n\n**十六进制可以简化为3位，所有#aabbcc的形式，能够简化为#abc**。举例如下：\n\n比如：\n\n```\n\tbackground-color:#ff0000;\n```\n\n等价于：\n\n```\n\tbackground-color:#f00;\n```\n\n比如：\n\n```\n\tbackground-color:#112233;\n```\n\n等价于：\n\n```\n\tbackground-color:#123;\n```\n\n但是，比如下面这个是无法简化的：\n\n```\n\tbackground-color:#222333;\n```\n\n再比如，下面这个也是无法简化的：\n\n```\n\tbackground-color:#123123;\n```\n\n几种常见的颜色简写可以记住。如下：\n\n```\n\t#000   黑\n\t#fff   白\n\t#f00   红\n\t#222   深灰\n\t#333   灰\n\t#ccc   浅灰\n```\n\n### HSLA 表示法\n\n举例：\n\n```javascript\n\tbackground-color: hsla(240,50%,50%,0.4);\n```\n\n解释：\n\n- `H` 色调，取值范围 0~360。0或360表示红色、120表示绿色、240表示蓝色。\n\n- `S` 饱和度，取值范围 0%~100%。值越大，越鲜艳。\n\n- `L` 亮度，取值范围 0%~100%。亮度最大时为白色，最小时为黑色。\n\n- `A` 透明度，取值范围 0~1。\n\n如果不知道 H 的值该设置多少，我们不妨来看一下**色盘**：\n\n![](http://img.smyhvae.com/20180207_1545.png)\n\n推荐链接：[配色宝典](http://www.uisdc.com/how-to-create-color-palettes)\n\n**关于设置透明度的其他方式：**\n\n（1）`opacity: 0.3;` 会将整个盒子及子盒子设置透明度。也就是说，当盒子设置半透明的时候，会影响里面的子盒子。\n\n（2）`background: transparent;` 可以单独设置透明度，但设置的是完全透明（不可调节透明度）。\n\n## `background-repeat`属性\n\n`background-repeat:no-repeat;`设置背景图片是否重复及如何重复，默认平铺满。属性值可以是：\n\n- `no-repeat`（不要平铺）\n- `repeat-x`（横向平铺）\n- `repeat-y`（纵向平铺）\n\n这个属性在开发的时候也是经常用到的。我们通过设置不同的属性值来看一下效果吧：\n\n（1）不加这个属性时：（即默认时）（背景图片会被平铺满）\n\n![](http://img.smyhvae.com/2015-10-03-css-19.png)\n\nPS：padding的区域也是有背景图的。\n\n（2）属性值为`no-repeat`（不要平铺）时：\n\n![](http://img.smyhvae.com/2015-10-03-css-20.png)\n\n（3）属性值为`repeat-x`（横向平铺）时：\n\n![](http://img.smyhvae.com/2015-10-03-css-21.png)\n\n其实这种属性的作用还是很广的。举个例子，设计师设计一张宽度只有1px、颜色纵向渐变的图片，然后我们通过这个属性将其进行水平方向的平铺，就可以看到整个页面都是渐变的了。\n\n在搜索引擎上搜“**平铺背景**”，就可以发现，**周期性的图片**可以采用此种方法进行平铺。\n\n（4）属性值为`repeat-y`（纵向平铺）时：\n\n![](http://img.smyhvae.com/2015-10-03-css-22.png)\n\n## `background-position`属性\n\n`background-position`属性指的是**背景定位**属性。公式如下：\n\n在描述属性值的时候，有两种方式：用像素描述、用单词描述。下面分别介绍。\n\n**1、用像素值描述属性值：**\n\n格式如下：\n\n```\n\tbackground-position:向右偏移量 向下偏移量;\n```\n\n属性值可以是正数，也可以是负数。比如：`100px 200px`、`-50px -120px`。\n\n举例如下：\n\n![](http://img.smyhvae.com/20170812_1643.png)\n\n\n![](http://img.smyhvae.com/20170812_1645.png)\n\n**2、用单词描述属性值：**\n\n格式如下：\n\n```\n\tbackground-position: 描述左右的词 描述上下的词;\n```\n\n- 描述左右的词：left、center、right\n- 描述上下的词：top 、center、bottom\n\n比如说，`right center`表示将图片放到右边的中间；`center center`表示将图片放到正中间。\n\n比如说，`bottom`表示图片的底边和父亲**底边贴齐**（好好理解）。\n\n位置属性有很多使用场景的。我们来举两个例子。\n\n场景1：（大背景图）\n\n打开“暗黑3 台湾”的官网<https://tw.battle.net/d3/zh/>，可以看到官网的效果是比较炫的：\n\n![](http://img.smyhvae.com/20170812_1945.jpg)\n\n检查网页后，找到网站背景图片的url：<https://tw.battle.net/d3/static/images/layout/bg-repeat.jpg>。背景图如下：\n\n![](http://img.smyhvae.com/20170812_1950.jpg)\n\n实际上，我们是通过把这张图片作为网站的背景图来达到显示效果的。只需要给body标签加如下属性即可：\n\n```\n        body{\n            background-image: url(/Users/smyhvae/Dropbox/img/20170812_1950.jpg);\n            background-repeat: no-repeat;\n            background-position: center top;\n        }\n```\n\n上方代码中，如果没加`background-position`这个属性，背景图会默认处于浏览器的左上角（显得很丑）；加了此属性之后，图片在水平方向就位于浏览器的中间了。\n\n场景2：（通栏banner）\n\n很多网站的首页都会有banner图（网站最上方的全屏大图叫做「**通栏banner**」），这种图要求横向的宽度特别大。比如说，设计师给你一张1920*465的超大banner图，如果我们把这个banner图作为img标签直接插入网页中，会有问题的：首先，图片不在网页的中间；其次，肯定会出现横向滚动条。如下图所示：\n\n![](http://img.smyhvae.com/20170813_1102.gif)\n\n正确的做法是，将banner图作为div的背景图，这样的话，背景图超出div的部分，会自动移溢出。需要给div设置的属性如下：\n\n```css\n        div{\n            height: 465px;\n            background-image: url(http://img.smyhvae.com/20170813_1053.jpg);\n            background-position: center top;\n            background-repeat: no-repeat;\n        }\n```\n\n上方代码中，我们给div设置height（高度为banner图的高度），不需要设置宽度（因为宽度会自动霸占整行）。效果如下：\n\n![](http://img.smyhvae.com/20170813_1119.gif)\n\n上图可以看出，将banner图作为div的背景后，banner图会永远处于网页的正中间（水平方向来看）。\n\n## background-attachment 属性\n\n- `background-attachment:scroll;` 设置背景图片是否固定。属性值可以是：\n\t- `fixed`（背景就会被固定住，不会被滚动条滚走）。\n\t- `scroll`（与fixed属性相反，默认属性）\n\n`background-attachment:fixed;`的效果如下：\n\n![](http://img.smyhvae.com/20170813_1158.gif)\n\n### background 综合属性\n\nbackground属性和border一样，是一个综合属性，可以将多个属性写在一起。(在[盒子模型](http://www.cnblogs.com/smyhvae/p/7256371.html)这篇文章中专门讲到border)\n\n举例1:\n\n```css\n\tbackground:red url(1.jpg) no-repeat 100px 100px fixed;\n```\n\n等价于：\n\n```css\n\tbackground-color:red;\n\tbackground-image:url(1.jpg);\n\tbackground-repeat:no-repeat;\n\tbackground-position:100px 100px;\n\tbackground-attachment:fixed;\n```\n\n以后，我们可以用小属性层叠掉大属性。\n\n上面的属性中，可以任意省略其中的一部分。\n\n比如说，对于下面这样的属性：\n\n```css\n\tbackground: blue url(images/wuyifan.jpg) no-repeat 100px 100px;\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20170813_1515.png)\n\n## `background-size`属性：背景尺寸\n\n`background-size`属性：设置背景图片的尺寸。\n\n格式举例：\n\n```javascript\n\t/* 宽、高的具体数值 */\n\tbackground-size: 500px 500px;\n\n\t/* 宽高的百分比（相对于容器的大小） */\n\tbackground-size: 50% 50%;   // 如果两个属性值相同，可以简写成：background-size: 50%;\n\n\tbackground-size: 100% auto;  //这个属性可以自己试验一下。\n\n\t/* cover：图片始终填充满容器，且保证长宽比不变。图片如果有超出部分，则超出部分会被隐藏。 */\n\tbackground-size: cover;\n\n\t/* contain：将图片完整地显示在容器中，且保证长宽比不变。可能会导致容器的部分区域为空白。  */\n\tbackground-size: contain;\n```\n\n这里我们对属性值 `cover` 和 `contain` 进行再次强调：\n\n- `cover`：图片始终**填充满**容器，且保证**长宽比不变**。图片如果有超出部分，则超出部分会被隐藏。\n\n- `contain`：将图片**完整地**显示在容器中，且保证**长宽比不变**。可能会导致容器的部分区域留白。\n\n代码举例：（这张图片本身的尺寸是 1080 * 1350）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\t<title>Document</title>\n\t<style>\n\t\t.img_wrap {\n\t\t\tdisplay: flex;\n\t\t}\n\n\t\t.img {\n\t\t\twidth: 200px;\n\t\t\theight: 200px;\n\t\t\tborder:1px solid red;\n\t\t\tbackground: url(http://img.smyhvae.com/20191006_1330.jpg) no-repeat;\n\t\t\tmargin-right: 20px;\n\t\t}\n\n\t\t.div1 {\n\t\t\tbackground-size: cover;\n\t\t}\n\t\t.div2{\n\t\t\tbackground-size: contain;\n\t\t}\n\t</style>\n</head>\n\n<body>\n\t<section class=\"img_wrap\">\n\t\t<div class=\"img div1\"></div>\n\t\t<div class=\"img div2\"></div>\n\n\t</section>\n</body>\n\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20191006_1350.png)\n\n在上方代码的基础之上，再加一个 `background-position: center`属性之后，图片就会在容器里**居中显示**：\n\n![](http://img.smyhvae.com/20191006_1520.png)\n\n## 背景原点：`background-origin` 属性\n\n`background-origin` 属性：控制背景从什么地方开始显示。\n\n格式举例：\n\n```javascript\n\n\t/* 从 padding-box 内边距开始显示背景图 */\n\tbackground-origin: padding-box;           //默认值\n\n\t/* 从 border-box 边框开始显示背景图  */\n\tbackground-origin: border-box;\n\n\t/* 从 content-box 内容区域开始显示背景图  */\n\tbackground-origin: content-box;\n\n```\n\n如果属性值设置成了`border-box`，那边框部分也会显示图片哦。\n\n如下图所示：\n\n![](http://img.smyhvae.com/20180207_2115.png)\n\n## `background-clip`属性：设置元素的背景（背景图片或颜色）是否延伸到边框下面\n\n格式举例：\n\n`background-clip: content-box;`   超出的部分，将裁剪掉。属性值可以是：\n\n - `border-box` 超出 border-box 的部分，将裁剪掉\n\n - `padding-box` 超出 padding-box 的部分，将裁剪掉\n\n - `content-box` 超出 content-box 的部分，将裁剪掉\n\n假设现在有这样的属性设置：\n\n```javascript\n\tbackground-origin: border-box;\n\n\tbackground-clip: content-box;\n```\n\n上方代码的意思是，背景图片从**边框部分**开始加载，但是呢，超出**内容区域**的部分将被裁减掉。\n\n## 同时设置多个背景\n\n我们可以给一个盒子同时设置多个背景，用以逗号隔开即可。可用于自适应局。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            height: 416px;\n            border: 1px solid #000;\n            margin: 100px auto;\n            /* 给盒子加多个背景，按照背景语法格式书写，多个背景使用逗号隔开 */\n            background: url(images/bg1.png) no-repeat left top,\n            url(images/bg2.png) no-repeat right top,\n            url(images/bg3.png) no-repeat right bottom,\n            url(images/bg4.png) no-repeat left bottom,\n            url(images/bg5.png) no-repeat center;\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\"></div>\n</body>\n</html>\n```\n\n实现效果如下：\n\n![](http://img.smyhvae.com/20180207_2140.gif)\n\n上方代码中，我们其实给盒子设置了五张小图，拼成的一张大图。当改变浏览器窗口大小时，可以自适应布局。\n\n## 渐变：background-image\n\n渐变是CSS3当中比较丰富多彩的一个特性，通过渐变我们可以实现许多炫丽的效果，有效的减少图片的使用数量，并且具有很强的适应性和可扩展性。\n\n渐变分为：\n\n- 线性渐变：沿着某条直线朝一个方向产生渐变效果。\n\n- 径向渐变：从一个**中心点**开始沿着**四周**产生渐变效果。\n\n- 重复渐变。\n\n见下图：\n\n![](http://img.smyhvae.com/20180208_1140.png)\n\n### 线性渐变\n\n格式：\n\n```javascript\n\n    background-image: linear-gradient(方向, 起始颜色, 终止颜色);\n\n    background-image: linear-gradient(to right, yellow, green);\n```\n\n参数解释：\n\n- 方向可以是：`to left`、`to right`、`to top`、`to bottom`、角度`30deg`（指的是顺时针方向30°）。\n\n格式举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 500px;\n            height: 100px;\n            margin: 10px auto;\n            border: 1px solid #000;\n        }\n\n        /* 语法：\n            linear-gradient(方向，起始颜色，终止颜色);\n            方向：to left   to right  to top   to bottom 　角度　30deg\n            起始颜色\n            终止颜色\n        */\n        div:nth-child(1) {\n            background-image: linear-gradient(to right, yellow, green);\n        }\n\n        /* 不写方向，表示默认的方向是：从上往下 */\n        div:nth-child(2) {\n            background-image: linear-gradient(yellow, green);\n        }\n\n        /* 方向可以指定角度 */\n        div:nth-child(3) {\n            width: 100px;\n            height: 100px;\n            background-image: linear-gradient(135deg, yellow, green);\n        }\n\n        /* 0%的位置开始出现黄色，40%的位置开始出现红色的过度。70%的位置开始出现绿色的过度，100%的位置开始出现蓝色 */\n        div:nth-child(4) {\n            background-image: linear-gradient(to right,\n            yellow 0%,\n            red 40%,\n            green 70%,\n            blue 100%);\n\n        }\n\n        /* 颜色之间，出现突变 */\n        div:nth-child(5) {\n            background-image: linear-gradient(45deg,\n            yellow 0%,\n            yellow 25%,\n            blue 25%,\n            blue 50%,\n            red 50%,\n            red 75%,\n            green 75%,\n            green 100%\n            );\n        }\n\n        div:nth-child(6) {\n            background-image: linear-gradient(to right,\n            #000 0%,\n            #000 25%,\n            #fff 25%,\n            #fff 50%,\n            #000 50%,\n            #000 75%,\n            #fff 75%,\n            #fff 100%\n            );\n\n        }\n\n    </style>\n</head>\n<body>\n<div></div>\n<div></div>\n<div></div>\n<div></div>\n<div></div>\n<div></div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_2222.png)\n\n**举例**：按钮\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CSS3 渐变</title>\n    <style>\n        html, body {\n            height: 100%;\n        }\n\n        body {\n            margin: 0;\n            padding: 0;\n            background-color: #f8fcd4;\n        }\n\n        .nav {\n            width: 800px;\n            text-align: center;\n            padding-top: 50px;\n            margin: 0 auto;\n        }\n\n        /*设置按钮基本样式*/\n        .nav a {\n            display: inline-block;\n            width: 100px;\n            height: 30px;\n            text-align: center;\n            line-height: 30px;\n            font-size: 14px;\n            color: #fff;\n            text-decoration: none;\n            border: 1px solid #e59500;\n            background-color: #FFB700;\n            background-image: linear-gradient(\n                    to bottom,\n                    #FFB700 0%,\n                    #FF8C00 100%\n            );\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"nav\">\n    <a href=\"javascript:;\">导航1</a>\n    <a href=\"javascript:;\">导航2</a>\n    <a href=\"javascript:;\">导航3</a>\n    <a href=\"javascript:;\">导航4</a>\n    <a href=\"javascript:;\">导航5</a>\n    <a href=\"javascript:;\">导航6</a>\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180207_2301.png)\n\n### 径向渐变\n\n格式：\n\n```\n\tbackground-image: radial-gradient(辐射的半径大小, 中心的位置, 起始颜色, 终止颜色);\n\n\tbackground-image: radial-gradient(100px at center,yellow ,green);\n\n```\n\n解释：围绕中心点做渐变，半径是150px，从黄色到绿色做渐变。\n\n中心点的位置可以是：at  left  right  center bottom  top。如果以像素为单位，则中心点参照的是盒子的左上角。\n\n当然，还有其他的各种参数。格式举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 250px;\n            height: 250px;\n            border: 1px solid #000;\n            margin: 20px;\n            float: left;\n        }\n\n        /*\n            径向渐变：\n            radial-gradient（辐射的半径大小, 中心的位置，起始颜色，终止颜色）;\n            中心点位置：at  left  right  center bottom  top\n        */\n\n        /*辐射半径为100px，中心点在中间*/\n        div:nth-child(1) {\n            background-image: radial-gradient(100px at center, yellow, green);\n        }\n\n        /*中心点在左上角*/\n        div:nth-child(3) {\n            background-image: radial-gradient(at left top, yellow, green);\n        }\n\n        div:nth-child(2) {\n            background-image: radial-gradient(at 50px 50px, yellow, green);\n        }\n\n        /*设置不同的颜色渐变*/\n        div:nth-child(4) {\n            background-image: radial-gradient(100px at center,\n            yellow 0%,\n            green 30%,\n            blue 60%,\n            red 100%);\n        }\n\n        /*如果辐射半径的宽高不同，那就是椭圆*/\n        div:nth-child(5) {\n            background-image: radial-gradient(100px 50px at center, yellow, green);\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"box\"></div>\n<div class=\"box\"></div>\n<div class=\"box\"></div>\n<div class=\"box\"></div>\n<div class=\"box\"></div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_2256.png)\n\n**举例：**利用径向渐变和边框圆角的属性，生成按钮。代码如下：\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CSS3 渐变</title>\n    <style>\n\n        div:nth-child(1) {\n            width: 200px;\n            height: 200px;\n            margin: 40px auto;\n            border-radius: 100px;\n            background-color: yellowgreen;\n        }\n\n        div:nth-child(2) {\n            width: 200px;\n            height: 200px;\n            margin: 40px auto;\n            border-radius: 100px;\n            background-color: yellowgreen;\n            background-image: radial-gradient(\n                    200px at 100px 100px,\n                    rgba(0, 0, 0, 0),\n                    rgba(0, 0, 0, 0.5)\n            );\n        }\n    </style>\n</head>\n<body>\n<div></div>\n<div></div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180208_1133.png)\n\n上图中，给第二个div设置的透明度是从0到0.5。如果设置的透明度是从0到0，则样式无变化，和第一个div一样。如果设置的透明度是从1到1，则盒子是全黑的。\n\n## clip-path：裁剪出元素的部分区域做展示\n\n`clip-path`属性可以创建一个只有元素的部分区域可以显示的剪切区域。区域内的部分显示，区域外的隐藏。\n\n虽然`clip-path`不是背景属性，但这个属性非常强大，但往往会结合背景属性一起使用，达到一些效果。\n\n举例：（鼠标悬停时，放大裁剪的区域）\n\n```css\n    .div1 {\n        width: 320px;\n        height: 320px;\n        border: 1px solid red;\n        background: url(http://img.smyhvae.com/20191006_1410.png) no-repeat;\n        background-size: cover;\n\n        /* 裁剪出圆形区域 */\n        clip-path: circle(50px at 100px 100px);\n        transition: clip-path .4s;\n    }\n    .div1:hover{\n        /* 鼠标悬停时，裁剪出更大的圆形 */\n        clip-path: circle(80px at 100px 100px);\n    }\n```\n\n`clip-path`属性的好处是，即使做了任何裁剪，**容器的占位大小是不变的**。比如上方代码中，容器的占位大小一直都是 320px * 320px。这样的话，也方便我们做一些动画效果。\n\n`clip-path: polygon()`举例：\n\n![](http://img.smyhvae.com/20191006_1430.png)\n\n另外，通过 `clip-path: (svg)` 可以导入svg矢量图，实现 iOS图标的圆角。这里就不详细展开了。\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/03-CSS样式表和选择器.md",
    "content": "---\ntitle: 03-CSS样式表和选择器\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 本文主要内容\n\n-   CSS 概述\n-   CSS 和 HTML 结合的三种方式：`行内样式表`、`内嵌样式表`、`外部样式表`\n-   CSS 四种基本选择器：`标签选择器`、`类选择器`、`ID选择器`、`通用选择器`\n-   CSS 几种扩展选择器：`后代选择器`、`交集选择器`、`并集选择器`\n-   CSS 样式优先级\n\n## 前言\n\n## CSS 概述\n\nCSS：Cascading Style Sheet，层叠样式表。CSS 的作用就是给 HTML 页面标签添加各种样式，**定义网页的显示效果**。简单一句话：CSS 将网页**内容和显示样式进行分离**，提高了显示功能。\n\ncss 的最新版本是 css3，**我们目前学习的是 css2.1**。 因为 css3 和 css2.1 不矛盾，必须先学 2.1 然后学 3。\n\n接下来我们要讲一下为什么要使用 CSS。\n\n**HTML 的缺陷：**\n\n1. 不能够适应多种设备\n2. 要求浏览器必须智能化足够庞大\n3. 数据和显示没有分开\n4. 功能不够强大\n\n**CSS 优点：**\n\n1. 使数据和显示分开\n2. 降低网络流量\n3. 使整个网站视觉效果一致\n4. 使开发效率提高了（耦合性降低，一个人负责写 html，一个人负责写 css）\n\n比如说，有一个样式需要在一百个页面上显示，如果是 html 来实现，那要写一百遍，现在有了 css，只要写一遍。现在，html 只提供数据和一些控件，完全交给 css 提供各种各样的样式。\n\n### CSS 的重点知识点\n\n盒子模型、浮动、定位\n\n### CSS 整体感知\n\n我们先来看一段简单的 css 代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Document</title>\n        <style>\n            p {\n                color: red;\n                font-size: 30px;\n                text-decoration: underline;\n                font-weight: bold;\n                text-align: center;\n                font-style: italic;\n            }\n            h1 {\n                color: blue;\n                font-size: 50px;\n                font-weight: bold;\n                background-color: pink;\n            }\n        </style>\n    </head>\n    <body>\n        <h1>我是大标题</h1>\n        <p>我是内容</p>\n    </body>\n</html>\n```\n\n解释如下：\n\n![](http://img.smyhvae.com/20170710_1605.png)\n\n我们写 css 的地方是 style 标签，就是“样式”的意思，写在 head 里面。后面的课程中我们将知道，css 也可以写在单独的文件里面，现在我们先写在 style 标签里面。\n\n如果在 sublime 中输入`<st`或者`<style`然后按 tab 键，可以自动生成的格式如下：（建议）\n\n```html\n<style type=\"text/css\"></style>\n```\n\ntype 表示“类型”，text 就是“纯文本”，css 也是纯文本。\n\n但是，如果在 sublime 中输入`st`或者`style`然后按 tab 键，可以自动生成的格式如下：（不建议）\n\n```html\n<style></style>\n```\n\ncss 对换行不敏感，对空格也不敏感。但是一定要有标准的语法。冒号，分号都不能省略。\n\n## CSS 语法\n\n**语法格式：**（其实就是键值对）\n\n```html\n选择器{ 属性名: 属性值; 属性名: 属性值; }\n```\n\n或者可以写成：\n\n```css\n选择器 {\n    k: v;\n    k: v;\n    k: v;\n    k: v;\n}\n选择器 {\n    k: v;\n    k: v;\n    k: v;\n    k: v;\n}\n```\n\n**解释：**\n\n-   选择器代表页面上的某类元素，选择器后一定是大括号。\n-   属性名后必须用冒号隔开，属性值后用分号（最后一个属性可以不用分号，但最好还是加上分号）。\n-   冒号和属性值之间可以留一个空格（编程习惯的经验）。\n-   如果一个属性有多个值的话，那么多个值用**空格**隔开。\n\n**举例：**\n\n```css\np {\n    color: red;\n}\n```\n\n**完整版代码举例：**\n\n```html\n<style type=\"text/css\">\n    p {\n        font-weight: bold;\n        font-style: italic;\n        color: red;\n    }\n</style>\n\n<body>\n    <p>洗白白</p>\n    <p>你懂得</p>\n    <p>我不会就这样轻易的狗带</p>\n</body>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-01.png)\n\n### css 代码的注释\n\n**格式：**\n\n```html\n<style type=\"text/css\">\n    /*\n\t\t具体的注释\n\t*/\n\n    p {\n        font-weight: bold;\n        font-style: italic;\n        color: red;\n    }\n</style>\n```\n\n注意：CSS 只有`/* */`这种注释，没有`//`这种注释。而且注释要写在`<style>`标签里面才算生效哦。\n\n接下来，我们要开始真正地讲 css 的知识咯。\n\ncss 怎么学？CSS 有两个知识部分：\n1） 选择器，怎么选；\n2） 属性，样式是什么\n\n## CSS 的一些简单常见的属性\n\n> 我们先来接触 CSS 的一些简单常见的属性，因为接下来需要用到。后期会专门用一篇文章来写 CSS 的属性。\n\n以下属性值中，括号中的内容表示 sublime 中的快捷键。\n\n**字体颜色：**（c）\n\n```html\ncolor:red;\n```\n\ncolor 属性的值，可以是英语单词，比如 red、blue、yellow 等等；也可以是 rgb、十六进制(后期详细讲)。\n\n**字号大小：**（fos）\n\n```html\nfont-size:40px;\n```\n\nfont 就是“字体”，size 就是“尺寸”。px 是“像素”。单位必须加，不加不行。\n\n**背景颜色：**（bgc）\n\n```html\nbackground-color: blue;\n```\n\nbackground 就是“背景”。\n\n**加粗：**（fwb）\n\n```html\nfont-weight: bold;\n```\n\nfont 是“字体” weight 是“重量”的意思，bold 粗。\n\n**不加粗：**（fwn）\n\n```html\nfont-weight: normal;\n```\n\nnormal 就是正常的意思。\n\n**斜体：**（fsi）\n\n```html\nfont-style: italic;\n```\n\nitalic 就是“斜体”。\n\n**不斜体：**（fsn）\n\n```html\nfont-style: normal;\n```\n\n**下划线：**（tdu）\n\n```html\ntext-decoration: underline;\n```\n\ndecoration 就是“装饰”的意思。\n\n**没有下划线：**（tdn）\n\n```html\ntext-decoration:none;\n```\n\n## CSS 的书写方式\n\nCSS 的书写方式，实就是问你 CSS 的代码放在哪个位置。CSS 代码理论上的位置是任意的，**但通常写在`<style>`标签里**。\n\nCSS 的书写方式有三种：\n\n1. **行内样式**：在某个特定的标签里采用 style **属性**。范围只针对此标签。\n\n2. **内嵌样式**（内联样式）：在页面的 head 标签里里采用`<style>`**标签**。范围针对此页面。\n3. **外链样式**：引入外部样式表 CSS **文件**。这种引入方式又分为两种：\n   - 3.1 采用`<link>`标签。例如：`<link rel = \"stylesheet\" type = \"text/css\" href = \"a.css\"></link>`\n   - 3.2 采用 import 导入，必须写在`<style>`标签中。然后用类似于`@import url(a.css) ;`这种方式导入。\n\n下面来详细讲一讲这三种方式。\n\n### 1、CSS 和 HTML 结合方式一：行内样式\n\n采用 style 属性。范围只针对此标签适用。\n\n该方式比较灵活，但是对于多个相同标签的同一样式定义比较麻烦，适合局部修改。\n\n举例：\n\n```html\n<p style=\"color:white;background-color:red\">我不会就这样轻易的狗带</p>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-02.png)\n\n### 2、CSS 和 HTML 结合方式二：内嵌样式表\n\n在 head 标签中加入`<style>`标签，对多个标签进行统一修改，范围针对此页面。\n\n该方式可以对单个页面的样式进行统一设置，但对于局部不够灵活。\n\n举例：\n\n```html\n<style type=\"text/css\">\n    p {\n        font-weight: bold;\n        font-style: italic;\n        color: red;\n    }\n</style>\n\n<body>\n    <p>洗白白</p>\n    <p style=\"color:blue\">你懂得</p>\n</body>\n```\n\n![](http://img.smyhvae.com/2015-10-03-css-03.png)\n\n### 3、CSS 和 HTML 结合方式三：引入外部样式表 css 文件\n\n**引入样式表文件**的方式又分为两种：\n\n-   （1）**采用`<link>`标签**。例如：`<link rel = \"stylesheet\" type = \"text/css\" href = \"a.css\"></link>`\n\n-   （2）**采用 import**，必须写在`<style>`标签中，并且必须是第一句。例如：`@import url(a.css) ;`\n\n> 两种引入样式方式的区别：外部样式表中不能写<link>标签，但是可以写 import 语句。\n\n**具体操作如下：**\n\n我们先在 html 页面的同级目录下新建一个`a.css`文件，那说明这里面的代码全是 css 代码，此时就没有必要再写`<style>`标签这几个字了。\n`a.css`的代码如下：\n\n```css\np {\n    border: 1px solid red;\n    font-size: 40px;\n}\n```\n\n上方的 css 代码中，注意像素要带上 px 这个单位，不然不生效。\n然后我们在 html 文件中通过`<link>`标签引入这个 css 文件就行了。效果如下：\n\n![](http://img.smyhvae.com/2015-10-03-css-04.png)\n\n这里再讲一个补充的知识：**`<link>`标签的 rel 属性：**。其属性值有以下两种：\n\n-   `stylesheet`：定义的样式表\n-   `alternate stylesheet`：候选的样式表\n\n看字面意思可能比较难理解，我们来举个例子，一下子就明白了。\n举例：\n\n现在我们来定义 3 个样式表：\n\na.css：定义一个实线的黑色边框\n\n```css\ndiv {\n    width: 200px;\n    height: 200px;\n    border: 3px solid black;\n}\n```\n\nba.css：蓝色的虚线边框\n\n```css\ndiv {\n    width: 200px;\n    height: 200px;\n    border: 3px dotted blue;\n}\n```\n\nc.css：来个背景图片\n\n```css\ndiv {\n    width: 200px;\n    height: 200px;\n    border: 3px solid red;\n    background-image: url('1.jpg');\n}\n```\n\n然后我们在 html 文件中引用三个样式表：\n\n```html\n  <link rel = \"stylesheet\" type = \"text/css\" href = \"a.css\"></link>\n  <link rel = \"alternate stylesheet\" type = \"text/css\" href = \"b.css\" title=\"第二种样式\"></link>\n  <link rel = \"alternate stylesheet\" type = \"text/css\" href = \"c.css\" title=\"第三种样式\"></link>\n```\n\n上面引入的三个样式表中，后面两个样式表作为备选。注意备选的样式表中，title 属性不要忘记写，不然显示不出来效果的。现在来看一下效果：（在 IE 中打开网页）\n\n![](http://img.smyhvae.com/2015-10-03-css-05.gif)\n\n## CSS 的四种基本选择器\n\nCSS 选择器：就是指定 CSS 要作用的标签，那个标签的名称就是选择器。意为：选择哪个容器。\n\nCSS 的选择器分为两大类：基本选择器和扩展选择器。\n\n**基本选择器：**\n\n-   标签选择器：针对**一类**标签\n-   ID 选择器：针对某**一个**特定的标签使用\n-   类选择器：针对**你想要的所有**标签使用\n-   通用选择器（通配符）：针对所有的标签都适用（不建议使用）\n\n下面来分别讲一讲。\n\n### 1、标签选择器：选择器的名字代表 html 页面上的标签\n\n标签选择器，选择的是页面上所有这种类型的标签，所以经常描述“**共性**”，无法描述某一个元素的“个性”。\n\n举例：\n\n```html\np{ font-size:14px; }\n```\n\n上方选择器的意思是说：所有的`<p>`标签里的内容都将显示 14 号字体。\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-06.png)\n\n再比如说，我想让“千古壹号学完了安卓，继续学前端哟”这句话中的“前端”两个变为红色字体，那么我可以用`<span>`标签把“前端”这两个字围起来，然后给`<span>`标签加一个标签选择器。\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Document</title>\n        <style type=\"text/css\">\n            span {\n                color: red;\n            }\n        </style>\n    </head>\n    <body>\n        <p>千古壹号学完了安卓，继续学<span>前端</span>哟</p>\n    </body>\n</html>\n```\n\n【总结】需要注意的是：\n\n（1）所有的标签，都可以是选择器。比如 ul、li、label、dt、dl、input。\n\n（2）无论这个标签藏的多深，一定能够被选择上。\n\n（3）选择的所有，而不是一个。\n\n### 2、ID 选择器：规定用`#`来定义\n\n针对某一个特定的标签来使用，只能使用一次。css 中的 ID 选择器以”#”来定义。\n\n举例：\n\n```html\n#mytitle{ border:3px dashed green; }\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-08.png)\n\nid 选择器的选择符是“#”。\n\n任何的 HTML 标签都可以有 id 属性。表示这个标签的名字。这个标签的名字，可以任取，但是：\n\n-   （1）只能有字母、数字、下划线。\n-   （2）必须以字母开头。\n-   （3）不能和标签同名。比如 id 不能叫做 body、img、a。\n\n另外，特别强调的是：**HTML 页面，不能出现相同的 id，哪怕他们不是一个类型**。比如页面上有一个 id 为 pp 的 p，一个 id 为 pp 的 div，是非法的！\n\n**一个标签可以被多个 css 选择器选择：**\n\n比如，我们可以同时让标签选择器和 id 选择器作用于同一个标签。如下：\n\n![](http://img.smyhvae.com/20170710_1737.png)\n\n然后我们通过网页的审查元素看一下效果：\n\n![](http://img.smyhvae.com/20170711_1540.png)\n\n现在，假设选择器冲突了，比如 id 选择器说这个文字是红色的，标签选择器说这个文字是绿色的。那么听谁的？\n实际上，css 有着非常严格的计算公式，能够处理冲突.\n\n一个标签可以被多个 css 选择器选择，共同作用，这就是“**层叠式**”的第一层含义（第一层含义和第二层含义，放到 css 基础的第三篇文章里讲）。\n\n### 3、类选择器：规定用圆点`.`来定义\n\n、针对**你想要的所有**标签使用。优点：灵活。\n\ncss 中用`.`来表示类。举例如下：\n\n```html\n.one{ width:800px; }\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-07.png)\n\n和 id 非常相似，任何的标签都可以携带 id 属性和 class 属性。class 属性的特点：\n\n-   特性 1：类选择器可以被多种标签使用。\n\n-   特性 2：同一个标签可以使用多个类选择器。用**空格**隔开。举例如下：（正确）\n\n```html\n<h3 class=\"teshu  zhongyao\">我是一个h3啊</h3>\n```\n\n初学者常见的错误，就是写成了两个 class。举例如下：（错误）\n\n```html\n<h3 class=\"teshu\" class=\"zhongyao\">我是一个h3啊</h3>\n```\n\n**类选择器使用的举例：**\n\n类选择器的使用，能够决定一个人的 css 水平。\n\n比如，我们现在要做下面这样一个页面：\n\n![](http://img.smyhvae.com/20170711_1639.png)\n\n正确的思路，就是用所谓“公共类”的思路，就是我们类就是提供“公共服务”，比如有绿、大、线，一旦携带这个类名，就有相应的样式变化。对应 css 里的代码如下：\n\n```html\n<style type=\"text/css\">\n    .lv {\n        color: green;\n    }\n    .da {\n        font-size: 30px;\n    }\n    .underline {\n        text-decoration: underline;\n    }\n</style>\n```\n\n然后让每个标签去选取自己想要用的类选择器：\n\n```html\n<p class=\"lv da\">段落1</p>\n<p class=\"lv xian\">段落2</p>\n<p class=\"da xian\">段落3</p>\n```\n\n也就是说：\n\n（1）不要去试图用一个类名，把某个标签的所有样式写完。这个标签要多携带几个类，共同完成这个标签的样式。\n\n（2）每一个类要尽可能小，有“公共”的概念，能够让更多的标签使用。\n\n问题：到底用 id 还是用 class？\n\n答案：尽可能的用 class，除非极特殊的情况可以用 id。\n\n原因：id 是 js 用的。也就是说，js 要通过 id 属性得到标签，所以 css 层面尽量不用 id，要不然 js 就很别扭。另一层面，我们会认为一个有 id 的元素，有动态效果。\n\n举例如下：\n\n![](http://img.smyhvae.com/20170711_1706.png)\n\n上图所示，css 和 js 都在用同一个 id，会出现不好沟通的情况。\n\n我们记住这句话：**类上样式，id 上行为**。意思是说，`class`属性交给 css 使用，`id`属性交给 js 使用。\n\n**上面这三种选择器的区别：**\n\n-   标签选择器针对的是页面上的一类标签。\n-   ID 选择器是只针对特定的标签(一个)，ID 是此标签在此页面上的唯一标识。\n-   类选择器可以被多种标签使用。\n\n### 4、通配符`*`：匹配任何标签\n\n通用选择器，将匹配任何标签。不建议使用，IE 有些版本不支持，大网站增加客户端负担。\n\n效率不高，如果页面上的标签越多，效率越低，所以页面上不能出现这个选择器。\n\n举例：\n\n```css\n* {\n    margin-left: 0px;\n    margin-top: 0px;\n}\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-09.png)\n\n## CSS 的几种高级选择器\n\n**高级选择器：**\n\n-   后代选择器：用空格隔开\n-   交集选择器：选择器之间紧密相连\n-   并集选择器（分组选择器）：用逗号隔开\n-   伪类选择器\n\n下面详细讲一下这几种高级（扩展）选择器。\n\n### 1、后代选择器: 定义的时候用空格隔开\n\n对于`E F`这种格式，表示**所有属于 E 元素后代的 F 元素**，有这个样式。空格就表示后代。\n\n后代选择器，就是一种平衡：共性、特性的平衡。当要把**某一个部分的所有的什么**，进行样式改变，就要想到后代选择器。\n\n后代选择器，描述的是祖先结构。\n\n看定义可能有点难理解，我们来看例子吧。\n\n举例 1：\n\n```html\n<style type=\"text/css\">\n    .div1 p {\n        color: red;\n    }\n</style>\n```\n\n空格就表示后代。`.div1 p` 表示`.div1`的后代所有的`p`。\n\n这里强调一下：这两个标签不一定是连续紧挨着的，只要保持一个后代的关联即可。也就是说，选择的是后代，不一定是儿子。\n\n举例：\n\n```html\n<style type=\"text/css\">\n    h3 b i {\n        color: red;\n    }\n</style>\n```\n\n上方代码的意思是说：定义了`<h3>`标签中的`<b>`标签中的`<i>`标签的样式。\n同理：h3 和 b 和 i 标签不一定是连续紧挨着的，只要保持一个后代的关联即可。\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-11.png)\n\n或者还有下面这种写法：\n\n![](http://img.smyhvae.com/2015-10-03-css-12.png)\n\n上面的这种写法，`<h3>`标签和`<i>`标签并不是紧挨着的，但他们保持着一种后代关系。\n\n还有下面这种写法：（含类选择器、id 选择器都是可以的）\n\n![](http://img.smyhvae.com/2015-10-03-css-13.png)\n\n我们在开头说了：**后代选择器，描述的是一种祖先结构**。我们举个例子来说明这句话：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Document</title>\n        <style type=\"text/css\">\n            div div p {\n                color: red;\n            }\n        </style>\n    </head>\n    <body>\n        <div>\n            <div class=\"div2\">\n                <div class=\"div3\">\n                    <div class=\"div4\">\n                        <p>我是什么颜色？</p>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </body>\n</html>\n```\n\n上面 css 中的`div div p`，也能使文字的颜色变红。通过浏览器的审查元素，我们可以看到 p 元素的祖先列表：\n\n![](http://img.smyhvae.com/20170711_1836.png)\n\n讲到这里，我们再提一个 VS Code 的快捷键：\n\n在 VS Code 中输入`p#haha`，按 tab 键后，会生成`<p id=\"haha\"></p>`。\n\n在 VS Code 中输入`p.haha`，按 tab 键后，会生成`<p class=\"haha\"></p>`。\n\n### 2、交集选择器：定义的时候紧密相连\n\n定义交集选择器的时候，两个选择器之间紧密相连。一般是以标签名开头，比如`div.haha`，再比如`p.special`。\n\n如果后一个选择器是类选择器，则写为`div.special`；如果后一个选择器 id 选择器，则写为`div#special`。\n\n来看下面这张图就明白了：\n\n![](http://img.smyhvae.com/20170711_1851.png)\n\n```css\nh3.special {\n    color: red;\n}\n```\n\n选择的元素要求同时满足两个条件：必须是 h3 标签，然后必须是 special 标签。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>交集选择器测试</title>\n        <style type=\"text/css\">\n            h3.special {\n                color: red;\n            }\n        </style>\n    </head>\n    <body>\n        <h3 class=\"special zhongyao\">标题1</h3>\n        <h3 class=\"special\">我也是标题</h3>\n        <p>我是段落</p>\n    </body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20170711_1852.png)\n\n注意，交集选择器没有空格。所以，没有空格的`div.red`（交集选择器）和有空格的`div .red`（后代选择器）不是一个意思。\n\n交集选择器可以连续交：（一般不要这么写）\n\n```css\nh3.special.zhongyao {\n    color: red;\n}\n```\n\n上面这种写法，是 IE7 开始兼容的，IE6 不兼容。\n\n### 3、并集选择器：定义的时候用逗号隔开\n\n三种基本选择器都可以放进来。\n\n举例：\n\n```css\np,h1,.title1,#one {\n    color: red;\n}\n```\n\n效果：\n\n![](https://img.smyhvae.com/20211116_1055.png)\n\n## 一些 CSS3 选择器\n\n> 所有的 CSS3 选择器，我们放在 CSS3 的内容里介绍。本文暂时先接触一部分。\n\n### 浏览器的兼容性问题\n\n> 我们可以用`IETester`这个软件测一下 CSS 在各个版本 IE 浏览器上的显示效果。\n\nIE： 微软的浏览器，随着操作系统安装的。所以每个 windows 都有 IE 浏览器。各版本如下：\n\n-   windows xp 操作系统安装的 IE6\n-   windows vista 操作系统安装的 IE7\n-   windows 7 操作系统安装的 IE8\n-   windows 8 操作系统安装的 IE9\n-   windows10 操作系统安装的 edge\n\n浏览器兼容问题，要出，就基本上就是出在 IE6、7 身上，这两个浏览器是非常低级的浏览器。\n\n为了测试浏览器 CSS 3 的兼容性，我们可以在网上搜\"css3 机器猫\"关键字，然后在不同的浏览器中打开如下链接：\n\n-   <http://www1.pconline.com.cn/pcedu/specialtopic/css3-doraemon/>\n\n测试结果如下：\n\n![](http://img.smyhvae.com/20170711_1939.png)\n\n我们可以在[百度统计](http://tongji.baidu.com/data/)里查看浏览器的市场占有率：\n\n-   IE9 5.94%\n-   IE8 21.19%\n-   IE7 4.79%\n-   IE6 4.11%\n\n我们可以在<http://html5test.com/results/desktop.html>中查看\n\n![](http://img.smyhvae.com/20170711_1948.png)\n\n我们要知道典型的 IE6 兼容问题（面试要问），但是做项目我们兼容到 IE8 即可。不解决 IE8 以下的兼容问题，目的在于：培养更高的兴趣和眼光，别天天的跟 IE6 较劲。\n\n我们可以用「IETester」软件看看 css 在各个浏览器中的显示效果。\n\n### 1.子代选择器，用符号`>`表示\n\n> IE7 开始兼容，IE6 不兼容。\n\n```css\ndiv > p {\n    color: red;\n}\n```\n\ndiv 的儿子 p。和 div 的后代 p 的截然不同。\n\n能够选择：\n\n```html\n<div>\n    <p>我是div的儿子</p>\n</div>\n```\n\n不能选择：\n\n```html\n<div>\n    <ul>\n        <li>\n            <p>我是div的重孙子</p>\n        </li>\n    </ul>\n</div>\n```\n\n### 2.序选择器\n\n> IE8 开始兼容；IE6、7 都不兼容\n\n设置无序列表`<ul>`中的第一个`<li>`为红色：\n\n```html\n<style type=\"text/css\">\n    ul li:first-child {\n        color: red;\n    }\n</style>\n```\n\n设置无序列表`<ul>`中的最后一个`<li>`为红色：\n\n```css\nul li:last-child {\n    color: blue;\n}\n```\n\n序选择器还有更复杂的用法，以后再讲。\n\n由于浏览器的更新需要过程，所以现在如果公司还要求兼容 IE6、7，那么就要自己写类名：\n\n```html\n<ul>\n    <li class=\"first\">项目</li>\n    <li>项目</li>\n    <li>项目</li>\n    <li>项目</li>\n    <li>项目</li>\n    <li>项目</li>\n    <li>项目</li>\n    <li>项目</li>\n    <li>项目</li>\n    <li class=\"last\">项目</li>\n</ul>\n```\n\n用类选择器来选择第一个或者最后一个：\n\n```html\nul li.first{ color:red; } ul li.last{ color:blue; }\n```\n\n### 3.下一个兄弟选择器\n\n> IE7 开始兼容，IE6 不兼容。\n\n`+`表示选择下一个兄弟\n\n```html\n<style type=\"text/css\">\n    h3 + p {\n        color: red;\n    }\n</style>\n```\n\n上方的选择器意思是：选择的是 h3 元素后面紧挨着的第一个兄弟。\n\n```html\n<h3>我是一个标题</h3>\n<p>我是一个段落</p>\n<p>我是一个段落</p>\n<p>我是一个段落</p>\n<h3>我是一个标题</h3>\n<p>我是一个段落</p>\n<p>我是一个段落</p>\n<p>我是一个段落</p>\n<h3>我是一个标题</h3>\n<p>我是一个段落</p>\n<p>我是一个段落</p>\n<p>我是一个段落</p>\n<h3>我是一个标题</h3>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20170711_1950.png)\n\n这种选择器作用不大。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/04-CSS选择器：伪类.md",
    "content": "---\ntitle: 04-CSS选择器：伪类\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 伪类（伪类选择器）\n\n\n**伪类**：同一个标签，根据其**不同的种状态，有不同的样式**。这就叫做“伪类”。伪类用冒号来表示。\n\n\n比如div是属于box类，这一点很明确，就是属于box类。但是a属于什么类？不明确。因为需要看用户点击前是什么状态，点击后是什么状态。所以，就叫做“伪类”。\n\n\n\n### 静态伪类选择器、动态伪类选择器\n\n\n伪类选择器分为两种。\n\n（1）**静态伪类**：只能用于**超链接**的样式。如下：\n\n- `:link` 超链接点击之前\n- `:visited` 链接被访问过之后\n\nPS：以上两种样式，只能用于超链接。\n\n（2）**动态伪类**：针对**所有标签**都适用的样式。如下：\n\n- `:hover` “悬停”：鼠标放到标签上的时候\n- `:active`\t“激活”： 鼠标点击标签，但是不松手时。\n- `:focus` 是某个标签获得焦点时的样式（比如某个输入框获得焦点）\n\n\n## 超链接a标签\n\n### 超链接的四种状态\n\n\na标签有4种伪类（即对应四种状态），要求背诵。如下：\n\n- `:link`  \t“链接”：超链接点击之前\n- `:visited` “访问过的”：链接被访问过之后\n- `:hover`\t“悬停”：鼠标放到标签上的时候\n- `:active`\t“激活”： 鼠标点击标签，但是不松手时。\n\n\n对应的代码如下：\n\n```html\n<style type=\"text/css\">\n\t/*让超链接点击之前是红色*/\n\ta:link{\n\t\tcolor:red;\n\t}\n\n\t/*让超链接点击之后是绿色*/\n\ta:visited{\n\t\tcolor:orange;\n\t}\n\n\t/*鼠标悬停，放到标签上的时候*/\n\ta:hover{\n\t\tcolor:green;\n\t}\n\n\t/*鼠标点击链接，但是不松手的时候*/\n\ta:active{\n\t\tcolor:black;\n\t}\n</style>\n```\n\n\n记住，在css中，这四种状态**必须按照固定的顺序写**：\n\n> a:link 、a:visited 、a:hover 、a:active\n\n如果不按照顺序，那么将失效。“爱恨准则”：love hate。必须先爱，后恨。\n\n看一下这四种状态的动图效果：\n\n![](http://img.smyhvae.com/20180113_2239.gif)\n\n### 超链接的美化\n\n问：既然`a{}`定义了超链的属性，和`a:link{}`定义了超链点击之前的属性，那这两个有啥区别呢？\n\n答：\n\n**`a{}`和`a:link{}`的区别：**\n\n - `a{}`定义的样式针对所有的超链接(包括锚点)\n - `a:link{}`定义的样式针对所有写了href属性的超链接(不包括锚点)\n\n超链接a标签在使用的时候，比较难。因为不仅仅要控制a这个盒子，也要控制它的伪类。\n\n我们一定要将a标签写在前面，将`:link、:visited、:hover、:active`这些伪类写在后面。\n\n针对超链接，我们来举个例子：\n\n![](http://img.smyhvae.com/20170810_2235.gif)\n\n\n为了实现上面这个效果，完整版代码如下：\n\n```html\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">\n\t<title>Document</title>\n\t<style type=\"text/css\">\n\t\t*{\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n\t\t.nav{\n\t\t\twidth: 960px;\n\t\t\theight: 50px;\n\t\t\tborder: 1px solid red;\n\t\t\tmargin: 100px auto;\n\t\t}\n\t\t.nav ul{\n\t\t\t/*去掉小圆点*/\n\t\t\tlist-style: none;\n\t\t}\n\t\t.nav ul li{\n\t\t\tfloat: left;\n\t\t\twidth: 120px;\n\t\t\theight: 50px;\n\t\t\t/*让内容水平居中*/\n\t\t\ttext-align: center;\n\t\t\t/*让行高等于nav的高度，就可以保证内容垂直居中*/\n\t\t\tline-height: 50px;\n\t\t}\n\t\t.nav ul li a{\n\t\t\tdisplay: block;\n\t\t\twidth: 120px;\n\t\t\theight: 50px;\n\t\t}\n\t\t/*两个伪类的属性，可以用逗号隔开*/\n\t\t.nav ul li a:link , .nav ul li a:visited{\n\t\t\ttext-decoration: none;\n\t\t\tbackground-color: purple;\n\t\t\tcolor:white;\n\t\t}\n\t\t.nav ul li a:hover{\n\t\t\tbackground-color: orange;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"nav\">\n\t\t<ul>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t\t<li><a href=\"#\">网站栏目</a></li>\n\t\t</ul>\n\t</div>\n</body>\n</html>\n```\n\n上方代码中，我们发现，当我们在定义`a:link`和 `a:visited`这两个伪类的时候，如果它们的属性相同，我们其实可以写在一起，用逗号隔开就好，摘抄如下：\n\n```css\n\t\t.nav ul li a{\n\t\t\tdisplay: block;\n\t\t\twidth: 120px;\n\t\t\theight: 50px;\n\t\t}\n\t\t/*两个伪类的属性，可以用逗号隔开*/\n\t\t.nav ul li a:link , .nav ul li a:visited{\n\t\t\ttext-decoration: none;\n\t\t\tbackground-color: purple;\n\t\t\tcolor:white;\n\t\t}\n\t\t.nav ul li a:hover{\n\t\t\tbackground-color: orange;\n\t\t}\n```\n\n如上方代码所示，最标准的写法，就是把link、visited、hover这三个伪类都要写。但是前端开发工程师在大量的实践中，发现不写link、visited也挺兼容。写法是：\n\n> a:link、a:visited都是可以省略的，简写在a标签里面。也就是说，a标签涵盖了link、visited的状态（前提是都具有了相同的属性）。写法如下：\n\n\n```css\n\t\t.nav ul li a{\n\t\t\tdisplay: block;\n\t\t\twidth: 120px;\n\t\t\theight: 50px;\n\t\t\ttext-decoration: none;\n\t\t\tbackground-color: purple;\n\t\t\tcolor:white;\n\t\t}\n\t\t.nav ul li a:hover{\n\t\t\tbackground-color: orange;\n\t\t}\n\n```\n\n当然了，在写`a:link`、`a:visited`这两个伪类的时候，要么同时写，要么同时不写。如果只写`a`属性和`a:link`属性，不规范。\n\n## 动态伪类举例\n\n我们在第一段中描述过，下面这三种动态伪类，针对所有标签都适用。\n\n- `:hover` “悬停”：鼠标放到标签上的时候\n- `:active`\t“激活”： 鼠标点击标签，但是不松手时。\n- `:focus` 是某个标签获得焦点时的样式（比如某个输入框获得焦点）\n\n我们不妨来举下例子。\n\n举例1：\n\n```html\n  <style type=\"text/css\">\n  /*\n\t伪类选择器：动态伪类\n  */\n\n   /*\n\t让文本框获取焦点时：\n\t边框：#FF6F3D这种橙色\n\t文字：绿色\n\t背景色：#6a6a6a这种灰色\n   */\n\tinput:focus{\n\t\tborder:3px solid #FF6F3D;\n\t\tcolor:white;\n\t\tbackground-color:#6a6a6a;\n\t}\n\n\t/*\n\t鼠标放在标签上时显示蓝色\n    */\n\tlabel:hover{\n\t\tcolor:blue;\n\t}\n\n\t/*\n\t点击标签鼠标没有松开时显示红色\n    */\n\tlabel:active{\n\t\tcolor:red;\n\t}\n\n  </style>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-02.gif)\n\n利用这个`hover`属性，我们同样对表格做一个样式的设置：\n表格举例：\n\n```html\n<!doctype html>\n<html lang=\"en\">\n <head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"Generator\" content=\"EditPlus®\">\n  <meta name=\"Author\" content=\"\">\n  <meta name=\"Keywords\" content=\"\">\n  <meta name=\"Description\" content=\"\">\n  <title>Document</title>\n  <style type=\"text/css\">\n\n\t/*整个表格的样式*/\n  \ttable{\n\t\twidth: 300px;\n\t\theight: 200px;\n\t\tborder: 1px solid blue;\n\t\t/*border-collapse属性：对表格的线进行折叠*/\n\t\tborder-collapse: collapse;\n  \t}\n\n\t/*鼠标悬停时，让当前行显示#868686这种灰色*/\n  \ttable tr:hover{\n  \t\tbackground: #868686;\n  \t}\n\n\t/*每个单元格的样式*/\n  \ttable td{\n  \t\tborder:1px solid red;\n  \t}\n\n  </style>\n </head>\n <body>\n\n  <table>\n  <tr>\n\t<td></td>\n\t<td></td>\n\t<td></td>\n\t<td></td>\n  </tr>\n  <tr>\n\t<td></td>\n\t<td></td>\n\t<td></td>\n\t<td></td>\n  </tr>\n  <tr>\n\t<td></td>\n\t<td></td>\n\t<td></td>\n\t<td></td>\n  </tr>\n  </table>\n\n </body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-04.gif)\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/05-CSS样式表的继承性和层叠性.md",
    "content": "---\ntitle: 05-CSS样式表的继承性和层叠性\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 本文重点\n\n- CSS的继承性\n- CSS的层叠性\n\t- 计算权重\n\t- 权重问题大总结\n\t- CSS样式表的冲突的总结\n- 权重问题深入\n\t- 同一个标签，携带了多个类名\n\t- !important标记\n\n## CSS的继承性\n\n我们来看下面这样的代码，来引入继承性：\n\n![](http://img.smyhvae.com/20170724_2359.png)\n\n上方代码中，我们给div标签增加红色属性，却发现，div里的每一个子标签`<p>`也增加了红色属性。于是我们得到这样的结论：\n\n> 有一些属性，当给自己设置的时候，自己的后代都继承上了，这个就是**继承性。**\n\n继承性是从自己开始，直到最小的元素。\n\n但是呢，如果再给上方的代码加一条属性：\n\n![](http://img.smyhvae.com/20170725_2122.jpg)\n\n上图中，我们给div加了一个border，但是发现只有div具备了border属性，而p标签却没有border属性。于是我们可以得出结论：\n\n- 关于文字样式的属性，都具有继承性。这些属性包括：color、 text-开头的、line-开头的、font-开头的。\n\n- 关于盒子、定位、布局的属性，都不能继承。\n\n以后当我们谈到css有哪些特性的时候，我们要首先想到继承性。而且，要知道哪些属性具有继承性、哪些属性没有继承性。\n\n## CSS的层叠性\n\n很多公司如果要笔试，那么一定会考层叠性。\n\n### 层叠性：计算权重\n\n**层叠性：就是css处理冲突的能力。** 所有的权重计算，没有任何兼容问题！\n\nCSS像艺术家一样优雅，像工程师一样严谨。\n\n我们来看一个例子，就知道什么叫层叠性了。\n\n![](http://img.smyhvae.com/20170725_2132.png)\n\n\n上图中，三种选择器同时给P标签增加颜色的属性，但是，文字最终显示的是蓝色，这个时候，就出现了层叠性的情况。\n\n当多个选择器，选择上了某个元素的时候，要按照如下顺序统计权重：\n\n-  id 选择器\n-  类选择器、属性选择器、伪类选择器\n-  标签选择器、伪元素选择器\n\n因为对于相同方式的样式表，其选择器排序的优先级为：ID选择器 > 类选择器 > 标签选择器\n\n针对上面这句话，我们接下来举一些复杂一点的例子。\n\n### 层叠性举例\n\n#### 举例1：计算权重\n\n![](http://img.smyhvae.com/20170725_2138.png)\n\n如上图所示，统计各个选择器的数量，优先级高的胜出。文字的颜色为红色。\n\nPS：不进位，实际上能进位（奇淫知识点：255个标签，等于1个类名）但是没有实战意义！\n\n#### 举例2：权重相同时\n\n![](http://img.smyhvae.com/20170725_2250.png)\n\n上图可以看到，第一个样式和第二个样式的权重相同。但第二个样式的书写顺序靠后，因此以第二个样式为准（就近原则）。\n\n#### 举例3：具有实战性的例子\n\n![](http://img.smyhvae.com/20170726_2221.png)\n\n现在我要让一个列表实现上面的这种样式：第一个li为红色，剩下的li全部为蓝色。\n\n如果写成下面这种代码是无法实现的：\n\n![](http://img.smyhvae.com/20170726_2225.png)\n\n无法实现的原因很简单，计算一下三个选择器的权重就清楚了，显然第二个样式被第一个样式表覆盖了。\n\n正确的做法是：（**非常重要**）\n\n![](http://img.smyhvae.com/20170726_2229.png)\n\n上图中，第二个样式比第一个样式的权重要大。因此在实战中可以实现这种效果：**所有人当中，让某一个人为红，让其他所有人为蓝。**\n\n这种方式好用是好用，但用好很难。\n\n就拿上方代码来举例，为了达到这种效果，即为了防止权重不够，比较稳妥的做法是：**把第二个样式表照着第一个样式表来写，在此基础上，给第二个样式表再加一个权重。**\n\n上面这个例子很具有实战性。\n\n#### 举例4：继承性造成的影响\n\n这里需要声明一点：\n\n >如果不能直接选中某个元素，通过继承性影响的话，那么权重是0。\n\n为了验证上面这句话，我们来看看下面这样的例子：\n\n![](http://img.smyhvae.com/20170727_0843.png)\n\n另外：**如果大家的权重相同，那么就采用就近原则：谁描述的近，听谁的**。举例如下：(box3 描述得最近，所以采用 box3 的属性)\n\n![](http://img.smyhvae.com/20190122_1530.png)\n\n上方代码的文字版如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n    <style>\n      #box1 {\n        color: red;\n      }\n\n      #box2 {\n        color: green;\n      }\n\n      #box3 {\n        color: blue;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"box1\">\n      <div id=\"box2\">\n        <div id=\"box3\"><p>猜猜我是什么颜色</p></div>\n      </div>\n    </div>\n  </body>\n</html>\n\n```\n\n### 层叠性：权重计算的问题大总结（非常重要）\n\n层叠性。层叠性是一种能力，就是处理冲突的能力。当不同选择器，对一个标签的同一个样式，有不同的值，听谁的？这就是冲突。css有着严格的处理冲突的机制。\n\n通过列举上面几个例子，我们对权重问题做一个总结。\n\n![](http://img.smyhvae.com/20170727_2050.png)\n\n上面这个图非常重要，我们针对这个图做一个文字描述：\n\n- 选择上了，数权重，(id的数量，类的数量，标签的数量)。如果权重一样，谁写在后面听谁的。\n- 没有选择上，通过继承影响的，就近原则，谁描述的近听谁的。如果描述的一样近，比如选择器权重，如果权重再一样重，谁写在后面听谁的。\n\n### CSS样式表的冲突的总结\n\n- 1、对于相同的选择器（比如同样都是类选择器），其样式表排序：行级样式 > 内嵌样式表 > 外部样式表（就近原则）\n- 2、对于相同类型的样式表（比如同样都是内部样式表），其选择器排序：ID选择器 > 类选择器 > 标签选择器\n- 3、外部样式表的ID选择器  > 内嵌样式表的标签选择器\n\n> 总结：就近原则。ID选择器优先级最大。\n\n举例：如果都是内嵌样式表，优先级的顺序如下：（ID 选择器 > 类选择器 > 标签选择器）\n\n![](http://img.smyhvae.com/2015-10-03-css-14.png)\n\n另外还有两个冲突的情况：\n\n- 1、对同一个标签，如果用到的都是内嵌样式表，且权重一致，那它的优先级：**定义**的CSS样式表中，谁最近，就用谁。\n- 2、对于同一个标签，如果用到的都是外部样式表，且权重一致，那它的优先级：html文件中，引用样式表的位置越近，就用谁。\n\n例如：\n\n![](http://img.smyhvae.com/2015-10-03-css-16.png)\n\n### 题目演示\n\nCSS的层叠性讲完了，我们来做几个题目吧。\n\n#### 题目1\n\n![](http://img.smyhvae.com/20170727_0851.png)\n\n#### 题目2\n\n![](http://img.smyhvae.com/20170727_0853.png)\n\n#### 题目3\n\n![](http://img.smyhvae.com/20170727_0855.png)\n\n#### 题目4\n\n![](http://img.smyhvae.com/20170727_0857.png)\n\n## 权重问题深入\n\n### 同一个标签，携带了多个类名，有冲突：\n\n这里需要补充两种冲突的情况：\n\n- 1、对同一个标签，如果用到了了多个相同的内嵌样式表，它的优先级：**定义**的样式表中，谁最近，就用谁。\n- 2、对于同一个标签，如果引用了多个相同的外部样式表，它的优先级：html文件中，引用样式表的位置越近，就用谁。\n\n例如：（就近原则）\n\n![](http://img.smyhvae.com/20170727_2021.png)\n\n上图中，**文字显示的颜色均为红色**。因为这和在标签中的挂类名的书序无关，只和css的顺序有关。\n\n### !important标记：优先级最高\n\n来看个很简单的例子：\n\n![](http://img.smyhvae.com/20170727_2029.png)\n\n上图中，显然id选择器的权重最大，所以文字的颜色是红色。\n\n如果我们想让文字的颜色显示为绿色，只需要给标签选择器的加一个`!important`标记，此时其权重为无穷大。如下：\n\n![](http://img.smyhvae.com/20170727_2035_2.png)\n\nimportant是英语里面的“重要的”的意思。我们可以通过如下语法：\n\n```\nk:v !important;\n```\n\n来给一个属性提高权重。这个属性的权重就是**无穷大**。\n\n注意，一定要注意语法的正确性。\n\n正确的语法：\n\n```\nfont-size:60px !important;\n```\n\n错误的语法：\n\n```\nfont-size:60px; !important;    不能把!important写在外面\nfont-size:60px important;      不能忘记感叹号\n```\n\n`!important`标记需要强调如下3点：\n\n**（1）!important提升的是一个属性，而不是一个选择器**\n\n```css\n\t\tp{\n\t\t\tcolor:red !important;    只写了这一个!important，所以只有字体颜色属性提升了权重\n\t\t\tfont-size: 100px ;       这条属性没有写!important，所以没有提升权重\n\t\t}\n\t\t#para1{\n\t\t\tcolor:blue;\n\t\t\tfont-size: 50px;\n\t\t}\n\t\t.spec{\n\t\t\tcolor:green;\n\t\t\tfont-size: 20px;\n\t\t}\n```\n\n所以，综合来看，字体颜色是red（听important的）；字号是50px（听id的）。\n\n**（2）!important无法提升继承的权重，该是0还是0**\n\n比如HTML结构：\n\n```html\n\t<div>\n\t\t<p>哈哈哈哈哈哈哈哈</p>\n\t</div>\n```\n\n有CSS样式：\n\n```css\n\tdiv{\n\t\tcolor:red !important;\n\t}\n\tp{\n\t\tcolor:blue;\n\t}\n```\n\n由于div是通过继承性来影响文字颜色的，所以!important无法提升它的权重，权重依然是0。\n\n干不过p标签，因为p标签是实实在在选中了，所以字是蓝色的（以p为准）。\n\n**(3)!important不影响就近原则**\n\n如果大家都是继承来的，按理说应该按照“就近原则”，那么important能否影响就近原则呢？\n答案是：不影响。远的，永远是远的。不能给远的写一个important，干掉近的。\n\n为了验证这个问题，我们可以搞两层具有继承性的标签，然后给外层标签加一个!important，最终看看就近原则有没有被打破。举例如下：\n\n![](http://img.smyhvae.com/20170727_2046.png)\n\nPS：做网站的时候，!important 尽量不要用。否则会让css写的很乱。\n\n## 知识回顾\n\n> 我们把以上内容和上一篇文章做一个简单的知识回顾。\n\n### CSS属性\n\n> css属性，面试的时候会有笔试，笔试没有智能提示的。\n\n加粗，倾斜，下划线：\n\n```\nfont-weight:bold;\nfont-style:italic;\ntext-decoration:underline;\n```\n\n背景颜色、前景色：\n\n```\nbackground-color:red;\ncolor:red;\n```\n\n### class和id的区别\n\nclass用于css的，id用于js的。\n\n1）class页面上可以重复。id页面上唯一，不能重复。\n2）一个标签可以有多个class，用空格隔开。但是id只能有id。\n\n### 各种选择器(浏览器兼容性)\n\nIE6层面兼容的选择器： 标签选择器、id选择器、类选择器、后代、交集选择器、并集选择器、通配符。如下：\n\n```\np\n#box\n.spec\ndiv p\ndiv.spec\ndiv,p\n*\n```\n\nIE7能够兼容的：儿子选择器、下一个兄弟选择器。如下：\n\n```\ndiv>p\nh3+p\n```\nIE8能够兼容的：\n\n```\nul li:first-child\nul li:last-child\n```\n### css两个性质\n\n- 继承性：好的事儿。继承从上到下，哪些能？哪些不能？\n\n- 层叠性：冲突，多个选择器描述了同一个属性，听谁的？\n\n再看几个题目：\n\n![](http://img.smyhvae.com/20170727_0900.png)\n\n![](http://img.smyhvae.com/20170727_0901.png)\n\n![](http://img.smyhvae.com/20170727_0902.png)\n\n![](http://img.smyhvae.com/20170727_0903.png)\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/06-CSS盒模型详解.md",
    "content": "---\ntitle: 06-CSS盒模型详解\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 盒子模型\n\n### 前言\n\n盒子模型，英文即box model。无论是div、span、还是a都是盒子。\n\n但是，图片、表单元素一律看作是文本，它们并不是盒子。这个很好理解，比如说，一张图片里并不能放东西，它自己就是自己的内容。\n\n### 盒子中的区域\n\n一个盒子中主要的属性就5个：width、height、padding、border、margin。如下：\n\n- width和height：**内容**的宽度、高度（不是盒子的宽度、高度）。\n- padding：内边距。\n- border：边框。\n- margin：外边距。\n\n盒子模型的示意图：\n\n![](http://img.smyhvae.com/20170727_2128.png)\n\n代码演示：\n\n![](http://img.smyhvae.com/20170727_2326.png)\n\n上面这个盒子，width:200px; height:200px; 但是真实占有的宽高是302*302。 这是因为还要加上padding、border。\n\n注意：**宽度和真实占有宽度，不是一个概念！**来看下面这例子。\n\n### 标准盒模型和IE盒模型\n\n> 我们目前所学习的知识中，以标准盒子模型为准。\n\n标准盒子模型：\n\n![](http://img.smyhvae.com/2015-10-03-css-27.jpg)\n\nIE盒子模型：\n\n![](http://img.smyhvae.com/2015-10-03-css-30.jpg)\n\n上图显示：\n\n在 CSS 盒子模型 (Box Model) 规定了元素处理元素的几种方式：\n\n- width和height：**内容**的宽度、高度（不是盒子的宽度、高度）。\n- padding：内边距。\n- border：边框。\n- margin：外边距。\n\nCSS盒模型和IE盒模型的区别：\n\n- 在 <font color=\"#0000FF\">**标准盒子模型**</font>中，<font color=\"#0000FF\">**width 和 height 指的是内容区域**</font>的宽度和高度。增加内边距、边框和外边距不会影响内容区域的尺寸，但是会增加元素框的总尺寸。\n\n- <font color=\"#0000FF\">**IE盒子模型**</font>中，<font color=\"#0000FF\">**width 和 height 指的是内容区域+border+padding**</font>的宽度和高度。\n\n\n注：Android中也有margin和padding的概念，意思是差不多的，如果你会一点Android，应该比较好理解吧。区别在于，Android中没有border这个东西，而且在Android中，margin并不是控件的一部分。\n\n### `<body>`标签也有margin\n\n`<body>`标签有必要强调一下。很多人以为`<body>`标签占据的是整个页面的全部区域，其实是错误的，正确的理解是这样的：整个网页最大的盒子是`<document>`，即浏览器。而`<body>`是`<document>`的儿子。浏览器给`<body>`默认的margin大小是8个像素，此时`<body>`占据了整个页面的一大部分区域，而不是全部区域。来看一段代码。\n\n```html\n<!doctype html>\n<html lang=\"en\">\n <head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"Generator\" content=\"EditPlus®\">\n  <meta name=\"Author\" content=\"\">\n  <meta name=\"Keywords\" content=\"\">\n  <meta name=\"Description\" content=\"\">\n  <title>Document</title>\n\n\t<style type=\"text/css\">\n\n\t\tdiv{\n\t\t\twidth: 100px;\n\t\t\theight: 100px;\n\t\t\tborder: 1px solid red;\n\t\t\tpadding: 20px;\n\t\t\tmargin: 30px;\n\t\t}\n\n\t</style>\n\n </head>\n\n <body>\n\n\t<div>有生之年</div>\n\t<div>狭路相逢</div>\n\n </body>\n\n</html>\n\n```\n\n上面的代码中，我们对div标签设置了边距等信息。打开google浏览器，按住F12，显示效果如下：\n\n![](http://img.smyhvae.com/20151003_27.png)\n\n## 认识width、height\n\n一定要知道，在前端开发工程师眼中，世界中的一切都是不同的。\n\n比如说，丈量稿纸，前端开发工程师只会丈量内容宽度：\n\n![](http://img.smyhvae.com/20170727_2329.png)\n\n下面这两个盒子，真实占有宽高，都是302*302：\n\n盒子1：\n\n```css\n.box1{\n\twidth: 100px;\n\theight: 100px;\n\tpadding: 100px;\n\tborder: 1px solid red;\n}\n```\n\n盒子2：\n\n```css\n.box2{\n\twidth: 250px;\n\theight: 250px;\n\tpadding: 25px;\n\tborder: 1px solid red;\n}\n```\n\n\n真实占有宽度 = 左border + 左padding + width + 右padding + 右border\n\n上面这两个盒子的盒模型图如下：\n\n![](https://img.smyhvae.com/20170728_0925.png)\n\n**如果想保持一个盒子的真实占有宽度不变，那么加width的时候就要减padding。加padding的时候就要减width**。因为盒子变胖了是灾难性的，这会把别的盒子挤下去。\n\n\n## 认识padding\n\n### padding区域也有颜色\n\npadding就是内边距。padding的区域有背景颜色，css2.1前提下，并且背景颜色一定和内容区域的相同。也就是说，background-color将填充**所有border以内的区域。**\n\n效果如下：\n\n![](http://img.smyhvae.com/20170728_1005.png)\n\n### padding有四个方向\n\npadding是4个方向的，所以我们能够分别描述4个方向的padding。\n\n方法有两种，第一种写小属性；第二种写综合属性，用空格隔开。\n\n小属性的写法：\n\n```css\n\tpadding-top: 30px;\n\tpadding-right: 20px;\n\tpadding-bottom: 40px;\n\tpadding-left: 100px;\n```\n\n综合属性的写法：(上、右、下、左)（顺时针方向，用空格隔开。margin的道理也是一样的）\n\n```css\npadding:30px 20px 40px 100px;\n```\n\n如果写了四个值，则顺序为：上、右、下、左。\n\n如果只写了三个值，则顺序为：上、右和左、下。\n\n如果只写了两个值，则顺序为：上和下、左和右。\n\n比如说：\n\n```\npadding: 30px 40px;\n```\n\n则顺序等价于：30px 40px 30px 40px;\n\n要懂得，**用小属性层叠大属性**。比如：\n\n```\npadding: 20px;\npadding-left: 30px;\n```\n\n上面的padding对应盒子模型为：\n\n![](http://img.smyhvae.com/20170728_1039.png)\n\n下面的写法：\n\n```\npadding-left: 30px;\npadding: 20px;\n```\n\n第一行的小属性无效，因为被第二行的大属性层叠掉了。\n\n下面的题，会做了，说明你明白了。\n\n### 一些题目\n\n**题目1**：说出下面盒子真实占有宽高，并画出盒模型图。\n\n```css\n\tdiv{\n\t\twidth: 200px;\n\t\theight: 200px;\n\t\tpadding: 10px 20px 30px;\n\t\tpadding-right: 40px;\n\t\tborder: 1px solid #000;\n\t}\n```\n\n答案：\n\n![](http://img.smyhvae.com/20170728_1048.png)\n\n**题目2**：说出下面盒子真实占有宽高，并画出盒模型图。\n\n```css\n\tdiv{\n\t\twidth: 200px;\n\t\theight: 200px;\n\t\tpadding-left: 10px;\n\t\tpadding-right: 20px;\n\t\tpadding:40px 50px 60px;\n\t\tpadding-bottom: 30px;\n\t\tborder: 1px solid #000;\n\t}\n```\n\n答案：\n\n`padding-left:10px；` 和`padding-right:20px;` 没用，因为后面的padding大属性，层叠掉了他们。\n\n盒子模型如下：\n\n![](http://img.smyhvae.com/20170728_1100.png)\n\n**题目3**：现在给你一个盒子模型图，请写出代码，试着用最最简单的方法写。\n\n![](http://img.smyhvae.com/20170728_1401.png)\n\n答案：\n\n```css\n\twidth:123px;\n\theight:123px;\n\tpadding:20px 40px;\n\tborder:1px solid red;\n```\n\n**题目4**：现在给你一个盒子模型图，请写出代码，试着用最最简单的方法写。\n\n![](http://img.smyhvae.com/20170728_1402.png)\n\n答案：\n\n```css\n\twidth:123px;\n\theight:123px;\n\tpadding:20px;\n\tpadding-right:40px;\n\tborder:1px solid red;\n\n```\n\n### 一些元素，默认带有padding\n\n一些元素，默认带有`padding`，比如ul标签。如下：\n\n![](http://img.smyhvae.com/20170728_1413.png)\n\n上图显示，不加任何样式的ul，也是有40px的padding-left。\n\n所以，我们做站的时候，为了便于控制，总是喜欢清除这个默认的padding。\n\n可以使用`*`进行清除：\n\n```css\n\t\t*{\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n```\n\n 但是，`*`的效率不高，所以我们使用并集选择器，罗列所有的标签（不用背，有专业的清除默认样式的样式表，今后学习）：\n\n```\nbody,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{\n    margin:0;\n    padding:0;\n}\n```\n\n## 认识border\n\nborder就是边框。边框有三个要素：像素（粗细）、线型、颜色。\n\n比如：\n\n```css\n\n.div1{\n\twidth: 10px;\n\theight: 10px;\n\tborder: 2px solid red;\n}\n\n```\n\n颜色如果不写，默认是黑色。另外两个属性如果不写，则无法显示边框。\n\n### border-style\n\nborder的所有的线型如下：（我们可以通过查看`CSS参考手册`得到）\n\n![](http://img.smyhvae.com/20170728_1435.png)\n\n比如`border:10px ridge red;`这个属性，在chrome和firefox、IE中有细微差别：（因为可以显示出效果，因此并不是兼容性问题，只是有细微差别而已）\n\n![](http://img.smyhvae.com/20170728_1619.png)\n\n\n如果公司里面的设计师是处女座的，追求极高的**页面还原度**，那么不能使用css来制作边框。就要用到图片，就要切图了。\n\n所以，比较稳定的border-style就几个：solid、dashed、dotted。\n\n### border拆分\n\nborder是一个大综合属性。比如说：\n\n```css\nborder:1px solid red;\n```\n\n就是把上下左右这四个方向的边框，都设置为 1px 宽度、线型实线、red颜色。\n\nPS：小技巧：在sublime text中，为了快速输入`border:1px solid red;`这个属性，可以直接输入`bd`，然后选第二个后回车。\n\nborder属性是能够被拆开的，有两大种拆开的方式：\n\n- （1）按三要素拆开：border-width、border-style、border-color。（一个border属性是由三个小属性综合而成的）\n\n- （2）按方向拆开：border-top、border-right、border-bottom、border-left。\n\n现在我们明白了：**一个border属性，是由三个小属性综合而成的**。如果某一个小属性后面是空格隔开的多个值，那么就是上右下左的顺序。举例如下：\n\n```\nborder-width:10px 20px;\nborder-style:solid dashed dotted;\nborder-color:red green blue yellow;\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20170728_1516.png)\n\n（1）按三要素拆：\n\n```css\nborder-width:10px;    //边框宽度\nborder-style:solid;   //线型\nborder-color:red;     //颜色。\n```\n等价于：\n\n```\nborder:10px solid red;\n```\n\n(2)按方向来拆：\n\n```css\nborder-top:10px solid red;\nborder-right:10px solid red;\nborder-bottom:10px solid red;\nborder-left:10px solid red;\n```\n\n等价于：\n\n```css\nborder:10px solid red;\n```\n\n（3）按三要素和方向来拆：(就是把每个方向的，每个要素拆开。3*4 = 12)\n\n```css\n\tborder-top-width:10px;\n\tborder-top-style:solid;\n\tborder-top-color:red;\n\tborder-right-width:10px;\n\tborder-right-style:solid;\n\tborder-right-color:red;\n\tborder-bottom-width:10px;\n\tborder-bottom-style:solid;\n\tborder-bottom-color:red;\n\tborder-left-width:10px;\n\tborder-left-style:solid;\n\tborder-left-color:red;\n```\n\n等价于：\n\n```css\nborder:10px solid red;\n\n```\n\n工作中到底用什么？很简答：什么简单用什么。但要懂得，用小属性层叠大属性。举例如下：\n\n![](http://img.smyhvae.com/20170728_1606.png)\n\n为了实现上方效果，写法如下：\n\n```css\nborder:10px solid red;\nborder-right-color:blue;\n```\n\n![](http://img.smyhvae.com/20170728_1608.png)\n\n为了实现上方效果，写法如下：\n\n```css\nborder:10px solid red;\nborder-style:solid dashed;\n```\n\nborder可以没有：\n\n```css\nborder:none;\n```\n\n可以某一条边没有：\n\n```css\nborder-left: none;\n```\n\n也可以调整左边边框的宽度为0：\n\n```css\nborder-left-width: 0;\n```\n\n### border-image 属性\n\n比如：\n\n```css\nborder-image: url(.img.png) 30 round;\n```\n\n这个属性在实际开发中用得不多，暂时忽略。\n\n### 举例1：利用 border 属性画一个三角形（小技巧）\n\n完整代码如下：\n\n```css\ndiv{\n\twidth: 0;\n\theight: 0;\n\tborder: 50px solid transparent;\n\tborder-top-color: red;\n\tborder-bottom: none;\n}\n\n```\n\n步骤如下：\n\n（1）当我们设置盒子的width和height为0时，此时效果如下：\n\n![](http://img.smyhvae.com/20170728_1639.png)\n\n（2）然后将border的底部取消：\n\n![](http://img.smyhvae.com/20170728_1645.png)\n\n（3）最后设置border的左边和右边为白色或者**透明**：\n\n![](http://img.smyhvae.com/20170728_1649.png)\n\n这样，一个三角形就画好了。\n\n### 举例2：利用 border 属性画一个三角形（更推荐的技巧）\n\n上面的例子1中，画出来的是直角三角形，可如果我想画等边三角形，要怎么做呢？\n\n完整代码如下：（用 css 画等边三角形）\n\n```css\n.div1{\n\twidth: 0;\n\theight: 0;\n\tborder-top: 30px solid red;\n\t/* 通过改变 border-left 和 border-right 中的像素值，来改变三角形的形状 */\n\tborder-left: 20px solid transparent;\n\tborder-right: 20px solid transparent;\n}\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20191004_1830.png)\n\n另外，我们在上方代码的基础之上，再加一个 `border-radus: 20px;` 就能画出一个扇形。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n"
  },
  {
    "path": "02-CSS基础/07-浮动.md",
    "content": "---\ntitle: 07-浮动\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 文本主要内容\n\n- 标准文档流\n\t- 标准文档流的特性\n\t- 行内元素和块级元素\n\t- 行内元素和块级元素的相互转换\n- 浮动的性质\n- 浮动的清除\n- 浏览器的兼容性问题\n- 浮动中margin相关\n- 关于margin的IE6兼容问题\n\n## 标准文档流\n\n\n宏观地讲，我们的web页面和photoshop等设计软件有本质的区别：web页面的制作，是个“流”，必须从上而下，像“织毛衣”。而设计软件，想往哪里画个东西，都能画。\n\n\n### 标准文档流的特性\n\n**（1）空白折叠现象：**\n\n无论多少个空格、换行、tab，都会折叠为一个空格。\n\n比如，如果我们想让img标签之间没有空隙，必须紧密连接：\n\n```\n<img src=\"images/0.jpg\" /><img src=\"images/1.jpg\" /><img src=\"images/2.jpg\" />\n```\n\n\n**（2）高矮不齐，底边对齐：**\n\n举例如下：\n\n![](http://img.smyhvae.com/20170729_1508_2.png)\n\n\n**（3）自动换行，一行写不满，换行写。**\n\n\n### 行内元素和块级元素\n\n学习的初期，我们就要知道，标准文档流等级森严。标签分为两种等级：\n\n- 行内元素\n- 块级元素\n\n\n我们可以举一个例子，看看块级元素和行内元素的区别：\n\n![](http://img.smyhvae.com/20170729_1529_2.png)\n\n\n上图中可以看到，`h1`标签是块级元素，占据了整行，`span`标签是行内元素，只占据内容这一部分。\n\n现在我们尝试给两个标签设置宽高。效果如下：\n\n![](http://img.smyhvae.com/20170729_1532_2.png)\n\n上图中，我们尝试给两个标签设置宽高，但发现，宽高属性只对块级元素`h1`生效。于是我们可以做出如下总结。\n\n**行内元素和块级元素的区别：**（非常重要）\n\n行内元素：\n\n- 与其他行内元素并排；\n- 不能设置宽、高。默认的宽度，就是文字的宽度。\n\n块级元素：\n\n- 霸占一行，不能与其他任何元素并列；\n- 能接受宽、高。如果不设置宽度，那么宽度将默认变为父亲的100%。\n\n\n\n\n**行内元素和块级元素的分类：**\n\n在以前的HTML知识中，我们已经将标签分过类，当时分为了：文本级、容器级。\n\n从HTML的角度来讲，标签分为：\n\n- 文本级标签：p、span、a、b、i、u、em。\n- 容器级标签：div、h系列、li、dt、dd。\n\n> PS：为甚么说p是文本级标签呢？因为p里面只能放文字&图片&表单元素，p里面不能放h和ul，p里面也不能放p。\n\n现在，从CSS的角度讲，CSS的分类和上面的很像，就p不一样：\n\n- 行内元素：除了p之外，所有的文本级标签，都是行内元素。p是个文本级，但是是个块级元素。\n\n- 块级元素：所有的容器级标签都是块级元素，还有p标签。\n\n我们把上面的分类画一个图，即可一目了然：\n\n![](http://img.smyhvae.com/20170729_1545.png)\n\n\n\n### 行内元素和块级元素的相互转换\n\n我们可以通过`display`属性将块级元素和行内元素进行相互转换。display即“显示模式”。\n\n#### 块级元素可以转换为行内元素：\n\n一旦，给一个块级元素（比如div）设置：\n\n```\ndisplay: inline;\n```\n\n那么，这个标签将立即变为行内元素，此时它和一个span无异。inline就是“行内”。也就是说：\n\n- 此时这个div不能设置宽度、高度；\n- 此时这个div可以和别人并排了。\n\n举例如下：\n\n![](http://img.smyhvae.com/20170729_1629.png)\n\n\n#### 行内元素转换为块级元素：\n\n同样的道理，一旦给一个行内元素（比如span）设置：\n\n```\ndisplay: block;\n```\n\n那么，这个标签将立即变为块级元素，此时它和一个div无异。block”是“块”的意思。也就是说：\n\n- 此时这个span能够设置宽度、高度\n- 此时这个span必须霸占一行了，别人无法和他并排\n- 如果不设置宽度，将撑满父亲\n\n举例如下：\n\n![](http://img.smyhvae.com/20170729_1638.png)\n\n标准流里面的限制非常多，导致很多页面效果无法实现。如果我们现在就要并排、并且就要设置宽高，那该怎么办呢？办法是：移民！**脱离标准流**！\n\n\ncss中一共有三种手段，使一个元素脱离标准文档流：\n\n- （1）浮动\n- （2）绝对定位\n- （3）固定定位\n\n这便引出我们今天要讲的内容：浮动。\n\n\n## 浮动的性质\n\n> 浮动是css里面布局用的最多的属性。\n\n现在有两个div，分别设置宽高。我们知道，它们的效果如下：\n\n![](http://img.smyhvae.com/20170729_1722.png)\n\n此时，如果给这两个div增加一个浮动属性，比如`float: left;`，效果如下：\n\n![](http://img.smyhvae.com/20170729_1723.png)\n\n这就达到了浮动的效果。此时，两个元素并排了，并且两个元素都能够设置宽度、高度了（这在上一段的标准流中，不能实现）。\n\n浮动想学好，一定要知道三个性质。接下来讲一讲。\n\n### 性质1：浮动的元素脱标\n\n脱标即脱离标准流。我们来看几个例子。\n\n证明1：\n\n![](http://img.smyhvae.com/20170729_2028.png)\n\n上图中，在默认情况下，两个div标签是上下进行排列的。现在由于float属性让上图中的第一个`<div>`标签出现了浮动，于是这个标签在另外一个层面上进行排列。而第二个`<div>`还在自己的层面上遵从标准流进行排列。\n\n证明2：\n\n![](http://img.smyhvae.com/20180111_2320.png)\n\n上图中，span标签在标准流中，是不能设置宽高的（因为是行内元素）。但是，一旦设置为浮动之后，即使不转成块级元素，也能够设置宽高了。\n\n所以能够证明一件事：**一旦一个元素浮动了，那么，将能够并排了，并且能够设置宽高了。无论它原来是个div还是个span。**所有标签，浮动之后，已经不区分行内、块级了。\n\n\n### 性质2：浮动的元素互相贴靠\n\n我们来看一个例子就明白了。\n\n我们给三个div均设置了`float: left;`属性之后，然后设置宽高。当改变浏览器窗口大小时，可以看到div的贴靠效果：\n\n![](http://img.smyhvae.com/20170730_1910.gif)\n\n\n上图显示，3号如果有足够空间，那么就会靠着2号。如果没有足够的空间，那么会靠着1号大哥。\n如果没有足够的空间靠着1号大哥，3号自己去贴左墙。\n\n不过3号自己去贴墙的时候，注意：\n\n![](http://img.smyhvae.com/20170730_1928.gif)\n\n\n上图显示，3号贴左墙的时候，并不会往1号里面挤。\n\n同样，float还有一个属性值是`right`，这个和属性值`left`是对称的。\n\n\n### 性质3：浮动的元素有“字围”效果\n\n来看一张图就明白了。我们让div浮动，p不浮动。\n\n![](http://img.smyhvae.com/20170730_2005.png)\n\n上图中，我们发现：**div挡住了p，但不会挡住p中的文字**，形成“字围”效果。\n\n总结：**标准流中的文字不会被浮动的盒子遮挡住**。（文字就像水一样）\n\n关于浮动我们要强调一点，浮动这个东西，为避免混乱，我们在初期一定要遵循一个原则：**永远不是一个东西单独浮动，浮动都是一起浮动，要浮动，大家都浮动。**\n\n\n### 性质4：收缩\n\n收缩：一个浮动的元素，如果没有设置width，那么将自动收缩为内容的宽度（这点非常像行内元素）。\n\n举例如下：\n\n![](http://img.smyhvae.com/20170801_1720.png)\n\n\n上图中，div本身是块级元素，如果不设置width，它会单独霸占整行；但是，设置div浮动后，它会收缩\n\n\n### 浮动的补充（做网站时注意）\n\n![](http://img.smyhvae.com/20170731_2248.png)\n\n\n上图所示，将para1和para2设置为浮动，它们是div的儿子。此时para1+para2的宽度小于div的宽度。效果如上图所示。可如果设置para1+para2的宽度大于div的宽度，我们会发现，para2掉下来了：\n\n![](http://img.smyhvae.com/20170731_2252_2.png)\n\n\n\n### 布置一个作业\n\n布置一个作业，要求实现下面的效果：\n\n![](http://img.smyhvae.com/20170801_0858.png)\n\n\n为实现上方效果，代码如下：\n\n```html\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">\n\t<title>Document</title>\n\t<style type=\"text/css\">\n\t\t*{\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n\t\t.header{\n\t\t\twidth: 970px;\n\t\t\theight: 103px;\n\t\t\t/*居中。这个语句的意思是：居中：*/\n\t\t\tmargin: 0 auto;\n\t\t}\n\t\t.header .logo{\n\t\t\tfloat: left;\n\t\t\twidth: 277px;\n\t\t\theight: 103px;\n\t\t\tbackground-color: red;\n\t\t}\n\t\t.header .language{\n\t\t\tfloat: right;\n\t\t\twidth: 137px;\n\t\t\theight: 49px;\n\t\t\tbackground-color: green;\n\t\t\tmargin-bottom: 8px;\n\t\t}\n\t\t.header .nav{\n\t\t\tfloat: right;\n\t\t\twidth: 679px;\n\t\t\theight: 46px;\n\t\t\tbackground-color: green;\n\t\t}\n\n\t\t.content{\n\t\t\twidth: 970px;\n\t\t\theight: 435px;\n\t\t\t/*居中，这个语句今天没讲，你照抄，就是居中：*/\n\t\t\tmargin: 0 auto;\n\t\t\tmargin-top: 10px;\n\t\t}\n\t\t.content .banner{\n\t\t\tfloat: left;\n\t\t\twidth: 310px;\n\t\t\theight: 435px;\n\t\t\tbackground-color: gold;\n\t\t\tmargin-right: 10px;\n\t\t}\n\t\t.content .rightPart{\n\t\t\tfloat: left;\n\t\t\twidth: 650px;\n\t\t\theight: 435px;\n\t\t}\n\t\t.content .rightPart .main{\n\t\t\twidth: 650px;\n\t\t\theight: 400px;\n\t\t\tmargin-bottom: 10px;\n\t\t}\n\t\t.content .rightPart .links{\n\t\t\twidth: 650px;\n\t\t\theight: 25px;\n\t\t\tbackground-color: blue;\n\t\t}\n\t\t.content .rightPart .main .news{\n\t\t\tfloat: left;\n\t\t\twidth: 450px;\n\t\t\theight: 400px;\n\t\t}\n\t\t.content .rightPart .main .hotpic{\n\t\t\tfloat: left;\n\t\t\twidth: 190px;\n\t\t\theight: 400px;\n\t\t\tbackground-color: purple;\n\t\t\tmargin-left: 10px;\n\t\t}\n\t\t.content .rightPart .main .news .news1{\n\t\t\twidth: 450px;\n\t\t\theight: 240px;\n\t\t\tbackground-color: skyblue;\n\t\t\tmargin-bottom: 10px;\n\t\t}\n\t\t.content .rightPart .main .news .news2{\n\t\t\twidth: 450px;\n\t\t\theight: 110px;\n\t\t\tbackground-color: skyblue;\n\t\t\tmargin-bottom: 10px;\n\t\t}\n\t\t.content .rightPart .main .news .news3{\n\t\t\twidth: 450px;\n\t\t\theight: 30px;\n\t\t\tbackground-color: skyblue;\n\t\t}\n\t\t.footer{\n\t\t\twidth: 970px;\n\t\t\theight: 35px;\n\t\t\tbackground-color: pink;\n\t\t\t/*没学，就是居中：*/\n\t\t\tmargin: 0 auto;\n\t\t\tmargin-top: 10px;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<!-- 头部 -->\n\t<div class=\"header\">\n\t\t<div class=\"logo\">logo</div>\n\t\t<div class=\"language\">语言选择</div>\n\t\t<div class=\"nav\">导航条</div>\n\t</div>\n\n\t<!-- 主要内容 -->\n\t<div class=\"content\">\n\t\t<div class=\"banner\">大广告</div>\n\t\t<div class=\"rightPart\">\n\t\t\t<div class=\"main\">\n\t\t\t\t<div class=\"news\">\n\t\t\t\t\t<div class=\"news1\"></div>\n\t\t\t\t\t<div class=\"news2\"></div>\n\t\t\t\t\t<div class=\"news3\"></div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"hotpic\"></div>\n\t\t\t</div>\n\t\t\t<div class=\"links\"></div>\n\t\t</div>\n\t</div>\n\n\t<!-- 页尾 -->\n\t<div class=\"footer\"></div>\n</body>\n</html>\n```\n\n其实，这个页面的布局是下面这个网站：\n\n\n![](http://img.smyhvae.com/20170801_0900.png)\n\n\n## 浮动的清除\n\n> 这里所说的清除浮动，指的是清除浮动与浮动之间的影响。\n\n### 前言\n\n通过上面这个例子，我们发现，此例中的网页就是通过浮动实现并排的。\n\n比如说一个网页有header、content、footer这三部分。就拿content部分来举例，如果设置content的儿子为浮动，但是，这个儿子又是一个全新的标准流，于是儿子的儿子仍然在标准流里。\n\n从学习浮动的第一天起，我们就要明白，浮动有开始，就要有清除。我们先来做个实验。\n\n下面这个例子，有两个块级元素div，div没有任何属性，每个div里有li，效果如下：\n\n![](http://img.smyhvae.com/20170801_2122.png)\n\n\n上面这个例子很简单。可如果我们给里面的`<li>`标签加浮动。效果却成了下面这个样子：\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Document</title>\n\t<style type=\"text/css\">\n\t\t*{\n\n\t\t}\n\t\tli{\n\t\t\tfloat: left;\n\t\t\twidth: 100px;\n\t\t\theight: 20px;\n\t\t\tbackground-color: pink;\n\n\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"box1\">\n\t\t<ul>\n\t\t\t<li>生命壹号1</li>\n\t\t\t<li>生命壹号2</li>\n\t\t\t<li>生命壹号3</li>\n\t\t\t<li>生命壹号4</li>\n\t\t</ul>\n\t</div>\n\t<div class=\"box2\">\n\t\t<ul>\n\t\t\t<li>许嵩1</li>\n\t\t\t<li>许嵩2</li>\n\t\t\t<li>许嵩3</li>\n\t\t\t<li>许嵩4</li>\n\t\t</ul>\n\t</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20170801_2137.png)\n\n\n上图中，我们发现：第二组中的第1个li，去贴靠第一组中的最后一个li了（我们本以为这些li会分成两排）。\n\n这便引出我们要讲的：清除浮动的第一种方式。\n那该怎么解决呢？\n\n\n### 方法1：给浮动元素的祖先元素加高度\n\n\n\n造成前言中这个现象的根本原因是：li的**父亲div没有设置高度**，导致这两个div的高度均为0px（我们可以通过网页的审查元素进行查看）。div的高度为零，导致不能给自己浮动的孩子，撑起一个容器。\n\n撑不起一个容器，导致自己的孩子没办法在自己的内部进行正确的浮动。\n\n好，现在就算给这个div设置高度，可如果div自己的高度小于孩子的高度，也会出现不正常的现象：\n\n![](http://img.smyhvae.com/20170801_2152.png)\n\n\n给div设置一个正确的合适的高度（至少保证高度大于儿子的高度），就可以看到正确的现象：\n\n![](http://img.smyhvae.com/20170801_2153.png)\n\n**总结：**\n\n**如果一个元素要浮动，那么它的祖先元素一定要有高度。**\n\n**有高度的盒子，才能关住浮动**。（记住这句过来人的经验之语）\n\n只要浮动在一个有高度的盒子中，那么这个浮动就不会影响后面的浮动元素。所以就是清除浮动带来的影响了。\n\n![](http://img.smyhvae.com/20170801_2200.png)\n\n\n![](http://img.smyhvae.com/20170801_2201.png)\n\n### 方法2：clear:both;\n\n网页制作中，高度height其实很少出现。为什么？因为能被内容撑高！也就是说，刚刚我们讲解的方法1，工作中用得很少。\n\n那么，能不能不写height，也把浮动清除了呢？也让浮动之间，互不影响呢？\n\n这个时候，我们可以使用`clear:both;`这个属性。如下：\n\n![](http://img.smyhvae.com/20170801_2233.png)\n\n\n```\nclear:both;\n```\n\nclear就是清除，both指的是左浮动、右浮动都要清除。`clear:both`的意思就是：**不允许左侧和右侧有浮动对象。**\n\n这种方法有一个非常大的、致命的问题，**它所在的标签，margin属性失效了**。读者可以试试看。\n\n\nmargin失效的本质原因是：上图中的box1和box2，高度为零。\n\n\n\n### 方法3：隔墙法\n\n上面这个例子中，为了防止第二个div贴靠到第二个div，我们可以在这两个div中间用一个新的div隔开，然后给这个新的div设置`clear: both;`属性；同时，既然这个新的div无法设置margin属性，我们可以给它设置height，以达到margin的效果（曲线救国）。这便是隔墙法。\n\n\n我们看看例子效果就知道了：\n\n![](http://img.smyhvae.com/20170802_1109.png)\n\n上图这个例子就是隔墙法。\n\n\n**内墙法：**\n\n\n近些年，有演化出了“内墙法”：\n\n![](http://img.smyhvae.com/20170802_1123.png)\n\n上面这个图非常重要，当作内墙法的公式，先记下来。\n\n\n为了讲内墙法，我们先记住一句重要的话：**一个父亲是不能被浮动的儿子撑出高度的**。举例如下：\n\n（1）我们在一个div里放一个有宽高的p，效果如下：（很简单）\n\n![](http://img.smyhvae.com/20170802_1716.png)\n\n（2）可如果在此基础之上，给p设置浮动，却发现父亲div没有高度了：\n\n![](http://img.smyhvae.com/20170802_1730.png)\n\n（3）此时，我么可以在div的里面放一个div（作为内墙），就可以让父亲div恢复高度：\n\n![](http://img.smyhvae.com/20170802_1812.png)\n\n于是，我们采用内墙法解决前言中的问题：\n\n![](http://img.smyhvae.com/20170802_1834.png)\n\n与外墙法相比，内墙法的优势（本质区别）在于：内墙法可以给它所在的家撑出宽度（让box1有高）。即：box1的高度可以自适应内容。\n\n而外墙法，虽然一道墙可以把两个div隔开，但是这两个div没有高，也就是说，无法wrap_content。\n\n\n### 清除浮动方法4：overflow:hidden;\n\n我们可以使用如下属性：\n\n```\noverflow:hidden;\n```\n\n\noverflow即“溢出”， hidden即“隐藏”。这个属性的意思是“溢出隐藏”。顾名思义：所有溢出边框的内容，都要隐藏掉。如下：\n\n![](http://img.smyhvae.com/20170804_1449.png)\n\n\n上图显示，`overflow:hidden;`的本意是清除溢出到盒子外面的文字。但是，前端开发工程师发现了，它能做偏方。如下：\n\n一个父亲不能被自己浮动的儿子，撑出高度。但是，只要给父亲加上`overflow:hidden`; 那么，父亲就能被儿子撑出高了。这是一个偏方。\n\n举个例子：\n\n![](http://img.smyhvae.com/20170804_1920.png)\n\n\n那么对于前言中的例子，我们同样可以使用这一属性：\n\n![](http://img.smyhvae.com/20170804_1934.png)\n\n\n这一招，实际上生成了BFC。关于BFC的解释，详见本项目的另外一篇文章《前端面试/CSS盒模型及BFC.md》。\n\n## 浮动清除的总结\n\n\n> 我们在上一段讲了四种清除浮动的方法，本段来进行一个总结。\n\n浮动的元素，只能被有高度的盒子关住。 也就是说，如果盒子内部有浮动，这个盒子有高，那么妥妥的，浮动不会互相影响。\n\n### 1、加高法\n\n工作上，我们绝对不会给所有的盒子加高度，这是因为麻烦，并且不能适应页面的快速变化。\n\n```html\n<div>     //设置height\n\t<p></p>\n\t<p></p>\n\t<p></p>\n</div>\n\n<div>    //设置height\n\t<p></p>\n\t<p></p>\n\t<p></p>\n</div>\n```\n\n\n### 2、`clear:both;`法\n\n最简单的清除浮动的方法，就是给盒子增加clear:both；表示自己的内部元素，不受其他盒子的影响。\n\n\n```html\n<div>\n\t<p></p>\n\t<p></p>\n\t<p></p>\n</div>\n\n<div>   //clear:both;\n\t<p></p>\n\t<p></p>\n\t<p></p>\n</div>\n```\n\n浮动确实被清除了，不会互相影响了。但是有一个问题，就是margin失效。两个div之间，没有任何的间隙了。\n\n\n\n### 3、隔墙法\n\n在两部分浮动元素中间，建一个墙。隔开两部分浮动，让后面的浮动元素，不去追前面的浮动元素。\n墙用自己的身体当做了间隙。\n\n```html\n<div>\n\t<p></p>\n\t<p></p>\n\t<p></p>\n</div>\n\n<div class=\"cl h10\"></div>\n\n<div>\n\t<p></p>\n\t<p></p>\n\t<p></p>\n</div>\n```\n\n我们发现，隔墙法好用，但是第一个div，还是没有高度。如果我们现在想让第一个div，自动根据自己的儿子撑出高度，我们就要想一些“小伎俩”。\n\n内墙法：\n\n```html\n<div>\n\t<p></p>\n\t<p></p>\n\t<p></p>\n\t<div class=\"cl h10\"></div>\n</div>\n\n<div>\n\t<p></p>\n\t<p></p>\n\t<p></p>\n</div>\n```\n\n内墙法的优点就是，不仅仅能够让后部分的p不去追前部分的p了，并且能把第一个div撑出高度。这样，这个div的背景、边框就能够根据p的高度来撑开了。\n\n\n### 4、`overflow:hidden;`\n\n这个属性的本意，就是将所有溢出盒子的内容，隐藏掉。但是，我们发现这个东西能够用于浮动的清除。\n我们知道，一个父亲，不能被自己浮动的儿子撑出高度，但是，如果这个父亲加上了overflow:hidden；那么这个父亲就能够被浮动的儿子撑出高度了。这个现象，不能解释，就是浏览器的偏方。\n并且,overflow:hidden;能够让margin生效。\n\n\n**清除浮动的例子：**\n\n我们现在举个例子，要求实现下图中无序列表部分的效果：\n\n![](http://img.smyhvae.com/20170804_2321.png)\n\n对比一下我们讲的四种清除浮动的方法。如果用外墙法，ul中不能插入div标签，因为ul中只能插入li，如果插入li的墙，会浪费语义。如果用内墙法，不美观。综合对比，还是用第四种方法来实现吧，这会让标签显得极其干净整洁：\n\n![](http://img.smyhvae.com/20170805_1615.png)\n\n上方代码中，如果没有加`overflow:hidden;`，那么第二行的li会紧跟着第一行li的后面。\n\n\n## 浏览器的兼容性问题\n\n\n\n> 讲一下上述知识点涉及到的浏览器兼容问题。\n\n\n### 兼容性1（微型盒子）\n\n**兼容性的第一条**：IE6不支持小于12px的盒子，任何小于12px的盒子，在IE6中看都大。即：IE 6不支持微型盒子。\n\n举个例子。我们设置一个height为 5px 、宽度为 200px的盒子，看下在IE 8和 IE 6中的显示效果：\n\n![](http://img.smyhvae.com/20170805_1726.png)\n\n解决办法很简单，就是将盒子的字号大小，设置为**小于盒子的高**，比如，如果盒子的高为5px，那就把font-size设置为0px(0px < 5px)。如下：\n\n```\nheight: 5px;\n_font-size: 0px;\n```\n\n我们现在介绍一下浏览器hack。hack就是“黑客”，就是使用浏览器提供的后门，针对某一种浏览器做兼容。\n\n\nIE6留了一个**后门**：只要给css属性之前，加上**下划线**，这个属性就是IE6的专有属性。\n\n\n比如说，我们给背景颜色这个属性加上下划线，就变成了`_background-color: green;`。效果如下：\n\n![](http://img.smyhvae.com/20170805_2026.png)\n\n\n\n于是乎，为了解决微型盒子（即height小于12px）的问题，正确写法：（注意不要忘记下划线）\n\n```\nheight: 10px;\n_font-size:0;\n```\n\n\n\n### 兼容性2\n\n**兼容性的第二条：**IE6不支持用`overflow:hidden;`来清除浮动。\n\n解决办法，以毒攻毒。追加一条：\n\n```\n_zoom:1;\n```\n\n完整写法：\n\n```\noverflow: hidden;\n_zoom:1;\n```\n\n实际上，`_zoom:1;`能够触发浏览器hasLayout机制。这个机制，不要深究了，因为只有IE6有。我们只需要让IE6好用，具体的实现机制，可以自行查阅。\n\n需要强调的是，`overflow:hidden;`的本意，就是让溢出盒子的border的内容隐藏，这个功能是IE6兼容的。不兼容的是`overflow:hidden;`清除浮动的时候。\n\n\n**总结：**\n\n我们刚才学习的两个IE6的兼容问题，都是通过多写一条hack来解决的，这个我们称为伴生属性，即两个属性，要写一起写。\n\n属性1：\n\n```\nheight:6px;\n_font-size:0;\n```\n\n属性2：\n\n```\noverflow:hidden;\n_zoom:1;\n```\n\n\n## margin相关\n\n> 我们来讲一下浮动中和margin相关的知识。\n\n\n### margin塌陷/margin重叠\n\n\n\n**标准文档流中，竖直方向的margin不叠加，取**较大的值**作为margin(水平方向的margin是可以叠加的，即水平方向没有塌陷现象)。如下图所示：\n\n![](http://img.smyhvae.com/20170805_0904.png)\n\n\n\n如果不在标准流，比如盒子都浮动了，那么两个盒子之间是没有塌陷现象的。\n\n### 盒子居中`margin:0 auto;`\n\nmargin的值可以为auto，表示自动。当left、right两个方向都是auto的时候，盒子居中了：\n\n```\nmargin-left: auto;\nmargin-right: auto;\n```\n\n盒子居中的简写为：\n\n```\nmargin:0 auto;\n```\n\n对上方代码的理解：上下的margin为0，左右的margin都尽可能的大，于是就居中了。\n\n注意：\n\n- （1）只有标准流的盒子，才能使用`margin:0 auto;`居中。也就是说，当一个盒子浮动了、绝对定位了、固定定位了，都不能使用margin:0 auto;\n- （2）使用`margin:0 auto;`的盒子，必须有width，有明确的width。（可以这样理解，如果没有明确的width，那么它的width就是霸占整行，没有意义）\n- （3）`margin:0 auto;`是让盒子居中，不是让盒子里的文本居中。文本的居中，要使用`text-align:center;`\n\n对上面的第三条总结一下：（非常重要）\n\n```\nmargin:0 auto;    //让这个div自己在大容器中的水平方向上居中。\ntext-align: center;  //让这个div内部的文本居中。\n```\n\n顺便普及一下知识，text-align还有：\n\n```\ntext-align:left;     //没啥用，因为默认居左\ntext-align:right;    //文本居右\n```\n\n\n\n### 善于使用父亲的padding，而不是儿子的margin\n\n我们来看一个奇怪的现象。现在有下面这样一个结构：（div中放一个p）\n\n```\n\t<div>\n\t\t<p></p>\n\t</div>\n```\n\n上面的结构中，我们尝试通过给儿子`p`一个`margin-top:50px;`的属性，让其与父亲保持50px的上边距。结果却看到了下面的奇怪的现象：\n\n![](http://img.smyhvae.com/20170806_1537.png)\n\n\n此时我们给父亲div加一个border属性，就正常了：\n\n![](http://img.smyhvae.com/20170806_1544.png)\n\n\n\n如果父亲没有border，那么儿子的margin实际上踹的是“流”，踹的是这“行”。所以，父亲整体也掉下来了。\n\n**margin这个属性，本质上描述的是兄弟和兄弟之间的距离； 最好不要用这个marign表达父子之间的距离。**\n\n所以，如果要表达父子之间的距离，我们一定要善于使用父亲的padding，而不是儿子的margin。\n\n\n## 关于margin的IE6兼容问题\n\n\n### IE6的双倍margin的bug：\n\n当出现连续浮动的元素，携带与浮动方向相同的margin时，队首的元素，会双倍marign。\n\n```\n\t<ul>\n\t\t<li></li>\n\t\t<li></li>\n\t\t<li></li>\n\t</ul>\n```\n\n![](http://img.smyhvae.com/20170806_1558.png)\n\n\n\n解决方案：\n\n（1）使浮动的方向和margin的方向，相反。\n\n所以，你就会发现，我们特别喜欢，浮动的方向和margin的方向相反。并且，前端开发工程师，把这个当做习惯了。\n\n```\n\tfloat: left;\n\tmargin-right: 40px;\n```\n\n\n\n（2）使用hack：（没必要，别惯着这个IE6）\n\n单独给队首的元素，写一个一半的margin：\n\n```\n<li class=\"no1\"></li>\n```\n\n```\nul li.no1{\n\t_margin-left:20px;\n}\n```\n\nPS：双倍margin的问题，面试经常问哦。\n\n\n\n### IE6的3px bug\n\n![](http://img.smyhvae.com/20170806_1616.png)\n\n解决办法：不用管，因为根本就不允许用儿子踹父亲（即描述父子之间的距离，请用padding，而不是margin）。所以，如果你出现了3px bug，说明你的代码不标准。\n\n\nIE6，千万不要跟他死坑、较劲，它不配。 格调要高，我们讲IE6的兼容性问题，就是为了增加面试的成功率，不是为了成为IE6的专家。\n\n\n\n## Fireworks和others\n\n### Fireworks\n\nfireworks是Adobe公司的一个设计软件。功能非常多，我们以后用啥讲啥。Fireworks的默认文件格式是png。\n\n标尺的快捷键：Ctrl + Alt+ R\n\n\n### others\n\n\n\n首行缩进两个汉字：\n\n```\ntext-indent: 2em;\n```\n\n上方属性中，单位比较奇怪，叫做em，em就是汉字的一个宽度。indent的意思是缩进。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n"
  },
  {
    "path": "02-CSS基础/08-CSS属性：定位属性.md",
    "content": "---\ntitle: 08-CSS属性：定位属性\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\nCSS的定位属性有三种，分别是绝对定位、相对定位、固定定位。\n\n```\n\tposition: absolute;  <!-- 绝对定位 -->\n\n\tposition: relative;  <!-- 相对定位 -->\n\n\tposition: fixed;     <!-- 固定定位 -->\n\n```\n\n下面逐一介绍。\n\n## 相对定位\n\n**相对定位**：让元素相对于自己原来的位置，进行位置调整（可用于盒子的位置微调）。\n\n我们之前学习的背景属性中，是通过如下格式：\n\n```\n\tbackground-position:向右偏移量 向下偏移量;\n```\n\n但这回的定位属性，是通过如下格式：\n\n```\n\tposition: relative;\n\tleft: 50px;\n\ttop: 50px;\n```\n\n相对定位的举例：\n\n```html\n<!doctype html>\n<html lang=\"en\">\n <head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"Generator\" content=\"EditPlus®\">\n  <meta name=\"Author\" content=\"\">\n  <meta name=\"Keywords\" content=\"\">\n  <meta name=\"Description\" content=\"\">\n  <title>Document</title>\n\n\t<style type=\"text/css\">\n\n\t\tbody{\n\t\t\tmargin: 0px;\n\t\t}\n\n\t\t.div1{\n\t\t\twidth: 200px;\n\t\t\theight: 200px;\n\t\t\tborder: 1px solid red;\n\t\t}\n\n\t\t.div2{\n\t\t\tposition: relative;/*相对定位：相对于自己原来的位置*/\n\t\t\tleft: 50px;/*横坐标：正值表示向右偏移，负值表示向左偏移*/\n\t\t\ttop: 50px;/*纵坐标：正值表示向下偏移，负值表示向上偏移*/\n\n\t\t\twidth: 200px;\n\t\t\theight: 200px;\n\t\t\tborder: 1px solid red;\n\t\t}\n\t</style>\n </head>\n\n <body>\n\n\t<div class=\"div1\">有生之年</div>\n\t<div class=\"div2\">狭路相逢</div>\n\n </body>\n\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/2015-10-03-css-28.png)\n\n### 相对定位不脱标\n\n**相对定位**：不脱标，老家留坑，**别人不会把它的位置挤走**。\n\n也就是说，相对定位的真实位置还在老家，只不过影子出去了，可以到处飘。\n\n### 相对定位的用途\n\n如果想做“压盖”效果（把一个div放到另一个div之上），我们一般**不用**相对定位来做。相对定位，就两个作用：\n\n- （1）微调元素\n- （2）做绝对定位的参考，子绝父相\n\n### 相对定位的定位值\n\n- left：盒子右移\n\n- right：盒子左移\n\n- top：盒子下移\n\n- bottom：盒子上移\n\nPS：负数表示相反的方向。\n\n↘：\n\n```\n\tposition: relative;\n\tleft: 40px;\n\ttop: 10px;\n```\n\n↙：\n\n```\n\tposition: relative;\n\tright: 100px;\n\ttop: 100px;\n```\n\n↖：\n\n```\n\tposition: relative;\n\tright: 100px;\n\tbottom: 100px;\n```\n\n↗：\n\n```\n\tposition: relative;\n\tleft: 200px;\n\tbottom: 200px;\n\n```\n\n![](http://img.smyhvae.com/20180115_1716.png)\n\n如果要描述上面这张图的方向，我们可以首先可以这样描述：\n\n```\n\tposition: relative;\n\tleft: 200px;\n\ttop: 100px;\n\n```\n\n因为`left: 200px`等价于`right: -200px`，所以这张图其实有四种写法。\n\n## 绝对定位\n\n**绝对定位**：定义横纵坐标。原点在父容器的左上角或左下角。横坐标用left表示，纵坐标用top或者bottom表示。\n\n格式举例如下：\n\n```\n\tposition: absolute;  /*绝对定位*/\n\tleft: 10px;  /*横坐标*/\n\ttop/bottom: 20px;  /*纵坐标*/\n```\n\n### 绝对定位脱标\n\n**绝对定位的盒子脱离了标准文档流。**\n\n所以，所有的标准文档流的性质，绝对定位之后都不遵守了。\n\n绝对定位之后，标签就不区分所谓的行内元素、块级元素了，不需要`display:block`就可以设置宽、高了。\n\n### 绝对定位的参考点（重要）\n\n（1）如果用**top描述**，那么参考点就是**页面的左上角**，而不是浏览器的左上角：\n\n![](http://img.smyhvae.com/20180115_2120.png)\n\n（2）如果用**bottom描述**，那么参考点就是**浏览器首屏窗口尺寸**（好好理解“首屏”二字），对应的页面的左下角：\n\n![](http://img.smyhvae.com/20180115_2121.png)\n\n为了理解“**首屏**”二字的含义，我们来看一下动态图：\n\n![](https://img.smyhvae.com/20180115_2200.gif)\n\n问题：\n\n![](http://img.smyhvae.com/20180115_2131.png)\n\n答案：\n\n用bottom的定位的时候，参考的是浏览器首屏大小对应的页面左下角。\n\n![](http://img.smyhvae.com/20180115_2132.png)\n\n### 以盒子为参考点\n\n一个绝对定位的元素，如果父辈元素中也出现了已定位（无论是绝对定位、相对定位，还是固定定位）的元素，那么将以父辈这个元素，为参考点。\n\n如下：（子绝父相）\n\n![](http://img.smyhvae.com/20180115_2210.png)\n\n以下几点需要注意。\n\n（1） 要听最近的已经定位的祖先元素的，不一定是父亲，可能是爷爷：\n\n```\n\t\t<div class=\"box1\">        相对定位\n\t\t\t<div class=\"box2\">    没有定位\n\t\t\t\t<p></p>           绝对定位，将以box1为参考，因为box2没有定位，box1就是最近的父辈元素\n\t\t\t</div>\n\t\t</div>\n\n```\n\n再比如：\n\n```\n\t\t<div class=\"box1\">        相对定位\n\t\t\t<div class=\"box2\">    相对定位\n\t\t\t\t<p></p>           绝对定位，将以box2为参考，因为box2是自己最近的父辈元素\n\t\t\t</div>\n\t\t</div>\n```\n\n（2）不一定是相对定位，任何定位，都可以作为儿子的参考点：\n\n子绝父绝、**子绝父相**、子绝父固，都是可以给儿子定位的。但是在工程上，如果子绝、父绝，没有一个盒子在标准流里面了，所以页面就不稳固，没有任何实战用途。\n\n**工程应用：**\n\n“**子绝父相**”有意义：这样可以保证父亲没有脱标，儿子脱标在父亲的范围里面移动。于是，工程上经常这样做：\n\n> 父亲浮动，设置相对定位（零偏移），然后让儿子绝对定位一定的距离。\n\n（3）绝对定位的儿子，无视参考的那个盒子的padding：\n\n下图中，绿色部分是父亲div的padding，蓝色部分p是div的内容区域。此时，如果div相对定位，p绝对定位，那么，\np将无视父亲的padding，在border内侧为参考点，进行定位：\n\n![](http://img.smyhvae.com/20180116_0812.png)\n\n**工程应用：**\n\n绝对定位非常适合用来做“压盖”效果。我们来举个lagou.com上的例子。\n\n现在有如下两张图片素材：\n\n![](http://img.smyhvae.com/20180116_1115.png)\n\n![](http://img.smyhvae.com/20180116_1116.jpg)\n\n要求作出如下效果：\n\n![](http://img.smyhvae.com/20180116_1117.png)\n\n代码实现如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Document</title>\n\t<style type=\"text/css\">\n\t\t.box{\n\t\t\tmargin: 100px;\n\t\t\twidth: 308px;\n\t\t\theight: 307px;\n\t\t\tborder: 1px solid #FF7E00;\n\t\t\tposition: relative;  /*子绝父相*/\n\n\t\t}\n\t\t.box .image img{\n\t\t\twidth: 308px;\n\t\t\theight: 196px;\n\t\t}\n\t\t.box .dtc{\n\t\t\tdisplay: block;  /*转为块级元素，才能设置span的宽高*/\n\t\t\twidth: 52px;\n\t\t\theight: 28px;\n\t\t\tbackground-image: url(http://img.smyhvae.com/20180116_1115.png);\n\t\t\tbackground-position: -108px 0px; /*这里用到了精灵图*/\n\t\t\tposition: absolute;  /*采用绝对定位的方式，将精灵图盖在最上层*/\n\t\t\ttop: -9px;\n\t\t\tleft: 13px;\n\t\t}\n\t\t.box h4{\n\t\t\tbackground-color: black;\n\t\t\tcolor: white;\n\t\t\twidth:308px;\n\t\t\theight: 40px;\n\t\t\tline-height: 40px;\n\t\t\tposition: absolute;\n\t\t\ttop: 156px;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"box\">\n\t\t<span class=\"dtc\"></span>\n\t\t<div class=\"image\">\n\t\t\t<img src=\"http://img.smyhvae.com/20180116_1116.jpg\" alt=\"\">\n\t\t</div>\n\t\t<h4>广东深圳宝安区建安一路海雅缤纷城4楼</h4>\n\t</div>\n</body>\n</html>\n```\n\n代码解释如下：\n\n- 为了显示“多套餐”那个小图，我们需要用到精灵图。\n\n- “多套餐”下方黑色背景的文字都是通过“子绝父相”的方式的盖在大海报image的上方的。\n\n代码的效果如下：\n\n![](http://img.smyhvae.com/20180116_1335.png)\n\n### 让绝对定位中的盒子在父亲里居中\n\n我们知道，如果想让一个**标准流中的盒子在父亲里居中**（水平方向看），可以将其设置`margin: 0 auto`属性。\n\n可如果盒子是绝对定位的，此时已经脱标了，如果还想让其居中（位于父亲的正中间），可以这样做：\n\n```\n\tdiv {\n\t\twidth: 600px;\n\t\theight: 60px;\n\t\tposition: absolute;  绝对定位的盒子\n\t\tleft: 50%;           首先，让左边线居中\n\t\ttop: 0;\n\t\tmargin-left: -300px;  然后，向左移动宽度（600px）的一半\n\t}\n```\n\n如上方代码所示，我们先让这个宽度为600px的盒子，左边线居中，然后向左移动宽度（600px）的一半，就达到效果了。\n\n![](http://img.smyhvae.com/20180116_1356.png)\n\n我们可以总结成一个公式：\n\n> left:50%; margin-left:负的宽度的一半\n\n## 固定定位\n\n**固定定位**：就是相对浏览器窗口进行定位。无论页面如何滚动，这个盒子显示的位置不变。\n\n备注：IE6不兼容。\n\n**用途1**：网页右下角的“返回到顶部”\n\n比如我们经常看到的网页右下角显示的“返回到顶部”，就可以固定定位。\n\n```html\n\t<style type=\"text/css\">\n\t\t.backtop{\n\t\t\tposition: fixed;\n\t\t\tbottom: 100px;\n\t\t\tright: 30px;\n\t\t\twidth: 60px;\n\t\t\theight: 60px;\n\t\t\tbackground-color: gray;\n\t\t\ttext-align: center;\n\t\t\tline-height:30px;\n\t\t\tcolor:white;\n\t\t\ttext-decoration: none;   /*去掉超链接的下划线*/\n\t\t}\n\t</style>\n```\n\n**用途2：**顶部导航条\n\n我们经常能看到固定在网页顶端的导航条，可以用固定定位来做。\n\n需要注意的是，假设顶部导航条的高度是60px，那么，为了防止其他的内容被导航条覆盖，我们要给body标签设置60px的padding-top。\n\n顶部导航条的实现如下：\n\n```html\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">\n\t<title>Document</title>\n\t<style type=\"text/css\">\n\t\t*{\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\nbody{\n\t\t\t/*为什么要写这个？*/\n\t\t\t/*不希望我们的页面被nav挡住*/\n\t\t\tpadding-top: 60px;\n\t\t\t/*IE6不兼容固定定位，所以这个padding没有什么用，就去掉就行了*/\n\t\t\t_padding-top:0;\n\t\t}\n\t\t.nav{\n\t\t\tposition: fixed;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t \t\twidth: 100%;\n\t\t\theight: 60px;\n\t\t\tbackground-color: #333;\n\t\t\tz-index: 99999999;\n\t\t}\n\t\t.inner_c{\n\t\t\twidth: 1000px;\n\t\t\theight: 60px;\n\t\t\tmargin: 0 auto;\n\n\t\t}\n\t\t.inner_c ul{\n\t\t\tlist-style: none;\n\t\t}\n\t\t.inner_c ul li{\n\t\t\tfloat: left;\n\t\t\twidth: 100px;\n\t\t\theight: 60px;\n\t\t\ttext-align: center;\n\t\t\tline-height: 60px;\n\t\t}\n\t\t.inner_c ul li a{\n\t\t\tdisplay: block;\n\t\t\twidth: 100px;\n\t\t\theight: 60px;\n\t\t\tcolor:white;\n\t\t\ttext-decoration: none;\n\t\t}\n\t\t.inner_c ul li a:hover{\n\t\t\tbackground-color: gold;\n\t\t}\n\t\tp{\n\t\t\tfont-size: 30px;\n\t\t}\n\t\t.btn{\n\t\t\tdisplay: block;\n\t\t\twidth: 120px;\n\t\t\theight: 30px;\n\t\t\tbackground-color: orange;\n\t\t\tposition: relative;\n\t\t\ttop: 2px;\n\t\t\tleft: 1px;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div class=\"nav\">\n\t\t<div class=\"inner_c\">\n\t\t\t<ul>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t\t<li><a href=\"#\">网页栏目</a></li>\n\t\t\t</ul>\n\t\t</div>\n\t</div>\n</body>\n</html>\n\n```\n\n### 5、z-index属性：\n\n**z-index**属性：表示谁压着谁。数值大的压盖住数值小的。\n\n有如下特性：\n\n （1）属性值大的位于上层，属性值小的位于下层。\n\n （2）z-index值没有单位，就是一个正整数。默认的z-index值是0。\n\n （3）如果大家都没有z-index值，或者z-index值一样，那么在HTML代码里写在后面，谁就在上面能压住别人。定位了的元素，永远能够压住没有定位的元素。\n\n （4）只有定位了的元素，才能有z-index值。也就是说，不管相对定位、绝对定位、固定定位，都可以使用z-index值。**而浮动的元素不能用**。\n\n （5）从父现象：父亲怂了，儿子再牛逼也没用。意思是，如果父亲1比父亲2大，那么，即使儿子1比儿子2小，儿子1也能在最上层。\n\n针对（1）（2）（3）条，举例如下：\n\n这是默认情况下的例子：（div2在上层，div1在下层）\n\n![](http://img.smyhvae.com/2015-10-03-css-32.png)\n\n现在加一个`z-index`属性，要求效果如下：\n\n![](http://img.smyhvae.com/2015-10-03-css-33.png)\n\n第五条分析：\n\n![](http://img.smyhvae.com/20180116_1445.png)\n\n\nz-index属性的应用还是很广泛的。当好几个已定位的标签出现覆盖的现象时，我们可以用这个z-index属性决定，谁处于最上方。也就是**层级**的应用。\n\n\n**层级：**\n\n（1）必须有定位（除去static）\n\n（2）用`z-index`来控制层级数。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/09-CSS案例讲解：博雅互动.md",
    "content": "---\ntitle: 09-CSS案例讲解：博雅互动\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 前言\n\n> CSS已经学了一些基础内容了，我们来讲解一个小案例吧。以[博雅互动](http://www.boyaa.com/)的官网首页举例。\n\n### 版心\n\n首页的**版心**如下：\n\n![](http://img.smyhvae.com/20170813_1535.png)\n\n这里我们要普及一个概念，叫“[版心](https://baike.baidu.com/item/%E7%89%88%E5%BF%83)”。**版心是页面中主要内容所在的区域。**\n\n比如说，网站左上角的logo，设计图给出的左边距是143像素，此时，我们千万不要以为，logo的左边距真的是143像素。因为设计图只是一个版心；而整个页面是处于浏览器的中间，浏览器的宽度是可以随时调整的。\n\n我们量一下中间四个方形图的width，是1000px，所以，网页版心的宽度是1000px。\n\n### 网页的结构\n\n从结构上来看，网页分为头部（导航栏）、banner区、内容区、底部。\n\n## 导航栏的制作\n\n在此我们只讲基础知识的使用，不涉及浏览器的优化。\n\n\n`class==header`这个div是顶部的通栏，我们在里面放一个1000px宽的div，作为通栏的版心，我一般把这个版心称为`class=inner_c`，c指的是center。\n\n`class=inner_c`不需要给高，因为它可以被内容撑高。\n\n现在我们需要在`class=inner_c`里放三个东西：左侧的logo、中间的导航栏、右侧的“加入我们”。\n\n接下来我们开始做右侧的「加入我们」，「加入我们」的背景是带圆角的矩形，这个圆角，实现的方式有两种：要么切图，要么用CSS3实现（IE 7、IE 8不兼容）。我们暂时使用切图来实现。\n\n我们最好把「加入我们」这个超链接`<a>`放到`div`里，然后设置div的margin和padding，而不是直接设置`<a>`的边距。\n\n我们起个名字叫`class=jrwm`是没有问题的，这在工作当中很常见，如果写成`class=join_us`反倒很别扭。\n\n暂时我们的做法是：\n\n- （1）给`class=jrwm_box`这个div里放一个`class=jrwm`的div。`class=jrwm`用来放绿色的背景图片。\n- （2）在`class=jrwm`里放一个超链接，并将超链接转为块级元素。\n\n最终，导航栏的代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <style type=\"text/css\">\n        *{\n            margin: 0px;\n            padding: 0px;\n        }\n        body{\n            font-size: 14px;\n            font-family: \"Microsoft YaHei\",\"SimSun\";\n            height: 8888px;\n        }\n        .header{\n            height: 58px;\n            background-color: #191D3A;\n        }\n        /*版心*/\n        .inner_c{\n            width: 1000px;\n            margin: 0 auto; /*让导航条、内容区域等部分的版心在父亲里居中*/\n        }\n        /*导航条的logo*/\n        .header .logo{\n            float: left;\n            margin-right: 40px;\n        }\n        .header .nav{\n            float: left;\n        }\n        .header .nav ul{\n            list-style: none; /*去掉列表前面的圆点*/\n        }\n        .header .nav ul li{\n            float: left;\n            width: 100px;\n            line-height: 58px; /*让行高等于这一行的高度，保证里面的文字垂直居中*/\n            border-left: 1px solid #252947; /*每个li之间有间隔线*/\n        }\n        .header .nav ul li.last{\n            border-right: 1px solid #252947;/*最后一个li的右边加间隔线*/\n        }\n        .header .nav ul li a{\n            display: block; /*将超链接转为块儿，可以保证其霸占父亲的整行*/\n            height: 58px;\n            text-decoration: none; /*去掉超链的下划线*/\n            color:#818496;\n            text-align: center;  /*让这个div内部的文本居中*/\n        }\n        .header .nav ul li a.current{\n            color:white;\n            background: #252947;\n        }\n        .header .nav ul li a:hover{\n            color: white;\n            background: #252947;\n        }\n\n        .header .jrwm_box{\n            float: left;\n            height: 58px;\n            width: 100px;\n            padding-left: 48px;\n            padding-top: 12px;\n\n        }\n        /*放背景图片的div*/\n        .header .jrwm_box .jrwm{\n            height: 34px;\n            background-image: url(images/jrwm.png);\n            background-repeat: no-repeat;\n            text-align: center; /*让这个div内部的超链接居中*/\n        }\n        .header .jrwm_box .jrwm a{\n            display: block; /*将超链接转为块儿，可以保证其霸占父亲的整行*/\n            line-height: 34px; /*让行高为背景图片的高度，可以保证超链接的文字在背景图片里垂直居中*/\n            text-decoration: none; /*去掉超链的下划线*/\n            color: white;\n        }\n\n    </style>\n</head>\n<body>\n    <div class=\"header\">\n        <div class=\"inner_c\">\n            <div class=\"logo\">\n                <img src=\"images/logo.png \" alt=\"\">\n            </div>\n            <div class=\"nav\">\n                <ul>\n                    <li><a href=\"#\" class=\"current\">首页</a></li>\n                    <li><a href=\"#\">博雅游戏</a></li>\n                    <li><a href=\"#\">博雅新闻</a></li>\n                    <li><a href=\"#\">关于我们</a></li>\n                    <li><a href=\"#\">客服中心</a></li>\n                    <li class=\"last\"><a href=\"#\">投资者关系</a></li>\n                </ul>\n            </div>\n            <div class=\"jrwm_box\">\n                <div class=\"jrwm\">\n                    <a href=\"https://www.google.com/\" target=\"_blank\">加入我们</a>\n                </div>\n            </div>\n        </div>\n    </div>\n</body>\n</html>\n```\n\n导航栏的效果如下：\n\n![](http://img.smyhvae.com/20180114_1332.gif)\n\n## banenr图\n\n> 因为涉及到 js 的内容，这里先不讲内容区域**轮播图**的效果。\n\n我们首先在导航条和banner图之间加一道墙，即`class=cl`，然后采用隔墙法对其设置`clear: both;`的属性。\n\n然后设置banner的背景图片属性，添加banner图。\n\n## 内容区域的制作\n\n导航栏+banner+内容区域的完整代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <style type=\"text/css\">\n        *{\n            margin: 0px;\n            padding: 0px;\n        }\n\n        /*清除浮动的影响*/\n        .cl{\n            clear: both;\n        }\n        body{\n            font-size: 14px;\n            font-family: \"Microsoft YaHei\",\"SimSun\";\n            height: 8888px;\n        }\n        .header{\n            height: 58px;\n            background-color: #191D3A;\n        }\n        /*版心*/\n        .inner_c{\n            width: 1000px;\n            margin: 0 auto; /*让导航条、内容区域等部分的版心在父亲里居中*/\n        }\n        /*导航条的logo*/\n        .header .logo{\n            float: left;\n            margin-right: 40px;\n        }\n        .header .nav{\n            float: left;\n        }\n        .header .nav ul{\n            list-style: none; /*去掉列表前面的圆点*/\n        }\n        .header .nav ul li{\n            float: left;\n            width: 100px;\n            line-height: 58px; /*让行高等于这一行的高度，保证里面的文字垂直居中*/\n            border-left: 1px solid #252947; /*每个li之间有间隔线*/\n        }\n        .header .nav ul li.last{\n            border-right: 1px solid #252947;/*最后一个li的右边加间隔线*/\n        }\n        .header .nav ul li a{\n            display: block; /*将超链接转为块儿，可以保证其霸占父亲的整行*/\n            height: 58px;\n            text-decoration: none; /*去掉超链的下划线*/\n            color:#818496;\n            text-align: center;  /*让这个div内部的文本居中*/\n        }\n        .header .nav ul li a.current{\n            color:white;\n            background: #252947;\n        }\n        .header .nav ul li a:hover{\n            color: white;\n            background: #252947;\n        }\n\n        .header .jrwm_box{\n            float: left;\n            height: 58px;\n            width: 100px;\n            padding-left: 48px;\n            padding-top: 12px;\n\n        }\n        /*放背景图片的div*/\n        .header .jrwm_box .jrwm{\n            height: 34px;\n            background-image: url(images/jrwm.png);\n            background-repeat: no-repeat;\n            text-align: center; /*让这个div内部的超链接居中*/\n        }\n        .header .jrwm_box .jrwm a{\n            display: block; /*将超链接转为块儿，可以保证其霸占父亲的整行*/\n            line-height: 34px; /*让行高为背景图片的高度，可以保证超链接的文字在背景图片里垂直居中*/\n            text-decoration: none; /*去掉超链的下划线*/\n            color: white;\n        }\n\n\n\n        .banner{\n            height: 465px;\n            background: url(images/banner.jpg) no-repeat center top;\n        }\n        .content{\n            padding-top: 50px;\n        }\n        .content .product{\n            height: 229px;\n            border-bottom: 1px solid #DBE1E7;\n        }\n        .content .product ul{\n            list-style: none;\n        }\n        .content .product ul li{\n            float: left;\n            width: 218px;\n            margin-right: 43px;\n        }\n        .content .product ul li.last{\n            margin-right: 0;\n            width: 217px;\n        }\n        .content .product ul li img{\n            width: 218px;\n            height: 130px;\n        }\n        .content .product ul li.last img{\n            width: 217px;\n        }\n\n        .content .product ul li h3{\n            text-align: center;\n            line-height: 38px;\n            font-size: 14px;\n            font-weight: bold;\n        }\n        .content .product ul li p.djbf{\n            text-align: center;\n            line-height: 16px;\n        }\n        .content .product ul li p.djbf a{\n            font-size: 12px;\n            color:#38B774;\n            text-decoration: none;\n            background:url(images/sanjiaoxing.png) no-repeat right 5px;\n            padding-right: 12px;\n        }\n\n    </style>\n</head>\n<body>\n    <div class=\"header\">\n        <div class=\"inner_c\">\n            <div class=\"logo\">\n                <img src=\"images/logo.png \" alt=\"\">\n            </div>\n            <div class=\"nav\">\n                <ul>\n                    <li><a href=\"#\" class=\"current\">首页</a></li>\n                    <li><a href=\"#\">博雅游戏</a></li>\n                    <li><a href=\"#\">博雅新闻</a></li>\n                    <li><a href=\"#\">关于我们</a></li>\n                    <li><a href=\"#\">客服中心</a></li>\n                    <li class=\"last\"><a href=\"#\">投资者关系</a></li>\n                </ul>\n            </div>\n            <div class=\"jrwm_box\">\n                <div class=\"jrwm\">\n                    <a href=\"https://www.google.com/\" target=\"_blank\">加入我们</a>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- 在导航条和banner之间隔一堵墙 -->\n    <div class=\"cl\"></div>\n\n    <div class=\"banner\"></div>\n\n    <!-- 内容区域 -->\n    <div class=\"content inner_c\">\n        <div class=\"product\">\n            <ul>\n                <li>\n                    <p><img src=\"images/pro1.jpg\" alt=\"\" /></p>\n                    <h3>BPT宣传片</h3>\n                    <p class=\"djbf\">\n                        <a href=\"#\">点击播放</a>\n                    </p>\n                </li>\n                <li>\n                    <p><img src=\"images/pro2.jpg\" alt=\"\" /></p>\n                    <h3>BPT宣传片</h3>\n                    <p class=\"djbf\">\n                        <a href=\"#\">点击播放</a>\n                    </p>\n                </li>\n                <li>\n                    <p><img src=\"images/pro3.jpg\" alt=\"\" /></p>\n                    <h3>BPT宣传片</h3>\n                    <p class=\"djbf\">\n                        <a href=\"#\">点击播放</a>\n                    </p>\n                </li>\n                <li class=\"last\">\n                    <p><img src=\"images/pro4.jpg\" alt=\"\" /></p>\n                    <h3>BPT宣传片</h3>\n                    <p class=\"djbf\">\n                        <a href=\"#\">点击播放</a>\n                    </p>\n                </li>\n            </ul>\n        </div>\n    </div>\n</body>\n</html>\n```\n\n代码解释：\n\n（1）导航栏，左侧的logo：\n\n**错误的写法：**\n\n可能会有人直接将img标签作为logo的布局：\n\n\n```html\n    <div class=\"logo\">\n        <img src=\"images/logo.png \" alt=\"\">\n    </div>\n```\n\n然后将img的样式设置为：\n\n```css\n    .header .logo{\n        float: left;\n        margin-right: 40px;\n    }\n```\n\n这样写虽然视觉效果上达到了，但是搜索引擎是搜不到图片的，不利于SEO。\n\n**正确的写法：**\n\n正确的写法是将超链接作为logo的布局，里面放入文字（文字可以被SEO）：\n\n```html\n\t\t\t<h1 class=\"logo\">\n\t\t\t\t<a href=\"#\">\n\t\t\t\t\t博雅互动-世界上最好的游戏公司\n\t\t\t\t</a>\n\t\t\t</h1>\n```\n\n然后将**logo设置为背景图**：\n\n```css\n\t\t.header .logo{\n\t\t\tfloat: left;\n\t\t\tpadding-left: 12px;\n\t\t\tmargin-right: 39px;\n\t\t\twidth: 174px;\n\t\t\theight: 58px;\n\t\t}\n\t\t.header .logo a{\n\t\t\tdisplay: block;\n\t\t\twidth: 174px;\n\t\t\theight: 58px;\n\t\t\tbackground:url(images/logo.png) no-repeat;\n\t\t\ttext-indent: -999em;\n\t\t}\n\n```\n\n由于搜索引擎是搜不到图片的，所以一定要把“博雅互动”这几个文字加上去，**然后通过`text-indent`缩进的属性把文字赶走到视线以外的地方**。这是做搜索引擎优化的一个重要的技巧。\n\n另外，背景要放在里层的a标签里，不要放在外层的h1标签里。假设背景图放在h1里，那么不管h1的padding有多大，背景图的位置都不会变。\n\n（1）内容区域，“点击播放”右侧的小三角形：\n\n我们在“点击播放”的右侧放了一个三角形。这个很有技巧。\n\n![](http://img.smyhvae.com/20180115_1356.png)\n\n代码截取如下：\n\n```css\n    .content .product ul li p.djbf a{\n        font-size: 12px;\n        color:#38B774;\n        text-decoration: none;\n        background:url(images/sanjiaoxing.png) no-repeat right center;\n        padding-right: 12px;\n    }\n```\n\n上方代码中，我们在第6行给“点击播放”这个超链接加一个右padding（很关键），然后在第5行把小三角这个背景图放在右padding的位置，就能达到想要的视觉效果。\n\n（2）导航栏+banner+内容区域的效果如下：\n\n![](http://img.smyhvae.com/20180114_1405.png)\n\n工程文件：[2018-03-20-boya.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-03-20-boya.rar)\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/10-CSS3选择器详解.md",
    "content": "---\ntitle: 10-CSS3选择器详解\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## CSS3介绍\n\nCSS3在CSS2基础上，**增强**或**新增**了许多特性， 弥补了CSS2的众多不足之处，使得Web开发变得更为高效和便捷。\n\n### CSS3的现状\n\n- 浏览器支持程度不够好，有些需要添加**私有前缀**\n\n- 移动端支持优于PC端\n\n- 不断改进中\n\n- 应用相对广泛\n\n### 应对的策略：渐进增强\n\n- （1）坚持**渐进增强**的原则：**让低版本浏览器能正常访问页面，高版本的浏览器用户体验更好**。【重要】\n\n比如说，同样是一个头像，可能在低版本的浏览器中，头像方的；在高版本的浏览器中，头像是圆的。\n\n- （2）考虑用户群体。\n\n- （3）遵照产品的方案。\n\n参考链接：\n\n- [渐进增强 VS 优雅降级 | 简书](https://www.jianshu.com/p/d313f1108862)\n\n- [渐进增强和优雅降级之间的不同（面试题目）](https://www.cnblogs.com/iceflorence/archive/2017/03/27/6625466.html)\n\n### 浏览器的版本问题\n\n由于CSS3普遍存在兼容性问题，为了避免因兼容性带来的干扰，浏览器的建议版本为：\n\n- Chrome浏览器 version 46+\n\n- Firefox浏览器 firefox 42+\n\n### 如何使用手册\n\nCSS参考手册的网址：<http://css.doyoe.com/>\n\nCSS参考手册的下载链接：<http://download.csdn.net/download/smyhvae/10243974>\n\n在查看[CSS参考手册](http://download.csdn.net/download/smyhvae/10243974)时，需要注意以下符号：\n\n![](http://img.smyhvae.com/20180206_2150.png)\n\n比如说，`{1,4}`表示可以设置一至四个参数。\n\n下面讲CSS3的基础知识。本文讲一下 CSS3 选择器的内容。\n\n## CSS3 选择器\n\n我们之前学过 CSS 的选择器，比如：\n\n```\n     div 标签选择器\n\n     .box 类名选择器\n\n     #box　id选择器\n\n     div p 后代选择器\n\n     div.box 交集选择器\n\n     div,p,span 并集选择器\n\n     div>p 子代选择器\n\n     * : 通配符\n\n     div+p: 选中div后面相邻的第一个p\n\n     div~p: 选中的div后面所有的p\n\n```\n\nCSS3新增了许多**灵活**查找元素的方法，极大的提高了查找元素的效率和**精准度**。CSS3选择器与 jQuery 中所提供的**绝大部分**选择器兼容。\n\n### 属性选择器\n\n属性选择器的标志性符号是 `[]`。\n\n匹配含义：\n\n```\n^：开头  $：结尾  *：包含\n```\n\n格式：\n\n- `E[title]` 选中页面的E元素，并且E存在 title 属性即可。\n\n- `E[title=\"abc\"]`选中页面的E元素，并且E需要带有title属性，且属性值**完全等于**abc。\n\n- `E[attr~=val]`  选择具有 att 属性且属性值为：用空格分隔的字词列表，其中一个等于 val 的E元素。\n\n- `E[attr|=val]` 表示要么是一个单独的属性值，要么这个属性值是以“-”分隔的。\n\n- `E[title^=\"abc\"]` 选中页面的E元素，并且E需要带有 title 属性,属性值以 abc 开头。\n\n- `E[title$=\"abc\"]` 选中页面的E元素，并且E需要带有 title 属性,属性值以 abc 结尾。\n\n- `E[title*=\"abc\"]` 选中页面的E元素，并且E需要带有 title 属性,属性值任意位置包含abc。\n\n比如说，我们用属性选择器去匹配标签的className，是非常方便的。\n\n这里我们用class属性来举例。代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>选择器 - 属性</title>\n    <style>\n        body {\n            margin: 0;\n            padding: 0;\n            font-family: '微软雅黑';\n            background-color: #F7F7F7;\n        }\n\n        .wrapper {\n            width: 1024px;\n            margin: 0 auto;\n        }\n\n        .wrapper > section {\n            min-height: 300px;\n            margin-bottom: 30px;\n            box-shadow: 1px 1px 4px #DDD;\n            background-color: #FFF;\n        }\n\n        .wrapper > header {\n            text-align: center;\n            line-height: 1;\n            padding: 20px;\n            margin-bottom: 10px;\n            font-size: 30px;\n        }\n\n        .wrapper section > header {\n            line-height: 1;\n            padding: 10px;\n            font-size: 22px;\n            color: #333;\n            background-color: #EEE;\n        }\n\n        .wrapper .wrap-box {\n            padding: 20px;\n        }\n\n        form {\n            width: 300px;\n            height: 300px;\n            margin: 0 auto;\n        }\n\n        form input[type=\"text\"] {\n            width: 200px;\n            height: 30px;\n        }\n\n        form input[type=\"password\"] {\n            width: 200px;\n            height: 30px;\n        }\n\n        .attr1 {\n\n        }\n\n        .download {\n        }\n\n        .attr1 a[href=\"./a.rmvb\"] {\n            color: red;\n        }\n\n        .attr1 a[href=\"./b.rmvb\"] {\n            color: pink;\n        }\n\n        /*  E[attr~=val] 表示的一个单独的属性值 这个属性值是以空格分隔的*/\n        .attr2 a[class~=\"download\"] {\n            color: red;\n        }\n\n        /*  E[attr|=val] 表示的要么一个单独的属性值 要么这个属性值是以\"-\"分隔的*/\n        .attr3 a[class|=\"download\"] {\n            color: red;\n        }\n\n        /*  E[attr*=val] 表示的属性值里包含val字符并且在“任意”位置 */\n        .attr4 a[class*=\"download\"] {\n            color: red;\n        }\n\n        /*  E[attr^=val] 表示的属性值里包含val字符并且在“开始”位置 */\n        .attr5 a[class^=\"download\"] {\n            color: red;\n        }\n\n        /*  E[attr$=val] 表示的属性值里包含val字符并且在“结束”位置 */\n        .attr6 a[class$=\"download\"] {\n            color: red;\n        }\n    </style>\n</head>\n<body>\n<div class=\"wrapper\">\n    <header>CSS3-属性选择器</header>\n    <section>\n        <header>简介</header>\n        <div class=\"wrap-box\">\n            <form action=\"\">\n\n                <ul>\n                    <li>\n                        姓名: <input type=\"text\">\n                    </li>\n                    <li>\n                        密码: <input type=\"password\">\n                    </li>\n\n                    <li>\n                        性别: <input type=\"radio\">男\n                        <input type=\"radio\"> 女\n                    </li>\n                    <li>\n                        兴趣: <input type=\"checkbox\" name=\"\" id=\"\">写代码\n                    </li>\n                    <li>\n                        <input type=\"submit\" value=\"提交\">\n                    </li>\n                </ul>\n            </form>\n        </div>\n    </section>\n    <section class=\"attr1\">\n        <header>E[attr]</header>\n        <div class=\"wrap-box\">\n            <a href=\"./a.rmvb\" class=\"download download-movie\">下载</a>\n            <a href=\"./b.rmvb\" class=\"download download-movie\">下载</a>\n            <a href=\"./a.mp3\" class=\"download download-music\">下载</a>\n        </div>\n    </section>\n    <section class=\"attr2\">\n        <header>E[attr~=attr]</header>\n        <div class=\"wrap-box\">\n            <a href=\"./a.rmvb\" class=\"download download-movie\">下载</a>\n            <a href=\"./b.rmvb\" class=\"download download-movie\">下载</a>\n            <a href=\"./a.mp3\" class=\"download download-music\">下载</a>\n        </div>\n    </section>\n    <section class=\"attr3\">\n        <header>E[attr|=attr]</header>\n        <div class=\"wrap-box\">\n            <a href=\"./a.rmvb\" class=\"download\">下载</a>\n            <a href=\"./b.rmvb\" class=\"download-movie\">下载</a>\n            <a href=\"./a.mp3\" class=\"download-music\">下载</a>\n        </div>\n    </section>\n    <section class=\"attr4\">\n        <header>E[attr*=val]</header>\n        <div class=\"wrap-box\">\n            <a href=\"./a.rmvb\" class=\"download\">下载</a>\n            <a href=\"./b.rmvb\" class=\"moviedownload\">下载</a>\n            <a href=\"./a.mp3\" class=\"downloadmusic\">下载</a>\n        </div>\n    </section>\n    <section class=\"attr5\">\n        <header>E[attr^=val]</header>\n        <div class=\"wrap-box\">\n            <a href=\"./a.rmvb\" class=\"download\">下载</a>\n            <a href=\"./b.rmvb\" class=\"moviedownload\">下载</a>\n            <a href=\"./a.mp3\" class=\"downloadmusic\">下载</a>\n        </div>\n    </section>\n    <section class=\"attr6\">\n        <header>E[attr$=val]</header>\n        <div class=\"wrap-box\">\n            <a href=\"./a.rmvb\" class=\"download\">下载</a>\n            <a href=\"./b.rmvb\" class=\"moviedownload\">下载</a>\n            <a href=\"./a.mp3\" class=\"downloadmusic\">下载</a>\n        </div>\n    </section>\n</div>\n</body>\n</html>\n```\n\n最后来张表格：\n\n![](http://img.smyhvae.com/20180207_1500.png)\n\n### 结构伪类选择器\n\n伪类选择器的标志性符号是 `:`。\n\nCSS中有一些伪类选择器，比如`:link`、`:visited`、`:hover`、`:active`、`:focus`。\n\nCSS3又新增了其它的伪类选择器。这一小段，我们来学习CSS3中的**结构伪类选择器**：即通过**结构**来进行筛选。\n\n\n**1、格式：（第一部分）**（重要）\n\n- `E:first-child` 匹配父元素的第一个子元素E。\n\n- `E:last-child` 匹配父元素的最后一个子元素E。\n\n- `E:nth-child(n)` 匹配父元素的第n个子元素E。**注意**，盒子的编号是从`1`开始算起，不是从`0`开始算起。\n\n- `E:nth-child(odd)` 匹配奇数\n\n- `E:nth-child(even)` 匹配偶数\n\n- `E:nth-last-child(n)` 匹配父元素的倒数第n个子元素E。\n\n理解：\n\n（1）这里我们要好好理解**父元素**的含义，它指的是：以 E 元素的父元素为参考。\n\n（2）注意：以上选择器中所选到的元素的类型，必须是指定的类型E，如果选不中，则无效。这个要好好理解，具体可以看CSS参考手册中的`E:nth-child(n)`的示例。我们可以理解成：**先根据选择器找到选中的全部位置，如果发现某个位置不是类型E，则该位置失效**。\n\n（3）另外，`E:nth-child(n)`这个属性也很有意思。比如，针对下面这样一组标签：\n\n```html\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n        <li>4</li>\n        <li>5</li>\n        <li>6</li>\n        <li>7</li>\n        <li>8</li>\n        <li>9</li>\n        <li>10</li>\n    </ul>\n```\n\n上方代码中：\n\n- 如果选择器写成`li:nth-child(2)`，则表示第2个 `li`。\n\n- 如果选择器写成`li:nth-child(n)`，则表示**所有的**`li`。因为此时的 `n` 表示 0,1,2,3,4,5,6,7,8.....（当n小于1时无效，因为n = 0 也是不会选中的）\n\n- 如果选择器写成`li:nth-child(2n)`，则表示所有的**第偶数个** li。\n\n- 如果选择器写成`li:nth-child(2n+1)`，则表示所有的**第奇数个** li。\n\n- 如果选择器写成`li:nth-child(-n+5)`，则表示**前5个** li。\n\n- 如果选择器写成`li:nth-last-child(-n+5)`，则表示**最后5个** li。\n\n- 如果选择器写成`li:nth-child(7n)`，则表示选中7的倍数。。\n\n上面列举的选择器中，我们只要记住： `n` 表示 0,1,2,3,4,5,6,7,8.....就很容易明白了。\n\n**2、格式：（第二部分）**\n\n- `E:first-of-type` 匹配同类型中的第一个同级兄弟元素E。\n\n- `E:last-of-type` 匹配同类型中的最后一个同级兄弟元素E。\n\n- `E:nth-of-type(n)` 匹配同类型中的第n个同级兄弟元素E。\n\n- `E:nth-last-of-type(n)` 匹配同类型中的倒数第n个同级兄弟元素E。\n\n既然上面这几个选择器带有`type`，我们可以这样理解：先在同级里找到所有的E类型，然后根据 n 进行匹配。\n\n\n**3、格式：（第三部分）**\n\n- `E:empty` 匹配没有任何子节点（包括空格等text节点）的元素E。\n\n- `E:target` 匹配相关URL指向的E元素。要配合锚点使用。\n\n\n**举例：**\n\n我们可以把多个伪类选择器结合起来使用，比如：\n\n![](http://img.smyhvae.com/20180207_1340.png)\n\n\n如果想把上图中，第一行的前三个 span 标红，我们可以这样使用结构伪类选择器：\n\n```css\n\tdt:first-child span:nth-of-type(-n+3) {\n\t\tcolor: red;\n\t}\n```\n\n最后来张表格：\n\n![](http://img.smyhvae.com/20180207_1502.png)\n\n### 伪元素选择器\n\n伪元素选择器的标志性符号是 `::`。\n\n**1、格式：（第一部分）**\n\n- `E::before` 设置在 元素E 前面（依据对象树的逻辑结构）的内容，配合content属性一起使用。\n\n- `E::after` 设置在 元素E 后面（依据对象树的逻辑结构）的内容，配合content属性一起使用。\n\n`E:after`、`E:before `在旧版本里是伪类，在 CSS3 这个新版本里是伪元素。新版本里，`E:after`、`E:before`会被自动识别为`E::after`、`E::before`，按伪元素来对待，这样做的目的是用来做兼容处理。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n\n        /*::before 和::after 是通过 css 模拟出的html标签的效果*/\n        span::before{\n            content:\"smyhvae\";\n            color:red;\n            background-color: pink;\n            width: 50px;\n            height: 50px;\n            display: inline-block;\n        }\n\n        span::after{\n            content:\"永不止步\";\n            color:yellowgreen;\n        }\n\n        /*给原本的span标签设置一个默认的属性*/\n        span{\n            border: 1px solid #000;\n        }\n    </style>\n</head>\n<body>\n\n<span>生命壹号</span>\n</body>\n</html>\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_1409.png)\n\n**上图可以看出**：\n\n- 通过伪元素选择器，就可以添加出类似于span标签的效果（记得要结合 content 属性使用）。\n\n- 通过这两个属性添加的伪元素，是**行内元素**，需要转换成块元素才能设置宽高。\n\n**2、格式：（第二部分）**\n\n- `E::first-letter` 设置元素 E 里面的**第一个字符**的样式。\n\n- `E::first-line` 设置元素 E 里面的**第一行**的样式。\n\n- `E::selection` 设置元素 E 里面被鼠标选中的区域的样式（一般设置颜色和背景色）。\n\n`E::first-letter` 的举例：\n\n![](http://img.smyhvae.com/20180207_1430.png)\n\n`E::first-line`的举例：\n\n![](http://img.smyhvae.com/20180207_1433.png)\n\n最后来张表格：\n\n![](http://img.smyhvae.com/20180207_1503.png)\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/11-CSS3属性详解（一）.md",
    "content": "---\ntitle: 11-CSS3属性详解（一）\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 前言\n\n我们在上一篇文章中学习了[CSS3的选择器](http://www.cnblogs.com/smyhvae/p/8426799.html)，本文来学一下CSS3的一些属性。\n\n本文主要内容：\n\n- 文本\n\n- 盒模型中的 box-sizing 属性\n\n- 处理兼容性问题：私有前缀\n\n- 边框\n\n- 背景属性\n\n- 渐变\n\n\n## 文本\n\n### text-shadow：设置文本的阴影\n\n格式举例：\n\n```javascript\n\ttext-shadow: 20px 27px 22px pink;\n```\n\n参数解释：水平位移 垂直位移 模糊程度 阴影颜色。\n\n效果举例：\n\n![](http://img.smyhvae.com/20180207_1600.png)\n\n### 举例：凹凸文字效果\n\ntext-shadow 可以设置多个阴影，每个阴影之间使用逗号隔开。我们来看个例子。\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        body {\n            background-color: #666;\n        }\n\n        div {\n            font-size: 100px;\n            text-align: center;\n            font-weight: bold;\n            font-family: \"Microsoft Yahei\";\n            color: #666;\n        }\n\n        /* text-shadow 可以设置多个阴影，每个阴影之间使用逗号隔开*/\n        .tu {\n            text-shadow: -1px -1px 1px #fff, 1px 1px 1px #000;\n        }\n\n        .ao {\n            text-shadow: -1px -1px 1px #000, 1px 1px 1px #fff;\n        }\n    </style>\n</head>\n<body>\n<div class=\"ao\">生命壹号</div>\n<div class=\"tu\">生命壹号</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_1617.png)\n\n上图中，实现凹凸文字效果的方式比较简单，给左上角放黑色的阴影，右下角放白色的阴影，就达到了凹下去的效果。\n\n\n## 盒模型中的 box-sizing 属性\n\n我们在**[之前的文章](http://www.cnblogs.com/smyhvae/p/7256371.html)**中专门讲过盒子模型。\n\nCSS3 对盒模型做出了新的定义，即允许开发人员**指定盒子宽度和高度的计算方式**。\n\n这就需要用到 `box-sizing`属性。它的属性值可以是：`content-box`、`border-box`。解释如下。\n\n**外加模式：**（css的默认方式）\n\n```javascript\n\tbox-sizing: content-box;\n```\n\n解释：此时设置的 width 和 height 是**内容区域**的宽高。`盒子的实际宽度 = 设置的 width + padding + border`。此时改变 padding 和 border 的大小，也不会改变内容的宽高，而是盒子的总宽高发生变化。\n\n\n**内减模式：**【需要注意】\n\n```javascript\n\tbox-sizing: border-box;\n```\n\n\n解释：此时设置的 width 和 height 是**盒子**的总宽高。`盒子的实际宽度 = 设置的 width`。此时改变 padding 和 border 的大小，会改变内容的宽高，盒子的总宽高不变。\n\n\n## 处理兼容性问题：私有前缀\n\n通过网址<http://caniuse.com/> 可以查询CSS3各特性的支持程度。\n\n处理兼容性问题的常见方法：为属性添加**私有前缀**。\n\n如此方法不能解决，应尽量避免使用，无需刻意去处理CSS3的兼容性问题。\n\n**私有前缀的举例**：\n\n比如说，我想给指定的div设置下面这样一个属性：\n\n```css\n\tbackground: linear-gradient(left, green, yellow);\n```\n\n上面这个属性的作用是：添加从左到右的线性渐变，颜色从绿色变为黄色。\n\n如果直接这样写属性，是看不到效果的：\n\n![](http://img.smyhvae.com/20180207_1700.png)\n\n此时，我们可以**为浏览器添加不同的私有前缀**，属性就可以生效了。\n\n格式如下：\n\n```html\n    -webkit-: 谷歌 苹果\n    -moz-:火狐\n    -ms-：IE\n    -o-：欧朋\n```\n\n格式举例如下：\n\n```css\n    background: -webkit-linear-gradient(left, green, yellow);\n    background: -moz-linear-gradient(left, green, yellow);\n    background: -ms-linear-gradient(left, green, yellow);\n    background: -o-linear-gradient(left, green, yellow);\n    background: linear-gradient(left, green, yellow);\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_1710.png)\n\n## 边框\n\n边框的属性很多，其中**边框圆角**和**边框阴影**这两个属性，应用十分广泛，兼容性也相对较好，且符合**渐进增强**的原则，需要重点熟悉。\n\n### 边框圆角：`border-radius` 属性\n\n边框的每个圆角，本质上是一个圆，圆有**水平半径**和**垂直半径**：如果二者相等，就是圆；如果二者不等， 就是椭圆。\n\n单个属性的写法：\n\n```css\n\tborder-top-left-radius: 60px 120px;        //参数解释：水平半径   垂直半径\n\n\tborder-top-right-radius: 60px 120px;\n\n\tborder-bottom-left-radius: 60px 120px;\n\n\tborder-bottom-right-radius: 60px 120px;\n\n```\n\n复合写法：\n\n```\n\tborder-radius: 60px/120px;             //参数：水平半径/垂直半径\n\n\tborder-radius: 20px 60px 100px 140px;  //从左上开始，顺时针赋值。如果当前角没有值，取对角的值\n\n\tborder-radius: 20px 60px;\n```\n\n最简洁的写法：（四个角的半径都相同时）\n\n```\n\tborder-radius: 60px;\n```\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n\n        .parent {\n            width: 400px;\n        }\n        .box {\n            width: 100px;\n            height: 100px;\n            float: left;\n            border: 1px solid rgb(144, 12, 63);\n            margin: 20px;\n            text-align: center;\n            line-height: 100px;\n            color: #fff;\n            font-size: 50px;\n            background-color: rgb(255, 141, 26);\n\n        }\n\n        /*画圆形的方式一*/\n        .box:nth-child(1) {\n            border-radius: 50px;\n        }\n\n        /*画圆形的方式二*/\n        .box:nth-child(2) {\n            border-radius: 50%;\n        }\n\n        .box:nth-child(3) {\n            border-radius: 100px 0 0 0;\n        }\n\n        .box:nth-child(4) {\n            border-radius: 100px/50px;\n        }\n\n        .box:nth-child(5) {\n            border-radius: 10%;\n        }\n\n        .box:nth-child(6) {\n            border-radius: 0 100px;\n        }\n\n    </style>\n</head>\n<body>\n\n<div class=\"parent\">\n    <div class=\"box\">1</div>\n    <div class=\"box\">2</div>\n    <div class=\"box\">3</div>\n    <div class=\"box\">4</div>\n    <div class=\"box\">5</div>\n    <div class=\"box\">6</div>\n</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_1750.png)\n\n### 边框阴影：`box-shadow` 属性\n\n格式举例：\n\n```javascript\n\tbox-shadow: 水平偏移 垂直偏移 模糊程度 阴影大小 阴影颜色\n\n\tbox-shadow: 15px 21px 48px -2px #666;\n```\n\n参数解释：\n\n- 水平偏移：正值向右 负值向左。\n\n- 垂直偏移：正值向下 负值向上。\n\n- 模糊程度：不能为负值。\n\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_2027.png)\n\n另外，后面还可以再加一个inset属性，表示内阴影。如果不写，则默认表示外阴影。例如：\n\n```javascript\n\tbox-shadow:3px 3px 3px 3px #666 inset;\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180207_2028.png)\n\n**注意**：设置边框阴影不会改变盒子的大小，即不会影响其兄弟元素的布局。\n\n我们还可以设置多重边框阴影，实现更好的效果，增强立体感。\n\n### 边框图片\n\n边框图片有以下属性：\n\n```javascript\n\t/* 边框图片的路径*/\n\tborder-image-source: url(\"images/border.png\");\n\n\t/* 图片边框的裁剪*/\n\tborder-image-slice: 27;\n\n\t/*图片边框的宽度*/\n\tborder-image-width: 27px;\n\n\t/*边框图片的平铺*/\n\t/* repeat :正常平铺 但是可能会显示不完整*/\n\t/*round: 平铺 但是保证 图片完整*/\n\t/*stretch: 拉伸显示*/\n\tborder-image-repeat: stretch;\n```\n\n我们也可以写成一个综合属性：\n\n```javascript\n\t border-image: url(\"images/border.png\") 27/20px round;\n```\n\n这个属性要好好理解，我们假设拿下面这张图来作为边框图片：\n\n![](http://img.smyhvae.com/20180207_2045.png)\n\n![](http://img.smyhvae.com/20180207_2046.png)\n\n这张图片将会被“切割”成**九宫格**形式，然后进行平铺。四个角位置、形状保持不变，中心位置和水平垂直向两个方向平铺：\n\n![](http://img.smyhvae.com/20180207_2050.png)\n\n再具体一点：\n\n![](http://img.smyhvae.com/20180207_2051.png)\n\n\n### 常见的边框图片汇总\n\n```html\n\n```\n\n\nCSS3的更多属性，且看下文继续。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "02-CSS基础/12-CSS3属性详解：动画详解.md",
    "content": "---\ntitle: 12-CSS3属性详解：动画详解\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 前言\n\n本文主要内容：\n\n- 过渡：transition\n\n- 2D 转换 transform\n\n- 3D 转换 transform\n\n- 动画：animation\n\n## 过渡：transition\n\n`transition`的中文含义是**过渡**。过渡是CSS3中具有颠覆性的一个特征，可以实现元素**不同状态间的平滑过渡**（补间动画），经常用来制作动画效果。\n\n- 补间动画：自动完成从起始状态到终止状态的的过渡。不用管中间的状态。\n\n- 帧动画：通过一帧一帧的画面按照固定顺序和速度播放。如电影胶片。\n\n参考链接：[补间动画基础](http://mux.alimama.com/posts/1009)\n\ntransition 包括以下属性：\n\n- `transition-property: all;`  如果希望所有的属性都发生过渡，就使用all。\n\n- `transition-duration: 1s;` 过渡的持续时间。\n\n- `transition-timing-function: linear;`  运动曲线。属性值可以是：\n\t- `linear` 线性\n\t- `ease`  减速\n\t- `ease-in` 加速\n\t- `ease-out` 减速\n\t- `ease-in-out`  先加速后减速\n\n- `transition-delay: 1s;` 过渡延迟。多长时间后再执行这个过渡动画。\n\n上面的四个属性也可以写成**综合属性**：\n\n```javascript\n\ttransition: 让哪些属性进行过度 过渡的持续时间 运动曲线 延迟时间;\n\n\ttransition: all 3s linear 0s;\n```\n\n其中，`transition-property`这个属性是尤其需要注意的，不同的属性值有不同的现象。我们来示范一下。\n\n如果设置 `transition-property: width`，意思是只让盒子的宽度在变化时进行过渡。效果如下：\n\n![](http://img.smyhvae.com/20180208_1440.gif)\n\n如果设置 `transition-property: all`，意思是让盒子的所有属性（包括宽度、背景色等）在变化时都进行过渡。效果如下：\n\n![](http://img.smyhvae.com/20180208_1445.gif)\n\n### 案例：小米商品详情\n\n代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CSS 过渡</title>\n    <style>\n        body {\n            margin: 0;\n            padding: 0;\n            background-color: #eeeeee;\n        }\n\n        .content {\n            width: 800px;\n            height: 320px;\n            padding-left: 20px;\n            margin: 80px auto;\n        }\n\n        .item {\n            width: 230px;\n            height: 300px;\n            text-align: center;\n            margin-right: 20px;\n            background-color: #FFF;\n            float: left;\n            position: relative;\n            top: 0;\n            overflow: hidden; /* 让溢出的内容隐藏起来。意思是让下方的橙色方形先躲起来 */\n            transition: all .5s; /* 从最初到鼠标悬停时的过渡 */\n        }\n\n        .item img {\n            margin-top: 30px;\n        }\n\n        .item .desc {\n            position: absolute;\n            left: 0;\n            bottom: -80px;\n            width: 100%;\n            height: 80px;\n            background-color: #ff6700;\n            transition: all .5s;\n        }\n\n        /* 鼠标悬停时，让 item 整体往上移动5px，且加一点阴影 */\n        .item:hover {\n            top: -5px;\n            box-shadow: 0 0 15px #AAA;\n        }\n\n        /* 鼠标悬停时，让下方的橙色方形现身 */\n        .item:hover .desc {\n            bottom: 0;\n        }\n    </style>\n</head>\n<body>\n<div class=\"content\">\n    <div class=\"item\">\n        <img src=\"./images/1.png\" alt=\"\">\n    </div>\n\n    <div class=\"item\">\n        <img src=\"./images/2.png\" alt=\"\">\n        <span class=\"desc\"></span>\n    </div>\n    <div class=\"item\">\n        <img src=\"./images/3.jpg\" alt=\"\">\n        <span class=\"desc\"></span>\n    </div>\n</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180208_1500.gif)\n\n动画效果录制的比较差，但真实体验还是可以的。\n\n工程文件：[2018-02-08-小米商品详情过渡](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-08-%E5%B0%8F%E7%B1%B3%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%E8%BF%87%E6%B8%A1.rar)\n\n\n\n\n## 2D 转换\n\n**转换**是 CSS3 中具有颠覆性的一个特征，可以实现元素的**位移、旋转、变形、缩放**，甚至支持矩阵方式。\n\n转换再配合过渡和动画，可以取代大量早期只能靠 Flash 才可以实现的效果。\n\n在 CSS3 当中，通过 `transform` 转换来实现 2D 转换或者 3D 转换。\n\n- 2D转换包括：缩放、移动、旋转。\n\n我们依次来讲解。\n\n### 1、缩放：`scale`\n\n格式：\n\n```javascript\n\ttransform: scale(x, y);\n\n\ttransform: scale(2, 0.5);\n```\n\n参数解释： x：表示水平方向的缩放倍数。y：表示垂直方向的缩放倍数。如果只写一个值就是等比例缩放。\n\n取值：大于1表示放大，小于1表示缩小。不能为百分比。\n\n格式举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 1000px;\n            margin: 100px auto;\n        }\n\n        .box div {\n            width: 300px;\n            height: 150px;\n            background-color: pink;\n            float: left;\n            margin-right: 15px;\n            color: white;\n            text-align: center;\n            font: 400 30px/150px “宋体”;\n\n        }\n\n        .box .box2 {\n            background-color: green;\n            transition: all 1s;\n        }\n\n        .box .box2:hover {\n            /*width: 500px;*/\n            /*height: 400px;*/\n            background-color: yellowgreen;\n\n            /* transform:  css3中用于做变换的属性\n                scale(x,y)：缩放 */\n            transform: scale(2, 0.5);\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"box\">\n    <div class=\"box1\">1</div>\n    <div class=\"box2\">2</div>\n    <div class=\"box3\">3</div>\n</div>\n\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_1551.gif)\n\n上图可以看到，给 box1 设置 2D 转换，并不会把兄弟元素挤走。\n\n### 2、位移：translate\n\n格式：\n\n\n```javascript\n\ttransform: translate(水平位移, 垂直位移);\n\n\ttransform: translate(-50%, -50%);\n```\n\n参数解释：\n\n- 参数为百分比，相对于自身移动。\n\n- 正值：向右和向下。 负值：向左和向上。如果只写一个值，则表示水平移动。\n\n格式举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 1000px;\n            margin: 100px auto;\n        }\n\n        .box > div {\n            width: 300px;\n            height: 150px;\n            border: 1px solid #000;\n            background-color: red;\n            float: left;\n            margin-right: 30px;\n        }\n\n        div:nth-child(2) {\n            background-color: pink;\n            transition: all 1s;\n        }\n\n        /* translate:(水平位移，垂直位移)*/\n        div:nth-child(2):hover {\n            transform: translate(-50%, -50%);\n        }\n    </style>\n\n</head>\n<body>\n<div class=\"box\">\n    <div class=\"box1\">1</div>\n    <div class=\"box2\">2</div>\n    <div class=\"box3\">3</div>\n</div>\n\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_1600.gif)\n\n上图中，因为我在操作的时候，鼠标悬停后，立即进行了略微的移动，所以产生了两次动画。正确的效果应该是下面这样的：\n\n\n![](http://img.smyhvae.com/20180208_1605.gif)\n\n**应用：**让绝对定位中的盒子在父亲里居中\n\n我们知道，如果想让一个**标准流中的盒子在父亲里居中**（水平方向看），可以将其设置`margin: 0 auto`属性。\n\n可如果盒子是绝对定位的，此时已经脱标了，如果还想让其居中（位于父亲的正中间），可以这样做：\n\n```\n\tdiv {\n\t\twidth: 600px;\n\t\theight: 60px;\n\t\tposition: absolute;  绝对定位的盒子\n\t\tleft: 50%;           首先，让左边线居中\n\t\ttop: 0;\n\t\tmargin-left: -300px;  然后，向左移动宽度（600px）的一半\n\t}\n```\n\n如上方代码所示，我们先让这个宽度为600px的盒子，左边线居中，然后向左移动宽度（600px）的一半，就达到效果了。\n\n![](http://img.smyhvae.com/20180116_1356.png)\n\n现在，我们还可以利用偏移 translate 来做，这也是比较推荐的写法：\n\n```javascript\n\tdiv {\n\t    width: 600px;\n\t    height: 60px;\n\t    background-color: red;\n\t    position: absolute;       绝对定位的盒子\n\t    left: 50%;               首先，让左边线居中\n\t    top: 0;\n\t    transform: translate(-50%);    然后，利用translate，往左走自己宽度的一半【推荐写法】\n\t}\n```\n\n### 3、旋转：rotate\n\n格式：\n\n```javascript\n\ttransform: rotate(角度);\n\n\ttransform: rotate(45deg);\n```\n\n参数解释：正值 顺时针；负值：逆时针。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 200px;\n            height: 200px;\n            background-color: red;\n            margin: 50px auto;\n            color: #fff;\n            font-size: 50px;\n            transition: all 2s; /* 过渡：让盒子在进行 transform 转换的时候，有个过渡期 */\n        }\n\n        /* rotate（角度）旋转 */\n        .box:hover {\n            transform: rotate(-405deg); /* 鼠标悬停时，让盒子进行旋转 */\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"box\">1</div>\n\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_1630.gif)\n\n注意，上方代码中，我们给盒子设置了 transform 中的 rotate 旋转，但同时还要给盒子设置 transition 过渡。如果没有这行过渡的代码，旋转会直接一步到位，效果如下：（不是我们期望的效果）\n\n![](http://img.smyhvae.com/20180208_1635.gif)\n\n**案例1：**小火箭\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        html,body{\n            height:100%;\n        }\n\n        body{\n            background-color: #DE8910;\n        }\n        .rocket{\n            position: absolute;\n            left:100px;\n            top:600px;\n            height: 120px;\n            transform:translate(-200px ,200px) rotate(45deg);\n            transition:all 1s ease-in;\n        }\n\n        body:hover .rocket{\n            transform:translate(500px,-500px) rotate(45deg);\n        }\n    </style>\n</head>\n<body>\n    <img  class=\"rocket\" src=\"images/rocket.png\" alt=\"\"/>\n</body>\n</html>\n```\n\n\n上方代码中，我们将 transform 的两个小属性合并起来写了。\n\n小火箭图片的url：<http://img.smyhvae.com/20180208-rocket.png>\n\n**案例2：**扑克牌\n\nrotate 旋转时，默认是以盒子的正中心为坐标原点的。如果想**改变旋转的坐标原点**，可以用`transform-origin`属性。格式如下：\n\n\n```javascript\n\ttransform-origin: 水平坐标 垂直坐标;\n\n\ttransform-origin: 50px 50px;\n\n\ttransform-origin: center bottom;   //旋转时，以盒子底部的中心为坐标原点\n```\n\n我们来看一下 rotate 结合 transform-origin 的用法举例。\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        body {\n            /*background-color: #eee;*/\n        }\n\n        .box {\n\n            width: 300px;\n            height: 440px;\n            margin: 100px auto;\n            position: relative;\n        }\n\n        img {\n            width: 100%;\n            transition: all 1.5s;\n            position: absolute;     /* 既然扑克牌是叠在一起的，那就都用绝对定位 */\n            left: 0;\n            top: 0;\n\n            transform-origin: center bottom; /*旋转时，以盒子底部的中心为坐标原点*/\n            box-shadow: 0 0 3px 0 #666;\n        }\n\n        .box:hover img:nth-child(6) {\n            transform: rotate(-10deg);\n        }\n\n        .box:hover img:nth-child(5) {\n            transform: rotate(-20deg);\n        }\n\n        .box:hover img:nth-child(4) {\n            transform: rotate(-30deg);\n        }\n\n        .box:hover img:nth-child(3) {\n            transform: rotate(-40deg);\n        }\n\n        .box:hover img:nth-child(2) {\n            transform: rotate(-50deg);\n        }\n\n        .box:hover img:nth-child(1) {\n            transform: rotate(-60deg);\n        }\n\n        .box:hover img:nth-child(8) {\n            transform: rotate(10deg);\n        }\n\n        .box:hover img:nth-child(9) {\n            transform: rotate(20deg);\n        }\n\n        .box:hover img:nth-child(10) {\n            transform: rotate(30deg);\n        }\n\n        .box:hover img:nth-child(11) {\n            transform: rotate(40deg);\n        }\n\n        .box:hover img:nth-child(12) {\n            transform: rotate(50deg);\n        }\n\n        .box:hover img:nth-child(13) {\n            transform: rotate(60deg);\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"box\">\n    <img src=\"images/pk1.png\"/>\n    <img src=\"images/pk2.png\"/>\n    <img src=\"images/pk1.png\"/>\n    <img src=\"images/pk2.png\"/>\n    <img src=\"images/pk1.png\"/>\n    <img src=\"images/pk2.png\"/>\n    <img src=\"images/pk1.png\"/>\n    <img src=\"images/pk2.png\"/>\n    <img src=\"images/pk1.png\"/>\n    <img src=\"images/pk2.png\"/>\n    <img src=\"images/pk1.png\"/>\n    <img src=\"images/pk2.png\"/>\n    <img src=\"images/pk1.png\"/>\n</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](https://img.smyhvae.com/20180208_1650.gif)\n\n### 4、倾斜\n\n暂略。\n\n## 3D 转换\n\n### 1、旋转：rotateX、rotateY、rotateZ\n\n**3D坐标系（左手坐标系）**\n\n![](http://img.smyhvae.com/20180208_2010.png)\n\n如上图所示，伸出左手，让拇指和食指成“L”形，大拇指向右，食指向上，中指指向前方。拇指、食指和中指分别代表X、Y、Z轴的正方向，这样我们就建立了一个左手坐标系。\n\n浏览器的这个平面，是X轴、Y轴；垂直于浏览器的平面，是Z轴。\n\n**旋转的方向：（左手法则）**\n\n左手握住旋转轴，竖起拇指指向旋转轴的**正方向**，正向就是**其余手指卷曲的方向**。\n\n从上面这句话，我们也能看出：所有的3d旋转，对着正方向去看，都是顺时针旋转。\n\n**格式：**\n\n```javascript\n\ttransform: rotateX(360deg);    //绕 X 轴旋转360度\n\n\ttransform: rotateY(360deg);    //绕 Y 轴旋转360度\n\n\ttransform: rotateZ(360deg);    //绕 Z 轴旋转360度\n\n```\n\n**格式举例：**\n\n（1）rotateX 举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .rotateX {\n            width: 300px;\n            height: 226px;\n            margin: 200px auto;\n\n            /* 透视 :加给变换的父盒子*/\n            /* 设置的是用户的眼睛距离 平面的距离*/\n            /* 透视效果只是视觉上的呈现，并不是正真的3d*/\n            perspective: 110px;\n        }\n\n        img {\n            /* 过渡*/\n            transition: transform 2s;\n        }\n\n        /* 所有的3d旋转，对着正方向去看，都是顺时针旋转*/\n        .rotateX:hover img {\n            transform: rotateX(360deg);\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"rotateX\">\n    <img src=\"images/x.jpg\" alt=\"\"/>\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_2025.gif)\n\n上方代码中，我们最好加个透视的属性，方能看到3D的效果；没有这个属性的话，图片旋转的时候，像是压瘪了一样。\n\n而且，透视的是要加给图片的父元素 div，方能生效。我们在后面会讲解透视属性。\n\n（2）rotateY 举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .rotateY {\n            width: 237px;\n            height: 300px;\n            margin: 100px auto;\n\n            /* 透视 */\n            perspective: 150px;\n        }\n\n        img {\n            transition: all 2s;  /* 过渡 */\n        }\n\n        .rotateY:hover img {\n            transform: rotateY(360deg);\n        }\n    </style>\n</head>\n<body>\n<div class=\"rotateY\">\n    <img src=\"images/y.jpg\" alt=\"\"/>\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_2030.gif)\n\n（3）rotateZ 举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .rotateZ {\n            width: 330px;\n            height: 227px;\n            margin: 100px auto;\n\n            /* 透视*/\n            perspective: 200px;\n        }\n\n        img {\n            transition: all 1s;\n        }\n\n        .rotateZ:hover img {\n            transform: rotateZ(360deg);\n        }\n    </style>\n</head>\n<body>\n<div class=\"rotateZ\">\n    <img src=\"images/z.jpg\" alt=\"\"/>\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_2035.gif)\n\n**案例**：百度钱包的水平翻转效果\n\n现在有下面这张图片素材：\n\n![](http://img.smyhvae.com/20180208_2055.png)\n\n要求做成下面这种效果：\n\n![](http://img.smyhvae.com/20180208_2100.gif)\n\n上面这张图片素材其实用的是精灵图。实现的代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        body {\n            background-color: cornflowerblue;\n        }\n\n        .box {\n            width: 300px;\n            height: 300px;\n            /*border: 1px solid #000;*/\n            margin: 50px auto;\n            position: relative;\n        }\n\n        .box > div {\n            width: 100%;\n            height: 100%;\n            position: absolute;\n            /*border: 1px solid #000;*/\n            border-radius: 50%;\n            transition: all 2s;\n            backface-visibility: hidden;\n        }\n\n        .box1 {\n            background: url(images/bg.png) left 0 no-repeat; /*默认显示图片的左半边*/\n        }\n\n        .box2 {\n            background: url(images/bg.png) right 0 no-repeat;\n            transform: rotateY(180deg); /*让图片的右半边默认时，旋转180度，就可以暂时隐藏起来*/\n\n        }\n\n        .box:hover .box1 {\n            transform: rotateY(180deg); /*让图片的左半边转消失*/\n        }\n\n        .box:hover .box2 {\n            transform: rotateY(0deg); /*让图片的左半边转出现*/\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n    <div class=\"box1\"></div>\n    <div class=\"box2\"></div>\n</div>\n</body>\n</html>\n```\n\n### 2、移动：translateX、translateY、translateZ\n\n**格式：**\n\n```javascript\n\ttransform: translateX(100px);    //沿着 X 轴移动\n\n\ttransform: translateY(360px);    //沿着 Y 轴移动\n\n\ttransform: translateZ(360px);    //沿着 Z 轴移动\n\n```\n\n**格式举例：**\n\n（1）translateX 举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 200px;\n            height: 200px;\n            background: green;\n            transition: all 1s;\n        }\n\n        .box:hover {\n            transform: translateX(100px);\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_2036.gif)\n\n（2）translateY 举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 200px;\n            height: 200px;\n            background: green;\n            transition: all 1s;\n        }\n\n        .box:hover {\n            transform: translateY(100px);\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_2037.gif)\n\n（3）translateZ 举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        body {\n            /* 给box的父元素加透视效果*/\n            perspective: 1000px;\n        }\n\n        .box {\n            width: 250px;\n            height: 250px;\n            background: green;\n            transition: all 1s;\n            margin: 200px auto\n        }\n\n        .box:hover {\n            /* translateZ必须配合透视来使用*/\n            transform: translateZ(400px);\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180208_2040.gif)\n\n上方代码中，如果不加透视属性，是看不到translateZ的效果的。\n\n### 3、透视：perspective\n\n电脑显示屏是一个 2D 平面，图像之所以具有立体感（3D效果），其实只是一种视觉呈现，通过透视可以实现此目的。\n\n透视可以将一个2D平面，在转换的过程当中，呈现3D效果。但仅仅只是视觉呈现出 3d 效果，并不是正真的3d。\n\n格式有两种写法：\n\n- 作为一个属性，设置给父元素，作用于所有3D转换的子元素\n\n- 作为 transform 属性的一个值，做用于元素自身。\n\n格式举例：\n\n```css\nperspective: 500px;\n```\n\n### 4、3D呈现（transform-style）\n\n3D元素构建是指某个图形是由多个元素构成的，可以给这些元素的**父元素**设置`transform-style: preserve-3d`来使其变成一个真正的3D图形。属性值可以如下：\n\n```css\n\ttransform-style: preserve-3d;     /* 让 子盒子 位于三维空间里 */\n\n\ttransform-style: flat;            /* 让子盒子位于此元素所在的平面内（子盒子被扁平化） */\n\n```\n\n**案例：**立方体\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 250px;\n            height: 250px;\n            border: 1px dashed red;\n            margin: 100px auto;\n            position: relative;\n            border-radius: 50%;\n\n            /* 让子盒子保持3d效果*/\n\n            transform-style: preserve-3d;\n\n            /*transform:rotateX(30deg) rotateY(-30deg);*/\n\n            animation: gun 8s linear infinite;\n        }\n\n        .box > div {\n            width: 100%;\n            height: 100%;\n            position: absolute;\n            text-align: center;\n            line-height: 250px;\n            font-size: 60px;\n            color: #daa520;\n        }\n\n        .left {\n            background-color: rgba(255, 0, 0, 0.3);\n            /* 变换中心*/\n            transform-origin: left;\n            /* 变换*/\n            transform: rotateY(90deg) translateX(-125px);\n        }\n\n        .right {\n            background: rgba(0, 0, 255, 0.3);\n            transform-origin: right;\n            /* 变换*/\n            transform: rotateY(90deg) translateX(125px);\n        }\n\n        .forward {\n            background: rgba(255, 255, 0, 0.3);\n            transform: translateZ(125px);\n        }\n\n        .back {\n            background: rgba(0, 255, 255, 0.3);\n            transform: translateZ(-125px);\n        }\n\n        .up {\n            background: rgba(255, 0, 255, 0.3);\n            transform: rotateX(90deg) translateZ(125px);\n        }\n\n        .down {\n            background: rgba(99, 66, 33, 0.3);\n            transform: rotateX(-90deg) translateZ(125px);\n        }\n\n        @keyframes gun {\n            0% {\n                transform: rotateX(0deg) rotateY(0deg);\n            }\n\n            100% {\n                transform: rotateX(360deg) rotateY(360deg);\n            }\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n    <div class=\"up\">上</div>\n    <div class=\"down\">下</div>\n    <div class=\"left\">左</div>\n    <div class=\"right\">右</div>\n    <div class=\"forward\">前</div>\n    <div class=\"back\">后</div>\n</div>\n</body>\n</html>\n```\n\n## 动画\n\n动画是CSS3中具有颠覆性的特征，可通过设置**多个节点** 来精确控制一个或一组动画，常用来实现**复杂**的动画效果。\n\n### 1、定义动画的步骤\n\n（1）通过@keyframes定义动画；\n\n（2）将这段动画通过百分比，分割成多个节点；然后各节点中分别定义各属性；\n\n（3）在指定元素里，通过 `animation` 属性调用动画。\n\n之前,我们在 js 中定义一个函数的时候，是先定义，再调用：\n\n```javascript\n    js 定义函数：\n        function fun(){ 函数体 }\n\n     调用：\n     \tfun();\n```\n\n同样，我们在 CSS3 中**定义动画**的时候，也是**先定义，再调用**：\n\n```javascript\n    定义动画：\n        @keyframes 动画名{\n            from{ 初始状态 }\n            to{ 结束状态 }\n        }\n\n     调用：\n      animation: 动画名称 持续时间；\n```\n\n其中，animation属性的格式如下：\n\n```javascript\n            animation: 定义的动画名称 持续时间  执行次数  是否反向  运动曲线 延迟执行。(infinite 表示无限次)\n\n            animation: move1 1s  alternate linear 3;\n\n            animation: move2 4s;\n```\n\n**定义动画的格式举例：**\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 100px;\n            height: 100px;\n            margin: 100px;\n            background-color: red;\n\n            /* 调用动画*/\n            /* animation: 动画名称 持续时间  执行次数  是否反向  运动曲线 延迟执行。infinite 表示无限次*/\n            /*animation: move 1s  alternate linear 3;*/\n            animation: move2 4s;\n        }\n\n        /* 方式一：定义一组动画*/\n        @keyframes move1 {\n            from {\n                transform: translateX(0px) rotate(0deg);\n            }\n            to {\n                transform: translateX(500px) rotate(555deg);\n            }\n        }\n\n        /* 方式二：定义多组动画*/\n        @keyframes move2 {\n            0% {\n                transform: translateX(0px) translateY(0px);\n                background-color: red;\n                border-radius: 0;\n            }\n\n            25% {\n                transform: translateX(500px) translateY(0px);\n\n            }\n\n            /*动画执行到 50% 的时候，背景色变成绿色，形状变成圆形*/\n            50% {\n                /* 虽然两个方向都有translate，但其实只是Y轴上移动了200px。\n                因为X轴的500px是相对最开始的原点来说的。可以理解成此时的 translateX 是保存了之前的位移 */\n                transform: translateX(500px) translateY(200px);\n                background-color: green;\n                border-radius: 50%;\n            }\n\n            75% {\n                transform: translateX(0px) translateY(200px);\n            }\n\n            /*动画执行到 100% 的时候，背景色还原为红色，形状还原为正方形*/\n            100% {\n                /*坐标归零，表示回到原点。*/\n                transform: translateX(0px) translateY(0px);\n                background-color: red;\n                border-radius: 0;\n            }\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n\n</div>\n</body>\n</html>\n```\n\n注意好好看代码中的注释。\n\n效果如下：\n\n![](http://img.smyhvae.com/20180209_1001.gif)\n\n### 2、动画属性\n\n我们刚刚在调用动画时，animation属性的格式如下：\n\nanimation属性的格式如下：\n\n```javascript\n            animation: 定义的动画名称  持续时间  执行次数  是否反向  运动曲线 延迟执行。(infinite 表示无限次)\n\n            animation: move1 1s  alternate linear 3;\n\n            animation: move2 4s;\n```\n\n\n可以看出，这里的 animation 是综合属性，接下来，我们把这个综合属性拆分看看。\n\n（1）动画名称：\n\n```javascript\n\tanimation-name: move;\n```\n\n（2）执行一次动画的持续时间：\n\n```javascript\n\tanimation-duration: 4s;\n```\n\n备注：上面两个属性，是必选项，且顺序固定。\n\n（3）动画的执行次数：\n\n```javascript\n\tanimation-iteration-count: 1;       //iteration的含义表示迭代\n```\n\n属性值`infinite`表示无数次。\n\n（3）动画的方向：\n\n```javascript\n\tanimation-direction: alternate;\n```\n\n属性值：normal 正常，alternate 反向。\n\n（4）动画延迟执行：\n\n\n```javascript\n\tanimation-delay: 1s;\n```\n\n（5）设置动画结束时，盒子的状态：\n\n```javascript\n\tanimation-fill-mode: forwards;\n```\n\n属性值： forwards：保持动画结束后的状态（默认），  backwards：动画结束后回到最初的状态。\n\n（6）运动曲线：\n\n```\n\tanimation-timing-function: ease-in;\n```\n\n属性值可以是：linear   ease-in-out  steps()等。\n\n注意，如果把属性值写成**` steps()`**，则表示动画**不是连续执行**，而是间断地分成几步执行。我们接下来专门讲一下属性值 `steps()`。\n\n### steps()的效果\n\n我们还是拿上面的例子来举例，如果在调用动画时，我们写成：\n\n\n```javascript\n\tanimation: move2 4s steps(2);\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180209_1020.gif)\n\n有了属性值 `steps()`，我们就可以作出很多不连续地动画效果。比如时钟；再比如，通过多张静态的鱼，作出一张游动的鱼。\n\n**step()举例：**时钟的简易模型\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 3px;\n            height: 200px;\n            background-color: #000;\n            margin: 100px auto;\n            transform-origin: center bottom;    /* 旋转的中心点是底部 */\n            animation: myClock 60s steps(60) infinite;\n        }\n\n        @keyframes myClock {\n            0% {\n                transform: rotate(0deg);\n            }\n\n            100% {\n                transform: rotate(360deg);\n            }\n        }\n    </style>\n</head>\n<body>\n<div></div>\n</body>\n</html>\n```\n\n上方代码，我们通过一个黑色的长条div，旋转360度，耗时60s，分成60步完成。即可实现。\n\n效果如下：\n\n![](http://img.smyhvae.com/20180209_1030.gif)\n\n### 动画举例：摆动的鱼\n\n现在，我们要做下面这种效果：\n\n![](http://img.smyhvae.com/20180209_1245.gif)\n\nPS：图片的url是<http://img.smyhvae.com/20180209_1245.gif>，图片较大，如无法观看，可在浏览器中单独打开。\n\n为了作出上面这种效果，要分成两步。\n\n**（1）第一步**：让鱼在原地摆动\n\n鱼在原地摆动并不是一张 gif动图，她其实是由很多张静态图间隔地播放，一秒钟播放完毕，就可以了：\n\n![](http://img.smyhvae.com/20180209_shark.png)\n\n上面这张大图的尺寸是：宽 509 px、高 2160 px。\n\n我们可以理解成，每一帧的尺寸是：宽 509 px、高 270 px。`270 * 8 = 2160`。让上面这张大图，在一秒内从 0px 的位置往上移动2160px，分成8步来移动。就可以实现了。\n\n代码是：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .shark {\n            width: 509px;\n            height: 270px; /*盒子的宽高是一帧的宽高*/\n            border: 1px solid #000;\n            margin: 100px auto;\n            background: url(images/shark.png) left top; /* 让图片一开始位于 0 px的位置 */\n            animation: sharkRun 1s steps(8) infinite; /* 一秒之内，从顶部移动到底部，分八帧， */\n        }\n\n        @keyframes sharkRun {\n            0% {\n            }\n\n            /* 270 * 8 = 2160 */\n            100% {\n                background-position: left -2160px; /* 动画结束时，让图片位于最底部 */\n            }\n        }\n    </style>\n</head>\n<body>\n<div class=\"sharkBox\">\n    <div class=\"shark\"></div>\n</div>\n\n</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180209_1250.gif)\n\n我们不妨把上面的动画的持续时间从`1s`改成 `8s`，就可以看到动画的慢镜头：\n\n![](http://img.smyhvae.com/20180209_1330.gif)\n\n这下，你应该恍然大悟了。\n\n**（2）第二步**：让鱼所在的盒子向前移动。\n\n实现的原理也很简单，我们在上一步中已经让`shark`这个盒子实现了原地摇摆，现在，让 shark 所在的父盒子 `sharkBox`向前移动，即可。完整版代码是：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .shark {\n            width: 509px;\n            height: 270px; /* 盒子的宽高是一帧的宽高 */\n            border: 1px solid #000;\n            margin: 100px auto;\n            background: url(images/shark.png) left top; /* 让图片一开始位于 0 px的位置 */\n            animation: sharkRun 1s steps(8) infinite; /* 一秒之内，从顶部移动到底部，分八帧 */\n        }\n\n        /* 鱼所在的父盒子 */\n        .sharkBox {\n            width: 509px;\n            height: 270px;\n            animation: sharkBoxRun 20s linear infinite;\n        }\n\n        @keyframes sharkRun {\n            0% {\n            }\n\n            /* 270 * 8 = 2160 */\n            100% {\n                background-position: left -2160px; /* 动画结束时，让图片位于最底部 */\n            }\n        }\n\n        @keyframes sharkBoxRun {\n            0% {\n                transform: translateX(-600px);\n            }\n\n            100% {\n                transform: translateX(3000px);\n            }\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"sharkBox\">\n    <div class=\"shark\"></div>\n</div>\n\n</div>\n</body>\n</html>\n```\n\n![](http://img.smyhvae.com/20180209_1350.gif)\n\n大功告成。\n\n工程文件：[2018-02-09-fishes.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-09-fishes.rar)\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n"
  },
  {
    "path": "02-CSS基础/13-CSS3属性：Flex布局图文详解.md",
    "content": "---\ntitle: 13-CSS3属性：Flex布局图文详解\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 前言\n\nCSS3中的 flex 属性，在布局方面做了非常大的改进，使得我们对**多个元素之间**的布局排列变得十分灵活，适应性非常强。其强大的伸缩性和自适应性，在网页开中可以发挥极大的作用。\n\n### flex 初体验\n\n我们先来看看下面这个最简单的布局：\n\n![](http://img.smyhvae.com/20191009_1555.png)\n\n上面这张图中的布局是我们都熟悉的：默认文档流中，在一个父容器里放置多个块级的子元素，那么，这些子元素会默认从上往下排列。\n\n在此基础之上，如果我给父容器仅仅加一个 `display: flex`属性，此时，这些子元素的布局会摇身一变：\n\n![](http://img.smyhvae.com/20191009_1600.png)\n\n没错，子元素们会**在水平方向上，从左至右排列**，就是这么神奇。到此为止，你已经掌握了关于 flex 的一半的知识。\n\n### flex 布局的优势\n\n1、**flex 布局的子元素不会脱离文档流**，很好地遵从了“流的特性”。\n\n但你如果用 float 来做布局，float 属性的元素会脱离文档流，而且会涉及到各种 BFC、清除浮动的问题。浮动相关的问题，比较麻烦，所以也成了面试必问的经典题目。但有了 flex 布局之后，这些问题都不存在的。\n\n2、**flex 是一种现代的布局方式，是 W3C 第一次提供真正用于布局的 CSS 规范**。 flex 提供了非常丰富的属性，非常灵活，让布局的实现更佳多样化，且方便易用。\n\nflex 唯一的缺点就在于，它不支持低版本的 IE 浏览器。\n\n### flex 的兼容性问题\n\n![](http://img.smyhvae.com/20191005_1200.png)\n\n上图中可以看到， flex 布局不支持 IE9 及以下的版本；IE10及以上也只是部分支持。如果你的页面不需要处理 IE浏览器的兼容性问题，则可以放心大胆地使用 flex 布局。\n\n但是，比如网易新闻、淘宝这样的大型网站，面对的是海量用户，即便使用低版本浏览器的用户比例很少，但绝对基数仍然是很庞大的。因此，这些网站为了兼容低版本的 IE 浏览器，暂时还不敢尝试使用 flex 布局。\n\n### 概念：弹性盒子、子元素\n\n在讲 flex 的知识点之前，我们事先约定两个概念：\n\n- **弹性盒子**：指的是使用 `display:flex` 或 `display:inline-flex` 声明的**父容器**。\n\n- **子元素/弹性元素**：指的是父容器里面的子元素们（父容器被声明为 flex 盒子的情况下）。\n\n### 概念：主轴和侧轴\n\n在上面的“初体验”例子中，我们发现，弹性盒子里面的子元素们，默认是从左至右排列的，这个方向，代表的就是主轴的方向。\n\n在此，我们引入主轴和侧轴的概念。\n\n![](http://img.smyhvae.com/20191009_1701.png)\n\n如上图所示：\n\n- 主轴：flex容器的主轴，默认是水平方向，从左向右。\n\n- 侧轴：与主轴垂直的轴称作侧轴，默认是垂直方向，从上往下。\n\nPS：主轴和侧轴并不是固定不变的，可以通过 `flex-direction` 更换方向，我们在后面会讲到。\n\n## 弹性盒子\n\n### 声明定义\n\n使用 `display:flex` 或 `display:inline-flex` 声明一个**父容器**为弹性盒子。此时，这个父容器里的子元素们，会遵循弹性布局。\n\n备注：一般是用 `display:flex`这个属性。`display:inline-flex`用得较少。\n\n### flex-direction 属性\n\n`flex-direction`：用于设置盒子中**子元素**的排列方向。属性值可以是：\n\n| 属性值 | 描述 |\n|:-------------|:-------------|\n| row | 从左到右水平排列子元素（默认值） |\n|column|从上到下垂直排列子元素|\n| row-reverse |从右向左排列子元素 |\n|column-reverse|从下到上垂直排列子元素|\n\n备注：如果我们不给父容器写`flex-direction`这个属性，那么，子元素默认就是从左到右排列的。\n\n代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        *{\n            margin: 0;\n            padding: 0;\n            list-style: none;\n        }\n       body{\n           background-color: #eee;\n           font-family: \"Microsoft Yahei\";\n           font-size:22px;\n       }\n\n        h3{\n            font-weight: normal;\n        }\n        section{\n            width: 1000px;\n\n            margin:40px auto;\n        }\n\n        ul{\n            background-color: #fff;\n            border: 1px solid #ccc;\n\n        }\n\n        ul li{\n            width: 200px;\n            height: 200px;\n            background-color: pink;\n            margin:10px;\n        }\n        section:nth-child(1) ul{\n            overflow: hidden; /* 清除浮动 */\n        }\n        section:nth-child(1) ul li{\n            float: left;\n        }\n        /* 设置伸缩盒子*/\n        section:nth-child(2) ul{\n            display: flex;\n        }\n\n        section:nth-child(3) ul{\n            /* 设置伸缩布局*/\n            display: flex;\n            /* 设置主轴方向*/\n            flex-direction: row;\n        }\n\n        section:nth-child(4) ul{\n            /* 设置伸缩布局*/\n            display: flex;\n            /* 设置主轴方向 :水平翻转*/\n            flex-direction: row-reverse;\n        }\n\n        section:nth-child(5) ul{\n            /* 设置伸缩布局*/\n            display: flex;\n            /* 设置主轴方向 :垂直*/\n            flex-direction: column;\n        }\n\n        section:nth-child(6) ul{\n            /* 设置伸缩布局*/\n            display: flex;\n            /* 设置主轴方向 :垂直*/\n            flex-direction: column-reverse;\n        }\n    </style>\n</head>\n<body>\n    <section>\n        <h3>传统布局</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>伸缩布局 display:flex</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴方向 flex-direction:row</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴方向 flex-direction:row-reverse</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴方向 flex-direction:column</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴方向 flex-direction:column-reverse</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n</body>\n</html>\n```\n\n\n### flex-wrap 属性\n\n`flex-wrap`：控制子元素溢出时的换行处理。\n\n### justify-content 属性\n\n`justify-content`：控制子元素在主轴上的排列方式。\n\n## 弹性元素\n\n### justify-content 属性\n\n- `justify-content: flex-start;` 设置子元素在**主轴上的对齐方式**。属性值可以是：\n    - `flex-start` 从主轴的起点对齐（默认值）\n    - `flex-end` 从主轴的终点对齐\n    - `center` 居中对齐\n    - `space-around` 在父盒子里平分\n    - `space-between` 两端对齐 平分\n\n\n代码演示：（在浏览器中打开看效果）\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        *{\n            margin: 0;\n            padding: 0;\n            list-style:none;}\n        body{\n            background-color: #eee;\n            font-family: \"Microsoft Yahei\";\n\n        }\n        section{\n            width: 1000px;\n            margin:50px auto;\n        }\n        section h3{\n            font-size:22px;\n            font-weight: normal;\n        }\n\n        ul{\n            border: 1px solid #999;\n            background-color: #fff;\n            display: flex;\n\n        }\n\n        ul li{\n            width: 200px;\n            height: 200px;\n            background: pink;\n            margin:10px;\n\n        }\n\n        section:nth-child(1) ul{\n            /* 主轴对齐方式：从主轴开始的方向对齐*/\n            justify-content: flex-start;\n        }\n\n        section:nth-child(2) ul{\n            /* 主轴对齐方式：从主轴结束的方向对齐*/\n            justify-content: flex-end;\n        }\n\n        section:nth-child(3) ul{\n            /* 主轴对齐方式：居中对齐*/\n            justify-content: center;\n        }\n\n        section:nth-child(4) ul{\n            /* 主轴对齐方式：在父盒子中平分*/\n            justify-content: space-around;\n\n           }\n\n        section:nth-child(5) ul{\n            /* 主轴对齐方式：两端对齐 平分*/\n            justify-content: space-between;\n        }\n    </style>\n</head>\n<body>\n    <section>\n        <h3>主轴的对齐方式：justify-content:flex-start</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴的对齐方式：justify-content:flex-end</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴的对齐方式：justify-content:center</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴的对齐方式：justify-content:space-round</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n        </ul>\n    </section>\n\n    <section>\n        <h3>主轴的对齐方式：justify-content:space-bettwen</h3>\n        <ul>\n            <li>1</li>\n            <li>2</li>\n            <li>3</li>\n            <li>4</li>\n        </ul>\n    </section>\n</body>\n</html>\n```\n\n\n### align-items 属性\n\n`align-items`：设置子元素在**侧轴上的对齐方式**。属性值可以是：\n    - `flex-start` 从侧轴开始的方向对齐\n    - `flex-end` 从侧轴结束的方向对齐\n    - `baseline` 基线 默认同flex-start\n    - `center` 中间对齐\n    - `stretch` 拉伸\n\n代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        *{\n            margin: 0;\n            padding: 0;\n            list-style:none;\n        }\n        body{\n            background-color: #eee;\n            font-family: \"Microsoft Yahei\";\n\n        }\n        section{\n            width: 1000px;\n            margin:50px auto;\n        }\n        section h3{\n            font-size:22px;\n            font-weight: normal;\n        }\n\n        ul{\n            border: 1px solid #999;\n            background-color: #fff;\n            display: flex;\n            height:500px;\n\n        }\n\n        ul li{\n            width: 200px;\n            height: 200px;\n            background: pink;\n            margin:10px;\n\n        }\n\n        section:nth-child(1) ul{\n            /* 侧轴对齐方式 ：从侧轴开始的方向对齐*/\n            align-items:flex-start;\n        }\n\n        section:nth-child(2) ul{\n            /* 侧轴对齐方式 ：从侧轴结束的方向对齐*/\n            align-items:flex-end;\n        }\n\n        section:nth-child(3) ul{\n            /* 侧轴对齐方式 ：居中*/\n            align-items:center;\n        }\n\n        section:nth-child(4) ul{\n            /* 侧轴对齐方式 ：基线 默认同flex-start*/\n            align-items:baseline;\n        }\n\n        section:nth-child(5) ul{\n            /* 侧轴对齐方式 ：拉伸*/\n            align-items:stretch;\n\n        }\n\n        section:nth-child(5) ul li{\n            height:auto;\n        }\n\n\n    </style>\n</head>\n<body>\n<section>\n    <h3>侧轴的对齐方式:align-items ：flex-start</h3>\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n    </ul>\n</section>\n\n<section>\n    <h3>侧轴的对齐方式：align-items:flex-end</h3>\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n    </ul>\n</section>\n\n<section>\n    <h3>侧轴的对齐方式：align-items:center</h3>\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n    </ul>\n</section>\n\n<section>\n    <h3>侧轴的对齐方式：align-itmes:baseline</h3>\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n    </ul>\n</section>\n\n<section>\n    <h3>侧轴的对齐方式：align-itmes: stretch</h3>\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n    </ul>\n</section>\n</body>\n</html>\n```\n\n### `flex`属性：设置子盒子的权重\n\n代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        *{\n            margin: 0;\n            padding: 0;\n            list-style:none;\n        }\n        body{\n            background-color: #eee;\n            font-family: \"Microsoft Yahei\";\n\n        }\n        section{\n            width: 1000px;\n            margin:50px auto;\n        }\n        section h3{\n            font-size:22px;\n            font-weight: normal;\n        }\n\n        ul{\n            border: 1px solid #999;\n            background-color: #fff;\n            display: flex;\n\n        }\n\n        ul li{\n            width: 200px;\n            height: 200px;\n            background: pink;\n            margin:10px;\n\n        }\n\n        section:nth-child(1) ul li:nth-child(1){\n            flex:1;\n        }\n\n        section:nth-child(1) ul li:nth-child(2){\n            flex:1;\n        }\n\n        section:nth-child(1) ul li:nth-child(3){\n            flex:8;\n        }\n\n        section:nth-child(2) ul li:nth-child(1){\n\n        }\n\n        section:nth-child(2) ul li:nth-child(2){\n            flex:1;\n        }\n\n        section:nth-child(2) ul li:nth-child(3){\n           flex:4;\n        }\n\n\n    </style>\n</head>\n<body>\n<section>\n    <h3>伸缩比例:flex</h3>\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n    </ul>\n</section>\n\n<section>\n    <h3>伸缩比例:flex</h3>\n    <ul>\n        <li>1</li>\n        <li>2</li>\n        <li>3</li>\n    </ul>\n</section>\n\n\n</body>\n</html>\n```\n\n## 相关链接\n\n### CSS Flexbox 可视化手册\n\n可视化的截图如下：（请点开链接，查看大图）\n\n<http://img.smyhvae.com/20190821_2101.png>\n\n相关文章：\n\n- 【英文原版】 CSS Flexbox Fundamentals Visual Guide：<https://medium.com/swlh/css-flexbox-fundamentals-visual-guide-1c467f480dac>\n\n- 【中文翻译】CSS Flexbox 可视化手册：<https://zhuanlan.zhihu.com/p/56046851>\n\n###  flex 相关的推荐文章\n\n- flex 效果在线演示：<https://demos.scotch.io/visual-guide-to-css3-flexbox-flexbox-playground/demos/>\n\n- A Complete Guide to Flexbox | 英文原版：<https://css-tricks.com/snippets/css/a-guide-to-flexbox/>\n\n- CSS3 Flexbox 布局完全指南 | 中文翻译：<https://www.html.cn/archives/8629>\n\n###  flex 相关的教程\n\n- [后盾人 flex 教程](http://houdunren.gitee.io/note/css/10%20%E5%BC%B9%E6%80%A7%E5%B8%83%E5%B1%80.html)\n\n## 技巧：使用 margin 自动撑满剩余空间\n\n\n\n\n\n\n"
  },
  {
    "path": "02-CSS基础/14-CSS3属性详解：Web字体.md",
    "content": "---\ntitle: 14-CSS3属性详解：Web字体\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 前言\n\n\n开发人员可以为自已的网页指定特殊的字体（将指定字体提前下载到站点中），无需考虑用户电脑上是否安装了此特殊字体。从此，把特殊字体处理成图片的方式便成为了过去。\n\n支持程度比较好，甚至 IE 低版本的浏览器也能支持。\n\n## 字体的常见格式\n\n> 不同浏览器所支持的字体格式是不一样的，我们有必要了解一下字体格式的知识。\n\n#### TureTpe格式：(**.ttf**)\n\n.ttf 字体是Windows和Mac的最常见的字体，是一种RAW格式。\n\n支持这种字体的浏览器有IE9+、Firefox3.5+、Chrome4+、Safari3+、Opera10+、iOS Mobile、Safari4.2+。\n\n\n\n#### OpenType格式：(**.otf**)\n\n.otf 字体被认为是一种原始的字体格式，其内置在TureType的基础上。\n\n支持这种字体的浏览器有Firefox3.5+、Chrome4.0+、Safari3.1+、Opera10.0+、iOS Mobile、Safari4.2+。\n\n\n#### Web Open Font Format格式：(**.woff**)\n\nwoff字体是Web字体中最佳格式，他是一个开放的TrueType/OpenType的压缩版本，同时也支持元数据包的分离。\n\n支持这种字体的浏览器有IE9+、Firefox3.5+、Chrome6+、Safari3.6+、Opera11.1+。\n\n#### Embedded Open Type格式：(**.eot**)\n\n.eot字体是IE专用字体，可以从TrueType创建此格式字体，支持这种字体的浏览器有IE4+。\n\n\n#### SVG格式：(**.svg**)\n\n.svg字体是基于SVG字体渲染的一种格式。\n\n支持这种字体的浏览器有Chrome4+、Safari3.1+、Opera10.0+、iOS Mobile Safari3.2+。\n\n**总结：**\n\n了解了上面的知识后，**我们就需要为不同的浏览器准备不同格式的字体**。通常我们会通过字体生成工具帮我们生成各种格式的字体，因此无需过于在意字体格式之间的区别。\n\n\n下载字体的网站推荐：\n\n- <http://www.zhaozi.cn/>\n\n- <http://www.youziku.com/>\n\n\n## WebFont 的使用步骤\n\n打开网站<http://iconfont.cn/webfont#!/webfont/index>，如下：\n\n![](http://img.smyhvae.com/20180220_1328.png)\n\n上图中，比如我想要「思源黑体-粗」这个字体，那我就点击红框中的「本地下载」。\n\n下载完成后是一个压缩包，压缩包链接：http://download.csdn.net/download/smyhvae/10253561\n\n解压后如下：\n\n![](http://img.smyhvae.com/20180220_1336.png)\n\n上图中， 我们把箭头处的html文件打开，里面告诉了我们 webfont 的**使用步骤**：\n\n![](http://img.smyhvae.com/20180220_1338.png)\n\n（1）第一步：使用font-face声明字体\n\n```css\n@font-face {font-family: 'webfont';\n    src: url('webfont.eot'); /* IE9*/\n    src: url('webfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n    url('webfont.woff') format('woff'), /* chrome、firefox */\n    url('webfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/\n    url('webfont.svg#webfont') format('svg'); /* iOS 4.1- */\n}\n```\n\n\n（2）第二步：定义使用webfont的样式\n\n```css\n.web-font{\n    font-family:\"webfont\" !important;\n    font-size:16px;font-style:normal;\n    -webkit-font-smoothing: antialiased;\n    -webkit-text-stroke-width: 0.2px;\n    -moz-osx-font-smoothing: grayscale;}\n```\n\n\n（3）第三步：为文字加上对应的样式\n\n```html\n<i class=\"web-font\">这一分钟，你和我在一起，因为你，我会记得那一分钟。从现在开始，我们就是一分钟的朋友。这是事实，你改变不了，因为已经完成了。</i>\n```\n\n**举例：**\n\n我们按照上图中的步骤来，引入这个字体。完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n\n        p{\n            font-size:30px;\n        }\n\n        /*  如果要在网页中使用web字体（用户电脑上没有这种字体）*/\n        /* 第一步：声明字体*/\n        /* 告诉浏览器 去哪找这个字体*/\n        @font-face {font-family: 'my-web-font';\n            src: url('font/webfont.eot'); /* IE9*/\n            src: url('font/webfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n            url('font/webfont.woff') format('woff'), /* chrome、firefox */\n            url('font/webfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/\n            url('font/webfont.svg#webfont') format('svg'); /* iOS 4.1- */\n        }\n        /* 第二步：定义一个类名，谁加这类名，就会使用 webfont 字体*/\n        .webfont{\n            font-family: 'my-web-font';\n        }\n    </style>\n</head>\n<body>\n    <!-- 第三步：引用 webfont 字体 -->\n    <p class=\"webfont\">生命壹号，永不止步</p>\n</body>\n</html>\n```\n\n\n代码解释：\n\n（1）`my-web-font`这个名字是随便起的，只要保证第一步和第二步中的名字一样就行。\n\n（2）因为我把字体文件单独放在了font文件夹中，所以在src中引用字体资源时，写的路径是 `font/...`\n\n工程文件：[2018-02-20-WebFont举例.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-20-WebFont%E4%B8%BE%E4%BE%8B.zip)\n\n\n\n## 字体图标（阿里的 iconfont 网站举例）\n\n我们其实可以把图片制作成字体。常见的做法是：把网页中一些小的图标，借助工具生成一个字体包，然后就可以像使用文字一样使用图标了。这样做的优点是：\n\n- 将所有图标打包成字体库，减少请求；\n\n- 具有矢量性，可保证清晰度；\n\n- 使用灵活，便于维护。\n\n也就是说，我们可以把这些图标当作字体来看待，凡是字体拥有的属性（字体大小、颜色等），均适用于图标。\n\n**使用步骤如下：**（和上一段的使用步骤是一样的）\n\n打开网站<http://iconfont.cn/>，找到想要的图标，加入购物车。然后下载下来：\n\n![](http://img.smyhvae.com/20180220_1750.png)\n\n压缩包下载之后，解压，打开里面的demo.html，里面告诉了我们怎样引用这些图标。\n\n![](http://img.smyhvae.com/20180220_1755.png)\n\n**举例1**：（图标字体引用）\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        /*申明字体*/\n        @font-face {font-family: 'iconfont';\n            src: url('font/iconfont.eot'); /* IE9*/\n            src: url('font/iconfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n            url('font/iconfont.woff') format('woff'), /* chrome、firefox */\n            url('font/iconfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/\n            url('font/iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */\n        }\n\n        .iconfont{\n            font-family: iconfont;\n        }\n\n        p{\n            width: 200px;\n            border: 1px solid #000;\n            line-height: 60px;\n            font-size:30px;\n            margin:100px auto;\n            text-align: center;\n        }\n\n        p span{\n            color:red;\n        }\n    </style>\n</head>\n<body>\n    <!-- 【重要】编码代表图标 -->\n    <p><span class=\"iconfont\">&#xe628;</span>扫码付款</p>\n</body>\n</html>\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180220_1800.png)\n\n\n**举例2**：（伪元素的方式使用图标字体）\n\n如果想要在文字的前面加图标字体，我们更习惯采用**伪元素**的方式进行添加。\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        /*申明字体*/\n        @font-face {font-family: 'iconfont';\n            src: url('font/iconfont.eot'); /* IE9*/\n            src: url('font/iconfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */\n            url('font/iconfont.woff') format('woff'), /* chrome、firefox */\n            url('font/iconfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/\n            url('font/iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */\n        }\n\n\n\n        p{\n            width: 200px;\n            border: 1px solid #000;\n            line-height: 60px;\n            font-size:30px;\n            margin:100px auto;\n            text-align: center;\n            position: relative;\n        }\n\n        .icon::before{\n            /*&#xe628;*/\n            content:\"\\e628\";\n            /*position: absolute;*/\n            /*left:10px;*/\n            /*top:0px;*/\n            font-family: iconfont;\n            color:red;\n        }\n\n        span{\n            position: relative;\n\n        }\n\n\n    </style>\n</head>\n<body>\n    <p class=\"icon\">扫码付款</p>\n    <span class=\"icon\" >我是span</span>\n    <div class=\"icon\">divvvvvvvvvvv</div>\n</body>\n</html>\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180220_1815.png)\n\n\n~工程文件~：\n\n- 2018-02-20-图标字体demo.zip\n\n- 下载链接暂无。\n\n## 其他相相关网站介绍\n\n- Font Awesome 使用介绍：<http://fontawesome.dashgame.com/>\n\n定制自已的字体图标库：\n\n- <http://iconfont.cn/>\n\n- <https://icomoon.io/>\n\nSVG素材：\n\n- <https://www.iconfont.cn/>\n\n\n\n\n## 360浏览器网站案例\n\n暂略。\n\n这里涉及到：jQuery fullPage   全屏滚动插件。\n\n- 中文网址:http://www.dowebok.com\n\n- 相关说明:http://www.dowebok.com/77.html\n\n\n## 使用 Bootstrap 网站的图标字体\n\n打开如下网站：<http://www.bootcss.com/p/font-awesome/>。\n\n![](http://img.smyhvae.com/20180223_2100.png)\n\n如上图所示，下载字体后，进行解压：\n\n![](http://img.smyhvae.com/20180223_2105.png)\n\n使用步骤如下：\n\n（1）如图只是想要字体的话，可以把`css`和`font`这两个文件夹拷贝到项目里。\n\n（2）在html文档中的 <head> 标签里，引入 font-awesome.min.css 文件：\n\n```html\n    <link rel=\"stylesheet\" href=\"css/font-awesome.min.css\">\n```\n\n（3）想在哪个标签里用这个图标，直接在这个标签里加className就行（className都在[网站](http://www.bootcss.com/p/font-awesome/)上列出来了）。\n\n\n完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <link rel=\"stylesheet\" href=\"css/font-awesome.min.css\">\n    <style>\n\n    </style>\n</head>\n<body>\n    <span class=\"icon-play\">播放</span>\n</body>\n</html>\n```\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "02-CSS基础/15-Sass入门.md",
    "content": "---\ntitle: 15-Sass入门\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## Sass简介\n\n大家都知道，js 中可以自定义变量，css 仅仅是一个标记语言，不是编程语言，因此不可以自定义变量、不可以引用等等。\n\n面对这些问题，我们现在来引入 Sass，简单的说，他是 css 的升级版，可以自定义变量，可以有 if 语句，还可以嵌套等等，很神奇吧！那下面我们就来介绍返个神奇的 Sass。\n\nSass比Less的功能更强大，也更复杂。\n\n\n### Sass 的定义\n\nSass：英文是 Syntactically Awesome Stylesheets Sass。最早由 Hampton Catlin 开发和设计。 一种帮助你简化 CSS 工作流程的方式，帮助你更容易维护和开发 CSS 内容。\n\n\n官网是：<https://sass-lang.com/>\n\nSass 是这个世界上最成熟、稳定和强大的专业级 CSS 扩展语言。\n\nSass专注的是怎样创建优雅的样式表，而不是内容。\n\n\n### Sass、Compass与CSS\n\n**关系：**\n\n- Less/Sass是语法、Compass是框架、CSS是目标。\n\n**Sass&Compass的好处**：\n\n- 写出更优秀的CSS。\n\n- 解决CSS编写过程中的痛点问题，比如精灵图合图、属性的浏览器前缀处理等。\n\n- 有效组织样式、图片、字体等项目元素。\n\n\n**受众群体：**\n\n- 重构的同学，写很多CSS，不知如何自动化。\n\n- 希望在项目周期内更好地组织项目内容。\n\n\n\n## Sass的安装\n\nsass引擎是用Ruby语言开发的（但是两者的语法没有关系），因此在安装 Sass 前，需要先安装Ruby（mac下自带Ruby无需再安装Ruby）。\n\n下面来讲一下 Windows 下的安装Sass的步骤。\n\n### 第一步：安装Ruby（windows环境）\n\n下载地址：<http://rubyinstaller.org/downloads/>\n\n貌似网络很慢，不一定能下载成功~\n\n安装时，记得勾选“环境变量”：\n\n![](http://img.smyhvae.com/20180407_2022.png)\n\n安装完ruby之后，在命令行中输入`ruby -v`，查看ruby的的版本：\n\n![](http://img.smyhvae.com/20180407_2039.png)\n\n\n### 关于Mac下的Ruby\n\n刚刚说了，Mac下自带Ruby，但是版本肯定很老：\n\n![](http://img.smyhvae.com/20180407_2145.png)\n\n有的时候，我们可能需要使用特定版本的ruby，或者在不同的ruby版本之间进行切换，所以，大家可以尝试安装`rvm`，它是ruby的版本管理工具。官网是：<https://rvm.io>\n\n### 第二步：安装 Sass\n\n安装完ruby之后，在开始菜单中，找到刚才我们安装的ruby，打开Start Command Prompt with Ruby。输入`gem install sass`安装Sass。\n\nPS：Ruby 是使用 gem 来管理它的各种包（比如Sass）。我们安装好ruby之后，gem会自动安装上；类似于，我们安装完node之后，npm也自动安装好了。\n\n但是，由于访问网络受限，我们可以先切换到淘宝的镜像，再安装Sass。步骤如下：\n\n（1）移除默认的镜像，添加淘宝的镜像：\n\n```\n\tgem sources --remove https://rubygems.org/\n\n\tgem sources -a https://ruby.taobao.org/  //注意：如果你系统不支持https，请将淘宝源更换成：gem sources -a http://gems.ruby-china.org\n```\n\nPS：我测试了一下，Win 7 不支持https，Mac支持https。\n\n（2）查看当前使用的是哪个镜像：\n\n```\n\tgem sources -l\n```\n\n![](http://img.smyhvae.com/20180407_2050.png)\n\n（3）安装sass：\n\n紧接着，输入如下命令安装Sass：\n\n```\n\tgem install sass        // 如果mac下输入这个命令时没有权限，则需要在前面加上 sudo\n```\n\n系统会自动安装上最新版本的Sass。\n\n查看sass版本的命令为:\n\n```\n\tsass -v\n```\n\n升级sass版本的命令为：\n\n```\n\tgem update sass\n```\n\n你也可以运行帮助命令行来查看你需要的命令：\n\n```\n\tsass -h\n```\n\n![](http://img.smyhvae.com/20180407_2100.png)\n\n参考链接：<https://www.w3cplus.com/sassguide/install.html>\n\n## Compass 简介和安装\n\n安装完sass之后，我们在main.scss中写一些代码，然后输入如下命令，就可以将`scss文件`转化为`css文件`：\n\n```\n\tsass main.scss main.css\n```\n\n然而，真正的项目开发中，我们不一定是直接使用 sass 命令，而是使用 Compass。\n\n### Compass 简介\n\n官网是：<http://compass-style.org/>。\n\nCompass 是开源的CSS书写框架。\n\n### Compass 安装\n\n输入如下命令安装 Compass：\n\n```\n\tgem isntall compass\n```\n\n输入如下命令查看版本：\n\n```\n\tcompass -v\n```\n\n![](http://img.smyhvae.com/20180407_2208.png)\n\ncompass可以直接用来搭建前端项目的样式部分，但并不是常用的方法。\n\n### Compass的简单使用\n\n通过 Compass 创建工程目录：\n\n\n```\ncd workspace\n\ncompass create CompassDemo\n```\n\n文件结构如下：\n\n- /sass\n\t- ie.scss\n\t- print.scss\n\t- screen.scss\n\n- /stylesheets\n\t- ie.css\n\t- print.css\n\t- screen.css\n\n- config.rb\n\n\n\n为了能够让文件实时编译，我们可以通过 copass watch 监听sass文件的变化：\n\n```\n\tcd CompassDemo\n\n\tcompass watch\n```\n\n当.scss文件改动时，会自动生成对应的.css文件。\n\n\n\n## Sass的语法\n\n### 两种后缀名（两种语法）\n\nsass 有两种后缀名文件：\n\n（1）`.sass`：对空格敏感。不使用大括号和分号，所以每个属性之间是通过换行来分隔。\n\n比如：\n\n```\nh1\n\tcolor: #000\n\tbackground: #fff\n```\n\n这种语法是类ruby的语法，和CSS的语法相比，相差较大。所以，在3.0版本中就引入了`.scss`的语法。\n\n\n\n（2）`.scss`：是css语法的超集，可以使用大括号和分号。\n\n比如：\n\n```\nh1 {\n\tcolor: #000;\n\tbackground: #fff;\n}\n```\n\n\n注意：一个项目中可以混合使用两种语法，但是一个文件中不能同时使用两种语法。\n\n\n**两种格式之间的转换：**\n\n我们在工程目录下新建`main.scss`，输入如下代码：\n\n```\n*{\n    margin: 0;\n    padding: 0;\n}\n```\n\n然后输入如下命令，就可以将上面的`main.scss`转化为`main.sass`：\n\n```bash\n\tsass-convert main.scss main.sass\n```\n\n\n打开生成的`main.sass`，内容如下：\n\n```\n*\n  margin: 0\n  padding: 0\n\n```\n\n\n### 变量语法\n\nSass 是通过`$`符号来声明变量。\n\n（1）我们新建一个文件`_variables.scss`，这个文件专门用来存放变量，然后在其他的文件中引入`_variables.scss`即可。\n\n因为这个文件只需要存储变量，并不需要它编译出对应的 css 文件，所以我们给文件名的前面加了**下划线**，意思是声明为**局部文件**。\n\n我们在这个文件中，声明两个字体变量：\n\n```css\n$font1: Braggadocio, Arial, Verdana, Helvetica, sans-serif;\n\n$font2: Arial, Verdana, Helvetica, sans-serif;\n```\n\n\n（2）新建文件main.scss，在里面引入步骤（1）中的变量文件：\n\n```\n@import \"variables\";    // 引入变量文件\n\n.div1{\n    font-family: $font1;\n}\n\n.div2{\n    font-family: $font2;\n}\n```\n\n基于 Sass 的既定规则：\n\n- 没有文件后缀名时，Sass 会自动添加 .scss 或者 .sass 的后缀（具体要看已经存在哪个后缀的文件）。\n\n- 同一目录下，局部文件和非局部文件不能重名。\n\n对应生成的main.css文件如下：\n\nmain.css\n\n```css\n/* line 9, ../sass/main.scss */\n.div1 {\n  font-family: Braggadocio, Arial, Verdana, Helvetica, sans-serif;\n}\n\n/* line 13, ../sass/main.scss */\n.div2 {\n  font-family: Arial, Verdana, Helvetica, sans-serif;\n}\n\n```\n\n\n\n### 注释语法\n\n单行注释：\n\n```\n//我是单行注释\n```\n\n块级注释：\n\n```\n/*\n\t我是块级注释\n\t哈哈\n*/\n\n```\n\n\n\n二者的区别是：单行注释不会出现在自动生成的css文件中。\n\n\n\n\n"
  },
  {
    "path": "02-CSS基础/16-浏览器的兼容性问题.md",
    "content": "---\ntitle: 16-浏览器的兼容性问题\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n我们在div里放一个img，发现：\n\n在html和html5中，div的长宽是不同的，后者的高度要超过几个像素。\n\n比如说，下面这个是html的。\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Document</title>\n\t<style type=\"text/css\">\n\t\t*{\n\t\t\tmargin: 0px;\n\t\t\tpadding: 0px;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div>\n\t<img src=\"/Users/smyhvae/Dropbox/img/20170813_1143.jpg\" alt=\"\">\n\n\t</div>\n</body>\n</html>\n```\n"
  },
  {
    "path": "02-CSS基础/17-CSS3的常见边框汇总.md",
    "content": "---\ntitle: 17-CSS3的常见边框汇总\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## CSS3 常见边框汇总\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CSS3 边框</title>\n    <style>\n        body, ul, li, dl, dt, dd, h1, h2, h3, h4, h5 {\n            margin: 0;\n            padding: 0;\n        }\n\n        body {\n            background-color: #F7F7F7;\n        }\n\n        .wrapper {\n            width: 1000px;\n            margin: 0 auto;\n            padding: 20px;\n            box-sizing: border-box;\n        }\n\n        header {\n            padding: 20px 0;\n            margin-bottom: 20px;\n            text-align: center;\n        }\n\n        header h3 {\n            line-height: 1;\n            font-weight: normal;\n            font-size: 28px;\n        }\n\n        .main {\n            /*overflow: hidden;*/\n        }\n\n        .main:after {\n            content: '';\n            clear: both;\n            display: block;\n        }\n\n        .main .item {\n            width: 210px;\n            height: 210px;\n            margin: 0 30px 30px 0;\n            display: flex;\n            position: relative;\n            background-color: #FFF;\n            float: left;\n            box-shadow: 2px 2px 5px #CCC;\n        }\n\n        .main .item:after {\n            content: attr(data-brief);\n            display: block;\n            width: 100%;\n            height: 100%;\n            text-align: center;\n            line-height: 210px;\n            position: absolute;\n            top: 0;\n            left: 0;\n            color: #FFF;\n            font-family: '微软雅黑';\n            font-size: 18px;\n            background-color: rgba(170, 170, 170, 0);\n            z-index: -1;\n            transition: all 0.3s ease-in;\n        }\n\n        .main .item:hover:after {\n            background-color: rgba(170, 170, 170, 0.6);\n            z-index: 100;\n        }\n\n        .main .item:nth-child(4n) {\n            margin-right: 0;\n        }\n\n        /*.main .item:nth-last-child(-n+5) {\n            margin-bottom: 0;\n        }*/\n\n        /* 以上是骨架样式 */\n        /* 1、2、3、4 顺时针 */\n        .border-radius {\n            width: 180px;\n            height: 180px;\n            margin: auto;\n            border: 1px solid red;\n            /*border-radius: 50% 30% 20%;*/\n        }\n\n        .square {\n            border-radius: 0;\n        }\n\n        /*拱形*/\n        .item:nth-child(1) .border-radius {\n            border-radius: 90px;\n        }\n\n        /*拱形*/\n        .item:nth-child(2) .border-radius {\n            border-radius: 90px 90px 0 0;\n        }\n\n        /*半圆*/\n        .item:nth-child(3) .border-radius {\n            height: 90px;\n            border-radius: 90px 90px 0 0;\n        }\n\n        /*左上角*/\n        .item:nth-child(4) .border-radius {\n            /*height: 90px;*/\n            border-radius: 90px 0 0 0;\n        }\n\n        /*四分之一圆*/\n        .item:nth-child(5) .border-radius {\n            width: 90px;\n            height: 90px;\n            border-radius: 90px 0 0 0;\n        }\n\n        /*横着的椭圆*/\n        .item:nth-child(6) .border-radius {\n            height: 90px;\n            /*border-radius: 50%;*/\n            border-radius: 90px 90px 90px 90px / 45px 45px 45px 45px;\n            /*border-radius: 45px / 90px;*/\n        }\n\n        /*竖着的椭圆*/\n        .item:nth-child(7) .border-radius {\n            width: 90px;\n            border-radius: 45px 45px 45px 45px / 90px 90px 90px 90px;\n        }\n\n        /*半个横着的椭圆*/\n        .item:nth-child(8) .border-radius {\n            height: 45px;\n            border-radius: 90px 90px 0 0 / 45px 45px 0 0;\n        }\n\n        /*半个竖着的椭圆*/\n        .item:nth-child(9) .border-radius {\n            width: 45px;\n            border-radius: 45px 0 0 45px / 90px 0 0 90px;\n        }\n\n        /*四分之一竖着的椭圆*/\n        .item:nth-child(10) .border-radius {\n            width: 45px;\n            height: 90px;\n            border-radius: 45px 0 0 0 / 90px 0 0 0;\n        }\n\n        /*饼环*/\n        .item:nth-child(11) .border-radius {\n            width: 40px;\n            height: 40px;\n            border: 70px solid red;\n            border-radius: 90px;\n        }\n\n        /*圆饼*/\n        .item:nth-child(12) .border-radius {\n            width: 40px;\n            height: 40px;\n            border: 70px solid red;\n            border-radius: 60px;\n        }\n\n        /*左上角圆饼*/\n        .item:nth-child(13) .border-radius {\n            width: 60px;\n            height: 60px;\n            border: 60px solid red;\n            border-radius: 90px 0 0 0;\n        }\n\n        /*对角圆饼*/\n        .item:nth-child(14) .border-radius {\n            width: 60px;\n            height: 60px;\n            border: 60px solid red;\n            border-radius: 90px 0 90px 0;\n        }\n\n        /*四边不同色*/\n        .item:nth-child(15) .border-radius {\n            width: 0px;\n            height: 0px;\n            border-width: 90px;\n            border-style: solid;\n            border-color: red green yellow blue;\n        }\n\n        /*右透明色*/\n        .item:nth-child(16) .border-radius {\n            width: 0px;\n            height: 0px;\n            border-width: 90px;\n            border-style: solid;\n            border-color: red green yellow blue;\n            border-right-color: transparent;\n        }\n\n        /*圆右透明色*/\n        .item:nth-child(17) .border-radius {\n            width: 0px;\n            height: 0px;\n            border-width: 90px;\n            border-style: solid;\n            border-color: red;\n            border-right-color: transparent;\n            border-radius: 90px;\n        }\n\n        /*圆右红透明色*/\n        .item:nth-child(18) .border-radius {\n            width: 0px;\n            height: 0px;\n            border-width: 90px;\n            border-style: solid;\n            border-color: transparent;\n            border-right-color: red;\n            border-radius: 90px;\n        }\n\n        /*阴阳图前世*/\n        .item:nth-child(19) .border-radius {\n            width: 180px;\n            height: 0px;\n            border-top-width: 90px;\n            border-bottom-width: 90px;\n            border-style: solid;\n            border-top-color: red;\n            border-bottom-color: green;\n            /*border-right-color: red;*/\n            border-radius: 90px;\n        }\n\n        /*阴阳图前世2*/\n        .item:nth-child(20) .border-radius {\n            width: 180px;\n            height: 90px;\n            border-bottom-width: 90px;\n            border-style: solid;\n            border-bottom-color: green;\n            background-color: red;\n            /*border-right-color: red;*/\n            border-radius: 90px;\n        }\n\n        /*阴阳图今生*/\n        .item:nth-child(21) .border-radius {\n            width: 180px;\n            height: 90px;\n            border-bottom-width: 90px;\n            border-style: solid;\n            border-bottom-color: green;\n            background-color: red;\n            border-radius: 90px;\n            position: relative;\n        }\n\n        .item:nth-child(21) .border-radius::after,\n        .item:nth-child(21) .border-radius::before {\n            content: '';\n            position: absolute;\n            top: 50%;\n            width: 20px;\n            height: 20px;\n            /*margin: -10px 0 0 0;*/\n            border-width: 35px;\n            border-style: solid;\n            border-radius: 45px;\n        }\n\n        /*左阴阳*/\n        .item:nth-child(21) .border-radius::after {\n            background-color: red;\n            border-color: green;\n        }\n\n        /*右阴阳*/\n        .item:nth-child(21) .border-radius::before {\n            background-color: green;\n            border-color: red;\n            right: 0;\n        }\n\n        /*右阴阳*/\n        .item:nth-child(22) .border-radius {\n            width: 180px;\n            height: 90px;\n            border-bottom-width: 90px;\n            border-bottom-color: green;\n            border-bottom-style: solid;\n            background-color: red;\n            border-radius: 90px;\n            position: relative;\n        }\n\n        .item:nth-child(22) .border-radius::after,\n        .item:nth-child(22) .border-radius::before {\n            content: '';\n            position: absolute;\n            top: 50%;\n            width: 20px;\n            height: 20px;\n            border-width: 35px;\n            border-style: solid;\n            border-radius: 45px;\n        }\n\n        .item:nth-child(22) .border-radius::before {\n            border-color: green;\n            background-color: red;\n        }\n\n        .item:nth-child(22) .border-radius::after {\n            right: 0;\n            border-color: red;\n            background-color: green;\n        }\n\n        /*消息框*/\n        .item:nth-child(23) .border-radius {\n            width: 160px;\n            height: 80px;\n            background-color: red;\n            border-radius: 6px;\n            position: relative;\n        }\n\n        .item:nth-child(23) .border-radius::after {\n            content: '';\n            width: 0;\n            height: 0;\n            border-width: 10px;\n            border-style: solid;\n            border-color: transparent;\n            border-right-color: red;\n            position: absolute;\n            top: 16px;\n            left: -20px;\n        }\n\n        /*奇怪的图形*/\n        .item:nth-child(24) .border-radius {\n            width: 40px;\n            height: 40px;\n            border-width: 45px 0 45px 70px;\n            border-style: solid;\n            border-radius: 0 0 60px 0;\n            border-color: red;\n        }\n\n        /*奇怪的图形2*/\n        .item:nth-child(25) .border-radius {\n            width: 100px;\n            height: 40px;\n            border-width: 45px 20px 45px 70px;\n            border-style: solid;\n            border-radius: 60px;\n            border-color: red;\n        }\n\n        /*QQ对话*/\n        .item:nth-child(26) .border-radius {\n            width: 160px;\n            height: 80px;\n            background-color: red;\n            border-radius: 6px;\n            position: relative;\n        }\n\n        .item:nth-child(26) .border-radius::after {\n            content: '';\n            position: absolute;\n            top: 0;\n            right: -20px;\n            width: 30px;\n            height: 30px;\n            border-width: 0 0 30px 30px;\n            border-style: solid;\n            border-bottom-color: red;\n            border-left-color: transparent;\n            border-radius: 0 0 60px 0;\n        }\n\n        /*圆角的百分比设置 */\n        .item:nth-child(27) .border-radius {\n            width: 180px;\n            /*height: 180px;*/\n            height: 90px;\n            border-radius: 50%;\n            border-radius: 90px/45px;\n\n            /*百分比是按横竖两个对应的方向的长度进行计算*/\n        }\n\n    </style>\n</head>\n<body>\n<div class=\"wrapper\">\n    <header>\n        <h3>CSS3 边框圆角</h3>\n    </header>\n    <div class=\"main\">\n        <div class=\"item\" data-brief=\"整圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"拱形\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"半圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"左上角\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"四分之一圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"横着的椭圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"竖着的椭圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"半个横着的椭圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"半个竖着的椭圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"四分之一竖着的椭圆\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"饼环\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"圆饼\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"左上角圆饼\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"对角圆饼\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"四边不同色\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"右透明色\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"圆右透明色\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"圆右红透明色\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"阴阳图前世\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"阴阳图前世2\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"阴阳图今生\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"阴阳图今生2\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"消息框\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"奇怪的图形\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"奇怪的图形2\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"QQ对话\">\n            <div class=\"border-radius\"></div>\n        </div>\n        <div class=\"item\" data-brief=\"圆角百分比\">\n            <div class=\"border-radius\"></div>\n        </div>\n    </div>\n</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180208_1730.png)\n\n\n## 爱心\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .heart {\n            width: 200px;\n            height: 300px;\n            /*border: 1px solid #000;*/\n            margin: 100px auto;\n            position: relative;\n        }\n\n        .heart::before, .heart::after {\n            content: \"左一半\";\n            width: 100%;\n            height: 100%;\n            position: absolute;\n            background-color: red;\n            left: 0;\n            top: 0;\n\n            border-radius: 100px 100px 0 0;\n            transform: rotate(-45deg);\n            text-align: center;\n            line-height: 100px;\n            color: yellow;\n            font-size: 30px;\n            font-family: \"MIcrosoft Yahei\";\n        }\n\n        .heart::after {\n            content: \"右一半\";\n            left: 71px;\n            transform: rotate(45deg);\n        }\n    </style>\n</head>\n<body>\n<div class=\"heart\">\n\n</div>\n</body>\n</html>\n```\n\n\n效果如下：\n\n![](http://img.smyhvae.com/20180208_1737.png)\n\n它其实是下面这两个盒子叠起来的：\n\n![](http://img.smyhvae.com/20180208_1738.png)\n\n改变 `.heart::after` 的 left值，即可叠起来。\n\n\n\n"
  },
  {
    "path": "02-CSS基础/others.md",
    "content": "---\npublish: false\n---\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n</head>\n<style>\n    label {\n        display: block;\n        vertical-align: middle;\n    }\n\n    label,\n    input,\n    select {\n        vertical-align: middle;\n    }\n\n    .mui-switch {\n        width: 52px;\n        height: 31px;\n        position: relative;\n        border: 1px solid #dfdfdf;\n        background-color: #fdfdfd;\n        box-shadow: #dfdfdf 0 0 0 0 inset;\n        border-radius: 20px;\n        border-top-left-radius: 20px;\n        border-top-right-radius: 20px;\n        border-bottom-left-radius: 20px;\n        border-bottom-right-radius: 20px;\n        background-clip: content-box;\n        display: inline-block;\n        -webkit-appearance: none;\n        user-select: none;\n        outline: none;\n    }\n\n    .mui-switch:before {\n        content: '';\n        width: 29px;\n        height: 29px;\n        position: absolute;\n        top: 0px;\n        left: 0;\n        border-radius: 20px;\n        border-top-left-radius: 20px;\n        border-top-right-radius: 20px;\n        border-bottom-left-radius: 20px;\n        border-bottom-right-radius: 20px;\n        background-color: #fff;\n        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n    }\n\n    .mui-switch:checked {\n        border-color: #64bd63;\n        box-shadow: #64bd63 0 0 0 16px inset;\n        background-color: #64bd63;\n    }\n\n    .mui-switch:checked:before {\n        left: 21px;\n    }\n\n    .mui-switch.mui-switch-animbg {\n        transition: background-color ease 0.4s;\n    }\n\n    .mui-switch.mui-switch-animbg:before {\n        transition: left 0.3s;\n    }\n\n    .mui-switch.mui-switch-animbg:checked {\n        box-shadow: #dfdfdf 0 0 0 0 inset;\n        background-color: #64bd63;\n        transition: border-color 0.4s, background-color ease 0.4s;\n    }\n\n    .mui-switch.mui-switch-animbg:checked:before {\n        transition: left 0.3s;\n    }\n\n    .mui-switch.mui-switch-anim {\n        transition: border cubic-bezier(0, 0, 0, 1) 0.4s, box-shadow cubic-bezier(0, 0, 0, 1) 0.4s;\n    }\n\n    .mui-switch.mui-switch-anim:before {\n        transition: left 0.3s;\n    }\n\n    .mui-switch.mui-switch-anim:checked {\n        box-shadow: #64bd63 0 0 0 16px inset;\n        background-color: #64bd63;\n        transition: border ease 0.4s, box-shadow ease 0.4s, background-color ease 1.2s;\n    }\n\n    .mui-switch.mui-switch-anim:checked:before {\n        transition: left 0.3s;\n    }\n\n    /*# sourceMappingURL=mui-switch.css.map */\n</style>\n\n<body>\n    <label>\n        <input class=\"mui-switch\" type=\"checkbox\"> 默认未选中</label>\n    <label>\n        <input class=\"mui-switch\" type=\"checkbox\" checked> 默认选中</label>\n    <label>\n        <input class=\"mui-switch mui-switch-animbg\" type=\"checkbox\"> 默认未选中,简单的背景过渡效果,加mui-switch-animbg类即可</label>\n    <label>\n        <input class=\"mui-switch mui-switch-animbg\" type=\"checkbox\" checked> 默认选中</label>\n    <label>\n        <input class=\"mui-switch mui-switch-anim\" type=\"checkbox\"> 默认未选中，过渡效果，加 mui-switch-anim 类即可\n    </label>\n    <label>\n        <input class=\"mui-switch mui-switch-anim\" type=\"checkbox\" checked> 默认选中</label>\n</body>\n\n</html>\n```"
  },
  {
    "path": "03-CSS进阶/00-准备.md",
    "content": "---\r\ntitle: 00-准备\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n## 前言\r\n\r\ncss 进阶的主要内容如下。\r\n\r\n### 1、css 非布局样式\r\n\r\n- html 元素的分类和特性\r\n\r\n- css 选择器\r\n\r\n- css 常见属性（非布局样式）\r\n\r\n### 2、css 布局相关\r\n\r\n- css 布局属性和组合解析\r\n\r\n- 常见布局方案\r\n\r\n- 三栏布局案例\r\n\r\n### 3、动画和效果\r\n\r\n> 属于 css 中最出彩的内容。\r\n\r\n- 多背景多投影特效\r\n\r\n- 3D特效编写实践\r\n\r\n- 过渡动画和关键帧动画实践\r\n\r\n- 动画细节和原理深入解析\r\n\r\n### 4、框架集成和 css 工程化\r\n\r\n- 预处理器作用和原理\r\n\r\n- less/sass 代码实践\r\n\r\n- Bootstrap 原理和用法\r\n\r\n- css 工程化的的实践方式\r\n\r\n- js 框架中的 css 集成实践\r\n\r\n## 常见问题\r\n\r\n> 不会 css 的前端称之为伪前端。\r\n\r\n### Vue 中模拟Scoped CSS的方式\r\n\r\n方案一：随机选择器。css  modules。\r\n\r\n方案二：**随机属性**。`<div abcd>`、`div[adcd]{}`\r\n\r\n### 其他问题\r\n\r\n- html 元素的嵌套关系是怎么确定的？哪些嵌套不可以发生？\r\n\r\n- 比如说，为什么 div 可以放在 a 标签里面？\r\n\r\n- css 选择器的权重是如何计算的？写代码时要注意什么？\r\n\r\n- 浮动布局是怎么回事？有什么优缺点？国内用的多吗？\r\n\r\n- css 可否做逐帧动画吗？性能如何？\r\n\r\n- Bootstrap 怎么做响应式布局？\r\n\r\n- 如何解决 css 模块化过程中的选择器互相干扰的问题？\r\n\r\n## 总结\r\n\r\n单独看 css 属性并不难，难的是需要把这些思路和思想，想到它的应用场景。\r\n\r\n"
  },
  {
    "path": "03-CSS进阶/01-CSS中的非布局样式.md",
    "content": "---\r\ntitle: 01-CSS中的非布局样式\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n## 前言\r\n\r\nCSS中，有很多**非布局样式**，这些样式（属性）和与布局无关，包括：\r\n\r\n- 字体、字重、颜色、大小、行高\r\n- 背景、边框\r\n- 滚动、换行\r\n- 装饰性属性（粗体、斜体、下划线）等。\r\n\r\n这篇文章，我们来对上面的部分样式做一个回顾。\r\n\r\n## 边框\r\n\r\n如何用边框画一个三角形？详见《02-CSS基础/06-CSS盒模型详解》中的最后一段。\r\n\r\n## 文字换行\r\n\r\n- ovferflow-wrap：通用的属性。用来说明当一个不能被分开的字符串（单词）太长而不能填充其包裹盒时，为防止其溢出，浏览器是否允许这样的单词**中断换行**。\r\n\r\n- word-break：指定了怎样在单词内断行。这里涉及到CJK（中文/日文/韩文）的文字换行。\r\n\r\n- white-space：空白处是否换行。\r\n\r\n上面这三个 css 属性进行组合，可以设置各种不同的属性。\r\n\r\n当然，如果想让一段很长的文本不换行，可以直接设置`white-space: nowrap` 这一个属性即可。如果想换行，可以试试`white-space: pre-wrap`。\r\n\r\n## CSS Hack\r\n\r\n- CSS Hack 的方式：不合法但可以生效的写法。\r\n\r\n- 可以用来解决一些浏览器的兼容性问题。\r\n\r\n- 缺点：难理解、难维护、易失效（比如浏览器升级后，hack可能会失效）。\r\n\r\n- 替代方案：特性检测。\r\n\r\n- 替代方案：针对性加 class\r\n\r\n\r\n## CSS 效果\r\n\r\n我们可以利用 CSS 实现各种效果，常见的效果属性有：\r\n\r\n- box-shadow：盒子的阴影\r\n\r\n- text-shadow：文本的阴影\r\n\r\n- border-radius\r\n\r\n- background\r\n\r\n- clip-path\r\n\r\n\r\n"
  },
  {
    "path": "03-CSS进阶/02-CSS布局.md",
    "content": "---\r\ntitle: 02-CSS布局\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n## 前言\r\n\r\n### 常见的布局属性\r\n\r\n（1）`display` 确定元素的显示类型：\r\n\r\n- block：块级元素。\r\n\r\n- inline：行内元素。\r\n\r\n- inline-block：对外的表现是行内元素（不会独占一行），对内的表现是块级元素（可以设置宽高）。\r\n\r\n（2）`position` 确定元素的位置：\r\n\r\n- static：默认属性值。\r\n\r\n- relative：相对定位。相对于元素本身进行偏移，**不会改变它所占据的空间**。\r\n\r\n- absolute：绝对定位。相对于父元素中最近的 relative/absolute 进行偏移，会脱离文档流。音标：[ˈæbsəluːt]。\r\n\r\n- fixed：固定定位。相对于可视区域固定，会脱离文档流。\r\n\r\n`relative`、`absolute`、`fixed`这三个属性，可以结合 z-index 来设置层级。\r\n\r\n### 常见的布局方法\r\n\r\n1、**table 表格布局**：早期使用的布局，如今用得很少。\r\n\r\n2、**float 浮动 + margin**：为了兼容低版本的IE浏览器，很多网站（比如腾讯新闻、网易新闻、淘宝等）都会采用 float 布局。\r\n\r\n3、**inline-block 布局**：对外的表现是行内元素（不会独占一行），对内的表现是块级元素（可以设置宽高）。\r\n\r\n4、**flex 布局**：为布局而生，非常灵活，是最为推荐的布局写法。\r\n\r\n唯一的缺点是兼容性问题：\r\n\r\n![](http://img.smyhvae.com/20191005_1200.png)\r\n\r\n上图中可以看到， flex 布局不支持 IE9 及以下的版本。如果你的页面不需要处理 IE浏览器的兼容性问题，则可以放心大胆地使用 flex 布局。\r\n\r\nflex 是一种现代的布局方式，是 W3C 第一次提供真正用于布局的 CSS 规范。\r\n\r\n5、响应式布局。\r\n\r\n## float 布局\r\n\r\n是 CSS 中一种比较麻烦的属性，涉及到 BFC 和清除浮动（面试的重点）。\r\n\r\n### float 属性的特点\r\n\r\n- 元素浮动\r\n\r\n- **脱离文档流，但不脱离文本流**\r\n\r\n代码举例：\r\n\r\n下面这两个并列的`div1`和`div2`，默认是在标准流中的：\r\n\r\n![](http://img.smyhvae.com/20191005_2029.png)\r\n\r\n在此基础之上，如果给`div1`增加`float: left`属性后，效果如下：\r\n\r\n![](http://img.smyhvae.com/20191005_2037.png)\r\n\r\n上图中，可以看到，`div1`设置为浮动后，会脱离文档流，不会对`div2`的布局造成影响；但是`div1`不会脱离文本流，它会影响`div2`中文字的排列。\r\n\r\n其实，这正是 float 属性的作用。float 本身是用来做图文混排、文字环绕的效果。\r\n\r\n### float 所带来的影响\r\n\r\n**1、对自身的影响**：\r\n\r\n- 形成“块”（BFC）\r\n\r\n- 位置尽量靠上\r\n\r\n- 位置尽量靠左/右\r\n\r\n下面这两个并列的`div1`和`div2`，设置为浮动之后的效果：（都是尽量靠左显示的）\r\n\r\n![](http://img.smyhvae.com/20191005_2130.png)\r\n\r\n在上方代码的基础之上，增加 `div2`的宽度之后，会发现，`div2`掉下来了：\r\n\r\n![](http://img.smyhvae.com/20191005_2135.png)\r\n\r\n**2、对兄弟元素的影响**：\r\n\r\n- 不影响其他块级元素的位置\r\n\r\n- 影响其他块级元素的内部文本\r\n\r\n**3、对父级元素的影响**：\r\n\r\n- 从父级的布局中“消失”\r\n\r\n- 造成父级元素的高度塌陷：父级元素撑开 div1 之后（父级元素里没有其他元素的情况下），如果设置 div1 为 float 之后，，会让父级元素的高度变为0。\r\n\r\n## inline-block 布局\r\n\r\n对外的表现是行内元素（不会独占一行），对内的表现是块级元素（可以设置宽高）。\r\n\r\n**思路**：像文本一样去排列 block 元素，没有清除浮动等问题。\r\n\r\n**存在的问题**：需要处理间隙。代码举例如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n\t<meta charset=\"UTF-8\">\r\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n\t<title>Document</title>\r\n</head>\r\n<style>\r\n\t.container{\r\n\t\twidth: 300px;\r\n\t\theight: 300px;\r\n\t\tbackground: pink;\r\n\t}\r\n\r\n\t.div1{\r\n\t\twidth: 100px;\r\n\t\theight: 100px;\r\n\t\tbackground: green;\r\n\t\tdisplay: inline-block;\r\n\t}\r\n\r\n\t.div2{\r\n\t\twidth: 100px;\r\n\t\theight: 100px;\r\n\t\tbackground: yellowgreen;\r\n\t\tdisplay: inline-block;\r\n\t}\r\n\r\n\t.div3{\r\n\t\tbackground: yellow;\r\n\t}\r\n</style>\r\n\r\n<body>\r\n\t<div class=\"container\">\r\n\t\t<div class=\"div1\">div1的inline-block 属性</div>\r\n\t\t<div class=\"div2\">div2的inline-block 属性</div>\r\n\t\t<div class=\"div3\">\r\n\t\t\t琴棋书画不会，洗衣做饭嫌累。\r\n\t\t</div>\r\n\t</div>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n![](http://img.smyhvae.com/20191005_2200.png)\r\n\r\n上面的代码，存在两个问题。\r\n\r\n**问题一**：如果设置`div2`的宽度为 200px 之后，`div2` 掉下来。\r\n\r\n**问题二**：`div1`和`div2`设置为 inline-block之后，这两个盒子之间存在了间隙。这是因为，此时的 `div1`和`div2` 已经被当成文本了。文本和文本之间，本身就会存在间隙。\r\n\r\n为了去掉这个间隙，可以有几种解决办法：\r\n\r\n办法1：设置父元素`container`的字体大小为0，即`font-size: 0`，然后设置子元素 `div1`、`div2`的字体`font-size: 12px`。\r\n\r\n办法2：在写法上，去掉`div1`和`div2`之间的换行。改为：\r\n\r\n```html\r\n<div class=\"div1\">div1的inline-block 属性</div><div class=\"div2\">div2的inline-block 属性</div>\r\n```\r\n\r\n## 响应式布局\r\n\r\n移动端用得较多，本文暂时先不讲。\r\n\r\n\r\n"
  },
  {
    "path": "03-CSS进阶/03-网页设计和开发中，关于字体的常识.md",
    "content": "---\ntitle: 03-网页设计和开发中，关于字体的常识\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 前言\n\n我周围的码农当中，有很多是技术大神，却常常被字体这种简单的东西所困扰。\n\n这篇文章，我们来讲一讲关于字体的常识。这些常识所涉及到的问题，有很强的可操作性，都是在实际业务中真实遇到的，都是需要开发同学和产品经理、设计师不断重复沟通的。\n\n字体真的只是“**系统默认，无从解释**”这么简单吗？是产品被忽悠？还是开发在敷衍？二者之间的博弈究竟谁能胜出？学会本文，你就能胜出。\n\n## 字体分类\n\n常见的字体可以分为两类：**衬线体、无衬线体**。\n\n![](https://img.smyhvae.com/20191004_1101.png)\n\n**1、serif（衬线体）**：在字的笔画开始、结束的地方有额外的装饰，而且笔画的粗细会有所不同。\n\n常见的衬线体有：\n\n- 宋体、楷体\n\n- Times New Roman\n\n**2、sans-serif（无衬线体）**：笔划粗细基本一致，只剩下主干，造型简明有力，起源也很晚。适用于标题、广告等，识别性高。\n\n常见的无衬线体有：\n\n- 黑体\n- Windows 平台默认的中文字体：微软雅黑（Microsoft Yahei）\n- Windows 平台默认的英文字体：Arial\n- Mac & iOS 平台默认的中文字体：苹方（PingFang SC）\n- Mac & iOS 平台默认的英文字体：San Francisco\n- Android 平台默认字体：Droid Sans\n\n\n**补充**：\n\n衬线体如今已经很少使用了，你所熟悉的“宋体”，也基本只能在纸质出版物中见到。而**非衬线体更符合现代审美**。\n\n所以，在这里温馨提示各位：**做PPT不要用宋体**。如果你不知道用什么字体，那就用系统的默认字体就好：Win 平台用微软雅黑、Mac 平台用苹方字体。\n\n如果你发现一名设计师，在做海报设计、或者制图的时候，使用了宋体，说明她一定是个外行。\n\n## 字体族\n\nCSS 中的字体族可以理解成是某一类字体。常见的字体族可以分为五类：\n\n- serif：衬线体。\n- sans-serif：无衬线体。\n- monospace：等宽字体。每一个字母所占的宽度是相同的。写代码的字体尽量用等宽字体。\n- cursive：手写字体。比如徐静蕾手写体。\n- fantasy：梦幻字体。比如一些艺术字。\n\n这五类字体族不代表某一个具体的字体，而是当你在 CSS 中指定字体族的时候，系统就有可能在字体族中找出一种字体来显示。\n\n![](https://img.smyhvae.com/20191004_1130.png)\n\n参考链接：[serif，sans-serif，monospace，cursive和fantasy](http://www.ayqy.net/blog/serif%EF%BC%8Csans-serif%EF%BC%8Cmonospace%EF%BC%8Ccursive%E5%92%8Cfantasy/)\n\n## 多字体 fallback 机制\n\n多字体 fallback 机制：当指定的字体找不到（或者某些文字不支持这个字体）时，那就接着往后找。比如：\n\n```css\n.div1{\n    font-family: \"PingFang SC\", \"Microsoft Yahei\", monospace;\n}\n\n```\n\n上方 CSS 代码的意思是：让指定标签元素中的文字，在 Mac & iOS 平台用苹方字体，在 Win 平台用微软雅黑字体，如果这两个字体都没有，就随便找一个等宽的字体进行渲染。\n\n**注意**：\n\n（1）写 CSS 代码时，字体族不需要带引号。\n\n（2）有些 Mac 用户会安装 Office 等软件，安装这些软件后，系统会顺带安装微软雅黑字体。此时，写 CSS 代码时，如果写成 `\"Microsoft Yahei\", \"PingFang SC\"`这种顺序，可能导致有些 Mac 用户用微软雅黑来显示字体。这么好看的苹方字体，你忍心割舍吗？\n\n## font-weight：字体的加粗属性\n\nfont-weight 字体加粗属性，是让前端同学最迷茫的属性。当你把做好的网页拿给产品经理验收时，网页一打开，产品经理首先关注到的就是字体的加粗问题，你信不信？下面这些话都是产品经理的口头禅，想必早已让你产生了幻听：\n\n- “这个字体怎么没有加粗？”\n\n- “这个字体是不是太粗了点？”\n\n- “为什么 iPhone 和 Android 手机上的文字粗细不一致？”\n\n想要明白这些疑惑，我们先来看看 `font-weight` 有哪些属性值。\n\n**font-weight 属性**：在设置字体是否加粗时，属性值既可以直接填写 100 至 900 这样的数字，也可以填写`normal`、`bold`这样的单词。`normal`的值相当于 400，`bold`的值相当于 700。如下：\n\n\n```css\nfont-weight: 100;\nfont-weight: 200;\nfont-weight: 300;\nfont-weight: 400;\nfont-weight: 500;\nfont-weight: 600;\nfont-weight: 700;\nfont-weight: 800;\nfont-weight: 900;\n\nfont-weight: normal; // 相当于 400\nfont-weight: bold;   // 相当于 700\n```\n\n\n关键问题来了。很多人会发现，在 Windows 平台的浏览器中， font-weight 无论是设置300、400，还是500，文字的粗细都没有任何变化，只有到600的时候才会加粗一下，感觉浏览器好像不支持这些数值，那搞这么多档位不就是多余的吗？\n\n这个时候，大家就开始吐槽 Windows 电脑太挫、Windows 浏览器太挫；同时还会感叹 Mac 真香，支持字体的各种粗细。\n\n**实际上，所有这些数值关键字浏览器都是支持的，之所以没有看到任何粗细的变化，是因为你所使用的字体不支持**。\n\n就拿“微软雅黑”来举例，它只支持 400 和 700 这两种粗细，所以当你在代码里写成500的时候，也会被认为是400。但是 Mac 上的“苹方”字体，就支持从100到900之间的各种粗细。\n\n再比如，前段时间，阿里巴巴开源的普惠字体，也是支持多种粗细的：\n\n![](https://img.smyhvae.com/20191013_1100.png)\n\n## 各大平台的默认字体加粗效果\n\n一张图，胜过千言万语。解释了这么多，我们来看看各大平台的字体加粗效果是什么样的。\n\n以下截图，都是我亲测的结果，如果你打算让别人看效果，直接把下面的图丢给他即可。像我这样贴心的前端，不多见了。\n\n**1、Mac 平台的默认字体加粗效果**：（苹方字体）\n\n![](https://img.smyhvae.com/20191016_1205_mac.png)\n\n**2、Windows 平台的默认字体加粗效果**：（微软雅黑字体）\n\n![](https://img.smyhvae.com/20191016_1205_windows2.png)\n\n**3、iOS 平台的默认字体加粗效果**：（苹方字体）\n\n![](https://img.smyhvae.com/20191016_1205_ios.png)\n\n**4、Android 平台（华为 P30 Pro）的默认字体加粗效果**：（Droid Sans 字体）\n\n![](https://img.smyhvae.com/20191016_1205_huawei_p30_pro.jpeg)\n\n### 总结各大平台的默认字体加粗档位（字重）\n\n> 注意，系统默认的 normal 字重是400；加粗的 bold 字重是700。\n\n1、Mac & iOS 平台的“苹方”字体的字重：（有6种粗细，`>=600`的加粗效果是相同的）\n\n- 极细体：100\n- 纤细体：200\n- 细体：300\n- 常规体：400\n- 中黑体：500\n- 中粗体：600、700、800、900\n\n2、Windows 平台的“微软雅黑”字体的字重：（只有两种粗细 ；`>=600` 才会加粗，而且加粗效果相同）\n\n- 不加粗的默认字体：100、200、300、400、500\n\n- 加粗字体：600、700、800\n\n3、Android 平台的 Droid Sans 字体的字重：（只有 `>=700`才会加粗；而且加粗效果相同）\n\n- 不加粗的默认字体：100、200、300、400、500、600\n\n- 加粗字体：700、800\n\n\n\n**实战中，系统默认字体的加粗总结**：\n\n- 如果你做的软件产品只有苹果系统（比如iOS或Mac），可以使用各种粗细和字重。\n- 如果你做的软件产品包括了苹果系统（比如iOS或Mac）和非苹果系统（比如Android或Windows），建议直接使用normal（系统默认） 和 bold 这两种粗细。\n\n\n\n### 补充：“苹方”字体的粗细效果，大图预览\n\n“苹方”字体包含了六种自重：常规体、中等、细体、特粗体、特细体、粗体。对应的CSS样式如下：\n\n```css\n/* 苹方-简 极细体：100 */\nfont-family: PingFangSC-Ultralight;\n\n/* 苹方-简 纤细体：200*/\nfont-family: PingFangSC-Thin;\n\n/* 苹方-简 细体：300 */\nfont-family: PingFangSC-Light;\n\n/* 苹方-简 常规体：400 */\nfont-family: PingFangSC-Regular;\n\n/* 苹方-简 中黑体：500 */\nfont-family: PingFangSC-Medium;\n\n/* 苹方-简 中粗体：600、700、800、900 */\nfont-family: PingFangSC-Semibold;\n```\n\n大图预览如下：\n\n![](https://img.smyhvae.com/20201028-1400.png)\n\n\n我还要多说一句：我实际测试发现，苹方字体的200字重和300字重，在iOS上的粗细是不同的（符合预期），但在 Mac 上的粗细效果是相同的（无论把字体放大多少倍，都是如此）。具体你可以看看我在上面的截图效果对比。我目测这应该是Mac系统的bug。\n\n\n\n\n## 大部分字体不是免费的\n\n有一点你需要知道：你所见到的大部分字体，都不是免费的。换句话说，如果你想用第三方字体从事商业活动，要先交钱，获得授权后才可以使用。你在给公司做网页的时候，就是一种商业行为。\n\n「微软雅黑」是免费字体吗？并不是。你可以将微软雅黑字体用在 office 软件中，但是一旦脱离了 office 软件或者脱离了 Windows 系统（比如说，你把做好的PPT打印出来拿去做商业宣传），你就不能使用该字体。\n\n同理，苹果专属的「苹方字体」也只能在苹果系统的生态内使用。\n\n免费字体当然有，比如[思源黑体](https://baike.baidu.com/item/%E6%80%9D%E6%BA%90%E9%BB%91%E4%BD%93/14919098)（Adobe 和 Google 在2014年7月联合推出的一款开源字体）、阿里巴巴普惠体等。但这些免费字体，我们平时基本用不到。\n\n这也就是为什么，很多公司会专门购买一套商用字体库、甚至是自己开发一套字体出来，避免未来潜在的纠纷和麻烦。\n\n给大家列举一个常见的场景。很多时候，前端同学拿到的视觉稿是 psd 稿，需要用 PS 软件打开源文件，才能看到里面的文字是什么字体。在 PS 软件里，当我们用光标选中字体的时候，出现了下面这种场景：\n\n![](https://img.smyhvae.com/20191010_1121.png)\n\n看到上面的`FZLTZCHK`，不要慌，马上去 Google 查一下，发现这个字体的全称是`方正兰亭`字体系列。恩，基本可以肯定， 这个字体也是要收费的。\n\n这个时候，前端同学要马上告诉产品或者设计师，不要用这个字体，因为它是商用字体，是要收费的，小心吃官司。那我们该用什么字体呢？接着往下看。\n\n## 网页一般用什么字体\n\n大多数情况下，网页使用系统默认的字体就足够了。如果要使用特殊字体，顶多只是让**阿拉伯数字**使用特殊字体。中文和英文，使用默认字体，完全足够。\n\n如果确实要使用特殊字体，只有这几种办法：\n\n- 使用开源的免费字体（比如思源黑体、阿里巴巴普惠体等）。但这类字体，种类很少，而且大多不是很好看。网页开发中，基本没人用。\n- 单独购买第三方的商用字体，获得授权。\n- 自己公司（找人）开发一套字体，给自己人用。\n\n关于第三种办法，下面，我将以「京东朗正体」来举例。\n\n## 自主研发的字体举例：京东朗正体\n\n\n\n### 使用举例\n\n比如 JD 公司就自主开发了一套商用字体`京东朗正体`，支持三种粗细。只允许 JD 公司自己用，别家公司不允许用。如下：\n\n```css\n/**\n * JD正黑体，提供三种字重，请严格按设计稿选择字体\n */\n @font-face {\n    font-family: 'JDZH-Light';\n    src: url('xxx.com/data/ppms/others/JDZH_Light.ttf') format('truetype');\n}\n\n@font-face {\n    font-family: 'JDZH-Regular';\n    src: url('xxx.com/data/ppms/others/JDZH_Regular.ttf') format('truetype');\n}\n\n@font-face {\n    font-family: 'JDZH-Bold';\n    src: url('xxx.com/data/ppms/others/JDZH_Bold.ttf') format('truetype');\n}\n\n@font-face {\n    font-family: 'JDZhengHT-EN-Bold';\n    src: url('xxx.com/data/ppms/others/JDZhengHei_01_Bold.ttf') format('truetype');\n}\n\n```\n\n为了使用这个`京东朗正体`字体， JD公司在实际开发网页时，是这样做的：\n\n- 步骤1：在公共的 CSS 文件中引入上方的字体库代码。这样的话，当页面加载时，用户的终端就会去下载这个字体库。\n\n- 步骤2：在业务代码中，针对目标样式，直接使用 `font-family: 'JDZH-Regular';`这样的代码，即可生效。\n\n这个`JDZH`，我们一般也只使用在阿拉伯数字中；中文和英文，建议使用系统默认字体就行，否则会导致字体文件过大。\n\n### 使用规则\n\n\n\n![](https://img.smyhvae.com/20201224_1916.png)\n\n\n\n![](https://img.smyhvae.com/20201224_1921.png)\n\n从资料中可以看出，**京东朗正体**的版权属于方正公司，而京东具有永久使用权。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 最后一段\n\n所谓「见微知著」，一个再不起眼的知识点，也是有很多学问的。光是“字体”这一点，就足够成为一门学科。\n\n2005年，苹果公司创始人乔布斯（Steve Jobs）在斯坦福大学的毕业典礼演讲上，有过这样一段话：\n\n> 当时的我从来没有期盼过我所学的这些东西，能够在我的生活中有什么实际的用处。\n\n> 但是到了十年之后，当我们在设计第一台 Macintosh 电脑时，这些所学都涌进了我的头脑。于是，我把这些设计融入到了 Mac 电脑之中，这也使这台 Mac 成为了第一台拥有漂亮字体的电脑。\n\n> 可以说，如果我当时没有退学，就不会有机会去参加我感兴趣的美术字课程，Mac 也就不会拥有那些美妙的排版和字体。而当 Windows 系统借鉴了Mac之后，似乎所有的电脑都应该是这个样子。\n\n> 当时的我没有办法把这些点点滴滴连接起来，但是，**当我十年后回顾的时候，一切都变得豁然开朗**。\n\n\n## 字体相关的网站推荐\n\n- 360字体版权查询：<https://fonts.safe.360.cn/>\n\n- 2020年最全的免费可商用字体清单：<https://github.com/wordshub/free-font>\n\n- 免费可商用字体-效果预览（跟上线的链接属于同一个项目）：<https://wordshub.github.io/free-font/index.html>\n\n- 常见的免费字体：<http://zenozeng.github.io/Free-Chinese-Fonts/>\n\n\n如果不花钱，免费可商用的字体挺少的，也才100多种。\n\n## 推荐阅读\n\n- [Web 中文字体处理总结](https://aotu.io/notes/2020/02/28/webfont-processing/index.html)\n\n- 常见的免费字体：<http://zenozeng.github.io/Free-Chinese-Fonts/>\n\n- 如何优雅的选择字体(font-family)：<https://segmentfault.com/a/1190000006110417>\n\n- iconfont：<https://www.iconfont.cn/>\n\n- [乔布斯斯坦福大学演讲-翻译](https://zhuanlan.zhihu.com/p/24242767)\n\n- [乔布斯斯坦福大学演讲-翻译](https://www.douban.com/note/149058647/)\n\n- 阿里巴巴官方发布免费商用字体：阿里巴巴普惠体：<https://mp.weixin.qq.com/s/daKUNnF_Ste-O1l0sR89sQ>\n\n- 得到 | 从甲骨文至得到今楷，造字的人都是神：<https://mp.weixin.qq.com/s/ZnMxrhoH9piLf9EaSIwiGA>\n\n- [APP设计必备字体：San Francisco Pro、苹方、思源黑体和Roboto等](https://www.shejidaren.com/app-she-ji-bi-bei-zi-ti.html)\n\n- [《独立宣言》使用了什么字体？](https://mp.weixin.qq.com/s/i1qgUaSHrQlvqT-u3qJySw)\n\n- [你的版权常识指南](https://mp.weixin.qq.com/s/4uEBoajTygSADslzem3yZA)\n\n- [免费商用 | 这几款字体你值得拥有](https://mp.weixin.qq.com/s/wPqd0H9125bK7775KGoFbg)\n\n\n![](https://img.smyhvae.com/20191016_2030.png)\n\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n"
  },
  {
    "path": "03-CSS进阶/04-如何让一个元素水平垂直居中？.md",
    "content": "---\r\ntitle: 04-如何让一个元素水平垂直居中？\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n![](http://img.smyhvae.com/20191108_2130.png)\r\n\r\n## 前言\r\n\r\n老板的手机收到一个红包，为什么红包没居中？\r\n\r\n如何让一个子元素在父容器里**水平垂直居中**？这个问题必考，在实战开发中，也应用得非常多。\r\n\r\n你也许能顺手写出好几种实现方法。但大部分人的写法不够规范，经不起千锤百炼。换句话说：这些人也就面试的时候夸夸其谈，但真的上战场的时候，他们不敢这么写，也不知道怎么写最靠谱。\r\n\r\n这篇文章中，我们来列出几种常见的写法，最终你会明白，哪种写法是最优雅的。\r\n\r\n当然，我还会拿出实际应用中的真实场景来举例，让你感受一下**标准垂直居中的魅力**。\r\n\r\n## 如何让一个行内元素（文字、图片等）水平垂直居中\r\n\r\n> 行内元素的居中问题比较简单。\r\n\r\n### 行内元素水平居中\r\n\r\n给父容器设置：\r\n\r\n```\r\n    text-align: center;\r\n\r\n```\r\n\r\n### 行内元素垂直居中\r\n\r\n让**文字的行高** 等于 **盒子的高度**，可以让单行文本垂直居中。比如：\r\n\r\n```css\r\n    .father {\r\n        height: 20px;\r\n        line-height: 20px;\r\n    }\r\n```\r\n\r\n## 如何让一个块级元素水平垂直居中\r\n\r\n> 这一段是本文的核心。如何让一个块级的子元素在父容器里水平垂直居中？有好几种写法。我们一起来看看。\r\n\r\n### margin: auto 的问题\r\n\r\n在 CSS 中对元素进行水平居中是非常简单的：如果它是一个行内元素，就对它的父容器应用 `text-align: center`；如果它是一个块级元素，就对它自身应用 `margin: auto`或者 `margin: 0 auto`。\r\n\r\n在这里，`margin: auto`相当于`margin: auto auto auto auto`。`margin: 0 auto`相当于`margin: 0 auto 0 auto`，四个值分别对应上右下左。其计算值取决于**剩余空间**。\r\n\r\n但是，如果要对一个元素垂直居中，`margin: auto`就行不通了。\r\n\r\n比如下面这段代码：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Document</title>\r\n    <style>\r\n        .father{\r\n            height: 500px;\r\n            background: pink;\r\n        }\r\n        .son {\r\n            width: 300px;\r\n            height: 200px;\r\n            background: red;\r\n\r\n            margin: auto;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"father\">\r\n        <div class=\"son\"></div>\r\n    </div>\r\n    <script></script>\r\n</body>\r\n</html>\r\n\r\n```\r\n\r\n上面的代码中，父元素和子元素都是定宽高的，即便在这种情况下，我给子元素设置 `margin: auto`，子元素依然没有垂直居中。\r\n\r\n那还有没有比较好的通用的做法呢？\r\n\r\n### 方式1：绝对定位 + margin\r\n\r\n> 需要指定子元素的宽高，不推荐。\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Document</title>\r\n    <style>\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n        }\r\n        .father{\r\n            position: relative;\r\n            min-height: 500px;\r\n            background: pink;\r\n        }\r\n        .son {\r\n            position: absolute;\r\n            width: 200px;\r\n            height: 100px;\r\n            background: red;\r\n            top: 50%;\r\n            left: 50%;\r\n            margin-top: -50px;\r\n            margin-left: -100px;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"father\">\r\n        <div class=\"son\">子元素的内容</div>\r\n    </div>\r\n    <script></script>\r\n</body>\r\n</html>\r\n\r\n```\r\n\r\n**代码解释**：我们先让子元素的左上角居中，然后向上移动宽度的一半(50px)，就达到了垂直居中的效果；水平居中的原理类似。\r\n\r\n**不足之处**：要求指定子元素的宽高，才能写出 `margin-top` 和 `margin-left` 的属性值。\r\n\r\n但是，在通常情况下，对那些需要居中的元素来说，其宽高往往是由其内容来决定的，不建议固定宽高。\r\n\r\n### 方式2：绝对定位 + translate\r\n\r\n> 无需指定子元素的宽高，推荐。\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Document</title>\r\n    <style>\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n        }\r\n        .father{\r\n            position: relative;\r\n            min-height: 500px;\r\n            background: pink;\r\n        }\r\n        .son {\r\n            position: absolute;\r\n            background: red;\r\n            top: 50%;\r\n            left: 50%;\r\n            transform: translate(-50%, -50%);\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"father\">\r\n        <div class=\"son\">子元素的内容</div>\r\n    </div>\r\n    <script></script>\r\n</body>\r\n</html>\r\n```\r\n\r\n这种写法，在没有指定子元素宽高的情况下，也能让其在父容器中垂直居中。因为 translate() 函数中使用百分比值时，是以这个元素自身的宽度和高度为基准进行换算和移动的（**动态计算宽高**）。\r\n\r\n### 方式3：绝对定位 + top,left,bottom,right = 0 + margin: auto\r\n\r\n> 无需指定子元素的宽高，推荐。\r\n\r\n### 方式4：flex 布局（待改进）\r\n\r\n将父容器设置为 Flex 布局，再给父容器加个属性`justify-content: center`，这样的话，子元素就能水平居中了；再给父容器加个属性 `align-items: center`，这样的话，子元素就能垂直居中了。\r\n\r\n代码举例：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Document</title>\r\n    <style>\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n        }\r\n        .father{\r\n            display: flex;\r\n            justify-content: center;\r\n            align-items: center;\r\n            min-height: 100vh;\r\n            background: pink;\r\n        }\r\n        .son {\r\n            background: red;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"father\">\r\n        <div class=\"son\">子元素的内容</div>\r\n    </div>\r\n    <script></script>\r\n</body>\r\n</html>\r\n\r\n```\r\n\r\n上面这种写法，不足之处在于：给父容器设置属性`justify-content: center`和`align-items: center`之后，导致父容器里的所有子元素们都垂直居中了（如果父容器里有多个子元素的话）。可我明明想让指定的**某个子元素**居中，要怎么改进呢？\r\n\r\n### 方式5： flex 布局 + margin: auto（推荐）\r\n\r\n我们只需写两行声明即可：先给父容器设置 `display: flex`，再给指定的子元素设置我们再熟悉不过的 `margin: auto`，即可让这个指定的子元素在**剩余空间**里，水平垂直居中。大功告成。\r\n\r\n代码举例：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Document</title>\r\n    <style>\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n        }\r\n        .father{\r\n            display: flex;\r\n            min-height: 100vh;\r\n            background: pink;\r\n        }\r\n        .son {\r\n            margin: auto;\r\n            background: red;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"father\">\r\n        <div class=\"son\">子元素的内容，想水平垂直居中</div>\r\n        <div class=\"son2\">这个元素不想水平垂直居中</div>\r\n    </div>\r\n    <script></script>\r\n</body>\r\n</html>\r\n```\r\n\r\n请注意，当我们给父容器使用 Flex 布局 时，子元素的`margin: auto`不仅能让其在水平方向上居中，**垂直方向上也是居中的**。\r\n\r\n参考文章：[探秘 flex 上下文中神奇的自动 margin](https://www.cnblogs.com/coco1s/p/10910588.html)\r\n\r\n## 垂直居中的典型应用场景：红包幕帘/弹窗\r\n\r\n### 问题引入\r\n\r\n就拿“弹窗”这一点来说，现在大家的弹窗都是各种样式、各种布局满天飞。不过进公司后，新人在第一次写弹窗之前，都会问一个问题：“弹窗这么通用的东西，没有一个规范吗？”说完之后，又默默写自己的有个性的弹窗去了。\r\n\r\n建议大家在写弹窗的时候，无论如何，一定要**严格采用**水平居中、垂直居中的写法。\r\n\r\n千万不要用 `margin-top` 这种距离屏幕顶部的距离来计算弹窗的位置，太搓了。不要让领导觉得：“你们写了这么久的前端代码，连个弹窗都搞不定？”\r\n\r\n### 移动端，红包幕帘/弹窗 居中的规范写法（非常标准）\r\n\r\n移动端场景，这里提供一个 红包幕帘/弹窗 的居中写法。注意，是严格居中，非常标准。为什么是移动端？你有见过PC网页端给你送红包的么？\r\n\r\n在实战开发中，下面的这段代码，可以直接拿去用。注释详细，贴心无比。\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n    <head>\r\n        <meta charset=\"UTF-8\" />\r\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n        <title>Document</title>\r\n        <style>\r\n            /* 整个弹窗组件 */\r\n            .component_popup {\r\n                position: fixed;\r\n                top: 0;\r\n                bottom: 0;\r\n                left: 0;\r\n                right: 0;\r\n                z-index: 100;\r\n            }\r\n\r\n            /* 遮罩背景 */\r\n            .popup_mask {\r\n                position: fixed;\r\n                top: 0;\r\n                bottom: 0;\r\n                left: 0;\r\n                right: 0;\r\n                background: rgba(0, 0, 0, 0.7);\r\n            }\r\n\r\n            /* 弹窗的内容区域（正文内容 + close）：自适应宽高，且严格居中 */\r\n            .popup_content {\r\n                position: absolute;\r\n                top: 50%;\r\n                left: 50%;\r\n                transform: translate(-50%, -50%);\r\n            }\r\n\r\n            /* 弹窗的正文部分 */\r\n            .content_box {\r\n                width: 15.45rem;\r\n                height: 19.32rem;\r\n                background: url(http://img.smyhvae.com/20191010_1500_red-packet.png) no-repeat;\r\n                background-size: 15.45rem 19.32rem;\r\n            }\r\n\r\n            /* 弹窗的close图标 */\r\n            .content_close {\r\n                width: 1.25em;\r\n                height: 1.25em;\r\n                background: url(http://img.smyhvae.com/20191010_1500_close.png) no-repeat;\r\n                background-size: 1.25rem 1.25rem;\r\n                margin: 0 auto;\r\n                margin-top: 0.5rem;\r\n            }\r\n        </style>\r\n    </head>\r\n    <body>\r\n        <div class=\"content\">默认文档流中的页面主体</div>\r\n\r\n        <div class=\"component_popup\">\r\n            <div class=\"popup_mask\"></div>\r\n            <div class=\"popup_content\">\r\n                <div class=\"content_box\"></div>\r\n                <div class=\"content_close\"></div>\r\n            </div>\r\n        </div>\r\n    </body>\r\n</html>\r\n\r\n```\r\n\r\n实现效果：\r\n\r\n![](http://img.smyhvae.com/20191010_1510.png)\r\n\r\n**补充**：\r\n\r\n1、如果你的页面中，有很多弹窗，建议将弹窗封装成一个抽象组件。\r\n\r\n2、任何弹窗，都需要解决“滚动穿透”的问题，本文篇幅有限，请自行查阅。\r\n\r\n## 最后一段\r\n\r\n有些实现方式虽然简单，但必须要经得起千锤百炼。我们要做到**敬畏每一行代码**，不能浮于表面。团队开发，要的不是个性，而是**标准和规范**。\r\n\r\n## 参考链接\r\n\r\n- [为什么「margin:auto」可以让块级元素水平居中？](https://www.zhihu.com/question/21644198/answer/22392394)\r\n\r\n- [七种方式实现垂直居中](https://jscode.me/t/topic/1936)\r\n\r\n- [margin:auto实现绝对定位元素的水平垂直居中](http://www.zhangxinxu.com/wordpress/2013/11/margin-auto-absolute-%E7%BB%9D%E5%AF%B9%E5%AE%9A%E4%BD%8D-%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD/)\r\n\r\n## 我的公众号\r\n\r\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\r\n\r\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\r\n\r\n![](http://img.smyhvae.com/20190101.png)\r\n"
  },
  {
    "path": "03-CSS进阶/CSS开发积累.md",
    "content": "---\ntitle: 认识Web和Web标准\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n### 让flex盒子中的子元素们，居中\n\nflex布局常用的三行代码：\n\n```\n    display: flex;\n    justify-content: center; // 子元素在横轴的对齐方式 （左右居中）\n    align-items: center;  // 子元素在竖轴的对齐方式（上下居中）\n```\n\n\n### 让文字只显示一行，超出显示省略号\n\n```css\n\toverflow: hidden;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n\n```\n\n### 让文字最多只显示两行，超出后显示省略号\n\n```css\n\tdisplay: -webkit-box;\n\t-webkit-box-orient: vertical;\n\t-webkit-line-clamp: 2;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n```\n\n上面的代码中，我们把 `-webkit-line-clamp`的值设为1，也能达到“让文字只显示一行，超出显示省略号”的效果。\n\n参考链接：<https://blog.csdn.net/NN_nan/article/details/55045562>\n\n\n### 让文字最多只显示三行，超过后显示省略号\n\n> 如果当前元素处于flex布局等复杂的场景中，那么，样式可能比较难调，用上面两种写法未必能达到预期效果。此时可以试试下面这种写法（多加了一行 `white-space: normal`）。\n\n```css\n    display: -webkit-box;\n    -webkit-box-orient: vertical;\n    -webkit-line-clamp: 3;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: normal;\n```\n\n\n\n### 问题描述：文本内容里自带了换行，但是在前端没有展示出来\n\n解决办法：增加如下属性即可。\n\n```\nwhite-space: pre-wrap;\n```\n\n或者：\n\n```\nwhite-space: pre-line;\n```\n\n\n\n### 2019-11-12-CSS的逗号和空格\n\nCSS的逗号一般写在()里。**不同属性值之间，用的是空格**，不是逗号。比如：\n\n```css\n\ttransform: translate(-50%, -50%);  /* 这种写逗号 */\n\ttransform: translate(-50%, -50%) scale(0.5);   /* 不同属性值之间，用的是空格 */\n\n\tbackground-size: 100% 100%;  /* 这里是空格，不是逗号 */\n```\n\n\n### 2019-11-01\n\n人民币价格中的羊角符号，有半角和全角之分：\n\n- ¥半角\n\n- ￥全角\n\n可以看出，半角的宽度更小。考虑到容器的空间一般比较紧张，所以推荐使用**半角**。\n\n\n### 2019-11-19-鼠标悬停时，弹出提示文字\n\n参考链接：[css实现鼠标悬浮后的提示效果](https://www.cnblogs.com/zhaojian-08/p/10074660.html)\n\n\n### 2019-11-27-图片的宽度固定，高度自适应\n\n这个场景下，别用background。直接放img元素就好了，将图片的高度设置为`auto`。\n\n\n### 2020-03-26-通过CSS扩大点击热区\n\n```css\n.button {\n\tposition: relative;\n\t/* [其余样式] */\n}\n\n.button::before {\n\tcontent: '';\n\tposition: absolute;\n\ttop: -10px;\n\tright: -10px;\n\tbottom: -10px;\n\tleft: -10px;\n}\n```\n注意，button 里面不要写 `overflow: hidden` 属性，否则扩大的热区无效。\n\n参考链接：<https://www.jianshu.com/p/b83fc87cb222>\n\n### 2020-10-09-上下滚动，不显示滚动条\n\n```css\n.sku_list {\n\tmargin-left: 25rpx;\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\n\theight: 617rpx;\n\toverflow: hidden;\n\toverflow-y: scroll;\n\n\t/* 去掉滚动条 */\n\t&::-webkit-scrollbar {\n\t\tdisplay: none;\n\t}\n}\n```\n\n注意，去掉滚动条的那行代码里，建议用`display: none;`，不要用`width: 0;`。因为，当你需要设置横向滚动的效果时，`display: none;`这个属性的效果更好，不会改变容器的size；`width: 0;`可能会改变容器的size。\n\n参考链接：\n\n- [html设置局部区域上下滚动，不显示滚动条](https://blog.csdn.net/weixin_42157001/article/details/90176510)\n\n\n### 2021-05-06-设置页面的宽高，撑满屏幕\n\n一般来说，我们在开发一个页面的时候，默认是希望这个页面的宽高能够撑满屏幕的。代码可以这样写：\n\n```css\n.app {\n\twidth: 100vw;\n\tmin-height: 100vh;\n}\n\n```\n\n### 2021-09-13-JS中插入CSS\n\n代码举例：\n\n```js\nnew_element = document.createElement(\"style\");\nnew_element.innerHTML =(\".tucao-content p{font-size:18px;}\");\ndocument.body.appendChild(new_element);\n```\n\n参考链接：\n\n- js 插入公共css的方法：https://blog.csdn.net/u013970232/article/details/90578937\n"
  },
  {
    "path": "03-CSS进阶/CSS文章推荐.md",
    "content": "---\ntitle: 认识Web和Web标准\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n### 2020-03-16\n\nBootstrap 中文文档：<https://code.z01.com/v4/content/tables.html>\n\n\n### 2020-03-13\n\n- [CSS实现鼠标悬停弹出微信二维码](https://www.hanost.com/637.html)\n\n"
  },
  {
    "path": "03-CSS进阶/CSS的一些小知识.md",
    "content": "---\ntitle: 认识Web和Web标准\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 隐藏盒子的几种方式\n\n隐藏盒子，有以下几种方式：\n\n（1）方式一：\n\n```\noverflow：hidden;   //隐藏盒子超出的部分\n```\n\n\n（2）**方式二**：\n\n```\ndisplay: none;\t  隐藏盒子，而且不占位置(用的最多)\n```\n\n比如，点击`X`，关闭京东首页上方的广告栏。\n\n（3）方式三：\n\n```\nvisibility: hidden;   //隐藏盒子，占位置。\nvisibility: visible;   //让盒子重新显示\n\n```\n\n（4）方式四：\n\n```\nopacity: 0;       //设置盒子的透明度（不建议，因为内容也会半透明），占位置\n```\n\n\n（4）方式五：\n\n```\nPosition/top/left/...-999px   //把盒子移得远远的，占位置。\n```\n\n（5）方式六：\n\n```\nmargin-left: 1000px;\n```\n\n\n\n### 设置盒子的半透明\n\n方式一：`opacity: 0.4`。优点是方便。缺点是：里面的内容也会半透明。\n\n方式二：css3的技术来解决半透明。如下：\n\n- background: rgba(0,0,0,0.3);\n\n- background: rgba(0,0,0,.3);\n\n备注：`a`指的是alpha透明度。\n\n\n### 给标签的形状设置为圆角矩形\n\n```\nborder-radius: 50%;\nborder-radius: 10px 0 0 10px;\n```\n\n\n### 行高的问题：儿子把父亲撑开\n\n比如对于下面这样的标签：\n\n```\n\t<div>\n\t\t<a href=\"\"></a>\n\t</div>\n\n```\n\n\n前置条件：如果我们给父亲div的行高设为31px，然后给儿子a的行高也设置为31\n\n结果：当我们给儿子a设置了字体属性之后，会发现，父亲被撑高为32px了。因为font字体自身会比较大，多撑出了一个像素。\n\n解决办法：**行内元素尽量不要设置font属性**。对于行内元素而言，如果它和父亲都设置了行高，就不要去给自己设置font属性了。要么就，不要同时设置行高。\n\n\n### 背景图不能撑开盒子\n\n高和行高都可以城开盒子，但背景图不能撑开盒子。\n\n\n\n\n\n\n\n## JS\n\n### 超链接`<a>`的href跳转\n\n一个空白的超链接如下：\n\n```\n<a href=\"\"></a>\n```\n\n当点击超链接时，由于 href 的属性值的不同，可以产生很多种情况：\n\n```bash\n\thref=\"\"                    //刷新页面\n\n\thref=\"#\"                   //跳转到当前页面的顶部（不会刷新）\n\n\thref=\"javascript:void(0)\"  // 什么都不做\n\n\thref=\"javascript:;\"        // 什么都不做\n\n```\n\n"
  },
  {
    "path": "03-CSS进阶/CSS面试题.md",
    "content": "---\ntitle: 认识Web和Web标准\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 常见问题\n\n### 你是如何理解 HTML 语义化的？\n\n**语义化**：指对文本内容的结构化（内容语义化），选择合乎语义的标签（代码语义化）。\n\n**举例**：段落用 p，边栏用 aside，主要内容用 main 标签。\n\n**好处：**\n\n- 便于开发者阅读和维护\n\n- 有利于SEO：让浏览器的爬虫和辅助技术更好的解析，\n\n**语义化标签介绍**：\n\n在HTML5出来之前，我们习惯于用div来表示页面的章节或者不同模块，但是`div`本身是没有语义的。但是现在，HTML5中加入了一些语义化标签，来更清晰的表达文档结构。\n\n20180322_1120.jpg\n\n参考链接：\n\n- [初探 · HTML5语义化](https://zhuanlan.zhihu.com/p/32570423)\n\n### meta viewport 是做什么用的，怎么写？\n\n```html\n \t<meta name=\"viewport\" content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n```\n\n控制页面在移动端不要缩小显示。\n\n### canvas 元素是干什么的？\n\n看 MDN 的 [canvas 入门手册](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API)。\n\n### 说一下CSS盒模型\n\n可以参考本人的另外一篇文章：《02-CSS基础/06-CSS盒模型详解》。\n\n### css reset 和 Normalize.css 有什么区别\n\n> 此题考英文。\n\n二者都是用来**统一**浏览器的默认样式：\n\n- reset：重置。相对「暴力」，不管你有没有用，统统重置成一样的效果，且影响的范围很大，讲求跨浏览器的一致性。（一刀切）\n\n- `Normalize.css` ：标准化。相对「平和」，注重通用的方案，重置掉该重置的样式，保留有用的 user agent 样式，同时进行一些 bug 的修复，这点是 reset 所缺乏的。（去伪存真）\n\n参考链接：\n\n- [Normalize.css 与传统的 CSS Reset 有哪些区别？](https://p.baidu.com/question/ab496162636234613761335c00)\n\n- [CSS3初始化代码Normalize.css中文版](http://www.bbsxiaomi.com/html_css/html5_css3/177.html)\n\n- [谈谈一些有趣的 CSS 话题](https://github.com/chokcoco/iCSS)\n\n- [前端面试之CSS总结(上)](https://segmentfault.com/a/1190000006890725)\n\n\n### 选择器的优先级如何确定\n\n- 选择器越具体，优先级越高。 #xxx 大于 .yyy\n\n- 同样优先级，写在后面的覆盖前面的。\n\n- color: red !important; 优先级最高。\n\n\n### BFC 是什么\n\n\noverflow:hidden ：取消父子 margin 合并。 （另一种推荐做法：`padding-top: 0.1px;`）\n\n\n### 如何清除浮动\n\n（1）overflow: hidden\n\n（2）.clearfix 清除浮动写在爸爸身上\n\n```css\n    .clearfix::after {\n        content: '';\n        display: block;\n        clear: both;\n    }\n\n    /* 兼容 IE */\n    .clearfix {\n        zoom: 1;\n    }\n```\n\n### 伪类和伪元素的区别是什么？\n\n概念上的区别：\n\n- 伪类表示一种状态\n\n- 伪元素是真的有元素。比如 ::after 是真的有元素，可以在页面上显示内容。\n\n使用上的区别：\n\n- 伪类：使用单冒号\n\n- 伪元素：使用双冒号\n\n## 参考链接\n\n- [互联网公司招聘启事的正确阅读方式](https://zhuanlan.zhihu.com/p/33998813)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/01-编程语言和JavaScript简介.md",
    "content": "---\ntitle: 01-编程语言和JavaScript简介\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 计算机语言\n\n### 概念\n\n**计算机语言**：人与计算机之间通信的语言。它是人与计算机之间传递信息的媒介，它通过特定的语法规则和语义约定，将人类可理解的指令转化为计算机可以执行的机器指令。\n\n**计算机程序**：就是计算机所执行的一系列的**指令集合**，程序全部都是用我们所掌握的语言来编写的，如果我们要控制计算机，就需要通过计算机语言向计算机发出指令。\n\n### 计算机语言的分类\n\n计算机语言的种类非常多，总的来说可以分成三大类：**机器语言、汇编语言和高级语言**。他们之间的转换过程如下：\n\n![](https://img.smyhvae.com/image-20230820183113674.png)\n\n计算机最终所执行的都是机器语言，它是由“0”和“1”组成的二进制数，二进制是计算机语言的基础。\n\n**计算机语言**的范围比**编程语言**的范围更广，后者是前者的子集。比如：\n\n- HTML 是**标记语言**，CSS 是**样式语言**，这两个是属于**计算机语言**；但不属于**编程语言**，因为它们编写出来的不是**程序**，只是简单的标记和样式。\n- JavaScript 是属于**编程语言**，当然也属于**计算机语言**。\n\n计算机语言的分类如下：\n\n![image-20231001002948987](https://img.smyhvae.com/202310010029086.png)\n\n从机器语言到汇编语言，再到高级语言，这些语言的发展越来越高级，编写方式越来越接近人的思维。我们来具体看看这三种计算机语言的发展历史。\n\n### 机器语言\n\n计算机的存储单元只有0和1两种状态。\n\n将数字0和1按照一定的规律组成的编码称为**机器码**，也称为**二进制编码**或者**机器指令**或者**计算机指令**。用这些机器指令所编写的程序称为**机器语言**。\n\n机器语言的优点是可以直接被计算机识别、直接操作硬件、不需要经过编译解析、程序的执行效率很高；缺点是可读性差、可维护性差、出错概率高。\n\n现如今，几乎没有人用这种方式编程。\n\n### 汇编语言\n\n对于人类来说，二进制程序是不可读的，根本看不出来机器干了什么。为了解决可读性和维护性的问题，就诞生了汇编语言。\n\n**汇编语言**：是二进制指令的文本形式，与指令是一一对应的关系，比如，加法指令00000011写成汇编语言就是 ADD；汇编语言使用助记符（Mnemonics）来代替和表示特定低级机器语言的操作。\n\n汇编语言经过**汇编器**，可翻译成机器语言，进而被 CPU 直接执行，所以汇编语言是最底层的低级语言。\n\n不同的硬件系统有不同的汇编语言语法。换而言之，每一种特定的汇编语言和其特定的机器语言指令集是一一对应的。汇编语言的常见的应用场景有：**操作系统内核、驱动程序、单片机程序**等。\n\n举例：用汇编语言写一个简单的加法程序“3+5=8”，加数和结果分别存在内存单元NUM1、NUM2、NUM3中。\n\n[代码实现](https://blog.csdn.net/qq_40871466/article/details/84720703)：\n\n```asm\ndata segment\n  num1 db 3\n  num2 db 5\n  num3 db ?\ndata ends\ncode segment\nassume cs:code,ds:data\n start:mov ax,data\n    mov ds,ax\n    mov al,num1\n    add al,num2\n    mov num3,al\n    mov ax,4c00h\n    int 21h\ncode ends\nend start\n```\n\n### 高级语言\n\n高级语言更接近人的思维方式。\n\n优点是可读性好、易于理解；上手门槛低，无需熟悉硬件知识即可进行编程入门的学习；很多高级语言可跨平台兼容，具备可移植性，在不同的机器上运行。\n\n缺点是无法直接被计算机硬件识别，需要通过**编译器**翻译为机器语言后，才能在计算机上运行。\n\n编译器的作用，就是将高级语言写好的程序，翻译成一条条机器指令。\n\n## 编程语言\n\n### 概念\n\n**编程**：让计算机为解决特定的问题而使用某种程序设计语言编写程序代码，并最终得到结果的过程。\n\n**编程语言**：是一种用于编写计算机**程序**的形式化语言。它定义了一套语法和规则，用来描述计算机程序的结构和逻辑，并精确定义了在不同情况下需要执行的行为。编程语言通过编写程序代码，将人类的思想和需求转化为计算机可以理解和执行的指令。不同的编程语言有不同的特点和用途，这些特点至少有：\n\n- 数据结构和算法、数据处理\n- 流程控制（if语句、循环语句等）\n- 引用机制和重用机制\n- 设计思想\n\n不同的编程语言有不同的语法，需要遵守。常见的编程语言有 C 语言、C++、Java、JavaScript、Python 等。\n\n如今通用的编程语言大致可以分成两类：**高级语言和汇编语言**。\n\n- **高级语言**：主要是相对于低级语言而言，它并不是特指某一种具体的语言，而是包括了很多编程语言，比如：C 语言、C++、Java、C#、PHP、JavaScript、Python、Objective-C、Swift、Go 语言等。\n\n- **汇编语言**：与机器语言实质是相同的，都是直接对硬件操作，只不过指令采用了英文缩写的标识符，容易识别和记忆。\n\n### 编程语言的发展\n\n从汇编语言发展到现在，编程语言层出不穷，具体数目已无法考证，比较流行的有超过200种以上。\n\n比如在1958年发明的 Lisp 语言，历史悠久（是世界上第二个发明出来的语言，只比 FORTRAN 语言晚一年），现在仍然非常流行，是人工智能领域最受欢迎的编程语言。《黑客与画家》这本书的作者也十分推崇 Lisp 语言。\n\n## 编译型语言 VS 解释型语言\n\n### 翻译器\n\n计算机不能直接理解任何除机器语言以外的语言，所以必须要把程序员所编写的高级语言翻译成机器语言，计算机才能执行程序。为此，我们需要一个翻译器。**程序语言翻译成机器语言的工具，被称为翻译器**。\n\n由此可见，所谓的“翻译”，指的是将人类所编写的源代码转换（翻译）为机器能够执行的指令，这也被称为二进制化。\n\n翻译器翻译的方式有两种：一种是**编译**，另一种是**解释**。两种方式之间的区别在于**翻译的时机**不同。\n\n- **编译器**：在代码执行之前，事前把所有的代码一次性翻译好，生成中间代码文件，然后整体执行。\n\n- **解释器**：边翻译，边执行（在代码执行时进行及时翻译，并立即执行）。当编译器以解释的方式运行时，也称之为解释器。\n\n对应的语言，称之为“编译型语言”、“解释型语言”。\n\n### 1、编译型语言\n\n- 定义：需要事先通过编译器**把所有的代码一次性翻译（编译/转换）好**，然后整体执行。比如 exe 文件。\n\n- 优点：执行效率高，运行更快。\n\n- 不足：移植性不好，不跨平台；编译之后如果需要修改就需要整个模块重新编译。\n\n- 编译型语言举例：C、C++\n\n比如，C 语言的代码文件是`.c`后缀，翻译之后文件是`.obj`后缀，系统执行的是 obj 文件；再比如， java 语言的代码文件是`.java`后缀，翻译之后的文件是`.class`后缀。（但是，Java 语言不是严格的 编译型语言，这个稍后讲）\n\n以 C 语言的`hello.c`来举例：\n\n```c\n#include <stdio.h>\nint main(int argc, char const *argv[])\n{\n  printf(\"Hello world!\");\n  return 0;\n}\n```\n\n对于以上 C 语言代码，main 是程序入口，实现的功能是打印字符串`hello world`到屏幕上，编译和执行过程如下：\n\n1. C 语言代码经过预处理（比如依赖处理、宏替换）。以上方代码示例，`#include<stdio.h>`会在预处理阶段被替换。\n2. 编译：编译器会把 C 语言翻译成汇编程序。一条 C 语言通常被编译为多条汇编代码，同时编译器会对程序进行优化，生成目标汇编程序。旁征博\n3. 汇编语言通过汇编器再汇编成目标程序`hello.o`。\n4. 链接：程序中往往包含一些共享目标文件，如示例代码中的`printf()`函数位于静态库，需要经过链接器进行链接。\n\n![20211030-0031-2](https://img.smyhvae.com/20211030-0031-2.png)\n\n（上方图片来源：[JavaScript 基础-基本概念](https://www.jianshu.com/p/230093183f47) ）\n\n![20211030-0026-2](http://img.smyhvae.com/20211030-0026-2.png)\n\n（上方图片来源：[编译型语言](https://p.0x06.cn/zh/program/) ）\n\n### 2、解释型语言\n\n- 定义：在运行过程中（runtime）通过解释器**边翻译边执行**，也就是逐行翻译。不需要事先一次性翻译，而是在运行时，边翻译变执行（翻译一行，执行一行）。\n\n- 优点：移植性好，跨平台。\n\n- 缺点：运行速度不如编译型语言。\n\n- 解释型语言举例：JavaScript、PHP、Python。\n\n为什么 JS 是解释型语言呢？这和浏览器的工作原理有关。浏览器中有一个专门的“JS 解析器”可以让 JS 边解析边执行。\n\n由于少了事先编译这一步骤，所以解释型语言开发起来尤为方便，但是解释型语言运行较慢也是它的劣势。不过解释型语言中使用了 JIT 技术，使得运行速度得以改善。\n\n### Java 语言\n\nJava 语言是属于半编译半解释型语言。翻译过程：\n\n（1）编译：`.java`代码文件先通过 javac 命令编译成`.class`文件。\n\n（2）执行：`.class`文件再通过 jvm 虚拟机，解释执行。有了 jvm 的存在， java 就可以跨平台了。\n\n## JavaScript 的历史和发展\n\n### JavaScript 的历史\n\n1、JavaScript 诞生于**1995 年**，是由**网景**公司（Netscape）的员工 Brendan Eich（兰登 • 艾奇，1961 年～）发明，最初命名为 LiveScript。1995 年 12 月与 SUN 公司合作，因市场宣传需要，为了蹭 Java 的热度，改名为 JavaScript。\n\n发明这个语言的[背景](https://www.ruanyifeng.com/blog/2011/06/birth_of_javascript.html)是这样的：1994 年网景公司发布历史上第一个比较成熟的浏览器（Navigator 0.9）, 但是只能浏览不能**交互**。1995 年为了解决表单有效性验证就要与服务器进行多次地往返交互问题，网景公司录用 Brendan Eich（兰登 • 艾奇），他只用了 10 天就设计并研发出 一种网页脚本语言——LiveScript 语言的第一版。\n\n由于 Sun 公司当时的 Java 语言特别火，所以为了傍大牌，就借势改名为 JavaScript。Java 和 JavaScript 的关系，就好比：雷锋和雷峰塔的关系、老婆和老婆饼的关系、北大和北大青鸟的关系。“北大青鸟”就是傍“北大”的大牌。\n\nJavaScript 是 Sun 公司注册并授权给 Netscape 使用的商标。后来 Sun 公司被 Oracle 收购，JavaScript 版权归 Oracle 所有。\n\n1996 年，微软为了抢占市场，推出了`JScript`在 IE3.0 中使用。\n\n### ECMAScript 标准\n\nECMAScript 是一种由 ECMA 组织制定和发布的脚本语言规范。\n\n1996 年 11 月网景公司向 ECMA（European Computer Manufacturers Association，欧洲电脑制造商协会，属于国际标准化组织）提交了 JS的语言标准，将其成为国际标准，以此对抗微软。\n\n- ECMA 的技术委员负责制定和审核这个标准，成员由业内的大公司派出的工程师组成。该委员会定期开会，所有的邮件讨论和会议记录，都是公开的。\n\n- 1997年7月，ECMA 组织发布262号标准文件（ECMA-262）的第一版，规定了浏览器脚本语言的标准，并将这种语言规范称为 ECMAScript，这个版本就是 ECMAScript 1.0 版。简而言之，ECMA-262是一份标准文件，定义了 ECMAScript 这个语言规范。\n- JavaScript 成为了 ECMAScript最著名的实现之一。ECMAScript 和 JavaScript 的关系是，前者是后者的语法规范，后者是前者的一种实现。\n- 除此之外，ActionScript 和 JScript 也是遵守 ECMAScript 规范的语言。\n- ECMAScript 只用来标准化 JavaScript 这种语言的基本语法。与部署环境相关的标准则由其他标准规定，比如 DOM 的标准就是由 W3C组织（World Wide Web Consortium）制定的。\n\n同时期还有其他的网页语言，比如 VBScript、JScript 等等，但是后来都被 JavaScript 打败了，所以现在的浏览器中，只运行一种脚本语言就是 JavaScript。JavaScript 是世界上用的最多的**脚本语言**。\n\nJavaScript 是由公司开发而成的，问题是不便于其他的公司拓展和使用。所以 ECMA 组织，牵头制定了 JavaScript 的标准，取名为 ECMAScript。\n\n简单来说，**ECMAScript 不是一门语言，而是一个标准**。ECMAScript 规定了 JS 的编程语法和基础核心知识，是所有浏览器厂商共同遵守的一套 JS 语法工业标准。\n\nECMAScript 在 2015 年 6 月，发布了 ECMAScript 6 版本（ES6），语言的能力更强，包含了很多新特性。\n\n### JavaScript语言是个大杂烩\n\nBrendan Eich 这位天才只用了10天就设计出了 JS，但这门语言当时更像是一个[大杂烩](https://wangdoc.com/javascript/basic/history)：\n\n- 基本语法：借鉴 C 语言和 Java 语言。\n- 数据类型和数据结构：借鉴 Java 语言，包括将值分成原始值和对象两大类。\n- 函数：借鉴 Scheme 语言和 Awk 语言，将函数当作第一等公民，并引入闭包。\n- 基于原型的继承机制：借鉴 Self 语言（Smalltalk 的一种变种）。\n- 正则表达式：借鉴 Perl 语言。\n- 字符串和数组处理：借鉴 Python 语言。\n\n由于设计时间太短，语言的一些细节考虑得不够严谨，导致后来很长一段时间，Javascript写出来的程序混乱不堪。所以，Javascript语言实际上是两种语言风格的混合体：（简化的）函数式编程+（简化的）面向对象编程。这是由Brendan Eich（函数式编程）与网景公司（面向对象编程）共同决定的。\n\n十八世纪英国文学家约翰逊博士有一句名言说得好：“这个作品是好的，也是原创的，它的优秀之处并非原创，它的原创之处并不优秀。”（It is both good and original; but the part that is good is not original, and the part that is original is not good.）\n\n后来，随着ES6语法的不断改进，JS语言越来越优秀。ECMA 和 ECMAScript 赋予了 JavaScript 新的能力和活力。\n\n### JavaScript 的发展：蒸蒸日上\n\n2003 年之前，JavaScript 被认为“牛皮鲜”，用来制作页面上的广告，弹窗、漂浮的广告。什么东西让人烦，什么东西就是 JavaScript 开发的。所以很多浏览器就推出了屏蔽广告功能。\n\n2004 年，JavaScript 命运开始改变。那一年，**谷歌公司开始带头使用 Ajax 技术**，Ajax 技术就是 JavaScript 的一个应用。并且，那时候人们逐渐开始提升用户体验了。Ajax 有一些应用场景。比如，当我们在搜索引擎框搜文字时，输入框下方的智能提示，可以通过 Ajax 实现。比如，当我们注册网易邮箱时，能够及时发现用户名是否被占用，而不用跳到另外一个页面。从 2005 年开始，几乎整个 B/S 开发界都在热情地追捧 Ajax。\n\n2007 年乔布斯发布了第一款 iPhone，这一年开始，更多的用户使用移动设备上网。这一年，互联网开始标准化，按照 W3C 规则三层分离，JavaScript 越来越被重视。**JavaScript 在移动页面中，也是不可或缺的**。\n\n2010 年，人们更加了解**HTML5 技术**，**HTML5 推出了一个东西叫做 Canvas**（画布），工程师可以在 Canvas 上进行游戏制作，利用的就是 JavaScript。\n\n2011 年，**Node.js 诞生**，使 JavaScript 能够开发服务器端的程序。\n\n如今，**WebApp**已经非常流行，也就是用**网页技术开发手机应用**。手机系统有 iOS、安卓。公司如果要开发一个“美团”App，就需要招聘四队人马：iOS 工程师 10 人、安卓工程师 12 人，前端工程师 8 人、后端工程师 16 人，共 50 人左右，开发成本巨大；而且如果要做需求迭代，就要改 3 个版本。现在，假设公司都用 web 技术，用 html+css+javascript 这一套技术就可以开发多种终端的页面。也易于迭代（网页如果改变，则所有的终端都会生效）。\n\n有个故事是这么说的：\n\n> 2040 年，某年轻产品经理说：有没有那么一个东西，不需要下载客户端，不需要限制品牌系统，无论是鸿蒙、安卓苹果，Linux 和 Windows 等等都能实现，无差别预览信息。用户也能在这里交流，如果我们开发出来这个，我们这个产品的前景一定十分宽广。\n>\n> 这时候一位 40 岁的老同志说：你说的这个是不是叫网页？\n>\n> 这是半个世纪前就有的。\n\n虽然目前 WebApp（Web 应用）在功能和性能上的体验远不如 Native App（原生应用），但是“**在原生 App 中内嵌一些 H5 页面**”已经是一种趋势。\n\nJavaScript 的发展，正在大放异彩，正如周爱民的《JavaScript 语言精髓与编程实战》中所描述的那样：\n\n> 是 ECMA 赋予了 JavaScript 新的能力和活力。\n>\n> 在 2015 年 6 月，ES6 发布了。这个 ECMAScript 版本几乎集成了当时其他语言梦寐以求的所有明星特性，并优雅地、不留后患地解决了几乎所有的 JavaScript 遗留问题—当然，其中那些最大的、最本质的和核心的问题其实都已经在 ES5 推出时通过“严格模式（strict mode）”解决了。\n>\n> ES6 提出了四大组件：Promise、类、模块、生成器/迭代器。这事实上是在并行语言、面向对象语言、结构化语言和函数式语言四个方向上的奠基工作。相对于这种重要性来说，其他类似于解构、展开、代理等看起来很炫很实用的特性，反倒是浮在表面的繁华了。\n>\n> 主流引擎厂商开始通过 ES6 释放出它们的能量，于是 JavaScript 在许多新的环境中被应用起来，大量的新技术得以推动，例如，WebAssembly、Ohm、Deeplearn.js、TensorFlow.js、GPU.js、GraphQL、NativeScript 等。有了 Babel 这类项目的强大助力，新规范得以“让少数人先用起来”，而标准的发布也一路披荆斩棘，以至于实现了“一年一更”。\n\n### JS的应用越来越广泛\n\nJS正在越来越多的场景和环境中得到应用，具体包括：\n\n- Web端网页\n- H5页面（即移动端网页）\n- 小程序开发、公众号开发\n- 跨端开发：Taro、ReactNative、Weex 等框架\n- PC客户端/桌面应用开发：Electron 框架。比如 VS Code、Typora等软件就是基于 Electron 框架进行开发的。\n- 后端开发：Node.js\n\n## JavaScript语言的介绍\n\n### JavaScript 入门易学性\n\n- JavaScript 对初学者比较友好。可以使用任何文本编辑工具编写，只需要浏览器就可以执行程序。\n\n- JavaScript 是有界面效果的（相比之下，C 语言只有白底黑字）。\n\n- JavaScript 的入门较简单（进阶不易）。比如，JS 是**弱变量类型**的语言，变量只需要用 var/let/const 来声明。而 Java 中变量的声明，要根据变量的类型来定义。\n\nJava 中需要这样定义变量：\n\n```java\nint a;\nfloat a;\ndouble a;\nString a;\nboolean a;\n```\n\n而 JS 中，只需要用一种方式来定义：\n\n```JavaScript\n// ES5 写法\nvar a;\n\n// ES6 写法\nconst b;\nlet c;\n```\n\n### JavaScript 既是前端语言，又是后端语言\n\n当 JavaScript 运行在用户的终端网页，而不是运行在服务器上的时候，我们称之为“**前端语言**”。前端语言是服务于页面的显示和交互，不能直接操作数据库。\n\n**后端语言**是运行在服务器上的，比如 Java、C++、PHP 等等，这些语言都能够操作数据库（对数据库进行“增删改查”），并在后台执行各种任务。\n\n另外，Node.js 是用 JavaScript 开发的，我们也可以用 Node.js 技术进行服务器端编程。\n\n### JavaScript 的组成\n\nJavaScript 基础分为三个部分：\n\n- **ECMAScript**：JavaScript 的**语法标准**。包括变量、表达式、运算符、函数、if 语句、for 语句等。\n\n- **DOM**：Document Object Model（文档对象模型），JS 操作**页面上的元素**（标签）的 API。比如让盒子移动、变色、改变大小、轮播图等等。\n\n- **BOM**：Browser Object Model（浏览器对象模型），JS 操作**浏览器部分功能**的 API。通过 BOM 可以操作浏览器窗口，比如弹框、控制浏览器跳转、获取浏览器分辨率等等。\n\n通俗理解就是：ECMAScript 是 JS 的语法；DOM 和 BOM 是浏览器运行环境为 JS 提供的 API。\n\n### JavaScript 的特点\n\n1、解释型语言。\n2、遵守ECMAScript 标准。\n3、单线程。\n\n## 参考链接\n\n- [编程语言的一些概念](https://jameszhan.github.io/2014/09/25/programming-languages-concepts.html)\n\n![pl_history](https://img.smyhvae.com/0964c49a88040dc69666487c6cbb6159d0dfd7e0.png)\n\n- [编程语言70年：谁是世界上最好的编程语言？](https://zhuanlan.zhihu.com/p/611924622)\n\n![img](https://img.smyhvae.com/v2-fcdbef5c589522df3960e4cea9825a71_1440w.png)\n\n- [汇编语言(转) | Xian Rong](https://xianrong.github.io/2017/11/14/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%28%E8%BD%AC%29/)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/02-开始写JavaScript：hello world.md",
    "content": "---\ntitle: 02-开始写JavaScript：hello world\n---\n\n## 开始写第一行 JavaScript：hello world\n\nJS 代码的书写位置在哪里呢？这个问题，也可以理解成：引入 JS 代码，有哪几种方式？有三种方式：（和 CSS 的引入方式类似）\n\n1. **行内式**：写在HTML标签内部。\n\n2. **内嵌式**（内联式）：写在 script 标签中。\n\n3. **外链式**：引入外部 JS 文件。\n\n### 方式 1：行内式\n\n代码举例：\n\n```javascript\n<input type=\"button\" value=\"点我点我\" onclick=\"alert('千古壹号 Hello 方式1')\" />\n```\n\n完整的可执行代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <input type=\"button\" value=\"点我点我\" onclick=\"alert('千古壹号 Hello 方式1')\" />\n  </body>\n</html>\n```\n\n解释：\n\n- 可以将单行或少量 JS 代码写在 HTML 标签的事件属性中（以 on 开头的属性），比如放在上面的 `onclick`点击事件中。\n\n- 这种书写方式，不推荐使用，原因是：可读性差，尤其是需要编写大量 JS 代码时，很难维护；引号多层嵌套时，也容易出错。\n\n- 关于代码中的「引号」，在 HTML 标签中，我们推荐使用双引号，JS 中推荐使用单引号。\n\n### 方式 2、内嵌式（内联式）\n\n我们可以在 HTML 页面的 `<body>` 标签里放入`<script type=”text/javascript”></script>`标签对，并在`<script>`里书写 JavaScript 代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <script type=\"text/javascript\">\n      // 在这里写 js 代码\n      alert('千古壹号 hello 方式2');\n      console.log('qianguyihao hello 方式2');\n    </script>\n  </body>\n</html>\n```\n\n解释：\n\n- tyue属性中的text 表示纯文本，因为 JavaScript 代码本身就是纯文本。当然，type属性可以省略，因为 JavaScript 是所有现代浏览器以及 HTML5 中的默认脚本语言。\n\n- 可以将多行 JS 代码写到 `<script>` 标签中。\n\n- 内嵌式 JS 是学习时常用的书写方式。\n\n### 方式 3、外链式（引入独立的JS文件）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 外链式：引入外部的 js 文件：这个 utils.js 文件与当前的 html 文件，处于同一级目录 -->\n    <script src=\"utils.js\"></script>\n  </body>\n</html>\n```\n\n解释：\n\n- 上面这段代码，依然是放到 body 标签里，可以和内嵌的 JS 代码并列。\n- 上方代码的 script 标签已经引入了外部 JS 文件，所以这个标签里面，不可以再写 JS 代码。\n- 方式2和方式3不能混用。\n\n**总结**：\n\n我们在实战开发中，基本都是采用方式 3，因为将 html 文件和 js 文件分开的方式，有利于代码的结构化和复用，符合高内聚、低耦合的思想。很少会有人把一大堆 JS 代码塞到 单个 html 文件里。\n\n## 拓展知识\n\n### window.onload：先加载，最后执行\n\n上面的三种方式，有个共同的地方是：JS 代码都是写在 body 标签中的，准确来说，是在页面标签元素（比如 `title` 标签）的后面，在 `</body>`结束标签的前面。\n\n为什么一般是按这样的顺序来写呢？这是因为：浏览器默认遵循**HTML文档**的加载顺序，即按照**从上至下**的加载顺序解析网页（这句话很重要）。所以，通常情况下，我们会把JS代码写在 body 内部的末尾。\n\n然而，当你需要通过 JS 来操作界面上的标签元素时，假如将 JS 代码或者`<script>`标签写到`<head>`标签中，或者写在HTML标签元素的前面，那么这样的 JS 是无效的，因为标签元素在此时都还没来得及加载，自然无法操作这个元素。\n\n**重点来了：**当你**需要通过 JS 来操作界面上的标签元素**时，如果实在想把 JS 写到`<head>`标签中，那么就必须用 window.onload 将 JS 代码进行包裹。代码格式如下：\n\n```html\n<head>\n  window.onload = function(){\n    // 这里可以写操作界面元素的JS代码，等页面加载完毕后再执行。这里面的JS代码，会延迟执行。\n    ...\n  }\n</head>\n```\n\n**window.onload** 的含义是：等界面上所有内容都加载完毕后（不仅要等文本加载完毕，而且要等图片也要加载完毕），再执行window.onload 内部的代码。做到了**先加载，最后执行**，也就是：**延迟执行**，等页面加载完毕后再执行。\n\n我们可以根据具体需要来决定，将 window.onload 写在 `<head>`标签中，或者写在`<script>`标签中。\n\n### `<noscript>` 标签：浏览器不支持JS脚本时的降级方案\n\n`<noscript>` 标签是一种HTML标签，用于提供在浏览器禁用JavaScript或无法执行JavaScript脚本时的替代内容。它允开发者在需要时向用户显示备用内容。这有助于提高网站的可访问性和用户体验。\n\n如果有些浏览器不支持 JS 脚本，或者用户禁用了浏览器的JS脚本（如下图），那么，该如何给用户提供一个友好的提示呢？我们可以通过 `<noscript>` 标签作为**降级**的处理方案。\n\n![](https://img.smyhvae.com/202310051617910.png)\n\n`<noscript>` 标签的基础语法：\n\n```html\n<noscript>\n  <!-- 降级方案：这里放置备用内容，当浏览器禁用 JS 或者浏览器不支持 JS 时将显示 -->\n  <p>请启用JavaScript以获得最佳网站体验。</p>\n</noscript>\n```\n\n说明：\n\n- `<noscript>` 标签内部可以包含任何HTML内容，通常包括用于向用户传达信息或提供备用功能的文本、图像、链接等。\n- `<noscript>` 标签的内容仅在浏览器不支持或禁用 JS 的情况下才会显示。换而言之，如果用户的浏览器支持JS脚本，则`<noscript>` 标签里的内容将无法显示，会被忽略；如果用户的浏览器不支持JS脚本（或者关闭了JS脚本），则会显示`<noscript>` 标签的内容。\n\n## JavaScript 一些简单的语法规则\n\n学习编程语言，是有规律可循的，程序会有相同的部分，这些部分就是一种规定，不能更改，我们称之为：语法。我们先来了解一个简单的语法规则。\n\n1、JS 对换行、缩进、空格不敏感。每一条语句以分号结尾。\n\n也就是说：\n\n代码一：\n\n```html\n<script type=\"text/javascript\">\n    alert('今天蓝天白云');\n    alert('我很高兴');\n</script>\n```\n\n等价于代码二：\n\n```html\n<script type=\"text/javascript\">\n    alert('今天蓝天白云');alert('我很高兴');\n</script>\n```\n\n2、每一条语句末尾建议加上**分号**。\n\n当存在换行符（line break）时，大多数情况下可以省略分号，JavaScript 会将换行符理解成“隐式”的分号，进而自动添加分号。\n\n也就是说，分号不是必须加的，如果不写分号，浏览器会在换行符的位置自动添加分号，但是会消耗一些系统资源和性能，甚至有可能**添加错误**，导致一些奇怪的bug。\n\n3、所有的符号都是英文的，比如括号、引号、分号。\n\n如果你用的是搜狗拼音，**建议不要用 shift 切换中英文**（可以在搜狗软件里进行设置），不然很容易输入中文的分号；建议用 ctrl+space 切换中英文输入法。\n\n4、JS 严格区分大小写。HTML和CSS不区分大小写，但是 JS 严格区分大小写。\n\n## 前端代码的注释\n\n注释：即解释、注解。注释有利于提高代码的可读性，且有利于程序员之间的沟通和工作交接。\n\n注释可以用来解释某一段代码的功能和作用；通过注释，还可以补充代码中未体现出来的部分。代码只是开发工作的结果，注释可以阐述开发工作的过程、思路、注意事项，以及踩过的坑。\n\n注释可以是任何文字，可以写中文。\n\n**我们不要把 HTML、CSS、JavaScript 三者的注释格式搞混淆了**。\n\n### HTML 的注释\n\n格式：\n\n```html\n<!-- 我是 HTML 注释  -->\n```\n\n举例：\n\n```html\n<!--头部开始-->\n<div class=\"header\"></div>\n<!--头部结束-->\n\n<!--内容开始-->\n<div class=\"main\"></div>\n<!--内容结束-->\n\n<!--底部开始-->\n<div class=\"footer\"></div>\n<!--底部结束-->\n```\n\n### CSS 的注释\n\n举例：\n\n```html\n<style type=\"text/css\">\n  /* 我是 CSS 注释 */\n  p {\n    font-weight: bold;\n    font-style: italic;\n    color: red;\n  }\n</style>\n```\n\n注意：CSS 只有`/* */`这种注释，没有`//`这种注释。而且注释要写在`<style>`标签里面才算生效。\n\n### JavaScript 的注释\n\n单行注释：\n\n```js\n// 我是注释\n```\n\n多行注释：（写法1）\n\n```js\n/*\n 多行注释1\n 多行注释2\n*/\n```\n\n多行注释：（写法2）\n\n```js\n/**\n * 多行注释1\n * 多行注释2\n */\n```\n\n补充：VS Code 中，单行注释的快捷键是「Ctrl + /」，多行注释的默认快捷键是「Alt + Shift + A」。\n\n当然，如果你觉得多行注释的默认快捷键不方便，我们还可以修改默认快捷键。操作如下：\n\nVS Code --> 首选项 --> 键盘快捷方式 --> 查找“注释”这两个字 --> 将原来的快捷键修改为其他的快捷键，比如「Ctrl + Shift + /」。\n\n### 注释的高级用法：文档注释（JSDoc）\n\n**JSDoc** 是 JavaScript 中用于**文档注释**的一种标准化格式。写完 JSDoc 的注释之后，再通过相关工具，开发者便可以为函数、方法、类和变量等添加注释，以便**生成可读性强的API文档**。\n\n以下是一个基本的 JSDoc 注释示例：\n\n```js\n/**\n * 这是一个示例函数，用于求两个数字的和。\n * @param {number} num1 - 第一个数字\n * @param {number} num2 - 第二个数字\n * @returns {number} - 两个数字的和\n */\nfunction sum(num1, num2) {\n  return num1 + num2;\n}\n```\n\n![](https://img.smyhvae.com/202310052143987.png)\n\n如上图所示，鼠标悬停在函数调用的地方就能看到关于这个函数的文档说明，而不需要定位函数的源码处，很神奇吧？\n\nJSDoc经常应用于工具函数，我们甚至可以借助 JSDoc的相关工具，生成函数的一系列的API文档，部署到到网站上，方便其他开发者查阅，非常方便。这种协作方式在日常开发中极为常见。具体如何实战，各位读者可自行查阅 JSDoc 这个关键词。\n\n另外需要说明的是，VS Code默认集成了文档注释的显示功能，可以在**单独的 JS 文件**中显示文档注释的效果，但是无法在 HTML 文件中显示效果：\n\n![](https://img.smyhvae.com/202310052153628.png)\n\n### 对注释的认知\n\n很多人认为：代码注释是多余的。他们的理由是：如果注释太多，说明代码写得不清晰；而且，代码更新的同时，如果注释没更新，那段注释就成了磁盘上的垃圾，误导他人。\n\n还有人认为：注释应该尽可能详细，就像写小作文一样。\n\n上面这样的理由，都不具有说服力。我告诉你为什么要写注释：\n\n1. 注释有利于提高代码的可读性，且有利于程序员之间的沟通和工作交接。\n2. 所有注释都是必要的吗？当然不是。注释不应该用来解释某些代码正在做什么。如果代码无法清楚到去解释自己时，那么代码需要变得更简单。有一些例外，比如正则表达式和复杂算法通常会从解释他们正在做什么事情的注释中获益很多。\n3. 注释用于解释为什么某些代码存在时很有用。大多数注释都是针对代码本身无法包含的信息，例如决策背后的推理、业务流程、业务逻辑、注意事项、踩坑经验总结、参考链接。\n4. 注释的本质是文档，需要持续更新维护，新陈代谢。文档也会过期，但不能因噎废食。即便文档过期，至少它记载了曾经的开发记录。\n\n关注注释，还有一个悖论：“我最喜欢的事情是别人写注释，我最不喜欢的事情是自己写注释”。无论如何，建议各位开发者多写注释、多写技术文档，越详细越好、越系统越好，这样才能提高协作效率，促进良性循环。\n\n推荐阅读：\n\n- 代码审查 review 指南：<https://lib.jimmysong.io/eng-practices/review/>\n\n## JavaScript 输出语句\n\n在下面的这几个输出语句中， 按照使用频率来排序的话，console.log()用得最多，其次是 alert()语句；其他语句用得较少，了解即可。\n\n### 弹窗：alert()语句\n\n我们要学习的第一个语句，就是 alert 语句。\n\n代码举例如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <script>\n      alert('千古壹号');\n    </script>\n  </body>\n</html>\n```\n\n**alert**（中文翻译为“警报、警告”）的用途：**弹出“警告框”**。它会在弹窗中显示一条信息，并等待用户按下 “OK”。\n\n![](http://img.smyhvae.com/20180116_1735.gif)\n\n这个弹窗，在 IE 浏览器中长这样：\n\n![](http://img.smyhvae.com/20180116_1906.png)\n\n上面的代码中，如果写了两个 alert()语句，则网页的效果是：弹出第一个警告框，点击确定后，继续弹出第二个警告框。\n\n补充：该方法只能传入一个参数。\n\n### 控制台输出：console.log() 打印日志\n\n`console.log()`用于在浏览器的控制台中输出信息，里面可以传入一个或多个参数，常用于代码调试和测试。console 表示“控制台”，log 表示“输出”。括号里可以写字符串、数字、变量、表达式等。\n\n语法格式：\n\n```js\nconsole.log('传入一个参数：' +arg1);\n// 传入多个参数时，可以用逗号隔开\nconsole.log('传入多个参数：', arg2, arg3);\n```\n\n控制台是程序员调试程序的地方。我们可以使用 console 语句打印日志，在控制台的 console 面板查看打印的内容，测试程序是否运行正常。\n\n在 Chrome 浏览器中，按 F12 即可打开控制台，选择「console」栏，即可看到打印的内容。\n\n![](http://img.smyhvae.com/20180116_2008.gif)\n\nconsole 语句可以设置不同的打印等级：\n\n```js\nconsole.log('千古壹号1'); // 普通打印\nconsole.warn('千古壹号2'); // 警告打印\nconsole.error('千古壹号3'); // 错误打印\n```\n\n效果如下：\n\n![](https://img.smyhvae.com/20211031_1552.png)\n\n上图中，不同的打印等级，区别不大，只是颜色背景上的区别，方便肉眼区分、过滤信息。\n\n普通人是不会在意控制台的，但是有些网站另藏玄机。比如百度首页的控制台，悄悄地放了一段招聘信息的彩蛋，挺有意思：\n\n![](http://img.smyhvae.com/20180116_2010.png)\n\n做前端开发时需要经常使用控制台做调试，我们甚至可以直接在控制台输入 JS 语句，然后打印执行结果，你可以自行试一试。\n\n**总结**：alert() 主要用来显示消息给用户，console.log() 用来给程序员做调试用。\n\n### 弹窗：confirm()语句（含确认/取消）\n\n代码举例如下：\n\n```\nvar result = confirm('你听说过千古壹号吗？');\nconsole.log(result);\n```\n\n代码运行后，页面上会显示一个弹窗。弹窗上有“确认”和“取消”两个按钮，点击“确定”返回 `true`，点击“取消”返回 `false`。\n\n![20211031-1537](http://img.smyhvae.com/20211031-1537.gif)\n\n### 弹出输入框：prompt()语句\n\n`prompt()`就是专门弹出能够让用户输入内容的对话框。用得少，测试的时候偶尔会用。\n\nJS 代码如下：\n\n```javascript\nvar a = prompt('请随便输入点什么东西吧');\nconsole.log(a);\n```\n\n上方代码中，用户输入的内容，将被传递到变量 a 里面，并在控制台打印出来。\n\n![](http://img.smyhvae.com/20180116_2230.gif)\n\n**alert()和 prompt()的区别：**\n\n- alert() 语句中可以输出数字和字符串，如果要输出字符串，则必须用引号括起来；prompt()语句中，用户不管输入什么内容，都是字符串。\n- prompt() 会返回用户输入的内容。我们可以用一个变量，来接收用户输入的内容。\n\n### 网页内容区域输出：document.write()语句\n\n该方法的作用是向网页中写入文本内容，可以是 HTML 代码。传入的参数可以是一个或者多个字符串。\n\n代码举例：\n\n```\ndocument.write('千古前端图文教程');\n```\n\n页面效果：\n\n![20211031_1543](http://img.smyhvae.com/20211031_1543.png)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/03-常量和变量.md",
    "content": "---\r\ntitle: 03-常量和变量\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n## 常量（字面量）：数字和字符串\r\n\r\n**常量**也称之为“字面量”，是固定值，不可改变。看见什么，它就是什么。\r\n\r\n常量有下面这几种：\r\n\r\n-   数字常量（数值常量）\r\n-   字符串常量\r\n-   布尔常量\r\n-   自定义常量\r\n\r\n### 数字常量\r\n\r\n数字常量非常简单，直接写数字就行，不需要任何其他的符号。既可以是整数，也可以是浮点数。\r\n\r\n例如：\r\n\r\n```javascript\r\n// 不需要加引号\r\nalert(996); // 996是整数\r\nalert(3.14); // 3.14是浮点数（即带了小数）\r\n```\r\n\r\n### 字符串常量\r\n\r\n字符串常量就是用单引号或双引号括起来的内容。可以是单词、句子等，一定要加引号。在JS中，只要是单引号或双引号括起来的内容，都是字符串常量。\r\n\r\n例如：\r\n\r\n```javascript\r\nconsole.log('996');\r\n\r\nconsole.log(\"千古壹号，永不止步\");\r\n```\r\n\r\n温馨提示：996 是数字，'996'是字符串。\r\n\r\n### 布尔常量\r\n\r\n布尔常量就是表达**真**或者**假**，在JS中用 true 和 false 来表达。\r\n\r\n布尔字面量举例：\r\n\r\n```javascript\r\nif (true) {\r\n\tconsole.log('如果为真，就走这里面的代码);\r\n}\r\n```\r\n\r\n### 自定义常量：const\r\n\r\n自定义常量是ES6中新增的语法。它的语法格式是这样的：\r\n\r\n```js\r\nconst 常量名称 = 常量取值;\r\n```\r\n\r\n举例：\r\n\r\n```js\r\nconst name = '千古壹号'; // 定义常量name，它的取值是 千古壹号\r\n\r\n// name =  '许嵩'; // 既然 name 是常量，所以这一行是错误的写法，因为 name 无法被修改为其他的值\r\n\r\nconsole.log(name); // 打印结果：千古壹号\r\n```\r\n\r\n后续我们讲ES6语法的时候，会深入学习 const 用法。\r\n\r\n### 开发技巧：用变量定义常量\r\n\r\n我们一般不会直接使用常量，否则会导致代码冗余、不易维护。如果多个地方要用到同一个常量，那就建议事先定义一个变量，用来保存这个常量；然后在需要的地方去引用这个变量就行了。另外，当我们学习了ES6中的 const 之后，还可以使用自定义常量达到目的。\r\n\r\n## 变量的概念\r\n\r\n**变量**表示可以改变的数据，一个变量，就是一个用于存放这个数据的容器。我们通过「变量名」获取数据，甚至修改数据。\r\n\r\n变量还可以用来保存常量。\r\n\r\n**本质**：变量是程序在内存中申请的一块用来存放数据的空间。比如，超市货架的储物格就是变量, 在不同的时间段里，储物格中存储的数据可以不一样。\r\n\r\n实际开发中，变量使用得非常频繁，因为这些数据并非固定不变。比如，以下使用场景中的信息都可以用变量存储：\r\n\r\n- 商品信息：价格、库存数量、购买的客单价\r\n- 歌曲信息：时长、播放进度、歌词内容\r\n- 用户信息：用户名、年龄、性别、地址\r\n- 时间和日期\r\n- App系统设置的配置参数；用户的偏好设置，比如主题、语言等。\r\n- 微博：用户关注的人、粉丝数量；发布的帖子数量、点赞数。\r\n\r\n\r\n## 变量的定义和赋值\r\n\r\n定义变量是在告诉浏览器，我们需要一块内存空间，相当于生成超市里的储物格。\r\n\r\n给变量赋值就是往相当于往储物格里塞东西，可能今天上午塞的是面包🍞，下午就换成了蛋糕🍰。\r\n\r\n### 变量的定义/声明（ES5）\r\n\r\n在 ES6 语法之前，统一使用`var`关键字来声明一个变量。比如：\r\n\r\n```javascript\r\nvar name; // 定义一个名为 name 的变量。name是变量名。\r\n```\r\n\r\nvar 是英语“variable”变量的缩写。var的后面要加一个空格，空格后面的紧跟的就是“变量名”。\r\n\r\nPS：**在 JavaScript 中，永远都是用 var 来定义变量**（在 ES6 之前），这和 C、Java 等语言不同。\r\n\r\n### 变量的定义/声明（ES6）\r\n\r\n在 ES6 语法及之后的版本里，可以使用 `const`、`let`关键字来定义一个变量。比如：\r\n\r\n```js\r\nconst name; // 定义一个常量\r\n\r\nlet age; // 定义一个变量\r\n```\r\n\r\n如果你想定义一个常量，就用 const；如果你想定义一个变量，就用 let。\r\n\r\n### 变量的赋值\r\n\r\n使用 `=` 这个符号即可给变量赋值。举例：\r\n\r\n```javascript\r\nname = '千古壹号';\r\n```\r\n\r\n综合起来，变量的定义、赋值、取值，举例如下：\r\n\r\n```js\r\n// 定义：声明一个变量\r\nvar num;\r\n// 赋值：往变量中存储数据\r\nnum = 996;\r\n// 取值：从变量中获取存储的数据\r\nconsole.log(num);\r\n```\r\n\r\n### 定义+赋值的合并写法\r\n\r\n变量的定义和赋值，还可以合并写在一起，是实战中常用的写法。举例如下：\r\n\r\n```javascript\r\nvar a = 100; // ES5语法\r\nconsole.log(a);\r\n\r\nconst b = 'hello'; // ES6 语法\r\n\r\nlet c = 'world'; // ES6 语法\r\nc = 'qianguyihao'; // 修改 变量 C 的值\r\n```\r\n\r\n定义一个变量并赋值， 我们称之为**变量的初始化**。如下图所示：\r\n\r\n![](http://img.smyhvae.com/20180116_2020.png)\r\n\r\n\r\n\r\n- 定义变量：var 就是一个**关键字**，用来定义变量。所谓关键字，就是有特殊功能的单词。\r\n\r\n- 变量名：我们可以给变量起个名字。\r\n\r\n- 变量赋值：等号表示**赋值**，将等号右边的值，赋给左边的变量。\r\n\r\n\r\n\r\n### 变量的初始化【重要】\r\n\r\n第一次给变量赋值，称之为“**变量的初始化**”，这个概念非常重要。第二次给这个变量赋值（也就是修改这个变量的值）就不叫初始化了。\r\n\r\n一个变量如果没有进行初始化（只声明，不赋值），那么这个变量中存储的值是`undefined`，这个知识点必须知道。\r\n\r\n变量的初始化，可以有两种形式，如下。\r\n\r\n形式1：先定义，在赋值。举例：\r\n\r\n```js\r\nvar name;\r\nname = 'qianguyhihao';\r\n```\r\n\r\n形式2：在定义的同时进行初始化。也就是上一小段讲的“合并写法”。举例：\r\n\r\n```js\r\nvar name = 'qianguyihao';\r\n```\r\n\r\n## 变量定义和赋值的补充\r\n\r\n### 修改变量的值\r\n\r\n一个变量被重新复赋值后，它原有的值就会被覆盖，变量值将以最后一次赋的值为准。\r\n\r\n举例：\r\n\r\n```javascript\r\nvar a = 100;\r\na = 110;\r\n\r\nconsole.log(a); // 打印结果：110。因为 110 覆盖了 100\r\n```\r\n\r\n### 同时定义多个变量\r\n\r\n1、同时定义多个变量时，只需要写一个 var， 多个变量名之间用英文逗号隔开。举例：\r\n\r\n```javascript\r\n// 同时定义多个变量\r\nvar num1, num2;\r\n```\r\n\r\n2、定义多个变量的同时，分别进行初始化。举例：\r\n\r\n```js\r\n// 定义多个变量的同时，进行初始化\r\nvar num1 = 10, num2 = 20;\r\n```\r\n\r\n如果多个变量的初始化值都是一样的，则可以这样简写：\r\n\r\n```js\r\nvar num1, num2;\r\nnum1 = num2 = 10; // 重点在这一行\r\n\r\nconsole.log(num1); // 10\r\nconsole.log(num2); // 10\r\n```\r\n\r\n上面的写法和下面的写法是有区别的：（注意看打印结果）\r\n\r\n```js\r\nvar num1, num2 = 10;\r\n\r\nconsole.log(num1); // undefined\r\nconsole.log(num2); // 10\r\n```\r\n\r\n### 变量之间可以相互赋值\r\n\r\n```js\r\nvar num1, num2;\r\nnum1 = 10;\r\nnum2 = num1; // 把 num1 的值拷贝一份，赋值给 num2\r\nconsole.log(num2); // 10\r\n```\r\n\r\n### 变量如果重复定义\r\n\r\n在ES5中，如果用 var 定义了同名变量，那么，后定义的变量，会覆盖先定义的变量。举例：\r\n\r\n```js\r\nvar name = '许嵩';\r\nvar name = '千古壹号'; // 这里会重新定义一个新的变量 name\r\n\r\nconsole.log(name); // 千古壹号\r\n```\r\n\r\n### 变量声明和赋值的几种情况\r\n\r\n变量建议先声明，再使用；否则可能会产生意想不到的结果。具体如下。\r\n\r\n**写法 1**、先声明，再赋值：（正常）\r\n\r\n```javascript\r\nvar a;\r\na = 100;\r\nconsole.log(a); // 打印结果：100\r\n```\r\n\r\n**写法 2**、只声明，不赋值：（默认值为 undefined）\r\n\r\n```javascript\r\nvar a;\r\nconsole.log(a); // 打印结果：undefined\r\n```\r\n\r\n**写法 3**、不声明，直接赋值：（正常）\r\n\r\n```javascript\r\na = 100;\r\nconsole.log(a); // 打印结果：100\r\n```\r\n\r\n写法3虽然不报错，但并不推荐这么写。变量 a 会被添加到 windows 对象上。它跟写法1是有区别的，等以后学习了「变量提升」的概念就明白了。相比之下，我们更推荐用写法 1。\r\n\r\n**写法 4**、不声明，不赋值，直接使用：（会报错）\r\n\r\n```javascript\r\nconsole.log(a); // 会报错\r\n```\r\n\r\n控制台会报错：\r\n\r\n![](http://img.smyhvae.com/20180116_2040.png)\r\n\r\n**补充**：写法 1 和写法 2 虽然都正常，但这两种写法是有区别的，举例\r\n\r\n**举例**：交换两个变量的值\r\n\r\n代码实现：\r\n\r\n```javascript\r\nvar a1 = 100;\r\nvar a2 = 200;\r\n\r\nvar temp;\r\n\r\ntemp = a1;\r\na1 = a2;\r\na2 = temp;\r\n```\r\n\r\n## 最后\r\n\r\n关于变量的命名规则，将在下一篇文章里讲。\r\n\r\n## 赞赏作者\r\n\r\n创作不易，你的赞赏和认可，是我更新的最大动力：\r\n\r\n![](https://img.smyhvae.com/20220401_1800.jpg)\r\n\r\n"
  },
  {
    "path": "04-JavaScript基础/04-标识符、关键字、保留字.md",
    "content": "---\ntitle: 04-标识符、关键字、保留字\n\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 变量的命名规则（重要）\n\nJS是大小敏感的语言。也就是说 A 和 a 是两个变量，并非同一个变量，比如：\n\n```javascript\nvar A = 250; //变量1\nvar a = 888; //变量2\n```\n\n我们来整理一下**变量的命名规则**，非常重要。\n\n必须遵守：\n\n-   只能由字母(A-Z、a-z)、数字(0-9)、下划线(\\_)、美元符( $ )组成。\n-   **不能以数字开头**。必须以字母(A-Z、a-z)、下划线(\\_)或者美元符( $ )开头。变量名中不允许出现空格。尤其注意，变量名中不能出现**中划线**`-`，很多人写了多年代码都不知道这一点，让人大跌眼镜。\n-   严格区分大小写（JS 是区分大小写的语言）。\n-   不能使用 JS 语言中保留的「关键字」和「保留字」作为变量名。下一段会讲。\n-   变量名长度不能超过 255 个字符。\n-   汉语可以作为变量名。但是不建议使用，因为 low。\n\n建议遵守：\n\n- 命名要有可读性，方便顾名思义。\n- 建议用驼峰命名法。比如 getElementById、getUserInfo、aaaOrBbbAndCcc\n- 赋值 `=` 符号的两边建议都加上空格。\n\n**补充**：\n\n1、不能以数字开头，是为了把数字和字母区分开。\n\n2、JS底层保存标识符的时候，是采用的 Unicode 编码。所以理论上讲，在遵守命名规则的前提下，utf-8中包含的所有内容都可以作为标识符。\n\n## 标识符\n\n**标识符**：在 JS 中所有的可以由我们**自主命名**的都可以称之为标识符。包括：**变量名、函数名、属性名、参数名**都是属于标识符。\n\n通俗来讲，标识符就是我们写代码时为某些东西起的名字。类似于人出生的时候，起个人名。\n\n**标识符的命名规则**和变量的命令规则是一样的。关于变量的命名规则，详见上一段。\n\n标识符不能使用语言中保留的**关键字**及**保留字**。\n\n## 关键字\n\n**关键字**：被JS赋予了特殊含义的单词。也就是说，关键字是 JS 本身已经使用了的单词，我们不能再用它们充当变量名、函数名等标识符。关键字在开发工具中会显示特殊的高亮颜色。\n\nJS 中的关键字如下：\n\n```bash\nif、else、switch、break、case、default、for、in、do、while、\n\nvar、let、const、void、function、continue、return、\n\ntry、catch、finally、throw、debugger、\n\nthis、typeof、instanceof、delete、with、\n\nexport、new、class、extends、super、with、yield、import、static、\n\ntrue、false、null、undefined、NaN\n```\n## 保留字\n\n**保留字**：实际上就是预留的“关键字”。它们虽然现在还不是关键字，但是未来可能会成为关键字。同样不能用它们当充当变量名、函数名等标识符。\n\nJS 中的保留字如下：\n\n```bash\nenum、await\n\nabstract、boolean、byte、char、double、final、float、goto、int、long、native、short、synchronized、transient、volatile、\n\narguments eval Infinity\n\n# 以下关键字只在严格模式中被当成保留字，在ES6中是属于关键字\nimplements、interface、package、private、protected、public\n```\n\n当你在网上搜“JS保留字”的时候，你会找到很多版本，每个版本都不一样，各有各的说法。**如果有不一样的地方，请以我写的为准**。\n\n其实，以谁的版本作为标准并不重要，因为有些单词到底是**关键字**还是**保留字**，并没有严格的界限。JS 关于保留字的规则非常复杂，上方列表中的一些单词在特殊情况下其实是可以使用的。\n\n我们只需要记住一点：上面提到的所有**关键字**和**保留字**，我们都不要用它们作为变量名或者参数名。不要尝试这些奇怪的做法。\n\n如果你还想了解更多，可以看这几个参考链接：\n\n- [知乎问答：undefined是保留字吗？](https://www.zhihu.com/question/472379938)\n\n- 书籍《[JavaScript 悟道](https://book.douban.com/subject/35469273/)》的第一章：保留字部分。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/05-变量的数据类型：基本数据类型和引用数据类型.md",
    "content": "---\ntitle: 05-变量的数据类型：基本数据类型和引用数据类型\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 数据分类\n\n数据分为：静态数据、动态数据。\n\n### 静态数据\n\n静态数据是指一些永久性的数据。一般是以文件的形式存储在硬盘上，比如文档、照片、视频等文件。\n\n电脑关闭后，静态数据仍然还在。只要不主动删掉数据或者硬盘没损坏，这些数据就一直都在。\n\n### 动态数据\n\n动态数据是在程序运行过程中，动态产生的临时数据，这些数据可能随时发生变化。一般存储在内存中。\n\n电脑关闭后，这些数据会被清除。\n\n为何不把应用程序的动态数据加载到硬盘中执行呢？这主要是因为，内存的访问速度比硬盘快无数倍。\n\n## 变量的数据类型\n\n### 为什么需要数据类型\n\n在计算机中，不同的数据所需占用的存储空间不同，为了充分利用存储空间，于是定义了不同的数据类型。而且，不同的数据类型，寓意也不同。\n\n我们都知道，无论这个变量是字符串类型，还是数字类型，我们都可以直接用 `var` 去定义它。比如：\n\n```javascript\nvar a = 'hello word';\n\nvar b = 123;\n```\n\n为什么可以这样做呢？这是因为：JavaScript 是一种「弱类型语言」，或者说是一种「动态语言」，这意味着不需要事先声明变量的具体数据类型，在程序运行过程中，类型会自动被确定。\n\n**JS 的变量数据类型，是在程序运行的过程中，根据等号右边的值来确定的**。而且，变量的数据类型是可以变化的。一个变量可以在上一秒是字符串类型，下一秒是数字类型。比如：\n\n```javascript\nvar name = 'qianguyihao';\n\nname = 123; // 强制将变量 name 修改为 数字类型\n```\n\n### JS 中一共有八种数据类型\n\n-   **基本数据类型（值类型）**：String 字符串、Boolean 布尔值、Number 数值、Undefined 未定义、Null 空对象、BigInt 大型数值、Symbol。\n\n-   **引用数据类型（引用类型）**：Object 对象。\n\n注意：内置对象 Function、Array、Date、RegExp、Error 等都是属于 Object 类型。也就是说，除了那七种基本数据类型之外，其他的，都称之为 Object 类型。\n\nBigInt 和 Symbol 是ES6中新增的类型，我们留到以后再讲。\n\n> 面试问：引用数据类型有几种？\n\n> 面试答：只有一种，即 Object 类型。\n\n**数据类型之间最大的区别**：\n\n-   基本数据类型：参数赋值的时候，传数值。\n\n-   引用数据类型：参数赋值的时候，传地址。\n\n## 一个经典的例子\n\n**基本数据类型举例**：\n\n```javascript\nvar a = 23;\nvar b = a;\n\na++;\n\nconsole.log(a); // 打印结果：24\nconsole.log(b); // 打印结果：23\n```\n\n上面的代码中：a 和 b 都是基本数据类型，让 b 等于 a，然后**改变 a 的值之后，发现 b 的值并没有被改变**。\n\n但是在引用数据类型中，就不同了，我们来看一看。\n\n**引用数据类型举例**：\n\n```javascript\nvar obj1 = new Object();\nobj1.name = 'smyh';\n\n// 让 obj2 等于 obj1\nvar obj2 = obj1;\n\n// 修改 obj1 的 name 属性\nobj1.name = 'vae';\n\nconsole.log(obj1.name); // 打印结果：vae\nconsole.log(obj2.name); // 打印结果：vae\n```\n\n上面的代码中：obj1 和 obj2 都是引用数据类型，让 obj2 等于 obj1，然后**修改 obj1.name 的值之后，发现 obj2.name 的值也发生了改变**。\n\n从上面的例子中，可以反映出，基本数据类型和引用数据类型是有区别的。\n\n那到底有什么区别呢？我们进一步往下看。\n\n## 栈内存和堆内存\n\n我们首先记住一句话：JS 中，所有的**变量**都是保存在**栈内存**中的。\n\n然后来看看下面的区别。\n\n**基本数据类型**：\n\n基本数据类型的值，直接保存在栈内存中。值与值之间是独立存在，修改一个变量不会影响其他的变量。\n\n**引用数据类型**：\n\n对象是保存到**堆内存**中的。每创建一个新的对象，就会在堆内存中开辟出一个新的空间；而**变量保存了对象的内存地址**（对象的引用），保存在栈内存当中。如果两个变量保存了同一个对象的引用，当一个通过一个变量修改属性时，另一个也会受到影响。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/06-基本数据类型：String 和 Boolean.md",
    "content": "---\ntitle: 06-基本数据类型：String 和 Boolean\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## String 字符串\n\n\n\n### 语法\n\n如果在开发中需要使用文本，就可以用 String 字符串类来表示，比如姓名、商品简介等。\n\n其语法为：双引号 `\"\"` 或者单引号 `''`。引号中的内容都是文本。\n\n来看个示例。下面的这些，都是字符串：\n\n```javascript\nvar a = 'abcde';\nvar b = '千古壹号';\nvar c = '123123';\nvar d = '哈哈哈哈哈';\nvar e = ''; //空字符串\n\nvar f = haha; // 没使用引号，到这里会直接报错。因为会被认为是js代码，但是之前并没有定义 haha。\n\nconsole.log(typeof a);\nconsole.log(typeof b);\nconsole.log(typeof c);\nconsole.log(typeof d);\nconsole.log(typeof e);\n```\n\n控制台输出如下：\n\n```\nstring\nstring\nstring\nstring\nstring\n```\n\n### 引号的注意事项\n\n1、单引号和双引号不能混用。比如下面这样写是不可以的：\n\n```javascript\nvar str = 'hello\";  // 报错：Uncaught SyntaxError: Invalid or unexpected token\n```\n\n2、同类引号不能嵌套：双引号里不能再放双引号，单引号里不能再放单引号。\n\n3、单引号里可以嵌套双引号；双引号里可以嵌套单引号。\n\n### 转义字符\n\n假设现在有这样一个字符串——`千古'壹号\"前端开发\"`。\n\n上面的字符串中，前端开发 这四个字是被双引号包围的， 千古 和 壹号 之间有一个单引号。难点就在于这个单引号。\n\n如果你想在JS中定义上述字符串的话，直接在代码中这样写是不行的，会报错：\n\n```js\nvar str = '千古'壹号\"前端开发\"'; // 会报错\n```\n\n那要怎么办呢？这个时候，转移字符就派上用场了。\n\n在字符串中我们可以使用`\\`作为转义字符。如果你想表示一些特殊符号，可以使用`\\`进行转义。\n\n-   `\\'` 表示 `'` 单引号\n-   `\\\"` 表示 `\"` 双引号\n-   `\\\\` 表示`\\`\n-   `\\r` 表示回车\n-   `\\n` 表示换行。n 的意思是 newline。\n-   `\\t` 表示缩进。t 的意思是 tab，制表符。\n-   `\\b` 表示空格。b 的意思是 blank。\n\n举例：\n\n```javascript\nvar str1 = '我说:\"今天\\t天气真不错！\"';\nvar str2 = '\\\\\\\\\\\\';\nvar str3 = '千古\\'壹号\"前端开发\"';\n\nconsole.log(str1);\nconsole.log(str2);\nconsole.log(str3);\n```\n\n上方代码的打印结果：\n\n```\n我说:\"今天\t天气真不错！\"\n\\\\\\\n千古'壹号\"前端开发\"\t\n```\n\n### 获取字符串的长度\n\n字符串是由若干个字符组成的，这些字符的数量就是字符串的长度。我们可以通过字符串的 `length` 属性可以获取整个字符串的长度。\n\n代码举例：\n\n```javascript\nvar str1 = '千古壹号';\nvar str2 = '千古壹号，永不止步！';\n\nvar str3 = 'qianguyihao';\nvar str4 = 'qianguyihao, keep moving!';\n\nconsole.log(str1.length); // 4\nconsole.log(str2.length); // 10\nconsole.log(str3.length); // 11\nconsole.log(str4.length); // 25\n```\n\n由此可见，字符串的 length 属性，在判断字符串的长度时，会认为：\n\n-   一个中文算一个字符，一个英文算一个字符\n\n-   一个标点符号（包括中文标点、英文标点）算一个字符\n\n-   一个空格算一个字符\n\n### 字符串拼接\n\n多个字符串之间可以使用加号 `+` 进行拼接。\n\n**拼接语法**：\n\n```\n字符串 + 任意数据类型 = 拼接之后的新字符串;\n```\n\n**拼接规则**：拼接前，会把与字符串相加的这个数据类型转成字符串，然后再拼接成一个新的字符串。\n\n**代码举例**：（字符串与六大数据类型相加）\n\n```javascript\nvar str1 = '千古壹号' + '永不止步';\nvar str2 = '千古壹号' + 666;\nvar str3 = '千古壹号' + true;\nvar str4 = '千古壹号' + null;\nvar str5 = '千古壹号' + undefined;\n\nvar obj = { name: '千古壹号', age: 28 };\nvar str6 = '千古壹号' + obj;\n\nconsole.log(str1);\nconsole.log(str2);\nconsole.log(str3);\nconsole.log(str4);\nconsole.log(str5);\nconsole.log(str6);\n```\n\n打印结果：\n\n```\n千古壹号永不止步\n\n千古壹号666\n\n千古壹号true\n\n千古壹号null\n\n千古壹号undefined\n\n千古壹号[object Object]\n```\n\n## 字符串的不可变性\n\n字符串里面的值不可被改变。虽然看上去可以改变内容，但其实是地址变了，内存中新开辟了一个内存空间。\n\n代码举例：\n\n```js\nvar str = 'hello';\n\nstr = 'qianguyihao';\n```\n\n比如上面的代码，当重新给变量 str 赋值时，常量`hello`不会被修改，依然保存在内存中；str 会改为指向`qianguyihao`。\n\n## 模板字符串（模板字面量）\n\nES6 中引入了**模板字符串**，让我们省去了字符串拼接的烦恼。下面一起来看看它的特性。\n\n### 在模板字符串中插入变量\n\n以前，让字符串进行拼接的时候，是这样做的：（传统写法的字符串拼接）\n\n```javascript\nvar name = 'smyhvae';\nvar age = '26';\nconsole.log('name:' + name + ',age:' + age); //传统写法\n```\n\n这种写法，比较繁琐，而且容易出错。\n\n现在，有了 ES6 语法，字符串拼接可以这样写：\n\n```javascript\nvar name = 'qianguyihao';\nvar age = '26';\n\nconsole.log('我是' + name + ',age:' + age); //传统写法\nconsole.log(`我是${name},age:${age}`); //ES6 写法。注意语法格式\n```\n\n**注意**，上方代码中，倒数第二行用的符号是单引号，最后一行用的符号是反引号（在 tab 键的上方）。\n\n参考链接：\n\n-   [ES6 的 rest 参数和扩展运算符](https://segmentfault.com/a/1190000010222698)\n\n### 在模板字符串中插入表达式\n\n以前，在字符串中插入表达式的写法必须是这样的：\n\n```js\nconst a = 5;\nconst b = 10;\nconsole.log('this is ' + (a + b) + ' and\\nnot ' + (2 * a + b) + '.');\n```\n\n现在，通过模板字符串，我们可以使用一种更优雅的方式来表示：\n\n```js\nconst a = 5;\nconst b = 10;\n\n// 下面这行代码，故意做了换行。\nconsole.log(`this is ${a + b} and\nnot ${2 * a + b}.`);\n```\n\n打印结果：\n\n```bash\nthis is 15 and\nnot 20.\n```\n\n### 模板字符串中可以换行\n\n因为模板字符串支持换行，所以可以让代码写得非常美观。\n\n代码举例：\n\n```js\nconst result = {\n    name: 'qianguyihao',\n    age: 28,\n    sex: '男',\n};\n\n// 模板字符串支持换行\nconst html = `<div>\n\t<span>${result.name}</span>\n\t<span>${result.age}</span>\n\t<span>${result.sex}</span>\n</div>`;\n\nconsole.log(html); // 打印结果也会换行\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20200825_2016.png)\n\n### 模板字符串中可以调用函数\n\n模板字符串中可以调用函数。字符串中调用函数的位置，将会显示函数执行后的返回值。\n\n举例：\n\n```js\nfunction getName() {\n    return 'qianguyihao';\n}\n\nconsole.log(`www.${getName()}.com`); // 打印结果：www.qianguyihao.com\n```\n\n### 模板字符串支持嵌套使用\n\n```js\nconst nameList = ['千古壹号', '许嵩', '解忧少帅'];\n\nfunction myTemplate() {\n    // join('') 的意思是，把数组里的内容合并成一个字符串\n    return `<ul>\n\t${nameList.map((item) => `<li>${item}</li>`).join('')}\n\t</ul>`;\n}\ndocument.body.innerHTML = myTemplate();\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20200607_2118.png)\n\n## 布尔值：Boolean\n\n布尔型有两个值：true 和 false。主要用来做逻辑判断： true 表示真，false 表示假。布尔值直接使用就可以了，千万不要加引号。\n\n布尔（英语：Boolean）是计算机科学中的逻辑数据类型，以发明布尔代数的英国数学家**乔治·布尔**为名。\n\n代码举例：\n\n```javascript\nvar a = true;\nconsole.log(typeof a);\n```\n\n控制台输出结果：\n\n```\nboolean\n```\n\n布尔型和数字型相加时， true 按 1 来算 ，false 按 0 来算。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/07-基本数据类型：Number.md",
    "content": "---\ntitle: 07-基本数据类型：Number\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 数值型：Number\n\n在 JS 中所有的数值都是 Number 类型，包括整数和浮点数（小数）。\n\n```javascript\nvar a = 100; // 定义一个变量 a，并且赋值整数100\nconsole.log(typeof a); // 输出变量 a 的类型\n\nvar b = 12.3; // 定义一个变量 b，并且赋值浮点数 12.3\nconsole.log(typeof a);\n```\n\n上方代码的输出结果为：\n\n```bash\nnumber\nnumber\n```\n\n再次补充：在 JS 中，只要是数，就是 Number 数值型的。无论整浮、浮点数（即小数）、无论大小、无论正负，都是 Number 类型的。包括接下来要讲的Infinity、-Infinity、NaN 等特殊数值也是 Number 类型的。\n\n### 数值范围\n\nECMAScript 并不能表示世界上所有的数值。\n\n-   最大正数值：`Number.MAX_VALUE`，这个值为： 1.7976931348623157e+308\n\n-   最小正数值：`Number.MIN_VALUE`，这个值为： 5e-324 ，或者 2的负1074 次方。它是能够在浮点精度范围内表示的最小正数（**不是最小负数**）。小于这个数的正数会被转成0。\n\n如果使用 Number 表示的变量超过了最大值，则会返回 Infinity。\n\n-   `Infinity`：无穷大（正无穷）。比如 1/0 的值就是无穷大。\n\n-   `-Infinity`：无穷小（负无穷）\n\n注意：`typeof Infinity`的返回结果是 number。\n\n### NaN\n\n**NaN**：是一个特殊的数字，表示 Not a Number，非数值。在进行数值运算时，如果得不到正常结果，就会返回 NaN。\n\n比如：\n\n```javascript\nconsole.log('abc' / 18); //结果是NaN\n\nconsole.log('abc' * 'abcd'); //按理说，字符串相乘是没有结果的，但如果你非要让JS去算，它就一定会给你一个结果，结果是NaN。\n```\n\n注意事项：\n\n1. `typeof NaN`的返回结果是 number。\n\n2. **Undefined 和任何数值计算的结果为 NaN。NaN 与任何值都不相等，包括 NaN 本身。**\n\n3. 关于 isNaN() 函数，可以看后续要将的内容——数据类型转换。\n\n### 连字符和加号的区别\n\n键盘上的`+`可能是连字符，也可能是数字的加号。如下：\n\n```js\nconsole.log(\"我\" + \"爱\" + \"你\");\t// 连字符，把三个独立的汉字，连接在一起了\nconsole.log(\"我+爱+你\");\t\t\t// 原样输出\nconsole.log(1+2+3);\t\t\t\t// 输出6\n```\n\n输出：\n\n```\n我爱你\n我+爱+你\n6\n```\n\n**总结**：如果加号两边**都是** Number 类型，此时是数字相加。否则，就是连字符（用来连接字符串）。\n\n举例 1：\n\n```javascript\nvar a = '1';\nvar b = 2;\nconsole.log(a + b);\n```\n\n控制台输出：\n\n```\n12\n```\n\n举例 2：\n\n```\nvar a = 1;\nvar b = 2;\nconsole.log(\"a\" + b);\t//\"a\"就不是变量了！所以就是\"a\"+2 输出a2\n\n```\n\n控制台输出：\n\n```\na2\n```\n\n于是我们明白了，在变量中加入字符串进行拼接，可以被同化为字符串。【重要】\n\n### 隐式转换\n\n我们知道，`\"2\"+1`得到的结果其实是字符串，但是`\"2\"-1`得到的结果却是数值 1，这是因为计算机自动帮我们进行了“**隐式转换**”。\n\n也就是说，`-`、`*`、`/`、`%`这几个符号会自动进行隐式转换。例如：\n\n```javascript\nvar a = '4' + 3 - 6;\nconsole.log(a);\n```\n\n输出结果：\n\n```javascript\n37;\n```\n\n虽然程序可以对`-`、`*`、`/`、`%``这几个符号自动进行“隐式转换”；但作为程序员，我们最好自己完成转换，方便程序的可读性。\n\n关于隐式转换的详细知识，可以看后续的内容——和数据类型转换。\n\n### 其他进制的数字\n\n数字不仅有10进制，也有其他进制。\n\n-   16 进制的数字，以`0x`开头\n\n-   8 进制的数字，以`0`开头\n\n-   2 进制的数字，`0b`开头（不是所有的浏览器都支持：chrome 和火狐支持，IE 不支持）\n\n## 浮点数的运算\n\n### 运算精度问题\n\n在 JS 中，整数的运算**基本**可以保证精确；但是**小数的运算，可能会得到一个不精确的结果**。所以，千万不要使用 JS 进行对精确度要求比较高的运算。\n\n如下：\n\n```javascript\nvar a = 0.1 + 0.2;\nconsole.log(a); //打印结果十分意外：0.30000000000000004\n```\n\n上方代码中，打印结果并不是 0.3，而是 0.30000000000000004。\n\n这是因为，计算机在做运算时，所有的运算都要转换成二进制去计算。然而，有些数字转换成二进制之后，无法精确表示。比如说，0.1 和 0.2 转换成二进制之后，是无穷的，因此存在浮点数的计算不精确的问题。\n\n### 处理数学运算的精度问题\n\n如果只是一些简单的精度问题，可以使用 `toFix()` 方法进行小数的截取。备注：关于 `toFixed()`方法， 详见《JavaScript 基础/内置对象：Number 和 Math》。\n\n在实战开发中，关于浮点数计算的精度问题，往往比较复杂。市面上有很多针对数学运算的开源库，比如[decimal.js](https://github.com/MikeMcl/decimal.js/)、 [Math.js](https://github.com/josdejong/mathjs)。这些开源库都比较成熟，我们可以直接拿来用。\n\n-   Math.js：属于很全面的运算库，文件很大，压缩后的文件就有 500kb。如果你的项目涉及到大型的复杂运算，可以使用 Math.js。\n\n-   decimal.js：属于轻量的运算库，压缩后的文件只有 32kb。大多数项目的数学运算，使用 decimal.js 足够了。\n\n在使用这几个开源库时，既可以用 cdn 的方式引入，也可以用 npm 包的方式引入。\n\n比如说，通过 cdn 的方式引入 decimal.js 时，可以这样用：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <script src=\"https://cdn.bootcdn.net/ajax/libs/decimal.js/10.2.0/decimal.min.js\"></script>\n    <script>\n      console.log('加法：');\n      var a = 0.1;\n      var b = 0.2;\n      console.log(a + b);\n      console.log(new Decimal(a).add(new Decimal(b)).toNumber());\n\n      console.log('减法：');\n      var a = 1.0;\n      var b = 0.7;\n      console.log(a - b);\n      console.log(new Decimal(a).sub(new Decimal(b)).toNumber());\n\n      console.log('乘法：');\n      var a = 1.01;\n      var b = 1.003;\n      console.log(a * b);\n      console.log(new Decimal(a).mul(new Decimal(b)).toNumber());\n\n      console.log('除法：');\n      var a = 0.029;\n      var b = 10;\n      console.log(a / b);\n      console.log(new Decimal(a).div(new Decimal(b)).toNumber());\n    </script>\n  </body>\n</html>\n```\n\n打印结果：\n\n```\n加法：\n0.30000000000000004\n0.3\n\n减法：\n0.30000000000000004\n0.3\n\n乘法：\n1.0130299999999999\n1.01303\n\n除法：\n0.0029000000000000002\n0.0029\n```\n\n参考链接：\n\n-   <https://www.bloghome.com.cn/post/nodejsxue-xi-bi-ji-shi-qi-fu-dian-yun-suan-decimal-js.html>\n\n-   <https://zhuanlan.zhihu.com/p/62381711>\n\n## 变量值的传递（赋值）\n\n语句：\n\n```\na = b;\n```\n\n把 b 的值赋给 a，b 不变。\n\n将等号右边的值，赋给左边的变量；等号右边的变量，值不变。\n\n来做几个题目，看看数字是如何参与运算的。\n\n举例 1：\n\n```js\n           // a\t\t  b       c\nvar a = 1; // 1\nvar b = 2; // 1     2\nvar c = 3; // 1     2       3\na = b + c; // 5     2       3\nb = c - a; // 5     -2      3\nc = a * b; // 5     -2      -10\nconsole.log(a);\nconsole.log(b);\nconsole.log(c);\n```\n\n输出：\n\n```\n5\n-2\n-10\n```\n\n举例 2：\n\n```js\n           // a    b     c\nvar a = 1;\nvar b = 2;\nvar c = 3; // 1     2     3\na = a + b; // 3     2     3\nb = b + a; // 3     5     3\nc = c + b; // 3     5     8\nconsole.log(a); // 3\nconsole.log(b); // 5\nconsole.log(c); // 8\n```\n\n输出：\n\n```\n3\n5\n8\n```\n\n举例 3：\n\n```js\n            //a       b\nvar a = '1';\nvar b = 2; // \"1\"     2\na = a + b; // \"12\"    2\nb = b + a; // \"12\"    \"212\"\nconsole.log(a); // 输出12\nconsole.log(b); // 输出212\n```\n\n输出：\n\n```\n12\n212\n```\n\n举例 4：\n\n```js\n           //a         b\nvar a = '1';\nvar b = 2;\na = b + a; //\"21\"       2\nb = b + a; //\"21\"       \"221\"\nconsole.log(a); //21\nconsole.log(b); //221\n```\n\n效果：\n\n```\n21\n221\n```\n\n举例 5：（这个例子比较特殊，字符串减去数字）\n\n```js\nvar a = '3';\nvar b = 2;\nconsole.log(a - b);\n```\n\n效果：（注意，字符串 - 数值 = 数值）\n\n```\n1\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/08-基本数据类型：Undefined 和 Null.md",
    "content": "---\ntitle: 08-基本数据类型：Null 和 Undefined\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n有些其他的语言中，只有 null；但 JS 语言中，既有 undefined，又有 null。很多人会弄混，由此觉得 JS 语言很麻烦。其实不然，学习完本文后，你会发现 undefined 和 null 的区别很容易理解。\n\n## Undefined：未定义类型\n\nUndefined 类型的值只有一个，就是 undefind。比如 `var a = undefined`。\n\n使用 typeof 检查一个 undefined 值时，会返回 undefined。\n\nundefined 的出现有以下几种情况。\n\n### case1：变量已声明，未赋值（未初始化）\n\n一个变量如果只**声明**了，但没有**赋值**，此时它的值就是 `undefined`。举例：\n\n```js\nvar name;\nconsole.log(name); // 打印结果：undefined\nconsole.log(typeof name); // 打印结果：undefined\n```\n\n下面这两行代码是等价的：\n\n```js\n// 写法1\nvar name;\n// 写法2。这种写法冗余了，不推荐。\nvar name = undefined;\n```\n\n注意事项：\n\n1、不要显式地将变量赋值为 undefined，不太规范。也就是说，上面的写法 2 是冗余的，增加了不必要的代码量，这种写法不太规范。\n\n2、变量在定义时，尽量做一下初始化（赋值操作），而不是只声明一个变量。上面的写法 1 就是属于只声明一个变量，也不太推荐这种写法。\n\n如果变量刚开始没有值，我们可以将其赋一个默认值（空字符串、false、0、null 等值），这有利于代码书写的语义化。推荐的代码举例：\n\n```js\nvar a = ''; // 字符串类型的变量，如果刚开始没有值，则可以初始化为空字符串\nvar b = false; // 布尔类型的变量，如果刚开始没有值，则可以考虑默认值为 false\nvar c = 0;  // 字符串类型的变量，如果刚开始没有值，可以考虑默认值为 0\nvar d = null; // 空对象，可以初始化为 null\n```\n\n\n\n### case2：变量未声明（未定义）\n\n如果你从未声明一个变量，就去使用它，则会报错（这个大家都知道）；此时，如果用 `typeof` 检查这个变量时，会返回 `undefined`。举例：\n\n```js\nconsole.log(typeof a); // undefined\nconsole.log(a); // 打印结果：Uncaught ReferenceError: a is not defined\n```\n\n### case3：函数无返回值时\n\n如果一个函数没有返回值，那么，这个函数的返回值就是 undefined。\n\n或者，也可以这样理解：在定义一个函数时，如果末尾没有 return 语句，那么，其实就是 `return undefined`。\n\n举例：\n\n```js\nfunction foo() {}\n\nconsole.log(foo()); // 打印结果：undefined\n```\n\n### case4：调用函数时，未传参\n\n调用函数时，如果没有传实参，那么，对应形参的值就是 undefined。\n\n举例：\n\n```js\nfunction foo(name) {\n    console.log(name);\n}\n\nfoo(); // 调用函数时，未传参。执行函数后的打印结果：undefined\n```\n\n实际开发中，如果调用函数时没有传参，我们可以根据需要给形参设置一个默认值：\n\n```js\nfunction foo(name) {\n    name = name || 'qianguyihao';\n}\n\nfoo();\n```\n\n等学习了 ES6 之后，上方代码也可以这样写：\n\n```js\nfunction foo(name = 'qianguyihao') {}\n\nfoo();\n```\n\n## Null：空对象\n\nNull 类型的值只有一个，就是 null。比如 `var a = null`。\n\nnull 专门用来定义一个**空对象**。例如：`let a = null`，又例如 `Object.create(null)`。\n\n如果你想定义一个变量用来保存引用类型（也就是对象），但是还不确定放什么内容，这个时候，可以在初始化时将其赋值为 null。\n\n从语义上讲，null表示一个空对象，所以使用 typeof 检查一个 null 值时，会返回 object。举例：\n\n```js\nvar myObj = null;\ncosole.log(typeof myObj); // 打印结果：object\n```\n\n## 其他区别\n\nundefined 实际上是由 null 衍生出来的，所以`null == undefined`的结果为 true。\n\n但是 `null === undefined` 的结果是 false。它们虽然相似，但还是有区别的，其中一个区别是，和数字运算时：\n\n-   10 + null 结果为 10。\n\n-   10 + undefined 结果为 NaN。\n\n规律总结：\n\n- 任何值和 null 运算，null 可看做 0 运算。\n\n-   任何数据类型和 undefined 运算都是 NaN。\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/09-数据类型转换.md",
    "content": "---\ntitle: 09-数据类型转换\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 前言\n\n**变量的数据类型转换**：将一种数据类型转换为另外一种数据类型。\n\n如果你需要在不同的数据类型之间进行某些操作，那就需要用到数据类型转换。比如：\n\n- 将字符串类型转为数字类型。\n- 将数字和字符串做减法操作。\n- 判断非 Boolean类型的值，是真还是假。\n\n通常有三种形式的数据类型转换：\n\n-   转换为字符串类型\n\n-   转换为数字型\n\n-   转换为布尔型\n\n我需要专门把某个数据类型转换成 null 或者 undefined 吗？因为这样做没有意义。\n\n## 变量的类型转换的分类\n\n类型转换分为两种：显式类型转换、隐式类型转换。\n\n### 显式类型转换\n\n显式类型转换：**手动**将某种数据类型，**强制**转换为另一种数据类型。也就是说，通过调用特定函数或运算符显式地将一个数据类型转换为另一个数据类型。\n\n常见的显示类型转换方法，有这几种：\n\n-   toString()\n\n-   String()\n\n-   Number()\n\n-   parseInt(string)\n\n-   parseFloat(string)\n\n-   Boolean()\n\n### 隐式类型转换\n\n隐式类型转换：这是JS在运行时会**自动执行**的一种类型转换，不需要明确的代码指示。JS 在某些情况下会隐式地将一个数据类型转换为另一个数据类型，以完成某些操作或比较。\n\n常见的隐式类型转换，包括下面这几种：\n\n-   isNaN() 函数\n-   自增/自减运算符：`++`、`—-`\n-   运算符：正号`+a`、负号`-a`\n-   运算符：加号`+`\n-   运算符：`-`、`*`、`/`、`%`\n-   比较运算符：`<`、`>`、 `<=`、 `>=`、`==`等。比较运算符的运算结果都是布尔值：要么是 true，要么是 false。\n-   逻辑运算符：`&&`、`||`、`!` 。非布尔值进行**与或**运算时，会先将其转换为布尔值，然后再运算。`&&`、`||`的运算结果是**原值**，`!`的运算结果为布尔值。\n\n重点：**隐式类型转换，内部调用的都是显式类型转换的方法**。\n\n接下来详细讲讲各种数据类型转换。\n\n## 一、转换为 String\n\n### 1、调用 toString() 方法\n\n语法：\n\n```javascript\n变量.toString();\n常量.toString(); // 这里的常量，不要直接写数字，但可以是其它常量；下文会具体讲。\n\n// 或者用一个新的变量接收转换结果\nvar result = 变量.toString();\n```\n\n该方法**不会影响到原变量**，它会将转换的结果返回。当然我们还可以直接写成`a = a.toString()`，这样的话，就是直接修改原变量。\n\n当我们对一个字符串字面量调用 toString() 方法时，它实际上是调用了 String 构造函数，并将字符串字面量转换为一个 String 对象，然后调用该对象的 toString() 方法。String 对象的 toString() 方法返回调用它的原始字符串值。\n\n举例：\n\n```js\n// 基本数据类型\nvar a1 = 'qianguyihao';\nvar a2 = 29;\nvar a3 = true;\n\n// 引用数据类型\nvar a4 = [1, 2, 3];\nvar a5 = { name: 'qianguyihao', age: 29 };\n\n// undefined 和 null\nvar a6 = null;\nvar a7 = undefined;\n\n// 打印结果都是字符串\nconsole.log(a1.toString()); // \"qianguyihao\"\nconsole.log(a2.toString()); // \"29\"\nconsole.log(a3.toString()); // \"true\"\nconsole.log(a4.toString()); // \"1,2,3\"\nconsole.log(a5.toString()); // \"object\"\n\n// 下面这两个，打印报错\nconsole.log(a6.toString()); // 报错：Uncaught TypeError: Cannot read properties of undefined'\nconsole.log(a7.toString()); // 报错：Uncaught TypeError: Cannot read properties of null\n```\n\n小技巧：在 chrome 浏览器的控制台中，Number类型、Boolean类型的打印结果是蓝色的，String类型的打印结果是黑色的。\n\n一起来看看 toString() 的注意事项：\n\n（1）undefined 和 null 这两个值没有 toString() 方法，所以它们不能用 toString() 。如果调用，会报错。\n\n```js\nconsole.log(undefined.toString());\nconsole.log(null.toString());\n```\n\n![](https://img.smyhvae.com/20211116_1458.png)\n\n如果你不确定一个值是不是`null`或`undefined`，可以使用`String()`函数，下一小段会讲。\n\n（2）多数情况下，`toString()`不接收任何参数；当然也有例外：Number 类型的变量，在调用 toString()时，可以在方法中传递一个整数作为参数。此时它会把数字转换为指定的进制，如果不指定则默认转换为 10 进制。例如：\n\n```javascript\nvar a = 255;\n\n//Number数值在调用toString()时，可以在方法中传递一个整数作为参数\n//此时它将会把数字转换为指定的进制,如果不指定则默认转换为10进制\na = a.toString(2); // 转换为二进制\n\nconsole.log(a); // \"11111111\"\nconsole.log(typeof a); // string\n```\n\n（3）纯小数的小数点后面，如果紧跟连续6个或6个以上的“0”时，那么，将用e来表示这个小数。代码举例：\n\n```js\nconst num1 = 0.000001; // 小数点后面紧跟五个零\nconsole.log(num1.toString()); // 打印结果：\"0.000001\"\n\nconst num2 = 0.0000001; // 小数点后面紧跟六个零\nconsole.log(num2.toString()); // 【重点关注】打印结果：\"1e-7\"\n\nconst num3 = 1.0000001;\nconsole.log(num3.toString()); // 打印结果：\"1.0000001\"\n\nconst num4 = 0.10000001;\nconsole.log(num4.toString()); // 打印结果：\"0.10000001\"\n```\n\n（4）常量可以直接调用 toString() 方法，但这里的常量，不允许直接写数字。举例如下：\n\n```js\n1.toString(); // 注意，会报错\n1..toString(); // 合法。得到的结果是字符串\"1\"\n1.2.toString(); // 合法。得到的结果是字符串\"1.2\"\n(1).toString(); // 合法。得到的结果是字符串\"1\"\n'1'.toString(); // 合法。得到的结果是字符串\"1\"\n```\n\n上方代码中，为何出现这样的打印结果？这是因为：\n\n- 第一行代码：JS引擎认为`1.toString()`中的`.`是小数点，**是数字字面量的一部分，而不是方法调用的分隔符**。小数点后面的字符是非法的。\n- 第二行、第三行代码：JS引擎认为第一个`.`是小数点，第二个`.`是属性访问的语法，所以能正常解释实行。\n- 第四行代码：用`()`排除了`.`被视为小数点的语法解释，所以这种写法也能正常解释执行。\n\n小结：因为点号（.）被解释为数字字面量的一部分，而不是方法调用的分隔符。为了正确调用 toString 方法，可以使用括号或额外的点号。\n\n如果想让数字调 toString() 方法，更推荐的做法是先把数字放到变量中存起来，然后通过变量调用 toString()。举例：\n\n```js\nconst a = 1;\na.toString(); // 合法。得到的结果是字符串\"1\"\n```\n\n\n参考链接：[你不知道的toString方法](https://www.jianshu.com/p/88570529a03c)\n\n（5）既然常量没有方法，那它为什么可以调用 toString() 呢？这是因为，除了 undefined、null 之外，其他的常量都有对应的特殊的引用类型——**基本包装类型**，所以代码在解释执行的时候，会将常量转为基本包装类型，这样就可以调用相应的引用类型的方法。\n\n我们在后续的内容《JavaScritpt基础/基本包装类型》中会专门讲到基本包装类型。\n\n### 2、使用 String() 函数\n\n语法：\n\n```javascript\nString(变量/常量);\n```\n\n该方法**不会影响到原数值**，它会将转换的结果返回。\n\n使用 String()函数做强制类型转换时：\n\n-   对于 Number、Boolean、String、Object 而言，本质上就是调用 toString()方法，返回结果同 toString()方法。\n-   但是对于 null 和 undefined，则不会调用 toString() 方法。它会，将 undefined 直接转换为 \"undefined\"，将 null 直接转换为 \"null\"。\n\n使用String()函数转为字符串的规则如下：\n\n| 原始值              | 转换后的值              |\n| ------------------- | ----------------------- |\n| 布尔值：true、false | 字符串：'true'、'false' |\n| 数字                | 字符串                  |\n| undefined           | 字符串：'undefined'     |\n| null                | 字符串：'null'          |\n| 对象                | 字符串：'object'        |\n\n### 3、隐式类型转换：字符串拼接\n\n如果加号的两边有一个是字符串，则另一边会自动转换成字符串类型进行拼接。\n\n字符串拼接的格式：变量+\"\" 或者 变量+\"abc\"\n\n举例：\n\n```javascript\nvar a = 123; // Number 类型\n// 使用空字符串进行拼接\nconsole.log(a + ''); // 打印结果：\"123\"\n// 使用普通字符串进行拼接\nconsole.log(a + 'haha'); // 打印结果：\"123haha\"\n```\n\n上面的例子中，打印的结果，都是字符串类型的数据。实际上底层是调用的 String() 函数。\n\n### 4、prompt()：用户的输入\n\n我们在前面的《JavaScript基础/02-JavaScript书写方式：hello world》就讲过，`prompt()`就是专门用来弹出能够让用户输入的对话框。重要的是：用户不管输入什么，都当字符串处理。\n\n## 二、转换为 Number\n\n### 1、使用 Number() 函数\n\n语法：\n\n```js\nconst result = Number(变量/常量);\n```\n\n使用 Number() 函数转为数字的规则如下：\n\n| 原始值    | 转换后的值                                                   |\n| --------- | ------------------------------------------------------------ |\n| 字符串    | （1）字符串去掉首尾空格后，剩余字符串的内容如果是纯数字，则直接将其转换为数字。<br/>（2）字符串去掉首尾空格后，剩余字符串包的内容只要含了其他非数字的内容（`小数点`按数字来算），则转换为 NaN。怎么理解这里的 **NaN** 呢？可以这样理解，使用 Number() 函数之后，**如果无法转换为数字，就会转换为 NaN**。<br />（3）如果字符串是一个**空串**或者是一个**全是空格**的字符串，则转换为 0。<br/> |\n| 布尔值    | true 转成 1；false 转成 0                                    |\n| undefined | NaN                                                          |\n| null      | 0                                                            |\n\n### 2、隐式类型转换——运算符：加号 `+`\n\n（1）**字符串 + 其他数据类型 = 字符串**\n\n任何数据类型和字符串做加法运算，都会先自动将那个数据类型调用 String() 函数转换为字符串，然后再做拼串操作。最终的运算结果是字符串。\n\n比如：\n\n```javascript\nresult1 = 1 + 2 + '3'; // 字符串：33\n\nresult2 = '1' + 2 + 3; // 字符串：123\n```\n\n某些函数在执行时也会自动将参数转为字符串类型，比如 `console.log()`函数。\n\n（2）**Boolean + 数字 = 数字**\n\nBoolean 型和数字型相加时， true 按 1 来算 ，false 按 0 来算。这里其实是先调 Number() 函数，将 Boolean 类型转为 Number 类型，然后再和 数字相加。\n\n（3）**null + 数字 = 数字**\n\n等价于：0 + 数字\n\n（4）**undefined + 数字 = NaN**\n\n计算结果：NaN\n\n（5）任何值和 **NaN** 运算的结果都是 NaN。\n\n### 3、隐式类型转换——运算符：`-`、`*`、`/`、`%`\n\n任何非 Number 类型的值做`-`、`*`、`/`、`%`运算时，会将这些值转换为 Number 然后再运算(内部调用的是 Number() 函数），运算结果是 Number 类型。\n\n任何数据和 NaN进行运算，结果都是NaN。\n\n比如：\n\n```js\nvar result1 = 100 - '1'; // 99\n\nvar result2 = true + NaN; // NaN\n```\n\n### 4、隐式类型转换：正负号 `+a`、`-a`\n\n> 注意，这里说的是正号/负号，不是加号/减号。\n\n任何值做`+a`、`-a`运算时， 底层调用的是 Number() 函数。不会改变原数值；得到的结果，会改变正负性。\n\n代码举例：\n\n```js\nconst a1 = '123';\nconsole.log(+a1); // 123\nconsole.log(-a1); // -123\n\nconst a2 = '123abc';\nconsole.log(+a2); // NaN\nconsole.log(-a2); // NaN\n\nconst a3 = true;\nconsole.log(+a3); // 1\nconsole.log(-a3); // -1\n\n\nconst a4 = false;\nconsole.log(+a4); // 0\nconsole.log(-a4); // -0\n\nconst a5 = null;\nconsole.log(+a5); // 0\nconsole.log(-a5); // -0\n\nconst a6 = undefined;\nconsole.log(+a6); // NaN\nconsole.log(-a6); // NaN\n```\n\n\n\n### 5、使用 parseInt()函数：字符串 -> 整数\n\n语法：\n\n```js\nconst result = parseInt(需要转换的字符串)\n```\n\n**parseInt()**：将传入的数据当作**字符串**来处理，从左至右提取数值，一旦遇到非数值就立即停止；停止时如果还没有提取到数值，就返回NaN。\n\nparse 表示“转换”，Int 表示“整数”。例如：\n\n```javascript\nparseInt('5'); // 得到的结果是数字 5\n```\n\n按照上面的规律，使用 parseInt() 函数转为数字的规则如下：\n\n| 原始值              | 转换后的值                                                   |\n| ------------------- | ------------------------------------------------------------ |\n| 字符串              | （1）**只保留字符串最开头的数字**，后面的中文自动消失。<br/>（2）如果字符串不是以数字开头，则转换为 NaN。<br/>（3）如果字符串是一个空串或者是一个全是空格的字符串，转换时会报错。 |\n| 布尔值：true、false | NaN                                                          |\n| undefined           | NaN                                                          |\n| null                | NaN                                                          |\n\nNumber() 函数和 parseInt() 函数的区别：\n\n就拿`Number()` 和 `parseInt()/parseFloat()`来举例，二者在使用时，是有区别的：\n\n-   Number() ：千方百计地想转换为数字；如果转换不了则返回 NaN。\n\n-   parseInt()/parseFloat() ：提取出最前面的数字部分（开头如果是空格，则自动忽略空格）；没提取出来，那就返回 NaN。\n\n**parseInt()具有以下特性**：\n\n（1）parseInt()、parseFloat()会将传入的数据当作**字符串**来处理。也就是说，如果对**非 String**使用 parseInt()、parseFloat()，它会**先将其转换为 String** 然后再操作。【重要】\n\n比如：\n\n```javascript\nvar a = 168.23;\nconsole.log(parseInt(a)); //打印结果：168  （因为是先将 a 转为字符串\"168.23\"，然后然后再操作）\n\nvar b = true;\nconsole.log(parseInt(b)); //打印结果：NaN （因为是先将 b 转为字符串\"true\"，然后然后再操作）\n\nvar c = null;\nconsole.log(parseInt(c)); //打印结果：NaN  （因为是先将 c 转为字符串\"null\"，然后然后再操作）\n\nvar d = undefined;\nconsole.log(parseInt(d)); //打印结果：NaN  （因为是先将 d 转为字符串\"undefined\"，然后然后再操作）\n```\n\n\n（2）**只保留字符串最开头的数字**，后面的中文自动消失。例如：\n\n```javascript\nconsole.log(parseInt('2017在公众号上写了6篇文章')); //打印结果：2017\n\nconsole.log(parseInt('2017.01在公众号上写了6篇文章')); //打印结果仍是：2017   （说明只会取整数）\n\nconsole.log(parseInt('aaa2017.01在公众号上写了6篇文章')); //打印结果：NaN （因为不是以数字开头）\n```\n\n\n（3）自动截断小数：**取整，不四舍五入**。\n\n例 1：\n\n```javascript\nvar a = parseInt(5.8) + parseInt(4.7);\nconsole.log(a);\n```\n\n打印结果：\n\n```\n9\n```\n\n例 2：\n\n```javascript\nvar a = parseInt(5.8 + 4.7);\nconsole.log(a);\n```\n\n打印结果：\n\n```javascript\n10;\n```\n\n（4）带两个参数时，表示在转换时，包含了进制转换。\n\n代码举例：\n\n```javascript\nvar a = '110';\n\nvar num = parseInt(a, 16); // 【重要】将 a 当成 十六进制 来看待，转换成 十进制 的 num\n\nconsole.log(num);\n```\n\n打印结果：\n\n```\n272\n```\n\n如果你对打印结果感到震惊，请仔细看上面的代码注释。就是说，无论 parseInt() 里面的进制参数是多少，最终的转换结果是十进制。\n\n我们知道，八进制的数字是用0开头进行表示。比如`\"070\"`这个字符串，如果我调用 parseInt() 转成数字时，有些浏览器会当成 8 进制解析，有些会当成 10 进制解析。\n\n所以，比较建议的做法是：可以在 parseInt()中传递第二个参数，来指定当前数字的进制。例如：\n\n```javascript\nvar a = '070';\n\na = parseInt(a, 8); //将 070 当成八进制来看待，转换结果为十进制。\nconsole.log(a); // 打印结果：56。这个地方要好好理解。\n```\n\n我们来看下面的代码，打印结果继续震惊：\n\n```javascript\nvar a = '5';\n\nvar num = parseInt(a, 2); // 将 a 当成 二进制 来看待，转换成 十进制 的 num\n\nconsole.log(num); // 打印结果：NaN。因为 二进制中没有 5 这个数，转换失败。\n```\n\n### 6、parseFloat()函数：字符串 --> 浮点数（小数）\n\nparseFloat()的作用是：将字符串转换为**浮点数**。\n\nparseFloat()和 parseInt()的作用类似，不同的是，parseFloat()可以获得小数部分。\n\n代码举例：\n\n```javascript\nvar a = '123.456.789px';\nconsole.log(parseFloat(a)); // 打印结果：123.456\n```\n\nparseFloat() 的几个特性，可以参照 parseInt()。\n\n## 三、转换为 Boolean\n\n### 转换结果列举【重要】\n\n其他的数据类型都可以转换为 Boolean 类型。无论是隐式转换，还是显示转换，转换结果都是一样的。有下面几种情况：\n\n转换为 Boolean 类型的规则如下：\n\n| 原始值    | 转换后的值                                                   |\n| --------- | ------------------------------------------------------------ |\n| 字符串    | 空串的转换结果是false，其余的都是 true。<br />全是空格的字符串，转换结果也是 true。<br />字符串`'0'`的转换结果也是 true。 |\n| 数字      | 0 和 NaN的转换结果 false，其余的都是 true。比如 `Boolean(NaN)`的结果是 false。 |\n| undefined | false                                                        |\n| null      | false                                                        |\n| 对象      | 引用数据类型会转换为 true。<br />注意，空数组`[]`和空对象`{}`，**转换结果也是 true**，这一点，很多人不知道。 |\n\n小结：空字符串''、0、NaN、undefined、null会转换为 false；其他值会转换为 true。\n\n**重中之重来了：**\n\n转换为 Boolean 的上面这几种情况，**极其重要**，项目开发中会频繁用到。比如说，我们在项目开发中，经常需要对一些**非布尔值**做**逻辑判断或者逻辑运算**，符合条件后，才做下一步的事情。这个逻辑判断就是依据上面的四种情况。\n\n举例：（接口返回的内容不为空，前端才做进一步的事情）\n\n```js\nconst result1 = '';\nconst result2 = { a: 'data1', b: 'data2' };\n\n// 逻辑判断\nif (result1) {\n    console.log('因为 result1的内容为空，所以代码进不了这里');\n}\n\n// 逻辑运算\nif (result2 && result2.a) {\n    // 接口返回了 result2，且 result2.a 里面有值，前端才做进一步的事情\n    console.log('代码能进来，前端继续在这里干活儿');\n}\n```\n\n这里再次强调一下，空数组`[]`和空对象`{}`转换为 Boolean 值时，转换结果为 true。\n\n我们在下一篇内容《运算符》中，还会详细讲非布尔值的逻辑运算。\n\n### 1. 隐式类型转换：逻辑运算\n\n当非 Boolean 类型的数值和 Boolean 类型的数值做比较时，会先把前者**临时**进行隐式转换为 Boolean 类型，然后再做比较；且不会改变前者的数据类型。举例如下：\n\n```js\nconst a = 1;\n\nconsole.log(a == true); // 打印结果：true\nconsole.log(typeof a); // 打印结果：number。可见，上面一行代码里，a 做了隐式类型转换，但是 a 的数据类型并没有发生变化，仍然是 Number 类型\n\nconsole.log(0 == true); // 打印结果：false\n```\n\n### 2. 使用 `!!`\n\n使用 `!!`可以显式转换为 Boolean 类型。比如 `!!3`的结果是 true。\n\n### 3. 使用  Boolean()函数\n\n使用 Boolean()函数可以显式转换为 Boolean 类型。\n\n## 隐式类型转换：isNaN() 函数\n\n语法：\n\n```javascript\nisNaN(参数)\n```\n\n解释：判断指定的参数是否**不是数字**（NaN，非数字类型），返回结果为 Boolean 类型。**不是数字时返回 true**，是数字时返回 false。\n\n在做判断时，会进行隐式类型转换。也就是说：**任何不能被转换为数值的参数，都会让这个函数返回 true**。\n\n**执行过程**：\n\n（1）先调用`Number(参数)`函数；\n\n（2）然后判断`Number(参数)`的返回结果是否为数值。如果不为数字，则返回结果为 true；如果为数字，则返回结果为 false。\n\n代码举例：\n\n```javascript\nconsole.log(isNaN('123')); // 返回结果：false。\n\nconsole.log(isNaN(null)); // 返回结果：false\n\nconsole.log(isNaN('abc')); // 返回结果：true。因为 Number('abc') 的返回结果是 NaN\n\nconsole.log(isNaN(undefined)); // 返回结果：true\n\nconsole.log(isNaN(NaN)); // 返回结果：true\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "04-JavaScript基础/10-运算符.md",
    "content": "---\ntitle: 10-运算符\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 运算符的定义和分类\n\n### 运算符的定义\n\n运算符和表达式形影不离，先来介绍一下概念。\n\n**运算符**：也叫操作符，是一种符号。通过运算符可以对一个或多个值进行运算，并获取运算结果。\n\n**表达式**：数字、运算符、变量的组合（组成的式子）。\n\n表达式最终都会有一个运算结果，我们将这个结果称为表达式的**返回值**。\n\n例如：`+`、`*`、`/`、`()` 都是**运算符**，而`（3+5）/2`则是**表达式**。\n\n比如：typeof 就是运算符，可以获得一个值的类型。它会将该值的类型以**字符串**的形式返回，返回值可以是 number、string、boolean、undefined、object。\n\n**运算元**：参与运算的对象。这个对象一般是数值或者变量。\n\n例如：在加法运算中，运算元就是加法操作符两侧的值或变量。在逻辑运算中，运算元是指用于计算逻辑表达式的操作数。\n\n 如果一个运算符（比如加法运算符）拥有两个运算元，那么它是二元运算符。一元运算符、三元运算符的概念同理。\n\n### 运算符的分类\n\nJS 中的运算符，分类如下：\n\n-   算数运算符\n-   自增/自减运算符\n-   一元运算符\n-   三元运算符（条件运算符）\n-   逻辑运算符\n-   赋值运算符\n-   比较运算符\n\n下面来逐一讲解。\n\n## 算数运算符\n\n**算术运算符**：用于执行两个变量或值的算术运算。\n\n此外，算数运算符存在隐式类型转换的情况，前文“数据类型转换”一节中已讲过，本文不再赘述。\n\n常见的算数运算符有以下几种：\n\n| 运算符 |          描述           |\n| :----- | :---------------------: |\n| +      |     加、字符串连接      |\n| -      |           减            |\n| \\*     |           乘            |\n| /      |           除            |\n| %      |  获取余数（取余/取模）  |\n| **     | 幂运算，是 ES7 新增特性 |\n\n**求余的举例**：\n\n假设用户输入 345，怎么分别得到 3、4、5 这三个数呢？\n\n**答案**：\n\n```\n得到3的方法：345 除以100，得到3.45然后取整，得到3。即：parseInt(345/100)\n\n得到4的方法：345 除以100，余数是45，除以10，得到4.5，取整。即：parseInt(345 % 100 / 10)\n\n得到5的方法：345 除以10，余数就是5。即：345 % 10\n```\n\n### 算数运算符的运算规则\n\n（1）`* / %` 的优先级高于 `+ -`\n\n（2）无论是`+ - * / %`都是左结合性（从左至右计算）\n\n（2）小括号`( )`：能够影响计算顺序，且可以嵌套。没有中括号、没有大括号，只有小括号。\n\n举例 1：（取余）\n\n```javascript\nconsole.log(3 % 5);\n```\n\n输出结果为 3。\n\n举例 2：（注意运算符的优先级）\n\n```javascript\nvar a = 1 + ((2 * 3) % 4) / 3;\n```\n\n结果分析：\n\n原式 = 1 + 6 % 4 / 3 = 1 + 2 / 3 = 1.66666666666666\n\n\n### 取余（取模）运算\n\n格式：\n\n```js\n余数 = m % n;\n```\n\n`%`符号在这里并不是用来做百分号的计算，和百分号计算没有关系。\n\n计算结果注意：\n\n- 取余运算结果的正负性，取决于 m，而不是 n。比如：`10 % -3`的运算结果是 1。`-10 % 3`的运算结果是-1。\n\n-   如果 n < 0，那就先把 n 取绝对值后，再计算。等价于 m % (-n)。\n-   如果 n 是 0，那么结果是 NaN。\n-   在 n > 0 的情况下：\n\n    -   如果 m>=n，那就正常取余。\n    -   如果 m<n，那结果就是 m。\n\n\n\n### 幂运算\n\n`**`这个符号在JS中是幂运算符，是ES7中新增的特性。比如，2的3次方，可以表示为 `2**3`。\n\n除了`**`运 算符之外，JavaScript 还提供了`Math.pow()方法，也可以进行幂运算。\n\n下面两行代码是等价的：（2的3次方）\n\n```\n2**3;\nMath.pow(2, 3);\n```\n\n此外，需要注意，`** `运算符的优先级高于乘法和除法运算符。\n\n### 浮点数运算的精度问题\n\n浮点数值的最高精度是 17 位小数，但在进行算术计算时，会丢失精度，导致计算不够准确。比如：\n\n```javascript\nconsole.log(0.1 + 0.2); // 运算结果不是 0.3，而是 0.30000000000000004\n\nconsole.log(0.07 * 100); // 运算结果不是 7，而是 7.000000000000001\n```\n\n因此，**不要直接判断两个浮点数是否相等**。前面的文章《JavaScript 基础：基本数据类型：Number》有详细介绍。\n\n实际项目中，涉及数字计算的这部分，比较麻烦，且非常严谨；尤其是交易、金钱相关的业务，则一定不能出错。\n\n如果你直接把两个数字进行加减乘除，很容易丢失精度，导致计算不准确。实战中，往往需要把计算相关的代码封装成公共方法，提供给业务侧调用。\n\n我们也可以在开源网站找到一些已经封装好的工具类，比较知名的是 [big.js](https://github.com/MikeMcl/big.js)。\n\n\n\n## 赋值运算符\n\n赋值：将等号右侧的值赋给符号左侧的变量。\n\n### 赋值运算符包括哪些\n\n| 运算符 | 运算规则     | 举例                        |\n| ------ | ------------ | --------------------------- |\n| =      | 直接赋值     | let a = 5                   |\n| +=     | 加后赋值     | a += 5 等价于 a = a + 5     |\n| -=     | 减后赋值     | a -= 5 等价于 a = a - 5     |\n| *=     | 乘后赋值     | a *= 5 等价于 a = a * 5     |\n| /=     | 除后赋值     | a /= 5 等价于 a = a / 5     |\n| %=     | 取余数后赋值 | a %= 5 等价于 a = a % 5     |\n| **=    | 幂运算后赋值 | `a **= 5` 等价于 `a = a**5` |\n\n### 直接赋值\n\n`=` 运算符是直接赋值，很容易理解。比如 `let a = 5`。意思是把 5 这个值，往 a 里面存一份。简称：把 5 赋值给 a。\n\n我们要注意， a = 5 是有返回值的，它的返回值就等于5。因为**赋值操作本身也是一个表达式，它会返回赋值后的值**。\n\n举例：\n\n```js\nlet a;\nconsole.log(a = 5); // 打印结果为5。\n```\n\n在上面的情况中，a = 5 语句不仅会将 5 赋值给 a，而且还会返回 5 作为整个表达式的值。\n\n算数运算符的优先级高于赋值运算符。举例：\n\n```js\nconst result = 1 + 2; // 先计算 1 + 2，再把计算结果赋值给 result。因为算数运算符的优先级高于赋值运算符。\n```\n\n**原地修改**（Modify-in-place）：\n\n“原地修改”是数据结构中比较常见的概念。定义是：直接在数据原有的存储位置上进行修改，而不是创建一个新的副本（即新的存储空间）来存储修改后的结果。这种方式可以减少内存的使用，因为不需要额外的存储空间来保存修改后的数据。\n\n通俗理解：对一个变量进行运算，并将新的运算结果存储在原有变量中。\n\n上面列出的赋值运算符中，`=`符号是直接赋值，其他的赋值运算都是属于原地修改。\n\n### 链式赋值（chaining assignments）\n\n举例：\n\n```js\n const a = b = c = 2;\n```\n\n解释：把 a、b、c 都赋值为2。\n\n注意：链式赋值的结合性是右结合性（从右至左的顺序进行计算）。举例：\n\n```js\nconst a, b;\na = b = 3; // 先将 3 复制给 b，再将 b 的值赋值给 a\n```\n\n\n\n## 自增和自减运算符\n\n### 自增运算符 `++`\n\n作用：可以快速对一个变量进行加1操作。\n\n注意事项：只能操作变量，不能操作常量或者表达式，否则会报错。\n\n例如：\n\n```js\nlet a1 = 1;\nlet a2 = 2;\n\na1++;\nconst result = a1++ + a2; // result的结果为4\n// (a1+a2)++; // 报错，不支持表达式的写法，\n\nconst a3 = 3;\na3++; // 报错，因为常量无法再自加\n```\n\n自增分成两种：`a++`和`++a`。共同点：\n\n（1）无论是 `a++` 还是`++a`，自增都会使原变量的值加 1。\n\n（2）**我们要注意的是**：`a`是变量，而`a++`和`++a`是**表达式**。\n\n（3）如果只想使用 a 的值，不使用表达式的值，那么这两种写法都可以，因为 a 的值没有区别。一般是用 a++ 这种写法更多一些。\n\n那这两种自增，有啥区别呢？区别是：`a++` 和 `++a`的值不同：（也就是说，表达式的值不同）\n\n-   `a++`这个表达式的值等于原变量的值（a 自增前的值）。可以这样理解：先把 a 的值赋值给表达式，然后 a 再自增。\n\n-   `++a`这个表达式的值等于新值 （a 自增后的值）。 可以这样理解：a 先自增，然后把自增后的值赋值给表达式。\n\n举例：\n\n```js\nlet a1 = 3;\nconst result1 = 10 + a1++;\n\nconsole.log('a1:', a1); // 打印结果：4\nconsole.log('result1:', result1); // 打印结果：13\n\nlet a2 = 3;\nconst result2 = 10 + ++a2;\n\nconsole.log('a2:', a2); // 打印结果：4\nconsole.log('result2:', result2); // 打印结果：14\n```\n\n### 自减运算符 `--`\n\n作用：可以快速对一个变量进行减1操作。原理同自增运算符。\n\n开发时，大多使用后置的自增/自减，并且代码独占一行，例如：`num++`，或者 `num--`。\n\n### 代码举例\n\n```javascript\nvar n1 = 10;\nvar n2 = 20;\n\nvar result1 = n1++;\n\nconsole.log(n1); // 11\nconsole.log(result1); // 10\n\nresult = ++n1;\nconsole.log(n1); //12\nconsole.log(result); //12\n\nvar result2 = n2--;\nconsole.log(n2); // 19\nconsole.log(result2); // 20\n\nresult2 = --n2;\nconsole.log(n2); // 18\nconsole.log(result2); // 18\n```\n\n### 隐式类型转换的过程\n\n自增和自减时，变量 a 包含了隐式类型转换的过程：\n\n（1）先调用`Number(a)`函数；\n\n（2）然后将`Number(a)`的返回结果进行 加 1 操作，得到的结果赋值给 a。\n\n举例 1：\n\n```javascript\nlet a = '666'; // 这里不能用 const 定义，否则报错。\na++;\n\nconsole.log(a); // 打印结果：667\nconsole.log(typeof a); // 打印结果： number\n```\n\n举例2：\n\n```javascript\nlet a = 'abc';\na++;\n\nconsole.log(a); // 打印结果：NaN。因为 Number('abc')的结果为 NaN，再自增后，结果依然是 NaN\nconsole.log(typeof a); // 打印结果：number\n```\n\n## 一元运算符\n\n一元运算符，只需要一个操作数。常见的一元运算符如下。\n\n### typeof\n\n> typeof 是典型的一元运算符，因为后面只跟一个操作数。\n\n因为 JS是弱类型语言，是[松散型](https://blog.csdn.net/cuk0051/article/details/108340196)语言，所以我们不需要**显式**指定数据的具体类型。但是很多时候，我们仍需要通过一种手段知道某个变量到底是哪一种数据类型，typeof 运算符应运而生。\n\n`typeof()`表示“**获取变量的数据类型**”，它是 JS 提供的一个操作符。返回的是小写，语法为：（两种写法都可以）\n\n```javascript\n// 写法1\ntypeof 变量;\n\n// 写法2\ntypeof(变量);\n```\n\ntypeof 这个运算符的返回结果就是变量的数据类型。那返回结果的类型是什么呢？是字符串。\n\ntypeof 是一个运算符，或者说是一个操作符，所以说，typeof() 并不是一个函数，`()`只是将括起来的内容当做一个整体而已。\n\n延伸一下，`()` 这个符号至少有两个作用：\n\n- 作用1：调用函数\n- 作用2：**表示括起来的内容/表达式是一个整体**。比如 `1+2*3` 与 (1+2)*3的写法是有区别的。\n\ntypeof() 的**返回结果**：\n\n| typeof 的语法                | 返回结果  |\n| :--------------------------- | :-------: |\n| typeof 数字（含 typeof NaN） |  number   |\n| typeof 字符串                |  string   |\n| typeof 布尔型                |  boolean  |\n| typeof 对象                  |  object   |\n| typeof 方法                  | function  |\n| typeof null                  |  object   |\n| typeof undefined             | undefined |\n\n备注：\n\n- 为啥 `typeof null`的返回值也是 object 呢？因为 null 代表的是**空对象**。\n- `typeof NaN`的返回值是 number，之前的内容中讲过，NaN 是一个特殊的数字。\n\n**返回结果举例**：\n\n```javascript\nvar a = '123';\nconsole.log(typeof a); // 打印结果：string\n\nconsole.log(typeof []); // 空数组的打印结果：object\n\nconsole.log(typeof {}); // 空对象的打印结果：object\n```\n\n代码解释：这里的空数组`[]`、空对象`{}` ，为啥他们在使用 typeof 时，返回值也是 `object`呢？因为空数组、空对象都是**引用数据类型 Object**。\n\ntypeof 无法区分数组，但 instanceof 可以。比如：\n\n```js\nconsole.log([] instanceof Array); // 打印结果：true\n\nconsole.log({} instanceof Array); // 打印结果：false\n```\n\n关于 instanceof 的详细内容，以后讲对象的时候，会详细介绍。\n\n### 正号/负号：`+a`、`-a`\n\n> 注意，这里说的是正号/负号，不是加号/减号。\n\n（1）不会改变原数值。\n\n（1）正号不会对数字产生任何影响。比如说，`2`和`+2`是一样的。\n\n（2）我们可以对其他的数据类型使用`+`，来将其转换为 number【重要的小技巧】。比如：\n\n```javascript\nvar a = true;\na = +a; // 注意这行代码的一元运算符操作\nconsole.log('a：' + a);\nconsole.log(typeof a);\n\nconsole.log('-----------------');\n\nvar b = '18';\nb = +b; // 注意这行代码的一元运算符操作\nconsole.log('b：' + b);\nconsole.log(typeof b);\n```\n\n打印结果：\n\n```\na：1\nnumber\n\n-----------------\n\nb：18\nnumber\n```\n\n（3）负号可以对数字进行取反。\n\n### 隐式类型转换——正号/负号\n\n任何值做`+a`、`-a`运算时， 内部调用的是 Number() 函数。\n\n**举例**：\n\n```javascript\nconst a = '666';\nconst b = +a; // 对 a 进行一元运算，b是运算结果\n\nconsole.log(typeof a); // 打印结果：string。说明 a 的数据类型保持不变。\nconsole.log(a); // 打印结果：\"666\"。不会改变原数值。\n\nconsole.log(typeof b); // 打印结果：number。说明 b 的数据类型发生了变化。\nconsole.log(b); // 打印结果：666\n```\n\n## 三目运算符\n\n三目运算符也叫三元运算符、条件运算符。\n\n语法：\n\n```\n条件表达式 ? 语句1 : 语句2;\n```\n\n**执行流程**——条件运算符在执行时，首先对条件表达式进行求值：\n\n-   如果该值为 true，则执行语句 1，并返回执行结果\n\n-   如果该值为 false，则执行语句 2，并返回执行结果\n\n如果条件表达式的求值结果是一个非布尔值，会将其转换为布尔值然后再运算。\n\n## 逻辑运算符\n\n逻辑运算符有三个：\n\n-   `&&`： 与（且）。两个都为真，结果才为真。特点：一假则假。\n\n-   `||` ：或。只要有一个是真，结果就是真。特点：特点: 一真则真。\n\n-   `!` ：非。对一个布尔值进行取反。特点: 真变假, 假变真。\n\n注意：能参与逻辑运算的，都是布尔值。\n\n**连比的写法：**\n\n来看看逻辑运算符连比的写法。\n\n举例 1：\n\n```javascript\nconsole.log(3 < 2 && 2 < 4);\n```\n\n输出结果为 false。\n\n举例 2：（判断一个人的年龄是否在 18~65 岁之间）\n\n```javascript\nconst a = prompt('请输入您的年龄');\n\nif (a >= 18 && a < 65) {\n    alert('可以上班');\n} else {\n    alert('准备退休');\n}\n```\n\nPS：上面的`a>=18 && a<= 65`千万别想当然地写成` 18<= a <= 65`，没有这种语法。\n\n### 非布尔值的与或运算【重要】\n\n> 之所以重要，是因为在实际开发中，我们经常用这种代码做容错处理或者兜底处理。\n\n非布尔值进行**与或运算**时，会通过隐式类型转换，先将其转换为布尔值，然后再运算，但返回结果是**原值**。比如说：\n\n```javascript\nvar result = 5 && 6; // 运算过程：true && true;\nconsole.log('result：' + result); // 打印结果：6（也就是最后面的那个值）\n```\n\n上方代码可以看到，虽然运算过程为布尔值的运算，但返回结果是原值。\n\n那么，返回结果是哪个原值呢？我们来看一下。\n\n1、两个非布尔值，做逻辑运算：\n\n**与运算**的返回结果：\n\n-   如果第一个值为 false，则只执行第一条语句，并直接返回第一个值；不会再往后执行。\n\n-   如果第一个值为 true，则继续执行第二条语句，并返回第二个值（无论第二个值的结果如何）。\n\n**或运算**的返回结果：\n\n-   如果第一个值为 true，则只执行第一条语句，并直接返回第一个值；不会再往后执行。\n\n-   如果第一个值为 false，则继续执行第二条语句，并返回第二个值（无论第二个值的结果如何）。\n\n2、三个及以上的非布尔值，做逻辑运算：\n\n**与运算**的返回结果：（value1 && value2 && value3）\n\n- 从左到右依次计算操作数，找到第一个为 false 的值为止。\n- 如果所有的值都为 true，则返回最后一个值。\n\n**或运算**的返回结果：（value1 || value2 || value3）\n\n- 从左到右依次计算操作数，找到第一个为 true 的值为止。\n- 如果所有的值都为 false，则返回最后一个值。\n\n### 非布尔值的 `!` 运算\n\n非布尔值进行**非运算**时，会先将其转换为布尔值，然后再运算，返回结果是**布尔值**。\n\n举例：\n\n```javascript\nlet a = 10;\na = !a;\n\nconsole.log(a); // false\nconsole.log(typeof a); // boolean\n```\n\n### 短路运算的妙用【重要】\n\n> 下方举例中的写法技巧，在实际开发中，经常用到。这种写法，是一种很好的「容错、容灾、降级」方案，需要多看几遍。\n\n1、JS 中的`&&`属于**短路**的与：\n\n-   如果第一个值为 false，则不会执行后面的内容。\n\n-   如果第一个值为 true，则继续执行第二条语句，并返回第二个值。\n\n举例：\n\n```javascript\nconst a1 = 'qianguyihao';\n// 第一个值为true，会继续执行后面的内容\na1 && alert('看 a1 出不出来'); // 可以弹出 alert 框\n\nconst a2 = undefined;\n// 第一个值为false，不会继续执行后面的内容\na2 && alert('看 a2 出不出来'); // 不会弹出 alert 框\n```\n\n2、JS 中的`||`属于**短路**的或：\n\n-   如果第一个值为 true，则不会执行后面的内容。\n\n-   如果第一个值为 false，则继续执行第二条语句，并返回第二个值。\n\n实际开发中，我们经常是这样来做「容错处理」的，如下。\n\n举例1：\n\n```js\nconst result; // 请求接口时，后台返回的内容\nlet errorMsg = ''; // 前端的文案提示\nif (result & result.retCode == 0) {\n  errorMsg = '恭喜你中奖啦~'\n}\n\nif (result && result.retCode != 0) {\n\t// 接口返回异常码时\n\terrorMsg = result.msg || '活动太火爆，请稍后再试'; // 文案提示信息，优先用 接口返回的msg字段，其次用 '活动太火爆，请稍后再试' 这个文案兜底。\n}\n\nif (!result) {\n\t// 接口挂掉时\n\terrorMsg = '网络异常，请稍后再试';\n}\n```\n\n举例2，当前端成功调用一个接口后，返回的数据为 result 对象。这个时候，我们用变量 a 来接收 result 里的图片资源：\n\n```javascript\nif (result.retCode == 0) {\n    var a = result && result.data && result.data.imgUrl || 'http://img.smyhvae.com/20160401_01.jpg';\n}\n```\n\n上方代码的意思是，获取返回结果中的`result.data.imgUrl`这个图片资源；如果返回结果中没有 `result.data.imgUrl` 这个字段，就用 `http://img.smyhvae.com/20160401_01.jpg` 作为**兜底**图片。这种写法，在实际开发中经常用到。\n\n\n\n\n\n## 比较运算符\n\n比较运算符可以比较两个值之间的大小关系，如果关系成立它会返回 true，如果关系不成立则返回 false。\n\n比较运算符有很多种，比如：\n\n```\n>\t大于号\n<\t小于号\n>= \t大于或等于\n<=  小于或等于\n== \t等于\n=== 全等于\n!=\t不等于\n!== 不全等于\n```\n\n**比较运算符，得到的结果都是布尔值：要么是 true，要么是 false**。如果关系成立，就返回true；如果关系不成立，就返回false。\n\n举例如下：\n\n```javascript\nconst result = 5 > 10; // false\n```\n\n### 非数值的比较\n\n（1）对于非数值进行比较时，会将其转换为数值类型（内部是调用`Number()方法`），再进行比较。\n\n举例如下：\n\n```javascript\nconsole.log(1 > true); //false\nconsole.log(1 >= true); //true\nconsole.log(1 > '0'); //true\n\n//console.log(10 > null); //true\n\n//任何值和NaN做任何比较都是false\n\nconsole.log(10 <= 'hello'); //false\nconsole.log(true > false); //true\n```\n\n（2）特殊情况：如果参与比较的都是字符串，则**不会**将其转换为数字进行比较，比较的是字符串的**Unicode 编码**。【非常重要，这里是个大坑，很容易踩到】\n\n比较字符编码时，是一位一位进行比较，顺序从左到右。如果大一样，则继续比较下一位。\n\n比如说，当你尝试去比较`\"123\"`和`\"56\"`这两个字符串时，你会发现，字符串\"56\"竟然比字符串\"123\"要大（因为 5 比 1 大）。也就是说，下面这样代码的打印结果，其实是 true:（这个我们一定要注意，在日常开发中，很容易忽视）\n\n```javascript\n// 比较两个字符串时，比较的是字符串的字符编码，所以可能会得到不可预期的结果\nconsole.log('56' > '123'); // true\n```\n\n**因此**：当我们想比较两个字符串型的数字时，**一定一定要先转型**再比较大小，比如 `parseInt()`。\n\n（3）任何值和 NaN 做任何比较都是 false。\n\n### `==`符号的强调\n\n`==`这个符号，它是**判断是否等于**，而不是赋值。注意事项如下：\n\n（1）`== `这个符号，还可以验证字符串是否相同。例如：\n\n```javascript\nconsole.log('我爱你中国' == '我爱你中国'); // 输出结果为true\n```\n\n（2）`== `这个符号并不严谨，会做隐式转换，将不同的数据类型，**转为相同类型**进行比较。例如：\n\n```javascript\nconsole.log('6' == 6); // 打印结果：true。这里的字符串\"6\"会先转换为数字6，然后再进行比较\nconsole.log(true == '1'); // 打印结果：true\nconsole.log(0 == -0); // 打印结果：true\n\nconsole.log(null == 0); // 打印结果：false\n```\n\n（3）undefined 衍生自 null，所以这两个值做相等判断时，会返回 true。\n\n```javascript\nconsole.log(undefined == null); //打印结果：true。\n```\n\n（4）NaN 不和任何值相等，包括它本身。\n\n```javascript\nconsole.log(NaN == NaN); //false\nconsole.log(NaN === NaN); //false\n```\n\n问题：那如果我想判断 b 的值是否为 NaN，该怎么办呢？\n\n答案：可以通过 isNaN()函数来判断一个值是否是 NaN。举例：\n\n```javascript\nconsole.log(isNaN(b));\n```\n\n如上方代码所示，如果 b 为 NaN，则返回 true；否则返回 false。\n\n### `===`全等符号的强调\n\n**全等在比较时，不会做类型转换**。如果要保证**完全等于**（即：不仅要判断取值相等，还要判断数据类型相同），我们就要用三个等号`===`。例如：\n\n```javascript\nconsole.log('6' === 6); //false\nconsole.log(6 === 6); //true\n```\n\n上述内容分析出：\n\n-   `==`两个等号，不严谨，\"6\"和 6 是 true。\n\n-   `===`三个等号，严谨，\"6\"和 6 是 false。\n\n另外还有：**`==`的反面是`!=`，`===`的反面是`!==`**。例如：\n\n```javascript\nconsole.log(3 != 8); // true\nconsole.log(3 != '3'); // false，因为3==\"3\"是true，所以反过来就是false。\nconsole.log(3 !== '3'); // true，应为3===\"3\"是false，所以反过来是true。\n```\n\n## 不同数据类型之间的大小比较\n\n这一段是比较运算符的延伸，内容繁琐，新手可以不用记，等以后用到的时候再查阅。\n\n### 数值类型和其他类型比较\n\n先将其他类型隐式转换为数值类型（内部是调用`Number()`方法），然后比较大小。代码举例：\n\n```js\n//字符串与数字比较\nconsole.log('200' > 100); // true\nconsole.log('a' > 100); // false。 'a' 被转换成 NaN 进行比较\nconsole.log('110a' > 100); // false。 '110a' 被转换成 NaN 进行比较。说明`110a`在做隐式转换的时候，是调用了 Number('110a')方法，而不是调用  parseInt('110a')方法\n\n// 布尔值与数字比较\nconsole.log(true == 1); // true\nconsole.log(false == 0); // true\n\n// null 与数字进行比较\nconsole.log(null < 0); // false\nconsole.log(null == 0); // false\nconsole.log(null > 0); // false\nconsole.log(null <= 0); // true。这是一个很严重的bug\nconsole.log(null >= 0); // true。同上\n\n// undefined 与数字进行比较：结果都是 false\nconsole.log(undefined > 0);\nconsole.log(undefined == 0);\nconsole.log(undefined < 0);\nconsole.log(undefined >= 0);\n```\n\n### 日期大小比较\n\n如果日期的格式为字符串，则比较字符串的**Unicode 编码**。代码举例：\n\n```js\nconst myDate1 = new Date(2022, 8, 8);\nconst myDate2 = new Date(2022, 8, 9);\nconst myDate3 = new Date(2022, 9, 8);\nconst myDate4 = new Date(2023, 8, 8);\nconsole.log(myDate1 < myDate2); // true\nconsole.log(myDate1 < myDate3); // true\nconsole.log(myDate3 < myDate4); // true\n\nconst date1 = '2022-08-08'; // \"2022/08/08\"同理\nconst date2 = '2022-08-09'; // \"2022/08/09\"同理\nconst date3 = '2022-09-08'; // \"2022/09/08\"同理\nconst date4 = '2023-08-08'; // \"2023/08/08\"同理\nconsole.log(date1 < date2); // true\nconsole.log(date1 < date3); // true\nconsole.log(date3 < date4); // true\n\nconst time1 = '2022-08-08 08:00:00';\nconst time2 = '2022-08-08 08:00:01';\nconst time3 = '2022-08-08 08:01:00';\nconst time4 = '2022-08-08 09:00:00';\nconsole.log(time1 < time2); // true\nconsole.log(time1 < time3); // true\nconsole.log(time1 < time4); // true\n\n// 数据类型不同，此处是先将 myDate1 转为字符串类型，然后比较大小。可想而知，结果都是 false\nconsole.log(myDate1 >= date1); // false\nconsole.log(myDate1 <= date1); // false\n\n// 虽然时间格式不同，但都是字符串，所以可以比较大小\nconsole.log(date1 < time1); // true\n```\n\n参考链接：\n\n- [【JavaScript】探究数据类型之间的隐式转换和大小比较](https://blog.csdn.net/w390058785/article/details/79957206)\n\n## 逗号运算符\n\n逗号运算符一般用于简化代码。逗号运算符的优先级是所有运算符中最低的。\n\n逗号运算符也是一个运算符, 所以也有运算符结果。它的运算符结果是最后一个表达式的结果。\n\n代码举例：\n\n```js\n// 利用逗号运算符同时定义多个变量\nlet a, b;\n// 利用逗号运算符同时给多个变量赋值\na = 10, b = 5;\n\nconst res1 = (1 + 2, 3 + 4, 5 + 6); // 打印结果：11\n```\n\n## 运算符的优先级\n\n运算符的优先级如下：（优先级从高到低）\n\n-   `.`、`[]`、`new`\n\n-   `()`\n\n-   `++`、`--`\n\n-   `!`、`~`、`+`（单目）、`-`（单目）、`typeof`、`void`、`delete`\n\n-   `*`、`/`、`%`\n\n-   `+`（双目）、`-`（双目）\n\n-   `<<`、`>>`、`>>>`\n\n-   比较运算符：`<`、`<=`、`>`、`>=`\n\n-   比较运算符：`==`、`!==`、`===`、`!==`\n\n-   `&`\n\n-   `^`\n\n-   `|`\n\n-   逻辑运算符：`&&` （注意：逻辑与 `&&` 比逻辑或 `||` 的优先级更高）\n\n-   逻辑运算符：`||`\n\n-   `?:`\n\n-   `=`、`+=`、`-=`、`*=`、`/=`、`%=`、`<<=`、`>>=`、`>>>=`、`&=`、`^=`、`|=`\n\n-   `,`\n\n备注：在实际写代码的时候，如果你不清楚哪个优先级更高，可以先尝试把括号用上。\n\n## Unicode 编码\n\n> 这一段中，我们来讲引申的内容：Unicode 编码的使用。\n\n各位同学可以先在网上查一下“Unicode 编码表”。\n\n1、在字符串中可以使用转义字符输入 Unicode 编码。格式如下：\n\n```\n\\u四位编码\n```\n\n举例如下：\n\n```javascript\nconsole.log('\\u2600'); // 这里的 2600 采用的是16进制\nconsole.log('\\u2602'); // 这里的 2602 采用的是16进制。\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20181222_1218.png)\n\n2、我们还可以在 HTML 网页中使用 Unicode 编码。格式如下：\n\n```\n&#四位编码;\n```\n\nPS：我们知道，Unicode 编码采用的是 16 进制，但是，这里的编码需要使用 10 进制。\n\n举例如下：\n\n```html\n<h1 style=\"font-size: 100px;\">&#9860;</h1>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20181222_1226.png)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/11-流程控制语句：选择结构（if和switch）.md",
    "content": "---\ntitle: 11-流程控制语句：选择结构（if和switch）\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 代码块\n\n用`{}`包围起来的代码，就是代码块。\n\n在 ES5 语法中，代码块，只具有**分组**的作用，没有其他的用途。代码块中的内容，在外部是完全可见的。举例：\n\n```javascript\n{\n    var a = 2;\n    alert('qianguyihao');\n    console.log('千古壹号');\n}\n\nconsole.log('a = ' + a);\n```\n\n打印结果：（可以看出，虽然变量 a 是定义在代码块中的，但是在外部依然可以访问）\n\n```\n千古壹号\na = 2\n```\n\n## 流程控制语句\n\n在一个程序执行的过程中，各条语句的执行顺序对程序的结果是有直接影响的。所以，我们必须清楚每条语句的执行流程。而且，很多时候我们要通过控制语句的执行顺序来实现我们想要的业务逻辑和功能。\n\n### 流程控制语句分类\n\n-   顺序结构\n\n-   选择结构：if 语句、switch 语句\n\n-   循环结构：while 语句、for 语句\n\n## 顺序结构\n\n按照代码的先后顺序，依次执行。结构图如下：\n\n![](http://img.smyhvae.com/20181227_1200.png)\n\n## if 语句\n\nif 语句有以下三种形式。\n\n### if 语句的三种形式\n\n形式1：（条件成立才执行。如果条件不成立，那就什么都不做）\n\n```javascript\nif (条件表达式) {\n    // 条件为真时，做的事情\n}\n```\n\n对于非布尔类型的数据，会先转换成布尔类型再判断。下同。\n\n形式 2：\n\n```javascript\nif (条件表达式) {\n    // 条件为真时，做的事情\n} else {\n    // 条件为假时，做的事情\n}\n```\n\n形式3：（多分支的 if 语句）\n\n```javascript\nif (条件表达式1) {\n    // 条件1为真时，做的事情\n} else if (条件表达式2) {\n    // 条件1不满足，条件2满足时，做的事情\n} else if (条件表达式3) {\n    // 条件1、2不满足，条件3满足时，做的事情\n} else {\n    // 条件1、2、3都不满足时，做的事情\n}\n```\n\n以上所有的语句体中，只执行其中一个。\n\n### 做个题目\n\n```\n根据BMI（身体质量指数）显示一个人的体型。\nBMI指数，就是体重、身高的一个计算公式。公式是：\nBMI =体重÷身高的平方\n\n比如，老师的体重是81.6公斤，身高是1.71米。\n那么老师的BMI就是  81.6 ÷ 1.712     等于 27.906022365856163\n\n过轻：低于18.5\n正常：18.5-24.99999999\n过重：25-27.9999999\n肥胖：28-32\n非常肥胖, 高于32\n\n用JavaScript开发一个程序，让用户先输入自己的体重，然后输入自己的身高（弹出两次prompt框）。\n计算它的BMI，根据上表，弹出用户的身体情况。比如“过轻” 、 “正常” 、“过重” 、 “肥胖” 、“非常肥胖”。\n```\n\n**答案**：\n\n写法 1：\n\n```javascript\n//第一步，输入身高和体重\nconst height = parseFloat(prompt('请输入身高，单位是米'));\nconst weight = parseFloat(prompt('请输入体重，单位是公斤'));\n//第二步，计算BMI指数\nconst BMI = weight / Math.pow(height, 2);\n//第三步，if语句来判断\nif (BMI < 18.5) {\n    alert('偏瘦');\n} else if (BMI < 25) {\n    alert('正常');\n} else if (BMI < 28) {\n    alert('过重');\n} else if (BMI <= 32) {\n    alert('肥胖');\n} else {\n    alert('非常肥胖');\n}\n```\n\n写法 2：\n\n```javascript\n//第一步，输入身高和体重\nconst height = parseFloat(prompt('请输入身高，单位是米'));\nconst weight = parseFloat(prompt('请输入体重，单位是公斤'));\n//第二步，计算BMI指数\nconst BMI = weight / Math.pow(height, 2);\n//第三步，if语句来判断\nif (BMI > 32) {\n    alert('非常肥胖');\n} else if (BMI >= 28) {\n    alert('肥胖');\n} else if (BMI >= 25) {\n    alert('过重');\n} else if (BMI >= 18.5) {\n    alert('正常');\n} else {\n    alert('偏瘦');\n}\n```\n\n### if 语句的嵌套\n\n我们通过下面这个例子来引出 if 语句的嵌套。\n\n```\n一个加油站为了鼓励车主多加油，所以加的多有优惠。\n92号汽油，每升6元；如果大于等于20升，那么每升5.9；\n97号汽油，每升7元；如果大于等于30升，那么每升6.95\n编写JS程序，用户输入自己的汽油编号，然后输入自己加多少升，弹出价格。\n```\n\n![](http://img.smyhvae.com/20180117_2232.png)\n\n代码实现如下：\n\n```javascript\n//第一步，输入\nconst bianhao = parseInt(prompt('您想加什么油？填写92或者97'));\nconst sheng = parseFloat(prompt('您想加多少升？'));\n\n//第二步，判断\nif (bianhao == 92) {\n    //编号是92的时候做的事情\n    if (sheng >= 20) {\n        const price = sheng * 5.9;\n    } else {\n        const price = sheng * 6;\n    }\n} else if (bianhao == 97) {\n    //编号是97的时候做的事情\n    if (sheng >= 30) {\n        const price = sheng * 6.95;\n    } else {\n        const price = sheng * 7;\n    }\n} else {\n    alert('不好意思，没有这个编号的汽油！');\n}\n\nalert('价格是' + price);\n```\n\n## switch 语句（条件分支语句）\n\nswitch 语句也叫条件分支语句。\n\n### 语法格式\n\n```javascript\nswitch(表达式) {\n\tcase 值1：\n\t\t语句体1;\n\t\tbreak;\n\n\tcase 值2：\n\t\t语句体2;\n\t\tbreak;\n\n\t...\n\t...\n\n\tdefault：\n\t\t语句体 n+1;\n\t\tbreak;\n}\n```\n\n**解释**：switch 可以理解为“开关、转换” 。case 可以理解为“案例、选项”。\n\n### switch 语句的执行流程\n\n流程图如下：\n\n![](http://img.smyhvae.com/20190815_1501.png)\n\n执行流程如下：\n\n（1）首先，计算出表达式的值，和各个 case 依次比较，一旦有对应的值，就会执行相应的语句，在执行的过程中，遇到 break 就会结束。\n\n（2）然后，如果所有的 case 都和表达式的值不匹配，就会执行 default 语句体部分。\n\n### switch 语句的结束条件【非常重要】\n\n-   情况 a：遇到 break 就结束（而不是遇到 default 就结束）。因为 break 在此处的作用是，立即结束并退出整个 switch 语句。\n-   情况 b：执行到程序的末尾就结束。\n\n我们稍后讲 case穿透的时候，你就会明白其中的奥妙了。\n\n### 注意点\n\n1、switch 后面的括号里可以是变量、常量、表达式， 通常是一个**变量**（一般做法是：先把表达式存放到变量中）。\n\ncase 后面的值可以是变量、常量、表达式。\n\n2、**case的判断逻辑是`===`，不是`==`**。因此，字符串`'6'`和 数字 `6` 是不一样的。\n\n举例 1：\n\n```js\nlet msg = 'notice';\n\nswitch (msg) {\n    case 'notice':\n        console.log('提示');\n        break;\n    case 'warning':\n        console.log('警告');\n        break;\n    case 'error':\n        console.log('错误');\n        break;\n    default:\n        console.log('默认文案');\n        break;\n}\n```\n\n举例 2：（case 后面的是表达式）\n\n```js\nlet age = 28;\n\nswitch (true) {\n    case age < 18:\n        console.log('未成年人');\n        break;\n    case age >= 18 && age <= 65:\n        console.log('还能干活儿');\n        break;\n    case age > 65:\n        console.log('该退休了');\n        break;\n    default:\n        console.log('默认文案');\n        break;\n}\n```\n\n上方代码解释：由于 switch 里的值是 true，所以，在众多的 case 语句中，会去匹配第一个符合 `case true`的语句，然后命中这条语句。\n\n3、default不一定要写在最后面。 switch 中的 default 无论放到什么位置，都会等到所有case 都不匹配再执行。default 也可以省略。\n\n### case 穿透\n\nswitch 语句中的`break`可以省略，但一般不建议（对于新手而言）。否则结果可能不是你想要的，会出现一个现象：**case 穿透**。\n\n当然，如果你能利用好 case 穿透，会让代码写得十分优雅。\n\n**举例 1**：（case 穿透的情况）\n\n```javascript\nconst num = 4;\n\n//switch判断语句\nswitch (num) {\n    case 1:\n        console.log('星期一');\n        break;\n    case 2:\n        console.log('星期二');\n        break;\n    case 3:\n        console.log('星期三');\n        break;\n    case 4:\n        console.log('星期四');\n    //break;\n    case 5:\n        console.log('星期五');\n    //break;\n    case 6:\n        console.log('星期六');\n        break;\n    case 7:\n        console.log('星期日');\n        break;\n    default:\n        console.log('你输入的数据有误');\n        break;\n}\n```\n\n上方代码的运行结果，可能会令你感到意外：\n\n```\n星期四\n星期五\n星期六\n```\n\n上方代码的解释：因为在 case 4 和 case 5 中都没有 break，那语句走到 case 6 的 break 才会停止。\n\n**举例 2**：\n\n```javascript\n//switch判断语句\nconst number = 5;\n\nswitch (number) {\n    default:\n        console.log('我是defaul语句');\n    // break;\n    case 2:\n        console.log('第二个呵呵:' + number);\n    //break;\n    case 3:\n        console.log('第三个呵呵:' + number);\n        break;\n    case 4:\n        console.log('第四个呵呵:' + number);\n        break;\n}\n```\n\n上方代码的运行结果，你也许会意外：\n\n```\n我是defaul语句\n第二个呵呵:5\n第三个呵呵:5\n```\n\n上方代码的解释：代码走到 default 时，因为没有遇到 break，所以会继续往下走，直到遇见 break 或者走到程序的末尾。 从这个例子可以看出：switch 语句的结束与 default 的顺序无关。\n\n## switch 语句的实战举例：替换 if 语句\n\n我们实战开发中，经常需要根据接口的返回码 retCode ，来让前端做不同的展示。\n\n这种场景是业务开发中经常出现的，请一定要掌握。然而，很多人估计会这么写：\n\n### 写法 1（不推荐。这种写法太挫了）\n\n```javascript\nlet retCode = 1003; // 返回码 retCode 的值可能有很多种情况\n\nif (retCode == 0) {\n    alert('接口联调成功');\n} else if (retCode == 101) {\n    alert('活动不存在');\n} else if (retCode == 103) {\n    alert('活动未开始');\n} else if (retCode == 104) {\n    alert('活动已结束');\n} else if (retCode == 1001) {\n    alert('参数错误');\n} else if (retCode == 1002) {\n    alert('接口频率限制');\n} else if (retCode == 1003) {\n    alert('未登录');\n} else if (retCode == 1004) {\n    alert('（风控用户）提示 活动太火爆啦~军万马都在挤，请稍后再试');\n} else {\n    // 其他异常返回码\n    alert('系统君失联了，请稍候再试');\n}\n```\n\n如果你是按照上面的 `if else`的方式来写各种条件判断，说明你的代码水平太初级了，会被人喷的，千万不要这么写。这种写法，容易导致**嵌套太深，可读性很差**。\n\n那要怎么改进呢？继续往下看。\n\n### 写法 2（推荐。通过 return 的方式，将上面的写法进行改进）\n\n```javascript\nlet retCode = 1003; // 返回码 retCode 的值可能有很多种情况\nhandleRetCode(retCode);\n\n// 方法：根据接口不同的返回码，处理前端不同的显示状态\nfunction handleRetCode(retCode) {\n    if (retCode == 0) {\n        alert('接口联调成功');\n        return;\n    }\n\n    if (retCode == 101) {\n        alert('活动不存在');\n        return;\n    }\n\n    if (retCode == 103) {\n        alert('活动未开始');\n        return;\n    }\n\n    if (retCode == 104) {\n        alert('活动已结束');\n        return;\n    }\n\n    if (retCode == 1001) {\n        alert('参数错误');\n        return;\n    }\n\n    if (retCode == 1002) {\n        alert('接口频率限制');\n        return;\n    }\n\n    if (retCode == 1003) {\n        alert('未登录');\n        return;\n    }\n\n    if (retCode == 1004) {\n        alert('（风控用户）提示 活动太火爆啦~军万马都在挤，请稍后再试');\n        return;\n    }\n\n    // 其他异常返回码\n    alert('系统君失联了，请稍候再试');\n    return;\n}\n```\n\n上面的写法 2，是比较推荐的写法：直接通过 return 的方式，让 function 里的代码不再继续往下走，这就达到目的了。对了，因为要用到 return ，所以整段代码是封装到一个 function 里的。\n\n如果你以后看到有前端小白采用的是**写法 1**，请一定要把**写法 2**传授给他：不需要那么多的 if else，直接用 return 返回就行了。\n\n### 写法 3（推荐。将 if else 改为 switch）\n\n```javascript\nlet retCode = 1003; // 返回码 retCode 的值可能有很多种情况\n\nswitch (retCode) {\n    case 0:\n        alert('接口联调成功');\n        break;\n    case 101:\n        alert('活动不存在');\n        break;\n\n    case 103:\n        alert('活动未开始');\n        break;\n\n    case 104:\n        alert('活动已结束');\n        break;\n\n    case 1001:\n        alert('参数错误');\n        break;\n\n    case 1002:\n        alert('接口频率限制');\n        break;\n\n    case 1003:\n        alert('未登录');\n        break;\n\n    case 1004:\n        alert('（风控用户）提示 活动太火爆啦~军万马都在挤，请稍后再试');\n        break;\n\n    // 其他异常返回码\n    default:\n        alert('系统君失联了，请稍候再试');\n        break;\n}\n```\n\n在实战开发中，方式 3 是非常推荐的写法，甚至比方式 2 还要好。我们尽量不要写太多的 if 语句，避免代码嵌套过深。\n\n### switch 语句的优雅写法：适时地去掉 break\n\n我们先来看看下面这段代码：（不推荐）\n\n```javascript\nlet day = 2;\n\nswitch (day) {\n    case 1:\n        console.log('work');\n        break;\n\n    case 2:\n        console.log('work');\n        break;\n\n    case 3:\n        console.log('work');\n        break;\n\n    case 4:\n        console.log('work');\n        break;\n\n    case 5:\n        console.log('work');\n        break;\n\n    case 6:\n        console.log('relax');\n        break;\n\n    case 7:\n        console.log('relax');\n        break;\n\n    default:\n        break;\n}\n```\n\n上面的代码，咋一看，好像没啥毛病。但你有没有发现，重复代码太多了？\n\n实战开发中，凡是有重复的地方，我们都必须要想办法简化。写代码就是在不断重构的过程。\n\n上面的代码，可以改进如下：（推荐，非常优雅）\n\n```javascript\nlet day = 2;\n\nswitch (day) {\n    case 1:\n    case 2:\n    case 3:\n    case 4:\n    case 5:\n        console.log('work');\n        break; // 在这里放一个 break\n\n    case 6:\n    case 7:\n        console.log('relax');\n        break; // 在这里放一个 break\n\n    default:\n        break;\n}\n}\n```\n\n你没看错，就是上面的这种写法，能达到同样的效果，非常优雅。\n\n小白可能认为这样的写法可读性不强，所以说他是小白。我可以明确告诉你，改进后的这种写法，才是最优雅的、最简洁、可读性最好的。\n\n## 补充\n\n### if 和 switch如何选择\n\n如果是对区间进行判断，则建议用 if。如果是对几个固定的值进行判断，那么，数量少的话用 if，数量多的话用switch。\n\n### 用 return 代替 if else\n\n业务场景举例：\n\n我们在实战业务中涉及到调接口时，一般会这样做：\n\n-   接口返回码为 0 时，前端 resolve。\n\n-   接口返回未登录时，前端跳转到登录页面。\n\n-   接口返回其他情况，或者无返回时，前端 reject。\n\n写法 1、if else 的写法：（不推荐）\n\n```js\nif (res) {\n    if (+res.retCode == 0) {\n        resolve(res);\n    } else if (+res.retCode == 8888) {\n        goLogin();\n    } else {\n        reject(res);\n    }\n} else {\n    reject();\n}\n```\n\n写法 2、 return 的写法：（推荐）\n\n```js\nif (!res || +res.retCode !== 0) {\n    if (+res.retCode === 8888) {\n        // 未登录\n        goLogin();\n        return;\n    }\n    reject(res);\n    return;\n}\nresolve(res);\n```\n\n备注：如果你没学过 Promise，这个例子可以先不看。等以后学了 Promise 再回来看就很容易明白了。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "04-JavaScript基础/12-流程控制语句：循环结构（for和while）.md",
    "content": "---\ntitle: 12-流程控制语句：循环结构（for和while）\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 前言\n\n循环语句：通过循环语句可以反复执行一段代码多次。\n\n## for 循环\n\n### for 循环的语法\n\n语法：\n\n```\nfor(①初始化表达式; ②条件表达式; ④更新表达式){\n\t③语句...\n}\n```\n\n执行流程：\n\n```\n①执行初始化表达式，初始化变量（初始化表达式只会执行一次）\n\n②执行条件表达式，判断是否执行循环：\n\t如果为true，则执行循环③\n\t如果为false，终止循环\n\n④执行更新表达式，更新表达式执行完毕继续重复②\n```\n\nfor 循环举例：\n\n```javascript\nfor (let i = 1; i <= 100; i++) {\n    console.log(i);\n}\n```\n\n上方代码的解释：i 是循环变量，1 是初始值，i<100是执行条件，i++是步长。\n\n### for 循环举例\n\n```javascript\nfor (let i = 1; i < 13; i = i + 4) {\n    console.log(i);\n}\n```\n\n上方代码的遍历步骤：\n\n```\n程序一运行，将执行let i = 1;这条语句， 所以i的值是1。\n然后程序会验证一下i < 13是否满足，1<13是真，所以执行一次循环体（就是大括号里面的语句）。\n执行完循环体之后，会执行i=i+4这条语句，所以i的值，是5。\n\n程序会会验证一下i < 13是否满足，5<13是真，所以执行一次循环体（就是大括号里面的语句）。\n执行完循环体之后，会执行i=i+4这条语句，所以i的值，是9。\n\n程序会会验证一下i < 13是否满足，9<13是真，所以执行一次循环体（就是大括号里面的语句）。\n执行完循环体之后，会执行i=i+4这条语句，所以i的值，是13。\n\n程序会会验证一下i < 13是否满足，13<13是假，所以不执行循环体了，将退出循环。\n\n最终输出输出结果为：1、5、9\n```\n\n接下来做几个题目。\n\n**题目 1**：\n\n```javascript\nfor (let i = 1; i < 10; i = i + 3) {\n    i = i + 1;\n    console.log(i);\n}\n```\n\n输出结果：2、6、10\n\n**题目 2**：\n\n```javascript\nfor (let i = 1; i <= 10; i++) {}\nconsole.log(i);\n```\n\n输出结果：11\n\n**题目 3**：\n\n```javascript\nfor (let i = 1; i < 7; i = i + 3) {}\nconsole.log(i);\n```\n\n输出结果：7\n\n**题目 4**：\n\n```javascript\nfor (let i = 1; i > 0; i++) {\n    console.log(i);\n}\n```\n\n死循环。\n\n## while 循环语句\n\n### while 循环\n\n语法：\n\n```javascript\nwhile(条件表达式){\n\t语句...\n}\n```\n\n执行流程：\n\n```\nwhile语句在执行时，先对条件表达式进行求值判断：\n\n\t如果值为true，则执行循环体：\n\t\t循环体执行完毕后，继续对表达式进行判断\n\t\t如果为true，则继续执行循环体，以此类推\n\n\t如果值为false，则终止循环\n```\n\n**如果有必要的话，我们可以使用 break 来终止循环**。\n\n### do...while 循环\n\n语法：\n\n```javascript\ndo{\n\t语句...\n}while(条件表达式)\n\n```\n\n执行流程：\n\n```\ndo...while语句在执行时，会先执行循环体：\n\n\t循环体执行完毕以后，再对while后的条件表达式进行判断：\n\t\t如果结果为true，则继续执行循环体，执行完毕继续判断，以此类推\n\t\t如果结果为false，则终止循环\n\n```\n\n### while 循环和 do...while 循环的区别\n\n这两个语句的功能类似，不同的是：\n\n-   while：先判断后执行。只有条件表达式为真，才会执行循环体。\n-   do...while：先执行后判断。无论条件表达式是否为真，循环体至少会被执行一次。\n\n\n\n### while 循环举例\n\n题目：假如投资的年利率为 5%，试求从 1000 块增长到 5000 块，需要花费多少年？\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <script>\n            /*\n             * 假如投资的年利率为5%，试求从1000块增长到5000块，需要花费多少年\n             */\n\n            //定义一个变量，表示当前的钱数\n            let money = 1000;\n\n            //定义一个计数器\n            let count = 0;\n\n            //定义一个while循环来计算每年的金额\n            while (money < 5000) {\n                money *= 1.05;\n\n                //使count自增\n                count++;\n            }\n\n            console.log(money);\n            console.log('一共需要' + count + '年');\n        </script>\n    </body>\n</html>\n```\n\n打印结果：\n\n```\n5003.18854203379\n\n一共需要33年\n```\n\n另外，你也可以自己算一下，假如投资的年利率为 5%，从 1000 块增长到 1 万块，需要花费 48 年：\n\n```\n10401.269646942128\n一共需要48年\n```\n\n## break 和 continue\n\n> 这个知识点非常重要。\n\n### break\n\n-   break 可以用来退出 switch 语句或退出**整个**循环语句（循环语句包括 for 循环、while 循环。不包括 if。单独的 if 语句里不能用 break 和 continue，否则会报错）。\n\n-   break 会立即终止离它**最近**的那个循环语句。\n\n-   可以为循环语句创建一个 label，来标识当前的循环（格式：label:循环语句）。使用 break 语句时，可以在 break 后跟着一个 label，这样 break 将会结束指定的循环，而不是最近的。\n\n**举例 1**：通过 break 终止循环语句\n\n```javascript\nfor (let i = 0; i < 5; i++) {\n    console.log('i的值:' + i);\n    if (i == 2) {\n        break; // 注意，虽然在 if 里 使用了 break，但这里的 break 是服务于外面的 for 循环。\n    }\n}\n```\n\n打印结果：\n\n```\ni的值:0\ni的值:1\ni的值:2\n```\n\n**举例 2**：label 的使用\n\n```javascript\nouter: for (let i = 0; i < 5; i++) {\n    console.log('外层循环 i 的值：' + i);\n    for (let j = 0; j < 5; j++) {\n        break outer; // 直接跳出outer所在的外层循环（这个outer是我自定义的label）\n        console.log('内层循环 j 的值:' + j);\n    }\n}\n```\n\n打印结果：\n\n```\n外层循环 i 的值：0\n```\n\n### continue\n\n-   continue 只能用于循环语句（包括 for 循环、while 循环，不包括 if。单独的 if 语句里不能用 break 和 continue，否则会报错）。可以用来跳过**当次**循环，继续下一次循环。\n\n-   同样，continue 默认只会离他**最近**的循环起作用。\n\n-   同样，如果需要跳过指定的当次循环，可以使用 label 标签。\n\n举例：\n\n```javascript\nfor (let i = 0; i < 10; i++) {\n    if (i % 2 == 0) {\n        continue;\n    }\n    console.log('i的值:' + i);\n}\n```\n\n打印结果：\n\n```\ni的值:1\n\ni的值:3\n\ni的值:5\n\ni的值:7\n\ni的值:9\n```\n\n## 各种练习\n\n### 练习一：质数相关\n\n**题目**：在页面中接收一个用户输入的数字，并判断该数是否是质数。\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <script type=\"text/javascript\">\n            /*\n            质数：只能被1和它自身整除的数，1不是质数也不是合数，质数必须是大于1的自然数。\n         */\n\n            const num = prompt('请输入一个大于1的整数:');\n\n            //判断这个值是否合法\n            if (num <= 1) {\n                alert('该值不合法！');\n            } else {\n                //先用flag标志位，来保存当前的数的状态\n                //默认当前num是质数\n                let flag = true;\n\n                //判断num是否是质数\n                //获取2-num之间的数\n                for (let i = 2; i < num; i++) {\n                    //console.log(i);\n                    //判断num是否能被i整除\n                    if (num % i == 0) {\n                        //一旦发现：如果num能被i整除，则说明num一定不是质数，\n                        //此时：设置flag为false，然后跳出 for 循环\n                        flag = false;\n                        break;\n                    }\n                }\n\n                //如果num是质数则输出\n                if (flag) {\n                    alert(num + '是质数！！！');\n                } else {\n                    alert('这个不是质数');\n                }\n            }\n        </script>\n    </head>\n\n    <body></body>\n</html>\n```\n\n### 练习二：质数相关\n\n**题目**：打印 1~100 之间的所有质数\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <script type=\"text/javascript\">\n            /*\n             * 打印出1-100之间所有的质数\n             */\n\n            //打印2-100之间所有的数\n            for (let i = 2; i <= 100; i++) {\n                //创建一个布尔值，用来保存结果，默认i是质数\n                let flag = true;\n\n                //判断i是否是质数\n                //获取到2-i之间的所有的数\n                for (let j = 2; j < i; j++) {\n                    //判断i是否能被j整除\n                    if (i % j == 0) {\n                        //如果进入判断则证明i不是质数,修改flag值为false\n                        flag = false;\n                    }\n                }\n\n                //如果是质数，则打印i的值\n                if (flag) {\n                    console.log(i);\n                }\n            }\n        </script>\n    </head>\n\n    <body></body>\n</html>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20181229_1415.png)\n\n### 练习三：99 乘法表\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <style type=\"text/css\">\n            body {\n                width: 2000px;\n            }\n\n            span {\n                display: inline-block;\n                width: 80px;\n            }\n        </style>\n        <script type=\"text/javascript\">\n            /*\n             * 1.打印99乘法表\n             *   1*1=1\n             *   1*2=2 2*2=4\n             *   1*3=3 2*3=6 3*3=9\n             *   1*4=4 2*4=8 3*4=12 4*4=16\n             *                      ....9*9=81\n             *\n             * 2.打印出1-100之间所有的质数\n             */\n\n            //创建外层循环，用来控制乘法表的高度\n            for (let i = 1; i <= 9; i++) {\n                //创建一个内层循环来控制图形的宽度\n                for (let j = 1; j <= i; j++) {\n                    document.write('<span>' + j + '*' + i + '=' + i * j + '</span>');\n                }\n\n                //输出一个换行\n                document.write('<br />');\n            }\n        </script>\n    </head>\n\n    <body></body>\n</html>\n```\n\n页面效果：\n\n![](http://img.smyhvae.com/20181229_1410.png)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "04-JavaScript基础/13-对象简介.md",
    "content": "---\ntitle: 13-对象简介\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 对象简介\n\n### 对象的概念\n\n在 JavaScript 中，对象是一组**无序**的相关属性和方法的集合。\n\n**对象的作用是：封装信息**。比如 Student 类里可以封装学生的姓名、年龄、成绩等。\n\n对象具有**特征**（属性）和**行为**（方法）。\n\n### 对象包括哪些数据类型\n\n我们知道，JS 中的数据类型，包括以下几种：\n\n-   **基本数据类型（值类型）**：String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。\n\n-   **引用数据类型（引用类型）**：Object 对象。\n\n只要不是那七种基本数据类型，就全都是对象。对象属于一种复合的数据类型，在对象中可以保存多个不同数据类型的属性。\n\n### 对象的分类\n\n1、内置对象：\n\n-   由 ES 标准中定义的对象，在任何的 ES 的实现中都可以使用。\n\n-   比如：Object、Math、Date、String、Array、Number、Boolean、Function 等。\n\n2、宿主对象：\n\n-   由 JS 的运行环境提供的对象，目前来讲主要指由浏览器提供的对象。\n\n-   比如 BOM、DOM，比如`console`、`document`。\n\n3、自定义对象：\n\n-   由开发人员自己创建的对象。\n\n通过 new 关键字创建出来的对象实例，都是属于对象类型。\n\n## 自定义对象\n\n### 为什么需要自定义对象\n\n保存一个值时，可以使用**变量**，保存多个值（一组值）时，可以使用**数组**。\n\n比如，如果要保存一个人的信息，通过数组的方式可以这样保存：\n\n```javascript\nconst arr = ['王二', 35, '男', '180'];\n```\n\n上面这种表达方式比较乱。而如果用 JS 中的**自定义对象**来表达，**结构会更清晰**。如下：\n\n```javascript\nconst person = {\n    name: 'qianguyihao',\n    age: 30,\n    sex: '男',\n    favor: ['阅读', '羽毛球'],\n    sayHi: function () {\n        console.log('qianguyihao');\n    },\n};\n```\n\n由此可见，自定义对象里面的属性均是**键值对（key: value）**，表示属性和值的映射关系：\n\n-   键/属性：属性名。\n\n-   值：属性值，可以是任意类型的值（数字类型、字符串类型、布尔类型，函数类型等）。\n\n### 自定义对象的语法\n\n语法如下：\n\n```js\nconst obj = {\n    key: value,\n    key: value,\n    key: value,\n};\n```\n\nkey 和 value 之间用冒号分隔，每组 key:vaue 之间用逗号分隔，最后一对 key:value 的末尾可以写逗号，也可以不写逗号。\n\n问：对象的属性名是否需要加引号？\n\n答：如果属性名不符合 JS 标识符的命名规范，则需要用引号包裹。比如：\n\n```js\nconst person = {\n    'my-name': 'qianguyihao',\n};\n```\n\n补充：其实，JS 的内置对象、宿主对象，底层也是通过自定义对象的形式（也就是键值对的形式）进行封装的。\n\n## 对象的属性值补充\n\n### 什么叫对象的方法【重要】\n\n对象的属性值可以是任意的数据类型，也可以是个**函数**（也称之为方法）。换而言之，**如果对象的属性值是函数，则这个函数可被称之为对象的“方法”**。\n\n```javascript\nconst obj = new Object();\nobj.sayName = function () {\n    console.log('qianguyihao');\n};\n\n// 没加括号，就是获取方法\nconsole.log(obj.sayName);\nconsole.log('-----------');\n// 加了括号，就是调用方法。即：执行函数内容，并执行函数体的内容\nconsole.log(obj.sayName());\n```\n\n打印结果：\n\n![](https://img.smyhvae.com/20221014_1130.png)\n\n### 对象中的属性值，也可以是一个对象\n\n举例：\n\n```javascript\n//创建对象 obj1\nvar obj1 = new Object();\nobj1.test = undefined;\n\n//创建对象 obj2\nvar obj2 = new Object();\nobj2.name = 'qianguyihao';\n\n//将整个 obj2 对象，设置为 obj1 的属性\nobj1.test = obj2;\n\nconsole.log(obj1.test.name);\n```\n\n打印结果为：qianguyihao\n\n## 传值和传址的区别\n\n### 对象保存在哪里\n\n1、基本数据类型的值直接保存在**栈内存**中，变量与变量之间是独立的，值与值之间是独立的，修改一个变量不会影响其他的变量。\n\n2、对象是保存到**堆内存**中的，每创建一个新的对象，就会在堆内存中开辟出一个新的空间。变量保存的是对象的内存地址（对象的引用）。换而言之，对象的值是保存在**堆内存**中的，而对象的引用（即变量）是保存在**栈内存**中的。\n\n**如果两个变量保存的是同一个对象引用，当一个通过一个变量修改属性时，另一个也会受到影响**。这句话很重要，我们来看看下面的例子。\n\n### 传值\n\n代码举例：\n\n```js\nlet a = 1;\n\nlet b = a; // 将 a 赋值给 b\n\nb = 2; // 修改 b 的值\n```\n\n上方代码中，当我修改 b 的值之后，a 的值并不会发生改变。这个大家都知道。我们继续往下看。\n\n### 传址（一个经典的例子）\n\n代码举例：\n\n```javascript\nvar obj1 = new Object();\nobj1.name = '孙悟空';\n\nvar obj2 = obj1; // 将 obj1 的地址赋值给 obj2。从此， obj1 和 obj2 指向了同一个堆内存空间\n\n//修改obj2的name属性\nobj2.name = '猪八戒';\n```\n\n上面的代码中，当我修改 obj2 的 name 属性后，会发现，obj1 的 name 属性也会被修改。因为 obj1 和 obj2 指向的是堆内存中的同一个地址。\n\n这个例子要尤其注意，实战开发中，很容易忽略。\n\n对于引用类型的数据，赋值相当于地址拷贝，a、b 指向了同一个堆内存地址。所以改了 b，a 也会变；本质上 a、b 就是一个东西。\n\n如果你打算把引用类型 A 的值赋值给 B，让 A 和 B 相互不受影响的话，可以通过 Object.assign() 来复制对象。效果如下：\n\n```js\nvar obj1 = { name: '孙悟空' };\n\n// 复制对象：把 obj1 赋值给 obj3。两者之间互不影响\nvar obj3 = Object.assign({}, obj1);\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/14-基本包装类型.md",
    "content": "---\ntitle: 14-基本包装类型\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 基本数据类型不能绑定属性和方法\n\n属性和方法只能添加给对象，不能添加给基本数据类型。我们拿字符串来举例。\n\n**1、基本数据类型：**\n\n基本数据类型`string`是**无法绑定属性和方法**的。\n\n```javascript\nvar str = 'qianguyihao';\n\nstr.aaa = 12;\nconsole.log(typeof str); //打印结果为：string\nconsole.log(str.aaa); //打印结果为：undefined\n```\n\n上方代码中，当我们尝试打印`str.aaa`的时候，会发现打印结果为：undefined。也就是说，不能给 `string` 绑定属性和方法。\n\n当然，我们可以打印 str.length、str.indexOf(\"m\")等等。因为这两个方法的底层做了数据类型转换（**临时**将 `string` 字符串转换为 `String` 对象，然后再调用内置方法），也就是我们在下一段将要讲到的**包装类**。\n\n**2、引用数据类型：**\n\n引用数据类型`String`是可以绑定属性和方法的。如下：\n\n```javascript\nvar strObj = new String('smyhvae');\nstrObj.aaa = 123;\nconsole.log(strObj);\nconsole.log(typeof strObj); //打印结果：Object\nconsole.log(strObj.aaa);\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180202_1351.png)\n\n内置对象 Number 也有一些自带的方法，比如：\n\n-   Number.MAX_VALUE;\n\n-   Number.MIN_VALUE;\n\n内置对象 Boolean 也有一些自带的方法，但是用的不多。\n\n### 基本包装类型\n\n### 介绍\n\n我们都知道，JS 中的数据类型包括以下几种。\n\n-   基本数据类型：String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。\n\n-   引用数据类型：Object 对象。\n\nJS 为我们提供了三个**基本包装类**：\n\n-   String()：将基本数据类型字符串，转换为 String 对象。\n\n-   Number()：将基本数据类型的数字，转换为 Number 对象。\n\n-   Boolean()：将基本数据类型的布尔值，转换为 Boolean 对象。\n\n通过上面这这三个包装类，我们可以**将基本数据类型的数据转换为对象**。\n\n代码举例：\n\n```javascript\nlet str1 = 'qianguyihao';\nlet str2 = new String('qianguyihao');\n\nlet num = new Number(3);\n\nlet bool = new Boolean(true);\n\nconsole.log(typeof str1); // 打印结果：string\nconsole.log(typeof str2); // 注意，打印结果：object\n```\n\n**需要注意的是**：我们在实际应用中一般不会使用基本数据类型的**对象**。如果使用基本数据类型的对象，在做一些比较时可能会带来一些**不可预期**的结果。\n\n比如说：\n\n```javascript\nvar boo1 = new Boolean(true);\nvar boo2 = new Boolean(true);\n\nconsole.log(boo1 === boo2); // 打印结果竟然是：false\n```\n\n再比如说：\n\n```javascript\nvar boo3 = new Boolean(false);\n\nif (boo3) {\n    console.log('qianguyihao'); // 这行代码竟然执行了\n}\n```\n\n### 基本包装类型的作用\n\n当我们对一些基本数据类型的值去调用属性和方法时，JS引擎会**临时使用包装类将基本数据类型转换为引用数据类型**（即“隐式类型转换”），这样的话，基本数据类型就有了属性和方法，然后再调用对象的属性和方法；调用完以后，再将其转换为基本数据类型。\n\n举例：\n\n```js\nvar str = 'qianguyihao';\nconsole.log(str.length); // 打印结果：11\n```\n\n比如，上面的代码，执行顺序是这样的：\n\n```js\n// 步骤（1）：把简单数据类型 string 转换为 引用数据类型  String，保存到临时变量中\nvar temp = new String('qianguyihao');\n\n// 步骤（2）：把临时变量的值 赋值给 str\nstr = temp;\n\n//  步骤（3）：销毁临时变量\ntemp = null;\n\n```\n\n## 在底层，字符串以字符数组的形式保存\n\n在底层，字符串是以字符数组的形式保存的。代码举例：\n\n```javascript\nvar str = 'smyhvae';\nconsole.log(str.length); // 获取字符串的长度\nconsole.log(str[2]); // 获取字符串中的第3个字符（下标为2的字符）\n```\n\n上方代码中，`smyhvae`这个字符串在底层是以`[\"s\", \"m\", \"y\", \"h\", \"v\", \"a\", \"e\"]`的形式保存的。因此，我们既可以获取字符串的长度，也可以获取指定索引 index 位置的单个字符。这很像数组中的操作。\n\n再比如，String 对象的很多内置方法，也可以直接给字符串用。此时，也是临时将字符串转换为 String 对象，然后再调用内置方法。\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/15-内置对象 String：字符串的常见方法.md",
    "content": "---\ntitle: 15-内置对象 String：字符串的常见方法\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 内置对象简介\n\n> JavaScript 中的对象分为3种：自定义对象、内置对象、浏览器对象。\n\n> 前面两种对象：是JS的基础内容，属于 ECMAScript； 第三个浏览器对象：属于JS独有，即 JS 内置的API。\n\n**内置对象**：就是指这个语言自带的一些对象，供开发者使用，这些对象提供了一些常用或者基本而必要的功能（属性和方法）。\n\n内置对象最大的优点就是帮助我们快速开发。\n\n**JavaScript的内置对象**：\n\n| 内置对象 | 对象说明 |\n|:-------------|:-------------|\n|  Arguments | 函数参数集合|\n|  Array | 数组|\n|  Boolean | 布尔对象|\n|  Math | 数学对象|\n|  Date | 日期时间|\n|  Error | 异常对象|\n|  Function | 函数构造器|\n|  Number | 数值对象|\n|  Object | 基础对象|\n|  RegExp | 正则表达式对象|\n|  String | 字符串对象|\n\n\n\n## 字符串前言\n\n> 在日常开发中，String 对象（字符串对象）的使用频率是非常高的。所以有必要详细介绍。\n\n需要注意的是：**字符串的所有方法，都不会改变原字符串**（字符串的不可变性），操作完成后会返回一个新的值。\n\n字符串的常见方法如下。\n\n## 查找字符串\n\n### 1、indexOf()/lastIndexOf()：获取字符串中指定内容的索引\n\n> 这个方法，是使用频率最高的一个方法。\n\n\n**语法 1**：\n\n```javascript\n索引值 = str.indexOf(想要查询的字符串);\n```\n\n备注：`indexOf()` 是从前向后查找字符串的位置。同理，`lastIndexOf()`是从后向前寻找。\n\n**解释**：可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容，则会返回其**第一次出现**的索引；如果没有找到指定的内容，则返回 -1。\n\n因此可以得出一个重要技巧：\n\n-   **如果获取的索引值为 0，说明字符串是以查询的参数为开头的**。\n\n-   如果获取的索引值为-1，说明这个字符串中没有指定的内容。\n\n举例 1：(查找单个字符)\n\n```javascript\nconst str = 'abcdea';\n\n//给字符查索引(索引值为0,说明字符串以查询的参数为开头)\nconsole.log(str.indexOf('c'));\nconsole.log(str.lastIndexOf('c'));\n\nconsole.log(str.indexOf('a'));\nconsole.log(str.lastIndexOf('a'));\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180202_1420.png)\n\n举例 2：（查找字符串）\n\n```js\nconst name = 'qianguyihao';\n\nconsole.log(name.indexOf('yi')); // 打印结果：6\n```\n\n**语法 2**：\n\n这个方法还可以指定第二个参数，用来指定查找的**起始位置**。语法如下：\n\n```javascript\n索引值 = str.indexOf(想要查询的字符串, [起始位置]);\n```\n\n举例 3：（两个参数时，需要特别注意）\n\n```javascript\nvar str = 'qianguyihao';\nresult = str.indexOf('a', 3); // 从下标为3的位置开始查找 'a'这个字符 【重要】\n\nconsole.log(result); // 打印结果：9\n```\n\n上方代码中，`indexOf()`方法中携带了两个参数，具体解释请看注释。\n\n### indexOf 举例\n\n**案例**：查找字符串\"qianguyihao\"中，所有 `a` 出现的位置以及次数。\n\n思路：\n\n（1）先查找第一个 a 出现的位置。\n\n（2）只要 indexOf 返回的结果不是 -1 就继续往后查找。\n\n（3）因为 indexOf 只能查找到第一个，所以后面的查找，可以利用第二个参数，在当前索引加 1，从而继续查找。\n\n代码实现：\n\n```js\nvar str = 'qianguyihao';\nvar index = str.indexOf('a');\nvar num = 0;\nwhile (index !== -1) {\n    console.log(index);\n    num++; // 每打印一次，就计数一次\n    index = str.indexOf('a', index + 1);\n}\n\nconsole.log('a 出现的次数是: ' + num);\n```\n\n\n### 2、search()：获取字符串中指定内容的索引（参数里一般是正则）\n\n\n**语法**：\n\n```javascript\n索引值 = str.search(想要查找的字符串);\n索引值 = str.search(正则表达式);\n\n```\n\n备注：`search()` 方法里的参数，既可以传字符串，也可以传正则表达式。\n\n**解释**：可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容，则会返回其**第一次出现**的索引；如果没有找到指定的内容，则返回 -1。\n\n\n举例：\n\n```js\nconst name = 'qianguyihao';\n\nconsole.log(name.search('yi')); // 打印结果：6\nconsole.log(name.search(/yi/i)); // 打印结果：6\n```\n\n备注：上方的`/yi/i`采用的是正则表达式的写法，意思是，让 name去匹配字符`yi`，忽略大小写。我们在后面会专门介绍正则表达式。\n\n\n### 3、includes()：字符串中是否包含指定的内容\n\n**语法**：\n\n```js\n布尔值 = str.includes(想要查找的字符串, [position]);\n```\n\n**解释**：判断一个字符串中是否含有指定内容。如果字符串中含有该内容，则会返回 true；否则返回 false。\n\n参数中的 `position`：如果不指定，则默认为0；如果指定，则规定了检索的起始位置。\n\n```js\nconst name = 'qianguyihao';\n\nconsole.log(name.includes('yi')); // 打印结果：true\nconsole.log(name.includes('haha')); // 打印结果：false\n\nconsole.log(name.includes('yi',7)); // 打印结果：false\n```\n\n\n### 4、startsWith()：字符串是否以指定的内容开头\n\n**语法**：\n\n```js\n布尔值 = str.startsWith(想要查找的内容, [position]);\n```\n\n**解释**：判断一个字符串是否以指定的子字符串开头。如果是，则返回 true；否则返回 false。\n\n**参数中的position**：\n\n- 如果不指定，则默认为0。\n\n- 如果指定，则规定了**检索的起始位置**。检索的范围包括：这个指定位置开始，直到字符串的末尾。即：[position, str.length)\n\n举例：\n\n```js\nconst name = 'abcdefg';\n\nconsole.log(name.startsWith('a')); // 打印结果：true\nconsole.log(name.startsWith('b')); // 打印结果：false\n\n// 因为指定了起始位置为3，所以是在 defg 这个字符串中检索。\nconsole.log(name.startsWith('d',3)); // 打印结果：true\nconsole.log(name.startsWith('c',3)); // 打印结果：false\n```\n\n### 5、endsWith()：字符串是否以指定的内容结尾\n\n**语法**：\n\n```js\n布尔值 = str.endsWith(想要查找的内容, [position]);\n```\n\n**解释**：判断一个字符串是否以指定的子字符串结尾。如果是，则返回 true；否则返回 false。\n\n**参数中的position**：\n\n- 如果不指定，则默认为 str.length。\n\n- 如果指定，则规定了**检索的结束位置**。检索的范围包括：从第一个字符串开始，直到这个指定的位置。即：[0, position)\n\n- 或者你可以这样简单理解：endsWith() 方法里的position，表示**检索的长度**。\n\n注意：startsWith() 和 endsWith()这两个方法，他们的 position 的含义是不同的，请仔细区分。\n\n举例：\n\n```js\nconst name = 'abcdefg';\n\nconsole.log(name.endsWith('g')); // 打印结果：true\nconsole.log(name.endsWith('f')); // 打印结果：false\n\n// 因为指定了截止位置为3，所以是在 abc 这个长度为3字符串中检索\nconsole.log(name.endsWith('c', 3)); // 打印结果：true\nconsole.log(name.endsWith('d', 3)); // 打印结果：false\n```\n\n注意看上方的注释。\n\n参考链接：[JavaScript endsWith()介绍](https://www.softwhy.com/article-2885-1.html)\n\n## 获取指定位置的字符\n\n### 1、charAt(index)\n\n语法：\n\n```javascript\n字符 = str.charAt(index);\n```\n\n解释：返回字符串指定位置的字符。这里的 `str.charAt(index)`和`str[index]`的效果是一样的。\n\n注意：字符串中第一个字符的下标是 0。如果参数 index 不在 [0, string.length) 之间，该方法将返回一个空字符串。\n\n**代码举例**：\n\n```javascript\nvar str = new String('smyhvae');\n\nfor (var i = 0; i < str.length; i++) {\n    console.log(str.charAt(i));\n}\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180202_1401.png)\n\n上面这个例子一般不用。一般打印数组和 json 的时候用索引，打印 String 不建议用索引。\n\n### 2、str[index]\n\n`str.charAt(index)`和`str[index]`的效果是一样的，不再赘述。区别在于：`str[index]`是 H5 标准里新增的特性。\n\n### 3、charCodeAt(index)\n\n语法：\n\n```javascript\n字符 = str.charCodeAt(index);\n```\n\n解释：返回字符串指定位置的字符的 Unicode 编码。不会修改原字符串。\n\n在实际应用中，通过这个方法，我们可以判断用户按下了哪个按键。\n\n**代码举例**：打印字符串的**占位长度**。\n\n提示：一个英文占一个位置，一个中文占两个位置。\n\n思路：判断该字符是否在 0-127 之间（在的话是英文，不在是非英文）。\n\n代码实现：\n\n```html\n<script>\n    //    sort();   底层用到了charCodeAt();\n\n    var str = 'I love my country!我你爱中国！';\n\n    //需求：求一个字符串占有几个字符位。\n    //思路；如果是英文，站一个字符位，如果不是英文占两个字符位。\n    //技术点：判断该字符是否在0-127之间。（在的话是英文，不在是非英文）\n    alert(getZFWlength(str));\n    alert(str.length);\n\n    //定义方法：字符位\n    function getZFWlength(string) {\n        //定义一个计数器\n        var count = 0;\n        for (var i = 0; i < string.length; i++) {\n            //对每一位字符串进行判断，如果Unicode编码在0-127，计数器+1；否则+2\n            if (string.charCodeAt(i) < 128 && string.charCodeAt(i) >= 0) {\n                count++;\n            } else {\n                count += 2;\n            }\n        }\n        return count;\n    }\n</script>\n```\n\n打印结果：\n\n```\n    30\n    24\n```\n\n从打印结果可以看出：字符串的长度是 24，但是却占了 30 个字符位（一个中文占两个字符位）。\n\n另外，sort()方法其实底层也是用到了 charCodeAt()，因为用到了 Unicode 编码。\n\n## 字符串截取\n\n\n### 1、slice()\n\n\n> slice() 方法用的最多。\n\n\n语法：\n\n```javascript\n新字符串 = str.slice(开始索引, 结束索引); //两个参数都是索引值。包左不包右。\n```\n\n解释：从字符串中截取指定的内容。不会修改原字符串，而是将截取到的内容返回。\n\n注意：上面的参数，包左不包右。参数举例如下：\n\n- `(2, 5)` 截取时，包左不包右。\n\n- `(2)` 表示**从指定的索引位置开始，截取到最后**。\n\n- `(-3)` 表示从倒数第三个开始，截取到最后。\n\n- `(1, -1)` 表示从第一个截取到倒数第一个。\n\n- `(5, 2)` 表示前面的大，后面的小，返回值为空。\n\n### 2、substring()\n\n语法：\n\n```javascript\n新字符串 = str.substring(开始索引, 结束索引); //两个参数都是索引值。包左不包右。\n```\n\n解释：从字符串中截取指定的内容。和`slice()`类似。\n\n`substring()`和`slice()`是类似的。但不同之处在于：\n\n- `substring()`不能接受负值作为参数。如果传递了一个**负值**，则默认使用 0。\n\n- `substring()`还会自动调整参数的位置，如果第二个参数小于第一个，则自动交换。比如说， `substring(1, 0)`相当于截取的是第一个字符。\n\n### 3、substr()\n\n语法：\n\n```javascript\n字符串 = str.substr(开始索引, 截取的长度);\n```\n\n解释：从字符串中截取指定的内容。不会修改原字符串，而是将截取到的内容返回。\n\n注意，这个方法的第二个参数**截取的长度**，不是结束索引。\n\n参数举例：\n\n- `(2,4)` 从索引值为 2 的字符开始，截取 4 个字符。\n\n- `(1)` 从指定位置开始，截取到最后。\n\n- `(-3)` 从倒数第几个开始，截取到最后。\n\n备注：ECMAscript 没有对 `substr()` 方法进行标准化，因此不建议使用它。\n\n\n\n## String.fromCharCode()\n\n`String.fromCharCode()`：根据字符的 Unicode 编码获取字符。\n\n代码举例：\n\n```javascript\nvar result1 = String.fromCharCode(72);\nvar result2 = String.fromCharCode(20013);\n\nconsole.log(result1); // 打印结果：H\nconsole.log(result2); // 打印结果：中\n```\n\n## concat()\n\n语法：\n\n```javascript\n    新字符串 = str1.concat(str2)； //连接两个字符串\n```\n\n解释：字符串的连接。\n\n这种方法基本不用，直接把两个字符串相加就好。\n\n是的，你会发现，数组中也有`concat()`方法，用于数组的连接。这个方法在数组中用得挺多的。\n\n代码举例：\n\n```javascript\nvar str1 = 'qiangu';\nvar str2 = 'yihao';\n\nvar result = str1.concat(str2);\nconsole.log(result); // 打印结果：qianguyihao\n```\n\n## split()：字符串转换为数组 【重要】\n\n语法：\n\n```javascript\n新的数组 = str.split(分隔符);\n```\n\n解释：通过指定的分隔符，将一个字符串拆分成一个**数组**。不会改变原字符串。\n\n备注：`split()`这个方法在实际开发中用得非常多。一般来说，从接口拿到的 json 数据中，经常会收到类似于`\"q, i, a, n\"`这样的字符串，前端需要将这个字符串拆分成`['q', 'i', 'a', 'n']`数组，这个时候`split()`方法就派上用场了。\n\n**代码举例 1**：\n\n```javascript\nvar str = 'qian, gu, yi, hao'; // 用逗号隔开的字符串\nvar array = str.split(','); // 将字符串 str 拆分成数组，通过逗号来拆分\n\nconsole.log(array); // 打印结果是数组：[\"qian\", \" gu\", \" yi\", \" hao\"]\n```\n\n**代码举例 2**：\n\n```javascript\n//split()方法：字符串变数组\nvar str3 = '千古壹号|qianguyihao|许嵩';\n\nconsole.log('结果1：' +str3.split()); // 无参数，表示：把整个字符串作为一个元素添加到数组中。\n\nconsole.log(str3.split('')); // 参数为空字符串，则表示：分隔字符串中每一个字符，分别添加到数组中\n\nconsole.log(str3.split('|')); // 参数为指定字符，表示：用 '|' 分隔字符串。此分隔符将不会出现在数组的任意一个元素中\n\nconsole.log(str3.split('许')); // 同上\n```\n\n打印结果：（都是数组）\n\n![](http://img.smyhvae.com/20200611_2050.png)\n\n\n\n\n\n## replace()\n\n语法：\n\n```javascript\n新的字符串 = str.replace(被替换的子串，新的子串);\n```\n\n解释：将字符串中的指定内容，替换为新的内容并返回。不会修改原字符串。\n\n注意：这个方法，默认只会替换第一个被匹配到的字符。如果要全局替换，需要使用正则。\n\n代码举例：\n\n```javascript\n//replace()方法：替换\nvar str2 = 'Today is fine day,today is fine day !';\nconsole.log(str2);\n\nconsole.log(str2.replace('today', 'tomorrow')); //只能替换第一个today\nconsole.log(str2.replace(/today/gi, 'tomorrow')); //这里用到了正则，才能替换所有的today\n```\n\n## repeat()：重复字符串\n\n语法：\n\n```js\nnewStr = str.repeat(重复的次数);\n```\n\n解释：将字符串重复指定的次数。会返回新的值，不会修改原字符串。\n\n举例1：\n\n```js\nconst name = 'qianguyihao';\n\nconsole.log(name.repeat(2)); // 打印内容：qianguyihaoqianguyihao\n```\n\n举例2：（模糊字符串的后四位）\n\n```js\nconst telephone = '13088889999';\nconst mix_telephone = telephone.slice(0, -4) + '*'.repeat(4); // 模糊电话号码的后四位\n\nconsole.log(telephone); // 打印结果：13088889999\nconsole.log(mix_telephone); // 打印结果：1308888****\n```\n\n\n\n\n\n\n## trim()\n\n`trim()`：去除字符串前后的空白。\n\n代码举例：\n\n```javascript\n//去除字符串前后的空格，trim();\nlet str = '   a   b   c   ';\nconsole.log(str);\nconsole.log(str.length);\n\nconsole.log(str.trim());\nconsole.log(str.trim().length);\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20200607_2132.png)\n\n## 大小写转换\n\n举例：\n\n```javascript\nvar str = 'abcdEFG';\n\n//转换成小写\nconsole.log(str.toLowerCase());\n\n//转换成大写\nconsole.log(str.toUpperCase());\n```\n\n## html 方法\n\n- anchor() 创建 a 链接\n\n- big()\n\n- sub()\n\n- sup()\n\n- link()\n\n- bold()\n\n注意，str.link() 返回值是字符串。\n\n举例：\n\n```javascript\nvar str = '你好';\n\nconsole.log(str.anchor());\nconsole.log(str.big());\nconsole.log(str.sub());\nconsole.log(str.sup());\nconsole.log(str.link('http://www.baidu.com'));\nconsole.log(str.bold());\n```\n\n![](http://img.smyhvae.com/20180202_1536.png)\n\n## 字符串练习\n\n**练习 1**：\"smyhvaevaesmyh\"查找字符串中所有 m 出现的位置。\n\n代码实现：\n\n```javascript\nvar str2 = 'smyhvaevaesmyh';\nfor (var i = 0; i < str2.length; i++) {\n    //如果指定位置的符号=== \"o\"\n    //str2[i]\n    if (str2.charAt(i) === 'm') {\n        console.log(i);\n    }\n}\n```\n\n**练习 2**：判断一个字符串中出现次数最多的字符，统计这个次数\n\n```html\n<script>\n    var str2 = 'smyhvaevaesmyhvae';\n\n    //定义一个json，然后判断json中是够有该属性，如果有该属性，那么值+1;否则创建一个该属性，并赋值为1；\n    var json = {};\n    for (var i = 0; i < str2.length; i++) {\n        //判断：如果有该属性，那么值+1;否则创建一个该属性，并赋值为1；\n        var key = str2.charAt(i);\n        if (json[key] === undefined) {\n            json[key] = 1;\n        } else {\n            json[key] += 1;\n        }\n    }\n    console.log(json);\n\n    console.log('----------------');\n    //获取json中属性值最大的选项\n    var maxKey = '';\n    var maxValue = 0;\n    for (var k in json) {\n        //        if(maxKey == \"\"){\n        //            maxKey = k;\n        //            maxValue = json[k];\n        //        }else{\n        if (json[k] > maxValue) {\n            maxKey = k;\n            maxValue = json[k];\n        }\n        //        }\n    }\n    console.log(maxKey);\n    console.log(maxValue);\n</script>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180202_1540.png)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/16-内置对象：Number和Math.md",
    "content": "---\ntitle: 16-内置对象：Number和Math\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 内置对象 Number 的常见方法\n\n\n### Number.isInteger() 判断是否为整数\n\n语法：\n\n```\n布尔值 = Number.isInteger(数字);\n```\n\n\n### toFixed() 小数点后面保留多少位\n\n语法：\n\n```js\n字符串 = myNum.toFixed(num);\n```\n\n解释：将数字 myNum 的小数点后面保留 num 位小数（四舍五入），并返回。不会改变原数字。注意，**返回结果是字符串**。\n\n参数 num：指定了小数点后面的位数。\n\n举例：\n\n```js\nlet num = 3.456;\nlet num2 = num.toFixed(2);\n\nconsole.log(num); // 打印结果：3.456\nconsole.log(num2); // 打印结果：3.46\n\nconsole.log(typeof num); // number\nconsole.log(typeof num2); // string\n```\n\n上方代码中，`num2`的结果是3.46，但是请注意，`num`的类型Number型，而`num2`的类型却是String型。\n\n另外需要注意的是，数字常量不能直接调 toFixed 方法。比如 `1.toFixed(2)`在 JS 中会引发语法错误。因为点号（.）被解释为数字字面量的一部分，而不是方法调用的分隔符。为了正确调用 toFixed 方法，可以使用括号或额外的点号。\n\ntoFixed()在这一点上，跟前面讲的 toString() 是类似的，推荐的做法是先把数字放到变量中存起来，然后通过变量调用 toFixed()。\n\n\n\n## 内置对象 Math 的常见方法\n\nMath 和其他的对象不同，它不是一个构造函数，不需要创建对象。所以我们不需要 通过 new 来调用，而是直接使用里面的属性和方法即可。\n\nMath属于一个工具类，里面封装了数学运算相关的属性和方法。如下：\n\n| 方法 | 描述 | 备注 |\n|:-------------|:-------------|:-------------|\n| Math.PI | 圆周率 | Math对象的属性  |\n| Math.abs() |  **返回绝对值** |  |\n| Math.random() | 生成0-1之间的**随机浮点数** | 取值范围是 [0，1) |\n| Math.floor() | **向下取整**（往小取值） |  |\n| Math.ceil() | **向上取整**（往大取值） |  |\n| Math.round() | 四舍五入取整（正数四舍五入，负数五舍六入） |  |\n| Math.max(x, y, z)  | 返回多个数中的最大值 |  |\n| Math.min(x, y, z)  | 返回多个数中的最小值 |  |\n| Math.pow(x,y) | 乘方：返回 x 的 y 次幂 |  |\n| Math.sqrt() | 开方：对一个数进行开方运算 |  |\n\n\n\n**举例**：\n\n```javascript\n    var num = -0.6;\n\n    console.log(Math.abs(num));        //取绝对值\n\n    console.log(Math.floor(num));      //向下取整，向小取\n\n    console.log(Math.ceil(num));       //向上取整，向大取\n\n    console.log(Math.round(num));      //四舍五入取整（正数四舍五入，负数五舍六入）\n\n    console.log(Math.random());        //生成0-1之间的随机数\n```\n\n运行结果：\n\n```\n    0.6\n\n    -1\n\n    -0\n\n    -1\n\n    0.6453756205275165\n```\n\n##  Math.abs()：获绝对值\n\n方法定义：返回绝对值。\n\n注意：\n\n- 参数中可以接收字符串类型的数字，此时会将字符串做隐式类型转换，然后再调用 Math.abs() 方法。\n\n代码举例：\n\n```javascript\n    console.log(Math.abs(2)); // 2\n    console.log(Math.abs(-2)); // 2\n\n    // 先做隐式类型转换，将 '-2'转换为数字类型 -2，然后再调用 Math.abs()\n    console.log(Math.abs('-2'));\n\n    console.log(Math.abs('hello')); // NaN\n```\n\n## Math.random() 方法：生成随机数\n\n方法定义：生成 [0, 1) 之间的**随机浮点数**。\n\n我们来看几个例子。\n\n### 生成 [0, x) 之间的随机数\n\n```javascript\n    Math.round(Math.random()*x)\n```\n\n\n### 生成 [x, y) 之间的随机数\n\n```javascript\n    Math.round(Math.random()*(y-x)+x)\n```\n\n### 【重要】生成 [x, y]之间的随机整数\n\n也就是说：生成两个整数之间的随机整数，**并且要包含这两个整数**。\n\n这个功能很常用，我们可以将其封装成一个方法，代码实现如下：\n\n```javascript\n    /*\n    * 生成两个整数之间的随机整数，并且要包含这两个整数\n    */\n    function getRandom(min, max) {\n        return Math.floor(Math.random() * (max - min + 1)) + min;\n    }\n\n    console.log(getRandom(1, 10));\n```\n\n### 举例：随机点名\n\n根据上面的例子，我们还可以再延伸一下，来看看随机点名的例子。\n\n```javascript\n    /*\n    * 生成两个整数之间的随机整数，并且要包含这两个整数\n    */\n    function getRandom(min, max) {\n        return Math.floor(Math.random() * (max - min + 1)) + min;\n    }\n\n    const arr = ['许嵩', '邓紫棋', '毛不易', '解忧邵帅'];\n    const index = getRandom(0, arr.length - 1); // 生成随机的index\n    console.log(arr[index]); // 随机点名\n```\n\n\n## pow()：乘方\n\n如果想计算 `a 的 b 次方`，可以使用如下函数：\n\n```\n\tMath.pow(a, b);\n```\n\nMath的中文是“数学”，pow是“幂”。\n\n**举例1：**\n\n![](http://img.smyhvae.com/20180117_1730.png)\n\n代码实现：\n\n```\n\tvar a = Math.pow(3, Math.pow(2, 2));\n\tconsole.log(a);\n```\n\n**举例2：**\n\n![](http://img.smyhvae.com/20180117_1740.png)\n\n代码实现：\n\n```\n\tvar a = Math.pow(Math.pow(3, 2), 4);\n\tconsole.log(a);\n```\n\n## sqrt()：开方\n\n如果想计算数值a的开二次方，可以使用如下函数：\n\n```\n\t Math.sqrt(a);\n```\n\nsqrt即“square 开方”。比如：\n\n```\n\tvar a = Math.sqrt(36);\n```\n\n\n## url 编码和解码\n\nURI (Uniform ResourceIdentifiers,通用资源标识符)进行编码，以便发送给浏览器。有效的URI中不能包含某些字符，例如空格。而这URI编码方法就可以对URI进行编码，它们用特殊的UTF-8编码替换所有无效的字符，从而让浏览器能够接受和理解。\n\n```javascript\n    encodeURIComponent();   //把字符串作为 URI 组件进行编码\n    decodeURIComponent();   //把字符串作为 URI 组件进行解码\n```\n\n举例：\n\n```javascript\n    var url = \"http://www.cnblogs.com/smyhvae/\";\n\n    var str = encodeURIComponent(url);\n    console.log(str);                           //打印url的编码\n    console.log(decodeURIComponent(str));       //对url进行编码后，再解码，还原为url\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180202_1432.png)\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/17-内置对象：Date.md",
    "content": "---\ntitle: 17-内置对象：Date\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 内置对象：Date\n\n> Date 对象在实际开发中，使用得很频繁，且容易在细节地方出错，需要引起重视。\n\n内置对象 Date 用来处理日期和时间。\n\n**需要注意的是**：与 Math 对象不同，Date 对象是一个**构造函数** ，需要**先实例化**后才能使用。\n\n## 创建Date对象\n\n创建Date对象有两种写法：\n\n- 写法一：如果Date()不写参数，就返回当前时间对象\n\n- 写法二：如果Date()里面写参数，就返回括号里输入的时间对象\n\n针对这两种写法，我们来具体讲一讲。\n\n### 写法一：不传递参数时，则获取系统的当前时间对象\n\n代码举例：\n\n```javascript\nvar date1 = new Date();\nconsole.log(date1);\nconsole.log(typeof date1);\n```\n\n代码解释：不传递参数时，表示的是获取系统的当前时间对象。也可以理解成是：获取当前代码执行的时间。\n\n打印结果：\n\n```\nMon Feb 17 2020 21:57:22 GMT+0800 (中国标准时间)\nobject\n```\n\n### 写法二：传递参数\n\n传递参数时，表示获取指定时间的时间对象。参数中既可以传递字符串，也可以传递数字，也可以传递时间戳。\n\n通过传参的这种写法，我们可以把时间字符串/时间数字/时间戳，按照指定的格式，转换为时间对象。\n\n举例1：（参数是字符串）\n\n```js\nconst date11 = new Date('2020/02/17 21:00:00');\nconsole.log(date11); // Mon Feb 17 2020 21:00:00 GMT+0800 (中国标准时间)\n\nconst date12 = new Date('2020/04/19'); // 返回的就是四月\nconsole.log(date12); // Sun Apr 19 2020 00:00:00 GMT+0800 (中国标准时间)\n\nconst date13 = new Date('2020-05-20');\nconsole.log(date13); // Wed May 20 2020 08:00:00 GMT+0800 (中国标准时间)\n\nconst date14 = new Date('Wed Jan 27 2017 12:00:00 GMT+0800 (中国标准时间)');\nconsole.log(date14); // Fri Jan 27 2017 12:00:00 GMT+0800 (中国标准时间)\n```\n\n\n举例2：（参数是多个数字）\n\n```js\nconst date21 = new Date(2020, 2, 18); // 注意，第二个参数返回的是三月，不是二月\nconsole.log(date21); // Wed Mar 18 2020 00:00:00 GMT+0800 (中国标准时间)\n\nconst date22 = new Date(2020, 3, 18, 22, 59, 58);\nconsole.log(date22); // Sat Apr 18 2020 22:59:58 GMT+0800 (中国标准时间)\n\nconst params = [2020, 06, 12, 16, 20, 59];\nconst date23 = new Date(...params);\nconsole.log(date23); // Sun Jul 12 2020 16:20:59 GMT+0800 (中国标准时间)\n```\n\n\n举例3：（参数是时间戳）\n\n```js\nconst date31 = new Date(1591950413388);\nconsole.log(date31); // Fri Jun 12 2020 16:26:53 GMT+0800 (中国标准时间)\n\n// 先把时间对象转换成时间戳，然后把时间戳转换成时间对象\nconst timestamp = new Date().getTime();\nconst date32 = new Date(timestamp);\nconsole.log(date32); // Fri Jun 12 2020 16:28:21 GMT+0800 (中国标准时间)\n```\n\n\n\n\n\n## 日期的格式化\n\n上一段内容里，我们获取到了 Date **对象**，但这个对象，打印出来的结果并不是特别直观。\n\n如果我们需要获取日期的**指定部分**，就需要用到 Date对象自带的方法。\n\n获取了日期指定的部分之后，我们就可以让日期按照指定的格式，进行展示（即日期的格式化）。比如说，我期望能以 `2020-02-02 19:30:59` 这种格式进行展示。\n\n在这之前，我们先来看看 Date 对象有哪些方法。\n\n### Date对象的方法\n\nDate对象 有如下方法，可以获取日期和时间的**指定部分**：\n\n| 方法名        | 含义              | 备注      |\n| ------------- | ----------------- | --------- |\n| getFullYear() | 获取年份          |           |\n| getMonth()    | **获取月： 0-11** | 0代表一月 |\n| getDate()       | **获取日：1-31** | 获取的是几号 |\n| getDay() | **获取星期：0-6** | 0代表周日，1代表周一 |\n| getHours() | 获取小时：0-23 |  |\n| getMinutes() | 获取分钟：0-59 |           |\n| getSeconds() | 获取秒：0-59 |           |\n| getMilliseconds() | 获取毫秒 | 1s = 1000ms |\n\n\n\n**代码举例**：\n\n```javascript\n\t// 我在执行这行代码时，当前时间为 2019年2月4日，周一，13:23:52\n\tvar myDate = new Date();\n\n\tconsole.log(myDate); // 打印结果：Mon Feb 04 2019 13:23:52 GMT+0800 (中国标准时间)\n\n\tconsole.log(myDate.getFullYear()); // 打印结果：2019\n\tconsole.log(myDate.getMonth() + 1); // 打印结果：2\n\tconsole.log(myDate.getDate()); // 打印结果：4\n\n\tvar dayArr  = ['星期日', '星期一', '星期二', '星期三', '星期四','星期五', '星期六'];\n\tconsole.log(myDate.getDay()); // 打印结果：1\n\tconsole.log(dayArr[myDate.getDay()]); // 打印结果：星期一\n\n\tconsole.log(myDate.getHours()); // 打印结果：13\n\tconsole.log(myDate.getMinutes()); // 打印结果：23\n\tconsole.log(myDate.getSeconds()); // 打印结果：52\n\tconsole.log(myDate.getMilliseconds()); // 打印结果：393\n\n\tconsole.log(myDate.getTime()); // 获取时间戳。打印结果：1549257832393\n```\n\n获取了日期和时间的指定部分之后，我们把它们用字符串拼接起来，就可以按照自己想要的格式，来展示日期。\n\n### 举例：年月日的格式化\n\n代码举例：\n\n```js\nconsole.log(formatDate());\n\n/*\n    方法：日期格式化。\n    格式要求：今年是：2020年02月02日 08:57:09 星期日\n*/\nfunction formatDate() {\n    var date = new Date();\n\n    var year = date.getFullYear(); // 年\n    var month = date.getMonth() + 1; // 月\n    var day = date.getDate(); // 日\n\n    var week = date.getDay(); // 星期几\n    var weekArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];\n\n    var hour = date.getHours(); // 时\n    hour = hour < 10 ? '0' + hour : hour; // 如果只有一位，则前面补零\n\n    var minute = date.getMinutes(); // 分\n    minute = minute < 10 ? '0' + minute : minute; // 如果只有一位，则前面补零\n\n    var second = date.getSeconds(); // 秒\n    second = second < 10 ? '0' + second : second; // 如果只有一位，则前面补零\n\n    var result = '今天是：' + year + '年' + month + '月' + day + '日 ' + hour + ':' + minute + ':' + second + ' ' + weekArr[week];\n\n    return result;\n}\n\n```\n\n\n\n\n## 获取时间戳\n\n### 时间戳的定义和作用\n\n**时间戳**：指的是从格林威治标准时间的`1970年1月1日，0时0分0秒`到当前日期所花费的**毫秒数**（1秒 = 1000毫秒）。\n\n计算机底层在保存时间时，使用的都是时间戳。时间戳的存在，就是为了**统一**时间的单位。\n\n我们经常会利用时间戳来计算时间，因为它更精确。而且，在实战开发中，接口返回给前端的日期数据，都是以时间戳的形式。\n\n我们再来看下面这样的代码：\n\n```javascript\n\tvar myDate = new Date(\"1970/01/01 0:0:0\");\n\n\tconsole.log(myDate.getTime()); // 获取时间戳\n```\n\n打印结果（可能会让你感到惊讶）\n\n```javascript\n\t-28800000\n```\n\n为啥打印结果是`-28800000`，而不是`0`呢？这是因为，我们的当前代码，是在中文环境下运行的，与英文时间会存在**8个小时的时差**（中文时间比英文时间早了八个小时）。如果代码是在英文环境下运行，打印结果就是`0`。\n\n\n### getTime()：获取时间戳\n\n`getTime()`  获取日期对象的**时间戳**（单位：毫秒）。这个方法在实战开发中，用得比较多。但还有比它更常用的写法，我们往下看。\n\n\n### 获取 Date 对象的时间戳\n\n代码演示：\n\n```js\n// 方式一：获取 Date 对象的时间戳（最常用的写法）\nconst timestamp1 = +new Date();\nconsole.log(timestamp1); // 打印结果举例：1589448165370\n\n// 方式二：获取 Date 对象的时间戳（较常用的写法）\nconst timestamp2 = new Date().getTime();\nconsole.log(timestamp2); // 打印结果举例：1589448165370\n\n// 方式三：获取 Date 对象的时间戳\nconst timestamp3 = new Date().valueOf();\nconsole.log(timestamp3); // 打印结果举例：1589448165370\n\n// 方式4：获取 Date 对象的时间戳\nconst timestamp4 = new Date() * 1;\nconsole.log(timestamp4); // 打印结果举例：1589448165370\n\n// 方式5：获取 Date 对象的时间戳\nconst timestamp5 = Number(new Date());\nconsole.log(timestamp5); // 打印结果举例：1589448165370\n```\n\n上面这五种写法都可以获取任意 Date 对象的时间戳，最常见的写法是**方式一**，其次是方式二。\n\n根据前面所讲的关于「时间戳」的概念，上方代码获取到的时间戳指的是：从 `1970年1月1日，0时0分0秒` 到现在所花费的总毫秒数。\n\n### 获取当前时间的时间戳\n\n如果我们要获取**当前时间**的时间戳，除了上面的几种方式之外，还有另一种方式。代码如下：\n\n```js\n// 方式六：获取当前时间的时间戳（很常用的写法）\nconsole.log(Date.now()); // 打印结果举例：1589448165370\n```\n\n上面这种方式六，用得也很多。只不过，`Date.now()`是H5标准中新增的特性，如果你的项目需要兼容低版本的IE浏览器，就不要用了。这年头，谁还用IE呢？\n\n\n### 利用时间戳检测代码的执行时间\n\n我们可以在业务代码的前面定义 `时间戳1`，在业务代码的后面定义 `时间戳2`。把这两个时间戳相减，就能得出业务代码的执行时间。\n\n\n### format()\n\n将时间对象转换为指定格式。\n\n参考链接：<https://www.cnblogs.com/tugenhua0707/p/3776808.html>\n\n## 练习\n\n### 举例1：模拟日历\n\n要求每天打开这个页面，都能定时显示当前的日期。\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n    <head lang=\"en\">\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <style>\n            div {\n                width: 800px;\n                margin: 200px auto;\n                color: red;\n                text-align: center;\n                font: 600 30px/30px 'simsun';\n            }\n        </style>\n    </head>\n    <body>\n        <div></div>\n\n        <script>\n            //模拟日历\n            //需求：每天打开这个页面都能定时显示年月日和星期几\n            function getCurrentDate() {\n                //1.创建一个当前日期的日期对象\n                const date = new Date();\n                //2.然后获取其中的年、月、日和星期\n                const year = date.getFullYear();\n                const month = date.getMonth();\n                const hao = date.getDate();\n                const week = date.getDay();\n                //        console.log(year+\" \"+month+\" \"+hao+\" \"+week);\n                //3.赋值给div\n                const arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];\n                const div = document.getElementsByTagName('div')[0];\n                return '今天是：' + year + '年' + (month + 1) + '月' + hao + '日 ' + arr[week];\n            }\n\n            const div = document.getElementsByTagName('div')[0];\n            div.innerText = getCurrentDate();\n        </script>\n    </body>\n</html>\n```\n\n实现效果：\n\n![](http://img.smyhvae.com/20180202_1110.png)\n\n\n### 举例2：发布会倒计时\n\n实现思路：\n\n- 设置一个定时器，每间隔1毫秒就自动刷新一次div的内容。\n\n- 核心算法：输入的时间戳减去当前的时间戳，就是剩余时间（即倒计时），然后转换成时分秒。\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n    <head lang=\"en\">\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <style>\n            div {\n                width: 1210px;\n                margin: 200px auto;\n                color: red;\n                text-align: center;\n                font: 600 30px/30px 'simsun';\n            }\n        </style>\n    </head>\n    <body>\n        <div></div>\n\n        <script>\n            var div = document.getElementsByTagName('div')[0];\n\n            var timer = setInterval(() => {\n                countDown('2022/02/03 11:20:00');\n            }, 1);\n\n            function countDown(myTime) {\n                var nowTime = new Date();\n                var future = new Date(myTime);\n                var timeSum = future.getTime() - nowTime.getTime(); //获取时间差：发布会时间减去此刻的毫秒值\n\n                var day = parseInt(timeSum / 1000 / 60 / 60 / 24); // 天\n                var hour = parseInt((timeSum / 1000 / 60 / 60) % 24); // 时\n                var minu = parseInt((timeSum / 1000 / 60) % 60); // 分\n                var sec = parseInt((timeSum / 1000) % 60); // 秒\n                var millsec = parseInt(timeSum % 1000); // 毫秒\n\n                //细节处理：所有的时间小于10的时候，在前面自动补0，毫秒值要补双0（比如如，把 8 秒改成 08 秒）\n                day = day < 10 ? '0' + day : day; //day小于10吗？如果小于，就补0；如果不小于，就是day本身\n                hour = hour < 10 ? '0' + hour : hour;\n                minu = minu < 10 ? '0' + minu : minu;\n                sec = sec < 10 ? '0' + sec : sec;\n                if (millsec < 10) {\n                    millsec = '00' + millsec;\n                } else if (millsec < 100) {\n                    millsec = '0' + millsec;\n                }\n\n                // 兜底处理\n                if (timeSum < 0) {\n                    div.innerHTML = '距离苹果发布会还有00天00小时00分00秒000毫秒';\n                    clearInterval(timer);\n                    return;\n                }\n\n                // 前端要显示的文案\n                div.innerHTML = '距离苹果发布会还有' + day + '天' + hour + '小时' + minu + '分' + sec + '秒' + millsec + '毫秒';\n            }\n        </script>\n    </body>\n</html>\n\n```\n\n实现效果：\n\n![](http://img.smyhvae.com/20180202_1130.gif)\n\n## Moment.js\n\nMoment.js 是一个轻量级的JavaScript时间库，我们可以利用它很方便地进行时间操作，提升开发效率。\n\n- 中文官网：<http://momentjs.cn/>\n\n使用举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <script src=\"https://cdn.bootcdn.net/ajax/libs/moment.js/2.26.0/moment.min.js\"></script>\n        <script>\n            // 按照指定的格式，格式化当前时间\n            console.log(moment().format('YYYY-MM-DD HH:mm:ss')); // 打印结果举例：2020-06-12 16:38:38\n            console.log(typeof moment().format('YYYY-MM-DD HH:mm:ss')); // 打印结果：string\n\n            // 按照指定的格式，格式化指定的时间\n            console.log(moment('2020/06/12 18:01:59').format('YYYY-MM-DD HH:mm:ss')); // 打印结果：2020-06-12 18:01:59\n\n            // 按照指定的格式，获取七天后的时间\n            console.log(moment().add(7, 'days').format('YYYY-MM-DD hh:mm:ss')); // 打印结果举例：2020-06-19 04:43:56\n        </script>\n    </body>\n</html>\n\n```\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/18-数组简介.md",
    "content": "---\ntitle: 18-数组简介\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n> 之前学习的数据类型，只能存储一个值（字符串也为一个值）。如果我们想存储多个值，就可以使用数组。\n\n## 数组简介\n\n数组（Array）是属于**内置对象**，数组和普通对象的功能类似，都可以用来存储一些值。不同的是：\n\n-   普通对象是使用字符串作为属性名，而数组是使用数字作为**索引**来操作元素。索引：从 0 开始的整数就是索引。\n\n数组的存储性能比普通对象要好。在实际开发中我们经常使用数组存储一些数据（尤其是**列表数据**），使用频率非常高。\n\n![](http://img.smyhvae.com/20200612_1707.png)\n\n比如说，上面这个页面的列表数据，它的数据结构就是一个数组。\n\n数组中的元素可以是任意的数据类型，可以是对象，可以是函数，也可以是数组。数组的元素中，如果存放的是数组，我们就称这种数组为二维数组。\n\n接下来，我们讲一讲数组的基本操作。\n\n## 创建数组对象\n\n### 方式一：使用字面量创建数组\n\n举例：\n\n```javascript\nlet arr1 = []; // 创建一个空的数组\n\nlet arr2 = [1, 2, 3]; // 创建带初始值的数组\n```\n\n方式一最简单，也用得最多。\n\n### 方式二：使用构造函数创建数组\n\n语法：\n\n```js\nlet arr = new Array(参数);\n\nlet arr = Array(参数);\n```\n\n如果**参数为空**，表示创建一个空数组；如果参数是**一个数值**，表示数组的长度；如果**有多个参数**，表示数组中的元素内容。\n\n举个例子：\n\n```javascript\n// 方式一\nlet arr1 = [11, 12, 13];\n\n// 方式二\nlet arr2 = new Array(); // 参数为空：创建空数组\nlet arr3 = new Array(4); // 参数为 size\nlet arr4 = new Array(15, 16, 17); // 参数为多个数值：创建一个带数据的数组\n\nconsole.log(typeof arr1); // 打印结果：object\n\nconsole.log('arr1 = ' + JSON.stringify(arr1));\nconsole.log('arr2 = ' + JSON.stringify(arr2));\nconsole.log('arr3 = ' + JSON.stringify(arr3));\nconsole.log('arr4 = ' + JSON.stringify(arr4));\n```\n\n打印结果：\n\n```javascript\nobject;\n\narr1 = [11, 12, 13];\narr2 = [];\narr3 = [null, null, null, null];\narr4 = [15, 16, 17];\n```\n\n从上方打印结果的第一行可以看出，数组的类型是属于**对象**。\n\n### 数组中的元素的类型\n\n数组中可以存放**任意类型**的数据，例如字符串、数字、布尔值、对象等。\n\n比如：\n\n```javascript\nconst arr = ['qianguyihao', 28, true, { name: 'qianguyihao' }];\n```\n\n我们甚至可以在数组里存放数组。比如：\n\n```js\nconst arr2 = [\n    [11, 12, 13],\n    [21, 22, 23],\n];\n```\n\n## 数组的基本操作\n\n### 数组的索引\n\n**索引** (下标) ：用来访问数组元素的序号，代表的是数组中的元素在数组中的位置（下标从 0 开始算起）。\n\n数组可以通过索引来访问、修改对应的数组元素。我们继续看看。\n\n### 向数组中添加元素\n\n语法：\n\n```javascript\n数组[索引] = 值;\n```\n\n代码举例：\n\n```javascript\nconst arr = [];\n\n// 向数组中添加元素\narr[0] = 10;\narr[1] = 20;\narr[2] = 30;\narr[3] = 40;\narr[5] = 50;\n\nconsole.log(JSON.stringify(arr));\n```\n\n打印结果：\n\n```\n[10,20,30,40,null,50]\n```\n\n### 获取数组中的元素\n\n语法：\n\n```javascript\n数组[索引];\n```\n\n如果读取不存在的索引（比如元素没那么多），系统不会报错，而是返回 undefined。\n\n代码举例：\n\n```javascript\nconst arr = [21, 22, 23];\n\nconsole.log(arr[0]); // 打印结果：21\nconsole.log(arr[5]); // 打印结果：undefined\n```\n\n### 获取数组的长度\n\n可以使用`length`属性来获取数组的长度(即“元素的个数”)。\n\n数组的长度是元素个数，不要跟索引号混淆。\n\n语法：\n\n```javascript\n数组的长度 = 数组名.length；\n```\n\n代码举例：\n\n```javascript\nconst arr = [21, 22, 23];\n\nconsole.log(arr.length); // 打印结果：3\n```\n\n补充：\n\n对于连续的数组，使用 length 可以获取到数组的长度（元素的个数）；对于非连续的数组（即“稀疏数组”，本文稍后会讲），length 的值会大于元素的个数。因此，尽量不要创建非连续的数组。\n\n### 修改数组的长度\n\n可以通过修改length属性修改数组的长度。\n\n-   如果修改的 length 大于原长度，则多出部分会空出来，置为 null。\n\n-   如果修改的 length 小于原长度，则多出的元素会被删除，数组将从后面删除元素。\n\n-   （特例：伪数组 arguments 的长度可以修改，但是不能修改里面的元素，以后单独讲。）\n\n代码举例：\n\n```javascript\nconst arr1 = [11, 12, 13];\nconst arr2 = [21, 22, 23];\n\n// 修改数组 arr1 的 length\narr1.length = 1;\nconsole.log(JSON.stringify(arr1));\n\n// 修改数组 arr2 的 length\narr2.length = 5;\nconsole.log(JSON.stringify(arr2));\n```\n\n打印结果：\n\n```javascript\n[11]\n[21, 22, 23, null, null]\n```\n\n### 遍历数组\n\n**遍历**: 就是把数组中的每个元素从头到尾都访问一次。\n\n最简单的做法是通过 for 循环，遍历数组中的每一项。举例：\n\n```javascript\nconst arr = [10, 20, 30, 40, 50];\n\nfor (let i = 0; i < arr.length; i++) {\n    console.log(arr[i]); // 打印出数组中的每一项\n}\n```\n\n下一篇文章，会学习数组的各种方法，到时候，会有更多的做法去遍历数组。\n\n## JS语言中，数组的注意点\n\n> 和其他编程语言相比，JS语言中的数组比较灵活，有许多与众不同的地方。\n\n1、如果访问数组中不存在的索引时，不会报错，会返回undefined。\n\n2、当数组的存储空间不够时，数组会自动扩容。其它编程语言中数组的大小是固定的，不会自动扩容。\n\n3、数组可以存储不同类型数据，其它编程语言中数组只能存储相同类型数据。\n\n4、数组分配的存储空间不一定是连续的。其它语言数组分配的存储空间是连续的。\n\nJS中的数组采用\"哈希映射\"的方式分配存储空间，我们可以通过索引找到对应空间。各大浏览器也对数组分配的存储空间进行了优化：如果存储的都是相同类型的数据，则会尽量分配连续的存储空间；如果存储的不是相同的数据类型，则不会分配连续的存储空间。\n\n## 数组的解构赋值\n\n解构赋值是ES6中新增的一种赋值方式。\n\nES5中，如果想把数组中的元素赋值给其他变量，是这样做的：\n\n```js\nconst arr = [1, 2, [3,4]];\nlet a = arr[0]; // 1\nlet b = arr[1]; // 2\nlet c = arr[2]; // [3, 4]\n```\n\n上面这种写法比较啰嗦。通过ES6中的结构复制，我们可以像下面这样做。\n\n1、数组解构赋值，代码举例：\n\n```js\nlet [a, b, c] = [1, 2, [3, 4]];\nconsole.log(a); // 1\nconsole.log(b); // 2\nconsole.log(c); // [3, 4]\n```\n\n注意点：\n\n（1）等号左边的个数和格式，必须和右边的一模一样，才能完全解构。\n\n（2）当然，左边的个数和右边的个数，可以不一样。\n\n2、默认值。在赋值之前，我们可以给左边的变量指定**默认值**：\n\n```js\nlet [a, b = 3, c = 4] = [1, 2];\nconsole.log(a); // 1\nconsole.log(b); // 2。默认值被覆盖。\nconsole.log(c); // 4。继续保持默认值。\n```\n\n3、我们可以使用ES6中新增的**扩展运算符**打包剩余的数据。如果使用了扩展运算符, 那么扩展运算符只能写在最后。代码举例：\n\n```js\nlet [a, ...b] = [1, 2, 3];\nconsole.log(a); // 1\nconsole.log(b); // [2, 3]\n```\n\n## 稀疏数组与密集数组\n\n>  这个知识点，简单了解即可。\n\n- 稀疏数组：索引不连续、数组长度大于元素个数的数组，可以简单理解为有 `empty`（有空隙）的数组。\n\n- 密集数组：索引连续、数组长度等于元素个数的数组。\n\n\n参考链接：\n\n- [JavaScript 之稀疏数组与密集数组](https://juejin.cn/post/6975531514444562462)\n\n- [JS 稀疏数组](https://github.com/JunreyCen/blog/issues/10)\n\n- [JS 中的稀疏数组和密集数组](https://juejin.cn/post/6844904050152964109)\n\n- [译]JavaScript中的稀疏数组与密集数组：https://www.cnblogs.com/ziyunfei/archive/2012/09/16/2687165.html\n\n- [JavaScript || 数组](https://segmentfault.com/a/1190000008533942)\n\n## 案例\n\n### 例 1：翻转数组\n\n代码实现：\n\n```javascript\nconst arr = [10, 20, 30, 40, 50]; // 原始数组\nconst newArr = []; // 翻转后的数组\nfor (let i = 0; i < arr.length; i++) {\n    newArr[i] = arr[arr.length - i - 1];\n}\nconsole.log(JSON.stringify(newArr));\n```\n\n打印结果：\n\n```\n    [50,40,30,20,10]\n```\n\n### 例 2：冒泡排序\n\n代码实现：\n\n```javascript\nconst arr = [20, 10, 50, 30, 40];\nfor (let i = 0; i < arr.length - 1; i++) {\n    for (let j = 0; j < arr.length - i - 1; j++) {\n        if (arr[j] > arr[j + 1]) {\n            let temp = arr[j];\n            arr[j] = arr[j + 1];\n            arr[j + 1] = temp;\n        }\n    }\n}\nconsole.log(JSON.stringify(arr));\n```\n\n打印结果：\n\n```\n    [10,20,30,40,50]\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/19-数组的常见方法.md",
    "content": "---\ntitle: 19-数组的常见方法\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 数组的方法清单\n\n### 数组的类型相关\n\n| 方法                             | 描述                                             | 备注             |\n| :------------------------------- | :----------------------------------------------- | :--------------- |\n| Array.isArray()                  | 判断是否为数组                                   |                  |\n| toString()                       | 将数组转换为字符串                               | 不会改变原数组   |\n| join()                           | 将数组转换为字符串，返回结果为**转换后的字符串** | 不会改变原数组   |\n| 字符串的方法：split()            | 将字符串按照指定的分隔符，组装为数组             | 不会改变原字符串 |\n|                                  |                                                  |                  |\n| Array.from(arrayLike)            | 将**伪数组**转化为**真数组**                     |                  |\n| Array.of(value1, value2, value3) | 创建数组：将**一系列值**转换成数组               |                  |\n\n注意：\n\n（1）获取数组的长度是用`length`属性，不是方法。关于 `length`属性，详见上一篇文章。\n\n（2）`split()`是字符串的方法，不是数组的方法。\n\n### 数组元素的添加和删除\n\n| 方法      | 描述                                                                       | 备注           |\n| :-------- | :------------------------------------------------------------------------- | :------------- |\n| push()    | 向数组的**最后面**插入一个或多个元素，返回结果为新数组的**长度**           | 会改变原数组   |\n| pop()     | 删除数组中的**最后一个**元素，返回结果为**被删除的元素**                   | 会改变原数组   |\n| unshift() | 在数组**最前面**插入一个或多个元素，返回结果为新数组的**长度**             | 会改变原数组   |\n| shift()   | 删除数组中的**第一个**元素，返回结果为**被删除的元素**                     | 会改变原数组   |\n|           |                                                                            |                |\n| splice()  | 从数组中**删除**指定的一个或多个元素，返回结果为**被删除元素组成的新数组** | 会改变原数组   |\n| slice()   | 从数组中**提取**指定的一个或多个元素，返回结果为**新的数组**               | 不会改变原数组 |\n|           |                                                                            |                |\n| concat() | 合并数组：连接两个或多个数组，返回结果为**新的数组** | 不会改变原数组 |\n| fill()    | 填充数组：用固定的值填充数组，返回结果为**新的数组**                       | 会改变原数组 |\n\n### 数组排序\n\n| 方法      | 描述                                                    | 备注         |\n| :-------- | :------------------------------------------------------ | :----------- |\n| reverse() | 反转数组，返回结果为**反转后的数组**                    | 会改变原数组 |\n| sort()    | 对数组的元素,默认按照**Unicode 编码**，从小到大进行排序 | 会改变原数组 |\n\n### 查找数组的元素\n\n| 方法                  | 描述                                                                           | 备注                                                     |\n| :-------------------- | :----------------------------------------------------------------------------- | :------------------------------------------------------- |\n| indexOf(value)        | 从前往后索引，检索一个数组中是否含有指定的元素                                 |                                                          |\n| lastIndexOf(value)    | 从后往前索引，检索一个数组中是否含有指定的元素                                 |                                                          |\n| includes(item)  | 数组中是否包含指定的内容                                                        |                                                        |\n| find(function())      | 找出**第一个**满足「指定条件返回 true」的元素                                  |                                                          |\n| findIndex(function()) | 找出**第一个**满足「指定条件返回 true」的元素的 index                          |                                                          |\n| every()               | 确保数组中的每个元素都满足「指定条件返回 true」，则停止遍历，此方法才返回 true | 全真才为真。要求每一项都返回 true，最终的结果才返回 true |\n| some()                | 数组中只要有一个元素满足「指定条件返回 true」，则停止遍历，此方法就返回 true   | 一真即真。只要有一项返回 true，最终的结果就返回 true     |\n\n### 遍历数组\n\n| 方法      | 描述                                                         | 备注                                                         |\n| :-------- | :----------------------------------------------------------- | :----------------------------------------------------------- |\n| for 循环  | 最传统的方式遍历数组，这个大家都懂                           |                                                              |\n| forEach() | 遍历数组，但需要兼容 IE8 以上                                | 不会改变原数组。forEach() 没有返回值。也就是说，它的返回值是 undefined |\n| for of    | 遍历数组（ES6语法）                                          | 不会改变原数组。另外，不要使用 for in 遍历数组                |\n| map()     | 对原数组中的每一项进行加工，将组成新的数组                   | 不会改变原数组                                               |\n| filter()  | 过滤数组：返回结果是 true 的项，将组成新的数组，返回结果为**新的数组** | 不会改变原数组                                               |\n| reduce    | 接收一个函数作为累加器，返回值是回调函数累计处理的结果       | 比较复杂                                                     |\n\n\n\n## isArray()：判断是否为数组\n\n语法：\n\n```javascript\n布尔值 = Array.isArray(被检测的数组);\n```\n\n以前，我们会通过 `A instanceof B`来判断 A 是否属于 B 类型。但是在数组里，这种 instanceof 方法已经用的不多了，因为有 isArray()方法。\n\n## 数组转换为字符串\n\n数组转为字符串，有三种方式。\n\n### 方式1、toString()\n\n```javascript\n// 语法\n字符串 = 数组.toString();\n\n// 举例\nconst result = [1, 3, 5].toString(); // 转换结果 result 为字符串 '1, 3, 5'\n```\n\n解释：把数组转换成字符串，每一项用英文逗号`,`分割。\n\n备注：大多数的数据类型都可以使用`.toString()`方法，将其转换为字符串。\n\n### 方式 2\n\n```js\n// 语法\n字符串 = String(数组);\n\n// 举例\nconst result = String([1, 3, 5]); // 转换结果 result 为字符串 '1, 3, 5'\n```\n\n### 方式 3：join()方法\n\n```js\n字符串 = 数组.join(','); // 将数组转为字符串，每一项用 英文逗号 分隔\n```\n\n关于 join()方法的详细介绍，详见下一段。\n\n## join()\n\n`join()`：将数组转换为字符串，返回结果为**转换后的字符串**（不会改变原来的数组）。\n\n补充：`join()`方法可以指定一个**字符串**作为参数，这个参数是元素之间的**连接符**；如果不指定连接符，则默认使用英文逗号`,` 作为连接符，此时和 `toString()的`效果是一致的。\n\n语法：\n\n```javascript\n新的字符串 = 原数组.join(参数); // 参数选填\n```\n\n代码举例：\n\n```javascript\nconst arr = ['a', 'b', 'c'];\n\nconst result1 = arr.join(); // 这里没有指定连接符，所以默认使用 , 作为连接符\n\nconst result2 = arr.join('-'); // 使用指定的字符串作为连接符\n\nconsole.log(typeof arr); // 打印结果：object\nconsole.log(typeof result1); // 打印结果：string\n\nconsole.log('arr =' + JSON.stringify(arr));\nconsole.log('result1：' + result1);\nconsole.log('result2：' + result2);\n```\n\n上方代码中，最后三行的打印结果是：\n\n```bash\narr =[\"a\",\"b\",\"c\"]\nresult1:a,b,c\nresult2:a-b-c\n```\n\n## split()\n\n> 注意，`split()`是字符串的方法，不是数组的方法。\n\n语法：\n\n```javascript\n新的数组 = str.split(分隔符);\n```\n\n解释：通过指定的分隔符，将一个字符串拆分成一个**数组**。不会改变原字符串。\n\n备注：`split()`这个方法在实际开发中用得非常多。一般来说，从接口拿到的 json 数据中，经常会收到类似于`\"q, i, a, n\"`这样的字符串，前端需要将这个字符串拆分成`['q', 'i', 'a', 'n']`数组，这个时候`split()`方法就派上用场了。\n\n\n\n\n\n## Array.from()：将伪数组转换为真数组\n\n**语法**：\n\n```javascript\narray = Array.from(arrayLike);\n```\n\n**作用**：将**伪数组**或可遍历对象转换为**真数组**。\n\n代码举例：\n\n```js\nconst name = 'qianguyihao';\nconsole.log(Array.from(name)); // 打印结果是数组：[\"q\",\"i\",\"a\",\"n\",\"g\",\"u\",\"y\",\"i\",\"h\",\"a\",\"o\"]\n```\n\n### 伪数组与真数组的区别\n\n**伪数组**：包含 length 属性的对象或可迭代的对象。\n\n另外，伪数组的原型链中没有 Array.prototype，而真数组的原型链中有 Array.prototype。因此伪数组没有数组的一般方法，比如 pop()、join() 等方法。\n\n### 伪数组举例\n\n```html\n<body>\n    <button>按钮1</button>\n    <button>按钮2</button>\n    <button>按钮3</button>\n\n    <script>\n        let btnArray = document.getElementsByTagName('button');\n        console.log(btnArray);\n        console.log(btnArray[0]);\n    </script>\n</body>\n```\n\n上面的布局中，有三个 button 标签，我们通过`getElementsByTagName`获取到的`btnArray`实际上是**伪数组**，并不是真实的数组：\n\n![](http://img.smyhvae.com/20180402_1116.png)\n\n既然`btnArray`是伪数组，它就不能使用数组的一般方法，否则会报错：\n\n![](http://img.smyhvae.com/20180402_1121.png)\n\n解决办法：采用`Array.from`方法将`btnArray`这个伪数组转换为真数组即可：\n\n```javascript\nArray.from(btnArray);\n```\n\n然后就可以使用数组的一般方法了：\n\n![](http://img.smyhvae.com/20180402_1125.png)\n\n## Array.of()：创建数组\n\n**语法**：\n\n```javascript\nArray.of(value1, value2, value3);\n```\n\n**作用**：根据参数里的内容，创建数组。\n\n**举例**：\n\n```javascript\nconst arr = Array.of(1, 'abc', true);\nconsole.log(arr); // 打印结果是数组：[1, \"abc\", true]\n```\n\n补充：`new Array()`和 `Array.of()`的区别在于：当参数只有一个时，前者表示数组的长度，后者表示数组中的内容。\n\n## 数组元素的添加和删除\n\n### push()\n\n`push()`：向数组的**最后面**插入一个或多个元素，返回结果为新数组的**长度**。会改变原数组，因为原数组变成了新数组。\n\n语法：\n\n```javascript\n新数组的长度 = 数组.push(元素);\n新数组的长度 = 数组.push(元素1，元素2 ...);\n```\n\n代码举例：\n\n```javascript\nvar arr = ['王一', '王二', '王三'];\n\nvar result1 = arr.push('王四'); // 末尾插入一个元素\nvar result2 = arr.push('王五', '王六'); // 末尾插入多个元素\n\nconsole.log(JSON.stringify(arr)); // 打印结果：[\"王一\",\"王二\",\"王三\",\"王四\",\"王五\",\"王六\"]\nconsole.log(result1); // 打印结果：4\nconsole.log(result2); // 打印结果：6\n```\n\n### pop()\n\n`pop()`：删除数组中的**最后一个**元素，返回结果为**被删除的元素**。\n\n语法：\n\n```javascript\n被删除的元素 = 数组.pop();\n```\n\n代码举例：\n\n```javascript\nvar arr = ['王一', '王二', '王三'];\nvar result1 = arr.pop();\n\nconsole.log(JSON.stringify(arr)); // 打印结果：[\"王一\",\"王二\"]\nconsole.log(result1); // 打印结果：王三\n```\n\n### unshift()\n\n`unshift()`：在数组**最前面**插入一个或多个元素，返回结果为新数组的**长度**。会改变原数组，将原数组变成了新数组。插入元素后，其他元素的索引会依次调整。\n\n语法：\n\n```javascript\n新数组的长度 = 数组.unshift(元素);\n新数组的长度 = 数组.unshift(元素1，元素2...);\n```\n\n代码举例：\n\n```javascript\nvar arr = ['王一', '王二', '王三'];\n\nvar result1 = arr.unshift('王四'); // 最前面插入一个元素\nvar result2 = arr.unshift('王五', '王六'); // 最前面插入多个元素\n\nconsole.log(JSON.stringify(arr)); // 打印结果：[\"王五\",\"王六\",\"王四\",\"王一\",\"王二\",\"王三\"]\nconsole.log(result1); // 打印结果：4\nconsole.log(result2); // 打印结果：6\n```\n\n### shift()\n\n`shift()`：删除数组中的**第一个**元素，返回结果为**被删除的元素**。\n\n语法：\n\n```javascript\n被删除的元素 = 数组.shift();\n```\n\n代码举例：\n\n```javascript\nvar arr = ['王一', '王二', '王三'];\n\nvar result1 = arr.shift();\n\nconsole.log(JSON.stringify(arr)); // 打印结果：[\"王二\",\"王三\"]\nconsole.log(result1); // 打印结果：王一\n```\n\n\n\n### splice()\n\n`splice()`：从数组中**删除**指定的一个或多个元素，返回结果为**被删除元素组成的新数组**（会改变原来的数组）。\n\n备注：该方法会改变原数组，会将指定元素从原数组中删除；被删除的元素会封装到一个新的数组中返回。\n\n语法：\n\n```javascript\n新数组 = 原数组.splice(起始索引index);\n\n新数组 = 原数组.splice(起始索引index, 需要删除的个数);\n\n新数组 = 原数组.splice(起始索引index, 需要删除的个数, 新的元素1, 新的元素2...);\n```\n\n上方语法中，第三个及之后的参数，表示：删除元素之后，向原数组中添加新的元素，这些元素将会自动插入到起始位置索引的前面。也可以理解成：删除了哪些元素，就在那些元素的所在位置补充新的内容。\n\n`slice()`方法和`splice()`方法很容易搞混，请一定要注意区分。\n\n举例 1：\n\n```javascript\nvar arr1 = ['a', 'b', 'c', 'd', 'e', 'f'];\nvar result1 = arr1.splice(1); //从第index为1的位置开始，删除元素\n\nconsole.log('arr1：' + JSON.stringify(arr1));\nconsole.log('result1：' + JSON.stringify(result1));\n```\n\n打印结果：\n\n```\n    arr1：[\"a\"]\n    result1：[\"b\",\"c\",\"d\",\"e\",\"f\"]\n```\n\n举例 2：\n\n```javascript\nvar arr2 = ['a', 'b', 'c', 'd', 'e', 'f'];\nvar result2 = arr2.splice(-2); //删除最后两个元素\n\nconsole.log('arr2：' + JSON.stringify(arr2));\nconsole.log('result2：' + JSON.stringify(result2));\n```\n\n打印结果：\n\n```\n    arr2：[\"a\",\"b\",\"c\",\"d\"]\n    result2：[\"e\",\"f\"]\n```\n\n举例 3：\n\n```javascript\nvar arr3 = ['a', 'b', 'c', 'd', 'e', 'f'];\nvar result3 = arr3.splice(1, 3); //从第index为1的位置开始删除元素，一共删除三个元素\n\nconsole.log('arr3：' + JSON.stringify(arr3));\nconsole.log('result3：' + JSON.stringify(result3));\n```\n\n打印结果：\n\n```\n    arr3：[\"a\",\"e\",\"f\"]\n    result3：[\"b\",\"c\",\"d\"]\n```\n\n举例4：（删除指定元素，用得很多）\n\n```js\nconst arr4 = ['a', 'b', 'c', 'd'];\narr4.splice(arr4.indexOf('c'), 1); // 删除数组中的'c'这个元素\n\nconsole.log('arr4：' + JSON.stringify(arr4));\n```\n\n\n举例 5：（**第三个参数**的用法）\n\n```javascript\nvar arr5 = ['a', 'b', 'c', 'd', 'e', 'f'];\n\n//从第index为1的位置开始删除元素,一共删除三个元素。并且在index=1的位置前面追加两个元素\"千古壹号\"、\"vae\"（其实就是将index为1的元素改为\"千古壹号\"，index为2的元素改为\"vae\"）。\nvar result5 = arr5.splice(1, 3, '千古壹号', 'vae');\n\nconsole.log('arr5：' + JSON.stringify(arr5));\nconsole.log('result5：' + JSON.stringify(result5));\n```\n\n打印结果：\n\n```javascript\narr5：[\"a\",\"千古壹号\",\"vae\",\"e\",\"f\"]\nresult5：[\"b\",\"c\",\"d\"]\n```\n\n我们再看个类似的例子：\n\n```js\n// 需求：针对数组 [a, b, c, d] 将索引为1的数据修改为e, 索引为2的修改为f\n\n// 写法1：普通写法\nconst arr = [a, b, c ,d];\narr[1] = 'e';\narr[2] = 'f';\n\n// 写法2：通过 splice() 实现\nconst arr = [a, b, c ,d];\narr.splice(1,2, 'e', 'f');\n```\n\n### concat()\n\n`concat()`：连接两个或多个数组，返回结果为**新的数组**。不会改变原数组。`concat()`方法的作用是**数组合并**。\n\n语法：\n\n```javascript\n    新数组 = 数组1.concat(数组2, 数组3 ...);\n```\n\n举例：\n\n```javascript\nconst arr1 = [1, 2, 3];\nconst arr2 = ['a', 'b', 'c'];\nconst arr3 = ['千古壹号', 'vae'];\n\nconst result1 = arr1.concat(arr2);\n\nconst result2 = arr2.concat(arr1, arr3);\n\nconsole.log('arr1 =' + JSON.stringify(arr1));\nconsole.log('arr2 =' + JSON.stringify(arr2));\nconsole.log('arr3 =' + JSON.stringify(arr3));\n\nconsole.log('result1 =' + JSON.stringify(result1));\nconsole.log('result2 =' + JSON.stringify(result2));\n```\n\n打印结果：\n\n```javascript\narr1 = [1, 2, 3];\narr2 = ['a', 'b', 'c'];\narr3 = ['千古壹号', 'vae'];\n\nresult1 = [1, 2, 3, 'a', 'b', 'c'];\nresult2 = ['a', 'b', 'c', 1, 2, 3, '千古壹号', 'vae'];\n```\n\n从打印结果中可以看到，原数组并没有被修改。\n\n**数组合并的另一种方式**：\n\n我们可以使用`...`这种扩展运算符，将两个数组进行合并。举例如下：\n\n```js\nconst arr1 = [1, 2, 3];\n\nconst result = ['a', 'b', 'c', ...arr1];\nconsole.log(JSON.stringify(result)); // 打印结果：[\"a\",\"b\",\"c\",1,2,3]\n```\n\n备注：数组不能使用加号进行拼接。如果使用加号进行拼接会先转换成字符串再拼接。\n\n### slice()\n\n`slice()`：从数组中**提取**指定的一个或者多个元素，返回结果为**新的数组**（不会改变原来的数组）。\n\n备注：该方法不会改变原数组，而是将截取到的元素封装到一个新数组中返回。\n\n**语法**：\n\n```javascript\n新数组 = 原数组.slice(开始位置的索引);\n\n新数组 = 原数组.slice(开始位置的索引, 结束位置的索引);  //注意：提取的元素中，包含开始位置，不包含结束位置\n```\n\n举例：\n\n```javascript\nconst arr = ['a', 'b', 'c', 'd', 'e', 'f'];\n\nconst result1 = arr.slice(); // 不加参数时，则获取所有的元素。相当于数组的整体赋值\nconst result2 = arr.slice(2); // 从第二个值开始提取，直到末尾\nconst result3 = arr.slice(-2); // 提取最后两个元素\nconst result4 = arr.slice(2, 4); // 提取从第二个到第四个之间的元素（不包括第四个元素）\nconst result5 = arr.slice(4, 2); // 空\n\nconsole.log('arr:' + JSON.stringify(arr));\nconsole.log('result1:' + JSON.stringify(result1));\nconsole.log('result2:' + JSON.stringify(result2));\nconsole.log('result3:' + JSON.stringify(result3));\nconsole.log('result4:' + JSON.stringify(result4));\nconsole.log('result5:' + JSON.stringify(result5));\n```\n\n打印结果：\n\n```javascript\narr: ['a', 'b', 'c', 'd', 'e', 'f'];\nresult1: ['a', 'b', 'c', 'd', 'e', 'f'];\nresult2: ['c', 'd', 'e', 'f'];\nresult3: ['e', 'f'];\nresult4: ['c', 'd'];\nresult5: [];\n```\n\n**补充**：\n\n很多前端开发人员会用 slice()将伪数组，转化为真数组。写法如下：\n\n```javascript\n// 方式1\narray = Array.prototype.slice.call(arrayLike);\n\n// 方式2\narray = [].slice.call(arrayLike);\n```\n\nES6 看不下去这种蹩脚的转化方法，于是出了一个新的 API：（专门用来将伪数组转化成真数组）\n\n```javascript\narray = Array.from(arrayLike);\n```\n\n关于这个 API 的详细介绍，上面的内容已经讲了，请往前翻。\n\n### fill()\n\n`fill()`：用一个固定值填充数组，返回结果为**新的数组**。会改变原数组。\n\n语法：\n\n```js\n// 用一个固定值填充数组。数组里的每个元素都会被这个固定值填充\n新数组 = 数组.fill(固定值);\n\n// 从 startIndex 开始的数组元素，用固定值填充\n新数组 = 数组.fill(固定值, startIndex);\n\n// 从 startIndex 到 endIndex 之间的元素（包左不包右），用固定值填充\n新数组 = 数组.fill(固定值, startIndex, endIndex);\n```\n\n举例1：\n\n```js\n// 创建一个长度为4的空数组，然后用 'f' 来填充这个空数组\nconsole.log(Array(4).fill('f')); // ['f', 'f', 'f,' 'f']\n\n// 将现有数组的每一个元素都进行填充\nconsole.log(['a', 'b', 'c', 'd'].fill('f')); // ['f', 'f', 'f,' 'f']\n\n```\n\n举例2：\n\n```js\n// 指定位置进行填充\nlet arr1 = ['a', 'b', 'c', 'd'];\nlet arr2 = arr1.fill('f', 1, 3);\n\nconsole.log(arr1); // ['a', 'f', 'f,' 'd']\nconsole.log(arr2); // ['a', 'f', 'f,' 'd']\n```\n\n## reverse()\n\n`reverse()`：反转数组，返回结果为**反转后的数组**（会改变原来的数组）。\n\n语法：\n\n```js\n反转后的数组 = 数组.reverse();\n```\n\n举例：\n\n```javascript\nvar arr = ['a', 'b', 'c', 'd', 'e', 'f'];\n\nvar result = arr.reverse(); // 将数组 arr 进行反转\n\nconsole.log('arr =' + JSON.stringify(arr));\nconsole.log('result =' + JSON.stringify(result));\n```\n\n打印结果：\n\n```\narr =[\"f\",\"e\",\"d\",\"c\",\"b\",\"a\"]\nresult =[\"f\",\"e\",\"d\",\"c\",\"b\",\"a\"]\n```\n\n从打印结果可以看出，原来的数组已经被改变了。\n\n## sort()\n\n> sort()方法需要好好理解。\n\n`sort()`：对数组的元素进行从小到大来排序（会改变原来的数组）。\n\n### 无参时\n\n如果在使用 sort() 方法时不带参，则默认按照元素的**Unicode 编码**，从小到大进行排序。\n\n**举例 1**：（当数组中的元素为字符串时）\n\n```javascript\nlet arr1 = ['e', 'b', 'd', 'a', 'f', 'c'];\n\nlet result = arr1.sort(); // 将数组 arr1 进行排序\n\nconsole.log('arr1 =' + JSON.stringify(arr1));\nconsole.log('result =' + JSON.stringify(result));\n```\n\n打印结果：\n\n```\n    arr1 =[\"a\",\"b\",\"c\",\"d\",\"e\",\"f\"]\n    result =[\"a\",\"b\",\"c\",\"d\",\"e\",\"f\"]\n```\n\n从上方的打印结果中，我们可以看到，sort 方法会改变原数组，而且方法的返回值也是同样的结果。\n\n**举例 2**：（当数组中的元素为数字时）\n\n```javascript\nlet arr2 = [5, 2, 11, 3, 4, 1];\n\nlet result = arr2.sort(); // 将数组 arr2 进行排序\n\nconsole.log('arr2 =' + JSON.stringify(arr2));\nconsole.log('result =' + JSON.stringify(result));\n```\n\n打印结果：\n\n```\narr2 =[1,11,2,3,4,5]\nresult =[1,11,2,3,4,5]\n```\n\n上方的打印结果中，你会发现，使用 sort() 排序后，数字`11`竟然在数字`2`的前面。这是为啥呢？因为上面讲到了，`sort()`方法是按照**Unicode 编码**进行排序的。\n\n那如果我想让 arr2 里的数字，完全按照从小到大排序，怎么操作呢？继续往下看。\n\n### 带参时，自定义排序规则\n\n如果在 sort()方法中带参，我们就可以**自定义**排序规则。具体做法如下：\n\n我们可以在 sort()的参数中添加一个回调函数，来指定排序规则。回调函数中需要定义两个形参，JS将会分别使用数组中的元素作为实参去调用回调函数。\n\nJS根据回调函数的返回值来决定元素的排序：（重要）\n\n-   如果返回一个大于 0 的值，则元素会交换位置\n\n-   **如果返回一个小于 0 的值，则不交换位置**。\n\n-   如果返回一个等于 0 的值，则认为两个元素相等，则不交换位置\n\n如果只是看上面的文字，可能不太好理解，我们来看看下面的例子，你肯定就能明白。\n\n### 举例：将数组中的数字按照从小到大排序\n\n**写法 1**：\n\n```javascript\nvar arr = [5, 2, 11, 3, 4, 1];\n\n// 自定义排序规则\nvar result = arr.sort(function (a, b) {\n    if (a > b) {\n        // 如果 a 大于 b，则交换 a 和 b 的位置\n        return 1;\n    } else if (a < b) {\n        // 如果 a 小于 b，则位置不变\n        return -1;\n    } else {\n        // 如果 a 等于 b，则位置不变\n        return 0;\n    }\n});\n\nconsole.log('arr =' + JSON.stringify(arr));\nconsole.log('result =' + JSON.stringify(result));\n```\n\n打印结果：\n\n```javascript\narr = [1, 2, 3, 4, 5, 11];\nresult = [1, 2, 3, 4, 5, 11];\n```\n\n上方代码的写法太啰嗦了，其实也可以简化为如下写法：\n\n**写法 2**：（ES5写法）\n\n```javascript\nvar arr = [5, 2, 11, 3, 4, 1];\n\n// 自定义排序规则\nvar result = arr.sort(function (a, b) {\n    return a - b; // 升序排列\n    // return b - a; // 降序排列\n});\n\nconsole.log('arr =' + JSON.stringify(arr));\nconsole.log('result =' + JSON.stringify(result));\n```\n\n打印结果不变。\n\n上方代码还可以写成 ES6 的形式，也就是将 function 改为箭头函数，其写法如下。\n\n**写法 3**：（ES6写法，箭头函数）\n\n```js\nlet arr = [5, 2, 11, 3, 4, 1];\n\n// 自定义排序规则\nlet result = arr.sort((a, b) => {\n    return a - b; // 升序排列\n});\n\nconsole.log('arr =' + JSON.stringify(arr));\nconsole.log('result =' + JSON.stringify(result));\n```\n\n上方代码，因为函数体内只有一句话，所以可以去掉 return 语句，继续简化为如下写法。\n\n**写法 4**：（推荐写法）\n\n```js\nlet arr = [5, 2, 11, 3, 4, 1];\n\n// 自定义排序规则：升序排列\nlet result = arr.sort((a, b) => a - b);\n\nconsole.log('arr =' + JSON.stringify(arr));\nconsole.log('result =' + JSON.stringify(result));\n```\n\n上面的各种写法中，写法 4 是我们在实战开发中用得最多的。\n\n为了确保代码的简洁优雅，接下来的讲解中，凡是涉及到函数，我们将尽量采用 ES6 中的箭头函数来写。\n\n### 举例：将数组从小到大排序\n\n将数组从小到大排序，这个例子很常见。但在实际开发中，总会有一些花样。\n\n下面这段代码，在实际开发中，经常用到，一定要掌握。完整代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <script>\n            let dataList = [\n                {\n                    title: '品牌鞋子，高品质低价入手',\n                    publishTime: 200,\n                },\n                {\n                    title: '不是很贵，但是很暖',\n                    publishTime: 100,\n                },\n                {\n                    title: '无法拒绝的美食，跟我一起吃',\n                    publishTime: 300,\n                },\n            ];\n\n            console.log('qianguyihao 排序前的数组：' + JSON.stringify(dataList));\n\n            // 将dataList 数组，按照 publishTime 字段，从小到大排序。（会改变原数组）\n            dataList.sort((a, b) => parseInt(a.publishTime) - parseInt(b.publishTime));\n\n            console.log('qianguyihao 排序后的数组：' + JSON.stringify(dataList));\n        </script>\n    </body>\n</html>\n```\n\n打印结果：\n\n```\nqianguyihao 排序前的数组：[\n    {\"title\":\"品牌鞋子，高品质低价入手\",\"publishTime\":200},\n    {\"title\":\"不是很贵，但是很暖\",\"publishTime\":100},\n    {\"title\":\"无法拒绝的美食，跟我一起吃\",\"publishTime\":300}\n]\n\nqianguyihao 排序后的数组：[\n    {\"title\":\"不是很贵，但是很暖\",\"publishTime\":100},\n    {\"title\":\"品牌鞋子，高品质低价入手\",\"publishTime\":200},\n    {\"title\":\"无法拒绝的美食，跟我一起吃\",\"publishTime\":300}\n]\n```\n\n上方代码中，肯定有人会问： publishTime 字段已经是 int 类型了，为啥在排序前还要做一次 parseInt() 转换？这是因为，这种数据，一般是后台接口返回给前端的，数据可能是 int 类型、也可能是字符串类型，所以前端还是统一先做一下 partInt() 比较保险。这是一种良好的工作习惯和风险意识。\n\n## indexOf() 和 lastIndexOf()：获取元素的索引\n\n**语法 1**：\n\n```javascript\n元素的索引 = 数组.indexOf(想要查询的元素);\n\n元素的索引 = 数组.lastIndexOf(想要查询的元素);\n```\n\n备注：`indexOf()` 是从左往右查找元素的位置。同理，`lastIndexOf()`是从右往左寻找。\n\n**解释**：可以检索一个数组中是否含有指定的元素。如果数组中含有该元素，则会返回其**第一次出现**的索引，并立即停止查找；如果没有找到指定的内容，则返回 -1。\n\n这个方法的作用：\n\n-   如果找到了指定的元素，就返回元素对应的位置。\n\n-   如果没有找到指定的元素，就会返回-1。\n\n**注意**：`indexOf()`在检索时，是严格类型约束，类似于`===`。\n\n**举例** ：\n\n```javascript\nconst arr = ['a', 'b', 'c', 'd', 'e', 'd', 'c'];\n\nconsole.log(arr.indexOf('c')); //从前往后，找第一个\"c\"在哪个位置\nconsole.log(arr.lastIndexOf('d')); //从后往前，找第一个\"d\"在哪个位置\n```\n\n打印结果：\n\n```\n2\n5\n```\n\n**举例**：\n\n```js\nlet arr = ['1', '2', '3', '4', '5'];\nconsole.log(arr.indexOf(2));\n```\n\n打印结果：\n\n```\n-1\n```\n\n**语法 2**：\n\n这个方法还可以指定第二个参数，用来指定查找的**起始位置**。语法如下：\n\n```javascript\n索引值 = 数组.indexOf(想要查找的元素, [查找的起始位置]);\n```\n\n这个方法的第二个参数非常巧妙，数据结构与算法的面试题中，时常出现。\n\n举例：（两个参数时，需要特别注意）\n\n```javascript\nlet arr = ['q', 'i', 'a', 'n', 'g', 'u', 'y', 'i', 'h', 'a', 'o'];\nresult = str.indexOf('a', 3); // 从下标为3的位置开始查找 'a'这个元素 【重要】\n\nconsole.log(result); // 打印结果：9\n```\n\n上方代码中，`indexOf()`方法中携带了两个参数，具体解释请看注释。\n\n## includes()\n\n**语法**：\n\n```js\n布尔值 = arr.includes(想要查找的元素, [position]);\n```\n\n**解释**：判断一个数组中是否包含指定的元素。如果是，则会返回 true；否则返回 false。\n\n参数中的 `position`：如果不指定，则默认为0；如果指定，则规定了检索的起始位置。\n\n```js\nconst arr = [11, 12, 13, 14, 15];\nconsole.log(arr.includes(12)); // 打印结果：true\nconsole.log(arr.includes(20)); // 打印结果：false\n\nconsole.log(arr.includes(11, 1)); // 打印结果：false\n```\n\n## find()和findIndex()\n\n### find()\n\n**语法**：\n\n```javascript\nconst itemResult = arr.find((currentItem, currentIndex, currentArray) => {\n    return true;\n});\n```\n\n**作用**：找出**第一个**满足「指定条件返回 true」的元素，并立即停止查找；如果没找到，则返回 undefined。\n\n备注：一旦找到符合条件的第一个元素，将不再继续往下遍历。\n\n\n举例1：\n\n```javascript\nlet arr = [2, 3, 2, 5, 7, 6];\n\nlet result = arr.find((item, index) => {\n    return item > 4; //遍历数组arr，一旦发现有第一个元素大于4，就把这个元素返回\n  \t// 上面这行代码是简写方式；完整写法也可以这样写：ccif (item > 4) {return true}\n});\n\nconsole.log(result); //打印结果：5\n```\n\n重要提醒：如果改变了 itemResult 内部的值，则 arr 里的对应元素，它的值也会被改变。举例如下。\n\n举例2：todo\n\n\n\n\n### findIndex()\n\n**语法**：\n\n```javascript\nconst indexResult = arr.findIndex((currentItem, currentIndex, currentArray) => {\n    return true;\n});\n```\n\n**作用**：找出**第一个**满足「指定条件返回 true」的元素的索引，并立即停止遍历；如果没找到，则返回 -1。\n\n举例：\n\n> 我们直接把上面find 方法的代码示例改成 findIndex，看看效果。\n\n```javascript\nlet arr = [2, 3, 2, 5, 7, 6];\n\nlet result = arr.findIndex((item, index) => {\n    return item > 4; //遍历数组arr，一旦发现有第一个元素大于4，就把这个元素的index返回\n});\n\nconsole.log(result); //打印结果：3\n```\n\n## every()和some()\n\n### every()\n\n**语法**：\n\n```javascript\nconst boolResult = arr.every((currentItem, currentIndex, currentArray) => {\n    return true;\n});\n```\n\n\n\n`every()`：对数组中每一项运行回调函数，如果都返回 true，every 就返回 true；如果有一项返回 false，则停止遍历，此方法返回 false。\n\n注意：every()方法的返回值是 boolean 值，参数是回调函数。\n\n举例：\n\n```javascript\nvar arr1 = ['千古', '宿敌', '南山忆', '素颜'];\nvar bool1 = arr1.every(function (item, index, array) {\n    if (item.length > 2) {\n        return false;\n    }\n    return true;\n});\nconsole.log(bool1); //输出结果：false。只要有一个元素的长度是超过两个字符的，就返回false\n\nvar arr2 = ['千古', '宿敌', '南山', '素颜'];\nvar bool2 = arr2.every(function (item, index, array) {\n    if (item.length > 2) {\n        return false;\n    }\n    return true;\n});\nconsole.log(bool2); //输出结果：true。因为每个元素的长度都是两个字符。\n```\n\n### some()\n\n`some()`：对数组中每一个元素运行回调函数，只要有一个元素返回 true，则停止遍历，此方法返回 true。\n\n注意：some()方法的返回值是 boolean 值。\n\n### every() 和 some() 的使用场景\n\nevery() 和 some() 这两个方法，初学者很容易搞混。要怎么区分呢？你可以这样记：\n\n-   every()：全部真，才为真。当你需要让数组中的每一个元素都满足指定条件时，那就使用 every()。\n\n-   some()：一个真，则为真，点到为止。数组中只要有一个元素满足指定条件时，就停止遍历。那就使用 some()。\n\n## valueOf()：返回数组本身\n\n```javascript\n数组本身 = 数组.valueOf();\n```\n\n这个方法的意义不大。因为我们直接写数组对象的名字，就已经是数组本身了。\n\n## 遍历数组\n\n### 概念\n\n**遍历数组**：获取并操作数组中的每一个元素，然后得到想要的返回结果。在实战开发中使用得非常频繁。\n\n语法：\n\n```js\n// ES5语法\n数组/boolean/无 = 数组.forEach/map/filter(function (item, index, arr) {\n   相关代码和返回值；\n})\n\n// ES6语法\n数组/boolean/无 = 数组.forEach/map/filter((item, index, arr) => {\n   相关代码和返回值；\n})\n```\n\n有了上面这些方法（其实远不止这几个），就可以替代 for 循环了。\n\n\n\n我们先来看看传统的for循环，然后依次介绍其他方法。\n\n### for 循环遍历\n\n举例：\n\n```javascript\nconst arr = ['千古壹号', '许嵩', 'vae'];\nfor (let i = 0; i < arr.length; i++) {\n    console.log(arr[i]); // arr[i]代表的是数组中的每一个元素i\n}\n\nconsole.log(JSON.stringify(arr));\n```\n\n打印结果：\n\n```\n千古壹号\n许嵩\nvae\n\n[\"千古壹号\",\"许嵩\",\"vae\"]\n```\n\n## forEach()\n\n> `forEach()` 这种遍历方法只支持 IE8 以上的浏览器。IE8 及以下的浏览器均不支持该方法。所以如果需要兼容 IE8，则不要使用 forEach，改为使用 for 循环来遍历即可。\n\n### 语法\n\n```js\n// ES5语法\narr.forEach(function (currentItem, currentIndex, currentArray) {\n\tconsole.log(currentValue);\n});\n\n// ES6语法\narr.forEach((currentItem, currentIndex, currentArray) => {\n\tconsole.log(currentValue);\n});\n```\n\nforEach()方法需要一个函数作为参数。这种函数，是由我们创建但是不由我们调用的，我们称为回调函数。\n\n数组中有几个元素，该回调函数就会执行几次。\n\n回调函数中传递三个参数：\n\n-   参数1：当前正在遍历的元素\n\n-   参数2：当前正在遍历的元素的索引\n\n-   参数3：正在遍历的数组\n\n注意，forEach() 没有返回值。也可以理解成：forEach() 的返回值是 undefined。如果你尝试 `newArray = currentArray.forEach()`这种方式来接收，是达不到效果的。\n\n代码举例：\n\n```javascript\nlet myArr = ['王一', '王二', '王三'];\n\nmyArr.forEach((currentItem, currentIndex, currentArray) => {\n    console.log('item:' + currentItem);\n    console.log('index:' + currentIndex);\n    console.log('arr:' + JSON.stringify(currentArray));\n    console.log('----------');\n});\n```\n\n打印结果：\n\n```javascript\nitem:王一\nindex:0\narr:[\"王一\",\"王二\",\"王三\"]\n----------\nitem:王二\nindex:1\narr:[\"王一\",\"王二\",\"王三\"]\n----------\nitem:王三\nindex:2\narr:[\"王一\",\"王二\",\"王三\"]\n----------\n```\n\n### forEach() 会不会改变原数组？\n\nforEach() 会不会改变原数组？关于这个问题，大部分人会搞错。我们来看看下面的代码。\n\n**1、数组的元素是基本数据类型**：（无法改变原数组）\n\n```js\nlet numArr = [1, 2, 3];\n\nnumArr.forEach((item) => {\n    item = item * 2;\n});\nconsole.log(JSON.stringify(numArr)); // 打印结果：[1, 2, 3]\n```\n\n上面这段代码，你可要看仔细了，打印结果是 `[1, 2, 3]`，不是 `[2, 4, 6]`。\n\n**2、数组的元素是引用数据类型**：（直接修改整个元素对象时，无法改变原数组）\n\n```js\nlet objArr = [\n    { name: '千古壹号', age: 20 },\n    { name: '许嵩', age: 30 },\n];\n\nobjArr.forEach((item) => {\n    item = {\n        name: '邓紫棋',\n        age: '29',\n    };\n});\nconsole.log(JSON.stringify(objArr)); // 打印结果：[{\"name\":\"千古壹号\",\"age\":20},{\"name\":\"许嵩\",\"age\":30}]\n```\n\n**3、数组的元素是引用数据类型**：（修改元素对象里的某个属性时，可以改变原数组）\n\n```js\nlet objArr = [\n    { name: '千古壹号', age: 28 },\n    { name: '许嵩', age: 30 },\n];\n\nobjArr.forEach((item) => {\n    item.name = '邓紫棋';\n});\nconsole.log(JSON.stringify(objArr)); // 打印结果：[{\"name\":\"邓紫棋\",\"age\":28},{\"name\":\"邓紫棋\",\"age\":30}]\n```\n\n如果你需要通过 forEach 修改原数组，建议用 forEach 里面的参数 2 和参数 3 来做，具体请看下面的标准做法。\n\n**4、forEach() 通过参数 2、参数 3 修改原数组**：（标准做法，一定要看）\n\n```js\n// 1、数组的元素是基本数据类型\nlet numArr = [1, 2, 3];\n\nnumArr.forEach((item, index, arr) => {\n    arr[index] = arr[index] * 2;\n});\nconsole.log(JSON.stringify(numArr)); // 打印结果：[2,4,6]\n\n// 2、数组的元素是引用数据类型时，直接修改对象\nlet objArr = [\n    { name: '千古壹号', age: 28 },\n    { name: '许嵩', age: 34 },\n];\n\nobjArr.forEach((item, index, arr) => {\n    arr[index] = {\n        name: '小明',\n        age: '10',\n    };\n});\nconsole.log(JSON.stringify(objArr)); // 打印结果：[{\"name\":\"小明\",\"age\":\"10\"},{\"name\":\"小明\",\"age\":\"10\"}]\n\n// 3、数组的元素是引用数据类型时，修改对象的某个属性\nlet objArr2 = [\n    { name: '千古壹号', age: 28 },\n    { name: '许嵩', age: 34 },\n];\n\nobjArr2.forEach((item, index, arr) => {\n    arr[index].name = '小明';\n});\nconsole.log(JSON.stringify(objArr2)); // 打印结果：[{\"name\":\"小明\",\"age\":28},{\"name\":\"小明\",\"age\":34}]\n```\n\n**总结**：\n\n如果纯粹只是遍历数组，那么，可以用 forEach() 方法。但是，如果你想在遍历数组的同时，去改变数组里的元素内容，那么，最好是用 map() 方法来做，不要用 forEach()方法，避免出现一些低级错误。\n\n参考链接：\n\n-   [forEach 到底可以改变原数组吗？](https://juejin.im/post/5d526a4ae51d4557dc774e7d)\n\n-   [forEach 会改变原数组值吗](https://lhajh.github.io/js/2018/05/26/Does-forEach-change-the-original-array-value.html)\n\n### 空数组调用 forEach() 方法时，会不会报错？\n\n例1：\n\n```js\nconst arr1 = undefined;\n\narr.forEach(item => {\n  console.log(item);\n  item.name = 'qianguyihao';\n});\n```\n\n上面的代码中，数组 arr1 并不存在，所以会报错`Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')`\n\n例2：\n\n```js\nconst arr2 = [];\n\narr2.forEach(item => {\n  console.log(item);\n  item.name = \"qianguyihao\";\n});\n```\n\n上面的代码中，arr2是空数组，但是在遍历时并不会报错，因为 forEach 是数组的内置方法。arr2作为空数组，是属于特殊的数组，数组在调用内置方法时不会报错。在上面的例2中，forEach 对空数组不会执行回调函数（也就意味着，console.log 那行不会执行），因为没有元素需要遍历。\n\n如果把 forEach() 换成 map()方法，也是一样的道理。\n\n## for of\n\nES6语法推出了 for of，可用于循环遍历数组。\n\n### 语法\n\n```js\nfor(let value of arr) {\n\tconsole.log(value);\n}\n```\n\n### 不要使用 for in 遍历数组\n\nfor in 是专门用于遍历对象的。对象的属性是无序的（而数组的元素有顺序），for in循环就是专门用于遍历无序的对象。所以，不要用 for in 遍历数组。\n\nfor in语法：\n\n```js\nfor (let key in obj) {\n\tconsole.log(key);\n\tconsole.log(obj.key);\n}\n```\n\n\n\n## map()\n\n### 语法\n\n```js\n// ES5语法\nconst newArr =  arr.map(function (currentItem, currentIndex, currentArray) {\n    return newItem;\n});\n\n// ES6语法\nconst newArr = arr.map((currentItem, currentIndex, currentArray) => {\n    return newItem;\n});\n```\n\n解释：对数组中每一项运行回调函数，返回该函数的结果，组成的新数组（返回的是**加工后**的新数组）。不会改变原数组。\n\n作用：对数组中的每一项进行加工。\n\n**举例 1**：（拷贝的过程中改变数组元素的值）\n\n有一个已知的数组 arr1，我要求让 arr1 中的每个元素的值都加 10，这里就可以用到 map 方法。代码举例：\n\n```javascript\nconst arr1 = [1, 3, 6, 2, 5, 6];\nconst arr2 = arr1.map(item => {\n  return item + 10; //让arr1中的每个元素加10\n});\nconsole.log(arr2); // 数组 arr2 的值：[11, 13, 16, 12, 15, 16]\n```\n\n**举例 2**：【重要案例，实际开发中经常用到】\n\n将 A 数组中某个属性的值，存储到 B 数组中。代码举例：\n\n```javascript\nconst arr1 = [\n    { name: '千古壹号', age: '28' },\n    { name: '许嵩', age: '32' },\n];\n\n// 举例2.1、将数组 arr1 中的 name 属性，存储到 数组 arr2 中\nconst arr2 = arr1.map(item => item.name);\n\n// 上面的代码是简写的方式。完整写法是下面这样：（这两种写法是等价的）\nconst _arr2 = arr1.map(item => {\n  return item.name;\n});\n\n// 举例2.2、将数组 arr1 中的 name、age这两个属性，改一下“键”的名字，存储到 arr3中\nconst arr3 = arr1.map(item => ({\n    myName: item.name,\n    myAge: item.age,\n})); // 将数组 arr1 中的 name 属性，存储到 数组 arr2 中\n\nconsole.log('arr1:' + JSON.stringify(arr1));\nconsole.log('arr2:' + JSON.stringify(arr2));\nconsole.log('arr3:' + JSON.stringify(arr3));\n```\n\n打印结果：\n\n```\narr1:[{\"name\":\"千古壹号\",\"age\":\"28\"},{\"name\":\"许嵩\",\"age\":\"32\"}]\n\narr2:[\"千古壹号\",\"许嵩\"]\n\narr3:[{\"myName\":\"千古壹号\",\"myAge\":\"28\"},{\"myName\":\"许嵩\",\"myAge\":\"32\"}]\n\n```\n\nmap 的应用场景，主要就是以上两种。\n\n### map() 方法会不会改变原数组？\n\n答案：不一定。\n\n举例：\n\n```javascript\n      const arr = [\n        {\n          name: \"qianguyihao1\",\n          age: 22,\n        },\n        {\n          name: \"qianguyihao2\",\n          age: 23,\n        },\n      ];\n\n      arr.map((item) => {\n        item.name = \"haha\"; // 修改 item 里的某个属性\n        return item;\n      });\n      console.log(JSON.stringify(arr));\n```\n\n打印结果：\n\n```\n[{\"name\":\"haha\",\"age\":22},{\"name\":\"haha\",\"age\":23}]\n```\n\n总结：map方法如果是修改整个item的值，则不会改变原数组。但如果是修改 item 里面的某个属性，那就会改变原数组。\n\n\n### map()在遍历时，如果不写 return 会怎么样\n\n举例：\n\n```js\nconst arr1 = [{ name: 'hehe1' }, { name: 'hehe2' }];\n\nconst arr2 = arr1.map(item => {\n  item.name = 'haha';\n});\n\nconsole.log(arr1);\nconsole.log(arr2);\n```\n\n代码执行完成后：\n\n- arr1 的结果：[{ name: 'haha' }, { name: 'haha' }]\n\n- arr2 的结果：[undefined, undefined]\n\n由此可见，如果 map() 方法中没有 return 语句也是合法的，它会默认返回 `undefined`。\n\n所以，针对对象数组，**如果你只是想修改对象中的某个属性值，而不想创建新数组的话，建议使用 forEach() 方法，而不是 map() 方法**。map() 方法的初衷是创建一个新数组。\n\n\n## filter()\n\n### 语法\n\n\n\n```js\nconst newArr = arr.filter((currentItem, currentIndex, currentArray) => {\n    return true;\n});\n```\n\n解释：对数组中的**每一项**运行回调函数，该函数返回结果是 true 的项，将组成新的数组（返回值就是这个新数组）。不会改变原数组。\n\n作用：对数组进行过滤。\n\n### 举例\n\n**举例 1**：找出数组 arr1 中大于 4 的元素，返回一个新的数组。代码如下：\n\n```javascript\nlet arr1 = [1, 3, 6, 2, 5, 6];\n\nlet arr2 = arr1.filter(item => {\n    if (item > 4) {\n        return true; // 将arr1中大于4的元素返回，组成新的数组\n    }\n    return false;\n});\n\nconsole.log(JSON.stringify(arr1)); // 打印结果：[1,3,6,2,5,6]\nconsole.log(JSON.stringify(arr2)); // 打印结果：[6,5,6]\n```\n\n上方代码更简洁的写法如下：\n\n```javascript\nlet arr1 = [1, 3, 6, 2, 5, 6];\n\nlet arr2 = arr1.filter(item => item > 4); // 将arr1中大于4的元素返回，组成新的数组\n\nconsole.log(JSON.stringify(arr1)); // 打印结果：[1,3,6,2,5,6]\nconsole.log(JSON.stringify(arr2)); // 打印结果：[6,5,6]\n```\n\n**举例 2**：\n\n获取对象数组 arr1 中指定类型的对象，放到数组 arr2 中。代码举例如下：\n\n```javascript\nconst arr1 = [\n  { name: '许嵩', type: '一线' },\n  { name: '周杰伦', type: '退居二线' },\n  { name: '邓紫棋', type: '一线' },\n];\n\nconst arr2 = arr1.filter(item => item.type == '一线'); // 筛选出一线歌手\n\nconsole.log(JSON.stringify(arr2));\n```\n\n打印结果：\n\n```javascript\n[\n    { name: '许嵩', type: '一线' },\n    { name: '邓紫棋', type: '一线' },\n];\n```\n\n### 两端代码对比\n\n仔细看看下面这两段代码，有什么区别。数组 arr2的打印结果是不一样的。\n\n第一段代码：\n\n```js\nconst arr1 = [\n  {\n    name: 'a',\n    num: 1,\n  },\n  {\n    name: 'b',\n    num: 2,\n  },\n];\n\nconst arr2 = [];\n\nconst arr3 = dataList.filter(item => {\n  return item.num === 1;\n  arr2.push(item);\n});\n\nconsole.log(arr2);\n```\n\n第二段代码：\n\n```js\nconst arr1 = [\n  {\n    name: 'a',\n    num: 1,\n  },\n  {\n    name: 'b',\n    num: 2,\n  },\n];\n\nconst arr2 = [];\n\nconst arr3 = dataList.filter(item => {\n  if (item.num === 1) return item;\n  arr2.push(item);\n});\n\nconsole.log('smyhvae arr2:', arr2);\n```\n\n分析：\n\n- 第一段代码的打印结果是 空数组 `[]`。因为`return` 语句位于回调函数的第一行，所以一旦执行就直接返回，导致后面的 `arr2.push(item);` 永远不会被执行，因此 `arr2` 始终为空。\n- 第二段代码的打印结果是` [{ name: 'b', num: 2 }]`。由于 `return` 语句位于 `if` 语句内部，只有在特定条件下（`item.num === 1`）才会终止回调函数，否则 `arr2.push(item);` 仍然会被执行，因此 `arr2` 中会有值。\n\n\n\n## reduce()\n\n### reduce() 语法\n\n> reduce 的发音：[rɪ'djuːs]。中文含义是减少，但这个方法跟“减少”没有任何关系。\n\nreduce() 方法接收一个函数作为累加器，数组中的每个值（从左到右）开始缩减，最终计算为一个值。返回值是回调函数累计处理的结果。\n\n**语法**：\n\n```javascript\narr.reduce(function (previousValue, currentValue, currentIndex, arr) {}, initialValue);\n```\n\n参数解释：\n\n-   previousValue：必填，上一次调用回调函数时的返回值\n\n-   currentValue：必填，当前正在处理的数组元素\n\n-   currentIndex：选填，当前正在处理的数组元素下标\n\n-   arr：选填，调用 reduce()方法的数组\n\n-   initialValue：选填，可选的初始值（作为第一次调用回调函数时传给 previousValue 的值）\n\n在以往的数组方法中，匿名的回调函数里是传三个参数：item、index、arr。但是在 reduce() 方法中，前面多传了一个参数`previousValue`，这个参数的意思是上一次调用回调函数时的返回值。第一次执行回调函数时，previousValue 没有值怎么办？可以用 initialValue 参数传给它。\n\n备注：绝大多数人在一开始接触 reduce() 的时候会很懵逼，但是没关系，有事没事多看几遍，自然就掌握了。如果能熟练使用 reduce() 的用法，将能替代很多其他的数组方法，并逐渐走上进阶之路，领先于他人。\n\n为了方便理解 reduce()，我们先来看看下面的简单代码，过渡一下：\n\n```js\nlet arr1 = [1, 2, 3, 4, 5, 6];\n\narr1.reduce((prev, item) => {\n    console.log(prev);\n    console.log(item);\n    console.log('------');\n    return 88;\n}, 0);\n```\n\n打印结果：\n\n```\n0\n1\n------\n88\n2\n------\n88\n3\n------\n88\n4\n------\n88\n5\n------\n88\n6\n------\n```\n\n上面的代码中，由于`return`的是固定值，所以 prev 打印的也是固定值（只有初始值是 0，剩下的遍历中，都是打印 88）。\n\n现在来升级一下，实际开发中，prev 的值往往是动态变化的，这便是 reduce()的精妙之处。我们来看几个例子就明白了。\n\n### reduce() 的常见应用\n\n**举例 1**、求和：\n\n计算数组中所有元素项的总和。代码实现：\n\n```javascript\nconst arr = [2, 0, 1, 9, 6];\n// 数组求和\nconst total = arr.reduce((prev, item) => {\n    return prev + item;\n});\n\nconsole.log('total:' + total); // 打印结果：18\n```\n\n**举例 2**、统计某个元素出现的次数：\n\n代码实现：\n\n```js\n// 定义方法：统一 value 这个元素在数组 arr 中出现的次数\nfunction repeatCount(arr, value) {\n    if (!arr || arr.length == 0) return 0;\n\n    return arr.reduce((totalCount, item) => {\n        totalCount += item == value ? 1 : 0;\n        return totalCount;\n    }, 0);\n}\n\nlet arr1 = [1, 2, 6, 5, 6, 1, 6];\n\nconsole.log(repeatCount(arr1, 6)); // 打印结果：3\n```\n\n**举例 3**、求元素的最大值：\n\n代码实现：\n\n```js\nconst arr = [2, 0, 1, 9, 6];\n// 数组求最大值\nconst maxValue = arr.reduce((prev, item) => {\n    return prev > item ? prev : item;\n});\n\nconsole.log(maxValue); // 打印结果：9\n```\n\n参考链接：\n\n-   [JS reduce 函数](https://juejin.im/post/5d78aa3451882521397645ae)\n\n## 数组练习\n\n### splice()练习：数组去重\n\n代码实现：\n\n```javascript\n//创建一个数组\nconst arr = [1, 2, 3, 2, 2, 1, 3, 4, 2, 5];\n\n//去除数组中重复的数字\n//获取数组中的每一个元素\nfor (let i = 0; i < arr.length; i++) {\n    /*获取当前元素后的所有元素*/\n    for (let j = i + 1; j < arr.length; j++) {\n        //console.log(\"---->\"+arr[j]);\n        //判断两个元素的值是否相等\n        if (arr[i] == arr[j]) {\n            //如果相等则证明出现了重复的元素，则删除j对应的元素\n            arr.splice(j, 1);\n            //当删除了当前j所在的元素以后，后边的元素会自动补位\n            //此时将不会再比较这个元素，我们需要再比较一次j所在位置的元素\n            //使j自减\n            j--;\n        }\n    }\n}\n\nconsole.log(arr);\n```\n\n### 清空数组\n\n清空数组，有以下几种方式：\n\n```javascript\nconst arr = [1, 2, 3];\n\narr = []; //方式1：推荐\narr.length = 0; //方式2：length属性可以赋值，在其它语言中length是只读\narr.splice(0); //方式3：删除数组中所有元素。也可以写成 arr.splice(0, arr.length)\n```\n\n### join() 练习\n\n**问题**：将一个字符串数组输出为`|`分割的形式，比如“千古|宿敌|素颜”。使用两种方式实现。\n\n答案：\n\n方式 1：（不推荐）\n\n```javascript\nvar arr = ['千古', '宿敌', '素颜'];\nvar str = arr[0];\nvar separator = '|';\nfor (var i = 1; i < arr.length; i++) {\n    str += separator + arr[i]; //从第1个数组元素开始，每个元素前面加上符号\"|\"\n}\n\nconsole.log(str);\n```\n\n输出结果：\n\n![](http://img.smyhvae.com/20180126_1336.png)\n\n不推荐这种方式，因为：由于字符串的不变性，str 拼接过多的话，容易导致内存溢出（很多个 str 都堆放在栈里）。\n\n方式 2：（推荐。通过 array 数组自带的 api 来实现）\n\n```javascript\nvar arr = ['千古', '宿敌', '素颜'];\n\nconsole.log(arr.join('|'));\n```\n\n结果：\n\n![](http://img.smyhvae.com/20180126_1339.png)\n\n### reverse() 练习\n\n题目：将一个字符串数组的元素的顺序进行反转，使用两种种方式实现。提示：第 i 个和第 length-i-1 个进行交换。\n\n答案：\n\n方式 1：\n\n```javascript\nfunction reverse(array) {\n    var newArr = [];\n    for (var i = array.length - 1; i >= 0; i--) {\n        newArr[newArr.length] = array[i];\n    }\n    return newArr;\n}\n```\n\n方式 2：（算法里比较常见的方式）\n\n```javascript\nfunction reverse(array) {\n    for (var i = 0; i < array.length / 2; i++) {\n        var temp = array[i];\n        array[i] = array[array.length - 1 - i];\n        array[array.length - 1 - i] = temp;\n    }\n    return array;\n}\n```\n\n方式 3：（数组自带的 reverse 方法）\n\n现在我们学习了数组自带的 api，我们就可以直接使用 reverse()方法。\n\n### 练习：数组去重\n\n问题：编写一个方法去掉一个数组中的重复元素。\n\n分析：创建一个新数组，循环遍历，只要新数组中有老数组的值，就不用再添加了。\n\n答案：\n\n```javascript\n//    编写一个方法 去掉一个数组的重复元素\nvar arr = [1, 2, 3, 4, 5, 2, 3, 4];\nconsole.log(arr);\nvar aaa = fn(arr);\nconsole.log(aaa);\n//思路：创建一个新数组，循环遍历，只要新数组中有老数组的值，就不用再添加了。\nfunction fn(array) {\n    var newArr = [];\n    for (var i = 0; i < array.length; i++) {\n        //开闭原则\n        var bool = true;\n        //每次都要判断新数组中是否有旧数组中的值。\n        for (var j = 0; j < newArr.length; j++) {\n            if (array[i] === newArr[j]) {\n                bool = false;\n            }\n        }\n        if (bool) {\n            newArr[newArr.length] = array[i];\n        }\n    }\n    return newArr;\n}\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/20-函数简介.md",
    "content": "---\ntitle: 20-函数简介\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 函数的介绍\n\n函数：就是一些功能或语句的**封装**。在需要的时候，通过**调用**的形式，执行这些语句。\n\n补充：\n\n- **函数也是一个对象**\n\n- 使用`typeof`检查一个函数对象时，会返回 function\n\n**函数的作用**：\n\n- 一次定义，多次调用。将大量重复的语句抽取出来，写在函数里，以后需要这些语句时，可以直接调用函数，避免重复劳动。\n\n- 简化代码，可读性更强，让编程模块化。高内聚、低耦合。\n\n来看个例子：\n\n```javascript\nconsole.log(\"你好\");\nsayHello();\t// 调用函数\nsayHello();\t// 再调用一次函数\n\n\n\n// 定义函数\nfunction sayHello(){\n\tconsole.log(\"欢迎\");\n\tconsole.log(\"welcome\");\n}\n```\n\n## 函数的定义/声明\n\n我们使用`function`关键字定义函数，中文含义是“函数”、“功能”。可以使用如下方式进行定义。\n\n### 方式一：函数声明（命名函数）\n\n使用`函数声明`来创建一个函数。语法：\n\n```javascript\nfunction 函数名([形参1,形参2...形参N]){  // 备注：语法中的中括号，表示“可选”\n\t// 函数体语句\n}\n```\n\n举例：\n\n```javascript\nfunction sum(a, b){\n\treturn a+b;\n}\n```\n\n解释如下：\n\n- 函数名：命名规定和变量的命名规定一样，必须符合JS标识符的命名规则。只能是字母、数字、下划线、美元符号，不能以数字开头。\n\n- 圆括号里，是形参列表，可选。即使没有形参，也必须书写圆括号。\n\n- 大括号里，是函数体语句。\n\nPS：在有些编辑器中，方法写完之后，我们在方法的前面输入`/**`，然后回车，会发现，注释的格式会自动补齐。\n\n### 方式二：函数表达式（匿名函数）\n\n使用`函数表达式`来创建一个函数。语法：\n\n```javascript\nconst 变量名  = function([形参1,形参2...形参N]){\n\t语句....\n}\n```\n\n举例：\n\n```javascript\nconst fun2 = function() {\n\tconsole.log(\"我是匿名函数中封装的代码\");\n};\n```\n\n解释如下：\n\n\n- 上面的 fun2 是变量名，不是函数名。\n\n- 函数表达式的声明方式跟声明变量类似，只不过变量里存的是值，而函数表达式里存的是函数。\n\n- 函数表达式也可以传递参数。\n\n从方式二的举例中可以看出：所谓的“函数表达式”，其实就是将匿名函数赋值给一个变量。因为，一个匿名函数终究还是要给它一个接收对象，进而方便地调用这个函数。\n\n### 方式三：使用构造函数 new Function()\n\n使用构造函数`new Function()`来创建一个对象。这种方式，用的少。\n\n语法：\n\n```javascript\nconst 变量名/函数名  = new Function('形参1', '形参2', '函数体');\n```\n\n注意，Function 里面的参数都必须是**字符串**格式。也就是说，形参也必须放在**字符串**里；函数体也是放在**字符串**里包裹起来，放在 Function 的最后一个参数的位置。\n\n代码举例：\n\n```javascript\nconst fun3 = new Function('a', 'b', 'console.log(\"我是函数内部的内容\");  console.log(a + b);');\n\nfun3(1, 2); // 调用函数\n```\n\n打印结果：\n\n```\n我是函数内部的内容\n3\n```\n\n**分析**：\n\n方式3的写法很少用，原因如下：\n\n- 不方便书写：写法过于啰嗦和麻烦。\n\n- 执行效率较低：首先需要把字符串转换为 js 代码，然后再执行。\n\n### 小结\n\n1、**所有的函数，都是 `Fuction` 的“实例”**（或者说是“实例对象”）。函数本质上都是通过 new Function 得到的。\n\n2、函数既然是实例对象，那么，**函数也属于“对象”**。还可以通过如下特征，来佐证函数属于对象：\n\n（1）我们直接打印某一个函数，比如 `console.log(fun2)`，发现它的里面有`__proto__`。（这个是属于原型的知识，后续再讲）\n\n（2）我们还可以打印 `console.log(fun2 instanceof Object)`，发现打印结果为 `true`。这说明 fun2 函数就是属于 Object。\n\n## 函数的调用\n\n调用函数即：执行函数体中的语句。函数必须要等到被调用时才执行。\n\n### 方式1：普通函数的调用\n\n函数调用的语法：\n\n```javascript\n// 写法1（最常用）\n函数名();\n\n// 写法2\n函数名.call();\n```\n\n\n\n代码举例：\n\n```javascript\nfunction fn1() {\n\tconsole.log('我是函数体里面的内容1');\n}\n\nfunction fn2() {\n\tconsole.log('我是函数体里面的内容2');\n}\n\nfn1(); // 调用函数\n\nfn2.call(); // 调用函数\n\n```\n\n### 方式2：通过对象的方法来调用\n\n```javascript\nvar obj = {\n\ta: 'qianguyihao',\n\tfn2: function() {\n\t\tconsole.log('千古壹号，永不止步!');\n\t},\n};\n\nobj.fn2(); // 调用函数\n```\n\n如果一个函数是作为一个对象的属性保存，那么，我们称这个函数是这个对象的**方法**。\n\nPS：关于函数和方法的区别，本文的后续内容里有讲到，可以往下面翻。\n\n\n### 方式3：立即执行函数\n\n代码举例：\n\n```javascript\n(function() {\n\tconsole.log('我是立即执行函数');\n})();\n\n```\n\n立即执行函数在定义后，会自动调用。\n\nPS：关于立即执行函数，本文的后续内容里有讲到，可以往下面翻。\n\n\n上面讲到的这三种方式，是用得最多的。接下来讲到的三种方式，暂时看不懂也没关系，可以等学完其他的知识点，再回过头来看。\n\n### 方式4：通过构造函数来调用\n\n代码举例：\n\n```javascript\nfunction Fun3() {\n\tconsole.log('千古壹号，永不止步~');\n}\n\nnew Fun3();\n```\n\n这种方式用得不多。\n\n### 方式5：绑定事件函数\n\n代码举例：\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <div id=\"btn\">我是按钮，请点击我</div>\n\n        <script>\n            var btn = document.getElementById('btn');\n            //2.绑定事件\n            btn.onclick = function() {\n                console.log('点击按钮后，要做的事情');\n            };\n        </script>\n    </body>\n</html>\n\n```\n\n这里涉及到DOM操作和事件的知识点，后续再讲。\n\n### 方式6：定时器函数\n\n代码举例：（每间隔一秒，将 数字 加1）\n\n```javascript\n    let num = 1;\n   setInterval(function () {\n       num ++;\n       console.log(num);\n   }, 1000);\n```\n\n这里涉及到定时器的知识点。\n\n## 函数的参数：形参和实参\n\n### 定义\n\n函数的参数包括形参和实参。形参是函数内的一些**待定值**。在调用函数时，需传入这些参数的具体值（即实参）。\n\n可以在函数的`()`中指定一个或多个参数，也可以不指定参数。多个参数之间用英文逗号隔开。\n\n举例：\n\n```js\n// a, b 是形参，表示待定值\nfunction add(a, b) {\nconst sum = a + b;\nconsole.log(sum);\n}\n\n// 1, 2 是实参，表示传入的具体值。调用函数时，传入实参\nadd(1, 2);\n```\n\n**形参：**\n\n- 概念：形式上的参数。定义函数时传递的待定值（此时并不知道是什么值）。\n- 声明形参相当于在函数内部声明了变量，但并不赋值。也可以说，**形参的默认值是 undefined**。\n\n**实参**：\n\n- 概念：实际上的参数。调用函数时传递的具体值。实参将传递给函数中对应的形参。\n\n举例：\n\n```javascript\n// 调用函数\nadd(3, 4);\nadd(\"3\", 4);\nadd(\"Hello\", \"World\");\n\n// 定义函数：求和\nfunction add(a, b) {\n\tconsole.log(a + b);\n}\n```\n\n控制台输出结果：\n\n```\n7\n34\nhelloworld\n```\n\n### 形参和实参的个数\n\n实际参数和形式参数的个数，可以不同。调用函数时，解析器不会检查实参的数量。\n\n- 如果实参个数 > 形参个数，则末尾的实参是多余的，不会被赋值，因为没有形参能接收它。\n- 如果实参个数 < 形参个数，则末尾的形参是多余的，值是 undefined，因为它没有接收到实参。（undefined参与运算时，表达式的运算结果为NaN）\n\n代码举例：\n\n```javascript\n\tfunction sum(a, b) {\n\t\tconsole.log(a + b);\n\t}\n\n\tsum(1, 2);\n\tsum(1, 2, 3);\n\tsum(1);\n```\n\n打印结果：\n\n```\n3\n3\nNaN\n```\n\n### 实参的数据类型\n\n函数的实参可以是任意的数据类型。调用函数时，解析器不会检查实参类型，所以要注意，是否有可能会接收到非法的参数，如果有可能则需要对参数进行类型检查。\n\n## 函数的返回值\n\n### return 关键字\n\n函数体内可以没有返回值，也可以根据需要加返回值。语法格式：`return 函数的返回值`。\n\n举例：\n\n```javascript\nconsole.log(sum(3, 4)); // 将函数的返回值打印出来\n\n//函数：求和\nfunction sum(a, b) {\n\treturn a + b;\n}\n```\n\nreturn关键字的作用既可以是**终止函数**，也可以给函数添加返回值。\n\n解释：\n\n（1）return 后的返回值将会作为函数的执行结果返回，可以定义一个变量，来接收该返回值。\n\n（2）在函数中，return后的语句都不会执行。也就是说，函数在执行完 return 语句之后，会立即退出函数。\n\n（3）如果return语句后不跟任何值，就相当于返回一个undefined\n\n（4）如果函数中不写return，则也会返回undefined\n\n（5）返回值可以是任意的数据类型，可以是对象，也可以是函数。\n\n（6）return 只能返回一个值。如果用逗号隔开多个值，则以最后一个为准。\n\n### break、continue、return 的区别\n\n- break ：结束当前的循环体（如 for、while）\n\n- continue ：跳出本次循环，继续执行下次循环（如 for、while）\n\n- return ：1、退出循环。2、返回 return 语句中的值，同时结束当前的函数体内的代码，退出当前函数。\n\n## 函数名、函数体和函数加载问题（重要，请记住）\n\n我们要记住：**函数名 == 整个函数**。举例：\n\n```javascript\nconsole.log(fn) == console.log(function fn(){alert(1)});\n\n//定义fn方法\nfunction fn(){\n\talert(1)\n};\n```\n\n我们知道，当我们在调用一个函数时，通常使用`函数()`这种格式；可如果，我们是直接使用`函数`这种格式，它的作用相当于整个函数。\n\n**函数的加载问题**：JS加载的时候，只加载函数名，不加载函数体。所以如果想使用内部的成员变量，需要调用函数。\n\n### fn()  和 fn 的区别【重要】\n\n- `fn()`：调用函数。调用之后，还获取了函数的返回值。\n\n- `fn`：函数对象。相当于直接获取了整个函数对象。\n\n\n## 方法\n\n函数也可以成为对象的属性。**如果一个函数是作为一个对象的属性保存，那么，我们称这个函数是这个对象的方法**。\n\n调用这个函数就说调用对象的方法（method）。函数和方法，有什么本质的区别吗？它只是名称上的区别，并没有其他的区别。\n\n函数举例：\n\n```javascript\n\t// 调用函数\n\tfn();\n```\n\n方法举例：\n\n```javascript\n\t// 调用方法\n\tobj.fn();\n```\n\n我们可以这样说，如果直接是`fn()`，那就说明是函数调用。如果是`XX.fn()`的这种形式，那就说明是**方法**调用。\n\n## 类数组对象 arguments\n\n> 这部分，初学者可能看不懂，可以以后再来看。\n\n在调用函数时，浏览器每次都会传递进两个隐含的参数：\n\n- 1.函数的上下文对象 this\n\n- 2.**封装实参的对象** arguments\n\n这一段，我们来讲一下 arguments。例如：\n\n```javascript\nfunction foo() {\n    console.log(arguments);\n    console.log(typeof arguments);\n}\n\nfoo('a', 'b');\n```\n\n打印结果：\n\n![](https://img.smyhvae.com/20220725_2000.png)\n\n\n### 定义\n\n函数内的 arguments 是一个**类数组对象**，里面存储的是它接收到的**实参列表**。所有函数都内置了一个 arguments 对象，有个讲究的地方是：只有函数才有arguments。\n\n具体来说，在调用函数时，我们所传递的实参都会在 arguments 中保存。**arguments 代表的是所有实参**。\n\narguments 的展示形式是一个**伪数组**。意思是，它和数组有点像，但它并不是数组。它具有以下特点：\n\n- 可以进行遍历；具有数组的 length 属性，可以获取长度。\n\n- 可以通过索引（从0开始计数）存储数据、获取和操作数据。比如，我们可以通过索引访问某个实参。\n\n- 不能调用数组的方法。比如push()、pop() 等方法都没有。\n\n我们看一下 arguments 的使用。\n\n### arguments.length 返回函数实参的个数\n\narguments.length 可以用来获取**实参的个数**。\n\n举例：\n\n```javascript\nfn(2, 4);\nfn(2, 4, 6);\nfn(2, 4, 6, 8);\n\nfunction fn(a, b) {\n    console.log(arguments);\n    console.log(fn.length); //获取形参的个数\n    console.log(arguments.length); //获取实参的个数\n\n    console.log('----------------');\n}\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180125_2140.png)\n\n此外，即使我们不定义形参，也可以通过 arguments 来获取实参：arguments[0] 表示第一个实参、arguments[1] 表示第二个实参，以此类推。\n\n举例：将传入的实参进行求和，无论实参的个数有多少。代码实现：\n\n```js\nfunction foo() {\n  let sum = 0;\n  for (let i = 0; i < arguments.length; i++) {\n    sum += arguments[i];\n  }\n  return sum;\n}\n\nconst result = foo(1, 2);\nconsole.log(result);\n```\n\n\n### arguments.callee 返回正在执行的函数\n\narguments 里边有一个属性叫做 callee，这个属性对应一个函数对象，就是当前正在指向的函数对象。\n\n```javascript\nfunction fun() {\n    console.log(arguments.callee == fun); // 打印结果为true\n}\n\nfun('hello');\n```\n\n在使用函数**递归**调用时，推荐使用 arguments.callee 代替函数名本身。\n\n### arguments 可以修改元素\n\narguments 还可以**修改元素，但不能改变数组的长度**。举例：\n\n```javascript\nfn(2, 4);\nfn(2, 4, 6);\nfn(2, 4, 6, 8);\n\nfunction fn(a, b) {\n    arguments[0] = 99; // 将实参的第一个数改为99\n    arguments.push(8); // 此方法不通过，因为无法增加元素\n}\n```\n\n### 使用场景举例\n\n当我们不确定有多少个参数传递的时候，可以用 **arguments** 来获取。\n\n**举例**：利用 arguments 求函数实参中的最大值。\n\n代码实现：\n\n```javascript\n\tfunction getMaxValue() {\n\t\tvar max = arguments[0];\n\t\t// 通过 arguments 遍历实参\n\t\tfor (var i = 0; i < arguments.length; i++) {\n\t\t\tif (max < arguments[i]) {\n\t\t\t\tmax = arguments[i];\n\t\t\t}\n\t\t}\n\t\treturn max;\n\t}\n\n\tconsole.log(getMaxValue(1, 3, 7, 5));\n\n```\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/21-递归函数.md",
    "content": "---\ntitle: 21-递归函数\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 递归函数\n\n### 概念\n\n如果一个函数在内部调用这个函数自身，这个函数就是递归函数。\n\n递归在数据结构和算法中经常用到，可以将很多复杂的数据模型拆解为简单问题进行求解。一定要掌握。\n\n### 递归的要素\n\n- 递归模式：把大问题拆解为小问题进行分析。也称为递归体。\n- 边界条件：需要确定递归到何时结束。也称为递归出口。\n\n### 代码演示：计算阶乘\n\n提问：求一个正整数的阶乘。\n\n**普通写法：**\n\n```js\n// 函数：计算一个正整数的阶乘\nfunction factorial(n) {\n  let result = 1;\n  for (let i = 1; i <= n; i++) {\n    result *= i;\n  }\n  return result;\n}\n\nconsole.log(factorial(5)); // 120\n```\n\n现在，我们学习了递归函数之后，会有更简洁的写法。\n\n**递归写法：**\n\n```js\n// 递归函数：计算一个正整数的阶乘\nfunction factorial(n) {\n  // 递归出口：如果计算1的阶乘，就不用递归了\n  if (n == 1) return 1;\n\n  // 开始递归：如果当前这个 n 不是1，就返回 n * (n-1)!\n  return n * factorial(n - 1);\n}\nconsole.log(factorial(5)); // 120\n```\n\n\n\n## 递归函数的案例\n\n### 寻找所有的喇叭花数\n\n题目：喇叭花数是一个**三位数**，其每一位数字的阶乘之和恰好等于它本身，即`abc＝a! + b! + c!`，其中abc表示一个三位数。请找出所有的喇叭花数。\n\n思路：将计算某个数字的阶乘封装成函数。\n\n代码实现：\n\n```js\n// 递归函数：计算一个数的阶乘\nfunction factorial(n) {\n  // 递归出口：如果计算1的阶乘，就不用递归了\n  if (n == 1) return 1;\n\n  // 开始递归：如果当前这个 n 不是1，就返回 n * (n-1)!\n  return n * factorial(n - 1);\n}\n\n// 穷举法，从100到999遍历，寻找喇叭花数\nfor (let i = 100; i <= 999; i++) {\n  // 将数字i转为字符串\n  const i_str = i.toString();\n  // abc分别表示百位、十位、个位\n  const a = Number(i_str[0]);\n  const b = Number(i_str[1]);\n  const c = Number(i_str[2]);\n\n  // 根据喇叭花数的条件进行判断\n  if (factorial(a) + factorial(b) + factorial(c) == i) {\n    console.log(i);\n  }\n}\n```\n\n打印结果：\n\n```\n145\n```\n\n### 斐波那契数列\n\n斐波那契数列是这样一个数列：1、1、2、3、5、8、13、21、34......最早是由意大利数学家斐波那契开始研究的。它的规律是：下标为0和1的项，值为1；从下标为2的项开始，每一项等于前面两项之和。\n\n提问：请找出斐波那契数列的前10项。\n\n代码实现：\n\n```js\n// 递归函数：返回斐波那契数列中下标为n的那一项的值\nfunction fib(n) {\n  // 下标为0和1的项，值为1\n  if (n == 0 || n == 1) return 1;\n  // 从下标为2的项开始，每一项等于前面两项之和\n  return fib(n - 1) + fib(n - 2);\n}\n\n// 循环语句：打印斐波那契数列的前10项\nfor (let i = 0; i < 15; i++) {\n  console.log(fib(i));\n}\n```\n\n### 小结\n\n关于递归的案例，今后我们还会学习更多的应用场景。比如**深拷贝**就会用到递归。\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/22-立即执行函数.md",
    "content": "---\ntitle: 22-立即执行函数\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 立即执行函数 IIFE\n\n### 概念\n\n函数定义完，就立即被调用，这种函数叫做立即执行函数。英文是 IIFE（Immediately-invoked function expression），立即调用函数表达式。\n\n### 语法格式\n\n语法1：\n\n```js\n(function() {\n  // 函数体\n})();\n```\n\n语法2：（立即执行函数也可以传参）\n\n```js\n(function() {\n  // 函数体\n})(a, b);\n```\n\n语法解释：\n\n\n\n- `function(){}`这种写法，需要再加一对圆括号，变成``\n\n### 举例\n\n现有匿名函数如下：\n\n```javascript\n\tfunction(a, b) {\n\t\tconsole.log(\"a = \" + a);\n\t\tconsole.log(\"b = \" + b);\n\t};\n```\n\n立即执行函数如下：\n\n```javascript\n\t(function(a, b) {\n\t\tconsole.log(\"a = \" + a);\n\t\tconsole.log(\"b = \" + b);\n\t})(123, 456);\n```\n\n立即执行函数往往只会执行一次。为什么呢？因为没有变量保存它，执行完了之后，就找不到它了。\n\n## IIFE的作用\n\n### 为变量赋值\n\n当给变量赋值需要一些较复杂的计算时，使用IIFE显得语法更紧凑。\n\n```js\nconst sex = 'male';\nconst nickName = (function () {\n  if (sex == 'male') {\n    return '帅哥';\n  } else {\n    return '美女';\n  }\n})();\n\nconsole.log(nickName);\n```\n\n### 将全局变量变为局部变量\n\n现有如下代码：\n\n```js\nvar arr = [];\nfor (var i = 0; i < 5; i++) {\n  arr.push(function () {\n    console.log(i);\n  });\n}\narr[2](); // 打印5\n```\n\n我们知道，上方代码中，i 是全局变量，所有函数共享内存中的同一个变量i。\n\n现在，我们通过立即执行函数进行改造：\n\n```js\nvar arr = [];\nfor (var i = 0; i < 5; i++) {\n  (function (i) {\n    arr.push(function () {\n      console.log(i);\n    });\n  })(i);\n}\narr[2](); // 打印2\n```\n\n上方代码中，i作为传递给了IIFE的形参，让 i 得以成为 IIFE 的局部变量；并让 IIFE 并形成了闭包（`arr[2]()`打印出了IIFE内部变量 i 的值，说明形成了闭包）。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "04-JavaScript基础/23-作用域、变量提升、函数提升.md",
    "content": "---\ntitle: 23-作用域、变量提升、函数提升\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 作用域\n\n### 作用域（Scope）的概念和分类\n\n-   **概念**：作用域是一个变量或函数的作用范围。作用域在**函数定义**时，就已经确定了。\n\n-   **目的**：为了提高程序的可靠性，同时减少命名冲突。\n\n在 JS 中，一共有两种作用域：（ES5 中）\n\n-   **全局作用域**：作用于整个 script 标签内部，或者作用于一个独立的 JS 文件。\n-   **函数作用域**（局部作用域）：作用于函数内的代码环境。\n\n### 全局作用域 和 window 对象\n\n直接编写在 script 标签中的 JS 代码，都在全局作用域。全局作用域在页面打开时创建，在页面关闭时销毁。\n\n在全局作用域中有一个全局对象 window，它代表的是浏览器的窗口，由浏览器创建，我们可以直接使用。相关知识点如下：\n\n-   创建的**变量**都会作为 window 对象的属性保存。比如在全局作用域内写 `const a = 100`，这里的 `a` 等价于 `window.a`。\n-   创建的**函数**都会作为 window 对象的方法保存。\n\n### 作用域的访问关系\n\n在内部作用域中可以访问外部作用域的变量，在外部作用域中无法访问到内部作用域的变量。\n\n代码举例：\n\n```javascript\nconst a = 'aaa';\nfunction foo() {\n    const b = 'bbb';\n    console.log(a); // 打印结果：aaa。说明 内层作用域 可以访问 外层作用域 里的变量\n}\n\nfoo();\nconsole.log(b); // 报错：Uncaught ReferenceError: b is not defined。说明 外层作用域 无法访问 内层作用域 里的变量\n```\n\n### 变量的作用域\n\n根据作用域的不同，变量可以分为两类：全局变量、局部变量。\n\n**全局变量**：\n\n-   在全局作用域下声明的变量，叫「全局变量」。在全局作用域的任何一地方，都可以访问这个变量。\n-   在全局作用域下声明的变量是全局变量。\n\n**局部变量**：\n\n-   定义在函数作用域的变量，叫「局部变量」。仅限函数内部访问这个变量。\n-   函数的**形参**也是属于局部变量。\n\n从执行效率来看全局变量和局部变量：\n\n-   全局变量：只有浏览器关闭时才会被销毁，比较占内存。\n\n-   局部变量：当其所在的代码块运行结束后，就会被销毁，比较节约内存。\n\n**特殊情况：**\n\n（1）无论是在函数外还是函数内，变量如果未经声明就赋值（意思是，如果不加var/let/const），这个变量是**全局变量**。\n\n比如：\n\n ```js\n // 声明变量时如果不加var/let/const，这个变量是全局变量。且可以被修改。\n function fn() {\n   a = 1;\n }\n fn(); // 这行代码必须要写，否则下一行代码执行时会报错：Uncaught ReferenceError: a is not defined\n console.log(a); // 打印结果：1\n ```\n\n当然，我们不建议这么用。\n\n（2）如果局部变量和全局变量重名，则在函数内部，变量是以局部变量为准。\n\n### 作用域的上下级关系\n\n当在函数作用域操作一个变量时，它会先在自身作用域中寻找，如果有就直接使用（**就近原则**）。如果没有则向上一级作用域中寻找，直到找到全局作用域；如果全局作用域中依然没有找到，则会报错 ReferenceError。\n\n在函数中要访问全局变量可以使用 window 对象。（比如说，全局作用域和函数作用域都定义了变量 a，如果想访问全局变量，可以使用`window.a`）\n\n## 全局作用域的预处理\n\n**预处理（预解析）**的概念：JS在解析代码之前，有一个“预处理（预解析）”阶段，将当前 JS 代码中所有变量的定义和函数的定义，放到所有代码的最前面。\n\n（打个比方，学生在学习文言文之前，会扫读整篇文章，做简单的预习。）\n\n这种预解析，也称之为声明提前。\n\n### 变量的声明提前（变量提升）\n\n使用 var 关键字声明的变量（ 比如 `var a = 1`），**会在所有的代码执行之前被声明**（但是不会赋值）。但是如果声明变量时不是用 var 关键字（比如直接写`a = 1`），则变量不会被声明提前。\n\n**举例 1**：\n\n```javascript\nconsole.log(a);\nvar a = 123;\n```\n\n打印结果：undefined。注意，打印结果并没有报错，而是 undefined，说明变量 a 被提前声明了，只是尚未被赋值。\n\n**举例 2**：\n\n```javascript\nconsole.log(a);\na = 123; //此时a相当于window.a\n```\n\n程序会报错：`Uncaught ReferenceError: a is not defined`。\n\n**举例 3**：\n\n```javascript\na = 123; //此时a相当于window.a\nconsole.log(a);\n```\n\n打印结果：123。\n\n**举例 4**：\n\n```javascript\nfoo();\n\nfunction foo() {\n    if (false) {\n        var i = 123;\n    }\n    console.log(i);\n}\n```\n\n打印结果：undefined。注意，打印结果并没有报错，而是 undefined。这个例子，再次说明了：变量 i 在函数执行前，就被提前声明了，只是尚未被赋值。\n\n例 4 中， `if(false)`里面的代码虽然不会被执行，但是整个代码有**解析**的环节，解析的时候就已经把 变量 i 给提前声明了。\n\n**总结**：\n\n既然在ES5 中存在变量提升的现象，那么，在实战开发中，为了避免出错，建议先声明一个变量，然后再使用这个变量。\n\n### 函数的声明提前（函数提升）\n\n**函数声明**：\n\n使用`函数声明`的形式创建的函数`function foo(){}`，**会被声明提前**。\n\n也就是说，整个函数会在所有的代码执行之前就被**创建完成**。所以，在代码顺序上，我们可以先调用函数，再定义函数。\n\n代码举例：\n\n```javascript\nfn1(); // 虽然 函数 fn1 的定义是在后面，但是因为被提前声明了， 所以此处可以调用函数\n\nfunction fn1() {\n    console.log('我是函数 fn1');\n}\n```\n\n**函数表达式**：\n\n使用`函数表达式`创建的函数`const foo = function(){}`，**不会被声明提前**，所以不能在声明前调用。\n\n很好理解，因为此时只是变量 foo 被提升了，且值为 undefined，并没有把 `function(){}` 赋值给 foo。\n\n所以，下面的例子会报错：\n\n```js\n// 不会报错，可以正常执行函数，正常打印结果\nfun1();\n\n// 此时 fun2 相当于 undefined。执行时会报错：Uncaught ReferenceError: Cannot access 'fun2' before initialization\nfun2();\n\n// 函数声明，会被提前声明\nfunction fun1() {\n  console.log('我是 fun1 函数');\n}\n\n// 函数表达式，不会被提前声明\nconst fun2 = function () {\n  console.log('我是 fun12 函数');\n};\n```\n\n### 函数提升优先于变量提升\n\n在JS的规则中，函数提升优先于变量提升。来看看下面这段代码，你认为打印结果应该如何：（这是一道经典面试题）\n\n```js\nfun(); // 打印 B\n\n// 变量提升\nvar fun = function () {\n  console.log('A');\n};\n\n// 函数提升\nfunction fun() {\n  console.log('B');\n}\n\nfun(); // 打印 A\n```\n\n打印结果：\n\n```\nB\nA\n```\n\n当声明被提前后，上方代码的实际顺序可以这样理解：（把它当成伪代码理解即可）\n\n```js\n/*伪代码*/\n\n// 函数提升\nfunction fun() {\n  console.log('B');\n}\n\nvar fun = undefined;\n\nfun(); // 打印 B\n\nfun = function () {\n  console.log('A');\n};\n\nfun(); // 打印A\n```\n\n当然，上方代码是ES5写法，如果把 var 改成ES6中的 let/const，代码会报错`Uncaught SyntaxError: Identifier 'fun' has already been declared`。也就是说，ES6中不需要关心谁优先提升的问题了。\n\n\n\n## 函数作用域的预处理\n\n上一段讲的是全局作用域中的声明提前。在函数作用域中，也有声明提前的现象：\n\n-   函数中，使用 var 关键字声明的变量，会在函数中所有代码执行之前被提前声明。\n\n-   函数中，没有 var 声明的变量都是**全局变量**，且并不会被提前声明。\n\n举例：\n\n```javascript\nvar a = 1;\n\nfunction foo() {\n    console.log(a);\n    a = 2; // 此处的a相当于window.a\n}\n\nfoo();\nconsole.log(a); //打印结果是2\n```\n\n上方代码中，执行 foo() 后，函数里面的打印结果是`1`。如果去掉第一行代码，执行 foo() 后，函数里面的打印结果是`Uncaught ReferenceError: a is not defined`。\n\n**补充**：定义形参就相当于在函数作用域中声明了变量。举例如下：\n\n```javascript\nfunction fun(e) {\n    // 这个函数中，因为有了形参 e，此时相当于在函数内部的第一行代码里，写了 var e;\n    console.log(e);\n}\n\nfun(); //打印结果为 undefined\nfun(123); //打印结果为123\n```\n\n## ES5中没有块级作用域\n\n在其他编程语言中（如 Java、C#等），存在块级作用域，由`{}`包括起来。比如在 Java 语言中，if 语句里创建的变量，只能在 if 语句内部使用：\n\n```java\nif(true){\n    int num = 123;\n    system.out.print(num); // 123\n}\nsystem.out.print(num); // 报错\n```\n\n但是，在 ES5 中没有块级作用域。举例如下：\n\n```javascript\nif (true) {\n    var num = 123;\n    console.log(num); //123\n}\n\nconsole.log(num); //123（可以正常打印）\n```\n\n## 作用域链\n\n先来认识函数的嵌套：\n\n-   只要是代码，就至少有一个作用域\n\n-   函数内部有局部作用域\n\n-   如果函数内部还嵌套了函数，那么在这个作用域中就又诞生了另一个作用域。\n\n基于上面几条内容，我们可以得出作用域链的概念。\n\n**作用域链**：在嵌套函数中，变量会从内到外逐层寻找它的定义（查找时，采用**就近原则**）。也就是说，采用的是链式查找的方式来决定取哪个值，这种结构称之为作用域链。\n\n代码举例：\n\n```javascript\nvar num = 10;\n\nfunction fn() {\n    // 外部函数\n    var num = 20;\n\n    function fun() {\n        // 内部函数\n        console.log(num);\n    }\n    fun();\n}\nfn();\n```\n\n打印结果：20。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/24-预编译.md",
    "content": "---\ntitle: 24-预编译\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n> 我们在上一篇文章《作用域》中简单讲过“变量提升”，今天来讲一下预编译，这对我们深入理解变量提升会有帮助。\n\n## JavaScript 运行三部曲\n\n- 语法分析\n\n- 预编译\n\n- 解释执行\n\n## 预编译前奏\n\n> 在讲预编译前，我们先来普及下面两个规律。\n\n### 两个规律\n\n**规律1：任何变量，如果未经声明就赋值，此变量是属于 window 的属性**，而且不会做变量提升。（注意，无论在哪个作用域内赋值）\n\n比如说，如果我们直接在代码里写 `console.log(a)`，这肯定会报错的，提示找不到 `a`。但如果我直接写 `a = 100`，这就不会报错，此时，这个 `a` 就是 `window.a`。\n\n**规律2：一切声明的全局变量，全是window的属性**。（注意，这里说的是在全局作用域内声明的全局变量，不是说局部变量）\n\n比如，当定义 `var a = 200` 时，这个 `a` 就是 `window.a`。\n\n由此，我们可以看出：**window 代表了全局作用域**（是说「代表」，没说「等于」）。\n\n### 举例\n\n掌握了上面两句话之后，我们再来看看下面的例子。\n\n```javascript\nfunction foo() {\n    var a = b = 100; // 连续赋值\n}\n\nfoo();\n\nconsole.log(window.b); // 在全局范围内访问 b\nconsole.log(b); // 在全局范围内访问 b，但是前面没有加 window 这个关键字\n\nconsole.log(window.a); // 在全局范围内访问 a\nconsole.log(a); // 在全局范围内访问 a，但是前面没有加 window 这个关键字\n\n```\n\n上方代码的打印结果：\n\n```\n100\n\n100\n\nundefined\n\n（会报错，提示 Uncaught ReferenceError: a is not defined）\n\n```\n\n**解释**：\n\n当执行了`foo()`函数之后， `var a = b = 100` 这行**连续赋值**的代码等价于 `var a = (b = 100)`，其执行顺序是：\n\n（1）先把 100 赋值给 b；\n\n（2）再声明变量 a；\n\n（3）再把 b 的值赋值给 a。\n\n我们可以看到，b 是未经声明的变量就被赋值了，此时，根据规律1，这个 b 是属于 `window.b`；而 a 的作用域仅限于 foo() 函数内部，不属于 window。所以也就有了这样的打印结果。\n\n## 预编译\n\n### 函数预编译的步骤\n\n> 函数预编译，发生在函数执行的前一刻。\n\n（1）创建AO对象。AO即 Activation Object 活跃对象，其实就是「执行期上下文」。\n\n（2）找形参和变量声明，将形参名和变量作为 AO 的属性名，值为undefined。\n\n（3）将实参值和形参统一，实参的值赋给形参。\n\n（4）查找函数声明，函数名作为 AO 对象的属性名，值为整个函数体。\n\n这个地方比较难理解。但只有了解了函数的预编译，才能理解明白函数的执行顺序。\n\n代码举例：\n\n```javascript\nfunction fn(a) {\n    console.log(a);\n\n    var a = 666;\n\n    console.log(a);\n\n    function a() {}\n\n    console.log(a);\n\n    var b = function() {};\n\n    console.log(b);\n\n    function c() {}\n}\n\nfn(1);\n```\n\n打印结果：\n\n```\nƒ a() {}\n666\n666\nƒ () {}\n```\n\n\n## 参考链接\n\n- JavaScript预编译原理分析：<https://blog.csdn.net/q1056843325/article/details/52951114>\n\n- <https://segmentfault.com/a/1190000018001871>\n\n- 预编译及变量提升：<https://juejin.im/post/5aa6693df265da23884cb571>\n\n- <https://juejin.im/post/5adaf8215188256712781830>\n\n- <https://www.qqzmly.com/archives/1521>\n\n- 宏任务&微任务相关：<https://segmentfault.com/a/1190000018134157>\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/25-this指向.md",
    "content": "---\ntitle: 25-this指向\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 执行期上下文\n\n当**函数执行**时（准确来说，是在函数发生预编译的前一刻），会创建一个执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境。\n\n每调用一次函数，就会创建一个新的上下文对象，他们之间是相互独立且独一无二的。当函数执行完毕，它所产生的执行期上下文会被销毁。\n\n参考链接：<https://www.cnblogs.com/chenyingjie1207/p/9966036.html>\n\n## this 指向\n\n解析器在调用函数每次都会向函数内部传递进一个隐含的参数，这个隐含的参数就是 this，this 指向的是一个对象，这个对象我们称为函数执行的 上下文对象。\n\n### ES5 函数内 this 的指向【非常重要】\n\n我们在《JavaScript 基础/函数.md》这篇文章讲过，函数的调用有**六种**形式。\n\n在ES5语法中，根据函数的调用方式的不同，this 会指向不同的对象：\n\n1、以函数的形式（包括普通函数、定时器函数、立即执行函数）调用时，this 的指向永远都是 window。比如`fun();`相当于`window.fun();`\n\n2、以方法的形式调用时，this 指向调用方法的那个对象\n\n3、以构造函数的形式调用时，this 指向实例对象\n\n4、以事件绑定函数的形式调用时，this 指向**绑定事件的对象**\n\n5、使用 call 和 apply 调用时，this 指向指定的那个对象\n\n**第 1 条的举例**：\n\n```javascript\nfunction fun() {\n    console.log(this);\n    console.log(this.name);\n}\n\nvar obj1 = {\n    name: 'smyh',\n    sayName: fun,\n};\n\nvar obj2 = {\n    name: 'vae',\n    sayName: fun,\n};\n\nvar name = '全局的name属性';\n\n//以函数形式调用，this是window\nfun(); //可以理解成 window.fun()\n```\n\n打印结果：\n\n```\n    Window\n    全局的name属性\n```\n\n上面的举例可以看出，this 指向的是 window 对象，所以 this.name 指的是全局的 name。\n\n**第 2 条的举例**：\n\n```javascript\nfunction fun() {\n    console.log(this);\n    console.log(this.name);\n}\n\nvar obj1 = {\n    name: 'smyh',\n    sayName: fun,\n};\n\nvar obj2 = {\n    name: 'vae',\n    sayName: fun,\n};\n\nvar name = '全局的name属性';\n\n//以方法的形式调用，this是调用方法的对象\nobj2.sayName();\n```\n\n打印结果：\n\n```\n    Object\n    vae\n```\n\n上面的举例可以看出，this 指向的是 对象 obj2 ，所以 this.name 指的是 obj2.name。\n\n### ES6 箭头函数中 this 的指向\n\nES6 中的箭头函数并不使用上面的准则，而是会继承外层函数调用的 this 绑定（无论 this 绑定到什么）。\n\n### 改变函数内部的 this 指向\n\nJS 专门为我们提供了一些方法来改变函数内部的 this 指向。常见的方法有 call()、apply()、bind() 方法。继续往下看。\n\n\n\n## call()\n\n### call() 方法的作用\n\ncall() 方法的作用：可以**调用**一个函数，与此同时，它还可以改变这个函数内部的 this 指向。\n\ncall() 方法的另一个应用：**可以实现继承**。之所以能实现继承，其实是利用了上面的作用。\n\n语法：\n\n```js\nfn1.call(想要将this指向哪里, 函数实参1, 函数实参2);\n```\n\n备注：第一个参数中，如果不需要改变 this 指向，则传 null。\n\n### call() 方法举例\n\n**举例 1**、通过 call() 调用函数：\n\n```js\nconst obj1 = {\n    nickName: 'qianguyihao',\n    age: 28,\n};\nfunction fn1() {\n    console.log(this);\n    console.log(this.nickName);\n}\nfn1.call(this); // this的指向并没有被改变，此时相当于 fn1();\n```\n\n上方代码的打印结果：\n\n```\nwindow\nundefined\n```\n\n上面的代码，跟普通的函数调用 `fn1()` 没有区别。\n\n**举例 2**、通过 call() 改变 this 指向：\n\n```js\nvar obj1 = {\n    nickName: 'qianguyihao',\n    age: 28,\n};\n\nfunction fn1(a, b) {\n    console.log(this);\n    console.log(this.nickName);\n    console.log(a + b);\n}\n\nfn1.call(obj1, 2, 4); // 先将 this 指向 obj1，然后执行 fn1() 函数\n```\n\n上方代码的打印结果：\n\n```\nobj1\nqianguyihao\n6\n```\n\n**举例 3**、通过 call() 实现继承：\n\n```js\n// 给 Father 增加 name 和 age 属性\nfunction Father(myName, myAge) {\n    this.name = myName;\n    this.age = myAge;\n}\n\nfunction Son(myName, myAge) {\n    // 【下面这一行，重要代码】\n    // 通过这一步，将 father 里面的 this 修改为 Son 里面的 this；另外，给 Son 加上相应的参数，让 Son 自动拥有 Father 里的属性。最终实现继承\n    Father.call(this, myName, myAge);\n}\n\nconst son1 = new Son('千古壹号', 28);\nconsole.log(JSON.stringify(son1));\n```\n\n上方代码中，通过 call() 方法，让 Son 继承了 Father 里面的 name 和 age 属性。\n\n打印结果：\n\n```\n{\"myName\":\"千古壹号\",\"myAge\":28}\n```\n\n## apply() 方法\n\n### apply() 方法的作用\n\napply() 方法的作用：可以**调用**一个函数，与此同时，它还可以改变这个函数内部的 this 指向。这一点，和 call()类似。\n\napply() 方法的应用： 由于 apply()需要传递**数组**，所以它有一些巧妙应用，稍后看接下来的应用举例就知道了。\n\n语法：\n\n```js\nfn1.apply(想要将this指向哪里, [函数实参1, 函数实参2]);\n```\n\n备注：第一个参数中，如果不需要改变 this 指向，则传 null。\n\n到这里可以看出， call() 和 apply() 方法的作用是相同的。唯一的区别在于，apply() 里面传入的**实参，必须是数组（或者伪数组）**。\n\n### apply() 方法举例\n\n**举例**、通过 apply() 改变 this 指向：\n\n```js\nvar obj1 = {\n    nickName: 'qianguyihao',\n    age: 28,\n};\n\nfunction fn1(a) {\n    console.log(this);\n    console.log(this.nickName);\n    console.log(a);\n}\n\nfn1.apply(obj1, ['hello']); // 先将 this 指向 obj1，然后执行 fn1() 函数\n```\n\n注意，上方代码中，apply() 里面传实参时，需要以数组的形式。即便是传一个实参，也需要传数组。\n\n打印结果：\n\n```\nobj1\nqianguyihao\nhello\n```\n\n### apply() 方法的巧妙应用：求数组的最大值\n\n我们知道，如果想要求数组中元素的最大值，数组本身是没有自带方法的。那怎么办呢？\n\n虽然数组里没有获取最大值的方法，但是数值里有 `Math.max(数字1，数字2，数字3)` 方法，可以获取**多个数值中的最大值**。 另外，由于 apply() 方法在传递实参时，传的刚好是**数组**，所以我们可以 通过 Math.max() 和 apply() 曲线救国。\n\n**举例**：求数组中多个元素的最大值：\n\n```js\nconst arr1 = [3, 7, 10, 8];\n\n// 下面这一行代码的目的，无需改变 this 指向，所以：第一个参数填 null，或者填 Math，或者填 this 都可以。严格模式中，不让填null。\nconst maxValue = Math.max.apply(Math, arr1); // 求数组 arr1 中元素的最大值\nconsole.log(maxValue);\n\nconst minValue = Math.min.apply(Math, arr1); // 求数组 arr1 中元素的最小值\nconsole.log(minValue);\n```\n\n打印结果：\n\n```\n10\n3\n```\n\n## bind() 方法\n\n### bind() 方法的作用\n\nbind() 方法**不会调用函数**，但是可以改变函数内部的 this 指向。\n\n把call()、apply()、bind()这三个方法做一下对比，你会发现：实际开发中， bind() 方法使用得最为频繁。如果有些函数，我们不需要立即调用，但是又想改变这个函数内部的this指向，此时用 bind() 是最为合适的。\n\n\n语法：\n\n```js\n新函数 = fn1.bind(想要将this指向哪里, 函数实参1, 函数实参2);\n```\n\n参数：\n\n- 第一个参数：在 fn1 函数运行时，指定 fn1 函数的this 指向。如果不需要改变 this 指向，则传 null。\n\n- 其他参数：fn1 函数的实参。\n\n解释：它不会调用 fn1 函数，但会返回 由指定this 和指定实参的**原函数拷贝**。可以看出， bind() 方法是有返回值的。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/26-闭包.md",
    "content": "---\ntitle: 26-闭包\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 闭包的引入\n\n我们知道，变量根据作用域的不同分为两种：全局变量和局部变量。\n\n- 函数内部可以访问全局变量和局部变量。\n\n- 函数外部只能访问全局变量，不能访问局部变量。\n\n- 当函数执行完毕，本作用域内的局部变量会销毁。\n\n比如下面这样的代码：\n\n```js\nfunction foo() {\n    let a = 1;\n}\n\nfoo();\nconsole.log(a); // 打印报错：Uncaught ReferenceError: a is not defined\n```\n\n上方代码中，由于变量 `a` 是函数内的局部变量，所以外部无法访问。\n\n但是，在有些场景下，我们就是想要在函数外部访问**函数内部作用域的局部变量**，那要怎么办呢？这就引入了闭包的概念。\n\n## 什么是闭包\n\n### 闭包（closure）的概念\n\n**闭包**：如果**外部作用域**有权访问另外一个**函数内部**的**局部变量**时，那就产生了闭包。这个内部函数称之为闭包函数。注意，这里强调的是访问**局部变量**。\n\n闭包代码举例：\n\n```js\nfunction fun1() {\n  const a = 10;\n  return function fun2() {\n    console.log(a);\n  };\n}\nfun1();\n// 调用外部函数，就能得到内部函数，并用 变量 result 接收\nconst result = fun1();\n// 在 fun1函数的外部，执行了内部函数 fun2，并访问到了 fun2的内部变量a\nresult(); // 10\n```\n\n打印结果：\n\n```\n10\n```\n\n上方代码中，外部作用域（即全局作用域） 访问了函数 fun1 中的局部变量，那么，在 fun1 中就产生了闭包，函数 fun1是闭包函数。\n\n全局作用域中，并没有定义变量a。正常情况下作为函数内的局部变量 a，无法被外部访问到。但是通过闭包，我们最后还是可以在全局作用域中拿到局部变量 a 的值。\n\n注意，闭包函数是fun1，不是fun2。fun2在这里的作用是让全局作用域访问到变量a，fun2只是一个桥梁。\n\n### 闭包的生命周期\n\n1. 产生：内部函数fn1被声明时（即被创建时，不是被调用时）就产生了。\n\n2. 死亡：嵌套的内部函数成为垃圾对象时。（比如fun1 = null，就可以让 fun1 成为垃圾对象）\n\n### 在 chrome 浏览器控制台中，调试闭包\n\n上面的代码中，要怎么验证，确实产生了闭包呢？我们可以在 chrome 浏览器的控制台中设置断点，当代码执行到 `console.log(a)`这一行的时候，如下图所示：\n\n![](http://img.smyhvae.com/20200703_2055.png)\n\n上图中， Local 指的是局部作用域，Global 指的是全局作用域；而 Closure 则是**闭包**，fn1 是一个闭包函数。\n\n## 闭包的表现形式\n\n### 形式1：将一个函数作为另一个函数的返回值\n\n```javascript\n    function fn1() {\n      var a = 2\n\n      function fn2() {\n        a++\n        console.log(a)\n      }\n      return fn2\n    }\n\n    var f = fn1();   //执行外部函数fn1，返回的是内部函数fn2\n    f() // 3       //执行fn2\n    f() // 4       //再次执行fn2\n```\n\n\n当f()第二次执行的时候，a加1了，也就说明了：闭包里的数据没有消失，而是保存在了内存中。如果没有闭包，代码执行完倒数第三行后，变量a就消失了。\n\n上面的代码中，虽然调用了内部函数两次，但是，闭包对象只创建了一个。\n\n也就是说，要看闭包对象创建了几个，就看：**外部函数执行了几次**（与内部函数执行几次无关）。\n\n### 形式2：将函数作为实参传递给另一个函数调用\n\n在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中，只要使用了回调函数，实际上就是在使用闭包。\n\n```javascript\n    function showDelay(msg, time) {\n      setTimeout(function() {  //这个function是闭包，因为是嵌套的子函数，而且引用了外部函数的变量msg\n        alert(msg)\n      }, time)\n    }\n    showDelay('qianguyihao', 2000)\n```\n\n上面的代码中，闭包是里面的function，因为它是嵌套的子函数，而且引用了外部函数的变量msg。\n\n\n## 闭包的作用\n\n- 作用1：延长局部变量的生命周期。\n\n- 作用2：让函数外部可以操作（读写）函数内部的数据（变量/函数）。\n\n代码演示：\n\n```javascript\nfunction fun1() {\n  let a = 2\n\n  function fun2() {\n    a++\n    console.log(a)\n  }\n  return fun2;\n}\n\nconst foo = fun1();   //执行外部函数fn1，返回的是内部函数fn2\nfoo() // 3       //执行fun2\nfoo() // 4       //再次执行fun2\n```\n\n上方代码中，foo 代表的就是整个 fun2 函数。当执行了 `foo()` 语句之后，也就执行了fun2()函数，fun1() 函数内就产生了闭包。\n\n**作用1分析**：\n\n一般来说，在 fn1() 函数执行完毕后，它里面的变量 a 会立即销毁。但此时由于产生了闭包，所以 **fun1 函数中的变量 a 不会立即销毁，仍然保留在内存中，因为 fn2 函数还要继续调用变量 a**。只有等所有函数把变量 a 调用完了，变量 a 才会销毁。\n\n**作用2分析：**\n\n在执行 `foo()`语句之后，竟然能够打印出 `3`，这就完美通过闭包实现了：全局作用域成功访问到了局部作用域中的变量 a。\n\n达到的效果是：**外界看不到变量a，但可以操作a**。当然，如果你真想看到a，可以在fun2中将a返回即可。\n\n## 闭包的应用场景\n\n### 场景1：高阶函数\n\n题目：不同的班级有不同成绩检测标准。比如：A班的合格线是60分，B 班的合格线是70分。已知某个人班级和分数，请用闭包函数判断他的成绩是否合格。\n\n思路：创建成绩检测函数 checkStandard(n)，检查成绩 n 是否合格，函数返回布尔值。\n\n代码实现：\n\n```js\n// 高阶函数：判断学生的分数是否合格。形参 standardTemp 为标准线\nfunction createCheckTemp(standardTemp) {\n  // 形参 n 表示具体学生的分数\n  function checkTemp(n) {\n    if (n >= standardTemp) {\n      alert('成绩合格');\n    } else {\n      alert('成绩不合格');\n    }\n  }\n  return checkTemp;\n}\n\n// 创建一个 checkStandard_A 函数，它以60分为合格线\nvar checkStandard_A = createCheckTemp(60);\n// 再创建一个 checkStandard_B 函数，它以70分为合格线\nvar checkStandard_B = createCheckTemp(70);\n\n// 调用函数\ncheckStandard_A(65); // 成绩合格\ncheckStandard_B(65); // 成绩不合格\n```\n\n对于A班来说，它的闭包函数是createCheckTemp()，闭包范围是 checkTemp()函数和参数`standardTemp = 60`。对于B班来说，它的闭包函数是全新的createCheckTemp()，闭包范围是全新的checkTemp()函数和全新的参数`standardTemp = 70`。\n\n因为有闭包存在，所以，并不会因为 createCheckTemp() 执行完毕后就销毁 standardTemp 的值；且A班和B班的standardTemp参数不会混淆。\n\n备注：关于“高阶函数”的更多解释，我们在以后的内容中讲解。\n\n### 场景2：封装JS模块\n\n闭包的第二个使用场景是：定义具有特定功能的JS模块，将所有的数据和功能都封装在一个函数内部，只向外暴露指定的对象或方法。模块的调用者，只能调用模块暴露的对象或方法来实现对应的功能。\n\n比如有这样一个需求：定义一个私有变量a，要求a只能被进行指定操作（加减），不能进行其他操作（乘除）。在  Java、C++ 等语言中，有私有属性的概念，但在JS中只能通过闭包模拟。\n\n我们来看看下面的代码，如何通过闭包封装JS模块。\n\n写法1：\n\n（1）myModule.js：（定义一个模块，向外暴露多个方法，供外界调用）\n\n```javascript\nfunction myModule() {\n    //私有数据\n    var msg = 'Qinguyihao Haha'\n\n    //操作私有数据的函数\n    function doSomething() {\n        console.log('doSomething() ' + msg.toUpperCase()); //字符串大写\n    }\n\n    function doOtherthing() {\n        console.log('doOtherthing() ' + msg.toLowerCase()) //字符串小写\n    }\n\n    //通过【对象字面量】的形式进行包裹，向外暴露多个函数\n    return {\n        doSomething1: doSomething,\n        doOtherthing2: doOtherthing\n    }\n}\n```\n\n上方代码中，外界只能通过doSomething1和doOtherthing2来操作里面的数据，但不让外界看到里面的具体实现。\n\n（2）index.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>闭包的应用-自定义JS模块</title>\n</head>\n<body>\n<!--\n闭包应用举例: 定义JS模块\n  * 具有特定功能的js文件\n  * 将所有的数据和功能都封装在一个函数内部(私有的)\n  * 【重要】只向外暴露一个包含n个方法的对象或方法\n  * 模块的使用者, 只需要调用模块暴露的对象或者方法来实现对应的功能\n-->\n<script type=\"text/javascript\" src=\"myModule.js\"></script>\n<script type=\"text/javascript\">\n    var module = myModule();\n    module.doSomething1();\n    module.doOtherthing2();\n</script>\n</body>\n</html>\n```\n\n写法2：\n\n同样是实现上面的功能，我们还采取另外一种写法，写起来更方便。如下：\n\n（1）myModule2.js：（是一个立即执行的匿名函数）\n\n```javascript\n(function () {\n    //私有数据\n    var msg = 'Qinguyihao Haha'\n\n    //操作私有数据的函数\n    function doSomething() {\n        console.log('doSomething() ' + msg.toUpperCase())\n    }\n\n    function doOtherthing() {\n        console.log('doOtherthing() ' + msg.toLowerCase())\n    }\n\n    //外部函数是即使运行的匿名函数，我们可以把两个方法直接传给window对象\n    window.myModule = {\n        doSomething1: doSomething,\n        doOtherthing2: doOtherthing\n    }\n})()\n```\n\n\n（2）index.html：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>闭包的应用-自定义JS模块</title>\n</head>\n<body>\n<!--\n闭包的应用2 : 定义JS模块\n  * 具有特定功能的js文件\n  * 将所有的数据和功能都封装在一个函数内部(私有的)\n  * 只向外暴露一个包信n个方法的对象或函数\n  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能\n-->\n\n<!--引入myModule文件-->\n<script type=\"text/javascript\" src=\"myModule2.js\"></script>\n<script type=\"text/javascript\">\n    myModule.doSomething1()\n    myModule.doOtherthing2()\n</script>\n</body>\n</html>\n\n```\n\n上方两个文件中，我们在`myModule2.js`里直接把两个方法直接传递给window对象了。于是，在index.html中引入这个js文件后，会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。\n\n**小结：**\n\n写法1和写法2都采用了闭包。\n\n## 内存溢出和内存泄露\n\n> 我们再讲两个概念，这两个概念和闭包有些联系。\n\n### 内存泄漏\n\n**内存泄漏**：**占用的内存**没有及时释放。\n\n内存泄露的次数积累多了，就容易导致内存溢出。\n\n**常见的内存泄露**：\n\n1、意外的全局变量\n\n2、没有及时清理的计时器或回调函数\n\n3、闭包\n\n\n情况1举例：\n\n```javascript\n// 意外的全局变量\nfunction fn() {\n  a = new Array(10000000);\n  console.log(a);\n}\n\nfn();\n```\n\n情况2举例：\n\n```javascript\n// 没有及时清理的计时器或回调函数\nvar intervalId = setInterval(function () { //启动循环定时器后不清理\n  console.log('----')\n}, 1000)\n\n// clearInterval(intervalId);  //清理定时器\n```\n\n情况3举例：\n\n```js\nfunction fn1() {\n  var a = 4;\n  function fn2() {\n    console.log(++a)\n  }\n  return fn2\n}\nvar f = fn1()\nf()\n\n// f = null //让内部函数成为垃圾对象-->回收闭包\n```\n\n### 内存溢出\n\n**内存溢出**：程序运行时出现的错误。当程序运行**需要的内存**超过**剩余的内存**时，就抛出内存溢出的错误。\n\n代码举例：\n\n```javascript\n    var obj = {};\n    for (var i = 0; i < 10000; i++) {\n    obj[i] = new Array(10000000);  //把所有的数组内容都放到obj里保存，导致obj占用了很大的内存空间\n    console.log(\"-----\");\n    }\n```\n\n### 闭包是否会造成内存泄漏\n\n一般来说，答案是否定的。因为内存泄漏是非预期情况，本来想回收，但实际没回收；而闭包是预期情况，一般不会造成内存泄漏。\n\n但如果因代码质量不高，滥用闭包，也会造成内存泄漏。\n\n## 闭包面试题\n\n代码举例：\n\n```js\nfunction addCount() {\n  let count = 0;\n  return function () {\n    count = count + 1;\n    console.log(count);\n  };\n}\n\nconst fun1 = addCount();\nconst fun2 = addCount();\nfun1();\nfun2();\n\nfun1();\nfun2();\n```\n\n打印结果：\n\n```\n1\n1\n2\n2\n```\n\n代码解释：\n\n（1）fun1 和 fun2 这两个闭包函数是互不影响的，因此第一次调用时，count变量都是0，最终各自都输出1。\n\n（2）第二次调用时，由于闭包有记忆性，所以各自会在上一次的结果上再加1，因此输出2。\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "04-JavaScript基础/27-面向对象简介.md",
    "content": "---\ntitle: 27-面向对象简介\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 面向过程和面向对象\n\n### 面向过程\n\n**面向过程**：先分析好的具体步骤，然后按照步骤，一步步解决问题。\n\n优点：性能比面向对象高，适合跟硬件联系很紧密的东西，例如单片机就采用的面向过程编程。\n\n缺点：没有面向对象易维护、易复用、易扩展。\n\n### 面向对象\n\n**面向对象**（OOP，Object Oriented Programming）：以对象功能来划分问题，而不是步骤。\n\n优点：易维护、易复用、易扩展，由于面向对象有封装、继承、多态性的特性，可以设计出低耦合的系统，使系统 更加灵活、更加易于维护。\n\n缺点：性能比面向过程低。\n\n### 面向对象的编程思想\n\n面向对象的编程思想：对代码和数据进行封装，并以对象调用的方式，对外提供统一的调用接口。\n\n比如说，当我们在开车的时候，无需关心汽车的内部构造有多复杂，对于大多数人而言，只需要会开、知道汽车有哪些功能就行了。\n\n### 面向对象的特性\n\n在面向对象程序开发思想中，每一个对象都是功能中心，具有明确分工。面向对象编程具有灵活、代码可复用、容易维护和开发的优点，适合多人合作的大型软件项目，更符合我们认识事物的规律。\n\n面向对象的特性如下：\n\n- 封装性\n\n- 继承性\n\n- 多态性\n\n### JS 中的面向对象\n\nJS 中的面向对象，是基于**原型**的面向对象。JS 中的对象（Object）是依靠构造器（constructor）和原型（prototype）构造出来的。\n\n另外，在ES6中，新引入了 类（Class）和继承（Extends）来实现面向对象。\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/28-对象的创建&构造函数.md",
    "content": "---\ntitle: 28-对象的创建&构造函数\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n> 在看本文之前，可以先复习前面的一篇文章：《04-JavaScript 基础/11-对象简介.md》\n\n## 创建自定义对象的几种方法\n\n### 方式一：对象字面量\n\n**对象的字面量**就是一个{}。里面的属性和方法均是**键值对**：\n\n-   键：相当于属性名。\n\n-   值：相当于属性值，可以是任意类型的值（数字类型、字符串类型、布尔类型，函数类型等）。\n\n使用对象字面量来创建一个对象，非常简洁，举例如下：：\n\n```javascript\nvar obj = {};\n```\n\n使用对象字面量，可以在创建对象时，直接指定对象中的属性。语法：{属性名:属性值,属性名:属性值....}\n\n例 1：（一个简单的对象）\n\n```js\nconst obj1 = {\n    name: '千古壹号',\n    age: 28\n};\n```\n\n例 2：（一个较复杂的对象）\n\n```javascript\nconst obj2 = {\n    name: \"千古壹号\",\n    age: 26,\n    isBoy: true,\n    // 还可以存放一个嵌套的对象\n    test: {\n        id: 123,\n        tel: 180\n    }\n    //我们还可以在对象中增加一个方法。以后可以通过obj2.sayName()的方式调用这个方法\n    sayName: function() {\n        console.log(this.name);\n    }\n};\n\nconsole.log(JSON.stringify(obj2));\nobj2.sayName();\n\n```\n\n对象字面量的属性名可以加引号也可以不加，建议不加。如果要使用一些特殊的名字，则必须加引号。\n\n属性名和属性值是一组一组的键值对结构，键和值之间使用`:`连接，多个值对之间使用`,`隔开。\n\n### 方式二：工厂模式 new Object()\n\n通过该方法可以大批量的创建对象。\n\n```javascript\n/*\n * 使用工厂方法创建对象\n *  通过该方法可以大批量的创建对象\n */\nfunction createPerson(name, age, gender) {\n    //创建一个新的对象\n    var obj = new Object();\n    //向对象中添加属性\n    obj.name = name;\n    obj.age = age;\n    obj.gender = gender;\n    obj.sayName = function () {\n        alert(this.name);\n    };\n    //将新的对象返回\n    return obj;\n}\n\nvar obj2 = createPerson('猪八戒', 28, '男');\nvar obj3 = createPerson('白骨精', 16, '女');\nvar obj4 = createPerson('蜘蛛精', 18, '女');\n```\n\n第一次看到这种工厂模式时，你可能会觉得陌生。如果简化一下，可以写成下面这种形式，更容易理解：（也就是，利用 new Object 创建对象）\n\n```javascript\nvar obj = new Obejct();\nobj.name = '猪八戒';\nobj.age = 28;\nobj.gender = '男';\nobj.sayHi = function () {\n    alert('hello world');\n};\n```\n\n**弊端：**\n\n使用工厂方法创建的对象，使用的构造函数都是 Object。**所以创建的对象都是 Object 这个类型，就导致我们无法区分出多种不同类型的对象**。\n\n### 方式三：利用构造函数\n\n```javascript\n//利用构造函数自定义对象\nvar stu1 = new Student('smyh');\nconsole.log(stu1);\nstu1.sayHi();\n\nvar stu2 = new Student('vae');\nconsole.log(stu2);\nstu2.sayHi();\n\n// 创建一个构造函数\nfunction Student(name) {\n    this.name = name; //this指的是当前对象实例【重要】\n    this.sayHi = function () {\n        console.log(this.name + '厉害了');\n    };\n}\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180125_1350.png)\n\n接下来，我们专门来讲一下构造函数。\n\n## 构造函数\n\n### 代码引入\n\n```javascript\n// 创建一个构造函数，专门用来创建Person对象\nfunction Person(name, age, gender) {\n    this.name = name;\n    this.age = age;\n    this.gender = gender;\n    this.sayName = function () {\n        alert(this.name);\n    };\n}\n\nvar per = new Person('孙悟空', 18, '男');\nvar per2 = new Person('玉兔精', 16, '女');\nvar per3 = new Person('奔波霸', 38, '男');\n\n// 创建一个构造函数，专门用来创建 Dog 对象\nfunction Dog() {}\n\nvar dog = new Dog();\n```\n\n### 构造函数的概念\n\n**构造函数**：是一种特殊的函数，主要用来创建和初始化对象，也就是为对象的成员变量赋初始值。它与 `new` 一起使用才有意义。\n\n我们可以把对象中一些公共的属性和方法抽取出来，然后封装到这个构造函数里面。\n\n### 构造函数和普通函数的区别\n\n构造函数的创建方式和普通函数没有区别，不同的是构造函数习惯上首字母大写。\n\n构造函数和普通函数的区别就是**调用方式**的不同：普通函数是直接调用，而构造函数需要使用 new 关键字来调用。\n\n**this 的指向也有所不同**：\n\n-   1.以函数的形式调用时，this 永远都是 window。比如`fun();`相当于`window.fun();`\n\n-   2.以方法的形式调用时，this 是调用方法的那个对象\n\n-   3.以构造函数的形式调用时，this 是新创建的实例对象\n\n### new 一个构造函数的执行流程\n\nnew 在执行时，会做下面这四件事：\n\n（1）开辟内存空间，在内存中创建一个新的空对象。\n\n（2）让 this 指向这个新的对象。\n\n（3）执行构造函数里面的代码，给这个新对象添加属性和方法。\n\n（4）返回这个新对象（所以构造函数里面不需要 return）。\n\n因为 this 指的是 new 一个 Object 之后的对象实例。于是，下面这段代码：\n\n```javascript\n// 创建一个函数\nfunction createStudent(name) {\n    var student = new Object();\n    student.name = name; //第一个name指的是student对象定义的变量。第二个name指的是createStudent函数的参数。二者不一样\n}\n```\n\n可以改进为：\n\n```javascript\n// 创建一个函数\nfunction Student(name) {\n    this.name = name; //this指的是构造函数中的对象实例\n}\n```\n\n注意上方代码中的注释。\n\n### 静态成员和实例成员\n\nJavaScript 的构造函数中可以添加一些成员，可以在构造函数本身上添加，也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员，就分别称为静态成员和实例成员。\n\n-   静态成员：在构造函数本上添加的成员称为静态成员，只能由构造函数本身来访问。\n\n-   实例成员：在构造函数内部创建的对象成员称为实例成员，只能由实例化的对象来访问。\n\n### 类、实例\n\n使用同一个构造函数创建的对象，我们称为一类对象，也将一个构造函数称为一个**类**。\n\n通过一个构造函数创建的对象，称为该类的**实例**。\n\n### instanceof\n\n使用 instanceof 可以检查**一个对象是否为一个类的实例**。\n\n**语法如下**：\n\n```javascript\n对象 instanceof 构造函数;\n```\n\n如果是，则返回 true；否则返回 false。\n\n**代码举例**：\n\n```javascript\nfunction Person() {}\n\nfunction Dog() {}\n\nvar person1 = new Person();\n\nvar dog1 = new Dog();\n\nconsole.log(person1 instanceof Person); // 打印结果： true\nconsole.log(dog1 instanceof Person); // 打印结果：false\n\nconsole.log(dog1 instanceof Object); // 所有的对象都是Object的后代。因此，打印结果为：true\n```\n\n根据上方代码中的最后一行，需要补充一点：**所有的对象都是 Object 的后代，因此 `任何对象 instanceof Object` 的返回结果都是 true**。\n\n## others\n\n### json 的介绍\n\n> 对象字面量和 json 比较像，这里我们对 json 做一个简单介绍。\n\nJSON：JavaScript Object Notation（JavaScript 对象表示形式）。\n\nJSON 和对象字面量的区别：JSON 的属性必须用双引号引号引起来，对象字面量可以省略。\n\njson 举例：\n\n```\n      {\n            \"name\" : \"zs\",\n            \"age\" : 18,\n            \"sex\" : true,\n            \"sayHi\" : function() {\n                console.log(this.name);\n            }\n        };\n```\n\n注：json 里一般放常量、数组、对象等，但很少放 function。\n\n另外，对象和 json 没有长度，json.length 的打印结果是 undefined。于是乎，自然也就不能用 for 循环遍历（因为遍历时需要获取长度 length）。\n\n**json 遍历的方法：**\n\njson 采用 `for...in...`进行遍历，和数组的遍历方式不同。如下：\n\n```html\n<script>\n    var myJson = {\n        \"name\": \"qianguyihao\",\n        \"aaa\": 111,\n        \"bbb\": 222,\n    };\n\n    //json遍历的方法：for...in...\n    for (var key in myJson) {\n        console.log(key); //获取 键\n        console.log(myJson[key]); //获取 值（第二种属性绑定和获取值的方法）\n        console.log('------');\n    }\n</script>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180203_1518.png)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/29-对象的基本操作.md",
    "content": "---\ntitle: 29-对象的基本操作\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 对象的基本操作\n\n### 创建对象\n\n使用 new 关键字调用的函数，是构造函数 constructor。**构造函数是专门用来创建对象的函数**。\n\n例如：\n\n```javascript\nconst obj = new Object();\n```\n\n记住，使用`typeof`检查一个对象时，会返回`object`。\n\n关于创建对象的更多方式，可以看上一篇文章《对象的创建&构造函数》。\n\n### 向对象中添加属性\n\n在对象中保存的值称为属性。\n\n向对象添加属性的语法：\n\n```javascript\n对象.属性名 = 属性值;\n```\n\n举例：\n\n```javascript\nconst obj = new Object();\n\n//向obj中添加一个name属性\nobj.name = '孙悟空';\n\n//向obj中添加一个gender属性\nobj.gender = '男';\n\n//向obj中添加一个age属性\nobj.age = 18;\n\nconsole.log(JSON.stringify(obj)); // 将 obj 以字符串的形式打印出来\n```\n\n打印结果：\n\n```\n\t{\n\t\t\"name\":\"孙悟空\",\n\t\t\"gender\":\"男\",\n\t\t\"age\":18\n\t}\n```\n\n这里我们也可以看出一个规律：如果对象里本身没有某个属性，则用点语法赋值时，这个属性会被创建出来。\n\n### 获取对象中的属性\n\n**方式 1**：\n\n语法：\n\n```javascript\n对象.属性名;\n```\n\n如果获取对象中没有的属性，不会报错而是返回`undefined`。\n\n举例：\n\n```javascript\nconst obj = new Object();\n\n//向obj中添加一个name属性\nobj.name = '孙悟空';\n\n//向obj中添加一个gender属性\nobj.gender = '男';\n\n//向obj中添加一个age属性\nobj.age = 18;\n\n// 获取对象中的属性，并打印出来\nconsole.log(obj.gender); // 打印结果：男\nconsole.log(obj.color); // 打印结果：undefined\n```\n\n**方式 2**：可以使用`[]`这种形式去操作属性\n\n如果属性名的命名规范没有遵循标识符的命名规范，就不能采用`.`的方式来操作对象的属性，则必须用方括号的形式来访问。比如说，`123`这种属性名，如果我们直接写成`obj.123 = 789`来操作属性，是会报错的。那怎么办呢？办法如下：\n\n语法格式如下：（读取时，也是采用这种方式）\n\n```javascript\n// 注意，括号里的属性名，必须要加引号\n\n// 获取属性\n对象['属性名']\n\n// 设置属性值\n对象['属性名'] = 属性值;\n```\n\n上面这种语法格式，举例如下：\n\n```javascript\nobj['123'] = 789;\n```\n\n当然，如果属性名遵循了标识符的命名规范，也可以使用方括号操作属性。\n\n**重要**：使用`[]`这种形式去操作属性会更灵活，因为我们可以在`[]`中传递一个**变量**。也就是说，如果属性名以变量的形式存储，请记得也必须使用方括号的形式操作属性。这在日常开发中，使用得非常多。比如：\n\n```js\nconst person = {\n\t\tname: '千古壹号',\n    age: 30\n}\n\nconst myKey = 'name';\n// 错误的访问方式\nconsole.log(obj.myKey); // undefined\n// 正确的访问方式\nconsole.log(obj[myKey]); // 千古壹号\n```\n\n### 修改对象的属性值\n\n语法：\n\n```javascript\n对象.属性名 = 新值;\n```\n\n举例：\n\n```javascript\nobj.name = 'qiangu yihao';\n```\n\n### 删除对象的属性\n\n语法：\n\n```javascript\ndelete obj.name;\n```\n\n### in 运算符\n\n通过该运算符可以检查一个对象中是否含有指定的属性。如果有则返回 true，没有则返回 false。\n\n语法：\n\n```javascript\n'属性名' in 对象;\n```\n\n举例：\n\n```javascript\n//检查对象 obj 中是否含有name属性\nconsole.log('name' in obj);\n```\n\n我们平时使用的对象不一定是自己创建的，可能是从接口获取的，这个时候，in 运算符可以派上用场。\n\n当然，还有一种写法可以达到上述目的：\n\n```js\nif (obj.name) {\n    // 如果对象 obj 中有name属性，我就继续做某某事情。\n}\n```\n\n## for of：遍历数组\n\n\nES6 中，如果我们要遍历一个数组，可以这样做：\n\n```js\nlet arr1 = [2, 6, 8, 5];\n\nfor (let value of arr1) {\n    console.log(value);\n}\n```\n\n打印结果：\n\n\n```\n2\n6\n8\n5\n```\n\n\nfor ... of 的循环可以避免我们开拓内存空间，增加代码运行效率，所以建议大家在以后的工作中使用 for…of 遍历数组。\n\n注意，上面的数组中，`for ... of`获取的是数组里的值；如果采用`for ... in`遍历数组，则获取的是 index 索引值。\n\n### Map 对象的遍历\n\n`for ... of`既可以遍历数组，也可以遍历 Map 对象。\n\n\n## for in：遍历对象的属性\n\n> `for ... in`主要用于遍历对象，不建议用来遍历数组。\n\n语法：\n\n```javascript\nfor (const 变量 in 对象) {\n\n}\n```\n\n解释：对象中有几个属性，循环体就会执行几次。每次执行时，会将对象中的**每个属性的 属性名 赋值给变量**。\n\n语法举例：\n\n```javascript\nfor (var key in obj) {\n    console.log(key); // 这里的 key 是：对象属性的键（也就是属性名）\n    console.log(obj[key]); // 这里的 obj[key] 是：对象属性的值（也就是属性值）\n}\n```\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <script type=\"text/javascript\">\n            const obj = {\n                name: 'qianguyihao',\n                age: 28,\n                gender: '男',\n                address: 'shenzhen',\n                sayHi: function () {\n                    console.log(this.name);\n                },\n            };\n\n            // 遍历对象中的属性\n            for (const key in obj) {\n                console.log('属性名:' + key);\n                console.log('属性值:' + obj[key]); // 注意，因为这里的属性名 key 是变量，所以，如果想获取属性值，不能写成 obj.key，而是要写成 obj[key]\n            }\n        </script>\n    </head>\n\n    <body></body>\n</html>\n```\n\n打印结果：\n\n```\n属性名:name\n属性值:qianguyihao\n\n属性名:age\n属性值:26\n\n属性名:gender\n属性值:男\n\n属性名:address\n属性值:shenzhen\n\n属性名:sayHi\n属性值:function() {\n                    console.log(this.name);\n                }\n```\n\n### for in 遍历数组（不建议）\n\n另外，for in 当然也可以用来遍历数组（只是不建议），此时的 key 是数组的索引。举例如下：\n\n```js\nconst arr = ['hello1', 'hello2', 'hello3'];\n\nfor (const key in arr) {\n    console.log('属性名：' + key);\n    console.log('属性值：' + arr[key]);\n}\n```\n\n打印结果：\n\n```\n属性名：0\n属性值：hello1\n\n属性名：1\n属性值：hello2\n\n属性名：2\n属性值：hello3\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "04-JavaScript基础/30-浅拷贝和深拷贝.md",
    "content": "---\ntitle: 30-浅拷贝和深拷贝\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 概念\n\n-   浅拷贝：只拷贝最外面一层的数据；更深层次的对象，只拷贝引用。\n\n-   深拷贝：拷贝多层数据；每一层级别的数据都会拷贝。\n\n**总结**：\n\n拷贝引用的时候，是属于**传址**，而非**传值**。关于传值和传址的区别，是很基础的内容，详见《JavaScript 基础/对象简介.md》这篇文章。\n\n深拷贝会把对象里**所有的数据**重新复制到新的内存空间，是最彻底的拷贝。\n\n## 浅拷贝的实现方式\n\n### 用 for in 实现浅拷贝（比较繁琐）\n\n```js\nconst obj1 = {\n    name: 'qianguyihao',\n    age: 28,\n    info: {\n        desc: '很厉害',\n    },\n};\n\nconst obj2 = {};\n//  用 for in 将 obj1 的值拷贝给 obj2\nfor (let key in obj1) {\n    obj2[key] = obj1[key];\n}\n\nconsole.log('obj2:' + JSON.stringify(obj2));\n\nobj1.info.desc = '永不止步'; // 当修改 obj1 的第二层数据时，obj2的值也会被改变。所以  for in 是浅拷贝\n\nconsole.log('obj2:' + JSON.stringify(obj2));\n```\n\n上方代码中，用 for in 做拷贝时，只能做到浅拷贝。也就是说，在 obj2 中， name 和 age 这两个属性会单独存放在新的内存地址中，和 obj1 没有关系。但是，`obj2.info` 属性，跟 `obj1.info`属性，**它俩指向的是同一个堆内存地址**。所以，当我修改 `obj1.info` 里的值之后，`obj2.info`的值也会被修改。\n\n打印结果如下：\n\n```\nobj2:{\"name\":\"qianguyihao\",\"age\":28,\"info\":{\"desc\":\"很厉害\"}}\n\nobj2:{\"name\":\"qianguyihao\",\"age\":28,\"info\":{\"desc\":\"永不止步\"}}\n```\n\n### 用 Object.assgin() 实现浅拷贝（推荐的方式）\n\n上面的 for in 方法做浅拷贝过于繁琐。ES6 给我们提供了新的语法糖，通过 `Object.assgin()` 可以实现**浅拷贝**。\n\n`Object.assgin()` 在日常开发中，使用得相当频繁，非掌握不可。\n\n**语法**：\n\n```js\n// 语法1\nobj2 = Object.assgin(obj2, obj1);\n\n// 语法2\nObject.assign(目标对象, 源对象1, 源对象2...);\n```\n\n**解释**：将`obj1` 拷贝给 `obj2`。执行完毕后，obj2 的值会被更新。\n\n**作用**：将 obj1 的值追加到 obj2 中。如果对象里的属性名相同，会被覆盖。\n\n从语法2中可以看出，Object.assign() 可以将多个“源对象”拷贝到“目标对象”中。\n\n**例 1**：\n\n```js\nconst obj1 = {\n    name: 'qianguyihao',\n    age: 28,\n    info: {\n        desc: 'hello',\n    },\n};\n\n// 浅拷贝：把 obj1 拷贝给 obj2。如果 obj1 只有一层数据，那么，obj1 和 obj2 则互不影响\nconst obj2 = Object.assign({}, obj1);\nconsole.log('obj2:' + JSON.stringify(obj2));\n\nobj1.info.desc = '永不止步'; // 由于 Object.assign() 只是浅拷贝，所以当修改 obj1 的第二层数据时，obj2 对应的值也会被改变。\nconsole.log('obj2:' + JSON.stringify(obj2));\n```\n\n代码解释：由于 Object.assign() 只是浅拷贝，所以在当前这个案例中， obj2 中的 name 属性和 age 属性是单独存放在新的堆内存地址中的，和 obj1 没有关系；但是，`obj2.info` 属性，跟 `obj1.info`属性，**它俩指向的是同一个堆内存地址**。所以，当我修改 `obj1.info` 里的值之后，`obj2.info`的值也会被修改。\n\n打印结果：\n\n```\nobj2:{\"name\":\"qianguyihao\",\"age\":28,\"info\":{\"desc\":\"hello\"}}\n\nobj2:{\"name\":\"qianguyihao\",\"age\":28,\"info\":{\"desc\":\"永不止步\"}}\n```\n\n**例 2**：\n\n```js\nconst myObj = {\n    name: 'qianguyihao',\n    age: 28,\n};\n\n// 【写法1】浅拷贝：把 myObj 拷贝给 obj1\nconst obj1 = {};\nObject.assign(obj1, myObj);\n\n// 【写法2】浅拷贝：把 myObj 拷贝给 obj2\nconst obj2 = Object.assign({}, myObj);\n\n// 【写法3】浅拷贝：把 myObj 拷贝给 obj31。注意，这里的 obj31 和 obj32 其实是等价的，他们指向了同一个内存地址\nconst obj31 = {};\nconst obj32 = Object.assign(obj31, myObj);\n\n```\n\n上面这三种写法，是等价的。所以，当我们需要将对象 A 复制（拷贝）给对象 B，不要直接使用 `B = A`，而是要使用 Object.assign(B, A)。\n\n**例 3**：\n\n```js\nlet obj1 = { name: 'qianguyihao', age: 26 };\nlet obj2 = { city: 'shenzhen', age: 28 };\nlet obj3 = {};\n\nObject.assign(obj3, obj1, obj2); // 将 obj1、obj2的内容赋值给 obj3\nconsole.log(obj3); // {name: \"qianguyihao\", age: 28, city: \"shenzhen\"}\n```\n\n上面的代码，可以理解成：将多个对象（obj1和obj2）合并成一个对象 obj3。\n\n**例4**：【重要】\n\n```js\nconst obj1 = {\n    name: 'qianguyihao',\n    age: 28,\n    desc: 'hello world',\n};\n\nconst obj2 = {\n    name: '许嵩',\n    sex: '男',\n};\n\n// 浅拷贝：把 obj1 赋值给 obj2。这一行，是关键代码。这行代码的返回值也是 obj2\nObject.assign(obj2, obj1);\n\nconsole.log(JSON.stringify(obj2));\n```\n\n打印结果：\n\n```\n{\n    \"name\":\"qianguyihao\",\n    \"sex\":\"男\",\n    \"age\":28,\n    \"desc\":\"hello world\"\n}\n```\n\n注意，**例 4 在实际开发中，会经常遇到，一定要掌握**。它的作用是：将 obj1 的值追加到 obj2 中。如果两个对象里的属性名相同，则 obj2 中的值会被 obj1 中的值覆盖。\n\n**例5：**\n\n```js\nconst a1 = undefined;\nconst a2 = null;\n\nObject.assgin(a1, {name: 'qiangu'}); // 报错：TypeError. Cannot convert undefined or null to object\nObject.assgin(a1, {name: 'yihao'}); // 报错：TypeError. Cannot convert undefined or null to object\n```\n\nObject.assign() 方法的第一个参数是目标对象，如果目标对象是 undefined 或 null，则会报错 TypeError。\n\n\n所以，为了避免报错，我们要先确目标对象存在。比如使用短路运算符确保 a1 是存在的，就不会报错：\n\n```js\nconst a1 = undefined || {}; // 短路苏奶奶福，确保 obj 是存在的对象\nObject.assgin(a1, {name: 'qiangu'});\n```\n\n## 深拷贝的实现方式\n\n深拷贝其实就是将浅拷贝进行递归。\n\n### 用 for in 递归实现深拷贝\n\n代码实现：\n\n```js\nlet obj1 = {\n    name: 'qianguyihao',\n    age: 28,\n    info: {\n        desc: 'hello',\n    },\n    color: ['red', 'blue', 'green'],\n};\nlet obj2 = {};\n\ndeepCopy(obj2, obj1);\nconsole.log(obj2);\nobj1.info.desc = 'github';\nconsole.log(obj2);\n\n// 方法：深拷贝\nfunction deepCopy(newObj, oldObj) {\n    for (let key in oldObj) {\n        // 获取属性值 oldObj[key]\n        let item = oldObj[key];\n        // 判断这个值是否是数组\n        if (item instanceof Array) {\n            newObj[key] = [];\n            deepCopy(newObj[key], item);\n        } else if (item instanceof Object) {\n            // 判断这个值是否是对象\n            newObj[key] = {};\n            deepCopy(newObj[key], item);\n        } else {\n            // 简单数据类型，直接赋值\n            newObj[key] = item;\n        }\n    }\n}\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/31-对象的高级操作.md",
    "content": "---\ntitle: 31-对象的高级操作\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## hasOwnProperty()：判断对象中是否包含某个属性\n\nhasOwnProperty() 是 Object 对象的一个方法，用于判断对象自身（即不包括从原型链继承来的属性）是否具有某个特定的属性。\n\n语法：\n\n```js\nobj.hasOwnProperty(prop);\n```\n\n解释：\n\n- obj 是要检查的对象。\n- prop 是一个字符串，表示要检查的属性名。\n\n返回值：如果对象 obj 自身包含名为 prop 的属性，则返回 true。否则，返回 false。\n\n举例：\n\n```js\nconst obj = {a: undefined, b: 2, c: 3};\n\nconsole.log(obj.hasOwnProperty('a')); // true\nconsole.log(obj.hasOwnProperty('b')); // true\nconsole.log(obj.hasOwnProperty('d')); // false\n\n```\n\n## Object.freeze() 冻结对象\n\nObject.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改；冻结了一个对象则不能向这个对象添加新的属性，不能删除已有属性，不能修改该对象已有属性的可枚举性、可配置性、可写性，以及不能修改已有属性的值。此外，冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。\n\n代码举例：\n\n```js\nconst params = {\n    name: 'qianguyihao';\n    port: '8899';\n}\n\nObject.freeze(params); // 冻结对象 params\n\nparams.port = '8080';// 修改无效\n\n```\n\n上方代码中，把 params 对象冻结后，如果想再改变 params 里面的属性值，是无效的。\n"
  },
  {
    "path": "04-JavaScript基础/32-原型链和原型继承（待更新）.md",
    "content": "---\ntitle: 31_2-原型链和原型继承（待更新）\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 前言\n\n在 ES6 中，我们可以通过 ES6 引入的**类 Class** 来实现面向对象的编程（下一篇文章会讲到）。但是**在 ES6 之前，我们是通过构造函数和原型，来模拟类的实现机制**。\n\n今天这篇文章，我们就来学习构造函数和原型。\n\n\n"
  },
  {
    "path": "04-JavaScript基础/33-类和构造继承（待更新）.md",
    "content": "---\ntitle: 31_3-类和构造继承（待更新）\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n"
  },
  {
    "path": "04-JavaScript基础/34-正则表达式.md",
    "content": "---\ntitle: 34-正则表达式\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 正则表达式简介\n\n**定义**：正则表达式用于定义一些字符串的规则。\n\n**作用**：计算机可以根据正则表达式，来检查一个字符串是否符合指定的规则；或者将字符串中符合规则的内容提取出来。\n\n如果你想查看正则更多的内容，可以查阅官方文档关于 RegExp 这个内置对象的用法。\n\n## 创建正则表达式的对象\n\n### 方式一：使用构造函数创建正则表达式的对象\n\n语法：\n\n```javascript\n\tvar 变量 = new RegExp(\"正则表达式\"); // 注意，参数是字符串\n\n\tvar 变量 = new RegExp(\"正则表达式\", \"匹配模式\"); // 注意，两个参数都是字符串\n```\n\n备注：`RegExp`的意思是 **Regular expression**。使用typeof检查正则对象，会返回object。\n\n上面的语法中，既可以传一个参数，也可以传两个参数。\n\n创建了正则表达式的对象后，该怎么使用呢？大致分为两个步骤：\n\n- （1）创建正则表达式的对象 reg。\n\n- （2）使用 reg 的test() 方法，判断指定字符串是否符合规则。\n\n**正则表达式的`test()`方法**：【重要】\n\n```javascript\n\tmyReg.test(str); // 判断字符串 str 是否符合 指定的 myReg 这个正则表达式的规则\n```\n\n解释：使用`test()`这个方法可以用来检查一个字符串是否符合正则表达式的规则，**如果符合则返回true，否则返回false**。\n\n我们来看看下面的例子。\n\n**1、传一个参数时**：\n\n构造函数 RegExp 中，可以只传一个参数。\n\n代码举例：\n\n```javascript\n\tvar reg = new RegExp(\"a\"); // 定义一个正则表达式：检查一个字符串中是否含有 a\n\n\tvar str1 = \"qianguyihao\";\n\tvar str2 = \"smyh\";\n\n\t// 通过 test()方法，判断字符串是否符合 上面定义的 reg 规则\n\tconsole.log(reg.test(str1)); // 打印结果：true\n\tconsole.log(reg.test(str2)); // 打印结果：false\n\n```\n\n注意，上面的例子中，我们是先定义了一个正则表达式的规则，然后通过正则表达式的`test()`方法来判断字符串是否符合之前定义的规则。\n\n**2、传两个参数时**：匹配模式 【重要】\n\n构造函数 RegExp 中，也可以传两个参数。我们可以传递一个**匹配模式**作为第二个参数。这个参数可以是：\n\n- `i` 忽略大小写。这里的 i 指的是 ignore。\n\n- `g` 全局匹配模式。这里的 g 指的是 global。\n\n代码举例：\n\n```javascript\n    var reg = new RegExp('A', 'i');\n    var str = 'qiangu';\n\n    console.log(reg.test(str)); // 打印结果：true\n```\n\n### 方式二：使用字面量创建正则表达式\n\n我们可以使用字面量来创建正则表达式。\n\n语法：\n\n```javascript\n\tvar 变量 = /正则表达式/;  // 注意，这个语法里没有引号\n\n\tvar 变量 = /正则表达式/匹配模式;  // 注意，这个语法里没有引号\n```\n\n代码举例：\n\n```javascript\n\tvar reg = /A/i; // 定义正则表达式的规则：检查一个字符串中是否含有 a。忽略大小写。\n\tvar str = \"qiangu\";\n\n\tconsole.log(typeof reg);  // 打印结果：object\n\tconsole.log(reg.test(str)); // 打印结果：true\n```\n\n### 以上两种方式的对比\n\n- 方式一：使用构造函数创建时，更加灵活，因为参数中还可以传递变量。\n\n- 方式二：使用字面量的方式创建，更加简单。\n\n代码举例：\n\n```javascript\n\tvar reg = new RegExp(\"a\", \"i\"); // 方式一\n\n\tvar reg = /a/i; // 方式二\n```\n\n上面这两行代码的作用是等价的。\n\n### 避坑指南：全局匹配 g 慎用test()方法\n\n对于非全局匹配的正则表达式，`test()`只会检测**是否存在某个目标字符串**（只要存在就为 true），多次检测的结果都相同。例如：\n\n```javascript\nconst reg = /test/;\nconst str = '_test_test';\n\nreg.test(str) // true\nreg.test(str) // true\nreg.test(str) // true\n```\n\n重点来了。\n\n当设置全局标志 `/g` 时，一旦字符串中还存在匹配，test() 方法都将返回 true，同时匹配成功后将把 `lastIndex` 属性的值**设置为上次匹配成功结果之后的第一个字符所在的位置**，下次匹配将从 `lastIndex` 指示的位置开始；匹配不成功时返回 false，同时将 lastIndex 属性的值重置为 0。\n\n举例：（很重要的例子，看仔细）\n\n```javascript\nconst reg = /test/g;\nconst str = '_test_test';\n\nconsole.log(reg.test(str)); // true\nconsole.log(reg.lastIndex); // 5\n\nconsole.log(reg.test(str)); // true\nconsole.log(reg.lastIndex); // 10\n\nconsole.log(reg.test(str)); // false\nconsole.log(reg.lastIndex); // 0\n```\n\n**总结**：\n\n全局匹配模式`g`一般用于 `exec()`、`match()`、`replace()`等方法。\n\n全局匹配模式`g`如果用于test()方法会有问题。因为g模式会生成一个`lastindex`参数来存储匹配最后一次的位置。\n\n参考链接：\n\n- [JS正则表达式全局匹配的那些坑](https://juejin.im/post/5de9bd5fe51d45582c27b6f3)\n\n- [javascript正则全局匹配g慎用test方法](https://blog.csdn.net/Leolu007/article/details/8576490)\n\n- [issues](https://github.com/qianguyihao/Web/issues/69)\n\n\n## 正则表达式的简单语法\n\n### 检查一个字符串中是否包含 a或b\n\n**写法1**：\n\n```javascript\n\tvar reg = /a|b/;\n```\n\n解释：使用 `|` 表示`或`的意思。\n\n**写法2**：\n\n```javascript\n\tvar reg = /[ab]/;  // 跟上面的那行语法，是等价的\n```\n\n解释：这里的`[]`也是表示`或`的意思。\n\n`[]`这个符号在正则还是比较常用的。我们接下来看几个例子。\n\n### []表示：或\n\n一些规则：\n\n- `/[ab]/` 等价于 `/a|b/`：检查一个字符串中是否包含 **a或b**\n\n- `/[a-z]/`：检查一个字符串那种是否包含**任意小写字母**\n\n- `/[A-Z]/`：任意大写字母\n\n- `/[A-z]/`：任意字母\n\n- `/[0-9]/`：任意数字\n\n- `/a[bde]c/`：检查一个字符串中是否包含 abc 或 adc 或 aec\n\n### [^ ] 表示：除了\n\n举例1：\n\n```javascript\n  var reg = /[^ab]/; // 规则：字符串中，除了a、b之外，还有没有其他的字符内容？\n  var str = \"acb\";\n\n  console.log(reg.test(str)); // 打印结果：true\n```\n\n举例2：（可以用来验证某字符串是否为 纯数字）\n\n```javascript\n\tvar reg = /[^0-9]/;  // 规则：字符串中，除了数字之外，还有没有其他的内容？\n\tvar str1 = \"1991\";\n\tvar str2 = \"199a1\";\n\n\tconsole.log(reg.test(str1)); // 打印结果：false （如果字符串是 纯数字，则返回 false）\n\tconsole.log(reg.test(str2)); // 打印结果：true\n```\n\n## 支持正则表达式的 String 对象的方法\n\n String对象的如下方法，是支持正则表达式的：\n\n| 方法 | 描述 | 备注 |\n|:-------------|:-------------|:-------------|\n| split() | 将字符串拆分成数组  |   |\n| search() | 搜索字符串中是否含有指定内容，返回索引 index |  |\n| match() | 根据正则表达式，从一个字符串中将符合条件的内容提取出来 |  |\n| replace()  | 将字符串中的指定内容，替换为新的内容并返回 |  |\n\n下面来分别介绍和举例。\n\n### split()\n\n`split()`：将一个字符串拆分成一个数组。可以接受一个正则表达式作为参数。\n\n备注：关于`split()`更详细的用法，可以看之前的关于《内置对象：String》这篇文章。\n\n**正则相关的举例**：根据任意字母，将字符串拆分成数组。\n\n代码实现：（通过正则）\n\n```javascript\n\tvar str = \"1a2b3c4d5e6f7g\";\n\n\tvar result = str.split(/[A-z]/); // 参数是一个正则表达式：表示所有字母\n\tconsole.log(result);\n```\n\n打印结果：\n\n```json\n\t[\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"\"]\n```\n\n### search()\n\n`search()`：搜索字符串中是否含有指定内容。如果搜索到指定内容，则会返回第一次出现的索引；否则返回-1。\n\n`search()`方法可以接受一个正则表达式作为参数，然后会根据正则表达式去检索字符串。`serach()`只会查找第一个，即使设置全局匹配也没用。\n\n**举例**：\n\n```javascript\n\tvar str = \"hello abc hello aec afc\";\n\t/*\n\t* 搜索字符串中是否含有abc 或 aec 或 afc\n\t*/\n\tresult = str.search(/a[bef]c/);\n\tconsole.log(result); // 打印结果：6\n```\n\n### match()\n\n`match()`：根据正则表达式，从一个字符串中将符合条件的内容提取出来，封装到一个数组中返回（即使只查询到一个结果）。\n\n**注意**：默认情况下，`match()`方法只会找到**第一个**符合要求的内容，找到以后就停止检索。我们可以设置正则表达式为**全局匹配**模式，这样就会匹配到所有的内容，并以**数组**的形式返回。\n\n另外，我们可以为一个正则表达式设置多个匹配模式，且匹配模式的顺序无所谓。\n\n**代码举例**：\n\n```javascript\n\tvar str = \"1a2a3a4a5e6f7A8B9C\";\n\n\tvar result1 = str.match(/[a-z]/);   // 找到符合要求的第一个内容，然后返回\n\tvar result2 = str.match(/[a-z]/g);  // 设置为“全局匹配”模式，匹配字符串中 所有的小写字母\n\tvar result3 = str.match(/[a-z]/gi); // 设置多个匹配模式，匹配字符串中 所有的字母（忽略大小写）\n\n\tconsole.log(result1); // 打印结果：[\"a\"]\n\tconsole.log(result2); // 打印结果：[\"a\", \"a\", \"a\", \"a\", \"e\", \"f\"]\n\tconsole.log(result3); // 打印结果：[\"a\", \"a\", \"a\", \"a\", \"e\", \"f\", \"A\", \"B\", \"C\"]\n```\n\n**总结**：\n\nmatch()这个方法还是很实用的，可以在一个很长的字符串中，提取出**有规则**的内容。这不就是爬虫的时候经常会遇到的场景么？\n\n### replace()\n\n`replace()`：将字符串中的指定内容，替换为新的内容并返回。不会修改原字符串。\n\n语法：\n\n```javascript\n\t新的字符串 = str.replace(被替换的内容，新的内容);\n```\n\n参数解释：\n\n- 被替换的内容：可以接受一个正则表达式作为参数。\n\n- 新的内容：默认只会替换第一个。如果需要替换全部符合条件的内容，可以设置正则表达式为**全局匹配**模式。\n\n代码举例：\n\n```javascript\n    //replace()方法：替换\n    var str2 = \"Today is fine day,today is fine day !!!\"\n\n    console.log(str2);\n    console.log(str2.replace(\"today\",\"tomorrow\"));  //只能替换第一个today\n    console.log(str2.replace(/today/gi,\"tomorrow\")); //这里用到了正则，且为“全局匹配”模式，才能替换所有的today\n```\n\n## 常见正则表达式举例\n\n### 检查一个字符串是否是一个合法手机号\n\n手机号的规则：\n\n- 以1开头（`^1` 表示1开头 , `[^1]`表示非1或除了1）\n\n- 第二位是3~9之间任意数字\n\n- 三位以后任意9位数字\n\n正则实现：\n\n```javascript\n\tvar phoneStr = \"13067890123\";\n\n\tvar phoneReg = /^1[3-9][0-9]{9}$/;\n\n\tconsole.log(phoneReg.test(phoneStr));\n```\n\n**备注**：如果在正则表达式中同时使用`^`和`$`符号，则要求字符串必须完全符合正则表达式。\n\n### 去掉字符串开头和结尾的空格\n\n正则实现：\n\n```javascript\n\tstr = str.replace(/^\\s*|\\s*$/g,\"\");\n```\n\n解释如下：\n\n```javascript\n\tstr = str.replace(/^\\s*/, \"\"); //去除开头的空格\n\n\tstr = str.replace(/\\s*$/, \"\"); //去除结尾的空格\n```\n\n### 判断字符串是否为电子邮件\n\n正则实现：\n\n```javascript\n\tvar emailReg = /^\\w{3,}(\\.\\w+)*@[A-z0-9]+(\\.[A-z]{2,5}){1,2}$/;\n\n\tvar email = \"abchello@163.com\";\n\n\tconsole.log(emailReg.test(email));\n```\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/35-事件简介.md",
    "content": "---\ntitle: 35-事件简介\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 事件简介\n\n事件：就是文档或浏览器窗口中发生的一些特定的交互瞬间。对于 Web 应用来说，有下面这些代表性的事件：点击某个元素、将鼠标移动至某个元素上方、关闭弹窗等等。\n\nJavaScript 是以**事件驱动为核心**的一门语言。JavaScript 与 HTML 之间的交互是通过事件实现的。\n\n### 事件的三要素\n\n**事件的三要素：事件源、事件、事件驱动程序**。\n\n比如，我用手去按开关，灯亮了。这件事情里，事件源是：手。事件是：按开关。事件驱动程序是：灯开了或者关了。\n\n再比如，网页上弹出一个广告，我点击右上角的`X`，广告就关闭了。这件事情里，事件源是：`X`。事件是：onclick。事件驱动程序是：广告关闭了。\n\n于是我们可以总结出：谁引发的后续事件，谁就是事件源。\n\n**总结如下：**\n\n- 事件源：引发后续事件的html标签。\n\n- 事件：js已经定义好了（见下图）。\n\n- 事件驱动程序：对样式和html的操作。也就是DOM。\n\n也就是说，我们可以在时间对应的属性中写一些js代码，当事件被触发时，这些代码将会执行。\n\n**代码书写步骤如下：**（重要）\n\n- （1）获取事件源：document.getElementById(“box”);   // 类似于Android里面的findViewById\n\n- （2）绑定事件： 事件源box.事件onclick = function(){ 事件驱动程序 };\n\n- （3）书写事件驱动程序：关于DOM的操作。\n\n最简单的代码举例：（点击box1，然后弹框）\n\n```html\n<body>\n<div id=\"box1\"></div>\n\n<script type=\"text/javascript\">\n    // 1、获取事件源\n    var div = document.getElementById(\"box1\");\n    // 2、绑定事件\n    div.onclick = function () {\n        // 3、书写事件驱动程序\n        alert(\"我是弹出的内容\");\n    }\n</script>\n\n</body>\n```\n\n常见的事件如下：\n\n![](http://img.smyhvae.com/20180126_1553.png)\n\n下面针对这事件的三要素，进行分别介绍。\n\n### 1、获取事件源的方式（DOM节点的获取）\n\n获取事件源的常见方式如下：\n\n```javascript\nvar div1 = document.getElementById(\"box1\");      //方式一：通过id获取单个标签\n\nvar arr1 = document.getElementsByTagName(\"div\");     //方式二：通过 标签名 获得 标签数组，所以有s\n\nvar arr2 = document.getElementsByClassName(\"hehe\");  //方式三：通过 类名 获得 标签数组，所以有s\n```\n\n### 2、绑定事件的方式\n\n方式一：直接绑定匿名函数\n\n```html\n<div id=\"box1\" ></div>\n\n<script type=\"text/javascript\">\n    var div1 = document.getElementById(\"box1\");\n    //绑定事件的第一种方式\n    div1.onclick = function () {\n        alert(\"我是弹出的内容\");\n    }\n</script>\n```\n\n方式二：先单独定义函数，再绑定\n\n```html\n <div id=\"box1\" ></div>\n\n<script type=\"text/javascript\">\n    var div1 = document.getElementById(\"box1\");\n    //绑定事件的第二种方式\n    div1.onclick = fn;   //注意，这里是fn，不是fn()。fn()指的是返回值。\n    //单独定义函数\n    function fn() {\n        alert(\"我是弹出的内容\");\n    }\n</script>\n```\n\n注意上方代码的注释。**绑定的时候，是写fn，不是写fn()**。fn代表的是整个函数，而fn()代表的是返回值。\n\n方式三：行内绑定\n\n```html\n<!--行内绑定-->\n<div id=\"box1\" onclick=\"fn()\"></div>\n\n<script type=\"text/javascript\">\n\n    function fn() {\n        alert(\"我是弹出的内容\");\n    }\n\n</script>\n```\n\n注意第一行代码，绑定时，是写的`\"fn()\"`，不是写的`\"fn\"`。因为绑定的这段代码不是写在js代码里的，而是被识别成了**字符串**。\n\n### 3、事件驱动程序\n\n我们在上面是拿alert举例，不仅如此，我们还可以操作标签的属性和样式。举例如下：\n\n点击鼠标时，原本粉色的div变大了，背景变红：\n\n```html\n    <style>\n        #box1 {\n            width: 100px;\n            height: 100px;\n            background-color: pink;\n            cursor: pointer;\n        }\n    </style>\n</head>\n\n<body>\n\n<div id=\"box1\" ></div>\n\n<script type=\"text/javascript\">\n    var div1 = document.getElementById(\"box1\");\n    //点击鼠标时，原本粉色的div变大了，背景变红了\n    div1.onclick = function () {\n        div1.style.width = \"200px\";   //属性值要写引号\n        div1.style.height = \"200px\";\n        div1.style.backgroundColor = \"red\";   //属性名是backgroundColor，不是background-color\n    }\n</script>\n```\n\n上方代码的注意事项：\n\n- 在js里写属性值时，要用引号\n\n- 在js里写属性名时，是`backgroundColor`，不是CSS里面的`background-color`。\n\n实现效果如下：\n\n![](http://img.smyhvae.com/20180126_1720.gif)\n\n### onload事件\n\n> onload事件比较特殊，这里单独讲一下。\n\n**当页面加载（文本和图片）完毕的时候，触发onload事件。**\n\n举例：\n\n```html\n<script type=\"text/javascript\">\n    window.onload = function () {\n        console.log(\"smyhvae\");  //等页面加载完毕时，打印字符串\n    }\n</script>\n```\n\n有一点我们要知道：**js的加载是和html同步加载的**。因此，如果使用元素在定义元素之前，容易报错。这个时候，onload事件就能派上用场了，我们可以把使用元素的代码放在onload里，就能保证这段代码是最后执行。\n\n建议是：整个页面上所有元素加载完毕再执行js内容。所以，window.onload可以预防使用标签在定义标签之前。\n\n**备注**：关于 onLoad事件，在下一篇文章《DOM简介和DOM操作》中有更详细的讲解和示例。\n\n### 事件举例：京东顶部广告栏\n\n![](http://img.smyhvae.com/20180122_1020.png)\n\n比如上面这张图，当鼠标点击右上角的`X`时，关掉整个广告栏，这就要用到事件。\n\n代码实现如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n        }\n        .top-banner {\n            background-color: pink;\n            height: 80px;\n        }\n        .w {\n            width: 1210px;\n            margin: 10px auto;\n            position: relative;\n        }\n        img {\n            display: block;\n            width: 1210px;\n            height: 80px;\n            background-color: blue;\n        }\n        a {\n            position: absolute;\n            top: 5px;\n            right: 5px;\n            color: #fff;\n            background-color: #000;\n            text-decoration: none;\n            width: 20px;\n            height: 20px;\n            font: 700 14px/20px \"simsum\";\n            text-align: center;\n        }\n        .hide {\n            display: none!important;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"top-banner\" id=\"topBanner\">\n        <div class=\"w\">\n            <img src=\"\" alt=\"\"/>\n            <a href=\"#\" id=\"closeBanner\">×</a>\n        </div>\n    </div>\n\n\n<script>\n    //需求：点击案例，隐藏盒子。\n    //思路：点击a链接，让top-banner这个盒子隐藏起来（加隐藏类名）。\n\n    //1.获取事件源和相关元素\n    var closeBanner = document.getElementById(\"closeBanner\");\n    var topBanner = document.getElementById(\"topBanner\");\n    //2.绑定事件\n    closeBanner.onclick = function () {\n        //3.书写事件驱动程序\n        //类控制\n//        topBanner.className += \" hide\"; //保留原类名，添加新类名\n        topBanner.className = \"hide\";//替换旧类名（方式一）\n//        topBanner.style.display = \"none\"; //方式二：与上一行代码的效果相同\n    }\n\n</script>\n</body>\n</html>\n\n```\n\n注意最后一行代码，这种方式会替换旧类名，意思是，不管之前的类名叫什么，都会被修改。\n\n### 事件举例：\n\n要求实现效果：当鼠标悬停在img上时，更换为另外一张图片；鼠标离开时，还原为本来的图片。\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script>\n        //window.onload页面加载完毕以后再执行此代码\n        window.onload = function () {\n            //需求：鼠标放到img上，更换为另一张图片，也就是修改路径（src的值）。\n            //步骤：\n            //1.获取事件源\n            //2.绑定事件\n            //3.书写事件驱动程序\n\n            //1.获取事件源\n            var img = document.getElementById(\"box\");\n            //2.绑定事件(悬停事件：鼠标进入到事件源中，立即触发事件)\n            img.onmouseover = function () {\n                //3.书写事件驱动程序(修改src)\n                img.src = \"image/jd2.png\";\n//                this.src = \"image/jd2.png\";\n            }\n\n            //2.绑定事件(悬停事件：鼠标进入到事件源中，立即触发事件)\n            img.onmouseout = function () {\n                //3.书写事件驱动程序(修改src)\n                img.src = \"image/jd1.png\";\n            }\n        }\n    </script>\n</head>\n<body>\n\n<img id=\"box\" src=\"image/jd1.png\" style=\"cursor: pointer;border: 1px solid #ccc;\"/>\n\n</html>\n```\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n"
  },
  {
    "path": "04-JavaScript基础/36-DOM简介和DOM操作.md",
    "content": "---\ntitle: 36-DOM简介和DOM操作\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 常见概念\n\n### JavaScript的组成\n\nJavaScript基础分为三个部分：\n\n- ECMAScript：JavaScript的语法标准。包括变量、表达式、运算符、函数、if语句、for语句等。\n\n- **DOM**：文档对象模型（Document object Model），操作**网页上的元素**的API。比如让盒子移动、变色、轮播图等。\n\n- **BOM**：浏览器对象模型（Browser Object Model），操作**浏览器部分功能**的API。比如让浏览器自动滚动。\n\n### 节点\n\n**节点**（Node）：构成 HTML 网页的最基本单元。网页中的每一个部分都可以称为是一个节点，比如：html标签、属性、文本、注释、整个文档等都是一个节点。\n\n虽然都是节点，但是实际上他们的具体类型是不同的。常见节点分为四类：\n\n- 文档节点（文档）：整个 HTML 文档。整个 HTML 文档就是一个文档节点。\n\n- 元素节点（标签）：HTML标签。\n\n- 属性节点（属性）：元素的属性。\n\n- 文本节点（文本）：HTML标签中的文本内容（包括标签之间的空格、换行）。\n\n节点的类型不同，属性和方法也都不尽相同。所有的节点都是Object。\n\n### 什么是DOM\n\n**DOM**：Document Object Model，文档对象模型。DOM 为文档提供了结构化表示，并定义了如何通过脚本来访问文档结构。目的其实就是为了能让js操作html元素而制定的一个规范。\n\nDOM就是由节点组成的。\n\n**解析过程**：\nHTML加载完毕，渲染引擎会在内存中把HTML文档，生成一个DOM树，getElementById是获取内中DOM上的元素节点。然后操作的时候修改的是该元素的**属性**。\n\n**DOM树**：（一切都是节点）\n\nDOM的数据结构如下：\n\n![](http://img.smyhvae.com/20180126_2105.png)\n\n上图可知，**在HTML当中，一切都是节点**（非常重要）。节点的分类，在上一段中，已经讲了。\n\n整个html文档就是一个文档节点。所有的节点都是Object。\n\n### DOM可以做什么\n\n- 找对象（元素节点）\n\n- 设置元素的属性值\n\n- 设置元素的样式\n\n- 动态创建和删除元素\n\n- 事件的触发响应：事件源、事件、事件的驱动程序\n\n## 元素节点的获取\n\nDOM节点的获取方式其实就是**获取事件源的方式**。关于事件，上一篇文章中已经讲到了。\n\n想要操作元素节点，必须首先要找到该节点。有三种方式可以获取DOM节点：\n\n```javascript\nvar div1 = document.getElementById(\"box1\"); //方式一：通过 id 获取 一个 元素节点（为什么是一个呢？因为 id 是唯一的）\n\nvar arr1 = document.getElementsByTagName(\"div\"); //方式二：通过 标签名 获取 元素节点数组，所以有s\n\nvar arr2 = document.getElementsByClassName(\"hehe\"); //方式三：通过 类名 获取 元素节点数组，所以有s\n```\n\n既然方式二、方式三获取的是标签数组，那么习惯性是**先遍历之后再使用**。\n\n特殊情况：数组中的值只有1个。即便如此，这一个值也是包在数组里的。这个值的获取方式如下：\n\n```javascript\ndocument.getElementsByTagName(\"div1\")[0];    //取数组中的第一个元素\n\ndocument.getElementsByClassName(\"hehe\")[0];  //取数组中的第一个元素\n```\n\n## DOM访问关系的获取\n\nDOM的节点并不是孤立的，因此可以通过DOM节点之间的相对关系对它们进行访问。如下：\n\n![](http://img.smyhvae.com/20180126_2140.png)\n\n节点的访问关系，是以**属性**的方式存在的。\n\nJS中的**父子兄**访问关系：\n\n![](http://img.smyhvae.com/20180126_2145.png)\n\n这里我们要重点知道**parentNode**和**children**这两个属性的用法。下面分别介绍。\n\n### 获取父节点\n\n调用者就是节点。一个节点只有一个父节点，调用方式就是\n\n```javascript\n\t节点.parentNode\n```\n\n### 获取兄弟节点\n\n**1、下一个节点 | 下一个元素节点**：\n\n> Sibling的中文是**兄弟**。\n\n（1）nextSibling：\n\n- 火狐、谷歌、IE9+版本：都指的是下一个节点（包括标签、空文档和换行节点）。\n\n- IE678版本：指下一个元素节点（标签）。\n\n（2）nextElementSibling：\n\n- 火狐、谷歌、IE9+版本：都指的是下一个元素节点（标签）。\n\n**总结**：为了获取下一个**元素节点**，我们可以这样做：在IE678中用nextSibling，在火狐谷歌IE9+以后用nextElementSibling，于是，综合这两个属性，可以这样写：\n\n```javascript\n\t下一个兄弟节点 = 节点.nextElementSibling || 节点.nextSibling\n```\n\n**2、前一个节点 | 前一个元素节点**：\n\n> previous的中文是：前一个。\n\n（1）previousSibling：\n\n- 火狐、谷歌、IE9+版本：都指的是前一个节点（包括标签、空文档和换行节点）。\n\n- IE678版本：指前一个元素节点（标签）。\n\n（2）previousElementSibling：\n\n- 火狐、谷歌、IE9+版本：都指的是前一个元素节点（标签）。\n\n**总结**：为了获取前一个**元素节点**，我们可以这样做：在IE678中用previousSibling，在火狐谷歌IE9+以后用previousElementSibling，于是，综合这两个属性，可以这样写：\n\n```javascript\n\t前一个兄弟节点 = 节点.previousElementSibling || 节点.previousSibling\n```\n\n**3、补充**：获得任意一个兄弟节点：\n\n```javascript\n\t节点自己.parentNode.children[index];  //随意得到兄弟节点\n```\n\n### 获取单个的子节点\n\n**1、第一个子节点 | 第一个子元素节点**：\n\n（1）firstChild：\n\n- 火狐、谷歌、IE9+版本：都指的是第一个子节点（包括标签、空文档和换行节点）。\n\n- IE678版本：指第一个子元素节点（标签）。\n\n（2）firstElementChild：\n\n- 火狐、谷歌、IE9+版本：都指的是第一个子元素节点（标签）。\n\n**总结**：为了获取第一个**子元素节点**，我们可以这样做：在IE678中用firstChild，在火狐谷歌IE9+以后用firstElementChild，于是，综合这两个属性，可以这样写：\n\n```javascript\n\t第一个子元素节点 = 节点.firstElementChild || 节点.firstChild\n```\n\n**2、最后一个子节点 | 最后一个子元素节点**：\n\n（1）lastChild：\n\n- 火狐、谷歌、IE9+版本：都指的是最后一个子节点（包括标签、空文档和换行节点）。\n\n- IE678版本：指最后一个子元素节点（标签）。\n\n（2）lastElementChild：\n\n- 火狐、谷歌、IE9+版本：都指的是最后一个子元素节点（标签）。\n\n**总结**：为了获取最后一个**子元素节点**，我们可以这样做：在IE678中用lastChild，在火狐谷歌IE9+以后用lastElementChild，于是，综合这两个属性，可以这样写：\n\n```javascript\n\t最后一个子元素节点 = 节点.lastElementChild || 节点.lastChild\n```\n\n### 获取所有的子节点\n\n（1）**childNodes**：标准属性。返回的是指定元素的**子节点**的集合（包括元素节点、所有属性、文本节点）。是W3C的亲儿子。\n\n- 火狐 谷歌等高本版会把换行也看做是子节点。\n\n用法：\n\n```javascript\n\t子节点数组 = 父节点.childNodes;   //获取所有节点。\n```\n\n（2）**children**：非标准属性。返回的是指定元素的**子元素节点**的集合。【重要】\n\n- 它只返回HTML节点，甚至不返回文本节点。\n- 在IE6/7/8中包含注释节点（在IE678中，注释节点不要写在里面）。\n\n虽然不是标准的DOM属性，但它和innerHTML方法一样，得到了几乎所有浏览器的支持。\n\n用法：（**用的最多**）\n\n```javascript\n\t子节点数组 = 父节点.children;   //获取所有节点。用的最多。\n```\n\n## DOM节点的操作（重要）\n\n上一段的内容：节点的**访问关系**都是**属性**。\n\n本段的内容：节点的**操作**都是**函数**（方法）。\n\n### 创建节点\n\n格式如下：\n\n```javascript\n\t新的标签(元素节点) = document.createElement(\"标签名\");\n```\n\n比如，如果我们想创建一个li标签，或者是创建一个不存在的adbc标签，可以这样做：\n\n```html\n<script type=\"text/javascript\">\n    var a1 = document.createElement(\"li\");   //创建一个li标签\n    var a2 = document.createElement(\"adbc\");   //创建一个不存在的标签\n\n    console.log(a1);\n    console.log(a2);\n\n    console.log(typeof a1);\n    console.log(typeof a2);\n</script>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180127_1135.png)\n\n### 插入节点\n\n插入节点有两种方式，它们的含义是不同的。\n\n方式1：\n\n```javascript\n\t父节点.appendChild(新的子节点);\n```\n\n解释：父节点的最后插入一个新的子节点。\n\n方式2：\n\n```javascript\n\t父节点.insertBefore(新的子节点,作为参考的子节点)\n```\n\n解释：\n\n- 在参考节点前插入一个新的节点。\n- 如果参考节点为null，那么他将在父节点里面的最后插入一个子节点。\n\n![](http://img.smyhvae.com/20180127_1257.png)\n\n我们可以看到，li标签确实被插入到了box1标签的里面，和box2并列了。\n\n方式2的举例：\n\n![](http://img.smyhvae.com/20180127_1302.png)\n\n我们可以看到，b1标签被插入到了box1标签的里面，和a1标签并列，在a1标签的前面。\n\n\n**特别强调：**\n\n关于方式1的appendChild方法，这里要强调一下。比如，现在有下面这样一个div结构：\n\n```html\n<div class=\"box11\">\n    <div class=\"box12\">生命壹号</div>\n</div>\n\n<div class=\"box21\">\n    <div class=\"box22\">永不止步</div>\n\n</div>\n```\n\n\n上方结构中，子盒子box12是在父亲box11里的，子盒子box22是在父亲box21里面的。现在，如果我调用方法`box11.appendChild(box22)`，**最后产生的结果是：box22会跑到box11中**（也就是说，box22不在box21里面了）。这是一个很神奇的事情：\n\n\n![](http://img.smyhvae.com/20180129_2125.png)\n\n### 删除节点\n\n格式如下：\n\n```javascript\n\t父节点.removeChild(子节点);\n```\n\n解释：**用父节点删除子节点**。必须要指定是删除哪个子节点。\n\n如果我想删除自己这个节点，可以这么做：\n\n```javascript\n\tnode1.parentNode.removeChild(node1);\n```\n\n### 复制节点（克隆节点）\n\n格式如下：\n\n```javascript\n\t要复制的节点.cloneNode();       //括号里不带参数和带参数false，效果是一样的。\n\n\t要复制的节点.cloneNode(true);\n```\n\n括号里带不带参数，效果是不同的。解释如下：\n\n- 不带参数/带参数false：只复制节点本身，不复制子节点。\n\n- 带参数true：既复制节点本身，也复制其所有的子节点。\n\n## 设置节点的属性\n\n我们可以获取节点的属性值、设置节点的属性值、删除节点的属性。\n\n我们就统一拿下面这个标签来举例：\n\n```html\n\t<img src=\"images/1.jpg\" class=\"image-box\" title=\"美女图片\" alt=\"地铁一瞥\" id=\"a1\">\n```\n\n下面分别介绍。\n\n### 1、获取节点的属性值\n\n**方式1**：\n\n```javascript\n\t元素节点.属性名;\n\t元素节点[属性名];\n```\n\n举例：（获取节点的属性值）\n\n```html\n<body>\n<img src=\"images/1.jpg\" class=\"image-box\" title=\"美女图片\" alt=\"地铁一瞥\" id=\"a1\">\n\n<script type=\"text/javascript\">\n    var myNode = document.getElementsByTagName(\"img\")[0];\n\n    console.log(myNode.src);\n    console.log(myNode.className);    //注意，是className，不是class\n    console.log(myNode.title);\n\n    console.log(\"------------\");\n\n    console.log(myNode[\"src\"]);\n    console.log(myNode[\"className\"]); //注意，是className，不是class\n    console.log(myNode[\"title\"]);\n</script>\n</body>\n```\n\n上方代码中的img标签，有各种属性，我们可以逐一获取，打印结果如下：\n\n![](http://img.smyhvae.com/20180127_1340.png)\n\n**方式2**：\n\n```javascript\n\t元素节点.getAttribute(\"属性名称\");\n```\n\n举例：\n\n```javascript\n    console.log(myNode.getAttribute(\"src\"));\n    console.log(myNode.getAttribute(\"class\"));   //注意是class，不是className\n    console.log(myNode.getAttribute(\"title\"));\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180127_1345.png)\n\n方式1和方式2的区别在于：前者是直接操作标签，后者是把标签作为DOM节点。推荐方式2。\n\n### 2、设置节点的属性值\n\n方式1举例：（设置节点的属性值）\n\n```javascript\n    myNode.src = \"images/2.jpg\"   //修改src的属性值\n    myNode.className = \"image2-box\";  //修改class的name\n```\n\n方式2：\n\n```javascript\n\t元素节点.setAttribute(\"属性名\", \"新的属性值\");\n```\n\n方式2举例：（设置节点的属性值）\n\n```javascript\n    myNode.setAttribute(\"src\",\"images/3.jpg\");\n    myNode.setAttribute(\"class\",\"image3-box\");\n    myNode.setAttribute(\"id\",\"你好\");\n```\n\n\n### 3、删除节点的属性\n\n格式：\n\n```javascript\n\t元素节点.removeAttribute(属性名);\n```\n\n举例：（删除节点的属性）\n\n```javascript\n    myNode.removeAttribute(\"class\");\n    myNode.removeAttribute(\"id\");\n```\n\n\n### 总结\n\n获取节点的属性值和设置节点的属性值，都有两种方式。\n\n**如果是节点的“原始属性”**（比如 普通标签的`class/className`属性、普通标签的`style`属性、普通标签的 title属性、img 标签的`src`属性、超链接的`href`属性等），**方式1和方式2是等价的**，可以混用。怎么理解混用呢？比如说：用 `div.title = '我是标题'`设置属性，用 `div.getAttribute('title')`获取属性，就是混用。\n\n但如果是节点的“非原始属性”，比如：\n\n```javascript\ndiv.aaa = 'qianguyihao';\n\ndiv.setAttribute('bbb', 'qianguyihao');\n\n```\n\n上面的这个“非原始属性”，在使用这两种方式时，是有区别的。区别如下：\n\n- 方式1 的`元素节点.属性`和`元素节点[属性]`：绑定的属性值不会出现在标签上。\n\n- 方式2 的`get/set/removeAttribut`：绑定的属性值会出现在标签上。\n\n- **这两种方式不能交换使用**，get值和set值必须使用同一种方法。\n\n举例：\n\n```html\n<body>\n<div id=\"box\" title=\"主体\" class=\"asdfasdfadsfd\">我爱你中国</div>\n<script>\n\n    var div = document.getElementById(\"box\");\n\n    //采用方式一进行set\n    div.aaaa = \"1111\";\n    console.log(div.aaaa);    //打印结果：1111。可以打印出来，但是不会出现在标签上\n\n    //采用方式二进行set\n    div.setAttribute(\"bbbb\",\"2222\");    //bbbb作为新增的属性，会出现在标签上\n\n    console.log(div.getAttribute(\"aaaa\"));   //打印结果：null。因为方式一的set，无法采用方式二进行get。\n    console.log(div.bbbb);                   //打印结果：undefined。因为方式二的set，无法采用方式一进行get。\n\n</script>\n</body>\n```\n\n\n## DOM对象的属性-补充\n\n### innerHTML和innerText的区别\n\n- value：标签的value属性。\n\n- **innerHTML**：双闭合标签里面的内容（包含标签）。\n\n- **innerText**：双闭合标签里面的内容（不包含标签）。（老版本的火狐用textContent）\n\n\n**获取内容举例：**\n\n如果我们想获取innerHTML和innerText里的内容，看看会如何：（innerHTML会获取到标签本身，而innerText则不会）\n\n![](http://img.smyhvae.com/20180127_1652.png)\n\n**修改内容举例：**（innerHTML会修改标签本身，而innerText则不会）\n\n![](http://img.smyhvae.com/20180127_1657.png)\n\n### nodeType属性\n\n这里讲一下nodeType属性。\n\n- **nodeType == 1  表示的是元素节点**（标签） 。记住：在这里，元素就是标签。\n\n- nodeType == 2  表示是属性节点。\n\n- nodeType == 3  是文本节点。\n\n### nodeType、nodeName、nodeValue\n\n我们那下面这个标签来举例：\n\n```html\n<div id=\"box\" value=\"111\">\n    生命壹号\n</div>\n```\n\n上面这个标签就包含了三种节点：\n\n- 元素节点（标签）\n\n- 属性节点\n\n- 文本节点\n\n获取这三个节点的方式如下：\n\n```javascript\n    var element = document.getElementById(\"box1\");  //获取元素节点（标签）\n    var attribute = element.getAttributeNode(\"id\"); //获取box1的属性节点\n    var txt = element.firstChild;                   //获取box1的文本节点\n\n    var value = element.getAttribute(\"id\");         //获取id的属性值\n\n    console.log(element);\n    console.log(\"--------------\");\n    console.log(attribute);\n    console.log(\"--------------\");\n    console.log(txt);\n    console.log(\"--------------\");\n    console.log(value);\n```\n\n打印结果如下：\n\n![](http://img.smyhvae.com/20180128_1935.png)\n\n既然这三个都是节点，如果我想获取它们的nodeType、nodeName、nodeValue，代码如下：\n\n```javascript\n    var element = document.getElementById(\"box1\");  //获取元素节点（标签）\n    var attribute = element.getAttributeNode(\"id\"); //获取box1的属性节点\n    var txt = element.firstChild;                   //获取box1的文本节点\n\n    //获取nodeType\n    console.log(element.nodeType);       //1\n    console.log(attribute.nodeType);     //2\n    console.log(txt.nodeType);           //3\n\n    console.log(\"--------------\");\n\n    //获取nodeName\n    console.log(element.nodeName);       //DIV\n    console.log(attribute.nodeName);     //id\n    console.log(txt.nodeName);           //#text\n\n    console.log(\"--------------\");\n\n    //获取nodeValue\n    console.log(element.nodeValue);     //null\n    console.log(attribute.nodeValue);   //box1\n    console.log(txt.nodeValue);         //生命壹号\n```\n\n打印结果如下：\n\n![](http://img.smyhvae.com/20180128_1939.png)\n\n## 文档的加载\n\n浏览器在加载一个页面时，是按照自上向下的顺序加载的，读取到一行就运行一行。如果将script标签写到页面的上边，在代码执行时，页面还没有加载，页面没有加载DOM对象也没有加载，会导致无法获取到DOM对象。\n\n**onload 事件**：\n\nonload 事件会在整个页面加载完成之后才触发。为 window 绑定一个onload事件，该事件对应的响应函数将会在页面加载完成之后执行，这样可以确保我们的代码执行时所有的DOM对象已经加载完毕了。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title></title>\n    <script type=\"text/javascript\">\n      // 【方式一：先加载，后执行】这段 js 代码是写在 <head> 标签里的，所以建议放在 window.onload 里面。\n      window.onload = function() {\n        // 获取id为btn的按钮\n        var btn = document.getElementById(\"btn\");\n        // 为按钮绑定点击事件\n        btn.onclick = function() {\n          alert(\"hello\");\n        };\n      };\n    </script>\n  </head>\n  <body>\n    <button id=\"btn\">点我一下</button>\n\n    <script type=\"text/javascript\">\n      // 【方式二：后加载，后执行】这段 js 代码是写在 <body> 标签里的，代码的位置是处在页面的下方。这么做，也可以确保：在页面加载完毕后，再执行 js 代码。\n\n      // 获取id为btn的按钮\n      var btn = document.getElementById(\"btn\");\n      // 为按钮绑定点击事件\n      btn.onclick = function() {\n        alert(\"hello\");\n      };\n    </script>\n  </body>\n</html>\n\n\n```\n\n上方代码中，方式一和方式二均可以确保：在页面加载完毕后，再执行 js 代码。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/37-通过style对象获取和设置行内样式.md",
    "content": "---\ntitle: 37-通过style对象获取和设置行内样式\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## style属性的获取和修改\n\n在DOM当中，如果想设置样式，有两种形式：\n\n- className（针对内嵌样式）\n\n- style（针对行内样式）\n\n这篇文章，我们就来讲一下style。\n\n需要注意的是：style是一个对象，只能获取**行内样式**，不能获取内嵌的样式和外链的样式。例如：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        div {\n            border: 6px solid red;\n        }\n    </style>\n</head>\n<body>\n\n    <div class=\"box1\" style=\"width: 200px;height: 100px;background-color: pink;\"></div>\n\n    <script>\n        var box1 = document.getElementsByTagName(\"div\")[0];\n\n        console.log(box1.style.backgroundColor);\n        console.log(box1.style.border);  //没有打印结果，因为这个属性不是行内样式\n        console.log(typeof box1.style);  //因为是对象，所以打印结果是Object\n        console.log(box1.style);         //打印结果是对象\n    </script>\n</body>\n</html>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180129_1407.png)\n\n上图显示，因为border属性不是行内样式，所以无法通过style对象获取。\n\n### 通过 js 读取元素的样式\n\n语法：（方式一）\n\n```javascript\n    元素.style.样式名\n```\n\n备注：我们通过style属性读取的样式都是**行内样式**。\n\n语法：（方式二）\n\n```javascript\n    元素.style[\"属性\"];  //格式\n\n    box.style[\"width\"];  //举例\n```\n\n方式二最大的优点是：可以给属性传递参数。\n\n### 通过 js 设置元素的样式\n\n语法：\n\n```javascript\n    元素.style.样式名 = 样式值;\n```\n\n举例：\n\n```\n    box1.style.width = \"300px\";\n    box1.style.backgroundColor = \"red\"; // 驼峰命名法\n\n```\n\n备注：我们通过style属性设置的样式都是**行内样式**，而行内样式有较高的优先级。但是如果在样式中的其他地方写了`!important`，则此时`!important`会有更高的优先级。\n\n### style属性的注意事项\n\nstyle属性需要注意以下几点：\n\n（1）样式少的时候使用。\n\n（2）style是对象。我们在上方已经打印出来，typeof的结果是Object。\n\n（3）值是字符串，没有设置值是“”。\n\n（4）命名规则，驼峰命名。\n\n（5）只能获取行内样式，和内嵌和外链无关。\n\n（6）box.style.cssText = “字符串形式的样式”。\n\n\n`cssText`这个属性，其实就是把行内样式里面的值当做字符串来对待。在上方代码的基础之上，举例：\n\n```html\n    <script>\n        var box1 = document.getElementsByTagName(\"div\")[0];\n\n        //通过cssText一次性设置行内样式\n        box1.style.cssText = \"width: 300px;height: 300px;background-color: green;\";\n\n        console.log(box1.style.cssText);   //这一行更加可以理解,style是对象\n\n    </script>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180129_1410.png)\n\n### style的常用属性\n\nstyle的常用属性包括：\n\n- backgroundColor\n\n- backgroundImage\n\n- color\n\n- width\n\n- height\n\n- border\n\n- opacity 设置透明度 (IE8以前是filter: alpha(opacity=xx))\n\n注意DOM对象style的属性和标签中style内的值不一样，因为在JS中，`-`不能作为标识符。比如：\n\n- DOM中：backgroundColor\n\n- CSS中：background-color\n\n## style属性的举例\n\n我们针对上面列举的几个style的样式，来举几个例子：\n\n- 举例1、改变div的大小和透明度\n\n- 举例2、当前输入的文本框高亮显示\n\n- 举例3、高级隔行变色、高亮显示\n\n下面来逐一实现。\n\n### 举例1：改变div的大小和透明度\n\n代码举例：\n\n```html\n<body>\n<div style=\"width: 100px;height: 100px;background-color: pink;\"></div>\n<script>\n\n    var div = document.getElementsByTagName(\"div\")[0];\n    div.onmouseover = function () {\n        div.style.width = \"200px\";\n        div.style.height = \"200px\";\n        div.style.backgroundColor = \"black\";\n        div.style.opacity = \"0.2\";   //设置背景色的透明度。单位是0.1\n        div.style.filter = \"alpha(opacity=20)\";   //上一行代码的兼容性写法。注意单位是百进制\n    }\n\n</script>\n\n</body>\n```\n\n### 举例2：当前输入的文本框高亮显示\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        input {\n            display: block;\n        }\n    </style>\n\n</head>\n\n<body>\n<ul>\n    <input type=\"text\"/>\n    <input type=\"text\"/>\n    <input type=\"text\"/>\n    <input type=\"text\"/>\n    <input type=\"text\"/>\n</ul>\n<script>\n    //需求：让所有的input标签获取焦点后高亮显示\n\n    //1.获取事件源\n    var inpArr = document.getElementsByTagName(\"input\");\n    //2.绑定事件\n    //3.书写事件驱动程序\n    for (var i = 0; i < inpArr.length; i++) {\n        //获取焦点后，所有的input标签被绑定onfocus事件\n        inpArr[i].onfocus = function () {\n            this.style.border = \"2px solid red\";\n            this.style.backgroundColor = \"#ccc\";\n        }\n        //绑定onblur事件，取消样式\n        inpArr[i].onblur = function () {\n            this.style.border = \"\";\n            this.style.backgroundColor = \"\";\n        }\n    }\n</script>\n</body>\n</html>\n```\n\n### 举例3：高级隔行变色、高亮显示\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n            text-align: center;\n        }\n\n        .wrap {\n            width: 500px;\n            margin: 100px auto 0;\n        }\n\n        table {\n            border-collapse: collapse;\n            border-spacing: 0;\n            border: 1px solid #c0c0c0;\n            width: 500px;\n        }\n\n        th,\n        td {\n            border: 1px solid #d0d0d0;\n            color: #404060;\n            padding: 10px;\n        }\n\n        th {\n            background-color: #09c;\n            font: bold 16px \"微软雅黑\";\n            color: #fff;\n        }\n\n        td {\n            font: 14px \"微软雅黑\";\n        }\n\n        tbody tr {\n            background-color: #f0f0f0;\n            cursor: pointer;\n        }\n\n        .current {\n            background-color: red !important;\n        }\n    </style>\n</head>\n<body>\n<div class=\"wrap\">\n    <table>\n        <thead>\n        <tr>\n            <th>序号</th>\n            <th>姓名</th>\n            <th>课程</th>\n            <th>成绩</th>\n        </tr>\n        </thead>\n        <tbody id=\"target\">\n        <tr>\n            <td>\n                1\n            </td>\n            <td>生命壹号</td>\n            <td>语文</td>\n            <td>100</td>\n\n        </tr>\n        <tr>\n            <td>\n                2\n            </td>\n            <td>生命贰号</td>\n            <td>日语</td>\n            <td>99</td>\n        </tr>\n        <tr>\n            <td>\n                3\n            </td>\n            <td>生命叁号</td>\n            <td>营销学</td>\n            <td>98</td>\n        </tr>\n        <tr>\n            <td>\n                4\n            </td>\n            <td>生命伍号</td>\n            <td>数学</td>\n            <td>90</td>\n        </tr>\n        <tr>\n            <td>\n                5\n            </td>\n            <td>许嵩</td>\n            <td>英语</td>\n            <td>96</td>\n        </tr>\n        <tr>\n            <td>\n                6\n            </td>\n            <td>vae</td>\n            <td>体育</td>\n            <td>90</td>\n        </tr>\n        </tbody>\n    </table>\n</div>\n\n<script>\n    //需求：让tr各行变色，鼠标放入tr中，高亮显示。\n\n    //1.隔行变色。\n    var tbody = document.getElementById(\"target\");\n    var trArr = tbody.children;\n    //循环判断并各行赋值属性（背景色）\n    for (var i = 0; i < trArr.length; i++) {\n        if (i % 2 !== 0) {\n            trArr[i].style.backgroundColor = \"#a3a3a3\";\n        } else {\n            trArr[i].style.backgroundColor = \"#ccc\";\n        }\n\n        //鼠标进入高亮显示\n        //难点：鼠标移开的时候要回复原始颜色。\n        //计数器（进入tr之后，立刻记录颜色，然后移开的时候使用记录好的颜色）\n        var myColor = \"\";\n        trArr[i].onmouseover = function () {\n            //赋值颜色之前，先记录颜色\n            myColor = this.style.backgroundColor;\n            this.style.backgroundColor = \"#fff\";\n        }\n        trArr[i].onmouseout = function () {\n            this.style.backgroundColor = myColor;\n        }\n    }\n\n\n</script>\n\n\n</body>\n</html>\n```\n\n实现的效果如下：\n\n![](http://img.smyhvae.com/20180129_1520.gif)\n\n代码解释：\n\n上方代码中，我们**用到了计数器myColor来记录每一行最原始的颜色**（赋值白色之前）。如果不用计数器，可能很多人以为代码是写的：（错误的代码）\n\n```html\n<script>\n    //需求：让tr各行变色，鼠标放入tr中，高亮显示。\n\n    //1.隔行变色。\n    var tbody = document.getElementById(\"target\");\n    var trArr = tbody.children;\n    //循环判断并各行赋值属性（背景色）\n    for (var i = 0; i < trArr.length; i++) {\n        if (i % 2 !== 0) {\n            trArr[i].style.backgroundColor = \"#a3a3a3\";\n        } else {\n            trArr[i].style.backgroundColor = \"#ccc\";\n        }\n\n        //鼠标进入高亮显示\n        //难点：鼠标移开的时候要回复原始颜色。\n        //计数器（进入tr之后，立刻记录颜色，然后移开的时候使用记录好的颜色）\n        trArr[i].onmouseover = function () {\n            this.style.backgroundColor = \"#fff\";\n        }\n        trArr[i].onmouseout = function () {\n            this.style.backgroundColor = \"#a3a3a3\";\n        }\n    }\n</script>\n\n```\n\n这种错误的代码，实现的效果却是：（未达到效果）\n\n![](http://img.smyhvae.com/20180129_1525.gif)\n\n## 通过 js 获取元素当前显示的样式\n\n我们在上面的内容中，通过`元素.style.className`的方式只能获取**行内样式**。但是，有些元素，也写了**内嵌样式或外链样式**。\n\n既然样式有这么多种，那么，如何获取元素当前显示的样式（包括行内样式、内嵌样式、外链样式）呢？我们接下来看一看。\n\n### 获取元素当前正在显示的样式\n\n（1）w3c的做法：\n\n```javascript\n    window.getComputedStyle(\"要获取样式的元素\", \"伪元素\");\n```\n\n两个参数都是必须要有的。参数二中，如果没有伪元素就用 null 代替（一般都传null）。\n\n（2）IE和opera的做法：\n\n```javascript\n    obj.currentStyle;\n```\n\n注意：\n\n- 如果当前元素没有设置该样式，则获取它的默认值。\n\n- 该方法会返回一个**对象**，对象中封装了当前元素对应的样式，可以通过`对象.样式名`来读取具体的某一个样式。\n\n- 通过currentStyle和getComputedStyle()读取到的样式都是只读的，不能修改，如果要修改必须通过style属性。\n\n综合上面两种写法，就有了一种兼容性的写法，同时将其封装。代码举例如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            background-color: pink;\n            /*border: 1px solid #000;*/\n            padding: 10px;\n        }\n    </style>\n</head>\n<body>\n\n<div style=\"width: 100px;height: 100px;\"></div>\n\n<script>\n\n    var div1 = document.getElementsByTagName(\"div\")[0];\n\n    console.log(getStyle(div1, \"width\"));\n    console.log(getStyle(div1, \"padding\"));\n    console.log(getStyle(div1, \"background-color\"));\n\n    /*\n     * 兼容方法，获取元素当前正在显示的样式。\n     * 参数：\n     *      obj     要获取样式的元素\n     *.     name    要获取的样式名\n    */\n    function getStyle(ele, attr) {\n        if (window.getComputedStyle) {\n            return window.getComputedStyle(ele, null)[attr];\n        }\n        return ele.currentStyle[attr];\n    }\n\n</script>\n</body>\n</html>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180204_1425.png)\n\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/38-offset相关属性和匀速动画（含轮播图的实现）.md",
    "content": "---\ntitle: 38-offset相关属性和匀速动画（含轮播图的实现）\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 前言\n\nJS动画的主要内容如下：\n\n1、三大家族和一个事件对象：\n\n- 三大家族：offset/scroll/client。也叫三大系列。\n\n- 事件对象/event（事件被触动时，鼠标和键盘的状态）（通过属性控制）。\n\n2、动画(闪现/匀速/缓动)\n\n3、冒泡/兼容/封装\n\n## offset 家族的组成\n\n我们知道，JS动画的三大家族包括：offset/scroll/client。今天来讲一下offset，以及与其相关的匀速动画。\n\n> offset的中文是：偏移，补偿，位移。\n\njs中有一套方便的**获取元素尺寸**的办法就是offset家族。offset家族包括：\n\n- offsetWidth\n\n- offsetHight\n\n- offsetLeft\n\n- offsetTop\n\n- offsetParent\n\n下面分别介绍。\n\n### 1、offsetWidth 和 offsetHight\n\n`offsetWidth` 和 `offsetHight`：获取元素的**宽高 + padding + border**，不包括margin。如下：\n\n- offsetWidth = width + padding + border\n\n- offsetHeight = Height + padding + border\n\n这两个属性，他们绑定在了所有的节点元素上。获取元素之后，只要调用这两个属性，我们就能够获取元素节点的宽和高。\n\n举例如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            padding: 10px;\n            border: 10px solid #000;\n            margin: 100px;\n            background-color: pink;\n        }\n    </style>\n</head>\n<body>\n\n<div class=\"box\"></div>\n<script>\n    var div1 = document.getElementsByTagName(\"div\")[0];\n\n    console.log(div1.offsetHeight);          //打印结果：140（100+20+20）\n    console.log(typeof div1.offsetHeight);   //打印结果：number\n\n</script>\n</body>\n</html>\n```\n\n### 2、offsetParent\n\n`offsetParent`：获取当前元素的**定位父元素**。\n\n- 如果当前元素的父元素，**有CSS定位**（position为absolute、relative、fixed），那么 `offsetParent` 获取的是**最近的**那个父元素。\n\n- 如果当前元素的父元素，**没有CSS定位**（position为absolute、relative、fixed），那么`offsetParent` 获取的是**body**。\n\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<div class=\"box1\" style=\"position: absolute;\">\n    <div class=\"box2\" style=\"position: fixed;\">\n        <div class=\"box3\"></div>\n    </div>\n</div>\n<script>\n\n    var box3 = document.getElementsByClassName(\"box3\")[0];\n\n    console.log(box3.offsetParent);\n</script>\n</body>\n</html>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180202_1725.png)\n\n### 3、offsetLeft 和 offsetTop\n\n`offsetLeft`：当前元素相对于其**定位父元素**的水平偏移量。\n\n`offsetTop`：当前元素相对于其**定位父元素**的垂直偏移量。\n\n备注：从父亲的 padding 开始算起，父亲的 border 不算在内。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box1 {\n            width: 300px;\n            height: 300px;\n            padding: 100px;\n            margin: 100px;\n            position: relative;\n            border: 100px solid #000;\n            background-color: pink;\n        }\n\n        .box2 {\n            width: 100px;\n            height: 100px;\n            background-color: red;\n            /*position: absolute;*/\n            /*left: 10px;*/\n            /*top: 10px;*/\n        }\n    </style>\n</head>\n<body>\n<div class=\"box1\">\n    <div class=\"box2\" style=\"left: 10px\"></div>\n</div>\n\n<script>\n\n    var box2 = document.getElementsByClassName(\"box2\")[0];\n\n    //offsetTop和offsetLeft\n    console.log(box2.offsetLeft);  //100\n    console.log(box2.style.left);  //10px\n\n\n</script>\n\n</body>\n</html>\n```\n\n在父盒子有定位的情况下，offsetLeft == style.left(去掉px之后)。注意，后者只识别行内样式。但区别不仅仅于此，下面会讲。\n\n### offsetLeft 和 style.left 区别\n\n（1）最大区别在于：\n\noffsetLeft 可以返回无定位父元素的偏移量。如果父元素中都没有定位，则body为准。\n\nstyle.left 只能获取行内样式，如果父元素中都没有设置定位，则返回\"\"（意思是，返回空字符串）;\n\n（2）offsetTop 返回的是数字，而 style.top 返回的是字符串，而且还带有单位：px。\n\n比如：\n\n```javascript\n\ndiv.offsetLeft = 100;\ndiv.style.left = \"100px\";\n\n```\n\n（3）offsetLeft 和 offsetTop **只读**，而 style.left 和 style.top 可读写（只读是获取值，可写是修改值）\n\n\n总结：我们一般的做法是：**用offsetLeft 和 offsetTop 获取值，用style.left 和 style.top 赋值**（比较方便）。理由如下：\n\n- style.left：只能获取行内式，获取的值可能为空，容易出现NaN。\n\n- offsetLeft：获取值特别方便，而且是现成的number，方便计算。它是只读的，不能赋值。\n\n\n## 动画的种类\n\n- 闪现（基本不用）\n\n- 匀速（本文重点）\n\n- 缓动（后续重点）\n\n简单举例如下：（每间隔500ms，向右移动盒子100px）\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            background-color: pink;\n            position: absolute;\n        }\n    </style>\n</head>\n<body>\n<button>动画</button>\n<div class=\"box\" style=\"left: 0px\"></div>\n\n<script>\n    var btn = document.getElementsByTagName(\"button\")[0];\n    var div = document.getElementsByTagName(\"div\")[0];\n\n    //1、闪动\n    //    btn.onclick = function () {\n    //        div.style.left = \"500px\";\n    //    }\n\n    //2、匀速运动\n    btn.onclick = function () {\n        //定时器，每隔一定的时间向右走一些\n        setInterval(function () {\n            console.log(parseInt(div.style.left));\n            //动画原理： 盒子未来的位置 = 盒子现在的位置 + 步长；\n            //方法1：用offsetLeft获取值，用style.left赋值。\n            div.style.left = div.offsetLeft + 100 + 'px';\n\n            // 方法2：必须一开始就在DOM节点上添加 style=\"left: 0px;\"属性，才能用方法2。否则， div.style.left 的值为 NaN\n            // div.style.left = parseInt(div.style.left)+100+\"px\";  //方法2：\n        }, 500);\n    };\n</script>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180202_1840.gif)\n\n## 匀速动画的封装：每间隔30ms，移动盒子10px【重要】\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box1 {\n            margin: 0;\n            padding: 5px;\n            height: 300px;\n            background-color: #ddd;\n            position: relative;\n        }\n\n        button {\n            margin: 5px;\n        }\n\n        .box2 {\n            width: 100px;\n            height: 100px;\n            background-color: red;\n            position: absolute;\n            left: 195px;\n            top: 40px;\n        }\n\n        .box3 {\n            width: 100px;\n            height: 100px;\n            background-color: yellow;\n            position: absolute;\n            left: 0;\n            top: 150px;\n        }\n    </style>\n</head>\n<body>\n<div class=\"box1\">\n    <button>运动到 left = 200px</button>\n    <button>运动到 left = 400px</button>\n    <div class=\"box2\"></div>\n    <div class=\"box3\"></div>\n</div>\n\n<script>\n    var btnArr = document.getElementsByTagName(\"button\");\n    var box2 = document.getElementsByClassName(\"box2\")[0];\n    var box3 = document.getElementsByClassName(\"box3\")[0];\n\n    //绑定事件\n    btnArr[0].onclick = function () {\n        //如果有一天我们要传递另外一个盒子，那么我们的方法就不好用了\n        //所以我们要增加第二个参数，被移动的盒子本身。\n        animate(box2, 200);\n        animate(box3, 200);\n    }\n\n    btnArr[1].onclick = function () {\n        animate(box2, 400);\n        animate(box3, 400);\n    }\n\n    //【重要】方法的封装：每间隔30ms，将盒子向右移动10px\n    function animate(ele, target) {\n        //要用定时器，先清除定时器\n        //一个盒子只能有一个定时器，这样的话，不会和其他盒子出现定时器冲突\n        //我们可以把定时器本身，当成为盒子的一个属性\n        clearInterval(ele.timer);\n        //我们要求盒子既能向前又能向后，那么我们的步长就得有正有负\n        //目标值如果大于当前值取正，目标值如果小于当前值取负\n        var speed = target > ele.offsetLeft ? 10 : -10;  //speed指的是步长\n        ele.timer = setInterval(function () {\n            //在执行之前就获取当前值和目标值之差\n            var val = target - ele.offsetLeft;\n            ele.style.left = ele.offsetLeft + speed + \"px\";\n            //移动的过程中，如果目标值和当前值之差如果小于步长，那么就不能在前进了\n            //因为步长有正有负，所有转换成绝对值来比较\n            if (Math.abs(val) < Math.abs(speed)) {\n                ele.style.left = target + \"px\";\n                clearInterval(ele.timer);\n            }\n        }, 30)\n    }\n</script>\n</body>\n</html>\n```\n\n实现的效果：\n\n![](http://img.smyhvae.com/20180202_1910.gif)\n\n上方代码中的方法封装，可以作为一个模板步骤，要记住。其实，这个封装的方法，写成下面这样，会更严谨，更容易理解：（将if语句进行了改进）\n\n```javascript\n    //【重要】方法的封装：每间隔30ms，将盒子向右移动10px\n    function animate(ele, target) {\n        //要用定时器，先清除定时器\n        //一个盒子只能有一个定时器，这样的话，不会和其他盒子出现定时器冲突\n        //我们可以把定时器本身，当成为盒子的一个属性\n        clearInterval(ele.timer);\n        //我们要求盒子既能向前又能向后，那么我们的步长就得有正有负\n        //目标值如果大于当前值取正，目标值如果小于当前值取负\n        var speed = target > ele.offsetLeft ? 10 : -10;  //speed指的是步长\n        ele.timer = setInterval(function () {\n            //在执行之前就获取当前值和目标值之差\n            var val = target - ele.offsetLeft;\n\n            //移动的过程中，如果目标值和当前值之差如果小于步长，那么就不能在前进了\n            //因为步长有正有负，所有转换成绝对值来比较\n            if (Math.abs(val) < Math.abs(speed)) {  //如果val小于步长，则直接到达目的地；否则，每次移动一个步长\n                ele.style.left = target + \"px\";\n                clearInterval(ele.timer);\n            } else {\n                ele.style.left = ele.offsetLeft + speed + \"px\";\n            }\n        }, 30)\n    }\n```\n\n## 代码举例：轮播图的实现\n\n完整版代码如下：（注释已经比较详细）\n\n```html\n<!doctype html>\n<html lang=\"en\">\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n    <title>无标题文档</title>\n    <style type=\"text/css\">\n        * {\n            padding: 0;\n            margin: 0;\n            list-style: none;\n            border: 0;\n        }\n\n        .all {\n            width: 500px;\n            height: 200px;\n            padding: 7px;\n            border: 1px solid #ccc;\n            margin: 100px auto;\n            position: relative;\n        }\n\n        .screen {\n            width: 500px;\n            height: 200px;\n            overflow: hidden;\n            position: relative;\n        }\n\n        .screen li {\n            width: 500px;\n            height: 200px;\n            overflow: hidden;\n            float: left;\n        }\n\n        .screen ul {\n            position: absolute;\n            left: 0;\n            top: 0px;\n            width: 3000px;\n        }\n\n        .all ol {\n            position: absolute;\n            right: 10px;\n            bottom: 10px;\n            line-height: 20px;\n            text-align: center;\n        }\n\n        .all ol li {\n            float: left;\n            width: 20px;\n            height: 20px;\n            background: #fff;\n            border: 1px solid #ccc;\n            margin-left: 10px;\n            cursor: pointer;\n        }\n\n        .all ol li.current {\n            background: yellow;\n        }\n\n        #arr {\n            display: none;\n        }\n\n        #arr span {\n            width: 40px;\n            height: 40px;\n            position: absolute;\n            left: 5px;\n            top: 50%;\n            margin-top: -20px;\n            background: #000;\n            cursor: pointer;\n            line-height: 40px;\n            text-align: center;\n            font-weight: bold;\n            font-family: '黑体';\n            font-size: 30px;\n            color: #fff;\n            opacity: 0.3;\n            border: 1px solid #fff;\n        }\n\n        #arr #right {\n            right: 5px;\n            left: auto;\n        }\n    </style>\n\n    <script>\n        window.onload = function () {\n\n            //需求：无缝滚动。\n            //思路：赋值第一张图片放到ul的最后，然后当图片切换到第五张的时候\n            //     直接切换第六章，再次从第一张切换到第二张的时候先瞬间切换到\n            //     第一张图片，然后滑动到第二张\n            //步骤：\n            //1.获取事件源及相关元素。（老三步）\n            //2.复制第一张图片所在的li,添加到ul的最后面。\n            //3.给ol中添加li，ul中的个数-1个，并点亮第一个按钮。\n            //4.鼠标放到ol的li上切换图片\n            //5.添加定时器\n            //6.左右切换图片（鼠标放上去隐藏，移开显示）\n\n\n            //1.获取事件源及相关元素。（老三步）\n            var all = document.getElementById(\"all\");\n            var screen = all.firstElementChild || all.firstChild;\n            var imgWidth = screen.offsetWidth;\n            var ul = screen.firstElementChild || screen.firstChild;\n            var ol = screen.children[1];\n            var div = screen.lastElementChild || screen.lastChild;\n            var spanArr = div.children;\n\n            //2.复制第一张图片所在的li,添加到ul的最后面。\n            var ulNewLi = ul.children[0].cloneNode(true);\n            ul.appendChild(ulNewLi);\n            //3.给ol中添加li，ul中的个数-1个，并点亮第一个按钮。\n            for (var i = 0; i < ul.children.length - 1; i++) {\n                var olNewLi = document.createElement(\"li\");\n                olNewLi.innerHTML = i + 1;\n                ol.appendChild(olNewLi)\n            }\n            var olLiArr = ol.children;\n            olLiArr[0].className = \"current\";\n\n            //4.鼠标放到ol的li上切换图片\n            for (var i = 0; i < olLiArr.length; i++) {\n                //自定义属性，把索引值绑定到元素的index属性上\n                olLiArr[i].index = i;\n                olLiArr[i].onmouseover = function () {\n                    //排他思想\n                    for (var j = 0; j < olLiArr.length; j++) {\n                        olLiArr[j].className = \"\";\n                    }\n                    this.className = \"current\";\n                    //鼠标放到小的方块上的时候索引值和key以及square同步\n//                    key = this.index;\n//                    square = this.index;\n                    key = square = this.index;\n                    //移动盒子\n                    animate(ul, -this.index * imgWidth);\n                }\n            }\n\n            //5.添加定时器\n            var timer = setInterval(autoPlay, 1000);\n\n            //固定向右切换图片\n            //两个定时器（一个记录图片，一个记录小方块）\n            var key = 0;\n            var square = 0;\n\n            function autoPlay() {\n                //通过控制key的自增来模拟图片的索引值，然后移动ul\n                key++;\n                if (key > olLiArr.length) {\n                    //图片已经滑动到最后一张，接下来，跳转到第一张，然后在滑动到第二张\n                    ul.style.left = 0;\n                    key = 1;\n                }\n                animate(ul, -key * imgWidth);\n                //通过控制square的自增来模拟小方块的索引值，然后点亮盒子\n                //排他思想做小方块\n                square++;\n                if (square > olLiArr.length - 1) {//索引值不能大于等于5，如果等于5，立刻变为0；\n                    square = 0;\n                }\n                for (var i = 0; i < olLiArr.length; i++) {\n                    olLiArr[i].className = \"\";\n                }\n                olLiArr[square].className = \"current\";\n            }\n\n            //鼠标放上去清除定时器，移开后在开启定时器\n            all.onmouseover = function () {\n                div.style.display = \"block\";\n                clearInterval(timer);\n            }\n            all.onmouseout = function () {\n                div.style.display = \"none\";\n                timer = setInterval(autoPlay, 1000);\n            }\n\n            //6.左右切换图片（鼠标放上去显示，移开隐藏）\n            spanArr[0].onclick = function () {\n                //通过控制key的自增来模拟图片的索引值，然后移动ul\n                key--;\n                if (key < 0) {\n                    //先移动到最后一张，然后key的值取之前一张的索引值，然后在向前移动\n                    ul.style.left = -imgWidth * (olLiArr.length) + \"px\";\n                    key = olLiArr.length - 1;\n                }\n                animate(ul, -key * imgWidth);\n                //通过控制square的自增来模拟小方块的索引值，然后点亮盒子\n                //排他思想做小方块\n                square--;\n                if (square < 0) {//索引值不能大于等于5，如果等于5，立刻变为0；\n                    square = olLiArr.length - 1;\n                }\n                for (var i = 0; i < olLiArr.length; i++) {\n                    olLiArr[i].className = \"\";\n                }\n                olLiArr[square].className = \"current\";\n            }\n            spanArr[1].onclick = function () {\n                //右侧的和定时器一模一样\n                autoPlay();\n            }\n\n\n            function animate(ele, target) {\n                clearInterval(ele.timer);\n                var speed = target > ele.offsetLeft ? 10 : -10;\n                ele.timer = setInterval(function () {\n                    var val = target - ele.offsetLeft;\n                    ele.style.left = ele.offsetLeft + speed + \"px\";\n\n                    if (Math.abs(val) < Math.abs(speed)) {\n                        ele.style.left = target + \"px\";\n                        clearInterval(ele.timer);\n                    }\n                }, 10)\n            }\n        }\n    </script>\n</head>\n\n<body>\n<div class=\"all\" id='all'>\n    <div class=\"screen\" id=\"screen\">\n        <ul id=\"ul\">\n            <li><img src=\"images/1.jpg\" width=\"500\" height=\"200\"/></li>\n            <li><img src=\"images/2.jpg\" width=\"500\" height=\"200\"/></li>\n            <li><img src=\"images/3.jpg\" width=\"500\" height=\"200\"/></li>\n            <li><img src=\"images/4.jpg\" width=\"500\" height=\"200\"/></li>\n            <li><img src=\"images/5.jpg\" width=\"500\" height=\"200\"/></li>\n        </ul>\n        <ol>\n\n        </ol>\n        <div id=\"arr\">\n            <span id=\"left\"><</span>\n            <span id=\"right\">></span>\n        </div>\n    </div>\n</div>\n</body>\n</html>\n\n\n```\n\n实现效果：\n\n![](http://img.smyhvae.com/20180202_2020.gif)\n\n温馨提示：动图太大，可以把<http://img.smyhvae.com/20180202_2020.gif>单独在浏览器中打开。\n\n工程文件：[2018-02-02-JS动画实现轮播图.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-02-JS%E5%8A%A8%E7%94%BB%E5%AE%9E%E7%8E%B0%E8%BD%AE%E6%92%AD%E5%9B%BE.rar)\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n\n"
  },
  {
    "path": "04-JavaScript基础/39-scroll相关属性和缓动动画.md",
    "content": "---\ntitle: 39-scroll相关属性和缓动动画\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## scroll 相关属性\n\n### window.onscroll() 方法\n\n当我们用鼠标滚轮，滚动网页的时候，会触发 window.onscroll() 方法。效果如下：（注意看控制台的打印结果）\n\n![](http://img.smyhvae.com/20180202_2258.gif)\n\n如果你需要做滚动监听，可以使用这个方法。\n\n我们来看看和 scroll 相关的有哪些属性。\n\n### 1、ScrollWidth 和 scrollHeight\n\n`ScrollWidth` 和 `scrollHeight`：获取元素**整个滚动区域**的宽、高。包括 width 和 padding，不包括 border和margin。\n\n\n**注意**：\n\n`scrollHeight` 的特点是：如果内容超出了盒子，`scrollHeight`为内容的高（包括超出的内容）；如果不超出，`scrollHeight`为盒子本身的高度。`ScrollWidth`同理。\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            padding: 10px;\n            margin: 3px;\n            border: 8px solid red;\n        }\n    </style>\n</head>\n<body>\n\n<div class=\"box\">\n    静，能寒窗苦守；动，能点石成金。\n    静，能寒窗苦守；动，能点石成金。\n    静，能寒窗苦守；动，能点石成金。\n    静，能寒窗苦守；动，能点石成金。\n    静，能寒窗苦守；动，能点石成金。\n    静，能寒窗苦守；动，能点石成金。\n</div>\n<script>\n\n    var div = document.getElementsByTagName(\"div\")[0];\n\n    // `scrollHeight` 的特点是：如果内容超出了盒子，`scrollHeight`为内容的高（包括超出的内容）；如果不超出，`scrollHeight`为盒子本身的高度。\n    //IE8以下（不包括IE8），为盒子本身内容的高度。\n    console.log(div.scrollWidth);\n    console.log(div.scrollHeight);\n\n</script>\n</body>\n</html>\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180203_1235.png)\n\n### 2、scrollTop 和 scrollLeft\n\n- `scrollLeft`：获取水平滚动条滚动的距离。\n\n- `scrollTop`：获取垂直滚动条滚动的距离。\n\n**实战经验**：\n\n当某个元素满足`scrollHeight - scrollTop == clientHeight`时，说明垂直滚动条滚动到底了。\n\n当某个元素满足`scrollWidth - scrollLeft == clientWidth`时，说明水平滚动条滚动到底了。\n\n这个实战经验非常有用，可以用来判断用户是否已经将内容滑动到底了。比如说，有些场景下，希望用户能够看完“长长的活动规则”，才允许触发接下来的表单操作。\n\n### scrollTop 的兼容性\n\n如果要获取页面滚动的距离，scrollTop 这个属性的写法要注意兼容性，如下。\n\n（1）如果文档没有 DTD 声明，写法为：\n\n```javascript\n    document.body.scrollTop\n```\n\n在没有 DTD 声明的情况下，要求是这种写法，chrome浏览器才能认出来。\n\n（2）如果文档有 DTD 声明，写法为：\n\n```javascript\n   document.documentElement.scrollTop\n```\n\n在有 DTD 声明的情况下，要求是这种写法，IE6、7、8才能认出来。\n\n综合上面这两个，就诞生了一种兼容性的写法：\n\n```javascript\n    document.body.scrollTop || document.documentElement.scrollTop //方式一\n\n    document.body.scrollTop + document.documentElement.scrollTop  //方式二\n```\n\n另外还有一种兼容性的写法：`window.pageYOffset` 和 `window.pageXOffset`。这种写法无视DTD的声明。这种写法支持的浏览器版本是：火狐/谷歌/ie9+。\n\n综合上面的几种写法，为了兼容，不管有没有DTD，**最终版的兼容性写法：**\n\n```javascript\n    window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;\n```\n\n### 判断是否已经 DTD 声明\n\n方法如下：\n\n```javascript\n    document.compatMode === \"CSS1Compat\"   // 已声明\n    document.compatMode === \"BackCompat\"   // 未声明\n```\n\n### 将 scrollTop 和 scrollLeft 进行封装\n\n这里，我们将 scrollTop 和 scrollLeft 封装为一个方法，名叫scroll()，返回值为 一个对象。以后就直接调用`scroll().top` 和 `scroll().left`就好。\n\n代码实现：\n\n```html\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        body {\n            height: 6000px;\n            width: 5000px;\n        }\n    </style>\n</head>\n<body>\n\n<script>\n\n    //需求：封装一个兼容的scroll().返回的是对象，用scroll().top获取scrollTop，用scroll().left获取scrollLeft\n\n    window.onscroll = function () {\n//        var myScroll = scroll();\n//        myScroll.top;\n        console.log(scroll().top);\n        console.log(scroll().left);\n    }\n\n    //函数封装（简单封装，实际工作使用）\n    function scroll() {\n        return { //此函数的返回值是对象\n            left: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop,\n            right: window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft\n        }\n    }\n</script>\n</body>\n</html>\n```\n\n上方代码中，函数定义的那部分就是要封装的代码。\n\n另外还有一种比较麻烦的封装方式：（仅供参考）\n\n```javascript\nfunction scroll() {  // 开始封装自己的scrollTop\n    if(window.pageYOffset !== undefined) {  // ie9+ 高版本浏览器\n        // 因为 window.pageYOffset 默认的是  0  所以这里需要判断\n        return {\n            left: window.pageXOffset,\n            top: window.pageYOffset\n        }\n    }\n    else if(document.compatMode === \"CSS1Compat\") {    // 标准浏览器   来判断有没有声明DTD\n        return {\n            left: document.documentElement.scrollLeft,\n            top: document.documentElement.scrollTop\n        }\n    }\n    return {   // 未声明 DTD\n        left: document.body.scrollLeft,\n        top: document.body.scrollTop\n    }\n}\n```\n\n## 获取 html 文档的方法\n\n获取title、body、head、html标签的方法如下：\n\n- `document.title` 文档标题；\n\n- `document.head`  文档的头标签\n\n- `document.body`  文档的body标签；\n\n- `document.documentElement`  （这个很重要）。\n\n`document.documentElement`表示文档的html标签。也就是说，基本结构当中的 `html 标签`而是通过`document.documentElement`访问的，并不是通过 document.html 去访问的。\n\n## scrollTop 举例：固定导航栏\n\n完整版代码实现：\n\n（1）index.html：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0\n        }\n\n        img {\n            vertical-align: top;\n        }\n\n        .main {\n            margin: 0 auto;\n            width: 1000px;\n            margin-top: 10px;\n\n        }\n\n        #Q-nav1 {\n            overflow: hidden;\n        }\n\n        .fixed {\n            position: fixed;\n            top: 0;\n            left: 0;\n        }\n    </style>\n\n    <!--引入工具js-->\n    <script src=\"tools.js\"></script>\n    <script>\n        window.onload = function () {\n            //需求1：当我们滚动界面的时候，被卷曲的头部如果超过第二个盒子距离顶部的位置，那么直接给第二个盒子加类名.fixed\n            //需求2：当我们滚动界面的时候，被卷曲的头部如果小于第二个盒子距离顶部的位置，那么直接给第二个盒子取消类名.fixed\n\n            //1.老三步。\n            var topDiv = document.getElementById(\"top\");\n            var height = topDiv.offsetHeight;\n            var middle = document.getElementById(\"Q-nav1\");\n            var main = document.getElementById(\"main\");\n\n            window.onscroll = function () {\n                //2.判断 ，被卷曲的头部的大小\n                if (scroll().top > height) {\n                    //3.满足条件添加类，否则删除类\n                    middle.className += \" fixed\";\n                    //第二个盒子也要占位置，为了避免重叠，我们给第三个盒子一个上padding的空间，把这个空间留给第二个盒子\n                    main.style.paddingTop = middle.offsetHeight + \"px\";\n                } else {\n                    middle.className = \"\";\n                    //清零\n                    main.style.paddingTop = 0;\n                }\n            }\n\n        }\n    </script>\n</head>\n<body>\n<div class=\"top\" id=\"top\">\n    <img src=\"images/top.png\" alt=\"\"/>\n</div>\n<div id=\"Q-nav1\">\n    <img src=\"images/nav.png\" alt=\"\"/>\n</div>\n<div class=\"main\" id=\"main\">\n    <img src=\"images/main.png\" alt=\"\"/>\n</div>\n</body>\n</html>\n\n```\n\n上方代码中，有一个技巧：\n\n```javascript\nmain.style.paddingTop = middle.offsetHeight + \"px\";\n```\n\n仔细看注释就好。\n\n（2）tools.js：\n\n```javascript\n/**\n * Created by smyhvae on 2018/02/03.\n */\nfunction scroll() {  // 开始封装自己的scrollTop\n    if (window.pageYOffset !== undefined) {  // ie9+ 高版本浏览器\n        // 因为 window.pageYOffset 默认的是  0  所以这里需要判断\n        return {\n            left: window.pageXOffset,\n            top: window.pageYOffset\n        }\n    }\n    else if (document.compatMode === \"CSS1Compat\") {    // 标准浏览器   来判断有没有声明DTD\n        return {\n            left: document.documentElement.scrollLeft,\n            top: document.documentElement.scrollTop\n        }\n    }\n    return {   // 未声明 DTD\n        left: document.body.scrollLeft,\n        top: document.body.scrollTop\n    }\n}\n```\n\n\n实现效果：\n\n![](http://img.smyhvae.com/20180203_1619.gif)\n\n\n~工程文件~：\n\n- 2018-02-03-scrollTop固定导航栏.rar\n\n- 下载链接暂无。\n\n\n## 缓动动画\n\n### 三个函数\n\n缓慢动画里，我们要用到三个函数，这里先列出来：\n\n- Math.ceil()         向上取整\n\n- Math.floor()        向下取整\n\n- Math.round();   四舍五入\n\n### 缓动动画的原理\n\n缓动动画的原理就是：在移动的过程中，步长越来越小。\n\n设置步长为：**目标位置和盒子当前位置的十分之一**。用公式表达，即：\n\n```\n    盒子位置 = 盒子本身位置 + (目标位置 - 盒子本身位置)/ 10；\n```\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            background-color: pink;\n            position: absolute;\n        }\n    </style>\n</head>\n<body>\n<button>运动到left = 400px</button>\n<div></div>\n\n<script>\n\n    var btn = document.getElementsByTagName(\"button\")[0];\n    var div = document.getElementsByTagName(\"div\")[0];\n\n    btn.onclick = function () {\n        setInterval(function () {\n            //动画原理：盒子未来的位置 = 盒子当前的位置+步长\n            div.style.left = div.offsetLeft + (400 - div.offsetLeft) / 10 + \"px\";\n        }, 30);\n    }\n\n</script>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180202_2046.gif)\n\n### 缓慢动画的封装（解决四舍五入的问题）\n\n我们发现一个问题，上图中的盒子最终并没有到达400px的位置，而是只到了396.04px就停住了：\n\n![](http://img.smyhvae.com/20180202_2140.png)\n\n原因是：JS在取整的运算时，进行了四舍五入。\n\n我们把打印396.04px这个left值打印出来看看：\n\n![](http://img.smyhvae.com/20180202_2150.png)\n\n我么发现，通过`div.style.left`获取的值是精确的，通过`div.offsetLeft`获取的left值会进行四舍五入。\n\n此时，我们就要用到取整的函数了。\n\n通过对缓动动画进行封装，完整版的代码实现如下：\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            background-color: pink;\n            position: absolute;\n            left: 0;\n        }\n    </style>\n</head>\n<body>\n<button>运动到200</button>\n<button>运动到400</button>\n<div></div>\n\n<script>\n\n    var btn = document.getElementsByTagName(\"button\");\n    var div = document.getElementsByTagName(\"div\")[0];\n\n    btn[0].onclick = function () {\n        animate(div, 200);\n    }\n\n    btn[1].onclick = function () {\n        animate(div, 400);\n    }\n\n    //缓动动画封装\n    function animate(ele, target) {\n        //要用定时器，先清定时器\n        //一个萝卜一个坑儿，一个元素对应一个定时器\n        clearInterval(ele.timer);\n        //定义定时器\n        ele.timer = setInterval(function () {\n            //获取步长\n            //步长应该是越来越小的，缓动的算法。\n            var step = (target - ele.offsetLeft) / 10;\n            //对步长进行二次加工(大于0向上取整,小于0向下取整)\n            //达到的效果是：最后10像素的时候都是1像素1像素的向目标位置移动，就能够到达指定位置。\n            step = step > 0 ? Math.ceil(step) : Math.floor(step);\n            //动画原理： 目标位置 = 当前位置 + 步长\n            ele.style.left = ele.offsetLeft + step + \"px\";\n            console.log(step);\n            //检测缓动动画有没有停止\n            console.log(\"smyhvae\");\n            if (Math.abs(target - ele.offsetLeft) <= Math.abs(step)) {\n                //处理小数赋值\n                ele.style.left = target + \"px\";\n                clearInterval(ele.timer);\n            }\n        }, 30);\n    }\n\n</script>\n</body>\n</html>\n```\n\n实现效果：\n\n![](http://img.smyhvae.com/20180202_2239.gif)\n\n\n##  window.scrollTo()方法举例：返回到顶部小火箭\n\n（1）index.html：\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        img {\n            position: fixed;\n            bottom: 100px;\n            right: 50px;\n            cursor: pointer;\n            display: none;\n            border: 1px solid #000;\n        }\n\n        div {\n            width: 1210px;\n            margin: 100px auto;\n            text-align: center;\n            font: 500 26px/35px \"simsun\";\n            color: red;\n        }\n    </style>\n    <script src=\"tools.js\"></script>\n    <script>\n        window.onload = function () {\n            //需求：被卷去的头部超过100显示小火箭，然后点击小火箭屏幕缓慢移动到最顶端。\n            //难点：我们以前是移动盒子，现在是移动屏幕，我们没有学过如何移动屏幕。\n            //      技术点：window.scrollTo(x,y);浏览器显示区域跳转到指定的坐标\n            //步骤：\n            //1.老三步\n            var img = document.getElementsByTagName(\"img\")[0];\n            window.onscroll = function () {\n                //被卷去的距离大于200显示小火箭，否则隐藏\n                //2.显示隐藏小火箭\n                if (scroll().top > 1000) {\n                    img.style.display = \"block\";\n                } else {\n                    img.style.display = \"none\";\n                }\n                //每次移动滚动条的时候都给leader赋值，模拟leader获取距离顶部的距离\n                leader = scroll().top;\n            }\n            //3.缓动跳转到页面最顶端（利用我们的缓动动画）\n            var timer = null;\n            var target = 0; //目标位置\n            var leader = 0; //显示区域自身的位置\n            img.onclick = function () {\n                //技术点：window.scrollTo(0,0);\n                //要用定时器，先清定时器\n                clearInterval(timer);\n                timer = setInterval(function () {\n                    //获取步长\n                    var step = (target - leader) / 10;\n                    //二次处理步长\n                    step = step > 0 ? Math.ceil(step) : Math.floor(step);\n                    leader = leader + step; //往上移动的过程中，step是负数。当前位置减去步长，就等于下一步的位置。\n                    //显示区域移动\n                    window.scrollTo(0, leader);\n                    //清除定时器\n                    if (leader === 0) {\n                        clearInterval(timer);\n                    }\n                }, 25);\n            }\n        }\n    </script>\n</head>\n<body>\n<img src=\"images/Top.jpg\"/>\n<div>\n    我是最顶端.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n    生命壹号，永不止步.....<br>\n\n</div>\n</body>\n</html>\n```\n\n（2）tools.js:\n\n```javascript\n/**\n * Created by smyhvae on 2015/12/8.\n */\n\n//函数：获取scrollTop和scrollLeft的值\nfunction scroll() {  // 开始封装自己的scrollTop\n    if (window.pageYOffset != null) {  // ie9+ 高版本浏览器\n        // 因为 window.pageYOffset 默认的是  0  所以这里需要判断\n        return {\n            left: window.pageXOffset,\n            top: window.pageYOffset\n        }\n    }\n    else if (document.compatMode === \"CSS1Compat\") {    // 标准浏览器   来判断有没有声明DTD\n        return {\n            left: document.documentElement.scrollLeft,\n            top: document.documentElement.scrollTop\n        }\n    }\n    return {   // 未声明 DTD\n        left: document.body.scrollLeft,\n        top: document.body.scrollTop\n    }\n}\n\n```\n\n实现效果：\n\n![](http://img.smyhvae.com/20180203_1710.gif)\n\n小火箭的图片资源：\n\n![](http://img.smyhvae.com/20180203-Top.jpg)\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/40-client（可视区）相关属性.md",
    "content": "---\ntitle: 40-client（可视区）相关属性\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## client 家族的组成\n\n### clientWidth 和 clientHeight\n\n元素调用时：\n\n- clientWidth：获取元素的可见宽度（width + padding）。\n\n- clientHeight：获取元素的可见高度（height + padding）。\n\n\nbody/html 调用时：\n\n- clientWidth：获取网页可视区域宽度。\n\n- clientHeight：获取网页可视区域高度。\n\n**声明**：\n\n- `clientWidth` 和 `clientHeight` 属性是只读的，不可修改。\n\n- `clientWidth` 和 `clientHeight` 的值都是不带 px 的，返回的都是一个数字，可以直接进行计算。\n\n\n### clientX 和 clientY\n\nevent调用：\n\n- clientX：鼠标距离可视区域左侧距离。\n\n-  clientY：鼠标距离可视区域上侧距离。\n\n\n\n### clientTop 和 clientLeft\n\n- clientTop：盒子的上border。\n\n- clientLeft：盒子的左border。\n\n\n## 三大家族 offset/scroll/client 的区别\n\n### 区别1：宽高\n\n- offsetWidth  = width  + padding + border\n- offsetHeight = height + padding + border\n\n- scrollWidth   = 内容宽度（不包含border）\n- scrollHeight  = 内容高度（不包含border）\n\n- clientWidth  = width  + padding\n- clientHeight = height + padding\n\n\n### 区别2：上左\n\n\noffsetTop/offsetLeft：\n\n- 调用者：任意元素。(盒子为主)\n- 作用：距离父系盒子中带有定位的距离。\n\n\nscrollTop/scrollLeft：\n\n- 调用者：document.body.scrollTop（window调用）(盒子也可以调用，但必须有滚动条)\n- 作用：浏览器无法显示的部分（被卷去的部分）。\n\n\nclientY/clientX：\n\n- 调用者：event\n- 作用：鼠标距离浏览器可视区域的距离（左、上）。\n\n\n\n\n## 函数封装：获取浏览器的宽高（可视区域）\n\n函数封装如下：\n\n```javascript\n//函数封装：获取屏幕可视区域的宽高\nfunction client() {\n    if (window.innerHeight !== undefined) {\n        //ie9及其以上的版本的写法\n        return {\n            \"width\": window.innerWidth,\n            \"height\": window.innerHeight\n        }\n    } else if (document.compatMode === \"CSS1Compat\") {\n        //标准模式的写法（有DTD时）\n        return {\n            \"width\": document.documentElement.clientWidth,\n            \"height\": document.documentElement.clientHeight\n        }\n    } else {\n        //没有DTD时的写法\n        return {\n            \"width\": document.body.clientWidth,\n            \"height\": document.body.clientHeight\n        }\n    }\n}\n\n```\n\n\n**案例：根据浏览器的可视宽度，给定不同的背景的色。**\n\n> PS：这个可以用来做响应式。\n\n代码如下：（需要用到上面的封装好的方法）\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n\n<script src=\"tools.js\"></script>\n<script>\n    //需求：浏览器每次更改大小，判断是否符合某一标准然后给背景上色。\n    //  // >960红色，大于640小于960蓝色，小于640绿色。\n\n    window.onresize = fn;  //页面大小发生变化时，执行该函数。\n    //页面加载的时候直接执行一次函数，确定浏览器可视区域的宽，给背景上色\n    fn();\n\n    //封装成函数，然后指定的时候去调用和绑定函数名\n    function fn() {\n        if (client().width > 960) {\n            document.body.style.backgroundColor = \"red\";\n        } else if (client().width > 640) {\n            document.body.style.backgroundColor = \"blue\";\n        } else {\n            document.body.style.backgroundColor = \"green\";\n        }\n    }\n</script>\n</body>\n</html>\n```\n\n\n上当代码中，`window.onresize`事件指的是：在窗口或框架被调整大小时发生。各个事件的解释如下：\n\n- window.onscroll        屏幕滑动\n\n- window.onresize       浏览器大小变化\n\n- window.onload\t        页面加载完毕\n\n- div.onmousemove    鼠标在盒子上移动（注意：不是盒子移动）\n\n\n\n## 获取显示器的分辨率\n\n比如，我的电脑的显示器分辨率是：1920*1080。\n\n\n获取显示器的分辨率：\n\n```javascript\n    window.onresize = function () {\n        document.title = window.screen.width + \"    \" + window.screen.height;\n    }\n```\n\n显示效果：\n\n\n![](http://img.smyhvae.com/20180203_2155.png)\n\n\n上图中，不管我如何改变浏览器的窗口大小，title栏显示的值永远都是我的显示器分辨率：1920*1080。\n\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/41-事件的绑定和事件对象Event.md",
    "content": "---\ntitle: 41-事件的绑定和事件对象Event\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 绑定事件的两种方式/DOM事件的级别\n\n我们在之前的一篇文章《04-JavaScript/22-DOM简介和DOM操作》中已经讲过事件的概念。这里讲一下绑定（注册）事件的两种方式，我们以onclick事件为例。\n\n### DOM0的写法：onclick\n\n\n```javascript\n    element.onclick = function () {\n\n    }\n```\n\n举例：\n\n```html\n<body>\n<button>点我</button>\n<script>\n    var btn = document.getElementsByTagName(\"button\")[0];\n\n    //这种事件绑定的方式，如果绑定多个，则后面的会覆盖掉前面的\n    btn.onclick = function () {\n        console.log(\"事件1\");\n    }\n\n    btn.onclick = function () {\n        console.log(\"事件2\");\n    }\n\n</script>\n</body>\n\n```\n\n点击按钮后，上方代码的打印结果：\n\n```html\n事件2\n```\n\n我们可以看到，`DOM对象.事件 =  函数`的这种绑定事件的方式：一个元素的一个事件只能绑定一个响应函数。如果绑定了多个响应函数，则后者会覆盖前者。\n\n### DOM2的写法：addEventListener（高版本浏览器）\n\n```javascript\n    element.addEventListener('click', function () {\n\n    }, false);\n```\n\n\n参数解释：\n\n- 参数1：事件名的字符串(注意，没有on)\n\n- 参数2：回调函数：当事件触发时，该函数会被执行\n\n- 参数3：**true表示捕获阶段触发，false表示冒泡阶段触发（默认）**。如果不写，则默认为false。【重要】\n\n举例：\n\n```html\n<body>\n<button>按钮</button>\n<script>\n    var btn = document.getElementsByTagName(\"button\")[0];\n\n    // addEventListener: 事件监听器。 原事件被执行的时候，后面绑定的事件照样被执行\n    // 这种写法不存在响应函数被覆盖的情况。（更适合团队开发）\n    btn.addEventListener(\"click\", fn1);\n    btn.addEventListener(\"click\", fn2);\n\n    function fn1() {\n        console.log(\"事件1\");\n    }\n\n    function fn2() {\n        console.log(\"事件2\");\n    }\n\n</script>\n</body>\n```\n\n点击按钮后，上方代码的打印结果：\n\n\n```html\n    事件1\n    事件2\n```\n\n我们可以看到，`addEventListener()`这种绑定事件的方式：\n\n- 一个元素的一个事件，可以绑定多个响应函数。不存在响应函数被覆盖的情况。**执行顺序是**：事件被触发时，响应函数会按照函数的绑定顺序执行。\n\n- addEventListener()中的this，是绑定事件的对象。\n\n- `addEventListener()`不支持 IE8 及以下的浏览器。在IE8中可以使用`attachEvent`来绑定事件（详见下一小段）。\n\n### DOM2的写法：attachEvent（IE8及以下版本浏览器）\n\n```javascript\n    element.attachEvent('onclick', function () {\n\n    });\n\n```\n\n参数解释：\n\n- 参数1：事件名的字符串(注意，有on)\n\n- 参数2：回调函数：当事件触发时，该函数会被执行\n\n举例：\n\n```html\n    <body>\n        <button>按钮</button>\n        <script>\n            var btn = document.getElementsByTagName('button')[0];\n\n            btn.attachEvent('onclick', function() {\n                console.log('事件1');\n            });\n\n            btn.attachEvent('onclick', function() {\n                console.log('事件2');\n            });\n        </script>\n    </body>\n```\n\n在低版本的IE浏览器上，点击按钮后，上方代码的打印结果：\n\n\n```html\n    事件2\n    事件1\n```\n\n我们可以看到，`attachEvent()`这种绑定事件的方式：\n\n- 一个元素的一个事件，可以绑定多个响应函数。不存在响应函数被覆盖的情况。**注意**：执行顺序是，后绑定的先执行。\n\n- attachEvent()中的this，是window\n\n### 兼容性写法\n\n上面的内容里，需要强调的是：\n\n- `addEventListener()`中的this，是绑定事件的对象。\n\n- `attachEvent()`中的this，是window。\n\n既然这两个写法的`this`不同，那么，有没有一种兼容性的写法可以确保这两种绑定方式的this是相同的呢？我们可以封装一下。代码如下：\n\n```html\n    <body>\n        <button>按钮</button>\n        <script>\n            var btn = document.getElementsByTagName('button')[0];\n\n            myBind(btn , \"click\" , function(){\n                alert(this);\n            });\n\n\n\n            //定义一个函数，用来为指定元素绑定响应函数\n            /*\n             * addEventListener()中的this，是绑定事件的对象\n             * attachEvent()中的this，是window\n             *  需要统一两个方法this\n             */\n            /*\n             * 参数：\n             *  element 要绑定事件的对象\n             *  eventStr 事件的字符串(不要on)\n             *  callback 回调函数\n             */\n            function myBind(element , eventStr , callback){\n                if(element.addEventListener){\n                    //大部分浏览器兼容的方式\n                    element.addEventListener(eventStr , callback , false);\n                }else{\n                    /*\n                     * this是谁，由调用方式决定\n                     * callback.call(element)\n                     */\n                    //IE8及以下\n                    element.attachEvent(\"on\"+eventStr , function(){\n                        //在匿名函数 function 中调用回调函数callback\n                        callback.call(element);\n                    });\n                }\n            }\n\n        </script>\n    </body>\n```\n\n\n## 事件对象\n\n当事件的响应函数被触发时，会产生一个事件对象`event`。浏览器每次都会将这个事件`event`作为实参传进之前的响应函数。\n\n这个对象中包含了与当前事件相关的一切信息。比如鼠标的坐标、键盘的哪个按键被按下、鼠标滚轮滚动的方向等。\n\n### 获取 event 对象（兼容性问题）\n\n所有浏览器都支持event对象，但支持的方式不同。如下。\n\n（1）普通浏览器的写法是 `event`。比如：\n\n![](http://img.smyhvae.com/20180203_1735.png)\n\n（2）ie 678 的写法是 `window.event`。此时，事件对象 event 是作为window对象的属性保存的。\n\n于是，我们可以采取一种兼容性的写法。如下：\n\n```javascript\n    event = event || window.event; // 兼容性写法\n```\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<script>\n    //点击页面的任何部分\n    document.onclick = function (event) {\n        event = event || window.event; ////兼容性写法\n\n        console.log(event);\n        console.log(event.timeStamp);\n        console.log(event.bubbles);\n        console.log(event.button);\n        console.log(event.pageX);\n        console.log(event.pageY);\n        console.log(event.screenX);\n        console.log(event.screenY);\n        console.log(event.target);\n        console.log(event.type);\n        console.log(event.clientX);\n        console.log(event.clientY);\n    }\n</script>\n</body>\n</html>\n```\n\n### event 属性\n\nevent 有很多属性，比如：\n\n![](http://img.smyhvae.com/20180203_1739.png)\n\n由于pageX 和 pageY的兼容性不好，我们可以这样做：\n\n- 鼠标在页面的位置 = 滚动条滚动的距离 + 可视区域的坐标。\n\n## Event举例\n\n### 举例1：使 div 跟随鼠标移动\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title></title>\n    <style type=\"text/css\">\n      #box1 {\n        width: 100px;\n        height: 100px;\n        background-color: red;\n        /*\n        * 开启box1的绝对定位\n        */\n        position: absolute;\n      }\n    </style>\n\n    <script type=\"text/javascript\">\n      window.onload = function() {\n        /*\n         * 使div可以跟随鼠标移动\n         */\n\n        //获取box1\n        var box1 = document.getElementById(\"box1\");\n\n        //给整个页面绑定：鼠标移动事件\n        document.onmousemove = function(event) {\n          //兼容的方式获取event对象\n          event = event || window.event;\n\n          // 鼠标在页面的位置 = 滚动条滚动的距离 + 可视区域的坐标。\n          var pagex = event.pageX || scroll().left + event.clientX;\n          var pagey = event.pageY || scroll().top + event.clientY;\n\n          //   设置div的偏移量（相对于整个页面）\n          // 注意，如果想通过 style.left 来设置属性，一定要给 box1开启绝对定位。\n          box1.style.left = pagex + \"px\";\n          box1.style.top = pagey + \"px\";\n        };\n      };\n\n      // scroll 函数封装\n      function scroll() {\n        return {\n          //此函数的返回值是对象\n          left: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop,\n          right:\n            window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft\n        };\n      }\n    </script>\n  </head>\n  <body style=\"height: 1000px;width: 2000px;\">\n    <div id=\"box1\"></div>\n  </body>\n</html>\n```\n\n### 举例2：获取鼠标距离所在盒子的距离\n\n关键点：\n\n```\n    鼠标距离所在盒子的距离 = 鼠标在整个页面的位置 - 所在盒子在整个页面的位置\n```\n\n代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .box {\n            width: 300px;\n            height: 200px;\n            padding-top: 100px;\n            background-color: pink;\n            margin: 100px;\n            text-align: center;\n            font: 18px/30px \"simsun\";\n            cursor: pointer;\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n\n</div>\n\n<script src=\"animate.js\"></script>\n<script>\n    //需求：鼠标进入盒子之后只要移动，哪怕1像素，随时显示鼠标在盒子中的坐标。\n    //技术点：新事件，onmousemove：在事件源上，哪怕鼠标移动1像素也会触动这个事件。\n    //一定程度上，模拟了定时器\n    //步骤：\n    //1.老三步和新五步\n    //2.获取鼠标在整个页面的位置\n    //3.获取盒子在整个页面的位置\n    //4.用鼠标的位置减去盒子的位置赋值给盒子的内容。\n\n    //1.老三步和新五步\n    var div = document.getElementsByTagName(\"div\")[0];\n\n    div.onmousemove = function (event) {\n\n        event = event || window.event;\n        //2.获取鼠标在整个页面的位置\n        var pagex = event.pageX || scroll().left + event.clientX;\n        var pagey = event.pageY || scroll().top + event.clientY;\n        //3.获取盒子在整个页面的位置\n        // var xx =\n        // var yy =\n        //4.用鼠标的位置减去盒子的位置赋值给盒子的内容。\n        var targetx = pagex - div.offsetLeft;\n        var targety = pagey - div.offsetTop;\n        this.innerHTML = \"鼠标在盒子中的X坐标为：\" + targetx + \"px;<br>鼠标在盒子中的Y坐标为：\" + targety + \"px;\"\n    }\n\n</script>\n</body>\n</html>\n```\n\n实现效果：\n\n![](http://img.smyhvae.com/20180203_1828.gif)\n\n### 举例3：商品放大镜\n\n代码实现：\n\n（1）index.html:\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n        }\n\n        .box {\n            width: 350px;\n            height: 350px;\n            margin: 100px;\n            border: 1px solid #ccc;\n            position: relative;\n        }\n\n        .big {\n            width: 400px;\n            height: 400px;\n            position: absolute;\n            top: 0;\n            left: 360px;\n            border: 1px solid #ccc;\n            overflow: hidden;\n            display: none;\n        }\n\n        /*mask的中文是：遮罩*/\n        .mask {\n            width: 175px;\n            height: 175px;\n            background: rgba(255, 255, 0, 0.4);\n            position: absolute;\n            top: 0;\n            left: 0;\n            cursor: move;\n            display: none;\n        }\n\n        .small {\n            position: relative;\n        }\n\n        img {\n            vertical-align: top;\n        }\n    </style>\n\n    <script src=\"tools.js\"></script>\n    <script>\n        window.onload = function () {\n            //需求：鼠标放到小盒子上，让大盒子里面的图片和我们同步等比例移动。\n            //技术点：onmouseenter==onmouseover 第一个不冒泡\n            //技术点：onmouseleave==onmouseout  第一个不冒泡\n            //步骤：\n            //1.鼠标放上去显示盒子，移开隐藏盒子。\n            //2.老三步和新五步（黄盒子跟随移动）\n            //3.右侧的大图片，等比例移动。\n\n            //0.获取相关元素\n            var box = document.getElementsByClassName(\"box\")[0];\n            var small = box.firstElementChild || box.firstChild;\n            var big = box.children[1];\n            var mask = small.children[1];\n            var bigImg = big.children[0];\n\n            //1.鼠标放上去显示盒子，移开隐藏盒子。(为小盒子绑定事件)\n            small.onmouseenter = function () {\n                //封装好方法调用：显示元素\n                show(mask);\n                show(big);\n            }\n            small.onmouseleave = function () {\n                //封装好方法调用：隐藏元素\n                hide(mask);\n                hide(big);\n            }\n\n            //2.老三步和新五步（黄盒子跟随移动）\n            //绑定的事件是onmousemove，而事件源是small(只要在小盒子上移动1像素，黄盒子也要跟随)\n            small.onmousemove = function (event) {\n                //新五步\n                event = event || window.event;\n\n                //想要移动黄盒子，必须要知道鼠标在small小图中的位置。\n                var pagex = event.pageX || scroll().left + event.clientX;\n                var pagey = event.pageY || scroll().top + event.clientY;\n\n                //x：mask的left值，y：mask的top值。\n                var x = pagex - box.offsetLeft - mask.offsetWidth / 2; //除以2，可以保证鼠标mask的中间\n                var y = pagey - box.offsetTop - mask.offsetHeight / 2;\n\n                //限制换盒子的范围\n                //left取值为大于0，小盒子的宽-mask的宽。\n                if (x < 0) {\n                    x = 0;\n                }\n                if (x > small.offsetWidth - mask.offsetWidth) {\n                    x = small.offsetWidth - mask.offsetWidth;\n                }\n                //top同理。\n                if (y < 0) {\n                    y = 0;\n                }\n                if (y > small.offsetHeight - mask.offsetHeight) {\n                    y = small.offsetHeight - mask.offsetHeight;\n                }\n\n                //移动黄盒子\n                console.log(small.offsetHeight);\n                mask.style.left = x + \"px\";\n                mask.style.top = y + \"px\";\n\n                //3.右侧的大图片，等比例移动。\n                //如何移动大图片？等比例移动。\n                //    大图片/大盒子 = 小图片/mask盒子\n                //    大图片走的距离/mask走的距离 = （大图片-大盒子）/（小图片-黄盒子）\n//                var bili = (bigImg.offsetWidth-big.offsetWidth)/(small.offsetWidth-mask.offsetWidth);\n\n                //大图片走的距离/mask盒子都的距离 = 大图片/小图片\n                var bili = bigImg.offsetWidth / small.offsetWidth;\n\n                var xx = bili * x;  //知道比例，就可以移动大图片了\n                var yy = bili * y;\n\n                bigImg.style.marginTop = -yy + \"px\";\n                bigImg.style.marginLeft = -xx + \"px\";\n            }\n        }\n    </script>\n</head>\n<body>\n<div class=\"box\">\n    <div class=\"small\">\n        <img src=\"images/001.jpg\" alt=\"\"/>\n        <div class=\"mask\"></div>\n    </div>\n    <div class=\"big\">\n        <img src=\"images/0001.jpg\" alt=\"\"/>\n    </div>\n</div>\n</body>\n</html>\n```\n\n（2）tools.js:\n\n```javascript\n/**\n * Created by smyhvae on 2018/02/03.\n */\n\n//显示和隐藏\nfunction show(ele) {\n    ele.style.display = \"block\";\n}\n\nfunction hide(ele) {\n    ele.style.display = \"none\";\n}\n\nfunction scroll() {  // 开始封装自己的scrollTop\n    if (window.pageYOffset != null) {  // ie9+ 高版本浏览器\n        // 因为 window.pageYOffset 默认的是  0  所以这里需要判断\n        return {\n            left: window.pageXOffset,\n            top: window.pageYOffset\n        }\n    }\n    else if (document.compatMode === \"CSS1Compat\") {    // 标准浏览器   来判断有没有声明DTD\n        return {\n            left: document.documentElement.scrollLeft,\n            top: document.documentElement.scrollTop\n        }\n    }\n    return {   // 未声明 DTD\n        left: document.body.scrollLeft,\n        top: document.body.scrollTop\n    }\n}\n\n```\n\n效果演示：\n\n![](http://img.smyhvae.com/20180203_1920.gif)\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/42-事件的传播和事件冒泡.md",
    "content": "---\ntitle: 42-事件的传播和事件冒泡\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## DOM事件流\n\n事件传播的三个阶段是：事件捕获、事件冒泡和目标。\n\n- 事件捕获阶段：事件从祖先元素往子元素查找（DOM树结构），直到捕获到事件目标 target。在这个过程中，默认情况下，事件相应的监听函数是不会被触发的。\n\n- 事件目标：当到达目标元素之后，执行目标元素该事件相应的处理函数。如果没有绑定监听函数，那就不执行。\n\n- 事件冒泡阶段：事件从事件目标 target 开始，从子元素往冒泡祖先元素冒泡，直到页面的最上一级标签。\n\n如下图所示：\n\n![](http://img.smyhvae.com/20180204_1218.jpg)\n\nPS：这个概念类似于 Android 里的 **touch 事件传递**。\n\n### 事件捕获\n\naddEventListener可以捕获事件：\n\n```javascript\n    box1.addEventListener(\"click\", function () {\n        alert(\"捕获 box3\");\n    }, true);\n```\n\n上面的方法中，参数为true，代表事件在捕获阶段执行。\n\n代码演示：\n\n```javascript\n    //参数为true，代表事件在「捕获」阶段触发；参数为false或者不写参数，代表事件在「冒泡」阶段触发\n    box3.addEventListener(\"click\", function () {\n        alert(\"捕获 child\");\n    }, true);\n\n    box2.addEventListener(\"click\", function () {\n        alert(\"捕获 father\");\n    }, true);\n\n    box1.addEventListener(\"click\", function () {\n        alert(\"捕获 grandfather\");\n    }, true);\n\n    document.addEventListener(\"click\", function () {\n        alert(\"捕获 body\");\n    }, true);\n```\n\n效果演示：\n\n![](http://img.smyhvae.com/20180204_1101.gif)\n\n（如果上面的图片打不开，请点击：<http://img.smyhvae.com/20180204_1101.gif>）\n\n**重点**：捕获阶段，事件依次传递的顺序是：window --> document --> html--> body --> 父元素、子元素、目标元素。\n\n这几个元素在事件捕获阶段的完整写法是：\n\n```javascript\n    window.addEventListener(\"click\", function () {\n        alert(\"捕获 window\");\n    }, true);\n\n    document.addEventListener(\"click\", function () {\n        alert(\"捕获 document\");\n    }, true);\n\n    document.documentElement.addEventListener(\"click\", function () {\n        alert(\"捕获 html\");\n    }, true);\n\n    document.body.addEventListener(\"click\", function () {\n        alert(\"捕获 body\");\n    }, true);\n\n    fatherBox.addEventListener(\"click\", function () {\n        alert(\"捕获 father\");\n    }, true);\n\n    childBox.addEventListener(\"click\", function () {\n        alert(\"捕获 child\");\n    }, true);\n\n```\n\n说明：\n\n（1）第一个接收到事件的对象是 **window**（有人会说body，有人会说html，这都是错误的）。\n\n（2）JS中涉及到DOM对象时，有两个对象最常用：window、doucument。它们俩是最先获取到事件的。\n\n**补充一个知识点：**\n\n在 js中：\n\n- 如果想获取 `html`节点，方法是`document.documentElement`。\n\n- 如果想获取 `body` 节点，方法是：`document.body`。\n\n二者不要混淆了。\n\n### 事件冒泡\n\n**事件冒泡**: 当一个元素上的事件被触发的时候（比如说鼠标点击了一个按钮），同样的事件将会在那个元素的所有**祖先元素**中被触发。这一过程被称为事件冒泡；这个事件从原始元素开始一直冒泡到DOM树的最上层。\n\n通俗来讲，冒泡指的是：**子元素的事件被触发时，父元素的同样的事件也会被触发**。取消冒泡就是取消这种机制。\n\n代码演示：\n\n```javascript\n    //事件冒泡\n    box3.onclick = function () {\n        alert(\"child\");\n    }\n\n    box2.onclick = function () {\n        alert(\"father\");\n    }\n\n    box1.onclick = function () {\n        alert(\"grandfather\");\n    }\n\n    document.onclick = function () {\n        alert(\"body\");\n    }\n\n```\n\n![](http://img.smyhvae.com/20180204_1028.gif)\n\n（如果上面的图片打不开，请点击：<http://img.smyhvae.com/20180204_1028.gif>）\n\n上图显示，当我点击子元素 box3 的时候，它的父元素box2、box1、body都依次被触发了。即使我改变代码的顺序，也不会影响效果的顺序。\n\n当然，上面的代码中，我们用 addEventListener 这种 DOM2的写法也是可以的，但是第三个参数要写 false，或者不写。\n\n**冒泡顺序**：\n\n一般的浏览器: （除IE6.0之外的浏览器）\n\n- div -> body -> html -> document -> window\n\nIE6.0：\n\n- div -> body -> html -> document\n\n### 不是所有的事件都能冒泡\n\n以下事件不冒泡：blur、focus、load、unload、onmouseenter、onmouseleave。意思是，事件不会往父元素那里传递。\n\n我们检查一个元素是否会冒泡，可以通过事件的以下参数：\n\n```javascript\n    event.bubbles\n```\n\n如果返回值为true，说明该事件会冒泡；反之则相反。\n\n举例：\n\n```javascript\n    box1.onclick = function (event) {\n        alert(\"冒泡 child\");\n\n        event = event || window.event;\n        console.log(event.bubbles); //打印结果：true。说明 onclick 事件是可以冒泡的\n    }\n```\n\n## 阻止冒泡\n\n大部分情况下，冒泡都是有益的。当然，如果你想阻止冒泡，也是可以的。可以按下面的方法阻止冒泡。\n\n### 阻止冒泡的方法\n\nw3c的方法：（火狐、谷歌、IE11）\n\n```javascript\n    event.stopPropagation();\n```\n\nIE10以下则是：\n\n```javascript\nevent.cancelBubble = true\n```\n\n兼容代码如下：\n\n```javascript\n   box3.onclick = function (event) {\n\n        alert(\"child\");\n\n        //阻止冒泡\n        event = event || window.event;\n\n        if (event && event.stopPropagation) {\n            event.stopPropagation();\n        } else {\n            event.cancelBubble = true;\n        }\n    }\n```\n\n上方代码中，我们对box3进行了阻止冒泡，产生的效果是：事件不会继续传递到 father、grandfather、body了。\n\n### 阻止冒泡的举例\n\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <style type=\"text/css\">\n            #box1 {\n                width: 100px;\n                height: 100px;\n                background-color: red;\n                /*\n        * 开启box1的绝对定位\n        */\n                position: absolute;\n            }\n        </style>\n\n        <script type=\"text/javascript\">\n            window.onload = function() {\n                /*\n                 * 使div可以跟随鼠标移动\n                 */\n\n                //获取box1\n                var box1 = document.getElementById('box1');\n\n                //给整个页面绑定：鼠标移动事件\n                document.onmousemove = function(event) {\n                    //兼容的方式获取event对象\n                    event = event || window.event;\n\n                    // 鼠标在页面的位置 = 滚动条滚动的距离 + 可视区域的坐标。\n                    var pagex = event.pageX || scroll().left + event.clientX;\n                    var pagey = event.pageY || scroll().top + event.clientY;\n\n                    //   设置div的偏移量（相对于整个页面）\n                    // 注意，如果想通过 style.left 来设置属性，一定要给 box1 开启绝对定位。\n                    box1.style.left = pagex + 'px';\n                    box1.style.top = pagey + 'px';\n                };\n\n                // 【重要注释】\n                // 当 document.onmousemove 和 box2.onmousemove 同时触发时，通过  box2 阻止事件向 document 冒泡。\n                // 也就是说，只要是在 box2 的区域，就只触发 document.onmousemove 事件\n                var box2 = document.getElementById('box2');\n                box2.onmousemove = function(event) {\n                    //阻止冒泡\n                    event = event || window.event;\n\n                    if (event && event.stopPropagation) {\n                        event.stopPropagation();\n                    } else {\n                        event.cancelBubble = true;\n                    }\n                };\n            };\n\n            // scroll 函数封装\n            function scroll() {\n                return {\n                    //此函数的返回值是对象\n                    left: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop,\n                    right: window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft,\n                };\n            }\n        </script>\n    </head>\n    <body style=\"height: 1000px;width: 2000px;\">\n        <div id=\"box2\" style=\"width: 300px; height: 300px; background-color: #bfa;\"></div>\n        <div id=\"box1\"></div>\n    </body>\n</html>\n```\n\n关键地方可以看代码中的注释。\n\n效果演示：\n\n![](http://img.smyhvae.com/20191112_1650.gif)\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n"
  },
  {
    "path": "04-JavaScript基础/43-事件委托.md",
    "content": "---\ntitle: 43-事件委托\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 事件委托\n\n事件委托，通俗地来讲，就是把一个元素响应事件（click、keydown......）的函数委托到另一个元素。\n\n比如说有一个列表 ul，列表之中有大量的列表项 `<a>`标签：\n\n```html\n<ul id=\"parent-list\">\n    <li><a href=\"javascript:;\" class=\"my_link\">超链接一</a></li>\n    <li><a href=\"javascript:;\" class=\"my_link\">超链接二</a></li>\n    <li><a href=\"javascript:;\" class=\"my_link\">超链接三</a></li>\n</ul>\n```\n\n当我们的鼠标移到`<a>`标签上的时候，需要获取此`<a>`的相关信息并飘出悬浮窗以显示详细信息，或者当某个`<a>`被点击的时候需要触发相应的处理事件。我们通常的写法，是为每个`<a>`都绑定类似onMouseOver或者onClick之类的事件监听：\n\n```javascript\n    window.onload = function(){\n        var parentNode = document.getElementById(\"parent-list\");\n        var aNodes = parentNode.getElementByTagName(\"a\");\n        for(var i=0, l = aNodes.length; i < l; i++){\n\n            aNodes[i].onclick = function() {\n                console.log('我是超链接 a 的单击相应函数');\n            }\n        }\n    }\n```\n\n但是，上面的做法过于消耗内存和性能。**我们希望，只绑定一次事件，即可应用到多个元素上**，即使元素是后来添加的。\n\n因此，比较好的方法就是把这个点击事件绑定到他的父层，也就是 `ul` 上，然后在执行事件函数的时候再去匹配判断目标元素。如下：\n\n```html\n    <!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\" />\n        <title></title>\n        <script type=\"text/javascript\">\n            window.onload = function() {\n\n                // 获取父节点，并为它绑定click单击事件。 false 表示事件在冒泡阶段触发（默认）\n                document.getElementById('parent-list').addEventListener('click', function(event) {\n                    event = event || window.event;\n\n                    // e.target 表示：触发事件的对象\n                    //如果触发事件的对象是我们期望的元素，则执行；否则不执行\n                    if (event.target && event.target.className == 'link') {\n                    // 或者写成 if (event.target && event.target.nodeName.toUpperCase() == 'A') {\n                        console.log('我是ul的单击响应函数');\n                    }\n                }, false);\n            };\n        </script>\n    </head>\n    <body>\n        <ul id=\"parent-list\" style=\"background-color: #bfa;\">\n            <li>\n                <p>我是p元素</p>\n            </li>\n            <li><a href=\"javascript:;\" class=\"link\">超链接一</a></li>\n            <li><a href=\"javascript:;\" class=\"link\">超链接二</a></li>\n            <li><a href=\"javascript:;\" class=\"link\">超链接三</a></li>\n        </ul>\n    </body>\n```\n\n上方代码，为父节点注册 click 事件，当子节点被点击的时候，click事件会从子节点开始**向父节点冒泡**。**父节点捕获到事件**之后，开始执行方法体里的内容：通过判断 event.target 拿到了被点击的子节点`<a>`。从而可以获取到相应的信息，并作处理。\n\n换而言之，参数为false，说明事件是在冒泡阶段触发（子元素向父元素传递事件）。而父节点注册了事件函数，子节点没有注册事件函数，此时，会在父节点中执行函数体里的代码。\n\n**总结**：事件委托是利用了冒泡机制，减少了事件绑定的次数，减少内存消耗，提高性能。\n\n事件委托的参考链接：\n\n- [荐 | JavaScript事件代理和委托（Delegation）](https://www.cnblogs.com/owenChen/archive/2013/02/18/2915521.html)\n\n- [JavaScript 事件委托详解](https://zhuanlan.zhihu.com/p/26536815)\n\n\n\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/44-键盘事件.md",
    "content": "---\ntitle: 44-键盘事件\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 鼠标的拖拽事件\n\n拖拽的流程：\n\n（1）`onmousedown`：当鼠标在被拖拽元素上按下时，开始拖拽；\n\n（2）`onmousemove`：当鼠标移动时被拖拽元素跟随鼠标移动；\n\n（3）`onmouseup`：当鼠标松开时，被拖拽元素固定在当前位置。\n\n## 鼠标的滚轮事件\n\n`onmousewheel`：鼠标滚轮滚动的事件，会在滚轮滚动时触发。但是火狐不支持该属性。\n\n`DOMMouseScroll`：在火狐中需要使用 DOMMouseScroll 来绑定滚动事件。注意该事件需要通过addEventListener()函数来绑定。\n\n## 键盘事件\n\n### 事件名\n\n`onkeydown`：按键被按下。\n\n`onkeyup`：按键被松开。\n\n\n**注意**：\n\n- 如果一直按着某一个按键不松手，那么，`onkeydown`事件会一直触发。此时，松开键盘，`onkeyup`事件会执行一次。\n\n- 当`onkeydown`连续触发时，第一次和第二次之间会间隔稍微长一点，后续的间隔会非常快。这种设计是为了防止误操作的发生。\n\n键盘事件一般都会绑定给一些可以获取到焦点的对象或者是document。代码举例：\n\n```html\n    <body>\n        <script>\n            document.onkeydown = function(event) {\n                event = event || window.event;\n                console.log('qianguyihao 键盘按下了');\n            };\n\n            document.onkeyup = function() {\n                console.log('qianguyihao 键盘松开了');\n            };\n        </script>\n\n        <input type=\"text\" />\n    </body>\n```\n\n\n### 判断哪个键盘被按下\n\n可以通过`event`事件对象的`keyCode`来获取按键的编码。\n\n\n此外，`event`事件对象里面还提供了以下几个属性：\n\n- altKey\n\n- ctrlKey\n\n- shiftKey\n\n\n上面这三个属性，可以用来判断`alt`、`ctrl`、和`shift`是否被按下。如果按下则返回true，否则返回false。代码举例：\n\n```html\n    <body>\n        <script>\n            document.onkeydown = function(event) {\n                event = event || window.event;\n                console.log('qianguyihao：键盘按下了');\n\n                // 判断y和ctrl是否同时被按下\n                if (event.ctrlKey && event.keyCode === 89) {\n                    console.log('ctrl和y都被按下了');\n                }\n            };\n        </script>\n    </body>\n```\n\n\n**举例**：input 文本框中，禁止输入数字。代码实现：\n\n\n```html\n    <body>\n        <input type=\"text\" />\n\n        <script>\n            //获取input\n            var input = document.getElementsByTagName('input')[0];\n\n            input.onkeydown = function(event) {\n                event = event || window.event;\n\n                //console.log('qianguyihao:' + event.keyCode);\n                //数字 48 - 57\n                //使文本框中不能输入数字\n                if (event.keyCode >= 48 && event.keyCode <= 57) {\n                    //在文本框中输入内容，属于onkeydown的默认行为\n                    return false; // 如果在onkeydown中取消了默认行为，则输入的内容，不会出现在文本框中\n                }\n            };\n        </script>\n    </body>\n\n```\n\n\n## 举例：通过键盘的方向键，移动盒子\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title></title>\n        <style type=\"text/css\">\n            #box1 {\n                width: 100px;\n                height: 100px;\n                background-color: red;\n                position: absolute;\n            }\n        </style>\n    </head>\n    <body>\n        <div id=\"box1\"></div>\n\n        <script type=\"text/javascript\">\n            // 使div可以根据不同的方向键向不同的方向移动\n            /*\n             * 按左键，div向左移\n             * 按右键，div向右移\n             * ...\n             */\n\n            //为document绑定一个按键按下的事件\n            document.onkeydown = function(event) {\n                event = event || window.event;\n\n                //定义一个变量，来表示移动的速度\n                var speed = 10;\n\n                //当用户按了ctrl以后，速度加快\n                if (event.ctrlKey) {\n                    console.log('smyhvae ctrl');\n                    speed = 20;\n                }\n\n                /*\n                 * 37 左\n                 * 38 上\n                 * 39 右\n                 * 40 下\n                 */\n                switch (event.keyCode) {\n                    case 37:\n                        //alert(\"向左\"); left值减小\n                        box1.style.left = box1.offsetLeft - speed + 'px'; // 在初始值的基础之上，减去 speed 大小\n                        break;\n                    case 39:\n                        //alert(\"向右\");\n                        box1.style.left = box1.offsetLeft + speed + 'px';\n                        break;\n                    case 38:\n                        //alert(\"向上\");\n                        box1.style.top = box1.offsetTop - speed + 'px';\n                        break;\n                    case 40:\n                        //alert(\"向下\");\n                        box1.style.top = box1.offsetTop + speed + 'px';\n                        break;\n                }\n            };\n        </script>\n    </body>\n</html>\n\n\n```\n\n上方代码，待改进的地方：\n\n（1）移动盒子时，如果要加速，需要先按`方向键`，再按`Ctrl键`。\n\n（2）首次移动盒子时，动作较慢。后续如果学习了定时器相关的内容，可以再改进。\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/45-BOM简介和navigator.userAgent&History&Location.md",
    "content": "---\ntitle: 45-BOM简介和navigator.userAgent&History&Location\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 常见概念\n\n### JavaScript的组成\n\nJavaScript基础分为三个部分：\n\n- ECMAScript：JavaScript的语法标准。包括变量、表达式、运算符、函数、if语句、for语句等。\n\n- **DOM**：文档对象模型（Document object Model），操作**网页上的元素**的API。比如让盒子移动、变色、轮播图等。\n\n- **BOM**：浏览器对象模型（Browser Object Model），操作**浏览器部分功能**的API。比如让浏览器自动滚动。\n\n### 常见的 BOM 对象\n\nBOM可以让我们通过JS来操作浏览器。BOM中为我们提供了一些对象，来完成对浏览器相关的操作。\n\n常见的 BOM对象有：\n\n- Window：代表整个浏览器的窗口，同时 window 也是网页中的全局对象。\n\n- Navigator：代表当前浏览器的信息，通过该对象可以识别不同的浏览器。\n\n- Location：代表当前浏览器的地址栏信息，通过 Location 可以获取地址栏信息，或者操作浏览器跳转页面。\n\n- History：代表浏览器的历史记录，通过该对象可以操作浏览器的历史记录。由于隐私原因，该对象不能获取到具体的历史记录，只能操作浏览器向前或向后翻页，而且该操作只在当次访问时有效。\n\n- Screen：代表用户的屏幕信息，通过该对象可以获取用户的显示器的相关信息。\n\n备注：这些 BOM 对象都是作为 window 对象的属性保存的，可以通过window对象来使用，也可以直接使用。比如说，我可以使用 `window.location.href`，也可以直接使用 `location.href`，二者是等价的。\n\n备注2：不要忘了，之前学习过的`document`也是在`window`中保存的。\n\n这篇文章，我们先来讲一下 几个常见的 BOM 对象。\n\n## Navigator 和 `navigator.userAgent`\n\n`Navigator`代表当前浏览器的信息，通过该对象可以识别不同的浏览器。\n\n由于历史原因，Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了。\n\n**一般我们只会使用`navigator.userAgent`来获取浏览器的信息**。\n\n\nuserAgent 的值是一个字符串，简称 **UA**，这个字符串中包含了用来描述浏览器信息的内容，不同的浏览器会有不同的userAgent。\n\n**代码举例**：（获取当前浏览器的UA）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <script>\n            var ua = navigator.userAgent; // 获取当前浏览器的 userAgent\n\n            console.log('qianguyihao 当前浏览器的UA是：' + ua);\n\n            if (/firefox/i.test(ua)) {\n                alert('是火狐浏览器');\n            } else if (/chrome/i.test(ua)) {\n                alert('是Chrome浏览器');\n            } else if (/msie/i.test(ua)) {\n                alert('是IE浏览器');\n            } else if ('ActiveXObject' in window) {\n                alert('是 IE11 浏览器');\n            }\n        </script>\n    </body>\n</html>\n```\n\n### 在电脑上模拟移动端浏览器\n\n不同浏览器（包括微信内置的浏览器）的 userAgent 信息，是不一样的，我们可以根据 `navigator.userAgent`属性来获取。\n\n比如说，我们在电脑浏览器上，按F12，然后在控制台输入`navigator.userAgent`，如下：\n\n![](http://img.smyhvae.com/20180425_1656.png)\n\n上图显示，MacOS上的Chrome浏览器的 userAgent 是：\n\n```\n\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36\"\n```\n\n我们还可以在电脑浏览器的控制台里可以添加很多设备，通过这种方式，可以模拟移动端浏览器的场景，非常有用，请务必掌握。操作如下：\n\n（1）需要点击 edit，手动添加：\n\n![](http://img.smyhvae.com/20191127_1903.png)\n\n（2）添加时，根据 User agent 来识别不同的浏览器：\n\n![](http://img.smyhvae.com/20191127_1918.png)\n\n\n### 不同浏览器的 userAgent\n\niOS 版微信浏览器：\n\n```\nMozilla/5.0 (iPhone; CPU iPhone OS 9_3 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13E233 MicroMessenger/6.3.15 NetType/WIFI Language/zh_CN\n```\n\nAndroid 版微信浏览器：\n\n```\nMozilla/5.0 (Linux; Android 5.0.1; GT-I9502 Build/LRX22C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.121 Mobile Safari/537.36 MicroMessenger/6.1.0.78_r1129455.543 NetType/WIFI\n```\n\niOS 版本QQ浏览器：\n\n```\nMozilla/5.0 (iPhone; CPU iPhone OS 11_2_2 like Mac OS X) AppleWebKit/604.4.7 (KHTML, like Gecko) Mobile/15C202 QQ/7.3.5.473 V1_IPH_SQ_7.3.5_1_APP_A Pixel/1125 Core/UIWebView Device/Apple(iPhone X) NetType/WIFI QBWebViewType/1\n```\n\nAndroid 版 QQ浏览器：\n\n```\nMozilla/5.0 (Linux; Android 4.4.2; PE-TL20 Build/HuaweiPE-TL20; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/043807 Mobile Safari/537.36 V1_AND_SQ_7.3.2_762_YYB_D QQ/7.3.2.3350 NetType/WIFI WebP/0.3.0 Pixel/1080\n```\n\n\n**参考链接**：\n\n- [微信、QQ在Android和iOS的UserAgent](https://blog.csdn.net/taambernk520/article/details/80801574)\n\n- [判断微信内置浏览器的UserAgent](http://www.cnblogs.com/7z7chn/p/5370352.html)\n\n- [微信内置浏览器UserAgent的判断](https://gist.github.com/wjp2013/fff34c063cf0cf227d65)\n\n\n## History 对象\n\nHistory对象：可以用来操作浏览器的向前或向后翻页。\n\n### History对象的属性\n\n```javascript\nhistory.length\n```\n\n解释：获取浏览器历史列表中的 url 数量。注意，只是统计当次的数量，如果浏览器关了，数量会重置为1。\n\n### History对象的方法\n\n**方法1**：\n\n```\nhistory.back();\n```\n\n解释：用来回退到上一个页面，作用和浏览器的「回退按钮」一样。\n\n**方法2**：\n\n```javascript\nhistory.forward();\n```\n\n解释：用来跳转下一个页面，作用和浏览器的「前进按钮」一样。\n\n**方法3**：\n\n```javascript\nhistory.go( int n);  // 需要整数作为参数\n\n// 代码举例：\nhistory.go( 1 ); // 向前跳转一个页面，相当于 history.forward()\n\nhistory.go( 2 ); // 表示向前跳转两个页面\n\nhistory.go( 0 ); // 刷新当前页面\n\nhistory.go( -1 ); // 向后跳转一个页面，相当于 history.back()\n\nhistory.go( -2 ); // 向后跳转两个页面\n\n```\n\n解释：向前/向后跳转 n 个页面。\n\n备注：浏览器的前进按钮、后退按钮，在这个位置：\n\n![](http://img.smyhvae.com/20180201_2146.png)\n\n\n## Location 对象\n\nLocation 对象：封装了浏览器地址栏的 URL 信息。\n\n下面介绍一些常见的属性和方法。\n\n### Location 对象的属性：location.href\n\n```\nlocation.href\n\nlocation.href = 'https://xxx';\n```\n\n解释：获取当前页面的 url 路径（或者设置 url 路径）；或者跳转到指定路径。\n\n举例1：\n\n```javascript\nconsole.log(location.href); // 获取当前页面的url 路径\n\n```\n\n举例2：\n\n```javascript\n    location.href = 'www.baidu.com'; // 跳转到指定的页面链接。通俗理解就是：跳转到其他的页面\n```\n\n从上方的**举例2**中可以看出：如果直接将`location.href`属性修改为一个绝对路径（或相对路径），则页面会自动跳转到该路径，并生成相应的历史记录。\n\n**window.location.href 是异步代码：**\n\n需要特别注意的是：window.location.href的赋值，并不会中断Javascript的执行立即进行页面跳转。因为 LocationChange 行为在浏览器内核中是起定时器异步执行的。异步执行的好处是为了防止代码调用过深，导致栈溢出，另外也是为了防止递归进入加载逻辑，导致状态紊乱，保证导航请求是顺序执行的。\n\n解决办法：在 location.href 的下一行，加上 return 即可。意思是，执行了 location.href 之后，就不要再继续往下执行了。\n\n参考链接：[location.href的异步机制](https://juejin.cn/post/6844904040518647815)\n\n\n### Location 对象的方法\n\n**方法1**：\n\n```javascript\n    location.assign(str);\n```\n\n解释：用来跳转到其他的页面，作用和直接修改`location.href`一样。\n\n**方法2**：\n\n```javascript\n    location.reload();\n```\n\n解释：用于重新加载当前页面，作用和刷新按钮一样。\n\n代码举例：\n\n```javascript\n    location.reload(); // 重新加载当前页面。\n    location.reload(true); // 在方法的参数中传递一个true，则会强制清空缓存刷新页面。\n\n```\n\n**方法3**：\n\n```javascript\n\n    location.replace();\n```\n\n解释：使用一个新的页面替换当前页面，调用完毕也会跳转页面。但不会生成历史记录，不能使用「后退按钮」后退。\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20190101.png)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/46-定时器.md",
    "content": "---\ntitle: 46-定时器\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 定时器的常见方法\n\n- setInterval()：循环调用。将一段代码，**每隔一段时间**执行一次。（循环执行）\n\n- setTimeout()：延时调用。将一段代码，等待一段时间之后**再执行**。（只执行一次）\n\n备注：在实际开发中，二者是可以根据需要，互相替代的。\n\n## setInterval() 的使用\n\n`setInterval()`：循环调用。将一段代码，**每隔一段时间**执行一次。（循环执行）\n\n**参数**：\n\n- 参数1：回调函数，该函数会每隔一段时间被调用一次。\n\n- 参数2：每次调用的间隔时间，单位是毫秒。\n\n**返回值**：返回一个Number类型的数据。这个数字用来作为定时器的**唯一标识**，方便用来清除定时器。\n\n### 定义定时器\n\n**方式一**：匿名函数\n\n每间隔一秒，将 数字 加1：\n\n```javascript\n    let num = 1;\n   setInterval(function () {\n       num ++;\n       console.log(num);\n   }, 1000);\n```\n\n**方式二：**\n\n每间隔一秒，将 数字 加1：\n\n```javascript\n    setInterval(fn,1000);\n\n    function fn() {\n       num ++;\n       console.log(num);\n    }\n\n```\n\n### 清除定时器\n\n定时器的返回值是作为这个定时器的**唯一标识**，可以用来清除定时器。具体方法是：假设定时器setInterval()的返回值是`参数1`，那么`clearInterval(参数1)`就可以清除定时器。\n\nsetTimeout()的道理是一样的。\n\n代码举例：\n\n```html\n<script>\n    let num = 1;\n\n    const timer = setInterval(function () {\n        console.log(num);  //每间隔一秒，打印一次num的值\n        num ++;\n        if(num === 5) {  //打印四次之后，就清除定时器\n            clearInterval(timer);\n        }\n\n    }, 1000);\n</script>\n\n```\n\n## setTimeout() 的使用\n\n`setTimeout()`：延时调用。将一段代码，等待一段时间之后**再执行**。（只执行一次）\n\n**参数**：\n\n- 参数1：回调函数，该函数会每隔一段时间被调用一次。\n\n- 参数2：每次调用的间隔时间，单位是毫秒。\n\n**返回值**：返回一个Number类型的数据。这个数字用来作为定时器的**唯一标识**，方便用来清除定时器。\n\n### 定义和清除定时器\n\n代码举例：\n\n```javascript\n    const timer = setTimeout(function() {\n        console.log(1); // 3秒之后，再执行这段代码。\n    }, 3000);\n\n    clearTimeout(timer);\n\n```\n\n代码举例：（箭头函数写法）\n\n```javascript\n    setTimeout(() => {\n        console.log(1); // 3秒之后，再执行这段代码。\n    }, 3000);\n```\n\n\n### setTimeout() 举例：5秒后关闭网页两侧的广告栏\n\n假设网页两侧的广告栏为两个img标签，它们的样式为：\n\n\n```html\n<style>\n    ...\n    ...\n\n</style>\n\n```\n\n5秒后关闭广告栏的js代码为：\n\n```html\n    <script>\n        window.onload = function () {\n            //获取相关元素\n            var imgArr = document.getElementsByTagName(\"img\");\n            //设置定时器：5秒后关闭两侧的广告栏\n            setTimeout(fn,5000);\n            function fn(){\n                imgArr[0].style.display = \"none\";\n                imgArr[1].style.display = \"none\";\n            }\n        }\n    </script>\n```\n\n\n"
  },
  {
    "path": "04-JavaScript基础/47-jQuery的介绍和选择器.md",
    "content": "---\ntitle: 47-jQuery的介绍和选择器\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n\n\n## jQuery 的介绍\n\n### 引入 jQuery 的原因\n\n在用 js 写代码时，会遇到一些问题：\n\n- window.onload 事件有事件覆盖的问题，因此只能写一个事件。\n\n- 代码容错性差。\n\n- 浏览器兼容性问题。\n\n- 书写很繁琐，代码量多。\n\n- 代码很乱，各个页面到处都是。\n\n- 动画效果很难实现。\n\n如下图所示：\n\n![](http://img.smyhvae.com/20180204_1710.png)\n\njQuery的出现，可以解决以上问题。\n\n### 什么是 jQuery\n\njQuery 是 js 的一个库，封装了我们开发过程中常用的一些功能，方便我们调用，提高开发效率。\n\njs库是把我们常用的功能放到一个单独的文件中，我们用的时候，直接引用到页面里即可。\n\n以下是jQuery的相关信息：\n\n- 官网：<http://jquery.com/>\n\n- 官网API文档：<http://api.jquery.com/>\n\n- 中文汉化API文档：<http://www.css88.com/jqapi-1.9/>\n\n### 学习jQuery，主要是学什么\n\n初期，主要学习如何使用jQuery操作DOM，其实就是学习jQuery封装好的那些API。\n\n这些API的共同特点是：几乎全都是方法。所以，在使用jQuery的API时，都是方法调用，也就是说要加小括号()，小括号里面是相应的参数，参数不同，功能不同。\n\n### jQuery初体验\n\n现在用原生 js 来写下面这一段代码：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            height: 100px;\n            background-color: pink;\n            margin: 10px;\n            display: none;\n        }\n    </style>\n\n    <script>\n        //原生js\n        window.onload = function () {\n            var btn = document.getElementsByTagName(\"button\")[0];\n            var divArr = document.getElementsByTagName(\"div\");\n\n            btn.onclick = function () {\n                for (var i = 0; i < divArr.length; i++) {\n                    divArr[i].style.display = \"block\";\n                    divArr[i].innerHTML = \"生命壹号\";\n                }\n            }\n        }\n    </script>\n</head>\n<body>\n\n<button>显示五个div盒子和设置内容</button>\n<div></div>\n<div></div>\n<div></div>\n<div></div>\n<div></div>\n\n</body>\n</html>\n```\n\n如果用 jQuery 来写，保持其他的代码不变，`<script>`部分的代码修改为：（需要提前引入 ）\n\n```html\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n\n        //jquery版\n        $(document).ready(function () {\n            //获取元素\n            var jQbtn = $(\"button\");//根据标签名获取元素\n            var jQdiv = $(\"div\");//根据标签名获取元素\n            //绑定事件\n            jQbtn.click(function () {\n                jQdiv.show(1000);//显示盒子。\n                jQdiv.html(\"tomorrow！\");//设置内容\n                //上面的两行可以写成链式编程：jQdiv.show(3000).html(1111);\n\n            });//事件是通过方法绑定的。\n\n        });\n    </script>\n```\n\n### jQuery 的两大特点\n\n（1）**链式编程**：比如`.show()`和`.html()`可以连写成`.show().html()`。\n\n\n链式编程原理：return this。\n\n通常情况下，只有设置操作才能把链式编程延续下去。因为获取操作的时候，会返回获取到的相应的值，无法返回 this。\n\n\n（2）**隐式迭代**：隐式 对应的是 显式。隐式迭代的意思是：在方法的内部会为匹配到的所有元素进行循环遍历，执行相应的方法；而不用我们再进行循环，简化我们的操作，方便我们调用。\n\n如果获取的是多元素的值，大部分情况下返回的是第一个元素的值。\n\n\n\n\n\n\n## jQuery 的使用\n\n### 使用 jQuery 的基本步骤\n\n（1）引包\n\n（2）入口函数\n\n（3）功能实现代码（事件处理）\n\n如下图所示：\n\n![](http://img.smyhvae.com/20180204_1940.png)\n\n主要，导包的代码一定要放在js代码的最上面。\n\n### jQuery 的版本\n\njQuery 有两个大版本：\n\n- 1.x版本：最新版为 v1.11.3。\n\n- 2.x版本：最新版为 v2.1.4（不再支持IE6、7、8）。\n\n- 3.x版本。\n\n\nPS：开发版本一般用1.10以上。\n\n我们以 v1.11.1版本为例，下载下来后发现，里面有两个文件：\n\n![](http://img.smyhvae.com/20180204_1950.png)\n\n它们的区别是：\n\n- 第一个是未压缩版，第二个是压缩版。\n\n- 平时开发过程中，可以使用任意一个版本；但是，项目上线的时候，推荐使用压缩版。\n\n\n## jQuery 的入口函数和 `$` 符号\n\n\n### 入口函数（重要）\n\n原生 js 的入口函数指的是：`window.onload = function() {};` 如下：\n\n```javascript\n        //原生 js 的入口函数。页面上所有内容加载完毕，才执行。\n        //不仅要等文本加载完毕，而且要等图片也要加载完毕，才执行函数。\n       window.onload = function () {\n           alert(1);\n       }\n```\n\n\n而 jQuery的入口函数，有以下几种写法：\n\n写法一：\n\n\n```javascript\n       //1.文档加载完毕，图片不加载的时候，就可以执行这个函数。\n       $(document).ready(function () {\n           alert(1);\n       })\n```\n\n写法二：（写法一的简洁版）\n\n```javascript\n       //2.文档加载完毕，图片不加载的时候，就可以执行这个函数。\n       $(function () {\n           alert(1);\n       });\n```\n\n写法三：\n\n```javascript\n       //3.文档加载完毕，图片也加载完毕的时候，在执行这个函数。\n       $(window).ready(function () {\n           alert(1);\n       })\n```\n\n**jQuery入口函数与js入口函数的区别**：\n\n区别一：书写个数不同：\n\n- Js 的入口函数只能出现一次，出现多次会存在事件覆盖的问题。\n\n- jQuery 的入口函数，可以出现任意多次，并不存在事件覆盖问题。\n\n\n区别二：执行时机不同：\n\n- Js的入口函数是在**所有的文件资源加载**完成后，才执行。这些**文件资源**包括：页面文档、外部的js文件、外部的css文件、图片等。\n\n- jQuery的入口函数，是在文档加载完成后，就执行。文档加载完成指的是：DOM树加载完成后，就可以操作DOM了，不用等到所有的**外部资源**都加载完成。\n\n文档加载的顺序：从上往下，边解析边执行。\n\n### jQuery的`$`符号\n\njQuery 使用 `$` 符号原因：书写简洁、相对于其他字符与众不同、容易被记住。\n\njQuery占用了我们两个变量：`$` 和 jQuery。当我们在代码中打印它们俩的时候：\n\n\n```html\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n\n        console.log($);\n        console.log(jQuery);\n        console.log($===jQuery);\n\n\n    </script>\n```\n\n打印结果如下：\n\n![](http://img.smyhvae.com/20180204_2014.png)\n\n从打印结果可以看出，$ 代表的就是 jQuery。\n\n那怎么理解jQuery里面的 `$` 符号呢？\n\n**`$` 实际上表示的是一个函数名** 如下：\n\n\n```html\n\t$(); // 调用上面我们自定义的函数$\n\n\t$(document）.ready(function(){}); // 调用入口函数\n\n\t$(function(){}); // 调用入口函数\n\n\t$(“#btnShow”) // 获取id属性为btnShow的元素\n\n\t$(“div”) // 获取所有的div标签元素\n\n```\n\n如上方所示，jQuery 里面的 `$` 函数，根据传入参数的不同，进行不同的调用，实现不同的功能。返回的是jQuery对象。\n\njQuery这个js库，除了` $` 之外，还提供了另外一个函数：jQuery。jQuery函数跟 `$` 函数的关系：`jQuery === $`。\n\n##  js中的DOM对象 和 jQuery对象 比较（重点，难点）\n\n### 二者的区别\n\n通过 jQuery 获取的元素是一个**数组**，数组中包含着原生JS中的DOM对象。举例：\n\n针对下面这样一个div结构：\n\n```html\n<div></div>\n<div class=\"box\"></div>\n<div id=\"box\"></div>\n<div class=\"box\"></div>\n<div></div>\n```\n\n通过原生 js 获取这些元素节点的方式是：\n\n```javascript\n    var myBox = document.getElementById(\"box\");           //通过 id 获取单个元素\n    var boxArr = document.getElementsByClassName(\"box\");  //通过 class 获取的是数组\n    var divArr = document.getElementsByTagName(\"div\");    //通过标签获取的是数组\n```\n\n通过 jQuery 获取这些元素节点的方式是：（获取的都是数组）\n\n```javascript\n    //获取的是数组，里面包含着原生 JS 中的DOM对象。\n    var jqBox1 = $(\"#box\");\n    var jqBox2 = $(\".box\");\n    var jqBox3 = $(\"div\");\n```\n\n我们打印出来看看：\n\n![](http://img.smyhvae.com/20180204_2045.png)\n\n上图显示，由于JQuery 自带了 css()方法，我们还可以直接在代码中给 div 设置 css 属性。\n\n**总结**：jQuery 就是把 DOM 对象重新包装了一下，让其具有了 jQuery 方法。\n\n### 二者的相互转换\n\n**1、 DOM 对象 转为 jQuery对象**：\n\n```javascript\n\t$(js对象);\n```\n\n举例：（拿上一段的代码举例）\n\n```javascript\n\t//转换。\n\tjqBox1 = $(myBox);\n\tjqBox2 = $(boxArr);\n\tjqBox3 = $(divArr);\n```\n\nDOM 对象转换成了 jquery 对象之后，上面的功能可以直接调用。\n\n**2、jQuery对象 转为 DOM 对象**：\n\n```javascript\n\tjquery对象[index];      //方式1（推荐）\n\n\tjquery对象.get(index);  //方式2\n```\n\njQuery对象转换成了 DOM 对象之后，可以直接调用 DOM 提供的一些功能。如：\n\n```javascript\n    //jquery对象转换成 DOM 对象之后\n    jqBox3[0].style.backgroundColor = \"black\";\n    jqBox3.get(4).style.backgroundColor = \"pink\";\n```\n\n**总结**：如果想要用哪种方式设置属性或方法，必须转换成该类型。\n\n### 举例：隔行变色\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        //入口函数\n        jQuery(function () {\n            var jqLi = $(\"li\");\n            for (var i = 0; i < jqLi.length; i++) {\n                if (i % 2 === 0) {\n                    //jquery对象，转换成了js对象\n                    jqLi[i].style.backgroundColor = \"pink\";\n                } else {\n                    jqLi[i].style.backgroundColor = \"yellow\";\n                }\n            }\n        });\n    </script>\n</head>\n<body>\n<ul>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n</ul>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180204_2111.png)\n\n## jQuery 选择器\n\n我们以前在CSS中学习的选择器有：\n\n![](http://img.smyhvae.com/20180204_2122.png)\n\n今天来学习一下jQuery 选择器。\n\njQuery选择器是jQuery强大的体现，它提供了一组方法，让我们更加方便的获取到页面中的元素。\n\n### 1、jQuery 的基本选择器\n\n![](http://img.smyhvae.com/20180204_2125.png)\n\n解释如下：\n\n![](http://img.smyhvae.com/20180204_2126.png)\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        //入口函数\n        jQuery(document).ready(function () {\n\n            //三种方式获取jquery对象\n            var jqBox1 = $(\"#box\");\n            var jqBox2 = $(\".box\");\n            var jqBox3 = $(\"div\");\n\n            //操作标签选择器\n            jqBox3.css(\"width\", 100);\n            jqBox3.css(\"height\", 100);\n            jqBox3.css(\"margin\", 10);\n            jqBox3.css(\"background\", \"pink\");\n\n            //操作类选择器(隐式迭代，不用一个一个设置)\n            jqBox2.css(\"background\", \"red\");\n\n            //操作id选择器\n            jqBox1.css(\"background\", \"yellow\");\n\n        });\n    </script>\n</head>\n<body>\n\n<div></div>\n<div class=\"box\"></div>\n<div id=\"box\"></div>\n<div class=\"box\"></div>\n<div></div>\n\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180204_2133.png)\n\n### 2、层级选择器\n\n![](http://img.smyhvae.com/20180204_2138.png)\n\n解释如下：\n\n![](http://img.smyhvae.com/20180204_2139.png)\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //获取ul中的li设置为粉色\n            //后代：儿孙重孙曾孙玄孙....\n            var jqLi = $(\"ul li\");\n            jqLi.css(\"margin\", 5);\n            jqLi.css(\"background\", \"pink\");\n\n            //子代：亲儿子\n            var jqOtherLi = $(\"ul>li\");\n            jqOtherLi.css(\"background\", \"red\");\n        });\n    </script>\n</head>\n<body>\n<ul>\n    <li>111</li>\n    <li>222</li>\n    <li>333</li>\n    <ol>\n        <li>aaa</li>\n        <li>bbb</li>\n        <li>ccc</li>\n    </ol>\n</ul>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180204_2145.png)\n\n### 3、基本过滤选择器\n\n![](http://img.smyhvae.com/20180204_2150.png)\n\n解释：\n\n![](http://img.smyhvae.com/20180204_2151.png)\n\n举例：\n\n```html\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(document).ready(function () {\n\n            // :odd\n            $(\"li:odd\").css(\"background\", \"red\");\n\n            // :even\n            $(\"li:even\").css(\"background\", \"green\");\n\n            // :eq(index)\n            $(\"ul li:eq(3)\").css(\"font-size\", \"30px\");  //设置第四个li的字体\n\n            // :lt(index)\n            $(\"li:lt(6)\").css(\"font-size\", \"30px\");\n\n            // :gt(index)\n            $(\".ulList1 li:gt(7)\").css(\"font-size\", \"40px\");\n\n            // :first\n            $(\".ulList li:first\").css(\"font-size\", \"40px\");\n\n            // :last\n            $(\"li:last\").css(\"font-size\", \"40px\");\n        });\n    </script>\n```\n\n### 4、属性选择器\n\n![](http://img.smyhvae.com/20180204_2155.png)\n\n### 5、筛选选择器\n\n![](http://img.smyhvae.com/20180204_2200.png)\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(function () {\n            var jqul = $(\"ul\");\n\n            //find(selector); 从jquery对象的后代中查找\n            //必须制定参数，如果不指定获取不到元素。length === 0\n            jqul.find(\"li\").css(\"background\", \"pink\");\n            console.log(jqul.find());\n\n            //chidlren(selector); 从jquery对象的子代中查找\n            //不写参数代表获取所有子元素。\n            jqul.children(\"li\").css(\"background\", \"green\");\n\n            //eq(索引值); 从jquery对象的子代中查找该索引值的元素\n            //要写该数组中的第几个。\n            jqul.children().eq(0).css(\"background\", \"red\");\n\n            //next(); 该元素的下一个兄弟元素\n            jqul.children().eq(0).next().css(\"background\", \"yellow\");\n\n            //siblings(selector); 该元素的所有兄弟元素\n            jqul.children().eq(0).next().siblings().css(\"border\", \"1px solid blue\");\n\n            //parent(); 该元素的父元素（和定位没有关系）\n            console.log(jqul.children().eq(0).parent());\n        });\n    </script>\n</head>\n<body>\n\n<ul>\n    <li>生命壹号，永不止步</li>\n    <li class=\"box\">生命壹号，永不止步</li>\n    <span>生命壹号，永不止步</span>\n    <li class=\"box\">生命壹号，永不止步</li>\n    <i>生命壹号，永不止步</i>\n    <li>生命壹号，永不止步</li>\n    <a id=\"box\" href=\"#\">生命壹号，永不止步</a>\n    <ol>\n        <li>我是ol中的li</li>\n        <li>我是ol中的li</li>\n        <li>我是ol中的li</li>\n        <li>我是ol中的li</li>\n    </ol>\n</ul>\n\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180204_2203.png)\n\n\n## 举例\n\n### 举例1：鼠标悬停时，弹出下拉菜单【重要】\n\n完整版代码：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        * {\n            margin: 0;\n            padding: 0;\n        }\n\n        ul {\n            list-style: none;\n        }\n\n        .wrap {\n            width: 330px;\n            height: 30px;\n            margin: 100px auto 0;\n            padding-left: 10px;\n            background-color: pink;\n        }\n\n        .wrap li {\n            background-color: yellowgreen;\n        }\n\n        .wrap > ul > li {\n            float: left;\n            margin-right: 10px;\n            position: relative;\n        }\n\n        .wrap a {\n            display: block;\n            height: 30px;\n            width: 100px;\n            text-decoration: none;\n            color: #000;\n            line-height: 30px;\n            text-align: center;\n        }\n\n        .wrap li ul {\n            position: absolute;\n            top: 30px;\n            display: none;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        //入口函数\n        $(document).ready(function () {\n            //需求：鼠标放入一级li中，让他里面的ul显示。移开隐藏。\n            var jqli = $(\".wrap>ul>li\");\n\n            //绑定事件\n            jqli.mouseenter(function () {\n                //这个位置用到了this.\n                // console.log(this);  //打印结果是js中的dom对象。注意：jquery对象绑定的事件中，this指js中的dom对象。【重要】\n\n                //让this中的ul显示出来。\n//                原生 js 的做法是：this.children[1].style.display = \"block\";\n                //把js的dom对象包装为jquery对象，然后用jquery方法操作\n                $(this).children(\"ul\").show();\n            });\n\n            //绑定事件：鼠标移开时，隐藏下拉菜单\n            jqli.mouseleave(function () {\n                $(this).children(\"ul\").hide();\n            });\n        });\n    </script>\n\n</head>\n<body>\n<div class=\"wrap\">\n    <ul>\n        <li>\n            <a href=\"javascript:void(0);\">一级菜单1</a>\n            <ul>\n                <li><a href=\"javascript:void(0);\">二级菜单1</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单2</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单3</a></li>\n            </ul>\n        </li>\n        <li>\n            <a href=\"javascript:void(0);\">一级菜单1</a>\n            <ul>\n                <li><a href=\"javascript:void(0);\">二级菜单1</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单2</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单3</a></li>\n            </ul>\n        </li>\n        <li>\n            <a href=\"javascript:void(0);\">一级菜单1</a>\n            <ul>\n                <li><a href=\"javascript:void(0);\">二级菜单1</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单2</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单3</a></li>\n            </ul>\n        </li>\n    </ul>\n</div>\n</body>\n</html>\n```\n\n\n上方代码中，我们可以看到，用 jQuery来操作，是非常方便的。\n\n实现效果如下：\n\n\n![](http://img.smyhvae.com/20180205_1030.gif)\n\n\n**this的用法：**\n\n上方代码中，核心的一行代码是：\n\n```javascript\n\t$(this).children(\"ul\").show();\n\n\t$(this).children(\"ul\").hide();\n```\n\n如果我把这行代码中的this直接写成 DOM对象：\n\n```javascript\n\tjqli.children(\"ul\").show();\n\n\tjqli.children(\"ul\").hide();\n```\n\n产生的结果是：（不是我们期望的结果）\n\n\n![](http://img.smyhvae.com/20180205_1050.gif)\n\n\n两张图的对比，可以看出this的作用：谁正在调用函数，this就指的是谁。\n\n### 举例2：鼠标悬停时变色\n\n完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n\n            //需求；隔行变色；鼠标悬停时，还要变色。\n            var jqli1 = $(\"li:odd\");\n            var jqli2 = $(\"li:even\");\n            jqli1.css(\"background\", \"#cccccc\");\n            jqli2.css(\"background\", \"white\");\n\n            //鼠标悬停时变色\n            var color = \"\";\n            $(\"li\").mouseenter(function () {\n                color = $(this).css(\"background\");  //先把之前的颜色保存下来，鼠标离开时还原\n                $(this).css(\"background\", \"green\");\n            });\n            //鼠标离开时，恢复为原来的颜色\n            $(\"li\").mouseleave(function () {\n                $(this).css(\"background\", color);\n            });\n        });\n    </script>\n</head>\n<body>\n\n<ul>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n    <li>生命壹号，永不止步</li>\n</ul>\n\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180205_1100.gif)\n\n### 举例3：突出显示\n\n要求：鼠标悬停时，突出显示这个li，让其他的li都半透明。\n\n用 jQuery的选择起来实现，会发现非常方便。\n\n完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        * {\n            margin: 0;\n            padding: 0;\n        }\n\n        ul {\n            list-style: none;\n        }\n\n        body {\n            background: #000;\n        }\n\n        .wrap {\n            margin: 100px auto 0;\n            width: 630px;\n            height: 394px;\n            padding: 10px 0 0 10px;\n            background: #000;\n            overflow: hidden;\n            border: 1px solid #fff;\n        }\n\n        .wrap li {\n            float: left;\n            margin: 0 10px 10px 0;\n\n        }\n\n        .wrap img {\n            display: block;\n            border: 0;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(window).ready(function () {\n            //需求：鼠标放入li中，其他的li半透明，当前盒子的opacity值为1\n            $(\".wrap\").find(\"li\").mouseenter(function () {\n                //链式编程\n                $(this).css(\"opacity\", 1).siblings(\"li\").css(\"opacity\", 0.4);\n            });\n\n            //离开wrap的时候所有的li的全部opacity值为1；\n            $(\".wrap\").mouseleave(function () {\n                $(this).children().children(\"li\").css(\"opacity\", 1);\n//                $(\".wrap li\").css(\"opacity\",1);\n            });\n        });\n    </script>\n</head>\n<body>\n<div class=\"wrap\">\n    <ul>\n        <li><a href=\"#\"><img src=\"images/01.jpg\" alt=\"\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/02.jpg\" alt=\"\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/03.jpg\" alt=\"\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/04.jpg\" alt=\"\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/05.jpg\" alt=\"\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/06.jpg\" alt=\"\"/></a></li>\n    </ul>\n</div>\n</body>\n</html>\n```\n\n\n实现的效果：\n\n![](http://img.smyhvae.com/20180205_1118_2.gif)\n\n注意这里的css布局里，每一个图片都用一个li来存放。设置li的父亲的宽度之后，然后将li设置为浮动，即可自适应地排列成两排。\n\n~工程文件~：\n\n- 2018-02-05-突出显示.rar\n\n- 下载链接暂无\n\n\n### 举例4：手风琴效果\n\n完整版代码：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        * {padding: 0;margin: 0;}\n        ul { list-style-type: none;}\n\n        .parentWrap {\n            width: 200px;\n            text-align:center;\n\n        }\n\n        .menuGroup {\n            border:1px solid #999;\n            background-color:#e0ecff;\n        }\n\n        .groupTitle {\n            display:block;\n            height:20px;\n            line-height:20px;\n            font-size: 16px;\n            border-bottom:1px solid #ccc;\n            cursor:pointer;\n        }\n\n        .menuGroup > div {\n            height: 200px;\n            background-color:#fff;\n            display:none;\n        }\n\n    </style>\n    <script src=\"jquery-1.11.1.min.js\"></script>\n    <script>\n        $(function () {\n            //需求：鼠标点击span，让他下面的div显示出来。让其他的div隐藏。\n            $(\".parentWrap span\").click(function () {\n//                $(this).next().show();\n//                //让其他的隐藏\n//                //点击的span的父亲li，的所有的兄弟元素li，的孩子元素div全部隐藏。\n//                $(this).parent(\"li\").siblings(\"li\").children(\"div\").hide();\n                //连式编程\n                $(this).next().show().parent(\"li\").siblings(\"li\").find(\"div\").hide();\n            });\n        })\n    </script>\n</head>\n<body>\n<ul class=\"parentWrap\">\n    <li class=\"menuGroup\">\n        <span class=\"groupTitle\">标题1</span>\n        <div>我是弹出来的div1</div>\n    </li>\n    <li class=\"menuGroup\">\n        <span class=\"groupTitle\">标题2</span>\n        <div>我是弹出来的div2</div>\n    </li>\n    <li class=\"menuGroup\">\n        <span class=\"groupTitle\">标题3</span>\n        <div>我是弹出来的div3</div>\n    </li>\n    <li class=\"menuGroup\">\n        <span class=\"groupTitle\">标题4</span>\n        <div>我是弹出来的div4</div>\n    </li>\n</ul>\n</body>\n</html>\n```\n\n\n效果：\n\n![](http://img.smyhvae.com/20180205_1120.gif)\n\n注意这里的 选择器的用法：parent、next\n\n### 举例5：淘宝精品服饰广告\n\n完整版代码：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        * {\n            margin: 0;\n            padding: 0;\n            font-size: 12px;\n        }\n\n        ul {\n            list-style: none;\n        }\n\n        a {\n            text-decoration: none;\n        }\n\n        .wrapper {\n            width: 298px;\n            height: 248px;\n            margin: 100px auto 0;\n            border: 1px solid pink;\n            overflow: hidden;\n        }\n\n        #left, #center, #right {\n            float: left;\n        }\n\n        #left li, #right li {\n            background: url(images/lili.jpg) repeat-x;\n        }\n\n        #left li a, #right li a {\n            display: block;\n            width: 48px;\n            height: 27px;\n            border-bottom: 1px solid pink;\n            line-height: 27px;\n            text-align: center;\n            color: black;\n        }\n\n        #left li a:hover, #right li a:hover {\n            background-image: url(images/abg.gif);\n        }\n\n        #center {\n            border-left: 1px solid pink;\n            border-right: 1px solid pink;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(function () {\n            //需求：鼠标放入两侧的li中，让中间的ul中对应索引值的li显示出来，其他的隐藏。（右侧的li要+9）\n            //左侧先绑。获取绑mouseenter\n            $(\"#left li\").mouseenter(function () {\n                //显示对应索引值的中间的li\n                //alert($(this).index());  获取索引值\n                $(\"#center li\").eq($(this).index()).show().siblings(\"li\").hide();\n            });\n\n            //右侧\n            $(\"#right li\").mouseenter(function () {\n                //显示对应索引值的中间的li\n                //alert($(this).index());  获取索引值\n                $(\"#center li:eq(\" + ($(this).index() + 9) + \")\").show().siblings(\"li\").hide();\n            });\n        });\n    </script>\n</head>\n<body>\n<div class=\"wrapper\">\n\n    <ul id=\"left\">\n        <li><a href=\"#\">女靴</a></li>\n        <li><a href=\"#\">雪地靴</a></li>\n        <li><a href=\"#\">冬裙</a></li>\n        <li><a href=\"#\">呢大衣</a></li>\n        <li><a href=\"#\">毛衣</a></li>\n        <li><a href=\"#\">棉服</a></li>\n        <li><a href=\"#\">女裤</a></li>\n        <li><a href=\"#\">羽绒服</a></li>\n        <li><a href=\"#\">牛仔裤</a></li>\n    </ul>\n    <ul id=\"center\">\n        <li><a href=\"#\"><img src=\"images/女靴.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/雪地靴.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/冬裙.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/呢大衣.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/毛衣.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/棉服.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/女裤.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/羽绒服.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/牛仔裤.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/女包.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/男包.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/登山鞋.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/皮带.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/围巾.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/皮衣.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/男毛衣.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/男棉服.jpg\" width=\"200\" height=\"250\"/></a></li>\n        <li><a href=\"#\"><img src=\"images/男靴.jpg\" width=\"200\" height=\"250\"/></a></li>\n    </ul>\n    <ul id=\"right\">\n        <li><a href=\"#\">女包</a></li>\n        <li><a href=\"#\">男包</a></li>\n        <li><a href=\"#\">登山鞋</a></li>\n        <li><a href=\"#\">皮带</a></li>\n        <li><a href=\"#\">围巾</a></li>\n        <li><a href=\"#\">皮衣</a></li>\n        <li><a href=\"#\">男毛衣</a></li>\n        <li><a href=\"#\">男棉服</a></li>\n        <li><a href=\"#\">男靴</a></li>\n    </ul>\n</div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180205_1135.gif)\n\n~工程文件~：\n\n- [2018-02-05-淘宝精品服饰广告.rar]()\n\n- 下载链接暂无。\n\n\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n"
  },
  {
    "path": "04-JavaScript基础/48-jQuery动画详解.md",
    "content": "---\ntitle: 48-jQuery动画详解\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n## 前言\n\njQuery提供的一组网页中常见的动画效果，这些动画是标准的、有规律的效果；同时还提供给我们了自定义动画的功能。\n\n## 显示动画\n\n方式一：\n\n```javascript\n\t$(\"div\").show();\n```\n\n解释：无参数，表示让指定的元素直接显示出来。其实这个方法的底层就是通过`display: block;`实现的。\n\n方式二：\n\n```javascript\n\t$(\"div\").show(2000);\n```\n\n解释：通过控制元素的宽高、透明度、display属性，逐渐显示，2秒后显示完毕。\n\n效果如下：\n\n![](http://img.smyhvae.com/20180205_1358.gif)\n\n方式三：\n\n```javascript\n\t$(\"div\").show(\"slow\");\n```\n\n参数可以是：\n\n- slow 慢：600ms\n\n- normal 正常：400ms\n\n- fast 快：200ms\n\n解释：和方式二类似，也是通过控制元素的宽高、透明度、display属性，逐渐显示。\n\n方式四：\n\n```javascript\n    //show(毫秒值，回调函数;\n    $(\"div\").show(5000,function () {\n        alert(\"动画执行完毕！\");\n    });\n```\n\n解释：动画执行完后，立即执行回调函数。\n\n**总结：**\n\n上面的四种方式几乎一致：参数可以有两个，第一个是动画的执行时长，第二个是动画结束后执行的回调函数。\n\n## 隐藏动画\n\n方式参照上面的show()方法的方式。如下：\n\n\n```javascript\n\t$(selector).hide();\n\n\t$(selector).hide(1000);\n\n\t$(selector).hide(\"slow\");\n\n\t$(selector).hide(1000, function(){});\n```\n\n**显示和隐藏的来回切换：**\n\n显示和隐藏的来回切换采用的是toggle()方法：就是先执行show()，再执行hide()。\n\n同样是四种方式：\n\n```javascript\n$(selector).toggle();\n\n```\n\n## 滑入和滑出\n\n**1、滑入动画效果**：（类似于生活中的卷帘门）\n\n\n```javascript\n\t$(selector).slideDown(speed, 回调函数);\n```\n\n解释：下拉动画，显示元素。\n\n注意：省略参数或者传入不合法的字符串，那么则使用默认值：400毫秒（同样适用于fadeIn/slideDown/slideUp）\n\n\n**2 滑出动画效果：**\n\n```javascript\n\t$(selector).slideUp(speed, 回调函数);\n```\n\n解释：上拉动画，隐藏元素。\n\n\n**3、滑入滑出切换动画效果：**\n\n```javascript\n\t$(selector).slideToggle(speed, 回调函数);\n```\n\n参数解释同show()方法。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 300px;\n            height: 300px;\n            display: none;\n            background-color: pink;\n        }\n    </style>\n\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //点击按钮后产生动画\n            $(\"button:eq(0)\").click(function () {\n\n                //滑入动画： slideDown(毫秒值，回调函数[显示完毕执行什么]);\n                $(\"div\").slideDown(2000, function () {\n                    alert(\"动画执行完毕！\");\n                });\n            })\n\n            //滑出动画\n            $(\"button:eq(1)\").click(function () {\n\n                //滑出动画：slideUp(毫秒值，回调函数[显示完毕后执行什么]);\n                $(\"div\").slideUp(2000, function () {\n                    alert(\"动画执行完毕！\");\n                });\n            })\n\n            $(\"button:eq(2)\").click(function () {\n                //滑入滑出切换（同样有四种用法）\n                $(\"div\").slideToggle(1000);\n            })\n\n        })\n    </script>\n</head>\n<body>\n<button>滑入</button>\n<button>滑出</button>\n<button>切换</button>\n<div></div>\n\n</body>\n</html>\n```\n\n![](http://img.smyhvae.com/20180205_1420.gif)\n\n## 淡入淡出动画\n\n1、淡入动画效果：\n\n```javascript\n\t$(selector).fadeIn(speed, callback);\n```\n\n作用：让元素以淡淡的进入视线的方式展示出来。\n\n2、淡出动画效果：\n\n```javascript\n\t$(selector).fadeOut(1000);\n```\n\n作用：让元素以渐渐消失的方式隐藏起来\n\n3、淡入淡出切换动画效果：\n\n\n```javascript\n\t$(selector).fadeToggle('fast', callback);\n```\n\n作用：通过改变透明度，切换匹配元素的显示或隐藏状态。\n\n参数的含义同show()方法。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 300px;\n            height: 300px;\n            display: none;\n            /*opacity: 1;*/\n            background-color: pink;\n        }\n    </style>\n\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //点击按钮后产生动画\n            $(\"button:eq(0)\").click(function () {\n//                //淡入动画用法1:   fadeIn();   不加参数\n                $(\"div\").fadeIn();\n\n//                //淡入动画用法2:   fadeIn(2000);   毫秒值\n//                $(\"div\").fadeIn(2000);\n//                //通过控制  透明度和display\n\n                //淡入动画用法3:   fadeIn(字符串);   slow慢：600ms   normal正常:400ms   fast快：200ms\n//                $(\"div\").fadeIn(\"slow\");\n//                $(\"div\").fadeIn(\"fast\");\n//                $(\"div\").fadeIn(\"normal\");\n\n                //淡入动画用法4:   fadeIn(毫秒值，回调函数[显示完毕执行什么]);\n//                $(\"div\").fadeIn(5000,function () {\n//                    alert(\"动画执行完毕！\");\n//                });\n            })\n\n            //滑出动画\n            $(\"button:eq(1)\").click(function () {\n//                //滑出动画用法1:   fadeOut();   不加参数\n//                $(\"div\").fadeOut();\n\n//                //滑出动画用法2:   fadeOut(2000);   毫秒值\n//                $(\"div\").fadeOut(2000);  //通过这个方法实现的：display: none;\n//                //通过控制  透明度和display\n\n                //滑出动画用法3:   fadeOut(字符串);   slow慢：600ms   normal正常:400ms   fast快：200ms\n//                $(\"div\").fadeOut(\"slow\");\n//                $(\"div\").fadeOut(\"fast\");\n//                $(\"div\").fadeOut(\"normal\");\n\n                //滑出动画用法1:   fadeOut(毫秒值，回调函数[显示完毕执行什么]);\n//                $(\"div\").fadeOut(2000,function () {\n//                    alert(\"动画执行完毕！\");\n//                });\n            })\n\n            $(\"button:eq(2)\").click(function () {\n                //滑入滑出切换\n                //同样有四种用法\n                $(\"div\").fadeToggle(1000);\n            })\n\n            $(\"button:eq(3)\").click(function () {\n                //改透明度\n                //同样有四种用法\n                $(\"div\").fadeTo(1000, 0.5, function () {\n                    alert(1);\n                });\n            })\n        })\n    </script>\n</head>\n<body>\n<button>淡入</button>\n<button>淡出</button>\n<button>切换</button>\n<button>改透明度为0.5</button>\n<div></div>\n\n</body>\n</html>\n```\n\n## 自定义动画\n\n```javascript\n\t$(selector).animate({params}, speed, callback);\n```\n\n作用：执行一组CSS属性的自定义动画。\n\n- 第一个参数表示：要执行动画的CSS属性（必选）\n\n- 第二个参数表示：执行动画时长（可选）\n\n- 第三个参数表示：动画执行完后，立即执行的回调函数（可选）\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            position: absolute;\n            left: 20px;\n            top: 30px;\n            width: 100px;\n            height: 100px;\n            background-color: pink;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(function () {\n            $(\"button\").click(function () {\n                var json = {\"width\": 500, \"height\": 500, \"left\": 300, \"top\": 300, \"border-radius\": 100};\n                var json2 = {\n                    \"width\": 100,\n                    \"height\": 100,\n                    \"left\": 100,\n                    \"top\": 100,\n                    \"border-radius\": 100,\n                    \"background-color\": \"red\"\n                };\n\n                //自定义动画\n                $(\"div\").animate(json, 1000, function () {\n                    $(\"div\").animate(json2, 1000, function () {\n                        alert(\"动画执行完毕！\");\n                    });\n                });\n\n            })\n        })\n    </script>\n</head>\n<body>\n<button>自定义动画</button>\n<div></div>\n</body>\n</html>\n```\n\n## 停止动画\n\n```javascript\n\t$(selector).stop(true, false);\n```\n\n**里面的两个参数，有不同的含义。**\n\n第一个参数：\n\n- true：后续动画不执行。\n\n- false：后续动画会执行。\n\n第二个参数：\n\n- true：立即执行完成当前动画。\n\n- false：立即停止当前动画。\n\nPS：参数如果都不写，默认两个都是false。实际工作中，直接写stop()用的多。\n\n**效果演示：**\n\n当第二个参数为true时，效果如下：\n\n![](http://img.smyhvae.com/20180205_1445.gif)\n\n当第二个参数为false时，效果如下：\n\n![](http://img.smyhvae.com/20180205_1450.gif)\n\n\n这个**后续动画**我们要好好理解，来看个例子。\n\n**案例：鼠标悬停时，弹出下拉菜单（下拉时带动画）**\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        * {\n            margin: 0;\n            padding: 0;\n        }\n\n        ul {\n            list-style: none;\n        }\n\n        .wrap {\n            width: 330px;\n            height: 30px;\n            margin: 100px auto 0;\n            padding-left: 10px;\n            background-color: pink;\n        }\n\n        .wrap li {\n            background-color: green;\n        }\n\n        .wrap > ul > li {\n            float: left;\n            margin-right: 10px;\n            position: relative;\n        }\n\n        .wrap a {\n            display: block;\n            height: 30px;\n            width: 100px;\n            text-decoration: none;\n            color: #000;\n            line-height: 30px;\n            text-align: center;\n        }\n\n        .wrap li ul {\n            position: absolute;\n            top: 30px;\n            display: none;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        //入口函数\n        $(document).ready(function () {\n            //需求：鼠标放入一级li中，让他里面的ul显示。移开隐藏。\n            var jqli = $(\".wrap>ul>li\");\n\n            //绑定事件\n            jqli.mouseenter(function () {\n                $(this).children(\"ul\").stop().slideDown(1000);\n            });\n\n            //绑定事件(移开隐藏)\n            jqli.mouseleave(function () {\n                $(this).children(\"ul\").stop().slideUp(1000);\n            });\n        });\n    </script>\n\n</head>\n<body>\n<div class=\"wrap\">\n    <ul>\n        <li>\n            <a href=\"javascript:void(0);\">一级菜单1</a>\n            <ul>\n                <li><a href=\"javascript:void(0);\">二级菜单1</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单2</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单3</a></li>\n            </ul>\n        </li>\n        <li>\n            <a href=\"javascript:void(0);\">一级菜单1</a>\n            <ul>\n                <li><a href=\"javascript:void(0);\">二级菜单1</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单2</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单3</a></li>\n            </ul>\n        </li>\n        <li>\n            <a href=\"javascript:void(0);\">一级菜单1</a>\n            <ul>\n                <li><a href=\"javascript:void(0);\">二级菜单1</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单2</a></li>\n                <li><a href=\"javascript:void(0);\">二级菜单3</a></li>\n            </ul>\n        </li>\n    </ul>\n</div>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180205_1500.gif)\n\n上方代码中，关键的地方在于，用了stop函数，再执行动画前，先停掉之前的动画。\n\n如果去掉stop()函数，效果如下：（不是我们期望的效果）\n\n![](http://img.smyhvae.com/20180205_1505.gif)\n\n### stop方法的总结\n\n当调用stop()方法后，队列里面的下一个动画将会立即开始。\n但是，如果参数clearQueue被设置为true，那么队列面剩余的动画就被删除了，并且永远也不会执行。\n\n如果参数jumpToEnd被设置为true，那么当前动画会停止，但是参与动画的每一个CSS属性将被立即设置为它们的目标值。比如：slideUp()方法，那么元素会立即隐藏掉。如果存在回调函数，那么回调函数也会立即执行。\n\n注意：如果元素动画还没有执行完，此时调用stop()方法，那么动画将会停止。并且动画没有执行完成，那么回调函数也不会被执行。\n\n\n## 举例：右下角的弹出广告\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        .ad {\n            position: fixed;\n            right: 0;\n            bottom: 0;\n            width: 230px;\n            height: 120px;\n            background-image: url(images/ad.jpg);\n            display: none;\n        }\n\n        .ad span {\n            position: absolute;\n            right: 0;\n            top: 0;\n            width: 40px;\n            height: 18px;\n            background-image: url(images/h.jpg);\n            cursor: pointer;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //需求：然广告ad部分，先滑入，在滑出，在淡入。然后绑定事件，点击span弹出\n            //步骤：\n            $(\".ad\").slideDown(1000).slideUp(1000).fadeIn(1000).children(\"span\").click(function () {\n                $(this).parent().fadeOut(1000);\n            });\n\n\n            //第二种写法\n//            $(\".ad\").slideDown(1000, function () {\n//                $(\".ad\").slideUp(1000, function () {\n//                    $(\".ad\").fadeIn(1000, function () {\n//                        $(\".ad\").children(\"span\").click(function () {\n//                            $(this).parent().fadeOut(1000, function () {\n//                                alert(\"执行完毕！\");\n//                            });\n//                        });\n//                    });\n//                });\n//            })\n        })\n    </script>\n</head>\n\n<body>\n\n<div class=\"ani\">我是内容</div>\n<div class=\"ad\">\n    <span></span>\n</div>\n\n</body>\n</html>\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180205_2000.gif)\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n\n"
  },
  {
    "path": "04-JavaScript基础/49-jQuery操作DOM.md",
    "content": "---\ntitle: 49-jQuery操作DOM\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 文本主要内容\n\n\n\n- 样式和类操作\n\n- 节点操作\n\n\n## 样式操作和类操作\n\n作用：设置或获取元素的样式属性值。\n\n### 样式操作\n\n**1、设置样式：**\n\n```javascript\n\n    //设置单个样式：  css(属性，值);\n    $(\"div\").css(\"background-color\",\"red\");\n\n    //设置多个样式：  css(json);\n\t $(\"div\").css({\"width\":100,\"height\":100,\"background-color\":\"pink\"});\n\n\n\n```\n\n\n**2、获取样式：**\n\n```javascript\n    //获取样式：css(属性);\n    //获取的时候如果有很多个，那么获取jquery对象中的第一个\n    alert($(\"div\").css(\"width\"));\n```\n\n举例如下：\n\n![](http://img.smyhvae.com/20180205_1315.png)\n\n### 类操作（className）\n\n**1、添加类样式：**\n\n```javascript\n\t$(selector).addClass(\"liItem\");  //为指定元素添加类className\n```\n\n注意：此处类名不带点，所有类操作的方法类名都不带点。\n\n\n**2、移除类样式：**\n\n\n```javascript\n\t$(selector).removeClass(\"liItem\");  //为指定元素移除类 className\n\t$(selector).removeClass();          //不指定参数，表示移除被选中元素的所有类\n```\n\n**3、判断有没有类样式：**\n\n```javascript\n\t$(selector).hasClass(\"liItem\");   //判断指定元素是否包含类 className\n```\n\n此时，会返回true或false。jquery对象中，只要有一个带有指定类名的就是true，所有都不带才是false。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            background-color: pink;\n        }\n\n        .current {\n            background-color: red;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            $(\"button\").eq(0).click(function () {\n                //添加类\n                $(\"div\").addClass(\"current\");\n                //判断类\n                alert($(\"div\").hasClass(\"current\"));\n            });\n\n            $(\"button\").eq(1).click(function () {\n                //删除类\n                $(\"div\").removeClass(\"current\");\n                //判断类\n                alert($(\"div\").hasClass(\"current\"));\n            });\n            //alert($(\"div\").hasClass(\"current\"));//jquery对象中只要有一个带有类名的就是true，所有都不带才是false。\n        })\n    </script>\n</head>\n<body>\n<button>添加</button>\n<button>删除</button>\n<div></div>\n<div></div>\n<div></div>\n<div class=\"current\"></div>\n</body>\n</html>\n\n```\n\n**4、切换类样式：**\n\n```javascript\n$(selector).toggleClass(“liItem”);    //为指定元素切换类 className，该元素有类则移除，没有指定类则添加。\n```\n\n解释：为指定元素切换类 className，该元素有类则移除，没有指定类则添加。\n\n如果采用采用正常的思路实现上面这句话，代码是：\n\n\n```javascript\n   if($(\"div\").hasClass(\"current\")){\n       //如果有类名，那么删除\n       $(\"div\").removeClass(\"current\")\n   }else{\n       //如果没有类名，那么添加\n       $(\"div\").addClass(\"current\")\n   }\n```\n\n现在有了toggleClass()方法，一行代码即可实现。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            background-color: green;\n        }\n        .current {\n            background-color: red;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            $(\"button\").click(function () {\n                //需求：点击按钮 ，切换背景\n                //切换类（toggleCLass）\n                $(\"div\").toggleClass(\"current\");\n            });\n        })\n    </script>\n</head>\n<body>\n<button>切换背景</button>\n<div></div>\n</body>\n</html>\n```\n\n实现的效果：\n\n![](http://img.smyhvae.com/20180205_1330.gif)\n\n### 样式操作和类操作的比较\n\n- 操作的样式非常少，那么可以通过`.css()`实现。\n\n- 操作的样式很多，建议通过使用类 class 的方式来操作。\n\n- 如果考虑以后维护方便（把CSS从js中分离出来）的话，推荐使用类的方式来操作。\n\n\n**举例**：addClass+removeClass\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        * {\n            margin: 0;\n            padding: 0;\n        }\n\n        ul {\n            list-style: none;\n        }\n\n        .wrapper {\n            width: 1000px;\n            height: 475px;\n            margin: 0 auto;\n            margin-top: 100px;\n        }\n\n        .tab {\n            border: 1px solid #ddd;\n            border-bottom: 0;\n            height: 36px;\n            width: 320px;\n        }\n\n        .tab li {\n            position: relative;\n            float: left;\n            width: 80px;\n            height: 34px;\n            line-height: 34px;\n            text-align: center;\n            cursor: pointer;\n            border-top: 4px solid #fff;\n        }\n\n        .tab span {\n            position: absolute;\n            right: 0;\n            top: 10px;\n            background: #ddd;\n            width: 1px;\n            height: 14px;\n            overflow: hidden;\n        }\n\n        .products {\n            width: 1002px;\n            border: 1px solid #ddd;\n            height: 476px;\n        }\n\n        .products .main {\n            float: left;\n            display: none;\n        }\n\n        .products .main.selected {\n            display: block;\n        }\n\n        .tab li.active {\n            border-color: red;\n            border-bottom: 0;\n        }\n\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(window).ready(function () {\n            //需求:鼠标放到那个li上，让该li添加active类，下面的对应的.main的div添加selected\n            $(\".tab>li\").mouseenter(function () {\n                //不用判断，当前的li添加active类，其他的删除active类\n                $(this).addClass(\"active\").siblings(\"li\").removeClass(\"active\");\n                //对应索引值的div添加selected类，其他的删除selected类\n                //【重要】根据tab的索引值获取下方图片div的索引值\n                $(\".products>div\").eq($(this).index()).addClass(\"selected\").siblings(\"div\").removeClass(\"selected\");\n            });\n        });\n    </script>\n</head>\n<body>\n<div class=\"wrapper\">\n    <ul class=\"tab\">\n        <li class=\"tab-item active\">国际大牌<span>◆</span></li>\n        <li class=\"tab-item\">国妆名牌<span>◆</span></li>\n        <li class=\"tab-item\">清洁用品<span>◆</span></li>\n        <li class=\"tab-item\">男士精品</li>\n    </ul>\n    <div class=\"products\">\n        <div class=\"main selected\">\n            <a href=\"###\"><img src=\"images/guojidapai.jpg\" alt=\"\"/></a>\n        </div>\n        <div class=\"main\">\n            <a href=\"###\"><img src=\"images/guozhuangmingpin.jpg\" alt=\"\"/></a>\n        </div>\n        <div class=\"main\">\n            <a href=\"###\"><img src=\"images/qingjieyongpin.jpg\" alt=\"\"/></a>\n        </div>\n        <div class=\"main\">\n            <a href=\"###\"><img src=\"images/nanshijingpin.jpg\" alt=\"\"/></a>\n        </div>\n    </div>\n</div>\n\n</body>\n</html>\n\n```\n\n\n上方代码中，我们注意，tab栏和下方图片栏的index值，一一对应，这里用得很巧妙。\n\n效果：\n\n![](http://img.smyhvae.com/20180205_1345.gif)\n\n\n\n## jQuery 的节点操作\n\n\n### 动态创建元素\n\n原生 js 有[三种动态创建元素](07-DOM操作练习：innerHTML的方式创建元素.md)的方式。这里我们学一下 jQuery 动态创建元素。**注意，创建的是  jQuery 对象**。\n\n方式一：\n\n```javascript\n\tvar $spanNode1 = $(\"<span>我是一个span元素</span>\");  // 返回的是 jQuery对象\n```\n\n此方法类似于 原生 js 中的`document.createElement(\"标签名\");`\n\n方式二：（推荐）\n\n\n```javascript\n\tvar node = $(\"#box\").html(\"<li>我是li</li>\");\n```\n\n此方法类似于 原生 js 中的`innerHTML`。\n\n举例：\n\n```javascript\n    //方式一：      $(\"标签\")             :类比于js中的document.createElement(\"li\");\n    console.log($(\"<li class='aaa'>我是li标签</li>\"));\n\n    //方式二：      $(\"ul\").html(\"\");     :类比innerHTML属性。因为此属性，识别标签。\n    $(\"ul\").html(\"<li>我是html方法穿件出来的li标签</li>\")\n```\n\n\n### 添加元素\n\njQuery 添加元素的方法非常多，最重要的方法是`append()`。格式如下：\n\n\n```javascript\n// 方式一：在$(selector)中追加$node\n$(selector).append($node);   //参数是 jQuery对象\n\n// 方式二：在$(selector)中追加div元素，\n$(selector).append('<div></div>');  //参数是 htmlString\n\n```\n\n\n作用：在被选元素内部的最后一个子元素（或内容）后面插入内容（存在或者创建出来的元素都可以）。\n\n\n通俗的解释：**在盒子里的最末尾添加元素**。\n\n- 如果是页面中存在的元素，那调用append()后，会把这个元素放到相应的目标元素里面去；但是，原来的这个元素，就不存在了。\n\n- 如果是给多个目标追加元素，那么方法的内部会复制多份这个元素，然后追加到多个目标里面去。\n\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(document).ready(function () {\n            $(\"button\").click(function () {\n                //创建一个新的jquery对象li\n                var jqNewLi = $(\"<li>我是jquery创建出来的li。用的是append方法添加</li>\");\n\n                //append();  在盒子里的最末尾添加与严肃\n                $(\"ul\").append(jqNewLi);    //把新创建的 li 塞进已知的 ul 中\n                //jqNewLi.appendTo($(\"ul\")); //方式二：把新创建的li塞进ul中。作用同上\n            })\n        });\n    </script>\n</head>\n<body>\n<button>添加li</button>\n\n<ul>\n    <li>我是土著li</li>\n</ul>\n\n</body>\n</html>\n```\n\n\n效果：\n\n![](http://img.smyhvae.com/20180205_2020.gif)\n\n\n**其他的添加元素的方法：**\n\n\n方法2：\n\n```javascript\n\t$(selector).appendTo(node);\n```\n\n作用：同append()，只不过是反着写的。\n\n方法3：\n\n```javascript\n\t$(selector).prepend(node);\n```\n\n作用：在元素的第一个子元素前面追加内容或节点。\n\n方法4：\n\n```javascript\n\t$(selector).after(node);\n```\n\n作用：在被选元素之后，作为**兄弟元素**插入内容或节点。\n\n**方法5：**\n\n```javascript\n\t$(selector).before(node);\n```\n\n作用：在被选元素之前，作为**兄弟元素**插入内容或节点。\n\n\n### 清空元素\n\n方式一：没有参数\n\n\n```javascript\n\t$(selector).empty();\n\t$(selector).html(\"\");\n```\n\n\n解释：清空指定元素的所有子元素（光杆司令）。\n\n方式二：\n\n//\n\n\n```javascript\n\t$(selector).remove();\n```\n\n解释：“自杀” 。把自己以及所有的内部元素从文档中删除掉。\n\n### 复制元素\n\n\n格式：\n\n\n```javascript\n\t复制的新元素 = $(selector).clone();\n```\n\n解释：复制$(selector)这个元素。是深层复制。\n\n### 总结\n\n推荐使用 `html(\"<span></span>\")` 方法来创建元素或者 `html(\"\")` 清空元素。\n\n### 案例：选择水果\n\n完整版代码：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        select {\n            width: 170px;\n            height: 240px;\n            font-size: 18px;\n            background-color: #a4ff34;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n\n            //步骤：\n            //1.将左侧的子标签全部移动到右侧。\n            $(\"button\").eq(0).click(function () {\n                //右侧标签.append(左侧标签);\n                $(\"#sel2\").append($(\"#sel1 option\"));\n            });\n\n            //2.将右侧的子标签全部移动到左侧。\n            $(\"button\").eq(1).click(function () {\n                //左侧标签.append(右侧标签);\n                $(\"#sel1\").append($(\"#sel2 option\"));\n            });\n\n            //第二步：获取子元素的时候要注意，获取的必须是，被选中的元素。\n            //技术点：怎么获取被选中的子元素呢？？？答案：option:selected;\n            //1.将左侧被选中的子标签移动到右侧\n            $(\"button\").eq(2).click(function () {\n                //右侧标签.append(左侧标签);\n                $(\"#sel2\").append($(\"#sel1 option:selected\"));\n            });\n\n            //2.将右侧被选中的子标签移动到左侧\n            $(\"button\").eq(3).click(function () {\n                //右侧标签.append(左侧标签);\n                $(\"#sel1\").append($(\"#sel2 option:selected\"));\n            });\n\n        })\n    </script>\n</head>\n<body>\n<select id=\"sel1\" size=\"10\" multiple>\n    <option value=\"0\">香蕉</option>\n    <option value=\"1\">苹果</option>\n    <option value=\"2\">鸭梨</option>\n    <option value=\"3\">葡萄</option>\n</select>\n<button>>>></button>\n<button><<<</button>\n<button>></button>\n<button><</button>\n<select id=\"sel2\" size=\"10\" multiple>\n\n</select>\n</body>\n</html>\n\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180205_2040.gif)\n\n\n### 案例：动态添加表格项\n\n代码如下：\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        table {\n            border-collapse: collapse;\n            border-spacing: 0;\n            border: 1px solid #c0c0c0;\n        }\n\n        th,\n        td {\n            border: 1px solid #d0d0d0;\n            color: #404060;\n            padding: 10px;\n        }\n\n        th {\n            background-color: #09c;\n            font: bold 16px \"微软雅黑\";\n            color: #fff;\n        }\n\n        td {\n            font: 14px \"微软雅黑\";\n        }\n\n        tbody tr {\n            background-color: #f0f0f0;\n        }\n\n        tbody tr:hover {\n            cursor: pointer;\n            background-color: #fafafa;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            // 模拟从后台拿到的数据\n            var data = [{\n                name: \"博客园\",\n                url: \"http://www.cnblogs.com/smyhvae/\",\n                type: \"程序员的精神家园\"\n            }, {\n                name: \"简书\",\n                url: \"https://www.jianshu.com/u/4d2e6b4bf117\",\n                type: \"写作平台\"\n            }, {\n                name: \"百度\",\n                url: \"https://www.baidu.com/\",\n                type: \"你就知道\"\n            }];\n\n            //需求:点击按钮，然后生成tr里面生成三个td.数组元素个数个tr。然后放入tbody中\n            //步骤：\n            //1.绑定事件\n            //2.利用for循环,把数组中的所有数据组合成一个字符串。\n            //3.把生成的字符串设置为html内容添加进tbody中。\n\n\n            //1.绑定事件\n            $(\"input\").click(function () {\n                //写入到click事件中，每次点击以后把之前的str清空【重要】\n                var str = \"\";\n                //2.利用for循环,把数组中的所有数据组合成一个字符串。\n                for(var i=0;i<data.length;i++){\n                    //如何生成3组？？？\n//                    str += \"<tr><td>1</td><td>2</td><td>3</td></tr>\";\n                    //data[i] = 数组中的每一个json\n                    //data[i].name = 数组中的每一个json的name属性值\n                    str += \"<tr><td>\"+data[i].name+\"</td><td>\"+data[i].url+\"</td><td>\"+data[i].type+\"</td></tr>\";\n                }\n\n                //3.把生成的字符串设置为html内容添加进tbody中。\n                $(\"#j_tbData\").html(str);\n            })\n        })\n    </script>\n</head>\n\n<body>\n<input type=\"button\" value=\"获取数据\" id=\"j_btnGetData\"/>\n<table>\n    <thead>\n    <tr>\n        <th>标题</th>\n        <th>地址</th>\n        <th>说明</th>\n    </tr>\n    </thead>\n    <tbody id=\"j_tbData\">\n    <!--<tr>-->\n    <!--<td>1</td>-->\n    <!--<td>2</td>-->\n    <!--<td>3</td>-->\n    <!--</tr>-->\n    </tbody>\n</table>\n</body>\n\n</html>\n\n```\n\n实现的效果：\n\n![](http://img.smyhvae.com/20180205_2045.gif)\n\n代码解释：每次生成字符串str之前，记得先把之前的str清空，不然每次点击按钮，都会继续添加表格项。\n\n\n### 将上一个案例进一步提升：点击按钮，添加数据\n\n暂略。\n\n## jQuery 设置和获取属性\n\njQuery 无法直接操作节点的属性和src等，我们需要借助attr()方法。下面介绍。\n\n### 属性操作\n\n**（1）设置属性：**\n\n```javascript\n\t$(selector).attr(\"title\", \"千古壹号\");\n```\n\n参数解释：第一个参数表示：要设置的属性名称。第二个参数表示：该属性名称对应的值。\n\n**（2）获取属性：**\n\n```javascript\n\t$(selector).attr(\"title\");\n```\n\n参数为：要获取的属性的名称，返回指定属性对应的值。\n\n**总结**：两个参数是给属性赋值，单个参数是获取属性值。\n\n\n**（3）移除属性：**\n\n\n```javascript\n\t$(selector).removeAttr(\"title\");\n```\n\n参数为：要移除的属性的名称。\n\n（4）form表单中的 `prop()`方法：\n\n针对`checked、selected、disabled`属性，要使用 `prop()`方法，而不是其他的方法。\n\nprop方法通常用来影响DOM元素的动态状态，而不是改变的HTML属性。例如：input和button的disabled特性，以及checkbox的checked特性。\n\n\n总结：细节可以参考：<http://api.jquery.com/prop/>。\n\n以上四项的代码演示：\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .aaa {\n            border: 1px solid red;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //获取元素，绑定属性\n            var jqinp = $(\"input\").eq(0);\n            var jqinp2 = $(\"input:checkbox\");\n            var jqbtn = $(\"button\");\n\n            jqbtn.click(function () {\n                //是绑定到jquery的衣服上，而不是标签上。所以没达到效果\n//                jqinp.title = 111;\n//                console.log(jqinp.title);\n\n                //绑定到标签上\n                jqinp.attr(\"title\", 111);\n                console.log(jqinp.attr(\"title\"));\n\n                jqinp.attr(\"aaa\", 111);\n                console.log(jqinp.attr(\"aaa\"));\n\n                //两个参数是给属性赋值，单个参数是获取属性值。\n                jqinp.attr(\"class\", \"aaa\");\n                console.log(jqinp.attr(\"class\"));\n\n                jqinp.removeAttr(\"class\");\n                console.log(jqinp.attr(\"class\"));\n\n                //form中的特殊属性，用prop\n                jqinp2.prop(\"checked\", true);\n//                jqinp2.attr(\"checked\",true);//一次性的。鼠标多点击几次，就失效了。\n\n            });\n        })\n    </script>\n</head>\n<body>\n<button>绑定</button>\n<input type=\"text\"/>\n<input type=\"checkbox\"/>\n\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180205_2115.gif)\n\n**案例：表格案例全选反选**\n\n完整版代码：\n\n```html\n<!DOCTYPE html>\n<html>\n\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n        }\n\n        .wrap {\n            width: 300px;\n            margin: 100px auto 0;\n        }\n\n        table {\n            border-collapse: collapse;\n            border-spacing: 0;\n            border: 1px solid #c0c0c0;\n        }\n\n        th,\n        td {\n            border: 1px solid #d0d0d0;\n            color: #404060;\n            padding: 10px;\n        }\n\n        th {\n            background-color: #09c;\n            font: bold 16px \"微软雅黑\";\n            color: #fff;\n        }\n\n        td {\n            font: 14px \"微软雅黑\";\n        }\n\n        tbody tr {\n            background-color: #f0f0f0;\n        }\n\n        tbody tr:hover {\n            cursor: pointer;\n            background-color: #fafafa;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //需求1：点击上面的多选按钮，下面的所有多选按钮都和上面的一致\n            //需求2：点击下面的多选按钮，判断下面的所有多选按钮都是否全部被选定只有全部被选定上面的才被选定\n\n\n            //需求1：点击上面的多选按钮，下面的所有多选按钮都和上面的一致\n            //步骤：绑定事件，直接让下面的所有按钮和上面的按钮属性值一模一样\n            $(\"#j_cbAll\").click(function () {\n                //直接让下面的所有按钮和上面的按钮属性值一模一样\n                $(\"#j_tb input:checkbox\").prop(\"checked\", $(this).prop(\"checked\"));\n            });\n\n            //需求2：点击下面的多选按钮，判断下面的所有多选按钮都是否全部被选定只有全部被选定上面的才被选定\n            //需求2：点击下面的多选按钮，判断下面的所有多选按钮都是否全部被选定只有全部被选定上面的才被选定\n            $(\"#j_tb input:checkbox\").click(function () {\n                //判断，只有所有都背选定，上面的才被选定\n                //技术点：带有checked属性的标签和checkbox个数一样多的时候\n                if ($(\"#j_tb input:checkbox\").length === $(\"#j_tb input:checked\").length) {\n                    //只有所有的都有checked属性，上面的才被选定\n                    $(\"#j_cbAll\").prop(\"checked\", true);\n                } else {\n                    $(\"#j_cbAll\").prop(\"checked\", false);\n                }\n            });\n\n\n        })\n    </script>\n</head>\n\n<body>\n<div class=\"wrap\">\n    <table>\n        <thead>\n        <tr>\n            <th>\n                <input type=\"checkbox\" id=\"j_cbAll\"/>\n            </th>\n            <th>课程名称</th>\n            <th>所属团队</th>\n        </tr>\n        </thead>\n        <tbody id=\"j_tb\">\n        <tr>\n            <td>\n                <input type=\"checkbox\"/>\n            </td>\n            <td>JavaScript</td>\n            <td>千古壹号</td>\n        </tr>\n        <tr>\n            <td>\n                <input type=\"checkbox\"/>\n            </td>\n            <td>css</td>\n            <td>千古壹号</td>\n        </tr>\n        <tr>\n            <td>\n                <input type=\"checkbox\"/>\n            </td>\n            <td>html</td>\n            <td>千古壹号</td>\n        </tr>\n        <tr>\n            <td>\n                <input type=\"checkbox\"/>\n            </td>\n            <td>jQuery</td>\n            <td>千古壹号</td>\n        </tr>\n        </tbody>\n    </table>\n</div>\n</body>\n\n</html>\n\n```\n\n\n### val()方法和 text()方法\n\n\n```javascript\n\t$(selector).val();\n```\n\n作用：设置或返回 form 表单元素的value值，例如：input、select、textarea 的值。\n\n\n```javascript\n\t$(selector).text();\n```\n\n作用：设置或获取匹配元素的文本内容。不带参数表示，会把所有匹配到的元素内容拼接为一个**字符串**，不同于其他获取操作。\n\n\n\n\n```javascript\n\t$(selector).text(\"我是内容\");\n```\n\n\n作用：设置的内容包含html标签，那么text()方法会把他们当作**纯文本**内容输出。\n\n总结：\n\n- text() 不识别标签。\n\n- html() 识别标签。\n\n举例：\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(document).ready(function () {\n            //val();   获取标签中的value属性的值。带有参数是赋值(类比js中的value属性)\n            console.log($(\"input\").val());\n\n            $(\"input\").val(\"我是value()赋值的input内容\");\n            console.log($(\"input\").val());\n\n            console.log(\"-----------------\");\n\n            //text();  获取双闭合标签中的文本值。（不识别标签）(类比js中的innerText)\n            console.log($(\"div\").text());\n            $(\"div\").text(\"<li>我是text()赋值的</li>\")\n            console.log($(\"div\").text());\n\n            console.log(\"-----------------\");\n\n            //html();  获取双闭合标签中的文本值。（识别标签）(类比js中的innerHTML)\n            console.log($(\"div\").html());\n            $(\"div\").html(\"<li>我是html()赋值的</li>\");\n            console.log($(\"div\").html());\n        })\n    </script>\n</head>\n<body>\n<input type=\"text\" value=\"我是input中已存在的 value内容\"/>\n<div>\n    <li>你好</li>\n</div>\n</body>\n</html>\n```\n\n\n打印结果：\n\n![](http://img.smyhvae.com/20180205_2139.png)\n\n\n"
  },
  {
    "path": "04-JavaScript基础/50-jQuery的事件机制和其他知识.md",
    "content": "---\ntitle: 50-jQuery的事件机制和其他知识\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## jQuery 设置宽度和高度\n\n宽度操作：\n\n```javascript\n\t$(selector).height();     //不带参数表示获取高度\n\t$(selector).height(200);  //带参数表示设置高度\n```\n\n\n宽度操作：\n\n\n```javascript\n\t$(selector).width();     //不带参数表示获取宽度\n\t$(selector).width(200);  //带参数表示设置高宽度\n```\n\n**问题**：jQuery的css()获取高度，和jQuery的height获取高度，二者的区别？\n\n答案：\n\n\n```javascript\n\t$(\"div\").css();     //返回的是string类型，例如：30px\n\n\t$(\"div\").height();  //返回得失number类型，例如：30。常用于数学计算。\n```\n\n如上方代码所示，`$(\"div\").height();`返回的是number类型，常用于数学计算。\n\n\n## jQuery 的坐标操作\n\n### offset()方法\n\n\n```javascript\n\t$(selector).offset();\n\t$(selector).offset({left:100, top: 150});\n```\n\n作用：获取或设置元素相对于 document 文档的位置。参数解释：\n\n- 无参数：表示获取。返回值为：{left:num, top:num}。返回值是相对于document的位置。\n\n- 有参数：表示设置。参数建议使用 number 数值类型。\n\n注意：设置offset后，如果元素没有定位(默认值：static)，则被修改为relative。\n\n### position()方法\n\n```javascript\n\t$(selector).position();\n```\n\n作用：获取相对于其最近的**带有定位**的父元素的位置。返回值为对象：`{left:num, top:num}`。\n\n注意：只能获取，不能设置。\n\n### scrollTop()方法\n\n```javascript\n\tscrollTop();\n\t$(selector).scrollTop(100);\n```\n\n作用：获取或者设置元素被卷去的头部的距离。参数解释：\n\n- 无参数：表示获取偏移。\n\n- 有参数：表示设置偏移，参数为数值类型。\n\n\n### scrollLeft()方法\n\n```javascript\n\tscrollLeft();\n\t$(selector).scrollLeft(100);\n```\n\n作用：获取或者设置元素水平方向滚动的位置。参数解释：\n\n- 无参数：表示获取偏移。\n\n- 有参数：表示设置偏移，参数为数值类型。\n\n代码示范：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        body {\n            height: 5000px;\n        }\n\n        .box1 {\n            width: 300px;\n            height: 300px;\n            position: relative;\n            margin: 10px;\n            overflow: auto;\n            background-color: pink;\n        }\n\n        .box2 {\n            width: 200px;\n            height: 400px;\n            position: absolute;\n            top: 50px;\n            left: 50px;\n            background-color: yellow;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //距离页面最顶端或者最左侧的距离和有没有定位没有关系\n            $(\"button\").eq(0).click(function () {\n                alert($(\".box2\").offset().top);\n            })\n\n            //距离页面最顶端或者最左侧的距离和有没有定位没有关系\n            $(\"button\").eq(1).click(function () {\n                $(\".box2\").offset({\"left\": 1000, \"top\": 1000});\n            })\n\n            //距离父系盒子中带有定位的盒子的距离(获取的就是定位值，和margin/padding无关)\n            $(\"button\").eq(2).click(function () {\n                alert($(\".box2\").position().top);\n            })\n\n            //距离父系盒子中带有定位的盒子的距离(获取的就是定位值，和margin/padding无关)\n            $(\"button\").eq(3).click(function () {\n                $(\".box2\").position().top = \"100px\";\n            })\n\n            //获取被选取的头部\n            $(\"button\").eq(4).click(function () {\n                alert($(window).scrollTop());\n            })\n\n            //获取被选取的头部\n            $(\"button\").eq(5).click(function () {\n                $(window).scrollTop(100);\n            })\n\n        })\n    </script>\n\n</head>\n<body>\n\n\n<div class=\"box1\">\n    <div class=\"box2\"></div>\n</div>\n\n<button>offset().top获取</button>\n<button>offset().top设置</button>\n<button>position().top获取</button>\n<button>position().top设置</button>\n<button>scrollTop()</button>\n<button>scrollTop()</button>\n\n</body>\n</html>\n```\n\n## jQuery的事件机制\n\n### 常见的事件绑定\n\n- click(handler) \t\t\t\t单击事件。\n\n- blur(handler) \t\t\t\t失去焦点事件。\n\n- mouseenter(handler) \t\t鼠标进入事件。\n\n- mouseleave(handler)\t\t\t鼠标离开事件。\n\n- dbclick(handler) \t\t\t双击事件。\n\n- change(handler)  改变事件，如：文本框值改变，下拉列表值改变等。\n\n- focus(handler) \t\t\t\t获得焦点事件。\n\n- keydown(handler) \t\t\t键盘按下事件。\n\n参考链接：<http://www.w3school.com.cn/jquery/jquery_ref_events.asp>\n\n### on方式绑定事件\n\n最早采用的是 bind、delegate等方式绑定的。jQuery 1.7版本后，jQuery用on统一了所有的事件处理的方法，此方法兼容zepto(移动端类似于jQuery的一个库)。\n\n格式举例：\n\n\n```javascript\n        $(document).on(\"click mouseenter\", \".box\", {\"name\": 111}, function (event) {\n            console.log(event.data);      //event.data获取的就是第三个参数这个json。\n            console.log(event.data.name); //event.data.name获取的是name的值。\n        });\n```\n\n参数解释：\n\n- 第一个参数：events，绑定事件的名称可以是由空格分隔的多个事件（标准事件或者自定义事件）。上方代码绑定的是单击事件和鼠标进入事件。\n\n- 第二个参数：selector, 执行事件的后代元素。\n\n- 第三个参数：data，传递给事件处理函数的数据，事件触发的时候通过event.data来使用（也就是说，可以通过event拿到data）\n\n- 第四个参数：handler，事件处理函数。\n\n\n简单点的写法：\n\n```javascript\n    $(document).on(\"click\",\".box\", function () {\n       alert(1);\n    });\n```\n\n### off方式解绑事件\n\n```javascript\n    $(selector).off();      // 解绑匹配元素的所有事件\n\n    $(selector).off(\"click\");   // 解绑匹配元素的所有click事件\n\n    $(selector).off( \"click\", \"**\" );   // 解绑所有代理的click事件，元素本身的事件不会被解绑\n```\n\n## jQuery的事件对象\n\nevent.data                  传递给事件处理程序的额外数据\n\nevent.currentTarget             等同于this，当前DOM对象\n\nevent.pageX                     鼠标相对于文档左部边缘的位置\n\nevent.target                    触发事件源，不一定===this\n\nevent.stopPropagation()；    阻止事件冒泡\n\nevent.preventDefault();     阻止默认行为\n\nevent.type                  事件类型：click，dbclick…\n\nevent.which                     鼠标的按键类型：左1 中2 右3\n\nevent.keyCode               键盘按键代码\n\n\n代码演示：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            $(document).on(\"click\", {}, function (event) {\n                console.log(event.data);\n                console.log(event.currentTarget);\n                console.log(event.target);\n                console.log(event.pageX);\n                console.log(event.type);\n                console.log(event.which);\n                console.log(event.keyCode);\n            });\n        })\n    </script>\n</head>\n<body>\n\n</body>\n</html>\n```\n\n上方代码中，我们通过event参数，获取了点击事件的各种event里的属性。\n\n单击网页后，打印结果为：\n\n![](http://img.smyhvae.com/20180205_2338.png)\n\n**举例**：键盘上对的按键按下时，变色\n\n这个时候就要用到event参数，因为要获取event.keyCode，才能知道按下的是键盘上的哪个按键。\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .wrap {\n            width: 400px;\n            height: 400px;\n            margin: 100px auto 0;\n        }\n\n        .wrap h1 {\n            text-align: center;\n        }\n\n        .wrap div {\n            width: 400px;\n            height: 300px;\n            background: pink;\n            font-size: 30px;\n            text-align: center;\n            line-height: 300px;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            //需求：在页面上，按键.是哪个颜色的首写字母div就改变为该颜色，然后span内容赋值。\n            //步骤：\n            //1.给document绑定keyup事件\n            //2.获取值，根据此值，给div和span上色和内容\n\n            var div = $(\"#bgChange\");\n            var span = $(\"#keyCodeSpan\");\n\n            //绑定事件\n            $(document).keyup(function (e) {  //键盘弹出时，触发事件\n                //调用方法\n                setColor(e.keyCode);\n            });\n\n\n            function setColor(keyCode) {\n                //alert(e.keyCode);\n                //2.获取值，根据此值，给div和span上色和内容\n                switch (keyCode) {\n                    case 80:\n                        //修改内容pink\n                        setEle(\"pink\", keyCode);\n                        break;\n                    case 66:\n                        //修改内容blue\n                        setEle(\"blue\", keyCode);\n                        break;\n                    case 79:\n                        //修改内容orange\n                        setEle(\"orange\", keyCode);\n                        break;\n                    case 82:\n                        //修改内容red\n                        setEle(\"red\", keyCode);\n                        break;\n                    case 89:\n                        //修改内容yellow\n                        setEle(\"yellow\", keyCode);\n                        break;\n                    default :\n                        alert(\"系统没有设置该颜色！\");\n                }\n\n                function setEle(a, b) {\n                    div.css(\"background-color\", a);\n                    span.text(b);\n                }\n\n            }\n\n            //1.给document绑定keyup事件\n//            $(document).keyup(function (e) {\n//                //alert(e.keyCode);\n//                //2.获取值，根据此值，给div和span上色和内容\n//                switch (e.keyCode){\n//                    case 80:\n//                        //修改内容pink\n//                        div.css(\"background-color\",\"pink\");\n//                        span.text(e.keyCode);\n//                        break;\n//                    case 66:\n//                        //修改内容blue\n//                        div.css(\"background-color\",\"blue\");\n//                        span.text(e.keyCode);\n//                        break;\n//                    case 79:\n//                        //修改内容orange\n//                        div.css(\"background-color\",\"orange\");\n//                        span.text(e.keyCode);\n//                        break;\n//                    case 82:\n//                        //修改内容red\n//                        div.css(\"background-color\",\"red\");\n//                        span.text(e.keyCode);\n//                        break;\n//                    case 89:\n//                        //修改内容yellow\n//                        div.css(\"background-color\",\"yellow\");\n//                        span.text(e.keyCode);\n//                        break;\n//                    default :\n//                        alert(\"系统没有设置该颜色！\");\n//                }\n//            });\n        })\n    </script>\n</head>\n\n<body>\n\n<div class=\"wrap\">\n    <h1>按键改变颜色</h1>\n    <div id=\"bgChange\">\n        keyCode为：\n        <span id=\"keyCodeSpan\">80</span>\n    </div>\n</div>\n\n</body>\n</html>\n\n```\n\n\n## jQuery 的两大特点\n\n（1）**链式编程**：比如`.show()`和`.html()`可以连写成`.show().html()`。\n\n\n链式编程原理：return this。\n\n通常情况下，只有设置操作才能把链式编程延续下去。因为获取操作的时候，会返回获取到的相应的值，无法返回 this。\n\n```javascript\n    end(); // 结束当前链最近的一次过滤操作，并且返回匹配元素之前的状态。\n```\n\n\n（2）**隐式迭代**：隐式 对应的是 显式。隐式迭代的意思是：在方法的内部会为匹配到的所有元素进行循环遍历，执行相应的方法；而不用我们再进行循环，简化我们的操作，方便我们调用。\n\n如果获取的是多元素的值，大部分情况下返回的是第一个元素的值。\n\n\n**举例：**五角星评分\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>五角星评分案例</title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n        }\n\n        .comment {\n            font-size: 40px;\n            color: #ff3100;\n        }\n\n        .comment li {\n            float: left;\n            cursor: pointer;\n        }\n\n        ul {\n            list-style: none;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        $(function () {\n            var star_none = '☆'; // 空心五角星\n            var star_sel = '★'; // 实心五角星\n\n            //需求1：鼠标放悬停时，当前的li和之前所有的li内容全部变为实心五角星，移开变为空心。\n            $(\".comment li\").on(\"mouseenter\", function () {\n                //当前五角星，和之前的所有五角星，全部是实心的，其他的为空心\n\n                //写法一：分两次写\n//                $(this).text(star_sel).prevAll(\"li\").text(star_sel);\n//                $(this).nextAll(\"li\").text(star_none);\n                //写法二：一次性写完，但要用到end()，否则会出问题。【重要】\n                $(this).text(star_sel).prevAll(\"li\").text(star_sel).end().nextAll(\"li\").text(star_none);\n            });\n\n            $(\".comment li\").on(\"mouseleave\", function () {\n                //bug：如果没有点击过li，那么会出现无法清除的现象，处理办法就是先判断，看看是否有current类\n                if ($(\"li.current\").length === 0) {\n                    $(\".comment li\").text(star_none);\n                } else {\n                    //当鼠标移开的时候，谁有current类名，那么当前和之前所有的li前部是实心五角星，后面的所有li都是空心\n                    $(\"li.current\").text(star_sel).prevAll(\"li\").text(star_sel).end().nextAll(\"li\").text(star_none);\n                }\n            });\n\n\n            //需求2：鼠标点击那个li，当你前li和之前所有的li都变成实心五角星，其他变为空心。\n            $(\".comment li\").on(\"click\", function () {\n                //点击哪个li给他加一个类名。清空其他所有的li的类名\n                $(this).attr(\"class\", \"current\").siblings(\"li\").removeAttr(\"class\");\n            });\n\n\n        });\n    </script>\n</head>\n<body>\n<ul class=\"comment\">\n    <li>☆</li>\n    <li>☆</li>\n    <li>☆</li>\n    <li>☆</li>\n    <li>☆</li>\n</ul>\n</body>\n</html>\n\n```\n\n\n上方代码中，注意end的用法，很巧妙。\n\n实现效果：\n\n![](http://img.smyhvae.com/20180206_1100.gif)\n\n## each的用法\n\n大部分情况下是不需要使用each方法的，因为jQuery的隐式迭代特性。\n\n但是，如果要对每个元素做不同的处理，这时候就用到了each方法。\n\n格式如下：\n\n\n```javascript\n    $(selector).each(function(index,element){});\n```\n\n参数解释：\n\n- 参数一：表示当前元素在所有匹配元素中的索引号\n\n- 参数二：参数二表示当前元素（是js 中的DOM对象，而不是jQuery对象）\n\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        li {\n            width: 100px;\n            height: 100px;\n            margin: 20px;\n            float: left;\n            list-style: none;\n            text-align: center;\n            font: 50px/100px \"simsun\";\n            color: white;\n            background-color: black;\n        }\n    </style>\n\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script>\n        jQuery(function () {\n            //设置每个不一样的盒子透明度逐渐递增\n            $(\"ul li\").each(function (index, element) {\n                $(element).css(\"opacity\", (index + 1) / 10);\n                console.log(index+\"---\"+element.tagName);\n\n            });\n        });\n    </script>\n</head>\n<body>\n<ul>\n    <li class=\"aaa1\">1</li>\n    <li class=\"aaa2\">2</li>\n    <li class=\"aaa3\">3</li>\n    <li class=\"aaa4\">4</li>\n    <li class=\"aaa5\">5</li>\n    <li class=\"aaa6\">6</li>\n    <li class=\"aaa7\">7</li>\n    <li class=\"aaa8\">8</li>\n    <li class=\"aaa9\">9</li>\n    <li class=\"aaa10\">10</li>\n</ul>\n\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180206_1110.png)\n\n## 多库共存\n\n**多库共存**指的是：jQuery占用了 `$` 和 `jQuery` 这两个变量。当在同一个页面中引用了 jQuery 库以及其他的库（或者其他版本的jQuery库），恰好其他的库中也用到了 `$` 或者`jQuery`变量.那么，要保证每个库都能正常使用，就产生了多库共存的问题。\n\n温馨提示：我们可以通过以下方式获取 jQuery 库的版本号。\n\n```javascript\n    console.log($.fn.jquery);  //打印 jQuery 库的版本号\n```\n\n**办法一**：让 jQuery 放弃对 `$` 的使用权：\n\n```javascript\n    $.noConflict();\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180206_1126.png)\n\n\n上图中，代码中同时包含了两个版本的库。1.11.1版本放弃了对 `$` 的使用权，交给了1.8.2版本；但是1.11.1版本并没有放弃对 `jQuery`关键字的使用权。\n\n\n办法二：同时放弃放弃两个符号的使用权，并定义一个新的使用权（如果有三个库时，可以这样用）\n\n```javascript\n    $.noConflict(true);   //返回值是新的关键字\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180206_1133.png)\n\n\n## jQuery 的插件机制\n\njQuery 库，虽然功能强大，但也不是面面俱到。jQuery 是通过插件的方式，来扩展它的功能：\n\n- 当你需要某个插件的时候，你可以“安装”到jQuery上面，然后使用。\n\n- 当你不再需要这个插件，那你就可以从jQuery上“卸载”它。\n\n\n### 插件之改变颜色\n\njQuery的自定义动画方法animate()，在执行动画时，是不支持设置背景色这个属性的。这个时候可以借助`jQuery.color.js`这个插件。\n\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            background-color: blue;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <script src=\"jquery.color.js\"></script>\n    <script>\n        $(function () {\n            //点击按钮，改变盒子的宽度和背景色\n            $(\"button\").on(\"click\", function () {\n                $(\"div\").animate({\"width\": 200, \"background-color\": \"red\"}, 2000, function () {\n                    alert(\"动画结束\");\n                });\n            });\n        })\n    </script>\n</head>\n<body>\n<button>变色</button>\n<div></div>\n</body>\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180206_1400.gif)\n\n上方代码中，因为加入了一行插件：（注意顺序是放在jQuery插件之后）\n\n```html\n    <script src=\"jquery.color.js\"></script>\n```\n\n否则的话，在动画执行的过程中，是无法设置背景色的。\n\n\n### 插件之懒加载\n\n懒加载：当打开一个网页时，只有当我看到某个部分，再加载那个部分；而不是一下子全部加载完毕。这样可以优化打开的速度。\n\n比如说，我可以设置一张图片为懒加载，于是，这张图片会等我宠幸到它的时候，它再打开。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        div {\n            height: 3000px;\n            background-color: pink;\n        }\n    </style>\n    <script src=\"jquery-1.11.1.js\"></script>\n    <!--懒加载的使用。第一步：导包(必须在jquery库的下方）-->\n    <script src=\"jquery.lazyload.js\"></script>\n    <script>\n        $(function () {\n\n\n            //第二步骤：调用懒加载的方法实现功能。参数的不同，功能也不同。\n            $(\"img.lazy\").lazyload();\n        })\n    </script>\n</head>\n<body>\n<div></div>\n<!--需要实现将图片设置为懒加载模式-->\n<img class=\"lazy\" data-original=\"images/01.jpg\" width=\"640\" height=\"480\">\n</body>\n</html>\n```\n\n"
  },
  {
    "path": "04-JavaScript基础/51-Zepto入门.md",
    "content": "---\r\ntitle: 51-Zepto入门\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n\r\n\r\n\r\n\r\n## Zepto 的介绍\r\n\r\n### 什么是 Zepto\r\n\r\nzepto是轻量级的JavaScript库，专门为移动端定制的框架。\r\n\r\n与jquery有着类似的API，俗称：会jquery就会用zepto\r\n\r\n\r\n\r\n### zepto的特点\r\n\r\n- 针对移动端\r\n\r\n- 轻量级，压缩版本只有8kb左右\r\n\r\n- 响应，执行快\r\n\r\n- 语法、API大部分同jquery一样，学习难度低，上手快。\r\n\r\n- 目前API完善的框架中体积最小的一个\r\n\r\n\r\n### 相关网址\r\n\r\n- 官网：<http://zeptojs.com/>\r\n\r\n- GitHub：<https://github.com/madrobby/zepto>\r\n\r\n\r\n## Zepto 与 jQuery 的前世今生\r\n\r\n### 相同点\r\n\r\n- 都是优秀的js函数库\r\n\r\n- 语法、API大部分都一样（zepto是按照jquery的思路来设计的）\r\n\r\n- Zepto 相当于 jQuery 的子集\r\n\r\n- 同jQuery一样，都是以`$`符号为核心函数。\r\n\r\n\r\n### 不同点\r\n\r\n\r\n## Zepto 的初体验\r\n\r\n（1）Zepto 库的下载：\r\n\r\n我们去官网下载 Zepto的开发版本`zepto.js`：\r\n\r\n![](http://img.smyhvae.com/20180414_2210.png)\r\n\r\n官网里，还有这样一张图：\r\n\r\n![](http://img.smyhvae.com/20180414_2215.png)\r\n\r\n上图的意思是：\r\n\r\n- 最前面打钩的那五个api，已经包含在`zepto.js `文件里了；\r\n\r\n- 后面没有打钩的那些api，如果需要用它们，必须单独下载响应的文件。\r\n\r\n比如说，移动端的 touch 事件是很常见的，我们可以将`touch.js`这个文件下载，稍后用。\r\n\r\n\r\n（2）代码演示：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <style>\r\n        #btn {\r\n            width: 200px;\r\n            height: 200px;\r\n            background: pink;\r\n            margin: 10px auto;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"btn\">我是 div</div>\r\n    <script src=\"libs/zepto1.2.0.js\"></script>\r\n    <script src=\"libs/touch.js\"></script>\r\n    <script>\r\n        $(function () {\r\n            $('#btn').on('touchstart', function () {\r\n                alert('hello world');\r\n            });\r\n        });\r\n\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n上方代码实现的效果是，当手在div上滑动时，就会弹出 alert窗。可以看出，这里面代码的写法和 jQuery 是一致的。\r\n\r\n注意，我们要将浏览器切换到手机模式，才能看到`touchstart`事件的效果；否则，在浏览器上点来点去，是没有反应的。\r\n\r\n## Zepto 和 jQuery 相同的  api\r\n\r\n> 意思是，jQuery 和 Zepto 有哪些共同点。\r\n\r\n\r\n###  jQuery 的主要特性\r\n\r\n下面来讲一下 jQuery 的主要特性（jQuery 的核心函数`$`、jQuery 对象），它们对 Zepto 来说，同样适用。\r\n\r\n**1、jQuery 的核心函数`$`**:\r\n\r\n作为函数使用（参数）：\r\n\r\n-  function\r\n\r\n-  html字符串\r\n\r\n-  DOM code\r\n\r\n-  选择器字符串\r\n\r\n作为对象调用(方法)：\r\n\r\n- $.ajax() $.get() $.post()\r\n\r\n- $.isArray()      $.each()      $.isFunction()      $.trim()\r\n\r\n**2、jQuery 对象**：\r\n\r\n概念：jquery核心函数$()调用返回的对象就是jquery对象的数组（可能有只有一个）。\r\n\r\n使用列举：\r\n\r\n- addClass()\r\n\r\n- removeClass()\r\n\r\n- show()\r\n\r\n- find()\r\n\r\n### 代码举例\r\n\r\n1、`$.each()`方法举例：（遍历数组）\r\n\r\n```html\r\n    <script src=\"libs/zepto-1.2.0.js\"></script>\r\n    <script src=\"libs/zepto-1.2.0.js\"></script>\r\n    <script>\r\n        var arr = [2, 4, 6, 8];\r\n\r\n        $.each(arr, function (index, item) {\r\n            console.log(index, item);\r\n        });\r\n    </script>\r\n\r\n```\r\n\r\n打印结果：\r\n\r\n![](http://img.smyhvae.com/20180416_1145.png)\r\n\r\n2、`append()`举例：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <style>\r\n        .box1 {\r\n            width: 200px;\r\n            height: 200px;\r\n            background: pink;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div class=\"box1\"></div>\r\n\r\n    <script src=\"libs/zepto-1.2.0.js\"></script>\r\n    <script src=\"libs/touch.js\"></script>\r\n    <script>\r\n        $('.box1').on('touchstart', function () {\r\n            $('.box1').append('<p>我是新添加的元素</p>');\r\n\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n上方代码实现的效果是：每次，当手在box1上滑动时，会在 box1 中新添加一个元素。\r\n\r\n\r\n4、`find()`方法举例：\r\n\r\n```javascript\r\n        $('.box1').on('touchstart', function () {\r\n            console.log('touch');\r\n            $(this).find('p').css('background', 'red');\r\n        });\r\n```\r\n\r\n代码解释：找到 box1 中的 p 标签， 给 p 标签设置背景色。\r\n\r\n注意，代码里的`$(this).find()`相当于`this.find`，只不过this没有find方法，而$有find方法。\r\n\r\n\r\n\r\n5、`show()`方法举例：\r\n\r\n```javascript\r\n        $(`.box1`).on('touchstart', function () {\r\n            $('.box2').show();\r\n        });\r\n```\r\n\r\n假设 box2 一开始是隐藏的，事件中，让 box2 显示出来。\r\n\r\n\r\n\r\n"
  },
  {
    "path": "04-JavaScript基础/BOM的常见内置方法和内置对象.md",
    "content": "---\npublish: false\n---\n\n\n\n\n> 本文最初发表于[博客园](http://www.cnblogs.com/smyhvae/p/8401662.html)，并在[GitHub](https://github.com/qianguyihao/Web)上持续更新**前端的系列文章**。欢迎在GitHub上关注我，一起入门和进阶前端。\n\n> 以下是正文。\n\n\n## BOM的介绍\n\n### JavaScript的组成\n\nJavaScript基础分为三个部分：\n\n- ECMAScript：JavaScript的语法标准。包括变量、表达式、运算符、函数、if语句、for语句等。\n\n- **DOM**：文档对象模型，操作**网页上的元素**的API。比如让盒子移动、变色、轮播图等。\n\n- **BOM**：浏览器对象模型，操作**浏览器部分功能**的API。比如让浏览器自动滚动。\n\n### 什么是BOM\n\nBOM：Browser Object Model，浏览器对象模型。\n\n**BOM的结构图：**\n\n![](http://img.smyhvae.com/20180201_2052.png)\n\n从上图也可以看出：\n\n- **window对象是BOM的顶层(核心)对象**，所有对象都是通过它延伸出来的，也可以称为window的子对象。\n\n- DOM越是BOM的一部分。\n\n**window对象：**\n\n- **window对象是JavaScript中的顶级对象**。\n\n- 全局变量、自定义函数也是window对象的属性和方法。\n\n- window对象下的属性和方法调用时，可以省略window。\n\n下面讲一下 **BOM 的常见内置方法和内置对象**。\n\n## 弹出系统对话框\n\n比如说，`alert(1)`是`window.alert(1)`的简写，因为它是window的子方法。\n\n系统对话框有三种：\n\n```javascript\n\talert();\t//不同浏览器中的外观是不一样的\n\tconfirm();  //兼容不好\n\tprompt();\t//不推荐使用\n\n```\n\n## 打开窗口、关闭窗口\n\n1、打开窗口：\n\n```\n\twindow.open(url,target,param)\n```\n\n**参数解释：**\n\n- url：要打开的地址。\n\n- target：新窗口的位置。可以是：`_blank` 、`_self`、 `_parent` 父框架。\n\n- param：新窗口的一些设置。\n\n- 返回值：新窗口的句柄。\n\n**param**这个参数，可以填各种各样的参数（），比如：\n\n- name：新窗口的名称，可以为空\n\n- features：属性控制字符串，在此控制窗口的各种属性，属性之间以逗号隔开。\n\n- fullscreen= { yes/no/1/0 } 是否全屏，默认no\n\n- channelmode= { yes/no/1/0 } 是否显示频道栏，默认no\n\n- toolbar= { yes/no/1/0 } 是否显示工具条，默认no\n\n- location= { yes/no/1/0 } 是否显示地址栏，默认no。（有的浏览器不一定支持）\n\n- directories = { yes/no/1/0 } 是否显示转向按钮，默认no\n\n- status= { yes/no/1/0 } 是否显示窗口状态条，默认no\n\n- menubar= { yes/no/1/0 } 是否显示菜单，默认no\n\n- scrollbars= { yes/no/1/0 } 是否显示滚动条，默认yes\n\n- resizable= { yes/no/1/0 } 是否窗口可调整大小，默认no\n\n- width=number 窗口宽度（像素单位）\n\n- height=number 窗口高度（像素单位）\n\n- top=number 窗口离屏幕顶部距离（像素单位）\n\n- left=number 窗口离屏幕左边距离（像素单位）\n\n各个参数之间用逗号隔开就行，但我们最好是把它们统一放到json里。\n\n2、关闭窗口：window.close()\n\n（1）和（2）的代码举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n</head>\n<body>\n<a href=\"javascript:;\">点击我打开一个新的页面</a>\n<a href=\"javascript:;\">点击我关闭本页面</a>\n<script>\n    //新窗口 = window.open(地址,是否开新窗口,新窗口的各种参数);\n    var a1 = document.getElementsByTagName(\"a\")[0];\n    var a2 = document.getElementsByTagName(\"a\")[1];\n    a1.onclick = function () {\n//举例1： window.open(\"http://www.jx.com\",\"_blank\");\n        var json = {\n            \"name\": \"helloworld\",\n            \"fullscreen\": \"no\",\n            \"location\": \"no\",\n            \"width\": \"100px\",\n            \"height\": \"100px\",\n            \"top\": \"100px\",\n            \"left\": \"100px\"\n        };\n        window.open(\"http://www.baidu.com\", \"_blank\", json); //举例2\n    }\n\n    //关闭本页面\n    a2.onclick = function () {\n        window.close();\n    }\n</script>\n</body>\n</html>\n```\n\n3、新窗口相关：\n\n- 新窗口.moveTo(5,5)\n\n- 新窗口.moveBy()\n\n- 新窗口.resizeTo()\n\n- window.resizeBy()\n\n代码举例：\n\n```javascript\n        var newWin = window.open(\"demo.html\", \"_blank\", json);\n        newWin.moveTo(500, 500);\n```\n\n\n## location对象\n\n`window.location`可以简写成location。location相当于浏览器地址栏，可以将url解析成独立的片段。\n\n### location对象的属性\n\n- **href**：跳转\n\n- hash   返回url中#后面的内容，包含#\n\n- host    主机名，包括端口\n\n- hostname   主机名\n\n- pathname     url中的路径部分\n\n- protocol    协议 一般是http、https\n\n- search\t     查询字符串\n\n**location.href属性举例**：\n\n**举例1：**点击盒子时，进行跳转。\n\n```html\n<body>\n<div>smyhvae</div>\n<script>\n\n    var div = document.getElementsByTagName(\"div\")[0];\n\n    div.onclick = function () {\n        location.href = \"http://www.baidu.com\";   //点击div时，跳转到指定链接\n //     window.open(\"http://www.baidu.com\",\"_blank\");  //方式二\n    }\n\n</script>\n</body>\n```\n\n**举例2：5秒后自动跳转到百度**。\n\n有时候，当我们访问一个不存在的网页时，会提示5秒后自动跳转到指定页面，此时就可以用到location。举例：\n\n```html\n<script>\n\n    setTimeout(function () {\n        location.href = \"http://www.baidu.com\";\n    }, 5000);\n</script>\n```\n\n\n### location对象的方法\n\n- location.assign()：改变浏览器地址栏的地址，并记录到历史中\n\n设置location.href  就会调用assign()。一般使用location.href 进行页面之间的跳转。\n\n- location.replace()：替换浏览器地址栏的地址，不会记录到历史中\n\n- location.reload()：重新加载\n\n\n## navigator对象\n\nwindow.navigator 的一些属性可以获取客户端的一些信息。\n\n- userAgent：系统，浏览器)\n\n- platform：浏览器支持的系统，win/mac/linux\n\n举例：\n\n```javascript\n    console.log(navigator.userAgent);\n    console.log(navigator.platform);\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180201_2140.png)\n\n\n\n"
  },
  {
    "path": "04-JavaScript基础/原型对象.md",
    "content": "---\npublish: false\n---\n\n\n\n\n> 在看本文之前，我们可以先复习上一篇文章：《03-JavaScript基础/12-对象的创建&构造函数.md》\n\n## 原型对象\n\n### 原型的引入\n\n```javascript\n        function Person(name, age, gender) {\n            this.name = name;\n            this.age = age;\n            this.gender = gender;\n            //向对象中添加一个方法\n            this.sayName = function () {\n                console.log(\"我是\" + this.name);\n            }\n        }\n\n        //创建一个Person的实例\n        var per = new Person(\"孙悟空\", 18, \"男\");\n        var per2 = new Person(\"猪八戒\", 28, \"男\");\n        per.sayName();\n        per2.sayName();\n\n        console.log(per.sayName == per2.sayName);  //打印结果为false\n```\n\n**分析如下**：\n\n上方代码中，我们的sayName方法是写在构造函数 Person 内部的，然后在两个实例中进行了调用。造成的结果是，**构造函数每执行一次，就会给每个实例创建一个新的 sayName 方法**。也就是说，每个实例的sayName都是唯一的。因此，最后一行代码的打印结果为false。\n\n按照上面这种写法，假设创建10000个对象实例，就会创建10000个 sayName 方法。这种写法肯定是不合适的。我们为何不让所有的对象共享同一个方法呢？\n\n还有一种方式是，将sayName方法在全局作用域中定义：（不建议。原因看注释）\n\n```javascript\n        function Person(name, age, gender) {\n            this.name = name;\n            this.age = age;\n            this.gender = gender;\n            //向对象中添加一个方法\n            this.sayName = fun;\n        }\n\n        //将sayName方法在全局作用域中定义\n        /*\n         * 将函数定义在全局作用域，污染了全局作用域的命名空间\n         *  而且定义在全局作用域中也很不安全\n         */\n        function fun() {\n            alert(\"Hello大家好，我是:\" + this.name);\n        };\n```\n\n比较好的方式是，在原型中添加sayName方法：\n\n```javascript\n    Person.prototype.sayName = function(){\n        alert(\"Hello大家好，我是:\"+this.name);\n    };\n```\n\n这也就引入了我们本文要讲的「原型」。\n\n### 原型prototype的概念\n\n**认识1**：\n\n我们所创建的每一个函数，解析器都会向函数中添加一个属性 prototype。这个属性对应着一个对象，这个对象就是我们所谓的原型对象。\n\n如果函数作为普通函数调用prototype没有任何作用，当函数以构造函数的形式调用时，它所创建的实例对象中都会有一个隐含的属性，指向该构造函数的原型，我们可以通过__proto__来访问该属性。\n\n代码举例：\n\n```javascript\n\t// 定义构造函数\n\tfunction Person() {}\n\n\tvar per1 = new Person();\n\tvar per2 = new Person();\n\n\tconsole.log(Person.prototype); // 打印结果：[object object]\n\n\tconsole.log(per1.__proto__ == Person.prototype); // 打印结果：true\n```\n\n上方代码的最后一行：打印结果表明，`实例.__proto__` 和 `构造函数.prototype`都指的是原型对象。\n\n**认识2**：\n\n原型对象就相当于一个公共的区域，所有同一个类的实例都可以访问到这个原型对象，我们可以将对象中共有的内容，统一设置到原型对象中。\n\n以后我们创建构造函数时，可以将这些对象共有的属性和方法，统一添加到构造函数的原型对象中，这样就不用分别为每一个对象添加，也不会影响到全局作用域，就可以使每个对象都具有这些属性和方法了。\n\n**认识3**：\n\n使用 `in` 检查对象中是否含有某个属性时，如果对象中没有但是**原型中**有，也会返回true。\n\n可以使用对象的`hasOwnProperty()`来检查**对象自身中**是否含有该属性。\n\n### 原型链\n\n原型对象也是对象，所以它也有原型，当我们使用或访问一个对象的属性或方法时：\n\n- 它会先在对象自身中寻找，如果有则直接使用；\n\n- 如果没有则会去原型对象中寻找，如果找到则直接使用；\n\n- 如果没有则去原型的原型中寻找，直到找到Object对象的原型。\n\n- Object对象的原型没有原型，如果在Object原型中依然没有找到，则返回 null\n\n### 总结\n\n第一次接触「原型」和「原型链」的时候，会比较难理解。多接触几次，再回过头来看，就慢慢熟悉了。\n\n## 对象的 toString() 方法\n\n我们先来看下面这段代码：\n\n```javascript\n\tfunction Person(name, age, gender) {\n\tthis.name = name;\n\tthis.age = age;\n\tthis.gender = gender;\n\t}\n\n\tvar per1 = new Person(\"vae\", 26, \"男\");\n\n\tconsole.log(\"per1 = \" + per1);\n\tconsole.log(\"per1 = \" + per1.toString());\n```\n\n打印结果：\n\n```\nper1 = [object Object]\nper1 = [object Object]\n```\n\n上面的代码中，我们尝试打印实例 per1 的内部信息，但是发现，无论是打印 `per1` 还是打印 `per1.toString()`，结果都是`object`，这是为啥呢？分析如下：\n\n- 当我们直接在页面中打印一个对象时，其实是输出了对象的toString()方法的返回值。\n\n- 如果我们希望在打印对象时，不输出[object Object]，可以手动为对象添加一个toString()方法。意思是，重写 toString() 方法。\n\n重写 toString() 方法，具体做法如下：\n\n```javascript\n\tfunction Person(name, age, gender) {\n\tthis.name = name;\n\tthis.age = age;\n\tthis.gender = gender;\n\t}\n\n\t//方式一：重写 Person 原型的toString方法。针对 Person 的所有实例生效\n\tPerson.prototype.toString = function() {\n\t\treturn (\n\t\t  \"Person[name=\" +\n\t\t  this.name +\n\t\t  \",age=\" +\n\t\t  this.age +\n\t\t  \",gender=\" +\n\t\t  this.gender +\n\t\t  \"]\"\n\t\t);\n\t};\n\n\t// 方式二：仅重写实例 per1 的 toString方法。但是这种写法，只对 per1 生效， 对 per2 无效\n\t/*\n\tper1.toString = function() {\n\t\treturn (\n\t\t  \"Person[name=\" +\n\t\t  this.name +\n\t\t  \",age=\" +\n\t\t  this.age +\n\t\t  \",gender=\" +\n\t\t  this.gender +\n\t\t  \"]\"\n\t\t);\n\t};\n\t*/\n\n\tvar per1 = new Person(\"smyh\", 26, \"男\");\n\n\tvar per2 = new Person(\"vae\", 30, \"男\");\n\n\tconsole.log(\"per1 = \" + per1);\n\tconsole.log(\"per2 = \" + per2.toString());\n```\n\n打印结果：\n\n```javascript\nper1 = Person[name=smyh,age=26,gender=男]\nper2 = Person[name=vae,age=30,gender=男]\n```\n\n代码分析：\n\n上面的代码中，仔细看注释。我们重写了 Person 原型的 toString()，这样的话，可以保证对 Person 的所有实例生效。\n\n从这个例子，我们可以看出 `prototype` 的作用。\n\n## JS的垃圾回收（GC）机制\n\n程序运行过程中会产生垃圾，这些垃圾积攒过多以后，会导致程序运行的速度过慢。所以我们需要一个垃圾回收的机制，来处理程序运行过程中产生垃圾。\n\n当一个对象没有任何的变量或属性对它进行引用时，此时我们将永远无法操作该对象，此时这种对象就是一个垃圾，这种对象过多会占用大量的内存空间，导致程序运行变慢，所以这种垃圾必须进行清理。\n\n上面这句话，也可以这样理解：如果堆内存中的对象，没有任何变量指向它时，这个堆内存里的对象就会成为垃圾。\n\nJS拥有自动的垃圾回收机制，会自动将这些垃圾对象从内存中销毁。我们不需要也不能进行垃圾回收的操作。我们仅仅需要做的是：如果你不再使用该对象，那么，将改对象的引用设置为 null 即可。\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n\n"
  },
  {
    "path": "04-JavaScript基础/原型链.md",
    "content": "---\npublish: false\n---\n\n## 常见概念\n\n-   构造函数\n\n-   构造函数-扩展\n\n-   原型规则和示例\n\n-   原型链\n\n-   instanceof\n\n## 构造函数\n\n任何一个函数都可以被 new，new 了之后，就成了构造方法。\n\n如下：\n\n```javascript\nfunction Foo(name, age) {\n    this.name = name;\n    this.age = age;\n    //retrun this;   //默认有这一行。new一个构造函数，返回一个对象\n}\n\nvar fn1 = new Foo('smyhvae', 26);\nvar fn2 = new Foo('vae', 30); //new 多个实例对象\n```\n\n与普通函数相比，构造函数有以下明显特点：\n\n-   用 new 关键字调用。\n\n-   不需要用 return 显式返回值的，默认会返回 this，也就是新的实例对象。\n\n-   建议函数名的首字母大写，与普通函数区分开。\n\n参考链接：\n\n-   [JavaScript 中的普通函数与构造函数](http://www.cnblogs.com/SheilaSun/p/4398881.html)\n\n当 new 之后，this 会先变成一个空对象，然后通过`this.name = name`来赋值。\n\n### 构造函数的扩展\n\n![](http://img.smyhvae.com/20180306_1633.png)\n\n上图中发现，数组、对象、函数也有构造函数，它们的构造函数是 Array、Object、function。实际开发中，都推荐前面的书写方式。\n\n## 原型规则\n\n原型规则是学习原型链的基础。原型规则有五条，下面来讲解。\n\n### 规则 1\n\n所有的引用类型（数组、对象、函数），都具有对象特性，都可以**自由扩展属性**。null 除外。\n\n举例：\n\n![](http://img.smyhvae.com/20180306_1651.png)\n\n### 规则 2\n\n所有的**引用类型**（数组、对象、函数），都有一个`_proto_`属性，属性值是一个**普通的对象**。`_proto_`的含义是隐式原型。\n\n![](http://img.smyhvae.com/20180306_1656.png)\n\n其实，规则 2 是规则 1 的特例，只不过，js 语法帮我们自动加了 规则 2。\n\n### 规则三\n\n所有的**函数**（不包括数组、对象），都有一个`prototype`属性，属性值是一个**普通的对象**。`prototype`的含义是**显式原型**。（实例没有这个属性）\n\n![](http://img.smyhvae.com/20180306_1659.png)\n\n### 规则四\n\n所有的**引用类型**（数组、对象、函数），`_proto_`属性指向它的**构造函数**的`prototype`值。\n\n![](http://img.smyhvae.com/20180306_1701.png)\n\n总结：以上四条，要先理解清楚，然后再来看下面的第五条。\n\n### 规则五\n\n当试图获取一个对象的某个属性时，如果这个对象本身没有这个属性，那么会去它的`_proto_`中寻找（即它的构造函数的`prototype`）。\n\n`举例代码1`：\n\n```javascript\n//创建方法\nfunction Foo(name) {\n    this.name = name;\n}\n\nFoo.prototype.alertName = function () {\n    // 既然 Foo.prototype 是普通的对象，那也允许给它添加额外的属性 alertName\n    console.log(this.name);\n};\n\nvar fn = new Foo('smyhvae');\nfn.printName = function () {\n    console.log(this.name);\n};\n\n// 测试\nfn.printName(); //输出结果：smyhvae\nfn.alertName(); //输出结果：smyhvae\n```\n\n上方代码中，虽然 alertName 不是 fn 自身的属性，但是会从它的构造函数的`prototype`里面找。\n\n**扩展：**遍历循环对象自身的属性\n\n我们知道，`for in`循环可以遍历对象。针对上面的那个 fn 对象，它自身有两个属性：`name`、`printName`，另外从原型中找到了第三个属性`alertName`。现在，如果我们对 fn 进行遍历，能遍历到两个属性还是三个属性呢？\n\n答案：两个。因为，**高级浏览器中已经在 `for in`循环中屏蔽了来自原型的属性。但是，为了保证代码的健壮性，我们最好自己加上判断**，手动将第三个属性屏蔽掉：\n\n```javascript\nfor (var item in fn) {\n    if (fn.hasOwnProperty(item)) {\n        console.log(item);\n    }\n}\n```\n\n## 原型链\n\n还是拿上面的`举例代码1`举例，如果此时在最后面加一行代码：\n\n```\n\tfn.toString();   //去 fn._proto_._proto_ 中查找 toString()方法\n```\n\n上面的代码中，fn 直接调用了 toString()方法，这是因为它通过**原型链**，去`_proto_`的`_proto_`里找到了`Object`，而`Object`是由`toString()`方法的。\n\n### instanceof\n\n格式：\n\n```javascript\n对象 instanceof 构造函数;\n```\n\n`instanceof`的作用：用于判断**引用类型**属于哪个**构造函数**。\n\n例 1：判断一个变量是否为数组： `变量 instanceof Array`\n\n例 2：\n\n```javascript\nfunction Person() {}\n\n//p--->Person.prototype--->Object.prototype--->null\nvar p = new Person();\n//构造函数的**原型**是否在 p 对象的原型链上！\nconsole.log(p instanceof Person);\n```\n\n例 3：\n\n```javascript\nfn instanceof Foo;\n```\n\n上面这句话，判断逻辑是：**fn 的`_proto_`一层一层往上找，看能否对应到 Foo.prototype**。\n\n原型链如下：（重要）\n\n![](http://img.smyhvae.com/20180306_1853.png)\n\n注意，Object 这个构造方法的显式原型是 null，这是一个特例。\n\nissues 101 补充：通过原型链查找时，如果你找的是一个属性的话，则返回 undefined，如果你找的是一个方法，则报错。\n\n## 常见题目\n\n-   如何准确判断一个变量是数组类型\n\n-   写一个原型链继承的例子\n\n-   描述 new 一个对象的过程\n\n-   zepto(或其他框架)源码中如何使用原型链\n\n下面分别讲解。\n\n### 题目一：如何准确判断一个变量是数组类型\n\n答案：\n\n```javascript\nvar arr1 = [];\n\nconsole.log(arr1 instanceof Array); //打印结果：true。\nconsole.log(typeof arr1); //打印结果：object。提示：typeof 方法无法判断是否为数组\n```\n\n上方代码表明，只能通过 instanceof 来判断是否为数组。而 typeof 的打印结果是 object。\n\n### 题目二：写一个原型链继承的例子\n\n来看个基础的代码：\n\n![](http://img.smyhvae.com/20180306_1931.png)\n\n上面这个例子是基础，但是，在回答面试官的问题时，不要写上面的例子。要写成下面这个例子：（更贴近实战）\n\n```js\nfunction DomElement(id) {\n    this.dom = document.getElementById(id);\n}\nDomElement.prototype.html = function (val) {\n    var ele = this.dom;\n    if (val) {\n        ele.innerHTML = val;\n        return this;\n    } else {\n        return ele.innerHTML;\n    }\n};\nDomElement.prototype.on = function (type, fn) {\n    var ele = this.dom;\n    ele.addEventListener(type, fn);\n    return this;\n};\nvar div1 = new DomElement('div1');\ndiv1.html('<p>这是一段文字</p >');\ndiv1.on('click', function () {\n    console.log('clicked');\n});\n```\n\n### 题目三：描述 new 一个对象的过程\n\n（1）创建一个新对象\n\n（2）this 指向这个新对象\n\n（3）执行代码（对 this 赋值）\n\n（4）返回 this\n\n参考链接：\n\n-   [原型、原型链、继承模式](https://my.oschina.net/u/2600761/blog/1524617)\n"
  },
  {
    "path": "04-JavaScript基础/常见代码解读.md",
    "content": "---\npublish: false\n---\n\n\n### `callback && callback()`的含义\n\n```javascript\ncallback && callback()\n```\n\n\n含义是：如果callback存在，则执行callback()函数。\n\n这个 callback 通常作为函数的参数使用。举例：\n\n\n```javascript\nfunction foo(callback) {\n    {\n        // do something\n    }\n    callback && callback() // 不传 callback 参数，则不会执行 callback() 函数\n}\n\nfoo(); // 只执行do something中的代码\nfoo(callback);//callback是另一个函数，将此函数传入 foo，将会执行callback()\n```\n\n\n\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/01-ES5和ES6的介绍.md",
    "content": "---\ntitle: 01-ES5和ES6的介绍\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 前言\n\n### ECMAScript 简介\n\nES 的全称是 ECMAScript，它是由 ECMA 国际标准化组织 制定的一套**脚本语言的标准化规范**。\n\n详细来说，ES 是由 ECMA 的第 39 号技术专家委员会（Technical Committee 39，简称 TC39）负责制订 ECMAScript 标准，成员包括 Microsoft、Mozilla、Google 等公司。\n\n简单来说，ECMAScript 是 JS 的语言标准。当然，ECMAScript 还包括其他脚本语言的语言标准。\n\n\n### ECMAScript 版本发布记录\n\n-   1995 年：ECMAScript 诞生。\n\n-   1997 年：ECMAScript 标准确立。ECMA 发布 ECMA-262 标准，推出浏览器标准语言 ECMAScript 1.0。\n\n-   1999 年：发布 ES3；与此同时，IE5 风靡一时。\n\n-   **2009 年**：发布 ECMAScript 5.0（简称 **ES5**）。例如 foreach、Object.keys、Object.create 和 json 标准。\n\n-   2011 年：发布 ECMAScript5.1，成为 ISO **国际标准**，从而推动所有浏览器都支持。\n\n-   **2015** 年 6 月：发布 ECMAScript 6（简称 **ES6**），即 ECMAScript 2015。（注意，**前者是按版本号区分，后者是按年份区分**。ES 的后续版本，请尽量用**年份**来命名。）\n\n-   2016 年 6 月：发布 ECMAScript 7，即 ECMAScript 2016。\n\n-   2017 年 6 月：发布 ECMAScript 8，即 ECMAScript 2017。\n\n-   2018 年 6 月：发布 ECMAScript 9，即 ECMAScript 2018。\n\n-   2019 年 6 月：发布 ECMAScript 10，即 ECMAScript 2019。\n\n-   2020 年 6 月：发布 ECMAScript 11，即 ECMAScript 2020。\n\n-   ......\n\n*   此后，每年更新一版。\n\n### ECMAScript5.1简介\n\nECMAScript 5.1是ECMAScript标准的最新修正版本，所以这个版本非常重要。与ECMAScript 5.0 相比，ECMAScript 5.1的改进如下：\n\n- 对于此前不合理的地方进行了修正。\n- 新增了一些新的方法。\n- 新增了**严格模式**的语法。（我们将在下一篇文章讲严格模式）\n\n\n\n推荐阅读链接：\n\n- [ECMAScript5.1规范中文版.pdf](https://yanhaijing.com/es5)\n- [张鑫旭翻译：ECMAScript 5.1简介](https://www.zhangxinxu.com/wordpress/2012/01/introducing-ecmascript-5-1/)\n\n### ES6 简介\n\n从上面的 ES 的版本记录可以看出：2015 年 6 月，ES6 正式发布。如果用年份来命名版本号，也可以称之为 ES2015。\n\nES6 是新的 JS 语法标准。**ES6 实际上是一个泛指，泛指 ES 2015 及后续的版本**。\n\n很多人在做业务选型的时候，会倾向于选 jQuery。其实 jQuery 的语法是偏向于 ES3 的。而现在主流的框架 Vue.js 和 React.js 的默认语法，都是用的 ES6。\n\nES6 的改进如下：\n\n-   ES6 之前的变量提升，会导致程序在运行时有一些不可预测性。而 ES6 中通过 let、const 变量优化了这一点。\n\n-   ES6 增加了很多功能，比如：**常量、作用域、对象代理、异步处理、类、继承**等。这些在 ES5 中想实现，比较复杂，但是 ES6 对它们进行了封装。\n\n-   ES6 之前的语法过于松散，实现相同的功能，不同的人可能会写出不同的代码。\n\nES6 的目标是：让 JS 语言可以编写复杂的大型应用程序，成为企业级开发语言。\n\n推荐阅读链接：\n\n- 阮一峰 | ES6 入门教程：https://es6.ruanyifeng.com/\n\n### ES各个版本的浏览器兼容性情况\n\n关于 ECMAScript各个版本的浏览器兼容性情况，可以看看 Juriy Zaytsev 统计的兼容性表格：https://kangax.github.io/compat-table/es5/\n\n这个网站很实用，而且还列出了每个版本里新增的主要API有哪些。\n\n比如说，ES5的兼容性是比较好的：\n\n![20211028_2115](https://img.smyhvae.com/20211028_2115.png)\n\nES6在IE 11浏览器里就不兼容：\n\n![20211028_2117](http://img.smyhvae.com/20211028_2117.png)\n\n另外，如果我们想在ES5环境中支持ES6的API，可以通过 [ES5-shim](https://github.com/es-shims/es5-shim) 这样的工具来实现。\n\n\n\n## 将ES6的语法转为ES5（为了兼容 ES5）\n\n> 掌握 ES6 之后，如果你的业务需要考虑 ES5 的兼容性，则可以这样做：写 ES6 语法的 js 代码，然后通过 `Babel`将 ES6 转换为 ES5。如果没有这样的需要，那么下面的内容，了解即可。\n\nbabel 的作用是将 ES6 语法转为 ES5 语法，支持低端浏览器。\n\n但是，在这之前，我们需要配置一下相关的环境。\n\n### 建立工程目录\n\n（1）先建立一个空的工程目录 `ES6Demo`，并在目录下建立两个文件夹 `src`和 `dist`：\n\n-   `src`：书写 ES6 代码，我们写的 js 程序都放在这里。\n\n-   `dist`：利用 Babel 编译生成的 ES5 代码。**我们在 HTML 页面需要引入 dist 里的 js 文件**。\n\n（2）在 src 里新建文件 `index.html`：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n        <title>Document</title>\n        <!-- 我们引入 ES5 中的 js 文件，而不是引入 ES6 中的 js 文件。 -->\n        <script src=\"./dist/index.js\"></script>\n    </head>\n    <body></body>\n</html>\n```\n\n**注意**，上方代码中，我们引入的是`dist`目录下的 js 文件。\n\n然后我们新建文件 `src/index.js`：\n\n```javascript\nlet a = 'smyhvae';\nconst b = 'qianguyihao';\n\nconsole.log(a);\nconsole.log(b);\n```\n\n这个文件是一个 ES6 语法 的 js 文件，稍后，我们尝试把这个 ES6 语法的 js 文件转化为 ES5 的 js 文件。\n\nPS：我们在写代码时，能用单引号尽量用单引号，而不是双引号，前者在压缩之后，程序执行会更快。\n\n### 全局安装 Babel-cli\n\n（1）初始化项目：\n\n在安装 Babel 之前，需要先用 npm init 先初始化我们的项目。打开终端或者通过 cmd 打开命令行工具，进入项目目录，输入如下命令：\n\n```bash\n\tnpm init -y\n```\n\n上方代码中，`-y` 代表全部默认同意，就不用一次次按回车了（稍后再根据需要，在文件中手动修改）。命令执行完成后，会在项目的根目录下生成 package.json 文件：\n\n```json\n{\n    \"name\": \"es6demo\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"smyhvae\",\n    \"license\": \"ISC\"\n}\n```\n\nPS：VS Code 里打开终端的快捷键是：`Contol + ~`。\n\n（2）全局安装 Babel-cli：\n\n在终端中输入以下命令：\n\n```bash\n\tnpm install -g babel-cli\n```\n\n![](http://img.smyhvae.com/20180304_1305.png)\n\n如果安装比较慢的话，Mac 下可以使用`cnpm`进行安装 ，windows 下可以使用`nrm`切换到 taobao 的镜像。\n\n（3）本地安装 babel-preset-es2015 和 babel-cli：\n\n```bash\n\tnpm install --save-dev babel-preset-es2015 babel-cli\n```\n\n![](http://img.smyhvae.com/20180304_1307.png)\n\n安装完成后，会发现`package.json`文件，已经多了 devDependencies 选项：\n\n![](https://img.smyhvae.com/20180304_1308.png)\n\n（4）新建.babelrc：\n\n在根目录下新建文件`.babelrc`，输入如下内容：\n\n```\n{\n    \"presets\":[\n        \"es2015\"\n    ],\n    \"plugins\":[]\n}\n```\n\n（5）开始转换：\n\n现在，我们应该可以将 ES6 的文件转化为 ES5 的文件了，命令如下：（此命令略显复杂）\n\n```\n\tbabel src/index.js -o dist/index.js\n```\n\n我们可以将上面这个命令进行简化一下。操作如下：\n\n在文件 `package.json` 中修改键 `scripts`中的内容：\n\n```json\n  \"scripts\": {\n    \"build\": \"babel src/index.js -o dist/index.js\"\n  },\n```\n\n修改后的效果如下：\n\n![](https://img.smyhvae.com/20180304_1315.png)\n\n目前为止，环境配置好了。以后，我们执行如下命令，即可将`src/index.js`这个 ES6 文件转化为 `dist/index.js`这个 ES5 文件：\n\n```bash\n\tnpm run build\n```\n\n我们执行上面的命令之后，会发现， dist 目录下会生成 ES5 的 js 文件：\n\nindex.js：\n\n```javascript\n'use strict';\n\nvar a = 'smyhvae';\nvar b = 'qianguyihao';\n\nconsole.log(a);\nconsole.log(b);\n```\n\n当我们打开网页后，就可以在浏览器的控制台，看到代码的输出结果。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/02-ES5中的严格模式.md",
    "content": "---\ntitle: 02-ES5中的严格模式\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n> 为什么在讲ES6之前，我们需要先了解ES5？因为很多人就是在学习ES6的过程中，才接触到es5这个概念。\n\n\n\n\n## ES的几个重要版本\n\n- ES 5 : 09年发布。\n\n- ES 6(ES2015) : 2015年发布，也称为ECMA2015。\n\n- ES 7(ES2016) : 2016年发布，也称为ECMA2016  (变化不大)。\n\n## 严格模式的理解\n\n我们知道，JS的语法是非常灵活的，比如说，我们随便写一个变量`x`，这个变量其实是挂在 windows下面的。这种灵活性在有些情况下，反而是一种缺点，造成了全局污染。因此，ES5还引入了一种严格的运行模式：\"严格模式\"（strict mode）。\n\n### 概念\n\n顾名思义，严格模式使得 Javascript 在更严格的语法条件下运行。限制性更强，也更安全。\n\n**目的**：\n\n- 消除Javascript语法的一些不合理、不严谨之处，减少一些怪异行为。\n\n- 消除代码运行的一些不安全之处，为代码的安全运行保驾护航。\n\n- 为未来新版本的Javascript做好铺垫\n\n### 使用\n\n- 针对整个文件：将`use strict`放在文件的第一行，则整个文件将以严格模式运行。\n\n- 针对单个函数：将`use strict`放在函数体的第一行，则整个函数以严格模式运行。\n\nPS：如果浏览器不支持，则这句话只会被解析为一条简单的语句，没有任何副作用。\n\n脚本文件的变通写法：因为第一种调用方法不利于文件合并，所以更好的做法是，借用第二种方法，将整个脚本文件放在一个立即执行的匿名函数之中。\n\n### 语法和行为改变\n\n- 必须用var声明变量\n\n- 禁止自定义的函数中的this指向window\n\n- 创建eval作用域\n\n- 对象不能有重名的属性\n\n\n## 严格模式和普通模式的区别\n\n> 下面列举几条严格模式的内容。\n\n### 全局变量显式声明\n\n在正常模式中，如果一个变量没有声明就赋值，默认是全局变量。严格模式禁止这种用法，全局变量必须显式声明。\n\n\n### 禁止this关键字指向全局对象：\n\n```javascript\n        var foo = function () {\n            console.log(this);\n        }\n\n        foo();\n```\n上方代码中，普通模式打印的是window。严格模式下打印的是undefined。\n\n### 创设eval作用域\n\n\n\n### 禁止使用with语句\n\n因为with语句无法在编译时就确定，属性到底归属哪个对象。\n\n\n### 构造函数必须通过new实例化对象\n\n构造函数必须通过new实例化对象，否则报错。因为this为undefined，此时无法设置属性。\n\n比如说：\n\n\n```\n        var Cat = function (name) {\n            this.name = name;\n        }\n\n        Cat('haha');\n```\n\n上方代码中，如果在严格模式下，则会报错。\n\n\n### 为了让代码更安全，禁止函数内部遍历调用栈\n\n### 严格模式下无法删除变量\n\n### 属性相关\n\n普通模式下，对一个对象的只读属性进行赋值，不会报错，只会默默地失败。严格模式下，将报错。\n\n严格模式下，对禁止扩展的对象添加新属性，会报错。\n\n普通模式下，如果对象有多个重名属性，最后赋值的那个属性会覆盖前面的值。严格模式下，这属于语法错误。\n\n\n普通模式下，如果函数有多个重名的参数，可以用arguments[i]读取。严格模式下，多个重名的参数属于语法错误。\n\n\n比如下面这样的代码：\n\n```javascript\n\tvar obj = {\n\t\tusername: 'smyh';\n\t\tusername: 'vae'\n\t}\n```\n\n上面的代码，在严格模式下属于语法错误，因为有重名的属性。\n\n### 函数必须声明在顶层\n\n\n将来Javascript的新版本会引入\"块级作用域\"。为了与新版本接轨，严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说，不允许在非函数的代码块内声明函数。\n\n### 新增关键字\n\n为了向将来Javascript的新版本过渡，严格模式新增了一些保留字：implements, interface, let, package, private, protected, public, static, yield。\n\n## 总结\n\n至少要能答出四五条。\n\n\n参考链接：\n\n- [阮一峰 | Javascript 严格模式详解](http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html)\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/03-ES5中的一些扩展.md",
    "content": "---\ntitle: 03-ES5中的一些扩展\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## JSON 对象\n\n1、js对象(数组) --> json对象(数组)：\n\n```javascript\n\tJSON.stringify(obj/arr)\n```\n\n2、json对象(数组) --> js对象(数组)：\n\n\n```javascript\n\tJSON.parse(json)\n```\n\n\n上面这两个方法是ES5中提供的。\n\n我们要记住，我们通常说的“json字符串”，只有两种：**json对象、json数组**。\n\n`typeof json字符串`的返回结果是string。\n\n## Object的扩展\n\nES5给Object扩展了一些静态方法，常用的有2个，我们接下来讲解。\n\n\n### 方法一\n\n```javascript\n\tObject.create(prototype, [descriptors])\n```\n\n作用: 以指定对象为原型，创建新的对象。同时，第二个参数可以为为新的对象添加新的属性，并对此属性进行描述。\n\n**举例1**：（没有第二个参数时）\n\n```javascript\n    var obj1 = {username: 'smyhvae', age: 26};\n    var obj2 = {address:'shenzhen'};\n\n    obj2 = Object.create(obj1);\n    console.log(obj2);\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180401_2150.png)\n\n我们发现，obj1成为了obj2的原型。\n\n**举例2**：（有第二个参数时）\n\n第二个参数可以给新的对象添加新的属性。我们修改上面的代码，尝试给obj2添加新属性`sex`：\n\n```javascript\n    var obj1 = {username: 'smyhvae', age: 26};\n    var obj2 = {address: 'shenzhen'};\n\n    obj2 = Object.create(obj1, {\n        sex: {//给obj2添加新的属性`sex`。注意，这一行的冒号不要漏掉\n            value: '男',  //通过value关键字设置sex的属性值\n            writable: false,\n            configurable: true,\n            enumerable: true\n        }\n    });\n\n    console.log(obj2);\n\n```\n\n上方代码中，我们通过第5行的sex给obj2设置了一个新的属性`sex`，但是要通过`value`来设置属性值（第6行）。\n\n设置完属性值后，这个属性值默认是不可修改的，要通过`writable`来设置。总而言之，这几个关键字的解释如下：\n\n- `value`：设置属性值。\n\n- `writable`：标识当前属性值是否可修改。如果不写的话，默认为false，不可修改。\n\n- `configurable`：标识当前属性是否可以被删除。默认为false，不可删除。\n\n- `enumerable`：标识当前属性是否能用 for in 枚举。 默认为false，不可。\n\n### 方法二\n\n> 这个方法有点难理解。\n\n\n```javascript\n\tObject.defineProperties(object, descriptors)\n```\n\n**作用**：为指定对象定义扩展多个属性。\n\n代码举例：\n\n\n```javascript\n    var obj2 = {\n        firstName : 'smyh',\n        lastName : 'vae'\n    };\n    Object.defineProperties(obj2, {\n        fullName : {\n            get : function () {\n                return this.firstName + '-' + this.lastName\n            },\n            set : function (data) {  //监听扩展属性，当扩展属性发生变化的时候自动调用，自动调用后将变化的值作为实参注入到set函数\n                var names = data.split('-');\n                this.firstName = names[0];\n                this.lastName = names[1];\n            }\n        }\n    });\n    console.log(obj2.fullName);\n    obj2.firstName = 'tim';\n    obj2.lastName = 'duncan';\n    console.log(obj2.fullName);\n    obj2.fullName = 'kobe-bryant';\n    console.log(obj2.fullName);\n```\n\n- get ：用来获取当前属性值的回调函数\n\n- set ：修改当前属性值得触发的回调函数，并且实参即为修改后的值\n\n存取器属性：setter,getter一个用来存值，一个用来取值。\n\n## Object的扩展（二）\n\nobj对象本身就自带了两个方法。格式如下：\n\n\n```javascript\nget 属性名(){} 用来得到当前属性值的回调函数\n\nset 属性名(){} 用来监视当前属性值变化的回调函数\n\n```\n\n举例如下：\n\n\n\n```javascript\n    var obj = {\n        firstName : 'kobe',\n        lastName : 'bryant',\n        get fullName(){\n            return this.firstName + ' ' + this.lastName\n        },\n        set fullName(data){\n            var names = data.split(' ');\n            this.firstName = names[0];\n            this.lastName = names[1];\n        }\n    };\n    console.log(obj.fullName);\n    obj.fullName = 'curry stephen';\n    console.log(obj.fullName);\n```\n\n\n## 数组的扩展\n\n> 下面讲的这几个方法，都是给数组的实例用的。\n\n> 下面提到的数组的这五个方法，更详细的内容，可以看《03-JavaScript基础/15-数组的常见方法.md》\n\n**方法1**：\n\n\n```javascript\n\tArray.prototype.indexOf(value)\n```\n\n作用：获取 value 在数组中的第一个下标。\n\n**方法2**：\n\n\n```javascript\n\tArray.prototype.lastIndexOf(value)\n```\n\n作用：获取 value 在数组中的最后一个下标。\n\n**方法3**：遍历数组\n\n\n```javascript\n\tArray.prototype.forEach(function(item, index){})\n```\n\n\n**方法4**：\n\n```javascript\n\tArray.prototype.map(function(item, index){})\n```\n\n作用：遍历数组返回一个新的数组，返回的是**加工之后**的新数组。\n\n\n**方法5**：\n\n```javascript\n\tArray.prototype.filter(function(item, index){})\n```\n\n作用：遍历过滤出一个新的子数组，返回条件为true的值。\n\n## 函数function的扩展：bind()\n\n> ES5中新增了`bind()`函数来改变this的指向。\n\n\n```javascript\n\tFunction.prototype.bind(obj)\n```\n\n作用：将函数内的this绑定为obj, 并将函数返回。\n\n**面试题**: call()、apply()和bind()的区别：\n\n- 都能改变this的指向\n\n- call()/apply()是**立即调用函数**\n\n- bind()：绑定完this后，不会立即调用当前函数，而是**将函数返回**，因此后面还需要再加`()`才能调用。\n\nPS：bind()传参的方式和call()一样。\n\n**分析**：\n\n为什么ES5中要加入bind()方法来改变this的指向呢？因为bind()不会立即调用当前函数。\n\nbind()通常使用在回调函数中，因为回调函数并不会立即调用。如果你希望在回调函数中改变this，不妨使用bind()。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/04-ES6：变量 let、const 和块级作用域.md",
    "content": "---\ntitle: 04-ES6：变量 let、const 和块级作用域\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## ES6 的变量声明\n\nES5 中，使用 `var` 定义变量（ var 是 variable 的简写）。\n\nES6 中，新增了 let 和 const 来定义变量：\n\n-   `let`：定义**变量**，替代 var。\n\n-   `const`：定义**常量**（定义后，不可修改）。\n\n### var：定义变量（ES5 知识回顾）\n\n看下面的代码：\n\n```javascript\n{\n    var a = 1;\n}\n\nconsole.log(a); //这里的 a，指的是 区块 里的 a\n```\n\n上方代码是可以输出结果的，输出结果为 1。因为 var 是全局声明的，所以，即使是在区块里声明，但仍然在全局起作用。\n\n也就是说：**使用 var 声明的变量不具备块级作用域特性**。\n\n再来看下面这段代码：\n\n```javascript\nvar a = 1;\n{\n    var a = 2;\n}\n\nconsole.log(a); //这里的 a，指的是 区块 里的 a\n```\n\n上方代码的输出结果为 2 ，因为 var 是全局声明的。\n\n**总结：**\n\nES5语法中，用 var 定义的变量，容易造成全局污染（污染整个 js 的作用域）。如果不考虑浏览器的兼容性，我们在今后的实战中，**尽量避免**使用 var 定义变量，尽量用接下来要讲的ES6语法。\n\n### 1、let：定义变量\n\n举例 1：\n\n```js\n{\n    let a = 'hello';\n}\nconsole.log(a); // 打印结果报错：Uncaught ReferenceError: a is not defined\n```\n\n上方代码，打印报错。\n\n举例 2：\n\n```javascript\nvar a = 2;\n{\n    let a = 3;\n}\n\nconsole.log(a); // 打印结果：2\n```\n\n通过上面两个例子可以看出，**用块级作用域内， 用let 声明的变量，只在局部起作用**。\n\n**经典面试题**：\n\nlet 可以防止数据污染，我们来看下面这个 **for 循环**的经典面试题。\n\n1、用 var 声明变量：\n\n```javascript\nfor (var i = 0; i < 10; i++) {\n    console.log('循环体中:' + i);\n}\n\nconsole.log('循环体外:' + i);\n```\n\n上方代码的最后一行可以正常打印结果，且最后一行的打印结果是 10。说明**循环体外**定义的变量 i，是**全局作用域**下的 i。\n\n2、用 let 声明变量：\n\n```javascript\nfor (let i = 0; i < 10; i++) {\n    console.log('循环体中:' + i); // // 每循环一次，就会在 { } 所在的块级作用域中，重新定义一个新的变量 i\n}\n\nconsole.log('循环体外:' + i);\n```\n\n上方代码的关键在于：**每次循环都会产生一个块级作用域，每个块级作用域中会重新定义一个新的变量 i**。\n\n另外，上方代码的最后一行，打印会报错。因为用 let 定义的变量 i，只在`{ }`这个**块级作用域**里生效。\n\n**总结：**我们要习惯用 let 声明，减少 var 声明带来的**污染全局空间**。\n\n为了进一步强调 let 不会带来污染，需要说明的是：当我们定义了`let a = 1`时，如果我们在同一个作用域内继续定义`let a = 2`，是会报错的。\n\n### 2、const：定义常量\n\n在程序开发中，有些变量是希望声明后，在业务层就不再发生变化，此时可以用 const 来定义**常量**。常量就是值（内存地址）不能变化的量。\n\n举例：\n\n```javascript\nconst name = 'smyhvae'; //定义常量\n```\n\n用 const 声明的常量，只在局部（块级作用域内）起作用；而且，用 const 声明常量时，必须赋值，否则报错。\n\n### let 和 const 的特点【重要】\n\n-   不属于顶层对象 Window\n\n-   不允许重复声明\n\n-   不存在变量提升\n\n-   暂时性死区\n\n-   支持块级作用域\n\n相反， 用`var`声明的变量：存在变量提升、可以重复声明、**没有块级作用域**。\n\n### var/let/const 的共同点\n\n-   全局作用域中定义的变量，可以在函数中使用。\n\n-   函数中声明的变量，只能在函数及其子函数中使用，外部无法使用。\n\n### 总结\n\n关于 let、const、var 更详细的介绍和区别，可以看本项目的另一篇文章《JavaScript 进阶/var、let、const 的区别》。\n\n## for 循环举例（经典案例）\n\n**代码 1**、我们先来看看如下代码：（用 var 定义变量 i）\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n    <head>\n        <meta />\n        <meta />\n        <meta />\n        <title>Document</title>\n    </head>\n    <body>\n        <input type=\"button\" value=\"aa\" />\n        <input type=\"button\" value=\"bb\" />\n        <input type=\"button\" value=\"cc\" />\n        <input type=\"button\" value=\"dd\" />\n\n        <script>\n            var myBtn = document.getElementsByTagName('input');\n\n            for (var i = 0; i < myBtn.length; i++) {\n                myBtn[i].onclick = function () {\n                    alert(i);\n                };\n            }\n        </script>\n    </body>\n</html>\n```\n\n上方代码中的运行效果如下：\n\n![](http://img.smyhvae.com/20190904_1030.gif)\n\n你可能会感到诧异，为何点击任何一个按钮，弹出的内容都是 4 呢？这是因为，我们用 var 定义的变量 i，是在全局作用域声明的。整个代码中，自始至终只有一个变量。\n\nfor 循环是同步代码，而 onclick 点击事件是异步代码。当我们还没点击按钮之前，同步代码已经执行完了，变量 i 已经循环到 4 了。\n\n也就是说，上面的 for 循环，相当于如下代码：\n\n```javascript\nvar i = 0;\nmyBtn[0].onclick = function () {\n    alert(i);\n};\ni++;\n\nmyBtn[1].onclick = function () {\n    alert(i);\n};\ni++;\n\nmyBtn[2].onclick = function () {\n    alert(i);\n};\ni++;\n\nmyBtn[3].onclick = function () {\n    alert(i);\n};\ni++; // 到这里，i 的值已经是4了。因此，当我们点击按钮时，i的值一直都是4\n```\n\n**代码 2**、上面的代码中，如果我们改为用 let 定义变量 i：\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n    <head>\n        <meta />\n        <meta />\n        <meta />\n        <title>Document</title>\n    </head>\n    <body>\n        <input type=\"button\" value=\"aa\" />\n        <input type=\"button\" value=\"bb\" />\n        <input type=\"button\" value=\"cc\" />\n        <input type=\"button\" value=\"dd\" />\n\n        <script>\n            var myBtn = document.getElementsByTagName('input');\n\n            for (let i = 0; i < myBtn.length; i++) {\n                myBtn[i].onclick = function () {\n                    alert(i);\n                };\n            }\n        </script>\n    </body>\n</html>\n```\n\n上方代码中的运行效果如下：\n\n![](http://img.smyhvae.com/20190904_1040.gif)\n\n上面这个运行结果，才是我们预期的效果。我们用 let 定义变量 i，在循环的过程中，每执行一次循环体，就会诞生一个新的 i。循环体执行 4 次，就会有四个 i。\n\n## 补充知识\n\n### 暂时性死区 DTC\n\nES6 规定：使用 let/const 声明的变量，会使区块形成封闭的作用域。若在声明之前使用变量，就会报错。\n\n也就是说，在使用 let/const 声明变量时，**变量需要先声明，再使用**（声明语句必须放在使用之前）。这在语法上，称为 “暂时性死区”（ temporal dead zone，简称 TDZ）。\n\nDTC 其实是一种保护机制，可以让我们养成良好的编程习惯。\n\n代码举例：\n\n```js\nconst name = 'qianguyihao';\n\nfunction foo() {\n    console.log(name);\n    const name = 'hello';\n}\n\nfoo(); // 执行函数后，控制台报错：Uncaught ReferenceError: Cannot access 'name' before initialization\n```\n\n### ES5 中如何定义常量\n\nES5中有`Object.defineProperty`这样一个api，可以定义常量。这个API中接收三个参数。\n\n代码举例：\n\n```js\n// 定义常量 PI\nObject.defineProperty(window, 'PI', {\n    value: 3.14,\n    writable: false,\n});\n\nconsole.log(PI); // 打印结果：3.14\nPI = 6; //尝试修改常量\nconsole.log(PI); //打印结果：3.14，说明修改失败\n```\n\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/05-ES6：变量的解构赋值.md",
    "content": "---\ntitle: 05-ES6：变量的解构赋值\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 解构赋值的概念\n\n**解构赋值**：ES6 允许我们，按照一一对应的方式，从数组或者对象中**提取值**，再将提取出来的值赋值给变量。\n\n解构：分解数据结构；赋值：给变量赋值。\n\n解构赋值在实际开发中可以大量减少我们的代码量，并且让程序结构更清晰。\n\n## 数组的解构赋值\n\n数组的结构赋值：将数组中的值按照**位置**提取出来，然后赋值给变量。\n\n### 语法\n\n在 ES6 之前，当我们在为一组变量赋值时，一般是这样写：\n\n```javascript\nvar a = 1;\nvar b = 2;\nvar c = 3;\n```\n\n或者是这样写：\n\n```js\nvar arr = [1, 2, 3];\n\nvar a = arr[0];\nvar b = arr[1];\nvar c = arr[2];\n```\n\n现在有了 ES6 之后，我们可以通过数组解构的方式进行赋值：（根据**位置**进行一一对应）\n\n```javascript\nlet [a, b, c] = [1, 2, 3];\n```\n\n二者的效果是一样的，但明显后者的代码更简洁优雅。\n\n### 未匹配到的情况\n\n数据的结构赋值，是根据位置进行一一对应来赋值的。可如果左边的数量大于右边的数量时（也就是变量的数量大于值的数量时），多余的变量要怎么处理呢？\n\n答案是：如果变量在一一对应时，没有找到对应的值，那么，**多余的变量会被赋值为 undefined**。\n\n### 解构时，左边允许有默认值\n\n在解构赋值时，是允许使用默认值的。举例如下：\n\n```javascript\n{\n    //一个变量时\n    let [foo = true] = [];\n    console.log(foo); //输出结果：true\n}\n\n{\n    //两个变量时\n    let [a, b] = ['千古壹号']; //a 赋值为：千古壹号。b没有赋值\n    console.log(a + ',' + b); //输出结果：千古壹号,undefined\n}\n\n{\n    //两个变量时\n    let [a, b = 'qianguyihao'] = ['千古壹号']; //a 赋值为：千古壹号。b 采用默认值 qianguyihao\n    console.log(a + ',' + b); //输出结果：千古壹号,qianguyihao\n}\n```\n\n### 将右边的 `undefined`和`null`赋值给变量\n\n如果我们在赋值时，采用的是 `undefined`或者`null`，那会有什么区别呢？\n\n```javascript\n{\n    let [a, b = 'qianguyihao'] = ['千古壹号', undefined]; //b 虽然被赋值为 undefined，但是 b 会采用默认值\n    console.log(a + ',' + b); //输出结果：千古壹号,qianguyihao\n}\n\n{\n    let [a, b = 'qianguyihao'] = ['千古壹号', null]; //b 被赋值为 null\n    console.log(a + ',' + b); //输出结果：千古壹号,null\n}\n```\n\n上方代码分析：\n\n-   undefined：相当于什么都没有，此时 b 采用默认值。\n\n-   null：相当于有值，但值为 null。\n\n## 对象的解构赋值\n\n对象的结构赋值：将对象中的值按照**属性匹配的方式**提取出来，然后赋值给变量。\n\n### 语法\n\n在 ES6 之前，我们从接口拿到 json 数据后，一般这么赋值：\n\n```javascript\nvar name = json.name;\n\nvar age = json.age;\n\nvar sex = json.sex;\n```\n\n上面这种写法，过于麻烦了。\n\n现在，有了 ES6 之后，我们可以使用对象解构的方式进行赋值。举例如下：\n\n```js\nconst person = { name: 'qianguyihao', age: 28, sex: '男' };\nlet { name, age, sex } = person; // 对象的结构赋值\n\nconsole.log(name); // 打印结果：qianguyihao\nconsole.log(age); // 打印结果：28\nconsole.log(sex); // 打印结果：男\n```\n\n上方代码可以看出，对象的解构与数组的结构，有一个重要的区别：**数组**的元素是按次序排列的，变量的取值由它的**位置**决定；而**对象的属性没有次序**，是**根据键来取值**的。\n\n\n### 未匹配到的情况\n\n对象的结构赋值，是根据属性名进行一一对应来赋值的。可如果左边的数量大于右边的数量时（也就是变量的数量大于值的数量时），多余的变量要怎么处理呢？\n\n答案是：如果变量在一一对应时，没有找到对应的值，那么，**多余的变量会被赋值为 undefined**。\n\n\n### 给左边的变量自定义命名\n\n对象的结构赋值里，左边的变量名一定要跟右边的属性名保持一致么？答案是不一定。我们可以单独给左边的变量自定义命名。\n\n举例如下：\n\n```js\nconst person = { name: 'qianguyihao', age: 28 };\nlet { name: myName, age: myAge } = person; // 对象的结构赋值\n\nconsole.log(myName); // 打印结果：qianguyihao\nconsole.log(myAge); // 打印结果：28\n\nconsole.log(name); // 打印报错：Uncaught ReferenceError: name is not defined\nconsole.log(age); // 打印报错：Uncaught ReferenceError: age is not defined\n```\n\n上方的第 2 行代码中：（请牢记）\n\n-   等号左边的属性名 name、age 是对应等号右边的属性名。\n\n-   等号左边的 myName、myAge 是左边自定义的变量名。\n\n或者，我们也可以理解为：将右边 name 的值赋值给左边的 myName 变量，将右边 age 的值赋值给左边的 myAge 变量。现在，你应该一目了然了吧？\n\n\n\n### 圆括号的使用\n\n如果变量 foo 在解构之前就已经定义了，此时你再去解构，就会出现问题。下面是错误的代码，编译会报错：\n\n```javascript\n\tlet foo = 'haha';\n\t{ foo } = { foo: 'smyhvae' };\n\tconsole.log(foo);\n\n```\n\n要解决报错，只要在解构的语句外边，加一个圆括号即可：\n\n```javascript\nlet foo = 'haha';\n({ foo } = { foo: 'smyhvae' });\nconsole.log(foo); //输出结果：smyhvae\n```\n\n## 字符串解构\n\n字符串也可以解构，这是因为，此时字符串被转换成了一个类似数组的对象。举例如下：\n\n```javascript\nconst [a, b, c, d] = 'hello';\nconsole.log(a);\nconsole.log(b);\nconsole.log(c);\n\nconsole.log(typeof a); //输出结果：string\n```\n\n打印结果：\n\n```\nh\ne\nl\nstring\n```\n\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/06-ES6：箭头函数.md",
    "content": "---\ntitle: 06-ES6：箭头函数\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 前言\n\nES6 在**函数扩展**方面，新增了很多特性。例如：\n\n-   箭头函数\n\n-   参数默认值\n\n-   参数结构赋值\n\n-   剩余参数\n\n- 扩展运算符\n\n-   this 绑定\n\n-   尾调用\n\n今天这篇文章，我们讲一下箭头函数。\n\n## 箭头函数\n\n### 定义箭头函数的语法\n\n语法：\n\n```js\n(参数1, 参数2 ...) => { 函数体 }\n```\n\n解释：\n\n-   如果有且仅有 1 个形参，则`()`可以省略\n\n-   如果函数体内有且仅有 1 条语句，则`{}`可以省略，但前提是，这条语句必须是 return 语句。\n\n需要强调的是，箭头函数是没有函数名的，既然如此，那要怎么调用箭头函数呢？你可以将箭头函数赋值给一个变量，通过变量名调用函数；也可以直接使用箭头函数。我们来看看下面的例子。\n\n### 举例\n\n写法 1、定义和调用函数：（传统写法）\n\n```javascript\nfunction fn1(a, b) {\n    console.log('haha');\n    return a + b;\n}\n\nconsole.log(fn1(1, 2)); //输出结果：3\n```\n\n写法 2、定义和调用函数：（ES6 中的写法）\n\n```javascript\nconst fn2 = (a, b) => {\n    console.log('haha');\n    return a + b;\n};\n\nconsole.log(fn2(1, 2)); //输出结果：3\n```\n\n上面的两种写法，效果是一样的。\n\n从上面的箭头函数中，我们可以很清晰地看到变量名、参数名、函数体。\n\n另外，箭头函数的写法还可以精简一下，继续往下看。\n\n【重要】在箭头函数中，如果方法体内只有一句话，且这句话是 return 语句，那就可以把 `{}`省略。写法如下：\n\n```javascript\nconst fn2 = (a, b) => a + b;\n\nconsole.log(fn2(1, 2)); //输出结果：3\n```\n\n在箭头函数中，如果形参只有一个参数，则可以把`()`省略。写法如下：\n\n```js\nconst fn2 = (a) => {\n    console.log('haha');\n    return a + 1;\n};\n\nconsole.log(fn2(1)); //输出结果：2\n```\n\n## 箭头函数的 this 的指向\n\n> 箭头函数只是为了让函数写起来更简洁优雅吗？当然不只是这个原因，还有一个很大的作用是与 this 的指向有关。\n\nES6 之前的普通函数中：this 指向的是函数被调用的对象（也就是说，谁调用了函数，this 就指向谁）。\n\n而 ES6 的箭头函数中：**箭头函数本身不绑定 this**，this 指向的是**箭头函数定义位置的 this**（也就是说，箭头函数在哪个位置定义的，this 就跟这个位置的 this 指向相同）。\n\n代码举例：\n\n```js\nconst obj = { name: '千古壹号' };\n\nfunction fn1() {\n    console.log(this); // 第一个 this\n    return () => {\n        console.log(this); // 第二个 this\n    };\n}\n\nconst fn2 = fn1.call(obj);\nfn2();\n```\n\n打印结果：\n\n```\nobj\nobj\n```\n\n代码解释：（一定要好好理解下面这句话）\n\n上面的代码中，箭头函数是在 fn1()函数里面定义的，所以第二个 this 跟 第一个 this 指向的是**同一个位置**。又因为，在执行 `fn1.call(obj)`之后，第一个 this 就指向了 obj，所以第二个 this 也是指向 了 obj。\n\n### 面试题：箭头函数的 this 指向\n\n代码举例：\n\n```js\nvar name = '许嵩';\nvar obj = {\n    name: '千古壹号',\n    sayHello: () => {\n        console.log(this.name);\n    },\n};\n\nobj.sayHello();\n```\n\n上方代码的打印结果是什么？你可能很难想到。\n\n正确答案的打印结果是`许嵩`。因为 `obj` 这个对象并不产生作用域， `sayHello()` 这个箭头函数实际仍然是定义在 window 当中的，所以 这里的 this 指向是 window。\n\n## 参数默认值\n\n**传统写法**：\n\n```javascript\nfunction fn(param) {\n    let p = param || 'hello';\n    console.log(p);\n}\n```\n\n上方代码中，函数体内的写法是：如果 param 不存在，就用 `hello`字符串做兜底。这样写比较啰嗦。\n\n**ES6 写法**：（参数默认值的写法，很简洁）\n\n```javascript\nfunction fn(param = 'hello') {\n    console.log(param);\n}\n```\n\n在 ES6 中定义方法时，我们可以给方法里的参数加一个**默认值**（缺省值）：\n\n-   方法被调用时，如果没有给参数赋值，那就是用默认值；\n\n-   方法被调用时，如果给参数赋值了新的值，那就用新的值。\n\n如下：\n\n```javascript\nvar fn2 = (a, b = 5) => {\n    console.log('haha');\n    return a + b;\n};\nconsole.log(fn2(1)); //第二个参数使用默认值 5。输出结果：6\n\nconsole.log(fn2(1, 8)); //输出结果：9\n```\n\n**提醒 1**：默认值的后面，不能再有**没有默认值的变量**。比如`(a,b,c)`这三个参数，如果我给 b 设置了默认值，那么就一定要给 c 设置默认值。\n\n**提醒 2**：\n\n我们来看下面这段代码：\n\n```javascript\nlet x = 'smyh';\nfunction fn(x, y = x) {\n    console.log(x, y);\n}\nfn('vae');\n```\n\n注意第二行代码，我们给 y 赋值为`x`，这里的`x`是括号里的第一个参数，并不是第一行代码里定义的`x`。打印结果：`vae vae`。\n\n如果我把第一个参数改一下，改成：\n\n```javascript\nlet x = 'smyh';\nfunction fn(z, y = x) {\n    console.log(z, y);\n}\nfn('vae');\n```\n\n此时打印结果是：`vae smyh`。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)"
  },
  {
    "path": "05-JavaScript基础：ES6语法/07-剩余参数和扩展运算符.md",
    "content": "---\ntitle: 07-剩余参数和扩展运算符\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 剩余参数\n\n**剩余参数**允许我们将不确定数量的**剩余的元素**放到一个**数组**中。\n\n比如说，当函数的实参个数大于形参个数时，我们可以将剩余的实参放到一个数组中。\n\n**传统写法**：\n\nES5 中，在定义方法时，参数要确定个数，如下：（程序会报错）\n\n```javascript\nfunction fn(a, b, c) {\n    console.log(a);\n    console.log(b);\n    console.log(c);\n    console.log(d);\n}\n\nfn(1, 2, 3);\n```\n\n上方代码中，因为方法的参数是三个，但使用时是用到了四个参数，所以会报错：\n\n![](http://img.smyhvae.com/20180304_1638.png)\n\n**ES6 写法**：\n\nES6 中，我们有了剩余参数，就不用担心报错的问题了。代码可以这样写：\n\n```javascript\nconst fn = (...args) => {\n    //当不确定方法的参数时，可以使用剩余参数\n    console.log(args[0]);\n    console.log(args[1]);\n    console.log(args[2]);\n    console.log(args[3]);\n};\n\nfn(1, 2);\nfn(1, 2, 3); //方法的定义中了四个参数，但调用函数时只使用了三个参数，ES6 中并不会报错。\n```\n\n打印结果：\n\n```bash\n1\n2\nundefined\nundefined\n\n\n1\n2\n3\nundefined\n```\n\n上方代码中注意，args 参数之后，不能再加别的参数，否则编译报错。\n\n下面这段代码，也是利用到了剩余参数：\n\n```js\nfunction fn1(first, ...args) {\n    console.log(first); // 10\n    console.log(args); // 数组：[20, 30]\n}\n\nfn1(10, 20, 30);\n```\n\n### 剩余参数的举例：参数求和\n\n代码举例：\n\n```js\nconst sum = (...args) => {\n    let total = 0;\n    args.forEach(item => total += item); // 注意 forEach里面的代码，写得 很精简\n    return total;\n};\nconsole.log(sum(10, 20, 30));\n```\n\n打印结果：60\n\n### 剩余参数和解构赋值配合使用\n\n代码举例：\n\n```js\nconst students = ['张三', '李四', '王五'];\nlet [s1, ...s2] = students;\n\nconsole.log(s1); // '张三'\nconsole.log(s2); // ['李四', '王五']\n```\n\n## 扩展运算符（展开语法）\n\n扩展运算符和剩余参数是相反的。\n\n剩余参数是将剩余的元素放到一个数组中；而扩展运算符是将数组或者对象拆分成逗号分隔的参数序列。\n\n代码举例：\n\n```js\nconst arr = [10, 20, 30];\n...arr // 10, 20, 30      注意，这一行是伪代码，这里用到了扩展运算符\nconsole.log(...arr); // 10 20 30\n\nconsole.log(10, 20, 30); // 10 20 30\n```\n\n上面的代码要仔细看：\n\n`arr`是一个数组，而`...arr`则表示`10, 20, 30`这样的序列。\n\n我们把`...arr` 打印出来，发现打印结果竟然是 `10 20 30`，为啥逗号不见了呢？因为逗号被当作了 console.log 的参数分隔符。如果你不信，可以直接打印 `console.log(10, 20, 30)` 看看。\n\n接下来，我们看一下扩展运算符的应用。\n\n### 举例1：数组赋值\n\n数组赋值的代码举例：\n\n```js\nlet arr2 = [...arr1]; // 将 arr1 赋值给 arr2\n```\n\n为了理解上面这行代码，我们先来分析一段代码：（将数组 arr1 赋值给 arr2）\n\n```javascript\nlet arr1 = ['www', 'smyhvae', 'com'];\nlet arr2 = arr1; // 将 arr1 赋值给 arr2，其实是让 arr2 指向 arr1 的内存地址\nconsole.log('arr1:' + arr1);\nconsole.log('arr2:' + arr2);\nconsole.log('---------------------');\n\narr2.push('你懂得'); //往 arr2 里添加一部分内容\nconsole.log('arr1:' + arr1);\nconsole.log('arr2:' + arr2);\n```\n\n运行结果：\n\n![](http://img.smyhvae.com/20180304_1950.png)\n\n上方代码中，我们往往 arr2 里添加了`你懂的`，却发现，arr1 里也有这个内容。原因是：`let arr2 = arr1;`其实是让 arr2 指向 arr1 的地址。也就是说，二者指向的是同一个内存地址。\n\n如果不想让 arr1 和 arr2 指向同一个内存地址，我们可以借助**扩展运算符**来做：\n\n```javascript\nlet arr1 = ['www', 'smyhvae', 'com'];\nlet arr2 = [...arr1]; //【重要代码】arr2 会重新开辟内存地址\nconsole.log('arr1:' + arr1);\nconsole.log('arr2:' + arr2);\nconsole.log('---------------------');\n\narr2.push('你懂得'); //往arr2 里添加一部分内容\nconsole.log('arr1:' + arr1);\nconsole.log('arr2:' + arr2);\n```\n\n运行结果：\n\n```bash\narr1:www,smyhvae,com\narr2:www,smyhvae,com\n---------------------\narr1:www,smyhvae,com\narr2:www,smyhvae,com,你懂得\n```\n\n我们明白了这个例子，就可以避免开发中的很多业务逻辑上的 bug。\n\n### 举例2：合并数组\n\n代码举例：\n\n```js\nlet arr1 = ['王一', '王二', '王三'];\nlet arr2 = ['王四', '王五', '王六'];\n// ...arr1  // '王一','王二','王三'\n// ...arr2  // '王四','王五','王六'\n\n// 方法1\nlet arr3 = [...arr1, ...arr2];\nconsole.log(arr3); // [\"王一\", \"王二\", \"王三\", \"王四\", \"王五\", \"王六\"]\n\n// 方法2\narr1.push(...arr2);\nconsole.log(arr1); // [\"王一\", \"王二\", \"王三\", \"王四\", \"王五\", \"王六\"]\n```\n\n### 举例3：将伪数组或者可遍历对象转换为真正的数组\n\n代码举例：\n\n```js\nconst myDivs = document.getElementsByClassName('div');\nconst divArr = [...myDivs]; // 利用扩展运算符，将伪数组转为真正的数组\n```\n\n**补充**：\n\n我们在《JavaScript基础/数组的常见方法》中也学过，还有一种方式，可以将伪数组（或者可遍历对象）转换为真正的数组。语法格式如下：\n\n```js\nlet arr2 = Array.from(arrayLike);\n```\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/08-字符串、数组、对象的扩展.md",
    "content": "---\ntitle: 08-字符串、数组、对象的扩展\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 字符串的扩展\n\n> 下面提到的字符串的几个方法，更详细的内容，可以看《04-JavaScript 基础/内置对象 String：字符串的常见方法.md》。\n\nES6 中的字符串扩展如下：\n\n-   `includes(str)`：判断是否包含指定的字符串\n\n-   `startsWith(str)`：判断是否以指定字符串开头\n\n-   `endsWith(str)`：判断是否以指定字符串结尾\n\n-   `repeat(count)`：重复指定次数\n\n举例如下：\n\n```javascript\nlet str = 'abcdefg';\n\nconsole.log(str.includes('a')); //true\nconsole.log(str.includes('h')); //false\n\n//startsWith(str) : 判断是否以指定字符串开头\nconsole.log(str.startsWith('a')); //true\nconsole.log(str.startsWith('d')); //false\n\n//endsWith(str) : 判断是否以指定字符串结尾\nconsole.log(str.endsWith('g')); //true\nconsole.log(str.endsWith('d')); //false\n\n//repeat(count) : 重复指定次数a\nconsole.log(str.repeat(5));\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180402_1050.png)\n\n## Number 的扩展\n\n-   二进制与八进制数值表示法: 二进制用`0b`, 八进制用`0o`。\n\n举例：\n\n```javascript\nconsole.log(0b1010); //10\nconsole.log(0o56); //46\n```\n\n-   `Number.isFinite(i)`：判断是否为有限大的数。比如`Infinity`这种无穷大的数，返回的就是 false。\n\n-   `Number.isNaN(i)`：判断是否为 NaN。\n\n-   `Number.isInteger(i)`：判断是否为整数。\n\n-   `Number.parseInt(str)`：将字符串转换为对应的数值。\n\n-   `Math.trunc(i)`：去除小数部分。\n\n举例：\n\n```javascript\n//Number.isFinite(i) : 判断是否是有限大的数\nconsole.log(Number.isFinite(NaN)); //false\nconsole.log(Number.isFinite(5)); //true\nconsole.log(Number.isFinite(Infinity)); //false\n\n//Number.isNaN(i) : 判断是否是NaN\nconsole.log(Number.isNaN(NaN)); //true\nconsole.log(Number.isNaN(5)); //falsse\n\n//Number.isInteger(i) : 判断是否是整数\nconsole.log(Number.isInteger(5.23)); //false\nconsole.log(Number.isInteger(5.0)); //true\nconsole.log(Number.isInteger(5)); //true\n\n//Number.parseInt(str) : 将字符串转换为对应的数值\nconsole.log(Number.parseInt('123abc')); //123\nconsole.log(Number.parseInt('a123abc')); //NaN\n\n// Math.trunc(i) : 直接去除小数部分\nconsole.log(Math.trunc(13.123)); //13\n```\n\n## 数组的扩展\n\n> 下面提到的数组的几个方法，更详细的内容，可以看《04-JavaScript 基础/数组的常见方法.md》。\n\n-   Array.from()\n\n-   find()\n\n-   findIndex()\n\n## 对象的扩展\n\n### 扩展 1\n\n```javascript\nObject.is(v1, v2);\n```\n\n**作用：**判断两个数据是否完全相等。底层是通过**字符串**来判断的。\n\n我们先来看下面这两行代码的打印结果：\n\n```javascript\nconsole.log(0 == -0);\nconsole.log(NaN == NaN);\n```\n\n打印结果：\n\n```\n\ttrue\n\tfalse\n```\n\n上方代码中，第一行代码的打印结果为 true，这个很好理解。第二行代码的打印结果为 false，因为 NaN 和任何值都不相等。\n\n但是，如果换成下面这种方式来比较：\n\n```javascript\nconsole.log(Object.is(0, -0));\nconsole.log(Object.is(NaN, NaN));\n```\n\n打印结果却是：\n\n```bash\n\tfalse\n\ttrue\n```\n\n代码解释：还是刚刚说的那样，`Object.is(v1, v2)`比较的是字符串是否相等。\n\n### Object.assign()\n\nObject.assign() 在实战开发中，使用到的频率非常高，一定要重视。关于它的内容，详见《04-JavaScript 基础/浅拷贝和深拷贝.md》。\n\n### 扩展 3：`__proto__`属性\n\n举例：\n\n```javascript\nlet obj1 = { name: 'smyhvae' };\nlet obj2 = {};\n\nobj2.__proto__ = obj1;\n\nconsole.log(obj1);\nconsole.log(obj2);\nconsole.log(obj2.name);\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180404_2251.png)\n\n上方代码中，obj2 本身是没有属性的，但是通过`__proto__`属性和 obj1 产生关联，于是就可以获得 obj1 里的属性。\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)"
  },
  {
    "path": "05-JavaScript基础：ES6语法/09-内置对象扩展：Set数据结构.md",
    "content": "---\ntitle: 09-内置对象扩展：Set数据结构\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## Set 数据结构\n\n### Set 数据结构的介绍\n\nES6 提供了 新的数据结构 Set。Set 类似于**数组**，但成员的值都是**唯一**的，没有重复的值。\n\nSet 的应用有很多。比如，在 H5 页面的搜索功能里，用户可能会多次搜索重复的关键字；但是在数据存储上，不需要存储重复的关键字。此时，我们就可以用 Set 来存储用户的搜索记录，Set 内部会自动判断值是否重复，如果重复，则不会进行存储，十分方便。\n\n### 生成 Set 数据结构\n\nSet 本身就是一个构造函数，可通过 `new Set()` 生成一个 Set 的实例。\n\n举例 1：\n\n```js\nconst set1 = new Set();\nconsole.log(set1.size); // 打印结果：0\n```\n\n**举例 2**、可以接收一个**数组**作为参数，实现**数组去重**：\n\n```js\nconst set2 = new Set(['张三', '李四', '王五', '张三']); // 注意，这个数组里有重复的值\n\n// 注意，这里的 set2 并不是数组，而是一个单纯的 Set 数据结构\nconsole.log(set2); // {\"张三\", \"李四\", \"王五\"}\n\n// 通过扩展运算符，拿到 set 中的元素（用逗号分隔的序列）\n// ...set2 //  \"张三\", \"李四\", \"王五\"\n\n// 注意，到这一步，才获取到了真正的数组\nconsole.log([...set2]); // [\"张三\", \"李四\", \"王五\"]\n```\n\n注意上方的第一行代码，虽然参数里传递的是数组结构，但拿到的 `set2` 不是数组结构，而是 Set 结构，而且里面元素是去重了的。通过 `[...set2]`就可以拿到`set2`对应的数组。\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "05-JavaScript基础：ES6语法/ES6：Symbol.md",
    "content": "---\npublish: false\n---\n\n\n\n## Symbol\n\n### 概述\n\n背景：ES5中对象的属性名都是字符串，容易造成重名，污染环境。\n\n**概念**：ES6 引入了一种新的原始数据类型Symbol，表示独一无二的值。它是 JavaScript 语言的第七种数据类型，前六种是：undefined、null、布尔值（Boolean）、字符串（String）、数值（Number）、对象（Object）。\n\n\n**特点：**\n\n- Symbol属性对应的值是唯一的，解决**命名冲突问题**\n\n- Symbol值不能与其他数据进行计算，包括同字符串拼串\n\n- for in、for of 遍历时不会遍历Symbol属性。\n\n\n### 创建Symbol属性值\n\nSymbol是函数，但并不是构造函数。创建一个Symbol数据类型：\n\n```javascript\n    let mySymbol = Symbol();\n\n    console.log(typeof mySymbol);  //打印结果：symbol\n    console.log(mySymbol);         //打印结果：Symbol()\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180317_1134.png)\n\n下面来讲一下Symbol的使用。\n\n### 1、将Symbol作为对象的属性值\n\n```javascript\n    let mySymbol = Symbol();\n\n    let obj = {\n        name: 'smyhvae',\n        age: 26\n    };\n\n    //obj.mySymbol = 'male'; //错误：不能用 . 这个符号给对象添加 Symbol 属性。\n    obj[mySymbol] = 'hello';    //正确：通过**属性选择器**给对象添加 Symbol 属性。后面的属性值随便写。\n\n    console.log(obj);\n```\n\n上面的代码中，我们尝试给obj添加一个Symbol类型的属性值，但是添加的时候，不能采用`.`这个符号，而是应该用`属性选择器`的方式。打印结果：\n\n![](http://img.smyhvae.com/20180317_1134.png)\n\n现在我们用for in尝试对上面的obj进行遍历：\n\n```javascript\n    let mySymbol = Symbol();\n\n    let obj = {\n        name: 'smyhvae',\n        age: 26\n    };\n\n    obj[mySymbol] = 'hello';\n\n    console.log(obj);\n\n    //遍历obj\n    for (let i in obj) {\n        console.log(i);\n    }\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180317_1134.png)\n\n从打印结果中可以看到：for in、for of 遍历时不会遍历Symbol属性。\n\n### 创建Symbol属性值时，传参作为标识\n\n如果我通过 Symbol()函数创建了两个值，这两个值是不一样的：\n\n```javascript\n    let mySymbol1 = Symbol();\n    let mySymbol2 = Symbol();\n\n    console.log(mySymbol1 == mySymbol2); //打印结果：false\n    console.log(mySymbol1);         //打印结果：Symbol()\n    console.log(mySymbol2);         //打印结果：Symbol()\n```\n\n![](http://img.smyhvae.com/20180317_1134.png)\n\n上面代码中，倒数第三行的打印结果也就表明了，二者的值确实是不相等的。\n\n最后两行的打印结果却发现，二者的打印输出，肉眼看到的却相同。那该怎么区分它们呢？\n\n既然Symbol()是函数，函数就可以传入参数，我们可以通过参数的不同来作为**标识**。比如：\n\n\n```javascript\n    //在括号里加入参数，来标识不同的Symbol\n    let mySymbol1 = Symbol('one');\n    let mySymbol2 = Symbol('two');\n\n    console.log(mySymbol1 == mySymbol2); //打印结果：false\n    console.log(mySymbol1);         //打印结果：Symbol(one)\n    console.log(mySymbol2);         //打印结果：Symbol(two)。颜色为红色。\n    console.log(mySymbol2.toString());//打印结果：Symbol(two)。颜色为黑色。\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180317_1134.png)\n\n### 定义常量\n\nSymbol 可以用来定义常量：\n\n\n```javascript\n    const MY_NAME = Symbol('my_name');\n```\n\n\n### 内置的 Symbol 值\n\n除了定义自己使用的 Symbol 值以外，ES6 还提供了 11 个内置的 Symbol 值，指向语言内部使用的方法。\n\n- `Symbol.iterator`属性\n\n对象的`Symbol.iterator`属性，指向该对象的默认遍历器方法。\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/00-服务器分类及PHP入门.md",
    "content": "---\ntitle: 00-服务器分类及PHP入门\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## C/S架构和B/S架构\n\n### C/S架构\n\n是Client/Server这两个单词的首字母，指的是客户端，服务器。\n\n优点:\n\n- 性能较高：可以将一部分的计算工作放在客户端上,这样服务器只需要处理数据即可。\n\n- 界面酷炫:客户端可以使用更多系统提供的效果,做出更为炫目的效果。\n\n缺点:\n\n- 更新软件：如果有新的功能，就要推出新的版本。\n\n- 不同设备访问：如果使用其他的电脑，没有安装客户端的话就无法登陆软件。\n\n\n\n\n\n\n### B/S架构\n\n是Browser/Server的这两个单词的首字母。指的是浏览器、服务器，是WEB兴起之后的一种架构。\n\n现在所有的网站都是B/S架构，较为常见的例子有百度、知乎、网易云音乐Web等等，只需要通过浏览器即可使用.\n\n优点：\n\n- 更新简洁：如果需要更新内容了,对开发人员而言需要更改服务器的内容，对用户而言只需要刷新浏览器即可。\n\n- 多设备同步：所有数据都在网上,只要能够使用浏览器即可登录使用。\n\n缺点:\n\n- 性能较低：相比于客户端应用性能较低,但是随着硬件性能的提升,这个差距在缩小。\n\n- 浏览器兼容：处理低版本的浏览器显示问题一直是前端开发人员头痛的问题之一。移动设备兼容性较好，ie6已经越来越少人用了。\n\n\n## 服务器分类\n\n项目开发时，有三套环境：\n\n- Development 开发环境\n\n- Test 测试环境\n\n- Production 生产环境\n\n程序员平时干活儿用开发环境；开发完成后，部署到测试环境；测试完成后，产品上线，部署到生产环境。\n\n三套环境意味着三个服务器。\n\n### 服务器类型\n\n\n\n按类型分：\n\n- 文件服务器\n\n- 数据库服务器\n\n- 邮件服务器\n\n- Web 服务器等\n\n\n按软件分：\n\n- Apache 服务器\n\n- Nginx 服务器\n\n- IIS 服务器\n\n- Tomcat 服务器\n\n- Node 服务器等\n\n\n按操作系统分：\n\n- Linux服务器\n\n- Windows服务器等\n\n\n### 服务器软件\n\n提供了某种服务的计算器，我们称之为服务器。那么这些赋予计算器各种服务功能的软件主要有哪一些呢？\n\n常见的服务器软件有：\n\n- 文件服务器：Server-U、FileZilla、VsFTP等；\n\n- 数据库服务器：Oracle、MySQL、PostgreSQL、MSSQL等；\n\n- 邮件服务器：Postfix、Sendmail等；\n\n- HTTP 服务器：Apache（免费、开源）、Nginx、IIS（微软的.net服务器）、Tomcat（java编程的服务器）、NodeJS 等。\n\n\n## 使用 WampServer 搭建 HTTP服务\n\n### 集成环境的分类\n\n- AMP：Apache + Mysql + PHP。\n\n- WAMP：windows + Apache + Mysql + PHP。\n\n- XAMPP：WAMP 是针对windows的，而 XAMPP 可以安装在Linux、Windows、MacOS、Solaris这些操作系统上面。\n\n在windows平台下，如果想要一步到位安装好这些软件，可是使用软件 **WampServer**。\n\n### WampServer 的安装\n\n去 WampServer 的[官网](http://www.wampserver.com/en/)下载软件。\n\n![](http://img.smyhvae.com/20180227_1936.png)\n\n\n安装完成后进行安装。\n\n### 测试访问\n\n打开浏览器输入 `127.0.0.1` 查看显示的内容，如果是第一次安装，默认显示的应该是如下图片：\n\n\n![](http://img.smyhvae.com/20180227_2203.png)\n\n127.0.0.1 是回送地址，指本地机，一般用来测试使用，如果想要让其他电脑也能够访问，需要进行如下配置：\n\n（1）关闭防火墙：\n\n\n![](http://img.smyhvae.com/20180227_2207.gif)\n\n（2）修改httpd.conf文件：\n\n因为 Apache 的配置默认不允许外部访问，我们需要修改配置。\n\n打开文件`c:\\wamp\\bin\\apache\\Apache2.2.21\\conf\\httpd.conf`，通过搜索功能找到`onlineoffline tag - don't remove`这句话，在第234行的 `Allow from 127.0.0.1`的下面，加一行：`Allow from all`。\n\n然后将第192行的`Deny from all`改为`Allow from all`。\n\n\n保存，然后重启 wamp 即可。\n\n\n### 配置网站根目录\n\n网站的根目录默认是在`D:\\wamp\\www`。如果想修改这个根目录，可以这样改：\n\n打开 Apache的配置文件 `D:\\wamp\\bin\\apache\\Apache2.2.21\\conf\\http.conf`，如果是初次安装，找到178行的`DocumentRoot \"d:/wamp/www/\"`，以及205行的`<Directory \"d:/wamp/www/\">`，改这两个位置的路径即可。我们可以通过搜索关键字`documentRoot`来定位。\n\n\n\n## 静态网站和动态网站\n\n静态网站：\n\n- 访问的是实实在在保存在服务器上的文件。静态资源包括：html页面、css文件、js文件、图片等。\n\n- 当内容、图片、界面需要更新时，直接修改.html文件。\n\n动态网站：\n\n- 当用户访问网站时，根据`某些逻辑`,动态生成对应的`HTML、CSS、JS`代码给用户（这也就是web服务器开发的本质）。\n\n- 通过某种手段，当有新的消息时，**自动**的完成网站的更新。\n\n总结：\n\n由于静态网站在维护的局限性，所以产生了动态网站。\n\n实现动态网站的技术：php/jsp/.net/python等。\n\n动态网站的原理：浏览器请求动态网站的页面（比如*.php），php拼接数据并动态生成html页面，然后将新生成的页面返回给浏览器\n\nphp 之所以被称为最好的语言，是因为：基本上，我们能够想到的功能，它都帮助我们封装成了方法。十分方便。\n\n\n## PHP的常见语法\n\n**PHP代码执行方式**：\n\n- 在服务器端执行，然后返回给用户结果。如果直接使用浏览器打开，就会解析为文本。\n\n- 意思是说，需要浏览器通过 http请求，才能够执行php页面。\n\n这里只列举常用的PHP语法，更为详细的语法教程可以查阅 [api 文档](http://www.w3school.com.cn/php/index.asp)。\n\n### 第一段 php 代码\n\n将 WampServer 跑起来，在D:\\wamp\\www下新建一个`1.php`文件，代码如下：\n\n1.php：\n\n```\n<?php\n\techo \"hello smyhvae\";\n?>\n```\n\n\n在浏览器中输入`http://127.0.0.1/2018-02-28/1.php`，效果如下：\n\n![](http://img.smyhvae.com/20180228_0910.png)\n\n\n**代码的编写位置**：\n\n上方代码中，注意php语言的格式，第一行和第三行的格式中，没有空格。代码的编写位置在`<?php 代码写在这里?>`。\n\n### 注释\n\nphp 注释的写法跟 js 一致。\n\n```\n<?php\n\t//这是单行注释\n\t/*\n\t\t这是多行注释\n\t*/\n?>\n```\n\n\n### 变量\n\n- 变量以`$`符号开头，其后是变量的名称。大小写敏感。\n\n- 变量名称必须以字母或下划线开头。\n\n举例：\n\n```\n\t$a1;\n\t$_abc;\n```\n\n### 数据类型\n\nPHP支持的数据类型包括：\n\n- 字符串\n\n- 整数\n\n- 浮点数\n\n- 布尔\n\n- 数组\n\n- 对象\n\n- NULLL\n\n\n定义字符串时需要注意：\n\n- 单引号`` ：内部的内容只是作为字符串。\n\n- 双引号\"\" ：如果内部是PHP的变量,那么会将该变量的值解析。如果内部是html代码，也会解析成html。\n\n\n说白了，单引号里的内容，一定是字符串。双引号里的内容，可能会进行解析。\n\n```\n\techo \"<input type=`button` value=`smyhvae`>\";\n```\n\n上面这个语句，就被会解析成按钮。\n\n\n```\n\t// 字符串\n\t$str = '123';\n\n\t// 字符串拼接\n\t$str2 = '123'.'哈哈哈';\n\n\n\t// 整数\n\t$numA = 1; //正数\n\t$numB = -2;//负数\n\n\t// 浮点数\n\t$x = 1.1;\n\n\t// 布尔\n\t$a = true;\n\t$b = false;\n\n\t// 普通数组：数组中可以放 数字、字符串、布尔值等，不限制类型。\n\t$arr1 = array('123', 123);\n\techo $arr1[0];\n\n\t// 关系型数组：类似于json格式\n\t$arr2 = $array(`name`=>`smyhvae`, `age`=>`26`);\n\techo $arr2[`name`];  //获取时，通过  key 来获取\n\n```\n\n上方代码中注意，php 中字符串拼接的方式是 `.`。要注意哦。\n\n\n### 运算符\n\nPHP 中的运算符跟 JavaScript 中的基本一致，用法也基本一致。\n\n- 算数运算符：`+`、`-`、`/`、`*`、`%`\n\n- 赋值运算符：`x = y`、`x += y`,`x -= y`等。\n\n举例：\n\n```php\n<?php\n\t$x = 10;\n\t$y = 6;\n\n\techo ($x + $y); // 输出 16\n\techo ($x - $y); // 输出 4\n\techo ($x * $y); // 输出 60\n\techo ($x / $y); // 输出 1.6666666666667\n\techo ($x % $y); // 输出 4\n?>\n```\n\n### 函数的定义\n\n语法格式：\n\n```php\n\n\tfunction functionName() {\n\t  //这里写代码\n\t}\n```\n\n（1）有参数、无返回值的函数：\n\n```php\n\tfunction sayName($name){\n\t    echo $name.'你好哦';\n\t}\n\t// 调用\n\tsayName('smyhvae');\n```\n\n（2）有参数、参数有默认值的函数：\n\n```php\n\tfunction sayFood($food='西兰花'){\n\t    echo $food.'好吃';\n\t}\n\t// 调用\n\tsayFood('西葫芦');// 如果传入参数,就使用传入的参数\n\tsayFood();// 如果不传入参数,直接使用默认值\n```\n\n（3）有参数、有返回值的函数：\n\n```php\n\tfunction sum($a,$b){\n\t\treturn $a+$b\n\t}\n\tsum(1,2);// 返回值为1+2 = 3\n```\n\n### 类和对象\n\nPHP中允许使用对象这种**自定义**的数据类型。必须先声明，实例化之后才能够使用。\n\n定义最基础的类：\n\n```php\n\tclass Fox{\n\n\t        public $name = 'itcast';\n\t        public $age = 10;\n\t}\n\n\t$fox = new $fox;\n\t// 对象属性取值\n\t$name = $fox->name;\n\t// 对象属性赋值\n\t$fox->name = '小狐狸';\n```\n\n\n带构造函数的类：\n\n```php\n\tclass fox{\n\t    // 私有属性,外部无法访问\n\t    var $name = '小狐狸';\n\t    // 定义方法 用来获取属性\n\t    function Name(){\n\t    return $this->name;\n\t    }\n\t    // 构造函数,可以传入参数\n\t    function fox($name){\n\t    $this->name = $name\n\t    }\n\t}\n\n    // 定义了构造函数 需要使用构造函数初始化对象\n    $fox = new fox('小狐狸');\n    // 调用对象方法,获取对象名\n    $foxName = $fox->Name();\n```\n\n\n\n### 内容输出\n\n- `echo`：输出字符串。\n\n- `print_r()`：输出复杂数据类型。比如数组、对象。\n\n- `var_dump()`：输出详细信息。\n\n\n\n\n\n\n```php\n\t$arr =array(1,2,'123');\n\n\techo'123';\n\t//结果：123\n\n\n\tprint_r($arr);\n\t//结果：Array ( [0] => 1 [1] => 2 [2] => 123 )\n\n\tvar_dump($arr);\n\t/* 结果：\n\tarray\n\t  0 => int 1\n\t  1 => int 2\n\t  2 => string '123' (length=3)\n\t*/\n\n```\n\n### 循环语句\n\n这里只列举了`foreach`、`for`循环。\n\nfor 循环：\n\n```php\n\tfor ($x=0; $x<=10; $x++) {\n\t  echo \"数字是：$x <br>\";\n\t}\n\n```\n\n\nforeach 循环：\n\n```php\n\t$colors = array(\"red\",\"green\",\"blue\",\"yellow\");\n\n\tforeach ($colors as $value) {\n\t  echo \"$value <br>\";\n\t}\n```\n\n上方代码中，参数一：循环的对象。参数二：将对象的值挨个取出，直到最后。\n\n如果循环的是对象，输出的是对象的属性的值。\n\n输出结果：\n\n\n```bash\n\tred\n\tgreen\n\tblue\n\tyellow\n```\n\n\n## php中的header()函数\n\n浏览器访问http服务器，接收到响应时，会根据响应**报文头**的内容进行一些具体的操作。在php中，我们可以根据 **header** 来设置这些内容。\n\n\n**header()函数的作用**：用来向客户端(浏览器)发送报头。直接写在php代码的第一行就行。\n\n下面列举几个常见的 header函数。\n\n（1）设置编码格式：\n\n```php\n\theader('content-type:text/html; charset= utf-8');\n```\n\n例如：\n\n```php\n<?php\n\theader('content-type:text/html; charset= utf-8');\n\techo \"我的第一段 PHP 脚本\";\n?>\n```\n\n（2）设置页面跳转：\n\n```php\n\theader('location:http://www.baidu.com');\n```\n\n\n设置页面刷新的间隔：\n\n\n```php\n\theader('refresh:3; url=http://www.xiaomi.com');\n```\n\n\n## php中的 get 请求和 post 请求\n\n### get 请求\n\n可以通过`$_GET`对象来获取。\n\n**举例**：下面是一个简单的表单代码，通过 get 请求将数据提交到01.php。\n\n\n（1）index.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n<!-- 通过 get 请求，将表单提交到 php 页面中 -->\n<form action=\"01.php\" method=\"get\">\n    <label for=\"\">姓名：\n        <input type=\"text\" name=\"userName\"></label>\n    <br/>\n    <label for=\"\">邮箱：\n        <input type=\"text\" name=\"userEmail\"></label>\n    <br/>\n    <input type=\"submit\" name=\"\">\n</form>\n\n</body>\n</html>\n```\n\n（2）01.php：\n\n\n```php\n<?php\n\theader('content-type:text/html; charset= utf-8');\n    echo \"<h1>php 的get 请求演示</h1>\";\n    echo '用户名：'.$_GET['userName'];\n    echo '<br/>';\n    echo '邮箱：'.$_GET['userEmail'];\n ?>\n```\n\n上方代码可以看出，`$_GET`是关系型数组，可以通过 **$_GET[`key`]**获取值。这里的 key 是 form 标签中表单元素的 name 属性的值。\n\n效果：\n\n![](http://img.smyhvae.com/20180228_1140.gif)\n\n### post 请求\n\n可以通过`$_POST`对象来获取。\n\n**举例**：下面是一个简单的表单代码，通过 post 请求将数据提交到02.php。\n\n（1）index.html：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n<!-- 通过 post 请求，将表单提交到 php 页面中 -->\n<form action=\"02.php\" method=\"post\" >\n  <label for=\"\">姓名：\n      <input type=\"text\" name= \"userName\"></label>\n      <br/>\n  <label for=\"\">邮箱：\n      <input type=\"text\" name= \"userEmail\"></label>\n      <br/>\n      <input type=\"submit\" name=\"\">\n</form>\n\n</body>\n</html>\n```\n\n（2）02.php：\n\n```php\n<?php\n\theader('content-type:text/html; charset= utf-8');\n    echo \"<h1>php 的 post 请求演示</h1>\";\n    echo '用户名：'.$_POST['userName'];\n    echo '<br/>';\n    echo '邮箱：'.$_POST['userEmail'];\n ?>\n```\n\n上方代码可以看出，`$_POST`是关系型数组，可以通过 **$_POST[`key`]**获取值。这里的 key 是 form 标签中表单元素的 name 属性的值。\n\n效果演示：\n\n![](http://img.smyhvae.com/20180228_1145.gif)\n\n实际开发中，可能不会单独写一个php文件，常见的做法是：在 html 文件中嵌入 php 的代码。\n\n比如说，原本 html 中有个 li 标签是存放用户名的：\n\n```html\n\t<li>smyhvae</li>\n```\n\n嵌入 php后，用户名就变成了动态获取的：\n\n```php\n\t<li><?php\n\t\techo $_POST[`userName`]\n\t\t?></li>\n```\n\n## php 中文件相关的操作\n\n### 文件上传 `$_FILES`\n\n上传文件时，需要在html代码中进行如下设置：\n\n（1）在html表单中，设置`enctype=\"multipart/form-data\"`。该值是必须的。\n\n（2）只能用 post 方式获取。\n\n代码如下：\n\n（1）index.html:\n\n```html\n  <form action=\"03-fileUpdate.php\" method=\"post\" enctype=\"multipart/form-data\">\n\t  <label for=\"\">照片:\n\t      <input type=\"file\" name = \"picture\" multiple=\"\"></label>\n\t  <br/>\n\t  <input type=\"submit\" name=\"\">\n  </form>\n\n```\n\n（2）在 php 文件中打印 file 的具体内容：\n\n```php\n<?php\n  sleep(5);// 让服务器休息一会\n  print_r($_FILES);  //打印 file 的具体内容\n?>\n```\n\n演示效果：\n\n![](http://img.smyhvae.com/20180228_php_post_file.gif)\n\n上方现象可以看出：\n\n- 点击提交后，服务器没有立即出现反应,而是休息了一会`sleep(5)`。\n\n- 在`wamp/tmp`目录下面出现了一个`.tmp`文件。\n\n- .tmp文件一会就被自动删除了。\n\n- 服务器返回的内容中有文件的名字`[name] => computer.png`，以及上传文件保存的位置`D:\\wamp\\tmp\\php3D70.tmp`。服务器返回的内容如下：\n\n```bash\n\tArray ( [upFile] => Array ( [name] => yangyang.jpg [type] => image/jpeg [tmp_name] => D:\\wamp\\tmp\\phpCC56.tmp [error] => 0 [size] => 18145 ) )\n```\n\n### 文件保存\n\n我们尝试一下，把上面的例子中的`临时目录`下面的文件保存起来。这里需要用到 php 里的 `move_uploaded_file()`函数。[#](http://www.w3school.com.cn/php/func_filesystem_move_uploaded_file.asp)\n\n格式如下：\n\n```php\n\tmove_uploaded_file($_FILES['photo']['tmp_name'], './images/test.jpg');\n```\n\n参数解释：参数一：移动的文件。参数二：目标路径。\n\n（1）index.html：（这部分的代码保持不变）\n\n```php\n\t<form action=\"03.fileUpdate.php\" method=\"post\" enctype=\"multipart/form-data\">\n      <label for=\"\">照片:\n          <input type=\"file\" name = \"picture\" multiple=\"\"></label>\n      <br/>\n      <input type=\"submit\" name=\"\">\n  \t</form>\n```\n\n\n（2）PHP代码：\n\n暂略。\n\n\n### WampServer 中修改上传文件的大小\n\n（1）打开 WampServer的文件`php.ini`：\n\n![](http://img.smyhvae.com/20180228_1454.png)\n\n\n（2）修改`php.ini`中的如下内容：\n\n设置文件最大上传限制：（值的大小可以根据需求修改）\n\n```php\n\tfile_uploads = On;         是否允许上传文件 On/Off 默认是On\n\tupload_max_filesize = 32M; 设置 上传文件的最大限制\n\tpost_max_size = 32M;       设置 通过Post提交的最多数据\n```\n\n\n考虑网络传输快慢：这里修改一些参数：\n\n\n```php\n\tmax_execution_time = 30000      ; 脚本最长的执行时间 单位为秒\n\tmax_input_time = 600            ; 接收提交的数据的时间限制 单位为秒\n\tmemory_limit = 1024M            ; 最大的内存消耗\n```\n\n## HTTP 协议\n\n### 请求\n\n客户端发出的请求，主要由三个组成部分：请求行、请求头、请求主体。如下图所示：\n\n![](https://img.smyhvae.com/20180228_1505.jpg)\n\n**1、请求行：**\n\n- 请求方法：GET or POST\n\n- 请求URL\n\n- HTTP协议版本\n\n\n**2、请求头：**\n\n常见的请求头如下：\n\n```bash\nUser-Agent：浏览器的具体类型　　如：User-Agent：Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0\n\nAccept：浏览器支持哪些数据类型　　如：Accept: text/html,application/xhtml+xml,application/xml;q=0.9;\n\nAccept-Charset：浏览器采用的是哪种编码　　如：Accept-Charset: ISO-8859-1\n\nAccept-Encoding：浏览器支持解码的数据压缩格式　　如：Accept-Encoding: gzip, deflate\n\nAccept-Language：浏览器的语言环境　　如：Accept-Language zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3\n\nHost：请求的主机名，允许多个域名同处一个IP地址，即虚拟主机。Host:www.baidu.com\n\nConnection：表示是否需要持久连接。\n属性值可以是Keep-Alive/close，HTTP1.1默认是持久连接，它可以利用持久连接的优点，当页面包含多个元素时（例如Applet，图片），显著地减少下载所需要的时间。\n要实现这一点，Servlet需要在应答中发送一个Content-Length头，最简单的实现方法是：先把内容写入ByteArrayOutputStream，然后在正式写出内容之前计算它的大小。如：Connection: Keep-Alive\n\nContent-Length：表示请求消息正文的长度。对于POST请求来说Content-Length必须出现。\n\nContent-Type：WEB服务器告诉浏览器自己响应的对象的类型和字符集。例如：Content-Type: text/html; charset='gb2312'\n\nContent-Encoding：WEB服务器表明自己使用了什么压缩方法（gzip，deflate）压缩响应中的对象。例如：Content-Encoding：gzip\n\nContent-Language：WEB服务器告诉浏览器自己响应的对象的语言。\n\nCookie：最常用的请求头，浏览器每次都会将cookie发送到服务器上，允许服务器在客户端存储少量数据。\n\nReferer：包含一个URL，用户从该URL代表的页面出发访问当前请求的页面。服务器能知道你是从哪个页面过来的。Referer: http://www.baidu.com/\n\n```\n\n**3、请求体：**\n\n指的是提交给服务器的数据。\n\n需要注意的是，如果是往服务器提交数据，需要在请求头中设置`Content-Type: application/x-www-form-urlencoded`(在ajax中需要手动设置)。\n\n### 响应\n\n响应报文是服务器返回给客户端的。组成部分有响应行、响应头、响应主体。\n\n![](http://img.smyhvae.com/20180228_1510.jpg)\n\n\n**1、状态行：**\n\nHTTP响应行：主要是设置响应状态等信息。\n\n\n\n**2、响应头：**\n\nCookie、缓存等信息就是在响应头的属性中设置的。\n\n常见的响应头如下：\n\n\n\n```bash\nCache-Control\n\n响应输出到客户端后，服务端通过该报文头属告诉客户端如何控制响应内容的缓存。\n\n下面，的设置让客户端对响应内容缓存3600秒，也即在3600秒内，如果客户再次访问该资源，直接从客户端的缓存中返回内容给客户，不要再从服务端获取（当然，这个功能是靠客户端实现的，服务端只是通过这个属性提示客户端“应该这么做”，做不做，还是决定于客户端，如果是自己宣称支持HTTP的客户端，则就应该这样实现）。\n\nCache-Control: max-age=3600\n\nETag\n\n一个代表响应服务端资源（如页面）版本的报文头属性，如果某个服务端资源发生变化了，这个ETag就会相应发生变化。它是Cache-Control的有益补充，可以让客户端“更智能”地处理什么时候要从服务端取资源，什么时候可以直接从缓存中返回响应。\n\nETag: \"737060cd8c284d8af7ad3082f209582d\"\n\nLocation\n\n我们在Asp.net中让页面Redirect到一个某个A页面中，其实是让客户端再发一个请求到A页面，这个需要Redirect到的A页面的URL，其实就是通过响应报文头的Location属性告知客户端的，如下的报文头属性，将使客户端redirect到iteye的首页中：\n\nLocation: http://www.google.com.hk\n\nSet-Cookie\n\n服务端可以设置客户端的Cookie，其原理就是通过这个响应报文头属性实现的。\n\nSet-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1\n\n\n```\n\n\n**3、HTTP响应体：**\n\n如果请求的是HTML页面，那么返回的就是HTML代码。如果是JS就是JS代码。\n\n### 抓包工具\n\n常见的抓包工具有：whistle、Fiddler、Charles。"
  },
  {
    "path": "06-JavaScript基础：异步编程/01-单线程和异步任务.md",
    "content": "---\ntitle: 01-单线程和异步任务\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 单线程\n\n### JS 是单线程的\n\nJavaScript 语言的执行是**单线程**的。即同一时间，只能处理一个任务。\n\n具体来说，所谓单线程，是指 JS 引擎中负责解释和执行 JavaScript 代码的线程只有一个，即同一时间，只能处理一个任务。这个任务执行完后才能执行下一个。所有的任务都**需要排队**。\n\n**JS 为何要被设计为单线程呢**？原因如下：\n\n-   首先是历史原因，在最初设计 JS 这门语言时，多进程、多线程的架构并不流行，硬件支持并不好。\n\n-   其次是因为多线程的复杂性，多线程操作需要加锁，编码的复杂性会增高。\n\n-   而且，如果多个线程同时操作同一个 DOM，在多线程不加锁的情况下，会产生冲突，最终会导致 DOM 渲染的结果不符预期。\n\n所以，为了避免这些复杂问题的出现，JS 被设计成了单线程语言。\n\n### 浏览器是多进程、多线程的\n\nJS代码在执行时有它的运行环境（也称之为“容器”），这个运行环境可以是浏览器，也可以是 Node.js 环境。\n\n浏览器是多进程的，**每打开一个新的 tab 标签页就会开启一个新的进程**。每个进程之间是独立的，这是为了防止一个页面卡死而造成所有页面都无法响应，甚至整个浏览器强制退出。\n\n**每个进程中有很多个线程**，其中有一个专门执行JS代码的线程，所以我们常说JS是单线程的，这没有说错。从JS语言的角度看，我们把这个线程称为“**主线程**”。\n\n如果JS正在执行某个耗时的任务，则当前的线程会被阻塞，那应该怎么办呢？\n\n实际上，**耗时的任务并不是在主线程中执行的**。因为浏览器的当前进程中有很多个线程，我们可以把耗时任务交给浏览器的其它线程来协助处理，然后在特定的时机通知主线程，该任务则会进入主线程同步完成。\n\n比如，现在有一个三秒延迟的定时器任务。计时工作是交给浏览器的其他线程完成的，等三秒时间到了之后，通知JS主线程，该任务进入主线程进行同步执行。\n\n## 同步任务和异步任务\n\n### 定义\n\n当前正在执行的任务，如果没有执行完成，它可能会**阻塞**其他正在排队的任务。为了解决这个问题，JS 在设计之初，将任务分成了两类：同步任务、异步任务。\n\n-   同步任务：在**主线程**上排队执行的任务。只有当前任务执行完毕，才能执行下一个任务。当前任务在没有得到结果之前，不会继续后续操作。\t\n\n-   异步任务：不进入主线程、而是进入**任务队列**（Event Queue）的任务，该任务无论有没有得到结果，都不会阻塞后续任务的执行。只有\"任务队列\"通知主线程，某个异步任务可以执行了，该任务才会进入主线程执行。\n\n代码举例：\n\n```js\nconsole.log('同步任务1');\n\nsetTimeout(() => {\n    console.log('异步任务');\n}, 1000);\n\nconsole.log('同步任务2');\n```\n\n打印结果是：\n\n```\n同步任务1\n同步任务2\n异步任务\n```\n\n代码解释：第一行代码是同步任务，会**立即执行**；定时器里的回调函数是异步任务，需要等 1 秒后才会执行。假如定时器里的代码是同步任务，那需要等待1秒后，才能执行最后一行代码`console.log('同步任务2')`，也就是造成了主线程里的同步任务阻塞，这不是我们希望看到的。\n\n比如说，网络图片的请求，就是一个异步任务。前端如果同时请求多张网络网络图片，谁先请求完成就让谁先显示出来。假如网络图片的请求做成同步任务，那就会出大问题，所有图片都得排队加载，如果第一张图片未加载完成，就得卡在那里，造成阻塞，导致其他图片都加载不出来。页面看上去也会很卡顿，这肯定是不能接受的。\n\n### 前端使用异步编程的场景\n\n什么时候需要**等待**，就什么时候用异步。常见的异步场景如下：\n\n-   1、事件监听（比如说，按钮绑定点击事件之后，用户爱点不点。我们不可能卡在按钮那里，什么都不做。所以，应该用异步）\n-   2、回调函数：\n    -   2.1、定时器：setTimeout（定时炸弹）、setInterval（循环执行）\n    -   2.2、ajax请求。\n    -   2.3、Node.js：FS文件读写、数据库操作。 \n-   3、ES6 中的 Promise、Generator、async/await\n\n现在的大部分软件项目，都是前后端分离的。后端生成接口，前端请求接口。前端发送 ajax 请求，向后端请求数据，然后**等待一段时间**后，才能拿到数据。这个请求过程就是异步任务。\n\n### 接口调用的方式\n\njs 中常见的接口调用方式，有以下几种：\n\n-   原生 ajax、基于 jQuery 的 ajax\n-   Promise\n-   Fetch\n-   axios\n\n后续文章，我们会重点讲一下接口调用里的 Ajax，然后在 ES6 语法中学习 **Promise**。在这之前，我们需要先了解同步任务、异步任务的事件循环机制。\n\n\n\n\n### 多次异步调用的顺序\n\n-   多次异步调用的结果，顺序可能不同步。\n\n-   异步调用的结果如果**存在依赖**，则需要通过回调函数进行嵌套。\n\n## 定时器：代码示例\n\n掌握了上面的事件循环原理之后，我们来看几个例子。\n\n### 举例 1\n\n```js\nconsole.log(1);\n\nsetTimeout(() => {\n    console.log(2);\n}, 1000);\nconsole.log(3);\nconsole.log(4);\n```\n\n打印结果：\n\n```\n1\n3\n4\n2\n```\n\n解释：先等同步任务执行完成后，再执行异步任务。\n\n### 举例 2（重要）\n\n如果我把上面的等待时间，从 1 秒改成 0 秒，你看看打印结果会是什么。\n\n```js\nconsole.log(1);\n\nsetTimeout(() => {\n    console.log(2);\n}, 0);\nconsole.log(3);\nconsole.log(4);\n```\n\n打印结果：\n\n```\n1\n3\n4\n2\n```\n\n可以看到，打印结果没有任何变化，这个题目在面试中经常出现，考的就是 `setTimeout(()=> {}, 0)`会在什么时候执行。这就需要我们了解同步任务、异步任务的执行顺序，即前面讲到的**事件循环机制**。\n\n解释：先等同步任务执行完成后，再执行异步任务。\n\n同理，我们再来看看下面这段伪代码：\n\n```js\nsetTimeout(() => {\n    console.log('异步任务');\n}, 2000);\n\n// 伪代码\nsleep(5000); //表示很耗时的同步任务\n```\n\n上面的代码中，异步任务不是 2 秒之后执行，而是等耗时的同步任务执行完毕之后，才执行。那这个异步任务，是在 5 秒后执行？还是在 7 秒后执行？这个作业，留给读者你来思考~\n\n### 举例 3（较真系列）\n\n```js\nsetTimeout(() => {\n    console.log('异步任务');\n}, 1000);\n```\n\n上面的代码中，等到 1 秒之后，真的会执行异步任务吗？其实不是。\n\n在浏览器中， setTimeout()/ setInterval() 的每调用一次定时器的最小时间间隔是**4毫秒**，这通常是由于函数嵌套导致（嵌套层级达到一定深度），或者是由于已经执行的 setInterval 的回调函数阻塞导致的。\n\n上面的案例中，异步任务需要等待 1004 毫秒之后，才会从 Event Table 进入到 Event Queue。这在面试中也经常被问到。\n\n## 异步任务举例\n\n### 例 1：加载图片\n\n```js\n// 加载图片的异步任务\nfunction loadImage(file, success, fail) {\n    const img = new Image();\n    img.src = file;\n    img.onload = () => {\n        // 图片加载成功\n        success(img);\n    };\n    img.onerror = () => {\n        // 图片加载失败\n        fail(new Error('img load fail'));\n    };\n}\n\nloadImage(\n    'images/qia nguyihao.png',\n    (img) => {\n        console.log('图片加载成功');\n        document.body.appendChild(img);\n        img.style.border = 'solid 2px red';\n    },\n    (error) => {\n        console.log('图片加载失败');\n        console.log(error);\n    }\n);\n```\n\n### 例 2：定时器计时，移动 DOM 元素\n\n```js\n// 函数封装：定义一个定时器，每间隔 delay 毫秒之后，执行 callback 函数\nfunction myInterval(callback, delay = 100) {\n    let timeId = setInterval(() => callback(timeId), delay);\n}\n\nmyInterval((timeId) => {\n    // 每间隔 500毫秒之后，向右移动 .box 元素\n    const myBox = document.getElementsByClassName('box')[0];\n    const left = parseInt(window.getComputedStyle(myBox).left);\n    myBox.style.left = left + 20 + 'px';\n    if (left > 300) {\n        clearInterval(timeId);\n\n        // 每间隔 10 毫秒之后，将 .box 元素的宽度逐渐缩小，直到消失\n        myInterval((timeId2) => {\n            const width = parseInt(window.getComputedStyle(myBox).width);\n            myBox.style.width = width - 1 + 'px';\n            if (width <= 0) clearInterval(timeId2);\n        }, 10);\n    }\n}, 200);\n```\n\n\n\n## 参考链接\n\n-   [JS-同步任务，异步任务，微任务，和宏任务](https://github.com/PleaseStartYourPerformance/javaScript/issues/34)\n-   [JS 同步异步宏任务微任务](https://juejin.cn/post/6875605533127081992)、[JavaScript 中事件循环的理解](https://zhuanlan.zhihu.com/p/364475433)、[javascript 事件循环机制](https://github.com/reng99/blogs/issues/34)\n-   [如何实现比 setTimeout 快 80 倍的定时器？](https://mp.weixin.qq.com/s/NqzWkeOhqAU85XPkJu_wCA)\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/02-Ajax入门和发送http请求.md",
    "content": "---\ntitle: 02-Ajax入门和发送http请求\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 同步和异步回顾\n\n### 同步和异步的简单理解\n\n-   同步：必须等待前面的任务完成，才能继续后面的任务。\n\n-   异步：不受当前任务的影响。\n\n拿排队举例：\n\n-   同步：在银行排队时，只有等到你了，才能够去处理业务。\n\n-   异步：在排队的时候，可以玩手机。\n\n### 异步更新网站\n\n我们在访问一个普通的网站时，当浏览器加载完`HTML、CSS、JS`以后，网站的内容就固定了。如果想让网站内容发生更改，就必须**刷新**页面才能够看到更新的内容。\n\n可如果用到**异步更新**，情况就大为改观了。比如，我们在访问新浪微博时，看到一大半了，点击底部的**加载更多**，会自动帮我们加载更多的微博，同时页面并不会整体刷新。\n\n试想一下，如果没有异步刷新的话，每次点击“加载更多”，网页都要重新刷新，体验就太糟糕了。\n\nweb 前端里的异步更新，就要用到 Ajax。很多人说，如果没有 Ajax，就没有互联网的今天。\n\n关于同步和异步的更详细介绍，可以参考本项目的另外一篇文章：《05-JavaScript 基础：异步编程和 Ajax/01-单线程和异步》\n\n## Ajax\n\n### Ajax 的概念\n\n在浏览器中，我们可以在不刷新页面的情况下，通过 Ajax 的方式去获取一些新的内容。\n\nAjax：Asynchronous Javascript And XML（异步 JavaScript 和 XML）。它并不是凭空出现的新技术，而是对于现有技术的结合。Ajax 的核心是 js 对象：**XMLHttpRequest**。\n\n### Ajax 原理（发送 Ajax 请求的五个步骤）\n\n> 其实也就是 使用 XMLHttpRequest 对象的五个步骤。\n\n我们先回忆一下，一个完整的 HTTP 请求需要的是：\n\n-   请求的网址、请求方法 get/post。\n\n-   提交请求的内容数据、请求主体等。\n\n-   接收响应回来的内容。\n\n发送 Ajax 请求的五个步骤：\n\n（1）创建异步对象，即 XMLHttpRequest 对象。\n\n（2）使用 open 方法设置请求参数。`open(method, url, async)`。参数解释：请求的方法、请求的 url、是否异步。第三个参数如果不写，则默认为 true。\n\n（3）发送请求：`send()`。\n\n（4）注册事件：注册 onreadystatechange 事件，状态改变时就会调用。\n\n如果要在数据完整请求回来的时候才调用，我们需要手动写一些判断的逻辑。\n\n（5）服务端响应，获取返回的数据。\n\n## XMLHttpRequest 对象详解\n\n我们在上一段讲解了使用 XMLHttpRequest 对象的五个步骤。本段，我们讲一下注意事项。\n\n### 发送请求\n\n发送请求的方法：\n\n```javascript\nopen(method, url, async);\n```\n\n参数解释：\n\n-   method：请求的类型；GET 或 POST\n\n-   url：文件在服务器上的位置\n\n-   async：true（异步）或 false（同步）\n\n另外还有个方法：（仅用于 POST 请求）\n\n```javascript\nsend(string);\n```\n\n### POST 请求时注意\n\n如果想让 像 form 表单提交数据那样使用 POST 请求，就需要使用 XMLHttpRequest 对象的 setRequestHeader()方法 来添加 HTTP 头。然后在 send() 方法中添加想要发送的数据：\n\n```javascript\nxmlhttp.open('POST', 'ajax_test.php', true);\n\nxmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n\nxmlhttp.send('name=smyhvae&age=27');\n```\n\n### onreadystatechange 事件\n\n注册 onreadystatechange 事件后，每当 readyState 属性改变时，就会调用 onreadystatechange 函数。\n\nreadyState：（存有 XMLHttpRequest 的状态。从 0 到 4 发生变化）\n\n-   0: 请求未初始化\n\n-   1: 服务器连接已建立\n\n-   2: 请求已接收\n\n-   3: 请求处理中\n\n-   4: 请求已完成，且响应已就绪\n\nstatus：\n\n-   200: \"OK\"。\n\n-   404: 未找到页面。\n\n在 onreadystatechange 事件中，**当 readyState 等于 4，且状态码为 200 时，表示响应已就绪**。\n\n### 服务器响应的内容\n\n-   responseText：获得字符串形式的响应数据。\n\n-   responseXML：获得 XML 形式的响应数据。\n\n如果响应的是普通字符串，就使用 responseText；如果响应的是 XML，使用 responseXML。\n\n## 手写 Ajax\n\n### 手写第一个 Ajax 请求\n\nget 请求：\n\n```js\n//【发送ajax请求需要五步】\n//（1）创建XMLHttpRequest对象\nvar xmlhttp = new XMLHttpRequest();\n\n//（2）设置请求的参数。包括：请求的方法、请求的url。\nxmlhttp.open('get', '02-ajax.php');\n\n//（3）发送请求\nxmlhttp.send();\n\n//（4）注册事件。 onreadystatechange事件，状态改变时就会调用。\n//如果要在数据完整请求回来的时候才调用，我们需要手动写一些判断的逻辑。\nxmlhttp.onreadystatechange = function () {\n    // 为了保证 数据 完整返回，我们一般会判断 两个值\n    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {\n        //（5）服务端相应：如果能够进入这个判断，说明数据请求成功了\n        console.log('数据返回成功：' + JSON.stringify(xmlhttp.responseText));\n\n        // 伪代码：按业务需要，将接口返回的内容显示在页面上\n        // document.querySelector('h1').innerHTML = xmlhttp.responseText;\n    }\n};\n```\n\npost 请求：\n\n```js\n//（1）异步对象\nvar xmlhttp = new XMLHttpRequest();\n\n//（2）设置请求参数。包括：请求的方法、请求的url。\nxmlhttp.open('post', '02.post.php');\n\n// 如果想要使用post提交数据,必须添加此行\nxmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n\n//（3）发送请求\nxmlhttp.send('name=fox&age=18');\n\n//（4）注册事件\nxmlhttp.onreadystatechange = function () {\n    //（5）服务端相应\n    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {\n        alert(xmlhttp.responseText);\n    }\n};\n```\n\n### 封装 Ajax 请求（重要）\n\n上面的代码，执行顺序很好理解，但在实战开发中，是不会这么写的。假如你的页面中，需要调十次接口，那岂不是要手写十遍 Ajax 请求？这样会导致大量的重复代码。\n\n所以，我们需要把重复代码封装成一个公共函数，然后通过**回调函数**处理成功和失败的逻辑。\n\n封装 Ajax 请求的代码如下：(get 请求为例)\n\n```js\n// 封装 Ajax为公共函数：传入回调函数 success 和 fail\nfunction myAjax(url, success, fail) {\n    // 1、创建XMLHttpRequest对象\n    var xmlhttp;\n    if (window.XMLHttpRequest) {\n        xmlhttp = new XMLHttpRequest();\n    } else {\n        // 兼容IE5、IE6浏览器。不写也没关系\n        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');\n    }\n    // 2、发送请求\n    xmlhttp.open('GET', url, true);\n    xmlhttp.send();\n    // 3、服务端响应\n    xmlhttp.onreadystatechange = function () {\n        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {\n            var obj = JSON.parse(xmlhttp.responseText);\n            console.log('数据返回成功：' + obj);\n            success && success(xmlhttp.responseText);\n        } else {\n            // 这里的 && 符号，意思是：如果传了 fail 参数，就调用后面的 fail()；如果没传 fail 参数，就不调用后面的内容。因为 fail 参数不一定会传。\n            fail && fail(new Error('接口请求失败'));\n        }\n    };\n}\n\n// 单次调用 ajax\nmyAjax('a.json', (res) => {\n    console.log(res);\n});\n\n// 多次调用 ajax。接口请求顺序：a --> b --> c\nmyAjax('a.json', (res) => {\n    console.log(res);\n    myAjax('b.json', (res) => {\n        console.log(res);\n        myAjax('c.json', (res) => {\n            console.log(res);\n        });\n    });\n});\n```\n\n学会了封装 get 请求之后，封装 post请求也是类似的写法。\n\n### Ajax 请求：get 请求举例\n\n（1）index.html：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <h1>Ajax 发送 get 请求</h1>\n        <input type=\"button\" value=\"发送get_ajax请求\" id=\"btnAjax\" />\n\n        <script type=\"text/javascript\">\n            // 绑定点击事件\n            document.querySelector('#btnAjax').onclick = function () {\n                // 这里直接使用上面封装的 myAjax() 方法即可\n                myAjax('02-ajax.php', (res) => {\n                    console.log(res);\n                    console.log('数据返回成功');\n                    // 显示在页面上\n                    document.querySelector('h1').innerHTML = res;\n                    // alert(xhr.responseText);\n                });\n            };\n        </script>\n    </body>\n</html>\n```\n\n（2）02-ajax.php：\n\n```php\n<?php\n\techo 'smyhvae';\n ?>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180228_1605.gif)\n\n### Ajax 多个接口的嵌套请求\n\n我们在做异步任务的时候，经常会涉及到多个接口的嵌套请求。比如说，接口 1 请求完成后，需要根据接口 1 的数据请求接口 2；接口 2 请求完成后，需要根据接口 3 的数据请求接口 3，以此类推。\n\n需求描述：\n\n-   请求接口 1，根据用户名获取用户 id\n\n-   请求接口 2，根据用户 id 获取用户的年龄、性别等信息。\n\n代码实现思路：\n\n```js\nmyAjax('http://localhost:8888/php/user.php?name=千古', (userInfo) => {\n    // 根据第一个接口返回的 userInfo.id，继续请求第二个接口\n    myAjax(`http://localhost:8888/php/info.php?id=${userInfo['id']}`, (res) => {\n        console.log(response);\n    });\n});\n```\n\n我们在实战开发中，经常会涉及到接口请求之间的**依赖**：需要上一个接口请求返回的数据，来发送本次请求。这种场景经常遇到，需要记住。\n\n但这种层层嵌套的代码，会导致**回调地域**的问题，也不利于维护。我们在后续的 ES6 章节中，会讲解 Promise，它是一种更优雅的异步任务解决方案。\n\n## jQuery 中的 Ajax\n\nJQuery 作为最受欢迎的 js 框架之一，常见的 Ajax 已经帮助我们封装好了，只需要调用即可。更为详细的 api 文档可以查阅：[w3cSchool_JQueryAjax](http://www.w3school.com.cn/jquery/jquery_ref_ajax.asp)\n\n格式举例：\n\n```javascript\n$.ajax({\n    url: 'https://xxx.com/getUserInfo.php', // 接口的请求地址\n    data: 'name=fox&age=18', // 请求参数\n    type: 'GET', //请求的方式\n    success: function (argument) {\n        // 接口请求成功时调用\n        console.log('接口请求成功');\n    },\n    beforeSend: function (argument) {}, // 在发送请求之前调用,可以做一些验证之类的处理\n    error: function (argument) {\n        // 接口请求失败时调用\n        console.log('接口请求失败');\n    },\n});\n```\n\n代码举例：\n\n（1）index.html\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>jquery-ajax</title>\n    </head>\n    <body>\n        <input type=\"button\" value=\"点击\" id=\"btn\" />\n        <div id=\"showInfo\"></div>\n        <script type=\"text/javascript\" src=\"jquery-1.11.2.js\"></script>\n        <script type=\"text/javascript\">\n            $(function () {\n                $('#btn').click(function () {\n                    $.ajax({\n                        url: 'https://xxx.com/getUserInfo.php', // 接口的请求地址\n                        dataType: 'text',\n                        data: 'name=fox&age=18', // 请求参数\n                        type: 'get',\n                        success: function (data) {\n                            console.log('接口请求成功');\n                            alert(data);\n                            // $(\"#showInfo\").html(data);\n                        },\n                        error: function (err) {\n                            console.log('接口请求失败：' + err);\n                        },\n                    });\n                });\n            });\n        </script>\n    </body>\n</html>\n```\n\n（2）data.php：\n\n```php\n<?php\n\n$text = 'hello world';\n\necho $text;\n\n ?>\n\n```\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n\n```\n\n```\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/03-Ajax传输json和XML.md",
    "content": "---\ntitle: 03-Ajax传输json和XML\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## Ajax 传输 JSON\n\n### JSON 的语法\n\nJSON(JavaScript Object Notation)：是 ECMAScript 的子集。作用是进行数据的交换。语法更为简洁，网络传输、机器解析都更为迅速。\n\n语法规则：\n\n-   数据在键值对中\n\n-   数据由逗号分隔\n\n-   花括号保存对象\n\n-   方括号保存数组\n\n数据类型：\n\n-   数字（整数或浮点数）\n\n-   字符串（在双引号中）\n\n-   逻辑值（true 或 false）\n\n-   数组（在方括号中）\n\n-   对象（在花括号中）\n\n-   null\n\n示例：\n\n```json\n// 对象\n{\n  \"name\":\"fox\",\n  \"age\":\"18\",\n  \"sex\":\"true\",\n  \"car\":null\n}\n\n// 数组\n[\n  {\n      \"name\":\"小小胡\",\n      \"age\":\"1\"\n  },\n  {\n      \"name\":\"小二胡\",\n      \"age\":\"2\"\n  }\n]\n```\n\n### JavaScript 中：json 字符串 <--> js 对象\n\n基本上，所有的语言都有**将 json 字符串转化为该语言对象**的语法。\n\n比如在 js 中：\n\n-   JSON.parse()：将 JSON 字符串转化为 js 对象。例如：\n\n```javascript\n// 将 JSON 字符串格式化为 js 对象\nvar jsObj = JSON.parse(ajax.responseText);\n```\n\n-   JSON.stringify()：将 JS 对象转化为 JSON 字符串。例如：\n\n```javascript\nvar Obj = {\n    name: 'fox',\n    age: 18,\n    skill: '撩妹',\n};\n\nconsole.log(Obj);\n\n// 将 js 对象格式化为 JSON 字符串\nvar jsonStr = JSON.stringify(Obj);\n```\n\n### PHP 中：json 字符串 <--> js 对象\n\n-   **json_decode()**方法：将`json`字符串转化为变量。\n\n-   **json_encode()**方法：将变量转化为`json`字符串。\n\n代码举例：\n\n```php\n<?php\n    header(\"Content-Type:text/html;charset=utf-8\");\n    // json字符串\n    $jsonStr = '{\"name\":\"itcast\",\"age\":54,\"skill\":\"歌神\"}';\n    // 字符串转化为 php对象\n      print_r(json_decode($jsonStr));\n\n      echo \"<br>\";\n      // php数组\n      $arrayName = array('name' =>'littleFox' ,'age' => 13 );\n      // php对象 转化为 json字符串\n      print_r(json_encode($arrayName));\n ?>\n```\n\n输出结果：\n\n```bash\n\tstdClass Object ( [name] => itcast [age] => 54 [skill] => 歌神 )\n\t{\"name\":\"littleFox\",\"age\":13}\n\n```\n\n### ajax 请求解析 json（举例）\n\n（1）Person.json:\n\n```json\n{\n    \"name\": \"小强\",\n    \"skill\": \"砍树\",\n    \"friend\": \"老板\"\n}\n```\n\n（2）myJson.php：\n\n```php\n<?php\n\n\t// 读取json文件 并返回即可\n\techo  file_get_contents('info/Person.json');\n\n ?>\n```\n\n（3）getJson.html：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <h1>获取 json 数据</h1>\n        <input type=\"button\" value=\"获取json\" id=\"btnJson\" />\n    </body>\n</html>\n<script type=\"text/javascript\">\n    // 获取的是一个 如果要获取多个\n    // document.querySelectorAll(selector)\n    document.querySelector('#btnJson').onclick = function () {\n        var ajax = new XMLHttpRequest();\n\n        ajax.open('get', 'myJson.php');\n\n        ajax.send();\n\n        ajax.onreadystatechange = function () {\n            if (ajax.readyState == 4 && ajax.status == 200) {\n                // json 字符串 是字符串 所以我们可以 通过  responseText获取\n                console.log(ajax.responseText);\n\n                // 转化为 js对象\n                var jsObj = JSON.parse(ajax.responseText);\n\n                console.log(jsObj);\n\n                // 拼接ul s\n                var str = '';\n\n                str += '<ul>';\n                str += '<li>' + jsObj.name + '</li>';\n                str += '<li>' + jsObj.skill + '</li>';\n                str += '<li>' + jsObj.friend + '</li>';\n                str += '</ul>';\n\n                // 设置到界面上\n\n                document.body.innerHTML = str;\n            }\n        };\n    };\n</script>\n```\n\n演示效果：\n\n![](http://img.smyhvae.com/20180228_1740.gif)\n\n\n## Ajax 传输 XML\n\n### XML 语法\n\nXML（Extensible Markup Language）：可扩展标记语言。详细语法可以查看：[#](http://www.w3school.com.cn/xml/index.asp)。\n\n**1、XML 声明：**\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n```\n\n第一行的声明，指定了 XML 版本(1.0)以及使用的编码。\n\n**2、自定义标签：**\n\nXML 中没有默认的标签，所有的标签都是我们自己已定义的。例如：\n\n```xml\n<fox></fox>\n<name></name>\n```\n\nXML 中没有单标签，都是双标签。\n\n**3、根节点：**\n\nXML 中必须要有一个根节点，所有的子节点都放置在根节点下。例如：\n\n```xml\n<root1>\n  <name></name>\n</root1>\n```\n\n### XML 解析\n\n因为 XML 就是标签，所以我们可以直接用**解析 Dom 元素**的方法解析 XML。\n\n**解析过程：**\n\n（1）html 部分：（包含 xml ）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <person id=\"personXML\">\n            <name>fox</name>\n            <age>18</age>\n            <skill>小花花</skill>\n        </person>\n    </body>\n</html>\n```\n\n（2）解析 xml：\n\n```html\n<script type=\"text/javascript\">\n    var xmlObj = document.getElementById('personXML');\n    var name = xmlObj.getElementsByTagName('name')[0].innerHTML;\n\n    console.log(name);\n</script>\n```\n\n### ajax 请求解析 xml（举例）\n\n（1）get_xml.php：（里面包含了 xml 文件）\n\n```php\n<?php\n\t// 设置 返回的为 xml\n\theader('content-type:text/xml; charset= utf-8');\n\n\t// 读取xml文件 并返回\n\techo file_get_contents('info/star.xml');\n\n ?>\n```\n\n上方代码解释：\n\n-   php 自带了 读取 xml 文件的方法。\n\n-   在 php 中，如果要使用 xml 传输数据，则需要使用 header()设置返回的内容为 xml。\n\n（2）get_xml.html：（Ajax 请求，获取并解析 xml）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <input type=\"button\" value=\"获取XMl数据\" id=\"getXML\" />\n    </body>\n</html>\n<script type=\"text/javascript\">\n    document.querySelector('#getXML').onclick = function () {\n        var ajax = new XMLHttpRequest();\n\n        ajax.open('get', 'get_XMl.php');\n\n        ajax.send();\n\n        ajax.onreadystatechange = function () {\n            if (ajax.readyState == 4 && ajax.status == 200) {\n                // 如果 返回的是 xml文件\n                console.log(ajax.responseText);\n\n                // 异步 对象中 有另外一个属性 用来专门获取 xml\n                // xml对象 在浏览器段 就是一个 document对象\n                // 解析时 可以直接使用 querySelector 或者 getElementById等等 document对象 有的语法\n                console.log(ajax.responseXML);\n                console.log(ajax.responseXML.querySelector('kuzi').innerHTML);\n                // 下面这个 页面文档对象 如果要获取某个标签\n                console.log(window.document);\n            }\n        };\n    };\n</script>\n```\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/04-同源和跨域.md",
    "content": "---\ntitle: 04-同源和跨域\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 同源和跨域\n\n## 同源\n\n同源策略是浏览器的一种安全策略，所谓同源是指，域名，协议，端口完全相同。\n\n\n\n## 跨域问题的解决方案\n\n从我自己的网站访问别人网站的内容，就叫跨域。\n\n![](http://img.smyhvae.com/20180228_2231.png)\n\n出于安全性考虑，浏览器不允许ajax跨域获取数据。\n\n\n- iframe：处于安全性考虑，浏览器的开发厂商已经禁止了这种方式。\n\n- JSONP：script 标签的 src 属性传递数据。\n\n## JSONP\n\nJSONP(JSON with Padding)：带补丁的 json，本质是利用了 `<script src=\"\"></script>`标签具有可跨域的特性，由服务端返回一个预先定义好的JS函数的调用，并且将服务器数据以该函数参数的形式传递过来。此方法需要前后端配合完成。\n\n我们知道， html标签的 src 属性是支持跨域的：\n\n```html\n\t<img src=\"http://img.smyhvae.com/2016040101.jpg\" alt=\"\">\n```\n\njsonp 就是利用这个特性实现的跨域，但用的是 script 标签。如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Document</title>\n</head>\n<body>\n\n<!-- jsonp 就是 利用 src，实现的跨域 用的是 script标签 -->\n<script type=\"text/javascript\"  src='http://192.168.141.137/2018-02-28/myData.php'></script>\n</body>\n</html>\n\n```\n\n上方那一行的代码，意思是：刷新A服务器上的index页面后，会去请求 B 服务器上的 `myData.php` 这个页面。而且请求的方式是 get 请求。\n\n但是 B 服务器上的页面不是你想请求就可以请求的，大家一起配合才可以。\n\n\n**具体实现步骤：**\n\n需要首先声明的是，jsonp 只能通过 GET 方式进行请求。\n\n（1）A客户端的代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Document</title>\n</head>\n<body>\n\n</body>\n</html>\n<script type=\"text/javascript\">\n\n\t// 定义 eatFood()方法\n\tfunction fn(data) {\n\t\tconsole.log('我被调用了哦');\n\t\tconsole.log(data);\n\t}\n</script>\n\n<!-- 使用 script标签 发送了 get请求 去到了一个 php页面 -->\n<script type=\"text/javascript\" src='http://192.168.141.137/01.php?callback1=fn'></script>\n```\n\n\n我们来分析上方代码中的最后一行的那个url：A 客户端请求的是 B服务器上的 `01.php`页面。url里有个`callback1=fn`，意思是：callback1是A和B 之间的约定，约定后，将执行方法 fn。\n\n其实，fn方法已经在最后一行代码中执行了。只不过，fn方法里的data数据，是从 B 服务器中获取的。\n\n（2）B服务器端的代码：\n\n```php\n<?php\n    $mycallBack = $_GET['callback1'];\n\n\t$arr = array(\"zhangsan\",\"lisi\",\"zhaoliu\");\n\n    echo $mycallBack.\"(\".json_encode($arr).\")\";    //字符串拼接\n?>\n```\n\n代码解释：\n\n第一行的`callback1` 是A和B之间的约定，二者必须一致。\n\necho语句中输出的内容，即要返回给A客户端的内容，此内容会保存在 A 客户端的fn方法的data里。 data[0]指的是 zhangsan。\n\n\n`json_encode`指的是，将php对象转化为 json。\n\n\n刷新A页面，输出结果为：\n\n```\n\tmycallBack([\"zhangsan\",\"lisi\",\"zhaoliu\"])\n```\n\n\n## jQuery 中的 JSONP\n\n我们知道，jQuery 中发送 Ajax 请求，格式是：\n\n```javascript\n\t\t$(\"#btn\").click(function(){\n\t\t\t$.ajax({\n\t\t\t\turl:\"./data.php?callback1=fn\",\n\t\t\t\tdataType:\"jsonp\",\n\t\t\t\ttype:\"get\",\n\t\t\t\t//jsonp:\"callback1\",   //传递给B服务器的回调函数的名字（默认为 callback）\n\t\t\t\t//jsonCallBack:\"fn\"    //自定义的函数名称。默认为 jQuery 自动生成的随机函数名\n\t\t\t\tsuccess:function(data){\n\t\t\t\t\talert(data);\n\t\t\t\t\t//$(\"#showInfo\").html(data);\n\t\t\t\t},\n\t\t\t\terror:function(e){\n\t\t\t\t\tconsole.log(e);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n```\n\n\n那如果数据是 JSONP，上方代码则改为：\n\n\n```javascript\n\t\t$(\"#btn\").click(function(){\n\t\t\t$.ajax({\n\t\t\t\turl:\"./data.php?fn\",\n\t\t\t\tdataType:\"text\",\n\t\t\t\ttype:\"get\",\n\t\t\t\tsuccess:function(data){\n\t\t\t\t\talert(data);\n\t\t\t\t\t//$(\"#showInfo\").html(data);\n\t\t\t\t},\n\t\t\t\terror:function(e){\n\t\t\t\t\tconsole.log(e);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n```\n\n\n\n## 参考链接\n\n参考链接：https://www.cnblogs.com/2050/p/3191744.html\n\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/05-回调函数.md",
    "content": "---\ntitle: 05-回调函数\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n我们在前面的文章《JavaScript 基础：异步编程/单线程和异步》中讲过，Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时，⼤部分情况都是通过回调函数来进⾏。\n\n（如果你还不了解单线程和异步的概念，可以先去回顾上一篇文章。）\n\n## 回调函数的定义\n\n把函数 A 传给另一个函数 B 调用，那么函数 A 就是回调函数。\n\n例如在浏览器中发送 ajax 网络请求，或者在定时器中执行异步任务，就是最常⻅的异步场景。发送请求后，需要等待一段时间，等服务端响应之后我们才能拿到结果。如果我们希望**等待异步任务结束之后再执⾏想要的操作**，就只能通过**回调函数**这样的⽅式进⾏处理。\n\n```js\n const dynamicFunc = function (callback) {\n  setTimeout(function () {\n    console.log(\"一开始在这里执行异步任务 task1，延迟3秒执行\");\n    // task1： total 计数\n    let total = 0;\n    for (let i = 0; i < 10; i++) {\n      total += i;\n    }\n\n    // 等待异步任务 task1 执行完成后，通过回调传入的 callback() 函数，通知外面的调用者，可以开始做后续任务 task2 了\n    // 如果有需要的话，可以把 task1 的执行结果 total 传给外面。\n    callback && callback(total);\n  }, 3000);\n};\n\n// 执行同步任务 task2。需要先等 异步任务 task1做完。\ndynamicFunc(function (value) {\n  console.log(\"外面监听到，异步任务 task1已经完成了，并且还能拿到 task1的执行结果 value\");\n  console.log(\"task1的返回值value:\" + value);\n\n  // task2：将task1的执行结果乘以2\n  const result = value * 2;\n  console.log(\"result:\" + result);\n});\n```\n\n上⾯的例⼦中，dynamicFunc() 函数里面的 setTimeout()就是⼀个异步函数，在里面执行了一些异步任务，延迟3秒执行。dynamicFunc() 的参数 callback() 就是一个回调函数。这段代码的诉求是：**先等待 异步任务 task1 做完，再做 同步任务task2**。我们来分析一下。\n\n已知异步任务 task1 需要3秒才能做完。**3秒结束后，通知 dynamicFunc 函数的调用者，里面的异步任务 task1 已经做完了，外面可以开始做后续的任务 task2 了**。那要怎么通知呢？在ES5中，最常见的做法就是**需要回调传入的 callback 函数**（也就是回调函数）， 通知外面的调用者。并且，如果有需要的话，外面还可以拿到异步任务task1的执行结果 total（详见代码注释）。\n\n（注：`callback`这个单词并不是关键字，可以自由命名，我们通常习惯性地用“回调”这个词的英文名 callback 代表回调函数。）\n\n## 回调函数的异常处理\n\n实际开发中，为什么会经常存在异步任务呢？这是因为，有很多函数在执行时无法立即完成，我们也不知道它什么时候能完成。但是，我们需要等待它完成后，才能做接下来的事情。换句话说，我们接下来要做的事情，需要依赖此前的异步任务。\n\n比如， ajax 网络请求就是典型的异步任务。在渲染一个页面时，我们需要请求接口，获取页面所需要的数据。等接口请求完成、数据准备好之后，前端就可以对数据进行处理，并将数据渲染到页面了。前端做的这部分事情，就是在回调函数里面做。\n\n当然，异步任务在执行时可能出现异常、错误信息、执行失败等等。当出现异常时，往往导致后续的回调函数无法执行。这就需要在异步任务中将异常信息通知给外部。\n\n代码举例如下：\n\n```js\n// 封装异步任务\nconst dynamicFunc = function (number, successCallback, failureCallback) {\n  setTimeout(function () {\n    console.log('一开始在这里执行异步任务 task1，延迟3秒执行');\n    let total = 0;\n    for (let i = 0; i < 10; i++) {\n      total += i;\n    }\n    if (number > 0) {\n      // 异步任务执行成功\n      successCallback && successCallback(total);\n    } else {\n      // 异步任务执行鼠标\n      failureCallback && failureCallback('异步任务执行失败');\n    }\n  }, 3000);\n};\n\n// 执行异步任务：等待 异步任务 执行完成后，再执行回调函数。\ndynamicFunc(\n  100,\n  (value) => {\n    console.log('异步函数调用成功:' + value);\n    // task2：将task1的执行结果乘以2\n    const result = value * 2;\n    console.log('result:' + result);\n  },\n  (err) => {\n    console.log('异步函数调用失败：', err);\n  }\n);\n```\n\n## 处理异步任务的基本模型\n\n我们以“发送网络请求”为例，通过回调函数处理异步任务时，既有请求成功的情况，也有请求失败的情况。其基本处理模型如下：\n\n（1）调用一个异步函数，在这个函数中发送网络请求（也可以用定时器来模拟异步任务）。\n\n（2）如果网络请求成功，则告知调用者请求成功，并将接口返回的数据传递出去。\n\n（3） 如果网络请求失败，则告知调用者发送失败，并将错误信息传递出去。\n\nES5中，回调函数处理异步任务的基本代码结构如下：\n\n```js\n// ES5中，使用传统的回调函数，处理异步任务的基本模型\n\n// 封装异步任务\nfunction requestData(url, successCallback, failureCallback) {\n  const res = {\n    retCode: 0,\n    data: 'qiangu yihao`s data',\n    errMsg: 'network is error',\n  };\n  setTimeout(() => {\n    if (res.retCode == 0) {\n      // 网络请求成功\n      successCallback(res.data);\n    } else {\n      // 网络请求失败\n      failureCallback(res.errMsg);\n    }\n  }, 1000);\n}\n\n// 调用（请求）异步任务\nrequestData(\n  'www.qianguyihao.com/xxx',\n  // 成功监听\n  res => {\n    console.log('异步任务执行成功:', res);\n  },\n  // 失败监听\n  err => {\n    console.log('异步任务执行失败:', err);\n  }\n);\n```\n\n我们一定要记住这个处理模型，它我们学习异步编程的范式之一。如果前端接下来要做的事情需要依赖这个异步任务、需要等待这个异步任务做完之后才能继续，那就符合上面的处理模型。\n\n## ES5中，回调的缺点（异步代码的困境）\n\n上面的回调函数的写法，都是ES5的写法。ES5中回调的写法比较直观，不需要 return，层层嵌套即可。但也存在两个问题：\n\n-   1. 如果嵌套过深，则会出现**回调地狱**的问题。\n\n-   2. 不同的异步函数，回调的参数，在写法上可能不一致，导致不规范、且需要**单独记忆**。\n\n我们来具体看看这两个问题。\n\n### 1、回调地狱的问题\n\n如果多个异步任务存在依赖关系（比如，需要等第一个异步任务执行完成后，才能执行第二个异步函数；等第二个异步任务执行完毕后，才能执行第三个异步任务），就需要多个异步任务进⾏层层嵌套，⾮常不利于后续的维护，而且会导致**回调地狱**（callback hell）的问题。\n\n简而言之，当一个回调函数嵌套另一个回调函数时，就会出现一个嵌套结构。如果嵌套次数过多，就会出现回调地狱的情况。像下面这样：\n\n![img](https://img.smyhvae.com/callback-hell.jpeg)\n\n\n\n关于回调地狱，我们来举一个形象的例子：\n\n> 假设买菜、做饭、洗碗、倒厨余垃圾都是异步的。\n\n> 但真实的场景中，实际的操作流程是：买菜成功之后，才能开始做饭。做饭成功后，才能开始洗碗。洗碗完成后， 再倒厨余垃圾。这里的一系列动作就涉及到了多层嵌套调用，也就是回调地狱。\n\n关于回调地狱，我们来看看几段代码举例。\n\n1.1、定时器的代码举例：（回调地狱）\n\n```js\nsetTimeout(function () {\n    console.log('qiangu1');\n    setTimeout(function () {\n        console.log('qiangu2');\n        setTimeout(function () {\n            console.log('qiangu3');\n        }, 3000);\n    }, 2000);\n}, 1000);\n```\n\n1.2、Node.js 读取文件的代码举例：（回调地狱）\n\n```js\nfs.readFile(A, 'utf-8', function (err, data) {\n    fs.readFile(B, 'utf-8', function (err, data) {\n        fs.readFile(C, 'utf-8', function (err, data) {\n            fs.readFile(D, 'utf-8', function (err, data) {\n                console.log('qianguyihao:' + data);\n            });\n        });\n    });\n});\n```\n\n上面代码的逻辑为：先读取 A 文本内容，再根据 A 文本内容读取 B，然后再根据 B 的内容读取 C。为了实现这个业务逻辑，上面的代码就很容易形成回调地狱。\n\n1.3、ajax 请求的代码举例：（回调地狱）\n\n```js\n// 伪代码\najax('a.json', (res1) => {\n    console.log(res1);\n    ajax('b.json', (res2) => {\n        console.log(res2);\n        ajax('c.json', (res3) => {\n            console.log(res3);\n        });\n    });\n});\n```\n\n### 2、回调写法不一致的问题\n\n我们需要自己去设计回调函数，包括回调函数的参数格式 、调用方式等等。\n\n```js\n// Node.js 读取文件时，成功回调和失败回调，是通过 error参数来区分\nreadFile('d:\\\\readme.text', function (err, data) {\n    if (error) {\n        console.log('文件读取失败');\n    } else {\n        console.log('文件读取成功');\n    }\n});\n\n// jQuery的 ajax 写法中，成功回调和失败回调，是通过两个回调函数来区分\n$.ajax({\n    url: '/ajax.json',\n    success: function (response) {\n        console.log('文件读取成功');\n    },\n    error: function (err) {\n        console.log('文件读取失败');\n    },\n});\n```\n\n我们可以看到，上面的回调函数的代码中，成功回调和失败回调，**参数的写法不一致**。在实战开发中，**封装异步函数的人和调用异步函数的人，往往不是同一个人**。甚至可能出现的极端的情况是，回调函数里需要传很多参数，**参数的顺序也不一致**，各有各的风格，每个人写得都不一样。因为这种回调参数的写法**不一致、不规范**的问题，所以需要单独记忆，导致在调用时需要小心翼翼，很容易出错。\n\n### 小结\n\n按照上面的分析，在 ES5 中处理异步任务时，产生的这两个问题，ES6 中的 Promise 就可以解决。当然， Promise 的强大功能，不止于此。我们去下一篇文章一探究竟。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "06-JavaScript基础：异步编程/06-Promise入门详解.md",
    "content": "---\ntitle: 06-Promise入门详解\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 前言\n\n\n Promise 是 JavaScript 中特有的语法。可以毫不夸张得说，Promise 是ES6中最重要的语法，没有之一。初学者可能对 Promise 的概念有些陌生，但是不用担心。大多数情况下，使用 Promise 的语法是比较固定的。我们可以先把这些固定语法和结构记下来，多默写几遍；然后在实战开发中逐渐去学习和领悟 Promise 的原理、底层逻辑以及细节知识点，自然就慢慢掌握了。\n\n在了解 Promise 之前，必须要知道什么是回调函数，这是必不可少的前置知识。关于回调函数的知识，已经在上一篇文章中做了讲解。\n\n## Promise 的介绍和优点（为什么需要 Promise？）\n\nPromise 是异步编程的一种**新的解决方案和规范**。ES6将其写进了语言标准，统一了用法，原生提供了 Promise 对象。\n\nPromise 对象, 可以**用同步的表现形式来书写异步代码**（也就是说，代码看起来是同步的，但本质上的运行过程是异步的）。使用 Promise 主要有以下优点：\n\n-   1、可以很好地解决ES5中的**回调地狱**的问题（避免了层层嵌套的回调函数）。\n-   2、统一规范、语法简洁、可读性和和可维护性强。\n-   3、Promise 对象提供了简洁的 API，使得管理异步任务更方便、更灵活。\n\n从语法上讲，Promise 是一个构造函数。从功能上来说，Promise 对象用于封装一个异步操作，并获取其成功/ 失败的结果值。\n\n从写法规范上讲，**Promise 本质上是处理异步任务的一种编写规范**，要求每个人都按照这种规范来写。异步任务成功了该怎么写、异步任务失败了该怎么写、成功或者失败之后怎么通知调用者，这些都有规定的写法。Promise 的目的就是要让每个使用ES6的人都遵守这种写法规范。\n\nPromise 的伪代码结构，大概是这样的：\n\n```js\n// 伪代码1\nmyPromise()\n    .then(\n        function () {},\n        function () {}\n    )\n    .then(\n        function () {},\n        function () {}\n    )\n    .then(\n        function () {},\n        function () {}\n    );\n\n// 伪代码2\n是时候展现真正的厨艺了().然后(买菜).然后(做饭).然后(洗碗);\n```\n\n上面的伪代码可以看出，业务逻辑上层层递进，但是代码写法上却十分优雅，没有过多的嵌套。\n\n## Promise 的基本使用\n\nES5中，使用传统的回调函数处理异步任务时，其基本模型的写法已在上一篇内容“回调函数”里讲过。\n\nES6中，有了 Promise之后，我们可以对那段代码进行改进（基本模型不变）。你会发现，代码简洁规范了许多。\n\n使用 Promise 处理异步任务的**基本代码结构**如下，我们先来认识一下：\n\n```js\n// 使用 Promise 处理异步任务的基本模型\n\n// 封装异步任务\nfunction requestData(url) {\n  // resolve 和 reject 这两个单词是形参，可以自由命名。大家的习惯写法是写成 resolve 和 reject\n  const promise = new Promise((resolve, reject) => {\n    const res = {\n      retCode: 0,\n      data: 'qiangu yihao`s data',\n      errMsg: 'not login',\n    };\n    setTimeout(() => {\n      if (res.retCode == 0) {\n        // 网络请求成功\n        resolve(res.data);\n      } else {\n        // 网络请求失败\n        reject(res.errMsg);\n      }\n    }, 1000);\n  });\n  return promise;\n}\n\n\n// 调用异步任务\nrequestData('www.qianguyihao.com/index1').then(data => {\n  console.log('异步任务执行成功:', data);\n}).catch(err=> {\n  console.log('异步任务执行失败:', err);\n})\n\n// 再次调用异步任务\nrequestData('www.qianguyihao.com/index2').then(data => {\n  console.log('异步任务再次执行成功:', data);\n}).catch(err=> {\n  console.log('异步任务再次执行失败:', err);\n})\n\n\n// 调用异步任务（写法2）\n/* 这段代码的写法比较啰嗦。一般推荐上面的写法。\nconst myPromise = requestData('www.qianguyihao.com/index1');\nmyPromise.then(data => {\n  console.log('异步任务执行成功:', data);\n});\nmyPromise.catch(err => {\n  console.log('异步任务执行失败:', err);\n});\n\nconst myPromise2 = requestData('www.qianguyihao.com/index2');\nmyPromise2.then(data => {\n  console.log('异步任务执行成功:', data);\n});\nmyPromise2.catch(err => {\n  console.log('异步任务执行失败:', err);\n});\n*/\n```\n\n在日常开发中使用Promise时，80%以上的场景都符合上面的代码结构。你说它重不重要？我们暂且先记下，默写十遍，形成肌肉记忆，然后继续往下边学习边理解。\n\n## Promise 的状态和回调函数\n\nPromise的三种状态是我们需要学习的第一个概念，也是最重要的概念，理解它才能理解 Promise的用法。\n\n### Promise 对象的 3 种状态\n\n在使用  Promise 时，我们可以将它划分为三种状态：\n\n-   `pending`：等待中。属于初始状态，既没有被兑现，也没有被拒绝。\n\n-   `fulfilled`：已兑现/已解决/成功。执行了`resolve()` 时，立即处于该状态，表示 Promise已经被**解决**，任务**执行成功**。\n\n-   `rejected`：已拒绝/失败。执行了 `reject()`时，立即处于该状态，表示 Promise已经被**拒绝**，任务**执行失败**。\n\n具体解释：\n\n1、Promise 的中文名翻译为“承诺”（一般不称呼中文名）。resolve 的中文翻译为“解决”，reject 的中文翻译为“拒绝”。\n\n2、当 new Promise()执行之后，promise 对象的状态会被初始化为`pending`，这个是初始状态。`new Promise()`这行代码，括号里的内容是同步执行的。括号里可以再定义一 异步任务的 function，function 有两个参数：resolve 和 reject。如下：\n\n-   如果异步任务成功了，请执行 resolve()，此时，promise 的状态会自动变为 fulfilled。\n\n-   如果异步任务失败了，请执行 reject()，此时，promise 的状态会自动变为 rejected。\n\n3、什么时候算成功，什么时候算失败呢？这是**你自己定**的，需要结合具体需求和业务逻辑灵活决定。\n\n关于 promise 的状态改变，以及如何处理状态改变，伪代码及详细注释如下：\n\n```javascript\n// 创建 promise 实例\nconst promise = new Promise((resolve, reject) => {\n  //进来之后，promise 的状态为 pending\n  console.log('同步代码'); //这行代码是同步的\n  //开始执行异步操作（这里开始，根据具体需求写异步的代码，比如ajax请求 or 开启定时器）\n  if (异步的ajax请求成功) {\n    console.log('233');\n    // 如果请求成功了，请写resolve()，此时，promise的状态会自动变为fulfilled（成功状态）\n    resolve('请求成功，并传参');\n  } else {\n    // 如果请求失败了，请写reject()，此时，promise的状态会被自动变为rejected（失败状态）\n    reject('请求失败，并传参');\n  }\n});\nconsole.log('qianguyihao');\n\n//调用promise的then()：开始处理成功和失败\npromise.then(\n  successValue => {\n    // 处理 promise 的成功状态：如果promise的状态为fulfilled，则执行这里的代码\n    console.log(successValue, '回调成功了'); // 这里的 successMsg 是前面的 resolve('请求成功，并传参')  传过来的参数\n  },\n  errorMsg => {\n    //处理 promise 的失败状态：如果promise的状态为rejected，则执行这里的代码\n    console.log(errorMsg, '回调失败了'); // 这里的 errorMsg 是前面的 reject('请求失败，并传参') 传过来的参数\n  }\n);\n```\n\n上面的注释要多看几遍。\n\n### Promise 的回调函数\n\nPromise的回调函数，伪代码如下：\n\n```js\nconst promise = new Promise(executor);\n\n// 【划重点】下面这两行代码是等价的，选其中一种写法即可。这两种写法没有区别，只是写法形式上的区别\npromise.then(onFulfilled, onRejected);\n\npromise.then(onFulfilled).catch(onRejected);\n```\n\nPromise是一个类，通过 `new Promise()` 进行**实例化**，构造出一个 Promise 实例对象。\n\n1、Promise 的构造函数中需要传入一个参数，这个参数是一个回调函数，常用于处理异步任务。这个回调函数有一个专有名词叫  **executor**（执行器），因为在 `new Promise()` 时，这个函数会**立即执行**。\n\n可以在该回调函数中传入两个参数：resolve 和 reject。我们可以在适当的时机执行 resolve()、reject()，用于改变当前 Promise 实例的状态到**成功**或**失败**。\n\n（2）当Promise状态变为成功时，会触发 then() 方法里的回调函数的执行，对成功的返回结果进行处理。\n\n（3）当Promise状态变为失败时，会触发 catch() 方法里的回调函数的执行，，对失败的返回结果进行处理。\n\n2、`then()`方法的括号里面有两个参数，分别代表两个回调函数 **onFulfilled** 和 **onRejected**，这两个函数一直处于**监听状态**：\n\n-   参数1：**成功的回调函数**。如果 Promise 的状态为 fulfilled（意思是：任务执行成功），则触发 onFulfilled 函数的执行。\n\n-   参数2：**失败的回调函数**。如果 Promise 的状态为 rejected（意思是，任务执行失败），则触发 onRejected 函数的执行。\n\n3、**只有 Promise 的状态被改变之后，才会走到 then() 或者 catch()**。也就是说，在 new Promise() 时，如果没有写 resolve()，则 promise.then() 不执行；如果没有写 reject()，则 promise.catch() 不执行。\n\n4、resolve()和 reject()这两个方法，可以给 promise.then()、promise.catch()传递参数。\n\n5、then() 可以被多次调用，会按照顺序执行。比如：\n\n```js\nconst promise = new Promise(executor);\n\n// then() 可以被多次调用\npromise.then(onFulfilled, onRejected);\npromise.then(onFulfilled, onRejected);\n```\n\n### Promise的状态图\n\n![image-20230624100254023](https://img.smyhvae.com/image-20230624100254023.png)\n\n上面的Promise状态图很经典，需要反复研读，了然于胸。\n\n### Promise 的状态一旦改变，就不能再变\n\nPromise 的状态一旦改变，就确定下来了，不能再变。也不能再次执行 resolve()或者 reject()来改变状态。Promise 的状态改变，是不可逆的。\n\n代码举例：\n\n```js\nconst p = new Promise((resolve, reject) => {\n    resolve(1); // 代码执行到这里时， promise状态是 fulfilled\n  \tresolve(111); // 这行重复代码写了没用，等于没写\n    reject(2); // 尝试修改状态为 rejected，是不行的。因为状态执行到上面的 resolve(1)时，已经被改变了。\n});\n\np.then((res) => {\n    console.log(res);\n}).catch((err) => {\n    console.log(err);\n});\n```\n\n上方代码的打印结果是 1，而不是 2，详见注释。\n\n### new Promise() 是同步代码\n\n`new Promise()`这行代码本身是同步的。promise 如果没有使用 resolve 或 reject 更改状态时，状态为 pending，里面的代码是同步代码。\n\n**举例 1**：（重要）\n\n```js\n// 会立即创建 Promise 实例\nconst promise1 = new Promise((resolve, reject) => {\n  // 这行代码会立即执行\n  console.log('qianguyihao1');\n})\n\nconsole.log(promise1); // 此时 promise1 的状态为 pending（准备阶段）\n\n// 需要调用 promise2函数，才会创建 Promise 实例\nfunction promise2() {\n  return new Promise((resolve, reject) => {\n    // 这行代码不会立即执行\n    console.log('qianguyihao2');\n  })\n}\n```\n\n上面的代码中，我既没有写 reslove()，也没有写 reject()。那么，Promise 一直处于准备阶段。\n\n此外，需要特别注意的是，promise1 中的 console.log() 会**立即执行**，因为**Promise的执行器函数在创建 Promise 实例时就会被调用，并立即开始执行其中的代码逻辑**。\n\n**举例 2**：\n\n```js\nnew Promise((resolve, reject) => {\n    console.log('promise1'); // 这行代码是同步代码，会立即执行\n}).then((res) => {\n    console.log('promise then:' + res); // 这行代码不会执行，因为前面没有写 resolve()，所以走不到 .then\n});\n```\n\n打印结果：\n\n```\npromise1\n```\n\n上方代码，仔细看注释：如果前面没有写 `resolve()`，那么后面的 `.then`是不会执行的。\n\n**举例 3**：\n\n```js\nnew Promise((resolve, reject) => {\n    resolve();\n    console.log('promise1'); // 代码1：同步任务，会立即执行\n}).then(res => {\n    console.log('promise  then'); // 代码2：异步任务中的微任务\n});\n\nconsole.log('千古壹号'); // 代码3：同步任务\n```\n\n打印结果：\n\n```\npromise1\n千古壹号\npromise  then\n```\n\n代码解释：\n\n当完成异步任务之后，状态分为成功或失败，此时我们就可以用 reslove() 和 reject() 来修改 promise 的状态。\n\n代码 1 是同步代码，所以最先执行。代码 2 是**微任务**里面的代码，所以要先等同步任务（代码 3）先执行完。当写完`resolve();`之后，就会立刻把 `.then()`里面的代码加入到微任务队列当中。\n\n补充知识：异步任务分为“宏任务”、“微任务”两种。我们到后续的章节中再详细讲。\n\n\n\n## Promise 封装定时器\n\n### 传统写法\n\n写法 1：\n\n```js\n// 定义一个异步的延迟函数：异步函数结束1秒之后，再执行cb回调函数\nfunction fun1(cb) {\n    setTimeout(function () {\n        console.log('即将执行cb回调函数');\n        cb();\n    }, 1000);\n}\n\n// 先执行异步函数 fun1，再执行回调函数 myCallback\nfun1(myCallback);\n\n// 定义回调函数\nfunction myCallback() {\n    console.log('我是延迟执行的cb回调函数');\n}\n```\n\n写法 2：（精简版，更常见）\n\n```js\n// 定义一个异步的延迟函数：异步函数结束1秒之后，再执行cb回调函数\nfunction fun1(cb) {\n    setTimeout(cb, 1000);\n}\n\n// 先执行异步函数fun1，再执行回调函数\nfun1(function () {\n    console.log('我是延迟执行的cb回调函数');\n});\n```\n\n上⾯的例⼦就是最传统的写法，在异步结束后通过传入回调函数的方式执⾏函数。\n\n学习 Promise 之后，我们可以将这个异步函数封装为 Promise，如下。\n\n### Promise 写法\n\n```js\nfunction myPromise() {\n    return new Promise((resolve) => {\n        setTimeout(resolve, 1000);\n    });\n}\n\n/* 【重要】上面的 myPromise 也可以写成：\nfunction myPromise() {\n    return new Promise((resolve) => {\n        setTimeout(() => {\n            resolve();\n        }, 1000);\n    });\n}\n*/\n\n// 先执行异步函数 myPromise，再执行回调函数\nmyPromise().then(() => {\n    console.log('我是延迟执行的回调函数');\n});\n```\n\n## Promise 封装 Ajax 请求\n\n### 传统写法\n\n```js\n// 封装 ajax 请求：传入回调函数 success 和 fail\nfunction ajax(url, success, fail) {\n    var xmlhttp = new XMLHttpRequest();\n    xmlhttp.open('GET', url);\n    xmlhttp.send();\n    xmlhttp.onreadystatechange = function () {\n        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {\n            success && success(xmlhttp.responseText);\n        } else {\n            // 这里的 && 符号，意思是：如果传了 fail 参数，就调用后面的 fail()；如果没传 fail 参数，就不调用后面的内容。因为 fail 参数不一定会传。\n            fail && fail(new Error('接口请求失败'));\n        }\n    };\n}\n\n// 执行 ajax 请求\najax(\n    '/a.json',\n    (res) => {\n        console.log('qianguyihao 第一个接口请求成功:' + JSON.stringify(res));\n    },\n    (err) => {\n        console.log('qianguyihao 请求失败:' + JSON.stringify(err));\n    }\n);\n```\n\n上面的传统写法里，定义和执行 ajax 时需要传⼊ success 和 fail 这两个回调函数，进而执行回调函数。\n\n注意看注释，`callback && callback()`这种格式的写法，很常见。\n\n### Promise 写法\n\n有了 Promise 之后，我们不需要传入回调函数，而是：\n\n-   先将 promise 实例化；\n\n-   然后在原来执行回调函数的地方，改为执行对应的改变 promise 状态的函数；\n\n-   并通过 then ... catch 或者 then ...then 等写法，实现链式调用，提高代码可读性。\n\n和传统写法相比，promise 在写法上的大致区别是：定义异步函数的时候，将 callback 改为 resolve 和 reject，待状态改变之后，我们在外面控制具体执行哪些函数。\n\n写法 1：\n\n```js\n// 封装 ajax 请求：传入回调函数 success 和 fail\nfunction ajax(url, success, fail) {\n    var xmlhttp = new XMLHttpRequest();\n    xmlhttp.open('GET', url);\n    xmlhttp.send();\n    xmlhttp.onreadystatechange = function () {\n        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {\n            success && success(xmlhttp.responseText);\n        } else {\n            // 这里的 && 符号，意思是：如果传了 fail 参数，就调用后面的 fail()；如果没传 fail 参数，就不调用后面的内容。因为 fail 参数不一定会传。\n            fail && fail(new Error('接口请求失败'));\n        }\n    };\n}\n\n// 第一步：model层的接口封装\nfunction promiseA() {\n    return new Promise((resolve, reject) => {\n        ajax('xxx_a.json', (res) => {\n            // 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。\n            if (res.retCode == 0) {\n                // 接口请求成功时调用\n                resolve('request success' + res);\n            } else {\n                // 接口请求失败时调用\n                reject({ retCode: -1, msg: 'network error' });\n            }\n        });\n    });\n}\n\n// 第二步：业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的，也就是从接口拿到的数据\npromiseA()\n    .then((res) => {\n        // 从 resolve 获取正常结果：接口请求成功后，打印接口的返回结果\n        console.log(res);\n    })\n    .catch((err) => {\n        // 从 reject 获取异常结果\n        console.log(err);\n    });\n```\n\n上方代码中，当从接口返回的数据`data.retCode`的值（接口返回码）不同时，可能会走 resolve，也可能会走 reject，这个由你自己的业务决定。\n\n接口返回的数据，一般是`{ retCode: 0, msg: 'qianguyihao' }` 这种 json 格式， retCode 为 0 代表请求接口成功，所以前端对应会写`if (res.retCode == 0) `这样的逻辑。\n\n另外，上面的写法中，是将 promise 实例定义成了一个**函数** `promiseA`。我们也可以将 promise 实例定义成一个**变量** `promiseB`，达到的效果和上面的代码是一模一样的。写法如下：（写法上略有区别）\n\n写法 2：\n\n```js\n// 第一步：model层的接口封装\nconst promiseB = new Promise((resolve, reject) => {\n    ajax('xxx_a.json', (res) => {\n        // 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。\n        if (res.retCode == 0) {\n            // 接口请求成功时调用\n            resolve('request success' + res);\n        } else {\n            // 接口请求失败时调用\n            reject({ retCode: -1, msg: 'network error' });\n        }\n    });\n});\n\n// 第二步：业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的，也就是从接口拿到的数据\npromiseB\n    .then((res) => {\n        // 从 resolve 获取正常结果\n        console.log(res);\n    })\n    .catch((err) => {\n        // 从 reject 获取异常结果\n        console.log(err);\n    });\n```\n\n注意，如果你用的是写法 1（将 promise 实例定义为函数），则调用 promise 的时候是`promiseA().then()`，如果你用的是写法 2（将 promise 实例定位为函数），则调用的时候用的是`promiseB.then()`。写法 1 多了个括号，不要搞混了。\n\n## resolve() 传入的参数（重要）\n\n执行 resolve()之后，Promise 的状态一定会变成 fulfilled 吗？这是不一定的。\n\n严格来说，在我们调用 resolve 时，如果 resolve()的参数中传入的值**本身不是一个Promise**，那么会将该 promise 的状态变成 fulfilled。\n\nresolve()的参数中，可以传入哪些值，Promise会进入哪种状态呢？具体情况如下：\n\n- 情况1：如果resolve()中传入**普通的值或者普通对象**（包括 undefined），那么Promise 的状态为fulfilled。这个值会作为then()回调的参数。这是最常见的情况。\n- 情况2：如果resolve()中传入的是**另外一个新的 Promise**，那么原 Promise 的状态将**交给新的 Promise 决定**。\n- 情况3：如果resolve()中传入的是一个对象，并且这个对象里有实现then()方法（这种对象称为 **thenable** 对象），那就会执行该then()方法，并且根据**then()方法的结果来决定Promise的状态**。\n\n情况3中，我们通常称这个对象为 thenable 对象。thenable 的意思是，在某个对象或者函数中定义了一个 then() 方法，我们就称其为 thenable 对象/thenable函数。注意，thenable对象里面的那个单词只能写 then，不能写其他的单词；如果写其他的单词，就不是 thenable 对象了，就不符合情况3，而是符合情况1。\n\n扩展一下：reject()的参数中可以传入什么值呢？无论传入什么值，Promise 都会直接进入 rejected 状态，并触发 catch() 方法的执行。\n\n情况1的代码已经在前面反复出现过了，接下来重点讲一下情况2 和情况3。\n\n### resolve() 中传入新的 Promise\n\n代码举例：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  resolve(promise2);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  reject('promise2 的 reject');\n});\n\npromise1\n  .then(res => {\n    console.log('qianguyihao then');\n    console.log(res);\n  })\n  .catch(err => {\n    console.log('qianguyihao catch');\n    console.log(err);\n  });\n```\n\n打印结果：\n\n```\nqianguyihao catch\npromise2 的 reject\n```\n\n代码解释：\n\npromise1 在执行resolve时，传入的是 promise2。那么，promise1接下来的状态将交给 promise2 处理。因为 promise2 执行的是 reject()，所以 promise1 的状态进入 rejected，执行 catch() 方法。\n\n上方代码中，如果把 promise1 和 promise2  的顺序换一下的话， 代码会报错：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  resolve(promise2);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  reject('promise2 的 reject');\n});\n\npromise1\n  .then(res => {\n    console.log('qianguyihao then');\n  })\n  .catch(err => {\n    console.log('qianguyihao catch');\n    console.log(err);\n  });\n```\n\n报错如下：\n\n![image-20230520224902096](https://img.smyhvae.com/image-20230520224902096.png)\n\n### resolve()中传入 thenable 对象\n\n代码举例：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  // resolve 里传入了一个 thenable 对象，里面有一个 then()方法，then()方法里执行的是 reject()\n  resolve({\n    name: 'qianguyihao',\n    then: (resolve, reject) => {\n      // 可以执行 resolve，也可以执行 reject，这里以 reject 为例\n      reject('thenable reject');\n    },\n  });\n});\n\npromise1\n  .then(res => {\n    console.log('qianguyihao then');\n    console.log(res);\n  })\n  .catch(err => {\n    console.log('qianguyihao catch');\n    console.log(err);\n  });\n```\n\n打印结果：\n\n```\nqianguyihao catch\nthenable reject\n```\n\n代码解释：\n\npromise1 在执行resolve时，传入的是一个 thenable 对象。thenable 对象里有一个 then()方法。那么，promise1接下来的状态将由 thenable 对象 里的 then() 方法决定。当前的代码中， then() 里执行的是 reject()，所以 promise1 的状态进入 rejected，执行 catch() 方法。\n\n上方代码中，如果把 thenable 对象里的单词`then`改成`then1`会怎么样呢？那它就不是 thenable 对象，只是一个普通的对象，代码如下：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  // resolve 里传入了一个 thenable 对象，里面有一个 then()方法，then()方法里执行的是 reject()\n  resolve({\n    name: 'qianguyihao',\n    // 把 单词 then 改成 then1，就不符合 thenable 对象 的特征了\n    then1: (resolve, reject) => {\n      reject('thenable resolve');\n    },\n  });\n});\n\npromise1\n  .then(res => {\n    console.log('qianguyihao then');\n    console.log(JSON.stringify(res));\n  })\n  .catch(err => {\n    console.log('qianguyihao catch');\n    console.log(err);\n  });\n```\n\n打印结果：\n\n```\nqianguyihao then\n{\"name\":\"qianguyihao\"}\n```\n\n\n\n## 小结\n\n学习这些内容之后， 相信你已经对 Promise 有了基本了解。下一篇文章，我们来讲一讲 Promise 在实战开发的常见用法。\n\n## 参考链接\n\n- [Promise 注册微任务和执行过程](https://juejin.cn/post/6844903987183894535)\n- [深入分析 Promise 实现细节](https://juejin.cn/post/6953452438300917790)\n\n-   [当面试官问你 Promise 的时候，他究竟想听到什么？](https://zhuanlan.zhihu.com/p/29235579)\n\n- [手写一个 Promise/A+,完美通过官方 872 个测试用例](https://www.cnblogs.com/dennisj/p/12660388.html)\n\n-   [从 Promises/A+ 看异步流控制 - Liu Bowen](https://set.sh/post/200215-how-promise-works)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/07-Promise实例的方法.md",
    "content": "---\ntitle: 07-Promise实例的方法\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## Promise 实例的方法简介\n\nPromise 的 API 分为两种：\n\n- Promise 实例的方法（也称为：Promis的实例方法）\n- Promise 类的方法（也称为：Promise的静态方法）\n\nPromise **实例**的方法：我们需要实例化 Promise，也就是先 new 一个 Promise 实例对象，然后通过 Promise 实例去调用 `then`、`catch`、`finally`等方法。这几个方法就是 Promise 的实例方法。\n\nPromise 实例提供了如下方法：\n\n-   promise.then()：异步任务成功的回调函数。\n\n-   promise.catch()：异步任务失败的回调函数。\n\n-   promise.finaly()：异步任务无论成功与否，都会执行的回调函数。\n\n我们在上一篇文章《Promise入门详解》中，用到的都是Promise实例的方法。本篇文章，我们来把这三个实例方法详细学习一下。\n\n## Promise 实例的 then()方法\n\nthen()方法是 Promise实例上的一个方法。它其实是放在Promise的原型上的 `Promise.prototype.then`。\n\n###  then()方法的参数\n\nthen()方法可以接收一个参数，也可以接收两个参数。两个参数时，分别代表两个回调函数，这两个函数一直处于**监听状态**：\n\n-   参数1：当 Promise 的状态变为 fulfilled（意思是：任务执行成功）时会立即执行的回调函数。\n\n-   参数2：当 Promise 的状态为 rejected（任务执行失败）时会立即执行的回调函数。\n\n下面这两种写法是等价的。处理 rejected 失败状态的回调函数，既可以放在 then() 方法的第二个参数里，也可以单独放在 catch() 方法的参数里。\n\n写法1：\n\n```js\nconst promise = new Promise((resolve, reject) => {\n  reject('qianguyihao');\n});\n\npromise.then(\n  res => {\n    console.log('res:', res);\n  },\n  err => {\n    console.log('err:', err);\n  }\n);\n```\n\n写法2：\n\n```js\nconst promise = new Promise((resolve, reject) => {\n  reject('qianguyihao');\n});\n\npromise\n  .then(res => {\n    console.log('res:', res);\n  })\n  .catch(err => {\n    console.log('err:', err);\n  });\n```\n\n### then()方法可以被多次调用\n\n一个 Promise 的 then() 方法可以被多次调用。每次调用时我们都可以传入对应fulfilled状态的回调函数。当 Promise 的状态变为 fulfilled 时，这些回调函数都会被执行。\n\nthen被调用多次的伪代码：\n\n```js\nconst myPromise = new Promise();\n\nmyPromise.then();\nmyPromise.then();\nmyPromise.then();\n```\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao');\n});\n\nmyPromise.then(res => {\n  console.log('成功回调1');\n  console.log('res1:', res);\n});\n\nmyPromise.then(res => {\n  console.log('成功回调2');\n  console.log('res2:', res);\n});\n\nmyPromise.then(res => {\n  console.log('成功回调3');\n  console.log('res3', res);\n});\n```\n\n打印结果：\n\n```\n成功回调1\nres1: qianguyihao\n\n成功回调2\nres2: qianguyihao\n\n成功回调3\nres3 qianguyihao\n```\n\n代码解释：\n\n当 myPromise 状态为 fulfilled 时，下面的四个 then() 方法**都在监听**，所以这四个 then() 方法都会收到状态确定的通知，进而都会执行。\n\n此外，then() 被调用多次还有一种**链式调用**的写法，它的打印结果与上面的打印结果不同，想要了解 Promise 的链式调用，需要先学习 then() 方法的返回值，我们继续往下看。\n\n## then() 方法的返回值（重要）\n\n> 这一段的知识点略有难度，但是非常重要，是我们学习 Promise 链式调用的理论基础。\n\nthen()方法本身是有返回值的，它会返回一个**新的Promise对象**。因为 then()方法的返回值永远是一个 Promise 对象，所以我们才可以对它进行**链式调用**。\n\nPromise 链式调用的伪代码：\n\n ```js\n// 伪代码\nmyPromise.then().then().catch()\n ```\n\n上方代码中，因为 myPromise.then() 的返回值本身就是一个 Promise，所以才可以继续调用 then()、继续调用 catch()。\n\n那么，**then()方法返回的 Promise 对象处于什么状态呢**？then()方法的参数里，是一个回调函数。这取决于回调函数的返回值是什么。情况如下：\n\n1、当then()方法中的回调函数在执行时，那么Promise 处于pending状态。\n\n2、当 then()方法中的回调函数中，手动 return 一个返回值时，那么 Promise 的状态取决于返回值的类型。当返回值这行代码执行完毕后， Promise 会立即决议，进入确定状态（成功 or 失败）。具体情况如下：\n\n- 情况1：如果没有返回值（相当于 return undefined），或者返回值是**普通值/普通对象**，那么 Promise 的状态为fulfilled。这个值会作为fulfilled 状态的回调函数的参数值。\n- 情况2：如果返回值是**另外一个新的 Promise**，那么原 Promise 的状态将**交给新的 Promise 决定**，这两个Promise 的状态一致。\n- 情况3：如果返回值是一个对象，并且这个对象里有实现then()方法（这种对象称为 **thenable** 对象），那就会执行该then()方法，并且根据**then()方法的结果来决定Promise的状态**。\n\n还有一种特殊情况：\n\n- 情况4：当then()方法传入的回调函数遇到异常或者手动抛出异常时，那么， Promise 处于rejected 状态，并将抛出的错误作为 rejected 状态的回调函数的参数值。\n\n**小结**：then()方法里，我们可以通过 return **传递结果和状态**给下一个新的Promise。\n\n### 默认返回值\n\n如果then()方法的回调函数里没写返回值（相当于 return undefined），那么then()方法的返回值是一个新的Promise。新 Promise 的状态为fulfilled，其then()方法里，res的值为 undefined。\n\nthen() 链式调用的代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao');\n});\n\nmyPromise\n  .then(res => {\n    console.log('成功回调1');\n    console.log('res1:', res);\n    /*\n    这里虽然什么都没写，底层默认写了如下代码：\n    return new Promise((resolve, reject) => {\n  \t\tresolve(); // resolve() 的参数是空，相当于 resolve(undefined)\n    })\n    */\n  })\n  .then(res => {\n    console.log('成功回调2');\n    console.log('res2:', res);\n  })\n  .then(res => {\n    console.log('成功回调3');\n    console.log('res3', res);\n  });\n```\n\n打印结果：\n\n```\n成功回调1\nres1: qianguyihao\n\n成功回调2\nres2: undefined\n\n成功回调3\nres3：undefined\n```\n\n代码解释：\n\n第一个 then()里的回调，是由 myPromise 进行决议。第二个then()、第三个then() 也在**等待决议**。\n\n但是，**第二个 then() 的回调是由第一个 then()传入的回调函数，返回的 Promise 进行决议**；第三个 then() 的回调是由第二个 then()传入的回调函数，返回的 Promise 进行决议，以此类推。所以，这两个then()里面的打印参数的结果是 undefined，并没有打印 myPromise 的决议结果。\n\n换句话说，第一个 then() 在等待 myPromise 的决议结果，有决议结果后执行；第二个 then() 在等待第一个 then()参数里返回的新 Promise的决议结果，有决议结果后执行；第三个 then() 在等待第二个 then()参数里返回的新 Promise的决议结果，有决议结果后执行。\n\n### 返回普通值：通过 return 传递数据结果\n\n我们也可以在 then()方法的回调函数里，手动 return 自己想要的数据，比如一个普通值 value1。这个普通值就可以传递给下一个新的Promise。新 Promise 的状态为fulfilled，其then()方法里，res的值为 value1。\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('1号');\n});\n\nmyPromise\n  .then(res => {\n    console.log('res1:', res);\n    // return一个普通值，把这个值传递给下一个Promise\n    return '2号';\n  \t/*\n  \t上面这行 return，相当于：\n  \treturn new Promise((resolve, reject)=> {\n  \t\tresolve('2号');\n  \t})\n  \t*/\n  })\n  .then(res => {\n  \t// res可以接收到上一个 Promise 传递的值\n    console.log('res2:', res);\n  })\n  .then(res => {\n    console.log('res3:', res);\n  });\n```\n\n返回结果：\n\n```\nres1: 1号\nres2: 2号\nres3: undefined\n```\n\n### 返回新的 Promise\n\n情况1、在 then() 方法的回调函数中 return 一个成功的新 Promise，那么，then()返回的Promise 也是成功状态。相当于把新Promise的成功结果传递出去。代码举例：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 1');\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 2');\n});\n\npromise1\n  .then(res => {\n    console.log('res1:', res);\n    return promise2;\n  })\n  .then(res => {\n    // 监听 promise2 的成功状态\n    console.log('res2:', res);\n  })\n  .then(res => {\n    console.log('res3', res);\n  });\n```\n\n打印结果：\n\n```\nres1: qianguyihao fulfilled 1\nres2: qianguyihao fulfilled 2\nres3 undefined\n```\n\n情况2、在 then() 方法的回调函数中 return 一个失败的新 Promise，那么，then()返回的Promise 也是失败状态。再继续往下走，会怎么样？相当于把新Promise 的失败原因传递出去。代码举例：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 1');\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  reject('qianguyihao rejected 2');\n});\n\npromise1\n  .then(res => {\n    console.log('res1:', res);\n    // return 一个 失败的 Promise\n    return promise2;\n  })\n  .then(res => {\n    console.log('res2:', res);\n  }, err => {\n    // 如果 promise2 为失败状态，可以通过 then() 的第二个参数（即失败的回调函数）捕获异常，然后就可以继续往下执行其他的代码\n    console.log('err2:', err);\n   // 这里相当于 return undefined\n  })\n  .then(res => {\n    console.log('res3', res);\n  }, err => {\n    console.log('err3:', err);\n  });\n```\n\n打印结果：\n\n```\nres1: qianguyihao fulfilled 1\nerr2: qianguyihao rejected 2\nres3: undefined\n```\n\n上方代码可以看到，第二个Promise走的是失败回调，这很容易理解。重点是，最后一个 Promise 走的是成功回调，这很出人意料。我们稍后学习 catch()方法的返回值后，就能看懂。**这例子很经典，一定要记住**。\n\n情况3：在 then() 方法的回调函数中 return 一个 pending 状态的新 Promise，那么 then() 返回的Promise状态也是 pending。\n\n### 返回 thenable 对象\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 1');\n});\n\nmyPromise\n  .then(res => {\n    console.log('res1:', res);\n    return {\n      then: (resolve, reject) => {\n        resolve('thenable fulfilled');\n      },\n    };\n  })\n  .then(res => {\n    console.log('res2:', res);\n  })\n  .then(res => {\n    console.log('res3', res);\n  });\n```\n\n打印结果：\n\n```\nres1: qianguyihao fulfilled 1\nres2: thenable fulfilled\nres3 undefined\n```\n\n### then() 中抛出异常\n\n当then()方法传入的回调函数遇到异常或者手动抛出异常时，那么，then()所返回的**新的 Promise 会进入rejected 状态**，进而触发新Promise 的 catch() 方法的执行，做异常捕获。\n\n这方面的内容，我们在后续的文章《异常处理方案》中会详细讲解。\n\n### 特殊情况：then() 中传入非函数时，会发生值穿透\n\n在Promise的`then()`方法中，如果传入一个非函数作为参数，JS 会将其忽略，并且将前一个 Promise 的结果值传递给下一个`then()`方法。这意味着如果你在`then()`中传入非函数参数，它将被视为一个空操作，而不会对Promise链产生任何影响。\n\n“值穿透”的意思是，传入的非函数值会被忽略。\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('Hello');\n});\n\nmyPromise\n  .then('Invalid Argument')\n  .then(res1 => {\n    console.log('res1:', res1);\n    return 'World';\n  })\n  .then(res2 => {\n    console.log('res2:', res2);\n  });\n```\n\n打印结果：\n\n```\nres1: Hello\nres2: World\n```\n\n\n\n## Promise 实例的 catch() 方法\n\ncatch()方法是 Promise实例上的一个方法。它其实是放在Promise的原型上的 `Promise.prototype.catch`。\n\n### catch() 方法的参数\n\ncatch()方法可以接收一个参数。这个参数是一直处于**监听状态**的回调函数。当 Promise 的状态为 rejected（任务执行失败）时会立即执行这个回调函数。\n\n代码举例：\n\n```js\nconst promise = new Promise((resolve, reject) => {\n  reject('qianguyihao reject');\n});\n\npromise\n  .then(res => {\n    console.log('res:', res);\n  })\n  .catch(err => {\n    console.log('err:', err);\n  });\n```\n\n打印结果：\n\n```\nerr: qianguyihao reject\n```\n\n\n\n### catch() 方法可以被多次调用\n\n一个 Promise 的 catch() 方法可以被多次调用。每次调用时我们都可以传入对应 rejected 状态的回调函数。当 Promise 的状态变为 rejected 时，这些回调函数都会被执行。\n\ncatch() 被调用多次的伪代码：\n\n```js\nconst myPromise = new Promise();\n\nmyPromise.catch();\nmyPromise.catch();\nmyPromise.catch();\n```\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  reject('qianguyihao rejected');\n});\n\nmyPromise.catch(err => {\n  console.log('失败回调1');\n  console.log('err1:', err);\n});\n\nmyPromise.catch(err => {\n  console.log('失败回调2');\n  console.log('err2:', err);\n});\n\nmyPromise.catch(err => {\n  console.log('失败回调3');\n  console.log('err3:', err);\n});\n```\n\n打印结果：\n\n```\n失败回调1\nerr1: qianguyihao rejected\n\n失败回调2\nerr2: qianguyihao rejected\n\n失败回调3\nerr3: qianguyihao rejected\n```\n\n代码解释：\n\n当 myPromise 状态为 rejected 时，下面的四个 catch() 方法**都在监听**，所以这四个 catch() 方法都会收到状态确定的通知，进而都会执行。\n\n## catch() 方法的返回值（重要）\n\n与 then() 方法类似，catch()方法默认也是有返回值的，它会返回一个**新的Promise对象**。因为 catch()方法的返回值永远是一个 Promise 对象，所以我们才可以对它进行**链式调用**。\n\nPromise 链式调用的伪代码：\n\n ```js\n// 伪代码\nmyPromise.then().then().catch().then()\n ```\n\n上方代码中，因为 myPromise.catch() 的返回值本身就是一个 Promise，所以才可以继续调用 then()、继续调用 catch()。\n\n与 then() 方法类似，**catch()方法返回的 Promise 对象处于什么状态呢**？catch()方法的参数里，是一个回调函数。这取决于回调函数的返回值是什么。情况如下：\n\n1、当catch()方法中的回调函数在执行时，那么Promise 处于 pending 状态。\n\n2、当 catch方法中的回调函数中，手动 return 一个返回值时，那么 Promise 的状态取决于返回值的类型。当返回值这行代码执行完毕后， Promise 会立即决议，进入确定状态（成功 or 失败），进而触发下一个then/catch 函数的执行。同时可以给下一个 then/catch 传递参数。具体情况如下：\n\n- 情况1：如果没有返回值（相当于 return undefined），或者返回值是**普通值/普通对象**，那么 Promise 的状态为fulfilled。这个值会作为then()回调的参数。\n- 情况2：如果返回值是**另外一个新的 Promise**，那么原 Promise 的状态将**交给新的 Promise 决定**。这两个Promise 的状态一致。\n- 情况3：如果返回值是一个对象，并且这个对象里有实现then()方法（这种对象称为 **thenable** 对象），那就会执行该then()方法，并且根据**then()方法的结果来决定Promise的状态**。\n\n还有一种特殊情况：\n\n- 情况4：当catch()方法传入的回调函数遇到异常或者手动抛出异常时，那么， Promise 处于rejected 状态。\n\n**小结**：catch()方法里，我们可以通过 return **传递结果**给下一个新的Promise。\n\n### 默认返回值\n\n如果catch()方法的回调函数里没写返回值（相当于 return undefined），那么catch()方法的返回值是一个新的Promise。新 Promise 的状态为fulfilled，其then()方法里，res的值为 undefined。\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  reject('qianguyihao rejected');\n});\n\nmyPromise\n  .catch(err => {\n    console.log('err:', err);\n    /*\n    这里虽然什么都没写，底层默认写了如下代码：\n    return new Promise((resolve, reject) => {\n      resolve(undefined); // resolve() 的参数是空\n    })\n    */\n  })\n  .then(res => {\n    console.log('res:', res);\n  });\n```\n\n打印结果：\n\n```\nerr: qianguyihao rejected\nres: undefined\n```\n\n### 返回普通值\n\n我们也可以在 catch()方法的回调函数里，手动 return 自己想要的数据，比如一个普通值 value1。这个普通值就可以传递给下一个新的Promise。新 Promise 的状态为fulfilled，其then()方法里，res的值为 value1。\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  reject('1号');\n});\n\nmyPromise\n  .catch(err => {\n    console.log('err1:', err);\n    return '2号';\n    /*\n    上面这行 return，相当于：\n    return new Promise((resolve, reject)=> {\n      resolve('2号');\n    })\n    */\n  })\n  .then(res => {\n    console.log('res2:', res);\n  })\n  .then(res => {\n    console.log('res3:', res);\n  });\n```\n\n返回结果：\n\n```\nerr1: 1号\nres2: 2号\nres3: undefined\n```\n\n\n\n## catch() 方法的执行时机\n\n### Promise 抛出 rejected 异常时，一定要捕获并处理\n\n当 Promise 状态为 rejected 时，表示抛出异常，如果不处理失败的回调，行不行呢？不行，会报错。代码举例：\n\n```js\n      const promise = new Promise((resolve, reject) => {\n        // 在这里抛出异常\n        reject('qianguyihao reject');\n      });\n\n      promise.then(res => {\n        console.log('res:', res);\n      });\n```\n\n![image-20230521135912267](https://img.smyhvae.com/image-20230521135912267.png)\n\n这个报错的意思是：未捕获 rejected 失败状态的 Promise 异常。必须要加一个 catch() 进行捕获。\n\n书写 Promise 时，比较好的习惯是，无论如何都要在末尾写一个 catch() 方法。\n\n\n\n### 可在 then() 中通过 throw 抛出异常\n\n先来看一段代码：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('aaa');\n});\n\nmyPromise\n  .then(res => {\n    console.log('res1:', res);\n   // 如果我想在这里 return 一个失败状态的promise，该怎么做？\n  })\n  .then(res => {\n    console.log('res2:', res);\n  })\n  .catch(err => {\n    console.log('err:', err);\n  });\n```\n\n注意看注释，如果在那个位置return 一个失败状态的Promise，该怎么做？\n\n做法1：\n\n```js\nreturn new Promise((resolve, reject)=> {\n  reject('第二个 promise 执行失败');\n})\n```\n\n做法2：\n\n```js\nthrow new Error('第二个 Promise 执行失败');\n```\n\n做法2比做法1更为常用，完整代码如下：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('aaa');\n});\n\nmyPromise\n  .then(res => {\n    console.log('res1:', res);\n    // 抛出异常：相当于 return 一个失败状态的 Promise\n    throw new Error('第二个 Promise 执行失败');\n  })\n  .then(res => {\n    console.log('res2:', res);\n  })\n  .catch(err => {\n    console.log('err:', err);\n  });\n```\n\n打印结果：\n\n```\nres1: aaa\nerr: Error: 第二个 Promise 执行失败\n```\n\n当通过 throw 抛出异常后，当前 then() 里的后续代码会暂停执行，后续的 then() 也会暂停执行，直接往后走到最近的 catch()。\n\nthrow 这种写法在实战开发中很常用，需要理解并记住。\n\n### 找到最近的 catch() 去执行\n\n我们先来看一段代码：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  reject('qianguyihao rejected');\n});\n\nmyPromise\n  .then(res => {\n    console.log('res1:', res);\n  })\n  .then(res => {\n    console.log('res2:', res);\n  })\n  .catch(err => {\n    console.log('err:', err);\n  });\n```\n\n打印结果：\n\n```\nerr: qianguyihao rejected\n```\n\n上方代码中的 catch() 是属于哪个 Promise 实例的方法呢？其实没有严格的界限。它既可以捕获 myPromise的异常，也可以捕获那两个 then()的异常，就是这么灵活。\n\n再来看一段代码：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled');\n});\n\nmyPromise\n  .then(res => {\n    console.log('res1:', res);\n    // 遇到异常（或者任务失败）后，会找到最近的 catch() 去执行\n    throw new Error('not login')\n  })\n  .then(res => {\n    console.log('res2:', res);\n  }, err => {\n    console.log('err2:', err);\n  })\n  .catch(err => {\n    console.log('err3:', err);\n  });\n```\n\n打印结果：\n\n```\nres1: qianguyihao fulfilled\nerr2: Error: not login\n```\n\n请记住，myPromise 的状态变为失败时，它会找到**最近的**那个**失败回调函数**并执行。这是 Promise的内部机制。\n\n## 处理失败状态的两种写法\n\n我们有两种写法可以捕获 Promise的失败/异常状态：\n\n-   写法 1：单独写 catch() 方法作为失败的回调函数。\n\n-   写法 2：then()方法里可以传两个参数，第⼀个参数是成功时的回调函数，第⼆个参数是失败时的回调函数。\n\n### 代码格式\n\n这两种写法的**代码格式**如下：\n\n```js\n// 第一步：model层的接口封装\nconst myPromise = new Promise((resolve, reject) => {\n  // 这里做异步任务（比如 ajax 请求接口，或者定时器），然后执行 resolve 或者 reject。\n\t...\n  ...\n});\n\nconst onFulfilled = (res) => {\n  console.log(res);\n};\n\nconst onRejected = function (err) {\n  console.log(err);\n};\n\n// 写法1：通过 catch 方法捕获失败状态的Promise\nmyPromise.then(onFulfilled).catch(onRejected);\n\n// 写法2：then()方法里可以传两个参数，第⼀个参数是成功时的回调函数，第⼆个参数是失败时的回调函数。\nmyPromise.then(onFulfilled, onRejected);\n```\n\n注意事项：\n\n1、上面这两种写法是等价的，选其中一种写法即可。这两种写法几乎没有区别。\n\n2、有一点点区别：\n\n- `myPromise.then(onFulfilled).catch(onRejected)`：既可以捕获到 myPromise 的异常，**也可以捕获到 then() 里面的异常**（划重点）。\n- `myPromise.then(onFulfilled, onRejected)`：只能捕获到 promise的异常，无法捕获then()里面的异常。\n\n知识拓展：`myPromise.catch().then()`这种写法，只能捕获到 myPromise 里面的异常。\n\n\n\n\n\n\n\n\n\n### 代码举例\n\n这两种写法在实战开发中的**代码举例**如下：\n\n```js\nfunction myPromise() {\n    return new Promise((resolve, reject) => {\n        // 这里做异步任务（比如 ajax 请求接口，或者定时器）\n            ...\n            ...\n    });\n}\n\n// 写法1\nmyPromise()\n    .then((res) => {\n        // 从 resolve 获取正常结果\n        console.log('接口请求成功时，走这里');\n        console.log(res);\n    })\n    .catch((err) => {\n        // 从 reject 获取异常结果\n        console.log('接口请求失败时，走这里');\n        console.log(err);\n    })\n    .finally(() => {\n        console.log('无论接口请求成功与否，都会走这里');\n    });\n\n\n// 写法 2：（和写法 1 等价）\nmyPromise()\n    .then(\n        (res) => {\n            // 从 resolve 获取正常结果\n            console.log('接口请求成功时，走这里');\n            console.log(res);\n        },\n        (err) => {\n            // 从 reject 获取异常结果\n            console.log('接口请求失败时，走这里');\n            console.log(err);\n        }\n    )\n    .finally(() => {\n        console.log('无论接口请求成功与否，都会走这里');\n    });\n```\n\n**代码解释**：写法 1 和写法 2 的作用是等价的。只不过，写法 2 是把 catch 里面的代码作为 then 里面的第二个参数而已。\n\n## Promise 实例的 finally() 方法\n\nfinally() 方法是在ES9（ES 2018）中新增的一个特性，表示 Promise 对象无论变成 fulfilled 状态 还是 rejected 状态，finally() 里传入的回调函数都会被执行。\n\nfinally() 里可传入一个参数，这个参数是一个回调函数。回调函数不传参数，因为前面无论是 fulfilled 状态，还是 rejected状态，这个回调函数都会执行。\n\nfinally() 方法很实用，可以避免我们写很多重复代码，它的执行时机也有很重要的应用场景。\n\n代码举例：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  resolve('promise1 fulfilled');\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  reject('promise2 rejected');\n});\n\npromise1\n  .then(res => {\n    console.log('res1:', res);\n  })\n  .catch(err => {\n    console.log('err1:', err);\n  })\n  .finally(() => {\n    console.log('promise1 决议后都会执行的代码');\n  });\n\npromise2\n  .then(res => {\n    console.log('res2:', res);\n  })\n  .catch(err => {\n    console.log('err2:', err);\n  })\n  .finally(() => {\n    console.log('promise2 决议后都会执行的代码');\n  });\n```\n\n打印结果：\n\n```\nres1: promise1 fulfilled\nerr2: promise2 rejected\npromise1 决议后都会执行的代码\npromise2 决议后都会执行的代码\n```\n\n## Promise的其他写法\n\n### 写法1\n\n\n\n\n\n## Promise 规范\n\nPromise 是⼀个拥有 then ⽅法的对象或函数。任何符合 promise 规范的对象或函数都可以成为 Promise。\n\n关于 promise 规范的详细解读，可以看下面这个链接：\n\n-   Promises/A+ 规范：<https://promisesaplus.com/>\n-   【翻译】Promises/A+规范：https://www.ituring.com.cn/article/66566\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/08-Promise的链式调用.md",
    "content": "---\ntitle: 08-Promise的链式调用\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 前言\n\n实际开发中，我们经常需要先后请求多个接口：发送第一次网络请求后，等待请求结果；有结果后，然后发送第二次网络请求，等待请求结果；有结果后，然后发送第三次网络请求。以此类推。\n\n比如说：在请求完接口 1 的数据`data1`之后，需要根据`data1`的数据，继续请求接口 2，获取`data2`；然后根据`data2`的数据，继续请求接口 3。换而言之，现在有三个网络请求，请求 2 必须依赖请求 1 的结果，请求 3 必须依赖请求 2 的结果。\n\n如果按照往常的写法，会有三层回调，陷入“回调地狱”的麻烦。\n\n这种场景其实就是接口的多层嵌套调用，在前端的异步编程开发中，经常遇到。有了 Promise 以及更高级的写法之后，我们可以把多层嵌套调用按照**线性**的方式进行书写，非常优雅。也就是说：Promise 等ES6的写法可以把原本的**多层嵌套写法**改进为**链式写法**。\n\n我们来对比一下嵌套写法和链式调用的写法，你会发现后者的非常优雅。\n\n## Promise 链式调用：封装多次网络请求\n\n### ES5 中的传统嵌套写法\n\n伪代码举例：\n\n```js\n// 封装 ajax 请求：传入请求地址、请求参数，以及回调函数 success 和 fail。\nfunction requestAjax(url, params, success, fail) {\n  var xhr = new xhrRequest();\n  // 设置请求方法、请求地址。请求地址的格式一般是：'https://api.example.com/data?' + 'key1=value1&key2=value2'\n  xhr.open('GET', url);\n  // 设置请求头（如果需要）\n  xhr.setRequestHeader('Content-Type', 'application/json');\n  xhr.send();\n  xhr.onreadystatechange = function () {\n    if (xhr.readyState === 4 && xhr.status === 200) {\n      success && success(xhr.responseText);\n    } else {\n      fail && fail(new Error('接口请求失败'));\n    }\n  };\n}\n\n// ES5的传统写法，执行 ajax 请求，层层嵌套\nrequestAjax(\n  'https://api.qianguyihao.com/url_1', params_1,\n  res1 => {\n    console.log('第一个接口请求成功:' + JSON.stringify(res1));\n    // ajax嵌套调用\n    requestAjax('https://api.qianguyihao.com/url_2', params_2, res2 => {\n      console.log('第二个接口请求成功:' + JSON.stringify(res2));\n      // ajax嵌套调用\n      requestAjax('https://api.qianguyihao.com/url_3', params_3, res3 => {\n        console.log('第三个接口请求成功:' + JSON.stringify(res3));\n      });\n    });\n  },\n  (err1) => {\n    console.log('qianguyihao 请求失败:' + JSON.stringify(err1));\n  }\n);\n```\n\n上面的代码层层嵌套，可读性很差，而且出现了我们常说的回调地狱问题。\n\n### Promise 的嵌套写法\n\n改用 ES6 的 Promise  之后，写法上会稍微改进一些。代码举例如下：\n\n```js\n// 【公共方法层】封装 ajax 请求的伪代码。传入请求地址、请求参数，以及回调函数 success 和 fail。\nfunction requestAjax(url, params, success, fail) {\n  var xhr = new xhrRequest();\n  // 设置请求方法、请求地址。请求地址的格式一般是：'https://api.example.com/data?' + 'key1=value1&key2=value2'\n  xhr.open('GET', url);\n  // 设置请求头（如果需要）\n  xhr.setRequestHeader('Content-Type', 'application/json');\n  xhr.send();\n  xhr.onreadystatechange = function () {\n    if (xhr.readyState === 4 && xhr.status === 200) {\n      success && success(xhr.responseText);\n    } else {\n      fail && fail(new Error('接口请求失败'));\n    }\n  };\n}\n\n// 【model层】将接口请求封装为 Promise\nfunction requestData1(params_1) {\n  return new Promise((resolve, reject) => {\n    requestAjax('https://api.qianguyihao.com/url_1', params_1, res => {\n      // 这里的 res 是接口返回的数据。返回码 retCode 为 0 代表接口请求成功。\n      if (res.retCode == 0) {\n        // 接口请求成功时调用\n        resolve('request success' + res);\n      } else {\n        // 接口请求异常时调用\n        reject({ retCode: -1, msg: 'network error' });\n      }\n    });\n  });\n}\n\n\n// requestData2、requestData3的写法与 requestData1类似。他们的请求地址、请求参数、接口返回结果不同，所以需要挨个单独封装 Promise。\nfunction requestData2(params_2) {\n  return new Promise((resolve, reject) => {\n    requestAjax('https://api.qianguyihao.com/url_2', params_2, res => {\n      if (res.retCode == 0) {\n        resolve('request success' + res);\n      } else {\n        reject({ retCode: -1, msg: 'network error' });\n      }\n    });\n  });\n}\n\nfunction requestData3(params_3) {\n  return new Promise((resolve, reject) => {\n    requestAjax('https://api.qianguyihao.com/url_3', params_3, res => {\n      if (res.retCode == 0) {\n        resolve('request success' + res);\n      } else {\n        reject({ retCode: -1, msg: 'network error' });\n      }\n    });\n  });\n}\n\n// 【业务层】Promise 调接口的嵌套写法。温馨提示：这段代码在接下来的学习中，会被改进无数次。\n// 发送第一次网络请求\nrequestData1(params_1).then(res1 => {\n  console.log('第一个接口请求成功:' + JSON.stringify(res1));\n\n  // 发送第二次网络请求\n  requestData1(params_2).then(res2 => {\n    console.log('第二个接口请求成功:' + JSON.stringify(res2));\n\n    // 发送第三次网络请求\n    requestData1(params_3).then(res3 => {\n      console.log('第三个接口请求成功:' + JSON.stringify(res3));\n    })\n  })\n})\n```\n\n上方代码非常经典。在真正的实战中，我们往往需要嵌套请求**多个不同的接口**，它们的接口请求地址、要处理的 resolve 和 reject 的时机、业务逻辑往往是不同的，所以需要分开封装不同的 Promise 实例。也就是说，如果要调三个不同的接口，建议单独封装三个不同的 Promise 实例：requestData1、requestData2、requestData3。\n\n这三个 Promise 实例，最终都需要调用底层的公共方法 requestAjax()。每个公司都有这样的底层方法，里面的代码会做一些公共逻辑，比如：封装原生的 ajax请求，用户登录态的校验等等；如果没有这种公共方法，你就自己写一个，为组织做点贡献。\n\n但是，细心的你可能会发现：上面的最后10行代码仍然不够优雅，因为 Promise 在调接口时出现了嵌套的情况，实际开发中如果真这么写的话，是比较挫的，阅读性非常差，我不建议这么写。要怎么改进呢？这就需要用到 Promise 的**链式调用**。\n\n### Promise 的链式调用写法（重要）\n\n针对多个不同接口的嵌套调用，采用 Promise 的**链式调用**写法如下：（将上方代码的最后10行，改进如下）\n\n```js\nrequestData1(params_1).then(res1 => {\n  console.log('第一个接口请求成功:' + JSON.stringify(res1));\n  // 【关键代码】继续请求第二个接口。如果有需要，也可以把 res1 的数据传给 requestData2()的参数\n  return requestData2(res1);\n}).then(res2 => {\n  console.log('第二个接口请求成功:' + JSON.stringify(res2));\n  // 【关键代码】继续请求第三个接口。如果有需要，也可以把 res2 的数据传给 requestData3()的参数\n  return requestData3(res2);\n}).then(res3 => {\n  console.log('第三个接口请求成功:' + JSON.stringify(res3));\n}).catch(err => {\n  console.log(err);\n})\n```\n\n上面代码中，then 是可以链式调用的，一旦 return 一个新的 Promise 实例之后，后面的 then() 就可以作为这个新 Promise 在成功后的回调函数。这种**扁平化**的写法，更方便维护，可读性更好；并且可以更好的**管理**请求成功和失败的状态。\n\n这段代码很经典，你一定要多看几遍，多默写几遍，倒背如流也不过分。如果你平时的异步编程代码能写到这个水平，说明你对 Promise 已经入门了，因为绝大多数人都是用的这个写法。\n\n其实还有更高级、更有水平的写法，那就是用生成器、用 async ... await 来写Promise的链式调用，也就是改进上面的十几行代码。你把它掌握了，编程水平才能更上一层楼。我们稍后会讲。\n\n### Promise 链式调用举例：封装 Node.js 的回调方法\n\n代码结构与上面的类似，这里仅做代码举例，不再赘述。\n\n传统写法：\n\n```js\nfs.readFile(A, 'utf-8', function (err, data) {\n    fs.readFile(B, 'utf-8', function (err, data) {\n        fs.readFile(C, 'utf-8', function (err, data) {\n          console.log('qianguyihao:' + data);\n        });\n    });\n});\n```\n\n上方代码多层嵌套，存在回调地狱的问题。\n\nPromise 写法：\n\n```js\nfunction read(url) {\n    return new Promise((resolve, reject) => {\n        fs.readFile(url, 'utf8', (err, data) => {\n            if (err) reject(err);\n            resolve(data);\n        });\n    });\n}\n\nread(A)\n    .then((data) => {\n        return read(B);\n    })\n    .then((data) => {\n        return read(C);\n    })\n    .then((data) => {\n        console.log('qianguyihao:' + data);\n    })\n    .catch((err) => {\n        console.log(err);\n    });\n```\n\n\n\n## 用 async ... await 封装链式调用\n\n前面讲的 Promise 链式调用是用 `then().then().then()` 这种写法。其实我们还可以用更高级的写法，也就是用生成器、用 async ... await  改写那段代码。改进之后，代码写起来非常简洁。\n\n在学习这段内容之前，你需要先去《JavaScript进阶/迭代器和生成器》那篇文章里去学习迭代器、生成器相关的知识。生成器是一种特殊的迭代器，async ... await 是生成器的语法糖。\n\n### 用生成器封装链式调用\n\n代码举例：\n\n```js\n// 封装 Promise 链式请求\nfunction* getData(params_1) {\n  // 【关键代码】\n  const res1 = yield requestData1(params_1);\n  const res2 = yield requestData2(res1);\n  const res3 = yield requestData3(res2);\n}\n\n// 调用 Promise 链式请求\nconst generator = getData(params_1);\n\ngenerator.next().value.then(res1 => {\n  generator.next(res1).value.then(res2 => {\n    generator.next(res2).value.then(res3 => {\n      generator.next(res3);\n    })\n  })\n})\n```\n\n生成器在执行时，是分阶段执行的，每次遇到 next()方法后就会执行一个阶段，遇到 yield 就会结束当前阶段的执行并暂停。 上方代码中，yield 后面的内容是当前阶段产生的 Promise 对象；yield 前面的内容是要传递给下一个阶段的参数。\n\n### 用 async ... await 封装链式调用（重要）\n\n上面的生成器代码有些晦涩难懂，实际开发中，通常不会这么写。我们更喜欢用 async ... await 语法封装 Promise 的链式调用。async ... await  是属于生成器的语法糖，写起来更简洁直观、更容易理解。\n\n代码举例：\n\n```js\n// 封装：用 async ... await 调用 Promise 链式请求\nasync function getData() {\n  const res1 = await requestData1(params_1);\n  const res2 = await requestData2(res1);\n  const res3 = await requestData3(res2);\n}\n\ngetData();\n```\n\n代码解释：requestData1()、requestData2()、requestData3() 这三个函数都是一个Promise对象，其内部封装的代码写法已经在前面「Promise 的嵌套写法」这一小段中讲过了。\n\n上面的代码非常简洁。实际开发中也经常用到，非常实用。暂时我们先记住用法，下一章我们会学习 async ... await 的详细知识。\n\n\n\n\n\n## 链式调用，如何处理任务失败的情况\n\n在链式调用多个异步任务的Promise时，如果中间有一个任务失败或者异常，要怎么处理呢？是继续往下执行？还是停止执行，直接抛出异常？这取决于你的业务逻辑是怎样的。\n\n常见的处理方案有以下几种，你可以根据具体情况**按需**选择。\n\n### 统一处理失败的情况，不继续往下走\n\n针对 a、b、c 这三个请求，不管哪个请求失败，我都希望做统一处理。这种代码要怎么写呢?我们可以在最后面写一个 catch。\n\n由于是统一处理多个请求的异常，所以**只要有一个请求失败了，就会马上走到 catch**，剩下的请求就不会继续执行。比如说：\n\n-   a 请求失败：然后会走到 catch，不执行 b 和 c\n\n-   a 请求成功，b 请求失败：然后会走到 catch，不执行 c。\n\n代码举例如下：\n\n```js\ngetPromise('a.json')\n  .then((res) => {\n    console.log(res);\n    return getPromise('b.json'); // 继续请求 b\n  })\n  .then((res) => {\n    // b 请求成功\n    console.log(res);\n    return getPromise('c.json'); // 继续请求 c\n  })\n  .then((res) => {\n    // c 请求成功\n    console.log('c：success');\n  })\n  .catch((err) => {\n    // 统一处理请求失败\n    console.log(err);\n  });\n```\n\n### 中间的任务失败后，如何继续往下走？\n\n在多个Promise的链式调用中，**如果中间的某个Promise 执行失败，还想让剩下的其他 Promise 顺利执行**的话，那就请在中间**那个失败的Promise里加一个失败的回调函数**（可以写到then函数的第二个参数里，也可以写到catch函数里）。捕获异常后，便可继续往下执行其他的Promise。\n\n代码举例：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 1');\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  reject('qianguyihao rejected 2');\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 3');\n});\n\n\npromise1\n  .then(res => {\n    console.log('res1:', res);\n    // return 一个 失败的 Promise\n    return promise2;\n  })\n  .then(res => {\n    console.log('res2:', res);\n    return promise3;\n  }, err => {\n    // 如果 promise2 为失败状态，可以通过 then() 的第二个参数（即失败的回调函数）捕获异常，然后就可以继续往下执行其他 Promise\n    console.log('err2:', err);\n    // 关键代码：即便 promise2 失败了，也要继续执行 Promise3\n    return promise3;\n  })\n  .then(res => {\n    console.log('res3', res);\n  }, err => {\n    console.log('err3:', err);\n  });\n```\n\n打印结果：\n\n```\nres1: qianguyihao fulfilled 1\nerr2: qianguyihao rejected 2\nres3 qianguyihao fulfilled 3\n```\n\n上方代码中，我们单独处理了 promise2 失败的情况。不管promise2 成功还是失败，我们都想让后续的 promise3 正常执行。\n\n\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/09-Promise类的方法.md",
    "content": "---\ntitle: 09-Promise类的方法\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## Promise 类的方法简介\n\nPromise 的 API 分为两种：\n\n- Promise 实例的方法（也称为：Promis的实例方法）\n- Promise 类的方法（也称为：Promise的静态方法）\n\n前面几篇文章，讲的都是 Promise **实例**的方法（需要先将Promise实例化），它们都是存放在Promise的prototype上的。今天这篇文章，我们来讲一下 Promise **类**的方法。\n\nPromise **类**的方法：可以直接通过大写的`Promise.xxx`调用的方法。这里的`xxx`就称之为静态方法。\n\nPromise 的自带 API 提供了如下静态方法：\n\n| Promise 的静态方法   | 含义                                                         | 版本    |\n| -------------------- | ------------------------------------------------------------ | ------- |\n| Promise.resolve()    | 返回一个成功状态的 Promise 对象                              | ES 2015 |\n| Promise.reject()     | 返回一个失败状态的 Promise 对象                              | ES 2015 |\n| Promsie.all()        | 所有 Promise 都执行成功才算成功；或者任意一个 Promise 执行失败，就算失败 | ES 2015 |\n| Proimse.allSettled() | 不论成功与失败，把所有Promise的执行结果全部返回              | ES 2020 |\n| Promise.race()       | Promise集合中，返回第一个执行完成（无论成功与失败）的 Promise | ES 2015 |\n| Promise.any()        | Promise集合中，返回第一个执行成功的Promise                   | ES 2021 |\n\n\n\n## Promise.resolve() 和 Promise.reject()\n\n### 使用场景\n\n当我们在定义一个 Promise 的过程中，如果涉及到异步操作，那就需要通过`new Promise`的方式创建一个 Promise 实例。\n\n但有些场景下，我们已经有一个**现成的内容**了，希望**将其转成 Promise 来使用**。此时，我们可以用 `Promise.resolve()` 将其封装为成功的状态。同理，用`Promise.reject()`可以封装为失败的状态。\n\n比如说，有时候，promise 里面并没有异步操作，我只是**单纯地想通过 promise 的方式返回一个字符串**（有的业务就是有这样的需求），那就可以通过 `Promise.reslove('字符串')`、 `Promise.reject('字符串')` 这种**简写**的方式返回。\n\n代码举例：\n\n```js\nconst promise = Promise.resolve('qianguyihao')\n\npromise.then(res => {\n  console.log('res:', res);\n});\n\n// 上方代码如果是连续书写的话，也可以简写成：\nPromise.resolve('qianguyihao').then(res => console.log('res:', res));\n```\n\n`Promise.resolve('qianguyihao')` 这种写法似乎过于啰嗦，直接 `return 'qianguyihao'`不行吗？that depands。举个例子，我们在调用别人的方法时，对方如果要求返回值必须是 Promise对象，那么，Promise.resolve() 就能派上用场了。\n\n`Promise.resolve()`和`Promise.reject()`的返回值就是一个 Promise。\n\n### 用法拆解\n\n`Promise.resolve()`的用法相当于new Promise()，并执行resolve()操作。下面这两种写法是等价的：\n\n```js\n// 写法1：Promise 类的 resolve() 方法\nconst promise = Promise.resolve(params);\n\n// 写法2：Promise 实例的 resolve() 方法\nconst promise = new Promise((resolve, reject)=> resolve(params));\n```\n\nPromise.reject()的用法同理。下面这两种写法是等价的：\n\n```js\n// 写法1：Promise 类的 reject() 方法\nconst promise = Promise.reject(params);\n\n// 写法2：Promise 实例的 reject() 方法\n// 第一个形参用不到，我们通常用 下划线 表示。这是一种约定俗成的规范写法。\nconst promise = new Promise((_, reject)=> reject(params));\n```\n\n写法2显然过于啰嗦，写法1用得更多。\n\n写法2中，我们可以学到一个写代码的小技巧：如果某个形参我们用不到，但又必须写出来的话，我们通常用**下划线**表示。这是一种约定俗成的规范写法，比较简洁。\n\n### resolve()和reject()的参数\n\nresolve()参数中传入的值，可以有很多种类型，进而决定 Promise 的状态：\n\n- 情况1：如果resolve()中传入**普通的值或者普通对象**，那么这个值会作为then()回调的参数。Promise 的状态为fulfilled。\n- 情况2：如果resolve()中传入的是**另外一个新的 Promise**，那么原 Promise 的状态将**交给新的 Promise 决定**。\n- 情况3：如果resolve()中传入的是**thenable** 对象，那就**会执行该then()方法**，并且根据**then()方法的结果来决定Promise的状态**。\n\nreject()的参数中，无论传入什么值，Promise都会直接进入 rejected 状态，并触发 catch() 方法的执行。\n\n我们在前面的文章《Promise入门详解》中针对这些情况做了详细介绍，在此不再赘述。\n\n### 代码详解\n\n\nresolve()、reject()既可以作为 Promise 实例的方法，也可以作为 Promise 类的方法。这两种情况，我们来对比看看。\n\n例 1：\n\n```js\nfunction foo(flag) {\n    if (flag) {\n        return new Promise((resolve) => {\n            // 这里可以做异步操作\n            resolve('success');\n        });\n\n        // return Promise.resolve('success2');\n    } else {\n        return new Promise((reslove, reject) => {\n            // 这里可以做异步操作\n            reject('fail');\n        });\n    }\n}\n\n// 执行 reslove 的逻辑\nfoo(true).then((res) => {\n    console.log(res);\n});\n\n// 执行 reject 的逻辑\nfoo(false).catch((err) => {\n    console.log(err);\n});\n```\n\n例 2：（见证奇迹的时刻）\n\n```js\nfunction foo(flag) {\n    if (flag) {\n        // Promise的静态方法：直接返回字符串\n        return Promise.resolve('success');\n    } else {\n        // Promise的静态方法：直接返回字符串\n        return Promise.reject('fail');\n    }\n}\n\n// 执行 reslove 的逻辑\nfoo(true).then((res) => {\n    console.log(res);\n});\n\n// 执行 reject 的逻辑\nfoo(false).catch((err) => {\n    console.log(err);\n});\n```\n\n例 1 和例 2 的打印结果是一样的。这两段代码的区别在于：例 1 里面可以封装异步任务；例 2 只能单纯的返回一个字符串等变量，不能封装异步任务。\n\n## Promise.all()\n\nPromise.all()的参数是一个数组，数组里可以填写多个 Promise；Promise.all()的返回值是一个新的 Promise。这里我们以三个 Promise 为例，比如 `Promsie.all([p1, p2, p3])`。它的作用是将p1、p2、p3 这三个 Promise 包裹在一起，**组成一个新的 Promise**。\n\n**新 Promise 的状态**由 p1、p2、p3 这三个 Promse **共同决定**：\n\n- 当 p1、p2、p3等所有的 Promise 状态都变为 fulfilled 时，新的 Promise 将变为 fulfilled 状态，并会将 p1、p2、p3 等所有 Promise 的返回值**组成一个数组**，作为 then() 的参数。\n- 当p1、p2、p3 等 Promise中有一个 Promise 状态为 rejected 时，新的 Promise 将立马变为 rejected 状态，并会将第一个 reject() 的返回值作为 catch() 的参数。\n\n`Promsie.all([p, p2, p3])` 的**使用场景**：并发处理多个异步任务，所有任务都执行成功，才算成功（才会走到 then）；只要有一个任务失败，就会马上走到 catch，整体都算失败。参数里传的是多个 Promise 实例组成的数组。\n\nPromsie.all() 在实际开发中使用得非常频繁，真的很好用。我们在开发一个前端页面时，经常需要同时调用多个接口，等待这些接口的数据都准备好之后，前端再来做接下来的事。如果你也遇到这样的需求，那么 Promsie.all() 适合你。\n\n### 语法举例\n\n**1、异步任务都执行成功时**：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise1');\n        resolve('promise 1 成功');\n    }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise2');\n        resolve('promise 2 成功');\n    }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise3');\n        resolve('promise 3 成功');\n    }, 3000);\n});\n\nPromise.all([promise1, promise2, promise3])\n    .then((res) => {\n        // 三个异步任务都执行成功，才会走到这里\n        // 这里拿到的 res，是三个成功的返回结果组成的数组\n        console.log('all promise res:' + JSON.stringify(res));\n    })\n    .catch((err) => {\n        // 只要有一个异步任务执行失败，就会马上走到这里\n        console.log(err);\n    });\n```\n\n打印结果：\n\n```js\n// 1秒后\n执行 promise1\n\n// 2秒后\n执行 promise2\n\n// 3秒后\n执行 promise3\n\nall promise res:[\"promise 1 成功\",\"promise 2 成功\",\"promise 3 成功\"]\n```\n\n**2、异步任务有至少一个执行失败时**：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise1');\n        resolve('promise 1 成功');\n    }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise2');\n        // 这里通过 reject() 的方式，表示任务执行失败\n        reject('promise 2 失败');\n    }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise3');\n        resolve('promise 3 成功');\n    }, 3000);\n});\n\nPromise.all([promise1, promise2, promise3])\n    .then((res) => {\n        // 三个异步任务都执行成功，才会走到这里\n        console.log('走到 then:' + JSON.stringify(res));\n    })\n    .catch((err) => {\n        // 只要有一个异步任务执行失败，就会马上走到这里\n        console.log('走到 catch:' + err);\n    });\n```\n\n打印结果：\n\n```js\n// 1秒后\n执行 promise1\n\n// 2秒后\n执行 promise2\n走到 catch:promise 2 失败\n\n// 3秒后\n执行 promise3\n```\n\n可以看到，当 promise2 执行失败之后，马上就走到了 catch，获取到了 promise2 失败的结果。\n\n要注意的是，promise1、promise3并不会执行 resolve()，它俩状态是 pending，且无法获取它俩的结果。我们只知道整体的任务是失败的，获取了整体的失败结果。\n\n### Promise.all()案例：多张图片上传\n\n案例：现在有一个**图片上传**的接口，每次请求接口时只能上传一张图片。需求是：当用户连续上传完九张图片（正好凑齐九宫格）之后，给用户一个“上传成功”的提示。这个时候，我们就可以使用`Promsie.all()`。\n\n这个例子，在实际的项目开发中，经常遇到，属于高频需求，需要记住并理解。\n\n1、代码举例如下：\n\n```js\nconst imgArr = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg', '6.jpg', '7.jpg', '8.jpg', '9.jpg'];\nconst promiseArr = [];\nimgArr.forEach((item) => {\n    const p = new Promise((resolve, reject) => {\n        // 在这里做图片上传的异步任务。图片上传成功后，接口会返回图片的 url 地址\n        //  upload img ==> return imgUrl\n        if (imgUrl) {\n            // 单张图片上传完成\n            resolve(imgUrl);\n        } else {\n            reject('单张图片上传失败');\n        }\n    });\n    promiseArr.push(p);\n});\nPromise.all(promiseArr)\n    .then((res) => {\n        console.log('图片全部上传完成');\n        console.log('九张图片的url地址，组成的数组：' + res);\n    })\n    .catch((res) => {\n        console.log('部分图片上传失败');\n    });\n```\n\n2、上方代码解释：\n\n（1）只有九张图片都上传成功，才会走到 then。\n\n第一张图会成功调 upload 接口，并返回 imgUrl，但不会走到 resolve，因为要等其他八张图的执行结果，再决定是一起走 resolove 还是一起走 reject。\n\n（2）按时间顺序来看，假设第一张图片上传成功，第二张图片上传失败，那么，最终的表现是：\n\n-   对于前端来说，九张图都会走到 reject；整体会走到 catch，不会走到 then。\n\n-   对于后端来说，第一张图片会上传成功（因为写入 DB 是不可逆的），第二张图上传失败，剩下的七张图，会正常请求 upload img 接口。\n\n**其实九张图的 upload img 请求都已经发出去了**。对于后端来说，是没有区别的（而且读写 DB 的操作不可逆），只是在前端的交互表现不同、走到 resolve / reject / then / catch 的时机不同而已。\n\n3、**思维拓展**：\n\n-   拓展 1：如果你希望九张图同时上传，并且想知道哪些图上传成功、哪些图上传失败，则可以这样做：**无论 upload img 接口请求成功与否，全都执行 resolve**。这样的话，最终一定会走到 then，然后再根据接口返回的结果判断九张图片的上传成功与否。\n\n-   拓展 2：实战开发中，在做多张图片上传时，可能是一张一张地单独上传，各自的上传操作相互独立。此时 `Promise.all`便不再适用，这就得具体需求具体分析了。\n\n\n\n### 注意：某个任务失败之后，其他任务会继续执行\n\n一定要注意，当执行 Promise.all() / Promise.race() / Promise.any() 等方法时，如果其中一个任务失败了，**其他任务并没有停止，会继续执行**。只是前端拿不到其他任务的执行状态而已。\n\n其他任务是否需要做一些特殊梳理，就要结合你自己的业务逻辑来考虑。\n\n## Promse.allSettled()\n\nPromise.all()方法组成的多个Promise中，有个明显的特点是：只要有一个 Promise 元素进入 rejected 状态，则整体的 Promise 会立即进入 rejected 状态。其他 Promise 元素会处于 pending 状态，任务本身是否执行成功，我们在前端代码里无从知晓，因为无法拿到处理结果。我们只知道整体的 Promise 是 fulfilled或者 rejected ，获取整体的成功/失败结果。\n\n如果你认为 Promise.all() 的这一点无法满足你的需求，那么， Promise.allSettled() 可以提供一种新思路。\n\nPromise.allSettled() 是ES11（ES 2020）中提供的新API。它会等待所有的 Promise 元素都有结果（无论是 fulfilled，还是rejected）后，才会有最终的结果（settled），而且状态一定是 fulfilled。\n\nPromise.allSettled() 的状态为 fulfilled，不代表 里面的 Promise 元素都是 fulfilled，这只是在表明，里面的 Promise 元素都已经有了就结果（可能成功、可能失败）。\n\n### 语法举例\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise1');\n    resolve('promise 1 成功');\n  }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise2');\n    reject('promise 2 失败');\n  }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise3');\n    resolve('promise 3 成功');\n  }, 3000);\n});\n\nPromise.allSettled([promise1, promise2, promise3]).then(res => {\n  // 注意看 res 的返回结果\n  console.log('allSettled:', res);\n});\n```\n\n打印结果：\n\n```\n执行 promise1\n\n执行 promise2\n\n执行 promise3\n\nallSettled:\n\n[\n    {\n        \"status\": \"fulfilled\",\n        \"value\": \"promise 1 成功\"\n    },\n    {\n        \"status\": \"rejected\",\n        \"reason\": \"promise 2 失败\"\n    },\n    {\n        \"status\": \"fulfilled\",\n        \"value\": \"promise 3 成功\"\n    }\n]\n```\n\n打印结果截图：\n\n<img src=\"https://img.smyhvae.com/image-20230523193237044.png\" alt=\"image-20230523193237044\" style=\"zoom:50%;\" />\n\n从上面的打印结果可以看出，Promise.allSettled() 的状态为 fulfilled后，then()的回调函数里，res 是一个数组，数组里存放了每个 Promise 元素的执行结果（包括状态和返回值）。\n\n在实际开发中，Promise.all() 比 Promise.allSettled() 用得更多一些。\n\n## Promise.race()\n\n`Promise.race([p1, p2, p3])`：参数里传的是多个 Promise 元素组成的数组。可以并发处理多个Promise，整体的执行状态取**第一个执行完成的 Promise**的状态，且状态和第一个完成的任务状态保持一致。\n\n上面这句话，第一次读时，可能很绕口。我以异步任务为例，说的再通俗一点：在多个同时执行的异步任务中，等待哪个任务 **最先执行完成**（无论是走到 resolve，还是走到 reject，都算执行完成），整体的状态就立即跟这个任务保持一致。如果这个任务执行成功，那整体就算成功（走到 then）；如果这个任务执行失败，那整体就算失败（走到 catch）。\n\n`race`的中文翻译，可以理解为“竞赛”、“竞争”。意思是，谁先抢到名额，就认定谁了。**谁前有结果，就用谁的结果**。无论这个人最终的结局是成功或者失败，整体的结局，都以这个人的结局为准。\n\n我刚开始学 Promise.race()的时候，误以为它的含义是“只要有一个异步**执行成功**，整体就算成功（走到 then）；所有任务都执行失败，整体才算失败（走到 catch）”。现在想来，真是大错特错，过于懵懂。\n\n现在我顿悟了，准确来说，Promise.race()强调的是：只要有一个异步任务**执行完成**，整体就是**完成**的。\n\nPromise.race()的**应用场景**：在众多 Promise 实例中，最终结果只取一个 Promise 的状态，**谁返回得最快就用谁的 Promise **状态。\n\n我们来看看各种场景的打印结果，继续前行。\n\n### 语法举例\n\n**场景 1、所有任务都执行成功时**：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise1');\n        resolve('promise 1 成功');\n    }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise2');\n        resolve('promise 2 成功');\n    }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise3');\n        resolve('promise 3 成功');\n    }, 3000);\n});\n\nPromise.race([promise1, promise2, promise3])\n    .then((res) => {\n        // 第一个完成的任务，如果执行成功，就会走到这里\n        // 这里拿到的 res，是第一个成功的 promise 返回的结果，不是数组\n        console.log(JSON.stringify(res));\n\t\t\t\tconsole.log('走到then:' + res);\n    })\n    .catch((err) => {\n        // 第一个完成的任务，如果执行失败，就会走到这里\n        console.log(err);\n    });\n```\n\n打印结果：\n\n```js\n// 1秒后\n执行 promise1\n走到then:promise 1 成功\n\n// 2秒后\n执行 promise2\n\n// 3秒后\n执行 promise3\n```\n\n**场景 2、第一个任务成功、第二个任务失败时**：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise1');\n        resolve('promise 1 成功');\n    }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise2');\n        // 第二个任务执行失败时\n        reject('promise 2 失败');\n    }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise3');\n        resolve('promise 3 成功');\n    }, 3000);\n});\n\nPromise.race([promise1, promise2, promise3])\n    .then((res) => {\n        // 第一个完成的任务，如果执行成功，就会走到这里\n        console.log('走到then:' + res);\n    })\n    .catch((err) => {\n        // 第一个完成的任务，如果执行失败，就会走到这里\n        console.log('走到catch:' + err);\n    });\n```\n\n打印结果：\n\n```js\n// 1秒后\n执行 promise1\n走到then:promise 1 成功\n\n// 2秒后\n执行 promise2\n\n// 3秒后\n执行 promise3\n```\n\n可以看出，场景 2 的打印结果和场景 1 的打印结果，是一样的。因为第一个执行完成的任务是成功的，所以整体就算成功，马上走到 then()。\n\n**场景 3、第一个任务失败、第二个任务成功时**：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise1');\n        // 第一个任务执行失败时\n        reject('promise 1 失败');\n    }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise2');\n        resolve('promise 2 成功');\n    }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n    setTimeout(() => {\n        console.log('执行 promise3');\n        resolve('promise 3 成功');\n    }, 3000);\n});\n\nPromise.race([promise1, promise2, promise3])\n    .then((res) => {\n        // 第一个完成的任务，如果执行成功，就会走到这里\n        console.log('走到then:' + res);\n    })\n    .catch((err) => {\n        // 第一个完成的任务，如果执行失败，就会走到这里\n        console.log('走到catch:' + err);\n    });\n```\n\n打印结果：\n\n```js\n// 1秒后\n执行 promise1\n走到catch：promise 1 失败\n\n// 2秒后\n执行 promise2\n\n// 3秒后\n执行 promise3\n```\n\n看清楚了没？场景 3 的最终打印结果，是走到了 catch；任务 2 和任务 3 里的 resolve，并没有执行。\n\n场景 3 的代码，一定要好好理解。\n\n### Promise.race()举例：图片加载超时\n\n现在有个需求是这样的：前端需要加载并显示一张图片。如果图片在三秒内加载成功，那就显示图片；如果三秒内没有加载成功，那就按异常处理，前端提示“加载超时”或者“请求超时”。\n\n代码实现：\n\n```js\n// 图片请求的Promise\nfunction getImg() {\n    return new Promise((resolve, reject) => {\n        let img = new Image();\n        img.onload = function () {\n            // 图片的加载，是异步任务\n            resolve(img);\n        };\n        img.src = 'https://img.smyhvae.com/20200102.png';\n    });\n}\n\n// 加载超时的 Promise\nfunction timeout() {\n    return new Promise((resolve, reject) => {\n        // 采用 Promise.race()之后，如果 timeout() 的 promise 比 getImg() 的 promise先执行，说明定时器时间到了，那就算超时。整体的最终结果按失败处理。\n        setTimeout(() => {\n            reject('图片加载超时');\n        }, 3000);\n    });\n}\n\nPromise.race([getImg(), timeout()])\n    .then((res) => {\n        // 图片加载成功\n        console.log(res);\n    })\n    .catch((err) => {\n        // 图片加载超时\n        console.log(err);\n    });\n```\n\n如代码注释所述：采用 Promise.race() 之后，如果 timeout() 的 Promise 比 getImg() 的 Promise 先执行，说明定时器时间到了，那就算超时。整体的最终结果按失败处理。\n\n这个思路很巧妙。用同样的思路，我们还可以处理网络请求超时的问题。如果接口请求时长超过 3 秒，就按超时处理，也就是下面我们要举的例子。\n\n### Promise.race()举例：网络请求超时\n\n现在有这种需求：如果接口请求时长超过 3 秒，就按超时处理。\n\n基于这种需求，我们可以用 Promise.race() 来实现：一个 Promise 用于请求接口，另一个 Promise 用于 setTimeout() 定时器。把这两个 Promise 用 Promise.race() 组装在一起，谁先执行，那么最终的结果就以谁的为准。\n\n代码举例：\n\n```js\nfunction query(url, delay = 4000) {\n    let promiseArr = [\n        myAajax(url),\n        new Promise((resolve, reject) => {\n            setTimeout(() => {\n                reject('网络请求超时');\n            }, delay);\n        }),\n    ];\n    return Promise.race(promiseArr);\n}\n\nquery('http://localhost:8899/xxx_url', 3000)\n    .then((res) => {\n        console.log(res);\n    })\n    .catch((error) => {\n        console.log(error);\n    });\n```\n\n## Promise.any()\n\nPromise.any() 是 ES12（ES 2021）中推出的新API。它类似于 Promise.race()，但有一个关键的区别：Promise.any() 会等待参数中第一个状态为 fulfilled 的Promise元素，然后立即进入 fulfilled状态。\n\n如果参数中所有的 Promise 元素都进入了 rejected，那么也会等到所有的Promise都变成rejected 状态，最终报错 AggregateError。\n\n### 语法举例\n\n**场景1**、第一个任务失败，第二个任务成功：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise1');\n    reject('promise 1 失败');\n  }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise2');\n    resolve('promise 2 成功');\n  }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise3');\n    resolve('promise 3 成功');\n  }, 3000);\n});\n\nPromise.any([promise1, promise2, promise3]).then(res => {\n  console.log('走到then:', res);\n});\n```\n\n打印结果：\n\n```\n// 1秒后\n执行 promise1\n\n// 2秒后\n执行 promise2\n走到then(): promise 2 成功\n\n// 3秒后\n执行 promise3\n```\n\n**场景2**、三个任务都失败：\n\n```js\nconst promise1 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise1');\n    reject('promise 1 失败');\n  }, 1000);\n});\n\nconst promise2 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise2');\n    reject('promise 3 失败');\n  }, 2000);\n});\n\nconst promise3 = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('执行 promise3');\n    reject('promise 3 失败');\n  }, 3000);\n});\n\nPromise.any([promise1, promise2, promise3])\n  .then(res => {\n    console.log('走到then:', res);\n  })\n  .catch(err => {\n    console.log('走到catch:', err);\n  });\n```\n\n打印日志：\n\n```\n// 1秒后\n执行 promise1\n\n// 2秒后\n执行 promise2\n\n// 3秒后\n执行 promise3\n走到catch: AggregateError: All promises were rejected\n```\n\n注意看打印结果中的报错信息。`执行 promise3`这行日志出来之后，报错的那行马上就出来了。\n\n### 兼容性问题\n\n`Promise.any()` 方法依然是实验性的，尚未被所有的浏览器完全支持。它当前处于 [TC39 第四阶段草案（Stage 4）](https://github.com/tc39/proposal-promise-any)。\n\n## 总结\n\nPromise 的静态方法简化处理了多个并发操作的代码，使其更加方便、直观地调用。\n\nPromise 不仅能解决嵌套异步任务的**回调地域**问题，也可管理多个异步任务的**并发请求**。\n\nPromise 本身不是异步的，但是它可以封装异步任务，并对异步操作进行良好的、舒适简洁的状态管理，这便是 Promise 的魅力所在。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/10-async异步函数.md",
    "content": "---\ntitle: 10-async异步函数\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 异步函数（用 async 声明的函数）\n\n### 异步函数的定义\n\n使用`async`关键字声明的函数，称之为异步函数。在普通函数前面加上 async 关键字，就成了异步函数。语法举例：\n\n```js\n// 写法1：函数声明的写法\nasync function foo1() {\n}\n\n// 写法2：表达式写法（ES5语法）\nconst foo2 = async function () {\n}\n\n// 写法3：表达式写法（ES6箭头函数语法）\nconst foo3 = async () => {\n}\n\n// 写法4：定义一个类，在类中添加一个异步函数\nclass Person {\n  async foo4() {\n  }\n}\n```\n\nJS中的“异步函数”是一个专有名词，特指用`async`关键字声明的函数，其他函数则称之为普通函数。如果你在一个普通函数中定义了一个异步任务，那并不叫异步函数，而是叫包含异步任务的普通函数。\n\nasync （异步的）这个单词是 asynchronous 的缩写；相反，sync（同步的）这单词是 synchronous 的缩写。\n\n上面的异步函数代码，执行顺序与普通函数相同，默认情况下会同步执行。如果想要发挥异步执行的作用，则需要配合 await 关键字使用。稍后我们再讲 async/await的语法。\n\n\n\n## 异步函数的返回值\n\n> 异步函数的返回值和普通函数差别比较大，需要特别关注。\n\n普通函数的返回值，默认是 undefined；也可以手动 return 一个返回值，那就以手动 return的值为准。\n\n**异步函数的返回值永远是 Promise 对象**。至于这个 Promise 后续会进入什么状态，那就要看情况了。主要有以下几种情况：\n\n- 情况1：如果异步函数内部返回的是普通值（包括 return undefined时）或者普通对象，那么Promise 的状态为fulfilled。这个值会作为then()回调的参数。\n\n- 情况2：如果异步函数内部返回的是**另外一个新的 Promise**，那么 Promise 的状态将**交给新的 Promise 决定**。\n- 情况3：如果异步函数内部返回的是一个对象，并且这个对象里有实现then()方法（这种对象称为 **thenable** 对象），那就会执行该then()方法，并且根据**then()方法的结果来决定Promise的状态**。\n\n另外还有一种特殊情况：\n\n- 情况4：如果异步函数内部在执行时遇到异常或者手动抛出异常时，那么， Promise 处于rejected 状态。\n\n上面这四种情况似曾相识，我们在前面学习“resolve() 传入的参数”、“then()方法的返回值”知识点时，都有类似的情况，知识都是相通的。\n\n### 默认返回值\n\n代码举例：\n\n```js\nasync function foo2() {\n  // 相当于 return undefined，也相当于 return Promise.resolve(undefined)\n};\n\nasync function foo3() {\n  Promise.resolve('qianguyihao');\n  // 相当于 return undefined，也相当于 return Promise.resolve(undefined)\n};\n\n// foo2()、foo3()都是一个Promise对象\nfoo2().then(res => {\n  console.log(res); // 打印结果：undefined\n})\n\nfoo3().then(res => {\n  console.log(res); // 打印结果：undefined\n})\n```\n\n代码解释：异步函数即便没有手动写返回值，也相当于 `return Promise.resolve(undefined)`。\n\n### 返回普通值\n\n比如下面这段代码：\n\n```js\nasync function foo() {\n  return 'qianguyihao'\n};\n```\n\n![image-20230608114346235](https://img.smyhvae.com/image-20230608114346235.png)\n\n可以看到，foo() 的返回值是Promise对象，不是字符串。上面的代码等价于下面这段代码：\n\n```js\nasync function foo() {\n  return Promise.resolve('qianguyihao');\n};\n```\n\n进而，我们可以通过 Promise 对象的then()方法。代码举例如下。\n\n举例1：（异步函数中手动 return 一个值）\n\n```js\nasync function foo() {\n  return 'qianguyihao';\n  // 上面这行代码相当于：return Promise.resolve('qianguyihao');\n};\n\n// foo() 是一个Promise对象\nfoo().then(res => {\n  console.log(res); // 打印结果：qianguyihao\n})\n```\n\n\n\n\n\n\n\n## async/await 的使用\n\n### 异步函数配合 await 关键字使用\n\n我们可以在`async`声明的异步函数中，使用 `await`关键字来暂停函数的执行，等待一个异步操作完成。温馨提示：await 关键字不能在普通函数中使用，只能在异步函数中使用。\n\n在等待异步操作期间，异步函数会暂停执行，并让出线程，使其他代码可以继续执行。一旦异步操作完成，该异步函数会恢复执行，并返回一个 Promise 对象。具体解释如下：\n\n（1）await的后面是一个表达式，这个表达式要求是一个 Promise 对象（通常是一个封装了异步任务的Promise对象）。await执行完成后可以得到异步结果。\n\n（2）await 会等到当前 Promise 的状态变为 fulfilled之后，才继续往下执行异步函数。\n\n- async 的返回值是 Promise 对象。\n\n### 本质是语法糖\n\nasync/await 是在 ES8(即ES 2017）中引入的新语法，是另外一种异步编程解决方案。\n\nasync/await 本质是 生成器 Generator 的语法糖，是对Generator的封装。什么是语法糖呢？语法糖就是让语法变得更加简洁、更加舒服，有一种甜甜的感觉。\n\nasync/await 的写法使得编写异步代码更加直观和易于管理，避免了使用回调函数或Promise链的复杂性。认识到这一点，非常重要。\n\n### Promise、Generator、async/await的对比\n\n我们在使用 Promise、async/await、Generator 的时候，返回的都是 Promise 的实例。\n\n如果直接使用 Promise，则需要通过 then 来进行链式调用；如果使用 async/await、Generator，写起来更像同步的代码。\n\n接下来，我们看看 async/await 的代码是怎么写的。\n\n### async/await 的基本用法\n\nasync 后面可以跟一个 Promise 实例对象。代码举例如下：\n\n```js\nconst request1 = function () {\n  const promise = new Promise((resolve, reject) => {\n    requestAjax('https://www.baidu.com/xxx_url', (res) => {\n      if (res.retCode == 200) {\n        // 这里的 res 是接口1的返回结果\n        resolve('request1 success' + res);\n      } else {\n        reject('接口请求失败');\n      }\n    });\n  });\n\n  return promise;\n};\n\nasync function requestData() {\n  // 关键代码\n  const res = await request1();\n  return res;\n}\nrequestData().then(res => {\n  console.log(res);\n});\n```\n\n### 用 async/await 封装Promise链式调用【重要】\n\n假设现在有三个网络请求，请求2必须依赖请求1的结果，请求3必须依赖请求2的结果，如果按照ES5的写法，会有三层回调，会陷入“回调地狱”。\n\n这种场景其实就是接口的多层嵌套调用。之前学过 Promise，它可以把原本的**多层嵌套调用**改进为**链式调用**。\n\n而本文要学习的 async/await ，可以把原本的“多层嵌套调用”改成类似于同步的写法，非常优雅。\n\n代码举例：\n\n```js\n// 【公共方法层】封装 ajax 请求的伪代码。传入请求地址、请求参数，以及回调函数 success 和 fail。\nfunction requestAjax(url, params, success, fail) {\n  var xhr = new xhrRequest();\n  // 设置请求方法、请求地址。请求地址的格式一般是：'https://api.example.com/data?' + 'key1=value1&key2=value2'\n  xhr.open('GET', url);\n  // 设置请求头（如果需要）\n  xhr.setRequestHeader('Content-Type', 'application/json');\n  xhr.send();\n  xhr.onreadystatechange = function () {\n    if (xhr.readyState === 4 && xhr.status === 200) {\n      success && success(xhr.responseText);\n    } else {\n      fail && fail(new Error('接口请求失败'));\n    }\n  };\n}\n\n// 【model层】将接口请求封装为 Promise\nfunction requestData1(params_1) {\n  return new Promise((resolve, reject) => {\n    requestAjax('https://api.qianguyihao.com/url_1', params_1, res => {\n      // 这里的 res 是接口返回的数据。返回码 retCode 为 0 代表接口请求成功。\n      if (res.retCode == 0) {\n        // 接口请求成功时调用\n        resolve('request success' + res);\n      } else {\n        // 接口请求异常时调用\n        reject({ retCode: -1, msg: 'network error' });\n      }\n    });\n  });\n}\n\n\n// requestData2、requestData3的写法与 requestData1类似。他们的请求地址、请求参数、接口返回结果不同，所以需要挨个单独封装 Promise。\nfunction requestData2(params_2) {\n  return new Promise((resolve, reject) => {\n    requestAjax('https://api.qianguyihao.com/url_2', params_2, res => {\n      if (res.retCode == 0) {\n        resolve('request success' + res);\n      } else {\n        reject({ retCode: -1, msg: 'network error' });\n      }\n    });\n  });\n}\n\nfunction requestData3(params_3) {\n  return new Promise((resolve, reject) => {\n    requestAjax('https://api.qianguyihao.com/url_3', params_3, res => {\n      if (res.retCode == 0) {\n        resolve('request success' + res);\n      } else {\n        reject({ retCode: -1, msg: 'network error' });\n      }\n    });\n  });\n}\n\n// 封装：用 async ... await 调用 Promise 链式请求\nasync function getData() {\n  // 【关键代码】\n  const res1 = await requestData1(params_1);\n  const res2 = await requestData2(res1);\n  const res3 = await requestData3(res2);\n}\n\ngetData();\n```\n\n上面这段代码比较长，我们在上一章学习《Promise的链式调用》时，已经详细讲过了。\n\n### await 后面也可以跟一个异步函数\n\n前面讲到，await后面通常是一个执行异步任务的Promise对象。由于异步函数的返回值本身就是一个Promise，所以，我们也可以在await 后面也可以跟一个异步函数。\n\n代码举例：\n\n```js\nconst request1 = function () {\n  return new Promise((resolve, reject) => {\n    resolve('request1 请求成功');\n  });\n};\n\nasync function request2() {\n  const res = await request1();\n  return res;\n}\n\nasync function request3() {\n  // 【关键代码】request2() 既是一个异步函数，同样也是一个 Promise，所以可以跟在 await 的后面\n  const res = await request2();\n  console.log('res:', res);\n}\n\nrequest3();\n```\n\n\n\n## 异步函数的异常处理\n\n前面讲过，如果异步函数内部在执行时遇到异常或者手动抛出异常时，那么， 这个异步函数返回的Promise 处于rejected 状态。\n\n捕获并处理异步函数的异常时，有两种方式：\n\n- 方式1：通过 Promise的catch()方法捕获异常。\n- 方式2：通过 try catch捕获异常。\n\n在处理异步函数的异常情况时，方式2更为常见。\n\n如果我们不捕获异常，则会往上层层传递，最终传递给浏览器，浏览器会在控制台报错。\n\n### 方式1：过 Promise的catch()方法捕获异常\n\n```js\nfunction requestData1() {\n  return new Promise((resolve, reject) => {\n    reject('任务1失败');\n  })\n}\n\nfunction requestData2() {\n  return new Promise((resolve, reject) => {\n    resolve('任务2成功');\n  })\n}\n\nasync function getData() {\n  // requestData1 在执行时，遇到异常\n  await requestData1();\n  /*\n  由于上面的代码在执行是遇到异常，所以，这里虽然什么都没写，底层默认写了如下代码：\n  return Promise.reject('任务1失败');\n  */\n\n  // 下面这行代码不会再走了\n  await requestData2();\n}\n\n// getData() 这个异步函数的返回值是一个 Promise，状态为 rejected，所以会走到 catch()\ngetData().then(res => {\n  console.log(res);\n}).catch(err => {\n  console.log('err:', err);\n});\n```\n\n打印结果：\n\n```\nerr: 任务1失败\n```\n\n### 方式2：通过 try catch 捕获异常\n\n如果你觉得上面的写法比较麻烦，还可以通过 try catch 捕获异常。\n\n代码举例：\n\n```js\nfunction requestData1() {\n  return new Promise((resolve, reject) => {\n    reject('任务1失败');\n  })\n}\n\nfunction requestData2() {\n  return new Promise((resolve, reject) => {\n    resolve('任务2成功');\n  })\n}\n\nasync function getData() {\n  try {\n    // requestData1 在执行时，遇到异常\n    await requestData1();\n    /*\n    由于上面的代码在执行是遇到异常，当前任务立即终止，所以，这里虽然什么都没写，底层默认写了如下代码：\n    return Promise.reject('任务1失败');\n    */\n\n    // 下面这两代码不会再走了\n    console.log('qianguyihao1');\n    await requestData2();\n  }\n  catch (err) {\n    // 捕获异常代码\n    console.log('err:', err);\n  }\n}\n\ngetData();\nconsole.log('qianguyihao2');\n\n```\n\n打印结果：\n\n```\nqianguyihao2\nerr1: 任务1失败\n```\n## 总结\n\n在 async 函数中，不是所有的 异步任务都需要 await。如果两个任务在业务上没有**依赖关系**，则不需要 await；也就是说，可以并发执行，不需要线性执行，避免无用的等待。\n\n## 参考链接\n\n- [js async await 终极异步解决方案](https://www.cnblogs.com/CandyManPing/p/9384104.html)\n\n- [理解 JavaScript 的 async/await](https://segmentfault.com/a/1190000007535316)\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/11-异常处理方案.md",
    "content": "---\ntitle: 11-异常处理方案\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n## 异常处理方案\n\n在JS开发中，**处理**异常包括两步：先**抛出**异常，然后**捕获**异常。\n\n### 为什么要做异常处理\n\n异常处理非常重要，至少有以下几个原因：\n\n1. 防止程序报错甚至停止运行：当代码执行过程中发生错误或异常时，如果没有适当的异常处理机制，程序可能会报错、停止运行，甚至崩溃。通过处理异常，我们可以捕获错误并采取适当的措施避免系统报错。\n2. 错误排查和调试：异常处理有助于定位和排查错误。可以通过捕获异常并输出相关信息，比如打印日志、错误上报、跟踪堆栈等等，以便快速定位问题所在，并进行调试和修复。\n3. 提高代码健壮性和可靠性：可以采取适当的措施处理潜在的异常情况，从而减少程序出错的可能性。\n4. 提升用户体验：通过兜底、容错、容灾等异常处理方案，可以向用户提供有效的错误信息提示，而不是让用户界面无响应甚至白屏。\n\n### 抛出异常\n\n抛出异常的使用场景举例：\n\n我们经常会封装一些工具函数，这些函数可能给自己用，也可能给外部团队用。\n\n在函数内部，如果不符合预期的业务逻辑，或者遇到异常情况时，很多人的写法是直接 return，不往下执行了。但是 return 的写法存在一个很大的弊端：**调用者不知道是因为函数内部没有正常执行，还是执行的返回结果就是一个undefined**。return 的写法只是规避了问题，没有解决问题。建议的做法是：我们**需要手动抛出异常**。\n\n\n\n### 捕获异常\n\n如果只是抛出异常，而不捕获异常的话，是比较危险的。这意味着当前任务立即终止，不再执行（当然，后续的其他任务会正常执行）。此外，这个异常信息会层层往上，抛给上层的**调用者**。如果一直未被捕获，则最终会抛给**浏览器**，浏览器控制台就会报错。\n\n接下来，我们看一下不同代码场景下的异常处理方案。\n\n### 上报异常\n\n如果有必要的话，你可以把异常信息和日志，上报给监控服务器，然后集中分析。我每天上班第一件事，就是打开监控系统，看错误日志，然后对症下药解决问题。\n\n## 同步代码的异常处理\n\n### 通过 throw 抛出异常\n\n我们可以通过 `throw`关键字，抛出一个**用户自定义**的异常。当代码执行时遇到 throw 语句时，当前函数会停止停止，即：**当前函数** throw 后面的代码不会再执行。\n\n throw 意思是，告诉调用者，当前被调用的函数报错了，调用者接下来需要捕获异常或者修改代码逻辑。\n\n可以在 throw 的后面添加表达式或者数据类型，将添加的内容抛出去。数据类型可以是：number、string、boolean、对象等。\n\n代码举例：\n\n```js\nfunction sum(num1, num2) {\n  if (typeof num1 !== \"number\") {\n    throw \"type error: num1传入的类型有问题, 必须是number类型\"\n  }\n\n  if (typeof num2 !== \"number\") {\n    throw \"type error: num2传入的类型有问题, 必须是number类型\"\n  }\n\n  return num1 + num2\n}\n\nsum('a', 'b');\n```\n\n打印结果：\n\n![image-20230608180755608](https://img.smyhvae.com/image-20230608180755608.png)\n\n当然，我们还可以 throw一个封装好的对象。比如：\n\n```js\nclass myError {\n  constructor(errCode, errMsg) {\n    this.errCode = errMsg;\n    this.errMsg = errMsg;\n  }\n}\n\nfunction foo() {\n  throw new myError(-1, 'not login');\n}\n\nfoo();\n```\n\n上面这种写法比较麻烦，一般不这么写。其实，JS中已经内置了 Error  类，专门用于生成错误信息。\n\n### Error 类\n\nJS内置的 Error 类非常好用。\n\n代码举例：\n\n```js\nfunction foo() {\n  throw new Error('not login');\n}\n\n\nfoo();\n```\n\n打印结果：\n\n![image-20230608180103263](https://img.smyhvae.com/image-20230608180103263.png)\n\n上面的打印结果可以看到，通过 Error 抛出来的错误，不仅可以看到报错信息，还可以看到调用栈，便于快速定位问题所在。非常方便。\n\n\n\n### 通过 try catch 捕获异常\n\n同步代码，只抛出异常，不捕获异常的代码举例：\n\n```js\nfunction foo() {\n  throw new Error('not login');\n}\n\nfoo();\n// 当前任务立即终止，不再执行；下面这行代码和 foo() 都在同一个 同步任务 中\nconsole.log('qianguyihao');\n```\n\n打印结果：\n\n![image-20230608182003407](https://img.smyhvae.com/image-20230608182003407.png)\n\n可以看到，最后一行的 log 并没有执行。\n\n我们可以使用 try catch 抛出异常， 对上述代码进行改进。代码举例：\n\n```js\nfunction foo() {\n  throw new Error('not login');\n}\n\n// 通过 try catch 手动捕获异常\ntry {\n  foo();\n} catch (err) {\n  console.log(err);\n}\n\n// 当前任务的后续代码会继续执行\nconsole.log('qianguyihao');\n```\n\n打印结果：\n\n![image-20230608182140002](https://img.smyhvae.com/image-20230608182140002.png)\n\n### 通过 try catch finally 捕获异常\n\n如果有些代码必须要执行，我们可以放到 finally 里。\n\n- 不管是否遇到异常，finally的代码一定会执行。\n- 如果 try 和 finally 中都有返回值，那么会使用finally中的返回值。\n\n代码举例：\n\n```js\nfunction foo() {\n  throw new Error('not login');\n}\n\n// 通过 try catch 捕获异常\ntry {\n  foo();\n} catch (err) {\n  console.log(err);\n} finally {\n  console.log(\"finally\")\n}\n\n// 后续代码会继续执行\nconsole.log('qianguyihao');\n```\n\n### try catch 只能捕获同步代码的异常\n\ntry catch只能捕获同步代码里的异常，而  Promise.reject() 是异步代码。\n\n原因是：当异步函数抛出异常时，对于宏任务而言，执行函数时已经将该函数推入栈，此时并不在 try-catch 所在的栈，所以 try-catch 并不能捕获到错误。对于微任务而言（比如 promise）promise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到。\n\n参考链接：\n\n- [try-catch 能抛出 promise 的异常吗](https://blog.csdn.net/xiaoluodecai/article/details/107297404)\n\n\n\n### 使用 window.onerror 监听未被捕获的代码异常\n\n如果JS代码抛出了异常但没有进行捕获，我们可以使用 JS 自带的  `window.onerror` 事件监听到这些错误。\n\n代码举例：\n\n```js\n// 监听同步代码的异常\nwindow.onerror = (event) => {\n  console.error('onerror 监听到未被捕获的异常:', event)\n};\n\nfunction foo1() {\n  throw new Error('not login');\n}\n\nfunction foo2() {\n  throw new Error('network error');\n}\n\nfoo1();\nfoo2();\n```\n\n打印结果：\n\n![image-20230624162123559](https://img.smyhvae.com/image-20230624162123559.png)\n\n\n\n\n\n## Promise 的异常处理\n\n### reject() 会自动抛出异常\n\n在使用 Promise 时，当我们调用了 reject() 之后，系统会**自动抛出异常**，不需要我们手动抛出异常。这是 Promise的内部机制。但是我们需要手动捕获异常。\n\n当 Promise 进入 rejected状态之后，会触发 catch()方法的执行，捕获异常。此时，成功完成了Promise异常处理的闭环。\n\n### 在then() 中抛出异常（重要）\n\n当then()方法传入的回调函数中，如果遇到异常或者手动抛出异常，那么，then()所返回的**新的 Promise 会进入rejected 状态**，进而触发新Promise 的 catch() 方法的执行，做异常捕获。\n\n**场景1**：在then()方法传入的回调函数中，如果代码**在执行时遇到异常**，系统会**自动抛出异常**。此时我们需要在 catch() 里**手动捕获异常**，否则会报错。\n\n自动抛出异常的代码举例：（由于没有捕获异常，所以会报错）\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao1 fulfilled');\n});\n\nmyPromise.then(res => {\n  console.log('res1:', res);\n  // 显然，person 并没有 forEach()方法。所以，代码在执行时，会遇到异常。\n  const person = { name: 'vae' };\n  person.forEach(item => {\n    console.log('item:', item);\n  })\n  // 这行代码不会执行，因为上面的代码报错了\n  console.log('qianguyihao2');\n}).then(res => {\n  console.log('res2:', res);\n})\n\n// 定时器里的代码正常执行\nsetTimeout(() => {\n  console.log('qianguyihao3');\n}, 100)\n```\n\n运行结果：\n\n![image-20230615090007932](https://img.smyhvae.com/image-20230615090007932.png)\n\n\n\n代码改进：（代码在执行时遇到异常，此时我们捕获异常，所以系统不会报错，这才是推荐的写法）\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao1 fulfilled');\n});\n\nmyPromise.then(res => {\n  console.log('res1:', res);\n  // 显然，person 并没有 forEach()方法。所以，代码在执行时，会遇到异常。\n  const person = { name: 'vae' };\n  person.forEach(item => {\n    console.log('item:', item);\n  })\n  // 这行代码不会执行，因为上面的代码报错了\n  console.log('qianguyihao2');\n}).then(res => {\n  console.log('res2:', res);\n}).catch(err => {\n  // 在 catch()方法传入的会调函数里，捕获异常\n  console.log('err2:', err);\n})\n\n// 定时器里的代码正常执行\nsetTimeout(() => {\n  console.log('qianguyihao3');\n}, 100)\n```\n\n打印结果：\n\n![image-20230624072927944](https://img.smyhvae.com/image-20230624072927944.png)\n\n**场景2**：在then()方法传入的回调函数中，如果我们手动抛出异常，此时我们需要在 catch() 里**手动捕获异常**，否则会报错。\n\n代码举例：（手动抛出异常，未捕获，所以会报错）\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 1');\n});\n\nmyPromise.then(res => {\n  console.log('res1:', res);\n  // 手动抛出异常\n  throw new Error('qianguyihao rejected 2')\n}).then(res => {\n  console.log('res2:', res);\n})\n\n// 定时器里的代码正常执行\nsetTimeout(() => {\n  console.log('qianguyihao3');\n}, 100)\n```\n\n打印结果：\n\n![image-20230624073252797](https://img.smyhvae.com/image-20230624073252797.png)\n\n代码改进：（代码在执行时遇到异常，此时我们捕获异常，所以系统不会报错，这才是推荐的写法）\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('qianguyihao fulfilled 1');\n});\n\nmyPromise.then(res => {\n  console.log('res1:', res);\n  // 手动抛出异常\n  throw new Error('qianguyihao rejected 2')\n}).then(res => {\n  console.log('res2:', res);\n}, (err) => {\n  console.log('err2:', err);\n})\n\n// 定时器里的代码正常执行\nsetTimeout(() => {\n  console.log('qianguyihao3');\n}, 100)\n```\n\n打印结果：\n\n![image-20230624073604599](https://img.smyhvae.com/image-20230624073604599.png)\n\n\n\n\n\n### 使用 unhandledrejection 事件监听未被捕获的Promise异常\n\n如果Promise抛出了异常但没有进行捕获，我们可以使用JS自带的  `unhandledrejection` 事件监听到这些错误。这个事件非常有用，尤其是当我们需要**集中做日志收集**时，屡试不爽。这个事件只能用于监听 Promise 中的异常，不能用于其他同步代码的异常。\n\n先来看下面这行代码：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  console.log('qianguyihao1');\n  reject('not login');\n  console.log('qianguyihao2');\n})\n```\n\n打印结果：\n\n![image-20230624154609747](https://img.smyhvae.com/image-20230624154609747.png)\n\n上面的代码抛出了异常，但没有捕获异常，所以我们可以用 unhandledrejection 事件监听到。代码举例：\n\n```js\n// 监听未被捕获的 Promise 异常\nwindow.addEventListener('unhandledrejection', (event) => {\n  console.error(`unhandledrejection 监听到异常，写法1: ${event.reason}`)\n});\n\nwindow.onunhandledrejection = event => {\n  console.error(`unhandledrejection 监听到异常，写法2: ${event.reason}`);\n};\n\nwindow.onerror = (event) => {\n  console.error('onerror 监听到异常:', event);\n};\n\n\nconst promise1 = new Promise((resolve, reject) => {\n  reject('not login');\n})\n\nconst promise2 = new Promise((resolve, reject) => {\n  throw new Error('network error');\n  resolve();\n})\n```\n\n打印结果：\n\n![image-20230624172634569](https://img.smyhvae.com/image-20230624172634569.png)\n\n可以看到，promise1 和 Promise2 的异常，都被 unhandledrejection 事件**收集**到了。\n\n代码举例2：\n\n```js\nwindow.addEventListener('unhandledrejection', (event) => {\n  console.error(`unhandledrejection 监听到异常: ${event.reason}`)\n});\n\nwindow.onerror = (event) => {\n  console.error('onerror 监听到异常:', event);\n};\n\nconst myPromise = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    throw new Error('not login');\n    resolve();\n  }, 100);\n})\n```\n\n打印结果：\n\n![image-20230624172350994](https://img.smyhvae.com/image-20230624172350994.png)\n\n上面的代码中，unhandledrejection 无法监听异常，因为定时器里的代码属于宏任务。\n\n### resolve()之后，再报错无效\n\n代码举例：\n\n```js\nconst myPromise = new Promise((resolve, reject) => {\n  resolve('fulfilled');\n  throw new Error(\"自定义错误\");\n});\n\nmyPromise.then(res => {\n  console.log(\"res\", res);\n  return res + 1;\n}).catch(err => {\n  console.log(\"err：\", err);\n});\n```\n\n打印结果：\n\n```js\nres fulfilled\n```\n\n上方代码中，第3行的异常代码相当于没写。因为 resolve()之后，Promise的状态会立即进入 fulfilled，然后走到 then()，状态不可逆。\n\n\n\n## async await 的异常处理\n\n### 捕获异常\n\n代码举例：\n\n```js\nfunction requestData1() {\n  return new Promise((resolve, reject) => {\n    reject('任务1失败');\n  })\n}\n\nfunction requestData2() {\n  return new Promise((resolve, reject) => {\n    resolve('任务2成功');\n  })\n}\n\nasync function getData() {\n  // requestData1 在执行时，遇到异常\n  await requestData1();\n  /*\n  由于上面的代码在执行是遇到异常，所以，这里虽然什么都没写，底层默认写了如下代码：\n  return Promise.reject('任务1失败');\n  */\n\n  // 下面这两行代码不会再执行了，因为上面的代码遇到了异常\n  console.log('qianguyihao1');\n  await requestData2();\n}\n\ngetData();\n\n// 【注意】定时器里的代码会正常实行，因为它在另外一个宏任务里，不在上面的微任务里\nsetTimeout(() => {\n  console.log('qianguyihao2');\n}, 100)\n```\n\n打印结果：\n\n![image-20230615085743284](https://img.smyhvae.com/image-20230615085743284.png)\n\n所以，为了避免上述问题，我们还需要手动捕获异常。我们捕获到异常之后，这个异常就不会继续网上抛了，更不会抛给浏览器。\n\n## 高级用法\n\n### \n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/12-事件循环机制、宏任务和微任务.md",
    "content": "---\ntitle: 12-事件循环机制、宏任务和微任务\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 浏览器的事件循环机制（重要）\n\n![image-20230608154453933](https://img.smyhvae.com/image-20230608154453933.png)\n\n执行顺序如下：\n\n-   同步任务：进入主线程后，立即执行。\n\n-   异步任务：会先进入 Event Table；等时间到了之后，再进入 任务队列 （Event Queue）排队（排队是因为同一时间，JS 只能执行一个任务），先进先出。比如说，`setTimeout(()=> {}, 1000)`这种定时器任务，需要等一秒之后再进入 Event Queue。\n\n-   当主线程的任务执行完毕之后，此时主线程处于空闲状态，于是会去读取 Event Queue 中的任务队列，如果有任务，则进入到主线程去执行。\n\n## Node.js 事件循环机制\n\n浏览器的 EventLoop 依据的是 HTML5 规范。而 Node.js 的 EventLoop 是由Node.js底层的 libuv 规定的。 libuv是一个专注于异步IO的跨平台库。\n\nNode.js的事件循环中，有六个队列。其中，微任务有两个队列，宏任务有四个队列。\n\n一、微任务队列：\n\n- 顺序1：next tick queue。比如：process.nextTick\n- 顺序2：other queue。比如：Promise的then回调、queueMicrotask\n\n二、宏任务队列：\n\n- 顺序3：timer queue。比如：setTimeout、setInterval\n- 顺序4：poll queue。比如：IO事件\n- 顺序5：check queue。比如：setImmediate\n- 顺序6：close queue。比如：close事件\n\n参考链接：\n\n- 【荐】浏览器及nodeJS中的EventLoop事件循环机制：https://www.cnblogs.com/weiyongchao/p/13766429.html\n- 浏览器和Node 事件循环的区别：https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/26\n\n\n\n## 宏任务和微任务\n\nJS中的任务分为同步任务、异步任务。\n\nJS中的异步任务分为宏任务（macrotask）、微任务（microtask）。在早期，异步任务中只有宏任务，没有微任务。后来的语言标准中，推出了“微任务”，因为**希望微任务能够尽早执行**。\n\n### 宏任务、微任务分类\n\n事件循环的队列中，有两个队列。\n\n1、**宏任务队列**，包含这些任务：\n\n- ajax 网络请求\n- setTimeout、setInterval\n- DOM事件\n- UI渲染\n- I/O文件读写操作。\n\n2、**微任务队列**，包含这些任务：\n\n- Promise的then回调\n-  Mutation Observer API：监听DOM节点变化。\n- queueMicrotask()：可直接将某个任务加入到微任务队列中。\n\n在执行一个 Promise 对象时，当走完 resolve() 进入 fulfilled状态后，会立刻把 `.then()`里面的代码加入到**微任务队列**当中。\n\n### 任务的执行顺序\n\nJS中的任务执行顺序：**同步任务 --> 微任务 --> 宏任务**。\n\n在执行任何一个宏任务之前（不是队列，是一个宏任务），都会**先查询微任务队列中是否还有任务需要执行**：\n\n- 当前宏任务执行之前，必须要保证微任务队列是空的。\n- 如果微任务队列不为空，那就优先执行微任务队列中的任务。\n\n\n\n## 任务执行顺序的面试题\n\n实际开发中，基本不会出现下面这些题目，因为很多时候我们无法精准控制异步任务的执行顺序。但是它们在面试中出现的频率特别高，因为熟悉这些思维训练，有利于考察我们对JS单线程、事件循环机制、宏任务和微任务等原理的掌握程度。\n\n### 题 1：宏任务和微任务的执行顺序\n\n```js\nsetTimeout(() => {\n  // 宏任务\n  console.log('setTimeout');\n}, 0);\n\nnew Promise((resolve, reject) => {\n  resolve();\n  console.log('promise1'); // 同步任务\n}).then((res) => {\n  // 微任务\n  console.log('promise then');\n});\n\nconsole.log('同步任务'); // 同步任务\n```\n\n打印结果：\n\n```\npromise1\n同步任务\npromise then\nsetTimeout\n```\n\n上方代码执行的顺序依次是：**同步任务 --> 微任务 --> 宏任务**。\n\n### 题 2：在宏任务中嵌套了微任务\n\n```js\nnew Promise((resolve, reject) => {\n  setTimeout(() => {\n    resolve();\n    console.log('setTimeout'); // 宏任务\n  }, 0);\n  console.log('promise1');\n}).then((res) => {\n  // 微任务\n  console.log('promise then');\n});\n\nconsole.log('同步任务');\n```\n\n打印结果：\n\n```\npromise1\n同步任务\nsetTimeout\npromise then\n```\n\n上方代码解释：在执行宏任务的**过程中**，创建了一个微任务。但是需要**先把当前这个宏任务执行完**，再去**创建并执行**微任务。\n\n### 题3：综合题\n\n```js\nconsole.log(\"script start\")\n\nsetTimeout(() => {\n  console.log(\"setTimeout1\");\n  new Promise(resolve => {\n    resolve();\n  }).then(() => {\n    new Promise(resolve => {\n      resolve();\n    }).then(() => {\n      console.log(\"then1\");\n    });\n    console.log(\"then2\");\n  });\n});\n\nnew Promise(resolve => {\n  // 下面这两行代码，即便调换顺序，也不影响打印结果\n  console.log(\"promise1\");\n  resolve();\n}).then(() => {\n  console.log(\"then3\");\n});\n\nsetTimeout(() => {\n  console.log(\"setTimeout2\");\n});\n\nconsole.log('同步代码');\n\nqueueMicrotask(() => {\n  console.log(\"queueMicrotask\")\n});\n\nnew Promise(resolve => {\n  resolve();\n}).then(() => {\n  console.log(\"then4\");\n});\n\nconsole.log(\"script end\");\n```\n\n打印结果：\n\n```\n// 第一次循环\nscript start\npromise1\n同步代码\nscript end\n\n// 第二次循环\nthen3\nqueueMicrotask\nthen4\n\n// 第三次循环\nsetTimeout1\nthen2\nthen1\n\n// 第四次循环\nsetTimeout2\n```\n\n### 题4：async await 题目\n\n代码举例：\n\n```js\nconsole.log('script start')\n\nasync function async2() {\n  console.log('async2')\n}\n\nasync function async1() {\n  console.log('async1 start')\n  await async2();\n  console.log('async1 end')\n}\n\nsetTimeout(() => {\n  console.log('setTimeout')\n}, 0)\n\nasync1();\n\nnew Promise(resolve => {\n  console.log('promise1')\n  resolve();\n}).then(function () {\n  console.log('then1')\n})\n\nconsole.log('script end');\n```\n\n打印结果：\n\n```\nscript start\nasync1 start\nasync2\npromise1\nscript end\n\nasync1 end\nthen1\n\nsetTimeout\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/13-Promise的高级用法.md",
    "content": "## \tNode.js 中的util.promisify()方法\n\nNode.js 中有一个内置的方法 util.promisify()，它可以很方便地将 ES5回调函数写法的方法，转成Promise写法的方法。就不需要我们手动封装Promise了。\n\n代码举例：\n\n```js\n// 引入 util 模块\nconst util = require('util');\n// 引入 fs 模块\nconst fs = require('fs');\n\n// 返回一个新的函数，这个函数是一个 Promise 对象\nconst readFilePromise = util.promisify(fs.readFile);\nreadFilePromise('readme.txt').then(res => {\n  console.log('res:', res.toString());\n});\n```\n\n## 使用 Promise 封装定时器，实现延迟函数\n\n代码举例：\n\n```js\n// 方法：XX秒后执行指定的代码。这个方法，就是在宏任务（定时器）的执行过程中，创建了一个微任务（resolve）\nfunction delaySeconds(delay = 1000) {\n    return new Promise((resolve) => setTimeout(resolve, delay));\n}\n\ndelaySeconds(2000)\n    .then(() => {\n        console.log('qiangu');\n        return delaySeconds(3000);\n    })\n    .then(() => {\n        console.log('yihao');\n    });\n```\n\n打印结果：\n\n```js\n// 2秒后打印：\nqiangu\n\n// 再等3秒后打印：\nyihao\n```\n\n\n\n## 请求重试\n\n参考链接：\n\n- 网络请求失败自动重试 js 重试机制：https://blog.csdn.net/Seasons_in_your_sun/article/details/126468481\n\n\n\n\n\n"
  },
  {
    "path": "06-JavaScript基础：异步编程/14-Promise常见面试题.md",
    "content": "## 这些代码的打印结果是什么？\n\n想要考察一个人对 Promise 的掌握程度，就让他看看这些代码的打印结果是什么。\n\n**例1**：resolve()多次\n\n```js\nconst promise = new Promise((resolve, reject) => {\n  resolve();\n  resolve();\n});\n\npromise\n  .then(res => {\n    console.log('成功的回调');\n  })\n  .catch(err => {\n    console.log('失败的回调');\n  });\n```\n\n打印结果：\n\n```\n成功的回调\n```\n\n"
  },
  {
    "path": "07-JavaScript进阶/01-var、let、const的区别.md",
    "content": "---\ntitle: 01-var、let、const的区别\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## var、let、const 的区别\n\n### 1、var 声明的变量会挂载在 window 对象上，而 let 和 const 声明的变量不会\n\n举例：\n\n```js\nvar a = '我是a';\nconsole.log(a); // 打印结果：我是a\nconsole.log(window.a); // 打印结果：我是a\n```\n\n```js\nlet b = '我是b';\nconsole.log(b); // 打印结果：我是b\nconsole.log(window.b); // 打印结果：undefined\n```\n\n```js\nlet c = '我是c';\nconsole.log(c); // 打印结果：我是c\nconsole.log(window.b); // 打印结果：undefined\n```\n\nvar 的这一特性，会造成 window 全局变量的污染。举例如下：\n\n```js\nvar innerHeight = 100;\nconsole.log(window.innerHeight); // 打印结果：永远都是100  ==> 会覆盖 window 自带的 innerHeight 属性\n```\n\n### 2、var 声明的变量存在变量提升，let 和 const 声明的变量不存在变量提升\n\n举例：(先使用，再声明)\n\n```js\nconsole.log(a); // 打印结果：undefined ==> a已经声明但没有赋值\nvar a = '我是a';\n```\n\n```js\nconsole.log(b); // 报错：Uncaught ReferenceError: Cannot access 'b' before initialization ==> 找不到b这个变量\nlet b = '我是b';\n```\n\n```js\nconsole.log(c); // 报错：Uncaught ReferenceError: Cannot access 'c' before initialization ==> 找不到c这个变量\nconst c = '我是c';\n```\n\n### 3、var 声明不存在块级作用域，let 和 const 声明存在块级作用域\n\n举例：\n\n```js\n{\n    var a = '我是a';\n    let b = '我是b';\n    const c = '我是c';\n}\n\nconsole.log(a); // 我是a\nconsole.log(b); // 报错：Uncaught ReferenceError: b is not defined ==> 找不到b这个变量\nconsole.log(c); // 报错：Uncaught ReferenceError: c is not defined ==> 找不到c这个变量\n```\n\n报错是因为找不到 b 和 c 这两个变量。\n\n### 4、同一作用域下，var 可以重复声明变量，let 和 const 不能重复声明变量\n\n```js\nvar a = '我是a';\nvar a = 'qianguyihao';\nconsole.log(a); // 打印结果：qianguyihao\n```\n\n```js\nlet b = '我是b';\nlet b = 'qianguyihao';\nconsole.log(b); //报错：Uncaught SyntaxError: Identifier 'b' has already been declared  ==> 变量 b 已经被声明了\n```\n\n```js\nconst c = '我是c';\nconst c = 'qianguyihao';\nconsole.log(c); //报错：Uncaught SyntaxError: Identifier 'c' has already been declared  ==> 变量 c 已经被声明了\n```\n\n备注：通过第3、第4点可以看出：使用 let/const 声明的变量，不会造成全局污染。\n\n\n\n### 5、let 和 const 的暂时性死区（DTC）\n\n**举例 1**：（表现正常）\n\n```js\nconst name = 'qianguyihao';\n\nfunction foo() {\n    console.log(name);\n}\n\nfoo(); // 执行函数后，打印结果：smyhvae\n```\n\n上方例子中， 变量 name 被声明在函数外部，此时函数内部可以直接使用。\n\n**举例 2**：（报错）\n\n```js\nconst name = 'qianguyihao';\n\nfunction foo() {\n    console.log(name);\n    const name = 'hello';\n}\n\nfoo(); // 执行函数后，控制台报错：Uncaught ReferenceError: Cannot access 'name' before initialization\n```\n\n代码解释：如果在当前块级作用域中使用了变量 name，并且当前块级作用域中通过 let/const 声明了这个变量，那么，**声明语句必须放在使用之前，也就是所谓的 DTC（暂时性死区）**。DTC 其实是一种保护机制，可以让我们养成良好的编程习惯。\n\n关于”暂时性死区“的更多介绍，详本项目的另一篇文章《JavaScript之ES6语法：变量let、const和块级作用域.md》。\n\n\n### 6、const：一旦声明必须赋值；声明后不能再修改\n\n一旦声明必须赋值：\n\n```js\nconst a;\nconsole.log(a); // 报错：Uncaught SyntaxError: Missing initializer in const declaration\n```\n\n### 总结\n\n基于上面的种种区别，我们可以知道：var 声明的变量，很容易造成全局污染。以后我们尽量使用 let 和 const 声明变量吧。\n\n\n\n\n\n## const 常量到底能不能被修改\n\n我们知道：用 const 声明的变量无法被修改。但还有一点，我们一定要记住：\n\n-   如果用 const 声明基本数据类型，则无法被修改；\n\n-   如果用 const 声明引用数据类型（即“对象”），这里的“无法被修改”指的是**不能改变内存地址的引用**；但对象里的内容是可以被修改的。\n\n举例 1：（不能修改）\n\n```js\nconst name = 'qianguyihao';\nname = 'vae'; // 因为无法被修改，所以报错：Uncaught TypeError: Assignment to constant variable\n```\n\n举例 2：（不能修改）\n\n```js\nconst obj = {\n    name: 'qianguyihao',\n    age: 28,\n};\n\nobj = { name: 'vae' }; // 因为无法被修改，所以报错：Uncaught TypeError: Assignment to constant variable\n```\n\n举例 3：（可以修改）\n\n```js\nconst obj = {\n    name: 'qianguyihao',\n    age: 28,\n};\nobj.name = 'vae'; // 对象里的 name 属性可以被修改\n```\n\n因为 变量名 obj 是保存在**栈内存**中的，它代表的是对象的引用地址，它是基本数据类型，无法被修改。但是 obj 里面的内容是保存在**堆内存**中的，它是引用数据类型，可以被修改。\n\n\n## 传值和传址的区别\n\n详见《JavaScript基础/对象简介.md》。\n\n## for 循环的经典案例\n\n详见《JavaScript之ES6语法：变量let、const和块级作用域.md》。\n\n## 参考链接\n\n-   [JS 中 var、let、const 区别](https://juejin.im/post/5e49249be51d4526e651b654)\n"
  },
  {
    "path": "07-JavaScript进阶/02-浅拷贝和深拷贝.md",
    "content": "## 前言\n\n在 JavaScript 的编程中经常需要对数据进行复制，这就涉及到浅拷贝和深拷贝，是非常重要的概念。\n\n## 浅拷贝\n\n### 概念\n\n创建一个新的对象B，来接收你要重新复制的对象A的值：\n\n- 如果对象A里面的属性是基本类型，拷贝的是基本类型的值；\n- 但如果对象A里面的属性是引用类型，拷贝的是内存中的**地址**（不是拷贝**值**）。也就是说，拷贝后的内容和原始内容，指向的是同一个地址。如果一个对象的属性值发生了变化，另一个对象的属性值也会发生变化。\n\n浅拷贝在拷贝引用类型的数据时，只拷贝**第一层**的属性，再深层的属性无法进行拷贝。用一个成语形容叫“藕断丝连”。\n\n\n\n\n\n## 深拷贝\n\n### 概念\n\n创建一个新的对象B，来接收你要重新复制的对象A的值：\n\n- 在堆内存中开辟了一块全新的内存地址，将对象A的属性完全复制过来。\n- 这两个对象相互独立、互不影响，彻底实现了内存上的分离。\n\n下面讲一下实现深拷贝的几种方式。\n\n### 方式1：JSON.stringify() 和 JSON.parse()\n\n这是最简单的深拷贝方法，先把对象序列化成 json 字符串，然后将JSON 字符串生成一个新的对象。\n\n代码实现：\n\n```js\nlet obj1 = { a:1, b:[1,2,3] }\nlet str = JSON.stringify(obj1)；\nlet obj2 = JSON.parse(str)；\nconsole.log(obj2);   //{a:1,b:[1,2,3]}\n\nobj1.a = 2；\nobj1.b.push(4);\nconsole.log(obj1);   //{a:2,b:[1,2,3,4]}\nconsole.log(obj2);   //{a:1,b:[1,2,3]}\n```\n\n方式1属于乞丐版。缺点是：\n\n（1）主要缺点：\n\n- 无法拷贝函数、undefined、symbol。经过 JSON.stringify 序列化之后的字符串中这个键值对会消失。\n- 无法拷贝 Map、Set；\n- 无法拷贝对象的循环引用，即 obj[key] = obj。\n\n（2）其他缺点：\n\n- 拷贝 Date 引用类型会变成字符串；\n- 拷贝 RegExp 引用类型会变成空对象；\n- 无法拷贝不可枚举的属性；\n- 无法拷贝对象的原型链；\n- 对象中含有 NaN、Infinity 以及 -Infinity，JSON 序列化的结果会变成 null；\n\n无法拷贝函数的代码举例：\n\n```js\nconst obj = { fn: () => {}, name: 'qianguyihao' };\nconsole.log(JSON.stringify(obj)); // {\"name\":\"qianguyihao\"}\n```\n\n无法拷贝循环引用的代码举例：\n\n```js\nconst obj = { fn: () => {}, name: 'qianguyihao' };\nobj.self = obj;\n/*\n\t控制台报错：\n\t\tUncaught TypeError: Converting circular structure to JSON\n\t\t--> starting at object with constructor 'Object'\n\t\t--- property 'self' closes the circle\n\t\tat JSON.stringify (<anonymous>)\n*/\nconsole.log(JSON.stringify(obj));\n```\n\n\n\n小结：如果你的数据结构是简单的数据类型，使用方式1是最简单和快捷的选择；但如果数据类型稍微复杂一点，方式1 就不行了。\n\n### 方式2：手写递归\n\n如果只考虑简单的数组、对象，方式2是满足要求的。\n\n\n```js\nconst obj1 = {\n    name: 'qianguyihao',\n    age: 30,\n    address: {\n        city: 'shenzhen'\n    }\n}\n\nconst obj2 = deepClone(obj1)\nobj2.address.city = 'beijing'\nconsole.log(obj1.address.city)\n\n/**\n * 深拷贝\n * @param {Object} obj 要拷贝的对象\n */\nfunction deepClone(obj = {}) {\n    // 1、判断是值类型还是引用类型\n    if (typeof obj !== 'object' || obj == null) {\n        // obj 如果不是对象和数组，或者是 null，就直接return\n        return obj\n    }\n\n    // 2、判断是数组还是对象\n    // 初始化返回结果：数组或者对象\n    let result\n    if (obj instanceof Array) {\n        result = []\n    } else {\n        result = {}\n    }\n\n    for (let key in obj) {\n        // 保证 key 不是原型的属性\n        if (obj.hasOwnProperty(key)) {\n            // 3、递归【关键代码】\n            result[key] = deepClone(obj[key])\n        }\n    }\n\n    return result\n}\n\nlet obj1 = {\n  a:{\n    b:1\n  }\n}\nlet obj2 = deepClone(obj1);\nobj1.a.b = 2;\nconsole.log(obj2);   //  {a:{b:1}}\n\n```\n\n上面的代码，还有一种写法，更容易理解：\n\n```js\n\nfunction deepClone(obj) {\n    let cloneObj = {}\n    for(let key in obj) {                 // 遍历\n        if(typeof obj[key] ==='object') {\n        cloneObj[key] = deepClone(obj[key])  // 是对象就再次调用该函数递归\n        } else {\n        cloneObj[key] = obj[key]  // 如果是基本类型，直接复制值\n        }\n    }\n    return cloneObj\n}\n\nlet obj1 = {\n    a:{\n        b:1\n    }\n}\nlet obj2 = deepClone(obj1);\nobj1.a.b = 2;\nconsole.log(obj2);   //  {a:{b:1}}\n```\n\n方式2只考虑了 object 和 Array这种 对普通的引用类型的值，是属于比较基础的深拷贝。缺点是：\n\n（1）主要缺点：\n\n- 无法拷贝函数 Function。\n- 无法拷贝 Map、Set。\n- 无法拷贝对象的循环引用，即 obj[key] = obj。\n\n（2）其他缺点：\n\n- 无法拷贝不可枚举的属性以及 Symbol 类型。\n- 无法拷贝 Date、RegExp、Error 这样的引用类型。\n\n### 方式3：改进版\n\n针对上面几个问题，可以用如下几点改进：\n\n\n（1）针对能够遍历对象的不可枚举属性以及 Symbol 类型，我们可以使用 Reflect.ownKeys 方法；\n\n（2）当参数为 Date、RegExp 类型，则直接生成一个新的实例返回；\n\n（3）利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性，以及对应的特性，顺便结合 Object 的 create 方法创建一个新对象，并继承传入原对象的原型链；\n\n（4）利用 WeakMap 类型作为 Hash 表，因为 WeakMap 是弱引用类型，可以有效防止内存泄漏（你可以关注一下 Map 和 weakMap 的关键区别，这里要用 weakMap），作为检测循环引用很有帮助，如果存在循环，则引用直接返回 WeakMap 存储的值。\n\n```js\n/**\n * 深拷贝\n * @param obj obj\n * @param map weakmap 为了避免循环引用\n */\nfunction cloneDeep(obj, map = new WeakMap()) {\n    if (typeof obj !== 'object' || obj == null ) return obj\n\n    // 避免循环引用\n    const objFromMap = map.get(obj)\n    if (objFromMap) return objFromMap\n\n    let target = {}\n    map.set(obj, target)\n\n    // Map\n    if (obj instanceof Map) {\n        target = new Map()\n        obj.forEach((v, k) => {\n            const v1 = cloneDeep(v, map)\n            const k1 = cloneDeep(k, map)\n            target.set(k1, v1)\n        })\n    }\n\n    // Set\n    if (obj instanceof Set) {\n        target = new Set()\n        obj.forEach(v => {\n            const v1 = cloneDeep(v, map)\n            target.add(v1)\n        })\n    }\n\n    // Array\n    if (obj instanceof Array) {\n        target = obj.map(item => cloneDeep(item, map))\n    }\n\n    // Object\n    for (const key in obj) {\n        const val = obj[key]\n        const val1 = cloneDeep(val, map)\n        target[key] = val1\n    }\n\n    return target\n}\n```\n"
  },
  {
    "path": "07-JavaScript进阶/03-迭代器和生成器.md",
    "content": "## 迭代器\n\n### 概念\n\n**迭代器**（Iterator）是 JavaScript 中一种特殊的对象，它提供了一种**统一的、通用的**方式**遍历**个各种不同类型的数据结构。可以遍历的数据结构包括：数组、字符串、Set、Map 等**可迭代对象**。我们也可以自定义实现迭代器，以支持遍历自定义的数据结构。\n\n通过迭代器，我们可以按顺序逐个获取数据中的元素，不需要手动跟踪索引（索引也可称之为指针、游标）。迭代器的行为很像数据库中的游标（cursor）。\n\n我们也不需要关心可迭代对象内部的实现细节，即不需要关心目标对象是数组还是字符串，还是其他的数据结构。对于迭代器来说，这些数据结构都是一样的处理方式。\n\n迭代器是一种常见的编程模式，最早出现在1974年设计的CLU编程语言中。不仅仅在JS中，其他许多编程语言（比如 Java、Python 等）都提供了迭代器的概念和实现。技术实现各有不同，但目的都是帮助我们用通用的方式遍历对象的数据结构，提高代码的简洁性、可读性和维护性。\n\n### 迭代协议\n\n迭代协议并不是编程语言的内置实现或语法，而是协议。迭代协议具体分为两个协议：可迭代协议、迭代器协议。\n\n**迭代器协议**规定了产生一系列值（无论是有限个还是无限个）的标准方式。\n\n迭代器是一个具体的对象，这个对象要符合迭代器协议。**在JS中，某个对象只有实现了符合特定要求的 next() 方法，这个对象才能成为迭代器**。\n\n\n\n### 实现原理：next() 方法\n\n在JS中，迭代器的实现原理是通过定义一个特定的`next()` 方法，该方法在每次迭代中返回一个包含两个属性的对象：done 和 value。\n\n具体来说，next() 方法有如下要求：\n\n（1）参数：无参数或者有一个参数。\n\n（2）返回值：返回一个应当有以下两个属性的对象。属性值如下：\n\n- done 属性（Boolean 类型）：表示迭代是否已经完成。当迭代器遍历完所有元素时，done 为 true，否则为 false。具体解释如下：\n\n  - 如果迭代器可以产生序列中的下一个值，则为 false，这等价于没有指定 done 属性。\n\n  - 如果迭代器已将序列迭代完毕，则为 true。这种情况下，value 可以省略，如果 value 依然存在，即为迭代结束之后的默认返回值。\n\n- value 属性：包含当前迭代步骤的值，可能是具体的值，也可能是 undefined。每次调用 next() 方法时，迭代器返回下一个值。done 为true时，可以省略。\n\n\n\n### 举例：为数组创建迭代器\n\n按照上面讲的迭代器协议，我们可以给一个数组手动创建一个用于遍历的迭代器。代码举例如下：\n\n```js\nconst strArr = ['qian', 'gu', 'yi', 'hao'];\n\n// 为数组封装迭代器\nfunction createArrayIterator(arr) {\n  let index = 0;\n  return {\n    next: () => {\n      if (index < arr.length) {\n        return { done: false, value: arr[index++] };\n      } else {\n        return { done: true };\n      }\n    },\n  };\n}\n\nconst strArrIterator = createArrayIterator(strArr);\nconsole.log(JSON.stringify(strArrIterator.next()));\nconsole.log(JSON.stringify(strArrIterator.next()));\nconsole.log(JSON.stringify(strArrIterator.next()));\nconsole.log(JSON.stringify(strArrIterator.next()));\nconsole.log(JSON.stringify(strArrIterator.next()));\n```\n\n打印结果：\n\n```\n{\"done\":false,\"value\":\"qian\"}\n{\"done\":false,\"value\":\"gu\"}\n{\"done\":false,\"value\":\"yi\"}\n{\"done\":false,\"value\":\"hao\"}\n{\"done\":true}\n```\n\n你可能会有疑问：实际开发中，我们真的需要大费周章地为一个简单的数组写一个迭代器函数吗？数组直接拿来遍历不就完事了吗？\n\n是的，这大可不必。初衷是为了了解迭代器的原理。\n\n## 可迭代对象\n\n我们要注意区分一些概念：迭代器、可迭代对象、容器。迭代器是提供迭代功能的对象。可迭代对象是被迭代的目标对象，也称之为容器。\n\n### 概念\n\n当一个对象实现了 **iterable protocol 协议**时，它就是一个可迭代对象。这个对象要求必须实现了 `@@iterator` 方法，在内部封装了迭代器。我们可以通过 `Symbol.iterator` 函数调用该迭代器。\n\n当我们使用迭代器的方式去遍历数组、字符串、Set、Map 等数据结构时，这些数据对象就属于可迭代对象。这些数据对象本身，内部就自带了迭代器。\n\n可是，有些数据对象，并不具备可迭代的能力，那要怎么封装成可迭代对象呢？以及，可迭代对象需要具备什么特征？可迭代对象有什么用处？这就是本段要讲的内容。\n\n### 可迭代对象的特征\n\n凡是可迭代对象，都具备如下特征：\n\n1、可迭代对象都有一个 [Symbol.iterator] 函数。通过这个函数，我们可以进行数据遍历操作。以一个简单的数组进行举例：\n\n```js\nconst myArr = ['qian', 'gu', 'yi', 'hao'];\n\nconsole.log(typeof myArr[Symbol.iterator]);\nconsole.log(myArr[Symbol.iterator]);\n\nconsole.log(typeof myArr[Symbol.iterator]());\nconsole.log(myArr[Symbol.iterator]());\n// 获取数组自带的迭代器对象\nconst myIterator = myArr[Symbol.iterator]();\n\n// 通过迭代器的 next() 方法遍历数组\nconsole.log(myIterator.next());\nconsole.log(myIterator.next());\nconsole.log(myIterator.next());\nconsole.log(myIterator.next());\nconsole.log(myIterator.next());\n```\n\n打印结果：\n\n<img src=\"https://img.smyhvae.com/image-20230525211636012.png\" alt=\"image-20230525211636012\" style=\"zoom:50%;\" />\n\n2、可迭对象可以进行 for ...  of 操作。其实  for ...  of  底层就是调用了 `@@iterator` 方法。代码举例：\n\n```js\nconst myArr = ['qian', 'gu', 'yi', 'hao'];\n\n// 可迭代对象可以进行 for ... of 操作。for ... of 也是一种遍历操作。\nfor (const item of myArr) {\n  // 这里的 item，其实就是迭代器里的 value 属性的值。\n  console.log(item);\n}\n```\n\n打印结果：\n\n```\nqian\ngu\nyi\nhao\n```\n\n### 原生可迭代对象\n\n以下这些对象，都是原生可迭代对象，请务必记住：\n\n- String 字符串\n- Array 数组\n- Map\n- Set\n- arguments 对象\n- NodeList 对象（DOM节点的集合）\n\n原生可迭代对象的内部已经实现了可迭代协议，它们都符合可迭代对象的特征。比如，它们内部都有一个迭代器；他们可以用  for ... of 进行遍历。\n\n为何要记住上面这些可迭代对象，因为可迭代对象的应用场景非常多，且非常好用。我们继续往下学习。\n\n### 可迭代对象的应用场景（重要）\n\n可迭代对象有许多应用场景，包括但不仅限于：\n\n1、JavaScript的语法：\n\n- for ... of\n- 展开语法 ...\n- yield*\n- 解构赋值\n\n2、创建一些对象：\n\n- new Map([Iterable])：参数是可选的，可不传参数，也可以传一个可迭代对象作为参数\n- new WeakMap([iterable])\n- new Set([iterable])\n- new WeakSet([iterable])\n\n3、方法调用\n\n- Array.from(iterable)：将一个可迭代对象转为数组\n- Promise.all(iterable)\n- Promise.race(iterable)\n\n今后在遇到这些应用场景时，这些原生可迭代对象可以直接拿来用。\n\n 比如说，通过阅读官方文档后我们得知，`new Set()`的写法中，括号里的参数可以不写，也可以传入一个可迭代对象 `iterable`。那么，字符串、数组、Set、Map等可迭代对象，在你需要的时候都可以传进去使用。而且，`const a = new Set()`写法中，变量 a 也是一个可迭代对象。\n\n`Promise.all(iterable)` 只能传数组吗？非也。准确来说，Promise.all()的参数中，传入的不是数组，而是一个可迭代对象。代码举例：\n\n```js\nconst promise1 = Promise.resolve('promise1 resolve');\nconst promise2 = Promise.resolve('promise2 resolve');\nconst promise3 = Promise.resolve('promise3 resolve');\n\nconst promiseSet = new Set();\npromiseSet.add(promise1);\npromiseSet.add(promise2);\npromiseSet.add(promise3);\n\n// 准确来说，Promise.all()的参数中，传入的不是数组，而是一个可迭代对象\nPromise.all(promiseSet).then(res => {\n  console.log('res:', res);\n});\n```\n\n代码举例：\n\n```\nres: ['promise1 resolve', 'promise2 resolve', 'promise3 resolve']\n```\n\narguments 同样是一个可迭代对象，但不是数组。我们可以通过`Array.from(iterable)`方法将 arguments 转为数组，进而让其享受数组的待遇，调用数组的各种方法。代码举例：\n\n```js\nfoo('a', 'b', 'c');\n\n// 定义函数\nfunction foo() {\n  // Array.from() 中的参数可以传入可迭代对象，将参数转为数组。arguments 是 foo() 函数的参数\n  const arr = Array.from(arguments);\n  console.log(arr);\n}\n```\n\n打印结果：\n\n```\n['a', 'b', 'c']\n```\n\n学完了迭代器、可迭代对象的知识之后，很多关于函数传参、数据遍历、数据结构等方面的JS知识，就能融会贯通了。\n\n## 手写迭代器\n\n很多数据对象由于不是可迭代对象，我们可以为其手动创建一个迭代器，这个数据对象就成了可迭代对象。\n\n### 为普通对象创建外部迭代器\n\n代码举例：\n\n```js\nconst myObj1 = {\n  strArr: ['qian', 'gu', 'yi', 'hao'],\n};\n\n// 为 myObj.strArr 封装迭代器\nlet index = 0;\nconst strArrIterator = {\n  next: () => {\n    if (index < myObj1.strArr.length) {\n      return { done: false, value: myObj1.strArr[index++] };\n    } else {\n      return { done: true };\n    }\n  },\n};\n\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\n```\n\n打印结果：\n\n```\n{done: false, value: 'qian'}\n{done: false, value: 'gu'}\n{done: false, value: 'yi'}\n{done: false, value: 'hao'}\n{done: true}\n```\n\n### 将普通对象封装为可迭代对象\n\n上面的数据 myObj1，不属于可迭代对象，因此我们单独写了一个迭代器对象 strArrIterator。但是这两个对象是分开的。\n\n还有一种更高级的做法是，把迭代器封装到数据对象的内部。完事之后，这个数据对象就是妥妥的可迭代对象。\n\n将普通的数据对象封装为可迭代对象时，**具体做法**是：在数据对象内部，创建一个名为`[Symbol.iterator]`的迭代器函数，这个函数名是固定的（这种写法属于计算属性名）；然后这个函数内需要返回一个迭代器，用于迭代当前的数据对象。\n\n我们以下面这两个对象为例：\n\n```js\nconst myObj1 = {\n  strArr: ['qian', 'gu', 'yi', 'hao'],\n};\n\nconst myObj2 = {\n  name: 'qianguyihao',\n  skill: 'web',\n};\n```\n\n如果尝试用 for of 去遍历它们，会报错：\n\n```js\nconst myObj2 = {\n  name: 'qianguyihao',\n  skill: 'web',\n};\n\nfor (const item of myObj2) {\n   // 打印报错：Uncaught TypeError: myObj2 is not iterable。意思是：myObj2 不是可迭代对象\n  console.log(item);\n}\n```\n\n所以，我们可以将这两个普通对象封装为可迭代对象。\n\n1、将 myObj1 封装为可迭代对象，遍历 myObj1.strArr。代码举例如下：\n\n```js\nconst myObj1 = {\n  strArr: ['qian', 'gu', 'yi', 'hao'],\n  // 在 myObj1 的内部创建一个迭代器\n  [Symbol.iterator]: function () {\n    let index = 0;\n    const strArrIterator = {\n      next: function () {\n        if (index < myObj1.strArr.length) {\n          return { done: false, value: myObj1.strArr[index++] };\n        } else {\n          return { done: true };\n        }\n      },\n    };\n    return strArrIterator;\n  },\n};\n\n// 获取 myObj1 的迭代器对象\nconst strArrIterator = myObj2[Symbol.iterator]();\n// 通过迭代器遍历 myObj1.strArr 的数据\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\n```\n\n打印结果：\n\n```\n{done: false, value: 'qian'}\n{done: false, value: 'gu'}\n{done: false, value: 'yi'}\n{done: false, value: 'hao'}\n{done: true}\n```\n\n上方代码有一个改进之处，如果把迭代器函数改成箭头函数，就可以通过 `this.strArr` 代表 `myObj2.strArr` 了，写法更简洁。代码改进如下：\n\n```js\nconst myObj1 = {\n  strArr: ['qian', 'gu', 'yi', 'hao'],\n  // 在 myObj1 的内部创建一个迭代器\n  [Symbol.iterator]: function () {\n    let index = 0;\n    const strArrIterator = {\n      next: () => {\n        if (index < this.strArr.length) {\n          return { done: false, value: this.strArr[index++] };\n        } else {\n          return { done: true };\n        }\n      },\n    };\n    return strArrIterator;\n  },\n};\n\n// 获取 myObj1 的迭代器对象\nconst strArrIterator = myObj2[Symbol.iterator]();\n// 通过迭代器遍历 myObj1.strArr 的数据\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\nconsole.log(strArrIterator.next());\n```\n\n打印结果不变。\n\n2、将 myObj2 封装为可迭代对象，遍历里面的键值对。代码举例如下：\n\n```js\nconst myObj2 = {\n  name: 'qianguyihao',\n  skill: 'web',\n  // 将普通对象 myObj2 封装为可迭代对象，目的是遍历 myObj2 的键值对\n  [Symbol.iterator]: function () {\n    // const keys = Object.keys(this); // 获取对象的 key\n    // const values = Object.values(this); // 获取对象的 value\n    const entries = Object.entries(this); // 获取对象的键值对\n    let index = 0;\n    const iterator = {\n      next: () => {\n        if (index < entries.length) {\n          return { done: false, value: entries[index++] };\n        } else {\n          return { done: true };\n        }\n      },\n    };\n    return iterator;\n  },\n};\n\n// 可迭对象可以进行for of操作，遍历对象的键值对\nfor (const item of myObj2) {\n  const [key, value] = item;\n  console.log(key, value);\n}\n```\n\n打印结果：\n\n```\nname qianguyihao\nskill web\n```\n\n### 将自定义类封装为可迭代对象\n\n在面向对象开发时，如果你希望自己创建的类也具备可迭代的能力，那么，你可以在定义类的时候手动添加一个 `@@iterator`方法，让其成为可迭代对象。\n\n代码举例：\n\n```json\n// 定义类\nclass Person {\n  constructor(name, arr) {\n    this.name = name;\n    this.arr = arr;\n  }\n\n  // 定义一个名为 [Symbol.iterator] 的实例方法，封装迭代器\n  [Symbol.iterator]() {\n    let index = 0;\n    const iterator = {\n      next: () => {\n        if (index < this.arr.length) {\n          return { done: false, value: this.arr[index++] };\n        } else {\n          return { done: true };\n        }\n      },\n    };\n    return iterator;\n  }\n}\n\nconst person1 = new Person('千古壹号', ['前端', '工程师']);\nconst person2 = new Person('许嵩', ['想象之中', '有何不可']);\n\n// Person的实例已经封装为可迭代对象了，可以通过 for ... of 进行遍历\nfor (const item of person2) {\n  console.log(item);\n}\n```\n\n打印结果：\n\n```\n想象之中\n有何不可\n```\n\n### 如何中断迭代器，停止继续遍历\n\n迭代器在遍历数据对象的过程中，如果我们希望在符合指定条件下停止继续遍历，那么，我们可以使用 break、return、throw 等关键字中断迭代器。其中， break 关键字用得最多。\n\n此外，我们还可在迭代器函数中添加一个名为`return()`的方法，这个方法的作用是监听迭代器的中断，书写代码的位置与 `next()`方法并列。\n\n代码举例如下：\n\n```js\nconst myObj2 = {\n  name: 'qianguyihao',\n  skill: 'web',\n  // 将普通对象 myObj2 封装为可迭代对象，目的是遍历 myObj2 的键值对\n  [Symbol.iterator]: function () {\n    const entries = Object.entries(this); // 获取对象的键值对\n    let index = 0;\n    const iterator = {\n      next: () => {\n        if (index < entries.length) {\n          return { done: false, value: entries[index++] };\n        } else {\n          return { done: true };\n        }\n      },\n      // 【关键代码】监听迭代器的中断\n      return: () => {\n        console.log('迭代器被中断了');\n        return { done: true };\n      },\n    };\n    return iterator;\n  },\n};\n\n// 可迭对象可以进行 for of 操作，遍历对象的键值对\nfor (const item of myObj2) {\n  const [key, value] = item;\n  console.log(key, value);\n  if (value == 'qianguyihao') {\n    // 【关键代码】如果发现 value 为 qianguyihao，则中断迭代器，停止继续遍历\n    break;\n  }\n}\n```\n\n打印结果：\n\n```\nname qianguyihao\n迭代器被中断了\n```\n\n根据打印结果可以看出，迭代器只遍历了 myObj2 对象的第一个元素，符合指定条件后，通过 break 语句中断了迭代器，停止了继续遍历；与此同时，迭代器中的 return() 函数监听到了迭代器的中断。对了，return() 函数中，还需要写 `return { done: true }`表示迭代器的使命已结束；如果不写这行则会报错：`Uncaught TypeError: Iterator result undefined is not an object`。\n\n## 生成器\n\n### 概念\n\n我们平时写的函数，基本是通过 return 返回值，或者发生异常，函数才会终止执行。这还不够灵活。\n\n**生成器**是 ES6 中新增的一种特殊的函数，所以也称为“生成器函数”。它可以更灵活地控制函数什么时候执行， 什么时候暂停等等，**控制精度很高**。\n\n生成器函数使用 `function*` 语法编写。最初调用时，生成器函数不执行任何代码，而是返回一个称为 Generator 的迭代器。通过调用生成器的 next() 方法时，Generator 函数将执行，直到遇到 yield 关键字时暂停执行。\n\n可以根据需要多次调用该函数，并且每次都返回一个新的 Generator，但每个 Generator 只能迭代一次。\n\n### 生成器函数和普通函数的区别\n\n- 生成器函数需要在 `function` 关键字后面加一个符号 `*`。\n- 生成器函数可以通过 `yield` 关键字控制函数的执行流程。\n- 生成器函数的返回值是一个生成器（Generator）。生成器是一种特殊的迭代器。\n\n## 生成器函数拆解\n\n### 定义一个生成器函数\n\n如果要定义一个生成器函数，我们需要在`function`单词和函数名之间加一个`*`符号。\n\n`*`符号有下面四种写法，最推荐的是第一种写法：\n\n```js\nfunction* generator1() { /*code*/ } // 推荐写法\nfunction *generator2() { /*code*/ }\nfunction * generator3() { /*code*/ }\nfunction*generator4() { /*code*/ }\n```\n\n截图如下：\n\n![image-20230624184902630](https://img.smyhvae.com/image-20230624184902630.png)\n\n代码举例：\n\n```js\nfunction* foo() {\n  console.log('1');\n  console.log('2');\n  console.log('3');\n}\n\nfoo();\n```\n\n### yield 表达式\n\n但是上面的代码写完后，并不会有打印结果，因为**我们还需要调用生成器的 next()方法，生成器函数才会执行，直到遇到 yield 关键字后暂停执行**；反反复复，**直到遇到 return关键字，或者遇到函数末尾时，结束执行**。代码举例：\n\n```js\n// 通过 * 符号，定义一个生成器函数\nfunction* foo() {\n  console.log('1');\n  yield;\n\n  console.log('2');\n  // 下面这行 console.log('a') 会跟 yield 一起执行\n  yield console.log('a');\n\n  console.log('3');\n}\n\nconst generator = foo(); // 返回一个生成器对象\n// 调用生成器的 next()方法，生成器才会执行，直到遇到 yield 后暂停执行\ngenerator.next(); // 这行代码执行后，打印结果是：1\ngenerator.next(); // 这行代码执行后，打印结果是：1 2 a\ngenerator.next(); // 这行代码执行后，打印结果是：1 2 a 3\n```\n\n仔细看注释，生成器 generator 每调用一次 next() ，foo()函数里的代码就往下执行一次，直到遇到 yield 后暂停。\n\n### next() 方法的返回值\n\n生成器既然是一种特殊的迭代器，那么它也有 next()方法，而且 next()方法里同样有 done 和 value 这两个属性。我们来看看这两个属性的**默认属性值**是什么：\n\n```js\n// 通过 * 符号，定义一个生成器函数\nfunction* foo() {\n  console.log('阶段1');\n  yield;\n\n  console.log('阶段2');\n  yield;\n\n  console.log('阶段3');\n  return; // 执行 return 之后，函数不再继续往下走了，生成器的 next()方法的 done 属性值为 true。\n\n  console.log('阶段4');\n}\n\n// 执行生成器函数，返回一个生成器对象\nconst generator = foo();\n// 调用生成器的 next()方法，生成器才会执行，直到遇到 yield 后暂停执行；遇到 return关键字，或者遇到函数末尾时，结束执行。\nconsole.log(generator.next());\nconsole.log(generator.next());\nconsole.log(generator.next());\n```\n\n打印结果：\n\n```\n阶段1\n{value: undefined, done: false}\n\n阶段2\n{value: undefined, done: false}\n\n阶段3\n{value: undefined, done: true}\n```\n\n上方代码的打印结果可以看出，生成器函数在遇到函数的末尾，或者遇到 return 之后，函数就不再继续往下走了，next()方法的 done 属性值为 true。\n\n还可以看到，next()方法的 value 属性值默认为 undefined，**如果某些情况下我们希望 value属性有值**的话，可以通过 yield 关键字进行传递。代码举例：\n\n```js\n// 通过 * 符号，定义一个生成器函数\nfunction* foo() {\n  console.log('阶段1');\n  yield 'a'; // 【关键代码】yield 后面写的内容，就是传递给 next() 方法的 value 属性值\n\n  console.log('阶段2');\n  yield 'b';\n\n  console.log('阶段3');\n  return; // 这里的 return，相当于 return undefined\n\n  console.log('阶段4');\n}\n\n// 执行生成器函数，返回一个生成器对象\nconst generator = foo();\n// 调用生成器的 next()方法，生成器才会执行，直到遇到 yield 后暂停执行；遇到 return关键字，或者遇到函数末尾时，结束执行。\nconsole.log(generator.next()); // 打印生成器对象的 next()方法\nconsole.log(generator.next());\nconsole.log(generator.next());\n```\n\n打印结果：\n\n```\n阶段1\n{value: 'a', done: false}\n\n阶段2\n{value: 'b', done: false}\n\n阶段3\n{value: undefined, done: true}\n```\n\n### next() 方法的参数\n\n根据前面的代码实例得知，生成器函数是分多个阶段执行的。此时有一个诉求：如何给当前阶段传递参数呢？\n\n答案是：可以通过当前阶段 next() 方法的参数，给当前阶段传值。这个参数值会成为**上一个阶段** yield 语句的返回值。\n\n代码举例：\n\n```js\n// 通过 * 符号，定义一个生成器函数\nfunction* foo() {\n  console.log('阶段1');\n  // 【关键代码】第二次调用 next()方法时，通过 res2 接收 next()方法的参数值\n  const res2 = yield 'a';\n\n  console.log('阶段2:', res2);\n  // 第三次调用 next()方法时，通过 res3 接收 next()方法的参数值\n  const res3 = yield 'b';\n\n  console.log('阶段3:', res3);\n  return;\n}\n\n// 执行生成器函数，返回一个生成器对象\nconst generator = foo();\n// 调用生成器的 next()方法，生成器才会执行，直到遇到 yield 后暂停执行；遇到 return关键字，或者遇到函数末尾时，结束执行。\nconsole.log(generator.next()); // 执行第一阶段\nconsole.log(generator.next('next2')); // 执行第二阶段，并传参\nconsole.log(generator.next('next3')); // 指定第三阶段\n```\n\n打印结果：\n\n```\n阶段1\n{value: 'a', done: false}\n\n阶段2: next2\n{value: 'b', done: false}\n\n阶段3: ntext3\n{value: undefined, done: true}\n```\n\n第一次学习时，这段代码可能比较难理解。在理解时需要注意的是，将 next2 这个属性值赋值给 res2，这个操作的执行时机是在**第二阶段的最开始**做的，不是在第一阶段的末尾做的。并且，这个属性值是通过第一阶段的 yield 返回值接收的。\n\n### 如何中途结束生成器的执行\n\n如果想在中途结束生成器的执行，有三种方式：\n\n- 方式1：return 语句。这个在前面已经讲过。\n- 方式2：通过生成器的 return() 函数。\n- 方式3：通过生成器的 throw() 函数抛出异常。\n\n方式2的代码举例：\n\n```js\n// 通过 * 符号，定义一个生成器函数\nfunction* foo() {\n  console.log('阶段1');\n  const res2 = yield 'a';\n\n  console.log('阶段2:', res2);\n  const res3 = yield 'b';\n\n  console.log('阶段3:', res3);\n  return;\n}\n\n// 执行生成器函数，返回一个生成器对象\nconst generator = foo();\nconsole.log(generator.next());\n// 【关键代码】通过生成器的 return()方法， 立即结束 foo 函数的执行\nconsole.log(generator.return('next2'));\n// 这行写了也没用，阶段2、阶段3都不会执行的\nconsole.log(generator.next('next3'));\n```\n\n打印结果：\n\n```\n阶段1\n{value: 'a', done: false}\n\n{value: 'next2', done: true}\n\n{value: undefined, done: true}\n```\n\n上方代码可以看出，阶段2、阶段3都不会执行；return()方法里的参数传给了 value 属性。\n\n方式3的代码举例：\n\n```js\n// 通过 * 符号，定义一个生成器函数\nfunction* foo() {\n  console.log('阶段1');\n  const res2 = yield 'a';\n\n  console.log('阶段2:', res2);\n  const res3 = yield 'b';\n\n  console.log('阶段3:', res3);\n  return;\n}\n\n// 执行生成器函数，返回一个生成器对象\nconst generator = foo();\nconsole.log(generator.next());\n// 【关键代码】通过生成器的 throw()方法抛出异常， 立即结束 foo 函数的执行\nconsole.log(generator.throw(new Error('next2 error')));\n// 这行写了也没用，阶段2、阶段3都不会执行的\nconsole.log(generator.next('next3'));\n```\n\n打印结果：\n\n```\n阶段1\n{value: 'a', done: false}\n\nUncaught Error: next2 error\n```\n\n## 生成器的应用\n\n### 用生成器代替迭代器\n\n在前面的迭代器内容中，我们学习过“将普通对象封装为可迭代对象”。那段代码改用生成器的写法也可以实现。代码举例：\n\n```js\nconst myObj2 = {\n  name: 'qianguyihao',\n  skill: 'web',\n  // 将普通对象 myObj2 封装为可迭代对象，目的是遍历 myObj2 的键值对。通过生成器 function* 的的方式实现\n  [Symbol.iterator]: function* () {\n    const entries = Object.entries(this); // 获取对象的键值对\n    for (let index = 0; index < entries.length; index++) {\n      // 【关键代码】通过 yield 控制迭代器分阶段执行；并将每个阶段的值存放到迭代器的 next() 方法的 value 属性中\n      yield entries[index];\n    }\n  },\n};\n\n\n// 写法1：通过 for ... of 遍历可迭代对象\nfor (const item of myObj2) {\n  const [key, value] = item;\n  console.log(key, value);\n}\n\nconsole.log('---');\n\n// 写法2：通过 next() 方法遍历可迭代对象。与写法1等价。\nconst iterator = myObj2[Symbol.iterator]();\nconsole.log(iterator.next());\nconsole.log(iterator.next());\nconsole.log(iterator.next());\n```\n\n打印结果：\n\n```\nname qianguyihao\ndemo.html:30 skill web\n---\ndone: false, value:['name', 'qianguyihao']\ndone: false, value:['skill', 'web']\ndone: true, value:undefined\n```\n\n### 在指定数字范围内生成一个值\n\n代码举例：\n\n```js\nfunction* createValueGenerator(start, end) {\n  // 前闭后开\n  for (let i = start; i < end; i++) {\n    yield i;\n  }\n}\n\nconst valueGenerator = createValueGenerator(1, 3);\nconsole.log(valueGenerator.next());\nconsole.log(valueGenerator.next());\nconsole.log(valueGenerator.next());\n```\n\n打印结果：\n\n```\n{value: 1, done: false}\n{value: 2, done: false}\n{value: undefined, done: true}\n```\n\n## yield* 的使用\n\n### 使用 yield* 迭代可迭代对象\n\n语法格式：\n\n```js\nyield* 某个可迭代对象\n```\n\n`yield*` 是 yield 的一种语法糖，也就是一种简写形式。它会依次迭代一个**可迭对对象**，每次迭代一个值，并且会产生一个**新的可迭代对象**。\n\n我们在前面讲过可迭代对象的应用场景，简单提到过 `yield*`，接下来讲讲它具体如何使用的。\n\n先来看下面这段代码，用生成器的方式迭代数组：\n\n```js\nfunction* createArrayGenerator(arr) {\n  for (const item of arr) {\n    yield item;\n  }\n}\n\nconst myArr = ['a', 'b', 'c'];\nconst arrGenerator = createArrayGenerator(myArr);\nconsole.log(arrGenerator.next());\nconsole.log(arrGenerator.next());\nconsole.log(arrGenerator.next());\nconsole.log(arrGenerator.next());\n```\n\n打印结果：\n\n```\n{value: 'a', done: false}\n{value: 'b', done: false}\n{value: 'c', done: false}\n{value: undefined, done: true}\n```\n\n上面这段代码，换成 `yield*` 的写法会非常简洁，如下：\n\n```js\nfunction* createArrayGenerator(arr) {\n  // 【关键代码】yield* 的后面必须是一个可迭代对象\n  yield* arr;\n}\n\nconst myArr = ['a', 'b', 'c'];\nconst arrGenerator = createArrayGenerator(myArr);\nconsole.log(arrGenerator.next());\nconsole.log(arrGenerator.next());\nconsole.log(arrGenerator.next());\nconsole.log(arrGenerator.next());\n```\n\n打印结果不变。代码解释：`yield*` 的后面必须是一个可迭代对象，而且 createArrayGenerator()函数会返回一个可迭代对象 arrGenerator。\n\n### 将自定义类封装为可迭代对象\n\n我们在前面学习迭代器时，曾通过迭代器“将自定义类封装为可迭代对象”。那段代码，改用生成器 `yield*` 的方式也可以实现。代码举例：\n\n```js\n// 定义类\nclass Person {\n  constructor(name, arr) {\n    this.name = name;\n    this.arr = arr;\n  }\n\n  // 【关键代码】在定义生成器函数时，如果没有 function 这个单词的话，也可以直接在函数名的前面添加 * 符号。\n  *[Symbol.iterator]() {\n    // 【关键代码】一行代码，直接遍历 this.arr 这个可迭代对象，同时返回一个新的可迭代对象\n    yield* this.arr;\n  }\n}\n\nconst person1 = new Person('千古壹号', ['前端', '工程师']);\nconst person2 = new Person('许嵩', ['想象之中', '有何不可']);\n\n\n// Person的实例已经封装为可迭代对象了，可以通过 for ... of 进行遍历\nfor (const item of person1) {\n  console.log(item);\n}\n\nconsole.log('---');\n\n// 也可以通过 Symbol.iterator() 方法进行遍历\nconst personIterator = person2[Symbol.iterator]();\nconsole.log(personIterator.next());\nconsole.log(personIterator.next());\nconsole.log(personIterator.next());\n```\n\n打印结果：\n\n```\n前端\n工程师\n---\n{value: '想象之中', done: false}\n{value: '有何不可', done: false}\n{value: undefined, done: true}\n```\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)"
  },
  {
    "path": "07-JavaScript进阶/JavaScript开发积累.md",
    "content": "---\ntitle: 09-JavaScript开发积累\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n### 方法的注释\n\n方法写完之后（注意，一定要先写完整），我们在方法的前面输入`/**`，然后回车，会发现，注释的格式会自动补齐。\n\n比如：\n\n\n```javascript\n/**\n * 功能：给定元素查找他的第一个元素子节点，并返回\n * @param ele\n * @returns {Element|*|Node}\n */\nfunction getFirstNode(ele){\n    var node = ele.firstElementChild || ele.firstChild;\n    return node;\n}\n```\n\n### 断点调试\n\n（1）先让程序运行一遍。\n\n（2）f12，弹出代码调试工具\n\n（3）打断点：\n\n![](http://img.smyhvae.com/20180124_2035.png)\n\n然后刷新页面。\n\n（4）一步步调试，每点击一次，执行一步：\n\n![](http://img.smyhvae.com/20180124_2036.png)\n\n（5）监视变量：\n\n当然，也可以添加变量或者表达式到监视窗口。操作如下：\n\n![](http://img.smyhvae.com/20180124_2037.png)\n\n上图中，选择变量或表达式，然后右键add to watch.\n\n然后监视窗口：\n\n![](http://img.smyhvae.com/20180124_2038.png)\n\n\n### 2019-05-20-给数组、对象赋值\n\n**数组赋值的正确写法**：\n\n```javascript\nthis.todayList.splice(0, 0, ...dataList);\n```\n\n**对象赋值的正确写法**：\n\n```javascript\nObject.assign(obj2, obj1);\n```\n\n上方代码中，是将`obj1` 的值追加到`obj2`中。如果对象里的属性名相同，会被覆盖。\n\n\n### 2019-11-25-在新的窗口中打开url\n\n在原来的窗体中跳转到新页面：\n\n```javascript\nwindow.location.href=\"要跳转的新页面\";\n```\n\n在新窗体中打开新页面：\n\n```javascript\nwindow.open('你所要跳转的新页面');\n```\n\n\n### 2019-12-10-JavaScript 新特性：Optional Chaining（可选链式调用）语法\n\n以往写代码，我们一般都这么写：\n\n```javascript\nif (result && result.user && result.user.name && result.user.name.length) {\n    console.log('qianguyihao');\n}\n```\n\n有了 Optinal Chain 语法之后，就简洁很多了，可以这么写：\n\n\n```javascript\nif (result?.user?.name?.length) {\n    console.log('qianguyihao');\n}\n```\n\n\n\n参考链接：\n\n- 了解 JavaScript 新特性：Optional Chaining：<https://www.infoq.cn/article/2JDORgXrU6VmZ7jlyuFD>\n\n- 原文链接： https://v8.dev/features/optional-chaining\n\n\n\n### 2020-04-28-判断字符串的包含关系\n\n```js\nvar str = 'qiangu2';\nif (str == ('qiangu1' || 'qiangu2')) {\n    console.log('qianguyihao');\n}\n```\n\n注意，上面的代码，根本就不会走 console.log 语句，因为if里面的内容是false。\n\n如果我们要判断变量 `str` 是否在 `qiangu1、qiangu2`的合集里，我们应该这样写：\n\n```js\nvar str = 'qiangu2';\nif (str == 'qiangu1' || str == 'qiangu2') {\n    console.log('qianguyihao');\n}\n```\n\n\n\n"
  },
  {
    "path": "07-JavaScript进阶/Promise的一些题目.md",
    "content": "---\ntitle: 07-Promise的一些题目\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## Promise 的执行顺序\n\n### 题目 1\n\n代码举例：\n\n```js\nconst p = new Promise((resolve, reject) => {\n    console.log(1);\n});\n\nconsole.log(2);\n```\n\n打印结果：\n\n```\n1\n2\n```\n\n我们需要注意的是：Promise 里的代码整体，其实是同步任务，会立即执行。\n\n补充：上面的代码中，如果继续写`p.then()`，那么 `then()`里面是不会执行的。因为在定义 promise 的时候需要写 resolve，调用 promise 的时候才会执行 `then()`。\n\n基于此，我们再来看下面这段代码：\n\n```js\nconst p = new Promise((resolve, reject) => {\n    console.log(1);\n    resolve();\n});\n\nconsole.log(2);\n\np.then((res) => {\n    console.log(3);\n});\n```\n\n打印结果：\n\n```\n1\n2\n3\n```\n\n### 题目 2\n\n代码举例：\n\n```js\n// 封装 ajax 请求：传入回调函数 success 和 fail\nfunction ajax(url, success, fail) {\n    var xmlhttp = new XMLHttpRequest();\n    xmlhttp.open('GET', url);\n    xmlhttp.send();\n    xmlhttp.onreadystatechange = function () {\n        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {\n            success(xmlhttp.responseText);\n        } else {\n            fail(new Error('接口请求失败'));\n        }\n    };\n}\n\nnew Promise((resolve, reject) => {\n    ajax('a.json', (res) => {\n        console.log('a接口返回的内容：' + res);\n        resolve();\n    });\n})\n    .then((res) => {\n        console.log('a成功');\n        new Promise((resolve, reject) => {\n            ajax('b.json', (res) => {\n                console.log('b接口返回的内容：' + res);\n                resolve();\n            });\n        });\n    })\n    .then((res) => {\n        // 因为上面在b接口的时候，并没有 return，也就是没有返回值。那么，这里的链式操作then，其实是针对一个空的 promise 对象进行then操作\n        console.log('b成功');\n    });\n```\n\n打印结果：\n\n```\na接口返回的内容\na成功\nb成功\nb接口返回的内容\n```\n\n### 题目 3\n\n举例1：\n\n```js\nnew Promise((resolve, reject) => {\n    resolove();\n    console.log('promise1');  // 代码1\n}).then(res => {\n    console.log('promise  then)';  // 代码2：微任务\n})\n\nconsole.log('千古壹号');  // 代码3\n```\n\n打印结果：\n\n```\npromise1\n千古壹号\npromise  then\n```\n\n代码解释：代码1是同步代码，所以最先执行。代码2是**微任务**里面的代码，所以要先等同步任务（代码3）先执行完。\n\n当写完`resolove();`之后，就会立刻把 `.then()`里面的代码加入到微任务队列当中。\n\n"
  },
  {
    "path": "07-JavaScript进阶/call、apply、bind的区别.md",
    "content": "---\ntitle: 06-call、apply、bind的区别\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## call()和apply()\n\n### 介绍\n\n这两个方法都是函数对象的方法，需要通过函数对象来调用。\n\n当函数调用call()和apply()时，函数都会立即**执行**。\n\n- 都可以用来改变函数的this对象的指向。\n\n- 第一个参数都是this要指向的对象（函数执行时，this将指向这个对象），后续参数用来传实参。\n\n### 显式绑定this\n\nJS提供的绝大多数函数以及我们自己创建的所有函数，都可以使用call 和apply方法。\n\n它们的第一个参数是一个对象。因为你可以直接指定 this 绑定的对象，因此我们称之为显式绑定。\n\n例1：\n\n```javascript\n    function foo() {\n        console.log(this.a);\n    }\n\n    var obj = {\n        a: 2\n    };\n\n    // 将 this 指向 obj\n    foo.apply(obj); //打印结果：2\n```\n\n### 第一个参数的传递\n\n1、thisObj不传或者为null、undefined时，函数中的this会指向window对象（非严格模式）。\n\n2、传递一个别的函数名时，函数中的this将指向这个**函数的引用**。\n\n3、传递的值为数字、布尔值、字符串时，this会指向这些基本类型的包装对象Number、Boolean、String。\n\n4、传递一个对象时，函数中的this则指向传递的这个对象。\n\n\n### call()和apply()的区别\n\ncall()和apply()方法都可以将实参在对象之后依次传递，但是apply()方法需要将实参封装到一个**数组**中统一传递（即使只有实参只有一个，也要放到数组中）。\n\n比如针对下面这样的代码：\n\n```javascript\n    var persion1 = {\n        name: \"小王\",\n        gender: \"男\",\n        age: 24,\n        say: function (school, grade) {\n            alert(this.name + \" , \" + this.gender + \" ,今年\" + this.age + \" ,在\" + school + \"上\" + grade);\n        }\n    }\n    var person2 = {\n        name: \"小红\",\n        gender: \"女\",\n        age: 18\n    }\n```\n\n如果是通过call的参数进行传参，是这样的：\n\n```javascript\n\tpersion1.say.call(persion2, \"实验小学\", \"六年级\");\n```\n\n如果是通过apply的参数进行传参，是这样的：\n\n```javascript\n\tpersion1.say.apply(persion2, [\"实验小学\", \"六年级\"]);\n```\n\n看到区别了吗，call后面的实参与say方法中是一一对应的，而apply传实参时，要封装成一个数组，数组中的元素是和say方法中一一对应的，这就是两者最大的区别。\n\n### call()和apply()的作用\n\n- 改变this的指向\n\n- 实现继承。Father.call(this)\n\n## bind()\n\n- 都能改变this的指向\n\n- call()/apply()是**立即调用函数**\n\n- bind()是将函数返回，因此后面还需要加`()`才能调用。\n\nbind()传参的方式与call()相同。\n\n参考链接：\n\n- <https://www.jianshu.com/p/56a9c2d11adc>\n\n- <https://github.com/lin-xin/blog/issues/7>\n\n- <https://segmentfault.com/a/1190000007402815>\n\n- [JS中改变this指向的方法](http://www.xiaoxiaohan.com/js/38.html)\n\n\n\n\n\n"
  },
  {
    "path": "07-JavaScript进阶/this.md",
    "content": "---\ntitle: 08-this\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## this\n\n### this的作用\n\n- this可以帮我们简化很多代码。比如`xiaoming.name`、`xiaoming.age`可以直接写成`this.name`、`this.age`。\n\n- 特别是当我们不知道一个对象是什么，或者这个对象没有名字但又很想调用它的时候，就会使用到this对象。\n\n**举例：**\n\n- 遍历DOM对象，绑定click事件，调用当前点击的对象的id，而不是所有对象的id。\n\n代码：\n\n```html\n<!doctype html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\"\n    <title>Document</title>\n    <style>\n        div {\n            width: 100px;\n            height: 100px;\n            background-color: green;\n            margin: 10px;\n        }\n\n    </style>\n</head>\n<body>\n<script>\n    window.onload = function () {\n        var myDiv = document.getElementsByTagName('div');\n        for (var i = 0; i < myDiv.length; i++) {\n            myDiv[i].onclick = function () {\n                console.log(i);\n                console.log(this.id);\n            }\n        }\n\n    }\n\n</script>\n<section>\n    <div id=\"div0\"> div0</div>\n    <div id=\"div1\"> div1</div>\n    <div id=\"div2\"> div2</div>\n    <div id=\"div3\"> div3</div>\n    <div id=\"div4\"> div4</div>\n</section>\n\n\n</body>\n</html>\n\n```\n\n\n点击其中的任何一个元素后，`i`的打印结果是5。你可能会觉得很惊讶。我们来解释一下：\n\n当代码执行完毕后，i已经等于5了。因为一旦运行程序，for循环已经执行完了，此时i等于5。\n\n现在，我们尝试在 myDiv[i].onclick事件中写代码，如果打印：\n\n```\n\tconsole.log(i);  //打印结果为5\n```\n\n\n如果打印：\n\n```\n\tconsole.log(myDiv[i].id);\n```\n\n上方这行代码，打印会报错，因为i=5；如果想打印每个div的id，应该这样写：\n\n```\n\tconsole.log(this.id);\n```\n\n你看，this的作用，就体现出来了。\n\nPS：顺便提醒一下，上面的代码中，如果把`var i`改成`let i`，效果又完全不同了。参考链接：[let和var在for循环中的表现](http://blog.csdn.net/stopllL/article/details/64130664)\n\n### 全局作用域中的this\n\n当一段代码在浏览器中执行时，所有的全局变量和对象都是在window对象上定义的。换而言之，所有的全局变量和对象都属于window对象。\n\n\n## this的定律\n\nthis关键字永远指向函数（方法）运行时的**所有者**。\n\n### 函数赋值给变量时，this指向window\n\n\n比如：\n\n```\nvar foo1 = args.getInfo;\nfoo1();\n\nvar foo2 = function(){};\nfoo2();\n```\n\n\nthis都是指向window。\n\n### 以函数形式调用时，this永远都是window\n\n\n### 以方法的形式调用时，this是调用方法的对象\n\n\n## 解决闭包中的this指向问题\n\n\n内部函数是可以访问到外部函数的变量的。\n\n方式一：直接通过父函数的名字访问\n\n方式二：如果不知道父函数的名字，在父函数里加一句`_this = this`，此时`_this`相当于父函数的名字。\n\n### 当this遇到一些特殊的函数时\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "07-JavaScript进阶/作用域.md",
    "content": "---\ntitle: 05-作用域和闭包\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 前言\n\n面试问题：\n\n- 说一下对变量提升的理解\n\n- 说明this的几种不同的使用场景\n\n- 创建10个`<a>`标签，点击的时候弹出来对应的序号\n\n- 如何理解作用域\n\n- 实际开发中闭包的应用\n\n涉及到的知识点：\n\n- 执行上下文\n\n- this\n\n- 作用域\n\n- 作用域链\n\n- 闭包\n\n\n## 执行上下文\n\n执行上下文主要有两种情况：\n\n- 全局代码： 一段`<script>`标签里，有一个全局的执行上下文。所做的事情是：变量定义、函数声明\n\n- 函数代码：每个函数里有一个上下文。所做的事情是：变量定义、函数声明、this、arguments\n\nPS：注意“函数声明”和“函数表达式”的区别。\n\n\n### 全局执行上下文\n\n在执行全局代码前将window确定为全局执行上下文。\n\n\n（1）对全局数据进行预处理：（并没有赋值）\n\n- var定义的全局变量==>undefined, 添加为window的属性\n\n- function声明的全局函数==>赋值(fun), 添加为window的方法\n\n- this==>赋值(window)\n\n（2）开始执行全局代码\n\n\n\n![](http://img.smyhvae.com/20180311_1100.png)\n\n\n### 函数执行上下文\n\n在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)。\n\n\n（1）对局部数据进行预处理：\n\n- 形参变量==>赋值(实参)==>添加为执行上下文的属性\n\n- arguments==>赋值(实参列表), 添加为执行上下文的属性\n\n- var定义的局部变量==>undefined, 添加为执行上下文的属性\n\n- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法\n\n- this==>赋值(调用函数的对象)\n\n（2）开始执行函数体代码\n\n### 执行上下文栈\n\n- 1.在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象\n\n- 2.在全局执行上下文(window)确定后, 将其添加到栈中(压栈)\n\n- 3.在函数执行上下文创建后, 将其添加到栈中(压栈)\n\n- 4.在当前函数执行完后,将栈顶的对象移除(出栈)\n\n- 5.当所有的代码执行完后, 栈中只剩下window\n\n\n## this\n\nthis指的是，**调用函数的那个对象**。this永远指向函数运行时所在的对象。\n\n解析器在调用函数每次都会向函数内部传递进一个隐含的参数，这个隐含的参数就是this。\n\n根据函数的调用方式的不同，this会指向不同的对象：【重要】\n\n- 1.以函数的形式调用时，this永远都是window。比如`fun();`相当于`window.fun();`\n\n- 2.以方法的形式调用时，this是调用方法的那个对象\n\n- 3.以构造函数的形式调用时，this是新创建的那个对象\n\n- 4.使用call和apply调用时，this是指定的那个对象\n\n需要特别提醒的是：this的指向在函数定义时无法确认，只有函数执行时才能确定。\n\n![](http://img.smyhvae.com/20180311_1117.png)\n\nthis的几种场景：\n\n- 1、作为构造函数执行\n\n例如：\n\n```javascript\n    function Foo(name) {\n        //this = {};\n        this.name = name;\n        //return this;\n    }\n\n    var foo = new Foo();\n```\n\n- 2、作为对象的属性执行\n\n\n\n```javascript\n    var obj = {\n        name: 'A',\n        printName: function () {\n            console.log(this.name);\n        }\n    }\n\n    obj.printName();\n\n```\n\n\n- 3、作为普通函数执行\n\n\n```javascript\n    function fn() {\n        console.log(this); //this === window\n    }\n\n    fn();\n```\n\n\n\n- 4、call apply bind\n\n\n## 作用域\n\n作用域指一个变量的**作用范围**。它是静态的(相对于上下文对象), 在编写代码时就确定了。\n\n作用：隔离变量，不同作用域下同名变量不会有冲突。\n\n作用域的分类：\n\n\n- 全局作用域\n\n- 函数作用域\n\n- 没有块级作用域(ES6有了)\n\n\n\n\n\n\n```javascript\nif (true) {\n    var name = 'smyhvae';\n}\nconsole.log(name);\n```\n\n\n上方代码中，并不会报错，因为：虽然 name 是在块里面定义的，但是 name 是全局变量。\n\n\n\n### 全局作用域\n\n直接编写在script标签中的JS代码，都在全局作用域。\n\n在全局作用域中：\n\n- 在全局作用域中有一个全局对象window，它代表的是一个浏览器的窗口，它由浏览器创建我们可以直接使用。\n\n\n- 创建的变量都会作为window对象的属性保存。\n\n- 创建的函数都会作为window对象的方法保存。\n\n全局作用域中的变量都是全局变量，在页面的任意的部分都可以访问到。\n\n**变量的声明提前：**（变量提升）\n\n\n使用var关键字声明的变量（ 比如 `var a = 1`），**会在所有的代码执行之前被声明**（但是不会赋值），但是如果声明变量时不是用var关键字（比如直接写`a = 1`），则变量不会被声明提前。\n\n举例1：\n\n```javascript\n    console.log(a);\n    var a = 123;\n```\n\n\n打印结果：undefined\n\n举例2：\n\n```javascript\n    console.log(a);\n    a = 123;   //此时a相当于window.a\n```\n\n程序会报错：\n\n\n![](http://img.smyhvae.com/20180314_2136.png)\n\n\n**函数的声明提前：**\n\n- 使用`函数声明`的形式创建的函数`function foo(){}`，**会被声明提前**。\n\n也就是说，它会在所有的代码执行之前就被创建，所以我们可以在函数声明之前，调用函数。\n\n- 使用`函数表达式`创建的函数`var foo = function(){}`，**不会被声明提前**，所以不能在声明前调用。\n\n很好理解，因为此时foo被声明了，且为undefined，并没有给其赋值`function(){}`。\n\n所以说，下面的例子，会报错：\n\n\n![](http://img.smyhvae.com/20180314_2145.png)\n\n### 函数作用域\n\n**调用函数时创建函数作用域，函数执行完毕以后，函数作用域销毁。**\n\n每调用一次函数就会创建一个新的函数作用域，他们之间是互相独立的。\n\n在函数作用域中可以访问到全局作用域的变量，在全局作用域中无法访问到函数作用域的变量。\n\n\n在函数中要访问全局变量可以使用window对象。（比如说，全局作用域和函数作用域都定义了变量a，如果想访问全局变量，可以使用`window.a`）\n\n**提醒1：**\n\n在函数作用域也有声明提前的特性：\n\n- 使用var关键字声明的变量，是在函数作用域内有效，而且会在函数中所有的代码执行之前被声明\n\n- 函数声明也会在函数中所有的代码执行之前执行\n\n\n因此，在函数中，没有var声明的变量都会成为**全局变量**，而且并不会提前声明。\n\n举例1：\n\n```javascript\n        var a = 1;\n\n        function foo() {\n            console.log(a);\n            a = 2;     // 此处的a相当于window.a\n        }\n\n        foo();\n        console.log(a);   //打印结果是2\n\n```\n\n上方代码中，foo()的打印结果是`1`。如果去掉第一行代码，打印结果是`Uncaught ReferenceError: a is not defined`\n\n\n**提醒2：**定义形参就相当于在函数作用域中声明了变量。\n\n```\n\n        function fun6(e) {\n            console.log(e);\n        }\n\n        fun6();  //打印结果为 undefined\n        fun6(123);//打印结果为123\n```\n\n\n\n### 作用域与执行上下文的区别\n\n区别1：\n\n- 全局作用域之外，每个函数都会创建自己的作用域，作用域在函数定义时就已经确定了。而不是在函数调用时\n\n- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建\n\n- 函数执行上下文是在调用函数时, 函数体代码执行之前创建\n\n区别2：\n\n- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化\n\n- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放\n\n联系：\n\n  * 执行上下文(对象)是从属于所在的作用域\n\n  * 全局上下文环境==>全局作用域\n\n  * 函数上下文环境==>对应的函数使用域\n\n\n### 作用域链\n\n\n当在函数作用域操作一个变量时，它会先在自身作用域中寻找，如果有就直接使用（**就近原则**）。如果没有则向上一级作用域中寻找，直到找到全局作用域；如果全局作用域中依然没有找到，则会报错ReferenceError。\n\n\n外部函数定义的变量可以被内部函数所使用，反之则不行。\n\n\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <script>\n        //只要是函数就可以创造作用域\n        //函数中又可以再创建函数\n        //函数内部的作用域可以访问函数外部的作用域\n        //如果有多个函数嵌套，那么就会构成一个链式访问结构，这就是作用域链\n\n        //f1--->全局\n        function f1(){\n            //f2--->f1--->全局\n            function f2(){\n                //f3---->f2--->f1--->全局\n                function f3(){\n                }\n                //f4--->f2--->f1---->全局\n                function f4(){\n                }\n            }\n            //f5--->f1---->全局\n            function f5(){\n            }\n        }\n\n    </script>\n</head>\n<body>\n\n</body>\n</html>\n```\n\n理解：\n\n- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)\n\n- 查找变量时就是沿着作用域链来查找的\n\n查找一个变量的查找规则：\n\n```javascript\n    var a = 1\n\n    function fn1() {\n      var b = 2\n\n      function fn2() {\n        var c = 3\n        console.log(c)\n        console.log(b)\n        console.log(a)\n        console.log(d)\n      }\n      fn2()\n    }\n    fn1()\n```\n\n\n- 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2\n\n- 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3\n\n- 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "07-JavaScript进阶/创建对象和继承.md",
    "content": "---\ntitle: 04-创建对象和继承\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 创建对象的几种方式\n\n### 通过Object\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>01_Object构造函数模式</title>\n</head>\n<body>\n<!--\n方式一: Object构造函数模式\n  * 套路: 先创建空Object对象, 再动态添加属性/方法\n  * 适用场景: 起始时不确定对象内部数据\n  * 问题: 语句太多\n-->\n<script type=\"text/javascript\">\n    /*\n    一个人: name:\"Tom\", age: 12\n     */\n    // 先创建空Object对象\n    var p = new Object()\n    p = {} //此时内部数据是不确定的\n    // 再动态添加属性/方法\n    p.name = 'Tom'\n    p.age = 12\n    p.setName = function (name) {\n        this.name = name\n    }\n\n    //测试\n    console.log(p.name, p.age)\n    p.setName('Bob')\n    console.log(p.name, p.age)\n\n\n</script>\n</body>\n</html>\n\n```\n\n\n### 方式二：对象字面量\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>02_对象字面量</title>\n</head>\n<body>\n<!--\n方式二: 对象字面量模式\n  * 套路: 使用{}创建对象, 同时指定属性/方法\n  * 适用场景: 起始时对象内部数据是确定的\n  * 问题: 如果创建多个对象, 有重复代码\n-->\n<script type=\"text/javascript\">\n    var p = {\n        name: 'Tom',\n        age: 12,\n        setName: function (name) {\n            this.name = name\n        }\n    }\n\n    //测试\n    console.log(p.name, p.age)\n    p.setName('JACK')\n    console.log(p.name, p.age)\n\n    var p2 = {  //如果创建多个对象代码很重复\n        name: 'Bob',\n        age: 13,\n        setName: function (name) {\n            this.name = name\n        }\n    }\n\n</script>\n</body>\n</html>\n```\n\n\n\n### 方式三：工厂模式\n\n- 方式：通过工厂函数动态创建对象并返回。\n\n返回一个对象的函数，就是**工厂函数**。\n\n- 适用场景: 需要创建多个对象。\n\n- 问题: 对象没有一个具体的类型，都是Object类型。\n\n由于这个问题的存在，工厂模式用得不多。\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>03_工厂模式</title>\n</head>\n<body>\n<!--\n方式三: 工厂模式\n  * 套路: 通过工厂函数动态创建对象并返回\n  * 适用场景: 需要创建多个对象\n  * 问题: 对象没有一个具体的类型, 都是Object类型\n-->\n<script type=\"text/javascript\">\n    function createPerson(name, age) { //返回一个对象的函数===>工厂函数\n        var obj = {\n            name: name,\n            age: age,\n            setName: function (name) {\n                this.name = name\n            }\n        }\n\n        return obj\n    }\n\n    // 创建2个人\n    var p1 = createPerson('Tom', 12)\n    var p2 = createPerson('Bob', 13)\n\n    // p1/p2是Object类型\n\n    function createStudent(name, price) {\n        var obj = {\n            name: name,\n            price: price\n        }\n        return obj\n    }\n\n    var s = createStudent('张三', 12000)\n    // s也是Object\n\n\n</script>\n</body>\n</html>\n\n```\n\n\n\n### 方式四：自定义构造函数\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>04_自定义构造函数模式</title>\n</head>\n\n<body>\n<!--\n方式四: 自定义构造函数模式\n  * 套路: 自定义构造函数, 通过new创建对象\n  * 适用场景: 需要创建多个类型确定的对象\n  * 问题: 每个对象都有相同的数据, 浪费内存\n-->\n<script type=\"text/javascript\">\n    //定义类型\n    function Person(name, age) {\n        this.name = name\n        this.age = age\n        this.setName = function (name) {\n            this.name = name\n        }\n    }\n\n    var p1 = new Person('Tom', 12)\n    p1.setName('Jack')\n    console.log(p1.name, p1.age)\n    console.log(p1 instanceof Person)\n\n    function Student(name, price) {\n        this.name = name\n        this.price = price\n    }\n\n    var s = new Student('Bob', 13000)\n    console.log(s instanceof Student)\n\n    var p2 = new Person('JACK', 23)\n    console.log(p1, p2)\n\n\n</script>\n</body>\n</html>\n\n```\n\n\n方式四引入了继承。\n\n## 继承的几种方式\n\n\n### 通过构造函数继承\n\n在子类型构造函数中通用call()调用父类型构造函数\n\n\n### 原型链继承\n\n子类型的原型为父类型的一个实例对象\n\n\n\n### 组合继承\n\n\n\n"
  },
  {
    "path": "07-JavaScript进阶/数据的赋值.md",
    "content": "---\ntitle: 02-数据的赋值\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 对象赋值\n\n\n### 用 Object.assgin() 实现浅拷贝\n\n代码举例：\n\n```js\nconst obj1 = {\n    name: 'qianguyihao',\n    age: 28,\n    desc: 'hello world',\n};\n\nconst obj2 = {\n    name: '许嵩',\n    sex: '男',\n};\n\n// 【关键代码】浅拷贝：把 obj1 赋值给 obj2。这行代码的返回值也是 obj2\nObject.assign(obj2, obj1);\n\nconsole.log(JSON.stringify(obj2));\n```\n\n打印结果：\n\n```\n{\n    \"name\":\"qianguyihao\",\n    \"sex\":\"男\",\n    \"age\":28,\n    \"desc\":\"hello world\"\n}\n```\n\n注意，**上面这行代码在实际开发中，会经常遇到，一定要掌握**。它的作用是：将 obj1 的值追加到 obj2 中。如果两个对象里的属性名相同，则 obj2 中的值会被 obj1 中的值覆盖。\n\n\n## 数组赋值\n\n### 扩展运算符\n\n```js\narr2 = arr1;\n```\n\n上方代码中，其实是让 arr2 指向 arr1 的地址。也就是说，二者指向的是同一个内存地址。\n\n如果不想让 arr1 和 arr2 指向同一个内存地址，我们可以借助扩展运算符来做：\n\n```javascript\nlet arr2 = [...arr1]; //arr2 会开辟新的内存地址\n```\n\n\n\n\n参考链接：\n\n- [javaScript中浅拷贝和深拷贝的实现](https://github.com/wengjq/Blog/issues/3)\n\n\n"
  },
  {
    "path": "07-JavaScript进阶/数组的进阶操作.md",
    "content": "---\ntitle: 数组的进阶操作\n---\n\n## 删除数组的元素\n\n现在有这样一个需求：遍历数组的同时，删除数组中的所有元素。\n\n思路：我们可以想到的办法是使用数组的 splice() 方法，此外还有 JS 的 delete 关键字。\n\n\n需要注意的是：使用数组的 splice() 方法删除数组元素之后，数组的长度会发生变化；使用 delete 删除数组中的元素之后，**数组的长度不会发生变化**。\n\n下面来看看具体写法。\n\n### 使用数组的 splice() 方法\n\n\n写法1：（错误）\n\n```js\nlet arr = [1, 2, 3, 4, 5];\nfor (let i = 0; i < arr.length; i++) {\n    console.log(arr.length);\n    arr.splice(i, 1);\n}\nconsole.log(arr);\n```\n\n打印结果及解释：\n\n```bash\n数组长度的打印结果：5、4、3\narr最终的值：[2, 4]\n```\n\n写法1的错误在于：没有意识到，splice方法会使 arr 的长度不断变化。\n\n写法2：（错误）\n\n```js\nlet arr = [1, 2, 3, 4, 5];\nconst len = arr.length;\nfor (let i = 0; i < len; i++) {\n    console.log(arr.length);\n    arr.splice(i, 1);\n}\nconsole.log(arr);\n```\n\n打印结果：\n\n```bash\n数组长度的打印结果：5、4、3、2、2\narr最终的值：[2, 4]\n```\n\n上方代码，依然没有达到预期的结果。\n\n写法3：（正确写法，从末尾开始删除元素）\n\n```js\nlet arr = [1, 2, 3, 4, 5];\nfor (let i = arr.length - 1; i >= 0; i--) {\n    console.log(arr.length);\n    arr.splice(i, 1);\n}\nconsole.log(arr);\nconsole.log(arr.length);\n\n```\n打印结果及解释：\n\n```bash\n数组长度的打印结果：// 5、4、3、2、1\narr最终的值：[]\narr最终的长度：0\n```\n\n### 使用 delete 关键字\n\n```js\nlet arr = [1, 2, 3, 4, 5];\nfor (let i = 0; i < arr.length; i++) {\n    console.log(arr.length);\n    // 注意点: 通过 delete 删除数组中的元素, 数组的长度不会发生变化\n    delete arr[i];\n}\nconsole.log(JSON.stringify(arr));\n```\n\n打印结果及解释：\n\n```bash\n数组长度的打印结果：5、5、5、5、5\n最终的arr，是空数组，长度为5：[null, null, null, null ,null]\n```\n"
  },
  {
    "path": "07-JavaScript进阶/高阶函数.md",
    "content": "---\ntitle: 25-高阶函数\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 高阶函数\n\n### 高阶函数的概念\n\n当 函数 A 接收函数 B 作为**参数**，或者把函数 C 作为**返回值**输出时，我们称 函数 A 为高阶函数。\n\n通俗来说，高阶函数是 对其他函数进行操作 的函数。\n\n\n### 高阶函数举例1：把其他函数作为参数\n\n```js\nfunction fn1(a, b, callback) {\n    console.log(a + b);\n    // 执行完上面的 console.log() 语句之后，再执行下面这个 callback 函数。也就是说，这个 callback 函数是最后执行的。\n    callback && callback();\n}\n\nfn1(10, 20, function () {\n    console.log('我是最后执行的函数');\n});\n\n```\n\n\n打印结果：\n\n```\n30\n我是最后执行的函数\n```\n\n\n### 高阶函数举例2：把其他区函数作为返回值\n\n\n\n```js\nfunction fn1() {\n    let a = 20;\n\n    return function () {\n        console.log(a);\n    };\n}\n\nconst foo = fn1(); // 执行 fn1() 之后，会得到一个返回值。这个返回值是函数\nfoo();\n```\n\n\n上面的代码，产生了闭包现象。关于闭包，详见下一篇文章《JavaScript基础/闭包.md》。\n"
  },
  {
    "path": "08-前端基本功：CSS和DOM练习/01-CSS基础练习：JD首页的制作（顶部和底部）.md",
    "content": "---\ntitle: 01-CSS基础练习：JD首页的制作（顶部和底部）\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 前言\n\n京东是典型的电商类网站，学习这个网站的制作比较有价值。我们准备用WebStorm进行开发。\n\n京东首页的截图为：<http://img.smyhvae.com/20180119_1653.jpg>\n\n### 页面规划：新建一个空的工程\n\n我们首先新建一个空的工程：\n\n![](http://img.smyhvae.com/20180118_1733.png)\n\n\n### CSS初始化（基本样式）\n\n\n京东网站有一些基本样式，在各个页面中都要用到：（将这些基本样式copy到css.base里面去）\n\nbase.css中的公共的部分：\n\n```css\n@charset \"UTF-8\";\n/*css 初始化 */\nhtml, body, ul, li, ol, dl, dd, dt, p, h1, h2, h3, h4, h5, h6, form, fieldset, legend, img { margin:0; padding:0; }\nfieldset, img,input,button { border:none; padding:0;margin:0;outline-style:none; }  /*去掉边框、去掉轮廓（比如输入框外面的蓝边框）*/\n\n/*去掉列表前面的圆点*/\nul, ol {\n    list-style: none;\n}\ninput { padding-top:0; padding-bottom:0; font-family: \"SimSun\",\"宋体\";}  /*字体一般是指定这两个*/\nselect, input { vertical-align:middle;}\nselect, input, textarea { font-size:12px; margin:0; }\ntextarea { resize:none; } /*禁止文本输入框在右下角拖拽（因为拖动后会调整输入框大小）*/\nimg {border:0;\tvertical-align:middle; }  /* 去掉图片底侧默认的3像素空白缝隙*/\ntable { border-collapse:collapse; }\nbody {\n    font:12px/150% Arial,Verdana,\"\\5b8b\\4f53\";  /*\\5b8b\\4f53指的是宋体*/\n    color:#666;\n    background:#fff\n}\n\n/*start:清除浮动【推荐此方式进行清除浮动】。左浮动和右浮动都清除了，盒子刚好达到闭合的状态*/\n.clearfix:before, .clearfix:after {\n    content: \"\";\n    display: table;\n}\n\n.clearfix:after {\n    clear: both;\n}\n\n.clearfix {\n    *zoom: 1; /*IE/7/6*/\n}\n/*end：清除浮动*/\n\na {color:#666; text-decoration:none; }  /*去掉超链接的下划线*/\na:hover{color:#C81623;} /*鼠标悬停时的颜色*/\nh1,h2,h3,h4,h5,h6 {text-decoration:none;font-weight:normal;font-size:100%;} /*font-size:100% 的意思是：让它们和父亲一样大，避免在不同的浏览器中显示大小不一致*/\ns,i,em{font-style:normal;text-decoration:none;}  /*去掉i标签和em的斜体，取消s标签的删除线*/\n.col-red{color: #C81623!important;}\n\n/*公共类*/\n.w { /*版心（可视区）。需要专门提取出来 */\n    width: 1210px;\n    margin: 0 auto;\n}\n\n.fl {\n    float: left\n}\n\n.fr {\n    float: right\n}\n\n.al {\n    text-align: left\n}\n\n.ac {\n    text-align: center\n}\n\n.ar {\n    text-align: right\n}\n\n.hide {\n    display: none\n}\n```\n\n\n上方代码解释：\n\n（1）**清除浮动**的方式：\n\n```css\n.clearfix:before, .clearfix:after {\n    content: \"\";\n    display: table;\n}\n\n.clearfix:after {\n    clear: both;\n}\n\n.clearfix {\n    *zoom: 1; /*IE/7/6*/\n}\n```\n\n这是如今比较流行的清除浮动的方式。比如小米官网就是采用的这种。\n\n（2）其他属性：\n\n我们给`fieldset, img,input,button`等标签设置了`outline-style:none`，意思去掉轮廓（比如去掉输入框外面的蓝边框，去掉之后，蓝色没有了，但是黑色依然存在）。去掉的原因是：首先，轮廓不好看；其次，在google浏览器和在火狐浏览器上，渲染的效果不同。\n\nimg标签中，我们通过`vertical-align:middle`属性**去掉图片底侧默认的3像素空白缝隙**，还有一种方法可以达到效果，那就是`display: block`。\n\n\n给`h1,h2,h3,h4,h5,h6`设置**`font-size:100%`**是因为：h标签在每个浏览器中显示的大小不一致，设置此属性则表示，**让它们都和父亲一样大**。\n\n（3）一些小标记\n\n`s`是删除线，`i`和`em`是斜体。我们经常用它们做一些小装饰、小图标。\n\n### 引入css文件\n\nbase.css初始化之后，我们需要在html文件中引入它。引入外部样式表的方式如下：（`stylesheet`指样式表）\n\n```html\n    <link rel=\"stylesheet\" href=\"css/base.css\">\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180118_2002.png)\n\n\n注意，**base.css和index.css的书写顺序不能颠倒**，因为是按照书写顺序，从上往下进行加载的。\n\n\n### Favicon 小图标\n\nFavicon 图标指的是箭头处这个小图标：\n\n![](http://img.smyhvae.com/20180118_2013.png)\n\n官网链接<https://www.jx.com/favicon.ico>可以下载这个小图标。\n\n我们把`favicon.ico`图片放到工程文件的根目录，通过下面这种方式进行加载：\n\n```html\n    <link rel=\"shortcut icon\" href=\"favicon.ico\">\n```\n\n注意，`shortcut icon`是Favicon的专有名词，不能改成别的单词。\n\n代码位置：\n\n![](http://img.smyhvae.com/20180118_2020.png)\n\n\n## 顶部导航的制作\n\n我们先制作下面这个部分，它位于网站的最顶部：\n\n![](http://img.smyhvae.com/20180118_2040.png)\n\n顶部导航栏的html结构如下：（直接放在body标签下）\n\n```html\n    <!--顶部导航start-->\n    <div class=\"shortcut\">\n        <!--版心-->\n        <div class=\"w\">\n            <!--左浮动区域-->\n            <div class=\"fl\">\n                <div class=\"dt\"> 送至：北京\n                    <i><s>◇</s></i>\n                </div>\n            </div>\n            <!--右浮动区域-->\n            <div class=\"fr\">\n                <ul>\n                    <li>\n                        <a href=\"#\">你好，请登录</a> &nbsp;&nbsp;\n                        <a href=\"#\" class=\"col-red\">免费注册</a>\n                    </li>\n                    <li class=\"line\"></li>\n                    <li>我的订单</li>\n                    <li class=\"line\"></li>\n                    <li class=\"fore\">我的京东\n                        <i><s>◇</s></i>\n                    </li>\n                    <li class=\"line\"></li>\n                    <li>京东会员</li>\n                    <li class=\"line\"></li>\n                    <li>企业采购</li>\n                    <li class=\"line\"></li>\n                    <li class=\"fore tel-jd\">\n                        <em class=\"tel\"></em>   <!--小手机图标-->\n                        手机京东\n                        <i><s>◇</s></i>\n                    </li>\n                    <li class=\"line\"></li>\n                    <li class=\"fore\">\n                        关注京东\n                        <i><s>◇</s></i>\n                    </li>\n                    <li class=\"line\"></li>\n                    <li class=\"fore\">\n                        客户服务\n                        <i><s>◇</s></i>\n                    </li>\n                    <li class=\"line\"></li>\n                    <li class=\"fore\">\n                        网站导航\n                        <i><s>◇</s></i>\n                    </li>\n                </ul>\n            </div>\n        </div>\n\n    </div>\n    <!--顶部导航end-->\n\n```\n\n顶部导航栏需要加入的css样式如下：（放到base.css中）\n\n```css\n/*顶部导航start*/\n.shortcut {\n    height: 30px;\n    line-height: 30px;\n    background-color: #f1f1f1;\n}\n\n.dt,\n.shortcut .fore {\n    padding: 0 20px 0 10px;\n    position: relative;\n}\n\n.dt i,\n.fore i {\n    font: 400 15px/15px \"宋体\";\n    position: absolute;\n    top: 13px;\n    right: 3px;\n    height: 7px;\n    overflow: hidden;\n    width: 15px;\n}\n\n.dt s,\n.fore s {\n    position: absolute;\n    top: -8px;\n    left: 0;\n}\n\n.fr li {\n    float: left;\n    padding: 0 10px;\n}\n\n.fr .line {\n    width: 1px;\n    height: 12px;\n    background-color: #ddd;\n    margin-top: 9px;\n    padding: 0;\n}\n\n.shortcut .tel-jd {\n    padding: 0 20px 0 25px;\n}\n\n.tel {\n    position: absolute;\n    width: 15px;\n    height: 20px;\n    background: url(../images/sprite.png) no-repeat;\n    left: 5px;\n    top: 5px;\n}\n/*顶部导航end*/\n\n```\n\ncss代码解释：\n\n（1）整个的顶部导航栏是一个shortcut：\n\n```css\n.shortcut {\n    height: 30px;\n    line-height: 30px;\n    background-color: #f1f1f1;\n}\n```\n\n然后将左侧的文字设置为左浮动，右侧的文字设置为右浮动。\n\n（2）完成左侧部分的文字。\n\n（3）右侧部分文字的结构：ul中放了九个li，用来存放文字。代码快捷键是`ul>li*9`（符号`>`是包含的关系）。\n\n需要注意的是，“登录”和“注册”是同一个<li>里面的两个`<a>`。它们是一个整体，所以要放到同一个li里。\n\n\n(4)文字中间的间隔线：\n\n![](http://img.smyhvae.com/20180119_1503.png)\n\n上图所示，我们发现，每个li之间都有`1像素宽、12像素高的间隔线`，这个也是用li做的。\n\n（5）增加文字右侧的小三角。\n\n（6）在`手机京东`这个li中增加手机小图标，这里用到了css精灵图。\n\n\n京东顶部导航条的工程文件：[2018-01-19-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-19-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar)\n\n## 顶部banner图\n\n接下来我们只做顶部的banner图，效果如下：\n\n![](http://img.smyhvae.com/20180122_1020.png)\n\n也就是上图中“1元抢宝”的那个位置。\n\n涉及到的html代码如下：\n\n\n```html\n<!--京东的topbanner部分-->\n<div class=\"topbanner\">\n    <div class=\"w tb\">\n        <img src=\"images/topbanner.jpg\" alt=\"\"/>\n        <a href=\"javascript:;\" class=\"close-banner\"></a>\n    </div>\n</div>\n<!--京东的topbanner部分 end-->\n\n```\n\n在base.css中涉及到的css代码如下：\n\n```css\n/*topbanner start*/\n.topbanner {\n    background-color: #8A25C6;\n}\n.close-banner {\n    position: absolute;\n    right:0;\n    top:5px;\n    width: 19px;\n    height: 19px;\n    background: url(../images/close.png) no-repeat;\n}\n.close-banner:hover {\n    background-position:bottom;\n}\n.tp{\n    position: relative;\n}\n/*topbanner end*/\n\n```\n\n代码解释：\n\n重点是`close-banner`这个class，也就是右上角的那个`X`。这里用到了子绝父相，注意，设置相对定位的父亲是`tb`这个class，因为要考虑到网页缩放的情况。\n\n`.close-banner:hover`这个属性里，我们设置的背景图的定位是bottom，意思是，保证精灵图和父亲的底边贴齐，就不用使用像素的方式对精灵兔图进行定位了。\n\n\n## 搜索框\n\n搜索框的UI如下：\n\n\n![](http://img.smyhvae.com/20180122_1301.png)\n\n上图中，包含了四个部分：\n\n- 左侧的logo\n\n- 中间的搜索框\n\n- 右侧的购物车\n\n- 热搜文字（中间搜索框的下方）\n\n我们在WebStorm中输入`.search-logo+.search-input+.search-car+.search-moreA`，然后按tab键，就可以补齐代码：\n\n```html\n        <div class=\"search-logo\"></div>\n        <div class=\"search-input\"></div>\n        <div class=\"search-car\"></div>\n        <div class=\"search-moreA\"></div>\n```\n\n相关的html代码如下：\n\n```html\n    <!--search部分start-->\n    <div class=\"serach\">\n        <div class=\"w clearfix\">\n            <div class=\"search-logo\">\n                <a href=\"http://www.jx.com\" title=\"京西\" target=\"_blank\">京东官网</a>\n            </div>\n            <div class=\"search-input\">\n                <!--placeholder=\"运动相机\"-->\n                <input type=\"text\" value=\"运动相机\"/>\n                <button>搜索</button>\n            </div>\n            <div class=\"search-car\">\n                <a href=\"#\">我的购物车</a>\n                <i class=\"icon1\"></i>\n                <i class=\"icon2\">&gt;</i>\n                <i class=\"icon3\">8</i>\n            </div>\n            <div class=\"search-moreAlink\">\n                <a href=\"#\" class=\"col-red\">出境999</a>\n                <a href=\"#\">沸腾厨卫</a>\n                <a href=\"#\">249减100</a>\n                <a href=\"#\">手机节</a>\n                <a href=\"#\">每150减50</a>\n                <a href=\"#\">男靴</a>\n                <a href=\"#\">巧克力</a>\n                <a href=\"#\">cool1手机</a>\n                <a href=\"#\">男士卫衣</a>\n            </div>\n        </div>\n    </div>\n    <!--search部分end-->\n```\n\n相关的css代码如下：\n\n```css\n/*search部分start*/\n.search-logo {\n    float: left;\n    width: 362px;\n    height: 60px;\n    padding: 20px 0;\n}\n.search-logo a {\n    width: 270px;\n    height: 60px;\n    display: block;\n    text-indent: -9999px;\n    background: url(../images/logo.png) no-repeat;\n}\n.search-input {\n    float: left;\n    height: 36px;\n    padding-top: 25px;\n}\n.search-input input {\n    float: left;\n    width: 450px;\n    height: 32px;\n    padding-left: 4px;\n    font: 400 14px/32px \"microsoft yahei\";\n    color: rgb(153, 153, 153);\n    border: 2px solid #B61D1D;\n    border-right: 0;\n}\n.search-input button {\n    width: 82px;\n    height: 36px;\n    color: #fff;\n    float: left;\n    font: 400 16px/36px \"微软雅黑\";\n    background-color: #B61D1D;\n    cursor: pointer;\n    /*cursor: pointer;          变成小手*/\n    /*cursor: text;             变成光标*/\n    /*cursor: move;             变成四角箭头*/\n    /*cursor: default;          变成小白*/\n}\n.search-car {\n    float: right;\n    width: 96px;\n    height: 34px;\n    line-height: 34px;\n    padding-left: 43px;\n    position: relative;\n    margin: 25px 65px 0 0;\n    border: 1px solid #DFDFDF;\n    background-color: #F9F9F9;\n}\n.icon1 {\n    position: absolute;\n    top: 9px;\n    left: 18px;\n    width: 18px;\n    height: 16px;\n    background: url(../images/tel.png) no-repeat 0 -58px;\n}\n.icon2 {\n    position: absolute;\n    right: 10px;\n    color: #999;\n    /*font-family: \"SimSun\";*/\n    font: 13px/34px \"SimSun\";\n}\n.icon3 {\n    position: absolute;\n    top: -5px;\n    /*left: 0;*/\n    width: 16px;\n    height: 14px;\n    background-color: #C81623;\n    line-height: 14px;\n    text-align: center;\n    color: #fff;\n    border-radius: 7px 7px 7px 0;  /*画圆角矩形*/\n}\n.search-moreAlink {\n    float: left;\n    width: 530px;\n    height: 28px;\n    line-height: 28px;\n}\n.search-moreAlink a {\n    margin-right: 8px;\n}\n/*search部分end*/\n```\n\n对于这四个部分，我们依次来讲解。\n\n### 1、左侧的logo\n\n为了便于SEO，需要给图片这个超链接加上文字，然后设置文字的缩进为`text-indent: -9999px;`。\n\n### 2、搜索栏\n\n“搜索”按钮：当我们把鼠标放在“搜索”上的时候， 发现鼠标变成了小手，这里是用到了`cursor`属性。\n\n`cursor`有如下属性值：\n\n```css\n        cursor: pointer;          /*变成小手*/\n        cursor: text;             /*变成光标*/\n        cursor: move;             /*变成四角箭头*/\n        cursor: default;          /*变成默认的箭头*/\n```\n\n### 3、购物车\n\n购物车里包含了四个元素：一个文字，三个图标。\n\n为了让文字“我的购物车”这个`<a>`上下方向居中，我们给`<a>`标签的行高line-height为父亲的高度。\n\n另外，“我的购物车”这四个字并不是水平居中的，于是，我们可以给它一个左侧的padding，而不用给右侧padding。\n\n另外三个小图标可以用绝对定位来做。\n\n右上角的小图标（圆角矩形）：它的红色背景不是图片，而是用`border-radius`属性画的**圆角矩形**。\n\n圆角矩形`border-radius`有下面几种画法：\n\n```\n    border-radius: 宽/高一半;\n    border-radius: 50%;\n    border-radius: 0.3em;\n    border-radius: 左上角  右上角  右下角  左下角;\n```\n\n### 搜索框下方的热搜文字\n\n热搜文字的功能性并不强，仅仅使用几个超链接`<a>`标签即可（每个 a 之间用margin隔开）。不需要像别的导航栏那样，在ul里放li，在li里放a。\n\n注意，每个 a 之间是用margin隔开，不是用padding隔开；否则的话，鼠标点击中间的空白处也会出现跳转。\n\n顶部导航条+顶部banner+搜索框的工程文件：[2018-01-21-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-21-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar)\n\n## slogen：口号\n\n要求实现的效果如下：\n\n![](http://img.smyhvae.com/20180122_1630.gif)\n\n上图可以看到，这里要实现的效果是：无论浏览器如何移动，要保证第二个slogen的左侧位于浏览器的正中间。这是可以用到绝对定位的知识。\n\nhtml的代码如下：\n\n```html\n    <!--底部的口号 start-->\n    <div class=\"slogen\">\n        <span class=\"item slogen1\">\n            <img src=\"images/slogen1.png\" alt=\"\"/>\n        </span>\n        <span class=\"item slogen2\">\n            <img src=\"images/slogen2.png\" alt=\"\"/>\n        </span>\n        <span class=\"item slogen3\">\n            <img src=\"images/slogen3.png\" alt=\"\"/>\n        </span>\n        <span class=\"item slogen4\">\n            <img src=\"images/slogen4.png\" alt=\"\"/>\n        </span>\n    </div>\n    <!--底部的口号 end-->\n```\n\n`class=slogen`指的是整个slogen区域。item表示四个口号中相同的部分。\n\ncss的代码如下；\n\n```css\n/*底部的口号 start*/\n.slogen {\n    height: 54px;\n    padding: 20px 0;\n    background-color: #f5f5f5;\n    position: relative;\n    margin-bottom: 15px;\n}\n\n.item {\n    width: 302px;\n    position: absolute;\n    top: 20px;\n    left: 50%;\n}\n\n.slogen1 {\n    margin-left: -608px;\n}\n\n.slogen2 {\n    margin-left: -304px;\n}\n\n.slogen3 {\n    margin-left: 2px;\n}\n\n.slogen4 {\n    margin-left: 304px;\n}\n\n/*底部的口号 end*/\n```\n\n我们给item设置`left: 50%;`，确保每个item移到了父亲的正中间。然后每个item各自移动相应的距离即可实现。\n\n\n## 最下方的购物指南&区域覆盖\n\n需要实现的效果如下：\n\n![](http://img.smyhvae.com/20180122_1726.png)\n\n\n上图中，需要实现的内容包括两个部分：左侧的购物指南和右侧的区域覆盖（我把这两个部分用红线隔开了）。\n\n### 购物指南\n\n需要使用的布局如下：\n\n![](http://img.smyhvae.com/20170704_1727.png)\n\n这里的重点是要量出dt和dd的行高。\n\nhtml代码如下：\n\n```html\n\n    <!--购物指南等 start-->\n    <div class=\"w footer-shopping clearfix\">\n        <dl>\n            <dt>购物指南</dt>\n            <dd><a href=\"#\">购物流程</a></dd>\n            <dd><a href=\"#\">会员介绍</a></dd>\n            <dd><a href=\"#\">生活旅行/团购</a></dd>\n            <dd><a href=\"#\">常见问题</a></dd>\n            <dd><a href=\"#\">大家电</a></dd>\n            <dd><a href=\"#\">联系客服</a></dd>\n        </dl>\n        <dl>\n            <dt>配送方式</dt>\n            <dd><a href=\"#\">上门自提</a></dd>\n            <dd><a href=\"#\">211限时达</a></dd>\n            <dd><a href=\"#\">配送服务查询</a></dd>\n            <dd><a href=\"#\">配送费收取标准</a></dd>\n            <dd><a href=\"#\">海外配送</a></dd>\n        </dl>\n        <dl>\n            <dt>购物指南</dt>\n            <dd><a href=\"#\">购物流程</a></dd>\n            <dd><a href=\"#\">会员介绍</a></dd>\n            <dd><a href=\"#\">生活旅行/团购</a></dd>\n            <dd><a href=\"#\">常见问题</a></dd>\n            <dd><a href=\"#\">大家电</a></dd>\n            <dd><a href=\"#\">联系客服</a></dd>\n        </dl>\n        <dl>\n            <dt>购物指南</dt>\n            <dd><a href=\"#\">购物流程</a></dd>\n            <dd><a href=\"#\">会员介绍</a></dd>\n            <dd><a href=\"#\">生活旅行/团购</a></dd>\n            <dd><a href=\"#\">常见问题</a></dd>\n            <dd><a href=\"#\">大家电</a></dd>\n            <dd><a href=\"#\">联系客服</a></dd>\n        </dl>\n        <dl class=\"last-dl\">\n            <dt>购物指南</dt>\n            <dd><a href=\"#\">购物流程</a></dd>\n            <dd><a href=\"#\">会员介绍</a></dd>\n            <dd><a href=\"#\">生活旅行/团购</a></dd>\n            <dd><a href=\"#\">常见问题</a></dd>\n            <dd><a href=\"#\">大家电</a></dd>\n            <dd><a href=\"#\">联系客服</a></dd>\n        </dl>\n    </div>\n    <!--购物指南等 end-->\n```\n\n因为这片区域是浮动的，我们要通过`clearfix`这个class清除浮动，防止其被覆盖。\n\ncss代码如下：\n\n```css\n/*购物指南等 start*/\n.footer-shopping {\n    margin-top: 16px;  /*和上方保持距离*/\n}\n.footer-shopping dl{\n    float: left;\n    width: 200px;\n}\ndl.last-dl {\n    width: 100px;\n}\n.footer-shopping dt{\n    height: 34px;\n    font: 400 16px/34px \"microsoft yahei\";\n}\n.footer-shopping dd{\n    line-height: 20px;\n}\n/*购物指南等 end*/\n```\n\n### 区域覆盖\n\nhtml代码如下：\n\n```html\n                <div class=\"coverage\">\n                    <h3>京东自营覆盖区县</h3>\n                    <p>京东已向全国2654个区县提供自营配送服务，支持货到付款、POS机刷卡和售后上门服务。</p>\n                    <a href=\"#\">查看详情 ></a>\n                </div>\n```\n\ncss代码如下：\n\n```css\n/*覆盖区域 start*/\n.coverage {\n    float: left;\n    width: 186px;\n    height: 169px;\n    margin-right: 60px;\n    padding-left: 17px;\n    background: url(../images/china.png) no-repeat left bottom;\n}\n\n.coverage h3 {\n    height: 34px;\n    font: 400 16px/34px \"microsoft yahei\";\n}\n\n.coverage p {\n    padding-top: 8px;\n}\n.coverage a {\n    float: right;\n}\n/*覆盖区域 end*/\n```\n\n注意这里将精灵图设置为背景时，用到的定位属性是`left bottom`，意思是保证精灵图的左侧跟父亲左侧贴齐，下方和父亲下方贴齐。这样做的话，就不用通过像素来进行定位了。\n\n\n## 最底部\n\n最底部的效果如下：\n\n![](http://img.smyhvae.com/20180122_1909.png)\n\n如上图所示，它包含了三个部分。\n\n涉及到的html代码如下：\n\n```html\n\n    <!--最底部 start-->\n    <div class=\"w footer-bottom\">\n\n        <div class=\"footer-about\">\n            <a href=\"#\">关于我们</a>|\n            <a href=\"#\">联系我们</a>|\n            <a href=\"#\">联系客服</a>|\n            <a href=\"#\">商家入驻</a>|\n            <a href=\"#\">营销中心</a>|\n            <a href=\"#\">手机京东</a>|\n            <a href=\"#\">友情链接</a>|\n            <a href=\"#\">销售联盟</a>|\n            <a href=\"#\">京东社区</a>|\n            <a href=\"#\">京东公益</a>|\n            <a href=\"#\">English Site</a>|\n            <a href=\"#\">Contact Us</a>\n        </div>\n\n        <div class=\"footer-copyright\">\n            <img src=\"images/guohui.png\"/>京公网安备 11000002000088号  |  京ICP证070359号  |  互联网药品信息服务资格证编号(京)-经营性-2014-0008  |  新出发京零 字第大120007号<br>\n            互联网出版许可证编号新出网证(京)字150号  |  出版物经营许可证  |  网络文化经营许可证京网文[2014]2148-348号  |  违法和不良信息举报电话：4006561155<br>\n            Copyright © 2004 - 2016   JX.com 版权所有  |  消费者维权热线：4006067733<br>\n            京东旗下网站：京东钱包\n        </div>\n\n        <div class=\"footer-bottom-img\">\n            <a href=\"#\"><img src=\"images/img1.jpg\"/></a>\n            <a href=\"#\"><img src=\"images/img1.jpg\"/></a>\n            <a href=\"#\"><img src=\"images/img1.jpg\"/></a>\n            <a href=\"#\"><img src=\"images/img1.jpg\"/></a>\n            <a href=\"#\"><img src=\"images/img1.jpg\"/></a>\n            <a href=\"#\"><img src=\"images/img1.jpg\"/></a>\n        </div>\n    </div>\n    <!--最底部 end-->\n\n```\n\n涉及到的css代码如下：\n\n```css\n\n/*最底部start*/\n.footer-bottom {\n    margin-top: 20px;\n    text-align: center;  /*让文字在容器中水平方向居中*/\n    padding: 20px 0 30px;\n    border-top: 1px solid #E5E5E5;\n}\n\n.footer-bottom .footer-about a{\n    margin: 0 10px;\n}\n\n.footer-copyright {\n    padding: 10px 0;\n}\n\n.footer-bottom-img a {\n    margin: 0 5px;\n}\n/*最底部end*/\n```\n\n你去京东官网看看，发现最最底部的文字竟然是图片：\n\n![](http://img.smyhvae.com/20180122_1912.png)\n\n\n## 总结\n\n以上全部内容，最终实现的效果如下：\n\n![](http://img.smyhvae.com/20180122_1920.png)\n\n对应的工程文件：[2018-01-22-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-22-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar)\n\n"
  },
  {
    "path": "08-前端基本功：CSS和DOM练习/02-CSS基础练习：JD首页的制作（快捷导航部分）.md",
    "content": "---\ntitle: 02-CSS基础练习：JD首页的制作（快捷导航部分）\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n我们在上一篇文章中制作的网页最顶部的导航，是属于网页导航。\n\n本文中，Banner图上方的导航，叫做**快捷导航**（shortcut）。\n\n##快捷导航的骨架\n\n我们先制作快捷导航的骨架。如下图所示：\n\n![](http://img.smyhvae.com/20180123_1057.png)\n\n上图中，`shortcut-nav-menu-all`和`shortcut-nav-menu-one`都是属于`shortcut-nav-menu`部分，只不过，后者是将父亲撑破了。\n\n为了实现上图，对应的html代码如下：\n\n```html\n<!--shortcut-nav部分start-->\n<div class=\"shortcut-nav\">\n    <div class=\"w\">\n        <div class=\"shortcut-nav-menu\">\n            <div class=\"shortcut-nav-menu-all\">\n                <a href=\"#\">全部商品分类</a>\n            </div>\n            <div class=\"shortcut-nav-menu-one\" style=\"color: #fff\">\n                下一段再讲\n            </div>\n        </div>\n        <div class=\"shortcut-nav-items\">\n            <ul>\n                <li><a href=\"#\">服装城</a></li>\n                <li><a href=\"#\">美妆馆</a></li>\n                <li><a href=\"#\">京东超市</a></li>\n                <li><a href=\"#\">生鲜</a></li>\n                <li><a href=\"#\">全球购</a></li>\n                <li><a href=\"#\">闪购</a></li>\n                <li><a href=\"#\">团购</a></li>\n                <li><a href=\"#\">拍卖</a></li>\n                <li><a href=\"#\">金融</a></li>\n            </ul>\n        </div>\n        <div class=\"shortcut-nav-img\">\n            <a href=\"#\">\n                <img src=\"images/img2.jpg\"/>\n            </a>\n        </div>\n    </div>\n</div>\n<!--shortcut-nav部分end-->\n```\n\ncss代码如下：\n\n```css\n/*shortcut-nav部分start*/\n.shortcut-nav {\n    height: 44px;\n    border-bottom: 2px solid #B1191A;\n}\n.shortcut-nav-menu {     /*撑开和撑破是两回事：撑开说明盒子变成那么大，撑破盒子还是那么大，子盒子很大。子盒子shortcut-nav-menu-one把父亲撑破了*/\n    width: 210px;\n    height: 44px;        /*浮动的盒子相互影响，不过是否在同一个盒子中*/\n    float: left;\n    position: relative;\n    z-index: 1;       /*通过z-index属性将层级放到最高*/\n}\n.shortcut-nav-menu-all a {\n    display: block;\n    width: 190px;\n    height: 44px;\n    color: white;\n    padding: 0 10px;\n    background-color: #B1191A;\n    font: 400 15px/44px \"microsoft yahei\";\n}\n.shortcut-nav-menu-one {\n    height: 465px;\n    margin-top: 2px;\n    background-color: #C81623;\n    border-left: 1px solid #B1191A;\n    border-bottom: 1px solid #B1191A;\n    /*border-left: 1px solid #000;*/\n    /*border-bottom: 1px solid #000;*/\n}\n.shortcut-nav-items {\n    width: 730px;\n    height: 44px;\n    float: left;\n}\n.shortcut-nav-items li {\n    float: left;\n}\n.shortcut-nav-items a {\n    display: inline-block;\n    height: 44px;\n    padding: 0 20px;\n    color: #333;\n    font: 400 16px/44px \"microsoft yahei\";\n}\n.shortcut-nav-items a:hover {\n    color: #C81623;\n}\n.shortcut-nav-img {\n    width: 200px;\n    height: 44px;\n    float: right;\n    margin-top: -10px;\n    margin-right: 50px;\n    /*position: relative;*/\n    /*left: -50px;*/\n    /*top: -10px;*/\n}\n/*shortcut-nav部分end*/\n```\n\n\n## 具体的商品分类\n\n商品的具体分类即`shortcut-nav-menu-one`部分，我们来实现这部分的代码。要求实现的效果如下：\n\n![](http://img.smyhvae.com/20180123_1510.gif)\n\n我们在上面的代码中已经给`shortcut-nav-menu-one`设置了一些属性（例如给左边和下边增加一个像素的红色边框），在此基础之上，需要新增的代码如下：\n\nhtml代码：\n\n```html\n            <!--具体的商品分类start-->\n            <div class=\"shortcut-nav-menu-one\" style=\"color: #fff\">\n                <ul>\n                    <li>\n                        <a href=\"\">家用电器</a>\n                        <i>></i>\n                    </li>\n                    <li>\n                        <a href=\"\">手机</a>\n                        <span>、</span>\n                        <a href=\"\">数码</a>\n                        <i>></i>\n                    </li>\n                    <li>\n                        <a href=\"\">电脑</a>\n                        <span>、</span>\n                        <a href=\"\">办公</a>\n                        <i>></i>\n\n                    </li>\n                    <li>\n                        <a href=\"\">家居</a>\n                        <span>、</span>\n                        <a href=\"\">家具</a>\n                        <i>></i>\n                    </li>\n                    <li>\n                        <a href=\"\">男装</a>\n                        <span>、</span>\n                        <a href=\"\">女装</a>\n                        <i>></i>\n                    </li>\n                    <li></li>\n                    <li></li>\n                    <li></li>\n                    <li></li>\n                    <li></li>\n                </ul>\n            </div>\n            <!--具体的商品分类end-->\n```\n\ncss部分如下：\n\n```css\n/*具体的商品分类start*/\n.shortcut-nav-menu-one ul {\n    font: 400 14px/31px \"microsoft yahei\";   /*字体属性具有继承性，让儿子 a 具有此继承性*/\n}\n.shortcut-nav-menu-one li {\n    padding-left: 10px;\n    position: relative;\n}\n\n.shortcut-nav-menu-one a{\n    color: #fff;\n}\n\n.shortcut-nav-menu-one i {\n    right: 10px;\n    position: absolute;\n}\n\n.shortcut-nav-menu-one li:hover {\n    background-color: #fff;\n}\n\n.shortcut-nav-menu-one li:hover a,span,i{\n    color: #C81623;\n}\n/*具体的商品分类end*/\n```\n\n\n## 轮播图slider + 京东快报\n\n接下来，我们要实现下面这个部分：\n\n![](http://img.smyhvae.com/20180123_1527.png)\n\n组成部分包括：左侧的轮播图、右侧的京东快报 & 充话费 & 右下角的小海报。\n\n结构如下：\n\n```html\n<!--main部分start-->\n<div class=\"main\">\n    <div class=\"w\">\n        <div class=\"main-slider\">\n\n        </div>\n        <div class=\"main-news\">\n            <div class=\"main-news-top\">    <!--京东快报+充话费-->\n                <div class=\"main-news-top-faster\">   <!--京东快报-->\n\n                </div>\n                <div class=\"main-news-top-money\">    <!--充话费-->\n\n                </div>\n\n            </div>\n            <div class=\"main-news-bottom\">\n\n            </div>\n        </div>\n    </div>\n</div>\n<!--main部分end-->\n```\n\n我们依次来讲解。\n\n### 1、轮播图：main-slider\n\n首页的banner图是首页独有的，所以这部分的css代码要写在index.css里，不要写在base.css里。\n\nhtml代码如下：\n\n```html\n\n<!--main部分start-->\n<div class=\"main\">\n    <div class=\"w\">\n        <div class=\"main-slider\">\n            <a href=\"\">\n                <img src=\"images/slider.jpg\" alt=\"\">\n            </a>\n\n            <ul>            <!--指示点-->\n                <li>1</li>\n                <li>2</li>\n                <li>3</li>\n                <li>4</li>\n                <li>5</li>\n                <li>6</li>\n            </ul>\n            <a href=\"javascript:;\" class=\"arrow-left\">&lt;</a>   <!--左边的箭头-->\n            <a href=\"javascript:;\" class=\"arrow-right\">&gt;</a>  <!--右边的箭头-->\n        </div>\n    </div>\n</div>\n<!--main部分end-->\n\n```\n\n注意：超链接 a 标签中，`href=\"javascript:;`表示点击超链接时，什么都不做。\n\nCSS代码如下：\n\n```css\n.main-slider {\n    float: left;\n    margin: 12px 0 0 220px;\n    width: 730px;\n    height: 454px;\n    position: relative;\n}\n\n.main-slider ul {\n    position: absolute;\n    bottom: 10px;\n    left: 50%;\n    margin-left: -66px;\n}\n\n.main-slider ul li {\n    float: left;\n    width: 18px;\n    height: 18px;\n    color: #fff;\n    background-color: #3E3E3E;\n    border-radius: 50%;   /*圆角矩形*/\n    line-height: 18px;\n    text-align: center;  /*让 li 里面的文本水平方向居中*/\n    margin: 0 2px;\n    cursor: pointer;   /*鼠标悬停时变成小手*/\n}\n\n.main-slider .arrow-left {  /* 轮播图左侧的箭头*/\n    position: absolute;\n    top: 50%;\n    margin-top: -31px;\n    left: 0px;\n    width: 28px;\n    height: 62px;\n    background-color: rgba(0,0,0,0.3);\n    color: #fff;\n    font: 400 22px/62px \"sumsun\";\n    text-align: center;\n    border-radius: 10px 0 0 10px;\n}\n\n.main-slider .arrow-left:hover {\n    background-color: rgba(0,0,0,0.7);\n}\n\n.main-slider .arrow-right {  /*轮播图右侧的箭头*/\n    position: absolute;\n    top: 50%;\n    margin-top: -31px;\n    right: 0px;\n    width: 28px;\n    height: 62px;\n    background-color: pink;\n    background-color: rgba(0,0,0,0.3);\n    color: #fff;\n    font: 400 22px/62px \"sumsun\";\n    text-align: center;\n    border-radius: 10px 0 0 10px;\n}\n\n.main-slider .arrow-right:hover {\n    background-color: rgba(0,0,0,0.7);\n}\n\n```\n\n代码解释如下；\n\n（1）轮播图，我们采取的方式是：在超链接 a 里面放一个img标签。\n\n（2）指示点：在一个ul中放多个li。然后通过绝对定位的方式，让ul放在轮播图的正中间（水平方向）。最后详细设置每个指示点li的属性（比如，`text-align: center`属性可以让li里面的文字水平居中）。\n\n（3）左右两边的箭头：鼠标悬停时，颜色不同。我们通过`background-color: rgba(0,0,0,0.3)`设置背景的透明度。\n\n最终实现的效果如下：\n\n![](http://img.smyhvae.com/20180123_1951.png)\n\n### 京东快报\n\nhtml代码如下：\n\n```html\n                <div class=\"main-news-top-faster\">   <!--京东快报-->\n                    <div class=\"main-news-top-faster-title\">\n                        <h2>京东快报</h2>\n                        <a href=\"#\">更多 ></a>\n                    </div>\n                    <div class=\"main-news-top-faster-content\">\n                        <ul>\n                            <li><span>[特惠]</span>新闻1</li>\n                            <li><span>[公告]</span>新闻2</li>\n                            <li><span>[特惠]</span>新闻3</li>\n                            <li><span>[公告]</span>新闻4</li>\n                            <li><span>[特惠]</span>新闻5</li>\n                        </ul>\n\n                    </div>\n                </div>\n\n```\n\ncss代码如下；\n\n```css\n.main-news-top-faster {\n    height: 163px;\n    border-bottom: 1px dashed #E4E4E4;    /*虚线*/\n}\n\n\n.main-news-top-faster-title {\n    height: 32px;\n    line-height: 32px;\n    border-bottom: 1px dotted #E8E8E8;   /*点线*/\n    padding: 0 15px;\n\n}\n\n.main-news-top-faster-title h2{\n    float: left;\n    font: 400 16px/32px \"microsoft yahei\";\n}\n\n.main-news-top-faster-title a {\n    float: right;\n}\n\n.main-news-top-faster-content {\n    padding: 5px 0 0 15px;\n\n}\n\n.main-news-top-faster-content li {\n    line-height: 24px;\n}\n\n.main-news-top-faster-content span {\n    font-weight: 700;\n    margin-right: 5px;\n    color: #666;\n}\n\n.main-news-top-money ul {\n    width: 250px;\n}\n\n```\n\n\n### 3、充话费部分：12个单元格（重要）\n\n**（1）步骤一：**画表格\n\n充话费这部分，我们不用table标签来做，一般table标签一般是用来放文字的。这里因为有图片，所以我们用ul标签来做，在ul里放12个浮动的li。\n\n如果我们直接这样进行设置：\n\n```css\n.main-news-top-money ul {\n    width: 250px;\n}\n\n.main-news-top-money li {\n    width: 62px;\n    height: 70px;\n    border: 1px solid #E8E8E8;\n    float: left;\n\n}\n```\n\n会发现，效果不尽人意：\n\n![](http://img.smyhvae.com/20180123_2202.png)\n\n上图所示，我们发现，红框部分的12个li，并没有按照我们预期的那样进行排列。因为每个li有border。真实的li当中，它们的border是有重叠的。\n\n解决办法：\n\n> 父亲宽度不够时，为了让盒子浮动不掉下去，可以给子盒子之上父盒子之下再给一个盒子，让它的宽度略大于父亲的宽度即可。\n\n比如这里，**本身这个区域整体的宽度是250，我们就设置ul的宽度是260px即可（**满足的条件是：li的宽度*4 < **ul的宽度** < li的宽度*5）。\n\nul的宽度设置为260px之后发现，最右边和最下面的部分会多出来：\n\n![](http://img.smyhvae.com/20180123_2207.png)\n\n我们可以给`main-news-top-money`设置`overflow: hidden`，将多余的部分切掉（这是没有办法的事情）。\n\n于是乎，充话费这部分的代码如下：\n\nhtml部分：\n\n```html\n                <div class=\"main-news-top-money\">    <!--充话费-->\n                    <ul>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                        <li></li>\n                    </ul>\n\n                </div>\n```\n\ncss部分：\n\n```css\n.main-news-top-money ul {\n    width: 260px;   /*让宽度略大于整体的宽度250px*/\n}\n\n.main-news-top-money li {\n    width: 62px;\n    height: 70px;\n    border: 1px solid #E8E8E8;\n    float: left;\n    border-top: 0;  /* 将每个单元格的上边框去掉，因为跟单元格的下边框重合了。*/\n    margin-top: -1px;  /* 整体向上移动一个单位，因为边框重合了*/\n    margin-left: -1px ;/* 整体向左移动一个单位，因为边框重合了*/\n}\n```\n\n这样的话，表格就画好了：\n\n![](http://img.smyhvae.com/20180123_2240.png)\n\n**（2）步骤二：**往表格里填充内容\n\n接下来，我们往表格里填充内容。最终，充话费部分的代码如下：\n\nhtml部分：\n\n```html\n               <div class=\"main-news-top-money\">    <!--充话费-->\n                    <ul>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i class=\"main-news-top-money-icon2\"></i>   <!--单元格里的图片-->\n                                <span>机票</span>\n                            </a>\n\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                        <li>\n                            <a href=\"\">\n                                <i></i>   <!--单元格里的图片-->\n                                <span>话费</span>\n                            </a>\n                        </li>\n                    </ul>\n                </div>\n```\n\nindex.css部分：\n\n```css\n/*充话费部分start*/\n.main-news-top-money {\n    overflow: hidden;   /*将多余的部分切掉*/\n}\n\n.main-news-top-money ul {\n    width: 260px;   /*让宽度略大于整体的宽度250px*/\n}\n\n.main-news-top-money li {\n    width: 62px;\n    height: 70px;\n    border: 1px solid #E8E8E8;\n    float: left;\n    border-top: 0;  /* 将每个单元格的上边框去掉，因为跟单元格的下边框重合了。*/\n    margin-top: -1px;  /* 整体向上移动一个单位，因为边框重合了*/\n    margin-left: -1px ;/* 整体向左移动一个单位，因为边框重合了*/\n}\n\n.main-news-top-money li a {\n    display: block;\n    width: 62px;\n    height: 30px;\n    padding-top: 40px;\n    text-align: center;\n    line-height: 30px;\n    position: relative;\n}\n\n.main-news-top-money li a i {\n    width: 25px;\n    height: 25px;\n    position: absolute;\n    top: 13px;\n    left: 18px;\n    background: url(\"../images/fly.png\") right top;\n}\n\n.main-news-top-money .main-news-top-money-icon2 {\n    background: url(\"../images/fly.png\") right -25px;\n}\n/*充话费部分end*/\n```\n\n代码解释：\n\n- 单元格里的文字：我们可以给单元格里的文字设置padding-top，保证文字位于单元格的底部。\n\n- 单元格里的图片（精灵图）的位置：通过子绝父相的方式（子是图片`<i>`本身，相是每个单元格里的超链接文字`<a>`）。通过子绝父相的方式定位之后，发现精灵图都是一样的图标，目前的处理办法是：手动添加不同的class进行修改精灵图，以后等我们学习js了，就不用这么麻烦了。\n\n画出的表格如下：\n\n![](http://img.smyhvae.com/20180124_1121.png)\n\n\n## 今日推荐\n\n接下来，我们开始做下面这部分：\n\n![](http://img.smyhvae.com/20180124_1434.png)\n\n上图中的“今日推荐”，标签可以这样布局：ul > li > a > img\n\n为了防止这部分的内容跑到上面去，我们可以给上面的`class-main`部分清除浮动。\n\n“今日推荐”这部分的代码如下。\n\nhtml代码如下：\n\n```html\n<!--今日推荐start-->\n<div class=\"today\">\n    <div class=\"w clearfix\">\n        <div class=\"today-left fl\">\n            <a href=\"\"></a>\n        </div>\n\n        <div class=\"today-right\">\n            <ul>\n                <li><a href=\"\"><img src=\"images/today1.jpg\" alt=\"\"></a></li>\n                <li><a href=\"\"><img src=\"images/today2.jpg\" alt=\"\"></a></li>\n                <li><a href=\"\"><img src=\"images/today3.jpg\" alt=\"\"></a></li>\n                <li><a href=\"\"><img src=\"images/today4.jpg\" alt=\"\"></a></li>\n            </ul>\n            <a href=\"javascript:;\" class=\"arrow-left\">&lt;</a>   <!--左边的箭头-->\n            <a href=\"javascript:;\" class=\"arrow-right\">&gt;</a>  <!--右边的箭头-->\n\n        </div>\n\n    </div>\n</div>\n<!--今日推荐end-->\n```\n\nindex.css中的代码如下；\n\n```css\n/*今日推荐start*/\n.today {\n    padding: 10px 0 20px;\n}\n\n.today-left a{\n    display: block;\n    width: 210px;\n    height: 150px;\n    background: url(\"../images/today.jpg\");\n}\n\n.today-right {\n    float: right;\n    width: 1000px;\n    overflow: hidden;  /*隐藏掉右侧超出的几个像素*/\n    position: relative;\n}\n\n.today-right ul {\n    width: 410%;   /*这一点很有技巧*/\n}\n\n.today-right li{\n    float: left;\n    margin-right: 1px;\n\n}\n/*今日推荐end*/\n```\n\n\n## banner两侧的广告\n\n要实现的内容是下图中的箭头处：\n\n![](http://img.smyhvae.com/20180124_1615.png)\n\n\n注意这部分的div的位置，是放在`class=\"shortcut-nav\"`和`class=\"main\"`之间的。\n\n两侧的广告其实是一个放在 a 标签里的超大背景图，而且这个大图的宽度超过了版心。所以，超链接的宽度给`width: 100%`更合适。a 的高度设置为图片的高度即可。\n\n代码实现如下：\n\nhtml:\n\n```html\n<!--网页两侧的广告start-->\n<div class=\"banner-ad\">\n    <a href=\"http://www.baidu.com\"></a>\n</div>\n<!--网页两侧的广告end-->\n```\n\nindex.css:\n\n```css\n/*banner两侧的广告start*/\n.banner-ad {\n    position: relative;\n}\n\n.banner-ad a {\n    width: 100%;\n    height: 644px;\n    background: url(\"../images/ad.png\") no-repeat center top;\n    position: absolute;\n}\n/*banner两侧的广告end*/\n```\n\n上方代码中，我们不用给图片的父亲`banner-ad`设置高度。\n\n超链接a ：我们不知道超链接的宽度是多少，所以直接设置为`width: 100%`。注意它的背景图的摆放位置，`center`确保了背景图位于水平方向的正中间，`top`确保了背景图和父亲定边对齐。\n\n\n\n\n注意，上图中，两侧的广告实现之后发现，蓝框部分的两个位置(`main-news-top-faster`和`today-left`)点击时，发现跳转的是两侧广告的链接，因为它们的层级不够高。解决办法：给蓝框这两个部分加一个`position: relative`属性即可提高层级。\n\n## 总结\n\n上一篇文章和这一篇文章，加起来，最终实现的效果如下：\n\n![](http://img.smyhvae.com/20180124_1607.png)\n\n工程文件：[2018-01-23-前端基础练习-JD顶部导航.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-23-%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0-JD%E9%A1%B6%E9%83%A8%E5%AF%BC%E8%88%AA.rar)\n\n\n"
  },
  {
    "path": "08-前端基本功：CSS和DOM练习/03-DOM操作练习：基础练习.md",
    "content": "---\ntitle: 03-DOM操作练习：基础练习\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## DOM操作练习\n\n### 举例1：点击按钮时，显示和隐藏盒子。\n\n代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        button {\n            margin: 10px;\n        }\n\n        div {\n            width: 200px;\n            height: 200px;\n            background-color: pink;\n        }\n\n        .show {\n            display: block;\n        }\n\n        .hide {\n            display: none;\n        }\n    </style>\n\n</head>\n<body>\n<button id=\"btn\">隐藏</button>\n<div>\n    千古壹号\n</div>\n\n<script>\n    //需求：点击button,隐藏盒子。改变文字，在点击按钮，显示出来。\n    //步骤：\n    //1.获取事件源和相关元素\n    //2.绑定事件\n    //3.书写事件驱动程序\n\n    //1.获取事件源和相关元素\n    var btn = document.getElementById(\"btn\");\n    var div1 = document.getElementsByTagName(\"div\")[0];\n    //2.绑定事件\n    btn.onclick = function () {\n        //3.书写事件驱动程序\n        //判断btn的innerHTML属性值，如果为隐藏就隐藏盒子，并修改按钮内容为显示。\n        //反之，则显示，并修改按钮内容为隐藏\n        if (this.innerHTML === \"隐藏\") {\n            div1.className = \"hide\";\n            //修改按钮上的文字（innerHTML）\n            btn.innerHTML = \"显示\";\n        } else {\n            div1.className = \"show\";\n            //修改按钮上的文字（innerHTML）\n            btn.innerHTML = \"隐藏\";\n        }\n    }\n\n</script>\n\n</body>\n</html>\n\n```\n\n代码解释：\n\n当盒子是显示状态时，就设置为隐藏；当盒子是隐藏状态时，就设置为显示。注意这里的逻辑判断。\n\n另外，这里用到了`innerHTHL`属性，它可以修改按钮上显示的文字。\n\n代码最终显示的效果如下：\n\n20180127_1518.gif\n\n### 举例2：美女相册\n\n这里推荐一个网站：\n\n- 占位图片生成的在线网站：<https://placeholder.com/>\n\n好处是：素材做出来之前，先留出空位，方便以后换图。比如<http://via.placeholder.com/400x300>这个链接可以生成400*300的占位图片。\n\n需求：\n\n- （1）点击小图片，改变下面的大图片的src属性值，让其赋值为a链接中的href属性值。\n- （2）让p标签的innnerHTML属性值，变成a标签的title属性值。\n\n\n为了实现美女相册，代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style type=\"text/css\">\n        body {\n            font-family: \"Helvetica\", \"Arial\", serif;\n            color: #333;\n            margin: 1em 10%;\n        }\n\n        h1 {\n            color: #333;\n            background-color: transparent;\n        }\n\n        a {\n            color: #c60;\n            background-color: transparent;\n            font-weight: bold;\n            text-decoration: none;\n        }\n\n        ul {\n            padding: 0;\n        }\n\n        li {\n            float: left;\n            padding: 1em;\n            list-style: none;\n        }\n\n        #imagegallery {\n\n            list-style: none;\n        }\n\n        #imagegallery li {\n            margin: 0px 20px 20px 0px;\n            padding: 0px;\n            display: inline;\n        }\n\n        #imagegallery li a img {\n            border: 0;\n        }\n    </style>\n\n</head>\n<body>\n<h2>\n    美女画廊\n</h2>\n<a href=\"#\">注册</a>\n<ul id=\"imagegallery\">\n    <li>\n        <a href=\"image/1.jpg\" title=\"美女A\">\n            <img src=\"image/1-small.jpg\" width=\"100\" alt=\"美女1\"/>\n        </a>\n    </li>\n    <li>\n        <a href=\"image/2.jpg\" title=\"美女B\">\n            <img src=\"image/2-small.jpg\" width=\"100\" alt=\"美女2\"/>\n        </a>\n    </li>\n    <li>\n        <a href=\"image/3.jpg\" title=\"美女C\">\n            <img src=\"image/3-small.jpg\" width=\"100\" alt=\"美女3\"/>\n        </a>\n    </li>\n    <li>\n        <a href=\"image/4.jpg\" title=\"美女D\">\n            <img src=\"image/4-small.jpg\" width=\"100\" alt=\"美女4\"/>\n        </a>\n    </li>\n</ul>\n\n\n<div style=\"clear:both\"></div>\n\n<img id=\"image\" src=\"image/placeholder.png\" width=\"450px\"/>\n\n<p id=\"des\">选择一个图片</p>\n\n<script>\n    //需求：\n    //（1）点击小图片，改变下面的大图片的src属性值，让其赋值为a链接中的href属性值。\n    //（2）让p标签的innnerHTML属性值，变成a标签的title属性值。\n\n    //1.获取事件源和相关元素\n    //利用元素获取其下面的标签。\n    var ul = document.getElementById(\"imagegallery\");\n    var aArr = ul.getElementsByTagName(\"a\");     //获取ul中的超链接<a>\n\n    //    console.log(aArr[0]);\n    var img = document.getElementById(\"image\");\n    var des = document.getElementById(\"des\");\n    //2.绑定事件\n    //以前是一个一个绑定，但是现在是一个数组。我们用for循环绑定\n    for (var i = 0; i < aArr.length; i++) {\n        aArr[i].onclick = function () {\n            //3.【核心代码】书写事件驱动程序：修改属性值\n            img.src = this.href;  //this指的是函数调用者，和i并无关系，所以不会出错。\n//            img.src = aArr[i].href;   注意，上面这一行代码不要写成这样\n            des.innerHTML = this.title;\n            return false;    //return false表示：阻止继续执行下面的代码。\n        }\n    }\n\n</script>\n</body>\n</html>\n```\n\n代码解释：\n\n（1）获取事件源：我们通过`ul.getElementsByTagName(\"a\")`来获取ul里面的a元素。\n\n（2）绑定事件：因为要绑定一个数组，所以这里用for循环来绑定\n\n（3）【重要】书写事件驱动程序：这里是用`img.src = this.href`，而不是用`img.src = aArr[i].href`。因为this指的是函数的调用者。如果写成后者，等i变成了4，就会一直是4。显然不能达到效果。\n\n（4）代码的最后一行：`return false`表示：阻止继续执行下面的代码。\n\n实现的效果如下：\n\n20180127_1630.gif\n\n工程文件：[2018-01-27-美女相册demo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-01-27-%E7%BE%8E%E5%A5%B3%E7%9B%B8%E5%86%8Cdemo.rar)\n\n### 举例3：鼠标悬停时，显示二维码大图\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .code {\n            width: 50px;\n            height: 50px;\n        }\n\n        .code a {\n            display: block;\n            width: 50px;\n            height: 50px;\n            background: url(http://img.smyhvae.com/20180127_QRcode_small.png) no-repeat -159px -51px;\n            position: relative;\n\n        }\n\n        .code-big {\n            position: absolute;\n            top: 10px;\n            left: 80px;\n        }\n\n        .hide {\n            display: none;\n        }\n\n        .show {\n            display: block;\n        }\n\n\n    </style>\n\n    <script>\n        window.onload = function () {\n            //需求：鼠标放到a链接上，显示二维码（添加show类名，去掉hide类名）\n            //      鼠标移开a链接，隐藏二维码（添加hide类名，去掉show类名）\n\n\n            //1.获取事件源和相关元素\n            var a = document.getElementsByTagName(\"a\")[0];\n            var div = document.getElementsByClassName(\"code-big\")[0];\n            //2.绑定事件\n            a.onmouseover = fn1;   //鼠标悬停时\n            a.onmouseout = fn2;     //鼠标离开时\n\n            //定义方法\n            function fn1() {\n                //3.书写事件驱动程序\n                div.className = \"code-big show\";\n                //div.className = div.className.replace(\"hide\", \"show\");\n\n            }\n\n            function fn2() {\n                div.className = \"code-big hide\";\n                //了解,字符串操作，把字符串中的hide替换成show。\n                // div.className = div.className.replace(\"show\",\"hide\");\n            }\n        }\n    </script>\n</head>\n<body>\n\n<div class=\"code\">\n    <a href=\"#\"></a>\n    <img src=\"http://img.smyhvae.com/2016040102.jpg\" alt=\"\" class=\"code-big hide\"/>\n</div>\n\n</body>\n</html>\n\n```\n\n\n实现效果：\n\n20180127_1800.gif\n\n\n## 表单元素的属性\n\n表单元素的属性包括：type、value、checked、selected、disabled等。\n\n\n### 举例1：禁用文本框/解禁文本框\n\n\n```html\n<body>\n\n账号: <input type=\"text\" value=\"千古壹号...\"/><button>禁用</button><button>解禁</button><br><br>\n密码: <input type=\"password\" value=\"aaabbbccc\"/>\n\n<script>\n\n    var inp = document.getElementsByTagName(\"input\")[0];\n    var btn1 = document.getElementsByTagName(\"button\")[0];\n    var btn2 = document.getElementsByTagName(\"button\")[1];\n\n    btn1.onclick = function () {\n        inp.disabled = \"no\";   //禁用文本框。属性值里随便写什么字符串都行，但不能为空。\n    }\n    btn2.onclick = function () {\n        inp.disabled = false;   //解禁文本框。让disabled属性消失即可。\n//            inp.removeAttribute(\"disabled\");\n    }\n</script>\n</body>\n\n```\n\n当文本框被禁用之后，文本框只读，不能编辑，光标点不进去。\n\n\n上方代码可以看到，**禁用文本框**的代码是：\n\n```javascript\n\tinp.disabled = \"no\";   //让disabled属性出现，即可禁用\n```\n\n我们的目的时让`disabled`这个属性出现，即可禁用。所以，属性值里可以写数字，可以写任意一个字符串，但不能写0，不能写false，不能为空。一般我们写no。\n\n\n\n**解禁文本框**的代码是：\n\n\n```javascript\n\tinp.disabled = false;      // 方法1：让disabled属性消失，即可解禁。\n\tinp.removeAttribute(\"disabled\");  //方法2：推荐\n\n```\n\n\n我们的目的是删除`disabled`属性，即可解禁。属性值可以是false，可以是0。但我们一般采用方式2进行解禁。\n\n实现效果：\n\n\n\n\n\n### 举例2：文本框获取焦点/失去焦点\n\n细心的读者会发现，京东和淘宝的搜索框，获取焦点时，提示文字的体验是不同的。\n\n京东：\n\n20180127_2000.gif\n\n淘宝：\n\n20180127_2005.gif\n\n其实，**淘宝的提示文字，是用一个绝对定位的单独的标签来做的**。\n\n京东是判断输入框是否获取焦点；淘宝是判断输入框内是否有用户输入的文字。\n\n\n我们现在来实现一下。代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        input {\n            width: 300px;\n            height: 36px;\n            padding-left: 5px;\n            color: #ccc;\n        }\n\n        label {\n            position: absolute;\n            top: 82px;\n            left: 56px;\n            font-size: 12px;\n            color: #ccc;\n            cursor: text;\n        }\n\n        .hide {\n            display: none;\n        }\n\n        .show {\n            display: block;\n        }\n    </style>\n</head>\n<body>\n京东: <input id=\"inp1\" type=\"text\" value=\"微单相机\"/><br><br>\n淘宝: <label for=\"inp2\">电动牙刷</label><input id=\"inp2\" type=\"text\"/><br><br>\nplaceholder: <input type=\"text\" placeholder=\"我是placeholder\"/>\n\n<script>\n    //需求：京东的input按钮获取焦点后，立刻删除内容。失去后光标显示文字。\n\n    var inp1 = document.getElementById(\"inp1\");\n\n    inp1.onfocus = function () {\n        //判断，如果input里面的内容是“微单相机”，那么把值赋值为“”；\n        if (this.value === \"微单相机\") {\n            inp1.value = \"\";\n            inp1.style.color = \"#000\";\n\n        }\n    }\n    //失去焦点事件\n    inp1.onblur = function () {\n        //判断：如果input内容为空，那么把内容赋值为微单相机。\n        if (this.value === \"\") {\n            inp1.value = \"微单相机\";\n            inp1.style.color = \"#ccc\";\n\n        }\n    }\n\n\n    //需求：在input中输入文字，label标签隐藏；当里面的文字变成空字符串，label显示。\n\n    var inp2 = document.getElementById(\"inp2\");\n    var lab = document.getElementsByTagName(\"label\")[0];\n\n    //2.绑定事件(输入文字、和删除文字时，都会触动这个事件)\n    inp2.oninput = function () {\n        //判断input中的值是否为空，如果为空，那么label显示，否则隐藏。\n        if (this.value === \"\") {\n            lab.className = \"show\";\n        } else {\n            lab.className = \"hide\";\n        }\n    }\n</script>\n</body>\n</html>\n```\n\n\n实现效果如下：\n\n20180127_2010.gif\n\n如上方所示，我们还可以用placeholder来做，但是IE678并不支持，所以不建议使用。\n\n\n### 举例3：用户注册信息错误时，输入框失去焦点后，高亮显示。\n\n代码实现：\n\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        .wrong {\n            border: 2px solid red;\n        }\n        .right {\n            border: 2px solid #91B81D;\n        }\n    </style>\n</head>\n<body>\n\n账号：<input type=\"text\" onblur=\"fn(this)\"/><br><br>\n密码：<input type=\"password\" onblur=\"fn(this)\"/>\n\n<script>\n    //需求：失去焦点的时候判断input按钮中的值，如果账号或密码在6-12个字符之间通过，否则报错。\n\n    function fn(aaa){\n        //html中的input标签行内调用function的时候,是先通过window调用的function，所以打印this等于打印window\n//            console.log(this)\n        //只有传递的this才指的是标签本身。\n//            console.log(aaa)\n//            console.log(this.value)\n        if(aaa.value.length < 6 || aaa.value.length>12){\n            aaa.className = \"wrong\";\n        }else{\n            aaa.className = \"right\";\n        }\n    }\n</script>\n</body>\n</html>\n```\n\n代码解释：这次我们是在标签内调用function的，此时是先通过window调用的function。所以行内调用的时候要带this。\n\n\n实现效果：\n\n20180127_2035.gif\n\n\n### 举例4：全选和反选\n\n\n对应的代码如下：\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n        }\n\n        .my-table {\n            width: 300px;\n            margin: 100px auto 0;\n        }\n\n        table {\n            border-collapse: collapse;\n            border-spacing: 0;\n            border: 1px solid #c0c0c0;\n            width: 300px;\n        }\n\n        th,\n        td {\n            border: 1px solid #d0d0d0;\n            color: #404060;\n            padding: 10px;\n        }\n\n        th {\n            background-color: #09c;\n            font: bold 16px \"微软雅黑\";\n            color: #fff;\n        }\n\n        td {\n            font: 14px \"微软雅黑\";\n        }\n\n        tbody tr {\n            background-color: #f0f0f0;\n        }\n\n        tbody tr:hover {\n            cursor: pointer;\n            background-color: #fafafa;\n        }\n    </style>\n\n    <script>\n\n        window.onload = function () {\n            //需求1：点击上面的的input，下面全选或者反选。\n            //思路：获取了上面的input按钮，只需要判断，checked属性是true还是false，如果是true，下面的全部变成true；false同理。\n\n            var topInp = document.getElementById(\"title\");\n\n            var tbody = document.getElementById(\"content\");\n            var botInpArr = tbody.getElementsByTagName(\"input\");\n\n            //绑定事件\n            topInp.onclick = function () {\n                //费劲版\n//                for(var i=0;i<botInpArr.length;i++){\n//                    if(topInp.checked === true){\n//                        botInpArr[i].checked = true;\n//                    }else{\n//                        botInpArr[i].checked = false;\n//                    }\n//                }\n\n                //优化版（被点击的input按钮的checked属性值，应该直接作为下面的所有的input按钮的checked属性值）\n                for(var i=0;i<botInpArr.length;i++){\n                    botInpArr[i].checked = this.checked;\n                }\n            }\n\n            //需求2：点击下面的的input，如果下面的全部选定了，上面的全选，否则相反。\n            //思路：为下面的每一个input绑定事件，每点击一次都判断所有的按钮\n            // checked属性值是否全部都是true，如果有一个是false，\n            // 那么上面的input的checked属性也是false;都是true，topInp的checked就是true；\n\n\n            for(var i=0;i<botInpArr.length;i++){\n                botInpArr[i].onclick = function () {  //每一个input都要绑定事件\n                    //开闭原则（用开关来控制）\n                    var bool = true;\n                    //检测每一个input的checked属性值。\n                    for(var j=0;j<botInpArr.length;j++){\n                        if(botInpArr[j].checked === false){\n                            bool = false;\n                        }\n                    }\n                    topInp.checked = bool;\n                }\n            }\n        }\n\n    </script>\n\n</head>\n<body>\n<div class=\"my-table\">\n    <table>\n        <thead>\n        <tr>\n            <th>\n                <input type=\"checkbox\" id=\"title\" />\n            </th>\n            <th>菜名</th>\n            <th>饭店</th>\n        </tr>\n        </thead>\n        <tbody id=\"content\">\n        <tr>\n            <td>\n                <input type=\"checkbox\" />\n            </td>\n            <td>菜品1</td>\n            <td>木屋烧烤</td>\n        </tr>\n        <tr>\n            <td>\n                <input type=\"checkbox\" />\n            </td>\n            <td>菜品2</td>\n            <td>蒸菜馆</td>\n        </tr>\n        <tr>\n            <td>\n                <input type=\"checkbox\" />\n            </td>\n            <td>菜品3</td>\n            <td>海底捞火锅</td>\n        </tr>\n        <tr>\n            <td>\n                <input type=\"checkbox\" />\n            </td>\n            <td>菜品4</td>\n            <td>面点王</td>\n        </tr>\n        </tbody>\n    </table>\n</div>\n\n</body>\n</html>\n```\n\n注意代码中的注释，需求2是比较难的地方，这里用到了两次for循环。第一次for循环是因为，要给每个input都要进行绑定事件。\n\n实现的效果如下：\n\n20180127_2320.gif\n\n\n\n\n```javascript\n\n```\n"
  },
  {
    "path": "08-前端基本功：CSS和DOM练习/04-DOM操作练习：Tab栏切换（通过className设置样式）.md",
    "content": "---\ntitle: 04-DOM操作练习：Tab栏切换（通过className设置样式）\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n京东网页上，可以看到下面这种tab栏的切换：\n\n![](http://img.smyhvae.com/20180128_1750.gif)\n\n我们把模型抽象出来，实现一下。\n\n## 举例引入：鼠标悬停时，current元素的背景变色\n\n> 本段我们先举一个例子，因为这里用到了**排他思想**（先干掉 all，然后保留我一个）。对于理解tab切换，很有帮助。\n\n完整的代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        button {\n            margin: 10px;\n            width: 100px;\n            height: 40px;\n            cursor: pointer;\n        }\n        .current {\n            background-color: red;\n        }\n    </style>\n</head>\n<body>\n<button>按钮1</button>\n<button>按钮2</button>\n<button>按钮3</button>\n<button>按钮4</button>\n<button>按钮5</button>\n\n<script>\n    //需求：鼠标放到哪个button上，改button变成黄色背景（添加类）\n\n\n    var btnArr = document.getElementsByTagName(\"button\");\n    //绑定事件\n    for(var i=0;i<btnArr.length;i++){   //要为每一个按钮绑定事件，所以用到了for循环\n        btnArr[i].onmouseover = function () {\n            //【重要】排他思想：先把所有按钮的className设置为空，然后把我（this）这个按钮的className设置为current\n            //排他思想和for循环连用\n            for(var j=0;j<btnArr.length;j++){\n                btnArr[j].className = \"\";\n            }\n            this.className = \"current\";  //【重要】核心代码\n        }\n    }\n\n    //鼠标离开current时，还原背景色\n    for(var i=0;i<btnArr.length;i++){   //要为每一个按钮绑定事件，所以用到了for循环\n        btnArr[i].onmouseout = function () { //鼠标离开任何一个按钮时，就把按钮的背景色还原\n            this.className = \"\";\n        }\n    }\n\n</script>\n\n</body>\n</html>\n```\n\n代码解释：\n\n鼠标悬停时，current栏变色，这里用到了排他思想：先把所有按钮的className设置为空，然后把我(this)这个按钮的className设置为current，就可以达到变色的效果。核心代码是：\n\n\n```javascript\n            //排他思想：先把所有按钮的className设置为空，然后把我（this）这个按钮的className设置为current\n            //排他思想和for循环连用\n            for(var j=0;j<btnArr.length;j++){\n                btnArr[j].className = \"\";\n            }\n            this.className = \"current\";\n```\n\n实现的效果如下：\n\n\n![](http://img.smyhvae.com/20180128_1740.gif)\n\n## tab切换：初步的代码\n\n代码如下：\n\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n        }\n        .box {\n            width: 500px;\n            height: 200px;\n            border: 1px solid #ccc;\n            margin: 50px auto;\n            overflow: hidden;\n        }\n        ul {\n            width: 600px;\n            height: 40px;\n            margin-left: -1px;\n            list-style: none;\n        }\n        li {\n            float: left;\n            width: 101px;\n            height: 40px;\n            text-align: center;\n            font: 600 18px/40px \"simsun\";\n            background-color: pink;\n            cursor: pointer;\n        }\n        span {\n            display: none;\n            width: 500px;\n            height: 160px;\n            background-color: yellow;\n            text-align: center;\n            font: 700 100px/160px \"simsun\";\n        }\n        .show {\n            display: block;\n        }\n        .current {\n            background-color: yellow;\n        }\n    </style>\n\n    <script>\n        window.onload = function () {\n            //需求：鼠标放到上面的li上，li本身变色(添加类)，对应的span也显示出来(添加类);\n            //思路：1.点亮上面的盒子。   2.利用索引值显示下面的盒子。\n\n            var liArr = document.getElementsByTagName(\"li\");\n            var spanArr = document.getElementsByTagName(\"span\");\n\n            for(var i=0;i<liArr.length;i++){\n                //绑定索引值（新增一个自定义属性：index属性）\n                liArr[i].index = i;\n                liArr[i].onmouseover = function () {\n\n                    //1.点亮上面的盒子。   2.利用索引值显示下面的盒子。(排他思想)\n                    for(var j=0;j<liArr.length;j++){\n                        liArr[j].className = \"\";\n                        spanArr[j].className = \"\";\n                    }\n                    this.className = \"current\";\n                    spanArr[this.index].className = \"show\"; //【重要代码】\n                }\n            }\n        }\n    </script>\n</head>\n<body>\n\n<div class=\"box\">\n    <ul>\n        <li class=\"current\">鞋子</li>\n        <li>袜子</li>\n        <li>帽子</li>\n        <li>裤子</li>\n        <li>裙子</li>\n    </ul>\n    <span class=\"show\">鞋子</span>\n    <span>袜子</span>\n    <span>帽子</span>\n    <span>裤子</span>\n    <span>裙子</span>\n</div>\n\n</body>\n</html>\n```\n\n实现效果如下：\n\n![](http://img.smyhvae.com/20180128_1610.gif)\n\n上方代码的核心部分是：\n\n```javascript\n            for(var i=0;i<liArr.length;i++){\n                //绑定索引值（新增一个自定义属性：index属性）\n                liArr[i].index = i;\n                liArr[i].onmouseover = function () {\n\n                    //1.点亮上面的盒子。   2.利用索引值显示下面的盒子。(排他思想)\n                    for(var j=0;j<liArr.length;j++){\n                        liArr[j].className = \"\";\n                        spanArr[j].className = \"\";\n                    }\n                    this.className = \"current\";\n                    spanArr[this.index].className = \"show\"; //【重要代码】\n                }\n            }\n\n```\n\n这段代码中，我们是通过给 `liArr[i]`一个index属性：` liArr[i].index = i`，然后让下方的span对应的index也随之对应显示：`spanArr[this.index].className = \"show\"`。\n\n这样做比较难理解，其实还有一种容易理解的方法是：**给liArr[i]增加index属性时，通过attribute的方式**，因为这种方式增加的属性是可以显示在标签上的。也就有了下面这样的代码：\n\n```javascript\n            for(var i=0;i<liArr.length;i++){\n                //绑定索引值(自定义属性)，通过Attribute的方式【重要】\n                liArr[i].setAttribute(\"index\",i);\n                liArr[i].onmouseover = function () {\n                    //3.书写事件驱动程序（排他思想）\n                    //1.点亮盒子。   2.利用索引值显示盒子。(排他思想)\n                    for(var j=0;j<liArr.length;j++){\n                        liArr[j].removeAttribute(\"class\");\n                        spanArr[j].removeAttribute(\"class\");\n                    }\n                    this.setAttribute(\"class\",\"current\");\n                    spanArr[this.getAttribute(\"index\")].setAttribute(\"class\",\"show\");\n                }\n            }\n\n```\n\n显示的效果是一样的，不同的地方在于，我们审查元素发现，li标签中确实新增了自定义的index属性：\n\n![](http://img.smyhvae.com/20180128_1625.gif)\n\n本段中，我们的目的已经达到了，不足的地方在于，**本段中的代码是通过document获取的的标签**，如果网页中有很多个这种tab选项卡，必然互相影响。\n\n为了多个tab栏彼此独立，我们就需要通过**封装函数**的方式把他们抽取出来，于是就有了下面的改进版代码。\n\n## tab切换：改进版代码（函数封装）\n\n### 方式一：给current标签设置index值【推荐的代码】\n\n完整版代码实现：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n        }\n\n        .box {\n            width: 500px;\n            height: 200px;\n            border: 1px solid #ccc;\n            margin: 50px auto;\n            overflow: hidden;\n        }\n\n        ul {\n            width: 600px;\n            height: 40px;\n            margin-left: -1px;\n            list-style: none;\n        }\n\n        li {\n            float: left;\n            width: 101px;\n            height: 40px;\n            text-align: center;\n            font: 600 18px/40px \"simsun\";\n            background-color: pink;\n            cursor: pointer;\n        }\n\n        span {\n            display: none;\n            width: 500px;\n            height: 160px;\n            background-color: yellow;\n            text-align: center;\n            font: 700 100px/160px \"simsun\";\n        }\n\n        .show {\n            display: block;\n        }\n\n        .current {\n            background-color: yellow;\n        }\n    </style>\n\n    <script>\n        window.onload = function () {\n            //需求：鼠标放到上面的li上，li本身变色(添加类)，下方对应的span也显示出来(添加类);\n            //思路：1.点亮上面的盒子。   2.利用索引值显示下面的对应的盒子。\n\n            //1、获取所有的box\n            var boxArr = document.getElementsByClassName(\"box\");\n\n            //让每一个box都调用函数\n            for (var i = 0; i < boxArr.length; i++) {\n                fn(boxArr[i]);\n            }\n\n            function fn(element) {\n                var liArr = element.getElementsByTagName(\"li\");   //注意，是element获取事件源，不是document获取事件源\n                var spanArr = element.getElementsByTagName(\"span\");\n                //2.绑定事件（循环绑定）\n                for (var i = 0; i < liArr.length; i++) {\n                    //绑定索引值（新增一个自定义属性：index属性）\n                    liArr[i].index = i;\n                    liArr[i].onmouseover = function () {\n                        //3.书写事件驱动程序（排他思想）\n                        //1.点亮上面的盒子。   2.利用索引值显示下面的盒子。(排他思想)\n                        for (var j = 0; j < liArr.length; j++) {\n                            liArr[j].className = \"\";\n                            spanArr[j].className = \"\";\n                        }\n                        this.className = \"current\";\n                        spanArr[this.index].className = \"show\"; //【重要】核心代码\n                    }\n                }\n            }\n        }\n    </script>\n</head>\n<body>\n\n<div class=\"box\">\n    <ul>\n        <li class=\"current\">鞋子</li>\n        <li>袜子</li>\n        <li>帽子</li>\n        <li>裤子</li>\n        <li>裙子</li>\n    </ul>\n    <span class=\"show\">鞋子</span>\n    <span>袜子</span>\n    <span>帽子</span>\n    <span>裤子</span>\n    <span>裙子</span>\n</div>\n\n<div class=\"box\">\n    <ul>\n        <li class=\"current\">鞋子</li>\n        <li>袜子</li>\n        <li>帽子</li>\n        <li>裤子</li>\n        <li>裙子</li>\n    </ul>\n    <span class=\"show\">鞋子</span>\n    <span>袜子</span>\n    <span>帽子</span>\n    <span>裤子</span>\n    <span>裙子</span>\n</div>\n\n<div class=\"box\">\n    <ul>\n        <li class=\"current\">鞋子</li>\n        <li>袜子</li>\n        <li>帽子</li>\n        <li>裤子</li>\n        <li>裙子</li>\n    </ul>\n    <span class=\"show\">鞋子</span>\n    <span>袜子</span>\n    <span>帽子</span>\n    <span>裤子</span>\n    <span>裙子</span>\n</div>\n\n</body>\n</html>\n```\n\n注意，通过函数fn的封装之后，我们是通过参数element获取元素，而不再是document了。这样可以达到“抽象性”的作用，各个tab栏之间相互独立。\n\n上方代码中，我们是通过给 liArr[i]一个index属性：` liArr[i].index = i`，然后让下方的span对应的index也随之对应显示：`spanArr[this.index].className = \"show\"`。\n\n这样做比较难理解，根据上一段的规律，当然还有一种容易理解的方法是：**给liArr[i]增加index属性时，通过attribute的方式**，因为这种方式增加的属性是可以显示在标签上的。也就有了下面的方式二。\n\n### 方式二：通过attribute设置index的值\n\n基于上面方式一中的代码，我们修改一下js部分的代码，其他部分的代码保持不变。js部分的代码如下：\n\n```html\n    <script>\n        window.onload = function () {\n            //需求：鼠标放到上面的li上，li本身变色(添加类)，下方对应的span也显示出来(添加类);\n            //思路：1.点亮上面的盒子。   2.利用索引值显示下面的对应的盒子。\n\n            //1、获取所有的box\n            var boxArr = document.getElementsByClassName(\"box\");\n\n            //让每一个box都调用函数\n            for (var i = 0; i < boxArr.length; i++) {\n                fn(boxArr[i]);\n            }\n\n            function fn(element) {\n                var liArr = element.getElementsByTagName(\"li\");   //注意，是element获取事件源，不是document获取事件源\n                var spanArr = element.getElementsByTagName(\"span\");\n                //2.绑定事件（循环绑定）\n                for (var i = 0; i < liArr.length; i++) {\n                    //绑定索引值(自定义属性)\n                    liArr[i].setAttribute(\"index\", i);\n                    liArr[i].onmouseover = function () {\n                        //3.书写事件驱动程序（排他思想）\n                        //1.点亮盒子。   2.利用索引值显示盒子。(排他思想)\n                        for (var j = 0; j < liArr.length; j++) {\n                            liArr[j].removeAttribute(\"class\");    //注意，这里是class，不是className\n                            spanArr[j].removeAttribute(\"class\");\n                        }\n                        this.setAttribute(\"class\", \"current\");\n                        spanArr[this.getAttribute(\"index\")].setAttribute(\"class\", \"show\");\n                    }\n                }\n            }\n        }\n    </script>\n```\n\n不过，方式一的写法应该比方式二更常见。\n\n**总结**：通过函数封装的形式，可以保证各个tab栏之间的切换互不打扰。最终实现效果如下：\n\n![](http://img.smyhvae.com/20180128_1651.gif)\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n\n"
  },
  {
    "path": "08-前端基本功：CSS和DOM练习/05-DOM操作练习：访问关系的封装.md",
    "content": "---\ntitle: 05-DOM操作练习：访问关系的封装\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 访问关系的函数封装\n\n（1）函数封装\n\n新建一个文件名叫`tools.js`，然后在里面封装访问关系。代码如下。\n\n\n\ntools.js:\n\n```javascript\n/**\n * Created by smyhvae on 2018/01/28.\n */\n\nfunction getEle(id){\n    return document.getElementById(id);\n}\n\n/**\n * 功能：给定元素查找他的第一个元素子节点，并返回\n * @param ele\n * @returns {Element|*|Node}\n */\nfunction getFirstNode(ele){\n    var node = ele.firstElementChild || ele.firstChild;\n    return node;\n}\n\n/**\n * 功能：给定元素查找他的最后一个元素子节点，并返回\n * @param ele\n * @returns {Element|*|Node}\n */\nfunction getLastNode(ele){\n    return ele.lastElementChild || ele.lastChild;\n}\n\n/**\n * 功能：给定元素查找他的下一个元素兄弟节点，并返回\n * @param ele\n * @returns {Element|*|Node}\n */\nfunction getNextNode(ele){\n    return ele.nextElementSibling || ele.nextSibling;\n}\n\n/**\n * 功能：给定元素查找他的上一个兄弟元素节点，并返回\n * @param ele\n * @returns {Element|*|Node}\n */\nfunction getPrevNode(ele){\n    return ele.previousElementSibling || ele.previousSibling;\n}\n\n/**\n * 功能：给定元素和索引值查找指定索引值的兄弟元素节点，并返回\n * @param ele 元素节点\n * @param index 索引值\n * @returns {*|HTMLElement}\n */\nfunction getEleOfIndex(ele,index){\n    return ele.parentNode.children[index];\n}\n\n/**\n * 功能：给定元素查找他的所有兄弟元素，并返回数组\n * @param ele\n * @returns {Array}\n */\nfunction getAllSiblings(ele){\n    //定义一个新数组，装所有的兄弟元素，将来返回\n    var newArr = [];\n    var arr = ele.parentNode.children;\n    for(var i=0;i<arr.length;i++){\n        //判断：对同级的所有元素节点进行遍历，如果不是传递过来的元素自身，那就是兄弟元素，于是添加到新数组中。\n        if(arr[i]!==ele){\n            newArr.push(arr[i]);\n        }\n    }\n    return newArr;\n}\n\n```\n\n\n上方代码中，我们单独来重视一下最后一个方法：获取指定元素的兄弟元素：\n\n\n```javascript\n/**\n * 功能：给定元素查找他的所有兄弟元素，并返回数组\n * @param ele\n * @returns {Array}\n */\nfunction getAllSiblings(ele){\n    //定义一个新数组，装所有的兄弟元素，将来返回\n    var newArr = [];\n    var arr = ele.parentNode.children;\n    for(var i=0;i<arr.length;i++){\n        //判断：对同级的所有元素节点进行遍历，如果不是传递过来的元素自身，那就是兄弟元素，于是添加到新数组中。\n        if(arr[i]!==ele){\n            newArr.push(arr[i]);\n        }\n    }\n    return newArr;\n}\n\n```\n\n（2）函数的调用举例：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        li {\n            width: 100px;\n            height: 100px;\n            background-color: pink;\n            margin: 5px;\n            list-style: none;\n        }\n    </style>\n</head>\n<body>\n\n<ul>\n    <li></li>\n    <li></li>\n    <li id=\"box\"></li>\n    <li></li>\n    <li></li>\n</ul>\n<script src=\"tools.js\"></script>\n<script>\n\n    //获取box改为red\n    var box = getEle(\"box\");\n    box.style.backgroundColor = \"red\"\n\n    //获取第一个和最后一个子节点\n    var parent = box.parentNode;\n    getFirstNode(parent).style.backgroundColor = \"yellow\";\n    getLastNode(parent).style.backgroundColor = \"yellow\";\n\n    //获取上一个和下一个兄弟节点\n    getNextNode(box).style.backgroundColor = \"blue\";\n    getPrevNode(box).style.backgroundColor = \"blue\";\n\n\n    //指定兄弟节点\n    getEleOfIndex(box,0).style.backgroundColor = \"green\";\n    getEleOfIndex(box,1).style.backgroundColor = \"green\";\n\n    //获取所有的兄弟节点（返回值是数组，所以用for循环操作）\n    var arr = getAllSiblings(box);\n    for(var i=0;i<arr.length;i++){\n        arr[i].style.backgroundColor = \"green\";\n    }\n\n</script>\n</body>\n</html>\n\n```\n\n注意：上方代码中，我们引用到了`tools.js`这个工具类。\n\n\n\n\n\n```\n\n\n```\n\n\n\n\n\n\n\n\n```\n\n\n```\n\n\n\n\n```\n\n\n```\n\n\n"
  },
  {
    "path": "08-前端基本功：CSS和DOM练习/07-DOM操作练习：innerHTML的方式创建元素.md",
    "content": "---\ntitle: 07-DOM操作练习：innerHTML的方式创建元素\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 动态创建DOM元素的三种方式\n\n- `document.write();` 不常用，因为容易覆盖原来的页面。\n\n- `innerHTML = ();` 用的比较多。绑定属性和内容比较方便。(节点套节点)\n\n- `document.createElement();` 用得也比较多，指定数量的时候一般用它。\n\n\n**1、方式一：**\n\n```javascript\ndocument.write();\n```\n\n这种方式的好处是：比较随意，想创建就创建，可以直接在`write`里写属性都行。但是会把原来的标签给覆盖掉。所以不建议。\n\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n<ul>\n    smyhvae\n</ul>\n\n<script>\n    //第一种创建方式：document.write();\n    document.write(\"<li class='hehe'>我是document.write创建的</li>\");\n</script>\n</body>\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180129_1908.png)\n\n\n**方式二：**innerHTML\n\n\n举例如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n<ul>\n    smyhvae\n</ul>\n\n<script>\n    var ul = document.getElementsByTagName(\"ul\")[0];\n\n    //第二种：直接利用元素的innerHTNL方法。（innerText方法不识别标签）\n    ul.innerHTML += \"<li id='li1'>我是innerHTML创建的</li>\"  //注意，是用符号“+=”，不是“=”\n</script>\n</body>\n</html>\n```\n\n注意，上方代码中，是用是用符号`+=`，不是`=`，前者是在原来的基础之上增加，后者是替换。\n\n\n效果如下：\n\n![](http://img.smyhvae.com/20180129_2017.png)\n\n\n\n**3、方式三：**利用DOM的api创建\n\n利用DOM的api创建节点，有很多种：\n\n比如：\n\n- createElement()\n\n- appendChild()\n\n- removeChild()\n\n- insertBefore()\n\n- replaceChild()\n\n\n这个我们在上一篇文章的`DOM节点的操作`这一段中已经讲到了。\n\n```html\n\n```\n\n\n## innerHTML举例：在线用户的获取\n\n现在要做下面这样一个页面：\n\n\n![](http://img.smyhvae.com/20180129_2151.png)\n\n\n上图的意思是，每次刷新页面，都从服务器获取最新的在线人数的名单（我们先用本地的数组来模拟服务器上的数据）。\n\n它的结构是：div > ul > li。每一个li就是一个头像。\n\n如果在本地直接添加几个头像的话，代码是：\n\n```html\n    //往ul中添加li元素以及li元素中的内容\n       ul.innerHTML += '<li>'+\n                       '<a href=\"#\" target=\"_blank\"><img src=\"images/noavatar_small.gif\" width=\"48\" height=\"48\" alt=\"千古壹号\"></a>'+\n                       '<p><a href=\"#\" title=\"千古壹号\" target=\"_blank\">千古壹号</a></p>'+\n                   '</li>';\n       ul.innerHTML += '<li>'+\n               '<a href=\"#\" target=\"_blank\"><img src=\"images/noavatar_small.gif\" width=\"48\" height=\"48\" alt=\"千古壹号\"></a>'+\n               '<p><a href=\"#\" title=\"千古壹号\" target=\"_blank\">千古壹号</a></p>'+\n               '</li>';\n       ul.innerHTML += '<li>'+\n               '<a href=\"#\" target=\"_blank\"><img src=\"images/noavatar_small.gif\" width=\"48\" height=\"48\" alt=\"千古壹号\"></a>'+\n               '<p><a href=\"#\" title=\"千古壹号\" target=\"_blank\">千古壹号</a></p>'+\n               '</li>';\n\n```\n\n上方代码有两点比较重要：\n\n- 我们是通过`ul.innerHTML += 元素节点`的方式来不停地往ul里面加内容，比`createElement`的方式要方便。\n\n- 元素的内容本身有双引号`\"`，所以我们要用单引号`'`进行字符串的连接。\n\n但是，当我们从服务器获取在线用户的时候，头像和用户的昵称是动态变化的，所以每个字符串要用变量进行表示：\n\n```html\n        ul.innerHTML += '<li>'+\n                            '<a href=\"#\" target=\"blank\"><img src=\"'+users[i].icon+'\" width=\"48\" height=\"48\" alt=\"'+users[i].name+'\"></a>'+\n                            '<p><a href=\"#\" title=\"'+users[i].name+'\" target=\"_blank\">'+users[i].name+'</a></p>'+\n                        '</li>';\n\n```\n\n这里我们暂时用本地的数组来代表服务器的数据，最终的完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            word-wrap: break-word;\n        }\n\n        .wp {\n            width: 730px;\n            margin: 0px auto;\n        }\n\n        .mtn {\n            margin-top: 5px !important;\n        }\n\n        #ct .frame {\n            margin: 0;\n            border: none;\n        }\n\n        .xfs_2 .frame-title, .xfs_2 .frametitle, .xfs_2 .tab-title {\n            background-color: #A90000;\n            background-position: 0 -99px;\n        }\n\n        .xfs .frame-title, .xfs .frametitle, .xfs .tab-title, .xfs .frame-title a, .xfs .frametitle a, .xfs .tab-title a {\n            color: #FFF !important;\n        }\n\n        .xfs .frame-title, .xfs .frametitle, .xfs .tab-title {\n            border: none;\n            background: transparent url(images/mu.png) repeat-x 0 95;\n        }\n\n        .title {\n            padding: 0 10px;\n            height: 32px;\n            font-size: 14px;\n            font-weight: 700;\n            line-height: 32px;\n            overflow: hidden;\n        }\n\n        .block {\n            margin: 10px 10px 0;\n        }\n\n        ul, menu, dir {\n            display: block;\n            list-style: none;\n            -webkit-margin-before: 1em;\n            -webkit-margin-after: 1em;\n            -webkit-margin-start: 0px;\n            -webkit-margin-end: 0px;\n            -webkit-padding-start: 25px;\n        }\n\n        .mls li {\n            padding: 0 0 5px;\n            width: 66px;\n            height: 85px;\n        }\n\n        .ml li {\n            float: left;\n            text-align: center;\n            overflow: hidden;\n        }\n\n        a {\n            color: #333;\n            text-decoration: none;\n            font: 12px/1.5 Tahoma, 'Microsoft Yahei', 'Simsun';\n        }\n\n        .mls p {\n            margin-top: 5px;\n        }\n\n        .ml p, .ml span {\n            display: block;\n            width: 100%;\n            height: 20px;\n            white-space: nowrap;\n            text-overflow: ellipsis;\n            overflow: hidden;\n        }\n\n        .mls img {\n            width: 48px;\n            height: 48px;\n        }\n\n        .ml img {\n            display: block;\n            margin: 0 auto;\n        }\n\n        a img {\n            border: none;\n        }\n    </style>\n</head>\n<body>\n\n<div class=\"wp mtn\">\n    <div id=\"diy3\" class=\"area\">\n        <div id=\"frameipq7f2\" class=\"xfs xfs_2 frame move-span cl frame-1\">\n            <div\n                    class=\"title frame-title\"><span class=\"titletext\">当前在线用户</span></div>\n            <div id=\"frameipq7f2_left\"\n                 class=\"column frame-1-c\">\n                <div\n                        id=\"frameipq7f2_left_temp\" class=\"move-span temp\"></div>\n                <div id=\"portal_block_695\"\n                     class=\"block move-span\">\n                    <div\n                            id=\"portal_block_695_content\" class=\"dxb_bc\">\n                        <div class=\"module cl ml mls\" id=\"users\">\n                            <ul>\n                                <!--<li>-->\n                                <!--<a href=\"#\" target=\"_blank\"><img src=\"images/noavatar_small.gif\" width=\"48\" height=\"48\" alt=\"千古壹号\"></a>-->\n                                <!--<p><a href=\"#\" title=\"千古壹号\" target=\"_blank\">千古壹号</a></p>-->\n                                <!--</li>-->\n\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<script>\n    //模拟从服务器获取数据\n    var users = [\n        {\"name\": \"smyhvae\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"smyh\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"smyh02\", \"icon\": \"images/75_avatar_small.jpg\"},\n        {\"name\": \"vae\", \"icon\": \"images/89_avatar_small.jpg\"},\n        {\"name\": \"today\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"enen\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"oyey\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"dongxiaojie\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"qishi\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"qqtang\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"wawawa\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"haha\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"robot\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"heheda\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"smyhvae1\", \"icon\": \"images/noavatar_small.gif\"},\n        {\"name\": \"lihaile\", \"icon\": \"images/noavatar_small.gif\"}\n    ];\n\n    //需求：页面显示所有的在线用户。\n    //思路：模拟服务器获取数据（数组中装着json）.获取ul，把ul的innerHTML属性获取到，然后不间断的往innerHTML属性中赋值。\n    //赋值要求：li标签的内容。\n    //步骤：(获取元素)\n    var div = document.getElementById(\"users\");\n    var ul = div.firstElementChild || div.firstChild;\n    //        var ul = div.children[0];\n\n    //1.模拟服务器获取数据（定义数组），通过循环添加元素（定义for）\n    //数组中有多少元素，我们就创建多少个li标签\n    for (var i = 0; i < users.length; i++) {\n        //2.模拟实验的操作方式。\n        ul.innerHTML += '<li>' +\n            '<a href=\"#\" target=\"blank\"><img src=\"' + users[i].icon + '\" width=\"48\" height=\"48\" alt=\"' + users[i].name + '\"></a>' +\n            '<p><a href=\"#\" title=\"' + users[i].name + '\" target=\"_blank\">' + users[i].name + '</a></p>' +\n            '</li>';\n    }\n</script>\n</body>\n</html>\n```\n\n工程文件：[2018-02-01-获取在线用户列表demo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-01-%E8%8E%B7%E5%8F%96%E5%9C%A8%E7%BA%BF%E7%94%A8%E6%88%B7%E5%88%97%E8%A1%A8demo.rar)\n\n\n\n## innerHTML举例2：模拟百度搜索的下方提示\n\n要求实现的效果如下：\n\n![](http://img.smyhvae.com/20180201_2030.gif)\n\n在这之前，我们先实现这样一个例子：**判断字符串以某个字符串为开头**。\n\n**判断字符串是否以某个字符串为开头：**\n\n\n```javascript\n    var str = \"smyhvae\";\n\n    //判断str是否以sm为开头？（给定字符串，然后他的索引值为0）\n    var num = str.indexOf(\"sm\");\n    //只有返回值为0，那么字符串才是以参数为开头\n    //如果在任何位置都查询不到参数，则返回值为-1；\n\n```\n\n\n代码解释：我们可以通过`indexOf(\"参数\")`来实现。如果返回的索引值为0，说明字符串就是以这个参数开头的。\n\n为了实现上方gif图的搜索功能，完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html>\n<head lang=\"en\">\n    <meta charset=\"UTF-8\">\n    <title></title>\n    <style>\n        * {\n            padding: 0;\n            margin: 0;\n        }\n\n        .box {\n            width: 500px;\n            margin: 200px auto;\n        }\n\n        ul {\n            width: 392px;\n            padding: 5px;\n            list-style: none;\n            border: 1px solid red;\n        }\n\n        li:hover {\n            background-color: red;\n        }\n\n        input {\n            width: 400px;\n        }\n\n        button {\n            width: 70px;\n        }\n    </style>\n</head>\n<body>\n<div class=\"box\">\n    <input type=\"text\"/>\n    <button>搜索</button>\n    <!--<ul>-->\n    <!--<li>aaaa</li>-->\n    <!--<li>bbb</li>-->\n    <!--<li>ccc</li>-->\n    <!--</ul>-->\n</div>\n\n<script>\n    //需求：输入内容(输入事件，键盘弹起事件)，模拟服务器获取内容，创建ul，并在其中显示。\n\n    //1.获取事件源\n    //模拟服务器获取内容\n    var arr = [\"a\", \"ab\", \"abc\", \"abcd\", \"aa\", \"aaa\"];\n    var box = document.getElementsByTagName(\"div\")[0];\n    var inp = box.children[0];\n    //        var inp = document.getElementsByTagName(\"input\")[0];\n\n    //2.绑定事件(输入内容(输入事件，键盘弹起事件))\n    inp.onkeyup = function () {\n        //创建一个字符串，里面添加满了li和对应的内容。\n        var newArr = [];\n        //遍历老数组arr，然后判断每一项，只要是以input的内容为开头的，就添加到新数组newArr中，然后转成字符串。\n        for (var i = 0; i < arr.length; i++) {\n\n            //获取输入内容input标签的value属性值。\n            if (arr[i].indexOf(this.value) === 0) {  //【重要】判断老数组arr中的每一项，是否以input的内容为开头\n                newArr.push(\"<li>\" + arr[i] + \"</li>\");\n            }\n        }\n        var str = newArr.join(\"\");\n\n        //Bug1：每次创建新的ul之前，如果有就的ul，就先删除旧的ul\n        if (box.getElementsByTagName(\"ul\")[0]) {\n            //只要存在，那么就是object，object类型的数据，只要不是null,对应的boolean值都是true；\n            box.removeChild(box.children[2]);\n        }\n\n        //Bug2.如果input的内容为空，那么就不能再生成ul了。\n        //如果input为空，那就切断函数\n\n        //Bug3.如果arr数组中找不到以input为开头的元素。那就切断函数\n        //newArr的长度为0，就能证明以input内容为开头的元素，在arr中不存在\n        if (this.value.length === 0 || newArr.length === 0) {  //fix bug2、fix bug3\n            //切断函数，直接return\n            return;\n        }\n\n        //3.书写事件驱动程序\n        var ul = document.createElement(\"ul\");\n        //把创建好的内容添加到ul中。\n        ul.innerHTML = str;\n        box.appendChild(ul);\n    }\n</script>\n</body>\n</html>\n```\n\n\n\n## 动态操作表格\n\n\n方式1：\n\n```\n  createElement()\n```\n\n方式2：\n\n- rows                          (只读，table和textarea能用)\n\n- insertRow()              (只有table能调用)\n\n- deleteRow()           (只有table能调用)\n\n- cells             (只读，table和textarea能用)\n\n- insertCell()               (只有tr能调用)\n\n- deleteCell()              (只有tr能调用)\n\n\nPS：括号里可以带index。用的不多。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "09-移动Web开发/01-Bootstrap入门.md",
    "content": "---\ntitle: 01-Bootstrap入门\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## Bootstrap 介绍\n\nBootstrap 是非常流行的前端框架。特点是：灵活简洁、代码优雅、美观大方。它是由Twitter的两名工程师 Mark Otto 和 Jacob Thornton 在2011年开发的。\n\n简单来说，Bootstrap 让 Web 开发**更简单、更快捷**。使用 Bootstrap 框架并不代表我们再开发时不用自己写 CSS 样式，而是不用写绝大多数常见的样式。\n\nPS：[Amaze UI](http://amazeui.org/) 这个框架其实跟 Bootstrap 很像。\n\n### 官网网站\n\n- 官方网站：<https://getbootstrap.com/>\n\n- 中文网站：<http://www.bootcss.com/>\n\nV3版本：\n\n![](http://img.smyhvae.com/20180225_1033.png)\n\nV4版本：\n\n![](http://img.smyhvae.com/20180225_1043.png)\n\n列举几个用 Bootstrap 做的网站：\n\n- [Bootstrap 优站精选](http://www.youzhan.org/)\n\n- <https://mobirise.com/>\n\n- <http://snappa.io/>\n\n### Bootstrap 版本\n\n目前市面上使用的最多的是 3.x.x 版本。各个版本的介绍：\n\n2.3.2版本：\n\n- 2013年之后，停止维护；\n\n- 支持更广泛的浏览器\n\n- 代码不够简洁， 功能不够多。\n\n3.x.x 版本：\n\n- 目前最新的稳定版本。\n\n- 不支持 IE7 和早期的 Firefox\n\n- 支持 IE8，单效果不好。\n\n\n2015年8月发布 4.0.0-alpha 的内部测试版。\n\n**版本号的普及：**\n\n- alpha 版：内部测试版。α 是希腊字母的第一个，表示最早的版本，bug很多。主要是给开发和测试人员找 bug 用的。\n\n- beta 版：公开测试版。 主要是给“部落”用户和忠实用户测试用的。bug依然很多，但比 Alpha 版要稳定。这个阶段的版本还会不断增加新功能，如果你是发烧友，可以下载这个版本。\n\n- rc 版：候选版本（Release Candidate）。该版本不再增加新的功能。类似于最终发行版之前的预览版（发行的候选版本）。此版本的发布，预示着最终发行版即将到来。作为普通用户，如果很着急，也可以下载 rc 版。\n\n- stable 版：稳定版。在开源软件中，都有 stable版本，这个是开源软件的最终发行版，用户可以放心大胆地使用。\n\n### Bootstrap 库的下载\n\n> 这里我们以  Bootstrap V3.3.7 为例。\n\n进入[中文官网](https://v3.bootcss.com/)，下载 `用于生产环境的 Bootstrap`，如下图所示：\n\n![](http://img.smyhvae.com/20180225_1052.png)\n\n下载之后，解压 `bootstrap-3.3.7-dist` ，有三个文件夹：\n\n![](http://img.smyhvae.com/20180225_1053.png)\n\n将其拷贝到工程文件的lib文件夹下即可。\n\nPS：`dist`表示编译之后的文件，这在库文件中是很常见的。\n\n因为 bootstrap.js依赖jQuery，所以要先引用jquery.js 然后引用bootstrap.js。\n\n\n### Bootstrap 基础模板的介绍\n\n[Bootstrap](https://v3.bootcss.com/getting-started/)官网提供了基本模板，如下图所示：\n\n![](http://img.smyhvae.com/20180225_1119.png)\n\n其完整版代码 copy 如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n\t  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n    <!-- 上述3个meta标签*必须*放在最前面，任何其他内容都*必须*跟随其后！ -->\n    <title>我的网站</title>\n\n    <!-- Bootstrap -->\n\t<link rel=\"stylesheet\" href=\"lib/bootstrap/css/bootstrap.css\">\n    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->\n    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->\n    <!--[if lt IE 9]>\n      <script src=\"https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js\"></script>\n      <script src=\"https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js\"></script>\n    <![endif]-->\n  </head>\n  <body>\n    <h1>你好，世界！</h1>\n\n    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->\n    <script src=\"https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js\"></script>\n    <!-- Include all compiled plugins (below), or include individual files as needed -->\n    <script src=\"js/bootstrap.min.js\"></script>\n  </body>\n</html>\n```\n\n我们需要对上面的代码进行解释。\n\n**（1）Compatible**：\n\n```html\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n```\n\n解释：设置浏览器的兼容模式版本。表示如果在IE浏览器下则使用最新的标准，渲染当前文档。\n\n**（2）viewport 视口**：\n\n\n```html\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n```\n\n解释：声明当前网页在移动端浏览器中展示的相关设置。我们在做移动 web 开发时，就用上面这行代码设置 viewport。\n\n视口的作用：在移动浏览器中，当页面宽度超出设备，浏览器内部虚拟的一个页面容器，将页面容器缩放到设备这么大，然后展示。\n\n\n需要注意的是：\n\n- 目前大多数手机浏览器的视口（承载页面的容器）宽度都是980；\n- 此属性为移动端页面视口设置，上方代码设置的值，表示在移动端页面的宽度为设备的宽度，并且不缩放（缩放级别为1）。\n\n属性解释：\n\n- width:设置viewport的宽度。\n- initial-scale：初始化缩放比例。\n- minimum-scale:最小缩放比例。\n- maximum-scale:最大缩放比例。\n- user-scalable:是否允许用户手动缩放（值可以写成yes/no，也可以写成1/0）\n\n\nPS：如果设置了不允许用户缩放，那么最小缩放和最大缩放就没有意义了。二者是矛盾的。\n\n\n\n**（3）条件注释**：\n\n```html\n    <!--[if lt IE 9]>\n      <script src=\"https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js\"></script>\n      <script src=\"https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js\"></script>\n    <![endif]-->\n```\n\n条件注释的作用：当判断条件满足时，就会执行注释中的HTML代码，不满足时会当做注释忽略掉。\n\n上方代码的条件注释中，引入了两个脚本：\n\n- [html5shiv](https://github.com/aFarkas/html5shiv)：让浏览器可以识别 HTML5 的新标签。如header、footer、section等。\n- [respond.js](https://github.com/scottjehl/Respond)：让低版本浏览器可以使用 CSS3 的媒体查询。\n\n另外，我们还需要引入下面这个库：\n\n- [jQuery](https://github.com/jquery/jquery)：Bootstrap框架中的所有 JS 组件都依赖于 jQuery 实现。\n\n我们可以把上面这三个库文件拷贝到 lib 文件夹中（注意引用的路径要写正确）。\n\n## 使用 Bootstrap 搭建项目\n\n### 1、工程文件的目录结构\n\n```\n├─ Demo ·························· 项目所在目录\n└─┬─ /css/ ······················· 我们自己的CSS文件\n  ├─ /font/ ······················ 使用到的字体文件\n  ├─ /img/ ······················· 使用到的图片文件\n  ├─ /js/ ························ 自己写的JS脚步\n  ├─ /lib/ ······················· 从第三方下载回来的库【只用不改】\n  ├─ /favicon.ico ················ 站点图标\n  └─ /index.html ················· 入口文件\n```\n\n### 2、下载并引入 Bootstrap 库文件\n\n见上一段的讲解。引入之后，另外还需要引入 html5shiv、respond、jQuery 这三个库文件。\n\n### 3、字符集、Viewport设置、浏览器兼容模式\n\n我们将 Bootstrap 的基础模板代码 copy到项目的index.html中，这其中就包括最前面的三个meta标签：\n\n```html\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <!-- 上述3个meta标签*必须*放在最前面，任何其他内容都*必须*跟随其后！ -->\n```\n\n### 4、favicon（站点图标）\n\n```html\n<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"favicon.ico\">\n```\n\n### 5、引入相应的第三方文件\n\n```html\n    <!-- 引入 Bootstrap 的核心样式文件（必须） -->\n    <link rel=\"stylesheet\" href=\"lib/bootstrap/css/bootstrap.css\">\n    <!-- 引入我们自己写的 css 样式文件-->\n    <link rel=\"stylesheet\" href=\"css/my.css\">\n\n\t...\n\n\t<script src=\"lib/jquery/jquery.js\"></script>\n\t<script src=\"lib/bootstrap/js/bootstrap.js\"></script>\n\t<script src=\"js/my.js\"></script>\n```\n\n注意，先引入第三方的文件，再引入我们自己写的文件。\n\n\n### 6、默认字体\n\n在我们默认的样式表中将默认字体设置为：\n\n```css\nbody{\n  font-family: \"Helvetica Neue\",\n                Helvetica,\n                Microsoft Yahei,\n                Hiragino Sans GB,\n                WenQuanYi Micro Hei,\n                sans-serif;\n}\n```\n\n### 7、完成页面空结构\n\n> 先划分好页面中的大容器，然后在具体看每一个容器中单独的情况。\n\n完整代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <title>我的网站</title>\n    <!--建议：第三方引用的css库放在上面，我们自己写的文件，都放在下面-->\n\n    <!-- 引入 Bootstrap 的核心样式文件（必须） -->\n    <link rel=\"stylesheet\" href=\"lib/bootstrap/css/bootstrap.css\">\n    <!-- 引入我们自己写的 css 样式文件-->\n    <link rel=\"stylesheet\" href=\"css/main.css\">\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"favicon.ico\">\n    <!--[if lt IE 9]>\n    <script src=\"lib/html5shiv/html5shiv.min.js\"></script>\n    <script src=\"lib/respond/respond.min.js\"></script>\n    <![endif]-->\n</head>\n\n<body>\n<!-- 头部区域 -->\n<header id=\"header\">\n</header>\n<!-- /头部区域 -->\n\n<!-- 广告轮播 -->\n<section id=\"main_ad\"></section>\n<!-- /广告轮播 -->\n\n<!-- 特色介绍 -->\n<section></section>\n<!-- /特色介绍 -->\n\n<!-- 立即预约 -->\n<section></section>\n<!-- /立即预约 -->\n\n<!-- 产品推荐 -->\n<section></section>\n<!-- /产品推荐 -->\n\n<!-- 新闻列表 -->\n<section></section>\n<!-- /新闻列表 -->\n\n<!-- 合作伙伴 -->\n<section></section>\n<!-- /合作伙伴 -->\n\n<!-- 脚注区域 -->\n<footer></footer>\n<!-- /脚注区域 -->\n\n<script src=\"lib/jquery/jquery.js\"></script>\n<script src=\"lib/bootstrap/js/bootstrap.js\"></script>\n<script src=\"js/main.js\"></script>\n</body>\n\n</html>\n\n```\n\n\n\n\n## CSS 样式\n\n全局 CSS 样式在[官网](https://v3.bootcss.com/css/)有介绍：\n\n![](http://img.smyhvae.com/20180225_1710.png)\n\n**如果需要哪个样式，直接根据文档的指引，在相应的元素里加指定的类名即可。**\n\n我们选部分重要的来讲一下。\n\n### 布局容器：container 类\n\n截图如下：\n\n![](http://img.smyhvae.com/20180225_1720.png)\n\n**作用**：用于定义一个固定宽度且居中的版心。只不过，这个版心的宽度具有**响应式**的效果。\n\n也就是说，在 Bootstrap 中，我们一般用 .container 类来表示版心。\n\n格式举例：\n\n```html\n<div class=\"topbar\">\n  <div class=\"container\">\n    <!--\n      此处的代码会显示在一个固定宽度且居中的容器中\n      该容器的宽度会跟随屏幕的变化而变化\n    -->\n  </div>\n</div>\n```\n\n这个 container 类我们自己其实也可以写，通过媒体查询即可实现。\n\n### 栅格参数\n\n栅格系统最主要的操作是：利用 css 的响应式去做一套行列布局的预置样式。\n\n栅格参数如下：\n\n![](http://img.smyhvae.com/20180225_1732.png)\n\n我们尤其要记住各个屏幕的尺寸和**类前缀**。\n\n## 组件\n\n一个按钮称之为样式；两个按钮在一起，就可以称之为组件。\n\n组件在[官网](https://v3.bootcss.com/components/)有介绍：\n\n![](http://img.smyhvae.com/20180225_1738.png)\n\n我们现在需要关注的不是组件怎么用，而是里面有哪些组件，避免**重复造轮子**：别人已经做得很好了，不需要我们再重复。\n\n## JS 组件\n\nJS 组件在[官网](https://v3.bootcss.com/javascript/)有介绍：\n\n![](http://img.smyhvae.com/20180225_1750.png)\n\n这里面包含了很多带交互的组件。比如轮播图：\n\n![](http://img.smyhvae.com/20180225_1841.png)\n\n## 博主提供的下载链接\n\n空结构的工程文件的下载地址：(lib文件夹里包含了Bootstrap相关的各种库和中文文档)\n\n- [2018-02-25-BootstrapDemo及文档.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-25-BootstrapDemo%E5%8F%8A%E6%96%87%E6%A1%A3.rar)\n\n还是那句话：**如果需要哪个样式，直接根据文档的指引，在相应的元素里加指定的类名即可。**\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n"
  },
  {
    "path": "09-移动Web开发/02-Bootstrap使用.md",
    "content": "---\ntitle: 02-Bootstrap使用\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n"
  },
  {
    "path": "09-移动Web开发/03-Less详解.md",
    "content": "---\ntitle: 03-Less详解\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## CSS 预处理器\n\n### 为什么要有 CSS 预处理器\n\n**CSS基本上是设计师的工具，不是程序员的工具**。在程序员的眼里，CSS是很头痛的事情，它并不像其它程序语言，比如说PHP、Javascript等等，有自己的变量、常量、条件语句以及一些编程语法，只是一行行单纯的属性描述，写起来相当的费事，而且代码难以组织和维护。\n\n很自然的，有人就开始在想，能不能给CSS像其他程序语言一样，加入一些编程元素，让CSS能像其他程序语言一样可以做一些预定的处理。这样一来，就有了“**CSS预处器**（CSS Preprocessor）”。\n\n### 什么是 CSS 预处理器\n\n- 是 CSS 语言的**超集**，比CSS更丰满。\n\nCSS 预处理器定义了一种新的语言，其基本思想是：**用一种专门的编程语言，为CSS增加了一些编程的特性**，将CSS作为目标生成文件，然后开发者就只要使用这种语言进行编码工作。\n\n通俗的说，**CSS预处理器用一种专门的编程语言，进行Web页面样式设计，然后再编译成正常的CSS文件**，以供项目使用。CSS预处理器为CSS增加一些编程的特性，无需考虑浏览器的兼容性问题，例如你可以在CSS中使用变量、简单的逻辑程序、函数等等在编程语言中的一些基本特性，可以让你的CSS更加简洁、适应性更强、可读性更佳，更易于代码的维护等诸多好处。\n\nCSS预处理器技术已经非常成熟，而且也涌现出了很多种不同的CSS预处理器语言，比如说：**Sass（SCSS）、LESS**、Stylus、Turbine、Swithch CSS、CSS Cacheer、DT CSS等。如此之多的CSS预处理器，那么“我应该选择哪种CSS预处理器？”也相应成了最近网上的一大热门话题，在Linkedin、Twitter、CSS-Trick、知乎以及各大技术论坛上，很多人为此争论不休。相比过去我们对是否应该使用CSS预处理器的话题而言，这已经是很大的进步了。\n\n到目前为止，在众多优秀的CSS预处理器语言中就属**Sass、LESS和Stylus最优秀**，讨论的也多，对比的也多。本文将分别从他们产生的背景、安装、使用语法、异同等几个对比之处向你介绍这三款CSS预处理器语言。相信前端开发工程师会做出自己的选择——我要选择哪款CSS预处理器。\n\n\n## less 的介绍\n\nless 是一款比较流行的**预处理 CSS**，支持变量、混合、函数、嵌套、循环等特点。\n\n\n- [官网](http://lesscss.org/)\n\n-  [中文网（less 文档）](http://lesscss.cn/)\n\n- [Bootstrap网站的 less 文档](https://less.bootcss.com/)\n\n-  推荐文章：<http://www.w3cplus.com/css/less>\n\n## less 的语法\n\n### 注释\n\nless 的注释可以有两种。\n\n\n第一种注释：模板注释\n\n```\n  // 模板注释 这里的注释转换成CSS后将会删除\n```\n\n因为 less 要转换为 css才能在浏览器中使用。转换成 css 之后，这种注释会被删除（毕竟 css 不识别这种注释）。\n\n第二种注释：CSS 注释语法\n\n```less\n\n/* CSS 注释语法 转换为CSS后让然保留 */\n```\n\n总结：如果在less中写注释，我们推荐写第一种注释。除非是类似于版权等内容，就采用第二种注释。\n\n\n### 定义变量\n\n我们可以把**重复使用或经常修改的值**定义为变量，在需要使用的地方引用这个变量即可。这样可以避免很多重复的工作量。\n\n（1）在less文件中，定义一个变量的格式：\n\n```less\n@变量名: 变量值;        //格式\n\n@bgColor: #f5f5f5;      //格式举例\n```\n\n（2）同时，在 less 文件中引用这个变量。\n\n最终，less文件的完整版代码如下：\n\nmain.less：\n\n```less\n// 定义变量\n@bgColor: #f5f5f5;\n\n// 引用变量\nbody{\n    background-color: @bgColor;\n}\n```\n\n\n我们将上面的less文件编译为 css 文件后（下一段讲less文件的编译），自动生成的代码如下：\n\nmain.css：\n\n```css\nbody{\n    background-color: #f5f5f5;\n}\n```\n\n\n### 使用嵌套\n\n在 css 中经常会用到子代选择器，效果可能是这样的：\n\n\n```css\n.container {\n  width: 1024px;\n}\n\n.container > .row {\n  height: 100%;\n}\n\n.container > .row a {\n  color: #f40;\n}\n\n.container > .row a:hover {\n  color: #f50;\n}\n```\n\n上面的代码嵌套了很多层，写起来很繁琐。可如果用 less 的嵌套语法来写这段代码，就比较简洁。\n\n嵌套的举例如下：\n\nmain.less:\n\n```less\n.container {\n  width: @containerWidth;\n\n  > .row {\n    height: 100%;\n    a {\n      color: #f40;\n\n      &:hover {\n        color: #f50;\n      }\n\n    }\n  }\n\n  div {\n    width: 100px;\n\n    .hello {\n      background-color: #00f;\n    }\n\n  }\n}\n```\n\n将上面的less文件编译为 css 文件后，自动生成的代码如下：\n\nmain.css\n\n```css\n.container {\n    width: 1024px;\n}\n\n.container > .row {\n    height: 100%;\n}\n\n.container > .row a {\n    color: #f40;\n}\n\n.container > .row a:hover {\n    color: #f50;\n}\n\n.container div {\n    width: 100px;\n}\n\n.container div .hello {\n    background-color: #00f;\n}\n```\n\n\n\n### Mixin\n\nMixin 的作用是把**重复的代码**放到一个类当中，每次只要引用类名，就可以引用到里面的代码了，非常方便。\n\n（1）在 less 文件中定义一个类：（将重复的代码放到自定义的类中）\n\n```less\n/* 定义一个类 */\n.roundedCorners(@radius: 5px) {\n  -moz-border-radius: @radius;\n  -webkit-border-radius: @radius;\n  border-radius: @radius;\n}\n```\n\n上方代码中，第一行里面，括号里的内容是参数：这个参数是**缺省值**。\n\n（2）在 less 文件中引用上面这个类：\n\n```less\n#header {\n  .roundedCorners;\n}\n#footer {\n  .roundedCorners(10px);\n}\n```\n\n\n\n上方代码中，header 中的引用没有带参数，表示参数为缺省值； footer 中的引用带了参数，那就用这个参数。\n\n\n\n### Import\n\n在开发阶段，我们可以将不同的样式放到多个文件中，最后通过@import 的方式合并。意思就是，当出现多个 less 文件时，怎么引用它们。\n\n这个很好理解， css 文件可以有很多个，less文件也可以有很多个。\n\n（1）定义一个被引用的less文件，名为`_button1.less`：\n\n`_button1.less:`\n\n```less\n.btn{\n  line-height: 100px;\n  color: @btnColor;\n}\n```\n\nPS1：被引用的less文件，我们习惯在前面加**下划线**，表示它是**部分文件**。\n\nPS2：`_button1.less`里可以引用`main.css`里的自定义变量。\n\n（2）在 `main.css` 中引用上面的 `_button1.less`：（代码的第二行）\n\nmain.css：\n\n```less\n@btnColor: red;\n\n@import url(`_button1.less:');    //这里的路径写的是相对路径\n\nbody{\n  width: 1024px;\n}\n```\n\n将 上面的main.less 编译为 main.css之后，自动生成的代码如下：\n\n```css\n.btn {\n    line-height: 100px;\n    color: red;\n}\n\nbody {\n    width: 1024px;\n}\n```\n\n### 内置函数\n\nless 里有一些内置的函数，这里讲一下 lighten 和 darken 这两个内置函数。\n\n\nmain.less:\n\n```less\nbody {\n  background-color: lighten(#000, 10%);   // 让黑色变亮 10%\n  color: darken(#fff, 10%);               // 让白色变暗 10%\n}\n\n```\n\n\n将 上面的 main.less 编译为 main.css 之后，自动生成的代码如下：\n\n\nmain.css：\n\n\n```css\nbody {\n  background-color: #1a1a1a;\n  color: #e6e6e6;\n}\n\n```\n\n\n如果还有什么不懂的，可以看 api 文档，在上面的第二段附上了链接。\n\n## 在 index.html中直接引用 less.js\n\n- 做法一：写完 less文件后，将其编译为 css 文件，然后在代码中引用css文件。\n\n- 做法二：在代码中直接用引用 less 文件。\n\n产品上线后，当然是使用做法一，因为做法二会多出编译的时间。\n\n平时开发或演示demo的时候可以用做法二。\n\n\n这一段，我们讲一下做法二，其实是浏览器在本地在线地把 less 文件转换为 css 文件。\n\n（1）在 less 官网下载 less.js 文件：\n\n![](http://img.smyhvae.com/20180226_2131.png)\n\n把下载好的文件放在工程文件的lib文件夹里：\n\n![](http://img.smyhvae.com/20180226_2143.png)\n\n（2）在 index.html 中引入 less.js 和我们自己写的  main.less。位置如下：\n\n![](http://img.smyhvae.com/20180226_2145.png)\n\ncopy 红框那部分的代码如下：\n\n```html\n    <link rel=\"stylesheet/less\" href=\"../main.less\">\n```\n\n我们可以在打开的网页中，通过控制台看到效果：\n\n![](http://img.smyhvae.com/20180226_2150.png)\n\n注意，我们要在服务器中打开 html 文件，否则，看不到效果。\n\n这里也告诉了我们：\n\n> 不提倡将 less 引入页面，因为 less 需要编译，因此你就需要再引入一个less.js, 多了一个HTTP 请求，同时当浏览器禁用了 js 你的样式就不起作用了，less 编译应该在服务端或使用 grunt 自动编译。\n\n\n工程文件：（我引用的less.js版本是 2.5.3）\n\n- [2018-02-27-LessDemo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-02-27-LessDemo.rar)\n\n\n参考链接：\n\n- [知乎 | less文件如何引入页面](https://www.zhihu.com/question/26075208)\n\n\n\n## less 的编译\n\nless 的编译指的是将写好的 less 文件 生成为 css 文件。\n\nless 的编译，依赖于 NodeJS 环境。因此，我们需要先安装 NodeJS。\n\n\n### 1、安装 Node.js\n\n去 [Node.js](https://nodejs.org/zh-cn/)的官网下载安装包：\n\n![](http://img.smyhvae.com/20180226_2153.png)\n\n一路 next 进行安装。\n\n安装完成后，配置环境变量：\n\n在 path 变量中追加安装路径：`;C:\\Program Files\\nodejs`。重启资源管理器，即可生效。\n\nPS：我发现，我安装的 node.js v8.9.4 版本，已经自动添加了环境变量。\n\n在 cmd 命令行，输入`node.exe -v`，可以查看 node.js 的版本。\n\n### 2、安装 less 的编译环境\n\n将 [npm.zip](http://download.csdn.net/download/smyhvae/10260414) 解压，将解压后的文件拷贝到路径`C:\\Users\\smyhvae\\AppData\\Roaming\\npm`下：\n\n![](http://img.smyhvae.com/20180226_2212.png)\n\n然后重启资源管理器（或者重启电脑）。在 cmd 中输入 `lessc`，如果能看到下面的效果，说明 less 编译环境安装成功：\n\n![](http://img.smyhvae.com/20180226_2217.png)\n\n如果你用的是 linux 系统，可以输入下面的命令安装：\n\n```bash\n    npm install -g less\n```\n\n### 3、将 less 文件编译为 css 文件\n\n在 less 所在的路径下，输入 `lessc xxx.less`，即可编译成功。或者，如果输入 `lessc xxx.less > ..\\xx.css`，表示输出到指定路径。\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n"
  },
  {
    "path": "10-MySQL数据库/01-数据库的基础知识.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n## 数据库的概念\n\n**数据库**：database（DB），是一种存储数据的仓库。具有如下特性：\n\n- 数据库是根据数据结构组织、存储和管理数据。\n- 数据库能够长期、高效的管理和存储数据。\n- 数据库的目的就是能够存储（写）和提供（读）数据。\n\n## 数据库分类\n\n数据库分为两类：\n\n- **关系型数据库**：把复杂的数据结构归结为简单的二元关系，即二维表格形式（二维表）。注重数据存储的持久性。\n\n- **非关系型数据库**：没有具体模型的数据结构。英文简称 NoSQL（Not Only SQL )，意为\"不仅仅是SQL\"。注重数据读取的效率。\n\n我们具体来看看。\n\n### 1、关系型数据库\n\n**关系型数据库**：把复杂的数据结构归结为简单的二元关系，即二维表格形式（二维表）。\n\n关系型数据库有四层结构：\n\n- 数据库管理系统（DBMS）：DataBase Management System。\n\n- 数据库（DB）：数据存储的管理者。\n\n- 数据表（Table）：数据关系管理者。\n\n- 数据字段（Field）：实际数据存储者。\n\n常见的关系型数据库产品：\n\n- 大型：Oracle\n\n- 中型：MySQL、SQL Server\n\n- 小型：Sybase、Access\n\n\n### 2、非关系型数据库\n\n**非关系型数据库**：没有具体模型的数据结构。英文简称 NoSQL（Not Only SQL )，意为\"不仅仅是SQL\"。\n\n常见的非关系型数据库产品：MongoDB、Redis、Memcached。\n\n\n\n## SQL 的介绍\n\n**SQL**：全称 **Structured Query Language**，译为**结构化查询语言**。\n\n**SQL**：是一种针对关系型数据库的标准化编程语言，能够实现用户数据库的查询和程序设计。\n\n通俗来讲，**SQL 是关系型数据库的操作指令**。\n\n根据操作类型不同，SQL 可分为几类：\n\n\n* DQL：Data Query Language，数据查询语言，用于查询和检索数据\n* DML：Data Manipulation Language，数据操作语言，用于数据的写操作（增删改）\n* DDL：Data Definition Language，数据定义语言，用于创建数据结构\n* DCL：Data Control Language，数据控制语言，用于用户权限管理\n* TPL：Transaction Process Language，事务处理语言，辅助DML进行事务操作（因此也归属于DML）\n\n\n补充：\n\n- SQL 虽然是编程语言，但通常只用来进行数据管理，逻辑部分交给其他编程语言。\n\n- SQL 是针对关系型数据库的**通用语言**，所有关系型数据库都是基于SQL进行数据操作；而不同的数据库产品，在 SQL 操作指令上略有差异。\n\n\n\n## MySQL 的介绍\n\n### MySQL 数据库介绍\n\nMySQL 是很有名的 关系型数据库产品，由瑞典MySQL AB 公司开发，现在属于 Oracle 旗下产品。\n\nMySQL 在 2008 年被 Sun 公司以10亿美金所收购，而 Sun 公司在2009年被 Oracle 甲骨文公司收购。\n\nMySQL 开源免费。\n\n### MySQL 访问原理\n\nMySQL是一种C/S结构的软件，因此我们需要安装 MySQL 的客户端来访问远程的服务端。也就是说，数据是存放在服务器上的，客户端通过执行 sql 指令来操作服务端的数据。\n\n具体步骤是：\n\n（1）客户端通过 主机（host） + 端口号（port） 服务端。\n\n（2）输入 username 和 password 验证身份。\n\n（3）客户端和服务端连接成功，通过 sql 指令开始操作数据库。\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "10-MySQL数据库/02-MySQL的安装和Navicat软件使用.md",
    "content": "---\ntitle: 02-MySQL的安装和Navicat软件使用\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## MySQL 安装\n\n### MySQL（Mac版）\n\n### 步骤1、下载安装包并安装：\n\nMySQL 下载地址：https://dev.mysql.com/downloads/mysql/\n\n![](http://img.smyhvae.com/20200415_1707.png)\n\n\n![](http://img.smyhvae.com/20200415_1708.png)\n\n\n#### 步骤2、配置环境变量\n\n打开 `~/.bash_profile` 文件，在文件的末尾，添加如下内容，即可配置环境变量：\n\n```bash\n# mysql\nexport PATH=${PATH}:/usr/local/mysql/bin\n#快速启动、结束MySQL服务, 可以使用alias命令\nalias mysqlstart='sudo /usr/local/mysql/support-files/mysql.server start'\nalias mysqlstop='sudo /usr/local/mysql/support-files/mysql.server stop'\n```\n\n配置好环境变量后，在终端输入 `source ~/.bash_profile` 命令，让配置生效。\n\n在终端的任何位置，输入如下命令，即可进入 mysql 命令的执行窗口：\n\n```sql\nmysql -u root -p\n```\n\n参考链接：\n\n- [MySQL安装（Mac版）](https://juejin.im/post/5cc2a52ce51d456e7079f27f)\n\n### 步骤3、继续配置环境变量\n\n在 `~/.bash_profile` 中配置好环境变量后，发现每次重启终端后，配置都会失效，需要重新执行 `source ~/.bash_profile` 命令。\n\n原因是，zsh加载的是 `~/.zshrc`文件，而 `.zshrc` 文件中并没有定义任务环境变量。\n\n解决办法：打开 `~/.zshrc` 文件，在文件的末尾，添加如下内容即可：\n\n```bash\nsource ~/.bash_profile\n```\n\n参考链接：<https://blog.csdn.net/science_Lee/article/details/79214127>\n\n## Navicat Premium 软件初体验\n\nNavicat Premium 软件是一种数据库管理的GUI软件，采用可视化的方式来查看和操作数据库，非常方便。支持的数据库有： MySQL、MongoDB、SQL Server、SQLite、Oracle 及 PostgreSQL等。\n\n安装好 Navicat Premium 软件之后，我们来看看这个软件是怎么用的。\n\n### 新建表和数据\n\n1、新建连接：\n\n打开 Navicat Premium 软件，选择菜单栏「文件-新建连接-mysql」，然后选择如下配置，即可在本地新建一个数据库连接：\n\n![](http://img.smyhvae.com/20200416_1157.png)\n\n\n2、选中连接后，右键新建数据库：\n\n![](http://img.smyhvae.com/20200416_1159.png)\n\n![](http://img.smyhvae.com/20200416_1127.png)\n\n\n3、选中数据库之后，新建表 `qiangu_student_table`：\n\n![](http://img.smyhvae.com/20200416_1138.png)\n\n\n4、在表中添加字段：\n\n![](http://img.smyhvae.com/20200416_1202.png)\n\n\n\n5、字段建好后，开始在表中插入数据：\n\n![](http://img.smyhvae.com/20200416_1259.png)\n\n\n### 导入外部 sql 文件\n\n在 Navicat中，选中当前 database 之后，右键选择“运行sql文件”，即可导入外部sql文件。\n\n\n\n"
  },
  {
    "path": "10-MySQL数据库/03-MySQL的基本操作.md",
    "content": "---\ntitle: 03-MySQL的基本操作\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## SQL 的一些简单语法规则\n\n### 结束符\n\nSQL 指令需要语句结束符，默认是英文分号`;`。\n\n当然，还有另外两个结束符：\n\n- `\\g` 与英文分号`;`等效。\n\n- `\\G`：将查到的结构旋转90度变成纵向。\n\n\n### 反引号``\n\nSQL语句中如果用到了关键字或者保留字，需要使用反引号``（Tab键上面的符号）来包裹，让系统忽略。\n\n## MySQL 数据库的操作分类\n\n根据数据库的对象层级，可以将**SQL的基础操作分为四类**：\n\n- 数据库（DB）操作。\n\n- 数据表（Table）操作。\n\n- 数据字段（Field）操作。\n\n- 数据操作。\n\n下面来详细讲一讲。\n\n## 一、数据库（DB）的基本操作\n\n在终端的任何位置，输入如下命令，即可进入 mysql 命令的执行窗口：\n\n```sql\nmysql -u root -p\n```\n\n### 1、创建数据库\n\n**语法格式**：\n\n```mysql\ncreate database 数据库名称 [数据库选项];\n```\n\n**数据库名称的命名规范**：\n\n- 由数字、字母和下划线组成。\n- 不区分大小写。\n- 不能以数字开头。\n- 建议使用下划线法创建复杂的数据库名字。比如 `db_qianguyihao`。\n\n**举例**：\n\n创建一个名为 db_qianguyihao1 的数据库：\n\n```mysql\ncreate database db_qianguyihao1;\n```\n\n\n创建一个指定字符集的数据库：\n\n```mysql\ncreate database db_qianguyihao2 charset utf8MB4;\n```\n\n创建一个指定校对集的数据库：\n\n```mysql\ncreate database db_qianguyihao3 charset utf8MB4 collate utf8mb4_general_ci\n```\n\n\n### 2、查看数据库\n\n查看有哪些数据库：(显示所有的数据库列表)\n\n```mysql\nshow databases;\n```\n\n查看 `db_qianguyihao1` 这个数据库的具体创建指令是怎样的：\n\n```mysql\nshow create database db_qianguyihao1;\n```\n\n备注：由于系统会加工，所以看到的结果不一定是真实的创建指令。\n\n\n### 3、使用指定的数据库\n\n使用指定的数据库：（也可以理解成：进入指定的数据库）\n\n```mysql\n# 语法格式\nuse database_xxx;\n\n# 举例\nuse db_qianguyihao;\n```\n\n假设当前服务器连接中有很多个数据库（db_qianguyihao1、db_qianguyihao2），此时，我输入 `use db_qianguyihao2`则代表我想使用 `db_qianguyihao2` 这个数据库。\n\n\n### 4、修改数据库的参数\n\n我们一般很少修改数据库的名称，一般是去修改数据库的一些选项，比如：\n\n- 修改字符集\n\n- 修改校对集\n\n**语法格式**：\n\n```mysql\nalter database 数据库名称 [库选项]\n```\n\n举例1、修改数据库的字符集为gbk：\n\n```mysql\nalter database db_qianguyihao1 charset gbk;\n```\n\n举例2、修改数据库的校对集：\n\n```sql\nalter database db_qianguyihao2 charset gbk collate gbk_chinese_ci;\n```\n\n备注：因为校对集是和字符集有关的，所以上方指令是在修改字符集的同时，修改校对集。\n\n\n\n### 5、删除指定的数据库\n\n**语法格式**：\n\n```mysql\ndrop database 数据库名称;\n```\n\n备注：删除数据库时，会清空当前数据库里的所有数据表，所以删除数据库的操作一定要谨慎。\n\n\n## 二、数据表（Table）的基本操作\n\n注意，我们最好先通过 `use xxx_database` 命令进入指定的数据库（DB），然后在当前数据库下，进行数据表（Table）的操作。\n\n### 1、创建数据表\n\n**语法格式**：\n\n```sql\ncreate table [数据库名].[表名] (\n\t字段名1 字段类型,\n\t...\n    ...\n    字段名2 字段类型\n) 表选项;\n```\n\n**举例**：\n\n1、在当前数据库中创建数据表 `table_qiangu1`，并新增**主键** id 字段：\n\n```sql\nCREATE TABLE table_qiangu1 (\n  id int NOT NULL AUTO_INCREMENT PRIMARY KEY\n);\n```\n\n2、在当前数据库中创建数据表 `t_student1`，并新增 name、age这连个字段：\n\n```sql\ncreate table t_student1(\n    name varchar(255),\n    age int\n);\n\n\n```\n\n3、在指定的数据库 `db_2` 中创建数据表 `t_student2`：\n\n\n```sql\ncreate table db_2.t_student2(\n    name varchar(255),\n    age int\n);\n```\n\n4、在当前数据库中创建数据表 `t_student3`（含表选项）：\n\n```sql\ncreate table t_student3(\n    name varchar(255),\n    age int\n)engine Innodb charset utf8MB4;\n```\n\n举例4中的代码涉及到存储引擎，这里解释一下：\n\n**存储引擎**是指数据存储和管理的方式，MySQL中提供了多种存储引擎，一般使用默认存储引擎 InnoDB。\n\n- InnoDB：默认存储引擎；支持事务处理和外键；数据统一管理。\n\n- MyIsam：不支持事务和外键；数据、表结构、索引独立管理；MySQL5.6以后不再维护。\n\n6、扩展：如果想创建一个与已有表一样的数据表，MySQL提供了一种便捷的复制模式\n\n### 2、复制数据表\n\n如果想创建一个与已有表一样的数据表，MySQL提供了一种便捷的**复制**模式。\n\n**语法格式**：（复制现有的表 `table_xx1` 到 `table_xx2`）\n\n```sql\ncreate table table_xx1 like 数据库名.table_xx2;\n```\n\n注意，这种复制模式，`table_xx2` 只会复制表 `table_xx1` 中的字段，不会复制表`table_xx1`中的数据。\n\n**举例**：\n\n\n```sql\n# 在当前数据库下，复制现有的表`t_qianguyihao1` 到表 `t_qianguyihao2`\ncreate table t_qianguyihao1 like t_qianguyihao2;\n\n# 复制现有的表`t_qianguyihao1` 到表 `t_qianguyihao2`，是复制到 `db2`这个数据库中\ncreate table t_qianguyihao1 like db2.t_qianguyihao2;\n```\n\n\n### 3、显示数据表的名称\n\n在当前数据库下，显示所有的数据表：\n\n```sql\nshow tables;\n```\n\n在指定的数据库中，显示所有的数据表：\n\n```sql\nshow tables from db_qianguyihao1;\n```\n\n显示数据表的创建指令：(查看 `t_qianguyihao1` 这个数据表的具体创建指令是怎样的)\n\n```mysql\nshow create table t_qianguyihao1; # 备注：由于系统会加工，所以看到的结果不一定是真实的创建指令。\n```\n\n\n\n### 4、查询（查找）数据表的名称\n\n> 根据 表名称 查询数据表，也可以理解成：按条件显示部分数据表。\n\n根据数据表的**表名称**查找数据表时，需要用到关键词`like`，而且还要涉及到两个符号：\n\n- `%` 表示匹配任意**多个字符**。\n\n- `_` 表示匹配任意**一个字符**（固定位置）。\n\n上面这两个模糊查询的符号，大家要牢记。我们来看看具体的例子。\n\n语法举例：\n\n```mysql\nshow tables like '%like_';\t# _表示匹配一个字符（固定位置），%表示匹配N个字符\n```\n\n**`%` 符号举例**：\n\n```sql\n# 查询表名称中，包含 “qiangu” 这个关键字的表（“qiangu”这个关键字的前后可能都有内容）\nshow tables like '%qiangu%';\n\n# 查询表名称以“qiangu”开头的表（这个命令应该很实用）\nshow tables like 'qiangu%';\n```\n\n**`_`符号举例**：\n\n```sql\n# 根据 表名称 来查询表，查询条件是：表名称以“qiangu”开头，而且要确保 qiangu 的后面有三个字符（因为我在 qiangu 的后面写了三个下划线）。\nshow tables like 'qiangu___';\n```\n\n\n### 5、desc：查看数据表的表结构\n\n查看数据表的表结构，就是**查看这张表中定义了哪些字段**，以及这些字段是如何定义的。通过这种方式，我们可以清晰地了解数据的存储形式。\n\n项目开发中，领导在检查我们的工作时，首先看的就是我们的表中定义了哪些字段。所以说，这种方式，还是很实用的。\n\n**语法格式**：\n\n```sql\n# 方式1\ndesc 表名称;\n\n# 方式2\ndescribe 表名称;\n\n# 方式3\nshow columns from 表名称;\n```\n\n上面的三种方式，效果都一样，三选一即可。\n\n\n### 6、修改数据表的表名称和表选项\n\n**修改数据表的表名称**：\n\n在当前数据库下，修改数据表的表名称：\n\n```sql\nrename table 原表名 to 新表名;\n```\n\n指定某个数据库，然后修改数据表的表名称：\n\n\n```sql\nrename table 数据库名.原表名 to 数据库名.新表名;\n```\n\n**修改数据表的表名称**：\n\n```sql\nalter table table1 charset gbk;\n```\n\n### 7、删除数据表\n\n语法格式：\n\n```sql\ndrop table 数据表名称;\n\n```\n\n\n## 三、字段（Field）的基本操作\n\n数据表 table 创建好了之后，我们就可以开始在这张表中新增字段了。\n\n### 1、新增字段\n\n**语法格式**：\n\n```sql\nalter table 表名 add [column] 字段名 字段类型 [字段属性] [字段位置];\n```\n\n注意事项：\n\n- 新增字段时，必须制指定字段类型。\n\n- [column]、 [字段属性]、[字段位置] 这几个都是选填，其他是必填。\n\n- 追加字段时，这个字段的顺序默认排在最后。\n\n**举例**：\n\n新增字段 `name`:\n\n```sql\nalter table table_qiangu1 add name varchar(255);\n\n```\n\n新增字段 `age`：\n\n```sql\nalter table table_qiangu1 add age int;\n```\n\n\n### 2、新增字段时，设置字段的位置（顺序）\n\n在新增字段时，它的顺序是默认放在最后面的，当然，我们也可以人工指定它的顺序。\n\n在修改字段的位置时，我们可以用到下面这两个关键字：\n\n- `first` 放到最前面\n\n- `after` 放到某个字段的后面\n\n**语法格式**：\n\n```sql\nalter table 表名 add 新字段名 字段类型 字段位置;\n```\n\n\n**举例1**：\n\n在 `name`字段的后面，新增一个 `sex` 字段：\n\n```sql\nalter table t_qiangu1 add sex varchar(255) default null comment '性别' after name;\n```\n\n注意，上方举例中，如果是新建 varchar 类型的字段，一定要指定 varchar 的长度（比如255），否则报错。\n\n**举例2**：\n\n\n新增一个 `id` 字段，放到最前面：\n\n```sql\nalter table t_qiangu1 add id int first;\n```\n\n\n\n\n### 3、change：修改现有字段的字段名\n\n> 修改现有字段的字段名，是通过 change 关键字，不是通过 modify 关键字（后者会报错，执行失败）。\n\n**语法格式**：\n\n```sql\n# 格式1（精简版）\nalter table 表名 change 原字段名 新字段名 字段类型;\n\n# 格式2（完整版）\nalter table 表名 change 原字段名 新字段名 字段类型 [字段属性] [位置];\n```\n\n注意：\n\n- 修改字段名时，一定要设置新字段的字段类型。\n\n- 虽然 change 关键字也可以修改现有字段的字段属性、字段位置，但我们一般是通过 modify 关键字来做（下面会讲）。\n\n\n**举例**：\n\n修改字段名 `sex` 为 `sexy`：\n\n```sql\nalter table t_qiangu2 change sex sexy varchar(255);\n```\n\n### 4、modify：修改现有字段的的字段类型、字段位置、字段属性\n\n**语法格式**：\n\n```sql\nalter table 表名 modify 现有字段的字段名 现有字段的字段类型 [字段属性] [位置]；\n```\n\n**举例1**、针对现有的字段 `name` 和 `age`，更换这两个字段的顺序：\n\n```sql\n# 注意，这里的 age 后面一定要跟上它的字段类型，否则执行失败\nalter table t_qiangu1 modify age int after name;\n```\n\n### 修改字段的默认值\n\n```sql\n# 若本身存在默认值，则先删除\nalter table 表名 alter column 字段名 drop default;\n\n# 若本身不存在则可以直接设定\nalter table 表名 alter column 字段名 set default 默认值;\n```\n### 5、删除字段\n\n>  删除字段的同时，会删除字段对应的数据。删除字段的操作不可逆，请谨慎操作。\n\n语法格式：\n\n```sql\nalter table 表名 drop 字段名;\n```\n\n举例：（删除 t_qiangu1 表中的 age 这个字段）\n\n```sql\nalter table t_qiangu1 drop age;\n```\n\n\n## 四、数据的基本操作\n\n### 1、新增数据\n\n**方式1、全字段插入**：\n\n语法格式：\n\n```sql\ninsert into 表名 values(值1, 值2, ... 最后一个值);\n```\n\n解释：\n\n- 值的顺序必须与所有字段的顺序一致。\n- 值的数据类型也必须与字段定义的数据类型一致。\n\n举例（给 t_qiangu1 这个表中插入一条完整的数据）：\n\n```sql\ninsert into t_qiangu1 values(3, 'qianguyihao', 28);\n```\n\n**方式1、部分字段插入**：\n\n语法格式：\n\n```sql\ninsert into 表名 (字段1, 字段2, 字段3) values(值1, 值2, 值3);\n```\n\n解释：\n\n-字段的顺序可以随意，但值的顺序必须要与前面的字段顺序**一一对应**，数据类型也要一致。\n\n举例（给 t_qiangu1 这个表中的指定字段插入数据）：\n\n```sql\ninsert into t_qiangu1 (id, name) values(4, 'xusong');\n```\n\n### 2、查询数据\n\n> 查询数据的操作，占sql日常操作的95%以上。\n\n**语法格式**：\n\n```sql\nselect xxx from 表名;\n```\n\n**举例**：\n\n查询表中的所有数据：\n\n```sql\nselect * from t_qiangu1;\n```\n\n查询表中 name、age 这两个字段的数据：\n\n```sql\nselect name, age from t_qiangu2;\n```\n\n查询表中 id=2 的数据：\n\n```sql\nselect * from t_qiangu3 where id = 2;\n```\n\n### 3、修改数据\n\n\n**语法格式**：\n\n```sql\nupdate 表名 set (字段1 = 新值1, 字段2 = 新值2) [where 条件筛选];\n```\n\n**解释**：\n\n- 我们通常是结合 where 条件语句来修改数据。\n\n- **修改数据之前，要先保证表里面有数据**。如果这张表是空表，那么，执行这个命令后，等于没执行。\n\n\n**举例**：\n\n将表中，name 这个字段的值全部修改为`千古壹号`：\n\n```sql\nupdate t_qiangu1 set name = '千古壹号';\n```\n\nid = 3 的这条记录中，修改 name 和 age 这两个字段的值：\n\n```sql\nupdate t_qiangu1 set name = '许嵩', age = '34' where id = 3;\n```\n\n### 4、删除数据\n\n> 删除字段的操作不可逆，请谨慎操作。\n\n**语法格式**：\n\n```sql\ndelete from 表名 [where 条件];\n```\n\n**解释**：\n\n- 执行删除操作之后，匹配到的**整条记录**，都会删除。\n\n- **删除数据之前，要先保证表里面有数据**。如果这张表是空表，那么，执行这个命令后，等于没执行。\n\n**举例**：\n\n删除表中`id = 2`的记录：\n\n```sql\ndelete from t_qiangu1 where id = 2;\n```\n"
  },
  {
    "path": "10-MySQL数据库/04-MySQL字段的数据类型.md",
    "content": "---\ntitle: 04-MySQL字段的数据类型\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 前言\n\nMySQL 中的字段，主要有四种数据类型：\n\n- 整型（整数）\n\n- 小数\n\n- 字符串类型\n\n- 时间日期类型\n\n下面来详细讲一讲。\n\n## 整数类型\n\n### 整数类型的分类\n\nMySQL中，整型有五种：\n\n* 迷你整型：tinyint，使用**1个字节**存储整数，最多存储256个整数（-128~127）。\n\n* 短整型：smallint，使用**2个字节**存储整数。\n\n* 中整型：mediumint，使用**3个字节**存储整数。\n\n* 标准整型：int，使用**4个字节**存储整数。\n\n* 大整型：bigint，使用**8个字节**存储。\n\n**强调**：\n\n（1）如果你不知道用哪一种，或者懒得计算，那就用标准整型 `int`吧，这个用的最多。\n\n（2）整型在 MySQL 中默认是有符号的，即有正负；无符号需要使用 `unsigned` 修饰整型，代表正整数。\n\n**举例**：\n\n在指定的表中新增 age 字段，要求 age 是正整数：\n\n```sql\nalter table table_qiangu1 add age int unsigned;\n```\n\n### 设计思路\n\n如果需要新建整型的字段，设计思路如下：\n\n（1）确定需要存储的数据是整数。\n\n（2）预估整数的范围，选择合适的整数类型。\n\n（3）确定这个整数是否需要包含负数。\n\n### 整数类型的取值范围\n\ntodo。参考链接：<https://blog.csdn.net/slyjit/article/details/54290486>\n\n\n### 整数类型的显示宽度、零填充\n\n> 我们在很多设计表中，可能会看到比如 `int(11)`这种数据类型，这里面的 `11`代表的就是`显示宽度`。\n\n所谓的**显示宽度**，其实就是显示的时候，看到的最少数字个数。\n\n比如 int(2) ，表示不管你的数值是多少，最少可以看到两个数字。假如你存的数值是9，没有满两位，就会在前面补零，显示为`09`；假如你的数值是120，超过了显示宽度，则直接显示原始值，不会做**零填充**。\n\n**显示宽度的注意事项**：\n\n- 显示宽度只适用于 MySQL 的整数类型。\n\n- 显示宽度只是指明 MySQL 整数类型最少显示的数字个数（可以通过desc查看表字段显示）。\n\n- **显示宽度只是在显示的时候改变数值的样式，不会对原本的值进行更改**。\n\n- 显示宽度和数值类型的取值范围无关。例如int(10) 他的取值范围依然是(-2 147 483 648，2 147 483 647)。\n\n**零填充的注意事项**：\n\n- 要想让显示宽度自动进行**零填充**，必须要配合 `ZEROFILL`这个关键字一起使用。\n\n- **零填充只能针对正整数**，也就是说，`ZEROFILL` 要求整型为无符号型。\n\n\n\n**举例**：\n\n1、新建一张表，然后在这张表中新增 num1 字段，要求 num1 显示3位，不够3位的自动进行零填充：\n\n```sql\n# 新建一张表\nCREATE TABLE table_qiangu1 (\n  id int NOT NULL AUTO_INCREMENT PRIMARY KEY\n);\n\n# 显示宽度有效（正确写法）\nalter table table_qiangu1 add num1 int(3) zerofill;\n\n# 对比：普通写法，显示宽度无效\nalter table table_qiangu1 add num2 int(3);\n\n# 对比：普通写法\nalter table table_qiangu1 add num3 int;\n```\n\n上述命令中，如果把 `zerofill` 这个关键字去掉，是达不到显示宽度的效果的。执行完上述命令后，我们执行 `desc table_qiangu1` 命令，对比一下 num1、num2、num3 的字段结构就知道了：\n\n![](https://img.smyhvae.com/20200423_1050.png)\n\n上方截图可以看到，只有 num1 才有显示宽度，它可以进行零填充，num2、num3不行。我们往表中插入整数 `6`，然后看看显示结果，就一目了然：\n\n![](https://img.smyhvae.com/20200423_1055.png)\n\n\n参考链接：[MySql数据库 数值类型的显示宽度](https://juejin.im/post/5b24a2c251882574d73c6f82)\n\n\n## 小数\n\nMySQL 中的小数分为两大类：\n\n\n浮点型的数据分为两种：\n\n- 单精度：float，使用4个字节存储，精度范围为6-7位有效数字。\n\n- 双精度：double，使用8个字节存储，精度范围为14-15位有效数字。\n\n注意：\n\n- 浮点数超过精度范围会自动进行四舍五入。\n\n- 精度可以指定整数和小数部分。比如\n\n\n\n\n\n\n\n"
  },
  {
    "path": "10-MySQL数据库/05-MySQL数据库的常用命令.md",
    "content": "---\ntitle: 05-MySQL数据库的常用命令\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## MySQL 的一些简单命令\n\n我们可以在 Navicat Premium 软件中，创建数据库和表，然后输入查询命令来查询数据。选择菜单栏「查询->新建查询->输入 sql 命令->运行」即可，效果如下：\n\n![](https://img.smyhvae.com/20200417_1750.png)\n\n我们还可以直接在终端输入命令行来操作。\n\n注意，在 Mac 终端执行 sql 命令时，命令的末尾必须加上`;`（英文格式的分号）。效果如下：\n\n![](https://img.smyhvae.com/20200417_1700.png)\n\nMySQL 命令行的一些简单命令如下。\n\n**以 root 身份进入命令行**：\n\n```\nmysql -u root -p\n```\n\n**查看有哪些数据库**：\n\n```sql\nshow databases;\n```\n\n**选择进入指定的数据库**：\n\n```sql\nuse xxx_database;\n\n# 举例\nuse qianguyihao_database;\n```\n\n**在当前数据库中，查看有哪些表**：\n\n```sql\nshow tables;\n```\n\n**在当前数据库中，查询指定表的全部数据**：\n\n```sql\nSELECT * FROM xxx_table;\n\n# 举例\nSELECT * FROM qianguyihao_student_table\n```\n\n**删除指定的表**：\n\n```sql\ndrop table xxx;\n\n# 举例\ndrop table qianguyihao_student_table;\n```\n\n**删除指定的数据库**：\n\n```sql\ndrop database qianguyihao_student_table;\n```\n\n**创建一个数据库**：\n\n```sql\ncreate database qianguyihao_database2;\n```\n\n## where 条件查询\n\n使用 `where` 子句可以对表中的数据进行筛选，结果为 true 的行会出现在查询结果中。\n\n语法格式如下：\n\n```sql\nSELECT * FROM 表名 where 条件;\n```\n\n上面的语法格式中，`条件` 具体要怎么写呢？这个可能有很多种情况。我们继续往下看。\n\n### 比较运算符\n\n- `=` 等于\n- `>` 大于\n- `>=` 大于等于\n- `<` 小于\n- `<=` 小于等于\n- `!=`：不等于\n- `age > 20`：查询 age 大于 30 的数据\n\n**举例**：\n\n```sql\n# 查询 age 大于 20 的数据\nSELECT * FROM qianguyihao_table WHERE age > 20;\n```\n\n### 逻辑运算符\n\n- AND\n\n- OR\n\n- NOT\n\n**举例**：\n\n```sql\n# 查询 age 在20至30之间的数据\nSELECT * FROM qianguyihao_table WHERE age BETWEEN 20 AND 30;\n\n```\n\n### 范围查询\n\n- `in` 表示在一个非连续的范围内。\n\n- `between ... and ...` 表示在一个连续的范围内\n\n举例：\n\n```sql\n# 查询 name 为 千古壹号 或者 许嵩的数据\nSELECT * FROM qianguyihao_table WHERE name IN ['千古壹号', '许嵩'];\n\nSELECT * FROM qianguyihao_table WHERE age BETWEEN 20 AND 30;\n```\n\n### 模糊查询\n\n- `like`\n  - `%` 表示任意多个任意字符\n  - `_` 表示一个任意字符\n\n`%` 符号举例：\n\n```sql\n# 查询标题中包含“前端”这两个字的数据（“前端”这两个字的前后可能都有内容）\nselect * from qianguyihao_table where `title` like \"%前端%\";\n\n# 查询标题以“前端”开头的数据\nselect * from qianguyihao_table where `title` like \"前端%\";\n\n```\n\n`_`符号举例：\n\n```sql\n# 查询标题，查询条件是：标题中至少有五个字符，而且，这五个字符中，前两个字符一定是“千古”开头的。\nSELECT * FROM qianguyihao_table WHERE `title` LIKE \"千古___\";\n```\n\n### NULL 的判断\n\n- `is null` 判断为空\n\n- `is not null` 判断为非空\n\n注意，`is null` 和**空字符串**`\"\"` 是有区别的。学过 js 基础之后，你应该知道：空字符串并非 null，只不过是里面的值为空而已；空字符串也是会占有内存空间的。\n\n举例：\n\n```sql\nselect * from qianguyihao_table where name is not NULL;\n\n```\n\n## join 联表查询\n\n### 联表查询命令\n\n- `tableA inner join tableB`：表 A 与表 B 匹配的行会出现在结果中。\n\n- `tableA left join tableB`：表 A 与表 B 匹配的行会出现在结果中。表 A 中独有的数据，对应表 B 中用 null 填充。\n\n- `tableA right join tableB`：表 A 与表 B 匹配的行会出现在结果中。表 B 中独有的数据，对应表 A 中用 null 填充。\n\n光是这样看，不好理解，我们来举个例子。\n\n### 举例\n\n现在有下面这两张表：作者表 author、图书表 book。\n\n**表 1**、作者表 author：\n\n| id  | authorId | authorName |\n| :-- | :------- | :--------- |\n| 1   | 11       | 王小波     |\n| 2   | 12       | 吴军       |\n| 3   | 88       | 千古壹号   |\n\n**表 2**、图书表 book：\n\n| id  | bookId | bookName   | authorId |\n| :-- | :----- | :--------- | -------- |\n| 1   | 201    | 黄金时代   | 11       |\n| 2   | 202    | 白银时代   | 11       |\n| 3   | 203    | 青铜时代   | 11       |\n| 4   | 204    | 浪潮之巅   | 12       |\n| 5   | 205    | 硅谷之谜   | 12       |\n| 6   | 206    | 数学之美   | 12       |\n| 7   | 777    | 设计心理学 | 99       |\n\n注意，表2中的每本图书都有对应的 authorId，这个 authorId 就是对应表1中的 authorId。**通过 authorId 把两张表关联起来**。\n\n通过联表查询上面的两张表，我们来对比一下查询结果。\n\n**情况 0**：（inner join）\n\n```sql\nSELECT * FROM author INNER JOIN book;\n```\n\n查询结果：\n\n![](https://img.smyhvae.com/20200418_2300.png)\n\n\n上面这种查询，没有意义，因为没有加任何查询条件。\n\n**情况 1**：（inner join）\n\n```sql\nSELECT * FROM author INNER JOIN book ON author.authorId = book.authorId;\n```\n\n查询结果：\n\n![](https://img.smyhvae.com/20200418_2305.png)\n\n上面这行命令，跟下面这行命令等价：\n\n```sql\nSELECT * FROM author, book WHERE author.authorId = book.authorId;\n```\n\n**情况 2**：（left join）\n\n```sql\nSELECT * FROM author LEFT JOIN book ON author.authorId = book.authorId;\n```\n\n查询结果：\n\n![](https://img.smyhvae.com/20200418_2310.png)\n\n**情况 3**：（right join）\n\n```sql\nSELECT * FROM author RIGHT JOIN book ON author.authorId = book.authorId;\n```\n\n查询结果：\n\n![](https://img.smyhvae.com/20200418_2315.png)\n\n### 参考链接\n\n- [Mysql 联表查询](https://blog.csdn.net/qmhball/article/details/8000003)\n\n\n## 自关联查询\n\n涉及到层级关系时可以用自关联查询。\n\n\n## 子查询\n\n当一个查询结果是另一个查询的条件时，这个查询称之为子查询。\n\n\n\n\n"
  },
  {
    "path": "10-MySQL数据库/MySQL设计三大范式.md",
    "content": "---\ntitle: 01-Bootstrap入门\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 前言\n\n范式即规范。MySQL 范式的作用是：\n\n- 让我们建的表更佳简洁和高效。\n\n- 让功能独立化，避免耦合。\n\n## MySQL 设计三大范式\n\n### 第一范式（1NF）：原子性\n\n表的每一列具有原子性，不可再分。\n\n### 第二范式：唯一性\n\n第二范式是建立在第一范式基础上的；外要求所有非主键字段必须完全依赖主键，而不是部分依赖。\n\n\n### 第三范式\n\n第三范式是建立在第二范式基础上的；且要求没有传递依赖。\n\n\n## 参考链接\n\n- [MySql--数据库设计三范式](https://www.jianshu.com/p/3e97c2a1687b)\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "10-MySQL数据库/事务.md",
    "content": "---\ntitle: 01-Bootstrap入门\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 事务语句\n\n- 开启：begin\n\n- 提交：commit\n\n- 回滚：rollback\n\n\n\n"
  },
  {
    "path": "11-Node.js/01-Node.js介绍.md",
    "content": "---\ntitle: 01-Node.js介绍\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## todo\n\n-   rpc 和 Node.js 的关系\n\n-   [《吊打面试官》系列 Node.js 全栈秒杀系统](https://mp.weixin.qq.com/s/uWeAsJ-P253je15A49uKIQ)\n\n## 前言\n\nNode 的重要性已经不言而喻，很多互联网公司都已经有大量的高性能系统运行在 Node 之上。Node 凭借其单线程、异步等举措实现了极高的性能基准。此外，目前最为流行的 Web 开发模式是前后端分离的形式，即前端开发者与后端开发者在自己喜欢的 IDE 上独立进行开发，然后通过 HTTP 或是 RPC 等方式实现数据与流程的交互。这种开发模式在 Node 的强大功能的引领下变得越来越高效，也越来越受到各个互联网公司的青睐。\n\n### 前端同学为什么要学习后端/后端同学为什么要学习前端\n\n-   了解前后端交互流程。\n\n-   前端同学能够和后台开发的程序员更佳紧密地结合、更顺畅地沟通。\n\n-   当网站的业务逻辑需要前置时，前端人员需要学习一些后台开发的技术，以完成相应的任务；；反过来也一样。\n\n-   拓宽知识视野和技术栈，能够站在全局的角度审视整个项目。\n\n### 前端同学为什么要学 Node.js\n\n1、Node.js 使用 JavaScript 语言开发服务器端应用，**便于前端同学上手**（一些公司甚至要求前端工程师掌握 Node.js 开发）。\n\n2、实现了前后端的语法统一，**有利于和前端代码整合**，甚至共用部分代码。\n\n比如说，针对接口返回的各种字段，前后端都必须要做校验。此时，如果用 Node.js 来做后台开发的话，前后端可以共用校验的代码。\n\n3、Node.js 性能高、生态系统活跃，提供了大量的开源库。\n\n4、Jeff Atwood 在 2007 年提出了著名的 Atwood 定律：**任何能够用 JavaScript 实现的应用系统，最终都必将用 JavaScript 实现**。 Jeff Atwood 是谁不重要（他是 Stack Overflow 网站的联合创始人），重要的是这条定律。\n\n### 后端同学为什么要学 Node.js\n\n因为前端同学在学 Node.js。\n\n## 什么是 Node.js\n\n### 官方定义\n\n[Node.js](https://nodejs.org/zh-cn/) 是一个基于 **Chrome V8 引擎**的 JavaScript 运行环境。Node.js 使用了一个**事件驱动**、**非阻塞式 I/O**的模型，使其轻量又高效。Node.js 的包管理工具 npm 是全球最大的开源库生态系统。\n\nNode.js 不是一门语言，也不是 JavaScript 的框架，也不是像Nginx一样的Web服务器 ，**Node.js 是 JavaScript 在服务器端的运行环境（平台）**。\n\n\n### Node.js 的组成\n\n在 Node.js 里运行 JavaScript，跟在 Chrome 里运行 JavaScript 有什么不同？\n\n二者采用的是同样的 JS 引擎。在 Node.js 里写 JS，和在前端写 JS，几乎没有不同。在写法上的区别在于：Node.js 没有浏览器、页面标签相关的 API，但是新增了一些 Node.js 相关的 API。通俗来说，对于开发者而言，在前端写 JS 是用于控制浏览器；而 Node.js 环境写 JS 可以控制整个计算机。\n\n我们知道，JavaScript 的组成分为三个部分：\n\n-   ECMAScript\n\n-   DOM：标签元素相关的API\n\n-   BOM：浏览器相关的API\n\nECMAScript 是 JS 的语法；DOM 和 BOM 浏览器端为 JS 提供的 API。\n\n而 Node.js 的组成分为：\n\n-   **ECMAScript**。ECMAScript 的所有语法在 Node 环境中都可以使用。\n\n-   **Node 环境**提供的一些**附加 API**(包括文件、网络等相关的 API)。\n\n如下图所示：\n\n![](http://img.smyhvae.com/20200409_1545.png)\n\n### 补充\n\n与 PHP、JSP、Python、Perl、Ruby 的“既是语言，也是平台”不同，Node.js 的使用 JavaScript 进行编程，运行在 Chrome 的 V8 引擎上。\n\n与 PHP、JSP 等相比（PHP、JSP、.net 都需要运行在服务器程序上，Apache、Nginx、Tomcat、IIS。\n），Node.js 跳过了 Apache、Nginx、IIS 等 HTTP 服务器，它自己不用建设在任何服务器软件之上。Node.js 的许多设计理念与经典架构（LAMP = Linux + Apache + MySQL + PHP）有着很大的不同，可以提供强大的伸缩能力。Node.js 没有 web 容器。\n\nJS 语言非常灵活，使得它在严谨性方面不如 Java 等传统的静态语言。JS 是一门动态语言，而且融合了面向对象和函数式编程这两种编程范式。\n\n随着 ES6、ES7 等 JS 语法规范的出现，以及浏览器对这些规范的支持，使得我们可以用更为现代化的 JS 语言特性，来编写现代化的应用。\n\n## Node.js 的架构和依赖\n\nNode.js 的架构如下：\n\n![](http://img.smyhvae.com/20180301_1540.png)\n\nNode.js 内部采用 Google Chrome 的 V8 引擎，作为 JavaScript 语言解释器；同时结合自行开发的 libuv 库，**扩展了 JS 在后端的能力（比如 I/O 操作、文件读写、数据库操作等）**。使得 JS 既可以在前端进行 DOM 操作（浏览器前端），又可以在后端调用操作系统资源，是目前最简单的全栈式语言。\n\n其次，Node 生态系统活跃，提供了大量的开源库，使得 JavaScript 语言能与操作系统进行更多的交互。\n\n### Node.js 运行环境的核心：V8 引擎 和 libuv 库\n\nNode.js 是 JavaScript 在服务器端的运行环境，在这个意义上，Node.js 的地位其实就是 JavaScript 在服务器端的虚拟机，类似于 Java 语言中的 Java 虚拟机。\n\n-   [V8 引擎](https://v8.dev/) ：编译和执行 JS 代码、管理内存、垃圾回收。V8 给 JS 提供了运行环境，可以说是 JS 的虚拟机。V8 引擎本身是用 C++ 写的。\n\n-   [libuv](https://zh.wikipedia.org/wiki/Libuv)： libuv 是一个专注于异步 I/O 的跨平台类库，目前主要在 Node.js 上使用。它是 Node.js 最初的作者 Ryan Dahl 为 Node.js 写的底层类库，也可以称之为虚拟机。libuv 本身是用 C 写的。\n\n### Java 虚拟机和 V8 引擎，是由同一人开发\n\nChrome 浏览器成功的背后，离不开 JS 的 V8 引擎。作为虚拟机，V8 的性能表现优异，它的开发者是 Lars Bak。在 Lars 的工作履历里，绝大部分都是与虚拟机相关的工作。在开发 V8 之前，他曾经在 Sun 公司工作，担任 HotSpot 团队的技术领导，主要致力于开发高性能的 Java 虚拟机。在这之前，他也曾为 Self、Smalltalk 语言开发过高性能虚拟机。这些无与伦比的经历让 V8 一出世就超越了当时所有的 JS 虚拟机。\n\n![](http://img.smyhvae.com/20200617_1120.png)\n\nV8 的性能优势使得用 JavaScript 写高性能后台服务程序成为可能。在这样的契机下，Ryan Dahl 选择了 JavaScript，选择了 V8，在事件驱动、非阻塞 I/O 模型的设计下实现了 Node。\n\n### V8 的内存限制\n\n在一般的后端开发语言中，在基本的内存使用上没有什么限制，然而在 Node 中通过 JavaScript 使用内存时就会发现只能使用部分内存（64 位系统下约为 1.4GB，32 位系统下约为 0.7GB）。在这样的限制下，将会导致 Node 无法直接操作大内存对象。\n\n造成这个问题的主要原因在于 Node 基于 V8 构建，所以在 Node 中使用的 JavaScript 对象基本上都是通过 V8 自己的方式来进行分配和管理的。V8 的这套内存管理机制在浏览器的应用场景下使用起来绰绰有余，足以胜任前端页面中的所有需求。但在 Node 中，这却限制了开发者随心所欲使用大内存的想法。\n\n## Node 的发展历史\n\n-   2008 年左右，随着 Ajax 的逐渐普及，Web 开发逐渐走向复杂化，系统化；\n\n-   Node.js 诞生于 2009 年，由 Joyent 的员工 Ryan Dahl 开发而成。2009 年 5 月，Ryan Dahl 在 GitHub 中开源了 Node 的最初版本，同年 11 月，在 JSConf 大会上展示了 Node 项目；\n\n-   2010 年 1 月，NPM 包管理工具诞生，使得程序员能够更方便地发布和分享 Node.js 的第三方库。\n\n-   Node.js 最初只支持 Linux 和 Mac OS 操作系统。2011 年 7 月，微软参与合作，Node.js 终于支持了 Windows 平台。PS：不过，node 的生产环境基本是在 Linux 下。\n\n-   目前官网最新版本已经更新到 14.x.x 版本，最新稳定的是 12.18.0。\n\n据 Node.js 创始人 Ryan Dahl 回忆，他最初希望采用 Ruby，但是 Ruby 的虚拟机效率不行。\n\n注意：是 Node 选择了 JavaScript，不是 JavaScript 发展出来了一个 Node。\n\n## Node.js 的应用\n\nNode.js 拥有强大的开发者社区，现在已经发展出比较成熟的技术体系，以及庞大的生态。它被广泛地应用在 Web 服务、开发工作流、客户端应用等诸多领域。其中，在 **Web 服务**领域，业界对 Node.js 的接受程度最高。\n\n### 1、BFF 中间层\n\nBFF，即 Backend For Frontend（服务于前端的后端）。玉伯在《[从前端技术进化到体验科技](https://mp.weixin.qq.com/s/IYddaaw2ps1wR2VT1dZWPg)》这篇文章中点出了 BFF 层的概念：\n\n> BFF 模式下，整体分工很清晰，**后端通过 Java/C++ 等语言负责服务实现，理想情况下给前端提供的是基于领域模型的 RPC 接口，前端则在 BFF 层直接调用服务端 RPC 接口拿到数据**，按需加工消费数据，并实现人机交互。基于 BFF 模式的研发，很适合拥有前端技术背景的全栈型工程师。这种模式的好处很明显，后端可以专注于业务领域，更多从领域模型的视角去思考问题，页面视角的数据则交给前端型全栈工程师去搞定。**领域模型与页面数据是两种思维模式，通过 BFF 可以很好地解耦开，让彼此更专业高效**。\n\n在 Web 服务里，搭建一个中间层，前端访问中间层的接口，中间层再访问后台的 Java/C++ 服务。这类服务的特点是不需要太强的服务器运算能力，但对程序的灵活性有较高的要求。这两个特点，正好和 Node.js 的优势相吻合。Node.js 非常适合用来做 BFF 层，优势如下：\n\n-   对于前端来说：让前端**有能力自由组装后台数据**，这样可以减少大量的业务沟通成本，加快业务的迭代速度；并且，前端同学能够**自主决定**与后台的通讯方式。\n\n-   对于后台和运维来说，好处是：安全性（不会把主服务器暴露在外面）、降低主服务器的复杂度等。\n\n### 2、服务端渲染\n\n**客户端渲染**（CSR / Client side render）：前端通过一大堆接口请求数据，然后通过 JS 动态处理和生成页面结构和展示。优点是**前后端分离**、减小服务器压力、局部刷新。缺点是不利于 SEO（如果你的页面然后通过 Ajax 异步获取内容，抓取工具并不会等待异步完成后再行抓取页面内容）、首屏渲染慢。\n\n**服务端渲染**（SSR / Server Side Render）：服务器返回的不是接口数据，而是一整个页面（或整个楼层）的 HTML 字符串，浏览器直接显示即可。也就是说，在服务器端直接就渲染好了，然后一次性打包返回给前端。优点是**有利于 SEO、首屏渲染很快**。\n\n**总结： 搜索引擎优化 + 首屏速度优化 = 服务端渲染**。\n\n备注：这里的「服务端渲染」只是让 Node.js 做中间层，不会替代后端的，后台同学请放心。\n\n参考链接：\n\n-   [Vue 服务端渲染的概念](https://ssr.vuejs.org/zh/)\n\n-   <https://blog.csdn.net/u012036171/article/details/88833200>\n\n-   <https://juejin.im/post/5c068fd8f265da61524d2abc>\n\n-   [方应杭](https://www.zhihu.com/question/59578433/answer/326694511)\n\n历史回顾：\n\n（1）一开始，页面很简单，html 是后端渲染的（比如PHP、ASP、JSP等方式）。后端发现页面中的 js 好麻烦（虽然简单，但是坑多），于是让公司招聘专门写 js 的人，简称「前端切图仔」。\n\n（2）随着 Node.js 和前端 MVC 的兴起，以及前端越来越复杂，慢慢演变成了「前后端分离」。\n\n（3）前端的 SPA 应用流行之后，发现 SEO 问题很大，而且首屏渲染速度很慢，但是自己选的路再难走也要走下去，于是用 Node.js 在服务端渲染被看成是一条出路。\n\n（4）以前在一起的时候，是后端做部分前端的工作；现在在一起的时候，是前端做部分后端的工作。\n\n### 3、做小型服务、小型网站的后端（基于 Express、Koa 框架）\n\n现在很多公司的后台管理系统，都是用 Node.js 来开发接口，毕竟，后台管理系统对性能和并发的要求不是太高。有了 Node.js 之后，通过 JS 直接操作 DB，做增删改查，生成接口，极大降低了前端同学的学习门槛。\n\n当然，有时候做 Node.js 开发，是因为：后台人力不够，所以把后台开发的一部分工作量，转移给前端同学。\n\n### 4、做项目构建工具\n\n前端正在广泛使用的构建工具 gulp、Webpack，就是基于 Node.js 来实现的。\n\n### 5、 做 PC 客户端软件（基于 Electron 框架）\n\nElectron 框架就是基于 Node.js 的，可以用来开发客户端软件。\n\nElectron 原名为 Atom Shell，是由 GitHub 开发的一个开源框架。Electron 以 Node.js 作为运行时（runtime），以  chromium 作为渲染引擎，使开发者可以使用 JS 这种前端技术栈开来发跨平台的桌面GUI应用程序。\n\n有一点你可能会感到惊讶：程序员们都在用的代码编辑器 VS Code 软件， 就是基于 Electron 框架来开发的。其他使用 Electron 进行开发的知名应用还有：Skype、GitHub Desktop、Slack、WhatsApp等。\n\n还有一个例子是：电子游戏直播网站 [Twitch](https://www.twitch.tv/)，号称是国外游戏直播的鼻祖，它在 PC 端的客户端软件，就是用 Electron 框架的。你会发现，Twitch 的网站视觉，和 PC 端的视觉，几乎是一样的。如果两端都采用 JS 语言，就可以极大的复用现有的工程。\n\n### 知名度较高的 Node.js 开源项目\n\n![](http://img.smyhvae.com/20180301_2009.png)\n\n-   express：Node.js 中著名的 web 服务框架。\n\n-   Koa：下一代的 Node.js 的 Web 服务框架。所谓的“下一代”是相对于 Express 而言的。\n\n-   [Egg](https://eggjs.org/zh-cn/)：2016 年，阿里巴巴研发了知名的 Egg.js 开源项目，号称企业级 Web 服务框架。Egg.js 是基于 Koa 开发的。\n\n*   mocha：是现在最流行的 JavaScript 测试框架，在浏览器和 Node 环境都可以使用。\n\n*   PM2：node 多进程管理。\n\n*   jade：非常优秀的模板引擎，不仅限于 js 语言。\n\n*   CoffeeScript：用简洁的方式展示 JavaScript 优秀的部分。\n\n*   Atom：编辑器。\n\n*   VS Code：最酷炫的编辑器。\n\n*   socket.io：实时通信框架。\n\n### 总结\n\n或许，能用 Node.js 做的后台应用，Java/C++ 也能做；但是 Node.js 可以让我们多一种选择。\n\n短期来看，Node.js 很难像 Java/C++ 那样，成为后台的主力开发语言。这并非是因为 Node.js 的性能问题，主要是因为，Node.js 还比较年轻，经验积累太少，框架的支持度不够。搞企业级服务，Node.js 敌不过 Java/C++，所以目前只能搞「轻量级」；但未来可期。\n\n限制语言能力的不是语言本身，而是生态。\n\n## 最后一段：前端同学会 Node.js 就真的全栈了吗？\n\n一个人的精力是有限的，既擅长前端、又精通后端的人，毕竟是极少数。\n\n林肯说过：“你可以在所有的时间欺骗一部分人，也可以在一段时间欺骗所有的人，但你不可能在所有的时间欺骗所有的人”。\n\n同样的，我也说过：“你可以在这一段时间擅长前端技术，也可以在另一段时间擅长后台技术，但你不可能在**同一段时间**同时擅长前端和后台，更不可能在**所有的时间**同时擅长前端和后台。”\n\n所谓的全栈，只是一个伪命题。个人不一定需要全栈，企业和项目也不强制要求全栈，分工协作，才最高效。\n\n对于个人而言，虽然全栈很难，但是 Node.js 的出现，**让 JS 语言实现了前后端语法的统一，让 JS 语言的技术栈更佳全面**。\n\n涉及到后台开发相关的技术，无论如何，也绕不开**框架设计、开发调试、数据库操作、高并发处理、大规模存储、性能优化、容灾方案、RPC 调用、进程管理、操作系统调度、网络安全、系统运维、日常维护、甚至是 Linux 内核、驱动开发**等过硬的知识技能和经验积累。等你亲身经历过这些，才算明白：语言只是一种工具。\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n"
  },
  {
    "path": "11-Node.js/02-Node.js的特点.md",
    "content": "---\ntitle: 02-Node.js的特点\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## Node.js 的特点\n\n-   异步、非阻塞 IO 模型\n\n-   事件循环\n\n-   单线程\n\n-   总结：轻量和高效\n\nNode.js 的性能和效率非常高。\n\n传统的 Java 语言是一个请求开启一个线程，当请求处理完毕后就关闭这个线程。而 Node.js 则完全没有采用这种模型，它本质上就是一个单线程。\n\n你可能会疑问：一个线程如何服务于大量的请求、如何处理高并发的呢？这是因为，Node.js 采用的是异步的、非阻塞的模型。\n\n这里所谓的“单线程”，指的是 Node 的主线程只有一个。为了确保主线程不被阻塞，主线程是用于接收客户端请求。但不会处理具体的任务。而 Node 的背后还有一个线程池，线程池会处理长时间运行的任务（比如 IO 操作、网络操作）。线程池里的任务是通过队列和事件循环的机制来执行。\n\n\n## 使用 Node.js 时的劣势\n\n- 程序运行不稳定，可能会出现服务不可用的情况\n\n- 程序运行效率较低，每秒的请求数维持在一个较低的水平\n\n- 前端同学对服务器端的技术不太熟悉。\n"
  },
  {
    "path": "11-Node.js/03-Node.js开发环境安装.md",
    "content": "---\ntitle: 03-Node.js开发环境安装\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## Node.js 版本常识\n\n- 偶数版本为稳定版（0.6.x ，8.10.x）\n\n- 奇数版本为非稳定版（0.7.x ，9.11.x）\n\n- LTS（Long Term Support）\n\n参考链接：[node.js 中 LTS 和 Current 的区别](https://blog.csdn.net/u012532033/article/details/73332099)\n\n## Node.js 运行环境配置：通过 Node.js 安装包（不推荐）\n\n去 Node.js 的[官网](https://nodejs.org/en/)下载安装包：\n\n![](http://img.smyhvae.com/20180301_1505.png)\n\n我们也可以在<https://nodejs.org/en/download/releases/> 里下载历史版本。\n\n![](http://img.smyhvae.com/20180301_1507.png)\n\n注意，我们以一定要用偶数版（V4、V6 等)，不要用奇数版（比如 V5），因为奇数版不稳定。\n\n后续如果需要安装其他版本，可以这样做：重新下载最新的安装包，覆盖安装即可。\n\n但我们并不推荐直接采用 Node.js.msi（windows）或者 Node.js.pkg（Mac） 安装包进行安装，因为会产生如下问题。\n\n**通过 Node.js 安装包产生的问题**：\n\n-   安装新版本时，需要覆盖就版本；而且以前版本安装的很多全局工具包，需要重新安装。\n\n-   无法回滚到之前的旧版本。\n\n-   无法在多个版本之间切换（很多时候，不同的项目需要使用特定版本。或者，我想临时尝鲜一下新版本的特性）\n\n因此，我们暂时先不用安装 Node.js，稍后用 NVM 的方式来安装 Node.js。通过 NVM 的方式，可以让多个版本的 Node.js 共存，并灵活切换。\n\n\n\n## Node.js 运行环境安装：通过 NVM（推荐）\n\n**[NVM](https://github.com/nvm-sh/nvm)**：node.js version manager，用来管理 node 的版本。\n\n**我们可以先安装 NVM，然后通过 NVM 安装 Node.js**。这是官方推荐的做法。\n\nWindows 和 Mac 下安装的 Node.js 的步骤如下。\n\n### Windows 系统安装 Node.js\n\n**1、安装 NVM**：\n\n（1）我们去 <https://github.com/coreybutler/nvm-windows/releases> 下载 NVM 的安装包：\n\n![](http://img.smyhvae.com/20180301_1603.png)\n\n下载下来后，直接解压到 `D:\\web`目录下：\n\n![](http://img.smyhvae.com/20180301_1610.png)\n\n（2）在上面的目录中，新建一个`settings.txt`文件，里面的内容填充如下：\n\n```bash\nroot: D:\\web\\nvm\npath: D:\\web\\nodejs\narch: 64\nproxy\n```\n\n上方内容的解释：\n\n-   root 配置为：当前 nvm.exe 所在的目录\n\n-   path 配置为：node 快捷方式所在的目录\n\n-   arch 配置为：当前操作系统的位数（32/64）\n\n-   proxy 不用配置\n\n（3）配置环境变量：\n\n-   `NVM_HOME` = `D:\\web\\nvm`（当前 nvm.exe 所在目录）\n\n-   `NVM_SYMLINK` = `D:\\web\\nodejs` （node 快捷方式所在的目录）\n\n-   PATH += `;%NVM_HOME%;%NVM_SYMLINK%`\n\n配置成功后，重启资源管理器。\n\n**2、验证：**(在 cmd 命令行中输入命令)\n\n（1）输入`nvm`命令查看环境变量是否配置成功：\n\n![](http://img.smyhvae.com/20180301_1645.png)\n\n（2）输入 `nvm ls`，查看已安装的所有 node 版本。\n\n（3）输入 `nvm -v`，查看 已安装的 nvm 版本。\n\n（4）输入 `node -v`，查看正在使用的 node 版本。\n\n\n如果 Node 安装失败，可以参考上面这个链接。\n\n**3、安装指定版本的 Node.js**：\n\n```bash\nnvm install 版本号\n\n# 安装举例\nnvm install 12.20.0\n\n# 使用该版本\nnvm use 12.20.0\n```\n\n输入 `node -v`，查看当前使用的 node 版本。\n\n关于 NVM 的常用命令，详见下一段。\n\n补充：\n\n如果 Node 安装失败，可以在上方的 `settings.txt`文件中，新增如下两行，修改镜像源：\n\n```\nnode_mirror: https://npm.taobao.org/mirrors/node/\nnpm_mirror: https://npm.taobao.org/mirrors/npm/\n```\n\n- 参考链接：[安装 npm，nvm，node](https://segmentfault.com/a/1190000011114680)\n\n\n### Mac 系统安装 Node.js\n\n**1、安装 [NVM](https://github.com/nvm-sh/nvm)**：\n\n（1）打开 终端.app，输入：\n\n```bash\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash\n```\n\n安装成功的界面：\n\n![](http://img.smyhvae.com/20180302_2126.png)\n\n完成后，nvm 就被安装在了`~/.nvm`下。我们可以点开 home目录，然后按快捷键「Cmd + Shift + .」，看看 `.nvm`这个文件夹在不在。\n\n问题1、如果安装时提示 `Failed to connect to raw.githubusercontent.com port 443: Connection refused`。解决办法：我们直接访问 https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh ，将安装文件保存到本地，然后`bash install.sh` 执行这个本地文件，即可安装成功。\n\n问题2、如果发现安装失败：\n\n![](http://img.smyhvae.com/20180302_2111.png)\n\n原因：Xcode 软件进行过更新。解决办法：打开 Xcode 软件，同意相关内容即可。\n\n\n\n（2）配置环境变量：\n\n编辑器打开`~/.bash_profile`文件，如果不会就在命令行输入`open ~/.bash_profile`。\n\n（补充：如果你的 Mac 电脑里找不到`~/.bash_profile`文件，那就找找有没有`~/.profile`文件，或者`~/.bashrc`文件，或者`~/.zshrc`文件。如果还是没有，那你就手动创建一个`~/.bash_profile`文件）。\n\n在最后一行输入：\n\n```bash\nexport NVM_DIR=\"$HOME/.nvm\"\n[ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\" # This loads nvm\n```\n\n如果你发现文件中已经存在了上面这行代码，就不用往里面加了。这一步的作用是每次新打开一个 bash，nvm 都会被自动添加到环境变量中。\n\n最后，输入 `source ~/.bash_profile`重启环境变量的配置。\n\nPS：NVM 现在已经不支持 Homebrew 的方式来安装了。\n\n参考链接：<https://www.jianshu.com/p/a3f8778bc0a1>\n\n\n\n参考链接：<https://blog.csdn.net/science_Lee/article/details/79214127>\n\n**2、验证：**(在 终端命令行中输入命令)\n\n（1）输入 `nvm` 命令查看环境变量是否配置成功。\n\n（2）输入 `nvm ls`，查看已安装的所有 node 版本。\n\n（3）输入 `nvm -v`，查看 已安装的 nvm 版本。\n\n（4）输入 `node -v`，查看正在使用的 node 版本。\n\n**3、安装指定版本的 Node.js**：\n\n和 Windows 下一样，也是执行如下命令：\n\n```bash\nnvm install 版本号\n\n# 举例\nnvm install 12.18.0\n```\n\n网速有点慢，要稍等。\n\n![](http://img.smyhvae.com/20180302_2148.png)\n\n输入 `node -v`，查看当前使用的 node 版本。\n\n安装好 `Node` 之后，`npm` 也会自动安装的，输入 `npm -v`，查看 npm 的版本。\n\n关于 NVM 的常用命令，详见下一段。\n\n**4、让 `.bash_profile` 环境变量永久生效**：\n\n在 `~/.bash_profile` 中配置好环境变量后，发现每次重启终端后，配置都会失效，需要重新执行 `source ~/.bash_profile` 命令。\n\n原因是，zsh加载的是 `~/.zshrc`文件，而 `.zshrc` 文件中并没有定义任务环境变量。\n\n解决办法：打开 `~/.zshrc` 文件，在文件的末尾，添加如下内容即可：\n\n```bash\nsource ~/.bash_profile\n```\n\n## NVM 的常用命令\n\n> 注意，这一段说的是 NVM 的常用命令，不是 Node 的常用命令。\n\n查看当前使用的 nvm 版本：\n\n```bash\nnvm --version\n```\n\n查看本地安装的所有的 Node.js 版本：\n\n```bash\n# 方式1\nnvm ls\n\n# 方式2\nnvm list\n```\n\n**安装指定版本的 Node.js：**\n\n```bash\nnvm install 版本号\n\n# 举例\nnvm install 8.10.0\n```\n\n卸载指定版本 Node.js：\n\n```bash\nnvm uninstall 版本号\n```\n\n**切换使用指定版本的 node**：\n\n```bash\nnvm use 版本号\n```\n\n**设置node的默认版本**：\n\n```bash\nnvm alias default 版本号\n```\n\n\n**查看全局npm包的安装路径**：\n\n```\nnpm root -g\n```\n\n\n查看远程服务器端的所有 Node 版本：\n\n```bash\nnvm ls-remote\n```\n\n执行上面的命令后，在列出的版本清单中，凡是用 `Latest LTS`标注的版本，则表明是**长期维护**的版本。我们在安装时，建议安装这些版本。当然，我们也可以在网址 <https://nodejs.org/en/download/releases/> 查看 LTS 的历史版本。\n\n## Node.js 的常用命令\n\n查看 node 的版本：\n\n```bash\n$ node -v\n```\n\n执行脚本字符串：\n\n```bash\n$ node -e 'console.log(\"Hello World\")'\n```\n\n运行脚本文件：\n\n```bash\n$ node index.js\n\n$ node path/index.js\n\n$ node path/index\n```\n\n查看帮助：\n\n```bash\n$ node --help\n\n```\n\n**进入 REPL 环境：**\n\n```bash\n$ node\n```\n\nREPL 的全称：Read、Eval、 Print、Loop。类似于浏览器的控制台。\n\n![](http://img.smyhvae.com/20180301_1900.png)\n\n如果要退出 REPL 环境，可以输入`.exit` 或 `process.exit()`。\n\n在 VS Code 里，我们可以在菜单栏选择“帮助->切换开发人员工具”，打开 console 控制台。\n\n## 包和 NPM\n\n### 什么是包\n\n由于 Node 是一套轻内核的平台，虽然提供了一系列的内置模块，但是不足以满足开发者的需求，于是乎出现了包（package）的概念：\n与核心模块类似，就是将一些预先设计好的功能或者说 API 封装到一个文件夹，提供给开发者使用。\n\nNode 本身并没有太多的功能性 API，所以市面上涌现出大量的第三方人员开发出来的 Package。\n\n### 包的加载机制\n\n如果 Node 中自带的包和第三方的包名冲突了，该怎么处理呢？原则是：\n\n-   先在系统核心（优先级最高）的模块中找；\n\n-   然后到当前项目中 node_modules 目录中找。\n\n比如说：\n\n```javascript\nrequiere(`fs`);\n```\n\n那加载的肯定是系统的包。所以，我们尽量不要创建一些和现有的包重名的包。\n\n### NPM 的概念\n\n**NPM**：Node Package Manager。官方链接： <https://www.npmjs.com/>\n\nNode.js 发展到现在，已经形成了一个非常庞大的生态圈。包的生态圈一旦繁荣起来，就必须有工具去来管理这些包。NPM 应运而生。\n\n举个例子，当我们在使用 Java 语言做开发时，需要用到 JDK 提供的内置库，以及第三方库。同样，在使用 JS 做开发时，我们可以使用 NPM 包管理器，方便地使用成熟的、优秀的第三方框架，融合到我们自己的项目中，极大地加速日常开发的构建过程。\n\n随着时间的发展，NPM 出现了两层概念：\n\n-   一层含义是 Node 的开放式模块登记和管理系统，亦可以说是一个生态圈，一个社区。\n\n-   另一层含义是 Node 默认的模块管理器，是一个命令行下的软件，用来安装和管理 Node 模块。\n\n### NPM 的安装（不需要单独安装）\n\nNPM 不需要单独安装。默认在安装 Node 的时候，会连带一起安装 NPM：\n\n![](http://img.smyhvae.com/20180302_1105.png)\n\nNVM、Node、NPM 安装之后，目录分布如下：\n\n![](http://img.smyhvae.com/20180302_1134.png)\n\n![](http://img.smyhvae.com/20180302_1137.png)\n\n![](http://img.smyhvae.com/20180302_1138.png)\n\n输入 `npm -v`，查看 npm 的版本：\n\n![](http://img.smyhvae.com/20180302_1139.png)\n\n如果上方命令无效，可能是之前的 node 并没有完全安装成功。解决办法：<https://segmentfault.com/a/1190000011114680>\n\n另外，Node 附带的 NPM 可能不是最新版本，可以用下面的命令，更新到最新版本：\n\n```bash\n$ npm install npm -g\n```\n\n### 配置 NPM 的全局目录（暂略）\n\nNPM 默认安装到当前正在使用 Node 版本所在目录下。我们建议重新配置 NPM 的全局目录。\n\n输入`npm config ls`，查看：\n\n![](http://img.smyhvae.com/20180302_1210.png)\n\n### NPM包的版本管理\n\nNPM包的管理都是通过项目根目录的 `package.json`文件实现。\n\n当你使用 npm 安装一个包或者更新一个包的时候，package.json 里会自动添加**包名和包的版本**。npm 默认安装**符合条件**的最新版本，然后在版本号之前添加`^`符号。\n\nNPM包的版本号，是用三位数表示。版本号前面的符号，代表开发者想要更新的的最新版本条件：\n\n- 符号`^`：固定第一位数。表示主版本固定的情况下，可更新至最新版。例如 `vue: \"^2.6.0\"` 表示 2.6.0及其以上的2.x.x 都是满足的。\n- 符号`~`：固定前两位数。表示次版本固定的情况下，可更新至最新版。例如 `vuex: \"~2.6.0\"`，2.6.0及其以上的2.6.x都是满足的。\n- 无符号：三位数都固定。无符号表示固定版本号。例如 `vue: \"2.6.0\"`，此时一定是安装`2.6.0`版本。\n\n参考链接：\n\n- [请将你的npm依赖版本锁定](https://juejin.cn/post/6960928446826741796)\n\n\n## NPM 的常用命令\n\n查看 npm 当前版本：\n\n```bash\nnpm -v\n```\n\n更新 npm：\n\n```bash\nnpm install npm@latest -g\n\n```\n\n项目初始化：（执行完成后，会生成`package.json`文件）\n\n```bash\nnpm init\n\n# 快速跳过问答式界面，选择默认配置\nnpm init --yes\n```\n\n只在当前工程下安装指定的包：\n\n```bash\nnpm install [package]\n```\n\n在全局安装指定的包：\n\n```\nnpm install -g [package]\n```\n\n安装的包只用于开发环境，不用于生产环境：（会出现在 package.json 文件中的 devDependencies 属性中）\n\n```bash\nnpm install [package] --save-dev\n\n# 或者\nnpm install [package] -D\n```\n\n安装的包需要发布到生产环境：（会出现在 package.json 文件中的 dependencies 属性中）\n\n```bash\nnpm install [package] --save\n\n# 或者\nnpm install [package] -S\n```\n\n查看当前目录下已安装的 node 包：\n\n```bash\nnpm list\n```\n\n查看全局已经安装的 node 包：\n\n```bash\nnpm list -g\n```\n\n查看 npm 帮助命令：\n\n```bash\nnpm --help\n```\n\n查看指定命令的帮助：\n\n```bash\nnpm [指定命令] --help\n```\n\n更新指定的包：\n\n```bash\nnpm update [package]\n```\n\n卸载指定的包：\n\n```bash\nnpm uninstall [package]\n```\n\n查看配置信息：\n\n```bash\nnpm config list\n```\n\n查看本地安装的指定包的信息，没有则显示 empty：\n\n```bash\nnpm ls [package]\n```\n\n查看全局安装的指定包的信息，没有则显示 empty：\n\n```bash\nnpm ls [package] -g\n```\n\n查看远程 npm 上指定包的所有版本信息：\n\n```bash\nnpm info [package]\n```\n\n查看当前包的安装路径：\n\n```bash\nnpm root\n```\n\n查看全局包的安装路径：\n\n```bash\nnpm root -g\n```\n\n## 配置 npm 镜像源\n\n由于 npm 默认的下载地址在国外（npmjs.com），有时候会被墙，导致无法下载或者下载很慢。因此，我们可以尝试切换成，从其他的镜像源下载 npm 包。\n\n切换镜像源，有下面这几种方式：\n\n-   方式 1：临时切换镜像源。\n\n-   方式 2：切换镜像源\n\n-   方式 3：通过 NRM 切换镜像源（最为推荐的方式）。\n\n-   方式 4：cnpm。\n\n下面来分别讲一下。\n\n### 方式 1：临时切换镜像源\n\n安装指定包的时候，通过追加 `--registry`参数即可。格式如下：\n\n```bash\n# 格式\nnpm install [package] --registry [https://xxx]\n\n# 举例：在下载安装 express 这个包的时候，临时指定镜像源为 https://registry.npm.taobao.org\nnpm install express --registry https://registry.npm.taobao.org\n```\n\n### 方式 2：切换镜像源\n\n```bash\nnpm config set registry https://registry.npm.taobao.org\n```\n\n执行上述命令后，以后下载所有 npm 包的时候，都会改为使用淘宝的镜像源。\n\n### 方式 3：通过 NRM 切换镜像源（推荐）\n\n**NRM**：Node Registry Manager。作用是：**切换和管理 npm 包的镜像源**。\n\n-   项目地址：<https://www.npmjs.com/package/nrm>\n\n-   GitHub 地址： <https://github.com/Pana/nrm>\n\n**安装 NRM**：\n\n```bash\n\tnpm install -g nrm\n```\n\n![](http://img.smyhvae.com/20180302_1208.png)\n\n**NRM 的常用命令：**\n\n```bash\n# 显示全部的镜像\nnrm ls\n\n# 使用淘宝的镜像\nnrm use taobao\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180302_1215.png)\n\n推荐的国内加速镜像淘宝：<https://npm.taobao.org/>\n\n## 方式 4：安装 cnpm\n\n-   项目地址：<https://npm.taobao.org/>\n\n安装`cnpm`替换 npm（npm 由于源服务器在国外，下载包的速度较慢，cnpm 会使用国内镜像）：\n\n```bash\nnpm install -g cnpm --registry=https://registry.npm.taobao.org\n```\n\n![](http://img.smyhvae.com/20180302_2204.png)\n\n以后我们就可以通过 cnpm 命令去安装一个包。举例如下：\n\n```bash\n# 安装 vue 这个包\ncnpm install vue\n```\n\n这里的单词 `install` 可以简写成 `i`。\n\n## Node.js 的简单使用\n\n我们可以输入`node`命令，然后在里面写 js 的代码。\n\n或者，也可以 通过 node 运行 指定的 js 文件。比如，编写好一个 js 文件`01.js`，然后在命令行输入：\n\n```bash\n\tnode 01.js\n```\n\n就可以执行这个 js 程序，直接在命令行查看运行结果。\n\n## 赞赏作者\n\n创作不易，你的赞赏和认可，是我更新的最大动力：\n\n![](https://img.smyhvae.com/20220401_1800.jpg)\n"
  },
  {
    "path": "11-Node.js/04-Node.js模块化规范：CommonJS.md",
    "content": "---\ntitle: 04-Node.js模块化规范：CommonJS\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 前言\n\n网站越来越复杂，js代码、js文件也越来越多，会遇到**一些问题**：\n\n- 文件依赖\n\n- 全局污染、命名冲突\n\n程序模块化包括：\n\n- 日期模块\n\n- 数学计算模块\n\n- 日志模块\n\n- 登陆认证模块\n\n- 报表展示模块等。\n\n所有这些模块共同组成了程序软件系统。\n\n一次编写，多次使用，才是提高效率的核心。\n\n\n\n\n## 模块化的理解\n\n### 什么是模块化\n\n\n**概念**：将一个复杂的程序依据一定的规则（规范）封装成几个块（文件），并组合在一起。\n\n模块的内部数据、实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。\n\n最早的时候，我们会把所有的代码都写在一个js文件里，那么，耦合性会很高（关联性强），不利于维护；而且会造成全局污染，很容易命名冲突。\n\n### 模块化的好处\n\n- 避免命名冲突，减少命名空间污染\n\n- 降低耦合性；更好地分离、按需加载\n\n- **高复用性**：代码方便重用，别人开发的模块直接拿过来就可以使用，不需要重复开发类似的功能。\n\n- **高可维护性**：软件的声明周期中最长的阶段其实并不是开发阶段，而是维护阶段，需求变更比较频繁。使用模块化的开发，方式更容易维护。\n\n- 部署方便\n\n\n\n\n## 模块化规范\n\n### 模块化规范的引入\n\n假设我们引入模块化，首先可能会想到的思路是：在一个文件中引入多个js文件。如下：\n\n```html\n<body>\n    <script src=\"zepto.js\"></script>\n    <script src=\"fastClick.js\"></script>\n    <script src=\"util/login.js\"></script>\n    <script src=\"util/base.js\"></script>\n    <script src=\"util/city.js\"></script>\n</body>\n```\n\n但是这样做会带来很多问题：\n\n- 请求过多：引入十个js文件，就有十次http请求。\n\n- 依赖模糊：不同的js文件可能会相互依赖，如果改其中的一个文件，另外一个文件可能会报错。\n\n以上两点，最终导致：**难以维护**。\n\n于是，这就引入了模块化规范。\n\n\n###  模块化的概念解读\n\n模块化起源于 Node.js。Node.js 中把很多 js 打包成 package，需要的时候直接通过 require 的方式进行调用（CommonJS），这就是模块化的方式。\n\n那如何把这种模块化思维应用到前端来呢？这就产生了两种伟大的 js：RequireJS 和 SeaJS。\n\n\n### 模块化规范\n\n服务器端规范：\n\n- [**CommonJS规范**](http://www.commonjs.org/)：是 Node.js 使用的模块化规范。\n\nCommonJS 就是一套约定标准，不是技术。用于约定我们的代码应该是怎样的一种结构。\n\n\n浏览器端规范：\n\n- [**AMD规范**](https://github.com/amdjs/amdjs-api)：是 **[RequireJS](http://requirejs.org/)** 在推广过程中对模块化定义的规范化产出。\n\n```\n- 异步加载模块；\n\n- 依赖前置、提前执行：require([`foo`,`bar`],function(foo,bar){});   //也就是说把所有的包都 require 成功，再继续执行代码。\n\n- define 定义模块：define([`require`,`foo`],function(){return});\n```\n\n- **[CMD规范]()**：是 **[SeaJS](http://seajs.org/)** 在推广过程中对模块化定义的规范化产出。淘宝团队开发。\n\n```\n\n  同步加载模块；\n\n  依赖就近，延迟执行：require(./a) 直接引入。或者Require.async 异步引入。   //依赖就近：执行到这一部分的时候，再去加载对应的文件。\n\n  define 定义模块， export 导出：define(function(require, export, module){});\n```\n\n\nPS：面试时，经常会问AMD 和 CMD 的区别。\n\n\n另外，还有ES6规范：import & export。\n\n这篇文章，我们来讲一下`CommonJS`，它是 Node.js 使用的模块化规范。\n\n## CommonJS 的基本语法\n\n### CommonJS 的介绍\n\n\n[CommonJS](http://www.commonjs.org/)：是 Node.js 使用的模块化规范。也就是说，Node.js 就是基于 CommonJS 这种模块化规范来编写的。\n\nCommonJS 规范规定：每个模块内部，module 变量代表当前模块。这个变量是一个对象，它的 exports 属性（即 module.exports）是对外的接口对象。加载某个模块，其实是加载该模块的 module.exports 对象。\n\n在 CommonJS 中，每个文件都可以当作一个模块：\n\n- 在服务器端：模块的加载是运行时同步加载的。\n\n- 在浏览器端: 模块需要提前编译打包处理。首先，既然同步的，很容易引起阻塞；其次，浏览器不认识`require`语法，因此，需要提前编译打包。\n\n### 模块的暴露和引入\n\nNode.js 中只有模块级作用域，两个模块之间的变量、方法，默认是互不冲突，互不影响，这样就导致一个问题：模块 A 要怎样使用模块B中的变量&方法呢？这就需要通过 `exports` 关键字来实现。\n\nNode.js中，每个模块都有一个 exports 接口对象，我们可以把公共的变量、方法挂载到这个接口对象中，其他的模块才可以使用。\n\n接下来详细讲一讲模块的暴露、模块的引入。\n\n\n### 暴露模块的方式一： exports\n\n`exports`对象用来导出当前模块的公共方法或属性。别的模块通过 require 函数调用当前模块时，得到的就是当前模块的 exports 对象。\n\n**语法格式**：\n\n```js\n// 相当于是：给 exports 对象添加属性\nexports.xxx = value\n```\n\n这个 value 可以是任意的数据类型。\n\n**注意**：暴露的关键词是`exports`，不是`export`。其实，这里的 exports 类似于 ES6 中的 export 的用法，都是用来导出一个指定名字的对象。\n\n\n\n**代码举例**：\n\n```js\nconst name = 'qianguyihao';\n\nconst foo = function (value) {\n\treturn value * 2;\n};\n\nexports.name = name;\nexports.foo = foo;\n```\n\n\n\n### 暴露模块的方式二： module.exports\n\n`module.exports`用来导出一个默认对象，没有指定对象名。\n\n语法格式：\n\n```javascript\n// 方式一：导出整个 exports 对象\nmodule.exports = value;\n\n// 方式二：给 exports 对象添加属性\nmodule.exports.xxx = value;\n```\n\n这个 value 可以是任意的数据类型。\n\n代码举例：\n\n```js\n// 方式1\nmodule.exports = {\n    name: '我是 module1',\n    foo(){\n        console.log(this.name);\n    }\n}\n\n// 我们不能再继续写 module.exports = value2。因为重新赋值，会把 exports 对象 之前的赋值覆盖掉。\n\n// 方式2\nconst age = 28;\nmodule.exports.age = age;\n\n```\n\n`module.exports` 还可以修改模块的原始导出对象。比如当前模块原本导出的是一个对象，我们可以通过 module.exports 修改为导出一个函数。如下：\n\n```js\nmodule.exports = function () {\n    console.log('hello world')\n}\n```\n\n### exports 和 module.exports 的区别\n\n\n\n最重要的区别：\n\n- 使用exports时，只能单个设置属性 `exports.a = a;`\n\n- 使用module.exports时，既单个设置属性 `module.exports.a`，也可以整个赋值 `module.exports = obj`。\n\n其他要点：\n\n- Node中每个模块的最后，都会执行 `return: module.exports`。\n\n- Node中每个模块都会把 `module.exports`指向的对象赋值给一个变量 `exports`，也就是说 `exports = module.exports`。\n\n- `module.exports = XXX`，表示当前模块导出一个单一成员，结果就是XXX。\n\n- 如果需要导出多个成员，则必须使用 `exports.add = XXX; exports.foo = XXX`。或者使用 `module.exports.add = XXX; module.export.foo = XXX`。\n\n### 问题: 暴露的模块到底是谁？\n\n**答案**：暴露的本质是`exports`对象。【重要】\n\n比如，方式一的 `exports.a = a` 可以理解成是，**给 exports 对象添加属性**。方式二的 `module.exports = a`可以理解成是给整个 exports 对象赋值。方式二的 `module.exports.c = c`可以理解成是给 exports 对象添加属性。\n\nNode.js 中每个模块都有一个 module 对象，module 对象中的有一个 exports 属性称之为**接口对象**。我们需要把模块之间公共的方法或属性挂载在这个接口对象中，方便其他的模块使用。\n\n\n### 引入模块的方式：require\n\nrequire函数用来在一个模块中引入另外一个模块。传入模块名，返回模块导出对象。\n\n**语法格式**：\n\n```js\nconst module1 = require('模块名');\n```\n\n解释：\n\n- 内置模块：require的是**包名**。\n\n- 下载的第三方模块：require的是**包名**。\n\n- 自定义模块：require的是**文件路径**。文件路径既可以用绝对路径，也可以用相对路径。后缀名`.js`可以省略。\n\n\n**代码举例**：\n\n```js\nconst module1 = require('./main.js');\n\nconst module2 = require('./main');\n\nconst module3 = require('Demo/src/main.js');\n```\n\n**require()函数的两个作用**：\n\n- 执行导入的模块中的代码。\n\n- 返回导入模块中的接口对象。\n\n\n### 主模块\n\n主模块是整个程序执行的入口，可以调度其他模块。\n\n```bash\n# 运行main.js启动程序。此时，main.js就是主模块\n$ node main.js\n```\n\n### 模块的初始化\n\n一个模块中的 JS 代码仅在模块**第一次被使用时**执行一次，并且在使用的过程中进行初始化，然后会被缓存起来，便于后续继续使用。\n\n代码举例：\n\n（1）calModule.js:\n\n```js\nvar a = 1;\n​\nfunction add () {\n  return ++a;\n}\n​\nexports.add = add;\n\n```\n\n（2）main.js：（在 main.js 中引入 hello.js 模块）\n\n```js\nvar addModule1 = require('./calModule')\nvar addModule2 = require('./calModule')\n​\nconsole.log(addModule1.add());\nconsole.log(addModule2.add());\n```\n\n在命令行执行 `node main.js` 运行程序，打印结果：\n\n```bash\n2\n3\n```\n\n从打印结果中可以看出，`calModule.js`这个模块虽然被引用了两次，但只初始化了一次。\n\n\n## CommonJS 在服务器端的实现举例\n\n\n### 1、初始化项目\n\n在工程文件中新建如下目录和文件：\n\n```\nmodules\n    | module1.js\n    | module2.js\n    | module3.js\n\napp.js\n```\n\n然后在根目录下新建如下命令：\n\n```\n  npm init\n```\n\n\n然后根据提示，依次输入如下内容：\n\n- **包名**：可以自己起包名，也可以用默认的包名。注意，包名里不能有中文，不能有大写。\n\n- **版本**：可以用默认的版本 1.0.0，也可以自己修改包名。\n\n其他的参数，一路回车即可。效果如下：\n\n![](http://img.smyhvae.com/20180410_1425.png)\n\n于是，根目录下会自动生成`package.json`这个文件。点进去看一下：\n\n```json\n{\n  \"name\": \"commonjs_node\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"app.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"smyhvae\",\n  \"license\": \"ISC\"\n}\n\n```\n\n\n### 2、导入第三方包\n\n`uniq`这个第三方包的作用是保证唯一性（我们拿它来举例）。我们在当前工程目录下，输入如下命令进行安装：\n\n```\n  npm install uniq\n```\n\n安装成功后，根目录下会自动生成相应的文件：\n\n![](http://img.smyhvae.com/20180410_1450.png)\n\n\n需要说明的是，我的node版本是 v8.10.0（v8以上），对应的 npm 版本是 v5.6.0，版本比较高，因此，当我输入完`npm install uniq`之后，`package.json`中就会自动添加`uniq`包的依赖：\n\n![](http://img.smyhvae.com/20180410_1855.png)\n\n\n如果有些童鞋的npm版本较低，就需要手动去添加依赖；另一种方式是，可以使用`npm install uniq --save`命令，这个多出来的`--save`就可以自动添加依赖。\n\n\n我们去[官网](https://www.npmjs.com/package/uniq)看一下`uniq`的用法：\n\n```javascript\n  let uniq = require('uniq');\n\n  let arr = [1, 1, 2, 2, 3, 5];\n  uniq(arr);\n  console.log(arr);  //输出结果：[ 1, 2, 3, 5 ]\n\n```\n\n可以看出，这个包可以起到数组去重的作用。\n\n### 3、自定义模块\n\n（1）module1.js：\n\n```javascript\n//暴露方式一：module.exports = value\n\n//暴露一个对象出去\nmodule.exports = {\n    name: '我是 module1',\n    foo(){\n        console.log(this.name);\n    }\n}\n\n//我们不能再继续写 module.exports = xxx。因为重新赋值，会把之前的赋值覆盖掉。\n\n```\n\n（2）module2.js：\n\n```javascript\n//暴露方式一：module.exports = value\n\n//暴露一个函数出去\nmodule.exports = function(){\n    console.log('我是 module2');\n}\n```\n\n注意，此时暴露出去的 exports 对象 等价于整个函数。\n\n（3）module3.js：\n\n```javascript\n//暴露方式二：exports.xxx = value\n\n//可以往 export 对象中不断地添加属性，进行暴露\n\nexports.foo1 = function(){\n    console.log('module3 中的 foo1 方法');\n}\n\nexports.foo2 = function(){\n    console.log('module3 中的 foo2 方法');\n}\n\nexports.arr = [1,1,2,2,3,5,11];\n\n```\n\n（4）app.js：（将其他模块汇集到主模块）\n\n```javascript\n//将其他模块汇集到主模块\n\nlet uniq = require('uniq'); //引入时，第三方模块要放在自定义模块的上面\n\nlet module1 = require('./modules/module1');\nlet module2 = require('./modules/module2');\nlet module3 = require('./modules/module3');\n\n//调用module1对象的方法\nmodule1.foo();\n\n//调用module2的函数\nmodule2();  //注意，在定义时，module2对象等价于整个函数function。所以，module2()的意思是，直接调用了函数。\n\n//调用module3中的属性\nmodule3.foo1();\nmodule3.foo2();\n\nuniq(module3.arr); //将module3中的数组进行去重操作\nconsole.log(module3.arr); //打印数组去重后的结果\n```\n\n这样的话，我们的代码就写完了。\n\n我们在命令行中输入`node app.js`，就可以把代码跑起来了。打印结果如下：\n\n```bash\n我是 module1\n我是 module2\nmodule3 中的 foo1 方法\nmodule3 中的 foo2 方法\n[ 1, 11, 2, 3, 5 ]\n\n```\n\n\n## CommonJS 基于浏览器端的实现举例\n\n\n### 1、初始化项目\n\n在工程文件中新建如下目录和文件：\n\n```\njs\n    dist //打包生成文件的目录\n    src //源码所在的目录\n      | module1.js\n      | module2.js\n      | module3.js\n      | app.js //应用主源文件\nindex.html    //因为CommonJS是基于浏览器端，js文件要跑在浏览器的页面上，所以要有这个html页面\n```\n\n然后在根目录下新建如下命令：\n\n```\n  npm init\n```\n\n\n然后根据提示，依次输入如下内容：\n\n- **包名**：可以自己起包名，也可以用默认的包名。注意，包名里不能有中文，不能有大写。\n\n- **版本**：可以用默认的版本 1.0.0，也可以自己修改包名。\n\n其他的参数，一路回车即可。\n\n于是，根目录下会自动生成`package.json`这个文件。点进去看一下：\n\n```json\n{\n  \"name\": \"commonjs_browser\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\"\n}\n\n\n```\n\n\n### 2、下载第三方包：Browserify\n\n这里需要用到[Browserify](http://browserify.org/)这个工具进行编译打包。Browserify 称为 CommonJS 的浏览器端的打包工具。\n\n输入如下命令进行安装：（两个命令都要输入）\n\n\n```javascript\n    npm install browserify -g          //全局\n    npm install browserify --save-dev  //局部。\n```\n\n上面的代码中，`-dev`表示开发依赖。这里解释一下相关概念：\n\n- 开发依赖：当前这个包，只在开发环境下使用。\n\n- 运行依赖：当前这个包，是在生产环境下使用。\n\n\n### 3、自定义模块 & 代码运行\n\n（1）module1.js：\n\n```javascript\n//暴露方式一：module.exports = value\n\n//暴露一个对象出去\nmodule.exports = {\n    name: '我是 module1',\n    foo(){\n        console.log(this.name);\n    }\n}\n\n//我们不能再继续写 module.exports = xxx。因为重新赋值，会把之前的赋值覆盖掉。\n\n```\n\n（2）module2.js：\n\n```javascript\n//暴露方式一：module.exports = value\n\n//暴露一个函数出去\nmodule.exports = function(){\n    console.log('我是 module2');\n}\n```\n\n注意，此时暴露出去的 exports 对象 等价于整个函数。\n\n（3）module3.js：\n\n```javascript\n//暴露方式二：exports.xxx = value\n\n//可以往export对象中不断地添加属性，进行暴露\n\nexports.foo1 = function(){\n    console.log('module3 中的 foo1 方法');\n}\n\nexports.foo2 = function(){\n    console.log('module3 中的 foo2 方法');\n}\n```\n\n（4）app.js：（将其他模块汇集到主模块）\n\n```javascript\nlet module1 = require('./module1');  // ./ 指的是当前路径\nlet module2 = require('./module2');\nlet module3 = require('./module3');\n\nmodule1.foo();\nmodule2();\nmodule3.foo1();\nmodule3.foo2();\n```\n\n引入的路径解释：\n\n- `./`是相对路径，指的是当前路径（app.js的当前路径是src）\n\n\n到此，我们的主要代码就写完了。\n\n但是，如果我们直接在index.html中，像下面这样写，是不行的：（因为浏览器不认识 require 关键字）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n</head>\n<body>\n    <script src=\"./js/src/app.js\"></script>\n</body>\n</html>\n\n```\n\n为了能够让index.html引入app.js，我们需要输入如下命令：\n\n打包处理js:\n\n```\n    browserify js/src/app.js -o js/dist/bundle.js\n```\n\n然后在index.html中引入打包后的文件：\n\n```html\n    <script type=\"text/javascript\" src=\"js/dist/bundle.js\"></script>\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "11-Node.js/05-Node.js内置模块：fs文件模块.md",
    "content": "---\ntitle: 05-Node.js内置模块：fs文件模块\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## Node.js 的官方API文档\n\n\n- Node.js 的API文档（英文）： <https://nodejs.org/docs/latest-v8.x/api/index.html>\n\n- Node.js 的API文档（中文）：<http://nodejs.cn/api/>\n\n关于 Node.js 的内置模块和常见API，可以看官方文档。\n\n\n查阅文档时，稳定指数如下：\n\n- 红色：废弃。\n\n- 橙色：实验。表示当前版本可用，其他版本不确定。也许不向下兼容，建议不要在生产环境中使用该特性。\n\n- 绿色：稳定。与 npm 生态系统的兼容性是最高的优先级。\n\n\n## Node.js 中模块的分类\n\n\nNode.js 应用由模块组成，采用 CommonJS 模块规范。Node.js中的模块分为三种：\n\n- 内置模块\n\n- 第三方模块\n\n- 自定义模块\n\n下面简单介绍一下。\n\n### 1、内置模块\n\n```js\nconst process = require('process');\nconst path = require('path');\n\nconsole.log(process.version);\nconsole.log(path.resolve('../'));\n```\n\nrequire方法用于加载模块。\n\n常见的内置模块包括：\n\n- FS：文件系统模块\n\n- path：路径模块\n\n- OS：操作系统相关\n\n- net：网络相关\n\n- http\n\n- ...\n\n你可能会有疑问：Node.js 这么牛吗？还能直接和操作系统做交互？\n\n带着这个疑问，我们不妨简单看看 Node.js 的源码，以 os 模块举例：\n\n- 打开os模块的源码：<https://github.com/nodejs/node/blob/master/lib/os.js>，翻到最底部，找到 `cpus`这个方法\n\n- 进而找到 `getCPUs()`\n\n- internalBinding('os')：通过 internalBinding 可以调用系统底层的方法。internalBinding 主要是 JS 虚拟机在做的事情。\n\n- `internalBinding('os')` 的实现，在 <https://github.com/nodejs/node/blob/master/src/node_os.cc> 里，里面都是 C++ 的代码。比如有一个`getCPUs`方法。\n\n现在你知道了，JS本身是没有能力获取底层系统资源的，这一切都是 JS虚拟机在和底层做交互，然后通过 JS 的表现形式，暴露给应用层。\n\n另外，还有很多库，是直接使用C/++编写的，通过编译之后，再提供给 JS 应用层调用，或者直接提供给 Node.js层使用。\n\n**所有的编程语言底层都会回归C/C++**，甚至是汇编语言。\n\n\n### 2、require 加载第三方包的机制\n\n```js\nconst express = require('express');\n```\n\nrequire 加载第三方包的机制：\n\n（1）第三方包安装好后，这个包一般会存放在当前项目的 node_modules 文件夹中。我们找到这个包的 package.json 文件，并且找到里面的main属性对应的入口模块，这个入口模块就是这个包的入口文件。\n\n（2）如果第三方包中没有找到package.json文件，或者package.json文件中没有main属性，则默认加载第三方包中的index.js文件。\n\n（3）如果在 node_modules 文件夹中没有找到这个包，或者以上所有情况都没有找到，则会向上一级父级目录下查找node_modules文件夹，查找规则如上一致。\n\n（4）如果一直找到该模块的磁盘根路径都没有找到，则会报错：can not find module xxx。\n\n### 3、自定义模块（module）：\n\n每个文件就是一个模块，有自己的作用域。在一个文件里面定义的变量、函数、类，都是私有的，对其他文件不可见。\n\n举例：\n\n```\nvar example = require('./example.js');\nconsole.log(example.x); // 5\nconsole.log(example.addX(1)); // 6\n```\n\n## 读取文件\n\n今天这篇文章，重点讲一下 Node 内置模块中的 **fs（文件处理模块）**。\n\n在使用文件模块之前，记得先导入：\n\n```js\n// 导入文件模块\nconst fs = require('fs');\n```\n\nfs 的英文全称是 File System。fs 模块提供了很多 api 方法，我们首先应该学习的方法是**文件读取**。\n\nNode中文件读取的方式主要有以下几种。\n\n\n### 异步读取文件 fs.readFile()\n\n\n\n语法格式：\n\n```js\nfs.readFile(file[, options], callback(error, data))\n```\n\n代码举例：\n\n```javascript\nconst fs = require('fs');\n\nfs.readFile('hello.txt', 'utf8', (err, data) => {\n    if (err) {\n        // 失败\n        console.log(err)\n    } else {\n        // 成功\n        console.log('异步读取数据：' + data2)\n    }\n});\n```\n\n\n如果需要嵌套读取多个文件，可以用 promise 或者 async ... await 进行封装。代码举例如下。\n\n### promise 封装 fs.readFile()\n\n```js\nconst fs = require('fs');\n\nfunction fsRead(path) {\n    return new Promise((resolve, reject) => {\n        fs.readFile(path, { flag: 'r', encoding: \"utf-8\" }, (err, data) => {\n            if (err) {\n                //失败执行的内容\n                reject(err)\n            } else {\n                //成功执行的内容\n                resolve(data)\n            }\n        })\n    })\n}\n\nvar promise1 = fsRead('hello1.txt')\npromise1.then(res1 => {\n    console.log(res1);\n    return fsRead('hello2.txt');\n}).then(res2 => {\n    console.log(res2);\n    return fsRead('hello3.txt');\n}).then(res3 => {\n    console.log(res);\n})\n\n```\n\n### async ... await 封装 fs.readFile()\n\n这个写法更为简洁，推荐。\n\n```js\nvar fs = require('fs');\n\nfunction fsRead(path) {\n    return new Promise((resolve, reject) => {\n        fs.readFile(path, { flag: 'r', encoding: \"utf-8\" }, (err, data) => {\n            if (err) {\n                //失败执行的内容\n                reject(err)\n            } else {\n                //成功执行的内容\n                resolve(data)\n            }\n        })\n    })\n}\n\nasync function ReadList() {\n    var res1 = await fsRead('hello1.txt');\n    var res2 = await fsRead('hello2.txt');\n    var res3 = await fsRead('hello3.txt');\n}\n\n// 执行方法\nReadList();\n\n```\n\n\n### 同步读取文件 fs.readFileSync()\n\n语法格式：\n\n```js\nfs.readFileSync(file[, options])\n```\n\n代码举例：\n\n```javascript\nconst fs = require('fs');\n\ntry {\n  const data = fs.readFileSync('hello.txt', 'utf8');\n  console.log(data);\n} catch(e) {\n  // 文件不存在，或者权限错误\n  throw e;\n}\n```\n\n### Node.js 中的同步和异步的区别\n\nfs模块对文件的几乎所有操作都有同步和异步两种形式。例如：readFile() 和 readFileSync()。\n\n区别：\n\n- 同步调用会阻塞代码的执行，异步则不会。\n\n- 异步调用会将 读取任务 下达到任务队列，直到任务执行完成才会回调。\n\n- 异常处理方面：同步必须使用 try catch 方式，异步可以通过回调函数的第一个参数。【重要】\n\n## 写入文件\n\n语法格式：\n\n```js\nfs.write(fd, string[, position[, encoding]], callback)\n\n```\n\nasync ... await 封装：\n\n```js\nlet fs = require('fs')\n\nfunction writeFs(path, content) {\n    return new Promise(function (resolve, reject) {\n        fs.writeFile(path, content, { flag: \"a\", encoding: \"utf-8\" }, function (err) {\n            if (err) {\n                //console.log(\"写入内容出错\")\n                reject(err)\n            } else {\n                resolve(err)\n                //console.log(\"写入内容成功\")\n            }\n        })\n    })\n}\n\n\nasync function writeList() {\n    await writeFs('1.html', \"<h1>qianguyihao</h1>\");\n    await writeFs('2.html', \"<h1>hello world</h1>\");\n    await writeFs('3.html', \"<h1>永不止步</h1>\");\n}\n\nwriteList()\n```\n\n## 删除文件\n\n语法格式：\n\n```js\nfs.unlink(path, callback)\n```\n\n参数说明：\n\n- path：文件路径。\n- callback：回调函数。\n\n\n代码举例：\n\n```js\nfs.unlink('path/file.txt', (err) => {\n    if (err) throw err;\n    console.log('文件删除成功');\n});\n\n```\n\n备注：`fs.unlink()` 不能用于删除目录。 如果要删除目录，可以使用 `fs.rmdir()`。\n\n\n## Buffer\n\n通过 Buffer 开辟的内存空间，都是连续的内存空间，所以效率比较高。\n\n代码举例1：\n\n```js\n\n// 将字符串转成 buffer 对象\nconst str = 'qianguyihao';\nlet buffer = Buffer.from(str);\n\nconsole.log(buffer); // 输出16进制编码\nconsole.log(buffer.toString()); // 输出字符串：qianguyihao\n```\n\n代码举例2：\n\n```js\n// 从内存中开辟一个新的缓冲区\nlet buffer = Buffer.alloc(20);\nbuffer[0] = 'a';\n\nconsole.log(buffer);\n\n```\n\n\n## 读取目录\n\n\n语法格式：\n\n```js\nfs.mkdir(path[, options], callback)\n```\n\n参数说明：\n\n- path：文件路径。\n\n- options参数可以是：\n    - recursive：是否以递归的方式创建目录，默认为 false。\n    - mode：设置目录权限，默认为 0777。\n\n\n代码举例：\n\n```js\nvar fs = require(\"fs\");\n​\nconsole.log(\"查看 /tmp 目录\");\nfs.readdir(\"/tmp/\",function(err, files){\n   if (err) {\n       return console.error(err);\n   }\n   files.forEach( function (file){\n       console.log( file );\n   });\n});\n\n```\n\n\n其他的还有：（暂略）\n\n- 删除目录\n\n- 输入输出\n\n\n\n\n\n"
  },
  {
    "path": "11-Node.js/06-Node.js内置模块：path路径模块.md",
    "content": "---\ntitle: 06-Node.js内置模块：path路径模块\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## path 路径模块\n\nNode.js 通过`path`这个内置模块，提供了一些路径操作的API，具体可以参考官方的api文档。这里列举一些常用的API。\n\n### path.extname() 获取文件/路径的扩展名\n\n语法格式：\n\n```js\n path.extname(myPath);\n```\n\n代码解释：\n\n- 获取 `myPath` 这个文件或者路径的扩展名。\n\n- `myPath` 这个参数要求是字符串。如果 `myPath` 不是字符串，则抛出 TypeError。\n\n代码举例：\n\n```js\nconst path = require('path');\n\npath.extname('hello.txt'); // 返回 '.txt'\n\npath.extname('www.qianguyihao.com'); // 返回 '.com'\n\npath.extname('index.coffee.md');  // 返回 '.md'\n\npath.extname('index.');  // 返回 '.'\n\npath.extname('index');  // 返回 ''\n\npath.extname('.index');  // 返回 ''\n\npath.extname('.index.md');  // 返回 '.md'\n\n```\n\n### path.resolve() 生成完成的绝对路径\n\n语法格式：\n\n```js\npath.resolve([...myPaths])\n```\n\n解释：\n\n- 将路径或路径片段的序列解析为绝对路径。\n\n- 返回的路径是**从右往左**处理，后面的每个 myPath 被依次解析，直到构造出一个完整的绝对路径。\n\n代码举例：\n\n```js\nconst path = require('path');\n\nlet arr1 = ['/foo1/foo2', 'qianguyihao', 'foo3'];\nlet result1 = path.resolve(...arr1);\nconsole.log(result1); // 打印结果：/foo1/foo2/qianguyihao/foo3\n\nlet arr2 = ['/foo1/foo2', '/qianguyihao', 'foo3'];\nlet result2 = path.resolve(...arr2);\nconsole.log(result2); // 打印结果：/qianguyihao/foo3\n```\n\n### 几个常见路径\n\n- `__dirname`：这是一个常量，表示：当前执行文件所在**完整目录**。\n\n- `__filename`：这是一个常量。表示：当前执行文件的**完整目录 + 文件名**。\n\n- `process.cwd`：获取当前执行 Node命令 时的目录名。\n\n\n代码举例：\n\n```js\nconsole.log(__dirname);\n\nconsole.log(__filename);\n\nconsole.log(process.cwd());\n```\n\n运行结果：\n\n```bash\n$ node app.js\n\n/Users/smyhvae/qianguyihao\n/Users/smyhvae/qianguyihao/app.js\n/Users/smyhvae/qianguyihao\n```\n\n### path.join() 将多个路径进行拼接\n\n如果是我们手动拼接路径，容易出错。这个时候，可以利用 path.join() 方法将路径进行拼接。\n\n语法格式：\n\n```js\npath.join([...paths]);\n\n```\n\n解释：使用平台特定的分隔符作为定界符将所有给定的 path 片段连接在一起，然后规范化生成的路径。\n\n代码举例：\n\n```js\nconst path = require('path');\n\nconst result1 = path.join(__dirname, './app.js');\nconsole.log(result1); // 返回：/Users/smyhvae/qianguyihao/app.js\n\nconst result2 = path.join('/foo1', 'foo2', './foo3');\nconsole.log(result2); // 返回：/foo1/foo2/foo3\n\nconst result3 = path.join('/foo1', 'foo2', '/foo3');\nconsole.log(result3); // 返回：/foo1/foo2/foo3\n```\n\n## OS 系统模块\n\n\n- os.networkInterfaces() 查看网络地址\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n\n\n\n"
  },
  {
    "path": "11-Node.js/07-Node.js操作MySQL数据库.md",
    "content": "---\ntitle: 07-Node.js操作MySQL数据库\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## Node.js 连接 MySQL\n\n（1）安装 mysql 包：\n\n```bash\n$ npm install mysql\n```\n\n（2）引入 mysql 包：\n\n```js\nconst mysql = require(\"mysql\");\n```\n\n（3）建立连接：\n\n```js\nlet mysql = require(\"mysql\");\nlet options = {\n  host: \"localhost\",\n  //port:\"3306\", //可选，默认3306\n  user: \"root\",\n  password: 'xxx', // 这里改成你自己的数据库连接密码\n  database: \"qiangu_database\",\n};\n//创建与数据库进行连接的连接对象\nlet connection = mysql.createConnection(options);\n\n//建立连接\nconnection.connect((err) => {\n  if (err) {\n      // 数据库连接失败\n    console.log(err);\n  } else {\n      // 数据库连接成功\n    console.log(\"数据库连接成功\");\n  }\n});\n```\n\n正常来说，运行程序后，应该会提示`数据库连接成功`。\n\n如果在运行时提示错误`Client does not support authentication protocol requested by server`，解决办法如下：(在终端进入 sql 之后，输入如下命令)\n\n```sql\n# 注意，这里的 'root' 请填你的user账号， 'localhost' 请填 你的 host， 'password' 请填你的密码\nALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';\n\n# 然后执行如下命令\nflush privileges;\n```\n\n## Node.js 增删改查 MySQL\n\n针对下面这张表：\n\n![](https://img.smyhvae.com/20200418_1728.png)\n\n\n通过 Node.js可以对其进行一些增删改查操作。代码举例如下。\n\n### 1、查询表\n\n```js\nlet mysql = require('mysql');\nlet options = {\n    host: 'localhost',\n    //port:\"3306\",//可选，默认3306\n    user: 'root',\n    password: 'xxx', // 这里改成你自己的数据库密码\n    database: 'qiangu_database'\n}\n//创建与数据库进行连接的连接对象\nlet connection = mysql.createConnection(options);\n\n//建立连接\nconnection.connect((err) => {\n    if (err) {\n        // 数据库连接失败\n        console.log(err)\n    } else {\n        // 数据库连接成功\n        console.log('数据库连接成功')\n    }\n});\n\n\n// 1、查询表\nlet strSql1 = 'select * from qiangu_student_table';\nconnection.query(strSql1, (err, result, fields) => {\n    if (err) {\n        // 表查询失败\n        console.log(err);\n    } else {\n        // 表查询成功\n        console.log('qiangu_student_table 表查询结果：' + JSON.stringify(result));\n        console.log('fields:' + JSON.stringify(fields));\n    }\n})\n\n```\n\n打印结果如下：\n\n```bash\nqiangu_student_table 表查询结果：\n[{\"id\":1,\"name\":\"千古壹号\",\"age\":28},{\"id\":2,\"name\":\"许嵩\",\"age\":34},{\"id\":3,\"name\":\"邓紫棋\",\"age\":28}]\n\nfields:[\n    {\"catalog\":\"def\",\"db\":\"qiangu_database\",\"table\":\"qiangu_student_table\",\"orgTable\":\"qiangu_student_table\",\"name\":\"id\",\"orgName\":\"id\",\"charsetNr\":63,\"length\":11,\"type\":3,\"flags\":0,\"decimals\":0,\"zeroFill\":false,\"protocol41\":true},\n    {\"catalog\":\"def\",\"db\":\"qiangu_database\",\"table\":\"qiangu_student_table\",\"orgTable\":\"qiangu_student_table\",\"name\":\"name\",\"orgName\":\"name\",\"charsetNr\":33,\"length\":765,\"type\":253,\"flags\":0,\"decimals\":0,\"zeroFill\":false,\"protocol41\":true},\n    {\"catalog\":\"def\",\"db\":\"qiangu_database\",\"table\":\"qiangu_student_table\",\"orgTable\":\"qiangu_student_table\",\"name\":\"age\",\"orgName\":\"age\",\"charsetNr\":63,\"length\":11,\"type\":3,\"flags\":0,\"decimals\":0,\"zeroFill\":false,\"protocol41\":true}\n]\n```\n\n### 删除表\n\n```js\n// 2、删除表\nlet strSql2 = 'drop table test2_table';\nconnection.query(strSql2, (err, result) => {\n    if (err) {\n        // 表删除失败\n        console.log(err);\n    } else {\n        // 表删除成功\n        console.log('表删除成功：' + result);\n    }\n});\n```\n\n打印结果：\n\n```bash\n表删除成功：\nOkPacket {\n    fieldCount: 0,\n    affectedRows: 0,\n    insertId: 0,\n    serverStatus: 2,\n    warningCount: 0,\n    message: '',\n    protocol41: true,\n    changedRows: 0\n}\n```\n\n### 删除数据库\n\n将上方的sql语句换一下即可：\n\n```sql\nlet strSql3 = 'drop database qiangu_database';\n\n```\n\n\n### 2、新建数据库\n\n```js\nlet mysql = require('mysql');\nlet options = {\n    host: 'localhost',\n    //port:\"3306\",//可选，默认3306\n    user: 'root',\n    password: 'smyhvae001',\n    // database: 'qiangu_database'  // 注意，因为代码里是创建新的数据库，所以这里不需要填其他的数据库名\n}\n//创建与数据库进行连接的连接对象\nlet connection = mysql.createConnection(options);\n\n//建立连接\nconnection.connect((err) => {\n    if (err) {\n        // 数据库连接失败\n        console.log(err);\n    } else {\n        // 数据库连接成功\n        console.log('数据库连接成功')\n    }\n});\n\n// 创建新的数据库\nconst strSql4 = 'create database qiangu_database3';\nconnection.query(strSql4, (err, result) => {\n    if (err) {\n        console.log(err);\n    } else {\n        console.log('新建数据库成功：' + JSON.stringify(result));\n    }\n\n});\n\n\n```\n\n打印结果：\n\n```bash\n数据库连接成功\n新建数据库成功：{\n    \"fieldCount\":0,\"affectedRows\":1,\"insertId\":0,\"serverStatus\":2,\"warningCount\":0,\"message\":\"\",\"protocol41\":true,\"changedRows\":0\n}\n\n```\n\n\n\n### 3、新建表\n\n新建表的sql语句举例：\n\n```sql\nCREATE TABLE `qiangu_table5` (\n    `id` int NOT NULL AUTO_INCREMENT,\n    `name` varchar(255) DEFAULT NULL,\n    `age` int DEFAULT NULL,\n    PRIMARY KEY (`id`)\n);\n```\n\n如果是在 js 代码中执行上面这样命令的话，要记得把 sql 语句存放在字符串里的同一行。\n\n代码举例如下：\n\n```js\nlet mysql = require('mysql');\nlet options = {\n    host: 'localhost',\n    //port:\"3306\",//可选，默认3306\n    user: 'root',\n    password: 'smyhvae001',\n    database: 'qiangu_database'\n}\n//创建与数据库进行连接的连接对象\nlet connection = mysql.createConnection(options);\n\n//建立连接\nconnection.connect((err) => {\n    if (err) {\n        // 数据库连接失败\n        console.log(err);\n    } else {\n        // 数据库连接成功\n        console.log('数据库连接成功')\n    }\n});\n\n\n// 新建表\n// 注意，在 js 代码中，sql 语句要存放在字符串里的同一行。\nconst strSql5 = 'CREATE TABLE `qianguyihao_table5` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`age` int DEFAULT NULL,PRIMARY KEY (`id`));';\n\nconnection.query(strSql5, (err, result) => {\n    if (err) {\n        // 新建表失败\n        console.log(err);\n    } else {\n        // 新建表成功\n        console.log('qianguyihao 新建表成功：' + JSON.stringify(result));\n    }\n})\n\n```\n\n打印结果：\n\n```bash\n数据库连接成功\nqianguyihao 新建表成功：\n{\n    \"fieldCount\":0,\"affectedRows\":0,\"insertId\":0,\"serverStatus\":2,\"warningCount\":0,\"message\":\"\",\"protocol41\":true,\"changedRows\":0\n}\n```\n\n\n### 在指定的表中插入数据\n\n在指定的表中插入数据：\n\n```js\n// 在指定的表中插入数据\nconst strSql6 = \"insert into qianguyihao_table5 (name, age) values ('千古壹号', '28')\";\n\nconnection.query(strSql6, (err, result) => {\n    if (err) {\n        // 插入数据失败\n        console.log(err);\n    } else {\n        // 在指定的表中插入数据成功\n        console.log('qianguyihao 在指定的表中插入数据成功：' + JSON.stringify(result));\n    }\n});\n\n```\n\n打印结果：\n\n```bash\nqianguyihao 在指定的表中插入数据成功：\n{\n    \"fieldCount\":0,\"affectedRows\":1,\"insertId\":1,\"serverStatus\":2,\"warningCount\":0,\"message\":\"\",\"protocol41\":true,\"changedRows\":0\n}\n```\n\n如果插入的数据是变量（比如是用户提交上来的数据），那么，sql 语句可以这样写：\n\n```js\n// 在指定的表中插入数据（数据作为变量）\nconst strSql7 = \"insert into qianguyihao_table5 (name, age) values (?, ?)\";\n\nconnection.query(strSql7, ['许嵩', '34'], (err, result) => {\n    if (err) {\n        // 插入数据失败\n        console.log(err);\n    } else {\n        // 在指定的表中插入数据成功\n        console.log('qiangauyihao 在指定的表中插入数据成功：' + JSON.stringify(result));\n    }\n});\n\n```\n\n\n\n\n"
  },
  {
    "path": "11-Node.js/CommonJS.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 全局对象\n\n### global\n\n类似于客户端 JavaScript 运行环境中的 window。\n\n\n## process\n\n用于获取当前的 Node 进程信息，一般用于获取环境变量之类的信息。\n\n### console\n\nNode 中内置的 console 模块，提供操作控制台的输入输出功能，常见使用方式与客户端类似。\n\n## 全局函数\n\n- setInterval(callback, millisecond)\n\n- clearInterval(timer)\n\n- setTimeout(callback, millisecond)\n\n- clearTimeout(timer)\n\n- Buffer：Class\n\t- 用于操作二进制数据\n\t- 以后介绍\n\n\n## Node 调试\n\n### 最简单的调试\n\n最方便也是最简单的调试：console.log()\n\n\n### Node 原生的调试\n\n网址：<https://nodejs.org/api/debugger.html>\n\n### 第三方模块提供的调试工具\n\n```\n$ npm install node-inspector –g   //方式一\n\n\n$ npm install devtool -g          //方式二\n```\n\n### 开发工具的调试\n\n- Visual Studio Code\n\n- WebStorm\n\n## 模块化结构\n\nNode 实现 CommonJS 规范，所以可以使用模块化的方式组织代码结构。\n\n- Node 采用的模块化结构是按照 CommonJS 规范。\n\n- 模块与文件是一一对应关系，即加载一个模块，实际上就是加载对应的一个模块文件。\n\n### CommonJS 规范\n\nCommonJS 就是一套约定标准，不是技术。用于约定我们的代码应该是怎样的一种结构。\n\n参考链接：\n\n- <http://wiki.commonjs.org/wiki/CommonJS>\n\n### 常用内置模块\n\n- `path`：处理文件路径。\n\n- `fs`：操作（CRUD）文件系统。\n\n- `child_process`：新建子进程。\n\n- `util`：提供一系列实用小工具。\n\n- `http`：提供 HTTP 服务器功能。\n\n- `url`：用于解析 URL。\n\n- `querystring`：解析 URL 中的查询字符串。\n\n- `crypto`：提供加密和解密功能。\n\n\n总结：更多内容可以参考 api文档：<https://nodejs.org/api/>\n\n\n## 文件系统操作\n\n### 相关模块\n\n- fs：基础的文件操作 API\n\n- path：提供和路径相关的操作 API\n\n- readline：用于读取大文本文件，一行一行读\n\n- fs-extra（第三方）：<https://www.npmjs.com/package/fs-extra>\n\n\n\n\n\n\n"
  },
  {
    "path": "11-Node.js/ES6.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 前言\n\nECMAScript 是 JS 的语言标准。而 ES6 是新的 JS 语法标准。\n\n\n### 发展历史\n\n20180303_1633.png\n\n- 2015年6月，ES6正式发布。\n\n\n### ES6 的其他优势\n\n- 使用 babel 语法转换器，支持低端浏览器\n\n- 流行的库基本都是基于 ES6 构建。 React 默认使用 ES6 新源发开发。\n\n\n## ES6 的常用语法\n\n### ES6语法概览\n\n- 块级作用域、字符串\n\n- 对象扩展、解构\n\n- 类、模块化等。\n\n\n### 作用域：let 和 const\n\n- 用 `let`定义变量 ，替代 var\n\n- 用const 定义常量（定义后，不可修改）\n\n- 作用域和 {}\n\n\n举例：\n\n```javascript\n    let a1 = 'haha';\n\n    const name = `smyhvae`;\n```\n\n\n### 模板字符串\n\n我们以前让字符串进行拼接的时候，是这样做的：（传统写法的字符串拼接）\n\n```javascript\n    var name = 'smyhvae';\n    var age = '26';\n    console.log('name:'+name+',age:'+age);   //传统写法\n```\n\n\n这种写法，比较繁琐，而且容易出错。\n\n现在有了 ES6 语法，字符串拼接可以这样写：\n\n```javascript\n    var name = 'smyhvae';\n    var age = '26';\n\n    console.log('name:'+name+',age:'+age);   //传统写法\n\n    console.log(`name:${name},age:${age}`);  //ES6 写法\n\n```\n\n注意，上方代码中，倒数第二行用的是单引号，最后一行用的是反引号（在tab键的上方）。\n\n\n### 函数扩展\n\nES6 中函数的用法：\n\n- 参数默认值\n\n- 箭头函数\n\n- 展开运算符\n\n\n\n定义和调用函数：（传统写法）\n\n```javascript\n    function fn1(name) {\n        console.log(name);\n    }\n\n    fn1('smyhvae');\n```\n\n\n定义和调用函数：（ES6写法）\n\n```javascript\n    var fn2 = (name)=>{\n        console.log(name);\n    }\n\n    fn2('smyhvae');\n```\n\n\n上面两端代码，执行的结果是一样的。\n\n当然，也可以给上面这个函数的参数加一个默认值：\n\n```javascript\n    var fn2 = (name='enen')=>{\n        console.log(name);\n    }\n\n    fn2();        //参数用默认值 enen\n    fn2('smyhvae');\n```\n\n\n\n比如说，1秒后执行一段代码，可以用箭头函数：\n\n```javascript\n    setTimeout(()=>{\n        console.log('something');\n    },1000);\n```\n\n如果函数体只有一条 return 语句，那么大括号可以省略：\n\n```javascript\n    const myDouble = x=>x*2;\n    console.log(myDouble(5));  //打印结果为10\n\n```\n\n\n箭头函数的好处：\n\n- 简写代码\n\n- 保持 this 的作用域\n\n\n\n##"
  },
  {
    "path": "11-Node.js/JavaScript模块化：AMD.md",
    "content": "---\ntitle: JavaScript模块化：AMD\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## AMD的基本语法\n\n### AMD的概念\n\n**AMD**（Asynchronous Module Definition）：异步模块定义。AMD专门用于浏览器端，模块的加载是异步的。\n\n[**AMD规范**](https://github.com/amdjs/amdjs-api)：是 **[RequireJS](http://requirejs.org/)** 在推广过程中对模块化定义的规范化产出。\n\nRequireJS：一个基于AMD规范实现的模块化开发解决方案。\n\n\n### 暴露模块的方式\n\n**定义没有依赖的模块**：（参数只有一个 function）\n\n```javascript\ndefine(function () {\n\n    return 模块\n\n})\n```\n\n\n**定义有依赖的模块**：（参数有两个：模块名、function）\n\n```javascript\n//定义有依赖的模块：第一个参数为数组\ndefine(['module1', 'module2'], function (m1, m2) {\n\n    return 模块\n\n})\n```\n\n代码解释：\n\n- 第一个参数必须是数组，里面存放的是，需要依赖的其他的模块。\n\n- 第二个参数是function，里面带了形参 m1 和 m2，分别代表了 module1 和 module2。这个形参的作用是，前面依赖的模块一旦声明了，就可以一一对应地注入到 function中去，从而在 function 内部使用依赖的模块。这种方式称之为**显式声明依赖注入**。\n\n### 引入模块的方式\n\n在主模块中引入其他的模块：\n\n\n```javascript\n//在主模块中引入其他的模块\nrequire(['module1', 'module2'], function (m1, m2) {\n\n    使用m1 / m2\n\n})\n```\n\n### RequireJS：是AMD的实现\n\n- <http://www.requirejs.org/>\n\n- <http://www.ruanyifeng.com/blog/2012/11/require_js.html>\n\n## RequireJS的使用举例（自定义模块）\n\n### 1、创建项目结构\n\n在工程文件中新建如下目录：\n\n\n  ```\njs\n    | libs\n\n    | modules\n      \t| alerter.js\n      \t| dataService.js\n    | main.js\n\nindex.html\n  ```\n\n所有的代码写完之后，项目结构如下：\n\n![](http://img.smyhvae.com/20180411_1331.png)\n\n\n### 2、下载require.js，并导入\n\n- 官网: <http://requirejs.org/docs/download.html>\n\n- GitHub：<https://github.com/requirejs/requirejs>\n\n在官网下载`require.js`文件：\n\n![](http://img.smyhvae.com/20180411_1127.png)\n\n然后将`require.js`文件拷贝到项目的`js/libs/`目录中。\n\n这样的话，就导入成功了。\n\n### 3、自定义模块\n\n（1）dataService.js：\n\n```javascript\n//定义没有依赖的模块\ndefine(function () {\n    let name = '我是 dataService.js中的内容';\n    function getName() {\n        return name;\n    }\n\n    //暴露模块\n    return { getName };\n});\n```\n\n\n这模块没有依赖。\n\n（2）alerter.js：\n\n```javascript\n//定义有依赖的模块\ndefine(['myDataService'], function (dataService) {\n    let msg = '我是 aleter.js中的内容';\n    function showMsg() {\n        console.log(dataService.getName());  //调用了 myDataService 模块中的内容\n        console.log(msg);\n    }\n\n    //暴露模块\n    return { showMsg };\n\n});\n```\n\n这个模块，依赖了`myDataService`这个模块，模块名是我自己起的。稍后，我们会在main.js中做映射，将`myDataService`这个名字和`dataService.js`文件关联起来。\n\n（3）main.js：\n\n> 这个是主模块。\n\n```javascript\nrequirejs.config({\n    //baseUrl: 'js/',     //基本路径\n    paths: {    //配置路径\n        myDataService: './modules/dataService',\n        myAlerter: './modules/alerter'\n    }\n});\n\nrequirejs(['myAlerter'], function (alerter) {\n    alerter.showMsg();\n})();\n```\n\n这个模块，依赖了`myAlerter`这个模块，模块名是我自己起的。并且，我们在文件的上方做了映射，将`myAlerter`这个名字和`alerter.js`文件关联了起来。\n\n\n我们来讲一下最上方的几行代码（即`requirejs.config`里的内容）的意思：\n\n- 我们可以看到，文件（3）依赖了文件（2），文件（2）依赖了文件（1）。\n\n- `paths`里做的就是映射：将键`myDataService`和文件`dataService.js`进行关联，将键`myAlerter`和文件`alerter.js`进行关联。\n\n另外，再讲一下注释里的`baseUrl`的用法：如果没有这个注释，那么`paths`里的路径，是从**当前这个文件**（main.js）的角度出发的；如果加了一行`baseUrl`，表明它是 paths 里所有路径的最开头的部分，`baseUrl`的路径是从**项目的根目录**的角度出发的。\n\n（4）index.html：\n\n这个是入口文件。\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n</head>\n<body>\n    <!-- 先通过 src 引入 require.js 文件，然后通过 data-main 引入主模块（main.js） -->\n    <script data-main=\"js/main.js\" src=\"js/libs/require.js\"></script>\n</body>\n</html>\n```\n\n注意，上面的代码中，我们直接通过`src`属性引入`requre.js `文件，一旦这个文件发挥作用了，会去找`data-main`属性里的指向，它正好指向的是主模块。\n\n有了上面这种引入的方式，我们就不用再老土地引入多个`<script>`标签了。\n\n\n运行 index.html，打印结果如下：\n\n![](http://img.smyhvae.com/20180411_1740.png)\n\n项目源码：[2018-04-11-RequireJSDemo](https://download.csdn.net/download/smyhvae/10341963)\n\n\n\n\n"
  },
  {
    "path": "11-Node.js/JavaScript模块化：CMD.md",
    "content": "---\ntitle: JavaScript模块化：CMD\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## CMD的基本语法\n\n### CMD的概念\n\n**CMD**（Common Module Definition）：同步模块定义。CMD专门用于浏览器端，模块的加载是同步的。模块在使用时才会加载执行。\n\n[**CMD规范**]()：是 **[SeaJS](http://seajs.org/)** 在推广过程中对模块化定义的规范化产出。\n\n\n### SeaJS\n\nSeaJS：一个基于CMD规范实现的模块化开发解决方案。\n\n官网链接：\n\n- <http://seajs.org/>\n\n- <https://github.com/seajs/seajs>\n\n推荐学习链接：\n\n- <http://www.zhangxinxu.com/sp/seajs/>\n\n- <http://es6.ruanyifeng.com/#docs/module>\n\n\n### 暴露模块的方式\n\n> 不管是定义没有依赖的模块，还是定义有依赖的模块，参数只有一个，那就是 function。\n\n**定义没有依赖的模块**：\n\n```javascript\ndefine(function (require, exports, module) {\n\n    exports.xxx = value\n\n    //暴露模块\n    module.exports = value\n\n})\n```\n\n参数只有一个，那就是 function。function 里有三个参数：\n\n\n**定义有依赖的模块**：\n\n```javascript\n//定义有依赖的模块\ndefine(function (require, exports, module) {\n\n    //引入依赖的模块(同步的方式)\n    var module2 = require('./module2')\n\n    //引入依赖的模块(异步的方式)\n    require.async('./module3', function (m3) {\n\n    })\n\n    //暴露模块\n    exports.xxx = value\n})\n\n```\n\n上面的代码可以看到，在引入依赖的模块时，有两种引入的方式：同步和异步。\n\n\n### 引入模块的方式\n\n```javascript\ndefine(function (require) {\n\n    var m1 = require('./module1')\n    var m4 = require('./module4')\n\n    m1.show()\n    m4.show()\n})\n```\n\n\n\n\n## SeaJS的使用举例（自定义模块）\n\n\n\n### 1、创建项目结构\n\n\n在工程文件中新建如下目录：\n\n\n```\njs\n    | libs\n      \t| sea.js\n    | modules\n      \t| module1.js\n      \t| module2.js\n      \t| module3.js\n      \t| module4.js\n      \t| main.js     //主模块\nindex.html\n```\n\n\n### 2、下载SeaJS，并导入\n\n- 官网: <https://seajs.github.io/seajs/docs/#downloads>\n\n- GitHub：<https://github.com/seajs/seajs>\n\n在官网下载`sea.js`文件，然后将其拷贝到项目的`js/libs/`目录中。这样的话，就导入成功了。\n\n\n### 3、自定义模块\n\n\n（1）module1.js：\n\n\n\n```javascript\n//定义没有依赖的模块\ndefine(function (require, exports, module) {\n    let name = '我是 module1 中的内容';\n    function foo1() {\n        return name;\n    }\n\n    //暴露模块\n    module.exports = { foo1 };  //暴露出去的是 foo1这个函数对象\n});\n```\n\n\n（2）module2.js：\n\n```javascript\n//定义没有依赖的模块\ndefine(function (require, exports, module) {\n    let name = '我是 module2 中的内容';\n    function foo2() {\n        console.log(name);\n    }\n\n    //暴露模块\n    module.exports = foo2;  //可以理解成：exports就是 foo2 这个函数\n});\n```\n\n（3）module3.js:\n\n```javascript\n//定义没有依赖的模块\ndefine(function (require,exports,module) {\n    let data = '我是 module3 中的内容';\n    function foo3() {\n        console.log(data);\n    }\n\n    //暴露模块\n    exports.module3 = { foo3 };  //可以理解成：给 export 对象暴露了 module3 这个属性，这个属性里有foo3 这个函数。\n});\n```\n\n（4）module4.js：\n\n这个模块依赖了 module2 和 module3。\n\n```javascript\n//定义有依赖的模块\ndefine(function (require, exports, module) {\n    let name = '我是 module4 中的内容';\n\n    //同步的方式引入 module2\n    let myModule2 = require('./module2');\n    myModule2();\n\n    //异步的方式引入 module3\n    require.async('./module3', function (myModule3) {\n        myModule3.module3.foo3();\n    });\n\n    function foo4() {\n        console.log(name);\n    }\n\n    exports.foo4 = foo4;\n})\n```\n\n（5）main.js：\n\n- `module1.js`没有依赖其他的模块，它是独立的\n\n- `module4.js`依赖了`module2`和`module3`。\n\n因此，让`main.js`依赖`module1.js`和`module4`就够了。\n\nmain.js：\n\n```javascript\n//主模块（主模块不需要导出）\ndefine(function (require) {\n\n    //导入 module1\n    let module1 = require('./module1');\n    console.log(module1.foo1());  //执行foo1函数后，将返回值打印\n\n    //导入 module4\n    let module4 = require('./module4');\n    module4.foo4();\n\n});\n\n```\n\n\n（6）index.html：\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n</head>\n\n<body>\n    <!-- 引入 sea.js库 -->\n    <script src=\"js/libs/sea.js\"></script>\n    <script>\n        // 引入主模块\n        seajs.use('./js/modules/main.js');\n    </script>\n</body>\n\n</html>\n\n```\n\n打印结果：\n\n![](http://img.smyhvae.com/20180412_1955.png)\n\n\n\n\n\n\n## others\n\n\n### SeaJS 的介绍\n\nSeaJS：一个基于CMD规范实现的模块化开发解决方案。\n\n作者：Alibaba 玉伯。\n\n官网：<http://seajs.org/>\n\nGitHub：<https://github.com/seajs/seajs>\n\n现在官网变成了：<https://seajs.github.io/seajs/docs/>\n\n特性：\n\n- 简单友好的模块定义规范。\n\n- 自然直观的代码组织方式。\n\n![](http://img.smyhvae.com/20180303_2107.png)\n\n### RequireJS（AMD）、SeaJS（CDM）、CommonJS、ES6 的对比\n\n1、RequireJS 和 AMD：\n\n![](http://img.smyhvae.com/20180303_1653.png)\n\n异步模块定义，特点是依赖前置。\n\n2、SeaJS 和 CMD：\n\n\n同步模块定义。\n\n```javascript\n  // 所有模块都通过 define 来定义\n  define(function(require, exports, module) {\n\n        //通过 require 引入依赖\n\n        var $ require(`jquery`);\n\n        var Spinning = require(`./spinning`);\n  })\n```\n\n3、CommonJS：\n\n![](http://img.smyhvae.com/20180303_1701.png)\n\n以上三个都是 ES5里面的规范。\n\n4、ES6：\n\nES6的特性：export/import\n\n![](http://img.smyhvae.com/20180303_1704.png)\n\n\n"
  },
  {
    "path": "11-Node.js/JavaScript模块化：ES6.md",
    "content": "---\r\ntitle: JavaScript模块化：ES6\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n\r\n\r\n## 模块化开发的引入\r\n\r\n### JS开发的弊端\r\n\r\nJS 在使用时存在两大问题，而 Node.js 可以很好地避免这两个问题：\r\n\r\n- 文件依赖。比如 a 文件依赖 b 文件，b 文件依赖 c 文件。而 Node.js 中的文件依赖，不需要人工维护和人为分析。\r\n\r\n- 命名冲突。js 的各个文件是相互开放的，容易导致命名冲突。而 Node.js 是属于半封闭的状态，可以指定哪些内容是开放的，哪些内容是封闭的。\r\n\r\nNode.js 在解决这两个问题时，用到的就是模块化开发。\r\n\r\n### 软件开发中的模块化开发\r\n\r\n一个功能就是一个模块，多个模块可以组成完整的应用，抽离一个模块不会影响其他功能的运行。\r\n\r\n效果如下：\r\n\r\n![](http://img.smyhvae.com/20200409_1934.png)\r\n\r\n### Node.js 中的模块化开发\r\n\r\nNode.js 规定，一个 JS 文件就是一个模块，模块内部定义的变量和函数默认情况下在外部无法访问。\r\n\r\n模块内部可以使用 `exports` 对象进行成员导出， 使用 `require` 方法导入其他模块。效果如下：\r\n\r\n![](http://img.smyhvae.com/20200409_1932.png)\r\n\r\n## ES6模块化的基本语法\r\n\r\n### ES6模块化的说明\r\n\r\n**依赖模块需要编译打包处理**。原因如下：\r\n\r\n- （1）有些浏览器不支持 ES6 的语法，写完 ES6 的代码后，需要通过`Babel`将 ES6 转化为 ES5。\r\n\r\n- （2）生成了ES5之后，里面仍然有`require`语法，而浏览器并不认识`require`这个关键字。此时，可以用 `Browserify`编译打包 js，进行再次转换。\r\n\r\n推荐学习链接：\r\n\r\n- <http://es6.ruanyifeng.com/#docs/module>\r\n\r\n\r\n### 基本语法：\r\n\r\n\r\n**导出模块**：\r\n\r\n```\r\n\texport\r\n```\r\n\r\n\r\n**引入模块**：\r\n\r\n```\r\n\timport xxx from '路径'\r\n```\r\n\r\n\r\n## ES6模块化的使用举例（自定义模块）\r\n\r\n### 1、初始化项目\r\n\r\n（1）在工程文件中新建如下目录：\r\n\r\n\r\n```\r\njs\r\n    | src\r\n    \t| module1.js\r\n    \t| module2.js\r\n    \t| module3.js\r\n    \t| main.js\r\n\r\n\r\nindex.html\r\n```\r\n\r\n（2）在工程的根目录下，新建文件`package.json`，内容如下：\r\n\r\n```json\r\n{\r\n    \"name\": \"es6-babel-browserify\",\r\n    \"version\": \"1.0.0\"\r\n}\r\n```\r\n\r\n### 2、环境配置：安装babel 和 browserify等\r\n\r\n（1）安装babel 和 browserify：\r\n\r\n```bash\r\n\tnpm install babel-cli -g\r\n\r\n\tnpm install babel-preset-es2015 --save-dev\r\n\r\n\tnpm install browserify -g\r\n```\r\n\r\n\r\n安装 babel 的详细解释，可以参考本人的另外一篇文章：[ES6的介绍和环境配置](https://github.com/smyhvae/Web/blob/master/10-ES6/03-ES6%E7%9A%84%E4%BB%8B%E7%BB%8D%E5%92%8C%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.md)\r\n\r\n（2）新建.babelrc：\r\n\r\n在根目录下新建文件`.babelrc`，输入如下内容：\r\n\r\n```\r\n{\r\n    \"presets\":[\r\n        \"es2015\"\r\n    ],\r\n    \"plugins\":[]\r\n}\r\n```\r\n\r\n### 3、编写代码\r\n\r\n\r\n（1）module1.js：\r\n\r\n```javascript\r\n//暴露模块：采用分别暴露的方式\r\n\r\nexport function foo1() {\r\n    console.log('我是 module1 中的 foo1');\r\n}\r\n\r\nexport function foo2() {\r\n    console.log('我是 module2 中的 foo2');\r\n}\r\n\r\nexport let arr = [1, 2, 3, 4, 5];\r\n```\r\n\r\n\r\n\r\n（2）module2.js：\r\n\r\n```javascript\r\n//暴露模块：采用统一暴露的方式\r\n\r\nfunction fn1() {\r\n    console.log('我是 module2 中的 fn1');\r\n}\r\n\r\nfunction fn2() {\r\n    console.log('我是 module2 中的 fn2');\r\n}\r\n\r\n//统一暴露\r\nexport { fn1, fn2 };\r\n```\r\n\r\n\r\n（3）module3.js：\r\n\r\n\r\n\r\n```javascript\r\n//暴露模块：采用默认暴露的方式。\r\n//默认暴露的方式可以暴露任意数据类型，暴露的是什么数据，接收到的就是什么数据\r\n\r\n//语法格式：export default value;\r\nexport default () => {\r\n    console.log('我是 module3 中 default 方式暴露的函数');\r\n};\r\n```\r\n\r\n\r\n这里，我们采取了一种新的暴露方式（默认暴露），在暴露时，加上了`default`这个关键字。代码里暴露了一个箭头函数，稍后，我们注意在main.js里是怎么引入module3.js的。\r\n\r\n注意，我们只能写一次 default，也就是说，只能进行一次默认暴露。\r\n\r\n（4）module4.js：（default方式暴露多个属性）\r\n\r\n```javascript\r\n//暴露模块：采用默认暴露的方式。\r\n//默认暴露的方式可以暴露任意数据类型，暴露的是什么数据，接收到的就是什么数据\r\n\r\n//语法格式：export default value;\r\nexport default {\r\n    name: '我是 module4 中 default 方式暴露的属性 name',\r\n    foo() {\r\n        console.log('我是 module4 中 default 方式暴露的函数 foo');\r\n    }\r\n}\r\n```\r\n\r\n这里，我们依旧采取了默认暴露的方式，只能写一次 default。代码里暴露了一个对象（对象里存放了一个属性、一个方法）。稍后，我们注意在main.js里是怎么引入module4.js的。\r\n\r\n如果我想暴露多个属性、多个对象怎呢？很简单，把你想要暴露的所有内容，都放在default里，包成一个对象。你看module4.js就是如此， 同时暴露了多个属性&方法。\r\n\r\n（5）main.js：\r\n\r\n这个是主模块。现在，我们来看一下，它如何引入上面的四个模块。\r\n\r\n\r\n```javascript\r\n\r\n//主模块。引入其他的模块\r\n\r\nimport { foo1, foo2 } from './module1'; //采用解构赋值的形式进行导入。注意，括号里的对象名，要和 module1 中的对象名一致。\r\nimport { fn1, fn2 } from './module2';   //采用解构赋值的形式进行导入。注意，括号里的对象名，要和 module2 中的对象名一致。\r\nimport myModule3 from './module3';   //module3 模块是采用 default 方式进行暴露的，myModule3 这个名字是我随便起的\r\nimport myModule4 from './module4';   //module4 模块是采用 default 方式进行暴露的，myModule4 这个名字是我随便起的\r\n\r\n//调用module1、module2中的内容\r\nfoo1();\r\nfoo2();\r\nfn1();\r\nfn2();\r\n\r\n//调用module3中的内容\r\nmyModule3();\r\n\r\n//调用module4中的内容\r\nconsole.log(myModule4.name);  //module4中的属性\r\nmyModule4.foo();              //module4中的方法\r\n```\r\n\r\n我们可以看出：（具体请看注释，非常重要）\r\n\r\n- module1和module2是采用**常规暴露**的形式，在引入它们时，模块名要一致。而且，要求用**对象解构赋值**的形式，而不是用 `import myModule from ...`这种形式（否则会报错 undefined）。\r\n\r\n- module2和module3是采用**默认暴露**的形式，在引入它们时，模块名随便起。\r\n\r\n（6）index.html：\r\n\r\n在这里引入main.js即可。\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n</head>\r\n\r\n<body>\r\n    <script src=\"dist/main.js\"></script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n\r\n### 4、编译转换\r\n\r\n如果我们不进行转换，而是直接在 index.html 中加载 js/src/main.js，是会报错的：\r\n\r\n\r\n接下来，我们就进行转换。\r\n\r\n（1）利用  babel 将 ES6 转换为 ES5：\r\n\r\n```\r\nbabel src -d build      //build目录会自动生成\r\n```\r\n\r\n上方命令的意思是，将`src`目录下的所有ES6文件转化为ES5文件，并放在`build`目录下（`build`目录会被自动创建）。\r\n\r\n转化成ES5之后，我们发现，如果直接在 index.html 中加载`build`目录下的ES5文件，也是会报错的，因为浏览器不认识`main.js`里的`require`关键字：\r\n\r\n![](http://img.smyhvae.com/20180414_1410.png)\r\n\r\n\r\n于是，我们还要进行一次转换。\r\n\r\n（2）利用`Browserify`编译打包 `build`目录下的 ES5 文件：\r\n\r\n```bash\r\nbrowserify build/main.js -o dist/main.js     //dist目录需要手动创建\r\n```\r\n\r\ndist/main.js就是我们需要引入到 index.html 里的文件。\r\n\r\n以后，我们每次修改完ES6的代码，就要执行上面的两个命令，重新生成新的js文件。\r\n\r\n\r\n运行效果：\r\n\r\n![](http://img.smyhvae.com/20180414_1615.png)\r\n\r\n\r\n工程文件：[2018-04-13-ES6Demo.rar](https://github.com/qianguyihao/web-resource/blob/main/project/2018-04-13-ES6Demo.rar)\r\n\r\n\r\n## ES6模块化的使用举例（引入第三方模块）\r\n\r\n下载 jQuery 包：\r\n\r\n```\r\nnpm install jquery@1      //下载jQuery 1.X 的版本里最新的\r\n```\r\n\r\n在main.js 中引入上面的 jQuery：\r\n\r\n```\r\nimport $ from 'jQuery';\r\n```\r\n\r\n\r\n然后我们就可以通过`$`这个符号去写jQuery的代码了。\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "11-Node.js/KOA2.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## KOA2 简介\n\nKOA已经发展到了第二个版本，简称 KOA2。突出的特点是插件和中间件，\n\n"
  },
  {
    "path": "11-Node.js/Node.js代码举例.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 在 Node.js 上建一个 http 服务器\n\n\n（1）新建一个文件 `server01.js`，然后在里面输入如下代码：\n\n```javascript\n\nconst http = require('http');  //引入 node.js里面的一个http包。因为引入之后，我们不会去修改它，所以用常量来表示\n\n\n// 创建一台服务器\nvar server =  http.createServer(function (){   //当有人来访问这台服务器时，就会执行 function 回调函数\n    console.log('有人来访问我了');\n});\n\nserver.listen(8080);   //要让服务器设置为监听状态，端口设置为8080\n\n```\n\n注意看注释。\n\n我们把上面这个 js 文件跑起来，然后在浏览器端输入`http://localhost:8080/`，每请求一次，服务器的控制台就会打印 `有人来访问我了`。\n\n\n（2）write()函数和 end()函数：\n\n将上面的代码修改如下：\n\nserver02.js：\n\n```javascript\nconst http = require('http');\n\n\n// 创建一台服务器\nvar server = http.createServer(function (request, response) {   //当有人来访问这个服务器时，就会执行function 这个回调函数\n    console.log('有人来访问我了');\n\n    response.write('smyhvae');  //向浏览器输出内容\n    response.end();   //结束了，浏览器你走吧。\n\n});\n\nserver.listen(8080);\n\n\n```\n\nfunction 回调函数里可以设置两个参数：request 和 response。`response.write()`表示向浏览器输出一些内容。\n\n将上面的 js 代码跑起来，产生的问题是，无论我们在浏览器端输入`http://localhost:8080/1.html`，还是输入`http://localhost:8080/2.jpg`，浏览器上显示的都是`smyhvae`。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "11-Node.js/WebSocket.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## WebSocket 的引入\n\n### 背景分析\n\nHTTP协议是无状态的，服务器只会响应来自客户端的请求，但是它与客户端之间不具备持续连接。\n\n\n当用户在浏览器上进行操作时，可以请求服务器上的api；但是反过来却不可能：服务器端发生了一个事件，无法将这个事件的信息**实时主动**地通知客户端。只有在客户端查询服务器当前状态时，所发生事件的信息才会从服务器传递到客户端。\n\n\n那怎么去实时地知道服务器的状态呢？方法有两个：\n\n（1）**轮询**：客户端每隔很短的时间，都会对服务器发出请求，查看是否有新的消息，只要轮询速度足够快，例如1秒，就能给人造成交互是实时进行的印象。这种做法是无奈之举，实际上对服务器、客户端双方都造成了大量的性能浪费。\n\n\n（2）**长连接**：客户端只请求一次，但是服务器会将连接保持，不会返回结果。当服务器有了新数据时，实时地发给客户端，而一直保持挂起状态。这种做法的也造成了大量的性能浪费。\n\n### WebSocket 协议\n\n最新的 HTML5协议，制定了 WebSocket 协议标准，允许客户端和服务器端以**全双工**的方式进行通信。\n\nWebSocket 的原理非常简单：利用HTTP请求产生握手，HTTP头部含有 WebSocket 协议的请求，**握手之后，二者转用TCP协议进行交流*（QQ的协议）。\n\nWebSocket协议需要浏览器和服务器都支持才可以使用：\n\n\n- 支持WebSocket协议的浏览器有：Chrome 4、火狐4、IE10、Safari5\n\n- 支持WebSocket协议的服务器有：Node 0、Apach7.0.2、Nginx1.3\n\n\n### http 长连接和 websocket 的长连接区别\n\nHTTP1.1通过使用Connection:keep-alive进行长连接，HTTP 1.1默认进行持久连接。在一次 TCP 连接中可以完成多个 HTTP 请求，但是对每个请求仍然要单独发 header，Keep-Alive不会永久保持连接，它有一个保持时间，可以在不同的服务器软件（如Apache）中设定这个时间。\n\nwebsocket是一个真正的全双工。长连接第一次tcp链路建立之后，后续数据可以双方都进行发送，**不需要发送请求头**。\n\nkeep-alive双方并没有建立正真的连接会话，服务端可以在任何一次请求完成后关闭。WebSocket 它本身就规定了是正真的、双工的长连接，两边都必须要维持住连接的状态。\n\n### Socket.IO 的引入\n\nNode.js上需要写一些程序，来处理TCP请求。\n\nNode.js从诞生之日起，就支持 WebSocket 协议。不过，从底层一步一步搭建一个Socket服务器很费劲（想象一下Node写一个静态文件服务都那么费劲）。所以，有大神帮我们写了一个库 Socket.IO。\n\nSocket.IO 是业界良心，新手福音。它屏蔽了所有底层细节，让顶层调用非常简单。并且还为不支持 WebSocket 协议的浏览器，提供了长轮询的透明模拟机制。\n\nNode的单线程、非阻塞I/O、事件驱动机制，使它非常适合Socket服务器。\n\n### Socket.IO 的安装\n\nSocket.IO 的官网是：<http://socket.io/>\n\n安装方式：\n\n```\n\tnpm install socket.io\n```\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "11-Node.js/事件驱动和非阻塞机制.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 异步编程\n\n### 异步操作\n\n- Node 采用 Chrome V8 引擎处理 JavaScript 脚本。V8 最大特点就是**单线程运行**，一次只能运行一个任务。\n\n- Node 大量采用异步操作（asynchronous operation），即任务不是马上执行，而是插在任务队列的尾部，等到前面的任务运行完后再执行。\n\n- 提高代码的响应能力。\n\n\n异步IO也叫非阻塞IO。例如读文件，传统的语言，基本都是读取完毕才能进行下一步操作。非阻塞就是Node的callback，不会影响下一步操作，等到文件读取完毕，回调函数自动被执行，而不是在等待。\n\n### 异步操作回调\n\n由于系统永远不知道用户什么时候会输入内容，所以代码不能永远停在一个地方。\n\nNode 中的操作方式就是以异步回调的方式解决无状态的问题。\n\n\n### 回调函数的设计：错误优先\n\n异步操作中，无法通过 try catch 捕获异常。\n\n这是因为回调函数主要用于异步操作，当回调函数运行时，前期的操作早结束了，错误的执行栈早就不存在了，传统的错误捕捉机制try…catch对于异步操作行不通，所以只能把错误交给回调函数处理。\n\n**统一约定：**\n\n回调函数的第一个参数默认接收错误信息，第二个参数才是真正的回调数据（便于外界获取调用的错误情况）：\n\n```\nfoo1('赵小黑', 19, function(error, data) {\n  if(error)  throw error;\n  console.log(data);\n});\n```\n\n\n### 异步回调的问题\n\n相比较于传统的代码：\n\n- 异步事件驱动的代码\n\n- 不容易阅读\n\n- 不容易调试\n\n- 不容易维护\n\n另外还有个问题是**回调地狱：**\n\n```javascript\ndo1(function() {\n  do2(function() {\n    do3(function() {\n      do4(function() {\n        do5(function() {\n          do6()\n        });\n      });\n    });\n  });\n});\n\n```\n\n\n## 进程和线程\n\n### 进程（进行中的程序）\n\n- 每一个 **正在运行** 的应用程序都称之为进程。\n\n- 每一个应用程序运行都至少有一个进程。\n\n- 进程是用来给应用程序提供一个运行的环境。\n\n- 进程是操作系统为应用程序分配资源的一个单位。\n\n\n### 线程\n\n- 用来执行应用程序中的代码\n\n- 在一个进程内部，可以有很多的线程\n\n- 在一个线程内部，同时只可以干一件事\n\n- 传统的开发方式大部分都是 I/O 阻塞的，所以需要多线程来更好的利用硬件资源。\n\n线程并不是越多越好。\n\n### 多线程的弊端\n\n缺点一：\n\n\t- 创建线程耗费。\n\t- 线程数量有限。\n\t- CPU 在不同线程之间转换，有个上下文转换，这个转换非常耗时。\n\n所谓的多线程其实都是假的，对于单核CPU而言，它们无非是在抢占 CPU 资源。线程和线程之间需要**切换和调度**，这是很耗费资源的。\n\n缺点二：\n\n- 线程之间共享某些数据，同步某个状态都很麻烦。\n\n就算 CPU 是多核的，现在的问题是，线程与线程之间如果要共享数据，该怎么办？比如 A 线程要访问 B 线程的变量。\n\n\n\n## 事件驱动和非阻塞机制\n\n\n参考链接：<https://www.kancloud.cn/revin/nodejs/176211>\n\n\n总结：\n\n- Node 中将所有的阻塞操作交给了内部线程池实现。\n\n- Node 主线程本身，主要就是不断的**往返调度**。\n\n\n### 平台实现差异\n\n\n由于 Windows 和 *nix 平台（其他平台）的差异，Node 提供了 libuv 作为抽象封装层，保证上层的 Node 与下层的自定义线程池及 IOCP 之间各自独立。\n\n如下图所示：\n\n![](http://img.smyhvae.com/20180301_2252.png)\n\n\n\n\n"
  },
  {
    "path": "12-Vue基础/01-Vue的介绍和vue-cli.md",
    "content": "---\ntitle: 01-Vue的介绍和vue-cli\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## MVVM模式\n\n![](http://img.smyhvae.com/20180420_2150.png)\n\n- Model：负责数据存储\n\n- View：负责页面展示\n\n- View Model：负责业务逻辑处理（比如Ajax请求等），对数据进行加工后交给视图展示\n\n## 关于框架\n\n### 为什么要学习流行框架\n\n**1、企业为了提高开发效率**：在企业中，时间就是效率，效率就是金钱；企业中，使用框架，能够提高开发的效率。\n\n**提高开发效率的发展历程**：\n\n原生JS -> Jquery之类的类库 -> 前端模板引擎 -> Angular.js / Vue.js（能够帮助我们减少不必要的DOM操作；提高渲染效率；双向数据绑定的概念）\n\n2、在Vue中，一个核心的概念就是：数据驱动，避免手动操作DOM元素。这样的话，可以让前端程序员可以更多的时间去关注数据的业务逻辑，而不是关心 DOM 是如何渲染的了。\n\n### 框架和库的区别\n\n**框架**：\n\n框架是一套完整的解决方案。\n\n对项目的**侵入性**较大，项目如果需要更换框架，则需要重新架构整个项目。但是优点也很明显：功能完善、提供了一整套的解决方案。\n\n**库（插件）**：\n\n只是提供某一个小功能。\n\n对项目的侵入性较小，如果某个库无法完成某些需求，可以很容易切换到其它库实现需求。\n\n举例：\n\n- 从Jquery 切换到 Zepto\n\n- 从 EJS 切换到 art-template\n\n## 前端的各种框架\n\n\n### Vue 和 React 的相同点\n\n- 利用虚拟DOM实现快速渲染\n\n- 轻量级\n\n- 响应式组件\n\n- 支持服务器端渲染\n\n- 易于集成路由工具、打包工具以及状态管理工具\n\nPS：Vue 在国内很受欢迎；React 在国内和国外都很受欢迎，适合做大型网站。\n\n### 什么是虚拟 DOM\n\n传统的web开发，是利用 jQuery操作DOM，这是非常耗资源的。\n\n我们可以在 JS 的内存里构建类似于DOM的对象，去拼装数据，拼装完整后，把数据整体解析，一次性插入到html里去。这就形成了虚拟 DOM。\n\nVue1.0没有虚拟DOM，Vue2.0改成了基于虚拟DOM。\n\n### 前端框架回顾\n\n![](http://img.smyhvae.com/20180302_1645.png)\n\n![](http://img.smyhvae.com/20180302_1651.png)\n\n![](http://img.smyhvae.com/20180302_1652.png)\n\nVue框架中，没有控制器。\n\n## Vue 框架\n\n### 发展历史\n\n- 2013年底作为尤雨溪个人实验项目开始开发\n\n- 2014年2月公开发布。\n\n- 2014年11月发布0.11版本\n\n- 2016年10月发布2.0版本。\n\n### 相关网址\n\n- [中文官网](https://cn.vuejs.org/)\n\n- [vuejs官方论坛](https://forum.vuejs.org/)\n\n- GitHub地址：<https://github.com/vuejs/vue>\n\n\n- Vue1.0 在线文档：<http://v1-cn.vuejs.org/guide/>\n\n- Vue2.x 在线文档：<https://cn.vuejs.org/v2/guide/>\n\n- Vue1下载地址：<http://v1-cn.vuejs.org/js/vue.js>\n\n- Vue2下载地址：<https://cdn.jsdelivr.net/npm/vue/>\n\n![](http://img.smyhvae.com/20180302_1658.png)\n\n上方截图的时间：2018-03-02。\n\n### 介绍\n\n Vue 本身并不是一个框架，Vue结合周边生态构成一个灵活的、渐进式的框架。\n\n Vue 以及大型 Vue 项目所需的周边技术，构成了生态。\n\n渐进式框架图：\n\n![](http://img.smyhvae.com/20180302_1701.png)\n\n### Vue框架的特点\n\n- 模板渲染：基于 html 的模板语法，学习成本低。\n\n- 响应式的更新机制：数据改变之后，视图会自动刷新。【重要】\n\n- 渐进式框架\n\n- 组件化/模块化\n\n- 轻量：开启 gzip压缩后，可以达到 20kb 大小。（React 达到 35kb，AngularJS 达到60kb）。\n\n## Vue 的环境搭建\n\n> 我们首先要安装好 NVM、Node.js环境，然后再来做下面的操作。\n\n### 常见的插件\n\n- Webpack：代码模块化构建打包工具。\n\n- Gulp：基于流的自动化构建工具。\n\n- Babel：使用最新的 规范来编写 js。\n\n- Vue：构建数据驱动的Web界面的渐进式框架\n\n- Express：基于 Node.js 平台，快速、开放、极简的 Web 开发框架。\n\n以上这些包，都可以通过 NPM 这个包管理工具来安装。\n\n### 引用 Vue.js 文件\n\n1、**方式一**：（CDN的方式进行引用）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <script src=\"https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js\"></script>\n</head>\n<body>\n\n\n</body>\n</html>\n```\n\n2、方式二：（下载 vue.js 文件）\n\n去网站 <https://cdn.jsdelivr.net/npm/vue/> 下载 vue.js 文件，直接放到工程文件里，然后引用。\n\n3、方式三：（NPM的方式安装vue）\n\n```bash\n# 最新稳定版\n$ npm install vue\n```\n\n如果网络不稳定，可以采用下面的方式安装：\n\n```\n$ cnpm i vue --save\n```\n\n然后在代码中通过下面这种方式进行引用：\n\n```javascript\n  import Vue from 'vue'\n```\n\n## 利用 vue-cli 新建一个空的项目\n\nVue 提供一个官方命令行工具，可用于快速搭建大型单页应用。该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目。\n\n### 官方代码参考\n\n```\n  npm install -g @vue/cli\n\n  vue create my-app\n\n  cd my-app\n\n  npm run serve\n```\n\n我们根据上方的参考代码，来看看“利用 vue-cli 新建一个空的项目”的步骤。\n\n### 安装 vue-cli（命令行工具）\n\n安装命令如下：\n\n```bash\n# 全局安装 vue-cli\n$ npm install -g @vue/cli\n```\n\n### 初始化一个 simple 项目\n\n（1）首先执行：\n\n```\n  vue create my-app\n```\n\n输入上方命令后，会弹出一个选项：\n\n![](http://img.smyhvae.com/20190624_163626.png)\n\n如果是初学者，直接选`default`就行。之后会自动生成一个空的初始化项目，包含了项目目录、以及项目依赖的脚本。\n\n这个空项目的工程文件如下：（请务必仔细研究这个项目的写法和目录结构）\n\n- [2019-06-21-vue-my-app.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-06-21-vue-my-app.zip)\n\n我们可以看到这个项目的结构：\n\n![](http://img.smyhvae.com/20190624_160726.png)\n\n- src：项目源码\n\n- .babelrc：ES6编译插件的配置\n\n- index.html：单页面的入口\n\n上方截图中，`npm install `指的是下载各种依赖包，`npm run dev`指的是打开发包，`npm run build`指的是打生产包。\n\n（2）本地运行项目：\n\n```\n  cd my-app\n\n  npm run serve\n```\n\n浏览器输入`http://localhost:8080/`，就可以让这个空的项目在本地跑起来：\n\n![](http://img.smyhvae.com/20190624_160229.png)\n\n备注：我们在 GitHub上下载的任何Vue有关的项目，第一步都是要首先执行 npm install，安装依赖的 mode_modules，然后再运行。我们发给同事的工程文件，建议不要包含 `node_modules`。\n\n### 构建一个 非 simple 项目\n\n构建一个空的项目，首先执行：\n\n```\n$ vue create vuedemo2\n```\n\n![](http://img.smyhvae.com/20190624_163726.png)\n\n上图中，选择 `Manually select features`，然后根据提示依次输入：\n\n![](http://img.smyhvae.com/20190624_164305.png)\n\n-  project name：**要求小写**。\n\n- description：默认即可。\n\n- vue-router：需要。\n\n- ESlint：语法检查，初学者可以暂时不需要。\n\n- 单元测试：暂时也不需要。\n\n- e2e test：不需要。\n\n选择 eslint 的配置：\n\n![](http://img.smyhvae.com/20190624_165001.png)\n\n然后让这个空的项目就可以在浏览器上跑起来。\n\n## vue 项目结构分析\n\n![](http://img.smyhvae.com/20180501_2100.png)\n\n- buid：打包配置的文件夹\n\n- config：webpack对应的配置\n\n- src：开发项目的源码\n\t- App.vue：入口组件。`.vue`文件都是组件。\n\t- main.js：项目入口文件。\n\n- static：存放静态资源\n\n- `.babelrc`：解析ES6的配置文件\n\n- `.editorcofnig`：编辑器的配置\n\n- `.postcssrc.js`：html添加前缀的配置\n\n- `index.html`：单页面的入口。通过 webpack打包后，会把 src 源码进行编译，插入到这个 html 里面来。\n\n- `package.json`：项目的基础配置，包含版本号、脚本命令、项目依赖库、开发依赖库、引擎等。\n\n### 图片的base64编码\n\n默认是10k以下，建议都通过 base64编码。在配置文件`webpack.base.conf.js`中进行修改：\n\n```\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('img/[name].[hash:7].[ext]')\n        }\n```\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20160401_01.jpg)\n\n\n"
  },
  {
    "path": "12-Vue基础/02-Vue的系统指令.md",
    "content": "---\ntitle: 02-Vue的系统指令\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n> 本文最初发表于[博客园]()，并在[GitHub](https://github.com/smyhvae/Web)上持续更新**前端的系列文章**。欢迎在GitHub上关注我，一起入门和进阶前端。\n\n> 以下是正文。\n\n\n## 本文主要内容\n\n- 插值表达式 {{}}\n\n- v-cloak\n\n- v-text\n\n- v-html\n\n- v-bind\n\n- v-on\n\n- 举例：文字滚动显示（跑马灯效果）\n\n- v-on的事件修饰符\n\n\n\n## Vue初体验\n\n新建一个空的项目，引入`vue.js`文件。写如下代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <!--1、导入Vue的包-->\n    <script src=\"https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js\"></script>\n</head>\n<body>\n<!--这个div区域就是MVVM中的 View-->\n<div id=\"div1\">\n    {{name}}\n</div>\n</body>\n\n<script>\n    // 2、创建一个Vue的实例\n    //new出来的对象就是MVVM中的 View Module（调度者）\n    var myVue = new Vue({\n        el: '#div1', //当前vue对象将接管上面的div1区域\n        data: {//data就是MVVM中的 module\n            name: 'smyhvae'\n        }\n    });\n</script>\n</html>\n```\n\n\n显示效果：\n\n![](http://img.smyhvae.com/20180313_0955.png)\n\n如果我们在控制台输入`myVue.$data.name = 'haha'`，页面会**自动更新**name的值。意思是，当我们直接修改data数据，页面会自动更新，而不用去操作DOM。\n\n\n下面来讲一下Vue的各种系统指令。\n\n\n## 插值表达式 {{}}\n\n数据绑定最常见的形式就是使用 “Mustache” 语法（双大括号）的文本插值。例如：\n\n```html\n<span>Message: {{ msg }}</span>\n```\n\nMustache 标签将会被替代为对应数据对象上 msg 属性（msg定义在data对象中）的值。\n无论何时，绑定的数据对象上 msg 属性发生了改变，插值处的内容都会**自动更新**。\n\n`{{}}`对JavaScript 表达式支持，例如：\n\n```javascript\n{{ number + 1 }}\n\n{{ ok ? 'YES' : 'NO' }}\n\n{{ name == 'smyhvae' ? 'true' : 'false' }}\n\n{{ message.split('').reverse().join('') }}\n```\n\n\n但是有个限制就是，每个绑定都**只能包含单个表达式**，如下表达式无效：\n\n```html\n<!-- 这是语句，不是表达式 -->\n{{ var a = 1 }}\n\n<!-- 流控制也不会生效，请使用三元表达式 -->\n{{ if (ok) { return message } }}\n```\n\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n</head>\n\n<body>\n    <div id=\"app\">\n        <span>content:{{name}}</span>\n    </div>\n    <script src=\"https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js\"></script>\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                name: 'smyhvae'\n            }\n        })\n    </script>\n\n</body>\n\n</html>\n```\n\n运行结果：\n\n\n![](http://img.smyhvae.com/20180506_2240.png)\n\n\n## v-cloak\n\n`v-cloak`：保持和元素实例的关联，直到结束编译后自动消失。\n\nv-cloak指令和CSS 规则一起用的时候，能够**解决插值表达式闪烁的问题**（即：可以隐藏未编译的标签直到实例准备完毕）。\n\n就拿上一段代码来举例，比如说，`{{name}}`这个内容，**在网速很慢的情况下，一开始会直接显示`{{name}}`这个内容**，等网络加载完成了，才会显示`smyhvae`。那这个**闪烁的问题**该怎么解决呢？\n\n解决办法是：通过`v-cloak`隐藏`{{name}}`这个内容，当加载完毕后，再显示出来。\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n  <style>\n\n    /*2、在样式表里设置：只要是有 v-cloak 属性的标签，我都让它隐藏。\n    直到 Vue实例化完毕以后，v-cloak 会自动消失，那么对应的css样式就会失去作用，最终将span中的内容呈现给用户 */\n    [v-cloak] {\n      display: none;\n    }\n  </style>\n</head>\n\n<body>\n  <div id=\"app\">\n    <!-- 1、给 span 标签添加 v-cloak 属性 -->\n    <span v-cloak>{{name}}</span>\n\n  </div>\n</body>\n\n<script src=\"vue2.5.16.js\"></script>\n<script>\n  new Vue({\n    el: '#app',\n    data: {\n      name: 'smyhvae'\n    }\n  });\n</script>\n\n</html>\n\n```\n\n\n## v-text\n\nv-text可以将一个变量的值渲染到指定的元素中。例如：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Title</title>\n  <!--vue的版本：2.5.15-->\n  <script src=\"vue.js\"></script>\n</head>\n\n<body>\n  <div id=\"div1\">\n    <span v-text=\"name\"></span>\n  </div>\n</body>\n\n<script>\n  new Vue({\n    el: '#div1',\n    data: {\n      name: 'hello smyhvae'\n    }\n  });\n</script>\n\n</html>\n```\n\n结果：\n\n\n![](http://img.smyhvae.com/20180313_1645.png)\n\n### 插值表达式和 v-text 的区别\n\n```html\n  <!-- 插值表达式 -->\n  <span>content:{{name}}</span>\n\n  <!-- v-text -->\n  <span v-text=\"name\">/span>\n```\n\n**区别1**： v-text 没有闪烁的问题，因为它是放在属性里的。\n\n**区别2** :插值表达式只会替换自己的这个占位符，并不会把整个元素的内容清空。v-text 会**覆盖**元素中原本的内容。\n\n为了解释区别2，我们来用代码举例：\n\n```html\n  <!-- 插值表达式 -->\n  <p>content:++++++{{name}}------</p>\n\n  <!-- v-text -->\n  <p v-text=\"name\">------++++++</p>\n```\n\n上方代码的演示结果：\n\n![](http://img.smyhvae.com/20180506_2320.png)\n\n其实，第二行代码中，只要浏览器中还没有解析到`v-text=\"name\"`的时候，会显示`------++++++`；当解析到`v-text=\"name\"`的时候，name的值会直接替换`------++++++`。\n\n\n## v-html\n\n\n`v-text`是纯文本，而`v-html`会被解析成html元素。\n\n注意：使用v-html渲染数据可能会非常危险，因为它很容易导致 XSS（跨站脚本） 攻击，使用的时候请谨慎，能够使用{{}}或者v-text实现的不要使用v-html。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n</head>\n\n<body>\n    <div id=\"app\">\n        <p>{{msg}}</p>\n        <p v-text=\"msg\"></p>\n        <p v-html=\"msg\"></p>\n\n    </div>\n    <script src=\"vue2.5.16.js\"></script>\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                msg: '<h1>我是一个大大的h1标题</h1>'\n            }\n        })\n    </script>\n\n</body>\n\n</html>\n```\n\n运行结果：\n\n![](http://img.smyhvae.com/20180506_2330.png)\n\n\n## v-bind：属性绑定机制\n\n`v-bind`：用于绑定**属性**。\n\n比如说：\n\n```html\n    <img v-bind:src=\"imageSrc +'smyhvaeString'\">\n\n    <div v-bind:style=\"{ fontSize: size + 'px' }\"></div>\n```\n\n\n上方代码中，给属性加了 v-bind 之后，属性值里的整体内容是**表达式**，属性值里的`imageSrc`和`size`是Vue实例里面的**变量**。\n\n也就是说， v-bind的属性值里，可以写合法的 js 表达式。\n\n上面两行代码也可以简写成：\n\n```html\n    <img :src=\"imageSrc\">\n\n    <div :style=\"{ fontSize: size + 'px' }\"></div>\n```\n\n**举例：**\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Title</title>\n  <style>\n  </style>\n</head>\n\n<body>\n  <div id=\"div1\">\n    <!-- value里的值只是简单的字符串 -->\n    <input type=\"text\" value=\"name\">\n    <!-- 加上 v-bind 之后，value里的值是 Vue 里的变量 -->\n    <input type=\"text\" v-bind:value=\"name\">\n    <!-- 超链接后面的path是 Vue 里面的变量 -->\n    <a v-bind=\"{href:'http://www.baidu.com/'+path}\">超链接</a>\n\n  </div>\n</body>\n\n<script src=\"vue.js\"></script>\n<script>\n  new Vue({\n    el: '#div1',\n    data: {\n      name: 'smyhvae',\n      path: `2.html`\n    }\n  });\n</script>\n\n</html>\n\n```\n\n上面的代码中，我们给`value`这个属性绑定了值，此时这个值是一个变量。\n\n效果：\n\n![](http://img.smyhvae.com/20180313_1745.png)\n\n\n\n## v-on：事件绑定机制\n\n### `v-on:click`：点击事件\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Title</title>\n  <!--vue的版本：2.5.15-->\n  <script src=\"vue.js\"></script>\n</head>\n\n<body>\n  <!--这个div区域就是MVVM中的 View-->\n  <div id=\"div1\">\n    <!-- 给button节点绑定按钮的点击事件 -->\n    {{name}}\n    <button v-on:click=\"change\">改变name的值</button>\n  </div>\n\n\n</body>\n\n<script>\n  //new出来的对象就是MVVM中的 View Module\n  var myVue = new Vue({\n    el: '#div1', //当前vue对象将接管上面的div区域\n    data: { //data就是MVVM中的 module\n      name: 'smyhvae'\n    },\n\n    //注意，下方这个 `methods` 是Vue中定义方法的关键字，不能改\n    //这个 methods 属性中定义了当前Vue实例所有可用的方法\n    methods: {\n      change: function() { //上面的button按钮的点击事件\n        this.name += '1';\n      }\n    }\n  });\n</script>\n\n</html>\n```\n\n\n上方代码中，我们给button按钮绑定了点击事件。注意，这个button标签要写在div区域里（否则点击事件不生效），因为下方的View module接管的是div区域。\n\n### `v-on`的简写形式\n\n例如：\n\n```html\n    <button v-on:click=\"change\">改变name的值</button>\n```\n\n可以简写成：\n\n```html\n    <button @click=\"change\">改变name的值</button>\n```\n\n\n### v-on的常用事件\n\nv-on 提供了click 事件，也提供了一些其他的事件。\n\n\n- v-on:click\n\n- v-on:keydown\n\n- v-on:keyup\n\n- v-on:mousedown\n\n- v-on:mouseover\n\n- v-on:submit\n\n- ....\n\n## 举例：文字滚动显示（跑马灯效果）\n\n我们利用上面几段所学的内容，做个跑马灯的小例子。要实现的效果是：类似于LED屏幕上，滚动显示的文字。\n\n**文字滚动显示的思路**：\n\n（1）每次点击按钮后，拿到 msg 字符串，然后调用字符串的`substring`来进行字符串的截取操作，把第一个字符截取出来，放到最后一个位置即可。这就实现了滚动的效果。\n（2）为了实现文字**自动连续滚动**的效果，需要把步骤（1）中点击按钮的操作，放到**定时器**中去。\n\n我们先来看一下 点击事件里的代码改怎么写。\n\n**步骤 1**：每次点击按钮，字符串就滚动一次。代码如下：\n\n```javascript\n    startMethod: function () {\n        // 获取 msg 的第一个字符\n        var start = this.msg.substring(0, 1);\n        // 获取 后面的所有字符\n        var end = this.msg.substring(1);\n        // 重新拼接得到新的字符串，并赋值给 this.msg\n        this.msg = end + start;\n    }\n```\n\n**步骤2**：给上面的操作添加定时器。代码如下：\n\n```javascript\n    startMethod: function () {\n        var _this = this;\n        //添加定时器：点击按钮后，让字符串连续滚动\n        setInterval(function () {\n            // 获取 msg 的第一个字符\n            var start = _this.msg.substring(0, 1);\n            // 获取 后面的所有字符\n            var end = _this.msg.substring(1);\n            // 重新拼接得到新的字符串，并赋值给 this.msg\n            // 注意： VM实例，会监听自己身上 data 中所有数据的改变，只要数据一发生变化，就会自动把 最新的数据，从data 上同步到页面中去\n            _this.msg = end + start;\n            console.log(_this.msg);\n        }, 400);\n    }\n```\n\n上面的代码中，我们发现，如果在定时器中直接使用this，这个this指向的是window。为了解决这个问题，我们是通过`_this`来解决了这个问题。\n\n另外，我们还可以利用箭头函数来解决this指向的问题，因为箭头函数总的this指向，会继承外层函数的this指向。如下。\n\n**步骤2的改进版**：用箭头函数来改进定时器，解决this指向的问题。代码如下：\n\n```javascript\n    startMethod: function () {\n        //添加定时器：点击按钮后，让字符串连续滚动\n        setInterval(() => {\n            // 获取 msg 的第一个字符\n            var start = this.msg.substring(0, 1);\n            // 获取 后面的所有字符\n            var end = this.msg.substring(1);\n            // 重新拼接得到新的字符串，并赋值给 this.msg\n            // 注意： VM实例，会监听自己身上 data 中所有数据的改变，只要数据一发生变化，就会自动把 最新的数据，从data 上同步到页面中去\n            this.msg = end + start;\n            console.log(_this.msg);\n        }, 400);\n    }\n```\n\n\n**步骤3**：停止定时器。如下：\n\n我们还需要加一个按钮，点击按钮后，停止文字滚动。也就是停止定时器。\n\n提示：我们最好把定时器的id放在全局的位置（放到data里），这样的话，开启定时器的方法和停止定时器的方法，都可以同时访问到这个定时器。\n\n代码如下：\n\n```javascript\n    data: {\n        msg: '千古壹号，前端教程～～～',\n        intervalId: null\n    },\n    methods: {\n        startMethod: function () {\n            //添加定时器：点击按钮后，让字符串连续滚动\n            this.intervalId = setInterval(() => {  //【注意】这个定时器的this，一定不要忘记了\n                // 获取 msg 的第一个字符\n                var start = this.msg.substring(0, 1);\n                // 获取 后面的所有字符\n                var end = this.msg.substring(1);\n                // 重新拼接得到新的字符串，并赋值给 this.msg\n                // 注意： VM实例，会监听自己身上 data 中所有数据的改变，只要数据一发生变化，就会自动把 最新的数据，从data 上同步到页面中去\n                this.msg = end + start;\n                console.log(_this.msg);\n            }, 400);\n        },\n\n        stopMethod: function () {\n            //停止定时器：点击按钮后，停止字符串的滚动\n            clearInterval(this.intervalId);\n        }\n    }\n```\n\n\n**【重要】步骤4**：一开始的时候，还需要判断是否已经存在定时器。如下：\n\n步骤3中的代码，虽然做了停止定时器的操作，但是有个问题：在连续多次点击“启动定时器”按钮的情况下，此时再点击“停止定时器”的按钮，是没有反应的。因此，我们需要改进的地方是：\n\n- **在开启定时器之前，先做一个判断**：如果定时器不为 null，就不继续往下执行了（即不再开启新的定时器），防止开启了多个定时器。\n\n- **停止定时器的时候，虽然定时器停止了，但定时器并不为 null**。因此，最后我们还需要手动将定时器设置为null。这样，才能恢复到最初始的状态。\n\n**完整版代码**：\n\n针对上面的四个步骤，为了实现这个案例，完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n</head>\n\n<body>\n    <div id=\"app\">\n        <p>{{msg}}</p>\n        <input type=\"button\" value=\"跑马灯走起\" @click=\"startMethod\">\n        <input type=\"button\" value=\"跑马灯停止\" @click=\"stopMethod\">\n\n    </div>\n    <script src=\"vue2.5.16.js\"></script>\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                msg: '千古壹号，前端教程～～～',\n                intervalId: null\n            },\n            methods: {\n                startMethod: function () {\n\n                    //【重要】在开启定时器之前，先进行判断，避免出现多个定时器\n                    //如果出现了多个定时器，后面的“停止定时器”操作是无效的\n                    if (this.intervalId != null) return; //【注意】这个定时器的this，一定不要忘记了\n\n                    //添加定时器：点击按钮后，让字符串连续滚动\n                    this.intervalId = setInterval(() => {\n                        // 获取 msg 的第一个字符\n                        var start = this.msg.substring(0, 1);\n                        // 获取 后面的所有字符\n                        var end = this.msg.substring(1);\n                        // 重新拼接得到新的字符串，并赋值给 this.msg\n                        // 注意： VM实例，会监听自己身上 data 中所有数据的改变，只要数据一发生变化，就会自动把 最新的数据，从data 上同步到页面中去\n                        this.msg = end + start;\n                        console.log(this.msg);\n                    }, 400);\n                },\n\n                stopMethod: function () {\n                    // 停止定时器：点击按钮后，停止字符串的滚动\n                    clearInterval(this.intervalId);\n                    // 每当清除了定时器之后，需要重新把 intervalId 置为 null\n                    this.intervalId = null;\n                }\n            }\n        })\n    </script>\n\n</body>\n\n</html>\n```\n\n\n**上方代码的总结**：\n\n- 在Vue的实例中，如果想要获取data里的属性、methods里面的方法，都要通过`this`来访问。这里的**this指向的是Vue的实例对象**。\n\n- VM实例，会监听自己身上 data 中所有数据的改变，只要数据一发生变化，就会自动把最新的数据，从 data 上同步到页面中去。这样做 的好处是：**程序员只需要关心数据，不需要考虑如何重新渲染DOM页面；减少DOM操作**。\n\n- 在调用定时器 id 的时候，代码是`this.intervalId`，这个`this`一定不要漏掉了。\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n\n"
  },
  {
    "path": "12-Vue基础/03-v-on的事件修饰符.md",
    "content": "---\ntitle: 03-v-on的事件修饰符\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## v-on的事件修饰符\n\n### v-on的常见事件修饰符\n\n`v-on` 提供了很多事件修饰符来辅助实现一些功能。事件修饰符有如下：\n\n- `.stop`  阻止冒泡。本质是调用 event.stopPropagation()。\n\n- `.prevent`  阻止默认事件（默认行为）。本质是调用 event.preventDefault()。\n\n- `.capture`  添加事件监听器时，使用捕获的方式（也就是说，事件采用捕获的方式，而不是采用冒泡的方式）。\n\n- `.self`  只有当事件在该元素本身（比如不是子元素）触发时，才会触发回调。\n\n- `.once`  事件只触发一次。\n\n- `.{keyCode | keyAlias}`   只当事件是从侦听器绑定的元素本身触发时，才触发回调。\n\n- `.native` 监听组件根元素的原生事件。\n\nPS：一个事件，允许同时使用多个事件修饰符。\n\n写法示范：\n\n```html\n          <!-- click事件 -->\n        <button v-on:click=\"doThis\"></button>\n\n        <!-- 缩写 -->\n        <button @click=\"doThis\"></button>\n\n        <!-- 内联语句 -->\n        <button v-on:click=\"doThat('hello', $event)\"></button>\n\n        <!-- 阻止冒泡 -->\n        <button @click.stop=\"doThis\"></button>\n\n        <!-- 阻止默认行为 -->\n        <button @click.prevent=\"doThis\"></button>\n\n        <!-- 阻止默认行为，没有表达式 -->\n        <form @submit.prevent></form>\n\n        <!--  串联修饰符 -->\n        <button @click.stop.prevent=\"doThis\"></button>\n```\n\n\n### `.stop`的举例\n\n我们来看下面这个例子：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n    <style>\n        .father {\n            height: 300px;\n            width: 300px;\n            background: pink;\n        }\n\n        .child {\n            width: 200px;\n            height: 200px;\n            background: green;\n        }\n    </style>\n</head>\n\n<body>\n    <div id=\"app\">\n        <div class=\"father\" @click=\"fatherClick\">\n            <div class=\"child\" @click=\"childClick\">\n            </div>\n        </div>\n    </div>\n    <script>\n\n        var vm = new Vue({\n            el: '#app',\n            data: {},\n            methods: {\n                fatherClick: function () {\n                    console.log('father 被点击了');\n                },\n                childClick: function () {\n                    console.log('child 被点击了');\n                }\n            }\n        })\n\n    </script>\n</body>\n\n</html>\n\n```\n\n上方代码中，存在冒泡的现象，父标签中包含了一个子标签。当点击子标签时，父标签也会被触发。打印顺序是：\n\n```\n  child 被点击了\n  father 被点击了\n```\n\n那么问题来了，如果我不想让子标签的点击事件冒泡到父亲，该怎么做呢？办法是：给子标签加一个**事件修饰符**`.stop`，阻止冒泡。代码如下：\n\n```html\n    <div class=\"child\" @click.stop=\"childClick\">\n```\n\n阻止冒泡后，当点击子标签时，打印结果是：\n\n```\n  child 被点击了\n```\n\nPS：我发现一个有意思的现象。上方的这行代码中，如果把`.stop`改为`:stop`，造成的现象是，父标签被触发了，而子标签没有被触发。\n\n### `.capture`举例\n\n`.capture`：触发事件时，采用捕获的形式，而不是冒泡的形式。\n\n还是采用上面的例子：当按钮点击时，如果想要采取捕获的方式，而不是冒泡的方式，办法是：可以直接在父标签上加事件修饰符`.capture`。代码如下：\n\n```html\n    <div class=\"father\" @click.capture=\"fatherClick\">\n```\n\n当点击子标签时，打印结果是：\n\n```\n  father 被点击了\n  child 被点击了\n```\n\n\n### `.prevent`的举例1\n\n比如说，超链接`<a>`默认有跳转行为，那我可以通过事件修饰符`.prevent`阻止这种跳转行为。\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 通过 .prevent 阻止超链接的默认跳转行为 -->\n        <a href=\"http://www.baidu.com\" @click.prevent=\"linkClick\">百度一下</a>\n    </div>\n    <script>\n\n        var vm = new Vue({\n            el: '#app',\n            data: {},\n            methods: {\n                linkClick: function () {\n                    console.log('超链接被点击了');\n                }\n            }\n        })\n    </script>\n</body>\n\n</html>\n```\n\n上方代码中：\n\n- 如果去掉`.prevent`，点击按钮后，既会打印log，又会跳转到百度页面。\n\n- 现在加上了`.prevent`，就只会打印log，不会跳转到百度页面。\n\n\n\n### `.prevent`的举例2\n\n现在有一个form表单：\n\n```html\n    <form action=\"http://www.baidu.com\">\n      <input type=\"submit\" value=\"表单提交\">\n    </form>\n```\n\n\n我们知道，上面这个表单因为`type=\"submit\"`，因此它是一个提交按钮，点击按钮后，这个表单就会被提交到form标签的action属性中指定的那个页面中去。这是表单的默认行为。\n\n现在，我们可以用`.prevent`来阻止这种默认行为。修改为：点击按钮后，不提交到服务器，而是执行我们自己想要的事件（在submit方法中另行定义）。如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n  <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n  <div id=\"app\">\n    <!-- 阻止表单中submit的默认事件 -->\n    <form @submit.prevent action=\"http://www.baidu.com\">\n      <!-- 执行自定义的click事件 -->\n      <input type=\"submit\" @click=\"mySubmit\" value=\"表单提交\">\n    </form>\n\n  </div>\n</body>\n\n<script>\n  new Vue({\n    el: '#app',\n    data: {\n    },\n    methods: {\n      mySubmit: function() {\n        alert('ok');\n      }\n    }\n  });\n</script>\n\n</html>\n```\n\n上方代码中，我们通过`.prevent`阻止了提交按钮的默认事件，点击按钮后，执行的是`mySubmit()`方法里的内容。这个方法名是可以随便起的，我们甚至可以起名为`submit`，反正默认的submit已经失效了。\n\n\n\n### `.self`举例\n\n- `.self`  只有当事件在该元素本身（比如不是子元素）触发时，才会触发回调。\n\n我们知道，在事件触发机制中，当点击子标签时，父标签会通过冒泡的形式被触发（父标签本身并没有被点击）。可如果我给父标签的点击事件设置`.self`修饰符，达到的效果是：子标签的点击事件不会再冒泡到父标签了，只有点击符标签本身，父标签的事件才会被触发。代码如下：\n\n```html\n    <div class=\"father\" @click.self=\"fatherClick\">\n```\n\n\n**疑问**：既然`.stop`和`.self`都可以阻止冒泡，那二者有什么区别呢？区别在于：前者能够阻止整个冒泡行为，而后者只能阻止自己身上的冒泡行为。\n\n\n"
  },
  {
    "path": "12-Vue基础/04-Vue的系统指令(二).md",
    "content": "---\ntitle: 03-v-on的事件修饰符\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 前言\n\n本文主要内容：\n\n- v-model\n\n- v-for\n\n- v-if\n\n- v-show\n\n## v-model：双向数据绑定\n\n\n> 重点：**双向数据绑定，只能用于表单元素，或者用于自定义组件**。\n\n之前的文章里，我们通过v-bind，给`<input>`标签绑定了`data`对象里的`name`属性。当`data`里的`name`的值发生改变时，`<input>`标签里的内容会自动更新。\n\n可我现在要做的是：我在`<input>`标签里修改内容，要求`data`里的`name`的值自动更新。从而实现双向数据绑定。该怎么做呢？这就可以利用`v-model`这个属性。\n\n**区别**：\n\n- v-bind：只能实现数据的**单向**绑定，从 M 自动绑定到 V。\n\n- v-model：只有`v-model`才能实现**双向**数据绑定。注意，v-model 后面不需要跟冒号，\n\n**注意**：v-model 只能运用在**表单元素**中，或者用于自定义组件。常见的表单元素包括：input(radio, text, address, email....) 、select、checkbox 、textarea。\n\n代码举例如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <script src=\"vue.js\"></script>\n</head>\n<body>\n<div id=\"app\">\n\n    <form action=\"#\">\n    \t<!-- 将 input 标签中的value值双向绑定到 Vue实例中的data。注意，v-model 后面不需要跟冒号 -->\n        <input type=\"text\" id=\"username\" v-model=\"myAccount.username\">\n        <input type=\"password\" id=\"pwd\" v-model=\"myAccount.userpwd\">\n\n        <input type=\"submit\" v-on:click=\"submit1\" value=\"注册\">\n\n    </form>\n</div>\n</body>\n\n<script>\n    var vm = new Vue({\n        el: '#app',\n        //上面的标签中采用v-model进行双向数据绑定，数据会自动更新到data里面来\n        data: {\n            name: 'qianguyihao',\n            myAccount: {username: '', userpwd: ''}\n        },\n        //在methods里绑定各种方法，根据业务需要进行操作\n        methods: {\n            submit1: function () {\n                alert(this.myAccount.username + \"  pwd=\" + this.myAccount.userpwd);\n            }\n        }\n    });\n</script>\n\n</html>\n```\n\n此时，便可实现我们刚刚要求的双向数据绑定的效果。\n\n## v-model举例：实现简易计算器\n\n题目：现在两个输入框，用来做加减乘除，将运算的结果放在第三个输入框。\n\n实现代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <input type=\"text\" v-model=\"n1\">\n\n        <select v-model=\"opt\">\n            <option value=\"+\">+</option>\n            <option value=\"-\">-</option>\n            <option value=\"*\">*</option>\n            <option value=\"/\">/</option>\n        </select>\n\n        <input type=\"text\" v-model=\"n2\">\n\n        <input type=\"button\" value=\"=\" @click=\"calc\">\n\n        <input type=\"text\" v-model=\"result\">\n    </div>\n\n    <script>\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                n1: 0,\n                n2: 0,\n                result: 0,\n                opt: '+'\n            },\n            methods: {\n                calc() { // 计算器算数的方法\n                    // 逻辑判断：\n                    switch (this.opt) {\n                        case '+':\n                            this.result = parseInt(this.n1) + parseInt(this.n2)\n                            break;\n                        case '-':\n                            this.result = parseInt(this.n1) - parseInt(this.n2)\n                            break;\n                        case '*':\n                            this.result = parseInt(this.n1) * parseInt(this.n2)\n                            break;\n                        case '/':\n                            this.result = parseInt(this.n1) / parseInt(this.n2)\n                            break;\n                    }\n\n                    //上面的逻辑判断，可能有点啰嗦，我们还可以采取下面的这种方式进行逻辑判断\n                    // 注意：这是投机取巧的方式，正式开发中，尽量少用\n                    // var codeStr = 'parseInt(this.n1) ' + this.opt + ' parseInt(this.n2)'\n                    // this.result = eval(codeStr)\n                }\n            }\n        });\n    </script>\n</body>\n\n</html>\n```\n\n注意上方代码中的注释，可以了解下`eval()`的用法。\n\n## Vue中通过属性绑定为元素设置class 类样式\n\n注意，是**类样式**。\n\n### 引入\n\n我们先来看下面这段代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <style>\n        .my-red {\n            color: red;\n        }\n\n        .my-thin {\n            /* 设置字体的粗细 */\n            font-weight: 200;\n        }\n\n        .my-italic {\n            font-style: italic;\n        }\n\n        .my-active {\n            /* 设置字符之间的间距 */\n            letter-spacing: 0.5em;\n        }\n    </style>\n</head>\n\n<body>\n    <h1 class=\"my-red my-thin\">我是千古壹号，qianguyihao</h1>\n</body>\n\n</html>\n```\n\n上面的代码中，我们直接通过正常的方式，给`<h1>`标签设置了两个 class 类的样式。代码抽取如下：\n\n```html\n    <h1 class=\"my-red my-thin\">我是千古壹号，qianguyihao</h1>\n```\n\n上面的效果，我们还可以用Vue来写。这就引入了本段要讲的方式。\n\n### 方式一：数组\n\n**方式一**：直接传递一个数组。注意：这里的 class 需要使用  v-bind 做数据绑定。\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n    <style>\n        .my-red {\n            color: red;\n        }\n\n        .my-thin {\n            /* 设置字体的粗细 */\n            font-weight: 200;\n        }\n\n        .my-italic {\n            font-style: italic;\n        }\n\n        .my-active {\n            /* 设置字符之间的间距 */\n            letter-spacing: 0.5em;\n        }\n    </style>\n</head>\n\n<body>\n    <div id=\"app\">\n\n        <!-- 普通写法 -->\n        <h1 class=\"my-red my-thin\">我是千古壹号，qianguyihao</h1>\n\n        <!-- vue的写法1：数组的形式 -->\n        <h1 :class=\"['my-red', 'my-thin']\">我是qianguyihao，千古壹号</h1>\n\n    </div>\n\n    <script>\n\n        var vm = new Vue({\n            el: '#app'\n        });\n\n    </script>\n</body>\n\n</html>\n```\n\n代码抽取如下：\n\n```html\n        <!-- vue的写法1：数组的形式 -->\n        <h1 :class=\"['my-red', 'my-thin']\">我是qianguyihao，千古壹号</h1>\n```\n\n上方代码中，注意，数组里写的是字符串；如果不加单引号，就不是字符串了，而是变量。\n\n演示效果如下：\n\n![](http://img.smyhvae.com/20180509_1058.png)\n\n### 写法二：在数组中使用三元表达式\n\n```html\n<body>\n    <div id=\"app\">\n        <!-- vue的写法2：在数组中使用三元表达式。注意格式不要写错-->\n        <!-- 通过data中布尔值 flag 来判断：如果 flag 为 true，就给 h1 标签添加`my-active`样式；否则，就不设置样式。 -->\n        <h1 :class=\"[flag?'my-active':'']\">我是qianguyihao，千古壹号</h1>\n    </div>\n\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                flag:true\n            }\n        });\n    </script>\n</body>\n```\n\n上方代码的意思是，通过data中布尔值 flag 来判断：如果 flag 为 true，就给 h1 标签添加`my-active`样式；否则，就不设置样式。\n\n注意，三元表达式的格式不要写错了。\n\n\n### 写法三：在数组中使用 对象 来代替 三元表达式（提高代码的可读性）\n\n上面的写法二，可读性较差。于是有了写法三。\n\n**写法三**：在数组中使用**对象**来代替**三元表达式**。\n\n代码如下：\n\n```html\n<body>\n    <div id=\"app\">\n        <!-- vue的写法3：在数组中使用对象来代替三元表达式。-->\n        <h1 :class=\"[ {'my-active':flag} ]\">我是qianguyihao，千古壹号</h1>\n    </div>\n\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                flag: true\n            }\n        });\n    </script>\n</body>\n```\n\n### 写法四：直接使用对象\n\n写法四：直接使用对象。代码如下：\n\n```html\n        <!-- vue的写法4：直接使用对象-->\n        <!-- 在为 class 使用 v-bind 绑定 对象的时候，对象的属性是类名。由于 对象的属性名可带引号，也可不带引号，所以 这里我没写引号；  属性的值 是一个标识符 -->\n        <h1 :class=\"{style1:true, style2:false}\">我是qianguyihao，千古壹号</h1>\n```\n\n上方代码的意思是，给`<h1>`标签使用样式`style1`，不使用样式`style2`。注意：\n\n1、既然class样式名是放在对象中的，这个样式名不能有中划线，比如说，写成`:class=\"{my-red:true, my-active:false}`，是会报错的。\n\n2、我们也可以对象通过存放在 data 的变量中。也就是说，上方代码可以写成：\n\n```html\n<body>\n    <div id=\"app\">\n        <!-- vue的写法4：直接使用对象-->\n        <!-- 在为 class 使用 v-bind 绑定 对象的时候，对象的属性是类名。由于 对象的属性名可带引号，也可不带引号，所以 这里我没写引号；  属性的值 是一个标识符 -->\n        <h1 :class=\"classObj\">我是qianguyihao，千古壹号</h1>\n    </div>\n\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                classObj:{style1:true, style2:false}\n            }\n        });\n    </script>\n</body>\n```\n\n## Vue中通过属性绑定为元素设置 style 行内样式\n\n注意，是行内样式（即内联样式）。\n\n### 写法一\n\n**写法一**：直接在元素上通过 `:style` 的形式，书写样式对象。\n\n例如：\n\n```html\n        <h1 :style=\"{color: 'red', 'font-size': '20px'}\">我是千古壹号，qianguyihao</h1>\n```\n\n### 写法二\n\n**写法二**：将样式对象，定义到 `data` 中，并直接引用到 `:style` 中。\n\n也就是说，把写法一的代码改进一下。代码如下：\n\n```html\n<body>\n    <div id=\"app\">\n        <h1 :style=\"styleObj\">我是千古壹号，qianguyihao</h1>\n    </div>\n\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                styleObj: { color: 'red', 'font-size': '20px' }\n            }\n        });\n    </script>\n</body>\n```\n\n### 写法三\n\n写法二只用到了一组样式。如果想定义**多组**样式，可以用写法三。\n\n**写法三**：在 `:style` 中通过数组，引用多个 `data` 上的样式对象。\n\n代码如下：\n\n```html\n<body>\n    <div id=\"app\">\n        <h1 :style=\"[ styleObj1, styleObj2 ]\">我是千古壹号，qianguyihao</h1>\n    </div>\n\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                styleObj1: { color: 'red', 'font-size': '20px' },\n                styleObj2: { 'font-style': 'italic' }\n            }\n        });\n    </script>\n</body>\n```\n\n\n## v-for：for循环的四种使用方式\n\n**作用**：根据数组中的元素遍历指定模板内容生成内容。\n\n### 引入\n\n比如说，如果我想给一个`ul`中的多个`li`分别赋值1、2、3...。如果不用循环，就要挨个赋值：\n\n```html\n<body>\n  <div id=\"app\">\n    <ul>\n      <li>{{list[0]}}</li>\n      <li>{{list[1]}}</li>\n      <li>{{list[2]}}</li>\n    </ul>\n  </div>\n</body>\n\n<script>\n  var vm = new Vue({\n    el: '#app',\n    data: {\n      list: [1, 2, 3]\n    }\n\n  });\n</script>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180329_1713.png)\n\n为了实现上面的效果，如果我用`v-for`进行赋值，代码就简洁很多了：\n\n```html\n<body>\n  <div id=\"app\">\n    <ul>\n      <!-- 使用v-for对多个li进行遍历赋值 -->\n      <li v-for=\"item in list\">{{item}}</li>\n    </ul>\n  </div>\n</body>\n\n<script>\n  var vm = new Vue({\n    el: '#app',\n    data: {\n      list: [1, 2, 3]\n    }\n\n  });\n</script>\n```\n\n接下来，我们详细讲一下`v-for`的用法。需要声明的是，Vue 1.0的写法和Vue 2.0的写法是不一样的。本文全部采用Vue 2.0的写法。\n\n### 方式一：普通数组的遍历\n\n针对下面这样的数组：\n\n```html\n<script>\n  new Vue({\n    el: '#app',\n    data: {\n      arr1: [2, 5, 3, 1, 1],\n    }\n  });\n</script>\n```\n\n将数组中的**值**赋给li：\n\n```html\n      <li v-for=\"item in arr1\">{{item}}</li>\n```\n\n将数组中的**值和index**赋给li：\n\n\n```html\n      <!-- 括号里如果写两个参数：第一个参数代表值，第二个参数代表index 索引 -->\n      <li v-for=\"(item,index) in arr1\">值：{{item}} --- 索引：{{index}}</li>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180329_1856.png)\n\n### 方式二：对象数组的遍历\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n    <style>\n    </style>\n</head>\n\n<body>\n    <div id=\"app\">\n        <ul>\n            <!-- 对象数组的遍历。括号里如果写两个参数：第一个参数代表数组的单个item，第二个参数代表 index 索引-->\n            <li v-for=\"(item, index) in dataList\">姓名：{{item.name}} --- 年龄：{{item.age}} --- 索引：{{index}}</li>\n\n        </ul>\n    </div>\n\n    <script>\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                //对象数组\n                dataList: [\n                    { name: 'smyh', age: '26' },\n                    { name: 'vae', age: '32' },\n                    { name: 'xiaoming', age: '20' }\n                ]\n            }\n        });\n    </script>\n</body>\n\n</html>\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180509_1500.png)\n\n### 方式三：对象的遍历\n\n针对下面这样的对象：\n\n```html\n<script>\n  new Vue({\n    el: '#app',\n    data: {\n      obj1: {\n        name: 'qianguyihao',\n        age: '26',\n        gender: '男'\n      }\n    }\n  });\n</script>\n```\n\n将上面的`obj1`对象的数据赋值给li，写法如下：\n\n```html\n<body>\n  <div id=\"app\">\n    <ul>\n      <!-- 括号里如果写两个参数：则第一个参数代表value，第二个参数代表key -->\n      <li v-for=\"(value,key) in obj1\">值：{{value}} --- 键：{{key}} </li>\n\n      <h3>---分隔线---</h3>\n\n      <!-- 括号里如果写三个参数：则第一个参数代表value，第二个参数代表key，第三个参数代表index -->\n      <li v-for=\"(value,key,index) in obj1\">值：{{value}} --- 键：{{key}} --- index：{{index}} </li>\n    </ul>\n  </div>\n</body>\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180329_1850.png)\n\n### 方式四：遍历数字\n\n`in`后面还可以直接放数字。举例如下：\n\n```html\n        <ul>\n            <!-- 对象数组的遍历 -->\n            <!-- 注意：如果使用 v-for 遍历数字的话，前面的 myCount 值从 1 开始算起 -->\n            <li v-for=\"myCount in 10\">这是第 {{myCount}}次循环</li>\n        </ul>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180509_1505.png)\n\n### v-for中key的使用注意事项\n\n**注意**：在 Vue 2.2.0+ 版本里，当在**组件中**使用 v-for 时，key 属性是必须要加上的。\n\n这样做是因为：每次 for 循环的时候，通过指定 key 来标示当前循环这一项的**唯一身份**。\n\n> 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时，它默认用 “**就地复用**” 策略。如果数据项的顺序被改变，Vue将**不是移动 DOM 元素来匹配数据项的顺序**， 而是**简单复用此处每个元素**，并且确保它在特定索引下显示已被渲染过的每个元素。\n\n> 为了给 Vue 一个提示，**以便它能跟踪每个节点的身份，从而重用和重新排序现有元素**，你需要为每项提供一个唯一 key 属性。\n\nkey的类型只能是：string/number，而且要通过 v-bind 来指定。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n\n        <div>\n            <label>Id:\n                <input type=\"text\" v-model=\"id\">\n            </label>\n\n            <label>Name:\n                <input type=\"text\" v-model=\"name\">\n            </label>\n\n            <input type=\"button\" value=\"添加\" @click=\"add\">\n        </div>\n\n        <!-- 注意： v-for 循环的时候，key 属性只能使用 number 或者 string -->\n        <!-- 注意： key 在使用的时候，必须使用 v-bind 属性绑定的形式，指定 key 的值 -->\n        <!-- 在组件中，使用v-for循环的时候，或者在一些特殊情况中，如果 v-for 有问题，必须 在使用 v-for 的同时，指定 唯一的 字符串/数字 类型 :key 值 -->\n        <p v-for=\"item in list\" :key=\"item.id\">\n            <input type=\"checkbox\">{{item.id}} --- {{item.name}}\n        </p>\n    </div>\n\n    <script>\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                id: '',\n                name: '',\n                list: [\n                    { id: 1, name: 'smyh' },\n                    { id: 2, name: 'vae' },\n                    { id: 3, name: 'qianguyihao' },\n                    { id: 4, name: 'xiaoming' },\n                    { id: 5, name: 'xiaohong' }\n                ]\n            },\n            methods: {\n                add() { // 添加方法\n                    this.list.unshift({ id: this.id, name: this.name })\n                }\n            }\n        });\n    </script>\n</body>\n\n</html>\n```\n\n## v-if：设置元素的显示和隐藏（添加/删除DOM元素）\n\n**作用**：根据表达式的值的真假条件，来决定是否渲染元素，如果为false则不渲染（达到隐藏元素的目的），如果为true则渲染。\n\n在切换时，元素和它的数据绑定会被销毁并重建。\n\n举例如下：（点击按钮时，切换和隐藏盒子）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n  <script src=\"vue.js\"></script>\n</head>\n\n<body>\n  <div id=\"app\">\n    <button v-on:click=\"toggle\">显示/隐藏</button>\n    <div v-if=\"isShow\">我是盒子</div>\n  </div>\n</body>\n\n<script>\n  new Vue({\n    el: '#app',\n    data: {\n      isShow: true\n    },\n    methods: {\n      toggle: function() {\n        this.isShow = !this.isShow;\n      }\n    }\n  });\n</script>\n\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180329_1920.gif)\n\n## v-show：设置元素的显示和隐藏（在元素上添加/移除`style=\"display:none\"`属性）\n\n**作用**：根据表达式的真假条件，来切换元素的 display 属性。如果为false，则在元素上添加 `display:none`属性；否则移除`display:none`属性。\n\n举例如下：（点击按钮时，切换和隐藏盒子）\n\n我们直接把上一段代码中的`v-if`改成`v-show`就可以了：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n  <script src=\"vue.js\"></script>\n</head>\n\n<body>\n  <div id=\"app\">\n    <button v-on:click=\"toggle\">显示/隐藏</button>\n    <div v-show=\"isShow\">我是盒子</div>\n  </div>\n</body>\n\n<script>\n  new Vue({\n    el: '#app',\n    data: {\n      isShow: true\n    },\n    methods: {\n      toggle: function() {\n        this.isShow = !this.isShow;\n      }\n    }\n  });\n</script>\n\n</html>\n\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180329_2040.gif)\n\n### v-if和v-show的区别\n\n`v-if`和`v-show`都能够实现对一个元素的隐藏和显示操作。\n\n区别：\n\n- v-if：每次都会重新添加/删除DOM元素\n\n- v-show：每次不会重新进行DOM的添加/删除操作，只是在这个元素上添加/移除`style=\"display:none\"`属性，表示节点的显示和隐藏。\n\n优缺点：\n\n- v-if：有较高的切换性能消耗。这个很好理解，毕竟每次都要进行dom的添加／删除操作。\n\n- v-show：**有较高的初始渲染消耗**。也就是说，即使一开始`v-show=\"false\"`，该节点也会被创建，只是隐藏起来了。而`v-if=\"false\"`的节点，根本就不会被创建。\n\n**总结**：\n\n- 如果元素涉及到频繁的切换，最好不要使用 v-if, 而是推荐使用 v-show\n\n- 如果元素可能永远也不会被显示出来被用户看到，则推荐使用 v-if\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "12-Vue基础/05-Vue的举例：列表功能.md",
    "content": "---\ntitle: 05-Vue的举例：列表功能\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 列表功能举例\n\n### 步骤 1：列表功能\n\n完整的代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <style>\n        .table {\n            width: 800px;\n            margin: 20px auto;\n            border-collapse: collapse; /*这一行，不能少：表格的两边框合并为一条*/\n        }\n\n        .table th {\n            background: #0094ff;\n            color: white;\n            font-size: 16px;\n            border: 1px solid black;\n            padding: 5px;\n\n        }\n\n        .table tr td {\n            text-align: center;\n            font-size: 16px;\n            padding: 5px;\n            border: 1px solid black;\n        }\n    </style>\n\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n<div id=\"app\">\n\n    <table class=\"table\">\n        <th>编号</th>\n        <th>名称</th>\n        <th>创建时间</th>\n        <th>操作</th>\n        <tr v-for=\"item in list\">\n            <td>{{item.id}}</td>\n            <td>{{item.name}}</td>\n            <td>{{item.ctime}}</td>\n            <td><a href=\"#\">删除</a></td>\n        </tr>\n    </table>\n</div>\n\n</body>\n\n<script>\n    var vm = new Vue({\n        el: '#app',\n        data: {\n            list: [{id: 1, name: '奔驰', ctime: new Date}, {id: 2, name: '大众', ctime: new Date}]\n        }\n    })\n\n</script>\n\n</html>\n```\n\n**代码分析**：数据是存放在data的list中的，将data中的数据通过`v-for`遍历给表格。\n\n\n上方代码运行的效果：\n\n![](http://img.smyhvae.com/20180401_1517.png)\n\n### 步骤 2：无数据时，增加提示\n\n如果list中没有数据，那么表格中就会只显示表头`<th>`，这样显然不太好看。\n\n为此，我们需要增加一个`v-if`判断：当数据为空时，显示提示。如下：\n\n```html\n\t\t\t<tr v-show=\"list.length == 0\">\n\t\t\t\t<td colspan=\"4\">列表无数据</td>\n\t\t\t</tr>\n```\n\n代码解释：`colspan=\"4\"`指的是让当前这个`<td>`横跨4个单元格的位置。如下：\n\n![](http://img.smyhvae.com/20180401_1535.png)\n\n### 步骤 3：item的添加\n\n具体实现步骤如下：\n\n（1）用户填写的数据单独存放在data属性里，并采用`v-model`进行双向绑定。\n\n（2）用户把数据填好后，点击add按钮。此时需要增加一个点击事件的方法，将data中的数据放到list中（同时，清空文本框中的内容）。\n\n（3）将数据展示出来。`v-for`有个特点：当list数组发生改变后，vue.js就会自动调用`v-for`重新将数据生成，这样的话，就实现了数据的自动刷新。\n\n完整的代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <style>\n        .table {\n            width: 800px;\n            margin: 20px auto;\n            border-collapse: collapse; /*这一行，不能少：表格的两边框合并为一条*/\n        }\n\n        .table th {\n            background: #0094ff;\n            color: white;\n            font-size: 16px;\n            border: 1px solid black;\n            padding: 5px;\n        }\n\n        .table tr td {\n            text-align: center;\n            font-size: 16px;\n            padding: 5px;\n            border: 1px solid black;\n        }\n\n        .form {\n            width: 800px;\n            margin: 20px auto;\n        }\n\n        .form button {\n            margin-left: 10px;\n        }\n    </style>\n\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n<div id=\"app\">\n\n    <div class=\"form\">\n\n        编号：<input type=\"text\" v-model=\"formData.id\">\n        名称：<input type=\"text\" v-model=\"formData.name\">\n\n        <button v-on:click=\"addData\">添加</button>\n    </div>\n\n    <table class=\"table\">\n        <th>编号</th>\n        <th>名称</th>\n        <th>创建时间</th>\n        <th>操作</th>\n        <tr v-show=\"list.length == 0\">\n            <td colspan=\"4\">列表无数据</td>\n        </tr>\n        <tr v-for=\"item in list\">\n            <td>{{item.id}}</td>\n            <td>{{item.name}}</td>\n            <td>{{item.ctime}}</td>\n            <td><a href=\"#\">删除</a></td>\n        </tr>\n    </table>\n</div>\n\n</body>\n\n<script>\n    var vm = new Vue({\n        el: '#app',\n        data: {\n            list: [{id: 1, name: '奔驰', ctime: new Date}, {id: 2, name: '大众', ctime: new Date}],\n            //用户添加的数据\n            formData: {\n                id: 0,\n                name: \"\"\n            }\n        },\n\n        methods: {\n            addData: function () {\n                //将数据追加到list中\n                var p = {id: this.formData.id, name: this.formData.name, ctime: new Date()};\n                this.list.push(p);\n\n                //清空页面上的文本框中的数据\n                this.formData.id = 0;\n                this.formData.name = '';\n            }\n        }\n    });\n\n</script>\n\n</html>\n\n```\n\n### 步骤 4：item的删除\n\nhtml部分：\n\n```html\n            <!--绑定delete事件，根据括号里的参数进行删除-->\n            <td><a href=\"#\" v-on:click=\"delData(item.id)\">删除</a></td>\n```\n\njs部分：\n\n```javascript\n            delData: function (id) {\n                // 0 提醒用户是否要删除数据\n                if (!confirm('是否要删除数据?')) {\n                    //当用户点击的取消按钮的时候，应该阻断这个方法中的后面代码的继续执行\n                    return;\n                }\n\n                // 1 调用list.findIndex()方法根据传入的id获取到这个要删除数据的索引值（在数组中的索引值）\n                var index = this.list.findIndex(function (item) {\n                    return item.id == id\n                });\n\n                // 2.0 调用方法：list.splice(待删除的索引, 删除的元素个数)\n                this.list.splice(index, 1);\n            }\n```\n\n\n代码解释：`find()`和`findIndex()`是ES6中为数组新增的函数。详细解释如下：\n\n```javascript\n    // 根据id得到下标\n\n    // 默认去遍历list集合，将集合中的每个元素传入到function的item里，\n    var index = this.list.findIndex(function(item){\n    //根据item中的id属性去匹配传进来的id\n    //如果是则返回true ；否返回false,继续下面的一条数据的遍历，以此类推\n    return item.id ==id; //如果返回true，那么findIndex方法会将这个item对应的index\n    });\n```\n\n也就是说，我们是根据 item.id 找到这个 item 是属于list 数组中的哪个index索引。找到了index，就可以根据index来删除数组中的那个元素了。\n\n当item被删除后，v-for会被自动调用，进而自动更新view。\n\n完整版代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <style>\n        .table {\n            width: 800px;\n            margin: 20px auto;\n            border-collapse: collapse;  /*这一行，不能少：表格的两边框合并为一条*/\n        }\n\n        .table th {\n            background: #0094ff;\n            color: white;\n            font-size: 16px;\n            border: 1px solid black;\n            padding: 5px;\n        }\n\n        .table tr td {\n            text-align: center;\n            font-size: 16px;\n            padding: 5px;\n            border: 1px solid black;\n        }\n\n        .form {\n            width: 800px;\n            margin: 20px auto;\n        }\n\n        .form button {\n            margin-left: 10px;\n        }\n    </style>\n\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"app\">\n\n        <div class=\"form\">\n\n            编号：\n            <input type=\"text\" v-model=\"formData.id\"> 名称：\n            <input type=\"text\" v-model=\"formData.name\">\n\n            <button v-on:click=\"addData\">添加</button>\n        </div>\n\n        <table class=\"table\">\n            <th>编号</th>\n            <th>名称</th>\n            <th>创建时间</th>\n            <th>操作</th>\n            <tr v-show=\"list.length == 0\">\n                <td colspan=\"4\">列表无数据</td>\n            </tr>\n            <tr v-for=\"item in list\">\n                <td>{{item.id}}</td>\n                <td>{{item.name}}</td>\n                <td>{{item.ctime}}</td>\n                <!--绑定delete事件，根据括号里的参数进行删除-->\n                <td>\n                    <a href=\"#\" v-on:click=\"delData(item.id)\">删除</a>\n                </td>\n            </tr>\n        </table>\n    </div>\n\n</body>\n\n<script>\n    var vm = new Vue({\n        el: '#app',\n        data: {\n            list: [{ id: 1, name: '奔驰', ctime: new Date }, { id: 2, name: '大众', ctime: new Date }],\n            //用户添加的数据\n            formData: {\n                id: 0,\n                name: \"\"\n            }\n        },\n\n        methods: {\n            addData: function () {\n                //将数据追加到list中\n                var p = { id: this.formData.id, name: this.formData.name, ctime: new Date() };\n                this.list.push(p);\n\n                //清空页面上的文本框中的数据\n                this.formData.id = 0;\n                this.formData.name = '';\n            },  //注意：方法之间用逗号隔开，这个逗号不要忘记了\n\n            delData: function (id) {\n                // 0 提醒用户是否要删除数据\n                if (!confirm('是否要删除数据?')) {\n                    //当用户点击的取消按钮的时候，应该阻断这个方法中的后面代码的继续执行\n                    return;\n                }\n\n                // 1 调用list.findIndex()方法根据传入的id获取到这个要删除数据的索引值\n                var index = this.list.findIndex(function (item) {\n                    return item.id == id\n                });\n\n                // 2 调用方法：list.splice(待删除的索引, 删除的元素个数)\n                this.list.splice(index, 1);\n            }\n\n\n        }\n    });\n\n</script>\n\n</html>\n```\n\n### 步骤 5：按条件筛选item\n\n现在要求实现的效果是，在搜索框输入关键字 keywords，列表中仅显示匹配出来的内容。也就是说：\n\n- 之前， v-for 中的数据，都是直接从 data 上的list中直接渲染过来的。\n\n- 现在， 我们在使用`v-for`进行遍历显示的时候，不能再遍历全部的 list 了；我们要自定义一个 search 方法，同时，把keywords作为参数，传递给 search 方法。即`v-for=\"item in search(keywords)\"`。\n\n在 search(keywords) 方法中，为了获取 list 数组中匹配的item，我们可以有两种方式实现。如下。\n\n**方式一**：采用`forEach + indexOf()`\n\n```javascript\n    search(keywords) { // 根据关键字，进行数据的搜索，返回匹配的item\n\n        //实现方式一：通过 indexOf() 进行匹配。\n        var newList = [];\n        this.list.forEach(item => {\n            if (item.name.indexOf(keywords) != -1) {  //只要不等于 -1，就代表匹配到了\n                newList.push(item)\n            }\n        })\n        return newList\n    }\n```\n\n上方代码中， 我们要注意 indexOf(str) 的用法。举例如下：\n\n```javascript\n        var str = 'smyhvae';\n\n        console.log(str.indexOf('s'));  //打印结果：0\n\n        console.log(str.indexOf(''));   //打印结果：0。（说明，即使去匹配空字符串，也是返回0）\n\n        console.log(str.indexOf('h'));  //打印结果：3\n\n        console.log(str.indexOf('x'));  //打印结果：-1 （说明，匹配不到任何字符串）\n```\n\n上方代码中，也就是说，如果参数为空字符串，那么，每个item都能匹配到。\n\n**方式二**： filter + includes()方法\n\n```javascript\n    search(keywords) { // 根据关键字，进行数据的搜索，返回匹配的item\n\n        var newList = this.list.filter(item => {\n            // 注意 ： ES6中，为字符串提供了一个新方法，叫做  String.prototype.includes('要包含的字符串')\n            //  如果包含，则返回 true ，否则返回 false\n            if (item.name.includes(keywords)) {\n                return item\n            }\n        })\n\n        return newList\n    }\n```\n\n注意：forEach   some   filter   findIndex，这些都属于数组的新方法，都会对数组中的每一项，进行遍历，执行相关的操作。这里我们采用数组中的 filter 方法，\n\n总的来说，方式二的写法更优雅，因为字符串的 includes()方法确实很实用。\n\n完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <style>\n        .table {\n            width: 800px;\n            margin: 20px auto;\n            border-collapse: collapse;/*这一行，不能少：表格的两边框合并为一条*/\n        }\n\n        .table th {\n            background: #0094ff;\n            color: white;\n            font-size: 16px;\n            border: 1px solid black;\n            padding: 5px;\n        }\n\n        .table tr td {\n            text-align: center;\n            font-size: 16px;\n            padding: 5px;\n            border: 1px solid black;\n        }\n\n        .form {\n            width: 800px;\n            margin: 20px auto;\n        }\n\n        .form button {\n            margin-left: 10px;\n        }\n    </style>\n\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"app\">\n\n        <div class=\"form\">\n\n            编号：\n            <input type=\"text\" v-model=\"formData.id\"> 名称：\n            <input type=\"text\" v-model=\"formData.name\">\n\n            <button v-on:click=\"addData\">添加</button>\n            搜索：\n            <input type=\"text\" v-model=\"keywords\">\n\n        </div>\n\n        <table class=\"table\">\n            <th>编号</th>\n            <th>名称</th>\n            <th>创建时间</th>\n            <th>操作</th>\n            <tr v-show=\"list.length == 0\">\n                <td colspan=\"4\">列表无数据</td>\n            </tr>\n            <tr v-for=\"item in search(keywords)\">\n                <td>{{item.id}}</td>\n                <td>{{item.name}}</td>\n                <td>{{item.ctime}}</td>\n                <!--绑定delete事件，根据括号里的参数进行删除-->\n                <td>\n                    <a href=\"#\" v-on:click=\"delData(item.id)\">删除</a>\n                </td>\n            </tr>\n        </table>\n    </div>\n\n</body>\n\n<script>\n    var vm = new Vue({\n        el: '#app',\n        data: {\n            list: [{ id: 1, name: '奔驰', ctime: new Date }, { id: 2, name: '大众', ctime: new Date }],\n            //用户添加的数据\n            formData: {\n                id: '',\n                name: \"\"\n            },\n            keywords: \"\"\n        },\n\n        methods: {\n            addData: function () {\n                //将数据追加到list中\n                var p = { id: this.formData.id, name: this.formData.name, ctime: new Date() };\n                this.list.push(p);\n\n                //清空页面上的文本框中的数据\n                this.formData.id = '';\n                this.formData.name = '';\n            },  //注意：方法之间用逗号隔开，这个逗号不要忘记了\n\n            delData: function (id) {\n                // 0 提醒用户是否要删除数据\n                if (!confirm('是否要删除数据?')) {\n                    //当用户点击的取消按钮的时候，应该阻断这个方法中的后面代码的继续执行\n                    return;\n                }\n\n                // 1 调用list.findIndex()方法根据传入的id获取到这个要删除数据的索引值\n                var index = this.list.findIndex(function (item) {\n                    return item.id == id\n                });\n\n                // 2 调用方法：list.splice(待删除的索引, 删除的元素个数)\n                this.list.splice(index, 1);\n            },\n\n            search(keywords) { // 根据关键字，进行数据的搜索，返回匹配的item\n\n                var newList = this.list.filter(item => {\n                    // 注意 ： ES6中，为字符串提供了一个新方法，叫做  String.prototype.includes('要包含的字符串')\n                    //  如果包含，则返回 true ，否则返回 false\n                    if (item.name.includes(keywords)) {\n                        return item\n                    }\n                })\n\n                return newList\n            }\n        }\n    });\n\n</script>\n\n</html>\n\n```\n\n备注：在1.x 版本中可以通过filterBy指令来实现过滤，但是在2.x中已经被废弃了。\n\n\n\n\n\n"
  },
  {
    "path": "12-Vue基础/06-自定义过滤器：时间格式化举例.md",
    "content": "---\ntitle: 06-自定义过滤器：时间格式化举例\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n\n## 前言\n\n\n> 我们接着上一篇文章01-04来讲。\n\n### 过滤器的概念\n\n**概念**：Vue.js 允许我们自定义过滤器，可被用作一些常见的文本格式化。过滤器可以用在两个地方：mustache **插值表达式**、 **v-bind表达式**。过滤器应该被添加在 JavaScript 表达式的尾部，由“管道”符指示。\n\n\n### Vue1.X中的系统过滤器\n\nVue提供了一系列的固定逻辑来使程序员更加容易的实现这些功能，这些过滤器称之为系统过滤器。\n\n系统过滤器是Vue1.0中存在的，在Vue2.0中已经删除了。\n\n系统过滤器的使用，可以参考参考文档：<http://v1-cn.vuejs.org/api/#过滤器>\n\n\nVue也提供了一个接口用来供程序员定义属于自己的特殊逻辑，Vue称之为自定义过滤器。我们接下来讲一讲。\n\n\n## 自定义全局过滤器\n\n文档地址：<http://v1-cn.vuejs.org/guide/custom-filter.html>\n\n\n### 全局过滤器的基本使用\n\n\n我们可以用全局方法`Vue.filter()`自定义一个全局过滤器。这样的话，每一个Vue的对象实例（每一个VM实例）都可以拿到这个过滤器。它接收两个参数：过滤器的名称 、过滤器函数。\n\n比如说，我要将`曾经，我也是一个单纯的少年，单纯的我，傻傻的问，谁是世界上最单纯的男人`这句 msg 中的“单纯”改为“邪恶”。可以这样做：\n\n（1）在插值表达式中这样调用：\n\n```html\n        <p>{{ msg | msgFormat }</p>\n```\n\n\n上方代码的意思是说：\n\n- **管道符前面**的`msg`：要把 `msg` 这段文本进行过滤，\n\n- **管道符后面**的`msgFormat`：是通过`msgFormat`这个过滤器进行来操作。\n\n（2）定义过滤器`msgFormat`：\n\n```javascript\n        // Vue.filter 中的第一个参数是过滤器的名称，第二个参数是具体的过滤器函数\n        // 定义一个 Vue 全局的过滤器，名字叫做  msgFormat\n        Vue.filter('msgFormat', function (myMsg) {  // function 的第一个参数指的是管道符前面的 msg\n            // 字符串的  replace 方法，第一个参数，除了可写一个 字符串之外，还可以定义一个正则\n            return myMsg.replace(/单纯/g, '邪恶')\n        })\n```\n\n上方代码解释：\n\n- `Vue.filter(‘过滤器的名称’, 具体的过滤器函数)`中的第一个参数指的就是过滤器的名称（必须和**管道符后面**的名称**完全一致**），第二个参数是具体的过滤器函数\n\n- 过滤器函数function中，第一个参数指的**管道符前面的**msg。\n\n- `replace()`方法是用来做字符串的替换的。第一个参数如果只写成`单纯`，那么就会只修改 msg 中的第一个`单纯`字样。所以这里就用正则去匹配msg 中所有的`单纯`字样。\n\n最终，完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 通过 过滤器 msgFormat 对 msg 进行过滤-->\n        <p>{{ msg | msgFormat }}</p>\n    </div>\n\n    <script>\n        // 定义一个 Vue 全局的过滤器，名字叫做  msgFormat\n        Vue.filter('msgFormat', function (myMsg) {\n            // 字符串的  replace 方法，第一个参数，除了可写一个 字符串之外，还可以定义一个正则\n            //将 myMsg 中的所有`单纯`字样，修改为`邪恶`\n            return myMsg.replace(/单纯/g, '邪恶')\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                msg: '曾经，我也是一个单纯的少年，单纯的我，傻傻的问，谁是世界上最单纯的男人'\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n\n网页显示效果如下：\n\n![](http://img.smyhvae.com/20180522_1240.png)\n\n### 给过滤器添加多个参数\n\n上面的举例代码中，`{{ msg | msgFormat }}`中，**过滤器的调用并没有加参数**，其实它还可以添加多个参数。\n\n接下来，我们在上面的举例代码中进行改进。\n\n\n**改进一**：过滤器加一个参数。如下：\n\n\n将 msg 这个字符串中的“单纯”改为 xxx 变量。代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 【重要】通过 过滤器 msgFormat 对 msg 进行过滤。括号里的参数代表 function中的 arg2-->\n        <p>{{ msg | msgFormat('xxx') }}</p>\n\n    </div>\n\n    <script>\n        // 定义一个 Vue 全局的过滤器，名字叫做  msgFormat\n        Vue.filter('msgFormat', function (myMsg, arg2) {\n            // 字符串的  replace 方法：第一个参数，除了可写一个 字符串之外，还可以定义一个正则；第二个参数代表要替换为上面的xxx\n            //将 myMsg 中的所有`单纯`字样，修改为 arg2\n            return myMsg.replace(/单纯/g, arg2)\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                msg: '曾经，我也是一个单纯的少年，单纯的我，傻傻的问，谁是世界上最单纯的男人'\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n\n```\n\n\n![](http://img.smyhvae.com/20180525_2135.png)\n\n注意代码中那行重要的注释：括号里的参数代表 function中的 arg2。\n\n**改进二**：过滤器加两个参数。如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 通过 过滤器 msgFormat 对 msg 进行过滤-->\n        <!-- 【重要】括号里的第一个参数代表 function 中的 arg2，括号里的第二个参数代表 function 中的 arg3-->\n        <p>{{ msg | msgFormat('【牛x】', '【参数arg3】') }}</p>\n\n    </div>\n\n    <script>\n        // 定义一个 Vue 全局的过滤器，名字叫做  msgFormat\n        Vue.filter('msgFormat', function (myMsg, arg2, arg3) {\n            // 字符串的  replace 方法：第一个参数，除了可写一个 字符串之外，还可以定义一个正则；第二个参数代表要替换为 xxx\n            //将 myMsg 中的所有`单纯`字样，修改为`arg2 + arg3`\n            return myMsg.replace(/单纯/g, arg2 + arg3)\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                msg: '曾经，我也是一个单纯的少年，单纯的我，傻傻的问，谁是世界上最单纯的男人'\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n\n效果如下：\n\n![](http://img.smyhvae.com/20180525_2150.png)\n\n\n**改进3：同时使用多个过滤器**\n\n对 msg 同时使用多个过滤器。例如：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 通过 两个过滤器（msgFormat、myFilter2）对 msg 进行过滤-->\n        <!-- 将 msg 交给第一个过滤器来处理，然后将处理的结果交给第二个过滤器来处理-->\n        <p>{{ msg | msgFormat('【牛x】', '【参数arg3】') | myFilter2}}</p>\n\n    </div>\n\n    <script>\n        // 定义一个 Vue 全局的过滤器，名字叫做  msgFormat\n        Vue.filter('msgFormat', function (myMsg, arg2, arg3) {\n            // 字符串的  replace 方法：第一个参数，除了可写一个 字符串之外，还可以定义一个正则；第二个参数代表要替换为 xxx\n            //将 myMsg 中的所有`单纯`字样，修改为`arg2 + arg3`\n            return myMsg.replace(/单纯/g, arg2 + arg3)\n        })\n\n        //定义第二个全局过滤器\n        Vue.filter('myFilter2', function (myMsg) {\n            //在字符串 msg 的最后面加上【后缀】\n            return myMsg + '【后缀】'\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                msg: '曾经，我也是一个单纯的少年，单纯的我，傻傻的问，谁是世界上最单纯的男人'\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n效果如下：\n\n![](http://img.smyhvae.com/20180525_2200.png)\n\n上方代码中，添加了多个过滤器，实现的思路是：**将 msg 交给第一个过滤器来处理，然后将处理的结果交给第二个过滤器来处理** 。\n\n\n\n\n### 举例1：时间格式化\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        {{ time }}\n        <br /> {{ time | datefmt }}\n    </div>\n\n    <div id=\"app1\">\n        {{ time | datefmt }}\n    </div>\n</body>\n<script>\n\n    // 定义一个名称为 datafmt的全局过滤器\n    Vue.filter('datefmt', function (input) {\n        // 过滤器的逻辑：将input的值格式化成 yyyy-MM-dd 字符串输出\n        var res = '';\n        var year = input.getFullYear();\n        var month = input.getMonth() + 1;\n        var day = input.getDate();\n\n        res = year + '-' + month + '-' + day;\n\n        return res;\n    });\n\n    new Vue({\n        el: '#app1',\n        data: {\n            time: new Date()\n        }\n    })\n\n    new Vue({\n        el: '#app',\n        data: {\n            time: new Date()\n        }\n    });\n</script>\n\n</html>\n```\n\n运行效果：\n\n![](http://img.smyhvae.com/20180525_2230.png)\n\n### 举例2：时间格式化\n\n上面的举例1，时间格式化的过滤器，我们还有个更高端的写法：（字符串模板）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        2018-05-25T14:06:51.618Z\n        <br /> {{ '2018-05-25T14:06:51.618Z' | dateFormat }}\n    </div>\n\n</body>\n<script>\n\n    Vue.filter('dateFormat', function (dateStr, pattern = \"\") {\n        // 根据给定的时间字符串，得到特定的时间\n        var dt = new Date(dateStr)\n\n        //   yyyy-mm-dd\n        var y = dt.getFullYear()\n        var m = dt.getMonth() + 1\n        var d = dt.getDate()\n\n        // return y + '-' + m + '-' + d\n\n        if (pattern.toLowerCase() === 'yyyy-mm-dd') { //如果调用过滤器的参数写的是 yyyy-mm-dd，那就按照这种  yyyy-mm-dd 的格式写\n            //这里用的是字符串模板\n            return `${y}-${m}-${d}`\n        } else {  //否则（比如说调用过滤器时不写参数），后面就补上 时-分-秒\n            var hh = dt.getHours()\n            var mm = dt.getMinutes()\n            var ss = dt.getSeconds()\n\n            return `${y}-${m}-${d} ${hh}:${mm}:${ss}`\n        }\n    })\n\n    new Vue({\n        el: '#app',\n        data: {\n            time: new Date()\n        }\n    });\n</script>\n\n</html>\n```\n\n运行结果：\n\n![](http://img.smyhvae.com/20180526_2319.png)\n\n【荐】**举例2的改进**：（字符串的padStart方法使用）\n\n上图中，我们可以看到，箭头处的时间有些问题，比如说，`6`要写成`06`更合适。为了实现这个功能，我们可以这样做：\n\n使用ES6中的字符串新方法 `String.prototype.padStart(maxLength, fillString='')` 或 `String.prototype.padEnd(maxLength, fillString='')`来填充字符串。 `pad`在英文中指的是`补充`。\n\n实现举例如下：\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        2018-05-25T14:06:51.618Z\n        <br /> {{ '2018-05-25T14:06:51.618Z' | dateFormat }}\n    </div>\n\n</body>\n<script>\n\n    Vue.filter('dateFormat', function (dateStr, pattern) {\n        // 根据给定的时间字符串，得到特定的时间\n        var dt = new Date(dateStr)\n\n        //   yyyy-mm-dd\n        var y = dt.getFullYear()\n        var m = (dt.getMonth() + 1).toString().padStart(2, '0')\n        var d = dt.getDate().toString().padStart(2, '0')\n\n        if (pattern && pattern.toLowerCase() === 'yyyy-mm-dd') { //如果调用过滤器的参数写的是 yyyy-mm-dd，那就按照这种  yyyy-mm-dd 的格式写\n            //这里用的是字符串模板\n            return `${y}-${m}-${d}`\n        } else { //否则（比如说调用过滤器时不写参数），后面就补上 时-分-秒\n            var hh = dt.getHours().toString().padStart(2, '0')\n            var mm = dt.getMinutes().toString().padStart(2, '0')\n            var ss = dt.getSeconds().toString().padStart(2, '0')\n\n            return `${y}-${m}-${d} ${hh}:${mm}:${ss} ~~~~~~~`\n        }\n    })\n\n    new Vue({\n        el: '#app',\n        data: {\n            time: new Date()\n        }\n    });\n</script>\n\n</html>\n```\n\n\n运行效果如下：\n\n![](http://img.smyhvae.com/20180526_2323.png)\n\n`pattern`参数的解释：\n\n在做`if (pattern && pattern.toLowerCase() === 'yyyy-mm-dd')`这个判断时，逻辑是：**先保证pattern参数传进来了，然后继续后面的判断**。\n\n我们不能写成：`if (pattern.toLowerCase() === 'yyyy-mm-dd')`。因为，万一在调用的时候，不传递参数pattern，那么 if语句就相当于`if (undefined.toLowerCase() === 'yyyy-mm-dd')`，就会报错。\n\n当然，ES6中有个新特性叫“默认参数”，我们就可以这样写：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        2018-05-25T14:06:51.618Z\n        <br /> {{ '2018-05-25T14:06:51.618Z' | dateFormat }}\n    </div>\n\n</body>\n<script>\n\n    Vue.filter('dateFormat', function (dateStr, pattern = '') {\n        // 根据给定的时间字符串，得到特定的时间\n        var dt = new Date(dateStr)\n\n        //   yyyy-mm-dd\n        var y = dt.getFullYear()\n        var m = (dt.getMonth() + 1).toString().padStart(2, '0')\n        var d = dt.getDate().toString().padStart(2, '0')\n\n        if (pattern.toLowerCase() === 'yyyy-mm-dd') { //如果调用过滤器的参数写的是 yyyy-mm-dd，那就按照这种  yyyy-mm-dd 的格式写\n            //这里用的是字符串模板\n            return `${y}-${m}-${d}`\n        } else { //否则（比如说调用过滤器时不写参数），后面就补上 时-分-秒\n            var hh = dt.getHours().toString().padStart(2, '0')\n            var mm = dt.getMinutes().toString().padStart(2, '0')\n            var ss = dt.getSeconds().toString().padStart(2, '0')\n\n            return `${y}-${m}-${d} ${hh}:${mm}:${ss} ~~~~~~~`\n        }\n    })\n\n    new Vue({\n        el: '#app',\n        data: {\n            time: new Date()\n        }\n    });\n</script>\n\n</html>\n\n```\n\n\n## 自定义私有过滤器\n\n**私有过滤器**：在某一个 vue 对象内部定义的过滤器称之为私有过滤器。这种过滤器只有在当前vue对象的el指定的监管区域有用。\n\n\n**举例**：日期格式化\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        {{ time }}\n        <br />\n        {{ time | datefmt }}\n    </div>\n\n</body>\n<script>\n\n    new Vue({\n        el: '#app',\n        data: {\n            time: new Date()\n        },\n        //在某一个vue对象内部定义的过滤器称之为私有过滤器，\n        //这种过滤器只有在当前vue对象el指定的监管的区域有用\n        filters: {\n            // input是自定义过滤器的默认参数，input的值永远都是取自于 | 左边的内容\n            datefmt: function (input) {\n                // 定义过滤器的内容：将input的值格式化成 yyyy-MM-dd 字符串输出\n                var res = '';\n                var year = input.getFullYear();\n                var month = input.getMonth() + 1;\n                var day = input.getDate();\n\n                res = year + '-' + month + '-' + day;\n\n                return res;\n            }\n        }\n    });\n</script>\n\n</html>\n```\n\n上面的代码中，我们在vue实例中，通过`filters`关键字，在里面定义了一个局部过滤器`datefmt`。\n\n\n运行结果：\n\n![](http://img.smyhvae.com/20180405_2038.png)\n\n第一行代码显示的是默认的date。第二行代码显示的是格式化之后的date，说明过滤器是起到了作用的。\n\n### 总结\n\n过滤器调用的时候，采用的是**就近原则**，如果私有过滤器和全局过滤器名称一致了，这时候 优先调用私有过滤器。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## axios\n\naxios是在Vue中专门用来发送ajax请求的。\n\n但是，axios并不依赖于Vue.js库，而是基于promise的。"
  },
  {
    "path": "12-Vue基础/07-自定义按键修饰符&自定义指令.md",
    "content": "---\ntitle: 07-自定义按键修饰符&自定义指令\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n## v-on的按键修饰符\n\n### Vue 内置的按键修饰符\n\n通俗一点讲，指的是：监听键盘输入的事件。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符。如下：\n\nVue内置的按键修饰符：\n\n```\n    .enter\n    .tab\n    .delete (捕获 “删除” 和 “退格” 键)\n    .esc\n    .space\n    .up\n    .down\n    .left\n    .right\n    1.0.8+版本：支持单字母的按键别名。\n```\n\n\n比如说，`keyup`指的是：键盘（任何键位）抬起时的监听事件。`.enter`指的是：按enter键的按键修饰符。我们把这两个结合起来看看。\n\n**`@keyup.enter`举例**：按enter键后的监听事件\n\n`@keyup.enter=\"addData\"`表示：按住enter键后，执行addData()方法。**全称**是`v-on:key.enter=\"addData\"`。\n\n我们还是拿`01-04`这篇文章中的列表功能来举例。之前是点击“添加”按钮后，列表中会添加一个item。现在要求：在**输入框**中按enter键后，也能添加一个item。\n\n核心代码如下：\n\n```html\n\t<input type=\"text\" v-model=\"formData.name\" @keyup.enter=\"addData\">\n```\n\n注意，如果写成`@keyup=\"addData\"`，效果却是：只要键盘的任何键位打了字（还没来得及按enter键），就会执行addData()方法，这种效果显然不是我们想要的。所以要加上修饰符`.enter`，表示只针对enter键。\n\n### 自定义的按键修饰符\n\n如果我们直接在代码的`<input>`标签里写`@keyup.f2=\"addData\"`，那么，按住「F2键」后，是没有效果的，因为「F2键」不是内置的按键修饰符（如果F2不能验证，你可以试一下F7）。\n\n我们知道，每个按键都有一个键盘码。参考链接：\n\n- [js 里面的键盘事件对应的键码](http://www.cnblogs.com/wuhua1/p/6686237.html)\n\n通过查阅，我们知道了「F2键」的键盘码为`113`，那代码可以这样写：（按住F2键后，执行 addData 方法）\n\n```html\n\t<input type=\"text\" v-model=\"formData.name\" @keyup.113=\"addData\">\n```\n\n虽然键盘码很全，但是不好记呀。于是，接下来，我们给键盘码定义别名。\n\n**自定义全局按键修饰符**：\n\n```\n    //自定义全局按键修饰符\n    Vue.config.keyCodes.f2 = 113;\n```\n\n上方代码的书写位置，与自定义全局过滤器的位置，是并列的。\n\n然后，我们就可以使用键盘码的别名了。\n\n## 自定义全局指令\n\n### 自定义全局指令的举例1\n\n**举例1**：让指定文本框自动获取焦点\n\n如果我们想实现这个例子，原生js的写法是：\n\n```javascript\n    //原生js写法：网页一打开，就让指定的输入框自动获取焦点\n    document.getElementById('search').focus()\n```\n\n代码的位置：\n\n![](http://img.smyhvae.com/20180527_2340.png)\n\n但我们不建议这样做。我们可以通过Vue中的自定义指令来实现这个例子。步骤如下。\n\n（1）使用`Vue.directive()`自定义全局指令：\n\n```javascript\n\n    //自定义全局指令 v-focus：让文本框自动获取焦点\n    //参数1：指令的名称。注意，在定义的时候，指令的名称前面，不需要加 v- 前缀；但是：在`调用`的时候，必须在指令名称前 加上 v- 前缀\n    //参数2：是一个对象，这个对象身上，有一些指令相关的函数，这些函数可以在特定的阶段，执行相关的操作\n    Vue.directive('focus', {\n    \t//在每个函数中，第一个参数，永远是 el ，表示 被绑定了指令的那个元素，这个 el 参数，是一个原生的JS对象（DOM对象）\n        bind: function (el) { // 每当指令绑定到元素上的时候，会立即执行这个 bind 函数，【只执行一次】\n            // 在元素 刚绑定了指令的时候，还没有 插入到 DOM中去，这时候，调用 focus 方法没有作用\n            //  因为，一个元素，只有插入DOM之后，才能获取焦点\n            // el.focus()\n        },\n        inserted: function (el) {  // inserted 表示元素 插入到DOM中的时候，会执行 inserted 函数【触发1次】\n            el.focus()\n            // 和JS行为有关的操作，最好在 inserted 中去执行，放置 JS行为不生效\n        },\n        updated: function (el) {  // 当VNode更新的时候，会执行 updated， 【可能会触发多次】\n\n        }\n    })\n```\n\n上方的代码中，如果我们把`el.focus()`这行代码写在`bind`方法里，是没有效果的（但不会报错）。没有效果是因为，在执行到`bind`方法的时候，元素还没有插入到dom中去。\n\n由此可以看看出：`bind`、`inserted`、`updated`这三个钩子函数的执行时机不同，且执行的次数有区别。\n\n（2）在指定的文本框上加``：\n\n```html\n<input type=\"text\" id=\"search\" v-model=\"keywords\" v-focus>\n```\n\n完整版代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"app\">\n        搜索框：\n        <input type=\"text\" id=\"search\" v-model=\"name\" v-focus>\n    </div>\n\n    <script>\n\n        //自定义全局指令 v-focus，让文本框自动获取焦点\n        //参数1：指令的名称。注意，在定义的时候，指令的名称前面，不需要加 v- 前缀；但是：在`调用`的时候，必须在指令名称前 加上 v- 前缀\n        //参数2：是一个对象，这个对象身上，有一些指令相关的函数，这些函数可以在特定的阶段，执行相关的操作\n        Vue.directive('focus', {\n            //在每个函数中，第一个参数，永远是 el ，表示 被绑定了指令的那个元素，这个 el 参数，是一个原生的JS对象（DOM对象）\n            bind: function (el) { // 每当指令绑定到元素上的时候，会立即执行这个 bind 函数，【只执行一次】\n                // 在元素 刚绑定了指令的时候，还没有 插入到 DOM中去，这时候，调用 focus 方法没有作用\n                //  因为，一个元素，只有插入DOM之后，才能获取焦点\n                // el.focus()\n            },\n            inserted: function (el) {  // inserted 表示元素 插入到DOM中的时候，会执行 inserted 函数【触发1次】\n                el.focus()\n                // 和JS行为有关的操作，最好在 inserted 中去执行，防止 JS行为不生效\n            },\n            updated: function (el) {  // 当VNode更新的时候，会执行 updated， 【可能会触发多次】\n            }\n        })\n\n        new Vue({\n            el: '#app',\n            data: {\n                name: 'smyhvae'\n            }\n        })\n    </script>\n</body>\n\n</html>\n\n```\n\n\n### 自定义全局指令：使用钩子函数的第二个binding参数拿到传递的值\n\n**举例2**：设置DOM元素的color样式\n\n\n参考举例1中的写法，我们可能会这样给DOM元素设置样式：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"app\">\n        搜索框：\n        <input type=\"text\" id=\"search\" v-model=\"name\" v-color>\n    </div>\n\n    <script>\n\n        //自定义全局指令 v-color：设置DOM元素的color属性\n        Vue.directive('color', {\n            bind: function (el) { // 每当指令绑定到元素上的时候，会立即执行这个 bind 函数，【只执行一次】\n                el.style.color = 'red';\n            },\n            inserted: function (el) {  // inserted 表示元素 插入到DOM中的时候，会执行 inserted 函数【触发1次】\n                // 和JS行为有关的操作，最好在 inserted 中去执行，防止 JS行为不生效\n                //el.focus()\n            },\n            updated: function (el) {  // 当VNode更新的时候，会执行 updated， 【可能会触发多次】\n            }\n        })\n\n        new Vue({\n            el: '#app',\n            data: {\n                name: ''\n            }\n        })\n    </script>\n</body>\n\n</html>\n```\n\n如上方代码所示，我们自定义了一个指令`v-color`，然后在`input`标签中用上了这个指令，就给元素设置了color属性。但是这个代码有个弊端是：color的属性值在定义指令的时候，被写死了。如何完善呢？我们可以在DOM元素中传参。一起来看看。\n\n代码如下：【荐】\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"app\">\n        搜索框1：\n        <input type=\"text\" id=\"search\" v-model=\"name\" v-color=\"'green'\">\n    </div>\n\n    <script>\n\n        //自定义全局指令 v-color：设置DOM元素的color属性\n        Vue.directive('color', {\n            // 样式，只要通过指令绑定给了元素，不管这个元素有没有被插入到页面中去，这个元素肯定有了一个内联的样式\n            // 将来元素肯定会显示到页面中，这时候，浏览器的渲染引擎必然会解析样式，应用给这个元素\n            // 意思是说，我们可以把样式的代码写到bind中去（即使这个时候，dom元素还没有被创建）\n            bind: function (el, binding) { // 每当指令绑定到元素上的时候，会立即执行这个 bind 函数，【只执行一次】\n\n                console.log(binding.name); //打印结果：color\n                console.log(binding.value); //打印结果：green\n                console.log(binding.expression);  //'green'\n\n                el.style.color = binding.value// 通过bining拿到v-color中传递过来的值\n\n            },\n            inserted: function (el) {  // inserted 表示元素 插入到DOM中的时候，会执行 inserted 函数【触发1次】\n                // 和JS行为有关的操作，最好在 inserted 中去执行，防止 JS行为不生效\n                //el.focus()\n            },\n            updated: function (el) {  // 当VNode更新的时候，会执行 updated， 【可能会触发多次】\n            }\n        })\n\n        new Vue({\n            el: '#app',\n            data: {\n                name: 'smyhvae'\n            }\n        })\n    </script>\n</body>\n\n</html>\n```\n\n上方代码中,bind方法里传递的第二个参数`binding`，可以拿到DOM元素中`v-color`里填的值。注意，`v-color=\"'green'\"`，这里面写的是字符串常量；如果去掉单引号，就成了变量，不是我们想要的。\n\n效果：\n\n![](http://img.smyhvae.com/20180610_1323.png)\n\n**自定义全局指令的简写形式**：\n\n\n在很多时候，你可能想在 bind 和 update 时触发相同行为，而不关心其它的钩子。比如上面的代码中，我们可以写成简写形式：\n\n```javascript\n        Vue.directive('color', function (el, binding) { //注意，这个function等同于把代码写到了 bind 和 update 中去\n            el.style.color = binding.value\n        })\n```\n\n\n## 自定义私有指令\n\n**自定义私有指令**：在某一个 vue 对象内部自定义的指令称之为私有指令。这种指令只有在当前vue对象的el指定的监管区域有用。\n\n代码举例：（设置文字的font-weight属性）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"app\">\n        <span v-fontweight=\"600\">生命壹号</span>\n    </div>\n    <script>\n\n        new Vue({\n            el: '#app',\n            data: {\n                name: 'smyhvae'\n            },\n            //自定义私有指令\n            directives: {\n                'fontweight': {\n                    bind: function (el, binding) {\n                        el.style.fontWeight = binding.value;\n                    }\n                }\n            }\n        })\n    </script>\n</body>\n\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180610_1400.png)\n\n注意， el.style.fontWeight设置属性值，至少要600，否则看不到加粗的效果。\n\n**自定义私有指令的简写形式**：\n\n在很多时候，你可能想在 bind 和 update 时触发相同行为，而不关心其它的钩子。比如上面的代码中，我们可以写成简写形式：\n\n```\n            //自定义私有指令（简写形式）\n            directives: {\n                'fontweight': function (el, binding) { //注意，这个function等同于把代码写到了 bind 和 update 中去\n                    el.style.fontWeight = binding.value;\n                }\n            }\n```\n\n\n\n\n\n\n\n"
  },
  {
    "path": "12-Vue基础/08-Vue实例的生命周期函数.md",
    "content": "---\r\ntitle: 08-Vue实例的生命周期函数\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 介绍\r\n\r\n\r\n\r\n- [vue实例的生命周期](https://cn.vuejs.org/v2/guide/instance.html#实例生命周期)：从Vue实例创建、运行、到销毁期间，总是伴随着各种各样的事件，这些事件，统称为生命周期。\r\n\r\n\r\n- [生命周期钩子](https://cn.vuejs.org/v2/api/#选项-生命周期钩子)：就是生命周期事件的别名而已。\r\n\r\n生命周期钩子 = 生命周期函数 = 生命周期事件。\r\n\r\n\r\n## 生命周期函数的主要分类\r\n\r\n![](http://img.smyhvae.com/20180422_1650.png)\r\n\r\n根据上面这张图，我们把生命周期函数主要分为三类。\r\n\r\n\r\n### 1、创建期间的生命周期函数\r\n\r\n- beforeCreate：实例刚在内存中被创建出来，此时，还没有初始化好 data 和 methods 属性\r\n\r\n- created：实例已经在内存中创建OK，此时 data 和 methods 已经创建OK，此时还没有开始 编译模板。我们可以在这里进行Ajax请求。\r\n\r\n- beforeMount：此时已经完成了模板的编译，但是还没有挂载到页面中\r\n\r\n- mounted：此时，已经将编译好的模板，挂载到了页面指定的容器中显示。（mounted之后，表示**真实DOM渲染完了，可以操作DOM了**）\r\n\r\n\r\n**举例**：\r\n\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>Title</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <!--这个div区域就是MVVM中的 View-->\r\n    <div id=\"app\">\r\n    </div>\r\n</body>\r\n\r\n<script>\r\n    new Vue({\r\n        el: '#app',\r\n        data: {\r\n            msg: 'hello vuejs'\r\n        },\r\n        // 这是第1个生命周期函数，表示实例完全被创建出来之前，会执行它\r\n        beforeCreate: function () {\r\n            console.log('01 beforeCreate', this.msg);\r\n            //注意：在 beforeCreate 生命周期函数执行的时候，data 和 methods 中的 数据都还没有没初始化\r\n        },\r\n\r\n        // 这是第2个生命周期函数\r\n        created: function () {\r\n            console.log('02 created', this.msg);\r\n            //注意：如果要调用 methods 中的方法，或者操作 data 中的数据，最早，只能在 created 中操作\r\n        },\r\n\r\n        // 这是第3个生命周期函数，表示 模板已经在内存中编辑完成了，但是尚未把模板渲染到页面中\r\n        beforeMount: function () {\r\n            console.log('03 beforeMount', this.msg);\r\n            // 在 beforeMount 执行的时候，页面中的元素，还没有被真正替换过来，只是之前写的一些模板字符串\r\n        },\r\n\r\n        // 这是第4个生命周期函数，表示，内存中的模板，已经真实的挂载到了页面中，用户已经可以看到渲染好的页面了\r\n        mounted: function () {\r\n            console.log('04 mounted', this.msg);\r\n            // 注意： mounted 是 实例创建期间的最后一个生命周期函数，当执行完 mounted 就表示，实例已经被完全创建好了\r\n            // 此时，如果没有其它操作的话，这个实例，就静静的 躺在我们的内存中，一动不动\r\n        }\r\n    });\r\n\r\n</script>\r\n\r\n</html>\r\n```\r\n\r\n打印结果：\r\n\r\n![](http://img.smyhvae.com/20180610_1500.png)\r\n\r\n### 运行期间的生命周期函数\r\n\r\n- beforeUpdate：状态更新之前执行此函数， 此时 data 中的状态值是最新的，但是界面上显示的 数据还是旧的，因为此时还没有开始重新渲染DOM节点\r\n\r\n- updated：实例更新完毕之后调用此函数，此时 data 中的状态值 和 界面上显示的数据，都已经完成了更新，界面已经被重新渲染好了。\r\n\r\nPS：数据发生变化时，会触发这两个方法。不过，我们一般用watch来做。\r\n\r\n\r\n**举例**：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>Title</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <!--这个div区域就是MVVM中的 View-->\r\n    <div id=\"app\">\r\n        <input type=\"button\" value=\"修改flag\" @click=\"myMethod\">\r\n        <h3 id=\"h3\">{{ flag }}</h3>\r\n    </div>\r\n</body>\r\n\r\n<script>\r\n    new Vue({\r\n        el: '#app',\r\n        data: {\r\n            msg: 'hello vue',\r\n            flag: false\r\n        },\r\n\r\n        methods: {\r\n            myMethod: function () {\r\n                this.flag = true;\r\n            }\r\n        },\r\n\r\n\r\n        // 接下来的是运行中的两个事件\r\n        // 这时候，我们的界面还没有被更新【但是，数据被更新了吗？  数据肯定被更新了】\r\n        beforeUpdate() {\r\n            console.log('-------------05 beforeUpdate', this.msg);\r\n\r\n            // 结论：当执行 beforeUpdate 的时候，页面中的显示的数据，还是旧的，此时 data 数据是最新的，页面尚未和 最新的数据保持同步\r\n            console.log('界面上DOM元素显示的内容：' + document.getElementById('h3').innerText)\r\n            console.log('data 中的 msg 数据：' + this.flag)\r\n        },\r\n        updated() {\r\n            console.log('-------------06 updated', this.msg);\r\n\r\n            // 结论：updated 事件执行的时候，页面和 data 数据已经保持同步了，都是最新的\r\n            console.log('界面上DOM元素显示的内容：' + document.getElementById('h3').innerText)\r\n            console.log('data 中的 msg 数据：' + this.flag)\r\n        }\r\n    });\r\n\r\n</script>\r\n\r\n</html>\r\n```\r\n\r\n\r\n当我们点击按钮后，运行效果是：\r\n\r\n![](http://img.smyhvae.com/20180610_1528.png)\r\n\r\n可以看出：\r\n\r\n- 当执行 beforeUpdate 的时候，页面中的显示的数据还是旧的，但此时 data 数据是最新的\r\n\r\n- updated 事件执行的时候，页面和 data 数据已经保持同步了，都是最新的\r\n\r\n\r\n\r\n### 3、销毁期间的生命周期函数\r\n\r\n- beforeDestroy：实例销毁之前调用。在这一步，实例仍然完全可用。\r\n\r\n- destroyed：Vue 实例销毁后调用。调用后，Vue 实例指示的所有东西都会解绑定，所有的事件监听器会被移除，所有的子实例也会被销毁。\r\n\r\nPS：可以在beforeDestroy里**清除定时器、或清除事件绑定**。\r\n\r\n\r\n## 生命周期函数图解\r\n\r\n![](http://img.smyhvae.com/20180611_2130.png)\r\n\r\nPS：图片来自网络。\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "12-Vue基础/09-Vue中的Ajax请求.md",
    "content": "---\ntitle: 09-Vue中的Ajax请求\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n\n## vue-resource的介绍\n\n`vue-resource`是Vue高度集成的第三方包。\n\n官网链接：\n\n- 文档（http相关）：<https://github.com/pagekit/vue-resource/blob/master/docs/http.md>\n\nvue-resource 依赖于 Vue。所以，我们要按照先后顺序，导入vue.js和vue-resource.js文件。\n\n**解释**：\n\n`vue.js`文件向Windows对象暴露了`Vue`这个关键词；`vue-resource.js`向Vue身上挂载了`this.$http`这个属性。于是，我们可以直接写`this.$http.get`或者`this.$http.post`或者`this.$http.jsonp`来调用。\n\n## vue-resource 发送Ajax请求\n\n常见的数据请求类型包括：get、post、jsonp。下面我们分别讲一讲。\n\n\n### get 请求\n\n**格式举例**：\n\n```javascript\n    this.$http.get(url)\n        .then(function (result) { // 当发起get请求之后，通过 .then 来设置成功的回调函数\n            console.log(result.body); // response.body就是服务器返回的成功的数据\n            var result = result.body;\n        },\n            function (err) {\n                //err是异常数据\n            });\n```\n\n获取到的`response.body`就是要获取的数据，但直接打印出来是 object，所以要记得转成string。\n\n**举例**：获取数据\n\n现规定，获取品牌数据的 api 接口说明如下：\n\n![](http://img.smyhvae.com/20180422_2140.png)\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <style>\n    #app {\n        width: 800px;\n        margin: 20px auto;\n    }\n\n    #tb {\n        width: 800px;\n        border-collapse: collapse;\n        margin: 20px auto;\n    }\n\n    #tb th {\n        background-color: #0094ff;\n        color: white;\n        font-size: 16px;\n        padding: 5px;\n        text-align: center;\n        border: 1px solid black;\n    }\n\n    #tb td {\n        padding: 5px;\n        text-align: center;\n        border: 1px solid black;\n    }\n    </style>\n    <script src=\"../vue.js\"></script>\n    <script src=\"../vue-resource121.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <input type=\"text\" v-model=\"id\">\n        <input type=\"text\" v-model=\"pname\">\n        <button>添加数据</button>\n\n        <table id=\"tb\">\n            <tr>\n                <th>编号</th>\n                <th>名称</th>\n                <th>创建时间</th>\n                <th>操作</th>\n            </tr>\n            <tr v-for=\"item in list\">\n                <td>{{item.id}}</td>\n                <td>{{item.name}}</td>\n                <td>{{item.ctime}}</td>\n                <td>\n                <a href=\"javascript:void(0)\">删除</a>\n                </td>\n            </tr>\n        </table>\n    </div>\n</body>\n<script>\n\n new Vue({\n    el :'#app',\n    data:{\n        list:[]\n    },\n    // Vue对象实例创建成功以后就会自动调用这个方法\n    created:function(){\n        this.getlist();\n    },\n    methods:{\n        getlist:function(){\n            // 请求服务器的api获取到品牌的数据列表\n            this.$http.get('http://vueapi.ittun.com/api/getprodlist')\n            .then(function(response){\n                // 1、处理服务器异常信息提示\n                if(response.body.status != 0){\n                    alert(response.body.message);\n                    return;\n                }\n\n                // 2、处理正常的数据逻辑\n               this.list =  response.body.message;  //直接将数据放到list数组当中，页面就会自动显示\n               console.log(this.list);\n            });\n        }\n    }\n });\n</script>\n\n</html>\n\n```\n\n上方代码中，我们用到了生命周期函数`created`，意思是：程序一加载，就马上在`created`这个函数里执行`getlist()`方法。\n\n\n运行的结果如下：\n\n![](http://img.smyhvae.com/20180422_2152.png)\n\n如果我直接在浏览器中输入请求的url，获取的json数据如下：（可以看到，这种方式获取的是相同的数据）\n\n![](http://img.smyhvae.com/20180422_2150.png)\n\n\n### post请求\n\n**格式举例**：\n\n```javascript\n    // 方法：$http.post(url, 传给服务器的请求体中的数据， {emulateJSON:true})\n    // 通过 post 方法的第三个参数{ emulateJSON: true } ，来设置 提交的内容类型 为 普通表单数据格式\n    this.$http.post(url, { name: '奔驰' }, { emulateJSON: true })\n        .then(function (response) {\n            alert(response.body.message);\n        },\n            function (error) {\n\n            });\n```\n\n上方代码中，post()方法中有三个参数，其中第三个参数是固定值，照着写就可以了。\n\n\n**代码举例**：（添加数据）\n\n现规定，添加品牌数据的 api 接口说明如下：\n\n![](http://img.smyhvae.com/20180422_1720.png)\n\n\n代码如下：（在上一段代码的基础之上，添加代码）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <style>\n        #app {\n            width: 800px;\n            margin: 20px auto;\n        }\n\n        #tb {\n            width: 800px;\n            border-collapse: collapse;\n            margin: 20px auto;\n        }\n\n        #tb th {\n            background-color: #0094ff;\n            color: white;\n            font-size: 16px;\n            padding: 5px;\n            text-align: center;\n            border: 1px solid black;\n        }\n\n        #tb td {\n            padding: 5px;\n            text-align: center;\n            border: 1px solid black;\n        }\n    </style>\n    <script src=\"vue.js\"></script>\n    <script src=\"vue-resource121.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <input type=\"text\" v-model=\"pname\">\n        <button @click=\"adddata\">添加数据</button>\n\n        <table id=\"tb\">\n            <tr>\n                <th>编号</th>\n                <th>名称</th>\n                <th>创建时间</th>\n                <th>操作</th>\n            </tr>\n            <tr v-for=\"item in list\">\n                <td>{{item.id}}</td>\n                <td>{{item.name}}</td>\n                <td>{{item.ctime}}</td>\n                <td>\n                    <a href=\"javascript:void(0)\">删除</a>\n                </td>\n            </tr>\n        </table>\n    </div>\n</body>\n<script>\n\n    new Vue({\n        el: '#app',\n        data: {\n            pname: '', //这个 pname 是我在输入框里添加的数据。我们要把这个传给服务器\n            list: []\n        },\n        // Vue对象实例创建成功以后就会自动调用这个方法\n        created: function () {\n            this.getlist();\n        },\n        methods: {\n            //ajax请求：添加数据\n            adddata: function () {\n                // 1、获取用户填写的文本框的值只需要通过this.pname即可\n                // 2、调用ajax的post方法将数据上传到服务器\n                var url = 'http://vueapi.ittun.com/api/addproduct';\n                var postData = { name: this.pname };  //【重要】键`name`是json中约定好的字段。我们把这个字段传给服务器\n                var options = { emulateJSON: true };\n                this.$http.post(url, postData, options).then(function (response) {\n                    if (response.body.status != 0) {\n                        alert(response.body.message);\n                        return;\n                    }\n\n                    this.pname = '';\n\n                    // 3、添加完成后，只需要手动再调用一次getlist（将列表数据重新加载一次），即可刷新页面上的数据\n                    this.getlist();\n\n                });\n            },\n\n            //ajax请求：获取数据\n            getlist: function () {\n                this.$http.get('http://vueapi.ittun.com/api/getprodlist')\n                    .then(function (response) {\n                        // 1、处理服务器异常信息提示\n                        if (response.body.status != 0) {\n                            alert(response.body.message);\n                            return;\n                        }\n\n                        // 2、处理正常的数据逻辑\n                        this.list = response.body.message;\n                        console.log(this.list);\n                    });\n            }\n        }\n    });\n</script>\n\n</html>\n```\n\n\n**代码举例**：（删除数据）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <style>\n        #app {\n            width: 800px;\n            margin: 20px auto;\n        }\n\n        #tb {\n            width: 800px;\n            border-collapse: collapse;\n            margin: 20px auto;\n        }\n\n        #tb th {\n            background-color: #0094ff;\n            color: white;\n            font-size: 16px;\n            padding: 5px;\n            text-align: center;\n            border: 1px solid black;\n        }\n\n        #tb td {\n            padding: 5px;\n            text-align: center;\n            border: 1px solid black;\n        }\n    </style>\n    <script src=\"vue.js\"></script>\n    <script src=\"vue-resource121.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <input type=\"text\" v-model=\"pname\">\n        <button @click=\"adddata\">添加数据</button>\n\n        <table id=\"tb\">\n            <tr>\n                <th>编号</th>\n                <th>名称</th>\n                <th>创建时间</th>\n                <th>操作</th>\n            </tr>\n            <tr v-for=\"item in list\">\n                <td>{{item.id}}</td>\n                <td>{{item.name}}</td>\n                <td>{{item.ctime}}</td>\n                <td>\n                    <!-- 具体要删除哪个item，不能写死。所以要根据id来删 -->\n                    <a href=\"javascript:void(0)\" @click=\"deldata(item.id)\">删除</a>\n                </td>\n            </tr>\n        </table>\n    </div>\n</body>\n<script>\n\n    new Vue({\n        el: '#app',\n        data: {\n            pname: '', //这个 pname 是我在输入框里添加的数据。我们要把这个传给服务器\n            list: []\n        },\n        // Vue对象实例创建成功以后就会自动调用这个方法\n        created: function () {\n            this.getlist();\n        },\n        methods: {\n            //ajax请求：添加数据\n            adddata: function () {\n                // 1、获取用户填写的文本框的值只需要通过this.pname即可\n                // 2、调用ajax的post方法将数据上传到服务器\n                var url = 'http://vueapi.ittun.com/api/addproduct';\n                var postData = { name: this.pname };  //【重要】键`name`是json中约定好的字段。我们把这个字段传给服务器\n                var options = { emulateJSON: true };\n                this.$http.post(url, postData, options).then(function (response) {\n                    if (response.body.status != 0) {\n                        alert(response.body.message);\n                        return;\n                    }\n\n                    this.pname = '';\n\n                    // 3、直接将列表数据重新加载一次，即可刷新页面上的数据\n                    this.getlist();\n\n                });\n            },\n\n            //ajax请求：获取数据\n            getlist: function () {\n                this.$http.get('http://vueapi.ittun.com/api/getprodlist')\n                    .then(function (response) {\n                        // 1、处理服务器异常信息提示\n                        if (response.body.status != 0) {\n                            alert(response.body.message);\n                            return;\n                        }\n\n                        // 2、处理正常的数据逻辑\n                        this.list = response.body.message;\n                        console.log(this.list);\n                    });\n            },\n\n            // ajax请求：删除数据\n            deldata: function (id) {\n                this.$http.get('http://vueapi.ittun.com/api/delproduct/' + id)\n                    .then(function (response) {\n                        if (response.body.status != 0) {\n                            alert(response.body.message);\n                            return;\n                        }\n\n                        // 刷新列表\n                        this.getlist();\n                    });\n            }\n        }\n    });\n</script>\n\n</html>\n\n```\n\n\n### jsonp\n\n![](http://img.smyhvae.com/20180420_2250.png)\n\n\n**格式举例**：\n\n```javascript\n    // 利用vue-resource中的jsonp方法实现跨域请求数据，这里要注意的是：\n    // url后面不需要跟callback=fn这个参数了，jsonp方法会自动加上\n    this.$http.jsonp('http://vuecms.ittun.com/api/getlunbo?id=1')\n        .then(function (response) {\n            console.log(JSON.stringify(response.body));\n        }, function (err) {\n            //err是异常数据\n        });\n```\n\n请求结果：\n\n![](http://img.smyhvae.com/20180420_2256.png)\n\n\n## JSONP的实现原理\n\n由于浏览器的安全性限制，默认不允许Ajax发起跨域（协议不同、域名不同、端口号不同）的请求。浏览器认为这种访问不安全。\n\n**JSONP的实现原理**：通过动态创建script标签的形式，用script标签的src属性，代表api接口的url，因为script标签不存在跨域限制，这种数据获取方式，称作JSONP（注意：根据JSONP的实现原理，知晓，JSONP只支持Get请求）。\n\n\n 具体实现过程：\n\n\n- 先在客户端定义一个回调方法，预定义对数据的操作\n\n- 再把这个回调方法的名称，通过URL传参的形式，提交到服务器的api接口；\n\n- 服务器api接口组织好要发送给客户端的数据，再拿着客户端传递过来的回调方法名称，拼接出一个调用这个方法的字符串，发送给客户端去解析执行；\n\n- 客户端拿到服务器返回的字符串之后，当作Script脚本去解析执行，这样就能够拿到JSONP的数据了\n\n\n## axios\n\n除了 vue-resource 之外，还可以使用 `axios` 的第三方包实现实现数据的请求。\n\n\n## 通过Vue全局配置api接口的url地址\n\napi接口的url地址包括：绝对路径+相对路径。\n\n我们在做Ajax请求的时候，所填写的url建议填**相对路径**，然后把**绝对路径**放在全局的位置。\n\nVue就提供了这个功能。举例如下：\n\n```html\n\n  <script>\n    // 如果我们通过全局配置了，请求的数据接口 根域名，则 ，在每次单独发起 http 请求的时候，请求的 url 路径，应该以相对路径开头，前面不能带 /  ，否则 不会启用根路径做拼接；\n    Vue.http.options.root = 'http://smyhvae/';\n    // 全局启用 emulateJSON 选项\n    Vue.http.options.emulateJSON = true;\n\n    var vm = new Vue({\n      el: '#app',\n      data: {\n        name: '',\n        list: [ // 存放所有品牌列表的数组\n        ]\n      },\n      created() { // 当 vm 实例 的 data 和 methods 初始化完毕后，vm实例会自动执行created 这个生命周期函数\n        this.getAllList()\n      },\n      methods: {\n        getAllList() { // 获取所有的品牌列表\n          // 分析：\n          // 1. 由于已经导入了 Vue-resource这个包，所以 ，可以直接通过  this.$http 来发起数据请求\n          // 2. 根据接口API文档，知道，获取列表的时候，应该发起一个 get 请求\n          // 3. this.$http.get('url').then(function(result){})\n          // 4. 当通过 then 指定回调函数之后，在回调函数中，可以拿到数据服务器返回的 result\n          // 5. 先判断 result.status 是否等于0，如果等于0，就成功了，可以 把 result.message 赋值给 this.list ; 如果不等于0，可以弹框提醒，获取数据失败！\n\n          this.$http.get('api/getprodlist').then(result => {\n            // 注意： 通过 $http 获取到的数据，都在 result.body 中放着\n            var result = result.body\n            if (result.status === 0) {\n              // 成功了\n              this.list = result.message\n            } else {\n              // 失败了\n              alert('获取数据失败！')\n            }\n          })\n        }\n      }\n    });\n  </script>\n\n```\n\n如上方代码所示，第一步是在全局的位置写**绝对路径**：\n\n```javascript\n    Vue.http.options.root = 'http://smyhvae/';\n```\n\n第二步是在Ajax请求的url中写**相对路径**：（注意，前面不要带`/`）\n\n```javascript\nthis.$http.get('api/getprodlist')\n```\n\n\n\n"
  },
  {
    "path": "12-Vue基础/10-Vue动画.md",
    "content": "---\r\ntitle: 10-Vue动画\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 前言\r\n\r\n动画的作用：提高用户的体验，帮助用户更好的理解页面中的功能。\r\n\r\n\r\n## 使用过渡类名实现动画\r\n\r\n### 官方文档的截图\r\n\r\n过渡类名如下：\r\n\r\n![](http://img.smyhvae.com/20180616_1555.png)\r\n\r\n动画进入：\r\n\r\n- v-enter：动画进入之前的**初始**状态\r\n\r\n- v-enter-to：动画进入之后的**结束**状态\r\n\r\n- v-enter-active：动画进入的时间段\r\n\r\nPS：第一、第二个是时间点；第三个是时间段。\r\n\r\n动画离开：\r\n\r\n- v-leave：动画离开之前的**初始**状态\r\n\r\n- v-leave-to：动画离开之后的**结束**状态\r\n\r\n- v-leave-active：动画离开的时间段\r\n\r\nPS：第一、第二个是时间点；第三个是时间段。\r\n\r\n\r\n### 使用举例（通过Vue的过渡类名来实现）\r\n\r\n`v-enter-to`和`v-leave`的状态是一样的。而且一般来说，`v-enter`和`v-leave-to`的状态也是一致的。所以，我们可以把这四个状态写成两组。\r\n\r\n现在我们来做个例子：点击按钮时，让div显示/隐藏。\r\n\r\n**1、引入**：\r\n\r\n如果我们不使用动画，应该是这样做：\r\n\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <input type=\"button\" value=\"toggle\" @click=\"flag=!flag\">\r\n        <!-- 需求： 点击按钮，让 h3 显示，再点击，让 h3 隐藏 -->\r\n        <h3 v-if=\"flag\">这是一个H3</h3>\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                flag: false\r\n            },\r\n            methods: {}\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n\r\n**2、使用动画**：（通过Vue的过渡类名来实现）\r\n\r\n现在，我们加**淡入淡出**的动画，让div显示和隐藏。代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <!-- 2. 自定义两组样式，来控制 transition 内部的元素实现动画 -->\r\n    <style>\r\n        /* v-enter 【这是一个时间点】 是进入之前，元素的起始状态，此时还没有开始进入 */\r\n\r\n        /* v-leave-to 【这是一个时间点】 是动画离开之后，离开的终止状态，此时，元素 动画已经结束了 */\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n        }\r\n\r\n        /* v-enter-active 【入场动画的时间段】 */\r\n\r\n        /* v-leave-active 【离场动画的时间段】 */\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 1s ease;   /*期间，设置过渡的属性：all表示所有的属性、时间为1秒、过渡的状态*/\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <input type=\"button\" value=\"toggle\" @click=\"flag=!flag\">\r\n        <!-- 需求： 点击按钮，让 h3 显示，再点击，让 h3 隐藏 -->\r\n        <!-- 1. 使用 transition 元素，把 需要被动画控制的元素，包裹起来 -->\r\n        <!-- transition 元素，是 Vue 官方提供的 -->\r\n        <transition>\r\n            <h3 v-if=\"flag\">这是一个H3</h3>\r\n        </transition>\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                flag: false\r\n            },\r\n            methods: {}\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n\r\n上方代码中，我们使用vue提供的`<transition>`标签把需要被动画控制的元素，包裹起来；然后使用`.v-enter`、`.v-leave-to`等进行动画的定义。\r\n\r\n运行效果如下：\r\n\r\n![](http://img.smyhvae.com/20180615_2200.gif)\r\n\r\n\r\n**3、再加一个 transform 属性进行位移**：\r\n\r\n我们在上方代码的基础之上，加一个 transform 属性，让动画有一个位移的效果。完整代码如下：\r\n\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <!-- 2. 自定义两组样式，来控制 transition 内部的元素实现动画 -->\r\n    <style>\r\n        /* v-enter 【这是一个时间点】 是进入之前，元素的起始状态，此时还没有开始进入 */\r\n\r\n        /* v-leave-to 【这是一个时间点】 是动画离开之后，离开的终止状态，此时，元素 动画已经结束了 */\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n            /* 在动画中加入位移 */\r\n            transform: translateX(80px); /* smyhvae提示：v-enter表示，一开始让DOM元素处于靠右80px的位置 */\r\n        }\r\n\r\n        /* v-enter-active 【入场动画的时间段】 */\r\n\r\n        /* v-leave-active 【离场动画的时间段】 */\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 1s ease;   /*期间，设置过渡的属性：all表示所有的属性、时间为1秒、过渡的状态*/\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <input type=\"button\" value=\"toggle\" @click=\"flag=!flag\">\r\n        <!-- 需求： 点击按钮，让 h3 显示，再点击，让 h3 隐藏 -->\r\n        <!-- 1. 使用 transition 元素，把 需要被动画控制的元素，包裹起来 -->\r\n        <!-- transition 元素，是 Vue 官方提供的 -->\r\n        <transition>\r\n            <h3 v-if=\"flag\">这是一个H3</h3>\r\n        </transition>\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                flag: false\r\n            },\r\n            methods: {}\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n\r\n效果如下：\r\n\r\n![](http://img.smyhvae.com/20180615_2205.gif)\r\n\r\n### 修改过渡类名的前缀\r\n\r\n在上一小段中，`.v-enter`、`.v-leave-to`这些过渡类名都是以`v-`开头的。这样做，会有一个局限性：假设有两个DOM元素都用`<transition>`进行了包裹，那这两个DOM元素就都具备了`v-`中所定义的动画。\r\n\r\n那**如果我们想把两个DOM元素的动画进行分开定义**，该怎么做呢？这里，我们可以通过修改过渡类名的前缀来做。比如：\r\n\r\n第一步：（自定义别名）\r\n\r\n```html\r\n    <transition name=\"my\">\r\n      <h6 v-if=\"flag2\">这是一个H6</h6>\r\n    </transition>\r\n```\r\n\r\n上方代码中，我们加了`name=\"my\"`。\r\n\r\n第二步：（我们就可以使用 `my-enter`、`.my-leave-to`这些类名了）\r\n\r\n```css\r\n    .my-enter,\r\n    .my-leave-to {\r\n      opacity: 0;\r\n      transform: translateY(70px);\r\n    }\r\n\r\n```\r\n\r\n\r\n完整代码举例如下；\r\n\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"./lib/vue-2.4.0.js\"></script>\r\n    <style>\r\n        /* 自定义第一组样式，来控制 transition 内部的元素实现动画 */\r\n\r\n        /* v-enter 【这是一个时间点】 是进入之前，元素的起始状态，此时还没有开始进入 */\r\n\r\n        /* v-leave-to 【这是一个时间点】 是动画离开之后，离开的终止状态，此时，元素 动画已经结束了 */\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n            transform: translateX(150px);\r\n        }\r\n\r\n        /* v-enter-active 【入场动画的时间段】 */\r\n\r\n        /* v-leave-active 【离场动画的时间段】 */\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 0.8s ease;\r\n        }\r\n\r\n\r\n        /* 自定义第二组样式，来控制 transition 内部的元素实现动画。这次，我们通过自己起的别名`name`来作为指令 */\r\n\r\n        .my-enter,\r\n        .my-leave-to {\r\n            opacity: 0;\r\n            transform: translateY(70px);\r\n        }\r\n\r\n        .my-enter-active,\r\n        .my-leave-active {\r\n            transition: all 1s ease;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <!-- 第一组 -->\r\n        <input type=\"button\" value=\"toggle\" @click=\"flag=!flag\">\r\n        <!-- 使用 transition 元素，把 需要被动画控制的元素 h3，包裹起来 -->\r\n        <!-- transition 元素，是 Vue 官方提供的 -->\r\n        <transition>\r\n            <h3 v-if=\"flag\">这是一个H3</h3>\r\n        </transition>\r\n\r\n        <hr>\r\n\r\n        <!-- 第二组 -->\r\n        <input type=\"button\" value=\"toggle2\" @click=\"flag2=!flag2\">\r\n        <!-- 使用 transition 元素，把 需要被动画控制的元素 h6，包裹起来 -->\r\n        <!-- transition 元素，是 Vue 官方提供的 -->\r\n        <!-- 【重点】在这里，我们给这个transition定义一个别名，叫`name`，然后，我们就可以通过 `.my-enter`等 来定义动画的样式【重要】 -->\r\n        <transition name=\"my\">\r\n            <h6 v-if=\"flag2\">这是一个H6</h6>\r\n        </transition>\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                flag: false,\r\n                flag2: false\r\n            },\r\n            methods: {}\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n运行效果如下：\r\n\r\n![](http://img.smyhvae.com/20180616_1513.gif)\r\n\r\n## 使用第三方animate.css类库实现动画\r\n\r\nanimate.css网址：\r\n\r\n- 官方网站：<https://daneden.github.io/animate.css/>\r\n\r\n\r\n**代码举例**：\r\n\r\n下面的代码中，我们使用animate.css提供的`bounceIn`、`bounceOut`这两个类来做入场、离场的动画。代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <link rel=\"stylesheet\" href=\"animate3.6.0.css\">\r\n    <!-- 入场 bounceIn    离场 bounceOut -->\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <input type=\"button\" value=\"toggle\" @click=\"flag=!flag\">\r\n        <!-- 需求： 点击按钮，让 h3 显示，再点击，让 h3 隐藏 -->\r\n        <transition enter-active-class=\"animated bounceIn\" leave-active-class=\"animated bounceOut\">\r\n            <h3 v-if=\"flag\">这是一个H3</h3>\r\n        </transition>\r\n\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                flag: false\r\n            },\r\n            methods: {}\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n\r\n\r\n上面的代码中，注意：\r\n\r\n注意1：`enter-active-class`和`leave-active-class`这两个类名是Vue动画里的关键词，不能写成自己**随意起**的类名。\r\n\r\n注意2：`bounceIn`、`bounceOut`这两个类不能直接使用，要在前面加上`animated`这个类；否则动画是不会生效的。当然，上面的代码中，我们还可以把`class = animated`这个代码移到`<h3>`标签里，效果是一样的，如下：\r\n\r\n```html\r\n        <!-- 需求： 点击按钮，让 h3 显示，再点击，让 h3 隐藏 -->\r\n        <transition enter-active-class=\"bounceIn\" leave-active-class=\"bounceOut\">\r\n            <h3 v-if=\"flag\" class=\"animated\">这是一个H3</h3>\r\n        </transition>\r\n\r\n```\r\n\r\n\r\n运行效果如下：\r\n\r\n![](http://img.smyhvae.com/20180616_1538.gif)\r\n\r\n**改进1**：（统一设置入场、出场动画的持续时间）\r\n\r\n我们把上面的代码改进一下，如果我们想给入场、出场动画设置持续的时间，可以使用`:duration`来做。如下：\r\n\r\n\r\n```html\r\n        <!-- 需求： 点击按钮，让 h3 显示，再点击，让 h3 隐藏 -->\r\n        <!-- 使用 :duration=\"毫秒值\" 来统一设置 入场 和 离场 时候的动画时长 -->\r\n        <transition enter-active-class=\"animated bounceIn\" leave-active-class=\"animated bounceOut\" :duration=\"500\">\r\n            <h3 v-if=\"flag\">这是一个H3</h3>\r\n        </transition>\r\n```\r\n\r\n\r\n**改进2**：（分别设置入场、出场动画的持续时间）\r\n\r\n```html\r\n        <!-- 需求： 点击按钮，让 h3 显示，再点击，让 h3 隐藏 -->\r\n        <!-- 使用  :duration=\"{ enter: 1000, leave: 300 }\"  来分别设置 入场的时长 和 离场的时长  -->\r\n        <transition enter-active-class=\"animated bounceIn\" leave-active-class=\"animated bounceOut\" :duration=\"{ enter: 1000, leave: 300 }\">\r\n            <h3 v-if=\"flag\">这是一个H3</h3>\r\n        </transition>\r\n```\r\n\r\n\r\n## 钩子函数实现半场动画\r\n\r\n只有出场动画、没有离场动画，这种就是属于半场动画。比如你把一件商品加入收藏，会出现一个动画；当再次点击收藏按钮的时候却看不到动画效果，这就说明，只有前一半才有动画。\r\n\r\n\r\n半场动画，可以使用钩子函数来实现。\r\n\r\n\r\n### 动画的钩子函数介绍\r\n\r\n可以在属性中声明 JavaScript 钩子函数：（这八个钩子函数可以理解成是动画的生命周期）\r\n\r\n```html\r\n<transition\r\n  v-on:before-enter=\"beforeEnter\"\r\n  v-on:enter=\"enter\"\r\n  v-on:after-enter=\"afterEnter\"\r\n  v-on:enter-cancelled=\"enterCancelled\"\r\n\r\n  v-on:before-leave=\"beforeLeave\"\r\n  v-on:leave=\"leave\"\r\n  v-on:after-leave=\"afterLeave\"\r\n  v-on:leave-cancelled=\"leaveCancelled\"\r\n>\r\n  <!-- DOM元素 -->\r\n</transition>\r\n```\r\n\r\n\r\n我们可以这样理解：上面这八个钩子函数（四个入场、四个离场），对应了八个事件，我们要紧接着在methods中定义八个函数。\r\n\r\n如果要定义半场动画，做法是：直接在methods中写入场动画的函数，不写离场动画的函数即可。\r\n\r\n### 举例：使用钩子函数模拟小球半场动画\r\n\r\n现在要实现的例子是：点击按钮后，让小球进行移动。完整代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <style>\r\n        .ball {\r\n            width: 15px;\r\n            height: 15px;\r\n            border-radius: 50%;\r\n            background-color: red;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <input type=\"button\" value=\"加入购物车\" @click=\"flag=!flag\">\r\n        <!-- 1. 使用 transition 元素把 小球包裹起来 -->\r\n        <transition @before-enter=\"beforeEnter\" @enter=\"enter\" @after-enter=\"afterEnter\">\r\n            <div class=\"ball\" v-show=\"flag\"></div>\r\n        </transition>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                flag: false\r\n            },\r\n            methods: {\r\n                // 注意： 动画钩子函数的第一个参数：el，表示 要执行动画的那个DOM元素，是个原生的 JS DOM对象\r\n                // 我们可以认为 ， el 是通过 document.getElementById('') 方式获取到的原生JS DOM对象\r\n                beforeEnter(el) {\r\n                    // beforeEnter 表示动画入场之前，此时，动画尚未开始，可以 在 beforeEnter 中，设置元素开始动画之前的起始样式\r\n                    // 设置小球开始动画之前的 起始位置\r\n                    el.style.transform = \"translate(0, 0)\"   // smyhvae提示：一开始的时候，让小球处于（0，0）的位置\r\n                },\r\n                enter(el, done) {\r\n                    // 【注意1】el.offsetWidth 这句话，没有实际的作用，但是，如果不写，出不来动画效果。可以认为 el.offsetWidth 会强制动画刷新\r\n                    el.offsetWidth\r\n                    // enter 表示动画 开始之后的样式，这里，可以设置小球完成动画之后的，结束状态\r\n                    el.style.transform = \"translate(150px, 300px)\" // smyhvae 提示：让小球从（0，0）移动到 (150px, 300px)\r\n                    el.style.transition = 'all 1s ease'\r\n\r\n                    // 【注意2】这里的 done， 起始就是 afterEnter 这个函数，也就是说：done 是 afterEnter 函数的引用\r\n                    done()\r\n                },\r\n                afterEnter(el) {\r\n                    // 动画完成之后，会调用 afterEnter\r\n                    // console.log('ok')\r\n                    // 动画结束后，让小球消失（直接让 flag 取反即可）\r\n                    this.flag = !this.flag  // 因为最开始的时候，小球就是处于消失的状态，这行代码可以让小球的动画重新开始\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n\r\n运行效果如下：（我们可以用这种动画效果，做类似于“加入购物车”的动画效果）\r\n\r\n![](http://img.smyhvae.com/20180616_1618.gif)\r\n\r\n上面的代码中，有两个地方要注意：\r\n\r\n\r\n**注意1**：\r\n\r\n`el.offsetWidth`这行代码不能少。虽然这行代码没有实际的意义，但是少了之后，动画效果出不来：\r\n\r\n![](http://img.smyhvae.com/20180616_1620.gif)\r\n\r\n当然，我们也可以把这行代码换成`el.offsetHeight`、`el.offsetLeft`、`el.offsetTop`之类的，只要包含了offset就行。\r\n\r\n\r\n**注意2**：\r\n\r\n`enter()`函数里，函数的第二个参数要加上`done`，函数体的最后一行要写`done()`，表示**立即执行**后面的`afterEnter()`函数；如果没有这个`done`，则会**延迟执行**后面的`afterEnter()`函数：\r\n\r\n![](http://img.smyhvae.com/20180616_2145.gif)\r\n\r\nVue官方文档的解释是这样：\r\n\r\n\r\n > 当只用 JavaScript 过渡的时候，在`enter`和`leave`中必须使用`done`进行回调。否则，它们将被同步调用，过渡会立即完成。\r\n\r\n\r\n\r\n\r\n## 使用transition-group元素实现列表动画\r\n\r\n现在的场景是：在一个`<ul>`列表中，如果我想给**指定的某个**`li`添加动画效果，该怎么做呢？（需要声明的是，这些`li`是用v-for循环进行遍历的）\r\n\r\n如果我们用`<transition>`把`li`包裹起来，就会让所有的`li`都具备了动画，这显然是不可取的。\r\n\r\n那该怎么做呢？这里我们就可以用`transition-group`进行包裹。\r\n\r\n**代码举例1**：点击添加按钮后，给新增的 item 加个动画\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <style>\r\n        li {\r\n            border: 1px dashed #999;\r\n            margin: 5px;\r\n            line-height: 35px;\r\n            padding-left: 5px;\r\n            font-size: 12px;\r\n            width: 100%;\r\n        }\r\n\r\n        li:hover {\r\n            background-color: hotpink;\r\n            transition: all 0.8s ease;  /*鼠标悬停时，出现背景色。让这个背景色的出现，也加一个淡入的动画*/\r\n        }\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n            transform: translateY(80px);\r\n        }\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 0.6s ease;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n        <div>\r\n            <label>\r\n                Id:\r\n                <input type=\"text\" v-model=\"id\">\r\n            </label>\r\n\r\n            <label>\r\n                Name:\r\n                <input type=\"text\" v-model=\"name\">\r\n            </label>\r\n\r\n            <input type=\"button\" value=\"添加\" @click=\"add\">\r\n        </div>\r\n\r\n        <!-- <ul> -->\r\n        <!-- 在实现列表过渡的时候，如果需要过渡的元素，是通过 v-for 循环渲染出来的，不能使用 transition 包裹，需要使用 transitionGroup -->\r\n        <!-- 如果要为 v-for 循环创建的元素设置动画，必须为每一个 元素 设置 :key 属性 -->\r\n        <transition-group>\r\n            <li v-for=\"(item, i) in list\" :key=\"item.id\">\r\n                {{item.id}} --- {{item.name}}\r\n            </li>\r\n        </transition-group>\r\n        <!-- </ul> -->\r\n\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                id: '',\r\n                name: '',\r\n                list: [\r\n                    { id: 1, name: '赵高' },\r\n                    { id: 2, name: '秦桧' },\r\n                    { id: 3, name: '严嵩' },\r\n                    { id: 4, name: '魏忠贤' }\r\n                ]\r\n            },\r\n            methods: {\r\n                add() {\r\n                    this.list.push({ id: this.id, name: this.name })\r\n                    this.id = this.name = ''\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n运行效果如下：\r\n\r\n![](http://img.smyhvae.com/20180616_2240.gif)\r\n\r\n\r\n**改进1**：添加删除item的功能\r\n\r\n基于上面的代码，我们来添加**删除item**的功能，代码本应该是这样写：\r\n\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <style>\r\n        li {\r\n            border: 1px dashed #999;\r\n            margin: 5px;\r\n            line-height: 35px;\r\n            padding-left: 5px;\r\n            font-size: 12px;\r\n            width: 100%;\r\n        }\r\n\r\n        li:hover {\r\n            background-color: hotpink;\r\n            transition: all 0.8s ease;\r\n            /*鼠标悬停时，出现背景色。让这个背景色的出现，也加一个淡入的动画*/\r\n        }\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n            transform: translateY(80px);\r\n        }\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 0.6s ease;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n        <div>\r\n            <label>\r\n                Id:\r\n                <input type=\"text\" v-model=\"id\">\r\n            </label>\r\n\r\n            <label>\r\n                Name:\r\n                <input type=\"text\" v-model=\"name\">\r\n            </label>\r\n\r\n            <input type=\"button\" value=\"添加\" @click=\"add\">\r\n        </div>\r\n\r\n        <!-- <ul> -->\r\n        <!-- 在实现列表过渡的时候，如果需要过渡的元素，是通过 v-for 循环渲染出来的，不能使用 transition 包裹，需要使用 transitionGroup -->\r\n        <!-- 如果要为 v-for 循环创建的元素设置动画，必须为每一个 元素 设置 :key 属性 -->\r\n        <transition-group>\r\n            <li v-for=\"(item, i) in list\" :key=\"item.id\" @click=\"del(i)\">\r\n                {{item.id}} --- {{item.name}}\r\n            </li>\r\n        </transition-group>\r\n        <!-- </ul> -->\r\n\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                id: '',\r\n                name: '',\r\n                list: [\r\n                    { id: 1, name: '赵高' },\r\n                    { id: 2, name: '秦桧' },\r\n                    { id: 3, name: '严嵩' },\r\n                    { id: 4, name: '魏忠贤' }\r\n                ]\r\n            },\r\n            methods: {\r\n                add() {\r\n                    this.list.push({ id: this.id, name: this.name })\r\n                    this.id = this.name = ''\r\n                },\r\n                del(i) {\r\n                    this.list.splice(i, 1);\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n运行效果如下：\r\n\r\n![](http://img.smyhvae.com/20180617_1555.gif)\r\n\r\n**改进2:**：\r\n\r\n上图中，我们发现，当我删除第2个item时，**第3、第4个item在往上移动的过程比会较突兀**。为了改进这个地方，我们可以给`.v-move`、`.v-leave-active`加一些动画属性。最终，完整版代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <style>\r\n        li {\r\n            border: 1px dashed #999;\r\n            margin: 5px;\r\n            line-height: 35px;\r\n            padding-left: 5px;\r\n            font-size: 12px;\r\n            width: 100%;\r\n        }\r\n\r\n        li:hover {\r\n            background-color: hotpink;\r\n            transition: all 0.8s ease;\r\n            /*鼠标悬停时，出现背景色。让这个背景色的出现，也加一个淡入的动画*/\r\n        }\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n            transform: translateY(80px);\r\n        }\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 0.6s ease;\r\n        }\r\n\r\n        /* 下面的 .v-move 和 .v-leave-active 配合使用，能够实现列表后续的元素，渐渐地漂上来的效果 */\r\n        .v-move {\r\n            transition: all 0.6s ease;\r\n        }\r\n\r\n        .v-leave-active {\r\n            position: absolute;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n        <div>\r\n            <label>\r\n                Id:\r\n                <input type=\"text\" v-model=\"id\">\r\n            </label>\r\n\r\n            <label>\r\n                Name:\r\n                <input type=\"text\" v-model=\"name\">\r\n            </label>\r\n\r\n            <input type=\"button\" value=\"添加\" @click=\"add\">\r\n        </div>\r\n\r\n        <!-- <ul> -->\r\n        <!-- 在实现列表过渡的时候，如果需要过渡的元素，是通过 v-for 循环渲染出来的，不能使用 transition 包裹，需要使用 transitionGroup -->\r\n        <!-- 如果要为 v-for 循环创建的元素设置动画，必须为每一个 元素 设置 :key 属性 -->\r\n        <transition-group>\r\n            <li v-for=\"(item, i) in list\" :key=\"item.id\" @click=\"del(i)\">\r\n                {{item.id}} --- {{item.name}}\r\n            </li>\r\n        </transition-group>\r\n        <!-- </ul> -->\r\n\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                id: '',\r\n                name: '',\r\n                list: [\r\n                    { id: 1, name: '赵高' },\r\n                    { id: 2, name: '秦桧' },\r\n                    { id: 3, name: '严嵩' },\r\n                    { id: 4, name: '魏忠贤' }\r\n                ]\r\n            },\r\n            methods: {\r\n                add() {\r\n                    this.list.push({ id: this.id, name: this.name })\r\n                    this.id = this.name = ''\r\n                },\r\n                del(i) {\r\n                    this.list.splice(i, 1);\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n运行效果如下：\r\n\r\n![](http://img.smyhvae.com/20180617_1556.gif)\r\n\r\n### transition-group中appear和tag属性的作用\r\n\r\n我们可以在上面的代码基础之上，给transition-group加上`appear`属性，这样的话，可以让transition-group包裹的所有DOM元素在刷新时，有**淡入效果**。\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <style>\r\n        li {\r\n            border: 1px dashed #999;\r\n            margin: 5px;\r\n            line-height: 35px;\r\n            padding-left: 5px;\r\n            font-size: 12px;\r\n            width: 100%;\r\n        }\r\n\r\n        li:hover {\r\n            background-color: hotpink;\r\n            transition: all 0.8s ease;\r\n        }\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n            transform: translateY(80px);\r\n        }\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 0.6s ease;\r\n        }\r\n\r\n        /* 下面的 .v-move 和 .v-leave-active 配合使用，能够实现列表后续的元素，渐渐地漂上来的效果 */\r\n\r\n        .v-move {\r\n            transition: all 0.6s ease;\r\n        }\r\n\r\n        .v-leave-active {\r\n            position: absolute;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n        <div>\r\n            <label>\r\n                Id:\r\n                <input type=\"text\" v-model=\"id\">\r\n            </label>\r\n\r\n            <label>\r\n                Name:\r\n                <input type=\"text\" v-model=\"name\">\r\n            </label>\r\n\r\n            <input type=\"button\" value=\"添加\" @click=\"add\">\r\n        </div>\r\n\r\n        <ul>\r\n            <!-- 在实现列表过渡的时候，如果需要过渡的元素，是通过 v-for 循环渲染出来的，不能使用 transition 包裹，需要使用 transitionGroup -->\r\n            <!-- 如果要为 v-for 循环创建的元素设置动画，必须为每一个 元素 设置 :key 属性 -->\r\n            <!-- 给 ransition-group 添加 appear 属性，实现页面刚展示出来时候，入场时候的效果 -->\r\n            <!-- 通过 为 transition-group 元素，设置 tag 属性，指定 transition-group 渲染为指定的元素，如果不指定 tag 属性，默认，渲染为 span 标签 -->\r\n            <transition-group appear>\r\n                <li v-for=\"(item, i) in list\" :key=\"item.id\" @click=\"del(i)\">\r\n                    {{item.id}} --- {{item.name}}\r\n                </li>\r\n            </transition-group>\r\n        </ul>\r\n\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                id: '',\r\n                name: '',\r\n                list: [\r\n                    { id: 1, name: '赵高' },\r\n                    { id: 2, name: '秦桧' },\r\n                    { id: 3, name: '严嵩' },\r\n                    { id: 4, name: '魏忠贤' }\r\n                ]\r\n            },\r\n            methods: {\r\n                add() {\r\n                    this.list.push({ id: this.id, name: this.name })\r\n                    this.id = this.name = ''\r\n                },\r\n                del(i) {\r\n                    this.list.splice(i, 1)\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n![](http://img.smyhvae.com/20180617_1600.gif)\r\n\r\n**改进**：`transition-group`的`tag`属性\r\n\r\n上面的代码中，我们审查一下代码元素会发现，用`transition-group`包裹的元素，会被默认套上一层`<span>`：\r\n\r\n![](http://img.smyhvae.com/20180617_1620.png)\r\n\r\n这个`<span>`虽然没有太大副作用，但是不符合代码规范。为了解决这个问题，我们可以通过`tag`属性给`transition-group`包谷的元素套上一层`<ul>`，然后把现有的`<ul>`注释掉，就可以了。最终代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <style>\r\n        li {\r\n            border: 1px dashed #999;\r\n            margin: 5px;\r\n            line-height: 35px;\r\n            padding-left: 5px;\r\n            font-size: 12px;\r\n            width: 100%;\r\n        }\r\n\r\n        li:hover {\r\n            background-color: hotpink;\r\n            transition: all 0.8s ease;\r\n        }\r\n\r\n\r\n\r\n        .v-enter,\r\n        .v-leave-to {\r\n            opacity: 0;\r\n            transform: translateY(80px);\r\n        }\r\n\r\n        .v-enter-active,\r\n        .v-leave-active {\r\n            transition: all 0.6s ease;\r\n        }\r\n\r\n        /* 下面的 .v-move 和 .v-leave-active 配合使用，能够实现列表后续的元素，渐渐地漂上来的效果 */\r\n\r\n        .v-move {\r\n            transition: all 0.6s ease;\r\n        }\r\n\r\n        .v-leave-active {\r\n            position: absolute;\r\n        }\r\n    </style>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n        <div>\r\n            <label>\r\n                Id:\r\n                <input type=\"text\" v-model=\"id\">\r\n            </label>\r\n\r\n            <label>\r\n                Name:\r\n                <input type=\"text\" v-model=\"name\">\r\n            </label>\r\n\r\n            <input type=\"button\" value=\"添加\" @click=\"add\">\r\n        </div>\r\n\r\n        <!-- <ul> -->\r\n        <!-- 在实现列表过渡的时候，如果需要过渡的元素，是通过 v-for 循环渲染出来的，不能使用 transition 包裹，需要使用 transitionGroup -->\r\n        <!-- 如果要为 v-for 循环创建的元素设置动画，必须为每一个 元素 设置 :key 属性 -->\r\n        <!-- 给 ransition-group 添加 appear 属性，实现页面刚展示出来时候，入场时候的效果 -->\r\n        <!-- 通过 为 transition-group 元素，设置 tag 属性，指定 transition-group 渲染为指定的元素，如果不指定 tag 属性，默认，渲染为 span 标签 -->\r\n        <transition-group appear tag=\"ul\">\r\n            <li v-for=\"(item, i) in list\" :key=\"item.id\" @click=\"del(i)\">\r\n                {{item.id}} --- {{item.name}}\r\n            </li>\r\n        </transition-group>\r\n        <!-- </ul> -->\r\n\r\n    </div>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                id: '',\r\n                name: '',\r\n                list: [\r\n                    { id: 1, name: '赵高' },\r\n                    { id: 2, name: '秦桧' },\r\n                    { id: 3, name: '严嵩' },\r\n                    { id: 4, name: '魏忠贤' }\r\n                ]\r\n            },\r\n            methods: {\r\n                add() {\r\n                    this.list.push({ id: this.id, name: this.name })\r\n                    this.id = this.name = ''\r\n                },\r\n                del(i) {\r\n                    this.list.splice(i, 1)\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n这样的话，审查元素的效果如下：\r\n\r\n![](http://img.smyhvae.com/20180617_1621.png)\r\n\r\n"
  },
  {
    "path": "12-Vue基础/11-Vue组件的定义和注册.md",
    "content": "---\ntitle: 11-Vue组件的定义和注册\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n## 前言\n\n### 什么是组件\n\n**组件**： 组件的出现，就是为了拆分Vue实例的代码量的，能够让我们以不同的组件，来划分不同的功能模块，将来我们需要什么样的功能，就可以去调用对应的组件即可。\n\n\n### 模块化和组件化的区别\n\n- 模块化：是从代码逻辑的角度进行划分的；方便代码分层开发，保证每个功能模块的职能单一\n\n- 组件化：是从UI界面的角度进行划分的；前端的组件化，方便UI组件的重用\n\n\n## 全局组件的定义和注册\n\n组件`Component`是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素，封装可重用的代码。\n\n全局组件的定义和注册有三种方式，我们接下来讲一讲。\n\n### 写法一\n\n写法一：使用Vue.extend方法定义组件，使用 Vue.component方法注册组件。\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 如果要使用组件，直接把组件的名称，以 HTML 标签的形式，引入到页面中，即可 -->\n        <account> </account>\n    </div>\n\n    <script>\n        //第一步：使用 Vue.extend 定义组件\n        var myAccount = Vue.extend({\n            template: '<div><h2>登录页面</h2> <h3>注册页面</h3></div>' // 通过 template 属性，指定了组件要展示的HTML结构。template 是 Vue 中的关键字，不能改。\n        });\n        //第二步：使用 Vue.component 注册组件\n        // Vue.component('组件的名称', 创建出来的组件模板对象)\n        Vue.component('account', myAccount); //第一个参数是组件的名称（标签名），第二个参数是模板对象\n\n        new Vue({\n            el: '#app'\n        });\n    </script>\n</body>\n\n</html>\n```\n\n上方代码中，在注册组件时，第一个参数是标签名，第二个参数是组件的定义。\n\n运行结果如下：\n\n![](http://img.smyhvae.com/20180422_2230.png)\n\n代码截图如下：\n\n![](http://img.smyhvae.com/20180422_2223.png)\n\n上图中，注意两点：\n\n**注意1**、红框部分，要保证二者的名字是一致的。如果在注册时，组件的名称是**驼峰命名**，比如：\n\n```javascript\nVue.component('myComponent', myAccount); //第一个参数是组件的名称（标签名），第二个参数是模板对象\n```\n\n那么，在标签中使用组件时，需要把大写的驼峰改为小写的字母，同时两个单词之间使用`-`进行连接：\n\n```html\n<my-component> </my-component>\n\n```\n\n所以，为了避免名字不一致的问题，我们注册组件时，组件的名称可以直接写成`my-component`。比如：（避免驼峰不一致的建议写法）\n\n```javascript\n    Vue.component('my-component', myAccount);\n```\n\n\n\n**注意2**、绿框部分，一定要用一个大的根元素（例如`<div>`）包裹起来。如果我写成下面这样，就没有预期的效果：\n\n```\n            template: '<h2>登录页面</h2> <h3>注册页面</h3>'\n```\n\n结果如下：（并非预期的效果）\n\n![](http://img.smyhvae.com/20180422_2232.png)\n\n### 写法二\n\n写法二：Vue.component方法定义、注册组件（一步到位）。\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <account> </account>\n    </div>\n\n    <script>\n\n        //定义、注册组件：第一个参数是组件的名称（标签名），第二个参数是组件的定义\n        Vue.component('account', {\n            template: '<div><h2>登录页面</h2> <h3>注册页面</h3></div>'   // template 是 Vue 中的关键字，不能改。\n        });\n\n        new Vue({\n            el: '#app'\n        });\n    </script>\n</body>\n\n</html>\n```\n\n代码截图如下：\n\n![](http://img.smyhvae.com/20180422_2251.png)\n\n上图中，同样注意两点：\n\n1、红框部分，要保证二者的名字是一致的。\n\n2、绿框部分，一定要用一个大的根元素（例如`<div>`）包裹起来。如果我写成下面这样，就没有预期的效果：\n\n```\n            template: '<h2>登录页面</h2> <h3>注册页面</h3>'\n```\n\n结果如下：（并非预期的效果）\n\n![](http://img.smyhvae.com/20180422_2232.png)\n\n\n### 写法三【荐】\n\n> 上面的写法一、写法二并不是很智能，因为在定义模板的时候，没有智能提示和高亮，容易出错。我们不妨来看看写法三。\n\n写法三：将组件内容定义到template标签中去。\n\n代码如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <!-- 定义模板 -->\n    <template id=\"myAccount\">\n        <div>\n            <h2>登录页面</h2>\n            <h3>注册页面</h3>\n        </div>\n    </template>\n\n    <div id=\"app\">\n        <!-- 使用组件 -->\n        <account> </account>\n    </div>\n\n    <script>\n\n        //定义、注册组件\n        Vue.component('account', {\n            template: '#myAccount'    // template 是 Vue 中的关键字，不能改。\n        });\n\n        new Vue({\n            el: '#app'\n        });\n    </script>\n</body>\n\n</html>\n```\n\n代码截图如下：\n\n![](http://img.smyhvae.com/20180422_2256.png)\n\n写法三其实和方法二差不多，无非是把绿框部分的内容，单独放在了`<template>`标签中而已，这样有利于 html 标签的书写。\n\n\n## 使用components定义私有组件\n\n我们在上一段中定义的是**全局组件**，这样做的时候，多个Vue实例都可以使用这个组件。\n\n我们还可以在一个Vue实例的内部定义**私有组件**，这样做的时候，只有当前这个Vue实例才可以使用这个组件。\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"app\">\n        <!-- 使用Vue实例内部的私有组件 -->\n        <my-login></my-login>\n    </div>\n\n    <script>\n\n        new Vue({\n            el: '#app',\n            data: {},\n            components: { // 定义、注册Vue实例内部的私有组件\n                myLogin: {\n                    template: '<h3>这是私有的login组件</h3>'\n                }\n            }\n\n\n        });\n    </script>\n</body>\n\n</html>\n```\n\n运行效果：\n\n![](http://img.smyhvae.com/20180617_1809.png)\n\n【荐】当然，我们还可以把**模板的定义**存放在`<template>`标签中，这样的话，模板里的html标签就可以出现智能提示和高亮，避免出错。如下：\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n\n    <!-- 定义模板 -->\n    <template id=\"loginTmp\">\n        <h3>这是私有的login组件</h3>\n    </template>\n\n    <div id=\"app\">\n        <!-- 调用Vue实例内部的私有组件 -->\n        <my-login></my-login>\n    </div>\n\n    <script>\n        new Vue({\n            el: '#app',\n            data: {},\n            components: { // 定义、注册Vue实例内部的私有组件\n                myLogin: {\n                    template: '#loginTmp'\n                }\n            }\n        });\n    </script>\n</body>\n\n</html>\n```\n\n运行效果不变。\n\n上方代码中，如果在注册私有组件时，组件的名称是**驼峰命名**，比如：\n\n```javascript\n            components: { // 定义、注册Vue实例内部的私有组件\n                myLogin: {\n                    template: '#loginTmp'\n                }\n            }\n```\n\n那么，在标签中使用组件时，需要把大写的驼峰改为小写的字母，同时两个单词之间使用`-`进行连接：\n\n```html\n        <my-login></my-login>\n```\n\n所以，为了避免名字不一致的问题，我们注册组件时，组件的名称可以直接写成`my-login`。比如：（避免驼峰不一致的建议写法）\n\n\n```javascript\n            components: { // 定义、注册Vue实例内部的私有组件\n                `my-login`: {\n                    template: '#loginTmp'\n                }\n            }\n```\n\n\n\n## 为组件添加 data 和 methods\n\n既然组件是一个页面，那么，页面中可能会有一些功能要**动态展示**。因此，我们有必要为组件添加 data 和 methods。\n\n代码举例如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <!-- 定义组件的模板 -->\n    <template id=\"myAccount\">\n        <div>\n            <!-- 在组件的模板中，调用本组件中的data -->\n            {{myData}}\n            <a href=\"#\" v-on:click=\"login\">登录1</a>\n            <h2>登录页面</h2>\n            <h3>注册页面</h3>\n\n        </div>\n    </template>\n\n    <div id=\"app\">\n        <!-- 第一次调用组件 -->\n        <account> </account>\n        <!-- 第二次调用组件 -->\n        <account> </account>\n    </div>\n\n    <script>\n\n        //定义、注册组件\n        Vue.component('account', {\n            template: '#myAccount',\n            //组件中的 data\n            //【注意】组件中的data，不再是对象，而是一个方法（否则报错）；而且这个方法内部，还必须返回一个对象才行\n            // 组件中 的data 数据,使用方式,和实例中的 data 使用方式完全一样!!!\n            data: function () {\n                return {\n                    myData: 'smyhvae'\n                }\n            },\n            //组件中的 method\n            methods: {\n                login: function () {\n                    alert('login操作');\n                }\n            }\n        });\n\n        new Vue({\n            el: '#app'\n        });\n    </script>\n</body>\n\n</html>\n\n```\n\n上方代码所示，我们在`account`组件中添加的data 和 methods，其**作用域**只限于`account`组件里，保证独立性。\n\n注意，在为组件添加数据时，data不再是对象了，而是function，而且要通过 return的形式进行返回；否则，页面上是无法看到效果的。通过 function返回对象的形式来定义data，作用是：\n\n- 上方代码中，组件`<account>`被调用了两次（不像根组件那样只能调用一次），但是每个组件里的数据 myData是**各自独立**的，不产生冲突。\n\n- 换而言之，通过函数返回对象的目的，是为了让每个组件都有自己**独立的数据存储**，而不应该共享一套数据。\n\n\n### 为什么组件的data必须是一个function\n\n\n我们先来看下面这样的例子：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 第一次调用组件 -->\n        <counter></counter>\n        <hr>\n\n        <!-- 第二次调用组件 -->\n        <counter></counter>\n    </div>\n\n    <!-- 定义模板 -->\n    <template id=\"tmpl\">\n        <div>\n            <input type=\"button\" value=\"让count加1\" @click=\"increment\">\n            <h3>{{count}}</h3>\n        </div>\n    </template>\n\n    <script>\n        var dataObj = { count: 0 }\n\n        // 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1\n        Vue.component('counter', {\n            template: '#tmpl',\n            data: function () {\n                return dataObj //当我们return全局的dataObj的时候，子组件们会共享这个dataObj\n            },\n            methods: {\n                increment() {\n                    this.count++\n                }\n            }\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {},\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n运行效果如下：\n\n\n![](http://img.smyhvae.com/20180617_1925.gif)\n\n\n上面的例子中，将组件`<counter>`调用了两次，由于`dataObj`是**全局对象**，导致两个组件实例都可以**共享**这个`dataObj`数据。于是，我们点击任何一个组件实例的按钮，都可以让`count`数据加1。\n\n\n现在问题来了，如果我们想让组件`<counter>`的两个实例去单独操作`count`数据，应该怎么做呢？我们应该修改 data中 return出去的内容：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <counter></counter>\n        <hr>\n        <counter></counter>\n        <hr>\n        <counter></counter>\n    </div>\n\n    <template id=\"tmpl\">\n        <div>\n            <input type=\"button\" value=\"让count加1\" @click=\"increment\">\n            <h3>{{count}}</h3>\n        </div>\n    </template>\n\n    <script>\n        var dataObj = { count: 0 }\n\n        // 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1\n        Vue.component('counter', {\n            template: '#tmpl',\n            data: function () {\n                // return dataObj //当我们return全局的dataObj的时候，这个dataObj是共享的\n                return { count: 0 } // 【重要】return一个**新开辟**的对象数据\n            },\n            methods: {\n                increment() {\n                    this.count++\n                }\n            }\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {},\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n\n```\n\n运行效果：\n\n![](http://img.smyhvae.com/20180617_1935.gif)\n\n如上图所示，每当我们创建一个新的组件实例时，就会调用data函数，data函数里会return一个**新开辟**的对象数据。这样做，就可以保证每个组件实例有**独立的数据存储**。\n\n## 组件的切换\n\n\n### 使用v-if和v-else结合flag进行切换\n\n\n代码举例：（登录组件/注册组件，二选一）\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 温馨提示：`.prevent`可以阻止超链接的默认事件 -->\n        <a href=\"\" @click.prevent=\"flag=true\">登录</a>\n        <a href=\"\" @click.prevent=\"flag=false\">注册</a>\n\n        <!-- 登录组件/注册组件，同时只显示一个 -->\n        <login v-if=\"flag\"></login>\n        <register v-else=\"flag\"></register>\n\n    </div>\n\n    <script>\n        Vue.component('login', {\n            template: '<h3>登录组件</h3>'\n        })\n\n        Vue.component('register', {\n            template: '<h3>注册组件</h3>'\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                flag: false\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n\n```\n\n运行效果如下：\n\n![](http://img.smyhvae.com/20180617_1957.gif)\n\n\n### 使用Vue提供的`<component>`标签实现组件切换\n\n上面的例子中，我们是通过flag的值来进行组件的切换。但是，flag的值只可能有两种情况，也就是说，v-if和v-else只能进行两个组件之间的切换。\n\n那如何实现三个甚至三个以上的组件切换呢？这里，我们可以用到Vue提供的`<component>`标签。\n\n\n我们先来看一下`<component>`标签的用法。\n\n基于上面的代码，如果我想让login组件显示出来，借助`<component>`标签可以这样做：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"Vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n\n        <!-- Vue提供了 component ,来展示对应名称的组件 -->\n        <!-- 【重要】component 是一个占位符, `:is` 属性,可以用来指定要展示的组件名称。这里，我们让 login 组件显示出来 -->\n        <component :is=\"'login'\"></component>\n\n    </div>\n\n    <script>\n        // 组件名称是 字符串\n        Vue.component('login', {\n            template: '<h3>登录组件</h3>'\n        })\n\n        Vue.component('register', {\n            template: '<h3>注册组件</h3>'\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                comName: 'login' // 当前 component 中的 :is 绑定的组件的名称\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n上方代码中，提取关键代码如下：\n\n```html\n        <component :is=\"'login'\"></component>\n```\n\n\n如果我想让register组件显示出来，借助`<component>`标签可以这样做：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"Vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n\n        <!-- Vue提供了 component ,来展示对应名称的组件 -->\n        <!-- 【重要】component 是一个占位符, `:is` 属性,可以用来指定要展示的组件名称 -->\n        <component :is=\"'register'\"></component>\n\n    </div>\n\n    <script>\n        // 组件名称是 字符串\n        Vue.component('login', {\n            template: '<h3>登录组件</h3>'\n        })\n\n        Vue.component('register', {\n            template: '<h3>注册组件</h3>'\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                comName: 'login' // 当前 component 中的 :is 绑定的组件的名称\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n上方代码中，提取关键代码如下：\n\n```html\n        <component :is=\"'register'\"></component>\n```\n\n因此，如果要实现组件之间的切换，我们可以给`<component>`标签里的is属性值设置为变量即可，来看看代码实现。\n\n**实现组件切换**的完整代码：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <!-- 点击按钮后，设置变量`comName`为不同的值，代表着后面的component里显示不同的组件 -->\n        <a href=\"\" @click.prevent=\"comName='login'\">登录</a>\n        <a href=\"\" @click.prevent=\"comName='register'\">注册</a>\n\n        <!-- Vue提供了 component ,来展示对应名称的组件 -->\n        <!-- component 是一个占位符, :is 属性,可以用来指定要展示的组件的名称 -->\n        <!-- 此处的`comName`是变量，变量值为组件名称 -->\n        <component :is=\"comName\"></component>\n\n    </div>\n\n    <script>\n        // 组件名称是 字符串\n        Vue.component('login', {\n            template: '<h3>登录组件</h3>'\n        })\n\n        Vue.component('register', {\n            template: '<h3>注册组件</h3>'\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                comName: 'login' // 当前 component 中的 :is 绑定的组件的名称\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n效果：\n\n![](http://img.smyhvae.com/20180617_1957.gif)\n\n\n## 多个组件切换时，通过mode属性添加过渡的动画\n\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n    <style>\n        .v-enter,\n        .v-leave-to {\n            opacity: 0;\n            transform: translateX(150px);\n        }\n\n        .v-enter-active,\n        .v-leave-active {\n            transition: all 0.5s ease;\n        }\n    </style>\n</head>\n\n<body>\n    <div id=\"app\">\n        <a href=\"\" @click.prevent=\"comName='login'\">登录</a>\n        <a href=\"\" @click.prevent=\"comName='register'\">注册</a>\n\n        <!-- 通过 mode 属性,设置组件切换时候的 过渡动画 -->\n        <!-- 【重点】亮点是 mode=\"out-in\" 这句话 -->\n        <transition mode=\"out-in\">\n            <component :is=\"comName\"></component>\n        </transition>\n\n    </div>\n\n    <script>\n        // 组件名称是 字符串\n        Vue.component('login', {\n            template: '<h3>登录组件</h3>'\n        })\n\n        Vue.component('register', {\n            template: '<h3>注册组件</h3>'\n        })\n\n        // 创建 Vue 实例，得到 ViewModel\n        var vm = new Vue({\n            el: '#app',\n            data: {\n                comName: 'login' // 当前 component 中的 :is 绑定的组件的名称\n            },\n            methods: {}\n        });\n    </script>\n</body>\n\n</html>\n```\n\n运行效果：\n\n\n![](https://img.smyhvae.com/20180618_2240.gif)\n\n\n如上方代码所示，多个组件切换时，如果要设置动画，可以用`<transition>`把组件包裹起来。需要注意的是，我给`<transition>`标签里添加了`mode=\"out-in\"`这种模式，它表示第一个组件消失之后，第二个组件才会出现。如果没有这个mode属性，效果如下：（第一个组件准备消失的时候，第二个组件马上就准备出现，这不是我们想要的效果）\n\n\n![](https://img.smyhvae.com/20180618_2245.gif)\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](https://img.smyhvae.com/20200102.png)\n\n\n"
  },
  {
    "path": "12-Vue基础/12-Vue组件之间的传值.md",
    "content": "---\r\ntitle: 12-Vue组件之间的传值\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 父组件向子组件传值\r\n\r\n\r\n我们可以这样理解：Vue实例就是一个**父组件**，而我们自定义的组件（包括全局组件、私有组件）就是**子组件**。\r\n\r\n【重点】需要注意的是，子组件不能直接使用父组件中的数据。**父组件可以通过`props`属性向子组件传值**。\r\n\r\n### 父组件向子组件传值的代码举例\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <!-- 第三步：父组件在引用子组件的时候， 通过 属性绑定（v-bind:）的形式,  -->\r\n        <!--   把 需要传递给 子组件的数据，以属性绑定的形式，传递到子组件内部，供子组件使用 -->\r\n        <component1 v-bind:parent-msg=\"msg\"></component1>\r\n    </div>\r\n\r\n    <!-- 定义子组件的模板 -->\r\n    <template id=\"myTemplate\">\r\n        <!-- 第二步：在子组件的模板中，使用props中的属性 -->\r\n        <h2 @click=\"change\">我是子组件。我想使用父组件中的数据parentMsg： {{ parentMsg }}</h2>\r\n    </template>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                msg: '父组件中的数据123'\r\n            },\r\n            methods: {},\r\n            components: {\r\n                // 子组件默认无法访问到 父组件中的 data 中的数据 和 methods 中的方法\r\n                component1: { //将子组件的名称定义为 component1\r\n                    template: '#myTemplate',\r\n                    data() { // 注意： 子组件中的 data 数据，并不是通过 父组件传递过来的，而是子组件自身私有的，比如： 子组件通过 Ajax ，请求回来的数据，都可以放到 data 身上；\r\n                        // data 上的数据，都是可读可写的\r\n                        return {\r\n                            title: '子组件私有的数据 title',\r\n                            content: '子组件私有的数据 content'\r\n                        }\r\n                    },\r\n                    // 注意： 组件中的 所有 props 中的数据，都是通过 父组件 传递给子组件的\r\n                    // props 中的数据，都是只读的，无法重新赋值\r\n                    props: ['parentMsg'], // 第一步：把父组件传递过来的 parentMsg 属性，先在 props 数组中，定义一下，这样，才能使用这个数据\r\n                    directives: {},\r\n                    filters: {},\r\n                    components: {},\r\n                    methods: {\r\n                        change() {\r\n                            // 下面这行会报错，因为子组件不要直接修改父组件中的data数据\r\n                            // this.parentMsg = '被修改了'\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n\r\n效果如下：\r\n\r\n![](http://img.smyhvae.com/20180618_2350.png)\r\n\r\n代码截图如下：\r\n\r\n![](http://img.smyhvae.com/20180618_2355.png)\r\n\r\n\r\n**父组件给子组件传值的步骤**：\r\n\r\n> 根据上方截图，我们可以总结出父组件给子组件传值的步骤如下。\r\n\r\n（1）在子组件的`props`属性中声明父亲传递过来的数据\r\n\r\n（2）定义子组件的模板时，使用props中的属性\r\n\r\n（3）父组件在引用子组件时，进行属性绑定。\r\n\r\n\r\n\r\n\r\n**子组件中，data中的数据和props中的数据的区别**：\r\n\r\n- 子组件中的 data 数据，并不是通过 父组件传递过来的，而是子组件自身私有的，比如： 子组件通过 Ajax ，请求回来的数据，都可以放到 data 身上。props 中的数据，都是通过 父组件 传递给子组件的。\r\n\r\n\r\n- data中的数据是可读可写的；props中的属性只是可读的，无法重新赋值，重新赋值会报错（也就是说，子组件不要直接去修改父组件中的数据）。\r\n\r\n### 父组件将方法传递给子组件\r\n\r\n> 父组件通过事件绑定机制，将父组件的方法传递给子组件\r\n\r\n代码举例：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <!-- 父组件向子组件 传递 方法，是通过 事件绑定机制； v-on。当我们自定义了 一个 事件属性 parent-show（这个地方不能用驼峰命名）之后，-->\r\n        <!-- 那么，子组件就能够，通过 emit 来调用 传递进去的 这个 方法了 -->\r\n        <!-- 【第一步】。意思是说，`show`是父组件的方法名，`parent-show`是自定义的时间属性，稍后要在子组件中用到 -->\r\n        <component1 @parent-show='show'></component1>\r\n    </div>\r\n\r\n    <!-- 定义子组件的模板 -->\r\n    <template id=\"myTemplate\">\r\n        <!-- 【第二步】按照正常的写法来：点击按钮，调用子组件的方法 -->\r\n        <div @click=\"childClick\">我是子组件，点击调用父组件的方法</div>\r\n    </template>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: { //父组件的data\r\n                // msg: '父组件中的数据'\r\n            },\r\n            methods: {\r\n                show: function () { // 定义父组件的show方法\r\n                    console.log('父组件提供的方法');\r\n                }\r\n            },\r\n            components: {\r\n                component1: { //将子组件的名称定义为 component1\r\n                    template: '#myTemplate',\r\n                    data() { // 子组件的data\r\n                        return {\r\n                            // content: '子组件私有的数据 content'\r\n                        }\r\n                    },\r\n                    props: [''],\r\n                    directives: {},\r\n                    filters: {},\r\n                    components: {},\r\n                    methods: {\r\n                        childClick() {\r\n                            // 当点击子组件的按钮时，如何 拿到 父组件传递过来的 func 方法，并调用这个方法？？？\r\n                            //  emit 英文原意： 是触发，调用、发射。意思是，触发父组件的方法\r\n                            // 【第三步】 在子组件的方法中，通过 emit 触发父组件的方法\r\n                            this.$emit('parent-show');\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n效果如下：（点击子组件，触发了父组件的方法）\r\n\r\n![](http://img.smyhvae.com/20180701_1800.png)\r\n\r\n根据上面的代码，我们可以总结出，父组件将方法传递给子组件，分为三步，具体可以看上方代码的注释。\r\n\r\n\r\n## 子组件向父组件传值\r\n\r\n上面的一段中，我们再看一遍**父组件将方法传递给子组件**的这段代码（一定要再看一遍，因为我们是要在此基础之上做修改）。\r\n\r\n如果要实现**子组件向父组件传值**，代码是类似的，我们只需要在子组件通过`emit`触发父组件的方法时，把子组件的参数带出去就可以了。代码如下。\r\n\r\n**代码举例1**：(将子组件中的常量传递给父组件)\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <component1 @parent-show='show'></component1>\r\n    </div>\r\n\r\n    <!-- 定义子组件的模板 -->\r\n    <template id=\"myTemplate\">\r\n        <h2 @click=\"childClick\">我是子组件，点击调用父组件的方法</h2>\r\n    </template>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: { //父组件的data\r\n                // msg: '父组件中的数据'\r\n            },\r\n            methods: { // 定义父组件的方法\r\n                show: function (arg1, arg2) { //【第二步】父组件里放两个参数，这个两个参数就代表着子组件中的`child 123`、`child 789`\r\n                    console.log('父组件提供的方法');\r\n                    console.log('打印子组件传递过来的参数。参数一：' + arg1 + '，参数二：'+ arg2);\r\n                }\r\n            },\r\n            components: {\r\n                component1: { //将子组件的名称定义为 component1\r\n                    template: '#myTemplate',\r\n                    data() { // 子组件的data\r\n                        return {\r\n                            // content: '子组件私有的数据 content'\r\n                        }\r\n                    },\r\n                    props: [''],\r\n                    directives: {},\r\n                    filters: {},\r\n                    components: {},\r\n                    methods: {\r\n                        childClick() {\r\n                            // 子组件如果要给父组件传递参数，在触发 emit 的时候，通过参数的形式带出去就可以了\r\n                            // 【第一步】在子组件里，我们带两个参数出去，传给父组件\r\n                            this.$emit('parent-show', 'child 123', 'child 789');\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n运行结果：（点击`<h2>`之后）\r\n\r\n![](http://img.smyhvae.com/20180623_1640.png)\r\n\r\n**代码举例2**：（将子组件中的data数据传递给父组件，存放到父组件的data中）\r\n\r\n> 在上方代码的基础之上，做改进。\r\n\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n        <component1 @parent-show='show'></component1>\r\n    </div>\r\n\r\n    <!-- 定义子组件的模板 -->\r\n    <template id=\"myTemplate\">\r\n        <h2 @click=\"childClick\">我是子组件，点击调用父组件的方法</h2>\r\n    </template>\r\n\r\n    <script>\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: { //父组件的data\r\n                parentData: null\r\n            },\r\n            methods: { // 定义父组件的方法\r\n                show: function (arg) { //【第二步】父组件里放参数，这个参数就代表着子组件中的 child.data\r\n                    console.log('父组件提供的方法');\r\n                    this.parentData = arg; //将参数arg传递给父组件的data，也就达到了目的：子组件传递数据，赋值给父组件\r\n                    console.log('打印父组件的数据（这是子组件传过来的）：'+ JSON.stringify(this.parentData));\r\n                }\r\n            },\r\n            components: {\r\n                component1: { //将子组件的名称定义为 component1\r\n                    template: '#myTemplate',\r\n                    data() { // 子组件的data\r\n                        return {\r\n                            childData: { //定义自组件的数据\r\n                                name: 'smyhvae',\r\n                                age: 26\r\n                            }\r\n                        }\r\n                    },\r\n                    props: [''],\r\n                    directives: {},\r\n                    filters: {},\r\n                    components: {},\r\n                    methods: {\r\n                        childClick() {\r\n                            // 子组件如果要给父组件传递参数，在触发 emit 的时候，通过参数的形式带出去就可以了\r\n                            // 【第一步】在子组件里，通过传参的形式，把子组件的data，传给父组件\r\n                            this.$emit('parent-show', this.childData);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n\r\n运行结果：（点击`<h2>`之后）\r\n\r\n\r\n![](http://img.smyhvae.com/20180623_1655.png)\r\n\r\n\r\n## 案例：发表评论功能的实现\r\n\r\n> 该案例需要完善，目前只是为了演示 localStorage\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <link rel=\"stylesheet\" href=\"bootstrap3.3.7.css\">\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n\r\n        <cmt-box @func=\"loadComments\"></cmt-box>\r\n\r\n\r\n        <ul class=\"list-group\">\r\n            <li class=\"list-group-item\" v-for=\"item in list\" :key=\"item.id\">\r\n                <span class=\"badge\">评论人： {{ item.user }}</span>\r\n                {{ item.content }}\r\n            </li>\r\n        </ul>\r\n\r\n\r\n    </div>\r\n\r\n\r\n    <template id=\"tmpl\">\r\n        <div>\r\n\r\n            <div class=\"form-group\">\r\n                <label>评论人：</label>\r\n                <input type=\"text\" class=\"form-control\" v-model=\"user\">\r\n            </div>\r\n\r\n            <div class=\"form-group\">\r\n                <label>评论内容：</label>\r\n                <textarea class=\"form-control\" v-model=\"content\"></textarea>\r\n            </div>\r\n\r\n            <div class=\"form-group\">\r\n                <input type=\"button\" value=\"发表评论\" class=\"btn btn-primary\" @click=\"postComment\">\r\n            </div>\r\n\r\n        </div>\r\n    </template>\r\n\r\n    <script>\r\n\r\n        var commentBox = {\r\n            data() {\r\n                return {\r\n                    user: '',\r\n                    content: ''\r\n                }\r\n            },\r\n            template: '#tmpl',\r\n            methods: {\r\n                postComment() { // 发表评论的方法\r\n                    // 分析：发表评论的业务逻辑\r\n                    // 提示：评论数据存到哪里去？？？   存放到了 localStorage 中  localStorage.setItem('cmts', '')\r\n                    // 1. 先组织出一个最新的评论数据对象\r\n                    // 2. 想办法，把 第一步中，得到的评论对象，保存到 localStorage 中（注意：localStorage 只支持存放字符串数据， 因此要先调用 JSON.stringify）\r\n                    //  2.1 在保存 最新的 评论数据之前，要先从 localStorage 获取到之前的评论数据（string）， 转换为 一个  数组对象， 然后，把最新的评论， push 到这个数组\r\n                    //         注意：如果获取到的 localStorage 中的 评论字符串，为空不存在， 则  可以 返回一个 '[]'\r\n                    //  2.2  把 最新的  评论列表数组，再次调用 JSON.stringify 转为  数组字符串，然后调用 localStorage.setItem()\r\n\r\n                    var comment = { id: Date.now(), user: this.user, content: this.content }\r\n\r\n                    // 第一步：一开始，从 localStorage 中获取已存在的评论\r\n                    var list = JSON.parse(localStorage.getItem('cmts') || '[]') //获取已存在的评论数据。【重要】需要考虑空字符串的可能性，否则返回的是undefined\r\n                    // 第二步：添加新的评论item\r\n                    list.unshift(comment)\r\n                    // 第三步：重新保存最新的 评论数据 到 localStorage 中\r\n                    localStorage.setItem('cmts', JSON.stringify(list))\r\n\r\n                    this.user = this.content = ''\r\n\r\n                    // this.loadComments() // ?????\r\n                    this.$emit('func')\r\n                }\r\n            }\r\n        }\r\n\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {\r\n                list: [\r\n                    { id: Date.now(), user: '李白', content: '天生我材必有用' },\r\n                    { id: Date.now(), user: '江小白', content: '劝君更尽一杯酒' },\r\n                    { id: Date.now(), user: '小马', content: '我姓马， 风吹草低见牛羊的马' }\r\n                ]\r\n            },\r\n            beforeCreate() { // 注意：这里不能调用 loadComments 方法，因为在执行这个钩子函数的时候，data 和 methods 都还没有被初始化好\r\n\r\n            },\r\n            created() {\r\n                //页面一开始加载的时候，就去读取 localStorage 中已存在的评论list\r\n                this.loadComments()\r\n            },\r\n            methods: {\r\n                loadComments() { // 从本地的 localStorage 中，加载评论列表\r\n\r\n                    var list = JSON.parse(localStorage.getItem('cmts') || '[]')\r\n                    this.list = list\r\n                }\r\n            },\r\n            components: {\r\n                'cmt-box': commentBox\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n\r\n上面的代码中，父组件定义了`loadComments()`方法，作用是**加载 localStorage 中的评论列表**。我们可以看到，页面在一开始加载的时候，就在create()生命周期中调用了`loadComments()`；当自组件中添加了评论之后，再次调用了`loadComments()`。\r\n\r\n**待改进**：\r\n\r\n不过，这段代码还有些问题：页面一开始加载的时候，读取的是 localStorage 中的评论列表。如果一开始的时候，从网络获取了已存在的列表，岂不是读不到了？\r\n\r\n正确的做法应该是：父组件和子组件共享 list数据，每当在子组件中 添加了一条评论之后，就往 list 中添加一条 item。\r\n\r\n\r\n## 在Vue中，通过 ref 属性获取DOM元素\r\n\r\n我们当然可以使用JS原生的做法（document.getElementById）或者 jQuery 来获取DOM，但是这种做法却在无形中操作了DOM，在Vue框架中并不推荐这种做法。\r\n\r\n我们可以通过`ref`属性获取DOM元素。\r\n\r\n`ref`的英文单词是**reference**，表示**引用**。我们平时可以经常看到控制台会报错**referenceError**的错误，就和引用类型的数据有关。\r\n\r\n\r\n**在Vue中，通过 ref 属性获取DOM元素**的步骤：\r\n\r\n（1）第一步：在标签中给 DOM 元素设置 ref 属性。\r\n\r\n```html\r\n    <h3 id=\"myH3\" ref=\"myTitle\"> 今天天气太好了</h3>\r\n```\r\n\r\n（2）第二步：通过 this.this.$refs.xxx 获取 DOM 元素\r\n\r\n```javascript\r\nconsole.log(this.$refs.myTitle.innerText)\r\n```\r\n\r\n**举例如下**：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n        <!-- 第一步：在标签中给 DOM 元素设置 ref 属性 -->\r\n        <h3 id=\"myH3\" ref=\"myTitle\"> 今天天气太好了</h3>\r\n\r\n        <input type=\"button\" value=\"按钮元素\" @click=\"getElement\" ref=\"myBtn\">\r\n\r\n\r\n    </div>\r\n\r\n    <script>\r\n\r\n        var login = {\r\n            template: '<h1>登录组件</h1>',\r\n            data() {\r\n                return {\r\n                    msg: 'son msg'\r\n                }\r\n            },\r\n            methods: {\r\n                show() {\r\n                    console.log('调用了子组件的方法')\r\n                }\r\n            }\r\n        }\r\n\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {},\r\n            methods: {\r\n                getElement() {\r\n                    // 原生js获取DOM元素\r\n                    // console.log(document.getElementById('myTitle').innerText)\r\n\r\n                    // 第二步：通过 this.this.$refs.xxx 获取 DOM 元素\r\n                    console.log(this.$refs.myTitle.innerText)\r\n\r\n\r\n                }\r\n            },\r\n            components: {\r\n                login\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n运行上方代码，然后我们在控制台输入`vm`，就可以看到：\r\n\r\n![](http://img.smyhvae.com/20180701_1640.png)\r\n\r\n\r\n### 使用 ref 属性获取整个子组件（父组件调用子组件的方法）\r\n\r\n根据上面的例子，我们可以得出**规律**：只要`ref`属性加在了DOM元素身上，我们就可以获取这个DOM元素。\r\n\r\n\r\n那我们可以通过ref属性获取整个**Vue子组件**吗？当然可以。这样做的意义是：**在父组件中通过`ref`属性拿到了子组件之后，就可以进一步拿到子组件中的data和method。\r\n\r\n\r\n\r\n\r\n举例：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n    <div id=\"app\">\r\n\r\n        <input type=\"button\" value=\"点击按钮\" @click=\"getElement\">\r\n\r\n        <login-component ref=\"loginTemplate\"></login-component>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        // 创建 Vue 实例，得到 ViewModel\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            data: {},\r\n            methods: {\r\n                getElement() {\r\n\r\n                    //在父组件中，通过ref获取整个子组件，进而获取子组件的data\r\n                    console.log(this.$refs.loginTemplate.myData)\r\n\r\n                    //在父组件中，通过ref获取整个子组件，进而获取子组件的method\r\n                    this.$refs.loginTemplate.showMethod()\r\n                }\r\n            },\r\n            components: {\r\n                'login-component': {\r\n                    template: '<h1>登录组件</h1>',\r\n                    data() {\r\n                        return {\r\n                            myData: '子组件的data'\r\n                        }\r\n                    },\r\n                    methods: {\r\n                        showMethod() {\r\n                            console.log('调用子组件的method')\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n运行代码，点击按钮后，效果如下：\r\n\r\n![](http://img.smyhvae.com/20180701_1735.png)\r\n\r\n我们直接在控制台输入`vm`，可以看到：\r\n\r\n![](http://img.smyhvae.com/20180701_1740.png)\r\n\r\n"
  },
  {
    "path": "12-Vue基础/13-Vue-router路由.md",
    "content": "---\r\ntitle: 13-Vue-router路由\r\npublish: true\r\n---\r\n\r\n<ArticleTopAd></ArticleTopAd>\r\n\r\n\r\n\r\n\r\n## 什么是路由\r\n\r\n\r\n### 后端路由\r\n\r\n对于普通的网站，所有的超链接都是URL地址，所有的URL地址都对应服务器上对应的资源。\r\n\r\n当前端输入url请求资源时，服务器会监听到是什么url地址，那后端会返回什么样的资源呢？后端这个处理的过程就是通过**路由**来**分发**的。\r\n\r\n**总结**：后端路由，就是把所有url地址都对应到服务器的资源，这个**对应关系**就是路由。\r\n\r\n### 前端路由\r\n\r\n对于单页面应用程序来说，主要通过URL中的`hash`（url地址中的#号）来实现不同页面之间的切换。\r\n\r\n同时，hash有一个特点：HTTP请求中不会包含hash相关的内容。所以，单页面程序中的页面跳转主要用hash实现。\r\n\r\n**总结**：在**单页应用**程序中，这种通过`hash`改变来**切换页面**的方式，称作前端路由（区别于后端路由）。\r\n\r\n## 安装Vue-router的两种方式\r\n\r\n- 官方文档：<https://router.vuejs.org/zh/>\r\n\r\n\r\n**方式一**：直接下载文件\r\n\r\n下载网址：<https://unpkg.com/vue-router/dist/vue-router.js>\r\n\r\n\r\n下载之后，放进项目工程，然后我们在引入`vue.js`之后，再引入`vue-router.js`即可：\r\n\r\n```html\r\n    <script src=\"vue2.5.16.js\"></script>\r\n    <script src=\"vue-router3.0.1.js\"></script>\r\n```\r\n\r\n\r\n然后，我们就可以在 window全局对象中使用 VueRouter这个对象。具体解释可以看接下来的代码中的注释。\r\n\r\n注意，只要我们导入了`vue-router.js`这个包，在浏览器中打开网页时，url后面就会显示`#`这个符号。\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "12-Vue基础/Vue-router路由.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 前言\n\n路由：就是SPA（单页应用）的**路径管理器**。\n\n"
  },
  {
    "path": "12-Vue基础/Vue.js在开发中的常见写法积累.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n### 001、对象的赋值\n\n（1）在 store 中定义一个对象：\n\n```javascript\n    userInfo: {\n        pin: '',\n        nickName: '',\n        avatarUrl: DEFAULT_AVATAR,\n        definePin: '',\n        isbind: true\n    },\n```\n\n（2）从接口拿到数据后，给这个对象赋值：\n\n```javascript\n    this.userInfo = {\n        ...this.userInfo,\n        pin: res.base.curPin,\n        nickName: res.base.nickname,\n        avatarUrl: res.base.headImageUrl ? res.base.headImageUrl : DEFAULT_AVATAR,\n        definePin: res.definePin\n    }\n```"
  },
  {
    "path": "12-Vue基础/Vue开发积累.md",
    "content": "---\ntitle: 01-数据库的基础知识\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n\n### 动态添加对象的属性\n\n- Vue中，动态新增对象的属性时，不能直接添加。正确的做法是：Vue.set(obj,key,value)。参考链接：[#](https://blog.csdn.net/tian361zyc/article/details/72909187)\n\n\n\n\n### 判断一个checkbox是否被选中\n\n```html\n<!-- v-model里的内容是变量，变量里的值可能是 true 后者 false -->\n<input type=\"checkbox\" v-model=\"isSelected\">\n\n<!-- 选中时，值为 true。未选中时，值为 false -->\n<span>{{isSelected}}</span>\n\n\n<!-- 选中时，显示文字。未选中时，隐藏文字 -->\n<span v-if=\"isSelected\">haha</span>\n\n```\n\n\n\n### 多个checkbox的全选和反选\n\n现在有多个checkbox的item在一个数组中，另外还有一个“全选”的checkbox按钮。\n\n**点击全选按钮，让子item全部选中**：\n\n采用 watch 监听全选按钮，然后改变子item。\n\n**当子item全部被选中时，触发全选按钮**：\n\n采用 computed 计算子item 的状态，存放到变量 allChecked 中，然后用 watch 监听 allChecked 的值。\n\n参考链接：\n\n- [问Vue.js 如何在 data 里含数组的情况下，监听数组内指定属性的变化？](https://segmentfault.com/q/1010000014514160/a-1020000014514452)\n\n\n\n\n\n"
  },
  {
    "path": "12-Vue基础/Vue组件.md",
    "content": "\r\n---\r\ntitle: 01-数据库的基础知识\r\npublish: false\r\n---\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 子组件的定义和注册\r\n\r\n我们在本文的第一段中，通过`Vue.component`形式定义的是**全局组件**。这一段中，我们来讲一下**子组件**。\r\n\r\n### 在父组件中定义子组件\r\n\r\n比如说，一个`账号`模块是父组件，里面分为`登陆`模块和`注册`模块，这两个晓得模块就可以定义为子组件。\r\n\r\n需要注意的是作用域的问题：我们在父组件中定义的子组件，只能在当前父组件的模板中使用；在其他的组件，甚至根组件中，都无法使用。\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <account>\r\n\r\n        </account>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        //定义、注册组件\r\n        Vue.component('account', {\r\n            template: '<div><h2>账号模块</h2> <login></login></div>',\r\n            components: {\r\n                'login': {\r\n                    template: '<h3>登录模块</h3>'\r\n                }\r\n            }\r\n        });\r\n\r\n        new Vue({\r\n            el: '#app'\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n我们发现，既然是定义父亲`<account>`的子组件，那么，子组件`<login>`的调用，只能写在父组件`<account>`的`template`模板中。\r\n\r\n显示效果：\r\n\r\n![](http://img.smyhvae.com/20180423_1029.png)\r\n\r\n\r\n### 在 Vue 根实例中定义子组件\r\n\r\n当然，我们还可以这样做：把整个 Vue 对象当成父亲，这样的话，就可以在 Vue 示例中定义一个子组件。如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <login> </login>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        new Vue({\r\n            el: '#app',\r\n            //在Vue实例中定义子组件\r\n            components: {  // components 是关键字，不能改\r\n                'login': {\r\n                    template: '<h3>登录模块</h3>'\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n这样写的话，我们定义的子组件`<login>`在整个`#app`区域，都是可以使用的。\r\n\r\n上面的代码，还有**另外一种写法**：（把子组件的模板定义，存放到变量中）【重要】\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <login> </login>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        //通过变量接收定义的子组件\r\n        var myLogin = {\r\n            template: '<h3>登录模块</h3>'   // template 是关键字，不能改\r\n        }\r\n\r\n        new Vue({\r\n            el: '#app',\r\n            //在Vue实例中定义子组件\r\n            components: {  // components 是关键字，不能改\r\n                'login': myLogin\r\n\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n注意，在定义子组件时，关键字`components`不要写错了。\r\n\r\n## 组件之间的动态切换（暂略）\r\n\r\n我们可以利用`<component> `标签的`:is`参数来进行组件之间的切换。\r\n\r\n## 父组件向子组件传递数据\r\n\r\n我们要记住：父组件通过**属性**的形式，向子组件传递数据。\r\n\r\n**引入**：\r\n\r\n我们先来看这样一段代码：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n</head>\r\n<script src=\"vue2.5.16.js\"></script>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <!-- 这里是父组件的范围，自定义一个数值 number -->\r\n        <counter number=\"1+2\"> </counter>\r\n        <counter number=\"10+20\"> </counter>\r\n    </div>\r\n\r\n    <script>\r\n        var myCounter = {\r\n            //【重要】这里是子组件的范围，无法直接获取父组件中的 number\r\n            template: '<div>我是子组件。{{number}}</div>'\r\n        }\r\n\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            components: {\r\n                'counter': myCounter\r\n            }\r\n        });\r\n    </script>\r\n\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n上方代码中，我想把父组件里 number 的数值传递给子组件，直接这样写，是看不到效果的：\r\n\r\n![](http://img.smyhvae.com/20180424_1520.png)\r\n\r\n**1、父组件传值给子组件**：\r\n\r\n要通过 props 属性将number进行传递给子组件才可以。代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n</head>\r\n<script src=\"vue2.5.16.js\"></script>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <!-- 这里是父组件的范围，自定义一个数值 number -->\r\n        <counter :number=\"1+2\"> </counter>\r\n        <counter :number=\"10+20\"> </counter>\r\n    </div>\r\n\r\n    <script>\r\n        var myCounter = {\r\n            //这里是子组件的范围\r\n            props: ['number'], //通过 props 属性将父亲的 number 数据传递给子组件\r\n            template: '<div>我是子组件。{{number}}</div>'\r\n        }\r\n\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            components: {\r\n                'counter': myCounter\r\n            }\r\n        });\r\n    </script>\r\n\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n在`<counter>`标签中，要注意`:number`里的冒号。加上冒号，那么引号里的内容就是表达式（期望的结果）；否则，引号的内容只是字符串：\r\n\r\n![](http://img.smyhvae.com/20180424_1530.png)\r\n\r\n**2、子组件获取了父组件的数据后，进行求和操作**：\r\n\r\n上方代码中，子组件已经获取了父组件的两个number，现在要求：每点击一次子组件，在子组件中将数据加 1。\r\n\r\n一般人可能会这样写：（不推荐的写法：子组件直接修改父组件中的数据）\r\n\r\n```javascript\r\n        var myCounter = {\r\n            //这里是子组件的范围\r\n            props: ['number'], //通过 props 属性将父亲的数据传递给子组件\r\n            template: '<div @click=\"addClick\">我是子组件。{{number}}</div>',\r\n            methods: {\r\n                addClick: function () {\r\n                    this.number ++;  //这种写法不推荐。不建议直接操作父组件中的数据\r\n                }\r\n\r\n            }\r\n        }\r\n```\r\n\r\n上方代码的写法不推荐，因为不建议直接操作父组件中的数据。虽然数据操作成功，但是控制台会报错：\r\n\r\nimg.png\r\n\r\n这样涉及到**单向数据流**的概念：\r\n\r\n- 父组件可以传递参数给子组件，但是反过来，子组件不要去修改父组件传递过来的参数。因为同一个参数，可能会传递给多个子组件，避免造成修改的冲突。\r\n\r\n既然如此，我可以把父组件中的数据，在子组件中创建**副本**，然后我们去修改这个副本，就不会造成影响了。最终，完整版代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n</head>\r\n<script src=\"vue2.5.16.js\"></script>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <!-- 这里是父组件的范围，自定义一个数值 number -->\r\n        <counter :number=\"1+2\"> </counter>\r\n        <counter :number=\"10+20\"> </counter>\r\n    </div>\r\n\r\n    <script>\r\n        var myCounter = {\r\n            //这里是子组件的范围\r\n            props: ['number'], //通过 props 属性将父亲的数据传递给子组件\r\n            data: function () {\r\n                return {\r\n                    number2: this.number\r\n                }\r\n            },\r\n            template: '<div @click=\"addClick\">我是子组件。{{number2}}</div>',\r\n\r\n            methods: {\r\n                addClick: function () {\r\n                    this.number2 ++;   //操作和修改number的副本\r\n                }\r\n\r\n            }\r\n        }\r\n\r\n        var vm = new Vue({\r\n            el: '#app',\r\n            components: {\r\n                'counter': myCounter\r\n            },\r\n\r\n        });\r\n    </script>\r\n\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n## 子组件向父组件传值\r\n\r\n我们要记住：子组件通过**事件触发**的形式，向父组件传值。\r\n\r\n\r\n\r\n\r\n**案例1:**子组件给父组件传递数据\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <!-- 在外层监听`mysend`事件，进而出发外层的 getData 方法 -->\r\n        <counter v-on:mysend=\"getData\"> </counter>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        Vue.component('counter', {\r\n            template: '<div @click = \"addClick\">发送数据给父组件</div>',  //当组件被点击时，触发 addClick 方法\r\n\r\n            methods: {\r\n                addClick: function () {\r\n                    //第一个参数为键(注意，要小写，不能大写)，第二个参数为值\r\n                    this.$emit('mysend', 'smyhvae'); //通过键`mysend`事件通知外面，将值`smyhvae`传给父组件\r\n                }\r\n            }\r\n        });\r\n\r\n        new Vue({\r\n            el: '#app',\r\n            methods: {\r\n                getData: function (input) { //通过括号里的参数，获取子组件传递过来的值\r\n                    console.log(input);   //打印结果：smyhvae\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n\r\n**案例2**：获取子组件的DOM对象\r\n\r\n题目：给两个相同的子组件定义计数器，每点击一次，数值加1。然后在父组件中求和。\r\n\r\n步骤（1）：给两个相同的子组件定义计数器，每点击一次，数值加1。代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <counter> </counter>\r\n        <counter> </counter>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        Vue.component('counter', {\r\n            template: '<div @click = \"addClick\">当前计数：{{number}}</div>',  //当组件被点击时，调用 addClick 方法\r\n\r\n            data: function () {\r\n                return {\r\n                    number: 0 //给组件定义一个数据：number\r\n                }\r\n            },\r\n            methods: {\r\n                addClick: function () {\r\n                    this.number++;   //定义方法：每点击一次，number 的数值加 1\r\n                }\r\n            }\r\n        });\r\n\r\n        new Vue({\r\n            el: '#app'\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n```\r\n\r\n步骤（2）：两个子组件的数值加 1 后，通知父组件。代码如下：\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <!-- 在外层监听`change`事件，进而出发外层的 myClick 方法 -->\r\n        <counter @change=\"myMethod\"> </counter>\r\n        <counter @change=\"myMethod\"> </counter>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        Vue.component('counter', {\r\n            template: '<div @click = \"addClick\">当前计数：{{number}}</div>',  //当组件被点击时，\r\n\r\n            data: function () {\r\n                return {\r\n                    number: 0 //给组件定义一个数据：number\r\n                }\r\n            },\r\n            methods: {\r\n                addClick: function () {\r\n                    this.number++;   //定义方法：每点击一次，number 的数值加 1\r\n                    this.$emit('change'); //通过这一行的`change`，通知外面，内部的 addClick 方法已经执行了\r\n                }\r\n            }\r\n        });\r\n\r\n        new Vue({\r\n            el: '#app',\r\n            methods: {\r\n                myMethod: function () {\r\n                    console.log('触发父组件');\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n上方代码中，通过关键字`emit`通知父组件，子组件里的 addClick 方法被执行了。父组件得知后，执行`myMethod()`方法（这个方法是在Vue实例中定义的，很好理解）\r\n\r\n步骤（3）：在父组件中求和\r\n\r\n既然父组件已经得知子组件的 addClick 事件，那我们直接在父组件的`myMethod()`里定义求和的方法即可。\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <script src=\"vue2.5.16.js\"></script>\r\n</head>\r\n\r\n<body>\r\n\r\n    <div id=\"app\">\r\n        <!-- 在外层监听`change`事件，进而触发外层的 myMethod 方法 -->\r\n        <counter ref=\"one\" @change=\"myMethod\"> </counter>\r\n        <counter ref=\"two\" @change=\"myMethod\"> </counter>\r\n        <div>{{totalData}}</div>\r\n    </div>\r\n\r\n    <script>\r\n\r\n        Vue.component('counter', {\r\n            template: '<div @click = \"addClick\">当前计数：{{number}}</div>',  //当组件被点击时，触发 addClick方法\r\n\r\n            data: function () {\r\n                return {\r\n                    number: 0 //给组件定义一个数据：number\r\n                }\r\n            },\r\n            methods: {\r\n                addClick: function () {\r\n                    this.number++;   //定义方法：每点击一次，number 的数值加 1\r\n                    this.$emit('change'); //通过这一行自定义的`change`，通知外面，内部的 addClick 方法已经执行了\r\n                }\r\n            }\r\n        });\r\n\r\n        new Vue({\r\n            el: '#app',\r\n            data: {\r\n                totalData: 0\r\n            },\r\n            methods: {\r\n                myMethod: function () {\r\n                    console.log('触发父组件');\r\n                    //通过`$refs`获取子组件中各自的number数值\r\n                    var a1 = this.$refs.one.number;\r\n                    var a2 = this.$refs.two.number;\r\n                    //求和，存放在父组件的 totalData 中\r\n                    this.totalData = a1 + a2;\r\n\r\n                    console.log();\r\n                }\r\n            }\r\n        });\r\n    </script>\r\n</body>\r\n\r\n</html>\r\n\r\n```\r\n\r\n代码的关键：\r\n\r\n- 在`<counter>`标签中，通过 `ref = \"xxx\"`属性，给各个组件起一个别名，代表组件的引用\r\n\r\n- 在父函数`myMethod()`中，通过`this.$refs.xxx`获取组件的引用。我们看一下最后两行代码在控制台的输出便知：（组件里有 number 属性）\r\n\r\n![](http://img.smyhvae.com/20180424_1455.png)\r\n\r\n\r\n\r\n"
  },
  {
    "path": "13-React基础/01-React介绍.md",
    "content": "---\ntitle: 01-React介绍\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 虚拟DOM和diff算法\n\n> 在学习 React 之前，我们需要先了解两个概念：虚拟DOM、diff算法。\n\n\n### 虚拟DOM\n\n**问题描述**：\n\n假设我们的数据发生一点点的变化，也会被强制重建整颗DOM树，这么做，会涉及到很多元素的重绘和重排，导致性能浪费严重。\n\n**解决上述问题的思路**：\n\n实现按需更新页面上的元素即可。也就是说，把 需要修改的元素，所对应的 DOM 元素重新构建；其他没有变化的数据，所对应的 DOM 节点不需要被强制更新。\n\n**具体实现方案**：（如何按需更新页面上的元素）\n\n只需要拿到 页面更新前的 内存中的DOM树，同时再拿到 页面更新前的 新渲染出来的 内存DOM树；然后，对比这两颗新旧DOM树，找到那些需要被重新创建和修改的元素即可。这样就能实现 DOM 的**按需更新**。\n\n\n**如何拿到这两棵DOM树**：（即：如何从浏览器的内存住哪个获取到 浏览器私有的那两颗DOM树？）\n\n如果要拿到浏览器私有的DOM树，那我们必须调用浏览器提供的相关JS的API才行。但是问题来了，浏览器并没有提供这样的API。既然如此，那我们可以自己**模拟**这两颗 新旧DOM树。\n\n**如何自己模拟这两颗 新旧DOM树**：（即：如何自己模拟一个DOM节点？）\n\n这里涉及到手动模拟DOM树的原理：使用 JS 创建一个对象，用和这个对象来模拟每一个DOM节点；然后在每个DOM节点中，又提供了类似于 children 这样的属性来描述当前DOM的子节点。这样的话，当DOM节点形成了嵌套关系，就模拟出了一颗 DOM 树。\n\n\n**总结**：\n\n- 虚拟DOM的**本质**：使用 JS 对象模拟DOM树。\n\n- 虚拟DOM的**目的**：为了实现 DOM 节点的高效更新。\n\nReact内部已经帮我们实现了虚拟DOM，初学者掌握如何调用即可。\n\n\n### diff算法\n\n怎么实现 两颗新旧DOM树的对比 呢？这里就涉及到了 diff算法。常见的 diff算法如下：\n\n - tree diff：新旧DOM树，逐层对比的方式，就叫做 tree diff。每当我们从前到后，把所有层的节点对比完后，必然能够找到那些 需要被更新的元素。\n\n - component diff：在对比每一层的时候，组件之间的对比，叫做 component diff。当对比组件的时候，如果两个组件的类型相同，则暂时认为这个组件不需要被更新，如果组件的类型不同，则立即将旧组件移除，新建一个组件，替换到被移除的位置。\n\n - element diff：在组件中，每个元素之间也要进行对比，那么，元素级别的对比，叫做 element diff。\n\n - key：key这个属性，可以把 页面上的 DOM节点 和 虚拟DOM中的对象，做一层关联关系。\n\n\n## React 介绍\n\n### React 是什么\n\n- Facebook 开源的一个JS库。\n\n- 一个用于动态构建用户界面的JS库。\n\n### React 的特点\n\n- Declarative（声明式编码）\n\n- Component-Based（组件化编码）\n\n- Learn Once, Write Anywhere（支持客户端、服务器端渲染）\n\n- 高效的DOM Diff算法，最小化页面重绘\n\n- 单向数据流\n\n### React高效的原因\n\n- 虚拟(virtual)DOM，不总是直接操作DOM\n\n- 高效的DOM Diff算法，最小化页面重绘（即“局部渲染”）。\n\n虚拟DOM指的是：在真实DOM的上一层**映射**一层虚拟DOM。我们操作的是映射关系，而不是真实的DOM。假设页面的样式做了修改（比如新增了一个标签），此时修改的是虚拟DOM的样式，真实的DOM并未发生变化。那什么时候，真实的DOM会发生变化呢？ 当我把所有的内容操作完之后，转化为真实的DOM，此时要打包统一的渲染页面，于是真实的DOM发生变化，然后渲染一次。 这样做的话，可以减少页面的渲染次数。\n\n### 相关网址\n\n- 官网：<https://reactjs.org/>\n\n- GitHub 地址：<https://github.com/facebook/react>  截至2019-02-08，React项目已经有 121k 的star。\n\n官网截图：\n\n20190208_1057.png\n\n上方截图中，有一个特性是“Learn Once, Write Anywhere”。这里的 “Anywhere” 其实指的是两个地方：一个是浏览器端，一个是服务器端。后者指的是，**React支持在服务器端渲染页面**。\n\n### 生态介绍\n\n- Vue生态：Vue + Vue-Router + Vuex + Axios + Babel + Webpack\n\n- React生态：React + React-Router + Redux + Axios + Babel + Webpack\n\n\n## React 模块化、组件化\n\n### 模块\n\n- 理解：向外提供特定功能的js程序, 一般就是一个js文件\n\n- 理由：js代码更多更复杂\n\n- 作用：简化js的编写，阅读，提高运行效率\n\n### 组件\n\n- 理解：用来实现特定功能效果的代码集合(html/css/js)\n\n- 理由：一个界面的功能更复杂\n\n- 作用：复用，简化项目编码，提高运行效率\n\n### 模块化与组件化\n\n- 模块化：当应用的js都以模块来编写的, 这个应用就是一个模块化的应用\n\n- 组件化：当应用是以多组件的方式实现功能, 这上应用就是一个组件化的应用\n\n\n### 面相对象与面向过程的区别\n\n面向对象编程：\n\n- 重点是对象\n\n- 更加关心的是干活的人\n\n面向过程编程：\n\n- 更加关心的是干活的过程\n\n- 谁去干活儿不关心\n\n\n## React 环境搭建：写第一个Hello World\n\n### react.js 和 react-dom.js\n\n为了通过 React 写一个Hello World程序，我们需要先安装几个包：\n\n- react.js: React的核心库。这个包，是专门用来创建React组件、组件生命周期等。\n\n- react-dom.js: 操作DOM的扩展库。这个包，主要封装了和 DOM 操作相关的包（比如，把组件渲染到页面上）。\n\n- babel.min.js: 将 JSX语法 解析为 纯JS语法代码。\n\n### 方式一：本地引入相关的js库\n\n入门的时候，我们建议采取方式一。\n\n如果是本地引入的话，可以这样写：\n\n```html\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n```\n\n如果是通过CDN的方式引入的话，可以使用网站 <https://www.bootcdn.cn/> 提供的CDN链接。\n\n**完整代码举例**：\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n    <div id=\"myContainer\"></div>\n\n    <!-- 注意，这一行的 type 是写 \"text/babel\"，而不是 \"text/javascript\" -->\n    <script type=\"text/babel\">\n\n      //页面中的真实容器元素\n      var containDiv = document.getElementById(\"myContainer\");\n\n      //1、创建虚拟DOM对象\n      var vDom = <div>Hello, React!</div>; // 不是字符串, 不能加引号\n\n      //2、渲染虚拟DOM对象（将虚拟DOM对象渲染到页面元素中）\n      ReactDOM.render(vDom, containDiv); // 参数1：虚拟DOM对象；参数2：页面中的容器\n    </script>\n  </body>\n</html>\n\n```\n\n代码运行后，页面上的DOM结构如下：\n\n```html\n<div id=\"myContainer\">\n\t<div>Hello, React!</div>\n</div>\n```\n\n\n**代码解释**：\n\nrender的中文含义是“渲染”。render 方法的语法如下：\n\n```javascript\n\tReactDOM.render(要渲染的虚拟DOM对象, 容器 container：要渲染到页面上的哪个位置);\n```\n\n工程文件：\n\n- [2019-02-08-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-08-ReactDemo.zip)\n\n\n### 方式二：npm install\n\n实际开发中，我们一般都是通过 npm install 的方式来安装 react 相关的包。\n\n首先，新建一个空的文件夹`2019-02-08-ReactDemo`，作为项目的根目录。然后在根目录下执行如下命令，进行**项目初始化**：\n\n```\n  npm init --yes\n```\n\n上方命令执行完成后，会生成`package.json`文件。\n\n然后继续执行如下命令，安装 react.js 和 react-dom.js 这两个包：\n\n```\n  npm i react react-dom\n```\n\n完整代码举例：\n\nindex.html:\n\n```\n\n```\n\n\nmain.js:\n\n```javascript\n// JS打包入口文件\n\nimport React from 'react'\nimport ReactDOM from 'react-dom'\n\n// 在 react 中，如要要创建 DOM 元素，只能使用 React 提供的 JS API 来创建，不能【直接】像 Vue 中那样，手写 HTML 元素\n// React.createElement() 方法，用于创建 虚拟DOM 对象，它接收 3个及以上的参数\n//     参数1： 是个字符串类型的参数，表示要创建的元素类型\n//     参数2： 是一个属性对象，表示 创建的这个元素上，有哪些属性\n//     参数3： 从第三个参数的位置开始，后面可以放好多的虚拟DOM对象，这写参数，表示当前元素的子节点\n\n// <div title=\"this is a div\" id=\"mydiv\">这是一个div</div>\nvar myDiv = React.createElement('div', { title: 'this is a div', id: 'mydiv' }, '这是一个div');\n\n// ReactDOM.render('要渲染的虚拟DOM元素', '要渲染到页面上的哪个位置');\nReactDOM.render(myDiv, document.getElementById('app'));\n```\n\n\n上方代码中，createElement()方法介绍如下：\n\n```javascript\n  React.createElement(需要创建的元素类型, 有哪些属性, 子节点)\n```\n\n\n工程文件：\n\n- [2019-02-09-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-09-ReactDemo.zip)\n\n\n## 我的公众号\n\n想学习<font color=#0000ff>**更多技能**</font>？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/20160401_01.jpg)\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "13-React基础/02-JSX语法介绍.md",
    "content": "---\ntitle: 02-JSX语法介绍\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## JSX介绍\n\n### JSX的引入\n\n如果直接让用户通过 JS 代码手动创建DOM元素，肯定是非常麻烦的。\n\n于是，React 官方就提出了一套 JSX 语法规范，能够让我们在 JS 文件中，书写类似于 HTML 那样的代码，快速定义虚拟DOM结构。\n\n### JSX的全称\n\nJSX：JavaScript XML，一种类似于XML的JS扩展语法。也可以理解成：符合 XML 规范的 JS 语法。\n\n需要注意的是，哪怕你在 JS 中写的是 JSX 语法（即JSX这样的标签），但是，JSX内部在运行的时候，并不是直接把 我们的 HTML 标签渲染到页面上；而是先把 类似于HTML 这样的标签代码，转换成 React.createElement 这样的JS代码，再渲染到页面中。\n\n从这一点我们可以看出，JSX是一个对程序员友好的语法糖。\n\n**JSX语法的本质**：以 React.createElement 的形式来实现的，并没有直接把 用户写的 HTML代码，渲染到页面上。\n\n### babel转换工具\n\n如果要直接使用 JSX 语法，需要先安装相关的 语法转换工具：\n\n```\n\t运行 cnpm i babel-preset-react -D\n```\n\n这个babel包的作用是：将 JSX语法 转换为 JS语法。\n\n安装完成后，就可以开始使用JSX语法了。\n\n完整代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n    <div id=\"app\"></div>\n\n    <!-- 注意，这一行的 type 是写 \"text/babel\"，而不是 \"text/javascript\" -->\n    <script type=\"text/babel\">\n      //页面中的真实容器元素\n      var containDiv = document.getElementById(\"app\");\n\n      //1、使用JSX语法 创建虚拟DOM对象\n      var vDom = (\n        <div>\n          Hello, React!\n          <h2>这是标题</h2>\n        </div>\n      );\n\n      //2、渲染虚拟DOM对象（将虚拟DOM对象渲染到页面元素中）\n      ReactDOM.render(vDom, containDiv); // 参数1：虚拟DOM对象；参数2：页面中的容器\n    </script>\n  </body>\n</html>\n\n```\n\n\n## JSX的基本语法\n\n（1）在 JSX内部 写 JS代码：如果要在 JSX 语法内部，书写 JS 代码，那么，所有的JS代码必须写到 `{}` 的内部。在{}内部，可以写任何符合JS规范的代码。\n\n例如：\n\n```javascript\n\tvar myTitle = '这是使用变量定义的 tilte 值'\n\n\t// 使用JSX语法 创建虚拟DOM对象\n\tvar vDom = (\n\t<div>\n\t  Hello, React!\n\t  <h2 title={myTitle + 'vae'}>这是标题</h2>\n\t</div>\n\t);\n```\n\n（2）当编译引擎在编译JSX代码的时候，如果遇到了`<`，会把它当作 HTML代码 去编译；如果遇到了 `{}`， 会把方括号里面的代码当作 普通JS代码 去编译。\n\n（3）在JSX中，如果要为元素添加`class`属性，则必须写成`className`，因为 `class`在ES6中是一个关键字；和`class`类似，label标签的 `for` 属性需要替换为 `htmlFor`。\n\n代码举例：\n\n```html\n  // 使用JSX语法 创建虚拟DOM对象\n  var vDom = (\n    <div>\n      Hello, React!\n      <p className=\"qianguyihao\">千古壹号</p>\n      <label htmlFor=\"\" />\n    </div>\n  );\n```\n\n（4）在JSX创建DOM的时候，所有的节点，必须有唯一的根元素进行包裹。\n\n（5）如果要写注释，注释必须放到 {} 内部。例如：\n\n```javascript\n\t// 使用JSX语法 创建虚拟DOM对象\n\tvar vDom = (\n\t// 这一行是注释\n\t<div>\n\t  Hello, React!\n\t  <p className=\"qianguyihao\">千古壹号</p>\n\t  {/*这一行也是注释 */}\n\t</div>\n\t);\n```\n\n最后，再举个例子：\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n    <div id=\"app\"></div>\n\n    <!-- 注意，这一行的 type 是写 \"text/babel\"，而不是 \"text/javascript\" -->\n    <script type=\"text/babel\">\n      //页面中的真实容器元素\n      var containDiv = document.getElementById(\"app\");\n\n      var arr = []\n      for (var i = 0; i < 6; i++) {\n        var p = <p className=\"myp\" key={i}>这个是p标签</p>  // 注意这个地方的写法： key = {i}\n        arr.push(p)\n      }\n\n      //1、使用JSX语法 创建虚拟DOM对象\n      var vDom = (\n        <div>\n          Hello, React!\n            {arr}\n        </div>\n      );\n\n      //2、渲染虚拟DOM对象\n      ReactDOM.render(vDom, containDiv); // 参数1：虚拟DOM对象；参数2：页面中的容器\n    </script>\n  </body>\n</html>\n```\n\n运行结果：\n\n20190210_1501.png\n\n\n## 创建组件的第一种方式\n\n### 创建组件\n\n在React中，构造函数就是一个最基本的组件。\n\n如果想要把组件放到页面中，可以把**构造函数的名称**当作**组件的名称**，以 HTML标签形式引入页面中即可。\n\n举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n    <div id=\"app\"></div>\n\n    <!-- 注意，这一行的 type 是写 \"text/babel\"，而不是 \"text/javascript\" -->\n    <script type=\"text/babel\">\n      // 这个构造函数，就相当于一个 组件\n      function Hello() {\n        return (\n          <div>\n            <h3>这是 Hello组件 中定义的元素</h3>\n          </div>\n        );\n      }\n\n      ReactDOM.render(\n        <div>\n          <Hello> </Hello>\n        </div>,\n        document.getElementById(\"app\")\n      );\n    </script>\n  </body>\n</html>\n\n```\n\n运行结果：\n\n20190210_1510.png\n\n**需要注意的是**：\n\nReact在解析所有标签的时候，是以标签的首字母来区分的：如果标签的首字母是小写，就按照普通的 HTML 标签来解析；如果首字母是大写，则按照 **组件**的形式来解析。\n\n比如上方代码中，如果把大写的 `Hello` 改成小写的 `hello`，运行会报错，无法看到预期的结果。\n\n**结论**：组件的首字母必须大写。\n\n### 父组件传值给子组件\n\n代码举例：\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n    <div id=\"app\"></div>\n\n    <!-- 注意，这一行的 type 是写 \"text/babel\"，而不是 \"text/javascript\" -->\n    <script type=\"text/babel\">\n      // 父组件中的数据\n      var person = {\n        name: \"qianguyihao\",\n        age: 27,\n        gender: \"男\",\n        address: \"深圳\"\n      };\n\n\t  // 在子组件中，如果想要使用外部传递过来的数据，必须显示的在 构造函数参数列表中，定义 props 属性来接收\n\t  // 通过 props 得到的任何数据都是只读的，不能重新赋值\n      function Hello(props) {\n        return (\n          <div>\n            <h3>这是 Hello子组件 中定义的元素： {props.name}</h3>\n          </div>\n        );\n      }\n\n      ReactDOM.render(\n      \t<!-- 注意：这里的 ...Obj 语法，是 ES6中的属性扩散，表示：把这个对象上的所有属性，展开了，放到这个位置 -->\n        <div>\n          <Hello {...person}> </Hello>\n        </div>,\n        document.getElementById(\"app\")\n      );\n    </script>\n  </body>\n</html>\n\n```\n\n上方代码中，我们是想把整个person对象传递给子组件，所以采用了`...Obj 语法`语法。传递给子组件后，子组件获取的数据仅仅只是可读的。\n\n## class 关键字的介绍\n\n面向对象语言的三个特性：封装、继承、多态。多态 和 接口、虚拟方法有关。\n\n### class的基本用法：使用class创建对象\n\nmyclass.js:\n\n```javascript\n// 以前学习的：使用构造函数创建对象\nfunction Person(name, age) {\n  this.name = name;\n  this.age = age;\n}\nPerson.prototype.say = function() {\n  console.log(\"呵呵哒\");\n};\nPerson.info = 123;\n\nvar p1 = new Person(\"zs\", 20);\n\n\n// 本次需要学习的：class 后面跟上类名，类名后面，不需要加 () ，直接上 {}\nclass Per {\n  // 在每个class类内部，都有一个 constructor 构造器， 如果没有显示定义 构造器，那么类内部默认都有个看不见的 constructor\n  // constructor 的作用，就好比 咱们之前的 function Person(){ }\n  // 每当，使用 new 关键字创建 class 类实例的时候，必然会优先调用 constructor 构造器\n  // constructor(){}\n  constructor(name, age) {\n    this.name = name;\n    this.age = age;\n  }\n\n  // 这是实例方法，必须通过 new 出来的对象调用\n  say() {\n    console.log(\"ok a \");\n  }\n\n  static info = 123;\n\n  static sayHello() {\n    console.log(\"这是静态方法\");\n  }\n}\n\nvar p2 = new Per(\"壹号\", 26);\nconsole.log(p2);\nconsole.log(Per.info);\nconsole.log(Per.sayHello());\n\n```\n\n\n### 使用 class 实现 JS 中的继承\n\nmyclass2.js：\n\n```javascript\nclass Person {\n  constructor(name, age) {\n    console.log(3);\n    this.name = name;\n    this.age = age;\n  }\n\n  say() {\n    console.log(\"这是 Person中的 say 方法\");\n  }\n  static info = 123;\n}\n\n// 使用 extends 实现继承，extends的前面的是子类，后面的是父类\nclass Chinese extends Person {\n  constructor(name, age, color, language) {\n    console.log(1);\n    // 注意： 当使用 extends 关键字实现了继承， 子类的 constructor 构造函数中，必须显示调用 super() 方法，这个 super 表示父类中 constructor 的引用\n    super(name, age);\n    this.color = color;\n    this.language = language;\n    console.log(2);\n  }\n}\n\nvar c1 = new Chinese(\"张三\", 22, \"yellow\", \"汉语\");\nconsole.log(c1);\n// 父类中任何东西，子类都能继承到\nc1.say();\n\n```\n\n注意上方 `constructor`处的注释：当使用 extends 关键字实现了继承， 子类的 constructor 构造函数中，必须显示调用 super() 方法，这个 super 表示父类中 constructor 的引用。也就是说，在子类当中，要么不写 constructor，如果写了 constructor，就一定要把 `super()`也加上。\n\n为啥我们要引入 `class`这个功能？就是因为， `class`里，永远都存在着一个 constructor。我们可以利用 `constructor`做很多事情。\n\n## 创建组件的第二种方式：使用 class 关键字\n\n\n使用 class 创建的类，通过 extends 关键字，继承 `React.Component` 之后，这个类，就是一个组件的模板了。如果想要引用这个组件，可以把类的名称以**标签的形式**，导入到 JSX 中使用。\n\n在 class 实现的组件内部，必须定义一个 render 函数。在 render 函数中，还必须 return 一个东西，如果没有什么需要被return 的，则需要 return null。\n\n\n**代码举例**：\n\nindex.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n    <div id=\"app\"></div>\n\n    <!-- 注意，这一行的 type 是写 \"text/babel\"，而不是 \"text/javascript\" -->\n    <script type=\"text/babel\">\n\n      // 使用 class 创建的类，通过 extends 关键字，继承 `React.Component` 之后，这个类，就是一个组件的模板了。\n      // 如果想要引用这个组件，可以把类的名称以**标签的形式**，导入到 JSX 中使用。\n      class Hello2 extends React.Component {\n        // 在 class 实现的组件内部，必须定义一个 render 函数\n        render() {\n          // 在 render 函数中，还必须 return 一个东西，如果没有什么需要被return 的，则需要 return null\n          return (\n            <div>\n              <h3>这是使用 class 类创建的组件 </h3>\n            </div>\n          );\n        }\n      }\n\n      ReactDOM.render(\n        <div>\n          <Hello2> </Hello2>\n        </div>,\n        document.getElementById(\"app\")\n      );\n    </script>\n  </body>\n</html>\n\n```\n\n### 父组件传值给子组件\n\n代码举例：\n\nindex.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta />\n    <meta />\n    <meta />\n    <title>Document</title>\n  </head>\n  <body>\n    <!-- 引入React相关的js库 -->\n    <script type=\"text/javascript\" src=\"./libs/react.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/react-dom.js\"></script>\n    <script type=\"text/javascript\" src=\"./libs/babel.min.js\"></script>\n\n    <div id=\"app\"></div>\n\n    <!-- 注意，这一行的 type 是写 \"text/babel\"，而不是 \"text/javascript\" -->\n    <script type=\"text/babel\">\n      // 使用 class 创建的类，通过 extends 关键字，继承 `React.Component` 之后，这个类，就是一个组件的模板了。\n      // 如果想要引用这个组件，可以把类的名称以**标签的形式**，导入到 JSX 中使用。\n      class Hello2 extends React.Component {\n        constructor(props) {\n          super(props);\n          console.log(props.name);\n\n          // 注意：`this.state` 是固定写法，表示当前组件实例的私有数据对象，就好比 vue 中，组件实例身上的 data(){ return {} } 函数\n          // 如果想要使用 组件中 state 上的数据，直接通过 this.state.*** 来访问即可\n          this.state = {\n            msg: \"这是 Hello2 组件的私有msg数据\",\n            info: \"永不止步\"\n          };\n        }\n        // 在 class 实现的组件内部，必须定义一个 render 函数\n        render() {\n          // 在 render 函数中，还必须 return 一个东西，如果没有什么需要被return 的，则需要 return null\n          return (\n            <div>\n              <h3>这是使用 class 类创建的组件 </h3>\n            </div>\n          );\n        }\n      }\n\n      ReactDOM.render(\n        <div>\n          <Hello2 name=\"qianguyihao\"> </Hello2>\n        </div>,\n        document.getElementById(\"app\")\n      );\n    </script>\n  </body>\n</html>\n\n```\n\n## 方式一和方式二的对比\n\n上面的内容里，我们使用了两种方式创建组件。这两种方式，有着本质的区别，我们来对比一下。\n\n**对比**：\n\n- **方式一**：通过 function构造函数 创建组件。内部没有 state 私有数据，只有 一个 props 来接收外界传递过来的数据。\n\n- **方式二**：通过 class 创建子组件。内部除了有 this.props 这个只读属性之外，还有一个专门用于 存放自己私有数据的 this.state 属性，这个 state 是可读可写的。\n\n基于上面的区别，我们可以为这两种创建组件的方式下定义： 使用 function 创建的组件，叫做【无状态组件】；使用 class 创建的组件，叫做【有状态组件】。\n\n**本质区别**：\n\n有状态组件和无状态组件，最本质的区别，就是有无 state 属性。同时， class 创建的组件，有自己的生命周期函数，但是，function 创建的 组件，没有自己的生命周期函数。\n\n**什么时候使用 有状态组件，什么时候使用无状态组件**：\n\n- （1）如果一个组件需要存放自己的私有数据，或者需要在组件的不同阶段执行不同的业务逻辑，此时，非常适合用 class 创建出来的有状态组件。\n\n- （2）如果一个组件，只需要根据外界传递过来的 props，渲染固定的页面结构即可的话，此时，非常适合使用 function 创建出来的无状态组件。（使用无状态组件的小小好处： 由于剔除了组件的生命周期，所以，运行速度会相对快一点点）。\n\n\n\n"
  },
  {
    "path": "13-React基础/03-React组件（一）：生命周期.md",
    "content": "---\ntitle: 03-React组件（一）：生命周期\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 组件的生命周期\n\n在组件创建、到加载到页面上运行、以及组件被销毁的过程中，总是伴随着各种各样的事件，这些在组件特定时期，触发的事件统称为组件的生命周期。\n\n## 生命周期的阶段\n\n组件生命周期分为三个阶段，下面分别来讲解。\n\n### 1、组件创建阶段\n\n> 组件创建阶段的生命周期函数，有一个显著的特点：创建阶段的生命周期函数，在组件的一辈子中，只执行一次。\n\n\n- getDefaultProps\n\n初始化 props 属性默认值。\n\n- getInitialState\n\n初始化组件的私有数据。因为 state 是定义在组件的 constructor 构造器当中的，只要new 了 class类，必然会调用 constructor构造器。\n\n- componentWillMount()\n\n组件将要被挂载。此时还没有开始渲染虚拟DOM。\n\n在这个阶段，不能去操作DOM元素，但可以操作属性、状态、function。相当于 Vue 中的Create()函数。\n\n- render()\n\n第一次开始渲染真正的虚拟DOM。当render执行完，内存中就有了完整的虚拟DOM了。\n\n意思是，此时，虚拟DOM在内存中创建好了，但是还没有挂在到页面上。\n\n在这个函数内部，不能去操作DOM元素，**因为还没return之前，虚拟DOM还没有创建**；当return执行完毕后，虚拟DOM就创建好了，但是还没有挂在到页面上。\n\n- **componentDidMount()**\n\n**当组件（虚拟DOM）挂载到页面之后，会进入这个生命周期函数**。\n\n只要进入到这个生命周期函数，则必然说明，页面上已经有可见的DOM元素了。此时，组件已经显示到了页面上，state上的数据、内存中的虚拟DOM、以及浏览器中的页面，已经完全保持一致了。\n\n当这个方法执行完，组件就进入都了 运行中 的状态。所以说，componentDidMount 是创建阶段的最后一个函数。\n\n在这个函数中，我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。如果我们想操作DOM元素，最早只能在 componentDidMount 中进行。相当于 Vue 中的 mounted() 函数\n\n### 2、组件运行阶段\n\n>有一个显著的特点，根据组件的state和props的改变，有选择性的触发0次或多次。\n\n- componentWillReceiveProps()\n\n组件将要接收新属性。只有当父组件中，通过某些事件，重新修改了 传递给 子组件的 props 数据之后，才会触发这个钩子函数。\n\n- shouldComponentUpdate()\n\n判断组件是否需要被更新。此时，组件尚未被更新，但是，state 和 props 肯定是最新的。\n\n- componentWillUpdate()\n\n组件将要被更新。此时，组件还没有被更新，在进入到这个生命周期函数的时候，内存中的虚拟DOM还是旧的，页面上的 DOM 元素也是旧的。（也就是说，此时操作的是旧的 DOM元素）\n\n- render\n\n此时，又要根据最新的 state 和 props，重新渲染一棵内存中的 虚拟DOM树。当 render 调用完毕，内存中的旧DOM树，已经被新DOM树替换了！此时，虚拟DOM树已经和组件的 state 保持一致了，都是最新的；但是页面还是旧的。\n\n- componentDidUpdate\n\n此时，组件完成更新，页面被重新渲染。此时，state、虚拟DOM 和 页面已经完全保持同步。\n\n### 3、组件销毁阶段\n\n一辈子只执行一次。\n\n- componentWillUnmount: 组件将要被卸载。此时组件还可以正常使用。\n\nReact 生命周期的截图如下：\n\n20190212_1745.jpg\n\n生命周期对比：\n\n- [vue中的生命周期图](https://cn.vuejs.org/v2/guide/instance.html#生命周期图示)\n\n- [React Native 中组件的生命周期](http://www.race604.com/react-native-component-lifecycle/)\n\n## 组件生命周期的执行顺序\n\n**1、Mounting**：\n\n- constructor()\n\n- componentWillMount()\n\n- render()\n\n- componentDidMount()\n\n**2、Updating**：\n\n- componentWillReceiveProps(nextProps)：接收父组件传递过来的属性\n\n- shouldComponentUpdate(nextProps, nextState)：一旦调用 setState，就会触发这个方法。方法默认 return true；如果 return false，后续的方法就不会走了。\n\n- componentWillUpdate(nextProps, nextState)\n\n- render()\n\n- componentDidUpdate(prevProps, prevState)\n\n**3、Unmounting**：\n\n - componentWillUnmount()\n\n\n"
  },
  {
    "path": "13-React基础/04-React组件（二）：常见属性和函数.md",
    "content": "---\ntitle: 04-React组件（二）：常见属性和函数\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## defaultProps 和 prop-types\n\n### 使用 defaultProps 设置组件的默认值\n\nReact 中，使用静态的 `defaultProps` 属性，来设置组件的默认属性值。\n\n格式举例：\n\n```javascript\n  // 在 React 中，使用静态的 defaultProps 属性，来设置组件的默认属性值\n  static defaultProps = {\n    initcount: 0 // 如果外界没有传递 initcount，那么，自己初始化一个数值（比如0）\n  };\n\n```\n\n\n### 使用prop-types进行props数据类型的校验\n\n在组件中，可以通过 `prop-types` 把外界传递过来的属性，做类型校验。如果类型不匹配，控制台会弹出告警。\n\n注意：如果要为 传递过来的属性做类型校验，必须安装 React 提供的 第三方包，叫做 `prop-types`。\n\n格式举例：\n\n```javascript\n  static propTypes = {\n    initcount: ReactTypes.number // 使用 prop-types 包，来定义 initcount 为 number 类型\n  };\n```\n\n下方代码中，在引用组件的时候，如果类型不匹配：\n\n```javascript\n// 使用 render 函数渲染 虚拟DOM\nReactDOM.render(\n  <div>\n    {/* 规定，每个用户在使用 组件的时候，必须传递一个 默认的 数值，作为 组件初始化的 数据 */}\n    <Counter initcount=\"我是string类型\"></Counter>\n  </div>,\n  document.getElementById(\"app\")\n);\n\n```\n\n控制台告警如下：\n\n20190212_2130.png\n\n\n### 代码举例\n\n我们把 `defaultProps` 和 `prop-types` 来举个例子。\n\n（1）index.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n\n<body>\n  <!-- 容器，通过 React 渲染得到的 虚拟DOM，会呈现到这个位置 -->\n  <div id=\"app\"></div>\n</body>\n\n</html>\n```\n\n（2）main.js:\n\n```js\n// JS打包入口文件\n// 1. 导入包\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\n\n// 导入计数器组件\nimport Counter from \"./components/Counter.jsx\";\n\n// 使用 render 函数渲染 虚拟DOM\nReactDOM.render(\n  <div>\n    {/* 规定，每个用户在使用 组件的时候，必须传递一个 默认的 数值，作为 组件初始化的 数据 */}\n    <Counter initcount={0}></Counter>\n  </div>,\n  document.getElementById(\"app\")\n);\n\n```\n\n\n（3）/components/Counter.jsx：\n\n\n```javascript\nimport React from \"react\";\n// 注意： prop-types 包中职能跟单一，只提供了 一些常见的 数据类型，用于做类型校验\nimport ReactTypes from \"prop-types\";\n\nexport default class Counter extends React.Component {\n  constructor(props) {\n    super(props);\n\n    // 初始化组件，保存的是组件的私有数据\n    this.state = {\n      msg: \"ok\",\n      count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话，就把 count 值改成了可读可写的 state 属性。因此，以后就能实现“点击 按钮 ，count 值 + 1”的需求了\n    };\n  }\n\n  // 在 React 中，使用静态的 defaultProps 属性，来设置组件的默认属性值\n  static defaultProps = {\n    initcount: 0 // 如果外界没有传递 initcount，那么，自己初始化一个 数值，为0\n  };\n\n  render() {\n    return (\n      <div>\n        <div>\n          <h3>这是 Counter 计数器组件 </h3>\n          <p>当前的计数是：{this.state.count}</p>\n        </div>\n      </div>\n    );\n    // 当 return 执行完毕后， 虚拟DOM创建好了，但是，还没有挂载到真正的页面中\n  }\n}\n\n```\n\n\n运行效果：\n\n20190212_2100.png\n\n\n\n## 事件绑定\n\n案例：点击按钮后，计数器 +1。\n\n### 原生js做事件绑定\n\n代码举例：\n\n（1）index.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n\n<body>\n  <!-- 容器，通过 React 渲染得到的 虚拟DOM，会呈现到这个位置 -->\n  <div id=\"app\"></div>\n</body>\n\n</html>\n```\n\n（2）main.js:\n\n```js\n// JS打包入口文件\n// 1. 导入包\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\n\n// 导入计数器组件\nimport Counter from \"./components/Counter.jsx\";\n\n// 使用 render 函数渲染 虚拟DOM\nReactDOM.render(\n  <div>\n    {/* 规定，每个用户在使用 组件的时候，必须传递一个 默认的 数值，作为 组件初始化的 数据 */}\n    <Counter initcount={0}></Counter>\n  </div>,\n  document.getElementById(\"app\")\n);\n\n```\n\n\n（3）/components/Counter.jsx：\n\n\n```java\nimport React from \"react\";\n// 注意： prop-types 包的职能跟单一，只提供了 一些常见的 数据类型，用于做类型校验\nimport ReactTypes from \"prop-types\";\n\nexport default class Counter extends React.Component {\n  constructor(props) {\n    super(props);\n\n    // 初始化组件，保存的是组件的私有数据\n    this.state = {\n      msg: \"ok\",\n      count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话，就把 count 值改成了可读可写的 state 属性。因此，以后就能实现“点击 按钮 ，count 值 + 1”的需求了\n    };\n  }\n\n  // 在 React 中，使用静态的 defaultProps 属性，来设置组件的默认属性值\n  static defaultProps = {\n    initcount: 0 // 如果外界没有传递 initcount，那么，自己初始化一个数值（比如0）\n  };\n\n  // 这是创建一个 静态的 propTypes 对象，在这个对象中，可以把 外界传递过来的属性，做类型校验\n  static propTypes = {\n    initcount: ReactTypes.number // 使用 prop-types 包，来定义 initcount 为 number 类型\n  };\n\n  render() {\n    return (\n      <div>\n        <div>\n          <h3>这是 Counter 计数器组件 </h3>\n          <input type=\"button\" value=\"+1\" id=\"btn\" />\n          <p>当前的计数是：{this.state.count}</p>\n        </div>\n      </div>\n    );\n    // 当 return 执行完毕后， 虚拟DOM创建好了，但是，还没有挂载到真正的页面中\n  }\n\n  // 当组件挂载到页面上之后，会进入这个生命周期函数，只要进入这个生命周期函数了，必然说明，页面上，已经有可见的DOM元素了\n  componentDidMount() {\n    // 在这个函数中，我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。\n    // 也就是说，如果我们想操作DOM元素，最早，只能在 componentDidMount 中进行。\n    document.getElementById(\"btn\").onclick = () => {\n      this.setState({\n        count: this.state.count + 1\n      });\n    };\n  }\n}\n\n```\n\n### 使用 React 提供的方法，做事件绑定\n\n代码举例：\n\n（1）index.html和 （2）main.js 的代码不变，和上一小段中的代码一致。\n\n（3）/components/Counter.jsx：\n\n```java\nimport React from \"react\";\n// 注意： prop-types 包的职能跟单一，只提供了 一些常见的 数据类型，用于做类型校验\nimport ReactTypes from \"prop-types\";\n\nexport default class Counter extends React.Component {\n  constructor(props) {\n    super(props);\n\n    // 初始化组件，保存的是组件的私有数据\n    this.state = {\n      msg: \"ok\",\n      count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话，就把 count 值改成了可读可写的 state 属性。因此，以后就能实现“点击 按钮 ，count 值 + 1”的需求了\n    };\n  }\n\n  // 在 React 中，使用静态的 defaultProps 属性，来设置组件的默认属性值\n  static defaultProps = {\n    initcount: 0 // 如果外界没有传递 initcount，那么，自己初始化一个数值（比如0）\n  };\n\n  // 这是创建一个 静态的 propTypes 对象，在这个对象中，可以把 外界传递过来的属性，做类型校验\n  static propTypes = {\n    initcount: ReactTypes.number // 使用 prop-types 包，来定义 initcount 为 number 类型\n  };\n\n  render() {\n    return (\n      <div>\n        <div>\n          <h3>这是 Counter 计数器组件 </h3>\n          {/* 这里的 this 指向的是 Counter 组件的实例  */}\n          <input type=\"button\" value=\"+1\" id=\"btn\" onClick={this.myMethod} />\n          <p>当前的计数是：{this.state.count}</p>\n        </div>\n      </div>\n    );\n    // 当 return 执行完毕后， 虚拟DOM创建好了，但是，还没有挂载到真正的页面中\n  }\n\n  // 点击事件的方法定义\n  myMethod = () => {\n    // 修改组件的state里面的值\n    this.setState({\n      count: this.state.count + 1\n    });\n  };\n}\n\n```\n\n\n## 生命周期函数：shouldComponentUpdate()\n\n在 shouldComponentUpdate() 函数中，必须要求返回一个**布尔值**。\n\n**需要注意的是**：如果返回的值是 false，则不会继续执行后续的生命周期函数，而是直接退回到了 运行中 的状态。因为此时，**后续的 render 函数并没有被调用**，因此页面不会被更新，但是组件的 state 状态，却被修改了。这种情况，我们也可以这样理解：如果返回值为 false，此时只是更新了 state 里面的数值，但是并没有渲染到 DOM节点上。\n\n利用上面这个特性，我们可以来举个例子。\n\n**举例**：实现 Counter 计数器只在偶数情况下更新。\n\n实现思路：在 shouldComponentUpdate() 函数中，如果 state 中 的count 的值为奇数，就 return false；否则就 return true。\n\n代码实现：（我们在上面的`Counter.jsx`代码基础之上，做添加）\n\n\n```javascript\n  // 判断组件是否需要更新\n  shouldComponentUpdate(nextProps, nextState) {\n\n    // 经过打印测试发现：在 shouldComponentUpdate 中，通过 this.state.count 拿到的值，是上一次的旧数据，并不是当前最新的；\n    // 解决办法：通过 shouldComponentUpdate 函数的第二个参数 nextState，可以拿到 最新的 state 数据。\n\n    console.log(this.state.count + \" ---- \" + nextState.count);\n\n    // 需求： 如果 state 中的 count 值是偶数，则 更新页面；如果 count 值 是奇数，则不更新页面。最终实现的的页面效果：2，4，6，8，10，12....\n    // return this.state.count % 2 === 0 ? true : false\n    return nextState.count % 2 === 0 ? true : false;\n  }\n```\n\n上面这部分的代码，和 render() 方法是并列的。我们需要注意里面的注释，关注 nextState 参数的用法。\n\n\n## 在js代码中获取html标签的属性\n\n比如说，如果想获取 html标签的 innerHTML 属性，做法如下：\n\n通过原生 js 获取：\n\n```javascript\n\tdocument.getElementById('myh3').innerHTML\n```\n\n也可以通过 React 提供的 `refs` 获取：\n\n\n```javascript\n\tthis.refs.h3.innerHTML\n```\n\n代码举例：\n\n（3）/components/Counter.jsx：\n\n\n```java\nimport React from \"react\";\n// 注意： prop-types 包的职能跟单一，只提供了 一些常见的 数据类型，用于做类型校验\nimport ReactTypes from \"prop-types\";\n\nexport default class Counter extends React.Component {\n  constructor(props) {\n    super(props);\n\n    // 初始化组件，保存的是组件的私有数据\n    this.state = {\n      msg: \"ok\",\n      count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话，就把 count 值改成了可读可写的 state 属性。因此，以后就能实现“点击 按钮 ，count 值 + 1”的需求了\n    };\n  }\n\n  // 在 React 中，使用静态的 defaultProps 属性，来设置组件的默认属性值\n  static defaultProps = {\n    initcount: 0 // 如果外界没有传递 initcount，那么，自己初始化一个数值（比如0）\n  };\n\n  // 这是创建一个 静态的 propTypes 对象，在这个对象中，可以把 外界传递过来的属性，做类型校验\n  static propTypes = {\n    initcount: ReactTypes.number // 使用 prop-types 包，来定义 initcount 为 number 类型\n  };\n\n  render() {\n    return (\n      <div>\n        <div>\n          <h3>这是 Counter 计数器组件 </h3>\n          {/* 这里的 this 指向的是 Counter 组件的实例  */}\n          <input type=\"button\" value=\"+1\" id=\"btn\" onClick={this.myMethod} />\n          <h3 id=\"myh3\" ref=\"mymyh3\">\n            当前的计数是：{this.state.count}\n          </h3>\n        </div>\n      </div>\n    );\n    // 当 return 执行完毕后， 虚拟DOM创建好了，但是，还没有挂载到真正的页面中\n  }\n\n  // 点击事件的方法定义\n  myMethod = () => {\n    // 修改组件的state里面的值\n    this.setState({\n      count: this.state.count + 1\n    });\n  };\n\n  // 判断组件是否需要更新\n  shouldComponentUpdate(nextProps, nextState) {\n    // 需求： 如果 state 中的 count 值是偶数，则 更新页面；如果 count 值 是奇数，则不更新页面。最终实现的的页面效果：2，4，6，8，10，12....\n\n    // 经过打印测试发现：在 shouldComponentUpdate 中，通过 this.state.count 拿到的值，是上一次的旧数据，并不是当前最新的；\n    // 解决办法：通过 shouldComponentUpdate 函数的第二个参数 nextState，可以拿到 最新的 state 数据。\n\n    console.log(this.state.count + \" ---- \" + nextState.count);\n    // return this.state.count % 2 === 0 ? true : false\n    // return nextState.count % 2 === 0 ? true : false;\n    return true;\n  }\n\n  // 组件将要更新。此时尚未更新，在进入这个 生命周期函数的时候，内存中的虚拟DOM是旧的，页面上的 DOM 元素 也是旧的\n  componentWillUpdate() {\n    // 经过打印分析发现：此时页面上的 DOM 节点，都是旧的，应该慎重操作，因为你可能操作的是旧DOM\n    // console.log(document.getElementById('myh3').innerHTML)\n    console.log(this.refs.mymyh3.innerHTML);\n  }\n\n  // 组件完成了更新。此时，state 中的数据、虚拟DOM、页面上的DOM，都是最新的，此时，你可以放心大胆的去操作页面了\n  componentDidUpdate() {\n    console.log(this.refs.mymyh3.innerHTML);\n  }\n}\n\n```\n\n\n上方代码中，componentWillUpdate() 和 componentDidUpdate() 方法里的代码，就是我们这一段要举的例子。\n\n需要注意的是，`<h3 id=\"myh3\" ref=\"mymyh3\">`这部分代码中，属性名只能小写，不能大写。\n\n工程文件：\n\n- [2019-02-12-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-12-ReactDemo.zip)\n\n\n## 生命周期函数：componentWillReceiveProps()\n\n\n当子组件第一次被渲染到页面上的时候，不会触发这个 函数。\n\n只有当父组件中，通过 某些 事件，重新修改了 传递给 子组件的 props 数据之后，才会触发 componentWillReceiveProps。\n\n代码举例：\n\n（1）index.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n\n<body>\n  <!-- 容器，通过 React 渲染得到的 虚拟DOM，会呈现到这个位置 -->\n  <div id=\"app\"></div>\n</body>\n\n</html>\n```\n\n\n（2）main.js:（引入组件）\n\n\n```javascript\n// JS打包入口文件\n// 1. 导入包\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\n\nimport MyParent from \"./components/TestReceiveProps.jsx\";\n\n// 使用 render 函数渲染 虚拟DOM\nReactDOM.render(\n  <div>\n    <MyParent></MyParent>\n  </div>,\n  document.getElementById(\"app\")\n);\n\n```\n\n\n（3）TestReceiveProps.jsx：（组件的定义）\n\n```javascript\nimport React from \"react\";\n\n// 父组件\nexport default class Parent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是父组件中的 msg 消息\"\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>这是父组件</h1>\n        <input\n          type=\"button\"\n          value=\"点击修改父组件的 MSG\"\n          onClick={this.changeMsg}\n        />\n        <hr />\n        {/* 在父组件 Parent 中引用子组件 Son */}\n        <Son pmsg={this.state.msg} />\n      </div>\n    );\n  }\n\n  changeMsg = () => {\n    this.setState({\n      msg: \"修改组件的msg为新的值\"\n    });\n  };\n}\n\n// 子组件\nclass Son extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {};\n  }\n\n  render() {\n    return (\n      <div>\n        <h3>这是子组件 --- {this.props.pmsg}</h3>\n      </div>\n    );\n  }\n\n  // 组件将要接收外界传递过来的新的 props 属性值\n  // 当子组件第一次被渲染到页面上的时候，不会触发这个 函数；\n  // 只有当 父组件中，通过 某些 事件，重新修改了 传递给 子组件的 props 数据之后，才会触发 componentWillReceiveProps\n  componentWillReceiveProps(nextProps) {\n    // console.log('被触发了！');\n    // 注意： 在 componentWillReceiveProps 被触发的时候，如果我们使用 this.props 来获取属性值，这个属性值，不是最新的，是上一次的旧属性值\n    // 如果想要获取最新的属性值，需要通过 componentWillReceiveProps 的参数列表来获取\n    console.log(this.props.pmsg + \" ---- \" + nextProps.pmsg);\n  }\n}\n\n```\n\n上方代码中，我们在组件 Parent 中引入了子组件 Son。重点注意 componentWillReceiveProps()函数 的注释部分。\n\n\n\n"
  },
  {
    "path": "13-React基础/05-React中绑定this并给函数传参的几种方式.md",
    "content": "---\ntitle: 05-React中绑定this并给函数传参的几种方式\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 前言\n\n我们先来看下面这段代码：\n\ncomponents/MyComponent.jsx\n\n```javascript\nimport React from \"react\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是 MyComponent 组件 默认的msg\"\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>绑定This并传参</h1>\n        <input type=\"button\" value=\"绑定this并传参\" onClick={this.changeMsg} />\n        <h3>{this.state.msg}</h3>\n      </div>\n    );\n  }\n\n  changeMsg() {\n    // 注意：这里的changeMsg()只是一个普通方法。因此，在触发的时候，这里的 this 是 undefined\n    console.log(this); // 打印结果：undefined\n    this.setState({\n      msg: \"设置 msg 为新的值\"\n    });\n  }\n}\n\n\n```\n\n上面的代码中，点击按钮，执行 changeMsg() 方法，尝试修改 this.state.msg 的值。但是，这个方法执行的时候，是会报错的：\n\n```\nUncaught TypeError: Cannot read property 'setState' of null\n```\n\n而且，打印this的结果也是 undefined。这是为啥呢？因为这里的 this 并不是指向 MyComponent 组件本身。\n\n那如何让 changeMsg() 方法里面的 this，指向MyComponent 组件呢？办法总是有的，比如说，将changeMsg() 修改为箭头函数：\n\n```javascript\n  changeMsg = () => {\n    console.log(this); // 打印结果：MyComponent 组件\n    this.setState({\n      msg: \"设置 msg 为新的值\"\n    });\n  };\n```\n\n那么，除了箭头函数可以 绑定 this，还有没有其他的方式呢？我们接下来讲一讲。\n\n\n## 绑定 this 的方式一：bind()\n\n代码举例：\n\n```javascript\nimport React from \"react\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是 MyComponent 组件 默认的msg\"\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>绑定This并传参</h1>\n        {/* bind 的作用：为前面的函数，修改函数内部的 this 指向。让 函数内部的this，指向 bind 参数列表中的 第一个参数 */}\n        <input\n          type=\"button\"\n          value=\"绑定this并传参\"\n          onClick={this.changeMsg1.bind(this)}\n        />\n        <h3>{this.state.msg}</h3>\n      </div>\n    );\n  }\n\n  changeMsg1() {\n    this.setState({\n      msg: \"设置 msg 为新的值\"\n    });\n  }\n}\n\n```\n\n\n上方代码中，我们为什么用 bind()，而不是用 call/apply 呢？因为 bind() 并不会立即调用，正是我们需要的。\n\n**注意**：bind 中的第一个参数，是用来修改 this 指向的。第一个参数**后面的所有参数**，都将作为函数的参数传递进去。\n\n我们来看看通过 bind() 是怎么传参的。\n\n**通过 bind() 绑定this，并给函数传参**：\n\n\n```javascript\nimport React from \"react\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是 MyComponent 组件 默认的msg\"\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>绑定This并传参</h1>\n        {/* bind 的作用：为前面的函数，修改函数内部的 this 指向。让 函数内部的this，指向 bind 参数列表中的 第一个参数 */}\n        <input type=\"button\" value=\"绑定this并传参\" onClick={this.changeMsg1.bind(this, \"千古啊\", \"壹号啊\")} />\n        <h3>{this.state.msg}</h3>\n      </div>\n    );\n  }\n\n  changeMsg1(arg1, arg2) {\n    this.setState({\n      msg: \"设置 msg 为新的值\" + arg1 + arg2\n    });\n  }\n}\n\n```\n\n\n## 绑定 this 并给函数传参 的方式二：构造函数里设置 bind()\n\n我们知道，构造函数中的 this 本身就是指向组件的实例的，所以，我们可以在这里做一些事情。\n\n代码举例：\n\n```javascript\nimport React from \"react\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是 MyComponent 组件 默认的msg\"\n    };\n\n    // 绑定 this 并给函数传参的方式2: 在构造函数中绑定并传参\n    // 注意：当一个函数调用 bind 改变了this指向后，bind 函数调用的结果，有一个【返回值】，这个值，就是被改变this指向后的函数的引用。\n    // 也就是说： bind 不会修改 原函数的 this 指向，而是改变了 “函数拷贝”的this指向。\n    this.changeMsg2 = this.changeMsg2.bind(this, \"千古恩\", \"壹号恩\");\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>绑定This并传参</h1>\n        <input type=\"button\" value=\"绑定this并传参\" onClick={this.changeMsg2} />\n        <h3>{this.state.msg}</h3>\n      </div>\n    );\n  }\n\n  changeMsg2(arg1, arg2) {\n    this.setState({\n      msg: \"设置 msg 为新的值\" + arg1 + arg2\n    });\n  }\n}\n\n\n```\n\n上方代码中，需要注意的是：当一个函数调用 bind 改变了this指向后，bind 函数调用的结果，有一个【返回值】，这个值，就是被改变this指向后的函数的引用。也就是说： bind 不会修改 原函数的 this 指向，而是改变了 “函数拷贝”的this指向。\n\n\n## 绑定 this 并给函数传参 的方式三：箭头函数【荐】\n\n第三种方式用得最多。\n\n代码举例：\n\n\n```javascript\nimport React from \"react\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是 MyComponent 组件 默认的msg\"\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>绑定This并传参</h1>\n        <input\n          type=\"button\"\n          value=\"绑定this并传参\"\n          onClick={() => {\n            this.changeMsg3(\"千古3\", \"壹号3\");\n          }}\n        />\n        <h3>{this.state.msg}</h3>\n      </div>\n    );\n  }\n\n  changeMsg3 = (arg1, arg2) => {\n    // console.log(this);\n    // 注意：这里的方式，是一个普通方法，因此，在触发的时候，这里的 this 是 undefined\n    this.setState({\n      msg: \"绑定this并传参的方式3：\" + arg1 + arg2\n    });\n  };\n}\n\n\n```\n\n\n\n\n\n"
  },
  {
    "path": "13-React基础/06-React的单向数据绑定.md",
    "content": "---\ntitle: 06-React的单向数据绑定\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 单项数据绑定\n\n\n在 Vue 中，可以通过 v-model 指令来实现双向数据绑定。但是，在 React 中并没有指令的概念，而且 **React 默认不支持 双向数据绑定**。\n\nReact 只支持，把数据从 state 上传输到 页面，但是，无法自动实现数据从 页面 传输到 state 中 进行保存。\n\nReact中，只支持单项数据绑定，不支持双向数据绑定。不信的话，我们来看下面这个例子：\n\n\n```java\nimport React from \"react\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是 MyComponent 组件 默认的msg\"\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <h3>呵呵哒</h3>\n        <input type=\"text\" value={this.state.msg} />\n      </div>\n    );\n  }\n\n}\n\n\n```\n\n上方代码中，我们尝试在 input文本框中读取 state.msg 的值，运行结果中，却弹出了警告：\n\n20190213_2000.png\n\n```\nWarning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.\n```\n\n## 通过onChange方法，实现双向数据绑定\n\n如果针对 表单元素做 value 属性绑定，那么，必须同时为 表单元素 绑定 readOnly, 或者提供 onChange 事件：\n\n- 如果是绑定readOnly，表示这个元素只读，不能被修改。此时，控制台就不会弹出警告了。\n\n- 如果是绑定onChange，表示这个元素的值可以被修改，但是，要自己定义修改的逻辑。\n\n绑定readOnly的举例如下：（表示value中的数据是只读的）\n\n```javascript\n\t<input type=\"text\" value={this.state.msg} readOnly />\n```\n\n**绑定 onChange 的举例如下**：（通过onChange方法，实现双向数据绑定）\n\n(1)index.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n\n<body>\n  <!-- 容器，通过 React 渲染得到的 虚拟DOM，会呈现到这个位置 -->\n  <div id=\"app\"></div>\n</body>\n\n</html>\n\n```\n\n\n（2）main.js:\n\n```javascript\n// JS打包入口文件\n// 1. 导入包\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\n\n// 导入组件\nimport MyComponent from \"./components/MyComponent.jsx\";\n\n// 使用 render 函数渲染 虚拟DOM\nReactDOM.render(\n  <div>\n    <MyComponent></MyComponent>\n  </div>,\n  document.getElementById(\"app\")\n);\n\n```\n\n（3）components/MyComponent.jsx\n\n```javascript\nimport React from \"react\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      msg: \"这是组件 默认的msg\"\n    };\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>呵呵哒</h1>\n        <input\n          type=\"text\" value={this.state.msg} onChange={this.txtChanged} ref=\"txt\" />\n        <h3>{\"实时显示msg中的内容：\" + this.state.msg}</h3>\n      </div>\n    );\n  }\n\n  // 为 文本框 绑定 txtChanged 事件\n  txtChanged = (e) => {\n    // 获取 <input> 文本框中 文本的3种方式：\n    //  方式一：使用 document.getElementById\n\n    //  方式二：使用 ref\n    // console.log(this.refs.txt.value);\n\n    //  方式三：使用 事件对象的 参数 e 来拿\n    // 此时，e.target 就表示触发 这个事件的 事件源对象，得到的是一个原生的JS DOM 对象。在这个案例里，e.target就是指文本框\n    // console.log(e.target.value);\n    this.setState({\n      msg: e.target.value\n    });\n  };\n}\n\n```\n\n\n工程文件：[2019-02-13-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-13-ReactDemo.zip)\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "13-React基础/07-React路由的使用.md",
    "content": "---\ntitle: 07-React路由的使用\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## React路由的使用\n\n使用React路由之前，我们需要先安装 `react-router-dom`这个包。比如：\n\n```\nyarn add react-router-dom\n```\n\n代码举例：\n\n（1）index.html\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n\n<body>\n  <!-- 容器，通过 React 渲染得到的 虚拟DOM，会呈现到这个位置 -->\n  <div id=\"app\"></div>\n</body>\n\n</html>\n\n```\n\n（2）main.js：\n\n```javascript\n// JS打包入口文件\n// 1. 导入包\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\n\nimport App from \"./App.jsx\";\n\n// 使用 render 函数渲染 虚拟DOM\nReactDOM.render(<App />, document.getElementById(\"app\"));\n\n```\n\n（3）app.jsx:\n\n```java\nimport React from \"react\";\n\n// 如果要使用 路由模块，第一步，运行 yarn add react-router-dom\n// 第二步，导入 路由模块\n\n// HashRouter 表示一个路由的跟容器，将来，所有的路由相关的东西，都要包裹在 HashRouter 里面，而且，一个网站中，只需要使用一次 HashRouter 就好了；\n// Route 表示一个路由规则， 在 Route 上，有两个比较重要的属性， path   component\n// Link 表示一个路由的链接 ，就好比 vue 中的 <router-link to=\"\"></router-link>\nimport { HashRouter, Route, Link } from \"react-router-dom\";\n\nimport Home from \"./components/Home.jsx\";\nimport Movie from \"./components/Movie.jsx\";\nimport About from \"./components/About.jsx\";\n\nexport default class App extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {};\n  }\n\n  render() {\n    // 当 使用 HashRouter 把 App 根组件的元素包裹起来之后，网站就已经启用路由了\n    // 在一个 HashRouter 中，只能有唯一的一个根元素\n    // 在一个网站中，只需要使用 唯一的一次 <HashRouter></HashRouter> 即可\n    return (\n      <HashRouter>\n        <div>\n          <h1>这是网站的APP根组件</h1>\n          <hr />\n\n          <Link to=\"/home\">首页</Link>&nbsp;&nbsp;\n          <Link to=\"/movie\">电影</Link>&nbsp;&nbsp;\n          <Link to=\"/about\">关于</Link>\n          <hr />\n\n          {/* Route 创建的标签，就是路由规则，其中 path 表示要匹配的路由，component 表示要展示的组件 */}\n          {/* 在 vue 中有个 router-view 的路由标签，专门用来放置，匹配到的路由组件的，但是，在 react-router 中，并没有类似于这样的标签，而是 ，直接把 Route 标签，当作的 坑（占位符） */}\n          {/* Route 具有两种身份：1. 它是一个路由匹配规则； 2. 它是 一个占位符，表示将来匹配到的组件都放到这个位置 */}\n          <Route path=\"/home\" component={Home} />\n          <hr />\n          <Route path=\"/movie\" component={Movie} />\n          <hr />\n          <Route path=\"/about\" component={About} />\n        </div>\n      </HashRouter>\n    );\n  }\n}\n\n```\n\n（4）ReactDemo/src/components/Home.jsx\n\n```java\nimport React from \"react\";\n\nexport default class Home extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {};\n  }\n\n  render() {\n    return <div>Home组件</div>;\n  }\n}\n\n```\n\n（5）ReactDemo/src/components/Movie.jsx\n\n```java\n\nimport React from \"react\";\n\nexport default class Movie extends React.Component {\n    constructor(props) {\n        super(props);\n        this.state = {};\n      }\n\n  render() {\n    return <div>Movie组件</div>;\n  }\n}\n\n```\n\n（6）ReactDemo/src/components/About.jsx\n\n```java\nimport React from \"react\";\n\nexport default class About extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {};\n  }\n\n  render() {\n    return <div>About组件</div>;\n  }\n}\n\n```\n\n运行结果：\n\n20190214_1000.png\n\n## 匹配路由参数\n\n### 模糊匹配与精准匹配\n\n我们在上面的代码中，进一步修改。假设 Movie 这个组件修改成这种路由匹配方式：\n\n```html\n<Link to=\"/movie/top250\">电影</Link>\n\n<Route path=\"/movie\" component={Movie} />\n\n```\n\n上面这种匹配方式，也是可以成功匹配到的。这是为啥呢？\n\n这是因为：默认情况下，路由中的匹配规则，是**模糊匹配**的。如果 路由可以部分匹配成功，就会展示这个路由对应的组件。\n\n如果想让路由规则，进行**精确匹配**，可以为Route添加 `exact` 属性。比如下面这种写法，因为是开启了精准匹配，所以是匹配不到的：（无法匹配）\n\n```html\n<Link to=\"/movie/top250/20\">电影</Link>\n\n<Route path=\"/movie/\" component={Movie} exact/>\n```\n\n\n另外，如果要匹配参数，可以在匹配规则中，使用 `:` 修饰符，表示这个位置匹配到的是参数。举例如下：（匹配正常）\n\n```html\n<Link to=\"/movie/top250/20\">电影</Link>&nbsp;&nbsp;\n\n<Route path=\"/movie/:type/:id\" component={Movie} exact/>\n```\n\n\n### 获取路由参数\n\n继续修改上面的代码。如果我想在 Movie 组件中显示路由中的参数，怎么做呢？\n\n我们可以通过 `props.match.params`获取路由中的参数。举例做法如下：\n\napp.jsx中的匹配规则如下：\n\n\n```html\n<Link to=\"/movie/top100/5\">电影</Link>&nbsp;&nbsp;\n\n<Route path=\"/movie/:type/:id\" component={Movie} exact/>\n```\n\n\nMoivie 组件的写法如下：\n\n```java\nimport React from \"react\";\n\nexport default class Movie extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      routeParams: props.match.params // 把路由中的参数保存到 state 中\n    };\n  }\n\n  render() {\n    console.log(this);\n    // 如果想要从路由规则中，提取匹配到的参数，进行使用，可以使用 this.props.match.params.*** 来访问\n    return (\n      <div>\n        {/* Movie --- {this.props.match.params.type} --- {this.props.match.params.id} */}\n        Movie --- {this.state.routeParams.type} --- {this.state.routeParams.id}\n      </div>\n    );\n  }\n}\n\n\n```\n\n打印结果如下：\n\n20190214_1030.png\n\n\n工程文件：[2019-02-14-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-14-ReactDemo.zip)\n\n\n\n\n"
  },
  {
    "path": "13-React基础/08-Ant Design的基本使用.md",
    "content": "---\ntitle: 08-Ant Design的基本使用\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## andt 的介绍\n\nAnt Design 是基于 React 实现，开发和服务于企业级后台产品。\n\n### 支持环境\n\n- 现代浏览器和 IE9 及以上（需要 polyfills）。\n\n- 支持服务端渲染。\n\n- [Electron](https://electronjs.org/)\n\n\nElectron（原名为Atom Shell）是GitHub开发的一个开源框架。 它允许使用Node.js（作为后端）和Chromium（作为前端）完成桌面GUI应用程序的开发。\n\n很多客户端软件都是基于 Electron 开发的。比如 VS Code。我们打开 VS Code 菜单栏的 “帮助 --> 切换开发人员工具”，就会看到类似于 chrome的调试工具。\n\n\n### 相关链接\n\n- 官方文档：<https://ant.design/docs/react/introduce-cn>\n\n\n\n\n\n## andt 的使用\n\n\n\n### 环境安装\n\n```\nnpm install antd --save\n```\n\n### 代码示例\n\n我们需要什么组件，就导入该组件即可。\n\n\n（1）index.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n\n<body>\n  <!-- 容器，通过 React 渲染得到的 虚拟DOM，会呈现到这个位置 -->\n  <div id=\"app\"></div>\n</body>\n\n</html>\n```\n\n\n（2）main.js:\n\n```java\n// JS打包入口文件\n// 1. 导入包\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\n\nimport MyComponent from \"./components/MyComponent.jsx\";\n\n// 使用 render 函数渲染 虚拟DOM\nReactDOM.render(<MyComponent></MyComponent>, document.getElementById(\"app\"));\n\n```\n\n\n\n(3)MyComponent.jsx:\n\n```java\nimport React from \"react\";\n\n// 导入 日期选择组件\nimport { DatePicker } from \"antd\";\n\nexport default class MyComponent extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {};\n  }\n\n  render() {\n    return (\n      <div>\n        <h3>在组件中引入 andt</h3>\n\n        <DatePicker />\n      </div>\n    );\n  }\n}\n\n```\n\n\n代码运行效果：\n\n20190217_1500.png\n\n\n## AntD组件\n\n### 表格\n\n\n`pagination`属性可以用来分页。\n\n\n### loading框\n\n需求：在数据显示之前，展示 loading；在数据显示之后，关闭loading。\n\n\n## 相关问题的链接\n\n\n\n\n### AntD pro，跳转到详情页，携带参数\n\n- [ant design列表页，转跳到详情页，携带参数](https://blog.csdn.net/u011613356/article/details/81505883)\n\n- [ant design pro商品页带参数转到详情页](https://blog.csdn.net/ws995339251/article/details/86771701)\n\n\n### AntD pro ，必填项前面，显示星号\n\n- [表单必填项label上的红色*号是怎么出现的](https://github.com/ant-design/ant-design-pro/issues/2044)\n\n### 其他问题\n\n- 面包屑层级显示问题：<https://github.com/ant-design/ant-design-pro/issues/1584>\n\n- from验证input框只能输入数字：<https://blog.csdn.net/zr15829039341/article/details/82745239>\n\n\n"
  },
  {
    "path": "13-React基础/09-AntD框架的upload组件上传图片时遇到的一些坑.md",
    "content": "---\ntitle: 09-AntD框架的upload组件上传图片时遇到的一些坑\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n## 前言\n\n本次做后台管理系统，采用的是 AntD 框架。涉及到图片的上传，用的是AntD的 [upload](https://ant.design/components/upload-cn/) 组件。\n\n前端做文件上传这个功能，是很有技术难度的。既然框架给我们提供好了，那就直接用呗。结果用的时候，发现 upload 组件的很多bug。下面来列举几个。\n\n备注：本文写于2019-03-02，使用的 antd 版本是 3.13.6。\n\n## 使用 AntD 的 upload 组件做图片的上传\n\n因为需要上传多张图片，所以采用的是照片墙的形式。上传成功后的界面如下：\n\n（1）上传中：\n\n![](http://img.smyhvae.com/20190302_1335.png)\n\n（2）上传成功：\n\n![](http://img.smyhvae.com/20190302_1336.png)\n\n（3）图片预览：\n\n![](http://img.smyhvae.com/20190302_1331.png)\n\n按照官方提供的实例，特此整理出项目开发中的完整写法，亲测有效。代码如下：\n\n```javascript\n/* eslint-disable */\n\nimport { Upload, Icon, Modal, Form } from 'antd';\n\nconst FormItem = Form.Item;\n\nclass PicturesWall extends PureComponent {\n  state = {\n    previewVisible: false,\n    previewImage: '',\n    imgList: [],\n  };\n\n\n  handleChange = ({ file, fileList }) => {\n    console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img\n    console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表\n\n    this.setState({\n      imgList: fileList,\n    });\n  };\n\n\n  handleCancel = () => this.setState({ previewVisible: false });\n\n  handlePreview = file => {\n    this.setState({\n      previewImage: file.url || file.thumbUrl,\n      previewVisible: true,\n    });\n  };\n\n\n  // 参考链接：https://www.jianshu.com/p/f356f050b3c9\n  handleBeforeUpload = file => {\n    //限制图片 格式、size、分辨率\n    const isJPG = file.type === 'image/jpeg';\n    const isJPEG = file.type === 'image/jpeg';\n    const isGIF = file.type === 'image/gif';\n    const isPNG = file.type === 'image/png';\n    if (!(isJPG || isJPEG || isGIF || isPNG)) {\n      Modal.error({\n        title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',\n      });\n      return;\n    }\n    const isLt2M = file.size / 1024 / 1024 < 2;\n    if (!isLt2M) {\n      Modal.error({\n        title: '超过2M限制，不允许上传~',\n      });\n      return;\n    }\n    return (isJPG || isJPEG || isGIF || isPNG) && isLt2M && this.checkImageWH(file);\n  };\n\n  //返回一个 promise：检测通过则返回resolve；失败则返回reject，并阻止图片上传\n  checkImageWH(file) {\n    let self = this;\n    return new Promise(function(resolve, reject) {\n      let filereader = new FileReader();\n      filereader.onload = e => {\n        let src = e.target.result;\n        const image = new Image();\n        image.onload = function() {\n          // 获取图片的宽高，并存放到file对象中\n          console.log('file width :' + this.width);\n          console.log('file height :' + this.height);\n          file.width = this.width;\n          file.height = this.height;\n          resolve();\n        };\n        image.onerror = reject;\n        image.src = src;\n      };\n      filereader.readAsDataURL(file);\n    });\n  }\n\n  handleSubmit = e => {\n    const { dispatch, form } = this.props;\n    e.preventDefault();\n    form.validateFieldsAndScroll((err, values) => {// values 是form表单里的参数\n      // 点击按钮后，将表单提交给后台\n      dispatch({\n        type: 'mymodel/submitFormData',\n        payload: values,\n      });\n    });\n  };\n\n  render() {\n    const { previewVisible, previewImage, imgList } = this.state; //  从 state 中拿数据\n    const uploadButton = (\n      <div>\n        <Icon type=\"plus\" />\n        <div className=\"ant-upload-text\">Upload</div>\n      </div>\n    );\n    return (\n      <div className=\"clearfix\">\n        <Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>\n          <FormItem label=\"图片图片\" {...formItemLayout}>\n            {getFieldDecorator('myImg')(\n              <Upload\n                action=\"//jsonplaceholder.typicode.com/posts/\" // 这个是图片上传的接口请求，实际开发中，要替换成你自己的业务接口\n                data={file => ({ // data里存放的是接口的请求参数\n                  param1: myParam1,\n                  param2: myParam2,\n                  photoCotent: file, // file 是当前正在上传的图片\n                  photoWidth: file.height, // 通过  handleBeforeUpload 获取 图片的宽高\n                  photoHeight: file.width,\n                })}\n                listType=\"picture-card\"\n                fileList={this.state.imgList}\n                onPreview={this.handlePreview} // 点击图片缩略图，进行预览\n                beforeUpload={this.handleBeforeUpload} // 上传之前，对图片的格式做校验，并获取图片的宽高\n                onChange={this.handleChange} // 每次上传图片时，都会触发这个方法\n              >\n                {this.state.imgList.length >= 9 ? null : uploadButton}\n              </Upload>\n            )}\n          </FormItem>\n        </Form>\n        <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>\n          <img alt=\"example\" style={{ width: '100%' }} src={previewImage} />\n        </Modal>\n      </div>\n    );\n  }\n}\n\nexport default PicturesWall;\n\n```\n\n## 上传后，点击图片预览，浏览器卡死的问题\n\n依据上方的代码，通过 Antd 的 upload 组件将图片上传成功后，点击图片的缩略图，理应可以在当前页面弹出  Modal，预览图片。但实际的结果是，浏览器一定会卡死。\n\n定位问题发现，原因竟然是：图片上传成功后， upload 会将其转为 base64编码。base64这个字符串太大了，点击图片预览的时候，浏览器在解析一大串字符串，然后就卡死了。详细过程描述如下。\n\n上方代码中，我们可以把 handleChange(file, fileList)方法中的 `file`、以及 `fileList`打印出来看看。 `file`指的是当前正在上传的 单个 img，`fileList`是已上传的全部 img 列表。 当我上传完 两张图片后， 打印结果如下：\n\nfile的打印的结果如下：\n\n```json\n    {\n        \"uid\": \"rc-upload-1551084269812-5\",\n        \"width\": 600,\n        \"height\": 354,\n        \"lastModified\": 1546701318000,\n        \"lastModifiedDate\": \"2019-01-05T15:15:18.000Z\",\n        \"name\": \"e30e7b9680634b2c888c8bb513cc595d.jpg\",\n        \"size\": 31731,\n        \"type\": \"image/jpeg\",\n        \"percent\": 100,\n        \"originFileObj\": {\n            \"uid\": \"rc-upload-1551084269812-5\",\n            \"width\": 600,\n            \"height\": 354\n        },\n        \"status\": \"done\",\n        \"thumbUrl\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z\",\n        \"response\": {\n            \"retCode\": 0,\n            \"imgUrl\": \"http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg\",\n            \"photoid\": 271850\n        }\n    }\n```\n\nfileList 的打印结果：\n\n```json\n[\n    {\n        \"uid\": \"rc-upload-1551084269812-3\",\n        \"width\": 1000,\n        \"height\": 667,\n        \"lastModified\": 1501414799000,\n        \"lastModifiedDate\": \"2017-07-30T11:39:59.000Z\",\n        \"name\": \"29381f30e924b89914e91b33.jpg\",\n        \"size\": 135204,\n        \"type\": \"image/jpeg\",\n        \"percent\": 100,\n        \"originFileObj\": {\n            \"uid\": \"rc-upload-1551084269812-3\",\n            \"width\": 1000,\n            \"height\": 667\n        },\n        \"status\": \"done\",\n        \"thumbUrl\": \"data:image/jpeg;base64,/E3ju1tlaK1fzJOnHQU3LsLV7HO6Zrk11MZJ7luT0A4FZuRagi9quvzQQ4iuEJ7ZpqTG4djDsPFl2Lg733f8C4q+YhQ8zoYfGSqoMmfwo5huLL0HjiyPDSYPvxRdC1XQvxeLrB8fvl/OnoLmL9vrdvvYS3NGFVe2YsASOh71JfQyrqV2mXLHOcccVSIYEnDyZO9XXB9KYH//Z\",\n        \"response\": {\n            \"retCode\": 0,\n            \"msg\": \"success\",\n            \"imgUrl\": \"http://qianguyihao.com/hfwpjouiurewnmbhepr689.jpg\",\n        }\n    },\n    {\n        \"uid\": \"rc-upload-1551084269812-5\",\n        \"width\": 600,\n        \"height\": 354,\n        \"lastModified\": 1546701318000,\n        \"lastModifiedDate\": \"2019-01-05T15:15:18.000Z\",\n        \"name\": \"e30e7b9680634b2c888c8bb513cc595d.jpg\",\n        \"size\": 31731,\n        \"type\": \"image/jpeg\",\n        \"percent\": 100,\n        \"originFileObj\": {\n            \"uid\": \"rc-upload-1551084269812-5\",\n            \"width\": 600,\n            \"height\": 354\n        },\n        \"status\": \"done\",\n        \"thumbUrl\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z\",\n        \"response\": {\n            \"retCode\": 0,\n            \"imgUrl\": \"http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg\",\n            \"photoid\": 271850\n        }\n    }\n]\n```\n\n上方的json数据中，需要做几点解释：\n\n（1）`response` 字段里面的数据，就是请求接口后，后台返回给前端的数据，里面包含了图片的url链接。\n\n（2）`status` 字段里存放的是图片上传的实时状态，包括上传中、上传完成、上传失败。\n\n（3）`thumbUrl`字段里面存放的是图片的base64编码。\n\n这个base64编码非常非常长。当点击图片预览的时候，其实就是加载的 thumbUrl 这个字段里的资源，难怪浏览器会卡死。\n\n**解决办法**：在 handleChange方法里，图片上传成功后，将 thumbUrl 字段里面的 base64 编码改为真实的图片url。代码实现如下：\n\n```javascript\n  handleChange = ({ file, fileList }) => {\n    console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img\n    console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表\n\n\n    // 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。\n    // 图片上传成功后，fileList数组中的 thumbUrl 中保存的是图片的base64字符串，这种情况，导致的问题是：图片上传成功后，点击图片缩略图，浏览器会会卡死。而下面这行代码，可以解决该bug。\n    fileList.forEach(imgItem => {\n      if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {\n        imgItem.thumbUrl = imgItem.response.imgUrl;\n      }\n    });\n\n    this.setState({\n      imgList: fileList,\n    });\n  };\n```\n\n\n## 新需求：编辑现有页面\n\n上面一段的代码中，我们是在新建的页面中，从零开始上传图片。\n\n现在有个新的需求：如何编辑现有的页面呢？也就是说，现有的页面在初始化时，是默认有几张图片的。当我编辑这个页面时，可以对现有的图片做增删，也能增加新的图片。而且要保证：新建页面和编辑现有页面，是共用一套代码。\n\n我看到upload 组件有提供 `defaultFileList` 的属性。我试了下，这个`defaultFileList` 的属性根本没法儿用。\n\n那就只有手动实现了。我的model层代码，是用 redux 写的。整体的实现思路如下：（这个也是在真正在实战中用到的代码）\n\n（1）PicturesWall.js：\n\n```javascript\n/* eslint-disable */\n\nimport { Upload, Icon, Modal, Form } from 'antd';\n\nconst FormItem = Form.Item;\n\nclass PicturesWall extends PureComponent {\n  state = {\n    previewVisible: false,\n    previewImage: '',\n  };\n\n  // 页面初始化的时候，从接口拉取默认的图片数据\n  componentDidMount() {\n    const { dispatch } = this.props;\n    dispatch({\n      type: 'mymodel/getAllInfo',\n      payload: { params: xxx },\n    });\n  }\n\n  handleChange = ({ file, fileList }) => {\n    const { dispatch } = this.props;\n    // 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。\n    // 图片上传成功后，fileList数组中的 thumbUrl 中保存的是图片的base64字符串，这种情况，导致的问题是：图片上传成功后，点击图片缩略图，浏览器会会卡死。而下面这行代码，可以解决该bug。\n    fileList.forEach(imgItem => {\n      if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {\n        imgItem.thumbUrl = imgItem.response.imgUrl;\n      }\n    });\n\n    dispatch({\n      type: 'mymodel/setImgList',\n      payload: fileList,\n    });\n  };\n\n  handleCancel = () => this.setState({ previewVisible: false });\n\n  handlePreview = file => {\n    this.setState({\n      previewImage: file.url || file.thumbUrl,\n      previewVisible: true,\n    });\n  };\n\n  // 参考链接：https://www.jianshu.com/p/f356f050b3c9\n  handleBeforeUpload = file => {\n    //限制图片 格式、size、分辨率\n    const isJPG = file.type === 'image/jpeg';\n    const isJPEG = file.type === 'image/jpeg';\n    const isGIF = file.type === 'image/gif';\n    const isPNG = file.type === 'image/png';\n    const isLt2M = file.size / 1024 / 1024 < 2;\n\n    if (!(isJPG || isJPEG || isGIF || isPNG)) {\n      Modal.error({\n        title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',\n      });\n    } else if (!isLt2M) {\n      Modal.error({\n        title: '超过2M限制，不允许上传~',\n      });\n    }\n\n  }\n\n    // 参考链接：https://github.com/ant-design/ant-design/issues/8779\n    return new Promise((resolve, reject) => {\n      if (!(isJPG || isJPEG || isGIF || isPNG)) {\n        reject(file);\n      } else {\n        resolve(file && this.checkImageWH(file));\n      }\n    });\n\n  };\n\n  //返回一个 promise：检测通过则返回resolve；失败则返回reject，并阻止图片上传\n  checkImageWH(file) {\n    let self = this;\n    return new Promise(function(resolve, reject) {\n      let filereader = new FileReader();\n      filereader.onload = e => {\n        let src = e.target.result;\n        const image = new Image();\n        image.onload = function() {\n          // 获取图片的宽高，并存放到file对象中\n          console.log('file width :' + this.width);\n          console.log('file height :' + this.height);\n          file.width = this.width;\n          file.height = this.height;\n          resolve();\n        };\n        image.onerror = reject;\n        image.src = src;\n      };\n      filereader.readAsDataURL(file);\n    });\n  }\n\n  handleSubmit = e => {\n    const { dispatch, form } = this.props;\n    e.preventDefault();\n\n    const {\n      mymodel: { imgList }, // 从props中拿默认的图片数据\n    } = this.props;\n\n    form.validateFieldsAndScroll((err, values) => {\n      // values 是form表单里的参数\n      // 点击按钮后，将表单提交给后台\n\n\n      // start 问题描述：当编辑现有页面时，如果针对已经存在的默认图片不做修改，则不会触发 upload 的 onChange方法。此时提交表单，表单里的 myImg 字段是空的。\n      // 解决办法：如果发现存在默认图片，则追加到表单中\n      if (!values.myImg) {\n\n        values.myImg = { fileList: [] };\n\n        values.myImg.fileList = imgList;\n      }\n      // end\n\n      dispatch({\n        type: 'mymodel/submitFormData',\n        payload: values,\n      });\n    });\n  };\n\n  render() {\n    const { previewVisible, previewImage } = this.state; //  从 state 中拿数据\n\n    const {\n      mymodel: { imgList }, // 从props中拿到的图片数据\n    } = this.props;\n\n    const uploadButton = (\n      <div>\n        <Icon type=\"plus\" />\n        <div className=\"ant-upload-text\">Upload</div>\n      </div>\n    );\n    return (\n      <div className=\"clearfix\">\n        <Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>\n          <FormItem label=\"图片上传\" {...formItemLayout}>\n            {getFieldDecorator('myImg')(\n              <Upload\n                action=\"//jsonplaceholder.typicode.com/posts/\" // 这个是图片上传的接口请求，实际开发中，要替换成你自己的业务接口\n                data={file => ({\n                  // data里存放的是接口的请求参数\n                  param1: myParam1,\n                  param2: myParam2,\n                  photoCotent: file, // file 是当前正在上传的图片\n                  photoWidth: file.height, // 通过  handleBeforeUpload 获取 图片的宽高\n                  photoHeight: file.width,\n                })}\n                listType=\"picture-card\"\n                fileList={imgList}  // 改为从 props 里拿图片数据，而不是从 state\n                onPreview={this.handlePreview} // 点击图片缩略图，进行预览\n                beforeUpload={this.handleBeforeUpload} // 上传之前，对图片的格式做校验，并获取图片的宽高\n                onChange={this.handleChange} // 每次上传图片时，都会触发这个方法\n              >\n                {this.state.imgList.length >= 9 ? null : uploadButton}\n              </Upload>\n            )}\n          </FormItem>\n        </Form>\n        <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>\n          <img alt=\"example\" style={{ width: '100%' }} src={previewImage} />\n        </Modal>\n      </div>\n    );\n  }\n}\n\nexport default PicturesWall;\n\n```\n\n（2）mymodel.js:\n\n```javascript\n/* eslint-disable */\n\nimport { routerRedux } from 'dva/router';\nimport { message, Modal } from 'antd';\nimport {\n  getGoodsInfo,\n  getAllGoods,\n} from '../services/api';\nimport { trim, getCookie } from '../utils/utils';\n\nexport default {\n  namespace: 'mymodel',\n\n  state: {\n    form: {},\n    list: [],\n    listDetail: [],\n    goodsList: [],\n    goodsListDetail: [],\n    pagination: {\n      pageSize: 10,\n      total: 0,\n      current: 1,\n    },\n    imgList: [], //图片\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      history.listen(location => {\n        if (location.pathname !== '/xx/xxx') return;\n        if (!location.state || !location.state.xxxId) return;\n        dispatch({\n          type: 'fetch',\n          payload: location.state,\n        });\n      });\n    },\n  },\n\n  effects: {\n    // 接口。获取所有工厂店的列表 (步骤02)\n    *getAllInfo({ payload }, { select, call, put }) {\n      yield put({\n        type: 'form',\n        payload,\n      });\n      console.log('params:' + JSON.stringify(payload));\n\n      let params = {};\n      params = payload;\n\n      const response = yield call(getGoodsInfo, params);\n\n      console.log('smyhvae response:' + JSON.stringify(response));\n      if (response.error) return;\n      yield put({\n        type: 'allInfo',\n        payload:\n          (response.data &&\n            response.data.map(item => ({\n              xx1: item.yy1,\n              xx2: item.yy2,\n            }))) ||\n          [],\n      });\n\n      // response 里包含了接口返回给前端的默认图片数据\n      if (response && response.data && response.data[0] && response.data[0].my_jpg) {\n        let tempImgList = response.data[0].my_jpg.split(',');\n        let imgList = [];\n\n        if (tempImgList.length > 0) {\n          tempImgList.forEach(item => {\n            imgList.push({\n              uid: item,\n              name: 'xxx.png',\n              status: 'done',\n              thumbUrl: item,\n            });\n          });\n        }\n\n        // 通过  redux的方式 将 默认图片 传给 imgList\n        console.log('smyhvae payload imgList:' + JSON.stringify(imgList));\n        yield put({\n          type: 'setImgList',\n          payload: imgList,\n        });\n      }\n    },\n\n    *setImgList({ payload }, { call, put }) {\n      console.log('model setImgList');\n      yield put({\n        type: 'getImgList',\n        payload,\n      });\n    },\n  },\n\n  reducers: {\n    allInfo(state, action) {\n      return {\n        ...state,\n        list: action.payload,\n      };\n    },\n    getImgList(state, action) {\n      return {\n        ...state,\n        imgList: action.payload,\n      };\n    },\n  },\n};\n\n```\n\n上面的代码，可以规避 upload 组件的一些bug；而且可以在上传前，通过校验图片的尺寸、大小等，如果不满足条件，则弹出modal弹窗，阻止上传。\n\n大功告成。本文感谢 ld 同学的支持。\n\n更多内容，可以看本人的另外一篇文章：\n\n- [AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为](https://www.cnblogs.com/qianguyihao/p/13093592.html)\n\n## 其他问题\n\n- [beforeUpload返回false后，文件仍然为上传中的状态](https://github.com/ant-design/ant-design/issues/8779)\n\n## 最后一段\n\n有人说，前端开发，连卖菜的都会。可如果真的遇到技术难题，还是得找个靠谱的前端同学才行。这不，来看看前端码农日常：\n\n![](http://img.smyhvae.com/20190302_1339_2.png)\n\n"
  },
  {
    "path": "13-React基础/10-AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为.md",
    "content": "---\ntitle: 10-AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n本次做后台管理系统，采用的是 AntD 框架。涉及到图片的上传，用的是AntD的 [upload](https://ant.design/components/upload-cn/) 组件。\n\n\n我在上一篇文章《[前端AntD框架的upload组件上传图片时遇到的一些坑](https://www.cnblogs.com/qianguyihao/p/10460834.html)》中讲到：AntD 的 upload 组件有很多坑，引起了很多人的关注。折腾过的人，自然明白其中的苦楚。\n\n今天这篇文章，我们继续来研究 AntD 的 upload 组件的另一个坑。\n\n备注：本文写于2020-06-11，使用的 antd 版本是 3.13.6。\n\n## 使用 AntD 的 upload 组件做图片的上传，效果演示\n\n因为需要上传多张图片，所以采用的是照片墙的形式。上传成功后的界面如下：\n\n（1）上传中：\n\n![](http://img.smyhvae.com/20190302_1335.png)\n\n（2）上传成功：\n\n![](http://img.smyhvae.com/20190302_1336.png)\n\n（3）图片预览：\n\n![](http://img.smyhvae.com/20190302_1331.png)\n\n## 代码实现\n\n\n首先，你需要让后台同学提供好图片上传的接口。上一篇文章中，我们是把接口调用直接写在了 `<Upload>` 标签的 action 属性当中。但如果你在调接口的时候，动作很复杂（比如根据业务要求，需要连续调两个接口才能上传图片，或者在调接口时还要做其他的事情），这个 action 方法就无法满足需求了。那该怎么做呢？\n\n好在 AntD 的 upload 组件给我们提供了 `customRequest`这个方法：\n\n![](http://img.smyhvae.com/20200611_1543.png)\n\n关于customRequest 这个方法， AntD 官方并没有给出示例，他们只是在 GitHub 上给出了这样一个简短的介绍：\n\n![](http://img.smyhvae.com/20200611_1536.png)\n\n\n但这个方法怎么用呢？用的时候，会遇到什么问题呢？AntD 官方没有说。我在网上搜了半天，也没看到比较完整的、切实可行的 Demo。我天朝地大物博，网络资料浩如烟海，AntD 可是口口声声被人们号称是天朝最好用的管理后台的样式框架。可如今，却面临这样的局面。我看着你们，满怀羡慕。\n\n既然如此，那我就自己研究吧。折腾了一天，总算是把 customRequest 的坑踩得差不多了。\n\n啥也不说了，直接上代码。\n\n采用 AntD框架的 [upload](https://ant.design/components/upload-cn/) 组件的 customRequest 方法，自定义上传行为。核心代码如下：\n\n\n\n```js\nimport React, { PureComponent } from 'react';\nimport { Button, Card, Form, message, Upload, Icon, Modal, Row, Col } from 'antd';\nimport { connect } from 'dva';\nimport { queryMyData, submitData } from '../api';\nimport { uploadImage } from '../../utils/wq.img.upload';\n\nimport styles from '../../utils/form.less';\n\nconst FormItem = Form.Item;\n\n@Form.create()\nexport default class PicturesWall extends PureComponent {\n  constructor(props) {\n    super(props);\n    const { id } = this.props.match.params;\n    this.state = {\n      id,\n      img: undefined, // 从接口拿到的图片字段\n      imgList: [], // 展示在 antd图片组件上的数据\n      previewVisible: false,\n      previewImage: '',\n    };\n  }\n\n  componentDidMount() {\n    const { id } = this.state;\n    id && this.queryData();\n  }\n\n  // 调接口，查询已有的数据\n  queryData() {\n    const { id } = this.state;\n    queryMyData({\n      id,\n    })\n      .then(({ ret, data }) => {\n        if (ret == 0 && data && data.list && data.list.length) {\n          const item = data.list[0];\n\n          const img = data.img;\n          const imgList = item.img\n            ? [\n              {\n                uid: '1', // 注意，这个uid一定不能少，否则展示失败\n                name: 'hehe.png',\n                status: 'done',\n                url: img,\n              },\n            ]\n            : [];\n\n          this.setState({\n            img,\n            imgList,\n          });\n        } else {\n          return Promise.reject();\n        }\n      })\n      .catch(() => {\n        message.error('查询出错，请重试');\n      });\n  }\n\n  handleCancel = () => this.setState({ previewVisible: false });\n\n  // 方法：图片预览\n  handlePreview = (file) => {\n    console.log('smyhvae handlePreview:' + JSON.stringify(file));\n    this.setState({\n      previewImage: file.url || file.thumbUrl,\n      previewVisible: true,\n    });\n  };\n\n  // 参考链接：https://www.jianshu.com/p/f356f050b3c9\n  handleBeforeUpload = (file) => {\n    console.log('smyhvae handleBeforeUpload file:' + JSON.stringify(file));\n    console.log('smyhvae handleBeforeUpload file.file:' + JSON.stringify(file.file));\n    console.log('smyhvae handleBeforeUpload file type:' + JSON.stringify(file.type));\n\n    //限制图片 格式、size、分辨率\n    const isJPG = file.type === 'image/jpeg';\n    const isJPEG = file.type === 'image/jpeg';\n    const isGIF = file.type === 'image/gif';\n    const isPNG = file.type === 'image/png';\n    const isLt2M = file.size / 1024 / 1024 < 1;\n    if (!(isJPG || isJPEG || isPNG)) {\n      Modal.error({\n        title: '只能上传JPG、JPEG、PNG格式的图片~',\n      });\n    } else if (!isLt2M) {\n      Modal.error({\n        title: '图片超过1M限制，不允许上传~',\n      });\n    }\n    return (isJPG || isJPEG || isPNG) && isLt2M;\n  };\n\n  // checkImageWH  返回一个promise  检测通过返回resolve  失败返回reject阻止图片上传\n  checkImageWH(file) {\n    return new Promise(function (resolve, reject) {\n      let filereader = new FileReader();\n      filereader.onload = (e) => {\n        let src = e.target.result;\n        const image = new Image();\n        image.onload = function () {\n          // 获取图片的宽高\n          file.width = this.width;\n          file.height = this.height;\n          resolve();\n        };\n        image.onerror = reject;\n        image.src = src;\n      };\n      filereader.readAsDataURL(file);\n    });\n  }\n\n  // 图片上传\n  doImgUpload = (options) => {\n    const { onSuccess, onError, file, onProgress } = options;\n\n    // start：进度条相关\n    // 伪装成 handleChange里面的图片上传状态\n    const imgItem = {\n      uid: '1', // 注意，这个uid一定不能少，否则上传失败\n      name: 'hehe.png',\n      status: 'uploading',\n      url: '',\n      percent: 99, // 注意不要写100。100表示上传完成\n    };\n\n    this.setState({\n      imgList: [imgItem],\n    }); // 更新 imgList\n    // end：进度条相关\n\n    const reader = new FileReader();\n    reader.readAsDataURL(file); // 读取图片文件\n\n    reader.onload = (file) => {\n      const params = {\n        myBase64: file.target.result, // 把 本地图片的base64编码传给后台，调接口，生成图片的url\n      };\n\n      // 上传图片的base64编码，调接口后，返回 imageId\n      uploadImage(params)\n        .then((res) => {\n          console.log('smyhvae doImgUpload:' + JSON.stringify(res));\n          console.log('smyhvae 图片上传成功：' + res.imageUrl);\n\n          const imgItem = {\n            uid: '1', // 注意，这个uid一定不能少，否则上传失败\n            name: 'hehe.png',\n            status: 'done',\n            url: res.imageUrl, // url 是展示在页面上的绝对链接\n            imgUrl: res.imageUrl, // imgUrl 是存到 db 里的相对链接\n            // response: '{\"status\": \"success\"}',\n          };\n\n          this.setState({\n            imgList: [imgItem],\n          }); // 更新 imgList\n        })\n        .catch((e) => {\n          console.log('smyhvae 图片上传失败:' + JSON.stringify(e || ''));\n          message.error('图片上传失败，请重试');\n        });\n    };\n  };\n\n  handleChange = ({ file, fileList }) => {\n    console.log('smyhvae handleChange file:' + JSON.stringify(file));\n    console.log('smyhvae handleChange fileList:' + JSON.stringify(fileList));\n\n    if (file.status == 'removed') {\n      this.setState({\n        imgList: [],\n      });\n    }\n  };\n\n  submit = (e) => {\n    e.preventDefault();\n\n    this.props.form.validateFields((err, fieldsValue) => {\n      if (err) {\n        return;\n      }\n\n      const { id, imgList } = this.state;\n\n      const tempImgList = imgList.filter((item) => item.status == 'done'); // 筛选出 status = done 的图片\n      const imgArr = [];\n      tempImgList.forEach((item) => {\n        imgArr.push(item.imgUrl);\n        // imgArr.push(item.url);\n      });\n\n      submitData({\n        id,\n        img: imgArr[0] || '', // 1、暂时只传一张图片给后台。如果传多张图片，那么，upload组件需要进一步完善，比较麻烦，以后有需求再优化。2、如果图片字段是选填，那就用空字符串兜底\n      })\n        .then((res) => {\n          if (res.ret == 0) {\n            message.success(`${id ? '修改' : '新增'}成功，自动跳转中...`);\n\n          } else if (res.ret == 201 || res.ret == 202 || res.ret == 203 || res.ret == 6) {\n            return Promise.reject(res.msg);\n          } else {\n            return Promise.reject();\n          }\n        })\n        .catch((e) => {\n          message.error(e || '提交失败，请重试');\n        });\n    });\n  };\n\n  render() {\n    const { id, imgList } = this.state;\n    console.log('smyhvae render imgList:' + JSON.stringify(imgList));\n    const { getFieldDecorator } = this.props.form;\n    const formItemLayout = {\n      labelCol: { span: 3 },\n      wrapperCol: { span: 10 },\n    };\n    const buttonItemLayout = {\n      wrapperCol: { span: 10, offset: 3 },\n    };\n\n    const uploadButton = (\n      <div>\n        <Icon type=\"plus\" />\n        <div className=\"ant-upload-text\">Upload</div>\n      </div>\n    );\n\n    return (\n      <Card title={id ? '修改信息' : '新增信息'}>\n        <Form onSubmit={this.submit} layout=\"horizontal\">\n\n          {/* 新建图片、编辑图片 */}\n          <FormItem label=\"图片\" {...formItemLayout}>\n            {getFieldDecorator('img', {\n              rules: [{ required: false, message: '请上传图片' }],\n            })(\n              <Upload\n                action=\"2\"\n                customRequest={this.doImgUpload}\n                listType=\"picture-card\"\n                fileList={imgList}\n                onPreview={this.handlePreview}\n                beforeUpload={this.handleBeforeUpload}\n                onChange={this.handleChange}\n              >\n                {imgList.length >= 1 ? null : uploadButton}\n              </Upload>\n            )}\n          </FormItem>\n          <Row>\n            <Col span={3} />\n            <Col span={18} className={styles.graytext}>\n              注：图片支持JPG、JPEG、PNG格式，小于1M，最多上传1张\n            </Col>\n          </Row>\n\n          <FormItem {...buttonItemLayout}>\n            <Button type=\"primary\" htmlType=\"submit\">\n              提交\n            </Button>\n          </FormItem>\n        </Form>\n\n        {/* 图片点开预览 */}\n        <Modal visible={this.state.previewVisible} footer={null} onCancel={this.handleCancel}>\n          <img alt=\"example\" style={{ width: '100%' }} src={this.state.previewImage} />\n        </Modal>\n      </Card>\n    );\n  }\n}\n\n```\n\n## 参考链接\n\n注意file的格式：https://www.lmonkey.com/t/oREQA5XE1\n\nDemo在线演示：\n\n- https://stackoverflow.com/questions/58128062/using-customrequest-in-ant-design-file-upload\n\n- <https://stackblitz.com/edit/so-58128062-upload-progress>\n\nfileList 格式在线演示：\n\n- https://stackoverflow.com/questions/51514757/action-function-is-required-with-antd-upload-control-but-i-dont-need-it\n\n- https://codesandbox.io/s/rl7ooo544q\n\nant design Upload组件的使用总结：https://www.jianshu.com/p/0aa4612af987\n\nantd上传功能的CustomRequest：https://mlog.club/article/3832743\n\n"
  },
  {
    "path": "13-React基础/11-React Navive初识.md",
    "content": "---\ntitle: 11-React Navive初识\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 搭建开发环境\n\n官方文档：<https://reactnative.cn/docs/getting-started.html>\n\n### 安装Node、homebrew、Watchman\n\n安装 homebrew：\n\n```\n\n```\n\n安装 watchman：\n\n```\nbrew install watchman\n```\n\n\nWatchman则是由 Facebook 提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能（packager 可以快速捕捉文件的变化从而实现实时刷新）。\n\n\n\n### 安装 React Native 的命令行工具（react-native-cli）\n\n安装 react-native-cli：\n\n```\nnpm install -g react-native-cli\n```\n\n\nReact Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务（packager）等任务。\n\n###  创建新项目\n\n```\nreact-native init MyApp --version 0.44.3\n```\n\n### 编译并运行 React Native 应用\n\n在 ios 模拟器上运行：\n\n```\nreact-native run-ios\n```\n\n## 调试\n\n官网文档：<https://reactnative.cn/docs/debugging.html>\n\n\n### 访问 App 内的开发菜单\n\n如果是在 iOS 模拟器中运行，还可以按下`Command + D`快捷键，Android 模拟器对应的则是Command⌘ + M（windows 上可能是 F1 或者 F2），或是直接在命令行中运行adb shell input keyevent 82来发送菜单键命令。\n\n\n\n"
  },
  {
    "path": "14-前端性能优化/00-前端性能优化认知.md",
    "content": "---\ntitle: 00-前端性能优化认知\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 前端性能优化认知\n\n\n\n### 什么是前端性能优化\n\n通常来讲，前端性能优化是指：从用户开始访问网站到整个页面完整地展现出来的过程中，通过各种优化策略和优化方法，让页面加载得更快，让用户的操作相应更及时，给用户更好的使用体验。\n\n优化是在做什么：\n\n\n\n![](http://img.smyhvae.com/20210115-1507.png)\n\n如上图所示，优化工作是围绕前端的基本工作原理展开的，包括：**客户端和服务器端建立连接、加载资源、解析资源并渲染**。\n\n上方图片的来源：\n\n- [The Cost Of JavaScript](https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e)\n- [[译]JavaScript 的时间消耗](https://github.com/dwqs/blog/issues/59)\n\n\n\n### 性能优化的重要性（程序员角度）\n\n当领导问：“**为什么网页访问这么慢**？”我们不能只是回答“**网络不好**”这么简单，网络不可能一直都不好。\n\n每个程序员如果想要成长，就不能回避“性能优化”这个话题。很多人写了多年的代码，一直在构建样式、写业务逻辑。但是平凡的程序员之路，何时才是尽头？前端职业发展的瓶颈在哪儿？怎么才能从团队中脱颖而出？如何区分出平凡程序员/大牛程序员/架构师的分水岭？\n\n职场晋升时，我们也要想一想：大部分人都在写业务代码，和别人相比，我的核心竞争力在哪里？除了**技术深度、前端工程化、综合素质**之外，还有其他的吗？**性能优化**，绝对是不能忽视的一方面。而且它是贯穿于开发和维护的的全过程。\n\n前端工程化是侧重于**提效**，具体包括编译打包发布流程、物料中心、组件化等；而前端性能优化是侧重于**体验**。\n\n公司评价一个程序员的价值，不是加班越多越好，也不是代码量越多越好，而是看他**是否能解决其他人解决不了的一些技术难题或者瓶颈**。\n\n**大家都知道性能优化很重要，但是落实到具体，怎么去优化**？这就需要我们深入去了解前端技术背后的原理，学习一些主流的前端性能优化技术方案，掌握性能优化技术，提升Web性能，才能总结出相应的优化方案，而且需要多年的经验积累；进而到达前端技术圈的上游，提高自己的核心竞争力。\n\n### 前端性能优化面试\n\n性能优化是前端面试的必考问题，面试者在回答这个问题时，大致情况如下：\n\n- 70% 的人上来就说减少合并资源、减少请求、数据缓存这些优化手段。\n\n- 15% 的人会提到需要在 DevTools 下先看看首屏时间，可以先围绕首屏来做优化。\n\n- 10%的人会提到需要接入一个性能平台来看看现状，诊断一下。\n\n- 而只有 5% 的人会从前端性能体系来系统考虑性能优化。\n\n面试官期待的是你在什么场景下，遇到了什么性能问题，围绕什么样的性能指标，采取了哪些性能优化手段，最后取得了什么样的结果，而不仅仅是直接说采取了哪些优化手段。\n\n比如说，“**为什么首页打开慢**？”\t面试官期待的是，前端能和后端一样，通过查日志和数据就能定位问题，而不是停留在猜测层面。但在实际当中，能做到这点的前端同学并不多。\n\n那么，前端有没有这样的工具呢？有，那就是性能监控平台。平台上面有各个业务的性能指标及其对应场景下的性能标准，一旦遇到性能问题，就能直接判断当前性能数据有没有问题，然后提示问题是出在前端、后端，还是网络层。\n\n### 性能优化的意义\n\n\n1、随着互联网的发展，**网页的内容越来越丰富，功能越来越强大，页面也越做越漂亮**；带来的问题是，访问速度和体验会收到影响。只有对网站进行持续不断的优化，才能保证网页的访问速度可以跟得上用户体验的需求。\n\n2、**高性能**可以带来更高的**用户参与度**、**用户留存**，进而带来更高的**转化率**和**SEO排名**，更好的**用户体验**，最终带来更高的**业务收益**。\n\n随着时间的推移，如果一个网站由于各种原因导致心梗越来越差，以至于用户每打开一个页面都要等待很长时间，甚至出现加载失败的情况，那么，不仅新用户不会沉淀下来，老用户也会纷纷离去，最终导致产品的加速衰败。\n\n而且网站的加载快慢，最产品收入有着直接的影响。**有数据表明：网页加载时间在5秒内的网站比加载时间为19秒的网站，广告收入会增加近一倍**。也就是说，网站或者App的性能直接关系到产品的用户增长和收入增长。\n\n正因为如此，我们才需要通过性能优化的技巧，并结合其他的技术手段来不断提高网站和App的用户体验，从而助力公司的业务增长；同时，我们也可以借此提升自己的技术实力，这对个人的职业成长也会以后很大的帮助。\n\n3、只要产品上线了，随着**业务规模量和用户访问量的扩大**，性能优化就是不可回避的话题。在遇到性能问题时，有些人的解决办法是：用一些粗糙的手段把问题绕过去，但却给后面的人埋下了坑。这些人常说的依据口头禅是：\n\n\n![](http://img.smyhvae.com/20210115-2150.jpg)\n\n### 相关案例\n\n- [Amazon发现每100ms延迟导致1%的销量损失](https://www.gigaspaces.com/blog/amazon-found-every-100ms-of-latency-cost-them-1-in-sales/)。\n\n- 歌地图首页文件从100KB减少到70KB，流量在第一周涨了10%，在接下来的三周涨了25%。\n\n- 腾讯根据长期数据监控发现，页面一秒钟延迟会造成页面访问量下降9.4%，跳出率增加8.3%，转化率下降3.5%。\n\n### 补充\n\n有些同学做事有拖延症，经常喜欢刷朋友圈、刷微博、看新闻，导致工作效率很低。为了解决这个问题，其实有个办法就是：把你的浏览器或者指定的软件，添加一个5秒的延迟，这时候，当你访问所有的网站，都会很慢。坚持一个月，你会发现，你再也不想看这些网站了，工作效率明显提升了许多。\n\n这件事情从侧面也反映出：网站的性能如果不够好，对用户来说是一种折磨。时间一长，用户就不想用这个网站了。性能和网站的利益直接相关。涉及到：流量、搜索、转化率、用户体验。\n\n\n\n## 如何学习性能优化\n\n### 学习难点\n\n我们在网上找到的文章，有很多都只是对CSS、JS技术本身的优化，一旦涉及到App、后端、网络等不是很熟悉的领域，学习起来就比较困难了。结合具体业务开发的应用场景时，却不知从何下手。因此，**我们需要要由点及面，学习全链路前端性能优化的知识体系和解决方案**。\n\n在实际工作当中，前端性能优化往往比较繁杂，学习难点主要体现在以下几个方面：todo\n\n\n### 优化标准\n\n我们在做优化时，需要有一个量化标准，比如：\n\n- loading 要做到什么效果、动画要达到什么效果才是好的？\n\n- 所有的事件处理，要在什么时间内完成，才能给用户良好的体验？\n\n### 技术储备前提\n\n- 掌握前端基础知识。\n\n- 具备Web开发实战经验。\n\n### 寻找性能瓶颈\n\n- 了解性能指标，多快才算快。\n\n- 利用测量工具和API\n\n- 优化问题，重新测量。持续迭代。\n\n### 移动端挑战多\n\n- 移动端的硬件不如PC端，且网络不稳定。\n\n- 屏幕尺寸和交互方式都是挑战。\n\n- 移动端用户更佳缺乏耐心。而且，很多用户是利用碎片化时间访问网页。数据参考： **>3秒**的加载时间，导致 53%的跳出率（bounce rate）。\n\n- 持续增长的移动端用户和电商业务。现在很多事情都是在移动端做的。\n\n### 收获\n\n- 由浅入深：解读优化技术内幕。\n\n- 流行+经典：了解技术背后的设计思想。\n\n- 了解性能优化的关键环节，升级知识储备。\n\n\n- 理论+实践：掌握前端业界的流行且成熟的多种性能优化技术，脱颖而出。\n\n- 了解大厂正在用的生产环境级别的高性能解决方案。\n\n\n## 前端性能优化全过程\n### 1、静态资源优化\n\n静态资源优化包括html、css、js、图片等资源的性能优化。包括：\n\n- 图片的应用场景和使用\n\n- html、css、js的具体优化策略\n\n\n- 资源文件的优化：比如文件压缩合并策略、打包方案、版本号更新方案\n\n- 前端工程化工具等。\n\n### 2、页面渲染架构设计及相关的技术方案选型\n\n按照技术方案的分类，包括：\n\n- 前后端分离技术\n\n- SPA单页应用\n\n- BigPipe\n\n- 同构直出\n\n- PWA\n\n- 页面加载策略\n\n- 接口服务调优、接口缓存策略\n\n- 大型网站背后的实际性能优化案例\n\n- 前端组件化、模块化，加速业务开发\n\n### 3、原生App优化、混合开发优化\n\n- 浏览器的整体优化方案。比如导航条、登录态、滚动条优化等。\n\n- 前端缓存策略和优化\n\n- H5静态资源请求代理的技术原理\n\n- H5离线技术，达到页面秒开的目标\n\n- 混合式开发解决方案\n\n- RN、小程序、flutter等\n\n### 4、服务端与网络优化\n\n- CDN 和 DNS 优化\n\n- 如何减少 http 请求数、减少cookie大小\n\n- nginx缓存配置和优化\n\n- 开启和配置 gzip 压缩\n\n- 如何开启全站 https\n\n- 升级 Http2.0 的好处和方法\n\n\n### 5、研发流程优化\n\n- 技术调用的方法\n\n- 前后端接口约定、加快前后端接口联调\n\n- 前端自动化测试\n\n- 自动化部署和上线\n\n- 从研发的整体流程层面梳理出提升研发效率的方式和方法。\n\n\n### 6、全链路质量监控体系建设\n\n主要是对性能优化的结果进行衡量、打分、考核：\n\n- 上线前，页面质量及时检测\n\n- 上线后，页面性能和错误监控\n\n- 线上运行时，页面的可用性监控\n\n- 愿生App的性能和错误监控\n\n## 前端性能优化包括哪些方面\n\n### 1、性能优化指标与测量工具\n\n- 行业标准\n\n- 优化模型\n\n- 性能测量工具：了解性能情况，并对比\n\n- 性能相关APIs\n\n### 2、渲染优化\n\n- 现代浏览器的渲染原理\n\n- 可优化的渲染环节和方法\n\n\n### 3、代码优化\n\n\n- JS优化：了解JS的开销、解析、优化方案，以及如何配合V8引擎做更有效的优化。\n\n- html优化\n\n- css优化\n### 4、资源优化\n\n- 压缩合并\n\n- 图片格式\n\n- 图片加载\n\n- 字体优化\n\n\n\n### 5、构建优化\n\n- webpack 优化配置\n\n- 代码拆分\n\n- 代码压缩\n\n- 持久化缓存\n\n- 监测与分析\n\n- 按需加载\n\n### 6、传输和加载优化\n\n- gZip\n\n- KeepAlive\n\n- HTTP缓存\n\n- Service Worker\n\n- HTTP/2\n\n- SSR 服务端渲染\n\n- Nginx\n### 7、更多主流优化方案\n\n\n- SVG 优化图标\n\n- FlexBox 布局\n\n- 预加载\n\n- 预渲染\n\n- 窗口化提高列表性能\n\n- 骨架屏\n\n\n\n\n\n\n"
  },
  {
    "path": "14-前端性能优化/01-前端性能分析工具和指标.md",
    "content": "---\ntitle: 01-前端性能分析工具和指标\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 性能指标和优化目标之：加载\n\n性能指标：我们在性能优化过程中可以参考的标准。这些标准都是业界或者前人总结出来的指导性经验。我们可以参考这些指标，去指导我们自己的优化。\n\n### 打开网站的初体验\n\n我们以淘宝网站为例，按下F12打开浏览器的调试模式。\n\n![](http://img.smyhvae.com/20210115_1601.png)\n\n上图中，鼠标右键点击“刷新”图标（或者鼠标长按刷新图标，松开鼠标后），会弹出三个选项，我们选择最后一个选项“清空缓存并硬性重新加载”。\n\n补充：这三个选项都是在调试模式下（按下F12弹出调试面板）才会出现的。\n\n浏览器的DevTools初印象：\n\n![](https://img.smyhvae.com/20210115_1617.png)\n\n上图中，打开 chrome 调试工具，点开「设置」icon，下面的四个选项中，除了“Group by frame”之外，其他的三个选项都可以勾选上。\n\n我们可以看到淘宝网站的一些指标：\n\n- 总资源量是 1.3M。\n- DOM加载完成时间（DOMContentLoaded）：511ms。这是一个很关键的指标。\n- 其他资源的总加载时间是 1.05秒。\n\n我们再来对比一下京东的：\n\n![](http://img.smyhvae.com/20210116-1357.png)\n\n\n\n### 保存快照\n\nnetwork里的信息挺多，我们可以将其保存下来，留着以后做分析、做对照。\n\n![](http://img.smyhvae.com/20210115-1723.png)\n\n如上图所示，我们可以在 network 的空白处右键，选择“Save all as HAR with content”，将 network 信息保存为 **HAR**文件格式。\n\n**HAR是一种标准的Web格式，用户保存性能测试的结果。里面的数据是json格式。**\n\n我们可以使用第三方的 HAR 分析软件来打开 HAR 文件，比如：\n\n- [Google 提供的 HAR 分析器](https://toolbox.googleapps.com/apps/har_analyzer/?lang=zh-CN)\n\n- Fiddler 抓包工具\n\n注意，HAR 文件包含了一些敏感信息：\n\n![](http://img.smyhvae.com/20210115-1733.png)\n\n### 瀑布图 Waterfall\n\n![](http://img.smyhvae.com/20210115_1618.png)\n\n瀑布图可以非常直观地把网站的加载过程，用自上而下的方式表达出来，就像瀑布一样。\n\n瀑布图有两中解读方式：一种是横向看，一种是纵向看。\n\n**1、横向看**：\n\n横向看的是具体的资源，每一行代表某个资源的加载信息。里面有一些色块来表达加载的过程，每个块的颜色不同。也就是说资源的下载不是单一的过程，而是经历了很多环节。\n\n为了了解资源的具体加载过程，我们把鼠标悬浮在第一个资源的色块上，可以看见一个详情列表：\n\n![](http://img.smyhvae.com/20210115_1632.png)\n\n（1）等待：\n\n- Queueing：排队。浏览器会对资源的请求做优先级排序。\n\n（2）连接：\n\n- DNS Lookup：DNS域名解析。每个资源都有域名，对域名做DNS解析，然后找到对应服务器的IP地址。\n\n- initial connection：客户端和服务器之间建立TCP连接。\n\n- SSL证书：该网站为了保证安全性，使用了 https 协议，启用了SSL证书。启用之后，需要做安全认证（SSL协商），这个过程也会耗时。到这里位置，我们可以看到，在请求资源之前，有很多的前置步骤。\n\n（3）请求和响应：\n\n- Request sent：到这一步，真正开始请求资源。\n\n- Waiting（**TTFB**）：资源从请求到响应，有一个等待的时间。\n\n- Content Download：收到响应后，资源的下载时间。如果值越大，表明下载时间越长。有些同步加载的资源会造成阻塞，导致网页的整体加载时间过长，让用户等待太久。\n\n**TTFB** 是一个很重要的指标，它表示的是：请求发出到响应，到底要经历多久。TTFB 可以给我们一个很直观的感受，我们网站的请求和响应到底是快还是慢，很大程度上是由 TTFB 决定。\n\n影响 TTFB 的因素是什么呢？比如：\n\n- 后台的处理能力的响应速度。\n\n- 网络状况：是否有网络延迟。\n\n**2、纵向看**：（主要看两点）\n\n（1）看资源与资源之间的联系：如果发生阻塞，说明资源可能是串行地按顺序加载。可以**按需要适当调整为并行**。\n\n（2）看关键的时间节点。Waterfall 中有**两根时间线**：蓝色的线是 DOM 加载完成的时间，红色的线是所有资源加载完成的时间。\n\n\n\n## 性能指标和优化目标之：交互\n\n上面的内容讲的是**加载**的性能，还有一个需要关注的性能指标是**交互**。也就是网站加载完成后，用户真正开始使用这个网站过程中的的交互体验。\n\n关于交互体验的性能，我们需要关注的是：\n\n- 交互动作的**响应时间**要短：比如点击按钮后的弹窗、在搜索框里输入关键字后的搜索结果。\n\n- 页面滚动要流畅：可以查看 FPS 帧率。\n\n- 异步请求接口的完成时间要短：比如关注/取关主播的响应、领取红包的操作。\n\n### FPS帧率、FRS\n\n这里首先科普两个概念：\n\n- 刷新率：显示器每秒有多少帧画面。大多数显示器的刷新率是60帧/秒（即60hz）。\n- 帧率（FPS：frames per second）：视频或者动画的内容本身，每秒有多少帧。由显卡输出帧率。\n\n上面的两个参数中，不要把「刷新率」和「帧率」弄混了。「刷新率」是屏幕的参数，「帧率」是图像、视频等内容的参数。人眼最终看到的效果，是以最低的参数为准的。\n\n目前，市场主流手机和电脑屏幕的刷新率基本都是60Hz，即每秒显示60帧画面。也就是说，当我们在使用手机的时候，本质上是手机在连续播放一张张静态图片，每秒播放60张，让肉眼误认为眼前的画面在动。\n\n![](http://img.smyhvae.com/20210107_2115.gif)\n\n持续滑动的过程中，如果页面输出到显示器的帧率低于60帧/秒，则人眼会感觉卡顿。\n\n那么，在浏览器中如何实时显示内容的 FPS 参数呢？打开浏览器的控制台后，按住快捷键「Cmd + Shift + P」，然后输入 `frame`，选择`Show frames per second（FPS） meter`。如下：\n\n![](http://img.smyhvae.com/20210115-1930.png)\n\n![](http://img.smyhvae.com/20210115-2146.png)\n\n**温馨提示**：\n\n从 2020年7月起，chrome 官方已经取消了 fps参数的显示，改为了 [FRS](https://twitter.com/addyosmani/status/1281483292026400768)：\n\n![](http://img.smyhvae.com/20210115-2006.png)\n\nFRS参数观察的是丢帧率：\n\n![](http://img.smyhvae.com/20210115-2010.png)\n\n\nChrome官方给我们提供了下面这个网站，用于观察 FPS 效果：\n\n- <http://googlesamples.github.io/web-fundamentals/tools/chrome-devtools/rendering-tools/forcedsync.html>\n\n如果实在想要看fps，我们可以借助第三方的 [chrome 插件]()来查看 fps参数。\n\n## 用 RAIL 模型测量性能\n\nRAIL 模型是Google提出的可以量化性能的测量**标准**。我们做性能优化时，要尽可能到这个标准。\n\n在做性能优化的时候，我们需要有人告诉我们：做到多好才算好？有没有一些通用的标准？而 RAIL 模型 可以给我们带来量化的指标。\n\n**RAIL 模型包括四个方面**：\n\n![](http://img.smyhvae.com/20210115-2027.png)\n\n- Response：响应\n\n- Animation：动画\n\n- Idle：空闲时间\n\n- load：加载\n\n参考链接：\n\n- [[Web翻译]用RAIL模型测量性能](https://juejin.cn/post/6872474167543857165)\n\n- <https://web.dev/rail/>\n\n**RAIL 的目标**：\n\n- 让良好的用户体验成为性能优化的目标\n\n接下来，我们再看看看 RAIL 的评估标准。\n\n### 1、响应\n\n**目标**：处理用户发起的响应，应该在 50ms 内完成。\n\n**准则**：\n\n- 在50毫秒内处理用户输入事件。这适用于大多数输入，如点击按钮、切换表单控件或启动动画。这不适用于触摸拖动或滚动。\n\n- 对于需要超过50毫秒才能完成的操作，需要提供反馈。\n\n\n![](http://img.smyhvae.com/20210115-2039.png)\n\n如上图所示，Google经过大量研究发现，用户能够接受的最高延时是100ms。所以，从用户发起交互请求（输入任务）后，前端最好能在100ms内给出反馈。\n\n**但是我们的预算只有50毫秒**。因为应用程序在接收到输入任务的时候，不一定会马上着手处理，它可能还有其他工作正在进行，这意味着当前的输入任务可能需要排队50ms左右。所以我们真正能处理这个请求的时间，并没有100ms。\n\n\n### 2、动画\n\n**目标**：在10毫秒或更短的时间内制作出动画中的每一帧。（即：100帧/秒。）\n\n我们知道，当动画的帧率是 >= 60帧/秒 的时候，人眼才不会觉得卡顿。此时的理论值为 1000毫秒/60帧 = 16.6 毫秒/帧。\n\n10毫秒和16毫秒之间，隔了6秒。这6秒是什么呢？因为浏览器需要大约6毫秒的时间来渲染每一帧，所以，每一帧的准则建议是10毫秒，而不是 16.6毫秒。\n\n假设动画本身是60帧/秒，那么，最终渲染出来的效果可能只有 45帧/秒。\n\n**广义的动画**：\n\n动画不仅仅是花哨的UI效果。每一种交互都被认为是动画。比如：\n\n- 视觉动画\n\n- 滚动\n\n- 拖动、平移元素、放大图片等。\n\n### 3、空闲时间\n\n**目标**：最大化闲置时间，增加页面在50毫秒内响应用户输入的几率。\n\n这个空闲时间，是和上面的第一点“响应”是结合在一起的。只有空闲足够多，当用户的交互来的时候，我们才能有足够的时间进行处理。\n\n**准则**：\n\n- 利用空闲时间做延迟加载。例如，页面在初始化的时候，尽可能少的加载数据，然后利用空闲时间加载其余部分。\n\n- 在空闲时间内处理任务，时间不能超过50毫秒。否则，就阻塞了用户做其他的输入请求，导致卡顿。\n\n- 如果用户在闲置时间工作期间与页面进行交互，那么这个交互应始终处于最高优先级，并中断闲置时间工作。\n\n### 4、加载\n\n**目标**：在5秒或更短的时间内加载页面并可以交互。\n\n**准则**：\n\n- 这里的5秒包括：加载、解析、渲染，并确保用户可以交互。\n\n- 加载的过程中，可以使用loading框、进度条、骨架屏等方式缓解用户焦虑。\n\n## 使用Chrome DevTools 分析性能\n\n现在主流的性能测量工具：\n\n- Chrome DevTools：开发调试、分析性能。\n\n- Lighthouse 网站整体质量评估。\n\n- WebPageTest：给网站提供多个地点的测试，以及全面的性能报告。\n\n这一段，我们先来讲一讲 Chrome DevTools 。\n\n大家平时在用 Chrome DevTools 的时候，一般使用来开发调试、查看 DOM、css、接口请求等，但其实，这个工具非常强大。\n\n### size：文件大小分析\n\n![](http://img.smyhvae.com/20210116-0946.png)\n\n可以把size从到小排序，看看哪个资源的文件较大。\n\n另外，上图中的横线处说明：该文件在网络传输的时候会做压缩（125kb），拿到资源之后再解压还原（526kb）。\n\n\n\n### performance：性能表现\n\n![](http://img.smyhvae.com/20210116-0959.png)\n\npreformance的两个作用：\n\n- Record button：记录页面加载、用户交互等全过程，直到我们手动点击停止按钮。\n- Reload button：记录页面从刷新到资源加载完成的过程。会自动停止记录。\n\n\n\n\n\n参数解读：\n\n- Timing：关键的时间节点。\n\n- Main：主线程做了哪些任务，以及调用关系。\n\nTiming参数中，尤其注意看`DCL`（DOMContentLoaded），即DOM加载完成的时间节点。我们可以通过`Main`参数看看DOM在加载完成之前，都做了些什么事情。很有可能就是这些事情导致 `DCL`的时间过晚。\n\n我们可以翻到`Main`里的最后一行（即最终调用的位置），往往这个位置就是我们自己写的代码。\n\n\n\n### Diable cache\n\n![](http://img.smyhvae.com/20210116-1014.png)\n\n上图中的`Diable cache`是一个很重要的设置选项。\n\n勾选`Diable cache`：\n\n- 不走缓存，相当于页面初次访问。\n- 如果你希望改的代码立即生效，就一定要勾选上。\n\n不勾选`Diable cache`：\n\n- 走缓存，相当于页面二次、三次访问。\n- 很多时候，我们需要关心用户在第二次、第三次访问时候，他的访问速度如何、性能如何、我们设置的缓存有没有生效。此时就不要勾选上。\n\n### 模拟网络情况\n\n![](http://img.smyhvae.com/20210116-1023.png)\n\n模拟网络状况（自定义参数）：\n\n![](http://img.smyhvae.com/20210116-1026.png)\n\n\n\n### Performance monitor\n\n\n\n![](http://img.smyhvae.com/20210116-1032.png)\n\n\n\n### 快捷键ESC\n\n按住快捷键ESC，会列出其他常用的功能菜单：\n\n![](http://img.smyhvae.com/20210116-1028.png)\n\n\n\n\n\n## 使用LightHouse分析性能\n\n我们之所以使用不同的性能测量工具，是因为他们都有不同的特点。这一段要讲的 lighthouse 既可以帮我们生成简易的测试报告，还可以给出一些针对性的优化建议。后面要讲的 WebPageTest 可以帮我们生成详细的性能测试报告。\n\n我们先来看看 Lighthouse。\n\n\n### Lighthouse 介绍\n\n![](http://img.smyhvae.com/20210115-1739.png)\n\nlighthouse 是 chrome 浏览器的一个性能测量工具。我们先来看看它的性能指标，至于它具体使用，后续的内容再详细介绍。\n\n\n淘宝跑分举例：\n\n\n![](http://img.smyhvae.com/20210115-1758.png)\n\n京东跑分举例：\n\n![](http://img.smyhvae.com/20210115-1759.png)\n\n\nLighthouse 跑分里，最重要的两个指标如下：\n\n- **First Contentful Paint（白屏时间）**：**从白屏到第一次出现内容的时间。**我们可以看到，上面提供了一些加载过程的截图，10屏里如果只有1到2屏是白屏，说明体验还是可以的。\n\n- **Speed Index**：速度指数。\n\n我们不需要关心这个指数是怎么来的，因为背后涉及一套很复杂的公式，我们暂时只需关注这个数值。\n\nSpeed Index 标准为4秒（超过4秒算比较慢的），我们测的淘宝的 speed index 是0.5s，很快了。但我们要结合网站本身的业务来**权衡**。并不是分数越高性能越高，比如百度这样的网站，页面上的内容很少，测出来的分数肯定很完美。而淘宝需要展示很多内容给用户看。所以，这个指标只是一个指导作用，并不一定能够达到最优的数值。\n\nLighthouse 的分析结果里，也给出了颜色标注：\n\n- 红色：比较严重的性能问题\n- 黄色：需要做适当优化\n- 绿色：说明性能表现很好。\n\n另外，Lighthouse  还会给出一些优化建议：\n\n- Opportunities:优化建议。\n\n- Diagnostics：问题诊断。\n\n- Passed audits：表示这部分没有问题。\n\n### 举例：确认某个JS 是否必须在首屏加载\n\n就拿B站来举例，来看看它的lighthouse报告：\n\n\n![](http://img.smyhvae.com/20210116_0107.png)\n\n上图中给出了一个优化建议：有些JS文件不是首屏加载必须的。\n\n![](http://img.smyhvae.com/20210116_0108.png)\n\n我们随便拿一个JS文件来测试（比如上图中，Header标签里的JS文件）。做法如下：\n\n![](http://img.smyhvae.com/20210116-0901.png)\n\n如上图所示，在 chrome 控制台输入快捷键「Cmd + Shift + P」，然后输入文本`block`，选择`Show Network request blocking`：\n\n![](http://img.smyhvae.com/20210116-0903.png)\n\n按照上面的步骤添加规则，点击add后，效果如下：\n\n![](http://img.smyhvae.com/20210116-0904.png)\n\n\n然后，我们切换到控制台的 network面板，并刷新页面：\n\n![](http://img.smyhvae.com/20210116-0905.png)\n\n然后观察这个js资源是不是首屏加载所必须的。但我们也不能就此定论说这个资源一定可以延迟加载，也许它就是想让页面在一开始loading的时候就捕获日志。\n\n对于我们自己的网站，这个资源是首屏加载必须的吗？一定要在第一时间加载吗？需要根据特定的业务做衡量。\n\n\n### 通过npm运行 Lighthouse工具\n\n```bash\n# 安装\nnpm install -g lighthouse\n\n# 执行\nlighthouse https://www.jd.com\n\n# 输出性能检测报告\nGenerating results...\nhtml output witten to /Users/smyh/Documents/wpt-mac-agent/www.jd.com._2021-01-16_09-00-00.html\n```\n\n\n\n\n\n\n## 使用 WebPageTest 评估网站性能\n\n\n\n程序员经常说的有句话是：“我这儿能打开啊。我这儿不报错呀。”大家应该都懂这个梗，这就是为什么，我们要借助第三方的测试工具，而不仅仅只是自己电脑上访问正常就ok了。\n\n我们需要借助 WebPageTest 这样的第三方测试工具，去模拟各种用户的真实场景。\n\n\n### WebPageTest 使用\n\n网址：<https://www.webpagetest.org>\n\n![](http://img.smyhvae.com/20210115-2203.png)\n\n\nWebPageTest 在世界各地提供了非常多的服务器，在每个服务器上部署了不同的浏览器，可以让我们有针对性的做测试。如果你做的是一款国际化网站，那更有必要使用一下了。\n\n我们以JD网站举例：\n\n![](http://img.smyhvae.com/20210115-2225.png)\n\n按照上面的选项配置完成后，点击右侧的「Start Test」即可开始测试。然后等待：\n\n![](http://img.smyhvae.com/20210115-2226.png)\n\n### WebPageTest 报告分析\n\n淘宝网站性能测试报告：\n\n- 2020年6月：https://webpagetest.org/result/200616_JK_78eebda338285ffe0c2e154ca5032839/\n\n- 2021年1月：https://www.webpagetest.org/result/210115_DiCB_f1344d732760365151755e89765b2d37/\n\nJD网站性能测试报告：\n\n- 2021年1月：https://www.webpagetest.org/result/210115_DiGT_8d7370e91230b7d077e40b7aafb485a5/\n\n拿到 WebPageTest 报告之后，我们来看看报告里的几个重点指标。\n\n![](http://img.smyhvae.com/20210116_1314.png)\n\n1、摘要里的参数：（如上图）\n\n- First Byte：第一个请求的响应时间。可以反映后台的处理能力，以及网络回路的情况。\n- Start Render：从白屏到首次渲染的时间。\n- Speed Index：速度指数。\n- **Total Blocking Time**：页面被阻塞，导致用户不能交互的累计时间。\n\n![](http://img.smyhvae.com/20210116_1315.png)\n\n2、详情里的参数：**First View**。\n\nFirst View展示的是：首次访问时，总的加载时间。这里面提供的瀑布图，比 chrome DevTools里提供的更为详细。\n\n点击进入 First View 的详情之后，可以看到：所有的资源请求，都会在这里列出来。如下：\n\n\n![](http://img.smyhvae.com/20210116_1316.png)\n\n\n\n- page is Interactive：页面在加载的过程中，大部分时间段，用户都是可以交互的。这是非常有用的一个指标。\n- Brower Main thread：浏览器主线程的占用情况。可以看看空闲的时间多不多。\n- CPU Utilization：CPU的使用情况。\n- 多张图片的资源请求。\n\n![](http://img.smyhvae.com/20210116_1317.png)\n\n上图中，我们可以看到：多张图片的开始请求时间都是相同的。也就是说，如果让资源做**并行加载**，我们就可以加大地减少加载时间，**最终所消耗的时间就由最大的图片来决定**。这是一个很好的优化技巧，至于具体是怎么实现的，可以自行了解。\n\n\n另外，我们看到，有一部分的请求，被高亮出来了：\n\n![](http://img.smyhvae.com/20210115-2250.png)\n\n\n上面这张图的意思是：302表示重定向，也就是说，这个资源已经不在原来请求的位置了，需要重定向才能找到真实的位置。这个地方其实可以做一个优化：\n\n> 不需要去访问之前的无效的资源，可以直接去访问重定向后的那个资源。\n\n### 局域网部署 WebPageTest 工具\n\n如果我们开发的页面，还没有上线，公网则无法访问。这个时候我们也想通过WebPageTest看看网站的性能，那要怎么办呢？\n\n我们可以在局域网部署 WebPageTest 工具，具体方法可自行研究。\n\n\n\n## chrome插件：PageSpeed Insights\n\n\n\n另外，google官方也有一个网址：https://developers.google.com/speed/pagespeed/insights/?hl=zh-cn\n\n但是这个网站在使用时，经常挂掉。\n\n这个插件是2018年的，已经好几年没更新了。大家参考即可。\n\n\n\n\n\n\n\n\n\n## 实时动态测量性能的API\n\nChrome DevTools能够检测各种性能参数，其实也是调用了一些性能相关的标准API。我们自己也可以直接在代码里调用这些api。\n\n通过 `performance`对象提供的API，我们可以实时的、精细化、自定义测量性能，获取相应的参数。也可以把这些性能参数，打印到控制台，或者实时上报给后台监控系统。\n\n### performance：获取常见性能参数\n\n常见性能参数，计算公式如下：\n\n> 时间戳1减去时间戳2，得到的差值，就是我们想要看到的耗时。\n\n- DNS 解析耗时: domainLookupEnd - domainLookupStart\n\n- TCP 连接耗时: connectEnd - connectStart\n\n- SSL 安全连接耗时: connectEnd - secureConnectionStart\n\n- 网络请求耗时 (TTFB): responseStart - requestStart\n\n- 数据传输耗时: responseEnd - responseStart\n\n- DOM 解析耗时: domInteractive - responseEnd\n\n- 资源加载耗时: loadEventStart - domContentLoadedEventEnd\n\n- First Byte时间: responseStart - domainLookupStart\n\n- 白屏时间: responseEnd - fetchStart\n\n- 首次可交互时间（**TTI**）: domInteractive - fetchStart\n\n- DOM Ready 时间: domContentLoadEventEnd - fetchStart\n\n- 页面完全加载时间: loadEventStart - fetchStart\n\n- http 头部大小： transferSize - encodedBodySize\n\n- 重定向次数：performance.navigation.redirectCount\n\n- 重定向耗时: redirectEnd - redirectStart\n\n比如说，如果我们想要获取 TTI参数，代码可以这样写：\n\n```javascript\n// 计算一些关键的性能指标\nwindow.addEventListener('load', (event) => {\n    // Time to Interactive\n    let timing = performance.getEntriesByType('navigation')[0];\n    console.log(timing.domInteractive);\n    console.log(timing.fetchStart);\n\n    let diff = timing.domInteractive - timing.fetchStart;\n    console.log(\"TTI: \" + diff); // 打印 TTI 参数\n})\n```\n\n\n\n### 观察长任务\n\n```javascript\nconst observer = new PerformanceObserver((list) => {\n    for (const entry of list.getEntries()) {\n        console.log(entry)\n    }\n})\n\nobserver.observe({entryTypes: ['longtask']})\n```\n\n\n\n### 页面可见性的状态监听\n\n使用场景举例：\n\n- 比如说，我们正在做一个视频网站（或者游戏页面）。如果用户当前没有在看这个视频，而是切换别的页面了。此时，我们可以对视频做节流等处理，避免造成性能的浪费。等用户再回到当前页面之后，再恢复之前的状态。\n- 当设备进入待机模式时（用户按下电源键关闭屏幕），网站想要关闭设备声音。\n\n针对这种场景，我们可以使用`visibilitychange`进行监听：\n\n```javascript\n// 见面可见性的状态监听\nlet vEvent = 'visibilitychange';\nif (document.webkitHidden != undefined) {\n    // webkit prefix detected\n    vEvent = 'webkitvisibilitychange';\n}\n\nfunction visibilityChanged() {\n    if (document.hidden || document.webkitHidden) {\n        console.log(\"Web page is hidden.\")\n    } else {\n        console.log(\"Web page is visible.\")\n    }\n}\n\ndocument.addEventListener(vEvent, visibilityChanged, false);\n```\n\n\n\n### 网络状况监听\n\n使用场景举例：\n\n- 高清图片按需加载：如果用户的网络条件较好，就加载高清图片资源；如果网络条件不好，就加载文件较小的图片资源。\n\n代码举例：\n\n```javascript\nvar connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;\n\nvar type = connection.effectiveType;\n\nfunction updateConnectionStatus() {\n  // type是之前的网络状态，connection.effectiveType是当前最新的网络状态\n  console.log(\"Connection type changed from \" + type + \" to \" + connection.effectiveType);\n\n  type = connection.effectiveType;\n}\n\nconnection.addEventListener('change', updateConnectionStatus);\n```\n\n打印结果举例：\n\n```\nConnection type changed from 4g to 3g\n```\n\n### 检测元素的可见状态，做曝光埋点\n\n我们可以通过`IntersectionObserver：`这个API来检测元素的可见状态：\n\n![](http://img.smyhvae.com/20210117_1635.png)\n\n做曝光上报的埋点：判断某个DOM（或者某个楼层）是否出现在视窗中，出现了就收集数据上报给服务端。\n\n本质就是要计算某一元素和另一元素（视窗）的相对位置/相对可视状态，然后进行一些操作（一般是上报给服务端）。\n\n参考：\n\n- [前端埋点之曝光实现](https://cnodejs.org/topic/5e0a0edb0696c446bf650dec)\n- [点击埋点和曝光卖点的封装](https://github.com/Hugohui/vueTrackSdk)\n\n\n"
  },
  {
    "path": "14-前端性能优化/02-浏览器渲染机制.md",
    "content": "---\ntitle: 02-浏览器渲染机制\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## 前言\n\n**渲染机制**包括的内容：\n\n- 什么是DOCTYPE及作用\n\n- 浏览器渲染过程。面试经常会问：在浏览器中输入url，发生了哪些事情。其中有一部就是浏览器的渲染过程。\n\n- Reflow：重排。面试官问完了渲染机制，一般会紧接着问重排Reflow，你可千万别说你没听过。\n\n- Repaint：重绘\n\n- Layout：布局。这里的Layout指的是浏览器的Layout。\n\n## 什么是DOCTYPE及作用\n\n### 定义\n\n**DTD**（Document Type Definition）：文档类型定义。\n\n是一系列的语法规则，用来定义XML或者(X)HTML文件类型。**浏览器会使用DTD来判断文本类型**，决定使用何种协议来解析，以及切换浏览器模式。（说白了就是：DTD就是告诉浏览器，我是什么文档类型，你要用什么协议来解析我）\n\n**DOCTYPE**：用来声明DTD规范。\n\n一个主要的用途便是文件的合法性验证。如果文件代码不合法，那么浏览器解析时便会出现一些差错。（说白了，DOCTYPE就是用来声明DTD的）\n\n\n### 常见的DOCTYPE声明有几种\n\n> 面试官紧接着会问，常见的 DOCTYPE 有哪些，以及 HTML5 的 DOCTYPE 怎么写。\n\n1、**HTML 4.01 Strict**：（严格的）\n\n```html\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n```\n\nPS：该DTD包含所有的HTML元素和属性，但不包括展示性的和弃用的元素（比如 font、u下划线等，这些是被废弃了的）。\n\n2、**HTML 4.01 Transitional**：（传统的）\n\n```html\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n\n```\n\nPS：该DTD包含所有的HTML元素和属性，但包括展示性的和弃用的元素（比如 font、u下划线等）。\n\n\n3、HTML 5：\n\n```html\n<!DOCTYPE html>\n```\n\n\n**总结：**\n\n面试时，不会让你写出 HTML 4.01的写法，因为大家都记不住。但是要记住 HTML 5 的写法，别看它简单，知道的人还真不多。\n\n面试时，可以这样回答： HTML 4.01 中有两种写法，一种是严格的，一种是传统的；并且答出二者的区别。 HTML 5的写法是`<!DOCTYPE html>`。\n\n\n\n\n## 浏览器的渲染过程\n\n\n\n### 渲染树\n\n![](http://img.smyhvae.com/20210118-2005.png)\n\n> 上方图片的来源：[Google 官方 | 渲染树构建、布局及绘制](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=zh-cn)\n\n**渲染树**包含了网页中有哪些节点、节点的从属关系、以及节点的CSS样式（大小、颜色等）。\n\n浏览器下载完html文档之后，第一步是先将其解析成文本。而html标签是由一对一对的尖括号表述的，可以被浏览器解析为有含义的标记。这些标记被翻译成节点对象，存放到链型数据结构中。这些节点被称之为**DOM对象**，这个链型数据结构就是**渲染树**。\n\n### 渲染过程（重要）\n\n浏览器的渲染过程非常复杂，面试时找重点说就行，不然太耗时间。如何快速简洁地描述清楚，是关键。来看看下面这张图。\n\n![](https://img.smyhvae.com/20180310_1257.png)\n\n渲染过程中，涉及到以下几个概念：\n\n- DOM树（DOM Tree）：浏览器将HTML标签解析成树形的数据结构。DOM树包含了有哪些节点，以及节点之间的从属关系（嵌套关系）。\n\n- CSSOM（CSS Rule Tree）：浏览器将CSS解析成树形的数据结构。CSSOM包含了节点的CSS样式（大小、颜色等）。\n\n- 渲染树（Render Tree）: DOM 树与 CSSOM 树**合并**后形成渲染树。渲染树只包含渲染网页所需的节点（但并不知道位置）。\n\n- 布局（Layout）: 计算出每个节点在屏幕中的**位置和大小**。\n\n- 绘制（Painting）：按照算出来的规则，通过显卡，把内容画出来。\n\n- composite：合成。浏览器在绘制的时候，一开始不会把所有的内容都画在同一层上。需要把这些内容画在不同的曾上，最终合并到一起，并显示在屏幕上。\n\n参考链接：\n\n- [浏览器渲染原理及流程](http://www.cnblogs.com/slly/p/6640761.html)\n\n### 关键渲染路径\n\n说到渲染，就不得不提到“关键渲染路径”，它描述的是渲染从触发到绘制的过程。浏览器渲染经历了五个阶段：\n\n> JavaScript/CSS --> Style --> Layout --> Paint --> Composite\n\n![](http://img.smyhvae.com/20210118-1950.jpg)\n\n> 上方图片的来源：<https://developers.google.com/web/fundamentals/performance/rendering>\n\n关键渲染路径描述的是渲染从触发到绘制的全过程，一共经历了五个阶段：\n\n（1）**触发视觉的变化：**通过JS、CSS代码来**触发**页面上的视觉变化。比如通过 jQuery添加节点、通过CSS添加动画，都可以触发视觉上的变化。\n\n（2）Style：浏览器对样式进行计算。匹配选择器，计算哪些CSS受到了影响。\n\n（3）layout：同上一段。\n\n（4）painting：同上一段。\n\n（5）conmposite：同上一段。\n\n理论上，上面的五个步骤都是必须要经历的。布局和绘制是关键渲染路径中，最重要、开销最高的两个步骤。\n\n但是，有些样式并不会影响布局，也不会影响绘制。所以，浏览器对这方面的性能进行了优化，并不一定要经历布局和绘制这两个过程。这就需要我们先了解一下「重排」和「重绘」这两个概念。详见下一段。\n\n\n\n\n\n## 布局/回流/重排\n\n### 定义\n\n布局 layout：\n\n渲染对象在创建完成并添加到渲染树时，是将DOM节点和它对应的样式结合起来，并不包含位置和大小信息。\n\n我们还需要通过 `Layout` 布局阶段，来计算它们在设备视口(viewport)内的确切位置和大小，计算这些值的过程称为`回流`、`布局`或`重排（Reflow）`。\n\n\n\n参考链接：\n\n- [从浏览器渲染原理，浅谈回流重绘与性能优化](https://www.cnblogs.com/xiahj/p/11777786.html)\n\n- [你真的了解回流和重绘吗](https://github.com/chenjigeng/blog/issues/4)\n\n### 什么时候会触发布局\n\nDOM元素的**大小**和**位置**发生变化的时候，会触发布局。\n\n- 增加、删除DOM元素\n\n- display: none\n\n- 移动元素位置，或是增加动画\n\n- 修改CSS样式时（宽高、display 为none等，都是通过css样式来修改的）\n\n- offsetLeft、scrollTop、clientWidth\n\n- 修改浏览器窗口大小时（即Resize窗口，移动端没有这个问题），或是滚动的时候，**有可能**会触发（具体要看浏览器的规则）。\n\n- 修改网页的默认字体时（这个很消耗性能）。\n\n**面试总结：**\n\n首先要答出 Reflow 定义；其次，什么时候触发，至少要答出两条。更进一步，面试官可能还会问你**怎么避免reflow**，这个可以自己去查查。\n\n\n\n## 绘制/重绘\n\n### 定义\n\n**绘制 paint**：当各种盒子的位置、大小以及其他属性，例如颜色、字体大小等都确定下来后，浏览器便把这些元素都按照各自的特性绘制一遍，于是页面的内容出现了，这个过程也称之为 Repaint（重绘制）。\n\n说白了，页面要呈现的内容，统统画在屏幕上，这就叫 Repaint。\n\n### 什么时候触发绘制\n\n- DOM改动\n\n- CSS改动\n\n其实，就是判断当视觉上是否发生变化（无论这个变化是通过DOM改动还是CSS改动）。只要页面显示的内容不一样了，肯定要 Repaint。\n\n**面试总结：**\n\n面试官经常会问：“如何**尽量减少**Repaint的频率？”\n\n注意， reflow是问“怎么避免”，repaint是问“怎么减少”。Repaint是无法避免的，否则就成了静态页面了。\n\n**答案**：\n\n（1）如果需要创建多个DOM节点，可以使用**DocumentFragment**创建完，然后一次性地加入document。（加一个节点，就repaint一次，不太好）\n\n（2）将元素的display设置为”none”，完成修改后再把display修改为原来的值。\n\n参考链接：[如何减少浏览器repaint和reflow ?](http://blog.csdn.net/liaozhongping/article/details/47057889)\n\n\n"
  },
  {
    "path": "14-前端性能优化/03-渲染优化.md",
    "content": "---\ntitle: 03-渲染优化\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 浏览器的渲染机制\n\n我们需要先理解浏览器的渲染经历了哪些过程，才能有针对性的进行相关优化。\n\n掌握浏览器的渲染优化，可以说是前端工程师的一个分水岭。如果想要具备架构师的思维，需要达到什么样的能力？不光是要解决当下的问题，还需要掌握基本的原理，将来在遇到新问题时也能解决，即“预测问题”。\n\n有一个经典的面试题是：“在浏览器的地址栏输入url，回车后，经历了哪些过程？”这个问题并不简单，根据你回答的详细程度，可以看出你对前后端知识的掌握程度。你能否答出“浏览器的渲染机制”？如果不能，说明你对浏览器渲染的性能优化，不够了解。\n\n关于浏览器的渲染机制，可以看本教程的另外一篇文章：\n\n> 《前端面试/面试必看/浏览器渲染机制.md》\n\n\n\n关键渲染路径举例：\n\n\n\n![image-20210323184157879](images/image-20210323184157879.png)\n\n\n\n![image-20210323184551245](images/image-20210323184551245.png)\n\n\n\n\n\n\n## 避免布局抖动（layout thrashing）\n\n1、尽量避免 重排：\n\n比如说，如果想改变一个元素的位置，很多人可能会使用相对布局的left、top属性，但是这个属性会引起重排。我们可以使用 `transfrom:translate`让元素做位移，这个属性既不会触发重排，也不会触发 重绘，只会触发 conmposite。\n\n再比如说，vue、react这样的框架，采用了虚拟DOM，它会把涉及到DOM修改的操作积攒起来，然后统一计算，批量处理，最后应用到真正的DOM上。\n\n2、读写分离。建议先批量读（获取位置等信息），然后再批量做写操作（修改位置）。\n\n补充：\n\n如果你的页面经常需要做重排、重绘，就很容易导致“页面抖动”。\n\n很多时候，我们知道原理和解决方案。但是在工程化实践的时候，往往时间很紧，没有时间去做这些事情。我们希望有一些拿来就可以用的、而且经过测试没有问题的工具，来帮我们解决问题。\n\nFastDom是用于做防抖的一个比较流行的解决方案。\n\n\n\n## 减少重绘（repaint）\n\n\n\n\n\n## 防抖（Debounce）：降低事件的触发频率\n\n我们可以针对**高频事件**做防抖。\n\n**高频事件处理函数**：有很多事件的触发频率非常高，甚至超过了屏幕的刷新率（60帧/秒）。比如页面滚动、鼠标移动、移动端的touch事件。\n\n如果我们不对这些事件做处理，就会频繁导致浏览器做重排、重绘，影响性能，导致页面卡顿，也就是“抖动”。因此需要对这些高频事件处理函数做防抖处理，降低它们的触发频率。\n\n比如说滚动事件：我其实并不关心滚动中间的过程，我只关心最终滚动到了哪里。\n\nrequestAnimationFrame 这个api可以做防抖。\n\n\n\n\n\n参考文章：\n\n- 防抖与节流：https://juejin.cn/post/6885250789825052679\n\n\n\n\n\n\n\n## 代码优化\n\n\n\n### JS的开销\n\n静态资源有很多种：js、图片、css、字体等。这些资源都有可能会很大，但是JS的开销仍然是最昂贵的，因为JS除了加载资源之外，还需要经历**解析&编译**、**执行的**过程。\n\n\n\n\n\n\n\n### 如何缩短JS的解析事件\n\n\n\n\n\n### Web loading is a Journey\n\n![img](images/1*vSGOCrLV9MiLhpmPid1CHQ.png)\n\n\n\n### V8引擎\n\n\n\n\n\n\n\n## 补充\n\n\n\n- 首屏尽快打开，剩下的内容延迟加载，减少初次加载的资源量。首屏的内容是可以确定的。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "14-前端性能优化/04-静态资源优化.md",
    "content": "---\ntitle: 04-静态资源优化\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 图片格式和应用场景\n\n### JPEG 格式\n\nJPEG（Joint Photographic Experts Group）是一种针对彩色照片而广泛使用的有损压缩图形格式，属于位图。\n\n常用文件扩展名为`.jpg`，也有 `.jpeg`和`.jpe`。JPEG 在互联网上常被应用于存储和传输照片。\n\n- 适合：颜色丰富的照片、彩色图大焦点图、通栏 banner 图；结构不规则的图形。\n\n- 不适合：线条图形和文字、图标图形，因为它的压缩算法不太这些类型的图形；并且不支持透明度。\n\n### PNG 格式\n\nPNG（Portable Network Graphics）是一种无损压缩的位图图形格式，支持索引、灰度、RGB 三种颜色方案以及 Alpha 通道等特性。\n\nPNG 最初是作为替代 GIF 来设计的，能够显示 256 色，文件比 JPEG 或者 GIF 大，但是 PNG 非常好的保留了图像质量。支持 Alpha 通道的半透明和透明特性。最高支持 24 位彩色图像（PNG-24）和 8 位灰度图像（PNG-8）。\n\n- 适合：纯色、**透明**、线条绘图，图标；边缘清晰、有大块相同颜色区域；需要带**半透明**的图片。\n\n- 适合：由于是无损存储，所以不太适合体积太大的彩色图像\n\n\n比如说，如果你需要带透明背景的图片，此时就可以用 png 格式的图。\n\n### GIF 格式\n\nGIF（Graphics Interchange Format）是一种位图图形格式，以 8 位色（即 256 种颜色）重现真彩色的图像，采用 LZW 压缩算法进行编码。\n\n支持 256 色；仅支持完全透明和完全不透明；如果需要带动画效果的图片，GIF 是比较通用的选择。\n\n- 适合：动画，图标。\n\n- 不适合：每个像素只有 8 比特，不适合存储彩色图片。\n\n### Webp 格式\n\nWebp 是一种现代图像格式，可为图像提供无损压缩和有损压缩，这使得它非常灵活。由 Google 在购买 On2 Technologies 后发展出来，以 BSD 授权条款发布。\n\nWebp的优秀算法能同时保证图像质量和较小体积；可以插入多帧，实现动画效果；可以设置透明度；采用 8 位压缩算法。\n\n无损的 Webp 比 PNG 小 26%，有损的 Webp 比 JPEG 小 25-34％，比 GIF 有更好的动画。\n\n- 适合：适用于图形和半透明图像。\n\n\n### 总结\n\n- banner图、大图，可以用 jpg、webp格式。\n\n- 图标、带透明背景的图，可以用 png 格式。\n\n- 带动画效果的图，可以用 gif 格式。\n\n\n## 图片优化的常见方法\n\n### 1、用工具压缩图片\n\n**压缩 PNG 图片**：\n\n- 工具：[node-pngquant-native](https://www.npmjs.com/package/node-pngquant-native)\n\n- 介绍：跨平台、压缩比特别高，压缩png24非常好。\n\n安装方法：\n\n```\nnpm install node-pngquant-native\n```\n\n**压缩 JPEG 图片**：\n\n- 工具：[jpegtran](https://www.npmjs.com/package/jpegtran)\n\n- 官网：<https://www.npmjs.com/package/jpegtran>\n\n- 介绍：跨平台，但压缩的比率只有80-90%。\n\n安装方法：\n\n```bash\nnpm install –g jpegtran\n```\n\n使用方法：\n\n```bash\njpegtran -copy none -optimize -outfile output_file.jpg input_file.jpg\n```\n\n**压缩 GIF 图**：\n\n- 工具：Gifsicle\n\n- 官网（含安装方法）：<https://www.lcdf.org/gifsicle/>\n\n- 介绍：Gifsicle 通过改变每帧比例，减小 gif文件大小，同时可以使用透明来达到更小的文件大小，是目前公认的最好的解决方案。\n\n使用方法：\n\n```bash\n# 压缩命令。注意，这里是将压缩级别设置为3。如果将压缩级别设置为1或者2，则基本不压缩。\ngifsicle --optimize=3 -o out_file.gif in_file.gif\n\n# 裁掉透明部分\ngifsicle --optimize=3 --crop-transparency -o out_file.gif in_file.gif\n```\n\n### 2、将图片尺寸跟随网络环境进行变化\n\n**具体方案**：不同网络环境（Wifi/4G/3G）下，加载不同尺寸和像素的图片，通过在图片 URL 中添加参数来改变。\n\n图片 url 举例1：（图片的原始url链接）\n\n```\nhttps://img12.360buyimg.com/img/s3866x3866_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg\n```\n\n图片 url 举例2：（通过图片的url参数，将这张图的尺寸设置为200px）\n\n```\nhttps://img12.360buyimg.com/img/s200x200_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg\n```\n\n### 3、响应式图片\n\n**方法1**：通过 JavaScript 绑定事件，检测窗口大小，以此设置图片大小。\n\n\n**方法2**：CSS媒体查询。\n\n代码举例：（在 640px的窗口大小里，设置图片的尺寸为640px）\n\n```css\n@media screen and (max-width:640px) {\n  my_image{\n    width:640px;\n   }\n }\n```\n\n**方法3**：img标签的 `srcset` 属性。这个是 H5的新特性。\n\n代码举例：\n\n```html\n<img srcset=\"img-320w.jpg, img-640w.jpg 2x, img-960w.jpg 3x\" src=“img-960w.jpg”\nalt=“img”> （x 描述符：表示图像的设备像素）\n```\n\n### 4、逐步加载图像：lazyload、LQIP、LQIP\n\n**方法1**、使用统一占位符。俗称图片的`懒加载（lazyload）`。\n\n**方法2**、使用 **LQIP** 的图片加载方式。也就是说，在大图没有完全加载出来的情况下，先这张图对应的的低质量图片进行占位。\n\nLQIP（Low Quality Image Placeholders）：低质量图像占位符。这种技术背后的想法是，在网络环境较差的情况下，你可以尽快向用户展示完全可用的网页，为他们提供更好的体验。即使在更好的网络连接上，这仍然为用户提供了更快的可用页面，并且改善了体验。\n\n- 安装 LQIP 工具：`npm install lqip`\n\n- GitHub源码：https://github.com/zouhir/lqip-loader\n\n代码举例：（将目标图片转换为 LQIP 形式的图）\n\n```js\nconst lqip = require('lqip');\n\n//文件路径\nconst file = './in.png';\n\n//将输入的图片转为base64\nlqip.base64(file).then(res => {\n    // 色值\n    console.log(res);\n});\n\nlqip.palette(file).then(res => {\n    //这里输出的是base64的图片地址\n    console.log(res);\n});\n\n```\n\n另外，我们还可以使用 **SQIP** 的图片加载方式。\n\nSQIP（SVG Quality Image Placeholders）： SVG 格式的图像占位符。\n\n- 安装  SQIP 工具：`npm install sqip`\n\n- GitHub 源码：<https://github.com/axe312ger/sqip>\n\n代码举例：（将目标图片转换为 SQIP 形式的图）\n\n```js\nconst sqip = require('sqip');\n\nconst result =  sqip({\n    filename: './input_file.png',\n    numberOfPrimitives: 10 //可根据不同应用场景设置大小\n});\n\nconsole.log(result.final_svg);\n```\n\n### 5、雪碧图（Image spriting）\n\n雪碧图是比较常见的图片优化方式，也就是把多张小图合并成一张大图。这样的话，就只需做一次网络请求，减少图片的 http 请求次数。\n\n读者们可以自行查阅。\n\n### 6、有些场景下，并不需要图片文件\n\n有些场景下，并不需要图片，我们可以用其他的方式来代替图片。\n\n举例：\n\n- Web Font 代替图片\n\n- 使用 Data URI 代替图片。base64就是属于 Data URI的方式。\n\n### 7、在服务器端进行图片自动优化\n\n\n图片服务器自动化优化是可以在图片 URL 链接上增加不同特殊参数，服务器自动化生成。通过这些参数，可以设置图片的不同格式、大小、质量。\n\n**常见处理方式**：\n\n- 图片裁剪：按长边、短边、填充、拉伸等缩放。\n\n- 图片格式转换：支持 JPG，GIF，PNG，WebP 等，支持不同的图片压缩率。\n\n- 图片处理：添加图片水印、高斯模糊、重心处理、裁剪边框等。\n\n- AI 能力：鉴黄、涉政、智能抠图、智能排版、智能配色、智能合成等 AI 功能。\n\n**图片举例**：\n\n比如JD公司的图片链接，就会在服务器端做优化处理。通过修改图片链接中的参数，就能自动达到相应的优化效果。\n\n原始图片链接：\n\n```\nhttps://img12.360buyimg.com/img/s3866x3866_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg\n```\n\n将图片压缩为 200*150：\n\n```\nhttps://img12.360buyimg.com/img/s200x200_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg\n```\n\n将图片转换为 webp 格式：\n\n```\nhttps://img12.360buyimg.com/img/s200x200_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.webp\n```\n\n将图片质量压缩至10%：\n\n```\nhttps://img12.360buyimg.com/img/s3866x3866_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg.q10\n```\n\n\n## HTML优化\n\n### 1、精简 HTML 代码\n\n- 减少 HTML 的嵌套。\n\n- 减少 DOM 节点数。\n\n- 减少无语义代码（比如: <div class=“clear”></div> 消除浮动，其实可以用css来处理）。\n\n- 删除 http 或者 https：如果URL的协议头和当前页面的协议头一致的，或者此 URL 在多个协议头都是可用的，则可以考虑删除协议头。\n\n- 删除多余的空格、换行符、缩进和不必要的注释。\n\n- 省略冗余标签和属性。\n\n- 使用相对路径的 URL。\n\n### 2、文件放在合适位置\n\n- CSS 样式文件链接尽量放在页面头部。\n\nCSS 加载不会阻塞 DOM tree 解析，但是会阻塞 DOM Tree 渲染，也会阻塞后面 JS 执行。\n\n任何 body 元素之前，可以确保在文档部分中解析了所有 CSS 样式（内联和外联），从而减少了浏览器必须重排文档的次数。\n\n如果放置页面底部，就要等待最后一个 CSS 文件下载完成，此时会出现\"白屏\"，影响用户体验。\n\n- JS 引用放在 HTML 底部\n\n防止 JS 在加载、解析、执行时，阻塞了页面后续元素的正常渲染。\n\n### 4、增强用户体验\n\n- 设置 favicon.ico\n\n网站如果不设置 favicon.ico，控制台会报错。另外页面加载过程中如果没有图标，则会出现 loading 过程，也不利于记忆网站品牌，建议统一添加。\n\n- 增加首屏必要的 CSS 和 JS\n\n页面如果需要等待所的依赖的 JS 和 CSS 加载完成才显示，则在渲染过程中页面会一直显示空白，影响用户体验，建议在首屏增加必要的 CSS 和 JS，比如页面框架背景图片或者loading 图标，内联在 HTML 页面中。这样做，首屏能快速显示出来，缓解用户焦虑。现在很多网页在初始化的时候，流行做**骨架屏**，小伙伴们也可以研究下。\n\n## CSS优化\n### 1、提升 CSS 渲染性能\n\n- 谨慎使用 expensive 属性，这类属性比较耗浏览器的性能。比如：`nth-child` 伪类；`position: fixed` 定位。\n\n- 尽量减少样式的层级数。\n\n比如：`div ul li span i {color: blue;}`这样的层级就太深了。建议给 i 标签设置 class属性，然后通过class直接设置样式属性，可以提升浏览器的查询效率。\n\n- 尽量避免使用占用过多 CPU 和内存的属性。比如：`text-indnt:-99999px`。\n\n- 尽量少使用耗电量大的属性。比如：CSS3 3D transforms、CSS3 transitions、Opacity 这样的属性会消耗GPU。\n\n### 2、合适使用 CSS 选择器\n\n- 尽量避免使用 CSS 表达式。\n\n比如 `background-color: expression( (new Date()).getHours()%2 ? \"#FFF\" : \"#000\" );`这个属性的意思是，每间隔两小时，改变白景色。\n\n- 尽量避免使用通配选择器。\n\n比如 `body > a {font-weight:blod;}`这样的属性，可能会把 body 里所有的标签遍历一遍，才找到 a 标签，比较耗时。\n\n- 尽量避免类正则的属性选择器：`*=， |=， ^=， $=`\n\n### 3、提升 CSS 文件加载性能\n\n- 使用外链的 CSS。\n\n我们知道，内联的 css 是在html 内部写的。相比之下，外链的 CSS文件是放在CDN上的，可以缓存，既能减少 html 页面的体积大小，也能利用缓存减少资源的请求。\n\n- 尽量避免使用 @import 方法\n\n整个CSS加载完成后，浏览器会把 import 中所有依赖的文件全部加载完成后，浏览器才会接着往下渲染。这个过程会阻塞CSS文件的加载过程。\n\n### 4、精简 CSS 代码\n\n- 使用缩写语句\n\n- 删除不必要的零。比如 0.2 可以写成 .2\n\n- 删除不必要的单位，比如 0px 可以写成 0\n\n- 删除过多的空格；注释言简意赅\n\n- 尽量减少样式表的大小\n\n当然，很多地方可以在编译时，通过压缩工具来处理；但是我们在写代码时，也应该有良好的编码习惯。\n### 5、合理使用 Web Fonts\n\n- 将字体文件部署在 CDN 上。\n\n- 或者将字体以 base64 形式保存在 CSS 中并通过 localStorage 进行缓存\n\n- Google 字体库因为某些不可抗拒原因，应该使用国内托管服务\n\n### 6、CSS 动画优化\n\n- 尽量避免同时出现过多动画。\n\n- 延迟动画初始化：让其他的重要的CSS样式优先渲染。\n\n- 结合 SVG。\n\n\n## JavaScript 总体优化\n\n### 提升 JavaScript 文件加载性能\n\n加载元素的顺序 CSS 文件放在 <head> 里， JavaScript 文件放在 <body> 里。\n\n### JavaScript 变量和函数优化\n\n- 尽量使用 id 选择器\n\n- 尽量避免使用 eval\n\n- JavaScript 函数尽可能保持简洁\n\n- 使用事件节流函数\n\n- 使用事件委托\n\n### JavaScript 动画优化\n\n- 避免添加大量 JavaScript 动画\n\n- 尽量使用 CSS3 动画\n\n- 尽量使用 Canvas 动画\n\n- 合理使用 requestAnimationFrame 动画代替 setTimeout、setInterval\n\n- requestAnimationFrame可以在正确的时间进行渲染，setTimeout（callback）和setInterval（callback）无法保证 callback 回调函数的执行时机。\n\n### 合理使用缓存\n\n- 合理缓存 DOM 对象\n\n- 缓存列表长度\n\n- 使用可缓存的 Ajax\n\n##  JavaScript 缓存优化\n\n### Cookie\n\n通常由浏览器存储，然后将 Cookie 与每个后续请求一起发送到同一服务器。收到HTTP 请求时，服务器可以发送带有 Cookie 的 header 头。可以给 Cookie 设置有效时间。\n\n应用：\n\n- 会话管理：登录名，购物车商品，游戏得分或服务器应要记录的其他任何内容\n\n- 个性化：用户首选项，主题或其他设置\n\n- 跟踪：记录和分析用户行为，比如visitkey\n\n### sessionStorage\n\n创建一个本地存储的键/值对。\n\n应用：\n\n- 缓存。\n\n- 页面应用页面之间传值。\n\n### LocalStorage\n\n本地存储。\n\n应用于：\n\n- 缓存静态文件内容 JavaScript /CSS（比如百度M站首页）\n\n- 缓存不常变更的 API 接口数据\n\n- 储存地理位置信息\n\n- 浏览在页面的具体位置\n\n### IndexedDB\n\n索引数据库。\n\n应用：\n\n- 客户端存储大量结构化数据\n\n- 没有网络连接的情况下使用（比如 Google Doc、石墨文档）\n\n- 将冗余、很少修改、但经常访问的数据，以避免随时从服务器获取数据\n\n## JavaScript 模块化加载方案和选型\n\n- CommonJS\n\n旨在 Web 浏览器之外为 JavaScript 建立模块生态系统。Node.js 模块化方案受 CommonJS。\n\n\n- AMD (Asynchronous Module Definition)（异步模块定义）规范。\n\nRequireJS 模块化加载器：基于 AMD API 实现。\n\n- CMD（ Common Module Definition）（通用模块定义）规范。\n\nSeaJS 模块化加载器：遵循 CMD API 编写。\n\n- ES6 import。\n\n## 减少回流和重绘重要举措\n\n### CSS\n\n- 避免过多样式嵌套\n\n- 避免使用 CSS 表达式\n\n- 使用绝对定位，可以让动画元素脱离文档流\n\n- 避免使用 table 布局\n\n- 尽量不使用 float 布局\n\n- 图片最好设置好 width 和 height\n\n- 尽量简化浏览器不必要的任务，减少页面重新布局\n\n- 使用 Viewport 设置屏幕缩放级别\n\n- 避免频繁设置样式，最好把新 style 属性设置完成后，进行一次性更改\n\n- 避免使用引起回流/重绘的属性，最好把相应变量缓存起来\n\n### JavaScript\n\n- 最小化回流和重排：为了减少回流发生次数，避免频繁或操作 DOM，可以合并多次对 DOM 修改，然后一次性批量处理。\n\n- 控制绘制过程和绘制区域：绘制过程开销比较大的属性设置应该尽量避免减少使用；同时，减少绘制区域范围。\n\n\n## DOM 编程优化的⽅式方法\n\n### 控制 DOM 大小\n\n众所周知，页面交互卡顿和流畅度很大一部分原因就是页面有大量 DOM 元素。想象一下，从一个上万节点的 DOM 树上，使用 querySelectorAll 或 getElementByTagName 方法查找某一个节点，是非常耗时的。另外元素绑定事件，事件冒泡和事件捕获的执行也会相对耗时。\n\n通常控制 DOM 大小的技巧包括：\n\n- 合理的业务逻辑\n\n- 延迟加载即将呈现的内容\n\n### 简化 DOM 操作\n\n对DOM节点的操作统一处理后，再统一插入到 DOM Tree中。\n\n可以使用 fragment，尽量不在页面 DOM Tree 里直接操作。\n\n现在流行的框架 Angular、React、Vue 都在使用虚拟 DOM 技术，通过 diff 算法简化和减少 DOM 操作。\n\n\n## 静态文件压缩工具介绍\n\nHTML 压缩工具：\n\n- html-minifier：https://www.npmjs.com/package/html-minifier\n\n\nCSS 压缩工具：\n\n- clean-css：https://www.npmjs.com/package/clean-css\n\nJavaScript 压缩工具：\n\n- uglify-js：https://www.npmjs.com/package/uglify-js\n\n- 使用方法：uglifyjs in.js -o out.js\n\n## 静态⽂文件打包⽅方案\n\n- 公共组件拆分\n\n- 压缩： JavaScript /CSS/图片\n\n- 合并： JavaScript /CSS 文件合并，CSS Sprite\n\n- Combo： JavaScript /CSS 文件\n\n## 静态⽂文件版本号更新策略\n\n缓存更新：CDN 或 ng 后台刷新文件路径，更新文件header头。\n\n文件 name.v1-v100.js：\n\n- 大功能迭代每次新增一个大版本，比如由 v1 到 v2\n\n- 小功能迭代新增加 0.0.1 或者 0.1.0，比如从 v1.0.0 至 v1.0.1\n\n- 年末 ng 统一配置所有版本 302 至最新版\n\n时间戳.文件 name.js：以每次上线时间点做差异。\n\nhash.文件。以文件内容 hash 值做 key。\n\n## 前端构建工具介绍和选型建议\n\n### 常用构建工具\n\n- Gulp：通过流（Stream）来简化多个任务间的配置和输出，配置代码相对较少。\n\n- Webpack：预编译，中间文件在内存中处理，支持多种模块化，配置相对很简单。\n\n- FIS\n\n\n### webpack 打包优化\n\n- 定位体积大的模块\n\n- 删除没有使用的依赖\n\n- 生产模式进行公共依赖包抽离\n\n- 开发模式进行 DLL & DllReference 方式优化\n\n"
  },
  {
    "path": "14-前端性能优化/05-页面渲染性能优化.md",
    "content": "---\ntitle: 05-页面渲染性能优化\npublish: true\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n## 浏览器渲染过程\n\n![](https://img.smyhvae.com/20210114_2115.png)\n\n1. 浏览器解析 HTML，生成 DOM Tree（Parse HTML）。\n\n2. 浏览器解析 CSS，生成 CSSOM（CSS Object Model）Tree。\n\n3. JavaScript 会通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree，浏览器将 DOM Tree 和 CSSOM Tree 合成渲染树（Render Tree）。\n\n4. 布局（Layout）：根据生成的 Render Tree，进行回流，以计算每个节点的几何信息（位置、大小、字体样式等等）。\n\n5. 绘制（Painting）：根据渲染树和回流得到的几何信息，得到每个节点的绝对像素。\n\n6. 展示（Display）：将像素发送给图形处理器（GPU），展示在页面上。\n\n## 页面渲染技术方案总览\n\n**服务端渲染**：\n\n-   后端同步渲染、同构直出、BigPipe。\n\n**客户端渲染**：\n\n-   JavaScript 渲染：静态化、前后端分离、单页面应用\n\n-   Web App：React、Vue、PWA\n\n-   Hybrid App：PhoneGap 、AppCan 等\n\n-   跨平台开发：RN 、Flutter 、小程序等。\n\n-   原生 App：iOS 、Android\n\n建议：\n\n-   依赖业务形式、依赖团队规模、依赖技术水平。\n\n## 静态化技术方案\n\n静态化是使动态化的网站生成静态 HTML 页面以供用户更好访问的技术，一般分为纯动态化和伪动态化。\n\n技术优势：\n\n-   提高了页面访问速度，降低了服务器的负担，因为访问页面时不需要每次去访问数据库。\n\n-   提高网站内容被搜索引擎搜索到的几率，因为搜索引擎更喜欢静态页面。\n\n-   网站更稳定，如果后端程序、数据库出现问题，会直接影响网站的正常访问，而静态化页面有缓存，更不容易出现问题。\n\n技术不足：\n\n-   服务器存储占用问题，因为页面量级在增加，要占用大量硬盘空间。\n\n-   静态页面中的链接更新问题会有死链或者错误链接问题。\n\n技术实现：\n\n-   跑定时任务，将已有的动态内容进行重定，生成静态的 HTML 页面。\n\n-   利用模板技术，将模板引擎中模板字符替换为从数据库字段中取出来的值， 同时生成 HTML 文件。\n\n协作方式：\n\n-   前端统一写好带有交互的完整静态页面。\n\n-   后端拆分出静态页面文件，并嵌套在后端模板文件中。\n\n选型建议：后端研发人员充分，又需要考虑用户体验、服务器负载的业务。\n\n## 前后端分离技术与实现\n\n前后端分离是指研发人员分离、业务代码分离、后端实现业务接口，前端渲染页面。\n\n技术实现：\n\n-   后端只负责功能接口实现，提供按照约定的数据格式并封装好的 API 接口。\n\n-   前端负责业务具体实现，获取到 API 接口数据后，进行页面模板拼接和渲染，独立上线。\n\n协作方式：\n\n-   前端负责实现页面前端交互，根据后端 API 接口拼装前端模板。\n\n-   后端专注于业务功能实现和 API 接口封装。\n\n技术优势：\n\n-   团队更加专注\n\n-   提升了开发效率\n\n-   增加代码可维护性\n\n技术架构：\n\n-   后端架构：Java、C++、PHP、 + Nginx，使用微服务（比如 Dubbo 等）等实现业务的解耦，所有的服务使用某种协议提供不同的服务（比如 JSF 等） 。\n\n-   前端架构：使用 Angular、React、Vue 前端框架并部署页面至 CDN。\n\n-   前端架构 2：使用 Angular、React、Vue 前端框架并部署在 Node Server。\n\n技术不足：\n\n-   因为前端需要负责一大部分业务逻辑实现，和服务端同步、静态化，需要前端人力非常多。\n\n-   页面数据异步渲染，不利于 SEO，搜索引擎更喜欢纯静态页面。\n\n选型建议：\n\n-   这是大型互联网公司正在采用的开发模式，一句话，如果考虑用户体验，以及前端人力够用，就可以积极采用。\n\n## 单页面应用技术方案\n\n单页应用（single-page application，缩写 SPA），通过动态重写当前页面，来与用户交互，而非传统的从服务器重新加载整个新页面。这种方法在使用过程中不需要重新加载页面，避免了页面之间切换打断用户体验，使应用程序更像一个桌面应用程序。\n\n技术优点：\n\n-   不错的加载速度：用户往往感觉页面加载非常快，因为一进入页面就能看到页面元素；\n\n-   良好的交互体验：进行局部渲染，避免不必要的页面间跳转和重复渲染；\n\n-   前后端职责分离：前端进行页面交互逻辑，后端负责业务逻辑；\n\n-   减轻服务器负载：服务器只处理数据接口输出，不用考虑页面模板渲染和 HTML 展示。\n\n技术缺点：\n\n-   开发成本相对较高\n\n-   首次页面加载时间过多\n\n-   SEO 难度比较大\n\n技术实现：\n\n-   使用 React、Vue 框架可以很好的。\n\n## BigPipe 简介和工作模式\n\nBigPipe 通过将页面加载到称为 Pagelet 的小部件中，来加快页面渲染速度，并允许浏览器在 PHB 服务器呈现页面的同时，一直请求页面不同区块的结构，类似一个“流”传输管道。\n\n**技术实现**：\n\n1. 浏览器从服务器请求页面。\n\n2. Server 迅速呈现一个包含 <head> 标记的页面框架，以及一个包含空 div 元素的主体，这些元素充当 Pagelet 的容器。由于该页面尚未完成，因此与浏览器的 HTTP 连接保持打开状态。\n\n3. 浏览器将开始下载 bigpipe.js 文件，然后它将开始呈现页面。\n\n4. PHP 服务器进程仍在执行，并且一次构建每个 Pagelet 。Pagelet 完成后，其结果将在`<script> BigPipe.onArrive（…）</ script>` 标记内发送到浏览器。\n\n5. 浏览器将收到的 html 代码注入正确的位置。如果小页面需要任何 CSS 资源，则也将下载这些 CSS 资源。\n\n6. 接收完所有的页面集之后，浏览器将开始加载那些页面集所需的所有外部 JavaScript 文件。\n\n7. 下载 JavaScript 后，浏览器将执行所有内联 JavaScript。\n\n## 同构直出技术方案\n\n一套代码既可以在服务端运行又可以在客户端运行，这就是同构（Universal）。\n\n技术优势：\n\n-   性能: 降低首屏渲染时间\n\n-   SEO: 服务端渲染对搜索引擎的爬取有着天然的优势\n\n-   兼容性: 有效规避客户端兼容性问题，比如白屏\n\n-   代码同构：直接上线两个版本，利于灾备。\n\n技术实现：\n\n-   next.js：服务器端渲染 React 组件框架（参考查看：https://nextjs.org/）, React 采用 ReactDOMServer 调用 renderToString() 方法。\n\n-   gatsbyjs：服务端 React 渲染框架（参考查看： https://www.gatsbyjs.org/）。\n\n-   nuxt.js：服务器端渲染 Vue 组件框架（参考查看：https://nuxtjs.org/）, Vue 采用 vue-server-renderer 调用 renderToString() 方法。\n\n协作方式：\n\n-   后端专注于业务功能实现和 API 接口封装。\n\n-   前端负责实现页面前端交互，根据后端 API 接口拼装前端模板，页面渲染，以及服务器维护。\n\n选型建议：\n\n-   前端要处理 Node server 的机器环境、代码部署、日志、容灾、监控等以往后端人员需要具备运维知识，前端人员的综合能力要求会比以往要高。\n\n-   前端项目开发周期变长了，需要事先和产品、运营沟通排期问题。\n\n## PWA 技术方案和实现思路\n\nProgressive Web App，简称 PWA，PWA 应用是使用特定技术和标准模式来开发的 Web 应用，这将同时赋予它们 Web 应用和原生应用的特性。\n\n技术优势：\n\n-   用户可以用手机屏幕启动应用，即使在离线状态或者弱网下，通过事先缓存的资源，也可正常加载运行当前应用，可以完全消除对网络的依赖，从而给用户非常可靠的体验。\n\n-   因为预先缓存了资源，部分资源无须经过网络，即秒开页面。\n\n-   和移动设备上的原生应用一样，具有沉浸式的用户体验。\n\n-   可以给用户发送离线推送消息。\n\n技术实现：\n\n-   全站改造成 HTTPS，没有 HTTPS 就没有 Service Worker。\n\n-   应用 Service Worker 技术提升性能，离线提供静态资源文件，提升首屏用户体验。\n\n-   使用 App Manifest。\n\n-   最后可以考虑离线消息推送等功能。\n\n浏览器兼容性：\n\n-   ServiceWorkerGlobalScope API：88%\n\n-   Web App Manifest 83%\n\n## 页面加载策略优化\n\n-   懒加载\n\n-   预加载\n\n-   预渲染\n\n-   按需加载\n\n下面具体展开讲讲。\n\n### 懒加载\n\n懒加载也叫延迟加载，指的是长网页中延迟加载特定元素（可以是图片，也可以是 JS/CSS 文件，当然也可以是 JavaScript 的特定函数和方法，以下简称“懒加载元素”）。\n\n好处：可以减少当前屏无效资源的加载。\n\n技术实现举例：把页面上“懒加载元素”src 属性设置为空字符，把真实的 src 属性写在 data-lazy 属性中，当页面滚动的时候监听 scroll 事件，如果“懒加载元素”在可视区域内，就把图片的 src 属性或者文件 URL 路径设置成 data-lazy 属性值。\n\n### 预加载\n\n可以使用预加载让浏览器来预先加载某些资源（比如图片、JS/CSS/模板），而这些资源是在将来才会被使用到的。简单来说，就是将所需资源提前加载到浏览器本地，这样后面在需要使用的时候就可以直接从浏览器缓存中取了，而不用再重新开始加载。\n\n使用场景：如果你希望这个资源能尽快显示给用户，就可以使用预加载。\n\n好处：减少用户后续加载资源等待的时间。\n\n**技术实现举例**：\n\n1. HTML 标签：\n\n```html\n<img src=\"https://xxx.jpg\" style=\"display: none\" />\n```\n\n2、使用 Image 对象：\n\n```js\nconst image = new Image();\n\nimage.src = 'https://xxx.jpg';\n```\n\n3、使用 preload、prefetch 和 preconnect：\n\n```html\n<link rel=“preload” href=“src/style.css” as=“style”>\n\n<link rel=\"prefetch\" href=\"scr/image.png\" />\n\n<link rel=\"dns-prefetch\" href=\"https://my.com\" />\n\n<link rel=\"preconnect\" href=\"https://my.com\" crossorigin />\n```\n\n### 预渲染\n\n有一种预加载组件的方式就是提前渲染它。在页面中渲染组件，但是并不在页面中展示。也就是渲染完成后，先隐藏起来，用的时候再展示。\n\n实现举例：\n\n```html\n<link rel=\"prerender\" href=\"https://my.com\" />\n```\n\n### 按需加载\n\n-   常规按需加载（如 JS 原生、jQuery）\n\n-   不同 App 按需加载（如 JS-SDK 脚本文件）\n\n-   不同设备按需加载（如 PC 端和 HTML5 端样式文件）\n\n-   不同分辨率按需加载（CSS Media Query）\n\nReact 异步加载举例：\n\n```javascript\nconst componentA = (location, callback) => {\n    require.ensure(\n        [],\n        (require) => {\n            callback(null, require('modules/componentA'));\n        },\n        'componentA'\n    );\n};\nconst componentB = (location, callback) => {\n    require.ensure(\n        [],\n        (require) => {\n            callback(null, require('modules/componentB'));\n        },\n        'componentB'\n    );\n};\n<Router history={history}>\n    <Route path=\"/\" component={App}>\n        <Route path=\"componentA\" getComponent={componentA}></Route>\n        <Route path=\"componentB\" getComponent={componentB}></Route>\n    </Route>\n</Router>;\n```\n\nVue 异步加载举例：\n\n```javascript\nimport Vue from 'vue';\nimport App from './App.vue';\nimport VueRouter from 'vue-router';\n\nVue.use(VueRouter);\nconst componentA = resolve => require(['src/a.vue' ], resolve);\nconst componentB = resolve => require(['src/b.vue' ], resolve);\nconst router = new VueRouter({\n    routes: [{path:\"a”,name:\"/a”,component:componentA},\n     {path:\"b”,name:\"/b”,component:componentB}]\n})\nnew Vue({\n    el: '#app',\n    router: router,\n    render: h => h(App)\n})\n```\n\n## 接口服务调用优化\n\n1、接口合并：一个页面的众多业务接口和依赖的第三方接口，合并为一个部署在集群的接口统一调用，以减少页面接口请求数。\n\n2、接口上 CDN：主要基于接口性能考虑，我们可以把**不需要实时更新的接口同步至 CDN**，等此接口内容变更之后自动同步至 CDN 集群上。如果一定时间内未请求到数据，会用源站接口再次请求。\n\n3、接口域名上 CDN：增强可用性、稳定性。\n\n\n4、接口降级：核心接口进行降级用基础接口进行业务实现，比如千人千面的推荐接口，在大促时间点可以直接运营编辑的数据。另外如果接口无访问，建议使用兜底数据。\n\n5、接口监控：监控接口成功率，不只是常说的 TP99，也包括弱网、超时、网络异常、网络切换等一段情况的监控情况。排查出来问题后，需要联合后端、运维、网络岗位人员一并解决。\n\n## 接口缓存策略优化\n\n1、Ajax/fetch 缓存：前端请求时候带上 cache，依赖浏览器本身缓存机制。\n\n2、本地缓存：异步接口数据优先使用本地 localStorage 中的缓存数据。\n\n3、多次请求：接口数据本地无 localStorage 缓存数据，重新再次发出 ajax 请求。\n\n"
  },
  {
    "path": "14-前端性能优化/lazyload&防抖动和节流阀.md",
    "content": "---\ntitle: 07-自定义按键修饰符&自定义指令\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n\n## lazyload\n\n用的最多的场景是：\n\n- 图片lazyload\n\n- 组件lazyload\n\n现在一般都单独做css的lazyload或者js的lazyload，因为这种方式，其实还是要加载图片和组件。\n\n\n\n### 图片lazyload\n\n图片一般是页面最大的资源，所以**非首屏**延迟加载很重要（让首屏尽快显示）。\n\n\n\n\n\n\n\n\n\n\n\n## 防抖动（Debouncing）和节流阀（Throtting）\n\n\n\n\n参考链接：\n\n- [实例解析防抖动（Debouncing）和节流阀（Throttling）](http://www.css88.com/archives/7010)\n\n\n"
  },
  {
    "path": "15-前端工程化/01-前端代码规范.md",
    "content": "\n\n## 前端代码规范-推荐资料\n\n### JS规范，重点推荐下面这两个\n\n1、Airbnb JavaScript Style Guide：\n\n- 英文原版：https://github.com/airbnb/javascript\n\n- 中文版：https://github.com/lin-123/javascript\n\n2、clean code JavaScript：\n\n- 英文原版：https://github.com/ryanmcdermott/clean-code-javascript\n\n- 中文版1：https://github.com/alivebao/clean-code-js\n\n- 中文版2：https://github.com/beginor/clean-code-javascript\n\n### eslint规范\n\n1、eslint-config-airbnb：\n\nhttps://www.npmjs.com/package/eslint-config-airbnb\n\nhttps://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb\n\n2、凹凸实验室的eslint规范：\n\nhttps://www.npmjs.com/package/eslint-config-o2team-wx\n\n备注：o2team-wx 这一套规则是参考了 StandardJS 和 Airbnb 的 JS 规范，然后结合业务中的最佳实践整理输出的。\n\n### 其他相关资料\n\n1、百度前端团队-前端各类规范集合：https://github.com/ecomfe/spec\n\n2、The mother of all demo apps：https://github.com/gothinkster/realworld\n\n3、JavaScript 代码规范：https://github.com/standard/standard/blob/master/docs/README-zhcn.md\n\n### 参考链接\n\n文案、编程、Git 风格规范汇总：https://www.bihell.com/article/130\n\ngithubStars：https://blog.teefing.top/posts/2019/05/27/githubstars.html\n\n\n\n\n## 其他链接\n\n- <http://blog.lovebug.cn/details/html/page02.html>\n\n- <https://github.com/ecomfe/spec>\n\n\n- <https://juejin.im/post/5e3d0362e51d4526d87c605d>\n\n- <https://juejin.im/post/592d4a5b0ce463006b43b6da>\n\n\n- <https://juejin.im/post/5b67e49551882508603d1431>\n\n- <https://juejin.im/post/5e1abeede51d453c913c340e>\n\n- <https://juejin.im/post/5d5d5197518825237330552d>\n\n- <http://alloyteam.github.io/CodeGuide/>\n\n\n"
  },
  {
    "path": "15-前端工程化/02-前端书籍推荐.md",
    "content": "\n## JavaScript 书籍\n\n\n### 《》\n\n\n\n"
  },
  {
    "path": "15-前端工程化/Vue开发积累.md",
    "content": "\n## Vue 开发积累\n\n\n### 001：scoped 关键字的作用\n\n在 `xx.vue` 组件中，我们可能会遇到带 `scoped` 关键字的样式。比如：\n\n```html\n<!-- Add \"scoped\" attribute to limit CSS to this component only -->\n<style scoped>\nh3 {\n  margin: 40px 0 0;\n}\nul {\n  list-style-type: none;\n  padding: 0;\n}\nli {\n  display: inline-block;\n  margin: 0 10px;\n}\na {\n  color: #42b983;\n}\n</style>\n```\n\n\n上方的`scoped`表示的是**作用域化**，样式只对当前子组件生效。\n\n"
  },
  {
    "path": "15-前端工程化/前端工程化.md",
    "content": "### 前端实战开发的各个方面\n\n前端实战开发包括很多方面，比如：\n\n- 跨终端技术体系\n\n- 前端监控体系\n\n- 多终端可视化页面搭建体系\n\n- 前端性能优化体系\n\n- 具体业务的架构设计\n\n- 前端通道建设\n\n- 搭建前端工程化技术体系\n\n- 网站前端基础架构升级\n\n- 研发日PV达千万的超大流量前端项目\n\n- 在 W3ctech、D2、FEDAY等技术大会中发表主题演讲\n\n- 分享前端性能优化方面的经验和见解"
  },
  {
    "path": "15-前端工程化/前端常见专有名词.md",
    "content": "\n\n\n## JS相关\n\n- JS装饰器\n\n\n\n## 性能相关\n\n- 防抖和节流\n\n- 滚动穿透\n\n\n## 样式相关\n\n- 多行文字截断\n\n\n## others\n\nnoop() 方法\n\n"
  },
  {
    "path": "15-前端工程化/前端的几道题目.md",
    "content": "\n\n几道面试题\n\n## 页面布局\n\n### 如何实现一个三栏布局，要求两边固定宽度、中间自适应。\n\n此题可以考察的知识点：\n\n- 圣杯布局\n\n- 双飞翼布局\n\n- flex布局（css3）\n\n\n### 让元素垂直居中\n\n**方式一：**如果宽高已知，可以利用绝对定位。\n\n**方式二：**用 translate 位移来做。（在宽高未知的情况下，也可以这样做）\n\n```css\n    div {\n        background-color: red;\n        position: absolute;       绝对定位的盒子\n        top: 50%;               首先，让上边线居中\n        transform: translateY(-50%);    然后，利用translate，往上走自己宽度的一半【推荐写法】\n    }\n```\n\n\n**方式三：**flex 布局\n\n```css\n    parentElement{\n        display: flex;/*Flex布局*/\n        display: -webkit-flex; /* Safari */\n        align-items: center;/*设置子元素在侧轴方向上的布局*/\n    }\n```\n\n\n参考链接：\n\n- <https://www.zhihu.com/question/20543196>\n\n- [水平垂直居中方案与flexbox布局](https://www.cnblogs.com/coco1s/p/4444383.html)\n\n\n\n\n## 变量提升\n\n\n**问题**：说一下你对JavaScript变量提升的理解。\n\n**定义**：\n\n在函数体内部，声明变量，会把该变量提升到函数体的最顶端。注意：**只提升变量声明，不赋值**。\n\n**代码1**：\n\n```javascript\n    fn();\n\n    function fn() {\n        var x = 1;\n        var y = 2;\n    }\n```\n\n\n上方代码中：\n\n（1）给fn创建函数上下文，找到fn中**所有**用var声明的变量（即x和y）；\n\n（2）将这些变量初始化为undefined；\n\n（3）将x赋值为1，将y赋值为2。\n\n\n**代码2**：\n\n\n```javascript\n    fn2();\n\n    function fn2() {\n        console.log(2);\n\n    }\n```\n\n\n上方代码中：\n\n（1）创建全局上下文，找到所有用function声明的变量，在环境中“创建”这些变量。\n\n（2）将这些变量**初始化**，并**赋值**为 `function(){ console.log(2) }`（并不是undefined）\n\n（3）开始执行代码`fn2();`\n\n**代码3**：（let的出现）\n\n```javascript\n{\n  let x = 1\n  x = 2\n}\n```\n\n\n上方代码中：\n\n（1）找到所有用 let 声明的变量，在环境中「创建」这些变量\n\n（2）开始执行代码（注意现在还没有初始化）\n\n（3）执行 x = 1，将 x 「初始化」为 1（这并不是一次赋值，如果代码是 let x，就将 x 初始化为 undefined）\n\n（4）执行 x = 2，对 x 进行「赋值」\n\n\n\n代码4：\n\n```javascript\nlet x = 'global'\n{\n  console.log(x) // Uncaught ReferenceError: x is not defined\n  let x = 1\n}\n```\n\n原因有两个：\n\n- console.log(x) 中的 x 指的是下面的 x，而不是全局的 x\n\n- 执行 log 时 x 还没「初始化」，所以不能使用（也就是所谓的暂时死区）\n\n看到这里，你应该明白了 let 到底有没有提升：\n\n- let 的「创建」过程被提升了，但是初始化没有提升。\n\n- var 的「创建」和「初始化」都被提升了。\n\n- function 的「创建」「初始化」和「赋值」都被提升了。\n\n\n参考链接：\n\n- [我用了两个月的时间才理解 let](https://zhuanlan.zhihu.com/p/28140450)\n\n\n### this\n\n问题：下方代码的打印结果是什么？\n\n```javascript\n    function A() {\n        this.name = 'smyhvae';\n    }\n\n    A.prototype.test = function () {\n        setTimeout(function () {\n            console.log(this.name);\n        }, 1)\n    }\n\n    var a = new A();\n    a.test();\n```\n\n打印结果是window.name，但实际的结果是空的。\n\n这个神奇的特性，被用来解决跨域数据传递。（网上可以查一下iframe相关）\n\n\n\n**总结1**：this永远指向**函数运行时所在的对象**，而不是函数被创建时所在的对象。即：**谁调用**，指向谁。\n\n**举例**：\n\n```javascript\n    var name = '全局';\n\n    function getName() {\n        var name = '局部';\n        return this.name;\n    };\n\n    alert(getName());\n\n```\n\n上方代码的打印结果是：`全局`。\n\n分析：`getName()`这个函数其实是window调用的，所以this指向的window，因为外部有name这个变量，所以打印结果为`全局`。\n\n\n**总结2**：没有明确的当前对象时，this永远指向window。这个在setTimeout里比较常见。\n\n\n\n### apply、call、bind的区别\n\n\n\n\n\n## 链式调用\n\n**问题**：如何实现类似jQuery的链式调用？\n\n答案：一直return `this`就好了。\n\n\n## Yslow和pageSpeed\n\n\nYslow和pageSpeed你知道怎么用吗？你记得其中多少规则？\n\n\n\n## DNS的查询时间\n\n**问题：**前端怎样拿到DNS的查询时间？\n\n### H5中的方法：performance.timing\n\nwindow.performance这个api可以用来做前端性能监控。其中，timing这个方法。。\n\n\n参考链接：\n\n- <https://github.com/fredshare/blog/issues/5>\n\n\n\n"
  },
  {
    "path": "15-前端工程化/前端监控技术.md",
    "content": "\n\n## 前言\n\n要监控的内容：\n\n- 业务数据\n\n- 稳定性\n\n- 性能\n\n- 错误\n\n- 用户操作路径\n\n\n怎么监控：\n\n- PV/UV、业务操作上报\n\n- 根据上报寻找异常\n\n- 将页面性能数据上报\n\n- 将页面产生错误上报\n\n- 跟踪用户操作路径\n\n\n\n\n\n\n"
  },
  {
    "path": "15-前端工程化/数组的常见操作.md",
    "content": "\n## 前言\n\n数组在实战开发中，使用得相当频繁。前端同学通过接口拿到json数据后，往往需要把数据进行各种形式的变换和展示。这个时候，数组的常见操作，就发挥了很大的作用。\n\n如果你对数组的基础知识不太熟悉，建议回去看看`03-JavaScript`的基础知识。\n\n掌握了基础知识之后，我们再来看看，实战开发中，数组都有哪些常见操作。\n\n## 数组的常见操作\n\n### 从对象数组中，将属性的值提取为数组\n\n一般人可能会想着通过 for 循环进行遍历，但这种做法不够简洁。\n\n最佳答案：\n\n```javascript\n      const arr1 = [\n        { skuId: \"123\", name: \"商品1\" },\n        { skuId: \"456\", name: \"商品2\" },\n        { skuId: \"789\", name: \"商品3\" }\n      ];\n\n      const skuIdArr = arr1.map(item => item.skuId); // 将数组 arr1 中的 skuId字段提取为一个新的数组\n      console.log(JSON.stringify(skuIdArr));\n```\n\n\n打印结果：\n\n```json\n\t[\"123\",\"456\",\"789\"]\n```\n\n\n- 参考链接：<https://codeday.me/bug/20170426/12102.html>"
  },
  {
    "path": "16-前端综合/01-Web前端开发流程和学习路线（详尽版）.md",
    "content": "\n---\ntitle: 01-Web前端开发流程和学习路线（详尽版）\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n> 本文的最新内容，会在[GitHub](https://github.com/qianguyihao/Web/blob/master/17-%E5%89%8D%E7%AB%AF%E7%BB%BC%E5%90%88/01-2022%E5%B9%B4Web%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E6%B5%81%E7%A8%8B%E5%92%8C%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF%EF%BC%88%E8%AF%A6%E5%B0%BD%E7%89%88%EF%BC%89.md)上同步更新，欢迎 star。大家完全不用担心这篇文章会过时，因为随着前端领域的技术更新，本文也会随之更新。\n\n\n## 前言\n\n\n前端侧重于人机交互和用户体验，后端侧重于业务逻辑和大规模数据处理。理论上，面向用户的产品里，所有问题（包括产品、设计、后端、甚至看不见的问题）的表现形式，都会暴露在前端，而只有部分问题（数据问题、计算问题、安全问题等）暴露在后端，这就意味着前端起到了至关重要的承载和连接作用。\n\n前端技术的更新日新月异；前端框架的技术选型百家争鸣；视觉审美的潮流不断更替；可视化效果酷炫无比；用户的运营体系逐渐精细化；适老化、无障碍化、青少年人群的诉求浮出水面；智能设备的升级和适配无穷无尽。所有的这一切，对前端领域和前端同学就一个要求：要折腾，爱折腾，反复折腾。\n\n## 一、前端开发流程\n\n### 需求分析\n\n- 需求背景和业务价值\n- 需求点分析\n- 业务流程和业务逻辑\n- 原型和交互设计\n- 输出PRD\n\n### 需求评审\n\n> 一般在做需求评审时，PRD里只有交互稿，尚未有视觉稿。需要在评审结束并达成一致后，再输出视觉稿。\n\n1、需求分析：需求点逐一讨论、需求合理性、交互评审、逻辑梳理，以及可能遗漏的部分。\n\n提示：逻辑梳理的过程很花时间，贯穿开发始末。\n\n2、涉及渠道/环境：\n\n> 渠道和环境，往往是需求盲点，也是影响技术选型和开发进度的关键因素。\n\n- App：App原生页面、**App内嵌H5**、App内嵌小程序。\n- 小程序：技术栈视角：小程序原生页面、**小程序内嵌H5**、App内嵌小程序。\n- 普通H5：微信H5、M站（即普通浏览器环境）\n- B端：运营管理平台等等\n\n3、可行性分析：是否有技术上的实现难点，是否有其他的依赖条件。\n\n数据来源：哪些是调接口，哪些是做成**可配置**，哪些是前端写死；可配置的部分，是前端读取，还是接口读取然后返给前端。提示：可配置的灵活性与风险正相关。\n\n异常流设计：容错、容灾、兜底、降级、回退机制、风险可控。prd一般只写了正常流的逻辑，异常流往往需要研发同学配合做全盘考虑。\n\n6、需求变更：如有需求不明确、改需求、加需求、砍需求、加时间、改时间、加人力等等，需要提前预判风险。\n\n### 视觉评审\n\n1、进度跟进：**视觉稿是分批交付，还是一次性给到**？这是要首先考虑的。\n\n按照历史经验，前端项目进度的延误，有一半的概率依赖于视觉稿的进度；因为一个新页面的开发，前端有30%~50%的工作在做页面构建。\n\n2、视觉稿的文件格式：\n\n- Sketch 原型设计软件：.sketch 格式。一般用来画**视觉稿**。\n- Figma 原型设计软件：.fig 格式。\n- Axure 原型设计软件：：.rp 格式。Axure 一般用来画**交互稿**。如果是输出高保真的视觉稿，推荐用 Sketch 或 Figma。\n- Photoshop 软件： .psd 格式。专门做**图片处理**。当然，有些CP外包人员的技能单一，喜欢用PS输出视觉稿。\n-  Adobe Illustrator 软件（简称AI软件）：.ai格式。制作矢量图。\n-  Adobe After Effects（AE） 软件：.aep 格式。制作动画。\n\n备注：Sketch 是Mac平台独有的原型设计软件，最为知名和常见。[Figma](https://www.figma.com/community/file/1038450359694759149) 是最近比较火的全平台原型设计软件，有取代 Sketch 的趋势。\n\n【划重点】交付视觉稿时，需要视觉同学输出“**带尺寸标注**”的视觉规范文件。\n\n3、检查需求：是否覆盖需求和交互设计中的全部设计点。\n\n4、检查视觉规范：\n\n- 样式和配色，是否符合页面和产品的整体风格。\n- 尺寸规范：移动端的视觉稿宽度是750px。\n- 排版、对齐、一致性。推荐阅读书籍《写个大家看的设计书》，了解基本的设计原则。\n- 字体：字号大小（一般是12px以上，特别小的是10px以上）、字重（注意bold属性值为700），以及有哪些**特殊字体**。尤其要注意字体的版权问题。\n\n5、哪些图是前端构建，哪些图是写死图片资源，哪些是可配置；可配置的图中，需要把每个元素做拆解，这个元素是合并到背景图中，还是单独切图，还是读取数据。\n\n6、切图格式：png（透明格式）、jpg\n\n切图的图片大小，不要太大。移动端的大图（比如幕帘弹窗的背景图）建议不超过50kb，小图建议不超过20kb。图片在上传之前，可以先在 https://tinypng.com/  上进行压缩。\n\n7、复杂图形、动画的实现难度和实现方式，技术评估。详见接下来要讲的「技术选型」。\n\n### 排期评估\n\n1、排期一般包含这几个要素：\n\n- 开发时间：视觉构建时长、接口文档（接口协议）交付时间、前后端联调时间、自测时间\n- 转体验时间\n- 转测时间\n- 上线时间（以及，需确认业务投放时间）\n\n2、评估排期时，**要根据视觉稿排期**，不要根据交互稿排期。这是首先要强调的。一个新页面的开发，前端有30%~50%的工作在做页面构建。 只看交互稿的话，无法评估真实的开发工作量。\n\n3、前端开发工作包括：概要设计、视觉构建、逻辑代码、前后端联调、自测、转体验。每一项都要单独拆解后评估时间，加在一起就是整体的排期。\n\n4、排期时，要考虑其它的依赖因素：比如视觉稿延期、需求不明确、接口进度、测试进度，当然最重要的是上线进度。紧急项目，经常是根据上线时间倒推开发排期。\n\n5、即将进入开发阶段后，与测试部门协调测试资源，确认转测时间；大型项目&重点项目，需要在需求评审阶段，提前知会测试部门，让其预留时间。\n\n6、如果自己有在并行开发其他项目，则排期时需要给自己预留 buffer。并行开发两个项目是家常便饭；但，这个项目在测试时，往往很难抽身去做别的项目，因为会一直被测试童鞋牵制。\n\n7、开发排期：如果开发排期有变更，需要立即周知其他相关人员，尤其是要评估测试排期的风险。测试排期比开发排期 更难变更。\n\n### 技术选型\n\n> 技术选型千变万化，百家争鸣。这里需要列出你所在部门的常用技术选型，并非市面上的技术栈罗列。\n\n1、页面开发框架：\n\n（1）多端页面：（小程序原生页面、H5）\n\n- [Taro 框架](https://taro-docs.jd.com/)（基于 React技术栈）\n\n注2：有些业务，一开始只做H5，后来迭代时，要求做小程序原生页面。这一点也要纳入需求评估。\n\n（2）H5页面：[Vue.js](https://v3.cn.vuejs.org/guide/introduction.html) 框架、React 框架\n\n（3）App端：\n\n- Android端开发语言：Kotlin（新）、Java（老）\n- iOS端开发语言：Swift（新）、Objective-C（老）\n\n（4）B端开发，UI框架：\n\n- React 技术栈：[Ant Design](https://ant.design/index-cn)（简称Antd）\n- Vue 技术栈：[Element](https://element.eleme.cn/#/zh-CN)、[Ant Design Vue](https://antdv.com/components/overview-cn)\n- 较简单的CSS响应式框架：[Bootstrap](https://www.bootcss.com/)\n\n（5）Node.js后端开发框架：\n\n- Koa：新一代 Node.js 框架。\n- [Egg.js](https://eggjs.github.io/zh/)：Egg 是在Koa基础上进一步封装的企业级Web开发框架。\n- Express：比较老的Node.js 框架。\n\n2、CSS预处理器：SASS\n\n3、复杂图形、动画的实现难度和实现方式，技术评估：\n\n- gif 动图：尽量不用。因为文件太大，且效果模糊。\n- CSS3 动画：适合简单的、有规律的动画。举例：[摆动的鱼](https://www.cnblogs.com/qianguyihao/p/8435182.html)、[京喜工厂](https://mp.weixin.qq.com/s/u5GHsA0vHz8A_MmGslRw2g)\n- [Canvas](https://www.liaoxuefeng.com/wiki/1022910821149312/1023022423592576)：Canvas 动画、小程序分享图采用 Canvas 绘制\n\n- 3D动画：[WebGL](https://www.zoo.team/article/webglabout)（[Three.js](http://www.webgl3d.cn/Three.js/) 是 WebGL 的综合库）常见案例：太阳系\n- 游戏框架：Cocos 引擎\n\n### 概要设计\n\n- 需求背景及资源\n- 风险评估\n- 技术选型\n- 项目结构设计\n-  主要功能点逻辑设计\n- 可扩展可复用设计\n- 依赖接口\n- 工作量拆解和排期\n\n### 开发环节\n\n1、代码设计：\n\n（1）目录结构设计、代码风格\n\n（2）公共组件、工具类设计：确保**可复用**、高内聚低耦合的原则。哪些可以复用平台的公共组件，哪些需要自己单独写 components、utils。\n\n（3）弹窗设计：如果一个页面有多个弹窗，建议先设计一个抽象的弹窗基类。**设计弹窗时，需要考虑的是**：\n\n- 设计原则：易扩展、复用性强\n- 避免重复代码\n- 避免同一时间出现多个弹窗\n- 弹窗的位置要严格居中（不能因为屏幕尺寸的大小变了，导致弹窗位置不居中）\n- 处理滚动穿透这个顽疾。\n\n2、视觉构建：\n\n（1）后端在开发接口时，前端做视觉构建；视觉构建完成后，前端根据接口文档的定义，通过 mock 数据的方式调接口，写前端逻辑；待接口开发完成后，可进入前后端联调阶段。\n\n（2）建议前端童鞋学会自己切图，可控程度更高，也能减少沟通成本。学会基本的 PS、Sketch操作就行，做一名合格的前端切图仔。\n\n（3）对于规则的样式和动画，建议用代码实现，而不是图片。图片会降低页面的打开性能，且大屏都显示效果比较模糊。\n\n（4）切图的尺寸要求：100%宽度以 750px 为准。\n\n（5）**像素级还原视觉稿**：推荐使用 [Snipaste](https://zh.snipaste.com/) 截图软件，按F1截图，F2贴图，然后调整贴图的透明度为50%，将贴图与前端页面进行像素级比对。\n\n3、业务逻辑实现：\n\n（1）建议先用**思维导图**，梳理业务逻辑，再着手写代码。思维导图有利于理清逻辑、事后复盘、高效给他人讲解，一目了然。重要的是思想，而不是用哪一款工具更酷。\n\n（2）在调用接口时，要明确前端自己的安全边界：假设接口字段异常时，前端需要有自己的降级措施，不能完全依赖和信任字段，导致让页面直接白屏、交互异常、甚至挂掉。\n\n（3）除了完成产品要求的业务逻辑之外，还要时刻考虑异常流的设计和容灾。\n\n（4）很多前端童鞋在做需求时，有个习惯是不喜欢细看prd，只对着交互稿和视觉稿进行开发。这样做虽然省事，但有三道手续不能少：\n\n- 开发前，一定要再和产品童鞋过一遍prd细节；\n- 开发过程中，随时和产品童鞋反复沟通确认；\n- 开发到80%时，自己对照prd，只字不差地阅读，看看是否有遗漏的地方。\n\n4、非功能性需求。业务代码写完后，还有很多细节需要打磨。比如：\n\n- 不同渠道的分享场景\n- 文案配置检查：运营配置端要做校验；是给产品运营用的，配置要尽量人性化。\n- 添加埋点：曝光上报、点击上报、呼吸上报\n- 监控上报、测试上报、badjs上报\n- 重复代码精简\n- 去掉 console.log、debugger 等多余的代码\n- 图片、字体等静态资源压缩\n- 常见的性能优化：骨架屏（按需）、图片懒加载、图片预加载、防抖节流、长列表*滚动*到可视区域动态加载\n- 用户体验完善：返回定位、滚动穿透\n- 屏幕适配\n- 小程序代码瘦身\n- 容灾演习\n\n5、代码提交：\n\n- 先 git pull，再 git push，减少代码冲突。\n- commit粒度要尽量细\n- commit之前，一定要diff代码，确认每一行改动，以免提交不必要的改动。\n- Commit Message 常用格式：feat、fix、docs、merge\n- 如合并代码时遇到冲突，一定要先解决完冲突后再提交代码。如冲突部分涉及到其他人的代码，一定要找到对应同学一起解决。\n\n6、自测：\n\n- 对照prd，查漏补缺。\n- 在真机上体验页面，而不是在模拟器上。\n- 写一部分测试用例，加快后续的测试进度。前面梳理的思维导图，其实就是测试的最佳素材。\n\n### 产品体验\n\n1、在真机体验，而不是在模拟器上。最好是 iOS和 Android 都要对比体验。\n\n2、体验时，记录整理各种 todolist：\n\n- 需求待确认 list：一些小的、风险可控的需求点，可以在体检阶段，集中向产品童鞋提出。\n- 开发未完成 list：有哪些尚未完成的部分，需要和产品童鞋交代清楚。\n- 已知 bug list\n- 体验问题 list：边体验，边记录产品反馈的问题，并在稍后同步给测试童鞋。\n- 依赖项 list：接口、视觉切图、真实的测试环境等等。\n\n### 代码评审/代码review\n\n> 代码 review 可以在测试期间进行。\n\nreview顺序：\n\n- 业务核心逻辑\n- 编码规范\n- 关键位置、容易踩坑的地方，需要注释详细\n- 系统保障（监控、容灾降级）\n- 系统安全和风险\n- 用户体验\n\n### 视觉走查\n\n>  视觉走查 可以在测试期间进行。\n\n视觉童鞋都有像素眼，即便是一两个像素的区别，他们都能瞧出来。所以，建议前端童鞋加强自测，努力做到**像素级还原视觉稿**。\n\n推荐前端童鞋使用 [Snipaste](https://zh.snipaste.com/) 截图软件，按F1截图，F2贴图，然后调整贴图的透明度为50%，将贴图与前端页面进行像素级比对。\n\n### 测试环节\n\n1、建议加强自测质量。进入测试阶段后，测试童鞋会进行一轮冒烟测试，如果质量不合格，将会被打回，这就很尴尬了。\n\n2、整理自测、测试、发布时需要的主流程checkList，每次迭代时都能用上。\n\n转测邮件的基本元素，包括但不仅限于：\n\n- prd链接、视觉稿链接\n- 页面链接\n- 项目相关人员\n- 数据配置系统\n- host 代理\n- 接口文档\n- 概要设计、前端开发整理（如果有的话）\n- 测试用例（如果有的话）\n- 核心业务逻辑梳理（如果有的话）\n- 体验问题列举\n- 测试重点建议\n- 风险点评估\n\n3、测试童鞋提的bug单，开发同学需要在 XX 小时内处理完成，否则会被QA催。\n\n4、需要控制bug单数量，否则会被追责复盘。同类问题，建议测试童鞋合并到同一个bug单中。\n\n5、测试管理系统 是所有人处理bug 流程的平台，不是让测试童鞋随便记录个人问题的。所以要提醒测试童鞋，明确该问题是bug，再提单；不是bug，要么不提，要么在沟通后驳回。\n\n### 发布方案\n\n- 发布顺序：一般是先发后端，再发前端\n- 依赖项是否准备就绪：配置的数据、配置项等\n- 是否会对线上业务、线上数据造成影响\n- 本地环境、dev环境、gamma环境，均要做好验证。\n- 回退机制\n- 发布 checkList\n\n### 上线确认\n\n- 发布完成后，需要输出上线确认邮件\n- 观察页面体验、页面性能表现\n- 观察监控数据、业务调用量\n- 总结复盘\n\n## 二、前端工程化\n\n\n### Git 版本管理\n\n1、前端代码仓库 git 分支规范：\n\n![](https://img.smyhvae.com/image-20220510164257833.png)\n\n![](https://img.smyhvae.com/image-20220510164323243.png)\n\n2、Commit Message 的格式，只允许使用以下10种标识，最常见的是 feat和 fix ：\n\n- **feat:** 新功能\n- **fix:** 修补 Bug\n- **docs:** 文档\n- **style:** 格式变更，不影响代码的运行\n- **refactor:** 重构（既不是新增功能，也不是修改 bug 的代码变动）\n- **test:** 增加测试\n- **chore:** 构建过程或辅助工具的变动\n- **up:** 不属于上述分类时，可使用此类别\n- **merge:** 用于 merge branch，需要手写 commit message 的情况\n- **revert:** 用于撤销之前的 commit\n\n3、业务分支，命名规范：（建议一定加上日期）\n\n- 特性分支：feature_xxx_YYMMDD\n- 紧急bug修复分支：hotfix_xxx_YYMMDD\n- 小程序发布分支（自动生成）：release_YYMMDD\n\n### 代码规范\n\n- 代码格式化：Prettier\n- 代码格式检查：ESLint\n\n### CSS预处理器\n\n- SASS（用得较多）\n- Less\n- PostCSS\n\n### 包管理\n\n- 包管理工具：npm（最常见）、yarn\n- cnpm、nvm、nrm常用命令\n\n### 编译构建\n\n- webpack（最常见）\n- Vite\n- Gulp\n- Babel：ES6语法转为ES5语法\n\n### 小程序工程化\n\n![图片](https://img.smyhvae.com/640.jpeg)\n\n- [小程序工程化探索](https://mp.weixin.qq.com/s/_NSJTQ-4-8gTnwTVK-tn0A)\n- [京喜小程序最佳实践：我是如何写超大型小程序代码的](https://mp.weixin.qq.com/s/tJN3Yz6usSt9LG37_pN7dw)\n\n### 测试相关\n\n- 编写测试用例代码\n- 单元测试\n- 自动化测试\n\n## 三、前端核心知识\n\n> 前端入门核心知识点\n\n### 浏览器\n\n- Web标准：结构标准（HTML）、表现标准（CSS）、行为标准（JS）\n- 浏览器分为两部分：渲染引擎（即：浏览器内核）、JS 引擎\n- 浏览器的工作原理：重绘和重排、V8引擎\n- App的WebView容器，相当于浏览器，可以内嵌H5网页\n\n### HTML5\n\n- 语义化标签：`<header>`、`<article>` 、`<footer>`等。\n- 多媒体标签：`<audio>`、`<video>`\n- 更强的本地存储能力和设备兼容性：indexDB、HTML5 APP cookie\n- 三维、图形及特效：SVG、Canvas、WebGL\n- 更有效的实时连接：WebSocket、Server-Sent Events\n- 无障碍体验\n\n### CSS、CSS3\n\n- CSS盒模型、BFC\n- 浮动、定位（绝对定位和相对定位）\n- flex 布局\n- 圣杯布局、双飞翼布局\n- 选择器：后代选择器、交集选择器、并集选择器、伪类选择器\n- 2D转换：移动translation、旋转rotate、缩放scale\n- 3D转换：透视 perspective、3D移动 translate3d、3D旋转 rotate3d、3D呈现 transform-style\n- CSS3动画：animation\n- CSS hack\n- Retina 屏幕的 1px 像素，如何实现\n\n### JS基础\n\n- ES6语法：严格模式、箭头函数、Promise、Symbol数据类型、Set 和Map数据结构\n\n- ES6转ES5\n\n- JS数据类型转换、隐式类型转换\n\n- 内置对象及其方法\n\n- 数组的各种方法：map、filter、every、reduce等\n\n- 事件机制、原型继承、立即执行函数\n\n- DOM操作、虚拟 DOM 的 diff 算法\n\n- BOM浏览器操作\n\n- 事件冒泡机制：捕获阶段、目标阶段、冒泡阶段。\n\n- 异步编程：Ajax、Promise、async await\n\n- SessionStorage和LocalStorage、Cookie\n\n- 迭代器Iterator和生成器Generator\n\n- Web Socket\n\n- 异步编程\n\n- 单线程\n\n- Canvas图像绘制\n\n- svg 动画\n\n\n\n### JS 高级\n\n- JS 三座大山：原型与原型链、作用域及闭包、异步和单线程\n- 作用域链、类、继承、原型继承\n- this的指向和绑定规则\n- 深拷贝和浅拷贝\n- 防抖和节流\n- Promise的宏任务和微任务\n- 浏览器的重排和重绘\n- 手写 Promise的整个逻辑和API：resolve、reject、then、catch、finally、allSettled、race any\n- 高阶函数\n- 事件委托\n- call、apply、bind\n- arguments 伪数组\n- 函数柯里化\n- 模块化：CommonJS、AMD、CMD、ESModule\n- JS高阶语法：Iterator 迭代器、Decorator 生成器\n- JS 高阶语法：Decorator、Proxy/Reflect、MutationObserver、 对象属性描述符、Object.assign、Object.freeze、Object.seal\n- JS 内存泄漏、JS垃圾回收算法\n- TypeScript 类型检查\n- Vue.js、React.js源码解析\n- Vue.js、React.js的**状态管理**：Vuex、Redux、Redux Toolkit、React Hooks、zustand\n- V8引擎源码\n\n### Node.js\n\n- 回调函数\n- 时间驱动机制\n- 模块化\n- 函数\n- 路由\n- 全局方法\n- 文件系统\n\n\n\n### Web 安全\n\n- 跨域问题、同源策略、JSONP\n- CORS\n- XSS\n- CSRF\n\n### 页面形式\n\n- 多端自适应布局\n\n- SPA单页应用\n\n- PWA（Progressive Web App）：小程序的鼻祖\n\n\n\n## 四、前端框架\n\n> 每个框架和工具，都有自己的约束、价值和最佳实践。\n\n### JS框架\n\n- Vue.js\n- React.js\n- Svelte（轻量级框架，最近比较火）。\n- angular（逐渐淘汰）\n\n对比：\n\n- vue ：声明式编程，数据驱动的思想\n- React：函数式编程。如果你要改变数据，那么必须调用api去改。\n\n在vue 中，几乎给你想要的全部给你了；而react 追求的更多的是自力更生。\n\n\n### CSS框架、组件库（B端常用）\n\n- React 技术栈：[Ant Design](https://ant.design/index-cn)（简称Antd）\n- Vue 技术栈：[Element](https://element.eleme.cn/#/zh-CN)、[Ant Design Vue](https://antdv.com/components/overview-cn)\n- 简单的CSS响应式框架：[Bootstrap](https://www.bootcss.com/)\n- [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss)（最近比较火）\n- [Vant Weapp](https://github.com/youzan/vant-weapp)：轻量级的移动端（含H5、小程序）组件库\n\n### 知识库框架\n\n- Vuepress（基于 Vue.js，推荐）\n- Docusaurus（基于 React.js，推荐）\n- GitBook\n- docsify：可制作简易的 wiki 文档。案例：[掘墓人的 Wiki](https://wiki.juemuren4449.com/)\n\n补充：知识库框架，首先推荐 Vuepress 和 Docusaurus，功能强大，成熟稳定。\n\n### API 文档框架\n\n- TypeDoc：将TypeScript项目生成 html、markdown等文档。\n- [storybook](https://github.com/storybookjs/storybook/)：用于搭建UI组件的知识库。可在线预览UI组件的样式和交互效果。\n\n### 跨端框架\n\n- Flutter（最近比较火）：Flutter 的Dart开发语言，可以编译为 ARM 64、x86 和 JavaScript 代码\n\n- ReactNative（逐渐没落）：App、Web端\n\n- Taro：小程序、H5\n\n\n\n### 桌面应用开发框架\n\n- Electron 框架。案例：VS Code软件就是用  Electron 开发的。\n\nElectron 非常流行，也被大量公司使用，也有很多成功软件，比如 VS Code、Facebook Messager、Twitch、Microsoft Teams 等。Electron 虽然上手容易，但问题也很明显，就是**慢、吃内存和大**，Electron 吃内存是因为打包的 Chromium 吃内存，同时一个 Hello World 编译后就要 120M+ 。\n\n**VS Code、chrome、小程序开发者工具**，本质上都是运行的 chrome 内核。所以你会发现，这三个软件都很占内存，都很卡。我将其称之为“**前端头痛三剑客**”。\n\n### 前端可视化框架、图表库\n\n- ECharts：百度的开源图表库。\n- D3.js：可视化 js 库。\n- [Three.js](http://www.webgl3d.cn/Three.js/)：基于原生 [WebGL](https://www.zoo.team/article/webglabout) 封装运行的三维引擎。[太阳系案例](http://www.yanhuangxueyuan.com/3D/solarSystem/index.html) [#](https://www.teqng.com/2021/08/16/%E6%95%99%E4%BD%A0%E5%A6%82%E4%BD%95%E7%94%A8three-js%E5%88%9B%E9%80%A0%E4%B8%80%E4%B8%AA%E4%B8%89%E7%BB%B4%E5%A4%AA%E9%98%B3%E7%B3%BB/)\n- [Cocos 引擎](https://www.cocos.com/products#Cocos2d-x)：游戏动画开发框架。\n- [白鹭引擎](https://www.egret.com/)：H5游戏引擎，一套完整的H5游戏解决方案。白鹭引擎的所在公司已在2022年初破产，不建议使用。\n\n### 编辑器框架\n\n- wangEditor：国内很流行\n- Tiptap：可定制性及极强；headerless，不提供任何 UI 样式，你完全可以自由地构建任何你想要的 UI。\n- TinyMCE：国外很火\n- ueditor：百度的开源框架。比较老，逐渐没落。\n- Monaco Editor：VS Code的在线版\n\n### Node.js 框架\n\n- Koa：新一代 Node.js 框架。\n- [Egg.js](https://eggjs.github.io/zh/)：Egg是在Koa基础上进一步封装的企业级Web开发框架。\n- Express：比较老的Node.js 框架。\n\n###  服务端渲染框架\n\n- Next.js （基于React.js）\n\n- Nuxt.js （基于Vue.js）\n\n\n### 前端测试框架\n\n- [Mocha](https://github.com/mochajs/mocha)：JS 测试框架。\n- [Tiga](https://tiga.jd.com/docs/)：跨端（H5、小程序）项目的自动化测试 SDK。凹凸实验室出品。\n\n## 五、前端性能优化\n\n### 性能分析工具\n\n- 控制台的瀑布图 Waterfall\n\n- 控制台的 performance工具：日常开发过程中观察分析运行时的性能表现\n\n- 控制台的 LightHouse ：跑分、输出性能报告，分析性能\n\n- [WebPageTest](https://www.webpagetest.org)网站：评估网站性能\n\n- Performance 这个API：实时动态测量性能\n\n\n\n### 性能参数\n\n- 首屏时间 = 白屏时间 + 渲染时间。预解析、预加载、预渲染、懒加载、懒执行。\n- FPS帧率\n- 性能的测量标准：RAIL 模型\n- 弱网环境，耗时对比\n\n\n\n### 浏览器渲染优化\n\n- 了解渲染过程、关键渲染路径\n- 减少重排和重绘\n- 用户从输入url到页面加载显示完成，经历了哪些过程\n\n### JavaScript 优化\n\n- JS资源优化：按需加载、编译打包、解析执行、异步加载\n- V8引擎工作原理、性能优化原理\n- 防抖和节流\n- 无限滚动列表：做节点回收\n- 骨架屏 skeleton：减少布局移动\n- 时间分片：把一个运行时间比较长的任务分解成一块一块比较小的任务，分块去执行，减少用户的卡顿感\n- JS内存管理\n\n### 资源优化\n\n- 资源的压缩与合并：减少http请求数量；减少请求资源的大小；使用 http缓存\n- HTML优化：减少iframe的使用；避免节点的深层次嵌套；避免使用table布局\n- CSS优化：降低CSS对页面渲染的阻塞，尽早加载CSS；利用GPU渲染CSS动画；使用 contain属性，减少布局和绘制的消耗时间\n- 图片优化：使用CSS3、SVG、IconFont代替图像；图片懒加载 lazy loading；图片的预加载；渐进式图片；响应式图片；使用 base64 代替小于8kb的图。\n- 字体优化：字体闪动问题；使用特殊字体时，建议动态加载网络字体\n- 异步加载第三方资源：第三方资源不可控会影响页面的加载和显示\n\n### 构建优化\n\n- tree shaking、代码拆分（Code Splitting）\n- 代码压缩混淆\n- 打包时，避免对不变的库重复构建。\n\n### 网络传输优化\n\n- 启用Gzip对资源进行压缩\n- CDN传输：静态资源全部放CDN上，使用户可就近获取所需内容，大幅减小光纤传输距离。\n- 避免重定向：301、302 重定向会降低响应速度\n- dns预解析：使用dns-prefetch 提前解析域名，提高资源加载速度。在访问以图片为主的网站时，DNS预解析可以让加载时间减少5%左右。\n- PWA，Service worker\n- SSR 服务端渲染/Node直出\n\n\n\n## 六、前端工具和资源\n\n### 前端文档\n\n- MDN 官方文档：<https://developer.mozilla.org/zh-CN/docs/Web>\n\n- ECMAScript 语法的底层实现：<https://tc39.es/ecma262>\n\n### 前端社区\n\n- GitHub\n- stackoverflow\n- 掘金\n\n\n\n### JS 学习资源\n\n- 现代 JavaScript 教程：https://zh.javascript.info/\n- 阮一峰 JS教程：https://wangdoc.com/javascript/\n- 阮一峰 ES6教程：https://es6.ruanyifeng.com/\n- TypeScript 入门教程：https://github.com/xcatliu/typescript-tutorial\n- Node.js学习指南：https://blog.poetries.top/node-learning-notes/\n\n### JS 代码规范\n\n1、Airbnb JavaScript Style Guide：\n\n- 英文原版：https://github.com/airbnb/javascript\n\n- 中文版：https://github.com/lin-123/javascript\n\n2、clean code JavaScript：\n\n- 英文原版：https://github.com/ryanmcdermott/clean-code-javascript\n\n- 中文版1：https://github.com/alivebao/clean-code-js\n\n- 中文版2：https://github.com/beginor/clean-code-javascript\n\n### 前端刷题\n\n- 前端进阶之道：https://yuchengkai.cn/\n\n\n\n### CSS 学习资源\n\n-  CSS灵感：https://github.com/chokcoco/CSS-Inspiration\n-  CSS的各种实现效果：https://lhammer.cn/You-need-to-know-css/#/\n-  css_tricks：https://github.com/QiShaoXuan/css_tricks\n-  按需定制 CSS 动画效果：https://github.com/QiShaoXuan/css_tricks\n\n### 字体相关资源\n\n- 360字体版权查询：https://fonts.safe.360.cn/\n- 最全的免费可商用字体-效果预览：https://wordshub.github.io/free-font/index.html\n- 常见的免费字体：http://zenozeng.github.io/Free-Chinese-Fonts/\n\n\n\n### 抓包工具\n\n- Whistle：https://wproxy.org/whistle/\n\n### 兼容性查看工具\n\n- Can I use（前端API兼容性查看）：https://caniuse.com/\n\n### 图片相关工具\n\n- 图片压缩：https://tinypng.com/\n- 图片压缩：https://docsmall.com/\n- 生成代码截图：https://carbon.now.sh/\n\n### 设计工具\n\n- **墨刀**：原型设计工具。网址：<https://modao.cc/>\n\n- **蓝湖**：一款产品文档和设计图的在线协作平台。网址：<https://lanhuapp.com>\n\n- **PxCook（像素大厨）**：高效易用的自动标注工具。软件下载链接：<https://www.fancynode.com.cn/pxcook>\n\n- 即时设计、稿定、master go\n\n\n\n### 流程图工具\n\n- ProcessOn：<https://www.processon.com/>\n\n### 大纲笔记\n\n- **幕布**：<https://mubu.com>\n\n-  飞书-思维笔记\n\n\n\n### markdown 编辑器\n\n- typora\n- VS Code\n\n### 代码编辑器\n\n- VS Code\n- Sublime Text\n\n\n\n## 七、前端书籍推荐\n\n### JS经典书籍\n\n- 红宝书：《JavaScript高级程序设计》\n- 小黄书：《你不知道的JavaScript》上、下册\n- 犀牛书：《JavaScript权威指南》\n- 绿皮书：《javascript语言精粹与编程实践》\n\n### JS进阶\n\n- 《前端开发核心知识进阶》\n- 《JavaScript 二十年》\n- 《JavaScript 悟道》\n- 《深入理解现代JavaScript》\n- 《JavaScript忍者秘籍》\n- 《编写可维护的JavaScript》\n- 《了不起的JavaScript工程师：从前端到全端高级进阶》\n- 《javascript设计模式与开发实践》\n- 《WebKit技术内幕》\n- 《JavaScript启示录》\n\n### CSS\n\n- 《CSS世界》\n- 《CSS新世界》\n- 《CSS揭秘》\n- 《精通 CSS》\n\n\n\n\n\n\n### Vue.js\n\n- 《Vue.js设计与实现》\n- 《深入浅出Vue.js》\n\n\n### Node.js\n\n- 《深入浅出Node.js》\n- 《Node.js：来一打 C++ 扩展》\n\n### 数据结构和算法\n\n- 《计算之魂》\n- 《大话数据结构》\n- 《学习JavaScript数据结构与算法》\n\n### 后端\n\n- 《领域驱动设计》\n- 《推荐系统实践》\n- 《数据密集型应用系统设计》\n- 《代码精进之路：从码农到工匠》\n\n\n\n### 项目管理和认知\n\n- 《人月神话》\n- 《黑客与画家》\n- Joel Spolsky的书：《软件随想录》《Joel 说软件》《Joel 谈优秀软件开发方法》\n- 《凤凰项目》\n- 《持续交付2.0》\n- 《Google软件工程》\n- 《软技能：代码之外的生存指南》\n- 《重来3：跳出疯狂的忙碌》\n- 《程序员的思维修炼》\n- 《管理的常识》\n\n### 产品\n\n- 《启示录》\n- 《结网》\n- 《人人都是产品经理》\n- 《用户体验要素》\n- 《有效需求分析》\n- 《产品逻辑之美：打造复杂的产品系统》\n- 《微信背后的产品观》\n- 《俞军产品方法论》\n- 《决胜B端——产品经理升级之路》\n- 《给产品经理讲技术》\n- 《精益数据分析》\n- 《产品经理面试宝典》\n- 《体验引擎：游戏设计全景探秘》\n\n### 设计\n\n- 《设计心理学》四册\n- 《用户体验要素》\n- 《点石成金》\n- 《写给大家看的设计书》\n- 《About Face 4: 交互设计精髓》\n- 《设计中的设计》\n- 《破茧成蝶》\n- 《简约至上：交互式设计四策略》\n- 《Web表单设计：点石成金的艺术》\n- 《触动人心：设计优秀的iPhone应用》\n- 《瞬间之美：Web界面设计如何让用户心动》\n- 《用户体验度量：收集、分析与呈现》\n\n### 运营\n\n- 《运营之光》两册\n- 《我在一线做用户增长》\n- 《增长黑客：创业公司的用户与收入增长秘籍》\n- 《流量池》\n- 《超级运营术》\n\n### 商业\n\n- 《史蒂夫·乔布斯传》\n- 《浪潮之巅》\n- 《赢》\n- 《你凭什么做好互联网：从技术思维到商业逻辑》\n- 《计算广告》\n- 《详谈：左晖》\n- 《在线：数据改变商业本质，计算重塑经济未来》\n- 《零售的哲学》\n- 《我看电商》\n- 《冲浪板上的公司》\n- 《一本书读懂财报》\n\n### 思维和认知\n\n- 《学会提问》\n- 《思考，快与慢》\n- 《清醒思考的艺术》\n- 《把时间当作朋友》\n- 《智识分子》\n- 《少有人走的路》\n- 《沟通的方法》\n- 《我们为什么要睡觉》\n\n\n\n## 八、前端总结和认知\n\n### 研发视角，如何理解需求\n\n> [点击查看大图](https://img.smyhvae.com/20220613_1330-2.jpg)。\n\n![](https://img.smyhvae.com/20220613_1330-2.jpg)\n\n从上面的流程图中可以看出，产品经理的交付物是什么？是prd吗？显然不是。\n\n产品经理的工作跟设计师、工程师非常不同。人们对工程师的期望是交付有效的代码，对设计师的期望是交付视觉稿。而对于产品经理来说，只交付一份prd是不够的。\n\n产品经理要负责跟进整个产品周期，包括上线后的页面效果和数据表现。编写需求规范是一种**沟通和推动项目**的手段，但**规范本身并没有内在的价值**。很多产品经理并不借助prd来交流他们的想法，他们可以用谈话，还可以把想法画在白板上。也有一些产品经理虽然写了规范，但却没有参照执行。\n\n### 前端工程师应该具备怎样的能力和素质\n\n- 技术功底、技术视野、技术追求\n- 除了开发业务功能外，还需要对开发规范、工程化、组件化、模块化、测试、设计模式有一定的认知和实践\n- 沟通表达能力、书面表达能力、总结复盘习惯\n- 全局思维、抽象思维、持续交付意识\n- 项目一号位担当，团队协作意识\n- 综合权衡：成本、效率、质量、风险、体验\n- 关注产品、设计、商业等各个领域。交叉学科会带来更多创新。\n\n### 前端认知\n\n1、虽然我们绝大多数时间耗在业务开发上，但仍需要积累其他方面的沉淀，做多一些有趣的、可持续的事情，比如分享总结、基础能力建设、研发效能提升、技术运营建设、技术沉淀等。\n\n2、学会提问。我们日常在提出问题和解决问题时，经常容易陷入[X-Y问题](https://coolshell.cn/articles/10804.html)，导致目标不明确、思路不清晰、沟通效率低下，甚至在一个完全错误的方向上浪费大量的资源、时间和精力。无论是在寻求帮助的人身上，还是在提供帮助的人身上，都有所体现。\n\n在面对一个问题时，要理解这句话的意图、事实、情绪、期待。学会提问，学会答疑，都是一种智慧。参考[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md) 。\n\n3、全流程跟进，持续交付，创造业务价值。\n\n4、前端的本质是链接商业、设计、计算能力，为用户提供专业的人机交互体验。\n\n5、产品能力和技术能力是：判断信息，抓住要点，整合有限的资源，把自己的价值打包成一个产品进行交付，并获得回报。\n\n6、部门体系的角色有很多：运营、产品、视觉、开发、测试、架构师、leader、行销、数据分析、运维等。有些工作不是“做或者不做”的问题，而是程度的问题。在注意边界的前提下，主动承担、全盘思考、多一份同理心，这是能力和责任逐渐增强的体现。\n\n7、谦逊、尊重和信任，是协同作战和良性合作的基础。\n\n8、组织内，人与人的关系应该是怎样的？有人认为是管理与被管理的关系，有人认为是合作关系。而我认为，**组织内的关系是奉献关系**。没有奉献作为基础，组织关系是不成立的。组织内的人与人之间是相互付出的关系，部门与部门是相互付出的关系，上级与下级之间是相互付出的关系，在这样的相互奉献关系中，组织才会真正地存在并发挥作用。\n\n奉献关系所产生的基本现象是：每个处于流程上的人更关心他能够为下一个工序做什么样的贡献；每个部门都关心自己如何调整才能够与其他部门有和谐的接口；下级会关注自己怎样配合才能够为上级提供支持，而上级会要求自己为下级解决问题并提供帮助。\n\n能力很重要，而付出更重要。\n\n9、优秀的人有几个特性：敏感、不能忍、有动手优化的能力。\n\n10、前端侧重于人机交互和用户体验，后端侧重于业务逻辑和大规模数据处理。理论上，面向用户的产品里，所有问题（包括产品、设计、后端、甚至看不见的问题）的表现形式，都会暴露在前端，而只有部分问题（数据问题、计算问题、安全问题等）暴露在后端，这就意味着前端起到了至关重要的**承载和连接**作用。\n\n11、前端技术的更新日新月异；前端框架的技术选型层出不穷；视觉审美的潮流不断更替；可视化效果酷炫无比；用户的运营体系逐渐精细化；适老化、无障碍化、青少年人群的诉求浮出水面；智能设备的升级和适配无穷无尽。所有的这一切，对前端领域和前端同学就一个要求：要折腾，爱折腾，反复折腾。\n\n\n## 推荐链接\n\n- 大众点评 | 设计流程规范：https://mp.weixin.qq.com/s/xRSemDaS9ifP9ta56FZFxQ\n\n"
  },
  {
    "path": "16-前端综合/02-Web前端入门自学路线（精简版）.md",
    "content": "---\ntitle: 02-Web前端入门自学路线（精简版）\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n> 本文的最新内容，会随着前端技术的更新，本文也会随之更新。\n\n> 本文的最新内容也会在[GitHub](https://github.com/qianguyihao/Web)上实时更新。欢迎在GitHub上关注我，一起入门和进阶前端。\n\n我之前写过一篇文章：[《裸辞两个月，海投一个月，从Android转战Web前端的求职之路》](http://www.cnblogs.com/smyhvae/p/8732781.html)。这篇文章讲述了我在转型过程中的亲身经历和感受，不少童鞋私信问我怎么入门前端，于是有了这篇文章。\n\n## Web前端入门的自学路线\n\n> 新手入门前端，需要学习的**基础内容**有很多，如下。\n\n一、HTML、CSS基础、JavaScript语法基础。学完基础后，可以仿照电商网站（例如京东、小米）做首页的布局。\n\n二、JavaScript语法进阶。包括：作用域和闭包、this和对象原型等。相信我，JS语法，永远是面试中最重要的部分。\n\n三、jQuery、Ajax等。jQuery没有过时，它仍然是前端基础的一部分。\n\n四、ES6语法。这部分属于JS新增的语法，面试必问。其中，关于 promise、async 等内容要尤其关注。\n\n五、HTML5和CSS3。要熟悉其中的新特性。\n\n六、canvas。面试时，有的公司不一定会问canvas，靠运气。如果时间不够，这部分的内容可以先不学。但如果你会，绝对属于加分项。\n\n七、移动Web开发、Bootstrap等。要注意移动开发中的适配和兼容性问题。\n\n八、前端框架：Vue.js和React。这两个框架至少要会一个。入门时，建议先学Vue.js，上手相对容易。但无论如何，同时掌握 Vue 和 React 才是合格的前端同学。\n\n九、UI框架：[Ant Design](https://ant.design/index-cn)、[Element UI](https://element.eleme.cn/#/)。在做管理后台的时候，这两个UI框架使用的比较多的。Element UI 是基于 Vue.js技术栈的。Ant Design 既有基于  React技术栈的，也有基于 Vue.js技术栈的 [Ant Design Vue](https://antdv.com/) 。\n\n九、Node.js。属于加分项，如果时间不够，可以先不学，但至少要知道 Node 环境的配置，以及 Node 的一些基础知识。\n\n十、前端工程化：构建工具 Webpack、构建工具 gulp、CSS 预处理器 Sass、自动化测试、持续集成 等。注意，Sass 比 Less 用得多，gulp 比 grunt 用得多。\n\n十一、前端综合：HTTP协议、跨域通信、安全问题（CSRF、XSS）、浏览器渲染机制、异步和单线程、页面性能优化、防抖动（Debouncing）和节流阀（Throtting）、lazyload、前端错误监控、虚拟DOM等。\n\n十二、编辑器相关。Visual Studio Code 是每个学前端的人都要用到的编辑器。另外，前端常见的编辑器、IDE有两个：Sublime Text 和 WebStorm 。WebStorm 什么都好，可就是太卡顿；VS Code就相对轻量很多，但是比较占内存。个人总结一下：用VS Code 的人越来越多，用 WebStorm 的人越来越少。具体可以看：《[第一次使用VS Code时你应该知道的一切配置](https://www.cnblogs.com/qianguyihao/p/10732375.html)》\n\n十三、TypeScript（简称TS）。ES 是 JS 的标准，TS 是 JS 的超集。TS属于进阶内容，建议把上面的基础掌握之后，再学TS。\n\n十四、小程序开发。学会基本的JS语法，再了解小程序独有的API（参考小程序的官方文档），就已经掌握了小程序开发，没有你想象的那么难，so easy。小程序在商业上是成功的，但我个人认为它是 Web 技术的倒退，也完全体现不出开源精神和开放精神。而且小程序的开发效率贼低，IDE也卡到了极点，卡爆了。\n\n当然，不得不承认，小程序开发让很多人找到了编程的工作。但你要一路谨记：不要做小程序开发工程师，要做Web前端开发工程师。\n\n十五、总结——框架有时候很虚；熟练掌握 JavaScript 基础、核心源码，才是行走江湖、驰骋千里的关键。\n\n## 推荐的前端图文教程\n\n我在GitHub上有一个Web前端入门的学习教程，非常详细，地址是：\n\n> <https://github.com/qianguyihao/Web>\n\n非常详细和贴心，你值得star。这个前端教程主要有三个作用：\n\n- 网上的大部分入门教程，都不太适合初学者，本项目争取照顾到每一位入门者的同理心。\n\n- 帮助前端同学提供一个精品学习路线和资源，提高学习效率，少走很多弯路。\n\n- 可以当做前端字典，随时翻阅，查漏补缺。\n\n## 推荐的技术博客\n\n- [阮一峰](http://www.ruanyifeng.com/blog/)\n\n- [张鑫旭](http://www.zhangxinxu.com/wordpress/)\n\n## 推荐的书籍\n\n### 1、基础知识相关书籍\n\n\n- 《[网络是怎样连接的](https://book.douban.com/subject/26941639/)》\n\n程序员面试的时候，经常会被问的一个问题是：“在浏览器的地址栏输入url，按下回车后，发生了什么？”\n\n为了清楚这个问题，看上面这本书，足够了。如果你想入门计算机网络，这本书也是必读的。评价非常高。\n\n关于这个问题，也可以看下面这篇文章：[浏览器输入 URL 后发生了什么？](https://zhuanlan.zhihu.com/p/43369093)\n\n### 2、JS相关书籍\n\n- 《[你不知道的JavaScript](https://book.douban.com/subject/26351021/)》\n\n上面这套书有上、中、下三本，你都可以读一读。如果时间不够，那就先读第一本。\n\n- 《[JavaScript语言精髓与编程实践](https://book.douban.com/subject/35085910/)》\n\n周爱民的书，谁能不爱？这本书不但完整解析了 JavaScript 语言，还逐一剖析了相关特性在多个开源项目中的编程实践与应用。\n\n- 《[JavaScript高级程序设计](https://book.douban.com/subject/35175321/)》\n\n红宝书，人人都知道。\n\n- 《[JavaScript 悟道](https://book.douban.com/subject/35469273/)》\n\n本书首先介绍JavaScript的基本知识：命名、数值、布尔值和字符串等，并展示了其缺陷和局限性，但随后展示了如何解决这些问题；接着继续研究数据结构和函数，探索底层机制，并使用高阶函数来实现面向对象编程。\n\n本书的翻译是@死月，\n\n### 3、CSS相关书籍\n\n- 《[CSS世界](https://book.douban.com/subject/27615777/)》\n\n关于 CSS 的书籍，首先推荐这本书，我身边的大佬们都说这本书好。虽然我不是大牛，但我也觉得这本书很好。\n\n如果 js 熟练，说明你是有技术深度的前端；如果 css 熟练，说明你是有经验的前端。\n\n- 《[CSS新世界](https://book.douban.com/subject/35539710/)》\n\n这是一本关于CSS的进阶读物，专门讲CSS3及其之后版本的新特性。\n\n- 《[精通 CSS](https://book.douban.com/subject/30450258/)》\n\nCSS 进阶书籍。\n\n### 4、面试相关书籍\n\n- 《[前端开发核心知识进阶：从夯实基础到突破瓶颈](https://book.douban.com/subject/35218831/)》\n\nJS基础、ES6语法、Vue源码、React源码、前端性能优化等等，这些话题都是面试必问。\n\n- 《[了不起的JavaScript工程师：从前端到全端高级进阶](https://book.douban.com/subject/34788884/)》\n\n作者@[亚里士朱德](https://yalishizhude.github.io/about/) 是慕课网讲师，博客和课程都写的好。这本书讲述了开发者使用JavaScript在各种Web开发场景下所需要掌握的重点知识和概念。\n\n### 5、Vue.js、React 源码书籍\n\n- 《[深入浅出Vue.js](https://book.douban.com/subject/32581281/)》\n\n这本书讲 Vue.js 2.0的实现原理，很不错。 评分不高，不是因为书不好，而是因为你没看懂。\n\n## 推荐的链接\n\n- 前端导航：<https://www.cnblogs.com/qianguyihao/p/10701923.html>\n\n这个导航里列出了很多常见网站、博客、工具等，整体来看比较权威。\n\n学是一方面，也是最主要的方面；但还有一个作用，比如，“这个前端框架你都不知道啊”、“这个前端大牛你都没听说过啊” 。此时，这份清单就能起到作用了。如果能把清单里列出的内容都了解下，逼格也会高很多。\n\n- MDN 官方文档：<https://developer.mozilla.org/zh-CN/docs/Web>\n\n如果你想查看前端的 api 文档，请首先去 MDN上看。很官方，很正规。\n\n不要去什么 w3school 、菜鸟教程上看，上面有一堆错误。\n\n## 前端资讯订阅源\n\n国内的很多技术博客，都是比较粗浅的二手知识，真正的大佬，看不上这些东西。\n\n要了解最新的的前端技术趋势、前端资讯，还得看国外的网站。下面这两个前端资讯的网站，极力推荐。它们都可以通过邮件的形式订阅，我认为是前端开发者必须要订阅的：\n\n- Daily JS：<https://medium.com/dailyjs>  - medium 上的博客。\n- JavaScript Weekly：<https://javascriptweekly.com/>  - 聚合类的技术周刊。\n\n\n## 我的公众号\n\n想学习**更多技能**？不妨关注我的微信公众号：**千古壹号**（id：`qianguyihao`）。\n\n扫一扫，你将发现另一个全新的世界，而这将是一场美丽的意外：\n\n![](http://img.smyhvae.com/2016040102.jpg)\n"
  },
  {
    "path": "16-前端综合/2018年-前端日记.md",
    "content": "\n## 2018年4月份\n\n### 2018-04-25\n\n\n\n- userAgent相关：[判断微信内置浏览器的UserAgent](http://www.cnblogs.com/7z7chn/p/5370352.html)\n\n\n\n\n### 2018-04-26\n\n**前端相关**：\n\n- 流程图制作工具：[ProcessOn](https://www.processon.com/)\n\n- api方法的浏览器兼容性问题，可以在这个网站上看：<https://caniuse.com/>\n\n- CSS3的兼容性问题，不一定要使用-webkit-, -moz-, -o-, -ms-等私有前缀。可以使用 PostCSS。[知乎](https://www.zhihu.com/question/20597072)\n\n- 浏览器常见的内核有：V8、WebKit。另外腾讯还有个[X5](http://x5.tencent.com/)。\n\n- 要查一下display none 和 visibility hidden的区别。\n\n- ES 的各个版本在 Node 环境下的支持情况，可以查看这个网站：<http://node.green/>\n\n- promise的实现，关键词：Promises/A、Promises/B、[bluebird](https://github.com/petkaantonov/bluebird)\n\n\n**综合**：\n\n- whistle安装证书后，可以拦截 https 请求。但是，我现在又不想拦截了，该怎么卸载证书呢？\n\n### 2018-04-27\n\n- [strider](https://github.com/Strider-CD/strider)：可以用来部署项目。\n\n- 有必要了解一下电商1.0、电商2.0、电商3.0的概念。\n\n\n### 2018-05-02\n\n- `location.pathname`：获取 url 的后半部分。参考链接：[#](http://www.cnblogs.com/itjeff/p/4645262.html)\n\n- 代码解读：`callback && callback()`的含义\n\n\n\n### 2018-05-03\n\n- 各种框架实现的todo项目：<http://todomvc.com/>\n\n- 对比 sass、less、stylus 这三个css预处理器，zqc说，后面两个已经不怎么用了。sass 比 less强大，stylus的书写方式比较奇怪。\n\n- npm命令中，--save 和 --save-dev的区别。参考链接：<http://pwcong.me/2017/01/05/npm%E5%BC%95%E5%85%A5%E6%A8%A1%E5%9D%97%E6%97%B6--save-%E4%B8%8E--save-dev-%E7%9A%84%E5%8C%BA%E5%88%AB/>\n\n\n### 2018-05-07\n\n- Vue组件的注册\n\n有一种组件注册的方式是 Vue+jQuery：\n\n```javascript\nVue.component('my-div', $.extend({\n\tprops:[],\n\tmethods:{\n\n\t},\n\tfilters:{\n\n\t}\n\n}), vueTpl.subs.myDiv)\n```\n\n\n根据 zqc 的建议，不一定要使用`$.extend()`，还可以使用`object.assign()`。\n\n### 2018-05-08\n\n- sku、spu的概念\n\n### 2018-05-09\n\n- 输入框正则的匹配\n\n让输入框仅支持输入单个id，且为字符串。如果输入多个id，或者非数字的字符，则自动删除：\n\n```javascript\nv-on:keyup=\"querysku = querysku.replace(/\\D/,'')\"\n```\n\n\n### 2018-05-10\n\n- 如果在控制台看到网络请求陈功，数据也获取成功，但是在ajax里走的是 error（数据获取失败），说明是 ajax代码的判断逻辑有问题。\n\n- 服务器返回的json数据到底是对象还是字符串？\n\n- josn数据里的字段，有顺序吗？比如下面这段：\n\n```jsonn\n{\n    \"1492948848\": {\n        \"3\": \"1\",\n        \"spec\": \"\",\n        \"imagePath\": \"hehe.jpg\",\n        \"color\": \"橘色   \",\n        \"name\": \"【多色可选】丽装铺园纯色百搭简约打底T恤女 橘色 M\",\n        \"size\": \"M\"\n    },\n    \"1492948847\": {\n        \"3\": \"1\",\n        \"spec\": \"\",\n        \"imagePath\": \"lala.jpg\",\n        \"color\": \"灰色   \",\n        \"name\": \"【多色可选】丽装铺园纯色百搭简约打底T恤女 灰色 S\",\n        \"size\": \"S  \"\n    }\n}\n```\n\n答案：顺序不重要。\n\n- Vue开发中，在其他地方用到Vue实例中的数据时，一定要用this，或者是`vm.$data.myName`之类的。\n\n- 疑问：下面的src路径的前面，为何要加`//`：\n\n```\n<img v-bind:src=\"'//img14.smyhvae.com/evalpic/s240x240_'+value.imagePath\" />\n```\n\n我发现，控制台看到的输出src中，会自动加上http。如果前面不加`//`则表示相对路径。\n\n### 2018-05-11\n\n- 将逗号分隔的字符串，转换为数组： `str.split(\",\")`。即使数组中只有一个元素，也可以这样用。参考链接：[#](https://blog.csdn.net/erlian1992/article/details/50561452)\n\n### 2018-05-14\n\n- ajax发的是post请求，但是后台却只收到了部分数据怎么办？答案：前端的post请求记得加content-type字段，否则会被识别成 get 请求。\n\n- 获取jsonp的数据，只能用get请求。如果要用post请求，那就传json数据，另外，可能还要解决跨域的问题。跨域需要在后台配置，三行代码即可。\n\n- p标签里的文字溢出怎么办？\n\n- whistle该怎样mock数据？\n\n\n### 2018-05-16\n\n- 在控制台看标签的样式，发现有些样式是出现在`element.style`中的（比如图片的尺寸），但是在代码里并没有找到。那是因为，这些样式是在 js 代码中**计算**出来的。\n\n- 图片自适应显示\n\n- [视区相关单位vw, vh..简介以及可实际应用场景](http://www.zhangxinxu.com/wordpress/2012/09/new-viewport-relative-units-vw-vh-vm-vmin/)\n\n- jingwen推荐的iconMoon图标网站。网址：<https://icomoon.io/>\n\n\n### 2018-06-01\n\n- `PingFangSC`字体是iOS独有的字体。`PingFangSC-Regular`是常规字体，`PingFangSC-Semibold`是加粗字体。如果我在代码里设置了这个字体，那么，ios上可以看到效果，但是Android上看不到效果，仍然会采用Android系统默认的字体。\n\n\n\n### 2018-06-04\n\n**1、git相关**：\n\n把 branch1 中的某条记录(比如myLog)，提交到 branch2中。做法如下：\n\n先切换到branch2中，然后输入如下命令：\n\n```\ngit cherry-pick myLog\n```\n\n\n### 2018-06-05\n\n**1、font-size**：\n\n`font-size`的最小值为12。\n\n也就是说，浏览器的最小字体为12，要是再小于这个值，是不生效的。如果想要小于12，需要在浏览器的高级设置里去修改。\n\n\n\n**2、git 多分支同时开发**：\n\n现在有这样一个场景：我要同时开发一个项目里的两个功能。今天上午开发功能1，下午开发功能2。明天上午改功能1的bug，明天下午改功能2的bug。\n\n相当于是，我现在是**并行**开发两个功能了，要怎么通过git来进行协作呢？\n\n目前考虑到的姣好的方式是：\n\n- 从master拉分支`branch1`，此分支专门用来开发功能1，改功能1的bug。\n\n- 再从master拉分支`branch2`，此分支专门用来开发功能2，改功能2的bug。\n\n以后需要上线哪个功能，就从那个分支merge代码到master。\n\n\n\n\n**3、其他**：\n\n- 两个span之间默认有5px的 margin\n\n\n- **shadow-root**：下一代。\n\n\n### 2018-06-07\n\n- [原生js实现淡入淡出效果](https://www.teakki.com/p/57dfb44cd3a7507f975e91e4)\n\n- 通过 jQuery 获取Dom的时候，比如`$('#topNavTop').css('background','red')`记得要指明是 id 还是 class。\n\n- 每次开发一个新的需求，记得要问清楚：“H5和小程序”都要做吗？要做的话，工作量基本乘以2。\n\n\n### 2018-06-14\n\n- pv、uv的概念\n\n\n### 2018-06-21\n\n- 今天学会了 iPhone上WebApp的真机调试，感觉很高端呀。具体可以看我在本文件夹中写的《前端开发积累》这篇文章。\n\n\n\n### 2018-06-27\n\n没想到，`''`和`' '`竟然还有区别。\n\n\n\n### 2018-06-30\n\n**并列条件**：\n\n来看下面这段代码：\n\n```javascript\n        var num = 80;\n        console.log(50 < num <= 70);\n```\n\n上面的代码，你认为打印的结果是什么？其实，它打印的结果是 true。\n\n如果我们要实现并列条件，千万不要使用 `if(50 < num < 70)`，而是要使用`if(num > 50 && num <=70)`。\n\n\n### 2018-07-16\n\n**FAQ：问答系统**\n\nFAQ是英文`Frequently Asked Questions`的缩写，中文意思就是“经常问到的问题”，或者更通俗地叫做“常见问题解答”。\n\n\n### 2018-08-03\n\n```javascript\nlet temp = 0.123;\nlet temp2 = temp.toFixed(2);\n```\n\n\n上方代码中，`temp2`的结果是0.12，但是请注意，`temp`的类型Number型，而`temp2`的类型却是String型。\n\n\n\n### 2018-08-15\n\nflex布局常用的三行代码：\n\n```\n    display: flex;\n    justify-content: center; // 子元素在横轴的对齐方式 （左右居中）\n    align-items: center;  // 子元素在竖轴的对齐方式（上下居中）\n```\n\n\n### 2018-08-16\n\n用CSS3 transition属性实现淡入淡出轮播图：<https://segmentfault.com/a/1190000007648070>\n\n\n\n### 2018-08-20\n\n**小程序问题**：\n\n用小程序调试时，如果出现故障（比如item点击无响应），可能是微信开发者工具IDE的版本太低了。注意，IDE上上虽然提示是最新版，但不一定是官网的最新版。所以，要去官网下载最新版。\n\n如果还是不行，看看是不是自己的代码写错了。有时候，代码写错了，不一定会有报错提示哦。\n\n\n**css问题**：\n\n- css3实现的switch开关按钮：<https://codepen.io/chutou/pen/qdGZQr>\n\n\n### 2018-08-22\n\n**两个span之间去空格**\n\n- html+css如何删除行内元素之间的空白/空隙：<http://www.manongjc.com/article/2171.html>\n\n方法二亲测有效：让父亲的font-size为0，然后具体设置子元素的font-size\n\n- 去除inline-block元素间间距的N种方法：<https://www.zhangxinxu.com/wordpress/2012/04/inline-block-space-remove-去除间距/>\n\n\n\n### 2018-08-28-修改用户的cookie\n\n```\n    document.cookie=\"visitkey=98\"\n```\n\n\n\n### 2018-09-20\n\n需求：当导航条滚动到屏幕顶部时（举例顶部的距离 < 0时），就设置导航条为fixed。\n\n实现：如果要设置为导航条为fixed，正确的做法应该是：给导航条这个父亲一个高度进行占位，然后让导航条的儿子为fixed。而不是让父亲为 fixed。\n\n\n### 2018-09-27\n\n如何让微信小程序禁止下拉_解决小程序下拉出现空白的情况：http://www.fly63.com/article/detial/1069\n\n我遇到问题的原因是：背景图太大，超出了视图。\n\n\n### 2018-10-12\n\n小程序代码中，如果我这样写view的度样式：\n\n```\nheight: 60rpx;\nline-height: 1.5rem;\n```\n\n\n上面的这种写法，并不会让里面的文字上下居中，我在 iPhone 7 plus 中看到的结果是：文字偏上移。\n\n正确的做法是：（单位一致用rpx，不要把两个单位混用）\n\n```\nheight: 60rpx;\nline-height: 60rpx;\n```\n\n\n\n### 2018-10-21\n\n\n时间戳和年月日的转换：<https://blog.csdn.net/qq_26747571/article/details/53289120?locationNum=10&fps=1>\n\n\n### 2018-11-28\n\ncss实现圆环进度条：<https://blog.csdn.net/wanglei1991gao/article/details/80009252>\n\n\n\n### 2018-12-13\n\n对象数组通过对象的属性进行排序：<https://blog.csdn.net/xiaobing_hope/article/details/68638706>\n\n\n\n\n\n"
  },
  {
    "path": "16-前端综合/2019年-前端日记.md",
    "content": "\n### 2019-04-02\n\nVue屏幕宽度自适应：\n\n<https://blog.csdn.net/qq_25386583/article/details/77161478>\n\n<https://blog.csdn.net/xuaner8786/article/details/81565219>\n\n### 2019-04-07\n\n- 控制iframe中的页面只显示一部分：<https://blog.csdn.net/iteye_18722/article/details/81918563>\n\n### 2019-04-09\n\n```javascript\nDate.parse(\"2019/04/20 18:14:00\")\n```\n\n上方代码转换的结果，单位是`毫秒`，不是秒。\n\n\n### 2019-04-23\n\n```javascript\nconst a = [];\nconst b = {};\n\nconsole.log(Boolean(a));\nconsole.log(Boolean(b));\n```\n\n上方代码的打印结果均为true。 具体解释，可以看我在 `03-JavaScript基础/03-变量的强制类型转换.md`这篇文章里讲到的**转换为Boolean**。\n\n所以，我们平时在写业务代码的时候，“判断是否为空对象/空数组”，不能直接写成 `if (myObj)`或者`if(myArray)`，会踩坑。\n\n判断是否为空数组，可以用：\n\n```javascript\nif (myArray.length)\n```\n\n判断是否为空对象，可以用 ：\n\n```javascript\nif (JSON.stringify(myObj) !== '{}')\n```\n\n\n### 2019-04-26\n\n我们知道，在移动端页面尅发时，单位一般是采用 rem。\n\n设计稿如果是750px宽，那么，默认换算的单位如下：**16px = 1rem**。但是这种换算比较麻烦。我们可以在 html里加上如下代码：\n\n```html\n    <style>\n        html {\n            font-size: 20px;\n            font-size: 5.3333333vw;\n        }\n    </style>\n```\n\n这样的话，换算单位就变成了：**20px = 1rem**。\n\n\n\n### 2019-05-16\n\n- 数组随机打乱顺序：<https://www.zhihu.com/question/68330851/answer/262111061>\n\n最佳的打乱算法是Fisher-Yates算法。\n\n\n### 2019-05-16\n\nVue的全局变量空间：`this.$root.data`，我们可以在这里面存放数据。比如`this.$root.data.skuIdList`。\n\n### 2019-05-17\n\n- css 动画实现闪烁效果：<https://blog.csdn.net/wangxiuyan0228/article/details/80701523>\n\n### 2019-05-20-css3动画水平/镜像翻转\n\n参考链接1：<https://www.oschina.net/question/2443483_247744>\n\n代码实现举例：\n\n```html\n<!DOCTYPE html>\n<html>\n  <head lang=\"en\">\n    <meta charset=\"UTF-8\" />\n    <title></title>\n    <style>\n      @keyframes featuresicon {\n        0% {\n          transform: scaleX(1);\n        }\n\n        20% {\n          transform: scaleX(1);\n        }\n\n        50% {\n          transform: scaleX(0);\n        }\n        80% {\n          transform: scaleX(1);\n        }\n        100% {\n          transform: scaleX(1);\n        }\n      }\n\n      .cube {\n        width: 40px;\n        height: 40px;\n        background: url(images/bg2.png) left 0 no-repeat;\n\n        animation: featuresicon 1.3s linear alternate none infinite;\n      }\n\n      body {\n        background-color: cornflowerblue;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"cube\"></div>\n  </body>\n</html>\n\n```\n\n\n参考链接2：<https://blog.csdn.net/wjnf012/article/details/78679131>\n\n代码实现：（立体感更强一点）\n\n ```html\n\n<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title></title>\n<style type=\"text/css\">\n*{\n    padding: 0;\n    margin: 0;\n}\n\n.cube{\n    display: block;\n    color: #ffffff;\n    text-align: center;\n    width: 40px;\n    height: 40px;\n    border-radius: 4px;\n    /* background-color: #9a6ad8 */\n    background: url(images/bg.png) left 0 no-repeat;\n    animation: proRotate 1.3s ease-in-out 500ms alternate none infinite;\n}\n\n\n\n@keyframes proRotate {\n    0%{transform:perspective(200px) rotateY(180deg);}\n    100%{transform:perspective(200px) rotateY(0deg);}\n}\n</style>\n</head>\n<body>\n<div class=\"test_wrap\">\n    <div class=\"cube\"></div>\n\n</div>\n<body>\n</html>\n\n ```\n\n\n### 2019-05-22-判断字符串是否为纯中文\n\n参考链接：https://blog.csdn.net/wozaixiaoximen/article/details/48340061\n\n\n### 2019-05-24\n\n- VScode代码格式化后不符合ESLint风格问题处理：<https://blog.csdn.net/SilenceJude/article/details/81589784>\n\n\n### 2019-05-27-针对 text 文本的属性举例\n\n```css\n    &_promote {\n        margin-left: 10px;\n        display: inline-block;\n        height: 20px;\n        padding: 4px;\n        line-height: 20px;\n        background: #fff0f0;\n        border-radius: 4px;\n        font-size: 20px;\n        color: #ff4142;\n        white-space: nowrap;\n        text-overflow: ellipsis;\n        overflow: hidden;\n        vertical-align: middle;\n    }\n\n```\n\n尤其要研究一下 `vertical-align: middle;`这个属性。\n\n\n### 2019-06-11\n\n已知某背景图片的尺寸是：586 * 931。现只截图图片的上面一部分区域（586 * 810）做展示。代码实现如下：\n\n标签部分：\n\n```html\n\n<div class=\"img\"> </div>\n\n```\n\ncss部分：（重点是 background 属性的写法）\n\n```css\n  .img{\n      width: 586rpx;\n      height: 810rpx;\n      background: url('https://img11.360buyimg.com/jdphoto/s586x931_jfs/t1/27766/15/3237/102443/5c258955Ee307620e/21a744b0d2e065b3.png') 0 0/cover no-repeat;\n      margin: 0 auto;\n  }\n\n```\n\n\n### 2019-08-25\n\n使用hover为div添加边框时，页面布局发生错位的解决办法：https://blog.csdn.net/u014033756/article/details/51049574\n\n\n"
  },
  {
    "path": "16-前端综合/2020年-前端日记.md",
    "content": "### 2020-06-04-跨域问题\n\n如果遇到跨域问题，先尝试下 https 或者 http。跨域不一定是域名问题，也可能是协议头的问题。\n\n"
  },
  {
    "path": "16-前端综合/2022年-前端日记.md",
    "content": "### 2022-03-30\n\n有些 Mac 设备里，Safari 浏览器的默认字体竟然是宋体，这太奇怪了。建议在页面的 body 标签设置**字体族**的优先级，还是很有必要的：\n\n```css\nfont-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n```\n\n参考链接：\n\n-   [Web 字体 font-family 再探秘 · Issue #69 · chokcoco/iCSS](https://github.com/chokcoco/icss/issues/69)\n\n### 2022-04-27\n\n在 flex 布局的容器里，如果发现某个子元素的尺寸与预期不符，说明这个子元素可能是被挤掉了。\n\n解决办法是给这个子元素设置如下属性，则表示它不会被挤压：\n\n```\nflex-shrink: 0;\n```\n\n### 2022-09-26-从 html 富文本中提取纯文本（通过正则表达式）\n\n```js\n// 写法1\nconst text = html_str.replace(/<[^<>]+>/g, '');\n\n// 写法2\nconst reg = new RegExp('<[^<>]+>', 'g');\nconst text = html_str.replace(reg, '');\n```\n\n参考链接：\n\n-   [js 提取 html 字符串的文本方法总结（去 html 标签） - 掘金](https://juejin.cn/post/7020985945218351135)\n\n### 2022-11-16-修改页面内引入的 iframe 页面内部元素的样式\n\n参考代码：\n\n```js\ndocument.getElementById('ifm').onload = function () {\n    document\n        .getElementById('ifm') //获取到iframe\n        .contentWindow //获取到iframe的window\n        .document //获取到iframe的document\n        .getElementById('ifmh1').style.color = 'orange'; //正常获取元素 //修改样式\n};\n```\n\n注意，`iframe.contentDocument` 是 `iframe.contentWindow.document` 的简写。\n\n参考链接：\n\n- [修改页面内引入的iframe页面内部元素的样式](https://zhuanlan.zhihu.com/p/31296331)\n\n- [终于搞懂了 Iframe （跨窗口通信）](https://juejin.cn/post/7127916577684471845)\n\n\n### 2022-11-17-VS Code报错semi-colon expectedcss(css-semicolonexpected)的解决方法\n\n这可能是VS Code 的一个bug。\n\n出现这个问题, 可能并不是该条CSS语句出现问题, 而是它之前的CSS。所以请检查它之前的CSS有没有不完整或不正确的。\n\n参考链接：[VS Code报错semi-colon expectedcss(css-semicolonexpected)的解决方法 - 话语的服侍](https://blog.class4ever.com/2943.html)"
  },
  {
    "path": "16-前端综合/2024年-前端日记.md",
    "content": "## 2024 年 12 月\n\n### 2024-01-09\n\n许多CSS属性都可以使用 inherit 以便元素继承父元素的相应属性值。使用 inherit 可以确保一致性和更好的维护性，尤其是在嵌套元素的情况下。\n\n代码举例：\n\n```css\ncolor: inherit;\nfont-size: inherit;\ntext-align: inherit;\n\nline-height: inherit;\nmax-height: inherit;\n\nmargin: inherit;\npadding: inherit;\n```\n\n\n"
  },
  {
    "path": "16-前端综合/CSS开发总结.md",
    "content": "\n### p标签里的文字溢出怎么办\n\n加一个属性即可：\n\n```css\n\tword-break: break-all;\n\n```\n\n### inline-block 相关\n\n图片默认是 inline-block 布局，会存在经典的底部 3px 的问题。\n"
  },
  {
    "path": "16-前端综合/Express.md",
    "content": "\n\n## Express介绍\n\n- Express 官方网站：<https://expressjs.com/>\n\n- Express 官方网站（中文）：<https://expressjs.com/zh-cn/>\n\n\n## Express 安装\n\n安装 express：\n\n```bash\nnpm install express -g\n```\n\n安装 express-generator，安装之后，可使用应用程序生成器工具 (express) 快速创建应用程序框架。\n\n```bash\nnpm install express-generator -g\n\n```\n\n\n查看安装的 express 版本：\n\n```bash\nexpress --version\n\n```"
  },
  {
    "path": "16-前端综合/ajax相关.md",
    "content": "\n\n### jsonp ajax\n\najax跨域访问是一个老问题了，解决方法很多，比较常用的是JSONP方法，JSONP方法是一种非官方方法，而且这种方法只支持GET方式，不如POST方式安全。\n\n意思是说，如果后台返回的数据类型是jsonp，那么前端的请求方式只能是get，不能是post。\n\n如果跨域使用POST方式，可以使用创建一个隐藏的iframe来实现，与ajax上传图片原理一样，但这样会比较麻烦。\n\n因此，在**前端使用post方法，数据类型是json**的情况下，如果想跨域的话，可以通过设置Access-Control-Allow-Origin来实现跨域访问比较简单。\n\n解决办法：\n\n在 Response header中加入这三行：\n\n```\n response.headers['Access-Control-Allow-Origin'] = '*'\nresponse.headers['Access-Control-Allow-Methods'] = 'POST'\nresponse.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type'\n```\n\n\n\n参考链接：\n\n- [ajax 设置Access-Control-Allow-Origin实现跨域访问](https://blog.csdn.net/fdipzone/article/details/46390573/)\n\n\n"
  },
  {
    "path": "16-前端综合/html相关.md",
    "content": "\r\n## SSI：服务器端嵌入\r\n\r\nSSI：Server Side Include，服务器端嵌入。\r\n\r\n通俗点讲，就是在本地的html页面中，插入服务器上的文件。即：静态页面中，插入动态的代码。\r\n\r\n比如：\r\n\r\n```html\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>Document</title>\r\n    <style>\r\n\r\n    </style>\r\n    <!--#include virtual=\"/sinclude/common/head_inc.shtml\"-->\r\n    <!--#include virtual=\"/sinclude/common/head_shortcut.shtml\"-->\r\n    <!--#include virtual=\"head.shtml\"-->\r\n</head>\r\n```\r\n\r\n上面的代码中，注释里的代码，就是SSI部分，它加载的是服务器端的html页面。\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "16-前端综合/json字符串的解析和遍历.md",
    "content": "\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <script src=\"vue2.5.16.js\"></script>\n</head>\n\n<body>\n    <div id=\"app\">\n        <div v-for=\"(value,key) in myData\">\n            <p>{{key}}</p>\n            <p>{{value.name}}</p>\n        </div>\n\n    </div>\n\n    <script>\n\n        var dataList = [\n            { id: 11492948852, price: \"49.90\" },\n            { id: 11492948847, price: \"39.90\" }\n        ];\n\n        var datas = {};\n\n        var dataList2 = {\n            \"11492948852\": {\n                \"3\": \"1\",\n                \"spec\": \"\",\n                \"imagePath\": \"jfs/t3136/15/8874896477/153924/ee5100df/58cb7fa8N64311629.jpg\",\n                \"color\": \"白色 \",\n                \"name\": \"【多色可选】丽装铺园纯色百搭简约打底T恤女 白色 XL\",\n                \"size\": \"XL\"\n            },\n            \"11492948847\": {\n                \"3\": \"1\",\n                \"spec\": \"\",\n                \"imagePath\": \"jfs/t3109/27/9469817576/176241/aa424d04/58d4c849Ne22114ed.jpg\",\n                \"color\": \"灰色 \",\n                \"name\": \"【多色可选】丽装铺园纯色百搭简约打底T恤女 灰色 S\",\n                \"size\": \"S \"\n            },\n            \"11325444606\": {\n                \"3\": \"1\",\n                \"spec\": \"\",\n                \"imagePath\": \"jfs/t4447/354/3613344795/347891/4800da35/5901549fN468c7073.jpg\",\n                \"color\": \"叶脉-五骨\",\n                \"name\": \"迷你超轻小太阳伞雨伞小清新口袋伞 黑胶防晒五折两用遮阳伞 防紫外线折叠太阳伞 叶脉-五骨 五折伞\",\n                \"size\": \"五折伞\"\n            },\n            \"11492948848\": {\n                \"3\": \"1\",\n                \"spec\": \"\",\n                \"imagePath\": \"jfs/t3076/90/7623078170/152165/9fe8c39d/58b94105N8ed8d2c0.jpg\",\n                \"color\": \"橘色 \",\n                \"name\": \"【多色可选】丽装铺园纯色百搭简约打底T恤女 橘色 M\",\n                \"size\": \"M\"\n            }\n        }\n\n\n        dataList.forEach(function (item) {\n            for (item2 in dataList2) {\n                if (item.id == item2) {\n                    console.log('匹配成功');\n                    datas[item.id] = { imagePath: dataList2[item2].imagePath, name: dataList2[item2].name }\n                }\n            }\n        })\n\n        console.log(JSON.stringify(datas));\n\n\n\n\n        new Vue({\n            el: \"#app\",\n            data: {\n                myData: dataList2,\n\n            },\n            methods:{\n                clickMethod:function(){\n                    \n                }\n            }\n\n        });\n\n    </script>\n</body>\n\n</html>\n```"
  },
  {
    "path": "16-前端综合/json相关.md",
    "content": "\n\n## json中根据键获取值\n\n参考链接：\n\n- <http://yuxisanren.iteye.com/blog/1895807>\n\n- <https://blog.csdn.net/w405722907/article/details/72828041>\n\n"
  },
  {
    "path": "16-前端综合/上海有哪些IT互联网大厂.md",
    "content": "\n\n### 一线大厂\n\n- 字节跳动/头条（上海）\n\n- 蚂蚁金服（上海）\n\n- 阿里（上海）\n\n- 饿了么。PS：阿里旗下。\n\n- 口碑（上海）。PS：阿里旗下。\n\n- 哈啰出行。PS：阿里旗下。\n\n- 腾讯（上海）\n\n- 拼多多。PS：工资高，加班多（11、11、6）。\n\n- 美团点评（上海）。PS：上海的大众点评业务比美团业务多一些。\n\n- 携程。\n\n- 百度（上海）\n\n- 京东（上海）\n\n- 华为（上海）\n\n- 网易（上海）\n\n\n### 二线大厂\n\n- 爱奇艺（上海）。PS：百度旗下，独立运营；爱奇艺总部在北京，上海也有分部。性价比较高。\n\n- 哔哩哔哩。PS：地理位置在杨浦区。\n\n- 阅文集团。PS：腾讯旗下，独立运营。\n\n### 独角兽公司\n\n- 叮咚买菜\n\n- 蔚来。PS：自动驾驶领域。地理位置在嘉定区。\n\n- 依图科技：人工智能领域\n\n- 七牛云：云服务\n\n- 途虎养车：车类B2C电商平台\n\n- 触宝。PS：触宝输入法、触宝电话等，还有很多其他的小众产品。海外市场做得很好。\n\n\n### 小而美的创业公司\n\n- 即刻（主要产品：即刻App）\n\n- 莉莉丝游戏：游戏领域，创始人是「王信文」。\n\n- 米哈游：游戏领域\n\n- LeetCode（力扣）：在线编程网站\n\n### 三线大厂\n\n- 平安系列（平安科技、平安寿险、平安产险、平安金融等，各自独立，可分开投简历；虽然可以分别投简历，但一旦其中一个开始走程序，其他将暂停）。\n\n- 喜马拉雅\n\n- 蜻蜓FM\n\n- 小红书\n\n- UCloud：云服务\n\n- 声网Agora：实时音视频云行业\n\n- 趣头条\n\n- WiFi万能钥匙\n\n- 巨人网络。PS：游戏领域。\n\n- 三七互娱\n\n- 盛大网络。PS：游戏领域。\n\n- 前程无忧（51job）\n\n### 外企-上海\n\n- Google（谷歌）\n\n- 微软\n\n- IBM\n\n- Tesla（特斯拉）\n\n- Cisco（思科）\n\n- Intel（英特尔）\n\n- AMD（超威半导体）\n\n- EMC（易安信）\n\n- SAP（思爱普）\n\n- PayPal：跨境支付\n\n- eBay：电子商务\n\n- NVidia（英伟达）：计算机图形技术、显卡相关。\n\n### 互联网金融\n\n\n- 陆金所（平安旗下）\n\n\n- 东方财富\n\n### 其他公司\n\n- 得物App：就是之前的「毒」App，头号炒鞋平台。\n\n- unity：游戏引擎相关\n\n- 虎扑：体育互联网平台\n\n\n- 宝尊电商\n\n\n### 传统行业（不仅限于IT领域）\n\n- 三大电信运营商：中国移动、中国电信、中国联通\n\n- 中国银联\n\n- 各大银行\n\n- 远景能源\n\n- 众安在线（众安保险）：互联网保险\n\n- 沪江英语\n\n### 传统行业-上海外企（不仅限于IT领域）\n\n- J. P. Morgan（摩根大通）：银行、互联网金融\n\n- 花旗银行\n\n- Autodesk（欧特克）：建筑设计\n\n"
  },
  {
    "path": "16-前端综合/前端分享群整理.md",
    "content": "\n\n## 前言\n\n以下内容，来自微信群的部分优质分享。不定期更新。文中涉及的内容和链接，均为群友自主推荐、自主分享。\n\n\n### 2019-05-10\n\n**1、深圳-团长**：\n\n新手学习Node.js\n\n- 推荐狼叔的《如何正确学习Node.js》，地址：https://github.com/i5ting/How-to-learn-node-correctly\n\n- Node.js国内交流社区：https://cnodejs.org/\n\n- 推荐书籍：《Node.js实战》（第二版）、《Node.js调试指南》、《深入浅出Node.js》（有一定的基础后再看）、《更了不起的Node.js》（据说今年会出版）\n\n备注：在一个QQ群里看到的，仅供参考。\n\n**2、深圳-团长**：\n\n- promise的各种用法：<https://github.com/sindresorhus/promise-fun>\n\n小组的一位同事，今天在周会上重点分享和讲解了这个项目，说这个项目非常吊。\n\n如果掌握了 promise 的深层次用法，绝对吊打面试官。\n\n我看了下这个项目作者的介绍，也很牛逼：\n2014年之后，作为自由职业者，全职做开源社区的项目。一边做开源项目，一边背包环游东南亚，目前已经在泰国曼谷定居，但仍然每天都在做开源。\n\n**3、广州-小阳**：\n\n- VS Code插件推荐：Code Runner\n\n我之前想跑js代码，都是写在html文件里的，然后就找到了这个。可以直接运行。\n\n**4、深圳-团长**：\n\n高效易用的自动标注工具：**PxCook（像素大厨）**。软件下载链接：<https://www.fancynode.com.cn/pxcook>\n\n可以直接标注 Photoshop、Sketch 的设计原稿。很方便。\n\n我们小组的一位前端妹子刚刚在用，大呼好用，于是一堆人跑过去围观。所以我推荐下。\n\n### 2019-05-09\n\n**1、上海-前端-强子**：\n\n- 《[从Oracle的裁员，到“技术专家陷阱”](https://mp.weixin.qq.com/s/oiPGntttmA-NFEYQMEfwxQ)》\n\n**2、上海-前端-强子**：\n\n\n- 阮一峰推特更新：<https://juejin.im/user/5a586bc66fb9a01cb1391339>\n\n**3、深圳-团长**：\n\n\n- 推荐一个chrome插件：**FireShot**。滚动截长图，很流畅。\n\n**3、杭州~nan**：\n\n\n- 《[零基础转行去阿里做前端，创业当 CTO，他是如何做到的？](https://blog.csdn.net/csdnsevenn/article/details/90033230)》\n\n**4、广州-斌桑**：\n\n- 《[考研到底值不值得](https://mp.weixin.qq.com/s/QPRAMmBk-gHzYQOU_0CzHg)》\n\n### 2019-05-08\n\n\n**1、深圳-团长**：\n\n- 带你了解一下科技类图书四大社：<https://www.jianshu.com/p/b65ac62bf407>\n\n到目前为止有三个品牌真正立起来了，读者认、作者也认：\n\n- 人民邮电出版社：图灵公司，合资企业\n\n- 电子工业出版社：博文视点，全资子公司\n\n- 机械工业出版社：华章公司，合资公司\n\n- 而清华大学出版社没有一个拿得出手的品牌，有些可惜。\n\n**2、上海-乐亦栗**：\n\n偶然发现张鑫旭大佬一篇旧文，分享出来希望对大家有用。\n\n- 话说我为什么要闭关学习：<http://www.zhangxinxu.com/life/?p=98>\n\n一点感慨：就算张鑫旭大佬从事别的事业，肯定也是拔尖的。\n\n**3、深圳-核桃**：\n\n- 2018年8月中级前端开发推荐书籍：<https://zhuanlan.zhihu.com/p/40761206>\n\n张鑫旭的《CSS世界》真是写的是真的好，准备翻出来看第三遍了。我最近看的书都是按照这个书单看的，前面基本还可以，从《Node.js:来一打C++扩展》后面开始感觉就有点get不到书里面的主题了。\n\n### 2019-05-07\n\n\n**1、广州 lien**：\n\n- 《[编程语言的发展趋势：从没有分号，到DSL](https://www.imooc.com/read/27/article/254)》\n\n\n### 2019-05-06\n\n\n**1、深圳-团长**：\n\n- Python - 100天从新手到大师：<https://github.com/jackfrued/Python-100-Days>\n\n这个项目的作者是 千锋培训机构的讲师。\n\n\n\n**1、广州 lien**：\n\n- 我司用的 mock 工具是：`eoLinker AMS 开源版本4.0`\n\n**2、上海-前端-邱明**：\n\n- 《[一名【合格】前端工程师的自检清单](https://juejin.im/post/5cc1da82f265da036023b628)》\n\n\n\n### 2019-05-05\n\n**1、上海-前端-强子**：\n\n- 《阿里云前端技术周刊》：<https://github.com/aliyunfe/weekly>\n\n\n**2、武汉-林夕之间**：\n\n\n- 国内10大前端团队网站：<https://zhuanlan.zhihu.com/p/60091235>\n\n**3、深圳-pubdreamcc**：\n\n\n- 腾讯新闻前端团队维护的一个周刊：<https://github.com/Tnfe/TNFE-Weekly>\n\n**4、上海-gzd**：\n\n- vue 作者写的 todolist：<http://todomvc.com/examples/vue/>\n\n### 2019-05-01\n\n**1、深圳-团长**：\n\n- 《[那些年的体验技术部](https://www.yuque.com/afx/blog/those-days)》\n\n**2、广州-古力**：\n\n- 《[JavaScript 高性能数组去重](https://www.cnblogs.com/wisewrong/p/9642264.html)》\n\n\n### 2019-04-30\n\n**1、成都-颜乐乐**：\n\n- <https://github.com/frontend9/fe9-library>\n\n这个是阿里蚂蚁为首的一群开发者发起的一个组织， 专门做前端知识布道工作， 输出各种高质量技术文章、以及各种技术教程。\n里面的文章从出门级别到架构级别的，各种框架测试其他应用层面的应有尽有。\n\n\n### 2019-04-28\n\n**1、上海-前端-强子**：\n\n分享一个小程序，名叫「GitHub专业版」。\n\n**2、深圳-核桃**：\n\n看到一篇to b产品UX & UI 设计总结不错。\n\n- “后台产品” UX & UI 设计总结（上）- 设计要点概括：<https://zhuanlan.zhihu.com/p/28787738>\n\n**3、广州Brenner**：\n\n- web安全学习笔记：<https://websec.readthedocs.io/>\n\n**4、北京～夜微凉**：\n\n- 可能是最全的前端动效库汇总：<https://juejin.im/post/5cc089eae51d456e7d189f9d>\n\n**5、成都-颜乐乐**：\n\n前一段时间还撸了一个 node 爬虫：TS+puppeteer+cheerio+fs-extra\n\n- <https://github.com/yanlele/node-spider>\n\n因为用了headless brower 所以就算是单页应用 也不要用怕爬去不到数据、\n模仿浏览器访问也不用担心 ip 被封。\n\npython 和 node 爬虫适应范围不一样。python 可以做分布式集群、做数据管理和分析， 可以直接同构代码一把梭。\nnode 爬虫， 更加贴近于前端节点访问请求。\n\n如果没有企业级需求， 每天抓个几百万的那种， 个人项目临时整点儿数据和或者临时需要抓取 数据，node 很好用的。\n\n很简单一个道理， 我们一个后台应用， 对接十几个Java服务， 需要一个调度者去支配这些服务和调度这些服务。 当然Java也可充当这个角色， 但是node的异步非阻塞模型， 显然这这个场景， 比Java优势大。\n\n可以参考阿里技术体系，他们nodejs 在服务端基本上就是发挥这个作用， 传统后端 controller -> service -> model, 可以理解为nodejs 中间层就干了 一个 controller 和 view 的工作， java 可以专注于service 业务实现。\n\n推荐阅读文章：\n\n- 使用JavaScript写爬虫：<https://zhuanlan.zhihu.com/p/53763115>\n\n\n**6、成都-颜乐乐**：\n\nmysql相关的书籍推荐：《深入浅出MySQL 数据库开发 优化与管理维护》\n\n\n### 2019-04-27\n\n**1、上海-前端-强子**：\n\n- 猴子都能懂的Git入门：<https://backlog.com/git-tutorial/cn/>\n\n\n**2、广州-萧雪圣**：\n\n- 像玩游戏闯关一样学习git！: <https://learngitbranching.js.org/>\n\n### 2019-04-26\n\n**1、成都-颜乐乐**：\n\n推荐一个，前端测试，全干货。陆陆续续总结整理了大半年才成了现在的体系：\n\n- <https://github.com/yanlele/node-index/tree/master/book/13%E3%80%81%E6%B5%8B%E8%AF%95%E4%B8%93%E9%A2%98>\n\n前端单元测试，非常重要。很简单的一个例子，别人写了一个很复杂的模块功能，怎么学习别人的代码？最简单的办法，就是跑测试。\n\n测试对于学习代码和看源码非常重要。\n\n（深圳-团长 备注：会前端单元测试，是很加分的。虽然我不会，但准备学一学。）\n\n（上海-前端-强子 备注：这个不错，别人的发的东西，先加入到需整理列表里面，后面空闲了，慢慢消化，内化成自己的知识体系。）\n\n**2、成都-颜乐乐**：\n\n再推荐一个，学习javascript数据结构与算法。是当年吃《javascript数据结构与算法 [巴西] Loiane Groner 著 孙晓博等译》 这本书总结的熟肉\n\n- <https://github.com/yanlele/node-index/tree/master/book/09%E3%80%81%E5%AD%A6%E4%B9%A0javascript%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95>\n\n吃完了文章很多东西， 用自己的语言方式组织精简过，也加了很多自己的理解和备注。\n\n**3、成都-颜乐乐**：\n\n吃了《javascript设计模式》 张容铭 著 这本书之后， 吐出来的熟肉：\n\n- <https://github.com/yanlele/node-index/tree/master/book/04%E3%80%81js%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F>\n\n这个数设计模式思想非常好，写的很棒，不过基本上都是ES5语法，这本书上出现的所有 demo 被我改成了es6 写法，计思想被我精简过和加上了一些自己的备注理解。\n\n**4、北京-Rocky**：\n\n- 各种免费的 PDF 在线工具：<https://www.ilovepdf.com/>\n\n**5、深圳-团长**：\n\n- 一名【合格】前端工程师的自检清单：<https://juejin.im/post/5cc1da82f265da036023b628>\n\n**6、成都-颜乐乐**：\n\n- 关于 css 实现多行**文字截断**问题：<https://segmentfault.com/a/1190000008649988>\n\n\n**7、杭州~nan**：\n\n- 《[2019第4届中国前端开发者大会](https://mp.weixin.qq.com/s/6ZuLEQZLsu7GUquwSgJT2w)》\n\n**8、深圳-团长**：\n\n- 《[深圳IT互联网大厂有哪些（2019年版）](https://github.com/qianguyihao/Web/blob/master/17-%E5%89%8D%E7%AB%AF%E7%BB%BC%E5%90%88/03-%E6%B7%B1%E5%9C%B3IT%E4%BA%92%E8%81%94%E7%BD%91%E5%A4%A7%E5%8E%82%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%882019%E5%B9%B4%E7%89%88%EF%BC%89.md)》\n\n**9、深圳-工兵**：\n\n- IOS加**阻尼滑动**可能导致的bug和处理方法：https://www.cnblogs.com/hrone/p/10143960.html\n\n（团长备注：滚动穿透、阻尼滑动，都是移动端的常见问题，可以关注下。）\n\n### 2019-04-18\n\n**1、深圳-团长**：\n\n**蓝湖**：一款产品文档和设计图的在线协作平台。网址：<https://lanhuapp.com>\n\n最近在做一个需求，我们设计师用到了这个网站。大家可以了解下，拓展视野。\n\n（广州-萧雪圣补充：蓝湖，这个我上家和现在公司都在用，UI用这个出效果图，标注好各种尺寸，前端也比较好开发，还可以直接下载切图，用得好是个能提高开发效率的工具。）\n\n### 2018-12-27\n\n**1、上海-前端-强子**：\n\n- 前端大佬汇总：<http://caibaojian.com/c/qianduan>\n\n（团长备注：几位前端大佬，基本都在里面了，除了我。）\n\n**2、广州Brenner**：\n\n- Canvas: Draw on the web（HTML5 Canvas 教程）：<https://www.yuque.com/airing/canvas>\n\n（上海-前端-强子 备注：这个很不错，我前几天做分享战绩的时候就用到这个了。）\n\n\n"
  },
  {
    "path": "16-前端综合/前端博客推荐.md",
    "content": "\n## 前端博客推荐\n\n- 冴羽的博客：<https://github.com/mqyqingfeng/Blog>\n\n冴羽写博客的地方，预计写四个系列：JavaScript深入系列、JavaScript专题系列、ES6系列、React系列。\n\n- 颜乐乐：<https://github.com/yanlele/node-index>\n\n在头条做前端，博客内容很全，尤其是“单元测试”系列。\n\n\n\n"
  },
  {
    "path": "16-前端综合/前端开发积累.md",
    "content": "\n\n### SPU 和 SKU\n\n\nSKU（stock keeping unit）：库存量单位。 SKU是物理上不可分割的最小库存单元。通俗来讲，你可以把 sku 理解成是「**最小单元**」。\n\nSPU（Standard Product Unit）：标准化产品单元。是商品信息聚合的最小单位。通俗来讲，你可以把 spu 理解成是「**某一类的单元**」。\n\n比如说，针对 Kindle paperwhite4 这款阅读器，颜色分白色、黑色两种。那么，白色和黑色的sku是不一样的。因此，Kindle paperwhite4 这款阅读器有「**两个sku、一个spu**」。\n\n再比如说，针对 iPhone 8 这款手机，颜色有银色、红色、金色三种，存储空间有分64G、256G两种。那么，64G和256G的sku是不一样的；不同的颜色，也代表着不同的sku。因此，iPhone 8 这款手机有「**六个sku、一个spu**」。\n\n单品：对一种商品而言，当其品牌、型号、配置、等级、花色、包装容量、单位、生产日期、保质期、用途、价格、产地等属性中任一属性与其他商品存在不同时，可称为一个单品。\n\n### 移动端WebApp前端真机调试：iPhone/iOS借助Safari进行真机调试\n\n（1）手机端：设置 → Safari → 高级 → Web 检查器 → 开。\n\n（2）mac端：Safari → 偏好设置 → 高级 → 在菜单栏中显示“开发”菜单。\n\n（3）在 OS X 中启动 Safari 之后，以 USB 电缆正常接入 iOS 设备，并在此移动设备上启动 Safari。此时点击计算机上的 Safari 菜单中的“开发”，可以看到有 iOS 设备的名称显示，其子菜单项即为移动设备上 Safari 的所有标签页，点击任意一个开始调试。如下：\n\n![](http://img.smyhvae.com/20180621_1900.png)\n\n参考链接：\n\n- [移动端前端开发真机调试攻略](https://juejin.im/entry/563ab66400b0bf37d79aa17d)\n\n- [iOS、Android 之类的如何调试 Web APP](https://segmentfault.com/q/1010000000124121)\n\n"
  },
  {
    "path": "16-前端综合/前端语录.md",
    "content": "### 2020-04-19\n\n语言的强大在于它的生态。如果是站在巨人的肩膀上，就不用做太多重复的事情。\n\n"
  },
  {
    "path": "16-前端综合/北京有哪些IT互联网大厂.md",
    "content": "\n### 一线大厂\n\n- 字节跳动/头条\n\n- 腾讯（北京）\n\n- 阿里（北京）\n\n- 美团点评\n\n- 滴滴出行\n\n- 网易\n\n- 百度\n\n- 京东\n\n- 小米\n\n- 360\n\n- 华为（北京）\n\n### 二线大厂\n\n- 爱奇艺\n\n- 好未来\n\n- 腾讯音乐（TencentMusicEntertainmentGroup，简称TME）。\n\n腾讯音乐独立上市，市值近200亿。旗下产品：qq音乐、酷狗音乐、酷我音乐、全民K歌。\n\n- 快手\n\n- 京东数科\n\n- 爱奇艺\n\n- 去哪儿网\n\n- 搜狗\n\n- 知乎\n\n- 新浪微博\n\n- 新浪\n\n- 豆瓣\n\n- 度小满（百度旗下金融服务）\n\n### 独角兽公司\n\n- 罗辑思维：主要产品是「得到App」\n\n- 商汤科技：人工智能领域的独角兽\n\n- 猿辅导：在线教育\n\n- 跟谁学：在线教育\n\n- 作业帮\n\n- VIPKID：在线青少儿英语教育品牌\n\n- 贝壳找房\n\n- 旷视科技：人工智能领域\n\n- 依图科技（北京）：人工智能领域\n\n\n### 小而美的公司\n\n- 多抓鱼\n\n- 神策\n\n### 三线大厂\n\n- 58同城\n\n- 完美世界\n\n- 金山软件\n\n- 陌陌\n\n- 英雄互娱\n\n- 一点资讯（小米旗下）\n\n- 搜狐\n\n- 用友网络科技\n\n- 映客直播\n\n- 美图（北京）。PS：美图秀秀、美颜相机、美拍等。\n\n- 英语流利说\n\n- 京东方\n\n### 外企-北京\n\n- 微软\n\n- Amazon（亚马逊）\n\n- IBM\n\n### 其他\n\n- 猎豹移动\n\n- 广联达：建筑产业互联网平台服务商\n\n### 传统行业（不仅限于IT领域）\n\n- 链家网\n\n- 瓜子二手车\n\n- 汽车之家\n\n- 新东方\n\n\n\n\n"
  },
  {
    "path": "16-前端综合/模板引擎.md",
    "content": "---\ntitle: 认识Web和Web标准\npublish: false\n---\n\n<ArticleTopAd></ArticleTopAd>\n\n\n\n\n## 模版引擎\n\n### 引入\n\n我们在使用ajax请求数据时，返回的如果是一个 JSON 格式的字符串，我们需要将其包装到对应的HTML代码中，再添加到页面上，才能看到效果。那么这个包装得过程有没有简单的方法呢?\n\n\n假设在 js 中有如下数据：\n\n\n\n```javascript\n\tvar obj = {\n\t\tname:\"fox\",\n\t\tage:18,\n\t\tskill:\"卖萌\"\n\t};\n```\n\n希望包装为:\n\n```html\n<ul>\n  <li>姓名:fox</li>\n  <li>年龄:18</li>\n  <li>爱好:卖萌</li>\n</ul>\n```\n\n\n我们可以通过模板插件来实现。\n\n### 模版插件的原理\n\n我们定义一段文本作为模板,读取文本,使用特殊的符号<%= 属性名 %>,通过正则表达式找到这些特殊的符号进行替换,是不是就实现了这样的效果呢?\n\n### 常见的模板引擎\n\n- BaiduTemplate(百度开发)\n\n- ArtTemplate(腾讯开发)：[GitHub地址](https://github.com/aui/art-template)、[文档地址](https://aui.github.io/art-template/zh-cn/docs/)。\n\n- velocity.js(淘宝开发)\n\n- Handlebars\n\n\n##  ArtTemplate\n\n\n标准语法：\n\n\n\n\n```html\n\t{{if user}}\n\t  <h2>{{user.name}}</h2>\n\t{{/if}}\n```\n\n\n\n\n渲染模板：\n\n\n```javascript\n\tvar data = {\n\t\ttitle: `标签`,\n\t\tlist: [`文艺`, `博客`, `摄影`]\n\t};\n\tvar html = template(`test`, data);\n\tdocument.getElementById(`content`).innerHTML = html;\n```\n\n\n举例：\n\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <script src=\"js/template-native-debug.js\"></script>\n    <script src=\"js/jquery-2.2.0.js\"></script>\n\n    <script id=\"tpl\" type=\"text/html\">\n        <h3><%= className %></h3>\n        <ul>\n            <% for(var i = 0; i< students.length;i++) { %>\n                <li><%= i+1 %>. <%= students[i] %></li>\n            <% } %>\n        </ul>\n\n    </script>\n\n    <script>\n        var data = {\n            className:\"前端1期\",\n            students:[\"张飞\",\"刘备\",\"诸葛亮\",\"甄姬\",\"小乔\",\"汪汪\"]\n        };\n\n        $(function (){\n            var html = template(\"tpl\",data);\n            $(\"#demo\").html(html);\n        })\n    </script>\n</head>\n<body>\n\n    <div id=\"demo\">\n\n    </div>\n</body>\n</html>\n```\n\n\n效果：\n\n![](http://img.smyhvae.com/20180301_1223.png)\n\n"
  },
  {
    "path": "16-前端综合/深圳有哪些IT互联网大厂.md",
    "content": "\n\n### 一线大厂\n\n- 腾讯\n\n- 字节跳动/头条（深圳）\n\n- 阿里（深圳）\n\n- 京东（深圳）\n\n- 百度（深圳）\n\n- 华为（深圳）\n\n\n### 二线大厂\n\n- 大疆\n\n- oppo（深圳）\n\n- 顺丰\n\n- 微众银行\n\n- vivo\n\n- 一加手机\n\n### 独角兽公司\n\n- 商汤科技：人工智能领域的独角兽\n\n- 超级猩猩：运动健身领域的独角兽\n\n### 小而美的公司\n\n- [HelloTalk](https://www.hellotalk.com/)。全球最大的语言学习社交社区。\n\n- 大宇无限\n\n- 知识星球\n\n- [XMind](https://www.xmind.cn/)。思维导图软件。\n\n- 房极客\n\n### 三线大厂\n\n- 平安系列（平安科技、平安寿险、平安产险、平安金融等，各自独立，可分开投简历）\n\n- Shopee（中文名“虾皮”，东南亚最大的电商平台）\n\n- 有赞（深圳）\n\n- 深信服\n\n- 中兴\n\n- 海能达（专网通信领域全国第一、全球第二，仅次于摩托罗拉）\n\n\n- 迅雷\n\n- 卷皮网\n\n### 互联网金融\n\n- 乐信\n\n- 广发\n\n- 中信\n\n- 招商\n\n### 其他\n\n- 金蝶、随手记（随手记属于金蝶旗下，但可以分开投简历）\n\n- 珍爱网\n\n- shein（跨境时尚电商）\n\n- 平安好医生\n\n### 传统行业（不仅限于IT领域）\n\n- 三大电信运营商：中国移动、中国电信、中国联通\n\n- 各大银行\n\n- 恒大、万科\n\n- 万师傅（行业领先家居服务平台）\n\n\n### 系列文章合集\n\n\n"
  },
  {
    "path": "16-前端综合/网友对本项目提的建议.md",
    "content": "\n\n\n- 图片的宽度，最好不要超过800px，否则在github上显示不完整，甚至无法显示。\n\n- 建议把图片压缩后再上传。图片大小最好控制在100kb左右。比如说，[javascript-tutorial-cn](https://github.com/iliakan/javascript-tutorial-cn)这个项目，在这方面就做得很好。\n\n比如`20180223_2201.gif`这张图，原本是500kb，我压缩后重新上传，是200kb。\n\n再比如`20180611_2130.png`这张图，原本是400kb，我压缩后重新上传，是200kb。"
  },
  {
    "path": "17-资源推荐/01-前端书籍推荐.md",
    "content": "## JS相关书籍\n\n### 《[JavaScript高级程序设计](https://book.douban.com/subject/35175321/)》\n\n红宝书，人人都知道。\n\n### 《[JavaScript语言精髓与编程实践](https://book.douban.com/subject/35085910/)》\n\n周爱民的书，谁能不爱？这本书不但完整解析了 JavaScript 语言，还逐一剖析了相关特性在多个开源项目中的编程实践与应用。\n\n### 《[你不知道的JavaScript](https://book.douban.com/subject/26351021/)》\n\n上面这套书有上、中、下三本，你都可以读一读。如果时间不够，那就先读第一本。\n\n### 《[JavaScript 悟道](https://book.douban.com/subject/35469273/)》\n\n本书首先介绍JavaScript的基本知识：命名、数值、布尔值和字符串等，并展示了其缺陷和局限性，但随后展示了如何解决这些问题；接着继续研究数据结构和函数，探索底层机制，并使用高阶函数来实现面向对象编程。\n\n本书的翻译是@死月，\n\n### 《[ECMAScript6入门](https://book.douban.com/subject/25966265/)》\n\n阮一峰的ES6入门书。\n\n### 《[javascript忍者秘籍](https://book.douban.com/subject/30143702/)》\n\n包含了实现常见功能的最佳实践，第二版是 ES6 为主，比如函数的上下文、闭包的原理、promise的实现、宏任务微任务等。\n\n## CSS相关书籍\n\n### 《[CSS世界](https://book.douban.com/subject/27615777/)》\n\n关于 CSS 的书籍，首先推荐这本书，我身边的大佬们都说这本书好。虽然我不是大牛，但我也觉得这本书很好。\n\n如果 js 熟练，说明你是有技术深度的前端；如果 css 熟练，说明你是有经验的前端。\n\n### 《[CSS新世界](https://book.douban.com/subject/35539710/)》\n\n这是一本关于CSS的进阶读物，专门讲CSS3及其之后版本的新特性。\n\n### 《[CSS揭秘](https://book.douban.com/subject/26745943/)》\n\n本书是一本注重实践的教程，作者为我们揭示了 47 个鲜为人知的 CSS 技巧。\n\n### 《[精通 CSS](https://book.douban.com/subject/30450258/)》\n\nCSS 进阶书籍。\n\n## 面试相关书籍\n\n### 《[前端开发核心知识进阶：从夯实基础到突破瓶颈](https://book.douban.com/subject/35218831/)》\n\nJS基础、ES6语法、Vue源码、React源码、前端性能优化等等，这些话题都是面试必问。\n\n### 《[了不起的JavaScript工程师：从前端到全端高级进阶](https://book.douban.com/subject/34788884/)》\n\n作者@[亚里士朱德](https://yalishizhude.github.io/about/) 是慕课网讲师，博客和课程都写的好。这本书讲述了开发者使用JavaScript在各种Web开发场景下所需要掌握的重点知识和概念。\n\n## Vue.js、React 源码相关书籍\n\n### 《[深入浅出Vue.js](https://book.douban.com/subject/32581281/)》\n\n这本书讲 Vue.js 2.0的实现原理，很不错。 评分不高，不是因为书不好，而是因为你没看懂。\n\n## 设计模式\n\n### 《[javascript设计模式与开发实践](https://book.douban.com/subject/26382780/)》\n\n\n\n## 推荐链接\n\n- [前端必备javascript书籍测评【含红宝书和绿皮书】](https://mp.weixin.qq.com/s/nffwL_ma1t30lr4ooyWARQ)\n\n- [2021前端学习路径书单—自我成长之路](https://mp.weixin.qq.com/s/_OZ7QS_f6vQpOABebHK0KQ)\n\n- [web前端工程师读书单](https://www.douban.com/doulist/2772859/)\n\n"
  },
  {
    "path": "17-资源推荐/02-Web前端最新导航.md",
    "content": "\n\n> 本文的最新内容将在[GitHub](https://github.com/qianguyihao/Web)上实时更新。欢迎在GitHub上关注我，一起入门和进阶前端。\n\n## 前言\n\n本文列出了很多与前端有关的常见网站、博客、工具等，整体来看比较权威。有些东西已经过时了，我就不列出来了。\n\n学是一方面，也是最主要的方面；但还有一个作用，比如，“这个前端框架你都不知道啊”、“这个前端大牛你都没听说过啊” ，此时，这份清单就能起到作用了。如果你能把清单里列出的内容都了解下，逼格也会高很多。\n\n## 技术社区\n\n- GitHub：<https://github.com/>\n\n高质量的内容创作和分享平台。\n\n请记住，作为一个码农，GitHub 代表了你的名片。\n\n- stackoverflow：<https://stackoverflow.com/>\n\n遇到技术问题请先Google，很多答案都能在 stackoverflow 上找到。\n\n## 技术博客\n\n- 掘金：<https://juejin.im/>\n\n掘金已经被前端同学攻陷了。目前来看，国内的很多优质前端文章，都在掘金上。\n\n如果你刚开始写前端博客，可以考虑在掘金上发文章。当然，文章最好在**掘金、博客园、知乎**上做同步。\n\n- 博客园：<https://www.cnblogs.com/>\n\n一个很纯粹的技术博客平台。\n\n- 知乎：<https://www.zhihu.com/>\n\n很多做技术的同学也开始玩知乎了，阿里的不少前端大牛在知乎上就非常活跃。\n\n- CSDN：<https://www.csdn.net/>\n\n广告太多，但奈何你这么老牌。\n\n- segmentfault：<https://segmentfault.com/>\n\n比较低调的技术博客平台。\n\n## GitHub 排名统计\n\n- GitHub 中文排行榜、高分优秀中文项目：<https://github.com/kon9chunkit/GitHub-Chinese-Top-Charts>\n\n中国区的高分项目，都在这里了。\n\n\n- GitHub 全球排名：<https://gitstar-ranking.com/>\n\n这个排名很权威。如果你的项目超过 10k star，就能上榜，跻身全球 GitHub 项目前1000名。\n\n- GitHub trending（官网推荐）：<https://github.com/trending>\n\n你的项目要是能上 GitHub trending，绝对火得一塌糊涂。\n\n2019-12备注：现在的 GitHub trending 已经不是严格按照新增的 star 数来排行了。所以说，这个排名，也不是那么权威了。\n\n- GitHub 中国区排名：<https://githuber.cn/search?language=JavaScript>\n\n这个网站虽然比较小众，但排名还是相对比较准的。\n\n- GitHub 中国区排名：<http://githubrank.com/>\n\n这个排名很久没更新了，早就不准了；而且还经常打不开。\n\n\n## 资讯\n\n- 36氪：<https://36kr.com/>\n\n- 虎嗅网：<https://www.huxiu.com/>\n\n- 利器：<https://liqi.io/>\n\n采访优秀的创造者，邀请他们来分享工作时所使用的工具。\n\n- 湾区日报：<https://wanqu.co/>\n\n每天推送 5 篇优质英文文章。\n\n- Solidot：<https://www.solidot.org/>\n\n- 品玩：<https://www.pingwest.com/>\n\n- 极客公园：<http://www.geekpark.net/>\n\n## 框架\n\n- Vue.js：<https://cn.vuejs.org/>\n\n- React：<https://reactjs.org/>\n\n- Angular：<https://angular.cn/>\n\n- AngularJS：<https://angularjs.org/>\n\n- Koa：<https://koa.bootcss.com/>\n\n基于 Node.js 平台的下一代 Web 开发框架。\n\n- Express：<http://www.expressjs.com.cn/>\n\n基于 Node.js 平台，快速、开放、极简的 Web 开发框架。\n\n- Egg：<https://eggjs.org/zh-cn/>\n\nEgg 继承于 Koa。\n\nKoa 是一个非常优秀的框架，然而对于企业级应用来说，它还比较基础。而 Egg 选择了 Koa 作为其基础框架，在它的模型基础上，进一步对它进行了一些增强。\n\n- Electron：<https://www.electronjs.cn/>\n\nElectron（原名为Atom Shell）是GitHub开发的一个开源js框架。 它允许使用Node.js（作为后端）和Chromium（作为前端）完成桌面GUI应用程序的开发。\n\n也就是说，我们可以用 js 语言开发客户端软件了。比如说，VS Code 这个客户端软件就是用 js 语言写的。\n\n- Redux：<https://www.redux.org.cn/>\n\nRedux 是 JavaScript 状态容器，提供可预测化的状态管理。\n\n- ReactNative：<https://reactnative.cn/>\n\n使用JavaScript编写原生移动应用。\n\n- mpvue：<http://mpvue.com/>\n\n基于 Vue.js 的小程序开发框架。\n\n## UI框架\n\n- Bootstrap：<http://www.bootcss.com/>\n\n- Ant Design：<https://ant.design>\n\n基于 React 的 UI 组件库，主要用于研发企业级中后台产品。官网推出了 [Ant Design pro](https://pro.ant.design/) 作为示例，可以看看。\n\n- Ant Design Mobile：<https://mobile.ant.design/>\n\n一个基于 Preact / React / React Native 的 移动端 UI 组件库。\n\n- Ant Design of Vue：<https://vue.ant.design/docs/vue/introduce-cn/>\n\nAnt Design 的 Vue 实现，开发和服务于企业级后台产品。\n\n- ElementUI：<http://element-cn.eleme.io/>\n\n基于 Vue.js 的组件库。\n\n- iView：<https://www.iviewui.com/>\n\n一套基于 Vue.js 的高质量 UI 组件库。\n\n## 类库\n\n- jQuery：<http://jquery.com/>\n\n- Zepto.js：<https://zeptojs.com/>\n\n可以理解成是移动端的 jQuery。\n\n- ECharts：<https://echarts.baidu.com/>\n\n使用 JavaScript 实现的开源可视化库。\n\n## CSS\n\n- Sass：<https://sass-lang.com/>\n\nSass 是成熟、稳定、强大的 CSS 扩展语言。入门文档可以看：<http://sass.bootcss.com/>\n\n- Less：<http://lesscss.org/>\n\n给 CSS 加点料。入门文档可以看：<https://less.bootcss.com/>\n\n- Stylus：<http://stylus-lang.com/>\n\n## 构建\n\n- NPM：<https://www.npmjs.com/>\n\n- Yarn：<https://yarnpkg.com/zh-Hans/>\n\n- Webpack：<https://webpack.js.org/>\n\n- Gulp：<https://www.gulpjs.com.cn/>\n\n- Babel：<https://babeljs.io/>\n\n- ESLint：<https://cn.eslint.org/>\n\n可组装的JavaScript和JSX检查工具。\n\n- PostCSS：<https://www.postcss.com.cn/>\n\n用 JavaScript 工具和插件转换 CSS 代码的工具\n\n## 调试抓包\n\n- whistle：<https://wproxy.org/whistle/>\n\n代理抓包工具，很好很强大。\n\n- Fiddler：<https://www.telerik.com/fiddler>\n\n代理抓包工具。\n\n## Mock数据\n\n- Easy Mock：<https://www.easy-mock.com>\n\n## 编辑器 && IDE\n\n- VS Code：<https://code.visualstudio.com/>\n\n- Sublime Text：<https://www.sublimetext.com/>\n\n- WebStorm：<https://www.jetbrains.com/webstorm/>\n\n- Atom：<https://atom.io/>\n\n## 编码规范\n\n- Bootstrap编码规范：<https://codeguide.bootcss.com/>\n\n- es6编程风格：<http://es6.ruanyifeng.com/#docs/style>\n\n- Airbnb Javascript Style Guide：<https://github.com/airbnb/javascript>\n\n## 静态站点搭建工具\n\n- Hexo：<https://hexo.io/zh-cn/>\n\n- VuePress：<https://www.vuepress.cn/>\n\n- GitBook：<https://www.gitbook.com/>\n\n\n## 设计工具\n\n- **墨刀**：原型设计工具。网址：<https://modao.cc/>\n\n- **蓝湖**：一款产品文档和设计图的在线协作平台。网址：<https://lanhuapp.com>\n\n- **PxCook（像素大厨）**：高效易用的自动标注工具。软件下载链接：<https://www.fancynode.com.cn/pxcook>\n\n\n## 图标\n\n- Font Awesome：<http://www.fontawesome.com.cn/>\n\n- Iconfont：<https://www.iconfont.cn/>\n\n- icomoon：<https://icomoon.io/>\n\n- EasyIcon：<https://www.easyicon.net/>\n\n- icons8：<https://icons8.cn/>\n\n- IconStore：<https://iconstore.co/>\n\n- iconninja：<http://www.iconninja.com/>\n\n## 设计素材\n\n- 设优 SHEUI：<http://www.sheui.com/Contact/>\n\n\n## 工具\n\n\n### 综合类\n\n- CanIUse：<https://caniuse.com/>\n\n浏览器兼容性查询。前端同学必须要知道。\n\n- 国家企业信用信息公示系统：<http://www.gsxt.gov.cn>\n\n通过这个网站，我们可以查到任何一家公司的基本信息（成立时间、法定代表人等）。如果你在这个网站上没有找到某公司的信息，放心吧，这个公司一定是个骗子。\n\n- ProcessOn：<https://www.processon.com/>\n\n在线制作流程图。推荐。\n\n- **幕布**：<https://mubu.com>\n\n极简大纲笔记、一键生成思维导图。非常好用。\n\n- JSON格式化：<http://www.bejson.com/>\n\n- 草料二维码：<https://cli.im/>\n\n- 短链生成：<http://www.sina.lt>\n\n- GitHub短网址：<https://git.io/>\n\n- **图片压缩**：<https://tinypng.com/>\n\n熊猫压缩。压缩后图片清晰度不会有太大变化。\n\n- **图片压缩**：<https://www.yasuotu.com/>\n\n- 在线PS：<https://www.photopea.com/>\n\n- 图片在线裁剪：<https://www.asqql.com/gifc/>\n\n- 多数据源IP地址查询：<https://haoip.cn/>\n\n- Gif添加字幕：<http://www.yingjingtu.com/>\n\n- Photoshop的投影参数转换为 CSS代码：<https://psd2css.mezw.com/>\n\n将Photoshop设计文件图层中的混合选项参数快速转换为CSS3代码，以节省前端开发人员的时间和精力。\n\n- Get Emoji：<https://emoji.svend.cc/>\n\n- 图片转Ascii：<http://picascii.com/>\n\n- 视频转GIF：<https://github.com/vvo/gifify>\n\n- OCR文字识别：<https://app.xunjiepdf.com/ocr>\n\n### 图片类\n\n- 图片转base64：<http://imgbase64.duoshitong.com/>\n\n## 前端周刊\n\n- WecTeam 前端周刊：<https://github.com/wecteam/weekly>\n\n- Js中文网周刊：<https://www.javascriptc.com/category/javascript-weekly>\n\n- 政采云 前端周刊：<https://weekly.zoo.team/>\n\n## 团队\n\n\n- 腾讯AlloyTeam：<http://www.alloyteam.com/>\n\n- 腾讯社交用户体验ISUX：<https://isux.tencent.com/>\n\n- 淘宝FED | 淘宝前端团队：<http://taobaofed.org/>\n\n- 阿里巴巴国际UED：<http://www.aliued.com/>\n\n- 京东 | 凹凸实验室：<https://aotu.io/>\n\n- 饿了么前端:<https://zhuanlan.zhihu.com/ElemeFE>\n\n- 百度前端研发部FEX：<http://fex.baidu.com/>\n\n- 360 | 奇舞团：<https://75team.com/>\n\n- 知道创宇FED：<https://knownsec-fed.com/>\n\n\n\n## 总结\n\n如果你有发现新的内容，欢迎在 GitHub 上提交 [issues](https://github.com/qianguyihao/web/issues)。\n\n\n\n"
  },
  {
    "path": "17-资源推荐/03-前端学习资源推荐.md",
    "content": "\n\n\n## 前端教程\n\n### 千古前端图文教程\n\n- 官网：<https://web.qianguyihao.com/>\n\n- GitHub：<https://github.com/qianguyihao/web>\n\n千古前端图文教程，超详细的前端入门到进阶学习笔记。从零开始学前端，做一名精致优雅的前端工程师。\n\n\n### 现代 JavaScript 教程\n\n- 官网：<https://zh.javascript.info/>\n- GitHub：<https://github.com/javascript-tutorial/zh.javascript.info>\n\n以最新的 JavaScript 标准为基准。通过简单但足够详细的内容，为你讲解从基础到高阶的 JavaScript 相关知识。\n\n## CSS\n\n- CSS Inspiration，在这里找到写 CSS 的灵感：<https://github.com/chokcoco/CSS-Inspiration>\n\n- CSS 常用样式：<https://github.com/QiShaoXuan/css_tricks>\n\n- css的各种效果实现：<https://lhammer.cn/You-need-to-know-css/#/>\n\ncss的各种效果实现（尤其是动画效果），关键时刻能救命。作者内化后输出，并贡献出来。这种也是颇为难得了。很有极客精神。\n\n比如，我在这里面找到了「抖动效果」，很有帮助。\n\n## JavaScript\n\n- 优秀的JS代码规范：<https://github.com/ryanmcdermott/clean-code-javascript>\n\n- 据说这个项目，是宝藏：<https://github.com/dexteryy/spellbook-of-modern-webdev>\n\n## TS\n\n- TypeScript 教程：<https://github.com/xcatliu/typescript-tutorial>\n\n## Node.js\n\nNode.js学习：<https://blog.poetries.top/node-learning-notes/>\n\n\n## 算法类\n\n- 数据结构和算法：<https://github.com/trekhleb/javascript-algorithms>\n\n- leetcode解题之路：<https://github.com/azl397985856/leetcode>\n\n- 五分钟学算法：<https://github.com/MisterBooo/LeetCodeAnimation>\n\n- LeetCode 攻略 - 2019 年 8 月上半月汇总（109 题攻略）：<https://juejin.im/post/5d522f7cf265da03c926ede5>\n\n- 极客时间 App 的《数据结构与算法之美》\n\n## Vue 教程\n\n- 你也许不知道的 Vuejs：<https://github.com/yugasun/You-May-Not-Know-Vuejs>\n\n\n## 综合类\n\n- [前端精读周刊](https://github.com/dt-fe/weekly)\n\n\n## 其他\n\n- 单元测试：<https://github.com/goldbergyoni/javascript-testing-best-practices>\n\n## 前端面试\n\n- 前端面试图谱：<https://yuchengkai.cn/docs/zh/>\n\n这个项目是从面试的角度出发的。我自己的项目是，是从基础入门的角度出发的。可以起到互补的作用。\n\n- 前端面试常考问题整理，按模块知识点分类：<https://blog.poetries.top/FE-Interview-Questions/>\n\n- 前端开发面试题: <https://github.com/markyun/My-blog/tree/master/Front-end-Developer-Questions>\n\n- web前端面试宝典：<https://github.com/h5bp/Front-end-Developer-Interview-Questions/>\n\n\n- 掘金前端面试题合集：<https://github.com/shfshanyue/blog/blob/master/post/juejin-interview.md>\n\n## 综合面试\n\n- 反向面试（反问面试官的问题）：<https://github.com/yifeikong/reverse-interview-zh>\n\n\n## 博客\n\n- 收集优质的中文前端博客：<https://github.com/FrankFang/best-chinese-front-end-blogs>\n\n- 前端博客：<https://github.com/laizimo/zimo-article>\n\n讲得比较详细。比如说这篇：[CSS布局说——可能是最全的](https://github.com/laizimo/zimo-article/issues/36)\n\n## 文档类\n\n- 中文博客排版指南：<https://github.com/qianguyihao/document-guide>\n\n- 中国程序员容易发音错误的单词：<https://github.com/shimohq/chinese-programmer-wrong-pronunciation>\n\n## 学会提问\n\n- <https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way>\n\n\n\n\n\n\n\n"
  },
  {
    "path": "17-资源推荐/04-前端大佬名单.md",
    "content": "## 前端大佬\n\n### 尤雨溪\n\n- GitHub：<https://github.com/yyx990803>\n\n- 博客：<http://blog.evanyou.me/>\n\n- 知乎：<https://www.zhihu.com/people/evanyou>\n\n\n\n### 阮一峰\n\n- GitHub：<https://github.com/ruanyf>\n\n- 博客：<http://www.ruanyifeng.com/blog/>\n\n\n\n### 玉伯\n\n- GitHub：<https://github.com/lifesinger>\n\n- 博客：<https://github.com/lifesinger/blog>\n\n- 知乎：<https://www.zhihu.com/people/lifesinger>\n\n### 司徒正美\n\n- GitHub：<https://github.com/RubyLouvre>\n\n- 博客：<http://www.cnblogs.com/rubylouvre/>\n\n- 知乎：<https://www.zhihu.com/people/si-tu-zheng-mei>\n\n### 张鑫旭（腾讯-阅文集团）\n\n- GitHub：<https://github.com/zhangxinxu>\n\n- 博客：<https://www.zhangxinxu.com/>\n\n- 知乎：<https://www.zhihu.com/people/iamzhangxinxu>\n\n### Anthony Fu\n\n Vue 和 Vite 团队的核心成员，目前在 NuxtLab 工作。\n\n 主页：\n\n- GitHub：<https://github.com/antfu>\n\n- 博客：<https://antfu.me/>\n\n- 即刻：<https://web.okjike.com/u/9B794103-2B15-4359-B721-5847C4DB5579>\n\n相关链接：\n\n- [跟 Anthony Fu 聊聊全职开源和他的故事](https://bytetalk.fm/posts/episode-6/)\n\n### 迷渡\n\n- GitHub：<https://github.com/justjavac>\n\n- 知乎：<https://www.zhihu.com/people/justjavac.com>\n\n### 羡辙 | Ovilia\n\n- GitHub：<https://github.com/Ovilia>\n\n- 知乎：<https://www.zhihu.com/people/ovilia>\n\n### 云谦（陈成）\n\n- GitHub：<https://github.com/sorrycc>\n\n- 博客：<https://sorrycc.com/>\n\n云谦装了啥：<https://github.com/sorrycc/awesome-tools>\n\n### 偏右\n\n- GitHub：<https://github.com/afc163>\n\n- 知乎：<https://www.zhihu.com/people/afc163>\n\n### 黄峰达/Phodal Huang（ThoughtWorks）\n\n- GitHub：<https://github.com/phodal>\n\n- 博客：<https://www.phodal.com/>\n\n- 知乎：<https://www.zhihu.com/people/phodal>\n\n### 贺师俊/Hax（百姓网）\n\n- GitHub：<https://github.com/hax>\n\n- 博客：<http://johnhax.net/>\n\n- 知乎：<https://www.zhihu.com/people/he-shi-jun>\n\n### 大漠\n\n**链接**：\n\n- GitHub：<https://github.com/airen>\n\n大漠的GitHub上没啥东西。\n\n- 博客：<https://www.w3cplus.com>\n\n- 知乎：<https://www.zhihu.com/people/w3cplus>\n\n**介绍**：\n\n常用昵称“大漠”，W3CPlus创始人，目前就职于淘宝。对HTML5、CSS3和CSS处理器等前端脚本语言有非常深入的认识和丰富的实践经验，尤其专注对CSS3和动画的研究，是国内最早研究和使用CSS3和CSS处理器技术的一批人。现在主要在探讨学习JavaScript、React和Vue相关技术知识。CSS、CSS处理器和Web动画中国布道者。2014年出版《图解CSS3：核心技术与案例实战》。\n\n### EGOIST\n\n- 博客：<https://egoist.sh/>\n\n- GitHub：<https://github.com/egoist>\n\n### 冴羽\n\n**链接**：\n\n- GitHub：<https://github.com/mqyqingfeng>\n\n- 博客：<https://github.com/mqyqingfeng/Blog>\n\n- 知乎：<https://www.zhihu.com/people/qing-feng-yi-yang>\n\n### 李靖/小胡子哥（淘宝网）\n\n- GitHub：<https://github.com/barretlee>\n\n- 博客：<https://www.barretlee.com/>\n\n- 知乎：<https://www.zhihu.com/people/barretlee/>\n\n### cangdu\n\n- GitHub：<https://github.com/bailicangdu>\n\n### Jackson Tian\n\n- GitHub：<https://github.com/JacksonTian>\n\n- 博客：<http://jacksontian.org/>\n\n### 题叶（饿了么、前 Teambition）\n\n- GitHub：<https://github.com/jiyinyiyong>\n\n- 博客：<http://tiye.me/>\n\n### 杨健（今日头条）\n\n- GitHub：<https://github.com/hardfist>\n\n- 知乎：<https://www.zhihu.com/people/hardfist>\n\n### 流形\n\n> （阿里巴巴数据技术与产品部前端团队负责人）\n\n- 知乎：<https://www.zhihu.com/people/arcthur/>\n\n## GitHub 优秀作者\n\n### 晴小篆\n\n- GitHub：<https://github.com/yanlele>\n\n### MuYunyun\n\n- GitHub：<https://github.com/MuYunyun>\n"
  },
  {
    "path": "17-资源推荐/05-前端GitHub项目整理.md",
    "content": "\n## 面试题\n\n- 前端赏金猎人：<https://github.com/BetaSu/fe-hunter>\n\n收集前端高质量问题 + 前端高质量答案。\n\n\n\n## 文章\n\n- 每日时报：<https://github.com/wubaiqing/zaobao>\n\n每日时报，以前端技术体系为主要分享课题。根据：文章、工具、新闻、视频几大板块作为主要分类。\n"
  },
  {
    "path": "17-资源推荐/06-前端文章推荐.md",
    "content": "\n\n### 2019-04-26\n\n- [一名【合格】前端工程师的自检清单](https://juejin.im/post/5cc1da82f265da036023b628)\n\n\n### 2020-05-14\n\n- [React中setState的怪异行为 ——setState没有即时生效](https://blog.csdn.net/handsomexiaominge/article/details/86348235)\n\nReact 中的 setState 是异步操作。\n\n\n### 2020-03-27\n\n- [「颜值即正义」那些管UI小姐姐要来的网站](https://juejin.im/post/5e7cdee26fb9a03c6e640cc7)\n\n### 2020-04-09\n\n- [使用 ImgBot 自动为 Github 项目优化图片](https://www.cnblogs.com/lfri/p/12257277.html)\n\n\n### 2020-03-31\n\n- [基于Vue2.0 + elementUI 后台管理平台](https://www.jianshu.com/p/deeddeabdef8)\n\n这个elementUI的demo比较完整。项目地址在 [GitHub](https://github.com/xiahuahua/vue-admin-demo)上。\n\n\n"
  },
  {
    "path": "17-资源推荐/2018-推荐文章.md",
    "content": "\n\n积累平时看到的一些好的前端文章。\n\n\n> 记录平时遇到的优质技术文章，按时间先后排序。\n\n\n### 2017-01-20\n\n- [阿里9年，我总结的前端架构演进3大阶段及团队管理心法](http://www.infoq.com/cn/news/2017/01/Ali-9-3)\n\n伟明的推荐，说是对前端开发的价值观形成有良好的影响。\n\n\n### 2017-07-13\n\n- [前端开发面试题](https://github.com/markyun/My-blog/tree/master/Front-end-Developer-Questions)\n\n在逛公众号「前端大全」的时候发现的，然后顺着找到了对应的GitHub链接。面试题有答案哦。\n\n\n### 2017-07-31\n\n- [你可能不需要 Vuex](https://github.com/chenbin92/blog/issues/1)\n\n这篇文章里的流程图画得挺好看的，gif图也很小，只有200多kb。我发邮件问作者用的什么软件，很幸运的是，得到了作者的回复。答案是：\n\n- 录制 GIF：[licecap](https://github.com/justinfrankel/licecap)\n\n- 画图：[processon免费在线作图](https://www.processon.com/)\n\n\n### 2017-08-01\n\n- [资深Web技术专家曹刘阳：2016年前端技术观察](http://geek.csdn.net/news/detail/128912)\n\n有很多争议，[知乎](https://www.zhihu.com/question/53625757)上都有人评论了。\n\n\n### 2017-08-16\n\n- [道阻且长啊TAT(前端面试总结)](https://segmentfault.com/a/1190000010631325?_ea=2359607)\n\n作者毕业于华科，面的是前端的实习，还拿了不少offer：百度外卖，头条，美团，狗东，其他，最终在美团实习。我最初是在[微信公众号](https://mp.weixin.qq.com/s/_clHr5a6_JzTos9bKpvKrA)上看到的文章。\n\n\nhello。我看你拿了不少实习offer。有没有推荐的前端学习资料呀？比如说：网站、链接、书籍等。可否分享给我？如果内容较多，欢迎发表成博客。谢啦～～～\n\n\n### 2017-08-17\n\n- [面试分享：一年经验初探阿里巴巴前端社招](https://github.com/jawil/blog/issues/22)\n\n在公众号「web前端课程」上看到的文章。作者经历了4面，也是厉害了。另外，作者在gihub上的[博客](https://github.com/jawil/blog)也是出彩的。比如说下面这篇文章就值得推荐：\n\n- [挖 \"掘\" 淘 \"金\" 之 前端闯关历险记学习资料汇总](https://github.com/jawil/blog/issues/4)\n\n作者搜集了几百篇在掘金上看到的好文章，让人惊叹。\n\n\n### 2017-09-15\n\n- [你所不知道的 CSS 滤镜技巧与细节](http://www.cnblogs.com/coco1s/p/7519460.html)\n\n\n### 2017-09-19\n\n- [适合程序员的写作技法](http://www.cnblogs.com/mindwind/p/7536748.html)\n\n写作过程包括下面的部分：\n\n- 需求\n- 设计\n- 实现\n- 测试\n- 交付\n\n\n### 2018-01-09\n\n- [翻译 | 像 JavaScript 一样思考](http://www.ituring.com.cn/article/497284)\n\n在MacTalk的小密圈里看到有人分享。\n\n- [大前端公共知识杂谈](https://time.geekbang.org/column/article/241)\n\n- [小白谈数据脱敏](http://www.54tianzhisheng.cn/2017/10/28/Data-Desensitization/)\n\n\n### 2018-01-10\n\n- [MAC全栈开发环境搭建指南](https://mac.aotu.io/)\n\n今天发现这个网址，感觉还不错。在里面发现了一个比较好的Sublime Text主题。\n\n- [介绍几个上网+分流+图床工具](http://www.viyuedu.com/kaopuseo/61071.html)\n\n### 2018-01-12\n\n- [张鑫旭 | 话说我为什么要闭关学习](http://www.zhangxinxu.com/life/2013/03/%E6%88%91%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E9%97%AD%E5%85%B3%E5%AD%A6%E4%B9%A0/)\n\n作者闭关学习了半年的前端。\n\n\n### 2018-01-17\n\n- [你的简历能帮你争取到面试机会吗](http://www.cnblogs.com/JavaArchitect/archive/2018/01/09/8249594.html)\n\n- [漫话JavaScript与异步·第三话——Generator：化异步为同步](http://www.cnblogs.com/leegent/archive/2018/01/10/8207246.html)\n\n\n### 2017-01-19\n\n- [一个三年工作经验的软件工程师的经验之谈](http://www.cnblogs.com/lovesong/p/5721828.html)\n\n### 2018-01-22\n\n- [前端程序员容易忽视的一些基础知识](https://www.cnblogs.com/fsyz/p/8327451.html)\n\n\n### 2018-01-23\n\n- [2018 前端趋势：更一致，更简单](https://mp.weixin.qq.com/s/HdNQv6eRchBXpNUVRuLZpQ)\n\n\n### 2018-01-24\n\n推荐一个网站，名叫：web骇客。网址：<http://www.webhek.com/>\n\n\n比如：\n\n\n- 电影里敲代码的样子：<http://www.webhek.com/post/hackertyper.html>\n\n- 测试眼睛对颜色的敏感程度：<http://www.webhek.com/post/color-test.html>\n\n\n\n### 2018-01-25\n\n- [面试分享：一年经验初探阿里巴巴前端社招](https://www.cnblogs.com/fsyz/p/8298921.html)\n\n一般阿里社招都是招3-5年的P6+高级工程师。面试官明知道作者只有一年工作经验，在面了这么多轮之后却来一句：“我们只要高工”。这是不是太欺负人了？\n\n\n### 2018-01-28\n\n- [WEB前端工作五年了，我来告诉你如何系统的学习现在的JAVASCRIPT](http://www.cnblogs.com/gongyue/p/8073235.html)\n\n\n### 2018-01-29\n\n- [最棒的 JavaScript 学习指南（2018版）](https://www.cnblogs.com/lhb25/p/8361799.html)\n\n\n- [我们为什么选择Vue.js而不是React](http://www.infoq.com/cn/news/2016/12/why-Vue-js-no-react)\n\n\n- [【长文慎入】通信十年—通信行业分析与跳槽之路](【长文慎入】通信十年—通信行业分析与跳槽之路)\n\n\n- [我在深圳南山写代码：是在改变世界还是养家糊口](https://news.cnblogs.com/n/588481/)\n\n\n### 2018-01-31\n\n- [我所理解的前端](https://www.cnblogs.com/Smiled/p/8377188.html)\n\n\n### 2018-02-02\n\n- [开发人员的奋斗目标](https://www.cnblogs.com/1si2/p/devroad.html)\n\n\n### 2018-03-21\n\n- [十倍效能提升——Web 基础研发体系的建立](https://www.cnblogs.com/sskyy/p/8613393.html)\n\n\n\n\n\n### 2018-01-29\n\n长期写博客的人，最少能证明他是一个善于思考和总结的人。\n\n这句话的来源：[我依然坚持建议你开始写博客 | 写给我的 2017](https://www.cnblogs.com/plokmju/p/8108846.html)\n\n### 2018-03-21\n\n- [不谈面试题，谈谈招聘时我喜欢见到的特质](https://www.cnblogs.com/dino623/p/8583514.html)\n\n### 2018-04-16\n\n- [Jerry和您聊聊Chrome开发者工具](https://mp.weixin.qq.com/s/CPnbx8ZfszPEcI3Y8RittA)\n\n\n### 2018-05-01\n\n- [What makes a good front end engineer](https://www.nczonline.net/blog/2007/08/15/what-makes-a-good-front-end-engineer/)\n\n\n### 2018-05-31\n\n- [一个bit一个bit的进行 Base64 白话科普，看不懂算你输](https://mp.weixin.qq.com/s/TcJUQbqjBditRvRIHuXX-Q)\n\n\n### 2018-06-23\n\n- [技术的热门度曲线](http://www.ruanyifeng.com/blog/2017/03/gartner-hype-cycle.html)\n\n\n### 2018-07-03\n\n- [浅谈XXE攻击](http://www.freebuf.com/articles/web/126788.html)\n\n关于XXE攻击，这几天微信支付被爆出漏洞，使用微信支付的各个业务都在努力修补。\n\n\n### 2018-12-01\n\n\n- [零基础转行前端，一年工作经验，我如何入职蚂蚁金服](https://juejin.im/post/5c011c92f265da614e2bd0c2)\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "17-资源推荐/2019-推荐文章.md",
    "content": "\n\n## 前言\n\n记录平时遇到的优质技术文章，按时间先后排序。\n\n## 内容\n\n### 2019-05-11\n\n- 《[博客园美化教程大集合----极致个性化你的专属博客（超详细，看这篇就够了）](https://www.cnblogs.com/shwee/p/9060226.html#dingzhi12)》\n\n网上写图文教程的人，还真是贴心。\n\n\n### 2019-09-06\n\n- [https://www.yuque.com/sxc/front/kvokg4](https://www.yuque.com/sxc/front/kvokg4)\n\n作者在语雀上的系列文章，都值得一看。\n\n### 2019-10-05\n\n- [前端学习网站和资源推荐](https://www.twblogs.net/a/5d400329bd9eee51fbf962b2)\n\n作者整理的一些基础性的学习资源，还可以。\n\n"
  },
  {
    "path": "17-资源推荐/2020-推荐文章.md",
    "content": ""
  },
  {
    "path": "17-资源推荐/2022-推荐文章.md",
    "content": ""
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2019 千古壹号\n\n# Attribution-NonCommercial-ShareAlike 4.0 International\n\nCreative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.\n\n### Using Creative Commons Public Licenses\n\nCreative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.\n\n* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).\n\n* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).\n\n## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License\n\nBy exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License (\"Public License\"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.\n\n### Section 1 – Definitions.\n\na. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.\n\nb. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.\n\nc. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License.\n\nd. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.\n\ne. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.\n\nf. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.\n\ng. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike.\n\nh. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.\n\ni. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.\n\nj. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.\n\nk. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.\n\nl. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.\n\nm. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.\n\nn. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.\n\n### Section 2 – Scope.\n\na. ___License grant.___\n\n   1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:\n\n        A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and\n\n        B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only.\n\n   2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.\n\n   3. __Term.__ The term of this Public License is specified in Section 6(a).\n\n   4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.\n\n   5. __Downstream recipients.__\n\n        A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.\n\n        B. __Additional offer from the Licensor – Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.\n\n        C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.\n\n   6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).\n\nb. ___Other rights.___\n\n   1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.\n\n   2. Patent and trademark rights are not licensed under this Public License.\n\n   3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.\n\n### Section 3 – License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the following conditions.\n\na. ___Attribution.___\n\n   1. If You Share the Licensed Material (including in modified form), You must:\n\n       A. retain the following if it is supplied by the Licensor with the Licensed Material:\n\n         i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);\n\n         ii. a copyright notice;\n\n         iii. a notice that refers to this Public License;\n\n         iv. a notice that refers to the disclaimer of warranties;\n\n         v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;\n\n       B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and\n\n       C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.\n\n   2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.\n\n   3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.\n\nb. ___ShareAlike.___\n\nIn addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.\n\n1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License.\n\n2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.\n\n3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.\n\n### Section 4 – Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:\n\na. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;\n\nb. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and\n\nc. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.\n\nFor the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.\n\n### Section 5 – Disclaimer of Warranties and Limitation of Liability.\n\na. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__\n\nb. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__\n\nc. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.\n\n### Section 6 – Term and Termination.\n\na. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.\n\nb. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:\n\n   1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or\n\n   2. upon express reinstatement by the Licensor.\n\n   For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.\n\nc. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.\n\nd. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.\n\n### Section 7 – Other Terms and Conditions.\n\na. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.\n\nb. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.\n\n### Section 8 – Interpretation.\n\na. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.\n\nb. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.\n\nc. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.\n\nd. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.\n\n> Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.\n>\n> Creative Commons may be contacted at creativecommons.org\n\n"
  },
  {
    "path": "README.md",
    "content": "\n## 前言\n\n千古前端图文教程，超详细的 Web 前端入门到进阶知识库。从零开始学前端，做一名精致优雅的前端工程师。持续更新中...\n\n通俗易懂，不懂技术也能学。此前端教程不玩猫腻，不会设置任何套路，因为我**相信启蒙的重要性**。\n\n\n## 项目介绍\n\n### 项目地址\n\n- 官网地址：<https://web.qianguyihao.com>\n\n如果官网打开异常，请先尝试强制刷新页面，或者清除浏览器缓存，或者提交 GitHub issues 反馈问题。\n\n- GitHub地址：<https://github.com/qianguyihao/Web>\n\n如果你发现本项目有内容上的错误，欢迎在 GitHub 提交 issues 或者 pull requests 进行指正，方便归档。\n\n### 项目作用\n\n- 网上的大部分入门教程，都不太适合初学者，本项目争取照顾到每一位前端入门者的同理心。即使你完全不懂前端，甚至不懂编程，通过这个教程，也能让小白入门。\n\n- 帮助前端同学提供一个精品学习资源和路线，提高学习效率，少走很多弯路。\n\n- 可以当做前端字典，随时翻阅，查漏补缺。\n\n### 相关链接\n\n- 维护这个项目的初衷，可以看这篇文章：[《裸辞两个月，海投一个月，从Android转战Web前端的求职之路》](https://www.cnblogs.com/qianguyihao/p/8732781.html)\n\n- 前端入门路线和推荐学习资源，可以看这篇文章：[《2025年Web前端开发流程和学习路线（详尽版）》](https://www.cnblogs.com/qianguyihao/p/16370961.html)\n\n\n## 学习交流\n\n在公众号「千古壹号」里回复“**前端学习**”，拉你进微信交流群：\n\n- 进群暗号：前端学习\n\n- 进群要求：少提问、少闲聊、多分享（不适合长期潜水）。\n\n![](https://img.smyhvae.com/20210329_1930.png)\n\n\n## LICENSE\n\n![](http://img.smyhvae.com/20210331_CC-BY-NC-SA.png)\n\n本作品采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。\n"
  }
]