<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hexo</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://wzes.github.io/"/>
  <updated>2020-01-05T10:01:25.442Z</updated>
  <id>https://wzes.github.io/</id>
  
  <author>
    <name>Xuantang Cun</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Chrome Extension 入门</title>
    <link href="https://wzes.github.io/2020/01/05/JavaScript/Chrome%20Extension/"/>
    <id>https://wzes.github.io/2020/01/05/JavaScript/Chrome Extension/</id>
    <published>2020-01-05T08:59:16.000Z</published>
    <updated>2020-01-05T10:01:25.442Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>2020年了，新年第一篇小文章！祝大家新年快乐～一直觉得浏览器是个很神秘的东西，天天在用，自以为很熟悉，也在用一些插件，有些还挺好用。所以自己也想写一个小小的插件，满足一下自己的好奇心！暂且翻译一下Chrome的插件开发的官网文档吧</p><p><a href="https://developer.chrome.com/extensions/getstarted" target="_blank" rel="noopener">原文链接</a></p><h3 id="开始"><a class="markdownIt-Anchor" href="#开始"></a> 开始</h3><p>扩展(Extension)由不同的但相互联系的组件构成。 组件可以包括后台脚本，内容脚本，选项页，UI元素和各种逻辑文件。 扩展组件是使用Web开发技术创建的：HTML，CSS和JavaScript。 扩展的组件将取决于其功能，并且可能不需要所有选项。</p><p>本教程将构建一个扩展，允许用户更改 <a href="http://developer.chrome.com" target="_blank" rel="noopener">developer.chrome.com</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">$ mkdir colorchange_extension &amp;&amp; cd colorchange_extension</span><br></pre></td></tr></table></figure><p>完整的扩展程序可以在<a href="https://developer.chrome.com/extensions/examples/tutorials/get_started_complete.zip" target="_blank" rel="noopener">此处</a>下载。</p><h4 id="创建-manifest-文件"><a class="markdownIt-Anchor" href="#创建-manifest-文件"></a> 创建 manifest 文件</h4><p>扩展从清单文件开始。 创建一个名为 manifest.json 的文件并包含以下代码，或在<a href="https://developer.chrome.com/extensions/examples/tutorials/get_started/manifest.json" target="_blank" rel="noopener">此处</a>下载该文件。</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line">    &quot;version&quot;: &quot;1.0&quot;,</span><br><span class="line">    &quot;description&quot;: &quot;Build an Extension!&quot;,</span><br><span class="line">    &quot;manifest_version&quot;: 2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在当前状态下在开发人员模式将包含清单文件的目录添加到Chrome扩展程序中。</p><ol><li>Chrome浏览器地址中输入 chrome://extensions，打开“扩展管理”页面。<ul><li>也可以通过单击Chrome菜单，将鼠标悬停在“更多工具”上，然后选择“扩展程序”来打开“扩展程序管理”页面。</li></ul></li><li>通过单击开发人员模式旁边的切换开关启用开发人员模式。</li><li>单击LOAD UNPACKED按钮，然后选择扩展目录。</li></ol><p><img src="https://developer.chrome.com/static/images/get_started/load_extension.png" alt></p><p>哇😯！Amazing！该扩展程序已成功安装。 由于清单中未包含任何图标，因此将为扩展程序创建通用工具栏图标。</p><h4 id="添加说明"><a class="markdownIt-Anchor" href="#添加说明"></a> 添加说明</h4><p>尽管已安装扩展程序，但没有说明。 通过创建名为background.js的文件或在<a href="https://developer.chrome.com/extensions/examples/tutorials/get_started/background.js" target="_blank" rel="noopener">此处</a>下载该文件并将其放置在扩展目录中，来引入背景脚本。</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><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">&#123;</span><br><span class="line">   &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line">   &quot;version&quot;: &quot;1.0&quot;,</span><br><span class="line">   &quot;description&quot;: &quot;Build an Extension!&quot;,</span><br><span class="line">   &quot;background&quot;: &#123;                     // new</span><br><span class="line">     &quot;scripts&quot;: [&quot;background.js&quot;],     // new</span><br><span class="line">     &quot;persistent&quot;: false               // new</span><br><span class="line">   &#125;,</span><br><span class="line">   &quot;manifest_version&quot;: 2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在，该扩展程序知道它包含一个非持久性后台脚本，并将扫描已注册文件中需要侦听的重要事件。</p><p>此扩展在安装后将需要来自持久变量的信息。首先在后台脚本中包含一个 <code>runtime.onInstalled</code> 的侦听事件。 在 <code>onInstalled</code> 侦听器内部，扩展将使用 <code>storage</code> API 设置一个值。 这将允许多个扩展组件访问该值并进行更新。</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">chrome.runtime.onInstalled.addListener(function() &#123;</span><br><span class="line">   chrome.storage.sync.set(&#123;color: &apos;#3aa757&apos;&#125;, function() &#123;</span><br><span class="line">     console.log(&quot;The color is green.&quot;);</span><br><span class="line">   &#125;);</span><br><span class="line"> &#125;);</span><br></pre></td></tr></table></figure><p>大多数API（包括 <code>storage</code> API）都必须在清单的 <code>&quot;permissions&quot;</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><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">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line">  &quot;version&quot;: &quot;1.0&quot;,</span><br><span class="line">  &quot;description&quot;: &quot;Build an Extension!&quot;,</span><br><span class="line">  &quot;permissions&quot;: [&quot;storage&quot;],</span><br><span class="line">  &quot;background&quot;: &#123;</span><br><span class="line">    &quot;scripts&quot;: [&quot;background.js&quot;],</span><br><span class="line">    &quot;persistent&quot;: false</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;manifest_version&quot;: 2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>浏览回到扩展管理页面，然后单击<code>重新加载</code>按钮。 带有蓝色链接（背景页面）的新字段“检查视图”可用。</p><p><img src="https://developer.chrome.com/static/images/get_started/view_background.png" alt></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">The color is green.</span><br></pre></td></tr></table></figure><h4 id="介绍用户界面"><a class="markdownIt-Anchor" href="#介绍用户界面"></a> 介绍用户界面</h4><p>扩展可以具有多种形式的用户界面，但是这种形式将使用弹出窗口。 创建一个名为 <code>popup.html</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><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">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">  &lt;head&gt;</span><br><span class="line">    &lt;style&gt;</span><br><span class="line">      button &#123;</span><br><span class="line">        height: 30px;</span><br><span class="line">        width: 30px;</span><br><span class="line">        outline: none;</span><br><span class="line">      &#125;</span><br><span class="line">    &lt;/style&gt;</span><br><span class="line">  &lt;/head&gt;</span><br><span class="line">  &lt;body&gt;</span><br><span class="line">    &lt;button id=&quot;changeColor&quot;&gt;&lt;/button&gt;</span><br><span class="line">  &lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>与后台脚本一样，需要在 <code>page_action</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><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">&#123;</span><br><span class="line">   &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line">   &quot;version&quot;: &quot;1.0&quot;,</span><br><span class="line">   &quot;description&quot;: &quot;Build an Extension!&quot;,</span><br><span class="line">   &quot;permissions&quot;: [&quot;storage&quot;],</span><br><span class="line">   &quot;background&quot;: &#123;</span><br><span class="line">     &quot;scripts&quot;: [&quot;background.js&quot;],</span><br><span class="line">     &quot;persistent&quot;: false</span><br><span class="line">   &#125;,</span><br><span class="line">   &quot;page_action&quot;: &#123;</span><br><span class="line">     &quot;default_popup&quot;: &quot;popup.html&quot;</span><br><span class="line">   &#125;,</span><br><span class="line">   &quot;manifest_version&quot;: 2</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>工具栏图标的名称也包含在 default_icons 字段的 page_action 下。 在<a href="https://developer.chrome.com/extensions/examples/tutorials/get_started/images.zip" target="_blank" rel="noopener">此处</a>下载 images 文件夹，将其解压缩，并将其放置在扩展程序的目录中。更新清单文件，以便扩展程序知道如何使用图像。</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><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line">    &quot;version&quot;: &quot;1.0&quot;,</span><br><span class="line">    &quot;description&quot;: &quot;Build an Extension!&quot;,</span><br><span class="line">    &quot;permissions&quot;: [&quot;storage&quot;],</span><br><span class="line">    &quot;background&quot;: &#123;</span><br><span class="line">      &quot;scripts&quot;: [&quot;background.js&quot;],</span><br><span class="line">      &quot;persistent&quot;: false</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;page_action&quot;: &#123;</span><br><span class="line">      &quot;default_popup&quot;: &quot;popup.html&quot;,</span><br><span class="line">      &quot;default_icon&quot;: &#123;</span><br><span class="line">        &quot;16&quot;: &quot;images/get_started16.png&quot;,</span><br><span class="line">        &quot;32&quot;: &quot;images/get_started32.png&quot;,</span><br><span class="line">        &quot;48&quot;: &quot;images/get_started48.png&quot;,</span><br><span class="line">        &quot;128&quot;: &quot;images/get_started128.png&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;manifest_version&quot;: 2</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>扩展程序还会在扩展程序管理页面上显示图像，权限警告和网站图标。 这些图像在清单中的图标下方指定。</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><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">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line">  &quot;version&quot;: &quot;1.0&quot;,</span><br><span class="line">  &quot;description&quot;: &quot;Build an Extension!&quot;,</span><br><span class="line">  &quot;permissions&quot;: [&quot;storage&quot;],</span><br><span class="line">  &quot;background&quot;: &#123;</span><br><span class="line">    &quot;scripts&quot;: [&quot;background.js&quot;],</span><br><span class="line">    &quot;persistent&quot;: false</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;page_action&quot;: &#123;</span><br><span class="line">    &quot;default_popup&quot;: &quot;popup.html&quot;,</span><br><span class="line">    &quot;default_icon&quot;: &#123;</span><br><span class="line">      &quot;16&quot;: &quot;images/get_started16.png&quot;,</span><br><span class="line">      &quot;32&quot;: &quot;images/get_started32.png&quot;,</span><br><span class="line">      &quot;48&quot;: &quot;images/get_started48.png&quot;,</span><br><span class="line">      &quot;128&quot;: &quot;images/get_started128.png&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;icons&quot;: &#123;</span><br><span class="line">    &quot;16&quot;: &quot;images/get_started16.png&quot;,</span><br><span class="line">    &quot;32&quot;: &quot;images/get_started32.png&quot;,</span><br><span class="line">    &quot;48&quot;: &quot;images/get_started48.png&quot;,</span><br><span class="line">    &quot;128&quot;: &quot;images/get_started128.png&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;manifest_version&quot;: 2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果在此阶段重新加载扩展，它将包含一个灰度图标，但不会包含任何功能差异。 由于 page_action 是在清单中声明的，因此由扩展程序决定是否告诉浏览器用户何时可以与 popup.html 进行交互。</p><p>在 <code>runtime.onInstalled</code> 侦听器事件中，使用 <code>declarativeContent API</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><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">chrome.runtime.onInstalled.addListener(function() &#123;</span><br><span class="line">  chrome.storage.sync.set(&#123;color: &apos;#3aa757&apos;&#125;, function() &#123;</span><br><span class="line">    console.log(&apos;The color is green.&apos;);</span><br><span class="line">  &#125;);</span><br><span class="line">  chrome.declarativeContent.onPageChanged.removeRules(undefined, function() &#123;</span><br><span class="line">    chrome.declarativeContent.onPageChanged.addRules([&#123;</span><br><span class="line">      conditions: [new chrome.declarativeContent.PageStateMatcher(&#123;</span><br><span class="line">        pageUrl: &#123;hostEquals: &apos;developer.chrome.com&apos;&#125;,</span><br><span class="line">      &#125;)</span><br><span class="line">      ],</span><br><span class="line">          actions: [new chrome.declarativeContent.ShowPageAction()]</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>declarativeContent API</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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">   &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line"> ...</span><br><span class="line">   &quot;permissions&quot;: [&quot;declarativeContent&quot;, &quot;storage&quot;],</span><br><span class="line"> ...</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>现在，当用户导航到包含“ <a href="http://developer.chrome.com" target="_blank" rel="noopener">developer.chrome.com</a>”的URL时，浏览器将在浏览器工具栏中显示一个全彩页面操作图标。 图标为全色时，用户可以单击它以查看popup.html。</p><p>弹出式UI的最后一步是为按钮添加颜色。 使用以下代码创建一个名为popup.js的文件并将其添加到扩展目录，或在<a href="https://developer.chrome.com/extensions/examples/tutorials/get_started/popup.js" target="_blank" rel="noopener">此处</a>下载。</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></pre></td><td class="code"><pre><span class="line">let changeColor = document.getElementById(&apos;changeColor&apos;);</span><br><span class="line"></span><br><span class="line">chrome.storage.sync.get(&apos;color&apos;, function(data) &#123;</span><br><span class="line">  changeColor.style.backgroundColor = data.color;</span><br><span class="line">  changeColor.setAttribute(&apos;value&apos;, data.color);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>此代码从 popup.html 获取按钮，并从存储中请求颜色值。 然后，它将颜色用作按钮的背景。 在popup.html 中将脚本标记包含到 <code>popup.js</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><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">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">...</span><br><span class="line">  &lt;body&gt;</span><br><span class="line">    &lt;button id=&quot;changeColor&quot;&gt;&lt;/button&gt;</span><br><span class="line">    &lt;script src=&quot;popup.js&quot;&gt;&lt;/script&gt;</span><br><span class="line">  &lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>重新加载扩展以查看绿色按钮。</p><h4 id="层逻辑"><a class="markdownIt-Anchor" href="#层逻辑"></a> 层逻辑</h4><p>现在，该扩展程序知道在 <a href="http://developer.chrome.com" target="_blank" rel="noopener">developer.chrome.com</a> 上用户应该可以使用该弹出窗口，并显示一个彩色按钮，但是需要逻辑来进行进一步的用户交互。 更新popup.js以包含以下代码。</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></pre></td><td class="code"><pre><span class="line">let changeColor = document.getElementById(&apos;changeColor&apos;);</span><br><span class="line">...</span><br><span class="line">changeColor.onclick = function(element) &#123;</span><br><span class="line">  let color = element.target.value;</span><br><span class="line">  chrome.tabs.query(&#123;active: true, currentWindow: true&#125;, function(tabs) &#123;</span><br><span class="line">    chrome.tabs.executeScript(</span><br><span class="line">        tabs[0].id,</span><br><span class="line">        &#123;code: &apos;document.body.style.backgroundColor = &quot;&apos; + color + &apos;&quot;;&apos;&#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>更新的代码在按钮上添加了onclick事件，该事件触发以编程方式插入的内容脚本。 这会将页面的背景色变成与按钮相同的颜色。 使用程序注入可以允许用户调用内容脚本，而不是将不需要的代码自动插入到网页中。</p><p>清单将需要activeTab权限，以允许扩展程序临时访问tabs API。 这使扩展程序可以调用tabs.executeScript。</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">   &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line"> ...</span><br><span class="line">   &quot;permissions&quot;: [&quot;activeTab&quot;, &quot;declarativeContent&quot;, &quot;storage&quot;],</span><br><span class="line"> ...</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>该扩展程序现在可以正常使用了！ 重新加载扩展程序，刷新此页面，打开弹出窗口，然后单击按钮将其变为绿色！ 但是，某些用户可能希望将背景更改为其他颜色。</p><h4 id="用户选项"><a class="markdownIt-Anchor" href="#用户选项"></a> 用户选项</h4><p>该扩展程序当前仅允许用户将背景更改为绿色。 包括一个选项页面使用户可以更好地控制扩展功能，从而进一步自定义其浏览体验。</p><p>首先在目录中创建一个名为options.html的文件，并包含以下代码，或在<a href="https://developer.chrome.com/extensions/examples/tutorials/get_started/options.html" target="_blank" rel="noopener">此处</a>下载。</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><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">  &lt;html&gt;</span><br><span class="line">    &lt;head&gt;</span><br><span class="line">      &lt;style&gt;</span><br><span class="line">        button &#123;</span><br><span class="line">          height: 30px;</span><br><span class="line">          width: 30px;</span><br><span class="line">          outline: none;</span><br><span class="line">          margin: 10px;</span><br><span class="line">        &#125;</span><br><span class="line">      &lt;/style&gt;</span><br><span class="line">    &lt;/head&gt;</span><br><span class="line">    &lt;body&gt;</span><br><span class="line">      &lt;div id=&quot;buttonDiv&quot;&gt;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">      &lt;div&gt;</span><br><span class="line">        &lt;p&gt;Choose a different background color!&lt;/p&gt;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">    &lt;/body&gt;</span><br><span class="line">    &lt;script src=&quot;options.js&quot;&gt;&lt;/script&gt;</span><br><span class="line">  &lt;/html&gt;</span><br></pre></td></tr></table></figure><p>然后在清单中注册选项页面</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;name&quot;: &quot;Getting Started Example&quot;,</span><br><span class="line">    ...</span><br><span class="line">    &quot;options_page&quot;: &quot;options.html&quot;,</span><br><span class="line">    ...</span><br><span class="line">    &quot;manifest_version&quot;: 2</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>重新加载扩展，然后单击详细信息。</p><p><img src="https://developer.chrome.com/static/images/get_started/click_details.png" alt></p><p>向下滚动详细信息页面，然后选择扩展选项以查看选项页面，尽管该页面当前将显示为空白。</p><p><img src="https://developer.chrome.com/static/images/get_started/options.png" alt></p><p>提供了四个颜色选项，然后使用 <code>onclick</code> 事件侦听器将它们生成为选项页面上的按钮。 用户单击按钮时，它将更新扩展程序全局存储中的颜色值。 由于所有扩展文件都从全局存储中提取颜色信息，因此无需更新其他值。</p><h4 id="下一步"><a class="markdownIt-Anchor" href="#下一步"></a> 下一步</h4><p>恭喜你！ 该目录现在包含一个功能齐全的Chrome扩展程序，尽管它很简单。</p><p>下一步是什么？</p><ul><li><p>“ Chrome扩展程序概述”进行了一些备份，并在总体上填写了有关扩展程序架构的许多详细信息，以及开发人员希望熟悉的一些特定概念。</p></li><li><p>在调试教程中了解可用于调试扩展的选项。</p></li><li><p>Chrome扩展程序可以访问功能强大的API，而这些API均超出开放网络所提供的范围。 chrome。* API文档将介绍每个API。</p></li><li><p>开发人员指南还有许多其他链接，这些链接指向与高级扩展创建相关的文档。</p></li></ul><p>根据CC-By 3.0许可提供的内容</p>]]></content>
    
    <summary type="html">
    
      2020年了，新年第一篇小文章！祝大家新年快乐～一直觉得浏览器是个很神秘的东西，天天在用，自以为很熟悉，也在用一些插件，有些还挺好用。所以自己也想写一个小小的插件，满足一下自己的好奇心！暂且翻译一下Chrome的插件开发的官网文档吧
    
    </summary>
    
      <category term="JavaSccript" scheme="https://wzes.github.io/categories/JavaSccript/"/>
    
    
      <category term="JavaSccript" scheme="https://wzes.github.io/tags/JavaSccript/"/>
    
      <category term="Chrome" scheme="https://wzes.github.io/tags/Chrome/"/>
    
  </entry>
  
  <entry>
    <title>Less 入门指南</title>
    <link href="https://wzes.github.io/2019/12/09/JavaScript/Less/"/>
    <id>https://wzes.github.io/2019/12/09/JavaScript/Less/</id>
    <published>2019-12-09T10:30:16.000Z</published>
    <updated>2020-01-05T10:09:15.465Z</updated>
    
    <content type="html"><![CDATA[<h3 id><a class="markdownIt-Anchor" href="#"></a> </h3><h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>啦啦啦，好久没有写Wiki了，已然成为了一名 Web 开发工程师，上周在写业务的时候用到了 less，周末了，过来总结下吧。</p><h3 id="less"><a class="markdownIt-Anchor" href="#less"></a> Less ？</h3><p>Less 是一个Css 预编译器,意思指的是它可以扩展Css语言,添加功能如允许变量(variables),混合(mixins),函数(functions) 和许多其他的技术，让你的Css更具维护性，主题性，扩展性。</p><p>Less 可运行在 Node 环境,浏览器环境和Rhino环境.同时也有3种可选工具供你编译文件和监视任何改变。</p><p>例如:</p><figure class="highlight less"><table><tr><td class="gutter"><pre><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="variable">@base:</span> <span class="number">#f938ab</span>;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.box-shadow</span>(<span class="variable">@style</span>, <span class="variable">@c</span>) <span class="keyword">when</span> (iscolor(<span class="variable">@c</span>)) &#123;</span><br><span class="line">  <span class="attribute">-webkit-box-shadow</span>: <span class="variable">@style</span> <span class="variable">@c</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>:         <span class="variable">@style</span> <span class="variable">@c</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.box-shadow</span>(<span class="variable">@style</span>, <span class="variable">@alpha</span>: <span class="number">50%</span>) <span class="keyword">when</span> (isnumber(<span class="variable">@alpha</span>)) &#123;</span><br><span class="line">  <span class="selector-class">.box-shadow</span>(<span class="variable">@style</span>, rgba(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="variable">@alpha</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: saturate(<span class="variable">@base</span>, <span class="number">5%</span>);</span><br><span class="line">  <span class="attribute">border-color</span>: lighten(<span class="variable">@base</span>, <span class="number">30%</span>);</span><br><span class="line">  <span class="selector-tag">div</span> &#123; <span class="selector-class">.box-shadow</span>(<span class="number">0</span> <span class="number">0</span> <span class="number">5px</span>, <span class="number">30%</span>) &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译为：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><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="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#fe33ac</span>;</span><br><span class="line">  <span class="attribute">border-color</span>: <span class="number">#fdcdea</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.box</span> <span class="selector-tag">div</span> &#123;</span><br><span class="line">  <span class="attribute">-webkit-box-shadow</span>: <span class="number">0</span> <span class="number">0</span> <span class="number">5px</span> <span class="built_in">rgba</span>(0, 0, 0, 0.3);</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">0</span> <span class="number">5px</span> <span class="built_in">rgba</span>(0, 0, 0, 0.3);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="开始"><a class="markdownIt-Anchor" href="#开始"></a> 开始</h3><p>如果是独立使用的话，首先安装 less</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">npm install -g less</span><br></pre></td></tr></table></figure><p>手写一个 less 文件</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">echo &apos;.test &#123; height: (1+1)  &#125;&apos; &gt; styles.less</span><br></pre></td></tr></table></figure><p>编译 less 文件，输出 css 文件</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">lessc styles.less</span><br></pre></td></tr></table></figure><p>输出</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">.test &#123;</span><br><span class="line">  height: 2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="更多姿势"><a class="markdownIt-Anchor" href="#更多姿势"></a> 更多姿势</h3><p>变量以 <code>@</code> 开头</p><h4 id="变量"><a class="markdownIt-Anchor" href="#变量"></a> 变量</h4><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">@color: white;</span><br><span class="line"></span><br><span class="line">.link &#123;</span><br><span class="line">color: @color;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译输出</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">.link &#123;</span><br><span class="line">  color: white;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="插值"><a class="markdownIt-Anchor" href="#插值"></a> 插值</h4><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></pre></td><td class="code"><pre><span class="line">@property: color;</span><br><span class="line"></span><br><span class="line">.widget &#123;</span><br><span class="line">  @&#123;property&#125;: #0ee;</span><br><span class="line">  background-@&#123;property&#125;: #999;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译输出</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">.widget &#123;</span><br><span class="line">  color: #0ee;</span><br><span class="line">  background-color: #999;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="混合"><a class="markdownIt-Anchor" href="#混合"></a> 混合</h4><p>混合可以是直接使用class或者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><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">.a, #b &#123;</span><br><span class="line">  color: red;</span><br><span class="line">&#125;</span><br><span class="line">.mixin-class &#123;</span><br><span class="line">  .a(); // 使用的时候 ➕不加 `()` 都一样的！</span><br><span class="line">&#125;</span><br><span class="line">.mixin-id &#123;</span><br><span class="line">  #b();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译输出</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></pre></td><td class="code"><pre><span class="line">.a, #b &#123;</span><br><span class="line">  color: red;</span><br><span class="line">&#125;</span><br><span class="line">.mixin-class &#123;</span><br><span class="line">  color: red;</span><br><span class="line">&#125;</span><br><span class="line">.mixin-id &#123;</span><br><span class="line">  color: red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="不输出混合集"><a class="markdownIt-Anchor" href="#不输出混合集"></a> 不输出混合集</h4><p>选择器带了括号，则编译后不输出</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></pre></td><td class="code"><pre><span class="line">.my-mixin &#123;</span><br><span class="line">  color: black;</span><br><span class="line">&#125;</span><br><span class="line">.my-other-mixin() &#123;</span><br><span class="line">  background: white;</span><br><span class="line">&#125;</span><br><span class="line">.class &#123;</span><br><span class="line">  .my-mixin;</span><br><span class="line">  .my-other-mixin;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出：</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></pre></td><td class="code"><pre><span class="line">.my-mixin &#123;</span><br><span class="line">  color: black;</span><br><span class="line">&#125;</span><br><span class="line">.class &#123;</span><br><span class="line">  color: black;</span><br><span class="line">  background: white;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="带参数的混合"><a class="markdownIt-Anchor" href="#带参数的混合"></a> 带参数的混合</h4><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></pre></td><td class="code"><pre><span class="line">.border-radius(@radius: 5px) &#123;</span><br><span class="line">  -webkit-border-radius: @radius;</span><br><span class="line">     -moz-border-radius: @radius;</span><br><span class="line">          border-radius: @radius;</span><br><span class="line">&#125;</span><br><span class="line">#header &#123;</span><br><span class="line">  .border-radius;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出：</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">#header &#123;</span><br><span class="line">  -webkit-border-radius: 5px;</span><br><span class="line">  -moz-border-radius: 5px;</span><br><span class="line">  border-radius: 5px;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="导入"><a class="markdownIt-Anchor" href="#导入"></a> 导入</h4><p>可以通过 <code>@import</code> 导入 less，css 文件</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">// this-is-valid.less</span><br><span class="line">.foo &#123;</span><br><span class="line">  background: #900;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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">@import &quot;this-is-valid.less&quot;;</span><br></pre></td></tr></table></figure><p>编译输出</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">.foo &#123;</span><br><span class="line">  color: red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="guard"><a class="markdownIt-Anchor" href="#guard"></a> Guard</h4><p>使用 when 进行判断</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></pre></td><td class="code"><pre><span class="line">.mixin (@a) when (lightness(@a) &gt;= 50%) &#123;</span><br><span class="line">  background-color: black;</span><br><span class="line">&#125;</span><br><span class="line">.mixin (@a) when (lightness(@a) &lt; 50%) &#123;</span><br><span class="line">  background-color: white;</span><br><span class="line">&#125;</span><br><span class="line">.mixin (@a) &#123;</span><br><span class="line">  color: @a;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">.class1 &#123; .mixin(#ddd) &#125;</span><br><span class="line">.class2 &#123; .mixin(#555) &#125;</span><br></pre></td></tr></table></figure><p>编译输出</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></pre></td><td class="code"><pre><span class="line">.class1 &#123;</span><br><span class="line">  background-color: black;</span><br><span class="line">  color: #ddd;</span><br><span class="line">&#125;</span><br><span class="line">.class2 &#123;</span><br><span class="line">  background-color: white;</span><br><span class="line">  color: #555;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="循环"><a class="markdownIt-Anchor" href="#循环"></a> 循环</h4><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></pre></td><td class="code"><pre><span class="line">.loop(@counter) when (@counter &gt; 0) &#123;</span><br><span class="line">  .loop((@counter - 1));    // 递归调用自身</span><br><span class="line">  width: (10px * @counter); // 每次调用时产生的样式代码</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">div &#123;</span><br><span class="line">  .loop(5); // 调用循环</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译输出</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></pre></td><td class="code"><pre><span class="line">div &#123;</span><br><span class="line">  width: 10px;</span><br><span class="line">  width: 20px;</span><br><span class="line">  width: 30px;</span><br><span class="line">  width: 40px;</span><br><span class="line">  width: 50px;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="合并"><a class="markdownIt-Anchor" href="#合并"></a> 合并</h4><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></pre></td><td class="code"><pre><span class="line">.mixin() &#123;</span><br><span class="line">  box-shadow+: inset 0 0 10px #555;</span><br><span class="line">&#125;</span><br><span class="line">.myclass &#123;</span><br><span class="line">  .mixin();</span><br><span class="line">  box-shadow+: 0 0 20px black;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译输出</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">.myclass &#123;</span><br><span class="line">  box-shadow: inset 0 0 10px #555, 0 0 20px black;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="思考"><a class="markdownIt-Anchor" href="#思考"></a> 思考</h3><p>王境泽-真香！</p><h3 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h3><p><a href="https://www.html.cn/doc/less" target="_blank" rel="noopener">https://www.html.cn/doc/less</a></p>]]></content>
    
    <summary type="html">
    
      啦啦啦，好久没有写Wiki了，已然成为了一名 Web 开发工程师，上周在写业务的时候用到了 less，周末了，过来总结下吧。
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Javascript 单元测试</title>
    <link href="https://wzes.github.io/2019/11/24/JavaScript/Javascript%20Test/"/>
    <id>https://wzes.github.io/2019/11/24/JavaScript/Javascript Test/</id>
    <published>2019-11-24T11:09:16.000Z</published>
    <updated>2019-12-21T04:27:37.884Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>又是一周过去了，前两天去了 TP，还是挺累的，但文章不能停，虽然有点水，但还是要继续的。这周就学习一下前端的单元测试框架吧，尤其是 UI 测试</p><h2 id="测试框架"><a class="markdownIt-Anchor" href="#测试框架"></a> 测试框架</h2><ul><li>Jest</li><li>Mocha</li><li>Jasmine</li></ul><h3 id="jest"><a class="markdownIt-Anchor" href="#jest"></a> Jest</h3><p>Jest 是 Facebook 出品的测试框架，是非常优秀的～其提供了 快照测试，对于 UI 测试是很有帮助的</p><h4 id="基本使用"><a class="markdownIt-Anchor" href="#基本使用"></a> 基本使用</h4><p>使用 npm 安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --save-dev jest</span><br></pre></td></tr></table></figure><p>让我们开始为一个假设函数编写测试，该函数将两个数字相加。 首先，创建一个 <code>sum.js</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sum</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = sum;</span><br></pre></td></tr></table></figure><p>然后，创建一个名为 <code>sum.test.js</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> sum = <span class="built_in">require</span>(<span class="string">'../src/lib/sum'</span>);</span><br><span class="line"></span><br><span class="line">test(<span class="string">'adds 1 + 2 to equal 3'</span>, () =&gt; &#123;</span><br><span class="line">  expect(sum(<span class="number">1</span>, <span class="number">2</span>)).toBe(<span class="number">3</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>将下面的配置部分添加到你的 <code>package.json</code> 里面：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"scripts"</span>: &#123;</span><br><span class="line">    <span class="attr">"test"</span>: <span class="string">"jest test"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后，运行 <code>yarn test</code> 或 <code>npm run test</code> ，Jest 将打印下面这个消息：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PASS  ./sum.test.js</span><br><span class="line">✓ adds 1 + 2 to equal 3 (5ms)</span><br></pre></td></tr></table></figure><p>也可以使用hook钩子，使用 describe，完成一个测试套件，</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></pre></td><td class="code"><pre><span class="line">const sum = require(&apos;../src/lib/sum&apos;);</span><br><span class="line"></span><br><span class="line">describe(&apos;sum&apos;, () =&gt; &#123;</span><br><span class="line">  beforeAll(() =&gt; &#123;</span><br><span class="line">    //</span><br><span class="line">    console.log(&quot;before all&quot;)</span><br><span class="line">  &#125;)</span><br><span class="line">  test(&apos;adds 1 + 2 to equal 3&apos;, () =&gt; &#123;</span><br><span class="line">    console.log(&quot;exec test&quot;)</span><br><span class="line">    expect(sum(1, 2)).toBe(3)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  afterAll(() =&gt; &#123;</span><br><span class="line">    //</span><br><span class="line">    console.log(&quot;after all&quot;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>输出</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></pre></td><td class="code"><pre><span class="line"> PASS  test/sum.test.js</span><br><span class="line">  sum</span><br><span class="line">    ✓ adds 1 + 2 to equal 3 (3ms)</span><br><span class="line"></span><br><span class="line">  console.log test/sum.test.js:6</span><br><span class="line">    before all</span><br><span class="line"></span><br><span class="line">  console.log test/sum.test.js:9</span><br><span class="line">    exec test</span><br><span class="line"></span><br><span class="line">  console.log test/sum.test.js:15</span><br><span class="line">    after all</span><br><span class="line"></span><br><span class="line">Test Suites: 1 passed, 1 total</span><br><span class="line">Tests:       1 passed, 1 total</span><br><span class="line">Snapshots:   0 total</span><br><span class="line">Time:        0.819s, estimated 1s</span><br><span class="line">Ran all test suites matching /test/i.</span><br></pre></td></tr></table></figure><h4 id="快照测试"><a class="markdownIt-Anchor" href="#快照测试"></a> 快照测试</h4><p>每当你想要确保你的UI不会有意外的改变，快照测试是非常有用的工具。</p><p>安装 <code>react-test-renderer</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><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">import React from &apos;react&apos;;</span><br><span class="line">import Link from &apos;../Link.react&apos;;</span><br><span class="line">import renderer from &apos;react-test-renderer&apos;;</span><br><span class="line"></span><br><span class="line">it(&apos;renders correctly&apos;, () =&gt; &#123;</span><br><span class="line">  const tree = renderer</span><br><span class="line">    .create(&lt;Link page=&quot;http://www.facebook.com&quot;&gt;Facebook&lt;/Link&gt;)</span><br><span class="line">    .toJSON();</span><br><span class="line">  expect(tree).toMatchSnapshot();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>运行以后会生成 snapshot 文件</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></pre></td><td class="code"><pre><span class="line">exports[`renders correctly 1`] = `</span><br><span class="line">&lt;a</span><br><span class="line">  className=&quot;normal&quot;</span><br><span class="line">  href=&quot;http://www.facebook.com&quot;</span><br><span class="line">  onMouseEnter=&#123;[Function]&#125;</span><br><span class="line">  onMouseLeave=&#123;[Function]&#125;</span><br><span class="line">&gt;</span><br><span class="line">  Facebook</span><br><span class="line">&lt;/a&gt;</span><br><span class="line">`;</span><br></pre></td></tr></table></figure><p>更新后</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></pre></td><td class="code"><pre><span class="line">// Updated test case with a Link to a different address</span><br><span class="line">it(&apos;renders correctly&apos;, () =&gt; &#123;</span><br><span class="line">  const tree = renderer</span><br><span class="line">    .create(&lt;Link page=&quot;http://www.instagram.com&quot;&gt;Instagram&lt;/Link&gt;)</span><br><span class="line">    .toJSON();</span><br><span class="line">  expect(tree).toMatchSnapshot();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>再次运行，会出现不一致的报错</p><p><img src="https://jestjs.io/img/content/failedSnapshotTest.png" alt="failedSnapshotTest"></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">jest --updateSnapshot</span><br></pre></td></tr></table></figure><h4 id="enzyme-ui测试"><a class="markdownIt-Anchor" href="#enzyme-ui测试"></a> Enzyme UI测试</h4><p>Enzyme是Airbnb开源的React测试工具库</p><h4 id="三种渲染方法"><a class="markdownIt-Anchor" href="#三种渲染方法"></a> 三种渲染方法</h4><ol><li>shallow：浅渲染，是对官方的Shallow Renderer的封装。将组件渲染成虚拟DOM对象，只会渲染第一层，子组件将不会被渲染出来，使得效率非常高。不需要DOM环境， 并可以使用jQuery的方式访问组件的信息</li><li>render：静态渲染，它将React组件渲染成静态的HTML字符串，然后使用Cheerio这个库解析这段字符串，并返回一个Cheerio的实例对象，可以用来分析组件的html结构</li><li>mount：完全渲染，它将组件渲染加载成一个真实的DOM节点，用来测试DOM API的交互和组件的生命周期。用到了jsdom来模拟浏览器环境</li></ol><p>三种方法中，shallow和mount因为返回的是DOM对象，可以用simulate进行交互模拟，而render方法不可以。一般shallow方法就可以满足需求，如果需要对子组件进行判断，需要使用render，如果需要测试组件的生命周期，需要使用mount方法。</p><h3 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h3><p>Jest 博大精深，功能非常完善</p>]]></content>
    
    <summary type="html">
    
      又是一周过去了，前两天去了 TP，还是挺累的，但文章不能停，虽然有点水，但还是要继续的。这周就学习一下前端的单元测试框架吧
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="Test" scheme="https://wzes.github.io/tags/Test/"/>
    
  </entry>
  
  <entry>
    <title>Koa-ejs 入门</title>
    <link href="https://wzes.github.io/2019/11/17/JavaScript/koa-ejs/"/>
    <id>https://wzes.github.io/2019/11/17/JavaScript/koa-ejs/</id>
    <published>2019-11-17T04:56:00.000Z</published>
    <updated>2019-11-17T13:00:00.951Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>又是一周过去了，该给自己充充电了，使用了 koa 作为服务端渲染的服务引擎，那么使用 koa-ejs 作为 HTML 输出也是顺其自然的事了</p><h3 id="开始"><a class="markdownIt-Anchor" href="#开始"></a> 开始</h3><p>安装 koa，koa-ejs</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">npm install koa koa-ejs</span><br></pre></td></tr></table></figure><p>index.js 目录，创建 <code>koa</code> 对象，koa-ejs 返回一个 <code>render</code> 对象，通过这个对象给 ctx 对象进行 render 赋值，之后就可以在路由中使用 <code>ctx.render</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><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">const Koa = require(&apos;koa&apos;)</span><br><span class="line">const render = require(&apos;koa-ejs&apos;);</span><br><span class="line"></span><br><span class="line">const app = new Koa();</span><br><span class="line"></span><br><span class="line">render(app, &#123;</span><br><span class="line">// 模版根目录</span><br><span class="line">  root: path.join(__dirname, &apos;view&apos;),</span><br><span class="line">  // 标准模版名字</span><br><span class="line">  layout: &apos;template&apos;,</span><br><span class="line">  // 模版后缀，默认是 html</span><br><span class="line">  viewExt: &apos;html&apos;,</span><br><span class="line">  // 是否使用缓存</span><br><span class="line">  cache: false,</span><br><span class="line">  // 是否开启 debug</span><br><span class="line">  debug: false,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.use(async function (ctx) &#123;</span><br><span class="line">  const users = [&#123; name: &apos;Dead Horse&apos; &#125;, &#123; name: &apos;Jack&apos; &#125;, &#123; name: &apos;Tom&apos; &#125;];</span><br><span class="line">  await ctx.render(&apos;content&apos;, &#123;</span><br><span class="line">    users</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>template.html 文件</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></pre></td><td class="code"><pre><span class="line">&lt;html&gt;</span><br><span class="line">  &lt;head&gt;</span><br><span class="line">    &lt;title&gt;koa ejs&lt;/title&gt;</span><br><span class="line">  &lt;/head&gt;</span><br><span class="line">  &lt;body&gt;</span><br><span class="line">    &lt;h3&gt;koa ejs&lt;/h3&gt;</span><br><span class="line">    &lt;%- body %&gt;</span><br><span class="line">  &lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>content.html 文件</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">&lt;div&gt;</span><br><span class="line">  &lt;% include user.html %&gt;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><p>user.html</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">&lt;ul&gt;</span><br><span class="line">  &lt;% users.forEach(function (user) &#123;%&gt;</span><br><span class="line">    &lt;li&gt;&lt;%= user.name %&gt;&lt;/li&gt;</span><br><span class="line">  &lt;% &#125;)%&gt;</span><br><span class="line">&lt;/ul&gt;</span><br></pre></td></tr></table></figure><h5 id="输出"><a class="markdownIt-Anchor" href="#输出"></a> 输出</h5><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">Dead Horse</span><br><span class="line">Jack</span><br><span class="line">Tom</span><br></pre></td></tr></table></figure><h3 id="源码解析"><a class="markdownIt-Anchor" href="#源码解析"></a> 源码解析</h3><p>其实 koa-ejs 的源码很简单，主要是引用了 ejs 框架，简单做了封装，将 render 挂载到 ctx 上</p><h5 id="入口"><a class="markdownIt-Anchor" href="#入口"></a> 入口</h5><p>当引入的时候，执行了 render 就会进行初始化，并且声明了 render 的异步方法，主要是获取 template 文件，然后使用 <code>ejs.compile</code> 去构造函数，该方法主要的作用就是替换 ejs 中的变量，生成 html。如果有缓存，那么则不读取文件了，直接进行替换</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><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></pre></td><td class="code"><pre><span class="line">exports = module.exports = function (app, settings) &#123;</span><br><span class="line">  if (app.context.render) &#123;</span><br><span class="line">    return;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  settings.root = path.resolve(process.cwd(), settings.root);</span><br><span class="line">  </span><br><span class="line">  /**</span><br><span class="line">   * cache the generate package</span><br><span class="line">   * @type &#123;Object&#125;</span><br><span class="line">   */</span><br><span class="line">  const cache = Object.create(null);</span><br><span class="line"></span><br><span class="line">  settings = Object.assign(&#123;&#125;, defaultSettings, settings);</span><br><span class="line"></span><br><span class="line">  settings.viewExt = settings.viewExt</span><br><span class="line">    ? &apos;.&apos; + settings.viewExt.replace(/^\./, &apos;&apos;)</span><br><span class="line">    : &apos;&apos;;</span><br><span class="line">    </span><br><span class="line">  async function render(view, options) &#123;</span><br><span class="line">    view += settings.viewExt;</span><br><span class="line">    const viewPath = path.join(settings.root, view);</span><br><span class="line">    debug(`render: $&#123;viewPath&#125;`);</span><br><span class="line">    // get from cache</span><br><span class="line">    if (settings.cache &amp;&amp; cache[viewPath]) &#123;</span><br><span class="line">      return cache[viewPath].call(options.scope, options);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    const tpl = await fs.readFile(viewPath, &apos;utf8&apos;);</span><br><span class="line"></span><br><span class="line">    // override `ejs` node_module `resolveInclude` function</span><br><span class="line">    ejs.resolveInclude = function(name, filename, isDir) &#123;</span><br><span class="line">      if (!path.extname(name)) &#123;</span><br><span class="line">        name += settings.viewExt;</span><br><span class="line">      &#125;</span><br><span class="line">      return parentResolveInclude(name, filename, isDir);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    const fn = ejs.compile(tpl, &#123;</span><br><span class="line">      filename: viewPath,</span><br><span class="line">      _with: settings._with,</span><br><span class="line">      compileDebug: settings.debug &amp;&amp; settings.compileDebug,</span><br><span class="line">      debug: settings.debug,</span><br><span class="line">      delimiter: settings.delimiter,</span><br><span class="line">      cache: settings.cache,</span><br><span class="line">      async: settings.async</span><br><span class="line">    &#125;);</span><br><span class="line">    if (settings.cache) &#123;</span><br><span class="line">      cache[viewPath] = fn;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return fn.call(options.scope, options);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  app.context.render = async function (view, _context) &#123;</span><br><span class="line">    //</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>最后将为 app 的 context 新增 render 方法</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><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">app.context.render = async function (view, _context) &#123;</span><br><span class="line">  const ctx = this;</span><br><span class="line"></span><br><span class="line">  const context = Object.assign(&#123;&#125;, ctx.state, _context);</span><br><span class="line">// 获取 html</span><br><span class="line">  let html = await render(view, context);</span><br><span class="line">// 如果使用了 layout，也就是有二级页面，再一次渲染</span><br><span class="line">  const layout = context.layout === false ? false : (context.layout || settings.layout);</span><br><span class="line">  if (layout) &#123;</span><br><span class="line">    // if using layout</span><br><span class="line">    context.body = html;</span><br><span class="line">    html = await render(layout, context);</span><br><span class="line">  &#125;</span><br><span class="line">// 直接赋值给 body</span><br><span class="line">  const writeResp = context.writeResp === false ? false : (context.writeResp || settings.writeResp);</span><br><span class="line">  </span><br><span class="line">  if (writeResp) &#123;</span><br><span class="line">    // normal operation</span><br><span class="line">    ctx.type = &apos;html&apos;;</span><br><span class="line">    ctx.body = html;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    // only return the html</span><br><span class="line">    return html;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>之后就可以使用 ctx.render</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">await ctx.render(&apos;content&apos;, &#123;</span><br><span class="line">    users</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>非常简单的源码，包含了缓存等逻辑，基本满足我们的日常需要</p><h3 id="思考"><a class="markdownIt-Anchor" href="#思考"></a> 思考</h3><p>🤔好好学习</p>]]></content>
    
    <summary type="html">
    
      又是一周过去了，该给自己充充电了，使用了 koa 作为服务端渲染的服务引擎，那么使用 koa-ejs 作为 HTML 输出也是顺其自然的事了
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="Koa" scheme="https://wzes.github.io/tags/Koa/"/>
    
  </entry>
  
  <entry>
    <title>NextJS 入门指南</title>
    <link href="https://wzes.github.io/2019/11/09/JavaScript/NextJS/"/>
    <id>https://wzes.github.io/2019/11/09/JavaScript/NextJS/</id>
    <published>2019-11-09T10:30:16.000Z</published>
    <updated>2019-11-10T07:50:48.873Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>最近在研究SSR服务端渲染，NextJS 算是比较经典的框架了，所以了解了解其用法对SSR或许能加深理解。如果能了解其实现原理，那就更好了。</p><h3 id="服务端渲染"><a class="markdownIt-Anchor" href="#服务端渲染"></a> 服务端渲染</h3><p>简单理解就是：你访问网页的时候，会一次性把完整的HTML返回给你。区别于 React，Vue 项目，HTML 文档只是一个壳子，需要运行 js 才能得到首屏的 Dom。</p><h3 id="简介"><a class="markdownIt-Anchor" href="#简介"></a> 简介</h3><p><strong>Next.js</strong> 是一个轻量级的 React 服务端渲染应用框架。</p><h3 id="get-started"><a class="markdownIt-Anchor" href="#get-started"></a> Get Started</h3><p>初始化项目，安装 next</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">npm install --save next react react-dom</span><br></pre></td></tr></table></figure><p>将下面脚本添加到 package.json 中:</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;scripts&quot;: &#123;</span><br><span class="line">    &quot;dev&quot;: &quot;next&quot;,</span><br><span class="line">    &quot;build&quot;: &quot;next build&quot;,</span><br><span class="line">    &quot;start&quot;: &quot;next start&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>新建 <code>./pages/index.js</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">export default () =&gt; &lt;div&gt;Welcome to next.js!&lt;/div&gt;</span><br></pre></td></tr></table></figure><p>运行 <code>npm run dev</code> 命令并打开 <code>http://localhost:3000</code>。 如果你想使用其他端口，可运行 <code>npm run dev -- -p &lt;设置端口号&gt;</code>.</p><h3 id="如何使用服务端渲染"><a class="markdownIt-Anchor" href="#如何使用服务端渲染"></a> 如何使用服务端渲染？</h3><p>默认支持服务端渲染，获取网络请求写在 getInitialProps 内部，就可以在 render 中获取数据，直接渲染。</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></pre></td><td class="code"><pre><span class="line">import React from &apos;react&apos;</span><br><span class="line"></span><br><span class="line">export default class extends React.Component &#123;</span><br><span class="line">  static async getInitialProps(&#123; req &#125;) &#123;</span><br><span class="line">    const userAgent = req ? req.headers[&apos;user-agent&apos;] : navigator.userAgent</span><br><span class="line">    return &#123; userAgent &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  render() &#123;</span><br><span class="line">    return (</span><br><span class="line">      &lt;div&gt;</span><br><span class="line">        Hello World  &#123;this.props.userAgent&#125;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="原理"><a class="markdownIt-Anchor" href="#原理"></a> 原理</h3><h4 id="应用入口"><a class="markdownIt-Anchor" href="#应用入口"></a> 应用入口</h4><p>使用了 node command line，能让我们仅仅写了 page 的代码，就能让整个应用跑起来。以至于我们都找不到入口。nextjs 真的是很厉害～封装的很好</p><p>可以看到，当我们使用 <code>yarn start</code> 的时候，应用执行了 next-start</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></pre></td><td class="code"><pre><span class="line">const commands: &#123; [command: string]: () =&gt; Promise&lt;cliCommand&gt; &#125; = &#123;</span><br><span class="line">  build: async () =&gt; await import(&apos;../cli/next-build&apos;).then(i =&gt; i.nextBuild),</span><br><span class="line">  start: async () =&gt; await import(&apos;../cli/next-start&apos;).then(i =&gt; i.nextStart),</span><br><span class="line">  export: async () =&gt;</span><br><span class="line">    await import(&apos;../cli/next-export&apos;).then(i =&gt; i.nextExport),</span><br><span class="line">  dev: async () =&gt; await import(&apos;../cli/next-dev&apos;).then(i =&gt; i.nextDev),</span><br><span class="line">  telemetry: async () =&gt;</span><br><span class="line">    await import(&apos;../cli/next-telemetry&apos;).then(i =&gt; i.nextTelemetry),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>next-start 开启了一个服务</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></pre></td><td class="code"><pre><span class="line">startServer(&#123; dir &#125;, port, args[&apos;--hostname&apos;])</span><br><span class="line">  .then(async app =&gt; &#123;</span><br><span class="line">    // tslint:disable-next-line</span><br><span class="line">    console.log(</span><br><span class="line">      `&gt; Ready on http://$&#123;args[&apos;--hostname&apos;] || &apos;localhost&apos;&#125;:$&#123;port&#125;`</span><br><span class="line">    )</span><br><span class="line">    await app.prepare()</span><br><span class="line">  &#125;)</span><br><span class="line">  .catch(err =&gt; &#123;</span><br><span class="line">    // tslint:disable-next-line</span><br><span class="line">    console.error(err)</span><br><span class="line">    process.exit(1)</span><br><span class="line">  &#125;)</span><br></pre></td></tr></table></figure><p>startServer 的实现，获取 next 实例，然后使用 http 创建服务，主要的请求逻辑的代码都在 next 的 getRequestHandler 里面</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></pre></td><td class="code"><pre><span class="line">export default async function start(</span><br><span class="line">  serverOptions: any,</span><br><span class="line">  port?: number,</span><br><span class="line">  hostname?: string</span><br><span class="line">) &#123;</span><br><span class="line">  const app = next(serverOptions)</span><br><span class="line">  const srv = http.createServer(app.getRequestHandler())</span><br><span class="line">  await new Promise((resolve, reject) =&gt; &#123;</span><br><span class="line">    // This code catches EADDRINUSE error if the port is already in use</span><br><span class="line">    srv.on(&apos;error&apos;, reject)</span><br><span class="line">    srv.on(&apos;listening&apos;, () =&gt; resolve())</span><br><span class="line">    srv.listen(port, hostname)</span><br><span class="line">  &#125;)</span><br><span class="line">  // It&apos;s up to caller to run `app.prepare()`, so it can notify that the server</span><br><span class="line">  // is listening before starting any intensive operations.</span><br><span class="line">  return app</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>handleRequest 处理请求</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></pre></td><td class="code"><pre><span class="line">private handleRequest(</span><br><span class="line">  req: IncomingMessage,</span><br><span class="line">  res: ServerResponse,</span><br><span class="line">  parsedUrl?: UrlWithParsedQuery</span><br><span class="line">): Promise&lt;void&gt; &#123;</span><br><span class="line">  res.statusCode = 200</span><br><span class="line">  return this.run(req, res, parsedUrl).catch(err =&gt; &#123;</span><br><span class="line">    this.logError(err)</span><br><span class="line">    res.statusCode = 500</span><br><span class="line">    res.end(&apos;Internal Server Error&apos;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>run 获取 route 实例并进行处理</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><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">protected async run(</span><br><span class="line">  req: IncomingMessage,</span><br><span class="line">  res: ServerResponse,</span><br><span class="line">  parsedUrl: UrlWithParsedQuery</span><br><span class="line">) &#123;</span><br><span class="line">  this.handleCompression(req, res)</span><br><span class="line">  try &#123;</span><br><span class="line">    const fn = this.router.match(req, res, parsedUrl)</span><br><span class="line">    if (fn) &#123;</span><br><span class="line">      await fn()</span><br><span class="line">      return</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; catch (err) &#123;</span><br><span class="line">    if (err.code === &apos;DECODE_FAILED&apos;) &#123;</span><br><span class="line">      res.statusCode = 400</span><br><span class="line">      return this.renderError(null, req, res, &apos;/_error&apos;, &#123;&#125;)</span><br><span class="line">    &#125;</span><br><span class="line">    throw err</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  await this.render404(req, res, parsedUrl)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>router 是在项目初始化的时候进行创建的，其中一个就是根据，默认都会有很多路由处理器</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><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></pre></td><td class="code"><pre><span class="line">protected generateRoutes(): Route[] &#123;</span><br><span class="line">  const publicRoutes = fs.existsSync(this.publicDir)</span><br><span class="line">    ? this.generatePublicRoutes()</span><br><span class="line">    : []</span><br><span class="line">  const staticFilesRoute = fs.existsSync(join(this.dir, &apos;static&apos;))</span><br><span class="line">    ? [</span><br><span class="line">        &#123;</span><br><span class="line">          match: route(&apos;/static/:path*&apos;),</span><br><span class="line">          fn: async (req, res, params, parsedUrl) =&gt; &#123;</span><br><span class="line">            const p = join(this.dir, &apos;static&apos;, ...(params.path || []))</span><br><span class="line">            await this.serveStatic(req, res, p, parsedUrl)</span><br><span class="line">          &#125;,</span><br><span class="line">        &#125; as Route,</span><br><span class="line">      ]</span><br><span class="line">    : []</span><br><span class="line">  const routes: Route[] = [</span><br><span class="line">    &#123;</span><br><span class="line">      match: route(&apos;/_next/static/:path*&apos;),</span><br><span class="line">      fn: async (req, res, params, parsedUrl) =&gt; &#123;</span><br><span class="line">        await this.serveStatic(req, res, p, parsedUrl)</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      match: route(&apos;/_next/data/:path*&apos;),</span><br><span class="line">      fn: async (req, res, params, _parsedUrl) =&gt; &#123;</span><br><span class="line">        await this.render(</span><br><span class="line">          req,</span><br><span class="line">          res,</span><br><span class="line">          pathname,</span><br><span class="line">          &#123; _nextSprData: &apos;1&apos; &#125;,</span><br><span class="line">          parsedUrl</span><br><span class="line">        )</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      match: route(&apos;/_next/:path*&apos;),</span><br><span class="line">      fn: async (req, res, _params, parsedUrl) =&gt; &#123;</span><br><span class="line">        await this.render404(req, res, parsedUrl)</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    ...publicRoutes,</span><br><span class="line">    ...staticFilesRoute,</span><br><span class="line">    &#123;</span><br><span class="line">      match: route(&apos;/api/:path*&apos;),</span><br><span class="line">      fn: async (req, res, params, parsedUrl) =&gt; &#123;</span><br><span class="line">        const &#123; pathname &#125; = parsedUrl</span><br><span class="line">        await this.handleApiRequest(</span><br><span class="line">          req as NextApiRequest,</span><br><span class="line">          res as NextApiResponse,</span><br><span class="line">          pathname!</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><br><span class="line">  if (this.nextConfig.useFileSystemPublicRoutes) &#123;</span><br><span class="line">    this.dynamicRoutes = this.getDynamicRoutes()</span><br><span class="line">    routes.push(&#123;</span><br><span class="line">      match: route(&apos;/:path*&apos;),</span><br><span class="line">      fn: async (req, res, _params, parsedUrl) =&gt; &#123;</span><br><span class="line">        const &#123; pathname, query &#125; = parsedUrl</span><br><span class="line">        if (!pathname) &#123;</span><br><span class="line">          throw new Error(&apos;pathname is undefined&apos;)</span><br><span class="line">        &#125;</span><br><span class="line">        await this.render(req, res, pathname, query, parsedUrl)</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">  return routes</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>默认页面路由都是走到最后，也就是执行 render，获取渲染的 html</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">public async render(</span><br><span class="line">  req: IncomingMessage,</span><br><span class="line">  res: ServerResponse,</span><br><span class="line">  pathname: string,</span><br><span class="line">  query: ParsedUrlQuery = &#123;&#125;,</span><br><span class="line">  parsedUrl?: UrlWithParsedQuery</span><br><span class="line">): Promise&lt;void&gt; &#123;</span><br><span class="line">  const url: any = req.url</span><br><span class="line">  if (isInternalUrl(url)) &#123;</span><br><span class="line">    return this.handleRequest(req, res, parsedUrl)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (isBlockedPage(pathname)) &#123;</span><br><span class="line">    return this.render404(req, res, parsedUrl)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  const html = await this.renderToHTML(req, res, pathname, query, &#123;</span><br><span class="line">    dataOnly:</span><br><span class="line">      (this.renderOpts.ampBindInitData &amp;&amp; Boolean(query.dataOnly)) ||</span><br><span class="line">      (req.headers &amp;&amp;</span><br><span class="line">        (req.headers.accept || &apos;&apos;).indexOf(&apos;application/amp.bind+json&apos;) !==</span><br><span class="line">          -1),</span><br><span class="line">  &#125;)</span><br><span class="line">  // Request was ended by the user</span><br><span class="line">  if (html === null) &#123;</span><br><span class="line">    return</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return this.sendHTML(req, res, html)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 renderToHTML 实现了具体逻辑，通过 sendHTML 返回页面 HTML</p><p>renderToHTML 实现，首先通过 findPageComponents 获取路由具体页面 Compenent，然后使用renderToHTMLWithComponents 进行 React 转换为 HTML</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></pre></td><td class="code"><pre><span class="line">public renderToHTML(</span><br><span class="line">    ...</span><br><span class="line">    ): Promise&lt;string | null&gt; &#123;</span><br><span class="line">    return this.findPageComponents(pathname, query)</span><br><span class="line">      .then(</span><br><span class="line">        result =&gt; &#123;</span><br><span class="line">          return this.renderToHTMLWithComponents(</span><br><span class="line">          ....</span><br><span class="line">            result,</span><br><span class="line">            &#123; ...this.renderOpts, amphtml, hasAmp, dataOnly &#125;</span><br><span class="line">          )</span><br><span class="line">        &#125;,</span><br><span class="line">      )</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>renderToHTML，在 renderToHTMLWithComponents 内部执行</p><p>省略了很多代码，为了了解主要逻辑。</p><p>首先会执行 loadGetInitialProps，也就是业务代码中的 props 获取，运行在服务端。</p><p>然后使用 Document 的 getInitialProps 创建 Document，并把 component 赋值，获取最终的 html</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br></pre></td><td class="code"><pre><span class="line">export async function renderToHTML(</span><br><span class="line">  req: IncomingMessage,</span><br><span class="line">  res: ServerResponse,</span><br><span class="line">  pathname: string,</span><br><span class="line">  query: ParsedUrlQuery,</span><br><span class="line">  renderOpts: RenderOpts</span><br><span class="line">): Promise&lt;string | null&gt; &#123;</span><br><span class="line">....</span><br><span class="line">  let props: any</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  const AppContainer = (&#123; children &#125;: any) =&gt; (</span><br><span class="line">    &lt;RouterContext.Provider value=&#123;router&#125;&gt;</span><br><span class="line">      &lt;DataManagerContext.Provider value=&#123;dataManager&#125;&gt;</span><br><span class="line">        &lt;AmpStateContext.Provider value=&#123;ampState&#125;&gt;</span><br><span class="line">          &lt;LoadableContext.Provider</span><br><span class="line">            value=&#123;moduleName =&gt; reactLoadableModules.push(moduleName)&#125;</span><br><span class="line">          &gt;</span><br><span class="line">            &#123;children&#125;</span><br><span class="line">          &lt;/LoadableContext.Provider&gt;</span><br><span class="line">        &lt;/AmpStateContext.Provider&gt;</span><br><span class="line">      &lt;/DataManagerContext.Provider&gt;</span><br><span class="line">    &lt;/RouterContext.Provider&gt;</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line">  try &#123;</span><br><span class="line">    props = await loadGetInitialProps(App, &#123;</span><br><span class="line">      AppTree: ctx.AppTree,</span><br><span class="line">      Component,</span><br><span class="line">      router,</span><br><span class="line">      ctx,</span><br><span class="line">    &#125;)</span><br><span class="line">    ......</span><br><span class="line"> &#125;</span><br><span class="line"> ....</span><br><span class="line">    renderPage = (</span><br><span class="line">      options: ComponentsEnhancer = &#123;&#125;</span><br><span class="line">    ): &#123; html: string; head: any &#125; =&gt; &#123;</span><br><span class="line">      const renderError = renderPageError()</span><br><span class="line">      if (renderError) return renderError</span><br><span class="line"></span><br><span class="line">      const &#123;</span><br><span class="line">        App: EnhancedApp,</span><br><span class="line">        Component: EnhancedComponent,</span><br><span class="line">      &#125; = enhanceComponents(options, App, Component)</span><br><span class="line"></span><br><span class="line">      return render(</span><br><span class="line">        renderElementToString,</span><br><span class="line">        &lt;AppContainer&gt;</span><br><span class="line">          &lt;EnhancedApp</span><br><span class="line">            Component=&#123;EnhancedComponent&#125;</span><br><span class="line">            router=&#123;router&#125;</span><br><span class="line">            &#123;...props&#125;</span><br><span class="line">          /&gt;</span><br><span class="line">        &lt;/AppContainer&gt;,</span><br><span class="line">        ampState</span><br><span class="line">      )</span><br><span class="line">    </span><br><span class="line">  &#125;</span><br><span class="line">  const documentCtx = &#123; ...ctx, renderPage &#125;</span><br><span class="line">  const docProps = await loadGetInitialProps(Document, documentCtx)</span><br><span class="line">  // the response might be finished on the getInitialProps call</span><br><span class="line">  if (isResSent(res) &amp;&amp; !isSpr) return null</span><br><span class="line"></span><br><span class="line">  let dataManagerData = &apos;[]&apos;</span><br><span class="line">  if (dataManager) &#123;</span><br><span class="line">    dataManagerData = JSON.stringify([...dataManager.getData()])</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (!docProps || typeof docProps.html !== &apos;string&apos;) &#123;</span><br><span class="line">    const message = `&quot;$&#123;getDisplayName(</span><br><span class="line">      Document</span><br><span class="line">    )&#125;.getInitialProps()&quot; should resolve to an object with a &quot;html&quot; prop set with a valid html string`</span><br><span class="line">    throw new Error(message)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (docProps.dataOnly) &#123;</span><br><span class="line">    return dataManagerData</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  const dynamicImportIdsSet = new Set&lt;string&gt;()</span><br><span class="line">  const dynamicImports: ManifestItem[] = []</span><br><span class="line"></span><br><span class="line">  for (const mod of reactLoadableModules) &#123;</span><br><span class="line">    const manifestItem = reactLoadableManifest[mod]</span><br><span class="line"></span><br><span class="line">    if (manifestItem) &#123;</span><br><span class="line">      manifestItem.map(item =&gt; &#123;</span><br><span class="line">        dynamicImports.push(item)</span><br><span class="line">        dynamicImportIdsSet.add(item.id as string)</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">  const dynamicImportsIds = [...dynamicImportIdsSet]</span><br><span class="line">  const inAmpMode = isInAmpMode(ampState)</span><br><span class="line">  const hybridAmp = ampState.hybrid</span><br><span class="line"></span><br><span class="line">  // update renderOpts so export knows current state</span><br><span class="line">  renderOpts.inAmpMode = inAmpMode</span><br><span class="line">  renderOpts.hybridAmp = hybridAmp</span><br><span class="line"></span><br><span class="line">  let html = renderDocument(Document, &#123;</span><br><span class="line">    ...renderOpts,</span><br><span class="line">    dangerousAsPath: router.asPath,</span><br><span class="line">    dataManagerData,</span><br><span class="line">    ampState,</span><br><span class="line">    props,</span><br><span class="line">    headTags: await headTags(documentCtx),</span><br><span class="line">    bodyTags: await bodyTags(documentCtx),</span><br><span class="line">    htmlProps: await htmlProps(documentCtx),</span><br><span class="line">    docProps,</span><br><span class="line">    pathname,</span><br><span class="line">    ampPath,</span><br><span class="line">    query,</span><br><span class="line">    inAmpMode,</span><br><span class="line">    hybridAmp,</span><br><span class="line">    dynamicImportsIds,</span><br><span class="line">    dynamicImports,</span><br><span class="line">    files,</span><br><span class="line">    devFiles,</span><br><span class="line">    polyfillFiles,</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  if (inAmpMode &amp;&amp; html) &#123;</span><br><span class="line">    // use replace to allow rendering directly to body in AMP mode</span><br><span class="line">    html = html.replace(</span><br><span class="line">      &apos;__NEXT_AMP_RENDER_TARGET__&apos;,</span><br><span class="line">      `&lt;!-- __NEXT_DATA__ --&gt;$&#123;docProps.html&#125;`</span><br><span class="line">    )</span><br><span class="line">    html = await optimizeAmp(html)</span><br><span class="line"></span><br><span class="line">    if (renderOpts.ampValidator) &#123;</span><br><span class="line">      await renderOpts.ampValidator(html, pathname)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (inAmpMode || hybridAmp) &#123;</span><br><span class="line">    // fix &amp;amp being escaped for amphtml rel link</span><br><span class="line">    html = html.replace(/&amp;amp;amp=1/g, &apos;&amp;amp=1&apos;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return html</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用 renderElementToString 渲染获取 html</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></pre></td><td class="code"><pre><span class="line">function render(</span><br><span class="line">  renderElementToString: (element: React.ReactElement&lt;any&gt;) =&gt; string,</span><br><span class="line">  element: React.ReactElement&lt;any&gt;,</span><br><span class="line">  ampMode: any</span><br><span class="line">): &#123; html: string; head: React.ReactElement[] &#125; &#123;</span><br><span class="line">  let html</span><br><span class="line">  let head</span><br><span class="line"></span><br><span class="line">  try &#123;</span><br><span class="line">    html = renderElementToString(element)</span><br><span class="line">  &#125; finally &#123;</span><br><span class="line">    head = Head.rewind() || defaultHead(isInAmpMode(ampMode))</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return &#123; html, head &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这里基本就结束了</p><p>Nextjs 还可以有很多自定的东西，与 koa，express 结合～</p><h3 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h3><p><a href="https://nextjs.org/" target="_blank" rel="noopener">https://nextjs.org/</a></p><h3 id="后续"><a class="markdownIt-Anchor" href="#后续"></a> 后续</h3><p>博大精深啊</p>]]></content>
    
    <summary type="html">
    
      最近在研究SSR服务端渲染，NextJS 算是比较经典的框架了，所以了解了解其用法对SSR或许能加深理解。如果能了解其实现原理，那就更好了
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Intersection Obersever API 中文</title>
    <link href="https://wzes.github.io/2019/10/26/JavaScript/IntersectionObserverAPI/"/>
    <id>https://wzes.github.io/2019/10/26/JavaScript/IntersectionObserverAPI/</id>
    <published>2019-10-26T07:30:16.000Z</published>
    <updated>2019-10-26T07:16:55.511Z</updated>
    
    <content type="html"><![CDATA[<h2 id="intersection-observer-简介"><a class="markdownIt-Anchor" href="#intersection-observer-简介"></a> Intersection observer 简介</h2><p><strong>Intersection Observer API</strong> 提供了一种异步观察目标元素与祖先元素或顶级文档的视口相交的方法。</p><p>在以前，检测一个元素的可见性或两个元素相对于彼此的相对可见性一直是一项艰巨的任务，其解决方案不可靠且易于导致浏览器和用户访问的网页变慢。 随着网络的成熟，对此类功能的需求也在增长。 出于多种原因需要元素的可见性信息，例如：</p><ul><li><p>滚动页面时延迟加载图像或其他内容。</p></li><li><p>实现“无限滚动”的网页，在您滚动时会加载和呈现越来越多的内容，从而使用户不必翻阅页面。</p></li><li><p>统计广告可见度，以计算广告收入。</p></li><li><p>根据用户是否会看到结果来决定是否执行任务或动画处理。</p></li></ul><p>过去实现交叉检测通常涉及到事件处理程序，并且循环调用 <code>Element.getBoundingClientRect（）</code>之类的方法来为每个受影响的元素建立所需的信息。 由于所有这些代码都在主线程上运行，因此即使只有一个，也可能会导致性能问题。 当网站加载了这些测试时，事情可能会变得很难看。</p><p>考虑使用无限滚动的网页。它使用供应商提供的库来管理在整个页面中定期放置的广告，这些广告具有动画图形，使用自定义库绘制通知框等等。每个元素都有其自己的相交检测程序，它们均在主线程上运行。该网站的作者甚至可能没有意识到这种情况的发生，因为他们可能对所使用的两个库的内部运作了解得很少。当用户滚动页面时，这些相交检测程序在滚动处理代码期间不断触发，从而导致用户对浏览器，网站及其计算机感到崩溃。</p><p><strong>Intersection Observer API</strong> 使代码可以注册一个回调函数，该回调函数将在他们希望监视的元素进入或退出另一个元素（或视口）时，或者当两个相交的量改变请求的量时执行。这样，站点不再需要在<strong>主线程</strong>上执行任何操作来监视这种元素交集，并且浏览器可以自由地优化交集的管理。</p><p>Intersection Observer API不能告诉您的一件事：重叠的确切像素数或确切地说是重叠像素。但是，它涵盖了更常见的用例：“如果它们相交N％左右，我需要做点什么。”</p><h2 id="intersection-observer-概念和使用"><a class="markdownIt-Anchor" href="#intersection-observer-概念和使用"></a> Intersection observer 概念和使用</h2><p>Intersection Observer API允许您配置一个 callback，只要一个元素（称为目标）与设备视口或指定元素相交，就会调用该 callback。 就此API而言，这称为根元素或根。 通常，您将需要注意与元素最接近的可滚动祖先相关的相交变化，或者，如果元素不是可滚动元素的后代，则要观察视口。 要注意相对于根元素的交点，请指定null。</p><p>无论您是使用视口还是其他元素作为根，API都以相同的方式工作，只要目标元素的可见性发生变化，以便与根交叉超过所需的交集量，就执行您提供的回调函数。</p><p>目标元素与其根之间的相交度为相交比。 这表示目标元素的百分比，该百分比可见为0.0到1.0之间的值。</p><h3 id="创建一个-intersection-observer"><a class="markdownIt-Anchor" href="#创建一个-intersection-observer"></a> 创建一个 intersection observer</h3><p>通过调用交集观察器的构造函数并向其传递一个回调函数来创建交集观察者，只要在一个方向或另一个方向上超过阈值，该回调函数便会运行：</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></pre></td><td class="code"><pre><span class="line">let options = &#123;</span><br><span class="line">  root: document.querySelector(&apos;#scrollArea&apos;),</span><br><span class="line">  rootMargin: &apos;0px&apos;,</span><br><span class="line">  threshold: 1.0</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let observer = new IntersectionObserver(callback, options);</span><br></pre></td></tr></table></figure><p>阈值1.0表示在root选项指定的元素中可见目标的100％时，将调用回调。</p><h4 id="intersection-observer-options-参数"><a class="markdownIt-Anchor" href="#intersection-observer-options-参数"></a> Intersection observer options 参数</h4><p>传递给 <code>IntersectionObserver（）</code>构造函数的 <code>options</code> 对象使您可以控制在哪些情况下调用观察者的回调。 它具有以下字段：</p><ul><li><p><strong>root</strong></p><p>用作检查目标可见性的视口的元素。 必须是目标的祖先。 如果未指定或为null，则默认为浏览器视口。</p></li><li><p><strong>rootMargin</strong></p><p>围绕根的边距。可以具有类似于CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/margin" target="_blank" rel="noopener"><code>margin</code></a>属性的值，例如 <code>&quot;10px 20px 30px 40px&quot;</code>（（上，右，下，左）。这些值可以是百分比。这组值用于在计算相交之前增大或缩小根元素边界框的每一侧。默认为全零。</p></li><li><p><strong>threshold</strong></p><p>一个数字或一个数字数组，指示观察者的回调应在目标可见性的百分比上执行。如果只想检测可见性何时超过50％标记，则可以使用0.5值。如果希望每次可见性再超过25％时都运行回调，则可以指定数组[0，0.25，0.5，0.75，1]。默认值为0（意味着即使可见一个像素，回调也将运行）。值为1.0意味着直到每个像素都可见，才认为阈值已通过。</p></li></ul><h4 id="定位要观察的元素"><a class="markdownIt-Anchor" href="#定位要观察的元素"></a> 定位要观察的元素</h4><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">let target = document.querySelector(&apos;#listItem&apos;);</span><br><span class="line">observer.observe(target);</span><br></pre></td></tr></table></figure><p>每当目标达到为所指定的阈值时<code>IntersectionObserver</code>，就会调用回调。回调接收<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" rel="noopener"><code>IntersectionObserverEntry</code></a>对象列表和观察者：</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></pre></td><td class="code"><pre><span class="line">let callback = (entries, observer) =&gt; &#123; </span><br><span class="line">  entries.forEach(entry =&gt; &#123;</span><br><span class="line">    // Each entry describes an intersection change for one observed</span><br><span class="line">    // target element:</span><br><span class="line">    //   entry.boundingClientRect</span><br><span class="line">    //   entry.intersectionRatio</span><br><span class="line">    //   entry.intersectionRect</span><br><span class="line">    //   entry.isIntersecting</span><br><span class="line">    //   entry.rootBounds</span><br><span class="line">    //   entry.target</span><br><span class="line">    //   entry.time</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>请注意，您的回调是在主线程上执行的。它应尽快运行；如果需要完成任何耗时的操作，请使用<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback" target="_blank" rel="noopener"><code>Window.requestIdleCallback()</code></a>。</p><p>另外，请注意，如果指定了该<code>root</code>选项，则目标必须是根元素的后代。</p><h3 id="如何计算交集"><a class="markdownIt-Anchor" href="#如何计算交集"></a> 如何计算交集</h3><p>Intersection Observer API考虑的所有区域都是矩形。形状不规则的元素被认为占据了包围元素所有部分的最小矩形。类似地，如果元素的可见部分不是矩形，则该元素的相交矩形被解释为包含该元素所有可见部分的最小矩形。</p><p>了解一点有关提供的各种属性如何<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" rel="noopener"><code>IntersectionObserverEntry</code></a>描述相交的方法很有用。</p><h4 id="交点根和根边距"><a class="markdownIt-Anchor" href="#交点根和根边距"></a> 交点根和根边距</h4><p>在跟踪元素与容器的交集之前，我们需要知道该容器是什么。该容器是<strong>交集根</strong>或<strong>根元素</strong>。这可以是文档中的特定元素（是要观察的元素的祖先），也<code>null</code>可以是文档的视口作为容器。</p><p><strong>根的相交矩形</strong>是用于所要检查的目标或目标的矩形。该矩形的确定如下：</p><ul><li>如果相交根是隐式根（即顶级<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document" target="_blank" rel="noopener"><code>Document</code></a>），则根相交矩形是视口的矩形。</li><li>如果相交根具有溢出剪辑，则根相交矩形是根元素的内容区域。</li><li>否则，根相交矩形是相交根的边界客户端矩形（通过调用<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect" target="_blank" rel="noopener"><code>getBoundingClientRect()</code></a>它返回）。</li></ul><p>创建时，可以通过设置<strong>根边缘</strong>来进一步调整根相交矩形。定义偏移量中的值添加到相交根的边界框的每一侧，以创建最终的相交根边界（在执行回调时公开）。<code>rootMargin</code><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" rel="noopener"><code>IntersectionObserver</code></a><code>rootMargin</code><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/rootBounds" target="_blank" rel="noopener"><code>IntersectionObserverEntry.rootBounds</code></a></p><h4 id="thresholds-门槛"><a class="markdownIt-Anchor" href="#thresholds-门槛"></a> Thresholds 门槛</h4><p>Intersection Observer API使用<strong>阈值</strong>，而不是报告可见的目标元素多少微小变化。创建观察者时，可以提供一个或多个数字值，这些数字值表示可见的目标元素的百分比。然后，API仅报告超过这些阈值的可见性更改。</p><p>例如，如果您希望每次目标的可见性通过每个25％标记向后或向前移动时都得到通知，则在创建观察者时，可以将数组[0，0.25，0.5，0.75，1]指定为阈值列表。您可以通过在可见性更改时检查传递给回调函数的<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/isIntersecting" target="_blank" rel="noopener"><code>isIntersecting</code></a>属性值，来确定可见性的变化方向（即，元素变得更可见还是不可见）<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" rel="noopener"><code>IntersectionObserverEntry</code></a>。如果<code>isIntersecting</code>为<code>true</code>，则目标元素已变得至少与已通过的阈值一样可见。如果为<code>false</code>，则目标不再像给定阈值那样可见。</p><p>要了解阈值的工作原理，请尝试滚动下面的框。其中的每个彩色框都会显示其在四个角上都可见的百分比，因此您可以在滚动容器时看到这些比例随时间的变化。每个框都有不同的阈值集：</p><ul><li>第一个框有一个针对每个可见度百分比的阈值；也就是说，<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/thresholds" target="_blank" rel="noopener"><code>IntersectionObserver.thresholds</code></a>数组是<code>[0.00, 0.01, 0.02, ..., 0.99, 1.00]</code>。</li><li>第二个框只有一个阈值，为50％。</li><li>第三个框的阈值是可见性的每10％（0％，10％，20％等）。</li><li>最后一个框的阈值各为25％。</li></ul><h4 id="裁剪和交点矩形"><a class="markdownIt-Anchor" href="#裁剪和交点矩形"></a> 裁剪和交点矩形</h4><p>浏览器按以下方式计算最终的相交矩形：这一切都为您完成，但是了解这些步骤有助于更好地准确把握何时发生交叉点。</p><ol><li>通过调用<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect" target="_blank" rel="noopener"><code>getBoundingClientRect()</code></a>目标，可以获得目标元素的边界矩形（即，完全包围组成该元素的每个组件的边界框的最小矩形）。这是最大的相交矩形。其余步骤将删除所有不相交的部分。</li><li>从目标的直接父块开始并向外移动，每个包含块的剪辑（如果有）都应用于相交矩形。根据两个块的交集和该<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overflow" target="_blank" rel="noopener"><code>overflow</code></a>属性指定的剪切模式（如果有）来确定块的剪切。设置<code>overflow</code>为其他任何值<code>visible</code>都会导致发生裁剪。</li><li>如果其中一个包含元素是嵌套浏览上下文的根（例如包含在中的文档）[<code>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)，则交集矩形会被裁剪到包含上下文的视口，并且向上递归通过容器继续进行到容器的包含块。</code>到达的最高层，将相交矩形剪切到框架的视口，然后框架的父元素是向相交根递归的下一个块。</li><li>当向上递归到达相交根时，生成的矩形将映射到相交根的坐标空间。</li><li>然后，通过与<a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#root-intersection-rectangle" target="_blank" rel="noopener">根相交矩形</a>相交来更新生成的<a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#root-intersection-rectangle" target="_blank" rel="noopener">矩形</a>。</li><li>最后，将此矩形映射到目标的坐标空间<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document" target="_blank" rel="noopener"><code>document</code></a>。</li></ol><h3 id="交叉变更callbacks"><a class="markdownIt-Anchor" href="#交叉变更callbacks"></a> 交叉变更callbacks</h3><p>当在根元素中可见的目标元素的数量超过可见性阈值之一时，将<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" rel="noopener"><code>IntersectionObserver</code></a>执行对象的回调。回调接收所有<code>IntersectionObserverEntry</code>对象的数组作为输入，每个超过阈值的对象一个，以及对<code>IntersectionObserver</code>对象本身的引用。</p><p>阈值列表中的每个条目都是一个<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" rel="noopener"><code>IntersectionObserverEntry</code></a>对象，它描述一个被超过的阈值。也就是说，每个条目都描述了给定元素中有多少与根元素相交，该元素是否被认为相交以及过渡发生的方向。</p><p>下面的代码段显示了一个回调，该回调保留了元素从不相交根到相交至少75％过渡的次数的计数。</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></pre></td><td class="code"><pre><span class="line">intersectionCallback(entries) =&gt; &#123;</span><br><span class="line">  entries.forEach(entry =&gt; &#123;</span><br><span class="line">    if (entry.isIntersecting) &#123;</span><br><span class="line">      let elem = entry.target;</span><br><span class="line"></span><br><span class="line">      if (entry.intersectionRatio &gt;= 0.75) &#123;</span><br><span class="line">        intersectionCounter++;</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><h2 id="接口"><a class="markdownIt-Anchor" href="#接口"></a> 接口</h2><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" rel="noopener"><code>IntersectionObserver</code></a></p><p>Intersection Observer API的主要接口。提供用于创建和管理观察者的方法，该观察者可以监视相同交集配置的任意数量的目标元素。每个观察者可以异步观察一个或多个目标元素和共用祖先元素之间或与它们顶层的交点变化<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document" target="_blank" rel="noopener"><code>Document</code></a>的<a href="https://developer.mozilla.org/en-US/docs/Glossary/viewport" target="_blank" rel="noopener">视口</a>。祖先或视口称为<strong>根</strong>。</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" rel="noopener"><code>IntersectionObserverEntry</code></a></p><p>描述在特定过渡时刻目标元素及其根容器之间的交集。只能以两种方式获得此类型的对象：作为<code>IntersectionObserver</code>回调的输入，或通过调用<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/takeRecords" target="_blank" rel="noopener"><code>IntersectionObserver.takeRecords()</code></a>。</p><h2 id="一个简单的例子"><a class="markdownIt-Anchor" href="#一个简单的例子"></a> 一个简单的例子</h2><p>这个简单的示例使目标元素在变得或多或少可见时更改其颜色和透明度。在<a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API/Timing_element_visibility" target="_blank" rel="noopener">使用Intersection Observer API的“计时元素可见性”中</a>，您可以找到一个更广泛的示例，该示例显示如何计时用户可以看到一组元素（例如广告）多长时间，以及如何通过记录统计信息或更新元素来对该信息做出反应…</p><h3 id="html"><a class="markdownIt-Anchor" href="#html"></a> HTML</h3><p>此示例的HTML非常简短，其中有一个主要元素，即我们将要定位的框（带有creative ID <code>&quot;box&quot;</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">&lt;div id=&quot;box&quot;&gt;</span><br><span class="line">  &lt;div class=&quot;vertical&quot;&gt;</span><br><span class="line">    Welcome to &lt;strong&gt;The Box!&lt;/strong&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h4 id="css"><a class="markdownIt-Anchor" href="#css"></a> CSS</h4><p>就本示例而言，CSS并不是十分重要。它对元素进行了布局，并确定<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-color" target="_blank" rel="noopener"><code>background-color</code></a>and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border" target="_blank" rel="noopener"><code>border</code></a>属性可以参与<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions" target="_blank" rel="noopener">CSS过渡</a>，当元素或多或少被遮盖时，我们将使用它来影响元素的更改。</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><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">#box &#123;</span><br><span class="line">  background-color: rgba(40, 40, 190, 255);</span><br><span class="line">  border: 4px solid rgb(20, 20, 120);</span><br><span class="line">  transition: background-color 1s, border 1s;</span><br><span class="line">  width: 350px;</span><br><span class="line">  height: 350px;</span><br><span class="line">  display: flex;</span><br><span class="line">  align-items: center;</span><br><span class="line">  justify-content: center;</span><br><span class="line">  padding: 20px;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">.vertical &#123;</span><br><span class="line">  color: white;</span><br><span class="line">  font: 32px &quot;Arial&quot;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">.extra &#123;</span><br><span class="line">  width: 350px;</span><br><span class="line">  height: 350px;</span><br><span class="line">  margin-top: 10px;</span><br><span class="line">  border: 4px solid rgb(20, 20, 120);</span><br><span class="line">  text-align: center;</span><br><span class="line">  padding: 20px;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="javascript"><a class="markdownIt-Anchor" href="#javascript"></a> JavaScript</h4><p>最后，让我们看一下使用Intersection Observer API进行事情的JavaScript代码。</p><h4 id="配置"><a class="markdownIt-Anchor" href="#配置"></a> 配置</h4><p>首先，我们需要准备一些变量并安装观察器。</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></pre></td><td class="code"><pre><span class="line">const numSteps = 20.0;</span><br><span class="line"></span><br><span class="line">let boxElement;</span><br><span class="line">let prevRatio = 0.0;</span><br><span class="line">let increasingColor = &quot;rgba(40, 40, 190, ratio)&quot;;</span><br><span class="line">let decreasingColor = &quot;rgba(190, 40, 40, ratio)&quot;;</span><br><span class="line"></span><br><span class="line">// Set things up</span><br><span class="line">window.addEventListener(&quot;load&quot;, (event) =&gt; &#123;</span><br><span class="line">  boxElement = document.querySelector(&quot;#box&quot;);</span><br><span class="line"></span><br><span class="line">  createObserver();</span><br><span class="line">&#125;, false);</span><br></pre></td></tr></table></figure><p>我们在此处设置的常量和变量是：</p><ul><li><p><code>numSteps</code></p><p>一个常数，指示我们希望在0.0和1.0的可见性比率之间具有多少个阈值。</p></li><li><p><code>prevRatio</code></p><p>此变量将用于记录上次超过阈值时可见性比率。这将让我们弄清楚目标元素是否变得越来越明显。</p></li><li><p><code>increasingColor</code></p><p>定义可见性比率增加时将应用于目标元素的颜色的字符串。该字符串中的“比率”一词将替换为目标的当前可见性比率，因此该元素不仅会改变颜色，而且会变得越来越不透明，因为它变得越来越模糊。</p></li><li><p><code>decreasingColor</code></p><p>同样，这是一个字符串，定义了可见率降低时将应用的颜色。</p></li></ul><p>我们呼吁<a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" target="_blank" rel="noopener"><code>Window.addEventListener()</code></a>开始收听<code>load</code>事件。一旦页面加载完成后，我们得到的元素的引用与ID <code>&quot;box&quot;</code>使用<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector" target="_blank" rel="noopener"><code>querySelector()</code></a>，然后调用<code>createObserver()</code>我们将在稍后创建来处理建筑方法和安装的交叉点观测。</p><h4 id="创建相交观察器"><a class="markdownIt-Anchor" href="#创建相交观察器"></a> 创建相交观察器</h4><p><code>createObserver()</code>一旦页面加载完成，便会调用该方法以处理实际创建新对象<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" rel="noopener"><code>IntersectionObserver</code></a>并开始观察目标元素的过程。</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></pre></td><td class="code"><pre><span class="line">function createObserver() &#123;</span><br><span class="line">  let observer;</span><br><span class="line"></span><br><span class="line">  let options = &#123;</span><br><span class="line">    root: null,</span><br><span class="line">    rootMargin: &quot;0px&quot;,</span><br><span class="line">    threshold: buildThresholdList()</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  observer = new IntersectionObserver(handleIntersect, options);</span><br><span class="line">  observer.observe(boxElement);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先从设置一个<code>options</code>包含观察者设置的对象开始。我们要留意的目标元素相对于文档的视口的可见性的变化，所以<code>root</code>是<code>null</code>。我们不需要边距，因此边距偏移量<code>rootMargin</code>指定为“ 0px”。这使观察者可以观察目标元素的边界与视口边界之间的交集处的变化，而无需增加（或减去）任何空间。</p><p>可见度阈值列表<code>threshold</code>由函数构造<code>buildThresholdList()</code>。在此示例中，以编程方式构建阈值列表，因为存在许多阈值列表，并且该数量旨在调整。</p><p>一旦<code>options</code>准备好了，我们创建了新的观察员，调用<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver" target="_blank" rel="noopener"><code>IntersectionObserver()</code></a>构造函数，指定一个函数被调用时，路口穿越我们的其中一个阈值，<code>handleIntersect()</code>和我们一组选项。然后<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/observe" target="_blank" rel="noopener"><code>observe()</code></a>，我们调用返回的观察者，将所需的目标元素传递给它。</p><p>如果我们愿意的话，我们可以选择通过监视<code>observer.observe()</code>每个元素来监视多个元素是否相对于视口相交。</p><h4 id="建立阈值比率数组"><a class="markdownIt-Anchor" href="#建立阈值比率数组"></a> 建立阈值比率数组</h4><p><code>buildThresholdList()</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><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">function buildThresholdList() &#123;</span><br><span class="line">  let thresholds = [];</span><br><span class="line">  let numSteps = 20;</span><br><span class="line"></span><br><span class="line">  for (let i=1.0; i&lt;=numSteps; i++) &#123;</span><br><span class="line">    let ratio = i/numSteps;</span><br><span class="line">    thresholds.push(ratio);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  thresholds.push(0);</span><br><span class="line">  return thresholds;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这将构建阈值数组-通过将值介于1和之间的每个整数推<code>i/numSteps</code>入<code>thresholds</code>数组，每个阈值之间的比率为0.0和1.0 <code>i</code>之间<code>numSteps</code>。它还推0以包括该值。给定默认值<code>numSteps</code>（20），结果是以下阈值列表：</p><table><thead><tr><th style="text-align:left">＃</th><th style="text-align:left">Ratio</th><th style="text-align:left">＃</th><th style="text-align:left">Ratio</th></tr></thead><tbody><tr><td style="text-align:left">1个</td><td style="text-align:left">0.05</td><td style="text-align:left">11</td><td style="text-align:left">0.55</td></tr><tr><td style="text-align:left">2</td><td style="text-align:left">0.1</td><td style="text-align:left">12</td><td style="text-align:left">0.6</td></tr><tr><td style="text-align:left">3</td><td style="text-align:left">0.15</td><td style="text-align:left">13</td><td style="text-align:left">0.65</td></tr><tr><td style="text-align:left">4</td><td style="text-align:left">0.2</td><td style="text-align:left">14</td><td style="text-align:left">0.7</td></tr><tr><td style="text-align:left">5</td><td style="text-align:left">0.25</td><td style="text-align:left">15</td><td style="text-align:left">0.75</td></tr><tr><td style="text-align:left">6</td><td style="text-align:left">0.3</td><td style="text-align:left">16</td><td style="text-align:left">0.8</td></tr><tr><td style="text-align:left">7</td><td style="text-align:left">0.35</td><td style="text-align:left">17</td><td style="text-align:left">0.85</td></tr><tr><td style="text-align:left">8</td><td style="text-align:left">0.4</td><td style="text-align:left">18</td><td style="text-align:left">0.9</td></tr><tr><td style="text-align:left">9</td><td style="text-align:left">0.45</td><td style="text-align:left">19</td><td style="text-align:left">0.95</td></tr><tr><td style="text-align:left">10</td><td style="text-align:left">0.5</td><td style="text-align:left">20</td><td style="text-align:left">1.0</td></tr></tbody></table><p>当然，我们可以将阈值数组硬编码到我们的代码中，而这通常是您最终要做的。但是，此示例为添加配置控件以调整粒度提供了空间。</p><h4 id="处理交集变更"><a class="markdownIt-Anchor" href="#处理交集变更"></a> 处理交集变更</h4><p>当浏览器检测到目标元素（在我们的示例中为ID的元素<code>&quot;box&quot;</code>）已经被公开或模糊，以致其可见性比率超过列表中的阈值之一时，它将调用处理程序函数<code>handleIntersect()</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="function"><span class="keyword">function</span> <span class="title">handleIntersect</span>(<span class="params">entries, observer</span>) </span>&#123;</span><br><span class="line">  entries.forEach(<span class="function">(<span class="params">entry</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (entry.intersectionRatio &gt; prevRatio) &#123;</span><br><span class="line">      entry.target.style.backgroundColor = increasingColor.replace(<span class="string">"ratio"</span>, entry.intersectionRatio);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      entry.target.style.backgroundColor = decreasingColor.replace(<span class="string">"ratio"</span>, entry.intersectionRatio);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    prevRatio = entry.intersectionRatio;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" rel="noopener"><code>IntersectionObserverEntry</code></a>列表中的每个<code>entries</code>条目，我们查看条目<a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/intersectionRatio" target="_blank" rel="noopener"><code>intersectionRatio</code></a>是否在上升；如果是，我们将目标的设置<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-color" target="_blank" rel="noopener"><code>background-color</code></a>为中的字符串<code>increasingColor</code>（请记住，它是<code>&quot;rgba(40, 40, 190, ratio)&quot;</code>），将单词“ ratio”替换为条目的<code>intersectionRatio</code>。结果：不仅颜色改变了，目标元素的透明度也改变了；当交叉比例降低时，背景色的Alpha值随之降低，从而使元素更透明。</p><p>同样，如果<code>intersectionRatio</code>下降，则使用字符串，<code>decreasingColor</code>并<code>intersectionRatio</code>在设置目标元素的之前将其中的“比率”一词替换为<code>background-color</code>。</p><p>最后，为了跟踪交叉比率是上升还是下降，我们记住变量中的当前比率<code>prevRatio</code>。</p><h2 id="浏览器兼容性"><a class="markdownIt-Anchor" href="#浏览器兼容性"></a> 浏览器兼容性</h2><p><a href="https://github.com/mdn/browser-compat-data" target="_blank" rel="noopener">Update compatibility data on GitHub</a></p><table><thead><tr><th style="text-align:left"></th><th style="text-align:center">Desktop</th><th style="text-align:center">Mobile</th><th style="text-align:center"></th><th style="text-align:center"></th><th style="text-align:center"></th><th style="text-align:center"></th><th style="text-align:center"></th><th style="text-align:center"></th><th style="text-align:center"></th><th style="text-align:center"></th><th style="text-align:center"></th><th></th></tr></thead><tbody><tr><td style="text-align:left"></td><td style="text-align:center">Chrome</td><td style="text-align:center">Edge</td><td style="text-align:center">Firefox</td><td style="text-align:center">Internet Explorer</td><td style="text-align:center">Opera</td><td style="text-align:center">Safari</td><td style="text-align:center">Android webview</td><td style="text-align:center">Chrome for Android</td><td style="text-align:center">Firefox for Android</td><td style="text-align:center">Opera for Android</td><td style="text-align:center">Safari on iOS</td><td>Samsung Internet</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" rel="noopener"><code>IntersectionObserver</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">Full support12.1</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.2</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver" target="_blank" rel="noopener"><code>IntersectionObserver()</code> constructor</a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.1</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.2</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/disconnect" target="_blank" rel="noopener"><code>disconnect</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15NotesOpen</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">?</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/observe" target="_blank" rel="noopener"><code>observe</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">Full support12.1</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.2</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/root" target="_blank" rel="noopener"><code>root</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">Full support12.1</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.2</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin" target="_blank" rel="noopener"><code>rootMargin</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">Full support12.1NotesOpen</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.2NotesOpen</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/takeRecords" target="_blank" rel="noopener"><code>takeRecords</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15NotesOpen</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">?</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/thresholds" target="_blank" rel="noopener"><code>thresholds</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">Full support12.1</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.2</td><td>Full support5.0</td></tr><tr><td style="text-align:left"><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/unobserve" target="_blank" rel="noopener"><code>unobserve</code></a>Experimental</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support15NotesOpen</td><td style="text-align:center">Full support55Open</td><td style="text-align:center">No supportNo</td><td style="text-align:center">Full supportYes</td><td style="text-align:center">Full support12.1</td><td style="text-align:center">Full support51</td><td style="text-align:center">Full support51</td><td style="text-align:center">?</td><td style="text-align:center">?</td><td style="text-align:center">Full support12.2</td><td>Full support5.0</td></tr></tbody></table>]]></content>
    
    <summary type="html">
    
      摘自MDN，翻译一下吧，对检测元素可见效问题挺有用的哈哈
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>iScroll 源码学习</title>
    <link href="https://wzes.github.io/2019/10/23/JavaScript/iScroll/"/>
    <id>https://wzes.github.io/2019/10/23/JavaScript/iScroll/</id>
    <published>2019-10-23T10:30:16.000Z</published>
    <updated>2019-10-24T06:15:03.500Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>强大的 iScroll 啊，功能的确多，但我只想看看你的 flinger 滑动处理的精髓！感觉 iscroll 的滑动处理的还可以吧，虽然比不上系统的 scroll，但也不至于很难受，学会了这招，可以出去吹吹牛（招摇撞骗）了。</p><h3 id="须知"><a class="markdownIt-Anchor" href="#须知"></a> 须知</h3><p>通常如果我们给容器设置一个高度，如果子节点的高度超出了父容器的高度，那么内容就可以进行滚动，原因在于 <code>overflow</code> 属性默认为 <code>auto</code>，当然最好将父容器设置为 <code>overflow: scroll</code>，子内容就可以进行滚动。还可以单独设置 X 或 Y 轴的滚动，<code>overflow-x:scroll</code> 或 <code>overflow-y:scroll</code>，如果不想让内容滚动，则设置 <code>overflow: hidden</code></p><h3 id="是什么"><a class="markdownIt-Anchor" href="#是什么"></a> 是什么</h3><p>iScroll 是一种高性能，占用空间小，无依赖的多平台 javascript 滚动器。</p><blockquote><p>它适用于台式机，移动电视和智能电视。它已针对性能和尺寸进行了优化，以在现代和旧设备上提供最平滑的结果。</p><p>iScroll不仅可以滚动。它可以处理需要通过用户交互移动的任何元素。它为您的项目添加了滚动，缩放，平移，无限滚动，视差滚动，轮播，并且仅以4kb的速度做到了这一点。给它扫帚，它也会打扫你的办公室。</p><p>即使在本机滚动足以胜任的平台上，iScroll也会添加原本无法实现的功能。特别：</p><p>即使在动量期间，也可以对滚动位置进行精细控制。您始终可以获取并设置滚动条的x，y坐标。<br>可以使用用户定义的缓动功能（弹跳，弹性，后退…）自定义动画。<br>您可以轻松地挂钩到大量自定义事件（onBeforeScrollStart，onScrollStart，onScroll，onScrollEnd，flick等）。<br>开箱即用的多平台支持。从较旧的Android设备到最新的iPhone，从Chrome到Internet Explorer。</p></blockquote><h3 id="get-started"><a class="markdownIt-Anchor" href="#get-started"></a> Get started</h3><p>假设我们的元素长这个样子</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></pre></td><td class="code"><pre><span class="line">&lt;div id=&quot;wrapper&quot;&gt;</span><br><span class="line">    &lt;ul&gt;</span><br><span class="line">        &lt;li&gt;...&lt;/li&gt;</span><br><span class="line">        &lt;li&gt;...&lt;/li&gt;</span><br><span class="line">        ...</span><br><span class="line">    &lt;/ul&gt;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><p>那我们只需要在脚本中使用 <code>new IScroll</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">myScroll = new IScroll(&apos;#wrapper&apos;);</span><br></pre></td></tr></table></figure><p>当然，他还有很多属性（只可意会）</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">this.options = &#123;</span><br><span class="line">disablePointer : !utils.hasPointer,</span><br><span class="line">disableTouch : utils.hasPointer || !utils.hasTouch,</span><br><span class="line">disableMouse : utils.hasPointer || utils.hasTouch,</span><br><span class="line">startX: 0, // 开始滚动的值</span><br><span class="line">startY: 0,</span><br><span class="line">scrollY: true,</span><br><span class="line">directionLockThreshold: 5,</span><br><span class="line">momentum: true,  // 对其 Native 的 flinger(手指放开后还会继续滚动一段距离)</span><br><span class="line">bounce: true,   // 滚动回弹</span><br><span class="line">bounceTime: 600,</span><br><span class="line">bounceEasing: &apos;&apos;,</span><br><span class="line">preventDefault: true,</span><br><span class="line">preventDefaultException: &#123; tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ &#125;,</span><br><span class="line">HWCompositing: true,</span><br><span class="line">useTransition: true,</span><br><span class="line">useTransform: true,</span><br><span class="line">bindToWrapper: typeof window.onmousedown === &quot;undefined&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="原理概括"><a class="markdownIt-Anchor" href="#原理概括"></a> 原理概括</h3><p>iScroll 当然没有使用系统的 scroll，原因有很多点，上面也提到过。</p><p>iScroll 使用了 <code>transform: translate(px, px)</code>来实现滚动，完全是通过自己计算来实现的，当然，如果系统支持 <code>transation</code>，那么 iScroll 的松手后滚动将借助 <code>transitionTimingFunction</code>，否则就自定义动画，完成平滑滚动。</p><h3 id="源码解析"><a class="markdownIt-Anchor" href="#源码解析"></a> 源码解析</h3><h4 id="入口"><a class="markdownIt-Anchor" href="#入口"></a> 入口</h4><p>IScroll 是一个方法，调用了以后会创建属性，进行初始化，其中 <code>this.x</code> 和 <code>this.y</code> 分别记录了x 轴 与 y 轴的滚动距离。</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><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">function IScroll (el, options) &#123;</span><br><span class="line">this.wrapper = typeof el == &apos;string&apos; ? document.querySelector(el) : el;</span><br><span class="line">this.scroller = this.wrapper.children[0];</span><br><span class="line">this.scrollerStyle = this.scroller.style;// cache style for better performance</span><br><span class="line"></span><br><span class="line">// 此处省略了 this.options 的赋值</span><br><span class="line"></span><br><span class="line">// Some defaults</span><br><span class="line">this.x = 0;  </span><br><span class="line">this.y = 0;</span><br><span class="line">this.directionX = 0;</span><br><span class="line">this.directionY = 0;</span><br><span class="line">this._events = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">this._init();</span><br><span class="line">this.refresh();</span><br><span class="line"></span><br><span class="line">this.scrollTo(this.options.startX, this.options.startY);</span><br><span class="line">this.enable();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="初始化事件"><a class="markdownIt-Anchor" href="#初始化事件"></a> 初始化事件</h4><p>在 <code>this._init();</code> 中初始化了事件监听，在手机端主要是  <code>touch</code> 开头的事件，PC 端主要是 <code>mouse</code> 事件，<code>pointer</code> 是指针事件，此外，还会监听 <code>transitionend</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">_initEvents: function (remove) &#123;</span><br><span class="line">var eventType = remove ? utils.removeEvent : utils.addEvent,</span><br><span class="line">target = this.options.bindToWrapper ? this.wrapper : window;</span><br><span class="line"></span><br><span class="line">eventType(window, &apos;orientationchange&apos;, this);</span><br><span class="line">eventType(window, &apos;resize&apos;, this);</span><br><span class="line"></span><br><span class="line">if ( this.options.click ) &#123;</span><br><span class="line">eventType(this.wrapper, &apos;click&apos;, this, true);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if ( !this.options.disableMouse ) &#123;</span><br><span class="line">eventType(this.wrapper, &apos;mousedown&apos;, this);</span><br><span class="line">eventType(target, &apos;mousemove&apos;, this);</span><br><span class="line">eventType(target, &apos;mousecancel&apos;, this);</span><br><span class="line">eventType(target, &apos;mouseup&apos;, this);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if ( utils.hasPointer &amp;&amp; !this.options.disablePointer ) &#123;</span><br><span class="line">eventType(this.wrapper, utils.prefixPointerEvent(&apos;pointerdown&apos;), this);</span><br><span class="line">eventType(target, utils.prefixPointerEvent(&apos;pointermove&apos;), this);</span><br><span class="line">eventType(target, utils.prefixPointerEvent(&apos;pointercancel&apos;), this);</span><br><span class="line">eventType(target, utils.prefixPointerEvent(&apos;pointerup&apos;), this);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if ( utils.hasTouch &amp;&amp; !this.options.disableTouch ) &#123;</span><br><span class="line">eventType(this.wrapper, &apos;touchstart&apos;, this);</span><br><span class="line">eventType(target, &apos;touchmove&apos;, this);</span><br><span class="line">eventType(target, &apos;touchcancel&apos;, this);</span><br><span class="line">eventType(target, &apos;touchend&apos;, this);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">eventType(this.scroller, &apos;transitionend&apos;, this);</span><br><span class="line">eventType(this.scroller, &apos;webkitTransitionEnd&apos;, this);</span><br><span class="line">eventType(this.scroller, &apos;oTransitionEnd&apos;, this);</span><br><span class="line">eventType(this.scroller, &apos;MSTransitionEnd&apos;, this);</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h4 id="初始化变量"><a class="markdownIt-Anchor" href="#初始化变量"></a> 初始化变量</h4><p>计算容器高度、宽度，滚动内容高度、宽度，最大滚动距离（X轴，Y轴）等等。</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><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></pre></td><td class="code"><pre><span class="line">refresh: function () &#123;</span><br><span class="line">utils.getRect(this.wrapper);// Force reflow</span><br><span class="line"></span><br><span class="line">this.wrapperWidth= this.wrapper.clientWidth;</span><br><span class="line">this.wrapperHeight= this.wrapper.clientHeight;</span><br><span class="line"></span><br><span class="line">var rect = utils.getRect(this.scroller);</span><br><span class="line">/* REPLACE START: refresh */</span><br><span class="line"></span><br><span class="line">this.scrollerWidth= rect.width;</span><br><span class="line">this.scrollerHeight= rect.height;</span><br><span class="line"></span><br><span class="line">this.maxScrollX= this.wrapperWidth - this.scrollerWidth;</span><br><span class="line">this.maxScrollY= this.wrapperHeight - this.scrollerHeight;</span><br><span class="line"></span><br><span class="line">/* REPLACE END: refresh */</span><br><span class="line"></span><br><span class="line">this.hasHorizontalScroll= this.options.scrollX &amp;&amp; this.maxScrollX &lt; 0;</span><br><span class="line">this.hasVerticalScroll= this.options.scrollY &amp;&amp; this.maxScrollY &lt; 0;</span><br><span class="line"></span><br><span class="line">if ( !this.hasHorizontalScroll ) &#123;</span><br><span class="line">this.maxScrollX = 0;</span><br><span class="line">this.scrollerWidth = this.wrapperWidth;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if ( !this.hasVerticalScroll ) &#123;</span><br><span class="line">this.maxScrollY = 0;</span><br><span class="line">this.scrollerHeight = this.wrapperHeight;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">this.endTime = 0;</span><br><span class="line">this.directionX = 0;</span><br><span class="line">this.directionY = 0;</span><br><span class="line"></span><br><span class="line">if(utils.hasPointer &amp;&amp; !this.options.disablePointer) &#123;</span><br><span class="line">// The wrapper should have `touchAction` property for using pointerEvent.</span><br><span class="line">this.wrapper.style[utils.style.touchAction] = utils.getTouchAction(this.options.eventPassthrough, true);</span><br><span class="line"></span><br><span class="line">// case. not support &apos;pinch-zoom&apos;</span><br><span class="line">// https://github.com/cubiq/iscroll/issues/1118#issuecomment-270057583</span><br><span class="line">if (!this.wrapper.style[utils.style.touchAction]) &#123;</span><br><span class="line">this.wrapper.style[utils.style.touchAction] = utils.getTouchAction(this.options.eventPassthrough, false);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">this.wrapperOffset = utils.offset(this.wrapper);</span><br><span class="line"></span><br><span class="line">this._execEvent(&apos;refresh&apos;);</span><br><span class="line"></span><br><span class="line">this.resetPosition();</span><br><span class="line"></span><br><span class="line">// INSERT POINT: _refresh</span><br><span class="line"></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h4 id="滚动函数"><a class="markdownIt-Anchor" href="#滚动函数"></a> 滚动函数</h4><p>给定一个 x，y，滚动时间 time，滚动效果 easing；</p><p>如果 time 为 0，则为瞬时滚动，使用 <code>_translate</code> 改变位置，如果环境支持 <code>transition</code> , 那么将使用 transition 属性实现动画。</p><p>如果浏览器不支持 transition，time 又不为 0，那么则自定义动画实现滚动</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></pre></td><td class="code"><pre><span class="line">scrollTo: function (x, y, time, easing) &#123;</span><br><span class="line">easing = easing || utils.ease.circular;</span><br><span class="line"></span><br><span class="line">this.isInTransition = this.options.useTransition &amp;&amp; time &gt; 0;</span><br><span class="line">var transitionType = this.options.useTransition &amp;&amp; easing.style;</span><br><span class="line">if ( !time || transitionType ) &#123;</span><br><span class="line">if(transitionType) &#123;</span><br><span class="line">this._transitionTimingFunction(easing.style);</span><br><span class="line">this._transitionTime(time);</span><br><span class="line">&#125;</span><br><span class="line">this._translate(x, y);</span><br><span class="line">&#125; else &#123;</span><br><span class="line">this._animate(x, y, time, easing.fn);</span><br><span class="line">&#125;</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h4 id="移动实现"><a class="markdownIt-Anchor" href="#移动实现"></a> 移动实现</h4><p>如果元素支持 transform 给元素赋值 transform 属性即可，否则使用 left 属性。然后将 <code>x</code> , <code>y</code> 设置为目标位置，既目前滚动位置，通常都是为<strong>负值</strong>。</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><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">_translate: function (x, y) &#123;</span><br><span class="line">if ( this.options.useTransform ) &#123;</span><br><span class="line"></span><br><span class="line">/* REPLACE START: _translate */</span><br><span class="line"></span><br><span class="line">this.scrollerStyle[utils.style.transform] = &apos;translate(&apos; + x + &apos;px,&apos; + y + &apos;px)&apos; + this.translateZ;</span><br><span class="line"></span><br><span class="line">/* REPLACE END: _translate */</span><br><span class="line"></span><br><span class="line">&#125; else &#123;</span><br><span class="line">x = Math.round(x);</span><br><span class="line">y = Math.round(y);</span><br><span class="line">this.scrollerStyle.left = x + &apos;px&apos;;</span><br><span class="line">this.scrollerStyle.top = y + &apos;px&apos;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">this.x = x;</span><br><span class="line">this.y = y;</span><br><span class="line"></span><br><span class="line">// INSERT POINT: _translate</span><br><span class="line"></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h4 id="触摸事件处理-core"><a class="markdownIt-Anchor" href="#触摸事件处理-core"></a> 触摸事件处理 Core</h4><p>便于分析，我只考虑y方向的滑动，x方向同理（会删除掉 x 方向的代码）</p><h5 id="startmovestartmousedown"><a class="markdownIt-Anchor" href="#startmovestartmousedown"></a> start（movestart，mousedown…）</h5><p>其中比较重要的是 startY，startTime，记录下目前滚动位置，滚动时刻 pointY 则是点击位置</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">_start: function (e) &#123;</span><br><span class="line"> var point = e.touches ? e.touches[0] : e,</span><br><span class="line">  pos;</span><br><span class="line"></span><br><span class="line"> this.initiated = utils.eventType[e.type];</span><br><span class="line"> this.moved  = false;</span><br><span class="line"> this.distY  = 0;</span><br><span class="line"> this.directionY = 0;</span><br><span class="line"> this.directionLocked = 0;</span><br><span class="line"></span><br><span class="line"> this.startTime = utils.getTime();</span><br><span class="line"></span><br><span class="line"> if ( this.options.useTransition &amp;&amp; this.isInTransition ) &#123;</span><br><span class="line">  this._transitionTime();</span><br><span class="line">  this.isInTransition = false;</span><br><span class="line">  pos = this.getComputedPosition();</span><br><span class="line">  this._translate(Math.round(pos.x), Math.round(pos.y));</span><br><span class="line">  this._execEvent(&apos;scrollEnd&apos;);</span><br><span class="line"> &#125; else if ( !this.options.useTransition &amp;&amp; this.isAnimating ) &#123;</span><br><span class="line">  this.isAnimating = false;</span><br><span class="line">  this._execEvent(&apos;scrollEnd&apos;);</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"> this.startY    = this.y;</span><br><span class="line"> this.absStartY = this.y;</span><br><span class="line"> this.pointY    = point.pageY;</span><br><span class="line"></span><br><span class="line"> this._execEvent(&apos;beforeScrollStart&apos;);</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h5 id="move"><a class="markdownIt-Anchor" href="#move"></a> move</h5><p>通常，手指不松开，屏幕滚动是跟随手指移动的，手指怎么移动，屏幕就怎么移动。</p><p>首先会判断该次滑动是否有效，然后锁定滑动方向，最后计算 newY，需要滑动的位置 <code>newY = this.y + deltaY</code> 是通过 delta 来计算的。然后使用 translate 函数移动进行瞬时移动。</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><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></pre></td><td class="code"><pre><span class="line">_move: function (e) &#123;</span><br><span class="line">var point= e.touches ? e.touches[0] : e,</span><br><span class="line">deltaY= point.pageY - this.pointY,</span><br><span class="line">timestamp= utils.getTime(),</span><br><span class="line">newY,</span><br><span class="line">absDistY;</span><br><span class="line"></span><br><span class="line">this.pointY= point.pageY;</span><br><span class="line">this.distY+= deltaY;</span><br><span class="line">absDistY= Math.abs(this.distY);</span><br><span class="line"></span><br><span class="line">// We need to move at least 10 pixels for the scrolling to initiate</span><br><span class="line">if ( timestamp - this.endTime &gt; 300 &amp;&amp; (absDistX &lt; 10 &amp;&amp; absDistY &lt; 10) ) &#123;</span><br><span class="line">return;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// If you are scrolling in one direction lock the other</span><br><span class="line">if ( !this.directionLocked &amp;&amp; !this.options.freeScroll ) &#123;</span><br><span class="line">if ( absDistX &gt; absDistY + this.options.directionLockThreshold ) &#123;</span><br><span class="line">this.directionLocked = &apos;h&apos;;// lock horizontally</span><br><span class="line">&#125; else if ( absDistY &gt;= absDistX + this.options.directionLockThreshold ) &#123;</span><br><span class="line">this.directionLocked = &apos;v&apos;;// lock vertically</span><br><span class="line">&#125; else &#123;</span><br><span class="line">this.directionLocked = &apos;n&apos;;// no lock</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if ( this.directionLocked == &apos;h&apos; ) &#123;</span><br><span class="line">if ( this.options.eventPassthrough == &apos;vertical&apos; ) &#123;</span><br><span class="line">e.preventDefault();</span><br><span class="line">&#125; else if ( this.options.eventPassthrough == &apos;horizontal&apos; ) &#123;</span><br><span class="line">this.initiated = false;</span><br><span class="line">return;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">deltaY = 0;</span><br><span class="line">&#125; else if ( this.directionLocked == &apos;v&apos; ) &#123;</span><br><span class="line">if ( this.options.eventPassthrough == &apos;horizontal&apos; ) &#123;</span><br><span class="line">e.preventDefault();</span><br><span class="line">&#125; else if ( this.options.eventPassthrough == &apos;vertical&apos; ) &#123;</span><br><span class="line">this.initiated = false;</span><br><span class="line">return;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">deltaX = 0;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">deltaY = this.hasVerticalScroll ? deltaY : 0;</span><br><span class="line">newY = this.y + deltaY;</span><br><span class="line"></span><br><span class="line">// Slow down if outside of the boundaries</span><br><span class="line">if ( newY &gt; 0 || newY &lt; this.maxScrollY ) &#123;</span><br><span class="line">newY = this.options.bounce ? this.y + deltaY / 3 : newY &gt; 0 ? 0 : this.maxScrollY;</span><br><span class="line">&#125;</span><br><span class="line">this.directionY = deltaY &gt; 0 ? -1 : deltaY &lt; 0 ? 1 : 0;</span><br><span class="line">if ( !this.moved ) &#123;</span><br><span class="line">this._execEvent(&apos;scrollStart&apos;);</span><br><span class="line">&#125;</span><br><span class="line">this.moved = true;</span><br><span class="line">this._translate(newX, newY);</span><br><span class="line"></span><br><span class="line">/* REPLACE START: _move */</span><br><span class="line"></span><br><span class="line">if ( timestamp - this.startTime &gt; 300 ) &#123;</span><br><span class="line">this.startTime = timestamp;</span><br><span class="line">this.startX = this.x;</span><br><span class="line">this.startY = this.y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/* REPLACE END: _move */</span><br><span class="line"></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>其中一个比较重要的是，如果这次 move 的时刻与上一次（start）的时间超过 300 ms，会进行重置（太妙了！！）这与接下来的 end 有着非常重要的意义</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">if ( timestamp - this.startTime &gt; 300 ) &#123;</span><br><span class="line">this.startTime = timestamp;</span><br><span class="line">this.startY = this.y;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="end"><a class="markdownIt-Anchor" href="#end"></a> end</h5><p>在 end 中将进行动量滚动（松开后还能进行滚动）</p><p>动量滚动最重要的是计算两个值，松开后滚动的 <code>时间</code> 和 <code>距离</code>，也是 iscroll 最核心的部分。</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><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></pre></td><td class="code"><pre><span class="line">_end: function (e) &#123;</span><br><span class="line">var point = e.changedTouches ? e.changedTouches[0] : e,</span><br><span class="line">momentumY,</span><br><span class="line">duration = utils.getTime() - this.startTime,</span><br><span class="line">newY = Math.round(this.y),</span><br><span class="line">distanceY = Math.abs(newY - this.startY),</span><br><span class="line">time = 0,</span><br><span class="line">easing = &apos;&apos;;</span><br><span class="line"></span><br><span class="line">this.isInTransition = 0;</span><br><span class="line">this.initiated = 0;</span><br><span class="line">this.endTime = utils.getTime();</span><br><span class="line"></span><br><span class="line">// reset if we are outside of the boundaries</span><br><span class="line">if ( this.resetPosition(this.options.bounceTime) ) &#123;</span><br><span class="line">return;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">this.scrollTo(newX, newY);// ensures that the last position is rounded</span><br><span class="line"></span><br><span class="line">// we scrolled less than 10 pixels</span><br><span class="line">if ( !this.moved ) &#123;</span><br><span class="line">if ( this.options.tap ) &#123;</span><br><span class="line">utils.tap(e, this.options.tap);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if ( this.options.click ) &#123;</span><br><span class="line">utils.click(e);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">this._execEvent(&apos;scrollCancel&apos;);</span><br><span class="line">return;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if ( this._events.flick &amp;&amp; duration &lt; 200 &amp;&amp; distanceX &lt; 100 &amp;&amp; distanceY &lt; 100 ) &#123;</span><br><span class="line">this._execEvent(&apos;flick&apos;);</span><br><span class="line">return;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// start momentum animation if needed</span><br><span class="line">if ( this.options.momentum &amp;&amp; duration &lt; 300 ) &#123;</span><br><span class="line">momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : &#123; destination: newY, duration: 0 &#125;;</span><br><span class="line">newY = momentumY.destination;</span><br><span class="line">time = Math.max(momentumX.duration, momentumY.duration);</span><br><span class="line">this.isInTransition = 1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// INSERT POINT: _end</span><br><span class="line"></span><br><span class="line">if ( newX != this.x || newY != this.y ) &#123;</span><br><span class="line">// change easing function when scroller goes out of the boundaries</span><br><span class="line">if ( newX &gt; 0 || newX &lt; this.maxScrollX || newY &gt; 0 || newY &lt; this.maxScrollY ) &#123;</span><br><span class="line">easing = utils.ease.quadratic;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">this.scrollTo(newX, newY, time, easing);</span><br><span class="line">return;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">this._execEvent(&apos;scrollEnd&apos;);</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>计算使用了 util.momentum</p><p>输入，现在的滚动位置 y，上一次开始的 startY（该位置会在 move 中重置），time（距离上一次 start 的时间），lowerMargin 最大的滚动距离，wrapperSize 容器的尺寸，deceleration 插值器</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><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">momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) &#123;</span><br><span class="line">var distance = current - start,</span><br><span class="line">speed = Math.abs(distance) / time,</span><br><span class="line">destination,</span><br><span class="line">duration;</span><br><span class="line"></span><br><span class="line">deceleration = deceleration === undefined ? 0.0006 : deceleration;</span><br><span class="line"></span><br><span class="line">destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance &lt; 0 ? -1 : 1 );</span><br><span class="line">duration = speed / deceleration;</span><br><span class="line"></span><br><span class="line">if ( destination &lt; lowerMargin ) &#123;</span><br><span class="line">destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;</span><br><span class="line">distance = Math.abs(destination - current);</span><br><span class="line">duration = distance / speed;</span><br><span class="line">&#125; else if ( destination &gt; 0 ) &#123;</span><br><span class="line">destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;</span><br><span class="line">distance = Math.abs(current) + destination;</span><br><span class="line">duration = distance / speed;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">return &#123;</span><br><span class="line">destination: Math.round(destination),</span><br><span class="line">duration: duration</span><br><span class="line">&#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>然后就是使用 scrollTo 函数进行动量滚动了。<code>this.isInTransition</code> 标志正在进行动量滚动，如果期间存在 touchdown 事件，则会立刻停止滚动。</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></pre></td><td class="code"><pre><span class="line">if ( this.options.useTransition &amp;&amp; this.isInTransition ) &#123;</span><br><span class="line">this._transitionTime();</span><br><span class="line">this.isInTransition = false;</span><br><span class="line">pos = this.getComputedPosition();</span><br><span class="line">this._translate(Math.round(pos.x), Math.round(pos.y));</span><br><span class="line">this._execEvent(&apos;scrollEnd&apos;);</span><br><span class="line">&#125; else if ( !this.options.useTransition &amp;&amp; this.isAnimating ) &#123;</span><br><span class="line">this.isAnimating = false;</span><br><span class="line">this._execEvent(&apos;scrollEnd&apos;);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，核心功能差不多了</p><p>但 iscroll 还有其他很多功能，比如</p><ul><li>轮播图</li><li>放大缩小</li><li>…</li></ul><h3 id="最后"><a class="markdownIt-Anchor" href="#最后"></a> 最后</h3><p>其实吧，iScroll 也也就那么回事。</p>]]></content>
    
    <summary type="html">
    
      强大的 iScroll 啊，功能的确多，但我只想看看你的 flinger 滑动处理的精髓！感觉 iscroll 的滑动处理的还可以吧，虽然比不上系统的 scroll，但也不至于很难受，学会了这招，可以出去吹吹牛（招摇撞骗）了
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Gulp 入门指南</title>
    <link href="https://wzes.github.io/2019/10/20/JavaScript/Gulp/"/>
    <id>https://wzes.github.io/2019/10/20/JavaScript/Gulp/</id>
    <published>2019-10-20T07:30:16.000Z</published>
    <updated>2019-10-20T09:58:06.150Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>明天就要晋升答辩了，今天写篇入门 Wiki 压压惊吧，正好最近想学构建工具之类的东西，Gulp 好像就很合适</p><h3 id="glup-是什么"><a class="markdownIt-Anchor" href="#glup-是什么"></a> Glup 是什么？</h3><p><code>Gulp</code> 有狼吞虎咽的意思。</p><p>官方【<a href="https://gulpjs.com/docs%E3%80%91%E8%A7%A3%E9%87%8A" target="_blank" rel="noopener">https://gulpjs.com/docs】解释</a></p><blockquote><p>gulp 是一个工具包，用于在开发工作流程中自动化繁琐或耗时的任务，因此您可以避免混乱并构建一些东西。</p></blockquote><p>直白来说，就是一个构建工具。常用来制作开发脚手架，生产模版～</p><h3 id="get-started"><a class="markdownIt-Anchor" href="#get-started"></a> Get Started</h3><h4 id="node-环境"><a class="markdownIt-Anchor" href="#node-环境"></a> Node 环境</h4><p>首先，检查 node、npm 和 npx 是否正确安装</p><h4 id="全局安装"><a class="markdownIt-Anchor" href="#全局安装"></a> 全局安装</h4><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 gulp-cli -g</span><br></pre></td></tr></table></figure><h4 id="创建项目目录并进入"><a class="markdownIt-Anchor" href="#创建项目目录并进入"></a> 创建项目目录并进入</h4><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">mkdir gulp-project &amp; cd gulp-project</span><br></pre></td></tr></table></figure><h4 id="在项目目录下创建-packagejson-文件"><a class="markdownIt-Anchor" href="#在项目目录下创建-packagejson-文件"></a> 在项目目录下创建 package.json 文件</h4><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 init</span><br></pre></td></tr></table></figure><h4 id="安装-gulp作为开发时依赖项"><a class="markdownIt-Anchor" href="#安装-gulp作为开发时依赖项"></a> 安装 gulp，作为开发时依赖项</h4><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 --save-dev gulp</span><br></pre></td></tr></table></figure><h4 id="创建-gulpfile-文件"><a class="markdownIt-Anchor" href="#创建-gulpfile-文件"></a> 创建 gulpfile 文件</h4><p>利用任何文本编辑器在项目大的根目录下创建一个名为 gulpfile.js 的文件，并在文件中输入以下内容：</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></pre></td><td class="code"><pre><span class="line">function defaultTask(cb) &#123;</span><br><span class="line">  // place code for your default task here</span><br><span class="line">  cb();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">exports.default = defaultTask</span><br></pre></td></tr></table></figure><h4 id="测试"><a class="markdownIt-Anchor" href="#测试"></a> 测试</h4><p>在项目根目录下执行 gulp 命令：</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">gulp</span><br></pre></td></tr></table></figure><h4 id="输出"><a class="markdownIt-Anchor" href="#输出"></a> 输出</h4><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">[17:16:32] Using gulpfile ~/WebProjects/gulp-project/gulpfile.js</span><br><span class="line">[17:16:32] Starting &apos;default&apos;...</span><br><span class="line">[17:16:32] Finished &apos;default&apos; after 1.37 ms</span><br></pre></td></tr></table></figure><h3 id="高级用法"><a class="markdownIt-Anchor" href="#高级用法"></a> 高级用法</h3><h4 id="文件监控和处理"><a class="markdownIt-Anchor" href="#文件监控和处理"></a> 文件监控和处理</h4><p>使用 watch，监听文件变化，将 src 目录的 js 文件复制到 output 目录下。</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></pre></td><td class="code"><pre><span class="line">const &#123; src, dest, watch &#125; = require(&apos;gulp&apos;);</span><br><span class="line">const gulp = require(&apos;gulp&apos;)</span><br><span class="line"></span><br><span class="line">function streamTask() &#123;</span><br><span class="line">  return src(&apos;src/**/*.js&apos;)</span><br><span class="line">    .pipe(dest(&apos;output&apos;));</span><br><span class="line">&#125;</span><br><span class="line">// 创建一个任务</span><br><span class="line">gulp.task(&apos;watch&apos;, function () &#123;</span><br><span class="line">  watch(&apos;src/*.js&apos;, streamTask);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>似乎非常方便，封装了 <code>chokidar</code></p><h3 id="写在最后"><a class="markdownIt-Anchor" href="#写在最后"></a> 写在最后</h3><p>可以集成 rollup，webpack 等 node api，做一个 hotreload</p><h3 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h3><ul><li><a href="https://www.gulpjs.com.cn/docs/getting-started/quick-start/" target="_blank" rel="noopener">https://www.gulpjs.com.cn/docs/getting-started/quick-start/</a></li></ul>]]></content>
    
    <summary type="html">
    
      明天就要晋升答辩了，今天写篇入门 Wiki 压压惊吧，正好最近想学构建工具之类的东西，Gulp 好像就很合适
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript Event preventDefault和stopPropagation</title>
    <link href="https://wzes.github.io/2019/09/30/JavaScript/JavaScript%20Event/"/>
    <id>https://wzes.github.io/2019/09/30/JavaScript/JavaScript Event/</id>
    <published>2019-09-30T07:30:16.000Z</published>
    <updated>2019-09-30T07:52:23.506Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>对于JS的事件传递还是比较陌生，所以打算好好理一理preventDefault和stopPropagation的用法，彻底告别模糊，提升自己的前端段位！</p><h2 id="栗子"><a class="markdownIt-Anchor" href="#栗子"></a> 栗子</h2><p>在以下示例中，单击Web浏览器中的超链接将触发事件的流程（执行事件监听器）和事件目标的默认操作（打开新选项卡）。</p><p>HTML：</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">&lt;div id=&quot;a&quot;&gt;</span><br><span class="line">  &lt;a id=&quot;b&quot; href=&quot;http://www.google.com/&quot; target=&quot;_blank&quot;&gt;Google&lt;/a&gt;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><p>JavaScript:</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></pre></td><td class="code"><pre><span class="line">var el = document.getElementById(&quot;c&quot;);</span><br><span class="line">function capturingOnClick1(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;DIV event capture&lt;br&gt;&quot;;</span><br><span class="line">&#125;</span><br><span class="line">function capturingOnClick2(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;A event capture&lt;br&gt;&quot;;</span><br><span class="line">&#125;</span><br><span class="line">function bubblingOnClick1(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;DIV event bubbling&lt;br&gt;&quot;;</span><br><span class="line">&#125;</span><br><span class="line">function bubblingOnClick2(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;A event bubbling&lt;br&gt;&quot;;</span><br><span class="line">&#125;</span><br><span class="line">// The 3rd parameter useCapture makes the event listener capturing (false by default)</span><br><span class="line">document.getElementById(&quot;a&quot;).addEventListener(&quot;click&quot;, capturingOnClick1, true);</span><br><span class="line">document.getElementById(&quot;b&quot;).addEventListener(&quot;click&quot;, capturingOnClick2, true);</span><br><span class="line">document.getElementById(&quot;a&quot;).addEventListener(&quot;click&quot;, bubblingOnClick1, false);</span><br><span class="line">document.getElementById(&quot;b&quot;).addEventListener(&quot;click&quot;, bubblingOnClick2, false);</span><br></pre></td></tr></table></figure><p>输出</p><figure class="highlight plain"><figcaption><span>event captureA event captureA event bubblingDIV event bubbling</span></figcaption><table><tr><td class="gutter"><pre><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">DIV event capture</span><br><span class="line">A event capture</span><br><span class="line">A event bubbling</span><br><span class="line">DIV event bubbling</span><br></pre></td></tr></table></figure><h5 id="向capturingonclick1函数添加stoppropagation"><a class="markdownIt-Anchor" href="#向capturingonclick1函数添加stoppropagation"></a> 向<code>capturingOnClick1</code>函数添加<code>stopPropagation()</code></h5><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">function capturingOnClick1(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;DIV event capture&lt;br&gt;&quot;;</span><br><span class="line">    ev.stopPropagation();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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">DIV event capture</span><br></pre></td></tr></table></figure><p>事件侦听器阻止了事件的进一步向下和向上传播。但是，这并没有阻止默认操作（打开新标签页）。</p><h5 id="向capturingonclick2函数添加stoppropagation"><a class="markdownIt-Anchor" href="#向capturingonclick2函数添加stoppropagation"></a> 向<code>capturingOnClick2</code>函数添加<code>stopPropagation()</code></h5><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">function capturingOnClick2(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;A event capture&lt;br&gt;&quot;;</span><br><span class="line">    ev.stopPropagation();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者</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">function bubblingOnClick2(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;A event bubbling&lt;br&gt;&quot;;</span><br><span class="line">    ev.stopPropagation();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</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">DIV event capture</span><br><span class="line">A event capture</span><br><span class="line">A event bubbling</span><br></pre></td></tr></table></figure><p>这是因为两个事件侦听器都注册在同一事件目标上。事件侦听器阻止了事件的进一步向上传播。但是，它们并没有阻止默认操作（打开新标签页）。</p><h5 id="将preventdefault添加到任何函数中"><a class="markdownIt-Anchor" href="#将preventdefault添加到任何函数中"></a> 将<code>preventDefault()</code>添加到任何函数中</h5><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">function capturingOnClick1(ev) &#123;</span><br><span class="line">    el.innerHTML += &quot;DIV event capture&lt;br&gt;&quot;;</span><br><span class="line">    ev.preventDefault();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果照常输出</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">DIV event capture</span><br><span class="line">A event capture</span><br><span class="line">A event bubbling</span><br><span class="line">DIV event bubbling</span><br></pre></td></tr></table></figure><p><strong>但它阻止打开新标签页</strong></p><h2 id="原理解析"><a class="markdownIt-Anchor" href="#原理解析"></a> 原理解析</h2><h3 id="事件顺序"><a class="markdownIt-Anchor" href="#事件顺序"></a> 事件顺序</h3><p>基本问题很简单，假设元素内部有一个元素，如下：</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></pre></td><td class="code"><pre><span class="line">-----------------------------------</span><br><span class="line">| element1                        |</span><br><span class="line">|   -------------------------     |</span><br><span class="line">|   |element2               |     |</span><br><span class="line">|   -------------------------     |</span><br><span class="line">|                                 |</span><br><span class="line">-----------------------------------</span><br></pre></td></tr></table></figure><p>两者都有一个onClick事件处理程序。如果用户单击element2，他将在element1和element2中都引起click事件。但是哪个事件首先触发？应该先执行哪个事件处理程序？换句话说，事件顺序是什么？</p><h3 id="两种事件模型"><a class="markdownIt-Anchor" href="#两种事件模型"></a> 两种事件模型</h3><p>在过去，Netscape和Microsoft得出了不同的结论。</p><ul><li><p>Netscape说，element1上的事件首先发生。这称为事件捕获***(capturing)***。</p></li><li><p>Microsoft坚持认为element2上的事件优先。这称为事件冒泡**(<em>bubbling</em>)**。</p></li></ul><p>这两个事件顺序完全相反。 Explorer仅支持事件冒泡。 Mozilla，Opera 7和Konqueror都支持。旧版Opera和iCab都不支持。</p><h4 id="事件捕获capturing"><a class="markdownIt-Anchor" href="#事件捕获capturing"></a> 事件捕获（capturing）</h4><p>使用事件捕获时</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></pre></td><td class="code"><pre><span class="line">               | |</span><br><span class="line">---------------| |-----------------</span><br><span class="line">| element1     | |                |</span><br><span class="line">|   -----------| |-----------     |</span><br><span class="line">|   |element2  \ /          |     |</span><br><span class="line">|   -------------------------     |</span><br><span class="line">|        Event CAPTURING          |</span><br><span class="line">-----------------------------------</span><br></pre></td></tr></table></figure><p>element1的事件处理程序首先触发，element2的事件处理程序最后触发。</p><h4 id="事件冒泡bubbling"><a class="markdownIt-Anchor" href="#事件冒泡bubbling"></a> 事件冒泡（bubbling）</h4><p>使用事件冒泡时</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></pre></td><td class="code"><pre><span class="line">               / \</span><br><span class="line">---------------| |-----------------</span><br><span class="line">| element1     | |                |</span><br><span class="line">|   -----------| |-----------     |</span><br><span class="line">|   |element2  | |          |     |</span><br><span class="line">|   -------------------------     |</span><br><span class="line">|        Event BUBBLING           |</span><br><span class="line">-----------------------------------</span><br></pre></td></tr></table></figure><p>element2的事件处理程序首先触发，element1的事件处理程序最后触发。</p><h3 id="w3c-事件模型"><a class="markdownIt-Anchor" href="#w3c-事件模型"></a> W3C 事件模型</h3><p>W3C非常明智地决定在这场斗争中处于中间位置。 W3C事件模型中发生的任何事件都首先被捕获，直到到达目标元素，然后再次冒泡。</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></pre></td><td class="code"><pre><span class="line">                 | |  / \</span><br><span class="line">-----------------| |--| |-----------------</span><br><span class="line">| element1       | |  | |                |</span><br><span class="line">|   -------------| |--| |-----------     |</span><br><span class="line">|   |element2    \ /  | |          |     |</span><br><span class="line">|   --------------------------------     |</span><br><span class="line">|        W3C event model                 |</span><br><span class="line">------------------------------------------`</span><br></pre></td></tr></table></figure><p>Web开发人员可以选择是在捕获阶段还是冒泡阶段中注册事件处理程序。这是通过“高级模型”页面上说明的<code>addEventListener()</code>方法完成的。如果最后一个参数为true，则为捕获阶段设置事件处理程序，如果为false，则为冒泡阶段设置事件处理程序。</p><p><strong>假设</strong></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">element1.addEventListener(&apos;click&apos;,doSomething2,true)</span><br><span class="line">element2.addEventListener(&apos;click&apos;,doSomething,false)</span><br></pre></td></tr></table></figure><p>如果用户单击element2，则会发生以下情况：</p><ul><li>单击事件在捕获阶段开始。该事件查找element2的任何祖先元素是否具有用于捕获阶段的onclick事件处理程序。</li><li>该事件在element1上找到一个。<code>doSomething2()</code>被执行。</li><li>事件向下传播到目标本身，找不到用于捕获阶段的事件处理程序。该事件进入其冒泡阶段并执行<code>doSomething()</code>，该事件已在冒泡阶段注册到element2。</li><li>事件再次向上传播，并检查目标的任何祖先元素是否具有用于冒泡阶段的事件处理程序。事实并非如此，因此什么也没有发生。</li></ul><p><strong>相反</strong></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">element1.addEventListener(&apos;click&apos;,doSomething2,false)</span><br><span class="line">element2.addEventListener(&apos;click&apos;,doSomething,false)</span><br></pre></td></tr></table></figure><p>现在，如果用户单击element2，则会发生以下情况：</p><ul><li>单击事件在捕获阶段开始。该事件将查找element2的任何祖先元素是否具有用于捕获阶段的onclick事件处理程序，而找不到任何事件处理程序。</li><li>事件向下传播到目标本身。该事件进入其冒泡阶段并执行<code>doSomething()</code>，该事件已在冒泡阶段注册到element2。</li><li>事件再次向上传播，并检查目标的任何祖先元素是否具有用于冒泡阶段的事件处理程序。</li><li>该事件在element1上找到一个。现在执行<code>doSomething2()</code>。</li></ul><h3 id="与传统模型的兼容性"><a class="markdownIt-Anchor" href="#与传统模型的兼容性"></a> 与传统模型的兼容性</h3><p>在支持W3C DOM的浏览器中，传统的事件注册</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">element1.onclick = doSomething2;</span><br></pre></td></tr></table></figure><p>被视为冒泡阶段的注册。</p><h3 id="使用事件冒泡"><a class="markdownIt-Anchor" href="#使用事件冒泡"></a> 使用事件冒泡</h3><p>很少有Web开发人员自觉使用事件捕获或冒泡。在当今的网页中，根本没有必要让冒泡事件由多个不同的事件处理程序处理。用户可能会因单击鼠标后发生的几件事而感到困惑，并且通常您希望将事件处理脚本分开。当用户单击某个元素时，会发生某些事情，而当用户单击另一个元素时，会发生其他事情。</p><p>当然，这种情况将来可能会改变，因此最好可以使用向前兼容的模型。但是，今天事件捕获和冒泡的主要实际用途是默认功能的注册。</p><h4 id="总是会发生"><a class="markdownIt-Anchor" href="#总是会发生"></a> 总是会发生</h4><p>您首先需要了解的是，事件捕获或冒泡总是会发生。如果您为整个document定义常规的onclick事件处理程序</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">document.onclick = doSomething;</span><br><span class="line">if (document.captureEvents) document.captureEvents(Event.CLICK);</span><br></pre></td></tr></table></figure><p>document中任何元素上的任何click事件最终都会冒泡到document中，从而触发此常规事件处理程序。仅当以前的事件处理脚本明确命令事件停止冒泡时，它才不会冒泡到 document。</p><h4 id="使用"><a class="markdownIt-Anchor" href="#使用"></a> 使用</h4><p>因为任何事件最终都出现在 document 上，所以默认事件处理程序成为可能。假设您有此页面：</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></pre></td><td class="code"><pre><span class="line">------------------------------------</span><br><span class="line">| document                         |</span><br><span class="line">|   ---------------  ------------  |</span><br><span class="line">|   | element1    |  | element2 |  |</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">element1.onclick = doSomething;</span><br><span class="line">element2.onclick = doSomething;</span><br><span class="line">document.onclick = defaultFunction;</span><br></pre></td></tr></table></figure><p>现在，如果用户单击element1或2，则将执行<code>doSomething()</code>。如果需要，可以在此处停止事件传播。如果您不这样做，则事件会上升到<code>defaultFunction()</code>。如果用户单击其他任何地方，还将执行<code>defaultFunction()</code>。有时这可能很有用。</p><p>在拖放脚本中，必须设置<code>document</code>范围的事件处理程序。通常，上层的<code>mousedown</code>事件会选择该层并使之响应<code>mousemove</code>事件。尽管通常在层上注册<code>mousedown</code>以避免浏览器错误，但是其他两个事件处理程序都必须在<code>document</code>范围内。</p><p>记住浏览器学的第一定律：任何事情都有可能发生，并且通常在您最没有准备的情况下才会发生。因此，用户可能会非常疯狂地移动鼠标，而脚本无法跟上，以至于鼠标不再位于图层上。</p><ul><li>如果<code>onmousemove</code>事件处理程序已注册到图层，则该图层不再对鼠标移动做出反应，从而引起混乱。</li><li>如果<code>onmouseup</code>事件处理程序已在图层上注册，则不会捕获此事件，因此即使用户认为他放下了该图层，该图层也会继续对鼠标移动做出反应。这引起了更多的混乱。</li></ul><p>因此，在这种情况下，事件冒泡非常有用，因为在文档级别注册事件处理程序可确保始终执行它们。</p><h4 id="禁用"><a class="markdownIt-Anchor" href="#禁用"></a> 禁用</h4><p>但是通常您想关闭所有捕获和冒泡功能，以防止功能相互干扰。此外，如果您的文档结构非常复杂（很多嵌套表等），则可以通过关闭冒泡来节省系统资源。浏览器必须遍历事件目标的每个祖先元素，以查看其是否具有事件处理程序。即使未找到，搜索仍然需要时间。</p><p>在Microsoft模型中，您必须将事件的cancelBubble属性设置为true。</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">window.event.cancelBubble = true</span><br></pre></td></tr></table></figure><p>在W3C模型中，您必须调用事件的*stopPropagation()*方法。</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">e.stopPropagation()</span><br></pre></td></tr></table></figure><p>这将停止事件在冒泡阶段的所有传播。要获得完整的跨浏览器体验，请执行</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></pre></td><td class="code"><pre><span class="line">function doSomething(e)</span><br><span class="line">&#123;</span><br><span class="line">if (!e) var e = window.event;</span><br><span class="line">e.cancelBubble = true;</span><br><span class="line">if (e.stopPropagation) e.stopPropagation();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在不支持该功能的浏览器中设置cancelBubble属性不会有任何问题。浏览器耸耸肩并创建属性。当然，它实际上并不能消除冒泡，但是作业本身是安全的。</p><h4 id="当前目标"><a class="markdownIt-Anchor" href="#当前目标"></a> 当前目标</h4><p>如我们前面所见，事件具有一个target或srcElement，其中包含对该事件发生所在元素的引用。在我们的示例中，这是element2，因为用户单击了它。</p><p>非常重要的一点是要理解，在捕获和冒泡阶段（如果有），该目标不会改变：它始终是对element2的引用。</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">element1.onclick = doSomething;</span><br><span class="line">element2.onclick = doSomething;</span><br></pre></td></tr></table></figure><p>如果用户单击element2，则<code>doSomething()</code>将执行两次。但是，您如何知道当前正在处理该事件的HTML元素？ target / srcElement不提供任何线索，它们始终引用element2，因为它是事件的原始来源。</p><p>为了解决此问题，W3C添加了currentTarget属性。它包含对事件当前正在处理的HTML元素的引用：正是我们所需要的。不幸的是，Microsoft模型不包含类似的属性。</p><p>您也可以使用this关键字。在上面的示例中，它引用处理事件的HTML元素，就像currentTarget一样。</p><h4 id="microsoft模式的问题"><a class="markdownIt-Anchor" href="#microsoft模式的问题"></a> Microsoft模式的问题</h4><p>但是，当您使用Microsoft事件注册模型时，此关键字并不引用HTML元素。加上Microsoft模型中缺少类似于currentTarget的属性，这意味着如果您这样做</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">element1.attachEvent(&apos;onclick&apos;,doSomething)</span><br><span class="line">element2.attachEvent(&apos;onclick&apos;,doSomething)</span><br></pre></td></tr></table></figure><p>您不知道当前哪个HTML元素处理该事件。这是Microsoft事件注册模型中最严重的问题，对我来说，这是一个从不使用它的理由，即使在仅IE / Win的应用程序中也是如此。</p><p>我希望微软能尽快添加类似于currentTarget的属性，甚至可以遵循该标准？ Web开发人员需要此信息。</p><h3 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h3><p><a href="https://www.quirksmode.org/js/events_order.html" target="_blank" rel="noopener">Event Order</a></p>]]></content>
    
    <summary type="html">
    
      对于JS的事件传递还是比较陌生，所以打算好好理一理preventDefault和stopPropagation的用法，彻底告别模糊，提升自己的前端段位！
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Browser Window 扫盲</title>
    <link href="https://wzes.github.io/2019/09/22/JavaScript/BrowserWindow/"/>
    <id>https://wzes.github.io/2019/09/22/JavaScript/BrowserWindow/</id>
    <published>2019-09-22T10:56:00.000Z</published>
    <updated>2019-09-21T14:18:05.202Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>作为一个 Node 开发工程师，似乎对于 Window 并不需要关心，但作为一个前端工程师，Window 是个啥子东东，还是需要花时间理一理的。本期看点，深入了解 Window 的 API ～</p><h3 id="window"><a class="markdownIt-Anchor" href="#window"></a> Window</h3><p>所有的浏览器都有 Window 对象，每个窗口都会具有一个 Window，通常在浏览器开发者模式中，可以使用 window 拿到这个对象。</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">window</span><br></pre></td></tr></table></figure><p>window 具有很多属性和方法，常见的有</p><ul><li><p>navigator 导航器</p></li><li><p>screen 显示器</p></li><li><p>history 历史对象</p></li><li><p>location 位置对象</p></li><li><p>document 文档对象</p></li><li><p>方法</p><ul><li><p>open</p></li><li><p>close</p></li><li><p>setTimeout</p></li><li><p>setInterval</p><p>…</p></li></ul></li></ul><p>在浏览器中，我们使用这些对象和方法，完整的用法是 <strong>window.xx</strong>，但我们可以省略 window，直接使用 xx 即可，对于新手来说可能觉得很奇怪。</p><p>复习一下方法调用， Javascript 的方法执行最终都会是以  <strong>xx.call(this, args…)</strong> 的形式，这里的 this 就是方法的上下文，通常是调用的对象</p><p>在浏览器里，如果我们直接使用一个属性或者方法，那么执行这个方法的上下文通常都是 window 对象，所以我们可以直接使用这些对象和方法，因为上下文环境就是 window，最后取的都是 window 的属性和方法。</p><p>放一张比较完整的图～</p><p><img src="/2019/09/22/JavaScript/BrowserWindow/window.gif" alt></p><h3 id="document"><a class="markdownIt-Anchor" href="#document"></a> document</h3><p>document 是我们的文档对象，我们可以使用开发者模式直接打印 document，document 打印出来（toString）就是我们的 html，实际上 document 对象也具有很多方法和属性。</p><p><img src="/2019/09/22/JavaScript/BrowserWindow/document.jpg" alt></p><h4 id="createelement"><a class="markdownIt-Anchor" href="#createelement"></a> createElement</h4><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">const div = document.createElement(&apos;div&apos;)</span><br><span class="line">div.appendChild()</span><br></pre></td></tr></table></figure><h4 id="getelementbyid"><a class="markdownIt-Anchor" href="#getelementbyid"></a> getElementById</h4><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">const div = document.getElementById(&apos;id&apos;)</span><br><span class="line">div.appendChild()</span><br></pre></td></tr></table></figure><p>利用这些方法创建元素，然后将元素挂载到已有的 dom (app)上，React，Vue 框架最核心的原理不过如此</p><h4 id="cookie"><a class="markdownIt-Anchor" href="#cookie"></a> cookie</h4><p>cookie 对象是 document 的属性，当访问同源的网站是，浏览器会自动帮我们吧 cookie 对象传过去，cookie 对象通常包含了用户信息，服务端可以通过 cookie 判断用户的状态，cookie 默认是持久化保存的</p><h4 id="location"><a class="markdownIt-Anchor" href="#location"></a> location</h4><p>location 对象即 window 的 location 对象</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">document.location == window.location</span><br><span class="line">// true</span><br></pre></td></tr></table></figure><h4 id="url"><a class="markdownIt-Anchor" href="#url"></a> URL</h4><p>返回当前的 url</p><h4 id="open"><a class="markdownIt-Anchor" href="#open"></a> open</h4><p>与 window 的 open 不同， document 的 open 是操作 document 对象，打开一个 document 的输入流，open 将会清空当前的文档</p><h4 id="close"><a class="markdownIt-Anchor" href="#close"></a> close</h4><p>关闭文档流</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">document.open(); </span><br><span class="line">document.write(&quot;&lt;p&gt;Hello world!&lt;/p&gt;&quot;);</span><br><span class="line">document.write(&quot;&lt;p&gt;I am a fish&lt;/p&gt;&quot;);</span><br><span class="line">document.write(&quot;&lt;p&gt;The number is 42&lt;/p&gt;&quot;); </span><br><span class="line">document.close();</span><br></pre></td></tr></table></figure><p><img src="/2019/09/22/JavaScript/BrowserWindow/open_close.jpg" alt></p><h3 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h3><p>window 对象博大精深～</p>]]></content>
    
    <summary type="html">
    
      作为一个 Node 开发工程师，似乎对于 Window 并不需要关心，但作为一个前端工程师，Window 是个啥子东东，还是需要花时间理一理的。本期看点，深入了解 Window 的 API ～～
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>koa-bodyparser 源码解析</title>
    <link href="https://wzes.github.io/2019/09/08/JavaScript/koa-bodyparser/"/>
    <id>https://wzes.github.io/2019/09/08/JavaScript/koa-bodyparser/</id>
    <published>2019-09-08T10:56:00.000Z</published>
    <updated>2019-09-08T15:31:51.951Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>前面我们已经把【裸配】 Koa 学习了一下，学完了以后好像并没有什么好用的东西，然而这玩意就像一个巨大的平台，容易集成各种小插件，来达到各种各样的功能。接下来我们学习一个 koa-bodyparser 这歌短小精悍的库！</p><h3 id="helloworld"><a class="markdownIt-Anchor" href="#helloworld"></a> HelloWorld</h3><p>当我的 hello world 如此简单的时候，我想发一个 POST 请求，那么我怎么拿到 POST 请求的参数呢，我们 ctx.request.body 是空的，Koa 原声的框架并没有帮我们解析 POST 的请求数据。所以我们就需要加入 koa-bodyparser 中间件，这样我们就可以通过  ctx.request.body 拿到数据了。</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></pre></td><td class="code"><pre><span class="line">const Koa = require(&apos;koa&apos;);</span><br><span class="line">const app = new Koa();</span><br><span class="line">app.use(async ctx =&gt; &#123;</span><br><span class="line">    // ctx.body = ctx.query.param;</span><br><span class="line">    console.log( ctx.request.body)</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(3000);</span><br></pre></td></tr></table></figure><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">npm install koa-bodyparser --save</span><br></pre></td></tr></table></figure><p>然后再代码中加入这个中间件：</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></pre></td><td class="code"><pre><span class="line">const Koa = require(&apos;koa&apos;);</span><br><span class="line">const bodyParser = require(&apos;koa-bodyparser&apos;)</span><br><span class="line"></span><br><span class="line">const app = new Koa();</span><br><span class="line"></span><br><span class="line">app.use(bodyParser());</span><br><span class="line"></span><br><span class="line">app.use(async ctx =&gt; &#123;</span><br><span class="line">    ctx.body = ctx.request.body.param;</span><br><span class="line">    console.log(ctx.request.body)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.listen(3000);</span><br></pre></td></tr></table></figure><p>这时候 ctx.request.body 便是一个解析好的对象了，直接取对象的属性就可以了</p><h5 id="题外话"><a class="markdownIt-Anchor" href="#题外话"></a> 题外话</h5><p>原声的 NodeJS 通过 createServer 也是没有办法直接取到 post 的参数的，还是需要做一些读取数据的操作才可以</p><h3 id="源码解析"><a class="markdownIt-Anchor" href="#源码解析"></a> 源码解析</h3><h4 id="bodyparser"><a class="markdownIt-Anchor" href="#bodyparser"></a> bodyParser</h4><p>作为一个中间件，需要返回一个 async 函数，在这个函数的最后调用 next()，关键部分是 parseBody 函数，将 request 进行解析，并将结果返回赋值给 ctx.request.body</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></pre></td><td class="code"><pre><span class="line">return async function bodyParser(ctx, next) &#123;</span><br><span class="line">    if (ctx.request.body !== undefined) return await next();</span><br><span class="line">    if (ctx.disableBodyParser) return await next();</span><br><span class="line">    try &#123;</span><br><span class="line">      const res = await parseBody(ctx);</span><br><span class="line">      ctx.request.body = &apos;parsed&apos; in res ? res.parsed : &#123;&#125;;</span><br><span class="line">      if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw;</span><br><span class="line">    &#125; catch (err) &#123;</span><br><span class="line">      if (onerror) &#123;</span><br><span class="line">        onerror(err, ctx);</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        throw err;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    await next();</span><br><span class="line">  &#125;;</span><br></pre></td></tr></table></figure><h5 id="parsebody"><a class="markdownIt-Anchor" href="#parsebody"></a> parseBody</h5><p><a href="http://ctx.request.is" target="_blank" rel="noopener">ctx.request.is</a> 可以检查 request 【Content-Type】 请求的类型是否是当中的一个，一般情况下以 form 居多，即 <strong>application/x-www-form-urlencoded</strong></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></pre></td><td class="code"><pre><span class="line">async function parseBody(ctx) &#123;</span><br><span class="line">    if (enableJson &amp;&amp; ((detectJSON &amp;&amp; detectJSON(ctx)) || ctx.request.is(jsonTypes))) &#123;</span><br><span class="line">      return await parse.json(ctx, jsonOpts);</span><br><span class="line">    &#125;</span><br><span class="line">    if (enableForm &amp;&amp; ctx.request.is(formTypes)) &#123;</span><br><span class="line">      return await parse.form(ctx, formOpts);</span><br><span class="line">    &#125;</span><br><span class="line">    if (enableText &amp;&amp; ctx.request.is(textTypes)) &#123;</span><br><span class="line">      return await parse.text(ctx, textOpts) || &apos;&apos;;</span><br><span class="line">    &#125;</span><br><span class="line">    return &#123;&#125;;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h5 id="parseform"><a class="markdownIt-Anchor" href="#parseform"></a> parse.form</h5><p>主要是 raw(inflate(req), opts) 方法，将请求的参数转化为一个 string</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><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">module.exports = async function(req, opts) &#123;</span><br><span class="line">  req = req.req || req;</span><br><span class="line">  opts = utils.clone(opts);</span><br><span class="line">  const queryString = opts.queryString || &#123;&#125;;</span><br><span class="line"></span><br><span class="line">  // keep compatibility with qs@4</span><br><span class="line">  if (queryString.allowDots === undefined) queryString.allowDots = true;</span><br><span class="line"></span><br><span class="line">  // defaults</span><br><span class="line">  const len = req.headers[&apos;content-length&apos;];</span><br><span class="line">  const encoding = req.headers[&apos;content-encoding&apos;] || &apos;identity&apos;;</span><br><span class="line">  if (len &amp;&amp; encoding === &apos;identity&apos;) opts.length = ~~len;</span><br><span class="line">  opts.encoding = opts.encoding || &apos;utf8&apos;;</span><br><span class="line">  opts.limit = opts.limit || &apos;56kb&apos;;</span><br><span class="line">  opts.qs = opts.qs || qs;</span><br><span class="line"></span><br><span class="line">  const str = await raw(inflate(req), opts);</span><br><span class="line">  try &#123;</span><br><span class="line">    const parsed = opts.qs.parse(str, queryString);</span><br><span class="line">    return opts.returnRawBody ? &#123; parsed, raw: str &#125; : parsed;</span><br><span class="line">  &#125; catch (err) &#123;</span><br><span class="line">    err.status = 400;</span><br><span class="line">    err.body = str;</span><br><span class="line">    throw err;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h5 id="raw"><a class="markdownIt-Anchor" href="#raw"></a> raw</h5><p>读取 body 数据，</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></pre></td><td class="code"><pre><span class="line">function getRawBody (stream, options, callback) &#123;</span><br><span class="line"></span><br><span class="line">  return new Promise(function executor (resolve, reject) &#123;</span><br><span class="line">    readStream(stream, encoding, length, limit, function onRead (err, buf) &#123;</span><br><span class="line">      if (err) return reject(err)</span><br><span class="line">      resolve(buf)</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>选取了 onData 函数，可以接受流数据，以字符串形式，结束后直接返回 字符串即可，数据为 buffer，使用 buffer.toString() 就可以转化为字符串。</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">function readStream (stream, encoding, length, limit, callback) &#123;</span><br><span class="line">  // attach listeners</span><br><span class="line">  stream.on(&apos;aborted&apos;, onAborted)</span><br><span class="line">  stream.on(&apos;close&apos;, cleanup)</span><br><span class="line">  stream.on(&apos;data&apos;, onData)</span><br><span class="line">  stream.on(&apos;end&apos;, onEnd)</span><br><span class="line">  stream.on(&apos;error&apos;, onEnd)</span><br><span class="line">  </span><br><span class="line">  function onData (chunk) &#123;</span><br><span class="line">    if (complete) return</span><br><span class="line"></span><br><span class="line">    received += chunk.length</span><br><span class="line"></span><br><span class="line">    if (limit !== null &amp;&amp; received &gt; limit) &#123;</span><br><span class="line">      done(createError(413, &apos;request entity too large&apos;, &#123;</span><br><span class="line">        limit: limit,</span><br><span class="line">        received: received,</span><br><span class="line">        type: &apos;entity.too.large&apos;</span><br><span class="line">      &#125;))</span><br><span class="line">    &#125; else if (decoder) &#123;</span><br><span class="line">      buffer += decoder.write(chunk)</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      buffer.push(chunk)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  function onEnd (err) &#123;</span><br><span class="line">    if (complete) return</span><br><span class="line">    if (err) return done(err)</span><br><span class="line"></span><br><span class="line">    if (length !== null &amp;&amp; received !== length) &#123;</span><br><span class="line">      done(createError(400, &apos;request size did not match content length&apos;, &#123;</span><br><span class="line">        expected: length,</span><br><span class="line">        length: length,</span><br><span class="line">        received: received,</span><br><span class="line">        type: &apos;request.size.invalid&apos;</span><br><span class="line">      &#125;))</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      var string = decoder</span><br><span class="line">        ? buffer + (decoder.end() || &apos;&apos;)</span><br><span class="line">        : Buffer.concat(buffer)</span><br><span class="line">      done(null, string)</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>在 onEnd 中返回字符串</p><h3 id="写在最后"><a class="markdownIt-Anchor" href="#写在最后"></a> 写在最后</h3><p>koa-bodyparser 不仅做了 post 的转化，还有一些可选的参数，比如说传输类型，大小限制，这些都是可以通过参数进行配置的。源码博大精深啊！</p>]]></content>
    
    <summary type="html">
    
      前面我们已经把【裸配】 Koa 学习了一下，学完了以后好像并没有什么好用的东西，然而这玩意就像一个巨大的平台，容易集成各种小插件，来达到各种各样的功能。接下来我们学习一个 koa-bodyparser 这歌短小精悍的库！
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="Node" scheme="https://wzes.github.io/tags/Node/"/>
    
      <category term="Koa" scheme="https://wzes.github.io/tags/Koa/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript Array 的 1 个属性，35 个方法</title>
    <link href="https://wzes.github.io/2019/09/01/JavaScript/JavaScript%20Array/"/>
    <id>https://wzes.github.io/2019/09/01/JavaScript/JavaScript Array/</id>
    <published>2019-09-01T07:30:16.000Z</published>
    <updated>2019-09-07T04:15:12.383Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>这周呢，彻底学习一下 Array 的所有方法。学习地址 MDN，里面还有各个函数实现的源码！数组作为 JavaScript 的一种特殊的对象类型，与 Number，Boolean，Null，String，Undefined，Symbol  七大数据类型有所不一样。了解 Array 的所有方法，能帮助我们最快找到适合自己的函数。</p><h4 id="create-an-array"><a class="markdownIt-Anchor" href="#create-an-array"></a> Create an Array</h4><p>创建一个数组很简单，直接赋值，或者使用 <code>[]</code> 创建空数组</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">var fruits = [&apos;Apple&apos;, &apos;Banana&apos;];</span><br><span class="line">console.log(fruits.length); // 2</span><br></pre></td></tr></table></figure><h4 id><a class="markdownIt-Anchor" href="#"></a> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length#" target="_blank" rel="noopener"><strong>Properties</strong></a></h4><h5 id="arraylength"><a class="markdownIt-Anchor" href="#arraylength"></a> Array.length</h5><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></pre></td><td class="code"><pre><span class="line">var clothing = [&apos;shoes&apos;, &apos;shirts&apos;, &apos;socks&apos;, &apos;sweaters&apos;];</span><br><span class="line"></span><br><span class="line">console.log(clothing.length);</span><br><span class="line">// expected output: 4</span><br><span class="line"></span><br><span class="line">var array = new Array(2)</span><br></pre></td></tr></table></figure><h4 id="-2"><a class="markdownIt-Anchor" href="#-2"></a> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#" target="_blank" rel="noopener"><strong>Methods</strong></a></h4><p>首先看一下 Array 的三个静态方法<br>#####1. Array.from()<br><strong>Array.from()</strong> 方法从类似数组或可迭代的对象创建一个新的，浅拷贝的 Array 实例，或者从 {length: 3} 对象中创建固定长度的 undefined 数组</p><blockquote><p>Array.from(arrayLike[, mapFn[, thisArg]])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">console.log(Array.from(&apos;foo&apos;));</span><br><span class="line">// expected output: Array [&quot;f&quot;, &quot;o&quot;, &quot;o&quot;]</span><br><span class="line"></span><br><span class="line">console.log(Array.from([1, 2, 3], x =&gt; x + x));</span><br><span class="line">// expected output: Array [2, 4, 6]</span><br><span class="line"></span><br><span class="line">console.log(Array.from(&#123;length: 3&#125;));</span><br><span class="line">// expected output: Array [undefined, undefined, undefined]</span><br></pre></td></tr></table></figure><h5 id="2-arrayisarray"><a class="markdownIt-Anchor" href="#2-arrayisarray"></a> 2. Array.isArray()</h5><p><strong>Array.isArray()</strong> 方法确定传递的值是否为 Array，由于 typeof Array = ‘object’ ，所以判断是否是一个数组使用 isArray 才可以</p><blockquote><p>Array.isArray(value)</p></blockquote><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">Array.isArray([1, 2, 3]);  // true</span><br><span class="line">Array.isArray(&#123;foo: 123&#125;); // false</span><br><span class="line">Array.isArray(&apos;foobar&apos;);   // false</span><br><span class="line">Array.isArray(undefined);  // false</span><br></pre></td></tr></table></figure><h5 id="3-arrayof"><a class="markdownIt-Anchor" href="#3-arrayof"></a> 3. Array.of()</h5><p><strong>Array.of()</strong> 方法从可变数量的参数创建一个新的Array实例，无论参数的数量或类型如何<br>注意它与构造函数的不同之处</p><blockquote><p>Array.of(element0[, element1[, …[, elementN]]])</p></blockquote><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">Array.of(7);       // [7] </span><br><span class="line">Array.of(1, 2, 3); // [1, 2, 3]</span><br><span class="line"></span><br><span class="line">Array(7);          // array of 7 empty slots</span><br><span class="line">Array(1, 2, 3);    // [1, 2, 3]</span><br></pre></td></tr></table></figure><p>接下来看 Array 的对象方法</p><h5 id="4-arrayprototypeconcat"><a class="markdownIt-Anchor" href="#4-arrayprototypeconcat"></a> 4. Array.prototype.concat()</h5><p><strong>concat()</strong> 方法用于合并两个或多个数组。 此方法不会更改现有数组，而是返回一个新数组</p><blockquote><p>var new_array = old_array.concat([value1[, value2[, …[, valueN]]]])</p></blockquote><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">var array1 = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;];</span><br><span class="line">var array2 = [&apos;d&apos;, &apos;e&apos;, &apos;f&apos;];</span><br><span class="line"></span><br><span class="line">console.log(array1.concat(array2));</span><br><span class="line">// expected output: Array [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;, &quot;f&quot;]</span><br></pre></td></tr></table></figure><h5 id="5-arrayprototypecopywithin"><a class="markdownIt-Anchor" href="#5-arrayprototypecopywithin"></a> 5. Array.prototype.copyWithin()</h5><p><strong>copyWithin()</strong> 方法浅析将数组的一部分复制到同一数组中的另一个位置，并返回它而不修改其长度</p><blockquote><p>arr.copyWithin(target[, start[, end]])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;, &apos;d&apos;, &apos;e&apos;];</span><br><span class="line"></span><br><span class="line">// copy to index 0 the element at index 3</span><br><span class="line">console.log(array1.copyWithin(0, 3, 4));</span><br><span class="line">// expected output: Array [&quot;d&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;]</span><br><span class="line"></span><br><span class="line">// copy to index 1 all elements from index 3 to the end</span><br><span class="line">console.log(array1.copyWithin(1, 3));</span><br><span class="line">// expected output: Array [&quot;d&quot;, &quot;d&quot;, &quot;e&quot;, &quot;d&quot;, &quot;e&quot;]</span><br></pre></td></tr></table></figure><h5 id="6-arrayprototypeentries"><a class="markdownIt-Anchor" href="#6-arrayprototypeentries"></a> 6. Array.prototype.entries()</h5><p><strong>entries()</strong> 方法返回一个新的Array Iterator对象，该对象包含数组中每个索引的键/值对。</p><blockquote><p>array.entries()</p></blockquote><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><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">var array1 = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;];</span><br><span class="line"></span><br><span class="line">var iterator1 = array1.entries();</span><br><span class="line"></span><br><span class="line">console.log(iterator1.next().value);</span><br><span class="line">// expected output: Array [0, &quot;a&quot;]</span><br><span class="line"></span><br><span class="line">console.log(iterator1.next().value);</span><br><span class="line">// expected output: Array [1, &quot;b&quot;]</span><br><span class="line"></span><br><span class="line">// 使用 for of 遍历</span><br><span class="line">var a = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;];</span><br><span class="line">var iterator = a.entries();</span><br><span class="line"></span><br><span class="line">for (let e of iterator) &#123;</span><br><span class="line">  console.log(e);</span><br><span class="line">&#125;</span><br><span class="line">// [0, &apos;a&apos;]</span><br><span class="line">// [1, &apos;b&apos;]</span><br><span class="line">// [2, &apos;c&apos;]</span><br></pre></td></tr></table></figure><h5 id="7-arrayprototypeevery"><a class="markdownIt-Anchor" href="#7-arrayprototypeevery"></a> 7. Array.prototype.every()</h5><p><strong>every()</strong> 方法测试数组中的所有元素是否都通过了由提供的函数实现的测试。 它返回一个布尔值</p><blockquote><p>arr.every(callback(element[, index[, array]])[, thisArg])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">function isBelowThreshold(currentValue) &#123;</span><br><span class="line">  return currentValue &lt; 40;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var array1 = [1, 30, 39, 29, 10, 13];</span><br><span class="line"></span><br><span class="line">console.log(array1.every(isBelowThreshold));</span><br><span class="line">// expected output: true</span><br></pre></td></tr></table></figure><h5 id="8-arrayprototypefill"><a class="markdownIt-Anchor" href="#8-arrayprototypefill"></a> 8. Array.prototype.fill()</h5><p><strong>fill()</strong> 方法使用静态值从开始索引（默认为零）到结束索引（默认数组长度）填充（修改）数组的所有元素。 它返回修改后的数组，原数组会改变～</p><blockquote><p>arr.fill(value[, start[, end]])</p></blockquote><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></pre></td><td class="code"><pre><span class="line"> var array1 = [1, 2, 3, 4];</span><br><span class="line"></span><br><span class="line">// fill with 0 from position 2 until position 4</span><br><span class="line">console.log(array1.fill(0, 2, 4));</span><br><span class="line">// expected output: [1, 2, 0, 0]</span><br><span class="line"></span><br><span class="line">// fill with 5 from position 1</span><br><span class="line">console.log(array1.fill(5, 1));</span><br><span class="line">// expected output: [1, 5, 5, 5]</span><br><span class="line"></span><br><span class="line">console.log(array1.fill(6));</span><br><span class="line">// expected output: [6, 6, 6, 6]</span><br></pre></td></tr></table></figure><h5 id="9-arrayprototypefilter"><a class="markdownIt-Anchor" href="#9-arrayprototypefilter"></a> 9.  Array.prototype.filter()</h5><p><strong>filter()</strong> 方法创建一个新数组，其中包含所有传递由提供的函数实现的测试的元素</p><blockquote><p>var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var words = [&apos;spray&apos;, &apos;limit&apos;, &apos;elite&apos;, &apos;exuberant&apos;, &apos;destruction&apos;, &apos;present&apos;];</span><br><span class="line"></span><br><span class="line">const result = words.filter(word =&gt; word.length &gt; 6);</span><br><span class="line"></span><br><span class="line">console.log(result);</span><br><span class="line">// expected output: Array [&quot;exuberant&quot;, &quot;destruction&quot;, &quot;present&quot;]</span><br></pre></td></tr></table></figure><h5 id="10-arrayprototypefind"><a class="markdownIt-Anchor" href="#10-arrayprototypefind"></a> 10. Array.prototype.find()</h5><p><strong>find()</strong> 方法返回数组中第一个满足提供的测试函数的元素的值。 否则返回undefined</p><blockquote><p>arr.find(callback(element[, index[, array]])[, thisArg])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [5, 12, 8, 130, 44];</span><br><span class="line"></span><br><span class="line">var found = array1.find(function(element) &#123;</span><br><span class="line">  return element &gt; 10;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">console.log(found);</span><br><span class="line">// expected output: 12</span><br></pre></td></tr></table></figure><h5 id="11-arrayprototypefindindex"><a class="markdownIt-Anchor" href="#11-arrayprototypefindindex"></a> 11. Array.prototype.findIndex()</h5><p><strong>findIndex()</strong> 方法返回数组中第一个满足提供的测试函数的元素的索引。 否则，它返回-1，表示没有元素通过测试</p><blockquote><p>arr.findIndex(callback(element[, index[, array]])[, thisArg])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [5, 12, 8, 130, 44];</span><br><span class="line"></span><br><span class="line">function isLargeNumber(element) &#123;</span><br><span class="line">  return element &gt; 13;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">console.log(array1.findIndex(isLargeNumber));</span><br></pre></td></tr></table></figure><h5 id="12-arrayprototypeflat"><a class="markdownIt-Anchor" href="#12-arrayprototypeflat"></a> 12. Array.prototype.flat()</h5><p><strong>flat()</strong> 方法创建一个新数组，所有子数组元素以递归方式连接到指定的深度。</p><blockquote><p>var newArray = arr.flat([depth]);</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var arr1 = [1, 2, [3, 4]];</span><br><span class="line">arr1.flat(); </span><br><span class="line">// [1, 2, 3, 4]</span><br><span class="line"></span><br><span class="line">var arr2 = [1, 2, [3, 4, [5, 6]]];</span><br><span class="line">arr2.flat();</span><br><span class="line">// [1, 2, 3, 4, [5, 6]]</span><br><span class="line"></span><br><span class="line">var arr3 = [1, 2, [3, 4, [5, 6]]];</span><br><span class="line">arr3.flat(2);</span><br><span class="line">// [1, 2, 3, 4, 5, 6]</span><br><span class="line"></span><br><span class="line">var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];</span><br><span class="line">arr4.flat(Infinity);</span><br><span class="line">// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]</span><br></pre></td></tr></table></figure><h5 id="13-arrayprototypeflatmap"><a class="markdownIt-Anchor" href="#13-arrayprototypeflatmap"></a> 13. Array.prototype.flatMap()</h5><p><strong>flatMap()</strong> 方法首先使用映射函数映射每个元素，然后将结果展平为新数组。 它与map（）后跟深度为1的flat（）相同，但flatMap（）通常非常有用，因为将两者合并到一个方法中效率稍高</p><blockquote><p>var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {<br>// return element for new_array<br>}[, thisArg])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">let arr1 = [&quot;it&apos;s Sunny in&quot;, &quot;&quot;, &quot;California&quot;];</span><br><span class="line"></span><br><span class="line">arr1.map(x =&gt; x.split(&quot; &quot;));</span><br><span class="line">// [[&quot;it&apos;s&quot;,&quot;Sunny&quot;,&quot;in&quot;],[&quot;&quot;],[&quot;California&quot;]]</span><br><span class="line"></span><br><span class="line">arr1.flatMap(x =&gt; x.split(&quot; &quot;));</span><br><span class="line">// [&quot;it&apos;s&quot;,&quot;Sunny&quot;,&quot;in&quot;, &quot;&quot;, &quot;California&quot;]</span><br></pre></td></tr></table></figure><h5 id="14-arrayprototypeforeach"><a class="markdownIt-Anchor" href="#14-arrayprototypeforeach"></a> 14. Array.prototype.forEach()</h5><p><strong>forEach()</strong> 方法为每个数组元素执行一次提供的函数</p><blockquote><p>arr.forEach(callback(currentValue [, index [, array]])[, thisArg]);</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;];</span><br><span class="line"></span><br><span class="line">array1.forEach(function(element) &#123;</span><br><span class="line">  console.log(element);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">// expected output: &quot;a&quot;</span><br><span class="line">// expected output: &quot;b&quot;</span><br><span class="line">// expected output: &quot;c&quot;</span><br></pre></td></tr></table></figure><h5 id="15-arrayprototypeincludes"><a class="markdownIt-Anchor" href="#15-arrayprototypeincludes"></a> 15. Array.prototype.includes()</h5><p><strong>includes()</strong> 方法确定数组是否在其条目中包含某个值，并在适当时返回true或false</p><blockquote><p>arr.includes(valueToFind[, fromIndex])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [1, 2, 3];</span><br><span class="line"></span><br><span class="line">console.log(array1.includes(2));</span><br><span class="line">// expected output: true</span><br><span class="line"></span><br><span class="line">var pets = [&apos;cat&apos;, &apos;dog&apos;, &apos;bat&apos;];</span><br><span class="line"></span><br><span class="line">console.log(pets.includes(&apos;cat&apos;));</span><br><span class="line">// expected output: true</span><br><span class="line"></span><br><span class="line">console.log(pets.includes(&apos;at&apos;));</span><br><span class="line">// expected output: false</span><br></pre></td></tr></table></figure><h5 id="16-arrayprototypeindexof"><a class="markdownIt-Anchor" href="#16-arrayprototypeindexof"></a> 16. Array.prototype.indexOf()</h5><p><strong>indexOf()</strong> 方法返回可在数组中找到给定元素的第一个索引，如果不存在则返回-1</p><blockquote><p>arr.indexOf(searchElement[, fromIndex])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var beasts = [&apos;ant&apos;, &apos;bison&apos;, &apos;camel&apos;, &apos;duck&apos;, &apos;bison&apos;];</span><br><span class="line"></span><br><span class="line">console.log(beasts.indexOf(&apos;bison&apos;));</span><br><span class="line">// expected output: 1</span><br><span class="line"></span><br><span class="line">// start from index 2</span><br><span class="line">console.log(beasts.indexOf(&apos;bison&apos;, 2));</span><br><span class="line">// expected output: 4</span><br><span class="line"></span><br><span class="line">console.log(beasts.indexOf(&apos;giraffe&apos;));</span><br><span class="line">// expected output: -1</span><br></pre></td></tr></table></figure><h5 id="17-arrayprototypejoin"><a class="markdownIt-Anchor" href="#17-arrayprototypejoin"></a> 17. Array.prototype.join()</h5><p><strong>join()</strong> 方法通过连接数组（或类数组对象）中的所有元素（用逗号或指定的分隔符字符串分隔）来创建并返回一个新字符串。 如果数组只有一个项目，那么将返回该项目而不使用分隔符</p><blockquote><p>arr.join([separator])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">ar elements = [&apos;Fire&apos;, &apos;Air&apos;, &apos;Water&apos;];</span><br><span class="line"></span><br><span class="line">console.log(elements.join());</span><br><span class="line">// expected output: &quot;Fire,Air,Water&quot;</span><br><span class="line"></span><br><span class="line">console.log(elements.join(&apos;&apos;));</span><br><span class="line">// expected output: &quot;FireAirWater&quot;</span><br><span class="line"></span><br><span class="line">console.log(elements.join(&apos;-&apos;));</span><br><span class="line">// expected output: &quot;Fire-Air-Water&quot;</span><br></pre></td></tr></table></figure><h5 id="18-arrayprototypekeys"><a class="markdownIt-Anchor" href="#18-arrayprototypekeys"></a> 18. Array.prototype.keys()</h5><p><strong>keys()</strong> 方法返回一个新的Array Iterator对象，该对象包含数组中每个索引的键</p><blockquote></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;];</span><br><span class="line">var iterator = array1.keys(); </span><br><span class="line">  </span><br><span class="line">for (let key of iterator) &#123;</span><br><span class="line">  console.log(key); // expected output: 0 1 2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="19-arrayprototypelastindexof"><a class="markdownIt-Anchor" href="#19-arrayprototypelastindexof"></a> 19. Array.prototype.lastIndexOf()</h5><p><strong>lastIndexOf()</strong> 方法返回可在数组中找到给定元素的最后一个索引，如果不存在则返回-1。 从fromIndex开始向后搜索数组</p><blockquote><p>arr.lastIndexOf(searchElement[, fromIndex])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var animals = [&apos;Dodo&apos;, &apos;Tiger&apos;, &apos;Penguin&apos;, &apos;Dodo&apos;];</span><br><span class="line"></span><br><span class="line">console.log(animals.lastIndexOf(&apos;Dodo&apos;));</span><br><span class="line">// expected output: 3</span><br><span class="line"></span><br><span class="line">console.log(animals.lastIndexOf(&apos;Penguin&apos;, 1));</span><br><span class="line">// expected output: -1</span><br></pre></td></tr></table></figure><h5 id="20-arrayprototypemap"><a class="markdownIt-Anchor" href="#20-arrayprototypemap"></a> 20. Array.prototype.map()</h5><p><strong>map()</strong> 方法创建一个新数组，其结果是在调用数组中的每个元素上调用提供的函数</p><blockquote><p>var new_array = arr.map(function callback(currentValue[, index[, array]]) {<br>// Return element for new_array<br>}[, thisArg])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [1, 4, 9, 16];</span><br><span class="line"></span><br><span class="line">// pass a function to map</span><br><span class="line">const map1 = array1.map(x =&gt; x * 2);</span><br><span class="line"></span><br><span class="line">console.log(map1);</span><br><span class="line">// expected output: Array [2, 8, 18, 32]</span><br></pre></td></tr></table></figure><h5 id="21-arrayprototypepop"><a class="markdownIt-Anchor" href="#21-arrayprototypepop"></a> 21. Array.prototype.pop()</h5><p><strong>pop()</strong> 方法从数组中删除最后一个元素并返回该元素。 此方法更改数组的长度。</p><blockquote><p>arr.pop()</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var plants = [&apos;broccoli&apos;, &apos;cauliflower&apos;, &apos;cabbage&apos;, &apos;kale&apos;, &apos;tomato&apos;];</span><br><span class="line"></span><br><span class="line">console.log(plants.pop());</span><br><span class="line">// expected output: &quot;tomato&quot;</span><br><span class="line"></span><br><span class="line">console.log(plants);</span><br><span class="line">// expected output: Array [&quot;broccoli&quot;, &quot;cauliflower&quot;, &quot;cabbage&quot;, &quot;kale&quot;]</span><br><span class="line"></span><br><span class="line">plants.pop();</span><br><span class="line"></span><br><span class="line">console.log(plants);</span><br><span class="line">// expected output: Array [&quot;broccoli&quot;, &quot;cauliflower&quot;, &quot;cabbage&quot;]</span><br></pre></td></tr></table></figure><h5 id="22-arrayprototypepush"><a class="markdownIt-Anchor" href="#22-arrayprototypepush"></a> 22. Array.prototype.push()</h5><p><strong>push()</strong> 方法将一个或多个元素添加到数组的末尾，并返回数组的新长度</p><blockquote><p>arr.push(element1[, …[, elementN]])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var animals = [&apos;pigs&apos;, &apos;goats&apos;, &apos;sheep&apos;];</span><br><span class="line"></span><br><span class="line">console.log(animals.push(&apos;cows&apos;));</span><br><span class="line">// expected output: 4</span><br><span class="line"></span><br><span class="line">console.log(animals);</span><br><span class="line">// expected output: Array [&quot;pigs&quot;, &quot;goats&quot;, &quot;sheep&quot;, &quot;cows&quot;]</span><br><span class="line"></span><br><span class="line">animals.push(&apos;chickens&apos;);</span><br><span class="line"></span><br><span class="line">console.log(animals);</span><br><span class="line">// expected output: Array [&quot;pigs&quot;, &quot;goats&quot;, &quot;sheep&quot;, &quot;cows&quot;, &quot;chickens&quot;]</span><br></pre></td></tr></table></figure><h5 id="23-arrayprototypereduce"><a class="markdownIt-Anchor" href="#23-arrayprototypereduce"></a> 23. Array.prototype.reduce()</h5><p><strong>reduce()</strong> 方法在数组的每个元素上执行reducer函数（您提供），从而产生单个输出值</p><blockquote><p>arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">const array1 = [1, 2, 3, 4];</span><br><span class="line">const reducer = (accumulator, currentValue) =&gt; accumulator + currentValue;</span><br><span class="line"></span><br><span class="line">// 1 + 2 + 3 + 4</span><br><span class="line">console.log(array1.reduce(reducer));</span><br><span class="line">// expected output: 10</span><br><span class="line"></span><br><span class="line">// 5 + 1 + 2 + 3 + 4</span><br><span class="line">console.log(array1.reduce(reducer, 5));</span><br><span class="line">// expected output: 15</span><br></pre></td></tr></table></figure><h5 id="24-arrayprototypereduceright"><a class="markdownIt-Anchor" href="#24-arrayprototypereduceright"></a> 24. Array.prototype.reduceRight()</h5><p><strong>reduceRight()</strong> 方法对累加器和数组的每个值（从右到左）应用函数以将其减少为单个值</p><blockquote><p>arr.reduceRight(callback(accumulator, currentValue[, index[, array]])[, initialValue])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">const array1 = [[0, 1], [2, 3], [4, 5]].reduceRight(</span><br><span class="line">  (accumulator, currentValue) =&gt; accumulator.concat(currentValue)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">console.log(array1);</span><br><span class="line">// expected output: Array [4, 5, 2, 3, 0, 1]</span><br></pre></td></tr></table></figure><h5 id="25-arrayprototypereverse"><a class="markdownIt-Anchor" href="#25-arrayprototypereverse"></a> 25. Array.prototype.reverse()</h5><p><strong>reverse()</strong> 方法将数组反转到位。 第一个数组元素成为最后一个，最后一个数组元素成为第一个</p><blockquote><p>a.reverse()</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [&apos;one&apos;, &apos;two&apos;, &apos;three&apos;];</span><br><span class="line">console.log(&apos;array1: &apos;, array1);</span><br><span class="line">// expected output: Array [&apos;one&apos;, &apos;two&apos;, &apos;three&apos;]</span><br><span class="line"></span><br><span class="line">var reversed = array1.reverse(); </span><br><span class="line">console.log(&apos;reversed: &apos;, reversed);</span><br><span class="line">// expected output: Array [&apos;three&apos;, &apos;two&apos;, &apos;one&apos;]</span><br><span class="line"></span><br><span class="line">/* Careful: reverse is destructive. It also changes</span><br><span class="line">the original array */ </span><br><span class="line">console.log(&apos;array1: &apos;, array1);</span><br><span class="line">// expected output: Array [&apos;three&apos;, &apos;two&apos;, &apos;one&apos;]</span><br></pre></td></tr></table></figure><h5 id="26-arrayprototypeshift"><a class="markdownIt-Anchor" href="#26-arrayprototypeshift"></a> 26. Array.prototype.shift()</h5><p><strong>shift()</strong> 方法从数组中删除第一个元素并返回已删除的元素。 此方法更改数组的长度</p><blockquote><p>arr.shift()</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [1, 2, 3];</span><br><span class="line"></span><br><span class="line">var firstElement = array1.shift();</span><br><span class="line"></span><br><span class="line">console.log(array1);</span><br><span class="line">// expected output: Array [2, 3]</span><br><span class="line"></span><br><span class="line">console.log(firstElement);</span><br><span class="line">// expected output: 1</span><br></pre></td></tr></table></figure><h5 id="27-arrayprototypeslice"><a class="markdownIt-Anchor" href="#27-arrayprototypeslice"></a> 27. Array.prototype.slice()</h5><p><strong>slice()</strong> 方法将数组的一部分的浅表副本返回到从头到尾选择的新数组对象（不包括结尾），其中begin和end表示该数组中项的索引。 原始数组不会被修改</p><blockquote><p>arr.slice([begin[, end]])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var animals = [&apos;ant&apos;, &apos;bison&apos;, &apos;camel&apos;, &apos;duck&apos;, &apos;elephant&apos;];</span><br><span class="line"></span><br><span class="line">console.log(animals.slice(2));</span><br><span class="line">// expected output: Array [&quot;camel&quot;, &quot;duck&quot;, &quot;elephant&quot;]</span><br><span class="line"></span><br><span class="line">console.log(animals.slice(2, 4));</span><br><span class="line">// expected output: Array [&quot;camel&quot;, &quot;duck&quot;]</span><br><span class="line"></span><br><span class="line">console.log(animals.slice(1, 5));</span><br><span class="line">// expected output: Array [&quot;bison&quot;, &quot;camel&quot;, &quot;duck&quot;, &quot;elephant&quot;]</span><br></pre></td></tr></table></figure><h5 id="28-arrayprototypesome"><a class="markdownIt-Anchor" href="#28-arrayprototypesome"></a> 28. # Array.prototype.some()</h5><p><strong>some()</strong> 方法测试数组中是否至少有一个元素通过了由提供的函数实现的测试。 它返回一个布尔值</p><blockquote><p>arr.some(callback(element[, index[, array]])[, thisArg])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array = [1, 2, 3, 4, 5];</span><br><span class="line"></span><br><span class="line">var even = function(element) &#123;</span><br><span class="line">  // checks whether an element is even</span><br><span class="line">  return element % 2 === 0;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">console.log(array.some(even));</span><br><span class="line">// expected output: true</span><br></pre></td></tr></table></figure><h5 id="29-arrayprototypesort"><a class="markdownIt-Anchor" href="#29-arrayprototypesort"></a> 29. Array.prototype.sort()</h5><p><strong>sort()</strong> 方法对数组中的元素进行排序并返回已排序的数组。 默认排序顺序是在将元素转换为字符串，然后比较它们的UTF-16代码单元值序列时构建的。</p><p>由于取决于实现，因此无法保证排序的时间和空间复杂性。</p><blockquote><p>arr.sort([compareFunction])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var months = [&apos;March&apos;, &apos;Jan&apos;, &apos;Feb&apos;, &apos;Dec&apos;];</span><br><span class="line">months.sort();</span><br><span class="line">console.log(months);</span><br><span class="line">// expected output: Array [&quot;Dec&quot;, &quot;Feb&quot;, &quot;Jan&quot;, &quot;March&quot;]</span><br><span class="line"></span><br><span class="line">var array1 = [1, 30, 4, 21, 100000];</span><br><span class="line">array1.sort();</span><br><span class="line">console.log(array1);</span><br><span class="line">// expected output: Array [1, 100000, 21, 30, 4]</span><br></pre></td></tr></table></figure><h5 id="30-arrayprototypesplice"><a class="markdownIt-Anchor" href="#30-arrayprototypesplice"></a> 30. Array.prototype.splice()</h5><p><strong>splice()</strong> 方法通过删除或替换现有元素和/或在适当位置添加新元素来更改数组的内容</p><blockquote><p>var arrDeletedItems = array.splice(start[, deleteCount[, item1[, item2[, …]]]])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var months = [&apos;Jan&apos;, &apos;March&apos;, &apos;April&apos;, &apos;June&apos;];</span><br><span class="line">months.splice(1, 0, &apos;Feb&apos;);</span><br><span class="line">// inserts at index 1</span><br><span class="line">console.log(months);</span><br><span class="line">// expected output: Array [&apos;Jan&apos;, &apos;Feb&apos;, &apos;March&apos;, &apos;April&apos;, &apos;June&apos;]</span><br><span class="line"></span><br><span class="line">months.splice(4, 1, &apos;May&apos;);</span><br><span class="line">// replaces 1 element at index 4</span><br><span class="line">console.log(months);</span><br><span class="line">// expected output: Array [&apos;Jan&apos;, &apos;Feb&apos;, &apos;March&apos;, &apos;April&apos;, &apos;May&apos;]</span><br></pre></td></tr></table></figure><h5 id="31-arrayprototypeunshift"><a class="markdownIt-Anchor" href="#31-arrayprototypeunshift"></a> 31. Array.prototype.unshift()</h5><p><strong>unshift()</strong> 方法将一个或多个元素添加到数组的开头并返回数组的新长度</p><blockquote><p>arr.unshift(element1[, …[, elementN]])</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [1, 2, 3];</span><br><span class="line"></span><br><span class="line">console.log(array1.unshift(4, 5));</span><br><span class="line">// expected output: 5</span><br><span class="line"></span><br><span class="line">console.log(array1);</span><br><span class="line">// expected output: Array [4, 5, 1, 2, 3]</span><br></pre></td></tr></table></figure><h5 id="32-arrayprototypevalues"><a class="markdownIt-Anchor" href="#32-arrayprototypevalues"></a> 32. Array.prototype.values()</h5><p><strong>values()</strong> 方法返回一个新的Array Iterator对象，该对象包含数组中每个索引的值</p><blockquote><p>arr.values()</p></blockquote><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></pre></td><td class="code"><pre><span class="line">const array1 = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;];</span><br><span class="line">const iterator = array1.values();</span><br><span class="line"></span><br><span class="line">for (const value of iterator) &#123;</span><br><span class="line">  console.log(value); // expected output: &quot;a&quot; &quot;b&quot; &quot;c&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="33-arrayprototypetolocalestring"><a class="markdownIt-Anchor" href="#33-arrayprototypetolocalestring"></a> 33. Array.prototype.toLocaleString()</h5><p><strong>toLocaleString()</strong> 方法返回表示数组元素的字符串。 使用toLocaleString方法将元素转换为字符串，并且这些字符串由特定于语言环境的字符串（例如逗号“，”）分隔</p><blockquote><p>arr.toLocaleString([locales[, options]]);</p></blockquote><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></pre></td><td class="code"><pre><span class="line">var array1 = [1, &apos;a&apos;, new Date(&apos;21 Dec 1997 14:12:00 UTC&apos;)];</span><br><span class="line">var localeString = array1.toLocaleString(&apos;en&apos;, &#123;timeZone: &quot;UTC&quot;&#125;);</span><br><span class="line"></span><br><span class="line">console.log(localeString);</span><br><span class="line">// expected output: &quot;1,a,12/21/1997, 2:12:00 PM&quot;,</span><br><span class="line">// This assumes &quot;en&quot; locale and UTC timezone - your results may vary</span><br></pre></td></tr></table></figure><h5 id="34-arrayprototypetostring"><a class="markdownIt-Anchor" href="#34-arrayprototypetostring"></a> 34. Array.prototype.toString()</h5><p><strong>toString()</strong> 方法返回表示指定数组及其元素的字符串</p><blockquote><p>arr.toString()</p></blockquote><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">var array1 = [1, 2, &apos;a&apos;, &apos;1a&apos;];</span><br><span class="line"></span><br><span class="line">console.log(array1.toString());</span><br><span class="line">// expected output: &quot;1,2,a,1a&quot;</span><br></pre></td></tr></table></figure><h5 id="35-arrayprototypeiterator"><a class="markdownIt-Anchor" href="#35-arrayprototypeiterator"></a> 35. Array.prototype<a href>@@iterator</a></h5><p><strong>@@iterator</strong> 属性的初始值与values（）属性的初始值是相同的函数对象。</p><blockquote><p>arr<a href>Symbol.iterator</a></p></blockquote><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></pre></td><td class="code"><pre><span class="line">var arr = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;, &apos;d&apos;, &apos;e&apos;];</span><br><span class="line">var eArr = arr[Symbol.iterator]();</span><br><span class="line">// your browser must support for..of loop</span><br><span class="line">// and let-scoped variables in for loops</span><br><span class="line">// const and var could also be used</span><br><span class="line">for (let letter of eArr) &#123;</span><br><span class="line">  console.log(letter);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      这周呢，彻底学习一下 Array 的所有方法。学习地址 MDN，里面还有各个函数实现的源码！数组作为 JavaScript 的一种特殊的对象类型，与 Number，Boolean，Null，String，Undefined，Symbol  七大数据类型有所不一样。了解 Array 的所有方法，能帮助我们最快找到适合自己的函数
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="Array" scheme="https://wzes.github.io/tags/Array/"/>
    
  </entry>
  
  <entry>
    <title>React Redux v0.2.1 源码学习</title>
    <link href="https://wzes.github.io/2019/08/25/JavaScript/Redux%20V0.2.1/"/>
    <id>https://wzes.github.io/2019/08/25/JavaScript/Redux V0.2.1/</id>
    <published>2019-08-25T11:30:16.000Z</published>
    <updated>2019-09-02T12:16:07.731Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>这周突然想学习一下状态管理的写法。看看业界是怎么实现的，之前使用过 redux，那就先从 redux 下手吧，但是，一上来就看最新版本的代码，不太适合新手学习，一方面最新版本已经发展n多年了，功能已经非常完善（代码多难懂），另一方面直接看最新的不了解这个工具是怎么设计出来的。于是就打算学习最早的发布版本 v0.2.1</p><p>先来说下我认识的一般的状态管理的基本路子：</p><blockquote><p>全局只存在 <strong>唯一state</strong>，而前端不直接改变 state，而是通过 <strong>action</strong> 去改变 state</p></blockquote><h3 id="helloworld"><a class="markdownIt-Anchor" href="#helloworld"></a> HelloWorld</h3><p>一个计数器的栗子，目录结构如下：</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></pre></td><td class="code"><pre><span class="line">counter</span><br><span class="line">├── App.js</span><br><span class="line">├── Counter.js</span><br><span class="line">├── actions</span><br><span class="line">│   ├── CounterActions.js</span><br><span class="line">│   └── index.js</span><br><span class="line">├── constants</span><br><span class="line">│   └── ActionTypes.js</span><br><span class="line">├── dispatcher.js</span><br><span class="line">└── stores</span><br><span class="line">    ├── CounterStore.js</span><br><span class="line">    └── index.js</span><br></pre></td></tr></table></figure><h4 id="actions"><a class="markdownIt-Anchor" href="#actions"></a> actions</h4><p>函数，返回一个带 type 的对象，或者返回一个函数</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><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">import &#123;</span><br><span class="line">  INCREMENT_COUNTER,</span><br><span class="line">  DECREMENT_COUNTER</span><br><span class="line">&#125; from &apos;../constants/ActionTypes&apos;;</span><br><span class="line"></span><br><span class="line">export function increment() &#123;</span><br><span class="line">  return &#123;</span><br><span class="line">    type: INCREMENT_COUNTER</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">export function incrementAsync() &#123;</span><br><span class="line">  return dispatch =&gt; &#123;</span><br><span class="line">    setTimeout(() =&gt; &#123;</span><br><span class="line">      dispatch(increment());</span><br><span class="line">    &#125;, 1000);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">export function decrement() &#123;</span><br><span class="line">  return &#123;</span><br><span class="line">    type: DECREMENT_COUNTER</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="store"><a class="markdownIt-Anchor" href="#store"></a> store</h4><p>返回一个函数，参数 state 和 action，当 state 为空时返回初始值，表示初始化。根据 action 的 type 值，进行相应的做法，返回一个新的 state。</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">import &#123;</span><br><span class="line">  INCREMENT_COUNTER,</span><br><span class="line">  DECREMENT_COUNTER</span><br><span class="line">&#125; from &apos;../constants/ActionTypes&apos;;</span><br><span class="line"></span><br><span class="line">const initialState = &#123; counter: 0 &#125;;</span><br><span class="line"></span><br><span class="line">function incremenent(&#123; counter &#125;) &#123;</span><br><span class="line">  return &#123; counter: counter + 1 &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">function decremenent(&#123; counter &#125;) &#123;</span><br><span class="line">  return &#123; counter: counter - 1 &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">export default function CounterStore(state, action) &#123;</span><br><span class="line">  if (!state) &#123;</span><br><span class="line">    return initialState;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  switch (action.type) &#123;</span><br><span class="line">  case INCREMENT_COUNTER:</span><br><span class="line">    return incremenent(state, action);</span><br><span class="line">  case DECREMENT_COUNTER:</span><br><span class="line">    return decremenent(state, action);</span><br><span class="line">  default:</span><br><span class="line">    return state;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>入口 App.js 你会发现 @provides(dispatcher) 这个奇怪的东西，在 React 里面还经常出现，装饰器。</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></pre></td><td class="code"><pre><span class="line">import React, &#123; Component &#125; from &apos;react&apos;;</span><br><span class="line">import Counter from &apos;./Counter&apos;;</span><br><span class="line">import &#123; provides &#125; from &apos;redux&apos;;</span><br><span class="line">import dispatcher from &apos;./dispatcher&apos;;</span><br><span class="line"></span><br><span class="line">@provides(dispatcher)</span><br><span class="line">export default class App extends Component &#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    return (</span><br><span class="line">      &lt;Counter /&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Couter.js，同样，也出现 performs（方法），observes（观察者）等关键字。使用 state 直接使用 this.props 解构赋值即可。</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">import React from &apos;react&apos;;</span><br><span class="line">import &#123; performs, observes &#125; from &apos;redux&apos;;</span><br><span class="line"></span><br><span class="line">@performs(&apos;increment&apos;, &apos;decrement&apos;)</span><br><span class="line">@observes(&apos;CounterStore&apos;)</span><br><span class="line">export default class Counter &#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    const &#123; increment, decrement &#125; = this.props;</span><br><span class="line">    return (</span><br><span class="line">      &lt;p&gt;</span><br><span class="line">        Clicked: &#123;this.props.counter&#125; times</span><br><span class="line">        &#123;&apos; &apos;&#125;</span><br><span class="line">        &lt;button onClick=&#123;() =&gt; increment()&#125;&gt;+&lt;/button&gt;</span><br><span class="line">        &#123;&apos; &apos;&#125;</span><br><span class="line">        &lt;button onClick=&#123;() =&gt; decrement()&#125;&gt;-&lt;/button&gt;</span><br><span class="line">      &lt;/p&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这些关键字是早起 Redux 状态管理的关键，现在的版本应该已经不使用这种方式了。</p><h3 id="解析"><a class="markdownIt-Anchor" href="#解析"></a> 解析</h3><h4 id="dispatcher"><a class="markdownIt-Anchor" href="#dispatcher"></a> dispatcher</h4><p>通过 provides 将 dispatcher 注入到 App 中，其中，dispatcher 是通过 createDispatcher 创建，并调用了 dispatcher.receive(stores, actions) 进行绑定。</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></pre></td><td class="code"><pre><span class="line">import * as stores from &apos;./stores/index&apos;;</span><br><span class="line">import * as actions from &apos;./actions/index&apos;;</span><br><span class="line">import &#123; createDispatcher &#125; from &apos;redux&apos;;</span><br><span class="line"></span><br><span class="line">const dispatcher =</span><br><span class="line">  module.hot &amp;&amp; module.hot.data &amp;&amp; module.hot.data.dispatcher ||</span><br><span class="line">  createDispatcher();</span><br><span class="line"></span><br><span class="line">dispatcher.receive(stores, actions);</span><br><span class="line"></span><br><span class="line">module.hot.dispose(data =&gt; &#123;</span><br><span class="line">  data.dispatcher = dispatcher;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">export default dispatcher;</span><br></pre></td></tr></table></figure><p>receive 方法，actionCreator 将 action 进行封装，</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></pre></td><td class="code"><pre><span class="line">// Provide a way to receive new stores and actions</span><br><span class="line">  function receive(nextStores, nextActionCreators) &#123;</span><br><span class="line">    stores = nextStores;</span><br><span class="line">    actionCreators = mapValues(nextActionCreators, wrapActionCreator);</span><br><span class="line"></span><br><span class="line">    // Merge the observers</span><br><span class="line">    observers = mapValues(stores,</span><br><span class="line">      (store, key) =&gt; observers[key] || []</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    // Dispatch to initialize stores</span><br><span class="line">    if (currentTransaction) &#123;</span><br><span class="line">      updateState(committedState);</span><br><span class="line">      currentTransaction.forEach(dispatch);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      dispatch(BOOTSTRAP_STORE);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>action 进行转化返回一个 dispatchAction 函数，如果 action 为函数，则先执行函数，把 dispatchInTransaction 作为参数传入，这样可以在 action 内部使用该函数了，否则使用 dispatchInTransaction 函数调用。</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></pre></td><td class="code"><pre><span class="line">// Bind action creator to the dispatcher</span><br><span class="line"> function wrapActionCreator(actionCreator) &#123;</span><br><span class="line">   return function dispatchAction(...args) &#123;</span><br><span class="line">     const action = actionCreator(...args);</span><br><span class="line">     if (typeof action === &apos;function&apos;) &#123;</span><br><span class="line">       // Async action creator</span><br><span class="line">       action(dispatchInTransaction);</span><br><span class="line">     &#125; else &#123;</span><br><span class="line">       // Sync action creator</span><br><span class="line">       dispatchInTransaction(action);</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>dispatchInTransaction ，执行 dispatch ，计算 nextState，执行 updateState 更新。</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></pre></td><td class="code"><pre><span class="line">  // Dispatch in the context of current transaction</span><br><span class="line">  function dispatchInTransaction(action) &#123;</span><br><span class="line">    if (currentTransaction) &#123;</span><br><span class="line">      currentTransaction.push(action);</span><br><span class="line">    &#125;</span><br><span class="line">    dispatch(action);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">// Reassign the current state on each dispatch</span><br><span class="line">  function dispatch(action) &#123;</span><br><span class="line">    if (typeof action.type !== &apos;string&apos;) &#123;</span><br><span class="line">      throw new Error(&apos;Action type must be a string.&apos;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    const nextState = computeNextState(currentState, action);</span><br><span class="line">    updateState(nextState);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>获取 store，也就是 CounterStore，把参数传入，获取新的 state</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></pre></td><td class="code"><pre><span class="line">// To compute the next state, combine the next states of every store</span><br><span class="line">function computeNextState(state, action) &#123;</span><br><span class="line">  return mapValues(stores,</span><br><span class="line">    (store, key) =&gt; store(state[key], action)</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>updateState 实现，计算变化的 changedKeys，执行  emitChange 进行更新。</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></pre></td><td class="code"><pre><span class="line">// Update state and emit change if needed</span><br><span class="line">function updateState(nextState) &#123;</span><br><span class="line">  // Swap the state</span><br><span class="line">  const previousState = currentState;</span><br><span class="line">  currentState = nextState;</span><br><span class="line"></span><br><span class="line">  // Notify the observers</span><br><span class="line">  const changedKeys = Object.keys(currentState).filter(key =&gt;</span><br><span class="line">    currentState[key] !== previousState[key]</span><br><span class="line">  );</span><br><span class="line">  emitChange(changedKeys);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>emitChange，获取需要通知的 observers，调用通知函数。</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">// Notify observers about the changed stores</span><br><span class="line">  function emitChange(changedKeys) &#123;</span><br><span class="line">    if (!changedKeys.length) &#123;</span><br><span class="line">      return;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // Gather the affected observers</span><br><span class="line">    const notifyObservers = [];</span><br><span class="line">    changedKeys.forEach(key =&gt; &#123;</span><br><span class="line">      observers[key].forEach(o =&gt; &#123;</span><br><span class="line">        if (notifyObservers.indexOf(o) === -1) &#123;</span><br><span class="line">          notifyObservers.push(o);</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">    // Emit change</span><br><span class="line">    notifyObservers.forEach(o =&gt; o());</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>这里可能有点疑问，obersevers 是什么，从哪来？往下看～</p><h4 id="observesjs"><a class="markdownIt-Anchor" href="#observesjs"></a> observes.js</h4><p>将 组件进行装饰，构造函数中有一个</p><blockquote><p>this.unobserve = this.context.observeStores(storeKeys, this.handleChange);<br>context 就是 dispatcher，</p></blockquote><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><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></pre></td><td class="code"><pre><span class="line">import React, &#123; Component, PropTypes &#125; from &apos;react&apos;;</span><br><span class="line">import pick from &apos;lodash/object/pick&apos;;</span><br><span class="line">import identity from &apos;lodash/utility/identity&apos;;</span><br><span class="line"></span><br><span class="line">const contextTypes = &#123;</span><br><span class="line">  observeStores: PropTypes.func.isRequired</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">export default function connect(...storeKeys) &#123;</span><br><span class="line">  let mapState = identity;</span><br><span class="line"></span><br><span class="line">  // Last argument may be a custom mapState function</span><br><span class="line">  const lastIndex = storeKeys.length - 1;</span><br><span class="line">  if (typeof storeKeys[lastIndex] === &apos;function&apos;) &#123;</span><br><span class="line">    [mapState] = storeKeys.splice(lastIndex, 1);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return function (DecoratedComponent) &#123;</span><br><span class="line">    const wrappedDisplayName =</span><br><span class="line">      DecoratedComponent.displayName ||</span><br><span class="line">      DecoratedComponent.name ||</span><br><span class="line">      &apos;Component&apos;;</span><br><span class="line"></span><br><span class="line">    return class extends Component &#123;</span><br><span class="line">      static displayName = `ReduxObserves($&#123;wrappedDisplayName&#125;)`;</span><br><span class="line">      static contextTypes = contextTypes;</span><br><span class="line"></span><br><span class="line">      constructor(props, context) &#123;</span><br><span class="line">        super(props, context);</span><br><span class="line">        this.handleChange = this.handleChange.bind(this);</span><br><span class="line">        this.unobserve = this.context.observeStores(storeKeys, this.handleChange);</span><br><span class="line">      &#125;</span><br><span class="line">      ....</span><br><span class="line"></span><br><span class="line">      componentWillUnmount() &#123;</span><br><span class="line">        this.unobserve();</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      render() &#123;</span><br><span class="line">        return (</span><br><span class="line">          &lt;DecoratedComponent &#123;...this.props&#125;</span><br><span class="line">                              &#123;...this.state&#125; /&gt;</span><br><span class="line">        );</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>dispatcher observeStores 方法，将需要监听的组件传入，以及 onChange 函数，作为回调使用。最后返回一个函数，移除监听，这个也太妙了吧。</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">// Provide subscription and unsubscription</span><br><span class="line">  function observeStores(observedKeys, onChange) &#123;</span><br><span class="line">    // Emit the state update</span><br><span class="line">    function handleChange() &#123;</span><br><span class="line">      onChange(currentState);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // Synchronously emit the initial value</span><br><span class="line">    handleChange();</span><br><span class="line"></span><br><span class="line">    // Register the observer for each relevant key</span><br><span class="line">    observedKeys.forEach(key =&gt;</span><br><span class="line">      observers[key].push(handleChange)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    // Let it unregister when the time comes</span><br><span class="line">    return () =&gt; &#123;</span><br><span class="line">      observedKeys.forEach(key =&gt; &#123;</span><br><span class="line">        const index = observers[key].indexOf(handleChange);</span><br><span class="line">        observers[key].splice(index, 1);</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>当计算好 nextState 后，就会调用 observe 的 onChange 方法， onChange 方法也就是 装饰器里面的方法。最后调用自身的 updateState，使用 setState 进行组件更新。而这些 state 作为 props 传入了我们自己的组件，也就可以通过 this.props  拿到。完美～～～</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">handleChange(stateFromStores) &#123;</span><br><span class="line">  this.currentStateFromStores = pick(stateFromStores, storeKeys);</span><br><span class="line">  this.updateState(stateFromStores, this.props);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">componentWillReceiveProps(nextProps) &#123;</span><br><span class="line">  this.updateState(this.currentStateFromStores, nextProps);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">updateState(stateFromStores, props) &#123;</span><br><span class="line">  if (storeKeys.length === 1) &#123;</span><br><span class="line">    // Just give it the particular store state for convenience</span><br><span class="line">    stateFromStores = stateFromStores[storeKeys[0]];</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  const state = mapState(stateFromStores, props);</span><br><span class="line">  if (this.state) &#123;</span><br><span class="line">    this.setState(state);</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    this.state = state;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="performs-组件"><a class="markdownIt-Anchor" href="#performs-组件"></a> performs 组件</h4><p>action 绑定到组件，可以通过 this.props ，通过 this.context.getActions() 拿到 actions</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><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">import React, &#123; Component, PropTypes &#125; from &apos;react&apos;;</span><br><span class="line">import pick from &apos;lodash/object/pick&apos;;</span><br><span class="line">import identity from &apos;lodash/utility/identity&apos;;</span><br><span class="line"></span><br><span class="line">const contextTypes = &#123;</span><br><span class="line">  getActions: PropTypes.func.isRequired</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">export default function performs(...actionKeys) &#123;</span><br><span class="line">  let mapActions = identity;</span><br><span class="line"></span><br><span class="line">  // Last argument may be a custom mapState function</span><br><span class="line">  const lastIndex = actionKeys.length - 1;</span><br><span class="line">  if (typeof actionKeys[lastIndex] === &apos;function&apos;) &#123;</span><br><span class="line">    [mapActions] = actionKeys.splice(lastIndex, 1);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return function (DecoratedComponent) &#123;</span><br><span class="line">    const wrappedDisplayName =</span><br><span class="line">      DecoratedComponent.displayName ||</span><br><span class="line">      DecoratedComponent.name ||</span><br><span class="line">      &apos;Component&apos;;</span><br><span class="line"></span><br><span class="line">    return class extends Component &#123;</span><br><span class="line">      static displayName = `ReduxPerforms($&#123;wrappedDisplayName&#125;)`;</span><br><span class="line">      static contextTypes = contextTypes;</span><br><span class="line"></span><br><span class="line">      constructor(props, context) &#123;</span><br><span class="line">        super(props, context);</span><br><span class="line">        this.updateActions(props);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      componentWillReceiveProps(nextProps) &#123;</span><br><span class="line">        this.updateActions(nextProps);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      updateActions(props) &#123;</span><br><span class="line">        this.actions = mapActions(</span><br><span class="line">          pick(this.context.getActions(), actionKeys),</span><br><span class="line">          props</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      render() &#123;</span><br><span class="line">        return (</span><br><span class="line">          &lt;DecoratedComponent &#123;...this.props&#125;</span><br><span class="line">                              &#123;...this.actions&#125; /&gt;</span><br><span class="line">        );</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>到这里就差不多了～<br>额外收获</p><h4 id="lodash"><a class="markdownIt-Anchor" href="#lodash"></a> Lodash</h4><ul><li><p>pick</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">var object = &#123; &apos;user&apos;: &apos;fred&apos;, &apos;age&apos;: 40 &#125;;</span><br><span class="line">_.pick(object, &apos;user&apos;);</span><br><span class="line">// =&gt; &#123; &apos;user&apos;: &apos;fred&apos; &#125;</span><br><span class="line">_.pick(object, _.isString);</span><br><span class="line">// =&gt; &#123; &apos;user&apos;: &apos;fred&apos; &#125;</span><br></pre></td></tr></table></figure></li><li><p>identity</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">function identity(value) &#123;</span><br><span class="line">  return value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>mapValues</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">_.mapValues(&#123; &apos;a&apos;: 1, &apos;b&apos;: 2 &#125;, function(n) &#123;</span><br><span class="line"> return n * 3;</span><br><span class="line">&#125;);</span><br><span class="line">// =&gt; &#123; &apos;a&apos;: 3, &apos;b&apos;: 6 &#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="最后"><a class="markdownIt-Anchor" href="#最后"></a> 最后</h3><p>麻雀虽小，却能看透精髓～</p>]]></content>
    
    <summary type="html">
    
      这周突然想学习一下状态管理的写法。看看业界是怎么实现的，之前使用过 redux，那就先从 redux 下手吧，但是，一上来就看最新版本的代码，不太适合新手学习，一方面最新版本已经发展n多年了，功能已经非常完善（代码多难懂），另一方面直接看最新的不了解这个工具是怎么设计出来的
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="React" scheme="https://wzes.github.io/tags/React/"/>
    
      <category term="Redux" scheme="https://wzes.github.io/tags/Redux/"/>
    
  </entry>
  
  <entry>
    <title>Node KoaJs 源码解析</title>
    <link href="https://wzes.github.io/2019/08/18/JavaScript/KoaJs/"/>
    <id>https://wzes.github.io/2019/08/18/JavaScript/KoaJs/</id>
    <published>2019-08-18T10:56:00.000Z</published>
    <updated>2019-09-02T12:15:48.563Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>又是一周过去了，常规学习不能断！但是选择什么主题呢？一时间不知道选什么好，于是又想起简单的 <strong>koajs</strong> 非常愉快的就选择他了 <a href="https://koajs.com/" target="_blank" rel="noopener">https://koajs.com/</a>，了解一下？</p><p>他是个什么东西呢？</p><blockquote><p>Koa 是一个新的 web 框架，由 Express 幕后的原班人马打造， 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数，Koa 帮你丢弃回调函数，并有力地增强错误处理。 Koa 并没有捆绑任何中间件， 而是提供了一套优雅的方法，帮助您快速而愉快地编写服务端应用程序。</p></blockquote><h3 id="hello-world"><a class="markdownIt-Anchor" href="#hello-world"></a> hello world</h3><p>首先新建一个 node 项目，其实很简单，只需要一个 package.json 文件，</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;koa-hello&quot;,</span><br><span class="line">  &quot;version&quot;: &quot;1.0.0&quot;,</span><br><span class="line">  &quot;description&quot;: &quot;&quot;,</span><br><span class="line">  &quot;main&quot;: &quot;index.js&quot;,</span><br><span class="line">  &quot;scripts&quot;: &#123;</span><br><span class="line">    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,</span><br><span class="line">    &quot;start&quot;: &quot;node src/index.js&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;keywords&quot;: [],</span><br><span class="line">  &quot;author&quot;: &quot;&quot;,</span><br><span class="line">  &quot;license&quot;: &quot;ISC&quot;,</span><br><span class="line">  &quot;dependencies&quot;: &#123;</span><br><span class="line">    &quot;koa&quot;: &quot;^2.7.0&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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">npm i koa</span><br></pre></td></tr></table></figure><p>代码 index.js 文件，新建一个 koa 实例，使用 app.use 写一个 async 方法，设置 ctx.body 的值就可以了。最后使用 app.listen 启动。</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></pre></td><td class="code"><pre><span class="line">const Koa = require(&apos;koa&apos;);</span><br><span class="line">const app = new Koa();</span><br><span class="line"></span><br><span class="line">app.use(async ctx =&gt; &#123;</span><br><span class="line">    ctx.body = &apos;Hello World&apos;;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.listen(3000);</span><br></pre></td></tr></table></figure><p>这样的话，一个 web 服务器就搭建好了，访问 <a href="http://localhost:3000/" target="_blank" rel="noopener">http://localhost:3000/</a> 就会得到 hello world 返回结果了。你可以尝试更改字段从而得到不同的返回结果。</p><h3 id="源码解析"><a class="markdownIt-Anchor" href="#源码解析"></a> 源码解析</h3><p>koa 的源码只有四个文件，不包含其他引用的话</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></pre></td><td class="code"><pre><span class="line"> .</span><br><span class="line">├── History.md</span><br><span class="line">├── LICENSE</span><br><span class="line">├── Readme.md</span><br><span class="line">├── lib</span><br><span class="line">│   ├── application.js</span><br><span class="line">│   ├── context.js</span><br><span class="line">│   ├── request.js</span><br><span class="line">│   └── response.js</span><br><span class="line">└── package.json</span><br></pre></td></tr></table></figure><p>主入口可以在 package.json 的 main 中得到，是 application.js，暂时先知道 middleware 是中间接，通常一个请求过来就会依次执行中间件的方法。</p><h4 id="构造函数"><a class="markdownIt-Anchor" href="#构造函数"></a> 构造函数</h4><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><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">module.exports = class Application extends Emitter &#123;</span><br><span class="line">  /**</span><br><span class="line">   * Initialize a new `Application`.</span><br><span class="line">   *</span><br><span class="line">   * @api public</span><br><span class="line">   */</span><br><span class="line"></span><br><span class="line">  constructor() &#123;</span><br><span class="line">    super();</span><br><span class="line"></span><br><span class="line">    this.proxy = false;</span><br><span class="line">    this.middleware = [];</span><br><span class="line">    this.subdomainOffset = 2;</span><br><span class="line">    this.env = process.env.NODE_ENV || &apos;development&apos;;</span><br><span class="line">    this.context = Object.create(context);</span><br><span class="line">    this.request = Object.create(request);</span><br><span class="line">    this.response = Object.create(response);</span><br><span class="line">    if (util.inspect.custom) &#123;</span><br><span class="line">      this[util.inspect.custom] = this.inspect;</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>app.use 其实就是添加一个中间件，我们通常使用 async 的函数，generator 被抛弃了！</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></pre></td><td class="code"><pre><span class="line">use(fn) &#123;</span><br><span class="line">  if (typeof fn !== &apos;function&apos;) throw new TypeError(&apos;middleware must be a function!&apos;);</span><br><span class="line">  if (isGeneratorFunction(fn)) &#123;</span><br><span class="line">    deprecate(&apos;Support for generators will be removed in v3. &apos; +</span><br><span class="line">              &apos;See the documentation for examples of how to convert old middleware &apos; +</span><br><span class="line">              &apos;https://github.com/koajs/koa/blob/master/docs/migration.md&apos;);</span><br><span class="line">    fn = convert(fn);</span><br><span class="line">  &#125;</span><br><span class="line">  debug(&apos;use %s&apos;, fn._name || fn.name || &apos;-&apos;);</span><br><span class="line">  this.middleware.push(fn);</span><br><span class="line">  return this;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>app.listen 创建一个服务器，监听 3000 端口，http.createServer 是 node 的服务器。</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">listen(...args) &#123;</span><br><span class="line">  debug(&apos;listen&apos;);</span><br><span class="line">  const server = http.createServer(this.callback());</span><br><span class="line">  return server.listen(...args);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>callback 是提供一个函数，所有请求都会走到这个函数里面进行处理。每次请求过来都会调用这个函数，所以，我们可以看到，每次请求都会创建一个 ctx 的对象。<br>compose 的作用就是将所有的中间件整合成一个函数，使用 next 函数继续调用下一个函数。</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></pre></td><td class="code"><pre><span class="line">callback() &#123;</span><br><span class="line">  const fn = compose(this.middleware);</span><br><span class="line"></span><br><span class="line">  if (!this.listenerCount(&apos;error&apos;)) this.on(&apos;error&apos;, this.onerror);</span><br><span class="line"></span><br><span class="line">  const handleRequest = (req, res) =&gt; &#123;</span><br><span class="line">    const ctx = this.createContext(req, res);</span><br><span class="line">    return this.handleRequest(ctx, fn);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  return handleRequest;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>初始化 ctx 对象，这里 this.request 将会把原生的 request 参数进行解析，方便我们进行相关参数获取。</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><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Initialize a new context.</span><br><span class="line"> *</span><br><span class="line"> * @api private</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">createContext(req, res) &#123;</span><br><span class="line">  const context = Object.create(this.context);</span><br><span class="line">  const request = context.request = Object.create(this.request);</span><br><span class="line">  const response = context.response = Object.create(this.response);</span><br><span class="line">  context.app = request.app = response.app = this;</span><br><span class="line">  context.req = request.req = response.req = req;</span><br><span class="line">  context.res = request.res = response.res = res;</span><br><span class="line">  request.ctx = response.ctx = context;</span><br><span class="line">  request.response = response;</span><br><span class="line">  response.request = request;</span><br><span class="line">  context.originalUrl = request.originalUrl = req.url;</span><br><span class="line">  context.state = &#123;&#125;;</span><br><span class="line">  return context;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>比如我们之后就可以使用<br>** ctx.query.key ** 来获取 <a href="http://localhost:3000?key=value%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8" target="_blank" rel="noopener">http://localhost:3000?key=value，为什么可以使用</a> ctx.query 又可以获取参数呢，这个要靠 Object.create 的本事了，它相当于创造了一个对象，继承了原来的对象，而 this.request 有 query 的参数，而最为重要的是 this.context = Object.create(context); context 委托（使用了 Delegator）了这些 request 的相关属性和方法。【第一次体会到 js 委托，以前知识听说不知道是啥】</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></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Request delegation.</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">delegate(proto, &apos;request&apos;)</span><br><span class="line">  .access(&apos;method&apos;)</span><br><span class="line">  .access(&apos;query&apos;)</span><br><span class="line">  .access(&apos;path&apos;)</span><br><span class="line">  .access(&apos;url&apos;)</span><br><span class="line">  ....... // 省略</span><br></pre></td></tr></table></figure><p>handleRequest 请求处理，fnMiddleware 就是所有的中间件，</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></pre></td><td class="code"><pre><span class="line">handleRequest(ctx, fnMiddleware) &#123;</span><br><span class="line">  const res = ctx.res;</span><br><span class="line">  res.statusCode = 404;</span><br><span class="line">  const onerror = err =&gt; ctx.onerror(err);</span><br><span class="line">  const handleResponse = () =&gt; respond(ctx);</span><br><span class="line">  onFinished(res, onerror);</span><br><span class="line">  return fnMiddleware(ctx).then(handleResponse).catch(onerror);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调用完中间件以后，就执行 handleResponse 将数据返回，返回数据也就是将 ctx.body 拿出来，使用 response.end 返回数据，返回时，会对数据进行处理，在最后面可以体会到～</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><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></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Response helper.</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line">function respond(ctx) &#123;</span><br><span class="line">  // allow bypassing koa</span><br><span class="line">  if (false === ctx.respond) return;</span><br><span class="line"></span><br><span class="line">  if (!ctx.writable) return;</span><br><span class="line"></span><br><span class="line">  const res = ctx.res;</span><br><span class="line">  let body = ctx.body;</span><br><span class="line">  const code = ctx.status;</span><br><span class="line"></span><br><span class="line">  // ignore body</span><br><span class="line">  if (statuses.empty[code]) &#123;</span><br><span class="line">    // strip headers</span><br><span class="line">    ctx.body = null;</span><br><span class="line">    return res.end();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (&apos;HEAD&apos; == ctx.method) &#123;</span><br><span class="line">    if (!res.headersSent &amp;&amp; isJSON(body)) &#123;</span><br><span class="line">      ctx.length = Buffer.byteLength(JSON.stringify(body));</span><br><span class="line">    &#125;</span><br><span class="line">    return res.end();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // status body</span><br><span class="line">  if (null == body) &#123;</span><br><span class="line">    if (ctx.req.httpVersionMajor &gt;= 2) &#123;</span><br><span class="line">      body = String(code);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      body = ctx.message || String(code);</span><br><span class="line">    &#125;</span><br><span class="line">    if (!res.headersSent) &#123;</span><br><span class="line">      ctx.type = &apos;text&apos;;</span><br><span class="line">      ctx.length = Buffer.byteLength(body);</span><br><span class="line">    &#125;</span><br><span class="line">    return res.end(body);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // responses</span><br><span class="line">  if (Buffer.isBuffer(body)) return res.end(body);</span><br><span class="line">  if (&apos;string&apos; == typeof body) return res.end(body);</span><br><span class="line">  if (body instanceof Stream) return body.pipe(res);</span><br><span class="line"></span><br><span class="line">  // body: json</span><br><span class="line">  body = JSON.stringify(body);</span><br><span class="line">  if (!res.headersSent) &#123;</span><br><span class="line">    ctx.length = Buffer.byteLength(body);</span><br><span class="line">  &#125;</span><br><span class="line">  res.end(body);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这里，基本的请求已经清楚了～～</p><h4 id="end"><a class="markdownIt-Anchor" href="#end"></a> End</h4><p>再来看一眼最简单的 http server 代码，对比一下，比 koa 代码的 hello world 相比并没有多复杂</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></pre></td><td class="code"><pre><span class="line">var http = require(&apos;http&apos;);</span><br><span class="line">http.createServer(function (req, res) &#123;</span><br><span class="line">  res.writeHead(200, &#123;&apos;Content-Type&apos;: &apos;text/plain&apos;&#125;);</span><br><span class="line">  res.write(&apos;Hello World!&apos;);</span><br><span class="line">  res.end();</span><br><span class="line">&#125;).listen(8080);</span><br></pre></td></tr></table></figure><p>但是，获取参数，使用路由等等插件，koa 生态做了很多，非常方便，快来体验吧！</p>]]></content>
    
    <summary type="html">
    
      又是一周过去了，常规学习不能断！但是选择什么主题呢？一时间不知道选什么好，于是又想起简单的 koajs 非常愉快的就选择他了，了解一下？
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="Node" scheme="https://wzes.github.io/tags/Node/"/>
    
      <category term="Koa" scheme="https://wzes.github.io/tags/Koa/"/>
    
  </entry>
  
  <entry>
    <title>Android 一个滑动组件的原理浅析</title>
    <link href="https://wzes.github.io/2019/08/11/Android/%E4%B8%80%E4%B8%AA%E6%BB%91%E5%8A%A8%E7%BB%84%E4%BB%B6%E7%9A%84%E5%8E%9F%E7%90%86%E6%B5%85%E6%9E%90/"/>
    <id>https://wzes.github.io/2019/08/11/Android/一个滑动组件的原理浅析/</id>
    <published>2019-08-11T06:47:16.000Z</published>
    <updated>2019-09-02T12:19:37.016Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>一个好的滑动不止能够响应手指的移动，而且还能响应 Fling（抛） 事件，响应手指的移动比较简单，手指移动多少距离，布局就移动多少距离；当快速滑动时，手指离开后，不能马上停止滑动，而是应该计算手指的移动速度，产生一个【抛】（Fling ）的动作，让内容继续滚动一段距离。这才是好的滑动组件。那么其中具体的设计究竟是如何呢，今天就来分析一波，借鉴 Android OverScroller 的实现。</p><h3 id="demo"><a class="markdownIt-Anchor" href="#demo"></a> Demo</h3><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></pre></td><td class="code"><pre><span class="line">if (mOverScroller.computeScrollOffset()) &#123;</span><br><span class="line">      // 获取 scrollY</span><br><span class="line">     int y = mOverScroller.getCurrY();</span><br><span class="line">     // 此处省略一堆控制滚动的代码</span><br><span class="line">     .....</span><br><span class="line">     // 更新</span><br><span class="line">    postInvalidate();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解析"><a class="markdownIt-Anchor" href="#解析"></a> 解析</h3><p>总体逻辑先梳理下，首先内容跟随手指移动而【移动】，手指离开时产生一个 【速度】，根据这个速度计算一个，此次滑动的【总时间】和【总距离】，之后可以计算每一个【时刻】对应的【位置】。那其中时刻与距离的对应关系是如何呢？看了一下 Android 的源码，计算关系之复杂，惨绝人寰，欲研又止！但这不重要，重要的是掌握全局。<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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">         * Update the current position and velocity for current time. Returns</span><br><span class="line">         * true if update has been done and false if animation duration has been</span><br><span class="line">         * reached.</span><br><span class="line">         */</span><br><span class="line">        boolean update() &#123;</span><br><span class="line">            final long time = AnimationUtils.currentAnimationTimeMillis();</span><br><span class="line">            final long currentTime = time - mStartTime;</span><br><span class="line"></span><br><span class="line">            if (currentTime == 0) &#123;</span><br><span class="line">                // Skip work but report that we&apos;re still going if we have a nonzero duration.</span><br><span class="line">                return mDuration &gt; 0;</span><br><span class="line">            &#125;</span><br><span class="line">            if (currentTime &gt; mDuration) &#123;</span><br><span class="line">                return false;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            double distance = 0.0;</span><br><span class="line">            switch (mState) &#123;</span><br><span class="line">                case SPLINE: &#123;</span><br><span class="line">                    final float t = (float) currentTime / mSplineDuration;</span><br><span class="line">                    final int index = (int) (NB_SAMPLES * t);</span><br><span class="line">                    float distanceCoef = 1.f;</span><br><span class="line">                    float velocityCoef = 0.f;</span><br><span class="line">                    if (index &lt; NB_SAMPLES) &#123;</span><br><span class="line">                        final float t_inf = (float) index / NB_SAMPLES;</span><br><span class="line">                        final float t_sup = (float) (index + 1) / NB_SAMPLES;</span><br><span class="line">                        final float d_inf = SPLINE_POSITION[index];</span><br><span class="line">                        final float d_sup = SPLINE_POSITION[index + 1];</span><br><span class="line">                        velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);</span><br><span class="line">                        distanceCoef = d_inf + (t - t_inf) * velocityCoef;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    distance = distanceCoef * mSplineDistance;</span><br><span class="line">                    mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f;</span><br><span class="line">                    break;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                case BALLISTIC: &#123;</span><br><span class="line">                    final float t = currentTime / 1000.0f;</span><br><span class="line">                    mCurrVelocity = mVelocity + mDeceleration * t;</span><br><span class="line">                    distance = mVelocity * t + mDeceleration * t * t / 2.0f;</span><br><span class="line">                    break;</span><br><span class="line">                &#125;</span><br><span class="line">                .....</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            mCurrentPosition = mStart + (int) Math.round(distance);</span><br><span class="line"></span><br><span class="line">            return true;</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><p>mCurrentPosition 就是某个时刻对应的位置，有趣的是 BALLISTIC 模式，还记得高中物理的重力加速度时间与距离的公式吗？是不是似曾相识？这算比较简单的公式，但我们今天但主角是 <strong>SPLINE</strong> 模式，ScrollView 但滑动正是使用了这个模式进行滑动，体验非常好！！！！公式有点复杂，就不洗洗研究了，喜欢的同学自行研究吧</p><h3 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h3><p>简单一个滚动，蕴藏的道理生不可测，一不小心就出现了物理知识点…学习还是挺有用的吧</p>]]></content>
    
    <summary type="html">
    
      一个好的滑动不止能够响应手指的移动，而且还能响应 Fling（抛） 事件，响应手指的移动比较简单，手指移动多少距离，布局就移动多少距离；当快速滑动时，手指离开后，不能马上停止滑动，而是应该计算手指的移动速度，产生一个【抛】（Fling ）的动作，让内容继续滚动一段距离
    
    </summary>
    
      <category term="Android" scheme="https://wzes.github.io/categories/Android/"/>
    
    
      <category term="Android" scheme="https://wzes.github.io/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript Babel-Loader 自定义入门</title>
    <link href="https://wzes.github.io/2019/08/04/JavaScript/Babel%20Loader/"/>
    <id>https://wzes.github.io/2019/08/04/JavaScript/Babel Loader/</id>
    <published>2019-08-04T05:42:00.000Z</published>
    <updated>2019-09-02T12:14:06.673Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>突然觉得 babel-loader <a href="https://github.com/babel/babel-loader" target="_blank" rel="noopener">https://github.com/babel/babel-loader</a> 很好玩，比较贴近AST，然而编译原理一直是噩梦，没学懂，好在这东西不需要什么编译原理的知识。但还是涉及到语法解析等操作，所以拿过来学一学还是挺好的。</p><h3 id="什么是-babel"><a class="markdownIt-Anchor" href="#什么是-babel"></a> 什么是 Babel</h3><p>Babel 是一个工具链，主要用于将 ECMAScript 2015+ 代码转换为当前和旧版浏览器或环境中的向后兼容版本的 JavaScript。 以下是 Babel 可以为您做的主要事情：</p><ul><li>转换语法</li><li>目标环境中缺少Polyfill功能（通过@ babel / polyfill）源代码转换（codemods）</li><li>更多</li></ul><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></pre></td><td class="code"><pre><span class="line">// Babel Input: ES2015 arrow function</span><br><span class="line">[1, 2, 3].map((n) =&gt; n + 1);</span><br><span class="line"></span><br><span class="line">// Babel Output: ES5 equivalent</span><br><span class="line">[1, 2, 3].map(function(n) &#123;</span><br><span class="line">  return n + 1;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>代码转化就涉及到了语法解析，这便是我们的重点</p><h3 id="自定义"><a class="markdownIt-Anchor" href="#自定义"></a> 自定义</h3><p>首先新建一个项目 webpack 项目 <a href="https://github.com/wzes/babel-demo" target="_blank" rel="noopener">babel-demo</a><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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── README.md</span><br><span class="line">├── babel-plugin-transform-class</span><br><span class="line">│   └── index.js</span><br><span class="line">├── dist</span><br><span class="line">│   └── bundle.js</span><br><span class="line">├── package-lock.json</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│   └── index.js</span><br><span class="line">└── webpack.config.js</span><br></pre></td></tr></table></figure><p>新建 package.json 并使用 npm install 安装所需插件</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">npm install -D babel-loader @babel/core @babel/preset-env webpack</span><br></pre></td></tr></table></figure><p><strong>package.json</strong></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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;scripts&quot;: &#123;</span><br><span class="line">    &quot;start&quot;: &quot;webpack&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;dependencies&quot;: &#123;&#125;,</span><br><span class="line">  &quot;devDependencies&quot;: &#123;</span><br><span class="line">    &quot;@babel/core&quot;: &quot;^7.5.5&quot;,</span><br><span class="line">    &quot;@babel/preset-env&quot;: &quot;^7.5.5&quot;,</span><br><span class="line">    &quot;babel-loader&quot;: &quot;^8.0.6&quot;,</span><br><span class="line">    &quot;webpack&quot;: &quot;^4.39.1&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>新建 webpack 文件</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">var path = require(&apos;path&apos;);</span><br><span class="line"></span><br><span class="line">module.exports = &#123;</span><br><span class="line">  entry: &apos;./src/index.js&apos;,</span><br><span class="line">  mode: &apos;development&apos;,</span><br><span class="line">  output: &#123;</span><br><span class="line">    path: path.join(__dirname, &apos;dist&apos;),</span><br><span class="line">    filename: &apos;bundle.js&apos;</span><br><span class="line">  &#125;,</span><br><span class="line">  module: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: /\.js$/,</span><br><span class="line">        loader: &apos;babel-loader&apos;,</span><br><span class="line">        options: &#123;</span><br><span class="line">          plugins: [</span><br><span class="line">            &quot;transform-class&quot;</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">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>module 里面的 rule 便是配置我们自定义的 babel 插件，<strong>transform-class</strong> 是插件名。</p><h3 id="手写插件"><a class="markdownIt-Anchor" href="#手写插件"></a> 手写插件</h3><p>下面是一个简单的插件<br>babel-plugin-transform-class/index.js</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></pre></td><td class="code"><pre><span class="line">// A plugin is just a function</span><br><span class="line">module.exports = function (&#123; types: t &#125;) &#123;</span><br><span class="line">  return &#123;</span><br><span class="line">      visitor: &#123;</span><br><span class="line">          Identifier(path) &#123;</span><br><span class="line">            let name = path.node.name; // reverse the name: JavaScript -&gt; tpircSavaJ</span><br><span class="line">            path.node.name = name.split(&apos;&apos;).reverse().join(&apos;&apos;);</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>将 babel-plugin-transform-class 文件夹移动到 node-modules 目录下即可。当运行 webpack 的时候，便会运行这个插件，这个插件会把 node 的 name 做反转。</p><p>我们的 demo</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">function babel() &#123;</span><br><span class="line">  let javascript = &apos;hello babel&apos;;</span><br><span class="line">  console.log(javascript);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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">eval(&quot;function lebab() &#123;\n  let tpircsavaj = &apos;hello babel&apos;;\n  elosnoc.gol(tpircsavaj);\n&#125;\n\n//# sourceURL=webpack:///./src/index.js?&quot;);</span><br></pre></td></tr></table></figure><p>我们看到变量名都反转了。</p><p>我们还可以实现更多·····更多属性，方法在这里 <a href="https://babeljs.io/docs/en/next/babel-types.html" target="_blank" rel="noopener">https://babeljs.io/docs/en/next/babel-types.html</a></p><h3 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h3><p>AST <a href="https://astexplorer.net/" target="_blank" rel="noopener">https://astexplorer.net/</a></p>]]></content>
    
    <summary type="html">
    
      突然觉得 babel-loader 很好玩，比较贴近AST，然而编译原理一直是噩梦，没学懂，好在这东西不需要什么编译原理的知识。但还是涉及到语法解析等操作，所以拿过来学一学还是挺好的
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="Babel" scheme="https://wzes.github.io/tags/Babel/"/>
    
      <category term="Node" scheme="https://wzes.github.io/tags/Node/"/>
    
  </entry>
  
  <entry>
    <title>React Lazyload 源码解析</title>
    <link href="https://wzes.github.io/2019/07/28/JavaScript/React%20Lazyload/"/>
    <id>https://wzes.github.io/2019/07/28/JavaScript/React Lazyload/</id>
    <published>2019-07-28T03:59:00.000Z</published>
    <updated>2019-09-02T12:17:04.380Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>早在多年前，lazyload 已经出现了，懒加载在前端里边同样具有十分重要的意义。react-lazyload 的作用是当组件未出现在屏幕内时，不去挂载该组件，而是使用 placeholder 去渲染，让滚动使内容出现后，组件会被挂载。就是这么简单！例如，一个复杂的组件（非首屏内容），使用了懒加载后，渲染首屏就会节省很多资源，从而减少首屏渲染时间。</p><h3 id="demo"><a class="markdownIt-Anchor" href="#demo"></a> Demo</h3><p>源码地址 <a href="https://github.com/twobin/react-lazyload" target="_blank" rel="noopener">react-lazyload</a><br>Demo地址 <a href="https://twobin.github.io/react-lazyload/examples/" target="_blank" rel="noopener">Demo</a></p><h4 id="helloworld"><a class="markdownIt-Anchor" href="#helloworld"></a> HelloWorld</h4><p>将需要懒加载的组件使用 LazyLoad 包裹即可，最好使用 height 进行站位，否则该组件位置将会为 0</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></pre></td><td class="code"><pre><span class="line">&lt;LazyLoad height=&#123;200&#125;&gt;</span><br><span class="line">    &lt;img src=&quot;tiger.jpg&quot; /&gt; /*</span><br><span class="line">                              Lazy loading images is supported out of box,</span><br><span class="line">                              no extra config needed, set `height` for better</span><br><span class="line">                              experience</span><br><span class="line">                             */</span><br><span class="line">  &lt;/LazyLoad&gt;</span><br></pre></td></tr></table></figure><h3 id="解析"><a class="markdownIt-Anchor" href="#解析"></a> 解析</h3><p>从源码角度分析～</p><h4 id="一览核心"><a class="markdownIt-Anchor" href="#一览核心"></a> 一览核心</h4><p>本小节摘取了最核心的代码，目的在于对 LazyLoad 组件有个最核心的认识，它的核心就是监听滚动事件，检查组件是否在屏幕内，如果在的话就显示，不在的话就不显示～</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></pre></td><td class="code"><pre><span class="line">class LazyLoad extends Component &#123;</span><br><span class="line">  componentDidMount() &#123;</span><br><span class="line">   on(scrollport, &apos;scroll&apos;, finalLazyLoadHandler, passiveEvent);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  render() &#123;</span><br><span class="line">    return this.visible ?</span><br><span class="line">           this.props.children :</span><br><span class="line">             this.props.placeholder ?</span><br><span class="line">                this.props.placeholder :</span><br><span class="line">                &lt;div style=&#123;&#123; height: this.props.height &#125;&#125; className=&quot;lazyload-placeholder&quot; ref=&#123;this.setRef&#125; /&gt;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>LazyLoad 的属性，透过属性，我们可以知道它大概有些什么功能。</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><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">LazyLoad.propTypes = &#123;</span><br><span class="line">  once: PropTypes.bool,</span><br><span class="line">  height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),</span><br><span class="line">  offset: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]),</span><br><span class="line">  overflow: PropTypes.bool, // 不是 window 滚动，而使用了 overflow: scroll </span><br><span class="line">  resize: PropTypes.bool, // 是否监听 resize</span><br><span class="line">  scroll: PropTypes.bool, // 是否监听滚动</span><br><span class="line">  children: PropTypes.node,</span><br><span class="line">  throttle: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),</span><br><span class="line">  debounce: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),</span><br><span class="line">  placeholder: PropTypes.node,</span><br><span class="line">  scrollContainer: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),</span><br><span class="line">  unmountIfInvisible: PropTypes.bool,</span><br><span class="line">  preventLoading: PropTypes.bool</span><br><span class="line">&#125;;</span><br><span class="line">// 默认值</span><br><span class="line">LazyLoad.defaultProps = &#123;</span><br><span class="line">  once: false,</span><br><span class="line">  offset: 0,</span><br><span class="line">  overflow: false,</span><br><span class="line">  resize: false,</span><br><span class="line">  scroll: true,</span><br><span class="line">  unmountIfInvisible: false,</span><br><span class="line">  preventLoading: false,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>完整的 componentDidMount，scrollport 是滚动试图，默认是 window，如果 props 传入了 scrollContainer，那么滚动试图将是自定义的。needResetFinalLazyLoadHandler 是控制是否重置滚动监听。debounce 和 throttle 分别是用来控制滚动事件的监听触发频率，默认都是 undefine，needResetFinalLazyLoadHandler 初始值为 false。finalLazyLoadHandler 初始值也为 undefine，而 overflow 也为 false，scroll 为 true，listeners 是需要懒加载的组件集合，初始大小肯定为0，componentDidMount 最后才会进行添加，因此最终会走到 **on(scrollport, ‘scroll’, finalLazyLoadHandler, passiveEvent)，事件只需要一次绑定即可。<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><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></pre></td><td class="code"><pre><span class="line">componentDidMount() &#123;</span><br><span class="line">    // It&apos;s unlikely to change delay type on the fly, this is mainly</span><br><span class="line">    // designed for tests</span><br><span class="line">    let scrollport = window;</span><br><span class="line">    const &#123;</span><br><span class="line">      scrollContainer,</span><br><span class="line">    &#125; = this.props;</span><br><span class="line">    if (scrollContainer) &#123;</span><br><span class="line">      if (isString(scrollContainer)) &#123;</span><br><span class="line">        scrollport = scrollport.document.querySelector(scrollContainer);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    const needResetFinalLazyLoadHandler = (this.props.debounce !== undefined &amp;&amp; delayType === &apos;throttle&apos;)</span><br><span class="line">      || (delayType === &apos;debounce&apos; &amp;&amp; this.props.debounce === undefined);</span><br><span class="line"></span><br><span class="line">    if (needResetFinalLazyLoadHandler) &#123;</span><br><span class="line">      off(scrollport, &apos;scroll&apos;, finalLazyLoadHandler, passiveEvent);</span><br><span class="line">      off(window, &apos;resize&apos;, finalLazyLoadHandler, passiveEvent);</span><br><span class="line">      finalLazyLoadHandler = null;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (!finalLazyLoadHandler) &#123;</span><br><span class="line">      if (this.props.debounce !== undefined) &#123;</span><br><span class="line">        finalLazyLoadHandler = debounce(lazyLoadHandler, typeof this.props.debounce === &apos;number&apos; ?</span><br><span class="line">                                                         this.props.debounce :</span><br><span class="line">                                                         300);</span><br><span class="line">        delayType = &apos;debounce&apos;;</span><br><span class="line">      &#125; else if (this.props.throttle !== undefined) &#123;</span><br><span class="line">        finalLazyLoadHandler = throttle(lazyLoadHandler, typeof this.props.throttle === &apos;number&apos; ?</span><br><span class="line">                                                         this.props.throttle :</span><br><span class="line">                                                         300);</span><br><span class="line">        delayType = &apos;throttle&apos;;</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        finalLazyLoadHandler = lazyLoadHandler;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (this.props.overflow) &#123;</span><br><span class="line">      const parent = scrollParent(this.ref);</span><br><span class="line">      if (parent &amp;&amp; typeof parent.getAttribute === &apos;function&apos;) &#123;</span><br><span class="line">        const listenerCount = 1 + (+parent.getAttribute(LISTEN_FLAG));</span><br><span class="line">        if (listenerCount === 1) &#123;</span><br><span class="line">          parent.addEventListener(&apos;scroll&apos;, finalLazyLoadHandler, passiveEvent);</span><br><span class="line">        &#125;</span><br><span class="line">        parent.setAttribute(LISTEN_FLAG, listenerCount);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; else if (listeners.length === 0 || needResetFinalLazyLoadHandler) &#123;</span><br><span class="line">      const &#123; scroll, resize &#125; = this.props;</span><br><span class="line"></span><br><span class="line">      if (scroll) &#123;</span><br><span class="line">        on(scrollport, &apos;scroll&apos;, finalLazyLoadHandler, passiveEvent);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      if (resize) &#123;</span><br><span class="line">        on(window, &apos;resize&apos;, finalLazyLoadHandler, passiveEvent);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    listeners.push(this);</span><br><span class="line">    checkVisible(this);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>通常 finalLazyLoadHandler 就是 lazyLoadHandler，不会对滚动事件进行 debounce 或 throttle，我们一般为了性能，会使用 throttle 进行处理。函数会对每一个懒加载组件进行 checkVisible，之后会移除 once component</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></pre></td><td class="code"><pre><span class="line">const lazyLoadHandler = () =&gt; &#123;</span><br><span class="line">  for (let i = 0; i &lt; listeners.length; ++i) &#123;</span><br><span class="line">    const listener = listeners[i];</span><br><span class="line">    checkVisible(listener);</span><br><span class="line">  &#125;</span><br><span class="line">  // Remove `once` component in listeners</span><br><span class="line">  purgePending();</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>checkVisible，检查组件是否出现在 viewport 中，如果出现了就吧 visible 设置为 true，当然如果设置了 unmountIfInvisible = true，那么不可见时组件将被移除，如果之前已经渲染了，需要避免再次渲染。</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><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">const checkVisible = function checkVisible(component) &#123;</span><br><span class="line">  const node = component.ref;</span><br><span class="line">  if (!(node instanceof HTMLElement)) &#123;</span><br><span class="line">    return;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  const parent = scrollParent(node);</span><br><span class="line">  const isOverflow = component.props.overflow &amp;&amp;</span><br><span class="line">                     parent !== node.ownerDocument &amp;&amp;</span><br><span class="line">                     parent !== document &amp;&amp;</span><br><span class="line">                     parent !== document.documentElement;</span><br><span class="line">  const visible = isOverflow ?</span><br><span class="line">                  checkOverflowVisible(component, parent) :</span><br><span class="line">                  checkNormalVisible(component);</span><br><span class="line">  if (visible) &#123;</span><br><span class="line">    // Avoid extra render if previously is visible</span><br><span class="line">    if (!component.visible &amp;&amp; !component.preventLoading) &#123;</span><br><span class="line">      if (component.props.once) &#123;</span><br><span class="line">        pending.push(component);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      component.visible = true;</span><br><span class="line">      component.forceUpdate();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; else if (!(component.props.once &amp;&amp; component.visible)) &#123;</span><br><span class="line">    component.visible = false;</span><br><span class="line">    if (component.props.unmountIfInvisible) &#123;</span><br><span class="line">      component.forceUpdate();</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>checkNormalVisible 检查组件是否 visible 的函数，判断组件的getgetBoundingClientRect 的 top - offset（相对于屏幕顶部的距离） 与 window 的 height 之间的关系</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><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">const checkNormalVisible = function checkNormalVisible(component) &#123;</span><br><span class="line">  const node = component.ref;</span><br><span class="line"></span><br><span class="line">  // If this element is hidden by css rules somehow, it&apos;s definitely invisible</span><br><span class="line">  if (!(node.offsetWidth || node.offsetHeight || node.getClientRects().length)) return false;</span><br><span class="line"></span><br><span class="line">  let top;</span><br><span class="line">  let elementHeight;</span><br><span class="line"></span><br><span class="line">  try &#123;</span><br><span class="line">    // 这个语法 node 也是支持的</span><br><span class="line">    (&#123; top, height: elementHeight &#125; = node.getBoundingClientRect());</span><br><span class="line">  &#125; catch (e) &#123;</span><br><span class="line">    (&#123; top, height: elementHeight &#125; = defaultBoundingClientRect);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  const windowInnerHeight = window.innerHeight || document.documentElement.clientHeight;</span><br><span class="line"></span><br><span class="line">  const offsets = Array.isArray(component.props.offset) ?</span><br><span class="line">                component.props.offset :</span><br><span class="line">                [component.props.offset, component.props.offset]; // Be compatible with previous API</span><br><span class="line"></span><br><span class="line">  return (top - offsets[0] &lt;= windowInnerHeight) &amp;&amp;</span><br><span class="line">         (top + elementHeight + offsets[1] &gt;= 0);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><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">(top - offsets[0] &lt;= windowInnerHeight) &amp;&amp;</span><br><span class="line">         (top + elementHeight + offsets[1] &gt;= 0);</span><br></pre></td></tr></table></figure><p>一张图解析！<br><img src="https://upload-images.jianshu.io/upload_images/7117641-a10f694d96109cc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640" alt="check visible example"><br>到这里解析的差不多了</p><p>欣赏一下 throttle</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><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">export default function throttle(fn, threshhold, scope) &#123;</span><br><span class="line">  threshhold || (threshhold = 250);</span><br><span class="line">  var last,</span><br><span class="line">      deferTimer;</span><br><span class="line">  return function () &#123;</span><br><span class="line">    var context = scope || this;</span><br><span class="line"></span><br><span class="line">    var now = +new Date,</span><br><span class="line">        args = arguments;</span><br><span class="line">    if (last &amp;&amp; now &lt; last + threshhold) &#123;</span><br><span class="line">      // hold on to it</span><br><span class="line">      clearTimeout(deferTimer);</span><br><span class="line">      deferTimer = setTimeout(function () &#123;</span><br><span class="line">        last = now;</span><br><span class="line">        fn.apply(context, args);</span><br><span class="line">      &#125;, threshhold);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      last = now;</span><br><span class="line">      fn.apply(context, args);</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>再欣赏一下 debounce</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">export default function debounce(func, wait, immediate) &#123;</span><br><span class="line">  let timeout;</span><br><span class="line">  let args;</span><br><span class="line">  let context;</span><br><span class="line">  let timestamp;</span><br><span class="line">  let result;</span><br><span class="line"></span><br><span class="line">  const later = function later() &#123;</span><br><span class="line">    const last = +(new Date()) - timestamp;</span><br><span class="line"></span><br><span class="line">    if (last &lt; wait &amp;&amp; last &gt;= 0) &#123;</span><br><span class="line">      timeout = setTimeout(later, wait - last);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      timeout = null;</span><br><span class="line">      if (!immediate) &#123;</span><br><span class="line">        result = func.apply(context, args);</span><br><span class="line">        if (!timeout) &#123;</span><br><span class="line">          context = null;</span><br><span class="line">          args = null;</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><span class="line"></span><br><span class="line">  return function debounced() &#123;</span><br><span class="line">    context = this;</span><br><span class="line">    args = arguments;</span><br><span class="line">    timestamp = +(new Date());</span><br><span class="line"></span><br><span class="line">    const callNow = immediate &amp;&amp; !timeout;</span><br><span class="line">    if (!timeout) &#123;</span><br><span class="line">      timeout = setTimeout(later, wait);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (callNow) &#123;</span><br><span class="line">      result = func.apply(context, args);</span><br><span class="line">      context = null;</span><br><span class="line">      args = null;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return result;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>获取 scrollParent</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><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">export default (node) =&gt; &#123;</span><br><span class="line">  if (!(node instanceof HTMLElement)) &#123;</span><br><span class="line">    return document.documentElement;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  const excludeStaticParent = node.style.position === &apos;absolute&apos;;</span><br><span class="line">  const overflowRegex = /(scroll|auto)/;</span><br><span class="line">  let parent = node;</span><br><span class="line"></span><br><span class="line">  while (parent) &#123;</span><br><span class="line">    if (!parent.parentNode) &#123;</span><br><span class="line">      return node.ownerDocument || document.documentElement;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    const style = window.getComputedStyle(parent);</span><br><span class="line">    const position = style.position;</span><br><span class="line">    const overflow = style.overflow;</span><br><span class="line">    const overflowX = style[&apos;overflow-x&apos;];</span><br><span class="line">    const overflowY = style[&apos;overflow-y&apos;];</span><br><span class="line"></span><br><span class="line">    if (position === &apos;static&apos; &amp;&amp; excludeStaticParent) &#123;</span><br><span class="line">      parent = parent.parentNode;</span><br><span class="line">      continue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (overflowRegex.test(overflow) &amp;&amp; overflowRegex.test(overflowX) &amp;&amp; overflowRegex.test(overflowY)) &#123;</span><br><span class="line">      return parent;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    parent = parent.parentNode;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return node.ownerDocument || node.documentElement || document.documentElement;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="总结思考"><a class="markdownIt-Anchor" href="#总结思考"></a> 总结思考</h3><p>我们可以看到，Lazyload 并不能实现类似客户端的图片懒加载，Lazyload 加载图片也会出现白屏时间，解决办法是使用 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Attribute/image.onload" target="_blank" rel="noopener">image.onload</a>，当图片资源请求关闭后，再显示图片，就可以做到类似客户端的效果。</p>]]></content>
    
    <summary type="html">
    
      早在多年前，lazyload 已经出现了，懒加载在前端里边同样具有十分重要的意义。react-lazyload 的作用是当组件未出现在屏幕内时，不去挂载该组件，而是使用 placeholder 去渲染，让滚动使内容出现后，组件会被挂载。就是这么简单！
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="React" scheme="https://wzes.github.io/tags/React/"/>
    
      <category term="Lazyload" scheme="https://wzes.github.io/tags/Lazyload/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript Swipe-js-ios 源码解析</title>
    <link href="https://wzes.github.io/2019/07/20/JavaScript/Swipe-js-ios/"/>
    <id>https://wzes.github.io/2019/07/20/JavaScript/Swipe-js-ios/</id>
    <published>2019-07-20T06:38:00.000Z</published>
    <updated>2019-09-02T12:16:32.909Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>Swipe，常用来做轮播图，需要翻页的场景，最经典的开源库 <a href="https://github.com/voronianski/swipe-js-iso" target="_blank" rel="noopener">swipe-js-iso</a> ，不过更推荐使用 React 组件 <a href="https://github.com/voronianski/react-swipe" target="_blank" rel="noopener">react-swipe</a>，它封装了 swipe-js-ios  组件，而 swipe-js-ios 组件则封装了 <a href="https://github.com/thebird/Swipe" target="_blank" rel="noopener">Swipe</a></p><h3 id="helloword"><a class="markdownIt-Anchor" href="#helloword"></a> HelloWord</h3><p><img src="https://upload-images.jianshu.io/upload_images/7117641-de83b4953b2ad82f.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/620" alt="ReactSwipe"></p><p>如果单独使用的化，创建一个 swipe，dom 必须是三层结构，最里面一层是放 slide 的。</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></pre></td><td class="code"><pre><span class="line">&lt;div id=&quot;slider&quot; class=&quot;swipe&quot;&gt;</span><br><span class="line">  &lt;div class=&quot;swipe-wrap&quot;&gt;</span><br><span class="line">    &lt;div&gt;&lt;/div&gt;</span><br><span class="line">    &lt;div&gt;&lt;/div&gt;</span><br><span class="line">    &lt;div&gt;&lt;/div&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><p>CSS 子元素 <strong>float: left;</strong> container 的宽度为自定义，swipe-wrap 的宽度为子页面数 * container 的 width，每一个 slide 的宽度为 container 的 width</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></pre></td><td class="code"><pre><span class="line">.swipe &#123;</span><br><span class="line">  overflow: hidden;</span><br><span class="line">  visibility: hidden;</span><br><span class="line">  position: relative;</span><br><span class="line">&#125;</span><br><span class="line">.swipe-wrap &#123;</span><br><span class="line">  overflow: hidden;</span><br><span class="line">  position: relative;</span><br><span class="line">&#125;</span><br><span class="line">.swipe-wrap &gt; div &#123;</span><br><span class="line">  float: left;</span><br><span class="line">  width: 100%;</span><br><span class="line">  position: relative;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>load 以后，创建 Swipe 即可</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">const mySwipe = Swipe(document.getElementById(&apos;slider&apos;));</span><br></pre></td></tr></table></figure><h3 id="源码解析"><a class="markdownIt-Anchor" href="#源码解析"></a> 源码解析</h3><p>swipe-js-ios 使用立即函数导出了一个 Swipe 模块，使用 <strong>typeof module !== ‘undefined’ &amp;&amp; module.exports</strong> 兼容 Node 和 浏览器环境，如果是 Node 环境，将会有 module.export 那么则使用 module.export 导出，否则使用 root.Swipe 全局变量导出</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></pre></td><td class="code"><pre><span class="line">(function(root, factory) &#123;</span><br><span class="line">  if (typeof module !== &apos;undefined&apos; &amp;&amp; module.exports) &#123;</span><br><span class="line">    module.exports = factory();</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    root.Swipe = factory();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)(this, function() &#123;</span><br><span class="line">  &apos;use strict&apos;;</span><br><span class="line">  return function Swipe(container, options) &#123;</span><br><span class="line">        ....</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>检查浏览器的环境，算是一种规范吧，风别检查触摸事件和 transition 的支持<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><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">var browser = &#123;</span><br><span class="line">      addEventListener: !!window.addEventListener,</span><br><span class="line">      touch:</span><br><span class="line">        &apos;ontouchstart&apos; in window ||</span><br><span class="line">        (window.DocumentTouch &amp;&amp; document instanceof window.DocumentTouch),</span><br><span class="line">      transitions: (function(temp) &#123;</span><br><span class="line">        var props = [</span><br><span class="line">          &apos;transitionProperty&apos;,</span><br><span class="line">          &apos;WebkitTransition&apos;,</span><br><span class="line">          &apos;MozTransition&apos;,</span><br><span class="line">          &apos;OTransition&apos;,</span><br><span class="line">          &apos;msTransition&apos;</span><br><span class="line">        ];</span><br><span class="line">        for (var i in props)</span><br><span class="line">          if (temp.style[props[i]] !== undefined) return true;</span><br><span class="line">        return false;</span><br><span class="line">      &#125;)(document.createElement(&apos;swipe&apos;))</span><br><span class="line">    &#125;;</span><br></pre></td></tr></table></figure><p>创建时会调用 setup，继而添加事件，touch 触摸事件、transitionend 移动事件，resize 重新布局事件</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">// trigger setup</span><br><span class="line">setup();</span><br><span class="line"></span><br><span class="line">// start auto slideshow if applicable</span><br><span class="line">if (delay) begin();</span><br><span class="line"></span><br><span class="line">// add event listeners</span><br><span class="line">if (browser.addEventListener) &#123;</span><br><span class="line">  // set touchstart event on element</span><br><span class="line">  if (browser.touch) &#123;</span><br><span class="line">    element.addEventListener(&apos;touchstart&apos;, events, false);</span><br><span class="line">    element.addEventListener(&apos;touchforcechange&apos;, function() &#123;&#125;, false);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (browser.transitions) &#123;</span><br><span class="line">    element.addEventListener(&apos;webkitTransitionEnd&apos;, events, false);</span><br><span class="line">    element.addEventListener(&apos;msTransitionEnd&apos;, events, false);</span><br><span class="line">    element.addEventListener(&apos;oTransitionEnd&apos;, events, false);</span><br><span class="line">    element.addEventListener(&apos;otransitionend&apos;, events, false);</span><br><span class="line">    element.addEventListener(&apos;transitionend&apos;, events, false);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // set resize event on window</span><br><span class="line">  window.addEventListener(&apos;resize&apos;, events, false);</span><br><span class="line">&#125; else &#123;</span><br><span class="line">  window.onresize = function() &#123;</span><br><span class="line">    setup();</span><br><span class="line">  &#125;; // to play nice with old IE</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>setup 函数的实现，slides 就是容器里面的页面，continuous 是否自动轮播，slidePos 记录了每一个页面的位置，width 是每一个页面的宽度，此处需要剪掉widthOfSiblingSlidePreview 的大小，可以预览前后页。element 的宽度是 **页数 * width **</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><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">function setup() &#123;</span><br><span class="line">      // cache slides</span><br><span class="line">      slides = element.children;</span><br><span class="line">      length = slides.length;</span><br><span class="line"></span><br><span class="line">      // set continuous to false if only one slide</span><br><span class="line">      continuous = slides.length &lt; 2 ? false : options.continuous;</span><br><span class="line"></span><br><span class="line">      // create an array to store current positions of each slide</span><br><span class="line">      slidePos = new Array(slides.length);</span><br><span class="line"></span><br><span class="line">      // determine width of each slide</span><br><span class="line">      width =</span><br><span class="line">        Math.round(</span><br><span class="line">          container.getBoundingClientRect().width || container.offsetWidth</span><br><span class="line">        ) -</span><br><span class="line">        widthOfSiblingSlidePreview * 2;</span><br><span class="line"></span><br><span class="line">      element.style.width = slides.length * width + &apos;px&apos;;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>初始化时，需要更新页面的位置</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></pre></td><td class="code"><pre><span class="line">var pos = slides.length;</span><br><span class="line">while (pos--) &#123;</span><br><span class="line">  var slide = slides[pos];</span><br><span class="line"></span><br><span class="line">  slide.style.width = width + &apos;px&apos;;</span><br><span class="line">  slide.setAttribute(&apos;data-index&apos;, pos);</span><br><span class="line"></span><br><span class="line">  if (browser.transitions) &#123;</span><br><span class="line">    slide.style.left = pos * -width + widthOfSiblingSlidePreview + &apos;px&apos;;</span><br><span class="line">    move(pos, index &gt; pos ? -width : index &lt; pos ? width : 0, 0);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果支持轮播的化，需要把左边和右边也填充，然后把 container.style.visibility 设置为 visible，如果不支持 transition 的话，只需要设置 element.style.left 即可</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></pre></td><td class="code"><pre><span class="line">// reposition elements before and after index</span><br><span class="line">    if (continuous &amp;&amp; browser.transitions) &#123;</span><br><span class="line">      move(circle(index - 1), -width, 0);</span><br><span class="line">      move(circle(index + 1), width, 0);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (!browser.transitions)</span><br><span class="line">      element.style.left = index * -width + widthOfSiblingSlidePreview + &apos;px&apos;;</span><br><span class="line"></span><br><span class="line">    container.style.visibility = &apos;visible&apos;;</span><br></pre></td></tr></table></figure><p>move 的实现，translate，更新 slidePos 的位置</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">function move(index, dist, speed) &#123;</span><br><span class="line">  translate(index, dist, speed);</span><br><span class="line">  slidePos[index] = dist;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>translate 三个参数，index：需要移动的页，dist：移动的位置，speed：移动速度，移动只需要给 页面设置 style 的 transform 就OK了，之后就会以动画移动过去了</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></pre></td><td class="code"><pre><span class="line">function translate(index, dist, speed) &#123;</span><br><span class="line">      var slide = slides[index];</span><br><span class="line">      var style = slide &amp;&amp; slide.style;</span><br><span class="line"></span><br><span class="line">      if (!style) return;</span><br><span class="line"></span><br><span class="line">      style.webkitTransitionDuration = style.MozTransitionDuration = style.msTransitionDuration = style.OTransitionDuration = style.transitionDuration =</span><br><span class="line">        speed + &apos;ms&apos;;</span><br><span class="line"></span><br><span class="line">      style.webkitTransform = &apos;translate(&apos; + dist + &apos;px,0)&apos; + &apos;translateZ(0)&apos;;</span><br><span class="line">      style.msTransform = style.MozTransform = style.OTransform =</span><br><span class="line">        &apos;translateX(&apos; + dist + &apos;px)&apos;;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>prev 对外提供接口，手动翻页使用</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">function prev() &#123;</span><br><span class="line">   if (continuous) slide(index - 1);</span><br><span class="line">   else if (index) slide(index - 1);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>slide 移动函数，指定移动的页 index 和速度</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">function slide(to, slideSpeed) &#123;</span><br><span class="line">      // do nothing if already on requested slide</span><br><span class="line">      if (index == to) return;</span><br><span class="line"></span><br><span class="line">      if (browser.transitions) &#123;</span><br><span class="line">        var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward</span><br><span class="line"></span><br><span class="line">        // get the actual position of the slide</span><br><span class="line">        if (continuous) &#123;</span><br><span class="line">          var natural_direction = direction;</span><br><span class="line">          direction = -slidePos[circle(to)] / width;</span><br><span class="line"></span><br><span class="line">          // if going forward but to &lt; index, use to = slides.length + to</span><br><span class="line">          // if going backward but to &gt; index, use to = -slides.length + to</span><br><span class="line">          if (direction !== natural_direction)</span><br><span class="line">            to = -direction * slides.length + to;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        var diff = Math.abs(index - to) - 1;</span><br><span class="line"></span><br><span class="line">        // move all the slides between index and to in the right direction</span><br><span class="line">        while (diff--)</span><br><span class="line">          move(</span><br><span class="line">            circle((to &gt; index ? to : index) - diff - 1),</span><br><span class="line">            width * direction,</span><br><span class="line">            0</span><br><span class="line">          );</span><br><span class="line"></span><br><span class="line">        to = circle(to);</span><br><span class="line"></span><br><span class="line">        move(index, width * direction, slideSpeed || speed);</span><br><span class="line">        move(to, 0, slideSpeed || speed);</span><br><span class="line"></span><br><span class="line">        if (continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        to = circle(to); </span><br><span class="line">        animate(index * -width, to * -width, slideSpeed || speed);</span><br><span class="line">        //no fallback for a circular continuous if the browser does not accept transitions</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      index = to;</span><br><span class="line">      offloadFn(options.callback &amp;&amp; options.callback(index, slides[index]));</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>如果浏览器不支持 transition，那么则使用 setInterval 实现渐进移动， animation 是对整个页面进行移动，而 move 是移动每一个子页面</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">function animate(from, to, speed) &#123;</span><br><span class="line">      // if not an animation, just reposition</span><br><span class="line">      if (!speed) &#123;</span><br><span class="line">        element.style.left = to + &apos;px&apos;;</span><br><span class="line">        return;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      var start = +new Date();</span><br><span class="line"></span><br><span class="line">      var timer = setInterval(function() &#123;</span><br><span class="line">        var timeElap = +new Date() - start;</span><br><span class="line"></span><br><span class="line">        if (timeElap &gt; speed) &#123;</span><br><span class="line">          element.style.left = to + &apos;px&apos;;</span><br><span class="line"></span><br><span class="line">          if (delay) begin();</span><br><span class="line"></span><br><span class="line">          options.transitionEnd &amp;&amp;</span><br><span class="line">            options.transitionEnd.call(event, index, slides[index]);</span><br><span class="line"></span><br><span class="line">          clearInterval(timer);</span><br><span class="line">          return;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        element.style.left =</span><br><span class="line">          (to - from) * (Math.floor((timeElap / speed) * 100) / 100) +</span><br><span class="line">          from +</span><br><span class="line">          &apos;px&apos;;</span><br><span class="line">      &#125;, 4);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>接下来研究一下触摸事件的处理，首先是 start，start 事件会记录起始触摸位置以及时间，并且添加 touchmove 和 touchend 事件，如果没有 start 事件，触摸事件是不存在的， end 的时候会被移除。</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">start: function(event) &#123;</span><br><span class="line">        var touches = event.touches[0];</span><br><span class="line"></span><br><span class="line">        // measure start values</span><br><span class="line">        start = &#123;</span><br><span class="line">          // get initial touch coords</span><br><span class="line">          x: touches.pageX,</span><br><span class="line">          y: touches.pageY,</span><br><span class="line"></span><br><span class="line">          // store time to determine touch duration</span><br><span class="line">          time: +new Date()</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        // used for testing first move event</span><br><span class="line">        isScrolling = undefined;</span><br><span class="line"></span><br><span class="line">        // reset delta and end measurements</span><br><span class="line">        delta = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">        // attach touchmove and touchend listeners</span><br><span class="line">        element.addEventListener(&apos;touchmove&apos;, this, false);</span><br><span class="line">        element.addEventListener(&apos;touchend&apos;, this, false);</span><br><span class="line">      &#125;,</span><br></pre></td></tr></table></figure><p>move 事件，delta 将手指移动距离记下，最后视同 translate 移动</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><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></pre></td><td class="code"><pre><span class="line">move: function(event) &#123;</span><br><span class="line">       // ensure swiping with one touch and not pinching</span><br><span class="line">       if (event.touches.length &gt; 1 || (event.scale &amp;&amp; event.scale !== 1))</span><br><span class="line">         return;</span><br><span class="line"></span><br><span class="line">       if (options.disableScroll) return;</span><br><span class="line"></span><br><span class="line">       var touches = event.touches[0];</span><br><span class="line"></span><br><span class="line">       // measure change in x and y</span><br><span class="line">       delta = &#123;</span><br><span class="line">         x: touches.pageX - start.x,</span><br><span class="line">         y: touches.pageY - start.y</span><br><span class="line">       &#125;;</span><br><span class="line"></span><br><span class="line">       // determine if scrolling test has run - one time test</span><br><span class="line">       if (typeof isScrolling == &apos;undefined&apos;) &#123;</span><br><span class="line">         isScrolling = !!(</span><br><span class="line">           isScrolling || Math.abs(delta.x) &lt; Math.abs(delta.y)</span><br><span class="line">         );</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       // if user is not trying to scroll vertically</span><br><span class="line">       if (!isScrolling) &#123;</span><br><span class="line">         // prevent native scrolling</span><br><span class="line">         event.preventDefault();</span><br><span class="line"></span><br><span class="line">         // stop slideshow</span><br><span class="line">         stop();</span><br><span class="line"></span><br><span class="line">         // increase resistance if first or last slide</span><br><span class="line">         if (continuous) &#123;</span><br><span class="line">           // we don&apos;t add resistance at the end</span><br><span class="line"></span><br><span class="line">           translate(</span><br><span class="line">             circle(index - 1),</span><br><span class="line">             delta.x + slidePos[circle(index - 1)],</span><br><span class="line">             0</span><br><span class="line">           );</span><br><span class="line">           translate(index, delta.x + slidePos[index], 0);</span><br><span class="line">           translate(</span><br><span class="line">             circle(index + 1),</span><br><span class="line">             delta.x + slidePos[circle(index + 1)],</span><br><span class="line">             0</span><br><span class="line">           );</span><br><span class="line">         &#125; else &#123;</span><br><span class="line">           delta.x =</span><br><span class="line">             delta.x /</span><br><span class="line">             ((!index &amp;&amp; delta.x &gt; 0) || // if first slide and sliding left</span><br><span class="line">             (index == slides.length - 1 &amp;&amp; // or if last slide and sliding right</span><br><span class="line">               delta.x &lt; 0) // and if sliding at all</span><br><span class="line">               ? Math.abs(delta.x) / width + 1 // determine resistance level</span><br><span class="line">               : 1); // no resistance if false</span><br><span class="line"></span><br><span class="line">           // translate 1:1</span><br><span class="line">           translate(index - 1, delta.x + slidePos[index - 1], 0);</span><br><span class="line">           translate(index, delta.x + slidePos[index], 0);</span><br><span class="line">           translate(index + 1, delta.x + slidePos[index + 1], 0);</span><br><span class="line">         &#125;</span><br><span class="line">         options.swiping &amp;&amp; options.swiping(-delta.x / width);</span><br><span class="line">       &#125;</span><br><span class="line">     &#125;,</span><br></pre></td></tr></table></figure><p>end 事件，主要判断本次触摸滑动是否有效，并持续接下来的操作，最后将会 remove 掉监听事件。</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line">end: function(event) &#123;</span><br><span class="line">        // measure duration</span><br><span class="line">        var duration = +new Date() - start.time;</span><br><span class="line"></span><br><span class="line">        // determine if slide attempt triggers next/prev slide</span><br><span class="line">        var isValidSlide =</span><br><span class="line">          (Number(duration) &lt; 250 &amp;&amp; // if slide duration is less than 250ms</span><br><span class="line">            Math.abs(delta.x) &gt; 20) || // and if slide amt is greater than 20px</span><br><span class="line">          Math.abs(delta.x) &gt; width / 2; // or if slide amt is greater than half the width</span><br><span class="line"></span><br><span class="line">        // determine if slide attempt is past start and end</span><br><span class="line">        var isPastBounds =</span><br><span class="line">          (!index &amp;&amp; delta.x &gt; 0) || // if first slide and slide amt is greater than 0</span><br><span class="line">          (index == slides.length - 1 &amp;&amp; delta.x &lt; 0); // or if last slide and slide amt is less than 0</span><br><span class="line"></span><br><span class="line">        if (continuous) isPastBounds = false;</span><br><span class="line"></span><br><span class="line">        // determine direction of swipe (true:right, false:left)</span><br><span class="line">        var direction = delta.x &lt; 0;</span><br><span class="line"></span><br><span class="line">        // if not scrolling vertically</span><br><span class="line">        if (!isScrolling) &#123;</span><br><span class="line">          if (isValidSlide &amp;&amp; !isPastBounds) &#123;</span><br><span class="line">            if (direction) &#123;</span><br><span class="line">              if (continuous) &#123;</span><br><span class="line">                // we need to get the next in this direction in place</span><br><span class="line"></span><br><span class="line">                move(circle(index - 1), -width, 0);</span><br><span class="line">                move(circle(index + 2), width, 0);</span><br><span class="line">              &#125; else &#123;</span><br><span class="line">                move(index - 1, -width, 0);</span><br><span class="line">              &#125;</span><br><span class="line"></span><br><span class="line">              move(index, slidePos[index] - width, speed);</span><br><span class="line">              move(</span><br><span class="line">                circle(index + 1),</span><br><span class="line">                slidePos[circle(index + 1)] - width,</span><br><span class="line">                speed</span><br><span class="line">              );</span><br><span class="line">              index = circle(index + 1);</span><br><span class="line">            &#125; else &#123;</span><br><span class="line">              if (continuous) &#123;</span><br><span class="line">                // we need to get the next in this direction in place</span><br><span class="line"></span><br><span class="line">                move(circle(index + 1), width, 0);</span><br><span class="line">                move(circle(index - 2), -width, 0);</span><br><span class="line">              &#125; else &#123;</span><br><span class="line">                move(index + 1, width, 0);</span><br><span class="line">              &#125;</span><br><span class="line"></span><br><span class="line">              move(index, slidePos[index] + width, speed);</span><br><span class="line">              move(</span><br><span class="line">                circle(index - 1),</span><br><span class="line">                slidePos[circle(index - 1)] + width,</span><br><span class="line">                speed</span><br><span class="line">              );</span><br><span class="line">              index = circle(index - 1);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            options.callback &amp;&amp; options.callback(index, slides[index]);</span><br><span class="line">          &#125; else &#123;</span><br><span class="line">            if (continuous) &#123;</span><br><span class="line">              move(circle(index - 1), -width, speed);</span><br><span class="line">              move(index, 0, speed);</span><br><span class="line">              move(circle(index + 1), width, speed);</span><br><span class="line">            &#125; else &#123;</span><br><span class="line">              move(index - 1, -width, speed);</span><br><span class="line">              move(index, 0, speed);</span><br><span class="line">              move(index + 1, width, speed);</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">        // kill touchmove and touchend event listeners until touchstart called again</span><br><span class="line">        element.removeEventListener(&apos;touchmove&apos;, events, false);</span><br><span class="line">        element.removeEventListener(&apos;touchend&apos;, events, false);</span><br><span class="line">        element.removeEventListener(&apos;touchforcechange&apos;, function() &#123;&#125;, false);</span><br><span class="line">      &#125;,</span><br></pre></td></tr></table></figure><h4 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h4><p>总的来说，swipe-js-ios 充分利用了 transition，来实现移动动画，搞清楚触摸事件就比较容易能写出来可滑动的 swipe</p>]]></content>
    
    <summary type="html">
    
      Swipe，常用来做轮播图，需要翻页的场景，最经典的开源库 [swipe-js-iso]，不过更推荐使用 React 组件 [react-swipe]，它封装了 swipe-js-ios  组件，而 swipe-js-ios 组件则封装了 [Swipe]
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="HTML" scheme="https://wzes.github.io/tags/HTML/"/>
    
      <category term="DOM" scheme="https://wzes.github.io/tags/DOM/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript Webpack 源码解析</title>
    <link href="https://wzes.github.io/2019/07/13/JavaScript/Webpack/"/>
    <id>https://wzes.github.io/2019/07/13/JavaScript/Webpack/</id>
    <published>2019-07-13T11:59:16.000Z</published>
    <updated>2019-09-02T12:17:16.955Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>算是一个h5开发了，虽然没写过什么完整的前端页面，但接触前端也有段时间了，对于一个合格的前端开发者而言，搞懂 webpack 打包原理还是比较重要的</p><h3 id="hello-world"><a class="markdownIt-Anchor" href="#hello-world"></a> hello world</h3><p>使用 commonjs 规范，lib.js 只导出一个方法</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">// lib.js</span><br><span class="line">module.exports = function () &#123;</span><br><span class="line">    return &quot;hello webpack!&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>index.js 使用 require 引入，代码很简单，输入方法返回值</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></pre></td><td class="code"><pre><span class="line">// index.js</span><br><span class="line">const func = require(&quot;./lib&quot;)</span><br><span class="line"></span><br><span class="line">const result = func()</span><br><span class="line">// print hello</span><br><span class="line">console.log(result)</span><br></pre></td></tr></table></figure><h5 id="目录结构"><a class="markdownIt-Anchor" href="#目录结构"></a> 目录结构</h5><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></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── dist</span><br><span class="line">│   └── main.js</span><br><span class="line">├── package-lock.json</span><br><span class="line">├── package.json</span><br><span class="line">├── node_modules</span><br><span class="line">├── src</span><br><span class="line">│   ├── index.js</span><br><span class="line">│   └── lib.js</span><br><span class="line">└── webpack.config.js</span><br></pre></td></tr></table></figure><h5 id="webpackconfigjs"><a class="markdownIt-Anchor" href="#webpackconfigjs"></a> webpack.config.js</h5><p>为了方便看生成的源码，我们将 mode 设置为 development，</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></pre></td><td class="code"><pre><span class="line">var path = require(&apos;path&apos;);</span><br><span class="line"></span><br><span class="line">module.exports = &#123;</span><br><span class="line">    context: path.resolve(__dirname, &apos;./&apos;),</span><br><span class="line">    mode: &apos;development&apos;,</span><br><span class="line">    entry: &apos;./src/index.js&apos;,</span><br><span class="line">    output: &#123;</span><br><span class="line">        path: path.resolve(__dirname, &apos;dist&apos;),</span><br><span class="line">        filename: &apos;main.js&apos;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h5 id="packagejson"><a class="markdownIt-Anchor" href="#packagejson"></a> package.json</h5><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;webpack-demo&quot;,</span><br><span class="line">  &quot;version&quot;: &quot;1.0.0&quot;,</span><br><span class="line">  &quot;description&quot;: &quot;&quot;,</span><br><span class="line">  &quot;private&quot;: true,</span><br><span class="line">  &quot;scripts&quot;: &#123;</span><br><span class="line">    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,</span><br><span class="line">    &quot;build&quot;: &quot;webpack&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;keywords&quot;: [],</span><br><span class="line">  &quot;author&quot;: &quot;&quot;,</span><br><span class="line">  &quot;license&quot;: &quot;ISC&quot;,</span><br><span class="line">  &quot;devDependencies&quot;: &#123;</span><br><span class="line">    &quot;webpack&quot;: &quot;^4.35.2&quot;,</span><br><span class="line">    &quot;webpack-cli&quot;: &quot;^3.3.5&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;dependencies&quot;: &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="编译运行"><a class="markdownIt-Anchor" href="#编译运行"></a> 编译运行</h5><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">npm run build</span><br><span class="line">node dist/main.js</span><br></pre></td></tr></table></figure><h5 id="输出"><a class="markdownIt-Anchor" href="#输出"></a> 输出</h5><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">$ node dist/main.js </span><br><span class="line">hello webpack!</span><br></pre></td></tr></table></figure><h4 id="起源"><a class="markdownIt-Anchor" href="#起源"></a> 起源</h4><p>浏览器，node 并不支持模块化，我们在项目中使用的 require、export 将会经过 webpack 后，这些 js 就会被打包整合成一个 js 文件，只需要运行 js 文件，整个模块将会运行起来了。</p><h4 id="mainjs-解析"><a class="markdownIt-Anchor" href="#mainjs-解析"></a> main.js 解析</h4><p>整个文件只有 111 行，这是未经过压缩的版本，生产环境下的输出文件比这还要精简，只需要在 webpack.config.js 中将 mode 值等于 production 即可改变打包环境</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><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line">/******/ (function(modules) &#123; // webpackBootstrap</span><br><span class="line">/******/ // The module cache</span><br><span class="line">/******/ var installedModules = &#123;&#125;;</span><br><span class="line">/******/</span><br><span class="line">/******/ // The require function</span><br><span class="line">/******/ function __webpack_require__(moduleId) &#123;</span><br><span class="line">/******/</span><br><span class="line">/******/ // Check if module is in cache</span><br><span class="line">/******/ if(installedModules[moduleId]) &#123;</span><br><span class="line">/******/ return installedModules[moduleId].exports;</span><br><span class="line">/******/ &#125;</span><br><span class="line">/******/ // Create a new module (and put it into the cache)</span><br><span class="line">/******/ var module = installedModules[moduleId] = &#123;</span><br><span class="line">/******/ i: moduleId,</span><br><span class="line">/******/ l: false,</span><br><span class="line">/******/ exports: &#123;&#125;</span><br><span class="line">/******/ &#125;;</span><br><span class="line">/******/</span><br><span class="line">/******/ // Execute the module function</span><br><span class="line">/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);</span><br><span class="line">/******/</span><br><span class="line">/******/ // Flag the module as loaded</span><br><span class="line">/******/ module.l = true;</span><br><span class="line">/******/</span><br><span class="line">/******/ // Return the exports of the module</span><br><span class="line">/******/ return module.exports;</span><br><span class="line">/******/ &#125;</span><br><span class="line">/******/</span><br><span class="line">/******/</span><br><span class="line">/******/ // expose the modules object (__webpack_modules__)</span><br><span class="line">/******/ __webpack_require__.m = modules;</span><br><span class="line">/******/</span><br><span class="line">/******/ // expose the module cache</span><br><span class="line">/******/ __webpack_require__.c = installedModules;</span><br><span class="line">/******/</span><br><span class="line">/******/ // define getter function for harmony exports</span><br><span class="line">/******/ __webpack_require__.d = function(exports, name, getter) &#123;</span><br><span class="line">/******/ if(!__webpack_require__.o(exports, name)) &#123;</span><br><span class="line">/******/ Object.defineProperty(exports, name, &#123; enumerable: true, get: getter &#125;);</span><br><span class="line">/******/ &#125;</span><br><span class="line">/******/ &#125;;</span><br><span class="line">/******/</span><br><span class="line">/******/ // define __esModule on exports</span><br><span class="line">/******/ __webpack_require__.r = function(exports) &#123;</span><br><span class="line">/******/ if(typeof Symbol !== &apos;undefined&apos; &amp;&amp; Symbol.toStringTag) &#123;</span><br><span class="line">/******/ Object.defineProperty(exports, Symbol.toStringTag, &#123; value: &apos;Module&apos; &#125;);</span><br><span class="line">/******/ &#125;</span><br><span class="line">/******/ Object.defineProperty(exports, &apos;__esModule&apos;, &#123; value: true &#125;);</span><br><span class="line">/******/ &#125;;</span><br><span class="line">/******/</span><br><span class="line">/******/ // create a fake namespace object</span><br><span class="line">/******/ // mode &amp; 1: value is a module id, require it</span><br><span class="line">/******/ // mode &amp; 2: merge all properties of value into the ns</span><br><span class="line">/******/ // mode &amp; 4: return value when already ns object</span><br><span class="line">/******/ // mode &amp; 8|1: behave like require</span><br><span class="line">/******/ __webpack_require__.t = function(value, mode) &#123;</span><br><span class="line">/******/ if(mode &amp; 1) value = __webpack_require__(value);</span><br><span class="line">/******/ if(mode &amp; 8) return value;</span><br><span class="line">/******/ if((mode &amp; 4) &amp;&amp; typeof value === &apos;object&apos; &amp;&amp; value &amp;&amp; value.__esModule) return value;</span><br><span class="line">/******/ var ns = Object.create(null);</span><br><span class="line">/******/ __webpack_require__.r(ns);</span><br><span class="line">/******/ Object.defineProperty(ns, &apos;default&apos;, &#123; enumerable: true, value: value &#125;);</span><br><span class="line">/******/ if(mode &amp; 2 &amp;&amp; typeof value != &apos;string&apos;) for(var key in value) __webpack_require__.d(ns, key, function(key) &#123; return value[key]; &#125;.bind(null, key));</span><br><span class="line">/******/ return ns;</span><br><span class="line">/******/ &#125;;</span><br><span class="line">/******/</span><br><span class="line">/******/ // getDefaultExport function for compatibility with non-harmony modules</span><br><span class="line">/******/ __webpack_require__.n = function(module) &#123;</span><br><span class="line">/******/ var getter = module &amp;&amp; module.__esModule ?</span><br><span class="line">/******/ function getDefault() &#123; return module[&apos;default&apos;]; &#125; :</span><br><span class="line">/******/ function getModuleExports() &#123; return module; &#125;;</span><br><span class="line">/******/ __webpack_require__.d(getter, &apos;a&apos;, getter);</span><br><span class="line">/******/ return getter;</span><br><span class="line">/******/ &#125;;</span><br><span class="line">/******/</span><br><span class="line">/******/ // Object.prototype.hasOwnProperty.call</span><br><span class="line">/******/ __webpack_require__.o = function(object, property) &#123; return Object.prototype.hasOwnProperty.call(object, property); &#125;;</span><br><span class="line">/******/</span><br><span class="line">/******/ // __webpack_public_path__</span><br><span class="line">/******/ __webpack_require__.p = &quot;&quot;;</span><br><span class="line">/******/</span><br><span class="line">/******/</span><br><span class="line">/******/ // Load entry module and return exports</span><br><span class="line">/******/ return __webpack_require__(__webpack_require__.s = &quot;./src/index.js&quot;);</span><br><span class="line">/******/ &#125;)</span><br><span class="line">/************************************************************************/</span><br><span class="line">/******/ (&#123;</span><br><span class="line"></span><br><span class="line">/***/ &quot;./src/index.js&quot;:</span><br><span class="line">/*!**********************!*\</span><br><span class="line">  !*** ./src/index.js ***!</span><br><span class="line">  \**********************/</span><br><span class="line">/*! no static exports found */</span><br><span class="line">/***/ (function(module, exports, __webpack_require__) &#123;</span><br><span class="line"></span><br><span class="line">eval(&quot;// index.js\nconst func = __webpack_require__(/*! ./lib */ \&quot;./src/lib.js\&quot;)\n\nconst result = func()\n// print hello\nconsole.log(result)\n\n//# sourceURL=webpack:///./src/index.js?&quot;);</span><br><span class="line"></span><br><span class="line">/***/ &#125;),</span><br><span class="line"></span><br><span class="line">/***/ &quot;./src/lib.js&quot;:</span><br><span class="line">/*!********************!*\</span><br><span class="line">  !*** ./src/lib.js ***!</span><br><span class="line">  \********************/</span><br><span class="line">/*! no static exports found */</span><br><span class="line">/***/ (function(module, exports) &#123;</span><br><span class="line"></span><br><span class="line">eval(&quot;module.exports = function () &#123;\n    return \&quot;hello webpack!\&quot;\n&#125;\n\n//# sourceURL=webpack:///./src/lib.js?&quot;);</span><br><span class="line"></span><br><span class="line">/***/ &#125;)</span><br><span class="line"></span><br><span class="line">/******/ &#125;);</span><br></pre></td></tr></table></figure><p>我们先将此文件的主要部分拿出来看</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><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><br><span class="line">(function (modules) &#123;</span><br><span class="line">    var installedModules = &#123;&#125;</span><br><span class="line"></span><br><span class="line">    function __webpack_require__ (moduleId) &#123;</span><br><span class="line">        if (installedModules[moduleId]) &#123;</span><br><span class="line">            return installedModules[moduleId].exports</span><br><span class="line">        &#125;</span><br><span class="line">        var module = installedModules[moduleId] = &#123;</span><br><span class="line">            i: moduleId,</span><br><span class="line">            l: false,</span><br><span class="line">            exports: &#123;&#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)</span><br><span class="line">        module.l = true</span><br><span class="line">        return module.exports</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return __webpack_require__(__webpack_require__.s = &apos;./src/index.js&apos;)</span><br><span class="line">&#125;)</span><br><span class="line">(&#123;</span><br><span class="line">    &apos;./src/index.js&apos;:</span><br><span class="line">        (function (module, exports, __webpack_require__) &#123;</span><br><span class="line">            eval(</span><br><span class="line">                &apos;// index.js\nconst func = __webpack_require__(/*! ./lib */ &quot;./src/lib.js&quot;)\n\nconst result = func()\n// print hello\nconsole.log(result)\n\n//# sourceURL=webpack:///./src/index.js?&apos;)</span><br><span class="line">        &#125;),</span><br><span class="line">    &apos;./src/lib.js&apos;:</span><br><span class="line">        (function (module, exports) &#123;</span><br><span class="line">            eval(</span><br><span class="line">                &apos;module.exports = function () &#123;\n    return &quot;hello webpack!&quot;\n&#125;\n\n//# sourceURL=webpack:///./src/lib.js?&apos;)</span><br><span class="line">        &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>这是一个立即执行函数，首先申明了 installedModules 对象，这是已安装的模块集合，之后定义了一个函数 <strong>webpack_require</strong> ，此函数用来获取模块的引用，最后 return 了此函数，参数为入口，moduleId = ‘./src/index.js’<br>modules 即为</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &apos;./src/index.js&apos;:</span><br><span class="line">        (function (module, exports, __webpack_require__) &#123;</span><br><span class="line">            eval(</span><br><span class="line">                &apos;// index.js\nconst func = __webpack_require__(/*! ./lib */ &quot;./src/lib.js&quot;)\n\nconst result = func()\n// print hello\nconsole.log(result)\n\n//# sourceURL=webpack:///./src/index.js?&apos;)</span><br><span class="line">        &#125;),</span><br><span class="line">    &apos;./src/lib.js&apos;:</span><br><span class="line">        (function (module, exports) &#123;</span><br><span class="line">            eval(</span><br><span class="line">                &apos;module.exports = function () &#123;\n    return &quot;hello webpack!&quot;\n&#125;\n\n//# sourceURL=webpack:///./src/lib.js?&apos;)</span><br><span class="line">        &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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">modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)</span><br></pre></td></tr></table></figure><p>module 即模块，modules[moduleId] 即为</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">(function (module, exports, __webpack_require__) &#123;</span><br><span class="line">           eval(</span><br><span class="line">               &apos;// index.js\nconst func = __webpack_require__(/*! ./lib */ &quot;./src/lib.js&quot;)\n\nconst result = func()\n// print hello\nconsole.log(result)\n\n//# sourceURL=webpack:///./src/index.js?&apos;)</span><br><span class="line">       &#125;)</span><br></pre></td></tr></table></figure><p>module.exports 为 this 上下文环境，该函数执行中，第一行即调用</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">const func = __webpack_require__(&quot;./src/lib.js&quot;)</span><br></pre></td></tr></table></figure><p><strong>webpack_require</strong>(&quot;./src/lib.js&quot;) 即调用了此函数</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">(function (module, exports) &#123;</span><br><span class="line">            eval(</span><br><span class="line">                &apos;module.exports = function () &#123;\n    return &quot;hello webpack!&quot;\n&#125;\n\n//# sourceURL=webpack:///./src/lib.js?&apos;)</span><br><span class="line">        &#125;)</span><br></pre></td></tr></table></figure><p>最后返回 module.exports 即 lib 里面的导出函数。再往后执行便是</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">const result = func()</span><br><span class="line">// print hello</span><br><span class="line">console.log(result)</span><br></pre></td></tr></table></figure><p>此刻基本已经将关系理顺了，此后如果在调用模块，则世界从 installedModules 中直接返回。</p><h4 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h4><p>此文只是分析了简单的模块引用，需要仔细分析才能融会贯通。</p>]]></content>
    
    <summary type="html">
    
      算是一个h5开发了，虽然没写过什么完整的前端页面，但接触前端也有段时间了，对于一个合格的前端开发者而言，搞懂 webpack 打包原理还是比较重要的
    
    </summary>
    
      <category term="JavaScript" scheme="https://wzes.github.io/categories/JavaScript/"/>
    
    
      <category term="JavaScript" scheme="https://wzes.github.io/tags/JavaScript/"/>
    
      <category term="Webpack" scheme="https://wzes.github.io/tags/Webpack/"/>
    
  </entry>
  
  <entry>
    <title>Android PageTransformer 源码解析</title>
    <link href="https://wzes.github.io/2019/07/05/Android/PageTransformer/"/>
    <id>https://wzes.github.io/2019/07/05/Android/PageTransformer/</id>
    <published>2019-07-05T12:35:00.000Z</published>
    <updated>2019-09-02T12:19:17.763Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h3><p>作为一个很久没写过 Android 业务的人，心里有点慌了，于是拿起 Android Studio，还是找点东西学习一下，并且记录一下。一直觉得 ViewPager 是个好东西，偶然间看到一些很好的案例，很酷炫的翻页效果。直到了解了这个东西的实现原来没有想象中的那么复杂，但如果没有深刻理解，还是很难写出酷炫的效果的。于是， ViewPager Transformer 的学习就提上了日程。</p><h3 id="hello-world"><a class="markdownIt-Anchor" href="#hello-world"></a> Hello World</h3><p>首先我们来实现一个场景，很简单，只需要一个 ViewPager，然后给他设置几页用来展现效果就行了</p><h6 id="layout-文件"><a class="markdownIt-Anchor" href="#layout-文件"></a> Layout 文件</h6><p>只需要放入一个 ViewPager</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></pre></td><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;</span><br><span class="line">&lt;androidx.constraintlayout.widget.ConstraintLayout</span><br><span class="line">        xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;</span><br><span class="line">        xmlns:tools=&quot;http://schemas.android.com/tools&quot;</span><br><span class="line">        xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;</span><br><span class="line">        android:layout_width=&quot;match_parent&quot;</span><br><span class="line">        android:layout_height=&quot;match_parent&quot;</span><br><span class="line">        tools:context=&quot;.MainActivity&quot;&gt;</span><br><span class="line">    &lt;androidx.viewpager.widget.ViewPager</span><br><span class="line">            android:layout_width=&quot;match_parent&quot;</span><br><span class="line">            android:layout_height=&quot;match_parent&quot;</span><br><span class="line">            android:id=&quot;@+id/view_pager&quot;&gt;</span><br><span class="line">    &lt;/androidx.viewpager.widget.ViewPager&gt;</span><br><span class="line"></span><br><span class="line">&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</span><br></pre></td></tr></table></figure><p>之后，我们创建一个 PageAdapter，可以直接使用 FragmentPagerAdapter，getItem 返回一个 Fragment 就好了</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><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">class PageAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) &#123;</span><br><span class="line"></span><br><span class="line">    override fun getItem(position: Int): Fragment &#123;</span><br><span class="line">        return PageFragment(&quot;Fragment $position&quot;, position)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    override fun getCount(): Int &#123;</span><br><span class="line">        return 4</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @SuppressLint(&quot;ValidFragment&quot;)</span><br><span class="line">    class PageFragment(private var content: String, private var position: Int) : Fragment() &#123;</span><br><span class="line"></span><br><span class="line">        private val colors = Arrays.asList(Color.GRAY, Color.RED, Color.BLUE, Color.YELLOW)!!</span><br><span class="line">        override fun onActivityCreated(savedInstanceState: Bundle?) &#123;</span><br><span class="line">            super.onActivityCreated(savedInstanceState)</span><br><span class="line">            text_view.text = content</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        override fun onCreateView(</span><br><span class="line">            inflater: LayoutInflater,</span><br><span class="line">            container: ViewGroup?,</span><br><span class="line">            savedInstanceState: Bundle?</span><br><span class="line">        ): View? &#123;</span><br><span class="line">            val view = inflater.inflate(R.layout.tab_item, container, false)</span><br><span class="line">            view.setBackgroundColor(colors[position % colors.size])</span><br><span class="line">            view.tag = &quot;$position&quot;</span><br><span class="line">            return view</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>在我们的 Activity 中，设置 view_pager，这里需要注意的是，由于我们使用了 FragmentPagerAdapter，所以我们在展示是如果需要展示多页的话，必须设置为 <strong>offscreenPageLimit</strong> 一个比较大的值，以便 ViewPager 能够渲染足够多的页面满足我们的需求。<br>最后为 ViewPager 设置一个 PageTransformer</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></pre></td><td class="code"><pre><span class="line">class MainActivity : AppCompatActivity() &#123;</span><br><span class="line"></span><br><span class="line">    override fun onCreate(savedInstanceState: Bundle?) &#123;</span><br><span class="line">        super.onCreate(savedInstanceState)</span><br><span class="line">        setContentView(R.layout.activity_main)</span><br><span class="line"></span><br><span class="line">        view_pager.adapter = PageAdapter(supportFragmentManager)</span><br><span class="line"></span><br><span class="line">        view_pager.offscreenPageLimit = 4</span><br><span class="line"></span><br><span class="line">        view_pager.setPageTransformer(true, ViewPagerTransformer(TransformType.DEPTH))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一个比较简单的 PageTransformer 的实现如下</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">class ViewPagerTransformer : ViewPager.PageTransformer  &#123;</span><br><span class="line">    override fun transformPage(page: View, position: Float) &#123;</span><br><span class="line">          page.rotationY = position * -30f</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>效果如下，翻页时会根据位置修改页面的显示，页面将会绕 Y 轴进行旋转一定的角度，效果很赞吧！！！只用了一点代码<br><img src="https://upload-images.jianshu.io/upload_images/7117641-ca06163fe13e1f15.gif?imageMogr2/auto-orient/strip" alt="页面翻转"></p><h4 id="viewpagerpagetransformer"><a class="markdownIt-Anchor" href="#viewpagerpagetransformer"></a> ViewPager.PageTransformer</h4><h5 id="定义"><a class="markdownIt-Anchor" href="#定义"></a> 定义</h5><p>PageTransfomer 接口只有一个方法，该方法有两个参数，一个是 page，指的是 ViewPage 的一个内容页</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></pre></td><td class="code"><pre><span class="line">public interface PageTransformer &#123;</span><br><span class="line">    /**</span><br><span class="line">     * Apply a property transformation to the given page.</span><br><span class="line">     *</span><br><span class="line">     * @param page Apply the transformation to this page</span><br><span class="line">     * @param position Position of page relative to the current front-and-center</span><br><span class="line">     *                 position of the pager. 0 is front and center. 1 is one full</span><br><span class="line">     *                 page position to the right, and -1 is one page position to the left.</span><br><span class="line">     */</span><br><span class="line">    void transformPage(@NonNull View page, float position);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>position</strong> 指的是该内容页的位置偏移，该偏移是相对的，具体表示请看一张图，页面静止时，以屏幕左边界为 0，屏幕内的页面 position 为0，左边为-1，依次递减，右侧为1，依次递增。当屏幕滑动时，page2只出现一半，此时，page2 的 position 为-0.5，page3 为0.5，依次类推可得出其他page 回调的 position 值<br><img src="https://upload-images.jianshu.io/upload_images/7117641-a9868f1c4f6cd870.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Page Transformer"></p><h4 id="实践"><a class="markdownIt-Anchor" href="#实践"></a> 实践</h4><p>1、淡入淡出 效果<br><img src="https://upload-images.jianshu.io/upload_images/7117641-d9f22c656de352bc.gif?imageMogr2/auto-orient/strip" alt="淡入淡出"></p><p>页面随着位置改变透明度，alpha = 0 是透明，alpha = 1 是不透明</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></pre></td><td class="code"><pre><span class="line">if (position &lt;= -1.0f || position &gt;= 1.0f) &#123;</span><br><span class="line">    page.alpha = 0.0f</span><br><span class="line">&#125; else if (position == 0.0f) &#123;</span><br><span class="line">    page.alpha = 1.0f</span><br><span class="line">&#125; else &#123;</span><br><span class="line">    page.alpha = 1.0f - Math.abs(position)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、缩放变大效果<br><img src="https://upload-images.jianshu.io/upload_images/7117641-af318d5c1ab4d301.gif?imageMogr2/auto-orient/strip" alt="缩放变大"></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><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">if (position &gt; 0 &amp;&amp; position &lt; 1) &#123;</span><br><span class="line">      page.alpha = 1 - position</span><br><span class="line">      page.scaleXY = 0.85f + (1 - 0.85f) * (1 - Math.abs(position))</span><br><span class="line">      page.translationX = page.width * -position</span><br><span class="line">&#125; else &#123;</span><br><span class="line">      page.alpha = 1f</span><br><span class="line">      page.scaleXY = 1f</span><br><span class="line">      page.translationX = 0f</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="更多效果"><a class="markdownIt-Anchor" href="#更多效果"></a> 更多效果</h4><p>等你去发现</p><h4 id="源码解析"><a class="markdownIt-Anchor" href="#源码解析"></a> 源码解析</h4><p>其实这个原理很简单，在每一次滚动的时候，在 ViewPager 内部，计算出 每一个view 的 position ，并且调用这个接口的方法就可以实现了<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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">protected void onPageScrolled(int position, float offset, int offsetPixels) &#123;</span><br><span class="line">     // 省略.......</span><br><span class="line">     if (mPageTransformer != null) &#123;</span><br><span class="line">         final int scrollX = getScrollX();</span><br><span class="line">         final int childCount = getChildCount();</span><br><span class="line">         for (int i = 0; i &lt; childCount; i++) &#123;</span><br><span class="line">             final View child = getChildAt(i);</span><br><span class="line">             final LayoutParams lp = (LayoutParams) child.getLayoutParams();</span><br><span class="line"></span><br><span class="line">             if (lp.isDecor) continue;</span><br><span class="line">             final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();</span><br><span class="line">             mPageTransformer.transformPage(child, transformPos);</span><br><span class="line">         &#125;</span><br><span class="line">     &#125;</span><br><span class="line">     // ....</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>首先判断 mPageTransformer 是否存在，存在的话就可以调用了，获取 scrollX，根据 childCount 对每一个 view 执行 mPageTransformer.transformPage 方法 transformPos 是由 (float) (child.getLeft() - scrollX) / getClientWidth() 计算得出。此处使用 getLeft - scrollX 计算验证了我们对想法。</p><p><a href="https://github.com/wzes/vptfdemo/tree/master" target="_blank" rel="noopener">demo</a></p><h4 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h4><p>看似复杂的功能，其实没那么复杂，静下心来研究，原来这么简单</p>]]></content>
    
    <summary type="html">
    
      作为一个很久没写过 Android 业务的人，心里有点慌了，于是拿起 Android Studio，还是找点东西学习一下，并且记录一下。一直觉得 ViewPager 是个好东西，偶然间看到一些很好的案例，很酷炫的翻页效果。直到了解了这个东西的实现原来没有想象中的那么复杂
    
    </summary>
    
      <category term="Android" scheme="https://wzes.github.io/categories/Android/"/>
    
    
      <category term="Android" scheme="https://wzes.github.io/tags/Android/"/>
    
      <category term="ViewPager" scheme="https://wzes.github.io/tags/ViewPager/"/>
    
  </entry>
  
</feed>
