<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>lizhen&#39;s blog</title>
  
  <subtitle>人生苦短，我再睡会</subtitle>
  <link href="https://lz5z.com/atom.xml" rel="self"/>
  
  <link href="https://lz5z.com/"/>
  <updated>2026-05-07T14:50:53.976Z</updated>
  <id>https://lz5z.com/</id>
  
  <author>
    <name>lizhen</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>[真] Node多线程</title>
    <link href="https://lz5z.com/%E7%9C%9F-Node%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    <id>https://lz5z.com/%E7%9C%9F-Node%E5%A4%9A%E7%BA%BF%E7%A8%8B/</id>
    <published>2019-01-31T11:52:19.000Z</published>
    <updated>2026-05-07T14:50:53.976Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文测试使用环境：<br>系统：macOS Mojave 10.14.2<br>CPU：4 核 2.3 GHz<br>Node: 10.15.1</p></blockquote><h2 id="从-Node-线程说起">从 Node 线程说起</h2><p>一般人理解 Node 是单线程的，所以 Node 启动后线程数应该为 1，我们做实验看一下。</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>())</span><br><span class="line">&#125;, <span class="number">3000</span>)</span><br></pre></td></tr></table></figure><img src="/assets/img/node_thread.png" alt="node_thread"><p>可以看到 Node 进程占用了 7 个线程。为什么会有 7 个线程呢？</p><p>我们都知道，Node 中最核心的是 v8 引擎，在 Node 启动后，会创建 v8 的实例，这个实例是多线程的。</p><ul><li>主线程：编译、执行代码。</li><li>编译/优化线程：在主线程执行的时候，可以优化代码。</li><li>分析器线程：记录分析代码运行时间，为 Crankshaft 优化代码执行提供依据。</li><li>垃圾回收的几个线程。</li></ul><span id="more"></span><p>所以大家常说的 Node 是单线程的指的是 JavaScript 的执行是单线程的，但 Javascript 的宿主环境，无论是 Node 还是浏览器都是多线程的。</p><blockquote><p>Node 有两个编译器：<br>full-codegen：简单快速地将 js 编译成简单但是很慢的机械码。<br>Crankshaft：比较复杂的实时优化编译器，编译高性能的可执行代码。</p></blockquote><h3 id="某些异步-IO-会占用额外的线程">某些异步 IO 会占用额外的线程</h3><p>还是上面那个例子，我们在定时器执行的同时，去读一个文件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>())</span><br><span class="line">&#125;, <span class="number">3000</span>)</span><br><span class="line"></span><br><span class="line">fs.<span class="title function_">readFile</span>(<span class="string">&#x27;./index.html&#x27;</span>, <span class="function">() =&gt;</span> &#123;&#125;)</span><br></pre></td></tr></table></figure><img src="/assets/img/node_thread_1.png" alt="node_thread"><p>线程数量变成了 11 个，这是因为在 Node 中有一些 IO 操作（DNS，FS）和一些 CPU 密集计算（Zlib，Crypto）会启用 Node 的线程池，而线程池默认大小为 4，因为线程数变成了 11。</p><p>我们可以手动更改线程池默认大小：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">process.<span class="property">env</span>.<span class="property">UV_THREADPOOL_SIZE</span> = <span class="number">64</span></span><br></pre></td></tr></table></figure><p>一行代码轻松把线程变成 71 😊</p><img src="/assets/img/node_thread_2.png" alt="node_thread"><h2 id="cluster-是多线程吗？">cluster 是多线程吗？</h2><p>Node 的单线程也带来了一些问题，比如对 cpu 利用不足，某个未捕获的异常可能会导致整个程序的退出等等。因为 Node 中提供了 cluster 模块，cluster 实现了对 child_process 的封装，通过 fork 方法创建子进程的方式实现了多进程模型。比如我们最常用到的 pm2 就是其中最优秀的代表。</p><p>我们看一个 cluster 的 demo：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cluster = <span class="built_in">require</span>(<span class="string">&#x27;cluster&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> numCPUs = <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>).<span class="title function_">cpus</span>().<span class="property">length</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (cluster.<span class="property">isMaster</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`主进程 <span class="subst">$&#123;process.pid&#125;</span> 正在运行`</span>);</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; numCPUs; i++) &#123;</span><br><span class="line">    cluster.<span class="title function_">fork</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  cluster.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="function">(<span class="params">worker, code, signal</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`工作进程 <span class="subst">$&#123;worker.process.pid&#125;</span> 已退出`</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">  <span class="comment">// 工作进程可以共享任何 TCP 连接。</span></span><br><span class="line">  <span class="comment">// 在本例子中，共享的是 HTTP 服务器。</span></span><br><span class="line">  http.<span class="title function_">createServer</span>(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    res.<span class="title function_">writeHead</span>(<span class="number">200</span>);</span><br><span class="line">    res.<span class="title function_">end</span>(<span class="string">&#x27;Hello World&#x27;</span>);</span><br><span class="line">  &#125;).<span class="title function_">listen</span>(<span class="number">8000</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`工作进程 <span class="subst">$&#123;process.pid&#125;</span> 已启动`</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个时候看下活动监视器：</p><img src="/assets/img/node_thread_3.png" alt="node_thread"><p>一共有 9 个进程，其中一个主进程，cpu 个数 * cpu 核数 = 2 * 4 = 8 个 子进程。</p><p>所以无论 child_process 还是 cluster，都不是多线程模型，而是多进程模型。虽然开发者意识到了单线程模型的问题，但是没有从根本上解决问题，而且提供了一个多进程的方式来模拟多线程。从前面的实验可以看出，虽然 Node （V8）本身是具有多线程的能力的，但是开发者并不能很好的利用这个能力，更多的是由 Node 底层提供的一些方式来使用多线程。Node 官方说：</p><blockquote><p>You can use the built-in Node Worker Pool by developing a C++ addon. On older versions of Node, build your C++ addon using NAN, and on newer versions use N-API. node-webworker-threads offers a JavaScript-only way to access Node’s Worker Pool.</p></blockquote><p>但是对于 JavaScript 开发者，一直没有一个标准的、好用的方式来使用 Node 的多线程能力。</p><h2 id="真-Node-多线程">真 - Node 多线程</h2><p>直到 Node 10.5.0 的发布，官方才给出了一个实验性质的模块 <a href="http://nodejs.cn/api/worker_threads.html">worker_threads</a> 给 Node 提供真正的多线程能力。</p><p>先看下简单的 demo：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  isMainThread,</span><br><span class="line">  parentPort,</span><br><span class="line">  workerData,</span><br><span class="line">  threadId,</span><br><span class="line">  <span class="title class_">MessageChannel</span>,</span><br><span class="line">  <span class="title class_">MessagePort</span>,</span><br><span class="line">  <span class="title class_">Worker</span></span><br><span class="line">&#125; = <span class="built_in">require</span>(<span class="string">&#x27;worker_threads&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">mainThread</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> worker = <span class="keyword">new</span> <span class="title class_">Worker</span>(__filename, &#123; <span class="attr">workerData</span>: i &#125;);</span><br><span class="line">    worker.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="function"><span class="params">code</span> =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`main: worker stopped with exit code <span class="subst">$&#123;code&#125;</span>`</span>); &#125;);</span><br><span class="line">    worker.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`main: receive <span class="subst">$&#123;msg&#125;</span>`</span>);</span><br><span class="line">      worker.<span class="title function_">postMessage</span>(msg + <span class="number">1</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">workerThread</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`worker: workerDate <span class="subst">$&#123;workerData&#125;</span>`</span>);</span><br><span class="line">  parentPort.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`worker: receive <span class="subst">$&#123;msg&#125;</span>`</span>);</span><br><span class="line">  &#125;),</span><br><span class="line">  parentPort.<span class="title function_">postMessage</span>(workerData);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line">  <span class="title function_">mainThread</span>();</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="title function_">workerThread</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码在主线程中开启五个子线程，并且主线程向子线程发送简单的消息。</p><p>由于 worker_thread 目前仍然处于实验阶段，所以启动时需要增加 <code>--experimental-worker</code> flag，运行后观察活动监视器：</p><img src="/assets/img/node_thread_4.png" alt="node_thread"><p>不多不少，正好多了五个子线程。😊</p><h3 id="worker-thread-模块"><a href="https://nodejs.org/api/worker_threads.html">worker_thread</a> 模块</h3><p>worker_thread <a href="https://github.com/nodejs/node/blob/master/lib/worker_threads.js">核心代码</a></p><p>worker_thread 模块中有 4 个对象和 2 个类。</p><ul><li>isMainThread: 是否是主线程，源码中是通过 <code>threadId === 0</code> 进行判断的。</li><li>MessagePort: 用于线程之间的通信，继承自 EventEmitter。</li><li>MessageChannel: 用于创建异步、双向通信的通道实例。</li><li>threadId: 线程 ID。</li><li>Worker: 用于在主线程中创建子线程。第一个参数为 filename，表示子线程执行的入口。</li><li>parentPort: 在 worker 线程里是表示父进程的 MessagePort 类型的对象，在主线程里为 null</li><li>workerData: 用于在主进程中向子进程传递数据（data 副本）</li></ul><p>来看一个进程通信的例子：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> assert = <span class="built_in">require</span>(<span class="string">&#x27;assert&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">  <span class="title class_">Worker</span>,</span><br><span class="line">  <span class="title class_">MessageChannel</span>,</span><br><span class="line">  <span class="title class_">MessagePort</span>,</span><br><span class="line">  isMainThread,</span><br><span class="line">  parentPort</span><br><span class="line">&#125; = <span class="built_in">require</span>(<span class="string">&#x27;worker_threads&#x27;</span>);</span><br><span class="line"><span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line">  <span class="keyword">const</span> worker = <span class="keyword">new</span> <span class="title class_">Worker</span>(__filename);</span><br><span class="line">  <span class="keyword">const</span> subChannel = <span class="keyword">new</span> <span class="title class_">MessageChannel</span>();</span><br><span class="line">  worker.<span class="title function_">postMessage</span>(&#123; <span class="attr">hereIsYourPort</span>: subChannel.<span class="property">port1</span> &#125;, [subChannel.<span class="property">port1</span>]);</span><br><span class="line">  subChannel.<span class="property">port2</span>.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">value</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;received:&#x27;</span>, value);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  parentPort.<span class="title function_">once</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">value</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">assert</span>(value.<span class="property">hereIsYourPort</span> <span class="keyword">instanceof</span> <span class="title class_">MessagePort</span>);</span><br><span class="line">    value.<span class="property">hereIsYourPort</span>.<span class="title function_">postMessage</span>(<span class="string">&#x27;the worker is sending this&#x27;</span>);</span><br><span class="line">    value.<span class="property">hereIsYourPort</span>.<span class="title function_">close</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>更多详细用法可以查看<a href="https://nodejs.org/api/worker_threads.html">官方文档</a>。</p><h3 id="多进程-vs-多线程">多进程 vs 多线程</h3><p>根据大学课本上的说法：“进程是资源分配的最小单位，线程是CPU调度的最小单位”，这句话应付考试就够了，但是在实际工作中，我们还是要根据需求合理选择。</p><p>下面对比一下多线程与多进程：</p><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:center">多进程</th><th style="text-align:center">多线程</th><th style="text-align:left">比较</th></tr></thead><tbody><tr><td style="text-align:left">数据</td><td style="text-align:center">数据共享复杂，需要用IPC；数据是分开的，同步简单</td><td style="text-align:center">因为共享进程数据，数据共享简单，同步复杂</td><td style="text-align:left">各有千秋</td></tr><tr><td style="text-align:left">CPU、内存</td><td style="text-align:center">占用内存多，切换复杂，CPU利用率低</td><td style="text-align:center">占用内存少，切换简单，CPU利用率高</td><td style="text-align:left">多线程更好</td></tr><tr><td style="text-align:left">销毁、切换</td><td style="text-align:center">创建销毁、切换复杂，速度慢</td><td style="text-align:center">创建销毁、切换简单，速度很快</td><td style="text-align:left">多线程更好</td></tr><tr><td style="text-align:left">coding</td><td style="text-align:center">编码简单、调试方便</td><td style="text-align:center">编码、调试复杂</td><td style="text-align:left">多进程更好</td></tr><tr><td style="text-align:left">可靠性</td><td style="text-align:center">进程独立运行，不会相互影响</td><td style="text-align:center">线程同呼吸共命运</td><td style="text-align:left">多进程更好</td></tr><tr><td style="text-align:left">分布式</td><td style="text-align:center">可用于多机多核分布式，易于扩展</td><td style="text-align:center">只能用于多核分布式</td><td style="text-align:left">多进程更好</td></tr></tbody></table><p>上述比较仅表示一般情况，并不绝对。</p><p>work_thread 让 Node 有了真正的多线程能力，算是不小的进步。</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本文测试使用环境：&lt;br&gt;
系统：macOS Mojave 10.14.2&lt;br&gt;
CPU：4 核 2.3 GHz&lt;br&gt;
Node: 10.15.1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;从-Node-线程说起&quot;&gt;从 Node 线程说起&lt;/h2&gt;
&lt;p&gt;一般人理解 Node 是单线程的，所以 Node 启动后线程数应该为 1，我们做实验看一下。&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;setInterval&lt;/span&gt;(&lt;span class=&quot;function&quot;&gt;() =&amp;gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Date&lt;/span&gt;().&lt;span class=&quot;title function_&quot;&gt;getTime&lt;/span&gt;())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;, &lt;span class=&quot;number&quot;&gt;3000&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;img src=&quot;/assets/img/node_thread.png&quot; alt=&quot;node_thread&quot;&gt;
&lt;p&gt;可以看到 Node 进程占用了 7 个线程。为什么会有 7 个线程呢？&lt;/p&gt;
&lt;p&gt;我们都知道，Node 中最核心的是 v8 引擎，在 Node 启动后，会创建 v8 的实例，这个实例是多线程的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主线程：编译、执行代码。&lt;/li&gt;
&lt;li&gt;编译/优化线程：在主线程执行的时候，可以优化代码。&lt;/li&gt;
&lt;li&gt;分析器线程：记录分析代码运行时间，为 Crankshaft 优化代码执行提供依据。&lt;/li&gt;
&lt;li&gt;垃圾回收的几个线程。&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="cluster" scheme="https://lz5z.com/tags/cluster/"/>
    
    <category term="worker_threads" scheme="https://lz5z.com/tags/worker-threads/"/>
    
  </entry>
  
  <entry>
    <title>React Hooks 是什么</title>
    <link href="https://lz5z.com/React-Hooks/"/>
    <id>https://lz5z.com/React-Hooks/</id>
    <published>2019-01-07T14:34:24.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<p>最近在重构 BadJS 的管理页面，使用 TypeScript + React Hooks 的技术栈，趁这个机会好好理一理 React Hooks 那些事儿。</p><p>React Hooks 是 16.7.0-alpha 版本的新特性，安装即可享用。</p><h2 id="React-Hooks-简介">React Hooks 简介</h2><p>React Hooks 是对 React function 组件的一种扩展，通过一些特殊的函数，让无状态组件拥有状态组件才拥有的能力。</p><p>Hooks 是 React 函数组件中的一类特殊函数，通常以 use 开头，比如 useRef，useState，useReducer 等。通常在我们写 React 组件的时候，如果这个组件比较复杂，拥有自己的生命周期或者 state，就将其写成 class 组件；如果这个组件仅仅用来展示，就将其写成 function 组件。</p><p>React Hooks 使用 function 组件的写法，通过 useState 这样的 API 解决了 function 组件没有 state 的问题，通过 useEffect 来解决生命周期的问题，通过自定义 hooks 来复用业务逻辑。</p><h3 id="Hooks-解决哪些问题">Hooks 解决哪些问题</h3><ul><li>复用与状态有关的逻辑，之前引申出来 HOC 的概念，但是 HOC 会导致组件树的臃肿。</li><li>解决组件随着业务扩展变得难以维护的问题。</li><li>使用更容易理解并且对初学者更友好的 function 组件。</li></ul><span id="more"></span><h2 id="用法">用法</h2><p>Hooks 主要分三种：</p><ul><li>State hooks: 允许开发者在 function 组件中使用 state。</li><li>Effect hooks: 允许开发者在 function 组件中使用生命周期和 side effect。</li><li>Custom hooks: 自定义 hooks，可以在里面使用 State Hooks 和 Effect Hooks，达到组件之间逻辑复用。</li></ul><h3 id="State-Hooks">State Hooks</h3><p>看一下官方给出的 demo</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Example</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>You clicked &#123;count&#125; times<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(count + 1)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Click me</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的 useState 就是一个 hook，返回一个数组，第一个 count 表示一个 state，默认值为 0；第二个 setCount 相当于 class function 中的 setState，表示对 count 的更新操作。</p><p>这样写的好处是每个 state 独立管理，避免状态复杂的时候 state 臃肿。</p><p>基本用法描述如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(initialState);</span><br><span class="line"><span class="title function_">setState</span>(newState);</span><br></pre></td></tr></table></figure><p>useState 返回一个数组，第一个值是一个 stateful（有状态）的值，第二个值是更新这个状态值的函数。在初始渲染的时候，返回的 state 与 initialState 相同，在后续重新渲染时，useState 返回的第一个值将始终是应用更新后的最新 state(状态) 。</p><p>setState 函数用于更新 state(状态) ，它接受一个新的 state(状态) 值，并将组件排入重新渲染的队列。</p><p>由于 setState 使用函数式的更新方式，所以可以传递函数给 setState，该函数将接收先前的值，并返回更新的值。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params">&#123;initialCount&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(initialCount);</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      Count: &#123;count&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(0)&#125;&gt;Reset<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(prevCount =&gt; prevCount + 1)&#125;&gt;+<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(prevCount =&gt; prevCount - 1)&#125;&gt;-<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码可以使用上次的 state 来计算新的 state。与 React 类组件中的 setState 不同，useState 不会自动合并更新对象。所以如果要更新的 state 依赖前一个 state 的时候，需要使用对象扩展的方式：</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="title function_">setState</span>(<span class="function"><span class="params">prevState</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// Object.assign 也是可行的</span></span><br><span class="line">  <span class="keyword">return</span> &#123;...prevState, ...updatedValues&#125;;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>initialState 参数既可以是一个值，也可以是一个函数，如果初始状态是高开销的计算结果，则可以改为提供函数，该函数仅在初始渲染时执行：</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="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> initialState = <span class="title function_">someExpensiveComputation</span>(props);</span><br><span class="line">  <span class="keyword">return</span> initialState;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>initialState 参数只有在初始渲染期间才会使用，在随后的渲染中，它会被忽略。</p><h3 id="Effect-Hooks">Effect Hooks</h3><p>Effect Hooks 允许在组件中执行副作用（side effects），类似于类中的生命周期方法。通常我们需要在 componentDidMount 和 componentDidUpdate 写一些操作，可能是更新数据，也可能是更新 Dom。除此之外，我们还会在 componentWillUnmount 的时候解绑一些事件监听防止内存泄露。这些都导致了组件维护成本的增大。而在 function 组件中，又没有这些生命周期，因此 Hooks 使用 Effect Hooks 来取代这些生命周期，完成一部分能力。</p><p>看一下官方给出的动态更改 title 的 demo：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useEffect &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Example</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Similar to componentDidMount and componentDidUpdate:</span></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// Update the document title using the browser API</span></span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">title</span> = <span class="string">`You clicked <span class="subst">$&#123;count&#125;</span> times`</span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>You clicked &#123;count&#125; times<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(count + 1)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Click me</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 useEffect 之前，我们需要在 componentDidMount 和 componentDidUpdate 中同时去调用更改 title 的方法，以完成组件初始化的状态和数据更新的状态。useEffect 传递一个函数给 React，React 在组件渲染完成后和更新后调用这个函数来完成上述功能。默认情况下，它在第一次渲染之后和每次更新之后都运行。</p><p>可以将 useEffect Hook 视为 componentDidMount，componentDidUpdate 和 componentWillUnmount 的组合。</p><p>那 useEffect 什么时候执行 componentWillUnmount 的操作呢？</p><p>如果 useEffect 中返回一个函数，在 React 卸载当前的组件的时候，会执行这个函数，用于清理 effect。</p><p>对比需要清理 effect 和不需要清理 effect 的两种写法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">FriendStatusWithCounter</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">title</span> = <span class="string">`You clicked <span class="subst">$&#123;count&#125;</span> times`</span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> [isOnline, setIsOnline] = <span class="title function_">useState</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title class_">ChatAPI</span>.<span class="title function_">subscribeToFriendStatus</span>(props.<span class="property">friend</span>.<span class="property">id</span>, handleStatusChange);</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title class_">ChatAPI</span>.<span class="title function_">unsubscribeFromFriendStatus</span>(props.<span class="property">friend</span>.<span class="property">id</span>, handleStatusChange);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleStatusChange</span>(<span class="params">status</span>) &#123;</span><br><span class="line">    <span class="title function_">setIsOnline</span>(status.<span class="property">isOnline</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (<span class="comment">/*...*/</span>);</span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><p>通过跳过 Effect 来优化性能。</p><p>通常，每次组件渲染或者更新都去执行某些逻辑会带来无谓的消耗，所以我们经常会写这样的代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">componentDidUpdate</span>(<span class="params">prevProps, prevState</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (prevState.<span class="property">count</span> !== <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">count</span>) &#123;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">title</span> = <span class="string">`You clicked <span class="subst">$&#123;<span class="variable language_">this</span>.state.count&#125;</span> times`</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>只有组件更新前后的 state.count 发生变化的时候，才去更新 title。</p><p>用 Hooks 可以更简单地处理这个问题</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></pre></td><td class="code"><pre><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">title</span> = <span class="string">`You clicked <span class="subst">$&#123;count&#125;</span> times`</span>;</span><br><span class="line">&#125;, [count]); <span class="comment">// Only re-run the effect if count changes</span></span><br></pre></td></tr></table></figure><p>给 useEffect 传入第二个参数，这个参数是一个数组。如果组件重新渲染，只有这个 count 发生变化的时候 React 才会执行函数 中的内容，否则会直接跳过这个 effect。如果数组中是多个参数，那么只要其中一个发生变化，React 都会执行函数中的内容。</p><p>这也适用于具有清理阶段的 effect ：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title class_">ChatAPI</span>.<span class="title function_">subscribeToFriendStatus</span>(props.<span class="property">friend</span>.<span class="property">id</span>, handleStatusChange);</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title class_">ChatAPI</span>.<span class="title function_">unsubscribeFromFriendStatus</span>(props.<span class="property">friend</span>.<span class="property">id</span>, handleStatusChange);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, [props.<span class="property">friend</span>.<span class="property">id</span>]); <span class="comment">// Only re-subscribe if props.friend.id changes</span></span><br></pre></td></tr></table></figure><p>如果希望 effect 只在组件 componentDidMount 和 componentWillUnmount 的时候执行，则只需要给第二个参数传一个空数组即可。传入一个空数组 [] 输入告诉 React 你的 effect 不依赖于组件中的任何值，因此该 effect 仅在 mount 时运行，并且在 unmount 时执行清理，从不在更新时运行。</p><h2 id="Hooks-的规则">Hooks 的规则</h2><p>React Hooks 其实不仅仅是功能层面的增强，也给 React 注入了新的软件思想。这就是最近几年开始流行的 “约定大于配置”，比如 Hooks 函数必须使用 use 开头，还有接下来要讲的规则。前面在我的文章 <a href="https://lz5z.com/webpack4-new/">webpack4 新特性</a> 也提到了这个内容。</p><h3 id="只在顶层调用-Hooks">只在顶层调用 Hooks</h3><p>Hooks 只能在顶层调用，不要在循环，条件或嵌套函数中调用 Hook。原因是 React 需要保证每次组件渲染的时候都以相同的顺序调用 Hooks。</p><p>假如一个组件中有多个 Hooks，React 如何知道哪个 state(状态) 对应于哪个 useState 调用呢？答案是 React 依赖于调用 Hooks 的顺序。本质上来说 Hooks 就是数组（<a href="https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e">React hooks: not magic, just arrays</a>）。每次执行 useState 都会改变下标，如果 useState 被包裹在 condition 中，那每次执行的下标就可能对不上，导致 useState 更新错数据。</p><h3 id="只能在-React-Function-中调用-Hooks">只能在 React Function 中调用 Hooks</h3><p>Hooks 只能在 React function 组件中调用，或者在自定义 Hooks 中调用。通过遵循此规则，可以确保组件中的所有 stateful （有状态）逻辑在其源代码中清晰可见。</p><h3 id="eslint">eslint</h3><p><a href="https://www.npmjs.com/package/eslint-plugin-react-hooks">eslint-plugin-react-hooks</a> 可以保证强制执行上述两个规则。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install eslint-plugin-react-hooks@next</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Your ESLint configuration</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;plugins&quot;</span>: [</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="string">&quot;react-hooks&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;rules&quot;</span>: &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="string">&quot;react-hooks/rules-of-hooks&quot;</span>: <span class="string">&quot;error&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="自定义-Hooks">自定义 Hooks</h2><p>自定义 Hooks 就是将组件之间需要共有的逻辑抽出来写成单独的函数。与一般的函数的区别是，自定义 Hooks 是一个以 use 开头的函数，内部可以调用其它的 Hooks。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useEffect &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useFriendStatus</span>(<span class="params">friendID</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [isOnline, setIsOnline] = <span class="title function_">useState</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleStatusChange</span>(<span class="params">status</span>) &#123;</span><br><span class="line">    <span class="title function_">setIsOnline</span>(status.<span class="property">isOnline</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title class_">ChatAPI</span>.<span class="title function_">subscribeToFriendStatus</span>(friendID, handleStatusChange);</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title class_">ChatAPI</span>.<span class="title function_">unsubscribeFromFriendStatus</span>(friendID, handleStatusChange);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> isOnline;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> useFriendStatus;</span><br></pre></td></tr></table></figure><p>在另外一个组件中，将其引入后，就可以使用了</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;useFriendStatus&#125; <span class="keyword">from</span> <span class="string">&#x27;hooks/xxx.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">FriendListItem</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> isOnline = <span class="title function_">useFriendStatus</span>(props.<span class="property">friend</span>.<span class="property">id</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">color:</span> <span class="attr">isOnline</span> ? &#x27;<span class="attr">green</span>&#x27; <span class="attr">:</span> &#x27;<span class="attr">black</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;props.friend.name&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看出，自定义 Hooks 就是一个 JavaScript 函数而已，并没有什么特别。不过需要注意的是，自定义 Hooks 函数也必须以 use 开头（规约优先）。</p><h3 id="useContext">useContext</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> context = <span class="title function_">useContext</span>(<span class="title class_">Context</span>);</span><br></pre></td></tr></table></figure><p>接受一个 context（上下文）对象（从 React.createContext 返回的值）并返回当前 context 值，当提供程序更新时，此 Hook 将使用最新的 context 值触发重新渲染。</p><h3 id="useReducer">useReducer</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, initialState);</span><br></pre></td></tr></table></figure><p>useReducer 可以理解为 Redux 的 Hooks，接受的第一个参数是 <code>(state, action) =&gt; newState</code> 的 reducer，并返回与 dispatch 方法配对的当前状态。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> initialState = &#123;<span class="attr">count</span>: <span class="number">0</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;reset&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> initialState;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;increment&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123;<span class="attr">count</span>: state.<span class="property">count</span> + <span class="number">1</span>&#125;;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;decrement&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123;<span class="attr">count</span>: state.<span class="property">count</span> - <span class="number">1</span>&#125;;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="comment">// A reducer must always return a valid state.</span></span><br><span class="line">      <span class="comment">// Alternatively you can throw an error if an invalid action is dispatched.</span></span><br><span class="line">      <span class="keyword">return</span> state;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params">&#123;initialCount&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, &#123;<span class="attr">count</span>: initialCount&#125;);</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      Count: &#123;state.count&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123;type: &#x27;reset&#x27;&#125;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Reset</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123;type: &#x27;increment&#x27;&#125;)&#125;&gt;+<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123;type: &#x27;decrement&#x27;&#125;)&#125;&gt;-<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>useReducer 接受可选的第三个参数 initialAction，表示在组件初始化期间执行的操作。比如利用 props 传递的值来初始化 state 的操作。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = &#123;<span class="attr">count</span>: <span class="number">0</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;reset&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123;<span class="attr">count</span>: action.<span class="property">payload</span>&#125;;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;increment&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123;<span class="attr">count</span>: state.<span class="property">count</span> + <span class="number">1</span>&#125;;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;decrement&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123;<span class="attr">count</span>: state.<span class="property">count</span> - <span class="number">1</span>&#125;;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="comment">// A reducer must always return a valid state.</span></span><br><span class="line">      <span class="comment">// Alternatively you can throw an error if an invalid action is dispatched.</span></span><br><span class="line">      <span class="keyword">return</span> state;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params">&#123;initialCount&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(</span><br><span class="line">    reducer,</span><br><span class="line">    initialState,</span><br><span class="line">    &#123;<span class="attr">type</span>: <span class="string">&#x27;reset&#x27;</span>, <span class="attr">payload</span>: initialCount&#125;,</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      Count: &#123;state.count&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123;type: &#x27;reset&#x27;, payload: initialCount&#125;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Reset</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123;type: &#x27;increment&#x27;&#125;)&#125;&gt;+<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123;type: &#x27;decrement&#x27;&#125;)&#125;&gt;-<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="useRef">useRef</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> refContainer = <span class="title function_">useRef</span>(initialValue);</span><br></pre></td></tr></table></figure><p>useRef 返回一个可变的 ref 对象，通过 <code>.current</code> 属性对其进行访问，返回的对象将存留在整个组件的生命周期中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">TextInputWithFocusButton</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> inputEl = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onButtonClick</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// `current` points to the mounted text input element</span></span><br><span class="line">    inputEl.<span class="property">current</span>.<span class="title function_">focus</span>();</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;inputEl&#125;</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onButtonClick&#125;</span>&gt;</span>Focus the input<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="useImperativeMethods">useImperativeMethods</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">useImperativeMethods</span>(ref, createInstance, [inputs]);</span><br></pre></td></tr></table></figure><p>useImperativeMethods 与 forwardRef 共同使用，表示强制方法。通过 ref 将子组件的某个方法暴露给父组件。</p><p>子组件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">FancyInput</span>(<span class="params">props, ref</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> inputRef = <span class="title function_">useRef</span>();</span><br><span class="line">  <span class="title function_">useImperativeMethods</span>(ref, <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">focus</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      inputRef.<span class="property">current</span>.<span class="title function_">focus</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;));</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;inputRef&#125;</span> <span class="attr">...</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">FancyInput</span> = <span class="title function_">forwardRef</span>(<span class="title class_">FancyInput</span>);</span><br></pre></td></tr></table></figure><p>父组件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">FancyParent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> fancyInputRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    fancyInputRef.<span class="property">current</span>.<span class="title function_">focus</span>(); </span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">FancyInput</span> <span class="attr">ref</span>=<span class="string">&#123;fancyInputRef&#125;</span> /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="useLayoutEffect">useLayoutEffect</h3><p>用法与 useEffect 相同，但在所有 DOM 变化后同步触发。使用它来从 DOM 读取布局并同步重新渲染。 在浏览器绘制之前 useLayoutEffect 将同步刷新。</p><p>useEffect 中的函数会在 layout(布局) 和 paint(绘制) 后触发。 这使得它适用于许多常见的 side effects ，例如设置订阅和事件处理程序，因为大多数类型的工作不应阻止浏览器更新屏幕。</p><p>但是如果 effect 不能够推迟，比如要 DOM 改变必须在下一次绘制之前同步触发，使用 useLayoutEffect 会更加合适。</p><h2 id="Hooks-API">Hooks API</h2><p>参考 <a href="https://reactjs.org/docs/hooks-reference.html">Hooks API Reference</a></p><h2 id="总结">总结</h2><p>Hooks 通过设定某些特殊函数，在 React 组件内部“钩住”其生命周期和 state，帮助开发者解决一些逻辑复用的问题，通过自定义的 Hooks 对代码进行抽象，让我们写出更加符合函数式编程的规范，同时也减少了层层嵌套带来的问题。</p><h2 id="参考文档">参考文档</h2><ul><li><a href="https://juejin.im/post/5be8d3def265da611a476231">精读《React Hooks》</a></li><li><a href="https://reactjs.org/docs/hooks-intro.html">React docs - Introducing Hooks<br></a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近在重构 BadJS 的管理页面，使用 TypeScript + React Hooks 的技术栈，趁这个机会好好理一理 React Hooks 那些事儿。&lt;/p&gt;
&lt;p&gt;React Hooks 是 16.7.0-alpha 版本的新特性，安装即可享用。&lt;/p&gt;
&lt;h2 id=&quot;React-Hooks-简介&quot;&gt;React Hooks 简介&lt;/h2&gt;
&lt;p&gt;React Hooks 是对 React function 组件的一种扩展，通过一些特殊的函数，让无状态组件拥有状态组件才拥有的能力。&lt;/p&gt;
&lt;p&gt;Hooks 是 React 函数组件中的一类特殊函数，通常以 use 开头，比如 useRef，useState，useReducer 等。通常在我们写 React 组件的时候，如果这个组件比较复杂，拥有自己的生命周期或者 state，就将其写成 class 组件；如果这个组件仅仅用来展示，就将其写成 function 组件。&lt;/p&gt;
&lt;p&gt;React Hooks 使用 function 组件的写法，通过 useState 这样的 API 解决了 function 组件没有 state 的问题，通过 useEffect 来解决生命周期的问题，通过自定义 hooks 来复用业务逻辑。&lt;/p&gt;
&lt;h3 id=&quot;Hooks-解决哪些问题&quot;&gt;Hooks 解决哪些问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;复用与状态有关的逻辑，之前引申出来 HOC 的概念，但是 HOC 会导致组件树的臃肿。&lt;/li&gt;
&lt;li&gt;解决组件随着业务扩展变得难以维护的问题。&lt;/li&gt;
&lt;li&gt;使用更容易理解并且对初学者更友好的 function 组件。&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="React" scheme="https://lz5z.com/categories/React/"/>
    
    
    <category term="React" scheme="https://lz5z.com/tags/React/"/>
    
    <category term="Hooks" scheme="https://lz5z.com/tags/Hooks/"/>
    
  </entry>
  
  <entry>
    <title>Koa 源码研读</title>
    <link href="https://lz5z.com/Koa-Source/"/>
    <id>https://lz5z.com/Koa-Source/</id>
    <published>2018-12-05T15:06:57.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介">简介</h2><p><a href="https://koajs.com/">Koa</a> 是一个非常轻量的 web 开发框架，由 Express 团队打造。相较于 Express，Koa 使用 async 函数解决异步的问题，并且完全脱离中间件，非常优雅，而且 Koa 代码简洁友好，很适合初学者阅读。</p><h3 id="Koa-代码结构">Koa 代码结构</h3><img src="/assets/img/koa-source.png" alt="koa-source" style="max-width: 300px;display: block;"><span id="more"></span><p>可以看到 Koa 的结构非常简单，lib 文件夹下面放着 koa 的核心文件：</p><h3 id="application-js">application.js</h3><p>application 是 koa 的入口文件，export 出一个 Application 的类（继承自 events.Emitter）。application 有以下几个主要（public）的 api：</p><ul><li><p>listen: 实现对 http.createServer() 的封装，传入的参数 callback 中完成中间件合并，错误监听以及上下文的创建和 request 的处理。</p></li><li><p>use: 我们通常使用 app.use(function) 将中间件添加到应用程序。use 方法中，koa 将中间件（函数）添加到 this.middleware 数组中。</p></li><li><p>callback: koa-compose 将中间件组合在一起, 然后返回一个 request 回调函数，同时给 listen 作为回调。</p></li><li><p>toJSON: 返回一个去除私有属性（<code>_</code>开头）的对象。</p></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="keyword">class</span> <span class="title class_">Application</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Emitter</span> &#123;</span><br><span class="line">  <span class="title function_">listen</span>(<span class="params">...args</span>) &#123;</span><br><span class="line">    <span class="title function_">debug</span>(<span class="string">&#x27;listen&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> server = http.<span class="title function_">createServer</span>(<span class="variable language_">this</span>.<span class="title function_">callback</span>());</span><br><span class="line">    <span class="keyword">return</span> server.<span class="title function_">listen</span>(...args);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">use</span>(<span class="params">fn</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> fn !== <span class="string">&#x27;function&#x27;</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">&#x27;middleware must be a function!&#x27;</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">middleware</span>.<span class="title function_">push</span>(fn);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">callback</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> fn = <span class="title function_">compose</span>(<span class="variable language_">this</span>.<span class="property">middleware</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="title function_">listenerCount</span>(<span class="string">&#x27;error&#x27;</span>)) <span class="variable language_">this</span>.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="variable language_">this</span>.<span class="property">onerror</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">handleRequest</span> = (<span class="params">req, res</span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> ctx = <span class="variable language_">this</span>.<span class="title function_">createContext</span>(req, res);</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">handleRequest</span>(ctx, fn);</span><br><span class="line">    &#125;;</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">return</span> handleRequest;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="context-js">context.js</h3><p>context 是我们在使用 koa 中最常接触到的 ctx，就是一个暴露出来的对象。context 中实现了对 cookie 的 get set 操作，这也是我们可以直接使用 ctx 对 cookie 操作的原理。除此之外，ctx 中最重要的是 delegate，也就是委托。我们简单看一下代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">delegate</span>(proto, <span class="string">&#x27;response&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;attachment&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;redirect&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;remove&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;vary&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;set&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;append&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;flushHeaders&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;status&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;message&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;body&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;length&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;type&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;lastModified&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;etag&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;headerSent&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;writable&#x27;</span>);</span><br></pre></td></tr></table></figure><p>以上的 proto 就是 ctx，实现了对 response 对象的代理，比如我们可以通过使用 ctx.status 来访问 ctx.response.status。</p><p>同样的，request 上面的属性和方法也被代理到了 ctx 中：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="title function_">delegate</span>(proto, <span class="string">&#x27;request&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;acceptsLanguages&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;acceptsEncodings&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;acceptsCharsets&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;accepts&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;get&#x27;</span>)</span><br><span class="line">  .<span class="title function_">method</span>(<span class="string">&#x27;is&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;querystring&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;idempotent&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;socket&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;search&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;method&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;query&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;path&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;url&#x27;</span>)</span><br><span class="line">  .<span class="title function_">access</span>(<span class="string">&#x27;accept&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;origin&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;href&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;subdomains&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;protocol&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;host&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;hostname&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;URL&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;header&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;headers&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;secure&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;stale&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;fresh&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;ips&#x27;</span>)</span><br><span class="line">  .<span class="title function_">getter</span>(<span class="string">&#x27;ip&#x27;</span>);</span><br></pre></td></tr></table></figure><p>ctx.hostname 即是 ctx.request.hostname。</p><h3 id="request-js-response-js">request.js &amp;&amp; response.js</h3><p>request.js 和 response.js 中完成对 Koa Request/Response 对象的封装，可以通过 <a href="http://request.xxx/response.xxx">request.xxx/response.xxx</a> 对其进行操作。其中使用了很多 get 和 set 方法。</p><h2 id="实现一个简单的-moa">实现一个简单的 moa</h2><ul><li>首先需要完成对 http 模块的封装，可以使用创建服务器。</li><li>然后完成 request 和 response 对象的封装，以及将其代理到 context 对象上。</li><li>然后需要处理中间件以及实现洋葱模型。</li><li>最后需要完成对错误的处理和异常捕获。</li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;简介&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://koajs.com/&quot;&gt;Koa&lt;/a&gt; 是一个非常轻量的 web 开发框架，由 Express 团队打造。相较于 Express，Koa 使用 async 函数解决异步的问题，并且完全脱离中间件，非常优雅，而且 Koa 代码简洁友好，很适合初学者阅读。&lt;/p&gt;
&lt;h3 id=&quot;Koa-代码结构&quot;&gt;Koa 代码结构&lt;/h3&gt;
&lt;img src=&quot;/assets/img/koa-source.png&quot; alt=&quot;koa-source&quot; style=&quot;max-width: 300px;display: block;&quot;&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="Koa" scheme="https://lz5z.com/tags/Koa/"/>
    
    <category term="node" scheme="https://lz5z.com/tags/node/"/>
    
  </entry>
  
  <entry>
    <title>使用 SRI 解决 CDN 劫持</title>
    <link href="https://lz5z.com/SRI-CDN/"/>
    <id>https://lz5z.com/SRI-CDN/</id>
    <published>2018-11-25T05:05:48.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h2 id="SRI-简介">SRI 简介</h2><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/Security/%E5%AD%90%E8%B5%84%E6%BA%90%E5%AE%8C%E6%95%B4%E6%80%A7">SRI</a> 全称 Subresource Integrity - 子资源完整性，是指浏览器通过验证资源的完整性（通常从 CDN 获取）来判断其是否被篡改的安全特性。</p><p>通过给 link 标签或者 script 标签增加 integrity 属性即可开启 SRI 功能，比如：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span> <span class="attr">src</span>=<span class="string">&quot;//s.url.cn/xxxx/xxx.js?_offline=1&quot;</span> <span class="attr">integrity</span>=<span class="string">&quot;sha256-mY9nzNMPPf8oL3CJss7THIEoXAC2ToW1tEX0NBhMvuw= sha384-ncIKElSEk2OR3YfjNLRSY35mzt0CUwrpNDVS//iD3dF9vxrWeZ7WPlAPJTqGkSai&quot;</span> <span class="attr">crossorigin</span>=<span class="string">&quot;anonymous&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>integrity 值分成两个部分，第一部分指定哈希值的生成算法（sha256、sha384 及 sha512），第二部分是经过 base64 编码的实际哈希值，两者之间通过一个短横（-）分割。integrity 值可以包含多个由空格分隔的哈希值，只要文件匹配其中任意一个哈希值，就可以通过校验并加载该资源。上述例子中我使用了 sha256 和 sha384 两张 hash 方案。</p><span id="more"></span><blockquote><p>备注：<code>crossorigin=&quot;anonymous&quot;</code> 的作用是引入跨域脚本，在 HTML5 中有一种方式可以获取到跨域脚本的错误信息，首先跨域脚本的服务器必须通过 Access-Controll-Allow-Origin 头信息允许当前域名可以获取错误信息，然后是当前域名的 script 标签也必须声明支持跨域，也就是 crossorigin 属性。link、img 等标签均支持跨域脚本。如果上述两个条件无法满足的话， 可以使用 <code>try catch</code> 方案。</p></blockquote><h2 id="为什么要使用-SRI">为什么要使用 SRI</h2><p>在 Web 开发中，使用 CDN 资源可以有效减少网络请求时间，但是使用 CDN 资源也存在一个问题，CDN 资源存在于第三方服务器，在安全性上并不完全可控。</p><p>CDN 劫持是一种非常难以定位的问题，首先劫持者会利用某种算法或者随机的方式进行劫持（狡猾大大滴），所以非常难以复现，很多用户出现后刷新页面就不再出现了。之前公司有同事做游戏的下载器就遇到这个问题，用户下载游戏后解压不能玩，后面通过文件逐一对比找到原因，原来是 CDN 劫持导致的。怎么解决的呢？听说是找 xx 交了保护费，后面也是利用文件 hash 的方式，想必原理上也是跟 SRI 相同的。</p><p>所幸的是，目前大多数的 CDN 劫持只是为了做一些夹带，比如通过 iframe 插入一些贴片广告，如果劫持者别有用心，比如 xss 注入之类的，还是非常危险的。</p><p>开启 SRI 能有效保证页面引用资源的完整性，避免恶意代码执行。</p><h2 id="浏览器如何处理-SRI">浏览器如何处理 SRI</h2><ul><li>当浏览器在 script 或者 link 标签中遇到 integrity 属性之后，会在执行脚本或者应用样式表之前对比所加载文件的哈希值和期望的哈希值。</li><li>当脚本或者样式表的哈希值和期望的不一致时，浏览器必须拒绝执行脚本或者应用样式表，并且必须返回一个网络错误说明获得脚本或样式表失败。</li></ul><h2 id="使用-SRI">使用 SRI</h2><p>通过使用 webpack 的 html-webpack-plugin 和 webpack-subresource-integrity 可以生成包含 integrity 属性 script 标签。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">SriPlugin</span> <span class="keyword">from</span> <span class="string">&#x27;webpack-subresource-integrity&#x27;</span>;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">const</span> compiler = <span class="title function_">webpack</span>(&#123;</span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">        <span class="attr">crossOriginLoading</span>: <span class="string">&#x27;anonymous&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">SriPlugin</span>(&#123;</span><br><span class="line">            <span class="attr">hashFuncNames</span>: [<span class="string">&#x27;sha256&#x27;</span>, <span class="string">&#x27;sha384&#x27;</span>],</span><br><span class="line">            <span class="attr">enabled</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;production&#x27;</span>,</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>那么当 script 或者 link 资源 SRI 校验失败的时候应该怎么做呢？</p><p>比较好的方式是通过 script 的 onerror 事件，当遇到 onerror 的时候重新 load 静态文件服务器之间的资源：</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></pre></td><td class="code"><pre><span class="line">&lt;script type=<span class="string">&quot;text/javascript&quot;</span> src=<span class="string">&quot;//11.url.cn/aaa.js&quot;</span></span><br><span class="line">        integrity=<span class="string">&quot;sha256-xxx sha384-yyy&quot;</span></span><br><span class="line">        crossorigin=<span class="string">&quot;anonymous&quot;</span> onerror=<span class="string">&quot;loadjs.call(this, event)&quot;</span>&gt;&lt;/script&gt;</span><br></pre></td></tr></table></figure><p>loadjs:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">loadjs</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="comment">// 上报</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="comment">// 重新加载 js</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span> (<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> script = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;script&#x27;</span>)</span><br><span class="line">    script.<span class="property">src</span> = <span class="variable language_">this</span>.<span class="property">src</span>.<span class="title function_">replace</span>(<span class="regexp">/\/\/11.src.cn/</span>, <span class="string">&#x27;https://x.y.z&#x27;</span>) <span class="comment">// 替换 cdn 地址为静态文件服务器地址</span></span><br><span class="line">    script.<span class="property">onload</span> = resolve</span><br><span class="line">    script.<span class="property">onerror</span> = reject</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementsByTagName</span>(<span class="string">&#x27;head&#x27;</span>)[<span class="number">0</span>].<span class="title function_">appendChild</span>(script);</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式的缺点是目前 onerror 中的 event 参数无法区分究竟是什么原因导致的错误，可能是资源不存在，也可能是 SRI 校验失败，不过目前来看，除非有统计需求，无差别对待并没有多大问题。</p><p>除此之外，我们还需要使用 <a href="https://www.npmjs.com/package/script-ext-html-webpack-plugin">script-ext-html-webpack-plugin</a> 将 onerror 事件注入进去：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">ScriptExtHtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;script-ext-html-webpack-plugin&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HtmlWebpackPlugin</span>(),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">SriPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">hashFuncNames</span>: [<span class="string">&#x27;sha256&#x27;</span>, <span class="string">&#x27;sha384&#x27;</span>]</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ScriptExtHtmlWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">custom</span>: &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/.js*/</span>,</span><br><span class="line">        <span class="attr">attribute</span>: <span class="string">&#x27;onerror=&quot;loadjs.call(this, event)&quot; onsuccess=&quot;loadSuccess.call(this)&quot;&#x27;</span></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>然后将 loadjs 和 loadSuccess 两个方法注入到 html 中，可以使用 inline 的方式。</p><p>还在知乎上看到一位大神另辟蹊径，通过 jsonp 的方式解决 CDN 劫持。个人感觉这种方式目前能够完美应对 CDN 劫持的主要原因是运营商通过文件名匹配的方式进行劫持，作者的方式就是通过 onerror 检测拦截，并且去掉资源文件的 js 后缀以应对 CDN 劫持。</p><p><a href="https://www.zhihu.com/question/35720092">应对流量劫持，前端能做哪些工作？</a></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;SRI-简介&quot;&gt;SRI 简介&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/Security/%E5%AD%90%E8%B5%84%E6%BA%90%E5%AE%8C%E6%95%B4%E6%80%A7&quot;&gt;SRI&lt;/a&gt; 全称 Subresource Integrity - 子资源完整性，是指浏览器通过验证资源的完整性（通常从 CDN 获取）来判断其是否被篡改的安全特性。&lt;/p&gt;
&lt;p&gt;通过给 link 标签或者 script 标签增加 integrity 属性即可开启 SRI 功能，比如：&lt;/p&gt;
&lt;figure class=&quot;highlight html&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;type&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;src&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;//s.url.cn/xxxx/xxx.js?_offline=1&amp;quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;integrity&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;sha256-mY9nzNMPPf8oL3CJss7THIEoXAC2ToW1tEX0NBhMvuw= sha384-ncIKElSEk2OR3YfjNLRSY35mzt0CUwrpNDVS//iD3dF9vxrWeZ7WPlAPJTqGkSai&amp;quot;&lt;/span&gt; &lt;span class=&quot;attr&quot;&gt;crossorigin&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;anonymous&amp;quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;script&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;integrity 值分成两个部分，第一部分指定哈希值的生成算法（sha256、sha384 及 sha512），第二部分是经过 base64 编码的实际哈希值，两者之间通过一个短横（-）分割。integrity 值可以包含多个由空格分隔的哈希值，只要文件匹配其中任意一个哈希值，就可以通过校验并加载该资源。上述例子中我使用了 sha256 和 sha384 两张 hash 方案。&lt;/p&gt;</summary>
    
    
    
    <category term="HTML" scheme="https://lz5z.com/categories/HTML/"/>
    
    
    <category term="CDN" scheme="https://lz5z.com/tags/CDN/"/>
    
    <category term="intergrity" scheme="https://lz5z.com/tags/intergrity/"/>
    
  </entry>
  
  <entry>
    <title>API 网关 Kong</title>
    <link href="https://lz5z.com/API-Gateway-Kong/"/>
    <id>https://lz5z.com/API-Gateway-Kong/</id>
    <published>2018-10-24T13:51:14.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Kong-简介"><a href="https://konghq.com/">Kong</a> 简介</h2><p>Kong 是一款基于 OpenResty 的 API 网关平台，在客户端和（微）服务之间转发 API 通信。Kong 通过插件的方式扩展自己的功能，其中包括身份验证、安全控制、流量控制、熔断机制、日志、黑名单、API 分发等等众多功能。下图是官网给出的传统项目架构和使用 Kong 的架构：</p><img src="/assets/img/kong.png" alt="kong"><p>Next-Generation API Platform for Modern Architectures。</p><span id="more"></span><p>可以看到，使用 Kong 之后，内部服务开发者只需要 focus 具体业务的实现，网关层提供 API 分发、管理、维护等功能，开发者只需要简单的配置就可以把自己开发的服务发布出去，同时置于网关的保护之下。</p><h3 id="OpenResty-简介"><a href="https://openresty.org/cn/">OpenResty</a> 简介</h3><p>OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台，其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。</p><h3 id="Kong-三大组件">Kong 三大组件</h3><ul><li>Kong Server ：基于 nginx 的服务器，用来接收 API 请求。</li><li>Apache Cassandra/<a href="https://www.postgresql.org/">PostgreSQL</a>：用来存储操作数据，本文以 PostgreSQL 为例进行讲解。</li><li><a href="https://github.com/PGBI/kong-dashboard">Kong dashboard</a>：UI 管理工具。</li></ul><h3 id="Kong-特性">Kong 特性</h3><ul><li><strong>可扩展</strong>：通过简单地添加机器来进行水平扩展，可以用较低的负载处理任何请求。</li><li><strong>模块化</strong>：通过 RESTful API 安装和配置插件。</li><li><strong>在任何基础设施上运行</strong>：Kong 可以部署在云端、机房、或者混合环境，包括单个或多个数据中心。</li></ul><h2 id="安装以及使用">安装以及使用</h2><p>Kong 可以安装运行在大部分 Linux 分布式平台和 macOS 上。全部安装方式请查看 <a href="https://konghq.com/install/">安装 Kong 社区版</a>。</p><h3 id="macOS-Homebrew">macOS Homebrew</h3><p>(1) 安装 Kong</p><figure class="highlight sh"><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">$ brew tap kong/kong</span><br><span class="line">$ brew install kong</span><br></pre></td></tr></table></figure><p>(2) 准备数据库</p><p>安装 PostgresSQL，在 Kong 启动之前指定数据库和用户。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ CREATE USER kong; CREATE DATABASE kong OWNER kong;</span><br></pre></td></tr></table></figure><p>由于对 Postgres 并不熟悉，我使用 GUI 工具 pgAdmin4 完成 User 和 Database 的创建。</p><p>(3) 准备 kong 配置文件</p><p>kong 默认使用 <code>/etc/kong/kong.conf</code> 作为启动的配置文件，因此我们在 <code>/etc/kong/</code> 目录下创建 kong.conf 文件，内容如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><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">database = postgres</span><br><span class="line">pg_port = 5432</span><br><span class="line">pg_user = kong</span><br><span class="line">pg_password = **** <span class="comment"># 如果你刚才设置密码的话</span></span><br></pre></td></tr></table></figure><p>全部 kong 的配置文件你可以查看 <a href="https://github.com/Kong/kong/blob/master/kong.conf.default">kong.conf.default</a>。</p><p>(4) 启动 kong</p><figure class="highlight sh"><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">$ kong migrations up</span><br><span class="line">$ kong start</span><br></pre></td></tr></table></figure><p>这个时候 kong 就启动起来了。然后我们可以通过下面的命令测试：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ curl -i http://localhost:8001/</span><br></pre></td></tr></table></figure><p>(5) 更多 kong 的命令</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">$ kong check /etc/kong/kong.conf <span class="comment"># 检验 kong 配置文件是否正确</span></span><br><span class="line">$ kong migrations up [-c /etc/kong/kong.conf] <span class="comment"># 通过配置文件准备数据存储</span></span><br><span class="line">$ kong start [-c /etc/kong/kong.conf] <span class="comment"># 启动 kong</span></span><br><span class="line">$ kong stop </span><br><span class="line">$ kong reload</span><br></pre></td></tr></table></figure><p>(6) kong 启动后监听了 4 个端口</p><ul><li><strong>8000</strong>: Kong 监听来自客户端的 HTTP 请求的，并将此请求转发到上游服务。</li><li><strong>8443</strong>: 与 8000 端口相同，不过只监听 HTTPS 请求。</li><li><strong>8001</strong>: 管理员对 Kong 进行配置管理的端口。</li><li><strong>8444</strong>: 管理员监听 HTTPS 请求的端口。</li></ul><h3 id="Docker">Docker</h3><p>(1) 创建一个名为 kong-net 的 network</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker network create kong-net</span><br></pre></td></tr></table></figure><p>(2) 启动数据库（PostgreSQL）</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">$ docker run -d --name kong-database \</span><br><span class="line">              --network=kong-net \</span><br><span class="line">              -p 5432:5432 \</span><br><span class="line">              -e <span class="string">&quot;POSTGRES_USER=kong&quot;</span> \</span><br><span class="line">              -e <span class="string">&quot;POSTGRES_DB=kong&quot;</span> \</span><br><span class="line">              postgres:9.6</span><br></pre></td></tr></table></figure><p>这个时候命令行会显示 <code>Unable to find image 'postgres:9.6' locally</code>，然后会自动帮我买下载 postgres 的 image。</p><p>(3) 准备数据库</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">$ docker run --<span class="built_in">rm</span> \</span><br><span class="line">    --network=kong-net \</span><br><span class="line">    -e <span class="string">&quot;KONG_DATABASE=postgres&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_PG_HOST=kong-database&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_CASSANDRA_CONTACT_POINTS=kong-database&quot;</span> \</span><br><span class="line">    kong:latest kong migrations up</span><br></pre></td></tr></table></figure><p>(4) 启动 Kong</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">$ docker run -d --name kong \</span><br><span class="line">    --network=kong-net \</span><br><span class="line">    -e <span class="string">&quot;KONG_DATABASE=postgres&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_PG_HOST=kong-database&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_CASSANDRA_CONTACT_POINTS=kong-database&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_PROXY_ACCESS_LOG=/dev/stdout&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_ADMIN_ACCESS_LOG=/dev/stdout&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_PROXY_ERROR_LOG=/dev/stderr&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_ADMIN_ERROR_LOG=/dev/stderr&quot;</span> \</span><br><span class="line">    -e <span class="string">&quot;KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl&quot;</span> \</span><br><span class="line">    -p 8000:8000 \</span><br><span class="line">    -p 8443:8443 \</span><br><span class="line">    -p 8001:8001 \</span><br><span class="line">    -p 8444:8444 \</span><br><span class="line">    kong:latest</span><br></pre></td></tr></table></figure><p>(5) 使用 Kong</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ curl -i http://localhost:8001/</span><br></pre></td></tr></table></figure><p>更详细的内容可以查看 <a href="https://docs.konghq.com/0.14.x/getting-started/quickstart/">5 分钟快速开始</a></p><h3 id="kong-dashboard">kong-dashboard</h3><p>Kong dashboard 是一个基于 node 实现的管理 Kong 网关设置的 GUI 工具。</p><p>使用 npm：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Install Kong Dashboard</span></span><br><span class="line">npm install -g kong-dashboard</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start Kong Dashboard</span></span><br><span class="line">kong-dashboard start --kong-url http://localhost:8001</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start Kong Dashboard on a custom port</span></span><br><span class="line">kong-dashboard start \</span><br><span class="line">  --kong-url http://localhost:8001 \</span><br><span class="line">  --port [port]</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start Kong Dashboard with basic auth</span></span><br><span class="line">kong-dashboard start \</span><br><span class="line">  --kong-url http://localhost:8001 \</span><br><span class="line">  --basic-auth user1=password1 user2=password2</span><br><span class="line"></span><br><span class="line"><span class="comment"># See full list of start options</span></span><br><span class="line">kong-dashboard start --<span class="built_in">help</span></span><br></pre></td></tr></table></figure><p>使用 Docker：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Start Kong Dashboard</span></span><br><span class="line">docker run --<span class="built_in">rm</span> -p 8080:8080 pgbi/kong-dashboard start --kong-url http://kong:8001</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start Kong Dashboard on a custom port</span></span><br><span class="line">docker run --<span class="built_in">rm</span> -p [port]:8080 pgbi/kong-dashboard start --kong-url http://kong:8001</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start Kong Dashboard with basic auth</span></span><br><span class="line">docker run --<span class="built_in">rm</span> -p 8080:8080 pgbi/kong-dashboard start \</span><br><span class="line">  --kong-url http://kong:8001</span><br><span class="line">  --basic-auth user1=password1 user2=password2</span><br><span class="line"></span><br><span class="line"><span class="comment"># See full list of start options</span></span><br><span class="line">docker run --<span class="built_in">rm</span> -p 8080:8080 pgbi/kong-dashboard start --<span class="built_in">help</span></span><br></pre></td></tr></table></figure><h2 id="Kong-使用">Kong 使用</h2><p>本质上 Kong 是作用于请求和响应之间的一层代理，我们可以通过 RESTful 的形式管理 API。</p><h3 id="添加一个-API">添加一个 API</h3><p>使用 curl 命令行：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">$ curl -i -X POST \</span><br><span class="line">  --url http://localhost:8001/apis/ \</span><br><span class="line">  --data <span class="string">&#x27;name=example-api&#x27;</span> \</span><br><span class="line">  --data <span class="string">&#x27;hosts=example.com&#x27;</span> \</span><br><span class="line">  --data <span class="string">&#x27;upstream_url=https://lz5z.com&#x27;</span></span><br></pre></td></tr></table></figure><p>或者使用 <code>kong-dashboard</code>，在 <a href="http://localhost:8080/#!/apis">http://localhost:8080/#!/apis</a> 编辑查看：</p><img src="/assets/img/kong-dashboard.png" alt="kong-dashboard"><p>这时，Kong 已经做好了对 HOST 是 <a href="http://example.com">example.com</a> 的 api 的代理请求，并且将其代理到 <a href="https://lz5z.com">https://lz5z.com</a> 上。</p><figure class="highlight sh"><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">$ curl -i -X GET \</span><br><span class="line">  --url http://localhost:8000/ \</span><br><span class="line">  --header <span class="string">&#x27;Host: example.com&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="总结">总结</h2><p>以上只是 Kong 简单的安装和工具的使用，由于之前对 docker、PostgresSQL 等周边工具并不熟悉，所以学习起来需要扩展的东西比较多，暂时先写到这里吧。关于 Kong 插件的使用已经编写，用户操作、授权、负载均衡、熔断等信息，这里先埋坑，后面有时间再补上吧。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;Kong-简介&quot;&gt;&lt;a href=&quot;https://konghq.com/&quot;&gt;Kong&lt;/a&gt; 简介&lt;/h2&gt;
&lt;p&gt;Kong 是一款基于 OpenResty 的 API 网关平台，在客户端和（微）服务之间转发 API 通信。Kong 通过插件的方式扩展自己的功能，其中包括身份验证、安全控制、流量控制、熔断机制、日志、黑名单、API 分发等等众多功能。下图是官网给出的传统项目架构和使用 Kong 的架构：&lt;/p&gt;
&lt;img src=&quot;/assets/img/kong.png&quot; alt=&quot;kong&quot;&gt;
&lt;p&gt;Next-Generation API Platform for Modern Architectures。&lt;/p&gt;</summary>
    
    
    
    <category term="网络" scheme="https://lz5z.com/categories/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="Gateway" scheme="https://lz5z.com/tags/Gateway/"/>
    
    <category term="nginx" scheme="https://lz5z.com/tags/nginx/"/>
    
    <category term="lua" scheme="https://lz5z.com/tags/lua/"/>
    
  </entry>
  
  <entry>
    <title>使用 requestAnimationFrame 解决滚动点停误触和 scroll 事件延迟</title>
    <link href="https://lz5z.com/requestAnimationFrame_to_solve_scroll_event_late/"/>
    <id>https://lz5z.com/requestAnimationFrame_to_solve_scroll_event_late/</id>
    <published>2018-10-09T05:07:37.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景">背景</h2><p>在手机端网页开发过程中，我们经常会遇到滚动点停误触的问题，最开始想到的解决办法就是判断当前页面（DOM）是否在滚动，如果在滚动，就取消点击或者其他事件。但是在判断页面是否在滚动的时候出现了一些问题，最常见的就 uiwebview scroll 事件延迟，导致我们无法准确判断当前页面（DOM）是否还在滚动。于是想到了使用 requestAnimationFrame 判断某个元素的位置是否发生变化来标识当前页面（DOM）是否在滚动。</p><span id="more"></span><h2 id="常见的滚动点停误触">常见的滚动点停误触</h2><p>这是移动端的前端开发中实际遇到的一个问题，当我们的页面出现滚动条的时候，用手滑动屏幕，屏幕上页面内容会快速滚动，不会因为手已经离开了屏幕而滚动停止。当我们想要停止滚动的时候，轻轻点击屏幕，让屏幕停止。但是这个时候有个问题，如果屏幕上点击的位置恰好可以点击，这个时候就会误触。还有一种常见的情况是，滚动已经停止了，点击屏幕发生在其之后，但是感觉像是发生了误触。</p><h3 id="常用的解决办法">常用的解决办法</h3><p>最先想到的解决办法当然是加锁，当页面在滚动的时候，就禁止元素的点击或者 touch 事件。但是这里存在一个问题，有些情况下，我们并不能正确的获得当前页面是否正在发生滚动。比如在 iOS UIWebViews 中, 在视图的滚动过程中，scroll 事件不会被触发；在滚动结束后，scroll 才会触发，参见 <a href="https://github.com/twbs/bootstrap/issues/16202">Bootstrap issue #16202</a> 。不能正确获取 scroll 事件就无法正确判断当前页面是否正在滚动。看起来我们陷入了僵局。</p><h2 id="新的解决方案">新的解决方案</h2><p>我们放弃 scroll 事件，使用别的方式判断页面是否滚动。最先想到的就是通过获取某个元素的相对位置，如果在两帧之内位置没有发生变化，那不就证明了当前页面已经不滚动了吗。</p><h3 id="如何判断元素位置没有发生改变">如何判断元素位置没有发生改变</h3><p>我们首先给 window 上绑定 touch 事件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;touchmove&#x27;</span>, <span class="variable language_">this</span>.<span class="property">onWindowTouchMove</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>))</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;touchend&#x27;</span>, <span class="variable language_">this</span>.<span class="property">onWindowTouchEnd</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>))</span><br></pre></td></tr></table></figure><p>如果发生 touchmove，就认为用户滑动了，在 touchend 的时候通过 getBoundingClientRect() 获取元素位置，再使用 requestAnimationFrame() 判断在两帧之间元素的位置是否发生变化，以此来标识页面滚动是否停止。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> element = e.<span class="property">target</span></span><br><span class="line"><span class="keyword">let</span> rectObject0 = element.<span class="title function_">getBoundingClientRect</span>()</span><br><span class="line"><span class="keyword">let</span> _this = <span class="variable language_">this</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">cancelAnimationFrame</span>(raf)</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">step</span> (<span class="params">timestamp</span>) &#123;</span><br><span class="line">    _this.<span class="property">scrollTime</span> = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span><br><span class="line">    <span class="keyword">let</span> rectObject1 = element.<span class="title function_">getBoundingClientRect</span>()</span><br><span class="line">    <span class="keyword">if</span> (rectObject0.<span class="property">top</span> !== rectObject1.<span class="property">top</span>) &#123;</span><br><span class="line">        rectObject0 = rectObject1</span><br><span class="line">        raf = <span class="variable language_">window</span>.<span class="title function_">requestAnimationFrame</span>(step)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        _this.<span class="property">isScrolling</span> = <span class="literal">false</span></span><br><span class="line">        <span class="variable language_">window</span>.<span class="title function_">cancelAnimationFrame</span>(raf)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">raf = <span class="variable language_">window</span>.<span class="title function_">requestAnimationFrame</span>(step)</span><br></pre></td></tr></table></figure><p>完整代码 <a href="https://github.com/Leo555/scrolling-observer">scrolling-observer</a>：</p><p>包已经发布在 npm 上了，可以 npm 或者 yarn 使用：</p><figure class="highlight javascript"><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 install scrolling-observer --save</span><br><span class="line">$ yarn add scrolling-observer</span><br></pre></td></tr></table></figure><p>使用方式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> scroll <span class="keyword">from</span> <span class="string">&#x27;scrolling-observer&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化</span></span><br><span class="line"><span class="title function_">scroll</span>()</span><br><span class="line"><span class="comment">// 页面是否在滚动</span></span><br><span class="line"><span class="keyword">let</span> isScrolling = <span class="title function_">scroll</span>().<span class="property">isScrolling</span></span><br><span class="line"><span class="comment">// 最后滚动时间</span></span><br><span class="line"><span class="keyword">let</span> scrollTime = <span class="title function_">scroll</span>().<span class="property">scrollTime</span></span><br><span class="line"><span class="comment">// destroy</span></span><br><span class="line"><span class="title function_">scroll</span>().<span class="title function_">destroy</span>()</span><br></pre></td></tr></table></figure><p>需要使用 ssr 的同学请注意不要在 node 端初始化，因为构造函数中使用了 window 对象。</p><h2 id="总结">总结</h2><p>简单通过判断两帧之间元素的相对位置是否发生变化来判断页面是否正在滚动。使用 requestAnimationFrame 并且只在 touchend 后触发检查机制，对页面性能也不会造成太大的影响。目前来看是不错的解决方案。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;
&lt;p&gt;在手机端网页开发过程中，我们经常会遇到滚动点停误触的问题，最开始想到的解决办法就是判断当前页面（DOM）是否在滚动，如果在滚动，就取消点击或者其他事件。但是在判断页面是否在滚动的时候出现了一些问题，最常见的就 uiwebview scroll 事件延迟，导致我们无法准确判断当前页面（DOM）是否还在滚动。于是想到了使用 requestAnimationFrame 判断某个元素的位置是否发生变化来标识当前页面（DOM）是否在滚动。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="requestAnimationFrame" scheme="https://lz5z.com/tags/requestAnimationFrame/"/>
    
    <category term="touch event" scheme="https://lz5z.com/tags/touch-event/"/>
    
  </entry>
  
  <entry>
    <title>webpack4 新特性</title>
    <link href="https://lz5z.com/webpack4-new/"/>
    <id>https://lz5z.com/webpack4-new/</id>
    <published>2018-09-15T09:13:58.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<p>wepack4 出来已经有半年了，目前最新的 release 版本为 <a href="https://webpack.docschina.org/concepts/">4.19.0</a>。由于之前项目打包一直存在性能问题，所以我一直很关注 webpack 和其社区的发展。目前来说 webpack4 已经趋于稳定，很多关键的插件也都更新了对 webpack4 的支持；更为重要的是，webpack4 的官方文档（中英文）已经很完善了，因此现在不学习 webpack4，更待何时。根据 webpack 作者 Tobias Koppers 的说法，他们已经着手开始开发 webpack5 了。</p><p>关于 webpack 入门的文章可以参考 <a href="https://lz5z.com/webpack/">webpack 从入门到放弃</a>。<br>关于 webpack 性能优化的内容可以参考 <a href="https://lz5z.com/webpack%E6%89%93%E5%8C%85%E5%8A%A0%E9%80%9F%E5%AE%9E%E6%88%98/">webpack 打包优化</a>。<br>关于 webpack4 全部新的特性可以查看官方的 <a href="https://github.com/webpack/webpack/releases/tag/v4.0.0">releases</a>。</p><span id="more"></span><h2 id="学习参考">学习参考</h2><p>学习一项新知识最好能站在巨人的肩膀上，其中 angular-cli、create-react-app 和 vue-cli 中对 webpack4 中的使用都是我们学习和模仿的对象。</p><h3 id="参考-create-react-app">参考 <a href="https://github.com/facebook/create-react-app">create-react-app</a></h3><p>使用 npx 创建 react-demo，创建之后 <code>npm run eject</code> 就可以看到它详细的 webpack 配置了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ npx create-react-app react-demo</span><br><span class="line">$ <span class="built_in">cd</span> react-demo</span><br><span class="line">$ npm run eject / yarn eject</span><br></pre></td></tr></table></figure><p>不过比较遗憾的是正式版本的 create-react-app 暂时还不支持 webpack4，我们可以使用 <code>react-scripts@2.0.0-next.3e165448</code> 来体验 webpack4 的特性。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="comment"># Create a new application</span></span><br><span class="line">$ npx create-react-app@next --scripts-version=2.0.0-next.3e165448 react-demo</span><br><span class="line">$ <span class="comment"># Upgrade an existing application</span></span><br><span class="line">$ yarn upgrade react-scripts@2.0.0-next.3e165448</span><br></pre></td></tr></table></figure><img src="/assets/img/create-react-app-webpack.png" alt="create-react-app-webpack"><p>其中 config 目录下的与 webpack 相关的三个文件是非常好的学习和借鉴的对象，可以说适应于绝大多数中小型项目。</p><h3 id="参考-vue-cli">参考 vue-cli</h3><p><a href="https://cli.vuejs.org/zh/">Vue CLI3</a> 简直可以说是学习和使用 vue 中一个无敌的存在，其中 @vue/cli-service 中集成了 webpack 的默认配置，带来开箱即用的快感；不过 Vue CLI 没有像 angular-cli 和 create-react-app 那样提供 eject 命令，而是通过 vue.config.js 进行包括 webpack 在内的全局配置。其可视化工具 <a href="https://cli.vuejs.org/zh/guide/creating-a-project.html#%E4%BD%BF%E7%94%A8%E5%9B%BE%E5%BD%A2%E5%8C%96%E7%95%8C%E9%9D%A2">vue ui</a> 中的 inspect 可以查看 webpack 参数，非常强大。</p><p>Vue CLI3 内部的 webpack 配置是通过 <a href="https://github.com/mozilla-neutrino/webpack-chain">webpack-chain</a> 维护的，这个库提供了一个 webpack 原始配置的上层抽象，使其可以定义具名的 loader 规则和具名插件，并有机会在后期进入这些规则并对它们的选项进行修改。</p><p>如果你的项目也有链式访问特定的 loader 的需求的话，不妨参考一下 Vue CLI3。</p><img src="/assets/img/vue-cli-webpack.png" alt="vue-cli-webpack"><p>如果不希望使用 webpack-chain 的话，可以参考其它比较成熟的 vue 项目，比如 <a href="https://github.com/PanJiaChen/vue-element-admin/tree/master/build">vue-element-admin</a> 也非常具有借鉴意义。</p><h2 id="webpack4-升级建议">webpack4 升级建议</h2><ul><li>webpack4 依赖 node 版本 &gt;= 6.11.5，node4 及其以下版本将不再支持。所以首先需要检查 node 是否需要升级。</li><li>还需要安装 webpack-cli 到 devDependencies 中。</li><li>如果是升级一个已有项目的话，可以使用 <code>npm outdated</code> 查看与 webpack 相关的 loader 和 plugin 是否需要升级。</li><li><a href="https://github.com/webpack-contrib/extract-text-webpack-plugin">extract-text-webpack-plugin</a> 让位于 <a href="https://github.com/webpack-contrib/mini-css-extract-plugin">mini-css-extract-plugin</a>。</li><li><a href="https://github.com/jantimon/html-webpack-plugin">html-webpack-plugin</a> 在使用过程中如果遇到  <code>thrownewError('Cyclic dependency'+nodeRep)</code> 的错误的话，可以使用 Alpha 版本 <code>npm i--save-dev html-webpack-plugin@next</code>。</li></ul><blockquote><p>由于 webpack4 以后对 css 模块支持的逐步完善和 commonChunk 插件的移除，在处理 css 文件提取的计算方式上也做了些调整。所以之前一直使用的 extract-text-webpack-plugin 也完成了它的历史使命，将让位于 mini-css-extract-plugin。</p></blockquote><p>extract-text-webpack-plugin 会将 css 内联在 js 中，这样带来的问题是：css 或者 js 的改动都会影响整个 bundle 的缓存。而 mini-css-extract-plugin 在 code Splitting 的时候会将原先内联写在每一个 js chunk bundle 的 css，单独拆成了一个个 css 文件。然后再通过 <a href="https://github.com/NMFR/optimize-css-assets-webpack-plugin">optimize-css-assets-webpack-plugin</a> 这个插件对 css 进行压缩和优化。</p><p><span style="opacity:0.7">备注：optimize-css-assets-webpack-plugin 默认使用 cssnano 进行 css 代码优化，但是也会导致一些问题，比如我之前遇到的 z-index 重新计算的问题和 keyframes 重命名的问题：<a href="https://lz5z.com/%E8%A7%A3%E5%86%B3webpack%E6%89%93%E5%8C%85%E5%90%8Ez-index%E9%87%8D%E6%96%B0%E8%AE%A1%E7%AE%97%E7%9A%84%E9%97%AE%E9%A2%98/">解决 webpack 打包后 z-index 重新计算的问题</a>。 </span></p><h2 id="webpack4-带来的变化">webpack4 带来的变化</h2><p>可能是受到 <a href="http://www.css88.com/doc/parcel/">parcel</a>（一款号称快速，零配置的 Web 应用程序打包器）的影响，webpack4 也引入了零配置的概念，遵从软件行业更先进的『规约大于配置』的理念。</p><h3 id="模式（mode）">模式（mode）</h3><p>mode 有三个值：</p><table><thead><tr><th style="text-align:left">选项</th><th style="text-align:left">描述</th></tr></thead><tbody><tr><td style="text-align:left">development</td><td style="text-align:left">会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。</td></tr><tr><td style="text-align:left">production</td><td style="text-align:left">会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。</td></tr><tr><td style="text-align:left">none</td><td style="text-align:left">不选用任何默认优化选项</td></tr></tbody></table><p>（1）可以在启动命令后加入参数使用：</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="string">&quot;scripts&quot;</span>: &#123;</span><br><span class="line">  <span class="string">&quot;dev&quot;</span>: <span class="string">&quot;webpack --mode development&quot;</span>,</span><br><span class="line">  <span class="string">&quot;build&quot;</span>: <span class="string">&quot;webpack --mode production&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）也可以在配置文件中加入 mode 属性：</p><ul><li>mode: development</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.development.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">+ <span class="attr">mode</span>: <span class="string">&#x27;development&#x27;</span></span><br><span class="line">- <span class="attr">plugins</span>: [</span><br><span class="line">-   <span class="keyword">new</span> webpack.<span class="title class_">NamedModulesPlugin</span>(), <span class="comment">// 当开启 HMR 的时候使用该插件会显示模块的相对路径</span></span><br><span class="line">-   <span class="keyword">new</span> webpack.<span class="title class_">NamedChunksPlugin</span>(),  <span class="comment">// 根据文件名来生成稳定的 chunkid</span></span><br><span class="line">-   <span class="keyword">new</span> webpack.<span class="title class_">DefinePlugin</span>(&#123; <span class="string">&quot;process.env.NODE_ENV&quot;</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(<span class="string">&quot;development&quot;</span>) &#125;) <span class="comment">// 向代码注入了 NODE\_ENV 这个环境变量</span></span><br><span class="line">- ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>development 模式默认开启了 NamedChunksPlugin 和 NamedModulesPlugin 方便调试，提供了更完整的错误信息，更快的重新编译的速度。</p><ul><li>mode: production</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.production.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">+  <span class="attr">mode</span>: <span class="string">&#x27;production&#x27;</span>,</span><br><span class="line">-  <span class="attr">plugins</span>: [</span><br><span class="line">-    <span class="keyword">new</span> <span class="title class_">UglifyJsPlugin</span>(<span class="comment">/* ... */</span>), <span class="comment">// JS 代码压缩</span></span><br><span class="line">-    <span class="keyword">new</span> webpack.<span class="property">optimize</span>.<span class="title class_">ModuleConcatenationPlugin</span>(), <span class="comment">// 作用域提升(scope hoisting)，提升代码在浏览器中的执行速度</span></span><br><span class="line">-    <span class="keyword">new</span> webpack.<span class="title class_">NoEmitOnErrorsPlugin</span>(), <span class="comment">// 在编译出现错误时，跳过输出阶段</span></span><br><span class="line">-    <span class="keyword">new</span> webpack.<span class="title class_">DefinePlugin</span>(&#123; <span class="string">&quot;process.env.NODE_ENV&quot;</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(<span class="string">&quot;production&quot;</span>) &#125;)</span><br><span class="line">-  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>production 模式提供代码压缩和代码分割，同时 webpack 也会自动进行 Scopehoisting 和 Tree-shaking。</p><p>可以看出 mode 本质上是提供了一些默认的配置，以此来简化 webpack 的使用门槛。</p><h2 id="optimization-优化"><a href="https://webpack.docschina.org/configuration/optimization/">optimization(优化)</a></h2><p>optimization 是 webpack4 中最大的改进，其中包括代码压缩，分割，优化等功能。</p><h3 id="使用-optimization-splitChunks-进行分包">使用 optimization.splitChunks 进行分包</h3><p>webpack4 移除 CommonsChunkPlugin，取而代之的是两个新的配置项（optimization.splitChunks 和 optimization.runtimeChunk）来进行分包。</p><p>我们来看下 create-react-app 生成的关于分包的配置：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">UglifyJsPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;uglifyjs-webpack-plugin&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">OptimizeCSSAssetsPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;optimize-css-assets-webpack-plugin&#x27;</span>); <span class="comment">// 用来压缩以及优化 css</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&#x27;production&#x27;</span>,</span><br><span class="line">  <span class="attr">optimization</span>: &#123;</span><br><span class="line">    <span class="attr">minimizer</span>: [</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">UglifyJsPlugin</span>(&#123;</span><br><span class="line">        <span class="attr">uglifyOptions</span>: &#123;<span class="comment">/* ... */</span>&#125;,</span><br><span class="line">        <span class="comment">// Use multi-process parallel running to improve the build speed</span></span><br><span class="line">        <span class="comment">// Default number of concurrent runs: os.cpus().length - 1</span></span><br><span class="line">        <span class="attr">parallel</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="comment">// Enable file caching</span></span><br><span class="line">        <span class="attr">cache</span>: <span class="literal">true</span></span><br><span class="line">      &#125;),</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">OptimizeCSSAssetsPlugin</span>(&#123; <span class="attr">cssProcessorOptions</span>: &#123; <span class="attr">safe</span>: <span class="literal">true</span> &#125; &#125;),</span><br><span class="line">    ],</span><br><span class="line">    <span class="comment">// Automatically split vendor and commons</span></span><br><span class="line">    <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">      <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;vendors&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// Keep the runtime chunk seperated to enable long term caching</span></span><br><span class="line">    <span class="attr">runtimeChunk</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在分包功能上主要使用 <a href="https://webpack.docschina.org/configuration/optimization/#optimization-splitchunks">splitChunks</a> 和 <a href="https://webpack.docschina.org/configuration/optimization/#optimization-runtimechunk">runtimeChunk</a> 两个参数。</p><h3 id="optimization-splitChunks">optimization.splitChunks</h3><p>默认情况下 splitChunks 的配置就适用于大多数用户。webpack4 将会按照以下规则自动进行分包：</p><ul><li>新的 chunk 是否被分享或者是否来自 node_modules。</li><li>新的 chunk 在压缩和 gzip 前是否大于 30kb。</li><li>按需加载 chunk 的并发请求数量小于等于 5 个。</li><li>页面初始化时需要加载的 chunk 并发数量小于等于 3 个。</li></ul><p>为了满足后面两个条件，webpack 有可能受限于包的最大数量值，生成的代码体积往上增加。</p><p>默认配置对应的参数如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">optimization</span>: &#123;</span><br><span class="line">  <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">    <span class="attr">chunks</span>: <span class="string">&#x27;async&#x27;</span>,</span><br><span class="line">    <span class="attr">minSize</span>: <span class="number">30000</span>,</span><br><span class="line">    <span class="attr">minChunks</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">maxAsyncRequests</span>: <span class="number">5</span>,</span><br><span class="line">    <span class="attr">maxInitialRequests</span>: <span class="number">3</span>,</span><br><span class="line">    <span class="attr">automaticNameDelimiter</span>: <span class="string">&#x27;~&#x27;</span>, </span><br><span class="line">    <span class="attr">name</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">      <span class="attr">vendors</span>: &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">        <span class="attr">priority</span>: -<span class="number">10</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">default</span>: &#123;</span><br><span class="line">        <span class="attr">minChunks</span>: <span class="number">2</span>,</span><br><span class="line">        <span class="attr">priority</span>: -<span class="number">20</span>,</span><br><span class="line">        <span class="attr">reuseExistingChunk</span>: <span class="literal">true</span></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>(1) splitChunks.chunks</p><p>表示哪些 chunks 会被分割，可以提供字符串或者 function 作为参数。如果传字符串的话，值可以是 “all”、“async”、“initial”。“all” 表示无论 chunk 是 async 还是 non-async 都可以被共享。</p><p>(2) splitChunks.cacheGroups</p><p>默认模式会将所有来自 node_modules 的模块分配到 一个叫 vendors 的缓存组；所有重复引用至少两次的代码，会被分配到 default 的缓存组。</p><p>一个模块可以被分配到多个缓存组，优化策略会将模块分配至跟高优先级别（priority）的缓存组，或者会分配至可以形成更大体积代码块的组里。</p><p>默认来说，缓存组会继承 splitChunks 的配置。所有上面列出的选择都是可以用在缓存组里的：chunks, minSize, minChunks, maxAsyncRequests, maxInitialRequests, name。</p><p>可以通过 <code>optimization.splitChunks.cacheGroups.default: false</code> 禁用 default 缓存组。</p><p>可以使用如下的方式提取公共代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">cacheGroups: &#123;</span><br><span class="line">  commons: &#123;</span><br><span class="line">    name: &quot;commons&quot;,</span><br><span class="line">    chunks: &quot;initial&quot;,</span><br><span class="line">    minChunks: 2</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(3) minSize: 形成一个新代码块最小的体积，默认是 30 kb。<br>(4) minChunks: 在分割之前，这个代码块最小应该被引用的次数（保证代码块复用性，默认值为 1 ，即不需要多次引用也可以被分割）。<br>(5) maxInitialRequests: 一个入口最大的并行请求数，默认是 3。<br>(6) maxAsyncRequests: 按需加载时候最大的并行请求数，默认是 5。<br>(7) name: 要控制代码块的命名，可以用 name 参数来配置，当不同分割代码块被赋予相同名称时候，他们会被合并在一起。如果赋予一个神奇的值 true，webpack 会基于代码块和缓存组的 key 自动选择一个名称。</p><h3 id="optimization-runtimeChunk">optimization.runtimeChunk</h3><p>webpack4 提供了 runtimeChunk 能让我们方便的提取 manifest，以前我们需要这样配置</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="keyword">new</span> webpack.<span class="property">optimize</span>.<span class="title class_">CommonsChunkPlugin</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;manifest&quot;</span>,</span><br><span class="line">  <span class="attr">minChunks</span>: <span class="title class_">Infinity</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>webpack4 中则只需要</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="attr">runtimeChunk</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="comment">// OR manifest</span></span><br><span class="line"><span class="attr">runtimeChunk</span>: &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;manifest&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 <code>optimization.runtimeChunk: true</code> 选项，webpack 会添加一个只包含运行时(runtime)额外代码块到每一个入口。<br>这个需要看场景使用，会导致每个入口都加载多一份运行时代码。其实打包生成的 runtime.js 非常的小，gzip 之后一般只有几 kb，但这个文件又经常会改变，导致我们每次都需要重新请求它，它的 http 耗时远大于它的执行时间了，所以建议不要将它单独拆包，而是将它内联到 index.html 之中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">ScriptExtHtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;script-ext-html-webpack-plugin&#x27;</span>) <span class="comment">// 支持 prefetch preload</span></span><br><span class="line"><span class="comment">// 注意一定要在 HtmlWebpackPlugin 之后引用</span></span><br><span class="line"><span class="comment">// inline 的 name 和 runtimeChunk 的 name 保持一致</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">ScriptExtHtmlWebpackPlugin</span>(&#123;</span><br><span class="line">  <span class="attr">inline</span>: <span class="regexp">/runtime..*.js$/</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="webpack-中的-runtime-和-manifest">webpack 中的 <a href="https://webpack.docschina.org/concepts/manifest/">runtime 和 manifest</a></h3><p>在使用 webpack 构建的应用程序中，主要包含三种类型的代码：</p><ul><li>我们自己编写的代码</li><li>源码依赖的第三方 library 或者 “vendor”</li><li>webpack 的 runtime 和 manifest，管理所有模块的交互</li></ul><p>runtime 以及伴随的 manifest 数据，主要是指：在浏览器运行时，webpack 用来连接模块化的应用程序的所有代码。</p><p>（1）runtime</p><p>在模块交互时，连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接，以及懒加载模块的执行逻辑。</p><p>（2）manifest</p><p>当编译器(compiler)开始执行、解析和映射应用程序时，它会保留所有模块的摘要信息。这个摘要的数据集合称为 “Manifest”，当完成打包并发送到浏览器时，在运行时通过 Manifest 来解析和加载模块。</p><p>无论选择哪种模块语法，那些 import 或 require 语句现在都已经转换为 <code>__webpack_require__</code> 方法，此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据，runtime 将能够查询模块标识符，检索出背后对应的模块。</p><p>可以理解为在应用程序运行时，编译器通过 manifest 中的数据来查找相应的模块，管理模块的加载和执行。</p><h3 id="优化分包策略">优化分包策略</h3><p>根据业务的复杂程度，一般在我们的代码中存在以下几种类型的代码：</p><p>基础组件库：react/vue; redux/vuex/mobx; react-router/vue-router; axios;<br>UI 组件库：Ant Design/Element;<br>必要组件/公共组件：Nav; Footer; Header; 全局配置等<br>非必要组件/代码：自己封装的组件和函数<br>低频组件：富文本; Markdown-Editor; Echarts 等<br>业务代码：业务组件; 业务模块; 业务页面等</p><ul><li>基础类库 chunk-libs</li></ul><p>它是构成我们项目必不可少的一些基础类库，比如 vue 全家桶或者 reat 全家桶，它们的升级频率都不高，但每个页面都需要它们，还有一些全局被共用的，体积不大的第三方库也可以放在其中：比如 nprogress、js-cookie、clipboard 等。</p><p>也可以使用 webpack 的 dll 技术将这些代码抽取为动态链接库。</p><ul><li>UI 组件库</li></ul><p>可以考虑将 UI 组件库也打包在 libs 中，不过相比于 chunk-libs，它的升级频率更高，并且体积更大，因此单独打包是更好的选择。</p><ul><li>自定义组件/函数 chunk-commons</li></ul><p>自定义组件可以选择单独打包成 bundle，也可以与业务代码打包在一起，还是要结合具体情况来看。</p><ul><li>低频组件</li></ul><p>低频组件和 chunk-commons 最大的区别是，它们只会在一些特定业务场景下使用，比如富文本编辑器、js-xlsx 等。webpack4 会根据这些库的大小（30kb）选择将其打包成独立的 bundle 或者 直接打包到具体的页面 bundle 中。</p><ul><li>业务代码</li></ul><p>一般按照页面来划分打包。</p><h2 id="webpack4-plugins"><a href="https://webpack.docschina.org/api/plugins">webpack4 plugins</a></h2><p>webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 属性会被 webpack compiler 调用，并且 compiler 对象可在整个编译生命周期访问。</p><ul><li>定义 apply 方法。</li><li>指定一个绑定到 webpack 自身的事件钩子。</li><li>使用 webpack 提供的 plugin API 操作构建结果。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pluginName = <span class="string">&#x27;BasicPlugin&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BasicPlugin</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">options</span>) &#123;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">apply</span>(<span class="params">compiler</span>) &#123;</span><br><span class="line">    compiler.<span class="property">hooks</span>.<span class="property">run</span>.<span class="title function_">tap</span>(pluginName, <span class="function"><span class="params">compilation</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;webpack 构建过程开始！&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用这个插件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">BasicPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;./BasicPlugin.js&#x27;</span>)</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">export</span> = &#123;</span><br><span class="line">  <span class="attr">plugins</span>:[</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">BasicPlugin</span>(options),</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="插件运作原理">插件运作原理</h3><p>webpack 基于插件的运行模式非常强大，也是其能够迅速占领市场，社区活跃的主要原因。如果把 webpack 比作流水线，插件就是流水线上一个个工人。webpack 通过 <a href="https://github.com/webpack/tapable">Tapable</a> 来组织这条复杂的流水线。</p><p>webpack 在运行过程中会广播事件，每个插件只需要监听它所关心的事件，就能加入到这条生产线中，从而改变生产线的运作。webpack 中基于观察者模式的事件流机制保证了其运行的有序性。</p><p>插件的核心是两个继承于 Tapable 的对象： Compiler 和 Compilation，它们是连接插件与 webpack 之间的桥梁。在插件代码的编写中，只要拿到了这两个对象，就可以实现广播和监听事件。</p><ul><li>Compiler 对象包含了 webpack 环境所有的的配置信息，包含 options，loaders，plugins 这些信息，这个对象在 webpack 启动时候被实例化，它是全局唯一的，可以简单地把它理解为 webpack 实例。</li><li>Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 webpack 以开发模式运行时，每当检测到一个文件变化，一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。</li></ul><p>Compiler 和 Compilation 的区别在于：Compiler 代表了整个 webpack 从启动到关闭的生命周期，而 Compilation 只是代表了一次新的编译。</p><h3 id="插件事件流变化">插件事件流变化</h3><p>webpack4 插件的编写方式与之前发生了变化，主要表现在 Compiler 和 Compilation 中事件监听和广播的表现形式。</p><p>webpack3:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 广播出事件</span></span><br><span class="line"><span class="comment">* event-name 为事件名称，注意不要和现有的事件重名</span></span><br><span class="line"><span class="comment">* params 为附带的参数</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">compiler.<span class="title function_">apply</span>(<span class="string">&#x27;event-name&#x27;</span>, params);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 监听名称为 event-name 的事件，当 event-name 事件发生时，函数就会被执行。</span></span><br><span class="line"><span class="comment">* 同时函数中的 params 参数为广播事件时附带的参数。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">compiler.<span class="title function_">plugin</span>(<span class="string">&#x27;event-name&#x27;</span>, <span class="keyword">function</span>(<span class="params">params</span>) &#123;</span><br><span class="line"></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// compilation.apply 和 compilation.plugin 使用方法和上面一致。</span></span><br></pre></td></tr></table></figure><p>webpack4:</p><p><a href="https://github.com/jantimon/html-webpack-plugin">html-webpack-plugin</a> 中在 compilation.hooks 上添加了 htmlWebpackPluginBeforeHtmlGeneration 对象：</p><img src="/assets/img/webpack-plugin-hooks-2.png" alt="webpack-plugin-hooks"><p>来看下 <a href="https://github.com/jharris4/html-webpack-include-assets-plugin">html-webpack-include-assets-plugin</a> 的兼容写法。</p><img src="/assets/img/webpack-plugin-hooks.png" alt="webpack-plugin-hooks"><h2 id="参考资料">参考资料</h2><ul><li><a href="https://webpack.docschina.org/">webpack</a></li><li><a href="https://mp.weixin.qq.com/s/bQvRFb3luLkvj0MEOvbsJw">手摸手，带你用合理的姿势使用 webpack 4</a></li><li><a href="https://github.com/yesvods/Blog/issues/15">没有了CommonsChunkPlugin，咱拿什么来分包（译）</a></li><li><a href="https://segmentfault.com/a/1190000012840742">Webpack原理-编写Plugin</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;wepack4 出来已经有半年了，目前最新的 release 版本为 &lt;a href=&quot;https://webpack.docschina.org/concepts/&quot;&gt;4.19.0&lt;/a&gt;。由于之前项目打包一直存在性能问题，所以我一直很关注 webpack 和其社区的发展。目前来说 webpack4 已经趋于稳定，很多关键的插件也都更新了对 webpack4 的支持；更为重要的是，webpack4 的官方文档（中英文）已经很完善了，因此现在不学习 webpack4，更待何时。根据 webpack 作者 Tobias Koppers 的说法，他们已经着手开始开发 webpack5 了。&lt;/p&gt;
&lt;p&gt;关于 webpack 入门的文章可以参考 &lt;a href=&quot;https://lz5z.com/webpack/&quot;&gt;webpack 从入门到放弃&lt;/a&gt;。&lt;br&gt;
关于 webpack 性能优化的内容可以参考 &lt;a href=&quot;https://lz5z.com/webpack%E6%89%93%E5%8C%85%E5%8A%A0%E9%80%9F%E5%AE%9E%E6%88%98/&quot;&gt;webpack 打包优化&lt;/a&gt;。&lt;br&gt;
关于 webpack4 全部新的特性可以查看官方的 &lt;a href=&quot;https://github.com/webpack/webpack/releases/tag/v4.0.0&quot;&gt;releases&lt;/a&gt;。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="webpack" scheme="https://lz5z.com/tags/webpack/"/>
    
    <category term="webpack4" scheme="https://lz5z.com/tags/webpack4/"/>
    
  </entry>
  
  <entry>
    <title>再见金山，你好腾讯</title>
    <link href="https://lz5z.com/GoodByeKingsoft-HelloTencent/"/>
    <id>https://lz5z.com/GoodByeKingsoft-HelloTencent/</id>
    <published>2018-08-24T02:25:35.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="有什么感受">有什么感受</h2><p>在金山的最后一天了，从昨天早上就开始了划水摸鱼<img src="/assets/img/花式摸鱼.jpg" alt="花式摸鱼" width="100px">的工作模式，主要是把项目的代码结构给几位同事讲一下，还有一些比较容易让人困惑的点，才发现我对项目是如此熟悉，大部分代码如数家珍。</p><p>从 2017 年 2 月 14 号情人节入职到明天 2018 年 8 月 24 正式离职，差不多在这里呆了一年半的时间，自己的进步自己看得到，周围的同事也看得到。老大曾经对我说，感觉入职的时候对我的评级评低了。听到以后很开心，可能不是评低了，是我确实进步了不少。跟去年要离开 OOCL 的时候感觉不同，那个时候正好赶上过年并且自己做的项目没有东西可以做，所以非常轻松，走之前差不多很长一段时间都在摸鱼。而这次从金山离职，从提出离职申请到昨天，基本上每天都有事情要做，赶需求，改 Bug，招新人，讲代码，交接功能，一直忙个不停。可见自己的重要性提高了，所以非常谨慎地又将一些可能留下的问题改了又改，争取不给后面的同事埋坑。</p><span id="more"></span><h2 id="为什么要走">为什么要走</h2><p>当然是为了多赚钱啦。说什么个人提升，开阔视野，年轻就要多吃苦之类的，本质上还是为了当前或者以后多赚钱。以三分兴趣，七分为了钱去奋斗，我觉得 <img src="/assets/img/ojbk.jpg" alt="ojbk" style="width: 45px">。当然跳槽也是有成本的，现在已经快要九月份了，跳槽相当于亏了几个月的年终奖，而且去深圳还要租房，每周往返深圳珠海还要不菲的路费，这些都是代价。粗略一算，可能未来几个月会过得更穷。再加上今年投资亏了一些，P2P 暴雷亏了一些，内推奖金没拿到，我感觉今年我就是跟钱过不去 😢。</p><p>虽然在别的厂能拿到更好的待遇，但是选择腾讯一个是离家近，一个是大厂光环。所以就算是去镀金了吧，希望以后能有更好的发展。</p><p>金山给了我很多的成长，因此内心对其还是非常感激的，唯一觉得公司有点亏待我的就是给的钱有点少，涨薪也没有谈判余地，等了很久给了我一个数字，内心当然是不满意的。因为那个时候已经有猎头联系并且开出接近 double 的待遇了，所以差不多知道涨薪后就有离开的意向了。但是当时我们组有新的产品即将上线，大家都处于加班加点的状态，个人觉得那个时候离开有些不厚道，就拖延了几个月。</p><h2 id="面试">面试</h2><p>到五六月份开始准备，陆陆续续面了头条，阿里，腾讯。头条是一面挂，后面就没有消息了；阿里非常顺利，一到三面都过了，后来栽到 HR 面，各种原因不说了。腾讯也是一面挂，但是没过多久又有电话打来，换个部门接着面，那个时候差不多已经算是拿到阿里 offer 了，想着反正面面也不吃亏，没想到这个却成了最终的归宿。腾讯的部门是 SNG，前前后后总共面了 7 面，简直是体力劳动，每次半小时到一小时不等，聊完都是口干舌燥、面红耳赤。中间又穿插了一个阿里的部门继续面。。。终于到了 7 月 24 号，拿到了腾讯 SNG 的 offer，没怎么犹豫就接受了。收到腾讯 offer 一周多以后，又接到阿里的电话，总监面。。。（这个效率有点低啊）</p><p>今日头条是第一次面试，而且只面了一面，视频面试加在线笔试（别人看着你写代码），因为表现不好，差不多面完就知道没戏了。今日头条比较好的是，面试前有声音很好听的 hr 小妹妹打电话提前预约，预约后有邮件通知，面试挂了还有婉拒的邮件。这点比很多大厂都要友好一些。</p><p>腾讯的面试很细节，个人感觉是需要提前查漏补缺的，从 api 到设计到原理还有算法都有涉猎。面 alloyteam 的时候，面试官的问题种种都是 “你觉得 a 比 b 相比怎么样”，“使用 a 技术开发某某可能会遇到什么问题”，“某某某为什么选择这样的设计” 这样很开放性的问题，感觉自己回答的都不是很好。后面通过同事咨询面试官，得到的答复是他还在考虑，然后就没有然后了。毕竟是腾讯的明星组，想进入还是有难度的。</p><p>阿里的面试差异化比较大，没有明确的风格，每个面试官都不同，有一个面试官聊完技术以后又跟我一起聊了大半个小时中国互联网环境（估计有创业的想法），还有一个面试官从头到尾没问技术问题，一直问我目前做什么，平时怎么学习，项目中遇到了哪些问题，后面又要了我的博客。当然大部分面试官还是常规性地问一些技术问题，不过我能够明显的感受到阿里的面试氛围更加活泼一些。还有就是阿里的 HR 给了我较为不愉快的体验，这里不多说了。</p><p>具体的面试问题我觉得没有啥参考性，就不贴了。</p><h2 id="遗憾">遗憾</h2><p>遗憾的事有几件，一个是之前想重构一下当前项目的打包，升级一下 webpack4 的，一直没有时间，希望后面的小伙伴能够再接再厉帮我完成心愿。一个是现在的前端负责人技术很好，感觉在他身上还有一些东西可以学习。还有一个是要离开珠海安逸的环境去深圳奋斗了，珠海真是太适合生活了，环境优雅，人人都很随和，交通也没有那么拥挤，而深圳会给人无形的压力。</p><h2 id="腾讯">腾讯</h2><p>虽然最近腾讯股价大跌，但是对我们底层员工来说应该是没有影响的吧。早就知道腾讯会有比较苦逼的加班，所以内心也并不是很担心。目前在金山我也会经常性的留下来加班，有时候是做项目，有时候是留下来学习，所以对加班也没有那么排斥。对要进的部门其实也没有太多的了解，后面熟悉了以后再说吧。</p><h2 id="最后">最后</h2><p>还是像以前一样，希望技术快快提升，money 快快来。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;有什么感受&quot;&gt;有什么感受&lt;/h2&gt;
&lt;p&gt;在金山的最后一天了，从昨天早上就开始了划水摸鱼&lt;img src=&quot;/assets/img/花式摸鱼.jpg&quot; alt=&quot;花式摸鱼&quot; width=&quot;100px&quot;&gt;的工作模式，主要是把项目的代码结构给几位同事讲一下，还有一些比较容易让人困惑的点，才发现我对项目是如此熟悉，大部分代码如数家珍。&lt;/p&gt;
&lt;p&gt;从 2017 年 2 月 14 号情人节入职到明天 2018 年 8 月 24 正式离职，差不多在这里呆了一年半的时间，自己的进步自己看得到，周围的同事也看得到。老大曾经对我说，感觉入职的时候对我的评级评低了。听到以后很开心，可能不是评低了，是我确实进步了不少。跟去年要离开 OOCL 的时候感觉不同，那个时候正好赶上过年并且自己做的项目没有东西可以做，所以非常轻松，走之前差不多很长一段时间都在摸鱼。而这次从金山离职，从提出离职申请到昨天，基本上每天都有事情要做，赶需求，改 Bug，招新人，讲代码，交接功能，一直忙个不停。可见自己的重要性提高了，所以非常谨慎地又将一些可能留下的问题改了又改，争取不给后面的同事埋坑。&lt;/p&gt;</summary>
    
    
    
    <category term="Work" scheme="https://lz5z.com/categories/Work/"/>
    
    
    <category term="跳槽" scheme="https://lz5z.com/tags/%E8%B7%B3%E6%A7%BD/"/>
    
  </entry>
  
  <entry>
    <title>Symbol</title>
    <link href="https://lz5z.com/JavaScript-Symbol/"/>
    <id>https://lz5z.com/JavaScript-Symbol/</id>
    <published>2018-07-09T13:31:49.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lz5z.com/ES2018%E6%96%B0%E7%89%B9%E6%80%A7%E5%AD%A6%E4%B9%A0/">ES2018新特性学习</a> 中又回顾到了 symbol 数据类型。Symbol 作为一种原始数据类型，除了其 <code>Symbol.iterator</code> 属性和 <code>Symbol.asyncIterator</code> 属性为数据提供 <code>for...of</code> 和 <code>for...await...of</code> 访问机制外，它还有什么功能呢？或者说，ES6 中增加 Symbol 数据类型主要面对什么场景呢？</p><h2 id="Symbol-简介">Symbol 简介</h2><p>Symbol() 函数返回 symbol 类型的值，该类型具有静态属性和静态方法，并且不支持 <code>new Symbol()</code> 语法。每个从 Symbol() 函数中返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符，这是该数据类型最大的目的。</p><span id="more"></span><h3 id="Symbol-vs-symbol">Symbol vs symbol</h3><ol><li>Symbol 是一个不支持 new 操作符的函数，用于创建 symbol 类型的值。</li><li>symbol 是一种基本数据类型。目前 JavaScript 支持的 7 种数据类型是：undefined、null、Boolean、String、Number、Object、symbol。</li></ol><h2 id="Symbol-使用">Symbol 使用</h2><p>我们可以直接使用 Symbol() 函数创建 symbol 类型，并且用一个字符串作为其描述，每次都会创建一个新的 symbol 类型。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="title class_">Symbol</span>()</span><br><span class="line"><span class="keyword">typeof</span> a <span class="comment">// &#x27;symbol&#x27;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(a) <span class="comment">// &#x27;[object Symbol]&#x27;</span></span><br><span class="line"><span class="keyword">let</span> a1 = <span class="title class_">Symbol</span>(<span class="string">&#x27;a&#x27;</span>)</span><br><span class="line"><span class="keyword">let</span> a2 = <span class="title class_">Symbol</span>(<span class="string">&#x27;a&#x27;</span>)</span><br><span class="line">a1 == a2 <span class="comment">// false</span></span><br><span class="line">a1 === a2 <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Symbol</span>(<span class="string">&#x27;foo&#x27;</span>) === <span class="title class_">Symbol</span>(<span class="string">&#x27;foo&#x27;</span>) <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>Symbol() 函数不能使用 new 操作符。因为 JavaScript 中 new 操作符用来创建对象，Symbol 生成的是一个原始类型的值，并不是对象。通过原始数据类型创建一个显式包装器对象的方式从 ECMAScript 6 开始不再被支持。 然而现有的原始包装器对象，如 <code>new Boolean()</code>、<code>new String()</code> 以及 <code>new Number()</code> 因为遗留原因仍可被创建。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Symbol</span>() <span class="comment">// TypeError: Symbol is not a constructor at new Symbol</span></span><br></pre></td></tr></table></figure><p>Symbol 可以接收字符串或者对象作为参数，如果参数是对象的话，Symbol 会调用该对象的 toString() 方法，将其转换为字符串，再生成 symbol 值。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = &#123;</span><br><span class="line">  <span class="title function_">toString</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;abc&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> sa = <span class="title class_">Symbol</span>(a)</span><br><span class="line">sa <span class="comment">// Symbol(abc)</span></span><br></pre></td></tr></table></figure><p>Symbol 值不能与其它数据类型的值进行运算，但是 Symbol 值可以<strong>显式转换</strong>为字符串或者 Boolean，其它类型的转换都会报 TypeError 错误。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="title class_">Symbol</span>(<span class="string">&#x27;World&#x27;</span>)</span><br><span class="line"><span class="string">&#x27;Hello &#x27;</span> + a <span class="comment">// TypeError: Cannot convert a Symbol value to a string</span></span><br><span class="line"><span class="string">`Hello <span class="subst">$&#123;a&#125;</span>`</span> <span class="comment">// TypeError: Cannot convert a Symbol value to a string  </span></span><br><span class="line">a.<span class="title function_">toString</span>() <span class="comment">// &#x27;Symbol(World)&#x27;</span></span><br><span class="line"><span class="title class_">String</span>(a) <span class="comment">// &#x27;Symbol(World)&#x27;</span></span><br><span class="line"><span class="title class_">Boolean</span>(a) <span class="comment">// true</span></span><br><span class="line">!a <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>每一个 Symbol 函数生成的值都不相等，因此 Symbol 可以作为标识符，当做对象属性名，这样就可以保证不会出现相同的属性名。这可以有效避免属性被覆盖。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="title class_">Symbol</span>()</span><br><span class="line"><span class="comment">// 第一种写法</span></span><br><span class="line"><span class="keyword">let</span> obj = &#123;&#125;</span><br><span class="line">obj[a] = <span class="string">&#x27;Hello&#x27;</span></span><br><span class="line"><span class="comment">// 第二种写法</span></span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line">  [a]: <span class="string">&#x27;Hello&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 第三种写法</span></span><br><span class="line"><span class="keyword">let</span> a = &#123;&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(a, mySymbol, &#123;<span class="attr">value</span>: <span class="string">&#x27;Hello&#x27;</span>&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 以上写法都得到同样结果</span></span><br><span class="line">a[mySymbol] <span class="comment">// &#x27;Hello&#x27;</span></span><br></pre></td></tr></table></figure><p>注意，Symbol 值作为对象属性名时，不能用点运算符，因为点运算符后面是字符串，而 symbol 值并不是字符串。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="title class_">Symbol</span>()</span><br><span class="line"><span class="keyword">let</span> obj = &#123;&#125;</span><br><span class="line"></span><br><span class="line">obj.<span class="property">a</span> = <span class="string">&#x27;Hello!&#x27;</span> <span class="comment">// 此时 a 相当于一个字符串，并不是 a 值</span></span><br><span class="line">obj[a] <span class="comment">// undefined</span></span><br><span class="line">obj[<span class="string">&#x27;a&#x27;</span>] <span class="comment">// &#x27;Hello!&#x27;</span></span><br></pre></td></tr></table></figure><p>在对象内部使用 Symbol 的时候，必须放在方括号中。</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">let</span> a = <span class="title class_">Symbol</span>()</span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line">  [a]: <span class="keyword">function</span>(<span class="params">arg</span>)&#123;...&#125;  </span><br><span class="line">&#125;</span><br><span class="line">obj[a](<span class="number">123</span>)</span><br></pre></td></tr></table></figure><h2 id="Symbol-属性">Symbol 属性</h2><h3 id="Symbol-length">Symbol.length</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Symbol</span>.<span class="property">length</span> <span class="comment">// 0</span></span><br></pre></td></tr></table></figure><h3 id="Symbol-iterator">Symbol.iterator</h3><p>返回对象默认迭代器方法，使用 <code>for...of</code> 进行迭代。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> iterable = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">iterator</span>]() &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">i</span>: <span class="number">0</span>,</span><br><span class="line">      <span class="title function_">next</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">i</span> &lt; <span class="number">3</span>) &#123;</span><br><span class="line">          <span class="keyword">return</span> &#123; <span class="attr">value</span>: <span class="variable language_">this</span>.<span class="property">i</span>++, <span class="attr">done</span>: <span class="literal">false</span> &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> &#123; <span class="attr">value</span>: <span class="literal">undefined</span>, <span class="attr">done</span>: <span class="literal">true</span> &#125;</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"><span class="keyword">for</span> (<span class="keyword">var</span> value <span class="keyword">of</span> iterable) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 0</span></span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 2</span></span><br></pre></td></tr></table></figure><h3 id="Symbol-asyncIterator">Symbol.asyncIterator</h3><p>返回对象默认的异步迭代器的方法，使用 <code>for await of</code> 进行迭代。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myAsyncIterator = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">asyncIterator</span>]: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> items = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>, <span class="string">&#x27;d&#x27;</span>]</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">next</span>: <span class="function">() =&gt;</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(&#123;</span><br><span class="line">        <span class="attr">done</span>: items.<span class="property">length</span> === <span class="number">0</span>,</span><br><span class="line">        <span class="attr">value</span>: items.<span class="title function_">shift</span>()</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 class="keyword">const</span> foo = <span class="keyword">async</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> <span class="title function_">await</span> (<span class="keyword">const</span> item <span class="keyword">of</span> myAsyncIterator) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(item)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">foo</span>()</span><br></pre></td></tr></table></figure><h3 id="Symbol-match"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/match">Symbol.match</a></h3><p>指定了匹配的是正则表达式而不是字符串。<code>String.prototype.match()</code> 方法会调用此函数。此函数还用于标识对象是否具有正则表达式的行为。比如： <code>String.prototype.startsWith()</code>，<code>String.prototype.endsWith()</code> 和 <code>String.prototype.includes()</code> 这些方法会检查其第一个参数是否是正则表达式，是正则表达式就抛出一个 TypeError。现在，如果 match symbol 设置为 false（或者一个 假值），就表示该对象不打算用作正则表达式对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> re = <span class="regexp">/foo/</span></span><br><span class="line">re[<span class="title class_">Symbol</span>.<span class="property">match</span>] = <span class="literal">false</span></span><br><span class="line"><span class="string">&#x27;/foo/&#x27;</span>.<span class="title function_">startsWith</span>(re) <span class="comment">// true</span></span><br><span class="line"><span class="string">&#x27;/baz/&#x27;</span>.<span class="title function_">endsWith</span>(re)   <span class="comment">// false</span></span><br><span class="line"><span class="comment">// 下面代码会抛出一个 TypeError：</span></span><br><span class="line"><span class="string">&#x27;/bar/&#x27;</span>.<span class="title function_">startsWith</span>(<span class="regexp">/bar/</span>) <span class="comment">// Throws TypeError, 因为 /bar/ 是一个正则表达式且 Symbol.match 没有修改。</span></span><br></pre></td></tr></table></figure><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/replace">Symbol.replace</a>，<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/search">Symbol.search</a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/split">Symbol.split</a> 使用方法都与 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/match">Symbol.match</a> 比较类似，这里就不赘述了。</p><h3 id="Symbol-hasInstance"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance">Symbol.hasInstance</a></h3><p>用于判断某对象是否为某构造器的实例，因此你可以用它自定义 instanceof 操作符在某个类上的行为。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyArray</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> [<span class="title class_">Symbol</span>.<span class="property">hasInstance</span>](instance) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Array</span>.<span class="title function_">isArray</span>(instance)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>([] <span class="keyword">instanceof</span> <span class="title class_">MyArray</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="Symbol-isConcatSpreadable"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable">Symbol.isConcatSpreadable</a></h3><p>用于配置某对象作为 <code>Array.prototype.concat()</code> 方法的参数时是否展开其数组元素。</p><p>对于数组对象，默认情况下，用于 concat 时，会按数组元素展开然后进行连接（数组元素作为新数组的元素）。</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="keyword">var</span> arr1 = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> arr2 = numeric = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="keyword">var</span> arr3 = arr1.<span class="title function_">concat</span>(arr2)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr3) <span class="comment">// 结果: [&#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><p>重置 <code>Symbol.isConcatSpreadable</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">var</span> arr1 = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> arr2 = numeric = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">arr2[<span class="title class_">Symbol</span>.<span class="property">isConcatSpreadable</span>] = <span class="literal">false</span> </span><br><span class="line"><span class="keyword">var</span> arr3 = arr1.<span class="title function_">concat</span>(arr2)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr3) <span class="comment">// 结果: [&#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><p>对于类似数组的对象，默认是不展开的，如果期望使用 concat 时，展开其元素用于连接，重置 <code>Symbol.isConcatSpreadable</code> 为 true。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arr1 = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">isConcatSpreadable</span>]: <span class="literal">true</span>, </span><br><span class="line">  <span class="attr">length</span>: <span class="number">2</span>, </span><br><span class="line">  <span class="number">0</span>: <span class="string">&#x27;hello&#x27;</span>, </span><br><span class="line">  <span class="number">1</span>: <span class="string">&#x27;world&#x27;</span> </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">arr1.<span class="title function_">concat</span>(obj) <span class="comment">// [1, 2, 3, &#x27;hello&#x27;, &#x27;world&#x27;]</span></span><br></pre></td></tr></table></figure><h3 id="Symbol-unscopables"><a href="">Symbol.unscopables</a></h3><h3 id="Symbol-species"><a href="">Symbol.species</a></h3><h3 id="Symbol-toPrimitive"><a href="">Symbol.toPrimitive</a></h3><h3 id="Symbol-toStringTag"><a href="">Symbol.toStringTag</a></h3><h2 id="Symbol-方法">Symbol 方法</h2><h3 id="Symbol-for-key">Symbol.for(key)</h3><p>使用给定的 key 搜索现有的 symbol，如果找到则返回该 symbol。否则将使用给定的 key 在全局 symbol 注册表中创建一个新的 symbol。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Symbol</span>.<span class="title function_">for</span>(<span class="string">&#x27;foo&#x27;</span>) <span class="comment">// 创建一个 symbol 并放入 symbol 注册表中，键为 &#x27;foo&#x27;</span></span><br><span class="line"><span class="title class_">Symbol</span>.<span class="title function_">for</span>(<span class="string">&#x27;foo&#x27;</span>) <span class="comment">// 从 symbol 注册表中读取键为&#x27;foo&#x27;的 symbol</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="title class_">Symbol</span>.<span class="title function_">for</span>(<span class="string">&#x27;bar&#x27;</span>) === <span class="title class_">Symbol</span>.<span class="title function_">for</span>(<span class="string">&#x27;bar&#x27;</span>) <span class="comment">// true</span></span><br><span class="line"><span class="title class_">Symbol</span>(<span class="string">&#x27;bar&#x27;</span>) === <span class="title class_">Symbol</span>(<span class="string">&#x27;bar&#x27;</span>) <span class="comment">// false，Symbol() 函数每次都会返回新的一个 symbol</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> sym = <span class="title class_">Symbol</span>.<span class="title function_">for</span>(<span class="string">&#x27;mario&#x27;</span>)</span><br><span class="line">sym.<span class="title function_">toString</span>() <span class="comment">// &#x27;Symbol(mario)&#x27;，mario 既是该 symbol 在 symbol 注册表中的键名，又是该 symbol 自身的描述字符串</span></span><br></pre></td></tr></table></figure><h3 id="Symbol-keyFor-sym">Symbol.keyFor(sym)</h3><p>用来获取 symbol 注册表中与某个 symbol 关联的键。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个 symbol 并放入 Symbol 注册表，key 为 &#x27;foo&#x27;</span></span><br><span class="line"><span class="keyword">var</span> globalSym = <span class="title class_">Symbol</span>.<span class="title function_">for</span>(<span class="string">&#x27;foo&#x27;</span>) </span><br><span class="line"><span class="title class_">Symbol</span>.<span class="title function_">keyFor</span>(globalSym) <span class="comment">// &#x27;foo&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建一个 symbol，但不放入 symbol 注册表中</span></span><br><span class="line"><span class="keyword">var</span> localSym = <span class="title class_">Symbol</span>()</span><br><span class="line"><span class="title class_">Symbol</span>.<span class="title function_">keyFor</span>(localSym) <span class="comment">// undefined，所以是找不到 key 的</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Symbol 默认属性并不在 symbol 注册表中</span></span><br><span class="line"><span class="title class_">Symbol</span>.<span class="title function_">keyFor</span>(<span class="title class_">Symbol</span>.<span class="property">iterator</span>) <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><h2 id="参考资料">参考资料</h2><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol">MDN-Symbol</a></li><li><a href="http://es6.ruanyifeng.com/#docs/symbol">Symbol</a></li><li></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lz5z.com/ES2018%E6%96%B0%E7%89%B9%E6%80%A7%E5%AD%A6%E4%B9%A0/&quot;&gt;ES2018新特性学习&lt;/a&gt; 中又回顾到了 symbol 数据类型。Symbol 作为一种原始数据类型，除了其 &lt;code&gt;Symbol.iterator&lt;/code&gt; 属性和 &lt;code&gt;Symbol.asyncIterator&lt;/code&gt; 属性为数据提供 &lt;code&gt;for...of&lt;/code&gt; 和 &lt;code&gt;for...await...of&lt;/code&gt; 访问机制外，它还有什么功能呢？或者说，ES6 中增加 Symbol 数据类型主要面对什么场景呢？&lt;/p&gt;
&lt;h2 id=&quot;Symbol-简介&quot;&gt;Symbol 简介&lt;/h2&gt;
&lt;p&gt;Symbol() 函数返回 symbol 类型的值，该类型具有静态属性和静态方法，并且不支持 &lt;code&gt;new Symbol()&lt;/code&gt; 语法。每个从 Symbol() 函数中返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符，这是该数据类型最大的目的。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="Symbol" scheme="https://lz5z.com/tags/Symbol/"/>
    
  </entry>
  
  <entry>
    <title>HTTP 协议 Transfer-Encoding</title>
    <link href="https://lz5z.com/HTTP-Transfer-Encoding/"/>
    <id>https://lz5z.com/HTTP-Transfer-Encoding/</id>
    <published>2018-07-08T16:08:27.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介">简介</h2><p>Transfer-Encoding (传输编码) 是常见的 HTTP 头 字段，表示将实体安全传递给用户所采用的编码形式。与另外一个更为常见的 Content-Encoding 不同，Content-Encoding 表示内容编码，通常用于对实体内容进行压缩编码，比如 gzip，deflate 等。而 Transfer-Encoding 不会减少实体内容传输大小，但是会改变实体传输的形式。Content-Encoding 和 Transfer-Encoding 二者是相辅相成的，对于一个 HTTP 报文，很可能同时进行了内容编码和传输编码。</p><p>在 HTTP 请求头中，Transfer-Encoding 被称为 TE，表示浏览器预期接受的传输编码方式，可使用 Response 头 Transfer-Encoding 字段中的值，比如 chunked；另外还可用 trailers 这个值来表明浏览器希望在最后一个大小为 0 的块之后还接收到一些额外的字段。</p><span id="more"></span><h2 id="HTTP-长连接">HTTP 长连接</h2><p>HTTP/1.0 后期引入长连接的概念，通过 <code>Connection: keep-alive</code> 实现，服务端和客户端通过这个头部告诉对方发送完数据后不需要断开 TCP 连接，后面可以继续使用。HTTP/1.1 则将其变为默认规则，只要不发送 <code>Connection: close</code>，所有的连接均保持为长连接。</p><h3 id="长连接存在的问题">长连接存在的问题</h3><p>持久链接需要服务器在开始发送消息体前发送 Content-Length 消息头字段，但是对于动态生成的内容来说，在内容创建完之前是不可知的。在 <a href="https://imququ.com/post/transfer-encoding-header-in-http.html">HTTP 协议中的 Transfer-Encoding</a> 这篇文章中，作者举了两个例子来阐述长连接存在的问题。使用 node 创建 server。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>).<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">sock</span>) &#123;</span><br><span class="line">  sock.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;HTTP/1.1 200 OK\r\n&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;\r\n&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;hello world!&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">destroy</span>()</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8080</span>, <span class="string">&#x27;127.0.0.1&#x27;</span>)</span><br></pre></td></tr></table></figure><p>使用 <code>sock.destroy()</code> ，则每次发送完请求后，就关闭 TCP 连接，假如去掉 <code>sock.destroy()</code>，服务变成长连接，但是请求的状态一直在 pending，因此浏览器无法确认数据是否传输完成，只能一直等待。通过设置 <code>Content-Length</code> 来解决这个问题。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>).<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">sock</span>) &#123;</span><br><span class="line">  sock.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;HTTP/1.1 200 OK\r\n&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;Content-Length: 12\r\n&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;\r\n&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;hello world!&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8080</span>, <span class="string">&#x27;127.0.0.1&#x27;</span>)</span><br></pre></td></tr></table></figure><p>这样浏览器能正常接收响应数据，通过 <code>Content-Length</code> 判断实体已经结束，但是如果 <code>Content-Length</code> 计算错误会导致数据异常，并且对于动态生成的内容来说，在内容创建完之前其长度是不可知的。</p><h2 id="Transfer-Encoding-chunked-分块传输编码"><a href="https://zh.wikipedia.org/wiki/%E5%88%86%E5%9D%97%E4%BC%A0%E8%BE%93%E7%BC%96%E7%A0%81">Transfer-Encoding: chunked (分块传输编码)</a></h2><p>Transfer-Encoding 的出现正是为了解决这个问题。如果一个 HTTP 消息（请求消息或应答消息）的 Transfer-Encoding 消息头的值为 chunked，那么，消息体由数量未定的块组成，并以最后一个大小为 0 的块为结束。分块传输编码只在 HTTP/1.1 中提供。</p><p>使用方式也很简单，在响应头部加上 <code>Transfer-Encoding: chunked</code> 后，就表示这个报文采用分块编码。每一个非空的块都以该块包含数据的字节数（字节数以十六进制表示）开始，跟随一个 CRLF （回车及换行），然后是数据本身，最后以一个大小为 0 的块 + CRLF 结束。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>).<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">sock</span>) &#123;</span><br><span class="line">  sock.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;HTTP/1.1 200 OK\r\n&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;Transfer-Encoding: chunked\r\n&#x27;</span>)</span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;\r\n&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;24\r\n&#x27;</span>) <span class="comment">// (36 字符 =&gt; 十六进制: 0x24)</span></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;This is the data in the first chunk \r\n&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;1b\r\n&#x27;</span>) <span class="comment">// (27 字符 =&gt; 十六进制: 0x1b)</span></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;and this is the second one \r\n&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;3\r\n&#x27;</span>) <span class="comment">// (3 字符 =&gt; 十六进制: 0x03)</span></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;con\r\n&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;8\r\n&#x27;</span>) <span class="comment">// (8 字符 =&gt; 十六进制: 0x08)</span></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;sequence\r\n&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;0\r\n&#x27;</span>) <span class="comment">// 块大小为 0 表示数据传输结束</span></span><br><span class="line">    sock.<span class="title function_">write</span>(<span class="string">&#x27;\r\n&#x27;</span>)  <span class="comment">// 消息最后以 CRLF 结尾</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8080</span>, <span class="string">&#x27;127.0.0.1&#x27;</span>)</span><br></pre></td></tr></table></figure><p>用浏览器访问 <code>http://localhost:8080/</code> 可以看到 “This is the data in the first chunk and this is the second one consequence”。</p><h2 id="Transfer-Encoding-其它定义方法">Transfer-Encoding 其它定义方法</h2><ol><li><code>Transfer-Encoding: chunked</code>：数据以一系列分块的形式进行发送。 Content-Length 首部在这种情况下不被发送。</li><li><code>Transfer-Encoding: compress</code>：采用 Lempel-Ziv-Welch (LZW) 压缩算法，这种内容编码方式已经被大部分浏览器弃用。</li><li><code>Transfer-Encoding: deflate</code>：采用 zlib 结构 (在 RFC 1950 中规定)，和 deflate 压缩算法(在 RFC 1951 中规定)。</li><li><code>Transfer-Encoding: gzip</code>：表示采用 Lempel-Ziv coding (LZ77) 压缩算法，以及 32 位 CRC 校验的编码方式。这个编码方式最初由 UNIX 平台上的 gzip 程序采用。</li><li><code>Transfer-Encoding: identity</code>：用于指代自身（例如：未经过压缩和修改）。除非特别指明，这个标记始终可以被接受。</li></ol><h2 id="参考资料">参考资料</h2><ul><li><a href="https://zh.wikipedia.org/wiki/%E5%88%86%E5%9D%97%E4%BC%A0%E8%BE%93%E7%BC%96%E7%A0%81">分块传输编码</a></li><li><a href="https://imququ.com/post/transfer-encoding-header-in-http.html">HTTP 协议中的 Transfer-Encoding</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Transfer-Encoding">Transfer-Encoding</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;简介&lt;/h2&gt;
&lt;p&gt;Transfer-Encoding (传输编码) 是常见的 HTTP 头 字段，表示将实体安全传递给用户所采用的编码形式。与另外一个更为常见的 Content-Encoding 不同，Content-Encoding 表示内容编码，通常用于对实体内容进行压缩编码，比如 gzip，deflate 等。而 Transfer-Encoding 不会减少实体内容传输大小，但是会改变实体传输的形式。Content-Encoding 和 Transfer-Encoding 二者是相辅相成的，对于一个 HTTP 报文，很可能同时进行了内容编码和传输编码。&lt;/p&gt;
&lt;p&gt;在 HTTP 请求头中，Transfer-Encoding 被称为 TE，表示浏览器预期接受的传输编码方式，可使用 Response 头 Transfer-Encoding 字段中的值，比如 chunked；另外还可用 trailers 这个值来表明浏览器希望在最后一个大小为 0 的块之后还接收到一些额外的字段。&lt;/p&gt;</summary>
    
    
    
    <category term="网络" scheme="https://lz5z.com/categories/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="HTTP" scheme="https://lz5z.com/tags/HTTP/"/>
    
    <category term="Transfer-Encoding" scheme="https://lz5z.com/tags/Transfer-Encoding/"/>
    
  </entry>
  
  <entry>
    <title>HTTP 状态码 301 与 302 的区别</title>
    <link href="https://lz5z.com/HTTP-301-vs-302/"/>
    <id>https://lz5z.com/HTTP-301-vs-302/</id>
    <published>2018-07-08T12:28:56.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="301-和-302-有啥区别">301 和 302 有啥区别</h2><p>301 Moved Permanently，永久重定向。被请求资源已永久移动到新位置，并且将来任何对该资源的引用都使用本响应返回的若干个 URI 之一。301 资源除非额外指定，否则都是可缓存的。</p><blockquote><p>注意：对于某些使用 HTTP/1.0 协议的浏览器，当它们发送的 POST 请求得到了一个 301 响应的话，接下来的重定向请求将会变成 GET 方式。</p></blockquote><p>302 Found 表示临时重定向 Moved Temporarily。由于这样的重定向是临时的，客户端应继续向原有地址发送以后的请求，只有在 Cache-Control 或 Expires 中进行了指定的情况下，这个响应才是可缓存的。</p><blockquote><p>注意：虽然 RFC1945 和 RFC 2068 规范不允许客户端在重定向时改变请求的方法，但是很多现存的浏览器将 302 响应视作为 303 响应，并且使用 GET 方式访问在 Location 中规定的 URI，而无视原先请求的方法。因此状态码 303 和 307 被添加了进来，用以明确服务器期待客户端进行何种反应。</p></blockquote><span id="more"></span><h2 id="301-和-302-相同点">301 和 302 相同点</h2><ol><li>都表示资源重定向。</li><li>新的 URI 地址都是在响应的 Location 中返回。</li><li>如果原始请求不是 GET 或者 HEAD 请求的话，浏览器会禁止自动进行重定向，除非得到用户的确认，因为请求的条件可能因此发生变化。</li></ol><h2 id="301-Moved-Permanently">301 Moved Permanently</h2><p>来看一个常见的 301 状态码的演示。访问<a href="https://lz5z.com">本网页</a>的时候，由于使用 https 协议，并且设置 http 自动重定向到 https，所以假如直接使用 http 协议<a href="http://lz5z.com">http://lz5z.com</a>进行访问，会有一次 301 重定向。</p><img src="/assets/img/http_301.png" alt="http_301" ><p>浏览器获得响应结果后，根据 Location 中的值进行重定向，打开页面 <a href="https://lz5z.com">https://lz5z.com</a>。</p><h2 id="302-Found">302 Found</h2><p>我们常用的短链接就是 302 跳转，比如我使用 <a href="http://dwz.wailian.work/">sina 的短链接</a>服务生成本页面的地址: <a href="http://t.cn/RdC6GHq">http://t.cn/RdC6GHq</a>。对其进行访问的时候就首先发生了 302 重定向。</p><img src="/assets/img/http_302.png" alt="http_302" ><h2 id="使用时机">使用时机</h2><p>由于 301 重定向是永久的重定向，搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。302 重定向是临时的重定向，搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回 302 代码，搜索引擎认为新的网址只是暂时的。</p><p>所以 301 是对搜索引擎更加友好的重定向，建议只要不是资源临时转移，都可以使用 301 的方式。</p><h2 id="参考资源">参考资源</h2><ul><li><a href="https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81#3xx%E9%87%8D%E5%AE%9A%E5%90%91">HTTP状态码#3xx重定向</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;301-和-302-有啥区别&quot;&gt;301 和 302 有啥区别&lt;/h2&gt;
&lt;p&gt;301 Moved Permanently，永久重定向。被请求资源已永久移动到新位置，并且将来任何对该资源的引用都使用本响应返回的若干个 URI 之一。301 资源除非额外指定，否则都是可缓存的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：对于某些使用 HTTP/1.0 协议的浏览器，当它们发送的 POST 请求得到了一个 301 响应的话，接下来的重定向请求将会变成 GET 方式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;302 Found 表示临时重定向 Moved Temporarily。由于这样的重定向是临时的，客户端应继续向原有地址发送以后的请求，只有在 Cache-Control 或 Expires 中进行了指定的情况下，这个响应才是可缓存的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：虽然 RFC1945 和 RFC 2068 规范不允许客户端在重定向时改变请求的方法，但是很多现存的浏览器将 302 响应视作为 303 响应，并且使用 GET 方式访问在 Location 中规定的 URI，而无视原先请求的方法。因此状态码 303 和 307 被添加了进来，用以明确服务器期待客户端进行何种反应。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="网络" scheme="https://lz5z.com/categories/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="HTTP" scheme="https://lz5z.com/tags/HTTP/"/>
    
    <category term="301" scheme="https://lz5z.com/tags/301/"/>
    
    <category term="302" scheme="https://lz5z.com/tags/302/"/>
    
  </entry>
  
  <entry>
    <title>ES2018新特性学习</title>
    <link href="https://lz5z.com/ES2018%E6%96%B0%E7%89%B9%E6%80%A7%E5%AD%A6%E4%B9%A0/"/>
    <id>https://lz5z.com/ES2018%E6%96%B0%E7%89%B9%E6%80%A7%E5%AD%A6%E4%B9%A0/</id>
    <published>2018-07-02T11:47:59.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>ECMAScript 2018 (ES9) 在 6 月底正式发布，带来了很多新特性。关于 ES7 和 ES8 相关的知识，可以查看这篇文章 <a href="https://lz5z.com/ES2016%E5%92%8CES2017%E5%AD%A6%E4%B9%A0/">ES2016 和 ES2017 学习</a>。目前大部分 ES7 和 ES8 的特性都得到主流浏览器的支持，而 ES9 的新特性还未能实现很好的兼容性。</p><p>关于 ES7/8/9 全部特性可以查看 tc39 官方的 <a href="https://github.com/tc39/proposals/blob/master/finished-proposals.md">proposals</a>，这些都是最后进入 stage 4 的特性。</p><p>ES9 的新特性：</p><ol><li>Lifting template literal restriction 模板语法修正</li><li><code>s</code> (dotAll) flag for regular expressions (正则表达式 dotAll 模式)</li><li>RegExp named capture groups (正则表达式命名捕获组)</li><li>Rest/Spread Properties (Rest/Spread 属性)</li><li>RegExp Lookbehind Assertions (正则表达式反向(lookbehind)断言)</li><li>RegExp Unicode Property Escapes (正则表达式 Unicode 转义)</li><li>Promise.prototype.finally</li><li>Asynchronous Iteration (异步迭代器)</li></ol><span id="more"></span><h2 id="正则表达式-dotAll-模式"><a href="https://github.com/tc39/proposal-regexp-dotall-flag">正则表达式 dotAll 模式</a></h2><p>dotAll 是一个新的正则表达式修饰符，目前 JS 拥有的修饰符有：</p><ul><li>g -&gt; global</li><li>i -&gt; ingoreCase</li><li>m -&gt; multiline</li><li>y -&gt; sticky</li><li>u -&gt; unicode</li><li>s -&gt; dotAll</li></ul><p>正则表达式中的 <code>.</code> 用来匹配任何单个字符，但是有 2 个除外：多字节 emoji 字符和行终结符。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/^.$/</span></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;😀&#x27;</span>)   <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>通过设置 u 表示 unicode</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/^.$/u</span></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;😀&#x27;</span>)   <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>行终止符包括</p><ul><li>U+000A LINE FEED (LF) (\n) - 换行</li><li>U+000D CARRIAGE RETURN (CR) (\r) - 回车</li><li>U+2028 LINE SEPARATOR - 行分隔符</li><li>U+2029 PARAGRAPH SEPARATOR - 段分隔符</li></ul><p>还有一些其它字符，也可以作为一行的开始：</p><ul><li>U+000B VERTICAL TAB (\v)</li><li>U+000C FORM FEED (\f)</li><li>U+0085 NEXT LINE</li></ul><p>目前 <code>.</code> 只能匹配其中的一部分：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/./</span></span><br><span class="line"></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;\n&#x27;</span>)       <span class="comment">// false</span></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;\r&#x27;</span>)       <span class="comment">// false</span></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;\u&#123;2028&#125;&#x27;</span>) <span class="comment">// false</span></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;\u&#123;2029&#125;&#x27;</span>) <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;\v&#x27;</span>)       <span class="comment">// true</span></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;\f&#x27;</span>)       <span class="comment">// true</span></span><br><span class="line">regex.<span class="title function_">test</span>(<span class="string">&#x27;\u&#123;0085&#125;&#x27;</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>标记 <code>s</code> 表示 dotAll，用来改变 <code>.</code> 不能匹配行终止符的行为:</p><figure class="highlight javascript"><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">/hello.<span class="property">world</span>/.<span class="title function_">test</span>(<span class="string">&#x27;hello\nworld&#x27;</span>)  <span class="comment">// false</span></span><br><span class="line">/hello.<span class="property">world</span>/s.<span class="title function_">test</span>(<span class="string">&#x27;hello\nworld&#x27;</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>或者用 <code>\s</code> 来匹配空白符：</p><figure class="highlight javascript"><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">/hello.<span class="property">world</span>/.<span class="title function_">test</span>(<span class="string">&#x27;hello\nworld&#x27;</span>)  <span class="comment">// false</span></span><br><span class="line">/hello[\s]world/s.<span class="title function_">test</span>(<span class="string">&#x27;hello\nworld&#x27;</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>dotAll 表示 <code>.</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> re = <span class="regexp">/hello.world/</span>s  <span class="comment">// 等价于 const re = new RegExp(&#x27;hello.world&#x27;, &#x27;s&#x27;)</span></span><br><span class="line"></span><br><span class="line">re.<span class="title function_">test</span>(<span class="string">&#x27;hello\nworld&#x27;</span>) <span class="comment">// true</span></span><br><span class="line">re.<span class="property">dotAll</span> <span class="comment">// true</span></span><br><span class="line">re.<span class="property">flags</span> <span class="comment">// &#x27;s&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="正则表达式命名捕获组"><a href="https://github.com/tc39/proposal-regexp-named-groups">正则表达式命名捕获组</a></h2><p>捕获组就是把正则表达式中匹配到的内容，保存到内存中以数字编号或者显式命名的数组里，方便后面使用。这种引用既可以在正则表达式内部，也可以是在正则表达式外部。</p><p>捕获组有两种形式，一种是普通捕获组，另一种是命名捕获组。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regex = <span class="regexp">/(\d&#123;4&#125;)-(\d&#123;2&#125;)-(\d&#123;2&#125;)/</span></span><br><span class="line"><span class="keyword">const</span> matchers = regex.<span class="title function_">exec</span>(<span class="string">&#x27;2018-07-02&#x27;</span>)</span><br><span class="line">matchers[<span class="number">0</span>]    <span class="comment">// 2018-07-02</span></span><br><span class="line">matchers[<span class="number">1</span>]    <span class="comment">// 2018</span></span><br><span class="line">matchers[<span class="number">2</span>]    <span class="comment">// 07</span></span><br><span class="line">matchers[<span class="number">3</span>]    <span class="comment">// 02</span></span><br></pre></td></tr></table></figure><p>使用数字捕获组的一个缺点是对于引用不太直观，以上面的例子，我们很难分清楚哪个组代表的是年，哪个组代表的是月。而命名捕获组就是为了解决这个问题。</p><h3 id="命名捕获组">命名捕获组</h3><p>ES2018 允许命名捕获组可以使用 <code>(?&lt;name&gt;...)</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> regex = <span class="regexp">/(?&lt;year&gt;[0-9]&#123;4&#125;)-(?&lt;month&gt;[0-9]&#123;2&#125;)-(?&lt;day&gt;[0-9]&#123;2&#125;)/</span></span><br><span class="line"><span class="keyword">const</span> match = regex.<span class="title function_">exec</span>(<span class="string">&#x27;2018-07-02&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match.<span class="property">groups</span>.<span class="property">day</span>) <span class="comment">// &#x27;02&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match.<span class="property">groups</span>.<span class="property">month</span>) <span class="comment">// &#x27;07&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match.<span class="property">groups</span>.<span class="property">year</span>) <span class="comment">// &#x27;2018&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="名字唯一">名字唯一</h3><p>每个捕获组的名字必须唯一，否则会抛出异常。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regex = <span class="regexp">/(?&lt;foo&gt;\d)-(?&lt;foo&gt;\d)/</span></span><br><span class="line"><span class="comment">// Uncaught SyntaxError: Invalid regular expression: /(?&lt;foo&gt;\d)-(?&lt;foo&gt;\d)/: Duplicate capture group name</span></span><br></pre></td></tr></table></figure><h3 id="匹配失败">匹配失败</h3><p>任何匹配失败的命名组都将返回 undefined。</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">let</span> re = <span class="regexp">/^(?&lt;optional&gt;\d+)?$/</span></span><br><span class="line"><span class="keyword">const</span> matchers = re.<span class="title function_">exec</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line"></span><br><span class="line">matchers[<span class="number">0</span>] === <span class="string">&#x27;&#x27;</span></span><br><span class="line">matchers.<span class="property">groups</span>.<span class="property">optional</span> === <span class="literal">undefined</span></span><br></pre></td></tr></table></figure><h3 id="使用解构赋值">使用解构赋值</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> re = <span class="regexp">/^(?&lt;one&gt;.*):(?&lt;two&gt;.*)$/</span></span><br><span class="line"><span class="keyword">let</span> &#123;<span class="attr">groups</span>: &#123;one, two&#125;&#125; = re.<span class="title function_">exec</span>(<span class="string">&#x27;foo:bar&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`one: <span class="subst">$&#123;one&#125;</span>, two: <span class="subst">$&#123;two&#125;</span>`</span>)  <span class="comment">// 输出 one: foo, two: bar</span></span><br></pre></td></tr></table></figure><h3 id="使用-replace">使用 replace</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> reDate = <span class="regexp">/(?&lt;year&gt;[0-9]&#123;4&#125;)-(?&lt;month&gt;[0-9]&#123;2&#125;)-(?&lt;day&gt;[0-9]&#123;2&#125;)/</span></span><br><span class="line"><span class="keyword">const</span> d = <span class="string">&#x27;2018-07-02&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(d.<span class="title function_">replace</span>(reDate, <span class="string">&#x27;$&lt;month&gt;-$&lt;day&gt;-$&lt;year&gt;&#x27;</span>)) <span class="comment">// 07-02-2018</span></span><br></pre></td></tr></table></figure><p><code>String.prototype.replace</code> 第 2 个参数可以接受一个函数。这时 命名捕获组的引用会作为 groups 参数传递进去:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> re = <span class="regexp">/(?&lt;year&gt;\d&#123;4&#125;)-(?&lt;month&gt;\d&#123;2&#125;)-(?&lt;day&gt;\d&#123;2&#125;)/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> result = <span class="string">&#x27;2018-07-02&#x27;</span>.<span class="title function_">replace</span>(re, <span class="function">(<span class="params">...args</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> &#123;day, month, year&#125; = args[args.<span class="property">length</span> - <span class="number">1</span>]</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;day&#125;</span>-<span class="subst">$&#123;month&#125;</span>-<span class="subst">$&#123;year&#125;</span>`</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">result === <span class="string">&#x27;02/07/2018&#x27;</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="反向引用">反向引用</h3><p>当需要在正则表达式里面引用命名捕获组时，使用 <code>\k&lt;name&gt;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> duplicate = <span class="regexp">/^(?&lt;half&gt;.*).\k&lt;half&gt;$/</span></span><br><span class="line">duplicate.<span class="title function_">test</span>(<span class="string">&#x27;a*b&#x27;</span>) <span class="comment">// false</span></span><br><span class="line">duplicate.<span class="title function_">test</span>(<span class="string">&#x27;a*a&#x27;</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="向下兼容">向下兼容</h3><p><code>/(?&lt;name&gt;)/</code> 和 <code>/\k&lt;foo&gt;/</code> 只有在命名捕获组中才有意义。如果正则表达式没有命名捕获组，那么 <code>/\k&lt;foo&gt;/</code> 仅仅是字符串字面量 “k<foo>” 而已。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/\k&lt;foo&gt;<span class="regexp">/.test(&#x27;k&lt;foo&gt;&#x27;)   /</span>/ <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="正则表达式反向-lookbehind-断言"><a href="https://github.com/tc39/proposal-regexp-lookbehind">正则表达式反向(lookbehind)断言</a></h2><p>断言 (Assertion) 是一个对当前匹配位置之前或之后的字符的测试，它不会实际消耗任何字符，所以断言也被称为“非消耗性匹配”或“非获取匹配”。</p><p>正则表达式的断言一共有 4 种形式：</p><ul><li><code>(?=pattern)</code> 零宽正向肯定断言(zero-width positive lookahead assertion)</li><li><code>(?!pattern)</code> 零宽正向否定断言(zero-width negative lookahead assertion)</li><li><code>(?&lt;=pattern)</code> 零宽反向肯定断言(zero-width positive lookbehind assertion)</li><li><code>(?&lt;!pattern)</code> 零宽反向否定断言(zero-width negative lookbehind assertion)</li></ul><h3 id="正向断言-lookahead">正向断言(lookahead)</h3><p>当前位置后面的字符串应该满足断言，但是并不捕获，在当前的 JavaScript 正则表达式只支持正向断言。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regex = <span class="regexp">/li(?=zhen)/</span></span><br><span class="line"><span class="keyword">const</span> match1 = regex.<span class="title function_">exec</span>(<span class="string">&#x27;lizhen&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match1[<span class="number">0</span>]) <span class="comment">// li</span></span><br><span class="line"><span class="comment">// 如果字符串没有zhen，则无法匹配</span></span><br><span class="line"><span class="keyword">const</span> match2 = regex.<span class="title function_">exec</span>(<span class="string">&#x27;liming&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match2) <span class="comment">// null</span></span><br></pre></td></tr></table></figure><p>正向否定断言正好相反</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regex = <span class="regexp">/li(?!zhen)/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> match1 = regex.<span class="title function_">exec</span>(<span class="string">&#x27;lizhen&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match1)    <span class="comment">// null</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> match2 = regex.<span class="title function_">exec</span>(<span class="string">&#x27;liming&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match2[<span class="number">0</span>]) <span class="comment">// li</span></span><br></pre></td></tr></table></figure><h3 id="反向断言-lookbehind">反向断言(lookbehind)</h3><p>反向断言和正向断言的行为一样，只是方向相反。反向肯定断言使用语法 <code>(?&lt;=...)</code>。</p><p>比如我们想获取所有的人民币金额，但是不获取其它货币（比如美元）：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regex = <span class="regexp">/(?&lt;=\D)\d+(\.\d*)?/</span></span><br><span class="line"><span class="keyword">const</span> match = regex.<span class="title function_">exec</span>(<span class="string">&#x27;$123.89&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(match[<span class="number">0</span>]) <span class="comment">// 123.89</span></span><br></pre></td></tr></table></figure><h2 id="正则表达式-Unicode-转义"><a href="https://github.com/tc39/proposal-regexp-unicode-property-escapes">正则表达式 Unicode 转义</a></h2><p>Unicode 标准为每个符号分配各种属性和属性值，比如希腊字母 <code>π</code> 在 Unicode 中有独特的属性和属性值。目前版本的 ECMAScript 中正则表达式是无法匹配这些 Unicode 的，通常开发人员有两种选择。</p><p>(1) 在运行时使用类似于 <a href="https://github.com/slevithan/xregexp">xregexp</a> 这样的库创建增强的正则表达式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regexGreekSymbol = <span class="title class_">XRegExp</span>(<span class="string">&#x27;\\p&#123;Greek&#125;&#x27;</span>, <span class="string">&#x27;A&#x27;</span>)</span><br><span class="line">regexGreekSymbol.<span class="title function_">test</span>(<span class="string">&#x27;π&#x27;</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>缺点是 xregexp 是一个运行时依赖，对性能要求较高的 web 应用来说不是很理想。而且其压缩文件 <code>xregexp-all-min.js.gz</code> 也有 35k，并且每当 Unicode 标准更新时，必须要更新 xregexp 才能使用新数据。</p><p>(2) 在编译时的时候使用 <a href="https://github.com/mathiasbynens/regenerate">regenerate</a> 生成正则表达式。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regenerate = <span class="built_in">require</span>(<span class="string">&#x27;regenerate&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> codePoints = <span class="built_in">require</span>(<span class="string">&#x27;unicode-9.0.0/Script/Greek/code-points.js&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> set = <span class="title function_">regenerate</span>(codePoints)</span><br><span class="line">set.<span class="title function_">toString</span>()</span><br><span class="line"><span class="comment">// → &#x27;[\u0370-\u0373\u0375-\u0377\u037A-\u037D\u037F\u0384\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03E1\u03F0-\u03FF\u1D26-\u1D2A\u1D5D-\u1D61\u1D66-\u1D6A\u1DBF\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FC4\u1FC6-\u1FD3\u1FD6-\u1FDB\u1FDD-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFE\u2126\uAB65]|\uD800[\uDD40-\uDD8E\uDDA0]|\uD834[\uDE00-\uDE45]&#x27;</span></span><br><span class="line"><span class="comment">// Imagine there’s more code here to save this pattern to a file.</span></span><br></pre></td></tr></table></figure><p>虽然这种方法所生成的正则表达式相当大，但是能够得到最佳的运行时性能。最大的缺点是它需要一个构建脚本，每当 Unicode 标准更新时，必须更新生成脚本。</p><h3 id="解决方案">解决方案</h3><p>ES2018 中使用 <code>\p&#123;…&#125;</code> 和 <code>\P&#123;…&#125;</code> 进行 Unicode 的属性转义，在正则表达式中使用 <code>u</code> 进行标记。在 <code>\p&#123;…&#125;</code> 内，可以以键值对的方式设置需要匹配的属性，而非具体内容。比如要匹配希腊字母 <code>π</code>：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> reGreekSymbol = <span class="regexp">/\p&#123;Script=Greek&#125;/u</span></span><br><span class="line">reGreekSymbol.<span class="title function_">test</span>(<span class="string">&#x27;π&#x27;</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>解决了以下几个问题：</p><ol><li>不用为创建 Unicode-aware 正则表达式担心。</li><li>不需要运行时依赖。</li><li>正则表达式不需要使用 Unicode 区间来判断特点的内容。</li><li>不需要生成正则表达式脚本。</li><li>Unicode 属性转义自动保持最新，每当 Unicode 标准更新时，ECMAScript 引擎更新其数据即可。</li></ol><h2 id="Rest-Spread-属性"><a href="https://github.com/tc39/proposal-object-rest-spread">Rest/Spread 属性</a></h2><p>ECMAScript 6 中增加了数组的 Rest 解构赋值和 Spread 语法，比如：</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">var</span> a, b, rest</span><br><span class="line">[a, b, ...rest] = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">40</span>, <span class="number">50</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b) <span class="comment">// 20</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(rest) <span class="comment">// [30, 40, 50]</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">x, y, z</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> x + y + z</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="title function_">sum</span>(...numbers) <span class="comment">// 6</span></span><br><span class="line">sum.<span class="title function_">apply</span>(<span class="literal">null</span>, numbers) <span class="comment">// 6</span></span><br></pre></td></tr></table></figure><p>ES2018 中增加了对象的 Rest 属性和 Spread 语法。</p><h3 id="Rest-属性">Rest 属性</h3><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="keyword">let</span> &#123;x, y, ...z&#125; = &#123;<span class="attr">x</span>:<span class="number">1</span>, <span class="attr">y</span>:<span class="number">2</span>, <span class="attr">a</span>:<span class="number">3</span>, <span class="attr">b</span>:<span class="number">4</span>&#125;</span><br><span class="line">x <span class="comment">// 1</span></span><br><span class="line">y <span class="comment">// 2</span></span><br><span class="line">z <span class="comment">// &#123;a:3, b: 4&#125;</span></span><br></pre></td></tr></table></figure><h3 id="Spread-语法">Spread 语法</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> n = &#123;x, y, ...z&#125;</span><br><span class="line">n <span class="comment">// &#123;x:1, y:2, a:3, b:4&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Promise-prototype-finally"><a href="https://github.com/tc39/proposal-promise-finally">Promise.prototype.finally</a></h2><p>Promise.prototype.finally 早就有很多实现，以至于我一直都认为它是原生对象的原型属性。常见的实现有：</p><ol><li><a href="http://bluebirdjs.com/docs/api/finally.html">Bluebird#finally</a></li><li><a href="https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback">Q#finally</a></li><li><a href="https://github.com/cujojs/when/blob/master/docs/api.md#promisefinally">when#finally</a></li><li><a href="http://api.jquery.com/jQuery.ajax/#jqXHR">jQuery jqXHR#always</a></li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="number">2</span>).<span class="title function_">finally</span>(<span class="function">() =&gt;</span> &#123;&#125;) <span class="comment">// will be resolved with 2</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="number">3</span>).<span class="title function_">finally</span>(<span class="function">() =&gt;</span> &#123;&#125;) <span class="comment">// will be rejected with 3</span></span><br></pre></td></tr></table></figure><h2 id="Asynchronous-Iteration"><a href="https://github.com/tc39/proposal-async-iteration">Asynchronous Iteration</a></h2><p>关于 JavaScript 的异步循环，我在之前的文章<a href="https://lz5z.com/JavaScript-Loop-Async/">JavaScript 循环与异步</a>有过探索。如今 ECMAScript 中有了对异步迭代的原生支持。</p><h3 id="迭代器-Iterator">迭代器 Iterator</h3><p>ES6 中引入迭代器来遍历数组，JavaScript 中的迭代器是一个对象，提供 next() 方法，用来返回序列中的下一项，这个方法包含两个属性：done 和 value。</p><p>迭代器对象一旦被创建，就可以反复调用 next()。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">makeIterator</span> (<span class="params">array</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> nextIndex = <span class="number">0</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> nextIndex &lt; array.<span class="property">length</span> ?</span><br><span class="line">        &#123;<span class="attr">value</span>: array[nextIndex++], <span class="attr">done</span>: <span class="literal">false</span>&#125; :</span><br><span class="line">        &#123;<span class="attr">done</span>: <span class="literal">true</span>&#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 class="comment">// 初始化后 next() 方法可以用来依次访问对象中的键值</span></span><br><span class="line"><span class="keyword">var</span> it = <span class="title function_">makeIterator</span>([<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>])</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(it.<span class="title function_">next</span>().<span class="property">value</span>) <span class="comment">// &#x27;a&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(it.<span class="title function_">next</span>().<span class="property">value</span>) <span class="comment">// &#x27;b&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(it.<span class="title function_">next</span>().<span class="property">done</span>)  <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="可迭代对象">可迭代对象</h3><p>常见的可迭代对象有：Array、String、TypedArray、Map、Set。这些对象都内置可迭代的对象，在其原型中有一个 <code>Symbol.iterator</code> 方法。</p><p>我们也可以定义可迭代对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myIterable = &#123;&#125;</span><br><span class="line">myIterable[<span class="title class_">Symbol</span>.<span class="property">iterator</span>] = <span class="keyword">function</span>* () &#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="number">1</span></span><br><span class="line">  <span class="keyword">yield</span> <span class="number">2</span></span><br><span class="line">  <span class="keyword">yield</span> <span class="number">3</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> myIterable) &#123; </span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// 3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> [a, b, c] = [...myIterable] <span class="comment">// [1, 2, 3]</span></span><br><span class="line"><span class="comment">// a ===1; b === 2; c === 3</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>(myIterable) <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p>当我们定义了可迭代对象后，就可以在 <code>Array.from</code>、<code>for...of</code> 中使用这个对象。</p><h3 id="异步迭代器">异步迭代器</h3><p>一个异步迭代器就像一个迭代器，除了它的 next() 方法返回一个 { value, done } 的 promise。如上所述，我们必须返回迭代器结果的 promise，因为在迭代器方法返回时，迭代器的下一个值和 done 状态可能未知。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myAsyncIterator = &#123;</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">asyncIterator</span>]: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> items = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>, <span class="string">&#x27;d&#x27;</span>]</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">next</span>: <span class="function">() =&gt;</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(&#123;</span><br><span class="line">        <span class="attr">done</span>: items.<span class="property">length</span> === <span class="number">0</span>,</span><br><span class="line">        <span class="attr">value</span>: items.<span class="title function_">shift</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于异步迭代器，使用 <code>for await of</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">async</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> <span class="title function_">await</span> (<span class="keyword">const</span> item <span class="keyword">of</span> myAsyncIterator) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(item)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)()</span><br></pre></td></tr></table></figure><h3 id="异步生成器函数">异步生成器函数</h3><p>异步生成器函数与生成器函数类似，但有以下区别：</p><ol><li>当被调用时，异步生成器函数返回一个对象：async generator，该对象有 3 个方法（next，throw，和 return），每个方法都返回一个 Promise，Promise 返回 { value, done }。而普通生成器函数并不返回 Promise，而是直接返回 { value, done }。这会自动使返回的异步生成器对象具有异步迭代的功能。</li><li>允许使用 await 表达式和 for-await-of 语句。</li><li>修改了 <code>yield*</code> 的行为以支持异步迭代。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span>* <span class="title function_">myAsyncGenerator</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> t = <span class="number">0</span></span><br><span class="line">  <span class="keyword">const</span> test = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">resolve</span>(+<span class="keyword">new</span> <span class="title class_">Date</span>())</span><br><span class="line">      &#125;, <span class="number">1000</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (t++ &lt; <span class="number">10</span>) &#123;</span><br><span class="line">      <span class="keyword">yield</span> <span class="keyword">await</span> <span class="title function_">test</span>()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;finally&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">main</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> <span class="title function_">await</span> (<span class="keyword">const</span> item <span class="keyword">of</span> <span class="title function_">myAsyncGenerator</span>()) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(item)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">main</span>()</span><br></pre></td></tr></table></figure><p>函数返回一个异步生成器（async generator）对象，可以用在 for-await-of 语句中使用。</p><h2 id="参考文档">参考文档</h2><ul><li><a href="http://esnext.justjavac.com/proposal/">esnext</a></li><li><a href="https://juejin.im/post/5b2a186cf265da596d04a648">[译] ES2018（ES9）的新特性</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;ECMAScript 2018 (ES9) 在 6 月底正式发布，带来了很多新特性。关于 ES7 和 ES8 相关的知识，可以查看这篇文章 &lt;a href=&quot;https://lz5z.com/ES2016%E5%92%8CES2017%E5%AD%A6%E4%B9%A0/&quot;&gt;ES2016 和 ES2017 学习&lt;/a&gt;。目前大部分 ES7 和 ES8 的特性都得到主流浏览器的支持，而 ES9 的新特性还未能实现很好的兼容性。&lt;/p&gt;
&lt;p&gt;关于 ES7/8/9 全部特性可以查看 tc39 官方的 &lt;a href=&quot;https://github.com/tc39/proposals/blob/master/finished-proposals.md&quot;&gt;proposals&lt;/a&gt;，这些都是最后进入 stage 4 的特性。&lt;/p&gt;
&lt;p&gt;ES9 的新特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lifting template literal restriction 模板语法修正&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; (dotAll) flag for regular expressions (正则表达式 dotAll 模式)&lt;/li&gt;
&lt;li&gt;RegExp named capture groups (正则表达式命名捕获组)&lt;/li&gt;
&lt;li&gt;Rest/Spread Properties (Rest/Spread 属性)&lt;/li&gt;
&lt;li&gt;RegExp Lookbehind Assertions (正则表达式反向(lookbehind)断言)&lt;/li&gt;
&lt;li&gt;RegExp Unicode Property Escapes (正则表达式 Unicode 转义)&lt;/li&gt;
&lt;li&gt;Promise.prototype.finally&lt;/li&gt;
&lt;li&gt;Asynchronous Iteration (异步迭代器)&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="ES2018" scheme="https://lz5z.com/tags/ES2018/"/>
    
    <category term="ES9" scheme="https://lz5z.com/tags/ES9/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出Node.js》-玩转进程</title>
    <link href="https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E7%8E%A9%E8%BD%AC%E8%BF%9B%E7%A8%8B/"/>
    <id>https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E7%8E%A9%E8%BD%AC%E8%BF%9B%E7%A8%8B/</id>
    <published>2018-06-18T09:28:28.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>第九章 玩转进程</h1><p>Node 基于 V8 引擎构建，采用单线程模型，所有的 JavaScript 将会运行在单个进程的单个线程上，它带来的好处是：没有多线程中常见的锁以及线程同步的问题，操作系统在调度时也能减少上下文切换，提高 CPU 使用率。但是如今 CPU 基本均是多核的，真正的服务器往往还有多个 CPU，一个 Node 进程只能利用一个核，这带来硬件资源的浪费。另外，Node 运行在单线程之上，一个单线程抛出异常而没有被捕获，将会导致进程的崩溃。</p><p>严格来说，Node 并非真正的单线程，Node 自身中还有 I/O 线程存在，这些 I/O 线程由底层 libuv 处理，这部分线程对于 JavaScript 而言是透明的，只有 C++ 扩展时才会关注到，JavaScript 代码运行在 V8 上，是单线程的。</p><span id="more"></span><h2 id="服务器变迁">服务器变迁</h2><ol><li>同步：最早服务器是同步模型，一次只能处理一个请求，其它请求都需要等待当前请求处理完毕。假如每次响应耗时为 N 秒，这类服务的 QPS 为 1/N。</li><li>复制进程：每一个连接使用一个进程来服务，采用进程的复制实现，代价非常昂贵。假如进程数上限为 M，这类服务的 QPS 为 M/N。</li><li>多线程：一个线程处理一个请求，线程相对进程开销要小很多，线程直接可以共享数据，利用线程池减少创建和销毁线程的开销。假如线程占用的资源为进程的 1/L，它的 QPS 为 M*L/N。</li><li>事件驱动：Node 和 Nginx 采用事件驱动的方式实现，避免了不必要的内存开销和上下文切换。事件驱动模型存在的主要问题是 CPU 利用率不高和程序健壮性不能保证。</li><li>协程：基于协程实现的服务器，比如 Golang 目前也非常流行，协程是用户级别线程，它对于内核透明，完全由用户自己进行程序之间的调用。详细内容可以参考我这篇文章<a href="https://lz5z.com/Python%E5%8D%8F%E7%A8%8B/">Python 协程</a>。</li></ol><h2 id="多进程架构">多进程架构</h2><p>Node 提供 child_process 模块来实现多核 CPU 的利用。child_process.fork() 函数来实现进程的复制。</p><p>worker.js 代码如下：</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">var</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>)</span><br><span class="line">http.<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">req, res</span>) &#123;</span><br><span class="line">  res.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span> &#125;)</span><br><span class="line">  res.<span class="title function_">end</span>(<span class="string">&#x27;Hello World\n&#x27;</span>)</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="title class_">Math</span>.<span class="title function_">round</span>((<span class="number">1</span> + <span class="title class_">Math</span>.<span class="title function_">random</span>()) * <span class="number">1000</span>), <span class="string">&#x27;127.0.0.1&#x27;</span>)</span><br></pre></td></tr></table></figure><p>通过 <code>node worker.js</code> 启动它，会监听 1000 到 2000 之间的一个随机端口。</p><p>master.js 代码如下：</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">var</span> fork = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="property">fork</span></span><br><span class="line"><span class="keyword">var</span> cpus = <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>).<span class="title function_">cpus</span>()</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; cpus.<span class="property">length</span>; i++) &#123;</span><br><span class="line">  <span class="title function_">fork</span>(<span class="string">&#x27;./worker.js&#x27;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码根据 CPU 数量复制出对应的 Node 进程数，Linux 系统下通过 ps aux | grep worker.js 查看进程的数量。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ps aux | grep worker.js</span></span><br><span class="line">lizhen 1475 0.0 0.0 2432768 600 s003 S+ 3:27AM 0:00.00 grep worker.js</span><br><span class="line">lizhen 1440 0.0 0.2 3022452 12680 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js</span><br><span class="line">lizhen 1439 0.0 0.2 3023476 12716 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js</span><br><span class="line">lizhen 1438 0.0 0.2 3022452 12704 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js</span><br><span class="line">lizhen 1437 0.0 0.2 3031668 12696 s003 S 3:25AM 0:00.15 /usr/local/bin/node ./worker.js </span><br></pre></td></tr></table></figure><p>这种通过 Master 启多个 Worker 的模式就是主从模式，进程被分为主进程和工作进程。主进程不负责具体的业务，而是负责调度和管理工作进程，它是趋于稳定的。</p><p>通过 fork() 复制的进程都是独立的进程，这个进程中有着独立的 V8 实例，它需要至少 30ms 的启动时间和至少 10MB 的内存。因此 fork 依然是昂贵的。</p><h3 id="创建子进程">创建子进程</h3><p>child_process 模块给予 Node 可以随意创建子进程的能力，详细的使用方法可以参考这篇文章：<a href="https://lz5z.com/Node.js%E4%B8%ADchild_procss%E6%A8%A1%E5%9D%97/">Node.js 中 child_procss 模块</a>。</p><ol><li>spawn() 启动一个子进程执行命令。</li><li>exec() 启动子进程执行命令，通过回调函数获取子进程状态。</li><li>execFile() 启动一个子进程执行可执行文件。</li><li>fork() 通过制定需要执行的 JavaScript 文件创建 Node 子进程。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>)</span><br><span class="line">cp.<span class="title function_">spawn</span>(<span class="string">&#x27;node&#x27;</span>, [<span class="string">&#x27;worker.js&#x27;</span>])</span><br><span class="line">cp.<span class="title function_">exec</span>(<span class="string">&#x27;node worker.js&#x27;</span>, <span class="keyword">function</span> (<span class="params">err, stdout, stderr</span>) &#123;</span><br><span class="line"> <span class="comment">// some code</span></span><br><span class="line">&#125;)</span><br><span class="line">cp.<span class="title function_">execFile</span>(<span class="string">&#x27;worker.js&#x27;</span>, <span class="keyword">function</span> (<span class="params">err, stdout, stderr</span>) &#123;</span><br><span class="line"> <span class="comment">// some code</span></span><br><span class="line">&#125;)</span><br><span class="line">cp.<span class="title function_">fork</span>(<span class="string">&#x27;./worker.js&#x27;</span>)</span><br></pre></td></tr></table></figure><h3 id="进程之间的通信">进程之间的通信</h3><p>首先来看一个示例：</p><p>parent.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> n = cp.<span class="title function_">fork</span>(__dirname + <span class="string">&#x27;/sub.js&#x27;</span>)</span><br><span class="line">n.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span> (<span class="params">m</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;PARENT got message:&#x27;</span>, m)</span><br><span class="line">&#125;)</span><br><span class="line">n.<span class="title function_">send</span>(&#123;<span class="attr">hello</span>: <span class="string">&#x27;world&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><p>sub.js</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">process.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span> (<span class="params">m</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;CHILD got message:&#x27;</span>, m)</span><br><span class="line">&#125;)</span><br><span class="line">process.<span class="title function_">send</span>(&#123;<span class="attr">foo</span>: <span class="string">&#x27;bar&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><p>通过 fork() 或者其它 API，创建子进程之后，为了实父子程之间的通信，父进程与子进<br>程之间会创建 IPC 通道。通过过 IPC 通道，父子进程之间才能通过 message 和 send() 传递信息。</p><blockquote><p>进程间通信原理：<br>IPC 全称是 Inter-Process Communication，即进程间通信，Node 实现 IPC 使用管道(pipe)技术，具体实现细节由 libuv 提供。在 Windows 下由命名管道（named pipe）实现，Linux 下采用 Unix Domain Socket 实现。表现在应用层上的进程间通信只有简单的 message 事件和 send() 方法。父进程在实际创建子进程之前，会创建 IPC 通道并监听它，然后才真正创建出子进程，并且通过环境变量 NODE_CHANNEL_FD 告诉子进程这个 IPC 通道的<strong>文件描述符</strong>。子进程通过这个文件描述符去连接这个已存在的 IPC 通道，从而完成父子进程之间的连接。</p></blockquote><img src="/assets/img/process_ipc.png" alt="process_ipc"><p>由于 IPC 管道是用命名管道或者 Domain Socket 创建的，与网络 socket 比较类似，属于双向通行。不同的是它们在系统内核中就完成了进程间的通信，而不是通过网络层，非常高效。</p><h3 id="句柄传递">句柄传递</h3><p>通常我们启用多个 Node 进程的时候，假如每个进程都监听 80 端口，会导致 EADDRINUSE 异常，解决方案是让每个进程监听不同的端口，其中主进程监听 80，对外接收所有的网络请求，再将这些请求代理到不同的端口的进程上。</p><img src="/assets/img/process_80.png" alt="process_80"><p>通过代理不仅能解决端口重复监听的问题，还能适当的做负载均衡。由于进程每接收一个连接都会用掉一个文件描述符，因此代理方案中客户端连接到代理进程，代理进程连接到工作进程的过程需要用掉两个文件描述符，操作系统的文件描述符是有限的，代理方式需要一倍数量的文件描述符影响了系统的扩展能力。</p><p>为了解决上述问题，Node 引入了进程间传递句柄的功能。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">child.<span class="title function_">send</span>(message, [sendHandle])</span><br></pre></td></tr></table></figure><p>句柄是一种可以用来标识资源的引用，比如句柄可以标识一个服务器端的 socket 对象，一个客户端的 socket 对象，一个 UDP scoket，一个管道等。</p><p>发送句柄意味着主进程接收到 socket 请求后，直接将 socket 发送给工作进程，而不是重新与工作进程之间建立新的 socket 连接来转发数据。</p><p>主进程 parent.js：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> child = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="title function_">fork</span>(<span class="string">&#x27;child.js&#x27;</span>)</span><br><span class="line"><span class="comment">// Open up the server object and send the handle</span></span><br><span class="line"><span class="keyword">var</span> server = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>).<span class="title function_">createServer</span>()</span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span>(<span class="params">socket</span>) &#123;</span><br><span class="line">  socket.<span class="title function_">end</span>(<span class="string">&#x27;handled by parent\n&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">1337</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  child.<span class="title function_">send</span>(<span class="string">&#x27;server&#x27;</span>, server)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>子进程 child.js：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span>(<span class="params">m, server</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (m === <span class="string">&#x27;server&#x27;</span>) &#123;</span><br><span class="line">    server.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span>(<span class="params">socket</span>) &#123;</span><br><span class="line">      socket.<span class="title function_">end</span>(<span class="string">&#x27;handled by child\n&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>通过 node 启动查看效果：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 先启动服务器</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">node parent.js</span></span><br><span class="line">// 使用 curl 工具</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl <span class="string">&quot;http://127.0.0.1:1337&quot;</span></span></span><br><span class="line">handled by parent</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl <span class="string">&quot;http://127.0.0.1:1337&quot;</span></span></span><br><span class="line">handled by child</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl <span class="string">&quot;http://127.0.0.1:1337&quot;</span></span></span><br><span class="line">handled by child</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl <span class="string">&quot;http://127.0.0.1:1337&quot;</span></span></span><br><span class="line">handled by parent</span><br></pre></td></tr></table></figure><p>可以看出父子进程都有可能处理客户端请求。</p><p>尝试将服务发送给多个子进程。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// parent.js</span></span><br><span class="line"><span class="keyword">var</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> child1 = cp.<span class="title function_">fork</span>(<span class="string">&#x27;child.js&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> child2 = cp.<span class="title function_">fork</span>(<span class="string">&#x27;child.js&#x27;</span>)</span><br><span class="line"><span class="comment">// Open up the server object and send the handle</span></span><br><span class="line"><span class="keyword">var</span> server = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>).<span class="title function_">createServer</span>()</span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span>(<span class="params">socket</span>) &#123;</span><br><span class="line">  socket.<span class="title function_">end</span>(<span class="string">&#x27;handled by parent\n&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">1337</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  child1.<span class="title function_">send</span>(<span class="string">&#x27;server&#x27;</span>, server)</span><br><span class="line">  child2.<span class="title function_">send</span>(<span class="string">&#x27;server&#x27;</span>, server)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>子进程将进程 ID 打印出来。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// child.js</span></span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span>(<span class="params">m, server</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (m === <span class="string">&#x27;server&#x27;</span>) &#123;</span><br><span class="line">    server.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span>(<span class="params">socket</span>) &#123;</span><br><span class="line">      socket.<span class="title function_">end</span>(<span class="string">&#x27;handled by child, pid is &#x27;</span> + process.<span class="property">pid</span> + <span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>通过 curl 测试依然是相同的结果，请求可能被父进程处理，也可能被不同的子进程处理。并且这些都是在 TCP 层面完成的事情。我们尝试将其改成 HTTP 层来处理。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// parent.js</span></span><br><span class="line"><span class="keyword">var</span> cp = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> child1 = cp.<span class="title function_">fork</span>(<span class="string">&#x27;child.js&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> child2 = cp.<span class="title function_">fork</span>(<span class="string">&#x27;child.js&#x27;</span>)</span><br><span class="line"><span class="comment">// Open up the server object and send the handle</span></span><br><span class="line"><span class="keyword">var</span> server = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>).<span class="title function_">createServer</span>()</span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">1337</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  child1.<span class="title function_">send</span>(<span class="string">&#x27;server&#x27;</span>, server)</span><br><span class="line">  child2.<span class="title function_">send</span>(<span class="string">&#x27;server&#x27;</span>, server)</span><br><span class="line">  <span class="comment">// 关闭</span></span><br><span class="line">  server.<span class="title function_">close</span>()</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>对子进程进行改动</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// child.js</span></span><br><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> server = http.<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">req, res</span>) &#123;</span><br><span class="line">  res.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span> &#125;)</span><br><span class="line">  res.<span class="title function_">end</span>(<span class="string">&#x27;handled by child, pid is &#x27;</span> + process.<span class="property">pid</span> + <span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span>(<span class="params">m, tcp</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (m === <span class="string">&#x27;server&#x27;</span>) &#123;</span><br><span class="line">    tcp.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span>(<span class="params">socket</span>) &#123;</span><br><span class="line">      server.<span class="title function_">emit</span>(<span class="string">&#x27;connection&#x27;</span>, socket)</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>重新启动 parent.js 后，再次测试，所有的请求都是由子进程处理了。整个过程中，服务的过程发生了一次改变：</p><img src="/assets/img/process_handle.png" alt="process_handle"><p>主进程发送完句柄并且关闭监听之后，成了下图的结构：</p><img src="/assets/img/process_handle_1.png" alt="process_handle_1"><ol><li>句柄发送与还原</li><li>端口共同监听</li></ol><h2 id="集群稳定之路">集群稳定之路</h2>]]></content>
    
    
    <summary type="html">&lt;h1&gt;第九章 玩转进程&lt;/h1&gt;
&lt;p&gt;Node 基于 V8 引擎构建，采用单线程模型，所有的 JavaScript 将会运行在单个进程的单个线程上，它带来的好处是：没有多线程中常见的锁以及线程同步的问题，操作系统在调度时也能减少上下文切换，提高 CPU 使用率。但是如今 CPU 基本均是多核的，真正的服务器往往还有多个 CPU，一个 Node 进程只能利用一个核，这带来硬件资源的浪费。另外，Node 运行在单线程之上，一个单线程抛出异常而没有被捕获，将会导致进程的崩溃。&lt;/p&gt;
&lt;p&gt;严格来说，Node 并非真正的单线程，Node 自身中还有 I/O 线程存在，这些 I/O 线程由底层 libuv 处理，这部分线程对于 JavaScript 而言是透明的，只有 C++ 扩展时才会关注到，JavaScript 代码运行在 V8 上，是单线程的。&lt;/p&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="child_process" scheme="https://lz5z.com/tags/child-process/"/>
    
    <category term="Cluster" scheme="https://lz5z.com/tags/Cluster/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出Node.js》-WebSocket</title>
    <link href="https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-WebSocket/"/>
    <id>https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-WebSocket/</id>
    <published>2018-06-08T03:58:26.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h2 id="构建-WebSocket-服务">构建 WebSocket 服务</h2><p>WebSocket 与 Node 之间的配合可以说是天作之合：WebSocket 客户端基于事件的编程模型与 Node 中自定义事件相差无几；WebSocket 实现了客户端与服务器之间的长连接，而 Node 在与大量客户端之间保持高并发连接方面非常擅长。</p><p>WebSocket 有以下好处：</p><ol><li>客户端与服务器之间只需要建立一个 TCP 连接，可以使用更少的连接。</li><li>WebSocket 服务器可以推送数据到客户端，比 HTTP 请求响应模型更灵活。</li><li>WebSocket 协议头更加轻量，减少数据传输。</li><li>WebSocket 既可以发送文本，也可以发送二进制数据。</li><li>WebSocket 没有同源限制，客户端可以与任意服务器通信。</li><li>建立在 TCP 协议之上，与 HTTP 协议有很好的兼容性，默认端口也是 80 和 443。</li></ol><span id="more"></span><p>WebSocket 在客户端的应用示例：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ws = <span class="keyword">new</span> <span class="title class_">WebSocket</span>(<span class="string">&quot;wss://127.0.0.1:12010/updates&quot;</span>)</span><br><span class="line"></span><br><span class="line">ws.<span class="property">onopen</span> = <span class="keyword">function</span>(<span class="params">evt</span>) &#123; </span><br><span class="line">  <span class="built_in">setInterval</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (socket.<span class="property">bufferedAmount</span> == <span class="number">0</span>)</span><br><span class="line">      socket.<span class="title function_">send</span>(<span class="title function_">getUpdateData</span>())</span><br><span class="line">  &#125;, <span class="number">50</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ws.<span class="property">onmessage</span> = <span class="keyword">function</span>(<span class="params">evt</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>( <span class="string">&quot;Received Message: &quot;</span> + evt.<span class="property">data</span>);</span><br><span class="line">  ws.<span class="title function_">close</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ws.<span class="property">onclose</span> = <span class="keyword">function</span>(<span class="params">evt</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Connection closed.&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述客户端代码与服务器建立 WebSocket 连接后，每 50 毫秒向服务器发送一次数据。并且通过 onmessage 接受服务端传来的数据。</p><p>在 WebSocket 之前，服务器与客户端通信最高效的是 Comet 技术，实现原理依赖于长轮询或 iframe 流。长轮询是客户端向服务器发起请求，服务器只有在超时或者数据响应时断开连接（res.end()），客户端在收到数据或者超时后重新发起请求，这个请求拖着长长的尾巴，所以用彗星命名。</p><p>使用 WebSocket 技术，客户端只需要保持一个 TCP 连接即可完成双向通信，无需频繁断开连接和重发请求。</p><p>WebSocket 协议主要分两个部分：握手和数据传输。</p><h3 id="WebSocket-握手">WebSocket 握手</h3><p>客户端建立连接时，通过 HTTP 发起报文请求：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable constant_">GET</span> /chat <span class="variable constant_">HTTP</span>/<span class="number">1.1</span></span><br><span class="line"><span class="title class_">Host</span>: server.<span class="property">example</span>.<span class="property">com</span></span><br><span class="line"><span class="title class_">Upgrade</span>: websocket</span><br><span class="line"><span class="title class_">Connection</span>: <span class="title class_">Upgrade</span></span><br><span class="line"><span class="title class_">Sec</span>-<span class="title class_">WebSocket</span>-<span class="title class_">Key</span>: dGhlIHNhbXBsZSBub25jZQ==</span><br><span class="line"><span class="title class_">Sec</span>-<span class="title class_">WebSocket</span>-<span class="title class_">Protocol</span>: chat, superchat</span><br><span class="line"><span class="title class_">Sec</span>-<span class="title class_">WebSocket</span>-<span class="title class_">Version</span>: <span class="number">13</span> </span><br></pre></td></tr></table></figure><p>其中 Upgrade 表示请求服务器升级协议为 WebSocket；Sec-WebSocket-Protocol 和 Sec-WebSocket-Version 表示协议和版本号；Sec-WebSocket-Key 用于安全校验，是一个随机生成的 Base64 编码的字符串，与服务器响应首部的 Sec-WebSocket-Accept 是配套使用的，为 WebSocket 提供基本防护。其对应的算法如下：</p><p>将 Sec-WebSocket-Key 跟 <code>258EAFA5-E914-47DA-95CA-C5AB0DC85B11</code> 拼接，通过 SHA1 计算出摘要，并转成 base64 字符串。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> magic = <span class="string">&#x27;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#x27;</span></span><br><span class="line"><span class="keyword">var</span> val = crypto.<span class="title function_">createHash</span>(<span class="string">&#x27;sha1&#x27;</span>)</span><br><span class="line">.<span class="title function_">update</span>(secWebSocketKey + magic)</span><br><span class="line">.<span class="title function_">digest</span>(<span class="string">&#x27;bash64&#x27;</span>)</span><br></pre></td></tr></table></figure><p>服务器处理完请求后，响应的报文如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable constant_">HTTP</span>/<span class="number">1.1</span> <span class="number">101</span> <span class="title class_">Switching</span> <span class="title class_">Protocols</span></span><br><span class="line"><span class="title class_">Upgrade</span>: websocket</span><br><span class="line"><span class="title class_">Connection</span>: <span class="title class_">Upgrade</span></span><br><span class="line"><span class="title class_">Sec</span>-<span class="title class_">WebSocket</span>-<span class="title class_">Accept</span>: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=</span><br><span class="line"><span class="title class_">Sec</span>-<span class="title class_">WebSocket</span>-<span class="title class_">Protocol</span>: chat </span><br></pre></td></tr></table></figure><p>客户端收到响应后，会校验 Sec-WebSocket-Accept 的值，如果成功，就开始接下来的数据传输。</p><h3 id="WebSocket-数据传输">WebSocket 数据传输</h3><p>握手顺利完成后，就开始 WebSocket 数据帧协议，协议升级过程如下图：</p><img src="/assets/img/webSocket-upgrade.png" alt="webSocket-upgrade"><p>握手完成后，客户端的 onopen() 将会被触发。服务器端没有 onopen() 方法，为了完成 TCP socket 事件到 WebSocket 事件的封装，需要在接收数据时进行处理，WebSocket 的数据帧协议在底层的 data 事件上封装完成的：</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="title class_">WebSocket</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">setSocket</span> = <span class="keyword">function</span> (<span class="params">socket</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">socket</span> = socket</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">socket</span>.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="variable language_">this</span>.<span class="property">receiver</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>客户端调用 send() 发送数据时，服务端出发 onmessage()；当服务器调用 send() 发送数据时，客户端的 onmessage() 触发。send() 发送的数据会被协议封装为一帧或者多帧，然后逐帧发送。</p><p>为了安全考虑，客户端需要对发送的数据帧进行掩码处理，服务器一旦收到无掩码帧的数据，连接将关闭；而服务器的数据则不需要掩码处理。</p><h3 id="客户端-API">客户端 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket">API</a></h3><p>(1) WebSocket 对象作为构造函数，用于新建 WebSocket 实例。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ws = <span class="keyword">new</span> <span class="title class_">WebSocket</span>(<span class="string">&#x27;ws://localhost:8080&#x27;</span>)</span><br></pre></td></tr></table></figure><p>(2) readyState</p><ul><li>CONNECTING0连接还没开启。</li><li>OPEN1连接已开启并准备好进行通信。</li><li>CLOSING2连接正在关闭的过程中。</li><li>CLOSED3连接已经关闭，或者连接无法建立。</li></ul><p>(3) 事件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ws.<span class="property">onopen</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line">ws.<span class="property">onclose</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line">ws.<span class="property">onmessage</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 服务器返回的数据可能是文本，也可能是二进制</span></span><br><span class="line">&#125;</span><br><span class="line">ws.<span class="property">onerror</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br></pre></td></tr></table></figure><h3 id="基于-Node-的-WebSocket-服务端实现">基于 Node 的 WebSocket 服务端实现</h3><p><a href="https://socket.io/">socket.io</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> app = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>)()</span><br><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>).<span class="title class_">Server</span>(app)</span><br><span class="line"><span class="keyword">var</span> io = <span class="built_in">require</span>(<span class="string">&#x27;socket.io&#x27;</span>)(http)</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/&#x27;</span>, <span class="keyword">function</span>(<span class="params">req, res</span>)&#123;</span><br><span class="line">  res.<span class="title function_">sendFile</span>(__dirname + <span class="string">&#x27;/index.html&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">io.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span>(<span class="params">socket</span>)&#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;a user connected&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">http.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;listening on *:3000&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="总结">总结</h2><p>在所有的 WebSocket 服务器实现中，Node 最贴近 WebSocket 的使用方式：</p><ul><li>基于事件的编程接口</li><li>基于 JavaScript，API 在服务端与客户端高度相似</li></ul><p>另外，Node 基于事件驱动的方式使得它应对 WebSocket 这类长连接的应用场景时可以轻松处理大量并发请求。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;构建-WebSocket-服务&quot;&gt;构建 WebSocket 服务&lt;/h2&gt;
&lt;p&gt;WebSocket 与 Node 之间的配合可以说是天作之合：WebSocket 客户端基于事件的编程模型与 Node 中自定义事件相差无几；WebSocket 实现了客户端与服务器之间的长连接，而 Node 在与大量客户端之间保持高并发连接方面非常擅长。&lt;/p&gt;
&lt;p&gt;WebSocket 有以下好处：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端与服务器之间只需要建立一个 TCP 连接，可以使用更少的连接。&lt;/li&gt;
&lt;li&gt;WebSocket 服务器可以推送数据到客户端，比 HTTP 请求响应模型更灵活。&lt;/li&gt;
&lt;li&gt;WebSocket 协议头更加轻量，减少数据传输。&lt;/li&gt;
&lt;li&gt;WebSocket 既可以发送文本，也可以发送二进制数据。&lt;/li&gt;
&lt;li&gt;WebSocket 没有同源限制，客户端可以与任意服务器通信。&lt;/li&gt;
&lt;li&gt;建立在 TCP 协议之上，与 HTTP 协议有很好的兼容性，默认端口也是 80 和 443。&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="WebSocket" scheme="https://lz5z.com/tags/WebSocket/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出Node.js》-网络编程</title>
    <link href="https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/"/>
    <id>https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/</id>
    <published>2018-05-29T16:12:53.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>第七章 网络编程</h1><p>Node 中提供了 net，dgram，http，https 四个模块，分别用来处理 TCP，UDP，HTTP，HTTPS，适用于客户端和服务器。</p><h2 id="TCP">TCP</h2><p>TCP 传输控制协议，在 OSI 模型中属于传输层，许多应用层的协议基于 TCP 构建，比如 HTTP，SMTP，IMAP 等。回顾一下 OSI 模型。</p><ul><li>第 7 层：<strong>应用层</strong>为操作系统或网络应用程序提供访问网络服务的接口。应用层协议的代表包括： HTTP，HTTPS，FTP，TELNET，SSH，SMTP，POP3等。</li><li>第 6 层：<strong>表示层</strong>把数据转换为接受者能够兼容并且适合传输的内容，比如数据加密，压缩，格式转换等。</li><li>第 5 层：<strong>会话层</strong>负责数据传输中设置和维持网络设备之间的通信连接。管理主机之间的会话进程，还可以利用在数据中插入校验点来实现数据的同步。</li><li>第 4 层：<strong>传输层</strong>把传输表头加至数据形成数据包，完成端到端的数据传输。传输表头包含了协议等信息，比如: TCP，UDP 等。</li><li>第 3 层：<strong>网络层</strong>负责对子网间的数据包进行寻址和路由选择，还可以实现拥塞控制，网际互联等功能。网络层的协议包括：IP，IPX 等。</li><li>第 2 层：<strong>数据链路层</strong>在不可靠的物理介质上提供可靠的传输，主要主要为：物理地址寻址、数据封装成帧、流量控制、数据校验、重发等。</li><li>第 1 层：<strong>物理层</strong>在局域网上传送数据帧，负责电脑通信设备与网络媒体之间的互通，包括针脚，电压，线缆规范，集线器，网卡，主机适配等。</li></ul><span id="more"></span><p>TCP 是面向连接的协议，其显著特征是在传输之前需要 3 次握手。只有建立会话，服务端与客户端才能互相发送数据，在建立会话的过程中，服务端和客户端分别提供一个 socket，这两个 socket 共同形成连接。服务端与客户端通过 socket 实现两者之间连接的操作。</p><h3 id="创建-TCP-服务端">创建 TCP 服务端</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> net = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> server = net.<span class="title function_">createServer</span>(<span class="keyword">function</span> (<span class="params">socket</span>) &#123;</span><br><span class="line">  <span class="comment">// 新的连接</span></span><br><span class="line">  socket.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    socket.<span class="title function_">write</span>(<span class="string">&#x27;Hello&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="comment">// 断开连接</span></span><br><span class="line">  socket.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Socket end&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">  socket.<span class="title function_">write</span>(<span class="string">&#x27;Welcome&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">8124</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;server bound&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>使用 telnet 工具作为客户端对刚才创建的服务器进行连接。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">telnet 127.0.0.1 8124</span></span><br><span class="line">// 随意输入任意字符</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">Hello</span></span><br></pre></td></tr></table></figure><p>同样的，我们也可以对 Domain Socket 进行监听</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">server.<span class="title function_">listen</span>(<span class="string">&#x27;/tmp/echo.sock&#x27;</span>)</span><br></pre></td></tr></table></figure><p>通过 net 模块自行构建客户端进行会话 client.js:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> net = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> client = net.<span class="title function_">connect</span>(&#123; <span class="attr">port</span>: <span class="number">8124</span> &#125;, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">//&#x27;connect&#x27; listener</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;client connected&#x27;</span>)</span><br><span class="line">  client.<span class="title function_">write</span>(<span class="string">&#x27;world!\r\n&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">client.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(data.<span class="title function_">toString</span>())</span><br><span class="line">  client.<span class="title function_">end</span>()</span><br><span class="line">&#125;)</span><br><span class="line">client.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;client disconnected&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>注意，如果是 Domain Socket，在填写选项时，填写 path 即可。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> client = net.<span class="title function_">connect</span>(&#123;<span class="attr">path</span>: <span class="string">&#x27;/tmp/echo.sock&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><h3 id="TCP-服务的事件">TCP 服务的事件</h3><p>上述代码分为服务端事件和连接事件。</p><p>(1) 服务端事件</p><p>对于 net.createServer() 创建的服务器而言，它是一个 EventEmitter 实例，它的自定义事件有如下几种。</p><ul><li>listening：在调用 server.listen() 绑定端口或 Domain Socket 后触发，可以写作 server.listen(port, listeningListener)。</li><li>connection：每个客户端 socket 连接到服务器时触发，可以写作 net.createServer()。</li><li>close：服务器关闭时触发。server.close() 会停止接受新的 socket，但是保存已有的连接，等待所有的连接断开后触发。</li><li>error：服务器发生异常时触发。</li></ul><p>(2) 连接事件</p><p>服务器可以与多个客户端保存连接，每个连接都是典型的可读可写的 Stream 对象。它的自定义事件有如下几种。</p><ul><li>data：当一端调用 write() 发送数据，另外一端触发 data 事件。</li><li>end：当连接中的任一端发送 FIN 数据时，触发该事件。</li><li>connect：客户端 socket 与服务器连接成功适触发。</li><li>drain：rain 和 socket.write() 的返回值强关联，当任意一端调用 write()，当前这端会触发该事件。</li><li>error：异常时触发。</li><li>close：socket 关闭时触发。</li><li>timeout：一定时间连接不再活跃时，该事件触发，通知用户当前连接已经闲置。</li></ul><p>TCP socket 为可读可写 Stream 对象，可以用 pipe() 实现管道操作。如下代码实现 echo 服务器。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> net = <span class="built_in">require</span>(<span class="string">&#x27;net&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> server = net.<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">socket</span>) &#123;</span><br><span class="line">  socket.<span class="title function_">write</span>(<span class="string">&#x27;Echo server\r\n&#x27;</span>)</span><br><span class="line">  socket.<span class="title function_">pipe</span>(socket)</span><br><span class="line">&#125;)</span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">1337</span>, <span class="string">&#x27;127.0.0.1&#x27;</span>)</span><br></pre></td></tr></table></figure><p>TCP 对网络中的小数据包有一定的优化策略：Nagle 算法，用来减少网络中小数据包。Nagle 算法针对这种情况，要求缓冲区数据达到一定数量或者一定时间后才将其发出，并且 Nagle 算法合并小数据包，一次优化网络。但是可能造成数据延迟发送。</p><p>Node 中默认开启 Nagle 算法，可以调用 socket.setNoDelay(true) 关闭 Nagle 算法，使得 write() 可以立即发送数据到网络中。</p><h2 id="构建-UDP-服务">构建 UDP 服务</h2><p>UDP 又称为用户数据包服务，与 TCP 一样属于网络传输层。UDP 不是面向连接的，TCP 中一旦建立连接，所有的会话都是基于连接完成，客户端如果要与另一个 TCP 服务同学，需要另创建一个 socket 处理。在 UDP 中，一个 socket 可以与多个 UDP 服务通信。</p><p>UDP 提供面向事物的不可靠传输服务，在网络差的情况下存在丢包的问题，但是它无须连接，资源消耗低，处理快速且灵活，fico适用于那些偶尔丢一两个数据包也不会产生问题的场景，比如音频、视频等。DNS 服务基于 UDP 实现。</p><h3 id="创建-UDP-socket">创建 UDP socket</h3><p>UDP socket 既可以作为服务端，又可以作为客户端。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> dgram = <span class="built_in">require</span>(<span class="string">&#x27;dgram&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> socket = dgram.<span class="title function_">createSocket</span>(<span class="string">&#x27;upd4&#x27;</span>)</span><br></pre></td></tr></table></figure><p>(1) 创建 UDP 服务器</p><p>通过调用 dgram.bind(port, [address]) 方法创建 UDP 服务器，接收网路消息。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> dgram = <span class="built_in">require</span>(<span class="string">&#x27;dgram&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> server = dgram.<span class="title function_">createSocket</span>(<span class="string">&#x27;udp4&#x27;</span>)</span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="keyword">function</span>(<span class="params">msg, rinfo</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;server got: &#x27;</span> + msg + <span class="string">&#x27; from &#x27;</span> +</span><br><span class="line">    rinfo.<span class="property">address</span> + <span class="string">&#x27;:&#x27;</span> + rinfo.<span class="property">port</span>)</span><br><span class="line">&#125;)</span><br><span class="line">server.<span class="title function_">on</span>(<span class="string">&#x27;listening&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> address = server.<span class="title function_">address</span>()</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;server listening &#x27;</span> +</span><br><span class="line">    address.<span class="property">address</span> + <span class="string">&#x27;:&#x27;</span> + address.<span class="property">port</span>)</span><br><span class="line">&#125;)</span><br><span class="line">server.<span class="title function_">bind</span>(<span class="number">41234</span>)</span><br></pre></td></tr></table></figure><p>(2) 创建 UDP 客户端</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> dgram = <span class="built_in">require</span>(<span class="string">&#x27;dgram&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> message = <span class="title class_">Buffer</span>.<span class="title function_">alloc</span>(<span class="number">13</span>, <span class="string">&#x27;Hello Node.js&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> client = dgram.<span class="title function_">createSocket</span>(<span class="string">&#x27;udp4&#x27;</span>)</span><br><span class="line">client.<span class="title function_">send</span>(message, <span class="number">0</span>, message.<span class="property">length</span>, <span class="number">41234</span>, <span class="string">&#x27;localhost&#x27;</span>,</span><br><span class="line">  <span class="keyword">function</span>(<span class="params">err, bytes</span>) &#123;</span><br><span class="line">    client.<span class="title function_">close</span>()</span><br><span class="line">  &#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>客户端执行后，服务端输出：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">node main.js</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">server listening 0.0.0.0:41234</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">server got: Hello Node.js from 127.0.0.1:61286</span></span><br></pre></td></tr></table></figure><p>当 socket 在客户端时，可以调用 send() 方法发生消息到网络。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">socket.<span class="title function_">send</span>(buf, offset, length, port, address, [callback])</span><br></pre></td></tr></table></figure><p>(3) UDP socket 事件</p><p>UDP 相对于 TCP 更简单，它只是一个 EventEmitter 的实例，而非 Stream 的实例。它自定义事件如下：</p><ul><li>message：当 UDP socket 侦听网卡端口后，接收到消息时触发该事件。</li><li>listening：当 UDP 开始侦听时触发该事件。</li><li>close：调用 close() 方法时触发该事件，并不再触发 message 事件。</li><li>error：发生异常时触发该事件。</li></ul><h2 id="构建-HTTP-服务">构建 HTTP 服务</h2><p>TCP 与 UDP 都属于网络传输层协议，如果要构造高效的网络应用，就应该从传输层进行着手。但是一般使用应用层协议就能满足我们大部分开发需求。Node 提供基本的 http 和 https 模块用于 HTTP 和 HTTPS 的封装。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>)</span><br><span class="line">http.<span class="title function_">createServer</span>(<span class="keyword">function</span> (<span class="params">req, res</span>) &#123;</span><br><span class="line">  res.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123;<span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span>&#125;)</span><br><span class="line">  res.<span class="title function_">end</span>(<span class="string">&#x27;Hello World&#x27;</span>)</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">1337</span>, <span class="string">&#x27;127.0.0.1&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Server running at http://127.0.0.1:1337/&#x27;</span>)</span><br></pre></td></tr></table></figure><h3 id="HTTP">HTTP</h3><p>HTTP 构建于 TCP 之上，属于应用层协议。</p><p>使用 curl 查看网络通信的报文信息。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl -v http://127.0.0.1:1337</span></span><br><span class="line">* About to connect() to 127.0.0.1 port 1337 (#0)</span><br><span class="line">*   Trying 127.0.0.1...</span><br><span class="line">* Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)</span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">GET / HTTP/1.1</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">User-Agent: curl/7.29.0</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">Host: 127.0.0.1:1337</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">Accept: */*</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash">&lt; HTTP/1.1 200 OK</span></span><br><span class="line">&lt; Date: Mon, 04 Jun 2018 15:34:30 GMT</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">&lt; Transfer-Encoding: chunked</span><br><span class="line">&lt; </span><br><span class="line">Hello World</span><br><span class="line">* Connection #0 to host 127.0.0.1 left intact</span><br><span class="line">* Closing connection #0 </span><br></pre></td></tr></table></figure><p>报文解析：</p><p>(1) TCP 三次握手</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">* About to connect() to 127.0.0.1 port 1337 (#0)</span><br><span class="line">*   Trying 127.0.0.1...</span><br><span class="line">* Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)</span><br></pre></td></tr></table></figure><p>(2) 客户端向服务端发送请求报文</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">GET / HTTP/1.1</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">User-Agent: curl/7.29.0</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">Host: 127.0.0.1:1337</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">Accept: */*</span></span><br><span class="line"><span class="meta prompt_">&gt; </span></span><br></pre></td></tr></table></figure><p>(3) 服务器响应客户端内容</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt; HTTP/1.1 200 OK</span><br><span class="line">&lt; Date: Mon, 04 Jun 2018 15:34:30 GMT</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">&lt; Transfer-Encoding: chunked</span><br><span class="line">&lt; </span><br><span class="line">Hello World</span><br></pre></td></tr></table></figure><p>(4) 结束会话</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">* Connection #0 to host 127.0.0.1 left intact</span><br><span class="line">* Closing connection #0 </span><br></pre></td></tr></table></figure><p>从上述报文信息中可以看出 HTTP 的特点：基于请求响应式的，以一问一答的方式实现服务，虽然基于 TCP 会话，但是本身并无会话的特点。</p><h3 id="http-模块">http 模块</h3><p>Node 的 http 模块包含对 HTTP 处理的封装，在 Node 中，HTTP 服务继承自 TCP 服务（net 模块），它能够与多个客户端保持连接，采用事件驱动的形式，并不为每一个连接创建额外的线程或者进程，占用很低的内存，并且实现高并发。</p><p>HTTP 服务与 TCP 服务的区别在于，开启 keepalive 后，一个 TCP 会话可以用于多次请求和响应，TCP 以 connection 为单位进行服务，HTTP 服务以 request 为单位进行服务。http 模块即是将 connection 到 request 的过程进行了封装。</p><p>除此之外，http 模块将连接所用的 socket 的读写抽象为 ServerRequest 和 ServerResponse 对象，它们分别对应请求和响应操作。在请求产生的过程中，http 模块拿到连接中传来的数据，调用二进制模块 http_parser 进行解析，在解析完请求报文的报头后，触发 request 事件，调用用户的业务逻辑。</p><p>(1) HTTP 请求</p><p>对于 TCP 连接的读操作，http 模块将其封装为 ServerRequest 对象。报头通过 http_parser 进行解析。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">GET / HTTP/1.1</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">User-Agent: curl/7.29.0</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">Host: 127.0.0.1:1337</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">Accept: */*</span></span><br><span class="line"><span class="meta prompt_">&gt; </span></span><br></pre></td></tr></table></figure><ul><li>req.method 属性： GET</li><li>req.url 属性： /</li><li>req.httpVersion 属性： 1.1<br>其余报头是很规律的 key: Value 格式，被解析后放置在 req.headers 属性上传递给业务逻辑调用。</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">headers: &#123;</span><br><span class="line">  &#x27;user-agent&#x27;: &#x27;curl/7.29.0&#x27;,</span><br><span class="line">  host: &#x27;127.0.0.7:1337&#x27;,</span><br><span class="line">  accept: &#x27;*/*&#x27;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>报文体部分则抽象为一个只读流对象，如果业务逻辑需要读取报文体中的数据，则要在这个数据流结束后才能进行操作。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> (<span class="params">req, res</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> buffers = []</span><br><span class="line">  req.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span> (<span class="params">trunk</span>) &#123;</span><br><span class="line">    buffers.<span class="title function_">push</span>(trunk)</span><br><span class="line">  &#125;).<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> buffer = <span class="title class_">Buffer</span>.<span class="title function_">concat</span>(buffers)</span><br><span class="line">    res.<span class="title function_">end</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(2) HTTP 响应</p><p>HTTP 响应对象封装了底层连接的写操作，可以将其看作一个可写的流对象，通过 res.setHeader() 和 res.writeHead() 响应报文头部信息。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">res.<span class="title function_">writeHead</span>(<span class="number">200</span>, &#123;<span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><p>转化为报文如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt; HTTP/1.1 200 OK</span><br><span class="line">&lt; Content-Type: text/plain</span><br></pre></td></tr></table></figure><p>setHeader 可以进行多次调用，但只有调用 writeHead 后，报文才会写入到连接中，此外，http 模块还会自动设置一些头信息。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt; Date: Mon, 04 Jun 2018 15:34:30 GMT</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">&lt; Transfer-Encoding: chunked</span><br><span class="line">&lt; </span><br></pre></td></tr></table></figure><p>报文体则是通过调用 res.write() 和 res.end() 方法实现，区别在于 res.end() 会调用 write() 发送数据，然后发送信号告知服务器这次响应结束。</p><p>响应结束后，HTTP 服务器可能将当期连接用于下一次请求，或者关闭连接。另外，无法服务器在处理业务逻辑时是否发生异常，务必在结束时调用 res.end() 结束请求，否则客户端将一直处于等待的状态。当然也可以通过延迟 res.end() 的方式实现客户端与服务器之间的长连接，但结束时务必关闭连接。</p><p>(3) HTTP 服务的事件</p><p>HTTP 服务器抽象了一些事件，供应用层使用，服务器也是一个 EventEmitter 实例。</p><ul><li>connection 事件：HTTP 请求响应前触发，客户端与服务器建立底层的 TCP 连接时触发。</li><li>request 事件：建立 TCP 连接后，http 模块底层将在数据流中抽象出 HTTP 请求和响应，当解析出 HTTP 请求头时，触发该事件。</li><li>close 事件：调用 server.close() 方法停止接受新的连接，并且已有连接全部断开时触发。</li><li>checkContinue 事件：客户端发送较大的数据时，并不会直接将数据发送，而是先发一个头部带 <code>Expect: 100-continue</code> 的请求到服务器，服务器将触发 checkContinue 事件。如果服务器没有监听这个事件，则会自动响应客户端 100 Continue 的状态码，表示接受数据上传。如果不接受，或者客户端数据较多时，响应 400 Bad Request 拒绝客户端继续发送数据。</li><li>connect 事件：当客户端发起 CONNECT 请求时触发。而发起 CONNECT 请求通常在 HTTP 代理时出现，如果不监听该事件，发起请求的连接将会关闭。</li><li>upgrade 事件：客户端要求升级连接协议时触发。</li><li>clientError 事件：连接的客户端触发 error 事件时，这个错误会传递到服务器，此时触发该事件。</li></ul><p>(4) HTTP 客户端</p><p>http 模块通过调用 http.request(options, connect) 构造客户端。与上文的 curl 大致相同：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> options = &#123;</span><br><span class="line">  <span class="attr">hostname</span>: <span class="string">&#x27;127.0.0.1&#x27;</span>,</span><br><span class="line">  <span class="attr">port</span>: <span class="number">1334</span>,</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">  <span class="attr">method</span>: <span class="string">&#x27;GET&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> req = http.<span class="title function_">request</span>(options, <span class="keyword">function</span> (<span class="params">res</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;STATUS: &#x27;</span> + res.<span class="property">statusCode</span>)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;HEADERS: &#x27;</span> + <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(res.<span class="property">headers</span>))</span><br><span class="line">  res.<span class="title function_">setEncoding</span>(<span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line">  res.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span> (<span class="params">chunk</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(chunk)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line">req.<span class="title function_">end</span>()</span><br></pre></td></tr></table></figure><p>执行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">node client.js</span></span><br><span class="line">STATUS: 200</span><br><span class="line">HEADERS: &#123;&quot;date&quot;:&quot;Mon, 04 Jun 2018 15:34:30 GMT&quot;,&quot;connection&quot;:&quot;keep-alive&quot;,&quot;transfer-encoding&quot;:&quot;chunked&quot;&#125;</span><br><span class="line">Hello World </span><br></pre></td></tr></table></figure><p>options 中选项有如下这些：</p><ul><li>host</li><li>hostname</li><li>port：默认 80</li><li>localAddress：建立网络连接的本地网卡</li><li>socketPath</li><li>method：默认为 GET</li><li>path：请求路径，默认为 /</li><li>headers</li><li>auth: Basic 认证，这个值将被计算成请求头中的 Authorization 部分。</li></ul><p>(5) HTTP 代理</p><p>http 提供的 ClientRequest 对象也是基于 TCP 层实现的，在 keepalive 的情况下，一个底层的会话连接可以用于多次请求。为了重用 TCP 连接，http 模块包含一个默认的客户端代理对象 http.globalAgent。</p><p>http.globalAgent 对每个服务器端（host + port）创建的连接进行管理，默认情况下，每个请求最多可以创建 5 个连接，它的实质是一个连接池。</p><p>调用 HTTP 客户端对一个服务器发起 10 次 HTTP 请求时，其实质只有 5 个请求处于并发状态，后续的请求需要等待某个请求完成后才真正发出，与浏览器对同一域名的并发限制相同。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> agent = <span class="keyword">new</span> http.<span class="title class_">Agent</span>(&#123;</span><br><span class="line">  <span class="attr">maxSockets</span>: <span class="number">10</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">var</span> options = &#123;</span><br><span class="line">  <span class="attr">hostname</span>: <span class="string">&#x27;127.0.0.1&#x27;</span>,</span><br><span class="line">  <span class="attr">port</span>: <span class="number">1334</span>,</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">  <span class="attr">method</span>: <span class="string">&#x27;GET&#x27;</span>,</span><br><span class="line">  <span class="attr">agent</span>: agent</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>也可以设置 agent 选项为 false，以脱离连接池管理，使请求不受并发限制。</p><p>(6) HTTP 客户端事件</p><ul><li>response：客户端收到服务器的响应时触发。</li><li>socket：当底层连接池中简历的连接分配给当前请求对象时触发该事件。</li><li>connect：客户端向服务器发起 CONNECT 请求时，如果服务器响应了 200 状态码，客户端触发。</li><li>upgrade：客户端发起 Upgrade 请求时，如果服务器响应了 101 Switching Protocols 状态，客户端触发。</li><li>continue：客户端向服务器发起 Expect: 100-continue 头信息，服务服务器响应 100 Continue 状态，客户端触发。</li></ul>]]></content>
    
    
    <summary type="html">&lt;h1&gt;第七章 网络编程&lt;/h1&gt;
&lt;p&gt;Node 中提供了 net，dgram，http，https 四个模块，分别用来处理 TCP，UDP，HTTP，HTTPS，适用于客户端和服务器。&lt;/p&gt;
&lt;h2 id=&quot;TCP&quot;&gt;TCP&lt;/h2&gt;
&lt;p&gt;TCP 传输控制协议，在 OSI 模型中属于传输层，许多应用层的协议基于 TCP 构建，比如 HTTP，SMTP，IMAP 等。回顾一下 OSI 模型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 7 层：&lt;strong&gt;应用层&lt;/strong&gt;为操作系统或网络应用程序提供访问网络服务的接口。应用层协议的代表包括： HTTP，HTTPS，FTP，TELNET，SSH，SMTP，POP3等。&lt;/li&gt;
&lt;li&gt;第 6 层：&lt;strong&gt;表示层&lt;/strong&gt;把数据转换为接受者能够兼容并且适合传输的内容，比如数据加密，压缩，格式转换等。&lt;/li&gt;
&lt;li&gt;第 5 层：&lt;strong&gt;会话层&lt;/strong&gt;负责数据传输中设置和维持网络设备之间的通信连接。管理主机之间的会话进程，还可以利用在数据中插入校验点来实现数据的同步。&lt;/li&gt;
&lt;li&gt;第 4 层：&lt;strong&gt;传输层&lt;/strong&gt;把传输表头加至数据形成数据包，完成端到端的数据传输。传输表头包含了协议等信息，比如: TCP，UDP 等。&lt;/li&gt;
&lt;li&gt;第 3 层：&lt;strong&gt;网络层&lt;/strong&gt;负责对子网间的数据包进行寻址和路由选择，还可以实现拥塞控制，网际互联等功能。网络层的协议包括：IP，IPX 等。&lt;/li&gt;
&lt;li&gt;第 2 层：&lt;strong&gt;数据链路层&lt;/strong&gt;在不可靠的物理介质上提供可靠的传输，主要主要为：物理地址寻址、数据封装成帧、流量控制、数据校验、重发等。&lt;/li&gt;
&lt;li&gt;第 1 层：&lt;strong&gt;物理层&lt;/strong&gt;在局域网上传送数据帧，负责电脑通信设备与网络媒体之间的互通，包括针脚，电压，线缆规范，集线器，网卡，主机适配等。&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="TCP" scheme="https://lz5z.com/tags/TCP/"/>
    
    <category term="网络" scheme="https://lz5z.com/tags/%E7%BD%91%E7%BB%9C/"/>
    
    <category term="UDP" scheme="https://lz5z.com/tags/UDP/"/>
    
    <category term="OSI" scheme="https://lz5z.com/tags/OSI/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出Node.js》-理解Buffer</title>
    <link href="https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E7%90%86%E8%A7%A3Buffer/"/>
    <id>https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E7%90%86%E8%A7%A3Buffer/</id>
    <published>2018-05-28T14:11:29.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>第六章 理解 Buffer</h1><h2 id="Buffer-结构">Buffer 结构</h2><p>Buffer 是一个像 Array 的对象，主要用来操作字节。Buffer 是一个典型的 JavaScript 与 C++ 结合的模块，它将性能相关的部分用 C++ 实现，将非性能相关的部分用 JavaScript 实现。</p><p>Buffer 所占用的内存不是通过 V8 分配的，而是堆外内存。由于 V8 垃圾回收性能的影响，将 Buffer 对象用更高效的专有内存分配回收策略来管理。</p><p>Buffer 在 Node 进程启动的时候已经载入了，并将其放在全局对象 global 上，因此无需 require() 就能使用。</p><h3 id="Buffer-对象">Buffer 对象</h3><p>Buffer 的元素为 16 进制的两位数，即 0 到 255 的数值。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;深入浅出node.js&#x27;</span></span><br><span class="line"><span class="keyword">var</span> buf = <span class="keyword">new</span> <span class="title class_">Buffer</span>(str, <span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(buf) <span class="comment">// &lt;Buffer e6 b7 b1 e5 85 a5 e6 b5 85 e5 87 ba 6e 6f 64 65 2e 6a 73&gt;</span></span><br></pre></td></tr></table></figure><p>不同编码的字符串占用的元素个数各不相同，中文在 UTF-8 编码下占用 3 个元素，字母和半角标点占用 1 个元素。</p><p>Buffer 可以通过 length 属性得到长度，也可以通过下标访问元素。</p><span id="more"></span><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">var</span> buf = <span class="keyword">new</span> <span class="title class_">Buffer</span>(<span class="number">100</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(buf.<span class="property">length</span>) <span class="comment">// 100</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(buf[<span class="number">10</span>]) <span class="comment">// 0</span></span><br><span class="line">buf[<span class="number">10</span>] = <span class="number">100</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(buf[<span class="number">10</span>]) <span class="comment">// 100</span></span><br></pre></td></tr></table></figure><p>如果给元素赋值不是 0 到 255 的整数而是小数，Buffer 通过不断 +256 或者不断 -256 得到一个位于 0 - 255 之间的整数。如果是小数，则直接舍弃小数部分，只保留整数部分。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">buf[<span class="number">10</span>] = -<span class="number">100</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(buf[<span class="number">10</span>]) <span class="comment">// 156</span></span><br><span class="line">buf[<span class="number">10</span>] = <span class="number">300</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(buf[<span class="number">10</span>]) <span class="comment">// 44</span></span><br><span class="line">buf[<span class="number">10</span>] = <span class="number">3.1415</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(buf[<span class="number">10</span>]) <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><h3 id="Buffer-内存分配">Buffer 内存分配</h3><p>Buffer 对象的内存不是在 V8 堆内存中，而且 Node 的 C++ 层面实现的内存申请。因为处理大量的字节数据不能采用需要一点内存就像操作系统申请一点内存的方式，这可能造成大量内存申请的系统调用，对操作系统有一定压力。Node 使用的策略是在 C++ 层面申请内存，在 JavaScript 中分配内存。</p><p>Node 操作 Buffer 使用 slab 内存分配策略。slab 是一种动态内存管理机制，最早出现于 SunOS，目前广泛应用于 Linux。</p><p>slab 是一块申请好的固定大小的内存区域。一共有三种状态： full：完全分配状态，partial：部分分配状态；empty：没有分配状态。</p><p>当我们需要一个 Buffer 对象，可以通过传入 size 来指定 Buffer 对象大小：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Buffer</span>(size)</span><br></pre></td></tr></table></figure><p>Node 以 8kb 为界限来区分 Buffer 是大对象还是小对象。这个 8kb 也就是每个 slab 的值，在 JavaScript 层面，以它作为单位进行内存分配。</p><p>(1) 小 Buffer 对象</p><p>如果指定 Buffer 的大小小于 8kb，Node会按照小对象的方式进行分配。</p><p>(2) 大 Buffer 对象</p><p>如果是超过 8kb 的对象，将会直接分配一个 SlowBuffer 对象作为 slab 单元，这个 slab 单元将被这个大 Buffer 对象独占。</p><h2 id="Buffer-转换">Buffer 转换</h2><p>Buffer 对象可以与字符串直接互相转换，目前支持的字符串编码类型有：ASCII、UTF-8、UTF-16LE/USC-2、Base64、Binary、Hex。</p><h3 id="字符串转-Buffer">字符串转 Buffer</h3><p>字符串可以通过 Buffer 构造函数转换为 Buffer 对象，存储的只能说一种编码类型。encoding 参数不传递时，默认按照 UTF-8 编码进行转码和存储。一个 Buffer 对象可以存储不同编码类型的字符串转码的值，调用 write() 可以实现。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Buffer</span>(str, [encoding])</span><br><span class="line">buf.<span class="title function_">write</span>(string, [offset], [length], [encoding])</span><br></pre></td></tr></table></figure><p>由于可以不断写内容到 Buffer 对象中，并且每次都可以指定编码，所以 Buffer 对象中可以存在多种编码转化后的内容，需要注意的是，每种编码所用的字节长度不同，反转 Buffer 回字符串时需要谨慎处理。</p><h3 id="Buffer-转字符串">Buffer 转字符串</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">buf.<span class="title function_">toString</span>([encoding], [start], [end])</span><br></pre></td></tr></table></figure><p>可以设置 encoding，start，end 这 3 个参数实现整体或者局部的转化。</p><h3 id="Buffer-不支持的编码类型">Buffer 不支持的编码类型</h3><p>由于 Node 中 Buffer 对象只支持上述几种类型的编码，因此可以用 isEncoding() 函数判断编码是否支持转化。</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></pre></td><td class="code"><pre><span class="line"><span class="title class_">Buffer</span>.<span class="title function_">isEncoding</span>(encoding)</span><br><span class="line"><span class="title class_">Buffer</span>.<span class="title function_">isEncoding</span>(<span class="string">&#x27;GBK&#x27;</span>) <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Buffer</span>.<span class="title function_">isEncoding</span>(<span class="string">&#x27;UTF-8&#x27;</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>如果需要转化其它类型的编码，可以借助 <a href="https://github.com/bnoordhuis/node-iconv">iconv</a> 和 <a href="https://github.com/ashtuchkin/iconv-lite">iconv-lite</a> 两个模块。</p><p>iconv-lite 由纯 JavaScript 实现，iconv 则是通过 C++ 调用 libiconv 库实现，前者比后者更轻量，无需编译和处理环境依赖。</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">var</span> iconv = <span class="built_in">require</span>(<span class="string">&#x27;iconv-lite&#x27;</span>)</span><br><span class="line"><span class="comment">// Buffer 转字符串</span></span><br><span class="line"><span class="keyword">var</span> str = icon.<span class="title function_">decode</span>(buf, <span class="string">&#x27;win1251&#x27;</span>)</span><br><span class="line"><span class="comment">// 字符串转 Buffer </span></span><br><span class="line"><span class="keyword">var</span> buf = iconv.<span class="title function_">encode</span>(<span class="string">&#x27;Sample input string&#x27;</span>, <span class="string">&#x27;win1251&#x27;</span>)</span><br></pre></td></tr></table></figure><h2 id="Buffer-拼接">Buffer 拼接</h2><p>Buffer 常用于从输入流中读取内容</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> rs = fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;./test.md&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> data = <span class="string">&#x27;&#x27;</span></span><br><span class="line">rs.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span> (<span class="params">chunk</span>) &#123;</span><br><span class="line">  data += chunk</span><br><span class="line">&#125;)</span><br><span class="line">rs.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span> (<span class="params">chunk</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>上述代码在英文环境中一般不会出现问题，但是在中文环境中，经常会看到乱码。data 事件中获取的 chunk 对象为 Buffer 对象，上述代码将其当做字符串处理：<code>data += chunk</code> 本质上是 <code>data = data.toString() + chunk.toString()</code>。在英文环境中，toString() 不会造成任何问题，但是对于宽字节的中文，却会形成问题。</p><p>我们创建 <a href="http://test.md">test.md</a>，内容为李白的《静夜思》，修改刚才的代码。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> rs = fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;./test.md&#x27;</span> &#123; <span class="attr">highWaterMark</span>: <span class="number">11</span> &#125;)</span><br></pre></td></tr></table></figure><p>输出结果如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">窗前明��光，疑���地上霜，举头��明月，���头思故乡。</span><br></pre></td></tr></table></figure><p>下面我们来分析乱码是怎么来的。</p><h3 id="乱码是如何产生的">乱码是如何产生的</h3><p>上面传的参数 highWaterMark 的作用是限制 Buffer 对象的长度为 11。前面说到中文 UTF-8 为 3 个字节，所以前 3 个字“床前明”能够正常输出，后面 11 - 3 * 3 = 2 个字节无法正常解析为 UTF-8 的中文字符串，所以输出乱码。在调用 toString() 的时候，默认使用 UTF-8 编码。后面的乱码都是相同的道理。</p><h3 id="setEncoding-与-string-decoder">setEncoding() 与 string_decoder()</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> rs = fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;./test.md&#x27;</span>, &#123; <span class="attr">highWaterMark</span>: <span class="number">11</span> &#125;)</span><br><span class="line">rs.<span class="title function_">setEncoding</span>(<span class="string">&#x27;utf8&#x27;</span>)</span><br></pre></td></tr></table></figure><p>setEncoding() 的作用是让 data 事件中传递的不再是一个 Buffer 对象，而是编码后的字符串。改进后重新执行，得到正确的输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">窗前明月光，疑是地上霜，举头望明月，低头思故乡。</span><br></pre></td></tr></table></figure><p>在调用 setEncoding() 的时候，可读流对象在内部设置了一个 decoder 对象，每次 data 事件都是通过 decoder 对象进行 Buffer 到字符串的解析。</p><h2 id="Buffer-性能">Buffer 性能</h2><p>Buffer 在文件 I/O 和网络 I/O 中运用广泛，在应用中，通常操作字符串，但一旦在网络中传输，都需要转换为 Buffer，以二进制数据进行传输。</p><h3 id="测试">测试</h3><p>构造一个 10kb 大小的字符串，通过纯字符串的方式向客户端发送：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> helloworld = <span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">1024</span> * <span class="number">10</span>; i++) &#123;</span><br><span class="line">  helloworld += <span class="string">&#x27;a&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// helloworld = new Buffer(helloworld)</span></span><br><span class="line">http.<span class="title function_">createServer</span>(<span class="keyword">function</span> (<span class="params">req, res</span>) &#123;</span><br><span class="line">  res.<span class="title function_">writeHead</span>(<span class="number">200</span>)</span><br><span class="line">  res.<span class="title function_">end</span>(helloworld)</span><br><span class="line">&#125;).<span class="title function_">listen</span>(<span class="number">8001</span>)</span><br></pre></td></tr></table></figure><p>使用 ab 进行性能测试，发起 200 个并发客户端：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ab -c 200 -t 100 http://127.0.0.1:8001</span><br></pre></td></tr></table></figure><p>在我的腾讯云上单核 1G CPU，1G 内存的服务器上测试结果如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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></pre></td><td class="code"><pre><span class="line">Server Software:</span><br><span class="line">Server Hostname:        127.0.0.1</span><br><span class="line">Server Port:            8001</span><br><span class="line"></span><br><span class="line">Document Path:          /</span><br><span class="line">Document Length:        10240 bytes</span><br><span class="line"></span><br><span class="line">Concurrency Level:      200</span><br><span class="line">Time taken for tests:   13.104 seconds</span><br><span class="line">Complete requests:      50000</span><br><span class="line">Failed requests:        0</span><br><span class="line">Write errors:           0</span><br><span class="line">Total transferred:      515750000 bytes</span><br><span class="line">HTML transferred:       512000000 bytes</span><br><span class="line">Requests per second:    3815.61 [#/sec] (mean)</span><br><span class="line">Time per request:       52.416 [ms] (mean)</span><br><span class="line">Time per request:       0.262 [ms] (mean, across all concurrent requests)</span><br><span class="line">Transfer rate:          38435.54 [Kbytes/sec] received</span><br><span class="line"></span><br><span class="line">Connection Times (ms)</span><br><span class="line">              min  mean[+/-sd] median   max</span><br><span class="line">Connect:        0   16 129.3      1    3014</span><br><span class="line">Processing:     7   35  10.2     38     255</span><br><span class="line">Waiting:        1   34  10.4     37     254</span><br><span class="line">Total:          7   51 130.0     39    3047</span><br><span class="line"></span><br><span class="line">Percentage of the requests served within a certain time (ms)</span><br><span class="line"><span class="meta prompt_">  50% </span><span class="language-bash">    39</span></span><br><span class="line"><span class="meta prompt_">  66% </span><span class="language-bash">    40</span></span><br><span class="line"><span class="meta prompt_">  75% </span><span class="language-bash">    40</span></span><br><span class="line"><span class="meta prompt_">  80% </span><span class="language-bash">    40</span></span><br><span class="line"><span class="meta prompt_">  90% </span><span class="language-bash">    43</span></span><br><span class="line"><span class="meta prompt_">  95% </span><span class="language-bash">    52</span></span><br><span class="line"><span class="meta prompt_">  98% </span><span class="language-bash">    56</span></span><br><span class="line"><span class="meta prompt_">  99% </span><span class="language-bash">  1040</span></span><br><span class="line"><span class="meta prompt_"> 100% </span><span class="language-bash">  3047 (longest request)</span></span><br></pre></td></tr></table></figure><p>测试的 QPS（每秒查询次数）为 3815.61，传输率为 38435.54。<br>去掉 helloworld = new Buffer(helloworld) 前面的注释，再次测试：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Server</span> <span class="title class_">Software</span>:</span><br><span class="line"><span class="title class_">Server</span> <span class="title class_">Hostname</span>:        <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line"><span class="title class_">Server</span> <span class="title class_">Port</span>:            <span class="number">8001</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Document</span> <span class="title class_">Path</span>:          /</span><br><span class="line"><span class="title class_">Document</span> <span class="title class_">Length</span>:        <span class="number">10240</span> bytes</span><br><span class="line"></span><br><span class="line"><span class="title class_">Concurrency</span> <span class="title class_">Level</span>:      <span class="number">200</span></span><br><span class="line"><span class="title class_">Time</span> taken <span class="keyword">for</span> <span class="attr">tests</span>:   <span class="number">7.260</span> seconds</span><br><span class="line"><span class="title class_">Complete</span> <span class="attr">requests</span>:      <span class="number">50000</span></span><br><span class="line"><span class="title class_">Failed</span> <span class="attr">requests</span>:        <span class="number">0</span></span><br><span class="line"><span class="title class_">Write</span> <span class="attr">errors</span>:           <span class="number">0</span></span><br><span class="line"><span class="title class_">Total</span> <span class="attr">transferred</span>:      <span class="number">515750000</span> bytes</span><br><span class="line"><span class="variable constant_">HTML</span> <span class="attr">transferred</span>:       <span class="number">512000000</span> bytes</span><br><span class="line"><span class="title class_">Requests</span> per <span class="attr">second</span>:    <span class="number">6886.98</span> [#/sec] (mean)</span><br><span class="line"><span class="title class_">Time</span> per <span class="attr">request</span>:       <span class="number">29.040</span> [ms] (mean)</span><br><span class="line"><span class="title class_">Time</span> per <span class="attr">request</span>:       <span class="number">0.145</span> [ms] (mean, across all concurrent requests)</span><br><span class="line"><span class="title class_">Transfer</span> <span class="attr">rate</span>:          <span class="number">69374.22</span> [<span class="title class_">Kbytes</span>/sec] received</span><br><span class="line"></span><br><span class="line"><span class="title class_">Connection</span> <span class="title class_">Times</span> (ms)</span><br><span class="line">              min  mean[+/-sd] median   max</span><br><span class="line"><span class="title class_">Connect</span>:        <span class="number">0</span>    <span class="number">7</span>  <span class="number">71.0</span>      <span class="number">2</span>    <span class="number">1012</span></span><br><span class="line"><span class="title class_">Processing</span>:     <span class="number">7</span>   <span class="number">17</span>  <span class="number">10.1</span>     <span class="number">14</span>     <span class="number">417</span></span><br><span class="line"><span class="title class_">Waiting</span>:        <span class="number">7</span>   <span class="number">15</span>  <span class="number">10.3</span>     <span class="number">12</span>     <span class="number">401</span></span><br><span class="line"><span class="title class_">Total</span>:         <span class="number">10</span>   <span class="number">24</span>  <span class="number">72.7</span>     <span class="number">16</span>    <span class="number">1234</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Percentage</span> <span class="keyword">of</span> the requests served within a certain <span class="title function_">time</span> (ms)</span><br><span class="line">  <span class="number">50</span>%     <span class="number">16</span></span><br><span class="line">  <span class="number">66</span>%     <span class="number">17</span></span><br><span class="line">  <span class="number">75</span>%     <span class="number">18</span></span><br><span class="line">  <span class="number">80</span>%     <span class="number">18</span></span><br><span class="line">  <span class="number">90</span>%     <span class="number">30</span></span><br><span class="line">  <span class="number">95</span>%     <span class="number">32</span></span><br><span class="line">  <span class="number">98</span>%     <span class="number">41</span></span><br><span class="line">  <span class="number">99</span>%     <span class="number">48</span></span><br><span class="line"> <span class="number">100</span>%   <span class="number">1234</span> (longest request)</span><br></pre></td></tr></table></figure><p>测试的 QPS（每秒查询次数）为 6886.98，传输率为 69374.22。性能提升了近一倍。</p><p>通过预先转换静态内容为 Buffer 对象，可以有效减少 CPU 重复使用，节省服务器资源。在 Node 构建的 Web 应用中，可以选择将页面中的动态内容和静态内容分类，静态内容预先转换为 Buffer 对象，使性能得到提升。由于文件本身是二进制数据，所以在不需要改变内容的场景中，设置 Buffer 为只读，不做额外的转换能达到更好的效果。</p><h3 id="文件读取">文件读取</h3><p>通过 fs.createReadStream(path, opts) 创建文件读流，其中可以传入的参数为：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">flags</span>: <span class="string">&#x27;r&#x27;</span>,</span><br><span class="line">  <span class="attr">encoding</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">fd</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">mode</span>: <span class="number">0666</span>,</span><br><span class="line">  <span class="attr">autoClose</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">highWaterMark</span>: <span class="number">64</span> &amp; <span class="number">1024</span>    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>opts 可以包括 start 和 end 值，使其可以从文件读取一定范围的字节而不是整个文件。例如从 100 个字节的文件中读取最后 10 个字节：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;sample.txt&#x27;</span>, &#123; <span class="attr">start</span>: <span class="number">90</span>, <span class="attr">end</span>: <span class="number">99</span> &#125;)</span><br></pre></td></tr></table></figure><p>fs.createReadStream() 的工作方式是在内存中准备一段 Buffer，然后在 fs.read() 读取时逐步从磁盘中将字节复制到 Buffer，完成一次读取后，从这个 Buffer 中通过 slice() 方法取出部分数据作为一个小 Buffer 对象，再通过 data 事件传递给调用方。如果 Buffer 用完，则重新分配一个，如果还有剩余则继续使用。</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">var</span> pool </span><br><span class="line"><span class="keyword">function</span> <span class="title function_">allocNewPool</span> (<span class="params">poolSize</span>) &#123;</span><br><span class="line">  pool = <span class="keyword">new</span> <span class="title class_">Buffer</span>(poolSize)</span><br><span class="line">  pool.<span class="property">used</span> = <span class="number">0</span>  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>理想状况下，每次读取的长度就是用户指定的 highWaterMark，但是假如读到文件最后，剩下的内容不到 highWaterMark 那么大，这是预先指定的 Buffer 对象将会有剩余，不过这部分内存可以分配给下次读取时用。</p><p>highWaterMark 的大小对性能有以下两个影响：</p><ul><li>highWaterMark 的设置对 Buffer 内存分配和使用有影响。</li><li>highWaterMark 设置过小，可能导致系统调用次数过多。</li></ul>]]></content>
    
    
    <summary type="html">&lt;h1&gt;第六章 理解 Buffer&lt;/h1&gt;
&lt;h2 id=&quot;Buffer-结构&quot;&gt;Buffer 结构&lt;/h2&gt;
&lt;p&gt;Buffer 是一个像 Array 的对象，主要用来操作字节。Buffer 是一个典型的 JavaScript 与 C++ 结合的模块，它将性能相关的部分用 C++ 实现，将非性能相关的部分用 JavaScript 实现。&lt;/p&gt;
&lt;p&gt;Buffer 所占用的内存不是通过 V8 分配的，而是堆外内存。由于 V8 垃圾回收性能的影响，将 Buffer 对象用更高效的专有内存分配回收策略来管理。&lt;/p&gt;
&lt;p&gt;Buffer 在 Node 进程启动的时候已经载入了，并将其放在全局对象 global 上，因此无需 require() 就能使用。&lt;/p&gt;
&lt;h3 id=&quot;Buffer-对象&quot;&gt;Buffer 对象&lt;/h3&gt;
&lt;p&gt;Buffer 的元素为 16 进制的两位数，即 0 到 255 的数值。&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; str = &lt;span class=&quot;string&quot;&gt;&amp;#x27;深入浅出node.js&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; buf = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Buffer&lt;/span&gt;(str, &lt;span class=&quot;string&quot;&gt;&amp;#x27;utf8&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(buf) &lt;span class=&quot;comment&quot;&gt;// &amp;lt;Buffer e6 b7 b1 e5 85 a5 e6 b5 85 e5 87 ba 6e 6f 64 65 2e 6a 73&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;不同编码的字符串占用的元素个数各不相同，中文在 UTF-8 编码下占用 3 个元素，字母和半角标点占用 1 个元素。&lt;/p&gt;
&lt;p&gt;Buffer 可以通过 length 属性得到长度，也可以通过下标访问元素。&lt;/p&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="内存" scheme="https://lz5z.com/tags/%E5%86%85%E5%AD%98/"/>
    
    <category term="Buffer" scheme="https://lz5z.com/tags/Buffer/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出Node.js》-内存控制</title>
    <link href="https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E5%86%85%E5%AD%98%E6%8E%A7%E5%88%B6/"/>
    <id>https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E5%86%85%E5%AD%98%E6%8E%A7%E5%88%B6/</id>
    <published>2018-05-27T13:14:20.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>第五章 内存控制</h1><p>本章学习 V8 的垃圾回收机制以及如何高效使用内存，内存泄漏以及如何排查内存泄漏。</p><h2 id="V8-的垃圾回收机制与内存限制">V8 的垃圾回收机制与内存限制</h2><p>关于 JavaScript 中常用的垃圾回收机制，可以参考这篇文章 <a href="https://lz5z.com/JavaScript%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/">JavaScript 垃圾回收</a>。</p><h3 id="V8-的内存限制">V8 的内存限制</h3><p>一般后端开发语言中，在基本的内存使用上都没有什么限制，而 Node 中将 JavaScript 的使用内存做出如下限制：64 位操作系统约为 1.4G，32 位操作系统约为 0.7G。在这样的限制下，Node 无法直接操作大内存对象，比如将一个 2GB 文件读取到内存中进行字符串分析，即使物理内存有 32 GB。</p><h3 id="V8-的对象分配">V8 的对象分配</h3><p>在 V8 中，所有的 JavaScript 对象都是通过堆来进行内存分配的，Node 中可以通过 <a href="http://nodejs.cn/api/process.html#process_process_memoryusage">process.memoryUsage()</a> 查看内存使用情况。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">node</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">process.memoryUsage()</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">&#123; rss: 24244224,</span></span><br><span class="line">    heapTotal: 9232384,</span><br><span class="line">    heapUsed: 5041608,</span><br><span class="line">    external: 11497 &#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>其中 heapTotal 和 heapUsed 是 V8 堆内存使用情况，前者是已经申请到的堆内存，后者是当前内存使用量。external 代表 V8 管理的，绑定到 Javascript 的 C++ 对象的内存使用情况。rss 代表进程常驻内存部分, 是给这个进程分配了多少物理内存(占总分配内存的一部分) 这些物理内存中包含堆，栈，和代码段。进程中的内存总共有几部分，一部分是 rss，其余部分在交换区（swap）或者文件系统（filesystem）中。</p><p>当我们在代码中声明变量并且赋值的时候，使用的对象就分配在堆中，如果已经申请到的堆内存不够分配时，就继续申请，直到超过 V8 的限制为止。</p><p>在 Node 环境中使用下面两个参数可以调整启动时内存限制的大小：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">node --max-nex-space-size=1024 app.js // 单位为KB</span><br><span class="line">node --max-old-space-size=2000 app.js // 单位为MB</span><br></pre></td></tr></table></figure><h3 id="V8-垃圾回收机制">V8 垃圾回收机制</h3><p>V8 采用分代式的垃圾回收机制，主要将内存分为新生代和老生代。新生代中对象存活时间较短，老生代中对象存活时间较长或者常驻内存。 <code>--max-old-space-size</code> 和 <code>--max-new-space-size</code> 就是用于设置老生代和新生代内存大小。</p><p>(1) Scavenge 算法</p><p>新生代对象主要通过 Scavenge 算法进行垃圾回收，在 Scavenge 的具体实现中，主要采取 Cheney 算法。</p><p>Cheney 算法是一种采用复制的方式实现的垃圾回收算法，它将堆内存一分为二，每份空间称为 semispace，两份堆内存一个处于使用中，一个处于闲置状态。处于使用状态的的空间称为 From 空间，处于闲置状态的空间称为 To 空间。当我们分配对象时，首先在 From 空间分配，当开始进行垃圾回收时，会检查 From 中存活的对象，将其复制到 To 空间中，非存活对象占用的空间将被释放。</p><p>Scavenge 的缺点是只能使用一半的堆内存，但是由于 Scavenge 只复制存活的对象，所以在面对声明周期较短的场景时，非常有优势。因此在 V8 新生代内存中垃圾回收使用 Scavenge 算法。</p><p>在 V8 分代式垃圾回收机制下，From 空间中存活的对象在复制到 To 空间之前要进行检查，将一些满足条件的对象移动到老生代内存中。</p><p>(2) Mark-Sweep &amp; Mark-Compact</p><p>V8 在老生代内存中，主要采用标记清除法和标记紧缩法进行垃圾回收。</p><p>Mark-Sweep 在标记阶段遍历堆中所有的对象，并标记活着的对象，在随后的清除阶段，只清除没有被标记的对象。Mark-Sweep 存在的问题是进行一次标记清除回收后，内存会出现不连续的状态。</p><p>为了解决 Mark-Sweep 中内存碎片的问题，Mark-Compact 被提出来了。Mark-Compact 是标记整理或者标记紧缩的意思。 Mark-Compact 在 Mark-Sweep 的基础上演变而来，它们的差别在于，清除完标记对象后，在整理的过程中，将活着的对象向一端移动，移动完成后，直接清理掉边界的内存。</p><p>(3) Incremental Marking</p><p>为了避免出现 JavaScript 应用逻辑与垃圾回收器中看到的不一致的情况，垃圾回收的 3 种算法都要将应用逻辑暂停下路，待执行完垃圾回收后再恢复执行逻辑。<br>增量标记是在 V8 为了降低垃圾回收时带来的停顿时间，V8 从停顿阶段入手，将原来要一口气完成的动作拆分为许多部分，每完成一部分，让 JavaScript 应用逻辑执行一小会儿，垃圾回收与应用逻辑交替执行直到标记阶段完成。</p><h3 id="查看垃圾回收日志">查看垃圾回收日志</h3><p>通过在启动参数中添加 <code>--trace_gc</code>，当进行垃圾回收时，会打印出垃圾回收的信息。</p><p>通过在启动参数中添加 <code>--prof</code>，可以得到 V8 执行时的性能分析数据，其中包含垃圾回收执行所占用的时间。</p><h2 id="如何高效实用内存">如何高效实用内存</h2><h3 id="作用域">作用域</h3><p>在 JavaScript 中能形成作用域的有函数调用，with 以及 全局作用域。比如在下面代码中：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> local = &#123;&#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>foo() 在每次被调用的时候都会创建对于的作用域，执行完后作用域销毁，作用域内声明的局部变量也随之销毁。在这个示例中，local 对象会分配在新生代内存 From 中，作用域释放后，local 被垃圾回收。</p><p>(1) 标识符查找</p><p>标识符可以理解为变量名，在 JavaScript 执行时，它会首先查找当前作用域，如果找不到，将会向上级作用域查找，直到查到为止。这种不断向上级作用域查找的方式也叫做作用域链。</p><p>(2) 变量主动释放</p><p>全局变量如果不主动删除，可能会导致对象常驻内存（老生代），可以通过 delete 操作符来删除引用关系。或者将变量重新赋值，让旧的对象脱离引用关系。</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="variable language_">global</span>.<span class="property">foo</span> = <span class="string">&#x27;I am a global object&#x27;</span></span><br><span class="line"><span class="keyword">delete</span> <span class="variable language_">global</span>.<span class="property">foo</span></span><br><span class="line"><span class="comment">//或者重新赋值</span></span><br><span class="line"><span class="variable language_">global</span>.<span class="property">foo</span> = <span class="literal">undefined</span></span><br></pre></td></tr></table></figure><h3 id="闭包-closure">闭包(closure)</h3><p>闭包是一种反作用域链的方式，通过高阶函数，实现外部作用域访问内部作用域中的变量的方法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> bar = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> local = <span class="string">&quot;局部变量&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> local</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">var</span> baz = <span class="title function_">bar</span>()</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">baz</span>())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一般来说，bar() 函数执行完毕后，局部变量 local 就会被垃圾回收，但是 bar() 函数返回了一个匿名函数，而且匿名函数还具备访问 local 的条件，所以只要执行匿名函数的 baz 存在，local 就不会被垃圾回收。</p><h3 id="小结">小结</h3><p>在正常的 JavaScript 执行中，无法立即回收的内存有闭包和全局变量，因此在使用的时候要多加小心，避免老生代内存不断增多的现象。</p><h2 id="内存指标">内存指标</h2><h3 id="查看内存使用情况">查看内存使用情况</h3><p>(1) process.memoryUsage()<br>(2) os 模块的 totalmem() 和 freemem() 可以查看操作系统总内存和闲置内存。</p><h3 id="堆外内存">堆外内存</h3><p>通过 process.memoryUsage() 可以发现堆中的内存使用量总是小于进程的常驻内存使用量的，这就意味着 Node 中内存的使用并非全部通过 V8 进行分配。那些不通过 V8 进行分配的内存成为堆外内存。比如 Buffer 对象使用的就是堆外内存。</p><h2 id="内存泄漏">内存泄漏</h2><p>造成内存泄漏的主要原因有：缓存，队列消费不及时，作用域未释放。</p><h3 id="缓存">缓存</h3><p>在 Node 中，一旦一个对象被当做缓存用，那就意味着它将会常驻老生代内存，老生代内存的堆积会导致垃圾回收在进行扫描时，对这些对象做无用功。</p><p>下面是我们经常都会写的代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cache = &#123;&#125;</span><br><span class="line"><span class="keyword">var</span> get = <span class="keyword">function</span> (<span class="params">key</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (cache[key]) &#123;</span><br><span class="line">    <span class="keyword">return</span> cache[key]</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// get from otherwise</span></span><br><span class="line">    cache[key] = value</span><br><span class="line">    <span class="keyword">return</span> value</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码十分容易理解，创建缓存以内存换取 CPU 执行时间，但是要注意一定要限定缓存对象的大小，再加上完善的过期策略防止内存无限制增长。</p><h3 id="缓存的解决方案">缓存的解决方案</h3><p>直接将内存作为缓存的方案要十分慎重，除了要限制缓存大小外，还需要考虑的事情是进程直接无法共享内存。解决方案是使用进程外缓存，比如 Redis 和 Memcached。</p><h3 id="关注队列状态">关注队列状态</h3><p>Node 通过生产者-消费者模式构建消息队列，假如队列的消费速度低于队列的生成速度，很容易造成堆积。举一个例子，有的应用会收集日志，假如采用数据库来记录日志，由于数据库构于文件系统之上，写入的效率低于文件直接写入，于是会形成数据库写入操作的堆积，而 JavaScript 中相关的作用域得不到释放，从而导致内存泄漏。</p><p>解决方法：</p><ol><li>使用更快消费速度的技术。比如日志使用文件系统读写代替数据库。</li><li>监控队列的长度，一旦堆积，监控系统产生警报并通知相关人员。</li><li>任意的异步调用都应该包含超时机制，一旦在限定时间内未完成响应，通过回掉函数传递超时异常，使异步调用有可控的响应时间。</li><li>Bagpipe 中提供超时模式和拒绝模式，启动超时模式时，函数超时就返回超时错误，启动拒绝模式时，当队列拥塞时，新来的调用会直接响应拥塞错误。</li></ol><h2 id="内存泄漏排查">内存泄漏排查</h2><p><a href="https://github.com/bnoordhuis/node-heapdump">node-heapdump</a> 允许对 V8 堆内存抓取快照，用于事后分析。</p><p><a href="https://github.com/lloyd/node-memwatch">node-memwatch</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> memwatch = <span class="built_in">require</span>(<span class="string">&#x27;memwatch&#x27;</span>)</span><br><span class="line">memwatch.<span class="title function_">on</span>(<span class="string">&#x27;leak&#x27;</span>, <span class="keyword">function</span> (<span class="params">info</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;leak:&#x27;</span>)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(info)</span><br><span class="line">&#125;)</span><br><span class="line">memwatch.<span class="title function_">on</span>(<span class="string">&#x27;stats&#x27;</span>, <span class="keyword">function</span> (<span class="params">stats</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stats:&#x27;</span>)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(stats)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="stats-事件">stats 事件</h3><p>在进程中使用 node-memwatch 之后，每次进行垃圾回收的时候，都会触发一次 stats 事件，这个事件将会传递内存的统计信息。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;num_full_gc&quot;</span>: <span class="number">17</span>,  <span class="comment">// 第 17 次进行全队垃圾回收</span></span><br><span class="line">  <span class="string">&quot;num_inc_gc&quot;</span>: <span class="number">8</span>, <span class="comment">// 第几次增量垃圾回收</span></span><br><span class="line">  <span class="string">&quot;heap_compactions&quot;</span>: <span class="number">8</span>, <span class="comment">// 第几次对老生代进行整理</span></span><br><span class="line">  <span class="string">&quot;estimated_base&quot;</span>: <span class="number">2592568</span>, <span class="comment">// 预估基数</span></span><br><span class="line">  <span class="string">&quot;current_base&quot;</span>: <span class="number">2592568</span>, <span class="comment">// 当前基数</span></span><br><span class="line">  <span class="string">&quot;min&quot;</span>: <span class="number">2499912</span>, <span class="comment">// 最小</span></span><br><span class="line">  <span class="string">&quot;max&quot;</span>: <span class="number">2592568</span>, <span class="comment">// 最大</span></span><br><span class="line">  <span class="string">&quot;usage_trend&quot;</span>: <span class="number">0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="leak-事件">leak 事件</h3><p>leak 事件记录 Node 中存在的内存泄漏。如果经过 5 次垃圾回收，内存仍然没有释放，这意味着可能存在内存泄漏，node-memwatch 会发出一个 leak 事件。</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">&#123; <span class="attr">start</span>: <span class="title class_">Fri</span>, <span class="number">29</span> <span class="title class_">Jun</span> <span class="number">2012</span> <span class="number">14</span>:<span class="number">12</span>:<span class="number">13</span> <span class="variable constant_">GMT</span>,</span><br><span class="line">  <span class="attr">end</span>: <span class="title class_">Fri</span>, <span class="number">29</span> <span class="title class_">Jun</span> <span class="number">2012</span> <span class="number">14</span>:<span class="number">12</span>:<span class="number">33</span> <span class="variable constant_">GMT</span>,</span><br><span class="line">  <span class="attr">growth</span>: <span class="number">67984</span>,</span><br><span class="line">  <span class="attr">reason</span>: <span class="string">&#x27;heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr&#x27;</span> &#125;</span><br></pre></td></tr></table></figure><p>growth 显示了 5 次垃圾回收的过程中内存增长了多少。</p><h2 id="大内存应用">大内存应用</h2><p>Node 中使用 Stream 模块处于处理大文件</p><p>Stream 模块是 Node 的原生模块，继承自 EventEmitter，具备基本自定义事件功能和标准的事件和方法。Stream 分为读和写两种，Node 中很多模块依赖于 Stream 模块，比如 fs.createReadStream() 和 fs.createWriteStream() 分别用来创建文件的可读流和可写流。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reader = fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;in.txt&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> writer = fs.<span class="title function_">createWriteStream</span>(<span class="string">&#x27;out.txt&#x27;</span>)</span><br><span class="line">reader.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="keyword">function</span> (<span class="params">chunk</span>) &#123;</span><br><span class="line">  writer.<span class="title function_">write</span>(chunk)</span><br><span class="line">&#125;)</span><br><span class="line">reader.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  writer.<span class="title function_">end</span>()</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>或者使用管道方法</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> reader = fs.<span class="title function_">createReadStream</span>(<span class="string">&#x27;in.txt&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> writer = fs.<span class="title function_">createWriteStream</span>(<span class="string">&#x27;out.txt&#x27;</span>)</span><br><span class="line">reader.<span class="title function_">pipe</span>(writer)</span><br></pre></td></tr></table></figure><p>通过流的方式进行文件的读写，不会受 V8 内存限制，如果不需要进行字符串层面的操作，可以借助 Buffer 操作，但是大片使用内存的情况依然需要消息，即使 V8 不限制内存，物理内存依然有限制。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;第五章 内存控制&lt;/h1&gt;
&lt;p&gt;本章学习 V8 的垃圾回收机制以及如何高效使用内存，内存泄漏以及如何排查内存泄漏。&lt;/p&gt;
&lt;h2 id=&quot;V8-的垃圾回收机制与内存限制&quot;&gt;V8 的垃圾回收机制与内存限制&lt;/h2&gt;
&lt;p&gt;关于 JavaScript 中常用的垃圾回收机制，可以参考这篇文章 &lt;a href=&quot;https://lz5z.com/JavaScript%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/&quot;&gt;JavaScript 垃圾回收&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&quot;V8-的内存限制&quot;&gt;V8 的内存限制&lt;/h3&gt;
&lt;p&gt;一般后端开发语言中，在基本的内存使用上都没有什么限制，而 Node 中将 JavaScript 的使用内存做出如下限制：64 位操作系统约为 1.4G，32 位操作系统约为 0.7G。在这样的限制下，Node 无法直接操作大内存对象，比如将一个 2GB 文件读取到内存中进行字符串分析，即使物理内存有 32 GB。&lt;/p&gt;
&lt;h3 id=&quot;V8-的对象分配&quot;&gt;V8 的对象分配&lt;/h3&gt;
&lt;p&gt;在 V8 中，所有的 JavaScript 对象都是通过堆来进行内存分配的，Node 中可以通过 &lt;a href=&quot;http://nodejs.cn/api/process.html#process_process_memoryusage&quot;&gt;process.memoryUsage()&lt;/a&gt; 查看内存使用情况。&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;node&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;process.memoryUsage()&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;&amp;#123; rss: 24244224,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    heapTotal: 9232384,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    heapUsed: 5041608,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    external: 11497 &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="V8" scheme="https://lz5z.com/tags/V8/"/>
    
    <category term="内存" scheme="https://lz5z.com/tags/%E5%86%85%E5%AD%98/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出Node.js》-异步I/O</title>
    <link href="https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E5%BC%82%E6%AD%A5I-O/"/>
    <id>https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E5%BC%82%E6%AD%A5I-O/</id>
    <published>2018-05-25T14:20:38.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h2 id="第三章-异步-I-O">第三章 异步 I/O</h2><p>异步的概念首先在 Web2.0 中火起来，是因为浏览器中 JavaScript 在单线程上执行，而且它还与 UI 渲染共用一个线程。这意味着 JavaScript 在执行的时候 UI 渲染和响应是处于停滞状态的。前端通过异步的方式来消除 UI 阻塞的现象。假如业务场景中有一组互不相关的任务需要完成，可以采用下面两种方式。</p><ol><li>单线程串行一次执行。</li><li>多线程并行执行。</li></ol><p>如果创建多线程的开销小于并行执行，那么多线程的方式是首选的。多线程的代价在于创建线程和执行期间线程上下文切换的开销较大。另外，在复杂业务中，多线程编程经常面临锁、状态同步等问题。但是多线程能有效利用 CPU。</p><p>单线程顺序执行比较符合编程人员按照顺序思考的思维方式，也是最主流的编程方式。缺点在于执行性能，任何一个略慢的任务都会导致后续执行代码被阻塞。</p><p>Node 在两者之间给出了它的方案：利用单线程，远离多线程死锁，状态同步问题；利用异步 I/O，让单线程远离阻塞，更好地利用 CPU。</p><p>异步 I/O 就是 I/O 的调用不再阻塞后续计算，将原有等待 I/O 完成这段时间分配给其它需要的业务去执行。</p><span id="more"></span><h3 id="异步-I-O-和-非阻塞-I-O">异步 I/O 和 非阻塞 I/O</h3><p>从计算机内核 I/O 而言，同步/异步和阻塞/非阻塞实际上是不同的。操作系统内核对 I/O 只有两种方式，阻塞和非阻塞。在调用阻塞 I/O 时，应用程序需要等待 I/O 完成才返回结果。阻塞 I/O 造成 CPU 等待 I/O，CPU 的处理能力得不到充分利用。为了提高性能，内核提供了非阻塞 I/O。非阻塞 I/O 在调用之后立马返回，但是数据并不在返回结果中，返回结果中只有当前调用的状态。为了获取完整的数据，应用程序需要重复调用 I/O 操作来确认是否完成。这种方式叫做轮询。</p><p>非阻塞 I/O 技术虽然不会让 CPU 等待造成浪费，但是却需要轮询去确认是否完成数据获取，其实也是对 CPU 资源的浪费。</p><p>主要轮询技术：</p><p>(1) read。反复调用来检查 I/O 的状态。<br>(2) select。通过文件描述符上的事件状态进行判断，select 轮询采用 1024 长度数组存储状态。<br>(3) poll。使用链表，减少不必要的检查。<br>(4) epoll。该方案是 Linux 下效率最高的 I/O 事件通知机制。在进入轮询的时候如果没有检查到 I/O 事件，将会进行休眠，知道事件发生将它唤醒。</p><h2 id="Node-的异步-I-O">Node 的异步 I/O</h2><h3 id="事件循环">事件循环</h3><p>事件循环是 Node 自身的执行模型，正是它使得回调函数十分普遍。</p><p>在进程启动时，Node 便会创建一个类似于 while(true) 的循环，每执行一次循环体成为 Tick。每个 Tick 的过程就是查看是否有事件待处理，如果有，就取出事件及其相关的回调函数。如果存在关联的回调函数，就执行它们，然后进入下个循环，直到没有事件处理，就退出进程。</p><h3 id="观察者">观察者</h3><p>在每个 Tick 的过程中，如何判断是否有事件需要处理呢？Node 在每个事件循环中都有一个或多个观察者，而判断是否有事件需要处理的过程就是向这些观察者询问是否有要处理的事件。</p><p>在 Node 中，事件主要来源于网络请求，文件 I/O 等。事件循环是一个典型的生产者/消费者模型。异步 I/O，网络请求等则是事件的生产者，源源不断为 Node 提供不同类型的事件，这些事件被传递到对应的观察者哪里，事件循环则从观察者那里取出事件并处理。</p><h3 id="请求对象">请求对象</h3><p>对于 Node 中的异步 I/O 而言，回调函数究竟是谁在调用呢？比如下述代码，当文件打开成功后，后面的回调的执行过程是怎样的呢？</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line">fs.<span class="title function_">open</span>(<span class="string">&#x27;xxx.js&#x27;</span>, <span class="string">&#x27;a+&#x27;</span>, callback)</span><br></pre></td></tr></table></figure><p>从 JavaScript 调用 Node 核心模块，核心模块调用 C++ 内建模块，内建模块通过 libuv 进行系统调用。libuv 作为封装层，有平台各自的实现，本质上是调用 uv_fs_open() 方法。在调用 uv_fs_open() 的过程中，我们创建了一个 FSReqWrap 请求对象。从 JavaScript 层传入的参数和当前方法都封装在这个请求对象中，回调函数也是这个请求对象的一个属性。而操作系统拿到这个对象后，将 FSReqWrap 对象推入线程池中等待执行。</p><p>至此，JavaScript 调用立即返回，异步调用第一阶段完成，JavaScript 线程可以继续执行后续任务。当前的 I/O 操作在线程池中等待执行，不管它是否阻塞，都不会影响 JavaScript 后续的执行。</p><h3 id="执行回调">执行回调</h3><p>线程池中的请求对象在得到 CPU 资源后调用操作系统底层的函数完成 I/O 操作，线程池调用 PostQueuedCompletionStatus() 方法提交状态，然后将结果存储在请求对象的 <code>req-&gt; result</code> 属性上，并且释放线程回归线程池。I/O 观察者在每次 Tick 的时候通过调用 GetQueuedCompletionStatus() 方法去检查线程池中是否有执行完的请求，如果存在，会将请求对象加入到 I/O 观察者队列中，然后将其当做事件处理。<br>I/O 观察者取出请求对象的 result 属性作为参数，取出绑定在上面的回调函数，然后执行，以此达到调用 JavaScript 回调函数的目的。至此，整个异步 I/O 完成。</p><img src="/assets/img/Node_IO.png" alt="Node_IO"><p>事件循环、观察者、请求对象、I/O 线程池这四者共同构成了 Node 异步 I/O 模型的基本要素。Windows 主要通过 IOCP 来向系统内核发送 I/O 调用和从系统内核获取 I/O 状态，配以事件循环，完成异步 I/O 的过程，Linux 下通过 epoll 实现这个过程。不同的是，线程池在 Windows 上由内核 IOCP 实现，Linux 下由 libuv 实现。</p><p>最后回答上面提到的问题，回调函数究竟由谁来执行？答案是：I/O 观察者。</p><h2 id="非-I-O-的异步-API">非 I/O 的异步 API</h2><p>Node 中还存在一些与 I/O 无关的 API：setTimeout()、setInterval()、setImmediate() 和 process.nextTick()。</p><h3 id="定时器">定时器</h3><p>(1) setTimeout 和 setInterval 的实现原理与异步 I/O 比较类似，只是不需要线程池参与。调用 setTimeout/setInterval 创建的定时器会被插入定时器观察者内部的红黑树中，每次 Tick 执行时，会从该红黑树中迭代选出定时器对象，检查是否超过时间，如果超过，它的回调函数立即执行。</p><p>执行回调函数的是定时器观察者。</p><p>定时器的问题在于，它并非精确的，尽管事件循环非常快，但是如果每一次循环占用时间较多，那么下次循环时，它可能已经超时很久了。比如 setTimeout 设定一个任务在 10 毫秒后执行，但是在 9 毫秒时，有一个任务占用了 5 毫秒的 CPU 时间片，再次轮到定时器执行时，时间已经超过 4 毫秒了。</p><img src="/assets/img/Node_IO_setTimeout.png" alt="Node_IO_setTimeout"><p>(2) process.nextTick() 的出现正是为了解决定时器精度不高，并且需要红黑树（性能浪费）的问题。它的作用是定义一个动作，在下次事件轮询的时间点上执行这个动作。</p><p>比如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;aaa&#x27;</span>)  </span><br><span class="line">&#125;</span><br><span class="line">process.<span class="title function_">nextTick</span>(foo)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bbb&#x27;</span>)</span><br></pre></td></tr></table></figure><p>终端上的输出结果是：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">bbb</span><br><span class="line">aaa</span><br></pre></td></tr></table></figure><p>使用 setTimeout 也能达到同样的效果：</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">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;aaa&#x27;</span>)  </span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">setTimeout</span>(foo, <span class="number">0</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bbb&#x27;</span>)</span><br></pre></td></tr></table></figure><p>每次调用 process.nextTick() 方法，只会将回调函数放入队列中，在下一轮 Tick 时取出执行。<br>定时器中采用红黑树的操作时间复杂度为 O(lg(n))，nextTick() 的时时复杂度为 O(1)。相比之下，<br>process.nextTick() 更高效。</p><p>(3) setImmediate() 与 process.nextTick() 方法十分类似，都是将回调函数延迟执行。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">process.<span class="title function_">nextTick</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;延迟执行&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;正常执行&#x27;</span>)</span><br><span class="line"><span class="comment">//----------//</span></span><br><span class="line"><span class="title function_">setImmediate</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;延迟执行&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;正常执行&#x27;</span>)</span><br></pre></td></tr></table></figure><p>两者的输出结果是一样的：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">正常执行</span><br><span class="line">延迟执行</span><br></pre></td></tr></table></figure><p>process.nextTick 的优先级要高于 setImmediate。原因是事件循环对观察者的检查是有先后顺序的。process.nextTick 属于 idle 观察者，setImmediate 属于 check 观察者。在每一个轮询检查中，idle 观察者优先于 I/O 观察者，I/O 观察者优先于 check 观察者。</p><p>还有一个主要的区别是，process.nextTick() 的回调函数保存在数组中，setImmediate() 的回调函数保存在链表中。在行为上，process.nextTick() 在每次轮询中会将数组内全部回调函数执行完，setImmediate() 在每次循环中只执行链表的第一个回调函数。</p><h2 id="事件驱动与高性能服务器">事件驱动与高性能服务器</h2><p>事件驱动的实质就是通过主循环和事件触发的方式来运行程序，Node 采用的事件驱动的方式，无需为每个请求简历额外的线程，可以省去线程创建切换和销毁带来的开销，使得服务器能有条不紊地处理消息，这是 Node 高性能的一个主要原因。</p><p>事件驱动带来的高效也被 Nginx 采用，不同之处在于 Nginx 由纯 C 编写，性能极其强大，非常适合做 Web 服务器。</p><h2 id="总结">总结</h2><p>异步 I/O 的核心是事件循环，Node 使用了和浏览器中一样的执行模型，让 JavaScript 在服务端发挥巨大的能量。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;第三章-异步-I-O&quot;&gt;第三章 异步 I/O&lt;/h2&gt;
&lt;p&gt;异步的概念首先在 Web2.0 中火起来，是因为浏览器中 JavaScript 在单线程上执行，而且它还与 UI 渲染共用一个线程。这意味着 JavaScript 在执行的时候 UI 渲染和响应是处于停滞状态的。前端通过异步的方式来消除 UI 阻塞的现象。假如业务场景中有一组互不相关的任务需要完成，可以采用下面两种方式。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;单线程串行一次执行。&lt;/li&gt;
&lt;li&gt;多线程并行执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果创建多线程的开销小于并行执行，那么多线程的方式是首选的。多线程的代价在于创建线程和执行期间线程上下文切换的开销较大。另外，在复杂业务中，多线程编程经常面临锁、状态同步等问题。但是多线程能有效利用 CPU。&lt;/p&gt;
&lt;p&gt;单线程顺序执行比较符合编程人员按照顺序思考的思维方式，也是最主流的编程方式。缺点在于执行性能，任何一个略慢的任务都会导致后续执行代码被阻塞。&lt;/p&gt;
&lt;p&gt;Node 在两者之间给出了它的方案：利用单线程，远离多线程死锁，状态同步问题；利用异步 I/O，让单线程远离阻塞，更好地利用 CPU。&lt;/p&gt;
&lt;p&gt;异步 I/O 就是 I/O 的调用不再阻塞后续计算，将原有等待 I/O 完成这段时间分配给其它需要的业务去执行。&lt;/p&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="异步" scheme="https://lz5z.com/tags/%E5%BC%82%E6%AD%A5/"/>
    
    <category term="I/O" scheme="https://lz5z.com/tags/I-O/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出Node.js》-读书笔记</title>
    <link href="https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <id>https://lz5z.com/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BANode-js-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</id>
    <published>2018-05-23T14:51:15.000Z</published>
    <updated>2026-05-07T14:50:53.976Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介">简介</h2><p>不知不觉 Node 已经更新到第十个版本了，本人使用 Node 也有两年多时间，之前学习的东西一直零零散散，没有形成系统的知识体系，于是最近又抽时间回顾这本经典的 <a href="https://book.douban.com/subject/25768396/">《深入浅出Node.js》</a>，阅读的过程中，难免有些东西不易理解或者容易忘记，因此选择博客的形式记录。</p><p>作者书写这本书的时候，Node 的稳定版本为 v0.10.13，当前最高版本为 v10.1.0，不过整个 Node 的核心体系在当时已经形成，因此对更高版本的理解问题不大。</p><h2 id="第一章-Node-简介">第一章 Node 简介</h2><p>Node 诞生于 2009 年 3 月，作者为 Ryan Dahl。作者选择 JavaScript 作为 Node 的实现语言主要因为：JavaScript 高性能（V8），符合事件驱动，没有后端历史包袱。</p><p>除了 HTML、WebKit 和显卡这些与 UI 相关技术没有支持外，整个 Node 的结构与 Chrome 非常相似，它们都是基于事件驱动的异步架构，浏览器通过事件驱动来服务界面上的交互，Node 通过事件驱动来服务 I/O。</p><span id="more"></span><h3 id="Node-的特点">Node 的特点</h3><p>(1) 异步 I/O。在 Node 中，绝大多数的操作都是以异步的方式进行调用，从文件操作到网络请求都是如此。<br>(2) 事件与回调函数。Node 将前端浏览器中应用广泛的事件机制引入后端，配合异步 I/O。优点是事件编程轻量，低耦合，只用关注事务点等，缺点是多个事件之间的协作是一个问题。<br>(3) 单线程。Node 保持了 JS 单线程的特点，在 Node 中，JS 与其余线程无法共享状态。单线程好处了不用处理多线程之间的状态同步与通信，没有死锁的存在，也没有线程切换带来的性能开销。缺点是无法利用多核 CPU；错误会引起整个应用退出，应用健壮性值得考验；对大规模高 CPU 计算不友好。</p><p>在浏览器中，HTML5 制定了 Web Worker 标准来解决 JS 大规模计算导致的阻塞 UI 渲染的问题。而 Node 中，使用 child_process 创建子进程来应对单线程带来的问题。</p><p>(4) 跨平台。</p><h3 id="Node-应用场景">Node 应用场景</h3><p>(1) I/O 密集型。I/O 密集的优势˞要在于 Node 利用事件循环的能力，而不是启动每一个线程为每一个请求服务，资源占用极少。<br>(2) Node 是否适用于 CPU 密集型应用？首先 Node 的计算性能并不差，但是由于 JavaScript 单线程的原因，如果有长时间运算，将导致 CPU 不能释放，使后续 I/O 无法发起。<br>(3) 与遗留系统和平共处。比如和 Java 配合，Node 完成 Web 端的开发，Java 提供稳定的接口。<br>(4) 分布式应用。</p><h2 id="第二章-模块机制">第二章 模块机制</h2><p>Node 的模块化采用 CommonJS 规范，关于 JavaScript 模块化的各种规范，可以参考 <a href="https://lz5z.com/JavaScript%E6%A8%A1%E5%9D%97%E5%8C%96-CommonJS-AMD-CMD-ES6/">前端模块化-CommonJS,AMD,CMD,ES6</a>。</p><p>CommonJS 规范涵盖了模块，二进制，Buffer，字符集编码，I/O 流，进程环境，文件系统，socket，单元测试，Web服务器接口，包管理等。</p><h3 id="CommonJS-模块规范">CommonJS 模块规范</h3><p>(1) 模块引用</p><p>通过 require() 方法引入一个模块的 API 到当前上下文中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> math = <span class="built_in">require</span>(<span class="string">&#x27;math&#x27;</span>)</span><br></pre></td></tr></table></figure><p>(2) 模块定义</p><p>在模块中，上下文提供 exports 对象用于导出当前模块的变量或者方法，并且它是唯一导出的出口。在模块中，还存在一个 module 对象，代表模块自身，而 exports 是 module 的属性。</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">add</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">eval</span>(<span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">join</span>.<span class="title function_">call</span>(<span class="variable language_">arguments</span>, <span class="string">&#x27;+&#x27;</span>))  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(3) 模块标识</p><p>模块标识就是传递给 require() 的参数，它必须是符合小驼峰命名的字符串，或者以 <code>.</code> 和 <code>..</code> 开头的相对路径，或者绝对路径。</p><p>CommonJS 构建的这套模块导出和引入机制使得用户完全不必考虑变量污染，命名空间等方案相形见绌。</p><h3 id="Node-模块实现">Node 模块实现</h3><p>Node 引入模块，需要经历三个步骤：路径分析，文件定位，编译执行。</p><p>Node 中的模块分为核心模块和文件模块。</p><p>(1) 核心模块在 Node 源码编译过程中，编译成为二进制文件，在 Node 启动阶段部分核心模块就被加载进内存，所以省去了文件定位和编译的时间，加载速度最快。</p><p>(2) 文件模块则是在运行时动态加载。</p><p>(3) 自定义模块是指非核心模块，也不是路径形式的文件模块。以文件或者包的形式存在，这类模块的查找是最费时的。</p><p>模块路径：Node 在定位文件模块的时候采用的一种查找策略。具体表现为一个路径组成的数组。比如我在自己的电脑 <code>/Users/lizhen/WorkSpaces/test</code> 目录下面创建文件 index.js：</p><p>内容如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">module</span>.<span class="property">paths</span>)</span><br></pre></td></tr></table></figure><p>运行脚本输出结果如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">[ &#x27;/Users/lizhen/WorkSpaces/test/node_modules&#x27;,</span><br><span class="line">  &#x27;/Users/lizhen/WorkSpaces/node_modules&#x27;,</span><br><span class="line">  &#x27;/Users/lizhen/node_modules&#x27;,</span><br><span class="line">  &#x27;/Users/node_modules&#x27;,</span><br><span class="line">  &#x27;/node_modules&#x27; ]</span><br></pre></td></tr></table></figure><p>其路径寻址规则如下：从当前目录的 node_modules 中寻找 -&gt; 父目录的 node_modules 中寻找 -&gt; 递归一直到根目录的 node_modules。</p><p>它的生成方式与 JavaScript 原型链或者作用域链的查找方式十分类似。Node 会逐个尝试模块路径，直到找到模块或者查找到根目录位置。可以看出，当文件路径比较深的时候，模块查找会比较耗时。</p><p>Node 对引入过的模块都会进行缓存，无论是核心模块还是文件模块，require() 方法都采用缓存优先的方式进行加载，并且核心模块的优先级高于文件模块。</p><h4 id="文件定位">文件定位</h4><p>require() 在分析标识符的过程中，如果标识符不包括扩展名，Node 会按照 <code>.js</code>, <code>.json</code>, <code>.node</code> 的次序补足扩展名，依次尝试。</p><p>在尝试的过程中，需要调用 fs 模块同步阻塞式地判断文件是否存在，所以会引起性能问题。解决的办法是：1. <code>.node</code> 和 <code>.json</code> 文件标识符中带上扩展名。2. 同步配合缓存，可以大幅缓解 Node 单线程中阻塞调用的缺陷。</p><h3 id="模块编译">模块编译</h3><p>在 Node 中，每个文件都是一个对象，它的定义如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Module</span> (<span class="params">id, parent</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">id</span> = id</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">exports</span> = &#123;&#125;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">parent</span> = parent</span><br><span class="line">  <span class="keyword">if</span> (parent &amp;&amp; parent.<span class="property">children</span>) &#123;</span><br><span class="line">    parent.<span class="property">children</span>.<span class="title function_">push</span>(<span class="variable language_">this</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">filename</span> = <span class="literal">null</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">loaded</span> = <span class="literal">false</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">children</span> = []</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译和执行是引入文件模块的最后一个阶段。定位到文件后，Node 会新建一个模块对象，然后根据路径载人并编译。不同文件载入方式不同：</p><ol><li><code>.js</code> 文件，通过 fs 模块同步读取文件后编译执行。</li><li><code>.node</code> 文件，由 C/C++ 编写，通过 dlopen() 加载最后编译生成的文件。</li><li><code>.json</code> 文件，通过 fs 模块同步读取后，用 JSON.parse() 解析。</li><li>其余文件都被当做 <code>.js</code> 文件载入。</li></ol><p>每个编译成功的模块都会将其文件路径作为索引缓存在 <code>Module._cache</code> 对象上，以提高二次引入的性能。</p><p>根据不同的文件扩展名，Node 会调用不同的读取方式，如 <code>.json</code> 文件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Module</span>.<span class="property">_extensions</span>[<span class="string">&#x27;.json&#x27;</span>] = <span class="keyword">function</span> (<span class="params"><span class="variable language_">module</span>, filename</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> content = <span class="title class_">NativeModule</span>.<span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>).<span class="title function_">readFileSync</span>(filename, <span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title function_">stripBOM</span>(content))</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      e.<span class="property">message</span> = filename + <span class="string">&#x27;:&#x27;</span> e.<span class="property">message</span></span><br><span class="line">      <span class="keyword">throw</span> e</span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中，<code>Module._extensions</code> 会赋值给 require 的 extensions 属性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="built_in">require</span>.<span class="property">extensions</span>) <span class="comment">// &#123; &#x27;.js&#x27;: [Function], &#x27;.json&#x27;: [Function], &#x27;.node&#x27;: [Function] &#125;</span></span><br></pre></td></tr></table></figure><p>也可以通过扩展 <code>require.extensions['.ext']</code> 的方式对自定义扩展名进行特殊的加载，但是 Node 官方并不鼓励这种行为。</p><h3 id="JavaScript-模块编译">JavaScript 模块编译</h3><p>在编译 JavaScript 的过程中，Node 对获取的 JavaScript 文件进行包装：<a href="http://nodejs.cn/api/modules.html#modules_the_module_wrapper">模块包装器</a></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></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"><span class="built_in">exports</span>, <span class="built_in">require</span>, <span class="variable language_">module</span>, __filename, __dirname</span>) &#123;</span><br><span class="line"><span class="comment">// 模块的代码实际上在这里</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这样每个模块文件之间都进行了作用域隔离，包装之后的代码会通过 vm 原生模块的 runInThisContext() 方法执行（类似 eval，只是有明确的上下文，不污染全局）。</p><p><strong>exports vs module.exports</strong></p><p>exports 对象本质上来说只是 Node 模块包装器的一个形参，直接对其进行赋值，只会改变形参的引用，但并不能改变作用域外的值。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> change = <span class="keyword">function</span> (<span class="params">a</span>) &#123;</span><br><span class="line">  a = <span class="number">100</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 100  </span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span></span><br><span class="line"><span class="title function_">change</span>(a)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><p>所以如果要实现 require 引入一个类的效果，请赋值给 module.exports 对象。</p><p>更详细的解释，可以查看 <a href="http://nodejs.cn/api/modules.html#modules_exports_shortcut">exports 快捷方式</a>。</p><p>我个人的理解是：module 对象在 Node 执行时创建，并且自带 exports 属性，而 exports 对象是对 module.exports 的值引用，当 module.exports 改变的时候， exports 不会被改变，而模块导出的时候，真正导出的是 module.exports，而不是 exports。</p><p>看这个例子：</p><p>math.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">add</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">eval</span>(<span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">join</span>.<span class="title function_">call</span>(<span class="variable language_">arguments</span>, <span class="string">&#x27;+&#x27;</span>))</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">add</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">join</span>.<span class="title function_">call</span>(<span class="variable language_">arguments</span>, <span class="string">&#x27;+&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>test.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> math = <span class="built_in">require</span>(<span class="string">&#x27;./math&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(math.<span class="title function_">add</span>(<span class="number">2</span>, <span class="number">34</span>)) <span class="comment">//  2+34</span></span><br></pre></td></tr></table></figure><p>可以看出，exports 上赋的值，在 module.exports 被重写后无效。</p><h3 id="核心模块">核心模块</h3><p>Node 的核心模块分为 C/C++ 编写和 JavaScript 编写的两部分。其中 C/C++ 文件在 <a href="https://github.com/nodejs/node/tree/master/src">src</a> 目录下，JavaScript 文件在 <a href="https://github.com/nodejs/node/tree/master/lib">lib</a> 目录下。</p><p>(1) JavaScript 核心模块编译过程</p><p>在编译所有的 C/C++ 文件之前，编译程序需要将所有的 JavaScript 模块文件编译为 C/C++ 代码。</p><ul><li>转为 C/C++ 代码。Node 使用 V8 附带的 <a href="http://js2c.py">js2c.py</a> 工具，将所有内置的 JS 代码（<code>src/node.js</code> 和 <code>lib/*.js</code>）转换为 C++ 里面的数组，生成 <code>node_natives.h</code> 头文件。</li><li>编译 JS 核心模块。首先在引入 JS 的核心模块的过程中，经历了模块包装器的过程，然后导出 exports 对象。JS 核心模块源文件通过 <code>process.binding('natives')</code> 取出，编译成功后模块缓存在 <code>NativeModule._cache</code>，文件模块则缓存在 <code>Module._cache</code>。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">NativeModule</span> (<span class="params">id</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">filename</span> = id + <span class="string">&#x27;.js&#x27;</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">id</span> = is</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">exports</span> = &#123;&#125;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">loaded</span> = <span class="literal">false</span>  </span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">NativeModule</span>.<span class="property">_source</span> = process.<span class="title function_">binding</span>(<span class="string">&#x27;natives&#x27;</span>)</span><br><span class="line"><span class="title class_">NativeModule</span>.<span class="property">_cache</span> = &#123;&#125;</span><br></pre></td></tr></table></figure><p>(2) C/C++ 核心模块的编译过程</p><p>Node 的高性能，很大程度是因为核心模型，多数有 C/C++ 编写，C++ 模块主内完成核心，JS 模块主外实现封装模块，充分利用了脚本语言易编写，C/C++ 高效执行的优点。Node 中常见的 buffer、crypto、evals、fs、os 等模块都是 C/C++ 编写的。</p><p>(3) 核心模块引入流程</p><img src="/assets/img/node_core_module_import.png" alt="node_core_module_import"><p>(4) 模块调用栈</p><img src="/assets/img/node_module_imports.png" alt="node_module_imports"><p>(5) 包与 NPM</p><p>在 Node 中，包和 NPM 是将模块联系起来的一种机制。CommonJS 规范中包目录应该包含如下这些文件。</p><ol><li>package.json：包描述文件</li><li>bin： 可执行二进制文件</li><li>lib：存放 JavaScript 文件</li><li>doc：存放文档目录</li><li>test：单元测试代码</li></ol><p>NPM 全局安装：</p><p>通过执行命令 <code>npm install express -g</code> 将 express 安装为全局可用的可执行命令，但并不意味着可以从任何地方通过 require() 都可以引入它。</p><p>实际上，全局安装的包都被安装在一个统一的目录下，这个目录为：</p><p><code>path.resolve(process.execPath, '..', '..', 'lib', 'node_modules')</code></p><p>这个路径是 Node 可执行文件的路径，比如，Node 可执行文件的路径为 <code>/usr/local/bin/node</code>，那么模块目录就是 <code>/usr/local/lib/node_modules</code>。</p><p>关于更多 JavaScript 模块的规范可以参考 <a href="https://lz5z.com/JavaScript%E6%A8%A1%E5%9D%97%E5%8C%96-CommonJS-AMD-CMD-ES6/">前端模块化-CommonJS,AMD,CMD,ES6</a>。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;简介&lt;/h2&gt;
&lt;p&gt;不知不觉 Node 已经更新到第十个版本了，本人使用 Node 也有两年多时间，之前学习的东西一直零零散散，没有形成系统的知识体系，于是最近又抽时间回顾这本经典的 &lt;a href=&quot;https://book.douban.com/subject/25768396/&quot;&gt;《深入浅出Node.js》&lt;/a&gt;，阅读的过程中，难免有些东西不易理解或者容易忘记，因此选择博客的形式记录。&lt;/p&gt;
&lt;p&gt;作者书写这本书的时候，Node 的稳定版本为 v0.10.13，当前最高版本为 v10.1.0，不过整个 Node 的核心体系在当时已经形成，因此对更高版本的理解问题不大。&lt;/p&gt;
&lt;h2 id=&quot;第一章-Node-简介&quot;&gt;第一章 Node 简介&lt;/h2&gt;
&lt;p&gt;Node 诞生于 2009 年 3 月，作者为 Ryan Dahl。作者选择 JavaScript 作为 Node 的实现语言主要因为：JavaScript 高性能（V8），符合事件驱动，没有后端历史包袱。&lt;/p&gt;
&lt;p&gt;除了 HTML、WebKit 和显卡这些与 UI 相关技术没有支持外，整个 Node 的结构与 Chrome 非常相似，它们都是基于事件驱动的异步架构，浏览器通过事件驱动来服务界面上的交互，Node 通过事件驱动来服务 I/O。&lt;/p&gt;</summary>
    
    
    
    <category term="Node" scheme="https://lz5z.com/categories/Node/"/>
    
    
    <category term="Node" scheme="https://lz5z.com/tags/Node/"/>
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>HTTPS 加密原理</title>
    <link href="https://lz5z.com/HTTPS%E5%8A%A0%E5%AF%86%E5%8E%9F%E7%90%86/"/>
    <id>https://lz5z.com/HTTPS%E5%8A%A0%E5%AF%86%E5%8E%9F%E7%90%86/</id>
    <published>2018-05-22T13:05:58.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<p>前面几天学习 DNS 缓存的时候，了解到了 DNS 劫持和 HTTP 劫持，关于 DNS 劫持和 HTTP 劫持的区别，知乎上一位同学给出了有趣的比喻<a href="https://www.zhihu.com/question/27035513">DNS劫持和HTTP劫持有什么区别？</a>:<br>DNS 劫持：你输入的网址是 <code>http://www.google.com</code>，出来的是百度的页面。<br>HTTP 劫持：你打开的是知乎的页面，右下角弹出唐老师的不孕不育广告（2018年更：右下角弹出：偶系渣渣辉）。<br>应对 HTTP 劫持最有效的方法就是 HTTPS。本文学习 HTTPS 相关的知识。在学习之前首先抛出三个问题：</p><ol><li>HTTPS 加密原理是什么？</li><li>HTTPS 是否安全？为什么？</li><li>为什么抓包工具比如 Fiddler/Charles 能抓取 HTTPS 协议的包？</li></ol><span id="more"></span><h2 id="HTTPS-简介">HTTPS 简介</h2><p><a href="https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE">超文本传输安全协议</a>（英文：Hypertext Transfer Protocol Secure 缩写为：HTTPS，常称为 HTTP over TLS/SSL 或 HTTP Secure）是一种通过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信，但利用 SSL/TLS 来加密数据包。关于 TLS/SSL 的详细内容，可以查看<a href="https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E6%80%A7%E5%8D%94%E5%AE%9A">传输层安全性协议</a>。</p><h3 id="HTTPS-加密原理">HTTPS 加密原理</h3><p>传统的 HTTP 协议基于 TCP/IP 协议来传递数据，客户端通过三次握手与服务端建立连接，HTTPS 在传输数据之前需要客户端与服务端之间进行一次握手，在握手的过程中确立双方加密传输数据的密码信息。TLS/SSL 使用非对称加密、对称加密以及 HASH 算法。握手过程可以简单描述如下：</p><p>(1) 浏览器向服务器发送自己所支持的加密规则。<br>(2) 服务器从中选取一组加密算法与 HASH 算法，将自己的身份信息以证书（CA）的形式返回给浏览器。证书里面包含网站地址，加密公钥 S_PuKey 以及证书的颁发机构等信息。<br>(3) 客户端确认其颁发的证书的有效性，如果证书有效浏览器会生成一串随机数的密码 C_Key，并用证书中提供的公钥 S_PuKey 加密。然后客户端使用约定好的 HASH 计算握手消息，并使用生成的随机数 C_Key 对消息进行加密，最后将之前生成的所有信息发送给服务器。<br>(4) 服务器使用自己的私钥将信息解密取出密码 C_Key，使用 C_Key 解密浏览器发来的握手消息，并验证 HASH 是否与浏览器发来的一致。然后服务器使用密码加密一段握手消息，发送给浏览器。<br>(5) 浏览器解密并计算握手消息的 HASH，如果与服务端发来的 HASH 一致，此时握手过程结束，之后所有的通信数据将由之前浏览器生成的随机密码 C_Key 并利用对称加密算法进行加密。</p><p>HTTPS 一般使用的加密与 HASH 算法如下：</p><ul><li>非对称加密算法：RSA，DSA/DSS，用于在握手过程中加密生成的密码</li><li>对称加密算法：AES，RC4，3DES，用于数据传输过程中进行加密</li><li>HASH 算法：MD5，SHA1，SHA256，用于验证数据的完整性</li></ul><p>由于浏览器生成的密码是整个数据加密的关键，因此在传输的时候使用了非对称加密算法对其加密。非对称加密算法会生成公钥和私钥，公钥只能用于加密数据，因此可以随意传输，而网站的私钥用于对数据进行解密，所以网站都会非常小心的保管自己的私钥，防止泄漏。</p><p>TLS握手过程中如果有任何错误，都会使加密连接断开，从而阻止了隐私信息的传输。</p><p>SSL 证书验证失败有以下三点原因:</p><ul><li>SSL 证书不是由受信任的 CA 机构颁发的</li><li>证书过期</li><li>访问的网站域名与证书绑定的域名不一致</li></ul><h3 id="HTTPS-安全吗？">HTTPS 安全吗？</h3><p>HTTPS 是否安全，是一个相对的概念，从服务器身份认证，保护交换数据的隐私性和完整性方面来说，它是安全的，但是它也有自己的<a href="https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE#%E5%B1%80%E9%99%90">局限</a>。</p><p>TLS/SSL 协议依赖浏览器和服务器所支持的加密算法。<br>HTTPS 也不能防止网站被爬虫抓取，攻击者可以根据某些手段推测加密后的密文，从而使选择密文攻击成为可能。</p><h3 id="Charles-为什么能抓-HTTPS-的包？">Charles 为什么能抓 HTTPS 的包？</h3><p>Charles 抓 HTTPS 包的过程可以理解为中间人攻击。</p><p>浏览器和服务器每次新建会话时都使用非对称密钥交换算法协商出对称密钥，也就是上文所说的 C_Key，使用 C_Key 完成应用数据的加解密和验证，整个会话过程中的密钥只在内存中生成和保存，而且每个会话的 C_Key 都不相同，并且无法窃取。</p><p>所以整个加密过程中，至关重要的就是客户端生成的对称秘钥 C_Key，中间人攻击是先伪装服务器向浏览器发送伪造的公钥，从而取得浏览器的私钥。这样就完成的浏览器端和服务器端的解密。</p><blockquote><p>中间人攻击：是指攻击者与通讯的两端分别建立独立的联系，并交换其所收到的数据，使通讯的两端认为他们正在通过一个私密的连接与对方直接对话，但事实上整个会话都被攻击者完全控制。</p></blockquote><p>整个过程可以总结为以下：</p><p>(1) 首先代理软件截获浏览器发送给服务端的 HTTPS 请求，然后代理软件假装自己是浏览器向服务器发送请求进行握手。<br>(2) 代理软件获取服务器 CA 证书，验证 CA 证书并且解密后获取公钥 S_PuKey。<br>(3) 代理软件伪造 CA 证书，冒充服务器传递给客户端浏览器。浏览器解析 CA 证书，使用代理软件伪造的 S_PubKey 生成 C_Key。浏览器根据 C_Key 加密消息，并且计算 HASH，传递给代理软件。<br>(4) 代理软件根据私钥解析密文计算出浏览器的 C_Key，然后再使用服务端的公钥 S_PuKey 进行加密后返回给服务器。服务器用自己的私钥解开密文后与代理软件建立信任，握手完成。<br>(5) 代理软件获取服务器发送的密文，用对称秘钥解开，计算出服务器的明文（Charles 为什么能抓 HTTPS 的包？），再次加密后返回给浏览器。<br>(6) 整个通信过程中，代理软件一直拥有对称秘钥，因此整个 HTTPS 的过程中，信息始终对其透明。</p><p>综述：代理软件能够抓取 HTTPS 的核心是让浏览器信任代理软件的证书，使用这个证书代替要访问网站的证书。由于 CA 证书只能有特定的信任机构才能颁发，所以一般来说，中间人是无法欺骗浏览器获取信任的。而我们自己抓包则是主动信任了代理软件的证书，因此达到了使用代理软件可以抓取 HTTPS 的功能。</p><h2 id="参考资料">参考资料</h2><ul><li><a href="https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE">超文本传输安全协议</a></li><li><a href="https://www.guokr.com/post/114121/">HTTPS那些事（一）HTTPS原理</a></li><li><a href="https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB">中间人攻击</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;前面几天学习 DNS 缓存的时候，了解到了 DNS 劫持和 HTTP 劫持，关于 DNS 劫持和 HTTP 劫持的区别，知乎上一位同学给出了有趣的比喻&lt;a href=&quot;https://www.zhihu.com/question/27035513&quot;&gt;DNS劫持和HTTP劫持有什么区别？&lt;/a&gt;:&lt;br&gt;
DNS 劫持：你输入的网址是 &lt;code&gt;http://www.google.com&lt;/code&gt;，出来的是百度的页面。&lt;br&gt;
HTTP 劫持：你打开的是知乎的页面，右下角弹出唐老师的不孕不育广告（2018年更：右下角弹出：偶系渣渣辉）。&lt;br&gt;
应对 HTTP 劫持最有效的方法就是 HTTPS。本文学习 HTTPS 相关的知识。在学习之前首先抛出三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTTPS 加密原理是什么？&lt;/li&gt;
&lt;li&gt;HTTPS 是否安全？为什么？&lt;/li&gt;
&lt;li&gt;为什么抓包工具比如 Fiddler/Charles 能抓取 HTTPS 协议的包？&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="网络" scheme="https://lz5z.com/categories/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="HTTPS" scheme="https://lz5z.com/tags/HTTPS/"/>
    
    <category term="SSL/TLS" scheme="https://lz5z.com/tags/SSL-TLS/"/>
    
    <category term="TCP" scheme="https://lz5z.com/tags/TCP/"/>
    
  </entry>
  
  <entry>
    <title>Web 性能优化-首屏和白屏时间</title>
    <link href="https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E9%A6%96%E5%B1%8F%E5%92%8C%E7%99%BD%E5%B1%8F%E6%97%B6%E9%97%B4/"/>
    <id>https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E9%A6%96%E5%B1%8F%E5%92%8C%E7%99%BD%E5%B1%8F%E6%97%B6%E9%97%B4/</id>
    <published>2018-05-17T11:24:09.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<h2 id="什么是首屏和白屏时间？">什么是首屏和白屏时间？</h2><p>白屏时间是指浏览器从响应用户输入网址地址，到浏览器开始显示内容的时间。<br>首屏时间是指浏览器从响应用户输入网络地址，到首屏内容渲染完成的时间。</p><p>白屏时间 = 地址栏输入网址后回车 - 浏览器出现第一个元素<br>首屏时间 = 地址栏输入网址后回车 - 浏览器第一屏渲染完成</p><p>影响白屏时间的因素：网络，服务端性能，前端页面结构设计。<br>影响首屏时间的因素：白屏时间，资源下载执行时间。</p><p>以百度为例，将 chrome 网速调为 Fast 3G，然后打开 Performance 工具，点击 “Start profiling and reload page” 按钮，查看 Screenshots 如下图：</p><span id="more"></span><img src="/assets/img/first_and_write_screen.png" alt="first_and_write_screen"><h2 id="白屏时间">白屏时间</h2><p>通常认为浏览器开始渲染 <code>&lt;body&gt;</code> 或者解析完 <code>&lt;head&gt;</code> 的时间是白屏结束的时间点。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>白屏<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// 不兼容 performance.timing 的浏览器</span></span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">window</span>.<span class="property">pageStartTime</span> = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 页面 CSS 资源 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;xx.css&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;zz.css&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 白屏结束时间</span></span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">window</span>.<span class="property">firstPaint</span> = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 白屏时间</span></span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">console</span>.<span class="title function_">log</span>(firstPaint - performance.<span class="property">timing</span>.<span class="property">navigationStart</span>)</span></span><br><span class="line"><span class="language-javascript">        </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello World<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>白屏时间 = firstPaint - performance.timing.navigationStart || pageStartTime</p><h2 id="首屏时间">首屏时间</h2><p>关于首屏时间是否包含图片加载网上有不同的说法，个人认为，只要首屏中的图片加载完成，即是首屏完成，不在首屏中的图片可以不考虑。</p><p>计算首屏时间常用的方法有：</p><p>(1) 首屏模块标签标记法</p><p>由于浏览器解析 HTML 是按照顺序解析的，当解析到某个元素的时候，你觉得首屏完成了，就在此元素后面加入 <code>script</code> 计算首屏完成时间。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>首屏<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// 不兼容 performance.timing 的浏览器</span></span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">window</span>.<span class="property">pageStartTime</span> = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 首屏可见内容 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 首屏可见内容 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 首屏屏结束时间</span></span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">window</span>.<span class="property">firstPaint</span> = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 首屏时间</span></span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">console</span>.<span class="title function_">log</span>(firstPaint - performance.<span class="property">timing</span>.<span class="property">navigationStart</span>)</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 首屏不可见内容 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 首屏不可见内容 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>(2) 统计首屏内加载最慢的图片/iframe</p><p>通常首屏内容中加载最慢的就是图片或者 iframe 资源，因此可以理解为当图片或者 iframe 都加载出来了，首屏肯定已经完成了。</p><p>由于浏览器对每个页面的 TCP 连接数有限制，使得并不是所有图片都能立刻开始下载和显示。我们只需要监听首屏内所有的图片的 onload 事件，获取图片 onload 时间最大值，并用这个最大值减去 navigationStart 即可获得近似的首屏时间。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>首屏<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// 不兼容 performance.timing 的浏览器</span></span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">window</span>.<span class="property">pageStartTime</span> = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;https://lz5z.com/assets/img/google_atf.png&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;img&quot;</span> <span class="attr">onload</span>=<span class="string">&quot;load()&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;https://lz5z.com/assets/img/css3_gpu_speedup.png&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;img&quot;</span> <span class="attr">onload</span>=<span class="string">&quot;load()&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">function</span> <span class="title function_">load</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">window</span>.<span class="property">firstScreen</span> = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span></span><br><span class="line"><span class="language-javascript">        &#125;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">window</span>.<span class="property">onload</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 首屏时间</span></span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">window</span>.<span class="property">firstScreen</span> - performance.<span class="property">timing</span>.<span class="property">navigationStart</span>)</span></span><br><span class="line"><span class="language-javascript">        &#125;</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Performance-API"><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Performance">Performance API</a></h3><p>Performance 接口可以获取到当前页面与性能相关的信息。</p><p>(1) Performance.timing</p><p>在 chrome 中查看 performance.timing 对象：</p><img src="/assets/img/performance-timing.png" alt="performance"><p>与浏览器对应的状态如下图：</p><img src="/assets/img/performance.png" alt="performance"><p>左边红线代表的是网络传输层面的过程，右边红线代表了服务器传输回字节后浏览器的各种事件状态，这个阶段包含了浏览器对文档的解析，DOM 树构建，布局，绘制等等。</p><ul><li>navigationStart: 表示从上一个文档卸载结束时的 unix 时间戳，如果没有上一个文档，这个值将和 fetchStart 相等。</li><li>unloadEventStart: 表示前一个网页（与当前页面同域）unload 的时间戳，如果无前一个网页 unload 或者前一个网页与当前页面不同域，则值为 0。</li><li>unloadEventEnd: 返回前一个页面 unload 时间绑定的回掉函数执行完毕的时间戳。</li><li>redirectStart: 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算，否则值为 0。</li><li>redirectEnd: 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算，否则值为 0。</li><li>fetchStart: 浏览器准备好使用 HTTP 请求抓取文档的时间，这发生在检查本地缓存之前。</li><li>domainLookupStart/domainLookupEnd: DNS 域名查询开始/结束的时间，如果使用了本地缓存（即无 DNS 查询）或持久连接，则与 fetchStart 值相等</li><li>connectStart: HTTP（TCP）开始/重新 建立连接的时间，如果是持久连接，则与 fetchStart 值相等。</li><li>connectEnd: HTTP（TCP） 完成建立连接的时间（完成握手），如果是持久连接，则与 fetchStart 值相等。</li><li>secureConnectionStart: HTTPS 连接开始的时间，如果不是安全连接，则值为 0。</li><li>requestStart: HTTP 请求读取真实文档开始的时间（完成建立连接），包括从本地读取缓存。</li><li>responseStart: HTTP 开始接收响应的时间（获取到第一个字节），包括从本地读取缓存。</li><li>responseEnd: HTTP 响应全部接收完成的时间（获取到最后一个字节），包括从本地读取缓存。</li><li>domLoading: 开始解析渲染 DOM 树的时间，此时 Document.readyState 变为 loading，并将抛出 readystatechange 相关事件。</li><li>domInteractive: 完成解析 DOM 树的时间，Document.readyState 变为 interactive，并将抛出 readystatechange 相关事件，注意只是 DOM 树解析完成，这时候并没有开始加载网页内的资源。</li><li>domContentLoadedEventStart: DOM 解析完成后，网页内资源加载开始的时间，在 DOMContentLoaded 事件抛出前发生。</li><li>domContentLoadedEventEnd: DOM 解析完成后，网页内资源加载完成的时间（如 JS 脚本加载执行完毕）。</li><li>domComplete: DOM 树解析完成，且资源也准备就绪的时间，Document.readyState 变为 complete，并将抛出 readystatechange 相关事件。</li><li>loadEventStart: load 事件发送给文档，也即 load 回调函数开始执行的时间。</li><li>loadEventEnd: load 事件的回调函数执行完毕的时间。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 计算加载时间</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getPerformanceTiming</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> t = performance.<span class="property">timing</span></span><br><span class="line">    <span class="keyword">var</span> times = &#123;&#125;</span><br><span class="line">    <span class="comment">// 页面加载完成的时间，用户等待页面可用的时间</span></span><br><span class="line">    times.<span class="property">loadPage</span> = t.<span class="property">loadEventEnd</span> - t.<span class="property">navigationStart</span></span><br><span class="line">    <span class="comment">// 解析 DOM 树结构的时间</span></span><br><span class="line">    times.<span class="property">domReady</span> = t.<span class="property">domComplete</span> - t.<span class="property">responseEnd</span></span><br><span class="line">    <span class="comment">// 重定向的时间</span></span><br><span class="line">    times.<span class="property">redirect</span> = t.<span class="property">redirectEnd</span> - t.<span class="property">redirectStart</span></span><br><span class="line">    <span class="comment">// DNS 查询时间</span></span><br><span class="line">    times.<span class="property">lookupDomain</span> = t.<span class="property">domainLookupEnd</span> - t.<span class="property">domainLookupStart</span></span><br><span class="line">    <span class="comment">// 读取页面第一个字节的时间</span></span><br><span class="line">    times.<span class="property">ttfb</span> = t.<span class="property">responseStart</span> - t.<span class="property">navigationStart</span></span><br><span class="line">    <span class="comment">// 资源请求加载完成的时间</span></span><br><span class="line">    times.<span class="property">request</span> = t.<span class="property">responseEnd</span> - t.<span class="property">requestStart</span></span><br><span class="line">    <span class="comment">// 执行 onload 回调函数的时间</span></span><br><span class="line">    times.<span class="property">loadEvent</span> = t.<span class="property">loadEventEnd</span> - t.<span class="property">loadEventStart</span></span><br><span class="line">    <span class="comment">// DNS 缓存时间</span></span><br><span class="line">    times.<span class="property">appcache</span> = t.<span class="property">domainLookupStart</span> - t.<span class="property">fetchStart</span></span><br><span class="line">    <span class="comment">// 卸载页面的时间</span></span><br><span class="line">    times.<span class="property">unloadEvent</span> = t.<span class="property">unloadEventEnd</span> - t.<span class="property">unloadEventStart</span></span><br><span class="line">    <span class="comment">// TCP 建立连接完成握手的时间</span></span><br><span class="line">    times.<span class="property">connect</span> = t.<span class="property">connectEnd</span> - t.<span class="property">connectStart</span></span><br><span class="line">    <span class="keyword">return</span> times</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(2) Performance.navigation</p><ul><li>redirectCount: 0 // 页面经过了多少次重定向</li><li>type: 0<ul><li>0 表示正常进入页面；</li><li>1 表示通过 <code>window.location.reload()</code> 刷新页面；</li><li>2 表示通过浏览器前进后退进入页面；</li><li>255 表示其它方式</li></ul></li></ul><p>(3) Performance.memory</p><ul><li>jsHeapSizeLimit: 内存大小限制</li><li>totalJSHeapSize: 可使用的内存</li><li>usedJSHeapSize: JS 对象占用的内存</li></ul><h3 id="DOMContentLoaded-vs-load">DOMContentLoaded vs load</h3><p>(1) DOMContentLoaded 是指页面元素加载完毕，但是一些资源比如图片还无法看到，但是这个时候页面是可以正常交互的，比如滚动，输入字符等。 jQuery 中经常使用的 <code>$(document).ready()</code> 其实监听的就是 DOMContentLoaded 事件。</p><p>(2) load 是指页面上所有的资源（图片，音频，视频等）加载完成。jQuery 中 <code>$(document).load()</code> 监听的是 load 事件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// load</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">onload</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// DOMContentLoaded</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ready</span> (<span class="params">fn</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">addEventListener</span>) &#123;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">            <span class="variable language_">document</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, <span class="variable language_">arguments</span>.<span class="property">callee</span>, <span class="literal">false</span>)</span><br><span class="line">            <span class="title function_">fn</span>()</span><br><span class="line">        &#125;, <span class="literal">false</span>)</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="comment">// 如果 IE</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">attachEvent</span>) &#123;</span><br><span class="line">        <span class="comment">// 确保当页面是在iframe中加载时，事件依旧会被安全触发</span></span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">attachEvent</span>(<span class="string">&#x27;onreadystatechange&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">readyState</span> == <span class="string">&#x27;complete&#x27;</span>) &#123;</span><br><span class="line">                <span class="variable language_">document</span>.<span class="title function_">detachEvent</span>(<span class="string">&#x27;onreadystatechange&#x27;</span>, <span class="variable language_">arguments</span>.<span class="property">callee</span>)</span><br><span class="line">                <span class="title function_">fn</span>()</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">        <span class="comment">// 如果是 IE 且页面不在 iframe 中时，轮询调用 doScroll 方法检测DOM是否加载完毕</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">doScroll</span> &amp;&amp; <span class="keyword">typeof</span> <span class="variable language_">window</span>.<span class="property">frameElement</span> === <span class="string">&#x27;undefined&#x27;</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="title function_">doScroll</span>(<span class="string">&#x27;left&#x27;</span>)</span><br><span class="line">            &#125; <span class="keyword">catch</span>(error) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="built_in">setTimeout</span>(<span class="variable language_">arguments</span>.<span class="property">callee</span>, <span class="number">20</span>)</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="title function_">fn</span>()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="参考资料">参考资料</h2><ul><li><a href="http://www.cnblogs.com/longm/p/7382163.html">前端优化-如何计算白屏和首屏时间</a></li><li><a href="https://segmentfault.com/a/1190000005784687">前端性能的几个基础指标</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Performance">Performance - MDN</a></li><li><a href="http://www.alloyteam.com/2015/09/explore-performance/">初探 performance – 监控网页与程序性能</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;什么是首屏和白屏时间？&quot;&gt;什么是首屏和白屏时间？&lt;/h2&gt;
&lt;p&gt;白屏时间是指浏览器从响应用户输入网址地址，到浏览器开始显示内容的时间。&lt;br&gt;
首屏时间是指浏览器从响应用户输入网络地址，到首屏内容渲染完成的时间。&lt;/p&gt;
&lt;p&gt;白屏时间 = 地址栏输入网址后回车 - 浏览器出现第一个元素&lt;br&gt;
首屏时间 = 地址栏输入网址后回车 - 浏览器第一屏渲染完成&lt;/p&gt;
&lt;p&gt;影响白屏时间的因素：网络，服务端性能，前端页面结构设计。&lt;br&gt;
影响首屏时间的因素：白屏时间，资源下载执行时间。&lt;/p&gt;
&lt;p&gt;以百度为例，将 chrome 网速调为 Fast 3G，然后打开 Performance 工具，点击 “Start profiling and reload page” 按钮，查看 Screenshots 如下图：&lt;/p&gt;</summary>
    
    
    
    <category term="性能" scheme="https://lz5z.com/categories/%E6%80%A7%E8%83%BD/"/>
    
    
    <category term="首屏" scheme="https://lz5z.com/tags/%E9%A6%96%E5%B1%8F/"/>
    
    <category term="白屏" scheme="https://lz5z.com/tags/%E7%99%BD%E5%B1%8F/"/>
    
  </entry>
  
  <entry>
    <title>Web 性能优化-缓存-HTTP 缓存</title>
    <link href="https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-HTTP%E7%BC%93%E5%AD%98/"/>
    <id>https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-HTTP%E7%BC%93%E5%AD%98/</id>
    <published>2018-05-16T08:39:55.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h2 id="浏览器缓存">浏览器缓存</h2><p>HTTP 缓存通常要配合客户端（浏览器）使用才能发挥效果，所以又被称之为浏览器缓存，是 Web 性能优化的一大利器。</p><h3 id="缓存类型">缓存类型</h3><p>浏览器缓存分为强缓存和协商缓存。</p><p>(1) 强缓存：浏览器在加载资源的时候，根据资源的 HTTP Header 判断它是否命中强缓存，如果命中，浏览器直接从自己的缓存中读取资源，不会发请求到服务器。</p><p>(2) 协商缓存：当强缓存没有命中的时候，浏览器向服务器发送请求，服务器端依据资源的另外一些  HTTP Header 验证这个资源是否命中协商缓存，如果协商缓存命中，服务器会将这个请求返回 304，浏览器从缓存中加载这个资源；若未命中请求，服务端返回 200 并将资源返回客户端，浏览器更新本地缓存数据。</p><span id="more"></span><p>另外一种分类方式，可以将浏览器缓存分成 HTTP 协议缓存和非 HTTP 协议缓存。</p><p>(1) 非 HTTP 协议缓存：使用 HTML Meta 标签，开发者可以告诉浏览器是否缓存当前页面。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">META</span> <span class="attr">HTTP-EQUIV</span>=<span class="string">&quot;Pragma&quot;</span> <span class="attr">CONTENT</span>=<span class="string">&quot;no-cache&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">META</span> <span class="attr">HTTP-EQUIV</span>=<span class="string">&quot;Expires&quot;</span> <span class="attr">CONTENT</span>=<span class="string">&quot;0&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上述代码告诉浏览器当前页面不能被缓存，每次访问都要去服务端拉取。只有部分浏览器支持，缓存代理服务器不支持。</p><p>(2) HTTP 协议缓存：通过在 HTTP 协议头里面定义一些字段来告诉浏览器当前资源是否缓存，比如  Cache-Control, Expires, Last-Modified, Etag 等。</p><h2 id="HTTP-缓存">HTTP 缓存</h2><h3 id="HTTP-1-0-缓存字段">HTTP/1.0 缓存字段</h3><p>(1) <strong>Pragma</strong>：设置资源是否缓存，no-cache 表示不缓存。在 HTTP/1.1 中被 Cache-Control 替代，所以优先级低于 Cache-Control。</p><p>(2) <strong>Expires</strong>：设置资源过期时间，Expires 的值对应一个 GMT(格林尼治时间) 来告诉浏览器资源什么时间过期。缺点是如果客户端与服务端时间相差很大，会导致时间计算不精确，在 HTTP/1.1 中被 max-age 取代。</p><h3 id="HTTP-1-1-相关字段">HTTP/1.1 相关字段</h3><p>(1) <strong><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control">Cache-Control</a></strong>：设置一个相对的时间，在缓存判定的时候，由浏览器进行判断。Cache-Control 的值可以是 public, private, no-cache, no-store, no-transform 等。</p><ul><li><p>max-age(单位为 s) 设定缓存最大的有效时间，<code>Cache-Control: max-age=3600</code> 表示该资源在浏览器端一个小时内均有效。</p></li><li><p>s-maxage(单位是 s) 设定共享缓存时间，比如 CDN 或者代理。</p></li><li><p>no-store 网络资源不缓存，每次都到服务器上拉取。</p></li><li><p>no-cache 表示网络资源可以缓存一份，但使用前必须询问服务器此资源是不是最新的。</p></li><li><p>public 表明响应可以被任何对象（客户端，代理服务器等）缓存。</p></li><li><p>private 表明响应只能被单个用户缓存，其它用户或者代理服务器不能缓存这些数据。</p></li></ul><p>(2) <strong>Last-Modified/If-Modified-Since</strong>：</p><ul><li><p>Last-Modified 表示响应资源最后修改时间，需要与 Cache-Control 共同使用，是检查服务端资源更新的一种方式。</p></li><li><p>If-Modified-Since 表示资源过期时（超过 max-age），发现资源具有 Last-Modified 声明，则再次向web服务器请求时带上头 If-Modified-Since，表示请求时间。web 服务器收到请求后发现 Header 中有 If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新，说明资源又被改动过，则响应整片资源内容（写在响应消息包体内），HTTP 200；若最后修改时间较旧，说明资源无新修改，则响应HTTP 304 (无需包体，节省浏览)，告知浏览器继续使用所保存的cache。</p></li></ul><p>(3) <strong>Etag/If-None-Match</strong>：</p><ul><li><p>Etag 是根据资源内容生成的一段 hash 字符串，标识资源的状态，由服务端产生。浏览器将这串字符串传回服务器，验证资源是否发生修改。</p></li><li><p>If-None-Match 表示当资源过期时（超过 max-age），发现资源有 Etag 声明，向 web 服务器发送请求时带上 If-None-Match （Etag 值）。web 服务器收到请求后发现 Header 中带有 If-None-Match 则与被请求资源的相应校验串进行对比，决定返回 200 或者 304。</p></li></ul><h3 id="Last-Modified-vs-Etag">Last-Modified vs Etag</h3><p>Etag 可以解决 Last-Modified 存在的一些问题：</p><ul><li>某些服务器不能精确得到资源的最后修改时间，这样就无法通过最后修改时间判断资源是否更新。</li><li>如果资源修改非常频繁，而 Last-modified 只能精确到秒。</li><li>一些资源的最后修改时间改变了，但是内容没改变，使用 ETag 就认为资源还是没有修改的。</li></ul><h2 id="浏览器行为">浏览器行为</h2><p>(1) F5 刷新页面时，会跳过强缓存，检查协商缓存。<br>(2) ctrl + F5 强制刷新页面时，之间从服务端加载数据，跳过强缓存和协商缓存。</p><h2 id="参考资料">参考资料</h2><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers">HTTP Headers</a></li><li><a href="https://www.cnblogs.com/vajoy/p/5341664.html">浅谈浏览器http的缓存机制</a></li><li><a href="https://segmentfault.com/a/1190000009638800">Web缓存相关知识整理</a></li><li><a href="http://www.alloyteam.com/2016/03/discussion-on-web-caching/">浅谈Web缓存</a></li><li><a href="https://segmentfault.com/a/1190000006741200">详谈Web缓存</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;浏览器缓存&quot;&gt;浏览器缓存&lt;/h2&gt;
&lt;p&gt;HTTP 缓存通常要配合客户端（浏览器）使用才能发挥效果，所以又被称之为浏览器缓存，是 Web 性能优化的一大利器。&lt;/p&gt;
&lt;h3 id=&quot;缓存类型&quot;&gt;缓存类型&lt;/h3&gt;
&lt;p&gt;浏览器缓存分为强缓存和协商缓存。&lt;/p&gt;
&lt;p&gt;(1) 强缓存：浏览器在加载资源的时候，根据资源的 HTTP Header 判断它是否命中强缓存，如果命中，浏览器直接从自己的缓存中读取资源，不会发请求到服务器。&lt;/p&gt;
&lt;p&gt;(2) 协商缓存：当强缓存没有命中的时候，浏览器向服务器发送请求，服务器端依据资源的另外一些  HTTP Header 验证这个资源是否命中协商缓存，如果协商缓存命中，服务器会将这个请求返回 304，浏览器从缓存中加载这个资源；若未命中请求，服务端返回 200 并将资源返回客户端，浏览器更新本地缓存数据。&lt;/p&gt;</summary>
    
    
    
    <category term="性能" scheme="https://lz5z.com/categories/%E6%80%A7%E8%83%BD/"/>
    
    
    <category term="HTTP" scheme="https://lz5z.com/tags/HTTP/"/>
    
    <category term="Cache" scheme="https://lz5z.com/tags/Cache/"/>
    
  </entry>
  
  <entry>
    <title>Web 性能优化-缓存-DNS 缓存</title>
    <link href="https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-DNS%E7%BC%93%E5%AD%98/"/>
    <id>https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-DNS%E7%BC%93%E5%AD%98/</id>
    <published>2018-05-16T05:32:17.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缓存梗概">缓存梗概</h2><p>缓存技术几乎存在于网络技术发展的各个角落，从数据库到服务器，从服务器到网络，再从网络到客户端，缓存随处可见。跟前端有关的缓存技术主要有：DNS 缓存，HTTP 缓存，浏览器缓存，HTML5 缓存（localhost/manifest）和 service worker 中的 cache api。</p><h2 id="DNS-缓存">DNS 缓存</h2><p>当用户在浏览器中输入网址的地址后，浏览器要做的第一件事就是解析 DNS：</p><p>(1) 浏览器检查缓存中是否有域名对应的 IP，如果有就结束 DNS 解析过程。浏览器中的 DNS 缓存有时间和大小双重限制，时间一般为几分钟到几个小时不等。DNS 缓存时间过长会导致如果 IP 地址发生变化，无法解析到正确的 IP 地址；时间过短会导致浏览器重复解析域名。</p><p>(2) 如果浏览器缓存中没有对应的 IP 地址，浏览器会继续查找操作系统缓存中是否有域名对应的 DNS 解析结果。我们可以通过在操作系统中设置 hosts 文件来设置 IP 与域名的关系。</p><span id="more"></span><p>(3) 如果还没有拿到解析结果，操作系统就会把域名发送给本地区的域名服务器（LDNS），LDNS 通常由互联网服务提供商（ISP）提供，比如电信或者联通。这个域名服务器一般在城市某个角落，并且性能较好，当拿到域名后，首先也是从缓存中查找，看是否有匹配的结果。一般来说，大多数的 DNS 解析到这里就结束了，所以 LDNS/ISP DNS 承担了大部分的域名解析工作。如果缓存中有 IP 地址，就直接返回，并且会被标记为<strong>非权威服务器应答</strong>。</p><blockquote><p>第三步有一点需要注意的是，如果用户在自己电脑里设置了 DNS，比如 Google 的 <code>8.8.8.8</code> 或者 CloudFlare 新出的 <a href="https://blog.cloudflare.com/announcing-1111/"><code>1.1.1.1</code></a>，将不会通过 ISP DNS 服务器解析。</p></blockquote><p>(4) 如果前面三步还没有命中 DNS 缓存，那只能到 Root Server 域名服务器中请求解析了。根域名服务器拿到请求后，首先判断域名是哪个顶级域名下的，比如 <code>.com</code>, <code>.cn</code>, <code>.org</code> 等，全球一共 13 台顶级域名服务器。根域名服务器返回对应的顶级域名服务器（gTLD Server）地址。</p><p>(5) 本地域名服务器（LDNS）拿到地址后，向 gTLD Server 发送请求，gTLD 服务器查找并且返回此域名对应的 Name Server 域名服务器地址。这个 Name Server 通常就是用户注册的域名服务器，例如用户在某个域名服务提供商申请的域名，那么这个域名解析任务就由这个域名提供商的服务器来完成。</p><blockquote><p>这个过程的解析方式为递归搜索。比如：<code>https://movie.lz5z.com</code>，本地域名服务器首先向顶级域名服务器（com 域）发送请求，com 域名服务器将域名中的二级域 <code>lz5z</code> 的 IP 地址返回给 LDNS，LDNS 再向二级域名服务器发送请求进行查询，之后不断重复直到 LDNS 得到最终的查询结果。</p></blockquote><p>(6) Name Server 域名服务器会查询存储的域名和 IP 的映射关系表，在正常情况下都根据域名得到目标 IP 地址，连同一个 TTL 值返回给 LDNS。LDNS 会缓存这个域名和 IP 的对应关系，缓存时间由 TTL 值控制。LDNS 会把解析结果返回给用户，DNS 解析结束。</p><h3 id="清除-DNS-缓存">清除 DNS 缓存</h3><p>(1) chrome: <code>chrome://net-internals/#dns</code><br>(2) 本地 DNS ：Windows: <code>ipconfig /flushdns</code>; Linux 和 mac 根据不同的版本有不同的方式</p><h3 id="减少-DNS-解析我们能做什么？">减少 DNS 解析我们能做什么？</h3><p>(1) 减少 DNS 查询，避免重定向。<br>(2) DNS 预解析：</p><ul><li>可以通过 meta 信息告诉浏览器，页面需要做 DNS 预解析。</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;x-dns-prefetch-control&quot;</span> <span class="attr">content</span>=<span class="string">&quot;on&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><ul><li>通过 link 标签强制 DNS 预解析</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;dns-prefetch&quot;</span> <span class="attr">href</span>=<span class="string">&quot;https://lz5z.com&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>(3) 域名发散/域名收敛</p><ul><li>域名发散</li></ul><p>PC 端因为浏览器有域名并发请求限制（chrome 为 6 个），也就是同一时间，浏览器最多向同一个域名发送 6 个请求，因此 PC 端使用域名发散策略，将 http 静态资源放入多个域名/子域名中，以保证资源更快加载。常见的办法为使用 cdn。</p><ul><li>域名收敛</li></ul><p>将静态资源放在同一个域名下，减少 DNS 解析的开销。域名收敛是移动互联网时代的产物，在 LDNS 没有缓存的情况下，DNS 解析占据一个请求的大多数时间，因此，采用尽可能少的域名对整个页面加载速度有显著的提高。</p><p>(4) HttpDNS</p><p>DNS 请求使用的是 UDP 协议，虽然没有 TCP 三次握手的开销，但是可能导致弱网环境下（2G，3G）数据丢失的问题。还记得之前<a href="https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E9%A1%B5%E9%9D%A2%E9%87%8D%E7%BB%98%E5%92%8C%E5%9B%9E%E6%B5%81/">Web 性能优化-页面重绘和回流（重排）</a>中提到的 Google 1s 终端首屏渲染标准，假如 DNS 解析出现问题，那可能几秒甚至几十秒都首屏不了了。而且国内牛 X 的运营商的品质你也是知道的，随便劫持一下 DNS 就让你的 web 应用不见天日。</p><p>为了应对以上两个问题，HttpDNS 应运而生，原理也非常简单，将 DNS 这种容易被劫持的协议，转而使用 HTTP 协议请求 Domain 与 IP 地址之间的映射。获得正确的 IP 地址后，就不用担心 ISP 篡改数据了。</p><p>国内腾讯云和阿里云都有相应的解决方案</p><ul><li><a href="https://cloud.tencent.com/product/hd">移动解析HttpDNS</a></li><li><a href="https://cn.aliyun.com/product/httpdns">HTTPDNS</a></li></ul><p>Google 的方案则更近一步，使用 https 协议。</p><ul><li><a href="https://developers.google.com/speed/public-dns/docs/dns-over-https">DNS-over-HTTPS</a></li></ul><h2 id="参考资料">参考资料</h2><ul><li><a href="http://www.cnblogs.com/xrq730/p/4931418.html">DNS域名解析过程</a></li><li><a href="http://taobaofed.org/blog/2015/12/16/h5-performance-optimization-and-domain-convergence/">无线性能优化：域名收敛</a></li><li><a href="https://www.cloudxns.net/Support/detail/id/1273.html">提升页面访问速度的前端优化大法：DNS预解析</a></li><li><a href="https://www.jianshu.com/p/6c790b9652a2">也谈 HTTPS - HTTPDNS + HTTPS</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;缓存梗概&quot;&gt;缓存梗概&lt;/h2&gt;
&lt;p&gt;缓存技术几乎存在于网络技术发展的各个角落，从数据库到服务器，从服务器到网络，再从网络到客户端，缓存随处可见。跟前端有关的缓存技术主要有：DNS 缓存，HTTP 缓存，浏览器缓存，HTML5 缓存（localhost/manifest）和 service worker 中的 cache api。&lt;/p&gt;
&lt;h2 id=&quot;DNS-缓存&quot;&gt;DNS 缓存&lt;/h2&gt;
&lt;p&gt;当用户在浏览器中输入网址的地址后，浏览器要做的第一件事就是解析 DNS：&lt;/p&gt;
&lt;p&gt;(1) 浏览器检查缓存中是否有域名对应的 IP，如果有就结束 DNS 解析过程。浏览器中的 DNS 缓存有时间和大小双重限制，时间一般为几分钟到几个小时不等。DNS 缓存时间过长会导致如果 IP 地址发生变化，无法解析到正确的 IP 地址；时间过短会导致浏览器重复解析域名。&lt;/p&gt;
&lt;p&gt;(2) 如果浏览器缓存中没有对应的 IP 地址，浏览器会继续查找操作系统缓存中是否有域名对应的 DNS 解析结果。我们可以通过在操作系统中设置 hosts 文件来设置 IP 与域名的关系。&lt;/p&gt;</summary>
    
    
    
    <category term="性能" scheme="https://lz5z.com/categories/%E6%80%A7%E8%83%BD/"/>
    
    
    <category term="DNS" scheme="https://lz5z.com/tags/DNS/"/>
    
    <category term="HttpDNS" scheme="https://lz5z.com/tags/HttpDNS/"/>
    
    <category term="LDNS" scheme="https://lz5z.com/tags/LDNS/"/>
    
  </entry>
  
  <entry>
    <title>Web 性能优化-CSS3 硬件加速(GPU 加速)</title>
    <link href="https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-CSS3%E7%A1%AC%E4%BB%B6%E5%8A%A0%E9%80%9F/"/>
    <id>https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-CSS3%E7%A1%AC%E4%BB%B6%E5%8A%A0%E9%80%9F/</id>
    <published>2018-05-03T12:10:40.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h2 id="CSS3-硬件加速简介">CSS3 硬件加速简介</h2><p>上一篇文章学习了重绘和回流对页面性能的影响，是从比较宏观的角度去优化 Web 性能，本篇文章从每一帧的微观角度进行分析，来学习 CSS3 硬件加速的知识。</p><p>CSS3 硬件加速又叫做 GPU 加速，是利用 GPU 进行渲染，减少 CPU 操作的一种优化方案。由于 GPU 中的 transform 等 CSS 属性不会触发 repaint，所以能大大提高网页的性能。</p><p>我做了一个页面，左边元素的动画通过 left/top 操作位置实现，右边元素的动画通过 <code>transform: translate</code> 实现，你可以打开 chrome 的 “Paint flashing” 查看，绿色部分是正在 repaint 的内容。</p><p><a href="https://lz5z.com/css3_hardware_speedup/">查看地址</a></p><p>从 demo 中可以看到左边的图形在运动时外层有一圈绿色的边框，表示元素不停地 repaint，并且可以看到其运动过程中有丢帧现象，具体表现为运动不连贯，有轻微闪动。</p><span id="more"></span><h3 id="动画与帧">动画与帧</h3><p>之前学习 flash 的时候，就知道动画是由一帧一帧的图片组成，在浏览器中也是如此。我们首先看一下，浏览器每一帧都做了什么。</p><img src="/assets/img/css3_gpu_speedup.png" alt="css3_gpu_speedup" style="max-height: : 66px"><blockquote><ol><li>JavaScript：JavaScript 实现动画效果，DOM 元素操作等。</li><li>Style（计算样式）：确定每个 DOM 元素应该应用什么 CSS 规则。</li><li>Layout（布局）：计算每个 DOM 元素在最终屏幕上显示的大小和位置。由于 web 页面的元素布局是相对的，所以其中任意一个元素的位置发生变化，都会联动的引起其他元素发生变化，这个过程叫 reflow。</li><li>Paint（绘制）：在多个层上绘制 DOM 元素的的文字、颜色、图像、边框和阴影等。</li><li>Composite（渲染层合并）：按照合理的顺序合并图层然后显示到屏幕上。</li></ol></blockquote><h3 id="动画与图层">动画与图层</h3><p>浏览器在获取 render tree（详细知识可以查看<a href="https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E9%A1%B5%E9%9D%A2%E9%87%8D%E7%BB%98%E5%92%8C%E5%9B%9E%E6%B5%81/">Web性能优化-页面重绘和回流（重排）</a>）后，渲染树中包含了大量的渲染元素，每一个渲染元素会被分到一个图层中，每个图层又会被加载到 GPU 形成渲染纹理。GPU 中 transform 是不会触发 repaint 的，这一点非常类似 3D 绘图功能，最终这些使用 transform 的图层都会由独立的合成器进程进行处理。</p><p>过程如下：</p><p>render tree -&gt; 渲染元素 -&gt; 图层 -&gt; GPU 渲染 -&gt; 浏览器复合图层 -&gt; 生成最终的屏幕图像。</p><blockquote><p>TIPS: chrome devtools 中可以开启 Rendering 中的 Layer borders 查看图层纹理。<br>其中黄色边框表示该元素有 3d 变换，表示放到一个新的复合层（composited layer）中渲染，蓝色栅格表示正常的 render layer。</p></blockquote><p>在文章开始给出的<a href="https://lz5z.com/css3_hardware_speedup/">例子</a>中，我们也可以开启 Layer borders，可以观察到，使用 <code>transform: translate</code> 动画的元素，外围有一个黄色的边框，可知其为复合层。</p><img src="/assets/img/css3_gpu_lauer_borders.png" alt="css3_gpu_lauer_borders"><p>在 GPU 渲染的过程中，一些元素会因为符合了某些规则，而被提升为独立的层（黄色边框部分），一旦独立出来，就不会影响其它 DOM 的布局，所以我们可以利用这些规则，将经常变换的 DOM 主动提升到独立的层，那么在浏览器的一帧运行中，就可以减少 Layout 和 Paint 的时间了。</p><h3 id="创建独立图层">创建独立图层</h3><p>哪些规则能让浏览器主动帮我们创建独立的层呢？</p><ol><li>3D 或者透视变换（perspective，transform） 的 CSS 属性。</li><li>使用加速视频解码的 video 元素。</li><li>拥有 3D（WebGL） 上下文或者加速 2D 上下文的 canvas 元素。</li><li>混合插件（Flash)。</li><li>对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素。</li><li>拥有加速 CSS 过滤器的元素。</li><li>元素有一个包含复合层的后代节点(换句话说，就是一个元素拥有一个子元素，该子元素在自己的层里)。</li><li>元素有一个兄弟元素在复合图层渲染，并且该兄弟元素的 z-index 较小，那这个元素也会被应用到复合图层。</li></ol><p>关于 z-index 导致的硬件加速的问题，可以查看这篇文章 <a href="http://div.io/topic/1348">CSS3硬件加速也有坑！！</a></p><h3 id="开启-GPU-加速">开启 GPU 加速</h3><p>CSS 中的以下几个属性能触发硬件加速：</p><ol><li>transform</li><li>opacity</li><li>filter</li><li>will-change</li></ol><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.element</span> &#123;</span><br><span class="line">    -webkit-<span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">    -moz-<span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">    -ms-<span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">    -o-<span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>); </span><br><span class="line">    <span class="comment">/**或者**/</span></span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">rotateZ</span>(<span class="number">360deg</span>);</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translate3d</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意：我在不同的资料中查到的 transform 是否能触发硬件加速的结果不同，自己测试后，发现结果是可以。</p><h3 id="要注意的问题">要注意的问题</h3><p>（1）过多地开启硬件加速可能会耗费较多的内存，因此什么时候开启硬件加速，给多少元素开启硬件加速，需要用测试结果说话。<br>（2）GPU 渲染会影响字体的抗锯齿效果。这是因为 GPU 和 CPU 具有不同的渲染机制，即使最终硬件加速停止了，文本还是会在动画期间显示得很模糊。</p><h2 id="参考文章">参考文章</h2><ul><li><a href="http://blog.teamtreehouse.com/increase-your-sites-performance-with-hardware-accelerated-css">Increase Your Site’s Performance with Hardware-Accelerated CSS</a></li><li><a href="http://www.cnblogs.com/rubylouvre/p/3471490.html">用CSS开启硬件加速来提高网站性能</a></li><li><a href="https://www.jianshu.com/p/f8b1d6e598db">css3硬件加速</a></li><li><a href="http://div.io/topic/1348">CSS3硬件加速也有坑！！</a></li><li><a href="https://aotu.io/notes/2017/04/11/GPU/index.html">GPU加速是什么</a></li><li><a href="http://www.zhangxinxu.com/wordpress/2015/11/css3-will-change-improve-paint/">使用CSS3 will-change提高页面滚动、动画等渲染性能</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;CSS3-硬件加速简介&quot;&gt;CSS3 硬件加速简介&lt;/h2&gt;
&lt;p&gt;上一篇文章学习了重绘和回流对页面性能的影响，是从比较宏观的角度去优化 Web 性能，本篇文章从每一帧的微观角度进行分析，来学习 CSS3 硬件加速的知识。&lt;/p&gt;
&lt;p&gt;CSS3 硬件加速又叫做 GPU 加速，是利用 GPU 进行渲染，减少 CPU 操作的一种优化方案。由于 GPU 中的 transform 等 CSS 属性不会触发 repaint，所以能大大提高网页的性能。&lt;/p&gt;
&lt;p&gt;我做了一个页面，左边元素的动画通过 left/top 操作位置实现，右边元素的动画通过 &lt;code&gt;transform: translate&lt;/code&gt; 实现，你可以打开 chrome 的 “Paint flashing” 查看，绿色部分是正在 repaint 的内容。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lz5z.com/css3_hardware_speedup/&quot;&gt;查看地址&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;从 demo 中可以看到左边的图形在运动时外层有一圈绿色的边框，表示元素不停地 repaint，并且可以看到其运动过程中有丢帧现象，具体表现为运动不连贯，有轻微闪动。&lt;/p&gt;</summary>
    
    
    
    <category term="性能" scheme="https://lz5z.com/categories/%E6%80%A7%E8%83%BD/"/>
    
    
    <category term="动画" scheme="https://lz5z.com/tags/%E5%8A%A8%E7%94%BB/"/>
    
    <category term="CSS" scheme="https://lz5z.com/tags/CSS/"/>
    
    <category term="GPU" scheme="https://lz5z.com/tags/GPU/"/>
    
  </entry>
  
  <entry>
    <title>Web 性能优化-页面重绘和回流（重排）</title>
    <link href="https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E9%A1%B5%E9%9D%A2%E9%87%8D%E7%BB%98%E5%92%8C%E5%9B%9E%E6%B5%81/"/>
    <id>https://lz5z.com/Web%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E9%A1%B5%E9%9D%A2%E9%87%8D%E7%BB%98%E5%92%8C%E5%9B%9E%E6%B5%81/</id>
    <published>2018-05-02T13:46:24.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言">前言</h2><p>早在五年前，Google 就提出了 1s 完成终端页面的首屏渲染的标准。</p><img src="/assets/img/google_atf.png" alt="google_atf"><p>常见的优化网络请求的方法有：DNS Lookup，减少重定向，避免 JS、CSS 阻塞，并行请求，代码压缩，缓存，按需加载，前端模块化…</p><span id="more"></span><p>虽然相较于网络方面的优化，前端渲染的优化显得杯水车薪，而且随着浏览器和硬件性能的增长，再加上主流前端框架（react、vue、angular）的已经帮我们解决了大多数的性能问题，但是前端渲染性能优化依然值得学习，除去网络方面的消耗，留给前端渲染的时间已经不多了。本文主要学习前端渲染相关的问题。</p><h2 id="浏览器是如何渲染一个页面的">浏览器是如何渲染一个页面的</h2><blockquote><ol><li>浏览器把获取到的 HTML 代码解析成1个 DOM 树，HTML 中的每个 tag 都是 DOM 树中的1个节点，根节点是 document 对象。DOM 树里包含了所有 HTML 标签，包括 <code>display:none</code> 隐藏的标签，还有用 JS 动态添加的元素等。</li><li>浏览器把所有样式解析成样式结构体，在解析的过程中会去掉浏览器不能识别的样式，比如 IE 会去掉 -moz 开头的样式。</li><li>DOM Tree 和样式结构体组合后构建 render tree, render tree 类似于 DOM tree，但区别很大，render tree 能识别样式，render tree 中每个 NODE 都有自己的 style，而且 render tree 不包含隐藏的节点 (比如 <code>display:none</code> 的节点，还有 head 节点)，因为这些节点不会用于呈现，而且不会影响呈现的节点，所以就不会包含到 render tree 中。注意 <code>visibility:hidden</code> 隐藏的元素还是会包含到 render tree 中的，因为 <code>visibility:hidden</code> 会影响布局(layout)，会占有空间。根据 CSS2 的标准，render tree 中的每个节点都称为 Box (Box dimensions)，理解页面元素为一个具有填充、边距、边框和位置的盒子。</li><li>一旦 render tree 构建完毕后，浏览器就可以根据 render tree 来绘制页面了。</li></ol></blockquote><p>总结为下图：</p><img src="/assets/img/web_repaint_reflow.png" alt="web_repaint_reflow"><p>图片来自 <a href="https://segmentfault.com/a/1190000010298038">浏览器渲染页面过程与页面优化</a></p><p>在此过程中，前端工程师主要的敌人为：</p><ol><li>重新计算样式（Recalculate Style）、计算布局（Layout）=&gt; Rendering/Reflow。</li><li>绘制 =&gt; Painting/Repaint。</li></ol><h3 id="重绘与回流">重绘与回流</h3><ol><li>当 render tree 中的一部分（或全部）因为元素的规模尺寸、布局、显示/隐藏等改变而需要重新构建，这个过程称作回流（reflow）。页面第一次加载的时候，至少发生一次回流。</li><li>当 render tree 中的一些元素需要更新属性，而这些属性只是影响元素的外观，风格，而不会影响布局的，比如 background-color，这个过程叫做重绘（repaint）</li></ol><p>在回流的时候，浏览器会使 render tree 中受到影响的部分失效，并重新构造这部分渲染树，完成回流后，浏览器会重新绘制受影响的部分到屏幕中，该过程成为重绘。因此<strong>回流必将引起重绘，而重绘不一定会引起回流。</strong></p><p>Reflow 的成本比 Repaint 高得多的多。DOM Tree 里的每个结点都会有 reflow 方法，一个结点的 reflow 很有可能导致子结点，甚至父点以及同级结点的 reflow。</p><h3 id="在-chrome-中查看-repaint">在 chrome 中查看 repaint</h3><p>F12 打开控制台 -&gt; DevTools -&gt; Show console drawer -&gt; Rendering -&gt; 勾选 Paint flashing。</p><h3 id="重绘何时发生">重绘何时发生</h3><p>当一个元素的外观的可见性 visibility 发生改变的时候，但是不影响布局。类似的例子包括：outline, visibility, background color。</p><h3 id="回流何时发生">回流何时发生</h3><ol start="0"><li>页面渲染初始化。</li><li>调整窗口大小。</li><li>改变字体，比如修改网页默认字体。</li><li>增加或者移除样式表。</li><li>内容变化，比如文本改变或者图片大小改变而引起的计算值宽度和高度改变。</li><li>激活 CSS 伪类，比如 :hover</li><li>操作 class 属性。</li><li>脚本操作 DOM，增加删除或者修改 DOM 节点，元素尺寸改变——边距、填充、边框、宽度和高度。</li><li>计算 offsetWidth 和 offsetHeight 属性。</li><li>设置 style 属性的值。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> s = <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">style</span></span><br><span class="line">s.<span class="property">padding</span> = <span class="string">&quot;2px&quot;</span> <span class="comment">// 回流+重绘</span></span><br><span class="line">s.<span class="property">border</span> = <span class="string">&quot;1px solid red&quot;</span> <span class="comment">// 回流+重绘</span></span><br><span class="line">s.<span class="property">color</span> = <span class="string">&quot;blue&quot;</span> <span class="comment">// 重绘</span></span><br><span class="line">s.<span class="property">backgroundColor</span> = <span class="string">&quot;#ccc&quot;</span> <span class="comment">// 重绘</span></span><br><span class="line">s.<span class="property">fontSize</span> = <span class="string">&quot;14px&quot;</span> <span class="comment">// 再一次 回流+重绘</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(<span class="variable language_">document</span>.<span class="title function_">createTextNode</span>(<span class="string">&#x27;abc!&#x27;</span>)) <span class="comment">// 回流+重绘</span></span><br></pre></td></tr></table></figure><h3 id="浏览器">浏览器</h3><p>如果向上述代码中那样，浏览器不停地回流+重绘，很可能性能开销非常大，实际上浏览器会优化这些操作，将所有引起回流和重绘的操作放入一个队列中，等待队列达到一定的数量或者时间间隔，就 flush 这个队列，一次性处理所有的回流和重绘。</p><p>虽然有浏览器优化，但是当我们向浏览器请求一些 style 信息的时候，浏览器为了确保我们能拿到精确的值，就会提前 flush 队列。</p><ol><li>offsetTop/Left/Width/Height</li><li>scrollTop/Left/Width/Height</li><li>clientTop/Left/Width/Height</li><li>width,height</li><li>getComputedStyle(), 或者 IE的 currentStyle</li></ol><h3 id="减少回流重绘">减少回流重绘</h3><ul><li><p>requestAnimationFrame：能保证浏览器在正确的时间进行渲染。</p></li><li><p>保持 DOM 操作“原子性”：</p></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">var</span> newWidth = ele.<span class="property">offsetWidth</span> + <span class="number">10</span></span><br><span class="line">ele.<span class="property">style</span>.<span class="property">width</span> = newWidth + <span class="string">&#x27;px&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> newHeight = ele.<span class="property">offsetHeight</span> + <span class="number">10</span></span><br><span class="line">ele.<span class="property">style</span>.<span class="property">height</span> = newHeight + <span class="string">&#x27;px&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// good 读写分离，批量操作</span></span><br><span class="line"><span class="keyword">var</span> newWidth = ele.<span class="property">offsetWidth</span> + <span class="number">10</span> <span class="comment">// read</span></span><br><span class="line"><span class="keyword">var</span> newHeight = ele.<span class="property">offsetHeight</span> + <span class="number">10</span> <span class="comment">// read</span></span><br><span class="line">ele.<span class="property">style</span>.<span class="property">width</span> = newWidth + <span class="string">&#x27;px&#x27;</span> <span class="comment">// write</span></span><br><span class="line">ele.<span class="property">style</span>.<span class="property">height</span> = newHeight + <span class="string">&#x27;px&#x27;</span> <span class="comment">// write</span></span><br></pre></td></tr></table></figure><ul><li>使用 classList 代替 className：</li></ul><p>className 只要赋值，就一定出现一次 rendering 计算；classList 的 add 和 remove，浏览器会进行样式名是否存在的判断，以减少重复的 rendering。</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></pre></td><td class="code"><pre><span class="line">ele.<span class="property">className</span> += <span class="string">&#x27;something&#x27;</span></span><br><span class="line">ele.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&#x27;something&#x27;</span>)</span><br><span class="line">ele.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&#x27;something&#x27;</span>)</span><br></pre></td></tr></table></figure><ul><li>批量操作借助临时变量</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">  el.<span class="property">style</span>.<span class="property">left</span> = el.<span class="property">offsetLeft</span> + <span class="number">5</span> + <span class="string">&#x27;px&#x27;</span></span><br><span class="line">  el.<span class="property">style</span>.<span class="property">top</span> = el.<span class="property">offsetTop</span> + <span class="number">5</span> + <span class="string">&#x27;px&#x27;</span> </span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// good</span></span><br><span class="line"><span class="keyword">let</span> left = el.<span class="property">offsetLeft</span></span><br><span class="line"><span class="keyword">let</span> top = el.<span class="property">offsetTop</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">  left += <span class="number">5</span></span><br><span class="line">  top += <span class="number">5</span> </span><br><span class="line">&#125;</span><br><span class="line">el.<span class="property">style</span>.<span class="property">left</span> = left + <span class="string">&#x27;px&#x27;</span></span><br><span class="line">el.<span class="property">style</span>.<span class="property">top</span> = left + <span class="string">&#x27;px&#x27;</span> </span><br></pre></td></tr></table></figure><ul><li>对元素进行“离线操作”，完成后再一起更新：</li></ul><ol><li>使用 DocumentFragment 进行缓存操作,引发一次回流和重绘 <a href="http://www.cnblogs.com/blueSkys/p/3685740.html">了解DocumentFragment 给我们带来的性能优化</a></li><li>元素操作前使用 <code>display: none</code>，完成后再将其显示出来，这样只会触发一次回流和重绘。</li><li>使用 cloneNode + replaceChild 技术，引发一次回流和重绘。</li></ol><p>假如需要在下面的 html 中添加两个 li 节点：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br></pre></td></tr></table></figure><p>使用 JavaScript：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> ul = <span class="variable language_">document</span>.<span class="title function_">getElementByTagName</span>(<span class="string">&#x27;ul&#x27;</span>)</span><br><span class="line"><span class="keyword">let</span> man = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;li&#x27;</span>)</span><br><span class="line">man.<span class="property">innerHTML</span> = <span class="string">&#x27;man&#x27;</span></span><br><span class="line">ul.<span class="title function_">appendChild</span>(li)</span><br><span class="line"> </span><br><span class="line"><span class="keyword">let</span> woman = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;li&#x27;</span>)</span><br><span class="line">woman.<span class="property">innerHTML</span> = <span class="string">&#x27;woman&#x27;</span></span><br><span class="line">ul.<span class="title function_">appendChild</span>(woman)</span><br></pre></td></tr></table></figure><p>上述代码会发生两次回流，假如使用 <code>display: none</code> 的方案，虽然能够减少回流次数，但是会发生一次闪烁，这时候使用 DocumentFragment  的优势就体现出来了。</p><p>DocumentFragment 有两大特点：</p><ol><li>DocumentFragment 节点不属于文档树，继承的 parentNode 属性总是 null。</li><li>当请求把一个 DocumentFragment 节点插入文档树时，插入的不是 DocumentFragment 自身，而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符，暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。、</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> fragment = <span class="variable language_">document</span>.<span class="title function_">createDocumentFragment</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> man = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;li&#x27;</span>)</span><br><span class="line"><span class="keyword">let</span> woman = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;li&#x27;</span>)</span><br><span class="line">man.<span class="property">innerHTML</span> = <span class="string">&#x27;man&#x27;</span></span><br><span class="line">woman.<span class="property">innerHTML</span> = <span class="string">&#x27;woman&#x27;</span></span><br><span class="line">fragment.<span class="title function_">appendChild</span>(man)</span><br><span class="line">fragment.<span class="title function_">appendChild</span>(woman)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(spanNode)</span><br></pre></td></tr></table></figure><p>可见 DocumentFragment 是一个孤儿节点，没爹就能出生，但是在需要它的时候，它又无私地把孩子奉献给文档树，然后自己默默离开。是不是有点像《银翼杀手2049》？</p><h2 id="参考资料">参考资料</h2><ul><li><a href="http://velocity.oreilly.com.cn/2013/ppts/16_ms_optimization--web_front-end_performance_optimization.pdf">16毫秒的优化<br></a></li><li><a href="https://segmentfault.com/a/1190000010298038">浏览器渲染页面过程与页面优化</a></li><li><a href="http://www.css88.com/archives/4996">页面重绘和回流以及优化</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;早在五年前，Google 就提出了 1s 完成终端页面的首屏渲染的标准。&lt;/p&gt;
&lt;img src=&quot;/assets/img/google_atf.png&quot; alt=&quot;google_atf&quot;&gt;
&lt;p&gt;常见的优化网络请求的方法有：DNS Lookup，减少重定向，避免 JS、CSS 阻塞，并行请求，代码压缩，缓存，按需加载，前端模块化…&lt;/p&gt;</summary>
    
    
    
    <category term="性能" scheme="https://lz5z.com/categories/%E6%80%A7%E8%83%BD/"/>
    
    
    <category term="CSS" scheme="https://lz5z.com/tags/CSS/"/>
    
    <category term="重绘" scheme="https://lz5z.com/tags/%E9%87%8D%E7%BB%98/"/>
    
    <category term="回流" scheme="https://lz5z.com/tags/%E5%9B%9E%E6%B5%81/"/>
    
  </entry>
  
  <entry>
    <title>前端模块化-CommonJS,AMD,CMD,ES6</title>
    <link href="https://lz5z.com/JavaScript%E6%A8%A1%E5%9D%97%E5%8C%96-CommonJS-AMD-CMD-ES6/"/>
    <id>https://lz5z.com/JavaScript%E6%A8%A1%E5%9D%97%E5%8C%96-CommonJS-AMD-CMD-ES6/</id>
    <published>2018-04-26T15:03:17.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<h2 id="模块化解决什么问题">模块化解决什么问题</h2><p>随着 JavaScript 工程越来越大，团队协作不可避免，为了更好地对代码进行管理和测试，模块化的概念逐渐引入前端。模块化可以降低协同开发的成本，减少代码量，同时也是“高内聚，低耦合”的基础。</p><p>模块化主要解决两个问题：</p><ol><li>命名冲突</li><li>文件依赖：比如 bootstrap 需要引入 jquery，jquery 文件的位置必须要 bootstrap.js 之前引入。</li></ol><span id="more"></span><h3 id="远古时代的人们是怎样解决模块化的">远古时代的人们是怎样解决模块化的</h3><p>在各种模块化规范出来之前，人们使用匿名闭包函数解决模块化的问题。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> num0 = <span class="number">2</span>; <span class="comment">// 注意这里的分号</span></span><br><span class="line">(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> num1 = <span class="number">3</span></span><br><span class="line">  <span class="keyword">var</span> num2 = <span class="number">5</span> </span><br><span class="line">  <span class="keyword">var</span> add = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> num0 + num1 + num2</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>()) <span class="comment">// 10</span></span><br><span class="line">&#125;)()</span><br><span class="line"></span><br><span class="line"><span class="comment">// console.log(num1) // num1 is not defined</span></span><br></pre></td></tr></table></figure><p>这样做的好处是，你可以在函数内部使用全局变量和局部变量，并且不用担心局部变量污染全局变量。这种用括号把匿名函数包起来的方式，也叫做立即执行函数（IIFE）。所有函数内部代码都在闭包(closure)内。它提供了整个应用生命周期的私有和状态。</p><h3 id="CommonJS-规范">CommonJS 规范</h3><p>CommonJS 将每个文件都视为一个模块，在每个模块中变量默认都是私有变量，通过 module.exports 定义当前模块对外输出的接口，通过 require 加载模块。</p><p>(1) 使用方法：</p><p>circle.js</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="variable constant_">PI</span> &#125; = <span class="title class_">Math</span></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">area</span> = <span class="function">(<span class="params">r</span>) =&gt;</span> <span class="variable constant_">PI</span> * r ** <span class="number">2</span></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">circumference</span> = <span class="function">(<span class="params">r</span>) =&gt;</span> <span class="number">2</span> * <span class="variable constant_">PI</span> * r</span><br></pre></td></tr></table></figure><p>app.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> circle = <span class="built_in">require</span>(<span class="string">&#x27;./circle.js&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(circle.<span class="title function_">area</span>(<span class="number">4</span>))</span><br></pre></td></tr></table></figure><p>(2) 原理：node 在编译 js 文件的过程中，会使用一个如下的函数包装器将其包装<a href="http://nodejs.cn/api/modules.html#modules_the_module_wrapper">模块包装器</a>：</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="keyword">function</span> (<span class="params"><span class="built_in">exports</span>, <span class="built_in">require</span>, <span class="variable language_">module</span>, __filename, __dirname</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> circle = <span class="built_in">require</span>(<span class="string">&#x27;./circle.js&#x27;</span>)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(circle.<span class="title function_">area</span>(<span class="number">4</span>))</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>这也是为什么在 node 环境中可以使用这几个没有显式定义的变量的原因。其中 <code>__filename</code> 和 <code>__dirname</code> 在查找文件路径的过程中分析得到后传入的。module 变量是这个模块对象自身，exports 是在 module 的构造函数中初始化的一个空对象。</p><p>更详细的内容可以参考 <a href="http://nodejs.cn/api/modules.html">node modules</a></p><p>关于什么时候使用 exports、什么时候使用 module.exports，可以参考 <a href="http://nodejs.cn/api/modules.html#modules_exports_shortcut">exports shutcut</a></p><p>(3) 优点 vs 缺点</p><p>CommonJS 能够避免全局命名空间污染，并且明确代码之间的依赖关系。但是 CommonJS 的模块加载是同步的，假如一个模块引用三个其它模块，那么这三个模块需要被完全加载后这个模块才能运行。这在服务端不是什么问题（node），但是在浏览器端就不是那么高效了，毕竟读取网络文件比本地文件要耗时的多。</p><h3 id="AMD">AMD</h3><p><a href="https://github.com/amdjs/amdjs-api/wiki/AMD">AMD</a> 全称异步模块化定义规范（Asynchronous Module Definition），采用异步加载模块的方式，模块的加载不影响后面语句的执行，并且使用 callback 回调函数的方式来运行模块加载完成后的代码。</p><p>(1) 使用方式</p><p>定义一个 myModule 的模块，它依赖 jQuery 模块：</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="title function_">define</span>(<span class="string">&#x27;myModule&#x27;</span>, [<span class="string">&#x27;jQuery&#x27;</span>], <span class="keyword">function</span> (<span class="params">$</span>) &#123;</span><br><span class="line">  <span class="comment">// $ 是 jQuery 的输出模块</span></span><br><span class="line">  $(<span class="string">&#x27;#app&#x27;</span>).<span class="title function_">text</span>(<span class="string">&#x27;Hello World&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>第一个参数表示模块 id，为可选参数，第二个参数表示模块依赖，也是可选参数。</p><p>使用 myModule 模块：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>([<span class="string">&#x27;myModule&#x27;</span>, <span class="keyword">function</span> (<span class="params">myModule</span>) &#123;&#125;])</span><br></pre></td></tr></table></figure><p><a href="http://requirejs.org/">requirejs</a> 是 AMD 规范的一个实现，详细的使用方法可以查看官方文档。</p><h3 id="CMD">CMD</h3><p>CMD 规范来源于 <a href="https://seajs.github.io/seajs/docs/">seajs</a>，CMD 总体于 AMD 使用起来非常接近，AMD 与 CMD 的区别，可以查看 与 RequireJS 的异同](<a href="https://github.com/seajs/seajs/issues/277">https://github.com/seajs/seajs/issues/277</a>)</p><p>(1) 使用方式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CMD</span></span><br><span class="line"><span class="title function_">define</span>(<span class="keyword">function</span>(<span class="params"><span class="built_in">require</span>, <span class="built_in">exports</span>, <span class="variable language_">module</span></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="built_in">require</span>(<span class="string">&#x27;./a&#x27;</span>)</span><br><span class="line">  a.<span class="title function_">doSomething</span>()</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">var</span> b = <span class="built_in">require</span>(<span class="string">&#x27;./b&#x27;</span>)</span><br><span class="line">  <span class="comment">// 依赖可以就近书写</span></span><br><span class="line">  b.<span class="title function_">doSomething</span>()</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>CMD 推崇依赖就近，可以把依赖写进你的代码中的任意一行，AMD 是依赖前置的，在解析和执行当前模块之前，模块必须指明当前模块所依赖的模块。</p><h3 id="UMD">UMD</h3><p>UMD（Universal Module Definition）并不是一种规范，而是结合 AMD 和 CommonJS 的一种更为通用的 JS 模块解决方案。</p><p>在打包模块的时候经常会见到这样的写法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">output</span>: &#123;</span><br><span class="line">  <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;../dist&#x27;</span>),</span><br><span class="line">  <span class="attr">filename</span>: <span class="string">&#x27;vue.js&#x27;</span>,</span><br><span class="line">  <span class="attr">library</span>: <span class="string">&#x27;Vue&#x27;</span>,</span><br><span class="line">  <span class="attr">libraryTarget</span>: <span class="string">&#x27;umd&#x27;</span></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>表示打包出来的模块为 umd 模块，既能在服务端（node）运行，又能在浏览器端运行。我们来看 vue 打包后的源码 <a href="https://github.com/vuejs/vue/blob/master/dist/vue.js">vue.js</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span> (<span class="params"><span class="variable language_">global</span>, factory</span>) &#123;</span><br><span class="line">  <span class="keyword">typeof</span> <span class="built_in">exports</span> === <span class="string">&#x27;object&#x27;</span> &amp;&amp; <span class="keyword">typeof</span> <span class="variable language_">module</span> !== <span class="string">&#x27;undefined&#x27;</span> ? <span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">factory</span>() :</span><br><span class="line">  <span class="keyword">typeof</span> define === <span class="string">&#x27;function&#x27;</span> &amp;&amp; define.<span class="property">amd</span> ? <span class="title function_">define</span>(factory) : </span><br><span class="line">  (<span class="variable language_">global</span>.<span class="property">Vue</span> = <span class="title function_">factory</span>());</span><br><span class="line">&#125;(<span class="variable language_">this</span>, (<span class="keyword">function</span> (<span class="params"></span>) &#123; <span class="string">&#x27;use strict&#x27;</span>;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;)))</span><br></pre></td></tr></table></figure><p>代码翻译过来就是：</p><ol><li>首先判断是否为 node 环境：exports 为一个对象，并且 module 存在。</li><li>如果是 node 环境就用 <code>module.exports = factory()</code> 把 vue 导出 （通过 require(‘vue’) 进行引用）。</li><li>如果不是 node 环境判断是否支持 AMD：define 为 function 并且 define.amd 存在。</li><li>如果支持 AMD 就使用 define 定义模块，（通过 require([‘vue’]) 引用）。</li><li>否则的话直接将 vue 绑定在全局变量上（通过 window.vue 引用）。</li></ol><h3 id="ES6">ES6</h3><p>终于到了 ES6 的时代，JS 开始从语言层面支持模块化，从 node8.5 版本开始支持原生 ES 模块。不过有两点限制：</p><ol><li>模块名（文件名）必须为 mjs</li><li>启动参数要加上 <code>--experimental-modules</code></li></ol><p>假如有 a.mjs 如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;Jack&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 b.mjs 中可以引用：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> a <span class="keyword">from</span> <span class="string">&#x27;./a.mjs&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// &#123; name: &#x27;Jack&#x27; &#125;</span></span><br></pre></td></tr></table></figure><p>chrome61 开始也支持 JS module，只需要在 script 属性中添加 <code>type=&quot;module&quot;</code> 即可。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span> <span class="attr">src</span>=<span class="string">&quot;module.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">import</span> &#123; sayHello &#125; <span class="keyword">from</span> <span class="string">&#x27;./main.js&#x27;</span></span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">sayHello</span>()</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">// main.js</span><br><span class="line">export function sayHello () &#123;</span><br><span class="line">  console.info(&#x27;Hello World&#x27;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ES6-module-详解">ES6 module 详解</h3><p>ES6 module 主要由两个命令组成：export 和 import。</p><p>(1) export 命令</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 输出变量</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">let</span> num = <span class="number">123</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> name = <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出一组变量</span></span><br><span class="line"><span class="keyword">let</span> num = <span class="number">123</span></span><br><span class="line"><span class="keyword">let</span> name = <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line"><span class="keyword">export</span> &#123; num, name &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params">x, y</span>) &#123; <span class="keyword">return</span> x ** y &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用别名</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">a</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">b</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">export</span> &#123;</span><br><span class="line">  a <span class="keyword">as</span> name,</span><br><span class="line">  b <span class="keyword">as</span> value</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 引用的时候按照别名引用</span></span><br><span class="line"><span class="keyword">import</span> &#123; name, value &#125; <span class="keyword">from</span> <span class="string">&#x27;..&#x27;</span></span><br></pre></td></tr></table></figure><p>需要注意的是，export 命令只能对外输出接口，以下的输出方式均为错误的：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">export</span> <span class="number">1</span></span><br><span class="line"><span class="keyword">var</span> m = <span class="number">1</span></span><br><span class="line"><span class="keyword">export</span> m</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">export</span> f</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确的写法</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> m = <span class="number">1</span></span><br><span class="line"><span class="keyword">var</span> m = <span class="number">1</span></span><br><span class="line"><span class="keyword">export</span> &#123; m &#125;</span><br><span class="line"><span class="keyword">export</span> &#123; m <span class="keyword">as</span> n&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">f</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">export</span> &#123; f &#125;</span><br></pre></td></tr></table></figure><p>export 输出的值是动态绑定的，这点与 CommonJS 不同，CommonJS 输出的是值的缓存，不存在动态更新。</p><p>如何删除 node 缓存？</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> config</span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">delete</span> <span class="built_in">require</span>.<span class="property">cache</span>[<span class="built_in">require</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;./config&#x27;</span>)]</span><br><span class="line">  config = <span class="built_in">require</span>(<span class="string">&#x27;./config&#x27;</span>)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(config)</span><br><span class="line">&#125;, <span class="number">3000</span>)</span><br></pre></td></tr></table></figure><p>export 命令必须处于模块顶层，如果处于块级作用域内，就会报错。</p><p>(2) import 命令</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// import 的变量是只读的</span></span><br><span class="line"><span class="keyword">import</span> &#123; a &#125; <span class="keyword">from</span> <span class="string">&#x27;./a.js&#x27;</span></span><br><span class="line">a = <span class="number">33</span> <span class="comment">// Syntax Error : &#x27;a&#x27; is read-only</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 但是可以修改其属性值</span></span><br><span class="line">a.<span class="property">name</span> = <span class="string">&#x27;Jack&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// import 命令可以提升变量</span></span><br><span class="line"><span class="title function_">foo</span>()</span><br><span class="line"><span class="keyword">import</span> &#123; foo &#125; <span class="keyword">from</span> <span class="string">&#x27;./a.js&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// import 是静态执行，不能使用表达式和变量</span></span><br><span class="line"><span class="keyword">import</span> &#123; a + b &#125; <span class="keyword">from</span> <span class="string">&#x27;./a.ls&#x27;</span> <span class="comment">// 报错</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable language_">module</span> = <span class="string">&#x27;my_module&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; foo &#125; <span class="keyword">from</span> <span class="variable language_">module</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">if</span> (x === <span class="number">1</span>) &#123;</span><br><span class="line">  <span class="keyword">import</span> &#123; foo &#125; <span class="keyword">from</span> <span class="string">&#x27;module1&#x27;</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="keyword">import</span> &#123; foo &#125; <span class="keyword">from</span> <span class="string">&#x27;module2&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载整个模块</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> utils <span class="keyword">from</span> <span class="string">&#x27;./utils.js&#x27;</span></span><br></pre></td></tr></table></figure><p>(3) export default</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// a.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello World&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 引用 a</span></span><br><span class="line"><span class="keyword">import</span> a <span class="keyword">from</span> <span class="string">&#x27;a.js&#x27;</span></span><br><span class="line"><span class="title function_">a</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment">// b.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> funtion <span class="title function_">foo</span> () &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello World&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 引用 b</span></span><br><span class="line"><span class="keyword">import</span> foo <span class="keyword">from</span> <span class="string">&#x27;b.js&#x27;</span></span><br><span class="line"><span class="title function_">foo</span>()</span><br></pre></td></tr></table></figure><p>export default 与 export 输出的模块在引用的时候，差别仅仅是是否用 <code>&#123;&#125;</code> 将变量包起来。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span> (<span class="params">x, y</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> x + y</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> &#123; add <span class="keyword">as</span> <span class="keyword">default</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 引用</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="keyword">default</span> <span class="keyword">as</span> foo &#125; <span class="keyword">from</span> <span class="string">&#x27;a.js&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> a = <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> a</span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">var</span> a = <span class="number">1</span></span><br></pre></td></tr></table></figure><p>(4) export 与 import 复合写法</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="keyword">export</span> &#123; foo, bar &#125; <span class="keyword">from</span> <span class="string">&#x27;a.js&#x27;</span></span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="keyword">import</span> &#123; foo, bar &#125; <span class="keyword">from</span> <span class="string">&#x27;a.js&#x27;</span></span><br><span class="line"><span class="keyword">export</span> &#123; foo, bar &#125;</span><br></pre></td></tr></table></figure><h2 id="参考资料">参考资料</h2><ul><li><a href="http://www.cnblogs.com/imwtr/p/4666181.html">关于 CommonJS AMD CMD UMD 规范的差异总结</a></li><li><a href="http://es6.ruanyifeng.com/#docs/module">Module 的语法</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;模块化解决什么问题&quot;&gt;模块化解决什么问题&lt;/h2&gt;
&lt;p&gt;随着 JavaScript 工程越来越大，团队协作不可避免，为了更好地对代码进行管理和测试，模块化的概念逐渐引入前端。模块化可以降低协同开发的成本，减少代码量，同时也是“高内聚，低耦合”的基础。&lt;/p&gt;
&lt;p&gt;模块化主要解决两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;命名冲突&lt;/li&gt;
&lt;li&gt;文件依赖：比如 bootstrap 需要引入 jquery，jquery 文件的位置必须要 bootstrap.js 之前引入。&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="ES6" scheme="https://lz5z.com/tags/ES6/"/>
    
    <category term="CommonJS" scheme="https://lz5z.com/tags/CommonJS/"/>
    
    <category term="AMD" scheme="https://lz5z.com/tags/AMD/"/>
    
    <category term="CMD" scheme="https://lz5z.com/tags/CMD/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 垃圾回收</title>
    <link href="https://lz5z.com/JavaScript%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/"/>
    <id>https://lz5z.com/JavaScript%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/</id>
    <published>2018-04-25T10:01:21.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<h2 id="垃圾回收">垃圾回收</h2><p>JavaScript 具有自动垃圾回收机制，这种垃圾回收机制原理其实很简单：找出那些不再继续使用的变量，然后释放其所占用的内存，垃圾回收器会按照固定的时间间隔周期性地执行这一操作。局部变量只有在函数执行的过程中存在，在这个过程中，会为局部变量在栈（或者堆）内存上分配空间，然后在函数中使用这些变量，直至函数执行结束。垃圾回收器必须追踪哪个变量有用哪个没用，对于不再有用的变量打上标记，以备将来回收其占用的内存，用于标识无用变量的策略主要有标记清除法和引用计数法。</p><span id="more"></span><h3 id="JavaScript-内存分配">JavaScript 内存分配</h3><p>JavaScript 在定义变量时就完成了内存分配，还可以通过函数调用分配内存：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 值的初始化</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> s = <span class="string">&quot;azerty&quot;</span> <span class="comment">// 给字符串分配内存</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> o = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="literal">null</span></span><br><span class="line">&#125; <span class="comment">// 给对象及其包含的值分配内存</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 给数组及其包含的值分配内存（就像对象一样）</span></span><br><span class="line"><span class="keyword">var</span> a = [<span class="number">1</span>, <span class="literal">null</span>, <span class="string">&quot;abra&quot;</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">a</span>)&#123;</span><br><span class="line">  <span class="keyword">return</span> a + <span class="number">2</span></span><br><span class="line">&#125; <span class="comment">// 给函数（可调用的对象）分配内存</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数表达式也能分配一个对象</span></span><br><span class="line">someElement.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  someElement.<span class="property">style</span>.<span class="property">backgroundColor</span> = <span class="string">&#x27;blue&#x27;</span></span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 函数调用分配内存</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> d = <span class="keyword">new</span> <span class="title class_">Date</span>() <span class="comment">// 分配一个 Date 对象</span></span><br><span class="line"><span class="keyword">var</span> e = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>) <span class="comment">// 分配一个 DOM 元素</span></span><br></pre></td></tr></table></figure><p>使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值，甚至传递函数的参数。</p><h3 id="mark-and-sweep">mark and sweep</h3><p>JavaScript 中最常用的垃圾回收方式就是标记清除（mark-and-sweep），当变量进入环境时，就将这个变量标记“进入环境”，当变量离开环境时，就将其标记为“离开环境”。至于怎么标记有很多种方式，比如特殊位的反转、维护一个列表等。</p><img src="/assets/img/gc_mark_sweep.gif" alt="gc_mark_sweep"><p>垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记，然后它会去掉环境中的变量已经被环境中变量被标记为引用的变量，在此之后再被标记的变量将被视为准备删除的变量。最后垃圾回收器清除标记的变量，回收它们所占用的内存空间。</p><p>目前主流浏览器都是使用标记清除式的垃圾回收策略，只不过收集的间隔有所不同。</p><h3 id="引用计数（refefence-counting）">引用计数（refefence counting）</h3><p>引用计数跟踪几个每个值被引用的次数，当声明一个引用类型值赋给该变量时，则这个值的引用次数就是 1，如果同一个值被赋给另外一个变量，则该值的引用次数加 1。相反，如果包含对这个值引用的变量又取了另外一个值，则这个值的引用次数减 1。当这个值的引用次数变成 0 时，就可以将其内存空间回收。当垃圾回收器再次运行时，它就会释放哪些引用次数为 0 的值所占用的内存。</p><p>Netscape Navigator 3.0 是最早使用引用计数策略的浏览器，它很快就遇到一个严重的问题：循环引用。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">problem</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> obj1 = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line">  <span class="keyword">var</span> obj2 = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line"></span><br><span class="line">  obj1.<span class="property">someOtherObj</span> = obj2</span><br><span class="line">  obj2.<span class="property">anotherObj</span> = obj1    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这个例子中，obj1 和 obj2 通过各自的属性相互引用，也就是说，这两个对象的引用次数都是 2。在采用标记清除策略的现实中，由于函数执行后，两个对象都离开了作用域，因此相互引用不存在问题。</p><p>但是在引用计数策略中，当函数执行完毕后，obj1 和 obj2 还得继续存在，因为它们的引用次数永远不会是 0，导致内存无法回收。</p><p>Netscape Navigator 4.0 中放弃了引用计数，转而使用标记清除来实现垃圾回收。</p><p>IE 存在的问题：</p><p>在 IE9 之前，IE 中有一部分对象并不是原生 JavaScript 对象。例如，BOM 和 DOM 中的对象就是 C++ 实现的 COM 对象，而 COM 对象的垃圾收集机制采用的是引用计数策略。因此，即使 IE 中的 JavaScript 引擎使用标记清除策略实现，但是 JS 访问的 COM 对象依然是基于引用计数策略的。可以在 IE 中涉及到 COM 对象，就会存在循环引用的问题。</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="keyword">var</span> ele = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;some_element&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> obj = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line">obj.<span class="property">ele</span> = ele</span><br><span class="line">ele.<span class="property">someObj</span> = obj</span><br></pre></td></tr></table></figure><p>在这个例子中一个 DOM 元素与一个原生 JS 对象之间创建了循环引用，由于 COM 的引用计数的垃圾回收策略，导致例子中的 DOM 从页面删除，也不会被垃圾回收。</p><p>解决办法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">obj.<span class="property">ele</span> = <span class="literal">null</span></span><br><span class="line">ele.<span class="property">someObj</span> = <span class="literal">null</span></span><br></pre></td></tr></table></figure><p>将变量设置为 null 意味着切断变量和它此前引用值之间的连接。当垃圾回收器下次运行时，就能删除这些值并回收它们占用的内存。</p><p>IE9 之后，DOM 和 BOM 对象都被转换成立真正的 JS 对象，这样就避免了两种垃圾回收算法并存导致的问题。</p><h3 id="性能问题">性能问题</h3><p>垃圾收集器是周期性运行，因此其运行时间间隔是一个非常重要的问题。IE7 之前的垃圾收集器是根据内存分配量运行的，达到某一个临界值（256 个变量，4096 个对象、或者 64 KB 字符串）就是启动垃圾回收器，这导致了一个问题：如果该脚本在其生命周期需要一直保持这么多变量，垃圾回收器就不得不频繁运行。</p><p>事实上，浏览器中一般可以主动触发垃圾收集过程。在 IE 中，调用 <code>window.CollectGarbage()</code> 方法会立即执行垃圾收集，在 Opera7 之后的版本中，调用 <code>window.opera.collect()</code> 也会启动垃圾收集。</p><h3 id="优化内存">优化内存</h3><p>比较好的办法就是执行代码中只保留必要的数据，一旦数据不再有用，通过设置为 null 来释放其引用（dereferencing），适用于大多数全局变量和全局对象的属性。</p><h2 id="V8-内存机制">V8 内存机制</h2><p>V8 引擎会限制 JavaScript 所能使用的内存大小，64 位系统是 1.4GB，32 位系统是 0.7GB。在 Node 环境中使用下面两个参数可以调整启动时内存限制的大小：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">node --max-nex-space-size=1024 app.js // 单位为KB</span><br><span class="line">node --max-old-space-size=2000 app.js // 单位为MB</span><br></pre></td></tr></table></figure><p>这两条命令分别对应 Node 内存堆中的「新生代」和「老生代」</p><h3 id="V8-的堆构成">V8 的堆构成</h3><p>V8 将堆分为了几个不同的区域：</p><ul><li>新生区：大多数对象被分配在这里。新生区是一个很小的区域，垃圾回收在这个区域非常频繁，与其他区域相独立。</li><li>老生指针区：这里包含大多数可能存在指向其他对象的指针的对象。大多数在新生区存活一段时间之后的对象都会被挪到这里。<br>老生数据区：这里存放只包含原始数据的对象（这些对象没有指向其他对象的指针）。字符串、封箱的数字以及未封箱的双精度数字数组，在新生区存活一段时间后会被移动到这里。</li><li>大对象区：这里存放体积超越其他区大小的对象。每个对象有自己 mmap 产生的内存。垃圾回收器从不移动大对象。<br>-代码区：代码对象，也就是包含 JIT 之后指令的对象，会被分配到这里。这是唯一拥有执行权限的内存区（不过如果代码对象因过大而放在大对象区，则该大对象所对应的内存也是可执行的。译注：但是大对象内存区本身不是可执行的内存区）。<br>-Cell 区、属性 Cell 区、Map 区：这些区域存放 Cell、属性 Cell 和 Map，每个区域因为都是存放相同大小的元素，因此内存结构很简单。</li></ul><h3 id="分代回收">分代回收</h3><p>脚本中，绝大多数对象的生存期很短，只有某些对象的生存期较长。为利用这一特点，V8将堆进行了分代。对象起初会被分配在新生区（通常很小，只有 1-8 MB，具体根据行为来进行启发）。在新生区的内存分配非常容易：我们只需保有一个指向内存区的指针，不断根据新对象的大小对其进行递增即可。当该指针达到了新生区的末尾，就会有一次清理（小周期），清理掉新生区中不活跃的死对象。对于活跃超过 2 个小周期的对象，则需将其移动至老生区。老生区在标记－清除或标记－紧缩（大周期）的过程中进行回收。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。至于足够多到底是多少，则根据老生区自身的大小和程序的动向来定。</p><h2 id="参考资料">参考资料</h2><ul><li>《JavaScript 高级程序设计》</li><li><a href="http://newhtml.net/v8-garbage-collection/">V8 之旅： 垃圾回收器</a></li><li><a href="https://segmentfault.com/a/1190000004934938">NodeJS中被忽略的内存</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;垃圾回收&quot;&gt;垃圾回收&lt;/h2&gt;
&lt;p&gt;JavaScript 具有自动垃圾回收机制，这种垃圾回收机制原理其实很简单：找出那些不再继续使用的变量，然后释放其所占用的内存，垃圾回收器会按照固定的时间间隔周期性地执行这一操作。局部变量只有在函数执行的过程中存在，在这个过程中，会为局部变量在栈（或者堆）内存上分配空间，然后在函数中使用这些变量，直至函数执行结束。垃圾回收器必须追踪哪个变量有用哪个没用，对于不再有用的变量打上标记，以备将来回收其占用的内存，用于标识无用变量的策略主要有标记清除法和引用计数法。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="GC" scheme="https://lz5z.com/tags/GC/"/>
    
    <category term="V8" scheme="https://lz5z.com/tags/V8/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 常见的内存泄漏</title>
    <link href="https://lz5z.com/JavaScript-Memory-Leaks/"/>
    <id>https://lz5z.com/JavaScript-Memory-Leaks/</id>
    <published>2018-04-23T14:07:24.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="什么是内存泄漏">什么是内存泄漏</h2><p>JavaScript 是一种垃圾回收语言，垃圾回收语言通过周期性地检查之前被分配的内存是否可以从应用的其它部分访问来帮助开发者管理内存。内存泄露是指当一块内存不再被应用程序使用的时候，由于某种原因，这块内存没有返还给操作系统或者内存池的现象。内存泄漏可能会导致应用程序卡顿或者崩溃。</p><span id="more"></span><h3 id="查看内存泄漏">查看内存泄漏</h3><p>在 chrome 中可以通过 performance 中的 Memory record 来查看，选中 Memory 后点击左边的 Record，然后模拟用户的操作，一段时间后点击 stop，在面板上查看这段时间的内存占用情况。如果内存基本平稳，则无内存泄漏情况；如果内存占用不断飙升，内可能出现内存泄漏的情况。</p><p>在 Node 环境中，可以输入 <code>process.memoryUsage()</code> 查看 Node 进程的内存占用情况。</p><ul><li>rss（resident set size）：进程的常驻内存部分。</li><li>heapTotal：&quot;堆&quot;占用的内存，包括用到的和没用到的。</li><li>heapUsed：用到的堆的部分。</li><li>external： V8 引擎内部的 C++ 对象占用的内存。</li></ul><p>判断内存泄漏，以 heapUsed 字段为准。</p><h2 id="常见的内存泄漏">常见的内存泄漏</h2><p>《JavaScript高级程序设计》中提到了一种内存泄漏：由于 IE9 之前的版本对 JS 对象和 DOM 对象中使用的垃圾回收机制，会导致如果闭包的作用域链中保存着一个 HTML 元素，那该元素将无法销毁。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">assignHandler</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> element = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;someElement&#x27;</span>)</span><br><span class="line">  element.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(element.<span class="property">id</span>)</span><br><span class="line">  &#125;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上代码创建了一个作为 element 元素事件处理程序的闭包，而这个闭包则又创建了一个循环引用，匿名函数中保存了一个对 element 对象的引用，因此无法减少 element 的引用数。只要匿名函数在，element 的引用数至少是 1，因此它所占用的内存就永远无法回收。</p><p>解决办法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">assignHandler</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> element = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;someElement&#x27;</span>)</span><br><span class="line">  <span class="keyword">var</span> id = element.<span class="property">id</span></span><br><span class="line">  element.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(id)</span><br><span class="line">  &#125;</span><br><span class="line">  element = <span class="literal">null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注意: 上述问题在现代浏览器上并不会出现</p></blockquote><h3 id="意外的全局变量">意外的全局变量</h3><p>在 JavaScript 非<a href="https://lz5z.com/JavaScript%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/">严格模式</a>中，未定义的变量会自动绑定在全局对象上（window/global），比如：</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="keyword">function</span> <span class="title function_">foo</span> (<span class="params"></span>) &#123;</span><br><span class="line">  bar = <span class="string">&#x27;something&#x27;</span>    </span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">foo</span>()</span><br></pre></td></tr></table></figure><p>foo 执行的时候，由于内部变量没有定义，所以相当于 <code>window.bar = 'something'</code>，函数执行完毕，本应该被销毁的变量 bar 却永久的保留在内存中了。</p><p>解决办法，使用<a href="https://lz5z.com/JavaScript%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/">严格模式</a>。</p><p>虽然全局变量上绑定的变量无法被垃圾回收，但是有时需要使用全局变量去存储临时信息，这个时候要格外小心，并在变量使用完毕后设置为 null，以回收内存。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">bar</span> = <span class="literal">null</span></span><br><span class="line"><span class="keyword">delete</span> <span class="variable language_">window</span>.<span class="property">bar</span></span><br></pre></td></tr></table></figure><p>下面写一个 demo：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>())</span><br><span class="line">    <span class="variable language_">window</span>[<span class="string">`<span class="subst">$&#123;<span class="keyword">new</span> <span class="built_in">Date</span>().getTime()&#125;</span>`</span>] = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000000</span>).<span class="title function_">join</span>(<span class="string">&#x27;x&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">grow</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">test</span>()</span><br><span class="line">  <span class="built_in">setTimeout</span>(grow, <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">onload</span> = <span class="title function_">grow</span>()</span><br></pre></td></tr></table></figure><p>将这段脚本放置于浏览器中，打开 chrome performance，记录一段时间后，发现内存线条如下：</p><img src="/assets/img/js-memory-leak.png" alt="js-memory-leak"><p>同时打开 chrome 任务管理器，会看到代表当前页面的标签页所占用的内存不断飙升。</p><h3 id="JS-错误引用-DOM-元素">JS 错误引用 DOM 元素</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> nodes = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> item = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000000</span>).<span class="title function_">join</span>(<span class="string">&#x27;x&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  nodes = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;nodes&#x27;</span>)</span><br><span class="line">  nodes.<span class="property">item</span> = item</span><br><span class="line">  nodes.<span class="property">parentElement</span>.<span class="title function_">removeChild</span>(nodes)</span><br><span class="line"> &#125;)()</span><br></pre></td></tr></table></figure><p>这里的 dom 元素虽然已经从页面上移除了，但是 js 中仍然保存这对该 dom 元素的引用，导致内存不能释放。</p><p>打开 chrome 控制台 Memory，点击 <code>Take snapshot</code>：</p><img src="/assets/img/js-memory-leak-profile.png" alt="js-memory-leak-profile"><p>点击生成的 Snapshot，通过关键字 <code>str</code> 进行 filter：</p><img src="/assets/img/js-memory-leak-snapshot.png" alt="js-memory-leak-snapshot"><p>从上图可知，代码运行结束后，内存中的长字符串依然没有被垃圾回收。</p><h3 id="闭包循环引用">闭包循环引用</h3><p><a href="https://lz5z.com/JavaScript%E9%97%AD%E5%8C%85/">闭包</a>是指函数能够访问父环境中定义的变量。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> bar = <span class="literal">null</span></span><br><span class="line">  <span class="keyword">var</span> foo = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> originalBar = bar</span><br><span class="line">    <span class="keyword">var</span> unused = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> originalBar</span><br><span class="line">    &#125;</span><br><span class="line">    bar = &#123;</span><br><span class="line">      <span class="attr">longStr</span>: <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1000000</span>).<span class="title function_">join</span>(<span class="string">&#x27;x&#x27;</span>),</span><br><span class="line">      <span class="title function_">someMethod</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>())</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">setInterval</span>(foo, <span class="number">100</span>)</span><br><span class="line">&#125;)()</span><br></pre></td></tr></table></figure><p>上面代码中的 unused 是一个闭包，因为其内部引用了父环境中的变量 originalBar，虽然它被没有使用，但 v8 引擎并不会把它优化掉，因为 JavaScript 里存在 eval 函数，所以 v8 引擎并不会随便优化掉暂时没有使用的函数。</p><p>需要注意的一点是： <strong>闭包的作用域一旦创建，它们有同样的父级作用域，作用域是共享的</strong>。</p><p>bar 引用了someMethod，someMethod 这个函数与 unused 这个闭包共享一个闭包上下文。所以 someMethod 也引用了 originalBar 这个变量。</p><p>因此引用链如下：</p><p>GCHandler -&gt; foo -&gt; bar -&gt; someMethod -&gt; originalBar -&gt; someMethod(old) -&gt; originalBar(older)-&gt; someMethod(older)</p><p>造成了闭包的循环引用。</p><img src="/assets/img/js-memory-leak-clouser.png" alt="js-memory-leak-clouser"><h2 id="参考资料">参考资料</h2><ul><li><a href="https://segmentfault.com/a/1190000008901861">javascript典型内存泄漏及chrome的排查方法</a></li><li>《JavaScript高级程序设计》</li><li><a href="https://github.com/wengjq/Blog/issues/1">4种JavaScript内存泄漏浅析及如何用谷歌工具查内存泄露</a></li><li><a href="https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/">4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;什么是内存泄漏&quot;&gt;什么是内存泄漏&lt;/h2&gt;
&lt;p&gt;JavaScript 是一种垃圾回收语言，垃圾回收语言通过周期性地检查之前被分配的内存是否可以从应用的其它部分访问来帮助开发者管理内存。内存泄露是指当一块内存不再被应用程序使用的时候，由于某种原因，这块内存没有返还给操作系统或者内存池的现象。内存泄漏可能会导致应用程序卡顿或者崩溃。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="内存泄漏" scheme="https://lz5z.com/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/"/>
    
    <category term="Memory Leaks" scheme="https://lz5z.com/tags/Memory-Leaks/"/>
    
  </entry>
  
  <entry>
    <title>Vim 学习</title>
    <link href="https://lz5z.com/vim-study/"/>
    <id>https://lz5z.com/vim-study/</id>
    <published>2018-04-19T05:09:32.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<p>本文的内容来自 vimtutor(v1.7)，在 Unix 系统下输入 “vimtutor” 即可进入教学模型。这里记录下来学习点滴，方便以后查看。</p><h2 id="Vim-简介">Vim 简介</h2><p>Vim 是一款由 Vi 派生出来的命令行编辑器，具有语法高亮、代码折叠、多语言支持、多视图等强大的功能，并且支持插件扩展和调用脚本语言。Vim 有多种模式，其中最常用的为插入和执行模式，仅仅通过键盘来在这些模式之中切换，大大提高了程序开发效率。</p><h2 id="Vim-使用">Vim 使用</h2><h3 id="移动光标">移动光标</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">要移动光标使用 h、j、k、l 键</span><br><span class="line">     ^</span><br><span class="line">     k        </span><br><span class="line">&lt; h       l &gt;</span><br><span class="line">     j</span><br><span class="line">     v</span><br></pre></td></tr></table></figure><span id="more"></span><h3 id="Vim-进入和退出">Vim 进入和退出</h3><p>通过 Vim + 文件名进去文件后，默认为普通模式。注意进入普通模式后请勿开启 Shift-Lock(大小写锁定键)。</p><p>退出 Vim，按 <ESC> 键，然后输入 <code>:q!</code> &lt;回车&gt;。 这种方式退出编辑器会丢弃进入编辑器以来所做的改动。</p><h3 id="文本编辑-删除">文本编辑-删除</h3><p>在普通模式下，按 x 键来删除光标所在位置的字符。</p><h3 id="文本编辑-插入">文本编辑-插入</h3><p>在普通模式下，按 i 键来插入文本。</p><h3 id="文本编辑-添加">文本编辑-添加</h3><p>按 a 键来添加文本。</p><p>插入与添加直接的区别：</p><p>插入是在光标前插入文本，添加光标字母后面添加。</p><h3 id="编辑文件">编辑文件</h3><p>使用 <code>:wq</code> 以保存文件并退出</p><h3 id="删除类命令">删除类命令</h3><p>输入 <code>dw</code> 可以从光标处删除至一个单词的末。</p><p>输入 <code>d$</code> 从当前光标删除到行末。</p><p>输入 <code>de</code> 从当前光标当前位置直到单词末尾，包括最后一个字符。</p><p>输入 <code>dd</code> 删除整行。</p><p>输入 <code>2dd</code> 删除两行。</p><h3 id="移动光标-2">移动光标</h3><p>输入 <code>2w</code> 使光标向后移动两个单词。</p><p>输入 <code>3e</code> 使光标向后移动到第三个单词的末尾。</p><p>比如之前的光标位置为：</p><p>—&gt; |this is a demo.</p><p>输入 <code>2w</code>:</p><p>—&gt; this is |a demo.</p><p>输入 <code>2e</code>:</p><p>—&gt; this i|s a demo.</p><h3 id="计数删除">计数删除</h3><p>—&gt; |this is a demo</p><p><code>d2w</code>: —&gt; |a demo.</p><p><code>d2e</code>: —&gt; | a demo.</p><h3 id="撤销">撤销</h3><p>输入 <code>u</code> 来撤消最后执行的命令。</p><p>输入 <code>U</code> 来撤消对整行的修改。</p><p>使用 <code>CTRL-R</code> （先按 CTRL 再按 R）撤销撤销命令。</p><h2 id="删除与粘贴">删除与粘贴</h2><p>删除操作后，输入 <code>p</code> 将最后一次删除的内容置入光标之后。</p><h2 id="替换">替换</h2><p>输入 <code>r</code> 加字符替换光标后一个字符。</p><h2 id="更改">更改</h2><p>要改变文本直到一个单词的末尾，请输入 <code>ce</code>。</p><p><code>ce</code> 命令相当于删除一个单词的同时，进入插入模式。</p><p>使用 <code>c2w</code> 删除两个单词并且进入插入模式。</p><p>使用 <code>c$</code> 删除光标后所有内容并且进入插入模式。</p><h3 id="文件定位">文件定位</h3><p>输入 <code>CTRL-G</code> 显示当前编辑文件中当前光标所在行位置以及文件状态信息。</p><p>输入行号 + G (注意是大写) 可以直接将光标定位于行数。</p><h3 id="文件搜索">文件搜索</h3><p>输入 <code>/</code> 加上字符串，可以在当前文件中查找该字符串。</p><p>要查找同上一次的字符串，只需要按 <code>n</code> 键。要向相反方向查找同上一次的字符串，请输入大写 <code>N</code> 即可。</p><p>回到之前的位置按 <code>CTRL-O</code>，重复按可以回退更多步。CTRL-I 会跳转到较新的位置。</p><p>提示：如果查找已经到达文件末尾，查找会自动从文件头部继续查找，除非 ‘wrapscan’ 选项被复位。</p><h3 id="配对括号的查找">配对括号的查找</h3><p>把光标置于有括号（ (、[ 或 { ）的地方，按下 <code>%</code> 光标会自动定位到与其配对的括号处。</p><h3 id="替换命令">替换命令</h3><p>在一行内替换头一个字符串 old 为新的字符串 new，输入 <code>:s/old/new</code>。</p><p>在一行内替换所有的字符串 old 为新的字符串 new，输入 <code>:s/old/new/g</code>。</p><p>在两行内替换所有的字符串 old 为新的字符串 new，输入 <code>:#,#s/old/new/g</code>，其中 #, # 代表的是替换操作的若干行中首尾两行的行号。</p><p>在文件内替换所有的字符串 old 为新的字符串 new，输入 <code>:%s/old/new/g</code></p><p>进行全文替换时询问用户确认每个替换需添加 c 标志 <code>:%s/old/new/gc</code></p><h3 id="Vim-中执行外部命令">Vim 中执行外部命令</h3><p>输入 <code>:!</code> 然后紧接着输入一个外部命令可以执行该外部命令，比如 <code>:!ls</code> 可以在 Vim 中查看当前目录。</p><h3 id="文件保存">文件保存</h3><p>要将对文件的改动保存到文件中，请输入 <code>:w FILENAME</code>。 该命令会以 FILENAME 为文件名保存整个文件。</p><h3 id="选择性保存">选择性保存</h3><p>移动光标至某一行，按下 <code>v</code> 键进入可视模式，移动光标选中内容，然后按 <code>:</code>，屏幕底部会出现 <code>:'&lt;,'&gt;</code>，再输入 <code>w FILENAME</code> 可将选中的内容报错到 FILENAME 中。</p><p>提示：按 v 键使 Vim 进入可视模式进行选取。可以四处移动光标使选取区域变大或变小。接着可以使用一个操作符对选中文本进行操作。例如，按 d 键会删除选中的文本内容。</p><h3 id="提取和合并文件">提取和合并文件</h3><p>要向当前文件中插入另外的文件的内容，请输入 <code>:r FILENAME</code>。</p><p><code>:r FILENAME</code> 可提取磁盘文件 FILENAME 并将其插入到当前文件的光标位置后面。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文的内容来自 vimtutor(v1.7)，在 Unix 系统下输入 “vimtutor” 即可进入教学模型。这里记录下来学习点滴，方便以后查看。&lt;/p&gt;
&lt;h2 id=&quot;Vim-简介&quot;&gt;Vim 简介&lt;/h2&gt;
&lt;p&gt;Vim 是一款由 Vi 派生出来的命令行编辑器，具有语法高亮、代码折叠、多语言支持、多视图等强大的功能，并且支持插件扩展和调用脚本语言。Vim 有多种模式，其中最常用的为插入和执行模式，仅仅通过键盘来在这些模式之中切换，大大提高了程序开发效率。&lt;/p&gt;
&lt;h2 id=&quot;Vim-使用&quot;&gt;Vim 使用&lt;/h2&gt;
&lt;h3 id=&quot;移动光标&quot;&gt;移动光标&lt;/h3&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;要移动光标使用 h、j、k、l 键&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     ^&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     k        &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt; h       l &amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     j&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     v&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="Tools" scheme="https://lz5z.com/categories/Tools/"/>
    
    
    <category term="Vim" scheme="https://lz5z.com/tags/Vim/"/>
    
    <category term="vimtutor" scheme="https://lz5z.com/tags/vimtutor/"/>
    
  </entry>
  
  <entry>
    <title>使用 requestAnimationFrame 实现动画</title>
    <link href="https://lz5z.com/requestAnimationFrame/"/>
    <id>https://lz5z.com/requestAnimationFrame/</id>
    <published>2018-04-14T10:24:28.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<h2 id="如何实现一个动画">如何实现一个动画</h2><p>我们来实现一个最简单的需求，将一个元素从屏幕左边均匀地移动到屏幕右边。</p><p>下面是效果:</p><!DOCTYPE html><html lang="en"><head>  <style>    .animate-warpper {      width: 100%;      height: 70px;    }    @keyframes move_animation {      0% { left: 0px; }      100% { left: calc(100% - 60px); }    }    .animate-div {      width: 60px;      height: 40px;      position: absolute;      left: 0;      border-radius: 5px;      background: #92B901;      transform: translateZ(0);      -webkit-transform: translateZ(0);      animation: move_animation 5s linear 2s infinite alternate;    }  </style></head><body>  <section class="animate-warpper">     <div class="animate-div"></div>  </section></body></html><span id="more"></span><p>（1）css animation</p><p>用 css 实现是最合理也是最高效的。</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@keyframes</span> move_animation1 &#123;</span><br><span class="line">  <span class="number">0%</span> &#123; <span class="attribute">left</span>: <span class="number">0px</span>; &#125;</span><br><span class="line">  <span class="number">100%</span> &#123; <span class="attribute">left</span>: <span class="built_in">calc</span>(<span class="number">100%</span> - <span class="number">60px</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">@keyframes</span> move_animation &#123;</span><br><span class="line">  <span class="number">0%</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translateX</span>(<span class="number">0</span>); &#125;</span><br><span class="line">  <span class="number">50%</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translateX</span>(<span class="number">250px</span>); &#125;</span><br><span class="line">  <span class="number">100%</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translateX</span>(<span class="number">500px</span>)); &#125;    </span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.animate-div</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">60px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">40px</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">5px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#92B901</span>;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">  -webkit-<span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="attribute">animation</span>: move_animation <span class="number">5s</span> linear <span class="number">2s</span> infinite alternate;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注：<code>transform:translateZ(0);</code> 用来开启 chrome GPU 加速，解决动画”卡顿”。<br>在动画中使用 transform 比 left/top 性能更好，能减少浏览器 repaint。</p></blockquote><p>（2）假如用 JS 实现呢</p><p>首先想到的是 setInterval/setTimeout，原理就是利用人眼的视觉残留和电脑屏幕的刷新，让元素以连贯平滑的方式逐步改变位置，最终实现动画的效果。</p><p>常用的屏幕刷新频率为 60Hz，一些电竞屏幕则为 144Hz。我们以常用的刷新频率为例，60Hz 意味着屏幕每 1000 / 60 ≈ 16.7ms 刷新一次，所以我们设置 setInterval 的间隔为 16.7ms：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> animateDiv = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.animate-div&#x27;</span>)</span><br><span class="line"><span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line"><span class="keyword">let</span> inter = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  animateDiv.<span class="property">style</span>.<span class="property">left</span> = <span class="number">1</span>/<span class="number">3</span> * (++i) + <span class="string">&#x27;%&#x27;</span></span><br><span class="line">  <span class="keyword">if</span> (i === <span class="number">300</span>) <span class="built_in">clearInterval</span>(inter)</span><br><span class="line">&#125;, <span class="number">16.7</span>)</span><br></pre></td></tr></table></figure><p>setInterval/setTimeout 存在两个问题：</p><blockquote><ul><li>setTimeout 的执行时间并不是确定的。在 Javascript 中， setTimeout 任务被放进了异步队列中，只有当主线程上的任务执行完以后，才会去检查该队列里的任务是否需要开始执行，因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。</li><li>刷新频率受屏幕分辨率和屏幕尺寸的影响，因此不同设备的屏幕刷新频率可能会不同，而 setTimeout 只能设置一个固定的时间间隔，这个时间不一定和屏幕的刷新时间相同。</li></ul></blockquote><p>以上两种情况都会导致 setTimeout 的执行步调和屏幕的刷新步调不一致，从而引起丢帧现象。 虽然在上述代码中我们将时间间隔设置为 16.7ms，但是还是不能完全避免丢帧的现象。</p><p>（3）requestAnimationFrame</p><p>requestAnimationFrame 与 setTimeout/setInterval 最大的区别是由系统自己的刷新机制来决定什么时候调用动画函数，开发者只需要定义好动画函数，这个函数会在浏览器重绘之前调用。</p><h2 id="requestAnimationFrame-简介">requestAnimationFrame 简介</h2><p>requestAnimationFrame 接收一个回调函数作为参数，<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/DOMHighResTimeStamp">DOMHighResTimeStamp</a>，指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。回调函数中传入时间戳作为参数，该时间戳是一个十进制数，单位毫秒，最小精度为 1ms。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> animateDiv = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.animate-div&#x27;</span>)</span><br><span class="line"><span class="keyword">let</span> start = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 回调函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">step</span>(<span class="params">timestamp</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!start) start = timestamp</span><br><span class="line">    <span class="keyword">let</span> progress = timestamp - start</span><br><span class="line">    animateDiv.<span class="property">style</span>.<span class="property">left</span> = progress + <span class="string">&#x27;px&#x27;</span></span><br><span class="line">    <span class="keyword">if</span> (progress &lt; <span class="number">350</span>) &#123;</span><br><span class="line">        <span class="comment">// 在动画没有结束前，递归渲染</span></span><br><span class="line">        <span class="variable language_">window</span>.<span class="title function_">requestAnimationFrame</span>(step)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第一帧渲染</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">requestAnimationFrame</span>(step)</span><br></pre></td></tr></table></figure><h3 id="requestAnimationFrame-优势">requestAnimationFrame 优势</h3><p>除了精准控制调用时机以外，requestAnimationFrame 还有两大优点：</p><ul><li>运行在后台标签页或者隐藏的 iframe 里时，requestAnimationFrame() 暂停调用以提升性能和电池寿命。</li><li>函数节流：在高频率事件(resize,scroll等)中，为了防止在一个刷新间隔内发生多次函数执行，使用 requestAnimationFrame 可保证每个刷新间隔内，函数只被执行一次。</li></ul><h3 id="cancelAnimationFrame">cancelAnimationFrame</h3><p>取消一个先前通过调用 window.requestAnimationFrame()方法返回的动画帧请求。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> animateDiv = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.animate-div&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> requestAnimationFrame = <span class="variable language_">window</span>.<span class="property">requestAnimationFrame</span> || <span class="variable language_">window</span>.<span class="property">mozRequestAnimationFrame</span> ||</span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">webkitRequestAnimationFrame</span> || <span class="variable language_">window</span>.<span class="property">msRequestAnimationFrame</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> cancelAnimationFrame = <span class="variable language_">window</span>.<span class="property">cancelAnimationFrame</span> || <span class="variable language_">window</span>.<span class="property">mozCancelAnimationFrame</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> start = <span class="literal">null</span></span><br><span class="line"><span class="keyword">let</span> myReq = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">step</span>(<span class="params">timestamp</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> progress = timestamp - start</span><br><span class="line">  animateDiv.<span class="property">style</span>.<span class="property">left</span> = progress + <span class="string">&#x27;px&#x27;</span></span><br><span class="line">  <span class="keyword">if</span> (progress &lt; <span class="number">2000</span>) &#123;</span><br><span class="line">    myReq = <span class="title function_">requestAnimationFrame</span>(step)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">myReq = <span class="title function_">requestAnimationFrame</span>(step)</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">cancelAnimationFrame</span>(myReq)</span><br><span class="line">&#125;, <span class="number">200</span>)</span><br></pre></td></tr></table></figure><h3 id="优雅降级">优雅降级</h3><p>requestAnimationFrame 目前还存在兼容性问题，使用 <a href="https://github.com/darius/requestAnimationFrame">requestAnimationFrame polyfill</a> 来进行优雅降级。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!<span class="title class_">Date</span>.<span class="property">now</span>)</span><br><span class="line">    <span class="title class_">Date</span>.<span class="property">now</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>(); &#125;;</span><br><span class="line"></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="string">&#x27;use strict&#x27;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">let</span> vendors = [<span class="string">&#x27;webkit&#x27;</span>, <span class="string">&#x27;moz&#x27;</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; vendors.<span class="property">length</span> &amp;&amp; !<span class="variable language_">window</span>.<span class="property">requestAnimationFrame</span>; ++i) &#123;</span><br><span class="line">        <span class="keyword">let</span> vp = vendors[i];</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">requestAnimationFrame</span> = <span class="variable language_">window</span>[vp+<span class="string">&#x27;RequestAnimationFrame&#x27;</span>];</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">cancelAnimationFrame</span> = (<span class="variable language_">window</span>[vp+<span class="string">&#x27;CancelAnimationFrame&#x27;</span>]</span><br><span class="line">                                   || <span class="variable language_">window</span>[vp+<span class="string">&#x27;CancelRequestAnimationFrame&#x27;</span>]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="regexp">/iP(ad|hone|od).*OS 6/</span>.<span class="title function_">test</span>(<span class="variable language_">window</span>.<span class="property">navigator</span>.<span class="property">userAgent</span>) <span class="comment">// iOS6 is buggy</span></span><br><span class="line">        || !<span class="variable language_">window</span>.<span class="property">requestAnimationFrame</span> || !<span class="variable language_">window</span>.<span class="property">cancelAnimationFrame</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> lastTime = <span class="number">0</span>;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">requestAnimationFrame</span> = <span class="keyword">function</span>(<span class="params">callback</span>) &#123;</span><br><span class="line">            <span class="keyword">let</span> now = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line">            <span class="keyword">let</span> nextTime = <span class="title class_">Math</span>.<span class="title function_">max</span>(lastTime + <span class="number">16</span>, now);</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="title function_">callback</span>(lastTime = nextTime); &#125;,</span><br><span class="line">                              nextTime - now);</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">cancelAnimationFrame</span> = <span class="built_in">clearTimeout</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;());</span><br></pre></td></tr></table></figure><h2 id="参考资料">参考资料</h2><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame">MDN-CSS requestAnimationFrame</a></li><li><a href="http://mp.weixin.qq.com/s/_m1flYySn6sgAROYbXqltg">深入理解 requestAnimationFrame</a></li><li><a href="https://www.w3cplus.com/css3/introduction-to-hardware-acceleration-css-animations.html">CSS动画之硬件加速</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;如何实现一个动画&quot;&gt;如何实现一个动画&lt;/h2&gt;
&lt;p&gt;我们来实现一个最简单的需求，将一个元素从屏幕左边均匀地移动到屏幕右边。&lt;/p&gt;
&lt;p&gt;下面是效果:&lt;/p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;style&gt;
    .animate-warpper {
      width: 100%;
      height: 70px;
    }
    @keyframes move_animation {
      0% { left: 0px; }
      100% { left: calc(100% - 60px); }
    }
    .animate-div {
      width: 60px;
      height: 40px;
      position: absolute;
      left: 0;
      border-radius: 5px;
      background: #92B901;
      transform: translateZ(0);
      -webkit-transform: translateZ(0);
      animation: move_animation 5s linear 2s infinite alternate;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;section class=&quot;animate-warpper&quot;&gt; 
    &lt;div class=&quot;animate-div&quot;&gt;&lt;/div&gt;
  &lt;/section&gt;
&lt;/body&gt;
&lt;/html&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="requestAnimationFrame" scheme="https://lz5z.com/tags/requestAnimationFrame/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 循环与异步</title>
    <link href="https://lz5z.com/JavaScript-Loop-Async/"/>
    <id>https://lz5z.com/JavaScript-Loop-Async/</id>
    <published>2018-04-11T06:03:53.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="JS-中的循环与异步">JS 中的循环与异步</h2><p>JS 中有多种方式实现循环：<code>for; for in; for of; while; do while; forEach; map</code> 等等。假如循环里面的内容是异步并且 await 的，那异步代码究竟是像 <code>Promise.all</code>一样将循环中的代码一起执行，还是每次等待上一次循环执行完毕再执行呢？</p><h2 id="首先看结论">首先看结论</h2><p>forEach 和 map, some, every 循环是并行执行的，相当于 Promise.all，其它 for, for in, for of, while, do while 都是串行执行的。</p><p>先定义异步函数 foo 和可遍历数组 arr：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">5</span> &#125;, <span class="function">(<span class="params">v, k</span>) =&gt;</span> k)</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">foo</span> = i =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">      <span class="title function_">resolve</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    &#125;, <span class="number">1000</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>并行执行：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * forEach</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">e</span>(<span class="params"></span>) &#123;</span><br><span class="line">  arr.<span class="title function_">forEach</span>(<span class="keyword">async</span> a =&gt; <span class="keyword">await</span> <span class="title function_">foo</span>(a))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * map</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  arr.<span class="title function_">map</span>(<span class="keyword">async</span> a =&gt; <span class="keyword">await</span> <span class="title function_">foo</span>(a))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * every</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">g</span>(<span class="params"></span>) &#123;</span><br><span class="line">  arr.<span class="title function_">every</span>(<span class="function"><span class="params">a</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (<span class="title function_">async</span>() =&gt; &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">foo</span>(a)</span><br><span class="line">    &#125;)()</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>串行执行：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * for</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">foo</span>(arr[i])</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * for</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">b</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">foo</span>(arr[i])</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * for of</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">c</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">of</span> arr) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">foo</span>(i)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * for in</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">d</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> arr) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">foo</span>(i)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * while</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">h</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">while</span> (i &lt; <span class="number">5</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">foo</span>(i++)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * do while</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">i</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">foo</span>(i++)</span><br><span class="line">  &#125; <span class="keyword">while</span> (i &lt; <span class="number">5</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="如何让-forEach-或者-map-也能串行执行">如何让 forEach 或者 map 也能串行执行</h2><p>首先查看 forEach 的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach">polyfill</a>，简化后可以理解为以下代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">forEach</span> = <span class="keyword">function</span>(<span class="params">callback, thisArg</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> len = <span class="variable language_">this</span>.<span class="property">length</span></span><br><span class="line">  <span class="keyword">var</span> k = <span class="number">0</span></span><br><span class="line">  <span class="keyword">while</span> (k &lt; len) &#123;</span><br><span class="line">    <span class="keyword">var</span> kValue</span><br><span class="line">    <span class="keyword">if</span> (k <span class="keyword">in</span> <span class="variable language_">this</span>) &#123;</span><br><span class="line">      kValue = <span class="variable language_">this</span>[k]</span><br><span class="line">      callback.<span class="title function_">call</span>(thisArg, kValue, k, <span class="variable language_">this</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    k++</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到本质上 forEach 还是通过 while 循环来实现的，假如我们想要一个异步的 forEach 的话，只需要将 callback 的调用改成 await 即可：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">forEachAsync</span> = <span class="keyword">async</span> <span class="keyword">function</span>(<span class="params">callback, thisArg</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> len = <span class="variable language_">this</span>.<span class="property">length</span></span><br><span class="line">  <span class="keyword">var</span> k = <span class="number">0</span></span><br><span class="line">  <span class="keyword">while</span> (k &lt; len) &#123;</span><br><span class="line">    <span class="keyword">var</span> kValue</span><br><span class="line">    <span class="keyword">if</span> (k <span class="keyword">in</span> <span class="variable language_">this</span>) &#123;</span><br><span class="line">      kValue = <span class="variable language_">this</span>[k]</span><br><span class="line">      <span class="keyword">await</span> callback.<span class="title function_">call</span>(thisArg, kValue, k, <span class="variable language_">this</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    k++</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>npm 上有一个更为完备的解决方案：<a href="https://github.com/FuturesJS/forEachAsync/blob/master/forEachAsync.js">forEachAsync</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> forEachAsync = <span class="built_in">require</span>(<span class="string">&#x27;forEachAsync&#x27;</span>).<span class="property">forEachAsync</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">forEachAsync</span>(arr, <span class="keyword">function</span>(<span class="params">a</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">foo</span>(a)</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Done!&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>就这么多。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;JS-中的循环与异步&quot;&gt;JS 中的循环与异步&lt;/h2&gt;
&lt;p&gt;JS 中有多种方式实现循环：&lt;code&gt;for; for in; for of; while; do while; forEach; map&lt;/code&gt; 等等。假如循环里面的内容是异步并且 await 的，那异步代码究竟是像 &lt;code&gt;Promise.all&lt;/code&gt;一样将循环中的代码一起执行，还是每次等待上一次循环执行完毕再执行呢？&lt;/p&gt;
&lt;h2 id=&quot;首先看结论&quot;&gt;首先看结论&lt;/h2&gt;
&lt;p&gt;forEach 和 map, some, every 循环是并行执行的，相当于 Promise.all，其它 for, for in, for of, while, do while 都是串行执行的。&lt;/p&gt;
&lt;p&gt;先定义异步函数 foo 和可遍历数组 arr：&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; arr = &lt;span class=&quot;title class_&quot;&gt;Array&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;from&lt;/span&gt;(&amp;#123; &lt;span class=&quot;attr&quot;&gt;length&lt;/span&gt;: &lt;span class=&quot;number&quot;&gt;5&lt;/span&gt; &amp;#125;, &lt;span class=&quot;function&quot;&gt;(&lt;span class=&quot;params&quot;&gt;v, k&lt;/span&gt;) =&amp;gt;&lt;/span&gt; k)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;foo&lt;/span&gt; = i =&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Promise&lt;/span&gt;(&lt;span class=&quot;function&quot;&gt;(&lt;span class=&quot;params&quot;&gt;resolve, reject&lt;/span&gt;) =&amp;gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;setTimeout&lt;/span&gt;(&lt;span class=&quot;function&quot;&gt;() =&amp;gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(i)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      &lt;span class=&quot;title function_&quot;&gt;resolve&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;, &lt;span class=&quot;number&quot;&gt;1000&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Loop" scheme="https://lz5z.com/tags/Loop/"/>
    
    <category term="async/await" scheme="https://lz5z.com/tags/async-await/"/>
    
  </entry>
  
  <entry>
    <title>正则表达式格式化查询参数</title>
    <link href="https://lz5z.com/QueryStringFormat-RegExp/"/>
    <id>https://lz5z.com/QueryStringFormat-RegExp/</id>
    <published>2018-03-26T15:05:05.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<p>记录一下，通过一行正则表达式和 replace 方法简单实现正则表达式格式化查询参数。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">const url = &#x27;https://lz5z.com/000/?a=123&amp;b=456&amp;c=%E4%B8%AD%E6%96%87&#x27;</span><br><span class="line">/** </span><br><span class="line"> * 格式化查询字符串(正则实现) </span><br><span class="line"> * @param url url地址 </span><br><span class="line"> * @return &#123;Object&#125; 格式化的json对象 </span><br><span class="line"> */</span><br><span class="line">function formatUrl(url) &#123;</span><br><span class="line">    const reg = /(?:[?&amp;]+)([^&amp;]+)=([^&amp;]+)/g</span><br><span class="line">    let data = &#123;&#125;</span><br><span class="line"></span><br><span class="line">    function fn(str, key, value) &#123;</span><br><span class="line">        data[decodeURIComponent(key)] = decodeURIComponent(value)</span><br><span class="line">    &#125;</span><br><span class="line">    url.replace(reg, fn)</span><br><span class="line">    return data</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">console.log(formatUrl(url)) // &#123; a: &#x27;123&#x27;, b: &#x27;456&#x27;, c: &#x27;中文&#x27; &#125;</span><br></pre></td></tr></table></figure><p>下次面试官问你的时候，你能答上来吗？😉😉😉</p><span id="more"></span><p>下面是 《JavaScript高级程序设计》 中给出的方案:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getQueryStringArgs</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 取得查询字符串并去掉开头的问号</span></span><br><span class="line">    <span class="keyword">var</span> qs = (location.<span class="property">search</span>.<span class="property">length</span> &gt; <span class="number">0</span> ? location.<span class="property">search</span>.<span class="title function_">substring</span>(<span class="number">1</span>) : <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    <span class="comment">// 保存数据的对象</span></span><br><span class="line">    <span class="keyword">var</span> args = &#123;&#125;</span><br><span class="line">    <span class="comment">// 取得每一项</span></span><br><span class="line">    <span class="keyword">var</span> items = qs.<span class="property">length</span> ? qs.<span class="title function_">split</span>(<span class="string">&#x27;&amp;&#x27;</span>) : []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; items.<span class="property">length</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">var</span> item = items[i].<span class="title function_">split</span>(<span class="string">&#x27;=&#x27;</span>)</span><br><span class="line">        <span class="keyword">var</span> name = <span class="built_in">decodeURIComponent</span>(item[<span class="number">0</span>])</span><br><span class="line">        <span class="keyword">var</span> value = <span class="built_in">decodeURIComponent</span>(item[<span class="number">1</span>])</span><br><span class="line">        <span class="keyword">if</span> (name.<span class="property">length</span>) args[name] = value</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> args</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;记录一下，通过一行正则表达式和 replace 方法简单实现正则表达式格式化查询参数。&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;const url = &amp;#x27;https://lz5z.com/000/?a=123&amp;amp;b=456&amp;amp;c=%E4%B8%AD%E6%96%87&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;/** &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; * 格式化查询字符串(正则实现) &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; * @param url url地址 &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; * @return &amp;#123;Object&amp;#125; 格式化的json对象 &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; */&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;function formatUrl(url) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    const reg = /(?:[?&amp;amp;]+)([^&amp;amp;]+)=([^&amp;amp;]+)/g&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    let data = &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    function fn(str, key, value) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        data[decodeURIComponent(key)] = decodeURIComponent(value)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    url.replace(reg, fn)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    return data&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;console.log(formatUrl(url)) // &amp;#123; a: &amp;#x27;123&amp;#x27;, b: &amp;#x27;456&amp;#x27;, c: &amp;#x27;中文&amp;#x27; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;下次面试官问你的时候，你能答上来吗？😉😉😉&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="RegExp" scheme="https://lz5z.com/tags/RegExp/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 深拷贝和浅拷贝</title>
    <link href="https://lz5z.com/JavaScript%E6%B7%B1%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B5%85%E6%8B%B7%E8%B4%9D/"/>
    <id>https://lz5z.com/JavaScript%E6%B7%B1%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B5%85%E6%8B%B7%E8%B4%9D/</id>
    <published>2018-03-10T14:02:49.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<p>在 JavaScript 引用数据类型中，变量保存的是一个指向堆内存的指针，当需要访问引用类型（如对象，数组等）的值时，首先从栈中获得该对象的地址指针，然后再从堆内存中取得所需的数据。</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">let</span> obj1 = &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;</span><br><span class="line"><span class="keyword">let</span> obj2 = obj1</span><br><span class="line">obj2.<span class="property">x</span> = <span class="number">2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj1) <span class="comment">// &#123; x: 2, y: 2 &#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj2) <span class="comment">// &#123; x: 2, y: 2 &#125;</span></span><br></pre></td></tr></table></figure><p>以上的拷贝方式就是浅拷贝，当 obj2 的值改变时，obj1 的值也随之发生改变。</p><span id="more"></span><h3 id="浅拷贝">浅拷贝</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr1 = [<span class="number">0</span>, <span class="number">1</span>, [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>]]</span><br><span class="line"><span class="keyword">let</span> arr2 = arr1.<span class="title function_">concat</span>()</span><br><span class="line"><span class="keyword">let</span> arr3 = arr1.<span class="title function_">slice</span>()</span><br><span class="line"><span class="keyword">let</span> arr4 = <span class="title class_">Array</span>.<span class="title function_">from</span>(arr3)</span><br><span class="line"></span><br><span class="line">arr2 === arr1 <span class="comment">// false 看起来像深拷贝</span></span><br><span class="line">arr3 === arr1 <span class="comment">// false 看起来像深拷贝</span></span><br><span class="line">arr4 === arr3 <span class="comment">// false 看起来像深拷贝</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 然鹅</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> arr5 = [&#123;<span class="attr">name</span>: <span class="string">&#x27;Leo&#x27;</span>&#125;]</span><br><span class="line"><span class="keyword">let</span> arr6 = arr4.<span class="title function_">slice</span>()</span><br><span class="line"><span class="keyword">let</span> arr7 = arr4.<span class="title function_">concat</span>()</span><br><span class="line"><span class="keyword">let</span> arr8 = <span class="title class_">Array</span>.<span class="title function_">from</span>(arr4)</span><br><span class="line"></span><br><span class="line">arr5[<span class="number">0</span>].<span class="property">name</span> = <span class="string">&#x27;Jack&#x27;</span></span><br><span class="line">arr6[<span class="number">0</span>].<span class="property">name</span> === <span class="string">&#x27;Jack&#x27;</span> <span class="comment">// 其实还是浅拷贝</span></span><br><span class="line">arr7[<span class="number">0</span>].<span class="property">name</span> === <span class="string">&#x27;Jack&#x27;</span> <span class="comment">// 其实还是浅拷贝</span></span><br><span class="line">arr8[<span class="number">0</span>].<span class="property">name</span> === <span class="string">&#x27;Jack&#x27;</span> <span class="comment">// 其实还是浅拷贝</span></span><br></pre></td></tr></table></figure><p>Array.prototype.concat(), Array.prototype.slice(), Array.from() 只能实现对一维数组的深拷贝。</p><h3 id="Object-assign">Object.assign()</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj1 = &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;</span><br><span class="line"><span class="keyword">let</span> obj2 = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, obj1)</span><br><span class="line">obj1 === obj2 <span class="comment">// false</span></span><br><span class="line">obj1.<span class="property">x</span> = <span class="number">2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj1) <span class="comment">// &#123; x: 2, y: 2 &#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj2) <span class="comment">// &#123; x: 1, y: 2 &#125; // 一维对象可以进行深拷贝</span></span><br><span class="line"><span class="comment">// 然鹅</span></span><br><span class="line"><span class="keyword">let</span> obj3 = &#123; <span class="attr">x</span>: &#123;<span class="attr">name</span>: <span class="string">&#x27;Leo&#x27;</span>&#125; &#125;</span><br><span class="line"><span class="keyword">let</span> obj4 = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, obj3)</span><br><span class="line">obj3 === obj4 <span class="comment">// false</span></span><br><span class="line">obj3.<span class="property">x</span>.<span class="property">name</span> = <span class="string">&#x27;Jack&#x27;</span></span><br><span class="line">obj4.<span class="property">x</span>.<span class="property">name</span> === <span class="string">&#x27;Jack&#x27;</span> <span class="comment">// true // 其实还是浅拷贝</span></span><br></pre></td></tr></table></figure><h3 id="深拷贝">深拷贝</h3><p>使用 JSON.parse() + JSON.stringify() 实现深拷贝</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj1 = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">y</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Leo&#x27;</span>,</span><br><span class="line">    <span class="attr">friends</span>: [<span class="string">&#x27;Lily&#x27;</span>, <span class="string">&#x27;Elsa&#x27;</span>]</span><br><span class="line">  &#125;    </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj2 = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(obj1))</span><br><span class="line"></span><br><span class="line">obj1 === obj2 <span class="comment">// false</span></span><br><span class="line">obj1.<span class="property">y</span>.<span class="property">name</span> = <span class="string">&#x27;Jack&#x27;</span></span><br><span class="line">obj1.<span class="property">y</span>.<span class="property">friends</span>.<span class="title function_">push</span>(<span class="string">&#x27;Tim&#x27;</span>)</span><br><span class="line">obj2.<span class="property">y</span>.<span class="property">name</span> === <span class="string">&#x27;Leo&#x27;</span> <span class="comment">// 深拷贝</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj2.<span class="property">y</span>.<span class="property">friends</span>) <span class="comment">// [&quot;Lily&quot;, &quot;Elsa&quot;] // 深拷贝</span></span><br></pre></td></tr></table></figure><p>JSON.parse 和 JSON.stringify 看起来不错，不过存在一些<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify">问题</a>：</p><ol><li>undefined、任意的函数以及 symbol 值，在序列化过程中会被忽略（出现在非数组对象的属性值中时）或者被转换成 null（出现在数组中时）。</li><li>所有以 symbol 为属性键的属性都会被完全忽略掉。</li><li>不可枚举的属性会被忽略。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;<span class="attr">a</span>: <span class="keyword">function</span> <span class="title function_">add</span> (<span class="params"></span>)&#123;&#125;&#125;) <span class="comment">// &#x27;&#123;&#125;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;<span class="attr">x</span>: <span class="literal">undefined</span>, <span class="attr">y</span>: <span class="title class_">Object</span>, <span class="attr">z</span>: <span class="title class_">Symbol</span>(<span class="string">&quot;&quot;</span>)&#125;) <span class="comment">// &#x27;&#123;&#125;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">JSON</span>.<span class="title function_">stringify</span>([<span class="literal">undefined</span>, <span class="title class_">Object</span>, <span class="title class_">Symbol</span>(<span class="string">&quot;&quot;</span>)]) <span class="comment">// &#x27;[null,null,null]&#x27; </span></span><br><span class="line"></span><br><span class="line"><span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;[<span class="title class_">Symbol</span>(<span class="string">&quot;foo&quot;</span>)]: <span class="string">&quot;foo&quot;</span>&#125;) <span class="comment">// &#x27;&#123;&#125;&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="使用递归">使用递归</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">deepClone</span>(<span class="params">o</span>) &#123;</span><br><span class="line">  <span class="comment">// if o is not an object </span></span><br><span class="line">  <span class="keyword">if</span> (!o || (<span class="keyword">typeof</span> o) != <span class="string">&#x27;object&#x27;</span>) <span class="keyword">return</span> o</span><br><span class="line">  <span class="keyword">let</span> res = <span class="title class_">Array</span>.<span class="title function_">isArray</span>(o) ? [] : &#123;&#125;</span><br><span class="line">  <span class="keyword">let</span> keys = <span class="title class_">Object</span>.<span class="title function_">keys</span>(o) </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i&lt; keys.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">let</span> key = keys[i]</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> key === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">        res[key] = <span class="title function_">deepClone</span>(o[key])</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        res[key] = o[key]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj1 = &#123;</span><br><span class="line">  <span class="attr">x</span>: &#123;<span class="attr">name</span>: <span class="string">&#x27;Leo&#x27;</span>&#125;,</span><br><span class="line">  <span class="attr">y</span>: <span class="literal">undefined</span>,</span><br><span class="line">  <span class="attr">z</span>: <span class="keyword">function</span> <span class="title function_">add</span> (<span class="params"></span>) &#123;&#125;,</span><br><span class="line">  <span class="attr">t</span>: <span class="title class_">Symbol</span>(<span class="string">&#x27;tt&#x27;</span>),</span><br><span class="line">  <span class="attr">m</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>],</span><br><span class="line">  <span class="attr">n</span>: [[<span class="number">1</span>, <span class="number">2</span>]]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj2 = <span class="title function_">deepClone</span>(obj1)</span><br><span class="line">obj1.<span class="property">n</span>[<span class="number">0</span>].<span class="title function_">push</span>(<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj2.<span class="property">n</span>[<span class="number">0</span>]) <span class="comment">// [1, 2]</span></span><br></pre></td></tr></table></figure><p>注意：由于使用 <code>for in</code> 循环，所以只能深度拷贝对象自身属性（非原型链上的属性），并且属性为 enumerable。</p><p>使用递归拷贝对象的方法，在目标非常大，层级关系非常深的时候会出现性能问题，具体解决方案可以参考我之前写的 <a href="https://lz5z.com/JavaScript%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96/">JavaScript递归优化</a> 使用栈代替递归的方式解决。</p><h3 id="lodash">lodash</h3><p>lodash 中提供 4 个对象<a href="https://lodash.com/docs/4.17.10#clone">拷贝</a>相关的方法：</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="title function_">clone</span>() <span class="comment">// 提供浅拷贝</span></span><br><span class="line">_.<span class="title function_">cloneDeep</span>() <span class="comment">// 提供深拷贝</span></span><br><span class="line">_.<span class="title function_">cloneDeepWith</span>() <span class="comment">// 提供递归拷贝，并且可以自定义拷贝内容</span></span><br><span class="line">_.<span class="title function_">cloneWith</span>() <span class="comment">// 提供浅拷贝，并且可以自定义拷贝内容</span></span><br></pre></td></tr></table></figure><p>demo</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">customizer</span>(<span class="params">value</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (_.<span class="title function_">isElement</span>(value)) &#123;</span><br><span class="line">    <span class="keyword">return</span> value.<span class="title function_">cloneNode</span>(<span class="literal">true</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">let</span> el = _.<span class="title function_">cloneDeepWith</span>(<span class="variable language_">document</span>.<span class="property">body</span>, customizer)</span><br><span class="line"> </span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(el === <span class="variable language_">document</span>.<span class="property">body</span>) <span class="comment">// =&gt; false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(el.<span class="property">nodeName</span>) <span class="comment">// =&gt; &#x27;BODY&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(el.<span class="property">childNodes</span>.<span class="property">length</span>) <span class="comment">// =&gt; 20</span></span><br></pre></td></tr></table></figure><p>相信上述几种方法已经能够满足我们平时大部分的需求了，如果有额外的需求，只能自己定义实现深/浅拷贝的方式了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 JavaScript 引用数据类型中，变量保存的是一个指向堆内存的指针，当需要访问引用类型（如对象，数组等）的值时，首先从栈中获得该对象的地址指针，然后再从堆内存中取得所需的数据。&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; obj1 = &amp;#123; &lt;span class=&quot;attr&quot;&gt;x&lt;/span&gt;: &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;attr&quot;&gt;y&lt;/span&gt;: &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; obj2 = obj1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;obj2.&lt;span class=&quot;property&quot;&gt;x&lt;/span&gt; = &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(obj1) &lt;span class=&quot;comment&quot;&gt;// &amp;#123; x: 2, y: 2 &amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(obj2) &lt;span class=&quot;comment&quot;&gt;// &amp;#123; x: 2, y: 2 &amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;以上的拷贝方式就是浅拷贝，当 obj2 的值改变时，obj1 的值也随之发生改变。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="clone" scheme="https://lz5z.com/tags/clone/"/>
    
    <category term="深拷贝" scheme="https://lz5z.com/tags/%E6%B7%B1%E6%8B%B7%E8%B4%9D/"/>
    
    <category term="浅拷贝" scheme="https://lz5z.com/tags/%E6%B5%85%E6%8B%B7%E8%B4%9D/"/>
    
    <category term="lodash" scheme="https://lz5z.com/tags/lodash/"/>
    
  </entry>
  
  <entry>
    <title>Shell 学习</title>
    <link href="https://lz5z.com/Shell-study/"/>
    <id>https://lz5z.com/Shell-study/</id>
    <published>2018-03-07T14:51:37.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Shell-变量">Shell 变量</h2><ul><li>变量默认都是字符串类型</li><li>变量名和等号之间不能有空格</li><li>命名：只能使用英文字母，数字和下划线，首个字符不能以数字开头</li><li>查看变量 set 命令，删除变量 <code>unset variable_name</code></li><li><code>set -u</code> 调用未声明变量报错（默认无提示）</li></ul><h3 id="变量叠加">变量叠加</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x=123</span><br><span class="line">x=&quot;$x&quot;456</span><br><span class="line">x=$&#123;x&#125;789</span><br><span class="line">echo $x # 123456789</span><br></pre></td></tr></table></figure><span id="more"></span><h3 id="readonly">readonly</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x=123</span><br><span class="line">readonly x</span><br><span class="line">x=312 #-bash: a: readonly variable</span><br></pre></td></tr></table></figure><h3 id="Shell-字符串">Shell 字符串</h3><p>Shell 字符串可以用单引号，也可以用双引号，也可以不用引号。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">str=&#x27;Hello World&#x27;</span><br><span class="line">name=&#x27;Jack&#x27;</span><br><span class="line">str=&quot;Hello, $&#123;name&#125;&quot;</span><br><span class="line">str=&quot;Hello, &quot;$name&quot;&quot;</span><br><span class="line">str=&quot;Hello, \&quot;$name\&quot;! &quot;</span><br></pre></td></tr></table></figure><p>其中双引号中可以出现变量和转义符。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">string=&quot;abcd&quot;</span><br><span class="line">echo $&#123;#string&#125; # 获取字符串长度</span><br></pre></td></tr></table></figure><p>提取子字符串<br>以下实例从字符串第 2 个字符开始截取 4 个字符：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">string=&quot;Hello World&quot;</span><br><span class="line">echo $&#123;string:1:4&#125; # 输出 ello</span><br></pre></td></tr></table></figure><p>查找字符串</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">string=&quot;Hello World&quot;</span><br><span class="line">echo `expr index &quot;$string&quot; llo` # 3</span><br></pre></td></tr></table></figure><h2 id="Shell-数组">Shell 数组</h2><p>Shell 中只支持一维数组</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">names=(&#x27;leo&#x27; &#x27;jack&#x27; &#x27;tim&#x27;)</span><br><span class="line">names[3]=&#x27;petter&#x27; # 可以不使用连续的下标，而且下标的范围没有限制</span><br><span class="line">echo $&#123;names[0]&#125; # leo</span><br><span class="line">echo $&#123;names[@]&#125; # 获取全部元素</span><br><span class="line">echo $&#123;#names[@]&#125; # 获取数组长度</span><br><span class="line">echo $&#123;#names[*]&#125; # 获取数组长度</span><br><span class="line">echo $&#123;#names[0]&#125; # 获取第一个元素长度</span><br></pre></td></tr></table></figure><h2 id="Shell-注释">Shell 注释</h2><p>Shell 没有多行注释</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">--------------------------------------------</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">这是一个注释</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">author：lizhen</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">site：https://lz5z.com</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">slogan：人生苦短，我再睡会</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">--------------------------------------------</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">#### config start #####</span></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash"><span class="comment">#</span></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">blabla</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash"><span class="comment">#</span></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment">#### config end  #####</span></span></span><br></pre></td></tr></table></figure><h2 id="Shell-参数">Shell 参数</h2><p>创建脚本 <code>test.sh</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">author:lizhen</span></span><br><span class="line"></span><br><span class="line">echo &quot;Shell 传递参数&quot;;</span><br><span class="line">echo &quot;执行的文件名：$0&quot;;</span><br><span class="line">echo &quot;第一个参数为：$1&quot;;</span><br><span class="line">echo &quot;第二个参数为：$2&quot;;</span><br><span class="line">echo &quot;第三个参数为：$3&quot;;</span><br><span class="line">echo &quot;参数个数：$#&quot;;</span><br><span class="line">echo &quot;参数字符串：$*&quot;;</span><br><span class="line">echo &quot;所有参数：$@&quot;;</span><br><span class="line">echo &quot;进程ID号: $$&quot;;</span><br></pre></td></tr></table></figure><p>为脚本设置执行权限，并执行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">chmod +x test.sh</span><br><span class="line">./test.sh 1 2 4</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Shell 传递参数</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">执行的文件名：./test.sh</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第一个参数为：1</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第二个参数为：2</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第三个参数为：3</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">参数个数：4</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">参数字符串：1 2 3 4</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">所有参数：1 2 3 4</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">进程ID号: 27694</span></span><br></pre></td></tr></table></figure><h2 id="Shell-运算符">Shell 运算符</h2><p>原生 bash 不支持数学运算符，但是可以通过其他命令实现，比如 expr。</p><h3 id="算术运算符">算术运算符</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">val=`expr 2 + 2` # 注意空格</span><br><span class="line">echo val # 4</span><br><span class="line">echo `expr 2 - 2` # 0</span><br><span class="line">echo `expr 2 \* 2` # 4 # 乘号前面必须要有反斜杠</span><br><span class="line">echo `expr 2 / 2` # 1</span><br><span class="line">echo `expr 3 % 2` # 1</span><br><span class="line">echo `expr 2 == 2` # 1</span><br><span class="line">echo `expr 2 != 2` # 0</span><br></pre></td></tr></table></figure><h3 id="关系运算符">关系运算符</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[ $a -eq $b ] # -eq 相等</span><br><span class="line">[ $a -ne $b ] # -ne 不等</span><br><span class="line">[ $a -gt $b ] # -gt 大于</span><br><span class="line">[ $a -lt $b ] # -lt 小于</span><br><span class="line">[ $a -ge $b ] # -ge 大于等于</span><br><span class="line">[ $a -le $b ] # -le 小于等于</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">a=$1</span><br><span class="line">b=$2</span><br><span class="line">echo $a</span><br><span class="line">echo $b</span><br><span class="line">if [ $a -eq $b ]</span><br><span class="line">then</span><br><span class="line">   echo &quot;$a -eq $b : a 等于 b&quot;</span><br><span class="line">else</span><br><span class="line">   echo &quot;$a -eq $b: a 不等于 b&quot;</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">./test.sh 10 20</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">10</span> </span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">20</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="string">&quot;10 -eq 20: a 不等于 b&quot;</span></span></span><br></pre></td></tr></table></figure><h3 id="布尔运算符">布尔运算符</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">author:lizhen</span></span><br><span class="line"></span><br><span class="line">a=10</span><br><span class="line">b=20</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">非运算 !</span></span><br><span class="line">if [ $a != $b ]</span><br><span class="line">then</span><br><span class="line">   echo &quot;$a != $b : a 不等于 b&quot;</span><br><span class="line">else</span><br><span class="line">   echo &quot;$a != $b: a 等于 b&quot;</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">或运算 -o</span></span><br><span class="line">if [ $a -lt 100 -o $b -gt 100 ]</span><br><span class="line">then</span><br><span class="line">   echo &quot;$a 小于 100 或 $b 大于 100 : true&quot;</span><br><span class="line">else</span><br><span class="line">   echo &quot;$a 小于 100 或 $b 大于 100 : false&quot;</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">与运算 -a</span></span><br><span class="line">if [ $a -lt 100 -a $b -gt 15 ]</span><br><span class="line">then</span><br><span class="line">   echo &quot;$a 小于 100 且 $b 大于 15 : true&quot;</span><br><span class="line">else</span><br><span class="line">   echo &quot;$a 小于 100 且 $b 大于 15 : false&quot;</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><h3 id="逻辑运算符">逻辑运算符</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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">a=10</span><br><span class="line">b=20</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">逻辑AND &amp;&amp;</span></span><br><span class="line">if [[ $a -lt 100 &amp;&amp; $b -gt 100 ]]</span><br><span class="line">then</span><br><span class="line">   echo &quot;true&quot;</span><br><span class="line">else</span><br><span class="line">   echo &quot;false&quot;</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">逻辑OR ||</span></span><br><span class="line">if [[ $a -lt 100 || $b -gt 100 ]]</span><br><span class="line">then</span><br><span class="line">   echo &quot;true&quot;</span><br><span class="line">else</span><br><span class="line">   echo &quot;false&quot;</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><h3 id="字符串运算符">字符串运算符</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">a=&quot;abc&quot;</span><br><span class="line">b=&quot;cde&quot;</span><br><span class="line">[ $a = $b ] # 字符串相等</span><br><span class="line">[ $a != $b ] # 字符串不等</span><br><span class="line">[ -z $a ] # 字符串长度为0</span><br><span class="line">[ -n $a ] # 字符串长度不为0</span><br><span class="line">[ $a ] # 字符串不为空</span><br></pre></td></tr></table></figure><h2 id="echo-命令">echo 命令</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;\&quot;Are you OK?\&quot;&quot; # 转义字符</span><br><span class="line">echo &quot;$a&quot; # 变量</span><br><span class="line">echo -e &quot;I\&#x27;m \nOK&quot; # -e 开启转义</span><br><span class="line">echo -e &quot;I\&#x27;m OK \c&quot; # -e 开启转义 \c 不换行</span><br><span class="line">echo &quot;Are you OK?&quot; &gt; tesh.sh # 显示结果到文件</span><br><span class="line">echo &#x27;$a\&quot;&#x27; # $a\&quot; # 原样输出字符串，不转义不去变量 单引号</span><br><span class="line">echo `date` # 显示时间</span><br></pre></td></tr></table></figure><h2 id="printf-命令">printf 命令</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">printf</span> format-string [arguments...]</span></span><br><span class="line"></span><br><span class="line">printf &quot;%-10s %-8s %-4s\n&quot; 姓名 性别 体重kg  </span><br><span class="line">printf &quot;%-10s %-8s %-4.2f\n&quot; 郭靖 男 66.1234 </span><br><span class="line">printf &quot;%-10s %-8s %-4.2f\n&quot; 杨过 男 48.6543 </span><br><span class="line">printf &quot;%-10s %-8s %-4.2f\n&quot; 郭芙 女 47.9876 </span><br></pre></td></tr></table></figure><p>%s %c %d %f都是格式替代符</p><p>%-10s 指一个宽度为10个字符（-表示左对齐，没有则表示右对齐），任何字符都会被显示在10个字符宽的字符内，如果不足则自动以空格填充，超过也会将内容全部显示出来。</p><p>%-4.2f 指格式化为小数，其中.2指保留2位小数。</p><h2 id="test-命令">test 命令</h2><p>test 命令用于检查某个条件是否成立，它可以进行数值、字符和文件三个方面的测试。</p><h3 id="数值测试">数值测试</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">a=10</span><br><span class="line">b=20</span><br><span class="line">if test $[a] -eq $[b] # 如果 a 等于 b</span><br><span class="line">then</span><br><span class="line">  echo &quot;$a 等于 $b&quot;</span><br><span class="line">else</span><br><span class="line">  echo &quot;$a 不等于 $b&quot;</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p>代码中的 [] 表示执行基本的算数运算。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">a=10</span><br><span class="line">b=10</span><br><span class="line">echo &quot;$[a+b]&quot; # 20 # 不能有空格</span><br></pre></td></tr></table></figure><h3 id="字符串测试">字符串测试</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">a=&quot;abc&quot;</span><br><span class="line">b=&quot;ABC&quot;</span><br><span class="line">if test $a = $b</span><br><span class="line">then </span><br><span class="line">  echo &quot;字符串相等&quot;</span><br><span class="line">else </span><br><span class="line">  echo &quot;字符串不等&quot;</span><br><span class="line">fi    </span><br></pre></td></tr></table></figure><h3 id="文件测试">文件测试</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-e 文件名  如果文件存在则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-r 文件名  如果文件存在且可读则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-w 文件名  如果文件存在且可写则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-x 文件名  如果文件存在且可执行则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-s 文件名  如果文件存在且至少有一个字符则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-d 文件名  如果文件存在且为目录则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-f 文件名  如果文件存在且为普通文件则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-c 文件名  如果文件存在且为字符型特殊文件则为真</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-b 文件名  如果文件存在且为块特殊文件则为真</span></span><br><span class="line"></span><br><span class="line">if test -e ./test.sh</span><br><span class="line">then </span><br><span class="line">  echo &quot;test.sh 文件存在&quot;</span><br><span class="line">else</span><br><span class="line">  echo &quot;文件不存在&quot;</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><h2 id="Shell-流程控制">Shell 流程控制</h2><h3 id="条件控制">条件控制</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="keyword">if</span></span> </span><br><span class="line">if condition</span><br><span class="line">then </span><br><span class="line">  command1</span><br><span class="line">  command2</span><br><span class="line">  ...</span><br><span class="line">  commandN</span><br><span class="line">fi  </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="keyword">if</span> else-if <span class="keyword">else</span></span></span><br><span class="line">if condition1</span><br><span class="line">then</span><br><span class="line">    command1</span><br><span class="line">elif condition2 </span><br><span class="line">then </span><br><span class="line">    command2</span><br><span class="line">else</span><br><span class="line">    commandN</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><h3 id="for-循环">for 循环</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">for var in item1 item2 ... itemN</span><br><span class="line">do</span><br><span class="line">    command1</span><br><span class="line">    command2</span><br><span class="line">    ...</span><br><span class="line">    commandN</span><br><span class="line">done</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="keyword">for</span> 循环</span></span><br><span class="line">for var in item1 item2 ... itemN; do command1; command2… done;</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="keyword">for</span> 循环</span></span><br><span class="line">for loop in 1 2 3 4 5</span><br><span class="line">do</span><br><span class="line">    echo &quot;The value is: $loop&quot;</span><br><span class="line">done</span><br></pre></td></tr></table></figure><h3 id="while-循环">while 循环</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">while condition</span><br><span class="line">do </span><br><span class="line">  command</span><br><span class="line">done</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="keyword">while</span> 循环</span></span><br><span class="line">int=1</span><br><span class="line">while(( $int&lt;=5 ))</span><br><span class="line">do</span><br><span class="line">  echo $int</span><br><span class="line">  let &quot;int++&quot;</span><br><span class="line">done</span><br></pre></td></tr></table></figure><h3 id="无限循环">无限循环</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">while true</span><br><span class="line">do </span><br><span class="line">  command</span><br><span class="line">done</span><br></pre></td></tr></table></figure><h3 id="case">case</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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">case 值 in</span><br><span class="line">模式1)</span><br><span class="line">    command1</span><br><span class="line">    command2</span><br><span class="line">    ...</span><br><span class="line">    commandN</span><br><span class="line">    ;;</span><br><span class="line">模式2）</span><br><span class="line">    command1</span><br><span class="line">    command2</span><br><span class="line">    ...</span><br><span class="line">    commandN</span><br><span class="line">    ;;</span><br><span class="line">esac</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="keyword">case</span></span></span><br><span class="line">echo &#x27;输入 1 到 4 之间的数字:&#x27;</span><br><span class="line">echo &#x27;你输入的数字为:&#x27;</span><br><span class="line">read aNum</span><br><span class="line">case $aNum in</span><br><span class="line">    1)  echo &#x27;你选择了 1&#x27;</span><br><span class="line">    ;;</span><br><span class="line">    2)  echo &#x27;你选择了 2&#x27;</span><br><span class="line">    ;;</span><br><span class="line">    3)  echo &#x27;你选择了 3&#x27;</span><br><span class="line">    ;;</span><br><span class="line">    4)  echo &#x27;你选择了 4&#x27;</span><br><span class="line">    ;;</span><br><span class="line">    *)  echo &#x27;你没有输入 1 到 4 之间的数字&#x27;</span><br><span class="line">    ;;</span><br><span class="line">esac</span><br></pre></td></tr></table></figure><p>case工作方式如上所示。取值后面必须为单词in，每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后，其间所有命令开始执行直至 ;;。</p><p>取值将检测匹配的每一个模式。一旦模式匹配，则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式，使用星号 * 捕获该值，再执行后面的命令。</p><h3 id="break">break</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">while :</span><br><span class="line">do</span><br><span class="line">    echo -n &quot;输入 1 到 5 之间的数字:&quot;</span><br><span class="line">    read aNum</span><br><span class="line">    case $aNum in</span><br><span class="line">        1|2|3|4|5) echo &quot;你输入的数字为 $aNum!&quot;</span><br><span class="line">        ;;</span><br><span class="line">        *) echo &quot;你输入的数字不是 1 到 5 之间的! 游戏结束&quot;</span><br><span class="line">            break</span><br><span class="line">        ;;</span><br><span class="line">    esac</span><br><span class="line">done</span><br></pre></td></tr></table></figure><h3 id="continue">continue</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">while :</span><br><span class="line">do</span><br><span class="line">    echo -n &quot;输入 1 到 5 之间的数字: &quot;</span><br><span class="line">    read aNum</span><br><span class="line">    case $aNum in</span><br><span class="line">        1|2|3|4|5) echo &quot;你输入的数字为 $aNum!&quot;</span><br><span class="line">        ;;</span><br><span class="line">        *) echo &quot;你输入的数字不是 1 到 5 之间的!&quot;</span><br><span class="line">            continue</span><br><span class="line">            echo &quot;游戏结束&quot;</span><br><span class="line">        ;;</span><br><span class="line">    esac</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>esac<br>case的语法和C family语言差别很大，它需要一个esac（就是case反过来）作为结束标记，每个case分支用右圆括号，用两个分号表示break。</p><h2 id="Shell-函数">Shell 函数</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">funWithReturn()&#123;</span><br><span class="line">    echo &quot;输入的两个数字进行相加运算...&quot;</span><br><span class="line">    echo &quot;输入第一个数字: &quot;</span><br><span class="line">    read aNum</span><br><span class="line">    echo &quot;输入第二个数字: &quot;</span><br><span class="line">    read anotherNum</span><br><span class="line">    echo &quot;两个数字分别为 $aNum 和 $anotherNum !&quot;</span><br><span class="line">    return $(($aNum+$anotherNum))</span><br><span class="line">&#125;</span><br><span class="line">funWithReturn</span><br><span class="line">echo &quot;输入的两个数字之和为 $? !&quot;</span><br></pre></td></tr></table></figure><p>函数返回值在调用该函数后通过 $? 来获得。</p><blockquote><p>注意：所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分，直至shell解释器首次发现它时，才可以使用。调用函数仅使用其函数名即可。</p></blockquote><h3 id="函数参数">函数参数</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">funWithParam()&#123;</span><br><span class="line">    echo &quot;第一个参数为 $1 !&quot;</span><br><span class="line">    echo &quot;第二个参数为 $2 !&quot;</span><br><span class="line">    echo &quot;第十个参数为 $10 !&quot;</span><br><span class="line">    echo &quot;第十个参数为 $&#123;10&#125; !&quot;</span><br><span class="line">    echo &quot;第十一个参数为 $&#123;11&#125; !&quot;</span><br><span class="line">    echo &quot;参数总数有 $# 个!&quot;</span><br><span class="line">    echo &quot;作为一个字符串输出所有参数 $* !&quot;</span><br><span class="line">&#125;</span><br><span class="line">funWithParam 1 2 3 4 5 6 7 8 9 34 73</span><br></pre></td></tr></table></figure><h2 id="Shell-重定向">Shell 重定向</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">command &gt; file # 将输出重定向到 file</span><br><span class="line">command &lt; file # 将输入重定向到 file</span><br><span class="line">command &gt;&gt; file # 将输出以追加的方式重定向到 file</span><br></pre></td></tr></table></figure><h3 id="禁止输出">禁止输出</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">command &gt; /dev/null</span><br></pre></td></tr></table></figure><p>/dev/null 是一个特殊的文件，写入到它的内容都会被丢弃；如果尝试从该文件读取内容，那么什么也读不到。但是 /dev/null 文件非常有用，将命令的输出重定向到它，会起到&quot;禁止输出&quot;的效果。</p><p>如果希望屏蔽 stdout 和 stderr，可以这样写：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">command</span> &gt; /dev/null 2&gt;&amp;1</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：0 是标准输入（STDIN），1 是标准输出（STDOUT），2 是标准错误输出（STDERR）</p></blockquote><h2 id="Shell-文件包含">Shell 文件包含</h2><p><a href="http://test1.sh">test1.sh</a></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a=&quot;abc&quot;</span><br></pre></td></tr></table></figure><p><a href="http://test2.sh">test2.sh</a></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">使用 . 号来引用test1.sh 文件</span></span><br><span class="line">. ./test1.sh</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">或者使用以下包含文件代码</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">source</span> ./test1.sh</span></span><br><span class="line"></span><br><span class="line">echo &quot;$a&quot;</span><br></pre></td></tr></table></figure><p>接下来，我们为 <a href="http://test2.sh">test2.sh</a> 添加可执行权限并执行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">chmod</span> +x test2.sh</span> </span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./test2.sh</span> </span><br></pre></td></tr></table></figure><blockquote><p>注：被包含的文件 <a href="http://test1.sh">test1.sh</a> 不需要可执行权限。</p></blockquote><h2 id="read">read</h2><p>-p 输入提示信息<br>-t 等待时间（单位是秒）<br>-n 字符数，read只<br>-s 输入隐藏数据</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line">read -p &quot;please input your name: &quot; -t 30 name</span><br><span class="line">echo $name</span><br><span class="line"></span><br><span class="line">read -p &quot;please input your sex [M/F]: &quot; -n 1 sex</span><br><span class="line">echo -e &quot;\n&quot;</span><br><span class="line">echo $sex</span><br><span class="line"></span><br><span class="line">read -p &quot;please input your password: &quot; -s password</span><br><span class="line">echo -e &quot;\n&quot;</span><br><span class="line">echo $password</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;Shell-变量&quot;&gt;Shell 变量&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;变量默认都是字符串类型&lt;/li&gt;
&lt;li&gt;变量名和等号之间不能有空格&lt;/li&gt;
&lt;li&gt;命名：只能使用英文字母，数字和下划线，首个字符不能以数字开头&lt;/li&gt;
&lt;li&gt;查看变量 set 命令，删除变量 &lt;code&gt;unset variable_name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set -u&lt;/code&gt; 调用未声明变量报错（默认无提示）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;变量叠加&quot;&gt;变量叠加&lt;/h3&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;x=123&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;x=&amp;quot;$x&amp;quot;456&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;x=$&amp;#123;x&amp;#125;789&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;echo $x # 123456789&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://lz5z.com/categories/Linux/"/>
    
    
    <category term="Shell" scheme="https://lz5z.com/tags/Shell/"/>
    
  </entry>
  
  <entry>
    <title>HTTP/2 学习</title>
    <link href="https://lz5z.com/HTTP2-study/"/>
    <id>https://lz5z.com/HTTP2-study/</id>
    <published>2018-03-06T13:35:06.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="HTTP-2-0-简介">HTTP/2.0 简介</h2><ol><li>HTTP/2 标准于 2015 年发布，目前大部分主流浏览器均已提供支持。</li><li>HTTP/2 没有改变 HTTP 的应用语义，其请求方法、状态码、URI 等核心概念与 HTTP/1.1 保持一致。</li><li>HTTP/2 采用了二进制而非明文来打包、传输客户端—服务器间的数据。</li><li>HTTP/2 的前身是 <a href="https://zh.wikipedia.org/wiki/SPDY">SPDY 协议</a>。</li><li>HTTP/2 中 TLS 为可选，但是大厂商如 chrome 和 firefox 表示只会实现基于 TLS 的 HTTP/2。所以要部署 HTTP/2，首先要升级 HTTPS。</li><li>HTTP/2 通过以下举措，减少网络延迟，提供浏览器加载速度：<ul><li>对 HTTP 头字段进行数据压缩(即 HPACK 算法)；</li><li>HTTP/2 服务端推送(Server Push)；</li><li>请求管线化；</li><li>修复 HTTP/1. 0版本以来未修复的队头阻塞问题；</li><li>对数据传输采用多路复用，让多个请求合并在同一 TCP 连接内。</li></ul></li></ol><span id="more"></span><h2 id="HTTP-2-测试">HTTP/2 测试</h2><p><a href="https://http2.akamai.com/demo">Akamai http2 demo</a> 这个 Akamai 公司建立的官方 demo，左右两边分别为 HTTP/1.1 和 HTTP/2，两边都同时请求 300 多张图片，从加载时间可以看出 HTTP/2 在速度上的绝对优势。</p><p>chrome 商店中有一个工具 <a href="https://chrome.google.com/webstore/detail/http2-and-spdy-indicator/mpbpobfflnpcgagjijhmgnchggcjblin">HTTP/2 and SPDY indicator</a> 用来查看当前网站是否基于 HTTP/2，添加到 chrome 后如果蓝色闪电亮了说明支持 HTTP/2。</p><h2 id="HTTP-2-新特性">HTTP/2 新特性</h2><p>HTTP/2 所有性能增强的核心在于新的二进制分帧层，它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。HTTP/1.x 协议解析基于纯文本，而 HTTP/2 将所有传输的信息分割为更小的消息和帧，并采用二进制格式对它们编码。二进制只有 0 和 1 的组合实现起来方便且健壮。</p><h3 id="帧、消息、流和-TCP-连接">帧、消息、流和 TCP 连接</h3><p>有别于 HTTP/1.1 在连接中的明文请求，HTTP/2 将一个 TCP 连接分为若干个流（Stream），每个流中可以传输若干消息（Message），每个消息由若干最小的二进制帧（Frame）组成。这也是 HTTP/1.1 与 HTTP/2 最大的区别。 HTTP/2 中，每个用户的操作行为被分配了一个流编号(stream ID)，这意味着用户与服务端之间创建了一个 TCP 通道；协议将每个请求分区为二进制的控制帧与数据帧部分，以便解析。</p><h3 id="多路复用">多路复用</h3><p>在 HTTP/1.1 协议中 「浏览器客户端在同一时间，针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」这也是我们在站点中使用 CDN 的主要原因。</p><p>多路复用原理上还是基于以上 TCP 连接通道，通过单一的 TCP 连接发起和响应多重请求机制。</p><h3 id="首部压缩-HPACK-算法">首部压缩 - HPACK 算法</h3><p>在 HTTP/1.x 中，header 中带有大量信息，而且每次都要重复发送，HTTP/2 中引入 HPACK 算法用于对 HTTP 头部做压缩。其原理在于：</p><ul><li>客户端与服务端共同维护一份静态字典（Static Table），其中包含了常见头部名及常见头部名称与值的组合的代码；</li><li>客户端和服务端根据先入先出的原则，维护一份可动态添加内容的共同动态字典（Dynamic Table）；</li><li>客户端和服务端共同支持基于相同内容得静态哈夫曼码表的哈夫曼编码（Huffman Coding）。</li></ul><h3 id="服务器推送-Server-Push">服务器推送 - Server Push</h3><p>HTTP/2 引入了服务器推送，可以在客户端请求资源之前发送数据，这允许服务器直接提供浏览器渲染页面所需资源，而无须浏览器在收到、解析页面后再提起一轮请求，节约了加载时间。除此之外，服务器还能够缓存数据，在同源策略下，不同页面共享缓存资源成为可能。</p><h3 id="重置">重置</h3><p>HTTP/1.1 的有一个缺点是：当一个含有确切值的 Content-Lengt h的 HTTP 消息被送出之后，你就很难中断它了。当然，通常你可以断开整个 TCP 链接（但也不总是可以这样），但这样导致的代价就是需要通过三次握手来重新建立一个新的TCP连接。</p><p>一个更好的方案是只终止当前传输的消息并重新发送一个新的。在 HTTP/2 里面，我们可以通过发送 RST_STREAM 帧来实现这种需求，从而避免浪费带宽和中断已有的连接。</p><h2 id="参考文档">参考文档</h2><ul><li><a href="http://www.alloyteam.com/2016/07/httphttp2-0spdyhttps-reading-this-is-enough/#prettyPhoto">HTTP,HTTP2.0,SPDY,HTTPS你应该知道的一些事</a></li><li><a href="https://imququ.com/post/http2-resource.html">HTTP/2 资料汇总</a></li><li><a href="https://www.zhihu.com/question/34074946">HTTP/2.0 相比1.0有哪些重大改进？</a></li><li><a href="https://zh.wikipedia.org/wiki/HTTP/2">HTTP/2</a></li><li><a href="https://ye11ow.gitbooks.io/http2-explained/">http2讲解</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;HTTP-2-0-简介&quot;&gt;HTTP/2.0 简介&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;HTTP/2 标准于 2015 年发布，目前大部分主流浏览器均已提供支持。&lt;/li&gt;
&lt;li&gt;HTTP/2 没有改变 HTTP 的应用语义，其请求方法、状态码、URI 等核心概念与 HTTP/1.1 保持一致。&lt;/li&gt;
&lt;li&gt;HTTP/2 采用了二进制而非明文来打包、传输客户端—服务器间的数据。&lt;/li&gt;
&lt;li&gt;HTTP/2 的前身是 &lt;a href=&quot;https://zh.wikipedia.org/wiki/SPDY&quot;&gt;SPDY 协议&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;HTTP/2 中 TLS 为可选，但是大厂商如 chrome 和 firefox 表示只会实现基于 TLS 的 HTTP/2。所以要部署 HTTP/2，首先要升级 HTTPS。&lt;/li&gt;
&lt;li&gt;HTTP/2 通过以下举措，减少网络延迟，提供浏览器加载速度：
&lt;ul&gt;
&lt;li&gt;对 HTTP 头字段进行数据压缩(即 HPACK 算法)；&lt;/li&gt;
&lt;li&gt;HTTP/2 服务端推送(Server Push)；&lt;/li&gt;
&lt;li&gt;请求管线化；&lt;/li&gt;
&lt;li&gt;修复 HTTP/1. 0版本以来未修复的队头阻塞问题；&lt;/li&gt;
&lt;li&gt;对数据传输采用多路复用，让多个请求合并在同一 TCP 连接内。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="网络" scheme="https://lz5z.com/categories/%E7%BD%91%E7%BB%9C/"/>
    
    
    <category term="HTTP" scheme="https://lz5z.com/tags/HTTP/"/>
    
    <category term="HTTP2" scheme="https://lz5z.com/tags/HTTP2/"/>
    
  </entry>
  
  <entry>
    <title>Web 安全学习</title>
    <link href="https://lz5z.com/Web%E5%AE%89%E5%85%A8%E5%AD%A6%E4%B9%A0/"/>
    <id>https://lz5z.com/Web%E5%AE%89%E5%85%A8%E5%AD%A6%E4%B9%A0/</id>
    <published>2018-03-01T11:57:53.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<p>去年一家专门做企业安全的公司来我们公司做测试和培训，经过他们一周多的测试，找到了公司多个项目中存在的很多问题，惊奇地发现，我们组的前端项目竟然没有发现一个漏洞。虽然没有找到并不代表没有，但是从中也能看出我们组在这方面还是有些实力的。而我对安全方面可以说是没有多少积累，最近抽时间学习一下 web 安全相关的知识。</p><h2 id="XSS">XSS</h2><p>XSS(Cross Site Script) 跨站脚本攻击，是攻击者利用网站漏洞在网站上注入恶意客户端代码，以获取访问权限，冒充用户，修改 HTML 内容等。恶意内容一般包括 JavaScript，主要方式是获取用户的隐私数据，例如 cookie，session 等。</p><p>XSS 攻击可以分为 3 类：存储型、反射型、基于 DOM。</p><span id="more"></span><h3 id="存储型-XSS">存储型 XSS</h3><p>存储型 XSS 是指恶意脚本永久存储在目标服务器上，当客户端请求数据时，脚本从服务器上传回并且执行。存储型 XSS 一般存在于 form 表单提交等交互功能，比如发帖留言，提交文本信息等。攻击者将内容经正常的功能提交于数据库存储，当前端页面获得后端从数据库中读取的注入代码时，将其渲染并且执行。</p><p>存储型 XSS 需要满足以下 3 个条件：</p><ul><li>请求提交的数据后端没有转义直接入库。</li><li>后端从数据库中读取的数据没有转义直接输出给前端。</li><li>前端拿到数据后没有转义直接渲染 DOM。</li></ul><p>因此防止存储型 XSS 需要前端和后端共同努力。</p><ul><li>后端获取前端数据后，将所有的字段统一进行转义处理。</li><li>后端输出给前端的数据统一进行转义处理。</li><li>前端渲染 DOM 的时候对后端返回的数据进行转义处理。</li></ul><h3 id="反射型-XSS">反射型 XSS</h3><p>服务器接受客户端的请求包，不会存储请求包的内容，只是简单的把用户数据 “反射” 给客户端造成反射型 XSS。常见的有用户搜索，错误信息的处理，这种攻击方式具有一次性。</p><p>反射型 XSS 有以下特征：</p><ul><li>即时性，不经过服务器存储，直接通过 HTTP 请求完成攻击，拿到用户隐私数据</li><li>攻击者需要诱骗用户点击</li></ul><p>下面写一个简单的示例：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>xss demo<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> &gt;</span><span class="tag">&lt;<span class="name">button</span>&gt;</span>搜索<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span>&gt;</span>搜索结果为： </span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;result&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> $ = <span class="variable language_">document</span>.<span class="property">querySelector</span>.<span class="title function_">bind</span>(<span class="variable language_">document</span>)</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">let</span> input = $(<span class="string">&#x27;input&#x27;</span>)</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">let</span> button = $(<span class="string">&#x27;button&#x27;</span>)</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">let</span> result = $(<span class="string">&#x27;#result&#x27;</span>)</span></span><br><span class="line"><span class="language-javascript">        button.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            result.<span class="property">innerHTML</span> = input.<span class="property">value</span></span></span><br><span class="line"><span class="language-javascript">        &#125;</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在页面 input 中输入 <code>&lt;img src=&quot;&quot; onerror=&quot;alert(document.cookie)&quot;&gt;</code>，可以看到页面弹出警告框，并且显示用户 cookie。</p><h3 id="基于-DOM-的-XSS">基于 DOM 的 XSS</h3><p>基于 DOM 的 XSS 是指恶意脚本修改页面结构，比如一些免费 wifi 用来植入悬浮广告。</p><h3 id="基于字符集的-XSS">基于字符集的 XSS</h3><p>现阶段很多开源的库都专门针对 XSS 进行转义处理，默认抵御大多数 XSS 攻击，但是还是有很多方法绕过转义规则。假如页面不设置字符集的话，浏览器有自动识别编码的机制，所以黑客通过使用非常规字符集来达到 XSS 注入的功能。</p><h3 id="XSS-攻击检测方法">XSS 攻击检测方法</h3><p>常用的有以下几种，你也可以根据页面 DOM 结果对其进行修改</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&gt;<span class="language-xml"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"><span class="title function_">alert</span>(<span class="variable language_">document</span>.<span class="property">cookie</span>)</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line">=<span class="string">&#x27;&gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;</span></span><br><span class="line"><span class="string">&quot;&gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;</span></span><br><span class="line"><span class="string">&lt;script&gt;alert(document.cookie)&lt;/script&gt;</span></span><br><span class="line"><span class="string">%3Cscript%3Ealert(&#x27;</span><span class="variable constant_">XSS</span><span class="string">&#x27;)%3C/script%3E</span></span><br><span class="line"><span class="string">&lt;script&gt;alert(&#x27;</span><span class="variable constant_">XSS</span><span class="string">&#x27;)&lt;/script&gt;</span></span><br><span class="line"><span class="string">&lt;img src=&quot;javascript:alert(&#x27;</span><span class="variable constant_">XSS</span><span class="string">&#x27;)&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;img src=&quot;http://xxx.com/yyy.png&quot; onerror=&quot;alert(&#x27;</span><span class="variable constant_">XSS</span><span class="string">&#x27;)&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;div style=&quot;height:expression(alert(&#x27;</span><span class="variable constant_">XSS</span><span class="string">&#x27;),1)&quot;&gt;&lt;/div&gt;（这个仅限IE有效）</span></span><br></pre></td></tr></table></figure><h3 id="XSS-预防">XSS 预防</h3><p>XSS 之所以会发生，是因为用户输入的数据变成了代码。所以我们需要对数据进行 HTML Encode 处理，将其中的特殊字符进行编码。</p><table><thead><tr><th style="text-align:center">HTML character</th><th style="text-align:center">HTML Encoded</th></tr></thead><tbody><tr><td style="text-align:center">&lt;</td><td style="text-align:center"><code>&amp;lt;</code></td></tr><tr><td style="text-align:center">&gt;</td><td style="text-align:center"><code>&amp;gt;</code></td></tr><tr><td style="text-align:center">&amp;</td><td style="text-align:center"><code>&amp;amp;</code></td></tr><tr><td style="text-align:center">’</td><td style="text-align:center"><code>&amp;#039;</code></td></tr><tr><td style="text-align:center">&quot;</td><td style="text-align:center"><code>&amp;quot;</code></td></tr><tr><td style="text-align:center">空格</td><td style="text-align:center"><code>&amp;nbsp;</code></td></tr></tbody></table><ol><li>将重要的 cookie 标记为 HTTP Only。</li><li>只允许用户输入期望的数据。</li><li>对数据进行 HTML Encode 处理。</li><li>过滤或者移除特殊的 HTML 标签，如果 <code>&lt;script&gt;</code>，<code>&lt;iframe&gt;</code>，<code>&lt;img&gt;</code> 等。</li><li>过滤特殊的 JavaScript 事件，比如 <code>onclick</code>，<code>onerror</code>，<code>onfocus</code> 等。</li></ol><h2 id="CSRF">CSRF</h2><p>CSRF(Cross-Site Request Forgery) 跨站请求伪造攻击，是指攻击者通过盗用用户登录信息，模拟发送各种请求。攻击者借助聊天软件、论坛、微博等发送链接（有些伪装成短域名），迫使用户去执行攻击者预设的操作。如果当前用户具有管理员权限的话，CSRF 攻击将危及到整个 Web 应用程序。与 XSS 相比，XSS 是利用用户对指定网站的信任，CSRF 是利用网站对用户浏览器的信任。</p><h3 id="CSRF-原理">CSRF 原理</h3><ol><li>用户登录信任网站 A，通过验证后，在浏览器中产生 cookie，记录登录状态。</li><li>用户在没有登出的情况下登录危险的网站 B。</li><li>网站 B 要求访问网站 A，发出一个请求。</li><li>浏览器带着 A 产生的 Cookie 访问网站 A，此时 A 不知道中请求是用户发出的还是 B 发出的，A 根据 Cookie 中的信息处理该请求，网站 B 达到了模拟用户请求的目的。</li></ol><p>要完成一次 CSRF 攻击，用户必须依次完成两个步骤：</p><ol><li>登录受信任网站 A，并在本地生成 Cookie。</li><li>在不登出 A 的情况下，访问危险网站 B。</li></ol><h3 id="CRSF-例子">CRSF 例子</h3><p>假如一家银行的转账操作的 URL 地址是：<code>http://lz5z.com/withdraw?account=AccountName&amp;amount=1000&amp;for=PayeeName</code>，恶意网站 B 中放置一段代码：<code>&lt;img src=http://lz5z.com/withdraw?account=lizhen&amp;amount=1000&amp;for=BadGuy&gt;</code>。由于 img、script、iframe 标签不受同源策略现在，假如用户在未登出 A 的情况下打开了 B 网站，在 Cookie 未过期的情况下，用户就会损失 1000 块。</p><h3 id="CSRF-防御">CSRF 防御</h3><ol><li>正确使用 GET、POST 请求和 cookie。</li><li>检查请求报头中的 Referer 参数。Referer 用来标明请求来源于哪个地址。检查 Referer 字段存在局限性，因其完全依赖浏览器发送正确的 Referer 字段，虽然 HTTP 协议对此字段的内容有明确的规定，但浏览器具体实现的时候可能存在问题，比如早期 IE 中就存在 Referer 可以被修改的 bug。</li><li>在非 GET 请求中添加校验 token。</li><li>关键请求增加验证码。缺点是用户多次输入验证码，用户体验较差。</li><li>渲染表单的时候，为每一个表单生成一个 csrfToken，提交表单的时候，后端做 csrf 验证。</li><li>对每个用户创建 token，将其存放于服务端的 session 和客户端的 cookie 中，对每次请求，都检查二者是否一致。缺点是如果用户被 xss 攻破，黑客可能同时获取用户的 cookie。</li></ol><h2 id="DDoS-攻击">DDoS 攻击</h2><p>DDos(Distributed Denial of Service) 分布式拒绝服务。原理是利用大量的请求造成资源过载，导致服务不可用。DDoS 攻击从层次上可以分为网络层攻击和应用层攻击。</p><h3 id="网络层-DDoS">网络层 DDoS</h3><p>网络层 DDoS 攻击包括 SYN Flood、ACK Flood、UDP Flood、ICMP Flood 等。</p><ol><li>SYN Flood 攻击：主要利用 TCP 三次握手过程中存在的问题，TCP 三次握手过程是要建立连接的双方发送 SYN，SYN + ACK，ACK 数据包，攻击者构造 IP 去发送 SYN 包时，服务器返回的 SYN + ACK 就得不到应答，此时服务器会尝试重新发送，并且至少有 30s 的等待时间，导致资源和服务不可用。</li><li>ACK Flood 攻击：TCP 连接建立后，所有的数据传输 TCP 都是带有 ACK 标志的，主机收到 ACK 标志的数据包后，需要检查数据包状态合法性。当攻击程序每秒发送 ACK 的速率达到一定程度时，使主机和防火墙负载变大。</li><li>UDP Flood 攻击：当大量 UDP 数据包发送给受害系统时，可能会导致带宽饱和从而使得合法服务无法请求访问受害系统。</li><li>ICMP Flood 攻击：ICMP（互联网控制消息协议）洪水攻击是通过向未良好设置的路由器发送广播信息占用系统资源的做法。</li></ol><h3 id="应用层-DDoS">应用层 DDoS</h3><p>应用层 DDoS 攻击不是发生在网络层，是发生在 TCP 建立握手成功之后，应用程序处理请求的时候，现在很多常见的 DDoS 攻击都是应用层攻击。</p><ol><li>CC 攻击：Challenge Collapasar，就是针对消耗资源比较大的页面不断发起不正常的请求，导致资源耗尽。</li><li>DNS Flood：攻击者向服务器发送大量的域名解析请求，通常请求解析的域名是随机生成或者是网络世界上根本不存在的域名，域名解析的过程给服务器带来了很大的负载。</li><li>HTTP 慢连接攻击：针对 HTTP 协议，先建立起 HTTP 连接，设置一个较大的 Conetnt-Length，每次只发送很少的字节，让服务器一直以为 HTTP 头部没有传输完成，这样连接一多就很快会出现连接耗尽。</li></ol><h3 id="防御方式">防御方式</h3><ol><li>防火墙：通过设置防火墙规则，比如允许或者拒绝特点通讯协议、端口或者 IP 地址。</li><li>交换机：通过使用交换机的访问控制，比如限速、假 IP 过滤、流量整形，深度包检测等功能，可以检测并过滤拒绝服务攻击。</li><li>路由器：与交换机类似。</li><li>流量清洗：当流量被送到 DDoS 防护清洗中心时，通过采用抗 DDoS 软件处理，将正常流量与恶意流量区分。</li></ol><h2 id="参考资料">参考资料</h2><ul><li><a href="https://zh.wikipedia.org/zh-cn/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC">跨站脚本</a></li><li><a href="https://zoumiaojiang.com/article/common-web-security/">常见 Web 安全攻防总结</a></li><li><a href="https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0">跨站请求伪造</a></li><li><a href="https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A">拒绝服务攻击</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;去年一家专门做企业安全的公司来我们公司做测试和培训，经过他们一周多的测试，找到了公司多个项目中存在的很多问题，惊奇地发现，我们组的前端项目竟然没有发现一个漏洞。虽然没有找到并不代表没有，但是从中也能看出我们组在这方面还是有些实力的。而我对安全方面可以说是没有多少积累，最近抽时间学习一下 web 安全相关的知识。&lt;/p&gt;
&lt;h2 id=&quot;XSS&quot;&gt;XSS&lt;/h2&gt;
&lt;p&gt;XSS(Cross Site Script) 跨站脚本攻击，是攻击者利用网站漏洞在网站上注入恶意客户端代码，以获取访问权限，冒充用户，修改 HTML 内容等。恶意内容一般包括 JavaScript，主要方式是获取用户的隐私数据，例如 cookie，session 等。&lt;/p&gt;
&lt;p&gt;XSS 攻击可以分为 3 类：存储型、反射型、基于 DOM。&lt;/p&gt;</summary>
    
    
    
    <category term="安全" scheme="https://lz5z.com/categories/%E5%AE%89%E5%85%A8/"/>
    
    
    <category term="xss" scheme="https://lz5z.com/tags/xss/"/>
    
    <category term="csrf" scheme="https://lz5z.com/tags/csrf/"/>
    
  </entry>
  
  <entry>
    <title>再见2017，你好2018</title>
    <link href="https://lz5z.com/GoodBye2017-Hello2018/"/>
    <id>https://lz5z.com/GoodBye2017-Hello2018/</id>
    <published>2018-02-11T12:11:42.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="2017">2017</h2><p>好久没有更新博客了。</p><p>2017年过得真快，转眼已经快触不到2017的尾巴了，如果算农历年的话，留给它的时间也已经不多了。</p><p>2017年对于我来说发生的最重要的事情就是跳槽了，从 OOCL 离职，到入职 WPS 正好一年了。这一年可以说是我职业发展最为重要的一年，以后应该都会在这个方向前行了。这一年差不多是我从门外汉逐步入门的过程，虽然之前也有一两年的工作经验，但大多时候是打酱油，在一个大的项目中缝缝补补。而经过 WPS 一年的训练，如今我可以写一些小的项目，也完全看懂了部门的大项目的整个架构。</p><p>第二件事就是今年八月份买了人生第一辆小车车-日产骐达。这辆小车车如今已经是我们家中第三重要的成员了，给生活提升了极大的幸福感。尤其是搬家的时候，身心俱疲，但是当进入小车的一瞬间，知道自己无论如何有落脚的地方，就觉得很安心。</p><span id="more"></span><p>第三件事就是公司搬家，下面是我离开旧金山时候拍的照片。</p><p>旧金山大楼：</p><img src="/assets/img/旧金山大楼.jpg" alt="旧金山" width="60%"><p>这是旧金山的工位，呆了差不多10个月：</p><img src="/assets/img/旧金山工位.jpg" alt="旧金山工位" width="60%"><p>下面是刚搬到新办公室的照片，一台 PC，一台 mac mini，两台电脑都很卡。</p><img src="/assets/img/新工位.jpg" alt="新工位" width="60%"><p>后面自己买了 2k 32 寸的显示器，公司又给升级了最新的 PC - DELL 的高配 7050 + 三星 SSD，现在不再抱怨电脑卡了。新升级设备后给自己加了好多天的班，用流畅舒服的设备敲代码让人欲罢不能啊。（老板听到后请给我加薪）</p><img src="/assets/img/新工位2.jpg" alt="新工位2" width="60%"><p>还有一件事就是部门给了优秀员工奖，虽然没有实质性的奖励，但是可以看到 leader 予以的器重，所以还是很开心的。年终考评时，前端负责人也给了很不错的评价，说以后会让我负责更多的东西，多给我成长的机会。</p><h2 id="2018">2018</h2><p>2018年又长了一岁了，最重要的当然是结婚的事情提上了日程，以前别人问怎么还不结婚的时候，总是答，人家还小呢。但是这几年越发觉得自己其实已是一个油腻的中年人了，上学时本来就比周围的人大，一直被称为 “振哥”，工作以后发现同等年纪的人已经工作三四年了，越发焦急，觉得自己一事无成，马齿徒增。大学毕业的时候选择工作而没有像别的同学那样读研也是同样的原因。而明年的结婚算是给自己和家人一个交代，完成了人生的一件大事。</p><p>2018年当然是希望自己技术越来好，能承担越来越多的责任。当然最重要的是，要赚更多的钱，家人都健康快乐。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;2017&quot;&gt;2017&lt;/h2&gt;
&lt;p&gt;好久没有更新博客了。&lt;/p&gt;
&lt;p&gt;2017年过得真快，转眼已经快触不到2017的尾巴了，如果算农历年的话，留给它的时间也已经不多了。&lt;/p&gt;
&lt;p&gt;2017年对于我来说发生的最重要的事情就是跳槽了，从 OOCL 离职，到入职 WPS 正好一年了。这一年可以说是我职业发展最为重要的一年，以后应该都会在这个方向前行了。这一年差不多是我从门外汉逐步入门的过程，虽然之前也有一两年的工作经验，但大多时候是打酱油，在一个大的项目中缝缝补补。而经过 WPS 一年的训练，如今我可以写一些小的项目，也完全看懂了部门的大项目的整个架构。&lt;/p&gt;
&lt;p&gt;第二件事就是今年八月份买了人生第一辆小车车-日产骐达。这辆小车车如今已经是我们家中第三重要的成员了，给生活提升了极大的幸福感。尤其是搬家的时候，身心俱疲，但是当进入小车的一瞬间，知道自己无论如何有落脚的地方，就觉得很安心。&lt;/p&gt;</summary>
    
    
    
    <category term="Others" scheme="https://lz5z.com/categories/Others/"/>
    
    
    <category term="总结" scheme="https://lz5z.com/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>webpack 打包加速实战</title>
    <link href="https://lz5z.com/webpack%E6%89%93%E5%8C%85%E5%8A%A0%E9%80%9F%E5%AE%9E%E6%88%98/"/>
    <id>https://lz5z.com/webpack%E6%89%93%E5%8C%85%E5%8A%A0%E9%80%9F%E5%AE%9E%E6%88%98/</id>
    <published>2018-01-13T01:05:31.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<h2 id="webpack-打包优化">webpack 打包优化</h2><p>最近项目不算忙，抽时间重构了一下项目的打包，先说一下成就。</p><p>在我的开发电脑上：</p><p>OS: macOS High Sierra<br>CPU: 2.6 GHz Intel Core i5<br>内存: 8G 1600 DDR3<br>硬盘: 1 TB SATA磁盘</p><p>代码全量编译时间从 4 分 51 秒优化到 2 分 08 - 20 秒左右。</p><p>在项目编译电脑上：</p><p>OS: Ubuntu 16.04.3 LTS<br>CPU: Intel® Core™ i5-7500 CPU @ 3.40GHz<br>内存: 64G 2133 DDR4<br>硬盘: 1 TB SSD</p><p>代码全量编译时间从 4 分 08 秒优化到 1 分 10 - 20 秒左右。</p><span id="more"></span><h2 id="用了哪些手段">用了哪些手段</h2><h3 id="升级电脑">升级电脑</h3><p>升级 SSD 可能是提升效果最明显的吧，从上面两组数据中就可以看出。相同的优化在 SSD 中表现要明显很多。</p><h3 id="升级-webpack3">升级 webpack3</h3><p><a href="https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b">webpack 3: Official Release!!</a></p><p>如果你的项目还在用 webpack2 的话，强烈建议你升级到 webpack3。webpack3 向下兼容，只不过有一些插件需要同时升级，注意看控制台给出的日志，把需要升级的一起升级了就好了。</p><h4 id="Scope-Hoisting-作用域提升">Scope Hoisting-作用域提升</h4><p>webpack 打包的时候，每个模块都被一个闭包函数包裹，过多的闭包函数降低了浏览器中 JS 执行效率，Scope Hoisting 的作用是减少闭包函数的数量，将有关联的模块放到同一个闭包函数中。</p><p>启用方法</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">  <span class="keyword">new</span> webpack.<span class="property">optimize</span>.<span class="title class_">ModuleConcatenationPlugin</span>()</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Scope Hoisting 是基于 ECMAScript Module syntax ，对于 Commonjs 和 AMD 的模块不适用。</p><p>上面升级的算是副本，下面才是正文。</p><h2 id="正文开始">正文开始</h2><p>现在开发的项目算是比较大的项目，严格来说，是多个 SPA 组成的多项目。这样做的好处是能减少架构师的工作，同一份架构给多个项目使用，能保证项目稳定性。坏处也比较明显，就是会额外引入无用的依赖，比如共用的 helper 模块，很多项目都引用了，但是并不是每个项目都使用里面的每个函数。这点 tree-shaking 可以给出解决方案，但是实际开发过程中，由于同事们代码质量参差不齐，有些没用到的函数和模块也都引用了，所以导致 tree-shaking 的效果并不是很好。比如在大项目中，同事把几个 helper 里面函数全部封装到 vue-filter 中，当然里面的内容主要项目大多数都引用到了，但是后面同事在初始化一个小项目的同时，无论是否需要也都用了相同的代码（copy and paste）。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> helpers <span class="keyword">from</span> <span class="string">&#x27;helpers&#x27;</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> helpers2 <span class="keyword">from</span> <span class="string">&#x27;helpers/string&#x27;</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> helpers3 <span class="keyword">from</span> <span class="string">&#x27;helpers/...&#x27;</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> helpers4 <span class="keyword">from</span> <span class="string">&#x27;helpers/...&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// register global utility filters.</span></span><br><span class="line"><span class="keyword">let</span> _filters = <span class="title class_">Object</span>.<span class="title function_">assign</span>(helpers, helpers2, helpers3, helpers4)</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(_filters).<span class="title function_">forEach</span>(<span class="function"><span class="params">key</span> =&gt;</span> <span class="title class_">Vue</span>.<span class="title function_">filter</span>(key, _filters[key]))</span><br></pre></td></tr></table></figure><p>于是 helper 中每个 function 都挂载在 Vue-filter 中，所以完美的避开了 tree-shaking。</p><p>另外 tree-shaking 虽然能够一定程度的减少打包后代码的体积，但是开发和编译的速度还是会受到一定的影响。</p><p>下面是代码打包速度优化的一些思路，多数来源于网上的资料。</p><h3 id="commonChunkPlugin-抽取公共代码">commonChunkPlugin 抽取公共代码</h3><p>抽取公共代码有两个好处，一个是能减少编译代码的数量，一个是能够充分利用浏览器缓存，比如遇到项目切换的情况，使用 service-worker 中缓存共用的 common 代码能够减少请求的数量。</p><p>以下是 vue-cli 中给出的解决方案</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// split vendor js into its own file</span></span><br><span class="line"><span class="keyword">new</span> webpack.<span class="property">optimize</span>.<span class="title class_">CommonsChunkPlugin</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;vendor&#x27;</span>,</span><br><span class="line">  <span class="title function_">minChunks</span>(<span class="params"><span class="variable language_">module</span></span>) &#123;</span><br><span class="line">    <span class="comment">// any required modules inside node_modules are extracted to vendor</span></span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="variable language_">module</span>.<span class="property">resource</span> &amp;&amp;</span><br><span class="line">      <span class="regexp">/\.js$/</span>.<span class="title function_">test</span>(<span class="variable language_">module</span>.<span class="property">resource</span>) &amp;&amp;</span><br><span class="line">      <span class="variable language_">module</span>.<span class="property">resource</span>.<span class="title function_">indexOf</span>(</span><br><span class="line">        path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;../node_modules&#x27;</span>)</span><br><span class="line">      ) === <span class="number">0</span></span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;),</span><br><span class="line"><span class="comment">// extract webpack runtime and module manifest to its own file in order to</span></span><br><span class="line"><span class="comment">// prevent vendor hash from being updated whenever app bundle is updated</span></span><br><span class="line"><span class="keyword">new</span> webpack.<span class="property">optimize</span>.<span class="title class_">CommonsChunkPlugin</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;manifest&#x27;</span>,</span><br><span class="line">  <span class="attr">minChunks</span>: <span class="title class_">Infinity</span></span><br><span class="line">&#125;),</span><br><span class="line"><span class="comment">// This instance extracts shared chunks from code splitted chunks and bundles them</span></span><br><span class="line"><span class="comment">// in a separate chunk, similar to the vendor chunk</span></span><br><span class="line"><span class="comment">// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk</span></span><br><span class="line"><span class="keyword">new</span> webpack.<span class="property">optimize</span>.<span class="title class_">CommonsChunkPlugin</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;app&#x27;</span>,</span><br><span class="line">  <span class="attr">async</span>: <span class="string">&#x27;vendor-async&#x27;</span>,</span><br><span class="line">  <span class="attr">children</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">minChunks</span>: <span class="number">3</span></span><br><span class="line">&#125;),</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="DLL-预编译">DLL 预编译</h3><p>DLL 预编译的作用是将项目中稳定的依赖单独打包编译生成动态链接库，在业务代码中引用。这点在开发过程中优势比较明显，每次更新代码重新编译的时候都能够省去 DLL 库的编译，有不小的速度提升。</p><p>DLL 需要有一个额外的打包过程，新建一个 webpck.dll.conf.js 用来打包 DLL，并且在 package.json 中添加打包过程。</p><p>package.json</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;scripts&quot;</span>: &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="string">&quot;build&quot;</span>: <span class="string">&quot;rimraf dist &amp;&amp; npm run dll &amp;&amp; npm run build:server &amp;&amp; npm run build:client&quot;</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="string">&quot;dll&quot;</span>: <span class="string">&quot;cross-env NODE_ENV=dll node build&quot;</span>,</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>webpack.dll.conf.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">&#x27;webpack&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">UglifyJsPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;uglifyjs-webpack-plugin&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> os = <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> vendors = [</span><br><span class="line">  <span class="string">&#x27;babel-polyfill&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;es6-promise&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;vue/dist/vue.esm.js&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;vue-router&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;vuex&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;vuex-router-sync&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;axios&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;cookie&#x27;</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;../dist&#x27;</span>),</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&#x27;[name].dll.js&#x27;</span>,</span><br><span class="line">    <span class="attr">library</span>: <span class="string">&#x27;[name]_[hash]&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="string">&#x27;vendor&#x27;</span>: vendors,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">UglifyJsPlugin</span>(&#123;</span><br><span class="line">      ... <span class="comment">// 压缩参数略</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> webpack.<span class="title class_">DllPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">context</span>: __dirname,</span><br><span class="line">      <span class="attr">path</span>: path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;../dist&#x27;</span>, <span class="string">&#x27;[name]-manifest.json&#x27;</span>),</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;[name]_[hash]&#x27;</span>,</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>通过运行 <code>npm run dll</code> 在 dist 目录下生成了两个文件 vendor.dll.js 和 vendor.manifest.json。其中 vendor.dll.js 中是打包压缩后的 vendor 代码，vendor.manifest.json 是 vendor 文件的 node_modle 路径和 webpack 打包 id 的映射。</p><p>然后通过 DllReferencePlugin 将 vendor 引入业务代码。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这里将生成的 vendor.dll.js 文件 copy 到 你需要的目录 </span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">CopyWebpackPlugin</span>([&#123;</span><br><span class="line">  <span class="attr">from</span>: <span class="string">&#x27;dist/vendor.dll.js&#x27;</span>,</span><br><span class="line">  <span class="attr">to</span>: config.<span class="property">build</span>.<span class="property">assetsSubDirectory</span>,</span><br><span class="line">  <span class="attr">flatten</span>: <span class="literal">true</span></span><br><span class="line">&#125;]),</span><br><span class="line"><span class="keyword">new</span> webpack.<span class="title class_">DllReferencePlugin</span>(&#123;</span><br><span class="line">  <span class="attr">context</span>: __dirname,</span><br><span class="line">  <span class="attr">manifest</span>: <span class="built_in">require</span>(<span class="string">&#x27;../dist/vendor-manifest.json&#x27;</span>)</span><br><span class="line">&#125;),</span><br></pre></td></tr></table></figure><p>最后还需要在 html 中引入生成的 DLL，网上有一些教程是直接把 script 标签写入 html 中的，但是由于我们多个项目同时依赖同一份 html 模板，其中某一些项目并不需要引入 DLL，比如一些静态页面。于是使用 html-webpack-include-assets-plugin 实现按需加载。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">...pkgs.<span class="title function_">reduce</span>(<span class="function">(<span class="params">pre, current</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> res = [<span class="keyword">new</span> <span class="title class_">HtmlWebpackPlugin</span>(current.<span class="property">plugin</span>)]</span><br><span class="line">  <span class="keyword">let</span> &#123;assets, filename&#125; = current.<span class="property">plugin</span> || &#123;&#125;</span><br><span class="line">  <span class="keyword">if</span> (pre) res = [...pre, ...res]</span><br><span class="line">  <span class="keyword">if</span> (assets) &#123;</span><br><span class="line">    <span class="keyword">return</span> [...res, <span class="keyword">new</span> <span class="title class_">HtmlWebpackIncludeAssetsPlugin</span>(&#123;</span><br><span class="line">    <span class="attr">files</span>: [filename],</span><br><span class="line">    <span class="attr">assets</span>: assets.<span class="title function_">map</span>(<span class="function"><span class="params">item</span> =&gt;</span> <span class="string">`<span class="subst">$&#123;assetsSubDirectory&#125;</span>/<span class="subst">$&#123;item&#125;</span>`</span>),</span><br><span class="line">    <span class="attr">append</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">publicPath</span>: assetsPublicPath</span><br><span class="line">    &#125;)]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> res</span><br><span class="line">&#125;, <span class="literal">null</span>),</span><br></pre></td></tr></table></figure><p>在 pkgs 中控制 HtmlWebpackPlugin 的参数，和是否需要引入 vendor.dll.js。</p><p>pkg 模板如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> extChunks = <span class="variable constant_">IS_PROD</span> ? [<span class="string">&#x27;manifest&#x27;</span>] : []</span><br><span class="line"><span class="keyword">const</span> chunksSortMode = <span class="variable constant_">IS_PROD</span> ? <span class="string">&#x27;dependency&#x27;</span> : <span class="string">&#x27;auto&#x27;</span></span><br><span class="line"><span class="keyword">const</span> template = <span class="string">&#x27;static/templates/index.pug&#x27;</span></span><br><span class="line"><span class="keyword">const</span> minfiy = &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span> pkg = &#123;</span><br><span class="line">  <span class="string">&#x27;index&#x27;</span>: &#123;</span><br><span class="line">    <span class="attr">buddle</span>: <span class="string">&#x27;server-index-bundle.js&#x27;</span>,</span><br><span class="line">    <span class="attr">entry</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;...省略路径../entry.js&#x27;</span>),</span><br><span class="line">    <span class="attr">plugin</span>: &#123;</span><br><span class="line">      <span class="attr">filename</span>: <span class="string">&#x27;app/index.html&#x27;</span>,</span><br><span class="line">      <span class="attr">chunks</span>: [<span class="string">&#x27;app&#x27;</span>, <span class="string">&#x27;vendor&#x27;</span>, <span class="string">&#x27;common&#x27;</span>, ...extChunks],</span><br><span class="line">      <span class="attr">inject</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">assets</span>: [<span class="string">&#x27;vendor.dll.js&#x27;</span>],</span><br><span class="line">      chunksSortMode,</span><br><span class="line">      template,</span><br><span class="line">      minify</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 如果没有额外的依赖 assets 不用传</span></span><br><span class="line"> <span class="string">&#x27;404&#x27;</span>: &#123;</span><br><span class="line">    <span class="attr">plugin</span>: &#123;</span><br><span class="line">      <span class="attr">filename</span>: <span class="string">&#x27;404.html&#x27;</span>,</span><br><span class="line">      <span class="attr">chunks</span>: [<span class="string">&#x27;exception&#x27;</span>],</span><br><span class="line">      <span class="attr">inject</span>: <span class="literal">true</span>,</span><br><span class="line">      chunksSortMode,</span><br><span class="line">      <span class="attr">template</span>: <span class="string">&#x27;static/404.pug&#x27;</span>,</span><br><span class="line">      minify</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.js 和 vendor.js 体积缩小，但是项目总体积略有增大。因为通过 DLL 的方式，额外存储了外部依赖的路径和 ID。</p><h3 id="alias-减少搜索路径">alias 减少搜索路径</h3><p>这点想必大家都知道</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">resolve</span>: &#123;</span><br><span class="line">    <span class="attr">alias</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;vue$&#x27;</span>: <span class="string">&#x27;vue/dist/vue.esm.js&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;static&#x27;</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;../static&#x27;</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 可以用引用 node_modules 里面的方法引用 src 下面的模块</span></span><br><span class="line">  <span class="attr">modules</span>: [path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;../src&#x27;</span>), <span class="string">&quot;node_modules&quot;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="多线程加速">多线程加速</h3><p>（1） uglifyjs-webpack-plugin 多线程提示 JS 压缩效率</p><p>使用 <a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin">uglifyjs-webpack-plugin</a> 不仅可以加速 webpack 压缩 js 代码的速度，还能与 <a href="https://doc.webpack-china.org/guides/tree-shaking/">webpack tree-shaking</a> 配合，减少代码体积。webpack 本身并不会执行 tree-shaking。它需要依赖于像 UglifyJS 这样的第三方工具来执行实际的未引用代码(dead code)删除工作。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">UglifyJsParallelPlugin</span>(&#123;</span><br><span class="line">  <span class="attr">uglifyOptions</span>: &#123;</span><br><span class="line">    <span class="attr">ecma</span>: <span class="number">8</span>,</span><br><span class="line">    <span class="attr">mangle</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">      <span class="attr">beautify</span>: <span class="literal">false</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">compress</span>: &#123;</span><br><span class="line">      <span class="attr">drop_console</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">sourceMap</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="attr">cache</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">parallel</span>: os.<span class="title function_">cpus</span>().<span class="property">length</span> * <span class="number">2</span>,</span><br><span class="line">  <span class="attr">exclude</span>: <span class="regexp">/\.min\.js$/</span></span><br><span class="line">&#125;),</span><br></pre></td></tr></table></figure><p>记得开启缓存，能有效提升打包效率。</p><p>（2） happypack 多线程提升 loader 执行效率。</p><p>使用 happypack 之前，你可以先去 <a href="https://github.com/amireh/happypack/wiki/Loader-Compatibility-List">Loader Compatibility List</a> 看一下 happypack 的兼容性列表。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> os = <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HappyPack</span> = <span class="built_in">require</span>(<span class="string">&#x27;happypack&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> happyThreadPool = <span class="title class_">HappyPack</span>.<span class="title class_">ThreadPool</span>(&#123; <span class="attr">size</span>: os.<span class="title function_">cpus</span>().<span class="property">length</span> &#125;)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">      <span class="attr">use</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">loader</span>: <span class="string">&#x27;cache-loader&#x27;</span>, <span class="comment">// 使用 cache-loader 缓存</span></span><br><span class="line">          <span class="attr">options</span>: &#123;</span><br><span class="line">          <span class="attr">cacheDirectory</span>: <span class="title function_">resolve</span>(<span class="string">&#x27;node_modules/.cache&#x27;</span>)</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">&#x27;happypack/loader?id=babel&#x27;</span> <span class="comment">// 将 babel loader替换为 happypack</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span>,</span><br><span class="line">        <span class="attr">include</span>: [</span><br><span class="line">          <span class="title function_">resolve</span>(<span class="string">&#x27;src&#x27;</span>), <span class="title function_">resolve</span>(<span class="string">&#x27;test&#x27;</span>)</span><br><span class="line">       ]</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">  <span class="comment">// 使用 happypack 插件</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HappyPack</span>(&#123; </span><br><span class="line">  <span class="attr">id</span>: <span class="string">&#x27;babel&#x27;</span>,</span><br><span class="line">  <span class="attr">threadPool</span>: happyThreadPool,</span><br><span class="line">  <span class="attr">verbose</span>: <span class="literal">false</span>,</span><br><span class="line">      <span class="attr">loaders</span>: [&#123;</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">`babel-loader`</span></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>使用 happypack 后，性能比较差的 mac mini 速度反而降低了一些，但是性能比较强的编译机速度有不少的提升，所以 happypack 可以酌情使用，测试后发现速度有提升再加入，没有提升就果断弃用。</p><h3 id="缓存-HardSourceWebpackPlugin">缓存 HardSourceWebpackPlugin</h3><p><a href="https://github.com/mzgoddard/hard-source-webpack-plugin">hard-source-webpack-plugin</a> 也是利用缓存效果提升打包速度。</p><blockquote><p>HardSourceWebpackPlugin is a plugin for webpack to provide an intermediate caching step for modules.</p></blockquote><p>用法很简单</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">HardSourceWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;hard-source-webpack-plugin&#x27;</span>)</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HardSourceWebpackPlugin</span>()</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="dev-优化">dev 优化</h2><p>开发的时候，使用 koa-webpack-middleware 的 devMiddleware, hotMiddleware 两个中间件是提供 dev 服务和代码热新服务，devMiddleware 本质上是对 webpack-dev-middleware 的一层封装，而 hotMiddleware 是对 webpack-hot-middleware 的一层封装。</p><p>开发过程中，所有的代码均被载入两个 webpack 服务中，因此有一丁点的代码改动都需要重新编译所有的 buddle，这对开发过程是极其不好的体验，因此划分代码依赖，通过 npm 参数编译不同的项目，来达到加速开发的效果。</p><p>比如使用 <code>npm run dev project1</code> 来开发项目 project1，而其它代码并不加载到 webpack 中。</p><p>拿到 project1 参数可以通过 node.js 的 process 对象</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="keyword">let</span> projects = process.<span class="property">argv</span>.<span class="title function_">slice</span>(<span class="number">2</span>)</span><br><span class="line"><span class="keyword">if</span> (!!projects &amp;&amp; projects.<span class="property">length</span>) &#123;</span><br><span class="line">  <span class="comment">// filter your entry</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结">总结</h2><p>以上打包优化都是参考网上的一些东西， 在实际使用过程中，发现有些文章内容是写了，但是并没有亲身实践，有些错误或者不完善的地方甚至都是一模一样的，所以自己结合实际项目走了一遍流程后，还是决定把东西写出来，希望对看到的人有帮助。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;webpack-打包优化&quot;&gt;webpack 打包优化&lt;/h2&gt;
&lt;p&gt;最近项目不算忙，抽时间重构了一下项目的打包，先说一下成就。&lt;/p&gt;
&lt;p&gt;在我的开发电脑上：&lt;/p&gt;
&lt;p&gt;OS: macOS High Sierra&lt;br&gt;
CPU: 2.6 GHz Intel Core i5&lt;br&gt;
内存: 8G 1600 DDR3&lt;br&gt;
硬盘: 1 TB SATA磁盘&lt;/p&gt;
&lt;p&gt;代码全量编译时间从 4 分 51 秒优化到 2 分 08 - 20 秒左右。&lt;/p&gt;
&lt;p&gt;在项目编译电脑上：&lt;/p&gt;
&lt;p&gt;OS: Ubuntu 16.04.3 LTS&lt;br&gt;
CPU: Intel® Core™ i5-7500 CPU @ 3.40GHz&lt;br&gt;
内存: 64G 2133 DDR4&lt;br&gt;
硬盘: 1 TB SSD&lt;/p&gt;
&lt;p&gt;代码全量编译时间从 4 分 08 秒优化到 1 分 10 - 20 秒左右。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="webpack" scheme="https://lz5z.com/tags/webpack/"/>
    
    <category term="dll" scheme="https://lz5z.com/tags/dll/"/>
    
  </entry>
  
  <entry>
    <title>常用命令</title>
    <link href="https://lz5z.com/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/"/>
    <id>https://lz5z.com/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</id>
    <published>2017-12-20T00:56:26.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<p>记录一些常用的系统或者软件命令</p><h2 id="kill-port">kill port</h2><p>linux</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看端口占用</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">lsof -i :8080</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">结束进程</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> <span class="built_in">kill</span> -9 [pid]</span></span><br></pre></td></tr></table></figure><p>windows</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看端口占用</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">netstat -aon | findstr 8080</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看 pid 进程</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">tasklist | findstr 19516</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">结束进程</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">taskkill /pid 19516 /F</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">或者使用进程名字</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">taskkill -F -IM node.exe</span></span><br></pre></td></tr></table></figure><span id="more"></span><h2 id="定时关机">定时关机</h2><p>linux</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">shutdown</span><br><span class="line">有如下选项：</span><br><span class="line">- k     =&gt;不执行任何关机操作，只发出警告信息给所有用户</span><br><span class="line">- r     =&gt; 重新启动计算机</span><br><span class="line">- h    =&gt; 关机并彻底断电</span><br><span class="line">- f     =&gt;快速关机且重启动时跳过fsck</span><br><span class="line">- n    =&gt;快速关机不经过init程序</span><br><span class="line">- c    =&gt; 取消之前的定时关机</span><br><span class="line">立即关机：shutdown -h now</span><br><span class="line">立即重启：shutdown -r now</span><br><span class="line">注意：now 不能省略</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">shutdown -h +10 <span class="comment"># 10分钟后自动关机</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">shutdown -h 10:00 <span class="comment"># 10点整关机</span></span></span><br></pre></td></tr></table></figure><p>windows</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">shutdown</span><br><span class="line">-a     =&gt;取消关机</span><br><span class="line">-s 关机</span><br><span class="line">-f     =&gt;强行关闭应用程序</span><br><span class="line">-l     =&gt;注销当前用户</span><br><span class="line">-r     =&gt;关机并重启</span><br><span class="line">-s -t 时间     =&gt;设置关机倒计时</span><br><span class="line">-h 休眠</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">shutdown -s -t 3600  <span class="comment"># 一个小时候自动关机</span></span></span><br></pre></td></tr></table></figure><h2 id="刷新-dns-hosts">刷新 dns hosts</h2><p>windows</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ipconfig /flushdns</span></span><br></pre></td></tr></table></figure><h2 id="Git">Git</h2><h3 id="config">config</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">显示当前的 Git 配置</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git config --list</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">编辑Git配置文件</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git config -e [--global]</span> </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置提交代码时的用户信息</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git config [--global] user.name <span class="string">&quot;[name]&quot;</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git config [--global] user.email <span class="string">&quot;[email address]&quot;</span></span></span><br></pre></td></tr></table></figure><h3 id="add">add</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git add -A (stage all files: new, modified, deleted)</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git add .  (stage files: new, modified, deleted[version 2])</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git add -u (stage files: modified, deleted)</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git add --ignore-removal . (stage files: new, modified)</span></span><br></pre></td></tr></table></figure><h3 id="commit">commit</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git commit -m [message]</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git commit [file1][file2]... -m [message] <span class="comment"># file 要包含路径</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">amend 使用一次新的 commit，替代上一次提交</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">如果代码没有任何新变化，则用来改写上一次 commit 的提交信息</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git commit --amend -m [message]</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">重做上一次commit，并包括指定文件的新变化</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git commit --amend [file1][file2]... -m[message]</span></span><br></pre></td></tr></table></figure><h3 id="pull">pull</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">取回远程仓库的变化，并与本地分支合并</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git pull [remote] [branch]</span></span><br></pre></td></tr></table></figure><h3 id="push">push</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">上传本地指定分支到远程仓库</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git push [remote] [branch]</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">强行推送当前分支到远程仓库，即使有冲突</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git push [remote] --force</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">推送所有分支到远程仓库</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git push [remote] --all</span></span><br></pre></td></tr></table></figure><h3 id="branch">branch</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出所有本地分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git branch</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出所有远程分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git branch -r</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出所有本地分支和远程分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git branch -a</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">新建一个分支，但依然停留在当前分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git branch [branch-name]</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">新建一个分支，并切换到该分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git checkout -b [branch]</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">切换到指定分支，并更新工作区</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git checkout [branch-name]</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">新建一个分支，与指定的远程分支建立追踪关系</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git branch --track [branch] [remote-branch]</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git branch -d [branch-name]</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除远程分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git push origin --delete [branch-name]</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git branch -dr [remote/branch]</span></span><br></pre></td></tr></table></figure><h3 id="cherry-pick">cherry-pick</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">选择一个commit，合并进当前分支</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git cherry-pick [commit <span class="built_in">id</span>]</span></span><br></pre></td></tr></table></figure><h3 id="remote">remote</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">显示所有远程仓库</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git remote -v</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">增加一个新的远程仓库</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git remote add [shortname] [url]</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;记录一些常用的系统或者软件命令&lt;/p&gt;
&lt;h2 id=&quot;kill-port&quot;&gt;kill port&lt;/h2&gt;
&lt;p&gt;linux&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;查看端口占用&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;lsof -i :8080&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;结束进程&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;kill&lt;/span&gt; -9 [pid]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;windows&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;查看端口占用&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;netstat -aon | findstr 8080&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;查看 pid 进程&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;tasklist | findstr 19516&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;结束进程&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;taskkill /pid 19516 /F&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;或者使用进程名字&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;taskkill -F -IM node.exe&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="Tools" scheme="https://lz5z.com/categories/Tools/"/>
    
    
    <category term="linux" scheme="https://lz5z.com/tags/linux/"/>
    
    <category term="windows" scheme="https://lz5z.com/tags/windows/"/>
    
    <category term="git" scheme="https://lz5z.com/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>CSS 伪元素技巧</title>
    <link href="https://lz5z.com/CSS%E4%BC%AA%E5%85%83%E7%B4%A0%E6%8A%80%E5%B7%A7/"/>
    <id>https://lz5z.com/CSS%E4%BC%AA%E5%85%83%E7%B4%A0%E6%8A%80%E5%B7%A7/</id>
    <published>2017-12-08T12:08:00.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<h2 id="伪元素技巧">伪元素技巧</h2><p>在 <a href="https://lz5z.com/CSS%E4%BC%AA%E5%85%83%E7%B4%A0%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/">CSS 伪元素基本用法</a>一文中讲述了伪元素的基础功能，本章学习一些进阶功能，看看伪元素能实现哪些方便好用的功能。</p><h3 id="清除浮动">清除浮动</h3><p>如果一个元素内部的子元素全部都是浮动的话，那么这个元素会出现高度塌陷，这个时候就需要清除浮动。高度塌陷的负面作用主要有：不能正确显示背景，边框不能撑开，margin 和 padding 不能正确显示。</p><p>假设有代码如下：</p><span id="more"></span><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;outer&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;inner&quot;</span>&gt;</span>1<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;inner&quot;</span>&gt;</span>2<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;inner&quot;</span>&gt;</span>3<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.outer</span> &#123;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#ccc</span>;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">10px</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">5px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.inner</span> &#123;</span><br><span class="line">  <span class="attribute">float</span>: left;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">80px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">80px</span>;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid;</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">.outer</span> &#123;</span><br><span class="line">  <span class="attribute">zoom</span>: <span class="number">1</span>; // IE6/<span class="number">7</span> 兼容性</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.outer</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">  <span class="attribute">display</span>: block;</span><br><span class="line">  <span class="attribute">clear</span>: both;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其它清除浮动的办法：</p><p>（1）给父元素设置高度。<br>（2）<code>clear: both</code> 清除浮动。</p><p>常见的用法是在父元素结束之前，统一引入一个元素 <code>clear: both</code> 用来清除浮动。</p><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;outer&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;inner&quot;</span>&gt;</span>1<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;inner&quot;</span>&gt;</span>2<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;inner&quot;</span>&gt;</span>3<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;clear&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.clear</span> &#123;</span><br><span class="line">  <span class="attribute">clear</span>: both;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方法实现起来很简单，不过缺点也很明显，引入了额外的 DOM 元素。</p><p>clear 属性可以对应的属性值有：</p><ul><li>left  在左侧不允许浮动元素。</li><li>right  在右侧不允许浮动元素。</li><li>both  在左右两侧均不允许浮动元素。</li><li>none  默认值。允许浮动元素出现在两侧。</li><li>inherit  规定应该从父元素继承 clear 属性的值。</li></ul><p>（3）给父级元素定义 <code>overflow: auto</code> 或者 <code>overflow: hidden</code></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.outer</span> &#123;</span><br><span class="line">  <span class="attribute">overflow</span>: auto;</span><br><span class="line">  <span class="attribute">zoom</span>: <span class="number">1</span>; // IE6/<span class="number">7</span> 兼容性</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用 overflow 属性来清除浮动只可以使用 hiddent 和 auto 不能使用 visible。 为了兼容 IE 最好用 <code>overflow:hidden</code>，缺点是元素会被截断。</p><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><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">// 全浏览器通用的 clearfix 方案</span><br><span class="line">// 引入了 <span class="attribute">zoom</span> 以支持 IE6/<span class="number">7</span></span><br><span class="line">// 同时加入 <span class="selector-pseudo">:before</span> 以解决现代浏览器上边距折叠的问题</span><br><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: table;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot; &quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">clear</span>: both;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.clearfix</span> &#123;</span><br><span class="line">  <span class="attribute">zoom</span>: <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="扩大可点击范围">扩大可点击范围</h3><p>这点在移动端开发显得尤为重要，可以增强用户体验。</p><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;btn&quot;</span>&gt;</span>click<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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><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="selector-class">.btn</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.btn</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: -<span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">right</span>: -<span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">bottom</span>: -<span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">left</span>: -<span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还有一种不使用伪元素扩大可点击范围的方式是使用 border + background-clip</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.btn</span> &#123;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">20px</span> solid transparent;</span><br><span class="line">  <span class="attribute">background-clip</span>: padding-box;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="实现分割线效果">实现分割线效果</h3><!DOCTYPE html><html lang="en"><head>  <style>  .divide {    width: 100%;    text-align: center;  }  .divide:before, .divide:after {    content: "";    position: absolute;    margin: 14px 14px 10px 10px;    height: 1px;    width: calc(50% - 75px);    background-color: red;    padding: 0 10px;  }  .divide:before {    left: 0;  }  .divide:after {    right: 0;  }  </style></head><body>  <p class="divide">我是分割线</p></body></html><p>实现方式</p><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;divide&quot;</span>&gt;</span>我是分割线<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.divide</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">text-align</span>: center;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.divide</span><span class="selector-pseudo">:before</span>, <span class="selector-class">.divide</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">14px</span> <span class="number">14px</span> <span class="number">10px</span> <span class="number">10px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">1px</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="number">50%</span> - <span class="number">75px</span>);</span><br><span class="line">  <span class="attribute">background-color</span>: red;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">10px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.divide</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.divide</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">right</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="调用元素属性">调用元素属性</h3><p>通过在 content 中使用 attr 函数可以调用元素的属性。</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></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">a</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">attr</span>(href);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="引用媒体资源">引用媒体资源</h3><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></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">a</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">url</span>(<span class="string">https://lz5z.com/assets/img/avatar.svg</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="计数器-counter">计数器 counter</h3><ol><li>counter-reset：创建或者重置一个计数器</li><li>counter-increment：计数器递增</li><li>content：配合伪元素插入内容</li></ol><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ol</span> <span class="attr">class</span>=<span class="string">&quot;sites&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;checkbox&quot;</span>&gt;</span>Apple<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;checkbox&quot;</span>&gt;</span>Google<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;checkbox&quot;</span>&gt;</span>Amazon<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;checkbox&quot;</span>&gt;</span>Facebook<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>一共选择了<span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;count&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span>个网站<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.sites</span> &#123;</span><br><span class="line">  <span class="attribute">counter-reset</span>: site;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.sites</span> <span class="selector-tag">input</span><span class="selector-pseudo">:checked</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: site;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.count</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(site);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自制-checked-样式">自制 checked 样式</h3><!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>Document</title>  <style>  #sites li {    margin: 0 auto;    width: 30px;    font-size: 14px;    line-height: 1.5;    cursor: pointer;  }    #sites li:before {    color: #7cfc00;    background: #fff;    border: 2px solid #d3d3d3;    content: " ";    width: 16px;    height: 16px;    line-height: 1;    margin-left: -38px;    position: absolute;    text-align: center;    vertical-align: middle;    cursor: pointer;    pointer-events: all;  }    #sites li.checked:before {    background: green;    border: 2px solid green;    color: #fff;    content: "\2714";  }  </style></head><body>  <ol id="sites">    <li class="checked">Apple</li>    <li>Google</li>    <li class="checked">Amazon</li>    <li>Facebook</li>  </ol></body><script>function hasClass(el, className) {  if (el.classList) {    return el.classList.contains(className)  } else {    return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)'))  }}function addClass(el, className) {  if (el.classList) {    el.classList.add(className)  } else if (!hasClass(el, className)) {    el.className += ` ${className}`  }}function removeClass(el, className) {  if (el.classList) {    el.classList.remove(className)  } else if (hasClass(el, className)) {    let reg = new RegExp('(\\s|^)' + className + '(\\s|$)')    el.className = el.className.replace(reg, ' ')  }}function changeStyle(ele) {  if (hasClass(ele, 'checked')) {    removeClass(ele, 'checked')  } else {    addClass(ele, 'checked')  }}let sites = document.querySelector('ol#sites')// 事件委托sites.addEventListener('click', function(e) {  e = e || window.event  let target = e.target || e.srcElement  if (target.tagName.toLowerCase() === 'li') {    changeStyle(target)  }}, false)</script></html><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ol</span> <span class="attr">id</span>=<span class="string">&quot;sites&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span> <span class="attr">class</span>=<span class="string">&quot;checked&quot;</span>&gt;</span>Apple<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span>&gt;</span>Google<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span> <span class="attr">class</span>=<span class="string">&quot;checked&quot;</span>&gt;</span>Amazon<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">li</span>&gt;</span>Facebook<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ol</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">li</span> &#123;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">14px</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">1.5</span>;</span><br><span class="line">  <span class="attribute">cursor</span>: pointer;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">li</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#7cfc00</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">2px</span> solid <span class="number">#d3d3d3</span>;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot; &quot;</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">margin-left</span>: -<span class="number">38px</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">text-align</span>: center;</span><br><span class="line">  <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">  <span class="attribute">cursor</span>: pointer;</span><br><span class="line">  <span class="attribute">pointer-events</span>: all;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">li</span><span class="selector-class">.checked</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: green;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">2px</span> solid green;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot;\2714&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最后">最后</h2><p>在网上还有很多关于伪元素的用法，非常有趣，既能减少 DOM 元素数量，还能用 CSS 实现一部分 JS 的功能，非常酷炫，后面见到有趣的用法会不断记录。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;伪元素技巧&quot;&gt;伪元素技巧&lt;/h2&gt;
&lt;p&gt;在 &lt;a href=&quot;https://lz5z.com/CSS%E4%BC%AA%E5%85%83%E7%B4%A0%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/&quot;&gt;CSS 伪元素基本用法&lt;/a&gt;一文中讲述了伪元素的基础功能，本章学习一些进阶功能，看看伪元素能实现哪些方便好用的功能。&lt;/p&gt;
&lt;h3 id=&quot;清除浮动&quot;&gt;清除浮动&lt;/h3&gt;
&lt;p&gt;如果一个元素内部的子元素全部都是浮动的话，那么这个元素会出现高度塌陷，这个时候就需要清除浮动。高度塌陷的负面作用主要有：不能正确显示背景，边框不能撑开，margin 和 padding 不能正确显示。&lt;/p&gt;
&lt;p&gt;假设有代码如下：&lt;/p&gt;</summary>
    
    
    
    <category term="CSS" scheme="https://lz5z.com/categories/CSS/"/>
    
    
    <category term="CSS" scheme="https://lz5z.com/tags/CSS/"/>
    
    <category term="伪元素" scheme="https://lz5z.com/tags/%E4%BC%AA%E5%85%83%E7%B4%A0/"/>
    
    <category term="清除浮动" scheme="https://lz5z.com/tags/%E6%B8%85%E9%99%A4%E6%B5%AE%E5%8A%A8/"/>
    
  </entry>
  
  <entry>
    <title>CSS 伪元素基本用法</title>
    <link href="https://lz5z.com/CSS%E4%BC%AA%E5%85%83%E7%B4%A0%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/"/>
    <id>https://lz5z.com/CSS%E4%BC%AA%E5%85%83%E7%B4%A0%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/</id>
    <published>2017-12-08T10:06:19.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<h1>伪元素</h1><p>CSS 中可以利用伪元素给 DOM 元素添加特殊的样式。比如说，我们可以通过 <code>:before</code> 在一个元素前增加一些文本，并为这些文本添加样式。虽然用户可以看到这些文本，但是这些文本实际上不在文档树中。</p><p>CSS3 规范中要求使用双冒号(::)添加伪元素，用以区分伪元素和伪类，比如 <code>::before</code> 是伪元素，<code>:hover</code> 是伪类。但是大部分伪元素依然支持单冒号的形式，<code>::before</code> 写成 <code>:before</code> 也可以，为了向后兼容，一般推荐使用单冒号的形式。</p><p>支持单双冒号的伪元素有： <code>:before/::before</code>，<code>:after/::after</code>，<code>:first-letter/::first-letter</code>，<code>:first-line/::first-line</code>。</p><p>仅支持双冒号的伪元素有： <code>::selection</code>，<code>::placeholder</code>，<code>::backdrop</code>。</p><span id="more"></span><h2 id="before-after"><code>:before</code> &amp; <code>:after</code></h2><p><code>:before</code> 和 <code>:after</code> 可以在元素前面或者后面插入内容，用 content 属性表示要插入的内容，这个虚拟元素默认是行内元素，可以配合其它样式使用。</p><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span> <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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-tag">p</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;Hello&#x27;</span>;</span><br><span class="line">  <span class="attribute">color</span>: red;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-tag">p</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;World&#x27;</span>;</span><br><span class="line">  <span class="attribute">color</span>: black;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>p 元素会显示 <strong>Hello World</strong>，但是被插入的内容实际上不在文档树中。</p><h2 id="first-letter"><code>:first-letter</code></h2><p><code>:first-letter</code> 用来获取元素中文本的首字母，被修饰的首字母不在文档树中。注意没有 <code>:last-letter</code>。</p><p>首行只在 block-container box 内部才有意义, 因此 <code>:first-letter</code> 伪元素 只在 display 属性值为 block, inline-block, table-cell, list-item 或者 table-caption 的元素上才起作用。 其他情况下 <code>:first-letter</code> 毫无意义。</p><p><code>:first-letter</code> 的优先级低于 <code>:before</code>，也就是如果元素用 <code>:before</code> 先插入文本，会获取 before 伪元素中的内容。</p><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>World<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">p</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;Hello &#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-tag">p</span><span class="selector-pseudo">:first</span>-letter &#123;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">40px</span>;</span><br><span class="line">  <span class="attribute">color</span>: red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这时，<code>:first-letter</code> 实际获取的元素是 <code>：before</code> 中的 <strong>H</strong>。</p><p>注意： 在一个使用了 <code>:first-letter</code> 伪元素的选择器中，只有很小的一部分 css 属性能被使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/::first-letter">::first-letter</a></p><h2 id="first-line"><code>:first-line</code></h2><p><code>:first-line</code> 用来获取 <strong>块状元素</strong> 中的第一行文本，不能用于内联元素。</p><p>html:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello<span class="tag">&lt;/<span class="name">br</span>&gt;</span>World<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css:</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></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">h1</span><span class="selector-pseudo">:first</span>-<span class="selector-tag">line</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: orange;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在一个使用了 ::first-line 伪元素的选择器中，只有很小的一部分css属性能被使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/::first-line">::first-line</a></p><h2 id="selection"><code>::selection</code></h2><p><code>::selection</code> 伪元素应用于文档中被用户高亮的部分（比如使用鼠标或其他选择设备选中的部分），该伪元素只支持双冒号的形式。</p><p>只有 Gecko 引擎需要加前缀（-moz）</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></pre></td><td class="code"><pre><span class="line">::-moz-selection &#123;</span><br><span class="line">  <span class="attribute">background</span>: orange;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="selector-pseudo">::selection</span>  &#123;</span><br><span class="line">  <span class="attribute">background</span>: orange;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意： 只有一小部分 CSS 属性可以用于 <code>::selection</code> 选择器： color, background-color, cursor, outline, text-decoration, text-emphasis-color 和 text-shadow。要特别注意的是，background-image 会如同其他属性一样被忽略。</p><h2 id="placeholder-试验性质"><code>::placeholder</code> (试验性质)</h2><p><code>:placeholder</code> 匹配占位符的文本，只有元素设置了 placeholder 属性时，该伪元素才能生效。在一些浏览器中（IE10 和 Firefox18 及其以下版本）会使用单冒号的形式。</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">input</span>::-moz-placeholder &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#666</span>;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="selector-tag">input</span>::-webkit-input-placeholder &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#666</span>;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">/* IE 10 only */</span></span><br><span class="line"><span class="selector-tag">input</span>:-ms-input-placeholder &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#666</span>;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">/* Firefox 18 and below */</span></span><br><span class="line"><span class="selector-tag">input</span>:-moz-input-placeholder &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#666</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="backdrop-试验性质"><code>::backdrop</code> (试验性质)</h2><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></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">h1</span><span class="selector-pseudo">:fullscreen</span><span class="selector-pseudo">::backdrop</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: orange;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1>参考文章</h1><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/Pseudo-elements">MDN - Pseudo-elements</a></li><li><a href="http://www.alloyteam.com/2016/05/summary-of-pseudo-classes-and-pseudo-elements/">summary-of-pseudo-classes-and-pseudo-elements/</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h1&gt;伪元素&lt;/h1&gt;
&lt;p&gt;CSS 中可以利用伪元素给 DOM 元素添加特殊的样式。比如说，我们可以通过 &lt;code&gt;:before&lt;/code&gt; 在一个元素前增加一些文本，并为这些文本添加样式。虽然用户可以看到这些文本，但是这些文本实际上不在文档树中。&lt;/p&gt;
&lt;p&gt;CSS3 规范中要求使用双冒号(::)添加伪元素，用以区分伪元素和伪类，比如 &lt;code&gt;::before&lt;/code&gt; 是伪元素，&lt;code&gt;:hover&lt;/code&gt; 是伪类。但是大部分伪元素依然支持单冒号的形式，&lt;code&gt;::before&lt;/code&gt; 写成 &lt;code&gt;:before&lt;/code&gt; 也可以，为了向后兼容，一般推荐使用单冒号的形式。&lt;/p&gt;
&lt;p&gt;支持单双冒号的伪元素有： &lt;code&gt;:before/::before&lt;/code&gt;，&lt;code&gt;:after/::after&lt;/code&gt;，&lt;code&gt;:first-letter/::first-letter&lt;/code&gt;，&lt;code&gt;:first-line/::first-line&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;仅支持双冒号的伪元素有： &lt;code&gt;::selection&lt;/code&gt;，&lt;code&gt;::placeholder&lt;/code&gt;，&lt;code&gt;::backdrop&lt;/code&gt;。&lt;/p&gt;</summary>
    
    
    
    <category term="CSS" scheme="https://lz5z.com/categories/CSS/"/>
    
    
    <category term="CSS" scheme="https://lz5z.com/tags/CSS/"/>
    
    <category term="伪元素" scheme="https://lz5z.com/tags/%E4%BC%AA%E5%85%83%E7%B4%A0/"/>
    
    <category term="before" scheme="https://lz5z.com/tags/before/"/>
    
    <category term="after" scheme="https://lz5z.com/tags/after/"/>
    
  </entry>
  
  <entry>
    <title>HTML meta 标签</title>
    <link href="https://lz5z.com/HTML-meta/"/>
    <id>https://lz5z.com/HTML-meta/</id>
    <published>2017-12-02T07:22:13.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h1>引言</h1><p>最近做的一个关于电影的网站 <a href="https://movie.lz5z.com/">IMDB Top250</a>，想对其进行 SEO 优化，用到 meta 信息的时候，很多知识都是 『似乎』、『好像』、『可能』 的感觉，回想自己一直没有系统的学习过 meta 相关的知识，这些东西虽然简单，但是很多时候能发挥出意想不到的效果，尤其对于 SEO 有非常重要的作用。</p><h2 id="meta-简介">meta 简介</h2><p>meta 标签位于文档的头部，可提供有关页面的元信息（meta-information）。 meta 标签本身不包含任何内容，通过其属性定义了与文档相关联的内容。</p><p>meta 标签一共有五个属性值： charset、content、http-equiv、name、scheme。 其中 http-equiv 和 name<br>必须与 content 配合组成键值对使用， charset 为 HTML5 属性， scheme 属性 HTML5 不支持。</p><span id="more"></span><h3 id="charset">charset</h3><p>定义 HTML 文档编码方式，一般使用世界通用语言编码 UTF-8。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在 HTML4 中的写法是</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;Content-Type&quot;</span> <span class="attr">content</span>=<span class="string">&quot;text/html; charset=UTF-8&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="http-equiv">http-equiv</h3><p>http-equiv 为枚举属性，与 content 属性组成键值对，一般用于服务器向浏览器传回一些特定的信息，以帮助浏览器编译和显示页面内容。虽然有些服务器会发送许多这种键值对，但是所有服务器都至少要发送一个：<code>content-type:text/html</code>。这将告诉浏览器准备接收一个 HTML 文档。</p><p>http-equiv 可枚举的值有： content-type, default-style, refresh。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;refresh&quot;</span> <span class="attr">content</span>=<span class="string">&quot;3;URL=https://lz5z.com&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>以上表示页面 3 秒后自动跳转。</p><h3 id="name">name</h3><p>name 属性是用的最多的属性，常用的有 description，keywords，author，viewport，generator 等等。</p><p>其中 keywords 对应 content 用逗号分隔，description 为搜索引擎显示网页时候的简介。</p><p>viewport 用于指定视窗的属性，在移动端开发时显得尤为重要。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;keywords&quot;</span> <span class="attr">content</span>=<span class="string">&quot;HTML5,meta&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;description&quot;</span> <span class="attr">content</span>=<span class="string">&quot;blabla&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>还有一些属性值，比如 referrer，robots，renderer。</p><p>(1) referrer 控制所有从该文档发出的 HTTP 请求中 HTTP Referer 头的内容：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;referer&quot;</span> <span class="attr">content</span>=<span class="string">&quot;always&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>referrer 对应的 content 属性可取的值：</p><ul><li><code>no-referrer</code>不要发送 HTTP Referer 首部。</li><li><code>origin</code>发送当前文档的 origin。</li><li><code>no-referrer-when-downgrade</code>当目的地是先验安全的(https-&gt;https)则发送 origin 作为 referrer ，但是当目的地是较不安全的 (https-&gt;http)时则不发送 referrer 。这个是默认的行为。</li><li><code>origin-when-crossorigin</code>在同源请求下，发送完整的URL (不含查询参数) ，其他情况下则仅发送当前文档的 origin。</li><li><code>unsafe-URL</code>在同源请求下，发送完整的URL (不含查询参数)。</li></ul><blockquote><p>HTTP Referer 头：<br>Referer 请求头字段允许由客户端指定资源的 URI 来自于哪一个请求地址，这对服务器有好处。Referer 请求头让服务器能够拿到请求资源的来源，可以用于分析用户的兴趣爱好、收集日志、优化缓存等等。同时也让服务器能够发现过时的和错误的链接并及时维护。</p></blockquote><p>注意：动态地插入 <code>&lt;meta name=&quot;referrer&quot;&gt;</code> (通过 document.write 或者 appendChild) 是不起作用的。同样注意如果同时有多个彼此冲突的策略被定义，那么 no-referrer 策略会生效。</p><p>(2) robots 用来告诉搜索引擎的爬虫哪些页面需要索引，哪些不需要索引。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;robots&quot;</span> <span class="attr">content</span>=<span class="string">&quot;all&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>robots 对应的 content 可取的值：</p><ul><li>all：文件将被检索，且页面上的链接可以被查询。</li><li>none：文件将不被检索，且页面上的链接不可以被查询。</li><li>index：文件将被检索。</li><li>follow：页面上的链接可以被查询。</li><li>noindex：文件将不被检索，但页面上的链接可以被查询。</li><li>nofollow：文件将被检索，但页面上的链接不可以被查询。</li></ul><p>还有一些只有固定的搜索引擎支持的参数，比如 noodp，noarchive 等，这里就不说明了。</p><p>(3) renderer</p><p>renderer 并不是 w3c 标准，但却经常见于一些网页中，这个属性主要用于双核或者多核浏览器（猎豹浏览器，360浏览器）使用指定的内核处理自己的网页。目前大多数 「双核」 浏览器内部的两个内核分别是 IE 内核和 WebKit 内核，IE 内核主要用于兼容「老一辈」的网页，使其能够正常显示；WebKit 内核则用于渲染「新一代」的网页，从而发挥出更快的显示速度、更好的显示效果、更优异的脚本执行性能。</p><p>作为用户来说并不关心你使用哪个内核，简单易用才是王道，因此在网页中设置首选内核会让网页有更好的效果。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;renderer&quot;</span> <span class="attr">content</span>=<span class="string">&quot;webkit&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;renderer&quot;</span> <span class="attr">content</span>=<span class="string">&quot;webkit|ie-stand&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>renderer</code> 对应的 content 用于指定浏览器内核，<br>webkit(WebKit 内核)、ie-stand(IE 内核-标准模式)、ie-comp(IE 内核-兼容模式)。我们也可以同时指定多个内核名称，之间以符号&quot;|&quot;进行分隔，此时浏览器将会按照从左到右的先后顺序选择其具备的渲染内核来处理当前网页。</p><p>IE8 有自己独特的写法 X-UA-Compatible 对于 IE8 之外的浏览器是不识别的。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// Edge 模式通知 IE 以最高级别的可用模式显示内容</span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;X-UA-Compatible&quot;</span> <span class="attr">content</span>=<span class="string">&quot;edge&quot;</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">// 如果 IE 有安装 Google Chrome Frame，那么就走安装的组件，如果没有就和上面一样。</span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;X-UA-Compatible&quot;</span> <span class="attr">content</span>=<span class="string">&quot;IE=edge,chrome=1&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>注： 如果设置浏览器内核为 Webkit (极速模式)，打开网页后却为 IE (兼容模式)，尝试刷新浏览器则会自动切换模式。</p></blockquote><p>通常是这样设置的</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;renderer&quot;</span> <span class="attr">content</span>=<span class="string">&quot;webkit&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;X-UA-Compatible&quot;</span> <span class="attr">content</span>=<span class="string">&quot;IE=edge,chrome=1&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>(4) format-detection</p><p>防止 ios 把数字/字符串识别为电话/邮件/日期/地址</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;format-detection&quot;</span> <span class="attr">content</span>=<span class="string">&quot;telephone=no&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;format-detection&quot;</span> <span class="attr">content</span>=<span class="string">&quot;date=no&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;format-detection&quot;</span> <span class="attr">content</span>=<span class="string">&quot;address=no&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;format-detection&quot;</span> <span class="attr">content</span>=<span class="string">&quot;email=no&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><h1>参考资料</h1><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/meta">MDN-meta</a><br><a href="http://web.jobbole.com/86648/">关于控制 Referer 你想要知道的一切</a></p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;引言&lt;/h1&gt;
&lt;p&gt;最近做的一个关于电影的网站 &lt;a href=&quot;https://movie.lz5z.com/&quot;&gt;IMDB Top250&lt;/a&gt;，想对其进行 SEO 优化，用到 meta 信息的时候，很多知识都是 『似乎』、『好像』、『可能』 的感觉，回想自己一直没有系统的学习过 meta 相关的知识，这些东西虽然简单，但是很多时候能发挥出意想不到的效果，尤其对于 SEO 有非常重要的作用。&lt;/p&gt;
&lt;h2 id=&quot;meta-简介&quot;&gt;meta 简介&lt;/h2&gt;
&lt;p&gt;meta 标签位于文档的头部，可提供有关页面的元信息（meta-information）。 meta 标签本身不包含任何内容，通过其属性定义了与文档相关联的内容。&lt;/p&gt;
&lt;p&gt;meta 标签一共有五个属性值： charset、content、http-equiv、name、scheme。 其中 http-equiv 和 name&lt;br&gt;
必须与 content 配合组成键值对使用， charset 为 HTML5 属性， scheme 属性 HTML5 不支持。&lt;/p&gt;</summary>
    
    
    
    <category term="HTML" scheme="https://lz5z.com/categories/HTML/"/>
    
    
    <category term="HTML5" scheme="https://lz5z.com/tags/HTML5/"/>
    
    <category term="meta" scheme="https://lz5z.com/tags/meta/"/>
    
  </entry>
  
  <entry>
    <title>迁移 github pages 到 coding.net</title>
    <link href="https://lz5z.com/%E8%BF%81%E7%A7%BBBlog%E5%88%B0Coding/"/>
    <id>https://lz5z.com/%E8%BF%81%E7%A7%BBBlog%E5%88%B0Coding/</id>
    <published>2017-10-27T07:37:59.000Z</published>
    <updated>2026-05-07T14:50:53.976Z</updated>
    
    <content type="html"><![CDATA[<p>由于众所周知的原因，github 在国内时不时不能访问，虽然有各种办法可以跨越屏障，但是你不能用预测未来会发生哪些事情，于是决定将博客迁移到国内，<a href="https://coding.net">coding</a> 是一个不错的选择，主要有以下几个优点。</p><ul><li>国内速度更快</li><li>自带 SSL，且免费</li><li>五个免费的私人仓库</li><li>功能较全: pages, webIDE, CI 等</li><li>经过一段时间迭代，产品经得起考验</li></ul><span id="more"></span><h2 id="步骤">步骤</h2><p>首先直接从 github 把 blog 项目导入到 coding，项目名称命名为 [name].coding.me，相当于 github 上面的 [name].github.io。</p><p>进入项目代码，点击左侧 『代码 -&gt; Pages 服务』，选择静态 Pages 服务，coding 部署来源仅支持 coding-pages 分支和 master 分支，所以选择 master 分支。</p><img src="/assets/img/coding.png" alt="我是一只图片"><p>这时，通过 [name].coding.me 就能够访问页面了，但是这还远远不够，我们还需要添加自定义域名和开启 SSL 服务。</p><h2 id="自定义域名-SSL">自定义域名 SSL</h2><p>首先确保项目根目录中有 CNAME 文件，里面是自己的域名，比如我的域名 <strong><a href="http://lz5z.com">lz5z.com</a></strong>，然后在 coding 页面自定义域名中输入此域名，并且开启强制 HTTPS 访问。</p><img src="/assets/img/coding_pages.png" alt="我是一只图片"><p>然后去自己域名服务商那里修改 DNS Server，我的域名在万网购买，于是在万网控制台添加一个 CNAME 记录和一个 A 记录，加上之前 github pages 添加的主机记录，截图如下。</p><img src="/assets/img/coding_dns.png" alt="我是一只图片"><p>红色部分为新添加的记录，如果不知道 <a href="http://coding.net">coding.net</a> 的 ip 地址的话，可以手动 ping 一下。</p><p>由于之前使用 cloudflare 的免费 SSL 服务而将 DNS Server 的地址指向了 cloudflare，这个时候把地址改回万网默认配置即可。</p><p>经过漫长的等待，DNS 解析生效，此时通过 <a href="https://lz5z.com">https://lz5z.com</a> 访问，发现域名已经生效了，但是存在两个问题：</p><ul><li>国内地址访问网站， SSL 没有问题，但是国外访问时 SSL 会报错，在 chrome 中有一个不能忍受的警告。</li><li>每次访问博客地址的时候，首先会看到一个 coding 的广告，然后再重定向到自己要访问的地址，这也是不能忍受的。</li></ul><h3 id="解决-SSL-证书错误">解决 SSL 证书错误</h3><p>国外地址访问网站报 SSL 不合法主要是因为这个原因：</p><blockquote><p>注意：申请 SSL/TLS 证书需要通过 Let’s Encrypt 的 HTTP 方式验证域名所有权。如果您的域名在境外无法访问 Coding Pages 的服务器，将导致 SSL/TLS 证书申请失败。</p></blockquote><p>查阅资料发现大家的解决方式都是设置双线解析，也就是国外访问通过 github pages，国内访问通过 <a href="http://coding.net">coding.net</a>，因此要为域名设置解析路线，如果域名服务商自定义解析路线，可以选择免费的 <a href="https://www.dnspod.cn/">DNSPod</a> 做 DNS 解析。</p><p>DNSPod 提供双线解析的原理我不是很明白，而且比较困惑的是 github pages 自定义域名原生是不资辞 SSL 的，之前的做法是使用 cloudflare 的 SSL 服务进行重定向，假如使用双线解析的话，那国外地址为什么能够看到合法的 SSL 呢？</p><p>而且按照网上的做法改了 DNS 解析后，并没有发生双线解析，无论是国外还是国内都是解析到 <a href="http://coding.net">coding.net</a>，但是解决了国外地址访问报 SSL 证书错误的问题。着实很奇怪，以下是我的做法。</p><h3 id="DNSPod">DNSPod</h3><p>注册 -&gt; 登录 -&gt; 实名认证 -&gt; 进入控制台 -&gt; 添加域名</p><p>添加域名的时候 DNSPod 会自动监测域名之前的解析情况，然后用 DNSPod 服务器提供的 DNS 地址替代万网提供的地址。</p><p>DNSPod DNS 记录如下：</p><img src="/assets/img/coding_dnspod.png" alt="我是一只图片"><p>更改万网 DNS Server 为 DNSPod:</p><img src="/assets/img/coding_dnsserver.png" alt="我是一只图片"><p>再次经过漫长的等待，DNS 生效后，无论国内国外访问网站都是合法的 SSL，excited！</p><h3 id="解决-coding-广告后重定向">解决 coding 广告后重定向</h3><p>每次新建隐私窗口打开网站都是先看 coding 的广告，然后再重定向到之前的地址，这是极差的用户体验，不过 coding 官方提供了解决办法，简单的就是购买 coding 的会员，免费的办法就是在网站首页任意位置放置「Hosted by Coding Pages」的文字版或图片版，具体办法参考 coding pages 服务的说明。添加之后勾选 <strong>已放置 Hosted by Coding Pages</strong>，等待一天或者两天就生效了。</p><h2 id="总结">总结</h2><p>这次切换 github pages 到 <a href="http://coding.net">coding.net</a> 真的费时费力，不过好在现在网页能够正常访问，而且速度也比之前快很多，所以还是比较满意的。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;由于众所周知的原因，github 在国内时不时不能访问，虽然有各种办法可以跨越屏障，但是你不能用预测未来会发生哪些事情，于是决定将博客迁移到国内，&lt;a href=&quot;https://coding.net&quot;&gt;coding&lt;/a&gt; 是一个不错的选择，主要有以下几个优点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;国内速度更快&lt;/li&gt;
&lt;li&gt;自带 SSL，且免费&lt;/li&gt;
&lt;li&gt;五个免费的私人仓库&lt;/li&gt;
&lt;li&gt;功能较全: pages, webIDE, CI 等&lt;/li&gt;
&lt;li&gt;经过一段时间迭代，产品经得起考验&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="Blog" scheme="https://lz5z.com/categories/Blog/"/>
    
    
    <category term="github pages" scheme="https://lz5z.com/tags/github-pages/"/>
    
    <category term="coding.net" scheme="https://lz5z.com/tags/coding-net/"/>
    
    <category term="SSL" scheme="https://lz5z.com/tags/SSL/"/>
    
  </entry>
  
  <entry>
    <title>解决 webpack 打包后 z-index 重新计算的问题</title>
    <link href="https://lz5z.com/%E8%A7%A3%E5%86%B3webpack%E6%89%93%E5%8C%85%E5%90%8Ez-index%E9%87%8D%E6%96%B0%E8%AE%A1%E7%AE%97%E7%9A%84%E9%97%AE%E9%A2%98/"/>
    <id>https://lz5z.com/%E8%A7%A3%E5%86%B3webpack%E6%89%93%E5%8C%85%E5%90%8Ez-index%E9%87%8D%E6%96%B0%E8%AE%A1%E7%AE%97%E7%9A%84%E9%97%AE%E9%A2%98/</id>
    <published>2017-10-24T05:18:37.000Z</published>
    <updated>2026-05-07T14:50:53.976Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景">背景</h2><p>与 PC 端共同开发一个页面，页面由 PC 端提供，内部 iframe 则由我们前端提供。开发时候遇到了一个问题，webpack 打包后 css 的 z-index 值与原始值不符，导致 iframe 里面的 toast 被外面 z-index 较小的 dialog 覆盖。更改 toast 的 z-index，发现没起作用，页面上的 z-index 依然是之前的值，而不是 css 中赋予的值。给 z-index 加上 !important 后依然无效，查资料发现是 OptimizeCssAssetsPlugin 调用 cssProcessor cssnano 对 z-index 进行了重新计算导致的。</p><p>这本来是 webpack 插件的一个善举（让 z-index 数值更加合理），但是具体情况来看，这里显然不需要这个 “善举”。</p><span id="more"></span><h2 id="解决方案">解决方案</h2><p>解决方案按照网上的资料，可以在 OptimizeCssAssetsPlugin 插件中关掉 <a href="http://cssnano.co/">cssnano</a> 对 z-index 的重新计算（cssnano 称为 rebase）。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">OptimizeCSSPlugin</span>(&#123;</span><br><span class="line">    <span class="attr">cssProcessor</span>: <span class="built_in">require</span>(<span class="string">&#x27;cssnano&#x27;</span>),</span><br><span class="line">    <span class="attr">cssProcessorOptions</span>: &#123;</span><br><span class="line">        <span class="attr">discardComments</span>: &#123;<span class="attr">removeAll</span>: <span class="literal">true</span>&#125;,</span><br><span class="line">        <span class="comment">// 避免 cssnano 重新计算 z-index</span></span><br><span class="line">        <span class="attr">safe</span>: <span class="literal">true</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">canPrint</span>: <span class="literal">false</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>cssnano 将 z-index rebase 归类为 unsafe，只有在单个网页的 css 全部写入一个 css 文件，并且不通过 JavaScript 进行改动时是 safe。</p><p>参考： <a href="http://cssnano.co/optimisations/zindex/">http://cssnano.co/optimisations/zindex/</a></p><p>cssnano 默认进行 z-index rebase。</p><p>unsafe (potential bug) 优化项默认不开启应该比较友好。</p><h2 id="另外一个方案">另外一个方案</h2><p>以上是网上提供的方案，而且亲测有效，但是由于项目太大，因为其中一个小功能改了整个项目的 css 处理策略，难免有些担心会影响到其它页面。思考再三，决定不改 webpack 配置。</p><p>观察之前项目中使用的框架，在生成 dialog 或者 toast 的时候，即使在 webpack 插件对 css 进行处理之后，其 z-index 依然是很大的。</p><p>比如 element-ui 下 的 popup-manager.js 中首先设置 zIndex 为 2000，然后在 openModal 的时候动态添加 css 到 DOM 中，并且改变 zIndex 的值，而在浏览器中观察弹框的 z-index，果然是没有经过 cssnano rebase 的。</p><p>于是仿照 element-ui 的做法，把 z-index 相关的 css 用 js 动态插入到 DOM 中，就完美地解决了这个问题，并且没有对其它项目产生影响。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 改变 toast 的 z-index</span></span><br><span class="line">(<span class="keyword">function</span> <span class="title function_">addToastStyle</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> nod = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;style&#x27;</span>)</span><br><span class="line">    <span class="keyword">let</span> str = <span class="string">`.mint-toast&#123;z-index:2009;&#125;`</span></span><br><span class="line">    nod.<span class="property">type</span> = <span class="string">&#x27;text/css&#x27;</span></span><br><span class="line">    nod.<span class="title function_">appendChild</span>(<span class="variable language_">document</span>.<span class="title function_">createTextNode</span>(str))</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementsByTagName</span>(<span class="string">&#x27;head&#x27;</span>)[<span class="number">0</span>].<span class="title function_">appendChild</span>(nod)</span><br><span class="line">&#125;)()</span><br></pre></td></tr></table></figure><h2 id="总结">总结</h2><p>webpack 在对代码进行打包之前，会扫描所有的模块，建立模块之间的依赖树，而插件的运作时机也是相对于此时的静态代码，因此用 js 动态插入 css，webpack 显然不会知道要插入的 css 是什么样的，因此动态插入的 css 内容就不会经过插件的处理，也就避免了 OptimizeCssAssetsPlugin 的 “善举”。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;
&lt;p&gt;与 PC 端共同开发一个页面，页面由 PC 端提供，内部 iframe 则由我们前端提供。开发时候遇到了一个问题，webpack 打包后 css 的 z-index 值与原始值不符，导致 iframe 里面的 toast 被外面 z-index 较小的 dialog 覆盖。更改 toast 的 z-index，发现没起作用，页面上的 z-index 依然是之前的值，而不是 css 中赋予的值。给 z-index 加上 !important 后依然无效，查资料发现是 OptimizeCssAssetsPlugin 调用 cssProcessor cssnano 对 z-index 进行了重新计算导致的。&lt;/p&gt;
&lt;p&gt;这本来是 webpack 插件的一个善举（让 z-index 数值更加合理），但是具体情况来看，这里显然不需要这个 “善举”。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="webpack" scheme="https://lz5z.com/tags/webpack/"/>
    
    <category term="css" scheme="https://lz5z.com/tags/css/"/>
    
  </entry>
  
  <entry>
    <title>Linux 文件权限</title>
    <link href="https://lz5z.com/Linux%E6%96%87%E4%BB%B6%E6%9D%83%E9%99%90/"/>
    <id>https://lz5z.com/Linux%E6%96%87%E4%BB%B6%E6%9D%83%E9%99%90/</id>
    <published>2017-10-10T11:06:38.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h2 id="linux-文件属性">linux 文件属性</h2><p>linux 中用户相对于文件有三种身份：owner、group、others，每种身份各有 read、write、execute 三种权限。</p><p>使用 <code>ls -l</code> 命令可以查看与文件权限相关的信息：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l</span></span><br><span class="line">drwxr-xr-x  2 lizhen  staff  68 10 10 19:14 foo</span><br><span class="line">-rw-r--r--  1 lizhen  staff   0 10 10 19:14 test.txt</span><br><span class="line">lrwxr-xr-x  1 lizhen  staff  62  7 10 10:01 subl -&gt; /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl</span><br></pre></td></tr></table></figure><p>其中第一个字符表示文件类型：d 表示文件为一个目录，- 表示文件为普通文件，l 表示链接， b 表示设备文件。</p><p>接下来的字符中，以三个为一组，且均为 r(read)、 w(write)、 x(execute) 三个参数的组合，首先三个字符表示文件所有者权限，后面三个字符表示用户组权限，最后三个表示其他人对文件的权限。这三个权限的位置不会改变，如果没有权限，就会出现减号[ - ]。</p><img src="/assets/img/linux_permission.png" alt="linux_permission"><p>后面的字段分别代表：硬链接个数，所有者，所在组，文件或者目录大小，最后访问/修改时间，文件或者目录名。</p><span id="more"></span><h2 id="更改文件属性">更改文件属性</h2><p>chgrp：改变文件所属群组 change group<br>chown：改变文件拥有者 change owner<br>chmod：改变文件的权限 change mod</p><h3 id="chgrp">chgrp</h3><p>首先使用 groups 命令查看当前用户在哪些分组中，然后使用 chgrp 命令改变文件所属用户组</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">chgrp</span> -R admin foo</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l</span> </span><br><span class="line">drwxr-xr-x  2 lizhen  admin  68 10 10 19:14 foo</span><br></pre></td></tr></table></figure><p>-R 表示递归更改文件属组，就是在更改某个目录文件的属组时，如果加上 -R 参数，那么该目录下的所有文件的属组都会更改。可以通过 <code>/etc/group</code> 查看当前系统所有的分组。</p><p>可以看到文件分组由 staff 变成了 admin。</p><h3 id="chown">chown</h3><p>语法</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">chown [–R] 属主名 文件名</span><br><span class="line">chown [-R] 属主名：属组名 文件名</span><br></pre></td></tr></table></figure><p>chown 可以更改文件的 owner，也可以同时更改文件属组。假如当前系统中有一个名为 test 的用户。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> <span class="built_in">chown</span> -R <span class="built_in">test</span> foo</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l</span></span><br><span class="line">drwxr-xr-x  2 test   admin  68 10 10 19:14 foo</span><br></pre></td></tr></table></figure><p>此时 foo 的 owner 变成了 test。可以通过 <code>/etc/passwd</code> 文件查看当前系统所有的用户。</p><p>chown 还可以用户修改文件所在的分组。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> <span class="built_in">chown</span> [-R] lizhen:staff foo</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l</span></span><br><span class="line">drwxr-xr-x  2 lizhen  staff  68 10 10 19:14 foo</span><br></pre></td></tr></table></figure><p>文件属性又变回去了。</p><h3 id="chmod">chmod</h3><p>chmod 用来更改文件属性，权限可以使用符号或数字来表示。</p><p>使用符号表示权限：</p><p>[ + ]为文件或目录增加权限<br>[ - ]删除文件或目录的权限<br>[ = ]设置指定的权限</p><p>通过使用 u(owner)、g(group)、o(other) 来代表三种身份的权限，此外 a 代表 all，即全部身份。</p><p>语法</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod u/g/o/a +/-/= r/w/x filename</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l test.txt</span></span><br><span class="line">-rw-r--r--  1 lizhen  staff  0 10 10 20:33 test.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment"># 修改 owner 权限增加 execute，group 和 others 减少 read</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">chmod</span> u+x,g-r,o-r test.txt</span> </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l test.txt</span></span><br><span class="line">-rwx------  1 lizhen  staff  0 10 10 20:33 test.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment"># 修改 owner 权限为 rw，group 和 others 为 r</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">chmod</span> u=rw,g=r,o=r test.txt</span> </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l test.txt</span></span><br><span class="line">-rw-r--r--  1 lizhen  staff  0 10 10 20:33 test.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment"># 增加所有用户的执行权限</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">chmod</span> a+x test.txt</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l test.txt</span></span><br><span class="line">-rwxr-xr-x  1 lizhen  staff  0 10 10 20:33 test.txt</span><br></pre></td></tr></table></figure><p>使用数字改变权限：</p><p>x: 1<br>w: 2<br>r: 4</p><p>所以权限 <code>rwx</code> 就等于 <code>4 + 2 + 1 = 7</code>，也就是 <code>chmod a=rwx file</code> 相当于 <code>chmod 777 file</code>。</p><p>-rw——- (600) 只有所有者才有读和写的权限<br>-rw-r–r– (644) 只有所有者才有读和写的权限，组群和其他人只有读的权限<br>-rwx—— (700) 只有所有者才有读，写，执行的权限<br>-rwxr-xr-x (755) 只有所有者才有读，写，执行的权限，组群和其他人只有读和执行的权限<br>-rwx–x–x (711) 只有所有者才有读，写，执行的权限，组群和其他人只有执行的权限<br>-rw-rw-rw- (666) 每个人都有读写的权限<br>-rwxrwxrwx (777) 每个人都有读写和执行的权限</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">chmod</span> 711 test.txt</span> </span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l test.txt</span> </span><br><span class="line">-rwx--x--x  1 lizhen  staff  0 10 10 20:33 test.txt</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;linux-文件属性&quot;&gt;linux 文件属性&lt;/h2&gt;
&lt;p&gt;linux 中用户相对于文件有三种身份：owner、group、others，每种身份各有 read、write、execute 三种权限。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;ls -l&lt;/code&gt; 命令可以查看与文件权限相关的信息：&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;ls&lt;/span&gt; -l&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;drwxr-xr-x  2 lizhen  staff  68 10 10 19:14 foo&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;-rw-r--r--  1 lizhen  staff   0 10 10 19:14 test.txt&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;lrwxr-xr-x  1 lizhen  staff  62  7 10 10:01 subl -&amp;gt; /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其中第一个字符表示文件类型：d 表示文件为一个目录，- 表示文件为普通文件，l 表示链接， b 表示设备文件。&lt;/p&gt;
&lt;p&gt;接下来的字符中，以三个为一组，且均为 r(read)、 w(write)、 x(execute) 三个参数的组合，首先三个字符表示文件所有者权限，后面三个字符表示用户组权限，最后三个表示其他人对文件的权限。这三个权限的位置不会改变，如果没有权限，就会出现减号[ - ]。&lt;/p&gt;
&lt;img src=&quot;/assets/img/linux_permission.png&quot; alt=&quot;linux_permission&quot;&gt;
&lt;p&gt;后面的字段分别代表：硬链接个数，所有者，所在组，文件或者目录大小，最后访问/修改时间，文件或者目录名。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://lz5z.com/categories/Linux/"/>
    
    
    <category term="权限" scheme="https://lz5z.com/tags/%E6%9D%83%E9%99%90/"/>
    
  </entry>
  
  <entry>
    <title>ES2016 和 ES2017 学习</title>
    <link href="https://lz5z.com/ES2016%E5%92%8CES2017%E5%AD%A6%E4%B9%A0/"/>
    <id>https://lz5z.com/ES2016%E5%92%8CES2017%E5%AD%A6%E4%B9%A0/</id>
    <published>2017-09-12T08:26:44.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>ES6 发布之后，TC-39 小组每年发布一次 ECMAScript 语言新特性，这个 repository <a href="https://github.com/tc39/ecma262">tc39/ecma262</a> 中记录着最新版的提议。新版本的 ECMAScript 使用年份来表示版本，所以 ES6 被称为 ES2015， ES7 被称为 ES2016，所以标准起见，以后我们也称之为 ES2016 和 ES2017。</p><h2 id="ECMAScript-2016">ECMAScript 2016</h2><p>ES2016 只有两个新特性</p><ol><li>Array.prototype.includes</li><li>求冥运算(Exponentiation Operator)</li></ol><span id="more"></span><h3 id="Array-prototype-includes">Array.prototype.includes</h3><p>includes 查找一个值是否在数组中</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>)         <span class="comment">//true</span></span><br><span class="line">[<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>].<span class="title function_">includes</span>(<span class="string">&#x27;d&#x27;</span>) <span class="comment">//false</span></span><br></pre></td></tr></table></figure><p>includes 还可以接收两个参数，第一个表示要查找的值，第二个表示从数组第 N 个元素开始查找。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">2</span>)     <span class="comment">// true</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">4</span>)     <span class="comment">// false</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>, <span class="number">2</span>)  <span class="comment">// true</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>, <span class="number">3</span>)  <span class="comment">// false</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>, -<span class="number">1</span>) <span class="comment">// true</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="title class_">NaN</span>].<span class="title function_">includes</span>(<span class="title class_">NaN</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>注意上面 <code>[1, 2, NaN].includes(NaN)</code> 的返回值为 true，虽然 <code>NaN === NaN</code> 的结果为 false，所以『包含』和『相等』还是有区别的。</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="keyword">const</span> tt = [-<span class="number">0</span>, <span class="number">1</span>, <span class="title class_">NaN</span>]</span><br><span class="line">tt.<span class="title function_">includes</span>(<span class="number">0</span>)   <span class="comment">// true</span></span><br><span class="line">tt.<span class="title function_">indexOf</span>(<span class="title class_">NaN</span>)  <span class="comment">// -1</span></span><br><span class="line">tt.<span class="title function_">includes</span>(<span class="title class_">NaN</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>测试发现 includes 和 indexOf 在 node 8 / chrome 61 下速度差异不大，因此在使用的时候不用考虑性能的问题。</p><p>在 ES2015 中，String 对象也有 includes 方法，String.prototype.includes，但是只能用于 String，不能用于 characters。</p><h3 id="幂运算-Exponentiation-operator">幂运算 Exponentiation operator</h3><p>ES2016 新增幂运算符改进语法</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="number">3</span> ** <span class="number">3</span> <span class="comment">// 27</span></span><br><span class="line"><span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="number">5</span>, <span class="number">2</span>) === <span class="number">5</span> ** <span class="number">2</span> <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="number">3</span></span><br><span class="line">a **= <span class="number">3</span> <span class="comment">// 27</span></span><br></pre></td></tr></table></figure><p>幂运算符的<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence">优先级</a>高于二元运算符，低于一元运算符。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2</span> * <span class="number">5</span> ** <span class="number">2</span>  <span class="comment">// 50</span></span><br><span class="line">-(<span class="number">5</span> ** <span class="number">2</span>)   <span class="comment">// -25</span></span><br><span class="line">(-<span class="number">5</span>) ** <span class="number">2</span>   <span class="comment">// 25</span></span><br><span class="line"><span class="comment">// 运算符左侧不能是除了 ++ 或 -- 之外的任意一元表达式</span></span><br><span class="line">-<span class="number">5</span> ** <span class="number">2</span>     <span class="comment">// Uncaught SyntaxError: Unexpected token ** </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> num = <span class="number">2</span></span><br><span class="line">++num ** <span class="number">2</span>    <span class="comment">// 9</span></span><br><span class="line">num-- ** <span class="number">2</span>    <span class="comment">// 9</span></span><br></pre></td></tr></table></figure><h2 id="ECMAScript-2017">ECMAScript 2017</h2><p>主要新特性：</p><ol><li>异步函数(Async/Await)</li><li>共享内存和原子(Shared memory and atomics)</li></ol><p>小改款</p><ol><li>Object.values() 和 Object.entries()</li><li>字符串填充(padStart 和 padEnd)</li><li>Object.getOwnPropertyDescriptors()</li><li>函数参数列表和调用中的尾逗号(Trailing commas)</li></ol><h3 id="async-await">async/await</h3><ul><li>async 函数声明： async function foo () {}</li><li>async 函数表达式： const foo = async function () {}</li><li>async 函数定义： let obj = { async foo () {} }</li><li>async 箭头函数： const foo = async () =&gt; {}</li></ul><p>关于 async/await 很早以前就写过了，而且现在基本上已经成了异步代码必备了，这里就不赘述了。</p><p>详情参考<a href="https://lz5z.com/JavaScript%E5%BC%82%E6%AD%A5%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/">JavaScript异步解决方案async/await</a></p><h3 id="共享内存和原子-Shared-memory-and-atomics">共享内存和原子(Shared memory and atomics)</h3><p>共享内存和原子内容比较多，后面会单独写一篇文章，暂时留坑。</p><h3 id="Object-entries-和-Object-values">Object.entries() 和 Object.values()</h3><p>(1) Object.entries()</p><p>该方法将一个对象中所有可枚举的属性与值按照二维数组的方式返回，如果对象是数组，则数组的下标作为键值。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">entries</span>(&#123; <span class="attr">one</span>: <span class="number">1</span>, <span class="attr">two</span>: <span class="number">2</span>&#125;) <span class="comment">// [[&#x27;one&#x27;, 1], [&#x27;two&#x27;, 2]]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">entries</span>([<span class="number">1</span>, <span class="number">2</span>]) <span class="comment">// [[&#x27;0&#x27;, 1], [&#x27;1&#x27;, 2]]</span></span><br></pre></td></tr></table></figure><p>返回数组的顺序与 Object.keys() 一致。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = &#123;<span class="number">3</span>: <span class="string">&#x27;a&#x27;</span>, <span class="number">2</span>: <span class="string">&#x27;b&#x27;</span>, <span class="number">1</span>: <span class="string">&#x27;c&#x27;</span>&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">entries</span>(obj)  <span class="comment">// [[&#x27;1&#x27;, &#x27;c&#x27;], [&#x27;2&#x27;, &#x27;b&#x27;], [&#x27;3&#x27;, &#x27;c&#x27;]]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(obj)     <span class="comment">// [&#x27;1&#x27;, &#x27;2&#x27;, &#x27;3&#x27;]</span></span><br></pre></td></tr></table></figure><p>Object.entries() 会忽略对象中 key 为 Symbol 的键值对。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">entries</span>(&#123; [<span class="title class_">Symbol</span>()]: <span class="number">123</span>, <span class="attr">foo</span>: <span class="string">&#x27;abc&#x27;</span> &#125;) <span class="comment">// [ [ &#x27;foo&#x27;, &#x27;abc&#x27; ] ]</span></span><br></pre></td></tr></table></figure><p>通过 Object.entries() 设置一个 Map 对象。</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">let</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>(<span class="title class_">Object</span>.<span class="title function_">entries</span>(&#123;</span><br><span class="line">    <span class="attr">one</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">two</span>: <span class="number">2</span>,</span><br><span class="line">&#125;))</span><br><span class="line"><span class="title class_">JSON</span>.<span class="title function_">stringify</span>([...map])  <span class="comment">// [[&quot;one&quot;,1],[&quot;two&quot;,2]]</span></span><br></pre></td></tr></table></figure><p>通过 Object.entries() 遍历对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = &#123; <span class="attr">one</span>: <span class="number">1</span>, <span class="attr">two</span>: <span class="number">2</span> &#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> [k,v] <span class="keyword">of</span> <span class="title class_">Object</span>.<span class="title function_">entries</span>(obj)) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(k)&#125;</span>: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(v)&#125;</span>`</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// &quot;one&quot;: 1</span></span><br><span class="line"><span class="comment">// &quot;two&quot;: 2</span></span><br></pre></td></tr></table></figure><p>(2) Object.values()</p><p>该方法返回对象可枚举键值对中所有的 value。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">values</span>(&#123; <span class="attr">one</span>: <span class="number">1</span>, <span class="attr">two</span>: <span class="number">2</span> &#125;)  <span class="comment">// [ 1, 2 ]</span></span><br></pre></td></tr></table></figure><h3 id="字符串填充-padStart-和-padEnd">字符串填充(padStart 和 padEnd)</h3><p>(1) String.prototype.padStart</p><p>padStart 函数通过填充字符串首部使字符串达到一定的长度。该方法接受两个参数，第一个表示目标字符串长度，第二个表示填充内容，默认填充内容为空格。</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="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">10</span>)         <span class="comment">// &quot;       abc&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">10</span>, <span class="string">&quot;foo&quot;</span>)  <span class="comment">// &quot;foofoofabc&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">6</span>,<span class="string">&quot;123465&quot;</span>) <span class="comment">// &quot;123abc&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">8</span>, <span class="string">&quot;0&quot;</span>)     <span class="comment">// &quot;00000abc&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padStart</span>(<span class="number">1</span>)          <span class="comment">// &quot;abc&quot;</span></span><br></pre></td></tr></table></figure><p>(2) String.prototype.padEnd</p><p>padEnd 填充字符串的时候从尾部开始填充，其它均与 padStart 相同。</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="string">&#x27;abc&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">10</span>)          <span class="comment">// &quot;abc       &quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">10</span>, <span class="string">&quot;foo&quot;</span>)   <span class="comment">// &quot;abcfoofoof&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">6</span>, <span class="string">&quot;123456&quot;</span>) <span class="comment">// &quot;abc123&quot;</span></span><br><span class="line"><span class="string">&#x27;abc&#x27;</span>.<span class="title function_">padEnd</span>(<span class="number">1</span>)           <span class="comment">// &quot;abc&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Object-getOwnPropertyDescriptors">Object.getOwnPropertyDescriptors()</h3><p>该方法获取目标对象所有属性的属性描述符，该属性必须是自己定义的，不能是通过原型链继承来的。</p><p>关于属性描述符的作用，可以查看<a href="https://lz5z.com/Object.defineProperty%E4%B8%BA%E5%AF%B9%E8%B1%A1%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7/">使用 Object.defineProperty 为对象定义属性</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">    [<span class="title class_">Symbol</span>(<span class="string">&#x27;foo&#x27;</span>)]: <span class="number">123</span>,</span><br><span class="line">    <span class="keyword">get</span> <span class="title function_">bar</span>() &#123; <span class="keyword">return</span> <span class="string">&#x27;abc&#x27;</span> &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptors</span>(obj));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// &#123; [Symbol(&#x27;foo&#x27;)]:</span></span><br><span class="line"><span class="comment">//    &#123; value: 123,</span></span><br><span class="line"><span class="comment">//      writable: true,</span></span><br><span class="line"><span class="comment">//      enumerable: true,</span></span><br><span class="line"><span class="comment">//      configurable: true &#125;,</span></span><br><span class="line"><span class="comment">//   bar:</span></span><br><span class="line"><span class="comment">//    &#123; get: [Function: bar],</span></span><br><span class="line"><span class="comment">//      set: undefined,</span></span><br><span class="line"><span class="comment">//      enumerable: true,</span></span><br><span class="line"><span class="comment">//      configurable: true &#125; &#125;</span></span><br></pre></td></tr></table></figure><p>使用 Object.assign() copy 一个对象/属性 的时候，不能正确 copy 属性的 get 和 set，而通过 getOwnPropertyDescriptors() 能够实现正确 copy 一个对象/对象属性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> =  <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">get</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> name</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">set</span>: <span class="keyword">function</span>(<span class="params">newName</span>) &#123;</span><br><span class="line">        name = newName</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">const</span> result = &#123;&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(result, <span class="title class_">Leo</span>)</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(result, <span class="string">&#x27;name&#x27;</span>)</span><br><span class="line"><span class="comment">// &#123;value: &quot;&quot;, writable: true, enumerable: true, configurable: true&#125;</span></span><br></pre></td></tr></table></figure><p>我们发现通过 Object.assign() copy 后的 ‘name’ 属性，其 ‘get’, ‘set’ 属性不见了</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="keyword">const</span> result2 = &#123;&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperties</span>(result2, <span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptors</span>(<span class="title class_">Leo</span>))</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(result2, <span class="string">&#x27;name&#x27;</span>)</span><br><span class="line"><span class="comment">// &#123;enumerable: true, configurable: true, get: ƒ, set: ƒ&#125;</span></span><br></pre></td></tr></table></figure><p>使用 Object.getOwnPropertyDescriptors 配合 Object.defineProperties 就可以实现正确 copy 了。</p><h3 id="函数参数列表和调用中的尾逗号-Trailing-commas">函数参数列表和调用中的尾逗号(Trailing commas)</h3><p>这个新特性很简单，就是允许我们在定义或者调用函数的时候参数后面多加一个逗号而不报错。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span> (<span class="params">a, b,</span>) &#123;&#125; <span class="comment">// correct</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">foo</span> (<span class="string">&#x27;abc&#x27;</span>, <span class="string">&#x27;def&#x27;</span>,)  <span class="comment">// correct</span></span><br></pre></td></tr></table></figure><p>在数组和对象中这样的写法也没有问题。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;green&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>,]</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line"><span class="attr">first</span>: <span class="string">&#x27;Leo&#x27;</span>,</span><br><span class="line"><span class="attr">last</span>: <span class="string">&#x27;Li&#x27;</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>新加入这个特性的好处就是当我们调整参数或者代码结构的时候，不需要再额外地添加或者删除逗号了，尤其是对代码进行注释的时候会方便很多。在版本管理上，不会因为出现一个逗号，导致原本只有一行的修改变成两行。</p><h2 id="参考资料">参考资料</h2><p><a href="http://exploringjs.com/es2016-es2017/">Exploring ES2016 and ES2017</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;ES6 发布之后，TC-39 小组每年发布一次 ECMAScript 语言新特性，这个 repository &lt;a href=&quot;https://github.com/tc39/ecma262&quot;&gt;tc39/ecma262&lt;/a&gt; 中记录着最新版的提议。新版本的 ECMAScript 使用年份来表示版本，所以 ES6 被称为 ES2015， ES7 被称为 ES2016，所以标准起见，以后我们也称之为 ES2016 和 ES2017。&lt;/p&gt;
&lt;h2 id=&quot;ECMAScript-2016&quot;&gt;ECMAScript 2016&lt;/h2&gt;
&lt;p&gt;ES2016 只有两个新特性&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Array.prototype.includes&lt;/li&gt;
&lt;li&gt;求冥运算(Exponentiation Operator)&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="ES7" scheme="https://lz5z.com/tags/ES7/"/>
    
    <category term="ES8" scheme="https://lz5z.com/tags/ES8/"/>
    
    <category term="ES2016" scheme="https://lz5z.com/tags/ES2016/"/>
    
    <category term="ES2017" scheme="https://lz5z.com/tags/ES2017/"/>
    
  </entry>
  
  <entry>
    <title>vue2 组件通信——使用 dispatch 和 broadcast</title>
    <link href="https://lz5z.com/vue2%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1-%E4%BD%BF%E7%94%A8dispatch%E5%92%8Cbroadcast/"/>
    <id>https://lz5z.com/vue2%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1-%E4%BD%BF%E7%94%A8dispatch%E5%92%8Cbroadcast/</id>
    <published>2017-09-01T01:37:44.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<p>最近在使用 <a href="http://element.eleme.io/">Element</a> 过程中发现组件通信大量使用 <code>dispatch</code> 和 <code>broadcast</code> 两个方法，之前在 <a href="https://lz5z.com/vue2%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/">vue2 组件通信</a> 也提到过 vue2 中取消了 <code>$dispatch</code> 和 <code>$broadcast</code> 两个重要的事件，而 Element 重新实现了这两个函数。</p><p>代码地址放在 <a href="https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js"><code>element-ui/lib/mixins/emitter</code></a></p><span id="more"></span><p><strong>emitter.js</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">__esModule</span> = <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">_broadcast</span>(<span class="params">componentName, eventName, params</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">$children</span>.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">child</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> name = child.<span class="property">$options</span>.<span class="property">componentName</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (name === componentName) &#123;</span><br><span class="line">      child.<span class="property">$emit</span>.<span class="title function_">apply</span>(child, [eventName].<span class="title function_">concat</span>(params));</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      _broadcast.<span class="title function_">apply</span>(child, [componentName, eventName].<span class="title function_">concat</span>([params]));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">exports</span>.<span class="property">default</span> = &#123;</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="attr">dispatch</span>: <span class="keyword">function</span> <span class="title function_">dispatch</span>(<span class="params">componentName, eventName, params</span>) &#123;</span><br><span class="line">      <span class="keyword">var</span> parent = <span class="variable language_">this</span>.<span class="property">$parent</span> || <span class="variable language_">this</span>.<span class="property">$root</span>;</span><br><span class="line">      <span class="keyword">var</span> name = parent.<span class="property">$options</span>.<span class="property">componentName</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">while</span> (parent &amp;&amp; (!name || name !== componentName)) &#123;</span><br><span class="line">        parent = parent.<span class="property">$parent</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (parent) &#123;</span><br><span class="line">          name = parent.<span class="property">$options</span>.<span class="property">componentName</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (parent) &#123;</span><br><span class="line">        parent.<span class="property">$emit</span>.<span class="title function_">apply</span>(parent, [eventName].<span class="title function_">concat</span>(params));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">broadcast</span>: <span class="keyword">function</span> <span class="title function_">broadcast</span>(<span class="params">componentName, eventName, params</span>) &#123;</span><br><span class="line">      _broadcast.<span class="title function_">call</span>(<span class="variable language_">this</span>, componentName, eventName, params);</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><h3 id="解析">解析</h3><p><code>dispatch</code> 和 <code>broadcast</code> 方法都需要 3 个参数，<code>componentName</code> 组件名称， <code>eventName</code> 事件名称， <code>params</code> 传递的参数。</p><p><code>dispatch</code> 方法会寻找所有的父组件，直到找到名称为 <code>componentName</code> 的组件，调用其 <code>$emit()</code> 事件。<code>broadcast</code> 方法则是遍历当前组件的所有子组件，找到名称为 <code>componentName</code> 的子组件，然后调用其 <code>$emit()</code> 事件。</p><p>这里也看出了 Element 中的 <code>dispatch</code> 与 <code>broadcast</code> 的不同，vue1 中的 <code>$dispatch</code> 和 <code>$broadcast</code> 会将事件通知给所有的 父/子 组件，只要其监听了相关事件，都能够（能够，不是一定）触发；而 Element 则更像是定向爆破，指哪打哪，其实更符合我们日常的需求。</p><h3 id="使用方式">使用方式</h3><p>兄弟组件之间的通信可以很好的诠释上述两个事件。假设父组件 <strong>App.vue</strong> 中引入了两个子组件 <strong>Hello.vue</strong> 和 <strong>Fuck.vue</strong>。<br>如果你的项目中巧合使用了 Element，那可以按照下面的方式将其引入进来，如果没有用 Element 也不用担心，复制上面的 <code>emitter.js</code>，通过 mixins 的方式引入即可。</p><p>在 <strong>App.vue</strong> 中监听 <code>message</code> 事件，收到事件后，通过 <code>broadcast</code> 和接收到的参数，将事件定向传播给相关组件。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">hello</span>&gt;</span><span class="tag">&lt;/<span class="name">hello</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">fuck</span>&gt;</span><span class="tag">&lt;/<span class="name">fuck</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">import</span> <span class="title class_">Hello</span> <span class="keyword">from</span> <span class="string">&#x27;components/Hello&#x27;</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">import</span> <span class="title class_">Fuck</span> <span class="keyword">from</span> <span class="string">&#x27;components/Fuck&#x27;</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">import</span> <span class="title class_">Emitter</span> <span class="keyword">from</span> <span class="string">&#x27;element-ui/lib/mixins/emitter&#x27;</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">name</span>: <span class="string">&#x27;app&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">componentName</span>: <span class="string">&#x27;ROOT&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">mixins</span>: [<span class="title class_">Emitter</span>],</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">components</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title class_">Hello</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="title class_">Fuck</span></span></span><br><span class="line"><span class="language-javascript">    &#125;,</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">created</span> () &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.$on(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">params</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">this</span>.<span class="title function_">broadcast</span>(params.<span class="property">componentName</span>, params.<span class="property">eventName</span>, params.<span class="property">text</span>)</span></span><br><span class="line"><span class="language-javascript">      &#125;)</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>Fuck.vue</strong> 与 <strong>Hello.vue</strong> 的内容基本相同，下面只列出 <strong>Fuck.vue</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Emitter</span> <span class="keyword">from</span> <span class="string">&#x27;element-ui/lib/mixins/emitter&#x27;</span></span><br><span class="line"><span class="keyword">import</span> event <span class="keyword">from</span> <span class="string">&#x27;mixins/event&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">componentName</span>: <span class="string">&#x27;Fuck&#x27;</span>,</span><br><span class="line">  <span class="attr">mixins</span>: [<span class="title class_">Emitter</span>, event],</span><br><span class="line">  <span class="title function_">data</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;Fuck&#x27;</span>,</span><br><span class="line">      <span class="attr">textarea</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">      <span class="attr">tableData</span>: []</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">submit</span> () &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">communicate</span>(<span class="string">&#x27;message&#x27;</span>, &#123;</span><br><span class="line">        <span class="attr">componentName</span>: <span class="string">&#x27;Hello&#x27;</span>,</span><br><span class="line">        <span class="attr">text</span>: <span class="variable language_">this</span>.<span class="property">textarea</span></span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">textarea</span> = <span class="string">&#x27;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">created</span> () &#123;</span><br><span class="line">    <span class="variable language_">this</span>.$on(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">text</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">tableData</span>.<span class="title function_">push</span>(<span class="variable language_">this</span>.<span class="title function_">getMessage</span>(text))</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><strong>mixins/event.js</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Emitter</span> <span class="keyword">from</span> <span class="string">&#x27;element-ui/lib/mixins/emitter&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">mixins</span>: [<span class="title class_">Emitter</span>],</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">communicate</span> (event, params = &#123;&#125;) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">dispatch</span>(<span class="string">&#x27;ROOT&#x27;</span>, event, <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;</span><br><span class="line">        <span class="attr">eventName</span>: event</span><br><span class="line">      &#125;, params))</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><strong>Fuck.vue</strong> 中监听了 <code>message</code> 事件，当收到消息时，向 <code>tableData</code> 中加入新的值。而 <code>summit</code> 方法则调用 <code>event.js</code> 中的 <code>communicate</code> 方法，通过 <code>dispatch</code> 方法将事件传播给 <code>ROOT</code> 组件。</p><p><a href="https://github.com/Leo555/vue_communication">完整代码地址</a></p><h2 id="vue-组件通信方式总结">vue 组件通信方式总结</h2><ol><li>父组件向子组件传递信息使用 props down</li><li>子组件向父组件传递信息使用 event up</li><li>其它关系类型组件通信使用 global event bus</li><li>大型 SPA 组件之间通信使用 Vuex 管理组件状态</li><li>使用 Element 下 emitter.js 中的 dispatch 和 broadcast 做事件定向传播</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近在使用 &lt;a href=&quot;http://element.eleme.io/&quot;&gt;Element&lt;/a&gt; 过程中发现组件通信大量使用 &lt;code&gt;dispatch&lt;/code&gt; 和 &lt;code&gt;broadcast&lt;/code&gt; 两个方法，之前在 &lt;a href=&quot;https://lz5z.com/vue2%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/&quot;&gt;vue2 组件通信&lt;/a&gt; 也提到过 vue2 中取消了 &lt;code&gt;$dispatch&lt;/code&gt; 和 &lt;code&gt;$broadcast&lt;/code&gt; 两个重要的事件，而 Element 重新实现了这两个函数。&lt;/p&gt;
&lt;p&gt;代码地址放在 &lt;a href=&quot;https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js&quot;&gt;&lt;code&gt;element-ui/lib/mixins/emitter&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Vue" scheme="https://lz5z.com/categories/Vue/"/>
    
    
    <category term="Vue" scheme="https://lz5z.com/tags/Vue/"/>
    
    <category term="dispatch" scheme="https://lz5z.com/tags/dispatch/"/>
    
    <category term="broadcast" scheme="https://lz5z.com/tags/broadcast/"/>
    
  </entry>
  
  <entry>
    <title>图片懒加载的几种实现方式</title>
    <link href="https://lz5z.com/%E5%9B%BE%E7%89%87%E6%87%92%E5%8A%A0%E8%BD%BD%E7%9A%84%E5%87%A0%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F/"/>
    <id>https://lz5z.com/%E5%9B%BE%E7%89%87%E6%87%92%E5%8A%A0%E8%BD%BD%E7%9A%84%E5%87%A0%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F/</id>
    <published>2017-08-28T06:55:20.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lz5z.com/lazyload">demo地址</a></p><h2 id="懒加载">懒加载</h2><p>Lazyload 可以加快网页访问速度，减少请求，实现思路就是判断图片元素是否可见来决定是否加载图片。当图片位于浏览器视口 (viewport) 中时，动态设置 <code>&lt;img&gt;</code> 标签的 src 属性，浏览器会根据 src 属性发送请求加载图片。</p><h2 id="懒加载实现">懒加载实现</h2><p>首先不设置 src 属性，将图片真正的 url 放在另外一个属性 data-src 中，在图片即将进入浏览器可视区域之前，将 url 取出放到 src 中。</p><p>懒加载的关键是<strong>如何判断图片处于浏览器可视范围内</strong>，通常有三种方法：</p><span id="more"></span><h3 id="方法一">方法一</h3><p>通过对比屏幕可视窗口高度和浏览器滚动距离与元素相对文档顶部的距离之间的关系，判断元素是否可见。</p><p>示意图如下：</p><img src="/assets/img/lazyload.svg" alt="lazyload.svg"><p>代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isInSight</span>(<span class="params">el</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> clientHeight = <span class="variable language_">window</span>.<span class="property">innerHeight</span> <span class="comment">// 获取屏幕可视窗口高度</span></span><br><span class="line">    <span class="keyword">const</span> scrollTop = <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">scrollTop</span> <span class="comment">// 浏览器窗口顶部与文档顶部之间的距离</span></span><br><span class="line">    <span class="comment">// el.offsetTop 元素相对于文档顶部的距离 </span></span><br><span class="line">    <span class="comment">// +100是为了提前加载</span></span><br><span class="line">    <span class="keyword">return</span> el.<span class="property">offsetTop</span> &lt;= clientHeight + scrollTop + <span class="number">100</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="方法二">方法二</h3><p>通过 getBoundingClientRect() 获取图片相对于浏览器视窗的位置</p><p>示意图如下：</p><img src="/assets/img/lazyload1.svg" alt="lazyload1.svg"><p>getBoundingClientRect() 方法返回一个 ClientRect 对象，里面包含元素的位置和大小的信息</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">ClientRect</span> &#123;</span><br><span class="line"><span class="attr">bottom</span>: <span class="number">596</span>,</span><br><span class="line"><span class="attr">height</span>: <span class="number">596</span>,</span><br><span class="line"><span class="attr">left</span>: <span class="number">0</span>,</span><br><span class="line"><span class="attr">right</span>: <span class="number">1920</span>,</span><br><span class="line"><span class="attr">top</span>: <span class="number">0</span>,</span><br><span class="line"><span class="attr">width</span>: <span class="number">1920</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中位置是相对于浏览器视图左上角而言。代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isInSight1</span>(<span class="params">el</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> bound = el.<span class="title function_">getBoundingClientRect</span>() </span><br><span class="line">    <span class="keyword">const</span> clientHeight = <span class="variable language_">window</span>.<span class="property">innerHeight</span> <span class="comment">// 表示浏览器可视区域的高度</span></span><br><span class="line">    <span class="comment">// bound.top 表示图片到可视区域顶部距离</span></span><br><span class="line">    <span class="comment">// +100是为了提前加载</span></span><br><span class="line">    <span class="keyword">return</span> bound.<span class="property">top</span> &lt;= clientHeight + <span class="number">100</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="方法三">方法三</h3><p>使用 IntersectionObserver API，观察元素是否可见。“可见”的本质是目标元素与 viewport 是否有交叉区，所以这个 API 叫做“交叉观察器”。</p><p>实现方式</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">loadImg</span>(<span class="params">el</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!el.<span class="property">src</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> source = el.<span class="property">dataset</span>.<span class="property">src</span></span><br><span class="line">        el.<span class="property">src</span> = source</span><br><span class="line">        el.<span class="title function_">removeAttribute</span>(<span class="string">&#x27;data-src&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> io = <span class="keyword">new</span> <span class="title class_">IntersectionObserver</span>(<span class="function"><span class="params">entries</span> =&gt;</span> &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> entry <span class="keyword">of</span> entries) &#123;</span><br><span class="line">        <span class="keyword">const</span> el = entry.<span class="property">target</span></span><br><span class="line">        <span class="keyword">const</span> intersectionRatio = entry.<span class="property">intersectionRatio</span></span><br><span class="line">        <span class="keyword">if</span> (intersectionRatio &gt; <span class="number">0</span> &amp;&amp; intersectionRatio &lt;= <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="title function_">loadImg</span>(el)</span><br><span class="line">        &#125;</span><br><span class="line">        el.<span class="property">onload</span> = el.<span class="property">onerror</span> = <span class="function">() =&gt;</span> io.<span class="title function_">unobserve</span>(el)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">checkImgs</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> imgs = <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;img[data-src]&#x27;</span>))</span><br><span class="line">    imgs.<span class="title function_">forEach</span>(<span class="function"><span class="params">item</span> =&gt;</span> io.<span class="title function_">observe</span>(item))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="IntersectionObserver">IntersectionObserver</h2><p>IntersectionObserver 的作用就是检测一个元素是否可见，以及元素什么时候进入或者离开浏览器视口。</p><h3 id="兼容性">兼容性</h3><ul><li>Chrome 51+（发布于 2016-05-25）</li><li>Android 5+ （Chrome 56 发布于 2017-02-06）</li><li>Edge 15 （2017-04-11）</li><li>iOS 不支持</li></ul><h3 id="Polyfill">Polyfill</h3><p>WICG 提供了一个 <a href="https://github.com/w3c/IntersectionObserver">polyfill</a></p><h3 id="API">API</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> io = <span class="keyword">new</span> <span class="title class_">IntersectionObserver</span>(callback, option)</span><br></pre></td></tr></table></figure><p>IntersectionObserver 是一个构造函数，接受两个参数，第一个参数是可见性变化时的回调函数，第二个参数定制了一些关于可见性的参数（可选），IntersectionObserver 实例化后返回一个观察器，可以指定观察哪些 DOM 节点。</p><p>下面是一个最简单的应用：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 获取 img</span></span><br><span class="line"><span class="keyword">const</span> img = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;img&#x27;</span>)</span><br><span class="line"><span class="comment">// 2. 实例化 IntersectionObserver，添加 img 出现在 viewport 瞬间的回调</span></span><br><span class="line"><span class="keyword">const</span> observer =  <span class="keyword">new</span> <span class="title class_">IntersectionObserver</span>(<span class="function"><span class="params">changes</span> =&gt;</span> &#123; </span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;我出现了！&#x27;</span>) </span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 3. 开始监听 img</span></span><br><span class="line">observer.<span class="title function_">observe</span>(img)</span><br></pre></td></tr></table></figure><p>(1) callback</p><p>回调 callback 接受一个数组作为参数，数组元素是 IntersectionObserverEntry 对象。IntersectionObserverEntry 对象上有7个属性，</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">IntersectionObserverEntry</span> &#123;</span><br><span class="line"><span class="attr">time</span>: <span class="number">72.15500000000002</span>, </span><br><span class="line"><span class="attr">rootBounds</span>: <span class="title class_">ClientRect</span>, </span><br><span class="line"><span class="attr">boundingClientRect</span>: <span class="title class_">ClientRect</span>, </span><br><span class="line"><span class="attr">intersectionRatio</span>: <span class="number">0.4502074718475342</span>,</span><br><span class="line"><span class="attr">intersectionRect</span>: <span class="title class_">ClientRect</span>, </span><br><span class="line"><span class="attr">isIntersecting</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="attr">target</span>: img</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>boundingClientRect: 对 observe 的元素执行 getBoundingClientRect 的结果</li><li>rootBounds: 对根视图执行 getBoundingClientRect 的结果</li><li>intersectionRect: 目标元素与视口（或根元素）的交叉区域的信息</li><li>target: observe 的对象，如上述代码就是 img</li><li>time: 过了多久才出现在 viewport 内</li><li>intersectionRatio：目标元素的可见比例，intersectionRect 占 boundingClientRect 的比例，完全可见时为1，完全不可见时小于等于0</li><li>isIntersecting: 目标元素是否处于视口中</li></ul><p>(2) option</p><p>假如我们需要特殊的触发条件，比如元素可见性为一半的时候触发，或者我们需要更改根元素，这时就需要配置第二个参数 option 了。</p><p>通过设置 option 的 threshold 改变回调函数的触发条件，threshold 是一个范围为0到1数组，默认值是[0]，也就是在元素可见高度变为0时就会触发。如果赋值为 [0, 0.5, 1]，那回调就会在元素可见高度是0%，50%，100%时，各触发一次回调。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> observer =  <span class="keyword">new</span> <span class="title class_">IntersectionObserver</span>(<span class="function">(<span class="params">changes</span>) =&gt;</span> &#123; </span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(changes.<span class="property">length</span>); </span><br><span class="line">&#125;, &#123;</span><br><span class="line">  <span class="attr">root</span>: <span class="literal">null</span>, </span><br><span class="line">  <span class="attr">rootMargin</span>: <span class="string">&#x27;20px&#x27;</span>, </span><br><span class="line">  <span class="attr">threshold</span>: [<span class="number">0</span>, <span class="number">0.5</span>, <span class="number">1</span>]</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>root 参数默认是 null，也就是浏览器的 viewport，可以设置为其它元素，rootMargin 参数可以给 root 元素添加一个 margin，如 <code>rootMargin: '20px'</code> 时，回调会在元素出现前 20px 提前调用，消失后延迟 20px 调用回调。</p><p>(3) 观察器</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 开始观察</span></span><br><span class="line">io.<span class="title function_">observe</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 观察多个 DOM 元素</span></span><br><span class="line">io.<span class="title function_">observe</span>(elementA)</span><br><span class="line">io.<span class="title function_">observe</span>(elementB)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 停止观察</span></span><br><span class="line">io.<span class="title function_">unobserve</span>(element)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 关闭观察器</span></span><br><span class="line">io.<span class="title function_">disconnect</span>()</span><br></pre></td></tr></table></figure><h3 id="使用-IntersectionObserver-优势">使用 IntersectionObserver 优势</h3><p>使用前两种方式实现 lazyload 都需要监听浏览器 scroll 事件，而且要对每个目标元素执行 getBoundingClientRect() 方法以获取所需信息，这些代码都在主线程上运行，所以可能造成性能问题。</p><p>Intersection Observer API 会注册一个回调方法，每当期望被监视的元素进入或者退出另外一个元素的时候(或者浏览器的视口)该回调方法将会被执行，或者两个元素的交集部分大小发生变化的时候回调方法也会被执行。通过这种方式，网站将不需要为了监听两个元素的交集变化而在主线程里面做任何操作，并且浏览器可以帮助我们优化和管理两个元素的交集变化。</p><h2 id="参考资料">参考资料</h2><ol><li><a href="https://juejin.im/entry/599a78be6fb9a0247e424d67">原生 JS 实现最简单的图片懒加载</a></li><li><a href="https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10">IntersectionObserver</a></li><li><a href="http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html">IntersectionObserver API 使用教程</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API#Browser_compatibility">MDN-Intersection Observer API</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lz5z.com/lazyload&quot;&gt;demo地址&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;懒加载&quot;&gt;懒加载&lt;/h2&gt;
&lt;p&gt;Lazyload 可以加快网页访问速度，减少请求，实现思路就是判断图片元素是否可见来决定是否加载图片。当图片位于浏览器视口 (viewport) 中时，动态设置 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 标签的 src 属性，浏览器会根据 src 属性发送请求加载图片。&lt;/p&gt;
&lt;h2 id=&quot;懒加载实现&quot;&gt;懒加载实现&lt;/h2&gt;
&lt;p&gt;首先不设置 src 属性，将图片真正的 url 放在另外一个属性 data-src 中，在图片即将进入浏览器可视区域之前，将 url 取出放到 src 中。&lt;/p&gt;
&lt;p&gt;懒加载的关键是&lt;strong&gt;如何判断图片处于浏览器可视范围内&lt;/strong&gt;，通常有三种方法：&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="lazyload" scheme="https://lz5z.com/tags/lazyload/"/>
    
    <category term="getBoundingClientRect" scheme="https://lz5z.com/tags/getBoundingClientRect/"/>
    
    <category term="IntersectionObserver" scheme="https://lz5z.com/tags/IntersectionObserver/"/>
    
  </entry>
  
  <entry>
    <title>service worker 使用</title>
    <link href="https://lz5z.com/service-worker%E5%AD%A6%E4%B9%A0/"/>
    <id>https://lz5z.com/service-worker%E5%AD%A6%E4%B9%A0/</id>
    <published>2017-07-09T12:05:46.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<h2 id="service-worker-简介">service worker 简介</h2><p>service worker 的功能和特性可以总结为以下几点：</p><ol><li>service worker 是一个独立 worker 线程，独立于当前网页进程，有自己独立的 worker context</li><li>service worker 的线程能力基于 webworker 而生，通过 postMessage 和 onMessage 进行线程之间的通信；缓存机制是依赖 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Cache">cache API</a> 实现的。service worker = webworker + cache API</li><li>一旦被 install 之后，就永远存在，除非被 uninstall；需要的时候可以直接唤醒，不需要的时候自动睡眠</li><li>可以可编程拦截代理请求( https 请求)和缓存文件，缓存的文件直接可以被网页进程取到（包括网络离线状态）</li><li>离线内容开发者可控；能向客户端推送消息；不能直接操作 dom</li><li>必须在 https 环境下才能工作，当然 localhost 或者 127.0.0.1 也是 ok 的</li><li>service worker 是异步的，内部通过 Promise 实现， localStorage 是同步的，因此 service worker 内不许用使用 loaclStorage</li><li>依赖 HTML5 fetch API 和 Promise</li></ol><span id="more"></span><h1>service worker 使用</h1><h2 id="注册">注册</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="string">&#x27;serviceWorker&#x27;</span> <span class="keyword">in</span> navigator) &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;load&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        navigator.<span class="property">serviceWorker</span>.<span class="title function_">register</span>(<span class="string">&#x27;/sw.js&#x27;</span>, &#123;<span class="attr">scope</span>: <span class="string">&#x27;/&#x27;</span>&#125;)</span><br><span class="line">            .<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">registration</span>) &#123;</span><br><span class="line">                <span class="comment">// 注册成功</span></span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;ServiceWorker registration successful with scope: &#x27;</span>, </span><br><span class="line">                    registration.<span class="property">scope</span>);</span><br><span class="line">            &#125;)</span><br><span class="line">            .<span class="title function_">catch</span>(<span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">                <span class="comment">// 注册失败:(</span></span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;ServiceWorker registration failed: &#x27;</span>, err);</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>每次页面加载成功后，就会调用 register() 方法，浏览器将会判断 service worker 线程是否已注册并做出相应的处理。<br>scope 参数是可选的，默认值为 <code>sw.js</code> 所在的文件目录。<br>打开 chrome 浏览器, 输入 chrome://inspect/#service-workers 可以可以用 DevTools 查看 Service workers 的工作情况。</p><h2 id="安装">安装</h2><p>service worker 注册后，浏览器就会尝试安装并激活它，并且在这里完成静态资源的缓存。</p><p>所以我们在 <code>sw.js</code> 中添加 install 事件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;install&#x27;</span>, <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">    event.<span class="title function_">waitUntil</span>(</span><br><span class="line">        caches.<span class="title function_">open</span>(<span class="string">&#x27;my-test-cache-v1&#x27;</span>).<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">cache</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> cache.<span class="title function_">addAll</span>([</span><br><span class="line">                <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;/index.html&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;/main.css&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;/index.js&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;/sw-lifecycle.jpg&#x27;</span></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>install 事件一般是被用来完成浏览器的离线缓存功能，service worker 的缓存机制是依赖 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Cache">cache API</a> 实现的。cache API 为绑定在 service worker 上的全局对象，可以用来存储网络响应发来的资源，这些资源只在站点域名内有效，并且一直存在，直到你告诉它不再存储。</p><h3 id="缓存和返回请求">缓存和返回请求</h3><p>每次任何被 service worker 控制的资源被请求到时，都会触发 fetch 事件，因此我们可以利用 fetch 事件对资源响应做一些拦截操作</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;fetch&#x27;</span>, <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">    event.<span class="title function_">respondWith</span>(</span><br><span class="line">        caches.<span class="title function_">match</span>(event.<span class="property">request</span>).<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">response</span>) &#123;</span><br><span class="line">            <span class="comment">// 如果 service worker 有自己的返回，就直接返回，减少一次 http 请求</span></span><br><span class="line">            <span class="keyword">if</span> (response) &#123;</span><br><span class="line">                <span class="keyword">return</span> response;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 如果 service worker 没有返回，从服务器请求资源</span></span><br><span class="line">            <span class="keyword">var</span> request = event.<span class="property">request</span>.<span class="title function_">clone</span>(); <span class="comment">// 把原始请求拷过来</span></span><br><span class="line">            <span class="keyword">return</span> <span class="title function_">fetch</span>(request).<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">httpRes</span>) &#123;</span><br><span class="line">                <span class="comment">// 请求失败了，直接返回失败的结果就好了。。</span></span><br><span class="line">                <span class="keyword">if</span> (!httpRes &amp;&amp; httpRes.<span class="property">status</span> !== <span class="number">200</span>) &#123;</span><br><span class="line">                    <span class="keyword">return</span> response;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 请求成功的话，再一次缓存起来。</span></span><br><span class="line">                <span class="keyword">var</span> responseClone = httpRes.<span class="title function_">clone</span>();</span><br><span class="line">                caches.<span class="title function_">open</span>(<span class="string">&#x27;my-test-cache-v1&#x27;</span>).<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">cache</span>) &#123;</span><br><span class="line">                    cache.<span class="title function_">put</span>(event.<span class="property">request</span>, responseClone);</span><br><span class="line">                &#125;);</span><br><span class="line">                <span class="keyword">return</span> httpRes;</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>这样看来，其实可以把 service worker 理解为一个浏览器端的代理服务器，这个代理服务器通过 scope 和 fetch 事件来 hook 站点的请求，来达到资源缓存的功能。</p><p>注意：request 和 response 不能直接使用而是通过 clone 的方式使用是因为他们是 stream，因此只能使用一次。</p><h3 id="install-vs-fetch">install vs fetch</h3><ul><li>install 的优点是第二次访问即可离线，缺点是需要将需要缓存的资源 URL 在编译时插入到脚本中，增加代码量和降低可维护性；</li><li>fetch 的优点是无需更改编译过程，也不会产生额外的流量，缺点是需要多一次访问才能离线可用。</li></ul><h2 id="service-worker-更新">service worker 更新</h2><p><code>/sw.js</code> 控制着页面资源和请求的缓存，如果 <code>/sw.js</code> 需要更新应该怎么办呢？</p><ul><li>service worker 控制着整个 App 的离线缓存。 为了避免 service worker 缓存自己导致死锁无法升级，通常将 sw.js 本身的缓存直接交给 HTTP 服务器缓存。</li><li>更新 <code>sw.js</code> 文件，当浏览器获取到了新的文件，发现 <code>sw.js</code> 文件发生更新，就会安装新的文件并触发 install 事件。</li><li>但是此时已经处于激活状态的旧的 service worker 还在运行，新的 service worker 完成安装后会进入 waiting 状态，直到所有已打开的页面都关闭。</li><li>新服务工作线程取得控制权后，将会触发其 activate 事件。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 安装阶段跳过等待，直接进入 activate</span></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&#x27;install&#x27;</span>, <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">    event.<span class="title function_">waitUntil</span>(self.<span class="title function_">skipWaiting</span>());</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&#x27;activate&#x27;</span>, <span class="keyword">function</span> (<span class="params">evnet</span>) &#123;</span><br><span class="line">    event.<span class="title function_">waitUntil</span>(</span><br><span class="line">        <span class="title class_">Promise</span>.<span class="title function_">all</span>([</span><br><span class="line">            <span class="comment">// 更新客户端</span></span><br><span class="line">            self.<span class="property">clients</span>.<span class="title function_">claim</span>(),</span><br><span class="line">            <span class="comment">// 清理旧版本</span></span><br><span class="line">            caches.<span class="title function_">keys</span>().<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">cacheList</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(</span><br><span class="line">                    cacheList.<span class="title function_">map</span>(<span class="keyword">function</span> (<span class="params">cacheName</span>) &#123;</span><br><span class="line">                        <span class="keyword">if</span> (cacheName !== <span class="string">&#x27;my-test-cache-v1&#x27;</span>) &#123;</span><br><span class="line">                            <span class="keyword">return</span> caches.<span class="title function_">delete</span>(cacheName);</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">        ])</span><br><span class="line">    );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>注意：如果 <code>sw.js</code> 文件被浏览器缓存，则可能导致更新得不到响应。如遇到该问题，可尝试这么做：在 webserver 上添加对该文件的过滤规则，不缓存或设置较短的有效期。</p><h3 id="手动更新-sw-js">手动更新 /sw.js</h3><p>也可以借助 <code>Registration.update()</code> 手动更新</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> version = <span class="string">&#x27;1.0.1&#x27;</span>;</span><br><span class="line"></span><br><span class="line">navigator.<span class="property">serviceWorker</span>.<span class="title function_">register</span>(<span class="string">&#x27;/sw.js&#x27;</span>).<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params">reg</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;sw_version&#x27;</span>) !== version) &#123;</span><br><span class="line">        reg.<span class="title function_">update</span>().<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">            <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;sw_version&#x27;</span>, version)</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><h3 id="自动更新">自动更新</h3><p>除了浏览器触发更新之外，service worker 还有一个特殊的缓存策略： 如果该文件已 24 小时没有更新，当 Update 触发时会强制更新。这意味着最坏情况下 service worker 会每天更新一次。</p><h3 id="调试时更新">调试时更新</h3><p>可以单独设置调试时 service worker 安装后立即激活：</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></pre></td><td class="code"><pre><span class="line">self.<span class="title function_">addEventListener</span>(<span class="string">&#x27;install&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    self.<span class="title function_">skipWaiting</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="service-worker-生命周期">service worker 生命周期</h2><img src="/assets/img/sw-lifecycle.png" alt="sw-lifecycle"><h3 id="service-worker-工作流程">service worker 工作流程</h3><p>service worker 基于注册、安装、激活等步骤在浏览器 js 主线程中独立分担缓存任务。</p><ul><li>首先在页面的 javaScript 主线程中使用 navigator.serviceWorker.register() 来注册 servcie worker。</li><li>如果注册成功，service worker 在 ServiceWorkerGlobalScope 环境中运行； 这是一个特殊的 worker context，与主脚本的运行线程相独立，同时也没有访问 DOM 的能力。</li><li>后台开始安装步骤，通常在安装的过程中需要缓存一些静态资源。install 事件回调中有两个方法：<ul><li>event.waitUntil()：传入一个 Promise 为参数，等到该 Promise 为 resolve 状态为止。</li><li>self.skipWaiting()：self 是当前 context 的 global 变量，执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。</li></ul></li><li>当 service worker 安装完成后，会接收到一个激活事件（activate event）。激活事件的处理函数中，主要操作是清理旧版本的 service worker 脚本中使用资源。activate 回调中有两个方法：<ul><li>event.waitUntil()：传入一个 Promise 为参数，等到该 Promise 为 resolve 状态为止。</li><li>self.clients.claim()：在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面，之后会被停止。</li></ul></li><li>激活成功后 service worker 可以控制页面了，刷新页面可以查看 service worker 的工作成果。</li></ul><h3 id="service-worker-事件">service worker 事件</h3><ul><li>install: service worker 安装成功后被触发的事件，在事件处理函数中可以添加需要缓存的文件。</li><li>activate：当 service worker 安装完成后并进入激活状态，会触发 activate 事件。通过监听 activate 事件你可以做一些预处理，如对于旧版本的更新、对于无用缓存的清理等。</li><li>message：service worker 通过 postMessage API，可以实现与主线程之间的通信。</li></ul><p>下面是一个使用 service worker 的 postMessage API 做的一个简单计算器，其中计算部分在 service worker 线程中完成。假如有一些比较耗时的工作，比如大量计算，或者 fetch 数据，可以将其放入 service worker 线程中，以达到提高页面响应的目的。</p><iframe defer src="https://lz5z.com/service_worker_postMessage/" frameBorder=0 marginwidth=0 marginheight=0 scrolling=no style="width:500px;height:50px;" width=500  height=50 scrolling=no ALLOWTRANSPARENCY="true"></iframe><p><a href="https://lz5z.com/service_worker_postMessage/">在线演示</a><br><a href="https://github.com/Leo555/service_worker_postMessage">源码</a></p><ul><li>fetch (请求)：当浏览器在当前指定的 scope 下发起请求时，会触发 fetch 事件，并得到传有 response 参数的回调函数，回调中就可以做各种代理缓存的事情了。</li><li>push (推送)：push 事件是为推送准备的。不过首先需要了解一下 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/notification">Notification API</a> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Push_API">PUSH API</a>。通过 PUSH API，当订阅了推送服务后，可以使用推送方式唤醒 Service Worker 以响应来自系统消息传递服务的消息，即使用户已经关闭了页面。</li></ul><h2 id="示例">示例</h2><p>这个<a href="http://service-worker.org/">网站</a>记录了很多 service worker demo。</p><h2 id="参考文档">参考文档</h2><ul><li><a href="https://lavas.baidu.com/doc/offline-and-cache-loading/service-worker/service-worker-introduction">lavas</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API">Service Worker API-MDN</a></li><li><a href="https://developers.google.com/web/fundamentals/getting-started/primers/service-workers?hl=zh-cn">服务工作线程</a></li><li><a href="http://harttle.com/2017/04/10/service-worker-update.html">Service Worker 更新机制</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;service-worker-简介&quot;&gt;service worker 简介&lt;/h2&gt;
&lt;p&gt;service worker 的功能和特性可以总结为以下几点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;service worker 是一个独立 worker 线程，独立于当前网页进程，有自己独立的 worker context&lt;/li&gt;
&lt;li&gt;service worker 的线程能力基于 webworker 而生，通过 postMessage 和 onMessage 进行线程之间的通信；缓存机制是依赖 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Cache&quot;&gt;cache API&lt;/a&gt; 实现的。service worker = webworker + cache API&lt;/li&gt;
&lt;li&gt;一旦被 install 之后，就永远存在，除非被 uninstall；需要的时候可以直接唤醒，不需要的时候自动睡眠&lt;/li&gt;
&lt;li&gt;可以可编程拦截代理请求( https 请求)和缓存文件，缓存的文件直接可以被网页进程取到（包括网络离线状态）&lt;/li&gt;
&lt;li&gt;离线内容开发者可控；能向客户端推送消息；不能直接操作 dom&lt;/li&gt;
&lt;li&gt;必须在 https 环境下才能工作，当然 localhost 或者 127.0.0.1 也是 ok 的&lt;/li&gt;
&lt;li&gt;service worker 是异步的，内部通过 Promise 实现， localStorage 是同步的，因此 service worker 内不许用使用 loaclStorage&lt;/li&gt;
&lt;li&gt;依赖 HTML5 fetch API 和 Promise&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="HTML" scheme="https://lz5z.com/categories/HTML/"/>
    
    
    <category term="HTML5" scheme="https://lz5z.com/tags/HTML5/"/>
    
    <category term="service worker" scheme="https://lz5z.com/tags/service-worker/"/>
    
    <category term="离线缓存" scheme="https://lz5z.com/tags/%E7%A6%BB%E7%BA%BF%E7%BC%93%E5%AD%98/"/>
    
    <category term="postMessage" scheme="https://lz5z.com/tags/postMessage/"/>
    
  </entry>
  
  <entry>
    <title>hexo 支持 emoji</title>
    <link href="https://lz5z.com/hexo%E6%94%AF%E6%8C%81emoji/"/>
    <id>https://lz5z.com/hexo%E6%94%AF%E6%8C%81emoji/</id>
    <published>2017-07-05T08:24:06.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<h3 id="添加方法">添加方法</h3><p>很简单，换一个 markdown 引擎，然后再增加 emoji 插件即可。😊</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm un hexo-renderer-marked --save</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm i hexo-renderer-markdown-it --save</span> </span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install markdown-it-emoji --save</span></span><br></pre></td></tr></table></figure><p>据说 <a href="https://github.com/hexojs/hexo-renderer-markdown-it">hexo-renderer-markdown-it</a> 的速度要比 Hexo 原装插件要快，而且功能更多：</p><span id="more"></span><blockquote><p>Main Features</p><ul><li>Support for <a href="http://daringfireball.net/projects/markdown/">Markdown</a>, <a href="https://help.github.com/articles/github-flavored-markdown/">GFM</a> and <a href="http://commonmark.org/">CommonMark</a></li><li>Extensive configuration</li><li>Faster than the default renderer | <code>hexo-renderer-marked</code></li><li>Safe ID for headings</li><li>Anchors for headings with ID</li><li>Footnotes</li><li><code>&lt;sub&gt;</code> H<sub>2</sub>O</li><li><code>&lt;sup&gt;</code> x<sup>2</sup></li><li><code>&lt;ins&gt;</code> <ins>Inserted</ins></li></ul></blockquote><p>然后编辑 <code>_config.yml</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">markdown:</span><br><span class="line">  plugins:</span><br><span class="line">    - markdown-it-footnote</span><br><span class="line">    - markdown-it-sup</span><br><span class="line">    - markdown-it-sub</span><br><span class="line">    - markdown-it-abbr</span><br><span class="line">    - markdown-it-emoji</span><br></pre></td></tr></table></figure><h3 id="使用方法">使用方法</h3><ol><li>在 <a href="https://emoji.codes/">Emoji</a> 中找到你想要的表情，然后点击即可复制。</li><li>比如你想发一个笑脸 😄 直接输入笑脸对应的 emoji 编码 <code>:smile:</code> 就可以。</li></ol>]]></content>
    
    
    <summary type="html">&lt;h3 id=&quot;添加方法&quot;&gt;添加方法&lt;/h3&gt;
&lt;p&gt;很简单，换一个 markdown 引擎，然后再增加 emoji 插件即可。😊&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;npm un hexo-renderer-marked --save&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;npm i hexo-renderer-markdown-it --save&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;npm install markdown-it-emoji --save&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;据说 &lt;a href=&quot;https://github.com/hexojs/hexo-renderer-markdown-it&quot;&gt;hexo-renderer-markdown-it&lt;/a&gt; 的速度要比 Hexo 原装插件要快，而且功能更多：&lt;/p&gt;</summary>
    
    
    
    <category term="Blog" scheme="https://lz5z.com/categories/Blog/"/>
    
    
    <category term="Hexo" scheme="https://lz5z.com/tags/Hexo/"/>
    
    <category term="emoji" scheme="https://lz5z.com/tags/emoji/"/>
    
  </entry>
  
  <entry>
    <title>为什么 call 的速度快于 apply</title>
    <link href="https://lz5z.com/call-faster-than-apply/"/>
    <id>https://lz5z.com/call-faster-than-apply/</id>
    <published>2017-06-29T08:43:04.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<p>在 stackoverflow 看到一个有趣的问题: <a href="https://stackoverflow.com/questions/23769556/why-is-call-so-much-faster-than-apply">Why is call so much faster than apply?</a> 于是使用 <a href="https://benchmarkjs.com/">benchmark.js</a> 在 node 中自己测试了一下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Benchmark</span> = <span class="built_in">require</span>(<span class="string">&#x27;benchmark&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> suite = <span class="keyword">new</span> <span class="title class_">Benchmark</span>.<span class="property">Suite</span></span><br><span class="line"><span class="keyword">const</span> applyFun = <span class="keyword">function</span> (<span class="params">str</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [].<span class="property">slice</span>.<span class="title function_">apply</span>(str, [<span class="number">1</span>])</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> callFun = <span class="keyword">function</span> (<span class="params">str</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [].<span class="property">slice</span>.<span class="title function_">call</span>(str, <span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// add tests</span></span><br><span class="line">suite.<span class="title function_">add</span>(<span class="string">&#x27;apply&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">applyFun</span>(<span class="string">&#x27;apple&#x27;</span>)</span><br><span class="line">&#125;).<span class="title function_">add</span>(<span class="string">&#x27;call&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">callFun</span>(<span class="string">&#x27;apple&#x27;</span>)</span><br><span class="line">&#125;).<span class="title function_">on</span>(<span class="string">&#x27;cycle&#x27;</span>, <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">String</span>(event.<span class="property">target</span>))</span><br><span class="line">&#125;).<span class="title function_">on</span>(<span class="string">&#x27;complete&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Fastest is &#x27;</span> + <span class="variable language_">this</span>.<span class="title function_">filter</span>(<span class="string">&#x27;fastest&#x27;</span>).<span class="title function_">map</span>(<span class="string">&#x27;name&#x27;</span>))</span><br><span class="line">&#125;).<span class="title function_">run</span>(&#123;<span class="string">&#x27;async&#x27;</span>: <span class="literal">true</span>&#125;)</span><br></pre></td></tr></table></figure><span id="more"></span><h3 id="测试环境：">测试环境：</h3><p>系统：macOS Sierra<br>CPU：2.6 GHz Intel Core i5<br>内存：8 GB 1600 MHz DDR3<br>Node: 8.1.0</p><h3 id="测试结果：">测试结果：</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">apply x 951,707 ops/sec ±0.46% (87 runs sampled)</span><br><span class="line">call x 969,699 ops/sec ±0.52% (91 runs sampled)</span><br><span class="line">Fastest is call</span><br></pre></td></tr></table></figure><p>可见虽然 call 比 apply 要快一些，但是差别并不是很大，那么在浏览器上面表现如何呢？</p><img src="/assets/img/call_vs_apply.jpg" alt="call_vs_apply_in_browsers"><p>你也可以点击下面的 button 在自己的浏览器上查看运行效果。</p><!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>call vs apply</title>    <script defer src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>    <script defer src="https://cdnjs.cloudflare.com/ajax/libs/platform/1.3.4/platform.min.js"></script>    <script defer src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.4/benchmark.min.js"></script>    <script>    window.onload = () => {        let log = document.getElementById("log")        let btn = document.getElementById("btn")        const applyFun = str => {            return [].slice.apply(str, [1])        }        const callFun = str => {            return [].slice.call(str, 1)        }        const logMessage = arg => {            let eDiv = document.createElement("div")            eDiv.innerHTML = typeof arg === "string" ? arg : arg.toString()            log.appendChild(eDiv)        }        const prepare = () => {            log.innerHTML = ''            btn.disabled = true        }        window.run = () => {            prepare()            const suite = new Benchmark.Suite            // add tests            suite.add('apply', function() {                applyFun('apple')            }).add('call', function() {                callFun('apple')            }).on('cycle', function(event) {                logMessage(String(event.target))            }).on('complete', function() {                logMessage('Fastest is ' + this.filter('fastest').map('name'))                btn.disabled = false            }).run({                'async': true            })        }    }    </script></head><body>    <button id="btn" onclick="run()">run test</button>    <span id="log"></span>    <hr></body></html><p>可以看到几个浏览器中都是 call 的速度要快于 apply，不过都没有特别明显。其中 Safari 的速度让我大吃一惊，直接比其它几个浏览器快了一个数量级。看来 WWDC 2017 发布会上苹果吹的牛没有那么大啊，不过也可能 mac 从硬件层面对 Safari 进行优化。</p><h3 id="为什么-call-要快于-apply">为什么 call 要快于 apply</h3><p>SO 上面解释的比较详细，在语言设计的时候，apply 需要执行的步数就比 call 要多：无论 call 还是 apply，最终都是调用一个叫做 <code>[[Call]]</code> 的内部函数，而 apply 相对于 call 多做了一些参数处理，如参数判断、格式化等。</p><h3 id="困惑">困惑</h3><p>SO 上面提到 call 的性能是 apply 的 4 倍甚至 30 倍，为什么在我这里的测试只有一丁点差距呢？<br>突然想到是否参数问题，于是去掉参数和增加参数，分别于 node 环境中测试，发现变化并不大，差距依然很小。那么猜想可能是 ES5 与 ES6 的差距导致的。</p><p>对比 <a href="http://es5.github.io/#x15.3.4.3">ES5</a> 和 <a href="http://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.apply">ES6</a> 中对这两个函数的定义，发现 Function.prototype.call 的变化并不大，主要变化发生在 Function.prototype.apply 上，从 ES5 的 9 步变成了 ES6 的 6步。主要变化发生在对参数处理的部分，其它关于内部函数调用的部分，看起来并没有太多差异。</p><h3 id="总结">总结</h3><p>通过测试发现随着 ECMAScript 语言和 JavaScript 解释器性能不断增强，call vs apply 在性能上的差距越来越小， SO 上面提到的数倍甚至几十倍的差距，目前已经不存在了，因此在使用上可以随心所欲了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 stackoverflow 看到一个有趣的问题: &lt;a href=&quot;https://stackoverflow.com/questions/23769556/why-is-call-so-much-faster-than-apply&quot;&gt;Why is call so much faster than apply?&lt;/a&gt; 于是使用 &lt;a href=&quot;https://benchmarkjs.com/&quot;&gt;benchmark.js&lt;/a&gt; 在 node 中自己测试了一下：&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Benchmark&lt;/span&gt; = &lt;span class=&quot;built_in&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;benchmark&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; suite = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Benchmark&lt;/span&gt;.&lt;span class=&quot;property&quot;&gt;Suite&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; applyFun = &lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;params&quot;&gt;str&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; [].&lt;span class=&quot;property&quot;&gt;slice&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;apply&lt;/span&gt;(str, [&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; callFun = &lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;params&quot;&gt;str&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; [].&lt;span class=&quot;property&quot;&gt;slice&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;call&lt;/span&gt;(str, &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// add tests&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;suite.&lt;span class=&quot;title function_&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;apply&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;params&quot;&gt;&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;title function_&quot;&gt;applyFun&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;apple&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;).&lt;span class=&quot;title function_&quot;&gt;add&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;call&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;params&quot;&gt;&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;title function_&quot;&gt;callFun&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;apple&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;).&lt;span class=&quot;title function_&quot;&gt;on&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;cycle&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;params&quot;&gt;event&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;title class_&quot;&gt;String&lt;/span&gt;(event.&lt;span class=&quot;property&quot;&gt;target&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;).&lt;span class=&quot;title function_&quot;&gt;on&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;complete&amp;#x27;&lt;/span&gt;, &lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; (&lt;span class=&quot;params&quot;&gt;&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;variable language_&quot;&gt;console&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;Fastest is &amp;#x27;&lt;/span&gt; + &lt;span class=&quot;variable language_&quot;&gt;this&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;filter&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;fastest&amp;#x27;&lt;/span&gt;).&lt;span class=&quot;title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;name&amp;#x27;&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;).&lt;span class=&quot;title function_&quot;&gt;run&lt;/span&gt;(&amp;#123;&lt;span class=&quot;string&quot;&gt;&amp;#x27;async&amp;#x27;&lt;/span&gt;: &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;&amp;#125;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="call" scheme="https://lz5z.com/tags/call/"/>
    
    <category term="apply" scheme="https://lz5z.com/tags/apply/"/>
    
  </entry>
  
  <entry>
    <title>CSS Modules 学习</title>
    <link href="https://lz5z.com/CSS_Modules/"/>
    <id>https://lz5z.com/CSS_Modules/</id>
    <published>2017-06-21T02:39:32.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>CSS Modules 的用法很简单，不过现阶段还需要 webpack 的支持。CSS Modules 的功能非常少，它一共就干了两件事：局部作用域和模块依赖。</p><h2 id="CSS-Modules-示例">CSS Modules 示例</h2><p><a href="https://github.com/Leo555/css_modules_study">代码地址</a></p><p>项目路径</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">├── README.md</span><br><span class="line">├── index.html</span><br><span class="line">├── node_modules</span><br><span class="line">│   └── ...</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│   ├── animation.css</span><br><span class="line">│   ├── colors.css</span><br><span class="line">│   ├── index.js</span><br><span class="line">│   └── style.css</span><br><span class="line">└── webpack.config.js</span><br></pre></td></tr></table></figure><span id="more"></span><p>把文件 clone 下来后，安装依赖，然后就可以运行了</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git <span class="built_in">clone</span> https://github.com/Leo555/css_modules_study.git</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> css_modules_study</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm run start</span></span><br></pre></td></tr></table></figure><p>浏览器会自动打开静态文件，方便查看效果。</p><h2 id="CSS-Modules-用法">CSS Modules 用法</h2><h3 id="webpack">webpack</h3><p>首先配置 webpack 环境(本文使用webpack2)，给 css-loader 增加一个 modules 查询参数，表示打开 CSS Modules 功能。</p><p>简单的示例如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">  <span class="attr">rules</span>: [&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">    <span class="attr">loader</span>: <span class="string">&quot;style-loader!css-loader?modules&quot;</span></span><br><span class="line">  &#125;]</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>如果需要给 CSS Modules 传递一些参数，可以用对象的方式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">  <span class="attr">rules</span>: [&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">    <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, </span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">loader</span>: <span class="string">&quot;css-loader&quot;</span>,</span><br><span class="line">      <span class="attr">options</span>: &#123;</span><br><span class="line">        <span class="attr">modules</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">localIdentName</span>: <span class="string">&#x27;[path][name]__[local]--[hash:base64:5]&#x27;</span>,</span><br><span class="line">        <span class="attr">importLoaders</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="attr">camelCase</span>: <span class="literal">true</span></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><h3 id="作用域">作用域</h3><p>开启 CSS Modules 后，所有的 CSS 选择器都是局部作用域，除非声明它为全局的。</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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*Scoped Selectors*/</span></span><br><span class="line"><span class="selector-class">.className</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: green;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">10px</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*Global Selectors*/</span></span><br><span class="line">:<span class="built_in">global</span>(.text) &#123;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">22px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上两个 CSS class 通过如下方法被 JS 引用</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./style.css&quot;</span>;</span><br><span class="line"><span class="comment">// import &#123; className &#125; from &quot;./style.css&quot;;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> app = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;app&#x27;</span>)</span><br><span class="line">app.<span class="property">innerHTML</span> = </span><br><span class="line"><span class="string">`</span></span><br><span class="line"><span class="string">&lt;div class=&quot;<span class="subst">$&#123;styles.className&#125;</span>&quot;&gt;Hello CSS Modules&lt;/div&gt;</span></span><br><span class="line"><span class="string">&lt;div class=&quot;text&quot;&gt;Global Selectors&lt;/div&gt;</span></span><br><span class="line"><span class="string">`</span></span><br></pre></td></tr></table></figure><p>后面的引用方式都相同，因此略去，具体可查看 <a href="https://github.com/Leo555/css_modules_study/blob/master/src/index.js">index.js</a>。</p><p>查看构建后的 CSS，发现局部变量的名字被编译成 hash (<code>localIdentName: '[path][name]__[local]--[hash:base64:5]'</code>)，而全局变量的名字不变。</p><img src="/assets/img/css-modules.png" alt="css-modules"><p>原来 CSS Modules 就做了这么一点微小的工作。</p><h3 id="class-继承和重写">class 继承和重写</h3><p>CSS Modules 通过组合的方式进行集成，以达成代码复用的效果。</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*Class Composes*/</span></span><br><span class="line"><span class="selector-class">.otherClassName</span> &#123;</span><br><span class="line">  composes: className;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">220px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">60px</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">60px</span>;</span><br><span class="line">  <span class="attribute">border-width</span>: <span class="number">2px</span>;</span><br><span class="line">  <span class="attribute">border-style</span>: solid;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*Composes Overrides*/</span></span><br><span class="line"><span class="selector-class">.background</span> &#123;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#efefef</span>;</span><br><span class="line">  composes: otherClassName;</span><br><span class="line">  <span class="attribute">border-style</span>: dotted;</span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><p>otherClassName 继承 className，因为拥有了 color 和 margin 属性，而 background 继承 otherClassName，却重写了 border-style。</p><h3 id="局部动画">局部动画</h3><p>在 animation.css 中，定义了动画 tada：</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@keyframes</span> tada &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">scale3d</span>(<span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="number">10%</span>, <span class="number">20%</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">scale3d</span>(.<span class="number">9</span>, .<span class="number">9</span>, .<span class="number">9</span>) <span class="built_in">rotate3d</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, -<span class="number">3deg</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="number">30%</span>, <span class="number">50%</span>, <span class="number">70%</span>, <span class="number">90%</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">scale3d</span>(<span class="number">1.1</span>, <span class="number">1.1</span>, <span class="number">1.1</span>) <span class="built_in">rotate3d</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">3deg</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="number">40%</span>, <span class="number">60%</span>, <span class="number">80%</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">scale3d</span>(<span class="number">1.1</span>, <span class="number">1.1</span>, <span class="number">1.1</span>) <span class="built_in">rotate3d</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, -<span class="number">3deg</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">scale3d</span>(<span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.tada</span> &#123;</span><br><span class="line">  <span class="attribute">animation</span>: tada <span class="number">2s</span> infinite;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 style 中的引用方式如下：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/*Scoped Animations*/</span></span><br><span class="line"><span class="selector-class">.backgroundAnimation</span> &#123;</span><br><span class="line">  composes: background;</span><br><span class="line">  composes: tada from <span class="string">&#x27;./animation.css&#x27;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面第二个 composes 也展示了如何从其它 CSS 模块中引用选择器。</p><h3 id="定义变量">定义变量</h3><p>通过 @value 定义变量和引用变量</p><p>colors.css</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@value</span> <span class="attribute">color</span>: black;</span><br><span class="line"><span class="keyword">@value</span> <span class="attribute">fontSize</span>: <span class="number">22px</span>;</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><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*Define variables*/</span></span><br><span class="line"><span class="keyword">@value</span> <span class="attribute">colors</span>: <span class="string">&quot;./colors.css&quot;</span>;</span><br><span class="line"><span class="keyword">@value</span> <span class="attribute">color</span>, fontSize from colors;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.color</span> &#123;</span><br><span class="line">  composes: otherClassName;</span><br><span class="line">  <span class="attribute">color</span>: color;</span><br><span class="line">  <span class="attribute">font-size</span>: fontSize;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Vue-结合-CSS-Modules">Vue 结合 CSS Modules</h2><p>vue-loader 中集成了 CSS Modules，可以作为模拟 CSS 作用域的替代方案。</p><h3 id="使用">使用</h3><p>在 <code>&lt;style&gt;</code> 上添加 module 属性即可开启 CSS Modules 模式：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">module</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-class">.red</span> &#123;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">color</span>: red;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"><span class="selector-class">.bold</span> &#123;</span></span><br><span class="line"><span class="language-css">  <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css-loader 会自动将生成的 CSS 对象注入到 $style 中，只需要在 <code>&lt;template&gt;</code> 中使用动态 class 绑定：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span> <span class="attr">:class</span>=<span class="string">&quot;$style.red&quot;</span>&gt;</span></span><br><span class="line">    This should be red</span><br><span class="line">  <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><p>由于它是一个计算属性，它也适用于 <code>:class</code> 的 object/array 语法：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span> <span class="attr">:class</span>=<span class="string">&quot;&#123; [$style.red]: isRed &#125;&quot;</span>&gt;</span></span><br><span class="line">      Am I red?</span><br><span class="line">    <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span> <span class="attr">:class</span>=<span class="string">&quot;[$style.red, $style.bold]&quot;</span>&gt;</span></span><br><span class="line">      Red and bold</span><br><span class="line">    <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在 JavaScript 访问它：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">created</span> () &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">$style</span>.<span class="property">red</span>)</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="自定义注入名称">自定义注入名称</h3><p>在 .vue 中可以定义不止一个 <code>&lt;style&gt;</code>，为了避免被覆盖，你可以通过设置 module 属性来为它们定义注入后计算属性的名称。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">module</span>=<span class="string">&quot;a&quot;</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="comment">/* identifiers injected as a */</span></span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="总结">总结</h2><p>在前端模块化的道路上，CSS 显然落后 JS 非常多。ES2015/ES2016 的快速普及和 Babel/webpack 等工具的发展，让 JS 在大型项目工程化中越发强大，<a href="http://www.infoq.com/cn/news/2017/05/JavaScript-become-language">最终成为一流语言</a> 。</p><p>CSS Modules 解决了哪些问题呢？</p><ol><li>消除了全局命名的问题，再也不用担心不同文件之间的命名冲突了，也不用写一层又一层的选择器了。</li><li>通过 JS 去管理 CSS 之间的依赖，在引入组件的时候，可以只引入次组件需要的 CSS，组件更加独立。</li><li>CSS 变量 可以在 CSS 和 JS 中共享，对于复杂组件的使用会有奇效。</li><li>对代码压缩也有一定帮助。</li></ol><blockquote><p>CSS 模块化的解决方案有很多，但主要有两类。一类是彻底抛弃 CSS，使用 JS 或 JSON 来写样式。Radium，jsxstyle，react-style 属于这一类。优点是能给 CSS 提供 JS 同样强大的模块化能力；缺点是不能利用成熟的 CSS 预处理器（或后处理器） Sass/Less/PostCSS，:hover 和 :active 伪类处理起来复杂。另一类是依旧使用 CSS，但使用 JS 来管理样式依赖，代表是 CSS Modules。CSS Modules 能最大化地结合现有 CSS 生态和 JS 模块化能力，API 简洁到几乎零学习成本。发布时依旧编译出单独的 JS 和 CSS。它并不依赖于 React，只要你使用 Webpack，可以在 Vue/Angular/jQuery 中使用。是我认为目前最好的 CSS 模块化解决方案。</p></blockquote><p>引自 <a href="https://github.com/camsong/blog/issues/5">CSS Modules 详解及 React 中实践</a></p><h2 id="参考资料">参考资料</h2><ul><li><a href="https://github.com/camsong/blog/issues/5">CSS Modules 详解及 React 中实践</a></li><li><a href="https://github.com/css-modules/css-modules">css-modules</a></li><li><a href="http://www.ruanyifeng.com/blog/2016/06/css_modules.html">CSS Modules 用法教程</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;CSS Modules 的用法很简单，不过现阶段还需要 webpack 的支持。CSS Modules 的功能非常少，它一共就干了两件事：局部作用域和模块依赖。&lt;/p&gt;
&lt;h2 id=&quot;CSS-Modules-示例&quot;&gt;CSS Modules 示例&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Leo555/css_modules_study&quot;&gt;代码地址&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;项目路径&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;├── README.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── index.html&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── node_modules&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   └── ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── package.json&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── src&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   ├── animation.css&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   ├── colors.css&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   ├── index.js&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   └── style.css&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└── webpack.config.js&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="CSS" scheme="https://lz5z.com/categories/CSS/"/>
    
    
    <category term="CSS Modules" scheme="https://lz5z.com/tags/CSS-Modules/"/>
    
    <category term="webpack" scheme="https://lz5z.com/tags/webpack/"/>
    
    <category term="vue" scheme="https://lz5z.com/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>CSS3 动画—animation</title>
    <link href="https://lz5z.com/CSS3%E5%8A%A8%E7%94%BB%E2%80%94animation/"/>
    <id>https://lz5z.com/CSS3%E5%8A%A8%E7%94%BB%E2%80%94animation/</id>
    <published>2017-06-12T09:23:48.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>animation 属性目前还存在浏览器兼容性问题，建议使用 PostCSS 或手动添加浏览器前缀。本文学习使用 animation 属性创建简单动画。</p><!DOCTYPE html><html lang="en"><head>  <style>  .anima_div {    width: 60px;    height: 40px;    background: #92B901;    position: relative;    padding: 20px 10px 0px 10px;    animation: animated_div 5s infinite;    border-radius: 5px;  }  @keyframes animated_div {    0%  {transform: rotate(0deg);left:0px;}    25%  {transform: rotate(20deg);left:0px;}    50%  {transform: rotate(0deg);left:300px;}    55%  {transform: rotate(0deg);left:300px;}    70%  {transform: rotate(0deg);left:300px;background:#1ec7e6;}    100%  {transform: rotate(-360deg);left:0px;}  }  </style></head><body>  <div class="anima_div"></div></body></html><span id="more"></span><h2 id="animation">animation</h2><p>animation 是复合属性，其子属性有：</p><p>(1) animation-delay 动画延时<br>(2) animation-direction 动画在每次运行完后是反向运行还是重新回到开始位置重复运行<br>(3) animation-duration 动画一个周期的时长<br>(4) animation-iteration-count 动画重复次数，infinite无限次重复动画<br>(5) animation-name 指定由 @keyframes<br>(6) animation-timing-function 设置动画速度曲线，默认是 “ease”<br>(7) animation-fill-mode 指定动画执行后跳回到初始状态还是保留在结束状态<br>此外，还有 animation-play-state 属性，但是不能简写到 animation 属性中，该属性允许暂停和恢复动画。</p><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">animation-name</span>: first_animation;</span><br><span class="line"><span class="attribute">animation-duration</span>: <span class="number">5s</span>;</span><br><span class="line"><span class="attribute">animation-timing-function</span>: linear;</span><br><span class="line"><span class="attribute">animation-delay</span>: <span class="number">2s</span>;</span><br><span class="line"><span class="attribute">animation-iteration-count</span>: infinite;</span><br><span class="line"><span class="attribute">animation-direction</span>: alternate;</span><br><span class="line"><span class="attribute">animation-play-state</span>: running;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 简写 */</span></span><br><span class="line"><span class="attribute">animation</span>: first_animation <span class="number">5s</span> linear <span class="number">2s</span> infinite alternate;</span><br></pre></td></tr></table></figure><blockquote><p>animation: name duration timing-function delay iteration-count direction;</p></blockquote><h3 id="keyframes">@keyframes</h3><p>@keyframes 用于规定动画如何从一种样式逐渐变化为另一种样式，其基本用法如下：</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@keyframes</span> first_animation &#123;</span><br><span class="line">  <span class="number">0%</span>   &#123;<span class="attribute">background</span>: red; <span class="attribute">left</span>:<span class="number">0px</span>; <span class="attribute">top</span>:<span class="number">0px</span>;&#125;</span><br><span class="line">  <span class="number">25%</span>  &#123;<span class="attribute">background</span>: yellow; <span class="attribute">left</span>:<span class="number">200px</span>; <span class="attribute">top</span>:<span class="number">0px</span>;&#125;</span><br><span class="line">  <span class="number">50%</span>  &#123;<span class="attribute">background</span>: blue; <span class="attribute">left</span>:<span class="number">200px</span>; <span class="attribute">top</span>:<span class="number">200px</span>;&#125;</span><br><span class="line">  <span class="number">75%</span>  &#123;<span class="attribute">background</span>: green; <span class="attribute">left</span>:<span class="number">0px</span>; <span class="attribute">top</span>:<span class="number">200px</span>;&#125;</span><br><span class="line">  <span class="number">100%</span> &#123;<span class="attribute">background</span>: red; <span class="attribute">left</span>:<span class="number">0px</span>; <span class="attribute">top</span>:<span class="number">0px</span>;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> first_animation &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123;<span class="attribute">background</span>: red;&#125;</span><br><span class="line">  <span class="number">50%</span> &#123; <span class="attribute">background</span>: orange &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123;<span class="attribute">background</span>: yellow;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键词 “from” 和 “to”，等同于 0% 和 100%，表示动画开始状态和结束状态。中间状态由浏览器自动推算。</p><h3 id="animation-iteration-count">animation-iteration-count</h3><p>animation-iteration-count 指定动画播放的次数，默认值为 1。可以指定具体的次数，也可以使用关键字 infinite 让动画无限次播放。</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></pre></td><td class="code"><pre><span class="line"><span class="attribute">animation-name</span>: first_animation;</span><br><span class="line"><span class="attribute">animation-duration</span>: <span class="number">5s</span>;</span><br><span class="line"><span class="attribute">animation-iteration-count</span>: <span class="number">1</span>;</span><br><span class="line"><span class="comment">/* 等同于 */</span></span><br><span class="line"><span class="attribute">animation</span>: first_animation <span class="number">5s</span> infinite;</span><br></pre></td></tr></table></figure><h3 id="animation-fill-mode">animation-fill-mode</h3><p>animation-fill-mode 指定动画执行后跳回到初始状态还是保留在结束状态。</p><blockquote><p>animation-fill-mode : none | forwards | backwards | both;</p></blockquote><p>none: 不改变默认行为<br>forwards：当动画完成后，保持最后一个属性值(在最后一个关键帧中定义)<br>backwards：让动画回到第一帧的状态(在第一个关键帧中定义)<br>both：根据 animation-direction 轮流应用 forwards 和 backwards 规则</p><script async src="//jsfiddle.net/Leo555/3nrjmak2/1/embed/result,html,css/"></script><h3 id="animation-direction">animation-direction</h3><p>animation-direction 指定对象动画运动的方向，有以下四种取值：</p><p>normal：正常方向，默认<br>reverse：动画反向运行,方向始终与normal相反<br>alternate：动画会循环正反方向交替运动<br>alternate-reverse：动画从反向开始，再正反方向交替运动</p><h3 id="animation-play-state">animation-play-state</h3><p>animation-play-state 用于手动控制动画的状态，有 paused 和 running 两种取值：</p><p>running：默认值，表示动画正常运动<br>paused：表示暂停动画</p><script async src="//jsfiddle.net/Leo555/0yzvd9nL/2/embed/result,css/"></script><h2 id="参考资料">参考资料</h2><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Animations">MDN-CSS Animations</a></li><li><a href="http://www.cnblogs.com/imwtr/p/5885885.html">CSS3的变形transform、过渡transition、动画animation学习</a></li><li><a href="http://www.ruanyifeng.com/blog/2014/02/css_transition_and_animation.html">CSS动画简介</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;animation 属性目前还存在浏览器兼容性问题，建议使用 PostCSS 或手动添加浏览器前缀。本文学习使用 animation 属性创建简单动画。&lt;/p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;style&gt;
  .anima_div {
    width: 60px;
    height: 40px;
    background: #92B901;
    position: relative;
    padding: 20px 10px 0px 10px;
    animation: animated_div 5s infinite;
    border-radius: 5px;
  }

  @keyframes animated_div {
    0%  {transform: rotate(0deg);left:0px;}
    25%  {transform: rotate(20deg);left:0px;}
    50%  {transform: rotate(0deg);left:300px;}
    55%  {transform: rotate(0deg);left:300px;}
    70%  {transform: rotate(0deg);left:300px;background:#1ec7e6;}
    100%  {transform: rotate(-360deg);left:0px;}
  }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class=&quot;anima_div&quot;&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</summary>
    
    
    
    <category term="CSS" scheme="https://lz5z.com/categories/CSS/"/>
    
    
    <category term="animation" scheme="https://lz5z.com/tags/animation/"/>
    
    <category term="动画" scheme="https://lz5z.com/tags/%E5%8A%A8%E7%94%BB/"/>
    
  </entry>
  
  <entry>
    <title>CSS3 动画—transition</title>
    <link href="https://lz5z.com/CSS3%E5%8A%A8%E7%94%BB%E2%80%94transition/"/>
    <id>https://lz5z.com/CSS3%E5%8A%A8%E7%94%BB%E2%80%94transition/</id>
    <published>2017-06-12T02:21:22.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>CSS3 过渡属性被封装在 transition 规范中，过渡的意义在于，给了 CSS 时间轴的概念，在此之前所有的 CSS 状态变化都是瞬间完成的。过渡可以视为简单版的动画，通过定义开始状态和结束状态，达到样式转变的功能。</p><p>目前各大浏览器都支持 transition，所以不加浏览器前缀即可使用。</p><p>CSS3 transition 规范定义了以下四个 CSS 属性：</p><p>transition-delay(过渡延迟时间)<br>transition-duration(过渡持续时间)<br>transition-property(过渡属性)<br>transition-timing-function(过渡效果的时间曲线)</p><span id="more"></span><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* transition: 1s 1s width ease; */</span></span><br><span class="line"><span class="attribute">transition-property</span>: width;</span><br><span class="line"><span class="attribute">transition-duration</span>: <span class="number">1s</span>;</span><br><span class="line"><span class="attribute">transition-delay</span>: <span class="number">1s</span>;</span><br><span class="line"><span class="attribute">transition-timing-function</span>: ease;</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/kd7f9khw/embed/result,html,css/"></script><h3 id="过渡属性-transition-property">过渡属性 transition-property</h3><p>默认值为 all，表示浏览器所有能接受的可过渡属性，可以使用单个值或以逗号隔开的多个值。</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></pre></td><td class="code"><pre><span class="line"><span class="attribute">transition-property</span>: width,height;</span><br><span class="line"><span class="attribute">transition-duration</span>: <span class="number">1s</span>,<span class="number">2s</span>;</span><br><span class="line"><span class="comment">/* transition: 1s width, 2s height; */</span></span><br><span class="line"><span class="comment">/* transition: width 1s, height 2s; */</span></span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/whanfhLk/2/embed/result,html,css/"></script><p>可以在 <a href="http://oli.jp/2010/css-animatable-properties/">这里</a> 和 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties">这里</a> 查看哪些 CSS 属性支持 transition。</p><h3 id="延迟时间-transition-delay">延迟时间 transition-delay</h3><p>transition-delay 属性规定了在执行一个过渡之前的等待时间。IE 和 Opera 不接受 transition-duration 在-10ms和10ms之间的值。默认值0表示不过渡直接看到执行后的结果。单位是秒s，也可以是毫秒ms。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">transition-delay</span>: <span class="number">1s</span>;</span><br><span class="line"><span class="attribute">transition-delay</span>: <span class="number">1000ms</span>;</span><br></pre></td></tr></table></figure><h3 id="过渡时间-transition-duration">过渡时间 transition-duration</h3><p>动画的执行时间，默认值0表示不过渡。单位是秒s，也可以是毫秒ms。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">transition-duration</span>: <span class="number">1s</span>;</span><br><span class="line"><span class="attribute">transition-duration</span>: <span class="number">1000ms</span>;</span><br></pre></td></tr></table></figure><h3 id="过渡效果-transition-timing-function">过渡效果 transition-timing-function</h3><p>ease：默认值，缓解效果，变化速度逐渐放慢<br>linear：线性效果，匀速变化<br>ease-in：渐显效果，加速变化<br>ease-out：渐隐效果，减速变化<br>ease-in-out：渐显渐隐效果<br>cubic-bezier： 自定义变化速度，可以使用 <a href="http://cubic-bezier.com/#.17,.67,.83,.67">cubic-bezier</a> 定制想要的效果。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">transition</span>: width <span class="built_in">cubic-bezier</span>(.<span class="number">14</span>,.<span class="number">78</span>,.<span class="number">92</span>,.<span class="number">36</span>) <span class="number">1s</span>;</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/37m1tc5a/1/embed/result,html,css/"></script><h3 id="transition">transition</h3><p>transition 是一个复合属性，可以同时定义<br>transition-property、transition-duration、transition-timing-function、transition-delay 子属性值。</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* property name | duration | timing function | delay */</span></span><br><span class="line"><span class="attribute">transition</span>: margin-left <span class="number">4s</span> ease-in-out <span class="number">1s</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* property name | duration | delay */</span></span><br><span class="line"><span class="attribute">transition</span>: margin-left <span class="number">4s</span> <span class="number">1s</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* property name | duration */</span></span><br><span class="line"><span class="attribute">transition</span>: margin-left <span class="number">4s</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Apply to all changed properties */</span></span><br><span class="line"><span class="attribute">transition</span>: all <span class="number">0.5s</span> ease-out;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Apply to multiple properties */</span></span><br><span class="line"><span class="attribute">transition</span>: width <span class="number">2s</span>, height <span class="number">2s</span>, background-color <span class="number">2s</span>, transform <span class="number">2s</span>;</span><br></pre></td></tr></table></figure><p>写复合属性的时候，四个属性是可以改变顺序的，不过两个时间属性若同时出现，第一个代表 duration，第二个代表 delay，如果只出现一个时间属性，则表示 duration。</p><h3 id="transition-结合-transform">transition 结合 transform</h3><p>使用 transition 结合 transform 能够完成一些简单的动画效果</p><script async src="//jsfiddle.net/Leo555/e7j3p7ru/2/embed/result,html,css/"></script><p>使用 transition 做动画简单易用，不过也存在一些缺点：</p><p>(1) 动画需要事件触发<br>(2) 动画只能执行一次<br>(3) transition 只能定义开始状态和结束状态，不能定义中间状态</p><p>因此如果想要完成比较复杂的动画，还是要用 css3 中的 animation 属性。</p><h2 id="参考资料">参考资料</h2><ul><li><a href="http://www.ruanyifeng.com/blog/2014/02/css_transition_and_animation.html">CSS动画简介</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions">MDN-Using CSS transitions</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;CSS3 过渡属性被封装在 transition 规范中，过渡的意义在于，给了 CSS 时间轴的概念，在此之前所有的 CSS 状态变化都是瞬间完成的。过渡可以视为简单版的动画，通过定义开始状态和结束状态，达到样式转变的功能。&lt;/p&gt;
&lt;p&gt;目前各大浏览器都支持 transition，所以不加浏览器前缀即可使用。&lt;/p&gt;
&lt;p&gt;CSS3 transition 规范定义了以下四个 CSS 属性：&lt;/p&gt;
&lt;p&gt;transition-delay(过渡延迟时间)&lt;br&gt;
transition-duration(过渡持续时间)&lt;br&gt;
transition-property(过渡属性)&lt;br&gt;
transition-timing-function(过渡效果的时间曲线)&lt;/p&gt;</summary>
    
    
    
    <category term="CSS" scheme="https://lz5z.com/categories/CSS/"/>
    
    
    <category term="动画" scheme="https://lz5z.com/tags/%E5%8A%A8%E7%94%BB/"/>
    
    <category term="transition" scheme="https://lz5z.com/tags/transition/"/>
    
    <category term="过渡" scheme="https://lz5z.com/tags/%E8%BF%87%E6%B8%A1/"/>
    
  </entry>
  
  <entry>
    <title>CSS3 动画—transform</title>
    <link href="https://lz5z.com/CSS3%E5%8A%A8%E7%94%BB%E2%80%94transform/"/>
    <id>https://lz5z.com/CSS3%E5%8A%A8%E7%94%BB%E2%80%94transform/</id>
    <published>2017-06-09T08:23:31.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>在 CSS3 中，跟动画相关的属性有：变形 transform、过渡 transition、动画 animation。先放一个 Lea Verou 大神的链接 <a href="http://leaverou.github.io/animatable/">animatable</a>。</p><p>本章学习 CSS3 中的 transform 属性。</p><span id="more"></span><h2 id="变形-transform">变形 transform</h2><p>transform 属性目前还存在浏览器兼容性问题，建议使用 PostCSS 或手动添加浏览器前缀。<br>使用 transform，元素可以被转换（translate）、旋转（rotate）、缩放（scale）、倾斜（skew）。<br>transform 属性只对 block 元素生效。</p><script async src="//jsfiddle.net/Leo555/jeejj6yL/13/embed/result,html,css/"></script><h3 id="移动-translate">移动 translate</h3><p>transform: translate(x, y); 表示使元素在 X 轴和 Y 轴移动，y 可以省略，表示不移动。如果参数为负，则表示往相反的方向移动。同时还可以使用 translateX、translateY 和 translateZ 表示在某一个方向移动。Z 轴移动的前提是元素本身或者元素的父元素设定了透视值。</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></pre></td><td class="code"><pre><span class="line"><span class="attribute">transform</span>: <span class="built_in">translate</span>(<span class="number">12px</span>, <span class="number">50%</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">translateX</span>(<span class="number">2em</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="number">0.3in</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">20px</span>);</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/bhotf9bb/embed/result,html,css/"></script><h3 id="旋转-rotate">旋转 rotate</h3><p>旋转 transform: rotate(angle) angle 取值有：角度值deg，弧度值rad，梯度gard，转/圈turn，正数值代表顺时针旋转，反之逆时针。</p><p>rotateX、rotateY、rotateZ 表示分别在 X、Y、Z 轴上旋转。rotate3d(x, y, z, angle) 表示在3维空间旋转。</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></pre></td><td class="code"><pre><span class="line"><span class="attribute">transform</span>: <span class="built_in">rotate</span>(-<span class="number">30deg</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">0.5turn</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">rotate3d</span>(<span class="number">1</span>, <span class="number">2.0</span>, <span class="number">3.0</span>, <span class="number">10deg</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">rotateX</span>(<span class="number">0.5deg</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">rotateY</span>(<span class="number">0.5deg</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">rotateZ</span>(<span class="number">0.5deg</span>);</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/L5xfztsb/2/embed/result,html,css/"></script><h3 id="缩放-scale">缩放 scale</h3><p>缩放 transform: scale(x, y) 表示使元素在 X 轴和 Y 轴缩放。</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></pre></td><td class="code"><pre><span class="line"><span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">2</span>, <span class="number">0.5</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">scaleX</span>(<span class="number">2</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">scaleY</span>(<span class="number">0.5</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">scale3d</span>(<span class="number">2.5</span>, <span class="number">1.2</span>, <span class="number">0.3</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">scaleZ</span>(<span class="number">0.3</span>);</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/bo2zu0fv/embed/result,html,css/"></script><h3 id="倾斜-skew">倾斜 skew</h3><p>倾斜 transform: skew(x, y) 表示 X 轴和 Y 轴倾斜的角度，取值类型为角度值deg。</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></pre></td><td class="code"><pre><span class="line"><span class="attribute">transform</span>: <span class="built_in">skew</span>(<span class="number">30deg</span>, <span class="number">20deg</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">skewX</span>(<span class="number">30deg</span>);</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">skewY</span>(<span class="number">1.07rad</span>);</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/h7pox5r3/4/embed/result,html,css/"></script><h3 id="矩阵变形-matrix">矩阵变形 matrix</h3><p>矩阵变形transform: matrix(a,c,e,b,d,f) 相当于直接应用一个[a c e b d f]变换矩阵。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">transform</span>:  <span class="built_in">matrix</span>(a, c, b, d, tx, ty)</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/nn7q512z/embed/result,html,css/"></script><h3 id="变形原点-transform-origin">变形原点 transform-origin</h3><p>transform-origin 用来定义转换元素的位置，在没有重置 transform-origin 改变元素原点位置的情况下，CSS 的变形操作都是以元素自己中心位置进行。</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></pre></td><td class="code"><pre><span class="line"><span class="attribute">transform-origin</span>: left;</span><br><span class="line"><span class="attribute">transform-origin</span>: left top;</span><br><span class="line"><span class="attribute">transform-origin</span>: <span class="number">50%</span> <span class="number">100%</span>;</span><br><span class="line"><span class="attribute">transform-origin</span>: <span class="number">50%</span> bottom;</span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">30deg</span>);</span><br></pre></td></tr></table></figure><script async src="//jsfiddle.net/Leo555/o992vtgg/embed/result,html,css/"></script><h2 id="参考资料">参考资料</h2><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform">MDN</a></li><li><a href="http://www.cnblogs.com/imwtr/p/5885885.html">CSS3的变形transform、过渡transition、动画animation学习</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 CSS3 中，跟动画相关的属性有：变形 transform、过渡 transition、动画 animation。先放一个 Lea Verou 大神的链接 &lt;a href=&quot;http://leaverou.github.io/animatable/&quot;&gt;animatable&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;本章学习 CSS3 中的 transform 属性。&lt;/p&gt;</summary>
    
    
    
    <category term="CSS" scheme="https://lz5z.com/categories/CSS/"/>
    
    
    <category term="动画" scheme="https://lz5z.com/tags/%E5%8A%A8%E7%94%BB/"/>
    
    <category term="transform" scheme="https://lz5z.com/tags/transform/"/>
    
    <category term="变形" scheme="https://lz5z.com/tags/%E5%8F%98%E5%BD%A2/"/>
    
  </entry>
  
  <entry>
    <title>异形：契约</title>
    <link href="https://lz5z.com/%E5%BC%82%E5%BD%A2%E5%A5%91%E7%BA%A6/"/>
    <id>https://lz5z.com/%E5%BC%82%E5%BD%A2%E5%A5%91%E7%BA%A6/</id>
    <published>2017-05-16T14:49:25.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<p>今年最期待的电影，没有之一。《异形》系列电影忠实粉丝，不知道看了多少遍。2012年的《普罗米修斯》也是不亚于《异形1》的经典之作，雷德利·斯科特更是越老电影越具有哲思。</p><span id="more"></span><p>下面是 B 站上的一则影评，建议高清食用。</p><p><embed height="415" width="544" quality="high" allowfullscreen="true" type="application/x-shockwave-flash" src="//static.hdslb.com/miniloader.swf" flashvars="aid=10597309&page=1" pluginspage="//www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash"></embed></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今年最期待的电影，没有之一。《异形》系列电影忠实粉丝，不知道看了多少遍。2012年的《普罗米修斯》也是不亚于《异形1》的经典之作，雷德利·斯科特更是越老电影越具有哲思。&lt;/p&gt;</summary>
    
    
    
    <category term="Movies" scheme="https://lz5z.com/categories/Movies/"/>
    
    
    <category term="电影" scheme="https://lz5z.com/tags/%E7%94%B5%E5%BD%B1/"/>
    
    <category term="异形" scheme="https://lz5z.com/tags/%E5%BC%82%E5%BD%A2/"/>
    
  </entry>
  
  <entry>
    <title>机器学习—通过 APP 预测用户性别</title>
    <link href="https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E2%80%94app%E9%A2%84%E6%B5%8B%E7%94%A8%E6%88%B7%E6%80%A7%E5%88%AB/"/>
    <id>https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E2%80%94app%E9%A2%84%E6%B5%8B%E7%94%A8%E6%88%B7%E6%80%A7%E5%88%AB/</id>
    <published>2017-05-16T06:32:51.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h2 id="项目描述">项目描述</h2><p>公司组织的一个机器学习的小比赛，<S> 数据<a href="">下载地址</a> </S> 。大意是根据用户所安装的 APP (加密)预测用户的性别，训练数据标记 label (性别)，典型的监督学习方案。</p><h2 id="数据描述">数据描述</h2><p>下载之后，解压成为文本文件。 数据格式如下：<br>每一行代表一个用户的数据，一共120万个样本用户数据<br>每一行都有5列，每一列以制表符 tab 分割（\t)。<br>第一列是用户编号（已经脱敏，转化成1 ~1,200,000的编号)<br>第二列是用户的性别 （male/female)<br>第三列是用户的移动设备类型<br>第四列是用户的 APP 列表，每个 APP 已经脱敏，以数字编号代替 APP 名称。多个 APP 之间以逗号（,)作为分隔符<br>第五列是用户所在区域。</p><p>其中移动设备类型/APP 列表/区域是特征数据。性别是结果数据。</p><span id="more"></span><h2 id="方案">方案</h2><p>首先分析数据，一共有机型、APP、区域三个维度。性别可能对 APP 和机型有偏好，但是不能对区域有偏好，而是不同的区域可能对 APP 有不同的偏好，比如某省用户偏爱直播，某省用户偏爱交友等等。</p><p>建模方案，把 APP 和 机型（数值化）作为两个维度对数据进行训练，分区域建模，不同的区域使用不同的模型。然后使用全部数据或随机部分数据建模形成公共数据模型，公共模型用来分析用户区域数据不足或者来自未建模区域的数据。</p><h2 id="具体实现">具体实现</h2><p>技术方案：Python + scikit-learn + pandas + numpy</p><p>环境搭建使用 Anaconda</p><p><S> <a href="https://github.com/Leo555/wps_ai_war">代码地址</a> </S></p><h2 id="项目难点">项目难点</h2><p>项目困难主要出现在 APP 降维，也就是判断哪些 APP 与性别相关，这是一个相关性分析的问题。网上找了很多资料，算法描述也有，不过没有找到合适的 Python 实现。Spark 版本的倒是很多，可是不想在一个小项目里面使用两种技术栈。</p><h2 id="进度">进度</h2><p>目前使用上海数据建模，只使用 APP 信息，未加入机型信息，预测准确度大约为79%。<br>后面会加入机型信息，并使用特征提取对 APP 信息进行降维，希望能提高准确率。</p><h2 id="说明">说明</h2><p>由于公司政策原因，代码不能放入 github，后续会把思路和核心代码写出来。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;项目描述&quot;&gt;项目描述&lt;/h2&gt;
&lt;p&gt;公司组织的一个机器学习的小比赛，&lt;S&gt; 数据&lt;a href=&quot;&quot;&gt;下载地址&lt;/a&gt; &lt;/S&gt; 。大意是根据用户所安装的 APP (加密)预测用户的性别，训练数据标记 label (性别)，典型的监督学习方案。&lt;/p&gt;
&lt;h2 id=&quot;数据描述&quot;&gt;数据描述&lt;/h2&gt;
&lt;p&gt;下载之后，解压成为文本文件。 数据格式如下：&lt;br&gt;
每一行代表一个用户的数据，一共120万个样本用户数据&lt;br&gt;
每一行都有5列，每一列以制表符 tab 分割（&#92;t)。&lt;br&gt;
第一列是用户编号（已经脱敏，转化成1 ~1,200,000的编号)&lt;br&gt;
第二列是用户的性别 （male/female)&lt;br&gt;
第三列是用户的移动设备类型&lt;br&gt;
第四列是用户的 APP 列表，每个 APP 已经脱敏，以数字编号代替 APP 名称。多个 APP 之间以逗号（,)作为分隔符&lt;br&gt;
第五列是用户所在区域。&lt;/p&gt;
&lt;p&gt;其中移动设备类型/APP 列表/区域是特征数据。性别是结果数据。&lt;/p&gt;</summary>
    
    
    
    <category term="Machine Learning" scheme="https://lz5z.com/categories/Machine-Learning/"/>
    
    
    <category term="Python" scheme="https://lz5z.com/tags/Python/"/>
    
    <category term="scikit-learn" scheme="https://lz5z.com/tags/scikit-learn/"/>
    
    <category term="性别预测" scheme="https://lz5z.com/tags/%E6%80%A7%E5%88%AB%E9%A2%84%E6%B5%8B/"/>
    
    <category term="APP" scheme="https://lz5z.com/tags/APP/"/>
    
  </entry>
  
  <entry>
    <title>再不学 flex 就不会写布局了</title>
    <link href="https://lz5z.com/FlexLayoutLearning/"/>
    <id>https://lz5z.com/FlexLayoutLearning/</id>
    <published>2017-04-19T12:35:52.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h2 id="如何居中的问题">如何居中的问题</h2><p>块状元素居中是一个老生常谈的话题，之前面试的时候考官也曾问到过这个。下面写几种常见的块状元素居中的方式。</p><span id="more"></span><p>假如想要 con 在 box 中居中</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;con&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span> </span><br></pre></td></tr></table></figure><h3 id="绝对布局，使用-margin">绝对布局，使用 margin</h3><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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">500px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#ebebeb</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.con</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#000</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">200px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="相对布局，计算-left-和-top">相对布局，计算 left 和 top</h3><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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">500px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#ebebeb</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.con</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#000</span>;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">200px</span>;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">200px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="父容器宽高不确定或者不容易确定，综合相对布局-绝对布局">父容器宽高不确定或者不容易确定，综合相对布局 + 绝对布局</h3><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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">500px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#ebebeb</span>;</span><br><span class="line">    <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.con</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#000</span>;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">50%</span>;</span><br><span class="line">    <span class="attribute">left</span>: <span class="number">50%</span>;</span><br><span class="line">    <span class="attribute">margin-top</span>: -<span class="number">50px</span>;</span><br><span class="line">    <span class="attribute">margin-left</span>: -<span class="number">50px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用-flex">使用 flex</h3><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><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="selector-class">.box</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">500px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#ebebeb</span>;</span><br><span class="line">    <span class="attribute">display</span>: flex;</span><br><span class="line">    <span class="attribute">justify-content</span>: center;</span><br><span class="line">    <span class="attribute">align-items</span>: center;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.con</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#000</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="flex-margin">flex + margin</h3><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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: flex;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">500px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#ebebeb</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.con</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#000</span>;</span><br><span class="line">    <span class="attribute">margin</span>: auto;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用 flex 布局的优势不可谓不明显：</p><ul><li>免去了很多计算。最后两个使用 flex 布局的例子中，无论父元素还是子元素的宽度和高度发生改变，都能依然能保持居中；而前面三种方法中，则需要都要改变其他值，才能保持居中。</li><li>使用 flex 布局的语义化要比前面几种都好，前面三种方法给了一大堆数字，不去认真看一看、算一算，很难确定是否是居中，这对代码阅读者也是非常不友好。</li><li>flex 对响应式布局的支持更好。虽然前面几种方法也能实现响应式布局，但是实现起来比较麻烦，不如 flex 来得实在。</li><li>flex 支持行内元素。</li></ul><h2 id="什么是-flex-布局">什么是 flex 布局</h2><p>传统布局的核心是盒子模型，依赖 display 属性 + position 属性 + float 属性。可以看出来传统布局非常容易实现像 word 左对齐，右对齐这样的功能，可以说，传统布局更适合于文字排版。</p><p>flex 是 flexible Box 的缩写，可以看做弹性的盒子模型。</p><h3 id="flex-用法">flex 用法</h3><p>使用 flex 首先要设置父元素 <code>display: flex</code>。任何元素都可以指定为 flex 布局：</p><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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: flex;</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: inline-box;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>设为 flex 布局以后，子元素的 float、clear 和 vertical-align 属性将失效。</p><h2 id="flex-两个基本概念">flex 两个基本概念</h2><p>flex 的核心的概念就是 <strong>容器</strong> 和 <strong>轴</strong>。容器包括外层的 <strong>父容器</strong> 和内层的 <strong>子容器</strong>，轴包括 <strong>主轴</strong> 和 <strong>交叉轴</strong>，如下图所示：</p><img src="/assets/img/flex-layout.png" alt="我是一只的图片"><p>容器默认存在两根轴：水平的主轴（main axis）和垂直的交叉轴（cross axis）。主轴的开始位置叫做 main start，结束位置叫做 main end；交叉轴同理，<br>子容器默认沿主轴排列。单个子容器占据的主轴空间叫做 main size，占据的交叉轴空间叫做 cross size。</p><p>容器具有这样的特点：父容器可以统一设置子容器的排列方式，子容器也可以单独设置自身的排列方式，如果两者同时设置，以子容器的设置为准。</p><h2 id="父容器">父容器</h2><p>父容器一共有6个属性: <strong>flex-direction, flex-wrap, flex-flow, justify-content, align-items, align-content</strong></p><h3 id="flex-direction-属性决定主轴的方向">flex-direction 属性决定主轴的方向</h3><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">描述</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">flex-direction: row</td><td style="text-align:left">（默认值）主轴为水平方向，起点在左端</td><td style="text-align:left"><img src="/assets/img/flex-direction-row.png" alt="flex-direction-row"></td></tr><tr><td style="text-align:left">flex-direction: row-reverse</td><td style="text-align:left">主轴为水平方向，起点在右端</td><td style="text-align:left"><img src="/assets/img/flex-direction-row-reverse.png" alt="flex-direction-row-reverse"></td></tr><tr><td style="text-align:left">flex-direction: column</td><td style="text-align:left">主轴为垂直方向，起点在上沿</td><td style="text-align:left"><img src="/assets/img/flex-direction-column.png" alt="flex-direction-column"></td></tr><tr><td style="text-align:left">flex-direction: column-reverse</td><td style="text-align:left">主轴为垂直方向，起点在下沿</td><td style="text-align:left"><img src="/assets/img/flex-direction-column-reverse.png" alt="flex-direction-column-reverse"></td></tr></tbody></table><h3 id="flex-wrap-决定子容器是否换行排列">flex-wrap 决定子容器是否换行排列</h3><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">描述</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">flex-wrap: nowrap</td><td style="text-align:left">（默认）不换行</td><td style="text-align:left"><img src="/assets/img/flex-wrap-nowrap.png" alt="flex-wrap-nowrap"></td></tr><tr><td style="text-align:left">flex-wrap: wrap</td><td style="text-align:left">换行，第一行在上方</td><td style="text-align:left"><img src="/assets/img/flex-wrap-wrap.png" alt="flex-wrap-wrap"></td></tr><tr><td style="text-align:left">flex-wrap: wrap-reverse</td><td style="text-align:left">换行，第一行在下方</td><td style="text-align:left"><img src="/assets/img/flex-wrap-wrap-reverse.png" alt="flex-wrap-wrap-reverse"></td></tr></tbody></table><h2 id="flex-flow">flex-flow</h2><p>flex-direction 属性和 flex-wrap 属性的简写形式，默认值为 row nowrap</p><h3 id="justify-content-设置子容器在主轴上的对齐方式">justify-content 设置子容器在主轴上的对齐方式</h3><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">描述</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">justify-content: flex-start</td><td style="text-align:left">（默认）起始端对齐</td><td style="text-align:left"><img src="/assets/img/flex-start.png" alt="flex-start"></td></tr><tr><td style="text-align:left">justify-content: flex-end</td><td style="text-align:left">末尾段对齐</td><td style="text-align:left"><img src="/assets/img/flex-end.png" alt="flex-end"></td></tr><tr><td style="text-align:left">justify-content: center</td><td style="text-align:left">居中对齐</td><td style="text-align:left"><img src="/assets/img/flex-center.png" alt="flex-center"></td></tr><tr><td style="text-align:left">justify-content: space-around</td><td style="text-align:left">子容器沿主轴均匀分布，位于首尾两端的子容器到父容器的距离是子容器间距的一半。</td><td style="text-align:left"><img src="/assets/img/flex-space-around.png" alt="space-around"></td></tr><tr><td style="text-align:left">justify-content: space-between</td><td style="text-align:left">子容器沿主轴均匀分布，位于首尾两端的子容器与父容器相切。</td><td style="text-align:left"><img src="/assets/img/flex-space-between.png" alt="space-between"></td></tr></tbody></table><h3 id="align-items-设置子容器沿交叉轴的对齐方式">align-items 设置子容器沿交叉轴的对齐方式</h3><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">描述</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">align-items: flex-start</td><td style="text-align:left">交叉轴的起点对齐</td><td style="text-align:left"><img src="/assets/img/align-flex-start.png" alt="flex-start"></td></tr><tr><td style="text-align:left">align-items: flex-end</td><td style="text-align:left">交叉轴的终点对齐</td><td style="text-align:left"><img src="/assets/img/align-flex-end.png" alt="flex-start"></td></tr><tr><td style="text-align:left">align-items: center</td><td style="text-align:left">交叉轴的中点对齐</td><td style="text-align:left"><img src="/assets/img/align-center.png" alt="align-center"></td></tr><tr><td style="text-align:left">align-items: baseline</td><td style="text-align:left">基线对齐（首行文字对齐）所有子容器向基线对齐，交叉轴起点到元素基线距离最大的子容器将会与交叉轴起始端相切以确定基线。</td><td style="text-align:left"><img src="/assets/img/align-baseline.png" alt="align-baseline"></td></tr><tr><td style="text-align:left">align-items: stretch</td><td style="text-align:left">（默认）如果子容器未设置高度或设为auto，子容器沿交叉轴方向的尺寸拉伸至与父容器一致</td><td style="text-align:left"><img src="/assets/img/align-stretch.png" alt="align-stretch"></td></tr></tbody></table><h2 id="子容器">子容器</h2><p>子容器一共有6个属性： <strong>order, flex-grow, flex-shrink, flex-basis, flex, align-self</strong></p><h3 id="order-改变子容器的排列顺序">order 改变子容器的排列顺序</h3><p>默认值为 0，可以为负值，数值越小排列越靠前。order 只能为整数。</p><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">order: -1</td><td style="text-align:left"><img src="/assets/img/flex-order.png" alt="flex-order"></td></tr></tbody></table><h3 id="flex-grow-定义子容器如何瓜分剩余空间">flex-grow 定义子容器如何瓜分剩余空间</h3><p>默认值为 0，就是即使存在剩余空间，也不瓜分。如果定义了非 0 值，则按照比例瓜分。flex-grow 只能为整数。</p><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">flex-grow: 1</td><td style="text-align:left"><img src="/assets/img/flex-grow.png" alt="flex-grow"></td></tr></tbody></table><h3 id="flex-shrink-定义了子容器的缩小比例。">flex-shrink 定义了子容器的缩小比例。</h3><p>默认为1，即如果空间不足，则子容器将缩小。如果所有子容器的 flex-shrink 都为1，当空间不足时，都将等比例缩小。如果某个子容器的 flex-shrink 为0，其他子容器都为1，则空间不足时，前者不缩小。</p><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">flex-shrink: 0</td><td style="text-align:left"><img src="/assets/img/flex-shrink.png" alt="flex-shrink"></td></tr></tbody></table><h3 id="flex-basis-用来改变子容器占据主轴空间的大小">flex-basis 用来改变子容器占据主轴空间的大小</h3><p>表示在不伸缩的情况下子容器占据主轴空间的大小，默认为 auto，表示子容器本来的大小。</p><h3 id="flex">flex</h3><p>flex-grow, flex-shrink 和 flex-basis 的简写，默认值为 0 1 auto</p><h3 id="align-self-用来覆盖父容器的-align-items-属性">align-self 用来覆盖父容器的 align-items 属性</h3><p>align-self 属性允许单个子容器有与其他子容器不一样的对齐方式，默认值为auto，表示继承父元素的 align-items 属性，如果没有父元素，则等同于 stretch。改属性的取值与 align-items 相同。</p><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">效果</th></tr></thead><tbody><tr><td style="text-align:left">align-self: flex-end</td><td style="text-align:left"><img src="/assets/img/flex-align-self.png" alt="flex-align-self"></td></tr></tbody></table><h2 id="参考资料">参考资料</h2><ol><li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout">MDN CSS Flexible Box Layout</a></li><li><a href="http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html?utm_source=tuicool">Flex 布局教程：语法篇</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;如何居中的问题&quot;&gt;如何居中的问题&lt;/h2&gt;
&lt;p&gt;块状元素居中是一个老生常谈的话题，之前面试的时候考官也曾问到过这个。下面写几种常见的块状元素居中的方式。&lt;/p&gt;</summary>
    
    
    
    <category term="css" scheme="https://lz5z.com/categories/css/"/>
    
    
    <category term="flex" scheme="https://lz5z.com/tags/flex/"/>
    
    <category term="css" scheme="https://lz5z.com/tags/css/"/>
    
    <category term="居中" scheme="https://lz5z.com/tags/%E5%B1%85%E4%B8%AD/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 中的作用域和声明提升</title>
    <link href="https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E5%A3%B0%E6%98%8E%E6%8F%90%E5%8D%87/"/>
    <id>https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E5%A3%B0%E6%98%8E%E6%8F%90%E5%8D%87/</id>
    <published>2017-04-03T12:19:35.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<p>首先看一个小问题：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;Hello&#x27;</span>;</span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="title function_">alert</span>(a)</span><br><span class="line">  <span class="keyword">var</span> a = <span class="string">&#x27;World&#x27;</span></span><br><span class="line">&#125;)()</span><br></pre></td></tr></table></figure><p>猜猜弹框中会输出 ‘Hello’ 还是 ‘World’。揭晓答案： ‘undefined’。这里是一个 JavaScript 的小陷阱–JavaScript 变量提升（Hoisting）。</p><span id="more"></span><h2 id="JavaScript-Scoping">JavaScript Scoping</h2><p>在 ES6 之前，JavaScript 没有块状作用域（block-level scope），只有函数级作用域（function-level scope）。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 块级作用域</span></span><br><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line"><span class="keyword">if</span> (name) &#123;</span><br><span class="line">  name = <span class="string">&#x27;Jack&#x27;</span> <span class="comment">// 这里的 name 是全局变量</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(name) <span class="comment">// Jack</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name) <span class="comment">// Jack</span></span><br><span class="line"><span class="comment">// 函数作用域</span></span><br><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sayName</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> name = <span class="string">&#x27;Jack&#x27;</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(name) <span class="comment">// Jack    </span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name) <span class="comment">// Leo</span></span><br></pre></td></tr></table></figure><p>如果在声明一个变量的时候没有使用 var 关键字，那么变量将成为一个全局变量。</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="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  a = <span class="string">&#x27;Hello World&#x27;</span></span><br><span class="line">&#125;)()</span><br><span class="line"><span class="title function_">alert</span>(a) <span class="comment">// Hello World</span></span><br></pre></td></tr></table></figure><p>在 setTimeout 中的函数是在全局作用域中执行的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">10</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="number">20</span>,</span><br><span class="line">  <span class="attr">doCalculate</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">a</span> + <span class="variable language_">this</span>.<span class="property">b</span>) <span class="comment">// 3</span></span><br><span class="line">    &#125;, <span class="number">1000</span>)</span><br><span class="line">  &#125;  </span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">doCalculate</span>() <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>为了避免对全局作用域的污染， 所以一般情况下我们尽可能少的声明全局变量。</p><p>关于 ES6 中 使用 let 和 const 声明块级作用域的内容，可以参考 <a href="https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84let%E5%92%8Cconst/">JavaScript 中的 let 和 const</a>。</p><p>关于 ES5 中严格模式的内容可以参考 <a href="https://lz5z.com/JavaScript%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/">JavaScript 严格模式</a>。</p><p>关于 JavaScript 中 this 的详细用法可以参考 <a href="https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84this/">JavaScript 中 的this</a>。</p><h2 id="JavaScript-Hoisting">JavaScript Hoisting</h2><p>在 JavaScript 中，函数、变量的声明都会被提升（hoisting）到该函数或变量所在的 scope 的顶部。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a </span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// undefined</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b) <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> b</span><br><span class="line">b = a = <span class="number">10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b) <span class="comment">// 10 10</span></span><br></pre></td></tr></table></figure><p>在 JavaScript 中，如果声明一个变量，但是为对其进行赋值，那么 JS 引擎会默认让其等于 undefined。所以上述例子中可以看到变量 b 在声明后，被提升到作用域顶部，和 a 一样，获得了 undefined 的值。</p><p>除了变量声明会提升，函数声明也会提升。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)) <span class="comment">// 6</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">eval</span>(<span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">join</span>.<span class="title function_">call</span>(<span class="variable language_">arguments</span>, <span class="string">&#x27;+&#x27;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>值得注意的是：函数声明可以提升，但是函数表达式不能提升。</p><p>函数声明： <code>function fun(arguments) &#123;&#125;</code><br>函数表达式： <code>var fun = function (arguments) &#123;&#125;</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="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>) <span class="comment">// 报错：Uncaught TypeError: add is not a function</span></span><br><span class="line"><span class="keyword">var</span> add = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">eval</span>(<span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">join</span>.<span class="title function_">call</span>(<span class="variable language_">arguments</span>, <span class="string">&#x27;+&#x27;</span>))  </span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>) <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>函数声明会覆盖变量声明。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> test </span><br><span class="line"><span class="keyword">function</span> <span class="title function_">test</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;test&#x27;</span>)  </span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> test) <span class="comment">// &#x27;function&#x27;</span></span><br></pre></td></tr></table></figure><p>如果变量已经赋值，则无法别覆盖：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> test = <span class="string">&#x27;test&#x27;</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">test</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> test) <span class="comment">// &#x27;string&#x27;</span></span><br><span class="line">test = <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> test) <span class="comment">// &#x27;function&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="优先级">优先级</h2><p>在 JavaScript 中，一个变量以四种方式进入作用域 scope：</p><ol><li>语言内置：所有的作用域中都有 this 和 arguments 关键字（global 没有 arguments）;</li><li>形式参数：函数的参数在函数作用域中都是有效的;</li><li>函数声明：形如 <code>function foo() &#123;&#125;</code>;</li><li>变量声明：形如 <code>var bar</code>;</li></ol><p>函数声明和变量声明总是会被移动（即 hoisting）到它们所在的作用域的顶部。而变量的解析顺序（优先级），与变量进入作用域的 4 种方式的顺序一致，如果一个变量的名字与函数的名字相同，那么函数的名字会覆盖变量的名字，无论其在代码中的顺序如何，但是名字的初始化却是按其在代码中书写的顺序进行的，不受以上优先级的影响。</p><p>而变量的解析顺序（优先级），与变量进入作用域的 4 种方式的顺序一致。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. var 声明并且赋值高于函数声明</span></span><br><span class="line"><span class="keyword">var</span> test = <span class="string">&#x27;test&#x27;</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">test</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> test) <span class="comment">// &#x27;string&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 函数声明高于形参</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">test</span> (<span class="params">a</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> a) <span class="comment">// &#x27;function&#x27;</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">a</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test</span>(<span class="number">100</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 形参高于语言内置变量</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">test</span> (<span class="params"><span class="variable language_">arguments</span></span>) &#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="variable language_">arguments</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test</span>(<span class="number">100</span>) <span class="comment">// 100</span></span><br><span class="line"><span class="comment">/*--对比以下--*/</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">test1</span> (<span class="params">a</span>) &#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="variable language_">arguments</span>) <span class="comment">// [object Arguments]</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test1</span>(<span class="number">100</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. 形参优先级高于 var 声明不赋值</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="variable language_">arguments</span>)</span><br><span class="line">  <span class="keyword">var</span> <span class="variable language_">arguments</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test</span>() <span class="comment">// [Object Arguments]</span></span><br></pre></td></tr></table></figure><p>变量声明（赋值） &gt; 形参 &gt; 语言内置变量 &gt; 变量声明不赋值 &gt; 函数外部作用域的其他所有声明</p><p>总结变量优先级正好验证了作用域链式查找，局部作用域 -&gt; 上一级局部作用域 -&gt; 全局作用域 -&gt; TypeError。</p><p>最后看一个例子：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"><span class="variable language_">arguments</span></span>) &#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="keyword">typeof</span> <span class="variable language_">arguments</span>) <span class="comment">// &#x27;function&#x27;</span></span><br><span class="line">  <span class="keyword">var</span> <span class="variable language_">arguments</span> = <span class="number">20</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">arguments</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="variable language_">arguments</span>) <span class="comment">// 20</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test</span>(<span class="number">100</span>)</span><br></pre></td></tr></table></figure><h2 id="参考文章">参考文章</h2><p><a href="http://enml.github.io/site/2014/06/13/js-resolution/">javascript变量声明优先级</a><br><a href="https://github.com/creeperyang/blog/issues/16">深入理解JS中声明提升、作用域（链）和 this 关键字</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;首先看一个小问题：&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; a = &lt;span class=&quot;string&quot;&gt;&amp;#x27;Hello&amp;#x27;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt;(&lt;span class=&quot;params&quot;&gt;&lt;/span&gt;)&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;title function_&quot;&gt;alert&lt;/span&gt;(a)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; a = &lt;span class=&quot;string&quot;&gt;&amp;#x27;World&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;)()&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;猜猜弹框中会输出 ‘Hello’ 还是 ‘World’。揭晓答案： ‘undefined’。这里是一个 JavaScript 的小陷阱–JavaScript 变量提升（Hoisting）。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="Scoping" scheme="https://lz5z.com/tags/Scoping/"/>
    
    <category term="Hoisting" scheme="https://lz5z.com/tags/Hoisting/"/>
    
  </entry>
  
  <entry>
    <title>PostCSS 初识</title>
    <link href="https://lz5z.com/PostCSS%E5%88%9D%E8%AF%86/"/>
    <id>https://lz5z.com/PostCSS%E5%88%9D%E8%AF%86/</id>
    <published>2017-03-29T10:20:33.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景">背景</h2><p>今天在吃早饭的时候就被同事@，说有一块页面效果在测试服务器的部署效果跟本地不一样：代码在本地运行没有问题，部署后发现有一个分割线的位置明显不对。来到公司后看了同事的演示，觉得可能是 css 代码压缩时出现了问题。</p><span id="more"></span><p>通过 chrome 查看相关 css，发现了问题所在，有一段代码是这样写的：</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><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.clz_editor_container</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: -moz-box;</span><br><span class="line">  <span class="attribute">display</span>: -webkit-flex;</span><br><span class="line">  <span class="attribute">display</span>: -ms-flexbox;</span><br><span class="line">  -webkit-<span class="attribute">box-orient</span>: vertical;</span><br><span class="line">  -webkit-<span class="attribute">flex-direction</span>: column;</span><br><span class="line">  -ms-<span class="attribute">flex-direction</span>: column;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>压缩后在 chrome 中代码变成了这样的：</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.clz_editor_container</span><span class="selector-attr">[data-v-5fd4dedf]</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>: -ms-flexbox;</span><br><span class="line">  -ms-<span class="attribute">flex-direction</span>: column;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><p>然而实际浏览器中前两句都没有生效。</p><p>因为在代码压缩时，相同的代码会默认选择比较靠后的，因此 <code>display: -ms-flexbox; -ms-flex-direction: column;</code>，而 <code>-ms-flexbox</code> 和 <code>-ms-flex-direction</code> 是为了兼容 IE 浏览器而存在的， 所以这两句 css 都没有生效。</p><p>而没有压缩的代码在浏览器中运行时，浏览器自动选择了合适的 css 语句所以没有出现问题。</p><p>解决方案很简单啦，这应该是同事写代码粗心导致的，直接把 <code>display: flex; flex-direction: column;</code>加上就行了。而且 idea 里面自动代码兼容性补全功能，所以用 idea 写出的代码应该不会出现这个问题。</p><p>然后有同事说应该有一些工具能够自动补全的，于是 google 了一下，发现这种问题早就有非常好的解决方案，那就是 PostCSS 的插件 <a href="https://github.com/postcss/autoprefixer">autoprefixer</a>。</p><h2 id="解决方案">解决方案</h2><p>首先安装 webpack 插件 postcss-loader 和 autoprefixer</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 i autoprefixer postcss-loader --save-dev</span><br></pre></td></tr></table></figure><p>然后修改 webpack 配置文件，在插件系统中更改 LoaderOptionsPlugin，在 options 中增加 postcss</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> webpack.<span class="title class_">LoaderOptionsPlugin</span>(&#123;</span><br><span class="line">    <span class="attr">minimize</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;production&#x27;</span>,</span><br><span class="line">    <span class="attr">options</span>: &#123;</span><br><span class="line">        <span class="attr">postcss</span>:[<span class="title function_">autoprefixer</span>()]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>然后在所有 css 相关的 loader 中增加 postcss-loader</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.vue$/</span>,</span><br><span class="line">    <span class="attr">loader</span>: <span class="string">&#x27;vue-loader&#x27;</span>,</span><br><span class="line">    <span class="attr">options</span>: &#123;</span><br><span class="line">        <span class="attr">loaders</span>: &#123;</span><br><span class="line">            <span class="attr">js</span>: <span class="string">&#x27;...&#x27;</span>,</span><br><span class="line">            <span class="attr">css</span>: <span class="title class_">ExtractTextPlugin</span>.<span class="title function_">extract</span>(&#123;</span><br><span class="line">                <span class="attr">use</span>: <span class="string">&#x27;css-loader!postcss-loader&#x27;</span>,</span><br><span class="line">                <span class="attr">fallback</span>: <span class="string">&#x27;vue-style-loader&#x27;</span></span><br><span class="line">            &#125;),</span><br><span class="line">            <span class="attr">less</span>: <span class="title class_">ExtractTextPlugin</span>.<span class="title function_">extract</span>(&#123;</span><br><span class="line">                <span class="attr">fallback</span>: <span class="string">&#x27;vue-style-loader&#x27;</span>,</span><br><span class="line">                <span class="attr">use</span>: <span class="string">&#x27;css-loader!postcss-loader!less-loader&#x27;</span></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">&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">    <span class="attr">loader</span>: <span class="title class_">ExtractTextPlugin</span>.<span class="title function_">extract</span>(&#123;</span><br><span class="line">        <span class="attr">fallback</span>: <span class="string">&#x27;style-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">use</span>: <span class="string">&#x27;css-loader!postcss-loader&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">&#125;,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">    <span class="attr">loader</span>: <span class="title class_">ExtractTextPlugin</span>.<span class="title function_">extract</span>(&#123;</span><br><span class="line">        <span class="attr">fallback</span>: <span class="string">&#x27;style-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">use</span>: <span class="string">&#x27;css-loader!postcss-loader!less-loader&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意 postcss-loader 应该放在 less-loader 和 css-loader 之间，处理顺序为:<br>less-loader -&gt; postcss-loader -&gt; css-loader -&gt; style-loader</p><p>修改前面出问题的 css 为原生</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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.clz_editor_container</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: flex;</span><br><span class="line">    <span class="attribute">flex-direction</span>: column;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>重新打包压缩后的 css 如下</p><img src="/assets/img/postcss-css.png" alt="css文件"><p>重新打开查看效果，问题解决。</p><p>注意如果你在 css 中使用 <code>@import</code> 引入其它 css 文件，而被引入的文件在 webpack 打包后又没有加入浏览器前缀的话，建议在 css-loader 中加入 <code>importLoaders=1</code> 参数</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">    <span class="attr">loader</span>: <span class="title class_">ExtractTextPlugin</span>.<span class="title function_">extract</span>(&#123;</span><br><span class="line">        <span class="attr">fallback</span>: <span class="string">&#x27;style-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">use</span>: <span class="string">&#x27;css-loader?importLoaders=1!postcss-loader&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="PostCSS"><a href="http://postcss.org/">PostCSS</a></h2><p>PostCSS 是什么？官方给出的定义是： PostCSS 是一个用 JavaScript 转化 CSS 的工具。准确的说，PostCSS 是一个平台，通过一些插件，能做很多事情：</p><p>（1） 增加代码可读性<br>比如刚才我们用的 <a href="https://github.com/postcss/autoprefixer">autoprefixer</a>，通过给 css 添加供应商前缀，让我们的 css 代码更加优雅。</p><p>（2） 使用未来 CSS 的语法特性<br>通过使用 <a href="http://cssnext.io/">cssnext</a> 插件，可以允许我们使用最新的 css 语法，而不用等待浏览器支持。</p><p>（3）global css 终结者<br>PostCSS 通过 <a href="https://github.com/css-modules/css-modules">CSS Modules</a> 对 css 命名做模块化处理，一般为添加前缀和后缀，让我们写 css 的时候不必担心命名太通用，只要觉得有意义即可。</p><p>（4）避免 css errors<br>通过使用 <a href="https://stylelint.io/">stylelint</a> 来避免 css errors。</p><p>（4）更强大的栅格系统<br><a href="http://lostgrid.org">LostGrid</a> 通过 calc() 轻松创建强大的栅格系统。</p><p>（5）<a href="http://postcss.parts/">更多插件</a> 更多功能</p><h2 id="PostCSS-webpack">PostCSS webpack</h2><p>在 webpack 中使用 PostCSS 的一般方式</p><ol><li>安装相关依赖</li></ol><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 postcss-loader --save-dev</span><br></pre></td></tr></table></figure><ol start="2"><li>创建 postcss.config.js</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="built_in">require</span>(<span class="string">&#x27;postcss-smart-import&#x27;</span>)(&#123; <span class="comment">/* ...options */</span> &#125;),</span><br><span class="line">    <span class="built_in">require</span>(<span class="string">&#x27;precss&#x27;</span>)(&#123; <span class="comment">/* ...options */</span> &#125;),</span><br><span class="line">    <span class="built_in">require</span>(<span class="string">&#x27;autoprefixer&#x27;</span>)(&#123; <span class="comment">/* ...options */</span> &#125;)</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以通过在不同路径下创建不同的 config 来实现配置覆盖的功能，在根目录下创建的 postcss.config.js 会被子目录中的配置文件覆盖。</p><ol start="3"><li>添加 PostCSS Loader 到 webpack.config.js 中，记得要把它放在 css-loader 和 style-loader 后面，如果有其它 loader，如 sass-loader 或者 less-loader， 要放在它们前面。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [</span><br><span class="line">          <span class="string">&#x27;style-loader&#x27;</span>,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">loader</span>: <span class="string">&#x27;css-loader&#x27;</span>,</span><br><span class="line">            <span class="attr">options</span>: &#123;</span><br><span class="line">              <span class="attr">importLoaders</span>: <span class="number">1</span></span><br><span class="line">            &#125;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">&#x27;postcss-loader&#x27;</span></span><br><span class="line">        ]</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><ol start="4"><li>如果不想使用 postcss.config.js 的话，也可以把插件直接写入到 webpack.config.js 中</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [&#123;</span><br><span class="line">      <span class="attr">test</span>: <span class="regexp">/\.css/</span>,</span><br><span class="line">      <span class="attr">use</span>: […&#123;</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&#x27;postcss-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">options</span>: &#123;</span><br><span class="line">          <span class="attr">plugins</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> [</span><br><span class="line">              <span class="built_in">require</span>(<span class="string">&#x27;precss&#x27;</span>),</span><br><span class="line">              <span class="built_in">require</span>(<span class="string">&#x27;autoprefixer&#x27;</span>)</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><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="demo">demo</h2><p>假如有 style.css 如下</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-pseudo">:root</span> &#123;</span><br><span class="line"> <span class="attr">--base-color</span>: gray;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">div</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">    <span class="attribute">border-radius</span>: <span class="number">10px</span>;</span><br><span class="line">    <span class="attribute">transition</span>: all <span class="number">0.8s</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">50px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="built_in">var</span>(--base-color);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>webpack 配置文件下</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cssnext = <span class="built_in">require</span>(<span class="string">&#x27;cssnext&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> autoprefixer = <span class="built_in">require</span>(<span class="string">&#x27;autoprefixer&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> px2rem = <span class="built_in">require</span>(<span class="string">&#x27;postcss-px2rem&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> <span class="title class_">Ex</span> = <span class="built_in">require</span>(<span class="string">&#x27;extract-text-webpack-plugin&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> webpack = <span class="built_in">require</span>(<span class="string">&#x27;webpack&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">entry</span>: <span class="string">&#x27;./src/style.css&#x27;</span>,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;./dist/[name].css&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">loaders</span>: [&#123;</span><br><span class="line">      <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">      <span class="attr">loader</span>: <span class="title class_">Ex</span>.<span class="title function_">extract</span>(&#123;</span><br><span class="line">        <span class="attr">fallback</span>: <span class="string">&#x27;style-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">use</span>: <span class="string">&#x27;css-loader!postcss-loader&#x27;</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Ex</span>(&#123;</span><br><span class="line">      <span class="attr">filename</span>: <span class="string">&#x27;./dist/style.css&#x27;</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> webpack.<span class="title class_">LoaderOptionsPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">options</span>: &#123;</span><br><span class="line">        <span class="attr">postcss</span>: [<span class="title function_">autoprefixer</span>(&#123;<span class="attr">browsers</span>: [<span class="string">&#x27;last 2 versions&#x27;</span>]&#125;), </span><br><span class="line">          <span class="title function_">cssnext</span>(), <span class="title function_">px2rem</span>(&#123;<span class="attr">remUnit</span>: <span class="number">100</span>&#125;)]</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>运行 webpack 命令后，dist 文件夹下面的 style.css 如下</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><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="selector-tag">div</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: -webkit-box;</span><br><span class="line">  <span class="attribute">display</span>: -ms-flexbox;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">0.1rem</span>;</span><br><span class="line">  -webkit-<span class="attribute">transition</span>: all <span class="number">0.8s</span>;</span><br><span class="line">  <span class="attribute">transition</span>: all <span class="number">0.8s</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">1rem</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">0.5rem</span>;</span><br><span class="line">  <span class="attribute">background</span>: gray;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里一共使用了三个插件，cssnext 解析 css 自定义属性和 val() 函数，autoprefixer 添加浏览器前缀，postcss-px2rem 完成 px 到 rem 单位的转化。</p><h2 id="参考资料">参考资料</h2><ol><li>参考 <a href="http://postcss.org/">PostCSS</a> 官方网站，了解 PostCSS 的更多内容。</li><li><a href="https://github.com/postcss/autoprefixer">autoprefixer</a></li><li><a href="http://cssnext.io/">cssnext</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;
&lt;p&gt;今天在吃早饭的时候就被同事@，说有一块页面效果在测试服务器的部署效果跟本地不一样：代码在本地运行没有问题，部署后发现有一个分割线的位置明显不对。来到公司后看了同事的演示，觉得可能是 css 代码压缩时出现了问题。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="webpack" scheme="https://lz5z.com/tags/webpack/"/>
    
    <category term="CSS" scheme="https://lz5z.com/tags/CSS/"/>
    
    <category term="PostCSS" scheme="https://lz5z.com/tags/PostCSS/"/>
    
  </entry>
  
  <entry>
    <title>webpack 从入门到放弃</title>
    <link href="https://lz5z.com/webpack/"/>
    <id>https://lz5z.com/webpack/</id>
    <published>2017-03-11T10:06:04.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介">简介</h2><p>Webpack + ES6 已经成为目前最流行的前端解决方案，本文是 Webpack2 学习教程。</p><p>在 「<a href="https://webpack.github.io/docs/what-is-webpack.html">What is webpack</a>」一文中作者讲述了自己为什么要开发出 webpack。</p><ol><li>切分代码依赖树到不同的代码块，按需加载</li><li>保持更少的初始化加载时间</li><li>把任何静态资源都视为模块</li><li>把任何第三方类库也当作模块</li><li>在模块打包中每一部分都允许自定义</li><li>更加适合大型项目</li></ol><span id="more"></span><h2 id="使用">使用</h2><h3 id="安装">安装</h3><p>新建 webpack-demo 文件夹，安装 webpack 到 dev</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">mkdir</span> webpack-demo</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm init -y</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm i webpack --save-dev</span></span><br></pre></td></tr></table></figure><h3 id="命令行打包">命令行打包</h3><p>新建一个 hello.js 文件</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="keyword">function</span> <span class="title function_">hello</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="string">&#x27;Hello webpack&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">hello</span>()</span><br></pre></td></tr></table></figure><p>在命令行中输入下面内容进行打包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">webpack hello.js hello.bundle.js</span></span><br></pre></td></tr></table></figure><p>打开打包后的文件发现里面注入了很多 webpack 所需的一些内置函数，比如 <strong><code>__webpack_require__</code></strong>，除此之外，webpack 还对我们写的代码进行编号，比如刚才我们写的 hello function 在 hello.bundle.js 中的编号就是  <code>/* 0 */</code>。</p><h3 id="引入css文件">引入css文件</h3><p>新建 style.css 文件</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></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">body</span> &#123;</span><br><span class="line">    <span class="attribute">background-color</span>: gray;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 hello.js 中引入该文件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;./style.css&#x27;</span>)</span><br></pre></td></tr></table></figure><p>再次使用刚才的命令打包，发现命令行报错</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ERROR in ./style.css</span><br><span class="line">Module parse failed: D:\webpack-demo\style.css Unexpected token (1:11)</span><br><span class="line">You may need an appropriate loader to handle this file type.</span><br><span class="line">| html, body &#123;</span><br><span class="line">|       background-color: gray;</span><br><span class="line"> @ ./hello.js 2:0-22</span><br></pre></td></tr></table></figure><p>错误提示很明显：模块解析错误，你可能需要一个合适的 loader 去处理这种类型的文件。</p><p>webpack 默认不支持 css 文件类型，所以我们来安装 css-loader 和 style-loader</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm i css-loader style-loader --save-dev</span></span><br></pre></td></tr></table></figure><p>css-loader 是使 webpack 可以处理 css 文件；style-loader 把 css-loader 处理完的代码，新建一个 style 标签，插入到 HTML 代码中。</p><p>然后将这两个 loader 引入 hello.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;style-loader!css-loader!./style.css&#x27;</span>)</span><br></pre></td></tr></table></figure><p>再次运行打包命令就可以在 hello.bundle.js 中找到下面这句话</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="title function_">push</span>([<span class="variable language_">module</span>.<span class="property">i</span>, <span class="string">&quot;body &#123;\r\n\tbackground-color: gray;\r\n&#125;&quot;</span>, <span class="string">&quot;&quot;</span>]);</span><br></pre></td></tr></table></figure><p>为了查看效果，我们新建 index.html</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>webpack demo<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./hello.bundle.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>打开 index.html 查看效果，发现 style-loader 在 head 中插入了一个 style 标签将 css 插入 html 中。</p><h2 id="webpack-配置文件">webpack <a href="https://webpack.js.org/configuration/">配置文件</a></h2><p>在命令行中输入 <code>webpack</code> 命令，webpack 会自动寻找 webpack.config.js 文件，并按照里面的配置对项目进行打包。还可以通过 <code>--config</code> 参数指定 webpack 配置文件。</p><p>webpack.config.js 使用 CommonJS 规范，下面是一个最基础的配置文件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">entry</span>: <span class="string">&#x27;./src/script/main.js&#x27;</span>,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&#x27;./dist/js&#x27;</span>,</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&#x27;bundle.js&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>entry</code> 参数表明我们的打包是从哪个文件开始的，<code>output</code> 参数定义打包后的文件如何存储。</p><p>如果需要使用一些 webpack 的参数，可以使用 npm 脚本来实现，比如</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></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;scripts&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;webpack&quot;</span>: <span class="string">&quot;webpack --display-modules --sort-modules-by size&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面是我们分析 webpack 打包后文件常用的方式，把每个 modules 显示出来，并且按照文件大小排序。</p><h2 id="webpack-几个重要概念">webpack 几个重要概念</h2><h3 id="entry"><a href="https://webpack.js.org/concepts/entry-points/">entry</a></h3><p>webpack 根据 entry 创建所有应用程序依赖图表，entry 告诉 webpack 从哪里开始，并遵循着依赖关系图打包。</p><p>entry 有以下几种写法</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">entry</span>: <span class="string">&#x27;./src/app.js&#x27;</span></span><br><span class="line"><span class="attr">entry</span>: [<span class="string">&#x27;./src/app.js&#x27;</span>, <span class="string">&#x27;./src/vendors.js&#x27;</span>]</span><br><span class="line"><span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="attr">main</span>: <span class="string">&#x27;./src/app.js&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="attr">app</span>: <span class="string">&#x27;./src/app.js&#x27;</span>,</span><br><span class="line">    <span class="attr">vendors</span>: <span class="string">&#x27;./src/vendors.js&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="attr">pageOne</span>: <span class="string">&#x27;./src/pageOne/index.js&#x27;</span>,</span><br><span class="line">    <span class="attr">pageTwo</span>: <span class="string">&#x27;./src/pageTwo/index.js&#x27;</span>,</span><br><span class="line">    <span class="attr">pageThree</span>: <span class="string">&#x27;./src/pageThree/index.js&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>指定多入口主要为了解决两种场景，一个是将业务代码和框架代码分割，一个是为了处理多页面应用。使用 <a href="https://github.com/webpack/docs/wiki/list-of-plugins#commonschunkplugin">CommonsChunkPlugin</a> 插件可以将公共的类库代码打包成一个 common 模块。这样在多页面程序中可以把共用代码缓存起来，方便其他页面使用。</p><h3 id="output"><a href="https://webpack.js.org/concepts/output/">output</a></h3><p>output 参数告诉 webpack 如何把编译后的文件写入到磁盘里，无论有多少个 entry 都只有一个 output 配置。一般形式的写法如下：</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="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&#x27;bundle.js&#x27;</span>,</span><br><span class="line">    <span class="attr">path</span>: __dirname + <span class="string">&#x27;/build&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>output.path 是一个绝对路径，filename 指生产打包文件后的名称</p><p>假如 entry 为多入口，使用上述写法只会生产一个 bundle.js，不符合我们代码分割的需求，那么我们可以用一些占位符来表示输出的结果。一共有四种占位符：[id], [name], [hash], [chunkhash]。注意 [hash] 指的是本次打包的 hash，这个 hash 在 webpack 打包时日志的第一行显示。而 [chunkhash] 是每一个 chunk 自己的 hash 值。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="attr">app</span>: <span class="string">&#x27;./src/app.js&#x27;</span>,</span><br><span class="line">    <span class="attr">search</span>: <span class="string">&#x27;./src/search.js&#x27;</span></span><br><span class="line">&#125;,</span><br><span class="line"><span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&#x27;[name]-[chunkhash].js&#x27;</span>,</span><br><span class="line">    <span class="attr">path</span>: __dirname + <span class="string">&#x27;/build&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>hash 值由 md5 算法生成，可以当做每个文件的版本号，这点对于我们管理产品时每次只上线被更改的文件非常有用。如果觉得默认 hash 值太长了，可以通过 [chunkhash:8] 来指定 hash 位数。</p><p>通常我们上线产品会使用 cdn 加速静态资源文件的获取，我们可以把 cdn 写入到 output.publicPath 中。publicPath 表示如果产品上线，js 的路径就会自动加上 publicPath。</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="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&#x27;[name]-[chunkhash].js&#x27;</span>,</span><br><span class="line">    <span class="attr">path</span>: __dirname + <span class="string">&#x27;/build&#x27;</span>,</span><br><span class="line">    <span class="attr">publicPath</span>: <span class="string">&#x27;http://cdn.example.com/&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="loaders"><a href="https://webpack.js.org/concepts/loaders/">loaders</a></h3><p>webpack 中把所有的资源都当做一个模块，无论这个文件是代码文件，还是图片文件，只要有对应的 loader 均可以在 webpack 中转换使用，这也是 webpack 最大的优势所在。</p><p>前面「引入css文件」中已经展示了如何使用 loader，通常配置方式如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">test</span>: <span class="regexp">/\.(js|jsx)$/</span>,</span><br><span class="line">            <span class="attr">use</span>: <span class="string">&#x27;babel-loader&#x27;</span></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>test 说明了当前 loader 能处理那些类型的文件的正则匹配，use 则指定了 loader 的类型。</p><p>注：这里说一下 webpack1 与 webpack2 的区别，在 webpack1 中，使用 module.loaders 声明 loader，而 webpack2 中使用功能更为强大的 module.rules。 为了兼容旧版，module.loaders 语法仍然有效，旧的属性名依然可以被解析。</p><p>loader 还可以在使用的时候传入相关的参数，比如我们使用 css-loader 时</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [&#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [</span><br><span class="line">            <span class="string">&#x27;style-loader&#x27;</span>, </span><br><span class="line">            &#123;</span><br><span class="line">                <span class="attr">loader</span>: <span class="string">&#x27;css-loader&#x27;</span>,</span><br><span class="line">                <span class="attr">options</span>: &#123;</span><br><span class="line">                    <span class="attr">importLoaders</span>: <span class="number">1</span></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>注：在 webpack 1 中，loader 可以链式调用，上一个 loader 的输出被作为输入传给下一个 loader，通常被用 ! 连写，如 <code>loader: &quot;style-loader!css-loader!less-loader&quot;</code>。这一写法在 webpack 2 中只在使用旧的选项 module.loaders 时才有效。使用 rule.use 配置选项，use 可以设置为一个 loader 数组。使用 module.rules 时，如果只有一个 loader，既可以用 loader 又可以用 use，但是如果是多 loader，则只能使用 use。</p><h4 id="处理-ES6-语法">处理 ES6 语法</h4><p>首先安装 babel</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install babel-loader babel-core --save-dev</span></span><br></pre></td></tr></table></figure><p>修改 webpack 配置文件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [&#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        <span class="attr">exclude</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;node_modules/&#x27;</span>),</span><br><span class="line">        <span class="attr">include</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;src/&#x27;</span>),</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span></span><br><span class="line">    &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意这里一定要加上 exclude 或者 include，因为 babel-loader 处理的速度非常慢。<br>然后还需要指定所用 ECMAScript 的版本，假如使用 ES6 语法</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install babel-preset-es2015 --save-dev</span></span><br></pre></td></tr></table></figure><p>告诉 webpack babel 使用哪个版本的 preset 有三种方式</p><p>(1) 在 webpack 中声明</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [&#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span>,</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span>,</span><br><span class="line">        <span class="attr">query</span>: &#123;</span><br><span class="line">            <span class="attr">presets</span>: [<span class="string">&quot;es2015&quot;</span>]</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注： 如果 loader 需要传参数的话，既可以写成 query 的形式，也可以写成像 url 传参一样的形式：</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="attr">rules</span>: [&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">    <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span>,</span><br><span class="line">    <span class="attr">loader</span>: <span class="string">&quot;babel-loader?presets=es2015&quot;</span></span><br><span class="line">&#125;]</span><br></pre></td></tr></table></figure><p>但是如果为多 loader 的话，只能用 use + options 的形式。</p><p>(2) 在根目录创建 .babelrc 文件，文件内容如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;presets&quot;: [&quot;es2015&quot;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(3) 在 package.json 中指定 preset</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></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;babel&quot;</span>: &#123;</span><br><span class="line">  <span class="string">&quot;presets&quot;</span>: [<span class="string">&quot;es2015&quot;</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="plugins"><a href="https://webpack.js.org/concepts/plugins/">plugins</a></h3><p>插件是 wepback 的支柱功能。在你使用 webpack 配置时，webpack 自身也构建于同样的插件系统上！插件目的在于解决 loader 无法实现的其他事，在<a href="https://webpack.js.org/plugins/">这个页面</a>你可以看到一些 webpack 常用的插件。</p><p>由于 plugin 可以传递参数，你必须在 wepback 配置中，向 plugins 属性传入 new 实例。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">HtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&#x27;html-webpack-plugin&#x27;</span>); <span class="comment">//通过 npm 安装</span></span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">&#x27;webpack&#x27;</span>); <span class="comment">//访问内置的插件</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line">  <span class="attr">entry</span>: <span class="string">&#x27;./path/to/my/entry/file.js&#x27;</span>,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&#x27;my-first-webpack.bundle.js&#x27;</span>,</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;dist&#x27;</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">loaders</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(js|jsx)$/</span>,</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&#x27;babel-loader&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> webpack.<span class="property">optimize</span>.<span class="title class_">UglifyJsPlugin</span>(),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HtmlWebpackPlugin</span>(&#123;<span class="attr">template</span>: <span class="string">&#x27;./src/index.html&#x27;</span>&#125;)</span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = config;</span><br></pre></td></tr></table></figure><h3 id="html-webpack-plugin-使用">html-webpack-plugin 使用</h3><p>下面我们以最常用的 <strong><a href="https://github.com/jantimon/html-webpack-plugin">html-webpack-plugin</a></strong> 为例，讲解插件的用法。</p><p>首先使用 npm 安装插件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm i html-webpack-plugin --save-dev</span></span><br></pre></td></tr></table></figure><p>然后在 webpack.config.js 配置文件中使用。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> htmlWebpackPlugin = <span class="built_in">require</span>(<span class="string">&#x27;html-webpack-plugin&#x27;</span>)</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">    <span class="attr">entry</span>: &#123;</span><br><span class="line">        <span class="attr">app</span>: <span class="string">&#x27;./src/script/main.js&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">        <span class="attr">filename</span>: <span class="string">&#x27;[name]-[chunkhash:8].js&#x27;</span>,</span><br><span class="line">        <span class="attr">path</span>: __dirname + <span class="string">&#x27;/build&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">        <span class="keyword">new</span> <span class="title function_">htmlWebpackPlugin</span>(&#123;</span><br><span class="line">            <span class="attr">filename</span>: <span class="string">&#x27;index-[hash].html&#x27;</span>,</span><br><span class="line">            <span class="attr">template</span>: <span class="string">&#x27;index.html&#x27;</span></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><strong>html-webpack-plugin</strong> 插件还能接受一些其它参数，比如<code>title</code>、<code>inject: (true | 'head' | 'body' | false)</code>、<code>favicon</code>、<code>minify</code>、<code>hash</code>、<code>cache</code>等。</p><p>还可以设置一些自定义的参数，在 html 文件中通过类似 js 模板语言的方式进行引用。<br>比如在 webpack 配置文件中</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title function_">htmlWebpackPlugin</span>(&#123;</span><br><span class="line">        <span class="attr">template</span>: <span class="string">&#x27;index.html&#x27;</span>,</span><br><span class="line">        <span class="attr">date</span>: <span class="keyword">new</span> <span class="title class_">Date</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>然后在 index.html 中使用 <code>&lt;%= htmlWebpackPlugin.options.date %&gt;</code> 对 date 进行引用，这样就给了我们更大的自由度，用相同的 html 模板生成不同的 html 文件。</p><p>通过加上 minify 来实现对 html 文件的压缩，minify 传入一个 <a href="https://github.com/kangax/html-minifier#options-quick-reference">html-minify</a> 对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title function_">htmlWebpackPlugin</span>(&#123;</span><br><span class="line">        <span class="attr">template</span>: <span class="string">&#x27;index.html&#x27;</span>,</span><br><span class="line">        <span class="attr">minify</span>: &#123;</span><br><span class="line">            <span class="attr">removeComments</span>: <span class="literal">true</span>,</span><br><span class="line">            <span class="attr">collapseInlineTagWhitespace</span>: <span class="literal">true</span>,</span><br><span class="line">            <span class="attr">collapseWhitespace</span>: <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>对于一个多页面应用程序，需要生成多少个页面，就 new 多少个 htmlWebpackPlugin 实例。假如不同的页面依赖不同的 chunks， 那么我们可以使用 chunks 参数指定当前页面所使用的 chunks。也可以使用 excludeChunks 来指定排除了某些 chunks 以后的全部 chunks。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> htmlWebpackPlugin = <span class="built_in">require</span>(<span class="string">&#x27;html-webpack-plugin&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">    <span class="attr">entry</span>: &#123;</span><br><span class="line">        <span class="attr">a</span>: <span class="string">&#x27;./src/script/a.js&#x27;</span>,</span><br><span class="line">        <span class="attr">b</span>: <span class="string">&#x27;./src/script/b.js&#x27;</span>,</span><br><span class="line">        <span class="attr">c</span>: <span class="string">&#x27;./src/script/c.js&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">        <span class="attr">filename</span>: <span class="string">&#x27;js/[name]-[chunkhash:8].js&#x27;</span>,</span><br><span class="line">        <span class="attr">path</span>: __dirname + <span class="string">&#x27;/build&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">        <span class="keyword">new</span> <span class="title function_">htmlWebpackPlugin</span>(&#123;</span><br><span class="line">            <span class="attr">filename</span>: <span class="string">&#x27;a.html&#x27;</span>,</span><br><span class="line">            <span class="attr">template</span>: <span class="string">&#x27;index.html&#x27;</span>,</span><br><span class="line">            <span class="attr">title</span>: <span class="string">&#x27;page a&#x27;</span>,</span><br><span class="line">            <span class="attr">chunks</span>: [<span class="string">&#x27;a&#x27;</span>]</span><br><span class="line">        &#125;),</span><br><span class="line">        <span class="keyword">new</span> <span class="title function_">htmlWebpackPlugin</span>(&#123;</span><br><span class="line">            <span class="attr">filename</span>: <span class="string">&#x27;b.html&#x27;</span>,</span><br><span class="line">            <span class="attr">template</span>: <span class="string">&#x27;index.html&#x27;</span>,</span><br><span class="line">            <span class="attr">title</span>: <span class="string">&#x27;page b&#x27;</span>,</span><br><span class="line">            <span class="attr">chunks</span>: [<span class="string">&#x27;b&#x27;</span>]</span><br><span class="line">        &#125;),</span><br><span class="line">        <span class="keyword">new</span> <span class="title function_">htmlWebpackPlugin</span>(&#123;</span><br><span class="line">            <span class="attr">filename</span>: <span class="string">&#x27;c.html&#x27;</span>,</span><br><span class="line">            <span class="attr">template</span>: <span class="string">&#x27;index.html&#x27;</span>,</span><br><span class="line">            <span class="attr">title</span>: <span class="string">&#x27;page c&#x27;</span>,</span><br><span class="line">            <span class="attr">chunks</span>: [<span class="string">&#x27;c&#x27;</span>]</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>对应的 html 模板文件为：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>&lt;%= htmlWebpackPlugin.options.title %&gt;<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>运行<code>npm run webpack</code>后生成 3 个 html 文件，分别引入其所需要的依赖。</p><h2 id="webpack-处理资源文件">webpack 处理资源文件</h2><h3 id="样式文件-less-sass">样式文件 less/sass</h3><p>假如有这么一段 less</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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.layer</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">600px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line">    <span class="attribute">background-color</span>: green;</span><br><span class="line">    &gt; <span class="selector-tag">div</span> &#123;</span><br><span class="line">        <span class="attribute">width</span>: <span class="number">400px</span>;</span><br><span class="line">        <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line">        <span class="attribute">background</span>: gray;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先安装 less 和 less-loader</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install less less-loader --save-dev</span></span><br></pre></td></tr></table></figure><p>在 webpack 配置文件中加入 less-loader</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [</span><br><span class="line">            <span class="string">&quot;style-loader&quot;</span>,</span><br><span class="line">            <span class="string">&quot;css-loader&quot;</span>,</span><br><span class="line">            <span class="string">&quot;less-loader&quot;</span></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>loader 的执行顺序为从后往前执行，所以其顺序为 less-loader -&gt; css-loader -&gt; style-loader。 如果需要引入 postcss-loader 的话，应该放在 less-loader 和 css-loader 中间。</p><h3 id="图片文件">图片文件</h3><p>图片文件一般使用 <a href="https://github.com/webpack-contrib/file-loader">file-loader</a> 配合 <a href="https://github.com/webpack-contrib/url-loader">url-loader</a>，如果有压缩需求的话，可以使用 <a href="https://github.com/tcoopman/image-webpack-loader">image-webpack-loader</a></p><p>安装两个 loader</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm i file-loader url-loader --save-dev</span></span><br></pre></td></tr></table></figure><p>url-loader 的功能与 file-loader 十分相似，不同的是 url-loader 可以指定一个 limit 参数， 当图片或者文件的大小大于 limit 的时候，url-loader 把资源直接交给 file-loader 处理，而当资源小于 limit 的时候，url-loader 会把图片转为 base64 的编码，并直接打包到引用的文件中。</p><p>file-loader 打包的文件通过 http 请求获取，url-loader 打包的文件通过 base64 的方式获取，这两个方法各有各的优势。通过 http 载入的图片可以享受到浏览器的图片缓存，当图片重复使用次数比较多的时候具有一定的便利。base64 的方式引入图片可以降低 http 请求的次数，但是也会带来一定程度的代码冗余。</p><p>(1) 使用 file-loader</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [&#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(ico|jpe?g|png|svg|gif)$/i</span>,</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&#x27;file-loader&#x27;</span></span><br><span class="line">    &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(2) 使用 url-loader</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [&#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(ico|jpe?g|png|svg|gif)$/i</span>,</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&#x27;url-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">query</span>: &#123;</span><br><span class="line">            <span class="attr">limit</span>: <span class="number">10000</span>,</span><br><span class="line">            <span class="attr">name</span>: <span class="string">&#x27;assets/[name]-[hash].[ext]&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>(3) image-webpack-loader 可以对图片文件进行压缩，并且配合 url-loader 和 file-loader 共同使用</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [&#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(ico|jpe?g|png|svg|gif)$/i</span>,</span><br><span class="line">        <span class="attr">use</span>: [</span><br><span class="line">            <span class="string">&#x27;url-loader?limit=10000&amp;name=assets/[name]-[hash].[ext]&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;image-webpack-loader?options=&#123;&#125;&#x27;</span></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>loader 的参数也可以通过 options 传递</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="attr">test</span>: <span class="regexp">/\.(ico|jpe?g|png|svg|gif)$/i</span>,</span><br><span class="line"><span class="attr">use</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&#x27;url-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">options</span>: &#123;</span><br><span class="line">            <span class="attr">limit</span>: <span class="number">10000</span>,</span><br><span class="line">            <span class="attr">name</span>: <span class="string">&#x27;assets/[name]-[hash].[ext]&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&#x27;image-webpack-loader&#x27;</span>,</span><br><span class="line">        <span class="attr">options</span>: &#123;&#125;</span><br><span class="line">    &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>image-webpack-loader 可以针对不同的图片类型就行压缩，详细的信息可以在<a href="https://github.com/tcoopman/image-webpack-loader">官网</a>里面查询。</p><p>注：在 image-webpack-loader 实际使用过程中，必须传入一个 options 参数，否则会报错，使用的时候注意一下。</p><blockquote><p>ERROR in   Error: Child compilation failed:<br>Module build failed: TypeError: Cannot read property ‘bypassOnDebug’ of null</p></blockquote><h2 id="总结">总结</h2><p>本文只是 webpack 打包的一些知识，只涉及到一些基本使用，关于 webpack 在项目中的实际应用，以及打包的一些技巧和优化，会在下一节中讲起。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;简介&lt;/h2&gt;
&lt;p&gt;Webpack + ES6 已经成为目前最流行的前端解决方案，本文是 Webpack2 学习教程。&lt;/p&gt;
&lt;p&gt;在 「&lt;a href=&quot;https://webpack.github.io/docs/what-is-webpack.html&quot;&gt;What is webpack&lt;/a&gt;」一文中作者讲述了自己为什么要开发出 webpack。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;切分代码依赖树到不同的代码块，按需加载&lt;/li&gt;
&lt;li&gt;保持更少的初始化加载时间&lt;/li&gt;
&lt;li&gt;把任何静态资源都视为模块&lt;/li&gt;
&lt;li&gt;把任何第三方类库也当作模块&lt;/li&gt;
&lt;li&gt;在模块打包中每一部分都允许自定义&lt;/li&gt;
&lt;li&gt;更加适合大型项目&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="webpack" scheme="https://lz5z.com/tags/webpack/"/>
    
  </entry>
  
  <entry>
    <title>CSS 中的各种单位</title>
    <link href="https://lz5z.com/CSS%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E5%8D%95%E4%BD%8D/"/>
    <id>https://lz5z.com/CSS%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E5%8D%95%E4%BD%8D/</id>
    <published>2017-02-23T06:15:49.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>之前遇到 css 中需要使用单位的情况，都草草用 px 或者百分比糊弄过去，导致当需要做一个响应式的页面的时候，要重新补一下 css 单位的技术债。</p><span id="more"></span><h2 id="px">px</h2><p>px 是 css 中最常用的字体大小单位。<br>px 就是表示 pixel，像素，是屏幕上显示数据的最基本的点；还有一个看起来很像的单位 pt，pt 就是 point，是印刷行业常用单位，等于1/72英寸，一般在打印的时候使用。<br>像素 px 是相对于显示器屏幕分辨率而言的，所以一般把它看做一个基础单位，很多其它单位都是以 px 为参照的。</p><h2 id="em-rem">em rem</h2><p>em 指的是相对于当前对象内文本的字体大小，比如设置 body 的字体大小(font-size)为 14px，而对 body 内所有的 div 设置字体大小为 1.5em，那么 div 内字体大小就是 14px * 1.5 = 21px</p><p>通常写 html 的时候会发生很多嵌套，每个节点都从父节点继承字体大小，这样很难控制每个层级的字体大小。rem (roo em) 应运而生，rem 是指相对于根节点字体大小，通常根节点是指 html 元素。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">html</span> &#123; <span class="attribute">font-size</span>: <span class="number">14px</span>; &#125; </span><br><span class="line"><span class="selector-tag">div</span> &#123; <span class="attribute">font-size</span>: <span class="number">1.5rem</span>; &#125;</span><br></pre></td></tr></table></figure><p>这样所有 div 中字体的大小都是 21px 了。</p><h2 id="百分比">百分比</h2><p>css 中的百分比是一种相对值，使用百分比的关键是找到它的参照物。</p><table><thead><tr><th style="text-align:center">属性</th><th style="text-align:center">参照</th></tr></thead><tbody><tr><td style="text-align:center">width &amp; height</td><td style="text-align:center">宽和高在使用百分比值时，其参照一般都是父元素的 content 的宽和高。</td></tr><tr><td style="text-align:center">margin &amp; padding</td><td style="text-align:center">margin 和 padding，其任意方向的百分比值，参照都是包含块的宽度。</td></tr><tr><td style="text-align:center">border-radius</td><td style="text-align:center">为一个元素的border-radius定义的百分比值，参照物是这个元素自身的尺寸。border-radius:50%;</td></tr><tr><td style="text-align:center">font-size</td><td style="text-align:center">参照是直接父元素的 font-size。</td></tr><tr><td style="text-align:center">line-height</td><td style="text-align:center">参照是元素自身的font-size</td></tr><tr><td style="text-align:center">vertical-align</td><td style="text-align:center">参照是元素自身的line-height</td></tr><tr><td style="text-align:center">bottom、left、right、top</td><td style="text-align:center">参照是元素的包含块。left和right是参照包含块的宽度，bottom和top是参照包含块的高度。</td></tr><tr><td style="text-align:center">transform: translate</td><td style="text-align:center">参照是元素自己的边界框的尺寸</td></tr></tbody></table><h2 id="vh-vm">vh vm</h2><p>移动互联网时代各种设备大小不一，响应式的布局变得更加流行，而响应式布局很大程度上依赖比例规则。</p><p>vh 和 vm 也是相对长度，不过其参照是显示窗口的宽度或高度，一般来说 100 vh = viewport 的高度，100vm = viewport 的宽度。</p><p>下面一段话是响应式的，你可以缩放浏览器大小来查看效果。</p><!DOCTYPE html><html><body><div class="css-vm-test">缩放浏览器大小来查看效果</div></body><style>.css-vm-test { font-size: 3vw; color: red; }</style></html><h2 id="vmin-和-vmax">vmin 和 vmax</h2><p>vmin 和 vmax 的出现主要是为了移动设备横竖屏切换。vmax 是相对于 viewport 宽度或者高度中比较大的一个，vmin 则是比较小的那个。比如手机屏幕宽度为1100px，高度为700px，那么 100vmin = 700px, 100vmax = 1100px。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;之前遇到 css 中需要使用单位的情况，都草草用 px 或者百分比糊弄过去，导致当需要做一个响应式的页面的时候，要重新补一下 css 单位的技术债。&lt;/p&gt;</summary>
    
    
    
    <category term="CSS" scheme="https://lz5z.com/categories/CSS/"/>
    
    
    <category term="CSS" scheme="https://lz5z.com/tags/CSS/"/>
    
  </entry>
  
  <entry>
    <title>vue2.0 组件通信</title>
    <link href="https://lz5z.com/vue2%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/"/>
    <id>https://lz5z.com/vue2%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/</id>
    <published>2017-02-17T02:46:29.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<p>Vue 采用基于组件的开发方式，那么组件之间的通信必不可少：比如父组件要给子组件传递数据，子组件将它内部发生的事情告知给父组件，因此定义一个良好的接口尽可能将组件解耦显得尤为重要，这保证不同的组件可以在相对独立的环境中开发测试，而且更方便阅读理解以及组件复用。</p><p>Vue 父子组件之间通信主要采取两种方式，通常可以总结为 <strong>props down</strong>、<strong>events up</strong>，父组件通过 props 向下传递数据给子组件，子组件通过 events 给父组件发送消息，这点跟 React 一模一样。</p><span id="more"></span><p>Vue2.0 废除了 <code>events</code>、<code>$dispatch</code>、<code>$broadcast</code> 几个事件，官方推荐使用 <a href="https://github.com/vuejs/vue/issues/2873">全局事件驱动 或者 vuex</a>代替，目前只剩下 <code>vm.$on</code>、<code>vm.$once</code>、<code>vm.$off</code>、<code>vm.$emit</code> 几个事件。</p><h2 id="props-down">props down</h2><p>Vue 组件之间的作用域是相互隔离的，父组件向子组件传值只能通过 props 的方式，子组件不能直接调用父组件的数据。在子组件中，如果需要调用父组件传来的参数，必须显式的声明 props。</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="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;child&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">props</span>: [<span class="string">&#x27;message&#x27;</span>],</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;span&gt;&#123;&#123; message &#125;&#125;&lt;/span&gt;&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>父组件向子组件传值</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">child</span> <span class="attr">message</span>=<span class="string">&quot;hello!&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">child</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="单向数据流">单向数据流</h3><p>props 传递值只能父组件向子组件传递，不能反回来，每当父组件更新时，子组件中的 props 会自动更新。如果在子组件中更改 props，Vue 控制台会给出 warning。因此如果需要在子组件中更改 props 通常会把其作为初始值赋值给某个变量，然后变量的值，或者在计算属性中定义一个基于 props 的值。</p><h2 id="events-up">events up</h2><p>如果子组件需要把信息传达给父组件，可以使用 <code>v-on</code> 绑定自定义事件</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;counter-event-example&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;&#123; total &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button-counter</span> <span class="attr">v-on:increment</span>=<span class="string">&quot;incrementTotal&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">button-counter</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button-counter</span> <span class="attr">v-on:increment</span>=<span class="string">&quot;incrementTotal&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">button-counter</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>我们给 button-counter 绑定了一个自定义事件 <code>increment</code>，v-on 绑定事件还可以简写为 <code>@increment=&quot;&quot;</code>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;button-counter&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">&#x27;&lt;button v-on:click=&quot;increment&quot;&gt;&#123;&#123; counter &#125;&#125;&lt;/button&gt;&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">counter</span>: <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">increment</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">counter</span> += <span class="number">1</span></span><br><span class="line">      <span class="variable language_">this</span>.$emit(<span class="string">&#x27;increment&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>: <span class="string">&#x27;#counter-event-example&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">total</span>: <span class="number">0</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">incrementTotal</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">total</span> += <span class="number">1</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>button-counter 组件的模板中包含一个 button，其 click 事件会触发($emit)自定义事件 <code>increment</code>，因此每次在子组件中点击一次 button，父组件中都会调用 incrementTotal() 方法。</p><h2 id="非父子组件通信">非父子组件通信</h2><p>上面讲的两种方法都父子组件之间的通信，有时候非父子关系的组件也需要通信。在 Vue1.0 时代，可以通过 $dispatch 和 $broadcast 来解决，首先 dispatch 到根组件，然后再 broadcast 到子组件。Vue2.0 中官方推荐用 event bus 或者 vuex 解决，event bus 的本质是一个发布者订阅者模式。</p><ul><li>使用一个空的 Vue 实例作为中央事件总线<br><code>var bus = new Vue()</code></li><li>触发组件 A 中的事件(发布消息)<br><code>bus.$emit('id-selected', 1)</code></li><li>在组件 B 创建的钩子中监听事件（订阅消息）<br><code>bus.$on('id-selected', function (id) &#123;&#125;)</code></li></ul><p>下面是 <a href="https://stackoverflow.com/questions/38064054/vue-js-global-event-bus">stackoverflow</a> 上面的一个例子</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;example&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">Display</span>&gt;</span><span class="tag">&lt;/<span class="name">Display</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">Increment</span>&gt;</span><span class="tag">&lt;/<span class="name">Increment</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> bus = <span class="keyword">new</span> <span class="title class_">Vue</span>()</span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;Increment&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">`&lt;button @click=&quot;increment&quot;&gt;+&lt;/button&gt;`</span>,</span><br><span class="line">  <span class="attr">data</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">   <span class="keyword">return</span> &#123;<span class="attr">count</span>: <span class="number">1</span>&#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="attr">increment</span>: <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">      <span class="keyword">var</span> increment = <span class="variable language_">this</span>.<span class="property">count</span>++</span><br><span class="line">      bus.$emit(<span class="string">&#x27;inc&#x27;</span>, increment)</span><br><span class="line">  &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">component</span>(<span class="string">&#x27;Display&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">template</span>: <span class="string">`&lt;h3&gt;Clicked: &#123;&#123;count&#125;&#125; times&lt;/h3&gt;`</span>,</span><br><span class="line">  <span class="attr">data</span>: <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;<span class="attr">count</span>: <span class="number">0</span>&#125;</span><br><span class="line">  &#125;,</span><br><span class="line"> <span class="attr">created</span>: <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">   bus.$on(<span class="string">&#x27;inc&#x27;</span>, <span class="keyword">function</span>(<span class="params">num</span>)&#123;</span><br><span class="line">     <span class="variable language_">this</span>.<span class="property">count</span> = num</span><br><span class="line">   &#125;.<span class="title function_">bind</span>(<span class="variable language_">this</span>))</span><br><span class="line"> &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line"> <span class="attr">el</span>: <span class="string">&quot;#example&quot;</span>,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="全局状态管理-Vuex">全局状态管理 <a href="https://vuex.vuejs.org">Vuex</a></h2><p>Vuex 是 Vue 组件的一个状态管理器，相当于一个只为 Vue 服务的 <a href="http://redux.js.org/">Redux</a>。下面一个图能很好的反映出 Vuex 是如何让组件之间通信的。</p><img src="/assets/img/vuex.png" alt="vuex" width="50%"><p>下面是 Vuex 官网上给出的一个 <a href="https://jsfiddle.net/n9jmu5v7/341/">计数器的例子</a></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;&#123; count &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">&quot;increment&quot;</span>&gt;</span>+<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">&quot;decrement&quot;</span>&gt;</span>-<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> store = <span class="keyword">new</span> <span class="title class_">Vuex</span>.<span class="title class_">Store</span>(&#123;</span><br><span class="line">  <span class="attr">state</span>: &#123;</span><br><span class="line">    <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mutations</span>: &#123;</span><br><span class="line">    <span class="attr">increment</span>: <span class="function"><span class="params">state</span> =&gt;</span> state.<span class="property">count</span>++,</span><br><span class="line">    <span class="attr">decrement</span>: <span class="function"><span class="params">state</span> =&gt;</span> state.<span class="property">count</span>--</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span><br><span class="line">  <span class="attr">computed</span>: &#123;</span><br><span class="line">    <span class="title function_">count</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> store.<span class="property">state</span>.<span class="property">count</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">methods</span>: &#123;</span><br><span class="line">    <span class="title function_">increment</span>(<span class="params"></span>) &#123;</span><br><span class="line">      store.<span class="title function_">commit</span>(<span class="string">&#x27;increment&#x27;</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title function_">decrement</span>(<span class="params"></span>) &#123;</span><br><span class="line">      store.<span class="title function_">commit</span>(<span class="string">&#x27;decrement&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>点击查看效果</p><!DOCTYPE html><html lang="en"><body><div id="app">  <p>{{ count }}</p>  <p>    <button @click="increment">+</button>    <button @click="decrement">-</button>  </p></div></body><script src="https://unpkg.com/vue/dist/vue.js"></script><script src="https://unpkg.com/vuex@2.0.0"></script><script type="text/javascript">const store = new Vuex.Store({  state: {    count: 0  },  mutations: {    increment: state => state.count++,    decrement: state => state.count--  }})const app = new Vue({  el: '#app',  computed: {    count() {      return store.state.count    }  },  methods: {    increment() {      store.commit('increment')    },    decrement() {      store.commit('decrement')    }  }})</script></html><p>在 Vuex 中，store 是组件状态的一个容器，上面的 store 中定义了一个初始的 state 对象，和两个 mutations 函数。我们可以通过 store.state 来获取状态对象，以及通过 store.commit 方法触发状态变更。要注意的是，我们不能直接更改 store 中的状态，改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。</p><h2 id="总结">总结</h2><ol><li>父组件向子组件传递信息使用 props down</li><li>子组件向父组件传递信息使用 event up</li><li>其它关系类型组件通信使用 global event bus</li><li>大型 SPA 组件之间通信使用 Vuex 管理组件状态</li></ol><p>如果想要在 vue2 中使用 dispatch 和 broadcast，可以参考 <a href="https://lz5z.com/vue2%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1-%E4%BD%BF%E7%94%A8dispatch%E5%92%8Cbroadcast/">vue2 组件通信——使用 dispatch 和 broadcast</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Vue 采用基于组件的开发方式，那么组件之间的通信必不可少：比如父组件要给子组件传递数据，子组件将它内部发生的事情告知给父组件，因此定义一个良好的接口尽可能将组件解耦显得尤为重要，这保证不同的组件可以在相对独立的环境中开发测试，而且更方便阅读理解以及组件复用。&lt;/p&gt;
&lt;p&gt;Vue 父子组件之间通信主要采取两种方式，通常可以总结为 &lt;strong&gt;props down&lt;/strong&gt;、&lt;strong&gt;events up&lt;/strong&gt;，父组件通过 props 向下传递数据给子组件，子组件通过 events 给父组件发送消息，这点跟 React 一模一样。&lt;/p&gt;</summary>
    
    
    
    <category term="Vue" scheme="https://lz5z.com/categories/Vue/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Vue" scheme="https://lz5z.com/tags/Vue/"/>
    
    <category term="Vuex" scheme="https://lz5z.com/tags/Vuex/"/>
    
  </entry>
  
  <entry>
    <title>跳槽小记</title>
    <link href="https://lz5z.com/%E8%B7%B3%E6%A7%BD%E5%B0%8F%E8%AE%B0/"/>
    <id>https://lz5z.com/%E8%B7%B3%E6%A7%BD%E5%B0%8F%E8%AE%B0/</id>
    <published>2017-02-15T05:26:28.000Z</published>
    <updated>2026-05-07T14:50:53.976Z</updated>
    
    <content type="html"><![CDATA[<img src="/assets/img/跳槽.png" alt="跳槽"><h2 id="面试经历">面试经历</h2><p>一共去金山面了三次试，一次是后台开发，两次是前台开发。去年11月份的时候第一次去金山面试服务器开发的职位，很多问题都没有答上来，就惨淡离去了。后来又有一个前端开发的岗位，抱着试一试的态度就去了。</p><span id="more"></span><h3 id="第一次前端面试">第一次前端面试</h3><p>刚开始跟第一个面试官聊得挺不错，技术问题讨论完了，面试官问我能否带项目，我表示自己暂时没有那个能力。面试官又问及学习能力，我表示自己平时一直坚持学习新知识，也有读一些书，然后这个面试官就出去了。出去以后听到外面有人在讨论，我隐约听到刚才的面试官说：「他完全不符合这个岗位的要求』。我想，看来这次又没戏了，然后安慰自己：没关系，回去继续学习就好了，反正知识都是自己的。</p><p>过了一会，进来了两个人，一个是项目组的经理，另外一个不知道是什么职位。经理首先跟我说，他们不是进来面试的，就是想跟我聊一下。那就聊呗。原来他们是想招一个有三五年经验的可以直接带项目的人，目前有一个项目的重构工作要做，不过看我表现还行，就想看下我有没有这个能力在短时间内成长为这样的角色。这个时候我内心是窃喜的，对于一个只有一年多工作经验的新人来说，能有这样一个锻炼的机会是多么宝贵，可是又觉得压力挺大，毕竟之前都是在 leader 手下工作，只是做一下 task，框架设计类的工作都是 leader 做好的，我只在里面添加功能而已。</p><p>后面一直聊，关于能否胜任这个职位，我始终都没有给经理一个准确的答复，只表示这对我是一个很大的挑战，也是我努力的方向。我想，后来没有拿到这个职位主要也是因为这个原因吧。不过职位是双向选择的，即使我愿意，他们也肯定会把我跟其他应聘者比较，最终选出合适的人选。</p><p>跟这个经理聊完已经快要六点了，这个时候我又累又渴，聊了这么久口干舌燥。经理出去后，HR 助理过来说 HR 还在面试别人，等下会过来，让我再稍等一会。那天好像有很多实习生过来，所以就把我排在了最后。</p><p>终于等到了 HR，HR 看起来也很疲倦，我就首先向她慰问，表示辛苦了。然后 HR 简单地聊了几句，就结束了，整个过程不到五分钟。</p><p>第二天 HR 助理打来电话，说我跟职位要求不是特别符合，不过有另外一个部门的职位问我愿不愿意试一下。我说愿意啊，然后答应过几天再去面试。感觉好累啊。</p><h3 id="第二次面试">第二次面试</h3><p>再一次去面试就轻松多了，跟第一个面试官简单聊了几个技术问题，写了几行代码他就出去了。第二个面试官是经理，也没有多聊，问了一些 HTTP 的问题，就 OK 了，两个面试加一起半个小时不到。</p><p>然后 HR 助理过来说之前也跟 HR 聊过了，让我先回去等通知，我就回去了。</p><p>第二天收到了 offer，工资比我要的低了一点，不过总体还算满意，就答应了。</p><h2 id="为什么跳槽">为什么跳槽</h2><p>首先我的老东家 OOCL 真是一家挺不错的公司，尤其在培养人方面非常舍得投入。而且各种分享，演讲都非常多。入职的时候跟 OOCL 签的培训协议，如果干不满两年，需要赔偿 X 美金（按月递减）的违约金。违约金还挺多的，不过个人觉得非常值，比在社会上报那些乱七八糟的培训班强多了。工作前三个月半封闭脱产培训，请的中科院的老师，讲课深入浅出，在那几个月内我也进步很大。最后离职的时候由于我只工作了一年半，赔偿了一部分钱。除了培训以外，OOCL 整个福利待遇在珠海来说也都算不错，每年两次调薪，竞争力还是比较大。</p><p>OOCL 这么好你为啥要走呢？</p><p>OOCL 是一个船运公司，有非常复杂的业务。在 OOCL 的时间大部分都在搞业务，而且做的东西很杂，感觉学了很多东西，又样样都不精通。去面试后端的时候，关于缓存，高并发之类的问题都没能答上来。而在互联网公司会觉得自己跟用户更近一些，业务方面也会很好理解。专职做前端或者后端会让自己在某个领域更精通一些，我还是希望成为某个领域的砖家，在金山这个理想实现起来会更快一些。</p><h2 id="金山怎么样啊？">金山怎么样啊？</h2><p>今天是入职第二天，说实话给不了太多的评价，只能从外部简单对比一下 OOCL 和金山。</p><h3 id="穿着">穿着</h3><p>OOCL 是香港的公司，感觉大家更 formal 一些，邮件、IM 基本都是英文，穿着也不会太随意。金山是典型的互联网公司，穿拖鞋短裤是家常便饭。</p><h3 id="食堂">食堂</h3><p>OOCL 没有食堂，不过培训的时候有中餐可以吃，质量参差不齐，好坏看运气。金山的食堂真心不错，荤素搭配，米面汤粉都有，而且三餐免费，赞。</p><h3 id="办公环境">办公环境</h3><p>办公环境的话 OOCL 就略胜一筹了，安静整洁，空调全年恒温。金山要乱一些，平时很多人说话，甚至还有人戴着耳机听歌的时候跟着吹口哨<img src="/assets/img/我也是醉了.jpg" alt="醉了">。还有一个要吐槽金山的是，新到职，没有新电脑用，为什么大家都有 mac 用，我只能用 win？</p><h3 id="加班">加班</h3><p>OOCL 是不加班的，即使有项目特别紧的时候加班，也会把加班的时间补给你，也就是可以在项目没那么紧的时候多休几天假，这点很人性化。金山据说加班挺严重的，目前还不知道如何。</p><h3 id="待遇">待遇</h3><p>其实这次跳槽，没有涨多少工资，感觉待遇差不太多。不知道这边工资上升空间大不大。不过金山住房公积金交 12%，这点不错。</p><h3 id="技术">技术</h3><p>技术上其实 OOCL 算是一个比较敢尝新的公司，除了公司主要业务网站一直坚持 JavaEE 的技术栈以外，很多新的业务都是用比较新的技术做的。比如我去年就接触了 MEAN、Hadoop、Scala、Spark、HBase、Hive、Impala、机器学习等很多比较新潮的技术，虽然很多都是浅尝辄止。也是前面几个月维护一个 Node.JS 的项目让我对 JavaScript 生态产生了兴趣，最终转入前端行业的。金山这边似乎也是比较愿意接受吸纳新技术，就我在的项目组而言，已经开始完全用 ES6 开发产品，对 React 和 Vue 也采取乐观的态度。</p><h2 id="最后">最后</h2><p>今年我的目标是丰富自己前端技术栈，提高自己独立开发的能力，弥补自己 CSS 方面的劣势。无论如何，希望今后能过得更好，技术要来越好，钱越来越多。</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/assets/img/跳槽.png&quot; alt=&quot;跳槽&quot;&gt;
&lt;h2 id=&quot;面试经历&quot;&gt;面试经历&lt;/h2&gt;
&lt;p&gt;一共去金山面了三次试，一次是后台开发，两次是前台开发。去年11月份的时候第一次去金山面试服务器开发的职位，很多问题都没有答上来，就惨淡离去了。后来又有一个前端开发的岗位，抱着试一试的态度就去了。&lt;/p&gt;</summary>
    
    
    
    <category term="Work" scheme="https://lz5z.com/categories/Work/"/>
    
    
    <category term="跳槽" scheme="https://lz5z.com/tags/%E8%B7%B3%E6%A7%BD/"/>
    
  </entry>
  
  <entry>
    <title>ReactJS 学习——组件2</title>
    <link href="https://lz5z.com/ReactJS%E2%80%94%E7%BB%84%E4%BB%B62/"/>
    <id>https://lz5z.com/ReactJS%E2%80%94%E7%BB%84%E4%BB%B62/</id>
    <published>2017-02-13T11:35:03.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h2 id="组件列表">组件列表</h2><p>使用循环的方式创建组件列表</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">const</span> listItems = numbers.<span class="title function_">map</span>(<span class="function">(<span class="params">number</span>) =&gt;</span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">);</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span>&#123;listItems&#125;<span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><span id="more"></span><p>使用参数</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">NumberList</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> numbers = props.<span class="property">numbers</span>;</span><br><span class="line">  <span class="keyword">const</span> listItems = numbers.<span class="title function_">map</span>(<span class="function">(<span class="params">number</span>) =&gt;</span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;number.toString()&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;number&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span>&#123;listItems&#125;<span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">NumberList</span> <span class="attr">numbers</span>=<span class="string">&#123;numbers&#125;</span> /&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>注意上面代码中的 <code>key</code>，它是一个 string 类型的属性，在创建 lists 元素的时候，你需要添加这个属性，如果不添加会有 warning。</p><h2 id="Keys">Keys</h2><p>React 元素可以具有一个特殊的属性 key，这个属性不是给用户用的，而是给 React 自己用的。如果我们动态地创建 React 元素，而且 React 元素内包含数量或顺序不确定的子元素时，我们就需要提供 key 这个特殊的属性。</p><p>为什么需要给每一个元素一个标识呢？我们知道当组件的属性发生了变化，其 render 方法会被重新调用，组件会被重新渲染。比如元素里面 <code>[&#123;name: 'Leo'&#125;] =&gt; [&#123;name: 'Jack'&#125;]</code> 那么有可能是删除了 Leo，然后为 Jack 新建了一个，也有可能是更改了 name 属性，因此为数组中的元素传一个唯一的 key（比如用户的 ID），就很好地解决了这个问题。React 比较更新前后的元素 key 值，如果相同则更新，如果不同则销毁之前的，重新创建一个元素。</p><h3 id="Keys-的用法">Keys 的用法</h3><p>Keys 只能被定义在循环里面</p><p>以下用法都是错误的</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ListItem</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> value = props.<span class="property">value</span>;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="comment">// Wrong! There is no need to specify the key here:</span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;value.toString()&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;value&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">NumberList</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> numbers = props.<span class="property">numbers</span>;</span><br><span class="line">  <span class="keyword">const</span> listItems = numbers.<span class="title function_">map</span>(<span class="function">(<span class="params">number</span>) =&gt;</span></span><br><span class="line">    <span class="comment">// Wrong! The key should have been specified here:</span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ListItem</span> <span class="attr">value</span>=<span class="string">&#123;number&#125;</span> /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;listItems&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">NumberList</span> <span class="attr">numbers</span>=<span class="string">&#123;numbers&#125;</span> /&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>下面是正确的用法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ListItem</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="comment">// Correct! There is no need to specify the key here:</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>&#123;props.value&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">NumberList</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> numbers = props.<span class="property">numbers</span>;</span><br><span class="line">  <span class="keyword">const</span> listItems = numbers.<span class="title function_">map</span>(<span class="function">(<span class="params">number</span>) =&gt;</span></span><br><span class="line">    <span class="comment">// Correct! Key should be specified inside the array.</span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ListItem</span> <span class="attr">key</span>=<span class="string">&#123;number.toString()&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">value</span>=<span class="string">&#123;number&#125;</span> /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;listItems&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">NumberList</span> <span class="attr">numbers</span>=<span class="string">&#123;numbers&#125;</span> /&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;组件列表&quot;&gt;组件列表&lt;/h2&gt;
&lt;p&gt;使用循环的方式创建组件列表&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; numbers = [&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;4&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;5&lt;/span&gt;];&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;const&lt;/span&gt; listItems = numbers.&lt;span class=&quot;title function_&quot;&gt;map&lt;/span&gt;(&lt;span class=&quot;function&quot;&gt;(&lt;span class=&quot;params&quot;&gt;number&lt;/span&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;&amp;#123;number&amp;#125;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;li&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;title class_&quot;&gt;ReactDOM&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;render&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;language-xml&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;&amp;#123;listItems&amp;#125;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;ul&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;variable language_&quot;&gt;document&lt;/span&gt;.&lt;span class=&quot;title function_&quot;&gt;getElementById&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;#x27;root&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="React" scheme="https://lz5z.com/categories/React/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="ES6" scheme="https://lz5z.com/tags/ES6/"/>
    
    <category term="React" scheme="https://lz5z.com/tags/React/"/>
    
  </entry>
  
  <entry>
    <title>Fetch API 使用</title>
    <link href="https://lz5z.com/FetchAPI/"/>
    <id>https://lz5z.com/FetchAPI/</id>
    <published>2017-02-12T08:25:06.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h1>背景</h1><p>在上一章学习 <a href="https://lz5z.com/ReactJS%E2%80%94%E7%BB%84%E4%BB%B6/">React 组件</a>的时候，想增加 React 对 Ajax 支持的内容，却发现网上的教程竟然用 jQuery 完成 Ajax 请求，个人觉得为了发送一个简单的请求引入 jQuery 库杀鸡焉用宰牛刀啊。其实 W3C 已经有了更好的替代品，那就是： <a href="https://fetch.spec.whatwg.org/">Fetch API</a>。</p><span id="more"></span><h2 id="Fetch-API"><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API">Fetch API</a></h2><p>Fetch API 的出现与 JavaScript 异步编程模型 Promise 息息相关，在 Fetch API 出现之前，JavaScript 通过 XMLHttpRequest(XHR) 来执行异步请求，XHR 将输入、输出和事件模型混杂在一个对象里，这种设计并不符合职责分离的原则。而且，基于事件的模型与 Promise 以及基于 Generator 的异步编程模型不太搭。</p><p>Fetch API 提供了对 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Headers">Headers</a>，<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Request">Request</a>，<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Response">Response</a> 三个对象的封装，以及一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalFetch">fetch()</a> 函数用来获取网络资源，并且在离线用户体验方面，由于 ServiceWorkers 的介入，Fetch API 也能提供强大的支持。</p><h3 id="兼容性">兼容性</h3><p>fetch() 方法被定义在 window 对象中，你可以直接在控制台中输入 fetch() 查看浏览器是否支持，gitHub 上有基于低版本浏览器的<a href="https://github.com/github/fetch">兼容实现</a>。</p><h3 id="简单示例">简单示例</h3><p>fetch() 方法接受一个参数——资源的路径。无论请求成功与否，它都返回一个 promise 对象，resolve 对应请求的 Response 对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> myImage = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.my-image&#x27;</span>);</span><br><span class="line"><span class="title function_">fetch</span>(<span class="string">&#x27;https://lz5z.com/assets/img/avatar.png&#x27;</span>)</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!response.<span class="property">ok</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Error</span>(response);</span><br><span class="line">    <span class="keyword">return</span> response.<span class="title function_">blob</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function"><span class="params">myBlob</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> objectURL = <span class="variable constant_">URL</span>.<span class="title function_">createObjectURL</span>(myBlob);</span><br><span class="line">    myImage.<span class="property">src</span> = objectURL;</span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  &#125;); </span><br></pre></td></tr></table></figure><p>点击查看<a href="/assets/demo/fetch-demo/index.html">效果</a></p><p>在获取请求的 Response 对象后，通过该对象的 json() 方法可以将结果作为 JSON 对象返回，response.json() 同样会返回一个 Promise 对象，因此可以继续链接一个 then() 方法。相比传统的 XHR 的基于事件类型的编程方式，四不四简单很多哈。</p><h3 id="Request-对象">Request 对象</h3><p>Fetch API 引入了3个接口，它们分别是 Headers，Request 以及 Response 。他们直接对应了相应的 HTTP 概念，但是基于安全考虑，有些区别，例如支持CORS规则以及保证 cookies 不能被第三方获取。</p><p>通过 Request 构造器函数创建一个新的请求对象，这也是建议标准的一部分。 第一个参数是请求的 url，第二个参数是一个选项对象，用于配置请求。然后将 Request 对象传递给 fetch() 方法，用于替代默认的 url 字符串。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//不缓存响应结果， 方法为 GET</span></span><br><span class="line"><span class="keyword">let</span> req = <span class="keyword">new</span> <span class="title class_">Request</span>(url, &#123;<span class="attr">method</span>: <span class="string">&#x27;GET&#x27;</span>, <span class="attr">cache</span>: <span class="string">&#x27;reload&#x27;</span>&#125;);</span><br><span class="line"><span class="title function_">fetch</span>(req).<span class="title function_">then</span>(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">//</span></span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>除此之外，还可以基于 Request 对象创建新对象，比如将一个 GET 请求创建成为一个 POST 请求</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> postReq = <span class="keyword">new</span> <span class="title class_">Request</span>(req, &#123;<span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>&#125;);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(postReq.<span class="property">method</span>); <span class="comment">//&quot;POST&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Headers-对象">Headers 对象</h3><p>每个 Request 对象都有一个 header 属性，在 Fetch API 中它对应了一个 Headers 对象。 我们可以使用 Headers 对象构建 Request 对象。而在 Response 对象中也有一个 header 属性，但是响应头是只读的。</p><p>Headers 接口是一个简单的多映射的名-值表</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> headers = <span class="keyword">new</span> <span class="title class_">Headers</span>();</span><br><span class="line">headers.<span class="title function_">append</span>(<span class="string">&#x27;Accept&#x27;</span>, <span class="string">&#x27;application/json&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> request = <span class="keyword">new</span> <span class="title class_">Request</span>(url, &#123;<span class="attr">headers</span>: headers&#125;);</span><br><span class="line"><span class="title function_">fetch</span>(request).<span class="title function_">then</span>(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(response.<span class="property">headers</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>也可以传一个多维数组或者 json：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">reqHeaders = <span class="keyword">new</span> <span class="title class_">Headers</span>(&#123;</span><br><span class="line">  <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;text/plain&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Content-Length&quot;</span>: content.<span class="property">length</span>.<span class="title function_">toString</span>(),</span><br><span class="line">  <span class="string">&quot;X-Custom-Header&quot;</span>: <span class="string">&quot;ProcessThisImmediately&quot;</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//操作 Headers 中的内容</span></span><br><span class="line">reqHeaders.<span class="title function_">has</span>(<span class="string">&quot;Content-Type&quot;</span>) <span class="comment">//true</span></span><br><span class="line">reqHeaders.<span class="title function_">get</span>(<span class="string">&quot;Content-Type&quot;</span>) <span class="comment">//&quot;text/plain&quot;</span></span><br><span class="line">reqHeaders.<span class="title function_">set</span>(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;text/html&quot;</span>)</span><br><span class="line">reqHeaders.<span class="title function_">delete</span>(<span class="string">&quot;X-Custom-Header&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="Response-对象">Response 对象</h3><p>构建 Respondse 对象有什么用呢？通常 Response 的内容在服务端生成，但是 Fetch API 是浏览器里面的内容啊。</p><p>对了，就是为了离线应用，通过 Service Worker 浏览器能够获取请求头的内容，然后通过在浏览器中构建响应头来替换来自服务器的响应头以达到构建离线应用的目的（这方面内容以后再说）。</p><p>构建方法</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> response = <span class="keyword">new</span> <span class="title class_">Response</span>(</span><br><span class="line">  <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;<span class="attr">photos</span>: &#123;<span class="attr">photo</span>: []&#125;&#125;),</span><br><span class="line">    &#123;<span class="attr">status</span>: <span class="number">200</span>, <span class="attr">headers</span>: headers&#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="steam-支持">steam 支持</h3><p>Request 和 Response 对象中的 body 只能被读取一次，它们有一个属性叫 bodyUsed，读取一次之后设置为 true，就不能再读取了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> res = <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="string">&quot;one time use&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">bodyUsed</span>); <span class="comment">//false</span></span><br><span class="line">res.<span class="title function_">text</span>().<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(v); <span class="comment">//&quot;one time use&quot;</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">bodyUsed</span>); <span class="comment">// true</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这样设计的目的是为了之后兼容基于流的 API，让应用只能消费一次 data，这样就允许了 JavaScript 处理大文件例如视频，并且可以支持实时压缩和编辑。</p><h3 id="clone-支持">clone 支持</h3><p>如何让 body 能经得起多次读取呢？Fetch API 提供了一个 clone() 方法。调用这个方法可以得到一个克隆对象。不过要记得，clone() 必须要在读取之前调用，也就是先 clone() 再读取。</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="keyword">let</span> res = <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="string">&quot;many times use&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">bodyUsed</span>); <span class="comment">//false</span></span><br><span class="line"><span class="keyword">let</span> clone = sheep.<span class="title function_">clone</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">bodyUsed</span>); <span class="comment">//false</span></span><br></pre></td></tr></table></figure><h2 id="总结">总结</h2><p>虽然 Fetch API 提供了更加简洁的接口，Promise 形式的编程体验，但是它也不是完美的，最大的问题就是不能中断一个请求，并且无法检测一个请求的进度，这些在 XHR 中早就有很好的解决方案。也行 Fetch API 需要更多的时间。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;背景&lt;/h1&gt;
&lt;p&gt;在上一章学习 &lt;a href=&quot;https://lz5z.com/ReactJS%E2%80%94%E7%BB%84%E4%BB%B6/&quot;&gt;React 组件&lt;/a&gt;的时候，想增加 React 对 Ajax 支持的内容，却发现网上的教程竟然用 jQuery 完成 Ajax 请求，个人觉得为了发送一个简单的请求引入 jQuery 库杀鸡焉用宰牛刀啊。其实 W3C 已经有了更好的替代品，那就是： &lt;a href=&quot;https://fetch.spec.whatwg.org/&quot;&gt;Fetch API&lt;/a&gt;。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="W3C" scheme="https://lz5z.com/tags/W3C/"/>
    
    <category term="Fetch API" scheme="https://lz5z.com/tags/Fetch-API/"/>
    
  </entry>
  
  <entry>
    <title>ReactJS 学习——组件</title>
    <link href="https://lz5z.com/ReactJS%E2%80%94%E7%BB%84%E4%BB%B6/"/>
    <id>https://lz5z.com/ReactJS%E2%80%94%E7%BB%84%E4%BB%B6/</id>
    <published>2017-02-08T09:02:15.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h1>ReactJS 组件</h1><p>React 提倡<a href="https://facebook.github.io/react/docs/react-component.html">组件化</a>的开发方式，每个组件只关心自己部分的逻辑，使得应用更加容易维护和复用。</p><p>React 还有一个很大的优势是基于组件的状态更新视图，对于测试非常友好。</p><span id="more"></span><h2 id="数据模型">数据模型</h2><h3 id="state">state</h3><p>React 每一个组件的实质是状态机（State Machines），在 React 的每一个组件里，通过更新 this.state，再调用 render() 方法进行渲染，React 会自动把最新的状态渲染到网页上。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">HelloMessage</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handleClick</span> = <span class="variable language_">this</span>.<span class="property">handleClick</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;<span class="attr">enable</span>: <span class="literal">false</span>&#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;<span class="attr">enable</span>: !<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">enable</span>&#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">disabled</span>=<span class="string">&#123;this.state.enable&#125;</span> /&gt;</span> </span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick&#125;</span>&gt;</span>click this<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">HelloMessage</span> /&gt;</span></span>,</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>通过在组件的 constructor 中给 this.state 赋值，来设置 state 的初始值，每当 state 的值发生变化， React 重新渲染页面。</p><p>注意：</p><p>(1) <strong>请不要直接编辑 this.state</strong>，因为这样会导致页面不重新渲染</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Wrong</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">comment</span> = <span class="string">&#x27;Hello&#x27;</span>;</span><br></pre></td></tr></table></figure><p>使用 this.setState() 方法来改变它的值</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Correct</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;<span class="attr">comment</span>: <span class="string">&#x27;Hello&#x27;</span>&#125;);</span><br></pre></td></tr></table></figure><p>(2) <strong>this.state</strong> 的更新可能是异步的(this.props 也是如此)</p><p>React 可能会批量地调用 this.setState() 方法，this.state 和 this.props 也可能会异步地更新，所以你不能依赖它们目前的值去计算它们下一个状态。</p><p>比如下面更新计数器的方法会失败：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Wrong</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">  <span class="attr">counter</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">counter</span> + <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">increment</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>第二种形式的 setState() 方法接收的参数为一个函数而不是一个对象。函数的第一个参数为 previous state，第二个参数为当前的 props</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="comment">// Correct</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(<span class="function">(<span class="params">prevState, props</span>) =&gt;</span> (&#123;</span><br><span class="line">  <span class="attr">counter</span>: prevState.<span class="property">counter</span> + props.<span class="property">increment</span></span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><p>实现一个计数器</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">HelloMessage</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handleClick</span> = <span class="variable language_">this</span>.<span class="property">handleClick</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;<span class="attr">counter</span>: <span class="number">0</span>&#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(<span class="function">(<span class="params">prevState, props</span>) =&gt;</span> (&#123;</span><br><span class="line">      <span class="attr">counter</span>: prevState.<span class="property">counter</span> + <span class="built_in">parseInt</span>(props.<span class="property">increment</span>)</span><br><span class="line">    &#125;));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;this.state.counter&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span> </span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick&#125;</span>&gt;</span>click this<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">HelloMessage</span> <span class="attr">increment</span>=<span class="string">&quot;1&quot;</span> /&gt;</span></span>,</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="props">props</h3><p>React 的数据流是单向的，是自上向下的层级传递的，props 可以对固定的数据进行传递。</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">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello, &#123;this.props.name&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="state-vs-props">state vs props</h3><p>state 和 props 看起来很相似，其实是完全不同的东西。</p><p>一般来说，this.props 表示那些一旦定义，就不再改变的特性，比如购物车里的商品名称、价格，而 this.state 是会随着用户互动而产生变化的特性，比如用户购买商品的个数。</p><h2 id="获取-DOM">获取 DOM</h2><p>在 React 中，我们可以通过 this.refs 方便地获取 DOM：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">HelloMessage</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handleClick</span> = <span class="variable language_">this</span>.<span class="property">handleClick</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="variable language_">this</span>.<span class="property">refs</span>.<span class="property">myInput</span>.<span class="property">value</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&quot;myInput&quot;</span> /&gt;</span> </span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick&#125;</span>&gt;</span>click this<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">HelloMessage</span> /&gt;</span></span>,</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="生命周期">生命周期</h2><p>React 组件的生命周期分为三类：</p><p>(1) 挂载(Mounting): 已插入真实 DOM</p><p>componentWillMount()： 在初次渲染之前执行一次，最早的执行点<br>componentDidMount()： 在初次渲染之后执行</p><p>getInitialState() –&gt; componentWillMount() –&gt; render() –&gt; componentDidMount()</p><p>(2) 更新(Updating): 正在被重新渲染</p><p>componentWillReceiveProps()： 在组件接收到新的 props 的时候调用。在初始化渲染的时候，该方法不会调用。<br>shouldComponentUpdate()： 在接收到新的 props 或者 state，将要渲染之前调用。<br>componentWillUpdate()： 在接收到新的 props 或者 state 之前立刻调用。<br>componentDidUpdate()： 在组件的更新已经同步到 DOM 中之后立刻被调用。</p><p>componentWillReceiveProps() –&gt; shouldComponentUpdate() –&gt; componentWillUpdate –&gt; render() –&gt; componentDidUpdate()</p><p>(3) 移除(Unmounting): 已移出真实 DOM</p><p>componentWillUnmount()： 在组件从 DOM 中移除的时候立刻被调用。</p><p>下面举 React 官网的一个输出时间的例子，在 Clock 渲染之前设置一个定时器，每隔一秒更新一下 this.state.date 的值，并在组件移除的时候清除定时器。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Clock</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;<span class="attr">date</span>: <span class="keyword">new</span> <span class="title class_">Date</span>()&#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//组件初次渲染之后执行</span></span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">timerID</span> = <span class="built_in">setInterval</span>(</span><br><span class="line">      <span class="function">() =&gt;</span> <span class="variable language_">this</span>.<span class="title function_">tick</span>(),</span><br><span class="line">      <span class="number">1000</span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//组件移除的时候执行</span></span><br><span class="line">  <span class="title function_">componentWillUnmount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="built_in">clearInterval</span>(<span class="variable language_">this</span>.<span class="property">timerID</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">tick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">date</span>: <span class="keyword">new</span> <span class="title class_">Date</span>()</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello, world!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h2</span>&gt;</span>It is &#123;this.state.date.toLocaleTimeString()&#125;.<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//渲染</span></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Clock</span> /&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="事件">事件</h2><p>React 内建的跨浏览器的<a href="https://facebook.github.io/react/docs/events.html">事件系统</a>，我们可以在组件里添加属性来绑定事件和相应的<a href="https://facebook.github.io/react/docs/handling-events.html">处理函数</a>。这种事件绑定方法极大的方便了事件操作，不用再像以前先定位到 DOM 节点，再通过 addEventListener 绑定事件，还要用 removeEventListener 解绑。当组件注销时，React 会自动帮我们解绑事件。</p><p>React 处理事件与 DOM 处理事件非常相似，有以下两点不同：</p><ul><li>React 事件用驼峰命名法，而不是全小写</li><li>通过 JSX 语法传递函数作为事件处理器，而不是字符串</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LoggingButton</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  handleClick = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;this is:&#x27;</span>, <span class="variable language_">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        Click me</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></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>另外一个不同的是 React 不支持向事件处理函数 <code>return false</code>，一般 HTML 事件函数中，可以通过 <code>return false</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></pre></td><td class="code"><pre><span class="line">&lt;a href=<span class="string">&quot;#&quot;</span> onclick=<span class="string">&quot;console.log(&#x27;The link was clicked.&#x27;); return false&quot;</span>&gt;</span><br><span class="line">  <span class="title class_">Click</span> me</span><br><span class="line">&lt;/a&gt;</span><br></pre></td></tr></table></figure><p>Vue 阻止浏览器默认行为的方式最简单，用一个装饰符就可以搞定 <code>&lt;form v-on:submit.prevent=&quot;onSubmit&quot;&gt;&lt;/form&gt;</code>。</p><p>而在 React 中，必须调用 preventDefault 方法才能完成以上功能。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ActionLink</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    e.<span class="title function_">preventDefault</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;The link was clicked.&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;#&quot;</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      Click me</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">a</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这里的 <code>e</code> 是 React 封装过后的，因此不用担心游览器差异带来的影响。☺</p><h2 id="条件渲染">条件渲染</h2><p>假设 Greeting 组件根据状态选择渲染 UserGreeting 和 GuestGreeting 中的一个。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">UserGreeting</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Welcome back!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">GuestGreeting</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Please sign up.<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Greeting</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> isLoggedIn = props.<span class="property">isLoggedIn</span>;</span><br><span class="line">  <span class="keyword">if</span> (isLoggedIn) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">UserGreeting</span> /&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">GuestGreeting</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LoginControl</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handleLogoutClick</span> = <span class="variable language_">this</span>.<span class="property">handleLogoutClick</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;<span class="attr">isLoggedIn</span>: <span class="literal">false</span>&#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">handleLogoutClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;<span class="attr">isLoggedIn</span>: !<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">isLoggedIn</span>&#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> isLoggedIn = <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">isLoggedIn</span>;</span><br><span class="line">    <span class="keyword">let</span> button = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (isLoggedIn) &#123;</span><br><span class="line">      button = <span class="language-xml"><span class="tag">&lt;<span class="name">LogoutButton</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleLogoutClick&#125;</span> /&gt;</span></span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      button = <span class="language-xml"><span class="tag">&lt;<span class="name">LoginButton</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleLogoutClick&#125;</span> /&gt;</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Greeting</span> <span class="attr">isLoggedIn</span>=<span class="string">&#123;isLoggedIn&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;button&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></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="行内条件判断">行内条件判断</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Mailbox</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> unreadMessages = props.<span class="property">unreadMessages</span>;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;unreadMessages.length &gt; 0 &amp;&amp;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          You have &#123;unreadMessages.length&#125; unread messages.</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> messages = [<span class="string">&#x27;React&#x27;</span>, <span class="string">&#x27;Re: React&#x27;</span>, <span class="string">&#x27;Re:Re: React&#x27;</span>];</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Mailbox</span> <span class="attr">unreadMessages</span>=<span class="string">&#123;messages&#125;</span> /&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>其它类型的逻辑判断，像三元运算符，<code>if else</code> React 也均支持。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> isLoggedIn = <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">isLoggedIn</span>;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      The user is <span class="tag">&lt;<span class="name">b</span>&gt;</span>&#123;isLoggedIn ? &#x27;currently&#x27; : &#x27;not&#x27;&#125;<span class="tag">&lt;/<span class="name">b</span>&gt;</span> logged in.</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> isLoggedIn = <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">isLoggedIn</span>;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;isLoggedIn ? (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">LogoutButton</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleLogoutClick&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      ) : (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">LoginButton</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleLoginClick&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      )&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="阻止组件渲染">阻止组件渲染</h3><p>通过在组件内部 <code>return null</code> 可以达到阻止组件渲染的</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">WarningBanner</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!props.<span class="property">warn</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;warning&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      Warning!</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最后">最后</h2><p>第一章 <a href="https://lz5z.com/ReactJS%E2%80%94%E5%85%A5%E9%97%A8/">React 入门</a> 和本章 React 组件都是比较基础的内容，后面会学习全新的程序设计模式 Flux 和 Redux 来管理应用的状态，很多函数式编程的思想正好努力学习一下。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;ReactJS 组件&lt;/h1&gt;
&lt;p&gt;React 提倡&lt;a href=&quot;https://facebook.github.io/react/docs/react-component.html&quot;&gt;组件化&lt;/a&gt;的开发方式，每个组件只关心自己部分的逻辑，使得应用更加容易维护和复用。&lt;/p&gt;
&lt;p&gt;React 还有一个很大的优势是基于组件的状态更新视图，对于测试非常友好。&lt;/p&gt;</summary>
    
    
    
    <category term="React" scheme="https://lz5z.com/categories/React/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="ES6" scheme="https://lz5z.com/tags/ES6/"/>
    
    <category term="React" scheme="https://lz5z.com/tags/React/"/>
    
  </entry>
  
  <entry>
    <title>ReactJS 学习——入门</title>
    <link href="https://lz5z.com/ReactJS%E2%80%94%E5%85%A5%E9%97%A8/"/>
    <id>https://lz5z.com/ReactJS%E2%80%94%E5%85%A5%E9%97%A8/</id>
    <published>2017-02-05T06:45:55.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h1>ReactJS 简介</h1><p><a href="https://facebook.github.io/react/">React</a> 首次被提出是在2014年的 F8 大会上，当期的主题为 “Rethinking Web App Development at Facebook”，这也是 React 名字的由来。</p><p>React 以组件化的开发方式，专注于 MVC 架构中的 View，即视图， 这使得React很容易和开发者已有的开发栈进行融合。React 推荐将 UI 上每一个功能相对独立的模块定义成组件，然后将小的组件通过组合或者嵌套的方式构成大的组件，最终完成整体 UI 的构建。</p><span id="more"></span><h1>ReactJS 原理</h1><p>Web 开发的最终目的是把数据反映到 UI 上，这时就需要对 DOM 进行操作，复杂或者频繁的 DOM 操作通常是性能瓶颈产生的原因。React 为此引入了虚拟 DOM（Virtual DOM） 的机制：开发者操作虚拟 DOM，React 在必要的时候将它们渲染到真正的 DOM 上。</p><h2 id="Virtual-DOM">Virtual DOM</h2><p>基于 React 进行开发时所有的 DOM 构造都是通过虚拟 DOM 进行，每当数据变化时，React 都会重新构建整个 DOM 树，然后 React 将当前整个 DOM 树和上一次的 DOM 树进行对比，得到 DOM 结构的区别，然后仅仅将需要变化的部分更新到实际的浏览器。</p><p>同时 React 能够批处理虚拟 DOM 的刷新，在一个事件循环（Event Loop）内的两次数据变化会被合并，例如你连续的先将节点内容从 A 变成 B，然后又从 B 变成 A，React 会认为 UI 不发生任何变化。尽管每一次都需要构造完整的虚拟 DOM 树，但是因为虚拟 DOM 是内存数据，性能是极高的，而对实际 DOM 进行操作的仅仅是 Diff 部分，因而能达到提高性能的目的。</p><h2 id="Hello-World">Hello World</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Hello World<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://unpkg.com/react@latest/dist/react.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://unpkg.com/react-dom@latest/dist/react-dom.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://unpkg.com/babel-standalone@6.15.0/babel.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;root&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/babel&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span></span><br><span class="line"><span class="language-javascript">        <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello, world!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>,</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span></span><br><span class="line"><span class="language-javascript">      );</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上面的 Hello World 的例子中，引入了三个库文件，react.js，react-dom.js 和 babel.js，它们必须首先加载。在之前的版本中，需要加载 “JSXTransformer.js”，后来 React 官方不再维护这个库，由 babel 对 JSX 语法进行编译。<br>ReactDOM.render 是 React 的最基本方法，用于将模板转为 HTML 语言，并插入指定的 DOM 节点。</p><h2 id="create-react-app">create-react-app</h2><p>一般我们启动一个 React 项目会使用 React 脚手架工具 <a href="https://github.com/facebookincubator/create-react-app">create-react-app</a>,它会帮助你创建一个基于 webpack、Babel 和 ESLint 的单页面项目。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn global add create-react-app</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">create-react-app react-demo</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> react-demo</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn start</span></span><br></pre></td></tr></table></figure><p>项目启动后会有一个 “Welcome to React” 的页面自动打开。</p><p>打开 package.json 文件，发现并没有找到 webpack、Babel 等 package 相关的依赖，所有的工作都是 “react-scripts” 帮助我们做的，这样极大地降低了初学者入门学习 React 的成本。</p><h2 id="JSX"><a href="https://facebook.github.io/react/docs/introducing-jsx.html">JSX</a></h2><p>HTML 语言直接写在 JavaScript 语言之中，不加任何引号，这就是 JSX 的语法，它允许 HTML 与 JavaScript 的混写。</p><p>例如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> names = [<span class="string">&#x27;Leo&#x27;</span>, <span class="string">&#x27;Jack&#x27;</span>, <span class="string">&#x27;John&#x27;</span>];</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  &#123;</span></span><br><span class="line"><span class="language-xml">    names.map((name)=&gt;&#123;</span></span><br><span class="line"><span class="language-xml">      return <span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;name&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#125;)</span></span><br><span class="line"><span class="language-xml">  &#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>上面代码体现了 JSX 的基本语法规则：遇到 HTML 标签（以 &lt; 开头），就用 HTML 规则解析；遇到代码块（以 { 开头），就用 JavaScript 规则解析。</p><p>JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组，则会展开这个数组的所有成员，代码如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>, </span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>world<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">];</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  &#123;arr&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>ReactDOM.render 方法也可以写在函数中，例如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> t0 = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">()=&gt;</span>&#123;</span><br><span class="line">  <span class="keyword">let</span> t = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>(),</span><br><span class="line">  delta = t - t0;</span><br><span class="line">  <span class="comment">//在虚拟DOM上创建元素</span></span><br><span class="line">  <span class="keyword">let</span> el = <span class="title class_">React</span>.<span class="title function_">createElement</span>(<span class="string">&quot;p&quot;</span>,<span class="literal">null</span>,delta);</span><br><span class="line">  <span class="comment">//渲染到真实DOM</span></span><br><span class="line">  <span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(el,<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>));</span><br><span class="line">&#125;,<span class="number">16</span>);</span><br></pre></td></tr></table></figure><h2 id="React-组件">React 组件</h2><p>定义 React 组件有三种方法，第一种是 JavaScript 函数，第二种是用 ES6 classes 的方式，一个是用 React.createClass(已经过时)</p><h3 id="JavaScript-函数">JavaScript 函数</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">HelloMessage</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello, &#123;props.name&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">HelloMessage</span> <span class="attr">name</span>=<span class="string">&quot;Leo&quot;</span>/&gt;</span></span>,</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>注意这里调用属性的时候没有 this。</p><h3 id="React-Component">React.Component</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">HelloMessage</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello, &#123;this.props.name&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">HelloMessage</span> <span class="attr">name</span>=<span class="string">&quot;Leo&quot;</span>/&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="React-createClass">React.createClass</h3><p>React.createClass(meta) 方法用于生成组件类，参数 meta 是一个实现预定义接口的 JavaScript 对象，用来对 React 组件原型进行扩展。<br>在 meta 中，至少需要实现一个 render() 方法，而这个方法， 必须而且只能返回一个有效的 React 元素。这意味着，如果你的组件是由多个元素构成的，那么你必须在外边包一个顶层元素，然后返回这个顶层元素。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">HelloMessage</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>(&#123;</span><br><span class="line">  <span class="attr">render</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello &#123;this.props.name&#125;!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">HelloMessage</span> <span class="attr">name</span>=<span class="string">&quot;Leo&quot;</span>/&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><ol><li>组件名必须以大写字母开头</li><li>组件类只能包含一个顶层标签</li><li>获取属性的值用的是 this.props.属性名</li><li>为元素添加 css 的 class 时，要用 className，for 属性需要写成 htmlFor， 因为 class 和 for 是 ES6 关键字</li></ol><h2 id="内联-css">内联 css</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">HelloMessage</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>(&#123;</span><br><span class="line">  <span class="attr">render</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;&#123;color:</span>&quot;<span class="attr">red</span>&quot;,<span class="attr">fontSize:</span> &quot;<span class="attr">44px</span>&quot;&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          Hello &#123;this.props.name&#125;!<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>内联 css 的写法与用 JavaScript 直接操作样式相同：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>).<span class="property">style</span>.<span class="property">paddingLeft</span>=<span class="string">&#x27;104px&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="组件组合">组件组合</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//组合组件</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WebSite</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Name</span> <span class="attr">name</span>=<span class="string">&#123;this.props.name&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Link</span> <span class="attr">site</span>=<span class="string">&#123;this.props.site&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//Name组件</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Name</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;this.props.name&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//L****ink组件</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Link</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&#123;this.props.site&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;this.props.site&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">a</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">WebSite</span> <span class="attr">name</span>=<span class="string">&quot;Leo&quot;</span> <span class="attr">site</span>=<span class="string">&quot;https://lz5z.com&quot;</span> /&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h1>最后</h1><p>ReactJS 入门暂时就到这里，后面会有更加详细的内容。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;ReactJS 简介&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://facebook.github.io/react/&quot;&gt;React&lt;/a&gt; 首次被提出是在2014年的 F8 大会上，当期的主题为 “Rethinking Web App Development at Facebook”，这也是 React 名字的由来。&lt;/p&gt;
&lt;p&gt;React 以组件化的开发方式，专注于 MVC 架构中的 View，即视图， 这使得React很容易和开发者已有的开发栈进行融合。React 推荐将 UI 上每一个功能相对独立的模块定义成组件，然后将小的组件通过组合或者嵌套的方式构成大的组件，最终完成整体 UI 的构建。&lt;/p&gt;</summary>
    
    
    
    <category term="React" scheme="https://lz5z.com/categories/React/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="ES6" scheme="https://lz5z.com/tags/ES6/"/>
    
    <category term="React" scheme="https://lz5z.com/tags/React/"/>
    
  </entry>
  
  <entry>
    <title>Yarn 管理 JavaScript 模块</title>
    <link href="https://lz5z.com/Yarn%E7%AE%A1%E7%90%86JavaScript%E6%A8%A1%E5%9D%97/"/>
    <id>https://lz5z.com/Yarn%E7%AE%A1%E7%90%86JavaScript%E6%A8%A1%E5%9D%97/</id>
    <published>2017-02-01T11:42:03.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<h1><a href="https://yarnpkg.com/">Yarn</a> 简介</h1><p>Yarn 是 Facebook 开发的一款新的 JavaScript 包管理工具， 作为 NPM 的替代产品，主要是为了解决下面两个问题：</p><ul><li>安装的时候无法保证速度/一致性</li><li>安全问题，因为 NPM 安装时允许运行代码</li></ul><span id="more"></span><h1>Yarn vs NPM</h1><h2 id="速度快">速度快</h2><p>相比于 NPM，Yarn 的速度更快，Yarn 会把使用过的模块在本地缓存一份，如果下次还要用到相同版本的模块，那么将会直接使用本地的而不是访问网络重新获取一份。而 NPM 使用的时候，如果不全局安装那么每个项目都要重新下载一次包，浪费时间和资源。</p><h2 id="安全性">安全性</h2><p>Yarn 在安装模块之前会验证文件完整性。</p><h2 id="并行安装">并行安装</h2><p>每当 NPM 或 Yarn 需要安装一个包时，它会进行一系列的任务。在 NPM 中这些任务是按包的顺序一个个执行，这意味着必须等待上一个包被完整安装才会进入下一个；Yarn 则并行的执行这些任务，提高了性能。</p><h2 id="输出清晰">输出清晰</h2><p>NPM 安装包的时候输出惨不忍睹，而 Yarn 的输出就清晰多了。</p><img src="/assets/img/yarn.png" alt="yarn"><h1>使用</h1><h2 id="常用命令对照表">常用命令对照表</h2><table><thead><tr><th style="text-align:left">作用</th><th style="text-align:left">NPM 命令</th><th style="text-align:left">Yarn 命令</th></tr></thead><tbody><tr><td style="text-align:left">初始化</td><td style="text-align:left">npm init</td><td style="text-align:left">yarn init</td></tr><tr><td style="text-align:left">安装 package.json 中的包</td><td style="text-align:left">npm install</td><td style="text-align:left">yarn</td></tr><tr><td style="text-align:left">安装某个包</td><td style="text-align:left">npm install xxx --save</td><td style="text-align:left">yarn add xxx</td></tr><tr><td style="text-align:left">删除某个包</td><td style="text-align:left">npm uninstall xxx --save</td><td style="text-align:left">yarn remove xxx</td></tr><tr><td style="text-align:left">开发模式下安装某个包</td><td style="text-align:left">npm install xxx --save-dev</td><td style="text-align:left">yarn add xxx -dev</td></tr><tr><td style="text-align:left">更新</td><td style="text-align:left">npm update --save</td><td style="text-align:left">yarn upgrade</td></tr><tr><td style="text-align:left">全局安装</td><td style="text-align:left">npm install xxx –global</td><td style="text-align:left">yarn global add xxx</td></tr><tr><td style="text-align:left">清除缓存</td><td style="text-align:left">npm cache clean</td><td style="text-align:left">yarn cache clean</td></tr><tr><td style="text-align:left">查看模块信息</td><td style="text-align:left">npm info xxx</td><td style="text-align:left">yarn info xxx</td></tr><tr><td style="text-align:left">运行script</td><td style="text-align:left">npm run</td><td style="text-align:left">yarn run</td></tr><tr><td style="text-align:left">测试</td><td style="text-align:left">npm test</td><td style="text-align:left">yarn test</td></tr></tbody></table><h2 id="yarn-lock-文件"><a href="https://yarnpkg.com/docs/configuration/#toc-use-yarn-lock-to-pin-your-dependencies">yarn.lock</a> 文件</h2><p>在使用 NPM 管理 JavaScript 模块的时候，可以用比较宽松的方式定义某个模块的版本信息，如</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><br><span class="line">~<span class="number">1.1</span><span class="number">.0</span>: &gt;=<span class="number">1.1</span><span class="number">.0</span> &amp;&amp; &lt; <span class="number">1.2</span><span class="number">.0</span></span><br><span class="line">^<span class="number">1.1</span><span class="number">.0</span>: &gt;=<span class="number">1.1</span><span class="number">.0</span> &amp;&amp; &lt; <span class="number">2.0</span><span class="number">.0</span></span><br><span class="line">&gt;= <span class="number">1.0</span><span class="number">.0</span>: &gt;= <span class="number">1.0</span><span class="number">.0</span></span><br><span class="line"><span class="number">3.</span><span class="attr">x</span>: 任意 <span class="number">3</span> 版本</span><br></pre></td></tr></table></figure><p>理想状态下使用<a href="http://deadhorse.me/nodejs/2014/04/27/semver-in-nodejs.html">语义化</a>版本发布补丁不会包含大的变化，但不幸的很多时候并非如此。NPM 的这种策略可能导致两台拥有相同 package.json 文件的电脑安装了不同版本的包，这可能导致一些错误。很多模块的安装错误和环境问题都是由于这个原因导致。</p><p>为了避免包版本的错误匹配，一个确定的安装版本被固定在一个锁文件中。每次模块被添加时，Yarn 就会创建（或更新） yarn.lock 文件，这样你就可以保证其它电脑也安装相同版本的包，同时包含了 package.json 中定义的一系列允许的版本。</p><p>在 npm 中同样可以使用 <a href="https://docs.npmjs.com/cli/shrinkwrap">npm shrinkwrap</a> 命令来生成一个锁文件，这样在使用 npm install 时会在读取 package.json 前先读取这个文件，就像 Yarn 会先读取 yarn.lock 一样。这里的区别是 Yarn 总会自动更新 yarn.lock，而 npm 需要你重新操作。</p><h2 id="yarn-install"><a href="https://yarnpkg.com/en/docs/cli/install">yarn install</a></h2><p>npm install 命令会根据 package.json 安装依赖以及允许你添加新的模块； yarn install 仅会按照 yarn.lock 或 package.json 里面的依赖顺序来安装模块。</p><h2 id="yarn-add-–dev"><a href="https://yarnpkg.com/en/docs/cli/add">yarn add [–dev]</a></h2><p>与 npm install 类似，yarn add 允许你添加与安装模块，添加依赖的同时也会将依赖写入 package.json，类似 npm 的 --save 参数；Yarn 的 --dev 参数则是添加开发依赖，类似 npm 的 --save-dev 参数。</p><h2 id="yarn-global"><a href="https://yarnpkg.com/en/docs/cli/global">yarn global</a></h2><p>不像 npm 添加 -g 或 --global 可以进行全局安装，Yarn 使用的是 global 前缀（yarn global add xxx）。global 前缀只能用于 yarn add, yarn bin, yarn ls 和 yarn remove。</p><h2 id="yarn-why"><a href="https://yarnpkg.com/en/docs/cli/why">yarn why</a></h2><p>该命令会查找依赖关系并找出为什么会将某些包安装在你的项目中。也许你知道为什么添加，也许它只是你安装包中的一个依赖，yarn why 可以帮你找出。<br><img src="/assets/img/yarn_why.png" alt="yarn_why"></p><h1>总结</h1><p>相比 NPM，Yarn 可以方便生成锁文件，安装模块时非常迅速并且会将依赖自动添加进 package.json，模块可以并行安装。不过个人认为，Yarn 的优势不是绝对的，毕竟 NPM 久经考验，或许不久的将来，NPM 也会拥有这些特性。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;&lt;a href=&quot;https://yarnpkg.com/&quot;&gt;Yarn&lt;/a&gt; 简介&lt;/h1&gt;
&lt;p&gt;Yarn 是 Facebook 开发的一款新的 JavaScript 包管理工具， 作为 NPM 的替代产品，主要是为了解决下面两个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装的时候无法保证速度/一致性&lt;/li&gt;
&lt;li&gt;安全问题，因为 NPM 安装时允许运行代码&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="Node" scheme="https://lz5z.com/tags/Node/"/>
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Yarn" scheme="https://lz5z.com/tags/Yarn/"/>
    
  </entry>
  
  <entry>
    <title>Gulp 快速入门</title>
    <link href="https://lz5z.com/Gulp%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/"/>
    <id>https://lz5z.com/Gulp%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/</id>
    <published>2017-01-18T08:09:35.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<img src="/assets/img/自动化.jpg" alt="自动化">[图片摘自「程序员的那些事」]<h2 id="什么是-gulp">什么是 gulp</h2><p>简单的讲，gulp 是一个构建工具，一个基于流的构建工具，一个 nodejs 写的构建工具，使用 gulp 的目的就是为了自动化构建，提高程序员工作效率😂。</p><span id="more"></span><h2 id="入门指南">入门指南</h2><ol><li>全局安装 gulp：</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install --global gulp</span></span><br></pre></td></tr></table></figure><ol start="2"><li>作为项目的开发依赖（devDependencies）安装：</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install --save-dev gulp</span></span><br></pre></td></tr></table></figure><ol start="3"><li>在项目根目录下创建一个名为 gulpfile.js 的文件：</li></ol><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">var</span> gulp = <span class="built_in">require</span>(<span class="string">&#x27;gulp&#x27;</span>);</span><br><span class="line"><span class="comment">// 默认task</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;default&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello World&#x27;</span>)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="4"><li>运行 gulp：</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">gulp</span></span><br></pre></td></tr></table></figure><p>默认的名为 default 的任务（task）将会被运行。</p><p>想要单独执行特定的任务（task），请输入</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">gulp &lt;task&gt; &lt;othertask&gt;。</span></span><br></pre></td></tr></table></figure><h2 id="tasks-依赖">tasks 依赖</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> gulp = <span class="built_in">require</span>(<span class="string">&#x27;gulp&#x27;</span>);</span><br><span class="line"><span class="comment">// task1</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;task1&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;task1&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// task2</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;task2&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;task2&#x27;</span>)</span><br><span class="line">&#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 在执行 default 之前先执行 task1 和 task2</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;default&#x27;</span>, [<span class="string">&#x27;task1&#x27;</span>, <span class="string">&#x27;task2&#x27;</span>], <span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello World&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>输出顺序为：</p><blockquote><p>task1<br>Hello World<br>task2</p></blockquote><h2 id="流式处理">流式处理</h2><p>(1) 在项目根目录下创建 src 文件目录，里面创建 index.js<br>(2) 在项目根目录下创建 dist 文件目录<br>(3) 安装 gulp-uglify</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install gulp-uglify --save-dev</span></span><br></pre></td></tr></table></figure><p>(4) 使用 gulp 压缩 index.js 并将结果输出</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> gulp = <span class="built_in">require</span>(<span class="string">&#x27;gulp&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> uglify = <span class="built_in">require</span>(<span class="string">&#x27;gulp-uglify&#x27;</span>);</span><br><span class="line"><span class="comment">// 压缩js</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;default&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">gulp.<span class="title function_">src</span>(<span class="string">&#x27;src/*.js&#x27;</span>)</span><br><span class="line">.<span class="title function_">pipe</span>(<span class="title function_">uglify</span>())</span><br><span class="line">.<span class="title function_">pipe</span>(gulp.<span class="title function_">dest</span>(<span class="string">&#x27;dist&#x27;</span>))</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>(5) 运行 “gulp” 命令后发现在 dist 目录下生产了压缩后的 index.js</p><p>(6) 解释</p><p>gulp.src 是输入； gulp.dest 是输出<br>pipe 是管道的意思，也是 stream 里核心概念，pipe 将上一个的输出作为下一个的输入。src 里所有 js，经过处理1，处理2，变成输出结果，中间的处理 pipe 可以1步，也可以是n步。第一步处理的结果是第二步的输入，以此类推，就像生产线一样，每一步都是一个 task 是不是很好理解呢？</p><p>每个独立操作单元都是一个 task，使用 pipe 来组装 tasks，于是 gulp 就变成了基于 task 的组装工具。</p><h2 id="gulp-src">gulp.src()</h2><p>在上面的例子中，gulp.src() 函数用字符串匹配一个文件或者文件的编号（被称为“glob”）,然后创建一个对象流来代表这些文件，接着传递给 uglify() 函数，它接受文件对象之后返回有新压缩源文件的文件对象，最后那些输出的文件被输入 gulp.dest()函数，并保存下来。</p><p>gulp.src() 可以接收以下类型的参数：</p><blockquote><p>js/app.js 精确匹配文件<br>js/<em>.js 仅匹配 js 目录下的所有后缀为 .js 的文件<br>js/</em>/.js 匹配 js 目录及其子目录下所有后缀为 .js 的文件<br>!js/app.js 从匹配结果中排除 js/app.js，这种方法在你想要匹配除了特殊文件之外的所有文件时非常好用<br>*.+(js|css) 匹配根目录下所有后缀为 .js 或者 .css 的文件</p></blockquote><p>假如 js 目录下包含了压缩和未压缩的 JavaScript 文件，现在我们想要创建一个任务来压缩还没有被压缩的文件，我们需要先匹配目录下所有的 JavaScript 文件，然后排除后缀为 .min.js 的文件:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gulp.<span class="title function_">src</span>([<span class="string">&#x27;js/**/*.js&#x27;</span>, <span class="string">&#x27;!js/**/*.min.js&#x27;</span>])</span><br></pre></td></tr></table></figure><h2 id="babel">babel</h2><p>babel 用于转化 JavaScript 代码，比如将 ES6 的语法转化成 ES5，或者将 JSX 语法转化为 JavaScript 语法。</p><p>假如上文中提到的 index.js 里面的内容如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> express, &#123; <span class="title class_">Router</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;express&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> bodyParser <span class="keyword">from</span> <span class="string">&#x27;body-parser&#x27;</span>;</span><br><span class="line"><span class="comment">// 定义app和router</span></span><br><span class="line"><span class="keyword">let</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">let</span> router = <span class="title class_">Router</span>();</span><br><span class="line"><span class="comment">//中间件</span></span><br><span class="line">app.<span class="title function_">use</span>(bodyParser.<span class="title function_">json</span>());</span><br><span class="line">app.<span class="title function_">use</span>(bodyParser.<span class="title function_">urlencoded</span>(&#123; <span class="attr">extended</span>: <span class="literal">true</span> &#125;));</span><br><span class="line"><span class="comment">//路由</span></span><br><span class="line">router.<span class="title function_">get</span>(<span class="string">&#x27;/&#x27;</span>, <span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">  res.<span class="title function_">end</span>(<span class="string">&#x27;Hello World!&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">&#x27;/&#x27;</span>, router);</span><br><span class="line"><span class="comment">//启动app</span></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;server listening at port 3000...&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>使用 babel 转化为 ES5 语法：</p><p>(1) 安装 babel-core babel-preset-es2015</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install --save-dev babel-core babel-preset-es2015</span></span><br></pre></td></tr></table></figure><p>(2) 创建 <strong>.babelrc</strong> 文件， 配置如下</p><blockquote><p>{<br>“presets”: [“es2015”]<br>}</p></blockquote><p>(3) 手动使用 babel 转译：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">babel src -d lib</span></span><br></pre></td></tr></table></figure><p>(4) 安装 gulp-babel</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"> npm install --save-dev gulp-babel</span></span><br></pre></td></tr></table></figure><p>(5) 编写 gulpfile</p><p>在根目录新建一个 gulpfile.babel.js 文件。<br>gulp 原生并不支持 ES6 语法，但是我们可以告诉 gulp 使用 babel 将 gulpfile 转换为 ES5，方法就是将 gulpfile 命名为 <strong>gulpfile.babel.js</strong>。</p><p>(6) 使用 ES6 编写 <strong>gulpfile.babel.js</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> gulp <span class="keyword">from</span> <span class="string">&#x27;gulp&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> babel <span class="keyword">from</span> <span class="string">&#x27;gulp-babel&#x27;</span>;</span><br><span class="line"><span class="comment">// 语法转化+压缩</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;default&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">gulp.<span class="title function_">src</span>(<span class="string">&#x27;src/*.js&#x27;</span>)</span><br><span class="line">.<span class="title function_">pipe</span>(<span class="title function_">babel</span>())</span><br><span class="line">.<span class="title function_">pipe</span>(gulp.<span class="title function_">dest</span>(<span class="string">&#x27;lib&#x27;</span>))</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>打开 lib 目录下的 index.js 文件，就可以查看 babel 编译后的 ES5 语法的文件了。</p><h2 id="gulp-watch">gulp-watch</h2><p>开始工作以后，每次改动 index.js 都要手动 gulp 一下实在太麻烦了，使用 gulp-watch 可以监听文件变化，当文件被修改之后，自动将文件转换。</p><p>(1) 安装 gulp-watch</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install gulp-watch --save-dev</span></span><br></pre></td></tr></table></figure><p>(2) 新增 task</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;watch&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">gulp.<span class="title function_">src</span>(<span class="string">&#x27;src/*.js&#x27;</span>)</span><br><span class="line">.<span class="title function_">pipe</span>(<span class="title function_">watch</span>(<span class="string">&#x27;src/*.js&#x27;</span>), &#123;</span><br><span class="line"><span class="attr">verbose</span>: <span class="literal">true</span></span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">pipe</span>(<span class="title function_">babel</span>())</span><br><span class="line">.<span class="title function_">pipe</span>(gulp.<span class="title function_">dest</span>(<span class="string">&#x27;lib&#x27;</span>))</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>(3) 启动 watch task</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">gulp watch</span></span><br></pre></td></tr></table></figure><p>修改 index.js 后 lib/index.js 也会随之改变。(≧∀≦)ゞ</p><h2 id="查看全部-tasks">查看全部 tasks</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">gulp -T</span></span><br><span class="line">[16:06:54] Requiring external module babel-register</span><br><span class="line">[16:06:54] ├── default</span><br><span class="line">[16:06:54] └── watch</span><br></pre></td></tr></table></figure><h2 id="gulp-顺序执行">gulp 顺序执行</h2><p>默认的，task 将以最大的并发数执行，也就是说，gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列，并以特定的顺序执行，需要做两件事：</p><ol><li>给出一个提示，来告知 task 什么时候执行完毕，</li><li>并且再给出一个提示，来告知一个 task 依赖另一个 task 的完成。</li></ol><p>假如我想要 task1 执行完成后再执行 task2， 可以用以下三种方式：</p><h3 id="直接返回一个流">直接返回一个流</h3><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">gulp.<span class="title function_">task</span>(<span class="string">&#x27;task1&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> gulp.<span class="title function_">watch</span>(<span class="string">&#x27;src/*.js&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//只要加一个return就好了</span></span><br></pre></td></tr></table></figure><h3 id="返回一个promise">返回一个promise</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;task1&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> Q = <span class="built_in">require</span>(<span class="string">&#x27;q&#x27;</span>);</span><br><span class="line">  <span class="keyword">var</span> deferred = Q.<span class="title function_">defer</span>();</span><br><span class="line">  <span class="comment">// do async stuff</span></span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    deferred.<span class="title function_">resolve</span>();</span><br><span class="line">  &#125;, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> deferred.<span class="property">promise</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="使用回调callback">使用回调callback</h3><p>task 的执行函数其实都有个回调，我们只需要在异步队列完成的时候调用它就好了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;task1&#x27;</span>, <span class="keyword">function</span> (<span class="params">cb</span>) &#123;</span><br><span class="line">  <span class="comment">// do async stuff</span></span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">cb</span>()</span><br><span class="line">  &#125;, <span class="number">1</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>所以只要依赖的任务是上面三种情况之一，就能保证当前任务在依赖任务执行完成后再执行。这边需要注意的是依赖的任务相互之间还是并行的。需要他们按顺序的话。记得给每个依赖的任务也配置好依赖关系。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> gulp = <span class="built_in">require</span>(<span class="string">&#x27;gulp&#x27;</span>);</span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;one&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;one&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// two 依赖 one</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;two&#x27;</span>, [<span class="string">&#x27;one&#x27;</span>], <span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;two&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// default 依赖 one，two</span></span><br><span class="line">gulp.<span class="title function_">task</span>(<span class="string">&#x27;default&#x27;</span>, [<span class="string">&#x27;one&#x27;</span>, <span class="string">&#x27;two&#x27;</span>], <span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;default&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/assets/img/自动化.jpg&quot; alt=&quot;自动化&quot;&gt;
[图片摘自「程序员的那些事」]
&lt;h2 id=&quot;什么是-gulp&quot;&gt;什么是 gulp&lt;/h2&gt;
&lt;p&gt;简单的讲，gulp 是一个构建工具，一个基于流的构建工具，一个 nodejs 写的构建工具，使用 gulp 的目的就是为了自动化构建，提高程序员工作效率😂。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Gulp" scheme="https://lz5z.com/tags/Gulp/"/>
    
    <category term="Babel" scheme="https://lz5z.com/tags/Babel/"/>
    
    <category term="streaming" scheme="https://lz5z.com/tags/streaming/"/>
    
  </entry>
  
  <entry>
    <title>机器学习常用算法——随机森林</title>
    <link href="https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97/"/>
    <id>https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97/</id>
    <published>2017-01-10T13:27:24.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>随机森林</h1><p>随机森林（Random Forest，简称RF），通过集成学习的思想将多棵决策树集成的一种算法，它的基本单元是<a href="https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E5%86%B3%E7%AD%96%E6%A0%91/">决策树</a>。从直观角度来解释，每棵决策树都是一个分类器（假设现在针对的是分类问题），那么对于一个输入样本，N棵树会有N个分类结果。而随机森林集成了所有的分类投票结果，将投票次数最多的类别指定为最终的输出。</p><img src="/assets/img/randforest.png" alt="随机森林"><span id="more"></span><h2 id="随机森林构建">随机森林构建</h2><h3 id="随机采样">随机采样</h3><p>首先是两个随机采样的过程，random forest 对输入的数据要进行行、列的采样。</p><p>对于行采样，采用有放回的方式，也就是在采样得到的样本集合中，可能有重复的样本。假设输入样本为 N 个，那么采样的样本也为 N 个，这选择好了的 N 个样本用来训练一个决策树，作为决策树根节点处的样本，同时使得在训练的时候，每一棵树的输入样本都不是全部的样本，使得相对不容易出现 over-fitting。</p><p>对于列采样，从 M 个 feature 中，选择 m 个 (m &lt;&lt; M)，即：当每个样本有M个属性时，在决策树的每个节点需要分裂时，随机从这 M 个属性中选取出 m 个属性，满足条件 m &lt;&lt; M。</p><h3 id="完全分裂">完全分裂</h3><p>对采样之后的数据使用完全分裂的方式建立出决策树，这样决策树的某一个叶子节点要么是无法继续分裂的，要么里面的所有样本的都是指向的同一个分类。分裂的办法是：采用上面说的列采样的过程从这m个属性中采用某种策略（比如说信息增益）来选择1个属性作为该节点的分裂属性。</p><p>决策树形成过程中每个节点都要按完全分裂的方式来分裂，一直到不能够再分裂为止（如果下一次该节点选出来的那一个属性是刚刚其父节点分裂时用过的属性，则该节点已经达到了叶子节点，无须继续分裂了）。</p><h2 id="随机森林的优点">随机森林的优点</h2><ol><li>比较适合做多分类问题，训练和预测速度快，在数据集上表现良好；</li><li>对训练数据的容错能力强，是一种有效地估计缺失数据的一种方法，当数据集中有大比例的数据缺失时仍然可以保持精度不变和能够有效地处理大的数据集；</li><li>能够处理很高维度的数据，并且不用做特征选择，即：可以处理没有删减的成千上万的变量；</li><li>能够在分类的过程中可以生成一个泛化误差的内部无偏估计；</li><li>能够在训练过程中检测到特征之间的相互影响以及特征的重要性程度；</li><li>不会出现过度拟合；</li><li>实现简单并且容易实现并行化。</li></ol><h1>例子</h1><p>假设有一组相亲网站提供的数据，抽取特征后发现是否相亲有四个因素组成： 年龄，是否有房，收入，是否公务员</p><blockquote><p>age, house, income, governor, go_date<br>30, 1, 80, 1, 1<br>28, 0, 30, 0, 0<br>29, 0, 80, 1, 1<br>32, 1, 40, 1, 1<br>32, 0, 100, 1, 1<br>40, 1, 30, 1, 0<br>28, 1, 40, 1, 1<br>57, 0, 80, 1, 0<br>45, 0, 78, 0, 0<br>34, 0, 70, 1, 0<br>…</p></blockquote><p>那么假如有一个新的会员注册后，填写了信息如下，</p><blockquote><p>年龄： 33<br>是否有房： 无<br>收入： 80<br>是否公务员： 是</p></blockquote><p>那么请问这位会员是否能得到相亲的机会？</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> numpy <span class="keyword">import</span> genfromtxt</span><br><span class="line"><span class="keyword">from</span> sklearn.ensemble <span class="keyword">import</span> RandomForestClassifier</span><br><span class="line"><span class="comment"># 加载数据</span></span><br><span class="line">dataset = genfromtxt(<span class="string">&#x27;data.csv&#x27;</span>, delimiter=<span class="string">&quot;,&quot;</span>)</span><br><span class="line">x = dataset[<span class="number">1</span>:, <span class="number">0</span>:<span class="number">4</span>]</span><br><span class="line">y = dataset[<span class="number">1</span>:, <span class="number">4</span>]</span><br><span class="line">clf = RandomForestClassifier(n_jobs=<span class="number">2</span>, oob_score=<span class="literal">True</span>)</span><br><span class="line">clf = clf.fit(x, y)</span><br><span class="line"><span class="comment"># 预测</span></span><br><span class="line"><span class="built_in">print</span>(clf.predict_proba([[<span class="number">33</span>, <span class="number">0</span>, <span class="number">80</span>, <span class="number">1</span>]]))</span><br></pre></td></tr></table></figure><p><a href="https://github.com/Leo555/scikit-learn_demo/tree/master/06Random_Forest">代码地址</a></p><h1>参考文献</h1><ul><li><a href="http://www.cnblogs.com/maybe2030/p/4585705.html">[Machine Learning &amp; Algorithm] 随机森林（Random Forest）</a></li><li><a href="http://blog.csdn.net/u011301133/article/details/52562874">sklearn中随机森林的参数</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h1&gt;随机森林&lt;/h1&gt;
&lt;p&gt;随机森林（Random Forest，简称RF），通过集成学习的思想将多棵决策树集成的一种算法，它的基本单元是&lt;a href=&quot;https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E5%86%B3%E7%AD%96%E6%A0%91/&quot;&gt;决策树&lt;/a&gt;。从直观角度来解释，每棵决策树都是一个分类器（假设现在针对的是分类问题），那么对于一个输入样本，N棵树会有N个分类结果。而随机森林集成了所有的分类投票结果，将投票次数最多的类别指定为最终的输出。&lt;/p&gt;
&lt;img src=&quot;/assets/img/randforest.png&quot; alt=&quot;随机森林&quot;&gt;</summary>
    
    
    
    <category term="Machine Learning" scheme="https://lz5z.com/categories/Machine-Learning/"/>
    
    
    <category term="Python" scheme="https://lz5z.com/tags/Python/"/>
    
    <category term="Machine Learning" scheme="https://lz5z.com/tags/Machine-Learning/"/>
    
    <category term="scikit-learn" scheme="https://lz5z.com/tags/scikit-learn/"/>
    
  </entry>
  
  <entry>
    <title>机器学习常用算法——决策树</title>
    <link href="https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E5%86%B3%E7%AD%96%E6%A0%91/"/>
    <id>https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E5%86%B3%E7%AD%96%E6%A0%91/</id>
    <published>2017-01-07T13:27:24.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>决策树</h1><p>决策树是一个非参数的监督式学习方法，主要用于分类和回归，算法的目标是通过推断数据特征，学习决策规则从而创建一个预测目标变量的模型。决策树（decision tree）是一个树结构（可以是二叉树或非二叉树）。其每个非叶节点表示一个特征属性上的测试，每个分支代表这个特征属性在某个值域上的输出，而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始，测试待分类项中相应的特征属性，并按照其值选择输出分支，直到到达叶子节点，将叶子节点存放的类别作为决策结果。</p><img src="/assets/img/决策树.png" alt="决策树"><span id="more"></span><p>决策树（Decision Tree）是一种简单但是广泛使用的分类器。通过训练数据构建决策树，可以高效的对未知的数据进行分类。决策数有两大优点：</p><ol><li>决策树模型可以读性好，具有描述性，有助于人工分析；</li><li>效率高，决策树只需要一次构建，反复使用，每一次预测的最大计算次数不超过决策树的深度。</li></ol><p>决策树既可以做分类，也可以做回归。</p><ol><li>分类树的输出是样本的类标。</li><li>回归树的输出是一个实数 (例如房子的价格，病人呆在医院的时间等)。</li></ol><h2 id="分类">分类</h2><p>以文章开始的图片为例子，假设银行贷款前需要审查用户信息，来确定是否批准贷款，构造数据 data.scv 如下:</p><blockquote><p>house, married, income, give_loan<br>1, 1, 80, 1<br>1, 0, 30, 1<br>1, 1, 30, 1<br>0, 1, 30, 1<br>0, 1, 40, 1<br>0, 0, 80, 1<br>0, 0, 78, 0<br>0, 0, 70, 1<br>0, 0, 88, 1<br>0, 0, 45, 0<br>0, 1, 87, 1<br>0, 0, 89, 1<br>0, 0, 100, 1</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> numpy <span class="keyword">import</span> genfromtxt</span><br><span class="line"><span class="keyword">from</span> sklearn <span class="keyword">import</span> tree</span><br><span class="line"><span class="comment"># 加载数据</span></span><br><span class="line">dataset = genfromtxt(<span class="string">&#x27;data.csv&#x27;</span>, delimiter=<span class="string">&quot;,&quot;</span>)</span><br><span class="line">x = dataset[<span class="number">1</span>:, <span class="number">0</span>:<span class="number">3</span>]</span><br><span class="line">y = dataset[<span class="number">1</span>:, <span class="number">3</span>]</span><br><span class="line">clf = tree.DecisionTreeClassifier()</span><br><span class="line">clf = clf.fit(x, y)</span><br><span class="line"><span class="comment"># 预测</span></span><br><span class="line"><span class="built_in">print</span>(clf.predict([[<span class="number">0</span>, <span class="number">0</span>, <span class="number">50</span>]])) <span class="comment"># [ 0.] 说明此用户不满足贷款条件</span></span><br></pre></td></tr></table></figure><h2 id="回归">回归</h2><p>回归和分类不同的是向量 y 可以是浮点数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn <span class="keyword">import</span> tree</span><br><span class="line">X = [[<span class="number">0</span>, <span class="number">0</span>], [<span class="number">2</span>, <span class="number">2</span>]]</span><br><span class="line">y = [<span class="number">0.5</span>, <span class="number">2.5</span>]</span><br><span class="line">clf = tree.DecisionTreeRegressor()</span><br><span class="line">clf = clf.fit(X, y)</span><br><span class="line">clf.predict([[<span class="number">1</span>, <span class="number">1</span>]])</span><br></pre></td></tr></table></figure><p>scikit-learn 官网给出的例子是：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">from</span> sklearn.tree <span class="keyword">import</span> DecisionTreeRegressor</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"><span class="comment"># 创建随机数据集</span></span><br><span class="line">rng = np.random.RandomState(<span class="number">1</span>)</span><br><span class="line">X = np.sort(<span class="number">5</span> * rng.rand(<span class="number">80</span>, <span class="number">1</span>), axis=<span class="number">0</span>)</span><br><span class="line">y = np.sin(X).ravel()</span><br><span class="line">y[::<span class="number">5</span>] += <span class="number">3</span> * (<span class="number">0.5</span> - rng.rand(<span class="number">16</span>))</span><br><span class="line"><span class="comment"># 训练决策树回归模型</span></span><br><span class="line">regr_1 = DecisionTreeRegressor(max_depth=<span class="number">2</span>)</span><br><span class="line">regr_2 = DecisionTreeRegressor(max_depth=<span class="number">5</span>)</span><br><span class="line">regr_1.fit(X, y)</span><br><span class="line">regr_2.fit(X, y)</span><br><span class="line"><span class="comment"># 预测</span></span><br><span class="line">X_test = np.arange(<span class="number">0.0</span>, <span class="number">5.0</span>, <span class="number">0.01</span>)[:, np.newaxis]</span><br><span class="line">y_1 = regr_1.predict(X_test)</span><br><span class="line">y_2 = regr_2.predict(X_test)</span><br><span class="line"><span class="comment"># 结果展示</span></span><br><span class="line">plt.figure()</span><br><span class="line">plt.scatter(X, y, c=<span class="string">&quot;darkorange&quot;</span>, label=<span class="string">&quot;data&quot;</span>)</span><br><span class="line">plt.plot(X_test, y_1, color=<span class="string">&quot;cornflowerblue&quot;</span>, label=<span class="string">&quot;max_depth=2&quot;</span>, linewidth=<span class="number">2</span>)</span><br><span class="line">plt.plot(X_test, y_2, color=<span class="string">&quot;yellowgreen&quot;</span>, label=<span class="string">&quot;max_depth=5&quot;</span>, linewidth=<span class="number">2</span>)</span><br><span class="line">plt.xlabel(<span class="string">&quot;data&quot;</span>)</span><br><span class="line">plt.ylabel(<span class="string">&quot;target&quot;</span>)</span><br><span class="line">plt.title(<span class="string">&quot;Decision Tree Regression&quot;</span>)</span><br><span class="line">plt.legend()</span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><h2 id="决策树的使用">决策树的使用</h2><ol><li>如果数据量大，决策树容易过拟合。样本和特征的比例非常重要。如果决策树样本少，特征多，非常可能过拟合。</li><li>可以考虑事先做维度约减(PCA，ICA)，以产生一个特征之间区别性大的决策树</li><li>通过 export 将你的训练的决策树可视化，使用 max_depth =3 作为一个初始的树的深度，有一个数据拟合决策树模型的大概感觉，然后逐渐增加深度<br>数据的样本量的增加将加深决策树的深度，使用 max_depth 控制决策树的尺寸以防止过拟合</li><li>使用 min_samples_split 或者 min_samples_leaf 来控制叶节点的样本数量。一个非常小的数量往往意味着过拟合，而一个较大的数可以防止过拟合。可以将 min_samples_leaf=5 作为一个初始值。如果样本数据变化巨大，可以采用一个浮点数。两者的区别在于 min_samples_leaf 保证了叶节点最小的数量，min_samples_split 能够建立任意数量的叶子节点，在文学上用到也更多</li><li>如果样本是有权重的，可以使用 min_weight_fraction_leaf 来实现基于权重的预修剪规则来优化决策树结构</li><li>决策树内部使用 np.float32 向量，如果样本不是这个形式的，将产生一个数据集的样本</li><li>如果数据矩阵 X 是非常稀疏的，建议在拟合和预测之前转换为稀疏矩阵 csc_matrix。稀疏矩阵将比稠密矩阵快数量级的速度</li></ol><p><a href="https://github.com/Leo555/scikit-learn_demo/tree/master/03DecisionTree">代码地址</a></p><h1>参考文献</h1><ul><li><a href="http://www.cnblogs.com/leoo2sk/archive/2010/09/19/decision-tree.html">算法杂货铺——分类算法之决策树(Decision tree)</a></li><li><a href="http://blog.csdn.net/gamer_gyt/article/details/51242815">《机器学习实战》基于信息论的三种决策树算法(ID3,C4.5,CART)</a></li><li><a href="http://python.jobbole.com/86911/">Scikit-learn中的决策树</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h1&gt;决策树&lt;/h1&gt;
&lt;p&gt;决策树是一个非参数的监督式学习方法，主要用于分类和回归，算法的目标是通过推断数据特征，学习决策规则从而创建一个预测目标变量的模型。决策树（decision tree）是一个树结构（可以是二叉树或非二叉树）。其每个非叶节点表示一个特征属性上的测试，每个分支代表这个特征属性在某个值域上的输出，而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始，测试待分类项中相应的特征属性，并按照其值选择输出分支，直到到达叶子节点，将叶子节点存放的类别作为决策结果。&lt;/p&gt;
&lt;img src=&quot;/assets/img/决策树.png&quot; alt=&quot;决策树&quot;&gt;</summary>
    
    
    
    <category term="Machine Learning" scheme="https://lz5z.com/categories/Machine-Learning/"/>
    
    
    <category term="Python" scheme="https://lz5z.com/tags/Python/"/>
    
    <category term="Machine Learning" scheme="https://lz5z.com/tags/Machine-Learning/"/>
    
    <category term="scikit-learn" scheme="https://lz5z.com/tags/scikit-learn/"/>
    
  </entry>
  
  <entry>
    <title>机器学习常用算法——逻辑回归</title>
    <link href="https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92/"/>
    <id>https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92/</id>
    <published>2017-01-06T13:27:24.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>逻辑回归</h1><p>首先，逻辑回归是一个分类算法而不是一个回归算法，该算法可根据已知的一系列因变量估计离散数值（比方说二进制数值 0 或 1 ，是或否，真或假），它通过将数据拟合进一个 <strong>逻辑函数</strong> 来预估一个事件出现的概率。因为它预估的是概率，所以它的输出值大小在 0 和 1 之间（正如所预计的一样）。</p><img src="/assets/img/逻辑回归.jpg" alt="线性回归">[比利时的人口增长数量图]<span id="more"></span><p>逻辑函数由于它的S形，有时也被称为sigmoid函数。</p><p>通过一个简单的例子来理解这个算法。<br>假设你的朋友让你解开一个谜题。这只会有两个结果：你解开了或是你没有解开（离散值）。想象你要解答很多道题来找出你所擅长的主题。这个研究的结果就会像是这样：假设题目是一道十年级的三角函数题，你有 70% 的可能会解开这道题。然而，若题目是个五年级的历史题，你只有 30% 的可能性回答正确。这就是逻辑回归能提供给你的信息。</p><h2 id="用途">用途</h2><p>逻辑回归主要用于分类，比如邮件分类，是否肿瘤、癌症诊断，用户性别判断，预测用户购买产品类别，判断评论是正面还是负面等。</p><p>逻辑回归的数学模型和求解都相对比较简洁，实现相对简单。通过对特征做离散化和其他映射，逻辑回归也可以处理非线性问题，是一个非常强大的分类器。因此在实际应用中，当我们能够拿到许多低层次的特征时，可以考虑使用逻辑回归来解决我们的问题。</p><h2 id="加载数据-Data-Loading">加载数据(Data Loading)</h2><p>我们假设输入是一个特征矩阵或者 csv 文件，我们使用 NumPy 来载入 csv 文件。<br>以下是从 UCI 机器学习数据仓库中下载的数据。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> urllib.request</span><br><span class="line"><span class="comment"># 加载数据</span></span><br><span class="line">url = <span class="string">&quot;http://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data&quot;</span></span><br><span class="line">raw_data = urllib.request.urlopen(url)</span><br><span class="line"><span class="comment"># 把CSV文件转化为numpy matrix</span></span><br><span class="line">dataset = np.loadtxt(raw_data, delimiter=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"><span class="comment"># 训练集和结果</span></span><br><span class="line">X = dataset[:, <span class="number">0</span>:<span class="number">7</span>]</span><br><span class="line">y = dataset[:, <span class="number">8</span>]</span><br></pre></td></tr></table></figure><h2 id="数据归一化-Data-Normalization-与标准化">数据归一化(Data Normalization)与标准化</h2><p>数据归一化是指把数字变成（0,1）之间的小数。</p><p>数据的标准化是将数据按比例缩放，使之落入一个小的特定区间。</p><p>大多数机器学习算法中的梯度方法对于数据的缩放和尺度都是很敏感的，在开始跑算法之前，我们应该进行归一化或者标准化的过程，这使得特征数据缩放到 0-1 范围中。scikit-learn 提供了归一化和标准化的方法：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn <span class="keyword">import</span> preprocessing</span><br><span class="line"><span class="comment"># 归一化</span></span><br><span class="line">normalized_X = preprocessing.normalize(X)</span><br><span class="line"><span class="comment"># 标准化</span></span><br><span class="line">standardized_X = preprocessing.scale(X)</span><br></pre></td></tr></table></figure><h2 id="特征选择-Feature-Selection">特征选择(Feature Selection)</h2><p>在解决一个实际问题的过程中，选择合适的特征或者构建特征的能力特别重要。这成为特征选择或者特征工程。<br>特征选择时一个很需要创造力的过程，更多的依赖于直觉和专业知识，并且有很多现成的算法来进行特征的选择。<br>下面的树算法(Tree algorithms)计算特征的信息量：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn <span class="keyword">import</span> metrics</span><br><span class="line"><span class="keyword">from</span> sklearn.ensemble <span class="keyword">import</span> ExtraTreesClassifier</span><br><span class="line">model = ExtraTreesClassifier()</span><br><span class="line">model.fit(X, y)</span><br><span class="line"><span class="comment"># 显示每个属性相对重要性</span></span><br><span class="line"><span class="built_in">print</span>(model.feature_importances_)</span><br></pre></td></tr></table></figure><h3 id="关于特征提取">关于特征提取</h3><p>机器学习是一个过程，这样的过程包括数据处理 + 模型训练，而特征提取是数据处理中不可或缺的一环。</p><p>比如预测什么样的生活方式特征是引发冠心病 (CHD) 的危险因素？给定具有吸烟状态、饮食、锻炼、饮酒和 CHD 状态度量的患者样本，可以使用这四个生活方式变量建立一个模型，用于预测患者样本中 CHD 的存在性。然后可使用此模型为每个因子推导几率比估计值，从而获知某些信息，例如吸烟者比非吸烟者在何种程度上更易患 CHD。</p><h2 id="算法选择–逻辑回归">算法选择–逻辑回归</h2><p>大多数问题都可以归结为二元分类问题。这个算法的优点是可以给出数据所在类别的概率。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn <span class="keyword">import</span> metrics</span><br><span class="line"><span class="keyword">from</span> sklearn.linear_model <span class="keyword">import</span> LogisticRegression</span><br><span class="line">model = LogisticRegression()</span><br><span class="line">model.fit(X, y)</span><br><span class="line"><span class="comment"># 预测</span></span><br><span class="line">expected = y</span><br><span class="line">predicted = model.predict(X)</span><br><span class="line"><span class="comment"># 模型拟合概述</span></span><br><span class="line"><span class="built_in">print</span>(metrics.classification_report(expected, predicted))</span><br><span class="line"><span class="built_in">print</span>(metrics.confusion_matrix(expected, predicted))</span><br></pre></td></tr></table></figure><h2 id="最后">最后</h2><p>以上 <strong>加载数据</strong> -&gt; <strong>数据归一化</strong> -&gt; <strong>特征选择</strong> -&gt; <strong>算法选择</strong> 既是机器学习的一般代码逻辑。如果选择其它算法，只需要更改最后一步算法选择即可。</p><p><a href="https://github.com/Leo555/scikit-learn_demo/tree/master/02LogisticRegression">代码地址</a></p><h1>参考文献</h1><ul><li><a href="http://tech.meituan.com/intro_to_logistic_regression.html">Logistic Regression 模型简介</a></li><li><a href="http://www.ibm.com/support/knowledgecenter/zh/SSLVMB_22.0.0/com.ibm.spss.statistics.help/spss/regression/idh_lreg.htm">Logistic 回归</a></li><li><a href="http://blog.jasonding.top/2015/04/17/Machine%20Learning%20Experiments/%E3%80%90%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E9%AA%8C%E3%80%91scikit-learn%E7%9A%84%E4%B8%BB%E8%A6%81%E6%A8%A1%E5%9D%97%E5%92%8C%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/#%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92">scikit-learn的主要模块和基本使用</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h1&gt;逻辑回归&lt;/h1&gt;
&lt;p&gt;首先，逻辑回归是一个分类算法而不是一个回归算法，该算法可根据已知的一系列因变量估计离散数值（比方说二进制数值 0 或 1 ，是或否，真或假），它通过将数据拟合进一个 &lt;strong&gt;逻辑函数&lt;/strong&gt; 来预估一个事件出现的概率。因为它预估的是概率，所以它的输出值大小在 0 和 1 之间（正如所预计的一样）。&lt;/p&gt;
&lt;img src=&quot;/assets/img/逻辑回归.jpg&quot; alt=&quot;线性回归&quot;&gt;
[比利时的人口增长数量图]</summary>
    
    
    
    <category term="Machine Learning" scheme="https://lz5z.com/categories/Machine-Learning/"/>
    
    
    <category term="Python" scheme="https://lz5z.com/tags/Python/"/>
    
    <category term="Machine Learning" scheme="https://lz5z.com/tags/Machine-Learning/"/>
    
    <category term="scikit-learn" scheme="https://lz5z.com/tags/scikit-learn/"/>
    
  </entry>
  
  <entry>
    <title>机器学习常用算法——线性回归</title>
    <link href="https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/"/>
    <id>https://lz5z.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E7%AE%97%E6%B3%95%E2%80%94%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/</id>
    <published>2017-01-05T13:27:24.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>背景</h1><p>上次的 ITA 项目开始接触机器学习相关的知识，从本文开始，我将学习并介绍机器学习最常用的几种算法，并使用 <a href="http://scikit-learn.org/">scikit-learn</a> 相关模型完成相关算法的 demo。</p><h1>线性回归</h1><img src="/assets/img/线性回归.jpg" alt="线性回归"><span id="more"></span><p>线性回归，是利用数理统计中回归分析，来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。我们通过拟合最佳直线来建立自变量和因变量的关系，这条最佳直线叫做回归线，并且用 <code>Y= a*x + b</code>这条线性等式来表示。</p><p>理解线性回归可以想象一下一般人身高与体重之间的关系，在不能准确测试体重的情况下，按照身高进行排序，也能大体得出体重的大小。这是现实生活中使用线性回归的例子。<br>在这个例子中，Y 是体重（因变量），x 是身高（自变量），a 和 b 分别为斜率和截距，可以通过最小二乘法获得。</p><h2 id="身高体重">身高体重</h2><h3 id="准备数据">准备数据</h3><p>自己伪造了一些数据</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">runplt</span>():</span><br><span class="line">    plt.figure()</span><br><span class="line">    plt.title(<span class="string">u&#x27;Height-Weight&#x27;</span>)</span><br><span class="line">    plt.xlabel(<span class="string">u&#x27;Height&#x27;</span>)</span><br><span class="line">    plt.ylabel(<span class="string">u&#x27;Weight&#x27;</span>)</span><br><span class="line">    plt.axis([<span class="number">150</span>, <span class="number">190</span>, <span class="number">40</span>, <span class="number">90</span>])</span><br><span class="line">    plt.grid(<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">return</span> plt</span><br><span class="line"></span><br><span class="line">plt = runplt()</span><br><span class="line">x = [[<span class="number">155</span>], [<span class="number">157</span>], [<span class="number">166</span>], [<span class="number">177</span>], [<span class="number">187</span>]]</span><br><span class="line">y = [[<span class="number">55</span>], [<span class="number">60</span>], [<span class="number">63</span>], [<span class="number">70</span>], [<span class="number">79</span>]]</span><br><span class="line">plt.plot(x, y, <span class="string">&#x27;k.&#x27;</span>)</span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><img src="/assets/img/线性回归_1.png" alt="线性回归"><h3 id="创建并拟合模型">创建并拟合模型</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn.linear_model <span class="keyword">import</span> LinearRegression</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="comment"># 创建并拟合模型</span></span><br><span class="line">model = LinearRegression()</span><br><span class="line">model.fit(x, y)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;预测身高180同学的体重：$%.2f&#x27;</span> % model.predict(np.array([<span class="number">180</span>]).reshape(-<span class="number">1</span>, <span class="number">1</span>))[<span class="number">0</span>])</span><br></pre></td></tr></table></figure><p>上述代码中 sklearn.linear_model.LinearRegression 类是一个估计器（estimator）。估计器依据观测值来预测结果。在 scikit-learn 里面，所有的估计器都带有:</p><ul><li>fit()</li><li>predict()</li></ul><p>fit() 用来分析模型参数，predict() 是通过 fit()算出的模型参数构成的模型，对解释变量进行预测获得的值。<br>因为所有的估计器都有这两种方法，所有 scikit-learn 很容易实现不同的模型。</p><h2 id="线性回归分类">线性回归分类</h2><p>线性回归的两种主要类型是一元线性回归和多元线性回归。一元线性回归的特点是只有一个自变量。多元线性回归则存在多个自变量。找最佳拟合直线的时候，你可以拟合到多项或者曲线回归。这些就被叫做多项或曲线回归。</p><h3 id="一元线性回归">一元线性回归</h3><p>一元线性回归模型是 <code>Y= a*x + b</code>，求解一元线性回归模型的本质就是求解参数 a 和 b 的过程，最常用的方法为最小二乘法。</p><h4 id="残差预测值">残差预测值</h4><p>模型的残差是训练样本点与线性回归模型的纵向距离</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 残差预测值</span></span><br><span class="line">y2 = model.predict(x)</span><br><span class="line">plt.plot(x, y, <span class="string">&#x27;k.&#x27;</span>)</span><br><span class="line">plt.plot(x, y2, <span class="string">&#x27;g-&#x27;</span>)</span><br><span class="line"><span class="keyword">for</span> idx, x <span class="keyword">in</span> <span class="built_in">enumerate</span>(x):</span><br><span class="line">    plt.plot([x, x], [y[idx], y2[idx]], <span class="string">&#x27;r-&#x27;</span>)</span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p>如图所示：<br><img src="/assets/img/线性回归_2.jpg" alt="线性回归"></p><p>我们可以通过残差之和最小化实现最佳拟合，也就是说模型预测的值与训练集的数据最接近就是最佳拟合。对模型的拟合度进行评估的函数称为残差平方和（residual sum of squares）成本函数。就是让所有训练数据与模型的残差的平方之和最小化，如下所示：<br><img src="/assets/img/线性回归_3.png" alt="线性回归"></p><p>其中， yi 是观测值， f(xi)f(xi) 是预测值。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;残差平方和: %.2f&#x27;</span> % np.mean((model.predict(x) - y) ** <span class="number">2</span>))</span><br></pre></td></tr></table></figure><p>残差平方和: 2.05</p><h4 id="模型评估">模型评估</h4><p>使用线性回归得出模型后，我们可以用 R 方（r-squared）评估模型的效果。R方也叫确定系数（coefficient of determination），表示模型对现实数据拟合的程度。</p><p>一元线性回归中R方等于皮尔逊积矩相关系数（Pearson product moment correlation coefficient或Pearson’s r）的平方。这种方法计算的R方一定介于0～1之间的正数。其他计算方法，包括scikit-learn中的方法，不是用皮尔逊积矩相关系数的平方计算的，因此当模型拟合效果很差的时候R方会是负值。</p><img src="/assets/img/线性回归_4.png" alt="线性回归"><p>LinearRegression的score方法可以计算R方</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 测试集</span></span><br><span class="line">x_test = [[<span class="number">156</span>], [<span class="number">163</span>], [<span class="number">166</span>], [<span class="number">170</span>], [<span class="number">188</span>]]</span><br><span class="line">y_test = [[<span class="number">56</span>], [<span class="number">63</span>], [<span class="number">63</span>], [<span class="number">72</span>], [<span class="number">80</span>]]</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;R方： &#x27;</span>, model.score(x_test, y_test))</span><br></pre></td></tr></table></figure><p>R 方：  0.898422638707</p><p>R 方是 0.898 说明测试集里面大多数的数据都可以通过模型解释</p><h3 id="多元回归">多元回归</h3><p>多元回归即存在多个自变量，比如影响体重的因素不仅仅有身高，还有胸围，假设 x 中的第一个参数为身高，第二个参数为胸围。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn.linear_model <span class="keyword">import</span> LinearRegression</span><br><span class="line"><span class="comment">## 伪造数据</span></span><br><span class="line">x = [[<span class="number">155</span>, <span class="number">80</span>], [<span class="number">157</span>, <span class="number">82</span>], [<span class="number">166</span>, <span class="number">85</span>], [<span class="number">177</span>, <span class="number">90</span>], [<span class="number">187</span>, <span class="number">97</span>]]</span><br><span class="line">y = [[<span class="number">55</span>], [<span class="number">60</span>], [<span class="number">63</span>], [<span class="number">70</span>], [<span class="number">79</span>]]</span><br><span class="line">model = LinearRegression()</span><br><span class="line">model.fit(x, y)</span><br><span class="line"><span class="comment">## 伪造测试集</span></span><br><span class="line">x_test = [[<span class="number">156</span>, <span class="number">80</span>], [<span class="number">163</span>, <span class="number">83</span>], [<span class="number">166</span>, <span class="number">84</span>], [<span class="number">170</span>, <span class="number">87</span>], [<span class="number">188</span>, <span class="number">99</span>]]</span><br><span class="line">y_test = [[<span class="number">56</span>], [<span class="number">63</span>], [<span class="number">63</span>], [<span class="number">72</span>], [<span class="number">80</span>]]</span><br><span class="line">predictions = model.predict(x_test)</span><br><span class="line"><span class="keyword">for</span> i, prediction <span class="keyword">in</span> <span class="built_in">enumerate</span>(predictions):</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Predicted: %.2f, Target: %s&#x27;</span> % (prediction, y_test[i]))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;R-squared: %.2f&#x27;</span> % model.score(x_test, y_test))</span><br></pre></td></tr></table></figure><p>Predicted: 56.05, Target: [56]<br>Predicted: 60.03, Target: [63]<br>Predicted: 61.30, Target: [63]<br>Predicted: 65.56, Target: [72]<br>Predicted: 82.42, Target: [80]<br>R-squared: 0.83</p><h3 id="多项式回归">多项式回归</h3><p>上面两个例中，都假设自变量和响应变量的关系是线性的。真实情况未必如此，现实世界中的曲线关系都是通过增加多项式实现的，其实现方式和多元线性回归类似。在 scikit-learn 中，我们使用 <strong>PolynomialFeatures</strong> 构建多项式回归模型。下面比较多项式回归和线性回归的区别。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sklearn.preprocessing <span class="keyword">import</span> PolynomialFeatures</span><br><span class="line"><span class="comment"># 建立线性回归，并用训练的模型绘图</span></span><br><span class="line">model = LinearRegression()</span><br><span class="line">model.fit(x, y)</span><br><span class="line">xx = np.linspace(<span class="number">150</span>, <span class="number">190</span>, <span class="number">100</span>)</span><br><span class="line">yy = model.predict(xx.reshape(xx.shape[<span class="number">0</span>], <span class="number">1</span>))</span><br><span class="line">plt = runplt()</span><br><span class="line">plt.plot(x, y, <span class="string">&#x27;k.&#x27;</span>)</span><br><span class="line">plt.plot(xx, yy)</span><br><span class="line"><span class="comment"># degree=3表示多项式最高项为3</span></span><br><span class="line">polynomial_featurizer = PolynomialFeatures(degree=<span class="number">3</span>)</span><br><span class="line">x_train_polynomial = polynomial_featurizer.fit_transform(x)</span><br><span class="line">x_test_polynomial = polynomial_featurizer.transform(x_test)</span><br><span class="line">model_polynomial = LinearRegression()</span><br><span class="line">model_polynomial.fit(x_train_polynomial, y)</span><br><span class="line">xx_polynomial = polynomial_featurizer.transform(xx.reshape(xx.shape[<span class="number">0</span>], <span class="number">1</span>))</span><br><span class="line">plt.plot(xx, model_polynomial.predict(xx_polynomial), <span class="string">&#x27;r-&#x27;</span>)</span><br><span class="line">plt.show()</span><br><span class="line"><span class="comment"># 输出结果</span></span><br><span class="line"><span class="built_in">print</span>(x)</span><br><span class="line"><span class="built_in">print</span>(x_train_polynomial)</span><br><span class="line"><span class="built_in">print</span>(x_test)</span><br><span class="line"><span class="built_in">print</span>(x_test_polynomial)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;1 r-squared&#x27;</span>, model.score(x_test, y_test))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;2 r-squared&#x27;</span>, model_polynomial.score(x_test_polynomial, y_test))</span><br></pre></td></tr></table></figure><img src="/assets/img/线性回归_5.jpg" alt="线性回归"><h3 id="拟合过度">拟合过度</h3><p>我们不断改变 polynomial_featurizer = PolynomialFeatures(degree=3) 中 degree 的参数，当 degree = 5 的时候曲线经过所有的点，这种情况就成为拟合过度（over-fitting）。当模型出现拟合过度的时候，并没有从输入和输出中推导出一般的规律，而是记忆训练集的结果，这样在测试集的测试效果就不好了。</p><img src="/assets/img/线性回归_6.jpg" alt="线性回归"><p><a href="https://github.com/Leo555/scikit-learn_demo/tree/master/01LinearRegression">代码地址</a></p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;背景&lt;/h1&gt;
&lt;p&gt;上次的 ITA 项目开始接触机器学习相关的知识，从本文开始，我将学习并介绍机器学习最常用的几种算法，并使用 &lt;a href=&quot;http://scikit-learn.org/&quot;&gt;scikit-learn&lt;/a&gt; 相关模型完成相关算法的 demo。&lt;/p&gt;
&lt;h1&gt;线性回归&lt;/h1&gt;
&lt;img src=&quot;/assets/img/线性回归.jpg&quot; alt=&quot;线性回归&quot;&gt;</summary>
    
    
    
    <category term="Machine Learning" scheme="https://lz5z.com/categories/Machine-Learning/"/>
    
    
    <category term="Python" scheme="https://lz5z.com/tags/Python/"/>
    
    <category term="Machine Learning" scheme="https://lz5z.com/tags/Machine-Learning/"/>
    
    <category term="scikit-learn" scheme="https://lz5z.com/tags/scikit-learn/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 数据类型</title>
    <link href="https://lz5z.com/JavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/"/>
    <id>https://lz5z.com/JavaScript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/</id>
    <published>2016-12-27T06:02:32.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<p>JavaScript 语言可以识别 7 中不同的数据类型，除 Object 外，其它均为基本数据类型，Object 为引用数据类型。</p><ul><li>Undefined, 只有一个值，即特殊值 undefined，使用 var/let/const 声明但未初始化的值。</li><li>Null，只有一个值，即特殊值 null，null 值表示一个空对象指针。</li><li>Boolean，布尔型，true 和 false。</li><li>Number, 整数和浮点数。</li><li>String, 字符串，由零个或者多个 16 位 Unicode 字符串组成的字符序列。</li><li>Symbol, ES6 新增类型，它的实例是唯一且不可改变的。</li><li>Object, 一组数据和功能的集合。可以通过 new 加对象名称创建。</li></ul><span id="more"></span><h3 id="Undefined-类型">Undefined 类型</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> message <span class="comment">// 变量声明之后默认取得了 undefined 值</span></span><br><span class="line">message == <span class="literal">undefined</span> <span class="comment">// true</span></span><br><span class="line">message === <span class="literal">undefined</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="Null-类型">Null 类型</h3><p>Null 类型只有一个值 null，null 表示一个空指针对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="literal">null</span> <span class="comment">// &quot;objec&quot;</span></span><br></pre></td></tr></table></figure><p>如果定义变量准备在将来保存对象，最好讲该变量初始化为 null，这样可以通过检查 null 来判断是否已经保存了一个对象的引用。</p><p>实际上，undefined 值派生自 null</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">null</span> == <span class="literal">undefined</span> <span class="comment">// true</span></span><br><span class="line"><span class="literal">null</span> === <span class="literal">undefined</span> <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>null vs undefined</p><p>尽管 null 和 undefined 之间的相等操作符（==）返回 true，不过它们的用途完全不同，如前所述，无论什么情况下，没有必要把一个变量的值设为 undefined，而如果一个变量将来要保存对象，应该将其显式地设为 null。</p><h3 id="Boolean-类型">Boolean 类型</h3><p>对于任何数据类型，调用 Boolean() 函数，总是会返回一个 Boolean 值。</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="title class_">Boolean</span>(<span class="number">0</span>) <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="title class_">NaN</span>) <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="literal">null</span>) <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="literal">undefined</span>) <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="string">&#x27;/t&#x27;</span>) <span class="comment">// false</span></span><br></pre></td></tr></table></figure><h3 id="Number-类型">Number 类型</h3><p>(1) 整数：</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">var</span> intNum = <span class="number">55</span></span><br><span class="line"><span class="keyword">var</span> octalNum = <span class="number">070</span> <span class="comment">// 八进制的 56</span></span><br><span class="line"><span class="keyword">var</span> hexNum = <span class="number">0xA</span> <span class="comment">// 十六进制的 10</span></span><br><span class="line"></span><br><span class="line">+<span class="number">0</span> === -<span class="number">0</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>(2) 浮点数：</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="number">3e-17</span> <span class="comment">// 0.000...03</span></span><br><span class="line"></span><br><span class="line"><span class="number">0.1</span> + <span class="number">0.2</span> <span class="comment">// 0.30000000000000004 浮点数最高精度为 17 位小数</span></span><br><span class="line"><span class="number">0.1</span> + <span class="number">0.2</span> === <span class="number">0.3</span> <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>ECMAScript 最小数：Number.MIN_VALUE，在大多数浏览器中为 5e-324。<br>ECMAScript 最大数：Number.MAX_VALUE，在大多数浏览器中为 1.7976931348623157e+308。</p><p>如果计算超过 JavaScript 数值范围，会自动转为特殊的 Infinity 值，负数则为 -Infinity。Infinity 不能参与数值计算。通过 isFinite() 函数判断参数是否位于最大值和最小值之间。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span> / <span class="number">0</span> <span class="comment">// Infinity</span></span><br><span class="line"><span class="built_in">isFinite</span>(<span class="title class_">Number</span>.<span class="property">MAX_VALUE</span> + <span class="title class_">Number</span>.<span class="property">MAX_VALUE</span>) <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>(3) NaN （Not a Number）</p><p>NaN 用来表示本来要返回数值的操作数未返回数值的情况，避免抛出错误。</p><p>NaN 的设计有两个特点：</p><p>1.任何涉及 NaN 的操作都返回 NaN<br>2.NaN与任何值都不相等，包括 NaN 本身</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></pre></td><td class="code"><pre><span class="line"><span class="number">0</span>/<span class="number">0</span> <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">NaN</span>/<span class="number">10</span> <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">NaN</span> == <span class="title class_">NaN</span> <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>针对这两个特点，ECMAScript 设计了 isNaN() 函数。这个函数帮助我们判断参数是否 “不是数值”。isNaN() 接受参数后，会尝试将这个值转换为数值，如果这个值不能被转换为数值，则返回 true。</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="built_in">isNaN</span>(<span class="title class_">NaN</span>) <span class="comment">// true</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="number">10</span>) <span class="comment">// false</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="string">&#x27;10&#x27;</span>) <span class="comment">// false 可以转换为数值 10</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="string">&#x27;blue&#x27;</span>) <span class="comment">// true 不能转换为数值</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="literal">true</span>) <span class="comment">// false 可以转换为数值 1</span></span><br></pre></td></tr></table></figure><p>(4) 数值转换</p><p>Number() 函数转换规则如下：</p><p>1.如果是 Boolean 值，返回 1 或者 0。<br>2.数字直接返回。<br>3.null 返回 0。<br>4.undefined 返回 NaN。<br>5.字符串：如果是十进制整数，八进制整数或者十六进制整数返回十进制整数，空字符串返回 0，其它均返回 NaN。<br>6.如果是对象，调用对象的 valueOf() 方法，然后按照前面的转换规则转换，如果转换值为 NaN，则调用对象的 toString() 方法。</p><p>parseInt()</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="built_in">parseInt</span>(<span class="string">&#x27;1234blue&#x27;</span>) <span class="comment">// 1234</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;&#x27;</span>) <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;0xA&#x27;</span>) <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="number">22.5</span>) <span class="comment">// 22</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="number">070</span>) <span class="comment">// 56</span></span><br></pre></td></tr></table></figure><p>parseInt() 解析八进制字面量的字符串时，ES3 和 ES5 存在区别，在 ES3 中 ‘070’ 被当做八进制字面量，ES5 则当做 ‘70’。<br>因此 parseInt 可以接收第二个参数，表示以多少进制解析第一个参数。</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;0xAF&#x27;</span>, <span class="number">16</span>) <span class="comment">// 175</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;070&#x27;</span>) <span class="comment">// 70</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;070&#x27;</span>, <span class="number">8</span>) <span class="comment">// 56</span></span><br></pre></td></tr></table></figure><h3 id="Symbol-类型">Symbol 类型</h3><p>Symbol 是 ES6 新增的数据类型，用来解决对象中属性名重复的问题，Symbol 表示独一无二的值，通过 Symbol 函数生成。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Symbol</span>(<span class="string">&quot;foo&quot;</span>) !== <span class="title class_">Symbol</span>(<span class="string">&quot;foo&quot;</span>) <span class="comment">// true</span></span><br><span class="line"><span class="keyword">const</span> foo = <span class="title class_">Symbol</span>()</span><br><span class="line"><span class="keyword">const</span> bar = <span class="title class_">Symbol</span>()</span><br><span class="line"><span class="keyword">typeof</span> foo === <span class="string">&quot;symbol&quot;</span> <span class="comment">// true</span></span><br><span class="line"><span class="keyword">typeof</span> bar === <span class="string">&quot;symbol&quot;</span> <span class="comment">// true</span></span><br><span class="line"><span class="keyword">let</span> obj = &#123;&#125;</span><br><span class="line">obj[foo] = <span class="string">&quot;foo&quot;</span></span><br><span class="line">obj[bar] = <span class="string">&quot;bar&quot;</span></span><br><span class="line"><span class="title class_">JSON</span>.<span class="title function_">stringify</span>(obj) <span class="comment">// &#123;&#125;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(obj) <span class="comment">// []</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(obj) <span class="comment">// []</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertySymbols</span>(obj) <span class="comment">// [ foo, bar ]</span></span><br></pre></td></tr></table></figure><h3 id="Object-类型">Object 类型</h3><p>Object 对象是一组数据和功能的集合。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line"><span class="keyword">var</span> o = <span class="keyword">new</span> <span class="title class_">Object</span></span><br></pre></td></tr></table></figure><p>关于 Object 对象的详细内容，可以参考 <a href="https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84Object%E5%AF%B9%E8%B1%A1/">深入学习JavaScript——Object对象</a> 和 <a href="https://lz5z.com/Object.defineProperty%E4%B8%BA%E5%AF%B9%E8%B1%A1%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7/">使用 Object.defineProperty 为对象定义属性</a>。</p><h3 id="如何判断数据类型">如何判断数据类型</h3><p>(1) typeof 操作符</p><p>typeof 操作符返回值一共有7种：number，boolean，symbol，string，object，undefined，function。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="string">&#x27;&#x27;</span> <span class="comment">// string 有效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="number">1</span> <span class="comment">// number 有效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="title class_">Symbol</span>() <span class="comment">// symbol 有效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">true</span> <span class="comment">//boolean 有效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">undefined</span> <span class="comment">//undefined 有效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">null</span> <span class="comment">//object 无效</span></span><br><span class="line"><span class="keyword">typeof</span> [] <span class="comment">//object 无效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="keyword">new</span> <span class="title class_">Function</span>() <span class="comment">// function 有效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="keyword">new</span> <span class="title class_">Date</span>() <span class="comment">//object 无效</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="keyword">new</span> <span class="title class_">RegExp</span>() <span class="comment">//object 无效</span></span><br></pre></td></tr></table></figure><ul><li>对于基本类型，除 null 以外，均可以返回正确的结果。</li><li>对于引用类型，除 function 以外，一律返回 object 类型。</li><li>对于 null ，返回 object 类型。</li><li>对于 function 返回  function 类型。</li></ul><p>(2) instanceof</p><p>instanceof 用来判断 A 是否为 B 的实例，需要注意的是，instanceof 检测的是原型。</p><p>可以理解为：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">instanceof</span> (A, B) &#123;</span><br><span class="line">  <span class="keyword">var</span> L = A.<span class="property">__proto__</span></span><br><span class="line">  <span class="keyword">var</span> R = B.<span class="property"><span class="keyword">prototype</span></span></span><br><span class="line">  <span class="keyword">return</span> L === R    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">[] <span class="keyword">instanceof</span> <span class="title class_">Array</span> <span class="comment">// true</span></span><br><span class="line">&#123;&#125; <span class="keyword">instanceof</span> <span class="title class_">Object</span> <span class="comment">// true</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Date</span>() <span class="keyword">instanceof</span> <span class="title class_">Date</span> <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">A</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">new</span> <span class="title function_">A</span>() <span class="keyword">instanceof</span> A <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line">[] <span class="keyword">instanceof</span> <span class="title class_">Object</span> <span class="comment">// true</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Date</span>() <span class="keyword">instanceof</span> <span class="title class_">Object</span> <span class="comment">// true</span></span><br><span class="line"><span class="keyword">new</span> <span class="title function_">A</span>() <span class="keyword">instanceof</span> <span class="title class_">Object</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>[] 的 <code>__proto__</code> 指向了 Array.prototype，而 <code>Array.prototype.__proto__</code> 又指向了 Object.prototype，而 <code>Object.prototype.__proto__</code> 指向了 null，因此 []、Array、Object 在内部形成了一条原型链。instanceof 只能用来判断两个对象是否属于实例关系，而不能判断一个对象实例具体属于哪种类型。</p><p>(3) constructor</p><p>当一个函数 F 被定义的时候，JS 引擎会自动帮其添加 prototype，并在 prototype 上添加一个 constructor 属性，并让其指向 F 的引用。</p><img src="/assets/img/js_constructor.png" alt="js_constructor"><p>当实例化 F 的时候，<code>var f = new F()</code>，F 原型上的 constructor 传递到了 f 上，因此 <code>f.constructor === F</code>。</p><p>F 利用原型对象上的 constructor 引用了自身，当 F 作为构造函数来创建对象时，原型上的 constructor 就被遗传到了新创建的对象上， 从原型链角度讲，构造函数 F 就是新对象的类型。这样做的意义是，让新对象在诞生以后，就具有可追溯的数据类型。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&#x27;&#x27;</span>.<span class="property">constructor</span> === <span class="title class_">String</span></span><br><span class="line">(<span class="number">1</span>).<span class="property">constructor</span> === <span class="title class_">Number</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Number</span>(<span class="number">1</span>).<span class="property">constructor</span> === <span class="title class_">Number</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Function</span>().<span class="property">constructor</span> === <span class="title class_">Function</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Date</span>().<span class="property">constructor</span> === <span class="title class_">Date</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Error</span>().<span class="property">constructor</span> === <span class="title class_">Error</span></span><br><span class="line">[].<span class="property">constructor</span> === <span class="title class_">Array</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="property">constructor</span> === <span class="title class_">HTMLDocument</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">constructor</span> === <span class="title class_">Window</span></span><br></pre></td></tr></table></figure><p>利用 constructor 判断数据类型存在的问题：</p><ol><li>null 和 undefined 是无效对象，因此没有 constructor 存在。</li><li>函数的 constructor 可以被重写，因此可能会出现判断错误。</li></ol><p>(4) toString</p><p>toString() 是 Object 的原型方式，调用该方法，默认返回当前对象的 <code>[[CLass]]</code>，其格式为 [object Xxx]，其中 Xxx 就是对象的类型。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="string">&#x27;&#x27;</span>) <span class="comment">// [object String]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="number">1</span>) <span class="comment">// [object Number]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="literal">true</span>) <span class="comment">// [object Boolean]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="title class_">Symbol</span>()) <span class="comment">//[object Symbol]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="literal">undefined</span>) <span class="comment">// [object Undefined]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="literal">null</span>) <span class="comment">// [object Null]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="keyword">new</span> <span class="title class_">Function</span>()) <span class="comment">// [object Function]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="keyword">new</span> <span class="title class_">Date</span>()) <span class="comment">// [object Date]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>([]) <span class="comment">// [object Array]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="keyword">new</span> <span class="title class_">RegExp</span>()) <span class="comment">// [object RegExp]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="keyword">new</span> <span class="title class_">Error</span>()) <span class="comment">// [object Error]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="variable language_">document</span>) <span class="comment">// [object HTMLDocument]</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="variable language_">window</span>) <span class="comment">// [object Window]</span></span><br></pre></td></tr></table></figure><h2 id="引用数据类型-vs-基本数据类型">引用数据类型 vs 基本数据类型</h2><p>基本数据类型复制相当于在内存中新开辟一块内存，引用数据类型的复制相当于在内存中创建了一个新的指针，指向存储在堆中的一个对象。</p><p>ECMAScript 中所有的函数都是 <strong>按值传递参数</strong> 的。也就是说，把函数外部的值复制给函数内部的参数，就和把值从一个变量复制到另外一个变量一样。<br>在向参数传递基本数据类型的值时，被传递的值会被复制给一个局部变量（即命名参数，也就是 arguments 对象中的一个元素）。在向参数传递引用类型的值时，会把这个值在内存中的地址复制给一个局部变量，因此这个局部变量的变化会反映在函数外部。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">setName</span> (<span class="params">obj</span>) &#123;</span><br><span class="line">  obj.<span class="property">name</span> = <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> person = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line"><span class="title function_">setName</span>(person)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person.<span class="property">name</span>) <span class="comment">// &quot;Leo&quot;</span></span><br></pre></td></tr></table></figure><h2 id="参考资料">参考资料</h2><ul><li>《JavaScript高级程序设计》</li><li><a href="https://www.cnblogs.com/onepixel/p/5126046.html">判断JS数据类型的4种方法</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Grammar_and_types">语法和数据类型</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;JavaScript 语言可以识别 7 中不同的数据类型，除 Object 外，其它均为基本数据类型，Object 为引用数据类型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Undefined, 只有一个值，即特殊值 undefined，使用 var/let/const 声明但未初始化的值。&lt;/li&gt;
&lt;li&gt;Null，只有一个值，即特殊值 null，null 值表示一个空对象指针。&lt;/li&gt;
&lt;li&gt;Boolean，布尔型，true 和 false。&lt;/li&gt;
&lt;li&gt;Number, 整数和浮点数。&lt;/li&gt;
&lt;li&gt;String, 字符串，由零个或者多个 16 位 Unicode 字符串组成的字符序列。&lt;/li&gt;
&lt;li&gt;Symbol, ES6 新增类型，它的实例是唯一且不可改变的。&lt;/li&gt;
&lt;li&gt;Object, 一组数据和功能的集合。可以通过 new 加对象名称创建。&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="数据类型" scheme="https://lz5z.com/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/"/>
    
    <category term="typeof" scheme="https://lz5z.com/tags/typeof/"/>
    
  </entry>
  
  <entry>
    <title>async 和 defer 的区别</title>
    <link href="https://lz5z.com/async%E5%92%8Cdefer%E7%9A%84%E5%8C%BA%E5%88%AB/"/>
    <id>https://lz5z.com/async%E5%92%8Cdefer%E7%9A%84%E5%8C%BA%E5%88%AB/</id>
    <published>2016-12-26T07:43:58.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<p>HTML 中的 <code>&lt;script&gt;</code> 元素定义了6个属性：</p><ul><li>async：可选，表示立即下载脚本，但不应该妨碍页面中其它的操作，比如下载其它资源或者等待加载其它脚本，只对外部脚本文件有效。</li><li>charset：可选，src 属性指定的代码的字符集。多数浏览器会忽略它的值。</li><li>defer：可选，表示脚本可以延迟到文档完全被解析和显示后再执行。只对外部脚本有效。</li><li>language：已废弃。</li><li>src：可选，表示要执行代码的外部文件。src 可以包含来自外部域的文件。</li><li>type：可选，可以看成 language 的替代属性。表示编写代码使用的脚本语言的内容类型（MIME），默认值为 text/javascript。</li></ul><p>要注意的是，带有 src 的 <code>&lt;script&gt;</code> 元素中不应该再包含额外的代码，如果包含了嵌入的代码，则只会下载外部文件，嵌入的代码不会执行。</p><span id="more"></span><h2 id="标签的位置">标签的位置</h2><p>按照惯例，所有的 <code>&lt;script&gt;</code> 都应该放入 <code>&lt;head&gt;</code> 中，但是这就意味着必须要等所有的 JavaScript 代码下载解析和执行完毕后才能开始呈现页面内容（浏览器在遇到 body 标签时，才开始呈现页面内容）。假如有很多 JavaScript 代码需要执行的话，就会导致浏览器窗口出现空白，因此比较好的做法是把 JavaScript 代码放在 <code>&lt;body&gt;</code> 的最后。</p><h2 id="延迟脚本-defer">延迟脚本 defer</h2><p>HTML4.01 中为 <code>&lt;script&gt;</code> 增加了 defer 属性，这个属性用来表明脚本执行的时候不会影响页面结构，也就是说脚本会延迟到整页面解析完毕后再运行。因此在 <code>&lt;script&gt;</code> 中设置 defer 属性，相当于告诉浏览器，立即下载，但延迟执行。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">script</span> <span class="attr">defer</span> <span class="attr">src</span>=<span class="string">&quot;./a.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">script</span> <span class="attr">defer</span> <span class="attr">src</span>=<span class="string">&quot;./b.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span>&gt;</span><br></pre></td></tr></table></figure><p>在这个例子中，虽然 <code>&lt;script&gt;</code> 放在了 head 中，但是其中包含的脚本将延迟到浏览器解析到 <code>&lt;/html&gt;</code> 标签才会开始执行。HTML5 规范要求脚本按照他们出现的先后顺序执行，因此第一个延迟脚本 a.js 会优先于 b.js 执行，而这两个脚本会先于 <a href="https://developer.mozilla.org/zh-CN/docs/Web/Events/DOMContentLoaded">DOMContentLoaded</a> 事件执行。在现实中，延迟脚本不一定会按照顺序执行，也不一定会在 DOMContentLoaded 事件触发之前执行，因此最好只包含一个延迟脚本。</p><p>defer 属性只适用于外部脚本文件，因此嵌入脚本的 defer 属性会被浏览器忽略，而且各个浏览器对 defer 属性的处理不尽相同，因此把延迟脚本放在页面底部仍是最佳选择。</p><blockquote><p>在 XHTML 文档中，要把 defer 属性设置为 defer=“defer”</p></blockquote><h2 id="异步脚本-async">异步脚本 async</h2><p>HTML5 为 <code>&lt;script&gt;</code> 元素定义了 async 属性。async 只适用于外部脚本文件，并且告诉浏览器立即下载文件。但与 defer 不同的是，标记为 async 的脚本并不能保证按照指定它们的先后顺序执行。例如</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scrpt</span> <span class="attr">async</span> <span class="attr">src</span>=<span class="string">&quot;a.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">scrpt</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scrpt</span> <span class="attr">async</span> <span class="attr">src</span>=<span class="string">&quot;b.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">scrpt</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在上述代码中，b.js 可能会在 a.js 之前执行，因此，确保两者之间互不依赖非常重要，指定 async 属性的目的是不让页面等待两个脚本下载和执行，从而异步脚在页面其它内容。因此，建议异步脚本不要在加载期间修改 DOM。</p><p>异步脚本一定会在页面 <a href="https://developer.mozilla.org/en-US/docs/Web/Events/load">load</a> 事件之前执行，但可能会在 DOMContentLoaded 事件触发之前或之后执行。</p><h2 id="defer-vs-async">defer vs async</h2><p>下面这张图能很好地说明 defer 与 async 之间的关系：</p><img src="/assets/img/async_vs_defer.svg" alt="defer_vs_async"><p>从图中我们可以得出以下几点：</p><ul><li>defer 和 async 在下载时是一样的，都是异步的（相较 HTML 解析）。</li><li>defer 会在 HTML 解析完成后执行的，async 则是下载完成后执行。</li><li>defer 是按照加载顺序执行的，async 是哪个文件先加载完，哪个先执行。</li><li>async 在使用的时候，可以用于完全无依赖的脚本，比如百度分析或者 Google Analytics。</li></ul><h2 id="chrome-是怎么样做的">chrome 是怎么样做的</h2><p>上面提到的只是规范，但是各个厂商的实现可能有所不同，chrome 浏览器首先会请求 HTML 文档，然后对其中的各种资源（图片、CSS、视频等）调用相应的资源加载器进行<strong>异步网络请求</strong>，同时进行 DOM 渲染，直到遇到 <code>&lt;script&gt;</code> 标签的时候，主进程才会停止渲染等待此资源加载完毕然后调用 V8 引擎对 js 解析，继而继续进行 DOM 解析。可以理解为如果加了 async 属性就相当于单独开了一个进程去独立加载和执行，而 defer 是和将 <code>&lt;script&gt;</code> 放到 body 底部一样的效果。</p><p>为验证我们设计测试代码如下：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">&quot;https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.css&quot;</span> <span class="attr">ref</span>=<span class="string">&quot;stylesheet&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">&quot;https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.0/css/bootstrap.css&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.6/quill.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdnjs.cloudflare.com/ajax/libs/react/16.3.2/umd/react.development.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello World<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="放在-head-中">放在 head 中</h3><img src="/assets/img/async_vs_defer_script.png" alt="async_vs_defer_script"><p>可以看到几个资源是异步加载并且执行后才开始出现首屏效果，首屏时间接近 1000ms，还是比较慢的。</p><h3 id="放在-body-底部">放在 body 底部</h3><img src="/assets/img/async_vs_defer_body.png" alt="async_vs_defer_body"><p>放在 body 底部的时候，首屏出现的时间快了很多，大约在 500ms 左右，资源文件在 HTML 解析后按顺序加载执行。</p><h3 id="放在-head-中并且使用-defer">放在 head 中并且使用 defer</h3><img src="/assets/img/async_vs_defer_head_defer.png" alt="async_vs_defer_head_defer"><p>defer 为延迟执行，但是下载是可以异步下载的，首屏时间不到 600ms，但是慢于 script 放于 body 底部。</p><h3 id="放在-head-中并且使用-async">放在 head 中并且使用 async</h3><img src="/assets/img/async_vs_defer_head_async.png" alt="async_vs_defer_head_async"><p>async 为异步代码，所有的代码都是在页面解析完成后执行，但是执行顺序并非按照代码书写顺序。</p><h3 id="defer-vs-async-2">defer vs async</h3><p>两个放在一起更能看出效果</p><img src="/assets/img/async_vs_defer.png" alt="async_vs_defer"><h2 id="参考资料">参考资料</h2><ul><li>《JavaScript 高级程序设计》</li><li><a href="https://segmentfault.com/a/1190000006778717">浅谈script标签的defer和async</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;HTML 中的 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 元素定义了6个属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;async：可选，表示立即下载脚本，但不应该妨碍页面中其它的操作，比如下载其它资源或者等待加载其它脚本，只对外部脚本文件有效。&lt;/li&gt;
&lt;li&gt;charset：可选，src 属性指定的代码的字符集。多数浏览器会忽略它的值。&lt;/li&gt;
&lt;li&gt;defer：可选，表示脚本可以延迟到文档完全被解析和显示后再执行。只对外部脚本有效。&lt;/li&gt;
&lt;li&gt;language：已废弃。&lt;/li&gt;
&lt;li&gt;src：可选，表示要执行代码的外部文件。src 可以包含来自外部域的文件。&lt;/li&gt;
&lt;li&gt;type：可选，可以看成 language 的替代属性。表示编写代码使用的脚本语言的内容类型（MIME），默认值为 text/javascript。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要注意的是，带有 src 的 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 元素中不应该再包含额外的代码，如果包含了嵌入的代码，则只会下载外部文件，嵌入的代码不会执行。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="async" scheme="https://lz5z.com/tags/async/"/>
    
    <category term="defer" scheme="https://lz5z.com/tags/defer/"/>
    
    <category term="script" scheme="https://lz5z.com/tags/script/"/>
    
  </entry>
  
  <entry>
    <title>DOM 事件机制</title>
    <link href="https://lz5z.com/DOM%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6/"/>
    <id>https://lz5z.com/DOM%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6/</id>
    <published>2016-12-25T09:44:44.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<p>DOM 事件流（event  flow）存在三个阶段：事件捕获 --&gt; 事件目标 --&gt; 事件冒泡。</p><p>事件捕获：当事件发生时（onclick, onmouseover……），浏览器会从根节点开始由外到内进行事件传播，即点击了子元素，如果父元素通过事件捕获方式注册了对应的事件的话，会先触发父元素绑定的事件。（IE10 及以下浏览器不支持捕获型事件）</p><p>事件冒泡：与事件捕获恰恰相反，事件冒泡顺序是由内到外进行事件传播，直到根节点。</p><span id="more"></span><h2 id="事件">事件</h2><p>(1) onlick --&gt;事件冒泡，重写 onlick 会覆盖之前属性，没有兼容性问题。DOM0 级事件处理程序，每个元素都有自己的事件处理程序属性，这些属性通常全部小写，将这些属性的值全部设置为一个函数，就可以指定事件处理程序。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> el = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;myBtn&#x27;</span>)</span><br><span class="line"><span class="comment">// 绑定单击事件</span></span><br><span class="line">el.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 解绑单击事件，将 onclick 属性设为 null 即可</span></span><br><span class="line">el.<span class="property">onclick</span> = <span class="literal">null</span> </span><br></pre></td></tr></table></figure><p>(2) addEventListener(event, listener, useCapture)</p><p>参数定义：event—（事件名称，如 click，不带 on），listener—事件监听函数，useCapture—是否采用事件捕获进行事件捕捉，默认为 false，即采用事件冒泡方式。 IE8  及以下不支持，属于 DOM2 级的方法，<strong>可添加多个方法不被覆盖</strong>。</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="comment">// 事件类型没有 on，false 表示在事件第三阶段（冒泡）触发，true表示在事件第一阶段（捕获）触发。 如果handle是同一个方法，只执行一次。</span></span><br><span class="line">ele.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>)&#123; &#125;, <span class="literal">false</span>) </span><br><span class="line"><span class="comment">// 解绑事件，参数和绑定一样</span></span><br><span class="line">ele.<span class="title function_">removeEventListener</span>(event.<span class="property">type</span>, handle, boolean)</span><br></pre></td></tr></table></figure><p>(3) attachEvent(event.type, handle) IE 特有，兼容 IE8 及以下，可添加多个事件处理程序，与 DOM 方法不同的是，多个事件的执行顺序与添加顺序相反，attachEvent 只支持冒泡阶段。</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="comment">// 如果 handle 是同一个方法，绑定几次执行几次，这点和 addEventListener 不同,事件类型要加 on, 例如 onclick 而不是 click</span></span><br><span class="line">ele.<span class="title function_">attachEvent</span>(<span class="string">&#x27;onclick&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>)&#123; &#125;)</span><br><span class="line"><span class="comment">// 解绑事件，参数和绑定一样</span></span><br><span class="line">ele.<span class="title function_">detachEvent</span>(<span class="string">&#x27;onclick&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>)&#123; &#125;)</span><br></pre></td></tr></table></figure><p>使用 attachEvent() 添加的事件可以通过 detachEvent() 来移除，条件是必须提供相同的参数，与 DOM 方法一样，这也意味着添加的匿名函数不能被移除。</p><p>(4) 默认事件行为：href=‘’，submit表单提交等</p><ul><li>return false 阻止独享属性（通过on这种方式）绑定的事件的默认事件</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ele.<span class="property">onclick</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  ……             <span class="comment">// 你的代码</span></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span>   <span class="comment">// 通过返回false值阻止默认事件行为</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>event.preventDefault() 阻止通过 addEventListener() 添加的事件的默认事件</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">element.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> event = e || <span class="variable language_">window</span>.<span class="property">event</span></span><br><span class="line">  ……</span><br><span class="line">  event.<span class="title function_">preventDefault</span>()    <span class="comment">// 阻止默认事件</span></span><br><span class="line">&#125;,<span class="literal">false</span>)</span><br></pre></td></tr></table></figure><ul><li>event.returnValue = false 阻止通过 attachEvent() 添加的事件的默认事件</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">element.<span class="title function_">attachEvent</span>(<span class="string">&quot;onclick&quot;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> event = e || <span class="variable language_">window</span>.<span class="property">event</span></span><br><span class="line">  ……</span><br><span class="line">  event.<span class="property">returnValue</span> = <span class="literal">false</span>     <span class="comment">// 阻止默认事件</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="DOM-中的事件对象">DOM 中的事件对象</h2><table><thead><tr><th style="text-align:left">属性/方法</th><th style="text-align:left">类型</th><th style="text-align:left">读写</th><th style="text-align:left">说明</th></tr></thead><tbody><tr><td style="text-align:left">Event.bubbles</td><td style="text-align:left">Boolean</td><td style="text-align:left">只读</td><td style="text-align:left">表示该事件是否在 DOM 中冒泡</td></tr><tr><td style="text-align:left">Event.cancelable</td><td style="text-align:left">Boolean</td><td style="text-align:left">只读</td><td style="text-align:left">表示这个事件是否可以取消</td></tr><tr><td style="text-align:left">Event.currentTarget</td><td style="text-align:left">Element</td><td style="text-align:left">只读</td><td style="text-align:left">当前注册事件的对象的引用，当前事件需要传递到的对象</td></tr><tr><td style="text-align:left">Event.defaultPrevented</td><td style="text-align:left">Boolean</td><td style="text-align:left">只读</td><td style="text-align:left">表示了是否已经执行过了 event.preventDefault()</td></tr><tr><td style="text-align:left">Event.eventPhase</td><td style="text-align:left">Integer</td><td style="text-align:left">只读</td><td style="text-align:left">指示事件流正在处理哪个阶段: 1.捕获 2.目标 3. 冒泡</td></tr><tr><td style="text-align:left">Event.target</td><td style="text-align:left">Element</td><td style="text-align:left">只读</td><td style="text-align:left">对事件起源目标的引用</td></tr><tr><td style="text-align:left">Event.timeStamp</td><td style="text-align:left">Number</td><td style="text-align:left">只读</td><td style="text-align:left">事件创建时的时间戳，毫秒级别</td></tr><tr><td style="text-align:left">Event.type</td><td style="text-align:left">String</td><td style="text-align:left">只读</td><td style="text-align:left">事件的类型（‘click’）</td></tr><tr><td style="text-align:left">event.preventDefault</td><td style="text-align:left">Function</td><td style="text-align:left">只读</td><td style="text-align:left">取消事件的默认行为，如果 cancelable 是 true，则可以使用这个方法</td></tr><tr><td style="text-align:left">event.stopImmediatePropagation</td><td style="text-align:left">Function</td><td style="text-align:left">只读</td><td style="text-align:left">取消事件的进一步捕获或者冒泡，同时阻止任何事件处理程序被调用（DOM3）</td></tr><tr><td style="text-align:left">event.stopPropagation</td><td style="text-align:left">Function</td><td style="text-align:left">只读</td><td style="text-align:left">取消事件的默认行为，如果 bubbles 是 true，则可以使用这个方法</td></tr></tbody></table><h3 id="target-vs-currnetTarget">target vs currnetTarget</h3><p>在事件处理程序的内部，对象的 this 始终等于 currentTarget 的值，而 target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素，则 this， currentTarget 和 target 包含相同的值。</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">var</span> btn = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;myBtn&#x27;</span>)</span><br><span class="line">btn.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">  e.<span class="property">currentTarget</span> === <span class="variable language_">this</span> <span class="comment">// true</span></span><br><span class="line">  e.<span class="property">target</span> === <span class="variable language_">this</span> <span class="comment">// true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于 click 事件的目标是按钮，因此这三个值是相等的，但如果事件处理程序存在于按钮的父节点中，结果就不一样了。</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="variable language_">document</span>.<span class="property">body</span>.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">  e.<span class="property">target</span> === <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;myBtn&#x27;</span>) <span class="comment">// true</span></span><br><span class="line">  e.<span class="property">currentTarget</span> === <span class="variable language_">document</span>.<span class="property">body</span> <span class="comment">// true</span></span><br><span class="line">  <span class="variable language_">this</span> === <span class="variable language_">document</span>.<span class="property">body</span> <span class="comment">// true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>单击这个按钮时，this 和 currentTarget 都等于 document.body，因为事件处理程序是注册到这个元素上的。然而 target 元素却等于按钮元素，因为它是 click 事件的真正目标。</p><h2 id="IE-事件对象">IE 事件对象</h2><p>访问 IE 中的 event 对象时，如果使用 DOM0 级方法添加事件处理程序，event 对象作为 window 对象的一个属性存在。使用 attachEvent() 添加事件处理程序时，event 会作为参数传入，也可以从 window 对象中访问 event 对象，就像 DOM0 级方法一样。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// onclick</span></span><br><span class="line"><span class="keyword">var</span> btn = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;myBtn&#x27;</span>)</span><br><span class="line">btn.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> event = <span class="variable language_">window</span>.<span class="property">event</span></span><br><span class="line">  <span class="title function_">alert</span>(event.<span class="property">type</span>) <span class="comment">// &#x27;click&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// attachEvent</span></span><br><span class="line"><span class="keyword">var</span> btn = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;myBtn&#x27;</span>)</span><br><span class="line">btn.<span class="title function_">attachEvent</span>(<span class="string">&#x27;onclick&#x27;</span>, <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="title function_">alert</span>(event.<span class="property">type</span>) <span class="comment">// &#x27;click&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><table><thead><tr><th style="text-align:left">属性/方法</th><th style="text-align:left">类型</th><th style="text-align:left">读写</th><th style="text-align:left">说明</th></tr></thead><tbody><tr><td style="text-align:left">Event.cancelable</td><td style="text-align:left">Boolean</td><td style="text-align:left">读/写</td><td style="text-align:left">默认为 false，但将其设置为 true 就可以取消事件冒泡（与 DOM0 级的 stopPropagation()方法的作用相同）</td></tr><tr><td style="text-align:left">Event.returnValue</td><td style="text-align:left">Boolean</td><td style="text-align:left">读/写</td><td style="text-align:left">默认为 false，但将其设置为 true 就可以取消事件的默认行为（与 DOM 中的 preventDefault()方法的作用相同）</td></tr><tr><td style="text-align:left">Event.srcElement</td><td style="text-align:left">Element</td><td style="text-align:left">只读</td><td style="text-align:left">事件的目标（DOM 中的 target）</td></tr><tr><td style="text-align:left">Event.type</td><td style="text-align:left">String</td><td style="text-align:left">只读</td><td style="text-align:left">事件的类型</td></tr></tbody></table><p>事件处理程序的作用域是根据指定它的方式来确定的，所以其 this 也会有所不同，比较好的办法是用 event.srcElement。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> btn = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;myBtn&#x27;</span>)</span><br><span class="line">btn.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">event</span>.<span class="property">srcElement</span> === <span class="variable language_">this</span> <span class="comment">// true</span></span><br><span class="line">&#125;</span><br><span class="line">btn.<span class="title function_">attachEvent</span>(<span class="string">&#x27;onclick&#x27;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  event.<span class="property">srcElement</span> === <span class="variable language_">this</span> <span class="comment">// false</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>IE 事件中的 returnValue 相当于 DOM 中的 preventDefault() 方法，它们的作用都是取消事件的默认行为，不过这里不能确定事件的默认行为是否已经被取消。</p><p>cancelBubble 属性与 DOM 中的 stopPropagation() 方法作用相同，用来阻止事件冒泡，由于 IE 不支持事件捕获，因此只能阻止事件冒泡，而 stopPropagation() 同时可以取消事件冒泡和捕获。</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">var</span> btn = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;myBtn&#x27;</span>)</span><br><span class="line">btn.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">event</span>.<span class="property">returnValue</span> = <span class="literal">false</span> <span class="comment">// 阻止默认事件</span></span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">event</span>.<span class="property">cancelBubble</span> = <span class="literal">true</span> <span class="comment">// 阻止事件冒泡</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="事件封装">事件封装</h2><p>JavaScript 中实现事件绑定主要使用两个方法： <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener</a>、<a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/attachEvent">attachEvent</a>。</p><p>为了兼容浏览器，按照网上通用的方案对事件进行封装</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 事件绑定</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">addEvent</span>(<span class="params">element, eType, handle, bol</span>) &#123;</span><br><span class="line"> <span class="keyword">if</span> (element.<span class="property">addEventListener</span>) &#123;       <span class="comment">// 如果支持addEventListener</span></span><br><span class="line">   element.<span class="title function_">addEventListener</span>(eType, handle, bol)</span><br><span class="line"> &#125; <span class="keyword">else</span> <span class="keyword">if</span> (element.<span class="property">attachEvent</span>) &#123;      <span class="comment">// 如果支持attachEvent</span></span><br><span class="line">   element.<span class="title function_">attachEvent</span>(<span class="string">&#x27;on&#x27;</span> + eType, handle)</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;                  <span class="comment">// 否则使用兼容的onclick绑定</span></span><br><span class="line">   element[<span class="string">&#x27;on&#x27;</span> + eType] = handle</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 事件解绑</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">removeEvent</span>(<span class="params">element, eType, handle, bol</span>) &#123;</span><br><span class="line"> <span class="keyword">if</span> (element.<span class="property">addEventListener</span>) &#123;</span><br><span class="line">   element.<span class="title function_">removeEventListener</span>(eType, handle, bol)</span><br><span class="line"> &#125; <span class="keyword">else</span> <span class="keyword">if</span> (element.<span class="property">attachEvent</span>) &#123;</span><br><span class="line">   element.<span class="title function_">detachEvent</span>(<span class="string">&#x27;on&#x27;</span> + eType, handle)</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">   element[<span class="string">&#x27;on&#x27;</span> + eType] = <span class="literal">null</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="阻止事件冒泡、事件捕获">阻止事件冒泡、事件捕获</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 阻止默认事件</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">preventDefault</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (event.<span class="property">preventDefault</span>) &#123;</span><br><span class="line">    event.<span class="title function_">preventDefault</span>()</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    event.<span class="property">returnValue</span> = <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 阻止事件进一步传播</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">stopPropagation</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (event.<span class="property">stopPropagation</span>) &#123;</span><br><span class="line">    event.<span class="title function_">stopPropagation</span>() <span class="comment">// 阻止事件的进一步传播，包括（冒泡，捕获），无参数</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    event.<span class="property">cancelBubble</span> = <span class="literal">true</span> <span class="comment">// true 为阻止冒泡</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125; </span><br><span class="line"><span class="comment">// 获取事件目标</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getTarget</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> event.<span class="property">target</span> || event.<span class="property">srcElement</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="事件流">事件流</h2><h3 id="事件冒泡">事件冒泡</h3><p>HTML内容：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;parent&quot;</span>&gt;</span></span><br><span class="line">    父元素</span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;child&quot;</span>&gt;</span></span><br><span class="line">      子元素</span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p>css</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#parent</span>&#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line">  <span class="attribute">height</span>:<span class="number">200px</span>;</span><br><span class="line">  <span class="attribute">text-align</span>: center;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">3</span>;</span><br><span class="line">  <span class="attribute">background</span>: green;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#child</span>&#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0</span> auto;</span><br><span class="line">  <span class="attribute">background</span>: orange;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>JavaScript</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> parent = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;parent&#x27;</span>)</span><br><span class="line"><span class="keyword">let</span> child = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;child&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;click-body&#x27;</span>)</span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">parent.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;click-parent&#x27;</span>)</span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">child.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;click-child&#x27;</span>)</span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br></pre></td></tr></table></figure><p>通过 ‘addEventListener’ 方法，采用事件冒泡方式给 DOM 元素注册 click 事件，点击“子元素”，控制台依次输出 “click-child” --&gt; “click-parent” --&gt; “click-body”。</p><p>事件触发顺序是由内到外的，这就是事件冒泡，虽然只点击子元素，但是它的父元素也会触发相应的事件。</p><!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>DOM 事件</title>  <style>    #parent{      width: 200px;      height:200px;      text-align: center;      line-height: 3;      background: green;    }    #child{      width: 100px;      height: 100px;      margin: 0 auto;      background: orange;    }  </style></head><body>  <div id="parent">    父元素    <div id="child">      子元素    </div>  </div>  <script type="text/javascript">    let parent = document.getElementById('parent')    let child = document.getElementById('child')      document.body.addEventListener('click', function(e) {      console.log('click-body')    }, false)        parent.addEventListener('click', function(e) {      console.log('click-parent')    }, false)    child.addEventListener('click', function(e) {      console.log('click-child')    }, false)  </script></body></html>（F12 打开控制台，点击查看效果）<p>如果点击子元素不想触发父元素的事件怎么办？<br>那就是停止事件传播—event.stopPropagation()</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">child.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>)&#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;click-child&#x27;</span>)</span><br><span class="line">  　e.<span class="title function_">stopPropagation</span>()</span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br></pre></td></tr></table></figure><!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>DOM 事件</title>  <style>    #parent2{      width: 200px;      height:200px;      text-align: center;      line-height: 3;      background: green;    }    #child2{      width: 100px;      height: 100px;      margin: 0 auto;      background: orange;    }  </style></head><body>  <div id="parent2">    父元素    <div id="child2">      子元素    </div>  </div>  <script type="text/javascript">    let parent2 = document.getElementById('parent2')    let child2 = document.getElementById('child2')        parent2.addEventListener('click', function(e) {      console.log('click-parent')    }, false)    child2.addEventListener('click', function(e) {      console.log('click-child')      e.stopPropagation()    }, false)  </script></body></html>（F12 打开控制台，点击查看效果）<h3 id="事件捕获">事件捕获</h3><p>修改上面事件冒泡的例子</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> parent = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;parent&#x27;</span>)</span><br><span class="line"><span class="keyword">let</span> child = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;child&#x27;</span>)</span><br><span class="line"></span><br><span class="line">parent.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;click-parent---事件传播&#x27;</span>)</span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br><span class="line"><span class="comment">// 新增事件捕获</span></span><br><span class="line">parent.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;click-parent--事件捕获&#x27;</span>)</span><br><span class="line">&#125;, <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line">child.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;click-child&#x27;</span>)</span><br><span class="line">&#125;, <span class="literal">false</span>)</span><br></pre></td></tr></table></figure><!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>DOM 事件</title>  <style>    #parent3{      width: 200px;      height:200px;      text-align: center;      line-height: 3;      background: green;    }    #child3{      width: 100px;      height: 100px;      margin: 0 auto;      background: orange;    }  </style></head><body>  <div id="parent3">    父元素    <div id="child3">      子元素    </div>  </div>  <script type="text/javascript">    let parent3 = document.getElementById('parent3')    let child3 = document.getElementById('child3')    parent3.addEventListener('click', function(e) {      console.log('click-parent---事件传播')    }, false)            // 新增事件捕获    parent3.addEventListener('click', function(e) {      console.log('click-parent--事件捕获')    }, true)    child3.addEventListener('click', function(e) {      console.log('click-child')    }, false)  </script></body></html>（F12 打开控制台，点击查看效果）<p>父元素通过事件捕获的方式注册了 click 事件，所以在事件捕获阶段就会触发，然后到了目标阶段，即事件源，之后进行事件传播，parent 同时也用冒泡方式注册了 click 事件，所以这里会触发冒泡事件，最后到根节点。这就是整个事件流程。</p><h3 id="事件委托">事件委托</h3><p>事件委托(事件代理)：利用事件冒泡的特性，将里层的事件委托给外层事件，根据 event 对象的属性进行事件委托，改善性能。<br>使用事件委托能够避免对特定的每个节点添加事件监听器；事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件，找到是哪个子元素的事件。</p><p>委托在 JQuery 中已经得到了实现，即通过 <strong>$(selector).on(event,childSelector,data,function,map)</strong> 实现委托，一般用于动态生成的元素，当然 JQuery 也是通过原生的 js 去实现的，下面举一个简单的栗子，如果要单独点击 table 里面的 td，普通做法是 for 循环给每个 td 绑定事件，td 少的话性能什么差别，td 如果多了，就不行了，我们使用事件委托:</p><p>HTML</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">id</span>=<span class="string">&quot;outside&quot;</span> <span class="attr">border</span>=<span class="string">&quot;1&quot;</span> <span class="attr">style</span>=<span class="string">&quot;cursor: pointer;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table01<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table02<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table03<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table04<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table05<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table06<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table07<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table08<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table09<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">td</span>&gt;</span>table10<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">table</span>&gt;</span></span><br></pre></td></tr></table></figure><p>JavaScript</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> out = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;outside&#x27;</span>)</span><br><span class="line"><span class="keyword">if</span> (out.<span class="property">addEventListener</span>) &#123;</span><br><span class="line">  out.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    e = e || <span class="variable language_">window</span>.<span class="property">event</span></span><br><span class="line">    <span class="comment">// IE没有e.target，有e.srcElement</span></span><br><span class="line">    <span class="keyword">let</span> target = e.<span class="property">target</span> || e.<span class="property">srcElement</span></span><br><span class="line">    <span class="comment">// 判断事件目标是否是td，是的话target即为目标节点td</span></span><br><span class="line">    <span class="keyword">if</span> (target.<span class="property">tagName</span>.<span class="title function_">toLowerCase</span>() == <span class="string">&#x27;td&#x27;</span>) &#123;</span><br><span class="line">      <span class="title function_">changeStyle</span>(target)</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(target.<span class="property">innerHTML</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="literal">false</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  out.<span class="title function_">attachEvent</span>(<span class="string">&#x27;onclick&#x27;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    e = e || <span class="variable language_">window</span>.<span class="property">event</span></span><br><span class="line">    <span class="comment">// IE没有e.target，有e.srcElement</span></span><br><span class="line">    <span class="keyword">let</span> target = e.<span class="property">target</span> || e.<span class="property">srcElement</span></span><br><span class="line">    <span class="comment">// 判断事件目标是否是td，是的话target即为目标节点td</span></span><br><span class="line">    <span class="keyword">if</span> (target.<span class="property">tagName</span>.<span class="title function_">toLowerCase</span>() == <span class="string">&#x27;td&#x27;</span>) &#123;</span><br><span class="line">      <span class="title function_">changeStyle</span>(target)</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(target.<span class="property">innerHTML</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">changeStyle</span>(<span class="params">ele</span>) &#123;</span><br><span class="line">  ele.<span class="property">innerHTML</span> = <span class="string">&#x27;已点击&#x27;</span></span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">background</span> = <span class="string">&#x27;#900&#x27;</span></span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">color</span> = <span class="string">&#x27;#fff&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>DOM 事件</title></head><body>  <table id="outside" border="1" style="cursor: pointer;">  <tr>    <td>table01</td>    <td>table02</td>    <td>table03</td>    <td>table04</td>    <td>table05</td>    <td>table06</td>    <td>table07</td>    <td>table08</td>    <td>table09</td>    <td>table10</td>  </tr>  </table>  <script type="text/javascript">    let out = document.getElementById('outside')    if (out.addEventListener) {      out.addEventListener('click', function(e) {        e = e || window.event          // IE 没有 e.target，有 e.srcElement        let target = e.target || e.srcElement          // 判断事件目标是否是td，是的话 target 即为目标节点 td        if (target.tagName.toLowerCase() == 'td') {          changeStyle(target)          console.log(target.innerHTML)        }      }, false)    } else {      out.attachEvent('onclick', function(e) {        e = e || window.event          // IE 没有 e.target，有 e.srcElement        let target = e.target || e.srcElement          // 判断事件目标是否是 td，是的话 target 即为目标节点 td        if (target.tagName.toLowerCase() == 'td') {          changeStyle(target)          console.log(target.innerHTML)        }      })    }    function changeStyle(ele) {      ele.innerHTML = '已点击'      ele.style.background = '#900'      ele.style.color = '#fff'    }  </script></body></html>（点击查看效果）<h2 id="总结">总结</h2><p>事件的三个阶段分别为：捕获，目标和冒泡，低版本 IE 不支持捕获。绑定事件的方法为 <strong>addEventListener</strong> 和 <strong>attachEvent</strong>。addEventListener 方法的第三个 boolean 型参数表示添加的事件为捕获或者冒泡，true 代表捕获，false 代表冒泡。</p><p>事件冒泡的优点为：</p><ol><li>可以大量节省内存占用，减少事件注册，比如在 table 上代理所有 td 的 click 事件。</li><li>可以实现为动态增加的 DOM 绑定事件的功能。</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;DOM 事件流（event  flow）存在三个阶段：事件捕获 --&amp;gt; 事件目标 --&amp;gt; 事件冒泡。&lt;/p&gt;
&lt;p&gt;事件捕获：当事件发生时（onclick, onmouseover……），浏览器会从根节点开始由外到内进行事件传播，即点击了子元素，如果父元素通过事件捕获方式注册了对应的事件的话，会先触发父元素绑定的事件。（IE10 及以下浏览器不支持捕获型事件）&lt;/p&gt;
&lt;p&gt;事件冒泡：与事件捕获恰恰相反，事件冒泡顺序是由内到外进行事件传播，直到根节点。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="DOM" scheme="https://lz5z.com/tags/DOM/"/>
    
    <category term="事件流" scheme="https://lz5z.com/tags/%E4%BA%8B%E4%BB%B6%E6%B5%81/"/>
    
    <category term="HTML" scheme="https://lz5z.com/tags/HTML/"/>
    
    <category term="事件委托/代理" scheme="https://lz5z.com/tags/%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98-%E4%BB%A3%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 中 的 DOM 和 BOM</title>
    <link href="https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84DOM%E5%92%8CBOM/"/>
    <id>https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84DOM%E5%92%8CBOM/</id>
    <published>2016-12-24T05:23:25.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<h2 id="JavaScript-与-ECMAScript-关系">JavaScript 与 ECMAScript 关系</h2><p>JavaScript = ECMAScript + DOM + BOM</p><p>1.ECMAScript 为 JavaScript 提供核心语言功能，是由欧洲计算机制造商协会（ECMA）39号技术委员会（TC39）制定的一种通用、跨平台、供应商中立的脚本语言和语义。ECMAScript 是一种由 ECMA 组织通过 ECMA-262 标准化的脚本程序设计语言。ECMA-262 标准没有参考 Web 浏览器，它规定了语言的语法、类型、语句、关键字、保留字、操作符、对象。</p><p>2.DOM (文档对象模型) 是针对 XML 但是经过扩展用于 HTML 的应用程序编程接口（API）。DOM 把 HTML 页面映射为一个多层节点结构，开发人员借助 DOM 提供的 API，可以轻松地删除，添加，替换或者修改节点。</p><p>3.BOM（浏览器对象模型）指的是由 Web 浏览器暴露的所有对象组成的表示模型。从根本上将 BOM 只处理浏览器窗口和框架，但是人们习惯把针对浏览器的 JavaScript 扩展也算作 BOM 的一部分，例如：浏览器弹出新窗口的功能；移动、缩放和关闭浏览器窗口的功能；navigator 对象；location 对象； screen 对象；cookies 支持；XMLHttpRequest 和 IE 的 ActiveXObject 对象。BOM 直到 HTML5 才有了规范可以遵守，在此之前每个浏览器都有自己不同的实现。</p><span id="more"></span><h3 id="DOM-级别">DOM 级别</h3><p>DOM1 级由两个模块组成，DOM 核心（DOM Core）和 DOM HTML。其中，DOM Core 规定如何映射基于 XML 的文档结构，DOM HTML 模块则在 DOM Core 基础上加以扩展，添加了针对 HTML 的对象和方法。</p><p>DOM2 在原有的 DOM 基础上又扩充了鼠标和用户界面事件、范围、遍历（迭代 DOM 文档的方法）等细分模块，并且通过对象接口增加了对 CSS 的支持。DOM2 级引入的模块有：<br>- DOM 视图（DOM Views）：定义了追踪不同文档的视图接口。<br>- DOM 事件（DOM Events）：定义了事件和事件处理的接口。<br>- DOM 样式（DOM Style）：定义了基于 CSS 为元素样式的接口。<br>- DOM 遍历和范围（DOM Traversal and Range）：定义了遍历和操作文档树的接口。</p><p>DOM3 级进一步扩展 DOM，引入了以统一方式加载和保存文档的方法——在 DOM 加载和保存（DOM Load and Save）模块中定义，新增了 DOM 验证（DOM Validation）。DOM3 级也对 DOM Core 进行了扩展，开始支持 XML 1.0 规范。</p><blockquote><p>DOM0 级，DOM0 级标准本质上不存在，所谓 DOM0 只是 DOM 历史坐标中的一个参照点，具体来说，DOM0 级是指 Internet Explorer 4.0 和 Netscape Navigator 4.0 最初支持的 DHTML。</p></blockquote><p>可以通过以下代码确定浏览器是否支持 DOM 模块：</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">var</span> supportsDOM2Core = <span class="variable language_">document</span>.<span class="property">implementation</span>.<span class="title function_">hasFeature</span>(<span class="string">&#x27;core&#x27;</span>, <span class="string">&#x27;2.0&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> supportsDOM3Core = <span class="variable language_">document</span>.<span class="property">implementation</span>.<span class="title function_">hasFeature</span>(<span class="string">&#x27;core&#x27;</span>, <span class="string">&#x27;3.0&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> supportsDOM2HTML = <span class="variable language_">document</span>.<span class="property">implementation</span>.<span class="title function_">hasFeature</span>(<span class="string">&#x27;HTML&#x27;</span>, <span class="string">&#x27;2.0&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> supportsDOM2Views = <span class="variable language_">document</span>.<span class="property">implementation</span>.<span class="title function_">hasFeature</span>(<span class="string">&#x27;Views&#x27;</span>, <span class="string">&#x27;2.0&#x27;</span>)</span><br><span class="line"><span class="keyword">var</span> supportsDOM2XML =  <span class="variable language_">document</span>.<span class="property">implementation</span>.<span class="title function_">hasFeature</span>(<span class="string">&#x27;XML&#x27;</span>, <span class="string">&#x27;2.0&#x27;</span>)</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;JavaScript-与-ECMAScript-关系&quot;&gt;JavaScript 与 ECMAScript 关系&lt;/h2&gt;
&lt;p&gt;JavaScript = ECMAScript + DOM + BOM&lt;/p&gt;
&lt;p&gt;1.ECMAScript 为 JavaScript 提供核心语言功能，是由欧洲计算机制造商协会（ECMA）39号技术委员会（TC39）制定的一种通用、跨平台、供应商中立的脚本语言和语义。ECMAScript 是一种由 ECMA 组织通过 ECMA-262 标准化的脚本程序设计语言。ECMA-262 标准没有参考 Web 浏览器，它规定了语言的语法、类型、语句、关键字、保留字、操作符、对象。&lt;/p&gt;
&lt;p&gt;2.DOM (文档对象模型) 是针对 XML 但是经过扩展用于 HTML 的应用程序编程接口（API）。DOM 把 HTML 页面映射为一个多层节点结构，开发人员借助 DOM 提供的 API，可以轻松地删除，添加，替换或者修改节点。&lt;/p&gt;
&lt;p&gt;3.BOM（浏览器对象模型）指的是由 Web 浏览器暴露的所有对象组成的表示模型。从根本上将 BOM 只处理浏览器窗口和框架，但是人们习惯把针对浏览器的 JavaScript 扩展也算作 BOM 的一部分，例如：浏览器弹出新窗口的功能；移动、缩放和关闭浏览器窗口的功能；navigator 对象；location 对象； screen 对象；cookies 支持；XMLHttpRequest 和 IE 的 ActiveXObject 对象。BOM 直到 HTML5 才有了规范可以遵守，在此之前每个浏览器都有自己不同的实现。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="DOM" scheme="https://lz5z.com/tags/DOM/"/>
    
    <category term="BOM" scheme="https://lz5z.com/tags/BOM/"/>
    
  </entry>
  
  <entry>
    <title>Angular 双向绑定实现原理</title>
    <link href="https://lz5z.com/Angular%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9A%E5%8E%9F%E7%90%86/"/>
    <id>https://lz5z.com/Angular%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9A%E5%8E%9F%E7%90%86/</id>
    <published>2016-12-19T13:33:09.000Z</published>
    <updated>2026-05-07T14:50:53.968Z</updated>
    
    <content type="html"><![CDATA[<h1>从一个 demo 讲起</h1><p>用 Angular + <a href="http://socket.io">socket.io</a> 做了一个聊天 demo，消息通信没有问题，在 Angular 数据绑定的地方却栽了跟头：明明 model 已经发生了改变，在视图上就是看不到更新。</p><p>后来仔细研究，通过使用 “$scope.$apply()” 解决了这个问题。</p><p>之前对 Angular 数据双向绑定只有一个大概的印象，并没有深入地了解，正好趁这个机会好好学习一下数据绑定的过程。</p><span id="more"></span><h2 id="简化代码">简化代码</h2><p>服务端代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"><span class="keyword">let</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">let</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>).<span class="title class_">Server</span>(app);</span><br><span class="line"><span class="keyword">let</span> io = <span class="built_in">require</span>(<span class="string">&#x27;socket.io&#x27;</span>)(http);</span><br><span class="line"><span class="keyword">let</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;public&#x27;</span>)));</span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/&#x27;</span>, <span class="keyword">function</span> (<span class="params">req, res</span>) &#123;</span><br><span class="line">    res.<span class="title function_">sendFile</span>(__dirname + <span class="string">&#x27;/index.html&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">io.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="keyword">function</span>(<span class="params">socket</span>)&#123;</span><br><span class="line">    <span class="comment">// 接收事件</span></span><br><span class="line">    socket.<span class="title function_">on</span>(<span class="string">&#x27;chat message&#x27;</span>, <span class="keyword">function</span>(<span class="params">msg</span>)&#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(msg);</span><br><span class="line">        <span class="comment">// 发送事件</span></span><br><span class="line">        io.<span class="title function_">emit</span>(<span class="string">&#x27;chat message&#x27;</span>, msg);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">http.<span class="title function_">listen</span>(<span class="number">3000</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;listening on :3000&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>客户端代码：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!doctype <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">ng-app</span>=<span class="string">&quot;chatApp&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Socket.IO demo<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span> <span class="attr">href</span>=<span class="string">&quot;style.css&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;/socket.io/socket.io.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;main.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span> <span class="attr">ng-controller</span>=<span class="string">&quot;ChatController&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">&quot;messages&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">ng-repeat</span>=<span class="string">&quot;item in chatMessage&quot;</span>&gt;</span>&#123;&#123;item&#125;&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">ng-submit</span>=<span class="string">&quot;submit()&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">input</span> <span class="attr">id</span>=<span class="string">&quot;m&quot;</span> <span class="attr">ng-model</span>=<span class="string">&quot;chatInput&quot;</span> <span class="attr">autocomplete</span>=<span class="string">&quot;off&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span>&gt;</span>Send<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>CSS 代码略。</p><p>JavaScript 代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line">angular.<span class="title function_">module</span>(<span class="string">&#x27;chatApp&#x27;</span>, [])</span><br><span class="line">    .<span class="title function_">controller</span>(<span class="string">&#x27;ChatController&#x27;</span>, [<span class="string">&#x27;$scope&#x27;</span>, <span class="keyword">function</span> (<span class="params">$scope</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> socket = <span class="title function_">io</span>();</span><br><span class="line">        $scope.<span class="property">chatMessage</span> = [];</span><br><span class="line">        <span class="comment">// 接收事件</span></span><br><span class="line">        socket.<span class="title function_">on</span>(<span class="string">&#x27;chat message&#x27;</span>, <span class="keyword">function</span> (<span class="params">msg</span>) &#123;</span><br><span class="line">            $scope.<span class="property">chatMessage</span>.<span class="title function_">push</span>(msg);</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        $scope.<span class="property">submit</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">            <span class="comment">//发送事件</span></span><br><span class="line">            socket.<span class="title function_">emit</span>(<span class="string">&#x27;chat message&#x27;</span>, $scope.<span class="property">chatInput</span>);</span><br><span class="line">            $scope.<span class="property">chatInput</span> = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;]);</span><br></pre></td></tr></table></figure><p><a href="https://github.com/Leo555/socket.io-demo">完整demo地址</a></p><p><a href="http://socket.io">socket.io</a> 通过 socket.emit() 发送事件，通过 socket.on() 监听事件。</p><p>上面代码似乎没有什么问题，可是运行的时候总是发生视图不更新的情况。<br>debug 发现 $scope.chatMessage 的值已经发生改变了，按理说 Angular 的 model 与 view 是双向绑定的，model 改变 view 也应该随之更新才对啊，为什么会出现这种情况呢？</p><h2 id="分析">分析</h2><p>$scope.chatMessage 发生变化后，没有强制 $digest 循环，监视 chatMessage 的 $watch 没有执行，而我们自己执行一次 $apply，那么这些 $watch 就会看见这些变化，然后根据需要更新 DOM。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;从一个 demo 讲起&lt;/h1&gt;
&lt;p&gt;用 Angular + &lt;a href=&quot;http://socket.io&quot;&gt;socket.io&lt;/a&gt; 做了一个聊天 demo，消息通信没有问题，在 Angular 数据绑定的地方却栽了跟头：明明 model 已经发生了改变，在视图上就是看不到更新。&lt;/p&gt;
&lt;p&gt;后来仔细研究，通过使用 “$scope.$apply()” 解决了这个问题。&lt;/p&gt;
&lt;p&gt;之前对 Angular 数据双向绑定只有一个大概的印象，并没有深入地了解，正好趁这个机会好好学习一下数据绑定的过程。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Angular" scheme="https://lz5z.com/tags/Angular/"/>
    
    <category term="socket.io" scheme="https://lz5z.com/tags/socket-io/"/>
    
    <category term="$digest" scheme="https://lz5z.com/tags/digest/"/>
    
    <category term="$apply" scheme="https://lz5z.com/tags/apply/"/>
    
  </entry>
  
  <entry>
    <title>跨域实践</title>
    <link href="https://lz5z.com/%E8%B7%A8%E5%9F%9F%E5%AE%9E%E8%B7%B5/"/>
    <id>https://lz5z.com/%E8%B7%A8%E5%9F%9F%E5%AE%9E%E8%B7%B5/</id>
    <published>2016-12-15T17:04:17.000Z</published>
    <updated>2026-05-07T14:50:53.976Z</updated>
    
    <content type="html"><![CDATA[<h1>背景</h1><p>最近在 ITA 写了一个聊天机器人的 Flask 服务，自己写了一些 node 单元测试脚本跑没有问题，但是测试的同学也想覆盖到所有的 case，于是就帮忙写一个 html 页面去测试，然后就遇到了下面的问题：</p><blockquote><p>XMLHttpRequest cannot load <a href="http://localhost:8085/predict">http://localhost:8085/predict</a>. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘null’ is therefore not allowed access.</p></blockquote><p>这个是典型的跨域问题(跨域是指：协议、域名、端口有任何一个不同，都被当做是不同的域)，想想之前也了解过跨域的知识，现在借着这个机会总结一下了。关于 GET 请求的跨域，使用 JSONP 是目前最好的解决方案，各大浏览器也基本都支持 JSONP，而 jQuery，AngularJS 等前端框架也都默认添加了对 JSONP 的封装，并且这次遇到的跨域问题是 POST 请求的，于是暂时先不写关于 JSONP 的相关知识。</p><span id="more"></span><h2 id="简化代码">简化代码</h2><p>服务器代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask</span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Start server&#x27;</span>)</span><br><span class="line">    app = Flask(__name__)</span><br><span class="line">    <span class="comment"># 路由</span></span><br><span class="line"><span class="meta">    @app.route(<span class="params"><span class="string">&#x27;/predict&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">predict</span>():</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;result&#x27;</span></span><br><span class="line">    app.run(host=<span class="string">&#x27;0.0.0.0&#x27;</span>, port=<span class="number">8085</span>, debug=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>页面代码：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!doctype <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">ng-app</span>=<span class="string">&quot;chatApp&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ng-controller</span>=<span class="string">&quot;ChatController&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">ng-model</span>=<span class="string">&quot;chat&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;Enter content here&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">ng-click</span>=<span class="string">&quot;onclick()&quot;</span>&gt;</span>POST<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">p</span>&gt;</span> &#123;&#123; result &#125;&#125; <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>– 原谅我用 Angular 做页面 ☹</p><p>main.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">angular.<span class="title function_">module</span>(<span class="string">&#x27;chatApp&#x27;</span>, [])</span><br><span class="line">    .<span class="title function_">controller</span>(<span class="string">&#x27;ChatController&#x27;</span>, [<span class="string">&#x27;$scope&#x27;</span>, <span class="string">&#x27;$http&#x27;</span>, <span class="keyword">function</span>(<span class="params">$scope, $http</span>) &#123;</span><br><span class="line">        $scope.<span class="property">onclick</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            $http(&#123;</span><br><span class="line">                <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">                <span class="attr">url</span>: <span class="string">&#x27;http://localhost:8085/predict&#x27;</span></span><br><span class="line">            &#125;).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">                $scope.<span class="property">result</span> = data;</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="解决方案">解决方案</h2><p>要想解决跨域，必先理解跨域。那什么是跨域呢？<br>对于 web 开发来讲，由于浏览器的同源策略，我们需要经常使用一些 hack 的方法去跨域获取资源，直到 W3C 出了一个标准－<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS">CORS</a>－“跨域资源共享”（Cross-origin resource sharing），<br>它允许浏览器向跨源服务器，发出 XMLHttpRequest 请求，从而克服了 AJAX 只能同源使用的限制。</p><p>CORS 与 JSONP 的使用目的相同，但是比 JSONP 更强大。<br><strong>JSONP 只支持 GET 请求，CORS 支持所有类型的 HTTP 请求。JSONP 的优势在于支持老式浏览器，以及可以向不支持 CORS 的网站请求数据。</strong></p><p>CORS 解决方案：</p><p>(1) 服务器代码</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, Response, request</span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Start server&#x27;</span>)</span><br><span class="line">    app = Flask(__name__)</span><br><span class="line">    <span class="comment"># post</span></span><br><span class="line"><span class="meta">    @app.route(<span class="params"><span class="string">&#x27;/predict&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">predict</span>():</span><br><span class="line">        <span class="keyword">if</span> request.form.get(<span class="string">&#x27;content&#x27;</span>) <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            exp = <span class="string">&#x27;Missing content&#x27;</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            exp = request.form.get(<span class="string">&#x27;content&#x27;</span>)</span><br><span class="line">        <span class="built_in">print</span>(exp)</span><br><span class="line">        headers = &#123;<span class="string">&quot;Access-Control-Allow-Origin&quot;</span>: <span class="string">&quot;*&quot;</span>&#125;</span><br><span class="line">        <span class="keyword">return</span> Response(exp, headers=headers)</span><br><span class="line">    <span class="comment"># port=8085</span></span><br><span class="line">    app.run(host=<span class="string">&#x27;0.0.0.0&#x27;</span>, port=<span class="number">8085</span>, debug=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>(2) main.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">angular.<span class="title function_">module</span>(<span class="string">&#x27;chatApp&#x27;</span>, [])</span><br><span class="line">    .<span class="title function_">controller</span>(<span class="string">&#x27;ChatController&#x27;</span>, [<span class="string">&#x27;$scope&#x27;</span>, <span class="string">&#x27;$http&#x27;</span>, <span class="keyword">function</span>(<span class="params">$scope, $http</span>) &#123;</span><br><span class="line">        $scope.<span class="property">onclick</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            $http(&#123;</span><br><span class="line">                <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">                <span class="attr">url</span>: <span class="string">&#x27;http://localhost:8085/predict&#x27;</span>,</span><br><span class="line">                <span class="attr">headers</span>: &#123;</span><br><span class="line">                    <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span></span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="attr">data</span>: <span class="string">&#x27;content= &#x27;</span> + $scope.<span class="property">chat</span></span><br><span class="line"></span><br><span class="line">            &#125;).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">                $scope.<span class="property">result</span> = data.<span class="property">data</span>;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;]);</span><br></pre></td></tr></table></figure><p>此时再次发送 Ajax call就可以拿到结果了:</p><img src="/assets/img/cors-post.png" alt="cors-post"><p>注意到服务器端代码发生了一点改动，那就是在Response header中增加了一个参数 “Access-Control-Allow-Origin”，表示接受某域名的请求，“*” 表示允许所有的请求。<br>也可以使用确定的值，如： “<a href="http://api.abc.com">http://api.abc.com</a>”。</p><p>于是代码中增加 <strong>headers = {“Access-Control-Allow-Origin”: “*”}</strong> 后服务器就可以响应所有的请求了。</p><p>再看 Web 端的代码，我们在请求头里面添加了 “Content-Type”，为了能向服务端传递数据。这里使用的 “Content-Type” 为 “application/x-www-form-urlencoded” 表示以表单提交的形式传递参数。</p><p><strong>为什么要用表单的形式提交POST请求呢？</strong></p><h2 id="两种请求">两种请求</h2><p>浏览器将 CORS 请求分成两类：简单请求（simple request）和非简单请求（not-so-simple request）。</p><p>只要同时满足以下两大条件，就属于简单请求。</p><blockquote><p>(1) 请求方法是以下三种方法中的一个：</p></blockquote><ul><li><strong>HEAD</strong></li><li><strong>GET</strong></li><li><strong>POST</strong><br>(2) HTTP的头信息不超出以下几种字段：</li><li><strong>Accept</strong></li><li><strong>Accept-Language</strong></li><li><strong>Content-Language</strong></li><li><strong>Last-Event-ID</strong></li><li><strong>Content-Type</strong>  其值仅限于 <strong>application/x-www-form-urlencoded、multipart/form-data、text/plain</strong></li></ul><p>上文中的请求属于简单请求。</p><h3 id="简单请求（simple-request）">简单请求（simple request）</h3><p>对于简单的跨域请求，浏览器会自动在请求的头信息加上 Origin 字段，表示本次请求来自哪个源（协议 + 域名 + 端口），服务端会获取到这个值，然后判断是否同意这次请求并返回。</p><blockquote><p>// 请求<br>GET /cors HTTP/1.1<br>Origin: <a href="http://api.abc.com">http://api.abc.com</a><br>Host: <a href="http://api.bcd.com">api.bcd.com</a><br>Accept-Language: en-US<br>Connection: keep-alive<br>User-Agent: Mozilla/5.0…</p></blockquote><p>如果服务端许可本次请求，就会在返回的头信息多出关于 <strong>Access-Control</strong> 的信息，比如上述服务器返回的信息：</p><img src="/assets/img/cors-res-header.png" alt="cors-res-header"><h3 id="非简单请求（not-so-simple-request）">非简单请求（not-so-simple request）</h3><p>非简单请求是那种对服务器有特殊要求的请求，比如请求方法是 PUT 或 DELETE，或者 Content-Type 字段的类型是 application/json。</p><p>非简单请求的 CORS 请求，会在正式通信之前，增加一次 HTTP 查询请求，称为“预检”请求（preflight）。<br>浏览器先询问服务器，当前网页所在的域名是否在服务器的许可名单之中，以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复，浏览器才会发出正式的 XMLHttpRequest 请求，否则就报错。</p><p>“预检”请求用的请求方法是 <strong>OPTIONS</strong>，表示这个请求是用来询问的。头信息里面，关键字段是Origin，表示请求来自哪个源。</p><h2 id="非简单请求解决方案">非简单请求解决方案</h2><p>项目中使用的 Content-Type 为 <strong>application/json</strong>，属于非简单请求，将上述程序修改为</p><p>(1) main.js:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">angular.<span class="title function_">module</span>(<span class="string">&#x27;chatApp&#x27;</span>, [])</span><br><span class="line">    .<span class="title function_">controller</span>(<span class="string">&#x27;ChatController&#x27;</span>, [<span class="string">&#x27;$scope&#x27;</span>, <span class="string">&#x27;$http&#x27;</span>, <span class="keyword">function</span>(<span class="params">$scope, $http</span>) &#123;</span><br><span class="line">        $scope.<span class="property">onclick</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            $http(&#123;</span><br><span class="line">                <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">                <span class="attr">url</span>: <span class="string">&#x27;http://localhost:8086/predict&#x27;</span>,</span><br><span class="line">                <span class="attr">headers</span>: &#123;</span><br><span class="line">                    <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="attr">data</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">                    <span class="string">&#x27;content&#x27;</span>: $scope.<span class="property">chat</span></span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">                $scope.<span class="property">result</span> = data.<span class="property">data</span>;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;]);</span><br></pre></td></tr></table></figure><p>服务器代码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, Response, request</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Start server&#x27;</span>)</span><br><span class="line">    app = Flask(__name__)</span><br><span class="line">    <span class="comment"># 路由</span></span><br><span class="line"><span class="meta">    @app.route(<span class="params"><span class="string">&#x27;/predict&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>, <span class="string">&#x27;OPTIONS&#x27;</span>]</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">predict</span>():</span><br><span class="line">    <span class="comment"># 返回头</span></span><br><span class="line">        headers = &#123;<span class="string">&quot;Access-Control-Allow-Origin&quot;</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">                   <span class="string">&quot;Access-Control-Allow-Headers&quot;</span>: <span class="string">&quot;Origin, X-Requested-With, Content-Type&quot;</span>,</span><br><span class="line">                   <span class="string">&quot;Access-Control-Allow-Methods&quot;</span>: <span class="string">&quot;POST, PUT, GET, OPTIONS, DELETE&quot;</span>&#125;</span><br><span class="line">        <span class="comment"># preflight</span></span><br><span class="line">        <span class="keyword">if</span> request.method == <span class="string">&#x27;OPTIONS&#x27;</span>:</span><br><span class="line">            <span class="keyword">return</span> Response(headers=headers)</span><br><span class="line">        <span class="comment"># request</span></span><br><span class="line">        <span class="keyword">if</span> <span class="string">&#x27;content&#x27;</span> <span class="keyword">in</span> request.json:</span><br><span class="line">            exp = request.json.get(<span class="string">&#x27;content&#x27;</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            exp = <span class="string">&#x27;Missing content&#x27;</span></span><br><span class="line">        <span class="built_in">print</span>(exp)</span><br><span class="line">        <span class="keyword">return</span> Response(exp, headers=headers)</span><br><span class="line">    <span class="comment"># run server</span></span><br><span class="line">    app.run(host=<span class="string">&#x27;0.0.0.0&#x27;</span>, port=<span class="number">8086</span>, debug=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>启动后发送请求，发现可以跑通，但是获取不到参数，原因是使用 <strong>application/json</strong> 的形式发送 request， 参数并没有放在 form 里面，而是放在 request.data 里面了。<br>request.data 里面为 bytes 类型的数据，通过 request.json 可以获取其 dict 类型。</p><p>通过以上方式，完美地解决了复杂请求的跨域问题。</p><p>才怪嘞！！！♋</p><h2 id="问题所在">问题所在</h2><p>以上解决跨域的方式为 CORS，准确地说，这是一种服务器端的技术。而现实生产环境中，如果一个前端想要用这种方式实现跨域，不知道要跟后端做多少沟通，那有没有纯前端的解决方案呢？<br>且听下回分解。☛</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;背景&lt;/h1&gt;
&lt;p&gt;最近在 ITA 写了一个聊天机器人的 Flask 服务，自己写了一些 node 单元测试脚本跑没有问题，但是测试的同学也想覆盖到所有的 case，于是就帮忙写一个 html 页面去测试，然后就遇到了下面的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;XMLHttpRequest cannot load &lt;a href=&quot;http://localhost:8085/predict&quot;&gt;http://localhost:8085/predict&lt;/a&gt;. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘null’ is therefore not allowed access.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个是典型的跨域问题(跨域是指：协议、域名、端口有任何一个不同，都被当做是不同的域)，想想之前也了解过跨域的知识，现在借着这个机会总结一下了。关于 GET 请求的跨域，使用 JSONP 是目前最好的解决方案，各大浏览器也基本都支持 JSONP，而 jQuery，AngularJS 等前端框架也都默认添加了对 JSONP 的封装，并且这次遇到的跨域问题是 POST 请求的，于是暂时先不写关于 JSONP 的相关知识。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="HTTP" scheme="https://lz5z.com/tags/HTTP/"/>
    
    <category term="Flask" scheme="https://lz5z.com/tags/Flask/"/>
    
    <category term="CORS" scheme="https://lz5z.com/tags/CORS/"/>
    
    <category term="跨域" scheme="https://lz5z.com/tags/%E8%B7%A8%E5%9F%9F/"/>
    
  </entry>
  
  <entry>
    <title>Pandas 数据处理学习</title>
    <link href="https://lz5z.com/Pandas%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E5%AD%A6%E4%B9%A0/"/>
    <id>https://lz5z.com/Pandas%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E5%AD%A6%E4%B9%A0/</id>
    <published>2016-12-07T13:07:46.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h1>10分钟 Pandas 入门</h1><p>Pandas 是 Python 做数据分析最重要的模块之一，本文源自Pandas 作者 Wes McKinney 写的 <a href="http://pandas.pydata.org/pandas-docs/stable/10min.html">10-minute tour of pandas</a>。</p><p>首先安装 Pandas 和相关的两个包 numpy、matplotlib</p><span id="more"></span><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pip install pandas</span><br><span class="line">pip install numpy</span><br><span class="line">pip install matplotlib</span><br></pre></td></tr></table></figure><p>导入 pandas、numpy、matplotlib</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pandas <span class="keyword">as</span> pd</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br></pre></td></tr></table></figure><h2 id="对象创建">对象创建</h2><p>Series 是一个序列，使用 Pandas 创建一个整数索引的序列：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>s = pd.Series([<span class="number">1</span>,<span class="number">3</span>,<span class="number">5</span>,np.nan,<span class="number">6</span>,<span class="number">8</span>])</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>s</span><br><span class="line"><span class="number">0</span>    <span class="number">1.0</span></span><br><span class="line"><span class="number">1</span>    <span class="number">3.0</span></span><br><span class="line"><span class="number">2</span>    <span class="number">5.0</span></span><br><span class="line"><span class="number">3</span>    NaN</span><br><span class="line"><span class="number">4</span>    <span class="number">6.0</span></span><br><span class="line"><span class="number">5</span>    <span class="number">8.0</span></span><br><span class="line">dtype: float64</span><br></pre></td></tr></table></figure><p>DataFrame 是有多个列的数据表，每个列拥有一个 label，当然，DataFrame 也有索引:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>dates = pd.date_range(<span class="string">&#x27;20170101&#x27;</span>, periods=<span class="number">6</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dates</span><br><span class="line">DatetimeIndex([<span class="string">&#x27;2017-01-01&#x27;</span>, <span class="string">&#x27;2017-01-02&#x27;</span>, <span class="string">&#x27;2017-01-03&#x27;</span>, <span class="string">&#x27;2017-01-04&#x27;</span>,</span><br><span class="line">               <span class="string">&#x27;2017-01-05&#x27;</span>, <span class="string">&#x27;2017-01-06&#x27;</span>],</span><br><span class="line">              dtype=<span class="string">&#x27;datetime64[ns]&#x27;</span>, freq=<span class="string">&#x27;D&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df = pd.DataFrame(np.random.randn(<span class="number">6</span>,<span class="number">4</span>), index=dates, columns=<span class="built_in">list</span>(<span class="string">&#x27;ABCD&#x27;</span>))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>&gt;&gt;&gt; df.shape</span><br><span class="line">(<span class="number">6</span>, <span class="number">5</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df</span><br><span class="line">                   A         B         C         D</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span>  <span class="number">0.143952</span>  <span class="number">0.831411</span></span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span> -<span class="number">0.104664</span>  <span class="number">1.265863</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span> -<span class="number">0.741193</span>  <span class="number">0.129071</span></span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span>  <span class="number">0.965653</span>  <span class="number">1.522392</span>  <span class="number">1.129707</span></span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span>  <span class="number">0.078448</span>  <span class="number">0.217550</span></span><br><span class="line"><span class="number">2017</span>-01-06  <span class="number">0.717721</span>  <span class="number">0.344734</span> -<span class="number">0.951364</span>  <span class="number">0.362032</span></span><br></pre></td></tr></table></figure><p>通过一个对象字典创建 DataFrame， dict 的每个 value 会被转化成一个 Series：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2 = pd.DataFrame(&#123; <span class="string">&#x27;A&#x27;</span> : <span class="number">1.</span>, </span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span> <span class="string">&#x27;B&#x27;</span> : pd.Timestamp(<span class="string">&#x27;20170102&#x27;</span>), </span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span> <span class="string">&#x27;C&#x27;</span> : pd.Series(<span class="number">1</span>,index=<span class="built_in">list</span>(<span class="built_in">range</span>(<span class="number">4</span>)),dtype=<span class="string">&#x27;float32&#x27;</span>), </span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span> <span class="string">&#x27;D&#x27;</span> : np.array([<span class="number">3</span>] * <span class="number">4</span>,dtype=<span class="string">&#x27;int32&#x27;</span>), </span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span> <span class="string">&#x27;E&#x27;</span> : pd.Categorical([<span class="string">&quot;test&quot;</span>,<span class="string">&quot;train&quot;</span>,<span class="string">&quot;test&quot;</span>,<span class="string">&quot;train&quot;</span>]), </span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span> <span class="string">&#x27;F&#x27;</span> : <span class="string">&#x27;foo&#x27;</span> &#125;)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2</span><br><span class="line">     A          B    C  D      E    F</span><br><span class="line"><span class="number">0</span>  <span class="number">1.0</span> <span class="number">2017</span>-01-02  <span class="number">1.0</span>  <span class="number">3</span>   test  foo</span><br><span class="line"><span class="number">1</span>  <span class="number">1.0</span> <span class="number">2017</span>-01-02  <span class="number">1.0</span>  <span class="number">3</span>  train  foo</span><br><span class="line"><span class="number">2</span>  <span class="number">1.0</span> <span class="number">2017</span>-01-02  <span class="number">1.0</span>  <span class="number">3</span>   test  foo</span><br><span class="line"><span class="number">3</span>  <span class="number">1.0</span> <span class="number">2017</span>-01-02  <span class="number">1.0</span>  <span class="number">3</span>  train  foo</span><br></pre></td></tr></table></figure><p>查看每列的格式：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2.dtypes</span><br><span class="line">A           float64</span><br><span class="line">B    datetime64[ns]</span><br><span class="line">C           float32</span><br><span class="line">D             int32</span><br><span class="line">E          category</span><br><span class="line">F            <span class="built_in">object</span></span><br><span class="line">dtype: <span class="built_in">object</span></span><br></pre></td></tr></table></figure><p>查看某一列的具体值</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2.C</span><br><span class="line"><span class="number">0</span>    <span class="number">1.0</span></span><br><span class="line"><span class="number">1</span>    <span class="number">1.0</span></span><br><span class="line"><span class="number">2</span>    <span class="number">1.0</span></span><br><span class="line"><span class="number">3</span>    <span class="number">1.0</span></span><br><span class="line">Name: C, dtype: float32</span><br></pre></td></tr></table></figure><h2 id="查看数据">查看数据</h2><p>使用 head() 查看 DataFrame 前几行； tail() 查看后几行：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.head(<span class="number">3</span>)</span><br><span class="line">                   A         B         C         D</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span>  <span class="number">0.143952</span>  <span class="number">0.831411</span></span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span> -<span class="number">0.104664</span>  <span class="number">1.265863</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span> -<span class="number">0.741193</span>  <span class="number">0.129071</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.tail(<span class="number">3</span>)</span><br><span class="line">                   A         B         C         D</span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span>  <span class="number">0.965653</span>  <span class="number">1.522392</span>  <span class="number">1.129707</span></span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span>  <span class="number">0.078448</span>  <span class="number">0.217550</span></span><br><span class="line"><span class="number">2017</span>-01-06  <span class="number">0.717721</span>  <span class="number">0.344734</span> -<span class="number">0.951364</span>  <span class="number">0.362032</span></span><br></pre></td></tr></table></figure><p>实际上，DataFrame 内部用 numpy 格式存储数据。你也可以单独查看 index、columns 和 values：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.index</span><br><span class="line">DatetimeIndex([<span class="string">&#x27;2017-01-01&#x27;</span>, <span class="string">&#x27;2017-01-02&#x27;</span>, <span class="string">&#x27;2017-01-03&#x27;</span>, <span class="string">&#x27;2017-01-04&#x27;</span>,</span><br><span class="line">               <span class="string">&#x27;2017-01-05&#x27;</span>, <span class="string">&#x27;2017-01-06&#x27;</span>],</span><br><span class="line">              dtype=<span class="string">&#x27;datetime64[ns]&#x27;</span>, freq=<span class="string">&#x27;D&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.columns</span><br><span class="line">Index([<span class="string">&#x27;A&#x27;</span>, <span class="string">&#x27;B&#x27;</span>, <span class="string">&#x27;C&#x27;</span>, <span class="string">&#x27;D&#x27;</span>], dtype=<span class="string">&#x27;object&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.values</span><br><span class="line">array([[ <span class="number">0.14707226</span>,  <span class="number">1.23522557</span>,  <span class="number">0.14395236</span>,  <span class="number">0.83141137</span>],</span><br><span class="line">       [ <span class="number">0.86229302</span>, -<span class="number">0.72510256</span>, -<span class="number">0.10466379</span>,  <span class="number">1.26586314</span>],</span><br><span class="line">       [ <span class="number">0.28151127</span>,  <span class="number">0.95686785</span>, -<span class="number">0.74119266</span>,  <span class="number">0.12907115</span>],</span><br><span class="line">       [-<span class="number">0.66447533</span>,  <span class="number">0.96565318</span>,  <span class="number">1.52239163</span>,  <span class="number">1.12970702</span>],</span><br><span class="line">       [-<span class="number">1.36453175</span>, -<span class="number">0.16787707</span>,  <span class="number">0.07844812</span>,  <span class="number">0.21755034</span>],</span><br><span class="line">       [ <span class="number">0.71772123</span>,  <span class="number">0.34473429</span>, -<span class="number">0.95136372</span>,  <span class="number">0.36203183</span>]])              </span><br></pre></td></tr></table></figure><p>使用 describe() 可以帮你做一些数据的概要</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.describe()</span><br><span class="line">              A         B         C         D</span><br><span class="line">count  <span class="number">6.000000</span>  <span class="number">6.000000</span>  <span class="number">6.000000</span>  <span class="number">6.000000</span></span><br><span class="line">mean  -<span class="number">0.003402</span>  <span class="number">0.434917</span> -<span class="number">0.008738</span>  <span class="number">0.655939</span></span><br><span class="line">std    <span class="number">0.855916</span>  <span class="number">0.763118</span>  <span class="number">0.872870</span>  <span class="number">0.486500</span></span><br><span class="line"><span class="built_in">min</span>   -<span class="number">1.364532</span> -<span class="number">0.725103</span> -<span class="number">0.951364</span>  <span class="number">0.129071</span></span><br><span class="line"><span class="number">25</span>%   -<span class="number">0.461588</span> -<span class="number">0.039724</span> -<span class="number">0.582060</span>  <span class="number">0.253671</span></span><br><span class="line"><span class="number">50</span>%    <span class="number">0.214292</span>  <span class="number">0.650801</span> -<span class="number">0.013108</span>  <span class="number">0.596722</span></span><br><span class="line"><span class="number">75</span>%    <span class="number">0.608669</span>  <span class="number">0.963457</span>  <span class="number">0.127576</span>  <span class="number">1.055133</span></span><br><span class="line"><span class="built_in">max</span>    <span class="number">0.862293</span>  <span class="number">1.235226</span>  <span class="number">1.522392</span>  <span class="number">1.265863</span></span><br></pre></td></tr></table></figure><p>DataFrame 的矩阵转置</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.T</span><br></pre></td></tr></table></figure><p>DataFrame 排序</p><p>（1） 使用 sort_index 按照索引排序<br>ascending 参数默认值为 True<br>axis = 0 指的是安装行排序，axis = 1 是指安装列排序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.sort_index(axis=<span class="number">1</span>, ascending=<span class="literal">False</span>)</span><br></pre></td></tr></table></figure><p>（2） 使用 sort_values 按照值排序</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.sort_values(by=<span class="string">&#x27;B&#x27;</span>, ascending=<span class="literal">False</span>)</span><br></pre></td></tr></table></figure><h2 id="选择">选择</h2><h3 id="行-列">行/列</h3><p>选择单独的列：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df[<span class="string">&#x27;A&#x27;</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.A</span><br></pre></td></tr></table></figure><p>切片，使用[]选择特定的行</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df[<span class="number">0</span>:<span class="number">3</span>]</span><br><span class="line">                   A         B         C         D</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span>  <span class="number">0.143952</span>  <span class="number">0.831411</span></span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span> -<span class="number">0.104664</span>  <span class="number">1.265863</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span> -<span class="number">0.741193</span>  <span class="number">0.129071</span></span><br></pre></td></tr></table></figure><h3 id="通过-label-选择">通过 label 选择</h3><p>通过 label 选择(dates[0]=Timestamp(‘2017-01-01 00:00:00’, offset=‘D’))</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.loc[dates[<span class="number">0</span>]]</span><br><span class="line">A    <span class="number">0.147072</span></span><br><span class="line">B    <span class="number">1.235226</span></span><br><span class="line">C    <span class="number">0.143952</span></span><br><span class="line">D    <span class="number">0.831411</span></span><br></pre></td></tr></table></figure><p>多选，「A：B」 表示从 A 到 B</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.loc[:,[<span class="string">&#x27;A&#x27;</span>,<span class="string">&#x27;B&#x27;</span>]]</span><br><span class="line">                   A         B</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span></span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span></span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span>  <span class="number">0.965653</span></span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span></span><br><span class="line"><span class="number">2017</span>-01-06  <span class="number">0.717721</span>  <span class="number">0.344734</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.loc[<span class="string">&#x27;20170102&#x27;</span>:<span class="string">&#x27;20170104&#x27;</span>,[<span class="string">&#x27;A&#x27;</span>,<span class="string">&#x27;B&#x27;</span>]]</span><br><span class="line">                   A         B</span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span></span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span>  <span class="number">0.965653</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.loc[<span class="string">&#x27;20170102&#x27;</span>,[<span class="string">&#x27;A&#x27;</span>,<span class="string">&#x27;B&#x27;</span>]]</span><br><span class="line">A    <span class="number">0.862293</span></span><br><span class="line">B   -<span class="number">0.725103</span></span><br><span class="line">Name: <span class="number">2017</span>-01-02 <span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span>, dtype: float64</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.at[dates[<span class="number">0</span>],<span class="string">&#x27;A&#x27;</span>]</span><br><span class="line"><span class="number">0.14707225966646126</span></span><br></pre></td></tr></table></figure><h3 id="通过下标选择">通过下标选择</h3><p>选择第四行所有元素</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.iloc[<span class="number">3</span>]</span><br><span class="line">A   -<span class="number">0.664475</span></span><br><span class="line">B    <span class="number">0.965653</span></span><br><span class="line">C    <span class="number">1.522392</span></span><br><span class="line">D    <span class="number">1.129707</span></span><br></pre></td></tr></table></figure><p>选出3<sub>4行，0</sub>1列</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.iloc[<span class="number">3</span>:<span class="number">5</span>,<span class="number">0</span>:<span class="number">2</span>]</span><br><span class="line">                   A         B</span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span>  <span class="number">0.965653</span></span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span></span><br></pre></td></tr></table></figure><p>选择单个元素</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.iloc[<span class="number">1</span>,<span class="number">1</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.iat[<span class="number">1</span>,<span class="number">1</span>] </span><br></pre></td></tr></table></figure><h3 id="比较运算">比较运算</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df[df.A &gt; <span class="number">0</span>]</span><br><span class="line">                   A         B         C         D</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span>  <span class="number">0.143952</span>  <span class="number">0.831411</span></span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span> -<span class="number">0.104664</span>  <span class="number">1.265863</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span> -<span class="number">0.741193</span>  <span class="number">0.129071</span></span><br><span class="line"><span class="number">2017</span>-01-06  <span class="number">0.717721</span>  <span class="number">0.344734</span> -<span class="number">0.951364</span>  <span class="number">0.362032</span></span><br></pre></td></tr></table></figure><p>选出大于0 的全部元素，没有填充的值等于 NaN</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df[df &gt; <span class="number">0</span>]</span><br><span class="line">                   A         B         C         D</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span>  <span class="number">0.143952</span>  <span class="number">0.831411</span></span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span>       NaN       NaN  <span class="number">1.265863</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span>       NaN  <span class="number">0.129071</span></span><br><span class="line"><span class="number">2017</span>-01-04       NaN  <span class="number">0.965653</span>  <span class="number">1.522392</span>  <span class="number">1.129707</span></span><br><span class="line"><span class="number">2017</span>-01-05       NaN       NaN  <span class="number">0.078448</span>  <span class="number">0.217550</span></span><br><span class="line"><span class="number">2017</span>-01-06  <span class="number">0.717721</span>  <span class="number">0.344734</span>       NaN  <span class="number">0.362032</span></span><br></pre></td></tr></table></figure><p>isin() 函数：是否在集合中</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2 = df.copy()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2[<span class="string">&#x27;E&#x27;</span>] = [<span class="string">&#x27;one&#x27;</span>, <span class="string">&#x27;one&#x27;</span>,<span class="string">&#x27;two&#x27;</span>,<span class="string">&#x27;three&#x27;</span>,<span class="string">&#x27;four&#x27;</span>,<span class="string">&#x27;three&#x27;</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2</span><br><span class="line">                   A         B         C         D      E</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span>  <span class="number">0.143952</span>  <span class="number">0.831411</span>    one</span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span> -<span class="number">0.104664</span>  <span class="number">1.265863</span>    one</span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span> -<span class="number">0.741193</span>  <span class="number">0.129071</span>    two</span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span>  <span class="number">0.965653</span>  <span class="number">1.522392</span>  <span class="number">1.129707</span>  three</span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span>  <span class="number">0.078448</span>  <span class="number">0.217550</span>   four</span><br><span class="line"><span class="number">2017</span>-01-06  <span class="number">0.717721</span>  <span class="number">0.344734</span> -<span class="number">0.951364</span>  <span class="number">0.362032</span>  three</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2[df2[<span class="string">&#x27;E&#x27;</span>].isin([<span class="string">&#x27;two&#x27;</span>,<span class="string">&#x27;four&#x27;</span>])]</span><br><span class="line">                   A         B         C         D     E</span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span> -<span class="number">0.741193</span>  <span class="number">0.129071</span>   two</span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span>  <span class="number">0.078448</span>  <span class="number">0.217550</span>  four</span><br></pre></td></tr></table></figure><h3 id="设置">设置</h3><p>按照 index 给 DataFrame 添加新的列：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>s1 = pd.Series([<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>], index=pd.date_range(<span class="string">&#x27;20170102&#x27;</span>, periods=<span class="number">6</span>))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>s1</span><br><span class="line"><span class="number">2017</span>-01-02    <span class="number">1</span></span><br><span class="line"><span class="number">2017</span>-01-03    <span class="number">2</span></span><br><span class="line"><span class="number">2017</span>-01-04    <span class="number">3</span></span><br><span class="line"><span class="number">2017</span>-01-05    <span class="number">4</span></span><br><span class="line"><span class="number">2017</span>-01-06    <span class="number">5</span></span><br><span class="line"><span class="number">2017</span>-01-07    <span class="number">6</span></span><br><span class="line">Freq: D, dtype: int64</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df[<span class="string">&#x27;F&#x27;</span>] = s1</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df</span><br><span class="line">                   A         B         C         D    F</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.147072</span>  <span class="number">1.235226</span>  <span class="number">0.143952</span>  <span class="number">0.831411</span>  NaN</span><br><span class="line"><span class="number">2017</span>-01-02  <span class="number">0.862293</span> -<span class="number">0.725103</span> -<span class="number">0.104664</span>  <span class="number">1.265863</span>  <span class="number">1.0</span></span><br><span class="line"><span class="number">2017</span>-01-03  <span class="number">0.281511</span>  <span class="number">0.956868</span> -<span class="number">0.741193</span>  <span class="number">0.129071</span>  <span class="number">2.0</span></span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span>  <span class="number">0.965653</span>  <span class="number">1.522392</span>  <span class="number">1.129707</span>  <span class="number">3.0</span></span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span>  <span class="number">0.078448</span>  <span class="number">0.217550</span>  <span class="number">4.0</span></span><br><span class="line"><span class="number">2017</span>-01-06  <span class="number">0.717721</span>  <span class="number">0.344734</span> -<span class="number">0.951364</span>  <span class="number">0.362032</span>  <span class="number">5.0</span></span><br></pre></td></tr></table></figure><p>通过 label 设置</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.at[dates[<span class="number">0</span>],<span class="string">&#x27;A&#x27;</span>] = <span class="number">0</span>  </span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df[<span class="string">&#x27;A&#x27;</span>]</span><br><span class="line"><span class="number">2017</span>-01-01    <span class="number">0.000000</span></span><br><span class="line"><span class="number">2017</span>-01-02    <span class="number">0.862293</span></span><br><span class="line"><span class="number">2017</span>-01-03    <span class="number">0.281511</span></span><br><span class="line"><span class="number">2017</span>-01-04   -<span class="number">0.664475</span></span><br><span class="line"><span class="number">2017</span>-01-05   -<span class="number">1.364532</span></span><br><span class="line"><span class="number">2017</span>-01-06    <span class="number">0.717721</span></span><br></pre></td></tr></table></figure><p>通过下标设置</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.iat[<span class="number">0</span>,<span class="number">1</span>] = <span class="number">0</span></span><br></pre></td></tr></table></figure><p>用 numpy 数组设置</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.loc[:,<span class="string">&#x27;D&#x27;</span>] = np.array([<span class="number">5</span>] * <span class="built_in">len</span>(df))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df.D</span><br><span class="line"><span class="number">2017</span>-01-01    <span class="number">5</span></span><br><span class="line"><span class="number">2017</span>-01-02    <span class="number">5</span></span><br><span class="line"><span class="number">2017</span>-01-03    <span class="number">5</span></span><br><span class="line"><span class="number">2017</span>-01-04    <span class="number">5</span></span><br><span class="line"><span class="number">2017</span>-01-05    <span class="number">5</span></span><br><span class="line"><span class="number">2017</span>-01-06    <span class="number">5</span></span><br></pre></td></tr></table></figure><p>使用比较设置</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2 = df.copy()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2[df2 &gt; <span class="number">0</span>] = -df2</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>df2</span><br><span class="line">                   A         B         C  D    F</span><br><span class="line"><span class="number">2017</span>-01-01  <span class="number">0.000000</span> -<span class="number">1.000000</span> -<span class="number">0.143952</span> -<span class="number">5</span>  NaN</span><br><span class="line"><span class="number">2017</span>-01-02 -<span class="number">0.862293</span> -<span class="number">0.725103</span> -<span class="number">0.104664</span> -<span class="number">5</span> -<span class="number">1.0</span></span><br><span class="line"><span class="number">2017</span>-01-03 -<span class="number">0.281511</span> -<span class="number">0.956868</span> -<span class="number">0.741193</span> -<span class="number">5</span> -<span class="number">2.0</span></span><br><span class="line"><span class="number">2017</span>-01-04 -<span class="number">0.664475</span> -<span class="number">0.965653</span> -<span class="number">1.522392</span> -<span class="number">5</span> -<span class="number">3.0</span></span><br><span class="line"><span class="number">2017</span>-01-05 -<span class="number">1.364532</span> -<span class="number">0.167877</span> -<span class="number">0.078448</span> -<span class="number">5</span> -<span class="number">4.0</span></span><br><span class="line"><span class="number">2017</span>-01-06 -<span class="number">0.717721</span> -<span class="number">0.344734</span> -<span class="number">0.951364</span> -<span class="number">5</span> -<span class="number">5.0</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h1&gt;10分钟 Pandas 入门&lt;/h1&gt;
&lt;p&gt;Pandas 是 Python 做数据分析最重要的模块之一，本文源自Pandas 作者 Wes McKinney 写的 &lt;a href=&quot;http://pandas.pydata.org/pandas-docs/stable/10min.html&quot;&gt;10-minute tour of pandas&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;首先安装 Pandas 和相关的两个包 numpy、matplotlib&lt;/p&gt;</summary>
    
    
    
    <category term="Python" scheme="https://lz5z.com/categories/Python/"/>
    
    
    <category term="Python" scheme="https://lz5z.com/tags/Python/"/>
    
    <category term="Pandas" scheme="https://lz5z.com/tags/Pandas/"/>
    
  </entry>
  
  <entry>
    <title>深入学习 JavaScript——继承</title>
    <link href="https://lz5z.com/JavaScript%E7%BB%A7%E6%89%BF/"/>
    <id>https://lz5z.com/JavaScript%E7%BB%A7%E6%89%BF/</id>
    <published>2016-12-05T15:50:08.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<h1>继承</h1><p>继承是面向对象语言中最重要的概念之一，许多 OO 语言都支持两种继承方式：接口继承和实现继承。接口继承只继承方法签名，而实现继承则继承实际的方法。由于 ECMAScript 中没有方法签名，所以不能实现接口继承，而是通过原型链的方式完成实现继承。</p><h2 id="原型链">原型链</h2><p>每个构造函数都有一个原型对象，原型对象包含一个指向构造函数的指针，而所有实例中都包含一个指向原型对象的内部指针。下面是一个实现原型链的基本方法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">SuperType</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">property</span> = <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">SuperType</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">getSuperValue</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">property</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">SubType</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">subProperty</span> = <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 继承了 SuperType</span></span><br><span class="line"><span class="title class_">SubType</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">SuperType</span>()</span><br><span class="line"><span class="title class_">SubType</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">getSubValue</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">subProperty</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> instance = <span class="keyword">new</span> <span class="title class_">SubType</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(instance.<span class="title function_">getSuperValue</span>()) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>上述代码定义了 SuperType 和 SubType 两种类型，每个类型分别有一个属性和一个方法，SubType 通过改写原型对象的方式实现对 SuperType 的继承。原来存在于 SuperType 中的属性和方法，现在也存在于 SubType.prototype 中。在确立了继承关系后，我们给 SubType.prototype 又添加了一个新方法，这个例子中的关系图如下：</p><img src="/assets/img/js_inherit.png" alt="js_inherit"><p>在上述代码中，我们修改 SubType 默认的原型为 SuperType 的实例，新原型不仅具有作为一个 SuperType 的实例所拥有的全部属性和方法，而且其内部还有一个指针，指向了 SuperType 的原型。最终的结果是这样的：instance 指向了 SubType 的原型，SubType 的原型又指向了 SuperType 的原型。</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></pre></td><td class="code"><pre><span class="line">instance.<span class="property">__proto__</span> === <span class="title class_">SubType</span>.<span class="property"><span class="keyword">prototype</span></span> <span class="comment">// true</span></span><br><span class="line"><span class="title class_">SubType</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">__proto__</span> === <span class="title class_">SuperType</span>.<span class="property"><span class="keyword">prototype</span></span> <span class="comment">//true</span></span><br><span class="line">instance.<span class="property">__proto__</span>.<span class="property">__proto__</span> === <span class="title class_">SuperType</span>.<span class="property"><span class="keyword">prototype</span></span> <span class="comment">//true</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;继承&lt;/h1&gt;
&lt;p&gt;继承是面向对象语言中最重要的概念之一，许多 OO 语言都支持两种继承方式：接口继承和实现继承。接口继承只继承方法签名，而实现继承则继承实际的方法。由于 ECMAScript 中没有方法签名，所以不能实现接口继承，而是通过原型链的方式完成实现继承。&lt;/</summary>
      
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="面向对象" scheme="https://lz5z.com/tags/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/"/>
    
    <category term="Object-Oriented" scheme="https://lz5z.com/tags/Object-Oriented/"/>
    
    <category term="继承" scheme="https://lz5z.com/tags/%E7%BB%A7%E6%89%BF/"/>
    
  </entry>
  
  <entry>
    <title>深入学习 JavaScript——理解原型</title>
    <link href="https://lz5z.com/JavaScript-prototype/"/>
    <id>https://lz5z.com/JavaScript-prototype/</id>
    <published>2016-12-02T05:21:53.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<h2 id="理解原型对象">理解原型对象</h2><p>在 JavaScript 中，只要创建了新函数，都会根据一组特定的规则为该函数创建一个 prototype 属性，这个属性指向函数的原型对象。默认情况下，所有原型对象都会自动获取一个 constructor（构造函数）属性，这个属性包含一个指向 prototype 属性所在函数的指针。比如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">constructor</span> === <span class="title class_">Person</span></span><br></pre></td></tr></table></figure><p>通过 constructor，我们可以继续为原型对象添加其他属性和方法。</p><p>创建自定义的构造函数之后，其原型对象默认只会取得 constructor 属性，其它属性和方法都是从 Object 继承而来的。</p><p>当调用构造函数创建一个新实例后，该实例的内部将包含一个指针（[[Prototype]]），指向构造函数的原型对象，该指针在常用的浏览器中被定义为 <code>__proto__</code>。需要说明的一点是，该连接存在于实例和构造函数的原型对象之间，而不是存在于原型和构造函数之间。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> leo = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line">leo.<span class="property">__proto__</span> === <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span></span><br></pre></td></tr></table></figure><span id="more"></span><h3 id="关系">关系</h3><p>构造函数，实例，prototype，<code>__proto__</code> 之间的关系可以理解为下图：</p><img src="/assets/img/js_prototype.png" alt="js_prototype"><p>注意：<code>__proto__</code> 并非 JS 标准属性，而是浏览器的实现。</p><p>从图中可以看出构造函数 Person 和实例 leo 之间并没有直接关系，而是通过 Person.prototype 原型对象进行关联。虽然实例中并不包含属性和方法，但是可以通过调用 <code>leo.sayName</code> 进行调用。在非浏览器环境或者浏览器不支持 <code>__proto__</code> 的环境中，我们可以通过 isPrototypeOf() 方法来确定对象之间是否存在这种关系。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="title function_">isPrototypeOf</span>(leo) <span class="comment">// true</span></span><br><span class="line">leo.<span class="property">__proto__</span> === <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span> <span class="comment">// 一些浏览器可能不支持</span></span><br></pre></td></tr></table></figure><p>ECMAScript5 中增加了 Object.getPrototypeOf() 方法，该方法返回 [[Prototype]] 的值。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">getPrototypeOf</span>(leo) === <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span></span><br></pre></td></tr></table></figure><p>每当代码读取某个对象的属性时，都会执行一次搜索：首先判断实例是否具有给定名字的属性，如果没有的话，继续搜索实例的原型对象。</p><p>原型对象中的属性对于实例来说是只读的，比如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">name</span> = <span class="string">&#x27;JavaScript&#x27;</span></span><br><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line"><span class="keyword">let</span> p2 = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line">p1.<span class="property">name</span> = <span class="string">&#x27;CSS&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p1.<span class="property">name</span>) <span class="comment">// CSS</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p2.<span class="property">name</span>) <span class="comment">// JavaScript</span></span><br><span class="line"><span class="keyword">delete</span> p1.<span class="property">name</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p1.<span class="property">name</span>) <span class="comment">// JavaScript</span></span><br></pre></td></tr></table></figure><h3 id="hasOwnProperty-与-in-操作符">hasOwnProperty() 与 in 操作符</h3><p>hasOwnProperty 可以检测一个属性是存在于实例中，还是存在于原型对象中，这个方法继承自 Object 对象；无论属性存在于实例中还是原型中，使用 in 操作符都能得到 true。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">name</span> = <span class="string">&#x27;JavaScript&#x27;</span></span><br><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line">p1.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;name&#x27;</span>) <span class="comment">// false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;name&#x27;</span> <span class="keyword">in</span> p1) <span class="comment">// true</span></span><br><span class="line">p1.<span class="property">name</span> = <span class="string">&#x27;nobody&#x27;</span></span><br><span class="line">p1.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;name&#x27;</span>) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;name&#x27;</span> <span class="keyword">in</span> p1) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>注：ES5 中 Object.getOwnpropertyDescriptor() 方法只能用于实例属性，要取得原型属性的描述符，必须直接在原型对象上调用 Object.getOwnpropertyDescriptor()。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(p1, <span class="string">&#x27;name&#x27;</span>)</span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//  configurable: true</span></span><br><span class="line"><span class="comment">//  enumerable: true</span></span><br><span class="line"><span class="comment">//  value: &quot;nobody&quot;</span></span><br><span class="line"><span class="comment">//  writable:true</span></span><br><span class="line"><span class="comment">//&#125;</span></span><br></pre></td></tr></table></figure><p>要取得对象上所有的可枚举的实例属性，可以使用 Object.keys() 方法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">name</span> = <span class="string">&#x27;JavaScript&#x27;</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">age</span> = <span class="number">18</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayName</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>) <span class="comment">// [&quot;name&quot;, &quot;age&quot;, &quot;sayName&quot;]</span></span><br><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(p1) <span class="comment">// []</span></span><br><span class="line">p1.<span class="property">name</span> = <span class="string">&#x27;JavaScript&#x27;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(p1) <span class="comment">// [&quot;name&quot;]</span></span><br></pre></td></tr></table></figure><p>可以看出，Object.keys() 方法只枚举实例属性，并不枚举原型对象中的属性，而且 constructor 属性也是不可枚举的。</p><h3 id="更简单的原型语法">更简单的原型语法</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span> = &#123;</span><br><span class="line">  <span class="attr">constructor</span>: <span class="title class_">Person</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;JavaScript&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">  <span class="attr">sayName</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">  &#125;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种写法存在一个问题，就是重设的 constructor 属性的 [[Enumerable]] 特性被设置为 true，默认情况下，原生的 constructor 属性是不可枚举的。所以可以写成如下情况：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span> = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;JavaScript&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">  <span class="attr">sayName</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">  &#125;    </span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">definedProperty</span>(<span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>, <span class="string">&#x27;constructor&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">enumerable</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="attr">value</span>: <span class="title class_">Person</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="原型的动态性">原型的动态性</h3><p>在修改原型的过程中，我们可以随时为原型添加属性和方法，但是如果重写整个原型对象，那有可能切断构造函数与原型之间的联系。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span> = &#123;</span><br><span class="line">  <span class="attr">constructor</span>: <span class="title class_">Person</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;JavaScript&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">  <span class="attr">sayName</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">  &#125;        </span><br><span class="line">&#125;</span><br><span class="line">p1.<span class="title function_">sayName</span>() <span class="comment">// p1.sayName is not a function</span></span><br></pre></td></tr></table></figure><p>为什么在调用 p1.sayName() 的时候会发生错误呢，因为 p1 指向的原型对象中并不包含 sayName 方法。</p><p>其关系可看下图：</p><img src="/assets/img/js_prototype_new.png" alt="js_prototype_new"><p>重写原型对象后，切断了现有原型与任何之前已经存在的对象实例之间的联系，它们引用的任然是最初的原型。</p><h3 id="原型对象的缺点">原型对象的缺点</h3><p>原型对象省略了为构造函数传递参数这一环节，使得所有实例在默认情况下都取得相同的属性值，而且原型中所有的属性是被全部实例共享的，这种共享对于函数来说非常合适，但是对于属性值，尤其是引用类型的属性值来说，问题就比较严重了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span> = &#123;</span><br><span class="line">  <span class="attr">constructor</span>: <span class="title class_">Person</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;JavaScript&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">  <span class="attr">friends</span>: [<span class="string">&#x27;Lily&#x27;</span>, <span class="string">&#x27;Tony&#x27;</span>],</span><br><span class="line">  <span class="attr">sayName</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">  &#125;        </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line"><span class="keyword">let</span> p2 = <span class="keyword">new</span> <span class="title class_">Person</span>()</span><br><span class="line">p1.<span class="property">friends</span>.<span class="title function_">push</span>(<span class="string">&#x27;Jack&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p2.<span class="property">friends</span>) <span class="comment">// [&quot;Lily&quot;, &quot;Tony&quot;, &quot;Jack&quot;]</span></span><br><span class="line">p1.<span class="property">friends</span> === p2.<span class="property">friends</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>修改实例 p1 的值的过程中，p2 的值也被修改了。这就导致了仅仅使用原型模式创建对象存在很大的问题。具体解决请查看<a href="https://lz5z.com/JavaScript%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/">深入学习JavaScript——面向对象</a>。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;理解原型对象&quot;&gt;理解原型对象&lt;/h2&gt;
&lt;p&gt;在 JavaScript 中，只要创建了新函数，都会根据一组特定的规则为该函数创建一个 prototype 属性，这个属性指向函数的原型对象。默认情况下，所有原型对象都会自动获取一个 constructor（构造函数）属性，这个属性包含一个指向 prototype 属性所在函数的指针。比如：&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;Person&lt;/span&gt; (&lt;span class=&quot;params&quot;&gt;&lt;/span&gt;) &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;title class_&quot;&gt;Person&lt;/span&gt;.&lt;span class=&quot;property&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;prototype&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;property&quot;&gt;constructor&lt;/span&gt; === &lt;span class=&quot;title class_&quot;&gt;Person&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;通过 constructor，我们可以继续为原型对象添加其他属性和方法。&lt;/p&gt;
&lt;p&gt;创建自定义的构造函数之后，其原型对象默认只会取得 constructor 属性，其它属性和方法都是从 Object 继承而来的。&lt;/p&gt;
&lt;p&gt;当调用构造函数创建一个新实例后，该实例的内部将包含一个指针（[[Prototype]]），指向构造函数的原型对象，该指针在常用的浏览器中被定义为 &lt;code&gt;__proto__&lt;/code&gt;。需要说明的一点是，该连接存在于实例和构造函数的原型对象之间，而不是存在于原型和构造函数之间。&lt;/p&gt;
&lt;figure class=&quot;highlight javascript&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;let&lt;/span&gt; leo = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Person&lt;/span&gt;()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;leo.&lt;span class=&quot;property&quot;&gt;__proto__&lt;/span&gt; === &lt;span class=&quot;title class_&quot;&gt;Person&lt;/span&gt;.&lt;span class=&quot;property&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;prototype&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="prototype" scheme="https://lz5z.com/tags/prototype/"/>
    
    <category term="原型" scheme="https://lz5z.com/tags/%E5%8E%9F%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>深入学习 JavaScript——面向对象</title>
    <link href="https://lz5z.com/JavaScript%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/"/>
    <id>https://lz5z.com/JavaScript%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/</id>
    <published>2016-12-01T10:14:29.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h1>JavaScript 面向对象</h1><p>几乎所有面向对象的语言都有一个标志，那就是类，通过类创建具有相同属性和方法的对象。而 ECMAScript 中没有类的概念，它把对象定义为：“无序属性的集合，其属性可以包含基本值、对象或者函数”。即对象是一组没有特定顺序的值，对象的每个属性或方法都有一个名字，而这个名字都映射到一个值。因此对象的本质是一个<a href="https://lz5z.com/JavaScript-Object-Hash/">散列表</a>。</p><span id="more"></span><h1>创建对象</h1><p>虽然 Object 构造函数或对象字面量都可以创建单个对象，但是这些方式有个明显的缺点：使用同一个接口创建很多对象，会产生大量重复的代码。为了解决这个问题，就可以使用工厂模式来创建对象。</p><h2 id="工厂模式">工厂模式</h2><p>工厂模式用函数来封装特定接口创建对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createPerson</span>(<span class="params">name, age, job</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line">    o.<span class="property">name</span> = name</span><br><span class="line">    o.<span class="property">age</span> = age</span><br><span class="line">    o.<span class="property">job</span> = job</span><br><span class="line">    o.<span class="property">sayName</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> o</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> leo = <span class="title function_">createPerson</span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>, <span class="string">&quot;Engineer&quot;</span>)</span><br></pre></td></tr></table></figure><p>工厂模式虽然解决了创建多个相似对象的问题，但没有解决对象识别的问题（即怎样知道一个对象的类型）。</p><h2 id="构造函数模式">构造函数模式</h2><p>ECMAScript 中的构造函数可以用来创建特定类型的对象，像 Object 和 Array 的原生的构造函数，在运行时会自动出现在执行环境中。此外，也可以创建自定义的构造函数，从而定义自定义对象类型的属性和方法。代码如下所示：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age, job</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = age</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">job</span> = job</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> leo = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>, <span class="string">&quot;Engineer&quot;</span>)</span><br><span class="line"><span class="keyword">let</span> jack = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>, <span class="string">&quot;Engineer&quot;</span>)</span><br></pre></td></tr></table></figure><p>构造函数模式与工厂模式有以下不同：</p><ol><li>没有显式的创建对象；</li><li>直接将属性和方法赋给了this对象；</li><li>没有return语句；</li></ol><p>构造函数应该以大写字母开头，使用 new 操作符。new 操作符创建对象经历以下 4 个步骤：</p><ol><li>创建新的对象；</li><li>将构造函数的作用域赋给新对象（因此 this 就指向了这个新对象）；</li><li>执行构造函数中的代码（为这个新对象添加属性）；</li><li>返回新对象；</li></ol><p>生成的对象 leo 中有一个 constructor 属性，该属性指向 Person，并且可以用 instanceof 做类型检测。</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></pre></td><td class="code"><pre><span class="line">leo.<span class="property">constructor</span> === <span class="title class_">Person</span> <span class="comment">// true</span></span><br><span class="line">leo <span class="keyword">instanceof</span>  <span class="title class_">Object</span> <span class="comment">// true</span></span><br><span class="line">leo <span class="keyword">instanceof</span> <span class="title class_">Person</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>构造函数的缺点在于每个方法都要在每个实例上重新创建一遍。在前面例子中，leo 和 jack 都有一个名为 sayName 的方法，但是这两个方法不属于同一个对象。</p><p>那么我们能不能共享一个 sayName() 方法。如果想要完成这种需求，大可像下面代码一样，通过把函数定义转移到构造函数的外部。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age, job</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = age</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">job</span> = job</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">sayName</span> = sayName</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sayName</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> leo = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>, <span class="string">&quot;Engineer&quot;</span>)</span><br><span class="line"><span class="keyword">let</span> jack = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>, <span class="string">&quot;Engineer&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(leo.<span class="property">sayName</span> === jack.<span class="property">sayName</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>上面例子中的做法，确实解决了两个函数做同一件事的问题，但是无意中定义了很多全局函数，而这些全局函数中由于包含 “this” 关键字，又只能被某个函数调用。不仅污染了全局作用域，还使得这个自定义的引用类型完全丧失封装性。好在这些问题都可以通过原型模式解决。</p><h2 id="原型模式">原型模式</h2><p>JavaScript 中创建的每个函数都有一个 prototype 属性，这个属性是一个指针，指向一个对象，而这个对象的用途是包含可以由特定类型的 <strong>所有实例共享的属性和方法</strong>。prototype是通过调用构造函数而创建的那个对象实例的对象原型，使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">name</span> = <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">age</span> = <span class="number">18</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> leo1 = <span class="keyword">new</span> <span class="title class_">Person</span></span><br><span class="line"><span class="keyword">let</span> leo2 = <span class="keyword">new</span> <span class="title class_">Person</span></span><br><span class="line">leo1.<span class="title function_">sayName</span>()</span><br><span class="line">leo2.<span class="title function_">sayName</span>()</span><br></pre></td></tr></table></figure><p>在此，我们将 sayName() 方法和所有的属性直接添加到了 Person 的 prototype 属性中，构造函数变成了空函数，而通过 new 创建出来的对象具有相同的属性和方法。但是与构造函数模式不同对的是，新对象的这些属性和方法是由所有的实例共享的，也就是说</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">leo1.<span class="property">sayName</span> === leo2.<span class="property">sayName</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h2 id="组合使用构造函数模式和原型模式">组合使用构造函数模式和原型模式</h2><p>创建自定义对象最常见的形式就是组合使用构造函数模式和原型模式，构造函数用于定义类的实例属性，而原型模式用于定义对象的共享属性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = age</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">friends</span> = []</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span> = &#123;</span><br><span class="line">    <span class="attr">constructor</span>: <span class="title class_">Person</span>,</span><br><span class="line">    <span class="attr">sayName</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> leo = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>)</span><br><span class="line"><span class="keyword">let</span> jack = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>)</span><br><span class="line">leo.<span class="property">friends</span>.<span class="title function_">push</span>(<span class="string">&#x27;Elsa&#x27;</span>)</span><br><span class="line">jack.<span class="property">friends</span>.<span class="title function_">push</span>(<span class="string">&#x27;Lucy&#x27;</span>)</span><br><span class="line">leo.<span class="property">sayName</span> === jack.<span class="property">sayName</span> <span class="comment">// true</span></span><br><span class="line">jack.<span class="property">friends</span> === leo.<span class="property">friends</span> <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>实例属性都是在构造函数中定义的，而实例共享属性 constructor 和方法 sayName() 则是在原型中定义的。这种构造函数与原型混成的模式，是目前 ECMAScript 中使用最广泛、认同度最高的一种创建自定义对象的方法。</p><h2 id="动态原型模式">动态原型模式</h2><p>动态原型模式将所有信息封装在了构造函数中，而通过构造函数中初始化原型（仅第一个对象实例化时初始化原型），又保持了同时使用构造函数和原型的优点。换句话说，可以通过检查某个应该存在的方法是否有效，来决定是否需要初始化原型。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = age</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="variable language_">this</span>.<span class="property">sayName</span> != <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">        <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> leo = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>)</span><br><span class="line">leo.<span class="title function_">sayName</span>()</span><br></pre></td></tr></table></figure><p>Person 是一个构造函数，通过 new Person() 来生成实例对象。每当一个 Person 的对象生成时，Person 内部的代码都会被调用一次。</p><p>如果去掉 if 的话，你每 new 一次(即每当一个实例对象生产时)，都会重新定义一个新的函数，然后挂到 Person.prototype.sayName 属性上。而实际上，你只需要定义一次就够了，因为所有实例都会共享此属性的。而加上 if 后，只在 new 第一个实例时才会定义 sayName 方法，之后就不会了。</p><p>假设除了sayName 方法外，你还定义了很多其他方法，比如 sayBye、cry、smile 等等。此时你只需要把它们都放到对 sayName 判断的 if 块里面就可以了。</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">if</span> (<span class="keyword">typeof</span> <span class="variable language_">this</span>.<span class="property">sayName</span> != <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">    <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;...&#125;</span><br><span class="line">    <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayBye</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;...&#125;</span><br><span class="line">    <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">cry</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;...&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样一来，要么它们全都还没有定义(new 第一个实例时)，要么已经全都定义了(new 其他实例后)，即它们的存在性是一致的，用同一个判断就可以了，而不需要分别对它们进行判断。</p><p>使用动图原型模式时，不能使用对象字面量重写原型，如果在已经创建实例的情况下重写原型，会切断现有实例和原型之间的联系。</p><h2 id="寄生构造函数模式">寄生构造函数模式</h2><p>寄生构造函数的基本思想是创建一个函数，该函数的作用仅仅是封装创建对象的代码，然后返回新创建的对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line">    o.<span class="property">name</span> = name</span><br><span class="line">    o.<span class="property">age</span> = age</span><br><span class="line">    o.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> o</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> leo = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>)</span><br><span class="line">leo.<span class="title function_">sayName</span>()</span><br></pre></td></tr></table></figure><p>在这个例子中，Person 函数创建了一个新对象，并以相应的属性和方法初始化该对象，然后返回这个对象。除了使用 new 操作符并把使用的包装函数叫做构造函数外，这个模式跟工厂模式一模一样。构造函数在不返回值的情况下，默认会返回新的对象实例。</p><p>这个模式在特殊的情况下可以用来为对象创建构造函数。假如我们想创建一个具有额外方法的特殊数组，由于不能直接修改 Array 的构造函数，因此可以使用这种模式。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">SpecialArray</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> values = <span class="keyword">new</span> <span class="title class_">Array</span>()</span><br><span class="line">    values.<span class="property">push</span>.<span class="title function_">apply</span>(values, <span class="variable language_">arguments</span>)</span><br><span class="line">    values.<span class="property">toPipedString</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">join</span>(<span class="string">&#x27;|&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> values</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> colors = <span class="keyword">new</span> <span class="title class_">SpecialArray</span>(<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors.<span class="title function_">toPipedString</span>()) <span class="comment">// &#x27;red|blue|green&#x27;</span></span><br></pre></td></tr></table></figure><p>关于寄生构造函数模式，有一点需要说明：返回的对象与构造函数或者构造函数的原型属性直接没有关系，所以不能依赖 instanceof 操作符来确定对象类型。</p><h2 id="稳妥构造函数模式">稳妥构造函数模式</h2><p>稳妥对象，是指没有公共属性，而且方法也不引用 this 的对象，适合在一些安全环境中（禁用 this 和 new），或者在防止数据被其它应用程序改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式，但是有两点不同：一是新创建对象的实例方法不引用 this，二是不使用 new 操作符调用构造函数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>()</span><br><span class="line">    o.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> o</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> leo = <span class="title class_">Person</span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>)</span><br><span class="line">leo.<span class="title function_">sayName</span>()</span><br></pre></td></tr></table></figure><p>注意在这种模式创建的对象中，除了使用 sayName 方法之外，没有其他办法访问 name 属性，即使有其他代码给这个对象添加属性或者方法，也不可能有别的办法访问传入到构造函数中的原始数据。</p><p>与寄生构造函数类似，稳妥构造函数模式创建的对象与构造函数直接也没有什么关系，所以不能依赖 instanceof 操作符来确定对象类型。</p><h1>总结</h1><p>组合使用构造函数模式和原型模式是目前使用最广的方法，如果不希望构造函数和原型相互分离的话，可以使用动态原型模式。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;JavaScript 面向对象&lt;/h1&gt;
&lt;p&gt;几乎所有面向对象的语言都有一个标志，那就是类，通过类创建具有相同属性和方法的对象。而 ECMAScript 中没有类的概念，它把对象定义为：“无序属性的集合，其属性可以包含基本值、对象或者函数”。即对象是一组没有特定顺序的值，对象的每个属性或方法都有一个名字，而这个名字都映射到一个值。因此对象的本质是一个&lt;a href=&quot;https://lz5z.com/JavaScript-Object-Hash/&quot;&gt;散列表&lt;/a&gt;。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="面向对象" scheme="https://lz5z.com/tags/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/"/>
    
    <category term="Object-Oriented" scheme="https://lz5z.com/tags/Object-Oriented/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 对象与 Hash 表</title>
    <link href="https://lz5z.com/JavaScript-Object-Hash/"/>
    <id>https://lz5z.com/JavaScript-Object-Hash/</id>
    <published>2016-12-01T02:02:04.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h1>简介</h1><p>哈希表(Hash table，也叫散列表)，是根据关键码值(Key value)而直接进行访问的数据结构。也就是说，它通过把关键码值映射到表中一个位置来访问记录，以加快查找的速度。这个映射函数叫做散列函数，存放记录的数组叫做散列表。</p><p>JavaScript 中的对象也是以 Key-Value 的形式访问，那么 JavaScript 的对象是否以 Hash 的结构存储呢？</p><p>我们首先来看一下 Hash 表结构。</p><span id="more"></span><h2 id="Hash-表结构">Hash 表结构</h2><p>数组的特点是：寻址容易，插入和删除困难；而链表的特点是：寻址困难，插入和删除容易，Hash 表综合两者的特性，做出一种寻址容易，插入删除也容易的数据结构。</p><p>下图是最常见的 <strong>拉链法</strong> 做出的 Hash 表</p><img src="/assets/img/hash.jpg" alt="hash"><p>左边是一个数组，数组的每个成员包括一个指针，指向一个链表的头，当然这个链表可能为空，也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去，也是根据这些特征，找到正确的链表，再从链表中找出这个元素。</p><p>元素特征转变为数组下标的方法就是散列法。上图运用的方法为 <strong>整除法</strong>，公式为：</p><blockquote><p>index = value % 16</p></blockquote><p>hash表的工作原理：</p><ol><li>第一步 先根据给定的key和散列算法得到具体的散列值，也就是对应的数组下标。</li><li>第二步，根据数组下标得到此下标里存储的指针，若指针为空，则不存在这样的键值对，否则根据此指针得到此链式数组。</li><li>遍历此链式数组，分别取出Key与给定的Key比较，若找到与给定key相等的Key，即在此hash表中存在此要查找的&lt;Key,Value&gt;键值对，此后便可以对此键值对进行相关操作；若找不到，即为不存在此键值对。</li></ol><h1>JavaScript 对象存储形式</h1><h2 id="JavaScript-对象-Key-存储形式">JavaScript 对象 Key 存储形式</h2><p>在我们创建或者访问对象属性的时候，如果使用 <strong>对象.属性名</strong> 的方式，属性名只能为字符串类型，而且不能以数字开头：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = &#123;&#125;;</span><br><span class="line">obj<span class="number">.2</span> = <span class="number">2</span>;  <span class="comment">//Uncaught SyntaxError: Unexpected number</span></span><br><span class="line">obj.12s = <span class="string">&#x27;12s&#x27;</span>;  <span class="comment">//Uncaught SyntaxError: Invalid or unexpected token</span></span><br></pre></td></tr></table></figure><p>而使用字面量的形式创建对象，或者用 <strong>对象[属性名]</strong> 的方法，却没有这样的限制:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> o = &#123;&#125;;</span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line">    <span class="attr">x</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="number">2</span>: <span class="number">2</span>,</span><br><span class="line">    <span class="attr">o</span>: <span class="string">&#x27;object&#x27;</span>,</span><br><span class="line">    &#123;<span class="attr">name</span>: <span class="string">&#x27;Leo&#x27;</span>&#125;: <span class="string">&#x27;object&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line">obj[<span class="string">&#x27;12s&#x27;</span>] = <span class="string">&#x27;12s&#x27;</span>;</span><br><span class="line">obj[&#123;<span class="attr">name</span>: <span class="string">&#x27;Leo&#x27;</span>&#125;] = <span class="string">&#x27;object&#x27;</span>; <span class="comment">//使用 对象[属性名] 的方式甚至可以把对象当做属性名传入</span></span><br></pre></td></tr></table></figure><p>此时 obj 里面的属性 <strong>2</strong> 是一个整数吗？</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> obj) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> i, i, obj[i]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// string 2 2</span></span><br><span class="line"><span class="comment">// string x 1</span></span><br><span class="line"><span class="comment">// string o object</span></span><br><span class="line"><span class="comment">// string 12s 12s</span></span><br><span class="line"><span class="comment">// string [object Object] object</span></span><br></pre></td></tr></table></figure><p>由此可见 JavaScript 中对象的 Key 均是 string 类型。</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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj[<span class="number">2</span>] === obj[<span class="string">&#x27;2&#x27;</span>]);  <span class="comment">// true</span></span><br><span class="line">object[<span class="number">2</span>]=<span class="number">3</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(object[<span class="string">&#x27;2&#x27;</span>]);<span class="comment">//3</span></span><br></pre></td></tr></table></figure><p>可见解释器在访问 object[2] 的时候，先将方括号里面的 2 转换成字符串，然后再访问。</p><p>而使用 obj[{name: ‘Leo’}] = ‘object’ 的时候，也是同样的，解释器先调用 Objcet.toString 方法把对象 {name: ‘Leo’} 转换成字符串，然后再访问。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> object = &#123;</span><br><span class="line">  <span class="attr">x</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="number">2</span>: <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;2&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(object[&#123;<span class="attr">name</span>: <span class="string">&#x27;Leo&#x27;</span>&#125;]);　　<span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p>上述的 <strong>object[{name: ‘Leo’}]</strong> 相当于 <strong>object[{name: ‘Leo’}.toString()]</strong> 亦相当于 <strong>object[‘2’]</strong>，于是就得到结果 2。</p><p>这里也间接证明了 JavaScript 对象中，所有的 key 都是字符串，即使你访问的时候不是字符串的形式，解释器也会先将其转化为字符串。</p><p>可是我们知道整数值直接调用 toString 方法是会报错的，因为 JavaScript 解析器会试图将点操作符解析为浮点数字面值的一部分。不过有很多变通方法可以让数字的字面值看起来像对象。</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="number">2.</span><span class="title function_">toString</span>() <span class="comment">// Uncaught SyntaxError: Invalid or unexpected token</span></span><br><span class="line"><span class="comment">//解决方案</span></span><br><span class="line"><span class="number">2.</span>.<span class="title function_">toString</span>(); <span class="comment">// 第二个点号可以正常解析</span></span><br><span class="line"><span class="number">2</span> .<span class="title function_">toString</span>(); <span class="comment">// 注意点号前面的空格</span></span><br><span class="line">(<span class="number">2</span>).<span class="title function_">toString</span>(); <span class="comment">// 2先被计算</span></span><br></pre></td></tr></table></figure><p>所以 JavaScript 解释器应该有帮我们做这一部分工作。</p><h2 id="JavaScript-对象-Value-存储形式">JavaScript 对象 Value 存储形式</h2><p>在JavaScript高级程序设计（第三版）中，是这么描述属性的：属性在创建时都带有一些特征值，JavaScript引擎通过这些特征值来定义他们的行为。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123;&#125;;</span><br><span class="line">person.<span class="property">name</span> = <span class="string">&#x27;Leo&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> descriptor=<span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(person,<span class="string">&quot;name&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(descriptor); </span><br><span class="line"><span class="comment">//Object</span></span><br><span class="line"><span class="comment">// configurable: true</span></span><br><span class="line"><span class="comment">// enumerable: true</span></span><br><span class="line"><span class="comment">// value: &quot;Leo&quot;</span></span><br><span class="line"><span class="comment">// writable: true</span></span><br><span class="line"><span class="comment">// __proto__: Object</span></span><br></pre></td></tr></table></figure><p>可见 value 的数据类型是结构体。</p><h2 id="JavaScript-对象存储形式">JavaScript 对象存储形式</h2><p>在 JavaScript 中，我们可以任意给对象添加或者删除属性，由此可以推断，对象不是由数组结构存储；链表虽然能够任意伸缩但是其查询效率低下，因此也排除链表。如果用树作为存储结构，效率较高的可能就是平衡树了。平衡树的查询效率还可以接受，但是当删除属性的时候，平衡树在调整的时候代价相比于 hash 表要大很多。于是 Hash 成为最好的选择。</p><p>假如有这么一段代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">id, name, age</span>) &#123;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">id</span> = id;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> num = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">let</span> bol = <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">let</span> obj = <span class="keyword">new</span> <span class="title class_">Object</span>;</span><br><span class="line"><span class="keyword">let</span> arr = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>];</span><br><span class="line"><span class="keyword">let</span> person = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="number">100</span>, <span class="string">&#x27;Leo&#x27;</span>, <span class="number">18</span>);</span><br></pre></td></tr></table></figure><p>JavaScript 内存分析图如下：<br><img src="/assets/img/js_obj_mem.png" alt="memory"></p><p>变量 num、bol、str 为基本数据类型，它们的值直接存放在栈中。obj、person、arr 为复合数据类型，他们的引用变量存储在栈中，指向于存储在堆中的实际对象。</p><p>在 JavaScript 中变量分为基本类型和引用类型（对象类型），分别对应着两种不同的存储方式–栈存储和堆存储。</p><p>基本类型一旦初始化则内存大小固定，访问变量就是访问变量的内存上实际的数据，称之为按值访问。而对象类型内存大小不固定，无法在栈中维护，所以 JavaScript 就把对象类型的变量放到堆中，让解释器为其按需分配内存，而通过对象的引用指针对其进行访问，因为对象在堆中的内存地址大小是固定的，因此可以将内存地址保存在栈内存的引用中。这种方式称之为按引用访问。</p><h1>总结</h1><p>在 JavaScript 中对象是以 Hash 结构存储的，用 &lt;Key, Value&gt; 键值对表示对象的属性，Key 的数据类型为字符串，Value 的数据类型是结构体，即对象是以 &lt;String, Object&gt; 类型的 HashMap 结构存储的。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;简介&lt;/h1&gt;
&lt;p&gt;哈希表(Hash table，也叫散列表)，是根据关键码值(Key value)而直接进行访问的数据结构。也就是说，它通过把关键码值映射到表中一个位置来访问记录，以加快查找的速度。这个映射函数叫做散列函数，存放记录的数组叫做散列表。&lt;/p&gt;
&lt;p&gt;JavaScript 中的对象也是以 Key-Value 的形式访问，那么 JavaScript 的对象是否以 Hash 的结构存储呢？&lt;/p&gt;
&lt;p&gt;我们首先来看一下 Hash 表结构。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Hash" scheme="https://lz5z.com/tags/Hash/"/>
    
  </entry>
  
  <entry>
    <title>Spark RDD 操作详解——Transformations</title>
    <link href="https://lz5z.com/rdd-operations/"/>
    <id>https://lz5z.com/rdd-operations/</id>
    <published>2016-11-29T07:08:50.000Z</published>
    <updated>2026-05-07T14:50:53.973Z</updated>
    
    <content type="html"><![CDATA[<h1>RDD 操作有哪些</h1><p>Spark RDD 支持2种类型的操作: transformations 和 actions。transformations： 从已经存在的数据集中创建一个新的数据集，如 map。actions： 数据集上进行计算之后返回一个值，如 reduce。</p><p>在 Spark 中，所有的 transformations 都是 lazy 的，它们不会马上计算它们的结果，而是仅仅记录转换操作是应用到哪些基础数据集上的，只有当 actions 要返回结果的时候计算才会发生。</p><span id="more"></span><p>默认情况下，每一个转换过的 RDD 会在每次执行 actions 的时候重新计算一次。但是可以使用 persist (或 cache)方法持久化一个 RDD 到内存中，这样Spark 会在集群上保存相关的元素，下次查询的时候会变得更快，也可以持久化 RDD 到磁盘，或在多个节点间复制。</p><h2 id="基础">基础</h2><p>在 Spark-shell 中运行如下脚本</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><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">scala&gt; <span class="keyword">val</span> lines = sc.textFile(<span class="string">&quot;test.txt&quot;</span>)</span><br><span class="line">scala&gt; <span class="keyword">val</span> lineLengths = lines.map(s =&gt; s.length)</span><br><span class="line">scala&gt; <span class="keyword">val</span> totalLength = lineLengths.reduce((a, b) =&gt; a + b))</span><br><span class="line">totalLength: <span class="type">Int</span> = <span class="number">30</span></span><br></pre></td></tr></table></figure><p>第一步： 定义外部文件 RDD，lines 指向 test.txt 文件， 这个文件即没有加载到内存也没有做其他的操作，所以即使文件不存在也不会报错。<br>第二步： 定义 lineLengths，它是 map 转换(transformation)的结果。同样，lineLengths 由于 lazy 模式也没有立即计算。<br>第三步： reduce 是一个 action， 所以真正执行读文件和 map 计算是在这一步发生的。Spark 将计算分成多个 task，并且让它们运行在多台机器上。每台机器都运行自己的 map 部分和本地 reduce 部分，最后将结果返回给驱动程序。</p><p>如果我们想要再次使用 lineLengths，我们可以使用 persist 或者 cache 将 lineLengths 保存到内存中。</p><figure class="highlight scala"><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">scala&gt; lineLengths.persist()</span><br><span class="line">scala&gt; lineLengths.collect()</span><br><span class="line">res7: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">5</span>, <span class="number">3</span>, <span class="number">15</span>, <span class="number">7</span>)</span><br></pre></td></tr></table></figure><h1>Transformations</h1><p>Transformations 是 RDD 的基本转换操作，主要方法有： map， filter， flatMap， mapPartitions， mapPartitionsWithIndex， sample， union， intersection， distinct， groupByKey， reduceByKey， aggregateByKey， sortByKey， join， cogroup， cartesian， pipe， coalesce， repartition。</p><h2 id="filter-func">filter(func)</h2><p>filter 返回一个新的数据集，从源数据中选出 func 返回 true 的元素。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><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">scala&gt; <span class="keyword">val</span> a = sc.parallelize(<span class="number">1</span> to <span class="number">9</span>)</span><br><span class="line">scala&gt; <span class="keyword">val</span> b = a.filter(x =&gt; x &gt; <span class="number">5</span>)</span><br><span class="line">scala&gt; b.collect</span><br><span class="line">res11: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>)</span><br></pre></td></tr></table></figure><h2 id="flatMap-func">flatMap(func)</h2><p>与 map 类似，区别是原 RDD 中的元素经 map 处理后只能生成一个元素，而经 flatmap 处理后可生成多个元素来构建新 RDD， 所以 func 必须返回一个 Seq，而不是单个 item。</p><p>举例：对原RDD中的每个元素x产生y个元素（从1到y，y为元素x的值）</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><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">scala&gt; <span class="keyword">val</span> a = sc.parallelize(<span class="number">1</span> to <span class="number">4</span>, <span class="number">2</span>)</span><br><span class="line">scala&gt; <span class="keyword">val</span> b = a.flatMap(x =&gt; <span class="number">1</span> to x)</span><br><span class="line">scala&gt; b.collect</span><br><span class="line">res12: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">1</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>)</span><br></pre></td></tr></table></figure><h2 id="mapPartitions-func">mapPartitions(func)</h2><p>mapPartitions 是 map 的一个变种。map 的输入函数是应用于 RDD 中每个元素，而 mapPartitions 的输入函数是应用于每个分区，也就是把每个分区中的内容作为整体来处理的。<br>它的函数定义为：</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mapPartitions</span></span>[<span class="type">U</span>](f: (<span class="type">Iterator</span>[<span class="type">T</span>]) =&gt; <span class="type">Iterator</span>[<span class="type">U</span>], preservesPartitioning: <span class="type">Boolean</span> = <span class="literal">false</span>)(<span class="keyword">implicit</span> arg0: <span class="type">ClassTag</span>[<span class="type">U</span>]): <span class="type">RDD</span>[<span class="type">U</span>]</span><br></pre></td></tr></table></figure><p>f 即为输入函数，它处理每个分区里面的内容。每个分区中的内容将以 Iterator[T] 传递给输入函数 f，f 的输出结果是 Iterator[U]。最终的 RDD 由所有分区经过输入函数处理后的结果合并起来的。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">scala&gt; <span class="keyword">val</span> rdd = sc.makeRDD(<span class="number">1</span> to <span class="number">5</span>, <span class="number">2</span>)</span><br><span class="line">scala&gt; <span class="keyword">val</span> rdd2 = rdd.mapPartitions(x =&gt; &#123;</span><br><span class="line">     | <span class="keyword">var</span> result = <span class="type">List</span>[<span class="type">Int</span>]()</span><br><span class="line">     | <span class="keyword">var</span> i = <span class="number">0</span></span><br><span class="line">     | <span class="keyword">while</span>(x.hasNext) &#123;</span><br><span class="line">     |   i += x.next</span><br><span class="line">     | &#125;</span><br><span class="line">     | result.::(i).iterator</span><br><span class="line">     |&#125;)</span><br><span class="line">scala&gt; rdd2.collect</span><br><span class="line">res13: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">3</span>, <span class="number">12</span>)</span><br><span class="line"></span><br><span class="line">scala&gt; rdd2.partitions.size</span><br><span class="line">res14: <span class="type">Int</span> = <span class="number">2</span></span><br></pre></td></tr></table></figure><p>上述例子中 rdd2 将 rdd 每个分区中的数值累加。</p><h2 id="mapPartitionsWithIndex-func">mapPartitionsWithIndex(func)</h2><p>函数定义</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mapPartitionsWithIndex</span></span>[<span class="type">U</span>](f: (<span class="type">Int</span>, <span class="type">Iterator</span>[<span class="type">T</span>]) =&gt; <span class="type">Iterator</span>[<span class="type">U</span>], preservesPartitioning: <span class="type">Boolean</span> = <span class="literal">false</span>)(<span class="keyword">implicit</span> arg0: <span class="type">ClassTag</span>[<span class="type">U</span>]): <span class="type">RDD</span>[<span class="type">U</span>]</span><br></pre></td></tr></table></figure><p>mapPartitionsWithIndex 的作用与 mapPartitions 相同，不过提供了两个参数，第一个参数为分区的索引。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">scala&gt; <span class="keyword">val</span> rdd = sc.makeRDD(<span class="number">1</span> to <span class="number">5</span>, <span class="number">2</span>)</span><br><span class="line">scala&gt; <span class="keyword">val</span> rdd2 = rdd.mapPartitionsWithIndex(</span><br><span class="line">     | (x, iter) =&gt; &#123;</span><br><span class="line">     |   <span class="keyword">var</span> result = <span class="type">List</span>[<span class="type">Int</span>]()</span><br><span class="line">     |   <span class="keyword">var</span> i = <span class="number">0</span></span><br><span class="line">     |    <span class="keyword">while</span>(iter.hasNext) &#123;</span><br><span class="line">     |     i += iter.next</span><br><span class="line">     |    &#125;</span><br><span class="line">     |   result.::(x + <span class="string">&quot;|&quot;</span> + i).iterator</span><br><span class="line">     |&#125;)</span><br><span class="line">scala&gt; rdd2.collect</span><br><span class="line">res14: <span class="type">Array</span>[<span class="type">String</span>] = <span class="type">Array</span>(<span class="number">0</span>|<span class="number">3</span>, <span class="number">1</span>|<span class="number">12</span>)</span><br><span class="line"></span><br><span class="line">scala&gt; rdd2.partitions.size</span><br><span class="line">res15: <span class="type">Int</span> = <span class="number">2</span></span><br></pre></td></tr></table></figure><h2 id="sample-withReplacement-fraction-seed">sample(withReplacement, fraction, seed)</h2><h2 id="union-otherDataset">union(otherDataset)</h2><p>函数定义：</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">union</span></span>(other: <span class="type">RDD</span>[<span class="type">T</span>]): <span class="type">RDD</span>[<span class="type">T</span>]</span><br></pre></td></tr></table></figure><p>该函数比较简单，就是将两个 RDD 进行合并，不去重。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><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">scala&gt; <span class="keyword">var</span> rdd1 = sc.makeRDD(<span class="number">1</span> to <span class="number">2</span>,<span class="number">1</span>)</span><br><span class="line">scala&gt; <span class="keyword">var</span> rdd2 = sc.makeRDD(<span class="number">2</span> to <span class="number">3</span>,<span class="number">1</span>)</span><br><span class="line">scala&gt; rdd1.union(rdd2).collect</span><br><span class="line">res18: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">3</span>)</span><br></pre></td></tr></table></figure><h2 id="intersection-otherDataset">intersection(otherDataset)</h2><p>函数定义：</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">intersection</span></span>(other: <span class="type">RDD</span>[<span class="type">T</span>]): <span class="type">RDD</span>[<span class="type">T</span>]</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">intersection</span></span>(other: <span class="type">RDD</span>[<span class="type">T</span>], numPartitions: <span class="type">Int</span>): <span class="type">RDD</span>[<span class="type">T</span>]</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">intersection</span></span>(other: <span class="type">RDD</span>[<span class="type">T</span>], partitioner: <span class="type">Partitioner</span>)(<span class="keyword">implicit</span> ord: <span class="type">Ordering</span>[<span class="type">T</span>] = <span class="literal">null</span>): <span class="type">RDD</span>[<span class="type">T</span>]</span><br></pre></td></tr></table></figure><p>该函数返回两个 RDD 的交集，并且去重。<br>参数numPartitions指定返回的RDD的分区数。<br>参数partitioner用于指定分区函数</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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">scala&gt; <span class="keyword">var</span> rdd1 = sc.makeRDD(<span class="number">1</span> to <span class="number">2</span>,<span class="number">1</span>)</span><br><span class="line">scala&gt; rdd1.collect</span><br><span class="line">res19: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line"> </span><br><span class="line">scala&gt; <span class="keyword">var</span> rdd2 = sc.makeRDD(<span class="number">2</span> to <span class="number">3</span>,<span class="number">1</span>)</span><br><span class="line">scala&gt; rdd2.collect</span><br><span class="line">res20: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line"> </span><br><span class="line">scala&gt; rdd1.intersection(rdd2).collect</span><br><span class="line">res21: <span class="type">Array</span>[<span class="type">Int</span>] = <span class="type">Array</span>(<span class="number">2</span>)</span><br></pre></td></tr></table></figure><h2 id="distinct-numTasks">distinct([numTasks])</h2><p>返回一个新的 RDD，里面包含源 RDD 中所有的（distinct）元素。</p><h2 id="groupByKey-numTasks">groupByKey([numTasks])</h2><h2 id="reduceByKey-func-numTasks">reduceByKey(func, [numTasks])</h2><h2 id="aggregateByKey-zeroValue-seqOp-combOp-numTasks">aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])</h2><h2 id="sortByKey-ascending-numTasks">sortByKey([ascending], [numTasks])</h2><h2 id="join-otherDataset-numTasks">join(otherDataset, [numTasks])</h2><h2 id="cogroup-otherDataset-numTasks">cogroup(otherDataset, [numTasks])</h2><h2 id="cartesian-otherDataset">cartesian(otherDataset)</h2><h2 id="pipe-command-envVars">pipe(command, [envVars])</h2><h2 id="coalesce-numPartitions">coalesce(numPartitions)</h2><h2 id="repartition-numPartitions">repartition(numPartitions)</h2>]]></content>
    
    
    <summary type="html">&lt;h1&gt;RDD 操作有哪些&lt;/h1&gt;
&lt;p&gt;Spark RDD 支持2种类型的操作: transformations 和 actions。transformations： 从已经存在的数据集中创建一个新的数据集，如 map。actions： 数据集上进行计算之后返回一个值，如 reduce。&lt;/p&gt;
&lt;p&gt;在 Spark 中，所有的 transformations 都是 lazy 的，它们不会马上计算它们的结果，而是仅仅记录转换操作是应用到哪些基础数据集上的，只有当 actions 要返回结果的时候计算才会发生。&lt;/p&gt;</summary>
    
    
    
    <category term="Big Data" scheme="https://lz5z.com/categories/Big-Data/"/>
    
    
    <category term="Spark" scheme="https://lz5z.com/tags/Spark/"/>
    
    <category term="Scala" scheme="https://lz5z.com/tags/Scala/"/>
    
    <category term="RDD" scheme="https://lz5z.com/tags/RDD/"/>
    
    <category term="Transformations" scheme="https://lz5z.com/tags/Transformations/"/>
    
  </entry>
  
  <entry>
    <title>Spark RDD 基础</title>
    <link href="https://lz5z.com/Spark-RDD/"/>
    <id>https://lz5z.com/Spark-RDD/</id>
    <published>2016-11-29T03:29:07.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h1>RDD 是什么？</h1><img src="/assets/img/spark-stack.png" alt="rdd">[图片摘自[Spark 官网](http://spark.apache.org/)]<p><a href="http://spark.apache.org/docs/latest/programming-guide.html">RDD</a> 全称 <strong>Resilient Distributed Datasets</strong>，是 Spark 中的抽象数据结构类型，任何数据在Spark中都被表示为RDD。 Spark 建立在统一抽象的RDD之上，使得它可以以基本一致的方式应对不同的大数据处理场景，包括MapReduce，Streaming，SQL，Machine Learning 等。</p><span id="more"></span><p>简单的理解就是 RDD 就是一个数据结构，不过这个数据结构中的数据是分布式存储的，Spark 中封装了对 RDD 的各种操作，可以让用户显式地将数据存储到磁盘和内存中，并能控制数据的分区。</p><h2 id="RDD-特性">RDD 特性</h2><p>RDD 是 Spark 的核心，也是整个 Spark 的架构基础。它的特性可以总结如下：</p><ul><li>它是不变的数据结构存储</li><li>它是支持跨集群的分布式数据结构</li><li>可以根据数据记录的key对结构进行分区</li><li>提供了粗粒度的操作，且这些操作都支持分区</li><li>它将数据存储在内存中，从而提供了低延迟性</li></ul><h1>创建 RDD</h1><p>本文中的例子全部基于 <a href="http://spark.apache.org/downloads.html">Spark-shell</a>，需要的请自行安装。</p><p>创建 RDD 主要有两种方式，一种是使用 SparkContext 的 <strong>parallelize</strong> 方法创建并行集合，还有一种是通过外部外部数据集的方法创建，比如本地文件系统，HDFS，HBase，Cassandra等。</p><h2 id="并行集合">并行集合</h2><p>使用 parallelize 方法从普通数组中创建 RDD:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">scala&gt; </span><span class="language-bash">val a = sc.parallelize(1 to 9, 3)</span></span><br><span class="line">a: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at &lt;console&gt;:21</span><br></pre></td></tr></table></figure><p>parallelize 方法接受两个参数，第一个是数据集合，第二个是切片的个数，表示将数据存放在几个分区中。</p><p>一旦创建完成，这个分布式数据集(a)就可以被并行操作。例如，我们可以调用 a.reduce((m, n) =&gt; m + n) 将这个数组中的元素相加。 更多的操作请见 <a href="https://lz5z.com/rdd-operations">Spark RDD 操作</a>。</p><h2 id="本地文件">本地文件</h2><p>文本文件 RDDs 可以使用 SparkContext 的 textFile 方法创建。 在这个方法里传入文件的 URI (机器上的本地路径或 hdfs://，s3n:// 等)，然后它会将文件读取成一个行集合。</p><p>读取文件 test.txt 来创建RDD，文件中的每一行就是RDD中的一个元素。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">scala&gt; </span><span class="language-bash">val b = sc.textFile(<span class="string">&quot;test.txt&quot;</span>)</span></span><br><span class="line">b: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[2] at textFile at &lt;console&gt;:21</span><br></pre></td></tr></table></figure><p>一旦创建完成，(b) 就能做数据集操作。例如，我们可以用下面的方式使用 map 和 reduce 操作将所有行的长度相加： b.map(s =&gt; s.length).reduce((m, n) =&gt; m + n)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">scala&gt; </span><span class="language-bash">b.collect</span></span><br><span class="line">res1: Array[String] = Array(Spark, RDD, Transformations, Actions)</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">scala&gt; </span><span class="language-bash">b.map(s =&gt; s.length).reduce((m, n) =&gt; m + n))</span></span><br><span class="line">res2: Int = 30</span><br></pre></td></tr></table></figure><h3 id="Spark-读文件注意事项">Spark 读文件注意事项</h3><ol><li><p>如果使用本地文件系统路径，文件必须能在 worker 节点上用相同的路径访问到。要么复制文件到所有的 worker 节点，要么使用网络的方式共享文件系统。</p></li><li><p>所有 Spark 的基于文件的方法，包括 textFile，能很好地支持文件目录，压缩过的文件和通配符。例如，你可以使用 textFile(“/文件目录”)，textFile(“/文件*.txt”) 和 textFile(“/文件目录/*.gz”)。</p></li><li><p>textFile 方法也可以选择第二个可选参数来控制切片(slices)的数目。默认情况下，Spark 为每一个文件块(HDFS 默认文件块大小是 64M)创建一个切片(slice)。但是你也可以通过一个更大的值来设置一个更高的切片数目。注意，你不能设置一个小于文件块数目的切片值。</p></li></ol><h3 id="ScalaAPI-对其它数据格式的支持">ScalaAPI 对其它数据格式的支持</h3><ol><li><p>SparkContext.wholeTextFiles 让你读取一个包含多个小文本文件的文件目录并且返回每一个(filename, content)对。与 textFile 的差异是：它记录的是每个文件中的每一行。</p></li><li><p>对于 <a href="http://hadoop.apache.org/docs/current/api/org/apache/hadoop/mapred/SequenceFileInputFormat.html">SequenceFiles</a>，可以使用 SparkContext 的 sequenceFile[K, V] 方法创建，K 和 V 分别对应的是 key 和 values 的类型。像 <a href="http://hadoop.apache.org/docs/current/api/org/apache/hadoop/io/IntWritable.html">IntWritable</a> 与 <a href="http://hadoop.apache.org/docs/current/api/org/apache/hadoop/io/Text.html">Text</a> 一样，它们必须是 Hadoop 的 <a href="http://hadoop.apache.org/docs/current/api/org/apache/hadoop/io/Writable.html">Writable</a> 接口的子类。另外，对于几种通用的 Writables，Spark 允许你指定原生类型来替代。例如： sequenceFile[Int, String] 将会自动读取 IntWritables 和 Text。</p></li><li><p>对于其他的 Hadoop InputFormats，你可以使用 SparkContext.hadoopRDD 方法，它可以指定任意的 JobConf，输入格式(InputFormat)，key 类型，values 类型。你可以跟设置 Hadoop job 一样的方法设置输入源。你还可以在新的 MapReduce 接口(org.apache.hadoop.mapreduce)基础上使用 SparkContext.newAPIHadoopRDD(译者注：老的接口是 SparkContext.newHadoopRDD)。</p></li><li><p>RDD.saveAsObjectFile 和 SparkContext.objectFile 支持保存一个RDD，保存格式是一个简单的 Java<br>对象序列化格式。这是一种效率不高的专有格式，如 Avro，它提供了简单的方法来保存任何一个 RDD。</p></li></ol>]]></content>
    
    
    <summary type="html">&lt;h1&gt;RDD 是什么？&lt;/h1&gt;
&lt;img src=&quot;/assets/img/spark-stack.png&quot; alt=&quot;rdd&quot;&gt;
[图片摘自[Spark 官网](http://spark.apache.org/)]
&lt;p&gt;&lt;a href=&quot;http://spark.apache.org/docs/latest/programming-guide.html&quot;&gt;RDD&lt;/a&gt; 全称 &lt;strong&gt;Resilient Distributed Datasets&lt;/strong&gt;，是 Spark 中的抽象数据结构类型，任何数据在Spark中都被表示为RDD。 Spark 建立在统一抽象的RDD之上，使得它可以以基本一致的方式应对不同的大数据处理场景，包括MapReduce，Streaming，SQL，Machine Learning 等。&lt;/p&gt;</summary>
    
    
    
    <category term="Big Data" scheme="https://lz5z.com/categories/Big-Data/"/>
    
    
    <category term="Spark" scheme="https://lz5z.com/tags/Spark/"/>
    
    <category term="Scala" scheme="https://lz5z.com/tags/Scala/"/>
    
    <category term="RDD" scheme="https://lz5z.com/tags/RDD/"/>
    
  </entry>
  
  <entry>
    <title>深入学习 JavaScript——Object 对象</title>
    <link href="https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84Object%E5%AF%B9%E8%B1%A1/"/>
    <id>https://lz5z.com/JavaScript%E4%B8%AD%E7%9A%84Object%E5%AF%B9%E8%B1%A1/</id>
    <published>2016-11-25T07:52:44.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<h1>Object–JavaScript世界的起源</h1><p>JavaScript的世界中「一切皆是对象」，而所有对象的起源就是 Object 对象。</p><p>神說：「要有光」。就有了光。</p><span id="more"></span><h2 id="Object简介">Object简介</h2><p>JavaScript中的对象其实是一组数据和功能的集合。我们通过执行 new 操作符 + <strong>对象类型的名称</strong>来创建对象。<br>创建 Object 类型的实例并为其添加属性和方法就可以创建自定义对象，Object既是一个对象，也是自身的构造函数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>;  <span class="comment">//如果不给构造函数传递参数可以省略圆括号，但不推荐这么写</span></span><br></pre></td></tr></table></figure><p>仅仅创建 Object 实例并没有什么用处，但关键是理解一个重要的思想，即在JavaScript中，Object 类型是它所有实例的基础，换句话说，Object类型所具有的任何属性和方法同样存在于更具体的对象中。</p><h2 id="Object对象属性">Object对象属性</h2><p>Object 对象一共有三个属性： _<em>proto</em>_, constructor, prototype。</p><h3 id="Object-proto">Object._<em>proto</em>_</h3><ol><li>为对象设置原型</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Rectangle</span>(<span class="params"></span>) &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> rec = <span class="keyword">new</span> <span class="title class_">Rectangle</span>();</span><br><span class="line">rec.<span class="property">__proto__</span> === <span class="title class_">Rectangle</span>.<span class="property"><span class="keyword">prototype</span></span>; <span class="comment">// true</span></span><br><span class="line">rec.<span class="property">__proto__</span> = <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>;</span><br><span class="line">rec.<span class="property">__proto__</span> === <span class="title class_">Rectangle</span>.<span class="property"><span class="keyword">prototype</span></span>; <span class="comment">//false</span></span><br></pre></td></tr></table></figure><ol start="2"><li>_<em>proto</em>_ 属性可用于设置对象的原型</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> proto = &#123; <span class="attr">y</span>: <span class="number">2</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj = &#123; <span class="attr">x</span>: <span class="number">10</span> &#125;;</span><br><span class="line">obj.<span class="property">__proto__</span> = proto;</span><br><span class="line"></span><br><span class="line">proto.<span class="property">y</span> = <span class="number">20</span>;</span><br><span class="line">proto.<span class="property">z</span> = <span class="number">40</span>;</span><br><span class="line"></span><br><span class="line">obj.<span class="property">x</span> === <span class="number">10</span>;  <span class="comment">// true</span></span><br><span class="line">obj.<span class="property">y</span> === <span class="number">20</span>;  <span class="comment">// true</span></span><br><span class="line">obj.<span class="property">z</span> === <span class="number">40</span>;  <span class="comment">// true</span></span><br></pre></td></tr></table></figure><ol start="3"><li>这只适用于可扩展的对象，一个不可扩展的对象的 _<em>proto</em>_ 属性是不可变的</li></ol><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="keyword">let</span> obj = &#123;&#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">preventExtensions</span>(obj);</span><br><span class="line"></span><br><span class="line">obj.<span class="property">__proto__</span> = &#123;&#125;; <span class="comment">// 抛出异常TypeError</span></span><br></pre></td></tr></table></figure><h3 id="Object-constructor">Object.constructor</h3><p>所有对象都会从它的原型上继承一个 constructor 属性， constructor 属性是保存当前对象的构造函数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>; <span class="comment">// 或者 o = &#123;&#125;</span></span><br><span class="line">o.<span class="property">constructor</span> === <span class="title class_">Object</span>; <span class="comment">// true</span></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> <span class="title class_">Array</span>  <span class="comment">// 或者 a = []</span></span><br><span class="line">a.<span class="property">constructor</span> === <span class="title class_">Array</span>; <span class="comment">// true</span></span><br><span class="line"><span class="keyword">let</span> n = <span class="keyword">new</span> <span class="title class_">Number</span>(<span class="number">3</span>); <span class="comment">// 或者 n = 3</span></span><br><span class="line">n.<span class="property">constructor</span> === <span class="title class_">Number</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="Object-prototype">Object.prototype</h3><p>Object.prototype 属性表示对象 Object 的原型对象，由于所有的对象都是基于 Object，所以 <strong>所有的对象都继承了Object.prototype的属性和方法</strong>，除非这些属性和方法被其他原型链更里层的改动所覆盖。</p><ol><li><strong>Object.prototype.hasOwnProperty()</strong></li></ol><p>返回一个布尔值 ，表示某个对象是否含有指定的属性，而且此属性非原型链继承的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">o.<span class="property">name</span> = <span class="string">&#x27;object&#x27;</span>;</span><br><span class="line">o.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;name&#x27;</span>);             <span class="comment">// true</span></span><br><span class="line">o.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;toString&#x27;</span>);         <span class="comment">// false</span></span><br><span class="line">o.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;hasOwnProperty&#x27;</span>);   <span class="comment">// false</span></span><br></pre></td></tr></table></figure><ol start="2"><li><strong>Object.prototype.isPrototypeOf()</strong></li></ol><p>返回一个布尔值，表示指定的对象是否在本对象的原型链中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Rectangle</span>(<span class="params"></span>) &#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> rec = <span class="keyword">new</span> <span class="title class_">Rectangle</span>();</span><br><span class="line"></span><br><span class="line"><span class="title class_">Rectangle</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="title function_">isPrototypeOf</span>(rec); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><ol start="3"><li><strong>Object.prototype.propertyIsEnumerable()</strong></li></ol><p>判断指定属性是否可枚举。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">object.<span class="title function_">propertyIsEnumerable</span>(proName)</span><br></pre></td></tr></table></figure><p>如果 proName 存在于 object 中，且可以使用 for 循环对其进行枚举，则 propertyIsEnumerable 方法返回 true。如果 object 不具有所指定名称的属性或者所指定的属性是不可枚举的，则 propertyIsEnumerable 方法将返回 false。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="string">&quot;apple&quot;</span>, <span class="string">&quot;banana&quot;</span>, <span class="string">&quot;cactus&quot;</span>);</span><br><span class="line">a.<span class="title function_">propertyIsEnumerable</span>(<span class="number">1</span>); <span class="comment">// true，0-2 都是true</span></span><br><span class="line">a.<span class="title function_">propertyIsEnumerable</span>(<span class="number">3</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><ol start="4"><li><strong>Object.prototype.toString()</strong></li></ol><p>返回对象的字符串表示。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> o = &#123;&#125;;</span><br><span class="line">o.<span class="title function_">toString</span>() <span class="comment">// &quot;[object Object]&quot;</span></span><br></pre></td></tr></table></figure><p>上面代码调用空对象的 toString 方法，结果返回一个字符串 <strong>“[object Object]”</strong>，其中第二个Object表示该值的构造函数，<br>实例对象可能会自定义 toString 方法，覆盖掉 Object.prototype.toString 方法。通过函数的 call 方法，可以在任意值上调用 Object.prototype.toString 方法，帮助我们判断这个值的类型。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="number">0</span>) <span class="comment">// &quot;[object Number]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="string">&#x27;&#x27;</span>) <span class="comment">// &quot;[object String]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="literal">true</span>) <span class="comment">// &quot;[object Boolean]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="literal">undefined</span>) <span class="comment">// &quot;[object Undefined]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="literal">null</span>) <span class="comment">// &quot;[object Null]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="title class_">Math</span>) <span class="comment">// &quot;[object Math]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(&#123;&#125;) <span class="comment">// &quot;[object Object]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>([]) <span class="comment">// &quot;[object Array]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="title class_">Symbol</span>()) <span class="comment">//&quot;[object Symbol]&quot;</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(<span class="regexp">/./</span>) <span class="comment">//&quot;[object RegExp]&quot;</span></span><br></pre></td></tr></table></figure><ol start="5"><li><strong>Object.prototype.valueOf()</strong></li></ol><p>返回指定对象的原始值。valueOf() 方法的作用是返回一个对象的“值”，默认情况下返回对象本身。</p><p>valueOf方法的主要用途是，JavaScript自动类型转换时会默认调用这个方法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"><span class="number">1</span> + o <span class="comment">// &quot;1[object Object]&quot;  //默认调用valueOf()方法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//自定义valueOf() 方法</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">valueOf</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="number">1</span> + <span class="keyword">new</span> <span class="title class_">Object</span>; <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><h2 id="Object对象方法">Object对象方法</h2><table><thead><tr><th style="text-align:left">函数</th><th style="text-align:left">描述</th></tr></thead><tbody><tr><td style="text-align:left">Object.assign(target, …sources)</td><td style="text-align:left">将来自一个或多个源对象中的值复制到一个目标对象。</td></tr><tr><td style="text-align:left">Object.create(prototype, descriptors)</td><td style="text-align:left">创建具有指定原型并可选择包含指定属性的对象。</td></tr><tr><td style="text-align:left">Object.defineProperties(obj, props)</td><td style="text-align:left">将一个或多个属性添加到对象，和/或修改现有属性的特性。</td></tr><tr><td style="text-align:left">Object.defineProperty(obj, prop, descriptor)</td><td style="text-align:left">将属性添加到对象，或修改现有属性的特性。</td></tr><tr><td style="text-align:left">Object.freeze(obj)</td><td style="text-align:left">防止修改现有属性的特性和值，并防止添加新属性。</td></tr><tr><td style="text-align:left">Object.getOwnPropertyDescriptor(obj, prop)</td><td style="text-align:left">返回数据属性或访问器属性的定义。</td></tr><tr><td style="text-align:left">Object.getOwnPropertyNames(obj)</td><td style="text-align:left">返回对象属性及方法的名称。</td></tr><tr><td style="text-align:left">Object.getOwnPropertySymbols(obj)</td><td style="text-align:left">返回对象的符号属性。</td></tr><tr><td style="text-align:left">Object.getPrototypeOf(obj)</td><td style="text-align:left">返回对象的原型。</td></tr><tr><td style="text-align:left"><a href="http://Object.is">Object.is</a>(value1, value2)</td><td style="text-align:left">返回一个值，该值指示两个值是否相同。</td></tr><tr><td style="text-align:left">Object.isExtensible(obj)</td><td style="text-align:left">返回指示是否可将新属性添加到对象的值。</td></tr><tr><td style="text-align:left">Object.isFrozen(obj)</td><td style="text-align:left">如果无法在对象中修改现有属性的特性和值，并且无法将新属性添加到对象，则返回 true。</td></tr><tr><td style="text-align:left">Object.seal(obj)</td><td style="text-align:left">防止修改现有属性的特性，并防止添加新属性。</td></tr><tr><td style="text-align:left">Object.isSealed(obj)</td><td style="text-align:left">如果无法在对象中修改现有属性特性，并且无法将新属性添加到对象，则返回 true。</td></tr><tr><td style="text-align:left">Object.keys(obj)</td><td style="text-align:left">返回对象的 <strong>可枚举</strong>属性和方法的名称。</td></tr><tr><td style="text-align:left">Object.preventExtensions(obj)</td><td style="text-align:left">防止向对象添加新属性。</td></tr><tr><td style="text-align:left">Object.setPrototypeOf(obj, prototype)</td><td style="text-align:left">设置对象的原型。</td></tr></tbody></table><hr><h3 id="Object-assign-target-…sources">Object.assign(target, …sources)</h3><p>Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象，然后返回目标对象。</p><p>如果存在分配错误，此函数将引发 TypeError，这将终止复制操作。如果目标属性不可写，则将引发 TypeError。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> first = &#123; <span class="attr">name</span>: <span class="string">&quot;Leo&quot;</span> &#125;;</span><br><span class="line"><span class="keyword">let</span> last = &#123; <span class="attr">lastName</span>: <span class="string">&quot;Li&quot;</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> person = <span class="title class_">Object</span>.<span class="title function_">assign</span>(first, last);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person); <span class="comment">//&#123; name: &quot;Leo&quot;, lastName: &quot;Li&quot; &#125; </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> clone = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, person); <span class="comment">//使用 Object.assign 克隆对象。</span></span><br></pre></td></tr></table></figure><h3 id="Object-create-prototype-descriptors">Object.create(prototype, descriptors)</h3><p>创建一个具有指定原型且包含指定属性的对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> newObj = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>, &#123;</span><br><span class="line">  <span class="attr">size</span>: &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&quot;large&quot;</span>,</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">shape</span>: &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&quot;round&quot;</span>,</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newObj.<span class="property">size</span>); <span class="comment">// large</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newObj.<span class="property">shape</span>); <span class="comment">// round</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">getPrototypeOf</span>(newObj)); <span class="comment">//null</span></span><br></pre></td></tr></table></figure><p>使用Object.create实现类式继承</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Shape</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">x</span> = <span class="number">0</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">y</span> = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Shape</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">move</span> = <span class="keyword">function</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">x</span> += x;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">y</span> += y;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&quot;Shape moved.&quot;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Rectangle - subclass</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Rectangle</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title class_">Shape</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>); <span class="comment">//call super constructor.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Rectangle</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="title class_">Shape</span>.<span class="property"><span class="keyword">prototype</span></span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> rect = <span class="keyword">new</span> <span class="title class_">Rectangle</span>();</span><br><span class="line"></span><br><span class="line">rect <span class="keyword">instanceof</span> <span class="title class_">Rectangle</span> <span class="comment">//true.</span></span><br><span class="line">rect <span class="keyword">instanceof</span> <span class="title class_">Shape</span> <span class="comment">//true.</span></span><br><span class="line"></span><br><span class="line">rect.<span class="title function_">move</span>(<span class="number">1</span>, <span class="number">1</span>); <span class="comment">//Outputs, &quot;Shape moved.&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Object-keys-obj">Object.keys(obj)</h3><p>返回对象可枚举的属性名组成的数组。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = [<span class="string">&quot;Hello&quot;</span>, <span class="string">&quot;World&quot;</span>];</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(a)</span><br><span class="line"><span class="comment">// [&quot;0&quot;, &quot;1&quot;]</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(a)</span><br><span class="line"><span class="comment">// [&quot;0&quot;, &quot;1&quot;, &quot;length&quot;]</span></span><br></pre></td></tr></table></figure><h3 id="Object-getOwnPropertyNames-obj">Object.getOwnPropertyNames(obj)</h3><p>返回一个由指定对象的所有自身属性的属性名（包括不可枚举属性）组成的数组。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>];</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(arr).<span class="title function_">sort</span>(); <span class="comment">//[ &#x27;0&#x27;, &#x27;1&#x27;, &#x27;2&#x27;, &#x27;length&#x27; ]</span></span><br></pre></td></tr></table></figure><h3 id="Object-getOwnPropertySymbols-obj">Object.getOwnPropertySymbols(obj)</h3><p>该特性属于 ECMAScript 2015（ES6）规范。</p><p>Object.getOwnPropertySymbols() 方法会返回一个数组，该数组包含了指定对象自身的（非继承的）所有 symbol 属性键。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = &#123;&#125;;</span><br><span class="line"><span class="keyword">let</span> a = <span class="title class_">Symbol</span>(<span class="string">&quot;a&quot;</span>);</span><br><span class="line"><span class="keyword">let</span> b = <span class="title class_">Symbol</span>.<span class="title function_">for</span>(<span class="string">&quot;b&quot;</span>);</span><br><span class="line"></span><br><span class="line">obj[a] = <span class="string">&quot;a&quot;</span>;</span><br><span class="line">obj[b] = <span class="string">&quot;b&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> objectSymbols = <span class="title class_">Object</span>.<span class="title function_">getOwnPropertySymbols</span>(obj);</span><br><span class="line"></span><br><span class="line">objectSymbols.<span class="property">length</span>; <span class="comment">// 2</span></span><br><span class="line">objectSymbols;        <span class="comment">// [Symbol(a), Symbol(b)]</span></span><br><span class="line">objectSymbols[<span class="number">0</span>];     <span class="comment">// Symbol(a)</span></span><br></pre></td></tr></table></figure><h3 id="对象限制型方法">对象限制型方法</h3><p>ES5中提供了一系列限制对象被修改的方法，用来防止被某些对象被无意间修改导致的错误。每种限制类型包含一个判断方法和一个设置方法。</p><h4 id="阻止对象扩展">阻止对象扩展</h4><p>Object.preventExtensions() 用来限制对象的扩展，设置之后，对象将无法添加新属性。</p><ol><li>对象的属性不可用扩展，但是已存在的属性可以被删除。</li><li>无法添加新属性指的是无法在自身上添加属性，如果是在对象的原型上，还是可以添加属性的。</li><li>Object.isExtensible() 方法用来判断一个对象是否可扩展。</li></ol><h4 id="将对象密封">将对象密封</h4><p>Object.seal() 可以密封一个对象并返回被密封的对象。<br>密封对象无法添加或删除已有属性，也无法修改属性的enumerable，writable，configurable，但是可以修改属性值。</p><p>通过 Object.isSealed() 判断一个对象是否密封。</p><h4 id="冻结对象">冻结对象</h4><p>Object.freeze() 方法用来冻结一个对象，被冻结的对象将无法添加，修改，删除属性值，也无法修改属性的特性值，即这个对象无法被修改。被冻结的对象无法删除自身的属性，但是通过其原型对象还是可以新增属性的。</p><p>通过 Object.isFrozen() 可以用来判断一个对象是否被冻结了。</p><h3 id="其它">其它</h3><p>Object.defineProperties、Object.defineProperty、Object.freeze、Object.getOwnPropertyDescriptor 的用法请参考<a href="https://lz5z.com/Object.defineProperty%E4%B8%BA%E5%AF%B9%E8%B1%A1%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7/">使用Object.defineProperty为对象定义属性</a>。</p><h2 id="总结">总结</h2><p>Object 对象虽然平时我们很少直接用到，但是很多对象的属性和方法都是由 Object 继承而来的，因此非常具有学习意义。<br>这篇 Blog 虽然都是 API 级别的学习，可是很多东西都是欠下的技术债，就当补课了。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;Object–JavaScript世界的起源&lt;/h1&gt;
&lt;p&gt;JavaScript的世界中「一切皆是对象」，而所有对象的起源就是 Object 对象。&lt;/p&gt;
&lt;p&gt;神說：「要有光」。就有了光。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Object" scheme="https://lz5z.com/tags/Object/"/>
    
  </entry>
  
  <entry>
    <title>深入学习 JavaScript——闭包</title>
    <link href="https://lz5z.com/JavaScript%E9%97%AD%E5%8C%85/"/>
    <id>https://lz5z.com/JavaScript%E9%97%AD%E5%8C%85/</id>
    <published>2016-11-24T14:39:45.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h1>什么是闭包（Closure）</h1><p>“函数挂载父环境的时机，如果是定义时就是闭包，如果是执行时就不是闭包。”——听一位大神同事讲的。</p><p>“闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用，但定义在一个封闭的作用域中)。换句话说，这些函数可以“记忆”它被创建时候的环境。”——<a href="https://developer.mozilla.org/cn/docs/Web/JavaScript/Closures">MDN</a></p><p>刚学JavaScript的时候看了这些定义后我就哭了，要想理解闭包还是要看例子。</p><span id="more"></span><h2 id="举个栗子">举个栗子</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> a = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">inner</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(a++);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> inner;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> fun = <span class="title function_">foo</span>();</span><br><span class="line"><span class="title function_">fun</span>(); <span class="comment">//1</span></span><br><span class="line"><span class="title function_">fun</span>(); <span class="comment">//2</span></span><br><span class="line">fun = <span class="literal">null</span>; <span class="comment">//a被垃圾回收</span></span><br></pre></td></tr></table></figure><p>函数 foo 返回一个内部函数 inner，所以“let fun = foo()”的结果应该是“fun = inner” 也就是 “fun = function (){console.log(a++)};”</p><p>那么当执行 fun() 的时候 <strong>a=?</strong>，显然在 fun 的外部环境中是没有 a 的定义的，于是就向 inner 函数定义时候的父环境中找 <strong>a</strong>，果然在 foo 函数中找到了。这样就可以理解上面给出的第一个闭包的定义了：一个函数在执行的时候，如果能拿到定义时候父环境的值，这样就是闭包，反之则不是闭包。</p><p>那闭包究竟是一个什么东西呢？我们可以把闭包理解成 “函数 + 函数创建时的环境”的组合，比如上面的 inner 函数 + 变量a 就是一个闭包。</p><h1>闭包的用途</h1><p>通过使用闭包，我们可以做很多事情。</p><ol><li>JavaScript面向对象</li><li>提升代码效率</li><li>编写更优雅的代码</li></ol><h2 id="匿名自执行函数（立即执行函数表达式）">匿名自执行函数（立即执行函数表达式）</h2><p>匿名自执行函数有两个作用：</p><ol><li>不污染全局变量</li><li>函数执行完立刻释放垃圾回收</li></ol><p>比如我上面栗子中创建的函数 foo 会自动绑定到全局变量中</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="title function_">foo</span>()(); <span class="comment">//1</span></span><br></pre></td></tr></table></figure><p>这样我们每次创建一个函数都必须要使用 const/let/var 去声明一个变量等于函数，不然全局对象的属性会越来越多，从而影响访问速度(因为变量的取值是需要从原型链上遍历的)，而且可能会导致变量冲突。</p><h2 id="结果缓存">结果缓存</h2><p>结果缓存是闭包能显著提高程序效率的一个用途。假如有一个处理过程很耗时的函数对象，我们可以将每次处理的结果缓存起来，当再次调用这个函数的时候，就先从缓存中查找。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cacheSearch = (<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> cache = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">search</span>(<span class="params">key</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (key <span class="keyword">in</span> cache) &#123;</span><br><span class="line">            <span class="keyword">return</span> cache[key];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            cache[key] = <span class="string">`Hello <span class="subst">$&#123;key&#125;</span>`</span>; <span class="comment">//假如这是一步比较复杂的计算</span></span><br><span class="line">            <span class="keyword">return</span> cache[key];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> search;</span><br><span class="line">&#125;)();</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="封装">封装</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> foo = (<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> name = <span class="string">&#x27;name&#x27;</span>; <span class="comment">// “闭包”内的函数可以访问 name 变量，而 name 变量对于外部却是隐藏的</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">getName</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="comment">// 通过定义的接口来访问 name</span></span><br><span class="line">            <span class="keyword">return</span> name;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">setName</span>: <span class="keyword">function</span>(<span class="params">new_name</span>) &#123; <span class="comment">// 通过定义的接口来修改 name</span></span><br><span class="line">            name = new_name;</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">foo.<span class="title function_">getName</span>(); <span class="comment">// 得到 &#x27;name&#x27;</span></span><br><span class="line">foo.<span class="title function_">setName</span>(<span class="string">&#x27;newName&#x27;</span>); <span class="comment">// 通过函数接口，我们访问并修改了 name 变量</span></span><br><span class="line">foo.<span class="title function_">getName</span>(); <span class="comment">// 得到 &#x27;newName&#x27;</span></span><br><span class="line">foo.<span class="property">name</span>; <span class="comment">// Type error，访问不能</span></span><br></pre></td></tr></table></figure><h2 id="实现类和继承">实现类和继承</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">function Person() &#123;</span><br><span class="line">  let name = &#x27;God&#x27;;</span><br><span class="line"></span><br><span class="line">  return &#123;</span><br><span class="line">    getName: function() &#123;</span><br><span class="line">      return name;</span><br><span class="line">    &#125;,</span><br><span class="line">    setName: function(newName) &#123;</span><br><span class="line">      name = newName;</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">let Student = function() &#123;&#125;;</span><br><span class="line">//继承自Person</span><br><span class="line">Student.prototype = new Person();</span><br><span class="line">//添加私有方法</span><br><span class="line">Student.prototype.Say = function(name) &#123;</span><br><span class="line">  console.log(`Hello $&#123;name&#125;`);</span><br><span class="line">&#125;;</span><br><span class="line">let leo = new Student();</span><br><span class="line">leo.setName(&#x27;Leo&#x27;);</span><br><span class="line">leo.Say(&#x27;World&#x27;);</span><br><span class="line">console.log(leo.getName());</span><br></pre></td></tr></table></figure><p>这里的 Person 是一个函数，由于 JavaScript “没有” class 的概念（有 class 关键字）<br>，所以在 JavaScript 中，new 后面跟的是构造函数。<br>上面的代码里面定义了 Student 继承自 Person，所以拥有 getName 方法，然后通过prototype添加自己的方法。</p><h2 id="经典题目">经典题目</h2><p>实现每隔一秒输出一个递增的数字（0 到 5）</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">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">    &#125;, i * <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面这种写法想必大家都知道结果是什么，那就是每隔一秒输出一个5</p><p>使用闭包实现输出数字为 0 到 5</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">    (<span class="function">(<span class="params">a</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(a)</span><br><span class="line">        &#125;, a * <span class="number">1000</span>)</span><br><span class="line">    &#125;)(i)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还有一种使用闭包的方式是使用 Array 的 forEach 循环，forEach 里的执行函数也行成了一个闭包</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="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>].<span class="title function_">forEach</span>(<span class="function">(<span class="params">i</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">    &#125;, i * <span class="number">1000</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>当然使用 ES6 的 let 才是最好的选择</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">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">    &#125;, i * <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="闭包总结">闭包总结</h2><p>闭包三个特性：</p><ol><li>函数嵌套函数</li><li>函数内部可以引用外部的参数和变量</li><li>参数和变量不会被垃圾回收机制回收</li></ol><p>闭包的优点：</p><ol><li>希望一个变量长期驻扎在内存中</li><li>避免全局变量的污染</li><li>私有成员的存在</li></ol><p>闭包的缺点：</p><ol><li>闭包的缺点就是常驻内存，会增大内存使用量，使用不当很容易造成内存泄露。</li></ol>]]></content>
    
    
    <summary type="html">&lt;h1&gt;什么是闭包（Closure）&lt;/h1&gt;
&lt;p&gt;“函数挂载父环境的时机，如果是定义时就是闭包，如果是执行时就不是闭包。”——听一位大神同事讲的。&lt;/p&gt;
&lt;p&gt;“闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用，但定义在一个封闭的作用域中)。换句话说，这些函数可以“记忆”它被创建时候的环境。”——&lt;a href=&quot;https://developer.mozilla.org/cn/docs/Web/JavaScript/Closures&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;刚学JavaScript的时候看了这些定义后我就哭了，要想理解闭包还是要看例子。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="闭包" scheme="https://lz5z.com/tags/%E9%97%AD%E5%8C%85/"/>
    
  </entry>
  
  <entry>
    <title>在 Hexo 中插入音乐与视频</title>
    <link href="https://lz5z.com/Music-Video/"/>
    <id>https://lz5z.com/Music-Video/</id>
    <published>2016-11-22T13:28:11.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h2 id="网易云音乐iframe">网易云音乐iframe</h2><p>首先打开网易云音乐首页找到你想要的音药，点击 「生成外链播放器」</p><img src="/assets/img/hexo_music.png" alt="我是一只的图片"><p>选择合适的尺寸后将生成的 iframe 插件或者 flash 插件代码复制到 markdown 中即可。</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=26085700&auto=0&height=66"></iframe><span id="more"></span><h2 id="腾讯视频">腾讯视频</h2><iframe frameborder="0" width="640" height="498" src="https://v.qq.com/iframe/player.html?vid=f0022xw9le3&tiny=0&auto=0" allowfullscreen></iframe><h2 id="优酷视频">优酷视频</h2><p>优酷暂时没有 https，这个比较讨厌。</p><h2 id="B站">B站</h2><p>B 站的视频，找到想要分享的视频，点击下方的分享即可。</p><p><embed height="415" width="544" quality="high" allowfullscreen="true" type="application/x-shockwave-flash" src="//static.hdslb.com/miniloader.swf" flashvars="aid=18827465&page=1" pluginspage="//www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash"></embed></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;网易云音乐iframe&quot;&gt;网易云音乐iframe&lt;/h2&gt;
&lt;p&gt;首先打开网易云音乐首页找到你想要的音药，点击 「生成外链播放器」&lt;/p&gt;
&lt;img src=&quot;/assets/img/hexo_music.png&quot; alt=&quot;我是一只的图片&quot;&gt;
&lt;p&gt;选择合适的尺寸后将生成的 iframe 插件或者 flash 插件代码复制到 markdown 中即可。&lt;/p&gt;
&lt;iframe frameborder=&quot;no&quot; border=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; width=330 height=86 src=&quot;//music.163.com/outchain/player?type=2&amp;id=26085700&amp;auto=0&amp;height=66&quot;&gt;&lt;/iframe&gt;</summary>
    
    
    
    <category term="Blog" scheme="https://lz5z.com/categories/Blog/"/>
    
    
    <category term="Hexo" scheme="https://lz5z.com/tags/Hexo/"/>
    
    <category term="Music" scheme="https://lz5z.com/tags/Music/"/>
    
    <category term="Video" scheme="https://lz5z.com/tags/Video/"/>
    
  </entry>
  
  <entry>
    <title>使用 Object.defineProperty 为对象定义属性</title>
    <link href="https://lz5z.com/Object.defineProperty%E4%B8%BA%E5%AF%B9%E8%B1%A1%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7/"/>
    <id>https://lz5z.com/Object.defineProperty%E4%B8%BA%E5%AF%B9%E8%B1%A1%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7/</id>
    <published>2016-11-21T14:38:27.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h1>先说句题外话</h1><p>目前前端开发中比较流行的两个框架： <a href="https://angularjs.org/">Angular</a> 和 <a href="https://cn.vuejs.org/">Vue</a> 都采用了数据双向绑定的技术。<br>Angular1 中数据双向绑定是通过「脏检测」的方式实现，每当数据发生变更，对所有的数据和视图的绑定关系进行一次检测，识别是否有数据发生了变化以及这个变化是否会影响其它数据的变化，然后将变更的数据发送到视图，更新页面展示。</p><p>Vue 数据双向绑定的原理与Angular有所不同，网上人称「数据劫持」<img src="/assets/img/scary.gif" alt="scary" width="5%">。Vue使用的是 ES5 提供的 Object.defineProperty() 结合发布者-订阅者模式，通过Object.defineProperty() 来劫持各个属性的setter，getter，在数据变动时发布消息给订阅者，触发相应的监听回调。</p><span id="more"></span><h1>Object.defineProperty()</h1><h2 id="定义以及使用">定义以及使用</h2><p>Object.defineProperty() 方法会直接在一个对象上定义一个新属性，或者修改一个已经存在的属性， 并返回这个对象。</p><p>我们来看下一般使用方法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Leo</span>.<span class="property">name</span>); <span class="comment">//Leo</span></span><br></pre></td></tr></table></figure><p>其基本语法规则如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(obj, prop, descriptor)</span><br></pre></td></tr></table></figure><ol><li>obj: 需要定义属性的对象。</li><li>prop: 需定义或修改的属性的名字。</li><li>descriptor: 将被定义或修改的属性的描述符。</li><li>返回值: 返回传入函数的对象，即第一个参数obj</li></ol><p>所以 <strong>Object.defineProperty(obj, ‘name’, { value: ‘Leo’})</strong> 相当于 <strong><a href="http://obj.name">obj.name</a> = ‘Leo’</strong> 或者 **obj[‘name’] = ‘Leo’**喽。</p><p>那我们直接使用「对象.属性」就好了，为什么要用 Object.defineProperty 这么复杂的方法呢？</p><h2 id="Object-defineProperty-解决什么问题">Object.defineProperty 解决什么问题</h2><p>如果你想定义一个对象的属性为只读怎么办？<br>「对象.属性」能做到吗？显然不能！Object.defineProperty 却可以做到。因此 <strong>Object.defineProperty 方法是对属性更加精确的定义</strong>。</p><h3 id="属性的状态设置">属性的状态设置</h3><p>我们可以在descriptor参数中设置如下值，来实现对属性的控制：</p><ul><li>value：默认为 undefined。该属性的值。</li><li>writable：默认为 false。该属性是否可写，如果设置成 false，则任何对该属性改写的操作都无效（<a href="https://lz5z.com/JavaScript%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/">严格模式</a>会报错，正常模式则什么都不做）</li><li>configurable：默认为 false。当且仅当该属性的 configurable 为 true 时，该属性描述符才能够被改变，也能够被删除。</li><li>enumerable：默认为 false。当且仅当该属性的 enumerable 为 true 时，该属性才能够出现在对象的枚举属性中（for…in 或者 Object.keys）</li><li>get: 默认为 undefined。一个给属性提供 getter 的方法。该方法返回值被用作属性值。</li><li>set: 默认为 undefined。一个给属性提供 setter 的方法。该方法将接受唯一参数，并将该参数的新值分配给该属性。</li></ul><h3 id="value、writable">value、writable</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">writable</span>: <span class="literal">true</span>, <span class="comment">//writable 为true的时候name属性才可以被更改</span></span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Leo</span>.<span class="property">name</span> = <span class="string">&#x27;Jack&#x27;</span>; <span class="comment">//strict mode下修改writable为false的属性会报错</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Leo</span>.<span class="property">name</span>);</span><br></pre></td></tr></table></figure><h3 id="configurable">configurable</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">delete</span> <span class="title class_">Leo</span>.<span class="property">name</span>; <span class="comment">//configurable为false的时候删除属性会报错</span></span><br></pre></td></tr></table></figure><p>configurable 参数不仅负责属性的删除，也与属性修改有关。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Leo</span>, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>, <span class="comment">// Cannot redefine property: name</span></span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Jack&#x27;</span>, <span class="comment">//Cannot redefine property: name</span></span><br><span class="line">    <span class="attr">writable</span>: <span class="literal">true</span>, <span class="comment">//Cannot redefine property: name</span></span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span> <span class="comment">//Cannot redefine property: name</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>假如一个属性被定义成 configurable 为 false，则这个属性既不能修改值（value），又不能修改属性的属性（configurable，writable，enumerable）；如果 configurable 为 true 就可以放心修改了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(<span class="title class_">Leo</span>, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Jack&#x27;</span>,</span><br><span class="line">    <span class="attr">writable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="enumerable">enumerable</h3><p>属性特性 enumerable 定义了对象的属性是否可以在 for…in 循环和 Object.keys() 中被枚举。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> o = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&quot;a&quot;</span>, &#123;<span class="attr">value</span>: <span class="number">1</span>, <span class="attr">enumerable</span>: <span class="literal">true</span>&#125;);</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(o, <span class="string">&quot;b&quot;</span>, &#123;<span class="attr">value</span>: <span class="number">2</span>, <span class="attr">enumerable</span>: <span class="literal">false</span>&#125;);</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(o, <span class="string">&quot;c&quot;</span>, &#123;<span class="attr">value</span>: <span class="number">3</span>&#125;); <span class="comment">// enumerable defaults to false</span></span><br><span class="line">o.<span class="property">d</span> = <span class="number">4</span>; <span class="comment">// 如果使用直接赋值的方式创建对象的属性，则这个属性的enumerable为true</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> o) &#123;    </span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// &quot;a&quot; &quot;d&quot; </span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(o); <span class="comment">// [&quot;a&quot;, &quot;d&quot;]</span></span><br><span class="line"></span><br><span class="line">o.<span class="title function_">propertyIsEnumerable</span>(<span class="string">&#x27;a&#x27;</span>); <span class="comment">// true</span></span><br><span class="line">o.<span class="title function_">propertyIsEnumerable</span>(<span class="string">&#x27;b&#x27;</span>); <span class="comment">// false</span></span><br><span class="line">o.<span class="title function_">propertyIsEnumerable</span>(<span class="string">&#x27;c&#x27;</span>); <span class="comment">// false</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="get、set">get、set</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> name = <span class="string">&#x27;Leo&#x27;</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">get</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;get&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">set</span>: <span class="keyword">function</span>(<span class="params">newName</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;set&#x27;</span>);</span><br><span class="line">        name = newName;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">enumerable</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">configurable</span>: <span class="literal">true</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Leo</span>.<span class="property">name</span> = <span class="string">&#x27;Jack&#x27;</span>; <span class="comment">// &#x27;set&#x27;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Leo</span>.<span class="property">name</span>); <span class="comment">// &#x27;get&#x27; &#x27;Jack&#x27;</span></span><br></pre></td></tr></table></figure><p>在对Leo.name进行赋值的时候，其实是调用了name的set方法；而使用Leo.name的时候则调用了get方法。这就是Vue数据双向绑定的原理：每当数据发生改变，其实是调用了set方法，set方法里面发布数据变动的消息给订阅者，触发相应的监听回调。</p><p>注意： 如果 get 方法与 value 同时出现，会报错。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> name = <span class="string">&#x27;Leo&#x27;</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">value</span>: name, <span class="comment">// A property cannot both have accessors and be writable or have a value</span></span><br><span class="line">    <span class="attr">get</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h1>相关的方法</h1><h2 id="Object-getOwnPropertyDescriptor-obj-prop">Object.getOwnPropertyDescriptor(obj, prop)</h2><p>Object.getOwnPropertyDescriptor() 返回指定对象上一个自有属性对应的属性描述符。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title class_">Leo</span> = <span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(&#123;&#125;, <span class="string">&#x27;name&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;Leo&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(<span class="title class_">Leo</span>, <span class="string">&#x27;name&#x27;</span>)); </span><br><span class="line"><span class="comment">//&#123; value: &#x27;Leo&#x27;,</span></span><br><span class="line"><span class="comment">//  writable: false,</span></span><br><span class="line"><span class="comment">//  enumerable: false,</span></span><br><span class="line"><span class="comment">//  configurable: false &#125;</span></span><br></pre></td></tr></table></figure><h2 id="Object-defineProperties-object-descriptors">Object.defineProperties(object, descriptors)</h2><p>Object.defineProperties 与 Object.defineProperty 作用相同，不过可以同时将多个属性添加/修改到对象。</p><h2 id="Object-freeze-obj">Object.freeze(obj)</h2><p>Object.freeze() 方法可以冻结一个对象，冻结指的是不能向这个对象添加新的属性，不能修改其已有属性的值，不能删除已有属性，以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说，这个对象永远是不可变的。该方法返回被冻结的对象。</p><h1>最后</h1><p>了解了 Object.defineProperty 的用法，接下来就是写一个自己的 Vue.js 了。敬请期待。<img src="/assets/img/smiling.png" alt="smiling"></p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;先说句题外话&lt;/h1&gt;
&lt;p&gt;目前前端开发中比较流行的两个框架： &lt;a href=&quot;https://angularjs.org/&quot;&gt;Angular&lt;/a&gt; 和 &lt;a href=&quot;https://cn.vuejs.org/&quot;&gt;Vue&lt;/a&gt; 都采用了数据双向绑定的技术。&lt;br&gt;
Angular1 中数据双向绑定是通过「脏检测」的方式实现，每当数据发生变更，对所有的数据和视图的绑定关系进行一次检测，识别是否有数据发生了变化以及这个变化是否会影响其它数据的变化，然后将变更的数据发送到视图，更新页面展示。&lt;/p&gt;
&lt;p&gt;Vue 数据双向绑定的原理与Angular有所不同，网上人称「数据劫持」&lt;img src=&quot;/assets/img/scary.gif&quot; alt=&quot;scary&quot; width=&quot;5%&quot;&gt;。Vue使用的是 ES5 提供的 Object.defineProperty() 结合发布者-订阅者模式，通过Object.defineProperty() 来劫持各个属性的setter，getter，在数据变动时发布消息给订阅者，触发相应的监听回调。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Object" scheme="https://lz5z.com/tags/Object/"/>
    
    <category term="defineProperty" scheme="https://lz5z.com/tags/defineProperty/"/>
    
  </entry>
  
  <entry>
    <title>函数柯里化</title>
    <link href="https://lz5z.com/%E5%87%BD%E6%95%B0%E6%9F%AF%E9%87%8C%E5%8C%96/"/>
    <id>https://lz5z.com/%E5%87%BD%E6%95%B0%E6%9F%AF%E9%87%8C%E5%8C%96/</id>
    <published>2016-11-20T03:00:36.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<h2 id="什么是柯里化">什么是柯里化</h2><p>柯里化是一种函数的转换，它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)©。</p><p>柯里化不会调用函数。它只是对函数进行转换。</p><h3 id="实现">实现</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 请实现 curry 函数，实现函数柯里化</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">curry</span>(<span class="params">fn</span>) &#123;</span><br><span class="line">    <span class="comment">// 代码</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b, c</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> a + b + c;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> $add = <span class="title function_">curry</span>(add);</span><br><span class="line"></span><br><span class="line">$add(<span class="number">2</span>)(<span class="number">3</span>)(<span class="number">4</span>); <span class="comment">// 9</span></span><br><span class="line">$add(<span class="number">2</span>)(<span class="number">3</span>, <span class="number">4</span>); <span class="comment">// 9</span></span><br><span class="line">$add(<span class="number">2</span>, <span class="number">3</span>)(<span class="number">4</span>); <span class="comment">// 9</span></span><br></pre></td></tr></table></figure><p>具体实现如下</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">curry</span>(<span class="params">fn</span>) &#123;</span><br><span class="line">    <span class="comment">// curry 返回一个函数。这里没有选择匿名函数是因为下面有递归调用</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span> <span class="title function_">curried</span> (<span class="params">...args</span>) &#123;</span><br><span class="line">        <span class="comment">// 如果传入的参数个数满足函数的形参要求，直接调用</span></span><br><span class="line">        <span class="keyword">if</span> (args.<span class="property">length</span> &gt;= fn.<span class="property">length</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> fn.<span class="title function_">apply</span>(<span class="variable language_">this</span>, args);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 如果不满足个数要求，返回一个新的函数，函数内部递归调用 curried</span></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params">...args1</span>) &#123;</span><br><span class="line">                <span class="keyword">const</span> newArgs = args.<span class="title function_">concat</span>(args1);</span><br><span class="line">                <span class="keyword">return</span> curried.<span class="title function_">apply</span>(<span class="variable language_">this</span>, newArgs);</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>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;什么是柯里化&quot;&gt;什么是柯里化&lt;/h2&gt;
&lt;p&gt;柯里化是一种函数的转换，它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)©。&lt;/p&gt;
&lt;p&gt;柯里化不会调用函数。它只是对函数进行转换。&lt;/p&gt;
&lt;h3 id=&quot;实现&quot;&gt;实现&lt;/h3&gt;
&lt;</summary>
      
    
    
    
    <category term="Blog" scheme="https://lz5z.com/categories/Blog/"/>
    
    
    <category term="curry" scheme="https://lz5z.com/tags/curry/"/>
    
    <category term="高阶函数" scheme="https://lz5z.com/tags/%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0/"/>
    
  </entry>
  
  <entry>
    <title>浏览器事件循环</title>
    <link href="https://lz5z.com/%E6%B5%8F%E8%A7%88%E5%99%A8%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/"/>
    <id>https://lz5z.com/%E6%B5%8F%E8%A7%88%E5%99%A8%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/</id>
    <published>2016-11-19T03:00:36.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>宏任务 &amp; 微任务</h1><p>JS 把异步任务分为宏任务和微任务。ES5 之后，JavaScript 引入 Promise，不需要浏览器，JavaScript 引擎自身也可以发起异步任务了。</p><h2 id="宏任务由宿主（浏览器，Node）发起">宏任务由宿主（浏览器，Node）发起</h2><p>宏任务包括 script，事件，网络请求（AJAX/Fetch），setTimeout，setInterval，setImmediate，I/O、UI 交互事件由宿主发起。</p><h2 id="微任务由-JS-引擎发起。">微任务由 JS 引擎发起。</h2><p>Promise.then, Promise.catch, Promise.finally, MutationObserver 是微任务，由 JS 引擎发起。</p><p><strong>注意：Promise 本身是同步的，但是其 then/catch 的回调是异步的。</strong></p><p>宏任务、微任务执行过程</p><ol><li>同步代码（js 执行/回调栈）</li><li>微任务的异步代码（js 引擎）<br>promise.nextTick(node)<br>Promise.then() Promise.catch()<br>Async/Await<br>Object.observe 等等</li><li>宏任务的异步代码（宿主环境）<br>script(代码块)<br>setTimeout，setInterval，setImmediate</li></ol><p>一次事件循环 = (同步代码 + 微任务队列 + 宏任务队列) 放入执行栈，先进先出，执行完成。</p><h2 id="举例">举例</h2><h3 id="1-执行顺序">1. 执行顺序</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;a&quot;</span>);</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;b&quot;</span>);</span><br><span class="line">&#125;, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;c&quot;</span>);</span><br><span class="line">  <span class="title function_">resolve</span>(<span class="number">1000</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;d&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(data);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;e&quot;</span>);</span><br></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">a</span><br><span class="line">c</span><br><span class="line">d</span><br><span class="line">e</span><br><span class="line">1000</span><br><span class="line">b</span><br></pre></td></tr></table></figure><h3 id="2-执行顺序">2. 执行顺序</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Promise</span>.<span class="title function_">resolve</span>().<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 微任务1</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Promise1&quot;</span>);</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 宏任务2</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;setTimeout2&quot;</span>);</span><br><span class="line">  &#125;, <span class="number">0</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 宏任务1</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;setTimeout1&quot;</span>);</span><br><span class="line">  <span class="title class_">Promise</span>.<span class="title function_">resolve</span>().<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 微任务2</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Promise2&quot;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Promise1</span><br><span class="line">setTimeout1</span><br><span class="line">Promise2</span><br><span class="line">setTimeout2</span><br></pre></td></tr></table></figure><p>最后输出顺序为：Promise1 =&gt; setTimeout1 =&gt; Promise2 =&gt; setTimeout2。具体流程如下：</p><p>同步任务执行完毕。微任务 1 进入微任务队列，宏任务 1 进入宏任务队列。<br>查看微任务队列，微任务 1 执行，打印 Promise1，生成宏任务 2，进入宏任务队列。<br>查看宏任务队列，宏任务 1 执行，打印 setTimeout1，生成微任务 2，进入微任务队列。<br>查看微任务队列，微任务 2 执行，打印 Promise2。<br>查看宏任务队列，宏任务 2 执行，打印 setTimeout2。</p><h3 id="3-执行顺序">3. 执行顺序</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="title function_">resolve</span>();</span><br><span class="line">&#125;);</span><br><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">&#125;)</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>);</span><br><span class="line">&#125;);</span><br><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">5</span>);</span><br><span class="line">&#125;);</span><br><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">6</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>输出顺序：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">0</span><br><span class="line">1</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td></tr></table></figure><h3 id="4-执行顺序">4. 执行顺序</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">async1</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;async1 start&quot;</span>); <span class="comment">// 2</span></span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">async2</span>();</span><br><span class="line">  <span class="comment">// 微任务</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;async1 end&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">async2</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;async2&quot;</span>); <span class="comment">// 3</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;script start&quot;</span>); <span class="comment">// 1</span></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 宏任务</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;setTimeout&quot;</span>);</span><br><span class="line">&#125;, <span class="number">0</span>);</span><br><span class="line"><span class="title function_">async1</span>();</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span> (<span class="params">resolve</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;promise1&quot;</span>); <span class="comment">// 4</span></span><br><span class="line">  <span class="title function_">resolve</span>();</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 微任务</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;promise2&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;script end&quot;</span>); <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>输出顺序：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">script start</span><br><span class="line">async1 start</span><br><span class="line">async2</span><br><span class="line">promise1</span><br><span class="line">script end</span><br><span class="line">async1 end</span><br><span class="line">promise2</span><br><span class="line">setTimeout</span><br></pre></td></tr></table></figure><h3 id="5-执行任务">5. 执行任务</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">async1</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;async1 start&quot;</span>); <span class="comment">// 2</span></span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">async2</span>();</span><br><span class="line">  <span class="comment">// 微任务</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;async1 end&quot;</span>); <span class="comment">// 这里不会执行</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">async2</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;async2 start&quot;</span>); <span class="comment">// 3</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">reject</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;async2 promise&quot;</span>); <span class="comment">// 4</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;illegalscript start&quot;</span>); <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 宏任务</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;setTimeout&quot;</span>);</span><br><span class="line">&#125;, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="title function_">async1</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span> (<span class="params">resolve</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;promise1&quot;</span>); <span class="comment">// 5</span></span><br><span class="line">  <span class="title function_">resolve</span>();</span><br><span class="line">&#125;)</span><br><span class="line">  .<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 微任务</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;promise2&quot;</span>);</span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 微任务</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;promise3&quot;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;illegalscript end&quot;</span>); <span class="comment">// 6</span></span><br></pre></td></tr></table></figure><p>输出顺序</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">illegalscript start</span><br><span class="line">async1 start</span><br><span class="line">async2 start</span><br><span class="line">async2 promise</span><br><span class="line">promise1</span><br><span class="line">illegalscript end</span><br><span class="line">promise2</span><br><span class="line">promise3</span><br><span class="line">setTimeout</span><br></pre></td></tr></table></figure><h3 id="6-执行任务">6. 执行任务</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">resolve</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">resolve</span>(<span class="number">2</span>);</span><br><span class="line">  &#125;).<span class="title function_">then</span>(<span class="variable language_">console</span>.<span class="property">log</span>);</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="variable language_">console</span>.<span class="property">log</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>);</span><br></pre></td></tr></table></figure><p>输出结果</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">3</span>;</span><br><span class="line"><span class="number">2</span>;</span><br><span class="line"><span class="number">1</span>;</span><br></pre></td></tr></table></figure><h3 id="7-执行任务">7. 执行任务</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 宏任务</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">&#125;, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>); <span class="comment">// 1</span></span><br><span class="line">  <span class="title function_">resolve</span>(<span class="string">&#x27;p1&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>); <span class="comment">// 2</span></span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 宏任务</span></span><br><span class="line">      <span class="title function_">resolve</span>(<span class="string">&#x27;setTimeout2&#x27;</span>);</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>);</span><br><span class="line">    &#125;, <span class="number">0</span>);</span><br><span class="line">    <span class="title function_">resolve</span>(<span class="string">&#x27;p2&#x27;</span>);</span><br><span class="line">  &#125;).<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 微任务</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data); <span class="comment">// p2</span></span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 宏任务</span></span><br><span class="line">    <span class="title function_">resolve</span>(<span class="string">&#x27;setTimeout1&#x27;</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">5</span>);</span><br><span class="line">  &#125;, <span class="number">0</span>);</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 微任务</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(data); <span class="comment">// 4</span></span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">6</span>); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>输出结果</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">2</span><br><span class="line">3</span><br><span class="line">6</span><br><span class="line">p2</span><br><span class="line">p1</span><br><span class="line">1</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td></tr></table></figure><h1>EventLoop</h1><ol><li>JS 是单线程的，同一个时间只能做一件事情</li><li>同步代码：立即放入 JS 引擎（JS 主线程）执行，并原地等待结果。</li><li>异步代码：先放入宿主环境（浏览器/Node），不必原地等待结果，并不阻塞主线程继续执行，异步结果在将来执行。异步任务等待时机到了以后把代码（回调函数）交给任务队列去排队执行。</li><li>每次事件循环就是执行栈里面代码执行完毕后，去查看任务队列里面是否还有其他任务需要执行，如果有，就按顺序依次执行。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;宏任务 &amp;amp; 微任务&lt;/h1&gt;
&lt;p&gt;JS 把异步任务分为宏任务和微任务。ES5 之后，JavaScript 引入 Promise，不需要浏览器，JavaScript 引擎自身也可以发起异步任务了。&lt;/p&gt;
&lt;h2 id=&quot;宏任务由宿主（浏览器，Node）发起&quot;&gt;宏</summary>
      
    
    
    
    <category term="Blog" scheme="https://lz5z.com/categories/Blog/"/>
    
    
    <category term="事件循环" scheme="https://lz5z.com/tags/%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/"/>
    
    <category term="宏任务" scheme="https://lz5z.com/tags/%E5%AE%8F%E4%BB%BB%E5%8A%A1/"/>
    
    <category term="微任务" scheme="https://lz5z.com/tags/%E5%BE%AE%E4%BB%BB%E5%8A%A1/"/>
    
    <category term="eventloop" scheme="https://lz5z.com/tags/eventloop/"/>
    
  </entry>
  
  <entry>
    <title>使用 Travis CI 自动部署 Hexo</title>
    <link href="https://lz5z.com/%E4%BD%BF%E7%94%A8TravisCI%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2Hexo/"/>
    <id>https://lz5z.com/%E4%BD%BF%E7%94%A8TravisCI%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2Hexo/</id>
    <published>2016-11-18T02:00:36.000Z</published>
    <updated>2026-05-07T14:50:53.974Z</updated>
    
    <content type="html"><![CDATA[<h1>Travis CI</h1><p><a href="https://travis-ci.org/">Travis CI</a> 是一个持续集成的平台，我们可以使用其自动构建部署的功能帮我们简化 Hexo 博客的部署流程。</p><h2 id="为什么要用-Travis-CI">为什么要用 Travis CI</h2><p>因为懒。 <img src="/assets/img/what_can_i_say.jpg" alt="what_can_i_say"></p><p>Hexo 部署 Blog 到 GitPage 通常需要三部曲：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">hexo clean</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">hexo g</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">hexo deploy</span></span><br></pre></td></tr></table></figure><span id="more"></span><p>很简单吧，但是如果是一个新的环境，你需要安装一大堆工具和依赖，比如要装 Node，要装 Hexo，还有 package.json 里面的各种依赖，虽然 Npm 提供了强大的包管理功能，但是有时候就是不方便。</p><p>使用 Travis，你只需要本地有一个 git 就可以了。</p><p>每当你 Push 一个 commit 到 Github 时，Travis CI 会检测到你的提交，并根据配置文件自动运行一些命令，通常这些命令用于测试，构建等等。</p><p>那么在我们的需求下，就可以用它运行一些 hexo deploy -g 之类的命令用来自动生成、部署我们的网站。</p><h1>使用方法</h1><p>使用 Travis 构建 Hexo 只需要三步：</p><ol><li>登录 Travis，配置仓库</li><li>在 Travis CI 配置 GitHub 的 Access Token</li><li>Blog 根目录下配置 .travis.yml</li></ol><h2 id="配置-Travis-仓库">配置 Travis 仓库</h2><p>首先使用 GitHub 账号登录<a href="https://travis-ci.org/">Travis CI</a>，登录后会进入如下页面</p><img src="/assets/img/Travis_main_page.png" alt="我是一只的图片"><p>点击「My Repositories」后面的 <strong>+</strong>，添加要自动构建的仓库</p><img src="/assets/img/Travis.png" alt="我是一只的图片"><p>这里会显示你 GitHub 下所有的项目，选中博客仓库，我的博客在GitHub上的仓库名字就叫做 <strong>Blog</strong>。然后点击仓库名进入仓库配置页面。</p><img src="/assets/img/Travis_settings.png" alt="我是一只的图片"><p>选择 Settings，配置选择如下：</p><img src="/assets/img/Travis_general_settings.png" alt="我是一只的图片"><ul><li>Build only if .travis.yml is present：是只有在 .travis.yml 文件中配置的分支改变了才构建</li><li>Build pushes：当推送完这个分支后开始构建</li></ul><p>这个时候，我们已经开启要构建的仓库，但是如何将构建完成后的文件推送到 Github 上呢？</p><h2 id="GitHub-Access-Token">GitHub Access Token</h2><p>Github 支持一种特殊的 URL 来执行 push/pull 等等操作，而不需要输入用户名密码。但这需要事先在 Github 上创建一个 token。</p><p>首先去 GitHub Settings 页面选择 <a href="https://github.com/settings/tokens"><strong>Personal access tokens</strong></a>，如果你已经登录了，点击链接进去即可。</p><img src="/assets/img/Travis_generate_token.png" alt="我是一只的图片"><p>选择 <strong>Generate new token</strong>，配置如下：</p><img src="/assets/img/Travis_token.png" alt="我是一只的图片"><p>点击绿色确认按钮，copy 刚刚生成的 token。回到 Travis Settings 页面，将复制的 token 加入到环境变量，并命名为  <strong>GitHub_token</strong>。</p><img src="/assets/img/Travis_add_token.png" alt="我是一只的图片"><h2 id="travis-yml">.travis.yml</h2><p>上述步骤完成后，只需要在你 Blog 源代码的根目录下增加一个 <strong>.travis.yml</strong> 文件，<br>我的文件内容如下：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">language:</span> <span class="string">node_js</span></span><br><span class="line"><span class="attr">node_js:</span> <span class="string">stable</span></span><br><span class="line"></span><br><span class="line"><span class="attr">install:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line"></span><br><span class="line"><span class="attr">script:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">hexo</span> <span class="string">clean</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">hexo</span> <span class="string">g</span></span><br><span class="line"></span><br><span class="line"><span class="attr">after_script:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">cd</span> <span class="string">./public</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">git</span> <span class="string">init</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">git</span> <span class="string">config</span> <span class="string">user.name</span> <span class="string">&quot;YOUR GITHUB USER NAME&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">git</span> <span class="string">config</span> <span class="string">user.email</span> <span class="string">&quot;YOUR GITHUB EMAIL&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">git</span> <span class="string">add</span> <span class="string">.</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">git</span> <span class="string">commit</span> <span class="string">-m</span> <span class="string">&quot;Update&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">git</span> <span class="string">push</span> <span class="string">--force</span> <span class="string">--quiet</span> <span class="string">&quot;https://$&#123;GitHub_token&#125;@$&#123;GH_REF&#125;&quot;</span> <span class="string">master:master</span></span><br><span class="line"></span><br><span class="line"><span class="attr">branches:</span></span><br><span class="line">  <span class="attr">only:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">master</span></span><br><span class="line"><span class="attr">env:</span></span><br><span class="line"> <span class="attr">global:</span></span><br><span class="line">   <span class="bullet">-</span> <span class="attr">GH_REF:</span> <span class="string">github.com/Leo555/Leo555.github.io.git</span></span><br></pre></td></tr></table></figure><p>将上面的 name 和 email 还有 GH_REF 修改成你自己的。</p><p>这里用 Linux 环境变量的引用方式将 GH_REF 和 GitHub_token 其引入 git push 的 url，因此 push 方法就能通过 GitHub OAuth 授权，完成自动 push 的功能。</p><p>此时就万事俱备了。</p><h1>测试</h1><p>使用 Hexo 创建新的 Blog 文件，然后 push 到 GitHub 上。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">hexo new test.md</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git add .</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git commit -m <span class="string">&quot;add new post test&quot;</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git push origin master</span></span><br></pre></td></tr></table></figure><p>然后回到 Travis 主页面，发现部署已经开始了</p><img src="/assets/img/Travis_deploy.png" alt="我是一只图片"><p>在下面的 log 中可以看到部署的详细情况。</p><p>包括 <strong>nvm install</strong>，<strong>npm install</strong>，<strong>hexo g</strong> 等命令都在这里执行。</p><h1>总结</h1><p>有了自动部署的功能，从此以后就可以将关注点集中在博客内容上，换了平台和环境也没有任何影响。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;Travis CI&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis CI&lt;/a&gt; 是一个持续集成的平台，我们可以使用其自动构建部署的功能帮我们简化 Hexo 博客的部署流程。&lt;/p&gt;
&lt;h2 id=&quot;为什么要用-Travis-CI&quot;&gt;为什么要用 Travis CI&lt;/h2&gt;
&lt;p&gt;因为懒。 &lt;img src=&quot;/assets/img/what_can_i_say.jpg&quot; alt=&quot;what_can_i_say&quot;&gt;&lt;/p&gt;
&lt;p&gt;Hexo 部署 Blog 到 GitPage 通常需要三部曲：&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;hexo clean&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;hexo g&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;hexo deploy&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="Blog" scheme="https://lz5z.com/categories/Blog/"/>
    
    
    <category term="Hexo" scheme="https://lz5z.com/tags/Hexo/"/>
    
    <category term="Travis" scheme="https://lz5z.com/tags/Travis/"/>
    
    <category term="持续集成" scheme="https://lz5z.com/tags/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    
  </entry>
  
  <entry>
    <title>Node.js 中 child_procss 模块</title>
    <link href="https://lz5z.com/Node.js%E4%B8%ADchild_procss%E6%A8%A1%E5%9D%97/"/>
    <id>https://lz5z.com/Node.js%E4%B8%ADchild_procss%E6%A8%A1%E5%9D%97/</id>
    <published>2016-11-16T12:53:46.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h1>简介</h1><p>Node.js 的单线程模型给了它无数的赞美，也带给它无数的诟病。单线程模型，让开发者远离了线程调度的复杂性，使用事件驱动也能开发出一个高并发的服务器；同样也是因为单线程，让CPU密集型计算应用完全不适用。</p><p>Node.js 中内建了一个 <a href="https://nodejs.org/api/child_process.html">child_process</a>模块，可以在程序中创建子进程，从而实现多核并行计算。</p><span id="more"></span><h1><a href="https://nodejs.org/api/child_process.html">child_process</a></h1><p>child_process 是 Node.js 中一个非常重要的模块，主要功能有：</p><ol><li>创建子进程</li><li>主进程与子进程通信</li><li>主进程读取子进程返回结果</li></ol><p>使用 child_process 模块创建进程一共有六种方法（Node.js v7.1.0）</p><h3 id="异步创建进程">异步创建进程</h3><ol><li>child_process.<strong>exec</strong>(command[, options][, callback])</li><li>child_process.<strong>execFile</strong>(file[, args][, options][, callback])</li><li>child_process.<strong>fork</strong>(modulePath[, args][, options])</li><li>child_process.<strong>spawn</strong>(command[, args][, options])</li></ol><h3 id="同步创建进程">同步创建进程</h3><ol><li>child_process.<strong>execFileSync</strong>(file[, args][, options])</li><li>child_process.<strong>execSync</strong>(command[, options])</li><li>child_process.<strong>spawnSync</strong>(command[, args][, options])</li></ol><p>以异步函数中 spawn 是最基本的创建子进程的函数，其他三个异步函数都是对 spawn 不同程度的封装。spawn 只能运行指定的程序，参数需要在列表中给出，而 exec 可以直接运行复杂的命令。</p><h2 id="spawn">spawn()</h2><p>spawn从定义来看，有3个参数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">child_process.<span class="title function_">spawn</span>(command[, args][, options])</span><br></pre></td></tr></table></figure><ol><li>command: 执行的命令</li><li>args: 参数列表，可输入多的参数</li><li>options: 环境变量对象</li><li>return: 返回一个ChildProcess 类的实例</li></ol><h3 id="options">options</h3><blockquote><ol><li>cwd [String] Current working directory of the child process</li></ol></blockquote><ol start="2"><li>env [Object] Environment key-value pairs</li><li>argv0 [String] Explicitly set the value of argv[0] sent to the child process. This will be set to command if not specified.</li><li>stdio [Array] | [String] Child’s stdio configuration. (See options.stdio)</li><li>detached [Boolean] Prepare child to run independently of its parent process. Specific behavior depends on the platform, see options.detached)</li><li>uid [Number] Sets the user identity of the process. (See setuid(2).)</li><li>gid [Number] Sets the group identity of the process. (See setgid(2).)</li><li>shell [Boolean] | [String] If true, runs command inside of a shell. Uses ‘/bin/sh’ on UNIX, and ‘cmd.exe’ on Windows. A different shell can be specified as a string. The shell should understand the -c switch on UNIX, or /d /s /c on Windows. Defaults to false (no shell).</li></ol><p>spawn 方法创建一个子进程来执行特定命令，它没有回调函数，只能通过监听事件，来获取运行结果。属于异步执行，适用于子进程长时间运行的情况。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> child_process = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> path = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line"><span class="keyword">let</span> child = child_process.<span class="title function_">spawn</span>(<span class="string">&#x27;ls&#x27;</span>, [<span class="string">&#x27;-l&#x27;</span>, path]);</span><br><span class="line">child.<span class="property">stdout</span>.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stdout: &#x27;</span> + data);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">child.<span class="property">stderr</span>.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stderr: &#x27;</span> + data);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">child.<span class="title function_">on</span>(<span class="string">&#x27;close&#x27;</span>, <span class="function">(<span class="params">code</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;child process exited with code &#x27;</span> + code);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>spawn 方法通过 stream 的方式发数据传给主进程，从而实现了多进程之间的数据交换。</p><h2 id="exec">exec()</h2><p>exec 方法的定义如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">child_process.<span class="title function_">exec</span>(command[, options][, callback])</span><br></pre></td></tr></table></figure><p>exec 方法是对 spawn 方法的封装，增加了 shell/bash 命令解析和回调函数，更加符合 JavaScript 的函数调用习惯。</p><p>command参数是一个命令字符串</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> exec = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="property">exec</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> ls = <span class="title function_">exec</span>(<span class="string">&#x27;ls -l&#x27;</span>, <span class="keyword">function</span> (<span class="params">error, stdout, stderr</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (error) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(error.<span class="property">stack</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Error code: &#x27;</span> + error.<span class="property">code</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Child Process STDOUT: &#x27;</span> + stdout);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>exec 方法第二个参数是回调函数，该函数接受三个参数，分别是发生的错误、标准输出的显示结果、标准错误的显示结果。</p><p>由于标准输出和标准错误都是流对象（stream），可以监听 data 事件，因此上面的代码也可以写成下面这样。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> exec = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="property">exec</span>;</span><br><span class="line"><span class="keyword">let</span> child = <span class="title function_">exec</span>(<span class="string">&#x27;ls -l&#x27;</span>);</span><br><span class="line"></span><br><span class="line">child.<span class="property">stdout</span>.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stdout: &#x27;</span> + data);</span><br><span class="line">&#125;);</span><br><span class="line">child.<span class="property">stderr</span>.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;stdout: &#x27;</span> + data);</span><br><span class="line">&#125;);</span><br><span class="line">child.<span class="title function_">on</span>(<span class="string">&#x27;close&#x27;</span>, <span class="function">(<span class="params">code</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;closing code: &#x27;</span> + code);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>exec 方法会直接调用 bash（/bin/sh程序） 来解释命令，如果用户输入恶意代码，将会带来安全风险。因此，在有用户输入的情况下，最好不使用 exec 方法，而是使用 execFile 方法。</p><h2 id="execFile">execFile()</h2><p>execFile的定义如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">child_process.<span class="title function_">execFile</span>(file[, args][, options][, callback])</span><br></pre></td></tr></table></figure><p>execFile 命令有四个参数，file 和callbakc 为必传参数，options、args 为可选参数：</p><ul><li>file 要执行程序的文件或命令名。字符串类型</li><li>args 要执行程序或命令的参数列表。数组类型</li><li>options 可选参数对象，与exec的options对象相同</li><li>callback 子进程执行完毕的回调函数。与exec的callback函数相同</li><li>返回值: ChildProcess 对象</li></ul><p>execFile 从可执行程序启动子进程。与 exec 相比，execFile 不启动独立的 bash/shell，因此更加轻量级，也更加安全。 execFile 也可以用于执行命令。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> childProcess = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> path = <span class="string">&quot;.&quot;</span>;</span><br><span class="line">childProcess.<span class="title function_">execFile</span>(<span class="string">&#x27;ls&#x27;</span>, [<span class="string">&#x27;-l&#x27;</span>, path], <span class="function">(<span class="params">err, result</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(result)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>那么，什么时候使用 exec，什么时候使用 execFile 呢？</p><p>如果命令参数是由用户来输入的，对于 exec 函数来说是有安全性风险的，因为 Shell 会运行多行命令，比如 ’ls -l .;pwd，如逗号分隔，之后的命令也会被系统运行。但使用 exeFile 命令时，命令和参数分来，防止了参数注入的安全风险。</p><h2 id="fork">fork()</h2><p>fork 函数，用于在子进程中运行的模块，如 fork(‘./son.js’) 相当于 spawn(‘node’, [‘./son.js’]) 。与 spawn 方法不同的是，fork 会在父进程与子进程之间，建立一个通信管道，用于进程之间的通信。</p><p>假设有一个主进程文件 mian.js:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> childProcess = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> son = childProcess.<span class="title function_">fork</span>(<span class="string">&#x27;./son.js&#x27;</span>);</span><br><span class="line"></span><br><span class="line">son.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">m</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Main Listen: &#x27;</span>, m);</span><br><span class="line">&#125;);</span><br><span class="line">son.<span class="title function_">send</span>(&#123;</span><br><span class="line">    <span class="attr">hello</span>: <span class="string">&#x27;son&#x27;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>有一个子进程文件 son.js:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">process.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">m</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Son Listen:&#x27;</span>, m);</span><br><span class="line">&#125;);</span><br><span class="line">process.<span class="title function_">send</span>(&#123;</span><br><span class="line">    <span class="title class_">Hello</span>: <span class="string">&#x27;main&#x27;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>运行程序：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ node test.js</span><br><span class="line">Son Listen: &#123; hello: &#x27;son&#x27; &#125;</span><br><span class="line">Main Listen:  &#123; Hello: &#x27;main&#x27; &#125;</span><br></pre></td></tr></table></figure><p>通过 main.js 启动子进程 son.js，通过 process 在两个进程之间传递数据。</p><p>使用 child_process.fork() 生成新进程之后，就可以用 son.send(message, [sendHandle]) 向新进程发送消息，新进程中通过监听message事件，来获取消息，这就是主线程与子线程之间的通信方式。</p><h2 id="Windows">Windows</h2><p>在Windows上执行一个 <strong>.bat</strong> 或者 <strong>.cmd</strong> 文件的方式略有不同。</p><p>假如有一个bat文件 my.bat</p><h3 id="spawn-2">spawn</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> spawn = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="property">spawn</span>;</span><br><span class="line"><span class="keyword">const</span> bat = <span class="title function_">spawn</span>(<span class="string">&#x27;cmd.exe&#x27;</span>, [<span class="string">&#x27;/c&#x27;</span>, <span class="string">&#x27;my.bat&#x27;</span>]);</span><br><span class="line"></span><br><span class="line">bat.<span class="property">stdout</span>.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(data);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">bat.<span class="property">stderr</span>.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(data);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">bat.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="function">(<span class="params">code</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Child exited with code <span class="subst">$&#123;code&#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="exec-2">exec</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> exec = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>).<span class="property">exec</span>;</span><br><span class="line"><span class="title function_">exec</span>(<span class="string">&#x27;my.bat&#x27;</span>, <span class="function">(<span class="params">err, stdout, stderr</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(stdout);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>如果文件名中有空格：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bat = <span class="title function_">spawn</span>(<span class="string">&#x27;&quot;my script.cmd&quot;&#x27;</span>, [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>], &#123; <span class="attr">shell</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"><span class="comment">// or:</span></span><br><span class="line"><span class="title function_">exec</span>(<span class="string">&#x27;&quot;my script.cmd&quot; a b&#x27;</span>, <span class="function">(<span class="params">err, stdout, stderr</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h1&gt;简介&lt;/h1&gt;
&lt;p&gt;Node.js 的单线程模型给了它无数的赞美，也带给它无数的诟病。单线程模型，让开发者远离了线程调度的复杂性，使用事件驱动也能开发出一个高并发的服务器；同样也是因为单线程，让CPU密集型计算应用完全不适用。&lt;/p&gt;
&lt;p&gt;Node.js 中内建了一个 &lt;a href=&quot;https://nodejs.org/api/child_process.html&quot;&gt;child_process&lt;/a&gt;模块，可以在程序中创建子进程，从而实现多核并行计算。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="Node" scheme="https://lz5z.com/tags/Node/"/>
    
    <category term="进程" scheme="https://lz5z.com/tags/%E8%BF%9B%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>快速排序思想解决水桶问题</title>
    <link href="https://lz5z.com/QuickSortToKettle/"/>
    <id>https://lz5z.com/QuickSortToKettle/</id>
    <published>2016-11-16T01:41:44.000Z</published>
    <updated>2026-05-07T14:50:53.972Z</updated>
    
    <content type="html"><![CDATA[<h1>水桶问题</h1><img src="/assets/img/Bucket.png" alt="我是一只的图片"><p>假设给你n个红色的水壶和n个蓝色的水壶。它们的形状和尺寸都各不相同。所有的红色水壶盛水量都各不相同，蓝色水壶也是如此。但对于每一个红色水壶来说，都有一个蓝色水壶盛水量和其相同；反之亦然。<br>你的任务是配对出全部盛水量相同的红色水壶和蓝色水壶。为此，可以执行的操作为，挑出一对水壶，一只红色一只蓝色，将红色水壶灌满水，将红色水壶的水倒入蓝色水壶中，看其是否恰好灌满来判断，这个红色水壶的盛水量大于、小于或等于蓝色水壶。假设这样的比较需要花费一个单位时间。<br>请找出一种算法，它能够用最少的比较次数来确定所有水壶的配对。<br>注意:不可直接比较两个红色或者两个蓝色水壶，一次比较必须取一只红色一只蓝色。</p><span id="more"></span><h1>解决方案</h1><h2 id="快速排序思想解">快速排序思想解</h2><p>1.首先在集合中选取一个元素作为 「基准」 pivot<br>2.将集合中所有元素与「基准」元素进行对比，所有小于「基准」的元素，都移到「基准」的左边；所有大于「基准」的元素，都移到「基准」的右边。<br>3.对「基准」元素左右两边的集合，分别进行上述两步，直到所有的子集只剩下一个元素。</p><p>代码描述：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">quickSort</span> = arr=&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (arr.<span class="property">length</span> &lt;= <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> arr;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> pivotIndex = <span class="title class_">Math</span>.<span class="title function_">floor</span>(arr.<span class="property">length</span> / <span class="number">2</span>);</span><br><span class="line">    <span class="keyword">let</span> pivot = arr.<span class="title function_">splice</span>(pivotIndex, <span class="number">1</span>)[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">let</span> left = [], right = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> ai <span class="keyword">of</span> arr) &#123;</span><br><span class="line">        <span class="keyword">if</span> (ai &lt; pivot) &#123;</span><br><span class="line">            left.<span class="title function_">push</span>(ai);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right.<span class="title function_">push</span>(ai);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">quickSort</span>(left).<span class="title function_">concat</span>([pivot], <span class="title function_">quickSort</span>(right));</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="水壶问题">水壶问题</h2><p>1.依次从红色水壶中选取一个水壶与蓝色水壶集合对比，对比过程如下：<br>2.红色水壶与每一个蓝色水壶对比，盛水量大于红色水壶的蓝水壶放在右边，小于的放在左边，水量相等的为当前集合的 「基准」 元素。<br>3.如果当前集合中已有 「基准」 元素，则拿红色水壶与「基准」元素对比： 红色水壶大于基准元素，则选取基准元素右边的集合重复第二步; 如果红色水壶小于基准元素，则选取基准元素左边边的集合重复第二步。</p><h3 id="举个栗子">举个栗子</h3><p>现在有红色水壶容量为： [3, 5, 1, 4, 8, 2, 6]<br>蓝色水壶： [6, 2, 3, 1, 8, 5, 4]</p><p>第一步，选取红色水壶中第一个水壶 3 跟蓝色水壶依次对比，大于 3 的放右边，小于 3 的放左边，等于 3 的水壶为当前集合的 「基准」 元素。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[2, 1, ③, 6, 8, 5, 4]</span><br></pre></td></tr></table></figure><p>然后选取红色水壶中的第二个水壶 5 与 「基准」 元素对比，5 &gt; 3, 因此使用第一步的方法，拿 5 与 「基准」 元素右边的元素依次对比。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[2, 1, ③, 4, ⑤, 6, 8]</span><br></pre></td></tr></table></figure><p>红色第三个水壶为 1， 拿 1 与第一个 「基准」 元素比较， 1 &lt; 3, 因此使用第一步的方法， 拿 1 与 「基准」 元素左边的元素依次对比。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[①, 2, ③, 4, ⑤, 6, 8]</span><br></pre></td></tr></table></figure><p>红色第四个水壶为 4， 拿 4 与第一个 「基准」 元素比较， 4 &gt; 3, 因此使用第一步的方法， 拿 4 与 「基准」 元素右边的元素依次对比。<br>右边元素集合中又有 「基准」 元素 5 ，因此先与 「基准」 元素对比， 4 &lt; 5， 所以拿 4 与 「基准」 元素左边的元素依次对比。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[①, 2, ③, ④, ⑤, 6, 8]</span><br></pre></td></tr></table></figure><p>后面的顺序为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[①, 2, ③, ④, ⑤, 6, ⑧]</span><br><span class="line"></span><br><span class="line">[①, ②, ③, ④, ⑤, 6, ⑧]</span><br><span class="line"></span><br><span class="line">[①, ②, ③, ④, ⑤, ⑥, ⑧]</span><br></pre></td></tr></table></figure><p>代码描述：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">pivot</span> = -<span class="number">1</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">quickMatch</span> = (<span class="params">key, arr</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (arr.<span class="property">length</span> &lt;= <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;key&#125;</span> matched!`</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (arr.<span class="property">pivot</span> &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        arr.<span class="property">left</span> = <span class="keyword">new</span> <span class="title class_">Array</span>();</span><br><span class="line">        arr.<span class="property">right</span> = <span class="keyword">new</span> <span class="title class_">Array</span>();</span><br><span class="line">        arr.<span class="title function_">map</span>(<span class="function"><span class="params">ai</span>=&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (ai &lt; key) &#123;</span><br><span class="line">                arr.<span class="property">left</span>.<span class="title function_">push</span>(ai);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ai &gt; key) &#123;</span><br><span class="line">                arr.<span class="property">right</span>.<span class="title function_">push</span>(ai);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ai === key) &#123;</span><br><span class="line">                arr.<span class="property">pivot</span> = key;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;key&#125;</span> matched!`</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (key &gt; arr.<span class="property">pivot</span>) &#123;</span><br><span class="line">            <span class="title function_">quickMatch</span>(key, arr.<span class="property">right</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key &lt; arr.<span class="property">pivot</span>) &#123;</span><br><span class="line">            <span class="title function_">quickMatch</span>(key, arr.<span class="property">left</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arrRed = [<span class="number">3</span>, <span class="number">5</span>, <span class="number">1</span>, <span class="number">4</span>, <span class="number">8</span>, <span class="number">2</span>, <span class="number">6</span>];</span><br><span class="line"><span class="keyword">let</span> arrBlue = [<span class="number">6</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">8</span>, <span class="number">5</span>, <span class="number">4</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">of</span> arrRed) &#123;</span><br><span class="line">    <span class="title function_">quickMatch</span>(key, arrBlue);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1>总结</h1><p>这个算法有点类似于二叉树的思想，将红色水壶与蓝色水壶依次对比的时候，构建蓝色水壶二叉树，每个二叉树的根结点为红色水壶。平均时间复杂度为O(nlgn)。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;水桶问题&lt;/h1&gt;
&lt;img src=&quot;/assets/img/Bucket.png&quot; alt=&quot;我是一只的图片&quot;&gt;
&lt;p&gt;假设给你n个红色的水壶和n个蓝色的水壶。它们的形状和尺寸都各不相同。所有的红色水壶盛水量都各不相同，蓝色水壶也是如此。但对于每一个红色水壶来说，都有一个蓝色水壶盛水量和其相同；反之亦然。&lt;br&gt;
你的任务是配对出全部盛水量相同的红色水壶和蓝色水壶。为此，可以执行的操作为，挑出一对水壶，一只红色一只蓝色，将红色水壶灌满水，将红色水壶的水倒入蓝色水壶中，看其是否恰好灌满来判断，这个红色水壶的盛水量大于、小于或等于蓝色水壶。假设这样的比较需要花费一个单位时间。&lt;br&gt;
请找出一种算法，它能够用最少的比较次数来确定所有水壶的配对。&lt;br&gt;
注意:不可直接比较两个红色或者两个蓝色水壶，一次比较必须取一只红色一只蓝色。&lt;/p&gt;</summary>
    
    
    
    <category term="Algorithm" scheme="https://lz5z.com/categories/Algorithm/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="Algorithm" scheme="https://lz5z.com/tags/Algorithm/"/>
    
    <category term="快速排序" scheme="https://lz5z.com/tags/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/"/>
    
    <category term="水桶问题" scheme="https://lz5z.com/tags/%E6%B0%B4%E6%A1%B6%E9%97%AE%E9%A2%98/"/>
    
  </entry>
  
  <entry>
    <title>HTML5 前端存储</title>
    <link href="https://lz5z.com/HTML%E5%89%8D%E7%AB%AF%E5%AD%98%E5%82%A8/"/>
    <id>https://lz5z.com/HTML%E5%89%8D%E7%AB%AF%E5%AD%98%E5%82%A8/</id>
    <published>2016-11-15T16:33:38.000Z</published>
    <updated>2026-05-07T14:50:53.969Z</updated>
    
    <content type="html"><![CDATA[<h1>Cookie, LocalStorage 与 SessionStorage</h1><h2 id="基本概念">基本概念</h2><p>Cookie，指某些网站为了辨别用户身份而储存在用户本地终端（Client Side）上的数据（通常经过加密）。</p><p>html5 中的 Web Storage 包括了两种存储方式：sessionStorage和localStorage。</p><p>sessionStorage 用于本地存储一个会话（session）中的数据，这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此 sessionStorage 不是一种持久化的本地存储，仅仅是会话级别的存储。</p><p>而 localStorage 用于持久化的本地存储，除非主动删除数据，否则数据是永远不会过期的。浏览器中同一个域下的窗口可以共享 localStorage 数据。</p><span id="more"></span><h2 id="兼容性">兼容性</h2><table><thead><tr><th style="text-align:left">特性</th><th style="text-align:center">Chrome</th><th style="text-align:center">Firefox (Gecko)</th><th style="text-align:center">Internet Explorer</th><th style="text-align:center">Opera</th><th style="text-align:center">Safari (WebKit)</th></tr></thead><tbody><tr><td style="text-align:left">localStorage</td><td style="text-align:center">4</td><td style="text-align:center">3.5</td><td style="text-align:center">8</td><td style="text-align:center">10.50</td><td style="text-align:center">4</td></tr><tr><td style="text-align:left">sessionStorage</td><td style="text-align:center">5</td><td style="text-align:center">2</td><td style="text-align:center">8</td><td style="text-align:center">10.50</td><td style="text-align:center">4</td></tr></tbody></table><h2 id="差别">差别</h2><p>Cookie 一般由服务器生成，可设置失效时间。如果在浏览器端生成 Cookie，默认是关闭浏览器后失效。Http 通信的时候 Cookie 的信息会保存的 Http 头中。<br>localStorage 和 sessionStorage 仅在客户端（即浏览器）中保存，不参与和服务器的通信。</p><h2 id="应用场景">应用场景</h2><p>因为每个 HTTP 请求都会带着 Cookie 的信息，所以 Cookie 应当尽可能精简，比较常用的一个应用场景就是判断用户是否登录。针对登录过的用户，服务器端会在他登录时往 Cookie 中插入一段加密过的唯一辨识单一用户的辨识码，下次只要读取这个值就可以判断当前用户是否登录啦。</p><p>localStorage 主要存储一些比较多的本地数据，如 HTML5 小游戏里面生成的数据。</p><p>如果遇到一些内容特别多的表单，为了优化用户体验，我们可能要把表单页面拆分成多个子页面，然后按步骤引导用户填写。这时候 sessionStorage 的作用就发挥出来了。</p><h2 id="安全性的考虑">安全性的考虑</h2><p>需要注意的是，不是什么数据都适合放在 Cookie、localStorage 和 sessionStorage 中的。使用它们的时候，需要时刻注意是否有代码存在 XSS 注入的风险。因为只要打开控制台，你就随意修改它们的值，所以千万不要用它们存储你系统中的敏感数据。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;Cookie, LocalStorage 与 SessionStorage&lt;/h1&gt;
&lt;h2 id=&quot;基本概念&quot;&gt;基本概念&lt;/h2&gt;
&lt;p&gt;Cookie，指某些网站为了辨别用户身份而储存在用户本地终端（Client Side）上的数据（通常经过加密）。&lt;/p&gt;
&lt;p&gt;html5 中的 Web Storage 包括了两种存储方式：sessionStorage和localStorage。&lt;/p&gt;
&lt;p&gt;sessionStorage 用于本地存储一个会话（session）中的数据，这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此 sessionStorage 不是一种持久化的本地存储，仅仅是会话级别的存储。&lt;/p&gt;
&lt;p&gt;而 localStorage 用于持久化的本地存储，除非主动删除数据，否则数据是永远不会过期的。浏览器中同一个域下的窗口可以共享 localStorage 数据。&lt;/p&gt;</summary>
    
    
    
    <category term="HTML" scheme="https://lz5z.com/categories/HTML/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
    <category term="HTML5" scheme="https://lz5z.com/tags/HTML5/"/>
    
  </entry>
  
  <entry>
    <title>Python一些书写技巧</title>
    <link href="https://lz5z.com/Python%E4%B8%80%E4%BA%9B%E4%B9%A6%E5%86%99%E6%8A%80%E5%B7%A7/"/>
    <id>https://lz5z.com/Python%E4%B8%80%E4%BA%9B%E4%B9%A6%E5%86%99%E6%8A%80%E5%B7%A7/</id>
    <published>2016-11-13T13:36:55.000Z</published>
    <updated>2026-05-07T14:50:53.971Z</updated>
    
    <content type="html"><![CDATA[<h1>简介</h1><p>本文介绍了一些平时用到的Python书写技巧。之后会不断更新。<br><img src="/assets/img/python.png" alt="我是一只的图片" width="20%"></p><span id="more"></span><h2 id="交换变量">交换变量</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">x = <span class="number">6</span></span><br><span class="line">y = <span class="number">5</span></span><br><span class="line"> </span><br><span class="line">x, y = y, x</span><br><span class="line"> </span><br><span class="line"><span class="built_in">print</span>(x, y) <span class="comment">#5 6</span></span><br></pre></td></tr></table></figure><h2 id="if-语句在行内">if 语句在行内</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Hello&quot;</span>) <span class="keyword">if</span> <span class="literal">True</span> <span class="keyword">else</span> <span class="string">&quot;World&quot;</span> <span class="comment">#Hello</span></span><br></pre></td></tr></table></figure><h2 id="连接">连接</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">a = [<span class="number">1</span>, <span class="number">2</span>]</span><br><span class="line">b = [<span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line"><span class="built_in">print</span>(a + b) <span class="comment">#[1, 2, 3, 4]</span></span><br><span class="line"> </span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span>(<span class="number">1</span>) + <span class="string">&quot; world&quot;</span>) <span class="comment">#1 world</span></span><br><span class="line"> </span><br><span class="line"><span class="built_in">print</span>(`<span class="number">1</span>` + <span class="string">&quot; world&quot;</span>) <span class="comment">#1 world</span></span><br><span class="line"> </span><br><span class="line"><span class="built_in">print</span>(<span class="number">1</span>, <span class="string">&quot;world&quot;</span>) <span class="comment">#1 world</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(a, <span class="number">3</span>) <span class="comment">#[1, 2] 3</span></span><br></pre></td></tr></table></figure><h2 id="除法">除法</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">print</span>(<span class="number">5.0</span>//<span class="number">2</span>) <span class="comment">#2 地板除</span></span><br><span class="line"><span class="built_in">print</span>(<span class="number">2</span>**<span class="number">5</span>) <span class="comment">#32 2的5次方</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="number">.3</span>/<span class="number">.1</span>) <span class="comment">#2.9999999999999996</span></span><br><span class="line"><span class="built_in">print</span>(<span class="number">.3</span>//<span class="number">.1</span>) <span class="comment">#2.0</span></span><br></pre></td></tr></table></figure><h2 id="数值比较">数值比较</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">x = <span class="number">2</span></span><br><span class="line"><span class="keyword">if</span> <span class="number">3</span> &gt; x &gt; <span class="number">1</span>:</span><br><span class="line">   <span class="built_in">print</span>(x) <span class="comment">#2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="number">1</span> &lt; x &gt; <span class="number">0</span>:</span><br><span class="line">   <span class="built_in">print</span>(x) <span class="comment">#2</span></span><br></pre></td></tr></table></figure><h2 id="迭代列表">迭代列表</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">names = (<span class="string">&#x27;Jack&#x27;</span>,<span class="string">&#x27;Leo&#x27;</span>,<span class="string">&#x27;Sony&#x27;</span>)</span><br><span class="line">ages = (<span class="number">2001</span>,<span class="number">2002</span>,<span class="number">2003</span>)</span><br><span class="line"><span class="keyword">for</span> a, n <span class="keyword">in</span> <span class="built_in">zip</span>(names, ages):</span><br><span class="line">    <span class="built_in">print</span>(a, n)</span><br><span class="line"><span class="comment">#Jack 2001</span></span><br><span class="line"><span class="comment">#Leo 2002</span></span><br><span class="line"><span class="comment">#Sony 2003</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 索引</span></span><br><span class="line"><span class="keyword">for</span> index, a <span class="keyword">in</span> <span class="built_in">enumerate</span>(names):</span><br><span class="line">    <span class="built_in">print</span>(index, a)</span><br><span class="line"><span class="comment">#0 Jack</span></span><br><span class="line"><span class="comment">#1 Leo</span></span><br><span class="line"><span class="comment">#2 Sony</span></span><br></pre></td></tr></table></figure><h2 id="列表推导式">列表推导式</h2><p>已知一个列表，我们可以筛选出偶数列表方法：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">numbers = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]</span><br><span class="line">even = []</span><br><span class="line"><span class="keyword">for</span> number <span class="keyword">in</span> numbers:</span><br><span class="line">    <span class="keyword">if</span> number%<span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        even.append(number)</span><br><span class="line"></span><br><span class="line"><span class="comment">#转变成如下：</span></span><br><span class="line">even = [number <span class="keyword">for</span> number <span class="keyword">in</span> numbers <span class="keyword">if</span> number%<span class="number">2</span> == <span class="number">0</span>]</span><br></pre></td></tr></table></figure><h2 id="字典推导">字典推导</h2><p>和列表推导类似，字典可以做同样的工作：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">names = [<span class="string">&#x27;Jack&#x27;</span>,<span class="string">&#x27;Leo&#x27;</span>,<span class="string">&#x27;Sony&#x27;</span>]</span><br><span class="line">people = [&#123;key: value <span class="keyword">for</span> value, key <span class="keyword">in</span> <span class="built_in">enumerate</span>(names)&#125;]</span><br><span class="line"><span class="built_in">print</span>(people)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>[&#123;<span class="string">&#x27;Sony&#x27;</span>: <span class="number">2</span>, <span class="string">&#x27;Leo&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;Jack&#x27;</span>: <span class="number">0</span>&#125;]</span><br></pre></td></tr></table></figure><h2 id="初始化列表的值">初始化列表的值</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">items = [<span class="number">0</span>]*<span class="number">3</span></span><br><span class="line"><span class="built_in">print</span>(items)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>[<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>]</span><br></pre></td></tr></table></figure><h2 id="列表转换为字符串">列表转换为字符串</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">names = [&quot;Leo&quot;, &quot;Jack&quot;, &quot;Lucy&quot;]</span><br><span class="line">print(&quot;, &quot;.join(names))</span><br><span class="line">&gt;&gt;&gt; Leo, Jack, Lucy</span><br></pre></td></tr></table></figure><h2 id="从字典中获取元素">从字典中获取元素</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">data = &#123;<span class="string">&#x27;user&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;Max&#x27;</span>, <span class="string">&#x27;age&#x27;</span>: <span class="number">4</span>&#125;</span><br><span class="line">is_admin = data.get(<span class="string">&#x27;admin&#x27;</span>, <span class="literal">False</span>)</span><br><span class="line"><span class="built_in">print</span>(is_admin)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="literal">False</span></span><br></pre></td></tr></table></figure><h2 id="切片">切片</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">x = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]</span><br><span class="line"><span class="comment">#前3个</span></span><br><span class="line"><span class="built_in">print</span>(x[:<span class="number">3</span>])</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]</span><br><span class="line"><span class="comment">#中间4个</span></span><br><span class="line"><span class="built_in">print</span>(x[<span class="number">1</span>:<span class="number">5</span>])</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>[<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>]</span><br><span class="line"><span class="comment">#最后3个</span></span><br><span class="line"><span class="built_in">print</span>(x[-<span class="number">3</span>:])</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>[<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]</span><br><span class="line"><span class="comment">#奇数项</span></span><br><span class="line"><span class="built_in">print</span>(x[::<span class="number">2</span>])</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>[<span class="number">1</span>,<span class="number">3</span>,<span class="number">5</span>]</span><br><span class="line"><span class="comment">#偶数项</span></span><br><span class="line"><span class="built_in">print</span>(x[<span class="number">1</span>::<span class="number">2</span>])</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>[<span class="number">2</span>,<span class="number">4</span>,<span class="number">6</span>]</span><br></pre></td></tr></table></figure><h2 id="一行代码解决FizzBuzz">一行代码解决FizzBuzz</h2><p>有一个简单的编程练习叫FizzBuzz，问题引用如下：</p><p>写一个程序，打印数字1到100，3的倍数打印“Fizz”来替换这个数，5的倍数打印“Buzz”，对于既是3的倍数又是5的倍数的数字打印“FizzBuzz”。</p><p>这里就是一个简短的，有意思的方法解决这个问题：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">101</span>):<span class="built_in">print</span>(<span class="string">&quot;fizz&quot;</span>[x%<span class="number">3</span>*<span class="number">4</span>::]+<span class="string">&quot;buzz&quot;</span>[x%<span class="number">5</span>*<span class="number">4</span>::]<span class="keyword">or</span> x)</span><br></pre></td></tr></table></figure><h2 id="集合">集合</h2><p>除了python内置的数据类型外，在collection模块同样还包括一些特别的用例，在有些场合Counter非常实用。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> Counter</span><br><span class="line"><span class="built_in">print</span>(Counter(<span class="string">&quot;hello&quot;</span>))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Counter(&#123;<span class="string">&#x27;l&#x27;</span>: <span class="number">2</span>, <span class="string">&#x27;h&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;e&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;o&#x27;</span>: <span class="number">1</span>&#125;)</span><br></pre></td></tr></table></figure><h2 id="迭代工具">迭代工具</h2><p>和collections库一样，还有一个库叫itertools，对某些问题真能高效地解决。其中一个用例是查找所有组合，他能告诉你在一个组中元素的所有不同的组合方式</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> itertools <span class="keyword">import</span> combinations</span><br><span class="line">names = [<span class="string">&quot;Leo&quot;</span>, <span class="string">&quot;Jack&quot;</span>, <span class="string">&quot;Lucy&quot;</span>]</span><br><span class="line"><span class="keyword">for</span> name <span class="keyword">in</span> combinations(names, <span class="number">2</span>):</span><br><span class="line">    <span class="built_in">print</span>(name)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="string">&#x27;Jack&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>(<span class="string">&#x27;Leo&#x27;</span>, <span class="string">&#x27;Lucy&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="string">&#x27;Lucy&#x27;</span>)</span><br></pre></td></tr></table></figure><h2 id="False-True">False == True</h2><p>在Python中，True和False是全局变量，因此：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">False</span> = <span class="literal">True</span></span><br><span class="line"><span class="keyword">if</span> <span class="literal">False</span>:</span><br><span class="line">   <span class="built_in">print</span>(<span class="string">&quot;Hello&quot;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">   <span class="built_in">print</span>(<span class="string">&quot;World&quot;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Hello</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h1&gt;简介&lt;/h1&gt;
&lt;p&gt;本文介绍了一些平时用到的Python书写技巧。之后会不断更新。&lt;br&gt;
&lt;img src=&quot;/assets/img/python.png&quot; alt=&quot;我是一只的图片&quot; width=&quot;20%&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Python" scheme="https://lz5z.com/categories/Python/"/>
    
    
    <category term="Python" scheme="https://lz5z.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>换了一个 Blog 主题</title>
    <link href="https://lz5z.com/%E6%8D%A2%E4%BA%86%E4%B8%80%E4%B8%AABlog%E4%B8%BB%E9%A2%98/"/>
    <id>https://lz5z.com/%E6%8D%A2%E4%BA%86%E4%B8%80%E4%B8%AABlog%E4%B8%BB%E9%A2%98/</id>
    <published>2016-11-11T01:17:09.000Z</published>
    <updated>2026-05-07T14:50:53.975Z</updated>
    
    <content type="html"><![CDATA[<h1>yilia</h1><p>前几天deploy博客的时候，发现打开blog页面是空的，只有head部分显示出来了。打开控制台排查问题，发现hexo主题里面有几个外部ajax call失败，导致整个页面都没有渲染出来，这是一件恼火的事情。<img src="/assets/img/angry.png" alt="我是一只生气的图片"></p><p>于是果断换主题，其实对之前的主题还是很满意的： 简洁，渲染速度也很快，功能虽然不多，但是基本满足我的需求。</p><p>这次选择的主题是腾讯的工程师Litten制作的 <a href="https://github.com/litten/hexo-theme-yilia">「yilia」</a></p><p>「yilia」 同样是我喜欢的简洁样式，作者甚至移除了搜索框。而且对于移动端的优化也做得很不错。</p><span id="more"></span><h1>yotuku</h1><p>之前Blog里面的图片一直都选择 <a href="http://yotuku.cn/">「yotuku」</a> 生成在线图片，然后在markdown里面引用，如果图片大小或者位置不合适的话，会在md里面手写一段html，这样做很省事。</p><p>今天早上看自己的Blog发现有几张图片没有加载出来，以为是新主题渲染的问题，重新deploy以后发现还是没有。看来不是主题的锅。</p><p>使用控制台发现</p><img src="/assets/img/ajax_call_failure.png" alt="我是一只失败的图片"><p>原来这几张图片都没有拿到，已经在官网留言，希望能够解决。</p><p>不过使用免费云服务存储自己Blog的图片确实不太安全，像这样丢失图片的行为可能会导致几张图片加载不出来，但是如果以后云服务提供商挂掉了（这里不是诅咒yutuku不好，希望这样良心企业越来越好），那这些图片岂不就再也找不到了。</p><p>还是老老实实把图片放到Blog路径下，用相对地址引用吧。</p>]]></content>
    
    
    <summary type="html">&lt;h1&gt;yilia&lt;/h1&gt;
&lt;p&gt;前几天deploy博客的时候，发现打开blog页面是空的，只有head部分显示出来了。打开控制台排查问题，发现hexo主题里面有几个外部ajax call失败，导致整个页面都没有渲染出来，这是一件恼火的事情。&lt;img src=&quot;/assets/img/angry.png&quot; alt=&quot;我是一只生气的图片&quot;&gt;&lt;/p&gt;
&lt;p&gt;于是果断换主题，其实对之前的主题还是很满意的： 简洁，渲染速度也很快，功能虽然不多，但是基本满足我的需求。&lt;/p&gt;
&lt;p&gt;这次选择的主题是腾讯的工程师Litten制作的 &lt;a href=&quot;https://github.com/litten/hexo-theme-yilia&quot;&gt;「yilia」&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;「yilia」 同样是我喜欢的简洁样式，作者甚至移除了搜索框。而且对于移动端的优化也做得很不错。&lt;/p&gt;</summary>
    
    
    
    <category term="Blog" scheme="https://lz5z.com/categories/Blog/"/>
    
    
    <category term="Hexo" scheme="https://lz5z.com/tags/Hexo/"/>
    
    <category term="Yilia" scheme="https://lz5z.com/tags/Yilia/"/>
    
    <category term="Theme" scheme="https://lz5z.com/tags/Theme/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 严格模式</title>
    <link href="https://lz5z.com/JavaScript%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/"/>
    <id>https://lz5z.com/JavaScript%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/</id>
    <published>2016-11-09T04:58:12.000Z</published>
    <updated>2026-05-07T14:50:53.970Z</updated>
    
    <content type="html"><![CDATA[<h1>简介</h1><p>ECMAScript 5 引入了严格模式（strict mode）的概念。严格模式为JavaScript定义了一种不同的解析与执行模型。在严格模式下，ECMAScript 3中的一些不确定的行为将得到处理，而且对于某些不安全的操作也会抛出错误。（<a href="https://github.com/Leo555/JavaScript/blob/master/books/JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%EF%BC%88%E7%AC%AC3%E7%89%88%EF%BC%89%E3%80%91%E4%B8%AD%E6%96%87%20%E9%AB%98%E6%B8%85%20%E5%AE%8C%E6%95%B4%20%E8%AF%A6%E7%BB%86%E4%B9%A6%E7%AD%BE%E7%89%88.pdf">JavaScript高级程序设计</a>）</p><span id="more"></span><p>设立严格模式的目的：</p><ol><li>严格模式会将JavaScript陷阱直接变成明显的错误。</li><li>严格模式修正了一些引擎难以优化的错误。</li><li>同样的代码有些时候严格模式会比非严格模式下更快。</li><li>严格模式禁用了一些有可能在未来版本中定义的语法。</li></ol><h1>开启严格模式</h1><p>使用 ‘use strict’; 进入严格模式。 严格模式可以应用到整个script标签或个别函数中。</p><h2 id="为整个script标签开启严格模式">为整个script标签开启严格模式</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 整个语句都开启严格模式的语法</span></span><br><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;严格模式script&#x27;</span>)</span><br></pre></td></tr></table></figure><p>注意： 如果要为整个script开启严格模式，‘use strict’; 一定要放在第一行。 如果担心文件合并带来严格模式与正常模式的混合，可以将script写成自执行函数的形式。</p><h2 id="为某个函数开启严格模式">为某个函数开启严格模式</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">strict</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="string">&quot;use strict&quot;</span>;　　</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;严格模式函数&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">notStrict</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;正常模式函数&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1>严格模式有哪些不同</h1><h2 id="全局变量显式声明">全局变量显式声明</h2><p>在正常模式下，如果一个变量未声明就直接赋值，相当于创建一个全局变量。这给新人开发者带来便利的同时，给整个项目留下巨大隐患。严格模式将这种失误当成错误。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line">a = <span class="string">&#x27;严格模式&#x27;</span>;  <span class="comment">//ReferenceError: a is not defined</span></span><br></pre></td></tr></table></figure><h2 id="不再Silently-Fail">不再Silently Fail</h2><p>严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常。</p><h3 id="不可变量赋值">不可变量赋值</h3><p>例如： NaN 是一个不可写的全局变量. 在正常模式下, 给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈. 但在严格模式下, 给 NaN 赋值会抛出一个异常。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"><span class="title class_">NaN</span> = <span class="number">3</span>; <span class="comment">//TypeError: Cannot assign to read only property &#x27;NaN&#x27; of #&lt;Object&gt;</span></span><br></pre></td></tr></table></figure><p>给不可写属性赋值, 给只读属性(getter-only)赋值赋值, 给不可扩展对象(non-extensible object)的新属性赋值) 都会抛出异常:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 给不可写属性赋值</span></span><br><span class="line"><span class="keyword">var</span> obj1 = &#123;&#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(obj1, <span class="string">&quot;x&quot;</span>, &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="number">42</span>,</span><br><span class="line">    <span class="attr">writable</span>: <span class="literal">false</span></span><br><span class="line">&#125;);</span><br><span class="line">obj1.<span class="property">x</span> = <span class="number">9</span>; <span class="comment">// TypeError: Cannot assign to read only property &#x27;x&#x27; of #&lt;Object&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 给只读属性赋值</span></span><br><span class="line"><span class="keyword">var</span> obj2 = &#123;</span><br><span class="line">    <span class="keyword">get</span> <span class="title function_">x</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">17</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line">obj2.<span class="property">x</span> = <span class="number">5</span>; <span class="comment">// TypeError: Cannot set property x of #&lt;Object&gt; which has only a getter</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 给不可扩展对象的新属性赋值</span></span><br><span class="line"><span class="keyword">var</span> fixed = &#123;&#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">preventExtensions</span>(fixed);</span><br><span class="line">fixed.<span class="property">newProp</span> = <span class="string">&quot;haha&quot;</span>; <span class="comment">// TypeError: Can&#x27;t add property newProp, object is not extensible</span></span><br></pre></td></tr></table></figure><h3 id="删除不可删除属性">删除不可删除属性</h3><p>在严格模式下, 试图删除不可删除的属性时会抛出异常(之前这种操作不会产生任何效果)</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="keyword">delete</span> <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>; <span class="comment">//TypeError: Cannot delete property &#x27;prototype&#x27; of function Object()</span></span><br></pre></td></tr></table></figure><h3 id="参数名唯一">参数名唯一</h3><p>严格模式要求函数的参数名唯一。在正常模式下, 最后一个重名参数名会掩盖之前的重名参数。 之前的参数仍然可以通过 arguments[i] 来访问。</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="keyword">function</span> <span class="title function_">sum</span>(<span class="params">a, a, c</span>) &#123; <span class="comment">//SyntaxError: Strict mode function may not have duplicate parameter names</span></span><br><span class="line">    <span class="string">&quot;use strict&quot;</span>;</span><br><span class="line">    <span class="keyword">return</span> a + b + c;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="禁止八进制数字语法">禁止八进制数字语法</h3><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="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> sum = <span class="number">015</span> + <span class="comment">// SyntaxError: Octal literals are not allowed in strict mode.</span></span><br><span class="line">          <span class="number">197</span> +</span><br><span class="line">          <span class="number">142</span>;</span><br></pre></td></tr></table></figure><h2 id="简化变量的使用">简化变量的使用</h2><h3 id="禁用-with">禁用 with</h3><p>先看一个with的例子：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">17</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="comment">//x: 4</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="title function_">with</span>(<span class="params">obj</span>) &#123;</span><br><span class="line">    x = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(x);  </span><br></pre></td></tr></table></figure><p>结果是2， with块内x为全局变量x。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">17</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">x</span>: <span class="number">4</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="title function_">with</span>(<span class="params">obj</span>) &#123;</span><br><span class="line">    x = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(x);  </span><br></pre></td></tr></table></figure><p>结果是17， with块内x为变量obj.x。</p><p>所以with中块内的x究竟是指全局变量x还是obj.x在运行之前是无法得知的，这对编译器优化十分不利，因此严格模式禁用 with。</p><h3 id="eval作用域">eval作用域</h3><p>严格模式下的 eval 不在为上层范围(surrounding scope,注:包围eval代码块的范围)引入新变量。</p><p>在正常模式下,  代码 eval(“var x;”) 会给上层函数(surrounding function)或者全局引入一个新的变量 x 。<br>严格模式下，eval语句本身就是一个作用域，它所生成的变量只能用于eval内部。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">17</span>;</span><br><span class="line"><span class="keyword">var</span> evalX = <span class="built_in">eval</span>(<span class="string">&quot;&#x27;use strict&#x27;; var x = 32; x&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(x); <span class="comment">//17</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> y = <span class="number">17</span>;</span><br><span class="line"><span class="keyword">var</span> evalY = <span class="built_in">eval</span>(<span class="string">&quot;var y = 32; y&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(y); <span class="comment">//32</span></span><br></pre></td></tr></table></figure><h3 id="禁止删除声明变量">禁止删除声明变量</h3><p>严格模式禁止删除声明变量。delete name 在严格模式下会引起语法错误</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x;</span><br><span class="line"><span class="keyword">delete</span> x; <span class="comment">// SyntaxError: Delete of an unqualified identifier in strict mode.</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">eval</span>(<span class="string">&quot;var x; delete x;&quot;</span>); <span class="comment">// SyntaxError</span></span><br></pre></td></tr></table></figure><h2 id="让eval和arguments变的简单">让eval和arguments变的简单</h2><h3 id="绑定或赋值">绑定或赋值</h3><p>eval 和 arguments 不能通过程序语法被绑定或赋值。 以下的所有尝试将引起语法错误:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="built_in">eval</span> = <span class="number">17</span>;</span><br><span class="line"><span class="variable language_">arguments</span>++;</span><br><span class="line">++<span class="built_in">eval</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="keyword">set</span> <span class="title function_">p</span>(<span class="params"><span class="variable language_">arguments</span></span>) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> <span class="built_in">eval</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;&#125; <span class="keyword">catch</span> (<span class="variable language_">arguments</span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">x</span>(<span class="params"><span class="built_in">eval</span></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">arguments</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">var</span> y = <span class="keyword">function</span> <span class="title function_">eval</span>(<span class="params"></span>) &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> f = <span class="keyword">new</span> <span class="title class_">Function</span>(<span class="string">&quot;arguments&quot;</span>, <span class="string">&quot;&#x27;use strict&#x27;; return 17;&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="arguments对象">arguments对象</h3><p>arguments对象不再追踪参数的变化</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">a</span>) &#123;</span><br><span class="line">    <span class="string">&quot;use strict&quot;</span>;</span><br><span class="line">    a = <span class="number">42</span>;</span><br><span class="line">    <span class="keyword">return</span> [a, <span class="variable language_">arguments</span>[<span class="number">0</span>]];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> pair = <span class="title function_">f</span>(<span class="number">17</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">assert</span>(pair[<span class="number">0</span>] === <span class="number">42</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">assert</span>(pair[<span class="number">1</span>] === <span class="number">17</span>);</span><br></pre></td></tr></table></figure><h3 id="不再支持-arguments-callee">不再支持 arguments.callee</h3><p>正常模式下，arguments.callee 指向当前正在执行的函数。这个作用很小：直接给执行函数命名就可以了。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> f = <span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="keyword">return</span> <span class="variable language_">arguments</span>.<span class="property">callee</span>; &#125;;</span><br><span class="line"><span class="title function_">f</span>(); <span class="comment">// TypeError: &#x27;caller&#x27;, &#x27;callee&#x27;, and &#x27;arguments&#x27; properties may not be accessed on strict mode functions or the arguments objects for calls to them</span></span><br></pre></td></tr></table></figure><h2 id="“安全的”-JavaScript">“安全的” JavaScript</h2><p>严格模式下更容易写出“安全”的JavaScript。</p><h3 id="this关键字">this关键字</h3><p>在严格模式下通过this传递给一个函数的值不会被强制转换为一个对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;　　　　</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);　　</span><br><span class="line">&#125;　</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;　　　　</span><br><span class="line">    <span class="string">&quot;use strict&quot;</span>;　　　　</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">f.<span class="title function_">bind</span>(<span class="number">3</span>)();  <span class="comment">//[Number: 3]</span></span><br><span class="line">f1.<span class="title function_">bind</span>(<span class="number">3</span>)();  <span class="comment">//3</span></span><br></pre></td></tr></table></figure><p>对一个普通的函数来说，this总会是一个对象：不管调用时this它本来就是一个对象；还是用布尔值，字符串或者数字调用函数时函数里面被封装成对象的this；还是使用undefined或者null调用函数时this代表的全局对象（使用call, apply或者bind方法来指定一个确定的this）。</p><p>这种自动转化为对象的过程不仅是一种性能上的损耗，同时在浏览器中暴露出全局对象也会成为安全隐患。</p><p>所以对于一个开启严格模式的函数，指定的this不再被封装为对象，而且如果没有指定this的话它值是undefined。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fun</span>(<span class="params"></span>) &#123; <span class="keyword">return</span> <span class="variable language_">this</span>; &#125;</span><br><span class="line"><span class="title function_">assert</span>(<span class="title function_">fun</span>() === <span class="literal">undefined</span>);</span><br><span class="line"><span class="title function_">assert</span>(fun.<span class="title function_">call</span>(<span class="number">2</span>) === <span class="number">2</span>);</span><br><span class="line"><span class="title function_">assert</span>(fun.<span class="title function_">apply</span>(<span class="literal">null</span>) === <span class="literal">null</span>);</span><br><span class="line"><span class="title function_">assert</span>(fun.<span class="title function_">call</span>(<span class="literal">undefined</span>) === <span class="literal">undefined</span>);</span><br><span class="line"><span class="title function_">assert</span>(fun.<span class="title function_">bind</span>(<span class="literal">true</span>)() === <span class="literal">true</span>);</span><br></pre></td></tr></table></figure><h2 id="为未来的ECMAScript版本铺平道路">为未来的ECMAScript版本铺平道路</h2><h3 id="保留的关键字">保留的关键字</h3><p>在严格模式中一部分字符变成了保留的关键字。这些字符包括implements, interface, let, package, private, protected, public,<br>static和yield。在严格模式下，你不能再用这些名字作为变量名或者形参名。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">package</span>(<span class="params">protected</span>) <span class="comment">// !!!</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;use strict&quot;</span>;</span><br><span class="line">    <span class="keyword">var</span> implements; <span class="comment">// !!!</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">interface</span>: <span class="comment">// !!!</span></span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">break</span> interface; <span class="comment">// !!!</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">private</span>(<span class="params"></span>) &#123;&#125; <span class="comment">// !!!</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fun</span>(<span class="params"><span class="keyword">static</span></span>) &#123;</span><br><span class="line">    <span class="string">&#x27;use strict&#x27;</span>;</span><br><span class="line">&#125; <span class="comment">// !!!</span></span><br></pre></td></tr></table></figure><h3 id="函数声明">函数声明</h3><p>严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说，不允许在非函数的代码块内声明函数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123; &#125; <span class="comment">// !!! 语法错误</span></span><br><span class="line">  <span class="title function_">f</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123; &#125; <span class="comment">// !!! 语法错误</span></span><br><span class="line">  <span class="title function_">f2</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">baz</span>(<span class="params"></span>) <span class="comment">// 合法</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">eit</span>(<span class="params"></span>) &#123; &#125; <span class="comment">// 同样合法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1>总结</h1><p>严格模式虽然限制了一部分JavaScript书写和运行的自由，但是随着JavaScript在更大的工程中扮演更重要的角色，规范化是必经之路。</p><h1>参考链接</h1><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode">MDN严格模式</a></li><li><a href="http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html">Javascript 严格模式详解</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h1&gt;简介&lt;/h1&gt;
&lt;p&gt;ECMAScript 5 引入了严格模式（strict mode）的概念。严格模式为JavaScript定义了一种不同的解析与执行模型。在严格模式下，ECMAScript 3中的一些不确定的行为将得到处理，而且对于某些不安全的操作也会抛出错误。（&lt;a href=&quot;https://github.com/Leo555/JavaScript/blob/master/books/JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%EF%BC%88%E7%AC%AC3%E7%89%88%EF%BC%89%E3%80%91%E4%B8%AD%E6%96%87%20%E9%AB%98%E6%B8%85%20%E5%AE%8C%E6%95%B4%20%E8%AF%A6%E7%BB%86%E4%B9%A6%E7%AD%BE%E7%89%88.pdf&quot;&gt;JavaScript高级程序设计&lt;/a&gt;）&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://lz5z.com/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://lz5z.com/tags/JavaScript/"/>
    
  </entry>
  
</feed>
