<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>ash66</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://minniexcode.github.io/memoirs/</id>
  <link href="https://minniexcode.github.io/memoirs/" rel="alternate"/>
  <link href="https://minniexcode.github.io/memoirs/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, ash66</rights>
  <subtitle>我只是一个影子，虽然我发着光。。。</subtitle>
  <title>ash</title>
  <updated>2026-04-01T16:08:03.641Z</updated>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Tooling" scheme="https://minniexcode.github.io/memoirs/tags/Tooling/"/>
    <category term="Workflow" scheme="https://minniexcode.github.io/memoirs/tags/Workflow/"/>
    <category term="Git" scheme="https://minniexcode.github.io/memoirs/tags/Git/"/>
    <category term="CLI" scheme="https://minniexcode.github.io/memoirs/tags/CLI/"/>
    <content>
      <![CDATA[<p><code>Git</code> 是目前最主流的分布式版本控制系统。很多人会用 <code>git add</code>、<code>git commit</code>、<code>git push</code>，但一旦进入分支协作、历史整理、冲突处理、误操作恢复，就容易开始混乱。</p><p>这篇文章按“从日常到深入”的顺序，把 <code>Git CLI</code> 的核心命令系统化整理出来：先讲最常用的工作流，再讲协作、历史、恢复、排错和高级功能，最后给出一份尽量完整的命令索引。</p><p>如果你只想先建立一套稳定工作流，可以重点看这些命令：<code>git init</code>、<code>git clone</code>、<code>git status</code>、<code>git add</code>、<code>git commit</code>、<code>git log</code>、<code>git diff</code>、<code>git branch</code>、<code>git switch</code>、<code>git restore</code>、<code>git fetch</code>、<code>git pull</code>、<code>git push</code>、<code>git merge</code>、<code>git rebase</code>、<code>git stash</code>、<code>git tag</code>、<code>git reset</code>、<code>git revert</code>、<code>git reflog</code>。</p><span id="more"></span><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><ol><li>Git 到底在管理什么</li></ol></li><li><ol start="2"><li>Git 配置与帮助系统</li></ol></li><li><ol start="3"><li>新建仓库与获取仓库</li></ol></li><li><ol start="4"><li>Git 的四个核心区域</li></ol></li><li><ol start="5"><li>日常高频命令</li></ol></li><li><ol start="6"><li>分支与切换</li></ol></li><li><ol start="7"><li>查看历史与定位问题</li></ol></li><li><ol start="8"><li>远程仓库协作</li></ol></li><li><ol start="9"><li>合并、变基与历史整理</li></ol></li><li><ol start="10"><li>撤销、回退与恢复</li></ol></li><li><ol start="11"><li>标签、暂存、清理与归档</li></ol></li><li><ol start="12"><li>高级协作与维护命令</li></ol></li><li><ol start="13"><li>常见场景速查</li></ol></li><li><ol start="14"><li>Git 命令全景索引</li></ol></li><li><ol start="15"><li>学习建议</li></ol></li></ul><h2 id="1-Git-到底在管理什么"><a href="#1-Git-到底在管理什么" class="headerlink" title="1. Git 到底在管理什么"></a>1. Git 到底在管理什么</h2><p>先别急着背命令。真正理解 <code>Git</code>，关键是先理解它在管理什么。</p><p><code>Git</code> 管理的不是“文件夹快照”这么简单，而是：</p><ul><li>工作区：你当前正在编辑的文件。</li><li>暂存区：下一次提交准备带走的内容。</li><li>本地仓库：已经提交到当前仓库历史中的内容。</li><li>远程仓库：例如 <code>GitHub</code>、<code>GitLab</code>、<code>Gitea</code> 上的仓库副本。</li></ul><p><code>Git</code> 的大部分命令，本质上都是在这几个区域之间移动内容，或者比较它们之间的差异。</p><p>你可以把它简单理解成：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">工作区 --git add--&gt; 暂存区 --git commit--&gt; 本地仓库 --git push--&gt; 远程仓库</span><br></pre></td></tr></table></figure><p>反过来也成立：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">远程仓库 --git fetch/pull--&gt; 本地仓库/当前分支</span><br><span class="line">本地仓库 --git restore/reset--&gt; 暂存区/工作区</span><br></pre></td></tr></table></figure><p>如果这个模型不清楚，后面很多命令都会越用越乱。</p><h2 id="2-Git-配置与帮助系统"><a href="#2-Git-配置与帮助系统" class="headerlink" title="2. Git 配置与帮助系统"></a>2. Git 配置与帮助系统</h2><h3 id="2-1-查看版本"><a href="#2-1-查看版本" class="headerlink" title="2.1 查看版本"></a>2.1 查看版本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git --version</span><br></pre></td></tr></table></figure><p>确认当前机器安装的 <code>Git</code> 版本。</p><h3 id="2-2-基础配置"><a href="#2-2-基础配置" class="headerlink" title="2.2 基础配置"></a>2.2 基础配置</h3><p>第一次使用 Git，通常先配用户名和邮箱：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --global user.name <span class="string">&quot;Your Name&quot;</span></span><br><span class="line">git config --global user.email <span class="string">&quot;you@example.com&quot;</span></span><br></pre></td></tr></table></figure><p>查看当前生效配置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git config --list</span><br><span class="line">git config --global --list</span><br><span class="line">git config --<span class="built_in">local</span> --list</span><br></pre></td></tr></table></figure><p>常见配置项：</p><ul><li><code>user.name</code>：提交记录显示的用户名。</li><li><code>user.email</code>：提交记录显示的邮箱。</li><li><code>core.editor</code>：默认编辑器。</li><li><code>init.defaultBranch</code>：默认初始分支名，例如 <code>main</code>。</li><li><code>pull.rebase</code>：<code>git pull</code> 时默认走 <code>rebase</code> 还是 <code>merge</code>。</li><li><code>merge.tool</code>：冲突时使用的合并工具。</li><li><code>alias.xxx</code>：给常用命令设置别名。</li></ul><p>例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git config --global init.defaultBranch main</span><br><span class="line">git config --global core.editor <span class="string">&quot;vim&quot;</span></span><br><span class="line">git config --global alias.st status</span><br><span class="line">git config --global alias.co checkout</span><br><span class="line">git config --global alias.br branch</span><br><span class="line">git config --global alias.lg <span class="string">&quot;log --oneline --graph --decorate --all&quot;</span></span><br></pre></td></tr></table></figure><h3 id="2-3-查看某个配置值"><a href="#2-3-查看某个配置值" class="headerlink" title="2.3 查看某个配置值"></a>2.3 查看某个配置值</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config user.name</span><br><span class="line">git config --global core.editor</span><br></pre></td></tr></table></figure><h3 id="2-4-删除配置"><a href="#2-4-删除配置" class="headerlink" title="2.4 删除配置"></a>2.4 删除配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --global --<span class="built_in">unset</span> alias.co</span><br><span class="line">git config --<span class="built_in">unset</span> user.name</span><br></pre></td></tr></table></figure><h3 id="2-5-Git-帮助系统"><a href="#2-5-Git-帮助系统" class="headerlink" title="2.5 Git 帮助系统"></a>2.5 Git 帮助系统</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">help</span></span><br><span class="line">git <span class="built_in">help</span> status</span><br><span class="line">git status -h</span><br><span class="line">git <span class="built_in">help</span> config</span><br><span class="line">git <span class="built_in">help</span> -a</span><br></pre></td></tr></table></figure><p>含义：</p><ul><li><code>git help</code>：查看帮助入口。</li><li><code>git help status</code>：查看 <code>status</code> 的完整手册。</li><li><code>git status -h</code>：查看简短帮助。</li><li><code>git help -a</code>：列出所有可用子命令。</li></ul><p>如果你真的想看“全部命令”，最权威的入口不是某篇博客，而是 <code>git help -a</code> 和官方手册。</p><h2 id="3-新建仓库与获取仓库"><a href="#3-新建仓库与获取仓库" class="headerlink" title="3. 新建仓库与获取仓库"></a>3. 新建仓库与获取仓库</h2><h3 id="3-1-初始化仓库：git-init"><a href="#3-1-初始化仓库：git-init" class="headerlink" title="3.1 初始化仓库：git init"></a>3.1 初始化仓库：<code>git init</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git init</span><br><span class="line">git init my-project</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>把当前目录初始化成 Git 仓库。</li><li>或直接创建一个新目录并初始化仓库。</li></ul><p>常见场景：</p><ul><li>本地新项目开始纳入版本控制。</li><li>把已有代码目录变成仓库。</li></ul><h3 id="3-2-克隆仓库：git-clone"><a href="#3-2-克隆仓库：git-clone" class="headerlink" title="3.2 克隆仓库：git clone"></a>3.2 克隆仓库：<code>git clone</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/user/repo.git</span><br><span class="line">git <span class="built_in">clone</span> git@github.com:user/repo.git</span><br><span class="line">git <span class="built_in">clone</span> https://github.com/user/repo.git myrepo</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>从远程获取完整仓库历史。</li><li>自动创建远程别名 <code>origin</code>。</li><li>默认检出远程默认分支。</li></ul><p>常见参数：</p><ul><li><code>--depth 1</code>：浅克隆，只拉最近历史。</li><li><code>--branch &lt;name&gt;</code>：克隆后直接检出指定分支。</li><li><code>--recurse-submodules</code>：同时拉取子模块。</li></ul><p>例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> --depth 1 --branch main https://github.com/user/repo.git</span><br></pre></td></tr></table></figure><h3 id="3-3-判断当前目录是不是-Git-仓库"><a href="#3-3-判断当前目录是不是-Git-仓库" class="headerlink" title="3.3 判断当前目录是不是 Git 仓库"></a>3.3 判断当前目录是不是 Git 仓库</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rev-parse --is-inside-work-tree</span><br></pre></td></tr></table></figure><p>输出 <code>true</code> 代表当前路径在仓库内部。</p><h2 id="4-Git-的四个核心区域"><a href="#4-Git-的四个核心区域" class="headerlink" title="4. Git 的四个核心区域"></a>4. Git 的四个核心区域</h2><p>理解这四个区域后，很多命令就不难了。</p><h3 id="4-1-工作区"><a href="#4-1-工作区" class="headerlink" title="4.1 工作区"></a>4.1 工作区</h3><p>就是你当前磁盘上的真实文件。</p><h3 id="4-2-暂存区"><a href="#4-2-暂存区" class="headerlink" title="4.2 暂存区"></a>4.2 暂存区</h3><p>也叫 <code>index</code>。<code>git add</code> 并不是“提交”，而是把改动放入“下一次提交候选区”。</p><h3 id="4-3-本地仓库"><a href="#4-3-本地仓库" class="headerlink" title="4.3 本地仓库"></a>4.3 本地仓库</h3><p><code>git commit</code> 之后，内容进入本地历史。</p><h3 id="4-4-远程仓库"><a href="#4-4-远程仓库" class="headerlink" title="4.4 远程仓库"></a>4.4 远程仓库</h3><p><code>git push</code> 才会把本地提交送到远程。</p><h3 id="4-5-最容易混淆的三个对比"><a href="#4-5-最容易混淆的三个对比" class="headerlink" title="4.5 最容易混淆的三个对比"></a>4.5 最容易混淆的三个对比</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git diff              比较工作区 和 暂存区</span><br><span class="line">git diff --cached     比较暂存区 和 最近一次提交</span><br><span class="line">git diff HEAD         比较工作区 和 最近一次提交</span><br></pre></td></tr></table></figure><p>这是很多人学 Git 时最容易糊的地方。</p><h2 id="5-日常高频命令"><a href="#5-日常高频命令" class="headerlink" title="5. 日常高频命令"></a>5. 日常高频命令</h2><p>这一节是实际工作中最常用的部分。</p><h3 id="5-1-查看状态：git-status"><a href="#5-1-查看状态：git-status" class="headerlink" title="5.1 查看状态：git status"></a>5.1 查看状态：<code>git status</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br><span class="line">git status -s</span><br><span class="line">git status --short</span><br><span class="line">git status -b</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>查看当前分支。</li><li>查看哪些文件已修改。</li><li>查看哪些内容已暂存。</li><li>查看哪些文件未跟踪。</li></ul><p>常见输出含义：</p><ul><li><code>M</code>：已修改。</li><li><code>A</code>：已添加。</li><li><code>D</code>：已删除。</li><li><code>R</code>：已重命名。</li><li><code>??</code>：未跟踪文件。</li></ul><h3 id="5-2-添加到暂存区：git-add"><a href="#5-2-添加到暂存区：git-add" class="headerlink" title="5.2 添加到暂存区：git add"></a>5.2 添加到暂存区：<code>git add</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git add file.txt</span><br><span class="line">git add src/</span><br><span class="line">git add .</span><br><span class="line">git add -A</span><br><span class="line">git add -p</span><br></pre></td></tr></table></figure><p>高频用法说明：</p><ul><li><code>git add file.txt</code>：只暂存一个文件。</li><li><code>git add src/</code>：暂存一个目录。</li><li><code>git add .</code>：暂存当前目录下变更。</li><li><code>git add -A</code>：更明确地把新增、修改、删除都纳入暂存。</li><li><code>git add -p</code>：按代码块交互式暂存，适合拆分提交。</li></ul><p><code>git add -p</code> 非常实用。比如你在一个文件里同时做了“修 bug”和“顺手重构”，这时可以按块选择，只暂存你想提交的那部分。</p><h3 id="5-3-提交：git-commit"><a href="#5-3-提交：git-commit" class="headerlink" title="5.3 提交：git commit"></a>5.3 提交：<code>git commit</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git commit -m <span class="string">&quot;fix: handle empty response&quot;</span></span><br><span class="line">git commit</span><br><span class="line">git commit --amend</span><br><span class="line">git commit -a -m <span class="string">&quot;docs: update README&quot;</span></span><br></pre></td></tr></table></figure><p>高频说明：</p><ul><li><code>git commit -m</code>：直接写提交说明。</li><li><code>git commit</code>：进入编辑器写更完整说明。</li><li><code>git commit --amend</code>：修改上一次提交。</li><li><code>git commit -a -m</code>：把已跟踪文件的修改直接提交，不包含新文件。</li></ul><p>提交信息建议：</p><ul><li>写清楚“为什么改”。</li><li>一次提交只做一类事情。</li><li>不要把大量无关改动揉成一个提交。</li></ul><h3 id="5-4-查看差异：git-diff"><a href="#5-4-查看差异：git-diff" class="headerlink" title="5.4 查看差异：git diff"></a>5.4 查看差异：<code>git diff</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">git diff</span><br><span class="line">git diff --cached</span><br><span class="line">git diff HEAD</span><br><span class="line">git diff branchA..branchB</span><br><span class="line">git diff commit1 commit2</span><br><span class="line">git diff --<span class="built_in">stat</span></span><br><span class="line">git diff --name-only</span><br></pre></td></tr></table></figure><p>高频场景：</p><ul><li>提交前确认到底改了什么。</li><li>对比两个提交、两个分支的内容差异。</li><li>看哪些文件变了，不看具体行时可以配 <code>--stat</code> 或 <code>--name-only</code>。</li></ul><h3 id="5-5-查看文件内容来自哪个提交：git-blame"><a href="#5-5-查看文件内容来自哪个提交：git-blame" class="headerlink" title="5.5 查看文件内容来自哪个提交：git blame"></a>5.5 查看文件内容来自哪个提交：<code>git blame</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git blame file.txt</span><br><span class="line">git blame -L 10,30 file.txt</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>看某一行是谁在什么时候改的。</li><li>排查历史问题时非常有用。</li></ul><p>但别把 <code>blame</code> 只理解成“追责”，它更多是为了找上下文。</p><h3 id="5-6-查看对象详情：git-show"><a href="#5-6-查看对象详情：git-show" class="headerlink" title="5.6 查看对象详情：git show"></a>5.6 查看对象详情：<code>git show</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git show</span><br><span class="line">git show HEAD</span><br><span class="line">git show HEAD~1</span><br><span class="line">git show &lt;commit&gt;</span><br><span class="line">git show v1.0.0</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>查看某个提交的详细变更。</li><li>查看标签、对象、提交说明。</li></ul><h2 id="6-分支与切换"><a href="#6-分支与切换" class="headerlink" title="6. 分支与切换"></a>6. 分支与切换</h2><h3 id="6-1-查看分支：git-branch"><a href="#6-1-查看分支：git-branch" class="headerlink" title="6.1 查看分支：git branch"></a>6.1 查看分支：<code>git branch</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git branch</span><br><span class="line">git branch -a</span><br><span class="line">git branch -r</span><br><span class="line">git branch -vv</span><br></pre></td></tr></table></figure><p>说明：</p><ul><li><code>git branch</code>：本地分支。</li><li><code>git branch -a</code>：本地和远程分支。</li><li><code>git branch -r</code>：远程跟踪分支。</li><li><code>git branch -vv</code>：看本地分支跟踪关系和最近提交。</li></ul><h3 id="6-2-创建分支"><a href="#6-2-创建分支" class="headerlink" title="6.2 创建分支"></a>6.2 创建分支</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git branch feature/login</span><br><span class="line">git switch -c feature/login</span><br></pre></td></tr></table></figure><p>更推荐 <code>git switch -c</code>，语义更清晰：创建并切换。</p><h3 id="6-3-切换分支：git-switch"><a href="#6-3-切换分支：git-switch" class="headerlink" title="6.3 切换分支：git switch"></a>6.3 切换分支：<code>git switch</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git switch main</span><br><span class="line">git switch feature/login</span><br><span class="line">git switch -c hotfix/header</span><br><span class="line">git switch -</span><br></pre></td></tr></table></figure><p>说明：</p><ul><li><code>git switch -</code>：切回上一个分支。</li></ul><p>旧命令里很多人还在用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout main</span><br><span class="line">git checkout -b feature/login</span><br></pre></td></tr></table></figure><p>现在更推荐把“切分支”和“恢复文件”交给 <code>git switch</code> &#x2F; <code>git restore</code>，比 <code>checkout</code> 更不容易误用。</p><h3 id="6-4-重命名分支"><a href="#6-4-重命名分支" class="headerlink" title="6.4 重命名分支"></a>6.4 重命名分支</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -m old-name new-name</span><br></pre></td></tr></table></figure><p>当前分支重命名：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -m new-name</span><br></pre></td></tr></table></figure><h3 id="6-5-删除分支"><a href="#6-5-删除分支" class="headerlink" title="6.5 删除分支"></a>6.5 删除分支</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git branch -d feature/login</span><br><span class="line">git branch -D feature/login</span><br></pre></td></tr></table></figure><p>区别：</p><ul><li><code>-d</code>：安全删除，只删已经合并过的分支。</li><li><code>-D</code>：强制删除，没合并也删。</li></ul><h3 id="6-6-跟踪远程分支"><a href="#6-6-跟踪远程分支" class="headerlink" title="6.6 跟踪远程分支"></a>6.6 跟踪远程分支</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git switch --track origin/feature/login</span><br><span class="line">git checkout --track origin/feature/login</span><br></pre></td></tr></table></figure><h2 id="7-查看历史与定位问题"><a href="#7-查看历史与定位问题" class="headerlink" title="7. 查看历史与定位问题"></a>7. 查看历史与定位问题</h2><h3 id="7-1-历史查看：git-log"><a href="#7-1-历史查看：git-log" class="headerlink" title="7.1 历史查看：git log"></a>7.1 历史查看：<code>git log</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">log</span></span><br><span class="line">git <span class="built_in">log</span> --oneline</span><br><span class="line">git <span class="built_in">log</span> --oneline --graph --decorate --all</span><br><span class="line">git <span class="built_in">log</span> -p</span><br><span class="line">git <span class="built_in">log</span> --<span class="built_in">stat</span></span><br><span class="line">git <span class="built_in">log</span> --author=<span class="string">&quot;ash&quot;</span></span><br><span class="line">git <span class="built_in">log</span> --since=<span class="string">&quot;2026-01-01&quot;</span></span><br><span class="line">git <span class="built_in">log</span> -- file.txt</span><br></pre></td></tr></table></figure><p>高频组合：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">log</span> --oneline --graph --decorate --all</span><br></pre></td></tr></table></figure><p>这个几乎可以作为默认日志视图。</p><h3 id="7-2-精简历史：git-shortlog"><a href="#7-2-精简历史：git-shortlog" class="headerlink" title="7.2 精简历史：git shortlog"></a>7.2 精简历史：<code>git shortlog</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git shortlog</span><br><span class="line">git shortlog -sn</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>汇总提交者和提交数量。</li><li>适合快速看项目贡献概览。</li></ul><h3 id="7-3-查找历史中的某段文本：git-grep"><a href="#7-3-查找历史中的某段文本：git-grep" class="headerlink" title="7.3 查找历史中的某段文本：git grep"></a>7.3 查找历史中的某段文本：<code>git grep</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git grep <span class="string">&quot;useEffect&quot;</span></span><br><span class="line">git grep <span class="string">&quot;TODO&quot;</span></span><br><span class="line">git grep -n <span class="string">&quot;function render&quot;</span></span><br></pre></td></tr></table></figure><p>它和系统里的 <code>grep</code> 不同，它是面向 Git 仓库内容的检索工具。</p><h3 id="7-4-二分定位引入-bug-的提交：git-bisect"><a href="#7-4-二分定位引入-bug-的提交：git-bisect" class="headerlink" title="7.4 二分定位引入 bug 的提交：git bisect"></a>7.4 二分定位引入 bug 的提交：<code>git bisect</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git bisect start</span><br><span class="line">git bisect bad</span><br><span class="line">git bisect good &lt;commit&gt;</span><br><span class="line">git bisect reset</span><br></pre></td></tr></table></figure><p>工作流程：</p><ol><li>告诉 Git 当前版本是坏的。</li><li>告诉 Git 某个历史提交是好的。</li><li>Git 自动在中间挑提交让你验证。</li><li>你反复标记 <code>good</code> &#x2F; <code>bad</code>，最终定位问题提交。</li></ol><p>这个命令在“某个 bug 是什么时候引入的”这种场景里非常强。</p><h2 id="8-远程仓库协作"><a href="#8-远程仓库协作" class="headerlink" title="8. 远程仓库协作"></a>8. 远程仓库协作</h2><h3 id="8-1-查看远程：git-remote"><a href="#8-1-查看远程：git-remote" class="headerlink" title="8.1 查看远程：git remote"></a>8.1 查看远程：<code>git remote</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git remote</span><br><span class="line">git remote -v</span><br></pre></td></tr></table></figure><h3 id="8-2-添加远程"><a href="#8-2-添加远程" class="headerlink" title="8.2 添加远程"></a>8.2 添加远程</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git remote add origin https://github.com/user/repo.git</span><br><span class="line">git remote add upstream git@github.com:org/repo.git</span><br></pre></td></tr></table></figure><p>常见约定：</p><ul><li><code>origin</code>：你自己的默认远程。</li><li><code>upstream</code>：上游仓库，例如 fork 场景里的原始仓库。</li></ul><h3 id="8-3-修改远程地址"><a href="#8-3-修改远程地址" class="headerlink" title="8.3 修改远程地址"></a>8.3 修改远程地址</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote set-url origin git@github.com:user/repo.git</span><br></pre></td></tr></table></figure><h3 id="8-4-删除远程"><a href="#8-4-删除远程" class="headerlink" title="8.4 删除远程"></a>8.4 删除远程</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote remove upstream</span><br></pre></td></tr></table></figure><h3 id="8-5-获取远程更新：git-fetch"><a href="#8-5-获取远程更新：git-fetch" class="headerlink" title="8.5 获取远程更新：git fetch"></a>8.5 获取远程更新：<code>git fetch</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git fetch</span><br><span class="line">git fetch origin</span><br><span class="line">git fetch --all</span><br><span class="line">git fetch --prune</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>只拉取远程最新提交和引用。</li><li>不自动合并到当前分支。</li></ul><p><code>git fetch</code> 很安全，因为它不会直接改你的工作内容。</p><h3 id="8-6-拉取并合并：git-pull"><a href="#8-6-拉取并合并：git-pull" class="headerlink" title="8.6 拉取并合并：git pull"></a>8.6 拉取并合并：<code>git pull</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git pull</span><br><span class="line">git pull origin main</span><br><span class="line">git pull --rebase</span><br></pre></td></tr></table></figure><p>它本质上等于：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch + git merge</span><br></pre></td></tr></table></figure><p>或者使用 <code>--rebase</code> 时：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch + git rebase</span><br></pre></td></tr></table></figure><p>什么时候用 <code>pull --rebase</code>：</p><ul><li>希望本地提交接在远程最新提交后面。</li><li>想减少无意义的 merge commit。</li></ul><h3 id="8-7-推送：git-push"><a href="#8-7-推送：git-push" class="headerlink" title="8.7 推送：git push"></a>8.7 推送：<code>git push</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git push</span><br><span class="line">git push origin main</span><br><span class="line">git push -u origin feature/login</span><br><span class="line">git push --tags</span><br></pre></td></tr></table></figure><p>说明：</p><ul><li><code>-u</code>：建立上游跟踪关系，之后可以直接 <code>git push</code> &#x2F; <code>git pull</code>。</li><li><code>--tags</code>：推送标签。</li></ul><h3 id="8-8-强制推送"><a href="#8-8-强制推送" class="headerlink" title="8.8 强制推送"></a>8.8 强制推送</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push --force-with-lease</span><br></pre></td></tr></table></figure><p>尽量不要直接用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push --force</span><br></pre></td></tr></table></figure><p>原因：</p><ul><li><code>--force</code> 可能覆盖别人已经推上来的提交。</li><li><code>--force-with-lease</code> 更安全，会先检查远程是否还是你预期的状态。</li></ul><h2 id="9-合并、变基与历史整理"><a href="#9-合并、变基与历史整理" class="headerlink" title="9. 合并、变基与历史整理"></a>9. 合并、变基与历史整理</h2><h3 id="9-1-合并分支：git-merge"><a href="#9-1-合并分支：git-merge" class="headerlink" title="9.1 合并分支：git merge"></a>9.1 合并分支：<code>git merge</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git switch main</span><br><span class="line">git merge feature/login</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>把另一个分支的提交合入当前分支。</li></ul><p>常见参数：</p><ul><li><code>--no-ff</code>：即使能快进，也创建 merge commit。</li><li><code>--squash</code>：把对方分支压成一个未提交改动，再由你手动提交。</li></ul><h3 id="9-2-变基：git-rebase"><a href="#9-2-变基：git-rebase" class="headerlink" title="9.2 变基：git rebase"></a>9.2 变基：<code>git rebase</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git switch feature/login</span><br><span class="line">git rebase main</span><br></pre></td></tr></table></figure><p>含义：</p><ul><li>把当前分支的提交“重新播放”到 <code>main</code> 最新提交之后。</li></ul><p>优点：</p><ul><li>历史更线性。</li><li>阅读提交链更顺。</li></ul><p>代价：</p><ul><li>会改写提交历史。</li><li>已共享给别人的分支上要谨慎使用。</li></ul><h3 id="9-3-交互式变基：git-rebase-i"><a href="#9-3-交互式变基：git-rebase-i" class="headerlink" title="9.3 交互式变基：git rebase -i"></a>9.3 交互式变基：<code>git rebase -i</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase -i HEAD~3</span><br></pre></td></tr></table></figure><p>常见用途：</p><ul><li>合并多个碎提交。</li><li>调整提交顺序。</li><li>修改提交信息。</li><li>删除不需要的提交。</li></ul><p>常见动作：</p><ul><li><code>pick</code>：保留提交。</li><li><code>reword</code>：保留提交，修改信息。</li><li><code>edit</code>：中途停下，修改提交内容。</li><li><code>squash</code>：压缩到前一个提交，并合并说明。</li><li><code>fixup</code>：压缩到前一个提交，丢弃当前说明。</li><li><code>drop</code>：移除提交。</li></ul><h3 id="9-4-挑选提交：git-cherry-pick"><a href="#9-4-挑选提交：git-cherry-pick" class="headerlink" title="9.4 挑选提交：git cherry-pick"></a>9.4 挑选提交：<code>git cherry-pick</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git cherry-pick &lt;commit&gt;</span><br><span class="line">git cherry-pick commit1 commit2</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>只拿某一个提交到当前分支。</li><li>非常适合 hotfix 回灌、跨分支补丁迁移。</li></ul><h3 id="9-5-合并中断与继续"><a href="#9-5-合并中断与继续" class="headerlink" title="9.5 合并中断与继续"></a>9.5 合并中断与继续</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git merge --abort</span><br><span class="line">git rebase --abort</span><br><span class="line">git rebase --<span class="built_in">continue</span></span><br><span class="line">git cherry-pick --abort</span><br><span class="line">git cherry-pick --<span class="built_in">continue</span></span><br></pre></td></tr></table></figure><p>这类命令在冲突处理中非常常见。</p><h2 id="10-撤销、回退与恢复"><a href="#10-撤销、回退与恢复" class="headerlink" title="10. 撤销、回退与恢复"></a>10. 撤销、回退与恢复</h2><p>这是 Git 最容易让人误操作的一部分，要区分清楚。</p><h3 id="10-1-恢复工作区文件：git-restore"><a href="#10-1-恢复工作区文件：git-restore" class="headerlink" title="10.1 恢复工作区文件：git restore"></a>10.1 恢复工作区文件：<code>git restore</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git restore file.txt</span><br><span class="line">git restore .</span><br><span class="line">git restore --<span class="built_in">source</span>=HEAD~1 file.txt</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>丢弃工作区未暂存修改。</li><li>或把文件恢复成某个提交中的版本。</li></ul><h3 id="10-2-取消暂存：git-restore-staged"><a href="#10-2-取消暂存：git-restore-staged" class="headerlink" title="10.2 取消暂存：git restore --staged"></a>10.2 取消暂存：<code>git restore --staged</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git restore --staged file.txt</span><br><span class="line">git restore --staged .</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>把文件从暂存区拿回工作区。</li><li>文件内容还在，只是不参与下一次提交了。</li></ul><h3 id="10-3-老写法：git-checkout-file"><a href="#10-3-老写法：git-checkout-file" class="headerlink" title="10.3 老写法：git checkout -- file"></a>10.3 老写法：<code>git checkout -- file</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -- file.txt</span><br></pre></td></tr></table></figure><p>历史上常用，但语义不够清晰，现在更推荐 <code>git restore</code>。</p><h3 id="10-4-重置分支：git-reset"><a href="#10-4-重置分支：git-reset" class="headerlink" title="10.4 重置分支：git reset"></a>10.4 重置分支：<code>git reset</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git reset --soft HEAD~1</span><br><span class="line">git reset --mixed HEAD~1</span><br><span class="line">git reset --hard HEAD~1</span><br></pre></td></tr></table></figure><p>三种模式必须分清：</p><ul><li><code>--soft</code>：回退提交，保留暂存区和工作区。</li><li><code>--mixed</code>：回退提交，清空暂存区，保留工作区。这是默认模式。</li><li><code>--hard</code>：回退提交，同时覆盖暂存区和工作区，危险最大。</li></ul><p>高频用途：</p><ul><li>刚提交错了，想撤回重新整理：<code>git reset --soft HEAD~1</code></li><li>想把暂存全部撤掉：<code>git reset</code></li><li>确认不再需要改动，强制回到某版本：<code>git reset --hard &lt;commit&gt;</code></li></ul><h3 id="10-5-反向提交撤销：git-revert"><a href="#10-5-反向提交撤销：git-revert" class="headerlink" title="10.5 反向提交撤销：git revert"></a>10.5 反向提交撤销：<code>git revert</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git revert &lt;commit&gt;</span><br><span class="line">git revert HEAD</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>不改写历史。</li><li>新建一个“反向提交”来抵消旧提交。</li></ul><p>在已经推送到远程、已经共享给团队的提交上，通常优先考虑 <code>git revert</code>，而不是 <code>git reset</code>。</p><h3 id="10-6-查看引用移动历史：git-reflog"><a href="#10-6-查看引用移动历史：git-reflog" class="headerlink" title="10.6 查看引用移动历史：git reflog"></a>10.6 查看引用移动历史：<code>git reflog</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git reflog</span><br><span class="line">git reflog show HEAD</span><br></pre></td></tr></table></figure><p>这是救命命令。</p><p>场景：</p><ul><li>你 <code>reset --hard</code> 了。</li><li>你误删分支了。</li><li>你做了 <code>rebase</code> 后找不到原来的提交了。</li></ul><p>只要引用移动过，很多情况下都能从 <code>reflog</code> 找回来。</p><p>例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git reflog</span><br><span class="line">git reset --hard HEAD@&#123;3&#125;</span><br></pre></td></tr></table></figure><h3 id="10-7-删除未跟踪文件：git-clean"><a href="#10-7-删除未跟踪文件：git-clean" class="headerlink" title="10.7 删除未跟踪文件：git clean"></a>10.7 删除未跟踪文件：<code>git clean</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git clean -n</span><br><span class="line">git clean -f</span><br><span class="line">git clean -fd</span><br><span class="line">git clean -fdx</span><br></pre></td></tr></table></figure><p>说明：</p><ul><li><code>-n</code>：先预览，不真正删除。</li><li><code>-f</code>：真正删除文件。</li><li><code>-d</code>：连目录一起删。</li><li><code>-x</code>：连 <code>.gitignore</code> 忽略的文件也删。</li></ul><p>这是非常危险的命令，最好总是先跑一次 <code>git clean -n</code>。</p><h2 id="11-标签、暂存、清理与归档"><a href="#11-标签、暂存、清理与归档" class="headerlink" title="11. 标签、暂存、清理与归档"></a>11. 标签、暂存、清理与归档</h2><h3 id="11-1-标签：git-tag"><a href="#11-1-标签：git-tag" class="headerlink" title="11.1 标签：git tag"></a>11.1 标签：<code>git tag</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">git tag</span><br><span class="line">git tag v1.0.0</span><br><span class="line">git tag -a v1.0.0 -m <span class="string">&quot;release v1.0.0&quot;</span></span><br><span class="line">git show v1.0.0</span><br><span class="line">git push origin v1.0.0</span><br><span class="line">git push --tags</span><br><span class="line">git tag -d v1.0.0</span><br></pre></td></tr></table></figure><p>标签分两类：</p><ul><li>轻量标签：只是一个引用。</li><li>附注标签：带作者、时间、说明，更适合正式发布。</li></ul><h3 id="11-2-临时保存现场：git-stash"><a href="#11-2-临时保存现场：git-stash" class="headerlink" title="11.2 临时保存现场：git stash"></a>11.2 临时保存现场：<code>git stash</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">git stash</span><br><span class="line">git stash push -m <span class="string">&quot;wip: header refactor&quot;</span></span><br><span class="line">git stash list</span><br><span class="line">git stash show</span><br><span class="line">git stash show -p stash@&#123;0&#125;</span><br><span class="line">git stash pop</span><br><span class="line">git stash apply</span><br><span class="line">git stash drop stash@&#123;0&#125;</span><br><span class="line">git stash clear</span><br></pre></td></tr></table></figure><p>适用场景：</p><ul><li>改到一半，需要先切分支修 bug。</li><li>当前改动还不想提交。</li></ul><p>区别：</p><ul><li><code>pop</code>：恢复后同时删除 stash。</li><li><code>apply</code>：恢复但保留 stash 记录。</li></ul><h3 id="11-3-归档导出：git-archive"><a href="#11-3-归档导出：git-archive" class="headerlink" title="11.3 归档导出：git archive"></a>11.3 归档导出：<code>git archive</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git archive --format=zip --output=release.zip HEAD</span><br><span class="line">git archive --format=tar HEAD | gzip &gt; release.tar.gz</span><br></pre></td></tr></table></figure><p>适合导出某个提交的源代码快照，但不包含 <code>.git</code> 历史。</p><h2 id="12-高级协作与维护命令"><a href="#12-高级协作与维护命令" class="headerlink" title="12. 高级协作与维护命令"></a>12. 高级协作与维护命令</h2><h3 id="12-1-多工作树：git-worktree"><a href="#12-1-多工作树：git-worktree" class="headerlink" title="12.1 多工作树：git worktree"></a>12.1 多工作树：<code>git worktree</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git worktree list</span><br><span class="line">git worktree add ../repo-hotfix hotfix/login</span><br><span class="line">git worktree remove ../repo-hotfix</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>一个仓库同时检出多个分支到不同目录。</li><li>特别适合“主线开发中，临时开一个目录修线上 bug”。</li></ul><h3 id="12-2-子模块：git-submodule"><a href="#12-2-子模块：git-submodule" class="headerlink" title="12.2 子模块：git submodule"></a>12.2 子模块：<code>git submodule</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git submodule status</span><br><span class="line">git submodule update --init --recursive</span><br><span class="line">git submodule add &lt;url&gt; path/to/submodule</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>在一个仓库中引用另一个仓库的特定提交。</li></ul><p>但子模块管理复杂度比较高，能不用就尽量别滥用。</p><h3 id="12-3-仓库完整性检查：git-fsck"><a href="#12-3-仓库完整性检查：git-fsck" class="headerlink" title="12.3 仓库完整性检查：git fsck"></a>12.3 仓库完整性检查：<code>git fsck</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fsck</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>检查对象完整性和引用有效性。</li></ul><h3 id="12-4-垃圾回收与优化：git-gc"><a href="#12-4-垃圾回收与优化：git-gc" class="headerlink" title="12.4 垃圾回收与优化：git gc"></a>12.4 垃圾回收与优化：<code>git gc</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git gc</span><br><span class="line">git gc --prune=now</span><br></pre></td></tr></table></figure><p>作用：</p><ul><li>压缩对象。</li><li>清理不可达对象。</li><li>优化仓库存储。</li></ul><h3 id="12-5-打包传输对象：git-bundle"><a href="#12-5-打包传输对象：git-bundle" class="headerlink" title="12.5 打包传输对象：git bundle"></a>12.5 打包传输对象：<code>git bundle</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git bundle create repo.bundle --all</span><br><span class="line">git <span class="built_in">clone</span> repo.bundle repo-copy</span><br></pre></td></tr></table></figure><p>适合离线传输仓库。</p><h3 id="12-6-查看引用：git-show-ref"><a href="#12-6-查看引用：git-show-ref" class="headerlink" title="12.6 查看引用：git show-ref"></a>12.6 查看引用：<code>git show-ref</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git show-ref</span><br><span class="line">git show-ref --heads</span><br><span class="line">git show-ref --tags</span><br></pre></td></tr></table></figure><h3 id="12-7-解析对象名：git-rev-parse"><a href="#12-7-解析对象名：git-rev-parse" class="headerlink" title="12.7 解析对象名：git rev-parse"></a>12.7 解析对象名：<code>git rev-parse</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git rev-parse HEAD</span><br><span class="line">git rev-parse --short HEAD</span><br><span class="line">git rev-parse --abbrev-ref HEAD</span><br></pre></td></tr></table></figure><p>这类命令在脚本里非常常见。</p><h2 id="13-常见场景速查"><a href="#13-常见场景速查" class="headerlink" title="13. 常见场景速查"></a>13. 常见场景速查</h2><h3 id="13-1-新项目第一次提交"><a href="#13-1-新项目第一次提交" class="headerlink" title="13.1 新项目第一次提交"></a>13.1 新项目第一次提交</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git init</span><br><span class="line">git add -A</span><br><span class="line">git commit -m <span class="string">&quot;chore: initial commit&quot;</span></span><br></pre></td></tr></table></figure><h3 id="13-2-从远程拉代码开始工作"><a href="#13-2-从远程拉代码开始工作" class="headerlink" title="13.2 从远程拉代码开始工作"></a>13.2 从远程拉代码开始工作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> &lt;repo-url&gt;</span><br><span class="line">git switch -c feature/xxx</span><br></pre></td></tr></table></figure><h3 id="13-3-提交前检查"><a href="#13-3-提交前检查" class="headerlink" title="13.3 提交前检查"></a>13.3 提交前检查</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><h3 id="13-4-提交到远程新分支"><a href="#13-4-提交到远程新分支" class="headerlink" title="13.4 提交到远程新分支"></a>13.4 提交到远程新分支</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push -u origin feature/xxx</span><br></pre></td></tr></table></figure><h3 id="13-5-暂时保存现场去修别的问题"><a href="#13-5-暂时保存现场去修别的问题" class="headerlink" title="13.5 暂时保存现场去修别的问题"></a>13.5 暂时保存现场去修别的问题</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git stash push -m <span class="string">&quot;wip&quot;</span></span><br><span class="line">git switch main</span><br></pre></td></tr></table></figure><p>修完后：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git switch feature/xxx</span><br><span class="line">git stash pop</span><br></pre></td></tr></table></figure><h3 id="13-6-本地提交还没推送，想重新整理"><a href="#13-6-本地提交还没推送，想重新整理" class="headerlink" title="13.6 本地提交还没推送，想重新整理"></a>13.6 本地提交还没推送，想重新整理</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset --soft HEAD~1</span><br></pre></td></tr></table></figure><h3 id="13-7-远程已经有别人提交，先同步再推送"><a href="#13-7-远程已经有别人提交，先同步再推送" class="headerlink" title="13.7 远程已经有别人提交，先同步再推送"></a>13.7 远程已经有别人提交，先同步再推送</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin</span><br><span class="line">git rebase origin/main</span><br><span class="line">git push</span><br></pre></td></tr></table></figure><h3 id="13-8-误删提交，想找回来"><a href="#13-8-误删提交，想找回来" class="headerlink" title="13.8 误删提交，想找回来"></a>13.8 误删提交，想找回来</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git reflog</span><br><span class="line">git reset --hard HEAD@&#123;n&#125;</span><br></pre></td></tr></table></figure><h3 id="13-9-回退线上某次错误提交"><a href="#13-9-回退线上某次错误提交" class="headerlink" title="13.9 回退线上某次错误提交"></a>13.9 回退线上某次错误提交</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git revert &lt;bad-commit&gt;</span><br><span class="line">git push</span><br></pre></td></tr></table></figure><h3 id="13-10-清掉未跟踪垃圾文件"><a href="#13-10-清掉未跟踪垃圾文件" class="headerlink" title="13.10 清掉未跟踪垃圾文件"></a>13.10 清掉未跟踪垃圾文件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git clean -n</span><br><span class="line">git clean -fd</span><br></pre></td></tr></table></figure><h2 id="14-Git-命令全景索引"><a href="#14-Git-命令全景索引" class="headerlink" title="14. Git 命令全景索引"></a>14. Git 命令全景索引</h2><p>这一节按类别列出常见和重要命令。严格来说，<code>Git</code> 还包含不少 plumbing 命令和内部维护命令，因此“覆盖所有命令”在实践里更适合理解为：覆盖你日常开发、排错、维护和脚本里最可能接触到的完整命令体系。</p><p>如果你想看当前版本 Git 提供的全部子命令，请执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">help</span> -a</span><br></pre></td></tr></table></figure><h3 id="14-1-初始化与获取"><a href="#14-1-初始化与获取" class="headerlink" title="14.1 初始化与获取"></a>14.1 初始化与获取</h3><ul><li><code>git init</code>：初始化仓库。</li><li><code>git clone</code>：克隆仓库。</li></ul><h3 id="14-2-配置与帮助"><a href="#14-2-配置与帮助" class="headerlink" title="14.2 配置与帮助"></a>14.2 配置与帮助</h3><ul><li><code>git config</code>：查看和设置配置。</li><li><code>git help</code>：查看帮助。</li><li><code>git bugreport</code>：生成 bug 报告信息。</li></ul><h3 id="14-3-工作区与暂存区"><a href="#14-3-工作区与暂存区" class="headerlink" title="14.3 工作区与暂存区"></a>14.3 工作区与暂存区</h3><ul><li><code>git status</code>：查看状态。</li><li><code>git add</code>：加入暂存区。</li><li><code>git restore</code>：恢复工作区或暂存区。</li><li><code>git rm</code>：删除文件并记录到 Git。</li><li><code>git mv</code>：移动或重命名文件。</li><li><code>git clean</code>：删除未跟踪文件。</li></ul><h3 id="14-4-提交与历史"><a href="#14-4-提交与历史" class="headerlink" title="14.4 提交与历史"></a>14.4 提交与历史</h3><ul><li><code>git commit</code>：提交。</li><li><code>git log</code>：查看历史。</li><li><code>git show</code>：查看对象详情。</li><li><code>git diff</code>：查看差异。</li><li><code>git blame</code>：查看行级作者信息。</li><li><code>git annotate</code>：<code>blame</code> 的相关形式。</li><li><code>git shortlog</code>：摘要式日志。</li><li><code>git describe</code>：根据最近标签描述提交。</li></ul><h3 id="14-5-分支与引用"><a href="#14-5-分支与引用" class="headerlink" title="14.5 分支与引用"></a>14.5 分支与引用</h3><ul><li><code>git branch</code>：管理分支。</li><li><code>git switch</code>：切换分支。</li><li><code>git checkout</code>：旧式切换&#x2F;恢复命令。</li><li><code>git tag</code>：管理标签。</li><li><code>git show-ref</code>：查看引用。</li><li><code>git update-ref</code>：底层更新引用。</li></ul><h3 id="14-6-远程协作"><a href="#14-6-远程协作" class="headerlink" title="14.6 远程协作"></a>14.6 远程协作</h3><ul><li><code>git remote</code>：管理远程。</li><li><code>git fetch</code>：获取远程更新。</li><li><code>git pull</code>：拉取并合并或变基。</li><li><code>git push</code>：推送提交。</li><li><code>git ls-remote</code>：查看远程引用。</li><li><code>git request-pull</code>：生成拉取请求摘要。</li></ul><h3 id="14-7-合并与历史整理"><a href="#14-7-合并与历史整理" class="headerlink" title="14.7 合并与历史整理"></a>14.7 合并与历史整理</h3><ul><li><code>git merge</code>：合并分支。</li><li><code>git rebase</code>：变基。</li><li><code>git cherry-pick</code>：挑选提交。</li><li><code>git revert</code>：反向提交撤销。</li><li><code>git reset</code>：移动当前分支并可重置暂存区&#x2F;工作区。</li><li><code>git range-diff</code>：比较两组提交序列。</li></ul><h3 id="14-8-暂存现场与补丁"><a href="#14-8-暂存现场与补丁" class="headerlink" title="14.8 暂存现场与补丁"></a>14.8 暂存现场与补丁</h3><ul><li><code>git stash</code>：暂存现场。</li><li><code>git apply</code>：应用补丁。</li><li><code>git am</code>：应用邮件格式补丁。</li><li><code>git format-patch</code>：导出补丁。</li></ul><h3 id="14-9-搜索与排错"><a href="#14-9-搜索与排错" class="headerlink" title="14.9 搜索与排错"></a>14.9 搜索与排错</h3><ul><li><code>git grep</code>：搜索内容。</li><li><code>git bisect</code>：二分定位问题提交。</li><li><code>git reflog</code>：查看引用变动历史。</li><li><code>git fsck</code>：检查仓库完整性。</li></ul><h3 id="14-10-子模块、多工作区与归档"><a href="#14-10-子模块、多工作区与归档" class="headerlink" title="14.10 子模块、多工作区与归档"></a>14.10 子模块、多工作区与归档</h3><ul><li><code>git submodule</code>：管理子模块。</li><li><code>git worktree</code>：多工作树。</li><li><code>git archive</code>：导出归档。</li><li><code>git bundle</code>：离线打包仓库。</li></ul><h3 id="14-11-对象库与维护"><a href="#14-11-对象库与维护" class="headerlink" title="14.11 对象库与维护"></a>14.11 对象库与维护</h3><ul><li><code>git gc</code>：垃圾回收。</li><li><code>git prune</code>：清理不可达对象。</li><li><code>git repack</code>：重新打包对象。</li><li><code>git pack-refs</code>：压缩引用存储。</li><li><code>git count-objects</code>：统计对象数量。</li><li><code>git maintenance</code>：仓库维护任务。</li></ul><h3 id="14-12-常见-plumbing-命令"><a href="#14-12-常见-plumbing-命令" class="headerlink" title="14.12 常见 plumbing 命令"></a>14.12 常见 plumbing 命令</h3><p>这些命令平时不一定直接手敲，但在脚本、底层理解和高级排错里很重要：</p><ul><li><code>git cat-file</code>：查看对象内容。</li><li><code>git hash-object</code>：计算对象哈希并可写入对象库。</li><li><code>git ls-files</code>：列出索引和工作树文件。</li><li><code>git ls-tree</code>：查看树对象。</li><li><code>git read-tree</code>：把树对象读入索引。</li><li><code>git write-tree</code>：把索引写成树对象。</li><li><code>git commit-tree</code>：直接基于树对象创建提交。</li><li><code>git mktree</code>：从文本描述创建树对象。</li><li><code>git mktag</code>：创建标签对象。</li><li><code>git rev-parse</code>：解析修订表达式。</li><li><code>git rev-list</code>：列出提交对象。</li><li><code>git name-rev</code>：用可读名称描述提交。</li><li><code>git merge-base</code>：查找分支共同祖先。</li><li><code>git symbolic-ref</code>：读取或修改符号引用。</li><li><code>git for-each-ref</code>：遍历引用。</li><li><code>git update-index</code>：直接操作索引。</li><li><code>git checkout-index</code>：从索引签出文件。</li><li><code>git diff-index</code>：比较树和工作区&#x2F;索引。</li><li><code>git diff-tree</code>：比较两个树对象。</li><li><code>git diff-files</code>：比较工作区和索引。</li><li><code>git unpack-objects</code>：解包对象。</li><li><code>git pack-objects</code>：打包对象。</li><li><code>git verify-pack</code>：校验 pack 文件。</li><li><code>git unpack-file</code>：解出 blob 内容。</li><li><code>git replace</code>：用替代对象覆盖显示效果。</li><li><code>git notes</code>：附加注释对象。</li></ul><h3 id="14-13-邮件工作流相关命令"><a href="#14-13-邮件工作流相关命令" class="headerlink" title="14.13 邮件工作流相关命令"></a>14.13 邮件工作流相关命令</h3><p>这类命令在内核社区或部分传统开源项目里比较常见：</p><ul><li><code>git send-email</code></li><li><code>git imap-send</code></li><li><code>git mailinfo</code></li><li><code>git mailsplit</code></li><li><code>git mailmap</code></li><li><code>git am</code></li><li><code>git format-patch</code></li><li><code>git request-pull</code></li></ul><h2 id="15-学习建议"><a href="#15-学习建议" class="headerlink" title="15. 学习建议"></a>15. 学习建议</h2><p>如果你不是想研究 Git 内部实现，而是想把工作流先跑稳，建议按下面顺序掌握：</p><ol><li>先彻底搞懂 <code>status</code>、<code>add</code>、<code>commit</code>、<code>diff</code>、<code>log</code>。</li><li>再掌握 <code>branch</code>、<code>switch</code>、<code>fetch</code>、<code>pull</code>、<code>push</code>。</li><li>然后掌握 <code>merge</code>、<code>rebase</code>、<code>stash</code>、<code>tag</code>。</li><li>最后重点掌握 <code>restore</code>、<code>reset</code>、<code>revert</code>、<code>reflog</code>，这组命令决定你出问题时能不能救回来。</li></ol><p>我自己的建议是：</p><ul><li>每次提交前都跑一次 <code>git status</code>。</li><li>每次提交前都看一次 <code>git diff --cached</code>。</li><li>已经推送到共享分支的历史，优先用 <code>git revert</code>，少用改写历史的方式。</li><li>强制推送优先用 <code>git push --force-with-lease</code>。</li><li>遇到 Git 混乱时，先停下来，看 <code>status</code>、<code>log</code>、<code>reflog</code>，不要盲目敲命令。</li></ul><p>最后给一个非常实用的最小速查表：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br><span class="line">git add -A</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br><span class="line">git commit -m <span class="string">&quot;message&quot;</span></span><br><span class="line">git <span class="built_in">log</span> --oneline --graph --decorate --all</span><br><span class="line">git switch -c feature/xxx</span><br><span class="line">git fetch --all --prune</span><br><span class="line">git pull --rebase</span><br><span class="line">git push -u origin feature/xxx</span><br><span class="line">git restore --staged .</span><br><span class="line">git restore .</span><br><span class="line">git reset --soft HEAD~1</span><br><span class="line">git revert &lt;commit&gt;</span><br><span class="line">git stash push -m <span class="string">&quot;wip&quot;</span></span><br><span class="line">git stash pop</span><br><span class="line">git reflog</span><br></pre></td></tr></table></figure><p>如果你把上面这一组命令吃透了，已经能覆盖绝大多数日常开发场景。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20260402/git-cli-handbook/</id>
    <link href="https://minniexcode.github.io/memoirs/20260402/git-cli-handbook/"/>
    <published>2026-04-02T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p><code>Git</code> 是目前最主流的分布式版本控制系统。很多人会用 <code>git add</code>、<code>git commit</code>、<code>git push</code>，但一旦进入分支协作、历史整理、冲突处理、误操作恢复，就容易开始混乱。</p>
<p>这篇文章按“从日常到深入”的顺序，把 <code>Git CLI</code> 的核心命令系统化整理出来：先讲最常用的工作流，再讲协作、历史、恢复、排错和高级功能，最后给出一份尽量完整的命令索引。</p>
<p>如果你只想先建立一套稳定工作流，可以重点看这些命令：<code>git init</code>、<code>git clone</code>、<code>git status</code>、<code>git add</code>、<code>git commit</code>、<code>git log</code>、<code>git diff</code>、<code>git branch</code>、<code>git switch</code>、<code>git restore</code>、<code>git fetch</code>、<code>git pull</code>、<code>git push</code>、<code>git merge</code>、<code>git rebase</code>、<code>git stash</code>、<code>git tag</code>、<code>git reset</code>、<code>git revert</code>、<code>git reflog</code>。</p>]]>
    </summary>
    <title>Git CLI 命令操作手册与详细介绍</title>
    <updated>2026-04-01T16:08:03.641Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Networking" scheme="https://minniexcode.github.io/memoirs/tags/Networking/"/>
    <category term="Certification" scheme="https://minniexcode.github.io/memoirs/tags/Certification/"/>
    <category term="Study" scheme="https://minniexcode.github.io/memoirs/tags/Study/"/>
    <category term="Ruankao" scheme="https://minniexcode.github.io/memoirs/tags/Ruankao/"/>
    <category term="Exam" scheme="https://minniexcode.github.io/memoirs/tags/Exam/"/>
    <content>
      <![CDATA[<p>如果你和我一样，白天上班，真正能拿出来备考的时间只有工作日晚间 <code>2-3 小时</code>，同时又不是网络科班出身，而是有开发经验、懂一些网络但没有系统梳理过，那么软考 <code>Network Engineer</code> 的难点通常不是“完全看不懂”，而是：知识点很散、真题节奏不熟、下午案例题写不出关键词。</p><p>这篇文章把三份资料合成了一篇可以直接照着执行的长文：</p><ul><li><code>8 周备考计划</code></li><li><code>高频知识速记表</code></li><li><code>端口 + 协议 + 易混点清单</code></li></ul><p>目标不是追求高分，而是尽量在本次考试通过；即使这次没过，也把下一次复习的框架一次搭好。我会保留 <code>English concepts</code> 的组织方式，因为对很多有工程背景的人来说，这种方式更容易形成检索路径。</p><span id="more"></span><blockquote><p>如果你不是按 <code>3/25 -&gt; 5/23</code> 这个时间窗备考，也没关系，直接把下面的 <code>Week 0 - Week 8</code> 整体平移即可。</p></blockquote><hr><h2 id="一、先讲策略：这场考试怎么准备更像“冲刺”而不是“啃书”"><a href="#一、先讲策略：这场考试怎么准备更像“冲刺”而不是“啃书”" class="headerlink" title="一、先讲策略：这场考试怎么准备更像“冲刺”而不是“啃书”"></a>一、先讲策略：这场考试怎么准备更像“冲刺”而不是“啃书”</h2><h3 id="1）目标分数"><a href="#1）目标分数" class="headerlink" title="1）目标分数"></a>1）目标分数</h3><ul><li><code>Morning paper</code>：稳住基础分，目标 <code>52-58</code></li><li><code>Afternoon paper</code>：不崩盘，目标 <code>48-55</code></li><li>最终目标：<code>两科都过线</code></li></ul><h3 id="2）学习原则"><a href="#2）学习原则" class="headerlink" title="2）学习原则"></a>2）学习原则</h3><ul><li>不走“从头到尾啃厚书”的路线</li><li>采用 <code>concept -&gt; real questions -&gt; notes -&gt; revisit</code> 的闭环</li><li>优先抓 <code>high-frequency topics</code></li><li>从第 <code>2</code> 周开始，必须接触 <code>afternoon case</code></li></ul><h3 id="3）时间预算"><a href="#3）时间预算" class="headerlink" title="3）时间预算"></a>3）时间预算</h3><p>按照工作日晚间 <code>2-3h</code> 计算，<code>8</code> 周大约可投入：</p><ul><li><code>2h/night</code>：约 <code>86h</code></li><li><code>2.5h/night</code>：约 <code>107h</code></li><li><code>3h/night</code>：约 <code>129h</code></li></ul><p>这个投入量，对于“有多年开发经验、但网络知识没有系统梳理”的人来说，足够完成一次质量不错的冲刺。</p><h3 id="4）Morning-和-Afternoon-分别怎么打"><a href="#4）Morning-和-Afternoon-分别怎么打" class="headerlink" title="4）Morning 和 Afternoon 分别怎么打"></a>4）Morning 和 Afternoon 分别怎么打</h3><h4 id="Morning"><a href="#Morning" class="headerlink" title="Morning"></a>Morning</h4><ul><li>覆盖面广</li><li>小知识点很多</li><li>很容易在端口、术语、协议差异上丢分</li><li>做题速度很重要</li></ul><h4 id="Afternoon"><a href="#Afternoon" class="headerlink" title="Afternoon"></a>Afternoon</h4><ul><li>题量少，但更偏场景题</li><li>高频题型喜欢考：<code>address planning</code>、<code>VLAN</code>、<code>routing</code>、<code>ACL/NAT</code>、<code>fault analysis</code>、<code>design</code></li><li><code>partial credit</code> 很重要</li><li>关键词比长篇大论更重要</li></ul><p>下午题一个很实用的答题原则是：</p><ol><li>要解决什么问题</li><li>采用什么技术或协议</li><li>为什么这个方案适合当前场景</li><li>最终能达到什么效果</li></ol><h3 id="5）如果时间很紧，优先锁定这些内容"><a href="#5）如果时间很紧，优先锁定这些内容" class="headerlink" title="5）如果时间很紧，优先锁定这些内容"></a>5）如果时间很紧，优先锁定这些内容</h3><ul><li><code>IPv4</code>, <code>subnetting</code>, <code>CIDR</code>, <code>VLSM</code>, <code>route summarization</code></li><li><code>VLAN</code>, <code>Trunk</code>, <code>STP</code></li><li><code>static routing</code>, <code>RIP</code>, <code>OSPF</code>, <code>BGP basics</code></li><li><code>ACL</code>, <code>NAT</code>, <code>PAT</code>, <code>firewall</code>, <code>VPN basics</code></li><li><code>DNS</code>, <code>DHCP</code>, <code>HTTP/HTTPS</code>, <code>SMTP</code>, <code>POP3</code>, <code>IMAP</code>, <code>FTP</code></li><li><code>network design</code>, <code>redundancy</code>, <code>troubleshooting</code>, <code>cabling/media</code></li></ul><p>如果时间特别紧，不要过度投入这些内容：</p><ul><li>很深的 <code>BGP policy</code></li><li>很细的 <code>IPv6 details</code></li><li><code>SDN / 5G / cloud overview</code> 这类只需要认知级理解的内容</li></ul><hr><h2 id="二、资料怎么选：尽量少而有效"><a href="#二、资料怎么选：尽量少而有效" class="headerlink" title="二、资料怎么选：尽量少而有效"></a>二、资料怎么选：尽量少而有效</h2><h3 id="1）官方信息"><a href="#1）官方信息" class="headerlink" title="1）官方信息"></a>1）官方信息</h3><ul><li>官方网站：<code>https://www.ruankao.org.cn/</code></li><li>考试计划：<code>https://www.ruankao.org.cn/exam/plan.html</code></li><li>官方教材入口：<code>https://www.ruankao.org.cn/book/main.html</code></li><li>报名入口：<code>https://bm.ruankao.org.cn/sign/welcome</code></li><li>机考模拟&#x2F;下载入口：<code>https://bm.ruankao.org.cn/sign/in?type=query&amp;s=vexam/download/main</code></li></ul><h3 id="2）教材"><a href="#2）教材" class="headerlink" title="2）教材"></a>2）教材</h3><ul><li>推荐主教材：<code>网络工程师教程（第6版）</code></li><li>清华大学出版社页面：<code>https://www.tup.com.cn/booksCenter/book_10453401.html</code></li></ul><p>使用方式不要搞反：</p><ul><li>把它当 <code>reference book</code></li><li>不要从第一页一路硬读到最后一页</li><li>哪些真题章节反复出错，再回书里补对应章节</li></ul><h3 id="3）免费视频"><a href="#3）免费视频" class="headerlink" title="3）免费视频"></a>3）免费视频</h3><p>晚间时间有限，更适合按专题看，所以优先用 <code>Bilibili</code>。</p><ul><li>考试介绍 &#x2F; 报考指南：<code>https://www.bilibili.com/video/BV1ZAivYsEt3/</code></li><li>搜索关键词 1：<code>希赛软考 网络工程师 第6版</code></li><li>搜索关键词 2：<code>网络工程师 下午真题 解析</code></li><li>搜索关键词 3：<code>软考 网络工程师 机考</code></li><li>搜索关键词 4：<code>子网划分 VLAN STP OSPF ACL NAT 网络工程师</code></li></ul><p>建议只看三类视频：</p><ul><li><code>high-frequency concept videos</code></li><li><code>afternoon case analysis</code></li><li><code>机考/答题节奏</code> 视频</li></ul><h3 id="4）高频知识点与真题"><a href="#4）高频知识点与真题" class="headerlink" title="4）高频知识点与真题"></a>4）高频知识点与真题</h3><ul><li>高频知识点参考：<code>https://www.educity.cn/rk/5374342.html</code></li><li>真题优先做近 <code>5 年</code></li><li>如果纸质真题不全，优先补 <code>2023-2025</code> 的线上真题和解析</li></ul><hr><h2 id="三、每晚怎么学：把时间切成可执行模板"><a href="#三、每晚怎么学：把时间切成可执行模板" class="headerlink" title="三、每晚怎么学：把时间切成可执行模板"></a>三、每晚怎么学：把时间切成可执行模板</h2><h3 id="Option-A：只有-2-hours"><a href="#Option-A：只有-2-hours" class="headerlink" title="Option A：只有 2 hours"></a>Option A：只有 <code>2 hours</code></h3><ol><li><code>15 min</code>：回顾昨天错题 &#x2F; notes</li><li><code>45 min</code>：学习当天主题的核心概念</li><li><code>40 min</code>：做 <code>15-20</code> 道 morning 选择题</li><li><code>20 min</code>：订正 + 记 <code>3-5</code> 条 notes</li></ol><h3 id="Option-B：有-2-5-3-hours"><a href="#Option-B：有-2-5-3-hours" class="headerlink" title="Option B：有 2.5-3 hours"></a>Option B：有 <code>2.5-3 hours</code></h3><p>在上面的基础上再增加：</p><ul><li><code>30-60 min</code>：做 <code>1</code> 道 <code>afternoon case</code>，或者看 <code>1</code> 道详细解析</li></ul><h3 id="Weekly-Output-Target"><a href="#Weekly-Output-Target" class="headerlink" title="Weekly Output Target"></a>Weekly Output Target</h3><p>每周至少完成：</p><ul><li><code>80-100</code> 道 morning questions</li><li><code>1-2</code> 道 afternoon case</li><li><code>1</code> 页自己的 topic notes</li></ul><h3 id="我额外建议你固定做的-4-张纸"><a href="#我额外建议你固定做的-4-张纸" class="headerlink" title="我额外建议你固定做的 4 张纸"></a>我额外建议你固定做的 4 张纸</h3><p>这些不是“可选项”，而是复习越到后面越关键：</p><ul><li><code>protocol comparison</code></li><li><code>common ports</code></li><li><code>subnetting cheat sheet</code></li><li><code>afternoon keywords</code></li></ul><hr><h2 id="四、考点优先级：先打通主线，再补边缘"><a href="#四、考点优先级：先打通主线，再补边缘" class="headerlink" title="四、考点优先级：先打通主线，再补边缘"></a>四、考点优先级：先打通主线，再补边缘</h2><h3 id="A-Level：必须拿下"><a href="#A-Level：必须拿下" class="headerlink" title="A-Level：必须拿下"></a>A-Level：必须拿下</h3><ul><li><code>IPv4 addressing</code></li><li><code>subnetting / VLSM / CIDR</code></li><li><code>route summarization</code></li><li><code>VLAN / Trunk</code></li><li><code>STP</code></li><li><code>static routing / default route</code></li><li><code>RIP / OSPF / BGP basics</code></li><li><code>ACL</code></li><li><code>NAT / PAT</code></li><li><code>firewall / VPN basics</code></li><li><code>DNS / DHCP / HTTP / SMTP</code></li><li><code>network design / redundancy / troubleshooting</code></li></ul><h3 id="B-Level：必须会做题"><a href="#B-Level：必须会做题" class="headerlink" title="B-Level：必须会做题"></a>B-Level：必须会做题</h3><ul><li><code>SNMP / Syslog</code></li><li><code>proxy</code></li><li><code>FTP / POP3 / IMAP</code></li><li><code>RAID</code></li><li><code>server basics</code></li><li><code>wireless LAN</code></li><li><code>IPv6 basics</code></li><li><code>cabling / media selection</code></li></ul><h3 id="C-Level：知道就行"><a href="#C-Level：知道就行" class="headerlink" title="C-Level：知道就行"></a>C-Level：知道就行</h3><ul><li><code>SDN</code></li><li><code>cloud networking overview</code></li><li><code>5G / new-outline overview topics</code></li><li>其他明显偏概念、偏新大纲的边缘内容</li></ul><hr><h2 id="五、8-周冲刺计划：按周推进，不要每天重新想“今天学什么”"><a href="#五、8-周冲刺计划：按周推进，不要每天重新想“今天学什么”" class="headerlink" title="五、8 周冲刺计划：按周推进，不要每天重新想“今天学什么”"></a>五、8 周冲刺计划：按周推进，不要每天重新想“今天学什么”</h2><h2 id="Week-0：3-25-Wed-3-27-Fri"><a href="#Week-0：3-25-Wed-3-27-Fri" class="headerlink" title="Week 0：3/25 Wed - 3/27 Fri"></a>Week 0：<code>3/25 Wed - 3/27 Fri</code></h2><h3 id="Goal"><a href="#Goal" class="headerlink" title="Goal"></a>Goal</h3><ul><li>建立考试框架</li><li>知道 morning &#x2F; afternoon 分别怎么考</li><li>完成一次 baseline test</li></ul><h3 id="Plan"><a href="#Plan" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>3/25 Wed</code><ul><li>了解考试结构、题型、时间分配</li><li>建立笔记文档：<code>wrong questions</code>、<code>topic notes</code>、<code>formula sheet</code></li><li>做一套 <code>morning</code> 的前 <code>20-30</code> 题，感受题型</li></ul></li><li><code>3/26 Thu</code><ul><li>学 <code>OSI / TCP-IP / encapsulation / decapsulation</code></li><li>学 <code>Ethernet / MAC / ARP</code></li><li>做 <code>15-20</code> 道对应选择题</li></ul></li><li><code>3/27 Fri</code><ul><li>学 <code>IPv4 addressing</code>、<code>subnet mask</code>、<code>CIDR</code></li><li>做 <code>15-20</code> 道地址基础题</li><li>记录第一次暴露出的薄弱点</li></ul></li></ul><h3 id="Optional-Weekend-Catch-up"><a href="#Optional-Weekend-Catch-up" class="headerlink" title="Optional Weekend Catch-up"></a>Optional Weekend Catch-up</h3><ul><li>用 <code>60-90 min</code> 回顾本周错题</li><li>看 <code>1</code> 道 <code>afternoon case</code> 解析，先理解答题风格，不求会做</li></ul><hr><h2 id="Week-1：3-30-Mon-4-3-Fri"><a href="#Week-1：3-30-Mon-4-3-Fri" class="headerlink" title="Week 1：3/30 Mon - 4/3 Fri"></a>Week 1：<code>3/30 Mon - 4/3 Fri</code></h2><h3 id="Goal-1"><a href="#Goal-1" class="headerlink" title="Goal"></a>Goal</h3><ul><li>拿下 <code>subnetting</code> 这个高频送分点</li><li>对 <code>VLAN / Trunk</code> 建立基本理解</li></ul><h3 id="Plan-1"><a href="#Plan-1" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>3/30 Mon</code><ul><li><code>subnetting</code>：网络地址、广播地址、可用主机数</li><li>做 <code>15</code> 道子网题</li></ul></li><li><code>3/31 Tue</code><ul><li><code>VLSM</code> 和 <code>CIDR</code></li><li>做 <code>15-20</code> 道地址规划题</li></ul></li><li><code>4/1 Wed</code><ul><li><code>route summarization</code></li><li>建一张地址计算速记表</li><li>做 <code>15</code> 道题</li></ul></li><li><code>4/2 Thu</code><ul><li><code>VLAN</code>、<code>access port</code>、<code>Trunk</code></li><li>做 <code>15-20</code> 道交换基础题</li></ul></li><li><code>4/3 Fri</code><ul><li>做一个小复盘</li><li><code>1</code> 道 <code>afternoon case</code>：address planning 或 VLAN 基础题</li></ul></li></ul><h3 id="End-of-Week-Check"><a href="#End-of-Week-Check" class="headerlink" title="End-of-Week Check"></a>End-of-Week Check</h3><ul><li>能快速计算网段 &#x2F; 广播地址 &#x2F; 主机范围</li><li>知道 <code>VLAN</code> 和 <code>Trunk</code> 的作用</li><li>能看懂基本地址规划题</li></ul><hr><h2 id="Week-2：4-6-Mon-4-10-Fri"><a href="#Week-2：4-6-Mon-4-10-Fri" class="headerlink" title="Week 2：4/6 Mon - 4/10 Fri"></a>Week 2：<code>4/6 Mon - 4/10 Fri</code></h2><h3 id="Goal-2"><a href="#Goal-2" class="headerlink" title="Goal"></a>Goal</h3><ul><li>建立 <code>switching + routing basics</code></li><li>开始接触 <code>STP</code> 和动态路由</li></ul><h3 id="Plan-2"><a href="#Plan-2" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>4/6 Mon</code><ul><li><code>STP</code>：why it exists、loop avoidance、root bridge</li><li>做 <code>15</code> 道题</li></ul></li><li><code>4/7 Tue</code><ul><li><code>link aggregation</code>、switch basics</li><li>做 <code>15</code> 道题</li></ul></li><li><code>4/8 Wed</code><ul><li><code>static route</code>、<code>default route</code></li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/9 Thu</code><ul><li><code>RIP</code>：hop count、convergence、suitable scenarios</li><li>做 <code>15</code> 道题</li></ul></li><li><code>4/10 Fri</code><ul><li><code>OSPF basics</code>：link-state、metric、area</li><li>做 <code>1</code> 道 afternoon case：switch &#x2F; routing basics</li></ul></li></ul><h3 id="End-of-Week-Check-1"><a href="#End-of-Week-Check-1" class="headerlink" title="End-of-Week Check"></a>End-of-Week Check</h3><ul><li>能说明 <code>STP</code> 解决什么问题</li><li>能区分 <code>static route</code>、<code>RIP</code>、<code>OSPF</code></li><li>能做基础的交换 &#x2F; 路由题</li></ul><hr><h2 id="Week-3：4-13-Mon-4-17-Fri"><a href="#Week-3：4-13-Mon-4-17-Fri" class="headerlink" title="Week 3：4/13 Mon - 4/17 Fri"></a>Week 3：<code>4/13 Mon - 4/17 Fri</code></h2><h3 id="Goal-3"><a href="#Goal-3" class="headerlink" title="Goal"></a>Goal</h3><ul><li>进入网络工程师核心区：<code>OSPF / BGP / ACL / NAT</code></li><li>afternoon 开始真正练习表达</li></ul><h3 id="Plan-3"><a href="#Plan-3" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>4/13 Mon</code><ul><li><code>OSPF</code>：neighbor、metric、convergence、area basics</li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/14 Tue</code><ul><li><code>BGP basics</code>：path selection basic ideas、inter-domain scenarios</li><li>做 <code>10-15</code> 道题</li></ul></li><li><code>4/15 Wed</code><ul><li><code>ACL</code>：standard vs extended、matching logic</li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/16 Thu</code><ul><li><code>NAT / PAT</code>、<code>firewall basics</code></li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/17 Fri</code><ul><li><code>VPN basics</code></li><li>做 <code>1-2</code> 道 afternoon case：ACL &#x2F; NAT &#x2F; routing</li></ul></li></ul><h3 id="End-of-Week-Check-2"><a href="#End-of-Week-Check-2" class="headerlink" title="End-of-Week Check"></a>End-of-Week Check</h3><ul><li>能说清 <code>OSPF</code> 和 <code>BGP</code> 的最核心差别</li><li>知道 <code>ACL</code> 一般按什么匹配</li><li>知道 <code>NAT / PAT</code> 的常见题型</li></ul><hr><h2 id="Week-4：4-20-Mon-4-24-Fri"><a href="#Week-4：4-20-Mon-4-24-Fri" class="headerlink" title="Week 4：4/20 Mon - 4/24 Fri"></a>Week 4：<code>4/20 Mon - 4/24 Fri</code></h2><h3 id="Goal-4"><a href="#Goal-4" class="headerlink" title="Goal"></a>Goal</h3><ul><li>补齐服务类高频考点</li><li>建立“服务 + 端口 + 故障”的联动记忆</li></ul><h3 id="Plan-4"><a href="#Plan-4" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>4/20 Mon</code><ul><li><code>DNS</code>：recursion、authoritative、cache、typical issues</li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/21 Tue</code><ul><li><code>DHCP</code>：lease、relay、address assignment flow</li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/22 Wed</code><ul><li><code>HTTP / HTTPS</code>、<code>proxy</code>、basic web service concepts</li><li>做 <code>15</code> 道题</li></ul></li><li><code>4/23 Thu</code><ul><li><code>SMTP / POP3 / IMAP / FTP</code></li><li>整理一张 <code>common ports</code> 速记表</li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/24 Fri</code><ul><li><code>SNMP</code>、<code>Syslog</code>、<code>server basics</code>、<code>RAID</code></li><li>做 <code>1</code> 道 afternoon case：service &#x2F; operation &#x2F; fault diagnosis</li></ul></li></ul><h3 id="End-of-Week-Check-3"><a href="#End-of-Week-Check-3" class="headerlink" title="End-of-Week Check"></a>End-of-Week Check</h3><ul><li>常见服务端口能认出来</li><li>知道 <code>DNS / DHCP / Mail</code> 常见故障模式</li><li>afternoon 答题开始能写出关键词</li></ul><hr><h2 id="Week-5：4-27-Mon-5-1-Fri"><a href="#Week-5：4-27-Mon-5-1-Fri" class="headerlink" title="Week 5：4/27 Mon - 5/1 Fri"></a>Week 5：<code>4/27 Mon - 5/1 Fri</code></h2><h3 id="Goal-5"><a href="#Goal-5" class="headerlink" title="Goal"></a>Goal</h3><ul><li>切入 <code>design / topology / availability / troubleshooting</code></li><li>让 afternoon 更像“写方案”而不是“猜答案”</li></ul><h3 id="Plan-5"><a href="#Plan-5" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>4/27 Mon</code><ul><li><code>network topology</code>、core &#x2F; distribution &#x2F; access</li><li>做 <code>15</code> 道题</li></ul></li><li><code>4/28 Tue</code><ul><li><code>redundancy</code>、<code>high availability</code>、backup links</li><li>做 <code>15</code> 道题</li></ul></li><li><code>4/29 Wed</code><ul><li><code>troubleshooting</code>：fault isolation、layered diagnosis mindset</li><li>做 <code>15-20</code> 道题</li></ul></li><li><code>4/30 Thu</code><ul><li><code>cabling / media selection</code>、<code>wireless LAN basics</code></li><li>做 <code>15</code> 道题</li></ul></li><li><code>5/1 Fri</code><ul><li><code>IPv6 basics</code> + 新大纲轻量内容扫一遍</li><li>做 <code>1-2</code> 道 afternoon case：design &#x2F; troubleshooting</li></ul></li></ul><h3 id="End-of-Week-Check-4"><a href="#End-of-Week-Check-4" class="headerlink" title="End-of-Week Check"></a>End-of-Week Check</h3><ul><li>知道典型企业网络分层结构</li><li>能解释冗余设计的目的</li><li>遇到故障题不会完全没思路</li></ul><hr><h2 id="Week-6：5-4-Mon-5-8-Fri"><a href="#Week-6：5-4-Mon-5-8-Fri" class="headerlink" title="Week 6：5/4 Mon - 5/8 Fri"></a>Week 6：<code>5/4 Mon - 5/8 Fri</code></h2><h3 id="Goal-6"><a href="#Goal-6" class="headerlink" title="Goal"></a>Goal</h3><ul><li>从“学知识”切换到“拿分”</li><li>真题优先级高于新视频</li></ul><h3 id="Plan-6"><a href="#Plan-6" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>5/4 Mon</code><ul><li>做 <code>1</code> 套完整 <code>morning</code> 真题</li><li>统计错题分布</li></ul></li><li><code>5/5 Tue</code><ul><li>复盘错题最多的两个专题</li><li>补对应概念或视频</li></ul></li><li><code>5/6 Wed</code><ul><li>做 <code>1</code> 道 <code>afternoon case</code></li><li>按标准答案整理关键词</li></ul></li><li><code>5/7 Thu</code><ul><li>再做 <code>20-25</code> 道 morning 高频专题题</li><li>重点盯 <code>subnetting / routing / services</code></li></ul></li><li><code>5/8 Fri</code><ul><li>做 <code>1</code> 道 afternoon case</li><li>输出一页 <code>high-frequency notes</code></li></ul></li></ul><h3 id="End-of-Week-Check-5"><a href="#End-of-Week-Check-5" class="headerlink" title="End-of-Week Check"></a>End-of-Week Check</h3><ul><li>知道自己最弱的 <code>3</code> 个专题</li><li>开始形成自己的速记表</li></ul><hr><h2 id="Week-7：5-11-Mon-5-15-Fri"><a href="#Week-7：5-11-Mon-5-15-Fri" class="headerlink" title="Week 7：5/11 Mon - 5/15 Fri"></a>Week 7：<code>5/11 Mon - 5/15 Fri</code></h2><h3 id="Goal-7"><a href="#Goal-7" class="headerlink" title="Goal"></a>Goal</h3><ul><li>稳定做题节奏</li><li>压缩知识点到考场可调用状态</li></ul><h3 id="Plan-7"><a href="#Plan-7" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>5/11 Mon</code><ul><li>做第 <code>2</code> 套完整 <code>morning</code> 真题</li></ul></li><li><code>5/12 Tue</code><ul><li>做 <code>1</code> 道 afternoon case，限时作答</li></ul></li><li><code>5/13 Wed</code><ul><li>回顾 <code>ACL / NAT / OSPF / DNS / DHCP</code></li><li>做 <code>20</code> 道专题题</li></ul></li><li><code>5/14 Thu</code><ul><li>做 <code>1</code> 套 <code>mini mock</code>：morning <code>30-40</code> 题 + <code>1</code> 道 afternoon</li></ul></li><li><code>5/15 Fri</code><ul><li>复盘整周错题</li><li>完成最终版 <code>common ports + protocol comparison</code> 速记表</li></ul></li></ul><h3 id="End-of-Week-Check-6"><a href="#End-of-Week-Check-6" class="headerlink" title="End-of-Week Check"></a>End-of-Week Check</h3><ul><li>morning 做题速度明显提升</li><li>afternoon 不再只会看答案</li><li>高频知识点开始成体系</li></ul><hr><h2 id="Week-8：5-18-Mon-5-22-Fri"><a href="#Week-8：5-18-Mon-5-22-Fri" class="headerlink" title="Week 8：5/18 Mon - 5/22 Fri"></a>Week 8：<code>5/18 Mon - 5/22 Fri</code></h2><h3 id="Goal-8"><a href="#Goal-8" class="headerlink" title="Goal"></a>Goal</h3><ul><li>只收口，不扩张</li><li>把已学内容压成可复述、可作答、可拿分的状态</li></ul><h3 id="Plan-8"><a href="#Plan-8" class="headerlink" title="Plan"></a>Plan</h3><ul><li><code>5/18 Mon</code><ul><li>做最后 <code>1</code> 套完整 <code>morning</code> 真题</li><li>只记高频错点</li></ul></li><li><code>5/19 Tue</code><ul><li>做 <code>1</code> 道 afternoon case</li><li>复盘答案模板</li></ul></li><li><code>5/20 Wed</code><ul><li>重点回顾：<code>subnetting / VLAN / STP / OSPF / ACL / NAT</code></li></ul></li><li><code>5/21 Thu</code><ul><li>重点回顾：<code>DNS / DHCP / HTTP / Mail / troubleshooting / design</code></li></ul></li><li><code>5/22 Fri</code><ul><li>只看自己的 <code>wrong questions + notes + formulas + ports</code></li><li>不再开新专题</li><li>提前调整作息</li></ul></li></ul><h3 id="Exam-Eve-Rules"><a href="#Exam-Eve-Rules" class="headerlink" title="Exam-Eve Rules"></a>Exam-Eve Rules</h3><ul><li>不熬夜</li><li>不刷偏题、怪题</li><li>不再看大段新视频</li><li>相信你已经完成的复习闭环</li></ul><hr><h2 id="六、把知识串起来：一张紧凑的知识地图"><a href="#六、把知识串起来：一张紧凑的知识地图" class="headerlink" title="六、把知识串起来：一张紧凑的知识地图"></a>六、把知识串起来：一张紧凑的知识地图</h2><h3 id="Layer-2-Layer-3"><a href="#Layer-2-Layer-3" class="headerlink" title="Layer 2 &#x2F; Layer 3"></a>Layer 2 &#x2F; Layer 3</h3><ul><li><code>Ethernet</code></li><li><code>MAC / ARP</code></li><li><code>VLAN / Trunk</code></li><li><code>STP</code></li><li><code>routing / forwarding</code></li></ul><h3 id="Addressing"><a href="#Addressing" class="headerlink" title="Addressing"></a>Addressing</h3><ul><li><code>IPv4</code></li><li><code>subnetting</code></li><li><code>VLSM</code></li><li><code>CIDR</code></li><li><code>route summarization</code></li><li><code>IPv6 basics</code></li></ul><h3 id="Routing"><a href="#Routing" class="headerlink" title="Routing"></a>Routing</h3><ul><li><code>static route</code></li><li><code>default route</code></li><li><code>RIP</code></li><li><code>OSPF</code></li><li><code>BGP basics</code></li></ul><h3 id="Security"><a href="#Security" class="headerlink" title="Security"></a>Security</h3><ul><li><code>ACL</code></li><li><code>NAT / PAT</code></li><li><code>firewall</code></li><li><code>VPN</code></li><li>常见攻击与基础防护思路</li></ul><h3 id="Services"><a href="#Services" class="headerlink" title="Services"></a>Services</h3><ul><li><code>DNS</code></li><li><code>DHCP</code></li><li><code>HTTP / HTTPS</code></li><li><code>SMTP / POP3 / IMAP</code></li><li><code>FTP</code></li><li><code>proxy</code></li><li><code>SNMP / Syslog</code></li></ul><h3 id="Design-Ops"><a href="#Design-Ops" class="headerlink" title="Design &#x2F; Ops"></a>Design &#x2F; Ops</h3><ul><li><code>topology</code></li><li><code>core / distribution / access</code></li><li><code>redundancy / HA</code></li><li><code>load balancing</code></li><li><code>troubleshooting</code></li><li><code>RAID</code></li><li><code>cabling / media</code></li></ul><h3 id="这些东西要明确背，不要只靠“理解过”"><a href="#这些东西要明确背，不要只靠“理解过”" class="headerlink" title="这些东西要明确背，不要只靠“理解过”"></a>这些东西要明确背，不要只靠“理解过”</h3><ul><li>常见服务端口</li><li><code>RIP / OSPF / BGP</code> 对比</li><li><code>ACL</code> 分类和匹配逻辑</li><li><code>NAT / PAT</code> 区别</li><li><code>VLAN / Trunk / STP</code> 关键词</li><li>子网划分计算套路</li><li><code>RAID</code> 级别特点</li><li>常见设备 &#x2F; 介质适用场景</li></ul><hr><h2 id="七、高频知识速记表：把最常考的地方先压缩到脑子里"><a href="#七、高频知识速记表：把最常考的地方先压缩到脑子里" class="headerlink" title="七、高频知识速记表：把最常考的地方先压缩到脑子里"></a>七、高频知识速记表：把最常考的地方先压缩到脑子里</h2><h2 id="1-IPv4-和-Subnetting"><a href="#1-IPv4-和-Subnetting" class="headerlink" title="1. IPv4 和 Subnetting"></a>1. IPv4 和 Subnetting</h2><h3 id="Core-Formulas"><a href="#Core-Formulas" class="headerlink" title="Core Formulas"></a>Core Formulas</h3><ul><li>子网总地址数：<code>2^(32-prefix)</code></li><li>常规可用主机数：<code>2^(32-prefix) - 2</code></li><li>子网步长：找 <code>interesting octet</code>，计算 <code>256 - mask</code></li></ul><h3 id="Fast-Memory-Table"><a href="#Fast-Memory-Table" class="headerlink" title="Fast Memory Table"></a>Fast Memory Table</h3><table><thead><tr><th>Prefix</th><th>Mask</th><th align="right">Total IPs</th><th align="right">Usable Hosts</th></tr></thead><tbody><tr><td><code>/24</code></td><td><code>255.255.255.0</code></td><td align="right">256</td><td align="right">254</td></tr><tr><td><code>/25</code></td><td><code>255.255.255.128</code></td><td align="right">128</td><td align="right">126</td></tr><tr><td><code>/26</code></td><td><code>255.255.255.192</code></td><td align="right">64</td><td align="right">62</td></tr><tr><td><code>/27</code></td><td><code>255.255.255.224</code></td><td align="right">32</td><td align="right">30</td></tr><tr><td><code>/28</code></td><td><code>255.255.255.240</code></td><td align="right">16</td><td align="right">14</td></tr><tr><td><code>/29</code></td><td><code>255.255.255.248</code></td><td align="right">8</td><td align="right">6</td></tr><tr><td><code>/30</code></td><td><code>255.255.255.252</code></td><td align="right">4</td><td align="right">2</td></tr></tbody></table><h3 id="Workflow-for-Subnetting-Questions"><a href="#Workflow-for-Subnetting-Questions" class="headerlink" title="Workflow for Subnetting Questions"></a>Workflow for Subnetting Questions</h3><ol><li>先看每个子网需要多少主机</li><li>选能满足需求的最小前缀</li><li><code>VLSM</code> 题先分配大网段，再分配小网段</li><li>写清楚 <code>network address</code>、<code>usable range</code>、<code>broadcast address</code></li><li>最后检查是否发生 overlap</li></ol><h3 id="High-Frequency-Traps"><a href="#High-Frequency-Traps" class="headerlink" title="High-Frequency Traps"></a>High-Frequency Traps</h3><ul><li>把 <code>network address</code> 和 <code>first usable host</code> 搞混</li><li>忘了 <code>broadcast address</code></li><li><code>VLSM</code> 没有先分配最大的子网</li><li><code>route summarization</code> 既要覆盖所有路由，又要尽量避免多余覆盖</li></ul><h2 id="2-VLAN、Trunk、STP"><a href="#2-VLAN、Trunk、STP" class="headerlink" title="2. VLAN、Trunk、STP"></a>2. VLAN、Trunk、STP</h2><h3 id="VLAN"><a href="#VLAN" class="headerlink" title="VLAN"></a>VLAN</h3><ul><li>逻辑上分割广播域</li><li>提升隔离性、可管理性和安全性</li><li>不同 VLAN 的主机通信需要 <code>Layer 3</code> 转发</li></ul><h3 id="Trunk"><a href="#Trunk" class="headerlink" title="Trunk"></a>Trunk</h3><ul><li>用来承载多个 VLAN 的流量</li><li>一般依赖 tagging</li><li>常见场景是交换机与交换机之间的链路</li></ul><h3 id="STP"><a href="#STP" class="headerlink" title="STP"></a>STP</h3><ul><li>作用：防止二层环路</li><li>核心思路：阻塞冗余路径，但保留备份链路</li><li>必记术语：<code>root bridge</code>、<code>root port</code>、<code>designated port</code>、<code>blocking</code></li></ul><h3 id="Common-Traps"><a href="#Common-Traps" class="headerlink" title="Common Traps"></a>Common Traps</h3><ul><li><code>VLAN</code> 解决的是分段，不会自动解决跨 VLAN 通信</li><li><code>Trunk</code> 不等于 <code>access port</code></li><li>冗余链路如果没有 <code>STP</code>，可能造成广播风暴和 MAC 表震荡</li></ul><h2 id="3-Routing-Cheat-Sheet"><a href="#3-Routing-Cheat-Sheet" class="headerlink" title="3. Routing Cheat Sheet"></a>3. Routing Cheat Sheet</h2><h3 id="Static-Route"><a href="#Static-Route" class="headerlink" title="Static Route"></a>Static Route</h3><ul><li>简单</li><li>可预测</li><li>开销低</li><li>在大网络里可扩展性差</li></ul><h3 id="RIP"><a href="#RIP" class="headerlink" title="RIP"></a>RIP</h3><ul><li><code>distance-vector</code></li><li>度量：<code>hop count</code></li><li>收敛慢</li><li>只适合小型、简单网络</li></ul><h3 id="OSPF"><a href="#OSPF" class="headerlink" title="OSPF"></a>OSPF</h3><ul><li><code>link-state</code></li><li>比 <code>RIP</code> 收敛更快</li><li>支持 <code>areas</code> 的分层设计</li><li>适合中大型内部网络</li></ul><h3 id="BGP"><a href="#BGP" class="headerlink" title="BGP"></a>BGP</h3><ul><li><code>inter-domain routing</code></li><li>偏 <code>policy-oriented</code></li><li>常见于 <code>ISP / multi-AS scenarios</code></li><li>不是普通企业内部路由的默认答案</li></ul><h3 id="Fast-Comparison-Table"><a href="#Fast-Comparison-Table" class="headerlink" title="Fast Comparison Table"></a>Fast Comparison Table</h3><table><thead><tr><th>Protocol</th><th>Type</th><th>Metric &#x2F; Key Idea</th><th>Typical Use</th></tr></thead><tbody><tr><td><code>Static</code></td><td>manual</td><td>admin control</td><td>small&#x2F;stable networks</td></tr><tr><td><code>RIP</code></td><td>distance-vector</td><td>hop count</td><td>small LAN&#x2F;WAN</td></tr><tr><td><code>OSPF</code></td><td>link-state</td><td>cost</td><td>enterprise internal routing</td></tr><tr><td><code>BGP</code></td><td>path-vector</td><td>policy&#x2F;path attributes</td><td>inter-AS &#x2F; ISP</td></tr></tbody></table><h3 id="High-Frequency-Traps-1"><a href="#High-Frequency-Traps-1" class="headerlink" title="High-Frequency Traps"></a>High-Frequency Traps</h3><ul><li><code>RIP</code> 简单，但不适合大规模网络</li><li><code>OSPF</code> 通常用于内部，<code>BGP</code> 通常用于域间</li><li>不要脱离场景写出“<code>BGP</code> 比 <code>OSPF</code> 更好”这种答案</li></ul><h2 id="4-ACL、NAT、Firewall、VPN"><a href="#4-ACL、NAT、Firewall、VPN" class="headerlink" title="4. ACL、NAT、Firewall、VPN"></a>4. ACL、NAT、Firewall、VPN</h2><h3 id="ACL"><a href="#ACL" class="headerlink" title="ACL"></a>ACL</h3><ul><li>做流量过滤和访问控制</li><li>标准 ACL：主要按源地址匹配</li><li>扩展 ACL：可按源、目的、协议、端口匹配</li></ul><h3 id="NAT-PAT"><a href="#NAT-PAT" class="headerlink" title="NAT &#x2F; PAT"></a>NAT &#x2F; PAT</h3><ul><li><code>NAT</code>：私网地址和公网地址之间转换</li><li><code>PAT</code>：多个内网主机共用一个公网 IP，通过不同端口复用</li></ul><h3 id="Firewall"><a href="#Firewall" class="headerlink" title="Firewall"></a>Firewall</h3><ul><li>执行安全策略</li><li>按规则允许或拒绝流量</li><li>常部署在网络边界或不同安全域之间</li></ul><h3 id="VPN"><a href="#VPN" class="headerlink" title="VPN"></a>VPN</h3><ul><li>在公共网络之上建立安全隧道</li><li>可用于 site-to-site 或 remote access</li></ul><h3 id="High-Frequency-Traps-2"><a href="#High-Frequency-Traps-2" class="headerlink" title="High-Frequency Traps"></a>High-Frequency Traps</h3><ul><li><code>ACL</code> 过滤流量，不等于 <code>NAT</code></li><li>多个私网主机共享少量公网 IP 时，<code>PAT</code> 往往才是题目的实际答案</li><li>远程管理里 <code>telnet</code> 不安全，<code>SSH</code> 更安全</li></ul><h2 id="5-Services-and-Servers"><a href="#5-Services-and-Servers" class="headerlink" title="5. Services and Servers"></a>5. Services and Servers</h2><h3 id="DNS"><a href="#DNS" class="headerlink" title="DNS"></a>DNS</h3><ul><li>域名解析</li><li>必记：<code>UDP/TCP 53</code></li><li>高频考法：cache、recursion、authoritative answer、DNS 故障症状</li></ul><h3 id="DHCP"><a href="#DHCP" class="headerlink" title="DHCP"></a>DHCP</h3><ul><li>自动分配 IP</li><li>必记：<code>UDP 67/68</code></li><li>关键概念：lease、pool、gateway、DNS server options、relay</li></ul><h3 id="HTTP-HTTPS"><a href="#HTTP-HTTPS" class="headerlink" title="HTTP &#x2F; HTTPS"></a>HTTP &#x2F; HTTPS</h3><ul><li>Web 服务</li><li>必记：<code>80 / 443</code></li><li><code>HTTPS</code> 在 <code>HTTP</code> 基础上增加了 TLS 加密和身份校验</li></ul><h3 id="Mail"><a href="#Mail" class="headerlink" title="Mail"></a>Mail</h3><ul><li><code>SMTP</code>：发信</li><li><code>POP3</code>：收信，偏简单下载模型</li><li><code>IMAP</code>：收信，偏服务器端邮箱同步</li></ul><h3 id="SNMP-Syslog"><a href="#SNMP-Syslog" class="headerlink" title="SNMP &#x2F; Syslog"></a>SNMP &#x2F; Syslog</h3><ul><li><code>SNMP</code>：监控和管理</li><li><code>Syslog</code>：集中日志传输</li></ul><h3 id="RAID-Basics"><a href="#RAID-Basics" class="headerlink" title="RAID Basics"></a>RAID Basics</h3><table><thead><tr><th>RAID</th><th>Key Feature</th></tr></thead><tbody><tr><td><code>RAID 0</code></td><td>striping，性能好，但无冗余</td></tr><tr><td><code>RAID 1</code></td><td>mirroring，有冗余</td></tr><tr><td><code>RAID 5</code></td><td>striping + parity，容量和容错平衡</td></tr><tr><td><code>RAID 10</code></td><td>性能和冗余兼顾，但成本更高</td></tr></tbody></table><h2 id="6-Design-and-Troubleshooting"><a href="#6-Design-and-Troubleshooting" class="headerlink" title="6. Design and Troubleshooting"></a>6. Design and Troubleshooting</h2><h3 id="Design-Keywords"><a href="#Design-Keywords" class="headerlink" title="Design Keywords"></a>Design Keywords</h3><ul><li><code>availability</code></li><li><code>redundancy</code></li><li><code>scalability</code></li><li><code>security</code></li><li><code>manageability</code></li><li><code>performance</code></li></ul><h3 id="Typical-Topology-Terms"><a href="#Typical-Topology-Terms" class="headerlink" title="Typical Topology Terms"></a>Typical Topology Terms</h3><ul><li><code>core layer</code></li><li><code>distribution layer</code></li><li><code>access layer</code></li></ul><h3 id="Troubleshooting-Method"><a href="#Troubleshooting-Method" class="headerlink" title="Troubleshooting Method"></a>Troubleshooting Method</h3><ol><li>先定义清楚症状</li><li>判断影响范围：单主机、单 VLAN、单子网、单服务还是全网</li><li>必要时先看物理连通性</li><li>按顺序排查：addressing、gateway、VLAN、ACL、routing、DNS</li><li>验证修复前后状态</li></ol><h3 id="Common-Fault-Paths"><a href="#Common-Fault-Paths" class="headerlink" title="Common Fault Paths"></a>Common Fault Paths</h3><ul><li>域名访问不了，但 IP 能访问：大概率是 <code>DNS</code></li><li>本地子网正常，跨子网失败：优先查 gateway &#x2F; routing &#x2F; ACL</li><li>间歇性环路或广播风暴：优先怀疑二层冗余没有正确跑 <code>STP</code></li><li>内网用户无法出网：优先查 <code>NAT</code>、default route、firewall policy</li></ul><h2 id="7-Cabling-and-Media"><a href="#7-Cabling-and-Media" class="headerlink" title="7. Cabling and Media"></a>7. Cabling and Media</h2><p>不用深挖到物理层细枝末节，但方向要清楚：</p><ul><li>双绞线：常见于接入层 LAN</li><li>光纤：距离更长、抗干扰更强，常用于骨干或上联</li><li>介质选型看：<code>distance</code>、<code>bandwidth</code>、<code>cost</code>、<code>environment</code>、<code>anti-interference</code></li></ul><hr><h2 id="八、端口与协议清单：这一块最适合考前反复默写"><a href="#八、端口与协议清单：这一块最适合考前反复默写" class="headerlink" title="八、端口与协议清单：这一块最适合考前反复默写"></a>八、端口与协议清单：这一块最适合考前反复默写</h2><h2 id="1-Must-Memorize-Ports"><a href="#1-Must-Memorize-Ports" class="headerlink" title="1. Must-Memorize Ports"></a>1. Must-Memorize Ports</h2><table><thead><tr><th>Service</th><th>Port &#x2F; Protocol</th><th>Notes</th></tr></thead><tbody><tr><td><code>FTP data</code></td><td><code>20/TCP</code></td><td>active mode data channel</td></tr><tr><td><code>FTP control</code></td><td><code>21/TCP</code></td><td>control channel</td></tr><tr><td><code>SSH</code></td><td><code>22/TCP</code></td><td>secure remote login</td></tr><tr><td><code>Telnet</code></td><td><code>23/TCP</code></td><td>insecure remote login</td></tr><tr><td><code>SMTP</code></td><td><code>25/TCP</code></td><td>mail sending</td></tr><tr><td><code>DNS</code></td><td><code>53/UDP,TCP</code></td><td>UDP 用于常规查询，TCP 常见于 zone transfer 或大响应</td></tr><tr><td><code>DHCP server</code></td><td><code>67/UDP</code></td><td>server side</td></tr><tr><td><code>DHCP client</code></td><td><code>68/UDP</code></td><td>client side</td></tr><tr><td><code>TFTP</code></td><td><code>69/UDP</code></td><td>simple file transfer</td></tr><tr><td><code>HTTP</code></td><td><code>80/TCP</code></td><td>web</td></tr><tr><td><code>POP3</code></td><td><code>110/TCP</code></td><td>receive mail</td></tr><tr><td><code>NTP</code></td><td><code>123/UDP</code></td><td>time sync</td></tr><tr><td><code>IMAP</code></td><td><code>143/TCP</code></td><td>receive mail, synchronized mailbox</td></tr><tr><td><code>SNMP</code></td><td><code>161/UDP</code></td><td>management&#x2F;query</td></tr><tr><td><code>SNMP Trap</code></td><td><code>162/UDP</code></td><td>unsolicited event&#x2F;alert</td></tr><tr><td><code>BGP</code></td><td><code>179/TCP</code></td><td>routing between ASes</td></tr><tr><td><code>LDAP</code></td><td><code>389/TCP,UDP</code></td><td>directory service</td></tr><tr><td><code>HTTPS</code></td><td><code>443/TCP</code></td><td>encrypted web</td></tr><tr><td><code>SMTPS</code></td><td><code>465/TCP</code></td><td>secure SMTP，部分题目或资料中会出现</td></tr><tr><td><code>Syslog</code></td><td><code>514/UDP</code></td><td>centralized logs</td></tr><tr><td><code>RIP</code></td><td><code>520/UDP</code></td><td>routing update protocol</td></tr><tr><td><code>RIPng</code></td><td><code>521/UDP</code></td><td>RIP for IPv6</td></tr><tr><td><code>DHCPv6 client</code></td><td><code>546/UDP</code></td><td>IPv6 DHCP client</td></tr><tr><td><code>DHCPv6 server</code></td><td><code>547/UDP</code></td><td>IPv6 DHCP server</td></tr></tbody></table><h2 id="2-Very-Common-Exam-Traps"><a href="#2-Very-Common-Exam-Traps" class="headerlink" title="2. Very Common Exam Traps"></a>2. Very Common Exam Traps</h2><h3 id="DNS-1"><a href="#DNS-1" class="headerlink" title="DNS"></a>DNS</h3><ul><li>不要只背 <code>53/UDP</code></li><li><code>DNS</code> 也会用 <code>TCP 53</code></li><li>典型考法包括 <code>zone transfer</code> 和大响应场景</li></ul><h3 id="FTP"><a href="#FTP" class="headerlink" title="FTP"></a>FTP</h3><ul><li><code>21</code> 是控制连接</li><li><code>20</code> 是主动模式下的数据连接</li><li>如果题目问得比较宽，不要只写一个端口而不加上下文</li></ul><h3 id="Mail-1"><a href="#Mail-1" class="headerlink" title="Mail"></a>Mail</h3><ul><li><code>SMTP</code> 负责发送</li><li><code>POP3</code> 和 <code>IMAP</code> 负责接收</li><li><code>IMAP</code> 更适合多设备同步邮箱</li></ul><h3 id="Remote-Management"><a href="#Remote-Management" class="headerlink" title="Remote Management"></a>Remote Management</h3><ul><li><code>Telnet</code> 是明文，不安全</li><li><code>SSH</code> 才是更安全的管理方案</li></ul><h2 id="3-Protocols-Without-a-Classic-Port-Number"><a href="#3-Protocols-Without-a-Classic-Port-Number" class="headerlink" title="3. Protocols Without a Classic Port Number"></a>3. Protocols Without a Classic Port Number</h2><p>有些协议不是靠 TCP&#x2F;UDP 端口来识别，而是靠 <code>IP protocol number</code>。</p><table><thead><tr><th>Protocol</th><th>Identifier</th><th>Notes</th></tr></thead><tbody><tr><td><code>ICMP</code></td><td><code>IP protocol 1</code></td><td>error reporting, diagnostics</td></tr><tr><td><code>TCP</code></td><td><code>IP protocol 6</code></td><td>connection-oriented transport</td></tr><tr><td><code>UDP</code></td><td><code>IP protocol 17</code></td><td>connectionless transport</td></tr><tr><td><code>GRE</code></td><td><code>IP protocol 47</code></td><td>tunneling</td></tr><tr><td><code>ESP</code></td><td><code>IP protocol 50</code></td><td>IPsec encryption</td></tr><tr><td><code>AH</code></td><td><code>IP protocol 51</code></td><td>IPsec authentication</td></tr><tr><td><code>OSPF</code></td><td><code>IP protocol 89</code></td><td>link-state routing</td></tr></tbody></table><p>这一点很重要，因为题目很喜欢把“端口号”和“IP 协议号”混着考。</p><h2 id="4-Transport-and-Core-Internet-Protocols"><a href="#4-Transport-and-Core-Internet-Protocols" class="headerlink" title="4. Transport and Core Internet Protocols"></a>4. Transport and Core Internet Protocols</h2><table><thead><tr><th>Protocol</th><th>Layer &#x2F; Type</th><th>Key Feature</th><th>Common Exam Angle</th></tr></thead><tbody><tr><td><code>ARP</code></td><td>Layer 2&#x2F;3 boundary</td><td>map IP to MAC</td><td>local network resolution</td></tr><tr><td><code>ICMP</code></td><td>network</td><td>control and diagnostics</td><td><code>ping</code>、error report</td></tr><tr><td><code>TCP</code></td><td>transport</td><td>reliable, ordered, connection-oriented</td><td>web、mail、SSH</td></tr><tr><td><code>UDP</code></td><td>transport</td><td>fast, no connection, lower overhead</td><td>DNS、DHCP、SNMP、RIP</td></tr></tbody></table><h3 id="TCP-vs-UDP"><a href="#TCP-vs-UDP" class="headerlink" title="TCP vs UDP"></a>TCP vs UDP</h3><table><thead><tr><th>Item</th><th><code>TCP</code></th><th><code>UDP</code></th></tr></thead><tbody><tr><td>connection</td><td>yes</td><td>no</td></tr><tr><td>reliability</td><td>yes</td><td>no built-in reliability</td></tr><tr><td>ordering</td><td>yes</td><td>no</td></tr><tr><td>overhead</td><td>higher</td><td>lower</td></tr><tr><td>typical services</td><td>HTTP, HTTPS, SSH, SMTP, POP3, IMAP, BGP</td><td>DNS, DHCP, SNMP, RIP, TFTP, NTP</td></tr></tbody></table><p>高频判断题思路：</p><ul><li>当可靠性、顺序性重要时，优先想到 <code>TCP</code></li><li>当低开销、速度更重要，且应用层能容忍或处理丢包时，优先想到 <code>UDP</code></li></ul><h2 id="5-Layer-2-and-Switching-Protocols"><a href="#5-Layer-2-and-Switching-Protocols" class="headerlink" title="5. Layer 2 and Switching Protocols"></a>5. Layer 2 and Switching Protocols</h2><table><thead><tr><th>Protocol &#x2F; Tech</th><th>Main Role</th><th>What To Say In Answers</th></tr></thead><tbody><tr><td><code>Ethernet</code></td><td>LAN framing and access</td><td>basic LAN data transmission</td></tr><tr><td><code>VLAN</code></td><td>logical segmentation</td><td>reduce broadcast domain, improve isolation</td></tr><tr><td><code>Trunk</code></td><td>carry multiple VLANs</td><td>inter-switch multi-VLAN transport</td></tr><tr><td><code>STP</code></td><td>loop prevention</td><td>avoid Layer 2 loop, keep redundancy</td></tr><tr><td><code>LACP</code> &#x2F; link aggregation</td><td>bundle links</td><td>increase bandwidth and&#x2F;or redundancy</td></tr></tbody></table><p>Common trap：</p><ul><li><code>VLAN</code> 本身不提供跨 VLAN 通信，跨 VLAN 仍然需要 <code>Layer 3 forwarding</code></li></ul><h2 id="6-Routing-Protocol-Comparison"><a href="#6-Routing-Protocol-Comparison" class="headerlink" title="6. Routing Protocol Comparison"></a>6. Routing Protocol Comparison</h2><table><thead><tr><th>Protocol</th><th>Category</th><th>Transport &#x2F; Identifier</th><th>Key Metric &#x2F; Idea</th><th>Typical Scenario</th></tr></thead><tbody><tr><td><code>RIP</code></td><td>distance-vector</td><td><code>UDP 520</code></td><td>hop count</td><td>small&#x2F;simple network</td></tr><tr><td><code>OSPF</code></td><td>link-state</td><td><code>IP protocol 89</code></td><td>cost, SPF</td><td>enterprise internal routing</td></tr><tr><td><code>BGP</code></td><td>path-vector</td><td><code>TCP 179</code></td><td>policy&#x2F;path attributes</td><td>inter-AS &#x2F; ISP</td></tr></tbody></table><h3 id="Quick-Notes"><a href="#Quick-Notes" class="headerlink" title="Quick Notes"></a>Quick Notes</h3><ul><li><code>RIP</code>：简单，但收敛慢、规模受限</li><li><code>OSPF</code>：收敛更快，支持 area 分层</li><li><code>BGP</code>：强调 route policy 和域间控制，不是企业内部路由的默认答案</li></ul><h2 id="7-Security-Protocols-and-Concepts"><a href="#7-Security-Protocols-and-Concepts" class="headerlink" title="7. Security Protocols and Concepts"></a>7. Security Protocols and Concepts</h2><table><thead><tr><th>Item</th><th>Identifier &#x2F; Port</th><th>Key Point</th></tr></thead><tbody><tr><td><code>SSH</code></td><td><code>22/TCP</code></td><td>secure remote management</td></tr><tr><td><code>Telnet</code></td><td><code>23/TCP</code></td><td>insecure plaintext management</td></tr><tr><td><code>HTTPS</code></td><td><code>443/TCP</code></td><td>encrypted web access</td></tr><tr><td><code>AH</code></td><td><code>IP protocol 51</code></td><td>IPsec authentication&#x2F;integrity</td></tr><tr><td><code>ESP</code></td><td><code>IP protocol 50</code></td><td>IPsec encryption + protection</td></tr><tr><td><code>VPN</code></td><td>varies</td><td>secure tunnel over public network</td></tr><tr><td><code>ACL</code></td><td>no fixed port</td><td>traffic filtering policy</td></tr><tr><td><code>NAT/PAT</code></td><td>no fixed port</td><td>address translation</td></tr><tr><td><code>Firewall</code></td><td>no fixed port</td><td>policy enforcement and traffic control</td></tr></tbody></table><p>High-frequency trap：</p><ul><li><code>ACL</code> 和 <code>firewall</code> 都能控流，但两者不是同一个概念</li><li><code>NAT</code> 不是安全策略本身，它更多是地址转换，最多只是“隐藏内部地址”</li></ul><h2 id="8-Network-Services"><a href="#8-Network-Services" class="headerlink" title="8. Network Services"></a>8. Network Services</h2><table><thead><tr><th>Service</th><th>Protocol &#x2F; Port</th><th>What Questions Often Test</th></tr></thead><tbody><tr><td><code>DNS</code></td><td><code>53/UDP,TCP</code></td><td>name resolution, cache, zone transfer, DNS faults</td></tr><tr><td><code>DHCP</code></td><td><code>67/68 UDP</code></td><td>automatic addressing, lease, relay</td></tr><tr><td><code>HTTP</code></td><td><code>80/TCP</code></td><td>web access</td></tr><tr><td><code>HTTPS</code></td><td><code>443/TCP</code></td><td>encrypted web access</td></tr><tr><td><code>SMTP</code></td><td><code>25/TCP</code></td><td>send mail</td></tr><tr><td><code>POP3</code></td><td><code>110/TCP</code></td><td>receive mail</td></tr><tr><td><code>IMAP</code></td><td><code>143/TCP</code></td><td>synchronized mailbox access</td></tr><tr><td><code>SNMP</code></td><td><code>161/162 UDP</code></td><td>monitoring and management</td></tr><tr><td><code>Syslog</code></td><td><code>514/UDP</code></td><td>centralized logging</td></tr><tr><td><code>NTP</code></td><td><code>123/UDP</code></td><td>time synchronization</td></tr><tr><td><code>TFTP</code></td><td><code>69/UDP</code></td><td>simple file transfer, device configs&#x2F;images in some contexts</td></tr></tbody></table><h2 id="9-Easy-Confusion-Pairs"><a href="#9-Easy-Confusion-Pairs" class="headerlink" title="9. Easy Confusion Pairs"></a>9. Easy Confusion Pairs</h2><h3 id="HTTP-vs-HTTPS"><a href="#HTTP-vs-HTTPS" class="headerlink" title="HTTP vs HTTPS"></a><code>HTTP</code> vs <code>HTTPS</code></h3><ul><li><code>HTTP</code>：明文 Web 传输</li><li><code>HTTPS</code>：基于 TLS 的加密 Web 传输</li></ul><h3 id="POP3-vs-IMAP"><a href="#POP3-vs-IMAP" class="headerlink" title="POP3 vs IMAP"></a><code>POP3</code> vs <code>IMAP</code></h3><ul><li><code>POP3</code>：更偏简单下载模型</li><li><code>IMAP</code>：更适合多设备同步邮箱</li></ul><h3 id="RIP-vs-OSPF"><a href="#RIP-vs-OSPF" class="headerlink" title="RIP vs OSPF"></a><code>RIP</code> vs <code>OSPF</code></h3><ul><li><code>RIP</code>：hop count，简单，小型网络</li><li><code>OSPF</code>：link-state，收敛更快，适合更大的内部网络</li></ul><h3 id="OSPF-vs-BGP"><a href="#OSPF-vs-BGP" class="headerlink" title="OSPF vs BGP"></a><code>OSPF</code> vs <code>BGP</code></h3><ul><li><code>OSPF</code>：组织内部 &#x2F; 网络域内部路由</li><li><code>BGP</code>：自治系统之间路由</li></ul><h3 id="ACL-vs-NAT"><a href="#ACL-vs-NAT" class="headerlink" title="ACL vs NAT"></a><code>ACL</code> vs <code>NAT</code></h3><ul><li><code>ACL</code>：filter &#x2F; control</li><li><code>NAT</code>：translate addresses</li></ul><h3 id="Telnet-vs-SSH"><a href="#Telnet-vs-SSH" class="headerlink" title="Telnet vs SSH"></a><code>Telnet</code> vs <code>SSH</code></h3><ul><li><code>Telnet</code>：明文，不安全</li><li><code>SSH</code>：加密，优先选择</li></ul><h2 id="10-What-To-Recite-Before-Exam-Day"><a href="#10-What-To-Recite-Before-Exam-Day" class="headerlink" title="10. What To Recite Before Exam Day"></a>10. What To Recite Before Exam Day</h2><p>如果考前只剩 <code>10</code> 分钟，把这些快速背一遍：</p><ul><li><code>20/21 FTP</code>、<code>22 SSH</code>、<code>23 Telnet</code>、<code>25 SMTP</code></li><li><code>53 DNS</code>、<code>67/68 DHCP</code>、<code>69 TFTP</code>、<code>80 HTTP</code></li><li><code>110 POP3</code>、<code>123 NTP</code>、<code>143 IMAP</code></li><li><code>161/162 SNMP</code>、<code>179 BGP</code>、<code>443 HTTPS</code>、<code>514 Syslog</code>、<code>520 RIP</code></li><li><code>OSPF = IP protocol 89</code></li><li><code>AH = 51</code>、<code>ESP = 50</code>、<code>GRE = 47</code></li></ul><hr><h2 id="九、下午题怎么写：不要写成“我觉得大概是这样”"><a href="#九、下午题怎么写：不要写成“我觉得大概是这样”" class="headerlink" title="九、下午题怎么写：不要写成“我觉得大概是这样”"></a>九、下午题怎么写：不要写成“我觉得大概是这样”</h2><p>下午题尽量固定成这个结构：</p><ol><li><code>What is the problem / goal?</code></li><li><code>Which protocol / technology should be used?</code></li><li><code>Why is it suitable here?</code></li><li><code>What result / effect does it achieve?</code></li></ol><h3 id="Example-Skeleton"><a href="#Example-Skeleton" class="headerlink" title="Example Skeleton"></a>Example Skeleton</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">该问题需要解决的是 ______。</span><br><span class="line">可采用 ______ 技术/协议。</span><br><span class="line">原因是：它能够 ______，并且适用于 ______ 场景。</span><br><span class="line">实施后可实现 ______，从而提高 ______。</span><br></pre></td></tr></table></figure><p>这个模板可以反复套进这些高频题：</p><ul><li><code>VLAN / Trunk</code></li><li><code>OSPF</code></li><li><code>ACL</code></li><li><code>NAT</code></li><li><code>DNS / DHCP</code></li><li><code>redundancy / backup design</code></li></ul><h3 id="Afternoon-Keywords-You-Should-Reuse"><a href="#Afternoon-Keywords-You-Should-Reuse" class="headerlink" title="Afternoon Keywords You Should Reuse"></a>Afternoon Keywords You Should Reuse</h3><p>答题时能复用就复用这些词：</p><ul><li><code>reduce broadcast domain</code></li><li><code>improve security isolation</code></li><li><code>provide redundancy</code></li><li><code>improve availability</code></li><li><code>speed up convergence</code></li><li><code>support scalable network design</code></li><li><code>centralized management</code></li><li><code>automatic address assignment</code></li><li><code>secure remote access</code></li><li><code>control traffic by policy</code></li></ul><p>我的补充建议是：不要追求句子华丽，先保证关键词到位。很多下午题你写不出完整论文没关系，但一定要让阅卷人看到“你知道问题、知道技术、知道原因、知道结果”。</p><hr><h2 id="十、最后两三周怎么复习：把知识压到“可调用”状态"><a href="#十、最后两三周怎么复习：把知识压到“可调用”状态" class="headerlink" title="十、最后两三周怎么复习：把知识压到“可调用”状态"></a>十、最后两三周怎么复习：把知识压到“可调用”状态</h2><p>这部分最适合搭配上面的速记表使用。</p><h3 id="Last-Week-Review-Checklist"><a href="#Last-Week-Review-Checklist" class="headerlink" title="Last-Week Review Checklist"></a>Last-Week Review Checklist</h3><p>考前一周，确认自己能快速回答这些问题：</p><ul><li>你能不能不慌地做 <code>subnetting</code>？</li><li>你能不能用自己的话解释 <code>VLAN</code>、<code>Trunk</code>、<code>STP</code>？</li><li>你能不能比较 <code>RIP</code>、<code>OSPF</code>、<code>BGP</code>？</li><li>你能不能说清 <code>ACL</code>、<code>NAT</code>、<code>PAT</code>、<code>firewall</code>、<code>VPN</code> 的区别？</li><li>你能不能快速回忆常见端口和服务职责？</li><li>你能不能按 <code>problem -&gt; solution -&gt; reason -&gt; result</code> 写出一段简短下午题答案？</li></ul><p>如果这些你都能做到，说明核心过线能力已经基本具备。</p><h3 id="If-You-Fall-Behind"><a href="#If-You-Fall-Behind" class="headerlink" title="If You Fall Behind"></a>If You Fall Behind</h3><p>如果某一周被工作打乱，不要慌，按照这个优先级保底。</p><h4 id="Never-Drop"><a href="#Never-Drop" class="headerlink" title="Never Drop"></a>Never Drop</h4><ul><li><code>subnetting</code></li><li><code>VLAN / STP</code></li><li><code>static / RIP / OSPF / BGP basics</code></li><li><code>ACL / NAT</code></li><li><code>DNS / DHCP</code></li><li><code>afternoon case practice</code></li></ul><h4 id="Can-Be-Simplified"><a href="#Can-Be-Simplified" class="headerlink" title="Can Be Simplified"></a>Can Be Simplified</h4><ul><li><code>wireless</code></li><li><code>IPv6 details</code></li><li><code>SDN / 5G / cloud overview</code></li><li>比较偏的管理类或边缘新概念</li></ul><h4 id="Recovery-Strategy"><a href="#Recovery-Strategy" class="headerlink" title="Recovery Strategy"></a>Recovery Strategy</h4><ul><li>工作日只保留 <code>1h</code> 也没关系，但不要断线</li><li>真断线时，至少完成：<code>10</code> 道题 + <code>10</code> 分钟复盘</li><li>追赶进度时优先补 <code>real questions</code>，不要先补长视频</li></ul><h3 id="Exam-Eve-Rules-1"><a href="#Exam-Eve-Rules-1" class="headerlink" title="Exam-Eve Rules"></a>Exam-Eve Rules</h3><ul><li>不熬夜</li><li>不刷偏题、怪题</li><li>不再看大段新视频</li><li>只看自己的 <code>wrong questions + notes + formulas + ports</code></li><li>相信你已经完成的复习闭环</li></ul><hr><h2 id="十一、临门一脚：6-个自测问题"><a href="#十一、临门一脚：6-个自测问题" class="headerlink" title="十一、临门一脚：6 个自测问题"></a>十一、临门一脚：6 个自测问题</h2><p>如果你不看笔记就能快速答出下面这些，这份清单就算发挥作用了：</p><ol><li><code>BGP</code> 用什么端口？</li><li>为什么 <code>DNS</code> 既可能用 <code>UDP</code>，也可能用 <code>TCP</code>？</li><li>设备远程管理更安全的是 <code>Telnet</code> 还是 <code>SSH</code>？</li><li><code>POP3</code> 和 <code>IMAP</code> 的核心区别是什么？</li><li><code>OSPF</code> 是靠 TCP&#x2F;UDP 端口识别，还是靠 IP 协议号识别？</li><li><code>ACL</code> 和 <code>NAT</code> 的核心区别是什么？</li></ol><hr><h2 id="十二、最后的建议"><a href="#十二、最后的建议" class="headerlink" title="十二、最后的建议"></a>十二、最后的建议</h2><ul><li>如果你本身就有多年开发经验，理解力通常不是问题，真正的问题往往是考试表达和体系化记忆</li><li>不要用“我懂一点网络”替代“我会做真题”</li><li>只要把 <code>subnetting + switching + routing + ACL/NAT + DNS/DHCP + afternoon case</code> 这条主线打通，过线概率就不低</li><li>即使这次没过，下次也不是从零开始，因为你的复习框架已经搭好了</li></ul><p>我再补一句最现实的话：这门考试对在职备考的人来说，最怕的不是难，而是断。哪怕某几天只能学 <code>1</code> 小时，也尽量别让链条断掉。稳定、小步、真题驱动，通常比“周末爆肝一次然后一周不碰”有效得多。</p><p>如果你真准备开始动手，今天晚上就做三件事：</p><ol><li>建好 <code>wrong questions</code>、<code>topic notes</code>、<code>formula sheet</code> 三份笔记。</li><li>先做 <code>20-30</code> 道 morning 题，摸清自己弱点。</li><li>开始做第一张 <code>common ports</code> 速记表。</li></ol>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20260325/learning/ruankao-network-engineer-study-guide/</id>
    <link href="https://minniexcode.github.io/memoirs/20260325/learning/ruankao-network-engineer-study-guide/"/>
    <published>2026-03-24T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>如果你和我一样，白天上班，真正能拿出来备考的时间只有工作日晚间 <code>2-3 小时</code>，同时又不是网络科班出身，而是有开发经验、懂一些网络但没有系统梳理过，那么软考 <code>Network Engineer</code> 的难点通常不是“完全看不懂”，而是：知识点很散、真题节奏不熟、下午案例题写不出关键词。</p>
<p>这篇文章把三份资料合成了一篇可以直接照着执行的长文：</p>
<ul>
<li><code>8 周备考计划</code></li>
<li><code>高频知识速记表</code></li>
<li><code>端口 + 协议 + 易混点清单</code></li>
</ul>
<p>目标不是追求高分，而是尽量在本次考试通过；即使这次没过，也把下一次复习的框架一次搭好。我会保留 <code>English concepts</code> 的组织方式，因为对很多有工程背景的人来说，这种方式更容易形成检索路径。</p>]]>
    </summary>
    <title>软考网络工程师备考全攻略：8 周计划、速记表与端口协议清单</title>
    <updated>2026-03-25T14:50:10.755Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Linux" scheme="https://minniexcode.github.io/memoirs/tags/Linux/"/>
    <category term="Nano" scheme="https://minniexcode.github.io/memoirs/tags/Nano/"/>
    <category term="Vim" scheme="https://minniexcode.github.io/memoirs/tags/Vim/"/>
    <category term="WSL" scheme="https://minniexcode.github.io/memoirs/tags/WSL/"/>
    <category term="SSH" scheme="https://minniexcode.github.io/memoirs/tags/SSH/"/>
    <content>
      <![CDATA[<p>这篇文档按一个更实用的思路重新整理：主角是 <code>nano</code>，使用场景是 WSL、Linux、SSH、远程服务器里的配置文件编辑，<code>vim</code> 只保留一个很短的附录，不再放在中间打断阅读。</p><p>如果你的真实需求是改 <code>~/.bashrc</code>、<code>~/.zshrc</code>、<code>~/.gitconfig</code>、<code>/etc/hosts</code>，或者改 <code>ssh</code>、<code>nginx</code>、<code>docker compose</code>、<code>yaml</code>、<code>env</code> 之类的配置，那么 <code>nano</code> 往往比 <code>vim</code> 更适合你。</p><span id="more"></span><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><ol><li>Nano 速查表</li></ol></li><li><ol start="2"><li>为什么 Nano 特别适合改配置文件</li></ol></li><li><ol start="3"><li>Nano 最常用的操作</li></ol></li><li><ol start="4"><li>配置文件场景里的高频用法</li></ol></li><li><ol start="5"><li>常用启动参数</li></ol></li><li><ol start="6"><li>推荐的 <code>.nanorc</code></li></ol></li><li><ol start="7"><li>WSL &#x2F; SSH 里的常见问题</li></ol></li><li><ol start="8"><li>Vim 极简附录</li></ol></li><li><ol start="9"><li>我的建议</li></ol></li></ul><h2 id="1-Nano-速查表"><a href="#1-Nano-速查表" class="headerlink" title="1. Nano 速查表"></a>1. Nano 速查表</h2><p>先看这个。你平时真正高频会用到的，通常就是下面这些。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">打开文件            nano file.txt</span><br><span class="line">跳到指定行          nano +120,8 file.txt</span><br><span class="line">保存                Ctrl+O</span><br><span class="line">退出                Ctrl+X</span><br><span class="line">搜索                Ctrl+W</span><br><span class="line">替换                Ctrl+\</span><br><span class="line">跳行                Ctrl+_</span><br><span class="line">剪切当前行          Ctrl+K</span><br><span class="line">粘贴                Ctrl+U</span><br><span class="line">开始/结束选区       Ctrl+6 或 Alt+A</span><br><span class="line">复制                Alt+6</span><br><span class="line">撤销                Alt+U</span><br><span class="line">重做                Alt+E</span><br><span class="line">帮助                Ctrl+G</span><br><span class="line">只读打开            nano -v file.txt</span><br><span class="line">显示行号            nano -l file.txt</span><br><span class="line">启用鼠标            nano -m file.txt</span><br><span class="line">软换行显示          nano -S file.txt</span><br><span class="line">备份原文件          nano -B file.txt</span><br></pre></td></tr></table></figure><p>如果你只想先记住一套最小命令集，记这 8 个就够用了：</p><ul><li><code>Ctrl+O</code> 保存</li><li><code>Ctrl+X</code> 退出</li><li><code>Ctrl+W</code> 搜索</li><li><code>Ctrl+\</code> 替换</li><li><code>Ctrl+K</code> 剪切当前行</li><li><code>Ctrl+U</code> 粘贴</li><li><code>Ctrl+_</code> 跳行</li><li><code>Ctrl+G</code> 帮助</li></ul><hr><h2 id="2-为什么-Nano-特别适合改配置文件"><a href="#2-为什么-Nano-特别适合改配置文件" class="headerlink" title="2. 为什么 Nano 特别适合改配置文件"></a>2. 为什么 Nano 特别适合改配置文件</h2><p>如果你不是想把终端编辑器当主力代码编辑器，而只是想高效改配置文件，那 <code>nano</code> 的优势非常明显。</p><h3 id="2-1-没有模式切换负担"><a href="#2-1-没有模式切换负担" class="headerlink" title="2.1 没有模式切换负担"></a>2.1 没有模式切换负担</h3><p><code>nano</code> 是无模式编辑器：</p><ul><li>打开就能直接输入</li><li>不需要先按 <code>i</code></li><li>不需要记 <code>:wq</code></li><li>不容易发生“我到底现在在哪个模式”这种问题</li></ul><p>这一点对临时改配置特别重要，因为你追求的是：</p><ul><li>快速打开</li><li>改掉问题</li><li>保存退出</li></ul><p>而不是研究一套复杂命令系统。</p><h3 id="2-2-底部自带快捷键提示"><a href="#2-2-底部自带快捷键提示" class="headerlink" title="2.2 底部自带快捷键提示"></a>2.2 底部自带快捷键提示</h3><p><code>nano</code> 底部通常会直接显示帮助提示，例如：</p><ul><li><code>^O</code> 写入文件</li><li><code>^X</code> 退出</li><li><code>^W</code> 搜索</li></ul><p>这里的：</p><ul><li><code>^</code> 表示 <code>Ctrl</code></li><li><code>M-</code> 表示 <code>Alt</code> &#x2F; <code>Meta</code></li></ul><p>这对配置文件编辑很友好，因为你不需要靠背诵完整命令系统才能开始工作。</p><h3 id="2-3-在-Linux-WSL-SSH-环境里几乎随处可用"><a href="#2-3-在-Linux-WSL-SSH-环境里几乎随处可用" class="headerlink" title="2.3 在 Linux &#x2F; WSL &#x2F; SSH 环境里几乎随处可用"></a>2.3 在 Linux &#x2F; WSL &#x2F; SSH 环境里几乎随处可用</h3><p>很多服务器、容器、WSL 环境里，<code>nano</code> 都更容易直接上手。</p><p>它特别适合这些场景：</p><ul><li>远程机器突然要改一行配置</li><li>用 <code>sudo</code> 编辑系统文件</li><li>改完就退出，不做复杂跳转</li><li>看日志、改 yaml、改 env、改 shell 配置</li></ul><h3 id="2-4-你本来就不打算用终端编辑器写大量代码"><a href="#2-4-你本来就不打算用终端编辑器写大量代码" class="headerlink" title="2.4 你本来就不打算用终端编辑器写大量代码"></a>2.4 你本来就不打算用终端编辑器写大量代码</h3><p>如果你的真实工作流是：</p><ul><li>写代码用专门编辑器，比如 VS Code、JetBrains、Notepad++</li><li>终端里只负责改配置、修小问题</li></ul><p>那 <code>nano</code> 通常更符合你的目标。</p><hr><h2 id="3-Nano-最常用的操作"><a href="#3-Nano-最常用的操作" class="headerlink" title="3. Nano 最常用的操作"></a>3. Nano 最常用的操作</h2><p>这一节只讲配置文件场景里最有用的内容。</p><h3 id="3-1-打开文件"><a href="#3-1-打开文件" class="headerlink" title="3.1 打开文件"></a>3.1 打开文件</h3><p>最常见：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano file.txt</span><br></pre></td></tr></table></figure><p>打开常见配置文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">nano ~/.bashrc</span><br><span class="line">nano ~/.zshrc</span><br><span class="line">nano ~/.gitconfig</span><br><span class="line">nano ~/.ssh/config</span><br></pre></td></tr></table></figure><p>编辑系统文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nano /etc/hosts</span><br><span class="line"><span class="built_in">sudo</span> nano /etc/ssh/sshd_config</span><br><span class="line"><span class="built_in">sudo</span> nano /etc/nginx/nginx.conf</span><br></pre></td></tr></table></figure><p>如果文件不存在，<code>nano</code> 会在保存时创建它。</p><h3 id="3-2-保存"><a href="#3-2-保存" class="headerlink" title="3.2 保存"></a>3.2 保存</h3><p>按：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Ctrl+O</span><br></pre></td></tr></table></figure><p>然后回车确认文件名。</p><h3 id="3-3-退出"><a href="#3-3-退出" class="headerlink" title="3.3 退出"></a>3.3 退出</h3><p>按：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Ctrl+X</span><br></pre></td></tr></table></figure><p>如果文件有改动但没保存，<code>nano</code> 会问你：</p><ul><li><code>Y</code>：保存后退出</li><li><code>N</code>：不保存退出</li><li><code>Ctrl+C</code>：取消这次退出</li></ul><h3 id="3-4-搜索"><a href="#3-4-搜索" class="headerlink" title="3.4 搜索"></a>3.4 搜索</h3><p>按：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Ctrl+W</span><br></pre></td></tr></table></figure><p>输入关键词后回车。</p><p>这是改配置文件时最高频的操作之一，比如查：</p><ul><li><code>proxy</code></li><li><code>timeout</code></li><li><code>host</code></li><li><code>listen</code></li><li><code>PATH</code></li></ul><h3 id="3-5-替换"><a href="#3-5-替换" class="headerlink" title="3.5 替换"></a>3.5 替换</h3><p>按：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Ctrl+\</span><br></pre></td></tr></table></figure><p>然后按提示：</p><ol><li>输入要查找的内容</li><li>输入替换后的内容</li><li>逐个确认，或者全部替换</li></ol><p>这个功能非常适合改配置项，比如：</p><ul><li>把端口 <code>8080</code> 改成 <code>3000</code></li><li>把旧路径替换成新路径</li><li>把某个域名替换成新域名</li></ul><h3 id="3-6-跳到指定行"><a href="#3-6-跳到指定行" class="headerlink" title="3.6 跳到指定行"></a>3.6 跳到指定行</h3><p>按：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Ctrl+_</span><br></pre></td></tr></table></figure><p>然后输入：</p><ul><li><code>120</code> 表示跳到第 120 行</li><li><code>120,8</code> 表示跳到第 120 行第 8 列</li></ul><p>适合：</p><ul><li>根据报错行定位问题</li><li>跳到配置文件中的某一段</li></ul><h3 id="3-7-剪切与粘贴"><a href="#3-7-剪切与粘贴" class="headerlink" title="3.7 剪切与粘贴"></a>3.7 剪切与粘贴</h3><p>没有选区时：</p><ul><li><code>Ctrl+K</code>：剪切当前整行</li><li><code>Ctrl+U</code>：在当前光标位置粘贴</li></ul><p>连续按多次 <code>Ctrl+K</code>：</p><ul><li>会把多行连续剪下来</li><li>然后一次 <code>Ctrl+U</code> 可以整段贴回去</li></ul><p>这在调整配置块顺序时非常有用，比如：</p><ul><li>调整 <code>server</code> 段顺序</li><li>调整 <code>alias</code> 或 <code>export</code> 的顺序</li><li>调整 docker compose 里的某个服务块</li></ul><h3 id="3-8-选择与复制"><a href="#3-8-选择与复制" class="headerlink" title="3.8 选择与复制"></a>3.8 选择与复制</h3><p>开始选区：</p><ul><li><code>Ctrl+6</code></li><li>或 <code>Alt+A</code></li></ul><p>然后移动光标，经过的内容就会被选中。</p><p>复制选区：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Alt+6</span><br></pre></td></tr></table></figure><p>剪切选区：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Ctrl+K</span><br></pre></td></tr></table></figure><h3 id="3-9-撤销与重做"><a href="#3-9-撤销与重做" class="headerlink" title="3.9 撤销与重做"></a>3.9 撤销与重做</h3><p>较新的 <code>nano</code> 版本通常支持：</p><ul><li><code>Alt+U</code>：撤销</li><li><code>Alt+E</code>：重做</li></ul><p>如果某些环境下 <code>Alt</code> 键不稳定，可以尝试：</p><ul><li>先按 <code>Esc</code></li><li>再按 <code>U</code> 或 <code>E</code></li></ul><h3 id="3-10-帮助"><a href="#3-10-帮助" class="headerlink" title="3.10 帮助"></a>3.10 帮助</h3><p>忘了按键时，直接按：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Ctrl+G</span><br></pre></td></tr></table></figure><p>这是最稳的保底方案。</p><hr><h2 id="4-配置文件场景里的高频用法"><a href="#4-配置文件场景里的高频用法" class="headerlink" title="4. 配置文件场景里的高频用法"></a>4. 配置文件场景里的高频用法</h2><p>这一节只讲你真正会遇到的配置文件工作流。</p><h3 id="4-1-改-shell-配置"><a href="#4-1-改-shell-配置" class="headerlink" title="4.1 改 shell 配置"></a>4.1 改 shell 配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">nano ~/.bashrc</span><br><span class="line">nano ~/.zshrc</span><br></pre></td></tr></table></figure><p>常见动作：</p><ul><li>增加 <code>alias</code></li><li>修改 <code>PATH</code></li><li>增加环境变量</li><li>调整 prompt</li></ul><p>改完后通常别忘了：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br></pre></td></tr></table></figure><p>或：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ~/.zshrc</span><br></pre></td></tr></table></figure><h3 id="4-2-改-Git-配置"><a href="#4-2-改-Git-配置" class="headerlink" title="4.2 改 Git 配置"></a>4.2 改 Git 配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano ~/.gitconfig</span><br></pre></td></tr></table></figure><p>常见动作：</p><ul><li>改用户名和邮箱</li><li>配别名</li><li>配默认分支行为</li><li>配颜色或编辑器</li></ul><h3 id="4-3-改-SSH-配置"><a href="#4-3-改-SSH-配置" class="headerlink" title="4.3 改 SSH 配置"></a>4.3 改 SSH 配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano ~/.ssh/config</span><br></pre></td></tr></table></figure><p>这个文件非常适合用 <code>nano</code>，因为通常只是：</p><ul><li>增加一个 <code>Host</code></li><li>改端口</li><li>改用户名</li><li>改 <code>IdentityFile</code></li></ul><h3 id="4-4-改系统文件"><a href="#4-4-改系统文件" class="headerlink" title="4.4 改系统文件"></a>4.4 改系统文件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nano /etc/hosts</span><br><span class="line"><span class="built_in">sudo</span> nano /etc/fstab</span><br><span class="line"><span class="built_in">sudo</span> nano /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p>这类文件一般不需要复杂编辑，只需要：</p><ul><li>搜索</li><li>改几行</li><li>保存退出</li></ul><p>这正是 <code>nano</code> 最擅长的场景。</p><h3 id="4-5-改-YAML-Docker-Compose-Nginx-配置"><a href="#4-5-改-YAML-Docker-Compose-Nginx-配置" class="headerlink" title="4.5 改 YAML &#x2F; Docker Compose &#x2F; Nginx 配置"></a>4.5 改 YAML &#x2F; Docker Compose &#x2F; Nginx 配置</h3><p>例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">nano docker-compose.yml</span><br><span class="line">nano compose.yaml</span><br><span class="line"><span class="built_in">sudo</span> nano /etc/nginx/nginx.conf</span><br></pre></td></tr></table></figure><p>这类文件的关键是：</p><ul><li>不要改错缩进</li><li>快速搜索关键字段</li><li>尽量少做复杂文本操作</li></ul><p><code>nano</code> 在这种场景下比 <code>vim</code> 更不容易带来额外心智负担。</p><h3 id="4-6-只读查看，避免误改"><a href="#4-6-只读查看，避免误改" class="headerlink" title="4.6 只读查看，避免误改"></a>4.6 只读查看，避免误改</h3><p>如果你只是想先看：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -v file.txt</span><br></pre></td></tr></table></figure><p>非常适合：</p><ul><li>先确认配置结构</li><li>查某个值</li><li>查某段内容</li></ul><h3 id="4-7-保存前自动留备份"><a href="#4-7-保存前自动留备份" class="headerlink" title="4.7 保存前自动留备份"></a>4.7 保存前自动留备份</h3><p>如果你改的是比较关键的配置，建议：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -B file.txt</span><br></pre></td></tr></table></figure><p>保存后会自动生成：</p><ul><li><code>file.txt~</code></li></ul><p>这对系统配置文件尤其有用。</p><h3 id="4-8-根据报错直接跳行"><a href="#4-8-根据报错直接跳行" class="headerlink" title="4.8 根据报错直接跳行"></a>4.8 根据报错直接跳行</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano +87,1 nginx.conf</span><br></pre></td></tr></table></figure><p>很适合：</p><ul><li>nginx 报第几行语法错误</li><li>shell 脚本报某一行</li><li>yaml 报某一行格式错误</li></ul><h3 id="4-9-临时查看命令输出"><a href="#4-9-临时查看命令输出" class="headerlink" title="4.9 临时查看命令输出"></a>4.9 临时查看命令输出</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git diff | nano -</span><br></pre></td></tr></table></figure><p>这里的 <code>-</code> 表示从标准输入读取。</p><p>这个用法适合：</p><ul><li>临时查看输出</li><li>复制一段内容</li><li>先人工读一下再决定怎么改</li></ul><hr><h2 id="5-常用启动参数"><a href="#5-常用启动参数" class="headerlink" title="5. 常用启动参数"></a>5. 常用启动参数</h2><p>下面这些选项，和配置文件编辑关系最大。</p><h3 id="5-1-显示行号"><a href="#5-1-显示行号" class="headerlink" title="5.1 显示行号"></a>5.1 显示行号</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -l file.txt</span><br></pre></td></tr></table></figure><h3 id="5-2-只读打开"><a href="#5-2-只读打开" class="headerlink" title="5.2 只读打开"></a>5.2 只读打开</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -v file.txt</span><br></pre></td></tr></table></figure><h3 id="5-3-启用鼠标"><a href="#5-3-启用鼠标" class="headerlink" title="5.3 启用鼠标"></a>5.3 启用鼠标</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -m file.txt</span><br></pre></td></tr></table></figure><h3 id="5-4-软换行显示超长行"><a href="#5-4-软换行显示超长行" class="headerlink" title="5.4 软换行显示超长行"></a>5.4 软换行显示超长行</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -S file.txt</span><br></pre></td></tr></table></figure><p>它只是显示成多行，不会真的写入换行。</p><h3 id="5-5-自动创建备份"><a href="#5-5-自动创建备份" class="headerlink" title="5.5 自动创建备份"></a>5.5 自动创建备份</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -B file.txt</span><br></pre></td></tr></table></figure><h3 id="5-6-自动缩进"><a href="#5-6-自动缩进" class="headerlink" title="5.6 自动缩进"></a>5.6 自动缩进</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -i file.txt</span><br></pre></td></tr></table></figure><h3 id="5-7-Tab-转空格"><a href="#5-7-Tab-转空格" class="headerlink" title="5.7 Tab 转空格"></a>5.7 Tab 转空格</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -E file.txt</span><br></pre></td></tr></table></figure><p>这个对很多配置文件有帮助，但要注意：</p><ul><li>对 <code>Makefile</code> 这类严格依赖 Tab 的文件要小心</li></ul><h3 id="5-8-记住光标位置"><a href="#5-8-记住光标位置" class="headerlink" title="5.8 记住光标位置"></a>5.8 记住光标位置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -P file.txt</span><br></pre></td></tr></table></figure><h3 id="5-9-打开时定位到指定行列"><a href="#5-9-打开时定位到指定行列" class="headerlink" title="5.9 打开时定位到指定行列"></a>5.9 打开时定位到指定行列</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano +120,8 file.txt</span><br></pre></td></tr></table></figure><hr><h2 id="6-推荐的-nanorc"><a href="#6-推荐的-nanorc" class="headerlink" title="6. 推荐的 .nanorc"></a>6. 推荐的 <code>.nanorc</code></h2><p>如果你主要是用 <code>nano</code> 改配置文件，我建议用一份偏实用、偏稳的配置。</p><p>用户配置文件通常是：</p><ul><li><code>~/.nanorc</code></li></ul><p>推荐示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">set linenumbers</span><br><span class="line">set mouse</span><br><span class="line">set autoindent</span><br><span class="line">set softwrap</span><br><span class="line">set atblanks</span><br><span class="line">set historylog</span><br><span class="line">set positionlog</span><br><span class="line">set indicator</span><br><span class="line">set minibar</span><br><span class="line"></span><br><span class="line">include &quot;/usr/share/nano/*.nanorc&quot;</span><br></pre></td></tr></table></figure><p>这些配置的含义：</p><ul><li><code>set linenumbers</code>：显示行号</li><li><code>set mouse</code>：启用鼠标</li><li><code>set autoindent</code>：新行自动继承缩进</li><li><code>set softwrap</code>：超长行按屏幕宽度显示</li><li><code>set atblanks</code>：软换行优先在空白处分行</li><li><code>set historylog</code>：记住搜索和替换历史</li><li><code>set positionlog</code>：记住上次光标位置</li><li><code>set indicator</code>：右侧显示滚动指示</li><li><code>set minibar</code>：底栏信息更紧凑</li><li><code>include &quot;/usr/share/nano/*.nanorc&quot;</code>：启用系统语法高亮</li></ul><h3 id="6-1-一个更保守的版本"><a href="#6-1-一个更保守的版本" class="headerlink" title="6.1 一个更保守的版本"></a>6.1 一个更保守的版本</h3><p>如果你不喜欢软换行，也可以用这个：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">set linenumbers</span><br><span class="line">set mouse</span><br><span class="line">set autoindent</span><br><span class="line">set historylog</span><br><span class="line">set positionlog</span><br><span class="line">set indicator</span><br><span class="line"></span><br><span class="line">include &quot;/usr/share/nano/*.nanorc&quot;</span><br></pre></td></tr></table></figure><p>这更适合：</p><ul><li>代码片段</li><li>yaml</li><li>不想看到长行自动换屏的场景</li></ul><hr><h2 id="7-WSL-SSH-里的常见问题"><a href="#7-WSL-SSH-里的常见问题" class="headerlink" title="7. WSL &#x2F; SSH 里的常见问题"></a>7. WSL &#x2F; SSH 里的常见问题</h2><h3 id="7-1-终端粘贴和-Nano-粘贴不是一回事"><a href="#7-1-终端粘贴和-Nano-粘贴不是一回事" class="headerlink" title="7.1 终端粘贴和 Nano 粘贴不是一回事"></a>7.1 终端粘贴和 Nano 粘贴不是一回事</h3><p>这是最常见的坑。</p><p><code>nano</code> 内部的剪切与粘贴是：</p><ul><li><code>Ctrl+K</code></li><li><code>Ctrl+U</code></li></ul><p>终端自己的系统粘贴通常是：</p><ul><li><code>Ctrl+Shift+V</code></li><li>鼠标右键</li></ul><p>不要把这两套逻辑混在一起。</p><h3 id="7-2-Ctrl-C-在传统-Nano-里不是复制"><a href="#7-2-Ctrl-C-在传统-Nano-里不是复制" class="headerlink" title="7.2 Ctrl+C 在传统 Nano 里不是复制"></a>7.2 <code>Ctrl+C</code> 在传统 Nano 里不是复制</h3><p>传统 <code>nano</code> 中：</p><ul><li><code>Ctrl+C</code> 通常是显示当前位置等状态信息</li><li>不是复制</li></ul><p>所以最稳妥的复制方式仍然是：</p><ul><li>选区后按 <code>Alt+6</code></li></ul><h3 id="7-3-Alt-键不工作怎么办"><a href="#7-3-Alt-键不工作怎么办" class="headerlink" title="7.3 Alt 键不工作怎么办"></a>7.3 Alt 键不工作怎么办</h3><p>有些终端里 <code>Alt</code> 不会直接传进去。</p><p>可以尝试：</p><ul><li>按一次 <code>Esc</code>，再按目标键</li><li>检查终端是否把 <code>Alt</code> 用作菜单快捷键</li><li>在终端里打开“Alt 作为 Meta 发送”之类的选项</li></ul><h3 id="7-4-nano-S-只是显示换行，不是真的插入换行"><a href="#7-4-nano-S-只是显示换行，不是真的插入换行" class="headerlink" title="7.4 nano -S 只是显示换行，不是真的插入换行"></a>7.4 <code>nano -S</code> 只是显示换行，不是真的插入换行</h3><p>这个点很重要。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -S file.txt</span><br></pre></td></tr></table></figure><p>只是为了让超长行看起来更容易读，不会真的改写文件内容结构。</p><h3 id="7-5-关键配置建议先留备份"><a href="#7-5-关键配置建议先留备份" class="headerlink" title="7.5 关键配置建议先留备份"></a>7.5 关键配置建议先留备份</h3><p>改重要系统配置前，优先考虑：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano -B file.txt</span><br></pre></td></tr></table></figure><p>或者你自己先复制一份：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> file.txt file.txt.bak</span><br></pre></td></tr></table></figure><hr><h2 id="8-Vim-极简附录"><a href="#8-Vim-极简附录" class="headerlink" title="8. Vim 极简附录"></a>8. Vim 极简附录</h2><p>这一节只是为了防止你偶尔遇到 <code>vim</code>，不是为了让你系统学习它。</p><p>如果你并不想用 <code>vim</code>，其实只记下面几个就够了：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">i        进入输入模式</span><br><span class="line">Esc      回普通模式</span><br><span class="line">:wq      保存并退出</span><br><span class="line">:q!      不保存退出</span><br><span class="line">/text    搜索</span><br><span class="line">dd       删除当前行</span><br></pre></td></tr></table></figure><p>一句话理解：</p><ul><li><code>nano</code> 更适合“改配置文件”</li><li><code>vim</code> 更适合“长期高频文本操作”</li></ul><p>既然你写代码本来就会用专门编辑器，那这里不用花太多精力。</p><hr><h2 id="9-我的建议"><a href="#9-我的建议" class="headerlink" title="9. 我的建议"></a>9. 我的建议</h2><p>如果你的目标已经很明确：</p><ul><li>写代码用专门编辑器</li><li>终端里只想快速改配置</li></ul><p>那我建议就是：</p><ol><li>把 <code>nano</code> 用熟</li><li>把上面的速查表记住</li><li>把 <code>.nanorc</code> 配起来</li><li>遇到 <code>vim</code> 时只会退出就够了</li></ol><p>对你这种使用方式来说，最值得练熟的不是一整套 <code>vim</code>，而是这几个 <code>nano</code> 动作：</p><ul><li>打开文件</li><li>搜索</li><li>替换</li><li>跳行</li><li>保存</li><li>退出</li></ul><p>这已经足够覆盖大多数配置文件场景。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20260318/nano-config-guide/</id>
    <link href="https://minniexcode.github.io/memoirs/20260318/nano-config-guide/"/>
    <published>2026-03-18T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>这篇文档按一个更实用的思路重新整理：主角是 <code>nano</code>，使用场景是 WSL、Linux、SSH、远程服务器里的配置文件编辑，<code>vim</code> 只保留一个很短的附录，不再放在中间打断阅读。</p>
<p>如果你的真实需求是改 <code>~/.bashrc</code>、<code>~/.zshrc</code>、<code>~/.gitconfig</code>、<code>/etc/hosts</code>，或者改 <code>ssh</code>、<code>nginx</code>、<code>docker compose</code>、<code>yaml</code>、<code>env</code> 之类的配置，那么 <code>nano</code> 往往比 <code>vim</code> 更适合你。</p>]]>
    </summary>
    <title>Nano 配置文件编辑指南（附极简 Vim 备忘）</title>
    <updated>2026-03-18T14:57:45.842Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="AI与工具" scheme="https://minniexcode.github.io/memoirs/categories/AI%E4%B8%8E%E5%B7%A5%E5%85%B7/"/>
    <category term="Tooling" scheme="https://minniexcode.github.io/memoirs/tags/Tooling/"/>
    <category term="WezTerm" scheme="https://minniexcode.github.io/memoirs/tags/WezTerm/"/>
    <category term="Terminal" scheme="https://minniexcode.github.io/memoirs/tags/Terminal/"/>
    <category term="Productivity" scheme="https://minniexcode.github.io/memoirs/tags/Productivity/"/>
    <content>
      <![CDATA[<p>WezTerm 这两年基本成了我最愿意推荐给开发者的终端之一：配置文件化、标签页和分屏能力强、跨平台一致性也不错。</p><p>但很多教程有两个问题：要么只讲“怎么装”，要么一上来就把配置堆得很满。对大多数人来说，真正需要的不是一份炫技配置，而是一套稳定可落地的起步方案。</p><p>这篇文章把 Windows 和 macOS 放在一起写，目标很直接：先让你用起来，再决定要不要继续折腾。</p><span id="more"></span><h2 id="一、先讲清楚：WezTerm-是什么，不是什么"><a href="#一、先讲清楚：WezTerm-是什么，不是什么" class="headerlink" title="一、先讲清楚：WezTerm 是什么，不是什么"></a>一、先讲清楚：WezTerm 是什么，不是什么</h2><p>WezTerm 是终端模拟器，不是 shell。</p><p>你可以把它理解成两层：</p><ul><li><code>WezTerm</code>：窗口、标签页、分屏、渲染、快捷键、主题</li><li><code>Shell</code>：真正执行命令的环境，比如 Windows 上的 <code>PowerShell</code>，macOS 上的 <code>zsh</code></li></ul><p>所以“我想把 WezTerm 配成 PowerShell &#x2F; zsh”这句话，更准确地说是：</p><ul><li>用 WezTerm 作为终端界面</li><li>用 PowerShell 或 zsh 作为默认 shell</li></ul><p>这个区分很重要，因为后面很多配置项其实都是围绕这两层展开的。</p><h2 id="二、为什么我会推荐-WezTerm"><a href="#二、为什么我会推荐-WezTerm" class="headerlink" title="二、为什么我会推荐 WezTerm"></a>二、为什么我会推荐 WezTerm</h2><p>和系统自带终端相比，WezTerm 最实用的优势有 4 个：</p><ol><li>配置文件就是 <code>~/.wezterm.lua</code>，适合版本管理和渐进调整。</li><li>默认就有很强的标签页、分屏、搜索和复制粘贴能力。</li><li>Windows &#x2F; macOS &#x2F; Linux 的整体体验比较统一，换机器成本低。</li><li>改完配置可以直接重载，不需要每次去找图形设置界面。</li></ol><p>如果你是开发者，尤其经常需要同时跑服务、看日志、连远程机器，它会比“能用就行”的终端更顺手。</p><h2 id="三、先用最稳的思路，不要一开始就过度定制"><a href="#三、先用最稳的思路，不要一开始就过度定制" class="headerlink" title="三、先用最稳的思路，不要一开始就过度定制"></a>三、先用最稳的思路，不要一开始就过度定制</h2><p>我更建议按这个顺序上手：</p><ul><li>第一步：先确定默认 shell</li><li>第二步：先选一个稳定深色主题</li><li>第三步：先用官方默认快捷键</li><li>第四步：一周后再决定要不要改键位、字体和状态栏</li></ul><p>原因很简单：</p><ul><li>shell 是工作流核心，先稳定</li><li>主题影响阅读舒适度，先解决</li><li>快捷键属于肌肉记忆，改太早容易把自己弄乱</li></ul><p>如果你当前目标只是“日常开发能稳定用”，那这套路径比一上来抄一份 200 行配置更有效。</p><h2 id="四、Windows：最适合大多数人的起步方案"><a href="#四、Windows：最适合大多数人的起步方案" class="headerlink" title="四、Windows：最适合大多数人的起步方案"></a>四、Windows：最适合大多数人的起步方案</h2><h3 id="1）安装与配置文件位置"><a href="#1）安装与配置文件位置" class="headerlink" title="1）安装与配置文件位置"></a>1）安装与配置文件位置</h3><p>Windows 上装好 WezTerm 后，默认配置文件通常放在：</p><p><code>%USERPROFILE%\.wezterm.lua</code></p><p>例如：</p><p><code>C:\Users\&lt;你的用户名&gt;\.wezterm.lua</code></p><p>改完配置后，推荐用 <code>Ctrl+Shift+R</code> 重载；不行再完全重启 WezTerm。</p><h3 id="2）Windows-推荐默认组合"><a href="#2）Windows-推荐默认组合" class="headerlink" title="2）Windows 推荐默认组合"></a>2）Windows 推荐默认组合</h3><p>如果你不想折腾，最稳的默认组合是：</p><ul><li>终端：<code>WezTerm</code></li><li>默认 shell：<code>powershell.exe -NoLogo</code></li><li>主题：<code>Tokyo Night Storm</code></li><li>快捷键：官方默认</li></ul><p>之所以先推荐 <code>powershell.exe</code>，不是因为它最强，而是因为它通常最稳定。<code>pwsh.exe</code> 当然更现代，但 profile、模块、环境变量一旦有历史包袱，排错成本会更高。</p><h3 id="3）一份够用的-Windows-配置"><a href="#3）一份够用的-Windows-配置" class="headerlink" title="3）一份够用的 Windows 配置"></a>3）一份够用的 Windows 配置</h3><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> wezterm = <span class="built_in">require</span>(<span class="string">&quot;wezterm&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> &#123;</span><br><span class="line">  color_scheme = <span class="string">&quot;Tokyo Night Storm&quot;</span>,</span><br><span class="line">  window_background_opacity = <span class="number">0.92</span>,</span><br><span class="line">  text_background_opacity = <span class="number">1.0</span>,</span><br><span class="line"></span><br><span class="line">  enable_tab_bar = <span class="literal">true</span>,</span><br><span class="line">  hide_tab_bar_if_only_one_tab = <span class="literal">true</span>,</span><br><span class="line">  use_fancy_tab_bar = <span class="literal">false</span>,</span><br><span class="line">  tab_max_width = <span class="number">32</span>,</span><br><span class="line">  window_padding = &#123; left = <span class="number">8</span>, right = <span class="number">8</span>, top = <span class="number">6</span>, bottom = <span class="number">6</span> &#125;,</span><br><span class="line"></span><br><span class="line">  default_prog = &#123; <span class="string">&quot;powershell.exe&quot;</span>, <span class="string">&quot;-NoLogo&quot;</span> &#125;,</span><br><span class="line">  exit_behavior = <span class="string">&quot;Close&quot;</span>,</span><br><span class="line">  scrollback_lines = <span class="number">8000</span>,</span><br><span class="line">  adjust_window_size_when_changing_font_size = <span class="literal">false</span>,</span><br><span class="line"></span><br><span class="line">  launch_menu = &#123;</span><br><span class="line">    &#123; label = <span class="string">&quot;Windows PowerShell&quot;</span>, args = &#123; <span class="string">&quot;powershell.exe&quot;</span>, <span class="string">&quot;-NoLogo&quot;</span> &#125; &#125;,</span><br><span class="line">    &#123; label = <span class="string">&quot;PowerShell 7&quot;</span>, args = &#123; <span class="string">&quot;pwsh.exe&quot;</span>, <span class="string">&quot;-NoLogo&quot;</span>, <span class="string">&quot;-NoProfile&quot;</span> &#125; &#125;,</span><br><span class="line">    &#123; label = <span class="string">&quot;Command Prompt&quot;</span>, args = &#123; <span class="string">&quot;cmd.exe&quot;</span> &#125; &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这份配置的重点不是“炫”，而是稳：</p><ul><li>深色主题，提升长期阅读舒适度</li><li>轻微透明和边距，界面更松弛</li><li>默认用 <code>powershell.exe</code></li><li>保留 <code>launch_menu</code>，需要时再手动切到 <code>pwsh</code> 或 <code>cmd</code></li></ul><h3 id="4）Windows-日常最常用快捷键"><a href="#4）Windows-日常最常用快捷键" class="headerlink" title="4）Windows 日常最常用快捷键"></a>4）Windows 日常最常用快捷键</h3><ul><li>新建标签页：<code>Ctrl+Shift+T</code></li><li>关闭当前标签页：<code>Ctrl+Shift+W</code></li><li>新建窗口：<code>Ctrl+Shift+N</code></li><li>复制：<code>Ctrl+Shift+C</code></li><li>粘贴：<code>Ctrl+Shift+V</code></li><li>搜索输出：<code>Ctrl+Shift+F</code></li><li>重载配置：<code>Ctrl+Shift+R</code></li></ul><p>分屏常见默认键位：</p><ul><li>水平分屏：<code>Ctrl+Shift+Alt+&quot;</code></li><li>垂直分屏：<code>Ctrl+Shift+Alt+%</code></li></ul><p>如果你的键盘布局和这两个符号位置不一致，不用慌，这在 Windows 上很常见。</p><h3 id="5）Windows-常见坑"><a href="#5）Windows-常见坑" class="headerlink" title="5）Windows 常见坑"></a>5）Windows 常见坑</h3><p>最典型的三个问题：</p><ul><li>配了系统里没有的字体，导致启动时报字体错误</li><li>把 <code>pwsh.exe</code> 设成默认后，profile 报错直接退出</li><li>改了配置但没重载，误以为配置没生效</li></ul><p>我的建议是：</p><ul><li>刚开始先不要配字体</li><li>默认 shell 先用 <code>powershell.exe</code></li><li>任何修改先按 <code>Ctrl+Shift+R</code> 再判断</li></ul><h2 id="五、macOS：更自然的组合是-WezTerm-zsh"><a href="#五、macOS：更自然的组合是-WezTerm-zsh" class="headerlink" title="五、macOS：更自然的组合是 WezTerm + zsh"></a>五、macOS：更自然的组合是 WezTerm + zsh</h2><h3 id="1）安装与配置文件位置-1"><a href="#1）安装与配置文件位置-1" class="headerlink" title="1）安装与配置文件位置"></a>1）安装与配置文件位置</h3><p>macOS 最省事的安装方式是 Homebrew：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install --cask wezterm</span><br></pre></td></tr></table></figure><p>配置文件位置是：</p><p><code>~/.wezterm.lua</code></p><p>例如：</p><p><code>/Users/&lt;你的用户名&gt;/.wezterm.lua</code></p><p>改完配置后，推荐按 <code>Command+Shift+R</code> 重载。</p><h3 id="2）macOS-推荐默认组合"><a href="#2）macOS-推荐默认组合" class="headerlink" title="2）macOS 推荐默认组合"></a>2）macOS 推荐默认组合</h3><p>Mac 上最自然的起步方案通常是：</p><ul><li>终端：<code>WezTerm</code></li><li>默认 shell：<code>/bin/zsh -l</code></li><li>主题：<code>Tokyo Night Storm</code></li><li>快捷键：官方默认</li></ul><p>之所以推荐 <code>zsh -l</code>，是因为它更贴近 macOS 的默认行为。很多用户的 PATH、Homebrew 初始化、代理配置、别名和提示符，都是在 shell login 过程里建立起来的。</p><h3 id="3）一份够用的-macOS-配置"><a href="#3）一份够用的-macOS-配置" class="headerlink" title="3）一份够用的 macOS 配置"></a>3）一份够用的 macOS 配置</h3><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> wezterm = <span class="built_in">require</span>(<span class="string">&quot;wezterm&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> &#123;</span><br><span class="line">  color_scheme = <span class="string">&quot;Tokyo Night Storm&quot;</span>,</span><br><span class="line">  window_background_opacity = <span class="number">0.95</span>,</span><br><span class="line">  text_background_opacity = <span class="number">1.0</span>,</span><br><span class="line"></span><br><span class="line">  enable_tab_bar = <span class="literal">true</span>,</span><br><span class="line">  hide_tab_bar_if_only_one_tab = <span class="literal">true</span>,</span><br><span class="line">  use_fancy_tab_bar = <span class="literal">false</span>,</span><br><span class="line">  tab_max_width = <span class="number">32</span>,</span><br><span class="line">  window_padding = &#123; left = <span class="number">8</span>, right = <span class="number">8</span>, top = <span class="number">6</span>, bottom = <span class="number">6</span> &#125;,</span><br><span class="line"></span><br><span class="line">  default_prog = &#123; <span class="string">&quot;/bin/zsh&quot;</span>, <span class="string">&quot;-l&quot;</span> &#125;,</span><br><span class="line">  exit_behavior = <span class="string">&quot;Close&quot;</span>,</span><br><span class="line">  scrollback_lines = <span class="number">10000</span>,</span><br><span class="line">  adjust_window_size_when_changing_font_size = <span class="literal">false</span>,</span><br><span class="line"></span><br><span class="line">  launch_menu = &#123;</span><br><span class="line">    &#123; label = <span class="string">&quot;zsh&quot;</span>, args = &#123; <span class="string">&quot;/bin/zsh&quot;</span>, <span class="string">&quot;-l&quot;</span> &#125; &#125;,</span><br><span class="line">    &#123; label = <span class="string">&quot;bash&quot;</span>, args = &#123; <span class="string">&quot;/bin/bash&quot;</span>, <span class="string">&quot;-l&quot;</span> &#125; &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你还装了 <code>fish</code>、<code>pwsh</code> 之类的 shell，再把它们补进 <code>launch_menu</code> 就行。需要注意的是，Homebrew 在 Apple Silicon 和 Intel Mac 上的安装路径不同：</p><ul><li>Apple Silicon 常见是 <code>/opt/homebrew/bin</code></li><li>Intel Mac 常见是 <code>/usr/local/bin</code></li></ul><h3 id="4）macOS-日常最常用快捷键"><a href="#4）macOS-日常最常用快捷键" class="headerlink" title="4）macOS 日常最常用快捷键"></a>4）macOS 日常最常用快捷键</h3><p>macOS 下很多高频操作优先用 <code>Command</code>，也就是 WezTerm 文档里的 <code>SUPER</code>：</p><ul><li>新建标签页：<code>Command+T</code></li><li>关闭当前标签页：<code>Command+W</code></li><li>新建窗口：<code>Command+N</code></li><li>复制：<code>Command+C</code></li><li>粘贴：<code>Command+V</code></li><li>搜索输出：<code>Command+F</code></li><li>放大字体：<code>Command+=</code></li><li>缩小字体：<code>Command+-</code></li><li>重置字体：<code>Command+0</code></li><li>重载配置：<code>Command+Shift+R</code></li></ul><p>要特别注意：</p><ul><li><code>Ctrl+C</code> 在终端里通常还是中断当前进程，不是复制</li><li>分屏默认键位通常仍是 <code>Ctrl+Shift+Alt+&quot;</code> 和 <code>Ctrl+Shift+Alt+%</code></li></ul><h3 id="5）macOS-常见坑"><a href="#5）macOS-常见坑" class="headerlink" title="5）macOS 常见坑"></a>5）macOS 常见坑</h3><p>最常见的不是 WezTerm 本身出问题，而是 shell 路径没配对：</p><ul><li><code>default_prog</code> 指向了不存在的可执行文件</li><li>Homebrew shell 的路径写成了另一种架构的目录</li><li>你以为在跟随系统默认 shell，其实手动写死了 <code>default_prog</code></li></ul><p>遇到这类问题时，先用下面几个命令确认路径：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="variable">$SHELL</span></span><br><span class="line"><span class="built_in">which</span> zsh</span><br><span class="line"><span class="built_in">which</span> bash</span><br><span class="line"><span class="built_in">which</span> fish</span><br><span class="line"><span class="built_in">which</span> pwsh</span><br></pre></td></tr></table></figure><h2 id="六、一个很容易踩的真实问题：Windows-下图片粘贴为什么总是不稳定"><a href="#六、一个很容易踩的真实问题：Windows-下图片粘贴为什么总是不稳定" class="headerlink" title="六、一个很容易踩的真实问题：Windows 下图片粘贴为什么总是不稳定"></a>六、一个很容易踩的真实问题：Windows 下图片粘贴为什么总是不稳定</h2><p>如果你在 Windows 上用 WezTerm 跑终端类 AI 工具，比如 <code>opencode</code>，很可能会遇到一个很拧巴的问题：</p><ul><li>在 VSCode 插件里，截图后直接粘贴图片基本没问题</li><li>但在 <code>PowerShell</code>、<code>WezTerm</code>、<code>Windows Terminal</code> 里运行同一个工具时，<code>Ctrl+V</code> 经常没反应，或者只能粘贴文本</li><li>更麻烦的是，它偶尔又会成功，于是问题看起来像“随机故障”</li></ul><p>这个现象特别容易让人误判成：模型不支持图片、CLI 不支持图片、或者 PowerShell 有问题。实际上，这几个方向大多都不是根因。</p><h3 id="1）先把责任边界分清楚"><a href="#1）先把责任边界分清楚" class="headerlink" title="1）先把责任边界分清楚"></a>1）先把责任边界分清楚</h3><p>这里至少有三层：</p><ul><li><code>WezTerm</code> &#x2F; <code>Windows Terminal</code>：terminal emulator，决定很多按键先由谁处理</li><li><code>PowerShell</code> &#x2F; <code>cmd</code>：shell，负责命令执行环境</li><li><code>opencode</code> 这类 TUI&#x2F;CLI 应用：真正实现图片附件能力的前台程序</li></ul><p>关键点在于：<code>Ctrl+V</code> 在终端环境里，通常优先表示普通文本粘贴；而图片粘贴往往是应用层收到某个快捷键后，再自己去读取系统剪贴板图片。</p><p>所以“文本 paste”和“图片附件”不是同一种能力，它们本来就不在同一层。</p><h3 id="2）为什么-VSCode-插件能行，终端里却经常不行"><a href="#2）为什么-VSCode-插件能行，终端里却经常不行" class="headerlink" title="2）为什么 VSCode 插件能行，终端里却经常不行"></a>2）为什么 VSCode 插件能行，终端里却经常不行</h3><p>因为两边拿到的根本不是同一种输入语义。</p><p>VSCode 这类 GUI 环境拿到的是更高层的 paste event，里面可能直接包含：</p><ul><li>文本</li><li>HTML</li><li>图片</li><li>文件</li></ul><p>终端前台程序拿到的通常却只是：</p><ul><li>某个按键被按下</li><li>某段文本被 paste 进来</li><li>一些控制序列</li></ul><p>这也是为什么 GUI 插件处理图片会显得很自然，而终端里的图片粘贴经常需要额外设计快捷键。</p><h3 id="3）WezTerm-里最典型的冲突长什么样"><a href="#3）WezTerm-里最典型的冲突长什么样" class="headerlink" title="3）WezTerm 里最典型的冲突长什么样"></a>3）WezTerm 里最典型的冲突长什么样</h3><p>如果你的 WezTerm 配置里写过这种映射：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123; key = <span class="string">&quot;v&quot;</span>, mods = <span class="string">&quot;CTRL&quot;</span>, action = wezterm.action.PasteFrom(<span class="string">&quot;Clipboard&quot;</span>) &#125;</span><br></pre></td></tr></table></figure><p>它的实际含义就是：</p><ul><li><code>Ctrl+V</code> 先由 WezTerm 接管</li><li>WezTerm 执行的是普通剪贴板文本粘贴</li><li>前台应用未必还能拿到这个按键机会</li></ul><p>结果就是：</p><ul><li>文本粘贴通常没问题</li><li>但依赖“应用自己读图片剪贴板”的那条链路更难触发</li></ul><p>这类问题最容易出现在你已经把 <code>Ctrl+V</code> 训练成高频文本粘贴键，同时又希望同一个组合键顺手承担图片附件语义的时候。</p><h3 id="4）一个更稳定的做法：把文本和图片拆开"><a href="#4）一个更稳定的做法：把文本和图片拆开" class="headerlink" title="4）一个更稳定的做法：把文本和图片拆开"></a>4）一个更稳定的做法：把文本和图片拆开</h3><p>我最后更推荐的分工是：</p><ul><li><code>Ctrl+V</code>：保留给普通文本粘贴</li><li><code>Alt+V</code>：交给应用处理图片粘贴</li><li><code>Ctrl+Alt+V</code>：作为备用图片粘贴键</li></ul><p>验证方法也很直接：</p><ol><li>用 <code>Win+Shift+S</code> 截图</li><li>聚焦到目标 CLI 工具的输入区</li><li>按 <code>Alt+V</code></li></ol><p>如果输入区出现类似 <code>[Image 1]</code> 这样的占位，基本就能说明：</p><ul><li>模型本身支持图片</li><li>应用本身支持图片</li><li>真正的问题是 <code>Ctrl+V</code> 在 terminal 和 app 之间的职责冲突</li></ul><p>这也是我现在更认同的结论：在终端世界里，文本和图片分不同快捷键，不是 workaround，而是更符合输入模型的工程化方案。</p><h3 id="5）把这件事和-WezTerm-的定位联系起来"><a href="#5）把这件事和-WezTerm-的定位联系起来" class="headerlink" title="5）把这件事和 WezTerm 的定位联系起来"></a>5）把这件事和 WezTerm 的定位联系起来</h3><p>这件事也顺手说明了一个很重要的使用原则：</p><ul><li>WezTerm 很强，但它首先是 terminal，不是 GUI 插件宿主</li><li>它能很好地处理文本流、按键和控制序列</li><li>它可以帮你透传按键、改键位、做工作流编排</li><li>但它不会自动把系统剪贴板图片变成某个 TUI 应用内部的附件对象</li></ul><p>所以，如果你的工作流里文本粘贴远高于图片粘贴，最稳的策略通常就是：</p><ul><li>不要为了低频图片场景牺牲高频 <code>Ctrl+V</code></li><li>单独给图片粘贴设计一个应用层快捷键</li><li>对图片重度依赖时，也接受 GUI 插件在体验上天然更占优势</li></ul><h2 id="七、跨平台都适用的配置理解"><a href="#七、跨平台都适用的配置理解" class="headerlink" title="七、跨平台都适用的配置理解"></a>七、跨平台都适用的配置理解</h2><p>不管是 Windows 还是 macOS，真正最值得理解的是这几类配置项：</p><h3 id="1）外观相关"><a href="#1）外观相关" class="headerlink" title="1）外观相关"></a>1）外观相关</h3><ul><li><code>color_scheme</code>：主题名称</li><li><code>window_background_opacity</code>：窗口背景透明度</li><li><code>text_background_opacity</code>：文本背景透明度</li><li><code>window_padding</code>：内容和边缘的留白</li></ul><p>建议：</p><ul><li>先固定一个深色主题，不要频繁切换</li><li>透明度轻一点就够了，过高会影响阅读</li></ul><h3 id="2）标签页相关"><a href="#2）标签页相关" class="headerlink" title="2）标签页相关"></a>2）标签页相关</h3><ul><li><code>enable_tab_bar</code></li><li><code>hide_tab_bar_if_only_one_tab</code></li><li><code>use_fancy_tab_bar</code></li><li><code>tab_max_width</code></li></ul><p>起步阶段我更推荐：</p><ul><li>开启标签栏</li><li>单标签时自动隐藏</li><li>关闭 fancy tab bar，保持简洁</li></ul><h3 id="3）启动相关"><a href="#3）启动相关" class="headerlink" title="3）启动相关"></a>3）启动相关</h3><ul><li><code>default_prog</code>：默认启动哪个 shell</li><li><code>default_cwd</code>：默认目录（Windows 更常用）</li><li><code>launch_menu</code>：新建标签或窗口时可选启动项</li><li><code>exit_behavior</code>：最后进程退出时窗口怎么处理</li></ul><p>如果你希望配置长期稳定，<code>launch_menu</code> 很值得加。它能让“主力 shell 稳定”和“保留其他入口”同时成立。</p><h3 id="4）体验相关"><a href="#4）体验相关" class="headerlink" title="4）体验相关"></a>4）体验相关</h3><ul><li><code>scrollback_lines</code>：历史输出回滚行数</li><li><code>adjust_window_size_when_changing_font_size</code>：调字号时是否改变窗口尺寸</li></ul><p>一般来说：</p><ul><li><code>8000-20000</code> 行足够大多数开发场景</li><li>关闭字号联动改窗体尺寸，体验更稳定</li></ul><h2 id="八、我建议的上手路径：三阶段就够了"><a href="#八、我建议的上手路径：三阶段就够了" class="headerlink" title="八、我建议的上手路径：三阶段就够了"></a>八、我建议的上手路径：三阶段就够了</h2><h3 id="阶段-A：先稳定用起来"><a href="#阶段-A：先稳定用起来" class="headerlink" title="阶段 A：先稳定用起来"></a>阶段 A：先稳定用起来</h3><ul><li>只保留默认 shell、深色主题、官方快捷键</li><li>熟悉标签页、复制粘贴、搜索、分屏</li></ul><h3 id="阶段-B：只加少量增强"><a href="#阶段-B：只加少量增强" class="headerlink" title="阶段 B：只加少量增强"></a>阶段 B：只加少量增强</h3><ul><li>增加 2-4 个高频键位</li><li>增加 <code>launch_menu</code></li><li>根据阅读习惯微调透明度、边距、滚动回溯行数</li></ul><h3 id="阶段-C：最后再做重度定制"><a href="#阶段-C：最后再做重度定制" class="headerlink" title="阶段 C：最后再做重度定制"></a>阶段 C：最后再做重度定制</h3><ul><li>字体与 fallback</li><li>状态栏与 workspace</li><li>leader key</li><li>启动时自动布局</li></ul><p>这一阶段当然很爽，但并不是“好用”的前提。</p><h2 id="九、两套我更推荐的模板"><a href="#九、两套我更推荐的模板" class="headerlink" title="九、两套我更推荐的模板"></a>九、两套我更推荐的模板</h2><p>如果你只想抄一份能用的配置，可以直接从下面两套开始。</p><h3 id="Windows-模板"><a href="#Windows-模板" class="headerlink" title="Windows 模板"></a>Windows 模板</h3><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> wezterm = <span class="built_in">require</span>(<span class="string">&quot;wezterm&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> &#123;</span><br><span class="line">  color_scheme = <span class="string">&quot;Tokyo Night Storm&quot;</span>,</span><br><span class="line">  window_background_opacity = <span class="number">0.92</span>,</span><br><span class="line">  text_background_opacity = <span class="number">1.0</span>,</span><br><span class="line">  enable_tab_bar = <span class="literal">true</span>,</span><br><span class="line">  hide_tab_bar_if_only_one_tab = <span class="literal">true</span>,</span><br><span class="line">  use_fancy_tab_bar = <span class="literal">false</span>,</span><br><span class="line">  tab_max_width = <span class="number">32</span>,</span><br><span class="line">  window_padding = &#123; left = <span class="number">8</span>, right = <span class="number">8</span>, top = <span class="number">6</span>, bottom = <span class="number">6</span> &#125;,</span><br><span class="line">  default_prog = &#123; <span class="string">&quot;powershell.exe&quot;</span>, <span class="string">&quot;-NoLogo&quot;</span> &#125;,</span><br><span class="line">  exit_behavior = <span class="string">&quot;Close&quot;</span>,</span><br><span class="line">  scrollback_lines = <span class="number">8000</span>,</span><br><span class="line">  adjust_window_size_when_changing_font_size = <span class="literal">false</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="macOS-模板"><a href="#macOS-模板" class="headerlink" title="macOS 模板"></a>macOS 模板</h3><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> wezterm = <span class="built_in">require</span>(<span class="string">&quot;wezterm&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> &#123;</span><br><span class="line">  color_scheme = <span class="string">&quot;Tokyo Night Storm&quot;</span>,</span><br><span class="line">  window_background_opacity = <span class="number">0.95</span>,</span><br><span class="line">  text_background_opacity = <span class="number">1.0</span>,</span><br><span class="line">  enable_tab_bar = <span class="literal">true</span>,</span><br><span class="line">  hide_tab_bar_if_only_one_tab = <span class="literal">true</span>,</span><br><span class="line">  use_fancy_tab_bar = <span class="literal">false</span>,</span><br><span class="line">  tab_max_width = <span class="number">32</span>,</span><br><span class="line">  window_padding = &#123; left = <span class="number">8</span>, right = <span class="number">8</span>, top = <span class="number">6</span>, bottom = <span class="number">6</span> &#125;,</span><br><span class="line">  default_prog = &#123; <span class="string">&quot;/bin/zsh&quot;</span>, <span class="string">&quot;-l&quot;</span> &#125;,</span><br><span class="line">  exit_behavior = <span class="string">&quot;Close&quot;</span>,</span><br><span class="line">  scrollback_lines = <span class="number">10000</span>,</span><br><span class="line">  adjust_window_size_when_changing_font_size = <span class="literal">false</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="十、最后给一个很实际的建议"><a href="#十、最后给一个很实际的建议" class="headerlink" title="十、最后给一个很实际的建议"></a>十、最后给一个很实际的建议</h2><p>很多人喜欢问：“WezTerm 最强配置是什么？”</p><p>我现在更认同另一种问法：你每天最常做的 3 件终端操作是什么？</p><p>如果你的答案是：</p><ul><li>开项目</li><li>跑服务</li><li>看日志</li></ul><p>那你真正需要的往往只有这些：</p><ul><li>稳定的默认 shell</li><li>好读的主题</li><li>够用的标签页和分屏</li><li>不打架的快捷键</li></ul><p>先把这 4 件事配好，WezTerm 就已经足够成为主力终端了。剩下的高级玩法，等你真的需要时再加，完全不晚。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20260317/learning/wezterm-guide-windows-macos/</id>
    <link href="https://minniexcode.github.io/memoirs/20260317/learning/wezterm-guide-windows-macos/"/>
    <published>2026-03-16T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>WezTerm 这两年基本成了我最愿意推荐给开发者的终端之一：配置文件化、标签页和分屏能力强、跨平台一致性也不错。</p>
<p>但很多教程有两个问题：要么只讲“怎么装”，要么一上来就把配置堆得很满。对大多数人来说，真正需要的不是一份炫技配置，而是一套稳定可落地的起步方案。</p>
<p>这篇文章把 Windows 和 macOS 放在一起写，目标很直接：先让你用起来，再决定要不要继续折腾。</p>]]>
    </summary>
    <title>WezTerm Guide：Windows 和 macOS 的入门、配置与日常工作流</title>
    <updated>2026-03-17T12:54:58.852Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="AI与工具" scheme="https://minniexcode.github.io/memoirs/categories/AI%E4%B8%8E%E5%B7%A5%E5%85%B7/"/>
    <category term="AI" scheme="https://minniexcode.github.io/memoirs/tags/AI/"/>
    <category term="Coding Agent" scheme="https://minniexcode.github.io/memoirs/tags/Coding-Agent/"/>
    <category term="Tooling" scheme="https://minniexcode.github.io/memoirs/tags/Tooling/"/>
    <category term="Workflow" scheme="https://minniexcode.github.io/memoirs/tags/Workflow/"/>
    <category term="OpenCode" scheme="https://minniexcode.github.io/memoirs/tags/OpenCode/"/>
    <content>
      <![CDATA[<p>最近我把 OpenCode 当成主力 coding agent 在用。网上已经有不少“功能总览”文章，但很多内容像产品说明书：信息全，落地弱。</p><p>这篇我按“真实开发流程”重写一版，重点是：你今天装好后，怎么在项目里稳定产出，而不是只会敲 <code>/help</code>。</p><span id="more"></span><p><img src="https://opencode.ai/docs/_astro/screenshot.CQjBbRyJ_1dLadc.webp" alt="OpenCode 官方 TUI 截图"></p><blockquote><p>图源：OpenCode 官方文档 Intro 页</p></blockquote><hr><h2 id="一、先讲结论：OpenCode-适合什么人"><a href="#一、先讲结论：OpenCode-适合什么人" class="headerlink" title="一、先讲结论：OpenCode 适合什么人"></a>一、先讲结论：OpenCode 适合什么人</h2><p>如果你符合下面 3 条中的 2 条，OpenCode 会比“IDE 内嵌 AI 聊天框”更好用：</p><ol><li>你愿意在终端里工作，接受命令行思维。</li><li>你希望 agent 不止回答问题，还能“读写文件 + 跑命令 + 调外部工具”。</li><li>你在意可控性（权限、规则、可审计、可自动化），而不是“一键魔法”。</li></ol><p>简单理解：</p><ul><li>Cursor&#x2F;Copilot 更像“编辑器里增强写代码”。</li><li>OpenCode 更像“可编排的工程代理平台”。</li></ul><hr><h2 id="二、安装与启动：3-分钟跑起来"><a href="#二、安装与启动：3-分钟跑起来" class="headerlink" title="二、安装与启动：3 分钟跑起来"></a>二、安装与启动：3 分钟跑起来</h2><p>官方安装方式很多，这里只保留常用命令。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 一键安装</span></span><br><span class="line">curl -fsSL https://opencode.ai/install | bash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者 npm 全局安装</span></span><br><span class="line">npm install -g opencode-ai</span><br></pre></td></tr></table></figure><p>启动方式：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在当前目录启动 TUI</span></span><br><span class="line">opencode</span><br><span class="line"></span><br><span class="line"><span class="comment"># 指定项目目录</span></span><br><span class="line">opencode /path/to/project</span><br><span class="line"></span><br><span class="line"><span class="comment"># 非交互执行（脚本/CI 常用）</span></span><br><span class="line">opencode run <span class="string">&quot;Explain this repository architecture&quot;</span></span><br></pre></td></tr></table></figure><p>第一次进入建议按这个顺序：</p><ol><li><code>/connect</code> 配 provider。</li><li><code>/models</code> 选模型。</li><li><code>/init</code> 让它生成项目 <code>AGENTS.md</code>。</li></ol><p><code>/init</code> 非常关键，它决定 agent 在你的仓库里是不是“懂规矩”。</p><hr><h2 id="三、你每天最常用的-6-个能力（带场景）"><a href="#三、你每天最常用的-6-个能力（带场景）" class="headerlink" title="三、你每天最常用的 6 个能力（带场景）"></a>三、你每天最常用的 6 个能力（带场景）</h2><h3 id="1-文件：把上下文喂准"><a href="#1-文件：把上下文喂准" class="headerlink" title="1) @文件：把上下文喂准"></a>1) <code>@文件</code>：把上下文喂准</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">解释一下 @src/api/auth.ts 的刷新 token 流程</span><br></pre></td></tr></table></figure><p>它会把文件内容注入上下文，避免 agent 在大仓库里“猜”。</p><h3 id="2-命令：把终端输出直接回填"><a href="#2-命令：把终端输出直接回填" class="headerlink" title="2) !命令：把终端输出直接回填"></a>2) <code>!命令</code>：把终端输出直接回填</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">!git status</span><br><span class="line">!npm run build</span><br></pre></td></tr></table></figure><p>适合把“报错现场”直接给 agent，不需要复制粘贴。</p><h3 id="3-undo-redo：可逆编辑"><a href="#3-undo-redo：可逆编辑" class="headerlink" title="3) /undo + /redo：可逆编辑"></a>3) <code>/undo</code> + <code>/redo</code>：可逆编辑</h3><p>OpenCode 的撤销&#x2F;重做依赖 Git（仓库必须是 git repo）。</p><ul><li><code>/undo</code>：撤回最后一次消息及其文件修改。</li><li><code>/redo</code>：恢复撤销的内容。</li></ul><p>这个是我敢让 agent 改代码的底气之一。</p><h3 id="4-compact：上下文太长时续命"><a href="#4-compact：上下文太长时续命" class="headerlink" title="4) /compact：上下文太长时续命"></a>4) <code>/compact</code>：上下文太长时续命</h3><p>长会话一定会碰到 token 压力。<code>/compact</code> 可以保留关键状态，压掉无关历史。</p><h3 id="5-details：审计工具调用"><a href="#5-details：审计工具调用" class="headerlink" title="5) /details：审计工具调用"></a>5) <code>/details</code>：审计工具调用</h3><p>你怀疑 agent 为什么做出某个修改时，打开 <code>/details</code> 看它调了哪些工具、传了什么参数。</p><h3 id="6-editor：写长-prompt-时救命"><a href="#6-editor：写长-prompt-时救命" class="headerlink" title="6) /editor：写长 prompt 时救命"></a>6) <code>/editor</code>：写长 prompt 时救命</h3><p>复杂任务建议进外部编辑器写需求（尤其是带验收标准时）。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> EDITOR=<span class="string">&quot;code --wait&quot;</span></span><br></pre></td></tr></table></figure><hr><h2 id="四、从“会用”到“好用”的关键：Plan-Build"><a href="#四、从“会用”到“好用”的关键：Plan-Build" class="headerlink" title="四、从“会用”到“好用”的关键：Plan -&gt; Build"></a>四、从“会用”到“好用”的关键：Plan -&gt; Build</h2><p>很多人一上来就让 agent 直接改，结果是：能改，但经常改偏。</p><p>更稳的流程：</p><ol><li>用 <code>plan</code> 模式先产方案（默认对 edit&#x2F;bash 更谨慎）。</li><li>人工确认边界、风险点。</li><li>切回 <code>build</code> 模式执行。</li></ol><p>你可以把它想成：</p><ul><li><code>plan</code> &#x3D; 资深同事做方案评审。</li><li><code>build</code> &#x3D; 开工落地并提交可运行结果。</li></ul><p>一个实用 prompt：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">先不要改代码。先给我：</span><br><span class="line">1) 受影响文件清单</span><br><span class="line">2) 迁移步骤</span><br><span class="line">3) 风险点和回滚策略</span><br><span class="line">4) 最小验证命令</span><br></pre></td></tr></table></figure><hr><h2 id="五、CLI-子命令怎么选（不是每次都开-TUI）"><a href="#五、CLI-子命令怎么选（不是每次都开-TUI）" class="headerlink" title="五、CLI 子命令怎么选（不是每次都开 TUI）"></a>五、CLI 子命令怎么选（不是每次都开 TUI）</h2><h3 id="适合人机协作：TUI"><a href="#适合人机协作：TUI" class="headerlink" title="适合人机协作：TUI"></a>适合人机协作：TUI</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">opencode</span><br></pre></td></tr></table></figure><h3 id="适合自动化脚本：run"><a href="#适合自动化脚本：run" class="headerlink" title="适合自动化脚本：run"></a>适合自动化脚本：<code>run</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">opencode run <span class="string">&quot;review latest changes and suggest improvements&quot;</span></span><br></pre></td></tr></table></figure><h3 id="适合服务化：serve-web"><a href="#适合服务化：serve-web" class="headerlink" title="适合服务化：serve &#x2F; web"></a>适合服务化：<code>serve</code> &#x2F; <code>web</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">opencode serve</span><br><span class="line">opencode web</span><br></pre></td></tr></table></figure><h3 id="适合远程附着：attach"><a href="#适合远程附着：attach" class="headerlink" title="适合远程附着：attach"></a>适合远程附着：<code>attach</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">opencode attach http://127.0.0.1:4096</span><br></pre></td></tr></table></figure><h3 id="适合-MCP-管理：opencode-mcp"><a href="#适合-MCP-管理：opencode-mcp" class="headerlink" title="适合 MCP 管理：opencode mcp ..."></a>适合 MCP 管理：<code>opencode mcp ...</code></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">opencode mcp add</span><br><span class="line">opencode mcp list</span><br><span class="line">opencode mcp auth &lt;name&gt;</span><br><span class="line">opencode mcp debug &lt;name&gt;</span><br></pre></td></tr></table></figure><h3 id="适合会话与成本复盘"><a href="#适合会话与成本复盘" class="headerlink" title="适合会话与成本复盘"></a>适合会话与成本复盘</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">opencode session list</span><br><span class="line">opencode stats --days 7 --models 10</span><br></pre></td></tr></table></figure><hr><h2 id="六、权限系统：让-agent-“能干活但不乱来”"><a href="#六、权限系统：让-agent-“能干活但不乱来”" class="headerlink" title="六、权限系统：让 agent “能干活但不乱来”"></a>六、权限系统：让 agent “能干活但不乱来”</h2><p>权限只有三种动作：</p><ul><li><code>allow</code>：直接执行</li><li><code>ask</code>：执行前确认</li><li><code>deny</code>：拒绝</li></ul><p>推荐起步策略：高风险工具 <code>ask</code>，低风险默认 <code>allow</code>。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;$schema&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://opencode.ai/config.json&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permission&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;read&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;git status*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;git diff*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;npm run build*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;rm *&quot;</span><span class="punctuation">:</span> <span class="string">&quot;deny&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;edit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;webfetch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>两个容易被忽略的点：</p><ol><li>规则是“最后匹配生效”（last match wins）。</li><li><code>external_directory</code> 和 <code>doom_loop</code> 是额外安全阀，默认建议保留 <code>ask</code>。</li></ol><hr><h2 id="七、MCP-才是-OpenCode-的上限（结合你的组合）"><a href="#七、MCP-才是-OpenCode-的上限（结合你的组合）" class="headerlink" title="七、MCP 才是 OpenCode 的上限（结合你的组合）"></a>七、MCP 才是 OpenCode 的上限（结合你的组合）</h2><p>你现在这组 <code>context7 + gh_grep + exa</code> 很实用，我建议按“信息可靠性分层”用：</p><ol><li><strong>官方定义</strong>先走 <code>context7</code>。</li><li><strong>真实代码范式</strong>走 <code>gh_grep</code>。</li><li><strong>时效信息补充</strong>走 <code>exa</code>。</li></ol><h3 id="一个可直接用的-MCP-配置示例"><a href="#一个可直接用的-MCP-配置示例" class="headerlink" title="一个可直接用的 MCP 配置示例"></a>一个可直接用的 MCP 配置示例</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;$schema&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://opencode.ai/config.json&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;mcp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;context7&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;remote&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://mcp.context7.com/mcp&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gh_grep&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;remote&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://mcp.grep.app&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permission&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;context7_*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gh_grep_*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;websearch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>如果你要启用 Exa Web Search（按官方说明）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">OPENCODE_ENABLE_EXA=1 opencode</span><br></pre></td></tr></table></figure><hr><h2 id="八、把“低幻觉”流程写进提示词"><a href="#八、把“低幻觉”流程写进提示词" class="headerlink" title="八、把“低幻觉”流程写进提示词"></a>八、把“低幻觉”流程写进提示词</h2><p>你原文里这个思路非常对，我给你扩展成可复用模板。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">请按以下顺序回答：</span><br><span class="line">1) 先用 context7 给官方文档结论，并附文档链接。</span><br><span class="line">2) 再用 gh_grep 给 2-3 个真实仓库代码示例（附仓库路径）。</span><br><span class="line">3) 若涉及时效性信息，再用 exa 补充，并标注检索时间。</span><br><span class="line">4) 最后给出“我在当前项目该怎么做”的最小落地步骤。</span><br></pre></td></tr></table></figure><p>这样做的好处是：</p><ul><li>你能看见“依据链”，不是只看结论。</li><li>讨论能落到“可执行步骤”，不是空话。</li></ul><hr><h2 id="九、Rules：为什么-AGENTS-md-值得认真写"><a href="#九、Rules：为什么-AGENTS-md-值得认真写" class="headerlink" title="九、Rules：为什么 AGENTS.md 值得认真写"></a>九、Rules：为什么 <code>AGENTS.md</code> 值得认真写</h2><p>OpenCode 会读项目规则文件。你把规则写清楚，等于在降低返工率。</p><p>建议 <code>AGENTS.md</code> 至少包含：</p><ol><li>构建&#x2F;测试命令（尤其单测命令）。</li><li>目录边界（哪些文件夹可改，哪些只读）。</li><li>代码风格和命名规范。</li><li>提交与验证标准。</li></ol><p>在团队里，<code>AGENTS.md</code> 其实就是“给 agent 的 CONTRIBUTING.md”。</p><hr><h2 id="十、分享与安全：能分享，但默认要克制"><a href="#十、分享与安全：能分享，但默认要克制" class="headerlink" title="十、分享与安全：能分享，但默认要克制"></a>十、分享与安全：能分享，但默认要克制</h2><p><code>/share</code> 很方便做协作复盘，但要注意链接是公开可访问。</p><p>建议默认：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;share&quot;</span><span class="punctuation">:</span> <span class="string">&quot;manual&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>涉敏项目直接：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;share&quot;</span><span class="punctuation">:</span> <span class="string">&quot;disabled&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>另外，跑 <code>serve</code> &#x2F; <code>web</code> 时建议加基本鉴权：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> OPENCODE_SERVER_PASSWORD=<span class="string">&quot;your-strong-password&quot;</span></span><br><span class="line"><span class="built_in">export</span> OPENCODE_SERVER_USERNAME=<span class="string">&quot;opencode&quot;</span></span><br></pre></td></tr></table></figure><hr><h2 id="十一、一个我自己更推荐的-opencode-json-起步模板"><a href="#十一、一个我自己更推荐的-opencode-json-起步模板" class="headerlink" title="十一、一个我自己更推荐的 opencode.json 起步模板"></a>十一、一个我自己更推荐的 <code>opencode.json</code> 起步模板</h2><p>下面这份配置偏“日常开发安全默认 + MCP 增强”。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;$schema&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://opencode.ai/config.json&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;model&quot;</span><span class="punctuation">:</span> <span class="string">&quot;heiyucode/gpt-5.3-codex&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;small_model&quot;</span><span class="punctuation">:</span> <span class="string">&quot;heiyucode/gpt-5.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;share&quot;</span><span class="punctuation">:</span> <span class="string">&quot;manual&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permission&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;read&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;webfetch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;websearch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;edit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;git status*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;git diff*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;npm run build*&quot;</span><span class="punctuation">:</span> <span class="string">&quot;allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;rm *&quot;</span><span class="punctuation">:</span> <span class="string">&quot;deny&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;git push *&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;agent&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;plan&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;permission&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;edit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;bash&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ask&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;instructions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;AGENTS.md&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>你可以再按项目微调，比如把 <code>npm test*</code> 放开，或者把某些目录的 <code>edit</code> 设为 <code>deny</code>。</p><hr><h2 id="十二、一个完整任务示例（端到端）"><a href="#十二、一个完整任务示例（端到端）" class="headerlink" title="十二、一个完整任务示例（端到端）"></a>十二、一个完整任务示例（端到端）</h2><p>目标：给博客项目加“文章字数统计”并验证部署。</p><p>建议流程：</p><ol><li><code>plan</code> 模式描述需求和约束。</li><li>明确“只改 <code>source/</code> 与 root config，不改主题子模块”。</li><li><code>build</code> 模式执行修改。</li><li>跑 <code>npm run build</code> 验证。</li><li>人工抽查页面。</li><li>最后再 <code>npm run deploy</code>。</li></ol><p>对应 prompt（可直接改后复用）：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">先进入 plan 模式，不要改代码。</span><br><span class="line">目标：给 Hexo 博客增加文章字数与阅读时长展示。</span><br><span class="line">约束：优先改 root config 或内容，不修改 themes/stellar 子模块。</span><br><span class="line">请先输出：</span><br><span class="line">1) 变更文件清单</span><br><span class="line">2) 风险点</span><br><span class="line">3) 验证步骤</span><br><span class="line">确认后我再让你切 build 执行。</span><br></pre></td></tr></table></figure><hr><h2 id="十三、常见坑（我自己踩过）"><a href="#十三、常见坑（我自己踩过）" class="headerlink" title="十三、常见坑（我自己踩过）"></a>十三、常见坑（我自己踩过）</h2><ol><li><strong>没写规则就开干</strong>：结果是 agent 改了你不想改的目录。</li><li><strong>权限全 allow</strong>：短期爽，长期会有不可控风险。</li><li><strong>MCP 开太多</strong>：上下文被工具说明吃掉，反而变慢变贵。</li><li><strong>不做会话压缩</strong>：长会话后期质量明显下降。</li><li><strong>只要答案，不要依据</strong>：最后很难判断对错，只能“凭感觉相信”。</li></ol><hr><h2 id="十四、最后的使用建议（针对你这种重度用户）"><a href="#十四、最后的使用建议（针对你这种重度用户）" class="headerlink" title="十四、最后的使用建议（针对你这种重度用户）"></a>十四、最后的使用建议（针对你这种重度用户）</h2><p>如果你已经在稳定使用 OpenCode，我建议把目标从“会用命令”升级到“可复制的工作流”：</p><ol><li>固化 <code>AGENTS.md</code>（项目规则）。</li><li>固化 <code>opencode.json</code>（权限与模型策略）。</li><li>固化提问模板（context7 -&gt; gh_grep -&gt; exa）。</li><li>固化验收步骤（build&#x2F;test&#x2F;lint&#x2F;手工检查）。</li></ol><p>当这 4 件事稳定后，OpenCode 就不只是“AI 助手”，而是你工程流程的一部分。</p><hr><h2 id="参考资料（官方）"><a href="#参考资料（官方）" class="headerlink" title="参考资料（官方）"></a>参考资料（官方）</h2><ul><li>Intro: <a href="https://opencode.ai/docs/">https://opencode.ai/docs/</a></li><li>TUI: <a href="https://opencode.ai/docs/tui/">https://opencode.ai/docs/tui/</a></li><li>CLI: <a href="https://opencode.ai/docs/cli/">https://opencode.ai/docs/cli/</a></li><li>Config: <a href="https://opencode.ai/docs/config/">https://opencode.ai/docs/config/</a></li><li>Tools: <a href="https://opencode.ai/docs/tools/">https://opencode.ai/docs/tools/</a></li><li>Permissions: <a href="https://opencode.ai/docs/permissions/">https://opencode.ai/docs/permissions/</a></li><li>Agents: <a href="https://opencode.ai/docs/agents/">https://opencode.ai/docs/agents/</a></li><li>Commands: <a href="https://opencode.ai/docs/commands/">https://opencode.ai/docs/commands/</a></li><li>MCP Servers: <a href="https://opencode.ai/docs/mcp-servers/">https://opencode.ai/docs/mcp-servers/</a></li></ul>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20260315/learning/opencode-in-action-2026/</id>
    <link href="https://minniexcode.github.io/memoirs/20260315/learning/opencode-in-action-2026/"/>
    <published>2026-03-14T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>最近我把 OpenCode 当成主力 coding agent 在用。网上已经有不少“功能总览”文章，但很多内容像产品说明书：信息全，落地弱。</p>
<p>这篇我按“真实开发流程”重写一版，重点是：你今天装好后，怎么在项目里稳定产出，而不是只会敲 <code>/help</code>。</p>]]>
    </summary>
    <title>OpenCode 实战指南（2026）：从能用到好用</title>
    <updated>2026-03-16T16:27:30.441Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="项目实践" scheme="https://minniexcode.github.io/memoirs/categories/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5/"/>
    <category term="T-Systems" scheme="https://minniexcode.github.io/memoirs/tags/T-Systems/"/>
    <category term="OneApp" scheme="https://minniexcode.github.io/memoirs/tags/OneApp/"/>
    <category term="Architecture" scheme="https://minniexcode.github.io/memoirs/tags/Architecture/"/>
    <category term="Performance" scheme="https://minniexcode.github.io/memoirs/tags/Performance/"/>
    <content>
      <![CDATA[<p>本方案包含三部分：性能与内存优化理论、架构改造（模块化完善）与内存优化实战，并补充性能监控方案、成本与风险评估。</p><h2 id="理论基础：内存模型、排查方法与治理策略"><a href="#理论基础：内存模型、排查方法与治理策略" class="headerlink" title="理论基础：内存模型、排查方法与治理策略"></a>理论基础：内存模型、排查方法与治理策略</h2><h3 id="1-内存构成（跨端共识）"><a href="#1-内存构成（跨端共识）" class="headerlink" title="1) 内存构成（跨端共识）"></a>1) 内存构成（跨端共识）</h3><ul><li>Flutter&#x2F;Dart：Dart Heap、Native Heap、GPU 纹理、ImageCache、Skia 资源</li><li>Android：Java Heap、Native<br>Heap、Graphics&#x2F;GL、Ashmem&#x2F;匿名共享、文件映射</li><li>iOS：Heap、VM、Metal&#x2F;纹理、ImageIO 解码缓存</li></ul><h3 id="2-常见内存问题类型"><a href="#2-常见内存问题类型" class="headerlink" title="2) 常见内存问题类型"></a>2) 常见内存问题类型</h3><ul><li>泄漏：对象生命周期失控，无法被 GC&#x2F;ARC 回收</li><li>峰值过高：大图、视频、批量解析造成瞬时激增</li><li>缓存失控：图片&#x2F;列表&#x2F;业务缓存无上限或无 TTL</li><li>碎片与抖动：高频创建大对象导致频繁 GC&#x2F;ARC</li></ul><h3 id="3-使用-DevTools-排查内存问题（流程化）"><a href="#3-使用-DevTools-排查内存问题（流程化）" class="headerlink" title="3) 使用 DevTools 排查内存问题（流程化）"></a>3) 使用 DevTools 排查内存问题（流程化）</h3><ul><li>基线建立：进入核心场景（首页、车控、地图&#x2F;媒体、个人中心）分别记录内存曲线与峰值</li><li>事件复现：复现场景触发后，观察内存是否回落到基线附近</li><li>快照对比：多次快照对比，锁定增长中的对象类型与数量</li><li>引用链分析：定位“谁在持有对象”，找出未释放的根因</li><li>GC 验证：触发 GC 后仍不回落，优先排查泄漏与缓存失控</li><li>输出结论：标注对象类型、持有链路、场景、复现步骤与修复建议</li></ul><h3 id="3-1-内存自动化回归（AirtestIDE）"><a href="#3-1-内存自动化回归（AirtestIDE）" class="headerlink" title="3.1) 内存自动化回归（AirtestIDE）"></a>3.1) 内存自动化回归（AirtestIDE）</h3><ul><li>目标<ul><li>替代人工点击，稳定复现核心场景并采集内存曲线</li><li>形成可重复的回归路径与对比基线</li></ul></li><li>脚本覆盖<ul><li>首页 → 车控 → 地图&#x2F;媒体 → 个人中心 → 返回首页</li><li>前后台切换、列表滚动、资源密集操作路径</li></ul></li><li>采集策略<ul><li>启动前清理缓存，保证基线一致</li><li>场景进入与退出分别记录峰值与回落</li><li>与内存告警联动，定位异常场景</li></ul></li><li>产出物<ul><li>Airtest 脚本与用例集</li><li>自动化内存基线报告与回归对比报告</li></ul></li></ul><h3 id="4-僵尸对象与内存不释放的判断方法"><a href="#4-僵尸对象与内存不释放的判断方法" class="headerlink" title="4) 僵尸对象与内存不释放的判断方法"></a>4) 僵尸对象与内存不释放的判断方法</h3><ul><li>僵尸对象特征：页面退出后对象仍在内存中、数量持续上升、被全局单例或静态集合持有</li><li>常见来源：未取消订阅、缓存未清理、生命周期未对齐、异步任务回调未释放</li><li>判断方式：同场景多次进入退出，观察对象数量是否线性增长</li><li>处理策略：明确生命周期边界、集中释放入口、限制缓存上限、引入弱引用或定时清理</li></ul><h3 id="5-解决思路的优先级"><a href="#5-解决思路的优先级" class="headerlink" title="5) 解决思路的优先级"></a>5) 解决思路的优先级</h3><ul><li>限制缓存上限与生命周期（优先）</li><li>下采样与降规格加载（优先）</li><li>资源释放与生命周期治理（优先）</li><li>计算迁移与异步分批处理（中）</li><li>架构层优化与模块拆分（长期）</li></ul><h3 id="6-性能监控方案（内存与卡顿）"><a href="#6-性能监控方案（内存与卡顿）" class="headerlink" title="6) 性能监控方案（内存与卡顿）"></a>6) 性能监控方案（内存与卡顿）</h3><ul><li>指标体系<ul><li>内存：PSS、Dart Heap、Native Heap、峰值与稳定值、低内存告警次数</li><li>卡顿：FPS、Jank 帧占比、平均&#x2F;90 分位帧耗时、UI&#x2F;渲染线程耗时、GC 频次</li><li>体验：页面首帧时间、路由切换耗时、列表滑动掉帧率</li><li>稳定性：OOM、Crash、页面白屏&#x2F;卡死</li></ul></li><li>采集策略<ul><li>核心场景高频采样，非核心场景抽样采集</li><li>前后台切换、页面进出、资源密集操作、帧耗时异常触发关键事件</li></ul></li><li>上报链路<ul><li>本地缓存 + 批量上报 + 失败重试</li><li>与现有内存告警联动，形成“告警—定位—修复—验证”闭环</li></ul></li><li>产出物<ul><li>基线报告、卡顿场景榜单、专项优化清单、回归对比报告</li></ul></li></ul><h3 id="7-代码层面的漏洞与泄漏高发点（排查清单）"><a href="#7-代码层面的漏洞与泄漏高发点（排查清单）" class="headerlink" title="7) 代码层面的漏洞与泄漏高发点（排查清单）"></a>7) 代码层面的漏洞与泄漏高发点（排查清单）</h3><ul><li>生命周期与订阅<ul><li>StreamSubscription&#x2F;RxDart&#x2F;EventBus 未取消订阅</li><li>Timer&#x2F;Isolate&#x2F;Compute 任务未停止或回收</li><li>MethodChannel&#x2F;EventChannel 监听未释放</li></ul></li><li>控制器与资源<ul><li>AnimationController&#x2F;ScrollController&#x2F;TextEditingController&#x2F;FocusNode<br>未 dispose</li><li>视频&#x2F;地图&#x2F;纹理资源未 stop&#x2F;destroy</li></ul></li><li>缓存与引用<ul><li>单例&#x2F;静态集合持有 BuildContext&#x2F;Widget&#x2F;State</li><li>图片&#x2F;业务缓存无上限或无 TTL，列表 keepAlive 过度使用</li></ul></li><li>主线程阻塞<ul><li>大 JSON 解析&#x2F;排序&#x2F;解码在主线程执行</li><li>同步磁盘 IO 或大量 setState&#x2F;重建导致帧耗时激增</li></ul></li></ul><p>排查要点</p><ul><li>同场景多次进入退出，对象数量应稳定或回落</li><li>退出页面后仍增长的对象，优先回溯持有链与订阅源</li><li>发生卡顿时定位长帧对应的函数与调用链，先移出主线程</li></ul><h3 id="8-常见内存问题与解决方案（摘要）"><a href="#8-常见内存问题与解决方案（摘要）" class="headerlink" title="8) 常见内存问题与解决方案（摘要）"></a>8) 常见内存问题与解决方案（摘要）</h3><ul><li>图片与纹理占用过大 → 统一下采样、限制缓存上限、资源按需加载</li><li>列表长期驻留 → 分页与复用、减少 keepAlive、页面退出清理</li><li>业务缓存失控 → TTL 与容量上限、冷热分层、统一清理入口</li><li>异步任务未释放 → 统一取消订阅与回调清理</li><li>资源引擎未释放 → 提供 destroy&#x2F;stop 入口并强制调用</li></ul><h3 id="9-验收标准（以基线报告为准）"><a href="#9-验收标准（以基线报告为准）" class="headerlink" title="9) 验收标准（以基线报告为准）"></a>9) 验收标准（以基线报告为准）</h3><ul><li>内存指标<ul><li>核心场景峰值下降、退场后回落至基线区间</li><li>低内存告警次数显著下降</li></ul></li><li>卡顿指标<ul><li>核心场景 Jank 帧占比下降、长帧数量显著降低</li><li>滑动与路由切换场景的帧耗时回落至基线区间</li></ul></li><li>稳定性指标<ul><li>OOM 与相关 Crash 明显下降</li></ul></li><li>过程指标<ul><li>形成可复用的排查流程与问题归因闭环</li><li>代码层泄漏清单闭环，关键模块释放入口覆盖率提升</li></ul></li></ul><h2 id="架构改造：模块化完善（高内聚低耦合）"><a href="#架构改造：模块化完善（高内聚低耦合）" class="headerlink" title="架构改造：模块化完善（高内聚低耦合）"></a>架构改造：模块化完善（高内聚低耦合）</h2><h3 id="1-模块分层与依赖方向"><a href="#1-模块分层与依赖方向" class="headerlink" title="1) 模块分层与依赖方向"></a>1) 模块分层与依赖方向</h3><ul><li>basic_*：基础设施层，只依赖基础能力，不依赖业务</li><li>kit_*：通用工具与能力，允许被 app_*&#x2F;clr_* 依赖</li><li>ui_*：UI 组件层，不依赖 app_*，仅依赖 basic_*&#x2F;kit_*</li><li>clr_*：服务 SDK 层，对外暴露 Facade，内部可依赖 basic_*&#x2F;kit_*</li><li>app_*：业务模块层，只依赖<br>basic_*&#x2F;kit_*&#x2F;ui_*&#x2F;clr_*，禁止互相强依赖</li></ul><p>现有模块依赖关系图（基于 pubspec.yaml 扫描）</p><p>应用入口与本地模块归属</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">graph TB</span><br><span class="line">    subgraph 应用入口层</span><br><span class="line">        MAIN[oneapp_main]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    subgraph 业务模块</span><br><span class="line">        ACC[app_account]</span><br><span class="line">        SET[app_setting]</span><br><span class="line">        COM[oneapp_community]</span><br><span class="line">        MEM[oneapp_membership]</span><br><span class="line">        AFTER[oneapp_after_sales]</span><br><span class="line">        CAR_SALES[oneapp_car_sales]</span><br><span class="line">        TP[oneapp_touch_point]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    subgraph 服务组件</span><br><span class="line">        CONF[app_configuration]</span><br><span class="line">        COMP[oneapp_companion]</span><br><span class="line">        POPUP[oneapp_popup]</span><br><span class="line">        SHARE[share_to_friends]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    subgraph 车辆相关</span><br><span class="line">        CAR[app_car]</span><br><span class="line">        CHARGE[app_charging]</span><br><span class="line">        WALLBOX[app_wallbox]</span><br><span class="line">        WATCH[app_carwatcher]</span><br><span class="line">        AVATAR[app_avatar]</span><br><span class="line">        ORDER[app_order]</span><br><span class="line">        TOUCHGO[app_touchgo]</span><br><span class="line">        VUR[app_vur]</span><br><span class="line">        MAINT[app_maintenance]</span><br><span class="line">        RPA[app_rpa]</span><br><span class="line">        COMPOSER[app_composer]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    MAIN --&gt; ACC</span><br><span class="line">    MAIN --&gt; SET</span><br><span class="line">    MAIN --&gt; COM</span><br><span class="line">    MAIN --&gt; MEM</span><br><span class="line">    MAIN --&gt; AFTER</span><br><span class="line">    MAIN --&gt; CAR_SALES</span><br><span class="line">    MAIN --&gt; TP</span><br><span class="line">    MAIN --&gt; CONF</span><br><span class="line">    MAIN --&gt; COMP</span><br><span class="line">    MAIN --&gt; POPUP</span><br><span class="line">    MAIN --&gt; SHARE</span><br><span class="line">    MAIN --&gt; CAR</span><br><span class="line">    MAIN --&gt; CHARGE</span><br><span class="line">    MAIN --&gt; WALLBOX</span><br><span class="line">    MAIN --&gt; WATCH</span><br><span class="line">    MAIN --&gt; AVATAR</span><br><span class="line">    MAIN --&gt; ORDER</span><br><span class="line">    MAIN --&gt; TOUCHGO</span><br><span class="line">    MAIN --&gt; VUR</span><br><span class="line">    MAIN --&gt; MAINT</span><br><span class="line">    MAIN --&gt; RPA</span><br><span class="line">    MAIN --&gt; COMPOSER</span><br></pre></td></tr></table></figure><p>业务模块内部依赖</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">graph TB</span><br><span class="line">    COM[oneapp_community] --&gt; MEM[oneapp_membership]</span><br><span class="line">    COM --&gt; CAR_SALES[oneapp_car_sales]</span><br><span class="line">    COM --&gt; TP[oneapp_touch_point]</span><br><span class="line">    COM --&gt; AFTER[oneapp_after_sales]</span><br><span class="line">    COM --&gt; SHARE[share_to_friends]</span><br><span class="line"></span><br><span class="line">    MEM --&gt; COM</span><br><span class="line">    MEM --&gt; AFTER</span><br><span class="line">    MEM --&gt; ACC[app_account]</span><br><span class="line"></span><br><span class="line">    CAR_SALES --&gt; CONF[app_configuration]</span><br><span class="line">    CAR_SALES --&gt; AFTER</span><br><span class="line">    CAR_SALES --&gt; TP</span><br><span class="line"></span><br><span class="line">    CONF --&gt; CAR_SALES</span><br><span class="line"></span><br><span class="line">    SET[app_setting] --&gt; MEM</span><br></pre></td></tr></table></figure><p>业务模块与 CLR 依赖</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">graph TB</span><br><span class="line">    subgraph 业务模块</span><br><span class="line">        ACC[app_account]</span><br><span class="line">        SET[app_setting]</span><br><span class="line">        AFTER[oneapp_after_sales]</span><br><span class="line">        CONF[app_configuration]</span><br><span class="line">        COMP[oneapp_companion]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    subgraph CLR</span><br><span class="line">        MNO_C[clr_mno]</span><br><span class="line">        SET_C[clr_setting]</span><br><span class="line">        MEDIA_C[clr_media]</span><br><span class="line">        GEO_C[clr_geo]</span><br><span class="line">        CONF_C[clr_configuration]</span><br><span class="line">        COMP_C[clr_companion]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    ACC --&gt; MNO_C</span><br><span class="line">    SET --&gt; SET_C</span><br><span class="line">    SET --&gt; MEDIA_C</span><br><span class="line">    AFTER --&gt; GEO_C</span><br><span class="line">    CONF --&gt; CONF_C</span><br><span class="line">    COMP --&gt; COMP_C</span><br></pre></td></tr></table></figure><p>车辆模块与 CLR 依赖</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">graph TB</span><br><span class="line">    subgraph 车辆模块</span><br><span class="line">        CAR[app_car]</span><br><span class="line">        CHARGE[app_charging]</span><br><span class="line">        MAINT[app_maintenance]</span><br><span class="line">        WATCH[app_carwatcher]</span><br><span class="line">        AVATAR[app_avatar]</span><br><span class="line">        ORDER[app_order]</span><br><span class="line">        TOUCHGO[app_touchgo]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    subgraph CLR</span><br><span class="line">        GEO_C[clr_geo]</span><br><span class="line">        CHARGE_C[clr_charging]</span><br><span class="line">        MAINT_C[clr_maintenance]</span><br><span class="line">        WATCH_C[clr_carwatcher]</span><br><span class="line">        TOUCHGO_C[clr_touchgo]</span><br><span class="line">        ORDER_C[clr_order]</span><br><span class="line">        PAY_C[clr_payment]</span><br><span class="line">        ACC_C[clr_account]</span><br><span class="line">        MNO_C[clr_mno]</span><br><span class="line">        VOICE_C[clr_voiceclone]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    CAR --&gt; GEO_C</span><br><span class="line">    CAR --&gt; CHARGE_C</span><br><span class="line">    CAR --&gt; MNO_C</span><br><span class="line"></span><br><span class="line">    CHARGE --&gt; CHARGE_C</span><br><span class="line">    CHARGE --&gt; GEO_C</span><br><span class="line">    CHARGE --&gt; PAY_C</span><br><span class="line"></span><br><span class="line">    MAINT --&gt; MAINT_C</span><br><span class="line">    MAINT --&gt; GEO_C</span><br><span class="line"></span><br><span class="line">    WATCH --&gt; WATCH_C</span><br><span class="line">    WATCH --&gt; GEO_C</span><br><span class="line"></span><br><span class="line">    AVATAR --&gt; ORDER_C</span><br><span class="line">    AVATAR --&gt; PAY_C</span><br><span class="line">    AVATAR --&gt; VOICE_C</span><br><span class="line"></span><br><span class="line">    ORDER --&gt; ORDER_C</span><br><span class="line">    ORDER --&gt; PAY_C</span><br><span class="line">    ORDER --&gt; ACC_C</span><br><span class="line"></span><br><span class="line">    TOUCHGO --&gt; GEO_C</span><br><span class="line">    TOUCHGO --&gt; TOUCHGO_C</span><br></pre></td></tr></table></figure><p>UI 模块与 CLR 依赖</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">graph TB</span><br><span class="line">    subgraph UI</span><br><span class="line">        UI_BASIC[ui_basic]</span><br><span class="line">        UI_BUSINESS[ui_business]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    subgraph CLR</span><br><span class="line">        GEO_C[clr_geo]</span><br><span class="line">        MSG_C[clr_message]</span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    UI_BUSINESS --&gt; GEO_C</span><br><span class="line">    UI_BUSINESS --&gt; MSG_C</span><br></pre></td></tr></table></figure><h3 id="2-当前存在的问题"><a href="#2-当前存在的问题" class="headerlink" title="2) 当前存在的问题"></a>2) 当前存在的问题</h3><ul><li>版本治理<ul><li>大量使用 dependency_overrides，反映依赖版本冲突与治理缺失</li><li>本地路径依赖多，模块版本同步与冲突解决成本高</li></ul></li><li>构建效率<ul><li>模块数量多且粒度偏小，构建时间长，增量构建收益不足</li></ul></li><li>依赖关系<ul><li>部分模块间存在潜在循环依赖风险</li><li>业务模块彼此直接引用内部实现，边界不清晰</li></ul></li></ul><h3 id="3-改善目标"><a href="#3-改善目标" class="headerlink" title="3) 改善目标"></a>3) 改善目标</h3><ul><li>版本治理统一化：逐步消灭 dependency_overrides，统一基础依赖版本</li><li>边界与解耦强化：业务模块只经由 Facade&#x2F;Service<br>接口交互，禁止跨层直接依赖</li><li>模块粒度优化：合并过小、强相关模块，提升构建与维护效率</li><li>构建效率提升：缩短全量构建时间，引入更明确的增量构建策略</li><li>依赖安全：杜绝循环依赖，建立依赖白名单&#x2F;黑名单与审计机制</li></ul><h3 id="4-模块边界与接口约束"><a href="#4-模块边界与接口约束" class="headerlink" title="4) 模块边界与接口约束"></a>4) 模块边界与接口约束</h3><ul><li>每个模块必须提供唯一对外入口（如 <code>lib/&lt;module&gt;.dart</code>）</li><li>模块内部代码禁止被外部直接 import（通过导出文件统一暴露）</li><li>业务模块之间通过接口或事件解耦，禁止直接访问彼此内部实现</li></ul><h3 id="5-模块自闭环能力要求"><a href="#5-模块自闭环能力要求" class="headerlink" title="5) 模块自闭环能力要求"></a>5) 模块自闭环能力要求</h3><ul><li>模块内部完成“接口 + 实现 + 测试”闭环</li><li>公共能力上收至 basic_*&#x2F;kit_*，避免横向复制</li><li>业务模块内禁止重复定义通用模型与工具类</li></ul><h3 id="6-依赖治理与版本策略"><a href="#6-依赖治理与版本策略" class="headerlink" title="6) 依赖治理与版本策略"></a>6) 依赖治理与版本策略</h3><ul><li>统一基础依赖版本，逐步减少 <code>dependency_overrides</code></li><li>建立模块依赖白名单与黑名单规则</li><li>每个模块提供版本变更说明与兼容策略</li></ul><h3 id="7-模块化完善具体方案（落地步骤）"><a href="#7-模块化完善具体方案（落地步骤）" class="headerlink" title="7) 模块化完善具体方案（落地步骤）"></a>7) 模块化完善具体方案（落地步骤）</h3><ol><li>依赖梳理：输出模块依赖关系图与循环依赖清单</li><li>依赖拆解：识别横向耦合点，抽取到 basic_*&#x2F;kit_*</li><li>接口对齐：模块外只通过 Facade&#x2F;Service 访问</li><li>路由收敛：模块内自注册路由，外部只调用入口</li><li>回归验证：每次迁移只影响单模块，确保低风险落地</li></ol><h3 id="8-模块化完善的验收清单"><a href="#8-模块化完善的验收清单" class="headerlink" title="8) 模块化完善的验收清单"></a>8) 模块化完善的验收清单</h3><ul><li>app_* 之间无直接依赖</li><li>ui_* 不依赖任何业务模块</li><li>clr_* 仅暴露 Facade API</li><li>basic_*&#x2F;kit_* 不依赖业务模块</li><li>模块版本与依赖版本统一可追溯</li><li>构建指标达标：全量构建时间下降、增量构建命中率提升</li><li>依赖指标达标：dependency_overrides 清零或极少量、循环依赖为零</li></ul><h2 id="1-内存优化（基于现有代码）"><a href="#1-内存优化（基于现有代码）" class="headerlink" title="1. 内存优化（基于现有代码）"></a>1. 内存优化（基于现有代码）</h2><ul><li>入口内存告警联动（应用主入口使用 MemoryWatcher）<ul><li>代码位置：[app_widget.dart:L312-L337]</li></ul></li></ul><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Widget _createApp() =&gt; MemoryWatcher(</span><br><span class="line">  memoryWarningLine: minWarningFreeMemory,</span><br><span class="line">  onMemoryWarning: () &#123;</span><br><span class="line">    pushFacade.postEvent(topic: memoryPressureTopic);</span><br><span class="line">    OneAppConfigHandler.instance.clearMemory();</span><br><span class="line">  &#125;,</span><br><span class="line">  child: MaterialApp.router(</span><br><span class="line">    builder: FlutterSmartDialog.init(),</span><br><span class="line">    key: ContextProvider.globalKey,</span><br><span class="line">    onGenerateTitle: (context) =&gt; AppIntlDelegate.of(context).appTitle,</span><br><span class="line">    title: <span class="string">&#x27;ID. UX&#x27;</span>,</span><br><span class="line">    theme: themeFacade.theme.ofStandard().themeData,</span><br><span class="line">    routeInformationParser: Modular.routeInformationParser,</span><br><span class="line">    routerDelegate: Modular.routerDelegate,</span><br><span class="line">    scrollBehavior: _NoGlowingBehavior(),</span><br><span class="line">  ).routerIntlConfig(</span><br><span class="line">    _supportLocale,</span><br><span class="line">    _localizationsDelegates,</span><br><span class="line">  ),</span><br><span class="line">);</span><br></pre></td></tr></table></figure><ul><li>系统内存压力回调与全局缓存清理<ul><li>代码位置：[app_config_handler.dart:L57-L78]</li></ul></li></ul><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@override</span></span><br><span class="line"><span class="keyword">void</span> didHaveMemoryPressure() &#123;</span><br><span class="line">  Logger.w(<span class="string">&quot;app_config: didHaveMemoryPressure&quot;</span>, LogTag_App);</span><br><span class="line">  <span class="keyword">if</span> (currentAppState != AppLifecycleState.resumed) &#123;</span><br><span class="line">    Logger.d(<span class="string">&quot;app_config: in background, not clear&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!OneDeviceInfoUtil.isLowMemoryIOSDevice()) &#123;</span><br><span class="line">    Logger.d(<span class="string">&quot;app_config: not need clear&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  clearMemory();</span><br><span class="line">  <span class="keyword">super</span>.didHaveMemoryPressure();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> clearMemory() &#123;</span><br><span class="line">  PaintingBinding.instance.imageCache.clear();</span><br><span class="line">  DefaultCacheManager manager = DefaultCacheManager();</span><br><span class="line">  manager.store.emptyMemoryCache(); <span class="comment">//clears all data in cache.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>设置模块缓存清理<ul><li>代码位置：[clear_cache_util.dart]</li></ul></li></ul><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> Future&lt;<span class="built_in">bool</span>&gt; clear() <span class="keyword">async</span> &#123;</span><br><span class="line">  <span class="keyword">final</span> Directory tempDir = <span class="keyword">await</span> getTemporaryDirectory();</span><br><span class="line">  <span class="keyword">await</span> _delete(tempDir);</span><br><span class="line">  <span class="built_in">String</span> locale = Intl.getCurrentLocale();</span><br><span class="line">  <span class="built_in">String</span> environment = AppEnvironmentConfig.getCurEnvironment().toString().split(<span class="string">&#x27;.&#x27;</span>).last;</span><br><span class="line">  <span class="built_in">String</span> componetJsonCacheKey1 = <span class="string">&#x27;Component_<span class="subst">$&#123;ComponentPageCode.DISCOVER&#125;</span>_<span class="subst">$&#123;locale&#125;</span>_<span class="subst">$&#123;environment&#125;</span>&#x27;</span>;</span><br><span class="line">  <span class="built_in">String</span> componetJsonCacheKey2 = <span class="string">&#x27;Component_<span class="subst">$&#123;ComponentPageCode.COMMUNITY&#125;</span>_<span class="subst">$&#123;locale&#125;</span>_<span class="subst">$&#123;environment&#125;</span>&#x27;</span>;</span><br><span class="line">  <span class="keyword">await</span> clearSpCahceByKeys([componetJsonCacheKey1, componetJsonCacheKey2]);</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>账号模块内存缓存清理<ul><li>代码位置：[garage_facade_impl.dart:L395-L401]</li></ul></li></ul><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@override</span></span><br><span class="line">Future&lt;<span class="keyword">void</span>&gt; clearCache() <span class="keyword">async</span> &#123;</span><br><span class="line">  Logger.i(<span class="string">&#x27;<span class="subst">$_tagPrefix</span>#clearCache: calling&#x27;</span>, clrAccountTag);</span><br><span class="line">  <span class="keyword">await</span> encryptedModuleBox.delete(vehicleCacheKey);</span><br><span class="line">  _memoryCache.clear();</span><br><span class="line">  _defaultVehicle = <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>原生资源释放（AvatarX 模块）<ul><li>代码位置：[AvatarxManager.swift:L685-L696]</li></ul></li></ul><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">destroy</span>() &#123;</span><br><span class="line">    <span class="type">FUAvatarHelper</span>.destroy()</span><br><span class="line">    <span class="type">FURenderKit</span>.destroy()</span><br><span class="line">    <span class="type">FUAIKit</span>.destroy()</span><br><span class="line">    <span class="type">FUAIKit</span>.unloadAllAIMode()</span><br><span class="line">    removeDisplayView()</span><br><span class="line">    initResource <span class="operator">=</span> <span class="literal">false</span></span><br><span class="line">    isLoadDefaultBundle <span class="operator">=</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-1-现状缺口（基于仓库检索）"><a href="#1-1-现状缺口（基于仓库检索）" class="headerlink" title="1.1 现状缺口（基于仓库检索）"></a>1.1 现状缺口（基于仓库检索）</h3><ul><li>未找到全局 ImageCache 上限的真实配置代码</li><li>未找到统一的图片下采样封装入口</li><li>未找到 Isolate 大计算通用封装入口</li><li>Android&#x2F;iOS 侧缺少统一的缓存限额策略代码</li></ul><h3 id="1-2-直接可做的补齐任务（基于现有框架）"><a href="#1-2-直接可做的补齐任务（基于现有框架）" class="headerlink" title="1.2 直接可做的补齐任务（基于现有框架）"></a>1.2 直接可做的补齐任务（基于现有框架）</h3><ol><li>在应用入口补齐 ImageCache 上限配置，挂载在已有 MemoryWatcher<br>初始化流程中</li><li>基于现有 DefaultCacheManager 清理逻辑，补齐业务级缓存 TTL<br>与最大内存占用</li><li>增加图片加载统一封装（业务模块只用封装入口，不直接 Image.network）</li><li>对耗时解析&#x2F;批量处理提供统一异步处理入口</li></ol><h2 id="2-Android-内存优化（现状与补齐）"><a href="#2-Android-内存优化（现状与补齐）" class="headerlink" title="2. Android 内存优化（现状与补齐）"></a>2. Android 内存优化（现状与补齐）</h2><h3 id="2-1-现状"><a href="#2-1-现状" class="headerlink" title="2.1 现状"></a>2.1 现状</h3><p>当前仓库中未检索到 Android 侧的 onTrimMemory&#x2F;onLowMemory、Bitmap<br>下采样、LruCache 等内存治理实现代码。现阶段内存清理主要集中在 Flutter<br>层与业务缓存逻辑。</p><h3 id="2-2-补齐方向（基于现有架构可落地）"><a href="#2-2-补齐方向（基于现有架构可落地）" class="headerlink" title="2.2 补齐方向（基于现有架构可落地）"></a>2.2 补齐方向（基于现有架构可落地）</h3><ul><li>在 Android Application 级别接入内存回调并驱动缓存清理</li><li>为高频图片&#x2F;视频模块补齐下采样与缓存限额策略</li><li>统一原生缓存上限与回收入口，供 Flutter 侧触发</li></ul><h2 id="3-iOS-内存优化（现状与补齐）"><a href="#3-iOS-内存优化（现状与补齐）" class="headerlink" title="3. iOS 内存优化（现状与补齐）"></a>3. iOS 内存优化（现状与补齐）</h2><h3 id="3-1-现状"><a href="#3-1-现状" class="headerlink" title="3.1 现状"></a>3.1 现状</h3><ul><li>iOS 低内存设备识别已在基础能力中实现，用于清理策略判断<ul><li>代码位置：[one_device_info_util.dart:L27-L49]</li></ul></li></ul><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="built_in">bool</span> isLowMemoryIOSDevice() &#123;</span><br><span class="line">  <span class="keyword">switch</span> (oneDeviceType) &#123;</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_7:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_7_Plus:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_8:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_8_Plus:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_X:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_XS:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_XS_MAX:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_XR:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_11:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_11_Pro:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_11_Pro_Max:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_SE_2:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_12_mini:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_12:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_13_mini:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_13:</span><br><span class="line">    <span class="keyword">case</span> OneDeviceType.iPhone_SE_3:</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-补齐方向"><a href="#3-2-补齐方向" class="headerlink" title="3.2 补齐方向"></a>3.2 补齐方向</h3><ul><li>统一 iOS 侧图片&#x2F;模型资源下采样入口</li><li>统一 NSCache&#x2F;LRU 上限并与 Flutter 清理逻辑联动</li></ul><h2 id="4-统一缓存治理（跨端，基于现状的改造点）"><a href="#4-统一缓存治理（跨端，基于现状的改造点）" class="headerlink" title="4. 统一缓存治理（跨端，基于现状的改造点）"></a>4. 统一缓存治理（跨端，基于现状的改造点）</h2><ul><li>现状清理入口：OneAppConfigHandler.clearMemory() 已在内存告警触发时执行<ul><li>代码位置：[app_config_handler.dart:L73-L77]</li></ul></li><li>业务缓存清理入口：设置模块提供清理临时目录与组件缓存逻辑<ul><li>代码位置：[clear_cache_util.dart]</li></ul></li></ul><h2 id="5-落地改造优先级（基于现有代码的下一步）"><a href="#5-落地改造优先级（基于现有代码的下一步）" class="headerlink" title="5. 落地改造优先级（基于现有代码的下一步）"></a>5. 落地改造优先级（基于现有代码的下一步）</h2><ol><li>在应用入口补齐 ImageCache 上限配置，接入现有 MemoryWatcher</li><li>业务缓存统一 TTL 与内存上限，复用 DefaultCacheManager 清理入口</li><li>引入统一图片加载封装，替换业务模块直接 Image.network 使用</li><li>对批量解析&#x2F;重计算提供统一异步处理入口</li><li>Android&#x2F;iOS 侧补齐统一的内存回调与缓存上限策略</li></ol><h2 id="6-成本与风险评估（结合当前项目）"><a href="#6-成本与风险评估（结合当前项目）" class="headerlink" title="6. 成本与风险评估（结合当前项目）"></a>6. 成本与风险评估（结合当前项目）</h2><ul><li>低成本&#x2F;低风险<ul><li>缓存清理入口统一、内存告警联动优化、生命周期释放规范化</li></ul></li><li>中成本&#x2F;中风险<ul><li>图片加载统一封装、缓存策略统一、模块依赖治理</li></ul></li><li>高成本&#x2F;高风险<ul><li>核心业务模块重构、原生侧深度改造、三端基础库大版本升级</li></ul></li></ul><h2 id="7-结合当前项目的建议行动"><a href="#7-结合当前项目的建议行动" class="headerlink" title="7. 结合当前项目的建议行动"></a>7. 结合当前项目的建议行动</h2><ul><li>先做基线：以核心场景建立内存基线与峰值对照表</li><li>先修高收益：围绕内存告警链路与缓存清理入口做闭环</li><li>再做结构性优化：模块边界治理、依赖统一、缓存策略统一</li></ul>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20260129/T-Systems/OneApp_performance/</id>
    <link href="https://minniexcode.github.io/memoirs/20260129/T-Systems/OneApp_performance/"/>
    <published>2026-01-28T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>本方案包含三部分：性能与内存优化理论、架构改造（模块化完善）与内存优化实战，并补充性能监控方案、成本与风险评估。</p>
<h2 id="理论基础：内存模型、排查方法与治理策略"><a href="#理论基础：内存模型、排查方法与治理策略" class="headerlin]]>
    </summary>
    <title>OneApp 性能与架构优化方案（内存与模块化）</title>
    <updated>2026-03-16T16:27:30.439Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="项目实践" scheme="https://minniexcode.github.io/memoirs/categories/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5/"/>
    <category term="T-Systems" scheme="https://minniexcode.github.io/memoirs/tags/T-Systems/"/>
    <category term="OneApp" scheme="https://minniexcode.github.io/memoirs/tags/OneApp/"/>
    <category term="Architecture" scheme="https://minniexcode.github.io/memoirs/tags/Architecture/"/>
    <category term="HarmonyOS" scheme="https://minniexcode.github.io/memoirs/tags/HarmonyOS/"/>
    <content>
      <![CDATA[<h2 id="Step-0：前置约束与目标确认（必做）"><a href="#Step-0：前置约束与目标确认（必做）" class="headerlink" title="Step 0：前置约束与目标确认（必做）"></a>Step 0：前置约束与目标确认（必做）</h2><ul><li>目标<ul><li>在不破坏 Android&#x2F;iOS 现有架构的前提下新增 HarmonyOS 支持</li><li>首版功能可裁剪、可降级</li></ul></li><li>约束<ul><li>不重写 Flutter 业务代码</li><li>业务模块不得直接依赖平台插件</li><li>鸿蒙首版不要求功能全量对齐</li></ul></li><li>产出<ul><li>《鸿蒙首版功能白名单》</li><li>《不支持&#x2F;延后支持模块清单》</li></ul></li></ul><h2 id="Step-1：插件与平台能力盘点（P0）"><a href="#Step-1：插件与平台能力盘点（P0）" class="headerlink" title="Step 1：插件与平台能力盘点（P0）"></a>Step 1：插件与平台能力盘点（P0）</h2><ul><li>1.1 插件全量清点<ul><li>对现有 Flutter 插件形成“插件→使用模块→平台支持矩阵”</li><li>核心关注：地图&#x2F;定位、推送、支付、存储、设备信息、媒体、车控</li><li>产出：《插件→模块→平台支持矩阵表》</li></ul></li><li>1.2 插件分级（强制）<ul><li>L0：纯 Dart（直接复用）</li><li>L1：轻平台（需补鸿蒙实现）</li><li>L2：重平台（能力抽象 + 原生实现）</li><li>L3：强绑定（首版可能不支持）</li><li>产出：《插件分级治理表》</li></ul></li></ul><h2 id="Step-2：平台能力抽象（架构改造核心，P0）"><a href="#Step-2：平台能力抽象（架构改造核心，P0）" class="headerlink" title="Step 2：平台能力抽象（架构改造核心，P0）"></a>Step 2：平台能力抽象（架构改造核心，P0）</h2><ul><li>新增 Platform Capability Layer（统一能力抽象包）<ul><li>目录建议：<ul><li>packages&#x2F;platform_capability&#x2F;{geo,push,payment,car_control,media,device,storage}</li></ul></li><li>定义跨端接口（示例）<ul><li>GeoCapability.getLocation(), PushCapability.subscribe(),<br>PaymentCapability.pay()</li></ul></li><li>强制规则<ul><li>业务模块不依赖任何平台插件</li><li>CLR 不直接引用 Android&#x2F;iOS&#x2F;HarmonyOS SDK</li><li>所有平台能力通过 Capability 接口访问</li><li>Capability Interface 禁止暴露 Platform<br>类型（Context&#x2F;Ability&#x2F;Activity）</li></ul></li><li>产出：Capability 接口定义、依赖调整方案</li></ul></li></ul><h3 id="Step-2A：模块化适配约束（Harmony-优先）"><a href="#Step-2A：模块化适配约束（Harmony-优先）" class="headerlink" title="Step 2A：模块化适配约束（Harmony 优先）"></a>Step 2A：模块化适配约束（Harmony 优先）</h3><ul><li>基础设施层（basic_*）<ul><li>原则：尽量纯 Dart 实现，避免依赖任何原生平台能力</li><li>目的：降低平台适配成本，保障 Harmony 接入可持续</li></ul></li><li>工具与组件层（kit_*&#x2F;ui_*）<ul><li>原则：不直接依赖平台插件；如需平台能力，必须经 Capability</li><li>目的：保持 UI&#x2F;工具层跨端一致性</li></ul></li><li>服务连接层（clr_*）<ul><li>原则：仅通过 Capability 访问平台能力，不直接引用 Android&#x2F;iOS&#x2F;Harmony<br>SDK</li><li>目的：统一平台差异收敛点，避免平台污染</li></ul></li><li>业务模块层（app_*）<ul><li>原则：只依赖 basic_*&#x2F;kit_*&#x2F;ui_*&#x2F;clr_*，禁止互相强依赖</li><li>目的：保证模块边界清晰、可裁剪、可灰度</li></ul></li></ul><p>验收要点</p><ul><li>basic_* 不含原生依赖</li><li>kit_*&#x2F;ui_* 不出现 Platform 类型（Context&#x2F;Ability&#x2F;Activity）</li><li>clr_* 仅调用 Capability 接口</li><li>app_* 无跨层直接依赖</li></ul><h2 id="Step-3：CLR-模块改造（P0–P1）"><a href="#Step-3：CLR-模块改造（P0–P1）" class="headerlink" title="Step 3：CLR 模块改造（P0–P1）"></a>Step 3：CLR 模块改造（P0–P1）</h2><ul><li>去插件化<ul><li>将 clr_* 模块中的“直接插件&#x2F;Native SDK 调用”替换为 Capability 调用</li><li>适配模块：clr_geo、clr_account、clr_message、clr_charging、clr_touchgo、clr_wallbox、clr_carwatcher</li><li>Before：CLR → Plugin → Native</li><li>After：CLR → Capability → Platform Impl</li><li>产出：CLR 重构完成，Android&#x2F;iOS Capability 实现保持不变</li></ul></li></ul><h2 id="Step-4：HarmonyOS-宿主工程与-Flutter-引擎接入（P0）"><a href="#Step-4：HarmonyOS-宿主工程与-Flutter-引擎接入（P0）" class="headerlink" title="Step 4：HarmonyOS 宿主工程与 Flutter 引擎接入（P0）"></a>Step 4：HarmonyOS 宿主工程与 Flutter 引擎接入（P0）</h2><ul><li>4.1 宿主工程结构（建议）<ul><li>Flutter 业务代码保持唯一真源</li><li>HarmonyOS 宿主与 Android&#x2F;iOS 宿主同级或并列目录即可</li><li>示例结构<ul><li>oneapp&#x2F;<ul><li>flutter&#x2F;<ul><li>lib&#x2F;</li><li>packages&#x2F;</li><li>pubspec.yaml</li></ul></li><li>host_android&#x2F;</li><li>host_ios&#x2F;</li><li>host_harmony&#x2F;</li></ul></li></ul></li><li>产出：《鸿蒙宿主工程结构与路径规范》</li></ul></li><li>4.2 HarmonyOS 启动流程与参数策略<ul><li>Harmony 宿主决定“给 Flutter 什么参数”</li><li>启动流程<ul><li>Harmony Ability</li><li>初始化 Flutter Engine</li><li>注入启动参数（appMode&#x2F;featureFlags）</li><li>runApp(main.dart)</li></ul></li><li>最小启动策略（首版）<ul><li>只做三件事：启动 Engine、注册最小插件集合、指定初始路由</li><li>启动参数语义<ul><li>appMode：应用形态（如 harmonyLite）</li><li>featureFlags：模块开关（如 map&#x2F;pay&#x2F;carControl）</li></ul></li><li>Harmony 宿主在 Engine 初始化阶段向 Flutter 注入启动参数</li><li>Flutter 启动后通过统一入口获取启动参数</li><li>Flutter 层保持完整 App，只是能力不可用或返回 not supported</li><li>iOS&#x2F;Android 不改动，默认无 args</li></ul></li><li>产出：《Harmony 启动参数规范》《最小插件集合清单》</li></ul></li></ul><h2 id="Step-5：HarmonyOS-原生能力实现（P1）"><a href="#Step-5：HarmonyOS-原生能力实现（P1）" class="headerlink" title="Step 5：HarmonyOS 原生能力实现（P1）"></a>Step 5：HarmonyOS 原生能力实现（P1）</h2><ul><li>Capability 的鸿蒙实现<ul><li>Geo → Map Kit</li><li>Push → Push Kit</li><li>Payment → IAP</li><li>Storage → Preferences</li><li>Device&#x2F;Media&#x2F;CarControl → 系统&#x2F;厂商 API</li><li>通过 MethodChannel 暴露给 Flutter</li><li>产出：鸿蒙 Capability 实现代码（Flutter 侧透明）</li></ul></li></ul><h2 id="Step-6：模块级功能适配与裁剪（P1–P2）"><a href="#Step-6：模块级功能适配与裁剪（P1–P2）" class="headerlink" title="Step 6：模块级功能适配与裁剪（P1–P2）"></a>Step 6：模块级功能适配与裁剪（P1–P2）</h2><ul><li>首版必保模块（建议）<ul><li>oneapp_main、app_home、oneapp_account</li><li>clr_account &#x2F; clr_geo &#x2F; clr_message</li><li>app_car &#x2F; app_charging（基础能力）</li></ul></li><li>可延后模块<ul><li>app_avatar、app_rpa、app_vur、3D&#x2F;AI&#x2F;音视频增强模块</li></ul></li><li>策略<ul><li>模块级 feature flag</li><li>UI 隐藏 &#x2F; 功能降级</li><li>产出：鸿蒙首版功能闭环</li></ul></li></ul><h2 id="Step-7：分阶段落地顺序（强执行）"><a href="#Step-7：分阶段落地顺序（强执行）" class="headerlink" title="Step 7：分阶段落地顺序（强执行）"></a>Step 7：分阶段落地顺序（强执行）</h2><ul><li>阶段 1（POC）<ul><li>新建 host_harmony</li><li>Flutter 首页跑通</li><li>只接入：网络、存储、账号</li><li>产出：鸿蒙最小可运行包</li></ul></li><li>阶段 2（核心可用）<ul><li>跑通：app_home、app_discover、clr_account、clr_message</li><li>UI 完整，部分按钮 disabled 或降级</li><li>产出：核心场景闭环</li></ul></li><li>阶段 3（逐模块放开）<ul><li>每新增模块：宿主补插件、Flutter 放 feature flag、打通 clr → app</li><li>启动参数示例<ul><li>appMode: “harmonyLite”</li><li>featureFlags: { map: false, pay: false, carControl: false }</li></ul></li><li>产出：模块级逐步放量清单</li></ul></li></ul><h2 id="Step-8：多宿主构建与-CI-约束（P1）"><a href="#Step-8：多宿主构建与-CI-约束（P1）" class="headerlink" title="Step 8：多宿主构建与 CI 约束（P1）"></a>Step 8：多宿主构建与 CI 约束（P1）</h2><ul><li>约束<ul><li>flutter&#x2F; 目录为唯一真源</li><li>host_harmony 仅负责 Engine 初始化与插件注册</li><li>Flutter 代码不得 import host_harmony 相关实现</li><li>Capability 必须有 Android&#x2F;iOS 默认实现</li></ul></li><li>产出<ul><li>Flutter lint &#x2F; 构建校验规则</li></ul></li></ul><h2 id="Step-9：测试与稳定性验证（P2）"><a href="#Step-9：测试与稳定性验证（P2）" class="headerlink" title="Step 9：测试与稳定性验证（P2）"></a>Step 9：测试与稳定性验证（P2）</h2><ul><li>测试重点<ul><li>Flutter 启动稳定性</li><li>MethodChannel 调用一致性</li><li>权限 &#x2F; 生命周期</li><li>弱网 &#x2F; 异常兜底</li></ul></li><li>真机测试（必须）<ul><li>至少 2–3 款鸿蒙设备</li><li>不以 Emulator 结论替代真机结果</li><li>产出：测试报告与问题清单</li></ul></li></ul><h2 id="Step-10：上架与合规检查（P2）"><a href="#Step-10：上架与合规检查（P2）" class="headerlink" title="Step 10：上架与合规检查（P2）"></a>Step 10：上架与合规检查（P2）</h2><ul><li>审核重点<ul><li>权限最小化</li><li>无动态代码下载</li><li>SDK 合规（华为生态）</li></ul></li><li>发布<ul><li>华为应用市场</li><li>首版灰度发布</li><li>产出：合规材料与发布记录</li></ul></li></ul><h2 id="附录：可视化补充（可直接插入评审材料）"><a href="#附录：可视化补充（可直接插入评审材料）" class="headerlink" title="附录：可视化补充（可直接插入评审材料）"></a>附录：可视化补充（可直接插入评审材料）</h2><h3 id="附录-A：多宿主-Flutter-架构示意"><a href="#附录-A：多宿主-Flutter-架构示意" class="headerlink" title="附录 A：多宿主 + Flutter 架构示意"></a>附录 A：多宿主 + Flutter 架构示意</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">            ┌───────────────┐</span><br><span class="line">            │   Flutter UI  │</span><br><span class="line">            │   lib/*       │</span><br><span class="line">            └───────┬───────┘</span><br><span class="line">                    │ Capability 接口</span><br><span class="line">    ┌───────────────┴─────────────────┐</span><br><span class="line">    │                                 │</span><br><span class="line">┌───▼─────────┐                   ┌───▼─────────┐</span><br><span class="line">│  Android    │                   │   iOS       │</span><br><span class="line">│ Platform Impl│                  │ Platform Impl│</span><br><span class="line">└─────────────┘                   └─────────────┘</span><br><span class="line">                    │</span><br><span class="line">                    │</span><br><span class="line">            ┌───────▼───────────┐</span><br><span class="line">            │ HarmonyOS Platform│</span><br><span class="line">            │ Impl (ArkTS/Java) │</span><br><span class="line">            └───────────────────┘</span><br></pre></td></tr></table></figure><ul><li>Flutter 层唯一真源，业务模块不依赖平台</li><li>Capability 层统一接口访问原生能力</li><li>HarmonyOS &#x2F; Android &#x2F; iOS 只提供实现</li></ul><h3 id="附录-B：HarmonyOS-启动流程图"><a href="#附录-B：HarmonyOS-启动流程图" class="headerlink" title="附录 B：HarmonyOS 启动流程图"></a>附录 B：HarmonyOS 启动流程图</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┐</span><br><span class="line">│ Harmony Ability  │</span><br><span class="line">└─────────┬───────┘</span><br><span class="line">          │ 初始化 Flutter Engine</span><br><span class="line">          ▼</span><br><span class="line">┌─────────────────────────┐</span><br><span class="line">│ 注入启动参数 initialArgs │</span><br><span class="line">│ appMode / featureFlags   │</span><br><span class="line">└─────────┬───────────────┘</span><br><span class="line">          │ 注册最小插件集合</span><br><span class="line">          ▼</span><br><span class="line">┌───────────────────┐</span><br><span class="line">│ runApp(main.dart) │</span><br><span class="line">│ Flutter 层启动     │</span><br><span class="line">└───────────────────┘</span><br></pre></td></tr></table></figure><ul><li>初版策略：只启动 Engine、注册最小插件、指定初始路由</li><li>Flutter 层保持完整 App，可根据 featureFlags 降级模块</li></ul><h3 id="附录-C：Feature-Flags-可视化表"><a href="#附录-C：Feature-Flags-可视化表" class="headerlink" title="附录 C：Feature Flags 可视化表"></a>附录 C：Feature Flags 可视化表</h3><table><thead><tr><th>模块</th><th>首版状态</th><th>功能降级 &#x2F; UI 隐藏</th></tr></thead><tbody><tr><td>map</td><td>false</td><td>隐藏地图按钮</td></tr><tr><td>pay</td><td>false</td><td>禁用支付入口</td></tr><tr><td>carControl</td><td>false</td><td>禁止操作车辆</td></tr><tr><td>oneapp_main</td><td>true</td><td>-</td></tr><tr><td>app_home</td><td>true</td><td>-</td></tr><tr><td>oneapp_account</td><td>true</td><td>-</td></tr><tr><td>clr_account</td><td>true</td><td>-</td></tr><tr><td>clr_geo</td><td>true</td><td>-</td></tr></tbody></table><p>启动参数示例：</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;appMode&quot;</span>: <span class="string">&quot;harmonyLite&quot;</span>,</span><br><span class="line">  <span class="string">&quot;featureFlags&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;map&quot;</span>: <span class="keyword">false</span>,</span><br><span class="line">      <span class="string">&quot;pay&quot;</span>: <span class="keyword">false</span>,</span><br><span class="line">      <span class="string">&quot;carControl&quot;</span>: <span class="keyword">false</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="附录-D：分阶段落地甘特-流程示意"><a href="#附录-D：分阶段落地甘特-流程示意" class="headerlink" title="附录 D：分阶段落地甘特&#x2F;流程示意"></a>附录 D：分阶段落地甘特&#x2F;流程示意</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">阶段 1：POC (新建 host_harmony)</span><br><span class="line"> ├─ Flutter 首页跑通</span><br><span class="line"> ├─ 最小插件集合</span><br><span class="line"> └─ 网络 / 存储 / 账号模块可用</span><br><span class="line"></span><br><span class="line">阶段 2：核心可用</span><br><span class="line"> ├─ app_home、app_discover</span><br><span class="line"> ├─ clr_account / clr_message</span><br><span class="line"> └─ UI 完整，部分按钮 disabled</span><br><span class="line"></span><br><span class="line">阶段 3：逐模块放开</span><br><span class="line"> ├─ 每新增模块宿主补插件</span><br><span class="line"> ├─ Flutter 放 feature flag</span><br><span class="line"> └─ 打通 clr → app</span><br></pre></td></tr></table></figure><p>阶段对应产出：</p><ul><li>POC → 鸿蒙最小可运行包</li><li>核心可用 → 核心场景闭环</li><li>模块放开 → 模块级逐步放量清单</li></ul><h2 id="参考资料（官方）"><a href="#参考资料（官方）" class="headerlink" title="参考资料（官方）"></a>参考资料（官方）</h2><ul><li>HarmonyOS<br>开发者官网：<a href="https://developer.harmonyos.com/">https://developer.harmonyos.com</a></li><li>OpenHarmony<br>文档中心：<a href="https://docs.openharmony.cn/">https://docs.openharmony.cn</a></li><li>DevEco<br>Studio（IDE）：<a href="https://developer.huawei.com/consumer/cn/deveco-studio">https://developer.huawei.com/consumer/cn/deveco-studio</a></li><li>华为应用市场审核规范：<a href="https://developer.huawei.com/consumer/cn/doc/app">https://developer.huawei.com/consumer/cn/doc/app</a></li></ul>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20260129/T-Systems/harmony_development/</id>
    <link href="https://minniexcode.github.io/memoirs/20260129/T-Systems/harmony_development/"/>
    <published>2026-01-28T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="Step-0：前置约束与目标确认（必做）"><a href="#Step-0：前置约束与目标确认（必做）" class="headerlink" title="Step 0：前置约束与目标确认（必做）"></a>Step 0：前置约束与目标确认（必做）</h2><]]>
    </summary>
    <title>OneApp 鸿蒙（HarmonyOS）工作步骤文档</title>
    <updated>2026-03-16T16:27:30.439Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="React" scheme="https://minniexcode.github.io/memoirs/tags/React/"/>
    <category term="JavaScript" scheme="https://minniexcode.github.io/memoirs/tags/JavaScript/"/>
    <category term="Fundamentals" scheme="https://minniexcode.github.io/memoirs/tags/Fundamentals/"/>
    <content>
      <![CDATA[<p>以下是一些推荐的文字资源，可以帮助你深入学习 React 的原理和应用：</p><h3 id="1-官方文档"><a href="#1-官方文档" class="headerlink" title="1. 官方文档"></a>1. 官方文档</h3><ul><li><strong>React 官方文档</strong>: 尽管你觉得内容较浅，但它仍然是学习 React 的基础，尤其是对核心概念的理解。<ul><li><a href="https://reactjs.org/docs/getting-started.html">React 官方文档</a></li></ul></li></ul><h3 id="2-书籍"><a href="#2-书籍" class="headerlink" title="2. 书籍"></a>2. 书籍</h3><ul><li><strong>《React - Up &amp; Running》</strong> - Stoyan Stefanov 著，适合想要快速上手和理解 React 的开发者。</li><li><strong>《Learning React》</strong> - Alex Banks 和 Eve Porcello 著，涵盖了 React 的基本概念和一些高级特性。</li><li><strong>《Fullstack React: The Complete Guide to ReactJS, Redux, and Friends》</strong> - 详细介绍了 React 的各个方面，包括 Redux 和其他相关技术。</li><li><strong>《React Design Patterns and Best Practices》</strong> - Michele Bertoli 著，深入探讨了 React 的设计模式和最佳实践。</li></ul><h3 id="3-博客和文章"><a href="#3-博客和文章" class="headerlink" title="3. 博客和文章"></a>3. 博客和文章</h3><ul><li><p><strong>Overreacted</strong>: Dan Abramov 的博客，涵盖了许多 React 的深层次概念和最佳实践。</p><ul><li><a href="https://overreacted.io/">Overreacted Blog</a></li></ul></li><li><p><strong>CSS-Tricks</strong>: 提供了许多关于 React 的实用文章和示例。</p><ul><li><a href="https://css-tricks.com/tag/react/">CSS-Tricks React Articles</a></li></ul></li><li><p><strong>Smashing Magazine</strong>: 提供了一些深入的技术文章，涵盖 React 和前端开发的各种主题。</p><ul><li><a href="https://www.smashingmagazine.com/tag/react/">Smashing Magazine React Articles</a></li></ul></li></ul><h3 id="4-GitHub-和开源项目"><a href="#4-GitHub-和开源项目" class="headerlink" title="4. GitHub 和开源项目"></a>4. GitHub 和开源项目</h3><ul><li><p><strong>React GitHub 仓库</strong>: 查看 React 的源代码，理解其实现原理。</p><ul><li><a href="https://github.com/facebook/react">React GitHub</a></li></ul></li><li><p><strong>Awesome React</strong>: 一个 GitHub 上的资源集合，包含许多学习资源和开源项目。</p><ul><li><a href="https://github.com/enaqx/awesome-react">Awesome React</a></li></ul></li></ul><h3 id="5-在线教程"><a href="#5-在线教程" class="headerlink" title="5. 在线教程"></a>5. 在线教程</h3><ul><li><p><strong>FreeCodeCamp</strong>: 提供了免费的文本教程，涵盖 React 和其他前端技术。</p><ul><li><a href="https://www.freecodecamp.org/news/tag/react/">FreeCodeCamp React Tutorial</a></li></ul></li><li><p><strong>MDN Web Docs</strong>: Mozilla 的开发者网络提供了关于 JavaScript 和前端开发的全面文档。</p><ul><li><a href="https://developer.mozilla.org/">MDN Web Docs</a></li></ul></li></ul><h3 id="6-论坛和社区"><a href="#6-论坛和社区" class="headerlink" title="6. 论坛和社区"></a>6. 论坛和社区</h3><ul><li><p><strong>Dev.to</strong>: 一个开发者社区，许多开发者在这里分享他们的经验和教程。</p><ul><li><a href="https://dev.to/t/react">Dev.to React</a></li></ul></li><li><p><strong>Stack Overflow</strong>: 通过提问和回答问题，深入理解具体的技术难题。</p></li></ul><h3 id="7-实践项目"><a href="#7-实践项目" class="headerlink" title="7. 实践项目"></a>7. 实践项目</h3><ul><li><strong>构建项目</strong>: 实际动手构建项目是学习的最好方式。可以尝试构建一个简单的应用（如待办事项列表、博客等），并逐步加入更复杂的功能。</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过结合以上文字资源，你可以更深入地理解 React 的原理和应用场景。建议从基础开始，逐渐深入到高级主题，并通过实践来巩固所学知识。如果你有特定的主题或问题需要深入了解，请随时告诉我！</p><hr><h3 id="React原理的简单介绍"><a href="#React原理的简单介绍" class="headerlink" title="React原理的简单介绍"></a>React原理的简单介绍</h3><p>当然可以！以下是对 React 原理的简要介绍，帮助你理解其背后的核心概念。</p><h3 id="1-组件化"><a href="#1-组件化" class="headerlink" title="1. 组件化"></a>1. 组件化</h3><ul><li><strong>组件</strong>是 React 的基本构建块。每个组件都是一个独立的、可复用的 UI 单元。</li><li>组件可以是类组件或函数组件，函数组件更为常用，尤其是结合 Hooks 使用时。</li></ul><h3 id="2-虚拟-DOM"><a href="#2-虚拟-DOM" class="headerlink" title="2. 虚拟 DOM"></a>2. 虚拟 DOM</h3><ul><li>React 使用 <strong>虚拟 DOM</strong> 来提高性能。虚拟 DOM 是 React 在内存中维护的一个轻量级的 DOM 表示。</li><li>当组件的状态发生变化时，React 会首先在虚拟 DOM 中进行更新，然后通过比较算法（称为 <strong>Diffing</strong>）找出需要更新的部分，最后只将这些更改应用到实际的 DOM 中。</li></ul><h3 id="3-单向数据流"><a href="#3-单向数据流" class="headerlink" title="3. 单向数据流"></a>3. 单向数据流</h3><ul><li>React 遵循 <strong>单向数据流</strong> 的原则，数据从父组件流向子组件。这种方式使得数据流动更加可预测，便于管理和调试。</li><li>父组件通过 props 将数据传递给子组件，子组件不能直接修改父组件的数据。</li></ul><h3 id="4-状态管理"><a href="#4-状态管理" class="headerlink" title="4. 状态管理"></a>4. 状态管理</h3><ul><li>组件的 <strong>状态（state）</strong> 是组件内部管理的数据。状态的变化会触发组件的重新渲染。</li><li>React 提供了 <strong>useState</strong> 和 <strong>useReducer</strong> Hooks 来管理状态，类组件则使用 <code>this.state</code> 和 <code>this.setState()</code>。</li></ul><h3 id="5-生命周期"><a href="#5-生命周期" class="headerlink" title="5. 生命周期"></a>5. 生命周期</h3><ul><li>组件的生命周期分为 <strong>挂载</strong>、<strong>更新</strong> 和 <strong>卸载</strong> 三个阶段。React 提供了一些生命周期方法（如 <code>componentDidMount</code>、<code>componentDidUpdate</code> 和 <code>componentWillUnmount</code>）来处理这些阶段。</li><li>在函数组件中，可以使用 <strong>useEffect</strong> Hook 来处理副作用，类似于类组件的生命周期方法。</li></ul><h3 id="6-Hooks"><a href="#6-Hooks" class="headerlink" title="6. Hooks"></a>6. Hooks</h3><ul><li><strong>Hooks</strong> 是 React 16.8 引入的特性，允许在函数组件中使用状态和其他 React 特性。</li><li>常用的 Hooks 包括：<ul><li><code>useState</code>: 管理组件状态。</li><li><code>useEffect</code>: 处理副作用（如数据获取、订阅等）。</li><li><code>useContext</code>: 访问上下文数据。</li></ul></li></ul><h3 id="7-上下文-API"><a href="#7-上下文-API" class="headerlink" title="7. 上下文 API"></a>7. 上下文 API</h3><ul><li><strong>上下文 API</strong> 提供了一种在组件树中共享数据的方法，而不必通过每层组件传递 props。</li><li>使用 <code>React.createContext</code> 创建上下文，<code>Provider</code> 组件提供数据，<code>Consumer</code> 组件或 <code>useContext</code> Hook 访问数据。</li></ul><h3 id="8-性能优化"><a href="#8-性能优化" class="headerlink" title="8. 性能优化"></a>8. 性能优化</h3><ul><li>React 提供了一些性能优化的手段，例如：<ul><li><strong>React.memo</strong>: 用于优化函数组件的渲染，避免不必要的更新。</li><li><strong>shouldComponentUpdate</strong>: 在类组件中控制组件的更新。</li><li><strong>useMemo</strong> 和 <strong>useCallback</strong>: 用于优化计算和函数引用的性能。</li></ul></li></ul><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>React 的原理围绕组件化、虚拟 DOM、单向数据流和状态管理等核心概念展开。理解这些原理将帮助你更有效地使用 React，并在实际开发中解决各种问题。如果你有更具体的方面需要深入了解，请告诉我！</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20241224/learning/react-references/</id>
    <link href="https://minniexcode.github.io/memoirs/20241224/learning/react-references/"/>
    <published>2024-12-24T05:52:28.479Z</published>
    <summary>
      <![CDATA[<p>以下是一些推荐的文字资源，可以帮助你深入学习 React 的原理和应用：</p>
<h3 id="1-官方文档"><a href="#1-官方文档" class="headerlink" title="1. 官方文档"></a>1. 官方文档</h3><ul>
<li><s]]>
    </summary>
    <title>React 学习参考</title>
    <updated>2026-03-16T16:27:30.442Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="跑步" scheme="https://minniexcode.github.io/memoirs/categories/%E8%B7%91%E6%AD%A5/"/>
    <category term="Running" scheme="https://minniexcode.github.io/memoirs/tags/Running/"/>
    <category term="Marathon" scheme="https://minniexcode.github.io/memoirs/tags/Marathon/"/>
    <content>
      <![CDATA[<h3 id="马拉松中签"><a href="#马拉松中签" class="headerlink" title="马拉松中签"></a>马拉松中签</h3><p>今年挺幸运的，第二轮的补录，中签了广州马拉松。作为全国的大满贯赛事，我还是很希望广州马拉松能够中签的，毕竟武汉马拉松连续4年没有中。</p><p>时间过得挺快的，不知不觉上一篇已经是3年前了。</p><p>今年跑步一点都不积极，月跑量可能只有20KM左右。</p><p>本来中签了孝感马拉松，但是那天我身体状态不好直接放弃了。</p><p>然后在广州马拉松开赛前的三周，我每周的跑量都是40左右，这个已经是我能够做到的极限了。</p><p>跑步是一个时间成本很高的运动，需要大量的时间。</p><!-- ![起点](/images/running/guangzhou_2.jpg) --><div class="tag-plugin image"><div class="image-bg"><img class="lazy" src="/memoirs/images/running/guangzhou_2.jpg" data-src="/memoirs/images/running/guangzhou_2.jpg" alt="起点"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">起点</span></div></div><h3 id="顺利完赛"><a href="#顺利完赛" class="headerlink" title="顺利完赛"></a>顺利完赛</h3><p>作为我的第一次马拉松（全马），我很认真准备了三周，最后一周的周日跑了一个21公里。</p><p>三周的训练时间，每个周末都会拉一个长距离，15KM -&gt; 18KM -&gt; 21KM，逐渐加长。</p><p>所以我对35KM是有信心完成的，后面的就不知道了，跑之前的计划是，35公里之后走一半跑一半，5小时内完赛就是胜利✌🏻</p><p>比赛当日的广州天气很好，温度很合适，而且有风，风也不大。</p><p>但是最后的8公里确实让人绝望，35KM之后的每一公里都是痛苦面具。</p><p>不过最后的成绩跟我预料的相差不大。</p><!-- ![成绩](/images/running/guangzhou_achieve.jpg) --><div class="tag-plugin image"><div class="image-bg"><img class="lazy" src="/memoirs/images/running/guangzhou_achieve.jpg" data-src="/memoirs/images/running/guangzhou_achieve.jpg" alt="成绩"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">成绩</span></div></div><h4 id="完赛前的视频"><a href="#完赛前的视频" class="headerlink" title="完赛前的视频"></a>完赛前的视频</h4><div class="tag-plugin video-player" style="max-width:50%;">  <video controls preload playsinline webkit-playsinline>  <source src="/memoirs/assets/guangzhou_3.mp4" type="video/mp4">Your browser does not support the video tag.  </video>  </div>  <h3 id="夜游广州"><a href="#夜游广州" class="headerlink" title="夜游广州"></a>夜游广州</h3><p>多年后来到广州，重新去了珠江边，上了珠江边的那个发着光的大桥。</p><p>当然首先是吃了一个晚饭，或者说中饭。因为我拿到赛事大礼包的时候已经3点多了，将近4点。</p><!-- ![米粒和夕夕](/images/minnie/guangzhou1.jpg) --><div class="tag-plugin image"><div class="image-bg"><img class="lazy" src="/memoirs/images/minnie/guangzhou1.jpg" data-src="/memoirs/images/minnie/guangzhou1.jpg" alt="米粒和夕夕"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">米粒和夕夕</span></div></div><p>大桥可以联通到珠江中间的二沙岛，二沙岛旁边就是马拉松的终点 - 海心沙</p><p>远远看去，那座桥像鹊桥一样，勾勒出一个很好看的弧形一直延伸到珠江中间的岛上。</p><p>因为桥的轮廓是发着光的，很有未来科技的感觉，反而是珠江中间的海心沙，融入黑暗中，让人感觉这座发光的鹊桥横跨了黑暗，一路延伸。</p><div class="tag-plugin gallery grid-box" size="xl" ratio="origin"><div class="grid-cell lazy-box"><img class="lazy" data-fancybox="gallery-1" data-src="/memoirs/images/minnie/guangzhou2.jpg" alt="米粒"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div><div class="image-meta"><span class="image-caption">米粒</span></div></div><div class="grid-cell lazy-box"><img class="lazy" data-fancybox="gallery-1" data-src="/memoirs/images/minnie/guangzhou3.jpg" alt="米粒"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div><div class="image-meta"><span class="image-caption">米粒</span></div></div></div><h2 id="长隆野生动物园"><a href="#长隆野生动物园" class="headerlink" title="长隆野生动物园"></a>长隆野生动物园</h2><p>第二天约好了一起去长隆野生动物园，不过因为要补课要12点才能下课，我们只能中午吃完饭之后出发，到达长隆的时候已经是2点多了。</p><p>我好像从没去过长隆，以前在广州的时候没有机会体验这些。</p><p>长隆有一点类似迪士尼的样子，不过长隆把几个乐园分开了，欢乐世界、水上乐园、野生动物园。同样的，门票也分开了，相比于迪士尼的599，还是相对便宜的，也只是相对便宜而已！</p><p>长隆野生动物园还是非常适合小孩子的，坐着小火车穿越丛林、草原，非洲的、美洲的，还有亚洲的动物都可以看见。</p><p>那些狮子老虎居然躺在山上看着我们，我们可以在小火车上跟狮子遥相对望。不过狮子们好像对我们没有兴趣，它们是我们的风景，我们也是他们的风景。</p><p>每每去动物园看这些，我都会想起一个电影《楚门的世界》。这个世界好像一个巨大的楚门的世界，每个人都在表演，表演活着，表演高兴、悲伤、痛苦，以及生离死别。</p><!-- ![非洲大草原](/images/running/guangzhou_3.jpg) --><div class="tag-plugin image"><div class="image-bg"><img class="lazy" src="/memoirs/images/running/guangzhou_3.jpg" data-src="/memoirs/images/running/guangzhou_3.jpg" alt="非洲大草原"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">非洲大草原</span></div></div><p>做完火车之后就可以自由选择路线了，有的孩子愿意去<strong>青龙山</strong>看蛇和恐龙，由于之前有一个小朋友被恐龙吓哭了，我们就不去看恐龙了。</p><p>另外一条路线可以看花车游行，还可以看见大熊猫，看完大熊猫可以坐缆车回去。 </p><p>花车游行的时候我抱着小米粒，所以没办法拍视频，只有米粒和八戒的合照🙂🙂🙂</p><!-- ![米粒和八戒](/images/minnie/guangzhou4.jpg) --><div class="tag-plugin image"><div class="image-bg"><img class="lazy" src="/memoirs/images/minnie/guangzhou4.jpg" data-src="/memoirs/images/minnie/guangzhou4.jpg" alt="米粒和八戒"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">米粒和八戒</span></div></div><p>后面游行完之后还有看大熊猫，三胞胎的大兄弟，全国也只有这一对。</p><p>但是这个时候我已经是困得不行了，加上看熊猫的人太热情了，人巨多，最后我累倒在一个小游乐场的餐桌前。</p><p>我只记得我趴着睡着了，睡醒的时候已经天黑了。小朋友们还很有激情，看着转转的飞天座椅，兴冲冲的排队。</p><p>结果在关门前的最后一轮，被挡在了前面，没有成功上去。</p><p>小朋友吃完了零食和冰淇淋，只能很失望的下山了。</p><h2 id="返程"><a href="#返程" class="headerlink" title="返程"></a>返程</h2><p>返程日的天气真好，阳光明媚的。</p><p>完赛之后大概是12点半，阳台晒在身上懒洋洋的，像一个调皮的孩子在你的皮肤上打滚，或者开心的在上面跳舞。</p><p>从海心沙走到地铁站真的好远啊，我跑完了42公里，还要走半个小时才能到地铁站。</p><p>从广州塔出来，阳光更舒服了，基本上可以穿着短袖短裤享受阳光。</p><!-- ![米粒和广州塔](/images/minnie/guangzhou.jpg) --><div class="tag-plugin image"><div class="image-bg"><img class="lazy" src="/memoirs/images/minnie/guangzhou.jpg" data-src="/memoirs/images/minnie/guangzhou.jpg" alt="米粒和广州塔"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">米粒和广州塔</span></div></div><p>在广州塔玩耍了几个小时，到了吃饭的点，吃完饭就该去赶火车了。</p><p>这个时候身体已经冷下来了，右腿一脚不能弯曲了，一弯曲就疼的不行，基本上只能一瘸一拐的走路。</p><p>综合我的训练量，这个结果是必然的，最后的8公里已经超出了我的能力极限。</p><div class="tag-plugin quot p"><p class="content"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from BoxIcons Solid by Atisa - https://creativecommons.org/licenses/by/4.0/ --><path fill="currentColor" d="M3.691 6.292C5.094 4.771 7.217 4 10 4h1v2.819l-.804.161c-1.37.274-2.323.813-2.833 1.604A2.9 2.9 0 0 0 6.925 10H10a1 1 0 0 1 1 1v7c0 1.103-.897 2-2 2H3a1 1 0 0 1-1-1v-5l.003-2.919c-.009-.111-.199-2.741 1.688-4.789M20 20h-6a1 1 0 0 1-1-1v-5l.003-2.919c-.009-.111-.199-2.741 1.688-4.789C16.094 4.771 18.217 4 21 4h1v2.819l-.804.161c-1.37.274-2.323.813-2.833 1.604A2.9 2.9 0 0 0 17.925 10H21a1 1 0 0 1 1 1v7c0 1.103-.897 2-2 2"/></svg><span class="text">广马是什么？</span><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from BoxIcons Solid by Atisa - https://creativecommons.org/licenses/by/4.0/ --><path fill="currentColor" d="M20.309 17.708C22.196 15.66 22.006 13.03 22 13V5a1 1 0 0 0-1-1h-6c-1.103 0-2 .897-2 2v7a1 1 0 0 0 1 1h3.078a2.9 2.9 0 0 1-.429 1.396c-.508.801-1.465 1.348-2.846 1.624l-.803.16V20h1c2.783 0 4.906-.771 6.309-2.292m-11.007 0C11.19 15.66 10.999 13.03 10.993 13V5a1 1 0 0 0-1-1h-6c-1.103 0-2 .897-2 2v7a1 1 0 0 0 1 1h3.078a2.9 2.9 0 0 1-.429 1.396c-.508.801-1.465 1.348-2.846 1.624l-.803.16V20h1c2.783 0 4.906-.771 6.309-2.292"/></svg></p></div><div class="tag-plugin quot p"><p class="content"><img class="icon prefix" src="https://api.iconify.design/line-md:moon-alt-to-sunny-outline-loop-transition.svg" /><span class="text">是无数颗心不舍昼夜</span><span class="empty"></span></p></div><div class="tag-plugin quot p"><p class="content"><span class="empty"></span><span class="text">是无数颗心不舍昼夜 共同书写的一场</span><img class="icon prefix" src="https://api.iconify.design/solar:running-round-bold-duotone.svg" /></p></div><div class="tag-plugin quot p"><p class="content"><span class="empty"></span><span class="text">关于热爱和信念的伟大篇章</span><img class="icon prefix" src="https://api.iconify.design/solar:running-round-bold-duotone.svg" /></p></div><div class="tag-plugin quot p"><p class="content"><span class="empty"></span><span class="text">是讲述不完的一个个故事</span><img class="icon prefix" src="https://api.iconify.design/solar:running-round-bold-duotone.svg" /></p></div><div class="tag-plugin quot p"><p class="content"><span class="empty"></span><span class="text">是分享不完的一串串感动</span><img class="icon prefix" src="https://api.iconify.design/solar:running-round-bold-duotone.svg" /></p></div><div class="tag-plugin quot p"><p class="content"><span class="empty"></span><span class="text">----是</span><img class="icon prefix" src="https://api.iconify.design/solar:running-round-bold-duotone.svg" /></p></div><div class="tag-plugin quot p"><p class="content"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from BoxIcons Solid by Atisa - https://creativecommons.org/licenses/by/4.0/ --><path fill="currentColor" d="M3.691 6.292C5.094 4.771 7.217 4 10 4h1v2.819l-.804.161c-1.37.274-2.323.813-2.833 1.604A2.9 2.9 0 0 0 6.925 10H10a1 1 0 0 1 1 1v7c0 1.103-.897 2-2 2H3a1 1 0 0 1-1-1v-5l.003-2.919c-.009-.111-.199-2.741 1.688-4.789M20 20h-6a1 1 0 0 1-1-1v-5l.003-2.919c-.009-.111-.199-2.741 1.688-4.789C16.094 4.771 18.217 4 21 4h1v2.819l-.804.161c-1.37.274-2.323.813-2.833 1.604A2.9 2.9 0 0 0 17.925 10H21a1 1 0 0 1 1 1v7c0 1.103-.897 2-2 2"/></svg><span class="text">“我们的力量”</span><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from BoxIcons Solid by Atisa - https://creativecommons.org/licenses/by/4.0/ --><path fill="currentColor" d="M20.309 17.708C22.196 15.66 22.006 13.03 22 13V5a1 1 0 0 0-1-1h-6c-1.103 0-2 .897-2 2v7a1 1 0 0 0 1 1h3.078a2.9 2.9 0 0 1-.429 1.396c-.508.801-1.465 1.348-2.846 1.624l-.803.16V20h1c2.783 0 4.906-.771 6.309-2.292m-11.007 0C11.19 15.66 10.999 13.03 10.993 13V5a1 1 0 0 0-1-1h-6c-1.103 0-2 .897-2 2v7a1 1 0 0 0 1 1h3.078a2.9 2.9 0 0 1-.429 1.396c-.508.801-1.465 1.348-2.846 1.624l-.803.16V20h1c2.783 0 4.906-.771 6.309-2.292"/></svg></p></div>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20241212/running/guangzhou/</id>
    <link href="https://minniexcode.github.io/memoirs/20241212/running/guangzhou/"/>
    <published>2024-12-12T09:12:20.000Z</published>
    <summary>
      <![CDATA[<h3 id="马拉松中签"><a href="#马拉松中签" class="headerlink" title="马拉松中签"></a>马拉松中签</h3><p>今年挺幸运的，第二轮的补录，中签了广州马拉松。作为全国的大满贯赛事，我还是很希望广州马拉松能够中签的，毕竟武汉马拉松]]>
    </summary>
    <title>广州马拉松</title>
    <updated>2026-03-16T16:27:30.444Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="阅读与思考" scheme="https://minniexcode.github.io/memoirs/categories/%E9%98%85%E8%AF%BB%E4%B8%8E%E6%80%9D%E8%80%83/"/>
    <category term="Reading" scheme="https://minniexcode.github.io/memoirs/tags/Reading/"/>
    <category term="Book Notes" scheme="https://minniexcode.github.io/memoirs/tags/Book-Notes/"/>
    <content>
      <![CDATA[<p>《乡土中国》是社会学的经典书，可以说是经典中的经典，也是所有研究中国的人必读的一本书。</p><p>这本书讲的是中国人的社会操作系统，是研究中国人复杂思维非常重要的一块基石，</p><h3 id="一、乡土本色"><a href="#一、乡土本色" class="headerlink" title="一、乡土本色"></a>一、乡土本色</h3><ol><li><p>中国基层社会是“乡土性的”。“乡土性”对应着自古以来人们对土地的依附和“不流动”。</p><p> 从历史的角度：“我们的民族确是和泥土分不开的了。<br> 从土里长出过光荣的历史，自然也会受到土的束缚…”<br> 从地理的角度：“农业和游牧或工业不同，它是直接取资于土地的。游牧的人可以逐水草而居，飘忽无定；做工业的人可以择地而居，迁移无碍；而种地的人却搬不动地，长在土里的庄稼行动不得，侍候庄稼的老农也因之像是半身插入了土里，土气是因为不流动而发生的。”</p></li><li><p>“不流动”是指中国农村以村落为单位彼此孤立隔绝。</p><p> “不流动是从人和空间的关系上说的，从人和人在空间的排列关系上说就是孤立和隔膜。孤立和隔膜并不是以个人为单位的，而是以住在一处的集团为单位的。”</p></li><li><p>中国农村聚村而居大致由于4个原因：小农经营下屋舍与农田较近、共用水力设施的需要、安全的需要、土地不断继承积累。</p></li><li><p>在缺乏流动性的乡土社会，每个人从出生便被嵌入了熟人社会的网络。</p><p> “乡土社会在地方性的限制下成了生于斯、死于斯的社会。常态的生活是终老是乡。…这是一个“熟悉”的社会，没有陌生人的社会。”</p></li><li><p>传统熟人社会依靠习俗和人与人之间的了解维系，与现代社会所讲的规则和契约精神产生了冲突。</p><p> “在一个熟悉的社会中，我们会得到从心所欲而不逾规矩的自由。这和法律所保障的自由不同。规矩不是法律，规矩是“习”出来的礼俗。从俗即是从心。换一句话说，社会和个人在这里通了家。”</p><p> “乡土社会的信用并不是对契约的重视，而是发生于对一种行为的规矩熟悉到不假思索时的可靠性。”</p></li></ol><p>“这种办法在一个陌生人面前是无法应用的。在我们社会的急速变迁中，从乡土社会进入现代社会的过程中，我们在乡土社会中所养成的生活方式处处产生了流弊。陌生人所组成的现代社会是无法用乡土社会的习俗来应付的。”</p><h3 id="二、文字下乡（空间尺度上文字在乡村的价值）"><a href="#二、文字下乡（空间尺度上文字在乡村的价值）" class="headerlink" title="二、文字下乡（空间尺度上文字在乡村的价值）"></a>二、文字下乡（空间尺度上文字在乡村的价值）</h3><ol><li><p>乡下人与城里人知识结构不尽相同，易产生乡下人“愚”的误解。</p></li><li><p>“乡下多文盲”与熟人社会下运用“特殊语言”比文字间接表意更有效有关。面对面能解决的事不必诉诸文字。</p><p> “特殊语言”不过是亲密社群中所使用的象征体系的一部分，用声音来作象征的那一部分。在亲密社群中可用来作象征体系的原料比较多。表情、动作，在面对面的情境中，有时比声音更容易传情达意。</p><p> “特殊语言”常是特别有效，因为它可以摆脱字句的固定意义。语言像是个社会定下的筛子，如果我们有一种情意和这筛子的格子不同也就漏不过去。</p><p> “在乡土社会中，不但文字是多余的，连语言都并不是传达情意的唯一象征体系。”</p></li><li><p>文字下乡需要结合现实情况。</p><p> “我决不是说我们不必推行文字下乡，在现代化的过程中，我们已经开始抛离乡土社会，文字是现代化的工具。我要辨明的是乡土社会中的文盲，并非出于乡下人的“愚”，而是由于乡土社会的本质。</br><br> 而且我还愿意进一步说，单从文字和语言的角度中去批判一个社会中人和人的了解程度是不够的，因为文字和语言，只是传情达意的一种工具，并非唯一的工具；而且这工具本身也是有缺陷的，能传的情、能达的意是有限的。</br><br> 所以提倡文字下乡的人，必须先考虑到文字和语言的基础。”</p></li></ol><h3 id="三、再论文字下乡（时间尺度上文字在乡村的价值）"><a href="#三、再论文字下乡（时间尺度上文字在乡村的价值）" class="headerlink" title="三、再论文字下乡（时间尺度上文字在乡村的价值）"></a>三、再论文字下乡（时间尺度上文字在乡村的价值）</h3><ol><li><p>人靠一套象征体系来存储记忆，因为人凭借记忆积累的过去的经验对现在的生活有益。而对集体而言，共同的记忆就是文化。</p><p> “文化是依赖象征体系和个人的记忆而维护着的社会共同经验。”</p></li><li><p>这套象征体系中最重要的就是“词”，但“词”不等于“文”，前者是文化存续的前提而后者不是。</p><p> “我们不断地在学习时说着话，把具体的情境抽象成一套能普遍应用的概念，概念必然是用词来表现的，于是我们靠着词，使我们从特殊走上普遍，在个别情境中搭下了桥梁；又使我们从当前走到今后，在片刻情境中搭下了桥梁。”</p><p> “没有象征体系也就没有概念，人的经验也就不能或不易在时间里累积”</p><p> “但是词却不一定要文。文是用眼睛可以看得到的符号，就是字。词不一定是刻出来或写出来的符号，也可以是用声音说出来的符号，语言。一切文化中不能没有“词”，可是不一定有“文字”。”</p></li><li><p>乡土社会的人日出而作日落而息生活定型，乡土社会的历史轨迹也是一条直线，语言足以传递代际经验，不需要文字了。而文字既不产生于基层也不属于基层乡土社会。所以只有在乡土性的基层发生了变化后，文字才能下乡。</p><p> “当一个人碰着生活上的问题时，他必然能在一个比他年长的人那里问得到解决这问题的有效办法，因为大家在同一环境里，走同一道路，他先走，你后走；后走的所踏的是先走的人的脚印，口口相传，不会有遗漏。哪里用得着文字？时间里没有阻隔，拉得十分紧，全部文化可以在亲子之间传授无缺。”</p></li></ol><h3 id="四、差序格局（划重点）"><a href="#四、差序格局（划重点）" class="headerlink" title="四、差序格局（划重点）"></a>四、差序格局（划重点）</h3><ol><li><p>在中国，由“私”引起的社会问题很普遍，“私”与“差序格局”密切相关。</p><p> “一说是公家的，差不多就是说大家可以占一点便宜的意思，有权利而没有义务了。”</p><p> “于是像格兰亨姆的公律，坏钱驱逐好钱一般，公德心就在这里被自私心驱走。”（外在激励与自我利益最大化）</p><p> “所谓贪污无能，并不是每个人绝对的能力问题，而是相对的，是从个人对公家的服务和责任上说的。”</p></li><li><p>与西方社会界限分明的“团体格局”不同，中国的“差序格局”下，团体的界限并不清晰，无论是在亲属关系还是地缘关系中，团体以个人为中心，其覆盖范围是任意的，正如投石入水激起的涟漪。</p><p> “这个网络像个蜘蛛的网，有一个中心，就是自己。我们每个人都有这么一个以亲属关系布出去的网，但是没有一个网所罩住的人是相同的。在一个社会里的人可以用同一个体系来记认他们的亲属，所同的只是这体系罢了。体系是抽象的格局”</p><p> “中国人也特别对世态炎凉有感触，正因为这富于伸缩的社会圈子会因中心势力的变化而大小。”</p></li><li><p>正是因为团体界限不清晰，我们乡土社会讲交情而不是权利。</p><p> “在孩子成年了住在家里都得给父母膳食费的西洋社会里，大家承认团体的界限。在团体里的有一定的资格。资格取消了就得走出这个团体。在他们不是人情冷热的问题，而是权利问题。在西洋社会里争的是权利，而在我们却是攀关系、讲交情。”</p></li><li><p>正是因为以“己”为中心，乡土社会利己的自我主义突出。</p><p> “在个人主义下，一方面是平等观念，指在同一团体中各分子的地位相等，个人不能侵犯大家的权利；一方面是宪法观念，指团体不能抹煞个人，只能在个人们所愿意交出的一分权利上控制个人。</br><br> 这些观念必须先假定了团体的存在。在我们中国传统思想里是没有这一套的，因为我们所有的是自我主义，一切价值是以“己”作为中心的主义。”</p></li><li><p>与西方不同，乡土社会的“公”与“私”是相对的，不利于现代国家观念的形成。</p><p> “国是皇帝之家，界线从来就是不清不楚的，不过是从自己这个中心里推出去的社会势力里的一圈而已。</br><br> 所以可以着手的，具体的只有己，克己也就成了社会生活中最重要的德行，他们不会去克群，使群不致侵略个人的权利。</br><br> 在这种差序格局中，是不会发生这问题的。”</p></li></ol><h3 id="五、系维着私人的道德"><a href="#五、系维着私人的道德" class="headerlink" title="五、系维着私人的道德"></a>五、系维着私人的道德</h3><ol><li><p>社会结构或者说社会格局决定社会的道德观念。</p><p> “道德观念是在社会里生活的人自觉应当遵守社会行为规范的信念。它包括着行为规范、行为者的信念和社会的制裁。它的内容是人和人关系的行为规范，是依着该社会的格局而决定的。</br><br> 从社会观点说，道德是社会对个人行为的制裁力，使他们合于规定下的形式行事，用以维持该社会的生存和绵续。”</p></li><li><p>西方“团体格局”在宗教的影响下形成，“神”象征着团体，人在神前平等，在团体（如国家）中平等享有权利履行义务，没有人可以凌驾于神或团体之上。</p><p> “宗教的虔诚和信赖不但是他们道德观念的来源，而且是支持行为规范的力量，是团体的象征。在象征着团体的神的观念下，有着两个重要的派生观念：一是每个个人在神前的平等；一是神对每个个人的公道。”</p><p> “可是上帝是在冥冥之中，正象征团体无形的实在；但是在执行团体的意志时，还得有人来代理。“代理者”Minister是团体格局的社会中一个基本的概念。</br><br> 执行上帝意志的牧师是Minister，执行团体权力的官吏也是Minister，都是“代理者”，而不是神或团体的本身”</p></li><li><p>中国没有平等爱人的宗教文化，只有以自我为中心的有等级差序的爱，难有统一的公共道德标准，道德依附在私人关系上。</p><p> “不但在我们传统道德系统中没有一个像基督教里那种“爱”的观念—不分差序的兼爱；而且我们也很不容易找到个人对于团体的道德要素。</br><br> 在西洋团体格局的社会中，公务，履行义务，是一个清楚明白的行为规范。而这在中国传统中是没有的。”</p><p> “而忠君并不是个人与团体的道德要素，而依旧是对君私之间的关系。</br><br> 团体道德的缺乏，在公私的冲突里更看得清楚。”</p><p> “传统的道德里不另找出一个笼统性的道德观念来，所有的价值标准也不能超脱于差序的人伦而存在了。</br><br> 中国的道德和法律，都因之得看所施的对象和“自己”的关系而加以程度上的伸缩。”</p></li></ol><h3 id="六、家族"><a href="#六、家族" class="headerlink" title="六、家族"></a>六、家族</h3><ol><li><p>理清本书说述“差序格局”不是指中国没有诸如家庭和氏族等“团体”，而是指“从主要的格局说，在中国乡土社会中，差序格局和社会圈子的组织是比较的重要。”</p></li><li><p>家庭不仅有生育合作功能，还有组织经济等功能，可以按需要任意伸缩范围，扩大路线一般只按父系，其性质近似与氏族。</p><p> “家庭这概念在人类学上有明确的界说：这是个亲子所构成的生育社群。”</p><p> “依人类学上的说法，氏族是一个事业组织，再扩大就可以成为一个部落”</p><p> “我的假设是中国乡土社会采取了差序格局，利用亲属的伦常去组合社群，经营各种事业，使这基本的家，变成氏族性了。</br><br> 家的结构不能限于亲子的小组合，必须加以扩大。而且凡是政治、经济、宗教等事物都需要长期绵续性的，这个基本社群决不能像西洋的家庭一般是临时的。</br><br> 家必须是绵续的，不因个人的长成而分裂，不因个人的死亡而结束，于是家的性质变成了族。氏族本是长期的，和我们的家一般。</br><br> 我称我们这种社群作小家族，也表示了这种长期性在内，和家庭的临时性相对照。”</p><p> “中国的家是一个事业组织，家的大小是依着事业的大小而决定。”</p></li><li><p>家庭包含了太多生育之外的功能，导致“中国人在感情上，尤其是在两性间的矜持和保留。”</p></li></ol><h3 id="七、男女有别"><a href="#七、男女有别" class="headerlink" title="七、男女有别"></a>七、男女有别</h3><ol><li><p>中国传统感情定向是接受秩序的安排。</p><p> “在上篇我说家族在中国的乡土社会里是一个事业社群，凡是做事业的社群，纪律是必须维持的，纪律排斥了私情。”</p></li><li><p>乡土社会的社会关系生而定型，不需创造新的社会关系，它追求的是稳定，因此“男女间的关系必须有一种安排，使他们之间不发生激动性的感情。那就是男女有别的原则。”</p><p> “社会秩序范围着个性，为了秩序的维持，一切足以引起破坏秩序的要素都被遏制着。男女之间的鸿沟从此筑下。乡土社会是个男女有别的社会，也是个安稳的社会。”</p></li></ol><h3 id="八、礼治秩序"><a href="#八、礼治秩序" class="headerlink" title="八、礼治秩序"></a>八、礼治秩序</h3><ol><li><p>“人治”与“法治”的区别不在于有没有法律可依据，而在于“维持秩序时所用的力量，和所根据的规范的性质。”</p></li><li><p>但无“法”并不会使乡土社会混乱不堪，因为有“礼治”的维系。</p><p> “礼是社会公认的行为规范，它本和法律无异，不同之处在于法律是靠国家的权力来推行的，而礼却不需要这样有形的权力机构来维持，维持礼这种规范的是传统。”</p></li><li><p>“法”由外而内约束人，“礼”由内而外控制人。</p><p> “礼并不是靠一个外在的权力来推行的，而是从教化中养成了个人的敬畏感，使人服膺，人服礼是主动的”</p></li></ol><h3 id="九、无讼"><a href="#九、无讼" class="headerlink" title="九、无讼"></a>九、无讼</h3><ol><li><p>传统生活是礼治社会，违背规矩是道德问题，需要的是教育和教化。而现代法治社会讲个人权利，权利不可侵犯，以刑法保护个人的权利和社会的安全。</p><p> “但是在乡土社会的礼治秩序中做人，如果不知道“礼”，就成了撒野，没有规矩，简直是个道德问题，不是个好人。一个负责地方秩序的父母官，维持礼治秩序的理想手段是教化，而不是折狱。”</p><p> “一个法官并不考虑道德问题、伦理观念，他并不在教化人。刑罚的用意已经不复“以儆效尤”，而是在保护个人的权利和社会的安全。尤其在民法范围里，他并不是在分辨是非，而是在厘定权利。”</p></li><li><p>法治秩序的建立不能单靠制定若干法律条文和设立若干法庭，还得看人民怎么去应用这些设备。社会结构和思想理念还得先有一番改革。</p></li></ol><h3 id="十、无为政治"><a href="#十、无为政治" class="headerlink" title="十、无为政治"></a>十、无为政治</h3><ol><li><p>权力有两种用途，一是在阶级斗争中平衡冲突的工具，二是支配社会分工。即横暴权力和同意权力。</p><p> “冲突的性质并没有消弭，但是武力的阶段过去了，被支配的一方面已认了输，屈服了。但是他们并没有甘心接受胜利者所规定下的条件，非心服也。于是两方面的关系中发生了权力。</br><br> 权力是维持这关系所必需的手段，它是压迫性质的，是上下之别”</br><br> “这种权力的基础是社会契约，是同意。社会分工愈复杂，这权力也愈扩大。”</p></li><li><p>权力的诱人之处在于它可能带来经济利益，但农业社会被权力征服的团体生产力不高，吸引力不强，但易于被征服。</p></li><li><p>权力滋长靠内部农业生产力的提高，而和平有利于人口增长和农业社会力量的积聚，所以需要“养民”。</p><p> “皇权力求无为，所谓养民。养到一个时候，皇权逐渐累积了一些力量，这力量又刺激皇帝的雄图大略，这种循环也因而复始。</p><p> 为了皇权自身的维持，在历史的经验中，找到了“无为”的生存价值，确立了无为政治的理想。”</p></li></ol><h3 id="十一、长老统治"><a href="#十一、长老统治" class="headerlink" title="十一、长老统治"></a>十一、长老统治</h3><ol><li><p>中国社会基层存在一种长老式的权力</p><p> “既不是横暴性质，也不是同意性质；既不是发生于社会冲突，也不是发生于社会合作；它是发生于社会继替的过程，是教化性的权力，或是说爸爸式的，英文里是Paternalism。”</p></li><li><p>教化权力没有政治权力的强制性，但由于人人都认可，也形成了一种每个成员都被迫接受的规范。</p><p> “所谓社会契约必先假定个人的意志。个人对于这种契约虽则并没有自由解脱的权利，但是这种契约性的规律在形成的过程中，必须尊重各个人的自由意志，民主政治的形式就是综合个人意志和社会强制的结果。</br><br> 在教化过程中并不发生这个问题，被教化者并没有选择的机会。”</p><p> “凡是文化性的，不是政治性的强制都包含这种权力。</br><br> 文化和政治的区别就在这里：凡是被社会不成问题地加以接受的规范，是文化性的；当一个社会还没有共同接受一套规范，各种意见纷呈，求取临时解决办法的活动是政治。</br><br> 文化的基础必须是同意的，但文化对于社会的新分子是强制的，是一种教化过程。”</p></li><li><p>文化性规范不如成文的政治性规范稳定，因此在社会变迁中需要年长者的维系。</p></li></ol><h3 id="十二、血缘和地缘"><a href="#十二、血缘和地缘" class="headerlink" title="十二、血缘和地缘"></a>十二、血缘和地缘</h3><ol><li><p>长幼权力差别形成了血缘社会个人权利义务不同的基础，这种基础与生俱来不可选择。</p><p> “缺乏变动的文化里，长幼之间发生了社会的差次，年长的对年幼的具有强制和权力。这是血缘社会的基础。”</p><p> “血缘的意思是人和人的权利和义务根据亲属关系来决定。”</p></li><li><p>血缘与地缘存在联系与冲突。</p><p> “血缘是稳定的力量。在稳定的社会中，地缘不过是血缘的投影，不分离的。”</p><p> “籍贯只是“血缘的空间投影。”</p><p> “地缘是从商业里发展出来的社会关系。血缘是身份社会的基础，而地缘却是契约社会的基础。”</p></li><li><p>现代社会讲求契约关系，是乡土社会所欠缺的，需要从血缘关系到地缘关系的转变。</p><p> “契约的完成是权利义务的清算，须要精密的计算、确当的单位、可靠的媒介。在这里是冷静的考虑，不是感情，于是理性支配着人们的活动—这一切是现代社会的特性，也正是乡土社会所缺的。</p><p> 从血缘结合转变到地缘结合是社会性质的转变，也是社会史上的一个大转变。”</p></li></ol><h3 id="十三、名实的分离"><a href="#十三、名实的分离" class="headerlink" title="十三、名实的分离"></a>十三、名实的分离</h3><ol><li><p>社会变动分为“社会继替”和“社会变迁”，二者同时存在，但前者更明显，后者由于需求作用不显著。</p><p> “社会继替是指人物在固定的社会结构中的流动；社会变迁却是指社会结构本身的变动。这两种过程并不是冲突的，而是同时存在的。”</p></li><li><p>乡土社会并非绝对静止，在其缓慢的变化中，长老权力在社会继替中壮大在“社会继替”中产生长老权力，如果社会变动得慢，长老权力也就更有势力。</p><p> “社会结构的变动是人要它变的，要它变的原因是在它已不能答复人的需要。”</p><p> “社会变迁可以吸收在社会继替之中的时候，我们可以称这社会是安定的。”</p></li><li><p>长老权力在乡土社会生命力极强。</p><p> “儒家所注重的“孝”道，其实是维持社会安定的手段，孝的解释是“无违”，那就是承认长老权力。</br><br> 长老代表传统，遵守传统也就可以无违于父之教。”</p><p> “长老权力是建立在教化作用之上的，教化是有知对无知，如果所传递的文化是有效的，被教的自没有反对的必要；如果所传递的文化已经失效，根本也就失去了教化的意义。“反对”在这种关系里是不发生的。”</p></li><li><p>长老依靠“加入注释”维系权力，而注释的变动可以引起名实之间发生极大的分离。</p><p> “长老权力下，传统的形式是不准反对的，但是只要表面上承认这形式，内容却可以经注释而改变。结果不免是口是心非。</br><br> 在中国旧式家庭中生长的人都明白家长的意志怎样在表面的无违下，事实上被歪曲的。</br><br> 虚伪在这种情境中不但是无可避免而且是必需的。”</p></li></ol><h3 id="十四、从欲望到需要"><a href="#十四、从欲望到需要" class="headerlink" title="十四、从欲望到需要"></a>十四、从欲望到需要</h3><ol><li><p>乡土社会靠欲望行事能自洽。</p><p> “在十九世纪发生了一种理论说，每个人只要能“自私”，那就是充分地满足我们本性里带来的欲望，社会就会形成一个最好、最融洽的秩序。”</p><p> “在乡土社会中，这种理论多少可以说是正确的，正确的原因并不是真是有个“冥冥中”的那只手，而是在乡土社会中个人的欲望常是合于人类生存条件的。”</p></li><li><p>于是乡土社会靠欲望，不像现代社会靠理性和计划。</p><p> “在乡土社会中人可以靠欲望去行事，但在现代社会中欲望并不能作为人们行为的指导，于是产生“需要”，因之有了“计划”。从欲望到需要是社会变迁中一个很重要的里程碑。”</p></li><li><p>乡土社会依赖经验，现代社会依赖知识。</p><p> “乡土社会是个传统社会，传统就是经验的累积，能累积就是说经得起自然选择的，各种“错误”—不合于生存条件的行为—被淘汰之后留下的那一套生活方式。不论行为者对于这套方式怎样说法，它们必然是有助于生存的。”</p><p> “在现代社会里知识即是权力，因为在这种社会里生活的人要依他们的需要去做计划。从知识里得来的权力是我在上文中所称的时势权力。”</p></li></ol>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20210915/%E4%B9%A1%E5%9C%9F%E4%B8%AD%E5%9B%BD/</id>
    <link href="https://minniexcode.github.io/memoirs/20210915/%E4%B9%A1%E5%9C%9F%E4%B8%AD%E5%9B%BD/"/>
    <published>2021-09-14T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>《乡土中国》是社会学的经典书，可以说是经典中的经典，也是所有研究中国的人必读的一本书。</p>
<p>这本书讲的是中国人的社会操作系统，是研究中国人复杂思维非常重要的一块基石，</p>
<h3 id="一、乡土本色"><a href="#一、乡土本色" class="hea]]>
    </summary>
    <title>乡土中国</title>
    <updated>2021-09-15T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="关于世界的一切" scheme="https://minniexcode.github.io/memoirs/categories/%E5%85%B3%E4%BA%8E%E4%B8%96%E7%95%8C%E7%9A%84%E4%B8%80%E5%88%87/"/>
    <category term="Essay" scheme="https://minniexcode.github.io/memoirs/tags/Essay/"/>
    <content>
      <![CDATA[<h2 id="小说的那些套路"><a href="#小说的那些套路" class="headerlink" title="小说的那些套路"></a>小说的那些套路</h2><ul><li>1.落魄大叔爱喝酒，以前肯定是高手。</li><li>2.高手厌倦俗世纷争，避世隐居。但往往无论什么杂鱼都知道他隐居到哪儿去了。</li><li>3.几拨人同时上山去请一个高手，谁先上去谁倒霉。高手都是跟着最后上去的那拨人走的。</li><li>4.大家族里从来不学文化课，整天研究怎么打架，年轻一代天天就盘算着出去强抢民女。就这点出息，见笑了。</li><li>5.叶、萧、林、易，四大修真家族，长期为修真界输送着各种优秀人才。</li><li>6.统领一方地界的强者，见到少年高手从来不想着笼络，只想着灭口。难怪手下只剩下些弱智。</li><li>7.超级废柴划入外门，走上人生巅峰。顶级天才被收为入室弟子，从此暗淡无光。</li><li>8.顶级高手的孩子自带GPS，不管小时候扔多远，最后总能找回来。</li><li>9.圣女选出来就是为了让人拐跑的。</li><li>10.名医给病人治完病往往会顺带把自家闺女搭出去。</li><li>11.一个普通人活了几十年学会写字绘画，修身养性。一个修真者修炼数百年满脑子是女人和钱。</li><li>12.但凡大家族都有一套特别邪门的武功，用了会短命的那种。</li><li>13.著名学院附近多半有树林，树林里必定蹲着一些特别危险的上古魔兽。</li><li>14.顶级高手不是二十岁以下的就是几百岁以上的，中间好几代人都在打酱油。</li><li>15.高手出招如电如风，但一定会很礼貌地等敌人摆完姿势喊完招式名才动手。</li><li>16.无论什么高级毒药都是可以化解的，唯独泻药无解。不管是什么等级的高手，服了泻药没有几个能解掉的。</li><li>17.正道长期向魔道输送高级人才魔道长期向正道提供绝世神兵。</li><li>18.他是众口相传的绝顶高手，令后辈敬仰的不朽传奇重新出山不到三天,就被新人按在地上暴打。</li><li>19.看管门派重地的人都很喜欢私自放人进去。</li><li>20.名门大派的后山或者断崖往往藏着一些不得了的东西。</li><li>21.有些感觉敏锐的掌门可能会发现，自从某个弟子加入之后，什么寻仇的，叛变的，踢馆的就都找上门了，各种上古魔兽、神级高手排着队来搞事，传说中的丧门星可能真的是存在的。</li></ul><p>后续持续更新。。。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20210820/%E5%A5%97%E8%B7%AF%E7%9C%9F%E5%A4%9A/</id>
    <link href="https://minniexcode.github.io/memoirs/20210820/%E5%A5%97%E8%B7%AF%E7%9C%9F%E5%A4%9A/"/>
    <published>2021-08-19T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="小说的那些套路"><a href="#小说的那些套路" class="headerlink" title="小说的那些套路"></a>小说的那些套路</h2><ul>
<li>1.落魄大叔爱喝酒，以前肯定是高手。</li>
<li>2.高手厌倦俗世纷争，避世隐居。]]>
    </summary>
    <title>套路真多</title>
    <updated>2021-08-19T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="阅读与思考" scheme="https://minniexcode.github.io/memoirs/categories/%E9%98%85%E8%AF%BB%E4%B8%8E%E6%80%9D%E8%80%83/"/>
    <category term="Reading" scheme="https://minniexcode.github.io/memoirs/tags/Reading/"/>
    <category term="Book Notes" scheme="https://minniexcode.github.io/memoirs/tags/Book-Notes/"/>
    <content>
      <![CDATA[<p>1.人一到群体中，智商就严重降低，为了获得认同，个体愿意抛弃是非，用智商去换取那份让人备感安全的归属感。</p><span id="more"></span><p>2.我们始终有一种错觉，以为我们的感情源自于我们自己的内心。</p><p>3.群体只会干两种事——锦上添花或落井下石。</p><p>4.个人一旦成为群体的一员，他所作所为就不会再承担责任，这时每个人都会暴露出自己不受到的约束的一面。群体追求和相信的从来不是什么真相和理性，而是盲从、残忍、偏执和狂热，只知道简单而极端的感情。</p><p>5.我们以为自己是理性的，我们以为自己的一举一动都是有其道理的。但事实上，我们的绝大多数日常行为，都是一些我们自己根本无法了解的隐蔽动机的结果。</p><p>6.所谓的信仰，它能让一个人变得完全受自己的梦想奴役。</p><p>7.在与理性永恒的冲突中，感情从未失过手。</p><p>8.有时不真实的东西比真实的东西包含更多的真理。</p><p>9.群众没有真正渴求过真理，面对那些不合口味的证据，他们会充耳不闻…凡是能向他们提供幻觉的，都可以很容易地成为他们的主人；凡是让他们幻灭的，都会成为他们的牺牲品。</p><p>10.数量，即是正义。</p><p>11.掌握了影响群众想象力的艺术，也就掌握了统治他们的艺术。</p><p>12.没有传统，就没有文明；没有对传统的缓慢淘汰，就没有进步。</p><p>13.孤立的个人很清楚，在孤身一人时，他不能焚烧宫殿或洗劫商店，即使受到这样做的诱惑，他也很容易抵制这种诱惑。但是在成为群体的一员时，他就会意识到人数赋予他的力量，这足以让他生出杀人劫掠的念头，并且会立刻屈从于这种诱惑。出乎预料的障碍会被狂暴地摧毁。人类的机体的确能够产生大量狂热的激情，因此可以说，愿望受阻的群体所形成的正常状态，也就是这种激愤状态。</p><p>14.一个国家为其年轻人所提供的教育，可以让我们看到这个国家未来的样子。</p><p>15.能够感觉到的现象可以比作波浪，是海洋深处我们一无所知的那些乱象在洋面上的表象。</p><p>16.昨天受群众拥戴的英雄一旦失败，今天就会受到侮辱。当然名望越高，反应就会越强烈。在这种情况下，群众就会把末路英雄视为自己的同类，为自己曾向一个已不复存在的权威低头哈腰而进行报复。</p><p>17.群体在智力上总是低于孤立的个人，但是从感情及其激发的行动这个角度看，群体可以比个人表现得更好或更差，这全看环境如何。一切取决于群体所接受的暗示具有什么性质。</p><p>18.群体因为夸大自己的感情，因此它只会被极端感情所打动。希望感动群体的演说家，必须出言不逊，信誓旦旦。夸大其辞、言之凿凿、不断重复、绝对不以说理的方式证明任何事情——这些都是公众集会上的演说家惯用的论说技巧。</p><p>19.令人难忘的历史事件，只是人类思想无形的变化造成的有形的后果而已。</p><p>20.孤立的个体具有控制自身反应行为的能力，而群体则不具备。</p><p>21.专横和偏执是一切类型的群体的共性。</p><p>22.影响民众想象力的，并不是事实本身，而是它们发生和引起注意的方式。</p><p>23.群体总是对强权俯首帖耳，却很少为仁慈善行感动！在他们看来，仁慈善良只不过是软弱可欺的代名词。</p><p>24.大众没有辨别能力，因而无法判断事情的真伪，许多经不起推敲的观点，都能轻而易举的得到普遍赞同!</p><p>25.群体盲从意识会淹没个体的理性，个体一旦将自己归入该群体，其原本独立的理性就会被群体的无知疯狂所淹没。</p><p>26.从长远看，不断重复的说法会进入我们无意识的自我的深层区域，而我们的行为动机正是在这里形成的。到了一定的时候，我们会忘记谁是那个不断被重复的主张的作者，我们最终会对它深信不移。</p><p>27.群体中的个人是沙中之沙，风可以随意搅动他们。</p><p>28.群体表现出来的感情不管是好是坏，其突出的特点就是极为简单而夸张。</p><p>29.但凡能够成就大业的领袖人物，他重要的品质不是博学多识，而是必须具备强大而持久的意志力，这是一种极为罕见，极为强大的品质，它足以征服一切。</p><p>30.文明向来只由少数知识贵族阶级而非群体来创造。</p><p>31.身为一位领袖，如果想要让自己创立的宗教或政治信条站住脚，就必须成功地激起群众想入非非的感情。</p><p>32.结群后，由于人多势众，个人会产生一种幻觉，感到自己力大无穷，不可战胜，好像没有什么事情是办不到的。</p><p>33.当一个人融入社会之中时，他便失去了自我。</p><p>34.在迫不得已的情况下，我们也许还是会愿意接受传统教育当中所有的弊端，因为尽管它只会培养一些被社会所抛弃的人、心怀不满的人，但起码，对冗繁知识的肤浅掌握，对成堆教科书的完美背诵，或许可以提高智力水平。但事实上它真的能提高智力吗？不可能！在生活中，判断力、经验、进取心和个性，这些才是取得成功的条件，这些都不是书本所能够给予的。书本是可供查询的有用字典，但倘若把这些冗长的词条都装在脑子里，那可是一点用都没有。</p><p>35.文明赖以形成的道德力量失去效力时，它的最终瓦解总是由无意识且野蛮的群体来完成的。</p><p>36.一切政治、神学或社会信条，要想在群众中扎根，都必须采取宗教的形式——能够把危险的讨论排除在外的形式。</p><p>37.群体精神最需要的不是自由而是服从。他们如此甘愿听从别人的意志，以至于只要有人自称是它们的主人，他们就会本能地听命于他。</p><p>38.尽管在那一天的那两个小时里他们做到了或者几乎做到了，到一个月以后他们却不再具备这种能力。他们无法再次通过考试。他们脑中不断丢失大量过于沉重的知识，而且没有新的知识来填充。他们的思想活力开始衰退，促进成长的才能渐渐干涸，这时一个得到充分发展的人诞生了，但此时的他早已疲惫不堪。结婚过上安定的生活，陷入某种循环，并永无止境地重复下去；他将自己封闭在狭隘的工作中，尽职尽责，仅此而已。他们最终变为了平庸之辈。</p><p>39.个人一旦融入群体，他的个性便会被湮没，群体的思想便会占据绝对的统治地位，而与此同时，群体的行为也会表现出排斥异议，极端化、情绪化及低智商化等特点。进而对社会产生破坏性的影响。</p><p>40.所有时代和所有国家的伟大政客，包括最专横的暴君，也都把群众的想象力视为他们权力的基础，他们从来没有设想过通过与它作对而进行统治。要想掌握这种本领，万万不可求助于智力或推理，也就是说，绝对不可以采用论证的方式。</p><p>41.群体的叠加只是愚蠢的叠加，而真正的智慧被愚蠢的洪流淹没。</p><p>42.人们经常说起那家大众剧院，它只演令人压抑的戏剧，散场后，必须保护扮演叛徒的演员，免得他遭到观众的暴打。他所犯的罪行，当然是想象出来的，引起了群众的巨大愤怒。我觉得这是群体精神状态最显著的表现之一，这清楚地说明，要给他们什么暗示是一件多么容易的事情。对他们来说，假与真几乎同样奏效。他们明显地表现出真假不分的倾向。</p><p>43.各种制度并没有固定的优点，就它们本身而言，它们无所谓好坏。因为在特定的时刻对一个民族有益的制度，对另一个民族也许是极为有害的 。</p><p>44.群体也许永远是无意识的，但这种无意识本身，可能就是它力量强大的秘密之一。在自然界，绝对服从本能的生物，其行为会复杂得让我们不敢相信。理智是人类新近才有的东西，太不完美了，不能向我们揭示无意识的规律，更不能替代它。在我们的行为举止中，无意识部分占的比重很大，理智所占的比例却很小。无意识现在仍作为未知的力量在起作用。</p><p>45.到了一定的时候我们不会记得那个不断被重复的主张的人是谁，我们最终会对它深信不疑，广告能有令人吃惊的威力，这就是原因 。</p><p>46.教育既不会让人更道德，同样不会使他更幸福，它既不能将他的本能改变 ，也不能将他天生的热情改变，而且有在进行不良引导后，它的害处远大于好处。</p><p>47.单独一个人必须要为他的行为承担责任——法律上或者道德上。但是，群体则不然，群体不需要承担任何责任，群体就是责任，群体就是道德，群体就是法律，群体就是道德，群体的行为自然是合理的。</p><p>48.名望的产生与若干因素有关，而成功永远是其中最重要的一个。</p><p>49.学习课程，把一种语法或一篇纲要牢记在心，重复得好，模仿也出色——这实在是一种十分可笑的教育方式，它的每项工作都是一种信仰行为，即默认教师不可能犯错误。这种教育的唯一结果，就是贬低自我，让我们变得无能。</p><p>50.民众的想象力是政客的权力基础。</p><p>51.在决定人们历史地位上起着更大作用的，不是他们的“真实”面目，而是后人对他们的认识和感受。</p><p>52.群体会让每个人在其中的错误缩小，同时让每个人的恶意被无限放大。</p><p>54.束缚个人行为的责任感一消失，人便会随心所欲，肆意妄为。</p><p>55.群体的无意识行为取代了个体有意识的行为，这是现时代最显著的特征之一。</p><p>56.偶像崇拜的五大标识：</p><ul><li><p>第一，偶像总是凌驾于信徒，处于高高在上的地位，这一点有着决定性地作用。</p></li><li><p>第二，信徒总是盲目服从偶像的命令。</p></li><li><p>第三，信徒没有能力，也不愿意对偶像规定的信条进行讨论。</p></li><li><p>第四，信徒有着狂热的愿望，希望把偶像的信条广加传播。</p></li><li><p>第五，信徒倾向于把不接受它们的任何人视为仇敌。</p></li></ul><p>当群体符合第一条，其形式就等同于宗教，而这种情感就变成了宗教信仰。</p><p>对群体来说，也许最不合理的才是最合理的选择</p><p>57.群众日益被大众文化所湮没，这种文化把平庸低俗当作最有价值的东西。</p><p>58.所以不要轻易地成为集体的一份子，这样很容易被别有用心的人利用，即使你以为自己只不过是随声附和了一下而已，实际上你已经成了帮凶。</p><p>59.群体的夸张倾向只作用于感情，对智力不起任何作用。</p><p>60.群体不善推理，却又急于行动。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20210729/%E4%B9%8C%E5%90%88%E4%B9%8B%E4%BC%97/</id>
    <link href="https://minniexcode.github.io/memoirs/20210729/%E4%B9%8C%E5%90%88%E4%B9%8B%E4%BC%97/"/>
    <published>2021-07-28T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>1.人一到群体中，智商就严重降低，为了获得认同，个体愿意抛弃是非，用智商去换取那份让人备感安全的归属感。</p>]]>
    </summary>
    <title>乌合之众</title>
    <updated>2021-07-28T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="React" scheme="https://minniexcode.github.io/memoirs/tags/React/"/>
    <category term="JavaScript" scheme="https://minniexcode.github.io/memoirs/tags/JavaScript/"/>
    <category term="Fundamentals" scheme="https://minniexcode.github.io/memoirs/tags/Fundamentals/"/>
    <content>
      <![CDATA[<h2 id="ECMAScript-6-简介"><a href="#ECMAScript-6-简介" class="headerlink" title="ECMAScript 6 简介"></a>ECMAScript 6 简介</h2><p>除开<code>JavaScript</code>的基础内容，<code>React</code> 必备的知识肯定非 ES6，其实前面的文章已经讲了一部分的ES6的内容，这边只记录，我觉得比较重要的ES6的内容<br>ECMAScript 6.0（以下简称 ES6）是 <code>JavaScript</code> 语言的下一代标准，已经在 2015 年 6 月正式发布了。它的目标，是使得 <code>JavaScript</code> 语言可以用来编写复杂的大型应用程序，成为企业级开发语言。</p><span id="more"></span><h3 id="let-和-const-命令"><a href="#let-和-const-命令" class="headerlink" title="let 和 const 命令"></a>let 和 const 命令</h3><p><code>let</code> <code>var</code>这个很简单，不需要解释了，跟现在新的语言的用法一致，<code>const</code> 就是类似 <code>C</code> 里面修饰指针，则指针不变，修饰常量则常量不可变化，没什么可说的。<br><code>JavaScript</code> 以前的var就挺曹丹的，现在的用法起码像个人了。</p><h3 id="箭头函数"><a href="#箭头函数" class="headerlink" title="箭头函数"></a>箭头函数</h3><p>箭头函数有几个使用注意点。</p><ul><li><p>（1）箭头函数没有自己的this对象（详见下文）。</p></li><li><p>（2）不可以当作构造函数，也就是说，不可以对箭头函数使用new命令，否则会抛出一个错误。</p></li><li><p>（3）不可以使用arguments对象，该对象在函数体内不存在。如果要用，可以用 rest 参数代替。</p></li><li><p>（4）不可以使用yield命令，因此箭头函数不能用作 Generator 函数。</p></li></ul><p>上面四点中，最重要的是第一点。对于普通函数来说，内部的this指向函数运行时所在的对象，但是这一点对箭头函数不成立。它没有自己的this对象，内部的this就是定义时上层作用域中的this。<br>也就是说，箭头函数内部的this指向是固定的，相比之下，普通函数的this指向是可变的。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;id:&#x27;</span>, <span class="variable language_">this</span>.<span class="property">id</span>);</span><br><span class="line">  &#125;, <span class="number">100</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> id = <span class="number">21</span>;</span><br><span class="line"></span><br><span class="line">foo.<span class="title function_">call</span>(&#123; <span class="attr">id</span>: <span class="number">42</span> &#125;);</span><br><span class="line"><span class="comment">// id: 42</span></span><br></pre></td></tr></table></figure><p>上面代码中，setTimeout()的参数是一个箭头函数，这个箭头函数的定义生效是在foo函数生成时，而它的真正执行要等到 100 毫秒后。<br>如果是普通函数，执行时this应该指向全局对象window，这时应该输出21。但是，箭头函数导致this总是指向函数定义生效时所在的对象（本例是{id: 42}），所以打印出来的是42。</p><p>箭头函数的什么教程一大堆，在我看来都是为了之前的设计买单而已，如果真的是像写Java 一样写，this的指向不会出现任何问题，普通函数的this 是不确定的，那么使用普通函数的时候bind一个this就完全可以解决了。</p><p><code>JavaScript</code> 是一个很让人头疼的语言，有些人总是摸着规则的边缘写代码，然后减少了几行代码，以为有多厉害。我只想说，并不是人家看不懂就厉害的，那些白痴们。<br>如果只学习<code>JavaScript</code>一种语言，那他的代码肯定是乱七八糟的。所以我每年基本上都会复习一下C的知识，还是很有用的，了解程序运行的底层逻辑，才能更好地写代码。</p><h3 id="对象的扩展"><a href="#对象的扩展" class="headerlink" title="对象的扩展"></a>对象的扩展</h3><p>对象（object）是 JavaScript 最重要的数据结构。ES6 对它进行了重大升级，本章介绍数据结构本身的改变，下一章介绍<code>Object</code>对象的新增方法。</p><h4 id="属性的赋值器（setter）和取值器（getter）"><a href="#属性的赋值器（setter）和取值器（getter）" class="headerlink" title="属性的赋值器（setter）和取值器（getter）"></a>属性的赋值器（setter）和取值器（getter）</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cart = &#123;</span><br><span class="line">  <span class="attr">_wheels</span>: <span class="number">4</span>,</span><br><span class="line"></span><br><span class="line">  get <span class="title function_">wheels</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">_wheels</span>;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  set <span class="title function_">wheels</span> (value) &#123;</span><br><span class="line">    <span class="keyword">if</span> (value &lt; <span class="variable language_">this</span>.<span class="property">_wheels</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;数值太小了！&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">_wheels</span> = value;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="super-关键字"><a href="#super-关键字" class="headerlink" title="super 关键字"></a>super 关键字</h4><p>我们知道，this关键字总是指向函数所在的当前对象，ES6 又新增了另一个类似的关键字super，指向当前对象的原型对象。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> proto = &#123;</span><br><span class="line">  <span class="attr">foo</span>: <span class="string">&#x27;hello&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">foo</span>: <span class="string">&#x27;world&#x27;</span>,</span><br><span class="line">  <span class="title function_">find</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">super</span>.<span class="property">foo</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">setPrototypeOf</span>(obj, proto);</span><br><span class="line">obj.<span class="title function_">find</span>() <span class="comment">// &quot;hello&quot;</span></span><br></pre></td></tr></table></figure><p>上面代码中，对象obj.find()方法之中，通过super.foo引用了原型对象proto的foo属性。</p><p>注意，super关键字表示原型对象时，只能用在对象的方法之中，用在其他地方都会报错。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">foo</span>: <span class="variable language_">super</span>.<span class="property">foo</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">foo</span>: <span class="function">() =&gt;</span> <span class="variable language_">super</span>.<span class="property">foo</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">foo</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">super</span>.<span class="property">foo</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面三种<code>super</code>的用法都会报错，因为对于 <code>JavaScript</code> 引擎来说，这里的<code>super</code>都没有用在对象的方法之中。第一种写法是<code>super</code>用在属性里面，第二种和第三种写法是<code>super</code>用在一个函数里面，然后赋值给foo属性。目前，只有对象方法的简写法可以让 <code>JavaScript</code> 引擎确认，定义的是对象的方法。</p><p><code>JavaScript</code> 引擎内部，<code>super.foo</code>等同于<code>Object.getPrototypeOf(this).foo</code>（属性）或<code>Object.getPrototypeOf(this).foo.call(this)</code>（方法）。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> proto = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="string">&#x27;hello&#x27;</span>,</span><br><span class="line">  <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">x</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="string">&#x27;world&#x27;</span>,</span><br><span class="line">  <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>.<span class="title function_">foo</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">setPrototypeOf</span>(obj, proto);</span><br><span class="line"></span><br><span class="line">obj.<span class="title function_">foo</span>() <span class="comment">// &quot;world&quot;</span></span><br></pre></td></tr></table></figure><p>上面代码中，super.foo指向原型对象proto的foo方法，但是绑定的this却还是当前对象obj，因此输出的就是world。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20210619/ReactStack-1/</id>
    <link href="https://minniexcode.github.io/memoirs/20210619/ReactStack-1/"/>
    <published>2021-06-18T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="ECMAScript-6-简介"><a href="#ECMAScript-6-简介" class="headerlink" title="ECMAScript 6 简介"></a>ECMAScript 6 简介</h2><p>除开<code>JavaScript</code>的基础内容，<code>React</code> 必备的知识肯定非 ES6，其实前面的文章已经讲了一部分的ES6的内容，这边只记录，我觉得比较重要的ES6的内容<br>ECMAScript 6.0（以下简称 ES6）是 <code>JavaScript</code> 语言的下一代标准，已经在 2015 年 6 月正式发布了。它的目标，是使得 <code>JavaScript</code> 语言可以用来编写复杂的大型应用程序，成为企业级开发语言。</p>]]>
    </summary>
    <title>React 技术栈（一）</title>
    <updated>2026-03-16T16:27:30.436Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="JavaScript" scheme="https://minniexcode.github.io/memoirs/tags/JavaScript/"/>
    <category term="Fundamentals" scheme="https://minniexcode.github.io/memoirs/tags/Fundamentals/"/>
    <category term="Frontend" scheme="https://minniexcode.github.io/memoirs/tags/Frontend/"/>
    <content>
      <![CDATA[<h2 id="写在前面的话"><a href="#写在前面的话" class="headerlink" title="写在前面的话"></a>写在前面的话</h2><p><code>JavaScript</code> 语言的内容，前面基本上也记录的差不多了。这里就聊一些<code>JavaScript</code>语言更深入的问题，加深对这个语言的理解。<br><code>C</code> 和 <code>Java</code> 始终是 <code>JavaScript</code> 的基础，很多概念都是直接继承过来的，所以学习 <code>C</code> 是很重要的。我基本上每年都会对 <code>C</code> 有一个回顾，然后把数据结构的书再看一遍。<br>扯远了，这边只是记录 <code>JavaScript</code> 一些知识点，让我以后更好地上手 <code>JavaScript</code>，也是学习<code>React</code>的一个必要的过程。虽然我已经有一个上线的 <code>React</code> 项目，但是<code>React</code>的很多原理我基本上是抓瞎的。<br>作为一个移动端，在现在大前端的趋势下，多一个<code>React</code>的能力也挺好的。好几年前已经用<code>Vue</code>上线过一个项目了，但是那个项目比较简单，所以几年过去，我基本上忘的差不多了。</p><span id="more"></span><h2 id="继承与原型链"><a href="#继承与原型链" class="headerlink" title="继承与原型链"></a>继承与原型链</h2><p>对于使用过基于类的语言 (如 <code>Java</code> 或 <code>C++</code>) 的开发人员来说，<code>JavaScript</code> 有点令人困惑，因为它是动态的，并且本身不提供一个 class 实现。（在 ES2015&#x2F;ES6 中引入了 class 关键字，但那只是语法糖，<code>JavaScript</code> 仍然是基于原型的）。</p><p>当谈到继承时，<code>JavaScript</code> 只有一种结构：对象。每个实例对象（ object ）都有一个私有属性（称之为 <strong>proto</strong> ）指向它的构造函数的原型对象（<strong>prototype</strong>）。该原型对象也有一个自己的原型对象( <strong>proto</strong> ) ，层层向上直到一个对象的原型对象为 null。根据定义，null 没有原型，并作为这个原型链中的最后一个环节。</p><p>几乎所有 <code>JavaScript</code> 中的对象都是位于原型链顶端的 <code>Object</code> 的实例。</p><p>尽管这种原型继承通常被认为是 <code>JavaScript</code> 的弱点之一，但是原型继承模型本身实际上比经典模型更强大。例如，在原型模型的基础上构建经典模型相当简单。</p><h3 id="基于原型链的继承"><a href="#基于原型链的继承" class="headerlink" title="基于原型链的继承"></a>基于原型链的继承</h3><p><code>JavaScript</code> 对象是动态的属性“包”（指其自己的属性）。<code>JavaScript</code> 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时，它不仅仅在该对象上搜寻，还会搜寻该对象的原型，以及该对象的原型的原型，依次层层向上搜索，直到找到一个名字匹配的属性或到达原型链的末尾。</p><blockquote><p>遵循<strong>ECMAScript</strong>标准，someObject.[[<code>Prototype</code>]] 符号是用于指向 someObject 的原型。从 <strong>ECMAScript 6</strong> 开始，[[Prototype]] 可以通过 <code>Object.getPrototypeOf()</code> 和 <code>Object.setPrototypeOf()</code> 访问器来访问。这个等同于 <code>JavaScript</code> 的非标准但许多浏览器实现的属性 <strong>proto</strong>。</p><p>但它不应该与构造函数 <code>func</code> 的 <code>prototype</code> 属性相混淆。被构造函数创建的实例对象的 [[Prototype]] 指向 func 的 <code>prototype</code> 属性。<code>Object.prototype</code> 属性表示 <code>Object</code> 的原型对象。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 让我们从一个函数里创建一个对象o，它自身拥有属性a和b的：</span></span><br><span class="line"><span class="keyword">let</span> f = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">   <span class="variable language_">this</span>.<span class="property">a</span> = <span class="number">1</span>;</span><br><span class="line">   <span class="variable language_">this</span>.<span class="property">b</span> = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* 这么写也一样</span></span><br><span class="line"><span class="comment">function f() &#123;</span></span><br><span class="line"><span class="comment">  this.a = 1;</span></span><br><span class="line"><span class="comment">  this.b = 2;</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title function_">f</span>(); <span class="comment">// &#123;a: 1, b: 2&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在f函数的原型上定义属性</span></span><br><span class="line">f.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">b</span> = <span class="number">3</span>;</span><br><span class="line">f.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">c</span> = <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不要在 f 函数的原型上直接定义 f.prototype = &#123;b:3,c:4&#125;;这样会直接打破原型链</span></span><br><span class="line"><span class="comment">// o.[[Prototype]] 有属性 b 和 c</span></span><br><span class="line"><span class="comment">//  (其实就是 o.__proto__ 或者 o.constructor.prototype)</span></span><br><span class="line"><span class="comment">// o.[[Prototype]].[[Prototype]] 是 Object.prototype.</span></span><br><span class="line"><span class="comment">// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null</span></span><br><span class="line"><span class="comment">// 这就是原型链的末尾，即 null，</span></span><br><span class="line"><span class="comment">// 根据定义，null 就是没有 [[Prototype]]。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 综上，整个原型链如下:</span></span><br><span class="line"><span class="comment">// &#123;a:1, b:2&#125; ---&gt; &#123;b:3, c:4&#125; ---&gt; Object.prototype---&gt; null</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(o.<span class="property">a</span>); <span class="comment">// 1</span></span><br><span class="line"><span class="comment">// a是o的自身属性吗？是的，该属性的值为 1</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(o.<span class="property">b</span>); <span class="comment">// 2</span></span><br><span class="line"><span class="comment">// b是o的自身属性吗？是的，该属性的值为 2</span></span><br><span class="line"><span class="comment">// 原型上也有一个&#x27;b&#x27;属性，但是它不会被访问到。</span></span><br><span class="line"><span class="comment">// 这种情况被称为&quot;属性遮蔽 (property shadowing)&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(o.<span class="property">c</span>); <span class="comment">// 4</span></span><br><span class="line"><span class="comment">// c是o的自身属性吗？不是，那看看它的原型上有没有</span></span><br><span class="line"><span class="comment">// c是o.[[Prototype]]的属性吗？是的，该属性的值为 4</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(o.<span class="property">d</span>); <span class="comment">// undefined</span></span><br><span class="line"><span class="comment">// d 是 o 的自身属性吗？不是，那看看它的原型上有没有</span></span><br><span class="line"><span class="comment">// d 是 o.[[Prototype]] 的属性吗？不是，那看看它的原型上有没有</span></span><br><span class="line"><span class="comment">// o.[[Prototype]].[[Prototype]] 为 null，停止搜索</span></span><br><span class="line"><span class="comment">// 找不到 d 属性，返回 undefined</span></span><br></pre></td></tr></table></figure><h3 id="在-JavaScript-中使用原型"><a href="#在-JavaScript-中使用原型" class="headerlink" title="在 JavaScript 中使用原型"></a>在 JavaScript 中使用原型</h3><p>接下去，来仔细分析一下这些应用场景下， <code>JavaScript</code> 在背后做了哪些事情。<br>正如之前提到的，在 <code>JavaScript</code> 中，函数（function）是允许拥有属性的。所有的函数会有一个特别的属性 —— <code>prototype</code> 。请注意，以下的代码是独立的（出于严谨，假定页面没有其他的<code>JavaScript</code>代码）。为了最佳的学习体验，我们强烈建议阁下打开浏览器的控制台（在Chrome和火狐浏览器中，按Ctrl+Shift+I即可），进入“console”选项卡，然后把如下的<code>JavaScript</code>代码复制粘贴到窗口中，最后通过按下回车键运行代码。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">doSomething</span>(<span class="params"></span>)&#123;&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>( doSomething.<span class="property"><span class="keyword">prototype</span></span> );</span><br><span class="line"><span class="comment">// 和声明函数的方式无关，</span></span><br><span class="line"><span class="comment">// JavaScript 中的函数永远有一个默认原型属性。</span></span><br><span class="line"><span class="keyword">var</span> doSomething = <span class="keyword">function</span>(<span class="params"></span>)&#123;&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>( doSomething.<span class="property"><span class="keyword">prototype</span></span> );</span><br></pre></td></tr></table></figure><p>在控制台显示的JavaScript代码块中，我们可以看到doSomething函数的一个默认属性prototype。而这段代码运行之后，控制台应该显示类似如下的结果：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">constructor</span>: ƒ <span class="title function_">doSomething</span>(),</span><br><span class="line">    <span class="attr">__proto__</span>: &#123;</span><br><span class="line">        <span class="attr">constructor</span>: ƒ <span class="title class_">Object</span>(),</span><br><span class="line">        <span class="attr">hasOwnProperty</span>: ƒ <span class="title function_">hasOwnProperty</span>(),</span><br><span class="line">        <span class="attr">isPrototypeOf</span>: ƒ <span class="title function_">isPrototypeOf</span>(),</span><br><span class="line">        <span class="attr">propertyIsEnumerable</span>: ƒ <span class="title function_">propertyIsEnumerable</span>(),</span><br><span class="line">        <span class="attr">toLocaleString</span>: ƒ <span class="title function_">toLocaleString</span>(),</span><br><span class="line">        <span class="attr">toString</span>: ƒ <span class="title function_">toString</span>(),</span><br><span class="line">        <span class="attr">valueOf</span>: ƒ <span class="title function_">valueOf</span>()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 我们可以给doSomething函数的原型对象添加新属性</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">doSomething</span>(<span class="params"></span>)&#123;&#125;</span><br><span class="line">doSomething.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">foo</span> = <span class="string">&quot;bar&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>( doSomething.<span class="property"><span class="keyword">prototype</span></span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以看到运行后的结果</span></span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    <span class="attr">foo</span>: <span class="string">&quot;bar&quot;</span>,</span><br><span class="line">    <span class="attr">constructor</span>: ƒ <span class="title function_">doSomething</span>(),</span><br><span class="line">    <span class="attr">__proto__</span>: &#123;</span><br><span class="line">        <span class="attr">constructor</span>: ƒ <span class="title class_">Object</span>(),</span><br><span class="line">        <span class="attr">hasOwnProperty</span>: ƒ <span class="title function_">hasOwnProperty</span>(),</span><br><span class="line">        <span class="attr">isPrototypeOf</span>: ƒ <span class="title function_">isPrototypeOf</span>(),</span><br><span class="line">        <span class="attr">propertyIsEnumerable</span>: ƒ <span class="title function_">propertyIsEnumerable</span>(),</span><br><span class="line">        <span class="attr">toLocaleString</span>: ƒ <span class="title function_">toLocaleString</span>(),</span><br><span class="line">        <span class="attr">toString</span>: ƒ <span class="title function_">toString</span>(),</span><br><span class="line">        <span class="attr">valueOf</span>: ƒ <span class="title function_">valueOf</span>()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在我们可以通过<code>new</code>操作符来创建基于这个原型对象的<code>doSomething</code>实例。使用<code>new</code>操作符，只需在调用<code>doSomething</code>函数语句之前添加<code>new</code>。这样，便可以获得这个函数的一个实例对象。一些属性就可以添加到该原型对象中。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">doSomething</span>(<span class="params"></span>)&#123;&#125;</span><br><span class="line">doSomething.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">foo</span> = <span class="string">&quot;bar&quot;</span>; <span class="comment">// add a property onto the prototype</span></span><br><span class="line"><span class="keyword">var</span> doSomeInstancing = <span class="keyword">new</span> <span class="title function_">doSomething</span>();</span><br><span class="line">doSomeInstancing.<span class="property">prop</span> = <span class="string">&quot;some value&quot;</span>; <span class="comment">// add a property onto the object</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>( doSomeInstancing );</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    <span class="attr">prop</span>: <span class="string">&quot;some value&quot;</span>,</span><br><span class="line">    <span class="attr">__proto__</span>: &#123;</span><br><span class="line">        <span class="attr">foo</span>: <span class="string">&quot;bar&quot;</span>,</span><br><span class="line">        <span class="attr">constructor</span>: ƒ <span class="title function_">doSomething</span>(),</span><br><span class="line">        <span class="attr">__proto__</span>: &#123;</span><br><span class="line">            <span class="attr">constructor</span>: ƒ <span class="title class_">Object</span>(),</span><br><span class="line">            <span class="attr">hasOwnProperty</span>: ƒ <span class="title function_">hasOwnProperty</span>(),</span><br><span class="line">            <span class="attr">isPrototypeOf</span>: ƒ <span class="title function_">isPrototypeOf</span>(),</span><br><span class="line">            <span class="attr">propertyIsEnumerable</span>: ƒ <span class="title function_">propertyIsEnumerable</span>(),</span><br><span class="line">            <span class="attr">toLocaleString</span>: ƒ <span class="title function_">toLocaleString</span>(),</span><br><span class="line">            <span class="attr">toString</span>: ƒ <span class="title function_">toString</span>(),</span><br><span class="line">            <span class="attr">valueOf</span>: ƒ <span class="title function_">valueOf</span>()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上所示, <code>doSomeInstancing</code> 中的<code>__proto__</code>是 <code>doSomething.prototype</code>. 但这是做什么的呢？当你访问<code>doSomeInstancing</code> 中的一个属性，浏览器首先会查看<code>doSomeInstancing</code> 中是否存在这个属性。</p><p>如果 <code>doSomeInstancing</code> 不包含属性信息, 那么浏览器会在 <code>doSomeInstancing</code> 的 <code>__proto__</code> 中进行查找(同 <code>doSomething.prototype</code>). 如属性在 <code>doSomeInstancing</code> 的 <code>__proto__</code> 中查找到，则使用 <code>doSomeInstancing</code> 中 <code>__proto__</code> 的属性。</p><p>否则，如果 <code>doSomeInstancing</code> 中 <code>__proto__</code> 不具有该属性，则检查<code>doSomeInstancing</code> 的 <code>__proto__</code> 的  <code>__proto__</code> 是否具有该属性。默认情况下，任何函数的原型属性 <code>__proto__</code> 都是 <code>window.Object.prototype</code>. 因此, 通过<code>doSomeInstancing</code> 的 <code>__proto__</code> 的  <code>__proto__</code>  ( 同 <code>doSomething.prototype</code> 的 <code>__proto__</code> (同  <code>Object.prototype</code>)) 来查找要搜索的属性。</p><p>如果属性不存在 <code>doSomeInstancing</code> 的 <code>__proto__</code> 的  <code>__proto__</code> 中， 那么就会在 <code>doSomeInstancing</code> 的 <code>__proto__</code> 的  <code>__proto__</code> 的  <code>__proto__</code> 中查找。然而, 这里存在个问题：<code>doSomeInstancing</code> 的 <code>__proto__</code> 的  <code>__proto__</code> 的  <code>__proto__</code> 其实不存在。因此，只有这样，在 <code>__proto__</code> 的整个原型链被查看之后，这里没有更多的 <code>__proto__</code> ， 浏览器断言该属性不存在，并给出属性值为 <code>undefined</code> 的结论。</p><h3 id="使用不同的方法来创建对象和生成原型链"><a href="#使用不同的方法来创建对象和生成原型链" class="headerlink" title="使用不同的方法来创建对象和生成原型链"></a>使用不同的方法来创建对象和生成原型链</h3><h4 id="使用语法结构创建的对象"><a href="#使用语法结构创建的对象" class="headerlink" title="使用语法结构创建的对象"></a>使用语法结构创建的对象</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> o = &#123;<span class="attr">a</span>: <span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// o 这个对象继承了 Object.prototype 上面的所有属性</span></span><br><span class="line"><span class="comment">// o 自身没有名为 hasOwnProperty 的属性</span></span><br><span class="line"><span class="comment">// hasOwnProperty 是 Object.prototype 的属性</span></span><br><span class="line"><span class="comment">// 因此 o 继承了 Object.prototype 的 hasOwnProperty</span></span><br><span class="line"><span class="comment">// Object.prototype 的原型为 null</span></span><br><span class="line"><span class="comment">// 原型链如下:</span></span><br><span class="line"><span class="comment">// o ---&gt; Object.prototype ---&gt; null</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = [<span class="string">&quot;yo&quot;</span>, <span class="string">&quot;whadup&quot;</span>, <span class="string">&quot;?&quot;</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组都继承于 Array.prototype</span></span><br><span class="line"><span class="comment">// (Array.prototype 中包含 indexOf, forEach 等方法)</span></span><br><span class="line"><span class="comment">// 原型链如下:</span></span><br><span class="line"><span class="comment">// a ---&gt; Array.prototype ---&gt; Object.prototype ---&gt; null</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">2</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="使用构造器创建的对象"><a href="#使用构造器创建的对象" class="headerlink" title="使用构造器创建的对象"></a>使用构造器创建的对象</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Graph</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">vertices</span> = [];</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">edges</span> = [];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Graph</span>.<span class="property"><span class="keyword">prototype</span></span> = &#123;</span><br><span class="line">  <span class="attr">addVertex</span>: <span class="keyword">function</span>(<span class="params">v</span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">vertices</span>.<span class="title function_">push</span>(v);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> g = <span class="keyword">new</span> <span class="title class_">Graph</span>();</span><br><span class="line"><span class="comment">// g 是生成的对象，他的自身属性有 &#x27;vertices&#x27; 和 &#x27;edges&#x27;。</span></span><br><span class="line"><span class="comment">// 在 g 被实例化时，g.[[Prototype]] 指向了 Graph.prototype。</span></span><br></pre></td></tr></table></figure><h4 id="使用-Object-create-创建的对象"><a href="#使用-Object-create-创建的对象" class="headerlink" title="使用 Object.create 创建的对象"></a>使用 Object.create 创建的对象</h4><p>ECMAScript 5 中引入了一个新方法：<code>Object.create()</code>。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = &#123;<span class="attr">a</span>: <span class="number">1</span>&#125;;</span><br><span class="line"><span class="comment">// a ---&gt; Object.prototype ---&gt; null</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> b = <span class="title class_">Object</span>.<span class="title function_">create</span>(a);</span><br><span class="line"><span class="comment">// b ---&gt; a ---&gt; Object.prototype ---&gt; null</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b.<span class="property">a</span>); <span class="comment">// 1 (继承而来)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> c = <span class="title class_">Object</span>.<span class="title function_">create</span>(b);</span><br><span class="line"><span class="comment">// c ---&gt; b ---&gt; a ---&gt; Object.prototype ---&gt; null</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> d = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="comment">// d ---&gt; null</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(d.<span class="property">hasOwnProperty</span>); <span class="comment">// undefined, 因为d没有继承Object.prototype</span></span><br></pre></td></tr></table></figure><h4 id="使用-class-关键字创建的对象"><a href="#使用-class-关键字创建的对象" class="headerlink" title="使用 class 关键字创建的对象"></a>使用 class 关键字创建的对象</h4><p><strong>ECMAScript6</strong> 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉，但它们是不同的。<code>JavaScript</code> 仍然基于原型。这些新的关键字包括 <code>class</code>, <code>constructor</code>，<code>static</code>，<code>extends</code> 和 <code>super</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Polygon</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">height, width</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">height</span> = height;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">width</span> = width;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Square</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Polygon</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">sideLength</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(sideLength, sideLength);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">get</span> <span class="title function_">area</span>() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">height</span> * <span class="variable language_">this</span>.<span class="property">width</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">set</span> <span class="title function_">sideLength</span>(<span class="params">newLength</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">height</span> = newLength;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">width</span> = newLength;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> square = <span class="keyword">new</span> <span class="title class_">Square</span>(<span class="number">2</span>);</span><br></pre></td></tr></table></figure><h2 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h2><p>像C语言这样的底层语言一般都有底层的内存管理接口，比如 <code>malloc()</code>和<code>free()</code>。相反，<code>JavaScript</code>是在创建变量（对象，字符串等）时自动进行了分配内存，并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。这个“自动”是混乱的根源，并让<code>JavaScript</code>（和其他高级语言）开发者错误的感觉他们可以不关心内存管理。</p><h3 id="内存生命周期"><a href="#内存生命周期" class="headerlink" title="内存生命周期"></a>内存生命周期</h3><p>不管什么程序语言，内存生命周期基本是一致的：</p><ul><li>分配你所需要的内存</li><li>使用分配到的内存（读、写）</li><li>不需要时将其释放\归还</li></ul><p>所有语言第二部分都是明确的。第一和第三部分在底层语言中是明确的，但在像<code>JavaScript</code>这些高级语言中，大部分都是隐含的。</p><h4 id="JavaScript-的内存分配"><a href="#JavaScript-的内存分配" class="headerlink" title="JavaScript 的内存分配"></a>JavaScript 的内存分配</h4><p>为了不让程序员费心分配内存，<code>JavaScript</code> 在定义变量时就完成了内存分配。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> n = <span class="number">123</span>; <span class="comment">// 给数值变量分配内存</span></span><br><span class="line"><span class="keyword">var</span> s = <span class="string">&quot;azerty&quot;</span>; <span class="comment">// 给字符串分配内存</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> o = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="literal">null</span></span><br><span class="line">&#125;; <span class="comment">// 给对象及其包含的值分配内存</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 给数组及其包含的值分配内存（就像对象一样）</span></span><br><span class="line"><span class="keyword">var</span> a = [<span class="number">1</span>, <span class="literal">null</span>, <span class="string">&quot;abra&quot;</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">a</span>)&#123;</span><br><span class="line">  <span class="keyword">return</span> a + <span class="number">2</span>;</span><br><span class="line">&#125; <span class="comment">// 给函数（可调用的对象）分配内存</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数表达式也能分配一个对象</span></span><br><span class="line">someElement.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  someElement.<span class="property">style</span>.<span class="property">backgroundColor</span> = <span class="string">&#x27;blue&#x27;</span>;</span><br><span class="line">&#125;, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure><p>通过函数调用分配内存</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> d = <span class="keyword">new</span> <span class="title class_">Date</span>(); <span class="comment">// 分配一个 Date 对象</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> e = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>); <span class="comment">// 分配一个 DOM 元素</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 有些方法分配新变量或者新对象：</span></span><br><span class="line"><span class="keyword">var</span> s = <span class="string">&quot;azerty&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> s2 = s.<span class="title function_">substr</span>(<span class="number">0</span>, <span class="number">3</span>); <span class="comment">// s2 是一个新的字符串</span></span><br><span class="line"><span class="comment">// 因为字符串是不变量，</span></span><br><span class="line"><span class="comment">// JavaScript 可能决定不分配内存，</span></span><br><span class="line"><span class="comment">// 只是存储了 [0-3] 的范围。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = [<span class="string">&quot;ouais ouais&quot;</span>, <span class="string">&quot;nan nan&quot;</span>];</span><br><span class="line"><span class="keyword">var</span> a2 = [<span class="string">&quot;generation&quot;</span>, <span class="string">&quot;nan nan&quot;</span>];</span><br><span class="line"><span class="keyword">var</span> a3 = a.<span class="title function_">concat</span>(a2);</span><br><span class="line"><span class="comment">// 新数组有四个元素，是 a 连接 a2 的结果</span></span><br></pre></td></tr></table></figure><p>使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值，甚至传递函数的参数。</p><h4 id="当内存不再需要使用时释放"><a href="#当内存不再需要使用时释放" class="headerlink" title="当内存不再需要使用时释放"></a>当内存不再需要使用时释放</h4><p>大多数内存管理的问题都在这个阶段。在这里最艰难的任务是找到“哪些被分配的内存确实已经不再需要了”。它往往要求开发人员来确定在程序中哪一块内存不再需要并且释放它。</p><p>高级语言解释器嵌入了“垃圾回收器”，它的主要工作是跟踪内存的分配和使用，以便当分配的内存不再使用时，自动释放它。这只能是一个近似的过程，因为要知道是否仍然需要某块内存是无法判定的（无法通过某种算法解决）。</p><h3 id="垃圾回收"><a href="#垃圾回收" class="headerlink" title="垃圾回收"></a>垃圾回收</h3><p>如上文所述自动寻找是否一些内存“不再需要”的问题是无法判定的。因此，垃圾回收实现只能有限制的解决一般问题。</p><p>垃圾回收算法主要依赖于引用的概念。在内存管理的环境中，一个对象如果有访问另一个对象的权限（隐式或者显式），叫做一个对象引用另一个对象。例如，一个<code>Javascript</code>对象具有对它原型的引用（隐式引用）和对它属性的引用（显式引用）。</p><p>在这里，“对象”的概念不仅特指 <code>JavaScript</code> 对象，还包括函数作用域（或者全局词法作用域）。</p><p>这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象（零引用），对象将被垃圾回收机制回收。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> o = &#123;</span><br><span class="line">  <span class="attr">a</span>: &#123;</span><br><span class="line">    <span class="attr">b</span>:<span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// 两个对象被创建，一个作为另一个的属性被引用，另一个被分配给变量o</span></span><br><span class="line"><span class="comment">// 很显然，没有一个可以被垃圾收集</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> o2 = o; <span class="comment">// o2变量是第二个对“这个对象”的引用</span></span><br><span class="line"></span><br><span class="line">o = <span class="number">1</span>;      <span class="comment">// 现在，“这个对象”只有一个o2变量的引用了，“这个对象”的原始引用o已经没有</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> oa = o2.<span class="property">a</span>; <span class="comment">// 引用“这个对象”的a属性</span></span><br><span class="line"><span class="comment">// 现在，“这个对象”有两个引用了，一个是o2，一个是oa</span></span><br><span class="line"></span><br><span class="line">o2 = <span class="string">&quot;yo&quot;</span>; <span class="comment">// 虽然最初的对象现在已经是零引用了，可以被垃圾回收了</span></span><br><span class="line"><span class="comment">// 但是它的属性a的对象还在被oa引用，所以还不能回收</span></span><br><span class="line"></span><br><span class="line">oa = <span class="literal">null</span>; <span class="comment">// a属性的那个对象现在也是零引用了</span></span><br><span class="line"><span class="comment">// 它可以被垃圾回收了</span></span><br></pre></td></tr></table></figure><p>该算法有个限制：无法处理循环引用的事例。在下面的例子中，两个对象被创建，并互相引用，形成了一个循环。它们被调用之后会离开函数作用域，所以它们已经没有用了，可以被回收了。然而，引用计数算法考虑到它们互相都有至少一次引用，所以它们不会被回收。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="keyword">var</span> o = &#123;&#125;;</span><br><span class="line">  <span class="keyword">var</span> o2 = &#123;&#125;;</span><br><span class="line">  o.<span class="property">a</span> = o2; <span class="comment">// o 引用 o2</span></span><br><span class="line">  o2.<span class="property">a</span> = o; <span class="comment">// o2 引用 o</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;azerty&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>();</span><br></pre></td></tr></table></figure><p>这里的内存管理讲的很一般，如果要详细的了解，还是要去看 <code>C</code> 的指针部分的内容。不过 <code>C</code> 的指针内容很复杂，需要慢慢斟酌，慢慢理解</p><h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><p>函数算是 <code>js</code> 里面花样最多的了，其他语言也有闭包，函数式编程，但是花样这么多，用法这么乱的挺少的。起码<code>Swift</code>的 函数真的很好用，然后对于引用对象的 拷贝也是正常的 <code>C</code> 的逻辑，<code>js</code>的我现在很难理解，也看不到底层的内存分布是怎么个逻辑。</p><p>还是先看看 函数的 教程吧</p><h3 id="箭头函数"><a href="#箭头函数" class="headerlink" title="箭头函数"></a>箭头函数</h3><p>箭头函数表达式的语法比函数表达式更简洁，并且没有自己的<code>this</code>，<code>arguments</code>，<code>super</code>或<code>new.target</code>。箭头函数表达式更适用于那些本来需要匿名函数的地方，并且它不能用作构造函数。</p><h4 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h4><p>基础语法</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">(param1, param2, …, paramN) =&gt; &#123; statements &#125;</span><br><span class="line">(param1, param2, …, paramN) =&gt; expression</span><br><span class="line"><span class="comment">//相当于：(param1, param2, …, paramN) =&gt;&#123; return expression; &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 当只有一个参数时，圆括号是可选的：</span></span><br><span class="line">(singleParam) =&gt; &#123; statements &#125;</span><br><span class="line">singleParam =&gt; &#123; statements &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 没有参数的函数应该写成一对圆括号。</span></span><br><span class="line">() =&gt; &#123; statements &#125;</span><br></pre></td></tr></table></figure><p>高级语法</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//加括号的函数体返回对象字面量表达式：</span></span><br><span class="line">params =&gt; (&#123;<span class="attr">foo</span>: bar&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">//支持剩余参数和默认参数</span></span><br><span class="line">(param1, param2, ...rest) =&gt; &#123; statements &#125;</span><br><span class="line">(param1 = defaultValue1, param2, …, paramN = defaultValueN) =&gt; &#123;</span><br><span class="line">  statements</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//同样支持参数列表解构</span></span><br><span class="line"><span class="keyword">let</span> <span class="title function_">f</span> = (<span class="params">[a, b] = [<span class="number">1</span>, <span class="number">2</span>], &#123;x: c&#125; = &#123;x: a + b&#125;</span>) =&gt; a + b + c;</span><br><span class="line"><span class="title function_">f</span>();  <span class="comment">// 6</span></span><br></pre></td></tr></table></figure><h4 id="描述"><a href="#描述" class="headerlink" title="描述"></a>描述</h4><p>引入箭头函数有两个方面的作用：更简短的函数并且不绑定this。</p><p>更短的函数</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> elements = [</span><br><span class="line">  <span class="string">&#x27;Hydrogen&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;Helium&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;Lithium&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;Beryllium&#x27;</span></span><br><span class="line">];</span><br><span class="line"></span><br><span class="line">elements.<span class="title function_">map</span>(<span class="keyword">function</span>(<span class="params">element</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> element.<span class="property">length</span>;</span><br><span class="line">&#125;); <span class="comment">// 返回数组：[8, 6, 7, 9]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 上面的普通函数可以改写成如下的箭头函数</span></span><br><span class="line">elements.<span class="title function_">map</span>(<span class="function">(<span class="params">element</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> element.<span class="property">length</span>;</span><br><span class="line">&#125;); <span class="comment">// [8, 6, 7, 9]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 当箭头函数只有一个参数时，可以省略参数的圆括号</span></span><br><span class="line">elements.<span class="title function_">map</span>(<span class="function"><span class="params">element</span> =&gt;</span> &#123;</span><br><span class="line"> <span class="keyword">return</span> element.<span class="property">length</span>;</span><br><span class="line">&#125;); <span class="comment">// [8, 6, 7, 9]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 当箭头函数的函数体只有一个 `return` 语句时，可以省略 `return` 关键字和方法体的花括号</span></span><br><span class="line">elements.<span class="title function_">map</span>(<span class="function"><span class="params">element</span> =&gt;</span> element.<span class="property">length</span>); <span class="comment">// [8, 6, 7, 9]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在这个例子中，因为我们只需要 `length` 属性，所以可以使用参数解构</span></span><br><span class="line"><span class="comment">// 需要注意的是字符串 `&quot;length&quot;` 是我们想要获得的属性的名称，而 `lengthFooBArX` 则只是个变量名，</span></span><br><span class="line"><span class="comment">// 可以替换成任意合法的变量名</span></span><br><span class="line">elements.<span class="title function_">map</span>(<span class="function">(<span class="params">&#123; <span class="string">&quot;length&quot;</span>: lengthFooBArX &#125;</span>) =&gt;</span> lengthFooBArX); <span class="comment">// [8, 6, 7, 9]</span></span><br></pre></td></tr></table></figure><p>没有单独的<code>this</code></p><p>在箭头函数出现之前，每一个新函数根据它是被如何调用的来定义这个函数的<code>this</code>值：</p><ul><li>如果是该函数是一个构造函数，this指针指向一个新的对象</li><li>在严格模式下的函数调用下，this指向undefined</li><li>如果是该函数是一个对象的方法，则它的this指针指向这个对象</li><li>等等</li></ul><p><code>This</code>被证明是令人厌烦的面向对象风格的编程。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// Person() 构造函数定义 `this`作为它自己的实例.</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">setInterval</span>(<span class="keyword">function</span> <span class="title function_">growUp</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 在非严格模式, growUp()函数定义 `this`作为全局对象,</span></span><br><span class="line">    <span class="comment">// 与在 Person()构造函数中定义的 `this`并不相同.</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span>++;</span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在ECMAScript 3/5中，通过将this值分配给封闭的变量，可以解决this问题。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> that = <span class="variable language_">this</span>;</span><br><span class="line">  that.<span class="property">age</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">setInterval</span>(<span class="keyword">function</span> <span class="title function_">growUp</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 回调引用的是`that`变量, 其值是预期的对象.</span></span><br><span class="line">    that.<span class="property">age</span>++;</span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>箭头函数不会创建自己的<code>this</code>,它只会从自己的作用域链的上一层继承<code>this</code>。因此，在下面的代码中，传递给<code>setInterval</code>的函数内的<code>this</code>与封闭函数中的<code>this</code>值相同.<br>严格模式的其他规则依然不变.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span>++; <span class="comment">// |this| 正确地指向 p 实例</span></span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br></pre></td></tr></table></figure><p>通过 <code>call</code> 或 <code>apply</code> 调用<br>由于 箭头函数没有自己的<code>this</code>指针，通过 <code>call()</code> 或 <code>apply()</code> 方法调用一个函数时，只能传递参数（不能绑定this—译者注），他们的第一个参数会被忽略。（这种现象对于bind方法同样成立—译者注）</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> adder = &#123;</span><br><span class="line">  base : <span class="number">1</span>,</span><br><span class="line">  </span><br><span class="line">  add : <span class="keyword">function</span>(<span class="params">a</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> <span class="title function_">f</span> = v =&gt; v + <span class="variable language_">this</span>.<span class="property">base</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">f</span>(a);</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">addThruCall</span>: <span class="keyword">function</span>(<span class="params">a</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> <span class="title function_">f</span> = v =&gt; v + <span class="variable language_">this</span>.<span class="property">base</span>;</span><br><span class="line">    <span class="keyword">var</span> b = &#123;</span><br><span class="line">      base : <span class="number">2</span></span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> f.<span class="title function_">call</span>(b, a);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(adder.<span class="title function_">add</span>(<span class="number">1</span>));         <span class="comment">// 输出 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(adder.<span class="title function_">addThruCall</span>(<span class="number">1</span>)); <span class="comment">// 仍然输出 2</span></span><br></pre></td></tr></table></figure><p>箭头函数不绑定<code>Arguments</code> 对象。因此，在本示例中，<code>arguments</code>只是引用了封闭作用域内的<code>arguments</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="variable language_">arguments</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> <span class="title function_">arr</span> = (<span class="params"></span>) =&gt; <span class="variable language_">arguments</span>[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"><span class="title function_">arr</span>(); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params">n</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; <span class="variable language_">arguments</span>[<span class="number">0</span>] + n; <span class="comment">// 隐式绑定 foo 函数的 arguments 对象. arguments[0] 是 n,即传给foo函数的第一个参数</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">f</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">foo</span>(<span class="number">1</span>); <span class="comment">// 2</span></span><br><span class="line"><span class="title function_">foo</span>(<span class="number">2</span>); <span class="comment">// 4</span></span><br><span class="line"><span class="title function_">foo</span>(<span class="number">3</span>); <span class="comment">// 6</span></span><br><span class="line"><span class="title function_">foo</span>(<span class="number">3</span>,<span class="number">2</span>);<span class="comment">//6</span></span><br><span class="line"><span class="title function_">foo</span>(<span class="number">4</span>);<span class="comment">//8</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在大多数情况下，使用剩余参数是相较使用arguments对象的更好选择。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params">arg</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params">...args</span>) =&gt; args[<span class="number">0</span>];</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">f</span>(arg);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">foo</span>(<span class="number">1</span>); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params">arg1,arg2</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params">...args</span>) =&gt; args[<span class="number">1</span>];</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">f</span>(arg1,arg2);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">foo</span>(<span class="number">1</span>,<span class="number">2</span>);  <span class="comment">//2</span></span><br></pre></td></tr></table></figure><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>就到这里吧，js的内容看得我头疼，又很困。</p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript">JavaScript</a></p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20201102/learning/JavaScript-study-record-2/</id>
    <link href="https://minniexcode.github.io/memoirs/20201102/learning/JavaScript-study-record-2/"/>
    <published>2020-11-01T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在前面的话"><a href="#写在前面的话" class="headerlink" title="写在前面的话"></a>写在前面的话</h2><p><code>JavaScript</code> 语言的内容，前面基本上也记录的差不多了。这里就聊一些<code>JavaScript</code>语言更深入的问题，加深对这个语言的理解。<br><code>C</code> 和 <code>Java</code> 始终是 <code>JavaScript</code> 的基础，很多概念都是直接继承过来的，所以学习 <code>C</code> 是很重要的。我基本上每年都会对 <code>C</code> 有一个回顾，然后把数据结构的书再看一遍。<br>扯远了，这边只是记录 <code>JavaScript</code> 一些知识点，让我以后更好地上手 <code>JavaScript</code>，也是学习<code>React</code>的一个必要的过程。虽然我已经有一个上线的 <code>React</code> 项目，但是<code>React</code>的很多原理我基本上是抓瞎的。<br>作为一个移动端，在现在大前端的趋势下，多一个<code>React</code>的能力也挺好的。好几年前已经用<code>Vue</code>上线过一个项目了，但是那个项目比较简单，所以几年过去，我基本上忘的差不多了。</p>]]>
    </summary>
    <title>JavaScript 温习记录（二）</title>
    <updated>2026-03-16T16:27:30.440Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="JavaScript" scheme="https://minniexcode.github.io/memoirs/tags/JavaScript/"/>
    <category term="Fundamentals" scheme="https://minniexcode.github.io/memoirs/tags/Fundamentals/"/>
    <category term="Frontend" scheme="https://minniexcode.github.io/memoirs/tags/Frontend/"/>
    <content>
      <![CDATA[<h2 id="写在前面的话"><a href="#写在前面的话" class="headerlink" title="写在前面的话"></a>写在前面的话</h2><p>最近用react-native 完成了一个公司的项目，JavaScript 其实已经看过很多遍了，上个月就看了一遍全部的教程，但是没有系统的记录，以及一些比较麻烦的地方。<br>有一些三方的源码还是看的不太懂，而且js的坑不算少，我希望能够记录一下，下次能够快速定位问题。<br>（起码我可以看得懂三方的源代码o(╯□╰)o）</p><span id="more"></span><h2 id="让我们愉快的开始吧"><a href="#让我们愉快的开始吧" class="headerlink" title="让我们愉快的开始吧"></a>让我们愉快的开始吧</h2><p>JavaScript ( JS ) 是一种具有函数优先的轻量级，解释型或即时编译型的编程语言。虽然它是作为开发Web 页面的脚本语言而出名的，但是它也被用到了很多非浏览器环境中，例如 Node.js、 Apache CouchDB 和 Adobe Acrobat。JavaScript 是一种基于原型编程、多范式的动态脚本语言，并且支持面向对象、命令式和声明式（如函数式编程）风格。了解更多 JavaScript。</p><p>一些基础的知识这里就不说了，主要是一些值得注意的。</p><h3 id="Promises"><a href="#Promises" class="headerlink" title="Promises"></a>Promises</h3><p>从 ECMAScript 6 开始，JavaScript 增加了对 Promise 对象的支持，它允许你对延时和异步操作流进行控制。<br>Promise 对象有以下几种状态：</p><ul><li>pending：初始的状态，即正在执行，不处于 fulfilled 或 rejected 状态。</li><li>fulfilled：成功的完成了操作。</li><li>rejected：失败，没有完成操作。</li><li>settled：Promise 处于 fulfilled 或 rejected 二者中的任意一个状态, 不会是 pending。</li></ul><p>通过 XHR 加载图片<br>你可以在 MDN GitHub promise-test 中找到这个简单的例子，它使用了 Promise 和 XMLHttpRequest 来加载一张图片，你也可以直接在这个页面查看他的效果。同时为了让你更清楚的了解 Promise 和 XHR 的结构，代码中每一个步骤后都附带了注释。</p><p>这里有一个未注释的版本，展现了 Promise 的工作流，希望可以对你的理解有所帮助。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">imgLoad</span>(<span class="params">url</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> request = <span class="keyword">new</span> <span class="title class_">XMLHttpRequest</span>();</span><br><span class="line">    request.<span class="title function_">open</span>(<span class="string">&#x27;GET&#x27;</span>, url);</span><br><span class="line">    request.<span class="property">responseType</span> = <span class="string">&#x27;blob&#x27;</span>;</span><br><span class="line">    request.<span class="property">onload</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (request.<span class="property">status</span> === <span class="number">200</span>) &#123;</span><br><span class="line">        <span class="title function_">resolve</span>(request.<span class="property">response</span>);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">reject</span>(<span class="title class_">Error</span>(<span class="string">&#x27;Image didn\&#x27;t load successfully; error code:&#x27;</span> </span><br><span class="line">                     + request.<span class="property">statusText</span>));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    request.<span class="property">onerror</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="title function_">reject</span>(<span class="title class_">Error</span>(<span class="string">&#x27;There was a network error.&#x27;</span>));</span><br><span class="line">    &#125;;</span><br><span class="line">    request.<span class="title function_">send</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Promise</strong> 这里只是简单介绍，后面会用单独的部分来讲解的</p><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><p>函数是 JavaScript 中的基本组件之一。 一个函数是 JavaScript 过程 — 一组执行任务或计算值的语句。要使用一个函数，你必须将其定义在你希望调用它的作用域内。</p><p>一个JavaScript 函数用function关键字定义，后面跟着函数名和圆括号。</p><p>函数很重要，函数作用域很重要，最关键的是js的作用域有很多奇葩的地方</p><p>在函数内定义的变量不能在函数之外的任何地方访问，因为变量仅仅在该函数的域的内部有定义。相对应的，一个函数可以访问定义在其范围内的任何变量和函数。换言之，定义在全局域中的函数可以访问所有定义在全局域中的变量。在另一个函数中定义的函数也可以访问在其父函数中定义的所有变量和父函数有权访问的任何其他变量。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下面的变量定义在全局作用域(global scope)中</span></span><br><span class="line"><span class="keyword">var</span> num1 = <span class="number">20</span>,</span><br><span class="line">    num2 = <span class="number">3</span>,</span><br><span class="line">    name = <span class="string">&quot;Chamahk&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 本函数定义在全局作用域</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">multiply</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> num1 * num3;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">change</span>(<span class="params"></span>) &#123;</span><br><span class="line">    num1 = <span class="number">40</span>;</span><br><span class="line">    num2 = <span class="number">3</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">multiplyWithNumber</span>(<span class="params">num1: number, num2: number</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> num = num1 * num2;</span><br><span class="line">    <span class="keyword">return</span> num;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">multiply</span>(); <span class="comment">// 返回 60</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">multiplyWithNumber</span>(num1, num2)</span><br><span class="line"></span><br><span class="line"><span class="title function_">change</span>();</span><br><span class="line"></span><br><span class="line"><span class="title function_">multiply</span>(); <span class="comment">// 返回 ??? 120</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> c = &#123;</span><br><span class="line">  <span class="attr">num</span>: <span class="number">10</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> d = &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = &#123;</span><br><span class="line">  <span class="title function_">init</span>(<span class="params">num, obj</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">num</span> = num</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">obj</span> = obj</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">a.<span class="title function_">init</span>(c.<span class="property">num</span>, d)</span><br><span class="line">c.<span class="property">num</span> = <span class="number">20</span></span><br><span class="line">d = &#123; <span class="attr">aaaa</span>: <span class="number">1</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> b = &#123;</span><br><span class="line">  <span class="attr">num</span>: a.<span class="property">num</span>,</span><br><span class="line"></span><br><span class="line">  <span class="attr">change</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    num = <span class="number">20</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">b.<span class="title function_">change</span>()</span><br><span class="line"></span><br><span class="line">a.<span class="property">num</span></span><br><span class="line">b.<span class="property">num</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="嵌套函数和闭包"><a href="#嵌套函数和闭包" class="headerlink" title="嵌套函数和闭包"></a>嵌套函数和闭包</h4><p>你可以在一个函数里面嵌套另外一个函数。嵌套（内部）函数对其容器（外部）函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的表达式（通常是函数）。</p><p>既然嵌套函数是一个闭包，就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说，内部函数包含外部函数的作用域。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">outside</span>(<span class="params">x</span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">inside</span>(<span class="params">y</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> inside;</span><br><span class="line">&#125;</span><br><span class="line">fn_inside = <span class="title function_">outside</span>(<span class="number">3</span>); <span class="comment">// 可以这样想：给一个函数，使它的值加3</span></span><br><span class="line">result = <span class="title function_">fn_inside</span>(<span class="number">5</span>); <span class="comment">// returns 8</span></span><br><span class="line"></span><br><span class="line">result1 = <span class="title function_">outside</span>(<span class="number">3</span>)(<span class="number">5</span>); <span class="comment">// returns 8</span></span><br></pre></td></tr></table></figure><p>闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套，并且内部函数可以访问定义在外部函数中的所有变量和函数，以及外部函数能访问的所有变量和函数。<br>但是，外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。</p><p>此外，由于内部函数可以访问外部函数的作用域，因此当内部函数生存周期大于外部函数时，外部函数中定义的变量和函数的生存周期将比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时，一个闭包就产生了。</p><h4 id="函数参数"><a href="#函数参数" class="headerlink" title="函数参数"></a>函数参数</h4><p>从ECMAScript 6开始，有两个新的类型的参数：默认参数，剩余参数。<br>在JavaScript中，函数参数的默认值是undefined。然而，在某些情况下设置不同的默认值是有用的。这时默认参数可以提供帮助。</p><p>剩余参数语法允许将不确定数量的参数表示为数组。在下面的例子中，使用剩余参数收集从第二个到最后参数。然后，我们将这个数组的每一个数与第一个参数相乘。这个例子是使用了一个箭头函数，这将在下面介绍。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">multiply</span>(<span class="params">multiplier, ...theArgs</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> theArgs.<span class="title function_">map</span>(<span class="function"><span class="params">x</span> =&gt;</span> multiplier * x);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr = <span class="title function_">multiply</span>(<span class="number">2</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [2, 4, 6]</span></span><br></pre></td></tr></table></figure><h4 id="箭头函数"><a href="#箭头函数" class="headerlink" title="箭头函数"></a>箭头函数</h4><p>箭头函数表达式（也称胖箭头函数）相比函数表达式具有较短的语法并以词法的方式绑定 this。箭头函数总是匿名的。另见 hacks.mozilla.org 的博文：<a href="https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/">“深度了解ES6：箭头函数”</a>。</p><p>有两个因素会影响引入箭头函数：更简洁的函数和 this。</p><h3 id="数字和日期"><a href="#数字和日期" class="headerlink" title="数字和日期"></a>数字和日期</h3><h4 id="数字"><a href="#数字" class="headerlink" title="数字"></a>数字</h4><p>在 JavaScript 里面，数字均为双精度浮点类型（double-precision 64-bit binary format IEEE 754），即一个介于±2−1023和±2+1024之间的数字，或约为±10−308到±10+308，数字精度为53位。整数数值仅在±(253 - 1)的范围内可以表示准确。</p><p>JavaScript最近添加了 BigInt 的支持，能够用于表示极大的数字。使用 BigInt 的时候有一些注意事项，例如，你不能让 BigInt 和 Number 直接进行运算，你也不能用 Math 对象去操作 BigInt 数字。</p><p>请参见Javascript指南中的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures">JavaScript 数据类型和数据结构</a> ，了解其他更多的基本类型。</p><h4 id="日期对象"><a href="#日期对象" class="headerlink" title="日期对象"></a>日期对象</h4><p>JavaScript没有日期数据类型。但是你可以在你的程序里使用 Date 对象和其方法来处理日期和时间。Date对象有大量的设置、获取和操作日期的方法。 它并不含有任何属性。<br>JavaScript 处理日期数据类似于Java。这两种语言有许多一样的处理日期的方法，也都是以1970年1月1日00:00:00以来的毫秒数来储存数据类型的。</p><p>处理日期时间的Date对象方法可分为以下几类：</p><ul><li>“set” 方法, 用于设置Date对象的日期和时间的值。</li><li>“get” 方法,用于获取Date对象的日期和时间的值。</li><li>“to” 方法,用于返回Date对象的字符串格式的值。</li><li>parse 和UTC 方法, 用于解析Date字符串。</li></ul><h3 id="正则表达式"><a href="#正则表达式" class="headerlink" title="正则表达式"></a>正则表达式</h3><p>正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中，正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法, 以及 String 的 match、matchAll、replace、search 和 split 方法。本章介绍 JavaScript 正则表达式。</p><table><thead><tr><th align="center">字符</th><th align="left">含义</th></tr></thead><tbody><tr><td align="center">\</td><td align="left">依照下列规则匹配：</br>在非特殊字符之前的反斜杠表示下一个字符是特殊字符，不能按照字面理解。例如，前面没有 “&quot; 的 “b” 通常匹配小写字母 “b”，即字符会被作为字面理解，无论它出现在哪里。但如果前面加了 “&quot;，它将不再匹配任何字符，而是表示一个字符边界。</br>在特殊字符之前的反斜杠表示下一个字符不是特殊字符，应该按照字面理解。详情请参阅下文中的 “转义（Escaping）” 部分。</br>如果你想将字符串传递给 RegExp 构造函数，不要忘记在字符串字面量中反斜杠是转义字符。所以为了在模式中添加一个反斜杠，你需要在字符串字面量中转义它。&#x2F;[a-z]\s&#x2F;i 和 new RegExp(“[a-z]\s”, “i”) 创建了相同的正则表达式：一个用于搜索后面紧跟着空白字符（\s 可看后文）并且在 a-z 范围内的任意字符的表达式。为了通过字符串字面量给 RegExp 构造函数创建包含反斜杠的表达式，你需要在字符串级别和正则表达式级别都对它进行转义。例如 &#x2F;[a-z]:\&#x2F;i 和 new RegExp(“[a-z]:\\“,”i”) 会创建相同的表达式，即匹配类似 “C:&quot; 字符串。</td></tr><tr><td align="center">^</td><td align="left">匹配输入的开始</td></tr><tr><td align="center">$</td><td align="left">匹配输入的结束</td></tr><tr><td align="center">*</td><td align="left">匹配前一个表达式 0 次或多次。等价于 {0,}</td></tr><tr><td align="center">+</td><td align="left">匹配前面一个表达式 1 次或者多次。等价于 {1,}</td></tr><tr><td align="center">?</td><td align="left">匹配前面一个表达式 0 次或者 1 次。等价于 {0,1}。</td></tr><tr><td align="center">.</td><td align="left">（小数点）默认匹配除换行符之外的任何单个字符。</td></tr><tr><td align="center">x|y</td><td align="left">匹配‘x’或者‘y’。</br> 例如, &#x2F;a{2,}&#x2F; 匹配 “aa”, “aaaa” 和 “aaaaa” 但是不匹配 “a”。</td></tr><tr><td align="center">(x)</td><td align="left">像下面的例子展示的那样，它会匹配 ‘x’ 并且记住匹配项。其中括号被称为捕获括号。</td></tr><tr><td align="center">(?:x)</td><td align="left">匹配 ‘x’ 但是不记住匹配项。这种括号叫作非捕获括号，使得你能够定义与正则表达式运算符一起使用的子表达式。</td></tr><tr><td align="center">x(?&#x3D;y)</td><td align="left">匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。</td></tr><tr><td align="center">(?&lt;&#x3D;y)x</td><td align="left">匹配’x’仅当’x’前面是’y’.这种叫做后行断言。</td></tr><tr><td align="center">x(?!y)</td><td align="left">仅仅当’x’后面不跟着’y’时匹配’x’，这被称为正向否定查找。</td></tr><tr><td align="center">(?&lt;!y)x</td><td align="left">仅仅当’x’前面不是’y’时匹配’x’，这被称为反向否定查找。</td></tr><tr><td align="center">{n}</td><td align="left">n 是一个正整数，匹配了前面一个字符刚好出现了 n 次。</br>比如， &#x2F;a{2}&#x2F; 不会匹配“candy”中的’a’,但是会匹配“caandy”中所有的 a，以及“caaandy”中的前两个’a’。</td></tr><tr><td align="center">{n,}</td><td align="left">n是一个正整数，匹配前一个字符至少出现了n次。</br>例如, &#x2F;a{2,}&#x2F; 匹配 “aa”, “aaaa” 和 “aaaaa” 但是不匹配 “a”。</td></tr><tr><td align="center">{n,m}</td><td align="left">n 和 m 都是整数。匹配前面的字符至少n次，最多m次。如果 n 或者 m 的值是0， 这个值被忽略。</br>例如，&#x2F;a{1, 3}&#x2F; 并不匹配“cndy”中的任意字符，匹配“candy”中的a，匹配“caandy”中的前两个a，也匹配“caaaaaaandy”中的前三个a。注意，当匹配”caaaaaaandy“时，匹配的值是“aaa”，即使原始的字符串中有更多的a。</td></tr><tr><td align="center">[xyz]</td><td align="left">一个字符集合。匹配方括号中的任意字符，包括转义序列。你可以使用破折号（-）来指定一个字符范围。对于点（.）和星号（*）这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义，不过转义也是起作用的。</br>例如，[abcd] 和[a-d]是一样的。他们都匹配”brisket”中的‘b’,也都匹配“city”中的‘c’。&#x2F;[a-z.]+&#x2F; 和&#x2F;[\w.]+&#x2F;与字符串“test.i.ng”匹配。</td></tr><tr><td align="center">[^xyz]</td><td align="left">一个反向字符集。也就是说， 它匹配任何没有包含在方括号中的字符。你可以使用破折号（-）来指定一个字符范围。任何普通字符在这里都是起作用的。</td></tr><tr><td align="center">[\b]</td><td align="left">匹配一个退格(U+0008)。（不要和\b混淆了。）</td></tr><tr><td align="center">\b</td><td align="left">匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者前面跟其他“字”字符的位置，例如在字母和空格之间。注意，匹配中不包括匹配的字边界。换句话说，一个匹配的词的边界的内容的长度是0。（不要和[\b]混淆了）</br>使用”moon”举例：</br>&#x2F;\bm&#x2F;匹配“moon”中的‘m’；</br>&#x2F;oo\b&#x2F;并不匹配”moon”中的’oo’，因为’oo’被一个“字”字符’n’紧跟着。</br>&#x2F;oon\b&#x2F;匹配”moon”中的’oon’，因为’oon’是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。</td></tr><tr><td align="center">\B</td><td align="left">匹配一个非单词边界。</td></tr><tr><td align="center">\d</td><td align="left">匹配一个数字。等价于[0-9]。</br>例如， &#x2F;\d&#x2F; 或者 &#x2F;[0-9]&#x2F; 匹配”B2 is the suite number.”中的’2’。</td></tr><tr><td align="center">\D</td><td align="left">匹配一个非数字字符。等价于[^0-9]。</td></tr><tr><td align="center">\f</td><td align="left">匹配一个换页符 (U+000C)。</td></tr><tr><td align="center">\n</td><td align="left">匹配一个换行符 (U+000A)。</td></tr><tr><td align="center">\r</td><td align="left">匹配一个回车符 (U+000D)。</td></tr><tr><td align="center">\s</td><td align="left">匹配一个空白字符，包括空格、制表符、换页符和换行符。</td></tr><tr><td align="center">\S</td><td align="left">匹配一个非空白字符。</td></tr><tr><td align="center">\t</td><td align="left">匹配一个水平制表符 (U+0009)。</td></tr><tr><td align="center">\v</td><td align="left">匹配一个垂直制表符 (U+000B)。</td></tr><tr><td align="center">\w</td><td align="left">匹配一个单字字符（字母、数字或者下划线）。等价于 [A-Za-z0-9_]。</br>例如, &#x2F;\w&#x2F; 匹配 “apple,” 中的 ‘a’，”$5.28,”中的 ‘5’ 和 “3D.” 中的 ‘3’。</td></tr><tr><td align="center">\W</td><td align="left">匹配一个非单字字符。等价于 [^A-Za-z0-9_]。</br>例如, &#x2F;\W&#x2F; 或者 &#x2F;[^A-Za-z0-9_]&#x2F; 匹配 “50%.” 中的 ‘%’。</td></tr><tr><td align="center">\n</td><td align="left">在正则表达式中，它返回最后的第n个子捕获匹配的子字符串(捕获的数目以左括号计数)。</br>比如 &#x2F;apple(,)\sorange\1&#x2F; 匹配”apple, orange, cherry, peach.”中的’apple, orange,’ 。</td></tr><tr><td align="center">\0</td><td align="left">匹配 NULL（U+0000）字符， 不要在这后面跟其它小数，因为 \0<digits> 是一个八进制转义序列。</td></tr><tr><td align="center">\xhh</td><td align="left">匹配一个两位十六进制数（\x00-\xFF）表示的字符。</td></tr><tr><td align="center">\uhhhh</td><td align="left">匹配一个四位十六进制数表示的 UTF-16 代码单元。</td></tr><tr><td align="center">\u{hhhh}或\u{hhhhh}</td><td align="left">（仅当设置了u标志时）匹配一个十六进制数表示的 Unicode 字符。</td></tr></tbody></table><h3 id="数组对象-Array-object"><a href="#数组对象-Array-object" class="headerlink" title="数组对象(Array object)"></a>数组对象(Array object)</h3><p>数组(<code>array</code>)是一个有序的数据集合，我们可以通过数组名称(name)和索引(index)进行访问。例如，我们定义了一个数组emp，数组中的每个元素包含了一个雇员的名字以及其作为索引的员工号。那么emp[1]将会代表1号员工，emp[2]将会代表2号员工，以此类推。</p><p>JavaScript中没有明确的数组数据类型。但是，我们可以通过使用内置Array对象和它的方法对数组进行操作。Array对象有很多操作数组的方法，比如合并、反转、排序等。数组对象有一个决定数组长度和使用正则表达式操作其他属性的属性。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(element0, element1, ..., elementN);</span><br><span class="line"><span class="keyword">var</span> arr = <span class="title class_">Array</span>(element0, element1, ..., elementN);</span><br><span class="line"><span class="keyword">var</span> arr = [element0, element1, ..., elementN];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 译者注: var arr=[4] 和 var arr=new Array(4)是不等效的，</span></span><br><span class="line"><span class="comment">// 后者4指数组长度，所以使用字面值(literal)的方式应该不仅仅是便捷，同时也不易踩坑</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(arrayLength);</span><br><span class="line"><span class="keyword">var</span> arr = <span class="title class_">Array</span>(arrayLength);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这样有同样的效果</span></span><br><span class="line"><span class="keyword">var</span> arr = [];</span><br><span class="line">arr.<span class="property">length</span> = arrayLength;</span><br></pre></td></tr></table></figure><h4 id="数组的方法-array-methods"><a href="#数组的方法-array-methods" class="headerlink" title="数组的方法(array methods)"></a>数组的方法(array methods)</h4><p>Array 对象具有下列方法：<br>concat() 连接两个数组并返回一个新的数组。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myArray = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="string">&quot;1&quot;</span>, <span class="string">&quot;2&quot;</span>, <span class="string">&quot;3&quot;</span>);</span><br><span class="line">myArray = myArray.<span class="title function_">concat</span>(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>); </span><br><span class="line"><span class="comment">// myArray is now [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;a&quot;, &quot;b&quot;, &quot;c&quot;]</span></span><br></pre></td></tr></table></figure><p>join(deliminator &#x3D; ‘,’) 将数组的所有元素连接成一个字符串。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myArray = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="string">&quot;Wind&quot;</span>, <span class="string">&quot;Rain&quot;</span>, <span class="string">&quot;Fire&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> list = myArray.<span class="title function_">join</span>(<span class="string">&quot; - &quot;</span>); <span class="comment">// list is &quot;Wind - Rain - Fire&quot;</span></span><br></pre></td></tr></table></figure><p>push() 在数组末尾添加一个或多个元素，并返回数组操作后的长度。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myArray = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="string">&quot;1&quot;</span>, <span class="string">&quot;2&quot;</span>);</span><br><span class="line">myArray.<span class="title function_">push</span>(<span class="string">&quot;3&quot;</span>); <span class="comment">// myArray is now [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;]</span></span><br></pre></td></tr></table></figure><p>pop() 从数组移出最后一个元素，并返回该元素。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myArray = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="string">&quot;1&quot;</span>, <span class="string">&quot;2&quot;</span>, <span class="string">&quot;3&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> last = myArray.<span class="title function_">pop</span>();</span><br><span class="line"><span class="comment">// myArray is now [&quot;1&quot;, &quot;2&quot;], last = &quot;3&quot;</span></span><br></pre></td></tr></table></figure><p>shift() 从数组移出第一个元素，并返回该元素。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myArray = <span class="keyword">new</span> <span class="title class_">Array</span> (<span class="string">&quot;1&quot;</span>, <span class="string">&quot;2&quot;</span>, <span class="string">&quot;3&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> first = myArray.<span class="title function_">shift</span>(); </span><br><span class="line"><span class="comment">// myArray is now [&quot;2&quot;, &quot;3&quot;], first is &quot;1&quot;</span></span><br></pre></td></tr></table></figure><p>unshift() 在数组开头添加一个或多个元素，并返回数组的新长度。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myArray = <span class="keyword">new</span> <span class="title class_">Array</span> (<span class="string">&quot;1&quot;</span>, <span class="string">&quot;2&quot;</span>, <span class="string">&quot;3&quot;</span>);</span><br><span class="line">myArray.<span class="title function_">unshift</span>(<span class="string">&quot;4&quot;</span>, <span class="string">&quot;5&quot;</span>); </span><br><span class="line"><span class="comment">// myArray becomes [&quot;4&quot;, &quot;5&quot;, &quot;1&quot;, &quot;2&quot;, &quot;3&quot;]</span></span><br></pre></td></tr></table></figure><p>其他的就不一一介绍了</p><h4 id="类型化数组-Typed-Arrays"><a href="#类型化数组-Typed-Arrays" class="headerlink" title="类型化数组(Typed Arrays )"></a>类型化数组(Typed Arrays )</h4><p>JavaScript typed arrays 是类数组对象（array-like object），其提供访问原始二进制数据的机制。 就像你知道的那样, Array 对象动态增长和收缩，可以有任何JavaScript值。但对于类型化数组，JavaScript引擎执行优化使得这些数组访问速度快速。 随着Web应用程序变得越来越强大，添加音频和视频处理等功能、可以使用 WebSockets 、使用原始数据， 这都需要访问原始的二进制数据，所以专门的优化将有助于JavaScript代码能够快速和容易地操纵原始二进制数据类型的数组。</p><h3 id="映射"><a href="#映射" class="headerlink" title="映射"></a>映射</h3><h4 id="Map对象"><a href="#Map对象" class="headerlink" title="Map对象"></a>Map对象</h4><p>ECMAScript 2015 引入了一个新的数据结构来将一个值映射到另一个值。一个Map对象就是一个简单的键值对映射集合，可以按照数据插入时的顺序遍历所有的元素。</p><h4 id="Object和Map的比较"><a href="#Object和Map的比较" class="headerlink" title="Object和Map的比较"></a>Object和Map的比较</h4><p>一般地，objects会被用于将字符串类型映射到数值。Object允许设置键值对、根据键获取值、删除键、检测某个键是否存在。而Map具有更多的优势。</p><p><code>Object</code>的键均为<code>Strings</code>类型，在<code>Map</code>里键可以是任意类型。<br>必须手动计算<code>Object</code>的尺寸，但是可以很容易地获取使用Map的尺寸。<br><code>Map</code>的遍历遵循元素的插入顺序。<br><code>Object</code>有原型，所以映射中有一些缺省的键。（可以用 <code>map = Object.create(null)</code> 回避）。</p><p>这三条提示可以帮你决定用<code>Map</code>还是<code>Object</code>：</p><p>如果键在运行时才能知道，或者所有的键类型相同，所有的值类型相同，那就使用<code>Map</code>。<br>如果需要将原始值存储为键，则使用<code>Map</code>，因为<code>Object</code>将每个键视为字符串，不管它是一个数字值、布尔值还是任何其他原始值。<br>如果需要对个别元素进行操作，使用<code>Object</code>。</p><h4 id="WeakMap对象"><a href="#WeakMap对象" class="headerlink" title="WeakMap对象"></a>WeakMap对象</h4><p>WeakMap对象也是键值对的集合。它的键必须是对象类型，值可以是任意类型。它的键被弱保持，也就是说，当其键所指对象没有其他地方引用的时候，它会被GC回收掉。WeakMap提供的接口与Map相同。</p><p>与Map对象不同的是，WeakMap的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态，是不可预知的。</p><h3 id="对象（Object）"><a href="#对象（Object）" class="headerlink" title="对象（Object）"></a>对象（Object）</h3><p>javascript 中的对象(物体)，和其它编程语言中的对象一样，可以比照现实生活中的对象(物体)来理解它。 javascript 中对象(物体)的概念可以比照着现实生活中实实在在的物体来理解。</p><p>在javascript中，一个对象可以是一个单独的拥有属性和类型的实体。我们拿它和一个杯子做下类比。一个杯子是一个对象(物体)，拥有属性。杯子有颜色，图案，重量，由什么材质构成等等。同样，javascript对象也有属性来定义它的特征。</p><h4 id="创建新对象"><a href="#创建新对象" class="headerlink" title="创建新对象"></a>创建新对象</h4><p>JavaScript 拥有一系列预定义的对象。另外，你可以创建你自己的对象。从  JavaScript 1.2 之后，你可以通过对象初始化器（Object Initializer）创建对象。或者你可以创建一个构造函数并使用该函数和 new 操作符初始化对象。</p><h3 id="Promise"><a href="#Promise" class="headerlink" title="Promise"></a>Promise</h3><p><code>Promise</code> 是一个对象，它代表了一个异步操作的最终完成或者失败。因为大多数人仅仅是使用已创建的 <code>Promise</code> 实例对象，所以本教程将首先说明怎样使用 <code>Promise</code>，再说明如何创建 <code>Promise</code>。</p><p>本质上 <code>Promise</code> 是一个函数返回的对象，我们可以在它上面绑定回调函数，这样我们就不需要在一开始把回调函数作为参数传入这个函数了。</p><p>假设现在有一个名为 <code>createAudioFileAsync()</code> 的函数，它接收一些配置和两个回调函数，然后异步地生成音频文件。一个回调函数在文件成功创建时被调用，另一个则在出现异常时被调用。</p><h4 id="链式调用"><a href="#链式调用" class="headerlink" title="链式调用"></a>链式调用</h4><p>连续执行两个或者多个异步操作是一个常见的需求，在上一个操作执行成功之后，开始下一个的操作，并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">doSomething</span>().<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">result</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">doSomethingElse</span>(result);</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">newResult</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">doThirdThing</span>(newResult);</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">finalResult</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Got the final result: &#x27;</span> + finalResult);</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">catch</span>(failureCallback);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 我们也可以用箭头函数来表示</span></span><br><span class="line"><span class="title function_">doSomething</span>()</span><br><span class="line">.<span class="title function_">then</span>(<span class="function"><span class="params">result</span> =&gt;</span> <span class="title function_">doSomethingElse</span>(result))</span><br><span class="line">.<span class="title function_">then</span>(<span class="function"><span class="params">newResult</span> =&gt;</span> <span class="title function_">doThirdThing</span>(newResult))</span><br><span class="line">.<span class="title function_">then</span>(<span class="function"><span class="params">finalResult</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Got the final result: <span class="subst">$&#123;finalResult&#125;</span>`</span>);</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">catch</span>(failureCallback);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="错误传递"><a href="#错误传递" class="headerlink" title="错误传递"></a>错误传递</h4><p>在之前的回调地狱示例中，你可能记得有 3 次 failureCallback 的调用，而在 Promise 链中只有尾部的一次调用。<br>通常，一遇到异常抛出，浏览器就会顺着 Promise 链寻找下一个 onRejected 失败回调函数或者由 .catch() 指定的回调函数。这和以下同步代码的工作原理（执行过程）非常相似。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> result = <span class="title function_">syncDoSomething</span>();</span><br><span class="line">  <span class="keyword">let</span> newResult = <span class="title function_">syncDoSomethingElse</span>(result);</span><br><span class="line">  <span class="keyword">let</span> finalResult = <span class="title function_">syncDoThirdThing</span>(newResult);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Got the final result: <span class="subst">$&#123;finalResult&#125;</span>`</span>);</span><br><span class="line">&#125; <span class="keyword">catch</span>(error) &#123;</span><br><span class="line">  <span class="title function_">failureCallback</span>(error);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在 ECMAScript 2017 标准的 async/await 语法糖中，这种异步代码的对称性得到了极致的体现：</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title function_">doSomething</span>();</span><br><span class="line">    <span class="keyword">const</span> newResult = <span class="keyword">await</span> <span class="title function_">doSomethingElse</span>(result);</span><br><span class="line">    <span class="keyword">const</span> finalResult = <span class="keyword">await</span> <span class="title function_">doThirdThing</span>(newResult);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Got the final result: <span class="subst">$&#123;finalResult&#125;</span>`</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span>(error) &#123;</span><br><span class="line">    <span class="title function_">failureCallback</span>(error);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Promise-拒绝事件"><a href="#Promise-拒绝事件" class="headerlink" title="Promise 拒绝事件"></a>Promise 拒绝事件</h4><p>当 Promise 被拒绝时，会有下文所述的两个事件之一被派发到全局作用域（通常而言，就是window；如果是在 web worker 中使用的话，就是 Worker 或者其他 worker-based 接口）。这两个事件如下所示：</p><ul><li>rejectionhandled<br>  当 Promise 被拒绝、并且在 reject 函数处理该 rejection 之后会派发此事件。</li><li>unhandledrejection<br>  当 Promise 被拒绝，但没有提供 reject 函数来处理该 rejection 时，会派发此事件。</li></ul><p>以上两种情况中，<code>PromiseRejectionEvent</code> 事件都有两个属性，一个是 <code>promise</code> 属性，该属性指向被驳回的 <code>Promise</code>，另一个是 <code>reason</code> 属性，该属性用来说明 <code>Promise</code> 被驳回的原因。</p><h3 id="元编程"><a href="#元编程" class="headerlink" title="元编程"></a>元编程</h3><p>从ECMAScript 2015 开始，JavaScript 获得了 Proxy 和 Reflect 对象的支持，允许你拦截并定义基本语言操作的自定义行为（例如，属性查找，赋值，枚举，函数调用等）。借助这两个对象，你可以在 JavaScript 元级别进行编程。</p><h4 id="代理"><a href="#代理" class="headerlink" title="代理"></a>代理</h4><p>在 ECMAScript 6 中引入的 Proxy 对象可以拦截某些操作并实现自定义行为。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> handler = &#123;</span><br><span class="line">  <span class="attr">get</span>: <span class="keyword">function</span>(<span class="params">target, name</span>)&#123;</span><br><span class="line">    <span class="keyword">return</span> name <span class="keyword">in</span> target ? target[name] : <span class="number">42</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Proxy</span>(&#123;&#125;, handler);</span><br><span class="line">p.<span class="property">a</span> = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p.<span class="property">a</span>, p.<span class="property">b</span>); <span class="comment">// 1, 42</span></span><br></pre></td></tr></table></figure><p>Proxy 对象定义了一个目标（这里是一个空对象）和一个实现了 get 陷阱的 handler 对象。这里，代理的对象在获取未定义的属性时不会返回 undefined，而是返回 42。</p><h4 id="反射"><a href="#反射" class="headerlink" title="反射"></a>反射</h4><p>Reflect 是一个内置对象，它提供了可拦截 JavaScript 操作的方法。该方法和代理句柄类似，但 Reflect 方法并不是一个函数对象。</p><h2 id="客户端-Web-API"><a href="#客户端-Web-API" class="headerlink" title="客户端 Web API"></a>客户端 Web API</h2><p>当你给网页或者网页应用编写客户端的JavaScript时， 你很快会遇上应用程序接口（API ）—— 这些编程特性可用来操控网站所基于的浏览器与操作系统的不同方面，或是操控由其他网站或服务端传来的数据。在这个单元里，我们将一同探索什么是API，以及如何使用一些在你开发中将经常遇见的API。</p><ul><li>Web API简介<br>首先, 我们将从一个更高的角度来看这些API —它们是什么，它们怎么起作用的，你该怎么在自己的代码中使用它们以及他们是怎么构成的？ 我们依旧会再来看一看这些API有哪些主要的种类和他们会有哪些用处。</li><li>操作文档<br>当你在制作WEB页面和APP时,一个你最经常想要做的事就是通过一些方法来操作WEB文档。这其中最常见的方法就是使用文档对象模型Document Object Model (DOM)，它是一系列大量使用了 Document object的API来控制HTML和样式信息。通过这篇文章，我们来看看使用DOM方面的一些细节， 以及其他一些有趣的API能够通过一些有趣的方式改变你的环境。</li><li>从服务器获取数据<br>在现代网页及其APP中另外一个很常见的任务就是与服务器进行数据交互时不再刷新整个页面，这看起来微不足道，但却对一个网页的展现和交互上起到了很大的作用，在这篇文章里，我们将阐述这个概念，然后来了解实现这个功能的技术，例如 XMLHttpRequest 和 Fetch API.（抓取API）。</li><li>第三方 API<br>到目前为止我们所涉及的API都是浏览器内置的，但并不代表所有。许多大网站如Google Maps, Twitter, Facebook, PayPal等，都提供他们的API给开发者们去使用他们的数据（比如在你的博客里展示你分享的推特内容）或者服务（如在你的网页里展示定制的谷歌地图或接入Facebook登录功能）。这篇文章介绍了浏览器API和第三方API 的差别以及一些最新的典型应用。</li><li>绘制图形<br>浏览器包含多种强大的图形编程工具，从可缩放矢量图形语言Scalable Vector Graphics (SVG) language，到HTML绘制元素<code>&lt;canvas&gt;</code>元素(The Canvas API and WebGL). 这篇文章提供了部分canvas的简介，以及让你更深入学习的资源。</li><li>视频和音频 API<br>HTML5能够通过元素标签嵌入富媒体——<code>&lt;video&gt; and &lt;audio&gt;</code>——而将有自己的API来控制回放，搜索等功能。本文向您展示了如何创建自定义播放控制等常见的任务。</li><li>客户端存储<br>现代web浏览器拥有很多不同的技术，能够让你存储与网站相关的数据，并在需要时调用它们，能够让你长期保存数据、保存离线网站及其他实现其他功能。本文解释了这些功能的基本原理。</li></ul><p>详细的内容以后在慢慢补充吧，这种 api 对于我来说没多大作用，我还是主要在语言方面多了解一些。</p><h2 id="重新介绍-JavaScript（JS-教程）"><a href="#重新介绍-JavaScript（JS-教程）" class="headerlink" title="重新介绍 JavaScript（JS 教程）"></a>重新介绍 JavaScript（JS 教程）</h2><p>为什么会有这一篇“重新介绍”呢？因为 <code>JavaScript</code> 堪称世界上被人误解最深的编程语言。虽然常被嘲为“玩具语言”，但在它看似简洁的外衣下，还隐藏着强大的语言特性。 <code>JavaScript</code> 目前广泛应用于众多知名应用中，对于网页和移动开发者来说，深入理解 <code>JavaScript</code> 就尤为必要。</p><p>我们有必要先从这门语言的历史谈起。在1995 年 Netscape 一位名为 Brendan Eich 的工程师创造了 <code>JavaScript</code>，随后在 1996 年初，<code>JavaScript</code> 首先被应用于 Netscape 2 浏览器上。最初的 <code>JavaScript</code> 名为 LiveScript，但是因为一个糟糕的营销策略而被重新命名，该策略企图利用Sun Microsystem的Java语言的流行性，将它的名字从最初的 LiveScript 更改为 <code>JavaScript</code>——尽管两者之间并没有什么共同点。这便是之后混淆产生的根源。</p><p>几个月后，Microsoft 随 IE 3 发布推出了一个与之基本兼容的语言 JScript。又过了几个月，Netscape 将 <code>JavaScript</code> 提交至 Ecma International（一个欧洲标准化组织）， ECMAScript 标准第一版便在 1997 年诞生了，随后在 1999 年以 ECMAScript 第三版的形式进行了更新，从那之后这个标准没有发生过大的改动。由于委员会在语言特性的讨论上发生分歧，ECMAScript 第四版尚未推出便被废除，但随后于 2009 年 12 月发布的 ECMAScript 第五版引入了第四版草案加入的许多特性。第六版标准已经于 2015 年 6 月发布。</p><p>与大多数编程语言不同，<code>JavaScript</code> 没有输入或输出的概念。它是一个在宿主环境（host environment）下运行的脚本语言，任何与外界沟通的机制都是由宿主环境提供的。浏览器是最常见的宿主环境，但在非常多的其他程序中也包含 <code>JavaScript</code> 解释器，如 Adobe Acrobat、Adobe Photoshop、SVG 图像、Yahoo! 的 Widget 引擎，Node.js 之类的服务器端环境，NoSQL 数据库（如开源的 Apache CouchDB）、嵌入式计算机，以及包括 GNOME （注：GNU&#x2F;Linux 上最流行的 GUI 之一）在内的桌面环境等等。</p><h3 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h3><p><code>JavaScript</code> 是一种多范式的动态语言，它包含类型、运算符、标准内置（ built-in）对象和方法。它的语法来源于 <code>Java</code> 和 <code>C</code>，所以这两种语言的许多语法特性同样适用于 <code>JavaScript</code>。<code>JavaScript</code> 通过原型链而不是类来支持面向对象编程（有关 ES6 类的内容参考这里Classes，有关对象原型参考见此继承与原型链）。<code>JavaScript</code>同样支持函数式编程——因为它们也是对象，函数也可以被保存在变量中，并且像其他对象一样被传递。</p><p>先从任何编程语言都不可缺少的组成部分——“类型”开始。<code>JavaScript</code> 程序可以修改值（value），这些值都有各自的类型。<code>JavaScript</code> 中的类型包括：</p><ul><li><code>Number</code>（数字）</li><li><code>String</code>（字符串）</li><li><code>Boolean</code>（布尔）</li><li><code>Function</code>（函数）</li><li><code>Object</code>（对象）</li><li><code>Symbol</code>（ES2015 新增）</li></ul><p>还有看上去有些…奇怪的 <code>undefined</code>（未定义）类型和 <code>null</code>（空）类型。此外还有<code>Array</code>（数组）类型，以及分别用于表示日期和正则表达式的 <code>Date</code>（日期）和 <code>RegExp</code>（正则表达式），这三种类型都是特殊的对象。严格意义上说，<code>Function</code>（函数）也是一种特殊的对象。所以准确来说，<code>JavaScript</code> 中的类型应该包括这些：</p><ul><li><code>Number</code>（数字）</li><li><code>String</code>（字符串）</li><li><code>Boolean</code>（布尔）</li><li><code>Symbol</code>（符号）（ES2015 新增）</li><li><code>Object</code>（对象）<ul><li><code>Function</code>（函数）</li><li><code>Array</code>（数组）</li><li><code>Date</code>（日期）</li><li><code>RegExp</code>（正则表达式）</li></ul></li><li><code>null</code>（空）</li><li><code>undefined</code>（未定义）</li></ul><p><code>JavaScript</code> 还有一种内置的 <code>Error</code>（错误）类型。但是，如果我们继续使用上面的分类，事情便容易得多；所以，现在，我们先讨论上面这些类型。</p><h3 id="数字-1"><a href="#数字-1" class="headerlink" title="数字"></a>数字</h3><p>根据语言规范，<code>JavaScript</code> 采用“遵循 IEEE 754 标准的双精度 64 位格式”（”double-precision 64-bit format IEEE 754 values”）表示数字。——在<code>JavaScript</code>（除了BigInt）当中，并不存在整数&#x2F;整型(<code>Integer</code>)。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span> / <span class="number">2</span>);             <span class="comment">// 1.5,not 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="number">3</span> / <span class="number">2</span>)); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 你可以使用内置函数 parseInt() 将字符串转换为整型。</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&quot;123&quot;</span>, <span class="number">10</span>); <span class="comment">// 123</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&quot;010&quot;</span>, <span class="number">10</span>); <span class="comment">// 10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 一些老版本的浏览器会将首字符为“0”的字符串当做八进制数字，2013 年以前的 JavaScript 实现</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&quot;010&quot;</span>);  <span class="comment">//  8</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&quot;0x10&quot;</span>); <span class="comment">// 16</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 这是因为字符串以数字 0 开头，parseInt()函数会把这样的字符串视作八进制数字；</span></span><br><span class="line"><span class="comment">// 同理，0x开头的字符串则视为十六进制数字。</span></span><br></pre></td></tr></table></figure><p><code>JavaScript</code> 还有两个特殊值：<code>Infinity</code>（正无穷）和 -<code>Infinity</code>（负无穷）：<br>可以使用内置函数 <code>isFinite()</code> 来判断一个变量是否是一个有穷数， 如果类型为<code>Infinity</code>, -<code>Infinity</code> 或 <code>NaN</code>则返回<code>false</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span> / <span class="number">0</span>; <span class="comment">//  Infinity</span></span><br><span class="line">-<span class="number">1</span> / <span class="number">0</span>; <span class="comment">// -Infinity</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">isFinite</span>(<span class="number">1</span>/<span class="number">0</span>); <span class="comment">// false</span></span><br><span class="line"><span class="built_in">isFinite</span>(<span class="title class_">Infinity</span>); <span class="comment">// false</span></span><br><span class="line"><span class="built_in">isFinite</span>(-<span class="title class_">Infinity</span>); <span class="comment">// false</span></span><br><span class="line"><span class="built_in">isFinite</span>(<span class="title class_">NaN</span>); <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">isFinite</span>(<span class="number">0</span>); <span class="comment">// true</span></span><br><span class="line"><span class="built_in">isFinite</span>(<span class="number">2e64</span>); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">isFinite</span>(<span class="string">&quot;0&quot;</span>); <span class="comment">// true</span></span><br><span class="line"><span class="comment">// 如果是纯数值类型的检测，则返回 false：</span></span><br><span class="line"><span class="title class_">Number</span>.<span class="built_in">isFinite</span>(<span class="string">&quot;0&quot;</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><h3 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h3><p><code>JavaScript</code> 中的字符串是一串<code>Unicode</code> 字符序列。这对于那些需要和多语种网页打交道的开发者来说是个好消息。更准确地说，它们是一串<code>UTF-16</code>编码单元的序列，每一个编码单元由一个 16 位二进制数表示。每一个<code>Unicode</code>字符由一个或两个编码单元来表示。</p><h3 id="其他类型"><a href="#其他类型" class="headerlink" title="其他类型"></a>其他类型</h3><p>与其他类型不同，<code>JavaScript</code> 中的 <code>null</code> 表示一个空值（<code>non-value</code>），必须使用 <code>null</code> 关键字才能访问，<code>undefined</code> 是一个“<code>undefined</code>（未定义）”类型的对象，表示一个未初始化的值，也就是还没有被分配的值。我们之后再具体讨论变量，但有一点可以先简单说明一下，<code>JavaScript</code> 允许声明变量但不对其赋值，一个未被赋值的变量就是 <code>undefined</code> 类型。还有一点需要说明的是，<code>undefined</code> 实际上是一个不允许修改的常量。</p><p>JavaScript 包含布尔类型，这个类型的变量有两个可能的值，分别是 true 和 false（两者都是关键字）。根据具体需要，JavaScript 按照如下规则将变量转换成布尔类型：</p><ul><li><code>false</code>、0、空字符串（””）、<code>NaN</code>、<code>null</code> 和 <code>undefined</code> 被转换为 <code>false</code></li><li>所有其他值被转换为 <code>true</code></li></ul><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><p>在 <code>JavaScript</code> 中声明一个新变量的方法是使用关键字 <code>let</code> 、<code>const</code> 和 <code>var</code>：</p><p><code>let</code> 语句声明一个块级作用域的本地变量，并且可选的将其初始化为一个值。<br><code>const</code> 允许声明一个不可变的常量。这个常量在定义域内总是可见的。<br><code>var</code> 是最常见的声明变量的关键字。它没有其他两个关键字的种种限制。这是因为它是传统上在 <code>JavaScript</code> 声明变量的唯一方法。<br>使用 <code>var</code> 声明的变量在它所声明的整个函数都是可见的。</p><p>剩下的对象，函数就不一一记录了，其实跟上面的没多大区别，只是强化一下记忆。有语言基础的人应该会很好理解这些内容。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p><code>JavaScript</code>还有很多的内容， 继承和原型链 是特别重要的内容，下一篇文章会详细聊一聊。</p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript">JavaScript</a></p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20201030/learning/JavaScript-study-record/</id>
    <link href="https://minniexcode.github.io/memoirs/20201030/learning/JavaScript-study-record/"/>
    <published>2020-10-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在前面的话"><a href="#写在前面的话" class="headerlink" title="写在前面的话"></a>写在前面的话</h2><p>最近用react-native 完成了一个公司的项目，JavaScript 其实已经看过很多遍了，上个月就看了一遍全部的教程，但是没有系统的记录，以及一些比较麻烦的地方。<br>有一些三方的源码还是看的不太懂，而且js的坑不算少，我希望能够记录一下，下次能够快速定位问题。<br>（起码我可以看得懂三方的源代码o(╯□╰)o）</p>]]>
    </summary>
    <title>JavaScript 温习记录（一）</title>
    <updated>2026-03-16T16:27:30.441Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="生活记录" scheme="https://minniexcode.github.io/memoirs/categories/%E7%94%9F%E6%B4%BB%E8%AE%B0%E5%BD%95/"/>
    <category term="Essay" scheme="https://minniexcode.github.io/memoirs/tags/Essay/"/>
    <category term="macOS" scheme="https://minniexcode.github.io/memoirs/tags/macOS/"/>
    <content>
      <![CDATA[<p>最近一段时间，我的笔记本（17年款 macbook pro 13寸）经常出现键盘连击问题。</p><p>最大的表现是 e&#x2F;n&#x2F;i 这几个按键，按下的时候，会有概率的出现两个或三个。</p><h2 id="这不是个案"><a href="#这不是个案" class="headerlink" title="这不是个案"></a>这不是个案</h2><p>搜索了一下，有不少人都反馈了相同的问题，比如</p><p><a href="https://www.v2ex.com/t/494645">2018 款 MacBook Pro 键盘连击问题。。。</a><br><a href="https://www.v2ex.com/t/432590">MacBook Pro 键盘又连击了~😤🤪</a></p><p>而且苹果官方确实也承认了，这一代的键盘确实存在设计问题。</p><h2 id="临时解决方案"><a href="#临时解决方案" class="headerlink" title="临时解决方案"></a>临时解决方案</h2><p>根据大家的经验，找到了一款软件 <a href="https://github.com/aahung/Unshaky"><code>Unshaky</code></a> ，号称可以通过软件识别连击问题。</p><p>论坛中也有人说通过这个软件解决了。</p><p><strong>Unshaky</strong> tries to address an issue on the butterfly keyboard (Macbook, Macbook Air 2018 &amp; MacBook Pro 2016 and later): Double Key Press (See “<a href="#complaints-about-this-issue">User complaints</a>“ below). </p><p>Apple made it difficult to replace only the keyboard and it costs hundreds of dollars. <strong>Unshaky</strong> might save your keyboard by dismissing such “second key hits” (any key presses that occur no later than x milliseconds after the previous effective one). I fixed my “w” key with <strong>Unshaky</strong>, and if it does not work for you, open an issue <a href="https://github.com/aahung/Unshaky/issues">here</a>. The image below illustrates how Unshaky works.</p><p>[May 2019] Apple extends <a href="https://www.apple.com/ca/support/keyboard-service-program-for-macbook-and-macbook-pro/">the service program</a> to cover all MacBook (Air &amp; Pro) with 3rd gen butterfly keyboards.</p><p>我也暂时用这个方案解决了三个按键连击的问题。</p><h2 id="终极解决方案"><a href="#终极解决方案" class="headerlink" title="终极解决方案"></a>终极解决方案</h2><p>之所以存在连击问题，大概率是因为这一代的键盘中进灰了，影响了按压时候的判断。</p><p>苹果官方给出的<a href="https://support.apple.com/zh-cn/HT205662"><strong>解决方案</strong></a>，简单的说就是用压缩空气喷键盘。</p><h4 id="这么好的东西，哪里买呢"><a href="#这么好的东西，哪里买呢" class="headerlink" title="这么好的东西，哪里买呢"></a>这么好的东西，哪里买呢</h4><p><a href="https://m.tb.cn/h.VWQwj5B"><strong>压缩空气罐</strong></a>淘宝上很多，20多块钱，不用买很多，一罐就够用了。除了解决键盘，还可以清理相机之类的设备。</p><p>记得不要买成别的气罐了（比如带WD-40，那是用于润滑去锈的，不要买），要纯净空气那种。</p><h4 id="为什么不用吸尘器或者电吹风呢"><a href="#为什么不用吸尘器或者电吹风呢" class="headerlink" title="为什么不用吸尘器或者电吹风呢"></a>为什么不用吸尘器或者电吹风呢</h4><p>理论上来说，吸尘器和电吹风效果是类似的，但是不能保证你身边的空气足够干净，万一把新的灰尘吹进去了岂不是…</p><h4 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h4><p>如果您的 MacBook（2015 年及更新机型）或 MacBook Pro（2016 年及更新机型）上的某个按键无反应，或某个按键按下时的触感与其他按键不同，请按照以下步骤用压缩空气来清洁键盘。</p><p>当您按照这些步骤进行操作时，应务必使用压缩空气自带的喷管来控制气流，在喷气时保持喷管末端距离键盘半英寸远。还要注意，喷气时不要颠倒瓶体。</p><ol><li>以 75 度角握持 Mac 笔记本电脑，这样电脑不会完全垂直。</li><li>按从左到右的方式将压缩空气喷向键盘，或仅喷向受影响的按键。</li><li>将 Mac 笔记本电脑向右侧旋转，然后再次按从左到右的方式喷向键盘。</li><li>这一次将 Mac 笔记本电脑向左侧旋转，然后重复以上操作。</li></ol><p>具体方法还是就要查看苹果的官方帮助文档。</p><h2 id="更新后续的使用情况"><a href="#更新后续的使用情况" class="headerlink" title="更新后续的使用情况"></a>更新后续的使用情况</h2><p>总的来说还是<strong>Unshaky</strong>还是很好用的，可以解决大部分的键盘连击问题，但是有时候键盘会有3次或者以上的连击，<strong>Unshaky</strong>没法很完美的解决这个问题。</p><p><img src="/memoirs/images/other_1.png"><br><img src="/memoirs/images/other_2.png"></p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20200911/other/macbook-%E9%94%AE%E7%9B%98%E8%BF%9E%E5%87%BB%E9%97%AE%E9%A2%98/</id>
    <link href="https://minniexcode.github.io/memoirs/20200911/other/macbook-%E9%94%AE%E7%9B%98%E8%BF%9E%E5%87%BB%E9%97%AE%E9%A2%98/"/>
    <published>2020-09-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>最近一段时间，我的笔记本（17年款 macbook pro 13寸）经常出现键盘连击问题。</p>
<p>最大的表现是 e&#x2F;n&#x2F;i 这几个按键，按下的时候，会有概率的出现两个或三个。</p>
<h2 id="这不是个案"><a href="#这不是个案"]]>
    </summary>
    <title>macbook-键盘连击问题</title>
    <updated>2020-10-28T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Python" scheme="https://minniexcode.github.io/memoirs/tags/Python/"/>
    <category term="Flask" scheme="https://minniexcode.github.io/memoirs/tags/Flask/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前面介绍了Flask和HTTP的基础知识，下面会介绍Flask的基础用法</p><h2 id="第4章-表单"><a href="#第4章-表单" class="headerlink" title="第4章 表单"></a>第4章 表单</h2><p>在<code>Web</code>程序中，表单是和用户交互最常见的方式之一。用户注册、登录、撰写文章、编辑设置，无一不用到表单。不过，表单的处理却并不简单。<br>你不仅要创建表单，验证用户输入的内容，向用户显示错误提示，还要获取并保存数据。幸运的是，强大的<code>WTForms</code>可以帮我们解决这些问题。<br><code>WTForms</code>是一个使用<code>Python</code>编写的表单库，它使得表单的定 义、验证（服务器端）和处理变得非常轻松。这一章我们会介绍在Web 程序中处理表单的方法和技巧。</p><h3 id="使用Flask-WTF处理表单"><a href="#使用Flask-WTF处理表单" class="headerlink" title="使用Flask-WTF处理表单"></a>使用Flask-WTF处理表单</h3><p>扩展<code>Flask-WTF</code>集成了<code>WTForms</code>，使用它可以在<code>Flask</code>中更方便地使用<code>WTForms</code>。<code>Flask-WTF</code>将表单数据解析、<code>CSRF</code>保护、文件上传等功能与<code>Flask</code>集成，另外还附加了<code>reCAPTCHA</code>支持。</p><p>Flask-WTF默认为每个表单启用CSRF保护，它会为我们自动生成和 验证CSRF令牌。默认情况下，Flask-WTF使用程序密钥来对CSRF令牌 进行签名，所以我们需要为程序设置密钥：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">app.secret_key = <span class="string">&#x27;secret string&#x27;</span></span><br></pre></td></tr></table></figure><h4 id="定义WTForms表单类"><a href="#定义WTForms表单类" class="headerlink" title="定义WTForms表单类"></a>定义WTForms表单类</h4><p>当使用WTForms创建表单时，表单由Python类表示，这个类继承从 WTForms导入的Form基类。一个表单由若干个输入字段组成，这些字 段分别用表单类的类属性来表示（字段即Field，你可以简单理解为表单 内的输入框、按钮等部件）。下面定义了一个LoginForm类，最终会生 成我们在前面定义的HTML表单：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> wtforms <span class="keyword">import</span> Form, StringField, PasswordField, BooleanField, SubmitField</span><br><span class="line"><span class="keyword">from</span> wtforms.validators <span class="keyword">import</span> DataRequired, Length</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4.2.1 basic form example</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LoginForm</span>(<span class="title class_ inherited__">FlaskForm</span>):</span><br><span class="line">    username = StringField(<span class="string">&#x27;Username&#x27;</span>, validators=[DataRequired()])</span><br><span class="line">    password = PasswordField(<span class="string">&#x27;Password&#x27;</span>, validators=[DataRequired(), Length(<span class="number">8</span>, <span class="number">128</span>)])</span><br><span class="line">    remember = BooleanField(<span class="string">&#x27;Remember me&#x27;</span>)</span><br><span class="line">    submit = SubmitField(<span class="string">&#x27;Log in&#x27;</span>)</span><br></pre></td></tr></table></figure><p>每个字段属性通过实例化WTForms提供的字段类表示。字段属性的名称将作为对应HTML<code>&lt;input&gt;</code>元素的name属性及id属性值。</p><p><img src="/memoirs/images/flask/w1.png" alt="常用的WTForms字段"></p><p><img src="/memoirs/images/flask/w2.png" alt="实例化字段类常用参数"></p><p><img src="/memoirs/images/flask/w3.png" alt="常用的WTForms验证器"></p><p>当使用<code>Flask-WTF</code>定义表单时，我们仍然使用<code>WTForms</code>提供的字段类和验证器，创建的方式也完全相同，只不过表单类要继承<code>Flask-WTF</code>提供的<code>FlaskForm</code>类。<code>FlaskForm</code>类继承自<code>Form</code>类，进行了一些设置，并附加了一些辅助方法，以便与<code>Flask</code>集成。</p><span id="more"></span><h4 id="输出HTML代码"><a href="#输出HTML代码" class="headerlink" title="输出HTML代码"></a>输出HTML代码</h4><p>以我们使用WTForms创建的LoginForm为例，实例化表单类，然后将实例属性转换成字符串或直接调用就可以获取表单字段对应的HTML代码：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form = LoginForm() &gt;&gt;&gt; form.username()</span></span><br><span class="line">u&#x27;&lt;input id=&quot;username&quot; name=&quot;username&quot; type=&quot;text&quot; value=&quot;&quot;&gt;&#x27;</span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form.submit()</span></span><br><span class="line">u&#x27;&lt;input id=&quot;submit&quot; name=&quot;submit&quot; type=&quot;submit&quot; value=&quot;Submit&quot;&gt;&#x27;</span><br></pre></td></tr></table></figure><p>在创建HTML表单时，我们经常会需要使用HTML<code>&lt;input&gt;</code>元素的其 他属性来对字段进行设置。比如，添加class属性设置对应的CSS类为字段添加样式；添加placeholder属性设置占位文本。默认情况下，WTForms输出的字段HTML代码只会包含id和name属性，属性值均为表单类中对应的字段属性名称。如果要添加额外的属性，通常有两种方法。</p><ol><li>使用render_kw属性</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">username = StringField(<span class="string">&#x27;Username&#x27;</span>, render_kw=&#123;<span class="string">&#x27;placeholder&#x27;</span>: <span class="string">&#x27;Your Username&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">id</span>=<span class="string">&quot;username&quot;</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;Your Username&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><ol start="2"><li>在调用字段时传入</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form.username(style=<span class="string">&#x27;width: 200px;&#x27;</span>, class_=<span class="string">&#x27;bar&#x27;</span>)</span></span><br><span class="line">u&#x27;&lt;i nput class=&quot;bar&quot; id=&quot;username&quot; name=&quot;username&quot; style=&quot;width: 200px;&quot; type=&quot;text&quot;&gt;&#x27;</span><br></pre></td></tr></table></figure><h4 id="在模板中渲染表单"><a href="#在模板中渲染表单" class="headerlink" title="在模板中渲染表单"></a>在模板中渲染表单</h4><p>为了能够在模板中渲染表单，我们需要把表单类实例传入模板。首 先在视图函数里实例化表单类LoginForm，然后在render_template()函 数中使用关键字参数form将表单实例传入模板。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, render_template, redirect, url_for, flash</span><br><span class="line"><span class="keyword">from</span> forms <span class="keyword">import</span> LoginForm</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/basic&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">basic</span>():</span><br><span class="line">    form = LoginForm()</span><br><span class="line">    <span class="keyword">return</span> render_template(<span class="string">&#x27;login.html&#x27;</span>, form=form)</span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</span>&gt;</span> &#123;&#123; form.csrf_token &#125;&#125;</span><br><span class="line">    <span class="comment">&lt;!-- 渲染CSRF令牌隐藏字段 --&gt;</span></span><br><span class="line">    &#123;&#123; form.username.label &#125;&#125;&#123;&#123; form.username &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;&#123; form.password.label &#125;&#125;&#123;&#123; form.password &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;&#123; form.remember &#125;&#125;&#123;&#123; form.remember.label &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;&#123; form.submit &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br></pre></td></tr></table></figure><p>需要注意的是，在上面的代码中，除了渲染各个字段的标签和字段本身，我们还调用了<code>form.csrf_token</code>属性渲染<code>Flask-WTF</code>为表单类自动创建的CSRF令牌字段。<code>form.csrf_token</code>字段包含了自动生成的CSRF令牌值，在提交表单后会自动被验证，为了确保表单通过验证，我们必须在表单中手动渲染这个字段。</p><h3 id="处理表单数据"><a href="#处理表单数据" class="headerlink" title="处理表单数据"></a>处理表单数据</h3><p>表单数据的处理涉及很多内容，除去表单提交不说，从获取数据到保存数据大致会经历以下步骤：</p><ol><li>解析请求，获取表单数据。</li><li>对数据进行必要的转换，比如将勾选框的值转换成Python的布尔值。</li><li>验证数据是否符合要求，同时验证CSRF令牌。</li><li>如果验证未通过则需要生成错误消息，并在模板中显示错误消息。</li><li>如果通过验证，就把数据保存到数据库或做进一步处理。</li></ol><p>除非是简单的程序，否则手动处理不太现实，使用Flask-WTF和 WTForms可以极大地简化这些步骤。</p><h4 id="提交表单"><a href="#提交表单" class="headerlink" title="提交表单"></a>提交表单</h4><p>在HTML中，当<code>&lt;form&gt;</code>标签声明的表单中类型为submit的提交字段被单击时，就会创建一个提交表单的HTTP请求，请求中包含表单各个字段的数据。<br>表单的提交行为主要由三个属性控制，如下图所示。</p><p><img src="/memoirs/images/flask/w4.png" alt="HTML表单中控制提交行为的属性"></p><p>form标签的action属性用来指定表单被提交的目标URL，默认为当前URL，也就是渲染该模板的路由所在的URL。如果你要把表单数据发送到其他URL，可以自定义这个属性值。</p><h4 id="验证表单数据"><a href="#验证表单数据" class="headerlink" title="验证表单数据"></a>验证表单数据</h4><p>表单数据的验证是Web表单中最重要的主题之一，这一节我们会学习如何使用Flask-WTF验证并获取表单数据。</p><ol><li><p>客户端验证和服务器端验证<br>表单的验证通常分为以下两种形式：</p><ul><li>客户端验证<br> 客户端验证（client side validation）是指在客户端（比如Web浏览器）对用户的输入值进行验证。比如，使用HTML5内置的验证属性即可实现基本的客户端验证（type、required、min、max、accept等）。比如，下面的username字段添加了required标志：</li></ul> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">required</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>服务器端验证</li></ul><p> 服务器端验证（server side validation）是指用户把输入的数据提交到服务器端，在服务器端对数据进行验证。如果验证出错，就在返回的响应中加入错误信息。用户修改后再次提交表单，直到通过验证。我们在Flask程序中使用WTForms实现的就是服务器端验证。</p></li><li><p>WTForms验证机制<br>WTForms验证表单字段的方式是在实例化表单类时传入表单数据，然后对表单实例调用validate()方法。这会逐个对字段调用字段实例化时定义的验证器，返回表示验证结果的布尔值。如果验证失败，就把错误消息存储到表单实例的errors属性对应的字典中，验证的过程如下所示：</p></li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; from wtforms import Form, StringField, PasswordField, BooleanField</span></span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; from wtforms.validators import DataRequired, Length</span></span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; class LoginForm(Form):</span></span><br><span class="line">... username = StringField(&#x27;Username&#x27;, validators=[DataRequired()])</span><br><span class="line">... password = PasswordField(&#x27;Password&#x27;, validators=[DataRequired() , Length(8, 128)])</span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form = LoginForm(username=<span class="string">&#x27;&#x27;</span>, password=<span class="string">&#x27;123&#x27;</span>)</span></span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form.data</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">表单数据字典</span></span><br><span class="line">&#123;&#x27;username&#x27;: &#x27;&#x27;, &#x27;password&#x27;: &#x27;123&#x27;&#125;</span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form.validate()</span></span><br><span class="line">False &gt;&gt;&gt; form.errors</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">错误消息字典</span></span><br><span class="line">&#123;&#x27;username&#x27;: [u&#x27;This field is required.&#x27;], &#x27;password&#x27;: [u&#x27;Field must be at least 6 characters long.&#x27;]&#125;</span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form2 = LoginForm(username=<span class="string">&#x27;greyli&#x27;</span>, password=<span class="string">&#x27;123456&#x27;</span>)</span></span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form2.data</span></span><br><span class="line">&#123;&#x27;username&#x27;: &#x27;greyli&#x27;, &#x27;password&#x27;: &#x27;123456&#x27;&#125;</span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form2.validate()</span></span><br><span class="line">True</span><br><span class="line"><span class="meta prompt_">&gt;</span><span class="language-bash">&gt;&gt; form2.errors &#123;&#125;</span></span><br></pre></td></tr></table></figure><ol start="3"><li>在视图函数中验证表单<br>因为现在的basic_form视图同时接收两种类型的请求：GET请求和POST请求。所以我们要根据请求方法的不同执行不同的代码。具体来说：首先是实例化表单，如果是GET请求，那么就渲染模板；如果是 POST请求，就调用validate()方法验证表单数据。</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/basic&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">basic</span>():</span><br><span class="line">    form = LoginForm()</span><br><span class="line">    <span class="keyword">if</span> form.validate_on_submit():</span><br><span class="line">        username = form.username.data</span><br><span class="line">        flash(<span class="string">&#x27;Welcome home, %s!&#x27;</span> % username)</span><br><span class="line">        <span class="keyword">return</span> redirect(url_for(<span class="string">&#x27;index&#x27;</span>))</span><br><span class="line">    <span class="keyword">return</span> render_template(<span class="string">&#x27;login.html&#x27;</span>, form=form)</span><br></pre></td></tr></table></figure><h4 id="在模板中渲染错误消息"><a href="#在模板中渲染错误消息" class="headerlink" title="在模板中渲染错误消息"></a>在模板中渲染错误消息</h4><p>如果form.validate_on_submit()返回False，那么说明验证没有通 过。对于验证未通过的字段，WTForms会把错误消息添加到表单类的 errors属性中，这是一个匹配作为表单字段的类属性到对应的错误消息 列表的字典。我们一般会直接通过字段名来获取对应字段的错误消息列表，即<code>&quot;form.字段名.errors&quot;</code>。比如，form.name.errors返回name字段的错 误消息列表。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</span>&gt;</span></span><br><span class="line">    &#123;&#123; form.csrf_token &#125;&#125;</span><br><span class="line">    &#123;&#123; form.username.label &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;&#123; form.username() &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;% for message in form.username.errors %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">small</span> <span class="attr">class</span>=<span class="string">&quot;error&quot;</span>&gt;</span>&#123;&#123; message &#125;&#125;<span class="tag">&lt;/<span class="name">small</span>&gt;</span><span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;% endfor %&#125; &#123;&#123; form.password.label &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;&#123; form.password &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;% for message in form.password.errors %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">small</span> <span class="attr">class</span>=<span class="string">&quot;error&quot;</span>&gt;</span>&#123;&#123; message &#125;&#125;<span class="tag">&lt;/<span class="name">small</span>&gt;</span><span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;% endfor %&#125;</span><br><span class="line">    &#123;&#123; form.remember &#125;&#125;&#123;&#123; form.remember.label &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">    &#123;&#123; form.submit &#125;&#125;<span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="第5章-数据库-重点"><a href="#第5章-数据库-重点" class="headerlink" title="第5章 数据库(重点)"></a>第5章 数据库(重点)</h2><p>数据库是大多数动态Web程序的基础设施，只要你想把数据存储下来，就离不开数据库。我们这里提及的数据库（Database）指的是由存储数据的单个或多个文件组成的集合，它是一种容器，可以类比为文件柜。而人们通常使用数据库来表示操作数据库的软件，这类管理数据库的软件被称为数据库管理系统（DBMS，Database Management System），常见的DBMS有MySQL、PostgreSQL、SQLite、MongoDB等。为了便于理解，我们可以把数据库看作一个大仓库，仓库里有一些负责搬运货物（数据）的机器人，而DBMS就是操控机器人搬运货物的程序。</p><h3 id="数据库的分类"><a href="#数据库的分类" class="headerlink" title="数据库的分类"></a>数据库的分类</h3><p>数据库一般分为两种，SQL（Structured Query Language，结构化查 询语言）数据库和NoSQL（Not Only SQL，泛指非关系型）数据库。</p><h4 id="SQL"><a href="#SQL" class="headerlink" title="SQL"></a>SQL</h4><p>SQL数据库指关系型数据库，常用的SQL DBMS主要包括SQL Server、Oracle、MySQL、PostgreSQL、SQLite等。关系型数据库使用表来定义数据对象，不同的表之间使用关系连接。</p><p>在SQL数据库中，每一行代表一条记录（record），每条记录又由不同的列（column）组成。在存储数据前，需要预先定义表模式（schema），以定义表的结构并限定列的输入数据类型。<br>为了避免在措辞上引起误解，我们先了解几个基本概念：</p><ul><li>表（table）：存储数据的特定结构。</li><li>模式（schema）：定义表的结构信息。</li><li>列&#x2F;字段（column&#x2F;field）：表中的列，存储一系列特定的数据，列组成表。</li><li>行&#x2F;记录（row&#x2F;record）：表中的行，代表一条记录。</li><li>标量（scalar）：指的是单一数据，与之相对的是集合 （collection）。</li></ul><h4 id="NoSQL"><a href="#NoSQL" class="headerlink" title="NoSQL"></a>NoSQL</h4><p>NoSQL最初指No SQL或No Relational，现在NoSQL社区一般会解释为Not Only SQL。NoSQL数据库泛指不使用传统关系型数据库中的表格形式的数据库。近年来，NoSQL数据库越来越流行，被大量应用在实时（real-time）Web程序和大型程序中。与传统的SQL数据库相比，它在速度和可扩展性方面有很大的优势，除此之外还拥有无模式（schema- free）、分布式、水平伸缩（horizontally scalable）等特点。</p><p>最常用的两种NoSQL数据库如下所示：</p><ol><li>文档存储（document store）<br>文档存储是NoSQL数据库中最流行的种类，它可以作为主数据库使用。文档存储使用的文档类似SQL数据库中的记录，文档使用类JSON格式来表示数据。常见的文档存储DBMS有MongoDB、CouchDB等。</li><li>键值对存储（key-value store）<br>键值对存储在形态上类似Python中的字典，通过键来存取数据，在读取上非常快，通常用来存储临时内容，作为缓存使用。常见的键值对 DBMS有Redis、Riak等，其中Redis不仅可以管理键值对数据库，还可以作为缓存后端（cache backend）和消息代理（message broker）。<br>另外，还有列存储（column store，又被称为宽列式存储）、图存储（graph store）等类型的NoSQL数据库，这里不再展开介绍。</li></ol><h3 id="ORM魔法"><a href="#ORM魔法" class="headerlink" title="ORM魔法"></a>ORM魔法</h3><p>在Web应用里使用原生SQL语句操作数据库主要存在下面两类问题：</p><ul><li>手动编写SQL语句比较乏味，而且视图函数中加入太多SQL语句会降低代码的易读性。另外还会容易出现安全问题，比如SQL注入。</li><li>常见的开发模式是在开发时使用简单的SQLite，而在部署时切换 到MySQL等更健壮的DBMS。但是对于不同的DBMS，我们需要使用不同的Python接口库，这让DBMS的切换变得不太容易。</li></ul><p>尽管使用ORM可以避免SQL注入问题，但你仍然需要对传入的查询参数进行验证。<br>另外，在执行原生SQL语句时也要注意避免使用字符串 拼接或字符串格式化的方式传入参数。<br>使用ORM可以很大程度上解决这些问题。它会自动帮你处理查询 参数的转义，尽可能地避免SQL注入的发生。<br>另外，它为不同的DBMS提供统一的接口，让切换工作变得非常简单。<br>ORM扮演翻译的角色，能够将我们的Python语言转换为DBMS能够读懂的SQL指令，让我们能 够使用Python来操控数据库。</p><p>尽管ORM非常方便，但如果你对SQL相当熟悉，那么自己编写SQL代码可以获得更大的灵活性和性能优势。<br>就像是使用IDE一样，ORM对初学者来说非常方便，但进阶以后你也许会想要自己掌控一切。<br>ORM把底层的SQL数据实体转化成高层的Python对象，这样一来， 你甚至不需要了解SQL，只需要通过Python代码即可完成数据库操作，ORM主要实现了三层映射关系：</p><ul><li>表→Python类。</li><li>字段（列）→类属性。</li><li>记录（行）→类实例。</li></ul><p>比如，我们要创建一个contacts表来存储留言，其中包含用户名称和电话号码两个字段。<br>在SQL中，下面的代码用来创建这个表，要向表中插入一条记录，需要使用下面的SQL语句：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> contacts(</span><br><span class="line">    name <span class="type">varchar</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    phone_number <span class="type">varchar</span>(<span class="number">32</span>),</span><br><span class="line">);</span><br><span class="line"><span class="comment">-- 插入一条记录</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> contacts(name, phone_number) <span class="keyword">VALUES</span>(<span class="string">&#x27;Grey Li&#x27;</span>, <span class="string">&#x27;12345678&#x27;</span>);</span><br></pre></td></tr></table></figure><p>如果使用ORM，我们可以使用类似下面的Python类来定义这个表：<br>使用ORM则只需要创建一个Contact类的实例，传入对应的参数表示各个列的数据即可。<br>下面的代码和使用上面的SQL语句效果相同：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> foo_orm <span class="keyword">import</span> Model, Column, String</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Contact</span>(<span class="title class_ inherited__">Model</span>):</span><br><span class="line">    __tablename__ = <span class="string">&#x27;contacts&#x27;</span></span><br><span class="line">    name = Column(String(<span class="number">100</span>), nullable=<span class="literal">False</span>)</span><br><span class="line">    phone_number = Column(String(<span class="number">32</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 插入一条记录</span></span><br><span class="line">contact = Contact(name=<span class="string">&#x27;Grey Li&#x27;</span>, phone_number=<span class="string">&#x27;12345678&#x27;</span>)</span><br></pre></td></tr></table></figure><p>除了便于使用，ORM还有下面这些优点：</p><ul><li>灵活性好。你既能使用高层对象来操作数据库，又支持执行原生 SQL语句。</li><li>提升效率。从高层对象转换成原生SQL会牺牲一些性能，但这微不足道的性能牺牲换取的是巨大的效率提升。</li><li>可移植性好。ORM通常支持多种DBMS，包括MySQL、PostgreSQL、Oracle、SQLite等。你可以随意更换DBMS，只需要稍微 改动少量配置。</li></ul><p>使用Python实现的ORM有SQLAlchemy、Peewee、PonyORM等。其中SQLAlchemy是Python社区使用最广泛的ORM之一，我们将介绍如何在Flask程序中使用它。SQL-Alchemy，直译过来就是SQL炼金术，下一节我们会见识到SQLAlchemy的神奇力量。</p><h3 id="使用Flask-SQLAlchemy管理数据库"><a href="#使用Flask-SQLAlchemy管理数据库" class="headerlink" title="使用Flask-SQLAlchemy管理数据库"></a>使用Flask-SQLAlchemy管理数据库</h3><p>扩展Flask-SQLAlchemy集成了SQLAlchemy，它简化了连接数据库服务器、管理数据库操作会话等各类工作，让Flask中的数据处理体验变得更加轻松。<br>下面在示例程序中实例化Flask-SQLAlchemy提供的SQLAlchemy类，传入程序实例app，以完成扩展的初始化。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> flask_sqlalchemy <span class="keyword">import</span> SQLAlchemy</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">app.jinja_env.trim_blocks = <span class="literal">True</span></span><br><span class="line">app.jinja_env.lstrip_blocks = <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">app.config[<span class="string">&#x27;SECRET_KEY&#x27;</span>] = os.getenv(<span class="string">&#x27;SECRET_KEY&#x27;</span>, <span class="string">&#x27;secret string&#x27;</span>)</span><br><span class="line"></span><br><span class="line">app.config[<span class="string">&#x27;SQLALCHEMY_DATABASE_URI&#x27;</span>] = os.getenv(</span><br><span class="line">    <span class="string">&#x27;DATABASE_URL&#x27;</span>, <span class="string">&#x27;sqlite:///&#x27;</span> + os.path.join(app.root_path, <span class="string">&#x27;data.db&#x27;</span>))</span><br><span class="line">app.config[<span class="string">&#x27;SQLALCHEMY_TRACK_MODIFICATIONS&#x27;</span>] = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">db = SQLAlchemy(app=app)</span><br></pre></td></tr></table></figure><h4 id="连接数据库服务器"><a href="#连接数据库服务器" class="headerlink" title="连接数据库服务器"></a>连接数据库服务器</h4><p>DBMS通常会提供数据库服务器运行在操作系统中。要连接数据库服务器，首先要为我们的程序指定数据库URI（Uniform Resource<br>Identifier，统一资源标识符）。数据库URI是一串包含各种属性的字符串，其中包含了各种用于连接数据库的信息。</p><p><img src="/memoirs/images/flask/w5.png" alt="常用的数据库URI格式"></p><p>在<code>Flask-SQLAlchemy</code>中，数据库的URI通过配置变量<code>SQLALCHEMY_DATABASE_URI</code>设置，默认为SQLite内存型数据库<code>(sqlite:///:memory:)</code>。<code>SQLite</code>是基于文件的<code>DBMS</code>，不需要设置数据库服务器，只需要指定数据库文件的绝对路径。</p><p>在生产环境下更换到其他类型的<code>DBMS</code>时，数据库URL会包含敏感 信息，所以这里优先从环境变量<code>DATABASE_URL</code>获取(注意这里为了便于理解使用了URL，而不是URI)。</p><p>安装并初始化<code>Flask-SQLAlchemy</code>后，启动程序时会看到命令行下有一行警告信息。这是因为<code>Flask-SQLAlchemy</code>建议你设置 <code>SQLALCHEMY_TRACK_MODIFICATIONS</code>配置变量，这个配置变量决定是否追踪对象的修改，这用于<code>Flask-SQLAlchemy</code>的事件通知系统。这 个配置键的默认值为None，如果没有特殊需要，我们可以把它设为False来关闭警告信息。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">app.config[<span class="string">&#x27;SQLALCHEMY_TRACK_MODIFICATIONS&#x27;</span>] = <span class="literal">False</span></span><br></pre></td></tr></table></figure><h4 id="定义数据库模型"><a href="#定义数据库模型" class="headerlink" title="定义数据库模型"></a>定义数据库模型</h4><p>用来映射到数据库表的Python类通常被称为数据库模型<br>(model)，一个数据库模型类对应数据库中的一个表。定义模型即使用Python类定义表模式，并声明映射关系。所有的模型类都需要继承<code>Flask-SQLAlchemy</code>提供的<code>db.Model</code>基类。本章的示例程序是一个笔记程序，笔记保存到数据库中，你可以通过程序查询、添加、更新和删除笔记。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Note</span>(db.Model):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    创建一个数据库Model</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    body = db.Column(db.Text)</span><br></pre></td></tr></table></figure><p>在上面的模型类中，表的字段（列）由db.Column类的实例表示，字段的类型通过Column类构造方法的第一个参数传入。在这个模型中，我们创建了一个类型为db.Integer的id字段和类型为db.Text的body列，分别存储整型和文本。</p><p><img src="/memoirs/images/flask/w6.png" alt="SQLAlchemy常用的字段类型"></p><p>字段类型一般直接声明即可，如果需要传入参数，你也可以添加括号。对于类似String的字符串列，有些数据库会要求限定长度，因此最 好为其指定长度。虽然使用Text类型可以存储相对灵活的变长文本，但从性能上考虑，我们仅在必须的情况下使用Text类型，比如用户发表的文章和评论等不限长度的内容。</p><p>一般情况下，字段的长度是由程序设计者自定的。尽管如此，也有一些既定的约束标准，比如姓名（英语）的长度一般不超过70个字符，中文名一般不超过20个字符，电子邮件地址的长度不超过254个字符，虽然各主流浏览器支持长达2048个字符的URL，但在网站中用户资料设置的限度一般为255。尽管如此，对于超过一定长度的Email和URL，比如20个字符，会在显示时添加省略号的形式。显示的用户名（username）允许重复，通常要短一些，以不超过36个字符为佳。当然，在程序中，你可以根据需要来自由设定这些限制值。</p><p><img src="/memoirs/images/flask/w7.png" alt="常用的SQLAlchemy字段参数"></p><h4 id="创建数据库和表"><a href="#创建数据库和表" class="headerlink" title="创建数据库和表"></a>创建数据库和表</h4><p>如果把数据库（文件）看作一个仓库，为了方便取用，我们需要把货物按照类型分别放置在不同货架上，这些货架就是数据库中的表。创建模型类后，我们需要手动创建数据库和对应的表，也就是我们常说的建库和建表。这通过对我们的db对象调用create_all()方法实现。</p><h3 id="数据库操作"><a href="#数据库操作" class="headerlink" title="数据库操作"></a>数据库操作</h3><p>现在我们创建了模型，也生成了数据库和表，是时候来学习常用的数据库操作了。数据库操作主要是<code>CRUD</code>，即Create（创建）、Read（读取&#x2F;查询）、Update（更新）和Delete（删除）。<br><code>SQLAlchemy</code>使用数据库会话来管理数据库操作，这里的数据库会话也称为事务(<code>transaction</code>)。<code>Flask-SQLAlchemy</code>自动帮我们创建会话，可以通过<code>db.session</code>属性获取。<br>数据库中的会话代表一个临时存储区，你对数据库做出的改动都会存放在这里。你可以调用<code>add()</code>方法将新创建的对象添加到数据库会话中，或是对会话中的对象进行更新。只有当你对数据库会话对象调用<code>commit()</code>方法时，改动才被提交到数据库，这确保了数据提交的一致性。另外，数据库会话也支持回滚操作。当你对会话调用<code>rollback()</code>方法时，添加到会话中且未提交的改动都将被撤销。</p><h4 id="CRUD"><a href="#CRUD" class="headerlink" title="CRUD"></a>CRUD</h4><p>这一节我们会在<code>Python Shell</code>中演示<code>CRUD</code>操作。默认情况下，<code>Flask-SQLAlchemy</code>(&gt;&#x3D;2.3.0版本)会自动为模型类生成一个<code>__repr__()</code>方法。当在<code>Python Shell</code>中调用模型的对象时，<code>__repr__()</code>方法会返回一条类似“&lt;模型类名主键值&gt;”的字符串，比如<code>&lt;Note&gt;</code>。</p><ol><li><p>Create<br>添加一条新记录到数据库主要分为三步：</p><ul><li>创建Python对象（实例化模型类）作为一条记录。</li><li>添加新创建的记录到数据库会话。</li><li>提交数据库会话。</li></ul></li><li><p>Read<br>我们已经知道了如何向数据库里添加记录，那么如何从数据库里取回数据呢？使用模型类提供的query属性附加调用各种过滤方法及查询方法可以完成这个任务。</p></li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;模型类&gt;.query.&lt;过滤方法&gt;.&lt;查询方法&gt;</span><br></pre></td></tr></table></figure><p>从某个模型类出发，通过在query属性对应的Query对象上附加的过滤方法和查询函数对模型类对应的表中的记录进行各种筛选和调整，最终返回包含对应数据库记录数据的模型类实例，对返回的实例调用属性即可获取对应的字段数据。</p><p><img src="/memoirs/images/flask/w8.png" alt="常用的SQLAlchemy查询方法"></p><p>精确的查询，比如获取指定字段值的记录。对模型类的query属性存储的Query对象调用过滤方法将返回一个更精确的Query对象(后面我们简称为查询对象)。因为每个过滤方法都会返回新的查询对象，所以过滤器可以叠加使用。在查询对象上调用前面介绍的查询方法，即可获得一个包含过滤后的记录的列表。</p><p><img src="/memoirs/images/flask/w9.png" alt="常用的SQLAlchemy过滤方法"></p><ol start="3"><li><p>Update<br>更新一条记录非常简单，直接赋值给模型类的字段属性就可以改变 字段值，然后调用commit()方法提交会话即可。<br>只有要插入新的记录或要将现有的记录添加到会话中时才需要使用 add()方法，单纯要更新现有的记录时只需要直接为属性赋新值，然 后提交会话。</p></li><li><p>Delete<br>删除记录和添加记录很相似，不过要把add()方法换成delete() 方法，最后都需要调用commit()方法提交修改。</p></li></ol><h4 id="在视图函数里操作数据库"><a href="#在视图函数里操作数据库" class="headerlink" title="在视图函数里操作数据库"></a>在视图函数里操作数据库</h4><p>在视图函数里操作数据库的方式和我们在<code>Python Shell中</code>的练习大致相同，只不过需要一些额外的工作。比如把查询结果作为参数传入模板渲染出来，或是获取表单的字段值作为提交到数据库的数据。在这一节，我们将把上一节学习的所有数据库操作知识运用到一个简单的笔记程序中。这个程序可以让你创建、编辑和删除笔记，并在主页列出所有保存后的笔记。</p><ol><li>Create</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/new&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">new_note</span>():</span><br><span class="line">    form = NewNoteForm()</span><br><span class="line">    <span class="keyword">if</span> form.validate_on_submit():</span><br><span class="line">        body = form.body.data</span><br><span class="line">        note = Note(body=body)</span><br><span class="line">        db.session.add(note)</span><br><span class="line">        db.session.commit()</span><br><span class="line">        flash(<span class="string">&#x27;Your note is saved.&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> redirect(url_for(<span class="string">&#x27;index&#x27;</span>))</span><br><span class="line">    <span class="keyword">return</span> render_template(<span class="string">&#x27;new_note.html&#x27;</span>, form=form)</span><br></pre></td></tr></table></figure><ol start="2"><li>Read</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/note/all&#x27;</span></span>)</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">index</span>():</span><br><span class="line">    form = DeleteNoteForm()</span><br><span class="line">    notes = Note.query.<span class="built_in">all</span>()</span><br><span class="line">    <span class="keyword">return</span> render_template(<span class="string">&#x27;index.html&#x27;</span>, notes=notes, form=form)</span><br></pre></td></tr></table></figure><ol start="3"><li>Update</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/edit/&lt;int:note_id&gt;&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">edit_note</span>(<span class="params">note_id</span>):</span><br><span class="line">    form = EditNoteForm()</span><br><span class="line">    note = Note.query.get(note_id)</span><br><span class="line">    <span class="keyword">if</span> form.validate_on_submit():</span><br><span class="line">        note.body = form.body.data</span><br><span class="line">        db.session.commit()</span><br><span class="line">        flash(<span class="string">&#x27;Your note is updated.&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> redirect(url_for(<span class="string">&#x27;index&#x27;</span>))</span><br><span class="line">    form.body.data = note.body  <span class="comment"># preset form input&#x27;s value</span></span><br><span class="line">    <span class="keyword">return</span> render_template(<span class="string">&#x27;edit_note.html&#x27;</span>, form=form)</span><br></pre></td></tr></table></figure><ol start="4"><li>Delete</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/delete/&lt;int:note_id&gt;&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">delete_note</span>(<span class="params">note_id</span>):</span><br><span class="line">    form = DeleteNoteForm()</span><br><span class="line">    <span class="keyword">if</span> form.validate_on_submit():</span><br><span class="line">        note = Note.query.get(note_id)</span><br><span class="line">        db.session.delete(note)</span><br><span class="line">        db.session.commit()</span><br><span class="line">        flash(<span class="string">&#x27;Your note is deleted.&#x27;</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        abort(<span class="number">400</span>)</span><br><span class="line">    <span class="keyword">return</span> redirect(url_for(<span class="string">&#x27;index&#x27;</span>))</span><br></pre></td></tr></table></figure><h3 id="定义关系"><a href="#定义关系" class="headerlink" title="定义关系"></a>定义关系</h3><p>在关系型数据库中，我们可以通过关系让不同表之间的字段建立联系。一般来说，定义关系需要两步，分别是创建外键和定义关系属性。在更复杂的多对多关系中，我们还需要定义关联表来管理关系。这一节我们会学习如何使用<code>SQLAlchemy</code>在模型之间建立几种基础的关系模 式。</p><h4 id="配置Python-Shell上下文"><a href="#配置Python-Shell上下文" class="headerlink" title="配置Python Shell上下文"></a>配置Python Shell上下文</h4><p>在上面的许多操作中，每一次使用<code>flask shell</code>命令启动<code>Python Shell</code>后都要从app模块里导入db对象和相应的模型类。为什么不把它们自动 集成到<code>Python Shell</code>上下文里呢？就像Flask内置的app对象一样。这当然可以实现！我们可以使用<code>app.shell_context_processor</code>装饰器注册一个shell上下文处理函数。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># handlers</span></span><br><span class="line"><span class="meta">@app.shell_context_processor</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">make_shell_context</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">dict</span>(db=db, Note=Note)</span><br></pre></td></tr></table></figure><h4 id="一对多"><a href="#一对多" class="headerlink" title="一对多"></a>一对多</h4><p>我们将以作者和文章来演示一对多关系：一个作者可以写作多篇文章。</p><p><img src="/memoirs/images/flask/w10.png" alt="一对多示意图"></p><p>Author类用来表示作者，Article类用来表示文章</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Author</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">70</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    phone = db.Column(db.String(<span class="number">20</span>))</span><br><span class="line">    articles = db.relationship(<span class="string">&#x27;Article&#x27;</span>)  <span class="comment"># collection</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Article</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    title = db.Column(db.String(<span class="number">50</span>), index=<span class="literal">True</span>)</span><br><span class="line">    body = db.Column(db.Text)</span><br><span class="line">    author_id = db.Column(db.Integer, db.ForeignKey(<span class="string">&#x27;author.id&#x27;</span>))</span><br></pre></td></tr></table></figure><p>我们将在这两个模型之间建立一个简单的一对多关系，建立这个一对多关系的目的是在表示作者的Author类中添加一个关系属性articles，<br>作为集合（collection）属性，当我们对特定的Author对象调用articles属性会返回所有相关的Article对象。我们会在下面介绍如何一步步定义这个一对多关系。</p><ol><li><p>定义外键<br>定义关系的第一步是创建外键。外键是（foreign key）用来在A表存储B表的主键值以便和B表建立联系的关系字段。因为外键只能存储单一数据（标量），所以外键总是在“多”这一侧定义，多篇文章属于同一个作者，所以我们需要为每篇文章添加外键存储作者的主键值以指向对应的作者。在Article模型中，我们定义一个author_id字段作为外键.</p></li><li><p>定义关系属性<br>定义关系的第二步是使用关系函数定义关系属性。关系属性在关系 的出发侧定义，即一对多关系的“一”这一侧。一个作者拥有多篇文章， 在Author模型中，我们定义了一个articles属性来表示对应的多篇文章</p></li><li><p>建立关系<br>建立关系有两种方式，第一种方式是为外键字段赋值，另一种方式是通过操作关系属性，将关系属性赋给实际的对象即可建立关系。</p></li></ol><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1.外键字段赋值</span></span><br><span class="line">spam.author_id = <span class="number">1</span></span><br><span class="line">db.session.commit()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2.关系属性赋给实际的对象</span></span><br><span class="line">foo.articles.append(spam)</span><br><span class="line">foo.articles.append(ham)</span><br><span class="line">db.session.commit()</span><br></pre></td></tr></table></figure><p><img src="/memoirs/images/flask/w11.png" alt="常用的SQLAlchemy关系函数参数"></p><p><img src="/memoirs/images/flask/w12.png" alt="常用的SQLAlchemy关系记录加载方式（lazy参数可选值）"></p><ol start="4"><li>建立双向关系<br>我们在Author类中定义了集合关系属性articles，用来获取某个作者 拥有的多篇文章记录。在某些情况下，你也许希望能在Article类中定义 一个类似的author关系属性，当被调用时返回对应的作者记录，这类返 回单个值的关系属性被称为标量关系属性。而这种两侧都添加关系属性 获取对方记录的关系我们称之为双向关系（bidirectional relationship）。</li></ol><p>双向关系并不是必须的，但在某些情况下会非常方便。双向关系的 建立很简单，通过在关系的另一侧也创建一个relationship()函数，我 们就可以在两个表之间建立双向关系。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># one to many + bidirectional relationship</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Writer</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">64</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    books = db.relationship(<span class="string">&#x27;Book&#x27;</span>, back_populates=<span class="string">&#x27;writer&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Book</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">50</span>), index=<span class="literal">True</span>)</span><br><span class="line">    writer_id = db.Column(db.Integer, db.ForeignKey(<span class="string">&#x27;writer.id&#x27;</span>))</span><br><span class="line">    writer = db.relationship(<span class="string">&#x27;Writer&#x27;</span>, back_populates=<span class="string">&#x27;books&#x27;</span>)</span><br></pre></td></tr></table></figure><ol start="5"><li>使用backref简化关系定义<br>在介绍关系函数的参数时，我们曾提到过，使用关系函数中的 backref参数可以简化双向关系的定义。以一对多关系为例，backref参数<br>用来自动为关系另一侧添加关系属性，作为反向引用（back reference），赋予的值会作为关系另一侧的关系属性名称。比如，我们 在Author一侧的关系函数中将backref参数设为author，SQLAlchemy会自 动为Article类添加一个author属性。</li></ol><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 尽管使用backref非常方便，但通常来说“显式好过隐式”，所以我们 应该尽量使用back_populates定义双向关系。</span></span><br><span class="line"><span class="comment"># 为了便于理解，将使用back_populates来建立双向关系。</span></span><br><span class="line"><span class="comment"># one to many + bidirectional relationship + use backref to declare bidirectional relationship</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Singer</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">70</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    songs = db.relationship(<span class="string">&#x27;Song&#x27;</span>, backref=<span class="string">&#x27;singer&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Song</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">50</span>), index=<span class="literal">True</span>)</span><br><span class="line">    singer_id = db.Column(db.Integer, db.ForeignKey(<span class="string">&#x27;singer.id&#x27;</span>))</span><br></pre></td></tr></table></figure><p>在定义集合属性songs的关系函数中，我们将backref参数设为 singer，这会同时在Song类中添加了一个singer标量属性。这时我们仅需 要定义一个关系函数，虽然singer是一个“看不见的关系属性”，但在使用上和定义两个关系函数并使用back_populates参数的效果完全相同。需要注意的是，使用backref允许我们仅在关系一侧定义另一侧的关 系属性，但是在某些情况下，我们希望可以对在关系另一侧的关系属性进行设置，这时就需要使用backref()函数。backref()函数接收第一 个参数作为在关系另一侧添加的关系属性名，其他关键字参数会作为关 系另一侧关系函数的参数传入。比如，我们要在关系另一侧“看不见的 relationship()函数”中将uselist参数设为False。</p><h4 id="多对一"><a href="#多对一" class="headerlink" title="多对一"></a>多对一</h4><p>一对多关系反过来就是多对一关系，这两种关系模式分别从不同的视角出发。一个作者拥有多篇文章，反过来就是多篇文章属于同一个作者。为了便于区分，我们使用居民和城市来演示多对一关系：多个居民居住在同一个城市。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># many to one</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Citizen</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">70</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    city_id = db.Column(db.Integer, db.ForeignKey(<span class="string">&#x27;city.id&#x27;</span>))</span><br><span class="line">    city = db.relationship(<span class="string">&#x27;City&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">City</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">30</span>), unique=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>这时定义的city关系属性是一个标量属性（返回单一数据）。当Citizen.city被调用时，SQLAlchemy会根据外键字段city_id存储的值查找对应的City对象并返回，即居民记录对应的城市记录。<br>当建立双向关系时，如果不使用backref，那么一对多和多对一关系模式在定义上完全相同，这时可以将一对多和多对一视为同一种关系模式。在后面我们通常都会为一对多或多对一建立双向关系，这时将弱化这两种关系的区别，一律称为一对多关系。</p><h4 id="一对一"><a href="#一对一" class="headerlink" title="一对一"></a>一对一</h4><p>我们将使用国家和首都来演示一对一关系：每个国家只有一个首 都；反过来说，一个城市也只能作为一个国家的首都。</p><p>Country类表示国家，Capital类表示首都。建立一对一关系后，我们将在Country类中创建一个标量关系属性capital，调用它会获取单个Capital对象；我们还将在Capital类中创建一个标量关系属性country，调用它会获取单个的Country对象。</p><p>一对一关系实际上是通过建立双向关系的一对多关系的基础上转化而来。我们要确保关系两侧的关系属性都是标量属性，都只返回单个值，所以要在定义集合属性的关系函数中将uselist参数设为False，这时一对多关系将被转换为一对一关系。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># one to one</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Country</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">30</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    capital = db.relationship(<span class="string">&#x27;Capital&#x27;</span>, uselist=<span class="literal">False</span>)  <span class="comment"># collection -&gt; scalar</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Capital</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">30</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    country_id = db.Column(db.Integer, db.ForeignKey(<span class="string">&#x27;country.id&#x27;</span>))</span><br><span class="line">    country = db.relationship(<span class="string">&#x27;Country&#x27;</span>)  <span class="comment"># scalar</span></span><br></pre></td></tr></table></figure><h4 id="多对多"><a href="#多对多" class="headerlink" title="多对多"></a>多对多</h4><p>我们将使用学生和老师来演示多对多关系：每个学生有多个老师，而每个老师有多个学生。</p><p>Student类表示学生，Teacher类表示老师。在这两个模型之间建立多对多关系后，我们需要在Student类中添加一个集合关系属性teachers，调用它可以获取某个学生的多个老师，而不同的学生可以和同一个老师建立关系。</p><p>在一对多关系中，我们可以在“多”这一侧添加外键指向“一”这一 侧，外键只能存储一个记录，但是在多对多关系中，每一个记录都可以与关系另一侧的多个记录建立关系，关系两侧的模型都需要存储一组外键。在SQLAlchemy中，要想表示多对多关系，除了关系两侧的模型外，我们还需要创建一个关联表（association table）。关联表不存储数据，只用来存储关系两侧模型的外键对应关系。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">70</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    grade = db.Column(db.String(<span class="number">20</span>))</span><br><span class="line">    teachers = db.relationship(<span class="string">&#x27;Teacher&#x27;</span>,</span><br><span class="line">                               secondary=association_table,</span><br><span class="line">                               back_populates=<span class="string">&#x27;students&#x27;</span>)  <span class="comment"># collection</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Teacher</span>(db.Model):</span><br><span class="line">    <span class="built_in">id</span> = db.Column(db.Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    name = db.Column(db.String(<span class="number">70</span>), unique=<span class="literal">True</span>)</span><br><span class="line">    office = db.Column(db.String(<span class="number">20</span>))</span><br><span class="line">    students = db.relationship(<span class="string">&#x27;Student&#x27;</span>,</span><br><span class="line">                               secondary=association_table,</span><br><span class="line">                               back_populates=<span class="string">&#x27;teachers&#x27;</span>)  <span class="comment"># collection</span></span><br></pre></td></tr></table></figure><p>关联表使用db.Table类定义，传入的第一个参数是关联表的名称。我们在关联表中定义了两个外键字段：teacher_id字段存储Teacher类的主键，student_id存储Student类的主键。借助关联表这个中间人存储的外键对，我们可以把多对多关系分化成两个一对多关系，如图所示。</p><p><img src="/memoirs/images/flask/w13.png" alt="关联表示意图"></p><h3 id="更新数据库表"><a href="#更新数据库表" class="headerlink" title="更新数据库表"></a>更新数据库表</h3><p>模型类（表）不是一成不变的，当你添加了新的模型类，或是在模 型类中添加了新的字段，甚至是修改了字段的名称或类型，都需要更新 表。在前面我们把数据库表类比成盛放货物的货架，这些货架是固定生 成的。当我们在操控程序（DBMS&#x2F;ORM）上变更了货架的结构时，仓 库的货架也要根据变化相应进行调整。而且，当货架的结构产生变动 时，我们还需要考虑如何处理货架上的货物（数据）。</p><h4 id="重新生成表"><a href="#重新生成表" class="headerlink" title="重新生成表"></a>重新生成表</h4><p>重新调用create_all()方法并不会起到更新表或重新创建表的作 用。如果你并不在意表中的数据，最简单的方法是使用drop_all()方法删除表以及其中的数据，然后再使用create_all()方法重新创建</p><h4 id="使用Flask-Migrate迁移数据库"><a href="#使用Flask-Migrate迁移数据库" class="headerlink" title="使用Flask-Migrate迁移数据库"></a>使用Flask-Migrate迁移数据库</h4><p>在开发时，以删除表再重建的方式更新数据库简单直接，但明显的缺陷是会丢掉数据库中的所有数据。在生产环境下，你绝对不会想让数 据库里的数据都被删除掉，这时你需要使用数据库迁移工具来完成这个工作。<br>SQLAlchemy的开发者Michael Bayer写了一个数据库迁移工具 ——Alembic来帮助我们实现数据库的迁移，数据库迁移工具可以在不破坏数据的情况下更新数据库表的结构。蒸馏器（Alembic）是炼金术士最重要的工具，要学习SQL炼金术（SQLAlchemy），我们当然要掌 握蒸馏器的使用。</p><p>我们实例化Flask-Migrate提供的Migrate类，进行初始化操作<br>实例化Migrate类时，除了传入程序实例app，还需要传入实例化 Flask-SQLAlchemy提供的SQLAlchemy类创建的db对象作为第二个参数。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask <span class="keyword">from</span> flask_sqlalchemy</span><br><span class="line"><span class="keyword">import</span> SQLAlchemy <span class="keyword">from</span> flask_migrate</span><br><span class="line"><span class="keyword">import</span> Migrate</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">...</span><br><span class="line">db = SQLAlchemy(app)</span><br><span class="line">migrate = Migrate(app, db)</span><br><span class="line"><span class="comment"># 在db对象创建后调用</span></span><br></pre></td></tr></table></figure><ol><li>创建迁移环境<br>在开始迁移数据之前，需要先使用下面的命令创建一个迁移环境<br>迁移环境只需要创建一次。这会在你的项目根目录下创建一个migrations文件夹，其中包含了自动生成的配置文件和迁移版本文件夹。</li></ol><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flask db init</span><br></pre></td></tr></table></figure><ol start="2"><li>生成迁移脚本</li></ol><p>使用migrate子命令可以自动生成迁移脚本：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">flask db migrate -m <span class="string">&quot;add note timestamp&quot;</span></span></span><br><span class="line">...</span><br><span class="line">INFO [alembic.autogenerate.compare] Detected added column &#x27;message.timestamp Generating /Path/to/your/database/migrations/versions/c52a02014635_add note_timestamp.py</span><br><span class="line">...</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>这条命令可以简单理解为在flask里对数据库（db）进行迁移 （migrate）。-m选项用来添加迁移备注信息。从上面的输出信息我们可以看到，Alembic检测出了模型的变化：表note新添加了一个timestamp列，并且相应生成了一个迁移脚本 c52a02014635_add_note_timestamp.py，</p><ol start="3"><li>更新数据库</li></ol><p>生成了迁移脚本后，使用upgrade子命令即可更新数据库</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">flask db upgrade</span></span><br><span class="line">...</span><br><span class="line">INFO [alembic.runtime.migration] Running upgrade -&gt; c52a02014635, add note timestamp</span><br></pre></td></tr></table></figure><p>如果还没有创建数据库和表，这个命令会自动创建；如果已经创建，则会在不损坏数据的前提下执行更新。</p><h4 id="开发时是否需要迁移"><a href="#开发时是否需要迁移" class="headerlink" title="开发时是否需要迁移"></a>开发时是否需要迁移</h4><p>在生产环境下，当对数据库结构进行修改后，进行数据库迁移是必要的。因为你不想损坏任何数据，毕竟数据是无价的。在生成自动迁移脚本后，执行更新之前，对迁移脚本进行检查，甚至是使用备份的数据库进行迁移测试，都是有必要的。</p><p>而在开发环境中，你可以按需要选择是否进行数据迁移。对于大多数程序来说，我们可以在开发时使用虚拟数据生成工具来生成虚拟数据，从而避免手动创建记录进行测试。这样每次更改表结构时，可以直接清除后重新生成，然后生成测试数据，这要比执行一次迁移简单很多（在后面我们甚至会学习通过一条命令完成所有工作），除非生成虚拟数据耗费的时间过长。</p><p>另外，在本地开发时通常使用SQLite作为数据库引擎。SQLite不支持ALTER语句，而这正是迁移工具依赖的工作机制。也就是说，当SQLite数据库表的字段删除或修改后，我们没法直接使用迁移工具进行更新，你需要手动添加迁移代码来进行迁移。在开发中，修改和删除列是很常见的行为，手动操作迁移会花费太多的时间。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本来还有一章讲邮件的，但是邮件这部分太简单就不放在这里了。<br>基础篇（二）主要是讲数据库的知识，简单了解在<code>Flask</code>应用中使用数据库的方法，但数据库的内容还有很多，这里只是一个简单的介绍。<br>如果你想了解更多具体细节，<a href="http://docs.sqlalchemy.org/en/latest/orm/tutorial.html">SQLAlchemy提供的入门教程</a>是个起步的好地方。<br>另外，如果还不熟悉<code>SQL</code>，那么有必要去学习一下，掌握<code>SQL</code>可以让你更高效地使用<code>ORM</code>。</p><p>这里也没有介绍在<code>Flask</code>中使用文档型<code>NoSQL</code>数据库的过程。<br>以流行的<a href="https://www.mongodb.com/">MongoDB</a>为例，通过使用ODM（Object Document Mapper，对象文档映射），比如<a href="http://mongoengine.org/">MongoEngine</a>，或是对应的扩展<a href="https://github.com/MongoEngine/flask-mongoengine">Flask- MongoEngine</a>，其操作数据库的方式和使用本章要介绍的<code>SQLAlchemy</code>基本相同。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20200321/python/Python-100-Flask-2/</id>
    <link href="https://minniexcode.github.io/memoirs/20200321/python/Python-100-Flask-2/"/>
    <published>2020-03-21T11:12:10.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前面介绍了Flask和HTTP的基础知识，下面会介绍Flask的基础用法</p>
<h2 id="第4章-表单"><a href="#第4章-表单" class="headerlink" title="第4章 表单"></a>第4章 表单</h2><p>在<code>Web</code>程序中，表单是和用户交互最常见的方式之一。用户注册、登录、撰写文章、编辑设置，无一不用到表单。不过，表单的处理却并不简单。<br>你不仅要创建表单，验证用户输入的内容，向用户显示错误提示，还要获取并保存数据。幸运的是，强大的<code>WTForms</code>可以帮我们解决这些问题。<br><code>WTForms</code>是一个使用<code>Python</code>编写的表单库，它使得表单的定 义、验证（服务器端）和处理变得非常轻松。这一章我们会介绍在Web 程序中处理表单的方法和技巧。</p>
<h3 id="使用Flask-WTF处理表单"><a href="#使用Flask-WTF处理表单" class="headerlink" title="使用Flask-WTF处理表单"></a>使用Flask-WTF处理表单</h3><p>扩展<code>Flask-WTF</code>集成了<code>WTForms</code>，使用它可以在<code>Flask</code>中更方便地使用<code>WTForms</code>。<code>Flask-WTF</code>将表单数据解析、<code>CSRF</code>保护、文件上传等功能与<code>Flask</code>集成，另外还附加了<code>reCAPTCHA</code>支持。</p>
<p>Flask-WTF默认为每个表单启用CSRF保护，它会为我们自动生成和 验证CSRF令牌。默认情况下，Flask-WTF使用程序密钥来对CSRF令牌 进行签名，所以我们需要为程序设置密钥：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">app.secret_key = <span class="string">&#x27;secret string&#x27;</span></span><br></pre></td></tr></table></figure>

<h4 id="定义WTForms表单类"><a href="#定义WTForms表单类" class="headerlink" title="定义WTForms表单类"></a>定义WTForms表单类</h4><p>当使用WTForms创建表单时，表单由Python类表示，这个类继承从 WTForms导入的Form基类。一个表单由若干个输入字段组成，这些字 段分别用表单类的类属性来表示（字段即Field，你可以简单理解为表单 内的输入框、按钮等部件）。下面定义了一个LoginForm类，最终会生 成我们在前面定义的HTML表单：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> wtforms <span class="keyword">import</span> Form, StringField, PasswordField, BooleanField, SubmitField</span><br><span class="line"><span class="keyword">from</span> wtforms.validators <span class="keyword">import</span> DataRequired, Length</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4.2.1 basic form example</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LoginForm</span>(<span class="title class_ inherited__">FlaskForm</span>):</span><br><span class="line">    username = StringField(<span class="string">&#x27;Username&#x27;</span>, validators=[DataRequired()])</span><br><span class="line">    password = PasswordField(<span class="string">&#x27;Password&#x27;</span>, validators=[DataRequired(), Length(<span class="number">8</span>, <span class="number">128</span>)])</span><br><span class="line">    remember = BooleanField(<span class="string">&#x27;Remember me&#x27;</span>)</span><br><span class="line">    submit = SubmitField(<span class="string">&#x27;Log in&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>每个字段属性通过实例化WTForms提供的字段类表示。字段属性的名称将作为对应HTML<code>&lt;input&gt;</code>元素的name属性及id属性值。</p>
<p><img src="/memoirs/images/flask/w1.png" alt="常用的WTForms字段"></p>
<p><img src="/memoirs/images/flask/w2.png" alt="实例化字段类常用参数"></p>
<p><img src="/memoirs/images/flask/w3.png" alt="常用的WTForms验证器"></p>
<p>当使用<code>Flask-WTF</code>定义表单时，我们仍然使用<code>WTForms</code>提供的字段类和验证器，创建的方式也完全相同，只不过表单类要继承<code>Flask-WTF</code>提供的<code>FlaskForm</code>类。<code>FlaskForm</code>类继承自<code>Form</code>类，进行了一些设置，并附加了一些辅助方法，以便与<code>Flask</code>集成。</p>]]>
    </summary>
    <title>Python-Flask基础篇(二)</title>
    <updated>2026-03-16T16:24:40.837Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Python" scheme="https://minniexcode.github.io/memoirs/tags/Python/"/>
    <category term="Flask" scheme="https://minniexcode.github.io/memoirs/tags/Flask/"/>
    <content>
      <![CDATA[<h2 id="第1章-初识Flask"><a href="#第1章-初识Flask" class="headerlink" title="第1章 初识Flask"></a>第1章 初识Flask</h2><h3 id="Flask介绍"><a href="#Flask介绍" class="headerlink" title="Flask介绍"></a>Flask介绍</h3><p>搭建开发环境，编写一个最小的Flask程序并运行它，了解 Flask基本知识</p><p>这一切开始于2010年4月1日，Armin Ronacher在网上发布了一篇关 于“下一代Python微框架”的介绍文章，文章里称这个Denied框架不依赖 Python标准库，只需要复制一份deny.py放到你的项目文件夹就可以开始 编程。伴随着一本正经的介绍、名人推荐语、示例代码和演示视频，这 个“虚假”的项目让不少人都信以为真。</p><p>5天后， <a href="http://flask.pocoo.org/">Flask</a>就从这么一个愚人节玩笑诞生了。</p><span id="more"></span><p>Flask是使用Python编写的Web微框架。Web框架可以让我们不用关 心底层的请求响应处理，更方便高效地编写Web程序。因为Flask核心简 单且易于扩展，所以被称作微框架（micro framework）。Flask有两个主 要依赖，一个是<a href="http://werkzeug.pocoo.org/">WSGI（Web Server Gateway Interface，Web服务器网关 接口）工具集——Werkzeug</a>，另一个是 <a href="http://jinja.pocoo.org/">Jinja2模板引擎</a>。Flask只保留了Web开发的核 心功能，其他的功能都由外部扩展来实现，比如数据库集成、表单认 证、文件上传等。如果没有合适的扩展，你甚至可以自己动手开发。 Flask不会替你做决定，也不会限制你的选择。总之，Flask可以变成任 何你想要的东西，一切都由你做主。</p><p>Flask（瓶子，烧瓶）的命名据说是对另一个Python Web框架—— Bottle的双关语&#x2F;调侃，即另一种容器（另一个Python Web框架）。 Werkzeug是德语单词“工具（tool）”，而Jinja指日本神社，因为神社 （庙）的英文temple与template（模板）相近而得名。</p><p>WSGI（Web Server Gateway Interface）是Python中用来规定Web服 务器如何与Python Web程序进行沟通的标准，在本书的第三部分将进行 详细介绍。</p><h3 id="Flask与MVC架构"><a href="#Flask与MVC架构" class="headerlink" title="Flask与MVC架构"></a>Flask与MVC架构</h3><p>你也许会困惑为什么用来处理请求并生成响应的函数被称为“视图 函数（view function）”，其实这个命名并不合理。在Flask中，这个命名 的约定来自Werkzeug，而Werkzeug中URL匹配的实现主要参考了 Routes（一个URL匹配库），再往前追溯，Routes的实现又参考了<a href="http://rubyonrails.org/">Ruby on Rails</a>。在Ruby on Rails中，术语views用来 表示MVC（Model-View-Controller，模型-视图-控制器）架构中的 View。</p><p>MVC架构最初是用来设计桌面程序的，后来也被用于Web程序，应 用了这种架构的Web框架有Django、Ruby on Rails等。在MVC架构中， 程序被分为三个组件：数据处理（Model）、用户界面（View）、交互 逻辑（Controller）。如果套用MVC架构的内容，那么Flask中视图函数 的名称其实并不严谨，使用控制器函数（Controller Function）似乎更合 适些，虽然它也附带处理用户界面。严格来说，Flask并不是MVC架构 的框架，因为它没有内置数据模型支持。为了方便表述，在本书中，使 用了<code>app.route()</code>装饰器的函数仍被称为视图函数，同时会使用“&lt;函数 名&gt;视图”（比如index视图）的形式来代指某个视图函数。</p><p>粗略归类，如果想要使用Flask来编写一个MVC架构的程序，那么 视图函数可以作为控制器（Controller），视图（View）则是我们将要介绍的使用Jinja2渲染的HTML模板，而模型（Model）可以使用其 他库来实现，在后面我们会介绍使用SQLAlchemy来创建数据库模型。</p><h3 id="本章小结"><a href="#本章小结" class="headerlink" title="本章小结"></a>本章小结</h3><p>Flask 基础其实很简单，这次主要是复习之前的知识，基础就不过多介绍了。<br>本章我们学习了Flask程序的运作方式和一些基本概念，这为我们进 一步学习打下了基础。<br>下面，我们会了解隐藏在Flask背后的重要角色 ——HTTP，并学习Flask是如何与之进行交互的。</p><h2 id="第2章-Flask与HTTP"><a href="#第2章-Flask与HTTP" class="headerlink" title="第2章 Flask与HTTP"></a>第2章 Flask与HTTP</h2><p>HTTP（Hypertext Transfer Protocol，超文本传输协议）定义了服务器和 客户端之间信息交流的格式和传递方式，它是万维网（World Wide Web）中数据交换的基础。</p><h3 id="请求响应循环"><a href="#请求响应循环" class="headerlink" title="请求响应循环"></a>请求响应循环</h3><p>为了更贴近现实，我们以一个真实的URL为例：</p><blockquote><p><a href="http://helloflask.com/hello">http://helloflask.com/hello</a></p></blockquote><p>当我们在浏览器中的地址栏中输入这个URL，然后按下Enter时，稍等片刻，浏览器会显示一个问候页面。<br>这背后到底发生了什么？你一定 可以猜想到，这背后也有一个web程序运行着。<br>它负责接收用户的请求，并把对应的内容返回给客户端，显示在用户的浏览 器上。<br>事实上，每一个Web应用都包含这种处理模式，即“请求-响应循 环（Request-Response Cycle）”：<br>客户端发出请求，服务器端处理请求 并返回响应，如图所示。</p><p><img src="/memoirs/images/python/2-1.png" alt="请求响应循环示意图"></p><p>这是每一个Web程序的基本工作模式，如果再进一步，这个模式又 包含着更多的工作单元，下图展示了一个Flask程序工作的实际流程。<br>从下图中可以看出，HTTP在整个流程中起到了至关重要的作用， 它是客户端和服务器端之间沟通的桥梁。</p><p><img src="/memoirs/images/python/2-2.png" alt="Flask Web程序工作流程"></p><p>当用户访问一个URL，浏览器便生成对应的HTTP请求，经由互联 网发送到对应的Web服务器。<br>Web服务器接收请求，通过WSGI将HTTP 格式的请求数据转换成我们的Flask程序能够使用的Python数据。<br>在程序中，Flask根据请求的URL执行对应的视图函数，获取返回值生成响应。<br>响应依次经过WSGI转换生成HTTP响应，再经由Web服务器传递，最终 被发出请求的客户端接收。<br>浏览器渲染响应中包含的HTML和CSS代 码，并执行JavaScript代码，最终把解析后的页面呈现在用户浏览器的窗口中。</p><h3 id="HTTP请求"><a href="#HTTP请求" class="headerlink" title="HTTP请求"></a>HTTP请求</h3><p>URL是一个请求的起源。不论服务器是运行在美国洛杉矶，还是运 行在我们自己的电脑上，当我们输入指向服务器所在地址的URL，都会 向服务器发送一个HTTP请求。一个标准的URL由很多部分组成，以下面这个URL为例：</p><blockquote><p><a href="http://helloflask.com/hello?name=Grey">http://helloflask.com/hello?name=Grey</a></p></blockquote><p>当我们在浏览器中访问这个URL时，随之产生的是一个发向<a href="http://helloflask.com/">http://helloflask.com</a>所在服务器的请求。<br>请求的实质是发送到服务器 上的一些数据，这种浏览器与服务器之间交互的数据被称为报文(message)，请求时浏览器发送的数据被称为请求报文(request message)，而服务器返回的数据被称为响应报文(response message)。</p><h4 id="请求报文"><a href="#请求报文" class="headerlink" title="请求报文"></a>请求报文</h4><p>请求报文由请求的方法、URL、协议版本、首部字段（header）以及内容实体组成。<br>报文由报文首部和报文主体组成，两者由空行分隔，请求报文的主 体一般为空。<br>如果URL中包含查询字符串，或是提交了表单，那么报文 主体将会是查询字符串和表单数据。<br>报文首部包含了请求的各种信息和设置，比如客户端的类型、是否 设置缓存、语言偏好等。</p><p><img src="/memoirs/images/python/2-3.png" alt="请求报文示意表"></p><h4 id="Request对象"><a href="#Request对象" class="headerlink" title="Request对象"></a>Request对象</h4><p>现在该让Flask的请求对象request出场了，这个请求对象封装了从客 户端发来的请求报文，我们能从它获取请求报文中的所有数据。</p><p>请求解析和响应封装实际上大部分是由Werkzeug完成的，Flask子 类化Werkzeug的请求（Request）和响应（Response）对象并添加了和程 序相关的特定功能。</p><p>当Flask接收到请求后，请求对 象会提供多个属性来获取URL的各个部分，常用的属性如下表所示。</p><p><img src="/memoirs/images/python/2-4.png" alt="使用request的属性获取请求URL"></p><p>除了URL，请求报文中的其他信息都可以通过request对象提供的属性和方法获取。</p><p><img src="/memoirs/images/python/2-5.png" alt="request对象常用的属性和方法"></p><h4 id="在Flask中处理请求"><a href="#在Flask中处理请求" class="headerlink" title="在Flask中处理请求"></a>在Flask中处理请求</h4><p>URL是指向网络上资源的地址。在Flask中，我们需要让请求的URL 匹配对应的视图函数，视图函数返回值就是URL对应的资源。</p><ol><li>路由匹配<br>为了便于将请求分发到对应的视图函数，程序实例中存储了一个路由表（app.url_map），其中定义了URL规则和视图函数的映射关系。<br>当请求发来后，Flask会根据请求报文中的URL（path部分）来尝试与这个 表中的所有URL规则进行匹配，调用匹配成功的视图函数。<br>如果没有找到匹配的URL规则，说明程序中没有处理这个URL的视图函数，Flask会 自动返回404错误响应（Not Found，表示资源未找到）。<br>你可以尝试在 浏览器中访问<a href="http://localhost:5000/nothing">http://localhost:5000/nothing</a> ，因为我们的程序中没有视图 函数负责处理这个URL，所以你会得到404响应。</li><li>设置监听的HTTP方法<br>通过flask routes命令打印出的路由列表可以看到，每一个 路由除了包含URL规则外，还设置了监听的HTTP方法。<br>GET是最常用 的HTTP方法，所以视图函数默认监听的方法类型就是GET，HEAD、 OPTIONS方法的请求由Flask处理，而像DELETE、PUT等方法一般不会在程序中实现，在后面我们构建Web API时才会用到这些方法。</li></ol><p>我们可以在app.route()装饰器中使用methods参数传入一个包含监 听的HTTP方法的可迭代对象。<br>比如，下面的视图函数同时监听GET请 求和POST请求：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/hello&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Hello, Flask!&lt;/h1&gt;&#x27;</span></span><br></pre></td></tr></table></figure><p>当某个请求的方法不符合要求时，请求将无法被正常处理。比如， 在提交表单时通常使用POST方法，而如果提交的目标URL对应的视图 函数只允许GET方法，这时Flask会自动返回一个405错误响应（Method Not Allowed，表示请求方法不允许）。<br>3. URL处理<br>从前面的路由列表中可以看到，除了&#x2F;hello，这个程序还包含许多 URL规则，比如和go_back端点对应的&#x2F;goback&#x2F;&lt;int：year&gt;。现在请尝试 访问<a href="http://localhost:5000/goback/34">http://localhost:5000/goback/34</a> ，在URL中加入一个数字作为时光倒 流的年数，你会发现加载后的页面中有通过传入的年数计算出的年 份：“Welcome to 1984！”。仔细观察一下，你会发现URL规则中的变量 部分有一些特别，&lt;int：year&gt;表示为year变量添加了一个int转换器， Flask在解析这个URL变量时会将其转换为整型。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;goback/&lt;int:year&gt;&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">go_back</span>(<span class="params">year</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;p&gt;Welcome to %d!&lt;/p&gt;&#x27;</span> % (<span class="number">2018</span> - year)</span><br></pre></td></tr></table></figure><h4 id="请求钩子"><a href="#请求钩子" class="headerlink" title="请求钩子"></a>请求钩子</h4><p>有时我们需要对请求进行预处理（preprocessing）和后处理 （postprocessing），这时可以使用Flask提供的一些请求钩子 （Hook），它们可以用来注册在请求处理的不同阶段执行的处理函数 （或称为回调函数，即Callback）。这些请求钩子使用装饰器实现，通 过程序实例app调用，用法很简单：以before_request钩子（请求之前） 为例，当你对一个函数附加了app.before_request装饰器后，就会将这个 函数注册为before_request处理函数，每次执行请求前都会触发所有 before_request处理函数。</p><p><img src="/memoirs/images/python/2-7.png" alt="请求钩子"></p><p>这些钩子使用起来和app.route()装饰器基本相同，每个钩子可以 注册任意多个处理函数，函数名并不是必须和钩子名称相同，下面是一 个基本示例：</p><p>假如我们创建了三个视图函数A、B、C，其中视图C使用了 after_this_request钩子，那么当请求A进入后，整个请求处理周期的请求 处理函数调用流程如图2-7所示。 下面是请求钩子的一些常见应用场景：</p><ul><li>before_first_request：<br>在玩具程序中，运行程序前我们需要进行一 些程序的初始化操作，比如创建数据库表，添加管理员用户。这些工作 可以放到使用before_first_request装饰器注册的函数中。</li><li>before_request：<br>比如网站上要记录用户最后在线的时间，可以通 过用户最后发送的请求时间来实现。为了避免在每个视图函数都添加更 新在线时间的代码，我们可以仅在使用before_request钩子注册的函数中 调用这段代码。</li><li>after_request：<br>我们经常在视图函数中进行数据库操作，比如更 新、插入等，之后需要将更改提交到数据库中。提交更改的代码就可以 放到after_request钩子注册的函数中。</li></ul><p><img src="/memoirs/images/python/2-8.png" alt="请求处理函数调用示意图"></p><p>另一种常见的应用是建立数据库连接，通常会有多个视图函数需要 建立和关闭数据库连接，这些操作基本相同。一个理想的解决方法是在 请求之前（before_request）建立连接，在请求之后（teardown_request） 关闭连接。通过在使用相应的请求钩子注册的函数中添加代码就可以实 现。这很像单元测试中的setUp()方法和tearDown()方法。</p><h3 id="HTTP响应"><a href="#HTTP响应" class="headerlink" title="HTTP响应"></a>HTTP响应</h3><p>在Flask程序中，客户端发出的请求触发相应的视图函数，获取返回 值会作为响应的主体，最后生成完整的响应，即响应报文。</p><h4 id="响应报文"><a href="#响应报文" class="headerlink" title="响应报文"></a>响应报文</h4><p>响应报文主要由协议版本、状态码（status code）、原因短语 （reason phrase）、响应首部和响应主体组成。<br>以发向localhost:5000&#x2F;hello的请求为例，服务器生成的响应报文示意如下表所示。</p><p><img src="/memoirs/images/python/2-8-1.png" alt="响应报文"></p><p>响应报文的首部包含一些关于响应和服务器的信息，这些内容由 Flask生成，而我们在视图函数中返回的内容即为响应报文中的主体内容。<br>浏览器接收到响应后，会把返回的响应主体解析并显示在浏览器窗口上。<br>HTTP状态码用来表示请求处理的结果，表2-9是常见的几种状态码 和相应的原因短语。</p><p><img src="/memoirs/images/python/2-9.png" alt="常见的HTTP状态码"></p><h4 id="在Flask中生成响应"><a href="#在Flask中生成响应" class="headerlink" title="在Flask中生成响应"></a>在Flask中生成响应</h4><p>响应在Flask中使用Response对象表示，响应报文中的大部分内容由 服务器处理，大多数情况下，我们只负责返回主体内容。<br>根据我们在上一节介绍的内容，Flask会先判断是否可以找到与请求 URL相匹配的路由，如果没有则返回404响应。<br>如果找到，则调用对应 的视图函数，视图函数的返回值构成了响应报文的主体内容，正确返回 时状态码默认为200。<br>Flask会调用make_response()方法将视图函数返 回值转换为响应对象。<br>完整地说，视图函数可以返回最多由三个元素组成的元组：响应主 体、状态码、首部字段。其中首部字段可以为字典，或是两元素元组组成的列表。</p><p>比如，普通的响应可以只包含主体内容：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/hello&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Hello, Flask!&lt;/h1&gt;&#x27;</span></span><br></pre></td></tr></table></figure><p>默认的状态码为200，下面指定了不同的状态码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/hello&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Hello, Flask!&lt;/h1&gt;&#x27;</span>, <span class="number">201</span></span><br></pre></td></tr></table></figure><p>有时你会想附加或修改某个首部字段。比如，要生成状态码为3XX 的重定向响应，需要将首部中的Location字段设置为重定向的目标 URL：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/hello&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>, <span class="number">302</span>, &#123;<span class="string">&#x27;Location&#x27;</span>, <span class="string">&#x27;http://www.example.com&#x27;</span>&#125;</span><br></pre></td></tr></table></figure><p>现在访问<a href="http://localhost:5000/hello">http://localhost:5000/hello</a> ，会重定向 到<a href="http://www.example.com/">http://www.example.com</a> 。在多数情况下，除了响应主体，其他部分我们通常只需要使用默认值即可。</p><ol><li>重定向<br>在Web程序中，我们经常需要进行重定向。比如，当某个用户在没 有经过认证的情况下访问需要登录后才能访问的资源，程序通常会重定向到登录页面。<br>对于重定向这一类特殊响应，Flask提供了一些辅助函数。<br>除了像前面那样手动生成302响应，我们可以使用Flask提供的redirect（）函数来生成重定向响应，重定向的目标URL作为第一个参数。<br>前面的例子可以简化为：</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, redirect</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/hello&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    <span class="keyword">return</span> redirect(<span class="string">&#x27;http://www.example.com&#x27;</span>)</span><br></pre></td></tr></table></figure><p>使用redirect()函数时，默认的状态码为302，即临时重定向。如果你想修改状态码，可以在redirect()函数中作为第二个参数或使用code关键字传入。</p><p>如果要在程序内重定向到其他视图，那么只需在redirect()函数中<br>使用url_for()函数生成目标URL即可</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, redirect, url_for</span><br><span class="line">...</span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/hi&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hi</span>():</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> redierct(url_for(<span class="string">&#x27;hello&#x27;</span>))</span><br><span class="line"><span class="comment"># 重定向到/hello</span></span><br></pre></td></tr></table></figure><ol start="2"><li>错误响应<br>大多数情况下，Flask会自动处理常见的错误响应。HTTP错误对应的异常类在Werkzeug的werkzeug.exceptions模块中定义，抛出这些异常即可返回对应的错误响应。<br>如果你想手动返回错误响应，更方便的方法 是使用Flask提供的abort()函数。 在abort()函数中传入状态码即可返回对应的错误响应</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, abort</span><br><span class="line">...</span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/404&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">not_found</span>():</span><br><span class="line">    abort(<span class="number">404</span>)</span><br></pre></td></tr></table></figure><h4 id="响应格式"><a href="#响应格式" class="headerlink" title="响应格式"></a>响应格式</h4><p>在HTTP响应中，数据可以通过多种格式传输。大多数情况下，我 们会使用HTML格式，这也是Flask中的默认设置。在特定的情况下，我 们也会使用其他格式。<br>MIME类型（又称为media type或content type）是一种用来标识文件 类型的机制，它与文件扩展名相对应，可以让客户端区分不同的内容类型，并执行不同的操作。一般的格式为“类型名&#x2F;子类型名”，其中的子类 型名一般为文件扩展名。<br>比如，HTML的MIME类型为“text&#x2F;html”，png图片的MIME类型为“image&#x2F;png”。完整的标准MIME类型列表可以在这 里看到：<a href="https://www.iana.org/assignments/media-types/media-types.xhtml">https://www.iana.org/assignments/media-types/media-types.xhtml</a>。</p><p>如果你想使用其他MIME类型，可以通过Flask提供的 make_response（）方法生成响应对象，传入响应的主体作为参数，然后 使用响应对象的mimetype属性设置MIME类型</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> make_response</span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/foo&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">foo</span>():</span><br><span class="line">    response = make_response(<span class="string">&#x27;Hello, World!&#x27;</span>)</span><br><span class="line">    response.mimetype = <span class="string">&#x27;text/plain&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> response</span><br></pre></td></tr></table></figure><p>你也可以直接设置首部字段，比如<code>response.headers[&#39;Content-Type&#39;]=&#39;text/xml；charset=utf-8&#39;</code>。但操作mimetype属性更加方便，而且不用设置字符集（charset）选项。</p><p>常用的数据格式有纯文本、HTML、XML和JSON，下面我们分别 对这几种数据进行简单的介绍和分析。</p><ol><li>纯文本<br> MIME类型：<code>text/plain</code><br> 事实上，其他几种格式本质上都是纯文本。比如同样是一行包含 HTML标签的文本<code>&lt;h1&gt;Hello，Flask！&lt;/h1&gt;</code>，当MIME类型设置为纯 文本时，浏览器会以文本形式显示<code>&lt;h1&gt;Hello，Flask！&lt;/h1&gt;</code>；当 MIME类型声明为text&#x2F;html时，浏览器则会将其作为标题1样式的HTML 代码渲染。</li><li>HTML<br> MIME类型：<code>text/html</code><br> <a href="https://www.w3.org/html/">HTML</a>指Hypertext Markup Language（超文本标记语言），是最常用的数据格式，也是Flask返回响 应的默认数据类型。从我们在本书一开始的最小程序中的视图函数返回 的字符串，到我们后面会学习的HTML模板，都是HTML。当数据类型 为HTML时，浏览器会自动根据HTML标签以及样式类定义渲染对应的 样式。<br> 因为HTML常常包含丰富的信息，我们可以直接将HTML嵌入页面 中，处理起来比较方便。因此，在普通的HTTP请求中我们使用HTTP作 为响应的内容，这也是默认的数据类型。</li><li>XML<br> MIME类型：<code>application/xml</code><br> <a href="https://www.w3.org/XML/">XML</a>指Extensible Markup Language（可扩展标记语言），它是一种简单灵活的文本格式，被设计 用来存储和交换数据。XML的出现主要就是为了弥补HTML的不足：对 于仅仅需要数据的请求来说，HTML提供的信息太过丰富了，而且不易 于重用。XML和HTML一样都是标记性语言，使用标签来定义文本，但 HTML中的标签用于显示内容，而XML中的标签只用于定义数据。 XML一般作为AJAX请求的响应格式，或是Web API的响应格式。</li><li>JSON<br> MIME类型：<code>application/json</code><br> <a href="http://json.org/">JSON</a>指JavaScript Object Notation（JavaScript对 象表示法），是一种流行的、轻量的数据交换格式。它的出现又弥补了 XML的诸多不足：XML有较高的重用性，但XML相对于其他文档格式 来说体积稍大，处理和解析的速度较慢。JSON轻量，简洁，容易阅读 和解析，而且能和Web默认的客户端语言JavaScript更好地兼容。JSON 的结构基于“键值对的集合”和“有序的值列表”，这两种数据结构类似 Python中的字典（dictionary）和列表（list）。正是因为这种通用的数据 结构，使得JSON在同样基于这些结构的编程语言之间交换成为可能。</li></ol><h4 id="来一块Cookie"><a href="#来一块Cookie" class="headerlink" title="来一块Cookie"></a>来一块Cookie</h4><p><code>HTTP</code>是无状态（<code>stateless</code>）协议。也就是说，在一次请求响应结束后，服务器不会留下任何关于对方状态的信息。<br>但是对于某些Web程序来说，客户端的某些信息又必须被记住，比如用户的登录状态，这样才可以根据用户的状态来返回不同的响应。<br>为了解决这类问题，就有了<code>Cookie</code>技术。<code>Cookie</code>技术通过在请求和响应报文中添加<code>Cookie</code>数据来保 存客户端的状态信息。</p><p>在Flask中，如果想要在响应中添加一个cookie，最方便的方法是使 用Response类提供的set_cookie()方法。要使用这个方法，我们需要先 使用make_response()方法手动生成一个响应对象，传入响应主体作为 参数。这个响应对象默认实例化内置的Response类。</p><p><img src="/memoirs/images/python/2-10.png" alt="Response类的常用属性和方法"></p><p><img src="/memoirs/images/python/2-11.png" alt="set_cookie()方法的参数"></p><p>set_cookie视图用来设置cookie，它会将URL中的name变量的值设置 到名为name的cookie里</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, make_response, url_for</span><br><span class="line">...</span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/set/&lt;name&gt;&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">set_cookie</span>(<span class="params">name</span>):</span><br><span class="line">    response = make_response(redirect(url_for(<span class="string">&#x27;hello&#x27;</span>)))</span><br><span class="line">    response.set_cookie(<span class="string">&#x27;name&#x27;</span>, name)</span><br><span class="line">    <span class="keyword">return</span> response</span><br></pre></td></tr></table></figure><p>当浏览器保存了服务器端设置的Cookie后，浏览器再次发送到该服 务器的请求会自动携带设置的Cookie信息，Cookie的内容存储在请求首部的Cookie字段中，整个交互过程由上到下如下图所示。</p><p><img src="/memoirs/images/python/2-11-1.png" alt="Cookie设置示意图"></p><p>在Flask中，Cookie可以通过请求对象的cookies属性读取。在修改后 的hello视图中，如果没有从查询参数中获取到name的值，就从Cookie中寻找：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, request</span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/&#x27;</span></span>)</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/hello&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    name = request.args.get(<span class="string">&#x27;name&#x27;</span>)</span><br><span class="line">    <span class="keyword">if</span> name <span class="keyword">is</span> <span class="literal">None</span>: name = request.cookies.get(<span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;Human&#x27;</span>)</span><br><span class="line">    <span class="comment"># 从Cookie中获取name值</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Hello, %s&lt;/h1&gt;&#x27;</span> % name</span><br></pre></td></tr></table></figure><h4 id="session：安全的Cookie"><a href="#session：安全的Cookie" class="headerlink" title="session：安全的Cookie"></a>session：安全的Cookie</h4><p>Cookie在Web程序中发挥了很大的作用，其中最重要的功能是存储 用户的认证信息。我们先来看看基于浏览器的用户认证是如何实现的。<br>当我们使用浏览器登录某个社交网站时，会在登录表单中填写用户名和 密码，单击登录按钮后，这会向服务器发送一个包含认证数据的请求。<br>服务器接收请求后会查找对应的账户，然后验证密码是否匹配，如果匹 配，就在返回的响应中设置一个cookie，比如，<code>login_user：greyli</code>。<br>响应被浏览器接收后，cookie会被保存在浏览器中。当用户再次向 这个服务器发送请求时，根据请求附带的Cookie字段中的内容，服务器 上的程序就可以判断用户的认证状态，并识别出用户。<br>但是这会带来一个问题，在浏览器中手动添加和修改Cookie是很容 易的事，仅仅通过浏览器插件就可以实现。<br>所以，如果直接把认证信息 以明文的方式存储在Cookie里，那么恶意用户就可以通过伪造cookie的 内容来获得对网站的权限，冒用别人的账户。<br>为了避免这个问题，我们 需要对敏感的Cookie内容进行加密。方便的是，Flask提供了session对象 用来将Cookie数据加密储存。</p><ol><li>设置程序密钥<br>session通过密钥对数据进行签名以加密数据，因此，我们得先设置 一个密钥。这里的密钥就是一个具有一定复杂度和随机性的字符串，比 如“Drmhze6EPcv0fN_81Bj-nA”。<br>程序的密钥可以通过Flask.secret_key属性或配置变量SECRET_KEY 设置，比如：<br>更安全的做法是把密钥写进系统环境变量（在命令行中使用export 或set命令），或是保存在.env文件中：</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">app.secret_key = <span class="string">&#x27;secret string&#x27;</span></span><br><span class="line"></span><br><span class="line">SECRET_KEY=secret string</span><br><span class="line">app.secret_key = SECRET_KEY</span><br></pre></td></tr></table></figure><p>然后在程序脚本中使用os模块提供的getenv（）方法获取：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line">app.secret_key = os.getenv(<span class="string">&#x27;SECRET_KEY&#x27;</span>, <span class="string">&#x27;secret string&#x27;</span>)</span><br></pre></td></tr></table></figure><ol start="2"><li>模拟用户认证<br>下面我们会使用session模拟用户的认证功能。代码清单2-5是用来 登入用户的login视图。</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> redirect, session, url_for</span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/login&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>():</span><br><span class="line">    session[<span class="string">&#x27;logged_in&#x27;</span>] = <span class="literal">True</span></span><br><span class="line">    <span class="comment"># 写入session</span></span><br><span class="line">    <span class="keyword">return</span> redirect(url_for(<span class="string">&#x27;hello&#x27;</span>))</span><br></pre></td></tr></table></figure><p>这个登录视图只是简化的示例，在实际的登录中，我们需要在页面 上提供登录表单，供用户填写账户和密码，然后在登录视图里验证账户和密码的有效性。<br>session对象可以像字典一样操作，我们向session中添 加一个logged-in cookie，将它的值设为True，表示用户已认证。<br>当我们使用session对象添加cookie时，数据会使用程序的密钥对其 进行签名，加密后的数据存储在一块名为session的cookie里。</p><h3 id="Flask上下文"><a href="#Flask上下文" class="headerlink" title="Flask上下文"></a>Flask上下文</h3><p>我们可以把编程中的上下文理解为当前环境（environment）的快照 （snapshot）。如果把一个Flask程序比作一条可怜的生活在鱼缸里的鱼 的话，那么它当然离不开身边的环境。</p><h4 id="上下文全局变量"><a href="#上下文全局变量" class="headerlink" title="上下文全局变量"></a>上下文全局变量</h4><p>每一个视图函数都需要上下文信息，在前面我们学习过Flask将请求 报文封装在request对象中。按照一般的思路，如果我们要在视图函数中使用它，就得把它作为参数传入视图函数，就像我们接收URL变量一 样。但是这样一来就会导致大量的重复，而且增加了视图函数的复杂度。</p><p>在前面的示例中，我们并没有传递这个参数，而是直接从Flask导入 一个全局的request对象，然后在视图函数里直接调用request的属性获取数据。你一定好奇，我们在全局导入时request只是一个普通的Python对 象，为什么在处理请求时，视图函数里的request就会自动包含对应请求 的数据？这是因为Flask会在每个请求产生后自动激活当前请求的上下文，激活请求上下文后，request被临时设为全局可访问。而当每个请求结束后，Flask就销毁对应的请求上下文。</p><p>我们在前面说request是全局对象，但这里的“全局”并不是实际意义 上的全局。我们可以把这些变量理解为动态的全局变量。<br>在多线程服务器中，在同一时间可能会有多个请求在处理。假设有 三个客户端同时向服务器发送请求，这时每个请求都有各自不同的请求报文，所以请求对象也必然是不同的。<br>因此，请求对象只在各自的线程 内是全局的。Flask通过本地线程（thread local）技术将请求对象在特定 的线程和请求中全局可访问。具体内容和应用我们会在后面进行详细介绍。</p><p><img src="/memoirs/images/python/2-12.png" alt="Flask中的上下文变量"></p><p>我们在前面对session和request都了解得差不多了，这里简单介绍一 下current_app和g。<br>你在这里也许会疑惑，既然有了程序实例app对象，为什么还需要 current_app变量。在不同的视图函数中，request对象都表示和视图函数 对应的请求，也就是当前请求（current request）。而程序也会有多个程 序实例的情况，为了能获取对应的程序实例，而不是固定的某一个程序实例，我们就需要使用current_app变量，后面会详细介绍。</p><p>因为g存储在程序上下文中，而程序上下文会随着每一个请求的进入而激活，随着每一个请求的处理完毕而销毁，所以每次请求都会重设 这个值。我们通常会使用它结合请求钩子来保存每个请求处理前所需要 的全局变量，比如当前登入的用户对象，数据库连接等。<br>在前面的示例中，我们在hello视图中从查询字符串获取name的值，如果每一个视图都需要这个值，那么就要在每个视图重复这行代码。借助g我们可以将这 个操作移动到before_request处理函数中执行，然后保存到g的任意属性上：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> g</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.before_request</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_name</span>():</span><br><span class="line">    g.name = request.args.get(<span class="string">&#x27;name&#x27;</span>)</span><br></pre></td></tr></table></figure><p>设置这个函数后，在其他视图中可以直接使用g.name获取对应的值。另外，g也支持使用类似字典的get()、pop()以及setdefault()方法进行操作。</p><h4 id="激活上下文"><a href="#激活上下文" class="headerlink" title="激活上下文"></a>激活上下文</h4><p>阳光柔和，鱼儿在水里欢快地游动，这一切都是上下文存在后的美好景象。如果没有上下文，我们的程序只能直挺挺地躺在鱼缸里。<br>在下 面这些情况下，Flask会自动帮我们激活程序上下文：</p><ul><li>当我们使用flask run命令启动程序时。</li><li>使用旧的app.run()方法启动程序时。</li><li>执行使用@app.cli.command()装饰器注册的flask命令时。</li><li>使用flask shell命令启动Python Shell时。</li></ul><p>当请求进入时，Flask会自动激活请求上下文，这时我们可以使用request和session变量。<br>另外，当请求上下文被激活时，程序上下文也被自动激活。当请求处理完毕后，请求上下文和程序上下文也会自动销毁。<br>也就是说，在请求处理时这两者拥有相同的生命周期。<br>结合Python的代码执行机制理解，这也就意味着，我们可以在视图 函数中或在视图函数内调用的函数&#x2F;方法中使用所有上下文全局变量。<br>在使用flask shell命令打开的Python Shell中，或是自定义的flask命令函数 中，我们可以使用current_app和g变量，也可以手动激活请求上下文来使用request和session。<br>如果我们在没有激活相关上下文时使用这些变量，Flask就会抛出 RuntimeError异常：<code>RuntimeError：Working outside of application context.</code>或是<code>RuntimeError：Working outside of request context.</code>。</p><h4 id="上下文钩子"><a href="#上下文钩子" class="headerlink" title="上下文钩子"></a>上下文钩子</h4><p>在前面我们学习了请求生命周期中可以使用的几种钩子，Flask也为上下文提供了一个teardown_appcontext钩子，使用它注册的回调函数会 在程序上下文被销毁时调用，而且通常也会在请求上下文被销毁时调用。<br>比如，你需要在每个请求处理结束后销毁数据库连接：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.teardown_appcontext</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">teardown_db</span>(<span class="params">exception</span>):</span><br><span class="line">    ...</span><br><span class="line">    db.close()</span><br></pre></td></tr></table></figure><p>使用app.teardown_appcontext装饰器注册的回调函数需要接收异常 对象作为参数，当请求被正常处理时这个参数值将是None，这个函数的 返回值将被忽略。<br>上下文是Flask的重要话题，在这里我们也只是简单了解一下，后面我们会详细了解上下文的实现原理。</p><h3 id="HTTP进阶实践"><a href="#HTTP进阶实践" class="headerlink" title="HTTP进阶实践"></a>HTTP进阶实践</h3><h4 id="重定向回上一个页面"><a href="#重定向回上一个页面" class="headerlink" title="重定向回上一个页面"></a>重定向回上一个页面</h4><p>在前面的示例程序中，我们使用redirect()函数生成重定向响应。 比如，在login视图中，登入用户后我们将用户重定向到&#x2F;hello页面。在 复杂的应用场景下，我们需要在用户访问某个URL后重定向到上一个页 面。最常见的情况是，用户单击某个需要登录才能访问的链接，这时程 序会重定向到登录页面，当用户登录后合理的行为是重定向到用户登录 前浏览的页面，以便用户执行未完成的操作，而不是直接重定向到主页。在示例程序中，我们创建了两个视图函数foo和bar，分别显示一个 Foo页面和一个Bar页面。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/foo&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">foo</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Foo page&lt;/h1&gt;&lt;a href=&quot;%s&quot;&gt;Do something&lt;/a&gt;&#x27;</span> % url_for(<span class="string">&#x27;do_something&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app.route(<span class="string">&#x27;/bar&#x27;</span>)</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bar</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Bar page&lt;/h1&gt;&lt;a href=&quot;%s&quot;&gt;Do something &lt;/a&gt;&#x27;</span> % url_for(<span class="string">&#x27;do_something&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/do_something&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">do_something</span>():</span><br><span class="line">    <span class="comment"># do something</span></span><br><span class="line">    <span class="keyword">return</span> redirect(url_for(<span class="string">&#x27;hello&#x27;</span>))</span><br></pre></td></tr></table></figure><ol><li>获取上一个页面的URL</li></ol><p>要重定向回上一个页面，最关键的是获取上一个页面的URL。上一 个页面的URL一般可以通过两种方式获取：</p><ul><li>（1）HTTP referer<br>HTTP referer（起源为referrer在HTTP规范中的错误拼写）是一个用来记录请求发源地址的HTTP首部字段（HTTP_REFERER），即访问来源。<br>当用户在某个站点单击链接，浏览器向新链接所在的服务器发起请求，请求的数据中包含的HTTP_REFERER字段记录了用户所在的原站点URL。<br>这个值通常会用来追踪用户，比如记录用户进入程序的外部站点，以此来更有针对性地进行营销。在Flask中，referer的值可以通过请求对象的referrer属性获取，即request.referrer（正确拼写形式）。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> redirect(request.referrer)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> redirect(request.referrer <span class="keyword">or</span> url_for(<span class="string">&#x27;hello&#x27;</span>))</span><br></pre></td></tr></table></figure><ul><li>（2）查询参数<br>除了自动从referrer获取，另一种更常见的方式是在URL中手动加入包含当前页面URL的查询参数，这个查询参数一般命名为next。比如，下面在foo和bar视图的返回值中的URL后添加next参数</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> request</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/foo&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">foo</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Foo page&lt;/h1&gt;&lt;a href=&quot;%s&quot;&gt;Do something and redirect&lt;/a&gt;&#x27;</span> % url_for(<span class="string">&#x27;do_something&#x27;</span>, <span class="built_in">next</span>=request.full_path)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/bar&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bar</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&lt;h1&gt;Bar page&lt;/h1&gt;&lt;a href=&quot;%s&quot;&gt;Do something and redirect&lt;/a&gt;&#x27;</span> % url_for(<span class="string">&#x27;do_something&#x27;</span>, <span class="built_in">next</span>=request.full_path)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> redirect(request.args.get(<span class="string">&#x27;next&#x27;</span>))</span><br><span class="line"><span class="keyword">return</span> redirect(request.args.get(<span class="string">&#x27;next&#x27;</span>, url_for(<span class="string">&#x27;hello&#x27;</span>)))</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="2"><li>对URL进行安全验证<br>虽然我们已经实现了重定向回上一个页面的功能，但安全问题不容 小觑，鉴于referer和next容易被篡改的特性，如果我们不对这些值进行 验证，则会形成开放重定向（Open Redirect）漏洞。</li></ol><p>以URL中的next参数为例，next变量以查询字符串的方式写在URL里，因此任何人都可以发给某个用户一个包含next变量指向任何站点的链接。举个简单的例子，如果你访问下面的URL：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://localhost:<span class="number">5000</span>/do-something?<span class="built_in">next</span>=http://helloflask.com</span><br></pre></td></tr></table></figure><p>程序会被重定向到<a href="http://helloflask.com/">http://helloflask.com</a> 。也就是说，如果我们不验 证next变量指向的URL地址是否属于我们的应用内，那么程序很容易就 会被重定向到外部地址。</p><p>假设我们的应用是一个银行业务系统（下面简称网站A），某个攻击者模仿我们的网站外观做了一个几乎一模一样的网站（下面简称网站 B）。<br>接着，攻击者伪造了一封电子邮件，告诉用户网站A账户信息需要更新，然后向用户提供一个指向网站A登录页面的链接，但链接中包 含一个重定向到网站B的next变量，比如：<a href="http://examplea.com/login?next=http://maliciousB.com">http://exampleA.com/login?next=http://maliciousB.com</a>。<br>当用户在A网站登录后，如果A网站重定向到next对应的URL，那么就会导致重定向到攻击者编写的B网站。因为B网站完全模仿A网站的外观，攻击者就可以在重定向后的B网站诱导用户输入敏感信息，比如银行卡号及密码。<br>确保URL安全的关键就是判断URL是否属于程序内部，我们创建了一个URL验证函数is_safe_url()，用来验证next变 量值是否属于程序内部URL。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> urlparse <span class="keyword">import</span> urlparse, urljoin</span><br><span class="line"><span class="comment"># Python3需要从urllib.parse导入</span></span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> request</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">is_safe_url</span>(<span class="params">target</span>):</span><br><span class="line">    ref_url = urlparse(request.host_url)</span><br><span class="line">    test_url = urlparse(urljoin(request.host_url, target))</span><br><span class="line">    <span class="keyword">return</span> test_url.scheme <span class="keyword">in</span> (<span class="string">&#x27;http&#x27;</span>, <span class="string">&#x27;https&#x27;</span>) <span class="keyword">and</span> ref_url.netloc == test_url.netloc</span><br></pre></td></tr></table></figure><h4 id="使用AJAX技术发送异步请求"><a href="#使用AJAX技术发送异步请求" class="headerlink" title="使用AJAX技术发送异步请求"></a>使用AJAX技术发送异步请求</h4><p>在传统的Web应用中，程序的操作都是基于请求响应循环来实现的。每当页面状态需要变动，或是需要更新数据时，都伴随着一个发向服务器的请求。<br>当服务器返回响应时，整个页面会重载，并渲染新页面。<br>这种模式会带来一些问题。首先，频繁更新页面会牺牲性能，浪费 服务器资源，同时降低用户体验。<br>另外，对于一些操作性很强的程序来说，重载页面会显得很不合理。比如我们做了一个Web计算器程序，所有的按钮和显示屏幕都很逼真，但当我们单击“等于”按钮时，要等到页面重新加载后才在显示屏幕上看到结果，这显然会严重影响用户体验。<br>我们这一节要学习的AJAX技术可以完美地解决这些问题。</p><h4 id="HTTP服务器端推送"><a href="#HTTP服务器端推送" class="headerlink" title="HTTP服务器端推送"></a>HTTP服务器端推送</h4><p>不论是传统的HTTP请求-响应式的通信模式，还是异步的AJAX式请求，服务器端始终处于被动的应答状态，只有在客户端发出请求的情况下，服务器端才会返回响应。这种通信模式被称为客户端拉取 （client pull）。在这种模式下，用户只能通过刷新页面或主动单击加载 按钮来拉取新数据。</p><p>然而，在某些场景下，我们需要的通信模式是服务器端的主动推送 （server push）。比如，一个聊天室有很多个用户，当某个用户发送消息后，服务器接收到这个请求，然后把消息推送给聊天室的所有用户。 类似这种关注实时性的情况还有很多，比如社交网站在导航栏实时显示新提醒和私信的数量，用户的在线状态更新，股价行情监控、显示商品 库存信息、多人游戏、文档协作等。</p><p><img src="/memoirs/images/python/2-14.png" alt="常用推送技术"></p><h4 id="Web安全防范"><a href="#Web安全防范" class="headerlink" title="Web安全防范"></a>Web安全防范</h4><p>无论是简单的博客，还是大型的社交网站，Web安全都应该放在首位。Web安全问题涉及广泛，我们在这里介绍其中常见的几种攻击（attack）和其他常见的漏洞（vulnerability）。</p><p>对于Web程序的安全问题，一个首要的原则是：永远不要相信你的用户。大部分Web安全问题都是因为没有对用户输入的内容进行“消 毒”造成的。</p><ol><li><p>注入攻击<br>在OWASP（<code>Open Web Application Security Project</code>，开放式Web程 序安全项目）发布的最危险的Web程序安全风险Top 10中，无论是最新 的2017年的排名，2013年的排名还是最早的2010年，注入攻击 （Injection）都位列第一。<br>注入攻击包括系统命令（OS Command）注 入、SQL（Structured Query Language，结构化查询语言）注入（SQL Injection）、NoSQL注入、ORM（Object Relational Mapper，对象关系 映射）注入等。我们这里重点介绍的是SQL注入。</p></li><li><p>XSS攻击<br>XSS（Cross-Site Scripting，跨站脚本）攻击历史悠久，最远可以追溯到90年代，但至今仍然是危害范围非常广的攻击方式。在OWASP TOP 10中排名第7。</p></li><li><p>CSRF攻击<br>CSRF（Cross Site Request Forgery，跨站请求伪造）是一种近年来才逐渐被大众了解的网络攻击方式，又被称为One-Click Attack或Session Riding。在OWASP上一次（2013）的TOP 10 Web程序安全风险中，它位列第8。随着大部分程序的完善，各种框架都内置了对CSRF保护的支 持，但目前仍有5%的程序受到威胁。</p></li></ol><h3 id="本章小结-1"><a href="#本章小结-1" class="headerlink" title="本章小结"></a>本章小结</h3><p>HTTP是各种Web程序的基础，本章只是简要介绍了和Flask相关的部分，没有涉及HTTP底层的TCP&#x2F;IP或DNS协议。<br>建议你通过阅读相关 书籍来了解完整的Web原理，这将有助于编写更完善和安全的Web程序。</p><p>在下一章，我们会学习使用Flask的模板引擎——Jinja2，通过学习运用模板和静态文件，我们可以让程序变得更加丰富和完善。<br>现在前后端分离的架构下，Jinja2我们就简单的介绍一下。</p><h2 id="Jinja模板"><a href="#Jinja模板" class="headerlink" title="Jinja模板"></a>Jinja模板</h2><p>在动态Web程序中，视图函数返回的HTML数据往往需要根据相应的变量（比如查询参数）动态生成。<br>当HTML代码保存到单独的文件中时，我们没法再使用字符串格式化或拼接字符串的方式来在HTML代码中插入变量，这时我们需要使用模板引擎（template engine）。<br>借助模板引擎，我们可以在HTML文件中使用特殊的语法来标记出变量，这类包含固定内容和动态部分的可重用文件称为模板（template）。<br>模板引擎的作用就是读取并执行模板中的特殊语法标记，并根据传入的数据将变量替换为实际值，输出最终的HTML页面，这个过程被称为渲染（rendering）。<br>Flask默认使用的模板引擎是Jinja2，它是一个功 能齐全的Python模板引擎，除了设置变量，还允许我们在模板中添加if判断，执行for迭代，调用函数等，以各种方式控制模板的输出。<br>对于Jinja2来说，模板可以是任何格式的纯文本文件，比如HTML、XML、 CSV、LaTeX等。</p><h3 id="模板基本用法"><a href="#模板基本用法" class="headerlink" title="模板基本用法"></a>模板基本用法</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">user = &#123;</span><br><span class="line">    <span class="string">&#x27;username&#x27;</span>: <span class="string">&#x27;Grey Li&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;bio&#x27;</span>: <span class="string">&#x27;A boy who loves movies and music.&#x27;</span>,</span><br><span class="line">&#125;</span><br><span class="line">movies = [</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;My Neighbor Totoro&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;1988&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;Three Colours trilogy&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;1993&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;Forrest Gump&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;1994&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;Perfect Blue&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;1997&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;The Matrix&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;1999&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;Memento&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;2000&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;The Bucket list&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;2007&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;Black Swan&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;2010&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;Gone Girl&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;2014&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;CoCo&#x27;</span>, <span class="string">&#x27;year&#x27;</span>: <span class="string">&#x27;2017&#x27;</span>&#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>我们在templates目录下创建一个watchlist.html作为模板文件，然后 使用Jinja2支持的语法在模板中操作这些变量</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>&#123;&#123; user.username &#125;&#125;&#x27;s Watchlist<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;&#123;&#123; url_for(&#x27;index&#x27;) &#125;&#125;&quot;</span>&gt;</span><span class="symbol">&amp;larr;</span> Return<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;&#123; user.username &#125;&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line">&#123;% if user.bio %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">i</span>&gt;</span>&#123;&#123; user.bio &#125;&#125;<span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">&#123;% else %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">i</span>&gt;</span>This user has not provided a bio.<span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">&#123;% endif %&#125;</span><br><span class="line">    &#123;# 下面是电影清单（这是注释） #&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">h5</span>&gt;</span>&#123;&#123; user.username &#125;&#125;&#x27;s Watchlist (&#123;&#123; movies|length &#125;&#125;):<span class="tag">&lt;/<span class="name">h5</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ul</span>&gt;</span>&#123;% for movie in movies %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span>&gt;</span>&#123;&#123; movie.name &#125;&#125; - &#123;&#123; movie.year &#125;&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">    &#123;% endfor %&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><hr><p>Jinja2 的用法很多，其实和Java的那个jsp用法差不多，具体的用法可以查询文档</p><p><a href="http://docs.jinkan.org/docs/jinja2/">Jinja2文档</a></p><p>Jinja2 其实跟jsp的优缺点也很像，在复杂的页面并不太好用，还是用Web API和Vue比较适合我。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>上面的3章介绍的都是Flask的基础知识，比较简单也比较枯燥，主要的篇幅放在了HTTP上面，其他两章在官方的文档中介绍的很详细。<br>基础篇还剩一部分，将会在后面介绍，剩下的基础篇会结合基础知识和实际的用途，以及代码来介绍。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20200316/python/Python-100-Flask-1/</id>
    <link href="https://minniexcode.github.io/memoirs/20200316/python/Python-100-Flask-1/"/>
    <published>2020-03-16T13:20:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="第1章-初识Flask"><a href="#第1章-初识Flask" class="headerlink" title="第1章 初识Flask"></a>第1章 初识Flask</h2><h3 id="Flask介绍"><a href="#Flask介绍" class="headerlink" title="Flask介绍"></a>Flask介绍</h3><p>搭建开发环境，编写一个最小的Flask程序并运行它，了解 Flask基本知识</p>
<p>这一切开始于2010年4月1日，Armin Ronacher在网上发布了一篇关 于“下一代Python微框架”的介绍文章，文章里称这个Denied框架不依赖 Python标准库，只需要复制一份deny.py放到你的项目文件夹就可以开始 编程。伴随着一本正经的介绍、名人推荐语、示例代码和演示视频，这 个“虚假”的项目让不少人都信以为真。</p>
<p>5天后， <a href="http://flask.pocoo.org/">Flask</a>就从这么一个愚人节玩笑诞生了。</p>]]>
    </summary>
    <title>Python-Flask基础篇(一)</title>
    <updated>2026-03-16T16:24:40.838Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Python" scheme="https://minniexcode.github.io/memoirs/tags/Python/"/>
    <content>
      <![CDATA[<h2 id="关系数据库入门"><a href="#关系数据库入门" class="headerlink" title="关系数据库入门"></a>关系数据库入门</h2><span id="more"></span><h3 id="关系数据库概述"><a href="#关系数据库概述" class="headerlink" title="关系数据库概述"></a>关系数据库概述</h3><ol><li><p>数据持久化 - 将数据保存到能够长久保存数据的存储介质中，在掉电的情况下数据也不会丢失。</p></li><li><p>数据库发展史 - 网状数据库、层次数据库、关系数据库、NoSQL数据库。</p><blockquote><p>1970年，IBM的研究员E.F.Codd在<em>Communication of the ACM</em>上发表了名为<em>A Relational Model of Data for Large Shared Data Banks</em>的论文，提出了关系模型的概念，奠定了关系模型的理论基础。后来Codd又陆续发表多篇文章，论述了范式理论和衡量关系系统的12条标准，用数学理论奠定了关系数据库的基础。</p></blockquote></li><li><p>关系数据库特点。</p><ul><li>理论基础：集合论和关系代数。</li><li>具体表象：用二维表（有行和列）组织数据。</li><li>编程语言：结构化查询语言（SQL）。</li></ul></li><li><p>ER模型（实体关系模型）和概念模型图。<br><strong>ER模型</strong>，全称为<strong>实体关系模型</strong>（Entity-Relationship Model），由美籍华裔计算机科学家陈品山先生提出，是概念数据模型的高层描述方式.</p><ul><li>实体 - 矩形框</li><li>属性 - 椭圆框</li><li>关系 - 菱形框</li><li>重数 - 1:1（一对一） &#x2F; 1:N（一对多） &#x2F; M:N（多对多）</li></ul><p>实际项目开发中，我们可以利用数据库建模工具（如：PowerDesigner）来绘制概念数据模型（其本质就是ER模型），然后再设置好目标数据库系统，将概念模型转换成物理模型，最终生成创建二维表的SQL（很多工具都可以根据我们设计的物理模型图以及设定的目标数据库来导出SQL或直接生成数据表）。</p></li><li><p>关系数据库产品。</p><ul><li><a href="https://www.oracle.com/index.html">Oracle</a> - 目前世界上使用最为广泛的数据库管理系统，作为一个通用的数据库系统，它具有完整的数据管理功能；作为一个关系数据库，它是一个完备关系的产品；作为分布式数据库，它实现了分布式处理的功能。在Oracle最新的12c版本中，还引入了多承租方架构，使用该架构可轻松部署和管理数据库云。</li><li><a href="https://www.ibm.com/analytics/us/en/db2/">DB2</a> - IBM公司开发的、主要运行于Unix（包括IBM自家的<a href="https://zh.wikipedia.org/wiki/AIX">AIX</a>）、Linux、以及Windows服务器版等系统的关系数据库产品。DB2历史悠久且被认为是最早使用SQL的数据库产品，它拥有较为强大的商业智能功能。</li><li><a href="https://www.microsoft.com/en-us/sql-server/">SQL Server</a> - 由Microsoft开发和推广的关系型数据库产品，最初适用于中小企业的数据管理，但是近年来它的应用范围有所扩展，部分大企业甚至是跨国公司也开始基于它来构建自己的数据管理系统。</li><li><a href="https://www.mysql.com/">MySQL</a> - MySQL是开放源代码的，任何人都可以在GPL（General Public License）的许可下下载并根据个性化的需要对其进行修改。MySQL因为其速度、可靠性和适应性而备受关注。</li><li><a href="/">PostgreSQL</a> - 在BSD许可证下发行的开放源代码的关系数据库产品。</li></ul></li></ol><h3 id="MySQL简介"><a href="#MySQL简介" class="headerlink" title="MySQL简介"></a>MySQL简介</h3><p>MySQL最早是由瑞典的MySQL AB公司开发的一个开放源码的关系数据库管理系统，该公司于2008年被昇阳微系统公司（Sun Microsystems）收购。在2009年，甲骨文公司（Oracle）收购昇阳微系统公司，因此在这之后MySQL成为了Oracle旗下产品。</p><p>MySQL在过去由于性能高、成本低、可靠性好，已经成为最流行的开源数据库，因此被广泛地应用于中小型网站开发。随着MySQL的不断成熟，它也逐渐被应用于更多大规模网站和应用，比如维基百科、谷歌（Google）、脸书（Facebook）、淘宝网等网站都使用了MySQL来提供数据持久化服务。</p><p>甲骨文公司收购后昇阳微系统公司，大幅调涨MySQL商业版的售价，且甲骨文公司不再支持另一个自由软件项目<a href="https://zh.wikipedia.org/wiki/OpenSolaris">OpenSolaris</a>的发展，因此导致自由软件社区对于Oracle是否还会持续支持MySQL社区版（MySQL的各个发行版本中唯一免费的版本）有所担忧，MySQL的创始人麦克尔·维德纽斯以MySQL为基础，成立分支计划<a href="https://zh.wikipedia.org/wiki/MariaDB">MariaDB</a>（以他女儿的名字命名的数据库）。有许多原来使用MySQL数据库的公司（例如：维基百科）已经陆续完成了从MySQL数据库到MariaDB数据库的迁移。</p><ol><li><p>安装和配置</p><blockquote><p><strong>说明</strong>：下面的安装和配置都是以CentOS Linux环境为例，如果需要在其他系统下安装MySQL，读者可以自行在网络上查找对应的安装教程）。</p></blockquote><ul><li><p>刚才说过，MySQL有一个分支版本名叫MariaDB，该数据库旨在继续保持MySQL数据库在<a href="https://zh.wikipedia.org/wiki/GNU%E9%80%9A%E7%94%A8%E5%85%AC%E5%85%B1%E8%AE%B8%E5%8F%AF%E8%AF%81">GNU GPL</a>下开源。如果要使用MariaDB作为MySQL的替代品，可以使用下面的命令进行安装。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install mariadb mariadb-server</span><br></pre></td></tr></table></figure></li><li><p>如果要安装官方版本的MySQL，可以在<a href="https://www.mysql.com/">MySQL官方网站</a>下载安装文件。首先在下载页面中选择平台和版本，然后找到对应的下载链接。下面以MySQL 5.7.26版本和Red Hat Enterprise Linux为例，直接下载包含所有安装文件的归档文件，解归档之后通过包管理工具进行安装。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar</span><br><span class="line">tar -xvf mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar</span><br></pre></td></tr></table></figure><p>如果系统上有MariaDB相关的文件，需要先移除MariaDB相关的文件。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum list installed | grep mariadb | awk &#x27;&#123;print $1&#125;&#x27; | xargs yum erase -y</span><br></pre></td></tr></table></figure><p>接下来可以按照如下所示的顺序用RPM（Redhat Package Manager）工具安装MySQL。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm</span><br><span class="line">rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm</span><br><span class="line">rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm</span><br><span class="line">rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm</span><br></pre></td></tr></table></figure><p>可以使用下面的命令查看已经安装的MySQL相关的包。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rpm -qa | grep mysql</span><br></pre></td></tr></table></figure></li><li><p>配置MySQL。</p><p>MySQL的配置文件在<code>/etc</code>目录下，名为<code>my.cnf</code>，默认的配置文件内容如下所示。如果对这个文件不理解并没有关系，什么时候用到这个配置文件什么时候再了解它就行了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /etc/my.cnf</span><br></pre></td></tr></table></figure><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># For advice on how to change settings please see</span></span><br><span class="line"><span class="comment"># http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html</span></span><br><span class="line"></span><br><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Remove leading # and set to the amount of RAM for the most important data</span></span><br><span class="line"><span class="comment"># cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.</span></span><br><span class="line"><span class="comment"># innodb_buffer_pool_size = 128M</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Remove leading # to turn on a very important data integrity option: logging</span></span><br><span class="line"><span class="comment"># changes to the binary log between backups.</span></span><br><span class="line"><span class="comment"># log_bin</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Remove leading # to set options mainly useful for reporting servers.</span></span><br><span class="line"><span class="comment"># The server defaults are faster for transactions and fast SELECTs.</span></span><br><span class="line"><span class="comment"># Adjust sizes as needed, experiment to find the optimal values.</span></span><br><span class="line"><span class="comment"># join_buffer_size = 128M</span></span><br><span class="line"><span class="comment"># sort_buffer_size = 2M</span></span><br><span class="line"><span class="comment"># read_rnd_buffer_size = 2M</span></span><br><span class="line"><span class="attr">datadir</span>=/var/lib/mysql</span><br><span class="line"><span class="attr">socket</span>=/var/lib/mysql/mysql.sock</span><br><span class="line"></span><br><span class="line"><span class="comment"># Disabling symbolic-links is recommended to prevent assorted security risks</span></span><br><span class="line"><span class="attr">symbolic-links</span>=<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="attr">log-error</span>=/var/log/mysqld.log</span><br><span class="line"><span class="attr">pid-file</span>=/var/run/mysqld/mysqld.pid</span><br></pre></td></tr></table></figure></li><li><p>启动MySQL服务。</p><p>可以使用下面的命令来启动MySQL。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">service mysqld start</span><br></pre></td></tr></table></figure><p>在CentOS 7中，更推荐使用下面的命令来启动MySQL。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start mysqld</span><br></pre></td></tr></table></figure><p>启动MySQL成功后，可以通过下面的命令来检查网络端口使用情况，MySQL默认使用3306端口。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netstat -ntlp | grep mysql</span><br></pre></td></tr></table></figure><p>也可以使用下面的命令查找是否有名为mysqld的进程。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pgrep mysqld</span><br></pre></td></tr></table></figure></li><li><p>使用MySQL客户端工具连接服务器。</p><p>命令行工具：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql -u root -p</span><br></pre></td></tr></table></figure><blockquote><p>说明：启动客户端时，<code>-u</code>参数用来指定用户名，MySQL默认的超级管理账号为<code>root</code>；<code>-p</code>表示要输入密码（用户口令）；如果连接的是其他主机而非本机，可以用<code>-h</code>来指定连接主机的主机名或IP地址。</p></blockquote><p>如果是首次安装MySQL，可以使用下面的命令来找到默认的初始密码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /var/log/mysqld.log | grep password</span><br></pre></td></tr></table></figure><p>上面的命令会查看MySQL的日志带有password的行，在显示的结果中<code>root@localhost:</code>后面的部分就是默认设置的初始密码。</p><p>修改超级管理员（root）的访问口令为<code>123456</code>。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">set</span> <span class="keyword">global</span> validate_password_policy<span class="operator">=</span><span class="number">0</span>;</span><br><span class="line"><span class="keyword">set</span> <span class="keyword">global</span> validate_password_length<span class="operator">=</span><span class="number">6</span>;</span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">user</span> <span class="string">&#x27;root&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> identified <span class="keyword">by</span> <span class="string">&#x27;123456&#x27;</span>;</span><br></pre></td></tr></table></figure><blockquote><p><strong>说明</strong>：MySQL较新的版本默认不允许使用弱口令作为用户口令，所以我们通过上面的前两条命令修改了验证用户口令的策略和口令的长度。事实上我们不应该使用弱口令，因为存在用户口令被暴力破解的风险。近年来，攻击数据库窃取数据和劫持数据库勒索比特币的事件屡见不鲜，要避免这些潜在的风险，最为重要的一点是不要让数据库服务器暴露在公网上（最好的做法是将数据库置于内网，至少要做到不向公网开放数据库服务器的访问端口），另外要保管好<code>root</code>账号的口令，应用系统需要访问数据库时，通常不使用<code>root</code>账号进行访问，而是创建其他拥有适当权限的账号来访问。</p></blockquote><p>再次使用客户端工具连接MySQL服务器时，就可以使用新设置的口令了。在实际开发中，为了方便用户操作，可以选择图形化的客户端工具来连接MySQL服务器，包括：</p><ul><li>MySQL Workbench（官方提供的工具）</li><li>Navicat for MySQL（界面简单优雅，功能直观强大）</li><li>SQLyog for MySQL（强大的MySQL数据库管理员工具）</li></ul></li></ul></li><li><p>常用命令。</p><ul><li><p>查看服务器版本。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> version();</span><br></pre></td></tr></table></figure></li><li><p>查看所有数据库。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">show</span> databases;</span><br></pre></td></tr></table></figure></li><li><p>切换到指定数据库。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">use mysql;</span><br></pre></td></tr></table></figure></li><li><p>查看数据库下所有表。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show tables;</span><br></pre></td></tr></table></figure></li><li><p>获取帮助。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">? contents;</span><br><span class="line">? functions;</span><br><span class="line">? <span class="type">numeric</span> functions;</span><br><span class="line">? round;</span><br><span class="line"></span><br><span class="line">? data types;</span><br><span class="line">? longblob;</span><br></pre></td></tr></table></figure></li></ul></li></ol><h3 id="SQL详解"><a href="#SQL详解" class="headerlink" title="SQL详解"></a>SQL详解</h3><h4 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h4><p>我们通常可以将SQL分为三类：DDL（数据定义语言）、DML（数据操作语言）和DCL（数据控制语言）。DDL主要用于创建（create）、删除（drop）、修改（alter）数据库中的对象，比如创建、删除和修改二维表；DML主要负责插入数据（insert）、删除数据（delete）、更新数据（update）和查询（select）；DCL通常用于授予权限（grant）和召回权限（revoke）。</p><blockquote><p>说明：SQL是不区分大小写的语言，为了书写方便，下面的SQL都使用了小写字母来书写。</p></blockquote><ol><li><p>DDL（数据定义语言）</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 如果存在名为school的数据库就删除它</span></span><br><span class="line"><span class="keyword">drop</span> database if <span class="keyword">exists</span> school;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建名为school的数据库并设置默认的字符集和排序方式</span></span><br><span class="line"><span class="keyword">create</span> database school <span class="keyword">default</span> charset utf8;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 切换到school数据库上下文环境</span></span><br><span class="line">use school;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建学院表</span></span><br><span class="line"><span class="keyword">create table</span> tb_college</span><br><span class="line">(</span><br><span class="line">collid <span class="type">int</span> auto_increment comment <span class="string">&#x27;编号&#x27;</span>,</span><br><span class="line">collname <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;名称&#x27;</span>,</span><br><span class="line">collintro <span class="type">varchar</span>(<span class="number">500</span>) <span class="keyword">default</span> <span class="string">&#x27;&#x27;</span> comment <span class="string">&#x27;介绍&#x27;</span>,</span><br><span class="line"><span class="keyword">primary key</span> (collid)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建学生表</span></span><br><span class="line"><span class="keyword">create table</span> tb_student</span><br><span class="line">(</span><br><span class="line">stuid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;学号&#x27;</span>,</span><br><span class="line">stuname <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;姓名&#x27;</span>,</span><br><span class="line">stusex <span class="type">boolean</span> <span class="keyword">default</span> <span class="number">1</span> comment <span class="string">&#x27;性别&#x27;</span>,</span><br><span class="line">stubirth <span class="type">date</span> <span class="keyword">not null</span> comment <span class="string">&#x27;出生日期&#x27;</span>,</span><br><span class="line">stuaddr <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">default</span> <span class="string">&#x27;&#x27;</span> comment <span class="string">&#x27;籍贯&#x27;</span>,</span><br><span class="line">collid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;所属学院&#x27;</span>,</span><br><span class="line"><span class="keyword">primary key</span> (stuid),</span><br><span class="line"><span class="keyword">foreign key</span> (collid) <span class="keyword">references</span> tb_college (collid)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建教师表</span></span><br><span class="line"><span class="keyword">create table</span> tb_teacher</span><br><span class="line">(</span><br><span class="line">teaid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;工号&#x27;</span>,</span><br><span class="line">teaname <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;姓名&#x27;</span>,</span><br><span class="line">teatitle <span class="type">varchar</span>(<span class="number">10</span>) <span class="keyword">default</span> <span class="string">&#x27;助教&#x27;</span> comment <span class="string">&#x27;职称&#x27;</span>,</span><br><span class="line">collid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;所属学院&#x27;</span>,</span><br><span class="line"><span class="keyword">primary key</span> (teaid),</span><br><span class="line"><span class="keyword">foreign key</span> (collid) <span class="keyword">references</span> tb_college (collid)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建课程表</span></span><br><span class="line"><span class="keyword">create table</span> tb_course</span><br><span class="line">(</span><br><span class="line">couid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;编号&#x27;</span>,</span><br><span class="line">couname <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;名称&#x27;</span>,</span><br><span class="line">coucredit <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;学分&#x27;</span>,</span><br><span class="line">teaid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;授课老师&#x27;</span>,</span><br><span class="line"><span class="keyword">primary key</span> (couid),</span><br><span class="line"><span class="keyword">foreign key</span> (teaid) <span class="keyword">references</span> tb_teacher (teaid)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建选课记录表</span></span><br><span class="line"><span class="keyword">create table</span> tb_record</span><br><span class="line">(</span><br><span class="line">recid <span class="type">int</span> auto_increment comment <span class="string">&#x27;选课记录编号&#x27;</span>,</span><br><span class="line">sid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;选课学生&#x27;</span>,</span><br><span class="line">cid <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;所选课程&#x27;</span>,</span><br><span class="line">seldate datetime <span class="keyword">default</span> now() comment <span class="string">&#x27;选课时间日期&#x27;</span>,</span><br><span class="line">score <span class="type">decimal</span>(<span class="number">4</span>,<span class="number">1</span>) comment <span class="string">&#x27;考试成绩&#x27;</span>,</span><br><span class="line"><span class="keyword">primary key</span> (recid),</span><br><span class="line"><span class="keyword">foreign key</span> (sid) <span class="keyword">references</span> tb_student (stuid),</span><br><span class="line"><span class="keyword">foreign key</span> (cid) <span class="keyword">references</span> tb_course (couid),</span><br><span class="line"><span class="keyword">unique</span> (sid, cid)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>上面的DDL有几个地方需要强调一下：</p><ul><li><p>创建数据库时，我们通过<code>default charset utf8</code>指定了数据库默认使用的字符集，我们推荐使用该字符集，因为utf8能够支持国际化编码。如果将来数据库中用到的字符可能包括类似于Emoji这样的图片字符，也可以将默认字符集设定为utf8mb4（最大4字节的utf-8编码）。查看MySQL支持的字符集可以执行下面的语句。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">show</span> <span class="keyword">character set</span>;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">+----------+---------------------------------+---------------------+--------+</span><br><span class="line">| Charset  | Description                     | Default collation   | Maxlen |</span><br><span class="line">+----------+---------------------------------+---------------------+--------+</span><br><span class="line">| big5     | Big5 Traditional Chinese        | big5_chinese_ci     |      2 |</span><br><span class="line">| dec8     | DEC West European               | dec8_swedish_ci     |      1 |</span><br><span class="line">| cp850    | DOS West European               | cp850_general_ci    |      1 |</span><br><span class="line">| hp8      | HP West European                | hp8_english_ci      |      1 |</span><br><span class="line">| koi8r    | KOI8-R Relcom Russian           | koi8r_general_ci    |      1 |</span><br><span class="line">| latin1   | cp1252 West European            | latin1_swedish_ci   |      1 |</span><br><span class="line">| latin2   | ISO 8859-2 Central European     | latin2_general_ci   |      1 |</span><br><span class="line">| swe7     | 7bit Swedish                    | swe7_swedish_ci     |      1 |</span><br><span class="line">| ascii    | US ASCII                        | ascii_general_ci    |      1 |</span><br><span class="line">| ujis     | EUC-JP Japanese                 | ujis_japanese_ci    |      3 |</span><br><span class="line">| sjis     | Shift-JIS Japanese              | sjis_japanese_ci    |      2 |</span><br><span class="line">| hebrew   | ISO 8859-8 Hebrew               | hebrew_general_ci   |      1 |</span><br><span class="line">| tis620   | TIS620 Thai                     | tis620_thai_ci      |      1 |</span><br><span class="line">| euckr    | EUC-KR Korean                   | euckr_korean_ci     |      2 |</span><br><span class="line">| koi8u    | KOI8-U Ukrainian                | koi8u_general_ci    |      1 |</span><br><span class="line">| gb2312   | GB2312 Simplified Chinese       | gb2312_chinese_ci   |      2 |</span><br><span class="line">| greek    | ISO 8859-7 Greek                | greek_general_ci    |      1 |</span><br><span class="line">| cp1250   | Windows Central European        | cp1250_general_ci   |      1 |</span><br><span class="line">| gbk      | GBK Simplified Chinese          | gbk_chinese_ci      |      2 |</span><br><span class="line">| latin5   | ISO 8859-9 Turkish              | latin5_turkish_ci   |      1 |</span><br><span class="line">| armscii8 | ARMSCII-8 Armenian              | armscii8_general_ci |      1 |</span><br><span class="line">| utf8     | UTF-8 Unicode                   | utf8_general_ci     |      3 |</span><br><span class="line">| ucs2     | UCS-2 Unicode                   | ucs2_general_ci     |      2 |</span><br><span class="line">| cp866    | DOS Russian                     | cp866_general_ci    |      1 |</span><br><span class="line">| keybcs2  | DOS Kamenicky Czech-Slovak      | keybcs2_general_ci  |      1 |</span><br><span class="line">| macce    | Mac Central European            | macce_general_ci    |      1 |</span><br><span class="line">| macroman | Mac West European               | macroman_general_ci |      1 |</span><br><span class="line">| cp852    | DOS Central European            | cp852_general_ci    |      1 |</span><br><span class="line">| latin7   | ISO 8859-13 Baltic              | latin7_general_ci   |      1 |</span><br><span class="line">| utf8mb4  | UTF-8 Unicode                   | utf8mb4_general_ci  |      4 |</span><br><span class="line">| cp1251   | Windows Cyrillic                | cp1251_general_ci   |      1 |</span><br><span class="line">| utf16    | UTF-16 Unicode                  | utf16_general_ci    |      4 |</span><br><span class="line">| utf16le  | UTF-16LE Unicode                | utf16le_general_ci  |      4 |</span><br><span class="line">| cp1256   | Windows Arabic                  | cp1256_general_ci   |      1 |</span><br><span class="line">| cp1257   | Windows Baltic                  | cp1257_general_ci   |      1 |</span><br><span class="line">| utf32    | UTF-32 Unicode                  | utf32_general_ci    |      4 |</span><br><span class="line">| binary   | Binary pseudo charset           | binary              |      1 |</span><br><span class="line">| geostd8  | GEOSTD8 Georgian                | geostd8_general_ci  |      1 |</span><br><span class="line">| cp932    | SJIS for Windows Japanese       | cp932_japanese_ci   |      2 |</span><br><span class="line">| eucjpms  | UJIS for Windows Japanese       | eucjpms_japanese_ci |      3 |</span><br><span class="line">| gb18030  | China National Standard GB18030 | gb18030_chinese_ci  |      4 |</span><br><span class="line">+----------+---------------------------------+---------------------+--------+</span><br><span class="line">41 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>如果要设置MySQL服务启动时默认使用的字符集，可以修改MySQL的配置并添加以下内容</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">character-set-server</span>=utf8</span><br></pre></td></tr></table></figure></li><li><p>在创建表的时候，我们可以在右圆括号的后面通过<code>engine=XXX</code>来指定表的存储引擎，MySQL支持多种存储引擎，可以通过<code>show engines</code>命令进行查看。MySQL 5.5以后的版本默认使用的存储引擎是InnoDB，它正好也就是我们推荐大家使用的存储引擎（因为InnoDB更适合互联网应用对高并发、性能以及事务支持等方面的需求）。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">show</span> engines\G</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">*************************** 1. row ***************************</span><br><span class="line">      Engine: InnoDB</span><br><span class="line">     Support: DEFAULT</span><br><span class="line">     Comment: Supports transactions, row-level locking, and foreign keys</span><br><span class="line">Transactions: YES</span><br><span class="line">          XA: YES</span><br><span class="line">  Savepoints: YES</span><br><span class="line">*************************** 2. row ***************************</span><br><span class="line">      Engine: MRG_MYISAM</span><br><span class="line">     Support: YES</span><br><span class="line">     Comment: Collection of identical MyISAM tables</span><br><span class="line">Transactions: NO</span><br><span class="line">          XA: NO</span><br><span class="line">  Savepoints: NO</span><br><span class="line">*************************** 3. row ***************************</span><br><span class="line">      Engine: MEMORY</span><br><span class="line">     Support: YES</span><br><span class="line">     Comment: Hash based, stored in memory, useful for temporary tables</span><br><span class="line">Transactions: NO</span><br><span class="line">          XA: NO</span><br><span class="line">  Savepoints: NO</span><br><span class="line">*************************** 4. row ***************************</span><br><span class="line">      Engine: BLACKHOLE</span><br><span class="line">     Support: YES</span><br><span class="line">     Comment: /dev/null storage engine (anything you write to it disappears)</span><br><span class="line">Transactions: NO</span><br><span class="line">          XA: NO</span><br><span class="line">  Savepoints: NO</span><br><span class="line">*************************** 5. row ***************************</span><br><span class="line">      Engine: MyISAM</span><br><span class="line">     Support: YES</span><br><span class="line">     Comment: MyISAM storage engine</span><br><span class="line">Transactions: NO</span><br><span class="line">          XA: NO</span><br><span class="line">  Savepoints: NO</span><br><span class="line">*************************** 6. row ***************************</span><br><span class="line">      Engine: CSV</span><br><span class="line">     Support: YES</span><br><span class="line">     Comment: CSV storage engine</span><br><span class="line">Transactions: NO</span><br><span class="line">          XA: NO</span><br><span class="line">  Savepoints: NO</span><br><span class="line">*************************** 7. row ***************************</span><br><span class="line">      Engine: ARCHIVE</span><br><span class="line">     Support: YES</span><br><span class="line">     Comment: Archive storage engine</span><br><span class="line">Transactions: NO</span><br><span class="line">          XA: NO</span><br><span class="line">  Savepoints: NO</span><br><span class="line">*************************** 8. row ***************************</span><br><span class="line">      Engine: PERFORMANCE_SCHEMA</span><br><span class="line">     Support: YES</span><br><span class="line">     Comment: Performance Schema</span><br><span class="line">Transactions: NO</span><br><span class="line">          XA: NO</span><br><span class="line">  Savepoints: NO</span><br><span class="line">*************************** 9. row ***************************</span><br><span class="line">      Engine: FEDERATED</span><br><span class="line">     Support: NO</span><br><span class="line">     Comment: Federated MySQL storage engine</span><br><span class="line">Transactions: NULL</span><br><span class="line">          XA: NULL</span><br><span class="line">  Savepoints: NULL</span><br><span class="line">9 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>下面的表格对MySQL几种常用的数据引擎进行了简单的对比。</p><table><thead><tr><th>特性</th><th>InnoDB</th><th>MRG_MYISAM</th><th>MEMORY</th><th>MyISAM</th></tr></thead><tbody><tr><td>存储限制</td><td>有</td><td>没有</td><td>有</td><td>有</td></tr><tr><td>事务</td><td>支持</td><td></td><td></td><td></td></tr><tr><td>锁机制</td><td>行锁</td><td>表锁</td><td>表锁</td><td>表锁</td></tr><tr><td>B树索引</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td></tr><tr><td>哈希索引</td><td></td><td></td><td>支持</td><td></td></tr><tr><td>全文检索</td><td>支持（5.6+）</td><td></td><td></td><td>支持</td></tr><tr><td>集群索引</td><td>支持</td><td></td><td></td><td></td></tr><tr><td>数据缓存</td><td>支持</td><td></td><td>支持</td><td></td></tr><tr><td>索引缓存</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td></tr><tr><td>数据可压缩</td><td></td><td></td><td></td><td>支持</td></tr><tr><td>内存使用</td><td>高</td><td>低</td><td>中</td><td>低</td></tr><tr><td>存储空间使用</td><td>高</td><td>低</td><td></td><td>低</td></tr><tr><td>批量插入性能</td><td>低</td><td>高</td><td>高</td><td>高</td></tr><tr><td>是否支持外键</td><td>支持</td><td></td><td></td><td></td></tr></tbody></table><p>通过上面的比较我们可以了解到，InnoDB是唯一能够支持外键、事务以及行锁的存储引擎，所以我们之前说它更适合互联网应用，而且它也是较新的MySQL版本中默认使用的存储引擎。</p></li><li><p>在定义表结构为每个字段选择数据类型时，如果不清楚哪个数据类型更合适，可以通过MySQL的帮助系统来了解每种数据类型的特性、数据的长度和精度等相关信息。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">? data types</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">You asked for help about help category: &quot;Data Types&quot;</span><br><span class="line">For more information, type &#x27;help &lt;item&gt;&#x27;, where &lt;item&gt; is one of the following</span><br><span class="line">topics:</span><br><span class="line">   AUTO_INCREMENT</span><br><span class="line">   BIGINT</span><br><span class="line">   BINARY</span><br><span class="line">   BIT</span><br><span class="line">   BLOB</span><br><span class="line">   BLOB DATA TYPE</span><br><span class="line">   BOOLEAN</span><br><span class="line">   CHAR</span><br><span class="line">   CHAR BYTE</span><br><span class="line">   DATE</span><br><span class="line">   DATETIME</span><br><span class="line">   DEC</span><br><span class="line">   DECIMAL</span><br><span class="line">   DOUBLE</span><br><span class="line">   DOUBLE PRECISION</span><br><span class="line">   ENUM</span><br><span class="line">   FLOAT</span><br><span class="line">   INT</span><br><span class="line">   INTEGER</span><br><span class="line">   LONGBLOB</span><br><span class="line">   LONGTEXT</span><br><span class="line">   MEDIUMBLOB</span><br><span class="line">   MEDIUMINT</span><br><span class="line">   MEDIUMTEXT</span><br><span class="line">   SET DATA TYPE</span><br><span class="line">   SMALLINT</span><br><span class="line">   TEXT</span><br><span class="line">   TIME</span><br><span class="line">   TIMESTAMP</span><br><span class="line">   TINYBLOB</span><br><span class="line">   TINYINT</span><br><span class="line">   TINYTEXT</span><br><span class="line">   VARBINARY</span><br><span class="line">   VARCHAR</span><br><span class="line">   YEAR DATA TYPE</span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">? <span class="type">varchar</span></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">Name: &#x27;VARCHAR&#x27;</span><br><span class="line">Description:</span><br><span class="line">[NATIONAL] VARCHAR(M) [CHARACTER SET charset_name] [COLLATE</span><br><span class="line">collation_name]</span><br><span class="line"></span><br><span class="line">A variable-length string. M represents the maximum column length in</span><br><span class="line">characters. The range of M is 0 to 65,535. The effective maximum length</span><br><span class="line">of a VARCHAR is subject to the maximum row size (65,535 bytes, which is</span><br><span class="line">shared among all columns) and the character set used. For example, utf8</span><br><span class="line">characters can require up to three bytes per character, so a VARCHAR</span><br><span class="line">column that uses the utf8 character set can be declared to be a maximum</span><br><span class="line">of 21,844 characters. See</span><br><span class="line">http://dev.mysql.com/doc/refman/5.7/en/column-count-limit.html.</span><br><span class="line"></span><br><span class="line">MySQL stores VARCHAR values as a 1-byte or 2-byte length prefix plus</span><br><span class="line">data. The length prefix indicates the number of bytes in the value. A</span><br><span class="line">VARCHAR column uses one length byte if values require no more than 255</span><br><span class="line">bytes, two length bytes if values may require more than 255 bytes.</span><br><span class="line"></span><br><span class="line">*Note*:</span><br><span class="line"></span><br><span class="line">MySQL follows the standard SQL specification, and does not remove</span><br><span class="line">trailing spaces from VARCHAR values.</span><br><span class="line"></span><br><span class="line">VARCHAR is shorthand for CHARACTER VARYING. NATIONAL VARCHAR is the</span><br><span class="line">standard SQL way to define that a VARCHAR column should use some</span><br><span class="line">predefined character set. MySQL uses utf8 as this predefined character</span><br><span class="line">set. http://dev.mysql.com/doc/refman/5.7/en/charset-national.html.</span><br><span class="line">NVARCHAR is shorthand for NATIONAL VARCHAR.</span><br><span class="line"></span><br><span class="line">URL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html</span><br></pre></td></tr></table></figure><p>在数据类型的选择上，保存字符串数据通常都使用VARCHAR和CHAR两种类型，前者通常称为变长字符串，而后者通常称为定长字符串；对于InnoDB存储引擎，行存储格式没有区分固定长度和可变长度列，因此VARCHAR类型好CHAR类型没有本质区别，后者不一定比前者性能更好。如果要保存的很大字符串，可以使用TEXT类型；如果要保存很大的字节串，可以使用BLOB（二进制大对象）类型。在MySQL中，TEXT和BLOB又分别包括TEXT、MEDIUMTEXT、LONGTEXT和BLOB、MEDIUMBLOB、LONGBLOB三种不同的类型，它们主要的区别在于存储数据的最大大小不同。保存浮点数可以用FLOAT或DOUBLE类型，而保存定点数应该使用DECIMAL类型。如果要保存时间日期，DATETIME类型优于TIMESTAMP类型，因为前者能表示的时间日期范围更大。</p></li></ul></li><li><p>DML</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 插入学院数据</span></span><br><span class="line"><span class="keyword">insert into</span> tb_college (collname, collintro) <span class="keyword">values</span> </span><br><span class="line">(<span class="string">&#x27;计算机学院&#x27;</span>, <span class="string">&#x27;创建于1956年是我国首批建立计算机专业。学院现有计算机科学与技术一级学科和网络空间安全一级学科博士学位授予权，其中计算机科学与技术一级学科具有博士后流动站。计算机科学与技术一级学科在2017年全国第四轮学科评估中评为A；2019 U.S.News全球计算机学科排名26名；ESI学科排名0.945‰，进入全球前1‰，位列第43位。&#x27;</span>),</span><br><span class="line">(<span class="string">&#x27;外国语学院&#x27;</span>, <span class="string">&#x27;1998年浙江大学、杭州大学、浙江农业大学、浙江医科大学四校合并，成立新的浙江大学。1999年原浙江大学外语系、原杭州大学外国语学院、原杭州大学大外部、原浙江农业大学公外部、原浙江医科大学外语教学部合并，成立浙江大学外国语学院。2003年学院更名为浙江大学外国语言文化与国际交流学院。&#x27;</span>),</span><br><span class="line">(<span class="string">&#x27;经济管理学院&#x27;</span>, <span class="string">&#x27;四川大学经济学院历史悠久、传承厚重，其前身是创办于1905年的四川大学经济科,距今已有100多年的历史。已故著名经济学家彭迪先、张与九、蒋学模、胡寄窗、陶大镛、胡代光，以及当代著名学者刘诗白等曾先后在此任教或学习。在长期的办学过程中，学院坚持以马克思主义的立场、观点、方法为指导，围绕建设世界一流经济学院的奋斗目标，做实“两个伟大”深度融合，不断提高党的建设质量与科学推进一流事业深度融合。&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入学生数据</span></span><br><span class="line"><span class="keyword">insert into</span> tb_student (stuid, stuname, stusex, stubirth, stuaddr, collid) <span class="keyword">values</span></span><br><span class="line">(<span class="number">1001</span>, <span class="string">&#x27;杨逍&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1990-3-4&#x27;</span>, <span class="string">&#x27;四川成都&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">1002</span>, <span class="string">&#x27;任我行&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1992-2-2&#x27;</span>, <span class="string">&#x27;湖南长沙&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">1033</span>, <span class="string">&#x27;王语嫣&#x27;</span>, <span class="number">0</span>, <span class="string">&#x27;1989-12-3&#x27;</span>, <span class="string">&#x27;四川成都&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">1572</span>, <span class="string">&#x27;岳不群&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1993-7-19&#x27;</span>, <span class="string">&#x27;陕西咸阳&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">1378</span>, <span class="string">&#x27;纪嫣然&#x27;</span>, <span class="number">0</span>, <span class="string">&#x27;1995-8-12&#x27;</span>, <span class="string">&#x27;四川绵阳&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">1954</span>, <span class="string">&#x27;林平之&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1994-9-20&#x27;</span>, <span class="string">&#x27;福建莆田&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">2035</span>, <span class="string">&#x27;东方不败&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1988-6-30&#x27;</span>, <span class="keyword">null</span>, <span class="number">2</span>),</span><br><span class="line">(<span class="number">3011</span>, <span class="string">&#x27;林震南&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1985-12-12&#x27;</span>, <span class="string">&#x27;福建莆田&#x27;</span>, <span class="number">3</span>),</span><br><span class="line">(<span class="number">3755</span>, <span class="string">&#x27;项少龙&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1993-1-25&#x27;</span>, <span class="keyword">null</span>, <span class="number">3</span>),</span><br><span class="line">(<span class="number">3923</span>, <span class="string">&#x27;杨不悔&#x27;</span>, <span class="number">0</span>, <span class="string">&#x27;1985-4-17&#x27;</span>, <span class="string">&#x27;四川成都&#x27;</span>, <span class="number">3</span>),</span><br><span class="line">(<span class="number">4040</span>, <span class="string">&#x27;隔壁老王&#x27;</span>, <span class="number">1</span>, <span class="string">&#x27;1989-1-1&#x27;</span>, <span class="string">&#x27;四川成都&#x27;</span>, <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除学生数据</span></span><br><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuid<span class="operator">=</span><span class="number">4040</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 更新学生数据</span></span><br><span class="line"><span class="keyword">update</span> tb_student <span class="keyword">set</span> stuname<span class="operator">=</span><span class="string">&#x27;杨过&#x27;</span>, stuaddr<span class="operator">=</span><span class="string">&#x27;湖南长沙&#x27;</span> <span class="keyword">where</span> stuid<span class="operator">=</span><span class="number">1001</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入老师数据</span></span><br><span class="line"><span class="keyword">insert into</span> tb_teacher (teaid, teaname, teatitle, collid) <span class="keyword">values</span> </span><br><span class="line">(<span class="number">1122</span>, <span class="string">&#x27;张三丰&#x27;</span>, <span class="string">&#x27;教授&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">1133</span>, <span class="string">&#x27;宋远桥&#x27;</span>, <span class="string">&#x27;副教授&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">1144</span>, <span class="string">&#x27;杨逍&#x27;</span>, <span class="string">&#x27;副教授&#x27;</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">2255</span>, <span class="string">&#x27;范遥&#x27;</span>, <span class="string">&#x27;副教授&#x27;</span>, <span class="number">2</span>),</span><br><span class="line">(<span class="number">3366</span>, <span class="string">&#x27;韦一笑&#x27;</span>, <span class="string">&#x27;讲师&#x27;</span>, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入课程数据</span></span><br><span class="line"><span class="keyword">insert into</span> tb_course (couid, couname, coucredit, teaid) <span class="keyword">values</span> </span><br><span class="line">(<span class="number">1111</span>, <span class="string">&#x27;Python程序设计&#x27;</span>, <span class="number">3</span>, <span class="number">1122</span>),</span><br><span class="line">(<span class="number">2222</span>, <span class="string">&#x27;Web前端开发&#x27;</span>, <span class="number">2</span>, <span class="number">1122</span>),</span><br><span class="line">(<span class="number">3333</span>, <span class="string">&#x27;操作系统&#x27;</span>, <span class="number">4</span>, <span class="number">1122</span>),</span><br><span class="line">(<span class="number">4444</span>, <span class="string">&#x27;计算机网络&#x27;</span>, <span class="number">2</span>, <span class="number">1133</span>),</span><br><span class="line">(<span class="number">5555</span>, <span class="string">&#x27;编译原理&#x27;</span>, <span class="number">4</span>, <span class="number">1144</span>),</span><br><span class="line">(<span class="number">6666</span>, <span class="string">&#x27;算法和数据结构&#x27;</span>, <span class="number">3</span>, <span class="number">1144</span>),</span><br><span class="line">(<span class="number">7777</span>, <span class="string">&#x27;经贸法语&#x27;</span>, <span class="number">3</span>, <span class="number">2255</span>),</span><br><span class="line">(<span class="number">8888</span>, <span class="string">&#x27;成本会计&#x27;</span>, <span class="number">2</span>, <span class="number">3366</span>),</span><br><span class="line">(<span class="number">9999</span>, <span class="string">&#x27;审计学&#x27;</span>, <span class="number">3</span>, <span class="number">3366</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插入选课数据</span></span><br><span class="line"><span class="keyword">insert into</span> tb_record (sid, cid, seldate, score) <span class="keyword">values</span> </span><br><span class="line">(<span class="number">1001</span>, <span class="number">1111</span>, <span class="string">&#x27;2017-09-01&#x27;</span>, <span class="number">95</span>),</span><br><span class="line">(<span class="number">1001</span>, <span class="number">2222</span>, <span class="string">&#x27;2017-09-01&#x27;</span>, <span class="number">87.5</span>),</span><br><span class="line">(<span class="number">1001</span>, <span class="number">3333</span>, <span class="string">&#x27;2017-09-01&#x27;</span>, <span class="number">100</span>),</span><br><span class="line">(<span class="number">1001</span>, <span class="number">4444</span>, <span class="string">&#x27;2018-09-03&#x27;</span>, <span class="keyword">null</span>),</span><br><span class="line">(<span class="number">1001</span>, <span class="number">6666</span>, <span class="string">&#x27;2017-09-02&#x27;</span>, <span class="number">100</span>),</span><br><span class="line">(<span class="number">1002</span>, <span class="number">1111</span>, <span class="string">&#x27;2017-09-03&#x27;</span>, <span class="number">65</span>),</span><br><span class="line">(<span class="number">1002</span>, <span class="number">5555</span>, <span class="string">&#x27;2017-09-01&#x27;</span>, <span class="number">42</span>),</span><br><span class="line">(<span class="number">1033</span>, <span class="number">1111</span>, <span class="string">&#x27;2017-09-03&#x27;</span>, <span class="number">92.5</span>),</span><br><span class="line">(<span class="number">1033</span>, <span class="number">4444</span>, <span class="string">&#x27;2017-09-01&#x27;</span>, <span class="number">78</span>),</span><br><span class="line">(<span class="number">1033</span>, <span class="number">5555</span>, <span class="string">&#x27;2017-09-01&#x27;</span>, <span class="number">82.5</span>),</span><br><span class="line">(<span class="number">1572</span>, <span class="number">1111</span>, <span class="string">&#x27;2017-09-02&#x27;</span>, <span class="number">78</span>),</span><br><span class="line">(<span class="number">1378</span>, <span class="number">1111</span>, <span class="string">&#x27;2017-09-05&#x27;</span>, <span class="number">82</span>),</span><br><span class="line">(<span class="number">1378</span>, <span class="number">7777</span>, <span class="string">&#x27;2017-09-02&#x27;</span>, <span class="number">65.5</span>),</span><br><span class="line">(<span class="number">2035</span>, <span class="number">7777</span>, <span class="string">&#x27;2018-09-03&#x27;</span>, <span class="number">88</span>),</span><br><span class="line">(<span class="number">2035</span>, <span class="number">9999</span>, <span class="keyword">default</span>, <span class="keyword">null</span>),</span><br><span class="line">(<span class="number">3755</span>, <span class="number">1111</span>, <span class="keyword">default</span>, <span class="keyword">null</span>),</span><br><span class="line">(<span class="number">3755</span>, <span class="number">8888</span>, <span class="keyword">default</span>, <span class="keyword">null</span>),</span><br><span class="line">(<span class="number">3755</span>, <span class="number">9999</span>, <span class="string">&#x27;2017-09-01&#x27;</span>, <span class="number">92</span>);</span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查询所有学生信息</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_student;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询所有课程名称及学分(投影和别名)</span></span><br><span class="line"><span class="keyword">select</span> couname, coucredit <span class="keyword">from</span> tb_course;</span><br><span class="line"><span class="keyword">select</span> couname <span class="keyword">as</span> 课程名称, coucredit <span class="keyword">as</span> 学分 <span class="keyword">from</span> tb_course;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询所有学生的姓名和性别(条件运算)</span></span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">as</span> 姓名, <span class="keyword">case</span> stusex <span class="keyword">when</span> <span class="number">1</span> <span class="keyword">then</span> <span class="string">&#x27;男&#x27;</span> <span class="keyword">else</span> <span class="string">&#x27;女&#x27;</span> <span class="keyword">end</span> <span class="keyword">as</span> 性别 <span class="keyword">from</span> tb_student;</span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">as</span> 姓名, if(stusex, <span class="string">&#x27;男&#x27;</span>, <span class="string">&#x27;女&#x27;</span>) <span class="keyword">as</span> 性别 <span class="keyword">from</span> tb_student;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询所有女学生的姓名和出生日期(筛选)</span></span><br><span class="line"><span class="keyword">select</span> stuname, stubirth <span class="keyword">from</span> tb_student <span class="keyword">where</span> stusex<span class="operator">=</span><span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询所有80后学生的姓名、性别和出生日期(筛选)</span></span><br><span class="line"><span class="keyword">select</span> stuname, stusex, stubirth <span class="keyword">from</span> tb_student <span class="keyword">where</span> stubirth<span class="operator">&gt;=</span><span class="string">&#x27;1980-1-1&#x27;</span> <span class="keyword">and</span> stubirth<span class="operator">&lt;=</span><span class="string">&#x27;1989-12-31&#x27;</span>;</span><br><span class="line"><span class="keyword">select</span> stuname, stusex, stubirth <span class="keyword">from</span> tb_student <span class="keyword">where</span> stubirth <span class="keyword">between</span> <span class="string">&#x27;1980-1-1&#x27;</span> <span class="keyword">and</span> <span class="string">&#x27;1989-12-31&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询姓&quot;杨&quot;的学生姓名和性别(模糊)</span></span><br><span class="line"><span class="keyword">select</span> stuname, stusex <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuname <span class="keyword">like</span> <span class="string">&#x27;杨%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询姓&quot;杨&quot;名字两个字的学生姓名和性别(模糊)</span></span><br><span class="line"><span class="keyword">select</span> stuname, stusex <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuname <span class="keyword">like</span> <span class="string">&#x27;杨_&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询姓&quot;杨&quot;名字三个字的学生姓名和性别(模糊)</span></span><br><span class="line"><span class="keyword">select</span> stuname, stusex <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuname <span class="keyword">like</span> <span class="string">&#x27;杨__&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询名字中有&quot;不&quot;字或&quot;嫣&quot;字的学生的姓名(模糊)</span></span><br><span class="line"><span class="keyword">select</span> stuname, stusex <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuname <span class="keyword">like</span> <span class="string">&#x27;%不%&#x27;</span> <span class="keyword">or</span> stuname <span class="keyword">like</span> <span class="string">&#x27;%嫣%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询没有录入家庭住址的学生姓名(空值)</span></span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuaddr <span class="keyword">is</span> <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询录入了家庭住址的学生姓名(空值)</span></span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuaddr <span class="keyword">is</span> <span class="keyword">not null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询学生选课的所有日期(去重)</span></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> seldate <span class="keyword">from</span> tb_record;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询学生的家庭住址(去重)</span></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> stuaddr <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuaddr <span class="keyword">is</span> <span class="keyword">not null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询男学生的姓名和生日按年龄从大到小排列(排序)</span></span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">as</span> 姓名, datediff(curdate(), stubirth) div <span class="number">365</span> <span class="keyword">as</span> 年龄 <span class="keyword">from</span> tb_student <span class="keyword">where</span> stusex<span class="operator">=</span><span class="number">1</span> <span class="keyword">order</span> <span class="keyword">by</span> 年龄 <span class="keyword">desc</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询年龄最大的学生的出生日期(聚合函数)</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">min</span>(stubirth) <span class="keyword">from</span> tb_student;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询年龄最小的学生的出生日期(聚合函数)</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">max</span>(stubirth) <span class="keyword">from</span> tb_student;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询男女学生的人数(分组和聚合函数)</span></span><br><span class="line"><span class="keyword">select</span> stusex, <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> tb_student <span class="keyword">group</span> <span class="keyword">by</span> stusex;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询课程编号为1111的课程的平均成绩(筛选和聚合函数)</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span>(score) <span class="keyword">from</span> tb_record <span class="keyword">where</span> cid<span class="operator">=</span><span class="number">1111</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询学号为1001的学生所有课程的平均分(筛选和聚合函数)</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span>(score) <span class="keyword">from</span> tb_record <span class="keyword">where</span> sid<span class="operator">=</span><span class="number">1001</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询每个学生的学号和平均成绩(分组和聚合函数)</span></span><br><span class="line"><span class="keyword">select</span> sid <span class="keyword">as</span> 学号, <span class="built_in">avg</span>(score) <span class="keyword">as</span> 平均分 <span class="keyword">from</span> tb_record <span class="keyword">group</span> <span class="keyword">by</span> sid;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询平均成绩大于等于90分的学生的学号和平均成绩</span></span><br><span class="line"><span class="comment">-- 分组以前的筛选使用where子句 / 分组以后的筛选使用having子句</span></span><br><span class="line"><span class="keyword">select</span> sid <span class="keyword">as</span> 学号, <span class="built_in">avg</span>(score) <span class="keyword">as</span> 平均分 <span class="keyword">from</span> tb_record <span class="keyword">group</span> <span class="keyword">by</span> sid <span class="keyword">having</span> 平均分<span class="operator">&gt;=</span><span class="number">90</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询年龄最大的学生的姓名(子查询/嵌套的查询)</span></span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">from</span> tb_student <span class="keyword">where</span> stubirth<span class="operator">=</span>( <span class="keyword">select</span> <span class="built_in">min</span>(stubirth) <span class="keyword">from</span> tb_student );</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询年龄最大的学生姓名和年龄(子查询+运算)</span></span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">as</span> 姓名, datediff(curdate(), stubirth) div <span class="number">365</span> <span class="keyword">as</span> 年龄 <span class="keyword">from</span> tb_student <span class="keyword">where</span> stubirth<span class="operator">=</span>( <span class="keyword">select</span> <span class="built_in">min</span>(stubirth) <span class="keyword">from</span> tb_student );</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询选了两门以上的课程的学生姓名(子查询/分组条件/集合运算)</span></span><br><span class="line"><span class="keyword">select</span> stuname <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuid <span class="keyword">in</span> ( <span class="keyword">select</span> stuid <span class="keyword">from</span> tb_record <span class="keyword">group</span> <span class="keyword">by</span> stuid <span class="keyword">having</span> <span class="built_in">count</span>(stuid)<span class="operator">&gt;</span><span class="number">2</span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询学生姓名、课程名称以及成绩(连接查询)</span></span><br><span class="line"><span class="keyword">select</span> stuname, couname, score <span class="keyword">from</span> tb_student t1, tb_course t2, tb_record t3 <span class="keyword">where</span> stuid<span class="operator">=</span>sid <span class="keyword">and</span> couid<span class="operator">=</span>cid <span class="keyword">and</span> score <span class="keyword">is</span> <span class="keyword">not null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询学生姓名、课程名称以及成绩按成绩从高到低查询第11-15条记录(内连接+分页)</span></span><br><span class="line"><span class="keyword">select</span> stuname, couname, score <span class="keyword">from</span> tb_student <span class="keyword">inner</span> <span class="keyword">join</span> tb_record <span class="keyword">on</span> stuid<span class="operator">=</span>sid <span class="keyword">inner</span> <span class="keyword">join</span> tb_course <span class="keyword">on</span> couid<span class="operator">=</span>cid <span class="keyword">where</span> score <span class="keyword">is</span> <span class="keyword">not null</span> <span class="keyword">order</span> <span class="keyword">by</span> score <span class="keyword">desc</span> limit <span class="number">5</span> <span class="keyword">offset</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> stuname, couname, score <span class="keyword">from</span> tb_student <span class="keyword">inner</span> <span class="keyword">join</span> tb_record <span class="keyword">on</span> stuid<span class="operator">=</span>sid <span class="keyword">inner</span> <span class="keyword">join</span> tb_course <span class="keyword">on</span> couid<span class="operator">=</span>cid <span class="keyword">where</span> score <span class="keyword">is</span> <span class="keyword">not null</span> <span class="keyword">order</span> <span class="keyword">by</span> score <span class="keyword">desc</span> limit <span class="number">10</span>, <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询选课学生的姓名和平均成绩(子查询和连接查询)</span></span><br><span class="line"><span class="keyword">select</span> stuname, avgmark <span class="keyword">from</span> tb_student, ( <span class="keyword">select</span> sid, <span class="built_in">avg</span>(score) <span class="keyword">as</span> avgmark <span class="keyword">from</span> tb_record <span class="keyword">group</span> <span class="keyword">by</span> sid ) temp <span class="keyword">where</span> stuid<span class="operator">=</span>sid;</span><br><span class="line"></span><br><span class="line"><span class="keyword">select</span> stuname, avgmark <span class="keyword">from</span> tb_student <span class="keyword">inner</span> <span class="keyword">join</span> ( <span class="keyword">select</span> sid, <span class="built_in">avg</span>(score) <span class="keyword">as</span> avgmark <span class="keyword">from</span> tb_record <span class="keyword">group</span> <span class="keyword">by</span> sid ) temp <span class="keyword">on</span> stuid<span class="operator">=</span>sid;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询每个学生的姓名和选课数量(左外连接和子查询)</span></span><br><span class="line"><span class="keyword">select</span> stuname, ifnull(total, <span class="number">0</span>) <span class="keyword">from</span> tb_student <span class="keyword">left</span> <span class="keyword">outer</span> <span class="keyword">join</span> ( <span class="keyword">select</span> sid, <span class="built_in">count</span>(sid) <span class="keyword">as</span> total <span class="keyword">from</span> tb_record <span class="keyword">group</span> <span class="keyword">by</span> sid ) temp <span class="keyword">on</span> stuid<span class="operator">=</span>sid;</span><br></pre></td></tr></table></figure><p>上面的DML有几个地方需要加以说明：</p><ol><li><p>MySQL中支持多种类型的运算符，包括：算术运算符（+、-、*、&#x2F;、%）、比较运算符（&#x3D;、&lt;&gt;、&lt;&#x3D;&gt;、&lt;、&lt;&#x3D;、&gt;、&gt;&#x3D;、BETWEEN…AND…、IN、IS NULL、IS NOT NULL、LIKE、RLIKE、REGEXP）、逻辑运算符（NOT、AND、OR、XOR）和位运算符（&amp;、|、^、~、&gt;&gt;、&lt;&lt;），我们可以在DML中使用这些运算符处理数据。</p></li><li><p>在查询数据时，可以在SELECT语句及其子句（如WHERE子句、ORDER BY子句、HAVING子句等）中使用函数，这些函数包括字符串函数、数值函数、时间日期函数、流程函数等，如下面的表格所示。</p><p>常用字符串函数。</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>CONCAT</td><td>将多个字符串连接成一个字符串</td></tr><tr><td>FORMAT</td><td>将数值格式化成字符串并指定保留几位小数</td></tr><tr><td>FROM_BASE64 &#x2F; TO_BASE64</td><td>BASE64解码&#x2F;编码</td></tr><tr><td>BIN &#x2F; OCT &#x2F; HEX</td><td>将数值转换成二进制&#x2F;八进制&#x2F;十六进制字符串</td></tr><tr><td>LOCATE</td><td>在字符串中查找一个子串的位置</td></tr><tr><td>LEFT &#x2F; RIGHT</td><td>返回一个字符串左边&#x2F;右边指定长度的字符</td></tr><tr><td>LENGTH &#x2F; CHAR_LENGTH</td><td>返回字符串的长度以字节&#x2F;字符为单位</td></tr><tr><td>LOWER &#x2F; UPPER</td><td>返回字符串的小写&#x2F;大写形式</td></tr><tr><td>LPAD &#x2F; RPAD</td><td>如果字符串的长度不足，在字符串左边&#x2F;右边填充指定的字符</td></tr><tr><td>LTRIM &#x2F; RTRIM</td><td>去掉字符串前面&#x2F;后面的空格</td></tr><tr><td>ORD &#x2F; CHAR</td><td>返回字符对应的编码&#x2F;返回编码对应的字符</td></tr><tr><td>STRCMP</td><td>比较字符串，返回-1、0、1分别表示小于、等于、大于</td></tr><tr><td>SUBSTRING</td><td>返回字符串指定范围的子串</td></tr></tbody></table><p>常用数值函数。</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>ABS</td><td>返回一个数的绝度值</td></tr><tr><td>CEILING &#x2F; FLOOR</td><td>返回一个数上取整&#x2F;下取整的结果</td></tr><tr><td>CONV</td><td>将一个数从一种进制转换成另一种进制</td></tr><tr><td>CRC32</td><td>计算循环冗余校验码</td></tr><tr><td>EXP &#x2F; LOG &#x2F; LOG2 &#x2F; LOG10</td><td>计算指数&#x2F;对数</td></tr><tr><td>POW</td><td>求幂</td></tr><tr><td>RAND</td><td>返回[0,1)范围的随机数</td></tr><tr><td>ROUND</td><td>返回一个数四舍五入后的结果</td></tr><tr><td>SQRT</td><td>返回一个数的平方根</td></tr><tr><td>TRUNCATE</td><td>截断一个数到指定的精度</td></tr><tr><td>SIN &#x2F; COS &#x2F; TAN &#x2F; COT &#x2F; ASIN &#x2F; ACOS &#x2F; ATAN</td><td>三角函数</td></tr></tbody></table><p>常用时间日期函数。</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>CURDATE &#x2F; CURTIME &#x2F; NOW</td><td>获取当前日期&#x2F;时间&#x2F;日期和时间</td></tr><tr><td>ADDDATE &#x2F; SUBDATE</td><td>将两个日期表达式相加&#x2F;相减并返回结果</td></tr><tr><td>DATE &#x2F; TIME</td><td>从字符串中获取日期&#x2F;时间</td></tr><tr><td>YEAR &#x2F; MONTH &#x2F; DAY</td><td>从日期中获取年&#x2F;月&#x2F;日</td></tr><tr><td>HOUR &#x2F; MINUTE &#x2F; SECOND</td><td>从时间中获取时&#x2F;分&#x2F;秒</td></tr><tr><td>DATEDIFF &#x2F; TIMEDIFF</td><td>返回两个时间日期表达式相差多少天&#x2F;小时</td></tr><tr><td>MAKEDATE &#x2F; MAKETIME</td><td>制造一个日期&#x2F;时间</td></tr></tbody></table><p>常用流程函数。</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>IF</td><td>根据条件是否成立返回不同的值</td></tr><tr><td>IFNULL</td><td>如果为NULL则返回指定的值否则就返回本身</td></tr><tr><td>NULLIF</td><td>两个表达式相等就返回NULL否则返回第一个表达式的值</td></tr></tbody></table><p>其他常用函数。</p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>MD5 &#x2F; SHA1 &#x2F; SHA2</td><td>返回字符串对应的哈希摘要</td></tr><tr><td>CHARSET &#x2F; COLLATION</td><td>返回字符集&#x2F;校对规则</td></tr><tr><td>USER &#x2F; CURRENT_USER</td><td>返回当前用户</td></tr><tr><td>DATABASE</td><td>返回当前数据库名</td></tr><tr><td>VERSION</td><td>返回当前数据库版本</td></tr><tr><td>FOUND_ROWS &#x2F; ROW_COUNT</td><td>返回查询到的行数&#x2F;受影响的行数</td></tr><tr><td>LAST_INSERT_ID</td><td>返回最后一个自增主键的值</td></tr><tr><td>UUID &#x2F; UUID_SHORT</td><td>返回全局唯一标识符</td></tr></tbody></table></li></ol></li><li><p>DCL</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 创建可以远程登录的root账号并为其指定口令</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">user</span> <span class="string">&#x27;root&#x27;</span>@<span class="string">&#x27;%&#x27;</span> identified <span class="keyword">by</span> <span class="string">&#x27;123456&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 为远程登录的root账号授权操作所有数据库所有对象的所有权限并允许其将权限再次赋予其他用户</span></span><br><span class="line"><span class="keyword">grant</span> <span class="keyword">all</span> privileges <span class="keyword">on</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">to</span> <span class="string">&#x27;root&#x27;</span>@<span class="string">&#x27;%&#x27;</span> <span class="keyword">with</span> <span class="keyword">grant</span> option;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建名为hellokitty的用户并为其指定口令</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">user</span> <span class="string">&#x27;hellokitty&#x27;</span>@<span class="string">&#x27;%&#x27;</span> identified <span class="keyword">by</span> <span class="string">&#x27;123123&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 将对school数据库所有对象的所有操作权限授予hellokitty</span></span><br><span class="line"><span class="keyword">grant</span> <span class="keyword">all</span> privileges <span class="keyword">on</span> school.<span class="operator">*</span> <span class="keyword">to</span> <span class="string">&#x27;hellokitty&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 召回hellokitty对school数据库所有对象的insert/delete/update权限</span></span><br><span class="line"><span class="keyword">revoke</span> <span class="keyword">insert</span>, <span class="keyword">delete</span>, <span class="keyword">update</span> <span class="keyword">on</span> school.<span class="operator">*</span> <span class="keyword">from</span> <span class="string">&#x27;hellokitty&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br></pre></td></tr></table></figure><blockquote><p>说明：创建一个可以允许任意主机登录并且具有超级管理员权限的用户在现实中并不是一个明智的决定，因为一旦该账号的口令泄露或者被破解，数据库将会面临灾难级的风险。</p></blockquote></li></ol><h4 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h4><p>索引是关系型数据库中用来提升查询性能最为重要的手段。关系型数据库中的索引就像一本书的目录，我们可以想象一下，如果要从一本书中找出某个知识点，但是这本书没有目录，这将是意见多么可怕的事情（我们估计得一篇一篇的翻下去，才能确定这个知识点到底在什么位置）。创建索引虽然会带来存储空间上的开销，就像一本书的目录会占用一部分的篇幅一样，但是在牺牲空间后换来的查询时间的减少也是非常显著的。</p><p>MySQL中，所有数据类型的列都可以被索引，常用的存储引擎InnoDB和MyISAM能支持每个表创建16个索引。InnoDB和MyISAM使用的索引其底层算法是B-tree（B树），B-tree是一种自平衡的树，类似于平衡二叉排序树，能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的操作都在对数时间内完成。</p><p>接下来我们通过一个简单的例子来说明索引的意义，比如我们要根据学生的姓名来查找学生，这个场景在实际开发中应该经常遇到，就跟通过商品名称查找商品道理是一样的。我们可以使用MySQL的<code>explain</code>关键字来查看SQL的执行计划。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuname<span class="operator">=</span><span class="string">&#x27;林震南&#x27;</span>\G</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">*************************** 1. row ***************************</span><br><span class="line">           id: 1</span><br><span class="line">  select_type: SIMPLE</span><br><span class="line">        table: tb_student</span><br><span class="line">   partitions: NULL</span><br><span class="line">         type: ALL</span><br><span class="line">possible_keys: NULL</span><br><span class="line">          key: NULL</span><br><span class="line">      key_len: NULL</span><br><span class="line">          ref: NULL</span><br><span class="line">         rows: 11</span><br><span class="line">     filtered: 10.00</span><br><span class="line">        Extra: Using where</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br></pre></td></tr></table></figure><p>在上面的SQL执行计划中，有几项值得我们关注：</p><ol><li>type：MySQL在表中找到满足条件的行的方式，也称为访问类型，包括：ALL（全表扫描）、index（索引全扫描）、range（索引范围扫描）、ref（非唯一索引扫描）、eq_ref（唯一索引扫描）、const&#x2F;system、NULL。在所有的访问类型中，很显然ALL是性能最差的，它代表了全表扫描是指要扫描表中的每一行才能找到匹配的行。</li><li>possible_keys：MySQL可以选择的索引，但是<strong>有可能不会使用</strong>。</li><li>key：MySQL真正使用的索引。</li><li>rows：执行查询需要扫描的行数，这是一个<strong>预估值</strong>。</li></ol><p>从上面的执行计划可以看出，当我们通过学生名字查询学生时实际上是进行了全表扫描，不言而喻这个查询性能肯定是非常糟糕的，尤其是在表中的行很多的时候。如果我们需要经常通过学生姓名来查询学生，那么就应该在学生姓名对应的列上创建索引，通过索引来加速查询。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> index idx_student_name <span class="keyword">on</span> tb_student(stuname);</span><br></pre></td></tr></table></figure><p>再次查看刚才的SQL对应的执行计划。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuname<span class="operator">=</span><span class="string">&#x27;林震南&#x27;</span>\G</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">*************************** 1. row ***************************</span><br><span class="line">           id: 1</span><br><span class="line">  select_type: SIMPLE</span><br><span class="line">        table: tb_student</span><br><span class="line">   partitions: NULL</span><br><span class="line">         type: ref</span><br><span class="line">possible_keys: idx_student_name</span><br><span class="line">          key: idx_student_name</span><br><span class="line">      key_len: 62</span><br><span class="line">          ref: const</span><br><span class="line">         rows: 1</span><br><span class="line">     filtered: 100.00</span><br><span class="line">        Extra: NULL</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br></pre></td></tr></table></figure><p>可以注意到，在对学生姓名创建索引后，刚才的查询已经不是全表扫描而是基于索引的查询，而且扫描的行只有唯一的一行，这显然大大的提升了查询的性能。MySQL中还允许创建前缀索引，即对索引字段的前N个字符创建索引，这样的话可以减少索引占用的空间（但节省了空间很有可能会浪费时间，<strong>时间和空间是不可调和的矛盾</strong>），如下所示。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> index idx_student_name_1 <span class="keyword">on</span> tb_student(stuname(<span class="number">1</span>));</span><br></pre></td></tr></table></figure><p>上面的索引相当于是根据学生姓名的第一个字来创建的索引，我们再看看SQL执行计划。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> tb_student <span class="keyword">where</span> stuname<span class="operator">=</span><span class="string">&#x27;林震南&#x27;</span>\G</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">*************************** 1. row ***************************</span><br><span class="line">           id: 1</span><br><span class="line">  select_type: SIMPLE</span><br><span class="line">        table: tb_student</span><br><span class="line">   partitions: NULL</span><br><span class="line">         type: ref</span><br><span class="line">possible_keys: idx_student_name</span><br><span class="line">          key: idx_student_name</span><br><span class="line">      key_len: 5</span><br><span class="line">          ref: const</span><br><span class="line">         rows: 2</span><br><span class="line">     filtered: 100.00</span><br><span class="line">        Extra: Using where</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br></pre></td></tr></table></figure><p>不知道大家是否注意到，这一次扫描的行变成了2行，因为学生表中有两个姓“林”的学生，我们只用姓名的第一个字作为索引的话，在查询时通过索引就会找到这两行。</p><p>如果要删除索引，可以使用下面的SQL。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">alter table</span> tb_student <span class="keyword">drop</span> index idx_student_name;</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">drop</span> index idx_student_name <span class="keyword">on</span> tb_student;</span><br></pre></td></tr></table></figure><p>我们简单的为大家总结一下索引的设计原则：</p><ol><li><strong>最适合</strong>索引的列是出现在<strong>WHERE子句</strong>和连接子句中的列。</li><li>索引列的基数越大（取值多重复值少），索引的效果就越好。</li><li>使用<strong>前缀索引</strong>可以减少索引占用的空间，内存中可以缓存更多的索引。</li><li><strong>索引不是越多越好</strong>，虽然索引加速了读操作（查询），但是写操作（增、删、改）都会变得更慢，因为数据的变化会导致索引的更新，就如同书籍章节的增删需要更新目录一样。</li><li>使用InnoDB存储引擎时，表的普通索引都会保存主键的值，所以<strong>主键要尽可能选择较短的数据类型</strong>，这样可以有效的减少索引占用的空间，利用提升索引的缓存效果。</li></ol><p>最后，还有一点需要说明，InnoDB使用的B-tree索引，数值类型的列除了等值判断时索引会生效之外，使用&gt;、&lt;、&gt;&#x3D;、&lt;&#x3D;、BETWEEN…AND… 、&lt;&gt;时，索引仍然生效；对于字符串类型的列，如果使用不以通配符开头的模糊查询，索引也是起作用的，但是其他的情况会导致索引失效，这就意味着很有可能会做全表查询。</p><h4 id="视图"><a href="#视图" class="headerlink" title="视图"></a>视图</h4><p>视图是关系型数据库中将一组查询指令构成的结果集组合成可查询的数据表的对象。简单的说，视图就是虚拟的表，但与数据表不同的是，数据表是一种实体结构，而视图是一种虚拟结构，你也可以将视图理解为保存在数据库中被赋予名字的SQL语句。</p><p>使用视图可以获得以下好处：</p><ol><li>可以将实体数据表隐藏起来，让外部程序无法得知实际的数据结构，让访问者可以使用表的组成部分而不是整个表，降低数据库被攻击的风险。</li><li>在大多数的情况下视图是只读的（更新视图的操作通常都有诸多的限制），外部程序无法直接透过视图修改数据。</li><li>重用SQL语句，将高度复杂的查询包装在视图表中，直接访问该视图即可取出需要的数据；也可以将视图视为数据表进行连接查询。</li><li>视图可以返回与实体数据表不同格式的数据，</li></ol><p>创建视图。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> vw_avg_score </span><br><span class="line"><span class="keyword">as</span> </span><br><span class="line">    <span class="keyword">select</span> sid, round(<span class="built_in">avg</span>(score), <span class="number">1</span>) <span class="keyword">as</span> avgscore </span><br><span class="line">    <span class="keyword">from</span> tb_record <span class="keyword">group</span> <span class="keyword">by</span> sid;</span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> vw_student_score </span><br><span class="line"><span class="keyword">as</span> </span><br><span class="line">    <span class="keyword">select</span> stuname, avgscore </span><br><span class="line">    <span class="keyword">from</span> tb_student, vw_avg_score </span><br><span class="line">    <span class="keyword">where</span> stuid<span class="operator">=</span>sid;</span><br></pre></td></tr></table></figure><blockquote><p><strong>提示</strong>：因为视图不包含数据，所以每次使用视图时，都必须执行查询以获得数据，如果你使用了连接查询、嵌套查询创建了较为复杂的视图，你可能会发现查询性能下降得很厉害。因此，在使用复杂的视图前，应该进行测试以确保其性能能够满足应用的需求。</p></blockquote><p>使用视图。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> stuname, avgscore <span class="keyword">from</span> vw_student_score <span class="keyword">order</span> <span class="keyword">by</span> avgscore <span class="keyword">desc</span>;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">+--------------+----------+</span><br><span class="line">| stuname      | avgscore |</span><br><span class="line">+--------------+----------+</span><br><span class="line">| 杨过         |     95.6 |</span><br><span class="line">| 任我行       |     53.5 |</span><br><span class="line">| 王语嫣       |     84.3 |</span><br><span class="line">| 纪嫣然       |     73.8 |</span><br><span class="line">| 岳不群       |     78.0 |</span><br><span class="line">| 东方不败     |     88.0 |</span><br><span class="line">| 项少龙       |     92.0 |</span><br><span class="line">+--------------+----------+</span><br></pre></td></tr></table></figure><p>既然视图是一张虚拟的表，那么视图的中的数据可以更新吗？视图的可更新性要视具体情况而定，以下类型的视图是不能更新的：</p><ol><li>使用了聚合函数（SUM、MIN、MAX、AVG、COUNT等）、DISTINCT、GROUP BY、HAVING、UNION或者UNION ALL的视图。</li><li>SELECT中包含了子查询的视图。</li><li>FROM子句中包含了一个不能更新的视图的视图。</li><li>WHERE子句的子查询引用了FROM子句中的表的视图。</li></ol><p>删除视图。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">view</span> vw_student_score;</span><br></pre></td></tr></table></figure><blockquote><p><strong>说明</strong>：如果希望更新视图，可以先用上面的命令删除视图，也可以通过<code>create or replace view</code>来更新视图。</p></blockquote><p>视图的规则和限制。</p><ol><li>视图可以嵌套，可以利用从其他视图中检索的数据来构造一个新的视图。视图也可以和表一起使用。</li><li>创建视图时可以使用<code>order by</code>子句，但如果从视图中检索数据时也使用了<code>order by</code>，那么该视图中原先的<code>order by</code>会被覆盖。</li><li>视图无法使用索引，也不会激发触发器（实际开发中因为性能等各方面的考虑，通常不建议使用触发器，所以我们也不对这个概念进行介绍）的执行。</li></ol><h4 id="存储过程"><a href="#存储过程" class="headerlink" title="存储过程"></a>存储过程</h4><p>存储过程是事先编译好存储在数据库中的一组SQL的集合，调用存储过程可以简化应用程序开发人员的工作，减少与数据库服务器之间的通信，对于提升数据操作的性能也是有帮助的。其实迄今为止，我们使用的SQL语句都是针对一个或多个表的单条语句，但在实际开发中经常会遇到某个操作需要多条SQL语句才能完成的情况。例如，电商网站在受理用户订单时，需要做以下一系列的处理。 </p><ol><li>通过查询来核对库存中是否有对应的物品以及库存是否充足。</li><li>如果库存有物品，需要锁定库存以确保这些物品不再卖给别人， 并且要减少可用的物品数量以反映正确的库存量。</li><li>如果库存不足，可能需要进一步与供应商进行交互或者至少产生一条系统提示消息。 </li><li>不管受理订单是否成功，都需要产生流水记录，而且需要给对应的用户产生一条通知信息。</li></ol><p>我们可以通过存储过程将复杂的操作封装起来，这样不仅有助于保证数据的一致性，而且将来如果业务发生了变动，只需要调整和修改存储过程即可。对于调用存储过程的用户来说，存储过程并没有暴露数据表的细节，而且执行存储过程比一条条的执行一组SQL要快得多。</p><p>下面的存储过程实现了查询某门课程的最高分、最低分和平均分。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">procedure</span> if <span class="keyword">exists</span> sp_score_by_cid;</span><br><span class="line"></span><br><span class="line">delimiter $$</span><br><span class="line"></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">procedure</span> sp_score_by_cid(</span><br><span class="line">courseId <span class="type">int</span>, </span><br><span class="line"><span class="keyword">out</span> maxScore <span class="type">decimal</span>(<span class="number">4</span>,<span class="number">1</span>), </span><br><span class="line"><span class="keyword">out</span> minScore <span class="type">decimal</span>(<span class="number">4</span>,<span class="number">1</span>),</span><br><span class="line"><span class="keyword">out</span> avgScore <span class="type">decimal</span>(<span class="number">4</span>,<span class="number">1</span>)</span><br><span class="line">)</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">max</span>(score) <span class="keyword">into</span> maxScore <span class="keyword">from</span> tb_record </span><br><span class="line"><span class="keyword">where</span> cid<span class="operator">=</span>courseId;</span><br><span class="line"><span class="keyword">select</span> <span class="built_in">min</span>(score) <span class="keyword">into</span> minScore <span class="keyword">from</span> tb_record </span><br><span class="line"><span class="keyword">where</span> cid<span class="operator">=</span>courseId;</span><br><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span>(score) <span class="keyword">into</span> avgScore <span class="keyword">from</span> tb_record </span><br><span class="line"><span class="keyword">where</span> cid<span class="operator">=</span>courseId;</span><br><span class="line"><span class="keyword">end</span> $$</span><br><span class="line"></span><br><span class="line">delimiter ;</span><br><span class="line"></span><br><span class="line"><span class="keyword">call</span> sp_score_by_cid(<span class="number">1111</span>, <span class="variable">@a</span>, <span class="variable">@b</span>, <span class="variable">@c</span>);</span><br><span class="line"><span class="keyword">select</span> <span class="variable">@a</span>, <span class="variable">@b</span>, <span class="variable">@c</span>;</span><br></pre></td></tr></table></figure><blockquote><p>说明：在定义存储过程时，因为可能需要书写多条SQL，而分隔这些SQL需要使用分号作为分隔符，如果这个时候，仍然用分号表示整段代码结束，那么定义存储过程的SQL就会出现错误，所以上面我们用<code>delimiter $$</code>将整段代码结束的标记定义为<code>$$</code>，那么代码中的分号将不再表示整段代码的结束，需要马上执行，整段代码在遇到<code>end $$</code>时才输入完成并执行。在定义完存储过程后，通过<code>delimiter ;</code>将结束符重新改回成分号。</p></blockquote><p>上面定义的存储过程有四个参数，其中第一个参数是输入参数，代表课程的编号，后面的参数都是输出参数，因为存储过程不能定义返回值，只能通过输出参数将执行结果带出，定义输出参数的关键字是<code>out</code>，默认情况下参数都是输入参数。</p><p>调用存储过程。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">call</span> sp_score_by_cid(<span class="number">1111</span>, <span class="variable">@a</span>, <span class="variable">@b</span>, <span class="variable">@c</span>);</span><br></pre></td></tr></table></figure><p>获取输出参数的值。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="variable">@a</span> <span class="keyword">as</span> 最高分, <span class="variable">@b</span> <span class="keyword">as</span> 最低分, <span class="variable">@c</span> <span class="keyword">as</span> 平均分;</span><br></pre></td></tr></table></figure><p>删除存储过程。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">procedure</span> sp_score_by_cid;</span><br></pre></td></tr></table></figure><p>在存储过程中，我们可以定义变量、条件，可以使用分支和循环语句，可以通过游标操作查询结果，还可以使用事件调度器，这些内容我们暂时不在此处进行介绍。虽然我们说了很多存储过程的好处，但是在实际开发中，如果过度的使用存储过程，将大量复杂的运算放到存储过程中，也会导致占用数据库服务器的CPU资源，造成数据库服务器承受巨大的压力。为此，我们一般会将复杂的运算和处理交给应用服务器，因为很容易部署多台应用服务器来分摊这些压力。</p><h3 id="几个重要的概念"><a href="#几个重要的概念" class="headerlink" title="几个重要的概念"></a>几个重要的概念</h3><h4 id="范式理论-设计二维表的指导思想"><a href="#范式理论-设计二维表的指导思想" class="headerlink" title="范式理论 - 设计二维表的指导思想"></a>范式理论 - 设计二维表的指导思想</h4><ol><li>第一范式：数据表的每个列的值域都是由原子值组成的，不能够再分割。</li><li>第二范式：数据表里的所有数据都要和该数据表的键（主键与候选键）有完全依赖关系。</li><li>第三范式：所有非键属性都只和候选键有相关性，也就是说非键属性之间应该是独立无关的。</li></ol><h4 id="数据完整性"><a href="#数据完整性" class="headerlink" title="数据完整性"></a>数据完整性</h4><ol><li><p>实体完整性 - 每个实体都是独一无二的</p><ul><li>主键（primary key） &#x2F; 唯一约束 &#x2F; 唯一索引（unique）</li></ul></li><li><p>引用完整性（参照完整性）- 关系中不允许引用不存在的实体</p><ul><li>外键（foreign key）</li></ul></li><li><p>域完整性 - 数据是有效的</p><ul><li><p>数据类型及长度</p></li><li><p>非空约束（not null）</p></li><li><p>默认值约束（default）</p></li><li><p>检查约束（check）</p><blockquote><p>说明：在MySQL数据库中，检查约束并不起作用。</p></blockquote></li></ul></li></ol><h4 id="数据一致性"><a href="#数据一致性" class="headerlink" title="数据一致性"></a>数据一致性</h4><ol><li><p>事务：一系列对数据库进行读&#x2F;写的操作，这些操作要么全都成功，要么全都失败。</p></li><li><p>事务的ACID特性</p><ul><li>原子性：事务作为一个整体被执行，包含在其中的对数据库的操作要么全部被执行，要么都不执行</li><li>一致性：事务应确保数据库的状态从一个一致状态转变为另一个一致状态</li><li>隔离性：多个事务并发执行时，一个事务的执行不应影响其他事务的执行</li><li>持久性：已被提交的事务对数据库的修改应该永久保存在数据库中</li></ul></li><li><p>MySQL中的事务操作</p><ul><li><p>开启事务环境</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">start</span> transaction</span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">begin</span></span><br></pre></td></tr></table></figure></li><li><p>提交事务</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">commit</span></span><br></pre></td></tr></table></figure></li><li><p>回滚事务</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">rollback</span></span><br></pre></td></tr></table></figure></li></ul></li></ol><h3 id="其他内容"><a href="#其他内容" class="headerlink" title="其他内容"></a>其他内容</h3><p>大家应该能够想到，关于MySQL的知识肯定远远不止上面列出的这些，比如MySQL的性能优化、管理和维护MySQL的相关工具、MySQL数据的备份和恢复、监控MySQL、部署高可用架构等问题我们在这里都没有进行讨论。当然，这些内容也都是跟项目开发密切相关的，我们就留到后续的章节中再续点进行讲解。</p><h3 id="Python数据库编程"><a href="#Python数据库编程" class="headerlink" title="Python数据库编程"></a>Python数据库编程</h3><p>我们用如下所示的数据库来演示在Python中如何访问MySQL数据库。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">drop</span> database if <span class="keyword">exists</span> hrs;</span><br><span class="line"><span class="keyword">create</span> database hrs <span class="keyword">default</span> charset utf8;</span><br><span class="line"></span><br><span class="line">use hrs;</span><br><span class="line"></span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">table</span> if <span class="keyword">exists</span> tb_emp;</span><br><span class="line"><span class="keyword">drop</span> <span class="keyword">table</span> if <span class="keyword">exists</span> tb_dept;</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> tb_dept</span><br><span class="line">(</span><br><span class="line">dno   <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;编号&#x27;</span>,</span><br><span class="line">dname <span class="type">varchar</span>(<span class="number">10</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;名称&#x27;</span>,</span><br><span class="line">dloc  <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;所在地&#x27;</span>,</span><br><span class="line"><span class="keyword">primary key</span> (dno)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">insert into</span> tb_dept <span class="keyword">values</span> </span><br><span class="line">(<span class="number">10</span>, <span class="string">&#x27;会计部&#x27;</span>, <span class="string">&#x27;北京&#x27;</span>),</span><br><span class="line">(<span class="number">20</span>, <span class="string">&#x27;研发部&#x27;</span>, <span class="string">&#x27;成都&#x27;</span>),</span><br><span class="line">(<span class="number">30</span>, <span class="string">&#x27;销售部&#x27;</span>, <span class="string">&#x27;重庆&#x27;</span>),</span><br><span class="line">(<span class="number">40</span>, <span class="string">&#x27;运维部&#x27;</span>, <span class="string">&#x27;深圳&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> tb_emp</span><br><span class="line">(</span><br><span class="line">eno   <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;员工编号&#x27;</span>,</span><br><span class="line">ename <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;员工姓名&#x27;</span>,</span><br><span class="line">job   <span class="type">varchar</span>(<span class="number">20</span>) <span class="keyword">not null</span> comment <span class="string">&#x27;员工职位&#x27;</span>,</span><br><span class="line">mgr   <span class="type">int</span> comment <span class="string">&#x27;主管编号&#x27;</span>,</span><br><span class="line">sal   <span class="type">int</span> <span class="keyword">not null</span> comment <span class="string">&#x27;员工月薪&#x27;</span>,</span><br><span class="line">comm  <span class="type">int</span> comment <span class="string">&#x27;每月补贴&#x27;</span>,</span><br><span class="line">dno   <span class="type">int</span> comment <span class="string">&#x27;所在部门编号&#x27;</span>,</span><br><span class="line"><span class="keyword">primary key</span> (eno)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">alter table</span> tb_emp <span class="keyword">add constraint</span> fk_emp_dno <span class="keyword">foreign key</span> (dno) <span class="keyword">references</span> tb_dept (dno);</span><br><span class="line"></span><br><span class="line"><span class="keyword">insert into</span> tb_emp <span class="keyword">values</span> </span><br><span class="line">(<span class="number">7800</span>, <span class="string">&#x27;张三丰&#x27;</span>, <span class="string">&#x27;总裁&#x27;</span>, <span class="keyword">null</span>, <span class="number">9000</span>, <span class="number">1200</span>, <span class="number">20</span>),</span><br><span class="line">(<span class="number">2056</span>, <span class="string">&#x27;乔峰&#x27;</span>, <span class="string">&#x27;分析师&#x27;</span>, <span class="number">7800</span>, <span class="number">5000</span>, <span class="number">1500</span>, <span class="number">20</span>),</span><br><span class="line">(<span class="number">3088</span>, <span class="string">&#x27;李莫愁&#x27;</span>, <span class="string">&#x27;设计师&#x27;</span>, <span class="number">2056</span>, <span class="number">3500</span>, <span class="number">800</span>, <span class="number">20</span>),</span><br><span class="line">(<span class="number">3211</span>, <span class="string">&#x27;张无忌&#x27;</span>, <span class="string">&#x27;程序员&#x27;</span>, <span class="number">2056</span>, <span class="number">3200</span>, <span class="keyword">null</span>, <span class="number">20</span>),</span><br><span class="line">(<span class="number">3233</span>, <span class="string">&#x27;丘处机&#x27;</span>, <span class="string">&#x27;程序员&#x27;</span>, <span class="number">2056</span>, <span class="number">3400</span>, <span class="keyword">null</span>, <span class="number">20</span>),</span><br><span class="line">(<span class="number">3251</span>, <span class="string">&#x27;张翠山&#x27;</span>, <span class="string">&#x27;程序员&#x27;</span>, <span class="number">2056</span>, <span class="number">4000</span>, <span class="keyword">null</span>, <span class="number">20</span>),</span><br><span class="line">(<span class="number">5566</span>, <span class="string">&#x27;宋远桥&#x27;</span>, <span class="string">&#x27;会计师&#x27;</span>, <span class="number">7800</span>, <span class="number">4000</span>, <span class="number">1000</span>, <span class="number">10</span>),</span><br><span class="line">(<span class="number">5234</span>, <span class="string">&#x27;郭靖&#x27;</span>, <span class="string">&#x27;出纳&#x27;</span>, <span class="number">5566</span>, <span class="number">2000</span>, <span class="keyword">null</span>, <span class="number">10</span>),</span><br><span class="line">(<span class="number">3344</span>, <span class="string">&#x27;黄蓉&#x27;</span>, <span class="string">&#x27;销售主管&#x27;</span>, <span class="number">7800</span>, <span class="number">3000</span>, <span class="number">800</span>, <span class="number">30</span>),</span><br><span class="line">(<span class="number">1359</span>, <span class="string">&#x27;胡一刀&#x27;</span>, <span class="string">&#x27;销售员&#x27;</span>, <span class="number">3344</span>, <span class="number">1800</span>, <span class="number">200</span>, <span class="number">30</span>),</span><br><span class="line">(<span class="number">4466</span>, <span class="string">&#x27;苗人凤&#x27;</span>, <span class="string">&#x27;销售员&#x27;</span>, <span class="number">3344</span>, <span class="number">2500</span>, <span class="keyword">null</span>, <span class="number">30</span>),</span><br><span class="line">(<span class="number">3244</span>, <span class="string">&#x27;欧阳锋&#x27;</span>, <span class="string">&#x27;程序员&#x27;</span>, <span class="number">3088</span>, <span class="number">3200</span>, <span class="keyword">null</span>, <span class="number">20</span>),</span><br><span class="line">(<span class="number">3577</span>, <span class="string">&#x27;杨过&#x27;</span>, <span class="string">&#x27;会计&#x27;</span>, <span class="number">5566</span>, <span class="number">2200</span>, <span class="keyword">null</span>, <span class="number">10</span>),</span><br><span class="line">(<span class="number">3588</span>, <span class="string">&#x27;朱九真&#x27;</span>, <span class="string">&#x27;会计&#x27;</span>, <span class="number">5566</span>, <span class="number">2500</span>, <span class="keyword">null</span>, <span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>在Python 3中，我们通常使用纯Python的三方库PyMySQL来访问MySQL数据库，它应该是目前Python操作MySQL数据库最好的选择。</p><ol><li><p>安装PyMySQL。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install pymysql</span><br></pre></td></tr></table></figure></li><li><p>添加一个部门。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pymysql</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    no = <span class="built_in">int</span>(<span class="built_in">input</span>(<span class="string">&#x27;编号: &#x27;</span>))</span><br><span class="line">    name = <span class="built_in">input</span>(<span class="string">&#x27;名字: &#x27;</span>)</span><br><span class="line">    loc = <span class="built_in">input</span>(<span class="string">&#x27;所在地: &#x27;</span>)</span><br><span class="line">    <span class="comment"># 1. 创建数据库连接对象</span></span><br><span class="line">    con = pymysql.connect(host=<span class="string">&#x27;localhost&#x27;</span>, port=<span class="number">3306</span>,</span><br><span class="line">                          database=<span class="string">&#x27;hrs&#x27;</span>, charset=<span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">                          user=<span class="string">&#x27;yourname&#x27;</span>, password=<span class="string">&#x27;yourpass&#x27;</span>)</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="comment"># 2. 通过连接对象获取游标</span></span><br><span class="line">        <span class="keyword">with</span> con.cursor() <span class="keyword">as</span> cursor:</span><br><span class="line">            <span class="comment"># 3. 通过游标执行SQL并获得执行结果</span></span><br><span class="line">            result = cursor.execute(</span><br><span class="line">                <span class="string">&#x27;insert into tb_dept values (%s, %s, %s)&#x27;</span>,</span><br><span class="line">                (no, name, loc)</span><br><span class="line">            )</span><br><span class="line">        <span class="keyword">if</span> result == <span class="number">1</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;添加成功!&#x27;</span>)</span><br><span class="line">        <span class="comment"># 4. 操作成功提交事务</span></span><br><span class="line">        con.commit()</span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        <span class="comment"># 5. 关闭连接释放资源</span></span><br><span class="line">        con.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></li><li><p>删除一个部门。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pymysql</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    no = <span class="built_in">int</span>(<span class="built_in">input</span>(<span class="string">&#x27;编号: &#x27;</span>))</span><br><span class="line">    con = pymysql.connect(host=<span class="string">&#x27;localhost&#x27;</span>, port=<span class="number">3306</span>,</span><br><span class="line">                          database=<span class="string">&#x27;hrs&#x27;</span>, charset=<span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">                          user=<span class="string">&#x27;yourname&#x27;</span>, password=<span class="string">&#x27;yourpass&#x27;</span>,</span><br><span class="line">                          autocommit=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">with</span> con.cursor() <span class="keyword">as</span> cursor:</span><br><span class="line">            result = cursor.execute(</span><br><span class="line">                <span class="string">&#x27;delete from tb_dept where dno=%s&#x27;</span>,</span><br><span class="line">                (no, )</span><br><span class="line">            )</span><br><span class="line">        <span class="keyword">if</span> result == <span class="number">1</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;删除成功!&#x27;</span>)</span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        con.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><blockquote><p>说明：如果不希望每次SQL操作之后手动提交或回滚事务，可以像上面的代码那样，在创建连接的时候多加一个名为<code>autocommit</code>的参数并将它的值设置为<code>True</code>，表示每次执行SQL之后自动提交。如果程序中不需要使用事务环境也不希望手动的提交或回滚就可以这么做。</p></blockquote></li><li><p>更新一个部门。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pymysql</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    no = <span class="built_in">int</span>(<span class="built_in">input</span>(<span class="string">&#x27;编号: &#x27;</span>))</span><br><span class="line">    name = <span class="built_in">input</span>(<span class="string">&#x27;名字: &#x27;</span>)</span><br><span class="line">    loc = <span class="built_in">input</span>(<span class="string">&#x27;所在地: &#x27;</span>)</span><br><span class="line">    con = pymysql.connect(host=<span class="string">&#x27;localhost&#x27;</span>, port=<span class="number">3306</span>,</span><br><span class="line">                          database=<span class="string">&#x27;hrs&#x27;</span>, charset=<span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">                          user=<span class="string">&#x27;yourname&#x27;</span>, password=<span class="string">&#x27;yourpass&#x27;</span>,</span><br><span class="line">                          autocommit=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">with</span> con.cursor() <span class="keyword">as</span> cursor:</span><br><span class="line">            result = cursor.execute(</span><br><span class="line">                <span class="string">&#x27;update tb_dept set dname=%s, dloc=%s where dno=%s&#x27;</span>,</span><br><span class="line">                (name, loc, no)</span><br><span class="line">            )</span><br><span class="line">        <span class="keyword">if</span> result == <span class="number">1</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;更新成功!&#x27;</span>)</span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        con.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></li><li><p>查询所有部门。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pymysql</span><br><span class="line"><span class="keyword">from</span> pymysql.cursors <span class="keyword">import</span> DictCursor</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    con = pymysql.connect(host=<span class="string">&#x27;localhost&#x27;</span>, port=<span class="number">3306</span>,</span><br><span class="line">                          database=<span class="string">&#x27;hrs&#x27;</span>, charset=<span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">                          user=<span class="string">&#x27;yourname&#x27;</span>, password=<span class="string">&#x27;yourpass&#x27;</span>)</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">with</span> con.cursor(cursor=DictCursor) <span class="keyword">as</span> cursor:</span><br><span class="line">            cursor.execute(<span class="string">&#x27;select dno as no, dname as name, dloc as loc from tb_dept&#x27;</span>)</span><br><span class="line">            results = cursor.fetchall()</span><br><span class="line">            <span class="built_in">print</span>(results)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;编号\t名称\t\t所在地&#x27;</span>)</span><br><span class="line">            <span class="keyword">for</span> dept <span class="keyword">in</span> results:</span><br><span class="line">                <span class="built_in">print</span>(dept[<span class="string">&#x27;no&#x27;</span>], end=<span class="string">&#x27;\t&#x27;</span>)</span><br><span class="line">                <span class="built_in">print</span>(dept[<span class="string">&#x27;name&#x27;</span>], end=<span class="string">&#x27;\t&#x27;</span>)</span><br><span class="line">                <span class="built_in">print</span>(dept[<span class="string">&#x27;loc&#x27;</span>])</span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        con.close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></li><li><p>分页查询员工信息。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pymysql</span><br><span class="line"><span class="keyword">from</span> pymysql.cursors <span class="keyword">import</span> DictCursor</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Emp</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, no, name, job, sal</span>):</span><br><span class="line">        <span class="variable language_">self</span>.no = no</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line">        <span class="variable language_">self</span>.job = job</span><br><span class="line">        <span class="variable language_">self</span>.sal = sal</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__str__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&#x27;\n编号：<span class="subst">&#123;self.no&#125;</span>\n姓名：<span class="subst">&#123;self.name&#125;</span>\n职位：<span class="subst">&#123;self.job&#125;</span>\n月薪：<span class="subst">&#123;self.sal&#125;</span>\n&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    page = <span class="built_in">int</span>(<span class="built_in">input</span>(<span class="string">&#x27;页码: &#x27;</span>))</span><br><span class="line">    size = <span class="built_in">int</span>(<span class="built_in">input</span>(<span class="string">&#x27;大小: &#x27;</span>))</span><br><span class="line">    con = pymysql.connect(host=<span class="string">&#x27;localhost&#x27;</span>, port=<span class="number">3306</span>,</span><br><span class="line">                          database=<span class="string">&#x27;hrs&#x27;</span>, charset=<span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">                          user=<span class="string">&#x27;yourname&#x27;</span>, password=<span class="string">&#x27;yourpass&#x27;</span>)</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">with</span> con.cursor() <span class="keyword">as</span> cursor:</span><br><span class="line">            cursor.execute(</span><br><span class="line">                <span class="string">&#x27;select eno as no, ename as name, job, sal from tb_emp limit %s,%s&#x27;</span>,</span><br><span class="line">                ((page - <span class="number">1</span>) * size, size)</span><br><span class="line">            )</span><br><span class="line">            <span class="keyword">for</span> emp_tuple <span class="keyword">in</span> cursor.fetchall():</span><br><span class="line">                emp = Emp(*emp_tuple)</span><br><span class="line">                <span class="built_in">print</span>(emp)</span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        con.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></li></ol><h2 id="NoSQL入门"><a href="#NoSQL入门" class="headerlink" title="NoSQL入门"></a>NoSQL入门</h2><h3 id="NoSQL概述"><a href="#NoSQL概述" class="headerlink" title="NoSQL概述"></a>NoSQL概述</h3><p>如今，大多数的计算机系统（包括服务器、PC、移动设备等）都会产生庞大的数据量。其实，早在2012年的时候，全世界每天产生的数据量就达到了2.5EB（艾字节，$$1EB\approx10^{18}B$$）。这些数据有很大一部分是由关系型数据库来存储和管理的。 早在1970年，E.F.Codd发表了论述关系型数据库的著名论文“<em>A relational model of data for large shared data banks</em>”，这篇文章奠定了关系型数据库的基础并在接下来的数十年时间内产生了深远的影响。实践证明，关系型数据库是实现数据持久化最为重要的方式，它也是大多数应用在选择持久化方案时的首选技术。</p><p>NoSQL是一项全新的数据库革命性运动，虽然它的历史可以追溯到1998年，但是NoSQL真正深入人心并得到广泛的应用是在进入大数据时候以后，业界普遍认为NoSQL是更适合大数据存储的技术方案，这才使得NoSQL的发展达到了前所未有的高度。2012年《纽约时报》的一篇专栏中写到，大数据时代已经降临，在商业、经济及其他领域中，决策将不再基于经验和直觉而是基于数据和分析而作出。事实上，在天文学、气象学、基因组学、生物学、社会学、互联网搜索引擎、金融、医疗、社交网络、电子商务等诸多领域，由于数据过于密集和庞大，在数据的分析和处理上也遇到了前所未有的限制和阻碍，这一切都使得对大数据处理技术的研究被提升到了新的高度，也使得各种NoSQL的技术方案进入到了公众的视野。</p><p>NoSQL数据库按照其存储类型可以大致分为以下几类：</p><table><thead><tr><th>类型</th><th>部分代表</th><th>特点</th></tr></thead><tbody><tr><td>列族数据库</td><td>HBase<br>Cassandra<br>Hypertable</td><td>顾名思义是按列存储数据的。最大的特点是方便存储结构化和半结构化数据，方便做数据压缩，对针对某一列或者某几列的查询有非常大的I&#x2F;O优势，适合于批量数据处理和即时查询。</td></tr><tr><td>文档数据库</td><td>MongoDB<br>CouchDB<br>ElasticSearch</td><td>文档数据库一般用类JSON格式存储数据，存储的内容是文档型的。这样也就有机会对某些字段建立索引，实现关系数据库的某些功能，但不提供对参照完整性和分布事务的支持。</td></tr><tr><td>KV数据库</td><td>DynamoDB<br>Redis<br>LevelDB</td><td>可以通过key快速查询到其value，有基于内存和基于磁盘两种实现方案。</td></tr><tr><td>图数据库</td><td>Neo4J<br>FlockDB<br>JanusGraph</td><td>使用图结构进行语义查询的数据库，它使用节点、边和属性来表示和存储数据。图数据库从设计上，就可以简单快速的检索难以在关系系统中建模的复杂层次结构。</td></tr><tr><td>对象数据库</td><td>db4o<br>Versant</td><td>通过类似面向对象语言的语法操作数据库，通过对象的方式存取数据。</td></tr></tbody></table><blockquote><p>说明：想了解更多的NoSQL数据库，可以访问<a href="http://nosql-database.org/">http://nosql-database.org/</a>。</p></blockquote><h3 id="Redis概述"><a href="#Redis概述" class="headerlink" title="Redis概述"></a>Redis概述</h3><p>Redis是一种基于键值对的NoSQL数据库，它提供了对多种数据类型（字符串、哈希、列表、集合、有序集合、位图等）的支持，能够满足很多应用场景的需求。Redis将数据放在内存中，因此读写性能是非常惊人的。与此同时，Redis也提供了持久化机制，能够将内存中的数据保存到硬盘上，在发生意外状况时数据也不会丢掉。此外，Redis还支持键过期、地理信息运算、发布订阅、事务、管道、Lua脚本扩展等功能，总而言之，Redis的功能和性能都非常强大，如果项目中要实现高速缓存和消息队列这样的服务，直接交给Redis就可以了。目前，国内外很多著名的企业和商业项目都使用了Redis，包括：Twitter、Github、StackOverflow、新浪微博、百度、优酷土豆、美团、小米、唯品会等。</p><h4 id="Redis简介"><a href="#Redis简介" class="headerlink" title="Redis简介"></a>Redis简介</h4><p>2008年，一个名为Salvatore Sanfilippo的程序员为他开发的LLOOGG项目定制了专属的数据库（因为之前他无论怎样优化MySQL，系统性能已经无法再提升了），这项工作的成果就是Redis的初始版本。后来他将Redis的代码放到了全球最大的代码托管平台<a href="https://github.com/antirez/redis">Github</a>，从那以后，Redis引发了大量开发者的好评和关注，继而有数百人参与了Redis的开发和维护，这使得Redis的功能越来越强大和性能越来越好。</p><p>Redis是REmote DIctionary Server的缩写，它是一个用ANSI C编写的高性能的key-value存储系统，与其他的key-value存储系统相比，Redis有以下一些特点（也是优点）：</p><ul><li>Redis的读写性能极高，并且有丰富的特性（发布&#x2F;订阅、事务、通知等）。</li><li>Redis支持数据的持久化（RDB和AOF两种方式），可以将内存中的数据保存在磁盘中，重启的时候可以再次加载进行使用。</li><li>Redis支持多种数据类型，包括：string、hash、list、set，zset、bitmap、hyperloglog等。</li><li>Redis支持主从复制（实现读写分析）以及哨兵模式（监控master是否宕机并自动调整配置）。</li><li>Redis支持分布式集群，可以很容易的通过水平扩展来提升系统的整体性能。</li><li>Redis基于TCP提供的可靠传输服务进行通信，很多编程语言都提供了Redis客户端支持。</li></ul><h4 id="Redis的应用场景"><a href="#Redis的应用场景" class="headerlink" title="Redis的应用场景"></a>Redis的应用场景</h4><ol><li>高速缓存  - 将不常变化但又经常被访问的热点数据放到Redis数据库中，可以大大降低关系型数据库的压力，从而提升系统的响应性能。</li><li>排行榜 - 很多网站都有排行榜功能，利用Redis中的列表和有序集合可以非常方便的构造各种排行榜系统。</li><li>商品秒杀&#x2F;投票点赞 - Redis提供了对计数操作的支持，网站上常见的秒杀、点赞等功能都可以利用Redis的计数器通过+1或-1的操作来实现，从而避免了使用关系型数据的<code>update</code>操作。</li><li>分布式锁 - 利用Redis可以跨多台服务器实现分布式锁（类似于线程锁，但是能够被多台机器上的多个线程或进程共享）的功能，用于实现一个阻塞式操作。</li><li>消息队列 - 消息队列和高速缓存一样，是一个大型网站不可缺少的基础服务，可以实现业务解耦和非实时业务削峰等特性，这些我们都会在后面的项目中为大家展示。</li></ol><h4 id="Redis的安装和配置"><a href="#Redis的安装和配置" class="headerlink" title="Redis的安装和配置"></a>Redis的安装和配置</h4><p>可以使用Linux系统的包管理工具（如yum）来安装Redis，也可以通过在Redis的<a href="https://redis.io/">官方网站</a>下载Redis的源代码，解压缩解归档之后通过make工具对源代码进行构建并安装，在更新这篇文档时，Redis官方提供的最新稳定版本是<a href="http://download.redis.io/releases/redis-5.0.4.tar.gz">Redis 5.0.4</a>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">wget http://download.redis.io/releases/redis-5.0.4.tar.gz</span><br><span class="line">gunzip redis-5.0.4.tar.gz</span><br><span class="line">tar -xvf redis-5.0.4.tar</span><br><span class="line">cd redis-5.0.4</span><br><span class="line">make &amp;&amp; make install</span><br></pre></td></tr></table></figure><p>在redis源代码目录下有一个名为redis.conf的配置文件，我们可以先查看一下该文件。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim redis.conf</span><br></pre></td></tr></table></figure><p>配置将Redis服务绑定到指定的IP地址和端口。<br>配置底层有多少个数据库。<br>配置Redis的持久化机制 - RDB。<br>配置Redis的持久化机制 - AOF。<br>配置访问Redis服务器的验证口令。<br>配置Redis的主从复制，通过主从复制可以实现读写分离。<br>配置慢查询。</p><p>上面这些内容就是Redis的基本配置，如果你对上面的内容感到困惑也没有关系，先把Redis用起来再回头去推敲这些内容就行了。如果想找一些参考书，<a href="https://item.jd.com/12121730.html">《Redis开发与运维》</a>是一本不错的入门读物，而<a href="https://item.jd.com/11791607.html">《Redis实战》</a>是不错的进阶读物。</p><h4 id="Redis的服务器和客户端"><a href="#Redis的服务器和客户端" class="headerlink" title="Redis的服务器和客户端"></a>Redis的服务器和客户端</h4><p>接下来启动Redis服务器，下面的方式将以默认的配置启动Redis服务。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-server</span><br></pre></td></tr></table></figure><p>如果希望修改Redis的配置（如端口、认证口令、持久化方式等），可以通过下面两种方式。</p><p>方式一：通过参数指定认证口令和AOF持久化方式。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-server --requirepass yourpass --appendonly yes</span><br></pre></td></tr></table></figure><p>方式二：通过指定的配置文件来修改Redis的配置。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-server /root/redis-5.0.4/redis.conf</span><br></pre></td></tr></table></figure><p>下面我们使用第一种方式来启动Redis并将其置于后台运行，将Redis产生的输出重定向到名为redis.log的文件中。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-server --requirepass yourpass &gt; redis.log &amp;</span><br></pre></td></tr></table></figure><p>可以通过ps或者netstat来检查Redis服务器是否启动成功。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ps -ef | grep redis-server</span><br><span class="line">netstat -nap | grep redis-server</span><br></pre></td></tr></table></figure><p>接下来，我们尝试用Redis客户端去连接服务器。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">127.0.0.1:6379&gt; auth yourpass</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; ping</span><br><span class="line">PONG</span><br><span class="line">127.0.0.1:6379&gt;</span><br></pre></td></tr></table></figure><p>Redis有着非常丰富的数据类型，也有很多的命令来操作这些数据，具体的内容可以查看<a href="http://redisdoc.com/">Redis命令参考</a>，在这个网站上，除了Redis的命令参考，还有Redis的详细文档，其中包括了通知、事务、主从复制、持久化、哨兵、集群等内容。</p><p><img src="/memoirs/./res/redis-data-types.png"></p><blockquote><p>说明：上面的插图来自付磊和张益军先生编著的《Redis开发与运维》一书。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; set username admin</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; get username</span><br><span class="line">&quot;admin&quot;</span><br><span class="line">127.0.0.1:6379&gt; set password &quot;123456&quot; ex 300</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; get password</span><br><span class="line">&quot;123456&quot;</span><br><span class="line">127.0.0.1:6379&gt; ttl username</span><br><span class="line">(integer) -1</span><br><span class="line">127.0.0.1:6379&gt; ttl password</span><br><span class="line">(integer) 286</span><br><span class="line">127.0.0.1:6379&gt; hset stu1 name hao</span><br><span class="line">(integer) 0</span><br><span class="line">127.0.0.1:6379&gt; hset stu1 age 38</span><br><span class="line">(integer) 1</span><br><span class="line">127.0.0.1:6379&gt; hset stu1 gender male</span><br><span class="line">(integer) 1</span><br><span class="line">127.0.0.1:6379&gt; hgetall stu1</span><br><span class="line">1) &quot;name&quot;</span><br><span class="line">2) &quot;hao&quot;</span><br><span class="line">3) &quot;age&quot;</span><br><span class="line">4) &quot;38&quot;</span><br><span class="line">5) &quot;gender&quot;</span><br><span class="line">6) &quot;male&quot;</span><br><span class="line">127.0.0.1:6379&gt; hvals stu1</span><br><span class="line">1) &quot;hao&quot;</span><br><span class="line">2) &quot;38&quot;</span><br><span class="line">3) &quot;male&quot;</span><br><span class="line">127.0.0.1:6379&gt; hmset stu2 name wang age 18 gender female tel 13566778899</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; hgetall stu2</span><br><span class="line">1) &quot;name&quot;</span><br><span class="line">2) &quot;wang&quot;</span><br><span class="line">3) &quot;age&quot;</span><br><span class="line">4) &quot;18&quot;</span><br><span class="line">5) &quot;gender&quot;</span><br><span class="line">6) &quot;female&quot;</span><br><span class="line">7) &quot;tel&quot;</span><br><span class="line">8) &quot;13566778899&quot;</span><br><span class="line">127.0.0.1:6379&gt; lpush nums 1 2 3 4 5</span><br><span class="line">(integer) 5</span><br><span class="line">127.0.0.1:6379&gt; lrange nums 0 -1</span><br><span class="line">1) &quot;5&quot;</span><br><span class="line">2) &quot;4&quot;</span><br><span class="line">3) &quot;3&quot;</span><br><span class="line">4) &quot;2&quot;</span><br><span class="line">5) &quot;1&quot;</span><br><span class="line">127.0.0.1:6379&gt; lpop nums</span><br><span class="line">&quot;5&quot;</span><br><span class="line">127.0.0.1:6379&gt; lpop nums</span><br><span class="line">&quot;4&quot;</span><br><span class="line">127.0.0.1:6379&gt; rpop nums</span><br><span class="line">&quot;1&quot;</span><br><span class="line">127.0.0.1:6379&gt; rpop nums</span><br><span class="line">&quot;2&quot;</span><br><span class="line">127.0.0.1:6379&gt; sadd fruits apple banana orange apple grape grape</span><br><span class="line">(integer) 4</span><br><span class="line">127.0.0.1:6379&gt; scard fruits</span><br><span class="line">(integer) 4</span><br><span class="line">127.0.0.1:6379&gt; smembers fruits</span><br><span class="line">1) &quot;grape&quot;</span><br><span class="line">2) &quot;orange&quot;</span><br><span class="line">3) &quot;banana&quot;</span><br><span class="line">4) &quot;apple&quot;</span><br><span class="line">127.0.0.1:6379&gt; sismember fruits apple</span><br><span class="line">(integer) 1</span><br><span class="line">127.0.0.1:6379&gt; sismember fruits durian</span><br><span class="line">(integer) 0</span><br><span class="line">127.0.0.1:6379&gt; sadd nums1 1 2 3 4 5</span><br><span class="line">(integer) 5</span><br><span class="line">127.0.0.1:6379&gt; sadd nums2 2 4 6 8</span><br><span class="line">(integer) 4</span><br><span class="line">127.0.0.1:6379&gt; sinter nums1 nums2</span><br><span class="line">1) &quot;2&quot;</span><br><span class="line">2) &quot;4&quot;</span><br><span class="line">127.0.0.1:6379&gt; sunion nums1 nums2</span><br><span class="line">1) &quot;1&quot;</span><br><span class="line">2) &quot;2&quot;</span><br><span class="line">3) &quot;3&quot;</span><br><span class="line">4) &quot;4&quot;</span><br><span class="line">5) &quot;5&quot;</span><br><span class="line">6) &quot;6&quot;</span><br><span class="line">7) &quot;8&quot;</span><br><span class="line">127.0.0.1:6379&gt; sdiff nums1 nums2</span><br><span class="line">1) &quot;1&quot;</span><br><span class="line">2) &quot;3&quot;</span><br><span class="line">3) &quot;5&quot;</span><br><span class="line">127.0.0.1:6379&gt; zadd topsinger 5234 zhangxy 1978 chenyx 2235 zhoujl 3520 xuezq</span><br><span class="line">(integer) 4</span><br><span class="line">127.0.0.1:6379&gt; zrange topsinger 0 -1 withscores</span><br><span class="line">1) &quot;chenyx&quot;</span><br><span class="line">2) &quot;1978&quot;</span><br><span class="line">3) &quot;zhoujl&quot;</span><br><span class="line">4) &quot;2235&quot;</span><br><span class="line">5) &quot;xuezq&quot;</span><br><span class="line">6) &quot;3520&quot;</span><br><span class="line">7) &quot;zhangxy&quot;</span><br><span class="line">8) &quot;5234&quot;</span><br><span class="line">127.0.0.1:6379&gt; zrevrange topsinger 0 -1</span><br><span class="line">1) &quot;zhangxy&quot;</span><br><span class="line">2) &quot;xuezq&quot;</span><br><span class="line">3) &quot;zhoujl&quot;</span><br><span class="line">4) &quot;chenyx&quot;</span><br><span class="line">127.0.0.1:6379&gt; geoadd pois 116.39738549206541 39.90862689286386 tiananmen 116.27172936413572 39.99</span><br><span class="line">135172904494 yiheyuan 117.27766503308104 40.65332064313784 gubeishuizhen</span><br><span class="line">(integer) 3</span><br><span class="line">127.0.0.1:6379&gt; geodist pois tiananmen gubeishuizhen km</span><br><span class="line">&quot;111.5333&quot;</span><br><span class="line">127.0.0.1:6379&gt; geodist pois tiananmen yiheyuan km</span><br><span class="line">&quot;14.1230&quot;</span><br><span class="line">127.0.0.1:6379&gt; georadius pois 116.86499108288572 40.40149669363615 50 km withdist</span><br><span class="line">1) 1) &quot;gubeishuizhen&quot;</span><br><span class="line">   2) &quot;44.7408&quot;</span><br></pre></td></tr></table></figure><h4 id="在Python程序中使用Redis"><a href="#在Python程序中使用Redis" class="headerlink" title="在Python程序中使用Redis"></a>在Python程序中使用Redis</h4><p>可以使用pip安装redis模块。redis模块的核心是名为Redis的类，该类的对象代表一个Redis客户端，通过该客户端可以向Redis服务器发送命令并获取执行的结果。上面我们在Redis客户端中使用的命令基本上就是Redis对象可以接收的消息，所以如果了解了Redis的命令就可以在Python中玩转Redis。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip3 install redis</span><br><span class="line">python3</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">import</span> redis</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client = redis.Redis(host=<span class="string">&#x27;1.2.3.4&#x27;</span>, port=<span class="number">6379</span>, password=<span class="string">&#x27;yourpass&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client.<span class="built_in">set</span>(<span class="string">&#x27;username&#x27;</span>, <span class="string">&#x27;admin&#x27;</span>)</span><br><span class="line"><span class="literal">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client.hset(<span class="string">&#x27;student&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;hao&#x27;</span>)</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client.hset(<span class="string">&#x27;student&#x27;</span>, <span class="string">&#x27;age&#x27;</span>, <span class="number">38</span>)</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client.keys(<span class="string">&#x27;*&#x27;</span>)</span><br><span class="line">[<span class="string">b&#x27;username&#x27;</span>, <span class="string">b&#x27;student&#x27;</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client.get(<span class="string">&#x27;username&#x27;</span>)</span><br><span class="line"><span class="string">b&#x27;admin&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client.hgetall(<span class="string">&#x27;student&#x27;</span>)</span><br><span class="line">&#123;<span class="string">b&#x27;name&#x27;</span>: <span class="string">b&#x27;hao&#x27;</span>, <span class="string">b&#x27;age&#x27;</span>: <span class="string">b&#x27;38&#x27;</span>&#125;</span><br></pre></td></tr></table></figure><h3 id="MongoDB概述"><a href="#MongoDB概述" class="headerlink" title="MongoDB概述"></a>MongoDB概述</h3><h4 id="MongoDB简介"><a href="#MongoDB简介" class="headerlink" title="MongoDB简介"></a>MongoDB简介</h4><p>MongoDB是2009年问世的一个面向文档的数据库管理系统，由C++语言编写，旨在为Web应用提供可扩展的高性能数据存储解决方案。虽然在划分类别的时候后，MongoDB被认为是NoSQL的产品，但是它更像一个介于关系数据库和非关系数据库之间的产品，在非关系数据库中它功能最丰富，最像关系数据库。</p><p>MongoDB将数据存储为一个文档，一个文档由一系列的“键值对”组成，其文档类似于JSON对象，但是MongoDB对JSON进行了二进制处理（能够更快的定位key和value），因此其文档的存储格式称为BSON。关于JSON和BSON的差别大家可以看看MongoDB官方网站的文章<a href="https://www.mongodb.com/json-and-bson">《JSON and BSON》</a>。</p><p>目前，MongoDB已经提供了对Windows、MacOS、Linux、Solaris等多个平台的支持，而且也提供了多种开发语言的驱动程序，Python当然是其中之一。</p><h4 id="MongoDB的安装和配置"><a href="#MongoDB的安装和配置" class="headerlink" title="MongoDB的安装和配置"></a>MongoDB的安装和配置</h4><p>可以从MongoDB的<a href="https://www.mongodb.com/download-center#community">官方下载链接</a>下载MongoDB，官方为Windows系统提供了一个Installer程序，而Linux和MacOS则提供了压缩文件。下面简单说一下Linux系统如何安装和配置MongoDB。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.6.5.tgz</span><br><span class="line">gunzip mongodb-linux-x86_64-amazon-3.6.5.tgz</span><br><span class="line">mkdir mongodb-3.6.5</span><br><span class="line">tar -xvf mongodb-linux-x86_64-amazon-3.6.5.tar --strip-components 1 -C mongodb-3.6.5/</span><br><span class="line">export PATH=$PATH:~/mongodb-3.6.5/bin</span><br><span class="line">mkdir -p /data/db</span><br><span class="line">mongod --bind_ip 172.18.61.250</span><br><span class="line"></span><br><span class="line">2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] MongoDB starting : pid=1163 port=27017 dbpath=/data/db 64-bit host=iZwz97tbgo9lkabnat2lo8Z</span><br><span class="line">2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] db version v3.6.5</span><br><span class="line">2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] git version: a20ecd3e3a174162052ff99913bc2ca9a839d618</span><br><span class="line">2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] OpenSSL version: OpenSSL 1.0.0-fips29 Mar 2010</span><br><span class="line">...</span><br><span class="line">2018-06-03T18:03:28.945+0800 I NETWORK  [initandlisten] waiting for connections on port 27017</span><br></pre></td></tr></table></figure><blockquote><p>说明：上面的操作中，export命令是设置PATH环境变量，这样可以在任意路径下执行mongod来启动MongoDB服务器。MongoDB默认保存数据的路径是&#x2F;data&#x2F;db目录，为此要提前创建该目录。此外，在使用mongod启动MongoDB服务器时，–bind_ip参数用来将服务绑定到指定的IP地址，也可以用–port参数来指定端口，默认端口为27017。</p></blockquote><h4 id="MongoDB基本概念"><a href="#MongoDB基本概念" class="headerlink" title="MongoDB基本概念"></a>MongoDB基本概念</h4><p>我们通过与关系型数据库进行对照的方式来说明MongoDB中的一些概念。</p><table><thead><tr><th>SQL</th><th>MongoDB</th><th>解释（SQL&#x2F;MongoDB）</th></tr></thead><tbody><tr><td>database</td><td>database</td><td>数据库&#x2F;数据库</td></tr><tr><td>table</td><td>collection</td><td>二维表&#x2F;集合</td></tr><tr><td>row</td><td>document</td><td>记录（行）&#x2F;文档</td></tr><tr><td>column</td><td>field</td><td>字段（列）&#x2F;域</td></tr><tr><td>index</td><td>index</td><td>索引&#x2F;索引</td></tr><tr><td>table joins</td><td>—</td><td>表连接&#x2F;嵌套文档</td></tr><tr><td>primary key</td><td>primary key</td><td>主键&#x2F;主键（<code>_id</code>字段）</td></tr></tbody></table><h4 id="通过Shell操作MongoDB"><a href="#通过Shell操作MongoDB" class="headerlink" title="通过Shell操作MongoDB"></a>通过Shell操作MongoDB</h4><p>启动服务器后可以使用交互式环境跟服务器通信，如下所示。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mongo --host 172.18.61.250</span><br><span class="line"></span><br><span class="line">MongoDB shell version v3.6.5</span><br><span class="line">connecting to: mongodb://172.18.61.250:27017/</span><br></pre></td></tr></table></figure><ol><li><p>查看、创建和删除数据库。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="comment">// 显示所有数据库</span></span><br><span class="line">&gt; show dbs</span><br><span class="line">admin   <span class="number">0.</span>000GB</span><br><span class="line">config  <span class="number">0.</span>000GB</span><br><span class="line">local   <span class="number">0.</span>000GB</span><br><span class="line">&gt; <span class="comment">// 创建并切换到school数据库</span></span><br><span class="line">&gt; use school</span><br><span class="line">switched to db school</span><br><span class="line">&gt; <span class="comment">// 删除当前数据库</span></span><br><span class="line">&gt; db.<span class="title function_">dropDatabase</span>(<span class="params"></span>)</span><br><span class="line">&#123; <span class="string">&quot;ok&quot;</span> : <span class="number">1</span> &#125;</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure></li><li><p>创建、删除和查看集合。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="comment">// 创建并切换到school数据库</span></span><br><span class="line">&gt; use school</span><br><span class="line">switched to db school</span><br><span class="line">&gt; <span class="comment">// 创建colleges集合</span></span><br><span class="line">&gt; db.<span class="title function_">createCollection</span>(<span class="params"><span class="string">&#x27;colleges&#x27;</span></span>)</span><br><span class="line">&#123; <span class="string">&quot;ok&quot;</span> : <span class="number">1</span> &#125;</span><br><span class="line">&gt; <span class="comment">// 创建students集合</span></span><br><span class="line">&gt; db.<span class="title function_">createCollection</span>(<span class="params"><span class="string">&#x27;students&#x27;</span></span>)</span><br><span class="line">&#123; <span class="string">&quot;ok&quot;</span> : <span class="number">1</span> &#125;</span><br><span class="line">&gt; <span class="comment">// 查看所有集合</span></span><br><span class="line">&gt; show collections</span><br><span class="line">colleges</span><br><span class="line">students</span><br><span class="line">&gt; <span class="comment">// 删除colleges集合</span></span><br><span class="line">&gt; db.<span class="property">colleges</span>.<span class="title function_">drop</span>()</span><br><span class="line"><span class="literal">true</span></span><br><span class="line">&gt; </span><br></pre></td></tr></table></figure><blockquote><p>说明：在MongoDB中插入文档时如果集合不存在会自动创建集合，所以也可以按照下面的方式通过创建文档来创建集合。</p></blockquote></li><li><p>文档的CRUD操作。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="comment">// 向students集合插入文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">insert</span>(&#123;<span class="attr">stuid</span>: <span class="number">1001</span>, <span class="attr">name</span>: <span class="string">&#x27;骆昊&#x27;</span>, <span class="attr">age</span>: <span class="number">38</span>&#125;)</span><br><span class="line"><span class="title class_">WriteResult</span>(&#123; <span class="string">&quot;nInserted&quot;</span> : <span class="number">1</span> &#125;)</span><br><span class="line">&gt; <span class="comment">// 向students集合插入文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">save</span>(&#123;<span class="attr">stuid</span>: <span class="number">1002</span>, <span class="attr">name</span>: <span class="string">&#x27;王大锤&#x27;</span>, <span class="attr">tel</span>: <span class="string">&#x27;13012345678&#x27;</span>, <span class="attr">gender</span>: <span class="string">&#x27;男&#x27;</span>&#125;)</span><br><span class="line"><span class="title class_">WriteResult</span>(&#123; <span class="string">&quot;nInserted&quot;</span> : <span class="number">1</span> &#125;)</span><br><span class="line">&gt; <span class="comment">// 查看所有文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">find</span>(<span class="params"></span>)</span><br><span class="line">&#123; <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c72e006ad854460ee70b&quot;</span>), <span class="string">&quot;stuid&quot;</span> : <span class="number">1001</span>, <span class="string">&quot;name&quot;</span> : <span class="string">&quot;骆昊&quot;</span>, <span class="string">&quot;age&quot;</span> : <span class="number">38</span> &#125;</span><br><span class="line">&#123; <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c790006ad854460ee70c&quot;</span>), <span class="string">&quot;stuid&quot;</span> : <span class="number">1002</span>, <span class="string">&quot;name&quot;</span> : <span class="string">&quot;王大锤&quot;</span>, <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13012345678&quot;</span>, <span class="string">&quot;gender&quot;</span> : <span class="string">&quot;男&quot;</span> &#125;</span><br><span class="line">&gt; <span class="comment">// 更新stuid为1001的文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">update</span>(&#123;<span class="attr">stuid</span>: <span class="number">1001</span>&#125;, &#123;<span class="string">&#x27;$set&#x27;</span>: &#123;<span class="attr">tel</span>: <span class="string">&#x27;13566778899&#x27;</span>, <span class="attr">gender</span>: <span class="string">&#x27;男&#x27;</span>&#125;&#125;)</span><br><span class="line"><span class="title class_">WriteResult</span>(&#123; <span class="string">&quot;nMatched&quot;</span> : <span class="number">1</span>, <span class="string">&quot;nUpserted&quot;</span> : <span class="number">0</span>, <span class="string">&quot;nModified&quot;</span> : <span class="number">1</span> &#125;)</span><br><span class="line">&gt; <span class="comment">// 插入或更新stuid为1003的文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">update</span>(&#123;<span class="attr">stuid</span>: <span class="number">1003</span>&#125;, &#123;<span class="string">&#x27;$set&#x27;</span>: &#123;<span class="attr">name</span>: <span class="string">&#x27;白元芳&#x27;</span>, <span class="attr">tel</span>: <span class="string">&#x27;13022223333&#x27;</span>, <span class="attr">gender</span>: <span class="string">&#x27;男&#x27;</span>&#125;&#125;,  upsert=<span class="literal">true</span>)</span><br><span class="line"><span class="title class_">WriteResult</span>(&#123;</span><br><span class="line">        <span class="string">&quot;nMatched&quot;</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">&quot;nUpserted&quot;</span> : <span class="number">1</span>,</span><br><span class="line">        <span class="string">&quot;nModified&quot;</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c92dd185894d7283efab&quot;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">&gt; <span class="comment">// 查询所有文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">find</span>().<span class="title function_">pretty</span>(<span class="params"></span>)</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c72e006ad854460ee70b&quot;</span>),</span><br><span class="line">        <span class="string">&quot;stuid&quot;</span> : <span class="number">1001</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span> : <span class="string">&quot;骆昊&quot;</span>,</span><br><span class="line">        <span class="string">&quot;age&quot;</span> : <span class="number">38</span>,</span><br><span class="line">        <span class="string">&quot;gender&quot;</span> : <span class="string">&quot;男&quot;</span>,</span><br><span class="line">        <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13566778899&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c790006ad854460ee70c&quot;</span>),</span><br><span class="line">        <span class="string">&quot;stuid&quot;</span> : <span class="number">1002</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span> : <span class="string">&quot;王大锤&quot;</span>,</span><br><span class="line">        <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13012345678&quot;</span>,</span><br><span class="line">        <span class="string">&quot;gender&quot;</span> : <span class="string">&quot;男&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c92dd185894d7283efab&quot;</span>),</span><br><span class="line">        <span class="string">&quot;stuid&quot;</span> : <span class="number">1003</span>,</span><br><span class="line">        <span class="string">&quot;gender&quot;</span> : <span class="string">&quot;男&quot;</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span> : <span class="string">&quot;白元芳&quot;</span>,</span><br><span class="line">        <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13022223333&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">&gt; <span class="comment">// 查询stuid大于1001的文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">find</span>(&#123;<span class="attr">stuid</span>: &#123;<span class="string">&#x27;$gt&#x27;</span>: <span class="number">1001</span>&#125;&#125;).<span class="title function_">pretty</span>(<span class="params"></span>)</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c790006ad854460ee70c&quot;</span>),</span><br><span class="line">        <span class="string">&quot;stuid&quot;</span> : <span class="number">1002</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span> : <span class="string">&quot;王大锤&quot;</span>,</span><br><span class="line">        <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13012345678&quot;</span>,</span><br><span class="line">        <span class="string">&quot;gender&quot;</span> : <span class="string">&quot;男&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c92dd185894d7283efab&quot;</span>),</span><br><span class="line">        <span class="string">&quot;stuid&quot;</span> : <span class="number">1003</span>,</span><br><span class="line">        <span class="string">&quot;gender&quot;</span> : <span class="string">&quot;男&quot;</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span> : <span class="string">&quot;白元芳&quot;</span>,</span><br><span class="line">        <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13022223333&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">&gt; <span class="comment">// 查询stuid大于1001的文档只显示name和tel字段</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">find</span>(&#123;<span class="attr">stuid</span>: &#123;<span class="string">&#x27;$gt&#x27;</span>: <span class="number">1001</span>&#125;&#125;, &#123;<span class="attr">_id</span>: <span class="number">0</span>, <span class="attr">name</span>: <span class="number">1</span>, <span class="attr">tel</span>: <span class="number">1</span>&#125;).<span class="title function_">pretty</span>(<span class="params"></span>)</span><br><span class="line">&#123; <span class="string">&quot;name&quot;</span> : <span class="string">&quot;王大锤&quot;</span>, <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13012345678&quot;</span> &#125;</span><br><span class="line">&#123; <span class="string">&quot;name&quot;</span> : <span class="string">&quot;白元芳&quot;</span>, <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13022223333&quot;</span> &#125;</span><br><span class="line">&gt; <span class="comment">// 查询name为“骆昊”或者tel为“13022223333”的文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">find</span>(&#123;<span class="string">&#x27;$or&#x27;</span>: [&#123;<span class="attr">name</span>: <span class="string">&#x27;骆昊&#x27;</span>&#125;, &#123;<span class="attr">tel</span>: <span class="string">&#x27;13022223333&#x27;</span>&#125;]&#125;, &#123;<span class="attr">_id</span>: <span class="number">0</span>, <span class="attr">name</span>: <span class="number">1</span>, <span class="attr">tel</span>: <span class="number">1</span>&#125;).<span class="title function_">pretty</span>(<span class="params"></span>)</span><br><span class="line">&#123; <span class="string">&quot;name&quot;</span> : <span class="string">&quot;骆昊&quot;</span>, <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13566778899&quot;</span> &#125;</span><br><span class="line">&#123; <span class="string">&quot;name&quot;</span> : <span class="string">&quot;白元芳&quot;</span>, <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13022223333&quot;</span> &#125;</span><br><span class="line">&gt; <span class="comment">// 查询学生文档跳过第1条文档只查1条文档</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">find</span>().<span class="title function_">skip</span>(<span class="number">1</span>).<span class="title function_">limit</span>(<span class="number">1</span>).<span class="title function_">pretty</span>(<span class="params"></span>)</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;_id&quot;</span> : <span class="title class_">ObjectId</span>(<span class="string">&quot;5b13c790006ad854460ee70c&quot;</span>),</span><br><span class="line">        <span class="string">&quot;stuid&quot;</span> : <span class="number">1002</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span> : <span class="string">&quot;王大锤&quot;</span>,</span><br><span class="line">        <span class="string">&quot;tel&quot;</span> : <span class="string">&quot;13012345678&quot;</span>,</span><br><span class="line">        <span class="string">&quot;gender&quot;</span> : <span class="string">&quot;男&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">&gt; <span class="comment">// 对查询结果进行排序(1表示升序，-1表示降序)</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">find</span>(&#123;&#125;, &#123;<span class="attr">_id</span>: <span class="number">0</span>, <span class="attr">stuid</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="number">1</span>&#125;).<span class="title function_">sort</span>(<span class="params">&#123;stuid: -<span class="number">1</span>&#125;</span>)</span><br><span class="line">&#123; <span class="string">&quot;stuid&quot;</span> : <span class="number">1003</span>, <span class="string">&quot;name&quot;</span> : <span class="string">&quot;白元芳&quot;</span> &#125;</span><br><span class="line">&#123; <span class="string">&quot;stuid&quot;</span> : <span class="number">1002</span>, <span class="string">&quot;name&quot;</span> : <span class="string">&quot;王大锤&quot;</span> &#125;</span><br><span class="line">&#123; <span class="string">&quot;stuid&quot;</span> : <span class="number">1001</span>, <span class="string">&quot;name&quot;</span> : <span class="string">&quot;骆昊&quot;</span> &#125;</span><br><span class="line">&gt; <span class="comment">// 在指定的一个或多个字段上创建索引</span></span><br><span class="line">&gt; db.<span class="property">students</span>.<span class="title function_">ensureIndex</span>(<span class="params">&#123;name: <span class="number">1</span>&#125;</span>)</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;createdCollectionAutomatically&quot;</span> : <span class="literal">false</span>,</span><br><span class="line">        <span class="string">&quot;numIndexesBefore&quot;</span> : <span class="number">1</span>,</span><br><span class="line">        <span class="string">&quot;numIndexesAfter&quot;</span> : <span class="number">2</span>,</span><br><span class="line">        <span class="string">&quot;ok&quot;</span> : <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line">&gt; </span><br></pre></td></tr></table></figure></li></ol><p>使用MongoDB可以非常方便的配置数据复制，通过冗余数据来实现数据的高可用以及灾难恢复，也可以通过数据分片来应对数据量迅速增长的需求。关于MongoDB更多的操作可以查阅<a href="https://mongodb-documentation.readthedocs.io/en/latest/">官方文档</a> ，同时推荐大家阅读Kristina Chodorow写的<a href="http://www.ituring.com.cn/book/1172">《MongoDB权威指南》</a>。</p><h4 id="在Python程序中操作MongoDB"><a href="#在Python程序中操作MongoDB" class="headerlink" title="在Python程序中操作MongoDB"></a>在Python程序中操作MongoDB</h4><p>可以通过pip安装pymongo来实现对MongoDB的操作。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip3 install pymongo</span><br><span class="line">python3</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> pymongo <span class="keyword">import</span> MongoClient</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>client = MongoClient(<span class="string">&#x27;mongodb://127.0.0.1:27017&#x27;</span>) </span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>db = client.school</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> student <span class="keyword">in</span> db.students.find():</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="string">&#x27;学号:&#x27;</span>, student[<span class="string">&#x27;stuid&#x27;</span>])</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="string">&#x27;姓名:&#x27;</span>, student[<span class="string">&#x27;name&#x27;</span>])</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="string">&#x27;电话:&#x27;</span>, student[<span class="string">&#x27;tel&#x27;</span>])</span><br><span class="line"><span class="meta">... </span></span><br><span class="line">学号: <span class="number">1001.0</span></span><br><span class="line">姓名: 骆昊</span><br><span class="line">电话: <span class="number">13566778899</span></span><br><span class="line">学号: <span class="number">1002.0</span></span><br><span class="line">姓名: 王大锤</span><br><span class="line">电话: <span class="number">13012345678</span></span><br><span class="line">学号: <span class="number">1003.0</span></span><br><span class="line">姓名: 白元芳</span><br><span class="line">电话: <span class="number">13022223333</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>db.students.find().count()</span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>db.students.remove()</span><br><span class="line">&#123;<span class="string">&#x27;n&#x27;</span>: <span class="number">3</span>, <span class="string">&#x27;ok&#x27;</span>: <span class="number">1.0</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>db.students.find().count()</span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>coll = db.students</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> pymongo <span class="keyword">import</span> ASCENDING</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>coll.create_index([(<span class="string">&#x27;name&#x27;</span>, ASCENDING)], unique=<span class="literal">True</span>)</span><br><span class="line"><span class="string">&#x27;name_1&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>coll.insert_one(&#123;<span class="string">&#x27;stuid&#x27;</span>: <span class="built_in">int</span>(<span class="number">1001</span>), <span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;骆昊&#x27;</span>, <span class="string">&#x27;gender&#x27;</span>: <span class="literal">True</span>&#125;)</span><br><span class="line">&lt;pymongo.results.InsertOneResult <span class="built_in">object</span> at <span class="number">0x1050cc6c8</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>coll.insert_many([&#123;<span class="string">&#x27;stuid&#x27;</span>: <span class="built_in">int</span>(<span class="number">1002</span>), <span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;王大锤&#x27;</span>, <span class="string">&#x27;gender&#x27;</span>: <span class="literal">False</span>&#125;, &#123;<span class="string">&#x27;stuid&#x27;</span>: <span class="built_in">int</span>(<span class="number">1003</span>), <span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;白元芳&#x27;</span>, <span class="string">&#x27;gender&#x27;</span>: <span class="literal">True</span>&#125;])</span><br><span class="line">&lt;pymongo.results.InsertManyResult <span class="built_in">object</span> at <span class="number">0x1050cc8c8</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> student <span class="keyword">in</span> coll.find(&#123;<span class="string">&#x27;gender&#x27;</span>: <span class="literal">True</span>&#125;):</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="string">&#x27;学号:&#x27;</span>, student[<span class="string">&#x27;stuid&#x27;</span>])</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="string">&#x27;姓名:&#x27;</span>, student[<span class="string">&#x27;name&#x27;</span>])</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="string">&#x27;性别:&#x27;</span>, <span class="string">&#x27;男&#x27;</span> <span class="keyword">if</span> student[<span class="string">&#x27;gender&#x27;</span>] <span class="keyword">else</span> <span class="string">&#x27;女&#x27;</span>)</span><br><span class="line"><span class="meta">... </span></span><br><span class="line">学号: <span class="number">1001</span></span><br><span class="line">姓名: 骆昊</span><br><span class="line">性别: 男</span><br><span class="line">学号: <span class="number">1003</span></span><br><span class="line">姓名: 白元芳</span><br><span class="line">性别: 男</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span></span><br></pre></td></tr></table></figure><p>关于PyMongo更多的知识可以通过它的<a href="https://api.mongodb.com/python/current/tutorial.html">官方文档</a>进行了解，也可以使用<a href="https://pypi.org/project/mongoengine/">MongoEngine</a>这样的库来简化Python程序对MongoDB的操作，除此之外，还有以异步I&#x2F;O方式访问MongoDB的三方库<a href="https://pypi.org/project/motor/">motor</a>都是不错的选择。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20200312/python/Python-100-3/</id>
    <link href="https://minniexcode.github.io/memoirs/20200312/python/Python-100-3/"/>
    <published>2020-03-12T12:14:23.000Z</published>
    <summary>
      <![CDATA[<h2 id="关系数据库入门"><a href="#关系数据库入门" class="headerlink" title="关系数据库入门"></a>关系数据库入门</h2>]]>
    </summary>
    <title>Python-100天(三)-数据库基础和进阶</title>
    <updated>2026-03-16T16:24:40.837Z</updated>
  </entry>
  <entry>
    <author>
      <name>ash66</name>
    </author>
    <category term="技术" scheme="https://minniexcode.github.io/memoirs/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Python" scheme="https://minniexcode.github.io/memoirs/tags/Python/"/>
    <content>
      <![CDATA[<h3 id="数据结构和算法"><a href="#数据结构和算法" class="headerlink" title="数据结构和算法"></a>数据结构和算法</h3><ul><li>算法：解决问题的方法和步骤</li><li>评价算法的好坏：渐近时间复杂度和渐近空间复杂度。</li><li>渐近时间复杂度的大O标记：<ul><li><p><img src="http://latex.codecogs.com/gif.latex?O(c)" style="margin-bottom: 0; display:inline;" /> - 常量时间复杂度 - 布隆过滤器 &#x2F; 哈希存储</p></li><li><p><img src="http://latex.codecogs.com/gif.latex?O(log_2n)" style="margin-bottom: 0; display:inline;" /> - 对数时间复杂度 - 折半查找（二分查找）</p></li><li><p><img src="http://latex.codecogs.com/gif.latex?O(n)" style="margin-bottom: 0; display:inline;"/> - 线性时间复杂度 - 顺序查找 &#x2F; 桶排序</p></li><li><p><img src="http://latex.codecogs.com/gif.latex?O(n*log_2n)" style="margin-bottom: 0; display:inline;"/> - 对数线性时间复杂度 - 高级排序算法（归并排序、快速排序）</p></li><li><p><img src="http://latex.codecogs.com/gif.latex?O(n^2)" style="margin-bottom: 0; display:inline;"/> - 平方时间复杂度 - 简单排序算法（选择排序、插入排序、冒泡排序）</p></li><li><p><img src="http://latex.codecogs.com/gif.latex?O(n^3)" style="margin-bottom: 0; display:inline;"/> - 立方时间复杂度 - Floyd算法 &#x2F; 矩阵乘法运算</p></li><li><p><img src="http://latex.codecogs.com/gif.latex?O(2^n)" style="margin-bottom: 0; display:inline;"/> - 几何级数时间复杂度 - 汉诺塔</p></li><li><p><img src="http://latex.codecogs.com/gif.latex?O(n!)" style="margin-bottom: 0; display:inline;"/> - 阶乘时间复杂度 - 旅行经销商问题 - NP</p></li></ul></li></ul><span id="more"></span><ul><li>排序算法（选择、冒泡和归并）和查找算法（顺序和折半）</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">select_sort</span>(<span class="params">origin_items, comp=<span class="keyword">lambda</span> x, y: x &lt; y</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;简单选择排序&quot;&quot;&quot;</span></span><br><span class="line">    items = origin_items[:]</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(items) - <span class="number">1</span>):</span><br><span class="line">        min_index = i</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(i + <span class="number">1</span>, <span class="built_in">len</span>(items)):</span><br><span class="line">            <span class="keyword">if</span> comp(items[j], items[min_index]):</span><br><span class="line">                min_index = j</span><br><span class="line">        items[i], items[min_index] = items[min_index], items[i]</span><br><span class="line">    <span class="keyword">return</span> items</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">bubble_sort</span>(<span class="params">origin_items, comp=<span class="keyword">lambda</span> x, y: x &gt; y</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;高质量冒泡排序(搅拌排序)&quot;&quot;&quot;</span></span><br><span class="line">    items = origin_items[:]</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(items) - <span class="number">1</span>):</span><br><span class="line">        swapped = <span class="literal">False</span></span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(i, <span class="built_in">len</span>(items) - <span class="number">1</span> - i):</span><br><span class="line">            <span class="keyword">if</span> comp(items[j], items[j + <span class="number">1</span>]):</span><br><span class="line">                items[j], items[j + <span class="number">1</span>] = items[j + <span class="number">1</span>], items[j]</span><br><span class="line">                swapped = <span class="literal">True</span></span><br><span class="line">        <span class="keyword">if</span> swapped:</span><br><span class="line">            swapped = <span class="literal">False</span></span><br><span class="line">            <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(items) - <span class="number">2</span> - i, i, -<span class="number">1</span>):</span><br><span class="line">                <span class="keyword">if</span> comp(items[j - <span class="number">1</span>], items[j]):</span><br><span class="line">                    items[j], items[j - <span class="number">1</span>] = items[j - <span class="number">1</span>], items[j]</span><br><span class="line">                    swapped = <span class="literal">True</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> swapped:</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">return</span> items</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">merge_sort</span>(<span class="params">items, comp=<span class="keyword">lambda</span> x, y: x &lt;= y</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;归并排序(分治法)&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(items) &lt; <span class="number">2</span>:</span><br><span class="line">        <span class="keyword">return</span> items[:]</span><br><span class="line">    mid = <span class="built_in">len</span>(items) // <span class="number">2</span></span><br><span class="line">    left = merge_sort(items[:mid], comp)</span><br><span class="line">    right = merge_sort(items[mid:], comp)</span><br><span class="line">    <span class="keyword">return</span> merge(left, right, comp)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">merge</span>(<span class="params">items1, items2, comp</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;合并(将两个有序的列表合并成一个有序的列表)&quot;&quot;&quot;</span></span><br><span class="line">    items = []</span><br><span class="line">    index1, index2 = <span class="number">0</span>, <span class="number">0</span></span><br><span class="line">    <span class="keyword">while</span> index1 &lt; <span class="built_in">len</span>(items1) <span class="keyword">and</span> index2 &lt; <span class="built_in">len</span>(items2):</span><br><span class="line">        <span class="keyword">if</span> comp(items1[index1], items2[index2]):</span><br><span class="line">            items.append(items1[index1])</span><br><span class="line">            index1 += <span class="number">1</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            items.append(items2[index2])</span><br><span class="line">            index2 += <span class="number">1</span></span><br><span class="line">    items += items1[index1:]</span><br><span class="line">    items += items2[index2:]</span><br><span class="line">    <span class="keyword">return</span> items</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">seq_search</span>(<span class="params">items, key</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;顺序查找&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">for</span> index, item <span class="keyword">in</span> <span class="built_in">enumerate</span>(items):</span><br><span class="line">        <span class="keyword">if</span> item == key:</span><br><span class="line">            <span class="keyword">return</span> index</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span></span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">bin_search</span>(<span class="params">items, key</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;折半查找&quot;&quot;&quot;</span></span><br><span class="line">    start, end = <span class="number">0</span>, <span class="built_in">len</span>(items) - <span class="number">1</span></span><br><span class="line">    <span class="keyword">while</span> start &lt;= end:</span><br><span class="line">        mid = (start + end) // <span class="number">2</span></span><br><span class="line">        <span class="keyword">if</span> key &gt; items[mid]:</span><br><span class="line">            start = mid + <span class="number">1</span></span><br><span class="line">        <span class="keyword">elif</span> key &lt; items[mid]:</span><br><span class="line">            end = mid - <span class="number">1</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> mid</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span></span><br></pre></td></tr></table></figure><ul><li>使用生成式（推导式）语法</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">prices = &#123;</span><br><span class="line">    <span class="string">&#x27;AAPL&#x27;</span>: <span class="number">191.88</span>,</span><br><span class="line">    <span class="string">&#x27;GOOG&#x27;</span>: <span class="number">1186.96</span>,</span><br><span class="line">    <span class="string">&#x27;IBM&#x27;</span>: <span class="number">149.24</span>,</span><br><span class="line">    <span class="string">&#x27;ORCL&#x27;</span>: <span class="number">48.44</span>,</span><br><span class="line">    <span class="string">&#x27;ACN&#x27;</span>: <span class="number">166.89</span>,</span><br><span class="line">    <span class="string">&#x27;FB&#x27;</span>: <span class="number">208.09</span>,</span><br><span class="line">    <span class="string">&#x27;SYMC&#x27;</span>: <span class="number">21.29</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment"># 用股票价格大于100元的股票构造一个新的字典</span></span><br><span class="line">prices2 = &#123;key: value <span class="keyword">for</span> key, value <span class="keyword">in</span> prices.items() <span class="keyword">if</span> value &gt; <span class="number">100</span>&#125;</span><br><span class="line"><span class="built_in">print</span>(prices2)</span><br></pre></td></tr></table></figure><blockquote><p>说明：生成式（推导式）可以用来生成列表、集合和字典。</p></blockquote><ul><li>嵌套的列表</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">names = [<span class="string">&#x27;关羽&#x27;</span>, <span class="string">&#x27;张飞&#x27;</span>, <span class="string">&#x27;赵云&#x27;</span>, <span class="string">&#x27;马超&#x27;</span>, <span class="string">&#x27;黄忠&#x27;</span>]</span><br><span class="line">courses = [<span class="string">&#x27;语文&#x27;</span>, <span class="string">&#x27;数学&#x27;</span>, <span class="string">&#x27;英语&#x27;</span>]</span><br><span class="line"><span class="comment"># 录入五个学生三门课程的成绩</span></span><br><span class="line"><span class="comment"># 错误 - 参考http://pythontutor.com/visualize.html#mode=edit</span></span><br><span class="line"><span class="comment"># scores = [[None] * len(courses)] * len(names)</span></span><br><span class="line">scores = [[<span class="literal">None</span>] * <span class="built_in">len</span>(courses) <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(names))]</span><br><span class="line"><span class="keyword">for</span> row, name <span class="keyword">in</span> <span class="built_in">enumerate</span>(names):</span><br><span class="line">    <span class="keyword">for</span> col, course <span class="keyword">in</span> <span class="built_in">enumerate</span>(courses):</span><br><span class="line">        scores[row][col] = <span class="built_in">float</span>(<span class="built_in">input</span>(<span class="string">f&#x27;请输入<span class="subst">&#123;name&#125;</span>的<span class="subst">&#123;course&#125;</span>成绩: &#x27;</span>))</span><br><span class="line">        <span class="built_in">print</span>(scores)</span><br></pre></td></tr></table></figure><p><a href="http://pythontutor.com/">Python Tutor</a> - VISUALIZE CODE AND GET LIVE HELP</p><ul><li>heapq、itertools等的用法</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">从列表中找出最大的或最小的N个元素</span></span><br><span class="line"><span class="string">堆结构(大根堆/小根堆)</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> heapq</span><br><span class="line"></span><br><span class="line">list1 = [<span class="number">34</span>, <span class="number">25</span>, <span class="number">12</span>, <span class="number">99</span>, <span class="number">87</span>, <span class="number">63</span>, <span class="number">58</span>, <span class="number">78</span>, <span class="number">88</span>, <span class="number">92</span>]</span><br><span class="line">list2 = [</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;IBM&#x27;</span>, <span class="string">&#x27;shares&#x27;</span>: <span class="number">100</span>, <span class="string">&#x27;price&#x27;</span>: <span class="number">91.1</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;AAPL&#x27;</span>, <span class="string">&#x27;shares&#x27;</span>: <span class="number">50</span>, <span class="string">&#x27;price&#x27;</span>: <span class="number">543.22</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;FB&#x27;</span>, <span class="string">&#x27;shares&#x27;</span>: <span class="number">200</span>, <span class="string">&#x27;price&#x27;</span>: <span class="number">21.09</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;HPQ&#x27;</span>, <span class="string">&#x27;shares&#x27;</span>: <span class="number">35</span>, <span class="string">&#x27;price&#x27;</span>: <span class="number">31.75</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;YHOO&#x27;</span>, <span class="string">&#x27;shares&#x27;</span>: <span class="number">45</span>, <span class="string">&#x27;price&#x27;</span>: <span class="number">16.35</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;ACME&#x27;</span>, <span class="string">&#x27;shares&#x27;</span>: <span class="number">75</span>, <span class="string">&#x27;price&#x27;</span>: <span class="number">115.65</span>&#125;</span><br><span class="line">]</span><br><span class="line"><span class="built_in">print</span>(heapq.nlargest(<span class="number">3</span>, list1))</span><br><span class="line"><span class="built_in">print</span>(heapq.nsmallest(<span class="number">3</span>, list1))</span><br><span class="line"><span class="built_in">print</span>(heapq.nlargest(<span class="number">2</span>, list2, key=<span class="keyword">lambda</span> x: x[<span class="string">&#x27;price&#x27;</span>]))</span><br><span class="line"><span class="built_in">print</span>(heapq.nlargest(<span class="number">2</span>, list2, key=<span class="keyword">lambda</span> x: x[<span class="string">&#x27;shares&#x27;</span>]))</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">迭代工具 - 排列 / 组合 / 笛卡尔积</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> itertools</span><br><span class="line"></span><br><span class="line">itertools.permutations(<span class="string">&#x27;ABCD&#x27;</span>)</span><br><span class="line">itertools.combinations(<span class="string">&#x27;ABCDE&#x27;</span>, <span class="number">3</span>)</span><br><span class="line">itertools.product(<span class="string">&#x27;ABCD&#x27;</span>, <span class="string">&#x27;123&#x27;</span>)</span><br></pre></td></tr></table></figure><ul><li>collections模块下的工具类</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">找出序列中出现次数最多的元素</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> Counter</span><br><span class="line"></span><br><span class="line">words = [</span><br><span class="line">    <span class="string">&#x27;look&#x27;</span>, <span class="string">&#x27;into&#x27;</span>, <span class="string">&#x27;my&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>, <span class="string">&#x27;look&#x27;</span>, <span class="string">&#x27;into&#x27;</span>, <span class="string">&#x27;my&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;the&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>, <span class="string">&#x27;the&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>, <span class="string">&#x27;the&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>, <span class="string">&#x27;not&#x27;</span>, <span class="string">&#x27;around&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;the&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>, <span class="string">&quot;don&#x27;t&quot;</span>, <span class="string">&#x27;look&#x27;</span>, <span class="string">&#x27;around&#x27;</span>, <span class="string">&#x27;the&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;look&#x27;</span>, <span class="string">&#x27;into&#x27;</span>, <span class="string">&#x27;my&#x27;</span>, <span class="string">&#x27;eyes&#x27;</span>, <span class="string">&quot;you&#x27;re&quot;</span>, <span class="string">&#x27;under&#x27;</span></span><br><span class="line">]</span><br><span class="line">counter = Counter(words)</span><br><span class="line"><span class="built_in">print</span>(counter.most_common(<span class="number">3</span>))</span><br></pre></td></tr></table></figure><ul><li>常用算法：<ul><li>穷举法 - 又称为暴力破解法，对所有的可能性进行验证，直到找到正确答案。</li><li>贪婪法 - 在对问题求解时，总是做出在当前看来</li><li>最好的选择，不追求最优解，快速找到满意解。</li><li>分治法 - 把一个复杂的问题分成两个或更多的相同或相似的子问题，再把子问题分成更小的子问题，直到可以直接求解的程度，最后将子问题的解进行合并得到原问题的解。</li><li>回溯法 - 回溯法又称为试探法，按选优条件向前搜索，当搜索到某一步发现原先选择并不优或达不到目标时，就退回一步重新选择。</li><li>动态规划 - 基本思想也是将待求解问题分解成若干个子问题，先求解并保存这些子问题的解，避免产生大量的重复运算。</li></ul></li></ul><p>穷举法例子：百钱百鸡和五人分鱼。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 公鸡5元一只 母鸡3元一只 小鸡1元三只</span></span><br><span class="line"><span class="comment"># 用100元买100只鸡 问公鸡/母鸡/小鸡各多少只</span></span><br><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">20</span>):</span><br><span class="line">    <span class="keyword">for</span> y <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">33</span>):</span><br><span class="line">        z = <span class="number">100</span> - x - y</span><br><span class="line">        <span class="keyword">if</span> <span class="number">5</span> * x + <span class="number">3</span> * y + z // <span class="number">3</span> == <span class="number">100</span> <span class="keyword">and</span> z % <span class="number">3</span> == <span class="number">0</span>:</span><br><span class="line">            <span class="built_in">print</span>(x, y, z)</span><br><span class="line"></span><br><span class="line"><span class="comment"># A、B、C、D、E五人在某天夜里合伙捕鱼 最后疲惫不堪各自睡觉</span></span><br><span class="line"><span class="comment"># 第二天A第一个醒来 他将鱼分为5份 扔掉多余的1条 拿走自己的一份</span></span><br><span class="line"><span class="comment"># B第二个醒来 也将鱼分为5份 扔掉多余的1条 拿走自己的一份</span></span><br><span class="line"><span class="comment"># 然后C、D、E依次醒来也按同样的方式分鱼 问他们至少捕了多少条鱼</span></span><br><span class="line">fish = <span class="number">6</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    total = fish</span><br><span class="line">    enough = <span class="literal">True</span></span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line">        <span class="keyword">if</span> (total - <span class="number">1</span>) % <span class="number">5</span> == <span class="number">0</span>:</span><br><span class="line">            total = (total - <span class="number">1</span>) // <span class="number">5</span> * <span class="number">4</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            enough = <span class="literal">False</span></span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">if</span> enough:</span><br><span class="line">        <span class="built_in">print</span>(fish)</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    fish += <span class="number">5</span></span><br></pre></td></tr></table></figure><p>贪婪法例子：假设小偷有一个背包，最多能装20公斤赃物，他闯入一户人家，发现如下表所示的物品。很显然，他不能把所有物品都装进背包，所以必须确定拿走哪些物品，留下哪些物品。</p><pre><code>|  名称  | 价格（美元） | 重量（kg） || :----: | :----------: | :--------: ||  电脑  |     200      |     20     || 收音机 |      20      |     4      ||   钟   |     175      |     10     ||  花瓶  |      50      |     2      ||   书   |      10      |     1      ||  油画  |      90      |     9      |</code></pre><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">贪婪法：在对问题求解时，总是做出在当前看来是最好的选择，不追求最优解，快速找到满意解。</span></span><br><span class="line"><span class="string">输入：</span></span><br><span class="line"><span class="string">20 6</span></span><br><span class="line"><span class="string">电脑 200 20</span></span><br><span class="line"><span class="string">收音机 20 4</span></span><br><span class="line"><span class="string">钟 175 10</span></span><br><span class="line"><span class="string">花瓶 50 2</span></span><br><span class="line"><span class="string">书 10 1</span></span><br><span class="line"><span class="string">油画 90 9</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Thing</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;物品&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, price, weight</span>):</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line">        <span class="variable language_">self</span>.price = price</span><br><span class="line">        <span class="variable language_">self</span>.weight = weight</span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">value</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;价格重量比&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.price / <span class="variable language_">self</span>.weight</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">input_thing</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;输入物品信息&quot;&quot;&quot;</span></span><br><span class="line">    name_str, price_str, weight_str = <span class="built_in">input</span>().split()</span><br><span class="line">    <span class="keyword">return</span> name_str, <span class="built_in">int</span>(price_str), <span class="built_in">int</span>(weight_str)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line">    max_weight, num_of_things = <span class="built_in">map</span>(<span class="built_in">int</span>, <span class="built_in">input</span>().split())</span><br><span class="line">    all_things = []</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(num_of_things):</span><br><span class="line">        all_things.append(Thing(*input_thing()))</span><br><span class="line">    all_things.sort(key=<span class="keyword">lambda</span> x: x.value, reverse=<span class="literal">True</span>)</span><br><span class="line">    total_weight = <span class="number">0</span></span><br><span class="line">    total_price = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> thing <span class="keyword">in</span> all_things:</span><br><span class="line">        <span class="keyword">if</span> total_weight + thing.weight &lt;= max_weight:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&#x27;小偷拿走了<span class="subst">&#123;thing.name&#125;</span>&#x27;</span>)</span><br><span class="line">            total_weight += thing.weight</span><br><span class="line">            total_price += thing.price</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&#x27;总价值: <span class="subst">&#123;total_price&#125;</span>美元&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><p>分治法例子：<a href="https://zh.wikipedia.org/zh/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F">快速排序</a>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">快速排序 - 选择枢轴对元素进行划分，左边都比枢轴小右边都比枢轴大</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">quick_sort</span>(<span class="params">origin_items, comp=<span class="keyword">lambda</span> x, y: x &lt;= y</span>):</span><br><span class="line">    items = origin_items[:]</span><br><span class="line">    _quick_sort(items, <span class="number">0</span>, <span class="built_in">len</span>(items) - <span class="number">1</span>, comp)</span><br><span class="line">    <span class="keyword">return</span> items</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">_quick_sort</span>(<span class="params">items, start, end, comp</span>):</span><br><span class="line">    <span class="keyword">if</span> start &lt; end:</span><br><span class="line">        pos = _partition(items, start, end, comp)</span><br><span class="line">        _quick_sort(items, start, pos - <span class="number">1</span>, comp)</span><br><span class="line">        _quick_sort(items, pos + <span class="number">1</span>, end, comp)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">_partition</span>(<span class="params">items, start, end, comp</span>):</span><br><span class="line">    pivot = items[end]</span><br><span class="line">    i = start - <span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(start, end):</span><br><span class="line">        <span class="keyword">if</span> comp(items[j], pivot):</span><br><span class="line">            i += <span class="number">1</span></span><br><span class="line">            items[i], items[j] = items[j], items[i]</span><br><span class="line">    items[i + <span class="number">1</span>], items[end] = items[end], items[i + <span class="number">1</span>]</span><br><span class="line">    <span class="keyword">return</span> i + <span class="number">1</span></span><br></pre></td></tr></table></figure><p>回溯法例子：<a href="https://zh.wikipedia.org/zh/%E9%AA%91%E5%A3%AB%E5%B7%A1%E9%80%BB">骑士巡逻</a>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">递归回溯法：叫称为试探法，按选优条件向前搜索，当搜索到某一步，发现原先选择并不优或达不到目标时，就退回一步重新选择，比较经典的问题包括骑士巡逻、八皇后和迷宫寻路等。</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">SIZE = <span class="number">5</span></span><br><span class="line">total = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">print_board</span>(<span class="params">board</span>):</span><br><span class="line">    <span class="keyword">for</span> row <span class="keyword">in</span> board:</span><br><span class="line">        <span class="keyword">for</span> col <span class="keyword">in</span> row:</span><br><span class="line">            <span class="built_in">print</span>(<span class="built_in">str</span>(col).center(<span class="number">4</span>), end=<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">        <span class="built_in">print</span>()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">patrol</span>(<span class="params">board, row, col, step=<span class="number">1</span></span>):</span><br><span class="line">    <span class="keyword">if</span> row &gt;= <span class="number">0</span> <span class="keyword">and</span> row &lt; SIZE <span class="keyword">and</span> \</span><br><span class="line">        col &gt;= <span class="number">0</span> <span class="keyword">and</span> col &lt; SIZE <span class="keyword">and</span> \</span><br><span class="line">        board[row][col] == <span class="number">0</span>:</span><br><span class="line">        board[row][col] = step</span><br><span class="line">        <span class="keyword">if</span> step == SIZE * SIZE:</span><br><span class="line">            <span class="keyword">global</span> total</span><br><span class="line">            total += <span class="number">1</span></span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&#x27;第<span class="subst">&#123;total&#125;</span>种走法: &#x27;</span>)</span><br><span class="line">            print_board(board)</span><br><span class="line">        patrol(board, row - <span class="number">2</span>, col - <span class="number">1</span>, step + <span class="number">1</span>)</span><br><span class="line">        patrol(board, row - <span class="number">1</span>, col - <span class="number">2</span>, step + <span class="number">1</span>)</span><br><span class="line">        patrol(board, row + <span class="number">1</span>, col - <span class="number">2</span>, step + <span class="number">1</span>)</span><br><span class="line">        patrol(board, row + <span class="number">2</span>, col - <span class="number">1</span>, step + <span class="number">1</span>)</span><br><span class="line">        patrol(board, row + <span class="number">2</span>, col + <span class="number">1</span>, step + <span class="number">1</span>)</span><br><span class="line">        patrol(board, row + <span class="number">1</span>, col + <span class="number">2</span>, step + <span class="number">1</span>)</span><br><span class="line">        patrol(board, row - <span class="number">1</span>, col + <span class="number">2</span>, step + <span class="number">1</span>)</span><br><span class="line">        patrol(board, row - <span class="number">2</span>, col + <span class="number">1</span>, step + <span class="number">1</span>)</span><br><span class="line">        board[row][col] = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    board = [[<span class="number">0</span>] * SIZE <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(SIZE)]</span><br><span class="line">    patrol(board, SIZE - <span class="number">1</span>, SIZE - <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><p>动态规划例子1：<a href="">斐波拉切数列</a>。（不使用动态规划将会是几何级数复杂度）</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">动态规划 - 适用于有重叠子问题和最优子结构性质的问题</span></span><br><span class="line"><span class="string">使用动态规划方法所耗时间往往远少于朴素解法(用空间换取时间)</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fib</span>(<span class="params">num, temp=&#123;&#125;</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;用递归计算Fibonacci数&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> num <span class="keyword">in</span> (<span class="number">1</span>, <span class="number">2</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">return</span> temp[num]</span><br><span class="line">    <span class="keyword">except</span> KeyError:</span><br><span class="line">        temp[num] = fib(num - <span class="number">1</span>) + fib(num - <span class="number">2</span>)</span><br><span class="line">        <span class="keyword">return</span> temp[num]</span><br></pre></td></tr></table></figure><p>动态规划例子2：子列表元素之和的最大值。（使用动态规划可以避免二重循环）</p><blockquote><p>说明：子列表指的是列表中索引（下标）连续的元素构成的列表；列表中的元素是int类型，可能包含正整数、0、负整数；程序输入列表中的元素，输出子列表元素求和的最大值，例如：</p><p>输入：1 -2 3 5 -3 2</p><p>输出：8</p><p>输入：0 -2 3 5 -1 2</p><p>输出：9</p><p>输入：-9 -2 -3 -5 -3</p><p>输出：-2</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    items = <span class="built_in">list</span>(<span class="built_in">map</span>(<span class="built_in">int</span>, <span class="built_in">input</span>().split()))</span><br><span class="line">    size = <span class="built_in">len</span>(items)</span><br><span class="line">    overall, partial = &#123;&#125;, &#123;&#125;</span><br><span class="line">    overall[size - <span class="number">1</span>] = partial[size - <span class="number">1</span>] = items[size - <span class="number">1</span>]</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(size - <span class="number">2</span>, -<span class="number">1</span>, -<span class="number">1</span>):</span><br><span class="line">        partial[i] = <span class="built_in">max</span>(items[i], partial[i + <span class="number">1</span>] + items[i])</span><br><span class="line">        overall[i] = <span class="built_in">max</span>(partial[i], overall[i + <span class="number">1</span>])</span><br><span class="line">    <span class="built_in">print</span>(overall[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><h3 id="函数的使用方式"><a href="#函数的使用方式" class="headerlink" title="函数的使用方式"></a>函数的使用方式</h3><ul><li><p>将函数视为“一等公民”</p><ul><li>函数可以赋值给变量</li><li>函数可以作为函数的参数</li><li>函数可以作为函数的返回值</li></ul></li><li><p>高阶函数的用法（<code>filter</code>、<code>map</code>以及它们的替代品）</p></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">items1 = <span class="built_in">list</span>(<span class="built_in">map</span>(<span class="keyword">lambda</span> x: x ** <span class="number">2</span>, <span class="built_in">filter</span>(<span class="keyword">lambda</span> x: x % <span class="number">2</span>, <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">10</span>))))</span><br><span class="line">items2 = [x ** <span class="number">2</span> <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">10</span>) <span class="keyword">if</span> x % <span class="number">2</span>]</span><br></pre></td></tr></table></figure><ul><li><p>位置参数、可变参数、关键字参数、命名关键字参数</p></li><li><p>参数的元信息（代码可读性问题）</p></li><li><p>匿名函数和内联函数的用法（<code>lambda</code>函数）</p></li><li><p>闭包和作用域问题</p></li><li><p>Python搜索变量的LEGB顺序（Local –&gt; Embedded –&gt; Global –&gt; Built-in）</p></li><li><p><code>global</code>和<code>nonlocal</code>关键字的作用<br><code>global</code>：声明或定义全局变量（要么直接使用现有的全局作用域的变量，要么定义一个变量放到全局作用域）。<br><code>nonlocal</code>：声明使用嵌套作用域的变量（嵌套作用域必须存在该变量，否则报错）。</p></li><li><p>装饰器函数（使用装饰器和取消装饰器）</p></li></ul><p>例子：输出函数执行时间的装饰器。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">record_time</span>(<span class="params">func</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;自定义装饰函数的装饰器&quot;&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line"><span class="meta">    @wraps(<span class="params">func</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        start = time()</span><br><span class="line">        result = func(*args, **kwargs)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&#x27;<span class="subst">&#123;func.__name__&#125;</span>: <span class="subst">&#123;time() - start&#125;</span>秒&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> result</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br></pre></td></tr></table></figure><p>如果装饰器不希望跟<code>print</code>函数耦合，可以编写带参数的装饰器。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> wraps</span><br><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">record</span>(<span class="params">output</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;自定义带参数的装饰器&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">decorate</span>(<span class="params">func</span>):</span><br><span class="line">    </span><br><span class="line"><span class="meta">    @wraps(<span class="params">func</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        start = time()</span><br><span class="line">        result = func(*args, **kwargs)</span><br><span class="line">        output(func.__name__, time() - start)</span><br><span class="line">        <span class="keyword">return</span> result</span><br><span class="line">            </span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> decorate</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> wraps</span><br><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Record</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;自定义装饰器类(通过__call__魔术方法使得对象可以当成函数调用)&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, output</span>):</span><br><span class="line">        <span class="variable language_">self</span>.output = output</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__call__</span>(<span class="params">self, func</span>):</span><br><span class="line"></span><br><span class="line"><span class="meta">        @wraps(<span class="params">func</span>)</span></span><br><span class="line">        <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">            start = time()</span><br><span class="line">            result = func(*args, **kwargs)</span><br><span class="line">            <span class="variable language_">self</span>.output(func.__name__, time() - start)</span><br><span class="line">            <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> wrapper</span><br></pre></td></tr></table></figure><blockquote><p>说明：由于对带装饰功能的函数添加了@wraps装饰器，可以通过<code>func.__wrapped__</code>方式获得被装饰之前的函数或类来取消装饰器的作用。</p></blockquote><p>例子：用装饰器来实现单例模式。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> wraps</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">singleton</span>(<span class="params">cls</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;装饰类的装饰器&quot;&quot;&quot;</span></span><br><span class="line">    instances = &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">    @wraps(<span class="params">cls</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        <span class="keyword">if</span> cls <span class="keyword">not</span> <span class="keyword">in</span> instances:</span><br><span class="line">            instances[cls] = cls(*args, **kwargs)</span><br><span class="line">        <span class="keyword">return</span> instances[cls]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@singleton</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">President</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;总统(单例类)&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><blockquote><p>说明：上面的代码中用到了闭包（closure），不知道你是否已经意识到了。还没有一个小问题就是，上面的代码并没有实现线程安全的单例，如果要实现线程安全的单例应该怎么做呢？</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> wraps</span><br><span class="line"><span class="keyword">from</span> threading <span class="keyword">import</span> Lock</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">singleton</span>(<span class="params">cls</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;线程安全的单例装饰器&quot;&quot;&quot;</span></span><br><span class="line">    instances = &#123;&#125;</span><br><span class="line">    locker = Lock()</span><br><span class="line"></span><br><span class="line"><span class="meta">    @wraps(<span class="params">cls</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        <span class="keyword">if</span> cls <span class="keyword">not</span> <span class="keyword">in</span> instances:</span><br><span class="line">            <span class="keyword">with</span> locker:</span><br><span class="line">                <span class="keyword">if</span> cls <span class="keyword">not</span> <span class="keyword">in</span> instances:</span><br><span class="line">                    instances[cls] = cls(*args, **kwargs)</span><br><span class="line">        <span class="keyword">return</span> instances[cls]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br></pre></td></tr></table></figure><h3 id="面向对象相关知识"><a href="#面向对象相关知识" class="headerlink" title="面向对象相关知识"></a>面向对象相关知识</h3><ul><li>三大支柱：封装、继承、多态</li></ul><p>例子：工资结算系统。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> abc <span class="keyword">import</span> ABCMeta, abstractmethod</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Employee</span>(metaclass=ABCMeta):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;员工(抽象类)&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name</span>):</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line"></span><br><span class="line"><span class="meta">    @abstractmethod</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_salary</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;结算月薪(抽象方法)&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Manager</span>(<span class="title class_ inherited__">Employee</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;部门经理&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_salary</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">15000.0</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Programmer</span>(<span class="title class_ inherited__">Employee</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;程序员&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, working_hour=<span class="number">0</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.working_hour = working_hour</span><br><span class="line">        <span class="built_in">super</span>().__init__(name)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_salary</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">200.0</span> * <span class="variable language_">self</span>.working_hour</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Salesman</span>(<span class="title class_ inherited__">Employee</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;销售员&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, sales=<span class="number">0.0</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.sales = sales</span><br><span class="line">        <span class="built_in">super</span>().__init__(name)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_salary</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1800.0</span> + <span class="variable language_">self</span>.sales * <span class="number">0.05</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EmployeeFactory</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;创建员工的工厂（工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合）&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @staticmethod</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">create</span>(<span class="params">emp_type, *args, **kwargs</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;创建员工&quot;&quot;&quot;</span></span><br><span class="line">        emp_type = emp_type.upper()</span><br><span class="line">        emp = <span class="literal">None</span></span><br><span class="line">        <span class="keyword">if</span> emp_type == <span class="string">&#x27;M&#x27;</span>:</span><br><span class="line">            emp = Manager(*args, **kwargs)</span><br><span class="line">        <span class="keyword">elif</span> emp_type == <span class="string">&#x27;P&#x27;</span>:</span><br><span class="line">            emp = Programmer(*args, **kwargs)</span><br><span class="line">        <span class="keyword">elif</span> emp_type == <span class="string">&#x27;S&#x27;</span>:</span><br><span class="line">            emp = Salesman(*args, **kwargs)</span><br><span class="line">        <span class="keyword">return</span> emp</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line">    emps = [</span><br><span class="line">        EmployeeFactory.create(<span class="string">&#x27;M&#x27;</span>, <span class="string">&#x27;曹操&#x27;</span>), </span><br><span class="line">        EmployeeFactory.create(<span class="string">&#x27;P&#x27;</span>, <span class="string">&#x27;荀彧&#x27;</span>, <span class="number">120</span>),</span><br><span class="line">        EmployeeFactory.create(<span class="string">&#x27;P&#x27;</span>, <span class="string">&#x27;郭嘉&#x27;</span>, <span class="number">85</span>), </span><br><span class="line">        EmployeeFactory.create(<span class="string">&#x27;S&#x27;</span>, <span class="string">&#x27;典韦&#x27;</span>, <span class="number">123000</span>),</span><br><span class="line">    ]</span><br><span class="line">    <span class="keyword">for</span> emp <span class="keyword">in</span> emps:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;%s: %.2f元&#x27;</span> % (emp.name, emp.get_salary()))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><ul><li><p>类与类之间的关系</p><ul><li>is-a关系：继承</li><li>has-a关系：关联 &#x2F; 聚合 &#x2F; 合成</li><li>use-a关系：依赖</li></ul></li></ul><p>例子：扑克游戏。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">经验：符号常量总是优于字面常量，枚举类型是定义符号常量的最佳选择</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> enum <span class="keyword">import</span> Enum, unique</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@unique</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Suite</span>(<span class="title class_ inherited__">Enum</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;花色&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    SPADE, HEART, CLUB, DIAMOND = <span class="built_in">range</span>(<span class="number">4</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__lt__</span>(<span class="params">self, other</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.value &lt; other.value</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Card</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;牌&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, suite, face</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;初始化方法&quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.suite = suite</span><br><span class="line">        <span class="variable language_">self</span>.face = face</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">show</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;显示牌面&quot;&quot;&quot;</span></span><br><span class="line">        suites = [<span class="string">&#x27;♠️&#x27;</span>, <span class="string">&#x27;♥️&#x27;</span>, <span class="string">&#x27;♣️&#x27;</span>, <span class="string">&#x27;♦️&#x27;</span>]</span><br><span class="line">        faces = [<span class="string">&#x27;&#x27;</span>, <span class="string">&#x27;A&#x27;</span>, <span class="string">&#x27;2&#x27;</span>, <span class="string">&#x27;3&#x27;</span>, <span class="string">&#x27;4&#x27;</span>, <span class="string">&#x27;5&#x27;</span>, <span class="string">&#x27;6&#x27;</span>, <span class="string">&#x27;7&#x27;</span>, <span class="string">&#x27;8&#x27;</span>, <span class="string">&#x27;9&#x27;</span>, <span class="string">&#x27;10&#x27;</span>, <span class="string">&#x27;J&#x27;</span>, <span class="string">&#x27;Q&#x27;</span>, <span class="string">&#x27;K&#x27;</span>]</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&#x27;<span class="subst">&#123;suites[self.suite.value]&#125;</span> <span class="subst">&#123;faces[self.face]&#125;</span>&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__str__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.show()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.show()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Poker</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;扑克&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.index = <span class="number">0</span></span><br><span class="line">        <span class="variable language_">self</span>.cards = [Card(suite, face)</span><br><span class="line">                    <span class="keyword">for</span> suite <span class="keyword">in</span> Suite</span><br><span class="line">                    <span class="keyword">for</span> face <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">14</span>)]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">shuffle</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;洗牌（随机乱序）&quot;&quot;&quot;</span></span><br><span class="line">        random.shuffle(<span class="variable language_">self</span>.cards)</span><br><span class="line">        <span class="variable language_">self</span>.index = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">deal</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;发牌&quot;&quot;&quot;</span></span><br><span class="line">        card = <span class="variable language_">self</span>.cards[<span class="variable language_">self</span>.index]</span><br><span class="line">        <span class="variable language_">self</span>.index += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> card</span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">has_more</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.index &lt; <span class="built_in">len</span>(<span class="variable language_">self</span>.cards)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Player</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;玩家&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name</span>):</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line">        <span class="variable language_">self</span>.cards = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_one</span>(<span class="params">self, card</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;摸一张牌&quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.cards.append(card)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">sort</span>(<span class="params">self, comp=<span class="keyword">lambda</span> card: (<span class="params">card.suite, card.face</span>)</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;整理手上的牌&quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.cards.sort(key=comp)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line">    poker = Poker()</span><br><span class="line">    poker.shuffle()</span><br><span class="line">    players = [Player(<span class="string">&#x27;东邪&#x27;</span>), Player(<span class="string">&#x27;西毒&#x27;</span>), Player(<span class="string">&#x27;南帝&#x27;</span>), Player(<span class="string">&#x27;北丐&#x27;</span>)]</span><br><span class="line">    <span class="keyword">while</span> poker.has_more:</span><br><span class="line">        <span class="keyword">for</span> player <span class="keyword">in</span> players:</span><br><span class="line">                player.get_one(poker.deal())</span><br><span class="line">    <span class="keyword">for</span> player <span class="keyword">in</span> players:</span><br><span class="line">        player.sort()</span><br><span class="line">        <span class="built_in">print</span>(player.name, end=<span class="string">&#x27;: &#x27;</span>)</span><br><span class="line">        <span class="built_in">print</span>(player.cards)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><blockquote><p>说明：上面的代码中使用了Emoji字符来表示扑克牌的四种花色，在某些不支持Emoji字符的系统上可能无法显示。</p></blockquote><ul><li>对象的复制（深复制&#x2F;深拷贝&#x2F;深度克隆和浅复制&#x2F;浅拷贝&#x2F;影子克隆）</li><li>垃圾回收、循环引用和弱引用<br>Python使用了自动化内存管理，这种管理机制以<strong>引用计数</strong>为基础，同时也引入了<strong>标记-清除</strong>和<strong>分代收集</strong>两种机制为辅的策略。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> struct_object &#123;</span><br><span class="line">    <span class="comment">/* 引用计数 */</span></span><br><span class="line">    <span class="type">int</span> ob_refcnt;</span><br><span class="line">    <span class="comment">/* 对象指针 */</span></span><br><span class="line">    struct_typeobject *ob_type;</span><br><span class="line">&#125; PyObject;</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 增加引用计数的宏定义 */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> Py_INCREF(op)   ((op)-&gt;ob_refcnt++)</span></span><br><span class="line"><span class="comment">/* 减少引用计数的宏定义 */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> Py_DECREF(op) \ <span class="comment">//减少计数</span></span></span><br><span class="line">    <span class="keyword">if</span> (--(op)-&gt;ob_refcnt != <span class="number">0</span>) \</span><br><span class="line">        ; \</span><br><span class="line">    <span class="keyword">else</span> \</span><br><span class="line">        __Py_Dealloc((PyObject *)(op))</span><br></pre></td></tr></table></figure><p>导致引用计数+1的情况：<br>    - 对象被创建，例如<code>a = 23</code><br>    - 对象被引用，例如<code>b = a</code><br>    - 对象被作为参数，传入到一个函数中，例如<code>f(a)</code><br>    - 对象作为一个元素，存储在容器中，例如<code>list1 = [a, a]</code></p><pre><code>导致引用计数-1的情况：- 对象的别名被显式销毁，例如`del a`- 对象的别名被赋予新的对象，例如`a = 24`- 一个对象离开它的作用域，例如f函数执行完毕时，f函数中的局部变量（全局变量不会）- 对象所在的容器被销毁，或从容器中删除对象引用计数可能会导致循环引用问题，而循环引用会导致内存泄露，如下面的代码所示。为了解决这个问题，Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候，对象被放在第一代中，如果在第一代的垃圾检查中对象存活了下来，该对象就会被放到第二代中，同理在第二代的垃圾检查中对象存活下来，该对象就会被放到第三代中。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 循环引用会导致内存泄露 - Python除了引用技术还引入了标记清理和分代回收</span></span><br><span class="line"><span class="comment"># 在Python 3.6以前如果重写__del__魔术方法会导致循环引用处理失效</span></span><br><span class="line"><span class="comment"># 如果不想造成循环引用可以使用弱引用</span></span><br><span class="line">list1 = []</span><br><span class="line">list2 = [] </span><br><span class="line">list1.append(list2)</span><br><span class="line">list2.append(list1)</span><br></pre></td></tr></table></figure>以下情况会导致垃圾回收：- 调用`gc.collect()`- gc模块的计数器达到阀值- 程序退出如果循环引用中两个对象都定义了`__del__`方法，gc模块不会销毁这些不可达对象，因为gc模块不知道应该先调用哪个对象的`__del__`方法，这个问题在Python 3.6中得到了解决。也可以通过`weakref`模块构造弱引用的方式来解决循环引用的问题。</code></pre><ul><li>魔法属性和方法（请参考《Python魔法方法指南》）</li></ul><p>有几个小问题请大家思考：<br>    - 自定义的对象能不能使用运算符做运算？<br>    - 自定义的对象能不能放到set中？能去重吗？<br>    - 自定义的对象能不能作为dict的键？<br>    - 自定义的对象能不能使用上下文语法？</p><ul><li>混入（Mixin）</li></ul><p>例子：自定义字典限制只有在指定的key不存在时才能在字典中设置键值对。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SetOnceMappingMixin</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;自定义混入类&quot;&quot;&quot;</span></span><br><span class="line">    __slots__ = ()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__setitem__</span>(<span class="params">self, key, value</span>):</span><br><span class="line">        <span class="keyword">if</span> key <span class="keyword">in</span> <span class="variable language_">self</span>:</span><br><span class="line">            <span class="keyword">raise</span> KeyError(<span class="built_in">str</span>(key) + <span class="string">&#x27; already set&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>().__setitem__(key, value)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SetOnceDict</span>(SetOnceMappingMixin, <span class="built_in">dict</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;自定义字典&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">my_dict= SetOnceDict()</span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    my_dict[<span class="string">&#x27;username&#x27;</span>] = <span class="string">&#x27;jackfrued&#x27;</span></span><br><span class="line">    my_dict[<span class="string">&#x27;username&#x27;</span>] = <span class="string">&#x27;hellokitty&#x27;</span></span><br><span class="line"><span class="keyword">except</span> KeyError:</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"><span class="built_in">print</span>(my_dict)</span><br></pre></td></tr></table></figure><ul><li>元编程和元类</li></ul><p>例子：用元类实现单例模式。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SingletonMeta</span>(<span class="title class_ inherited__">type</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;自定义元类&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">cls, *args, **kwargs</span>):</span><br><span class="line">        cls.__instance = <span class="literal">None</span></span><br><span class="line">        cls.__lock = threading.Lock()</span><br><span class="line">        <span class="built_in">super</span>().__init__(*args, **kwargs)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__call__</span>(<span class="params">cls, *args, **kwargs</span>):</span><br><span class="line">        <span class="keyword">if</span> cls.__instance <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">with</span> cls.__lock:</span><br><span class="line">                <span class="keyword">if</span> cls.__instance <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">                    cls.__instance = <span class="built_in">super</span>().__call__(*args, **kwargs)</span><br><span class="line">        <span class="keyword">return</span> cls.__instance</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">President</span>(metaclass=SingletonMeta):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;总统(单例类)&quot;&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><ul><li><p>面向对象设计原则</p><ul><li>单一职责原则 （<strong>S</strong>RP）- 一个类只做该做的事情（类的设计要高内聚）</li><li>开闭原则 （<strong>O</strong>CP）- 软件实体应该对扩展开发对修改关闭</li><li>依赖倒转原则（DIP）- 面向抽象编程（在弱类型语言中已经被弱化）</li><li>里氏替换原则（<strong>L</strong>SP） - 任何时候可以用子类对象替换掉父类对象</li><li>接口隔离原则（<strong>I</strong>SP）- 接口要小而专不要大而全（Python中没有接口的概念）</li><li>合成聚合复用原则（CARP） - 优先使用强关联关系而不是继承关系复用代码</li><li>最少知识原则（迪米特法则，Lo<strong>D</strong>）- 不要给没有必然联系的对象发消息</li></ul><blockquote><p>说明：上面加粗的字母放在一起称为面向对象的<strong>SOLID</strong>原则。</p></blockquote><p>  GoF设计模式</p><ul><li>创建型模式：单例、工厂、建造者、原型</li><li>结构型模式：适配器、门面（外观）、代理</li><li>行为型模式：迭代器、观察者、状态、策略</li></ul></li></ul><p>例子：可插拔的哈希算法。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">StreamHasher</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;哈希摘要生成器(策略模式)&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, alg=<span class="string">&#x27;md5&#x27;</span>, size=<span class="number">4096</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.size = size</span><br><span class="line">        alg = alg.lower()</span><br><span class="line">        <span class="variable language_">self</span>.hasher = <span class="built_in">getattr</span>(<span class="built_in">__import__</span>(<span class="string">&#x27;hashlib&#x27;</span>), alg.lower())()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__call__</span>(<span class="params">self, stream</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.to_digest(stream)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">to_digest</span>(<span class="params">self, stream</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;生成十六进制形式的摘要&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">for</span> buf <span class="keyword">in</span> <span class="built_in">iter</span>(<span class="keyword">lambda</span>: stream.read(<span class="variable language_">self</span>.size), <span class="string">b&#x27;&#x27;</span>):</span><br><span class="line">            <span class="variable language_">self</span>.hasher.update(buf)</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.hasher.hexdigest()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line">    hasher1 = StreamHasher()</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;Python-3.7.1.tgz&#x27;</span>, <span class="string">&#x27;rb&#x27;</span>) <span class="keyword">as</span> stream:</span><br><span class="line">        <span class="built_in">print</span>(hasher1.to_digest(stream))</span><br><span class="line">    hasher2 = StreamHasher(<span class="string">&#x27;sha1&#x27;</span>)</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;Python-3.7.1.tgz&#x27;</span>, <span class="string">&#x27;rb&#x27;</span>) <span class="keyword">as</span> stream:</span><br><span class="line">        <span class="built_in">print</span>(hasher2(stream))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><h3 id="迭代器和生成器"><a href="#迭代器和生成器" class="headerlink" title="迭代器和生成器"></a>迭代器和生成器</h3><ul><li><p>和迭代器相关的魔术方法（<code>__iter__</code>和<code>__next__</code>）</p></li><li><p>两种创建生成器的方式（生成器表达式和<code>yield</code>关键字）</p></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">fib</span>(<span class="params">num</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成器&quot;&quot;&quot;</span></span><br><span class="line">    a, b = <span class="number">0</span>, <span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(num):</span><br><span class="line">        a, b = b, a + b</span><br><span class="line">        <span class="keyword">yield</span> a</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Fib</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;迭代器&quot;&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, num</span>):</span><br><span class="line">        <span class="variable language_">self</span>.num = num</span><br><span class="line">        <span class="variable language_">self</span>.a, <span class="variable language_">self</span>.b = <span class="number">0</span>, <span class="number">1</span></span><br><span class="line">        <span class="variable language_">self</span>.idx = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__iter__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__next__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.idx &lt; <span class="variable language_">self</span>.num:</span><br><span class="line">            <span class="variable language_">self</span>.a, <span class="variable language_">self</span>.b = <span class="variable language_">self</span>.b, <span class="variable language_">self</span>.a + <span class="variable language_">self</span>.b</span><br><span class="line">            <span class="variable language_">self</span>.idx += <span class="number">1</span></span><br><span class="line">            <span class="keyword">return</span> <span class="variable language_">self</span>.a</span><br><span class="line">        <span class="keyword">raise</span> StopIteration()</span><br></pre></td></tr></table></figure><h3 id="并发编程"><a href="#并发编程" class="headerlink" title="并发编程"></a>并发编程</h3><p>Python中实现并发编程的三种方案：多线程、多进程和异步I&#x2F;O。并发编程的好处在于可以提升程序的执行效率以及改善用户体验；坏处在于并发的程序不容易开发和调试，同时对其他程序来说它并不友好。</p><ul><li>多线程：Python中提供了Thread类并辅以Lock、Condition、Event、Semaphore和Barrier。Python中有GIL来防止多个线程同时执行本地字节码，这个锁对于CPython是必须的，因为CPython的内存管理并不是线程安全的，因为GIL的存在多线程并不能发挥CPU的多核特性。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">面试题：进程和线程的区别和联系？</span></span><br><span class="line"><span class="string">进程 - 操作系统分配内存的基本单位 - 一个进程可以包含一个或多个线程</span></span><br><span class="line"><span class="string">线程 - 操作系统分配CPU的基本单位</span></span><br><span class="line"><span class="string">并发编程（concurrent programming）</span></span><br><span class="line"><span class="string">1. 提升执行性能 - 让程序中没有因果关系的部分可以并发的执行</span></span><br><span class="line"><span class="string">2. 改善用户体验 - 让耗时间的操作不会造成程序的假死</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> glob</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"></span><br><span class="line">PREFIX = <span class="string">&#x27;thumbnails&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_thumbnail</span>(<span class="params">infile, size, <span class="built_in">format</span>=<span class="string">&#x27;PNG&#x27;</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成指定图片文件的缩略图&quot;&quot;&quot;</span></span><br><span class="line">file, ext = os.path.splitext(infile)</span><br><span class="line">file = file[file.rfind(<span class="string">&#x27;/&#x27;</span>) + <span class="number">1</span>:]</span><br><span class="line">outfile = <span class="string">f&#x27;<span class="subst">&#123;PREFIX&#125;</span>/<span class="subst">&#123;file&#125;</span>_<span class="subst">&#123;size[<span class="number">0</span>]&#125;</span>_<span class="subst">&#123;size[<span class="number">1</span>]&#125;</span>.<span class="subst">&#123;ext&#125;</span>&#x27;</span></span><br><span class="line">img = Image.<span class="built_in">open</span>(infile)</span><br><span class="line">img.thumbnail(size, Image.ANTIALIAS)</span><br><span class="line">img.save(outfile, <span class="built_in">format</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(PREFIX):</span><br><span class="line">    os.mkdir(PREFIX)</span><br><span class="line"><span class="keyword">for</span> infile <span class="keyword">in</span> glob.glob(<span class="string">&#x27;images/*.png&#x27;</span>):</span><br><span class="line">    <span class="keyword">for</span> size <span class="keyword">in</span> (<span class="number">32</span>, <span class="number">64</span>, <span class="number">128</span>):</span><br><span class="line">            <span class="comment"># 创建并启动线程</span></span><br><span class="line">        threading.Thread(</span><br><span class="line">            target=generate_thumbnail, </span><br><span class="line">            args=(infile, (size, size))</span><br><span class="line">        ).start()</span><br><span class="line">        </span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">main()</span><br></pre></td></tr></table></figure><p>多个线程竞争资源的情况</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">多线程程序如果没有竞争资源处理起来通常也比较简单</span></span><br><span class="line"><span class="string">当多个线程竞争临界资源的时候如果缺乏必要的保护措施就会导致数据错乱</span></span><br><span class="line"><span class="string">说明：临界资源就是被多个线程竞争的资源</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> concurrent.futures <span class="keyword">import</span> ThreadPoolExecutor</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Account</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;银行账户&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.balance = <span class="number">0.0</span></span><br><span class="line">        <span class="variable language_">self</span>.lock = threading.Lock()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">deposit</span>(<span class="params">self, money</span>):</span><br><span class="line">        <span class="comment"># 通过锁保护临界资源</span></span><br><span class="line">        <span class="keyword">with</span> <span class="variable language_">self</span>.lock:</span><br><span class="line">            new_balance = <span class="variable language_">self</span>.balance + money</span><br><span class="line">            time.sleep(<span class="number">0.001</span>)</span><br><span class="line">            <span class="variable language_">self</span>.balance = new_balance</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AddMoneyThread</span>(threading.Thread):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;自定义线程类&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, account, money</span>):</span><br><span class="line">        <span class="variable language_">self</span>.account = account</span><br><span class="line">        <span class="variable language_">self</span>.money = money</span><br><span class="line">        <span class="comment"># 自定义线程的初始化方法中必须调用父类的初始化方法</span></span><br><span class="line">        <span class="built_in">super</span>().__init__()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="comment"># 线程启动之后要执行的操作</span></span><br><span class="line">        <span class="variable language_">self</span>.account.deposit(<span class="variable language_">self</span>.money)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line">    account = Account()</span><br><span class="line">    <span class="comment"># 创建线程池</span></span><br><span class="line">    pool = ThreadPoolExecutor(max_workers=<span class="number">10</span>)</span><br><span class="line">    futures = []</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">100</span>):</span><br><span class="line">        <span class="comment"># 创建线程的第1种方式</span></span><br><span class="line">        <span class="comment"># threading.Thread(</span></span><br><span class="line">        <span class="comment">#     target=account.deposit, args=(1, )</span></span><br><span class="line">        <span class="comment"># ).start()</span></span><br><span class="line">        <span class="comment"># 创建线程的第2种方式</span></span><br><span class="line">        <span class="comment"># AddMoneyThread(account, 1).start()</span></span><br><span class="line">        <span class="comment"># 创建线程的第3种方式</span></span><br><span class="line">        <span class="comment"># 调用线程池中的线程来执行特定的任务</span></span><br><span class="line">        future = pool.submit(account.deposit, <span class="number">1</span>)</span><br><span class="line">        futures.append(future)</span><br><span class="line">    <span class="comment"># 关闭线程池</span></span><br><span class="line">    pool.shutdown()</span><br><span class="line">    <span class="keyword">for</span> future <span class="keyword">in</span> futures:</span><br><span class="line">        future.result()</span><br><span class="line">    <span class="built_in">print</span>(account.balance)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><p>修改上面的程序，启动5个线程向账户中存钱，5个线程从账户中取钱，取钱时如果余额不足就暂停线程进行等待。为了达到上述目标，需要对存钱和取钱的线程进行调度，在余额不足时取钱的线程暂停并释放锁，而存钱的线程将钱存入后要通知取钱的线程，使其从暂停状态被唤醒。可以使用<code>threading</code>模块的Condition来实现线程调度，该对象也是基于锁来创建的，代码如下所示：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">多个线程竞争一个资源 - 保护临界资源 - 锁（Lock/RLock）</span></span><br><span class="line"><span class="string">多个线程竞争多个资源（线程数&gt;资源数） - 信号量（Semaphore）</span></span><br><span class="line"><span class="string">多个线程的调度 - 暂停线程执行/唤醒等待中的线程 - Condition</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> concurrent.futures <span class="keyword">import</span> ThreadPoolExecutor</span><br><span class="line"><span class="keyword">from</span> random <span class="keyword">import</span> randint</span><br><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> sleep</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Account</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;银行账户&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, balance=<span class="number">0</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.balance = balance</span><br><span class="line">        lock = threading.Lock()</span><br><span class="line">        <span class="variable language_">self</span>.condition = threading.Condition(lock)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">withdraw</span>(<span class="params">self, money</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;取钱&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">with</span> <span class="variable language_">self</span>.condition:</span><br><span class="line">            <span class="keyword">while</span> money &gt; <span class="variable language_">self</span>.balance:</span><br><span class="line">                <span class="variable language_">self</span>.condition.wait()</span><br><span class="line">            new_balance = <span class="variable language_">self</span>.balance - money</span><br><span class="line">            sleep(<span class="number">0.001</span>)</span><br><span class="line">            <span class="variable language_">self</span>.balance = new_balance</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">deposit</span>(<span class="params">self, money</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;存钱&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">with</span> <span class="variable language_">self</span>.condition:</span><br><span class="line">            new_balance = <span class="variable language_">self</span>.balance + money</span><br><span class="line">            sleep(<span class="number">0.001</span>)</span><br><span class="line">            <span class="variable language_">self</span>.balance = new_balance</span><br><span class="line">            <span class="variable language_">self</span>.condition.notify_all()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add_money</span>(<span class="params">account</span>):</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        money = randint(<span class="number">5</span>, <span class="number">10</span>)</span><br><span class="line">        account.deposit(money)</span><br><span class="line">        <span class="built_in">print</span>(threading.current_thread().name, </span><br><span class="line">            <span class="string">&#x27;:&#x27;</span>, money, <span class="string">&#x27;====&gt;&#x27;</span>, account.balance)</span><br><span class="line">        sleep(<span class="number">0.5</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sub_money</span>(<span class="params">account</span>):</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        money = randint(<span class="number">10</span>, <span class="number">30</span>)</span><br><span class="line">        account.withdraw(money)</span><br><span class="line">        <span class="built_in">print</span>(threading.current_thread().name, </span><br><span class="line">            <span class="string">&#x27;:&#x27;</span>, money, <span class="string">&#x27;&lt;====&#x27;</span>, account.balance)</span><br><span class="line">        sleep(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    account = Account()</span><br><span class="line">    <span class="keyword">with</span> ThreadPoolExecutor(max_workers=<span class="number">10</span>) <span class="keyword">as</span> pool:</span><br><span class="line">        <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line">            pool.submit(add_money, account)</span><br><span class="line">            pool.submit(sub_money, account)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><ul><li>多进程：多进程可以有效的解决GIL的问题，实现多进程主要的类是Process，其他辅助的类跟threading模块中的类似，进程间共享数据可以使用管道、套接字等，在multiprocessing模块中有一个Queue类，它基于管道和锁机制提供了多个进程共享的队列。下面是官方文档上关于多进程和进程池的一个示例。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">多进程和进程池的使用</span></span><br><span class="line"><span class="string">多线程因为GIL的存在不能够发挥CPU的多核特性</span></span><br><span class="line"><span class="string">对于计算密集型任务应该考虑使用多进程</span></span><br><span class="line"><span class="string">time python3 example22.py</span></span><br><span class="line"><span class="string">real    0m11.512s</span></span><br><span class="line"><span class="string">user    0m39.319s</span></span><br><span class="line"><span class="string">sys     0m0.169s</span></span><br><span class="line"><span class="string">使用多进程后实际执行时间为11.512秒，而用户时间39.319秒约为实际执行时间的4倍</span></span><br><span class="line"><span class="string">这就证明我们的程序通过多进程使用了CPU的多核特性，而且这台计算机配置了4核的CPU</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> concurrent.futures</span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"></span><br><span class="line">PRIMES = [</span><br><span class="line">    <span class="number">1116281</span>,</span><br><span class="line">    <span class="number">1297337</span>,</span><br><span class="line">    <span class="number">104395303</span>,</span><br><span class="line">    <span class="number">472882027</span>,</span><br><span class="line">    <span class="number">533000389</span>,</span><br><span class="line">    <span class="number">817504243</span>,</span><br><span class="line">    <span class="number">982451653</span>,</span><br><span class="line">    <span class="number">112272535095293</span>,</span><br><span class="line">    <span class="number">112582705942171</span>,</span><br><span class="line">    <span class="number">112272535095293</span>,</span><br><span class="line">    <span class="number">115280095190773</span>,</span><br><span class="line">    <span class="number">115797848077099</span>,</span><br><span class="line">    <span class="number">1099726899285419</span></span><br><span class="line">] * <span class="number">5</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">is_prime</span>(<span class="params">n</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;判断素数&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> n % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">    sqrt_n = <span class="built_in">int</span>(math.floor(math.sqrt(n)))</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">3</span>, sqrt_n + <span class="number">1</span>, <span class="number">2</span>):</span><br><span class="line">        <span class="keyword">if</span> n % i == <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">with</span> concurrent.futures.ProcessPoolExecutor() <span class="keyword">as</span> executor:</span><br><span class="line">        <span class="keyword">for</span> number, prime <span class="keyword">in</span> <span class="built_in">zip</span>(PRIMES, executor.<span class="built_in">map</span>(is_prime, PRIMES)):</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;%d is prime: %s&#x27;</span> % (number, prime))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><blockquote><p>说明：<strong>多线程和多进程的比较</strong>。</p><p>以下情况需要使用多线程：</p><ol><li>程序需要维护许多共享的状态（尤其是可变状态），Python中的列表、字典、集合都是线程安全的，所以使用线程而不是进程维护共享状态的代价相对较小。</li><li>程序会花费大量时间在I&#x2F;O操作上，没有太多并行计算的需求且不需占用太多的内存。</li></ol><p>以下情况需要使用多进程：</p><ol><li>程序执行计算密集型任务（如：字节码操作、数据处理、科学计算）。</li><li>程序的输入可以并行的分成块，并且可以将运算结果合并。</li><li>程序在内存使用方面没有任何限制且不强依赖于I&#x2F;O操作（如：读写文件、套接字等）。</li></ol></blockquote><ul><li>异步处理：从调度程序的任务队列中挑选任务，该调度程序以交叉的形式执行这些任务，我们并不能保证任务将以某种顺序去执行，因为执行顺序取决于队列中的一项任务是否愿意将CPU处理时间让位给另一项任务。异步任务通常通过多任务协作处理的方式来实现，由于执行时间和顺序的不确定，因此需要通过回调式编程或者<code>future</code>对象来获取任务执行的结果。Python 3通过<code>asyncio</code>模块和<code>await</code>和<code>async</code>关键字（在Python 3.7中正式被列为关键字）来支持异步处理。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">异步I/O - async / await</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">num_generator</span>(<span class="params">m, n</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;指定范围的数字生成器&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">yield</span> <span class="keyword">from</span> <span class="built_in">range</span>(m, n + <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">prime_filter</span>(<span class="params">m, n</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;素数过滤器&quot;&quot;&quot;</span></span><br><span class="line">    primes = []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> num_generator(m, n):</span><br><span class="line">        flag = <span class="literal">True</span></span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">2</span>, <span class="built_in">int</span>(i ** <span class="number">0.5</span> + <span class="number">1</span>)):</span><br><span class="line">            <span class="keyword">if</span> i % j == <span class="number">0</span>:</span><br><span class="line">                flag = <span class="literal">False</span></span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">if</span> flag:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;Prime =&gt;&#x27;</span>, i)</span><br><span class="line">            primes.append(i)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">await</span> asyncio.sleep(<span class="number">0.001</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">tuple</span>(primes)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">square_mapper</span>(<span class="params">m, n</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;平方映射器&quot;&quot;&quot;</span></span><br><span class="line">    squares = []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> num_generator(m, n):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Square =&gt;&#x27;</span>, i * i)</span><br><span class="line">        squares.append(i * i)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">await</span> asyncio.sleep(<span class="number">0.001</span>)</span><br><span class="line">    <span class="keyword">return</span> squares</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;主函数&quot;&quot;&quot;</span></span><br><span class="line">    loop = asyncio.get_event_loop()</span><br><span class="line">    future = asyncio.gather(prime_filter(<span class="number">2</span>, <span class="number">100</span>), square_mapper(<span class="number">1</span>, <span class="number">100</span>))</span><br><span class="line">    future.add_done_callback(<span class="keyword">lambda</span> x: <span class="built_in">print</span>(x.result()))</span><br><span class="line">    loop.run_until_complete(future)</span><br><span class="line">    loop.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><blockquote><p>说明：上面的代码使用<code>get_event_loop</code>函数获得系统默认的事件循环，通过<code>gather</code>函数可以获得一个<code>future</code>对象，<code>future</code>对象的<code>add_done_callback</code>可以添加执行完成时的回调函数，<code>loop</code>对象的<code>run_until_complete</code>方法可以等待通过<code>future</code>对象获得协程执行结果。</p></blockquote><p>Python中有一个名为<code>aiohttp</code>的三方库，它提供了异步的HTTP客户端和服务器，这个三方库可以跟<code>asyncio</code>模块一起工作，并提供了对<code>Future</code>对象的支持。Python 3.6中引入了async和await来定义异步执行的函数以及创建异步上下文，在Python 3.7中它们正式成为了关键字。下面的代码异步的从5个URL中获取页面并通过正则表达式的命名捕获组提取了网站的标题。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> aiohttp</span><br><span class="line"></span><br><span class="line">PATTERN = re.<span class="built_in">compile</span>(<span class="string">r&#x27;\&lt;title\&gt;(?P&lt;title&gt;.*)\&lt;\/title\&gt;&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">fetch_page</span>(<span class="params">session, url</span>):</span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">with</span> session.get(url, ssl=<span class="literal">False</span>) <span class="keyword">as</span> resp:</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">await</span> resp.text()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">show_title</span>(<span class="params">url</span>):</span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">with</span> aiohttp.ClientSession() <span class="keyword">as</span> session:</span><br><span class="line">        html = <span class="keyword">await</span> fetch_page(session, url)</span><br><span class="line">        <span class="built_in">print</span>(PATTERN.search(html).group(<span class="string">&#x27;title&#x27;</span>))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    urls = (<span class="string">&#x27;https://www.python.org/&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;https://git-scm.com/&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;https://www.jd.com/&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;https://www.taobao.com/&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;https://www.douban.com/&#x27;</span>)</span><br><span class="line">    loop = asyncio.get_event_loop()</span><br><span class="line">    tasks = [show_title(url) <span class="keyword">for</span> url <span class="keyword">in</span> urls]</span><br><span class="line">    loop.run_until_complete(asyncio.wait(tasks))</span><br><span class="line">    loop.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><blockquote><p>说明：<strong>异步I&#x2F;O与多进程的比较</strong>。</p><p>当程序不需要真正的并发性或并行性，而是更多的依赖于异步处理和回调时，asyncio就是一种很好的选择。如果程序中有大量的等待与休眠时，也应该考虑asyncio，它很适合编写没有实时数据处理需求的Web应用服务器。</p></blockquote><p>Python还有很多用于处理并行任务的三方库，例如：joblib、PyMP等。实际开发中，要提升系统的可扩展性和并发性通常有垂直扩展（增加单个节点的处理能力）和水平扩展（将单个节点变成多个节点）两种做法。可以通过消息队列来实现应用程序的解耦合，消息队列相当于是多线程同步队列的扩展版本，不同机器上的应用程序相当于就是线程，而共享的分布式消息队列就是原来程序中的Queue。消息队列（面向消息的中间件）的最流行和最标准化的实现是AMQP（高级消息队列协议），AMQP源于金融行业，提供了排队、路由、可靠传输、安全等功能，最著名的实现包括：Apache的ActiveMQ、RabbitMQ等。</p><p>要实现任务的异步化，可以使用名为Celery的三方库。Celery是Python编写的分布式任务队列，它使用分布式消息进行工作，可以基于RabbitMQ或Redis来作为后端的消息代理。</p>]]>
    </content>
    <id>https://minniexcode.github.io/memoirs/20200310/python/Python-100-2/</id>
    <link href="https://minniexcode.github.io/memoirs/20200310/python/Python-100-2/"/>
    <published>2020-03-10T13:51:59.000Z</published>
    <summary>
      <![CDATA[<h3 id="数据结构和算法"><a href="#数据结构和算法" class="headerlink" title="数据结构和算法"></a>数据结构和算法</h3><ul>
<li>算法：解决问题的方法和步骤</li>
<li>评价算法的好坏：渐近时间复杂度和渐近空间复杂度。</li>
<li>渐近时间复杂度的大O标记：<ul>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(c)" style="margin-bottom: 0; display:inline;" /> - 常量时间复杂度 - 布隆过滤器 &#x2F; 哈希存储</p>
</li>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(log_2n)" style="margin-bottom: 0; display:inline;" /> - 对数时间复杂度 - 折半查找（二分查找）</p>
</li>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(n)" style="margin-bottom: 0; display:inline;"/> - 线性时间复杂度 - 顺序查找 &#x2F; 桶排序</p>
</li>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(n*log_2n)" style="margin-bottom: 0; display:inline;"/> - 对数线性时间复杂度 - 高级排序算法（归并排序、快速排序）</p>
</li>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(n^2)" style="margin-bottom: 0; display:inline;"/> - 平方时间复杂度 - 简单排序算法（选择排序、插入排序、冒泡排序）</p>
</li>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(n^3)" style="margin-bottom: 0; display:inline;"/> - 立方时间复杂度 - Floyd算法 &#x2F; 矩阵乘法运算</p>
</li>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(2^n)" style="margin-bottom: 0; display:inline;"/> - 几何级数时间复杂度 - 汉诺塔</p>
</li>
<li><p><img src="http://latex.codecogs.com/gif.latex?O(n!)" style="margin-bottom: 0; display:inline;"/> - 阶乘时间复杂度 - 旅行经销商问题 - NP</p>
</li>
</ul>
</li>
</ul>]]>
    </summary>
    <title>Python-100天(二)-Python语言进阶</title>
    <updated>2026-03-16T16:24:40.838Z</updated>
  </entry>
</feed>
