<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Raul Zuo</title>
  
  <subtitle>Raul Zuo个人小站</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://raulzuo.github.io/"/>
  <updated>2019-03-17T11:20:24.666Z</updated>
  <id>https://raulzuo.github.io/</id>
  
  <author>
    <name>Raul Zuo</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>js-excel</title>
    <link href="https://raulzuo.github.io/js-excel/"/>
    <id>https://raulzuo.github.io/js-excel/</id>
    <published>2019-03-17T07:26:26.000Z</published>
    <updated>2019-03-17T11:20:24.666Z</updated>
    
    <content type="html"><![CDATA[<h2 id="纯JavaScript前端解析Excel文件"><a href="#纯JavaScript前端解析Excel文件" class="headerlink" title="纯JavaScript前端解析Excel文件"></a>纯JavaScript前端解析Excel文件</h2><p>通常情况下，读取Excel都有由后台来处理，不过会出现需要前端直接解析文件然后再校验内容格式是否正确。<br>在这种情况下，只需通过 <a href="https://www.npmjs.com/package/xlsx" target="_blank" rel="noopener">xlsc</a> 就能实现。</p><h3 id="解析Excel文件"><a href="#解析Excel文件" class="headerlink" title="解析Excel文件"></a>解析Excel文件</h3><p>使用 <code>FileReader</code> 对象读取指定的 <code>File</code> 和 <code>Blob</code> 对象指定要读取的文件或者数据，在 <code>FileReader</code> 对象加载结束（onload事件）后进行Excel数据的处理。</p><p>整个Excel数据处理中，主要是以下四个对象和函数：</p><ol><li>workbook对象，整个xlsx文件</li><li>worksheet对象，xlsx文件中的sheet，一个workbook由多个worksheet组成</li><li>XLSX.utils.sheet_to_json方法，将worksheet转化为JSON数据</li><li>XLSX.utils.json_to_sheet方法，将JSON数据转换成worksheet对象</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> XLSX <span class="keyword">from</span> <span class="string">'xlsx'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> reader = <span class="keyword">new</span> FileReader();</span><br><span class="line">reader.onload = <span class="function"><span class="keyword">function</span> (<span class="params">event</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 获取 workbook</span></span><br><span class="line">  <span class="keyword">const</span> wb = XLSX.read(event.target.result, &#123; <span class="attr">type</span>: <span class="string">'binary'</span> &#125;);</span><br><span class="line">  <span class="comment">// 获取 workbook 第一个 worksheet 中的数据</span></span><br><span class="line">  <span class="keyword">const</span> ws = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[<span class="number">0</span>]]);</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 读取原始 file 或者 blob，并将该文件转化为原始二进制数据</span></span><br><span class="line">reader.readAsBinaryString(file);</span><br></pre></td></tr></table></figure><h3 id="JSON数据转化为Excel文件"><a href="#JSON数据转化为Excel文件" class="headerlink" title="JSON数据转化为Excel文件"></a>JSON数据转化为Excel文件</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> XLSX <span class="keyword">from</span> <span class="string">"xlsx"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 worksheet</span></span><br><span class="line"><span class="keyword">const</span> ws = XLSX.utils.json_to_sheet(data);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新建空workbook，然后加入worksheet</span></span><br><span class="line"><span class="keyword">const</span> wb = XLSX.utils.book_new();</span><br><span class="line">XLSX.utils.book_append_sheet(wb, ws, <span class="string">"sheet1"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成 xlsx 文件</span></span><br><span class="line">XLSX.writeFile(wb, <span class="string">"sheetjs.xlsx"</span>);</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;纯JavaScript前端解析Excel文件&quot;&gt;&lt;a href=&quot;#纯JavaScript前端解析Excel文件&quot; class=&quot;headerlink&quot; title=&quot;纯JavaScript前端解析Excel文件&quot;&gt;&lt;/a&gt;纯JavaScript前端解析Excel
      
    
    </summary>
    
      <category term="前端基础" scheme="https://raulzuo.github.io/categories/%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/"/>
    
    
      <category term="javascript" scheme="https://raulzuo.github.io/tags/javascript/"/>
    
  </entry>
  
  <entry>
    <title>Vue源码解析---数据的双向绑定</title>
    <link href="https://raulzuo.github.io/vue-data-bind/"/>
    <id>https://raulzuo.github.io/vue-data-bind/</id>
    <published>2018-12-23T09:02:23.000Z</published>
    <updated>2019-03-17T06:39:04.076Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文主要抽离Vue源码中数据双向绑定的核心代码，解析Vue是如何实现数据的双向绑定<br>核心思想是ES5的<strong>Object.defineProperty()</strong>和<strong>发布-订阅</strong>模式</p></blockquote><h3 id="整体结构"><a href="#整体结构" class="headerlink" title="整体结构"></a>整体结构</h3><ol><li>改造Vue实例中的data，通过Object.defineProperty()将其所有属性设置为<strong>访问器属性</strong></li><li>对每个属性添加<strong>Observer</strong>，并在observer中添加订阅者对象序列<strong>Dep</strong></li><li>添加订阅者对象<strong>Watcher</strong>，每次初始化的时候添加到对应data属性中的<strong>Dep</strong>之中</li></ol><p>所有，我们从代码的角度将整体分为三个部分：<strong>监听数据变化</strong>、<strong>管理订阅者</strong>、<strong>订阅者</strong></p><h3 id="监听数据变化"><a href="#监听数据变化" class="headerlink" title="监听数据变化"></a>监听数据变化</h3><p>使用ES5中的<strong>Object.defineProperty</strong>将data中的属性修改为<strong>访问者属性</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Dep用于订阅者的存储和收集，将在下面实现</span></span><br><span class="line"><span class="keyword">import</span> Dep <span class="keyword">from</span> <span class="string">'Dep'</span></span><br><span class="line"><span class="comment">// Observer类用于给data属性添加set&amp;get方法</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Observer</span></span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(value)&#123;</span><br><span class="line">    <span class="keyword">this</span>.value = value</span><br><span class="line">    <span class="keyword">this</span>.walk(value)</span><br><span class="line">  &#125;</span><br><span class="line">  walk(value)&#123;</span><br><span class="line">    <span class="built_in">Object</span>.keys(value).forEach(<span class="function"><span class="params">key</span> =&gt;</span> <span class="keyword">this</span>.convert(key, value[key]))</span><br><span class="line">  &#125;</span><br><span class="line">  convert(key, val)&#123;</span><br><span class="line">    defineReactive(<span class="keyword">this</span>.value, key, val)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">defineReactive</span>(<span class="params">obj, key, val</span>)</span>&#123;</span><br><span class="line">  <span class="comment">// 用于存放某个属性的所有订阅者</span></span><br><span class="line">  <span class="keyword">var</span> dep = <span class="keyword">new</span> Dep()</span><br><span class="line">  <span class="comment">// 给当前属性的值添加监听</span></span><br><span class="line">  <span class="keyword">var</span> chlidOb = observe(val)</span><br><span class="line">  <span class="built_in">Object</span>.defineProperty(obj, key, &#123;</span><br><span class="line">    enumerable: <span class="literal">true</span>,</span><br><span class="line">    configurable: <span class="literal">true</span>,</span><br><span class="line">    <span class="keyword">get</span>: ()=&gt; &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'get value'</span>)</span><br><span class="line">      <span class="comment">// 如果Dep类存在target属性，将其添加到dep实例的subs数组中</span></span><br><span class="line">      <span class="comment">// target指向一个Watcher实例，每个Watcher都是一个订阅者</span></span><br><span class="line">      <span class="comment">// Watcher实例在实例化过程中，会读取data中的某个属性，从而触发当前get方法</span></span><br><span class="line">      <span class="keyword">if</span>(Dep.target)&#123;</span><br><span class="line">        dep.addSub(Dep.target)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> val</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="keyword">set</span>: (newVal) =&gt; &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'new value setted'</span>)</span><br><span class="line">      <span class="keyword">if</span>(val === newVal) <span class="keyword">return</span></span><br><span class="line">      val = newVal</span><br><span class="line">      <span class="comment">// 对新值进行监听</span></span><br><span class="line">      chlidOb = observe(newVal)</span><br><span class="line">      <span class="comment">// 通知所有订阅者，数值被改变了</span></span><br><span class="line">      dep.notify()</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">export</span> <span class="function"><span class="keyword">function</span> <span class="title">observe</span>(<span class="params">value</span>)</span>&#123;</span><br><span class="line">  <span class="comment">// 当值不存在，或者不是复杂数据类型时，不再需要继续深入监听</span></span><br><span class="line">  <span class="keyword">if</span>(!value || <span class="keyword">typeof</span> value !== <span class="string">'object'</span>)&#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> Observer(value)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="管理订阅者"><a href="#管理订阅者" class="headerlink" title="管理订阅者"></a>管理订阅者</h3><p>对订阅者进行收集，存储和通知</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Dep</span></span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>()&#123;</span><br><span class="line">    <span class="keyword">this</span>.subs = []</span><br><span class="line">  &#125;</span><br><span class="line">  addSub(sub)&#123;</span><br><span class="line">    <span class="comment">// 在收集订阅者的时候，需要对subs中的订阅者进行去重，这边不详细解析</span></span><br><span class="line">    <span class="keyword">this</span>.subs.push(sub)</span><br><span class="line">  &#125;</span><br><span class="line">  notify()&#123;</span><br><span class="line">    <span class="comment">// 通知所有的订阅者(Watcher)，触发订阅者的相应逻辑处理</span></span><br><span class="line">    <span class="keyword">this</span>.subs.forEach(<span class="function">(<span class="params">sub</span>) =&gt;</span> sub.update())</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="订阅者"><a href="#订阅者" class="headerlink" title="订阅者"></a>订阅者</h3><p>每个watcher对象都是对data中每个属性的订阅，是多对一的关系，每个watcher只能对应一个data属性，而一个data属性可以对应多个watcher</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Dep <span class="keyword">from</span> <span class="string">'Dep'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Watcher</span></span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(vm, expOrFn, cb)&#123;</span><br><span class="line">    <span class="keyword">this</span>.vm = vm <span class="comment">// 被订阅的数据一定来自于当前Vue实例</span></span><br><span class="line">    <span class="keyword">this</span>.cb = cb <span class="comment">// 当数据更新时想要做的事情</span></span><br><span class="line">    <span class="keyword">this</span>.expOrFn = expOrFn <span class="comment">// 被订阅的数据</span></span><br><span class="line">    <span class="keyword">this</span>.val = <span class="keyword">this</span>.get() <span class="comment">// 维护更新之前的数据</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 对外暴露的接口，用于在订阅的数据被更新时，由订阅者管理员(Dep)调用</span></span><br><span class="line">  update()&#123;</span><br><span class="line">    <span class="keyword">this</span>.run()</span><br><span class="line">  &#125;</span><br><span class="line">  run()&#123;</span><br><span class="line">    <span class="keyword">const</span> val = <span class="keyword">this</span>.get()</span><br><span class="line">    <span class="keyword">if</span>(val !== <span class="keyword">this</span>.val)&#123;</span><br><span class="line">      <span class="keyword">this</span>.val = val;</span><br><span class="line">      <span class="keyword">this</span>.cb.call(<span class="keyword">this</span>.vm)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">get</span>()&#123;</span><br><span class="line">    <span class="comment">// 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时，通知订阅者管理员收集当前订阅者</span></span><br><span class="line">    Dep.target = <span class="keyword">this</span></span><br><span class="line">    <span class="keyword">const</span> val = <span class="keyword">this</span>.vm._data[<span class="keyword">this</span>.expOrFn]</span><br><span class="line">    <span class="comment">// 置空，用于下一个Watcher使用</span></span><br><span class="line">    Dep.target = <span class="literal">null</span></span><br><span class="line">    <span class="keyword">return</span> val;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h3><p>下边我们创建一个简易的Vue来实际运行下对数据的监听</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Observer, &#123;observe&#125; <span class="keyword">from</span> <span class="string">'Observer'</span></span><br><span class="line"><span class="keyword">import</span> Watcher <span class="keyword">from</span> <span class="string">'Watcher'</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">Vue</span></span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(options = &#123;&#125;)&#123;</span><br><span class="line">    <span class="comment">// 简化了$options的处理</span></span><br><span class="line">    <span class="keyword">this</span>.$options = options</span><br><span class="line">    <span class="comment">// 简化了对data的处理</span></span><br><span class="line">    <span class="keyword">let</span> data = <span class="keyword">this</span>._data = <span class="keyword">this</span>.$options.data</span><br><span class="line">    <span class="comment">// 将所有data最外层属性代理到Vue实例上</span></span><br><span class="line">    <span class="built_in">Object</span>.keys(data).forEach(<span class="function"><span class="params">key</span> =&gt;</span> <span class="keyword">this</span>._proxy(key))</span><br><span class="line">    <span class="comment">// 监听数据</span></span><br><span class="line">    observe(data)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 对外暴露调用订阅者的接口，内部主要在指令中使用订阅者</span></span><br><span class="line">  $watch(expOrFn, cb)&#123;</span><br><span class="line">    <span class="keyword">new</span> Watcher(<span class="keyword">this</span>, expOrFn, cb)</span><br><span class="line">  &#125;</span><br><span class="line">  _proxy(key)&#123;</span><br><span class="line">    <span class="built_in">Object</span>.defineProperty(<span class="keyword">this</span>, key, &#123;</span><br><span class="line">      configurable: <span class="literal">true</span>,</span><br><span class="line">      enumerable: <span class="literal">true</span>,</span><br><span class="line">      <span class="keyword">get</span>: () =&gt; this._data[key],</span><br><span class="line">      <span class="keyword">set</span>: (val) =&gt; &#123;</span><br><span class="line">        <span class="keyword">this</span>._data[key] = val</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><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'./Vue'</span>;</span><br><span class="line"><span class="keyword">let</span> demo = <span class="keyword">new</span> Vue(&#123;</span><br><span class="line">  data: &#123;</span><br><span class="line">    <span class="string">'a'</span>: &#123;</span><br><span class="line">      <span class="string">'ab'</span>: &#123;</span><br><span class="line">        <span class="string">'c'</span>: <span class="string">'C'</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">'b'</span>: [</span><br><span class="line">      <span class="string">'bb'</span>: <span class="string">'BB'</span>,</span><br><span class="line">      <span class="string">'bbb'</span>: <span class="string">'BBB'</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="string">'c'</span>: <span class="string">'C'</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line">demo.$watch(<span class="string">'c'</span>, () =&gt; <span class="built_in">console</span>.log(<span class="string">'c is changed'</span>));</span><br><span class="line"><span class="comment">// get value</span></span><br><span class="line">demo.$watch(<span class="string">'a.ab'</span>, () =&gt; <span class="built_in">console</span>.log(<span class="string">'a.ab is changed'</span>));</span><br><span class="line">demo.$watch(<span class="string">'b'</span>, () =&gt; <span class="built_in">console</span>.log(<span class="string">'b is changed'</span>));</span><br><span class="line"><span class="comment">// get value</span></span><br><span class="line">demo.c = <span class="string">'CCC'</span>;</span><br><span class="line"><span class="comment">// new value setted</span></span><br><span class="line"><span class="comment">// get value</span></span><br><span class="line"><span class="comment">// c is changed</span></span><br><span class="line">demo.a.ab = <span class="string">'AB'</span>;</span><br><span class="line"><span class="comment">// get value</span></span><br><span class="line"><span class="comment">// new value setted</span></span><br><span class="line">demo.b.push(&#123;<span class="string">'bbbb'</span>: <span class="string">'BBBB'</span>&#125;);</span><br><span class="line"><span class="comment">// get value</span></span><br></pre></td></tr></table></figure><p>根据实例的输出结果，我们很奇怪的发现，只有对简单的数据监听才能实现数据双向绑定。</p><ol><li><code>demo.$watch(&#39;a.ab&#39;, () =&gt; console.log(&#39;a.ab is changed&#39;))</code>注册订阅者并没有调用<code>getter</code></li><li><code>demo.a.ab = &#39;AB&#39;</code>有监听到数据的变化，并没有调用对应的callback</li><li><code>demo.b.push({&#39;bbbb&#39;: &#39;BBBB&#39;})</code>对数值进行操作，并没有调用对应的callback</li></ol><p>这是为什么呢？因为我们对数据的监听的实现，目前仅限于简单对应，对于某个属性内部有更多复杂属性时，就无能为力了。</p><p>为了实现进一步对数据和复杂对象的监听，请戳<a href>Vue源码解析—数组的双向绑定</a>和<a href>Vue源码解析—复杂队形的双向绑定</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;本文主要抽离Vue源码中数据双向绑定的核心代码，解析Vue是如何实现数据的双向绑定&lt;br&gt;核心思想是ES5的&lt;strong&gt;Object.defineProperty()&lt;/strong&gt;和&lt;strong&gt;发布-订阅&lt;/strong&gt;模式&lt;/p&gt;

      
    
    </summary>
    
      <category term="源码解析" scheme="https://raulzuo.github.io/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
      <category term="vue" scheme="https://raulzuo.github.io/tags/vue/"/>
    
  </entry>
  
  <entry>
    <title>Koa源码分析（三） -- middleware机制的实现</title>
    <link href="https://raulzuo.github.io/koa-middleware/"/>
    <id>https://raulzuo.github.io/koa-middleware/</id>
    <published>2018-12-23T08:54:05.000Z</published>
    <updated>2019-03-17T06:39:04.076Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h2><p>本系列是关于Koa框架的文章，目前关注版本是<strong>Koa v1</strong>。主要分为以下几个方面：</p><ol><li><a href="/koa-generator/">Koa源码分析（一） – generator</a></li><li><a href="/koa-co/">Koa源码分析（二） – co的实现</a></li><li><a href="/koa-middleware/">Koa源码分析（三） – middleware机制的实现</a></li></ol><h2 id="Koa概括"><a href="#Koa概括" class="headerlink" title="Koa概括"></a>Koa概括</h2><p>Koa是基于generator与co之上的新一代中间件框架，它的优势主要集中在以下几个方面</p><ol><li>中间件机制</li><li>封装了request/response, context对象</li><li>使用yield，方便异步编程进行流程控制</li><li>在忽略同步或者异步的情况下，使用try catch可以获取程序运行中的异常（错误处理是服务端程序的核心）</li></ol><h2 id="示例代码"><a href="#示例代码" class="headerlink" title="示例代码"></a>示例代码</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">var</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"><span class="comment">//添加中间件1</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span> *(<span class="params">next</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">var</span> start = <span class="keyword">new</span> <span class="built_in">Date</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"start=======1111"</span>);</span><br><span class="line">  <span class="keyword">yield</span> next;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"end  =======1111"</span>);</span><br><span class="line">  <span class="keyword">var</span> ms = <span class="keyword">new</span> <span class="built_in">Date</span> - start;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'%s %s - %s'</span>, <span class="keyword">this</span>.method, <span class="keyword">this</span>.url, ms);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//添加中间件2</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span> *(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"start=======2222"</span>);</span><br><span class="line">  <span class="keyword">this</span>.body = <span class="string">'Hello World'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"end  =======2222"</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">3000</span>);</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">start=======1111</span></span><br><span class="line"><span class="comment">start=======2222</span></span><br><span class="line"><span class="comment">end  =======2222</span></span><br><span class="line"><span class="comment">end  =======1111</span></span><br><span class="line"><span class="comment">GET / - 10</span></span><br><span class="line"><span class="comment">start=======1111</span></span><br><span class="line"><span class="comment">start=======2222</span></span><br><span class="line"><span class="comment">end  =======2222</span></span><br><span class="line"><span class="comment">end  =======1111</span></span><br><span class="line"><span class="comment">GET /favicon.ico - 5</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>从上述代码中，我们添加了两个middleware，其中第一个middleware中有一个输入参数<code>next</code>，并通过<code>yield</code>进行调用。通过分析输出的log信息，不难发现，先运行middelware1中的<code>yield</code>之前的代码，然后进入到middleware2中运行，待middleware2运行结束后又回到middleware1中，并运行<code>yield</code>之后的代码。</p><p>由于<code>app.use</code>输入的是generator函数，如果熟悉generator函数的同学，或许会说，这是将middleware2作为middleware1中的next参数，依次调用多个generator函数。对，没错，实际运行就是这样的，但是koa框架是如何组织代码实现这样方面的调用，将<strong>地狱式调用</strong>的异步编程编程这样清晰的结构？请看下文的源码分析</p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><h3 id="Application初始化"><a href="#Application初始化" class="headerlink" title="Application初始化"></a>Application初始化</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Application</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!(<span class="keyword">this</span> <span class="keyword">instanceof</span> Application)) <span class="keyword">return</span> <span class="keyword">new</span> Application;</span><br><span class="line">  <span class="keyword">this</span>.env = process.env.NODE_ENV || <span class="string">'development'</span>;</span><br><span class="line">  <span class="keyword">this</span>.subdomainOffset = <span class="number">2</span>;</span><br><span class="line">  <span class="comment">// 用于存放中间件，即generator对象</span></span><br><span class="line">  <span class="keyword">this</span>.middleware = [];</span><br><span class="line">  <span class="keyword">this</span>.proxy = <span class="literal">false</span>;</span><br><span class="line">  <span class="comment">// 获得封装的上下文对象</span></span><br><span class="line">  <span class="keyword">this</span>.context = <span class="built_in">Object</span>.create(context);</span><br><span class="line">  <span class="comment">// 获取封装的请求对象</span></span><br><span class="line">  <span class="keyword">this</span>.request = <span class="built_in">Object</span>.create(request);</span><br><span class="line">  <span class="comment">// 获取封装的响应对象</span></span><br><span class="line">  <span class="keyword">this</span>.response = <span class="built_in">Object</span>.create(response);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="启动服务"><a href="#启动服务" class="headerlink" title="启动服务"></a>启动服务</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">listen() &#123;</span><br><span class="line">  debug(<span class="string">'listen'</span>);</span><br><span class="line">  <span class="comment">// 调用node原生中的创建服务</span></span><br><span class="line">  <span class="comment">// 其中callback()是服务创建的核心，具体见下面分析</span></span><br><span class="line">  <span class="keyword">const</span> server = http.createServer(<span class="keyword">this</span>.callback());</span><br><span class="line">  <span class="comment">// 开启服务的监听</span></span><br><span class="line">  <span class="keyword">return</span> server.listen.apply(server, <span class="built_in">arguments</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="添加中间件"><a href="#添加中间件" class="headerlink" title="添加中间件"></a>添加中间件</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">app.use = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="keyword">this</span>.experimental) &#123;</span><br><span class="line">    <span class="comment">// es7 async functions are not allowed,</span></span><br><span class="line">    <span class="comment">// so we have to make sure that `fn` is a generator function</span></span><br><span class="line">    assert(fn &amp;&amp; <span class="string">'GeneratorFunction'</span> == fn.constructor.name, <span class="string">'app.use() requires a generator function'</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  debug(<span class="string">'use %s'</span>, fn._name || fn.name || <span class="string">'-'</span>);</span><br><span class="line">  <span class="comment">// 将输入的fn依次push到middleware数组中</span></span><br><span class="line">  <span class="keyword">this</span>.middleware.push(fn);</span><br><span class="line">  <span class="comment">// 返回this，以便链式调用</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="node-native-创建服务"><a href="#node-native-创建服务" class="headerlink" title="node native 创建服务"></a>node native 创建服务</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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">app.callback = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">this</span>.experimental) &#123;</span><br><span class="line">    <span class="built_in">console</span>.error(<span class="string">'Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.'</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 将中间件按照加入的顺序，实现yield的链式调用，即组织异步调用结构，详细见下面的compose</span></span><br><span class="line">  <span class="comment">// co.wrap方法将generator函数转化为Promise</span></span><br><span class="line">  <span class="keyword">var</span> fn = <span class="keyword">this</span>.experimental ? compose_es7(<span class="keyword">this</span>.middleware) : co.wrap(compose(<span class="keyword">this</span>.middleware));</span><br><span class="line">  <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!<span class="keyword">this</span>.listeners(<span class="string">'error'</span>).length) <span class="keyword">this</span>.on(<span class="string">'error'</span>, <span class="keyword">this</span>.onerror);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 返回node native的请求处理函数</span></span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">handleRequest</span>(<span class="params">req, res</span>)</span>&#123;</span><br><span class="line">    res.statusCode = <span class="number">404</span>;</span><br><span class="line">    <span class="keyword">var</span> ctx = self.createContext(req, res);</span><br><span class="line">    onFinished(res, ctx.onerror);</span><br><span class="line">    fn.call(ctx).then(<span class="function"><span class="keyword">function</span> <span class="title">handleResponse</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      respond.call(ctx);</span><br><span class="line">    &#125;).catch(ctx.onerror);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="中间件异步构建"><a href="#中间件异步构建" class="headerlink" title="中间件异步构建"></a>中间件异步构建</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回一个启动函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">compose</span>(<span class="params">middleware</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> *(<span class="params">next</span>)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!next) next = noop();</span><br><span class="line">    <span class="keyword">var</span> i = middleware.length;</span><br><span class="line">    <span class="comment">// 对中间件队列从后遍历，逐个获取对应的generator对象</span></span><br><span class="line">    <span class="keyword">while</span> (i--) &#123;</span><br><span class="line">      <span class="comment">// 将后面的generator对象传递给前面中间件的generatorFunction</span></span><br><span class="line">      next = middleware[i].call(<span class="keyword">this</span>, next);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 返回一个yield，next指向第一个中间件的generator</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">yield</span> *next;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">noop</span>(<span class="params"></span>)</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>这样，我们就从返回的启动函数（generator函数）的yield处指向第一个中间件，然后从之前<code>while</code>循环构成的从前往后的调用链，依次调用下一个中间件，直至最后一个中间件然后再返回。</p><p>这边我们再次回到<code>callback()</code>这个启动函数处，调用<code>co.wrap()</code>实现对generator函数的逐步调用。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Abstract&quot;&gt;&lt;a href=&quot;#Abstract&quot; class=&quot;headerlink&quot; title=&quot;Abstract&quot;&gt;&lt;/a&gt;Abstract&lt;/h2&gt;&lt;p&gt;本系列是关于Koa框架的文章，目前关注版本是&lt;strong&gt;Koa v1&lt;/strong&gt;。
      
    
    </summary>
    
      <category term="源码解析" scheme="https://raulzuo.github.io/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
      <category term="nodejs" scheme="https://raulzuo.github.io/tags/nodejs/"/>
    
      <category term="koa" scheme="https://raulzuo.github.io/tags/koa/"/>
    
  </entry>
  
  <entry>
    <title>Koa源码分析（二） -- co的实现</title>
    <link href="https://raulzuo.github.io/koa-co/"/>
    <id>https://raulzuo.github.io/koa-co/</id>
    <published>2018-12-23T08:53:58.000Z</published>
    <updated>2019-03-17T06:39:04.075Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h2><p>本系列是关于Koa框架的文章，目前关注版本是<strong>Koa v1</strong>。主要分为以下几个方面：</p><ol><li><a href="/koa-generator/">Koa源码分析（一） – generator</a></li><li><a href="/koa-co/">Koa源码分析（二） – co的实现</a></li><li><a href="/koa-middleware">Koa源码分析（三） – middleware机制的实现</a></li></ol><h2 id="co"><a href="#co" class="headerlink" title="co"></a>co</h2><p>大名鼎鼎的co是什么？它是<a href="https://github.com/tj" target="_blank" rel="noopener">TJ大神</a>基于ES6的一些新特性开发的异步流程控制库，基于它所开发的<a href="https://github.com/koajs/koa" target="_blank" rel="noopener">koa</a>被视为未来主流的web框架。</p><p>koa基于co实现，而co又是使用了ES6的generator和promise特性。如果还不理解，可以查看阮一峰老师的<a href="http://es6.ruanyifeng.com/#docs/generator" target="_blank" rel="noopener">《ECMAScript 6 入门 — Generator》</a>和<a href="http://es6.ruanyifeng.com/#docs/promise" target="_blank" rel="noopener">《ECMAScript 6 入门 — Promise》</a>。目前co升级为4.X版本事，代码进行了一次颇有规模的重构，我们主要关注co（4.X）的实现思路和源码分析。</p><h2 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">co(<span class="function"><span class="keyword">function</span>* (<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="keyword">yield</span> <span class="built_in">Promise</span>.resolve(<span class="string">'one'</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(a);</span><br><span class="line">    <span class="keyword">var</span> b = <span class="keyword">yield</span> <span class="built_in">Promise</span>.reslove(<span class="string">'two'</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(b);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">'three'</span>;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">value</span>) =&gt;</span> <span class="built_in">console</span>.log(value));</span><br><span class="line"><span class="comment">// one</span></span><br><span class="line"><span class="comment">// two</span></span><br><span class="line"><span class="comment">// three</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">co(<span class="function"><span class="keyword">function</span>* (<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="keyword">var</span> res = <span class="keyword">yield</span> [<span class="built_in">Promise</span>.resolve(<span class="number">1</span>), <span class="built_in">Promise</span>.resolve(<span class="number">2</span>), <span class="built_in">Promise</span>.resolve(<span class="number">3</span>)];</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;).then(<span class="function">(<span class="params">value</span>) =&gt;</span> <span class="built_in">console</span>.log(res));</span><br><span class="line"><span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p>根据co的功能，它作为异步流程控制的作用，自动调用generator对象的next()方法，实现generator函数的运行，并返回最终运行的结果。</p><p>如果要涉及到co的实现细节，我们就会存在以下几个疑问：</p><ol><li>如何依次调用next()方法</li><li>如何将<strong>yield</strong>后边运算异步结果返回给对应的变量</li><li>co自身如何返回generator函数最后的return值</li></ol><p>接下来我们正对以上问题，分析TJ大神的源码</p><h2 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h2><h3 id="co源码的流程控制"><a href="#co源码的流程控制" class="headerlink" title="co源码的流程控制"></a>co源码的流程控制</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">co</span>(<span class="params">gen</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 保持当前函数的上下文</span></span><br><span class="line">  <span class="keyword">var</span> ctx = <span class="keyword">this</span>;</span><br><span class="line">  <span class="comment">// 截取co输入的参数，剔除arguments中的第一个参数，即gen对象，剩余参数作为gen的入参</span></span><br><span class="line">  <span class="keyword">var</span> args = slice.call(<span class="built_in">arguments</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 返回一个Promise对象，即最外围Promise对象</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 判断传入的gen是否为函数，若是则执行，将结果赋值给gen对象</span></span><br><span class="line">    <span class="comment">// 若不是，则不执行</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> gen === <span class="string">'function'</span>) gen = gen.apply(ctx, args);</span><br><span class="line">    <span class="comment">// 根据generator函数执行结果是否存在next字段，判断gen是否为generator迭代器对象</span></span><br><span class="line">    <span class="comment">// 若不是，则调用resolve返回最外围Promise对象的状态</span></span><br><span class="line">    <span class="keyword">if</span> (!gen || <span class="keyword">typeof</span> gen.next !== <span class="string">'function'</span>) <span class="keyword">return</span> resolve(gen);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 若是generator迭代器对象，开始控制gen.next()方法的调用</span></span><br><span class="line">    onFulfilled();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 两个用途</span></span><br><span class="line">    <span class="comment">// 一、generator函数的执行入口</span></span><br><span class="line">    <span class="comment">// 二、当做所有内部Promise对象的resolve方法，处理异步结果，并继续调用下一个Promise</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">onFulfilled</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">var</span> ret;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// gen运行至yield处被挂起，开始处理异步操作，并将异步操作的结果返回给ret.value</span></span><br><span class="line">        ret = gen.next(res);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="comment">// 若报错，直接调用reject返回外围Promise对象的状态，并传出错误对象</span></span><br><span class="line">        <span class="keyword">return</span> reject(e);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 将gen.next的执行结果传入next函数，实现依次串行调用gen.next方法</span></span><br><span class="line">      next(ret);</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="comment">// 当做所有内部Promise对象的reject方法，处理异步结果，并继续调用下一个Promise</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">onRejected</span>(<span class="params">err</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">var</span> ret;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        ret = gen.throw(err);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="comment">// 若报错，直接调用reject返回外围Promise对象的状态，并传出错误对象</span></span><br><span class="line">        <span class="keyword">return</span> reject(e);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 将gen.throw的执行结果传入next函数，实现依次串行调用gen.next方法</span></span><br><span class="line">      next(ret);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 实现串行调用gen.next的核心</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">next</span>(<span class="params">ret</span>) </span>&#123;</span><br><span class="line">      <span class="comment">// 判断内部Promise是否全部执行完毕</span></span><br><span class="line">      <span class="comment">// 若执行完毕，直接调用resolve改变外围Promise的状态，并返回最终的return值[问题3]</span></span><br><span class="line">      <span class="keyword">if</span> (ret.done) <span class="keyword">return</span> resolve(ret.value);</span><br><span class="line">      <span class="comment">// 若未执行完毕，调用toPromise方法将上一个Promise返回的值转化为Promise对象</span></span><br><span class="line">      <span class="comment">// 具体参见toPromise方法</span></span><br><span class="line">      <span class="keyword">var</span> value = toPromise.call(ctx, ret.value);</span><br><span class="line">      <span class="comment">// 根据value转化后的Promise对象的两个状态，执行下一个next方法</span></span><br><span class="line">      <span class="keyword">if</span> (value &amp;&amp; isPromise(value)) <span class="keyword">return</span> value.then(onFulfilled, onRejected);</span><br><span class="line">      <span class="comment">// 抛出不符合转化规则的类型的值</span></span><br><span class="line">      <span class="keyword">return</span> onRejected(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">'You may only yield a function, promise, generator, array, or object, '</span></span><br><span class="line">        + <span class="string">'but the following object was passed: "'</span> + <span class="built_in">String</span>(ret.value) + <span class="string">'"'</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>源码分析完了，我们可以把co串行调用generator函数中yield的过程总结如下：</p><ol><li>进入外围Promise</li><li>通过入口onFilfilled()方法，将generator函数运行至第一个yield处，执行该yield后边的异步操作，并将结果传入next方法</li><li>如果next中传入结果的done为true，则返回外围Promise的resolve</li><li>如果next中传入结果的done为true，则返回value（即yield后边的对象）是否可以转化为内部Promise对象。如无法转化，则抛出错误，返回外围Promise的reject</li><li>若能转化为Promise对象，将所有内部Promise并行执行，通过then(onFilfilled, onRejected)开始执行</li><li>在onFilfilled()或者onRejected()内部调用再次调用next()方法，实现串行执行yield，并肩yield后边的对象传递给next()，依次重复。</li><li>所有yield执行返回，将最后的return值返回给外围Promise的resovle方法，结束co对generator函数的调用</li></ol><h3 id="yield后面对象转化为Promise"><a href="#yield后面对象转化为Promise" class="headerlink" title="yield后面对象转化为Promise"></a>yield后面对象转化为Promise</h3><p>能够在co中实现generator函数的逐步调用next()方法，转化为内部Promise将至关重要，而源码是如何转化的呢？哪些对象又是能够转化的呢？接下来，我们看下源码。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">toPromise</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 确保obj有意义</span></span><br><span class="line">  <span class="keyword">if</span> (!obj) <span class="keyword">return</span> obj;</span><br><span class="line">  <span class="comment">// 若是Promise对象，则直接返回</span></span><br><span class="line">  <span class="keyword">if</span> (isPromise(obj)) <span class="keyword">return</span> obj;</span><br><span class="line">  <span class="comment">// 若是generator函数或者generator对象，则传入一个新的co，并返回新co的外围Promise</span></span><br><span class="line">  <span class="comment">// 作为当前co的内部Promise，这样实现多层级调用</span></span><br><span class="line">  <span class="keyword">if</span> (isGeneratorFunction(obj) || isGenerator(obj)) <span class="keyword">return</span> co.call(<span class="keyword">this</span>, obj);</span><br><span class="line">  <span class="comment">// 若是函数，则返回thunk规范的函数</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="string">'function'</span> == <span class="keyword">typeof</span> obj) <span class="keyword">return</span> thunkToPromise.call(<span class="keyword">this</span>, obj);</span><br><span class="line">  <span class="comment">// 若是数组，把数组中每个元素转化为内部Promise，返回Promise.all并行运算</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(obj)) <span class="keyword">return</span> arrayToPromise.call(<span class="keyword">this</span>, obj);</span><br><span class="line">  <span class="comment">// 若是对象，遍历对象中的每个key对应的value，转化成Promise.all并行运算</span></span><br><span class="line">  <span class="keyword">if</span> (isObject(obj)) <span class="keyword">return</span> objectToPromise.call(<span class="keyword">this</span>, obj);</span><br><span class="line">  <span class="keyword">return</span> obj;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">thunkToPromise</span>(<span class="params">fn</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> ctx = <span class="keyword">this</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span> (<span class="params">resolve, reject</span>) </span>&#123;</span><br><span class="line">    fn.call(ctx, <span class="function"><span class="keyword">function</span> (<span class="params">err, res</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (err) <span class="keyword">return</span> reject(err);</span><br><span class="line">      <span class="keyword">if</span> (<span class="built_in">arguments</span>.length &gt; <span class="number">2</span>) res = slice.call(<span class="built_in">arguments</span>, <span class="number">1</span>);</span><br><span class="line">      resolve(res);</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="function"><span class="keyword">function</span> <span class="title">arrayToPromise</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// Array.map并行计算返回每一个元素的Promise</span></span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.all(obj.map(toPromise, <span class="keyword">this</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">objectToPromise</span>(<span class="params">obj</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">var</span> results = <span class="keyword">new</span> obj.constructor();</span><br><span class="line">  <span class="keyword">var</span> keys = <span class="built_in">Object</span>.keys(obj);</span><br><span class="line">  <span class="keyword">var</span> promises = [];</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; keys.length; i++) &#123;</span><br><span class="line">    <span class="keyword">var</span> key = keys[i];</span><br><span class="line">    <span class="keyword">var</span> promise = toPromise.call(<span class="keyword">this</span>, obj[key]);</span><br><span class="line">    <span class="keyword">if</span> (promise &amp;&amp; isPromise(promise)) defer(promise, key);</span><br><span class="line">    <span class="keyword">else</span> results[key] = obj[key];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// Promise链式调用，后续的then能偶获取此处的results</span></span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.all(promises).then(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> results;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">defer</span>(<span class="params">promise, key</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// key对应的元素成功转化为Promise对象后，构造这些Promise的resovle方法</span></span><br><span class="line">    <span class="comment">// 以便在results中获取每个Promise对象成功执行后结果</span></span><br><span class="line">    results[key] = <span class="literal">undefined</span>;</span><br><span class="line">    promises.push(promise.then(<span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>&#123;</span><br><span class="line">      results[key] = res;</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>结合上述分析，我们可以得到，yield后面只能是<strong>函数、Promise对象、Generator函数、Generator迭代器对象、数组（元素仅限之前的4类）和Object(对应value仅限定之前的4类)</strong></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Abstract&quot;&gt;&lt;a href=&quot;#Abstract&quot; class=&quot;headerlink&quot; title=&quot;Abstract&quot;&gt;&lt;/a&gt;Abstract&lt;/h2&gt;&lt;p&gt;本系列是关于Koa框架的文章，目前关注版本是&lt;strong&gt;Koa v1&lt;/strong&gt;。
      
    
    </summary>
    
      <category term="源码解析" scheme="https://raulzuo.github.io/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
      <category term="nodejs" scheme="https://raulzuo.github.io/tags/nodejs/"/>
    
      <category term="koa" scheme="https://raulzuo.github.io/tags/koa/"/>
    
  </entry>
  
  <entry>
    <title>Koa源码分析（一） -- generator</title>
    <link href="https://raulzuo.github.io/koa-generator/"/>
    <id>https://raulzuo.github.io/koa-generator/</id>
    <published>2018-12-23T08:44:20.000Z</published>
    <updated>2019-03-17T06:39:04.076Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h2><p>本系列是关于Koa框架的文章，目前关注版本是<strong>Koa v1</strong>。主要分为以下几个方面：</p><ol><li><a href="/koa-generator/">Koa源码分析（一） – generator</a></li><li><a href="/koa-co/">Koa源码分析（二） – co的实现</a></li><li><a href="/koa-middleware">Koa源码分析（三） – middleware机制的实现</a></li></ol><h2 id="Genetator函数"><a href="#Genetator函数" class="headerlink" title="Genetator函数"></a>Genetator函数</h2><p>Generator函数是ES6提供的一种异步编程解决方案，其语法行为完全不同于传统函数。</p><blockquote><p>详细解析可见阮一峰老师的<a href="http://es6.ruanyifeng.com/#docs/generator" target="_blank" rel="noopener">《ECMAScript 6 入门 — Generator》</a></p></blockquote><h3 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h3><p>两大特征:</p><ol><li><code>function</code> 关键字与函数名之间的 <code>*</code></li><li>函数体内部使用 <code>yield</code> 语句</li></ol><p>我们定义一个generatorFunction示例：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">firstGenerator</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> one = <span class="keyword">yield</span> <span class="string">'one'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(one);</span><br><span class="line">  <span class="keyword">var</span> two = <span class="keyword">yield</span> <span class="string">'two'</span>;</span><br><span class="line">  concole.log(two);</span><br><span class="line">  <span class="keyword">var</span> third = <span class="keyword">yield</span> <span class="string">'third'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(third);</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'over'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>带有 <code>*</code> 的函数声明就代表 <code>firstGenerator</code> 函数是一个generator函数，函数里面的 yield 关键字可以理解为在当前位置设置断点，这一点如有疑问，可以看后续。</p><h3 id="语法行为"><a href="#语法行为" class="headerlink" title="语法行为"></a>语法行为</h3><p>那generator函数的语法行为究竟与传统函数不同在哪里呢？下边我们来梳理下generator函数的运行步骤。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> it = firstGenerator();</span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">1</span>));  <span class="comment">// &#123;value: "one", done: false&#125;</span></span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">2</span>));  <span class="comment">// &#123;value: "two", done: false&#125;</span></span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">3</span>));  <span class="comment">// &#123;value: "third", done: false&#125;</span></span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">4</span>));  <span class="comment">// &#123;value: "over", done: true&#125;</span></span><br></pre></td></tr></table></figure><p>首先通过执行 <code>firstGenerator</code> 函数，我们可以得到一个generator对象 <code>it</code>，它是一个迭代器对象。此时， <code>firstGenerator</code> 函数并<strong>未执行</strong>，只是返回了迭代器对象 <code>it</code> ，我们可以通过 <code>it</code> 对象中 <code>next</code> 方法触发 <code>firstGenerator</code> 函数开始执行，此时我们调用 <code>it.next(1)</code>，注意 <code>next</code> 注入的参数 <code>1</code> 并没有任何效果。当 <code>firstGenerator</code> 函数执行到 <code>yield</code> 语句时，被打了断点，停留在此处，并将 <code>yield</code> 后的变量传递给 <code>it.next(1)</code> 结果对象中的 <code>value</code> 字段，另外其中的 <code>done</code> 字段表示 <code>firstGenerator</code> 函数尚未完全执行完，还停留在断点。以此同时，将执行权交换给 <code>it.next(1)</code>。</p><p>执行第二次 <code>it.next(2)</code>，执行权再次交给 <code>firstGenerator</code> 函数，并将 <code>next</code> 带入的参数传递给函数中的变量 <code>one</code>，此时输出 <code>2</code>。当运行到 <code>yield &#39;two&#39;</code> 语句时，再次将执行权返回给 <code>it.next(2)</code> 并传值。</p><p>第三次执行 <code>it.next(3)</code>的过程与第二次完全一样。</p><p>最后一次执行 <code>it.next(4)</code> 时，在此之前， <code>firstGenerator</code> 函数断点在 <code>var third = yield &#39;third&#39;</code>，当 <code>it.next(4)</code> 将执行权交给 <code>firstGenerator</code> 函数时，将 <code>4</code> 传递给变量 <code>third</code>，此刻输出 <code>4</code>。当执行到 <code>return</code> 语句时，整个函数已经执行完了，并将 <code>&#39;over&#39;</code>传递给 <code>it.next(4)</code> 返回结果中的 <code>value</code> 字段，且 <code>done</code> 字段为 <code>true</code>。若没有 <code>return</code> 语句，则 <code>value</code> 字段返回 <code>null</code>。</p><p>这样下来，整个 <code>firstGenerator</code> 整个函数执行完毕。我们可以将Generator函数比喻成<strong>懒惰的癞蛤蟆</strong>，每次都需要使用<strong>it.next()</strong>方法戳一下，才会有对应的行动。如果大家了解python中协程的概念，应该很好理解Generator函数的语法行为。</p><h3 id="进阶语法"><a href="#进阶语法" class="headerlink" title="进阶语法"></a>进阶语法</h3><p>在Generator函数中 <code>yield</code> 的语法，其后边的值也可以是函数、对象等等。 <code>yield</code> 后边可以是另一个Generator对象。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">subGen</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'step in sub generator'</span>);</span><br><span class="line">  <span class="keyword">var</span> b = <span class="keyword">yield</span> <span class="string">'sub 1'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(b);</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'step out sub generator'</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> subGenerator = <span class="keyword">new</span> subGen();</span><br><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">mainGen</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="keyword">yield</span> <span class="string">'main 1'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(a);</span><br><span class="line">  <span class="keyword">var</span> b = <span class="keyword">yield</span> *subGenerator;</span><br><span class="line">  <span class="built_in">console</span>.log(b);</span><br><span class="line">  <span class="keyword">var</span> c = <span class="keyword">yield</span> <span class="string">'main 2'</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(c);</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'over'</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> it = mainGen();</span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">1</span>));</span><br><span class="line"><span class="comment">// &#123;value: 'main 1', done: false&#125;</span></span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">2</span>));</span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// step in sub generator</span></span><br><span class="line"><span class="comment">// &#123;value: 'sub 1', done: false&#125;</span></span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">3</span>));</span><br><span class="line"><span class="comment">// 3</span></span><br><span class="line"><span class="comment">// step out sub generator</span></span><br><span class="line"><span class="comment">// null</span></span><br><span class="line"><span class="comment">// &#123;value: 'main 2', done: false&#125;</span></span><br><span class="line"><span class="built_in">console</span>.log(it.next(<span class="number">4</span>));</span><br><span class="line"><span class="comment">// 4</span></span><br><span class="line"><span class="comment">// &#123;value: 'over', done: true&#125;</span></span><br></pre></td></tr></table></figure><p><code>yield</code> 后面跟着 <code>*subGenerator</code> 对象，这等同于断点就进入 <code>subGenerator</code> 的 <code>subGen</code>里面，等待 <code>subGen</code> 全部执行完后再回来继续执行，类似于递归。<br>直白点说，就是将 <code>subGen</code> 内嵌到 <code>mainGen</code>中。</p><p><strong>在<code>subGen</code>函数中的return语句并不会起到断点的作用</strong></p><h3 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h3><p>Generator函数作为ES6的新特性，通过它可以很好的解决JavaScript中的<strong>恶魔</strong>回调问题。Koa框架使用这特性很好组织了异步代码的结构。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Abstract&quot;&gt;&lt;a href=&quot;#Abstract&quot; class=&quot;headerlink&quot; title=&quot;Abstract&quot;&gt;&lt;/a&gt;Abstract&lt;/h2&gt;&lt;p&gt;本系列是关于Koa框架的文章，目前关注版本是&lt;strong&gt;Koa v1&lt;/strong&gt;。
      
    
    </summary>
    
      <category term="源码解析" scheme="https://raulzuo.github.io/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
      <category term="nodejs" scheme="https://raulzuo.github.io/tags/nodejs/"/>
    
      <category term="koa" scheme="https://raulzuo.github.io/tags/koa/"/>
    
  </entry>
  
  <entry>
    <title>CSS布局技巧 -- 内凹圆角</title>
    <link href="https://raulzuo.github.io/CSS-inter-radius/"/>
    <id>https://raulzuo.github.io/CSS-inter-radius/</id>
    <published>2018-12-23T08:34:01.000Z</published>
    <updated>2019-03-17T06:39:04.075Z</updated>
    
    <content type="html"><![CDATA[<p>圆角，相信每一个了解CSS属性的都知道，通过border-radius实现圆角（外凸圆角），但是如果需要实现<strong>内凹圆角</strong>怎么办呢？比如四角内凹的元素，比如如下所示这样的内凹圆角</p><p><img src="http://images2015.cnblogs.com/blog/936737/201612/936737-20161204143241099-912365423.png" alt="内凹圆角"></p><p>对于这种问题，很多人的反应都是采用CSS的伪类或者子元素的<strong>绝对定位</strong>来覆盖，但是这样做的后果就是被覆盖部分是不透明的。在不同样色的背景下，会出现非常突兀的覆盖元素，这样一来，视觉上将会非常难看，自适应行不强。</p><p>如果需要实现透明的效果，很多人又会说，<strong>切图</strong>！对，切图作为background-image是一个很好的解决方法。如果只能使用CSS属性来实现，是不是就懵逼了。。。</p><p>下边就来介绍一种使用<strong>纯CSS实现这种背景透明的内凹圆角</strong>效果</p><p>首先先介绍两个CSS3的属性</p><ul><li><p>线性渐变 <strong>linear-gradient()</strong></p><p>从该属性名中可以看出，这是一个生成颜色渐变图片的CSS方法。根据渐变基准方向（Gradient line）和颜色点，其中变化颜色点可以有多个。</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></pre></td><td class="code"><pre><span class="line">// 两种颜色渐变</span><br><span class="line"><span class="selector-tag">background</span>: <span class="selector-tag">linear-gradient</span>(90<span class="selector-tag">deg</span>, <span class="selector-id">#F6327C</span>, <span class="selector-id">#DF3DF0</span>);</span><br><span class="line"></span><br><span class="line">// 两种以上颜色渐变，三个颜色点，在50%处有颜色#FF0，剩余的两个分别是起始点和结束点</span><br><span class="line"><span class="selector-tag">background</span>: <span class="selector-tag">linear-gradient</span>(90<span class="selector-tag">deg</span>, <span class="selector-id">#F6327C</span>, <span class="selector-id">#FF0</span> 50%, <span class="selector-id">#DF3DF0</span>);</span><br></pre></td></tr></table></figure></li></ul><blockquote><p>渐变基准方向若使用 to left/to right/to top/to bottom 这样属性时，注意-webkit-等内核兼容性方面的语法的变化，细节可以查看<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient" target="_blank" rel="noopener">文档</a></p></blockquote><ul><li><p>径向渐变 <strong>radial-gradient()</strong></p><p>顾名思义，从文字可以明白所谓的径向渐变就是以某点为圆心，固定直径内颜色渐变。它的属性包含了起始位置、方向、颜色渐变梯度，径向梯度允许变化的形状和大小。详细内容可以查看<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/radial-gradient" target="_blank" rel="noopener">文档</a></p><p>其语法为：</p><figure class="highlight"><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">radial-gradient([[ circle || &lt;length&gt;] [at &lt;position&gt; ]?, | [ ellipse || [ &lt;length&gt; | &lt;percentage&gt; ]&#123;2&#125; [ at &lt;position&gt; ]?, | [ [ circle | ellipse] || &lt;extent-keyword&gt; ] [at &lt;position&gt; ]?, | at &lt;position&gt; ,]? &lt;color-stop&gt; [, &lt;color-stop&gt;] +)</span><br><span class="line">where &lt;extent-keyword&gt; = closest-corner | closest-side | farthest-corner | farthest-side and &lt;color-stop&gt;     = &lt;color&gt; [ &lt;percentage&gt; | &lt;length&gt; ]?</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></pre></td><td class="code"><pre><span class="line">// 椭圆渐变</span><br><span class="line"><span class="selector-tag">background-image</span>: <span class="selector-tag">radial-gradient</span>(<span class="selector-tag">ellipse</span> <span class="selector-tag">farthest-corner</span> <span class="selector-tag">at</span> 45<span class="selector-tag">px</span> 45<span class="selector-tag">px</span> , <span class="selector-id">#00FFFF</span> 0%, <span class="selector-tag">rgba</span>(0, 0, 255, 0) 50%, <span class="selector-id">#0000FF</span> 95%);</span><br><span class="line"></span><br><span class="line">// 固定半径的圆渐变</span><br><span class="line"><span class="selector-tag">background-image</span>:  <span class="selector-tag">radial-gradient</span>(16<span class="selector-tag">px</span> <span class="selector-tag">at</span> 60<span class="selector-tag">px</span> 50% , <span class="selector-id">#000000</span> 0%, <span class="selector-id">#000000</span> 14<span class="selector-tag">px</span>, <span class="selector-tag">rgba</span>(0, 0, 0, 0<span class="selector-class">.3</span>) 18<span class="selector-tag">px</span>, <span class="selector-tag">rgba</span>(0, 0, 0, 0) 19<span class="selector-tag">px</span>);</span><br></pre></td></tr></table></figure></li></ul><h3 id="言归正传，回到内凹圆角"><a href="#言归正传，回到内凹圆角" class="headerlink" title="言归正传，回到内凹圆角"></a>言归正传，回到内凹圆角</h3><p>首先主元素左右两边留有固定大小的margin值，然后使用圆角元素覆盖对应的margin区域。若圆角元素不大于2个，可以使用<strong>::after</strong>和<strong>::before</strong>两个伪类元素。若大于4个，如四角内凹元素，则采用自元素的绝对定位方式。</p><p>DOM元素结构（采用伪类）：</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">div</span> <span class="attr">class</span>=<span class="string">"main"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>CSS结构分以下2个方面来进行设计：</p><ol><li><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">.main</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</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">40px</span>;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0</span> <span class="number">5px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">-webkit-linear-gradient</span>(left, #F6327C, #DF3DF0);</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(to right, #F6327C, #DF3DF0);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><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><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="selector-class">.main</span><span class="selector-pseudo">::before</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line">  <span class="attribute">display</span>: block;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">left</span>: -<span class="number">5px</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">5px</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">2px</span> <span class="number">0</span> <span class="number">0</span> <span class="number">2px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">-webkit-radial-gradient</span>(10px at left,transparent 50%,#F6327C 50%);</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">radial-gradient</span>(10px at left,transparent 50%,#F6327C 50%);</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.main</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line">  <span class="attribute">display</span>: block;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">right</span>: -<span class="number">5px</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">5px</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">0</span> <span class="number">2px</span> <span class="number">2px</span> <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">-webkit-radial-gradient</span>(10px at right,transparent 50%,#F6327C 50%);</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">radial-gradient</span>(10px at right,transparent 50%,#F6327C 50%);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p>根据以上代码就能够实现如图所示的带透明内凹圆角效果。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;圆角，相信每一个了解CSS属性的都知道，通过border-radius实现圆角（外凸圆角），但是如果需要实现&lt;strong&gt;内凹圆角&lt;/strong&gt;怎么办呢？比如四角内凹的元素，比如如下所示这样的内凹圆角&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://images201
      
    
    </summary>
    
      <category term="前端基础" scheme="https://raulzuo.github.io/categories/%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/"/>
    
    
      <category term="CSS" scheme="https://raulzuo.github.io/tags/CSS/"/>
    
  </entry>
  
  <entry>
    <title>CSS布局技巧 -- 各种居中</title>
    <link href="https://raulzuo.github.io/CSS-center/"/>
    <id>https://raulzuo.github.io/CSS-center/</id>
    <published>2018-12-23T08:23:16.000Z</published>
    <updated>2019-03-17T06:39:04.074Z</updated>
    
    <content type="html"><![CDATA[<h3 id="多行垂直居中"><a href="#多行垂直居中" class="headerlink" title="多行垂直居中"></a>多行垂直居中</h3><p>废话少说，直接上例子!!!</p><ul><li><p>display:table</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></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">"wrapper"</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">"content"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">span</span>&gt;</span>我是很短的文本<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">span</span>&gt;</span>我是很长的文本，内容非常多多多多多多多多多多多多...<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span>&gt;</span>我只是一个div标签<span class="tag">&lt;/<span class="name">div</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">"..."</span> <span class="attr">style</span>=<span class="string">"width:200px;height:200px;"</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>&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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.wrapper</span>&#123;<span class="attribute">display</span>:table;<span class="attribute">height</span>:<span class="number">800px</span>;<span class="attribute">width</span>:<span class="number">300px</span>;&#125;</span><br><span class="line"><span class="selector-class">.content</span>&#123;<span class="attribute">display</span>:table-cell;<span class="attribute">vertical-align</span>:middle;<span class="attribute">text-align</span>:center;&#125;</span><br><span class="line">//文本内容不足一行时居中，内容多行时左对齐</span><br><span class="line"><span class="selector-class">.content</span> <span class="selector-tag">span</span>&#123;<span class="attribute">display</span>:inline-block;<span class="attribute">text-align</span>:left;&#125;</span><br></pre></td></tr></table></figure><p>虽然对于现代浏览器都能够生效，但是在奇葩的IE6-7下就无法正常运行。在这里为了兼容这些特殊的情况，必然要使用另外一种相对定位和绝对定位的方式，在使用IE的特有的条件语法的同时，也可以使用ie hack来写。</p><p>兼容IE的CSS代码：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">.wrapper</span>&#123;<span class="attribute">display</span>:table;<span class="attribute">height</span>:<span class="number">800px</span>;<span class="attribute">width</span>:<span class="number">300px</span>;<span class="attribute">position</span>:relative;&#125;</span><br><span class="line">.content&#123;display:table-cell;vertical-align:middle;text-align:center;*position:absolute;*top:50%;*left:50%;&#125;</span><br><span class="line">//文本内容不足一行时居中，内容多行时左对齐</span><br><span class="line"><span class="selector-class">.content</span> <span class="selector-tag">span</span>&#123;<span class="attribute">display</span>:inline-block;<span class="attribute">text-align</span>:left;&#125;</span><br><span class="line">.content *&#123;position:relative;*top:-50%;*left:-50%;&#125;</span><br></pre></td></tr></table></figure><p><strong>优点：</strong><br>wrapper的高度没有限制，可以自适应，根据内部元素动态的改变高度<br><strong>缺点：</strong><br>结构复杂，需要增加额外的标签，对于IE6-7浏览器需要额外的兼容</p><p>以上结构能满足所有的多行内容的垂直水平居中，对于单行垂直水平居中的情况，实现相对更加简单，即通过line-height来实现。</p><hr></li><li><p>float属性</p><p>在child元素之前插入一个div元素，使其left浮动，高度为parent元素的50%，同时设置margin-bottom为<code>负</code>的child元素高度的<code>一半</code>，然后再child元素中清除浮动。这样，child元素就相对parent元素垂直居中。</p><p><strong>No Code No Truth</strong></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></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">"wrapper"</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">"floater"</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> <span class="attr">class</span>=<span class="string">"content"</span>&gt;</span>Contents<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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.floater</span>&#123;<span class="attribute">float</span>:left;<span class="attribute">height</span>:<span class="number">50%</span>;<span class="attribute">margin-bottom</span>:-<span class="number">100px</span>;&#125;</span><br><span class="line"><span class="selector-class">.content</span>&#123;<span class="attribute">clear</span>:both;<span class="attribute">height</span>:<span class="number">240px</span>;<span class="attribute">position</span>:relative;&#125;</span><br></pre></td></tr></table></figure><p><strong>优点：</strong></p><p>不存在兼容问题，所有浏览器都适应；内部元素的高度需要固定</p><p><strong>缺点：</strong></p><p>需要插入额外的空元素</p><hr></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;多行垂直居中&quot;&gt;&lt;a href=&quot;#多行垂直居中&quot; class=&quot;headerlink&quot; title=&quot;多行垂直居中&quot;&gt;&lt;/a&gt;多行垂直居中&lt;/h3&gt;&lt;p&gt;废话少说，直接上例子!!!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;display:table&lt;/p&gt;
&lt;p&gt;Htm
      
    
    </summary>
    
      <category term="前端基础" scheme="https://raulzuo.github.io/categories/%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/"/>
    
    
      <category term="CSS" scheme="https://raulzuo.github.io/tags/CSS/"/>
    
  </entry>
  
  <entry>
    <title>CSS布局技巧 -- 纯CSS让子元素的宽度总和决定其父元素的宽度</title>
    <link href="https://raulzuo.github.io/CSS-parent-width/"/>
    <id>https://raulzuo.github.io/CSS-parent-width/</id>
    <published>2018-12-23T08:19:26.000Z</published>
    <updated>2019-03-17T06:39:04.075Z</updated>
    
    <content type="html"><![CDATA[<h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><p>在移动端屏幕宽度有限的前提下，使用横向滚动的方式展示更多的内容。在这样的需求下，希望父元素作为容器，其宽度可以又横向排列资源的总宽度<strong>动态撑开</strong>，超过祖父元素的宽度；在不超过祖父元素时，自动继承100%的宽度。</p><p>DOM结构如下：</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="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"grantparent"</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">"parent"</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">"child"</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> <span class="attr">class</span>=<span class="string">"child"</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> <span class="attr">class</span>=<span class="string">"child"</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> <span class="attr">class</span>=<span class="string">"child"</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> <span class="attr">class</span>=<span class="string">"child"</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">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="一般处理方法"><a href="#一般处理方法" class="headerlink" title="一般处理方法"></a>一般处理方法</h3><ul><li>将子元素设为<code>float</code>或者<code>inline-block</code>，然后再通过js计算子元素的个数和其宽度，从而设置父元素的宽度</li><li>不利因素<ul><li>增加DOM操作</li><li>js重新设定属性增加渲染重绘次数</li><li>float在渲染时计算量比较大</li></ul></li></ul><h3 id="纯CSS处理方法"><a href="#纯CSS处理方法" class="headerlink" title="纯CSS处理方法"></a>纯CSS处理方法</h3><ul><li>设置父元素的属性</li></ul><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">white-space</span>: <span class="selector-tag">nowrap</span>;</span><br><span class="line"><span class="selector-tag">display</span>: <span class="selector-tag">inline-block</span>;</span><br></pre></td></tr></table></figure><ul><li>设置子元素的属性</li></ul><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="selector-tag">display</span>: <span class="selector-tag">inline-block</span>;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;使用场景&quot;&gt;&lt;a href=&quot;#使用场景&quot; class=&quot;headerlink&quot; title=&quot;使用场景&quot;&gt;&lt;/a&gt;使用场景&lt;/h3&gt;&lt;p&gt;在移动端屏幕宽度有限的前提下，使用横向滚动的方式展示更多的内容。在这样的需求下，希望父元素作为容器，其宽度可以又横向排列资源
      
    
    </summary>
    
      <category term="前端基础" scheme="https://raulzuo.github.io/categories/%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/"/>
    
    
      <category term="CSS" scheme="https://raulzuo.github.io/tags/CSS/"/>
    
  </entry>
  
  <entry>
    <title>CSS布局技巧 -- STICKY属性</title>
    <link href="https://raulzuo.github.io/CSS-sticky/"/>
    <id>https://raulzuo.github.io/CSS-sticky/</id>
    <published>2018-12-22T16:21:58.000Z</published>
    <updated>2019-03-17T06:39:04.075Z</updated>
    
    <content type="html"><![CDATA[<p>在一些很长的表格中，往往需要使用表头悬浮的设计以方便用户使用，例如H5电商页面通过下滑展示大量商品列表时，顶部的导航栏需要在离开屏幕时，需要固定在屏幕顶部以方便用户筛选类别。这种效果一直以来需要通过JavaScript来实现，此外，由于设置对应的DOM对象为fixed时会脱离常规流，会导致下部元素对象瞬间向上移动，影响用户体验。CSS3中的position:sticky为解决这些问题而生。</p><h3 id="position-sticky用法"><a href="#position-sticky用法" class="headerlink" title="position:sticky用法"></a>position:sticky用法</h3><p>sticky是CSS3的一个新属性，对象在常态时遵循常规流，如relative属性。但是，当对象滑动到屏幕外时则吸附在屏幕中设置的固定位置，如fixed属性。简而言之，sticky属性就像relative和fixed的合体，同时很好的解决对象脱离常规流时带来的下部对象瞬间偏移的问题。</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-class">.sticky</span> &#123;</span><br><span class="line">    <span class="attribute">position</span>: -webkit-sticky;</span><br><span class="line">    <span class="attribute">position</span>:    -moz-sticky;</span><br><span class="line">    <span class="attribute">position</span>:     -ms-sticky;</span><br><span class="line">    <span class="attribute">position</span>:         sticky;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">1px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>由于样式尚未进入标准，必须使用私有前缀。</p></blockquote><h3 id="浏览器兼容性"><a href="#浏览器兼容性" class="headerlink" title="浏览器兼容性"></a>浏览器兼容性</h3><p>虽然很多浏览器不支持sticky属性，但是可以通过JavaScript简单实现这种效果：</p><p><img src="/images/css_sticky.png" alt="浏览器兼容性"></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="selector-class">.sticky</span> &#123;</span><br><span class="line">    <span class="attribute">position</span>: -webkit-sticky;</span><br><span class="line">    <span class="attribute">position</span>:    -moz-sticky;</span><br><span class="line">    <span class="attribute">position</span>:     -ms-sticky;</span><br><span class="line">    <span class="attribute">position</span>:         sticky;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.header</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">height</span>: <span class="number">20px</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#fcc</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.empty</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">height</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.hidden</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h3><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">section</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">"header"</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> <span class="attr">class</span>=<span class="string">"empty hidden"</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">section</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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> $win = $(<span class="built_in">window</span>),</span><br><span class="line">      $header = $(<span class="string">'.header'</span>),</span><br><span class="line">      $empty = $(<span class="string">'.empty'</span>);</span><br><span class="line"></span><br><span class="line">$win.on(<span class="string">'scroll'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>&#123;</span><br><span class="line">     <span class="keyword">if</span>($win.scrollTop() &gt; $header.offset().top) &#123;</span><br><span class="line">         $header.addClass(<span class="string">'sticky'</span>);</span><br><span class="line">         $empty.removeClass(<span class="string">'hidden'</span>);</span><br><span class="line">     &#125; <span class="keyword">else</span> <span class="keyword">if</span>($win.scrollTop() &lt; $empty.offset().top) &#123;</span><br><span class="line">         $header.removeClass(<span class="string">'sticky'</span>);</span><br><span class="line">         $empty.addClass(<span class="string">'hidden'</span>);</span><br><span class="line">     &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>其中，当header对象脱离常规流时，使用empty元素占位，消除下部元素快速向上移动的问题。</p><p>如有错误，各位大牛敬请指出！</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在一些很长的表格中，往往需要使用表头悬浮的设计以方便用户使用，例如H5电商页面通过下滑展示大量商品列表时，顶部的导航栏需要在离开屏幕时，需要固定在屏幕顶部以方便用户筛选类别。这种效果一直以来需要通过JavaScript来实现，此外，由于设置对应的DOM对象为fixed时会脱
      
    
    </summary>
    
      <category term="前端基础" scheme="https://raulzuo.github.io/categories/%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/"/>
    
    
      <category term="CSS" scheme="https://raulzuo.github.io/tags/CSS/"/>
    
  </entry>
  
</feed>
