<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>HK Talk</title>
  
  <subtitle>关于技术，关于生活</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://blog.hhking.cn/"/>
  <updated>2020-08-21T11:20:08.931Z</updated>
  <id>https://blog.hhking.cn/</id>
  
  <author>
    <name>hhking</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>从 Babel7 编译 node_modules 报错说起</title>
    <link href="https://blog.hhking.cn/2020/08/21/babel-with-node-modules/"/>
    <id>https://blog.hhking.cn/2020/08/21/babel-with-node-modules/</id>
    <published>2020-08-21T11:06:48.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>好久没更新博客了🙄, 这篇可以结合 <a href="https://blog.hhking.cn/2019/04/02/babel-v7-update/">Babel 7 升级实践</a> 一起看</p></blockquote><h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><ul><li>A：我这边有问题啊，怎么页面空白？</li><li>B：我这里明明是好的！</li><li>A：你自己来试</li><li>B：怎么这么多奇奇怪怪的问题</li></ul><p>这是发生在我和同事之间的对话。我跑过去看了一下控制台，页面直接爆炸💥了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Uncaught TypeError: $ is not a function</span><br></pre></td></tr></table></figure><p>定位到是 <code>core-js</code> 的某个文件提示这个错，怎么回事？</p><p>google 了一圈，再结合项目的配置，终于定位到问题：<code>babel</code> 编译 <code>node_modules</code>, <code>core-js</code> 自己 polyfill 自己，导致的报错。</p><h2 id="原因分析"><a href="#原因分析" class="headerlink" title="原因分析"></a>原因分析</h2><p>好长一段时间没有看 babel 的配置，又重新学习了一遍。</p><p>我之前的博客 <a href="https://blog.hhking.cn/2019/04/02/babel-v7-update/">Babel 7 升级实践</a> 在最后面的<strong>总结</strong>中提到，Babel 7 解决了编译 <code>node_modules</code> 的问题, 所以按照我们之前学到的内容，项目的 babel 的配置可能会是这样的：</p><figure class="highlight plain"><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">&#123;</span><br><span class="line">  &quot;presets&quot;: [</span><br><span class="line">    [</span><br><span class="line">      &quot;@babel/preset-env&quot;,</span><br><span class="line">      &#123;</span><br><span class="line">        modules: false,</span><br><span class="line">        useBuiltIns: &apos;usage&apos;,</span><br><span class="line">        corejs: &#123;</span><br><span class="line">          version: 3,</span><br><span class="line">          proposals: true,</span><br><span class="line">        &#125;,</span><br><span class="line">        targets: &#123;</span><br><span class="line">          chrome: 58,</span><br><span class="line">          ie: 11,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    ]</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是我们项目里还有 <code>core-js</code> 和 <code>webpack</code>，所以可能就悲剧了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Uncaught TypeError: $ is not a function</span><br></pre></td></tr></table></figure><p>为什么呢？具体可以看这个 <a href="https://github.com/zloirock/core-js/issues/743#issuecomment-572096103" target="_blank" rel="noopener">issue 的解释</a> ：</p><p>我们使用 polyfill 的时候会注入 <code>require(&quot;core-js/...&quot;)</code>, 这时</p><ol><li>导入 <code>core-js</code></li><li><code>core-js</code> 依赖 <code>webpack/buildin</code></li><li><code>webpack/buildin</code> 被编译，polyfill 需要依赖 <code>core-js</code></li><li><code>core-js</code> 在 <code>1</code> 已经导入过，<code>module.exports</code> 初始化成了 <code>{}</code>，原本是期望导入的是个函数，也就出现错误。</li></ol><p>所以在编译 <code>node_modules</code> 时，需要注意：</p><ul><li>排除掉 <code>core-js</code></li><li><code>webpack/buildin</code></li><li>如果还用到了 <code>@babel/plugin-transform-runtime</code>，还需要排除 <code>@babel/runtime-corejs3</code></li></ul><p>也就是 babel 需要添加下面配置：</p><figure class="highlight plain"><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">exclude : [</span><br><span class="line">  /\bcore-js\b/,</span><br><span class="line">  /\bwebpack\/buildin\b/,</span><br><span class="line">  /@babel\/runtime-corejs3/</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>通过 chrome 的调试，也可以确定上面说的，最后 <code>$</code> 其实是一个空对象（也就是导入了空对象，不是期望的函数）</p><p>我们断点在报错的位置：<br><img src="https://i.loli.net/2020/08/21/ZUnMzubiDetrOox.png" alt="1.png"></p><p>可以看到是导入 <code>/internals/export</code> 是个空对象。我们找到 <code>/internals/export</code> ，在顶部断点后刷新页面，会发现先执行了这个文件<br><img src="https://i.loli.net/2020/08/21/FptPDOJ192vhniz.png" alt="2.png"></p><p>由此我们大致可以看出来过程：<br>1、require <code>/internals/export</code><br>2、<code>/internals/export</code> 往后执行，又 require 了 <code>es.array.index-of</code><br>3、<code>es.array.index-of</code> 这时候 require <code>/internals/export</code><br>4、<code>/internals/export</code> 已经导入了，<code>module.exports</code> 初始化成了 <code>{}</code>，所以 <code>$</code> 赋值成了空对象，继续执行就报错了</p><p>问题来了？为什么 <code>$</code> 会变成，其实是 <code>commonjs</code> 循环 <code>import</code> 的问题。</p><h2 id="循环导入"><a href="#循环导入" class="headerlink" title="循环导入"></a>循环导入</h2><p>上面的问题，可以说是 <code>commonjs</code> 的特性，看下面的例子：</p><figure class="highlight plain"><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">File A:</span><br><span class="line">var b = require(`file B`)</span><br><span class="line"></span><br><span class="line">File B:</span><br><span class="line">var a = require(`file A`)</span><br></pre></td></tr></table></figure><p>当使用 commonjs require 一个模块的时候，模块的 export 会初始化成空对象：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">module.exports = &#123;&#125;</span><br></pre></td></tr></table></figure><p>在执行这个模块里的后续代码时，会对 export 进行扩展或者重写，也就是我们平时导出的内容：<br></p><figure class="highlight plain"><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">exports.namedExport = function() &#123; /* ... */ &#125;; // extends</span><br><span class="line"></span><br><span class="line">module.exports = &#123; namedExport: function() &#123; /* ... */ &#125; &#125;; // overrides</span><br></pre></td></tr></table></figure><p></p><p>但是我们上面的代码，情况是这样的：</p><ul><li>require 模块A，A 的导出初始化成空对象</li><li>A 又去 require 模块 B</li><li>B 这时又 require A</li></ul><p>而 A 在一开始已经 require 过，A 里的后续代码不会再执行，所以返回了一个空对象。如果 A 继续执行，就会导致死循环了。所以 a 的值也就是个空对象了。</p><h2 id="最开始的问题"><a href="#最开始的问题" class="headerlink" title="最开始的问题"></a>最开始的问题</h2><p>通过上面的内容，我们知道了，只要我们编译 <code>node_modules</code> 时候，排除一些不需要的文件就行了。</p><p>但是在我们的项目，其实是已经配置了：</p><figure class="highlight plain"><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">// babel.config.js</span><br><span class="line">ignore: [/\core-js/, /webpack\/buildin/]</span><br></pre></td></tr></table></figure><p>所以在我自己的电脑上是没问题的。问题在哪？</p><p>我用的是 <code>mac</code>，同事的是 <code>windows</code>，看来是 <code>windows</code> 下出现的问题。</p><p>查了一下 babel 的文档，发现 <code>ignore</code> 的匹配模式有两种，我们这里用的是 <code>RegExp</code>：</p><blockquote><p>RegExp - A regular expression to match against the normalized filename. On POSIX the path RegExp will run against a /-separated path, and on Windows it will be on a -separated path.</p></blockquote><p>也就是说 mac 和 windows 的分隔符是没法自动处理的，需要区分！</p><p>解决办法也简单，可以使用 <code>string</code> 的模式，或者简单粗暴的直接添加对 windows 的匹配：<br></p><figure class="highlight plain"><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">// babel.config.js</span><br><span class="line">ignore: [/\/core-js/, /webpack\/buildin/, /\\core-js/, /webpack\\buildin/]</span><br></pre></td></tr></table></figure><p></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在 babel 编译 node_modules 时，需要注意下面的配置：</p><ul><li>看情况需要 exclude <code>core-js</code>、<code>webpack/buildin</code>、<code>@babel/runtime-corejs3</code></li><li>如果是在 <code>babel.config.js</code> 配置匹配模式，需要注意是否有跨平台的问题，比如分隔符的兼容</li></ul><p>最后需要吐槽一下：加了 <code>node_modules</code> 编译，初次编译慢了一倍！！！告辞！👋</p><p>THE END！</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://stackoverflow.com/questions/30378226/circular-imports-with-webpack-returning-empty-object" target="_blank" rel="noopener">circular imports with webpack returning empty object</a></li><li><a href="https://stackoverflow.com/questions/57361439/how-to-exclude-core-js-using-usebuiltins-usage" target="_blank" rel="noopener">How to exclude core-js using useBuiltIns: “usage”</a></li><li><a href="https://babeljs.io/docs/en/options#matchpattern" target="_blank" rel="noopener">Babel MatchPattern</a></li><li><a href="https://github.com/babel/babel/issues/7559" target="_blank" rel="noopener">useBuiltins: ‘usage’ fails with babel-loader when not excluding node_modules</a></li><li><a href="https://github.com/zloirock/core-js/issues/743" target="_blank" rel="noopener">Uncaught TypeError: isObject is not a function (with useBuiltIns: usage)</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;&lt;p&gt;好久没更新博客了🙄, 这篇可以结合 &lt;a href=&quot;https://blog.hhking.cn/2019/04/02/babel-v7-update/&quot;&gt;Babel 7 升级实践&lt;/a&gt; 一起看&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;
      
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="Babel" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/Babel/"/>
    
    
      <category term="Babel" scheme="https://blog.hhking.cn/tags/Babel/"/>
    
  </entry>
  
  <entry>
    <title>Git 分支开发规范</title>
    <link href="https://blog.hhking.cn/2019/12/28/git-branch-guideline/"/>
    <id>https://blog.hhking.cn/2019/12/28/git-branch-guideline/</id>
    <published>2019-12-28T03:30:39.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在整理文档和一些团队规范，整理了一下团队中使用 Git 的一些规范。</p><blockquote><p>规范的制定，要根据不同团队和场景</p></blockquote><h2 id="分支命名"><a href="#分支命名" class="headerlink" title="分支命名"></a>分支命名</h2><h3 id="master-分支"><a href="#master-分支" class="headerlink" title="master 分支"></a>master 分支</h3><ul><li><code>master</code> 是主分支，用于部署线上生产环境</li><li><code>master</code> 分支由 <code>release</code>、<code>hotfix</code> 分支合并，禁止直接修改</li></ul><h3 id="release-分支"><a href="#release-分支" class="headerlink" title="release 分支"></a>release 分支</h3><ul><li><code>release</code> 是预发布分支，对应部署预发布环境</li><li><code>release</code> 分支一般由 <code>develop</code> 分支合并</li><li><code>release</code> 分支完成测试上线时，要分别合并到 <code>master</code> 和 <code>develop</code> 分支</li></ul><h3 id="develop-分支"><a href="#develop-分支" class="headerlink" title="develop 分支"></a>develop 分支</h3><ul><li><code>develop</code> 为开发分支，对应部署测试环境</li><li><code>develop</code> 分支需要保持最新和 bug 修复后的代码</li></ul><h3 id="feature-分支"><a href="#feature-分支" class="headerlink" title="feature 分支"></a>feature 分支</h3><ul><li>开发新功能时，从 <code>develop</code> 分支创建新的功能分支</li><li>功能分支都以 <code>feature/</code> 开头，命名规则：<code>feature/**</code>，例如：<code>feature/user-center</code></li><li>功能完成后合并到 <code>develop</code> 分支测试，并删除对应的功能分支</li></ul><h3 id="hotfix-分支"><a href="#hotfix-分支" class="headerlink" title="hotfix 分支"></a>hotfix 分支</h3><ul><li>紧急修复 bug 分支，从 <code>master</code> 分支创建</li><li>命名以 <code>hotfix/</code> 开头，例如：<code>hotfix/user-bug</code></li><li>bug 修复完成后，要分别合并到 <code>master</code> 和 <code>develop</code> 分支</li></ul><h3 id="注意点"><a href="#注意点" class="headerlink" title="注意点"></a>注意点</h3><ul><li>为了保证 <code>develop</code> 分支是最新的，develop 在合并到 release 时，先合并一下 master 分支，可以避免之前忘记 release 或者 hotfix 合并到 develop</li><li>设置 master 为保护分支，控制权限</li><li>上线要合并到 master 时，通过在 gitlab 上提 Merge Request 给对应的管理员来操作</li><li>分支命名以 <code>/</code> 划分可以在 GUI 工具中分成一个目录分类</li><li>具体的操作流程要根据团队来调整</li></ul><h2 id="提交规范"><a href="#提交规范" class="headerlink" title="提交规范"></a>提交规范</h2><h3 id="日志规范"><a href="#日志规范" class="headerlink" title="日志规范"></a>日志规范</h3><p>编写清晰良好的 commit message ，可以提高可读性，方便查看提交历史记录</p><p>业界广泛采用的一种规范是 <a href="https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines" target="_blank" rel="noopener">Angular Git Commit Guidelines</a></p><p>在实际使用中，我们可以根据团队的情况来使用，例如简化使用：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;type&gt;: &lt;subject&gt;</span><br></pre></td></tr></table></figure><p>type的类别说明:</p><ul><li>feat: 添加新特性</li><li>fix: 修复bug</li><li>docs: 仅仅修改了文档</li><li>style: 仅仅修改了空格、格式缩进、都好等等，不改变代码逻辑</li><li>refactor: 代码重构，没有加新功能或者修复bug</li><li>perf: 增加代码进行性能测试</li><li>test: 增加测试用例</li><li>chore: 改变构建流程、或者增加依赖库、工具等</li></ul><p>subject 主要写清楚这次更新或者修改的内容</p><h3 id="commit"><a href="#commit" class="headerlink" title="commit"></a>commit</h3><p>完成一个功能点或者完成一次完整的修改后，再提交 commit。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;最近在整理文档和一些团队规范，整理了一下团队中使用 Git 的一些规范。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;规范的制定，要根据不同团队和场
      
    
    </summary>
    
      <category term="Git" scheme="https://blog.hhking.cn/categories/Git/"/>
    
    
      <category term="Git" scheme="https://blog.hhking.cn/tags/Git/"/>
    
  </entry>
  
  <entry>
    <title>HTTP 和安全相关的头信息</title>
    <link href="https://blog.hhking.cn/2019/08/25/http-security-headers/"/>
    <id>https://blog.hhking.cn/2019/08/25/http-security-headers/</id>
    <published>2019-08-25T09:01:33.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>WEB 应用越来越复杂，前端所承担的也不再仅仅是切图、写界面的任务。作为一个前端工程师，掌握必要的 WEB 安全相关的知识，也是必要的。<br>这里收集整理了 HTTP 中和安全相关的头信息内容，了解这些头信息，在提升网站的安全性上，是有不少帮助的。</p><h2 id="安全相关的头信息"><a href="#安全相关的头信息" class="headerlink" title="安全相关的头信息"></a>安全相关的头信息</h2><h3 id="Content-Security-Policy"><a href="#Content-Security-Policy" class="headerlink" title="Content-Security-Policy"></a>Content-Security-Policy</h3><p>内容安全策略 CSP (Content-Security-Policy) 的主要目的是减少和报告 XSS 攻击。</p><p>实现的方式是通过白名单机制，也就是提供可信赖的外部资源来源的白名单，浏览器根据白名单来获取资源。</p><p>来看下 MDN 上提供的一个例子：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Content-Security-Policy: default-src &apos;self&apos;; img-src *; media-src media1.com media2.com; script-src userscripts.example.com; report-uri http://reportcollector.example.com/collector.cgi</span><br></pre></td></tr></table></figure><p><code>default-src &#39;self&#39;;</code>: 表示各种内容允许从文档所在的源获取(不包括其子域名)<br><code>img-src *;</code>: 图片可以从任何地方加载<br><code>media-src media1.com media2.com;</code>: 多媒体文件仅允许从 media1.com 和 media2.com 加载(不包括其子域名)<br><code>script-src userscripts.example.com</code>: 可运行脚本仅允许来自于userscripts.example.com<br><code>report-uri</code>: 设置这个选项，表示启用发送违规报告，也就是会把违规的信息 (JSON 格式) 发送到配置的 URI 地址</p><blockquote><p>参考:</p><p><a href="https://content-security-policy.com/" target="_blank" rel="noopener">CSP Reference</a><br><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP" target="_blank" rel="noopener">内容安全策略( CSP )</a></p></blockquote><h3 id="Strict-Transport-Security"><a href="#Strict-Transport-Security" class="headerlink" title="Strict-Transport-Security"></a>Strict-Transport-Security</h3><p>如果你的网站启动了 HTTPS，可以使用这个配置，告诉浏览器你的网站只能通过 HTTPS 来访问。</p><p>作用：例如我们在访问 HTTP 网站时，网站再自动跳转到 HTTPS，这个过程中，存在中间人攻击潜在威胁，因为在跳转到 HTTPS 之前，用户信息是未加密的。配置 STS 可以让浏览器自动替换 HTTP 为 HTTPS 请求。</p><p>示例：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Strict-Transport-Security: max-age=1000; includeSubDomains</span><br></pre></td></tr></table></figure><p><code>max-age</code>: 设置在浏览器收到这个请求后的 1000 秒的时间内凡是访问这个域名下的请求都使用HTTPS请求。</p><p><code>includeSubDomains</code> 该网站的所有子域名也启用该规则。</p><blockquote><p>参考：<a href="https://developer.mozilla.org/zh-CN/docs/Security/HTTP_Strict_Transport_Security" target="_blank" rel="noopener">HTTP Strict Transport Security</a></p></blockquote><h3 id="X-Content-Type-Options"><a href="#X-Content-Type-Options" class="headerlink" title="X-Content-Type-Options"></a>X-Content-Type-Options</h3><p>我们知道，浏览器会根据响应头的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type" target="_blank" rel="noopener"><code>Content-Type</code></a> 来辨别请求资源的类型，例如：<code>&quot;</code>text/css<code>&quot;</code> 表示 <code>style</code> 类型。但是如果不指定 MIME 类型或者指定错误，浏览器会进行 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#MIME_sniffing" target="_blank" rel="noopener">MIME 类型嗅探</a>，猜测资源的类型，然后进行解析。而这个行为可能会被恶意攻击。</p><p>配置方式如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">X-Content-Type-Options: nosniff</span><br></pre></td></tr></table></figure><p>作用：通过这个配置，可以让浏览器按服务端返回的指定类型进行内容解析。</p><blockquote><p>参考：<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Content-Type-Options" target="_blank" rel="noopener">X-Content-Type-Options</a></p></blockquote><h3 id="X-Frame-Options"><a href="#X-Frame-Options" class="headerlink" title="X-Frame-Options"></a>X-Frame-Options</h3><p>这个响应头是用来告诉浏览器，这个页面是否可以被 <code>&lt;frame&gt;</code>, <code>&lt;iframe&gt;</code>, <code>&lt;embed&gt;</code>, <code>&lt;object&gt;</code> 嵌入。</p><p>作用：网站可以避免被嵌入到别人的网站中，从而避免点击劫持 (clickjacking)。</p><p>配置示例：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">X-Frame-Options: deny</span><br></pre></td></tr></table></figure><blockquote><p>注意：这个功能，也可以通过 CSP 中配置 <code>frame-ancestors: none</code> 来实现，以后可能会慢慢淘汰非标准的 <code>X-Frame-Options</code></p><p>参考：<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options" target="_blank" rel="noopener">X-Frame-Options 响应头</a></p></blockquote><h3 id="X-XSS-Protection"><a href="#X-XSS-Protection" class="headerlink" title="X-XSS-Protection"></a>X-XSS-Protection</h3><p>顾名思义，这个是用来设置防御 XSS 攻击的。目前这个一般浏览器都是默认开启的。</p><p>配置示例：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">X-XSS-Protection: 1; mode=block</span><br></pre></td></tr></table></figure><p>作用：这个配置启用 XSS 过滤，如果检测到有 XSS 攻击，阻止页面加载。</p><blockquote><p>参考：<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-XSS-Protection" target="_blank" rel="noopener">X-XSS-Protection</a></p></blockquote><h3 id="Set-Cookie"><a href="#Set-Cookie" class="headerlink" title="Set-Cookie"></a>Set-Cookie</h3><p><code>Set-Cookie</code> 中的 <code>Secure</code> 和 <code>HttpOnly</code> 配置是用来保证 cookie 的安全的。</p><p><code>Secure</code>: 配置这个安全属性，让 cookie 只在 HTTPS 中加密传输。</p><p><code>HttpOnly</code>: 这个配置可以禁止通过 JavaScript 来访问 cookie，防止 XSS 攻击。</p><blockquote><p>参考：<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie" target="_blank" rel="noopener">Set-Cookie</a></p></blockquote><h3 id="Access-Control-Allow-Origin"><a href="#Access-Control-Allow-Origin" class="headerlink" title="Access-Control-Allow-Origin"></a>Access-Control-Allow-Origin</h3><p>这个响应头前端会比较熟悉，因为跨域请求里 (CORS)，就和这个有关系。</p><p>通过配置这个响应头，可以指定哪些域有权限访问该资源。</p><p>配置方法：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Access-Control-Allow-Origin: &lt;origin&gt;|*</span><br></pre></td></tr></table></figure><blockquote><p>参考：<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Origin" target="_blank" rel="noopener">Access-Control-Allow-Origin</a></p></blockquote><h3 id="Cache-Control-amp-amp-Expires"><a href="#Cache-Control-amp-amp-Expires" class="headerlink" title="Cache-Control &amp;&amp; Expires"></a>Cache-Control &amp;&amp; Expires</h3><p>这个其实是属于缓存配置，但是，我们需要注意的是，针对一些具有敏感数据，例如用户信息、交易页面，这些不应该配置缓存，以免因为页面缓存而导致敏感信息的泄露。</p><blockquote><p>具体配置可以看之前的文章：<a href="https://blog.hhking.cn/2018/08/10/browser-cache/">浏览器的缓存机制</a></p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这些 HTTP 安全配置，有助于提升网站的安全性，而从这些配置里，我们也能学到一些安全防御方面的思路：</p><ul><li>传输信息加密</li><li>XSS 过滤</li><li>白名单机制</li><li>同源策略</li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><blockquote><p><a href="https://nullsweep.com/http-security-headers-a-complete-guide/" target="_blank" rel="noopener">HTTP Security Headers - A Complete Guide</a><br><a href="https://imququ.com/post/web-security-and-response-header.html" target="_blank" rel="noopener">一些安全相关的HTTP响应头</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;WEB 应用越来越复杂，前端所承担的也不再仅仅是切图、写界面的任务。作为一个前端工程师，掌握必要的 WEB 安全相关的知识，也是必要的。&lt;b
      
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="HTTP" scheme="https://blog.hhking.cn/tags/HTTP/"/>
    
  </entry>
  
  <entry>
    <title>《你读我记》小程序</title>
    <link href="https://blog.hhking.cn/2019/08/12/reading-keeper/"/>
    <id>https://blog.hhking.cn/2019/08/12/reading-keeper/</id>
    <published>2019-08-12T15:12:44.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>上班通勤时间很长，在地铁上竟成了我看书最多的地方。看过很多书、小说，也忘了很多。</p><p>小说 App 和 iReader 都带有阅读记录的时长，但是我经常是各个地方、各个姿势、各个类型都会看，没法统一起来。于是乎，就想有个记录的地方，也试过不少有次功能的 App，各有特色，看起来也不复杂，何不自己做个呢？</p><h2 id="搞起"><a href="#搞起" class="headerlink" title="搞起"></a>搞起</h2><p>想了很久，忙忙碌碌，终究是懒，断断续续至今，终于做了个线上版的小程序 —— <strong>你读我记</strong></p><p>也算是自己体验了一下小程序开发，从前端到后端，到部署上线，到最后发正式版，磕磕碰碰，竟然过去将近两个月了。</p><p>小程序主要用的是：<a href="https://taro.aotu.io/" target="_blank" rel="noopener">Taro</a>、<a href="https://taro-ui.aotu.io/#/" target="_blank" rel="noopener">Taro UI</a>、<a href="https://github.com/wux-weapp/wux-weapp" target="_blank" rel="noopener">wux-weapp</a><br>后台用的是：<a href="https://eggjs.org/zh-cn/intro/" target="_blank" rel="noopener">egg</a>、MySQL、Nginx 代理</p><p>没有复杂的东西，只是去接触和尝试了一下相关的内容，最重要的是一个产品，从想法到落地实现，最终上线。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>目前主要自己使用，虽然还不少问题，但是不影响使用。有空慢慢完善，后续再出个总结。</p><p>目前实现的功能：</p><ul><li style="list-style:none"><input type="checkbox" checked>书籍搜索功能</li><li style="list-style:none"><input type="checkbox" checked>书架功能，分成 在读、想读、已读和全部</li><li style="list-style:none"><input type="checkbox" checked>阅读时长记录，包括暂停、取消、记录，也可以修改阅读时长</li><li style="list-style:none"><input type="checkbox" checked>阅读日历，在日历上显示阅读记录</li><li style="list-style:none"><input type="checkbox" checked>阅读统计，包括累计时长、阅读天数、阅读数量</li><li style="list-style:none"><input type="checkbox" checked>每本书的阅读记录，包括阅读开始和结束时间，还有阅读进度分析和预测</li></ul><p>TODO:</p><ul><li style="list-style:none"><input type="checkbox">阅读分享海报</li><li style="list-style:none"><input type="checkbox">待续</li></ul><p>下面是我最近使用的记录截图<br><img src="/images/reading-keeper-my.PNG" alt=""></p><h2 id="扫一扫"><a href="#扫一扫" class="headerlink" title="扫一扫"></a>扫一扫</h2><p>最后奉上小程序码</p><p><img src="/images/reading-keeper.png" alt=""></p><p>THE END！</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h2&gt;&lt;p&gt;上班通勤时间很长，在地铁上竟成了我看书最多的地方。看过很多书、小说，也忘了很多。&lt;/p&gt;&lt;p&gt;小说 App 和 iReader 都带有阅读记
      
    
    </summary>
    
      <category term="小程序" scheme="https://blog.hhking.cn/categories/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
    
      <category term="Node" scheme="https://blog.hhking.cn/tags/Node/"/>
    
      <category term="小程序" scheme="https://blog.hhking.cn/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
      <category term="egg" scheme="https://blog.hhking.cn/tags/egg/"/>
    
      <category term="Taro" scheme="https://blog.hhking.cn/tags/Taro/"/>
    
  </entry>
  
  <entry>
    <title>Redux 源码解读</title>
    <link href="https://blog.hhking.cn/2019/07/12/redux-interpretation/"/>
    <id>https://blog.hhking.cn/2019/07/12/redux-interpretation/</id>
    <published>2019-07-12T02:42:14.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本篇解读基于 Redux 版本 4.0.1。<br>完整的注释发在这个仓库 <a href="https://github.com/hhking/redux-interpretation" target="_blank" rel="noopener">redux-interpretation</a></p></blockquote><p>Redux 的源码很短，核心就是实现下面这些 api，也是我们使用的时候会遇到的。</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">store: &#123;</span><br><span class="line">  dispatch,</span><br><span class="line">  subscribe,</span><br><span class="line">  getState,</span><br><span class="line">  replaceReducer</span><br><span class="line">&#125;,</span><br><span class="line">bindActionCreators,</span><br><span class="line">combineReducers,</span><br><span class="line">applyMiddleware</span><br></pre></td></tr></table></figure><p>先来看看 Redux 的数据流向图，流程图也能看出一点这些 api 的作用。<br><img src="/images/redux-flow.png" alt=""></p><h2 id="createStore"><a href="#createStore" class="headerlink" title="createStore"></a>createStore</h2><p>createStore 是生成 store 的函数，返回 store 对象，<code>dispatch</code>, <code>subscribe</code>, <code>getState</code>, <code>replaceReducer</code> 都是在这里实现的</p><blockquote><p>还有个 observable，是提供给 观察/响应式 库的接口</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// createStore.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createStore</span>(<span class="params">reducer, preloadedState, enhancer</span>) </span>&#123;</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    dispatch,</span><br><span class="line">    subscribe,</span><br><span class="line">    getState,</span><br><span class="line">    replaceReducer,</span><br><span class="line">    [$$observable]: observable</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="dispatch"><a href="#dispatch" class="headerlink" title="dispatch"></a>dispatch</h2><p><code>dispatch</code> 本质就是调用 <code>reducer</code> 得到新的 state，然后再循环执行监听 state 变化的回调函数</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// createStore.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">dispatch</span>(<span class="params">action</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 开始执行 reducer</span></span><br><span class="line">    isDispatching = <span class="literal">true</span></span><br><span class="line">    <span class="comment">// reducer 的参数是当前的 state 和指定的 action，返回值作为新的 state, 所以要保证 reducer 一定要有 state 返回</span></span><br><span class="line">    currentState = currentReducer(currentState, action)</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="comment">// reducer 执行完成</span></span><br><span class="line">    isDispatching = <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 这里获取最新的回调函数数组, 然后循环逐个执行.</span></span><br><span class="line"><span class="comment">   * 这里让 currentListeners = nextListeners, 如果这时候出现新增或者取消订阅, 之前的 ensureCanMutateNextListeners 就起作用了,</span></span><br><span class="line"><span class="comment">   * 改动不会影响当前执行的数组, 下次执行 dispatch 才会拿到改过后的数组</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">const</span> listeners = (currentListeners = nextListeners)</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; listeners.length; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> listener = listeners[i]</span><br><span class="line">    listener()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> action</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="subscribe"><a href="#subscribe" class="headerlink" title="subscribe"></a>subscribe</h2><p><code>subscribe</code> 就是添加监听 state 变化的回调函数，保存在一个数组中，在 <code>dispatch</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><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// createStore.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">subscribe</span>(<span class="params">listener</span>) </span>&#123;</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="comment">// 标记订阅状态，取消订阅时避免重复取消订阅的逻辑执行，造成的性能损耗</span></span><br><span class="line">  <span class="keyword">let</span> isSubscribed = <span class="literal">true</span></span><br><span class="line">  <span class="comment">// 添加 listener 之前，确保不改动 currentListeners，而是 currentListeners 的复制出来的 nextListeners</span></span><br><span class="line">  ensureCanMutateNextListeners()</span><br><span class="line">  <span class="comment">// 添加回调函数 listener 到 nextListeners</span></span><br><span class="line">  nextListeners.push(listener)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 订阅的返回值是个函数，调用这个返回值来取消订阅（类似于 setTimeout 的返回值可以用来取消定时器）</span></span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">unsubscribe</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!isSubscribed) &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// reducer 执行时不能取消订阅</span></span><br><span class="line">    <span class="keyword">if</span> (isDispatching) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(</span><br><span class="line">        <span class="string">'You may not unsubscribe from a store listener while the reducer is executing. '</span> +</span><br><span class="line">          <span class="string">'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'</span></span><br><span class="line">      )</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 标记为未订阅</span></span><br><span class="line">    isSubscribed = <span class="literal">false</span></span><br><span class="line">    <span class="comment">// 这里会再次确认 nextListeners 和 currentListeners 时，浅复制一份新的 nextListeners 出来</span></span><br><span class="line">    ensureCanMutateNextListeners()</span><br><span class="line">    <span class="comment">// 找到需要取消订阅的 listener，通过 splice 从数组中删除，变化体现在 nextListeners 数组中</span></span><br><span class="line">    <span class="keyword">const</span> index = nextListeners.indexOf(listener)</span><br><span class="line">    nextListeners.splice(index, <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里注意到一个函数 <code>ensureCanMutateNextListeners</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="comment">// createStore.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ensureCanMutateNextListeners</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (nextListeners === currentListeners) &#123;</span><br><span class="line">      nextListeners = currentListeners.slice()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>考虑下面这种场景：<br><code>dispatch</code> 过程中，监听回调 listener 数组 <code>[ a, b, c ,d ]</code> 在循环执行，但是刚执行完 a，a 被取消监听了，这时候数组就会变成 <code>[ b, c ,d ]</code>，c 是数组的第二项了。原本要执行第二项的 b 就被跳过了，而去执行 c 去了。<br>这个函数的作用是为了保证在 <code>dispatch</code> 过程中，新增或者取消订阅不会影响到当前的 <code>dispatch</code>，避免类似这种场景下 bug 的产生。<br>浅复制一份 <code>currentListeners</code>，保证当前的 <code>dispatch</code> 的不变，新增或者取消的会在 <code>nextListeners</code> 中体现，也就是下次 dispatch 时。 (subscribe 的注释里也有说明)</p><h2 id="getState"><a href="#getState" class="headerlink" title="getState"></a>getState</h2><p>这个简单粗暴，就是返回当前的 state</p><h2 id="replaceReducer"><a href="#replaceReducer" class="headerlink" title="replaceReducer"></a>replaceReducer</h2><p>这个方法直接替换当前的 <code>reducer</code>，然后执行 <code>dispatch({ type: ActionTypes.REPLACE })</code> 这个内置的 <code>action</code>，根据新的 <code>reducer</code> 生成新的 <code>state</code>。</p><p>这个方法的使用场景：</p><ul><li>代码分割按需加载</li><li>热替换</li></ul><h3 id="bindActionCreators"><a href="#bindActionCreators" class="headerlink" title="bindActionCreators"></a>bindActionCreators</h3><p>这个方法就是提供一个简写的调用方式，给 ActionCreator 加个自动 <code>dispatch</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// bindActionCreators.js 部分代码</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 这个方法的做用，其实就是一个简写的调用方法，方便使用</span></span><br><span class="line"><span class="comment">// 结果就是返回一个函数: `dispatch(actionCreator(xxx))`</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bindActionCreator</span>(<span class="params">actionCreator, dispatch</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> dispatch(actionCreator.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>))</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="combineReducers"><a href="#combineReducers" class="headerlink" title="combineReducers"></a>combineReducers</h3><p><code>combineReducers</code> 这个函数的作用是，把一个包含各个 reducer 函数的对象，合并成一个 reducer 函数。<br>这部分代码也不复杂，除去一些错误检查之类的，就是对 reducer 就行处理合并，执行返回的函数，才是执行真正的所有的 reducer 逻辑</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//combineReducers</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">combineReducers</span>(<span class="params">reducers</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 获取 reducers 这个对象的建 keys 对象</span></span><br><span class="line">  <span class="keyword">const</span> reducerKeys = <span class="built_in">Object</span>.keys(reducers)</span><br><span class="line">  <span class="keyword">const</span> finalReducers = &#123;&#125;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; reducerKeys.length; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> key = reducerKeys[i]</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断 key 对应的 reducer 是不是 `undefined`</span></span><br><span class="line">    <span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">'production'</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> reducers[key] === <span class="string">'undefined'</span>) &#123;</span><br><span class="line">        warning(<span class="string">`No reducer provided for key "<span class="subst">$&#123;key&#125;</span>"`</span>)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 确保 reducer 是个函数，然后根据 key/value 存在新对象 finalReducers</span></span><br><span class="line"><span class="comment">     * 这个新对象，包含的是过滤 undefined 和 非函数 后的 reducer</span></span><br><span class="line"><span class="comment">     * 保存在另一个对象，可以再后续的修改中，不影响到原来的 reducers 这个对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> reducers[key] === <span class="string">'function'</span>) &#123;</span><br><span class="line">      finalReducers[key] = reducers[key]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> finalReducerKeys = <span class="built_in">Object</span>.keys(finalReducers)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// This is used to make sure we don't warn about the same</span></span><br><span class="line">  <span class="comment">// keys multiple times.</span></span><br><span class="line">  <span class="keyword">let</span> unexpectedKeyCache</span><br><span class="line">  <span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">'production'</span>) &#123;</span><br><span class="line">    unexpectedKeyCache = &#123;&#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> shapeAssertionError</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    assertReducerShape(finalReducers)</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    shapeAssertionError = e</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 返回一个合并后的 reducer 函数, state 默认值为空对象</span></span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">combination</span>(<span class="params">state = &#123;&#125;, action</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (shapeAssertionError) &#123;</span><br><span class="line">      <span class="keyword">throw</span> shapeAssertionError</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">'production'</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> warningMessage = getUnexpectedStateShapeWarningMessage(</span><br><span class="line">        state,</span><br><span class="line">        finalReducers,</span><br><span class="line">        action,</span><br><span class="line">        unexpectedKeyCache</span><br><span class="line">      )</span><br><span class="line">      <span class="keyword">if</span> (warningMessage) &#123;</span><br><span class="line">        warning(warningMessage)</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> hasChanged = <span class="literal">false</span></span><br><span class="line">    <span class="keyword">const</span> nextState = &#123;&#125;</span><br><span class="line">    <span class="comment">// 循环执行 reducer，这部分写的很清晰</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; finalReducerKeys.length; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> key = finalReducerKeys[i]</span><br><span class="line">      <span class="keyword">const</span> reducer = finalReducers[key]</span><br><span class="line">      <span class="keyword">const</span> previousStateForKey = state[key]</span><br><span class="line">      <span class="keyword">const</span> nextStateForKey = reducer(previousStateForKey, action)</span><br><span class="line">      <span class="comment">// 这里验证是否会没有 state 返回</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> nextStateForKey === <span class="string">'undefined'</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> errorMessage = getUndefinedStateErrorMessage(key, action)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(errorMessage)</span><br><span class="line">      &#125;</span><br><span class="line">      nextState[key] = nextStateForKey</span><br><span class="line">      hasChanged = hasChanged || nextStateForKey !== previousStateForKey</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// state 有改变返回下一个 state，否则返回原来的 state</span></span><br><span class="line">    <span class="keyword">return</span> hasChanged ? nextState : state</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="applyMiddleware"><a href="#applyMiddleware" class="headerlink" title="applyMiddleware"></a>applyMiddleware</h2><p><code>applyMiddleware</code> 是一个特殊的 <code>enhancer</code>，执行 <code>applyMiddleware</code> 和 <code>enhancer</code>，都要返回和 <code>createStore</code> 一样的 api。<br><code>applyMiddleware</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// applyMiddleware.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">applyMiddleware</span>(<span class="params">...middlewares</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="params">createStore</span> =&gt;</span> (...args) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> store = createStore(...args)</span><br><span class="line">    <span class="keyword">let</span> dispatch = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(</span><br><span class="line">        <span class="string">'Dispatching while constructing your middleware is not allowed. '</span> +</span><br><span class="line">          <span class="string">'Other middleware would not be applied to this dispatch.'</span></span><br><span class="line">      )</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> middlewareAPI = &#123;</span><br><span class="line">      getState: store.getState,</span><br><span class="line">      dispatch: <span class="function">(<span class="params">...args</span>) =&gt;</span> dispatch(...args)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// middlewareAPI 作为参数执行一遍所有的中间件</span></span><br><span class="line">    <span class="keyword">const</span> chain = middlewares.map(<span class="function"><span class="params">middleware</span> =&gt;</span> middleware(middlewareAPI))</span><br><span class="line">    <span class="comment">// 根据 compose 生产的其实是类似 mid1(mid2(mid3(store.dispatch)))</span></span><br><span class="line">    <span class="comment">// 所以在数组最后的 middleware 其实是第一个执行的</span></span><br><span class="line">    dispatch = compose(...chain)(store.dispatch)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      ...store,</span><br><span class="line">      dispatch</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>这里要结合实际的中间件来理解，下面是 logger 中间件的示例代码:</p><ul><li>可以看到中间件 logger 其实是一个函数，参数为 dispatch 和 getState，这个对应上面的 middlewareAPI</li><li>middleware(middlewareAPI) 执行后得到的还是个函数，参数是 next，看上面的 compose, 这个 next 其实就是 store.dispatch，返回的是新的 dispatch， 假如是 newDispatch</li><li>根据上面的 compose，你会发现，这个 newDispatch 是作为中间件链的下一个 middleware 的参数值（就是 next）这样一环扣一环，得到一个最终的 dispatch</li><li>在实际调用最终的 dispatch 时，你会发现，middleware 的执行顺序又变成是从左往右了（因为最终返回的 dispatch 是第一个中间件的函数）</li><li>执行过程中，遇到 next(action)，这时候其实就是右边一个 middleware 的返回的 dispatch，控制权就转到这个 middleware 了，</li><li>这样执行到最后，控制权返回，再执行 next 后面的代码</li><li>最终就是形成了中间件的洋葱模型，看下面的示意图</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// logger 中间件的示例代码</span></span><br><span class="line"><span class="keyword">const</span> logger = <span class="function">(<span class="params">&#123; getState, dispatch &#125;</span>) =&gt;</span> next =&gt; <span class="function"><span class="params">action</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'dispatching'</span>, action)</span><br><span class="line">  <span class="keyword">let</span> result = next(action)</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'next state'</span>, store.getState())</span><br><span class="line">  <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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="comment">// 洋葱模型示意图</span></span><br><span class="line">+-------------------------------------------------------------+</span><br><span class="line">|   mid21                                                     |</span><br><span class="line">|         +---------------------------------------------+     |</span><br><span class="line">|         | mid2    +-----------------------------+     |     |</span><br><span class="line">|         |         | mid2   +-------------+      |     |     |</span><br><span class="line">|         |         |        |             |      |     |     |</span><br><span class="line">-&gt; next() -&gt; next() -&gt;next() -&gt; dispatch() -&gt;    -&gt;    -&gt;    -&gt;</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">+-------------------------------------------------------------+</span><br></pre></td></tr></table></figure><p>从这个模型可以知道中间件的工作原理，就是在发送 dispatch 之前以及发送之后，可以做一些你想做的事。</p><p>比如这个 logger 中间件的执行顺序会是 打印 dispatching -&gt; 等待 next 执行返回 -&gt; 打印 next state</p><p>这里可以思考一下，为什么 logger 中间件要求放在最后面?</p><p>写的比较粗糙，详细的可以看这个仓库的注释 <a href="https://github.com/hhking/redux-interpretation" target="_blank" rel="noopener">redux-interpretation</a></p><p>THE END!</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;&lt;p&gt;本篇解读基于 Redux 版本 4.0.1。&lt;br&gt;完整的注释发在这个仓库 &lt;a href=&quot;https://github.com/hhking/redux-interpretation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;
      
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="Redux" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/Redux/"/>
    
    
      <category term="Redux" scheme="https://blog.hhking.cn/tags/Redux/"/>
    
  </entry>
  
  <entry>
    <title>Babel 7 升级实践</title>
    <link href="https://blog.hhking.cn/2019/04/02/babel-v7-update/"/>
    <id>https://blog.hhking.cn/2019/04/02/babel-v7-update/</id>
    <published>2019-04-02T04:14:49.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>最近在看项目的升级和优化，项目用的是 Babel 6，踩了一下升级到 Babel 7 的坑。</p><a id="more"></a><h2 id="babel-preset-env"><a href="#babel-preset-env" class="headerlink" title="@babel/preset-env"></a>@babel/preset-env</h2><p><a href="https://babeljs.io/docs/en/babel-preset-env" target="_blank" rel="noopener">@babel/preset-env</a> 根据指定的执行环境提供语法装换，也提供配置 polyfill。</p><blockquote><p><code>Babel 7</code> 已经弃用年份 <code>preset</code>: <code>babel-preset-es2015, babel-preset-es2016, babel-preset-es2017, babel-preset-latest</code>. 直接使用一个 <code>env</code> 搞定。</p></blockquote><p>所以我们需要指定执行环境 Browserslist， Browserslist 的配置有几种方式，并按下面的优先级使用：</p><ol><li><code>@babel/preset-env</code> 里的 <code>targets</code></li><li><code>package.json</code> 里的 <code>browserslist</code> 字段</li><li><code>.browserslistrc</code> 配置文件</li></ol><blockquote><p><a href="https://github.com/browserslist/browserslist" target="_blank" rel="noopener">browserslist</a> 是用来给不同的前端工具（Autoprefixer, babel-preset-env）共享 <code>target browsers</code> 和 <code>Node.js versions</code> 配置的. 一般推荐将配置写在 <code>package.json</code> 里的 <code>browserslist</code> 字段。</p></blockquote><p>例如我们配置 <code>.babelrc</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><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">// .babelrc</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">"presets"</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">"@babel/preset-env"</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">"targets"</span>: &#123;</span><br><span class="line">          <span class="string">"ie"</span>: <span class="number">10</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>源文件：<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></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">const</span> f = <span class="function"><span class="params">()</span> =&gt;</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>();</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;&#125;</span><br></pre></td></tr></table></figure><p></p><p>编译后是：<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><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">"use strict"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">_classCallCheck</span>(<span class="params">instance, Constructor</span>) </span>&#123; <span class="keyword">if</span> (!(instance <span class="keyword">instanceof</span> Constructor)) &#123; <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"Cannot call a class as a function"</span>); &#125; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> Test = <span class="function"><span class="keyword">function</span> <span class="title">Test</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  _classCallCheck(<span class="keyword">this</span>, Test);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p></p><p>Babel 转换了浏览器不支持的箭头函数和 <code>Class</code>，但是 <code>Promise</code> 并没有变化。这是因为 Babel 只转换不兼容的新语法，而对新的 API，如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise、Object.assign() 等，是不会转换的。这时候就需要 polyfill 了。</p><p>polyfill 的使用在 Babel 7 有一些不同：</p><ul><li><code>useBuiltIns</code> 提供 <code>false</code>, <code>entry</code>, <code>usage</code> 三种方式</li><li><code>@babel/runtime</code>, <code>@babel/plugin-transform-runtime</code> 把 <code>helpers</code> 和 polyfill 功能拆分了。默认只提供 <code>helpers</code>。</li></ul><h2 id="useBuiltIns"><a href="#useBuiltIns" class="headerlink" title="useBuiltIns"></a>useBuiltIns</h2><h3 id="false"><a href="#false" class="headerlink" title="false"></a>false</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"useBuiltIns"</span>: <span class="literal">false</span>,</span><br></pre></td></tr></table></figure><p>此时不对 <code>polyfill</code> 做操作。如果引入 <code>@babel/polyfill</code>，则无视配置的浏览器兼容，引入所有的 <code>polyfill</code>。</p><h3 id="entry"><a href="#entry" class="headerlink" title="entry"></a>entry</h3><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="string">"useBuiltIns"</span>: <span class="string">"entry"</span>,</span><br><span class="line"><span class="string">"corejs"</span>: <span class="number">2</span>,</span><br></pre></td></tr></table></figure><p>根据配置的浏览器兼容，引入浏览器不兼容的 <code>polyfill</code>。需要在入口文件手动添加 <code>import &#39;@babel/polyfill&#39;</code>，会自动根据 <code>browserslist</code> 替换成浏览器不兼容的所有 <code>polyfill</code>。</p><p>这里需要指定 <code>core-js</code> 的版本, 如果 <code>&quot;corejs&quot;: 3</code>, 则 <code>import &#39;@babel/polyfill&#39;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'core-js/stable'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'regenerator-runtime/runtime'</span>;</span><br></pre></td></tr></table></figure><p></p><p>编译结果：<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><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="meta">"use strict"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"core-js/modules/es6.array.copy-within"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 此处省略一大堆的 polyfill</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"core-js/modules/web.immediate"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"core-js/modules/web.dom.iterable"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"regenerator-runtime/runtime"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">_classCallCheck</span>(<span class="params">instance, Constructor</span>) </span>&#123; <span class="keyword">if</span> (!(instance <span class="keyword">instanceof</span> Constructor)) &#123; <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"Cannot call a class as a function"</span>); &#125; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> Test = <span class="function"><span class="keyword">function</span> <span class="title">Test</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  _classCallCheck(<span class="keyword">this</span>, Test);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p></p><h3 id="usage"><a href="#usage" class="headerlink" title="usage"></a>usage</h3><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="string">"useBuiltIns"</span>: <span class="string">"usage"</span>,</span><br><span class="line"><span class="string">"corejs"</span>: <span class="number">2</span>,</span><br></pre></td></tr></table></figure><p><code>usage</code> 会根据配置的浏览器兼容，以及你代码中用到的 API 来进行 <code>polyfill</code>，实现了按需添加。</p><p>编译结果：<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><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="meta">"use strict"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"core-js/modules/es6.promise"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"core-js/modules/es6.object.to-string"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">_classCallCheck</span>(<span class="params">instance, Constructor</span>) </span>&#123; <span class="keyword">if</span> (!(instance <span class="keyword">instanceof</span> Constructor)) &#123; <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"Cannot call a class as a function"</span>); &#125; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> Test = <span class="function"><span class="keyword">function</span> <span class="title">Test</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  _classCallCheck(<span class="keyword">this</span>, Test);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p></p><p>看上面的编译结果，会发现还有个问题，<code>_classCallCheck</code> 辅助函数是直接内嵌的，如果多个地方使用 <code>Class</code>，那每个地方都会添加这个辅助函数，大量重复。<br>这时候就需要 <code>@babel/plugin-transform-runtime</code> 了。</p><h2 id="babel-plugin-transform-runtime"><a href="#babel-plugin-transform-runtime" class="headerlink" title="@babel/plugin-transform-runtime"></a>@babel/plugin-transform-runtime</h2><p><a href="https://babeljs.io/docs/en/babel-plugin-transform-runtime" target="_blank" rel="noopener">@babel/plugin-transform-runtime</a> 这个插件是用来复用辅助函数的。</p><p>配置 <code>.babelrc</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><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">&#123;</span><br><span class="line">  <span class="string">"presets"</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">"@babel/preset-env"</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">"useBuiltIns"</span>: <span class="string">"usage"</span>,</span><br><span class="line">        <span class="string">"corejs"</span>: <span class="number">2</span>,</span><br><span class="line">        <span class="string">"targets"</span>: &#123;</span><br><span class="line">          <span class="string">"ie"</span>: <span class="number">10</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  ],</span><br><span class="line">  <span class="string">"plugins"</span>: [</span><br><span class="line">    [<span class="string">"@babel/plugin-transform-runtime"</span>]</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>编译结果：<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><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="meta">"use strict"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> _interopRequireDefault = <span class="built_in">require</span>(<span class="string">"@babel/runtime/helpers/interopRequireDefault"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> _classCallCheck2 = _interopRequireDefault(<span class="built_in">require</span>(<span class="string">"@babel/runtime/helpers/classCallCheck"</span>));</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"core-js/modules/es6.promise"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">"core-js/modules/es6.object.to-string"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> Test = <span class="function"><span class="keyword">function</span> <span class="title">Test</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  (<span class="number">0</span>, _classCallCheck2.default)(<span class="keyword">this</span>, Test);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p></p><p>可以发现，helpers 是通过 <code>require</code> 引入的，这样就不会存在代码重复的问题了。</p><h2 id="拾遗"><a href="#拾遗" class="headerlink" title="拾遗"></a>拾遗</h2><p><code>Babel 7</code> 废弃了 <code>stage-x</code>，如果需要用到一些特性，需要自己安装对应的 plugin，然后再配置中配置 plugin。</p><p><code>stage-x</code> 原本是对应 ECMAScript 提案的不同阶段，每个阶段有不同的特性，但是提案一直在变，这些特性可能更进一步，也可能废弃。<code>state-x</code> 是否应该保持和不断更新的提案一致？怎么处理都无法适应变化，所以直接使用 <code>stage-x</code> 对后期的维护造成困惑和风险。</p><p><code>Babel 7</code> 还有个配置文件查找的问题，升级后可能会出现 <code>.babelrc</code> 配置无效的情况，需要根据目录结构和规则调整。简单的说：</p><ul><li>Babel 7 增加了 <code>root</code> 目录的概念，默认是 cwd 目录</li><li>Babel 分成项目级配置文件(如：<code>.babel.config.js</code>)和文件级配置文件(如：<code>.babelrc</code>)</li><li>项目级目录是默认是在 <code>root</code> 目录查找，可以配置 <code>root</code> 或者 <code>rootMode</code> 来更改查找方式；也可以配置 <code>configFile</code> 来指定文件或者关闭项目级配置</li><li>文件级配置文件 <code>.babelrc</code> 会根据 <code>babelrcRoots</code> 配置（默认值为 <code>root</code>）的目录查找，且只作用于当前编译目录的 <code>package.json</code></li></ul><blockquote><p>更详细的可以直接看文档的解释说明 <a href="https://babeljs.io/docs/en/config-files#project-wide-configuration" target="_blank" rel="noopener">Config Files</a></p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Babel 7 的配置方案：</p><ul><li>@babel/preset-env + targets + useBuiltins: usage</li><li>@babel/plugin-transform-runtime</li><li>引入必要的 plugin</li></ul><p>这样就实现了按需引入 <code>polyfill</code>，看起来也是相当完美。</p><p>但是想想还是存在问题的，比如：Babel 编译通常会排除 node_modules，所以 <code>&quot;useBuiltIns&quot;: &quot;usage&quot;</code> 存在风险，可能无法为依赖包添加必要的 <code>polyfill</code>。云谦在博客 <a href="https://github.com/sorrycc/blog/issues/80" target="_blank" rel="noopener">Polyfill 方案的过去、现在和未来</a> 也提到一些问题，还有一些想法和展望，感觉写得挺好的。</p><blockquote><p>关于 Babel 编译通常会排除 node_modules 的做法，通用的约定是 node_modules 下的包在发布时打包成 es5。但是也难保证全部都遵守约定，这就存在一定的风险。但是如果 Babel 处理 node_modules，编译速度慢不说，Babel 6 编译已编译过的代码也是存在问题的，据说这个问题在 Babel 7 解决了，maybe! 告辞！👋</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h2&gt;&lt;p&gt;最近在看项目的升级和优化，项目用的是 Babel 6，踩了一下升级到 Babel 7 的坑。&lt;/p&gt;
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="Babel" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/Babel/"/>
    
    
      <category term="Babel" scheme="https://blog.hhking.cn/tags/Babel/"/>
    
  </entry>
  
  <entry>
    <title>React Hooks 阅读笔记 —— Hooks 简介</title>
    <link href="https://blog.hhking.cn/2019/03/16/hooks-intro-note/"/>
    <id>https://blog.hhking.cn/2019/03/16/hooks-intro-note/</id>
    <published>2019-03-16T07:03:51.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>官方文档关于 Hooks 的阅读笔记</p></blockquote><h2 id="关于-Hooks"><a href="#关于-Hooks" class="headerlink" title="关于 Hooks"></a>关于 Hooks</h2><ul><li><p>这是个可选的功能。如果你不想用，你可以选择忽略它。</p></li><li><p>100% 向后兼容。Hooks 没有破坏性的变更。</p></li><li><p>现在已经可以使用了。Hooks 在 React V16.8.0 已经发布。</p></li></ul><a id="more"></a><h2 id="Hooks-要解决的问题"><a href="#Hooks-要解决的问题" class="headerlink" title="Hooks 要解决的问题"></a>Hooks 要解决的问题</h2><h3 id="组件之间的状态逻辑复用"><a href="#组件之间的状态逻辑复用" class="headerlink" title="组件之间的状态逻辑复用"></a>组件之间的状态逻辑复用</h3><p><code>render props</code> 和 <code>higer-order components</code> 模式，也是为了解决这个问题而出现的。但是，这两种模式需要重构代码来实现，并且在 chrome 调试的时候，你会发现 <code>React DevTools</code> 中组件会多了很多层嵌套（<code>wrapper hell</code>）。</p><p>使用 Hooks，可以让你不改变组件层级就可以复用状态逻辑。这样使用起来更加自然，也更容易理解。</p><h3 id="复杂组件变得越来越难理解"><a href="#复杂组件变得越来越难理解" class="headerlink" title="复杂组件变得越来越难理解"></a>复杂组件变得越来越难理解</h3><p>随着需求的增加，组件越来越复杂，而且很多的逻辑处理（例如数据获取、事件绑定和解绑）依赖于生命周期函数，但是这些逻辑本身互相无关，这也就会出现一个生命周期的方法里混杂着互不相关的处理逻辑，导致组件越来越难以理解，从而导致出错的概率增加。</p><p>Hooks 可以实现组件更小粒度的函数拆分，互相无关的逻辑也可以区分开来，而不用再依赖生命周期方法来进行拆分。</p><h3 id="令人困惑的-Class"><a href="#令人困惑的-Class" class="headerlink" title="令人困惑的 Class"></a>令人困惑的 Class</h3><p>使用 <code>Class</code> ，是使用和学习 <code>React</code> 的一大障碍。要求使用者理解 <code>class</code> 的用法（例如要理解 <code>this</code> ，处理事件绑定，使用一些提案中的语法），而且需要学会分辨，什么时候该用 <code>function component</code>，什么时候该用 <code>class component</code>。</p><p><code>class component</code> 也可能会导致 <a href="https://prepack.io/" target="_blank" rel="noopener">Prepack</a> 一些优化失效。</p><p>还有就是 <code>class</code> 对一些开发工具的影响，例如： don’t minify very well(这个应该是 es6 的通病)，同时也会导致 hot reloading 出问题。</p><p>Hooks 可以让你不使用 <code>class</code> ，也可以用上 React 的一些功能。而 React 本身也是更倾向于 functions，使用 Hooks 可以更好的拥抱 functions。</p><blockquote><p><a href="https://reactjs.org/docs/hooks-intro.html" target="_blank" rel="noopener">Introducing Hooks</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;官方文档关于 Hooks 的阅读笔记&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;关于-Hooks&quot;&gt;&lt;a href=&quot;#关于-Hooks&quot; class=&quot;headerlink&quot; title=&quot;关于 Hooks&quot;&gt;&lt;/a&gt;关于 Hooks&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;这是个可选的功能。如果你不想用，你可以选择忽略它。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;100% 向后兼容。Hooks 没有破坏性的变更。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;现在已经可以使用了。Hooks 在 React V16.8.0 已经发布。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="React" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/React/"/>
    
    
      <category term="React" scheme="https://blog.hhking.cn/tags/React/"/>
    
      <category term="React Hooks" scheme="https://blog.hhking.cn/tags/React-Hooks/"/>
    
  </entry>
  
  <entry>
    <title>webpack4 升级记</title>
    <link href="https://blog.hhking.cn/2019/03/10/webpack-migrate2v4-from-v3/"/>
    <id>https://blog.hhking.cn/2019/03/10/webpack-migrate2v4-from-v3/</id>
    <published>2019-03-10T13:38:03.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>号称 “<strong>零配置</strong>”、“<strong>最高可提升98%的速度</strong>” 的 webpack4 已经出来一段时间了，而且 webpack5 也已经在路上了。再不体验一下 webpack4 就老了！目前项目使用的还是 webpack3，打包速度确实是锻炼人的耐心，这次趁着有点时间，决心优化一下项目，尝试从 webpack 升级开始。期间断断续续也遇到不少问题，在这里对大致的过程做个记录。</p><blockquote><p>犹记当年，也是我把 webpack 从 v1 升级到 v3 的</p></blockquote><a id="more"></a><h2 id="安装-webpack-以及相关配置"><a href="#安装-webpack-以及相关配置" class="headerlink" title="安装 webpack 以及相关配置"></a>安装 webpack 以及相关配置</h2><ol><li>先删除之前的 webpack、webpack-dev-server</li></ol><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">npm uninstall webpack webpack-dev-server --save-dev</span><br></pre></td></tr></table></figure><ol start="2"><li>安装最新版本的 webpack、webpack-cli (webpack4 把脚手架 webpack-cli 从 webpack 中抽离出来的，所以必须安装 webpack-cli)、webpack-dev-server</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install webpack webpack-dev-server webpack-cli --save-dev</span><br></pre></td></tr></table></figure><ol start="3"><li>webpack4 已不支持 <a href="https://github.com/webpack-contrib/extract-text-webpack-plugin" target="_blank" rel="noopener">extract-text-webpack-plugin</a>，改用 <a href="https://github.com/webpack-contrib/mini-css-extract-plugin" target="_blank" rel="noopener">mini-css-extract-plugin</a></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">npm uninstall --save-dev extract-text-webpack-plugin</span><br><span class="line">npm install --save-dev mini-css-extract-plugin</span><br></pre></td></tr></table></figure><ol start="4"><li>安装新版的 <a href="https://github.com/jantimon/html-webpack-plugin" target="_blank" rel="noopener">html-webpack-plugin</a></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">npm uninstall --save-dev html-webpack-plugin</span><br><span class="line">npm i --save-dev html-webpack-plugin</span><br></pre></td></tr></table></figure><ol start="5"><li>还有其他的一些相关的包，在调试的过程中，排查出对应升级</li></ol><h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><p>按 webpack4 的要求升级并配置好之后，就开始尝试在项目跑开发环境了。这个过程一波三折，升级花费的时间主要都在这里了。</p><h3 id="路径问题"><a href="#路径问题" class="headerlink" title="路径问题"></a>路径问题</h3><p>首先遇到的是巨坑，是 scss 里的路径问题。</p><p>路径处理使用 <code>resolve-url-loader</code> 来把路径处理成绝对路径，这个 <code>loader</code> 可以解决 sass 中，引用不同目录的 scss 文件，从而导致原来的相对 <code>url</code> 错误的问题。</p><p>升级这个 <code>loader</code> 后，scss 中图片字体路径一直 <code>can&#39;t resolve</code>，一开始以为是升级后配置不对，在这里卡了好久，一直看配置，改配置，查相关的 <code>loader</code>，无果！</p><p>最后决定还是从源码入手，使用 <code>vscode</code> 的调试功能，调试一下这个 <code>loader</code> 的处理情况。果然发现问题， <code>resolve-url-loader</code> v2 和 v3 的路径处理的方式改了！v2 会循环查找每一级的目录，而 v3 只会根据提供的路径生成对应的绝对路径来查找。</p><p>发现错的不是升级后的配置，而是以前代码里很多路径本身就写错了，比如</p><figure class="highlight scss"><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="comment">// 然而事实上，当前目录下根本没有 img 目录。应该是 ../img/test.png</span></span><br><span class="line"><span class="attribute">background-image</span>: url(./img/test.png);</span><br></pre></td></tr></table></figure><p>但是因为 <code>resolve-url-loader</code> 循环每一级查找的原因，还是能找到对应的图片。所以原来虽然写的自由奔放，但是也没有报错。（有些是因为 <code>copy</code> 其他地方的代码，但是没有 <code>copy</code> 对应的资源文件导致的）</p><p>如此巨坑，所以又陷入了修改大批错误路径的工作中。。。</p><h3 id="性能问题"><a href="#性能问题" class="headerlink" title="性能问题"></a>性能问题</h3><p>解决路径问题之后，势如破竹，大部分问题只要找到相关处理的 <code>loader</code> 或者 <code>plugin</code> 通过调试，都能找到原因。</p><p>webpack4 跑起来后，整体的速度已经是提升一大截了，单独打包主流程的时间从 <code>155s</code> 降到了 <code>70s</code> 左右，提升近 <strong>55%</strong>。</p><p>但是接着发现了一个神奇的问题，开发环境和打包速度竟然差不多，甚至更慢！！！</p><p>首先想到的是 <code>source-map</code> 的配置，严重拖慢速度。</p><p>但是修改不同的 <code>devtool</code>，发现变化并不大。问题不在这。</p><p>然后想到 <code>icon-font</code>。</p><p>于是看满屏密密麻麻的打包信息，发现字体文件不断重复的引入。发现是 sass 公共 <code>util</code> 模块里引用了字体的模块，然后之前一次修改，增加了每个 <code>scss</code> 自动引入 <code>util</code> 的 <code>loader</code> 。</p><p>果然，改正这个问题，只引入一次字体后，开发环境速度直接快了一倍。</p><h3 id="sass-公共模块的问题"><a href="#sass-公共模块的问题" class="headerlink" title="sass 公共模块的问题"></a>sass 公共模块的问题</h3><p>上面的问题，其实和这个问题相关：就是 sass 公共模块的问题。</p><p>其实原因在于，<code>sass-loader</code> 对 <code>@import</code> 是不会去重的，重复 <code>import</code> 的 <code>scss</code> 会重复出现。<a href="https://github.com/webpack-contrib/sass-loader" target="_blank" rel="noopener">sass-loader</a>` issue <a href="https://github.com/webpack-contrib/sass-loader/issues/145" target="_blank" rel="noopener">Duplicate Imports </a>中有讨论这个问题，但是并没有很好的解决方案。</p><p>要解决这个问题，从几个方面入手：</p><ol><li>规范 sass 公共模块，需要在每个文件引入的公共变量、function 之类的，不要带有会生成 css 的代码。（可以借助 <a href="https://github.com/shakacode/sass-resources-loader" target="_blank" rel="noopener">sass-resources-loader</a> 自动每个文件引入需要的公共模块）。</li><li>对于 <code>global.scss</code>, <code>reset.scss</code>, <code>common.scss</code> 这种生成 <code>css</code> 的公共模块，在 <code>js</code> 中 <code>import/require</code>。</li><li>使用 <a href="https://github.com/NMFR/optimize-css-assets-webpack-plugin" target="_blank" rel="noopener">optimize-css-assets-webpack-plugin</a> 可以去掉重复的 <code>css</code>。</li></ol><h2 id="对比"><a href="#对比" class="headerlink" title="对比"></a>对比</h2><h3 id="某单个项目升级前后对比"><a href="#某单个项目升级前后对比" class="headerlink" title="某单个项目升级前后对比"></a>某单个项目升级前后对比</h3><table><thead><tr><th>time</th><th>development</th><th>production</th></tr></thead><tbody><tr><td>webpack3</td><td>82.676s</td><td>183.235s</td></tr><tr><td>webpack4</td><td>33.6s(提升59.3%)</td><td>66.429s(提升63.7%)</td></tr></tbody></table><h3 id="全部项目打包前后对比"><a href="#全部项目打包前后对比" class="headerlink" title="全部项目打包前后对比"></a>全部项目打包前后对比</h3><table><thead><tr><th>time</th><th>production</th></tr></thead><tbody><tr><td>webpack3</td><td>1070s</td></tr><tr><td>webpack4</td><td>343s(提升67.9%)</td></tr></tbody></table><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>这次升级虽然费了不少功夫，踩了不少坑，但也收获不少经验和一些新的思路。同时，一些问题，也暴露出团队项目上的一些问题。</p><p>升级 webpack4 后，速度大幅度提升，整个团队的开发效率可以说将近 <strong>提升 70% * 团队</strong> 吧。但是还可以继续深入优化，还有不少的提升空间。可以使用 <a href="https://github.com/stephencookdev/speed-measure-webpack-plugin" target="_blank" rel="noopener">speed-measure-webpack-plugin</a> 来检测webpack打包过程中各个部分所花费的时间，再根据分析来做优化。还可以考虑 <code>babel</code> 升级到 v7 和开发环境的 <code>node</code> 层升级优化。</p><p>告辞！</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;号称 “&lt;strong&gt;零配置&lt;/strong&gt;”、“&lt;strong&gt;最高可提升98%的速度&lt;/strong&gt;” 的 webpack4 已经出来一段时间了，而且 webpack5 也已经在路上了。再不体验一下 webpack4 就老了！目前项目使用的还是 webpack3，打包速度确实是锻炼人的耐心，这次趁着有点时间，决心优化一下项目，尝试从 webpack 升级开始。期间断断续续也遇到不少问题，在这里对大致的过程做个记录。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;犹记当年，也是我把 webpack 从 v1 升级到 v3 的&lt;/p&gt;&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="webpack" scheme="https://blog.hhking.cn/tags/webpack/"/>
    
  </entry>
  
  <entry>
    <title>[译]JavaScript 如何复制对象</title>
    <link href="https://blog.hhking.cn/2018/12/11/copying-objects-in-javascript/"/>
    <id>https://blog.hhking.cn/2018/12/11/copying-objects-in-javascript/</id>
    <published>2018-12-11T11:14:13.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文: <a href="https://smalldata.tech/blog/2018/11/01/copying-objects-in-javascript" target="_blank" rel="noopener">COPYING OBJECTS IN JAVASCRIPT</a><br>作者: Victor Parmar</p></blockquote><p>这篇文章，我们将介绍在 JavaScript 中复制对象的各种方法。其中包括了浅复制和深复制。</p><a id="more"></a><p>开始之前，有必要说一些基础概念：JavaScript 中的对象，是对内存中存储位置的引用。这些引用是可变的，即：引用可以被重新赋值。因此，简单的复制引用，结果只会是两个引用同时指向内存中的同一个位置：</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> foo = &#123;</span><br><span class="line">    a : <span class="string">"abc"</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">console</span>.log(foo.a); <span class="comment">// abc</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> bar = foo;</span><br><span class="line"><span class="built_in">console</span>.log(bar.a); <span class="comment">// abc</span></span><br><span class="line"></span><br><span class="line">foo.a = <span class="string">"yo foo"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(foo.a); <span class="comment">// yo foo</span></span><br><span class="line"><span class="built_in">console</span>.log(bar.a); <span class="comment">// yo foo</span></span><br><span class="line"></span><br><span class="line">bar.a = <span class="string">"whatup bar?"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(foo.a); <span class="comment">// whatup bar?</span></span><br><span class="line"><span class="built_in">console</span>.log(bar.a); <span class="comment">// whatup bar?</span></span><br></pre></td></tr></table></figure><p>上面的例子可以看到，<code>foo</code> 和 <code>bar</code> 任意一个对象发生变化，都会反映到另一个对象上。所以，根据你的使用场景，在 JavaScript 中复制对象需要小心。</p><h2 id="SHALLOW-COPY-浅复制"><a href="#SHALLOW-COPY-浅复制" class="headerlink" title="SHALLOW COPY 浅复制"></a>SHALLOW COPY 浅复制</h2><p>如果你的对象的属性都是值类型，你可以使用展开语法或者 <code>Object.assign(...)</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">foo</span>: <span class="string">"foo"</span>, <span class="attr">bar</span>: <span class="string">"bar"</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> copy = &#123; ...obj &#125;; <span class="comment">// Object &#123; foo: "foo", bar: "bar" &#125;</span></span><br></pre></td></tr></table></figure><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> obj = &#123; <span class="attr">foo</span>: <span class="string">"foo"</span>, <span class="attr">bar</span>: <span class="string">"bar"</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> copy = <span class="built_in">Object</span>.assign(&#123;&#125;, obj); <span class="comment">// Object &#123; foo: "foo", bar: "bar" &#125;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj1 = &#123; <span class="attr">foo</span>: <span class="string">"foo"</span> &#125;;</span><br><span class="line"><span class="keyword">var</span> obj2 = &#123; <span class="attr">bar</span>: <span class="string">"bar"</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> copySpread = &#123; ...obj1, ...obj2 &#125;; <span class="comment">// Object &#123; foo: "foo", bar: "bar" &#125;</span></span><br><span class="line"><span class="keyword">var</span> copyAssign = <span class="built_in">Object</span>.assign(&#123;&#125;, obj1, obj2); <span class="comment">// Object &#123; foo: "foo", bar: "bar" &#125;</span></span><br></pre></td></tr></table></figure><p>上述方法存在的问题是，当对象的属性本身是对象时，这个属性只会复制引用，也就是说，这和第一个例子中的 <code>var bar = foo</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = &#123; <span class="attr">a</span>: <span class="number">0</span> , <span class="attr">b</span>: &#123; <span class="attr">c</span>: <span class="number">0</span> &#125; &#125;;</span><br><span class="line"><span class="keyword">var</span> copy = &#123; ...foo &#125;;</span><br><span class="line"></span><br><span class="line">copy.a = <span class="number">1</span>;</span><br><span class="line">copy.b.c = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.dir(foo); <span class="comment">// &#123; a: 0, b: &#123; c: 2 &#125; &#125;</span></span><br><span class="line"><span class="built_in">console</span>.dir(copy); <span class="comment">// &#123; a: 1, b: &#123; c: 2 &#125; &#125;</span></span><br></pre></td></tr></table></figure><h2 id="DEEP-COPY-深复制（带警告）"><a href="#DEEP-COPY-深复制（带警告）" class="headerlink" title="DEEP COPY 深复制（带警告）"></a>DEEP COPY 深复制（带警告）</h2><p>深复制对象，一个可能可行的方法是，把对象序列化成字符串，然后再反序列化回来：</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> obj = &#123; <span class="attr">a</span>: <span class="number">0</span>, <span class="attr">b</span>: &#123; <span class="attr">c</span>: <span class="number">0</span> &#125; &#125;;</span><br><span class="line"><span class="keyword">var</span> copy = <span class="built_in">JSON</span>.parse(<span class="built_in">JSON</span>.stringify(obj));</span><br></pre></td></tr></table></figure><p>可惜的是，这个方法只适用于：源对象包含的是可序列化的类型值，并且没有循环引用。一个不能序列化的类型值例子是 <code>Date</code> 对象 - 即使它打印成 ISO 格式字符串，<code>JSON.parse</code> 只会把它解释成字符串而不是 <code>Date</code> 对象。</p><h2 id="DEEP-COPY-深复制（带少量的警告）"><a href="#DEEP-COPY-深复制（带少量的警告）" class="headerlink" title="DEEP COPY 深复制（带少量的警告）"></a>DEEP COPY 深复制（带少量的警告）</h2><p>对更复杂的情况，可以使用新的 HTML5 克隆算法 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm" target="_blank" rel="noopener">“structured clone”</a>。可惜的是，在写本文的时候，这个方法还是限制于针对某些内置类型，但是它比 <code>JSON.parse</code> 支持更多的类型：Date, RegExp, Map, Set, Blob, FileList, ImageData, 稀疏数组和类数组。它还保留了克隆数据的引用，从而支持循环和递归结构，而上面提到的序列化方法不支持这点。</p><p>目前，没有直接调用结构化克隆算法的方法，但是一些较新的浏览器特性有使用这个算法。因此，通过一些变通的方法可以用来实现对象的深复制。</p><p><code>通过 MessageChannels</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><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="class"><span class="keyword">class</span> <span class="title">StructuredCloner</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>() &#123;</span><br><span class="line">    <span class="keyword">this</span>.pendingClones_ = <span class="keyword">new</span> <span class="built_in">Map</span>();</span><br><span class="line">    <span class="keyword">this</span>.nextKey_ = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> channel = <span class="keyword">new</span> MessageChannel();</span><br><span class="line">    <span class="keyword">this</span>.inPort_ = channel.port1;</span><br><span class="line">    <span class="keyword">this</span>.outPort_ = channel.port2;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.outPort_.onmessage = <span class="function">(<span class="params">&#123;data: &#123;key, value&#125;&#125;</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> resolve = <span class="keyword">this</span>.pendingClones_.get(key);</span><br><span class="line">      resolve(value);</span><br><span class="line">      <span class="keyword">this</span>.pendingClones_.delete(key);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">this</span>.outPort_.start();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  cloneAsync(value) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> key = <span class="keyword">this</span>.nextKey_++;</span><br><span class="line">      <span class="keyword">this</span>.pendingClones_.set(key, resolve);</span><br><span class="line">      <span class="keyword">this</span>.inPort_.postMessage(&#123;key, value&#125;);</span><br><span class="line">    &#125;);</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> structuredCloneAsync = <span class="built_in">window</span>.structuredCloneAsync =</span><br><span class="line">    StructuredCloner.prototype.cloneAsync.bind(<span class="keyword">new</span> StructuredCloner);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> main = <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> original = &#123; <span class="attr">date</span>: <span class="keyword">new</span> <span class="built_in">Date</span>(), <span class="attr">number</span>: <span class="built_in">Math</span>.random() &#125;;</span><br><span class="line">  original.self = original;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> clone = <span class="keyword">await</span> structuredCloneAsync(original);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// different objects:</span></span><br><span class="line">  <span class="built_in">console</span>.assert(original !== clone);</span><br><span class="line">  <span class="built_in">console</span>.assert(original.date !== clone.date);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// cyclical:</span></span><br><span class="line">  <span class="built_in">console</span>.assert(original.self === original);</span><br><span class="line">  <span class="built_in">console</span>.assert(clone.self === clone);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// equivalent values:</span></span><br><span class="line">  <span class="built_in">console</span>.assert(original.number === clone.number);</span><br><span class="line">  <span class="built_in">console</span>.assert(<span class="built_in">Number</span>(original.date) === <span class="built_in">Number</span>(clone.date));</span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"Assertions complete."</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">main();</span><br></pre></td></tr></table></figure><p><code>通过 history API</code>: <code>history.pushState()</code> 和 <code>history.replaceState()</code> 都会对第一个参数创建结构化克隆。注意，这个方法是同步的，操作浏览器的 history 并不是个很快的操作，所以频繁调用这个方法会导致浏览器无响应。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> structuredClone = <span class="function"><span class="params">obj</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> oldState = history.state;</span><br><span class="line">  history.replaceState(obj, <span class="literal">null</span>);</span><br><span class="line">  <span class="keyword">const</span> clonedObj = history.state;</span><br><span class="line">  history.replaceState(oldState, <span class="literal">null</span>);</span><br><span class="line">  <span class="keyword">return</span> clonedObj;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><code>通过 notification API</code>: 创建新的通知时，构造函数会创建其中关联的 data 的结构化克隆。注意，这个也会尝试将通知展现给用户，但是这个默默的失败，除非应用请求过显示通知的权限。在已授予权限的场景下，通知马上被关闭。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> structuredClone = <span class="function"><span class="params">obj</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> n = <span class="keyword">new</span> Notification(<span class="string">""</span>, &#123;<span class="attr">data</span>: obj, <span class="attr">silent</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">  n.onshow = n.close.bind(n);</span><br><span class="line">  <span class="keyword">return</span> n.data;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="NODE-JS-中深复制"><a href="#NODE-JS-中深复制" class="headerlink" title="NODE.JS 中深复制"></a>NODE.JS 中深复制</h2><p>从 version 8.0.0 开始，Node.js 提供 <a href="https://nodejs.org/api/v8.html#v8_serialization_api" target="_blank" rel="noopener">serialization api</a>，它兼容结构化克隆。注意，写这篇文章是，这个 API 属于实验性的：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> v8 = <span class="built_in">require</span>(<span class="string">'v8'</span>);</span><br><span class="line"><span class="keyword">const</span> buf = v8.serialize(&#123;<span class="attr">a</span>: <span class="string">'foo'</span>, <span class="attr">b</span>: <span class="keyword">new</span> <span class="built_in">Date</span>()&#125;);</span><br><span class="line"><span class="keyword">const</span> cloned = v8.deserialize(buf);</span><br><span class="line">cloned.b.getMonth();</span><br></pre></td></tr></table></figure><p>对于 8.0.0 以下版本，或者是需要更稳定的实现，一个是使用 lodash 的 <code>cloneDeep</code> 方法，这也是基于结构化克隆算法的。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>总结一下，JavaScript 中最好的复制对象算法，很大程度上取决于环境和你要复制的对象的类型。虽然 lodash 是通用深复制方法中最安全的选择，但是如果你自己实现，可以得到更高效的方案。下面是个简单的深复制例子，支持日期（<code>Date</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepClone</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> copy;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Handle the 3 simple types, and null or undefined</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">null</span> == obj || <span class="string">"object"</span> != <span class="keyword">typeof</span> obj) <span class="keyword">return</span> obj;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Handle Date</span></span><br><span class="line">  <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">Date</span>) &#123;</span><br><span class="line">    copy = <span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">    copy.setTime(obj.getTime());</span><br><span class="line">    <span class="keyword">return</span> copy;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Handle Array</span></span><br><span class="line">  <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">Array</span>) &#123;</span><br><span class="line">    copy = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, len = obj.length; i &lt; len; i++) &#123;</span><br><span class="line">        copy[i] = deepClone(obj[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> copy;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Handle Function</span></span><br><span class="line">  <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">Function</span>) &#123;</span><br><span class="line">    copy = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> obj.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> copy;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Handle Object</span></span><br><span class="line">  <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="built_in">Object</span>) &#123;</span><br><span class="line">      copy = &#123;&#125;;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">var</span> attr <span class="keyword">in</span> obj) &#123;</span><br><span class="line">          <span class="keyword">if</span> (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> copy;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"Unable to copy obj as type isn't supported "</span> + obj.constructor.name);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>个人而言，我希望可以在任何地方使用结构化克隆，这样这个问题（对象复制）就可以解决了：开心的克隆:)</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;原文: &lt;a href=&quot;https://smalldata.tech/blog/2018/11/01/copying-objects-in-javascript&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;COPYING OBJECTS IN JAVASCRIPT&lt;/a&gt;&lt;br&gt;作者: Victor Parmar&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这篇文章，我们将介绍在 JavaScript 中复制对象的各种方法。其中包括了浅复制和深复制。&lt;/p&gt;
    
    </summary>
    
      <category term="翻译" scheme="https://blog.hhking.cn/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
      <category term="复制对象" scheme="https://blog.hhking.cn/tags/%E5%A4%8D%E5%88%B6%E5%AF%B9%E8%B1%A1/"/>
    
      <category term="浅复制" scheme="https://blog.hhking.cn/tags/%E6%B5%85%E5%A4%8D%E5%88%B6/"/>
    
      <category term="深复制" scheme="https://blog.hhking.cn/tags/%E6%B7%B1%E5%A4%8D%E5%88%B6/"/>
    
  </entry>
  
  <entry>
    <title>Awesome Font-end</title>
    <link href="https://blog.hhking.cn/2018/12/03/awesome-front-end/"/>
    <id>https://blog.hhking.cn/2018/12/03/awesome-front-end/</id>
    <published>2018-12-03T13:44:47.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<p>前端的发展日新月异，新的技术、新的工具如雨后春笋，不断的冒出来。</p><p>作为前端，身处于大前端的繁荣时代，需要不断的学习，不断的更新自我。</p><blockquote><p>这是最好的时代，也是最坏的时代</p></blockquote><p>鱼，还是渔，这时就显得非常的重要。</p><a id="more"></a><p>掌握好的信息来源，接收优质的内容，才能在浪潮中游刃有余。</p><p>所以，这里记录一些个人觉得值得关注和收藏的前端领域内容:</p><ul><li>前端工具</li><li>教程&amp;&amp;博客</li><li>实用的库</li><li>值得关注的大神</li><li>值得订阅的资讯</li><li>书单</li></ul><p>主要的目的是作为个人备忘！😀</p><p>GitHub 地址: <a href="https://github.com/hhking/Awesome-Font-end" target="_blank" rel="noopener">Awesome Font-end 😀</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;前端的发展日新月异，新的技术、新的工具如雨后春笋，不断的冒出来。&lt;/p&gt;&lt;p&gt;作为前端，身处于大前端的繁荣时代，需要不断的学习，不断的更新自我。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这是最好的时代，也是最坏的时代&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;鱼，还是渔，这时就显得非常的重要。&lt;/p&gt;
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="Front-end" scheme="https://blog.hhking.cn/tags/Front-end/"/>
    
      <category term="awesome" scheme="https://blog.hhking.cn/tags/awesome/"/>
    
  </entry>
  
  <entry>
    <title>HTML5 图片上传解决方案</title>
    <link href="https://blog.hhking.cn/2018/11/29/html5-img-upload/"/>
    <id>https://blog.hhking.cn/2018/11/29/html5-img-upload/</id>
    <published>2018-11-29T06:18:59.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前端做图片上传时，经常会遇到图片压缩、图片预览等需求。而这个过程中，会遇到一个个的坑。下面就来看一看 HTML5 实现图片上传的整个过程。</p><a id="more"></a><h2 id="基本结构"><a href="#基本结构" class="headerlink" title="基本结构"></a>基本结构</h2><p>图片上传是使用 input 标签来选择图片的：</p><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">"file"</span> <span class="attr">accept</span>=<span class="string">"image/*"</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里可能遇到一个坑：</p><p>可能会遇到响应迟钝，文件选择框过好几秒才弹出。<a href="https://zhuanlan.zhihu.com/p/27946188" target="_blank" rel="noopener">具体的原因可以查看这里</a>。</p><p>解决的方法是将 * 通配符改成指定的 MIME 类型。例如</p><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">"file"</span> <span class="attr">accept</span>=<span class="string">"image/gif,image/jpeg,image/jpg,image/png"</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="获取图片文件"><a href="#获取图片文件" class="headerlink" title="获取图片文件"></a>获取图片文件</h2><p>通过监听 input 的 <code>change</code> 事件，获取 <code>FileList</code>类数组对象（event.target.files）。<code>FileList</code> 对象的成员就是 <code>File</code>对象，包含的属性如图：</p><p><img src="/images/h5-img-upload/h5-img-upload1.jpg" alt=""></p><h2 id="FileReader"><a href="#FileReader" class="headerlink" title="FileReader"></a>FileReader</h2><p>要对图片进行压缩，首先要获取图片内容。</p><p>这里需要使用 <code>FileReader</code>的<code>readAsDataURL(Blob|File)</code> 来读取图片内容。<code>readAsDataURL</code>方法返回 data URL，将文件进行 base64 编码。示例如下：</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"><span class="keyword">let</span> fr = <span class="keyword">new</span> FileReader();</span><br><span class="line"><span class="comment">// 读取成功回调</span></span><br><span class="line">fr.onload = <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// e.target.result 就是图片的 base64 地址，可以直接用于图片的 src</span></span><br><span class="line">  img.src = e.target.result;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 失败回调</span></span><br><span class="line">fr.onerror = <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// error handle</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 读取图片</span></span><br><span class="line">fr.readAsDataURL(file);</span><br></pre></td></tr></table></figure><blockquote><p>方法参考：<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader" target="_blank" rel="noopener">FileReader</a></p></blockquote><h2 id="图片压缩"><a href="#图片压缩" class="headerlink" title="图片压缩"></a>图片压缩</h2><p>前端进行图片压缩，不但节省流量，而且加速上传速度，提高用户体验。</p><p>上一步读取了图片，就可以对图片进行操作了。前端实现图片压缩，原理很简单：</p><ol><li>缩小图片，大图片转成小图片</li><li>降低图片质量</li></ol><p>第一点是通过 <code>canvas</code> 的 <code>drawImage()</code>方法来实现，第二点在后面会提到。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> ctx.drawImage(image, dx, dy, dWidth, dHeight)</span><br></pre></td></tr></table></figure><p>是在 canvas 上绘制图像，我们只需要把原图，在 canvas 上绘制成更小的图片，就实现了压缩，就是这么简单。</p><p>所以关键就是怎么来设置 <code>dWidth</code> 和<code>dHeight</code>。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// width height 图片长宽</span></span><br><span class="line"><span class="comment">// maxWidth maxHeight 限制的图片最大长宽</span></span><br><span class="line"><span class="keyword">let</span> scale = width / height;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (scale &gt;= maxWidth / maxHeight) &#123;</span><br><span class="line">  <span class="keyword">if</span> (width &gt; maxWidth) &#123;</span><br><span class="line">    height = maxWidth / scale;</span><br><span class="line">    width = maxWidth;</span><br><span class="line">  &#125;</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (height &gt; maxHeight) &#123;</span><br><span class="line">  width = maxHeight * scale;</span><br><span class="line">  height = maxHeight;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 这里的 image 就是上一步得到的图片内容</span></span><br><span class="line">ctx.drawImage(image, <span class="number">0</span>, <span class="number">0</span>, width, height);</span><br></pre></td></tr></table></figure><blockquote><p>方法参考： <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage" target="_blank" rel="noopener">CanvasRenderingContext2D.drawImage()</a></p></blockquote><h2 id="图片输出"><a href="#图片输出" class="headerlink" title="图片输出"></a>图片输出</h2><p>上一步在 canvas 绘画了图片，接下来就需要把 canvas 画布转化成 img 图像。</p><p><code>canvas</code>提供了两个转图片的方法：</p><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL" target="_blank" rel="noopener">HTMLCanvasElement.toDataURL()</a>：图片转换成base64格式</li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob" target="_blank" rel="noopener">HTMLCanvasElement.toBlob()</a>：图片转换成Blob文件</li></ul><h3 id="toDataURL"><a href="#toDataURL" class="headerlink" title="toDataURL()"></a>toDataURL()</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">canvas.toDataURL(type, encoderOptions);</span><br></pre></td></tr></table></figure><p>属于同步方法，返回 base64 格式的图片。</p><p>第一个参数 <code>type</code>是图片格式；</p><p>第二个参数 <code>encoderOptions</code> 就是用于之前提到的，控制图片质量，达到压缩图片的效果。</p><blockquote><p>在指定图片格式为 <code>image/jpeg 或</code> <code>image/webp的情况下，可以从 0 到 1 的区间内选择图片的质量</code>。如果超出取值范围，将会使用默认值 <code>0.92</code>。</p></blockquote><h3 id="toBlob"><a href="#toBlob" class="headerlink" title="toBlob()"></a>toBlob()</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> canvas.toBlob(callback, type, encoderOptions);</span><br></pre></td></tr></table></figure><p>属于异步方法，所以有个<code>callback</code> 参数。</p><p><code>type</code> 参数指定图片格式；</p><p><code>encoderOptions</code>参数指定图片质量，用于压缩图片</p><blockquote><p>值在0与1之间，当请求图片格式为<code>image/jpeg</code>或者<code>image/webp</code>时用来指定图片展示质量。</p></blockquote><blockquote><p>关于 Blob：</p><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Blob" target="_blank" rel="noopener"><code>Blob</code></a>对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/File" target="_blank" rel="noopener"><code>File</code></a> 接口基于<code>Blob</code>，继承了 blob 的功能并将其扩展使其支持用户系统上的文件。</p><p><a href="https://juejin.im/entry/5937c98eac502e0068cf31ae" target="_blank" rel="noopener">JavaScript 中 Blob 对象</a>: 这篇文章介绍了 Blob，里面也提到了大文件分割上传的实现。</p></blockquote><p>一般来说，对比 Blob 文件和 base64 ，有下面几点优点：</p><ul><li>二进制文件，对后端更友好</li><li>base64 字符串一般都非常长，会有性能等问题</li></ul><p>所以选择转化成 Blob 文件进行上传更好。把 base64 或者 Blob 文件加入 <code>FormData</code> 里就可以实现上传了。</p><h2 id="图片预览"><a href="#图片预览" class="headerlink" title="图片预览"></a>图片预览</h2><p>一般图片上传还会要求实现图片上传进度、图片预览功能。</p><p>关于图片预览的实现，可以通过下面的方法，来获取图片链接，做为本地预览：</p><ul><li><p>base64 可以作为图片链接，<code>FileReader.readAsDataURL(Blob|File)</code>方法可以得到 base64</p></li><li><p><code>URL.createObjectURL(Blob|File)</code>返回的 URL 可以作为图片的链接</p></li></ul><blockquote><p>详情参考：</p><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader/readAsDataURL" target="_blank" rel="noopener">FileReader.readAsDataURL()</a></p><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL" target="_blank" rel="noopener">URL.createObjectURL()</a></p></blockquote><h2 id="手机照片旋转问题"><a href="#手机照片旋转问题" class="headerlink" title="手机照片旋转问题"></a>手机照片旋转问题</h2><p>把在 iPhone 上拍出来的照片，通过上面的方式进行上传，你会发现图片方向和你预料的不同。</p><h3 id="orientation"><a href="#orientation" class="headerlink" title="orientation"></a>orientation</h3><p>使用 iPhone 拍照片，会根据你拍照时手机的方向，照片会有不同的方向。这个方向可以通过图片的 <code>orientation</code>参数来确定。</p><table><thead><tr><th>旋转角度</th><th>参数值</th><th>手机方向</th></tr></thead><tbody><tr><td>0°</td><td>1</td><td>home 键在右方的横屏拍摄方式</td></tr><tr><td>逆时针90°</td><td>6</td><td>home键在下方(正常拿手机的方向)</td></tr><tr><td>顺时针90°</td><td>8</td><td>home键在上方</td></tr><tr><td>180°</td><td>3</td><td>home键在左侧</td></tr></tbody></table><p>可以通过 <a href="https://github.com/exif-js/exif-js" target="_blank" rel="noopener">exif-js</a> 来获取图片的 <code>orientation</code>：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> EXIF <span class="keyword">from</span> <span class="string">'exif-js'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// file 是上面提到的 File 文件</span></span><br><span class="line">EXIF.getData(file, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"><span class="keyword">var</span> orientation = EXIF.getTag(<span class="keyword">this</span>, <span class="string">'Orientation'</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><blockquote><p>详情参考：<a href="http://feihu.me/blog/2015/how-to-handle-image-orientation-on-iOS/" target="_blank" rel="noopener">如何处理iOS中照片的方向</a></p></blockquote><h3 id="校正方向"><a href="#校正方向" class="headerlink" title="校正方向"></a>校正方向</h3><p>要校正图片的方向，只需要根据 <code>orientation</code> 参数，把图片的方向旋转会正常即可。旋转需要用到 canvas 的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/rotate" target="_blank" rel="noopener"><code>rotate()</code></a>方法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> ctx.rotate(angle);</span><br></pre></td></tr></table></figure><p>参数 angle 是顺时针旋转的弧度，旋转中心点是 canvas 的起始点。</p><p>角度值换算弧度的公式： <code>angle = degree * Math.PI / 180</code></p><p>以 <code>orientation</code>等于 6 时为例，也就是图片逆时针旋转了 90°，要把图片校正方向，就要画布顺时针旋转 90° : <code>rotate((90 * Math.PI) / 180)</code></p><p><img src="/images/h5-img-upload/h5-img-upload2.jpg" alt=""></p><p>画布旋转之后，<code>drawImage()</code>根据画布的位置进行调整，如上图所示:</p><p>旋转之前：<code>drawImage(image, 0, 0, x, y)</code></p><p>旋转之后，原有的画布不变，坐标跟着旋转，图片转到可视范围之外，所以：</p><ul><li>需要把图片移到可视范围里</li><li>调整画布大小</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// other code...</span></span><br><span class="line"><span class="keyword">case</span> <span class="number">6</span>:</span><br><span class="line"><span class="comment">// 旋转角度</span></span><br><span class="line">degree = <span class="number">90</span>;</span><br><span class="line"><span class="comment">// 调整画布大小</span></span><br><span class="line">canvas.width = imgHeight;</span><br><span class="line">canvas.height = imgWidth;</span><br><span class="line"><span class="comment">// 修改绘画位置</span></span><br><span class="line">imgHeight = - imgHeight;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="comment">// other code ...</span></span><br><span class="line"><span class="comment">// canvas 旋转</span></span><br><span class="line">ctx.rotate((degree * <span class="built_in">Math</span>.PI)/<span class="number">180</span>);</span><br><span class="line"><span class="comment">// 绘制图片</span></span><br><span class="line">ctx.drawImage(image, <span class="number">0</span>, <span class="number">0</span>, imgWidth, imgHeight);</span><br></pre></td></tr></table></figure><p>其他的 <code>orientation</code> 也是类似的原理。<a href="https://github.com/hhking/fe-demo/blob/054ec906907988aae919deda5e3369c7a1cb84e9/h5ImgCompress/h5ImgCompress.js#L52" target="_blank" rel="noopener">查看示例代码</a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>图片压缩上传的过程，总结起来就是：图片 → 压缩 → 图片。</p><p>这个过程中，核心点是：</p><ul><li><p>FileReader API 使用</p></li><li><p>Canvas 实现图片压缩、绘制和方向校正</p></li></ul><p>这里主要总结了使用 HTML5 API 实现图片上传的过程，在实际的使用还要根据具体的使用场景，考虑兼容问题，选择合适的解决方案。</p><p>最后，查看完整的示例代码：<a href="https://github.com/hhking/fe-demo/tree/054ec906907988aae919deda5e3369c7a1cb84e9/h5ImgCompress" target="_blank" rel="noopener">h5ImgCompress</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;前端做图片上传时，经常会遇到图片压缩、图片预览等需求。而这个过程中，会遇到一个个的坑。下面就来看一看 HTML5 实现图片上传的整个过程。&lt;/p&gt;
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="HTML5" scheme="https://blog.hhking.cn/tags/HTML5/"/>
    
      <category term="图片上传" scheme="https://blog.hhking.cn/tags/%E5%9B%BE%E7%89%87%E4%B8%8A%E4%BC%A0/"/>
    
      <category term="图片压缩" scheme="https://blog.hhking.cn/tags/%E5%9B%BE%E7%89%87%E5%8E%8B%E7%BC%A9/"/>
    
      <category term="图片预览" scheme="https://blog.hhking.cn/tags/%E5%9B%BE%E7%89%87%E9%A2%84%E8%A7%88/"/>
    
  </entry>
  
  <entry>
    <title>阿里云 GitLab 折腾笔记</title>
    <link href="https://blog.hhking.cn/2018/11/24/aliyun-gitlab-install/"/>
    <id>https://blog.hhking.cn/2018/11/24/aliyun-gitlab-install/</id>
    <published>2018-11-24T15:09:44.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<p>想自己搭建一个 git 服务来玩一玩，正好有个阿里云，虽然配置很渣，但是也想着随便搞一搞。</p><p>于是从<a href="https://about.gitlab.com/install/" target="_blank" rel="noopener">官方教程</a>开始，遇到一些坑，查看一些资料，解决一些问题，有了下面的笔记。</p><a id="more"></a><p>选择对应的版本。我的阿里云装的是 <code>CentOS 7</code>,所以选择 <code>CentOS 7</code>的版本。</p><p><img src="/images/aliyun-gitlab-install/aliyun-gitlab-install1.jpg" alt=""></p><blockquote><p>通过下面的命令可以查看当前属于什么系统</p></blockquote><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">lsb_release -a</span><br></pre></td></tr></table></figure><h2 id="安装和配置一些必要的依赖"><a href="#安装和配置一些必要的依赖" class="headerlink" title="安装和配置一些必要的依赖"></a>安装和配置一些必要的依赖</h2><h3 id="Step1"><a href="#Step1" class="headerlink" title="Step1"></a>Step1</h3><p>通过下面的命令，在系统防火墙中开启 HTTP 和 SSH 的访问。</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">sudo yum install -y curl policycoreutils-python openssh-server</span><br><span class="line">sudo systemctl enable sshd</span><br><span class="line">sudo systemctl start sshd</span><br><span class="line">sudo firewall-cmd --permanent --add-service=http</span><br><span class="line">sudo systemctl reload firewalld</span><br></pre></td></tr></table></figure><p>在执行</p><p><code>sudo firewall-cmd --permanent --add-service=http</code></p><p>时可能会遇到 <code>FirewallD is not running</code>的错误提示；</p><p>意思是：<code>防火墙服务没有运行</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">systemctl start firewalld.service</span><br></pre></td></tr></table></figure><p>然后重新执行命令就会提示 <code>success</code></p><p><img src="/images/aliyun-gitlab-install/aliyun-gitlab-install2.jpg" alt=""></p><h3 id="Step-2"><a href="#Step-2" class="headerlink" title="Step 2"></a>Step 2</h3><p>执行下面命令，安装并开启 <code>Postfix</code> 邮件通知服务</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">sudo yum install postfix</span><br><span class="line">sudo systemctl enable postfix</span><br><span class="line">sudo systemctl start postfix</span><br></pre></td></tr></table></figure><p>这里遇到一个报错</p><p><img src="/images/aliyun-gitlab-install/aliyun-gitlab-install3.jpg" alt=""></p><p>解决办法是修改 <code>/etc/postfix/main.cf</code>文件中的下面信息</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">inet_interfaces = all</span><br><span class="line">inet_protocols = ipv4 // 或者 all</span><br></pre></td></tr></table></figure><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">sudo systemctl restart postfix</span><br></pre></td></tr></table></figure><h2 id="添加-GitLab-软件包的仓库，并安装"><a href="#添加-GitLab-软件包的仓库，并安装" class="headerlink" title="添加 GitLab 软件包的仓库，并安装"></a>添加 GitLab 软件包的仓库，并安装</h2><p>安装 GitLab</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">curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash</span><br></pre></td></tr></table></figure><p>设置 GitLab 访问域名</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">sudo EXTERNAL_URL="http://gitlab.example.com" yum install -y gitlab-ee</span><br></pre></td></tr></table></figure><p>注意：这里 <code>http://gitlab.example.com</code>替换成对应的<strong>域名</strong>或者<strong>IP</strong></p><p>我这里设置成 IP 加 8888 端口，这需要在阿里云上配置安全组规则，出入方向都要配置对应端口。例如：</p><p><img src="/images/aliyun-gitlab-install/aliyun-gitlab-install4.jpg" alt=""></p><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"><span class="meta">#</span> 开启 8888 端口</span><br><span class="line">firewall-cmd --zone=public --add-port=8888/tcp --permanent</span><br><span class="line"><span class="meta">#</span> 重启防火墙</span><br><span class="line">systemctl restart firewalld</span><br></pre></td></tr></table></figure><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">sudo EXTERNAL_URL="http://xx.xx.xx.xx:8888" yum install -y gitlab-ee</span><br></pre></td></tr></table></figure><p>如果安装完之后要修改访问的域名或者 IP，则修改 <code>/etc/gitlab/gitlab.rb</code>文件中的 <code>external_url</code> 内容</p><p>然后重新配置服务</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gitlab-ctl reconfigure</span><br></pre></td></tr></table></figure><p>然后就可能用设置的域名（IP）访问了。</p><h2 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h2><p>第一次打开，会打开密码设置页面，让你设置密码。</p><p>设置密码之后，使用用户名 root 和设置的密码登录。</p><p>最后来个登录成功的图：</p><p><img src="/images/aliyun-gitlab-install/aliyun-gitlab-install5.jpg" alt=""></p><h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><h3 id="一些常用命令"><a href="#一些常用命令" class="headerlink" title="一些常用命令"></a>一些常用命令</h3><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">//启动</span><br><span class="line">sudo gitlab-ctl start</span><br><span class="line"></span><br><span class="line">//停止</span><br><span class="line">sudo gitlab-ctl stop</span><br><span class="line"></span><br><span class="line">//重启</span><br><span class="line">sudo gitlab-ctl restart</span><br><span class="line"></span><br><span class="line">//查看状态</span><br><span class="line">sudo gitlab-ctl status</span><br><span class="line"></span><br><span class="line">//使更改配置生效</span><br><span class="line">sudo gitlab-ctl reconfigure</span><br></pre></td></tr></table></figure><h3 id="开启阿里云-SWAP"><a href="#开启阿里云-SWAP" class="headerlink" title="开启阿里云 SWAP"></a>开启阿里云 SWAP</h3><p>刚安装结束，开启 GitLab 之后，阿里云就开始卡爆了，GitLab 也直接无法访问，或者是直接 502，白忙活的半天！内存爆炸了啊！这时候想到阿里云好像是没开启 SWAP 的。于是就有了下面的内容。</p><p>查看 swap 分区：</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 /proc/swaps</span><br></pre></td></tr></table></figure><p>发现是空的，下面来开启 swap</p><p>详情参考：<a href="https://help.aliyun.com/knowledge_detail/42534.html" target="_blank" rel="noopener">云服务器 ECS Linux SWAP 配置概要说明</a></p><ol><li><p>创建swap大小为bs(block_size)*count(number_of_block)=4G</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">dd if=/dev/zero of=/mnt/swap bs=1M count=4096</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">mkswap /mnt/swap</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">swapon /mnt/swap</span><br></pre></td></tr></table></figure></li><li><p>权限设置</p><p>提示：swapon: /mnt/swap：不安全的权限 0644，建议使用 0600，设置权限</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">chmod 0600 /mnt/swap</span><br></pre></td></tr></table></figure><p>然后重新启用</p></li><li><p>设置开机时自启用 SWAP 分区</p><p>需要修改文件 /etc/fstab 中的 SWAP 行，在文件末位添加</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">/mnt/swap swap swap defaults 0 0</span><br></pre></td></tr></table></figure></li><li><p>修改 swpapiness 参数</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 /proc/sys/vm/swappiness</span><br></pre></td></tr></table></figure><p>通过上面命令看到 swappiness 值为 0，需要在物理内存使用完毕后才会使用 SWAP 分区</p><p><img src="/images/aliyun-gitlab-install/aliyun-gitlab-install6.jpg" alt=""></p><p>我们配置为空闲内存少于 10% 时才使用 SWAP 分区</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">echo 10 &gt;/proc/sys/vm/swappiness</span><br></pre></td></tr></table></figure><p>若需要永久修改此配置，在系统重启之后也生效的话，可以修改 /etc/sysctl.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">vm.swappiness=10</span><br></pre></td></tr></table></figure></li></ol><p>开启 SWAP 之后，GitLab 就能访问了！</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这篇笔记，主要是记录一下这次安装 GitLab 遇到的坑，以及对应的解决方案。</p><blockquote><p>参考资料：</p><p><a href="https://about.gitlab.com/install/" target="_blank" rel="noopener">官方教程</a><br><a href="https://blog.csdn.net/zhaoyanjun6/article/details/79144175" target="_blank" rel="noopener">CentOS 初体验十四：阿里云安装Gitlab</a><br><a href="https://help.aliyun.com/knowledge_detail/42534.html" target="_blank" rel="noopener">云服务器 ECS Linux SWAP 配置概要说明</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;想自己搭建一个 git 服务来玩一玩，正好有个阿里云，虽然配置很渣，但是也想着随便搞一搞。&lt;/p&gt;&lt;p&gt;于是从&lt;a href=&quot;https://about.gitlab.com/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;官方教程&lt;/a&gt;开始，遇到一些坑，查看一些资料，解决一些问题，有了下面的笔记。&lt;/p&gt;
    
    </summary>
    
      <category term="GitLab" scheme="https://blog.hhking.cn/categories/GitLab/"/>
    
    
      <category term="阿里云" scheme="https://blog.hhking.cn/tags/%E9%98%BF%E9%87%8C%E4%BA%91/"/>
    
      <category term="CentOS 7" scheme="https://blog.hhking.cn/tags/CentOS-7/"/>
    
      <category term="GitLab" scheme="https://blog.hhking.cn/tags/GitLab/"/>
    
  </entry>
  
  <entry>
    <title>[译]JavaScript 终极指南之执行上下文、变量提升、作用域和闭包</title>
    <link href="https://blog.hhking.cn/2018/10/24/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/"/>
    <id>https://blog.hhking.cn/2018/10/24/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/</id>
    <published>2018-10-24T15:18:37.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="https://tylermcginnis.com/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/" target="_blank" rel="noopener">The Ultimate Guide to Execution Contexts, Hoisting, Scopes, and Closures in JavaScript</a><br>作者：<a href="https://twitter.com/tylermcginnis" target="_blank" rel="noopener">Tyler McGinnis</a></p></blockquote><p><img src="https://i.loli.net/2019/05/26/5cea077212cb313490.png" alt=""></p><p>视频：<a href="https://www.youtube.com/watch?v=Nt-qa_LlUH0" target="_blank" rel="noopener">The Ultimate Guide to Execution Contexts, Hoisting, Scopes, and Closures in JavaScript</a></p><p>我认为理解 JavaScript 语言的最重要的基本概念是理解执行上下文（Execution Context），这点可能令人感到意外。正确的学习执行上下文，可以让你更容易学习更高级的内容，比如变量提升（hoisting）、作用域链（scope chains）和闭包（closures）。既然如此，那到底什么是“执行上下文”呢？为了更好的理解它，我们先来看看我们是如何写软件的。</p><a id="more"></a><p>编写软件的一种策略是把代码拆分成独立的块。虽然这些“块”有不同的命名（函数、模块、包等等），但是他们有同样的目的——分解和处理应用的复杂性。现在我们不要以编写代码的思维来思考，而是以 JavaScript 引擎的角度来思考，JavaScript 引擎是用来解释代码的。那么我们是否也可以使用和我们在写代码的时候一样的策略，把代码拆分成块，来处理解释代码的复杂性。答案是可以，这些“块”被称为执行上下文。<strong>正如可以使用函数、模块、包（functions/modules/packages）来处理编写代码的复杂性，JavaScript 引擎可以通过执行上下文来处理解释和运行代码的复杂性。</strong> 现在我们知道执行上下文的用途了，接下来需要解答的问题是：它们是如何创建的和它们是什么组成的？</p><p>JavaScript 引擎执行代码时，第一个被创建的执行上下文叫做全局执行上下文（Global Execution Context）。最开始这个执行上下文包含两个东西——一个全局对象和 <code>this</code> 变量。<code>this</code> 会引用全局对象，在浏览器执行 JavaScript 则全局对象是 <code>window</code>，在 Node 环境执行则全局对象是 <code>global</code>。</p><p><img src="https://i.loli.net/2019/05/26/5cea07aa5677b67067.png" alt=""></p><p>上图我们可以看到，即使没有任何代码，全局执行上下文还是会包含两个东西—— <code>window</code> 和 <code>this</code>。这是全局执行上下文的最基本形式。</p><p>我们一步一步来，看看当向程序添加代码时会发生什么。我们先添加一些变量。</p><p><img src="https://i.loli.net/2019/05/26/5cea07d962a7729018.png" alt=""></p><p><img src="https://i.loli.net/2019/05/26/5cea07ed8270a22948.png" alt=""></p><p>你可以看出上面两张图的不同之处吗？关键的是每个执行上下文都有两个独立的阶段——创建（<code>Creation</code>）阶段和执行（<code>Execution</code>）阶段，每个阶段都有它特有的职责。</p><p>在全局<code>创建</code>阶段，JavaScript 引擎将会：</p><ol><li>创建全局对象</li><li>创建 <code>this</code> 对象</li><li>给变量和函数设置内存空间</li><li>变量声明并默认赋值为 <code>undefined</code>，同时在内存中放置所有函数声明。</li></ol><p>直到<code>执行</code>阶段，JavaScript 引擎才会开始一行一行的执行代码。</p><p>我们从下面的 GIF 图可以看到从<code>创建</code>阶段到<code>执行</code>阶段这一流程。</p><p><img src="https://tylermcginnis.com/images/posts/advanced-javascript/global-execution-context-gif.gif" alt=""></p><p>在<code>创建</code>阶段，创建 <code>window</code> 和 <code>this</code> ，变量声明（<code>name</code> 和 <code>handle</code>）默认赋值为 <code>undefined</code>，所有函数声明（<code>getUser</code>）全部放入内存中。然后一旦进入 <code>执行</code> 阶段，JavaScript 引擎开始一行一行的执行代码，然后给内存中已存在的变量赋上真实的值。</p><blockquote><p>Gif 很酷，但是一步一步执行代码并亲自查看执行过程更酷。我为你创建了 <a href="https://tylermcginnis.com/javascript-visualizer/" target="_blank" rel="noopener">JavaScript Visualizer</a>，你值得拥有。如果你想查看上面的确切代码，打开这个<a href="https://tylermcginnis.com/javascript-visualizer/?code=var%20name%20%3D%20%27Tyler%27%0Avar%20handle%20%3D%20%27%40tylermcginnis%27%0A%0Afunction%20getUser%20%28%29%20%7B%0A%20%20return%20%7B%0A%20%20%20%20name%3A%20name%2C%0A%20%20%20%20handle%3A%20handle%0A%20%20%7D%0A%7D" target="_blank" rel="noopener">链接</a>。</p></blockquote><p>为了真正巩固 <code>创建</code> 阶段和 <code>执行</code> 阶段的知识点，我们打印一些 <code>创建</code> 阶段之后和 <code>执行</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'name: '</span>, name)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'handle: '</span>, handle)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'getUser :'</span>, getUser)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> name = <span class="string">'Tyler'</span></span><br><span class="line"><span class="keyword">var</span> handle = <span class="string">'@tylermcginnis'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUser</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    name: name,</span><br><span class="line">    handle: handle</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的代码，你想在 console 中打印出什么？当 JavaScript 开始一行一行执行代码和调用 <code>console.log</code> 时，<code>创建</code> 阶段已经完成了。这意味着，正如之前看到的，变量声明已经赋值为 <code>undefined</code> ，而函数声明则整个放在内存中了。所以正如我们期望的那样，<code>name</code> 和 <code>handle</code> 值为 <code>undefined</code>，<code>getUser</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(<span class="string">'name: '</span>, name) <span class="comment">// name: undefined</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'handle: '</span>, handle) <span class="comment">// handle: undefined</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'getUser :'</span>, getUser) <span class="comment">// getUser: ƒ getUser () &#123;&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> name = <span class="string">'Tyler'</span></span><br><span class="line"><span class="keyword">var</span> handle = <span class="string">'@tylermcginnis'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUser</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    name: name,</span><br><span class="line">    handle: handle</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>在创建阶段给变量声明默认赋值为 <code>undefined</code> 的过程称为<strong><em>变量提升（<code>Hoisting</code>）</em></strong></p></blockquote><p>之前你可能尝试过向自己解释“变量提升”，但是不尽人意。“变量提升”令人迷惑的点在于实际上没有任何东西“提升”或者移动。现在你理解了执行上下文和变量声明在 <code>创建</code> 阶段默认赋值为 <code>undefined</code>，从而你也理解了“变量提升”，因为这就是“变量提升”。</p><hr><p>现在，你应该对全局执行上下文和它的两个阶段——<code>创建</code> 和 <code>执行</code>相当熟悉了。好消息是只剩一个其他的执行上下文你需要学习，而且它几乎和全局执行上下文完全相同。它就是函数执行上下文，它在函数<strong>调用</strong>的时候创建。</p><p>关键的是，执行上下文只有在 JavaScript 引擎第一次开始解释代码时（全局执行上下文）或者函数调用时才创建。</p><p>现在主要的问题是，全局执行上下文和函数执行上下文的不同点是什么？如果你还记得，之前提到过在全局 <code>创建</code> 阶段，JavaScript 引擎将会：</p><ol><li>创建全局对象</li><li>创建 <code>this</code> 对象</li><li>为变量和函数设置内存空间</li><li>变量声明默认赋值为 <code>undefined</code>，同时函数声明存入内存</li></ol><p>这些步骤对于函数执行上下文来说哪些是不对的？步骤 1。我们有且只有一个全局对象，它在全局执行上下文的 <code>创建</code> 阶段创建，而不会在函数调用和 JavaScript 引擎创建函数执行上下文时创建。函数执行上下文不需要创建全局变量，而是需要考虑参数（arguments）问题，而全局执行上下文没有这个问题。考虑到这些，我们可以调整之前的列表。当<strong>函数</strong>执行上下文创建时，JavaScript 引擎将会：</p><ol><li><del>创建全局对象</del></li><li>创建一个参数对象</li><li>创建 <code>this</code> 对象</li><li>为变量和函数设置内存空间</li><li>变量声明默认赋值为 <code>undefined</code>，同时函数声明存入内存</li></ol><p>我们回到之前提到的代码，来看看这个过程，但是这次除了定义 <code>getUser</code>，还要看看调用它时会发生什么。</p><blockquote><p><a href="https://tylermcginnis.com/javascript-visualizer/?code=var%20name%20%3D%20%27Tyler%27%0Avar%20handle%20%3D%20%27%40tylermcginnis%27%0A%0Afunction%20getUser%20%28%29%20%7B%0A%20%20return%20%7B%0A%20%20%20%20name%3A%20name%2C%0A%20%20%20%20handle%3A%20handle%0A%20%20%7D%0A%7D%0A%0AgetUser%28%29" target="_blank" rel="noopener">查看可视化代码</a></p></blockquote><p><img src="https://tylermcginnis.com/images/posts/advanced-javascript/function-execution-context-gif.gif" alt=""></p><p>正如我们所说，调用 <code>getUser</code> 时会创建新的执行上下文。在 <code>getUser</code> 执行上下文的 <code>创建</code> 阶段，JavaScript 引擎创建 <code>this</code> 对象和 <code>arguments</code> 对象。因为 <code>getUser</code> 没有任何变量，所以 JavaScript 不需要给变量设置内存空间和进行“提升”。</p><p>你可能也注意到，当 <code>getUser</code> 函数执行完，它在可视化图里被移除了。实际上，JavaScript 引擎创建了“执行栈”（也被成为“调用栈”）。当函数调用时，创建新的执行上下文并把它加入执行栈。当函数执行结束，完成了 <code>创建</code> 和 <code>执行</code> 阶段，它会从执行栈中弹出。因为 JavaScript 是单线程的（意味着同时只能执行一个任务），所以这个过程很容易实现可视化。使用 “JavaScript Visualizer”，执行栈以嵌套形式显示，每个嵌套项对应执行栈中的新的执行上下文。</p><blockquote><p><a href="https://tylermcginnis.com/javascript-visualizer/?code=function%20a%20%28%29%20%7B%0A%20%20console.log%28%27In%20fn%20a%27%29%0A%20%20%0A%20%20function%20b%20%28%29%20%7B%0A%20%20%20%20console.log%28%27In%20fn%20b%27%29%0A%20%20%20%20%0A%20%20%20%20function%20c%20%28%29%20%7B%0A%20%20%20%20%20%20console.log%28%27In%20fn%20c%27%29%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20c%28%29%0A%20%20%7D%0A%0A%20%20b%28%29%0A%7D%0A%0Aa%28%29" target="_blank" rel="noopener">查看可视化代码</a></p></blockquote><p><img src="https://tylermcginnis.com/images/posts/advanced-javascript/javascript-execution-stack.gif" alt=""></p><hr><p>现在，我们已经知道函数调用如何创建自己的执行上下文，并加入执行栈中。我们还不知道的是有局部变量时会怎么样。我们修改代码，让函数有局部变量。</p><blockquote><p><a href="https://tylermcginnis.com/javascript-visualizer/?code=var%20name%20%3D%20%27Tyler%27%0Avar%20handle%20%3D%20%27%40tylermcginnis%27%0A%0Afunction%20getURL%20%28handle%29%20%7B%0A%20%20var%20twitterURL%20%3D%20%27https%3A%2F%2Ftwitter.com%2F%27%0A%0A%20%20return%20twitterURL%20%2B%20handle%0A%7D%0A%0AgetURL%28handle%29" target="_blank" rel="noopener">查看可视化代码</a></p></blockquote><p><img src="https://tylermcginnis.com/images/posts/advanced-javascript/local-variables.gif" alt=""></p><p>这里有一些重要的细节要注意。第一点是，你传入的任何参数都会作为局部变量添加到函数的执行上下文。在例子中，<code>handle</code> 作为变量出现在 <code>全局</code> 执行上下文（在变量定义的地方），也出现在 <code>getURL</code> 执行上下文中，因为把它当做参数传入了。然后是，函数内部声明的变量，存在于函数执行上下文中。所以当我们创建 <code>twitterURL</code> 时，它存在于 <code>getURL</code> 执行上下文——它定义的地方，而不在 <code>全局</code> 执行上下文中。这点看起来很明显，但它是我们下个主题——作用域的基本原理。</p><hr><p>过去，你可能听到过对“作用域”的定义，即为“可访问到变量的地方”。现在不管这个定义是否正确，使用你新学到的知识——执行上下文和 JavaScript Visualizer 工具，作用域的概念会变得比之前更加清晰。实际上，MDN 将 “作用域” 定义为 “当前执行的上下文”。听起来很熟悉？我们可以用类似执行上下文的思维来思考“作用域”和“可访问到变量的地方”。</p><p>这里有个测试。下面代码中，<code>bar</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> bar = <span class="string">'Declared in foo'</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">foo()</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(bar)</span><br></pre></td></tr></table></figure><p>我们用 JavaScript Visualizer 来验证。</p><blockquote><p><a href="https://tylermcginnis.com/javascript-visualizer/?code=function%20foo%20%28%29%20%7B%0A%20%20var%20bar%20%3D%20%27Declared%20in%20foo%27%0A%7D%0A%0Afoo%28%29%0A%0Aconsole.log%28bar%29" target="_blank" rel="noopener">查看可视化代码</a></p></blockquote><p><img src="https://tylermcginnis.com/images/posts/advanced-javascript/scope.gif" alt=""></p><p><code>foo</code> 调用时，我们在执行栈中创建新的执行上下文。在 <code>创建</code> 阶段创建 <code>this</code>、<code>arguments</code>，并给 <code>bar</code> 赋值为 <code>undefined</code>。然后开始 <code>执行</code> 阶段，把字符串 <code>Declared in foo</code> 赋值给 <code>bar</code>。在 <code>执行</code> 阶段结束后，<code>foo</code> 执行上下文从栈中弹出。当 <code>foo</code> 从执行栈中移除时，我们尝试在 console 中打印 <code>bar</code>。此时通过 JavaScript Visualizer，发现 <code>bar</code> 好像是从来没有出现过，所以我们得到 <code>undefined</code>。这个告诉我们，函数内部定义的变量是局部作用域的。这意味着（对大多数而已，后面会看到例外的情况）一旦函数执行上下文从执行栈弹出，变量就无法访问到了。</p><p>下面是另一个测试。下面的代码执行完之后 console 会打印出什么？</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="function"><span class="keyword">function</span> <span class="title">first</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> name = <span class="string">'Jordyn'</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log(name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">second</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> name = <span class="string">'Jake'</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log(name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(name)</span><br><span class="line"><span class="keyword">var</span> name = <span class="string">'Tyler'</span></span><br><span class="line">first()</span><br><span class="line">second()</span><br><span class="line"><span class="built_in">console</span>.log(name)</span><br></pre></td></tr></table></figure><p>我们还是来看看 JavaScript Visualizer。</p><blockquote><p><a href="https://tylermcginnis.com/javascript-visualizer/?code=function%20first%20%28%29%20%7B%0A%20%20var%20name%20%3D%20%27Jordyn%27%0A%0A%20%20console.log%28name%29%0A%7D%0A%0Afunction%20second%20%28%29%20%7B%0A%20%20var%20name%20%3D%20%27Jake%27%0A%0A%20%20console.log%28name%29%0A%7D%0A%0Aconsole.log%28name%29%0Avar%20name%20%3D%20%27Tyler%27%0Afirst%28%29%0Asecond%28%29%0Aconsole.log%28name%29" target="_blank" rel="noopener">查看可视化代码</a></p></blockquote><p><img src="https://tylermcginnis.com/images/posts/advanced-javascript/unique-scopes.gif" alt=""></p><p>我们得到的结果是：<code>undefined</code>、<code>Jordyn</code>、<code>Jake</code> 和 <code>Tyler</code>。这个告诉我们，我们可以认为每个新的执行上下文有它自己的特有的变量环境。即使还有其他执行上下文包含变量 <code>name</code>，JavaScript 引擎会先从当前执行上下文查找变量。</p><p>这就引出新的问题，如果当前执行上下文中不存在变量怎么办？JavaScript 引擎是否就停止查找该变量？我们来看个例子，它会告诉我们答案。下面的代码，会打印什么结果？</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> name = <span class="string">'Tyler'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">logName</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">logName()</span><br></pre></td></tr></table></figure><blockquote><p><a href="https://tylermcginnis.com/javascript-visualizer/?code=var%20name%20%3D%20%27Tyler%27%0A%0Afunction%20logName%20%28%29%20%7B%0A%20%20console.log%28name%29%0A%7D%0A%0AlogName%28%29" target="_blank" rel="noopener">查看可视化代码</a></p></blockquote><p>可能直觉告诉你会打印 <code>undefined</code>，因为 <code>logName</code> 执行上下文的作用域下没有 <code>name</code> 变量。这样想是正常的，但是是错误的。如果 JavaScript 引擎在函数执行上下文中找不到变量会发生什么呢？它会在最近的父级执行上下文中查找该变量。这个查找链将会一直持续，直到引擎查找到全局执行上下文。这种情况下，如果全局执行上下文也没有该变量，那么将会抛出引用错误（Reference Error）。</p><blockquote><p>如果变量在局部执行上下文中不存在，JavaScript 引擎会逐个检查各自的父级执行上下文，这个过程称为 <code>作用域链</code>。在 JavaScript Visualizer 中显示，每个新的执行上下文添加了缩进并加上特别的背景颜色。通过可视化，你可以看到每个子级执行上下文可以引用它父级执行上下文中的任何变量，但是反之则不行。</p></blockquote><hr><p>前面我们学习到，函数内部定义的变量是局部作用域的，当函数执行上下文从执行栈弹出后，变量就无法访问了（针对大多数情况）。这个说法错误的一种情况是：当一个函数内嵌在另一个函数里时。这种情况下，即使父级函数的执行上下文已经从执行栈中移除，子函数也可以保持能访问外部函数作用域。这个说起来就复杂了。还是使用 JavaScript Visualizer，它可以帮助我们。</p><blockquote><p><a href="https://tylermcginnis.com/javascript-visualizer/?code=var%20count%20%3D%200%0A%0Afunction%20makeAdder%28x%29%20%7B%0A%20%20return%20function%20inner%20%28y%29%20%7B%0A%20%20%20%20return%20x%20%2B%20y%3B%0A%20%20%7D%3B%0A%7D%0A%0Avar%20add5%20%3D%20makeAdder%285%29%3B%0Acount%20%2B%3D%20add5%282%29" target="_blank" rel="noopener">查看可视化代码</a></p></blockquote><p><img src="https://tylermcginnis.com/images/posts/advanced-javascript/closure-scope.gif" alt=""></p><p>在 <code>makeAdder</code> 执行上下文在执行栈弹出后，JavaScript Visualizer 创建了 <code>闭包作用域（Closure Scope）</code>。在 <code>闭包作用域（Closure Scope）</code> 里拥有和 <code>makeAdder</code> 执行上下文里一样的变量环境。产生这个情况的原因是，我们在把函数嵌入到另一个函数里。在我们这个例子里，函数 <code>inner</code> 内嵌在函数 <code>makeAdder</code> 里，所以 <code>inner</code> 创建了包含 <code>makeAdder</code> 变量环境的 <code>闭包</code>。因为创建了<code>闭包作用域（Closure Scope）</code>，所以即使 <code>makeAdder</code> 执行环境已经从执行栈弹出了，<code>inner</code> 还是可以访问变量 <code>x</code>（通过作用域链）。</p><p>正如你所想，子函数“包含”它父级函数的变量环境，把这个概念称为“闭包”。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;原文：&lt;a href=&quot;https://tylermcginnis.com/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The Ultimate Guide to Execution Contexts, Hoisting, Scopes, and Closures in JavaScript&lt;/a&gt;&lt;br&gt;作者：&lt;a href=&quot;https://twitter.com/tylermcginnis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Tyler McGinnis&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2019/05/26/5cea077212cb313490.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;&lt;p&gt;视频：&lt;a href=&quot;https://www.youtube.com/watch?v=Nt-qa_LlUH0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The Ultimate Guide to Execution Contexts, Hoisting, Scopes, and Closures in JavaScript&lt;/a&gt;&lt;/p&gt;&lt;p&gt;我认为理解 JavaScript 语言的最重要的基本概念是理解执行上下文（Execution Context），这点可能令人感到意外。正确的学习执行上下文，可以让你更容易学习更高级的内容，比如变量提升（hoisting）、作用域链（scope chains）和闭包（closures）。既然如此，那到底什么是“执行上下文”呢？为了更好的理解它，我们先来看看我们是如何写软件的。&lt;/p&gt;
    
    </summary>
    
      <category term="翻译" scheme="https://blog.hhking.cn/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
      <category term="JavaScript" scheme="https://blog.hhking.cn/tags/JavaScript/"/>
    
      <category term="闭包" scheme="https://blog.hhking.cn/tags/%E9%97%AD%E5%8C%85/"/>
    
      <category term="执行上下文" scheme="https://blog.hhking.cn/tags/%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/"/>
    
      <category term="Execution Contexts" scheme="https://blog.hhking.cn/tags/Execution-Contexts/"/>
    
      <category term="变量提升" scheme="https://blog.hhking.cn/tags/%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87/"/>
    
      <category term="Hoisting" scheme="https://blog.hhking.cn/tags/Hoisting/"/>
    
      <category term="作用域" scheme="https://blog.hhking.cn/tags/%E4%BD%9C%E7%94%A8%E5%9F%9F/"/>
    
      <category term="Scopes" scheme="https://blog.hhking.cn/tags/Scopes/"/>
    
      <category term="Closures" scheme="https://blog.hhking.cn/tags/Closures/"/>
    
  </entry>
  
  <entry>
    <title>[译] 简单 5 步，理解 JWT</title>
    <link href="https://blog.hhking.cn/2018/10/01/5-easy-steps-to-understanding-json-web-tokens/"/>
    <id>https://blog.hhking.cn/2018/10/01/5-easy-steps-to-understanding-json-web-tokens/</id>
    <published>2018-10-01T12:03:05.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文: <a href="https://medium.com/vandium-software/5-easy-steps-to-understanding-json-web-tokens-jwt-1164c0adfcec" target="_blank" rel="noopener">5 Easy Steps to Understanding JSON Web Tokens (JWT)</a><br>作者: <a href="https://medium.com/@mikeysteckyefantis" target="_blank" rel="noopener">Mikey Stecky-Efantis</a><br>译者: <a href="https://blog.hhking.cn/">hhking</a></p></blockquote><p><img src="https://ws1.sinaimg.cn/large/006tNc79gy1fvqfflj9rkj31jk0q1tcx.jpg" alt=""></p><p>这篇文章，将会解释 JSON Web Tokens (JWT) 的基本原理以及为什么使用它们。JWT 是确保你的应用程序可信任和安全的重要部分。JWT 允许以安全的方式来表示信息，例如用户数据。</p><a id="more"></a><p>为了解释 JWT 的工作原理，我们从抽象的定义开始：</p><blockquote><p>JSON Web Token (JWT) 是一个 JSON 对象, 在 <a href="https://tools.ietf.org/html/rfc7519" target="_blank" rel="noopener">RFC 7519</a> 中定义为一种用来表示双方信息集的安全方式。Token 由头部 (header)、负载 (payload)、签名 (signature) 组成。</p></blockquote><p>简单的说，JWT 只是形如下面格式的字符串：<br></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">header.payload.signature</span><br></pre></td></tr></table></figure><p></p><p><em>需要注意的是，双引号字符串也是合法的 JSON 对象</em></p><p>为了说明如何使用和为什么使用 JWT，我们将用一个简单的 3 实体示例（参见下图）。例子里的三个实体分别是用户（user）、应用服务器（application server）、认证服务器（authentication server）。认证服务器提供 JWT 给用户，然后用户可以安全的和应用通信。</p><p><img src="https://ws3.sinaimg.cn/large/006tNc79gy1fvqhw0cokfj318g0ukq4q.jpg" alt=""></p><p><center>应用使用 JWT 校验用户真实性的过程</center><br></p><p>在这个例子中，用户首先通过认证服务器提供的登录系统（例如：用户名和密码，Facebook 登录，Google 登录，等等）登录认证服务器。然后认证服务器生成 JWT 并返回给用户。当用户向应用服务器发起 API 请求时，会附带上 JWT。在这一步中，应用服务器会配置成去验证传入的 JWT 是否由认证服务器生成的（验证过程将在后面详细解释）。所以，当用户携带 JWT 发起 API 请求时，应用可以通过 JWT 来校验 API 请求是否来自认证的用户。</p><p>现在，我们将更加深入的研究 JWT 本身及其构建和验证的方式。</p><h2 id="Step-1-创建-HEADER"><a href="#Step-1-创建-HEADER" class="headerlink" title="Step 1. 创建 HEADER"></a>Step 1. 创建 HEADER</h2><p>JWT 的头部（header）模块包含了如何计算 JWT 签名的信息。header 是个 JSON 对象，格式如下：<br></p><figure class="highlight plain"><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">&#123;</span><br><span class="line">    &quot;typ&quot;: &quot;JWT&quot;,</span><br><span class="line">    &quot;alg&quot;: &quot;HS256&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>这个 JSON 中，”type” 字段的值指明这个对象是个 JWT，”alg” 字段的值说明用什么 hash 算法来生成 JWT 签名模块。例子中，我们使用的是 HMAC-SHA256 算法 —— 带有密钥的 hash 算法，来计算签名（在 step 3 详细说明）。</p><h2 id="Step-2-创建-PAYLOAD"><a href="#Step-2-创建-PAYLOAD" class="headerlink" title="Step 2. 创建 PAYLOAD"></a>Step 2. 创建 PAYLOAD</h2><p>JWT 的负载（payload）模块是存储在其中的数据（这个数据也被称为 JWT 的 “声明”）。在例子中，认证服务器生成 JWT，JWT 中保存了用户的信息，具体的说是用户 ID。</p><figure class="highlight plain"><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">&#123;</span><br><span class="line">    &quot;userId&quot;: &quot;b08f86af-35da-48f2-8fab-cef3904660bd&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>例子中，我们在 payload 中只保存了一个声明。你想要的话，也可以添加更多的声明。JWT 的 payload 有几种不同的标准声明，例如：“iss” 表示发行人, “sub” 表示主题, 以及 “exp” 表示过期时间。这些字段在创建 JWT 时非常有用，并且是可选的字段。可以在 <a href="https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields" target="_blank" rel="noopener">wikipedia page</a> 上查看详细的 JWT 标准 字段列表。</p><p>要记住，数据的大小会影响整个 JWT 的大小，整个一般不是问题，但是太大的 JWT 可能会对性能有负面影响并导致延迟。</p><h2 id="Step-3-创建-SIGNATURE"><a href="#Step-3-创建-SIGNATURE" class="headerlink" title="Step 3. 创建 SIGNATURE"></a>Step 3. 创建 SIGNATURE</h2><p>签名（signature）是有下面的伪代码计算得到的：</p><figure class="highlight plain"><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">// signature algorithm</span><br><span class="line">data = base64urlEncode( header ) + “.” + base64urlEncode( payload )</span><br><span class="line">hashedData = hash( data, secret )</span><br><span class="line">signature = base64urlEncode( hashedData )</span><br></pre></td></tr></table></figure><p>这个算法做的是：对步骤 1 和 2 生成的 header 和 payload 进行 <a href="http://kjur.github.io/jsjws/tool_b64uenc.html" target="_blank" rel="noopener">base64url encodes</a> 编码，然后把编码生成的字符串中间通过句号（.）连接起来。在伪代码中，把连接的字符串赋值给 <code>data</code>。通过 JWT header 中定义的 hash 算法，加上密钥对 <code>data</code> 字符串 <a href="https://en.wikipedia.org/wiki/Hash_function" target="_blank" rel="noopener">hash</a> 处理。把 hash 后的数据赋值给 <code>hashedData</code>。这个 hash 后的数据通过 base64url 编码生成 JWT 签名（signature）。</p><p>我们的例子中，header 和 payload 被 base64url 编码成：<br></p><figure class="highlight plain"><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">// header</span><br><span class="line">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9</span><br><span class="line">// payload</span><br><span class="line">eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ</span><br></pre></td></tr></table></figure><p></p><p>然后，将编译后的 header 和 payload 进行拼接，使用指定的签名算法和密钥对它计算，得到签名需要的 hash 后的数据。在我们例子中，也就是使用 HS256 算法，密钥为字符串 <code>secret</code>，对字符串 <code>data</code> 计算得到字符串 <code>hashedData</code>。然后，通过 base64url 对字符串 <code>hashedData</code> 编码得到下面的 JWT 签名（signature）：</p><figure class="highlight plain"><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">// signature</span><br><span class="line">-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM</span><br></pre></td></tr></table></figure><h2 id="Step-4-将-JWT-三个模块合并"><a href="#Step-4-将-JWT-三个模块合并" class="headerlink" title="Step 4. 将 JWT 三个模块合并"></a>Step 4. 将 JWT 三个模块合并</h2><p>现在，我们已经创建了所有的三个模块，可以生成 JWT 了。记住 JWT 的结构 <code>header.payload.signature</code>，我们简单的使用句点（.）作为分隔来合并他们。我们使用 base64url 编码后的 header 和 payload，以及在 Step 3 得到的 signature：</p><figure class="highlight plain"><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">// JWT Token</span><br><span class="line">eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM</span><br></pre></td></tr></table></figure><p>你可以使用 <a href="https://jwt.io/" target="_blank" rel="noopener">jwt.io</a> 去创建你的 JWT。</p><p>回到我们的例子，现在认证服务器可以把这个 JWT 发送给用户。</p><h3 id="JWT-是如何保护数据的"><a href="#JWT-是如何保护数据的" class="headerlink" title="JWT 是如何保护数据的"></a>JWT 是如何保护数据的</h3><p>重要的是要理解，使用 JWT 的目的不是以任何方式隐藏或者模糊数据。使用 JWT 的目的是为了证明发送的数据是由可信的源创建的。</p><p>正如前面的步骤所示，JWT 里的数据被编码、签名，但是没有加密。编码数据的目的是为了转化数据的结构。签名数据是可以让数据接收者校验数据来源的可靠性。所以编码和签名数据并没有保护数据。而另一方面，加密的主要目的是保护数据和防止未授权访问。关于编码和加密的不同之处的详细解释，以及更多关于 hash 的原理，可以看 <a href="https://danielmiessler.com/study/encoding-encryption-hashing-obfuscation/#encoding" target="_blank" rel="noopener">这篇文章</a>。</p><blockquote><p>由于 JWT 只是签名和编码数据，没有加密，所有 JWT 不保证敏感数据的任何安全性。</p></blockquote><h2 id="Step-5-校验-JWT"><a href="#Step-5-校验-JWT" class="headerlink" title="Step 5. 校验 JWT"></a>Step 5. 校验 JWT</h2><p>在我们的简单 3 实体实例中，我们使用的 JWT 是由 HS256 算法签名 —— 这个算法的密钥只有认证服务器和应用服务器知道。当应用服务器设置他的认证过程时，应用服务器从认证服务器那接收密钥。由于应用知道密钥，当用户向应用发起携带 JWT 的 API 请求时，应用可以执行和 Step 3 一样的签名算法。然后应用可以校验它自己 hash 操作生成的签名是否和 JWT 中的签名匹配（即它匹配认证服务器创建的 JWT 签名）。如果签名匹配，意味着 JWT 是合法的，标明 API 请求是来自可靠的来源。否则，如果签名不匹配，意味着接收的 JWT 是无效的，这也可能说明应用遭受潜在的攻击。所以，通过校验 JWT，应用在和用户之间建立了信任。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>我们了解了 JWT 是什么，如何创建和校验它，以及如何用它来建立应用和用户的信任。这是理解 JWT 的基本原理和它的作用的起点。JWT 只是确保应用中信任和安全性的难题之一。</p><p>需要指出的是，本文所述的 JWT 认证设置使用的是对称密钥算法（HS256）。你也可以用类似的方式来设置你的 JWT 认证，只是使用非对称算法（例如 RS256）—— 认证服务器有一个密钥，应用有一个公钥。关于使用对称和非对称算法的不同之处的详细分析，可以查看 <a href="https://stackoverflow.com/questions/39239051/rs256-vs-hs256-whats-the-difference" target="_blank" rel="noopener">这个 Stack Overflow 上的问题</a>。</p><p>同时要说明的是，JWT 必须通过 HTTPS 连接（不是 HTTP）来发送。使用 HTTPS 可以防止未授权用户窃取发送的 JWT，从而无法拦截服务端和用户之间的通信。</p><p>还有就是，给你的 JWT payload 设置有效期 —— 特别是很短的有效期，这个很重要，从而如果旧的 JWT 被泄漏，它可能已经无效并无法在使用了。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;原文: &lt;a href=&quot;https://medium.com/vandium-software/5-easy-steps-to-understanding-json-web-tokens-jwt-1164c0adfcec&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;5 Easy Steps to Understanding JSON Web Tokens (JWT)&lt;/a&gt;&lt;br&gt;作者: &lt;a href=&quot;https://medium.com/@mikeysteckyefantis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Mikey Stecky-Efantis&lt;/a&gt;&lt;br&gt;译者: &lt;a href=&quot;https://blog.hhking.cn/&quot;&gt;hhking&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;img src=&quot;https://ws1.sinaimg.cn/large/006tNc79gy1fvqfflj9rkj31jk0q1tcx.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;&lt;p&gt;这篇文章，将会解释 JSON Web Tokens (JWT) 的基本原理以及为什么使用它们。JWT 是确保你的应用程序可信任和安全的重要部分。JWT 允许以安全的方式来表示信息，例如用户数据。&lt;/p&gt;
    
    </summary>
    
      <category term="翻译" scheme="https://blog.hhking.cn/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="JSON Web Tokens" scheme="https://blog.hhking.cn/tags/JSON-Web-Tokens/"/>
    
      <category term="JWT" scheme="https://blog.hhking.cn/tags/JWT/"/>
    
  </entry>
  
  <entry>
    <title>React 高阶组件其实很简单</title>
    <link href="https://blog.hhking.cn/2018/09/28/react-high-order-components/"/>
    <id>https://blog.hhking.cn/2018/09/28/react-high-order-components/</id>
    <published>2018-09-28T11:23:34.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>高阶组件 High-Order Components，不属于 React 的 API，而是 React 的一种运用技巧，或者说设计模式。所以，不使用高阶组件，也是可以实现功能，但是掌握高阶组件，可以提高代码的灵活性和复用性，实现起来更加优雅。当然，学会 React 的高级运用，也可以让你更加深入的理解 React。</p><p>高阶组件，其实没有想象的那么复杂。</p><a id="more"></a><h2 id="什么是高阶组件"><a href="#什么是高阶组件" class="headerlink" title="什么是高阶组件"></a>什么是高阶组件</h2><p><strong>高阶组件是一个函数，接收一个组件，然后返回一个新的组件。</strong><br></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> EnhancedComponent = highOrderComponent(WrappedComponent);</span><br></pre></td></tr></table></figure><p></p><p>要记住的是，虽然名称是<strong>高阶组件</strong>，但是高阶组件不是组件，而是<strong>函数</strong>！</p><p>既然是函数，那就可以有参数，有返回值。从上面可以看出，这个函数接收一个组件 <code>WrappedComponent</code> 作为参数 ，返回<strong>加工过</strong>的新组件 <code>EnhancedComponent</code>。其实高阶组件就是设计模式里的装饰者模式。</p><p>可以说，组件是把 props 转化成 UI，而高阶组件是把一个组件转化成另外一个组件。</p><p>下面是一个简单的高阶组件：<br></p><figure class="highlight jsx"><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">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> (WrappedComponent) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="class"><span class="keyword">class</span> <span class="title">EnhancedComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">    render() &#123;</span><br><span class="line">      <span class="keyword">return</span> &lt;WrappedComponent /&gt;;</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></p><p>从上面的代码可以看出，我们可以对传入的原始组件 <code>WrappedComponent</code> 做一些你想要的操作（比如操作 props，提取 state，给原始组件包裹其他元素等），从而<strong>加工</strong>出你想要的组件 <code>EnhancedComponent</code> 。把通用的逻辑放在高阶组件中，对组件实现一致的处理，从而实现代码的复用。</p><h2 id="使用高阶组件"><a href="#使用高阶组件" class="headerlink" title="使用高阶组件"></a>使用高阶组件</h2><p>高阶组件的一个应用场景，就是在表单中的使用。</p><p>React 中 <a href="https://reactjs.org/docs/forms.html" target="_blank" rel="noopener">受控组件</a>，是通过 <code>state</code> 来控制用户的输入行为，所以我们需要通过监听 <code>onChange</code> 来更新 <code>state</code>，例如：<br></p><figure class="highlight jsx"><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> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Input</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(props) &#123;</span><br><span class="line">    <span class="keyword">super</span>(props);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.state = &#123;</span><br><span class="line">      value: <span class="string">''</span>,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  handleChange = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">      value: e.target.value,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;input type=<span class="string">"text"</span> value=&#123;<span class="keyword">this</span>.state.value&#125; onChange=&#123;<span class="keyword">this</span>.handleChange&#125; /&gt;</span><br><span class="line">    );</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">export</span> <span class="keyword">default</span> Input;</span><br></pre></td></tr></table></figure><p></p><p>通常，我们还需要对表单元素进行验证，这时可能需要在 <code>handleChange</code> 中添加验证的逻辑。如果是一个表单元素，这样写没什么问题，但是当表单元素越来越多，还是这样一个一个元素的添加逻辑的话，是一种什么体验？如果产品再改一下需求，那你就只能含泪加班了！</p><p>这时候，高阶组件的作用就可以体现出来了。只需要把所有表单元素通用的逻辑放在高阶组件中。把输入框需要的 <code>value</code> 和 <code>onChange</code> 提取到高阶组件，再通过 <code>props</code> 传给原始的组件：</p><figure class="highlight jsx"><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="keyword">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> (WrappedComponent) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="class"><span class="keyword">class</span> <span class="title">InputHOC</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="keyword">constructor</span>(props) &#123;</span><br><span class="line">      <span class="keyword">super</span>(props);</span><br><span class="line">      <span class="keyword">this</span>.state = &#123;</span><br><span class="line">        value: <span class="string">''</span>,</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    handleChange = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">        value: e.target.value,</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    render() &#123;</span><br><span class="line">      <span class="keyword">const</span> passProps = &#123;</span><br><span class="line">        value: <span class="keyword">this</span>.state.value,</span><br><span class="line">        onChange: <span class="keyword">this</span>.handleChange,</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> &lt;WrappedComponent &#123;...passProps&#125; /&gt;;</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>state</code> 和 <code>onChange</code> 的逻辑，只需要处理传入的 <code>props</code>, 并调用一下<strong>高阶组件</strong>，得到的就是带有我们需要的属性和功能的<strong>新组件</strong>：<br></p><figure class="highlight jsx"><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">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> InputHOC <span class="keyword">from</span> <span class="string">'./InputHOC'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">'./style.css'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Input</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;input className=<span class="string">"input"</span> type=<span class="string">"text"</span> &#123;...this.props&#125; /&gt;</span><br><span class="line">    )</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">export</span> <span class="keyword">default</span> InputHOC(Input);</span><br></pre></td></tr></table></figure><p></p><p>然后你只要拿新的组件去使用，就可以了。如果还有其他的表单元素，同样只需要调用高阶组件，就可以得到你需要的组件。这就是一个简单的高阶组件的运用，实现代码的复用。</p><p>从上面的例子可以看出，在高阶组件里，你可以操作 <code>props</code>、<code>state</code>，甚至修改 <code>render</code> 方法里的渲染内容。</p><p>高阶组件的模式和数据流实际如图：<br><img src="/images/react-hoc/react-hoc1.jpg" alt=""></p><h2 id="一些注意点"><a href="#一些注意点" class="headerlink" title="一些注意点"></a>一些注意点</h2><h3 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h3><p>既然高级组件是一个函数，那么这个函数也可以有多个参数，例如：<br></p><figure class="highlight jsx"><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">export</span> <span class="keyword">default</span> (WrappedComponent, data) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="class"><span class="keyword">class</span> <span class="title">EnhancedComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="comment">// do something with data</span></span><br><span class="line">    render() &#123;</span><br><span class="line">      <span class="keyword">return</span> &lt;WrappedComponent /&gt;;</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></p><h3 id="命名"><a href="#命名" class="headerlink" title="命名"></a>命名</h3><p>高阶组件的创建的容器组件的名称在 React Developer Tools 中看起来和普通组件一样，看不出是不是使用了高阶组件。<br><img src="/images/react-hoc/react-hoc2.jpg" alt=""></p><p>为了方便调试，一般约定用容器组件类名包裹原始组件来命名容器组件：<br></p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> getDisplayName = <span class="function">(<span class="params">WrappedComponent</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> WrappedComponent.displayName ||</span><br><span class="line">    WrappedComponent.name ||</span><br><span class="line">    <span class="string">'Component'</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> (WrappedComponent, validate) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="class"><span class="keyword">class</span> <span class="title">InputHOC</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> displayName = <span class="string">`InputHOC(<span class="subst">$&#123;getDisplayName(WrappedComponent)&#125;</span>)`</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(props) &#123;</span><br><span class="line">      <span class="keyword">super</span>(props);</span><br><span class="line">      <span class="keyword">this</span>.state = &#123;</span><br><span class="line">        value: <span class="string">''</span>,</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    handleChange = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(e);</span><br><span class="line">      <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">        value: e.target.value,</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    render() &#123;</span><br><span class="line">      <span class="keyword">const</span> passProps = &#123;</span><br><span class="line">        value: <span class="keyword">this</span>.state.value,</span><br><span class="line">        onChange: <span class="keyword">this</span>.handleChange,</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> &lt;WrappedComponent &#123;...passProps&#125; /&gt;;</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></p><p>结果如下，这样就可以直观的看出是使用了高阶组件：<br><img src="/images/react-hoc/react-hoc3.jpg" alt=""></p><h3 id="多层高阶组件"><a href="#多层高阶组件" class="headerlink" title="多层高阶组件"></a>多层高阶组件</h3><p>可以对一个组件，应用多个高阶组件，形成多层嵌套：<br></p><figure class="highlight jsx"><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">Input = InputHOC(Input);</span><br><span class="line">Input = InputHHOC(Input);</span><br></pre></td></tr></table></figure><p></p><p><img src="/images/react-hoc/react-hoc4.jpg" alt=""></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>高阶组件是属于 React 高级运用，但是其实是一个很简单的概念，但是它非常实用。在实际的业务场景中，灵活合理的使用高阶组件，可以提高代码的复用性和灵活性。</p><p>对高阶组件，我们可以总结以下几点：</p><ul><li>高阶组件是一个函数，而不是组件</li><li>组件是把 props 转化成 UI，高阶组件是把一个组件转化成另一个组件</li><li>高阶组件的作用是复用代码</li><li>高阶组件对应设计模式里的装饰者模式</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;高阶组件 High-Order Components，不属于 React 的 API，而是 React 的一种运用技巧，或者说设计模式。所以，不使用高阶组件，也是可以实现功能，但是掌握高阶组件，可以提高代码的灵活性和复用性，实现起来更加优雅。当然，学会 React 的高级运用，也可以让你更加深入的理解 React。&lt;/p&gt;&lt;p&gt;高阶组件，其实没有想象的那么复杂。&lt;/p&gt;
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="React" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/React/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="React" scheme="https://blog.hhking.cn/tags/React/"/>
    
      <category term="高阶组件" scheme="https://blog.hhking.cn/tags/%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6/"/>
    
      <category term="High-Order Components" scheme="https://blog.hhking.cn/tags/High-Order-Components/"/>
    
  </entry>
  
  <entry>
    <title>[译] 5 分钟学习一些优雅的 JavaScript 技巧</title>
    <link href="https://blog.hhking.cn/2018/09/21/9-neat-javascript-tricks/"/>
    <id>https://blog.hhking.cn/2018/09/21/9-neat-javascript-tricks/</id>
    <published>2018-09-21T09:49:44.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="https://medium.freecodecamp.org/9-neat-javascript-tricks-e2742f2735c3" target="_blank" rel="noopener">Learn these neat JavaScript tricks in less than 5 minutes</a><br>作者：Alcides Queiroz</p></blockquote><p><img src="https://i.loli.net/2019/05/26/5ce9e86f1eff798032.png" alt=""><br><strong>5 分钟学习一些优雅的 JavaScript 技巧 —— 专业的省时技巧</strong></p><a id="more"></a><h2 id="1-清空或截取数组"><a href="#1-清空或截取数组" class="headerlink" title="1. 清空或截取数组"></a>1. 清空或截取数组</h2><p>一个简单的清空或者截取数组的方法，就是修改它的 <code>length</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">11</span>, <span class="number">22</span>, <span class="number">33</span>, <span class="number">44</span>, <span class="number">55</span>, <span class="number">66</span>];</span><br><span class="line"><span class="comment">// 截取</span></span><br><span class="line">arr.length = <span class="number">3</span>;</span><br><span class="line"><span class="built_in">console</span>.log(arr); <span class="comment">//=&gt; [11, 22, 33]</span></span><br><span class="line"><span class="comment">// 清空</span></span><br><span class="line">arr.length = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">console</span>.log(arr); <span class="comment">//=&gt; []</span></span><br><span class="line"><span class="built_in">console</span>.log(arr[<span class="number">2</span>]); <span class="comment">//=&gt; undefined</span></span><br></pre></td></tr></table></figure><h2 id="2-用对象解构来模拟参数命名"><a href="#2-用对象解构来模拟参数命名" class="headerlink" title="2.用对象解构来模拟参数命名"></a>2.用对象解构来模拟参数命名</h2><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></pre></td><td class="code"><pre><span class="line">doSomething(&#123; <span class="attr">foo</span>: <span class="string">'Hello'</span>, <span class="attr">bar</span>: <span class="string">'Hey!'</span>, <span class="attr">baz</span>: <span class="number">42</span> &#125;);</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">config</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> foo = config.foo !== <span class="literal">undefined</span> ? config.foo : <span class="string">'Hi'</span>;</span><br><span class="line">  <span class="keyword">const</span> bar = config.bar !== <span class="literal">undefined</span> ? config.bar : <span class="string">'Yo!'</span>;</span><br><span class="line">  <span class="keyword">const</span> baz = config.baz !== <span class="literal">undefined</span> ? config.baz : <span class="number">13</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 JavaScript 中模拟命名参数，是一个陈旧但是很有效的方式。这样函数调用看起来舒服多了。另一方面，配置对象的控制逻辑也不需要这么冗长，使用 ES2015 的对象解构，可以避免这个问题：</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="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">&#123; foo = <span class="string">'Hi'</span>, bar = <span class="string">'Yo!'</span>, baz = <span class="number">13</span> &#125;</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">doSomething</span>(<span class="params">&#123; foo = <span class="string">'Hi'</span>, bar = <span class="string">'Yo!'</span>, baz = <span class="number">13</span> &#125; = &#123;&#125;</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-对数组使用对象解构"><a href="#3-对数组使用对象解构" class="headerlink" title="3.对数组使用对象解构"></a>3.对数组使用对象解构</h2><p>使用对象解构把数组项赋值给特定的变量：</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">const</span> csvFileLine = <span class="string">'1997,John Doe,US,john@doe.com,New York'</span>;</span><br><span class="line"><span class="keyword">const</span> &#123; <span class="number">2</span>: country, <span class="number">4</span>: state &#125; = csvFileLine.split(<span class="string">','</span>);</span><br></pre></td></tr></table></figure><blockquote><p>译注：这里把数组下标为 2 的值（<code>US</code>）赋给变量 <code>country</code>; 下标为 4 的值（<code>New York</code>）赋给变量 <code>state</code>。</p></blockquote><h2 id="4-switch-中使用范围"><a href="#4-switch-中使用范围" class="headerlink" title="4. switch 中使用范围"></a>4. switch 中使用范围</h2><blockquote><p>注意：经过一番思考，我决定把这个技巧和文中其他的区别开来。这个并不是一个节约时间的技巧，在真正代码中也不适合使用。记住：这种场景下使用 <code>if</code> 通常会更好。<br>和文中其他的技巧不同，这个方法主要是为了好奇而不是实用。<br>总之，由于历史原因我会保留这个技巧在这篇文章中。</p></blockquote><p>这里有个简单的在 switch 语句中使用范围的技巧：</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="function"><span class="keyword">function</span> <span class="title">getWaterState</span>(<span class="params">tempInCelsius</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> state;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">switch</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> (tempInCelsius &lt;= <span class="number">0</span>): </span><br><span class="line">      state = <span class="string">'Solid'</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> (tempInCelsius &gt; <span class="number">0</span> &amp;&amp; tempInCelsius &lt; <span class="number">100</span>): </span><br><span class="line">      state = <span class="string">'Liquid'</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>: </span><br><span class="line">      state = <span class="string">'Gas'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> state;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5-使用-async-await-时，await-多个-async-函数"><a href="#5-使用-async-await-时，await-多个-async-函数" class="headerlink" title="5. 使用 async/await 时，await 多个 async 函数"></a>5. 使用 <code>async/await</code> 时，<code>await</code> 多个 <code>async</code> 函数</h2><p>可以使用 <code>Promise.all</code> 来 <code>await</code> 多个 async 函数执行完成：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">await</span> <span class="built_in">Promise</span>.all([anAsyncCall(), thisIsAlsoAsync(), oneMore()])</span><br></pre></td></tr></table></figure><h2 id="6-创建纯对象"><a href="#6-创建纯对象" class="headerlink" title="6. 创建纯对象"></a>6. 创建纯对象</h2><p>你可以创建 100% 的纯对象（pure object）, 不继承 <code>Object</code> 的任何属性和方法（例如：<code>constructor</code>、<code>toString()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pureObject = <span class="built_in">Object</span>.create(<span class="literal">null</span>);</span><br><span class="line"><span class="built_in">console</span>.log(pureObject); <span class="comment">//=&gt; &#123;&#125;</span></span><br><span class="line"><span class="built_in">console</span>.log(pureObject.constructor); <span class="comment">//=&gt; undefined</span></span><br><span class="line"><span class="built_in">console</span>.log(pureObject.toString); <span class="comment">//=&gt; undefined</span></span><br><span class="line"><span class="built_in">console</span>.log(pureObject.hasOwnProperty); <span class="comment">//=&gt; undefined</span></span><br></pre></td></tr></table></figure><h2 id="7-格式化-JSON-代码"><a href="#7-格式化-JSON-代码" class="headerlink" title="7. 格式化 JSON 代码"></a>7. 格式化 JSON 代码</h2><p><code>JSON.stringify</code> 能做的不只是简单的字符串化对象，你还可以使用它来格式化 JSON 的输出：</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">const</span> obj = &#123; </span><br><span class="line">  foo: &#123; <span class="attr">bar</span>: [<span class="number">11</span>, <span class="number">22</span>, <span class="number">33</span>, <span class="number">44</span>], <span class="attr">baz</span>: &#123; <span class="attr">bing</span>: <span class="literal">true</span>, <span class="attr">boom</span>: <span class="string">'Hello'</span> &#125; &#125; </span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// The third parameter is the number of spaces used to </span></span><br><span class="line"><span class="comment">// beautify the JSON output.</span></span><br><span class="line"><span class="built_in">JSON</span>.stringify(obj, <span class="literal">null</span>, <span class="number">4</span>); </span><br><span class="line"><span class="comment">// =&gt;"&#123;</span></span><br><span class="line"><span class="comment">// =&gt;    "foo": &#123;</span></span><br><span class="line"><span class="comment">// =&gt;        "bar": [</span></span><br><span class="line"><span class="comment">// =&gt;            11,</span></span><br><span class="line"><span class="comment">// =&gt;            22,</span></span><br><span class="line"><span class="comment">// =&gt;            33,</span></span><br><span class="line"><span class="comment">// =&gt;            44</span></span><br><span class="line"><span class="comment">// =&gt;        ],</span></span><br><span class="line"><span class="comment">// =&gt;        "baz": &#123;</span></span><br><span class="line"><span class="comment">// =&gt;            "bing": true,</span></span><br><span class="line"><span class="comment">// =&gt;            "boom": "Hello"</span></span><br><span class="line"><span class="comment">// =&gt;        &#125;</span></span><br><span class="line"><span class="comment">// =&gt;    &#125;</span></span><br><span class="line"><span class="comment">// =&gt;&#125;"</span></span><br></pre></td></tr></table></figure><h2 id="8-数组去重"><a href="#8-数组去重" class="headerlink" title="8. 数组去重"></a>8. 数组去重</h2><p>只使用 ES2015 的 <code>Set</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> removeDuplicateItems = <span class="function"><span class="params">arr</span> =&gt;</span> [...new <span class="built_in">Set</span>(arr)];</span><br><span class="line">removeDuplicateItems([<span class="number">42</span>, <span class="string">'foo'</span>, <span class="number">42</span>, <span class="string">'foo'</span>, <span class="literal">true</span>, <span class="literal">true</span>]);</span><br><span class="line"><span class="comment">//=&gt; [42, "foo", true]</span></span><br></pre></td></tr></table></figure><h2 id="9-扁平化多维数组"><a href="#9-扁平化多维数组" class="headerlink" title="9.扁平化多维数组"></a>9.扁平化多维数组</h2><p>使用展开运算符可以轻松的扁平化数组：</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">const</span> arr = [<span class="number">11</span>, [<span class="number">22</span>, <span class="number">33</span>], [<span class="number">44</span>, <span class="number">55</span>], <span class="number">66</span>];</span><br><span class="line"><span class="keyword">const</span> flatArr = [].concat(...arr); <span class="comment">//=&gt; [11, 22, 33, 44, 55, 66]</span></span><br></pre></td></tr></table></figure><p>可惜的是，上面的这个技巧只对二维数组有效。但是通过递归，我们可以使它也适用于二维以上的数组：<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="function"><span class="keyword">function</span> <span class="title">flattenArray</span>(<span class="params">arr</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> flattened = [].concat(...arr);</span><br><span class="line">  <span class="keyword">return</span> flattened.some(<span class="function"><span class="params">item</span> =&gt;</span> <span class="built_in">Array</span>.isArray(item)) ? </span><br><span class="line">    flattenArray(flattened) : flattened;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> arr = [<span class="number">11</span>, [<span class="number">22</span>, <span class="number">33</span>], [<span class="number">44</span>, [<span class="number">55</span>, <span class="number">66</span>, [<span class="number">77</span>, [<span class="number">88</span>]], <span class="number">99</span>]]];</span><br><span class="line"><span class="keyword">const</span> flatArr = flattenArray(arr); </span><br><span class="line"><span class="comment">//=&gt; [11, 22, 33, 44, 55, 66, 77, 88, 99]</span></span><br></pre></td></tr></table></figure><p></p><p>就这些啦！希望这些优雅的小技巧可以帮你写出更好更漂亮的 JavaScript。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;原文：&lt;a href=&quot;https://medium.freecodecamp.org/9-neat-javascript-tricks-e2742f2735c3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Learn these neat JavaScript tricks in less than 5 minutes&lt;/a&gt;&lt;br&gt;作者：Alcides Queiroz&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;img src=&quot;https://i.loli.net/2019/05/26/5ce9e86f1eff798032.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;strong&gt;5 分钟学习一些优雅的 JavaScript 技巧 —— 专业的省时技巧&lt;/strong&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="翻译" scheme="https://blog.hhking.cn/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="JavaScript" scheme="https://blog.hhking.cn/tags/JavaScript/"/>
    
      <category term="经验分享" scheme="https://blog.hhking.cn/tags/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/"/>
    
  </entry>
  
  <entry>
    <title>重新认识 React 生命周期</title>
    <link href="https://blog.hhking.cn/2018/09/18/react-lifecycle-change/"/>
    <id>https://blog.hhking.cn/2018/09/18/react-lifecycle-change/</id>
    <published>2018-09-18T11:48:51.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>React 从 v16 开始，像是跨入了新的时代，性能和新的 API 都令人瞩目。重新认识 React，从重新认识生命周期开始。</p><p>为了更好的支持异步渲染（Async Rendering），解决一些生命周期滥用可能导致的问题，React 从 V16.3 开始，对生命周期进行渐进式调整，同时在官方文档也提供了使用的最佳实践。</p><p>这里我们将简要对比 React 新旧生命周期，重新认识一下 React 生命周期。</p><a id="more"></a><h1 id="新的生命周期"><a href="#新的生命周期" class="headerlink" title="新的生命周期"></a>新的生命周期</h1><p>先看看两张经典的生命周期的示意图<br><img src="/images/react-life-cycle/react-life-cycle-old.png" alt="旧的生命周期"></p><p><center>旧的生命周期</center><br><br></p><p><img src="/images/react-life-cycle/react-life-cycle-new.jpg" alt="新的生命周期"></p><p><center>新的生命周期</center><br><br></p><p>React 16.3 新增的生命周期方法</p><ol><li>getDerivedStateFromProps()</li><li>getSnapshotBeforeUpdate()</li></ol><p>逐渐废弃的生命周期方法：</p><ol><li>componentWillMount()</li><li>componentWillReceiveProps()</li><li>componentWillUpdate()</li></ol><blockquote><p>虽然废弃了这三个生命周期方法，但是为了向下兼容，将会做渐进式调整。（详情见<a href="https://github.com/facebook/react/pull/12028" target="_blank" rel="noopener">#12028</a>）<br>V16.3 并未删除这三个生命周期，同时还为它们新增以 UNSAFE_ 前缀为别名的三个函数 <code>UNSAFE_componentWillMount()</code>、<code>UNSAFE_componentWillReceiveProps()</code>、<code>UNSAFE_componentWillUpdate()</code>。<br>在 16.4 版本给出警告将会弃用 <code>componentWillMount()</code>、<code>componentWillReceiveProps()</code>、<code>componentWillUpdate()</code> 三个函数<br>然后在 17 版本将会删除 <code>componentWillMount()</code>、<code>componentWillReceiveProps()</code>、<code>componentWillUpdate()</code> 这三个函数，会保留使用 <code>UNSAFE_componentWillMount()</code>、<code>UNSAFE_componentWillReceiveProps()</code>、<code>UNSAFE_componentWillUpdate()</code></p></blockquote><p>一般将生命周期分成三个阶段：</p><ol><li>创建阶段（Mounting）</li><li>更新阶段（Updating）</li><li>卸载阶段（Unmounting）</li></ol><p>从 React v16 开始，还对生命周期加入了错误处理（Error Handling）。</p><p>下面分析一下生命周期的各个阶段。</p><h2 id="创建阶段-Mounting"><a href="#创建阶段-Mounting" class="headerlink" title="创建阶段 Mounting"></a>创建阶段 Mounting</h2><p>组件实例创建并插入 DOM 时，按顺序调用以下方法：</p><ul><li>constructor()</li><li>static getDerivedStateFromProps()</li><li><del>componentWillMount()/UNSAFE_componentWillMount()</del>（being deprecated）</li><li>render()</li><li>componentDidMount()</li></ul><blockquote><p>有定义 <code>getDerivedStateFromProps</code> 时，会忽略 <code>componentWillMount()/UNSAFE_componentWillMount()</code><br>（<a href="https://github.com/facebook/react/blob/master/packages/react-dom/src/server/ReactPartialRenderer.js" target="_blank" rel="noopener">详情查看源码</a>）</p></blockquote><h3 id="constructor-NaN"><a href="#constructor-NaN" class="headerlink" title="constructor()"></a>constructor()</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">constructor</span>(props)</span><br></pre></td></tr></table></figure><p>构造函数通常用于：</p><ul><li>使用 <code>this.state</code> 来初始化 <code>state</code></li><li>给事件处理函数绑定 <code>this</code></li></ul><blockquote><p>注意：ES6 子类的构造函数必须执行一次 super()。React 如果构造函数中要使用 this.props，必须先执行 super(props)。</p></blockquote><h3 id="static-getDerivedStateFromProps"><a href="#static-getDerivedStateFromProps" class="headerlink" title="static getDerivedStateFromProps()"></a>static getDerivedStateFromProps()</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> getDerivedStateFromProps(nextProps, prevState)</span><br></pre></td></tr></table></figure><p>当创建时、接收新的 props 时、setState 时、forceUpdate 时会执行这个方法。</p><blockquote><p>注意：v16.3 setState 时、forceUpdate 时不会执行这个方法，v16.4 修复了这个问题。</p></blockquote><p>这是一个 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static" target="_blank" rel="noopener">静态方法</a>，参数 <code>nextProps</code> 是新接收的 <code>props</code>，<code>prevState</code> 是当前的 <code>state</code>。返回值（对象）将用于更新 <code>state</code>，如果不需要更新则需要返回 <code>null</code>。</p><p>下面是官方文档给出的例子<br></p><figure class="highlight jsx"><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="class"><span class="keyword">class</span> <span class="title">ExampleComponent</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>&#123;</span><br><span class="line">  <span class="comment">// Initialize state in constructor,</span></span><br><span class="line">  <span class="comment">// Or with a property initializer.</span></span><br><span class="line">  state = &#123;</span><br><span class="line">    isScrollingDown: <span class="literal">false</span>,</span><br><span class="line">    lastRow: <span class="literal">null</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> getDerivedStateFromProps(props, state) &#123;</span><br><span class="line">    <span class="keyword">if</span> (props.currentRow !== state.lastRow) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        isScrollingDown: props.currentRow &gt; state.lastRow,</span><br><span class="line">        lastRow: props.currentRow,</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">// Return null to indicate no change to state.</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>这个方法的常用作用也很明显了：父组件传入新的 <code>props</code> 时，用来和当前的 <code>state</code> 对比，判断是否需要更新 <code>state</code>。以前一般使用 <code>componentWillReceiveProps</code> 做这个操作。</p><p>这个方法在建议尽量少用，只在必要的场景中使用，一般使用场景如下：</p><ol><li>无条件的根据 <code>props</code> 更新 <code>state</code></li><li>当 <code>props</code> 和 <code>state</code> 的不匹配情况更新 <code>state</code></li></ol><p>详情可以参考官方文档的最佳实践 <a href="http://react.css88.com/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization" target="_blank" rel="noopener">You Probably Don’t Need Derived State</a></p><h3 id="componentWillMount-UNSAFE-componentWillMount-（弃用）"><a href="#componentWillMount-UNSAFE-componentWillMount-（弃用）" class="headerlink" title="componentWillMount()/UNSAFE_componentWillMount()（弃用）"></a>componentWillMount()/UNSAFE_componentWillMount()（弃用）</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UNSAFE_componentWillMount()</span><br></pre></td></tr></table></figure><p>这个方法已经不推荐使用。因为在未来异步渲染机制下，该方法可能会多次调用。它所行使的功能也可以由 <code>componentDidMount()</code> 和 <code>constructor()</code> 代替：</p><ul><li>之前有些人会把异步请求放在这个生命周期，其实大部分情况下都推荐把异步数据请求放在 <code>componentDidMount()</code> 中。</li><li>在服务端渲染时，通常使用 <code>componentWillMount()</code> 获取必要的同步数据，但是可以使用 <code>constructor()</code> 代替它。</li></ul><p><strong>可以使用 setState，不会触发 re-render</strong></p><h3 id="render"><a href="#render" class="headerlink" title="render"></a>render</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">render()</span><br></pre></td></tr></table></figure><p>每个类组件中，<code>render()</code> 唯一必须的方法。</p><p><code>render()</code> 正如其名，作为渲染用，可以返回下面几种类型：</p><ul><li>React 元素（React elements）</li><li>数组（Arrays）</li><li>片段（fragments）</li><li>插槽（Portals）</li><li>字符串或数字（String and numbers）</li><li>布尔值或 null（Booleans or null）</li></ul><blockquote><p>注意：<br>Arrays 和 String 是 v16.0.0 新增。<br>fragments 是 v16.2.0 新增。<br>Portals 是 V16.0.0 新增。</p></blockquote><p>里面不应该包含副作用，应该作为纯函数。</p><p><strong>不能使用 setState。</strong></p><h3 id="componentDidMount"><a href="#componentDidMount" class="headerlink" title="componentDidMount()"></a>componentDidMount()</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">componentDidMount()</span><br></pre></td></tr></table></figure><p>组件完成装载（已经插入 DOM 树）时，触发该方法。这个阶段已经获取到真实的 DOM。</p><p>一般用于下面的场景：</p><ul><li>异步请求 ajax</li><li>添加事件绑定（注意在 <code>componentWillUnmount</code> 中取消，以免造成内存泄漏）</li></ul><p><strong>可以使用 setState，触发re-render，影响性能。</strong></p><h2 id="更新阶段-Updating"><a href="#更新阶段-Updating" class="headerlink" title="更新阶段 Updating"></a>更新阶段 Updating</h2><ul><li><del>componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()</del>（being deprecated）</li><li>static getDerivedStateFromProps()</li><li>shouldComponentUpdate()</li><li><del>componentWillUpdate()/UNSAFE_componentWillUpdate()</del>（being deprecated）</li><li>render()</li><li>getSnapshotBeforeUpdate()</li><li>componentDidUpdate()</li></ul><blockquote><p>有 <code>getDerivedStateFromProps</code> 或者 <code>getSnapshotBeforeUpdate</code> 时，<code>componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()</code> 和 <code>componentWillUpdate()/UNSAFE_componentWillUpdate()</code> 不会执行 （<a href="https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberClassComponent.js" target="_blank" rel="noopener">详情查看源码</a>）</p></blockquote><h3 id="componentWillReceiveProps-UNSAFE-componentWillReceiveProps-（弃用）"><a href="#componentWillReceiveProps-UNSAFE-componentWillReceiveProps-（弃用）" class="headerlink" title="componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()（弃用）"></a>componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()（弃用）</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UNSAFE_componentWillReceiveProps(nextProps)</span><br></pre></td></tr></table></figure><p>这个方法在接收新的 props 时调用，需要注意的是，如果父组件导致组件重新渲染，即使 props 没有更改，也会调用此方法。</p><p>一般用这个方法来判断 props 的前后变化来更新 <code>state</code>，如下面的例子：</p><figure class="highlight jsx"><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="class"><span class="keyword">class</span> <span class="title">ExampleComponent</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>&#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    isScrollingDown: <span class="literal">false</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  componentWillReceiveProps(nextProps) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">this</span>.props.currentRow !== nextProps.currentRow) &#123;</span><br><span class="line">      <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">        isScrollingDown:</span><br><span class="line">          nextProps.currentRow &gt; <span class="keyword">this</span>.props.currentRow,</span><br><span class="line">      &#125;);</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>getDerivedStateFromProps</code> 代替。</p><p><strong>可以使用 setState</strong></p><h3 id="static-getDerivedStateFromProps-1"><a href="#static-getDerivedStateFromProps-1" class="headerlink" title="static getDerivedStateFromProps()"></a>static getDerivedStateFromProps()</h3><p>同 Mounting 时所述一致。</p><h3 id="shouldComponentUpdate"><a href="#shouldComponentUpdate" class="headerlink" title="shouldComponentUpdate()"></a>shouldComponentUpdate()</h3><p>在接收新的 <code>props</code> 或新的 <code>state</code> 时，在渲染前会触发该方法。</p><p>该方法通过返回 <code>true</code> 或者 <code>false</code> 来确定是否需要触发新的渲染。返回 <code>false</code>， 则不会触发后续的 <code>UNSAFE_componentWillUpdate()</code>、<code>render()</code> 和 <code>componentDidUpdate()</code>（但是 <code>state</code> 变化还是可能引起子组件重新渲染）。</p><p>所以通常通过这个方法对 <code>props</code> 和 <code>state</code> 做比较，从而避免一些不必要的渲染。</p><blockquote><p>PureComponent 的原理就是对 <code>props</code> 和 <code>state</code> 进行浅对比（shallow comparison），来判断是否触发渲染。</p></blockquote><h3 id="componentWillUpdate-UNSAFE-componentWillUpdate-（弃用）"><a href="#componentWillUpdate-UNSAFE-componentWillUpdate-（弃用）" class="headerlink" title="componentWillUpdate()/UNSAFE_componentWillUpdate() （弃用）"></a>componentWillUpdate()/UNSAFE_componentWillUpdate() （弃用）</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UNSAFE_componentWillUpdate(nextProps, nextState)</span><br></pre></td></tr></table></figure><p>当接收到新的 props 或 state 时，在渲染前执行该方法。</p><p>在以后异步渲染时，可能会出现某些组件暂缓更新，导致 <code>componentWillUpdate</code> 和 <code>componentDidUpdate</code> 之间的时间变长，这个过程中可能发生一些变化，比如用户行为导致 DOM 发生了新的变化，这时在 <code>componentWillUpdate</code> 获取的信息可能就不可靠了。</p><p><strong>不能使用 setState</strong></p><h3 id="render-1"><a href="#render-1" class="headerlink" title="render()"></a>render()</h3><p>同 Mounting 时所述一致。</p><h3 id="getSnapshotBeforeUpdate"><a href="#getSnapshotBeforeUpdate" class="headerlink" title="getSnapshotBeforeUpdate()"></a>getSnapshotBeforeUpdate()</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getSnapShotBeforeUpdate(prevProps, prevState)</span><br></pre></td></tr></table></figure><p>这个方法在 <code>render()</code> 之后，<code>componentDidUpdate()</code> 之前调用。</p><p>两个参数 <code>prevProps</code> 表示更新前的 <code>props</code>，<code>prevState</code> 表示更新前的 <code>state</code>。</p><p>返回值称为一个快照（snapshot），如果不需要 snapshot，则必须显示的返回 <code>null</code> —— 因为返回值将作为 <code>componentDidUpdate()</code> 的第三个参数使用。所以这个函数必须要配合 <code>componentDidUpdate()</code> 一起使用。</p><p>这个函数的作用是在真实 DOM 更新（<code>componentDidUpdate</code>）前，获取一些需要的信息（类似快照功能），然后作为参数传给 <code>componentDidUpdate</code>。例如：在 <code>getSnapShotBeforeUpdate</code> 中获取滚动位置，然后作为参数传给 <code>componentDidUpdate</code>，就可以直接在渲染真实的 DOM 时就滚动到需要的位置。</p><p>下面是官方文档给出的例子：<br></p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ScrollingList</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(props) &#123;</span><br><span class="line">    <span class="keyword">super</span>(props);</span><br><span class="line">    <span class="keyword">this</span>.listRef = React.createRef();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  getSnapshotBeforeUpdate(prevProps, prevState) &#123;</span><br><span class="line">    <span class="comment">// Are we adding new items to the list?</span></span><br><span class="line">    <span class="comment">// Capture the scroll position so we can adjust scroll later.</span></span><br><span class="line">    <span class="keyword">if</span> (prevProps.list.length &lt; <span class="keyword">this</span>.props.list.length) &#123;</span><br><span class="line">      <span class="keyword">const</span> list = <span class="keyword">this</span>.listRef.current;</span><br><span class="line">      <span class="keyword">return</span> list.scrollHeight - list.scrollTop;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  componentDidUpdate(prevProps, prevState, snapshot) &#123;</span><br><span class="line">    <span class="comment">// If we have a snapshot value, we've just added new items.</span></span><br><span class="line">    <span class="comment">// Adjust scroll so these new items don't push the old ones out of view.</span></span><br><span class="line">    <span class="comment">// (snapshot here is the value returned from getSnapshotBeforeUpdate)</span></span><br><span class="line">    <span class="keyword">if</span> (snapshot !== <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> list = <span class="keyword">this</span>.listRef.current;</span><br><span class="line">      list.scrollTop = list.scrollHeight - snapshot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;div ref=&#123;<span class="keyword">this</span>.listRef&#125;&gt;&#123;<span class="comment">/* ...contents... */</span>&#125;&lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">    );</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br></pre></td></tr></table></figure><p></p><h3 id="componentDidUpdate"><a href="#componentDidUpdate" class="headerlink" title="componentDidUpdate()"></a>componentDidUpdate()</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">componentDidUpdate(prevProps, prevState, snapshot)</span><br></pre></td></tr></table></figure><p>这个方法是在更新完成之后调用，第三个参数 <code>snapshot</code> 就是 <code>getSnapshotBeforeUpdate</code> 的返回值。</p><p>正如前面所说，有 <code>getSnapshotBeforeUpdate</code> 时，必须要有 <code>componentDidUpdate</code>。所以这个方法的一个应用场景就是上面看到的例子，配合 <code>getSnapshotBeforeUpdate</code> 使用。</p><p><strong>可以使用 setState，会触发 re-render，所以要注意判断，避免导致死循环。</strong></p><h2 id="卸载阶段-Unmounting"><a href="#卸载阶段-Unmounting" class="headerlink" title="卸载阶段 Unmounting"></a>卸载阶段 Unmounting</h2><ul><li>componentWillUnmount()</li></ul><h3 id="componentWillUnmount"><a href="#componentWillUnmount" class="headerlink" title="componentWillUnmount"></a>componentWillUnmount</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">componentWillUnmount()</span><br></pre></td></tr></table></figure><p>在组件卸载或者销毁前调用。这个方法主要用来做一些清理工作，例如：</p><ul><li>取消定时器</li><li>取消事件绑定</li><li>取消网络请求</li></ul><p><strong>不能使用 setState</strong></p><h2 id="错误处理-Error-Handling"><a href="#错误处理-Error-Handling" class="headerlink" title="错误处理 Error Handling"></a>错误处理 Error Handling</h2><ul><li>componentDidCatch()</li></ul><h3 id="componentDidCatch"><a href="#componentDidCatch" class="headerlink" title="componentDidCatch()"></a>componentDidCatch()</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">componentDidCatch(err, info)</span><br></pre></td></tr></table></figure><p>任何子组件在渲染期间，生命周期方法中或者构造函数 constructor 发生错误时调用。</p><p>错误边界不会捕获下面的错误：</p><ul><li>事件处理 (Event handlers) （因为事件处理不发生在 React 渲染时，报错不影响渲染）</li><li>异步代码 (Asynchronous code) (e.g. setTimeout or requestAnimationFrame callbacks)</li><li>服务端渲染 (Server side rendering)</li><li>错误边界本身(而不是子组件)抛出的错误</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>React 生命周期可以查看 <a href="http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/" target="_blank" rel="noopener">生命周期图</a></p><p>虽然 React 有做向下兼容，但是推荐尽量避免使用废弃的生命周期，而是拥抱未来，用新的生命周期替换它们。</p><p>如果你不想升级 React，但是想用新的生命周期方法，也是可以的。使用 <a href="https://github.com/reactjs/react-lifecycles-compat" target="_blank" rel="noopener">react-lifecycles-compat</a> polyfill，可以为低版本的 React（0.14.9+）提供新的生命周期方法。</p><blockquote><p>参考：<br><a href="https://reactjs.org/docs/react-component.html" target="_blank" rel="noopener">React.Component</a><br><a href="http://react.css88.com/blog/2018/03/27/update-on-async-rendering.html" target="_blank" rel="noopener">Update on Async Rendering</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;React 从 v16 开始，像是跨入了新的时代，性能和新的 API 都令人瞩目。重新认识 React，从重新认识生命周期开始。&lt;/p&gt;&lt;p&gt;为了更好的支持异步渲染（Async Rendering），解决一些生命周期滥用可能导致的问题，React 从 V16.3 开始，对生命周期进行渐进式调整，同时在官方文档也提供了使用的最佳实践。&lt;/p&gt;&lt;p&gt;这里我们将简要对比 React 新旧生命周期，重新认识一下 React 生命周期。&lt;/p&gt;
    
    </summary>
    
      <category term="前端" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="React" scheme="https://blog.hhking.cn/categories/%E5%89%8D%E7%AB%AF/React/"/>
    
    
      <category term="React" scheme="https://blog.hhking.cn/tags/React/"/>
    
      <category term="生命周期" scheme="https://blog.hhking.cn/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"/>
    
  </entry>
  
  <entry>
    <title>[译] 使用 React 一年后，我学到的最重要经验</title>
    <link href="https://blog.hhking.cn/2018/09/12/mindset-lessons-from-a-year-with-react/"/>
    <id>https://blog.hhking.cn/2018/09/12/mindset-lessons-from-a-year-with-react/</id>
    <published>2018-09-12T12:27:23.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="https://medium.freecodecamp.org/mindset-lessons-from-a-year-with-react-1de862421981" target="_blank" rel="noopener">The most important lessons I’ve learned after a year of working with<br>React</a><br>作者：Tomas Eglinskas</p></blockquote><p><img src="https://cdn-images-1.medium.com/max/2000/1*TheYckj9udF4qLjoJW8sjg.png" alt="images"></p><center>No, I didn’t wrote that, but I know it got your attention 🔪</center><p><br></p><p>开始学习一项新的技术时候令人很苦恼。你经常发现自己处于教程和文章的海洋里，还伴随着无数的个人观点。并且每个人都声称他们找到了“正确而完美的方式”。</p><a id="more"></a><p>这让我们需要去辨别：我们选择的教程是否会浪费时间。</p><p>在潜入这片海洋之前，我们必须要理解该技术的基本概念。然后我们需要建立基于技术的思维模式。如果我们开始学习 React，我们首先要以 React 的方式去思考。后面我们才可以开始将各种思维模式融为一体。</p><p>在这篇文章，我将从个人使用 React 的经历来介绍从中学到的一些关于这种思维的经验。我们将回顾工作时间和做个人项目的夜晚，甚至我在本地 JavaScript 活动中的演讲。</p><p>我们开始吧！</p><h2 id="React-在不断发展，你需要与时俱进"><a href="#React-在不断发展，你需要与时俱进" class="headerlink" title="React 在不断发展，你需要与时俱进"></a>React 在不断发展，你需要与时俱进</h2><p>如果你记得 16.3.0 版本的首次公告，你会想起当时大家是多么的激动。</p><p>下面是我们看到的一些变化和提升：</p><ul><li>Official Context API</li><li>createRef API</li><li>forwardRef API</li><li>StrictMode</li><li>Component Lifecycle Changes</li></ul><p>React 的核心团队和所有的贡献者正在做着伟大的工作 —— 努力改进我们所喜爱的技术。在 16.4.0 版本我们看到了 <a href="https://reactjs.org/blog/2018/05/23/react-v-16-4.html" target="_blank" rel="noopener">Pointer Events</a>。</p><p>毫无疑问更多的变化将会来临，这只是时间问题：异步渲染 (Async Rendering)、缓存 (Caching) 、version 17.0.0 以及其他未知的。</p><p>所以，如果你想成为佼佼者，你必须紧跟着社区里变化的脚步。</p><p><strong><code>了解事物的工作原理，以及被开发出来的原因。了解正在解决的问题，以及开发是如何变得越来越简单。这会让你受益匪浅。</code></strong></p><h2 id="不要害怕把你的代码拆分成更小的块"><a href="#不要害怕把你的代码拆分成更小的块" class="headerlink" title="不要害怕把你的代码拆分成更小的块"></a>不要害怕把你的代码拆分成更小的块</h2><p>React 是基于组件的。所以你应该利用这个概念，而不是害怕把大的模块拆分更小的模块。</p><p>有时候，一个简单的组件可能只有 4-5 行代码，在某些情况下，这完全没有问题。</p><p>这样做，可以让新人加入时，不需要花几天的时间去理解这些代码是如何运行的。</p><figure class="highlight jsx"><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="comment">// isn't this easy to understand?</span></span><br><span class="line"><span class="keyword">return</span> (</span><br><span class="line">  [</span><br><span class="line">   &lt;ChangeButton</span><br><span class="line">    onClick=&#123;<span class="keyword">this</span>.changeUserApprovalStatus&#125;</span><br><span class="line">    text=<span class="string">"Let’s switch it!"</span></span><br><span class="line">   /&gt;,</span><br><span class="line">   &lt;UserInformation status=&#123;status&#125;/&gt; </span><br><span class="line">  ]</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>你不必让组件都包含复杂的逻辑。它们可以只当视觉组件。如果这样可以提高代码的可读性和方便测试，并减少代码的“坏味道”，那么对团队的每个人来说都是个伟大的胜利。</p><figure class="highlight jsx"><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">import</span> ErrorMessage <span class="keyword">from</span> <span class="string">'./ErrorMessage'</span>;</span><br><span class="line"><span class="keyword">const</span> NotFound = <span class="function"><span class="params">()</span> =&gt;</span> (</span><br><span class="line">  &lt;ErrorMessage</span><br><span class="line">    title=<span class="string">"Oops! Page not found."</span></span><br><span class="line">    message=<span class="string">"The page you are looking for does not exist!"</span></span><br><span class="line">    className=<span class="string">"test_404-page"</span></span><br><span class="line">  /&gt;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>上面这个例子，属性都是固定的。所以我们可以有个纯组件控制网站的错误信息 <code>Not Found</code>，仅此而已。</p><p>并且，如果你不喜欢 CSS 的 class 选择器作为 class 名到处都是，我会推荐使用 styled components。这样可以提高可读性。</p><figure class="highlight jsx"><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">const</span> <span class="built_in">Number</span> = styled.h1<span class="string">`</span></span><br><span class="line"><span class="string">  font-size: 36px;</span></span><br><span class="line"><span class="string">  line-height: 40px;</span></span><br><span class="line"><span class="string">  margin-right: 5px;</span></span><br><span class="line"><span class="string">  padding: 0px;</span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line"><span class="comment">//..</span></span><br><span class="line">&lt;Container&gt;</span><br><span class="line">  &lt;<span class="built_in">Number</span>&gt;&#123;skipRatePre&#125;&lt;<span class="regexp">/Number&gt;</span></span><br><span class="line"><span class="regexp">  &lt;InfoName&gt;Skip Rate&lt;/</span>InfoName&gt;</span><br><span class="line">&lt;<span class="regexp">/Container&gt;</span></span><br></pre></td></tr></table></figure><p>如果你担心创建太多组件是因为太多文件会污染目录，那么重新考虑如何架构你的代码。我一直在使用分形结构 (<a href="https://hackernoon.com/fractal-a-react-app-structure-for-infinite-scale-4dab943092af" target="_blank" rel="noopener">fractal structrue</a>)，它非常棒。</p><h2 id="不要局限于基础-——-转向高级"><a href="#不要局限于基础-——-转向高级" class="headerlink" title="不要局限于基础 —— 转向高级"></a>不要局限于基础 —— 转向高级</h2><p>有时你可能会认为你对某些东西理解不够，不能转向高级应用。但是通常你不必过多担心 —— 接受挑战并证明自己的担心是错的。</p><p>通过掌握高级内容并推动你自己，你可以理解更多基础知识以及如何将他们运用在更大的项目上。</p><p>这里有许多模式可以尝试：</p><ul><li>复合组件（Compound Components）</li><li>高阶组件 （High Order Components）</li><li>Render Props</li><li>Smart/Dumb 组件 （Smart/Dumb Components）</li><li>等等 (尝试去分析)</li></ul><p>去研究他们，你会知道为什么使用它们、在哪里使用它们。用起 React 你会感觉更加得心应手。</p><figure class="highlight jsx"><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">// looks like magic?</span></span><br><span class="line"><span class="comment">// it's not that hard when you just try</span></span><br><span class="line">render() &#123;</span><br><span class="line">  <span class="keyword">const</span> children = React.Children.map(<span class="keyword">this</span>.props.children,</span><br><span class="line">   (child, index) =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> React.cloneElement(child, &#123;</span><br><span class="line">        onSelect: <span class="function"><span class="params">()</span> =&gt;</span> <span class="keyword">this</span>.props.onTabSelect(index)</span><br><span class="line">    &#125;);    </span><br><span class="line"> &#125;);  </span><br><span class="line"> <span class="keyword">return</span> children;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同时，不要害怕在工作中使用新东西 —— 当然，在一定范围内。</p><p>有人可能会质疑，这很正常。你的任务就是用强有力的证据来捍卫你的工作和决定。</p><p>你的目标应该是解决现有的问题，让后续开发更简单，或者清理一些面条式代码。即使你的建议被拒绝，你的收获也会比保持沉默得到的更多。</p><h2 id="不要过度复杂"><a href="#不要过度复杂" class="headerlink" title="不要过度复杂"></a>不要过度复杂</h2><p>这个听起来像是个相反的观点，但是有所不同。在生活中，各个地方，我们都需要保持平衡。我们不应该过度设计来炫耀，而是要务实，编写易于理解并实现其功能的代码。</p><p>如果你不需要 Redux，但是你想使用它，因为很多人都在不知道它真正用途的情况下使用它。你不要这样做。要有自己的主张，如果有人推倒你，不要害怕站起来。</p><p>有时候你可能会想，通过使用最新的技术并编写复杂的代码，你可以向世界宣称：“我不是初级，我是中级/高级开发者。看看我能做什么！”</p><p>老实说，这便是我开发者生涯初期的心态。但是随着时间的推移，你会明白，不为炫耀、“能实现”的代码更容易接受。</p><ol><li>你的同事可以接手你的项目，你不是唯一负责开发、修复、测试工作的人。</li><li>团队可以不需要通过长时间的会议就知道其他人做的事情。几分钟讨论就足够了。</li><li>当你同事外出度假两周时，你可以接手他们的工作。而且你不需要工作 8 小时，因为你 1 小时就可以搞定。</li></ol><p>人们尊重让别人生活更轻松的人。因此如果你的目标是获得别人的尊敬、提升排名、提升自己，那么你应该为团队而不是为你自己编写代码。</p><p>你会成为大家最喜欢的团队成员。</p><h2 id="重构，重构，再重构-——-这是正常的。"><a href="#重构，重构，再重构-——-这是正常的。" class="headerlink" title="重构，重构，再重构 —— 这是正常的。"></a>重构，重构，再重构 —— 这是正常的。</h2><p>你可能会几十次的改变想法，虽然项目经理改变得更频繁。有人会评论你的工作，你也会评论他。所以结果是，你将会很多次的修改你的代码。</p><p>但是别担心，这是个正常的学习过程。不犯错、代码不报错，我们就无法提升自己。</p><p>跌倒的次数越多，重新站起来就变得越容易。</p><p>但是这里有个提醒：你要保证去测试你现在的软件。冒烟测试、单元测试、集成测试、快照测试 —— 别不好意思去测试。</p><p>每个人都看到或者将会看到测试可以节约宝贵时间的场景。</p><p>如果你和很多人一样，都认为做这些是浪费时间，试着以不同的角度想一想。</p><ol><li>你不需要和你同事一起向他解释是如何工作的。</li><li>你不需要和你同事一起向他解释为何会出现错误。</li><li>你不需要帮你同事修 bug。</li><li>你不需要修 3 周后才发现的 bug。</li><li>你将有时间做你想做的事情。</li></ol><p>这些都是有很多益处的。</p><h2 id="如果你喜欢它，你会不断成长"><a href="#如果你喜欢它，你会不断成长" class="headerlink" title="如果你喜欢它，你会不断成长"></a>如果你喜欢它，你会不断成长</h2><p>在过去的一年里，我的目标是更好的使用 React。我想做一个关于它的演讲。我想其他人和我一样享受它。</p><p>我可以不停歇的整晚坐在那编程，观看各种演讲并享受每一分钟。</p><p>事实是，当你想要什么，你会发现，不知怎么的就会有人开始帮助你。在上个月，我为 200 人做了首次关于 React 的演讲。</p><p>在这过去的一年里，我变得更加强大，React 用得更加得心应手 —— 各种模式，范式和内部原理。我可以进行高级内容的讨论并向别人讲授我之前害怕接触的话题。</p><p>今天我依旧感到兴奋和享受，一如一年前的感受。</p><p>因此，我建议每个人都问问自己：“你是否喜欢你在做的事情？”</p><p>如果不喜欢，那就继续寻找你可以谈论几个小时、每晚学习并且使你开心的那个特别的事情。</p><p>因为我们必须找到最接近我们内心的东西。成功不能强迫，而是主动获取。</p><p>如果我可以回到一年前，那么在进行这个大的旅程之前，这些就是我想对自己说的。</p><p>Thank you for reading!</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;原文：&lt;a href=&quot;https://medium.freecodecamp.org/mindset-lessons-from-a-year-with-react-1de862421981&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The most important lessons I’ve learned after a year of working with&lt;br&gt;React&lt;/a&gt;&lt;br&gt;作者：Tomas Eglinskas&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/2000/1*TheYckj9udF4qLjoJW8sjg.png&quot; alt=&quot;images&quot;&gt;&lt;/p&gt;&lt;center&gt;No, I didn’t wrote that, but I know it got your attention 🔪&lt;/center&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;开始学习一项新的技术时候令人很苦恼。你经常发现自己处于教程和文章的海洋里，还伴随着无数的个人观点。并且每个人都声称他们找到了“正确而完美的方式”。&lt;/p&gt;
    
    </summary>
    
      <category term="翻译" scheme="https://blog.hhking.cn/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="React" scheme="https://blog.hhking.cn/tags/React/"/>
    
      <category term="JavaScript" scheme="https://blog.hhking.cn/tags/JavaScript/"/>
    
      <category term="经验分享" scheme="https://blog.hhking.cn/tags/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/"/>
    
  </entry>
  
  <entry>
    <title>[译] 我从没理解过 JavaScript 闭包</title>
    <link href="https://blog.hhking.cn/2018/09/08/i-never-understood-javascript-closures/"/>
    <id>https://blog.hhking.cn/2018/09/08/i-never-understood-javascript-closures/</id>
    <published>2018-09-08T13:59:11.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文: <a href="https://medium.com/dailyjs/i-never-understood-javascript-closures-9663703368e8" target="_blank" rel="noopener">I never understood JavaScript closures</a><br>作者: <a href="https://medium.com/@odemeulder?source=post_header_lockup" target="_blank" rel="noopener">Olivier De Meulder</a><br>时间: Sep 7, 2017<br>译注：作者从 JavaScript 的原理出发，详细解读执行过程，通过“背包”的形象比喻，来解释闭包。</p></blockquote><p><img src="https://cdn-images-1.medium.com/max/1600/1*1vMy563vz6LA_67VrzeT9g.png" alt="images"></p><p><strong>我从没理解过 JavaScript 闭包</strong><br><strong>直到有人这样跟我解释……</strong></p><p>正如标题所说，JavaScript 闭包对我来说一直是个迷。我 <a href="https://medium.freecodecamp.org/lets-learn-javascript-closures-66feb44f6a44" target="_blank" rel="noopener">看过</a> <a href="https://medium.freecodecamp.org/whats-a-javascript-closure-in-plain-english-please-6a1fc1d2ff1c" target="_blank" rel="noopener">很多</a> <a href="https://en.wikipedia.org/wiki/Closure_%28computer_programming%29" target="_blank" rel="noopener">文章</a>，在工作中用过闭包，甚至有时候我都没有意识到我在使用闭包。</p><p>最近参加一个交流会，有人用某种方式向我解释了闭包，点醒了我。这篇文章我也将用这种方式来解释闭包。这里要称赞一下 <a href="https://www.codesmith.io/" target="_blank" rel="noopener">CodeSmith </a>的优秀人才和他们的《JavaScript The Hard Parts》系列。</p><a id="more"></a><h2 id="开始之前"><a href="#开始之前" class="headerlink" title="开始之前"></a>开始之前</h2><p>在理解闭包之前，一些重要的概念需要理解。其中一个就是 <strong><em>执行上下文（execution context）</em></strong>。</p><p><a href="http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/" target="_blank" rel="noopener">这篇文章</a> 对执行上下文有很好的介绍。引用一下这篇文章：</p><blockquote><p>JavaScript 代码在执行时，它的执行环境非常重要，它会被处理成下面的某一种情况：</p><p><strong>全局代码(Global code)</strong> —— 代码开始执行时的默认环境。</p><p><strong>函数代码(Function code)</strong> —— 当执行到函数体时。</p><p>(…)</p><p>(…), 我们把术语 <code>执行上下文（execution context）</code> 称为当前执行代码所处的 <em>环境或者作用域</em>。</p></blockquote><p>换句话说，当我们开始执行程序时，首先处于全局上下文中。在全局上下文中声明的变量，称为全局变量。当程序调用函数时，会发生什么？发生下面这几步：</p><ol><li>JavaScript 创建一个新的执行上下文 —— 局部执行上下文。</li><li>这个局部执行上下文有属于它的变量集，这些变量是这个执行上下文的局部变量。</li><li>这个新的执行上下文被压入执行栈中。将执行栈当成是用来跟踪程序执行位置的一种机制。</li></ol><p>函数什么时候执行完？当遇到 <code>return</code> 语句或者结束括号 <code>}</code> 时。函数结束时，发生下面情况：</p><ol><li>局部执行上下文从执行栈弹出。</li><li>函数把返回值返回到调用上下文。调用上下文是指调用该函数的的执行上下文，它可以是全局执行上下文也可以是另外一个局部执行上下文。这里的返回值怎么处理取决于调用执行上下文。返回值可是 <code>object</code>, <code>array</code>, <code>function</code>, <code>boolean</code> 等任何类型。如果函数没有 <code>return</code> 语句，那么返回值是 <code>undefined</code>。</li><li>局部执行上下文被销毁。这点很重要 —— 被销毁。所有在局部执行上下文中声明的变量都被清除。这些变量不再可用。这也是为什么称它们为局部变量。</li></ol><h2 id="一个非常简单的例子"><a href="#一个非常简单的例子" class="headerlink" title="一个非常简单的例子"></a>一个非常简单的例子</h2><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></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>: <span class="keyword">let</span> a = <span class="number">3</span></span><br><span class="line"><span class="number">2</span>: <span class="function"><span class="keyword">function</span> <span class="title">addTwo</span>(<span class="params">x</span>) </span>&#123;</span><br><span class="line"><span class="number">3</span>:   <span class="keyword">let</span> ret = x + <span class="number">2</span></span><br><span class="line"><span class="number">4</span>:   <span class="keyword">return</span> ret</span><br><span class="line"><span class="number">5</span>: &#125;</span><br><span class="line"><span class="number">6</span>: <span class="keyword">let</span> b = addTwo(a)</span><br><span class="line"><span class="number">7</span>: <span class="built_in">console</span>.log(b)</span><br></pre></td></tr></table></figure><p>为了理解 JavaScript 引擎的真正工作原理，我们来详细解释一下。</p><ol><li>在代码第一行，我们在全局执行上下文声明了一个新的变量 <code>a</code>，并赋值为 <code>3</code>。</li><li>接下来比较棘手了。第 2 到第 5 行属于一个整体。这里发生了什么呢？我们在全局执行上下文声明了一个变量，命名为 <code>addTwo</code>。然后我们怎么对它赋值的？通过函数定义。所有在两个括号 <code>{}</code> 之间的内容都被赋给 <code>addTwo</code>。函数里的代码不计算、不执行，只是保存在变量，留着后面使用。</li><li>现在我们到了第 6 行。看似很简单，其实这里有很多需要解读。首先我们在全局执行上下文声明了一个变量，标记为 <code>b</code>。当变量刚声明时，它的默认值是 <code>undefined</code>。</li><li>接着，还是在第 6 行，我们看到有个赋值运算符。我们准备给变量 <code>b</code> 赋新值。接着看到一个将要被调用的函数。当你看到变量后面跟着圆括号 <code>(...)</code> ，那就是函数调用的标识。提前说下后面的情况：每个函数都有返回值（一个值、一个对象或者是 <code>undefined</code>）。函数的返回值将被赋值给变量 <code>b</code>。</li><li>但是（在赋值前）我们首先要调用函数 <code>addTwo</code>。JavaScript 将在全局执行上下文内存中查找变量 <code>addTwo</code>。找到了！它在第 2 步（第 2-5 行）中定义，你瞧，变量 <code>addTwo</code> 包含函数定义。注意，变量 <code>a</code> 当做参数传给了函数。JavaScript 在全局执行上下文内存中寻找变量 <code>a</code>，找到并发现它的值是 <code>3</code>，然后把数值 <code>3</code> 做为参数传给函数。函数执行准备就绪。</li><li>现在执行上下文将会切换。一个新的局部执行上下文被创建，我们把它命名为 “addTwo 执行上下文”。该执行上下文被压入调用栈。在局部执行上下文中首先做些什么事呢？</li><li>你可能会想说：“在局部执行上下文中声明一个新的变量 <code>ret</code> ”。然后答案不是这样。正确答案是：我们首先需要查看函数的参数：在局部执行上下文中声明新的变量 <code>x</code>，因为值 <code>3</code> 作为参数传给函数，所以变量 <code>x</code> 赋值为数值 <code>3</code>。</li><li>下一步：局部执行上下文中声明新变量 <code>ret</code>。它的值默认为 <code>undefined</code>。（第3行）</li><li>还是第 3 行，准备执行加法。我们首先需要获取 <code>x</code> 的值。JavaScript 将寻找变量 <code>x</code>。首先在局部执行上下文中寻找。找到变量 <code>x</code> 的值为 <code>3</code>。第二个操作数是数值 <code>2</code>，加法的结果（<code>5</code>）赋值给变量 <code>ret</code>。</li><li>第 4 行。我们返回变量 <code>ret</code> 的值。在局部执行上下文中又进行查找 <code>ret</code>。<code>ret</code> 的值为 <code>5</code>。所以该函数返回数值 <code>5</code>，函数结束。</li><li>第 4-5 行。函数结束。局部执行上下文被销毁。变量 <code>x</code> 和 <code>ret</code> 被清除，不再存在。调用栈弹出该上下文，返回值返回给调用上下文。在这个例子中，调用上下文是全局执行上下文，因为函数 <code>addTwo</code> 是在全局执行上下文中调用的。</li><li>现在回到我们在第 4 步遗留的内容。返回值（数值 <code>5</code>）复制给变量 <code>b</code>。在这个小程序中，我们还在第 6 行。</li><li>下面我不再详细说明了。在第 7 行，变量 <code>b</code> 的值在 <code>console</code> 中打印出来。在我们的例子里将打印出数值 <code>5</code>。</li></ol><p>对一个简单的程序，这真是个冗长的解释！而且我们甚至还没涉及到闭包。我保证一定会讲解闭包的。但是我们还是需求绕一两次。</p><h2 id="词法作用域-Lexical-scope"><a href="#词法作用域-Lexical-scope" class="headerlink" title="词法作用域 (Lexical scope)"></a>词法作用域 (Lexical scope)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>: <span class="keyword">let</span> val1 = <span class="number">2</span></span><br><span class="line"><span class="number">2</span>: <span class="function"><span class="keyword">function</span> <span class="title">multiplyThis</span>(<span class="params">n</span>) </span>&#123;</span><br><span class="line"><span class="number">3</span>:   <span class="keyword">let</span> ret = n * val1</span><br><span class="line"><span class="number">4</span>:   <span class="keyword">return</span> ret</span><br><span class="line"><span class="number">5</span>: &#125;</span><br><span class="line"><span class="number">6</span>: <span class="keyword">let</span> multiplied = multiplyThis(<span class="number">6</span>)</span><br><span class="line"><span class="number">7</span>: <span class="built_in">console</span>.log(<span class="string">'example of scope:'</span>, multiplied)</span><br></pre></td></tr></table></figure><p>例子中，在局部执行上下文和全局执行上下文各有一些变量。JavaScript 的一个难点是如何寻找变量。如果在局部执行上下文没找到某个变量，那么到它的调用上下文中去找。如果在它的调用上下文也没找到，重复上面的查找步骤，直到在全局执行上下文中找（如果也没找到，那么就是 <code>undefined</code> ）。按照上面的例子来说明，它会验证这点。如果你理解作用域的原理，你可以跳过这部分。</p><ol><li>在全局执行上下文声明一个新变量 <code>val1</code> ，并赋值为数值 <code>2</code>。</li><li>第 2-5 行声明新变量 <code>multiplyThis</code> 并赋值为函数定义。</li><li>第 6 行，在全局执行上下文声明新变量 <code>multiplied</code>。</li><li>在全局执行上下文内存中获取变量 <code>multiplyThis</code> 并作为函数执行。传入参数数值 <code>6</code>。</li><li>新函数调用 = 新的执行上下文：创建新的局部执行上下文。</li><li>在局部执行上下文中，声明变量 <code>n</code> 并赋值为数值 <code>6</code>。</li><li>第 3 行，在局部执行上下文中声明变量 <code>ret</code>。</li><li>还是第 3 行，两个操作数——变量 <code>n</code> 和 <code>val1</code> 的值执行乘法运算。先在局部执行上下文查找变量 <code>n</code>，它是我们在第 6 步中声明的，值为数值 <code>6</code>。接着在局部执行上下文查找变量 <code>val1</code>，在局部执行上下文没有找到名为 <code>val1</code> 的变量，所以我们检查调用上下文中。这里调用上下文是全局执行上下文。我们在全局执行上下文中找到它，它在第 1 步中被定义，值为数值 <code>2</code>。</li><li>依旧是第 3 行。两个操作数相乘然后赋值给变量 <code>ret</code>。6 * 2 = 12。<code>ret</code> 现在值为 <code>12</code>。</li><li>返回变量 <code>ret</code>。局部执行上下文以及相应的变量 <code>ret</code> 和 <code>n</code> 一起被销毁。变量 <code>val1</code> 作为全局执行上下文的一部分没有被销毁。</li><li>回到第 6 行。在调用上下文中，变量 <code>multiplied</code> 被赋值为数值 <code>12</code>。</li><li>最后在第 7 行，我们在 console 中显示变量 <code>multiplied</code> 的值。</li></ol><p>在这个例子中，我们需要记住，函数可以访问到它调用上下文中定义的变量。这种现象正式学名是 <strong>词法作用域</strong>。</p><blockquote><p>（译者注：觉得这里对词法作用域的解释限于此例，并不完全准确。词法作用域，函数的作用域是在函数定义的时候决定的，而不是调用时）。</p></blockquote><h2 id="返回值是函数的函数"><a href="#返回值是函数的函数" class="headerlink" title="返回值是函数的函数"></a>返回值是函数的函数</h2><p>在第一个例子里函数 <code>addTwo</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></pre></td><td class="code"><pre><span class="line"> <span class="number">1</span>: <span class="keyword">let</span> val = <span class="number">7</span></span><br><span class="line"> <span class="number">2</span>: <span class="function"><span class="keyword">function</span> <span class="title">createAdder</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="number">3</span>:   <span class="function"><span class="keyword">function</span> <span class="title">addNumbers</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line"> <span class="number">4</span>:     <span class="keyword">let</span> ret = a + b</span><br><span class="line"> <span class="number">5</span>:     <span class="keyword">return</span> ret</span><br><span class="line"> <span class="number">6</span>:   &#125;</span><br><span class="line"> <span class="number">7</span>:   <span class="keyword">return</span> addNumbers</span><br><span class="line"> <span class="number">8</span>: &#125;</span><br><span class="line"> <span class="number">9</span>: <span class="keyword">let</span> adder = createAdder()</span><br><span class="line"><span class="number">10</span>: <span class="keyword">let</span> sum = adder(val, <span class="number">8</span>)</span><br><span class="line"><span class="number">11</span>: <span class="built_in">console</span>.log(<span class="string">'example of function returning a function: '</span>, sum)</span><br></pre></td></tr></table></figure><p>我们来一步一步分解：</p><ol><li>第 1 行，我们在全局执行上下文声明变量 <code>val</code> 并赋值为数值 <code>7</code>。</li><li>第 2-8 行，我们在全局执行上下文声明变量 <code>createAdder</code> 并赋值为函数定义。第 3-7 行表示函数定义。和前面所说，这时候不会进入函数，我们只是把函数定义保存在变量 (createAdder)。</li><li>第 9 行，我们在全局执行上下文声明名为 <code>adder</code> 的新变量，暂时赋值为 <code>undefined</code>。</li><li>还是第 9 行，我们看到有括号 <code>()</code>，知道需要执行或者调用函数。我们从全局执行上下文的内存中查找变量 <code>createAdder</code>，它在第 2 步创建。ok，现在调用它。</li><li>调用函数，我们现在处于第 2 行。新的局部执行上下文被创建。我们可以在新的执行上下文中创建局部变量。JavaScript 引擎把新的上下文压入调用栈。该函数没有参数，我们直接进入函数体。</li><li>还是在 3-6 行。我们声明了个新函数。我们在局部执行上下文中创建了新的变量 <code>addNumbers</code>，这点很重要，<code>addNumbers</code> 只在局部执行上下文中出现。我们使用局部变量 <code>addNumbers</code> 保存了函数定义。</li><li>现在到了第 7 行。我们返回变量 <code>addNumbers</code> 的值。JavaScript 引擎找到 <code>addNumbers</code> 这个变量，它是个函数定义。这没问题，函数可以返回任意类型，包括函数定义。所以我们返回了 <code>addNumbers</code> 这个函数定义。括号中的所有内容——第 4-5 行组成了函数定义。我们也从调用栈中移除了该局部执行上下文。</li><li>局部执行上下文在返回时销毁了。<code>addNumbers</code> 变量不存在了，但是函数定义还在，它被函数返回并赋值给了变量 <code>adder</code> —— 我们在第 3 步创建的变量。</li><li>现在到了第 10 行。我们在全局执行上下文中定义了新变量 <code>sum</code>，暂时赋值是 <code>undefined</code>。</li><li>接下来需要需要执行函数。函数定义在变量 <code>adder</code> 中。我们在全局执行上下文中查找并确保找到了它。这个函数带有两个参数。</li><li>我们获取这两个参数，以便能调用函数并传入正确的参数。第一个参数是变量 <code>val</code>，在第 1 步中定义，表示数值 <code>7</code> , 第二个参数是数值 <code>8</code>。</li><li>现在我们开始执行函数。该函数在定义在 3-5 行。新的局部执行上下文被创建，同时创建了两个新变量：<code>a</code> 和 <code>b</code>，他们分别赋值为 <code>7</code> 和 <code>8</code>，这是上一步提到的传给函数的参数。</li><li>第 4 行，声明变量 <code>ret</code>。它是在局部执行上下文中声明的。</li><li>第 4 行，进行加法运算：我们让变量 <code>a</code> 和变量 <code>b</code> 的值相加。相加的结果（15）赋值给变量 <code>ret</code>。</li><li>函数返回变量 <code>ret</code> 。局部执行上下文销毁，从调用栈中移除，变量 <code>a</code>、<code>b</code> 和 <code>ret</code> 都不存在了。</li><li>返回值赋值给在第 9 步定义的变量 <code>sum</code>。</li><li>在 console 中打印 <code>sum</code> 的值。</li></ol><p>正如所预期的，console 打印出 15，但是这个过程我们真的经历了很多困难。我想在这里说明几点。首先，函数定义可以保存在变量中，函数定义在执行前对程序是不可见的；第二点，每次函数调用，都会创建一个局部执行上下文（临时的），局部执行上下文在函数结束后消失，函数在遇到 <code>return</code> 语句或者右括号 <code>}</code> 时结束。</p><h2 id="最后，闭包"><a href="#最后，闭包" class="headerlink" title="最后，闭包"></a>最后，闭包</h2><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></pre></td><td class="code"><pre><span class="line"> <span class="number">1</span>: <span class="function"><span class="keyword">function</span> <span class="title">createCounter</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="number">2</span>:   <span class="keyword">let</span> counter = <span class="number">0</span></span><br><span class="line"> <span class="number">3</span>:   <span class="keyword">const</span> myFunction = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="number">4</span>:     counter = counter + <span class="number">1</span></span><br><span class="line"> <span class="number">5</span>:     <span class="keyword">return</span> counter</span><br><span class="line"> <span class="number">6</span>:   &#125;</span><br><span class="line"> <span class="number">7</span>:   <span class="keyword">return</span> myFunction</span><br><span class="line"> <span class="number">8</span>: &#125;</span><br><span class="line"> <span class="number">9</span>: <span class="keyword">const</span> increment = createCounter()</span><br><span class="line"><span class="number">10</span>: <span class="keyword">const</span> c1 = increment()</span><br><span class="line"><span class="number">11</span>: <span class="keyword">const</span> c2 = increment()</span><br><span class="line"><span class="number">12</span>: <span class="keyword">const</span> c3 = increment()</span><br><span class="line"><span class="number">13</span>: <span class="built_in">console</span>.log(<span class="string">'example increment'</span>, c1, c2, c3)</span><br></pre></td></tr></table></figure><p>通过之前的两个例子，我们应该掌握了其中的窍门，让我们按我们期望的执行方式来快速过一遍执行过程。</p><ol><li>1-8 行。我们在全局执行上下文创建了变量 <code>createCounter</code> 并赋值为函数定义。</li><li>第 9 行。在全局执行上下文声明变量 <code>increment</code>。</li><li>还是第 9 行。我们需要调用函数 <code>createCounter</code> 并把它的返回值赋值给变量 <code>increment</code>。</li><li>1-8 行，函数调用，创建新的局部执行上下文。</li><li>第 2 行，在局部执行上下文中声明变量 <code>counter</code>，并赋值为数值 <code>0</code>。</li><li>3-6 行，声明名为 <code>myFunction</code> 的变量。该变量是在局部执行上下文声明的。变量的内容是另一个函数定义 —— 在 4-5 行定义。</li><li>第 7 行，返回变量 <code>myFunction</code> 的值。局部执行上下文被删除了，<code>myFunction</code> 和 <code>counter</code> 也不存在了。程序控制权回到调用上下文。</li><li>第 9 行。在调用上下文，也是全局执行上下文中，<code>createCounter</code> 的返回值赋给 <code>increment</code>。现在变量 <code>increment</code> 包含一个函数定义。该函数定义是 <code>createCounter</code> 返回的。它不再是标记为 <code>myFunction</code>，但是是同一个函数定义。在全局执行上下文中，它被命名为 <code>increment</code>。</li><li>第 10 行，声明变量 <code>c1</code>。</li><li>继续第 10 行，寻找变量 <code>increment</code>，它是个函数，调用函数。它包含之前返回的函数定义 —— 在 4-5 行定义的。</li><li>创建新的执行上下文，这里没有参数，开始执行函数。</li><li>第 4 行，<code>counter = counter + 1</code>。在局部执行上下文寻找 <code>counter</code> 的值。我们只是创建了上下文而没有声明任何局部变量。我们看看全局执行上下文，也没有变量 <code>counter</code>。JavaScript 会把这个转化成 <code>counter = undefined + 1</code>，声明新的局部变量 <code>counter</code> 并赋值为数值 <code>1</code>，因为 <code>undefined</code> 会转化成 <code>0</code>。</li><li>第 5 行，我们返回 <code>counter</code> 的值，或者说数值 <code>1</code>。销毁局部执行上下文和变量 <code>counter</code>。</li><li>回到第 10 行，返回值（<code>1</code>）赋给 <code>c1</code>。</li><li>第 11 行，重复第 10-14 的步骤，最后 <code>c2</code> 也赋值为 <code>1</code>。</li><li>第 12 行，重复第 10-14 的步骤，最后 <code>c3</code> 也赋值为 <code>1</code>。</li><li>第 13 行，我们打印出变量 <code>c1</code>、<code>c2</code> 和 <code>c3</code> 的值。</li></ol><p>自己尝试一下这个，看看会发生什么。你会发现，打印出来的并不是上面解释的预期结果 <code>1</code>、 <code>1</code> 和 <code>1</code>，而是打印出 <code>1</code>、 <code>2</code> 和 <code>3</code>。所以发生了什么？</p><p>不知道为什么，<code>increment</code> 函数记住了 <code>counter</code> 的值。这是怎么实现的呢？</p><p>是不是因为 <code>counter</code> 是属于全局执行上下文？试试 <code>console.log(counter)</code>，你会得到 <code>undefined</code>。所以它并不是。</p><p>或许，是因为当你调用 <code>increment</code> 时，它以某种方式返回创建它的函数（createCounter）的地方？这是怎么回事呢？变量 <code>increment</code> 包含函数定义，而不是它从哪里创建。所以并不是这个原因。</p><p>所以这里肯定存在另一种机制。它就是<strong>闭包</strong>。我们终于讲到它了，一直缺失的部分。</p><p>下面是它的工作原理。只要你声明一个新的函数并赋值给一个变量，你就保存了这个函数定义，<strong><em>也就形成了闭包</em></strong>。闭包包含函数创建时的作用域里的所有变量。这类似于一个背包。函数定义带着一个背包，包里保存了所有在函数定义创建时作用域里的变量。</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></pre></td><td class="code"><pre><span class="line"> <span class="number">1</span>: <span class="function"><span class="keyword">function</span> <span class="title">createCounter</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="number">2</span>:   <span class="keyword">let</span> counter = <span class="number">0</span></span><br><span class="line"> <span class="number">3</span>:   <span class="keyword">const</span> myFunction = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"> <span class="number">4</span>:     counter = counter + <span class="number">1</span></span><br><span class="line"> <span class="number">5</span>:     <span class="keyword">return</span> counter</span><br><span class="line"> <span class="number">6</span>:   &#125;</span><br><span class="line"> <span class="number">7</span>:   <span class="keyword">return</span> myFunction</span><br><span class="line"> <span class="number">8</span>: &#125;</span><br><span class="line"> <span class="number">9</span>: <span class="keyword">const</span> increment = createCounter()</span><br><span class="line"><span class="number">10</span>: <span class="keyword">const</span> c1 = increment()</span><br><span class="line"><span class="number">11</span>: <span class="keyword">const</span> c2 = increment()</span><br><span class="line"><span class="number">12</span>: <span class="keyword">const</span> c3 = increment()</span><br><span class="line"><span class="number">13</span>: <span class="built_in">console</span>.log(<span class="string">'example increment'</span>, c1, c2, c3)</span><br></pre></td></tr></table></figure><ol><li>1-8 行。我们在全局执行上下文创建了变量 <code>createCounter</code> 并赋值为函数定义。同上。</li><li>第 9 行。在全局执行上下文声明变量 <code>increment</code>。同上。</li><li>还是第 9 行。我们需要调用函数 <code>createCounter</code> 并把它的返回值赋值给变量 <code>increment</code>。同上。</li><li>1-8 行，函数调用，创建新的局部执行上下文。同上。</li><li>第 2 行，在局部执行上下文中声明变量 <code>counter</code>，并赋值为数值 <code>0</code>。同上。</li><li>3-6 行，声明名为 <code>myFunction</code> 的变量。该变量是在局部执行上下文声明的。变量的内容是另一个函数定义 —— 在 4-5 行定义。现在我们同时 <em>创建了一个闭包</em> 并把它作为函数定义的一部分。闭包包含了当前作用域里的变量，在这里是变量 <code>counter</code> (值为 <code>0</code>)。</li><li>第 7 行，返回变量 <code>myFunction</code> 的值。局部执行上下文被删除了，<code>myFunction</code> 和 <code>counter</code> 也不存在了。程序控制权回到调用上下文。所以我们返回了函数定义和它的 <em>闭包</em> —— 这个背包包含了函数创建时作用域里的变量。</li><li>第 9 行。在调用上下文，也是全局执行上下文中，<code>createCounter</code> 的返回值赋给 <code>increment</code>。现在变量 <code>increment</code> 包含一个函数定义（和闭包）。该函数定义是 <code>createCounter</code> 返回的。它不再是标记为 <code>myFunction</code>，但是是同一个函数定义。在全局执行上下文中，它被命名为 <code>increment</code>。</li><li>第 10 行，声明变量 <code>c1</code>。</li><li>继续第 10 行，寻找变量 <code>increment</code>，它是个函数，调用函数。它包含之前返回的函数定义 —— 在 4-5 行定义的。（同时它也有个包含变量的背包）</li><li>创建新的执行上下文，这里没有参数，开始执行函数。</li><li>第 4 行，<code>counter = counter + 1</code>。我们需要寻找变量 <code>counter</code>。我们在局部或者全局执行上下文寻找前，先查看我们的背包。我们检查闭包。你瞧！闭包里包含变量 <code>counter</code>，值为 <code>0</code>。通过第 4 行的表达式，它的值设为 <code>1</code>。它继续保存在背包里。现在闭包包含值为 <code>1</code> 的变量 <code>counter</code>。</li><li>第 5 行，我们返回 <code>counter</code> 的值，或者说数值 <code>1</code>。销毁局部执行上下文和变量 <code>counter</code>。</li><li>回到第 10 行，返回值（<code>1</code>）赋给 <code>c1</code>。</li><li>第 11 行，重复第 10-14 的步骤。这次，当我们查看闭包时，我们看到变量 <code>counter</code> 的值为 <code>1</code>。它是在第 12 步（程序第 4 行）设置的。通过 <code>increment</code> 函数，它的值增加并保存为 <code>2</code>。 最后 <code>c2</code> 也赋值为 <code>2</code>。</li><li>第 12 行，重复第 10-14 的步骤，最后 <code>c3</code> 也赋值为 <code>3</code>。</li><li>第 13 行，我们打印出变量 <code>c1</code>、<code>c2</code> 和 <code>c3</code> 的值。</li></ol><p>现在我们理解它的原理了。需要记住的关键点是，但函数声明时，它包含函数定义和一个闭包。<strong>闭包是函数创建时作用域内所有变量的集合。</strong></p><p>你可能会问，是不是所有函数都有闭包，即使是在全局作用域下创建的函数？答案是肯定的。全局作用域下创建的函数也生成闭包。但是既然函数是在全局作用域下创建的，他们可以访问全局作用域下的所有变量。所以这和闭包的概念不相关。</p><p>当函数的返回值是一个函数时，闭包的概念就变得更加相关了。返回的函数可以访问不在全局作用域里的变量，但它们只存在于闭包里。</p><h2 id="并不简单的闭包"><a href="#并不简单的闭包" class="headerlink" title="并不简单的闭包"></a>并不简单的闭包</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> c = <span class="number">4</span></span><br><span class="line"><span class="keyword">const</span> addX = <span class="function"><span class="params">x</span> =&gt;</span> n =&gt; n + x</span><br><span class="line"><span class="keyword">const</span> addThree = addX(<span class="number">3</span>)</span><br><span class="line"><span class="keyword">let</span> d = addThree(c)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'example partial application'</span>, d)</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> c = <span class="number">4</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addX</span>(<span class="params">x</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">n</span>) </span>&#123;</span><br><span class="line">     <span class="keyword">return</span> n + x</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> addThree = addX(<span class="number">3</span>)</span><br><span class="line"><span class="keyword">let</span> d = addThree(c)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'example partial application'</span>, d)</span><br></pre></td></tr></table></figure><p>我们声明了一个通用的相加函数 <code>addX</code>：传入一个参数（<code>x</code>）然后返回另一个函数。</p><p>返回的函数也带有一个参数，这个参数和变量 <code>x</code> 相加。</p><p>变量 <code>x</code> 是闭包的一部分。当变量 <code>addThree</code> 在局部上下文中声明时，被赋值为函数定义和闭包。该闭包包含变量 <code>x</code>。</p><p>所以现在调用执行 <code>addThree</code> 是，它可以从闭包中获取变量 <code>x</code>，而变量 <code>n</code> 是通过参数传入，所以函数可以返回相加的和。</p><p>这个例子 console 会打印出数值 <code>7</code>。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>我牢牢记住闭包的方法是通过 <strong>背包的比喻</strong> 。当一个函数被创建、传递或者从另一个函数中返回时，它就背着一个背包。背包里是函数声明时的作用域里的所有变量。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;原文: &lt;a href=&quot;https://medium.com/dailyjs/i-never-understood-javascript-closures-9663703368e8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;I never understood JavaScript closures&lt;/a&gt;&lt;br&gt;作者: &lt;a href=&quot;https://medium.com/@odemeulder?source=post_header_lockup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Olivier De Meulder&lt;/a&gt;&lt;br&gt;时间: Sep 7, 2017&lt;br&gt;译注：作者从 JavaScript 的原理出发，详细解读执行过程，通过“背包”的形象比喻，来解释闭包。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1600/1*1vMy563vz6LA_67VrzeT9g.png&quot; alt=&quot;images&quot;&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;我从没理解过 JavaScript 闭包&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;直到有人这样跟我解释……&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;正如标题所说，JavaScript 闭包对我来说一直是个迷。我 &lt;a href=&quot;https://medium.freecodecamp.org/lets-learn-javascript-closures-66feb44f6a44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;看过&lt;/a&gt; &lt;a href=&quot;https://medium.freecodecamp.org/whats-a-javascript-closure-in-plain-english-please-6a1fc1d2ff1c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;很多&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Closure_%28computer_programming%29&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;文章&lt;/a&gt;，在工作中用过闭包，甚至有时候我都没有意识到我在使用闭包。&lt;/p&gt;&lt;p&gt;最近参加一个交流会，有人用某种方式向我解释了闭包，点醒了我。这篇文章我也将用这种方式来解释闭包。这里要称赞一下 &lt;a href=&quot;https://www.codesmith.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CodeSmith &lt;/a&gt;的优秀人才和他们的《JavaScript The Hard Parts》系列。&lt;/p&gt;
    
    </summary>
    
      <category term="翻译" scheme="https://blog.hhking.cn/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="JavaScript" scheme="https://blog.hhking.cn/tags/JavaScript/"/>
    
      <category term="闭包" scheme="https://blog.hhking.cn/tags/%E9%97%AD%E5%8C%85/"/>
    
  </entry>
  
  <entry>
    <title>[译] axios 内部设计分析</title>
    <link href="https://blog.hhking.cn/2018/09/04/http-request-library-with-axios/"/>
    <id>https://blog.hhking.cn/2018/09/04/http-request-library-with-axios/</id>
    <published>2018-09-04T12:06:10.000Z</published>
    <updated>2020-08-21T11:20:08.931Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文: <a href="https://www.tutorialdocs.com/article/axios-learn.html" target="_blank" rel="noopener">How to Implement an HTTP Request Library with Axios</a><br>作者：Alex</p></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在前端开发过程中，我们经常遇到需要用到异步请求的场景。所以，一个功能齐全的 HTTP 请求库，可以极大的减少开发时间，提高开发效率。</p><p>axios 是近几年非常热门 HTTP 请求库。目前在 <a href="https://github.com/axios/axios" target="_blank" rel="noopener">Github</a> 已经有超过 40k 的 stars，也得到很多权威人士的推荐。</p><p>因此，有必要去了解一下 axios 是如何设计的、是如何实现 HTTP 请求库的。写这篇文章时 axios 的版本是 0.18.0，我们以此版本为例，来解读分析源码细节。axios 的源码是在 lib 目录下，以下涉及到的路径都是相对 <code>lib</code> 目录。</p><p>此篇主要讨论一下几点：</p><ul><li>如何使用 axios</li><li>axios 核心模块（请求 requests, 拦截器 interceptors, 撤销 withdrawals）的设计和实现方式</li><li>axios 的设计优点</li></ul><a id="more"></a><h2 id="如何使用-axios"><a href="#如何使用-axios" class="headerlink" title="如何使用 axios"></a>如何使用 axios</h2><p>在理解 axios 的设计之前，我们先了解一下如何使用 axios。我们通过一个简单的例子来说明下面的 axios API。</p><h3 id="发送请求"><a href="#发送请求" class="headerlink" title="发送请求"></a>发送请求</h3><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></pre></td><td class="code"><pre><span class="line">axios(&#123;</span><br><span class="line">  method:<span class="string">'get'</span>,</span><br><span class="line">  url:<span class="string">'http://bit.ly/2mTM3nY'</span>,</span><br><span class="line">  responseType:<span class="string">'stream'</span></span><br><span class="line">&#125;)</span><br><span class="line">  .then(<span class="function"><span class="keyword">function</span>(<span class="params">response</span>) </span>&#123;</span><br><span class="line">  response.data.pipe(fs.createWriteStream(<span class="string">'ada_lovelace.jpg'</span>))</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这是官方提供的 API 例子。从例子可以看出来，axios 的使用方式和 jQuery 的 ajax 非常相似，它们都返回 Promise （此例中 axios 也可以使用成功回调的方式，但是推荐使用 Promise 或者 await）来进行后续操作。</p><p>这个例子很简单，不需要过多解释，我们来看怎么添加 <strong>过滤函数(filter function)</strong>。</p><h3 id="添加拦截函数"><a href="#添加拦截函数" class="headerlink" title="添加拦截函数"></a>添加拦截函数</h3><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加一个请求拦截器</span></span><br><span class="line"><span class="comment">// 注意这里有两个方法 —— 一个成功时的和一个失败时的，后面会对这些做解释</span></span><br><span class="line">axios.interceptors.request.use(<span class="function"><span class="keyword">function</span> (<span class="params">config</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 请求前的一些处理</span></span><br><span class="line">    <span class="keyword">return</span> config;</span><br><span class="line">  &#125;, <span class="function"><span class="keyword">function</span> (<span class="params">error</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 处理请求失败的情况</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(error);</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">axios.interceptors.response.use(<span class="function"><span class="keyword">function</span> (<span class="params">response</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 处理响应的数据</span></span><br><span class="line">    <span class="keyword">return</span> response;</span><br><span class="line">  &#125;, <span class="function"><span class="keyword">function</span> (<span class="params">error</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 响应失败时的处理</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(error);</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure><p>从上述代码可知：在请求前，可以对请求 <code>config</code> 的参数做处理；请求后响应时，也可以对返回的数据做些特殊处理。同时，在请求或者响应失败时，我们也可以做相应的特殊错误处理。</p><h3 id="取消-HTTP-请求"><a href="#取消-HTTP-请求" class="headerlink" title="取消 HTTP 请求"></a>取消 HTTP 请求</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> CancelToken = axios.CancelToken;</span><br><span class="line"><span class="keyword">const</span> source = CancelToken.source();</span><br><span class="line"></span><br><span class="line">axios.get(<span class="string">'/user/12345'</span>, &#123;</span><br><span class="line">  cancelToken: source.token</span><br><span class="line">&#125;).catch(<span class="function"><span class="keyword">function</span>(<span class="params">thrown</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (axios.isCancel(thrown)) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'Request canceled'</span>, thrown.message);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// handle error</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">axios.post(<span class="string">'/user/12345'</span>, &#123;</span><br><span class="line">  name: <span class="string">'new name'</span></span><br><span class="line">&#125;, &#123;</span><br><span class="line">  cancelToken: source.token</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// cancel the request (the message parameter is optional)</span></span><br><span class="line">source.cancel(<span class="string">'Operation canceled by the user.'</span>);</span><br></pre></td></tr></table></figure><p>从上述例子可知，axios 使用基于 CancelToken 的撤销方式的提案。但是该提案已经被撤销了，<a href="https://github.com/tc39/proposal-cancelable-promises" target="_blank" rel="noopener">查看详情</a>。撤销的实现细节在后面的源码分析部分会解释。</p><h2 id="axios-核心模块的设计和实现方式"><a href="#axios-核心模块的设计和实现方式" class="headerlink" title="axios 核心模块的设计和实现方式"></a>axios 核心模块的设计和实现方式</h2><p>通过上面的例子，相信大家对 axios 的使用有了一个大致的了解。下面，我们根据 axios 的模块来分析其设计和实现。下图展示了该博文涉及到的 axios 相关目录。如果你感兴趣的话，建议 clone 相关的代码来看，这样可以对相应的模块有更深的理解。</p><p><img src="https://www.tutorialdocs.com/upload/2018/08/axios-module.png" alt="images"></p><h3 id="HTTP-请求模块（HTTP-request-module）"><a href="#HTTP-请求模块（HTTP-request-module）" class="headerlink" title="HTTP 请求模块（HTTP request module）"></a>HTTP 请求模块（HTTP request module）</h3><p>和请求模块相关的代码在 <code>core/dispatchReqeust.js</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="comment">// core/dispatchReqeust.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span> <span class="title">dispatchRequest</span>(<span class="params">config</span>) </span>&#123;</span><br><span class="line">    throwIfCancellationRequested(config);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// other source code</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 默认的适配器模块是根据当前环境来选择使用 Node 或者 XHR 来发起请求</span></span><br><span class="line">    <span class="keyword">var</span> adapter = config.adapter || defaults.adapter; </span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> adapter(config).then(<span class="function"><span class="keyword">function</span> <span class="title">onAdapterResolution</span>(<span class="params">response</span>) </span>&#123;</span><br><span class="line">        throwIfCancellationRequested(config);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// other source code</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;, <span class="function"><span class="keyword">function</span> <span class="title">onAdapterRejection</span>(<span class="params">reason</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!isCancel(reason)) &#123;</span><br><span class="line">            throwIfCancellationRequested(config);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// other source code</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(reason);</span><br><span class="line">        &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>通过上面的代码可知，<code>dispatchRequest</code> 方法是用来获取发送请求模块，发送请求模块是通过 <code>config.adapter</code> 得到。我们也可以通过传入符合规范的适配器函数（adapter function）来代替原始的模块（我们一般不会这么做，但是这是一个低耦合扩展点）。</p><p>在 <code>default.js</code> 文件里，可以看到 <code>adapter</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// default.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getDefaultAdapter</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> adapter;</span><br><span class="line">    <span class="comment">// Only Node.js has the classes of which variable type is process.</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> process !== <span class="string">'undefined'</span> &amp;&amp; <span class="built_in">Object</span>.prototype.toString.call(process) === <span class="string">'[object process]'</span>) &#123;</span><br><span class="line">        <span class="comment">// Node.js request module.</span></span><br><span class="line">        adapter = <span class="built_in">require</span>(<span class="string">'./adapters/http'</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> XMLHttpRequest !== <span class="string">'undefined'</span>) &#123;</span><br><span class="line">        <span class="comment">// The browser request module.</span></span><br><span class="line">        adapter = <span class="built_in">require</span>(<span class="string">'./adapters/xhr'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> adapter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>axios 里的 XHR 模块是对 <code>XMLHTTPRequest</code> 对象的封装，相对比较简单。所以这里不会过多的说明，有兴趣的话可以自己去阅读，代码在 <code>adapters/xhr.js</code> 文件。</p><h3 id="拦截器模块（Interceptor-module）"><a href="#拦截器模块（Interceptor-module）" class="headerlink" title="拦截器模块（Interceptor module）"></a>拦截器模块（Interceptor module）</h3><p>现在来看看 axios 是如何实现请求和响应的拦截器方法。先来看看 axios 的统一接口 —— <code>request</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="comment">// core/Axios.js</span></span><br><span class="line">Axios.prototype.request = <span class="function"><span class="keyword">function</span> <span class="title">request</span>(<span class="params">config</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// other code</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> chain = [dispatchRequest, <span class="literal">undefined</span>];</span><br><span class="line">    <span class="keyword">var</span> promise = <span class="built_in">Promise</span>.resolve(config);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.interceptors.request.forEach(<span class="function"><span class="keyword">function</span> <span class="title">unshiftRequestInterceptors</span>(<span class="params">interceptor</span>) </span>&#123;</span><br><span class="line">        chain.unshift(interceptor.fulfilled, interceptor.rejected);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.interceptors.response.forEach(<span class="function"><span class="keyword">function</span> <span class="title">pushResponseInterceptors</span>(<span class="params">interceptor</span>) </span>&#123;</span><br><span class="line">        chain.push(interceptor.fulfilled, interceptor.rejected);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (chain.length) &#123;</span><br><span class="line">        promise = promise.then(chain.shift(), chain.shift());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> promise;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这个函数是 axios 发送请求的接口。因为这个功能实现相对较长，所以我简要介绍一下相关的设计思路：</p><ol><li>chain 是执行队列。队列的初始值是一个带 config 参数的 Promise。</li><li>chain 执行队列中，插入了两个值，一个是用来发送请求的初始化函数 <code>dispatchRequest</code>，一个是<br>和 <code>dispatchRequest</code> 配对的函数 <code>undefined</code>。为什么要加 <code>undefined</code> 呢？因为在 Promise 里需要一个成功回调和一个失败回调，从代码段 <code>promise = promise.then(chain.shift(), chain.shift());</code> 也可以看出来。所以，<code>dispatchRequest</code> 和 <code>undefined</code> 可以当成是成对的函数。</li><li><code>chain</code> 执行队列中，发送请求的 <code>dispatchRequest</code> 函数位于“中间位置”。在它的前面是请求拦截器，通过 <code>unshift</code> 方法插入；在 <code>dispatchRequest</code> 后面的是响应拦截器，通过 <code>push</code> 方法插入。需要注意的是这些方法都是成对添加的，也就意味着一次会添加两个方法。</li></ol><p>通过上述 <code>request</code> 的代码，我们大致知道怎么使用拦截器了。现在我们来看看怎么取消 HTTP 请求。</p><h3 id="取消请求模块（Cancel-request-module）"><a href="#取消请求模块（Cancel-request-module）" class="headerlink" title="取消请求模块（Cancel-request module）"></a>取消请求模块（Cancel-request module）</h3><p>和取消相关的模块在 <code>Cancel/</code> 目录。现在来看一下相关的核心代码。</p><p>首先来看一下元类 <code>Cancel</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// cancel/Cancel.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Cancel</span>(<span class="params">message</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.message = message;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Cancel.prototype.toString = <span class="function"><span class="keyword">function</span> <span class="title">toString</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'Cancel'</span> + (<span class="keyword">this</span>.message ? <span class="string">': '</span> + <span class="keyword">this</span>.message : <span class="string">''</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Cancel.prototype.__CANCEL__ = <span class="literal">true</span>;</span><br></pre></td></tr></table></figure><p>在 <code>CancelToken</code> 类中，通过传递 Promise 方法来实现 HTTP 请求的取消，具体代码如下：</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"><span class="comment">// cancel/CancelToken.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">CancelToken</span>(<span class="params">executor</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> executor !== <span class="string">'function'</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">'executor must be a function.'</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> resolvePromise;</span><br><span class="line">    <span class="keyword">this</span>.promise = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span> <span class="title">promiseExecutor</span>(<span class="params">resolve</span>) </span>&#123;</span><br><span class="line">        resolvePromise = resolve;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> token = <span class="keyword">this</span>;</span><br><span class="line">    executor(<span class="function"><span class="keyword">function</span> <span class="title">cancel</span>(<span class="params">message</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (token.reason) &#123;</span><br><span class="line">            <span class="comment">// Cancellation has already been requested</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        token.reason = <span class="keyword">new</span> Cancel(message);</span><br><span class="line">        resolvePromise(token.reason);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">CancelToken.source = <span class="function"><span class="keyword">function</span> <span class="title">source</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> cancel;</span><br><span class="line">    <span class="keyword">var</span> token = <span class="keyword">new</span> CancelToken(<span class="function"><span class="keyword">function</span> <span class="title">executor</span>(<span class="params">c</span>) </span>&#123;</span><br><span class="line">        cancel = c;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        token: token,</span><br><span class="line">        cancel: cancel</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>相关的取消请求的代码在 <code>adapter/xhr.js</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="comment">// adapter/xhr.js</span></span><br><span class="line"><span class="keyword">if</span> (config.cancelToken) &#123;</span><br><span class="line">    <span class="comment">// Wait for the cancellation.</span></span><br><span class="line">    config.cancelToken.promise.then(<span class="function"><span class="keyword">function</span> <span class="title">onCanceled</span>(<span class="params">cancel</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!request) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        request.abort();</span><br><span class="line">        reject(cancel);</span><br><span class="line">        <span class="comment">// Reset the request.</span></span><br><span class="line">        request = <span class="literal">null</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过上述取消 HTTP 请求的代码，我们简要的解释一下相关实现逻辑：</p><ol><li>在需要取消的请求中，调用 <code>source</code> 方法来初始化，该方法会返回 <code>CancelToken</code> 类的实例 A 和 <code>cancle</code> 方法。</li><li>当 <code>source</code> 方法返回实例 A 时，会初始化一个处于 <code>pending</code> 状态的 promise。然后把实例 A 传递给 axios，promise 就可以当做取消请求的触发器。</li><li>当调用 <code>source</code> 方法返回的 <code>cancel</code> 方法时，实例 A 中的 promise 从 pending 转化成 fulfilled 状态，然后马上触发回调函数。从而 axios 的取消逻辑 —— <code>request.abort()</code> 被触发。</li></ol><h2 id="axios-的设计优点"><a href="#axios-的设计优点" class="headerlink" title="axios 的设计优点"></a>axios 的设计优点</h2><h3 id="发送请求函数的处理逻辑"><a href="#发送请求函数的处理逻辑" class="headerlink" title="发送请求函数的处理逻辑"></a>发送请求函数的处理逻辑</h3><p>正如前面章节提到的，axios 并没有把发送请求的 <code>dispatchRequest</code> 函数当成特殊函数对待。实际上，<code>dispatchRequest</code> 函数放在队列的中间，从而保证队列处理的一致性并提高代码的可读性。</p><h3 id="适配器的处理逻辑"><a href="#适配器的处理逻辑" class="headerlink" title="适配器的处理逻辑"></a>适配器的处理逻辑</h3><p>在适配器的的处理逻辑中，<code>http</code> 和 <code>xhr</code> 模块（<code>http</code> 用于 Node.js 发送请求，<code>xhr</code> 用于浏览器发送请求）并不直接放在 <code>dispatchRequest</code> 模块中，而是通过默认配置从 <code>default.js</code> 引入。从而不仅可以保证两个模块的低耦合，而且为将来的用户预留了定制化请求的空间。</p><h3 id="取消-HTTP-请求的处理逻辑"><a href="#取消-HTTP-请求的处理逻辑" class="headerlink" title="取消 HTTP 请求的处理逻辑"></a>取消 HTTP 请求的处理逻辑</h3><p>在取消 HTTP 请求的逻辑中，axios 设计使用 Promise 作为触发器，把 <code>resolve</code> 方法作为 <code>callback</code> 的参数传递到外面。这样不仅确保内部逻辑的一致性，还可以确保在需要取消请求时，不必直接修改相关类的样本数据，从而可以最大程度避免侵入其他的模块。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文详细介绍了 axios 的使用，设计思路和实现方法。阅读后，你可以知道 axios 的设计，同时学习模块的封装和交互。</p><p>本文只介绍了 axios 的核心模块。如果你对其他源码感兴趣，你可以去 <a href="https://github.com/axios/axios" target="_blank" rel="noopener">Github</a> 上查看。</p><p><em>在 <a href="http://www.ruanyifeng.com/blog/2018/08/weekly-issue-20.html" target="_blank" rel="noopener">阮一峰每周分享第 20 期</a> 看到这篇文章。文章没有很复杂的东西，也不是很细致的使用说明或者源码解读，作者主要是对 axios 内部设计进行分析，从整体上了解其中的核心设计思想。翻译可能有不准确或者错误地方，欢迎指正！</em></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;&lt;p&gt;原文: &lt;a href=&quot;https://www.tutorialdocs.com/article/axios-learn.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;How to Implement an HTTP Request Library with Axios&lt;/a&gt;&lt;br&gt;作者：Alex&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;p&gt;在前端开发过程中，我们经常遇到需要用到异步请求的场景。所以，一个功能齐全的 HTTP 请求库，可以极大的减少开发时间，提高开发效率。&lt;/p&gt;&lt;p&gt;axios 是近几年非常热门 HTTP 请求库。目前在 &lt;a href=&quot;https://github.com/axios/axios&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github&lt;/a&gt; 已经有超过 40k 的 stars，也得到很多权威人士的推荐。&lt;/p&gt;&lt;p&gt;因此，有必要去了解一下 axios 是如何设计的、是如何实现 HTTP 请求库的。写这篇文章时 axios 的版本是 0.18.0，我们以此版本为例，来解读分析源码细节。axios 的源码是在 lib 目录下，以下涉及到的路径都是相对 &lt;code&gt;lib&lt;/code&gt; 目录。&lt;/p&gt;&lt;p&gt;此篇主要讨论一下几点：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;如何使用 axios&lt;/li&gt;&lt;li&gt;axios 核心模块（请求 requests, 拦截器 interceptors, 撤销 withdrawals）的设计和实现方式&lt;/li&gt;&lt;li&gt;axios 的设计优点&lt;/li&gt;&lt;/ul&gt;
    
    </summary>
    
      <category term="翻译" scheme="https://blog.hhking.cn/categories/%E7%BF%BB%E8%AF%91/"/>
    
    
      <category term="前端" scheme="https://blog.hhking.cn/tags/%E5%89%8D%E7%AB%AF/"/>
    
      <category term="Ajax" scheme="https://blog.hhking.cn/tags/Ajax/"/>
    
      <category term="源码分析" scheme="https://blog.hhking.cn/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
  </entry>
  
</feed>
