<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Mirreal Note]]></title>
  <link href="https://blog.mirreal.net/atom.xml" rel="self"/>
  <link href="https://blog.mirreal.net/"/>
  <updated>2019-04-10T23:23:30+08:00</updated>
  <id>https://blog.mirreal.net/</id>
  <author>
    <name><![CDATA[Patrick Ran]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[从零开始写一个 Promise 库]]></title>
    <link href="https://blog.mirreal.net/blog/2018/04/15/write-your-own-node-js-promise-library-from-scratch/"/>
    <updated>2018-04-15T12:15:14+08:00</updated>
    <id>https://blog.mirreal.net/blog/2018/04/15/write-your-own-node-js-promise-library-from-scratch</id>
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="http://thecodebarbarian.com/write-your-own-node-js-promise-library-from-scratch.html">Write Your Own Node.js Promise Library from Scratch</a></p>

<p>作者：<a href="https://twitter.com/code_barbarian">code_barbarian</a></p></blockquote>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a> 已经是 JavaScript 中异步处理的基石，<a href="http://thecodebarbarian.com/2015/03/20/callback-hell-is-a-myth">回调</a>的场景将会越来越少，而且现在<a href="http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html">可以直接在 Node.js 使用 async/await</a>。async/await 基于 Promise，因此需要了解 Promise 来掌握 async/await。这篇文章，将介绍如何编写一个 Promise 库，并演示如何使用 async/await。</p>

<h2>Promise 是什么？</h2>

<p>在 ES6 规范中，<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-promise-executor">Promise 是一个类</a>，它的构造函数接受一个 <code>executor</code> 函数。Promise 类的实例有一个 <a href="http://www.ecma-international.org/ecma-262/6.0/#sec-promise.prototype.then"><code>then()</code> 方法</a>。根据规范，Promise 还有其他的一些属性，但在这里可以暂时忽略，因为我们要实现的是一个精简版的库。下面是一个 <code>MyPromise</code> 类的脚手架：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>class MyPromise {
</span><span class='line'>    // `executor` 函数接受两个参数，`resolve()` 和 `reject()`
</span><span class='line'>    // 负责在异步操作成功(resolved)或者失败(rejected)的时候调用 `resolve()` 或者 `reject()`
</span><span class='line'>    constructor(executor) {}
</span><span class='line'>
</span><span class='line'>    // 当 promise 的状态是 fulfilled（完成）时调用 `onFulfilled` 方法，
</span><span class='line'>    // 当 promise 的状态是 rejected（失败）时调用 `onRejected` 方法
</span><span class='line'>    // 到目前为止，可以认为 'fulfilled' 和 'resolved' 是一样的
</span><span class='line'>    then(onFulfilled, onRejected) {}
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p><code>executor</code> 函数需要两个参数，<code>resolve()</code> 和 <code>reject()</code>。promise 是一个状态机，包含三个状态：</p>

<ul>
<li>pending：初始状态，既不是成功，也不是失败状态</li>
<li>fulfilled：意味着操作成功完成，返回结果值</li>
<li>rejected：意味着操作失败，返回错误信息</li>
</ul>


<p>这样很容易就能实现 <code>MyPromise</code> 构造函数的初始版本：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>constructor(executor) {
</span><span class='line'>    if (typeof executor !== 'function') {
</span><span class='line'>        throw new Error('Executor must be a function')
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>    // 初始状态，$state 表示 promise 的当前状态
</span><span class='line'>    // $chained 是当 promise 处在 settled 状态时需要调用的函数数组
</span><span class='line'>    this.$state = 'PENDING'
</span><span class='line'>    this.$chained = []
</span><span class='line'>
</span><span class='line'>    // 为处理器函数实现 `resolve()` 和 `reject()`
</span><span class='line'>    const resolve = res =&gt; {
</span><span class='line'>        // 只要当 `resolve()` 或 `reject()` 被调用
</span><span class='line'>        // 这个 promise 对象就不再处于 pending 状态，被称为 settled 状态
</span><span class='line'>        // 调用 `resolve()` 或 `reject()` 两次，以及在 `resolve()` 之后调用 `reject()` 是无效的
</span><span class='line'>        if (this.$state !== 'PENDING') {
</span><span class='line'>            return
</span><span class='line'>        }
</span><span class='line'>        // 后面将会谈到 fulfilled 和 resolved 之间存在细微差别
</span><span class='line'>        this.$state = 'FULFILLED'
</span><span class='line'>        this.$internalValue = res
</span><span class='line'>        // If somebody called `.then()` while this promise was pending, need
</span><span class='line'>        // to call their `onFulfilled()` function
</span><span class='line'>        for (const { onFulfilled } of this.$chained) {
</span><span class='line'>            onFulfilled(res)
</span><span class='line'>        }
</span><span class='line'>    }
</span><span class='line'>    const reject = err =&gt; {
</span><span class='line'>        if (this.$state !== 'PENDING') {
</span><span class='line'>            return
</span><span class='line'>        }
</span><span class='line'>        this.$state = 'REJECTED'
</span><span class='line'>        this.$internalValue = err
</span><span class='line'>        for (const { onRejected } of this.$chained) {
</span><span class='line'>            onRejected(err)
</span><span class='line'>        }
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>    // 如规范所言，调用处理器函数中的 `resolve()` 和 `reject()`
</span><span class='line'>    try {
</span><span class='line'>        // 如果处理器函数抛出一个同步错误，我们认为这是一个失败状态
</span><span class='line'>        // 需要注意的是，`resolve()` 和 `reject()` 只能被调用一次
</span><span class='line'>        executor(resolve, reject)
</span><span class='line'>    } catch (err) {
</span><span class='line'>        reject(err)
</span><span class='line'>    }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p><code>then()</code> 函数的实现更简单，它接受两个参数，<code>onFulfilled()</code> 和 <code>onRejected()</code>。<code>then()</code> 函数必须确保 promise 在 fulfilled 时调用 <code>onFulfilled()</code>，在 rejected 时调用 <code>onRejected()</code>。如果 promise 已经 resolved 或 rejected，<code>then()</code> 函数会立即调用 <code>onFulfilled()</code> 或 <code>onRejected()</code>。如果 promise 仍处于 pending 状态，就将函数推入 <code>$chained</code> 数组，因此后续 <code>resolve()</code> 和 <code>reject()</code> 函数仍然可以调用它们。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>then(onFulfilled, onRejected) {
</span><span class='line'>    if (this.$state === 'FULFILLED') {
</span><span class='line'>        onFulfilled(this.$internalValue)
</span><span class='line'>    } else if (this.$state === 'REJECTED') {
</span><span class='line'>        onRejected(this.$internalValue)
</span><span class='line'>    } else {
</span><span class='line'>        this.$chained.push({ onFulfilled, onRejected })
</span><span class='line'>    }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p><em>*除此之外：ES6 规范表示，如果在已经 resolved 或 rejected 的 promise 调用 <code>.then()</code>, 那么 <code>onFulfilled()</code> 或 <code>onRejected()</code> 将在下一个时序被调用。由于本文代码只是一个教学示例而不是规范的精确实现，因此实现会忽略这些细节。</em></p>

<h2>Promise 调用链</h2>

<p>上面的例子特意忽略了 promise 中最复杂也是最有用的部分：<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#Chaining">链式调用</a>。如果 <code>onFulfilled()</code> 或者 <code>onRejected()</code> 函数返回一个 promise，则 <code>then()</code> 应该返回一个 <a href="http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects">“locked in”</a> 的新 promise 以匹配这个 promise 的状态。例如：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>p = new MyPromise(resolve =&gt; {
</span><span class='line'>    setTimeout(() =&gt; resolve('World'), 100)
</span><span class='line'>})
</span><span class='line'>
</span><span class='line'>p
</span><span class='line'>    .then(res =&gt; new MyPromise(resolve =&gt; resolve(`Hello, ${res}`)))
</span><span class='line'>    // 在 100 ms 后打印 'Hello, World'
</span><span class='line'>    .then(res =&gt; console.log(res))</span></code></pre></td></tr></table></div></figure>


<p>下面是可以返回 promise 的 <code>.then()</code> 函数实现，这样就可以进行链式调用。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>then(onFulfilled, onRejected) {
</span><span class='line'>    return new MyPromise((resolve, reject) =&gt; {
</span><span class='line'>        // 确保在 `onFulfilled()` 和 `onRejected()` 的错误将导致返回的 promise 失败（reject)
</span><span class='line'>        const _onFulfilled = res =&gt; {
</span><span class='line'>            try {
</span><span class='line'>                // 如果 `onFulfilled()` 返回一个 promise, 确保 `resolve()` 能正确处理
</span><span class='line'>                resolve(onFulfilled(res))
</span><span class='line'>            } catch (err) {
</span><span class='line'>                reject(err)
</span><span class='line'>            }
</span><span class='line'>        }
</span><span class='line'>        const _onRejected = err =&gt; {
</span><span class='line'>            try {
</span><span class='line'>                reject(onRejected(err))
</span><span class='line'>            } catch (_err) {
</span><span class='line'>                reject(_err)
</span><span class='line'>            }
</span><span class='line'>        }
</span><span class='line'>        if (this.$state === 'FULFILLED') {
</span><span class='line'>            _onFulfilled(this.$internalValue)
</span><span class='line'>        } else if (this.$state === 'REJECTED') {
</span><span class='line'>            _onRejected(this.$internalValue)
</span><span class='line'>        } else {
</span><span class='line'>            this.$chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected })
</span><span class='line'>        }
</span><span class='line'>    })
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>现在 <code>then()</code> 返回一个 promise，但是还需要完成一些工作：如果 <code>onFulfilled()</code> 返回一个 promise，<code>resolve()</code> 要能够正确处理。所以 <code>resolve()</code> 函数需要在 <code>then()</code> 递归调用，下面是更新后的 <code>resolve()</code> 函数：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>const resolve = res =&gt; {
</span><span class='line'>    // 只要当 `resolve()` 或 `reject()` 被调用
</span><span class='line'>    // 这个 promise 对象就不再处于 pending 状态，被称为 settled 状态
</span><span class='line'>    // 调用 `resolve()` 或 `reject()` 两次，以及在 `resolve()` 之后调用 `reject()` 是无效的
</span><span class='line'>    if (this.$state !== 'PENDING') {
</span><span class='line'>        return
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>    // 如果 `res` 是 thenable（带有then方法的对象）
</span><span class='line'>    // 将锁定 promise 来保持跟 thenable 的状态一致
</span><span class='line'>    if (res !== null && typeof res.then === 'function') {
</span><span class='line'>        // 在这种情况下，这个 promise 是 resolved，但是仍处于 'PENDING' 状态
</span><span class='line'>        // 这就是 ES6 规范中说的"一个 resolved 的 promise"，可能处在 pending, fulfilled 或者 rejected 状态
</span><span class='line'>        // http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects
</span><span class='line'>        return res.then(resolve, reject)
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>    this.$state = 'FULFILLED'
</span><span class='line'>    this.$internalValue = res
</span><span class='line'>    // If somebody called `.then()` while this promise was pending, need
</span><span class='line'>    // to call their `onFulfilled()` function
</span><span class='line'>    for (const { onFulfilled } of this.$chained) {
</span><span class='line'>        onFulfilled(res)
</span><span class='line'>    }
</span><span class='line'>
</span><span class='line'>    return res
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p><em>为了简单起见，上面的例子省略了一旦 promise 被锁定用以匹配另一个 promise 时，调用 resolve() 或者 reject() 是无效的关键细节。在上面的例子中，你可以 resolve() 一个 pending 的 promise ，然后抛出一个错误，然后 res.then(resolve, reject) 将会无效。这仅仅是一个例子，而不是 ES6 promise 规范的完全实现。</em></p>

<p>上面的代码说明了 resolved 的 promise 和 fulfilled 的 promise 之间的区别。这种区别是微妙的，并且与 promise 链式调用有关。resolved 不是一种真正的 promise 状态，但它是<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects">ES6规范中定义</a>的<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects">术语</a>。当对一个已经 resolved 的 promise 调用 <code>resolve()</code>，可能会发生以下两件事之一：</p>

<ul>
<li>在调用 <code>resolve(v)</code>时，如果 <code>v</code> 不是一个 promise ，那么 promise 立即成为 fulfilled。在这种简单的情况下，resolved 和 fulfilled 就是一样的。</li>
<li>在调用 <code>resolve(v)</code>时，如果 <code>v</code> 是另一个 promise，那么这个 promise 一直处于 pending 直到 <code>v</code> 调用 resolve 或者 reject。在这种情况下， promise 是 resolved 但处于 pending 状态。</li>
</ul>


<h2>与 Async/Await 一起使用</h2>

<p>关键字 <code>await</code> 会暂停执行一个 <code>async</code> 函数，直到等待的 promise 变成 settled 状态。现在我们已经有了一个简单的自制 promise 库，看看结合使用 async/await 中时会发生什么。向 <code>then()</code> 函数添加一个 <code>console.log()</code> 语句：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>then(onFulfilled, onRejected) {
</span><span class='line'>    console.log('Then', onFulfilled, onRejected, new Error().stack)
</span><span class='line'>    return new MyPromise((resolve, reject) =&gt; {
</span><span class='line'>        /* ... */
</span><span class='line'>    })
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>现在，我们来 <code>await</code> 一个 <code>MyPromise</code> 的实例，看看会发生什么。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>run().catch(error =&gt; console.error(error.stack))
</span><span class='line'>
</span><span class='line'>async function run() {
</span><span class='line'>    const start = Date.now()
</span><span class='line'>    await new MyPromise(resolve =&gt; setTimeout(() =&gt; resolve(), 100))
</span><span class='line'>    console.log('Elapsed time', Date.now() - start)
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>注意上面的 <code>.catch()</code> 调用。<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch"><code>catch()</code> 函数</a>是 ES6 promise 规范的核心部分。本文不会详细讲述它，因为 <code>.catch(f)</code> 相当于 <code>.then(null, f)</code>，没有什么特别的内容。</p>

<p>以下是输出内容，注意 await 隐式调用 <code>.then()</code> 中的 <code>onFulfilled()</code> 和 <code>onRejected()</code> 函数，这是 V8 底层的 C++ 代码（native code）。此外，<code>await</code> 会一直等待调用 <code>.then()</code> 直到下一个时序。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>Then function () { [native code] } function () { [native code] } Error
</span><span class='line'>    at MyPromise.then (/home/val/test/promise.js:63:50)
</span><span class='line'>    at process._tickCallback (internal/process/next_tick.js:188:7)
</span><span class='line'>    at Function.Module.runMain (module.js:686:11)
</span><span class='line'>    at startup (bootstrap_node.js:187:16)
</span><span class='line'>    at bootstrap_node.js:608:3
</span><span class='line'>Elapsed time 102</span></code></pre></td></tr></table></div></figure>


<h2>更多</h2>

<p><a href="http://thecodebarbarian.com/common-async-await-design-patterns-in-node.js.html">async/await</a> 是非常强大的特性，但掌握起来稍微有点困难，因为需要使用者了解 promise 的基本原则。 promise 有很多细节，例如捕获处理器函数中的同步错误，以及 promise 一旦解决就无法改变状态，这使得 async/await 成为可能。一旦对 promise 有了充分的理解，async/await 就会变得容易得多。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[魔幻语言 JavaScript 系列之类型转换、宽松相等以及原始值]]></title>
    <link href="https://blog.mirreal.net/blog/2018/04/12/looking-into-assembly-code-of-coercion/"/>
    <updated>2018-04-12T10:18:01+08:00</updated>
    <id>https://blog.mirreal.net/blog/2018/04/12/looking-into-assembly-code-of-coercion</id>
    <content type="html"><![CDATA[<blockquote><p>编译自：<a href="https://wanago.io/2018/04/02/1-2-3-9-looking-into-assembly-code-of-coercion/"><code>[1] + [2] – [3] === 9</code>!? Looking into assembly code of coercion.</a></p>

<p>全文从两个题目来介绍类型转换、宽松相等以及原始值的概念:</p>

<p>[1] + [2] – [3] === 9</p>

<p>如果让 a == true &amp;&amp; a == false 的值为 true</p>

<p>第二道题目是译者加的，因为这其实是个很好的例子，体现出 JavaScript 的魔幻之处</p></blockquote>

<p>变量值都具有类型，但仍然可以将一种类型的值赋值给另一种类型，如果是由开发者进行这些操作，就是<strong>类型转换</strong>（显式转换）。如果是发生在后台，比如在尝试对不一致的类型执行操作时，就是<strong>隐式转换</strong>（强制转换）。</p>

<h2>类型转换（Type casting）</h2>

<h3>基本包装类型（Primitive types wrappers）</h3>

<p>在 JavaScript 中除了 <code>null</code> 和 <code>undefined</code> 之外的所有基本类型都有一个对应的基本包装类型。通过使用其构造函数，可以将一个值的类型转换为另一种类型。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">String</span><span class="p">(</span><span class="mi">123</span><span class="p">);</span> <span class="c1">// &#39;123&#39;</span>
</span><span class='line'><span class="nb">Boolean</span><span class="p">(</span><span class="mi">123</span><span class="p">);</span> <span class="c1">// true</span>
</span><span class='line'><span class="nb">Number</span><span class="p">(</span><span class="s1">&#39;123&#39;</span><span class="p">);</span> <span class="c1">// 123</span>
</span><span class='line'><span class="nb">Number</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="c1">// 1</span>
</span></code></pre></td></tr></table></div></figure>


<blockquote><p>基本类型的包装器不会保存很长时间，一旦完成相应工作，就会消失</p></blockquote>

<p>需要注意的是，如果在构造函数前使用 <code>new</code> 关键字，结果就完全不同，比如下面的例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">bool</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Boolean</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span><span class='line'><span class="nx">bool</span><span class="p">.</span><span class="nx">propertyName</span> <span class="o">=</span> <span class="s1">&#39;propertyValue&#39;</span><span class="p">;</span>
</span><span class='line'><span class="nx">bool</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">();</span> <span class="c1">// false</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">bool</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">bool</span><span class="p">.</span><span class="nx">propertyName</span><span class="p">);</span> <span class="c1">// &#39;propertyValue&#39;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>由于 <code>bool</code> 在这里是一个新的对象，已经不再是基本类型值，它的计算结果为 <code>true</code>。</p>

<p>上述例子，因为在 if 语句中，括号间的表达式将会装换成布尔值，比如</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="k">if</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>其实，上面这段代码跟下面一样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="k">if</span> <span class="p">(</span> <span class="nb">Boolean</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h3>parseFloat</h3>

<p><code>parseFloat</code> 函数的功能跟 <code>Number</code> 构造函数类似，但对于传参并没有那么严格。当它遇到不能转换成数字的字符，将返回一个到该点的值并忽略其余字符。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">Number</span><span class="p">(</span><span class="s1">&#39;123a45&#39;</span><span class="p">);</span> <span class="c1">// NaN</span>
</span><span class='line'><span class="nb">parseFloat</span><span class="p">(</span><span class="s1">&#39;123a45&#39;</span><span class="p">);</span> <span class="c1">// 123</span>
</span></code></pre></td></tr></table></div></figure>


<h3>parseInt</h3>

<p><code>parseInt</code> 函数在解析时将会对数字进行向下取整，并且可以使用不同的进制。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">parseInt</span><span class="p">(</span><span class="s1">&#39;1111&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span> <span class="c1">// 15</span>
</span><span class='line'><span class="nb">parseInt</span><span class="p">(</span><span class="s1">&#39;0xF&#39;</span><span class="p">);</span> <span class="c1">// 15</span>
</span><span class='line'>
</span><span class='line'><span class="nb">parseFloat</span><span class="p">(</span><span class="s1">&#39;0xF&#39;</span><span class="p">);</span> <span class="c1">// 0</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>parseInt</code> 函数可以猜测进制，或着你可以显式地通过第二个参数传入进制，参考 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt">MDN web docs</a>。</p>

<p>而且不能正常处理大数，所以不应该成为 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor"><strong>Math.floor</strong></a> 的替代品，是的，<code>Math.floor</code> 也会进行类型转换：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">parseInt</span><span class="p">(</span><span class="s1">&#39;1.261e7&#39;</span><span class="p">);</span> <span class="c1">// 1</span>
</span><span class='line'><span class="nb">Number</span><span class="p">(</span><span class="s1">&#39;1.261e7&#39;</span><span class="p">);</span> <span class="c1">// 12610000</span>
</span><span class='line'><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="s1">&#39;1.261e7&#39;</span><span class="p">)</span> <span class="c1">// 12610000</span>
</span><span class='line'>
</span><span class='line'><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="c1">// 1</span>
</span></code></pre></td></tr></table></div></figure>


<h3>toString</h3>

<p>可以使用 <strong>toString</strong> 函数将值转换为字符串，但是在不同原型之间的实现有所不同。</p>

<p><strong>String.prototype.toString</strong></p>

<p>返回字符串的值</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">dogName</span> <span class="o">=</span> <span class="s1">&#39;Fluffy&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">dogName</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="c1">// &#39;Fluffy&#39;</span>
</span><span class='line'><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toString</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="s1">&#39;Fluffy&#39;</span><span class="p">)</span> <span class="c1">// &#39;Fluffy&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toString</span><span class="p">.</span><span class="nx">call</span><span class="p">({})</span> <span class="c1">// Uncaught TypeError: String.prototype.toString requires that &#39;this&#39; be a String</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>Number.prototype.toString</strong></p>

<p>返回将数字的字符串表示形式，可以指定进制作为第一个参数传入</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="p">(</span><span class="mi">15</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span> <span class="c1">// &quot;15&quot;</span>
</span><span class='line'><span class="p">(</span><span class="mi">15</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span> <span class="c1">// &quot;1111&quot;</span>
</span><span class='line'><span class="p">(</span><span class="o">-</span><span class="mi">15</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span> <span class="c1">// &quot;-1111&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>Symbol .prototype.toString</strong></p>

<p>返回  <code>Symbol（${description}）</code></p>

<p><strong>Boolean.prototype.toString</strong></p>

<p>返回 <code>“true”</code> 或 <code>“false”</code></p>

<p><strong>Object.prototype.toString</strong></p>

<p>返回一个字符串 <code>[ object $ { tag } ]</code> ，其中 tag 可以是内置类型比如 “Array”，“String”，“Object”，“Date”，也可以是自定义 tag。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">dogName</span> <span class="o">=</span> <span class="s1">&#39;Fluffy&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">dogName</span><span class="p">.</span><span class="nx">toString</span><span class="p">();</span> <span class="c1">// &#39;Fluffy&#39; (String.prototype.toString called here)</span>
</span><span class='line'><span class="nb">Object</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toString</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">dogName</span><span class="p">);</span> <span class="c1">// &#39;[object String]&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>随着 ES6 的推出，还可以使用 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol"><strong>Symbol</strong></a> 进行自定义 tag。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">dog</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Fluffy&#39;</span> <span class="p">}</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">dog</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="p">)</span> <span class="c1">// &#39;[object Object]&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">dog</span><span class="p">[</span><span class="nx">Symbol</span><span class="p">.</span><span class="nx">toStringTag</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;Dog&#39;</span><span class="p">;</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="nx">dog</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="p">)</span> <span class="c1">// &#39;[object Dog]&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>或者</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">Dog</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="nx">Dog</span><span class="p">.</span><span class="nx">prototype</span><span class="p">[</span><span class="nx">Symbol</span><span class="p">.</span><span class="nx">toStringTag</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;Dog&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">dog</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Dog</span><span class="p">(</span><span class="s1">&#39;Fluffy&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">dog</span><span class="p">.</span><span class="nx">toString</span><span class="p">();</span> <span class="c1">// &#39;[object Dog]&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>还可以结合使用 ES6 class 和 getter：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">class</span> <span class="nx">Dog</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>  <span class="nx">get</span> <span class="p">[</span><span class="nx">Symbol</span><span class="p">.</span><span class="nx">toStringTag</span><span class="p">]()</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="s1">&#39;Dog&#39;</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">dog</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Dog</span><span class="p">(</span><span class="s1">&#39;Fluffy&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">dog</span><span class="p">.</span><span class="nx">toString</span><span class="p">();</span> <span class="c1">// &#39;[object Dog]&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>Array.prototype.toString</strong></p>

<p>在每个元素上调用 <code>toString</code>，并返回一个字符串，并且以逗号分隔。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>  <span class="p">{},</span>
</span><span class='line'>  <span class="mi">2</span><span class="p">,</span>
</span><span class='line'>  <span class="mi">3</span>
</span><span class='line'><span class="p">]</span>
</span><span class='line'>
</span><span class='line'><span class="nx">arr</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="c1">// &quot;[object Object],2,3&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>强制转换</h2>

<p>如果了解类型转换的工作原理，那么理解强制转换就会容易很多。</p>

<h3>数学运算符</h3>

<p><strong>加号运算符</strong></p>

<p>在作为二元运算符的 <code>+</code> 如果两边的表达式存在字符串，最后将会返回一个字符串。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="s1">&#39;2&#39;</span> <span class="o">+</span> <span class="mi">2</span> <span class="c1">// &#39;22&#39;</span>
</span><span class='line'><span class="mi">15</span> <span class="o">+</span> <span class="s1">&#39;&#39;</span> <span class="c1">// &#39;15&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以使用一元运算符将其转换为数字：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="o">+</span><span class="s1">&#39;12&#39;</span> <span class="c1">// 12</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>其他数学运算符</strong></p>

<p>其他数学运算符（如 <code>-</code>或 <code>/</code>）将始终转换为数字。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="s1">&#39;04-02-2018&#39;</span><span class="p">)</span> <span class="o">-</span> <span class="s1">&#39;1&#39;</span> <span class="c1">// 1522619999999</span>
</span><span class='line'><span class="s1">&#39;12&#39;</span> <span class="o">/</span> <span class="s1">&#39;6&#39;</span> <span class="c1">// 2</span>
</span><span class='line'><span class="o">-</span><span class="s1">&#39;1&#39;</span> <span class="c1">// -1</span>
</span></code></pre></td></tr></table></div></figure>


<p>上述例子中，Date 类型将转换为数字，即 <a href="https://en.wikipedia.org/wiki/Unix_time">Unix 时间戳</a>。</p>

<h3>逻辑非</h3>

<p>如果原始值是 <em>假</em>，则使用逻辑非将输出 <em>真</em>，如果 <em>真</em>，则输出为 <em>假</em>。 如果使用两次，可用于将该值转换为相应的布尔值。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="o">!</span><span class="mi">1</span> <span class="c1">// false</span>
</span><span class='line'><span class="o">!!</span><span class="p">({})</span> <span class="c1">// true</span>
</span></code></pre></td></tr></table></div></figure>


<h3>位或</h3>

<p>值得一提的是，即使 ToInt32 实际上是一个抽象操作（仅限内部，不可调用），将一个值转换为一个<a href="https://en.wikipedia.org/wiki/32-bit">有符号的 32 位整数</a>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="mi">0</span> <span class="o">|</span> <span class="kc">true</span>          <span class="c1">// 1</span>
</span><span class='line'><span class="mi">0</span> <span class="o">|</span> <span class="s1">&#39;123&#39;</span>         <span class="c1">// 123</span>
</span><span class='line'><span class="mi">0</span> <span class="o">|</span> <span class="s1">&#39;2147483647&#39;</span>  <span class="c1">// 2147483647</span>
</span><span class='line'><span class="mi">0</span> <span class="o">|</span> <span class="s1">&#39;2147483648&#39;</span>  <span class="c1">// -2147483648 (too big)</span>
</span><span class='line'><span class="mi">0</span> <span class="o">|</span> <span class="s1">&#39;-2147483648&#39;</span> <span class="c1">// -2147483648</span>
</span><span class='line'><span class="mi">0</span> <span class="o">|</span> <span class="s1">&#39;-2147483649&#39;</span> <span class="c1">// 2147483647 (too small)</span>
</span><span class='line'><span class="mi">0</span> <span class="o">|</span> <span class="kc">Infinity</span>      <span class="c1">// 0</span>
</span></code></pre></td></tr></table></div></figure>


<p>当其中一个操作数为 0 时执行按位或操作将不改变另一个操作数的值。</p>

<h3>其他情况下的强制转换</h3>

<p>在编码时，可能会遇到更多强制转换的情况，比如这个例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">foo</span> <span class="o">=</span> <span class="p">{};</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">bar</span> <span class="o">=</span> <span class="p">{};</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">{};</span>
</span><span class='line'>
</span><span class='line'><span class="nx">x</span><span class="p">[</span><span class="nx">foo</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;foo&#39;</span><span class="p">;</span>
</span><span class='line'><span class="nx">x</span><span class="p">[</span><span class="nx">bar</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;bar&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">x</span><span class="p">[</span><span class="nx">foo</span><span class="p">]);</span> <span class="c1">// &quot;bar&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>发生这种情况是因为 <code>foo</code> 和 <code>bar</code> 在转换为字符串的结果均为 <code>“[object Object]”</code>。就像这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">x</span><span class="p">[</span><span class="nx">bar</span><span class="p">.</span><span class="nx">toString</span><span class="p">()]</span> <span class="o">=</span> <span class="s1">&#39;bar&#39;</span><span class="p">;</span>
</span><span class='line'><span class="nx">x</span><span class="p">[</span><span class="s2">&quot;[object Object]&quot;</span><span class="p">];</span> <span class="c1">// &quot;bar&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">模板字符串</a>的时候也会发生强制转换，在下面例子中重写 <code>toString</code> 函数：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">Dog</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="nx">Dog</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">toString</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">dog</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Dog</span><span class="p">(</span><span class="s1">&#39;Fluffy&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="err">`</span><span class="nx">$</span><span class="p">{</span><span class="nx">dog</span><span class="p">}</span> <span class="nx">is</span> <span class="nx">a</span> <span class="nx">good</span> <span class="nx">dog</span><span class="o">!</span><span class="err">`</span><span class="p">);</span> <span class="c1">// &quot;Fluffy is a good dog!&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>正因为如此，<strong>宽松相等</strong>（==）被认为是一种不好的做法，如果两边类型不一致，就会试图进行强制隐式转换。</p>

<p>看下面这个有趣的例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">foo</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">String</span><span class="p">(</span><span class="s1">&#39;foo&#39;</span><span class="p">);</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">foo2</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">String</span><span class="p">(</span><span class="s1">&#39;foo&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="nx">foo</span> <span class="o">===</span> <span class="nx">foo2</span> <span class="c1">// false</span>
</span><span class='line'><span class="nx">foo</span> <span class="o">&gt;=</span> <span class="nx">foo2</span> <span class="c1">// true</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这里我们使用了 <code>new</code> 关键字，所以 <code>foo</code> 和 <code>foo2</code> 都是字符串包装类型，原始值都是 <code>foo</code> 。但是，它们现在引用了两个不同的对象，所以 <code>foo === foo2</code> 将返回 <code>false</code>。这里的关系运算符 <code>&gt;=</code> 会在两个操作数上调用 <code>valueOf</code> 函数，因此比较的是它们的原始值，<code>'foo' &gt; = 'foo'</code> 的结果为 <code>true</code>。</p>

<h2>[1] + [2] - [3] === 9</h2>

<p>希望这些知识都能帮助揭开这个题目的神秘面纱</p>

<ol>
<li><code>[1] + [2]</code> 将调用 <code>Array.prototype.toString</code> 转换为字符串，然后进行字符串拼接。结果将是 <code>“12”</code>

<ul>
<li><code>[1,2] + [3,4]</code> 的值讲师 <code>“1,23,4”</code></li>
</ul>
</li>
<li><code>12 - [3]</code>，减号运算符会将值转换为 Number 类型，所以等于 <code>12-3</code>，结果为 <code>9</code>

<ul>
<li>12 - [3,4] 的值是 <code>NaN</code>，因为<code>"3,4"</code> 不能被转换为 Number</li>
</ul>
</li>
</ol>


<h2>总结</h2>

<p>尽管很多人会建议尽量避免强制隐式转换，但了解它的工作原理非常重要，在调试代码和避免错误方面大有帮助。</p>

<p>【译文完】</p>

<h2>再谈点，关于宽松相等和原始值</h2>

<p>这里看另一道题目，在 JavaScript 环境下，能否让表达式 <code>a == true &amp;&amp; a == false</code> 为 <code>true</code>。</p>

<p>就像下面这样，在控制台打印出 <code>’yeah'</code>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// code here</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">a</span> <span class="o">==</span> <span class="kc">true</span> <span class="o">&amp;&amp;</span> <span class="nx">a</span> <span class="o">==</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;yeah&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>关于宽松相等（==），先看看 ECMA 5.1 的规范，包含 <code>toPrimitive</code>:</p>

<ul>
<li><a href="http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3">11.9.3</a> The Abstract Equality Comparison Algorithm</li>
<li><a href="http://www.ecma-international.org/ecma-262/5.1/#sec-9.1">9.1</a> ToPrimitive</li>
</ul>


<h3>稍作总结</h3>

<p>规范很长很详细，简单总结就是，对于下述表达式：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">x</span> <span class="o">==</span> <span class="nx">y</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>类型相同，判断的就是 x === y</li>
<li>类型不同

<ul>
<li>如果 x，y 其中一个是布尔值，将这个布尔值进行 ToNumber 操作</li>
<li>如果 x，y 其中一个是字符串，将这个字符串进行 ToNumber 操作</li>
<li>若果 x，y 一方为对象，将这个对象进行 ToPrimitive 操作</li>
</ul>
</li>
</ul>


<p>至于 <code>ToPrimitive</code>，即求原始值，可以简单理解为进行 <code>valueOf()</code> 和 <code>toString()</code> 操作。</p>

<p>稍后我们再详细剖析，接下来先看一个问题。</p>

<h3>Question：是否存在这样一个变量，满足 x == !x</h3>

<p>就像这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// code here</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">x</span> <span class="o">==</span> <span class="o">!</span><span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;yeah&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>可能很多人会想到下面这个，毕竟我们也曾热衷于各种奇技淫巧：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="p">[]</span> <span class="o">==</span> <span class="o">!</span><span class="p">[]</span> <span class="c1">// true</span>
</span></code></pre></td></tr></table></div></figure>


<p>但答案绝不仅仅局限于此，比如：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">x</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Boolean</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">x</span> <span class="o">==</span> <span class="o">!</span><span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;yeah&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="c1">// x.valueOf() -&gt; false</span>
</span><span class='line'><span class="c1">// x is a object, so: !x -&gt; false</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">y</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Number</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span><span class='line'><span class="nx">y</span> <span class="o">==</span> <span class="o">!</span><span class="nx">y</span> <span class="c1">// true</span>
</span><span class='line'><span class="c1">// y.valueOf() -&gt; 0</span>
</span><span class='line'><span class="c1">// !y -&gt; false</span>
</span><span class='line'><span class="c1">// 0 === Number(false) // true</span>
</span><span class='line'><span class="c1">// 0 == false // true</span>
</span></code></pre></td></tr></table></div></figure>


<p>理解这个问题，那下面的这些例子都不是问题了：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="p">[]</span> <span class="o">==</span> <span class="o">!</span><span class="p">[]</span>
</span><span class='line'><span class="p">[]</span> <span class="o">==</span> <span class="p">{}</span>
</span><span class='line'><span class="p">[]</span> <span class="o">==</span> <span class="o">!</span><span class="p">{}</span>
</span><span class='line'><span class="p">{}</span> <span class="o">==</span> <span class="o">!</span><span class="p">[]</span>
</span><span class='line'><span class="p">{}</span> <span class="o">==</span> <span class="o">!</span><span class="p">{}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在来看看什么是 <code>ToPrimitive</code></p>

<h3>ToPrimitive</h3>

<p>看规范：<a href="http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8">8.12.8</a> <code>[[DefaultValue]] (hint)</code></p>

<p>如果是 <code>Date</code> 求原始值，则 hint 是 <code>String</code>，其他均为 <code>Number</code>，即先调用 <code>valueOf()</code> 再调用 <code>toString()</code>。</p>

<p>如果 hint 为 <code>Number</code>，具体过程如下：</p>

<ol>
<li>调用对象的 <code>valueOf()</code> 方法，如果值是原值则返回</li>
<li>否则，调用对象的 <code>toString()</code> 方法，如果值是原值则返回</li>
<li>否则，抛出 TypeError 错误</li>
</ol>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// valueOf 和 toString 的调用顺序</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">valueOf</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;valueof&#39;</span><span class="p">)</span>
</span><span class='line'>        <span class="k">return</span> <span class="p">[]</span>
</span><span class='line'>    <span class="p">},</span>
</span><span class='line'>    <span class="nx">toString</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;toString&#39;</span><span class="p">)</span>
</span><span class='line'>        <span class="k">return</span> <span class="p">{}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">a</span> <span class="o">==</span> <span class="mi">0</span>
</span><span class='line'><span class="c1">// valueof</span>
</span><span class='line'><span class="c1">// toString</span>
</span><span class='line'><span class="c1">// Uncaught TypeError: Cannot convert object to primitive value</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="c1">// Date 类型先 toString，后 valueOf</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">t</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="s1">&#39;2018/04/01&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">t</span><span class="p">.</span><span class="nx">valueOf</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;valueof&#39;</span><span class="p">)</span>
</span><span class='line'>    <span class="k">return</span> <span class="p">[]</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="nx">t</span><span class="p">.</span><span class="nx">toString</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;toString&#39;</span><span class="p">)</span>
</span><span class='line'>    <span class="k">return</span> <span class="p">{}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="nx">t</span> <span class="o">==</span> <span class="mi">0</span>
</span><span class='line'><span class="c1">// toString</span>
</span><span class='line'><span class="c1">// valueof</span>
</span><span class='line'><span class="c1">// Uncaught TypeError: Cannot convert object to primitive value</span>
</span></code></pre></td></tr></table></div></figure>


<p>到目前为止，上面的都是 ES5 的规范，那么在 ES6 中，有什么变化呢</p>

<h3>ES6 中 ToPrimitive</h3>

<p><a href="http://www.ecma-international.org/ecma-262/6.0/index.html#sec-toprimitive">7.1.1</a>ToPrimitive ( input [, PreferredType] )</p>

<p>在 ES6 中吗，是可以自定义 <code>@@toPrimitive</code> 方法的，这是 Well-Known Symbols(<a href="http://www.ecma-international.org/ecma-262/6.0/index.html#sec-well-known-symbols">§6.1.5.1</a>)中的一个。JavaScript 内建了一些在 ECMAScript 5 之前没有暴露给开发者的 symbol，它们代表了内部语言行为。</p>

<p>来自 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive">MDN</a> 的例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// 没有 Symbol.toPrimitive 属性的对象</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">obj1</span> <span class="o">=</span> <span class="p">{};</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="o">+</span><span class="nx">obj1</span><span class="p">);</span> <span class="c1">// NaN</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="err">`</span><span class="nx">$</span><span class="p">{</span><span class="nx">obj1</span><span class="p">}</span><span class="err">`</span><span class="p">);</span> <span class="c1">// &#39;[object Object]&#39;</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">obj1</span> <span class="o">+</span> <span class="s1">&#39;&#39;</span><span class="p">);</span> <span class="c1">// &#39;[object Object]&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 拥有 Symbol.toPrimitive 属性的对象</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">obj2</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>    <span class="p">[</span><span class="nx">Symbol</span><span class="p">.</span><span class="nx">toPrimitive</span><span class="p">](</span><span class="nx">hint</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">hint</span> <span class="o">==</span> <span class="s1">&#39;number&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="k">return</span> <span class="mi">10</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">hint</span> <span class="o">==</span> <span class="s1">&#39;string&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="k">return</span> <span class="s1">&#39;hello&#39;</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="o">+</span><span class="nx">obj2</span><span class="p">);</span> <span class="c1">// 10 -- hint is &#39;number&#39;</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="err">`</span><span class="nx">$</span><span class="p">{</span><span class="nx">obj2</span><span class="p">}</span><span class="err">`</span><span class="p">);</span> <span class="c1">// &#39;hello&#39; -- hint is &#39;string&#39;</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">obj2</span> <span class="o">+</span> <span class="s1">&#39;&#39;</span><span class="p">);</span> <span class="c1">// &#39;true&#39; -- hint is &#39;default&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>有了上述铺垫，答案就呼之欲出了</p>

<h3><code>a == true &amp;&amp; a == false</code> 为 <code>true</code> 的答案</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">flag</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">toString</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">flag</span> <span class="o">=</span> <span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">flag</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>或者使用 <code>valueOf()</code>：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">flag</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">valueOf</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">flag</span> <span class="o">=</span> <span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">flag</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>或者是直接改变 ToPrimitive 行为：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// 其实只需设置 default 即可</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">flag</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>    <span class="p">[</span><span class="nx">Symbol</span><span class="p">.</span><span class="nx">toPrimitive</span><span class="p">](</span><span class="nx">hint</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">hint</span> <span class="o">===</span> <span class="s1">&#39;number&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="k">return</span> <span class="mi">10</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">hint</span> <span class="o">===</span> <span class="s1">&#39;string&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="k">return</span> <span class="s1">&#39;hello&#39;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">flag</span> <span class="o">=</span> <span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">flag</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h3>如果是严格相等呢</h3>

<p>这个问题在严格相等的情况下，也是能够成立的，这又是另外的知识点了，使用 <code>defineProperty</code> 就能实现：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">let</span> <span class="nx">flag</span> <span class="o">=</span> <span class="kc">false</span>
</span><span class='line'><span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nb">window</span><span class="p">,</span> <span class="s1">&#39;a&#39;</span><span class="p">,</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">get</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="p">(</span><span class="nx">flag</span> <span class="o">=</span> <span class="o">!</span><span class="nx">flag</span><span class="p">)</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">a</span> <span class="o">===</span> <span class="kc">true</span> <span class="o">&amp;&amp;</span> <span class="nx">a</span> <span class="o">===</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;yeah&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h3>阅读更多</h3>

<ul>
<li><a href="https://stackoverflow.com/questions/48270127/can-a-1-a-2-a-3-ever-evaluate-to-true">Can (a== 1 &amp;&amp; a ==2 &amp;&amp; a==3) ever evaluate to true?</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[React 整洁代码最佳实践]]></title>
    <link href="https://blog.mirreal.net/blog/2017/12/01/clean-code-vs-dirty-code-react-best-practices/"/>
    <updated>2017-12-01T08:53:45+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/12/01/clean-code-vs-dirty-code-react-best-practices</id>
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="http://americanexpress.io/clean-code-dirty-code/">Clean Code vs. Dirty Code: React Best Practices</a></p>

<p>作者：Donavon West</p></blockquote>

<p>本文主要介绍了适用于现代 React 软件开发的整洁代码实践，顺便谈谈 ES6/ES2015 带来的一些好用的“语法糖”。</p>

<h2>什么是整洁代码，为什么要在乎？</h2>

<p>整洁代码代表的是一种一致的编码风格，目的是让代码更易于编写，阅读和维护。通常情况下，开发者在解决问题的时候，一旦问题解决就发起一个 Pull Request（译注：合并请求，在 Gitlab 上叫 Merge Request）。但我认为，这时候工作并没有真正完成，我们不能仅仅满足于代码可以工作。</p>

<p>这时候其实就是整理代码的最好时机，可以通过删除死代码（僵尸代码），重构以及删除注释掉的代码，来保持代码的可维护性。不妨问问自己，“从现在开始再过六个月，其他人还能理解这些代码吗？”简而言之，对于自己编写的代码，你应该保证能很自豪地拿给别人看。</p>

<p>至于为什么要在乎这点？因为我们常说一个优秀的开发者大都比较”懒“。在遇到需要重复做某些事情的情况下，他们会去找到一个自动化（或更好的）解决方案来完成这些任务。</p>

<h3>整洁代码能够通过“味道测试”</h3>

<p>整洁代码应该可以通过“味道测试”。什么意思呢？我们在看代码的时候，包括我们自己写的或或是别人的，会说：“这里不太对劲。”如果感觉不对，那可能就真的是有问题的。如果你觉得你正在试图把一个方形钉子装进一个圆形的洞里，那么就暂停一下，然后休息一下。多次尝试之后，你会找到一个更好的解决方案。</p>

<h3>整洁代码是符合 DRY 原则的</h3>

<p>DRY 是一个缩略词，意思是“不要重复自己”（Don’t Repeat Yourself）。如果发现多个地方在做同样的事情，那么这时候就应该合并重复代码。如果在代码中看到了模式，那么表明需要实行 DRY。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">MyComponent</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">OtherComponent</span> <span class="nx">type</span><span class="o">=</span><span class="s2">&quot;a&quot;</span> <span class="nx">className</span><span class="o">=</span><span class="s2">&quot;colorful&quot;</span> <span class="nx">foo</span><span class="o">=</span><span class="p">{</span><span class="mi">123</span><span class="p">}</span> <span class="nx">bar</span><span class="o">=</span><span class="p">{</span><span class="mi">456</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">OtherComponent</span> <span class="nx">type</span><span class="o">=</span><span class="s2">&quot;b&quot;</span> <span class="nx">className</span><span class="o">=</span><span class="s2">&quot;colorful&quot;</span> <span class="nx">foo</span><span class="o">=</span><span class="p">{</span><span class="mi">123</span><span class="p">}</span> <span class="nx">bar</span><span class="o">=</span><span class="p">{</span><span class="mi">456</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">MyOtherComponent</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">type</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">OtherComponent</span> <span class="nx">type</span><span class="o">=</span><span class="p">{</span><span class="nx">type</span><span class="p">}</span> <span class="nx">className</span><span class="o">=</span><span class="s2">&quot;colorful&quot;</span> <span class="nx">foo</span><span class="o">=</span><span class="p">{</span><span class="mi">123</span><span class="p">}</span> <span class="nx">bar</span><span class="o">=</span><span class="p">{</span><span class="mi">456</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'><span class="p">);</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">MyComponent</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">MyOtherComponent</span> <span class="nx">type</span><span class="o">=</span><span class="s2">&quot;a&quot;</span> <span class="o">/&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">MyOtherComponent</span> <span class="nx">type</span><span class="o">=</span><span class="s2">&quot;b&quot;</span> <span class="o">/&gt;</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>有时候，比如在上面的例子中，实行 DRY 原则反而可能会增加代码量。但是，DRY 通常也能够提高代码的可维护性。</p>

<p>注意，很容易陷入过分使用 DRY 原则的陷阱，应该学会适可而止。</p>

<h3>整洁代码是可预测和可测试的</h3>

<p>编写单元测试不仅仅只是一个好想法，而且应该是强制性的。不然，怎么能确保新功能不会在其他地方引起 Bug 呢？</p>

<p>许多 React 开发人员选择 <a href="https://facebook.github.io/jest/">Jest</a> 作为一个零配置测试运行器，然后生成代码覆盖率报告。如果对测试前后对比可视化感兴趣，请查看美国运通的 <a href="https://github.com/americanexpress/jest-image-snapshot">Jest Image snanshot</a>。</p>

<h3>整洁代码是自注释的</h3>

<p>以前发生过这种情况吗？你写了一些代码，并且包含详细的注释。后来你发现一个 bug，于是回去修改代码。但是，你有没有改变注释来体现新的逻辑？也许会，也许不会。下一个看你代码的人可能因为注意到这些注释而掉进一个陷阱。</p>

<p>注释只是为了解释复杂的想法，也就是说，不要对显而易见的代码进行注释。同时，更少的注释也减少了视觉上的干扰。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">fetchUser</span> <span class="o">=</span> <span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="nx">fetch</span><span class="p">(</span><span class="nx">buildUri</span><span class="err">`</span><span class="o">/</span><span class="nx">users</span><span class="o">/</span><span class="nx">$</span><span class="p">{</span><span class="nx">id</span><span class="p">}</span><span class="err">`</span><span class="p">)</span> <span class="c1">// Get User DTO record from REST API</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">convertFormat</span><span class="p">)</span> <span class="c1">// Convert to snakeCase</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">validateUser</span><span class="p">)</span> <span class="c1">// Make sure the the user is valid</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>在整洁代码的版本中，我们对一些函数进行重命名，以便更好地描述它们的功能，从而消除注释的必要性，减少视觉干扰。并且避免后续因代码与注释不匹配导致的混淆。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">fetchUser</span> <span class="o">=</span> <span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="nx">fetch</span><span class="p">(</span><span class="nx">buildUri</span><span class="err">`</span><span class="o">/</span><span class="nx">users</span><span class="o">/</span><span class="nx">$</span><span class="p">{</span><span class="nx">id</span><span class="p">}</span><span class="err">`</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">snakeToCamelCase</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">validateUser</span><span class="p">)</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<h3>命名</h3>

<p>在我之前的文章 <a href="http://americanexpress.io/faccs-are-an-antipattern">将函数作为子组件是一种反模式</a>，强调了命名的重要性。每个开发者都应该认真考虑变量名，函数名，甚至是文件名。</p>

<p>这里列举一下命名原则：</p>

<ul>
<li><p>布尔变量或返回布尔值的函数应该以“is”，“has”或“should”开头。</p>

<pre><code class="js">// Dirty
const done = current &gt;= goal;
</code></pre>

<pre><code class="js">// Clean
const isComplete = current &gt;= goal;
</code></pre></li>
<li><p>函数命名应该体现做了什么，而不是是怎样做的。换言之，不要在命名中体现出实现细节。假如有天出现变化，就不需要因此而重构引用该函数的代码。比如，今天可能会从 REST API 加载配置，但是可能明天就会将其直接写入到 JavaScript 中。</p>

<pre><code class="js">// Dirty
const loadConfigFromServer = () =&gt; {
  ...
};
</code></pre>

<pre><code class="js">// Clean
const loadConfig = () =&gt; {
  ...
};
</code></pre></li>
</ul>


<h3>整洁代码遵循成熟的设计模式和最佳实践</h3>

<p>计算机已经存在很长一段时间了。多年以来，程序员通过解决某些特定问题，发现了一些固有套路，被称为设计模式。换言之，有些算法已经被证明是可以工作的，所以应该站在前人的肩膀上，避免犯同样的错误。</p>

<p>那么，什么是最佳实践，与设计模式类似，但是适用范围更广，不仅仅针对编码算法。比如，“应该对代码进行静态检查”或者“当编写一个库时，应该将 React 作为 <code>peerDependency</code>”，这些都可以称为最佳实践。</p>

<p>构建 React 应用程序时，应该遵循以下最佳实践：</p>

<ul>
<li>使用小函数，每个函数具备单一功能，即所谓的单一职责原则（Single responsibility principle）。确保每个函数都能完成一项工作，并做得很好。这样就能将复杂的组件分解成许多较小的组件。同时，将具备更好的可测试性。</li>
<li>小心抽象泄露（leaky abstractions）。换言之，不要强迫消费方去了解内部代码实现细节。</li>
<li>遵循严格的代码检查规则。这将有助于编写整洁，一致的代码。</li>
</ul>


<h3>整洁代码不需要花长时间来编写</h3>

<p>总会听到这样的说法：编写整洁代码会降低生产力。简直是在胡说八道。是的，可能刚开始需要放慢速度，但最终会随着编写更少的代码而节奏加快。</p>

<p>而且，不要小看代码评审导致的重写重构，以及修复问题花费的时间。如果把代码分解成小的模块，每个模块都是单一职责，那么很可能以后再也不用去碰大多数模块了。时间就省下来了，也就是说 “write it and forget it”。</p>

<h2>槽糕代码与整洁代码的实例</h2>

<h3>使用 DRY 原则</h3>

<p>看看下面的代码示例。如上所述，从你的显示器退后一步，发现什么模式了吗？注意 <code>Thingie</code> 组件与 <code>ThingieWithTitle</code> 组件除了 <code>Title</code> 组件几乎完全相同，这是实行 DRY 原则的最佳情形。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">import</span> <span class="nx">Title</span> <span class="nx">from</span> <span class="s1">&#39;./Title&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">export</span> <span class="kr">const</span> <span class="nx">Thingie</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">description</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;thingie&quot;</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;description-wrapper&quot;</span><span class="o">&gt;</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="nx">Description</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">description</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="kr">export</span> <span class="kr">const</span> <span class="nx">ThingieWithTitle</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">description</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">Title</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">title</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;description-wrapper&quot;</span><span class="o">&gt;</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="nx">Description</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">description</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这里，我们将 <code>children</code> 传递给 <code>Thingie</code>。然后创建 <code>ThingieWithTitle</code>，这个组件包含 <code>Thingie</code>，并将 <code>Title</code> 作为其子组件传给 <code>Thingie</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">import</span> <span class="nx">Title</span> <span class="nx">from</span> <span class="s1">&#39;./Title&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">export</span> <span class="kr">const</span> <span class="nx">Thingie</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">description</span><span class="p">,</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;thingie&quot;</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="p">{</span><span class="nx">children</span><span class="p">}</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">&quot;description-wrapper&quot;</span><span class="o">&gt;</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="nx">Description</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">description</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="kr">export</span> <span class="kr">const</span> <span class="nx">ThingieWithTitle</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">title</span><span class="p">,</span> <span class="p">...</span><span class="nx">others</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">Thingie</span> <span class="p">{...</span><span class="nx">others</span><span class="p">}</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">Title</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">title</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/Thingie&gt;</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<h3>默认值</h3>

<p>看看下面的代码。使用逻辑或将 <code>className</code> 的默认值设置成 “icon-large”，看起来像是上个世纪的人才会写的代码。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">Icon</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">className</span><span class="p">,</span> <span class="nx">onClick</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">additionalClasses</span> <span class="o">=</span> <span class="nx">className</span> <span class="o">||</span> <span class="s1">&#39;icon-large&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">return</span> <span class="p">(</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">span</span>
</span><span class='line'>      <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="err">`</span><span class="nx">icon</span><span class="o">-</span><span class="nx">hover</span> <span class="nx">$</span><span class="p">{</span><span class="nx">additionalClasses</span><span class="p">}</span><span class="err">`</span><span class="p">}</span>
</span><span class='line'>      <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">onClick</span><span class="p">}</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="err">/span&gt;</span>
</span><span class='line'>  <span class="p">);</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>这里我们使用 ES6 的默认语法来替换 <code>undefined</code> 时的值，而且还能使用 ES6 的箭头函数表达式写成单一语句形式，从而去除对 <code>return</code> 的依赖。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">Icon</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">className</span> <span class="o">=</span> <span class="s1">&#39;icon-large&#39;</span><span class="p">,</span> <span class="nx">onClick</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">span</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="err">`</span><span class="nx">icon</span><span class="o">-</span><span class="nx">hover</span> <span class="nx">$</span><span class="p">{</span><span class="nx">className</span><span class="p">}</span><span class="err">`</span><span class="p">}</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">onClick</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>在下面这个更整洁的版本中，使用 React 中的 API 来设置默认值。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Cleaner</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">Icon</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">className</span><span class="p">,</span> <span class="nx">onClick</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">span</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="err">`</span><span class="nx">icon</span><span class="o">-</span><span class="nx">hover</span> <span class="nx">$</span><span class="p">{</span><span class="nx">className</span><span class="p">}</span><span class="err">`</span><span class="p">}</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">onClick</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="nx">Icon</span><span class="p">.</span><span class="nx">defaultProps</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">className</span><span class="o">:</span> <span class="s1">&#39;icon-large&#39;</span><span class="p">,</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>为什么这样显得更加整洁？而且它真的会更好吗？三个版本不是都在做同样的事情吗？某种意义上来说，是对的。让 React 设置 prop 默认值的好处是，可以产生更高效的代码，而且在基于 <code>Class</code> 的生命周期组件中允许通过 <code>propTypes</code> 检查默认值。还有一个优点是：将默认逻辑从组件本身抽离出来。</p>

<p>例如，你可以执行以下操作，将所有默认属性放到一个地方。当然，并不是建议你这样做，只是说具有这样的灵活性。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">import</span> <span class="nx">defaultProps</span> <span class="nx">from</span> <span class="s1">&#39;./defaultProps&#39;</span><span class="p">;</span>
</span><span class='line'><span class="c1">// ...</span>
</span><span class='line'><span class="nx">Icon</span><span class="p">.</span><span class="nx">defaultProps</span> <span class="o">=</span> <span class="nx">defaultProps</span><span class="p">.</span><span class="nx">Icon</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<h3>从渲染分离有状态的部分</h3>

<p>将有状态的数据加载逻辑与渲染逻辑混合可能增加组件复杂性。更好的方式是，写一个负责完成数据加载的有状态的容器组件，然后编写另一个负责显示数据的组件。这被称为 <a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0">容器模式</a>。</p>

<p>在下面的示例中，用户数据加载和显示功能放在一个组件中。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">class</span> <span class="nx">User</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">loading</span><span class="o">:</span> <span class="kc">true</span> <span class="p">};</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="p">{</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">user</span> <span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">loading</span>
</span><span class='line'>      <span class="o">?</span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">Loading</span><span class="p">...</span><span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'>      <span class="o">:</span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span class='line'>          <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span class='line'>            <span class="nx">First</span> <span class="nx">name</span><span class="o">:</span> <span class="p">{</span><span class="nx">user</span><span class="p">.</span><span class="nx">firstName</span><span class="p">}</span>
</span><span class='line'>          <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'>          <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span>
</span><span class='line'>            <span class="nx">First</span> <span class="nx">name</span><span class="o">:</span> <span class="p">{</span><span class="nx">user</span><span class="p">.</span><span class="nx">lastName</span><span class="p">}</span>
</span><span class='line'>          <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'>          <span class="p">...</span>
</span><span class='line'>        <span class="o">&lt;</span><span class="err">/div&gt;;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">componentDidMount</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">fetchUser</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">user</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="nx">loading</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">user</span> <span class="p">})})</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在整洁版本中，加载数据和显示数据已经分离。这不仅使代码更容易理解，而且能减少测试的工作量，因为可以独立测试每个部分。而且由于 <code>RenderUser</code> 是一个无状态组件，所以结果是可预测的。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">import</span> <span class="nx">RenderUser</span> <span class="nx">from</span> <span class="s1">&#39;./RenderUser&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">class</span> <span class="nx">User</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">loading</span><span class="o">:</span> <span class="kc">true</span> <span class="p">};</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="p">{</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">user</span> <span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">loading</span> <span class="o">?</span> <span class="o">&lt;</span><span class="nx">Loading</span> <span class="o">/&gt;</span> <span class="o">:</span> <span class="o">&lt;</span><span class="nx">RenderUser</span> <span class="nx">user</span><span class="o">=</span><span class="p">{</span><span class="nx">user</span><span class="p">}</span> <span class="o">/&gt;</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">componentDidMount</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">fetchUser</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">user</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="nx">loading</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">user</span> <span class="p">})})</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h3>使用无状态组件</h3>

<p>React v0.14.0 中引入了无状态函数组件（SFC），被简化成纯渲染组件，但有些开发者还在使用过去的方式。例如，以下组件就应该转换为 SFC。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">class</span> <span class="nx">TableRowWrapper</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="p">(</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="nx">tr</span><span class="o">&gt;</span>
</span><span class='line'>        <span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">}</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="err">/tr&gt;</span>
</span><span class='line'>    <span class="p">);</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>整洁版本清除了很多可能导致干扰的信息。通过 React 核心的优化，使用无状态组件将占用更少的内存，因为没有创建 Component 实例。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">TableRowWrapper</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">tr</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="p">{</span><span class="nx">children</span><span class="p">}</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/tr&gt;</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<h3>剩余/扩展属性（rest/spread）</h3>

<p>大约在一年前，我还推荐大家多用 <code>Object.assign</code>。但时代变化很快，在 ES2016/ES7 中引入新特性 <a href="https://github.com/tc39/proposal-object-rest-spread">rest/spread</a>。</p>

<p>比如这样一种场景，当传递给一些 props 给一个组件，只希望在组件本身使用 <code>className</code>，但是需要将其他所有 props 传递到子组件。这时，你可能会这样做：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">MyComponent</span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">others</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">({},</span> <span class="nx">props</span><span class="p">);</span>
</span><span class='line'>  <span class="k">delete</span> <span class="nx">others</span><span class="p">.</span><span class="nx">className</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">return</span> <span class="p">(</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">className</span><span class="p">}</span><span class="o">&gt;</span>
</span><span class='line'>      <span class="p">{</span><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="nx">MyOtherComponent</span><span class="p">,</span> <span class="nx">others</span><span class="p">)}</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'>  <span class="p">);</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>这不是一个非常优雅的解决方案。但是使用 rest/spread，就能轻而易举地实现，</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">MyComponent</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">className</span><span class="p">,</span> <span class="p">...</span><span class="nx">others</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">className</span><span class="p">}</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">MyOtherComponent</span> <span class="p">{...</span><span class="nx">others</span><span class="p">}</span> <span class="o">/&gt;</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/div&gt;</span>
</span><span class='line'><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>我们将剩余属性展开并作为新的 props 传递给 <code>MyOtherComponent</code> 组件。</p>

<h3>合理使用解构</h3>

<p>ES6 引入 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">解构</a>（destructuring） 的概念，这是一个非常棒的特性，用类似对象或数组字面量的语法获取一个对象的属性或一个数组的元素。</p>

<h4>对象解构</h4>

<p>在这个例子中，<code>componentWillReceiveProps</code> 组件接收 <code>newProps</code> 参数，然后将其 <code>active</code> 属性设置为新的 <code>state.active</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="nx">componentWillReceiveProps</span><span class="p">(</span><span class="nx">newProps</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span>
</span><span class='line'>    <span class="nx">active</span><span class="o">:</span> <span class="nx">newProps</span><span class="p">.</span><span class="nx">active</span>
</span><span class='line'>  <span class="p">});</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在整洁版本中，我们解构 <code>newProps</code>成 <code>active</code>。这样我们不仅不需要引用 <code>newProps.active</code>，而且也可以使用 ES6 的简短属性特性来调用 <code>setState</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="nx">componentWillReceiveProps</span><span class="p">({</span> <span class="nx">active</span> <span class="p">})</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="nx">active</span> <span class="p">});</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h4>数组解构</h4>

<p>一个经常被忽视的 ES6 特性是数组解构。以下面的代码为例，它获取 <code>locale</code> 的值，比如“en-US”，并将其分成 <code>language</code>（en）和 <code>country</code>（US）。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Dirty</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">splitLocale</span> <span class="o">=</span> <span class="nx">locale</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">);</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">language</span> <span class="o">=</span> <span class="nx">splitLocale</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">country</span> <span class="o">=</span> <span class="nx">splitLocale</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>在整洁版本，使用 ES6 的数组解构特性可以自动完成上述过程：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Clean</span>
</span><span class='line'><span class="kr">const</span> <span class="p">[</span><span class="nx">language</span><span class="p">,</span> <span class="nx">country</span><span class="p">]</span> <span class="o">=</span> <span class="nx">locale</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<h2>所以结论是</h2>

<p>希望这篇文章能有助于你看到编写整洁代码的好处，甚至可以直接使用这里介绍的一些代码示例。一旦你习惯编写整洁代码，将很快就会体会到 “write it and forget it” 的生活方式。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[魔幻语言 JavaScript 系列之 Call、bind 以及上下文]]></title>
    <link href="https://blog.mirreal.net/blog/2017/11/09/call-apply-and-context-in-javascript/"/>
    <updated>2017-11-09T14:24:57+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/11/09/call-apply-and-context-in-javascript</id>
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="http://blog.bloomca.me/2017/11/08/the-most-clever-line-of-javascript.html">The Most Clever Line of JavaScript</a></p>

<p>作者：<a href="https://twitter.com/blooomca">Seva Zaikov</a></p></blockquote>

<h2>原文</h2>

<p>最近 <a href="https://twitter.com/vedroarbuzov">一个朋友</a> 发给我一段非常有趣的 JavaScript 代码，是他在某个 <a href="https://github.com/pelias/openstreetmap/blob/313f208ea323232919e42bf88871d8e19ddacec3/stream/address_extractor.js#L54">开源库中</a> 看到的：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">addressParts</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">,</span> <span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>一开始，我觉得这是一个“不错的尝试”。但是，印象中 <code>map</code> 好像只接受一个参数，这里却出现第二个参数，所以去查看了 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">MDN文档</a>，才知道可以传一个上下文（context）作为第二个参数。在这时候，我还无法解释这段代码，运行完之后感到更加困惑了，因为它竟然能如预期那样工作。</p>

<p>我花了至少半个小时来尝试理解这段代码，这是一个很有趣的例子，可以用来说明 JavaScript 是一门多么魔幻的语言，即使已经写了好几年的 JS。当然，你可以选择自己去弄清楚，如果想看看我的理解，请继续阅读。</p>

<p>那么，它到底是如何工作的呢？让我们从一种更简单的实现开始（实际上这种实现代码更短，并且更易读:)）：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">addressParts</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">str</span> <span class="o">=&gt;</span> <span class="nx">str</span><span class="p">.</span><span class="nx">trim</span><span class="p">());</span>
</span></code></pre></td></tr></table></div></figure>


<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call">Function.prototype.call</a> 是 JavaScript 函数原型中的一个函数，它调用函数，使用第一个参数作为 <code>this</code> 参数，并传递剩余参数作为被调用函数的参数。举个例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// this function has `Function` in prototype chain</span>
</span><span class='line'><span class="c1">// so `call` is available</span>
</span><span class='line'><span class="kd">function</span> <span class="nx">multiply</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">y</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">multiply</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span> <span class="c1">// 15</span>
</span><span class='line'><span class="nx">multiply</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span> <span class="c1">// same, 15</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>map</code> 第二个参数的典型用法如下所示，假设有一个基于类的 React 组件，其功能是渲染一个按钮列表：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">class</span> <span class="nx">ExampleComponent</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">renderButton</span><span class="p">({</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">name</span> <span class="p">})</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// without proper `this` it will fail</span>
</span><span class='line'>    <span class="kr">const</span> <span class="p">{</span> <span class="nx">isActive</span> <span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="p">(</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="nx">Button</span> <span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">title</span><span class="p">}</span> <span class="nx">title</span><span class="o">=</span><span class="p">{</span><span class="nx">title</span><span class="p">}</span><span class="o">&gt;</span>
</span><span class='line'>        <span class="p">{</span><span class="nx">name</span><span class="p">}</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span><span class='line'>    <span class="p">);</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="p">{</span> <span class="nx">buttons</span> <span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// without second param our function won&#39;t be able</span>
</span><span class='line'>    <span class="c1">// to access `this` inside</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">buttonsMarkup</span> <span class="o">=</span> <span class="nx">buttons</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">renderButton</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>但是，以我的经验来看，这种使用第二个参数的做法并不常见，更常见的做法是使用类属性或装饰器来避免绑定。</p>

<blockquote><p>译者注：<code>map</code> 第二个参数的用法等同于
<code>javascript
const buttonsMarkup = buttons.map(this.renderButton.bind(this);
</code></p></blockquote>

<p>还有一个类似的方法 &ndash; <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply">Function.prototype.apply</a>，工作原理与 <code>call</code> 相同，只是第二个参数应该是一个数组（译者注：或者是一个类数组），它将被转换成一个参数列表，用逗号分隔。所以，让我们看看如何使用它来计算最大值：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span> <span class="c1">// if we know all numbers upfront</span>
</span><span class='line'><span class="c1">// we can call it like that</span>
</span><span class='line'>
</span><span class='line'><span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span> <span class="c1">// won&#39;t work!</span>
</span><span class='line'>
</span><span class='line'><span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span> <span class="c1">// will work!</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// however, ES2015 array destructuring works as well:</span>
</span><span class='line'><span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(...[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
</span></code></pre></td></tr></table></div></figure>


<p>现在，我们重新创建一个可以解决问题的函数调用方式。我们想删除字符串两端的空白字符，这个方法位于 <code>String.prototype</code> ，所以我们使用 <code>.</code> 操作符来调用它（虽然，字符串是原始值（primitive），但是当我们进行方法调用时，会在内部被转换成对象）。我们继续：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// let&#39;s try to imagine how trim method is implemented</span>
</span><span class='line'><span class="c1">// on String.prototype</span>
</span><span class='line'><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// string itself is contained inside `this`!</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">str</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
</span><span class='line'>  <span class="c1">// this is a very naive implementation</span>
</span><span class='line'>  <span class="k">return</span> <span class="nx">str</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/(^\s+)|(\s+$)/g</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// let&#39;s try to use `.call` method to invoke `trim`</span>
</span><span class='line'><span class="s2">&quot; aa &quot;</span><span class="p">.</span><span class="nx">trim</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">thisArg</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// but `this` is our string itself!</span>
</span><span class='line'><span class="c1">// so, next two calls are equivalent:</span>
</span><span class='line'><span class="s2">&quot; aa &quot;</span><span class="p">.</span><span class="nx">trim</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="s2">&quot; aa &quot;</span><span class="p">);</span>
</span><span class='line'><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="s2">&quot; aa &quot;</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>我们现在距离答案更近一步，但是仍然没有解释清楚最初那段代码：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">addressParts</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">,</span> <span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>让我们自己来实现 <code>Function.prototype.call</code>：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">thisArg</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// `this` in our case is actually our function!</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">fn</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// also, pretty naive implementation</span>
</span><span class='line'>  <span class="k">return</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">thisArg</span><span class="p">)(...</span><span class="nx">args</span><span class="p">);</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<p>现在，我们可以来理一理所有的东西。当我们在 <code>.map</code> 里面声明函数的时候，我们给 <code>Function.prototype.call</code> 绑定<code>String.prototype.trim</code> 作为 <code>this</code> 上下文，然后我们在数组中的每个元素上调用这个函数，把每个字符串作为 <code>thisArg</code> 参数的值传递给 <code>call</code>。这意味着，<code>String.prototype.trim</code> 将使用字符串作为 <code>this</code> 上下文来调用。我们已经知道这样做是有效的，看看下面的例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="s2">&quot; aa &quot;</span><span class="p">);</span> <span class="c1">// &quot;aa&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>问题解决了！但是，我认为这并不是一个好的做法，至于应该如何使用一种好的方式来完成这件事， 很简单，只需传递一个匿名函数就能搞定：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">addressParts</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">str</span> <span class="o">=&gt;</span> <span class="nx">str</span><span class="p">.</span><span class="nx">trim</span><span class="p">());</span> <span class="c1">// same effect</span>
</span></code></pre></td></tr></table></div></figure>


<h2>也谈谈 JavaScript 中的 call、apply 和 bind</h2>

<p>作者在最后这一段可能讲得有些简略，尤其是对于 <code>bind</code> 的用法，谈谈我的理解思路：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// 我们从常用的 slice 说起</span>
</span><span class='line'><span class="c1">// 相信很多人都写过这样的代码</span>
</span><span class='line'><span class="c1">// 我们称之为方法借用</span>
</span><span class='line'><span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">slice</span><span class="p">.</span><span class="nx">call</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">// [ 2, 3]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 也会有人这样写</span>
</span><span class='line'><span class="p">[].</span><span class="nx">slice</span><span class="p">.</span><span class="nx">call</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">// [2, 3]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 但上面的例子其实不是其真实的使用场景，因为 [1, 2, 3] 本身就是一个 array，可以直接调用 slice</span>
</span><span class='line'><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1">// [2, 3]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 之前比较常见的场景是处理 argumnents，通过这种方式将这种类数组转换成真正的数组</span>
</span><span class='line'><span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">slice</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">arguments</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 回到最上面的例子，我们已经知道使用 call 可以让你在某个特定上下文(context)调用函数(fn)</span>
</span><span class='line'><span class="c1">// fn.call(context [, ...args])</span>
</span><span class='line'><span class="c1">// 而对 call 来说，它的上下文就是 fn</span>
</span><span class='line'><span class="c1">// 所以 call 本身也是有上下文的，那我们为什么不可以直接给 call 指定一个上下文，就像这样：</span>
</span><span class='line'><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">slice</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">// [2, 3]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 或者是这样，apply 接受一个数组</span>
</span><span class='line'><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">slice</span><span class="p">,</span> <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="mi">1</span><span class="p">])</span> <span class="c1">// [2, 3]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 当然，也可以使用一下 bind，这样会返回一个新的函数</span>
</span><span class='line'><span class="c1">// 我们直接将 slice 绑定到 call 的上下文</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">slice</span> <span class="o">=</span> <span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">slice</span><span class="p">)</span>
</span><span class='line'><span class="nx">slice</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">// [2, 3]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 我们来稍微改动一下，跟上述 slice 的例子一致</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">trim</span> <span class="o">=</span> <span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 上述 slice 等同于 Array.prototype.slice.call</span>
</span><span class='line'><span class="c1">// 所以这里的 trim，等同于 String.prototype.trim.call</span>
</span><span class='line'><span class="c1">// 那么</span>
</span><span class='line'><span class="nx">trim</span><span class="p">(</span><span class="s1">&#39; node&#39;</span><span class="p">)</span> <span class="c1">// &#39;node&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 现在，在 map 里使用 trim</span>
</span><span class='line'><span class="nx">addressParts</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">))</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 回到最初的那段代码，这里面包含一个隐式的 bind 操作，与上面的代码等效</span>
</span><span class='line'><span class="c1">// 问题到这里就已经解决</span>
</span><span class='line'><span class="nx">addressParts</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">,</span> <span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="c1">// 如作者所言，这样的代码确实不容易阅读，不过对于我们理解 call、bind 以及 context 的概念仍是个很好的例子</span>
</span><span class='line'><span class="c1">// 我们还可以写得更复杂</span>
</span><span class='line'><span class="c1">// 不用担心，下面这段代码什么新东西都没有，不过是给 map 绑定到 call 而已</span>
</span><span class='line'><span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">map</span><span class="p">)(</span><span class="nx">addressParts</span><span class="p">,</span> <span class="nb">Function</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">call</span><span class="p">,</span> <span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">trim</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[编写扁平化的代码]]></title>
    <link href="https://blog.mirreal.net/blog/2017/11/02/writing-flat-and-declarative-code/"/>
    <updated>2017-11-02T11:20:03+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/11/02/writing-flat-and-declarative-code</id>
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="https://peeke.nl/writing-flat-code">Writing flat &amp; declarative code</a></p>

<p>作者：<a href="https://twitter.com/peeke__">Peeke Kuepers</a></p></blockquote>

<p>&ndash; <em>给你的代码增加一点点函数式编程的特性</em></p>

<p><strong>最近我对函数式编程非常感兴趣。这个概念让我着迷：应用数学来增强抽象性和强制纯粹性，以避免副作用，并实现代码的良好可复用性。同时，函数式编程非常复杂。</strong></p>

<p>函数式编程有一个非常陡峭的学习曲线，因为它来源于数学中的<a href="https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch5.md#category-theory">范畴论</a>。接触不久之后，就将遇到诸如组合（composition）、恒等（identity），函子（functor）、单子（monad），以及逆变（contravariant）等术语。我根本不太了解这些概念，可能这也是我从来没有在实践中运用函数式编程的原因。</p>

<p>我开始思考：在常规的命令式编程和完全的函数式编程之间是否可能会有一些中间形式？既允许在代码库引入函数式编程的一些很好的特性，同时暂时保留已有的旧代码。</p>

<p>对我而言，函数式编程最大的作用就是强制你编写声明性代码：代码描述你做什么，而不是在描述如何做。这样就可以轻松了解特定代码块的功能，而无需了解其真正的运行原理。事实证明，编写声明式代码是函数式编程中最简单的部分之一。</p>

<h2>循环</h2>

<blockquote><p>&hellip;一个循环就是一个命令式控制结构，难以重用，并且难以插入到其他操作中。此外，它还得不断变化代码来响应新的迭代需求。</p>

<p>&ndash; Luis Atencio</p></blockquote>

<p>所以，让我们先看一下循环，循环是命令式编程的一个很好的例子。循环涉及很多语法，都是描述它们的行为是如何工作，而不是它们在做什么。例如，看看这段代码：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">helloworld</span><span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">*=</span> <span class="mi">2</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">doSomething</span><span class="p">(</span><span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>这段代码在做什么呢？它将数组内除第一个数字 （<code>let i = 1</code>）的其他所有数字乘以 2，如果是偶数的话（<code>if (arr % 2 === 0)</code>），就进行某些操作。在此过程中，原始数组的值会被改变。但这通常是不必要的，因为数组可能还会在代码库中的其他地方用到，所以刚才所做的改变可能会导致意外的结果。</p>

<p>但最主要的原因是，这段代码看起来很难一目了然。它是命令式的，for 循环告诉我们如何遍历数组，在里面，使用一个 if 语句有条件地调用一个函数。</p>

<p>我们可以通过使用数组方法以声明式的方式重写这段代码。数组方法直接表达所做的事，比较常见的方法包括：<code>forEach</code>，<code>map</code>，<code>filter</code>，<code>reduce</code> 和 <code>slice</code>。</p>

<p>结果就像下面这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">helloworld</span><span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">evenNumbers</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">===</span> <span class="mi">0</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">arr</span>
</span><span class='line'>        <span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>        <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">v</span> <span class="o">=&gt;</span> <span class="nx">v</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
</span><span class='line'>        <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">evenNumbers</span><span class="p">)</span>
</span><span class='line'>        <span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">v</span> <span class="o">=&gt;</span> <span class="nx">doSomething</span><span class="p">(</span><span class="nx">v</span><span class="p">))</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这个例子中，我们使用一种很好的，扁平的链式结构去描述我们在做什么，明确表明意图。此外，我们避免了改变原始数组，从而避免不必要的副作用，因为大多数数组方法会返回一个新数组。当箭头函数开始变得越来越复杂时，可以地将其提取到一个特定的函数中，比如 <code>evenNumbers</code>， 从而尽量保持结构简单易读。</p>

<p>在上面的例子，链式调用并没有返回值，而是以 forEach 结束。然而，我们可以轻松地剥离最后一部分，并返回结果，以便我们可以在其他地方处理它。如果还需要返回除数组以外的任何东西，可以使用 <code>reduce</code> 函数。</p>

<p>对于接下来的一个例子，假设我们有一组 JSON 数据，其中包含在一个虚构歌唱比赛中不同国家获得的积分：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='json'><span class='line'><span class="p">[</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="nt">&quot;country&quot;</span><span class="p">:</span> <span class="s2">&quot;NL&quot;</span><span class="p">,</span>
</span><span class='line'>        <span class="nt">&quot;points&quot;</span><span class="p">:</span> <span class="mi">12</span>
</span><span class='line'>    <span class="p">},</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="nt">&quot;country&quot;</span><span class="p">:</span> <span class="s2">&quot;BE&quot;</span><span class="p">,</span>
</span><span class='line'>        <span class="nt">&quot;points&quot;</span><span class="p">:</span> <span class="mi">3</span>
</span><span class='line'>    <span class="p">},</span>
</span><span class='line'>    <span class="p">{</span>
</span><span class='line'>        <span class="nt">&quot;country&quot;</span><span class="p">:</span> <span class="s2">&quot;NL&quot;</span><span class="p">,</span>
</span><span class='line'>        <span class="nt">&quot;points&quot;</span><span class="p">:</span> <span class="mi">0</span>
</span><span class='line'>    <span class="p">},</span>
</span><span class='line'>    <span class="err">...</span>
</span><span class='line'><span class="p">]</span>
</span></code></pre></td></tr></table></div></figure>


<p>我们想计算荷兰（NL）获得的总积分，根据印象中其强大的音乐能力，我们可以认为这是一个非常高的分数，但我们想要更精确地确认这一点。</p>

<p>使用循环可能会是这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">countVotes</span><span class="p">(</span><span class="nx">votes</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">let</span> <span class="nx">score</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">votes</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">votes</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">country</span> <span class="o">===</span> <span class="s1">&#39;NL&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">score</span> <span class="o">+=</span> <span class="nx">votes</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">points</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="nx">score</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用数组方法重构，我们得到一个更干净的代码片段：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">countVotes</span><span class="p">(</span><span class="nx">votes</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">sum</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="nx">votes</span>
</span><span class='line'>        <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">vote</span> <span class="o">=&gt;</span> <span class="nx">vote</span><span class="p">.</span><span class="nx">country</span> <span class="o">===</span> <span class="s1">&#39;NL&#39;</span><span class="p">)</span>
</span><span class='line'>        <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">vote</span> <span class="o">=&gt;</span> <span class="nx">vote</span><span class="p">.</span><span class="nx">points</span><span class="p">)</span>
</span><span class='line'>        <span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="nx">sum</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>有时候 <code>reduce</code> 可能有点难以阅读，将 <code>reduce</code> 函数提取出来会在理解上有帮助。在上面的代码片段中，我们定义了一个 <code>sum</code> 函数来描述函数的作用，因此方法链仍然保持很好的可读性。</p>

<h2>if else 语句</h2>

<p>接下来，我们来聊聊大家都很喜欢的 if else 语句，if else 语句也是命令式代码里一个很好的例子。为了使我们的代码更具声明式，我们将使用三元表达式。</p>

<p>一个三元表达式是 if else 语句的替代语法。以下两个代码块具有相同的效果：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// Block 1</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">condition</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">doThis</span><span class="p">();</span>
</span><span class='line'><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">doThat</span><span class="p">();</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Block 2</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">condition</span> <span class="o">?</span> <span class="nx">doThis</span><span class="p">()</span> <span class="o">:</span> <span class="nx">doThat</span><span class="p">();</span>
</span></code></pre></td></tr></table></div></figure>


<p>当在定义（或返回）一个常量时，三元表达式非常有用。使用 if else 语句会将该变量的使用范围限制在语句内，通过使用三元语句，我们可以避免这个问题：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">condition</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="s1">&#39;foo&#39;</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="s1">&#39;bar&#39;</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">b</span> <span class="o">=</span> <span class="nx">condition</span> <span class="o">?</span> <span class="s1">&#39;foo&#39;</span> <span class="o">:</span> <span class="s1">&#39;bar&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">a</span><span class="p">);</span> <span class="c1">// Uncaught ReferenceError: a is not defined</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">b</span><span class="p">);</span> <span class="c1">// &#39;bar&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>现在，我们来看看如何应用这一点来重构一些更重要的代码：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">box</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">();</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nx">box</span><span class="p">.</span><span class="nx">top</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nx">box</span><span class="p">.</span><span class="nx">bottom</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&lt;</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">reveal</span><span class="p">();</span>
</span><span class='line'><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">hide</span><span class="p">();</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>那么，上面的代码发生了什么呢？if 语句检查元素当前是否在页面的可见部分内，这个信息在代码的任何地方都没有表达出来。基于此布尔值，再调用 <code>reveal()</code> 或者 <code>hide()</code> 函数。</p>

<p>将这个 if 语句转换成三元表达式迫使我们将条件移动到它自己的变量中。这样我们可以将三元表达式组合在一行上，现在通过变量的名称来传达布尔值表示的内容，这样还不错。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">box</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">();</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">isInViewport</span> <span class="o">=</span>
</span><span class='line'>    <span class="nx">box</span><span class="p">.</span><span class="nx">top</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span>
</span><span class='line'>    <span class="nx">box</span><span class="p">.</span><span class="nx">bottom</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&lt;</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">isInViewport</span> <span class="o">?</span> <span class="nx">reveal</span><span class="p">()</span> <span class="o">:</span> <span class="nx">hide</span><span class="p">();</span>
</span></code></pre></td></tr></table></div></figure>


<p>通过这个例子，重构带来的好处可能看起来不大。接下来会有一个相比更复杂的例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">elements</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">element</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>        <span class="kr">const</span> <span class="nx">box</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">();</span>
</span><span class='line'>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">box</span><span class="p">.</span><span class="nx">top</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nx">box</span><span class="p">.</span><span class="nx">bottom</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&lt;</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">reveal</span><span class="p">();</span>
</span><span class='line'>        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">hide</span><span class="p">();</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>


<p>这很不好，打破了我们优雅的扁平的调用链，从而使代码更难读。我们再次使用三元操作符，而在使用它的时候，使用 <code>isInViewport</code> 检查，并跟它自己的动态函数分开。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">isInViewport</span> <span class="o">=</span> <span class="nx">element</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">box</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">();</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">topInViewport</span> <span class="o">=</span> <span class="nx">box</span><span class="p">.</span><span class="nx">top</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">bottomInViewport</span> <span class="o">=</span> <span class="nx">box</span><span class="p">.</span><span class="nx">bottom</span> <span class="o">-</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">&lt;</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">topInViewport</span> <span class="o">&amp;&amp;</span> <span class="nx">bottomInViewport</span><span class="p">;</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="nx">elements</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">elem</span> <span class="o">=&gt;</span> <span class="nx">isInViewport</span><span class="p">(</span><span class="nx">elem</span><span class="p">)</span> <span class="o">?</span> <span class="nx">reveal</span><span class="p">()</span> <span class="o">:</span> <span class="nx">hide</span><span class="p">());</span>
</span></code></pre></td></tr></table></div></figure>


<p>此外，现在我们将 <code>isInViewport</code> 移动到一个独立函数，可以很容易地把它放在它自己的 helper 类/对象之内：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">isInViewport</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;helpers&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">elements</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">elem</span> <span class="o">=&gt;</span> <span class="nx">isInViewport</span><span class="p">(</span><span class="nx">elem</span><span class="p">)</span> <span class="o">?</span> <span class="nx">reveal</span><span class="p">()</span> <span class="o">:</span> <span class="nx">hide</span><span class="p">());</span>
</span></code></pre></td></tr></table></div></figure>


<p>虽然上面的例子依赖于所处理的是数组，但是在不明确是在数组的情况下，也可以采用这种编码风格。</p>

<p>例如，看看下面的函数，它通过三条规则来验证密码的有效性。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">passwordRegex</span> <span class="nx">as</span> <span class="nx">requiredChars</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;regexes&#39;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">getJson</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;helpers&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">validatePassword</span> <span class="o">=</span> <span class="nx">async</span> <span class="nx">value</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">value</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;</span> <span class="mi">6</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">requiredChars</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
</span><span class='line'>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">forbidden</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">getJson</span><span class="p">(</span><span class="s1">&#39;/forbidden-passwords&#39;</span><span class="p">)</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">forbidden</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">return</span> <span class="nx">value</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">validatePassword</span><span class="p">(</span><span class="nx">someValue</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">persist</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果我们使用数组包装初始值，就可以使用在上面的例子中里面所用到的所有数组方法。此外，我们已经将验证函数打包成 <code>validationRules</code> 使其可重用。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">minLength</span><span class="p">,</span> <span class="nx">matchesRegex</span><span class="p">,</span> <span class="nx">notBlacklisted</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;validationRules&#39;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">passwordRegex</span> <span class="nx">as</span> <span class="nx">requiredChars</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;regexes&#39;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">getJson</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;helpers&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">validatePassword</span> <span class="o">=</span> <span class="nx">async</span> <span class="nx">value</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">minLength</span><span class="p">(</span><span class="mi">6</span><span class="p">))</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">matchesRegex</span><span class="p">(</span><span class="nx">requiredChars</span><span class="p">))</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">await</span> <span class="nx">notBlacklisted</span><span class="p">(</span><span class="s1">&#39;/forbidden-passwords&#39;</span><span class="p">))</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">shift</span><span class="p">()</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="k">return</span> <span class="nx">result</span>
</span><span class='line'>  <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;something went wrong...&#39;</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">validatePassword</span><span class="p">(</span><span class="nx">someValue</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">persist</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>目前在 JavaScript 中有一个 <a href="https://github.com/tc39/proposal-pipeline-operator">管道操作符</a> 的提案。使用这个操作符，就不用再把原始值换成数组了。可以直接在前面的值调用管道操作符之后的函数，有点像 <code>Array</code> 的 <code>map</code> 功能。修改之后的代码大概就像这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">minLength</span><span class="p">,</span> <span class="nx">matchesRegex</span><span class="p">,</span> <span class="nx">notBlacklisted</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;validationRules&#39;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">passwordRegex</span> <span class="nx">as</span> <span class="nx">requiredChars</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;regexes&#39;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">getJson</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;helpers&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">validatePassword</span> <span class="o">=</span> <span class="nx">async</span> <span class="nx">value</span> <span class="o">=&gt;</span>
</span><span class='line'>  <span class="nx">value</span>
</span><span class='line'>    <span class="o">|&gt;</span> <span class="nx">minLength</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span>
</span><span class='line'>    <span class="o">|&gt;</span> <span class="nx">matchesRegex</span><span class="p">(</span><span class="nx">requiredChars</span><span class="p">)</span>
</span><span class='line'>    <span class="o">|&gt;</span> <span class="nx">await</span> <span class="nx">notBlacklisted</span><span class="p">(</span><span class="s1">&#39;/forbidden-passwords&#39;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="k">try</span> <span class="p">{</span> <span class="nx">someValue</span> <span class="o">|&gt;</span> <span class="nx">await</span> <span class="nx">validatePassword</span> <span class="o">|&gt;</span> <span class="nx">persist</span> <span class="p">}</span>
</span><span class='line'><span class="k">catch</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// handle specific error, thrown in validation rule</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>但需要注意的是，这仍然是一个非常早期的提案，不过可以稍微期待一下。</p>

<h2>事件</h2>

<p>最后，我们来看看事件处理。一直以来，事件处理很难以扁平化的方式编写代码。可以 <code>Promise</code> 化来保持一种链式的，扁平化的编程风格，但 <code>Promise</code> 只能 <code>resolve</code> 一次，而事件绝对会多次触发。</p>

<p>在下面的示例中，我们创建一个类，它对用户的每个输入值进行检索，结果是一个自动补全的数组。首先检查字符串是否长于给定的阈值长度。如果满足条件，将从服务器检索自动补全的结果，并将其渲染成一系列标签。</p>

<p>注意代码的不“纯”，频繁地使用 <code>this</code> 关键字。几乎每个函数都在访问 <code>this</code> 这个关键字：</p>

<blockquote><p>译注：作者在这里使用 &ldquo;this keyword&#8221;，有一种双关的意味</p></blockquote>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">apiCall</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;helpers&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">class</span> <span class="nx">AutoComplete</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">constructor</span> <span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">_endpoint</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">endpoint</span>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">_threshold</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">threshold</span>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">_inputElement</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">inputElement</span>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">_containerElement</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">list</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">_inputElement</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span>
</span><span class='line'>      <span class="k">this</span><span class="p">.</span><span class="nx">_onInput</span><span class="p">())</span>
</span><span class='line'>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">_onInput</span> <span class="p">()</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_inputElement</span><span class="p">.</span><span class="nx">value</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">_options</span><span class="p">.</span><span class="nx">threshold</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">this</span><span class="p">.</span><span class="nx">_updateList</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">_updateList</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">apiCall</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_endpoint</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span> <span class="p">})</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">items</span> <span class="o">=&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">_render</span><span class="p">(</span><span class="nx">items</span><span class="p">))</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">html</span> <span class="o">=&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">_containerElement</span> <span class="o">=</span> <span class="nx">html</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">_render</span> <span class="p">(</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="kd">let</span> <span class="nx">html</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">items</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>      <span class="nx">html</span> <span class="o">+=</span> <span class="err">`</span><span class="o">&lt;</span><span class="nx">a</span> <span class="nx">href</span><span class="o">=</span><span class="s2">&quot;${ item.href }&quot;</span><span class="o">&gt;</span><span class="nx">$</span><span class="p">{</span> <span class="nx">item</span><span class="p">.</span><span class="nx">label</span> <span class="p">}</span><span class="o">&lt;</span><span class="err">/a&gt;`</span>
</span><span class='line'>    <span class="p">})</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="nx">html</span>
</span><span class='line'>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>通过使用 <code>Observable</code>，我们将用一种更好的方式对这段代码进行重写。可以简单将 <code>Observable</code> 理解成一个能够多次 <code>resolve</code> 的 <code>Promise</code>。</p>

<blockquote><p>Observable 类型可用于基于推送模型的数据源，如 DOM 事件，定时器和套接字</p></blockquote>

<p><code>Observable</code> 提案目前处于 Stage-1。在下面 <code>listen</code> 函数的实现是从 GitHub 上的<a href="https://github.com/tc39/proposal-observable">提案</a>中直接复制的，主要是将事件监听器转换成 <code>Observable</code>。可以看到，我们可以将整个 <code>AutoComplete</code> 类重写为单个方法的函数链。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">apiCall</span><span class="p">,</span> <span class="nx">listen</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;helpers&#39;</span><span class="p">;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">renderItems</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;templates&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="kd">function</span> <span class="nx">AutoComplete</span> <span class="p">({</span> <span class="nx">endpoint</span><span class="p">,</span> <span class="nx">threshold</span><span class="p">,</span> <span class="nx">input</span><span class="p">,</span> <span class="nx">container</span> <span class="p">})</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">listen</span><span class="p">(</span><span class="nx">input</span><span class="p">,</span> <span class="s1">&#39;input&#39;</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">value</span> <span class="o">=&gt;</span> <span class="nx">value</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">threshold</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">value</span> <span class="o">=&gt;</span> <span class="nx">apiCall</span><span class="p">(</span><span class="nx">endpoint</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span> <span class="p">}))</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">items</span> <span class="o">=&gt;</span> <span class="nx">renderItems</span><span class="p">(</span><span class="nx">items</span><span class="p">))</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">html</span> <span class="o">=&gt;</span> <span class="nx">container</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">html</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>由于大多数 <code>Observable</code> 库的实现过于庞大，我很期待 ES 原生的实现。<code>map</code>，<code>filter</code> 和 <code>forEach</code>方法还不是规范的一部分，但是在 <a href="https://github.com/zenparsing/zen-observable">zen-observable</a> 已经在扩展 API 实现，而 zen-observable 本身是 ES Observables 的一种实现 。</p>

<p>&ndash;</p>

<p>我希望你会对这些“扁平化”模式感兴趣。就个人而言，我很喜欢以这种方式重写我的程序。你接触到的每一段代码都可以更易读。使用这种技术获得的经验越多，就越来越能认识到这一点。记住这个简单的法则：</p>

<p><strong>The flatter the better!</strong></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ES6 Promise：模式与反模式]]></title>
    <link href="https://blog.mirreal.net/blog/2017/09/29/es6-promises-patterns-and-anti-patterns/"/>
    <updated>2017-09-29T16:50:11+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/09/29/es6-promises-patterns-and-anti-patterns</id>
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="https://medium.com/datafire-io/es6-promises-patterns-and-anti-patterns-bbb21a5d0918">ES6 Promises: Patterns and Anti-Patterns</a>
作者：Bobby Brennan</p></blockquote>

<p>当几年前，第一次使用 NodeJS 的时候，对现在被称为“ <a href="http://callbackhell.com/">回调地狱</a> ”的写法感到很困扰。幸运的是，现在是 2017 年了，NodeJS 已经采用大量 JavaScript 的最新特性，从 <a href="http://node.green/">v4</a> 开始已经支持 Promise。</p>

<p>尽管 Promise 可以让代码更加简洁易读，但对于只熟悉回调函数的人来说，可能对此还是会有所怀疑。在这里，将列出我在使用Promise 时学到的一些基本模式，以及踩的一些坑。</p>

<p><em>注意：<strong>在本文中</strong>将使用<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">箭头函数</a> ，如果你还不是很熟悉，其实很简单，建议<a href="http://exploringjs.com/es6/ch_arrow-functions.html">先读一下使用它们的好处</a></em></p>

<h2>模式与最佳实践</h2>

<h4>使用 Promise</h4>

<p>如果使用的是已经支持 Promise 的第三方库，那么使用起来非常简单。只需关心两个函数：<code>then()</code> 和 <code>catch()</code>。例如，有一个客户端 API 包含三个方法，<code>getItem()</code>，<code>updateItem()</code>，和<code>deleteItem()</code>，每一个方法都返回一个 Promise：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">()</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">item</span><span class="p">.</span><span class="nx">amount</span><span class="o">++</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">api</span><span class="p">.</span><span class="nx">updateItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">item</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">update</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;error while working on item 1&#39;</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>每次调用 <code>then()</code> 会在 Promise 链中创建一个新的步骤，如果链中的任何一个地方出现错误，就会触发接下来的 <code>catch()</code> 。<code>then()</code> 和 <code>catch()</code> 都可以返回一个值或者一个新的 Promise，结果将被传递到 Promise 链的下一个<code>then()</code>。</p>

<p>为了比较，这里使用回调函数来实现相同逻辑：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">item</span><span class="p">.</span><span class="nx">amount</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">api</span><span class="p">.</span><span class="nx">updateItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">item</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">update</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
</span><span class='line'>    <span class="p">})</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>要注意的第一个区别是，使用回调函数，我们必须在过程的<strong>每个</strong>步骤中进行错误处理，而不是用单个的 catch-all 来处理。回调函数的第二个问题更直观，每个步骤都要水平缩进，而使用 Promise 的代码则有显而易见的顺序关系。</p>

<h4>回调函数 Promise 化</h4>

<p>需要学习的第一个技巧是如何将回调函数转换为 Promise。你可能正在使用仍然基于回调的库，或是自己的旧代码，不过不用担心，因为只需要几行代码就可以将其包装成一个 Promise。这是将 Node 中的一个回调方法 <code>fs.readFile</code> 转换为 Promise的示例：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">readFilePromise</span><span class="p">(</span><span class="nx">filename</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="k">new</span> <span class="nx">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">filename</span><span class="p">,</span> <span class="s1">&#39;utf8&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="nx">reject</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span><span class='line'>      <span class="k">else</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span><span class='line'>    <span class="p">})</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">readFilePromise</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">)</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">))</span>
</span></code></pre></td></tr></table></div></figure>


<p>关键部分是 Promise 构造函数，它接收一个函数作为参数，这个函数有两个函数参数：<code>resolve</code> 和 <code>reject</code>。在这个函数里完成所有工作，完成之后，在成功时调用 <code>resolve</code>，如果有错误则调用 <code>reject</code>。</p>

<p>需要注意的是只有<strong>一个</strong><code>resolve</code> 或者 <code>reject</code> 被调用，即应该只被调用一次。在我们的示例中，如果 <code>fs.readFile</code> 返回错误，我们将错误传递给 <code>reject</code>，否则将文件数据传递给<code>resolve</code>。</p>

<h4>Promise 的值</h4>

<p>ES6 有两个很方便的辅助函数，用于通过普通值创建 Promise：<code>Promise.resolve()</code> 和 <code>Promise.reject()</code>。例如，可能需要在同步处理某些情况时一个返回 Promise 的函数：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">readFilePromise</span><span class="p">(</span><span class="nx">filename</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">filename</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">reject</span><span class="p">(</span><span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">&quot;Filename not specified&quot;</span><span class="p">));</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">filename</span> <span class="o">===</span> <span class="s1">&#39;index.html&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s1">&#39;&lt;h1&gt;Hello!&lt;/h1&gt;&#39;</span><span class="p">);</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>  <span class="k">return</span> <span class="k">new</span> <span class="nx">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="cm">/*...*/</span><span class="p">})</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>注意，虽然可以传递任何东西（或者不传递任何值）给 <code>Promise.reject()</code>，但是好的做法是传递一个<code>Error</code>。</p>

<h4>并行运行</h4>

<p><code>Promise.all</code>是一个并行运行 Promise 数组的方法，也就是说是同时运行。例如，我们有一个要从磁盘读取文件的列表。使用上面创建的 <code>readFilePromise</code> 函数，将如下所示：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">let</span> <span class="nx">filenames</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;blog.html&#39;</span><span class="p">,</span> <span class="s1">&#39;terms.html&#39;</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="nx">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">filenames</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">readFilePromise</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">files</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;index:&#39;</span><span class="p">,</span> <span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;blog:&#39;</span><span class="p">,</span> <span class="nx">files</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;terms:&#39;</span><span class="p">,</span> <span class="nx">files</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>我甚至不会使用传统的回调函数来尝试编写与之等效的代码，那样会很凌乱，而且也容易出错。</p>

<h4>串行运行</h4>

<p>有时同时运行一堆 Promise 可能会出现问题。比如，如果尝试使用 <code>Promise.all</code> 的 API ​​去检索一堆资源，则可能会在达到速率限制时开始响应<a href="https://httpstatuses.com/429">429错误</a>。</p>

<p>一种解决方案是串行运行 Promise，或一个接一个地运行。但是在 ES6 中没有提供类似 <code>Promise.all</code> 这样的方法（为什么？），但我们可以使用 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=b"><code>Array.reduce</code></a> 来实现：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">let</span> <span class="nx">itemIDs</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="nx">itemIDs</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">promise</span><span class="p">,</span> <span class="nx">itemID</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="nx">promise</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="nx">itemID</span><span class="p">));</span>
</span><span class='line'><span class="p">},</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">());</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这种情况下，我们需要等待每次调用 <code>api.deleteItem()</code> 完成之后才能进行下一次调用。这种方法，比为每个 itemID 写 <code>.then()</code> 更简洁更通用：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">()</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">4</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">5</span><span class="p">));</span>
</span></code></pre></td></tr></table></div></figure>


<h4>Race</h4>

<p>ES6 提供的另一个很方便的函数是 <code>Promise.race</code>。跟 <code>Promise.all</code> 一样，接收一个 Promise 数组，并同时运行它们，但不同的是，会在一旦<strong>任何</strong> Promise 完成或失败的情况下返回，并放弃所有其他的结果。</p>

<p>例如，我们可以创建一个在几秒钟之后超时的 Promise：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">timeout</span><span class="p">(</span><span class="nx">ms</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="k">new</span> <span class="nx">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">reject</span><span class="p">,</span> <span class="nx">ms</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">Promise</span><span class="p">.</span><span class="nx">race</span><span class="p">([</span><span class="nx">readFilePromise</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">),</span> <span class="nx">timeout</span><span class="p">(</span><span class="mi">1000</span><span class="p">)])</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&quot;Timed out after 1 second&quot;</span><span class="p">))</span>
</span></code></pre></td></tr></table></div></figure>


<p>需要注意的是，其他 Promise 仍将继续运行 ，只是看不到结果而已。</p>

<h4>捕获错误</h4>

<p>捕获错误最常见的方式是添加一个  <code>.catch()</code> 代码块，这将捕获前面所有 <code>.then()</code> 代码块中的错误  ：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">()</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">item</span><span class="p">.</span><span class="nx">amount</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">api</span><span class="p">.</span><span class="nx">updateItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">item</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;failed to get or update item&#39;</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这里，只要有 <code>getItem</code> 或者 <code>updateItem</code> 失败，<code>catch()</code>就会被触发。但是如果我们想分开处理 <code>getItem</code> 的错误怎么办？只需再插入一个<code>catch()</code> 就可以，它也可以返回另一个 Promise。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">()</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">api</span><span class="p">.</span><span class="nx">createItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">{</span><span class="nx">amount</span><span class="o">:</span> <span class="mi">0</span><span class="p">}))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">item</span><span class="p">.</span><span class="nx">amount</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">api</span><span class="p">.</span><span class="nx">updateItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">item</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;failed to update item&#39;</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>现在，如果<code>getItem()</code>失败，我们通过第一个 <code>catch</code> 介入并创建一条新的记录。</p>

<h4>抛出错误</h4>

<p>应该将 <code>then()</code> 语句中的所有代码视为 <code>try</code> 块内的所有代码。<code>return Promise.reject()</code> 和 <code>throw new Error()</code> 都会导致下一个 <code>catch()</code> 代码块的运行。</p>

<p>这意味着运行时错误也会触发 <code>catch()</code>，所以不要去假设错误的来源。例如，在下面的代码中，我们可能希望该 <code>catch()</code> 只能获得 <code>getItem</code> 抛出的错误，但是如示例所示，它还会在我们的 <code>then()</code> 语句中捕获运行时错误。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">delete</span> <span class="nx">item</span><span class="p">.</span><span class="nx">owner</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">owner</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span> <span class="c1">// Cannot read property &#39;name&#39; of undefined</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<h4>动态链</h4>

<p>有时，我们想要动态地构建 Promise 链，例如，在满足特定条件时，插入一个额外的步骤。在下面的示例中，在读取给定文件之前，我们可以选择创建一个锁定文件：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">readFileAndMaybeLock</span><span class="p">(</span><span class="nx">filename</span><span class="p">,</span> <span class="nx">createLockFile</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">let</span> <span class="nx">promise</span> <span class="o">=</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">();</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">createLockFile</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">promise</span> <span class="o">=</span> <span class="nx">promise</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">writeFilePromise</span><span class="p">(</span><span class="nx">filename</span> <span class="o">+</span> <span class="s1">&#39;.lock&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">))</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">return</span> <span class="nx">promise</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">readFilePromise</span><span class="p">(</span><span class="nx">filename</span><span class="p">));</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>一定要通过重写 <code>promise = promise.then(/*...*/)</code> 来更新 <code>Promise</code> 的值。参看接下来反模式中会提到的 <strong>多次调用 then()</strong>。</p>

<h2>反模式</h2>

<p>Promise 是一个整洁的抽象，但很容易陷入某些陷阱。以下是我遇到的一些最常见的问题。</p>

<h4>重回回调地狱</h4>

<p>当我第一次从回调函数转到 Promise 时，发现很难摆脱一些旧习惯，仍像使用回调函数一样嵌套 Promise：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">item</span><span class="p">.</span><span class="nx">amount</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">api</span><span class="p">.</span><span class="nx">updateItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">item</span><span class="p">)</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">update</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>          <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">deletion</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;done!&#39;</span><span class="p">);</span>
</span><span class='line'>          <span class="p">})</span>
</span><span class='line'>      <span class="p">})</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>这种嵌套是完全没有必要的。有时一两层嵌套可以帮助组合相关任务，但是最好总是使用 <code>.then()</code> 重写成 Promise 垂直链  。</p>

<h4>没有返回</h4>

<p>我遇到的一个经常会犯的错误是在一个 Promise 链中忘记 <code>return</code> 语句。你能发现下面的 bug 吗？</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">item</span><span class="p">.</span><span class="nx">amount</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">api</span><span class="p">.</span><span class="nx">updateItem</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">item</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">update</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">api</span><span class="p">.</span><span class="nx">deleteItem</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">deletion</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;done!&#39;</span><span class="p">);</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>因为我们没有在第4行的 <code>api.updateItem()</code> 前面写 <code>return</code>，所以 <code>then()</code> 代码块会立即 resolove，导致 <code>api.deleteItem()</code> 可能在<code>api.updateItem()</code> 完成之前就被调用。</p>

<p>在我看来，这是 ES6 Promise 的一个大问题，往往会引发意想不到的行为。问题是，  <code>.then()</code> 可以返回一个值，也可以返回一个新的 Promise，<code>undefined</code> 完全是一个有效的返回值。就个人而言，如果我负责 Promise API，我会在 <code>.then()</code> 返回   <code>undefined</code> 时抛出运行时错误，但现在我们需要特别注意 <code>return</code> 创建的 Promise。</p>

<h4>多次调用 <code>.then()</code></h4>

<p>根据规范，在同一个 Promise 上多次调用 <code>then()</code> 是完全有效的，并且回调将按照其注册顺序被调用。但是，我并未见过需要这样做的场景，并且在使用返回值和错误处理时可能会产生一些意外行为：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">let</span> <span class="nx">p</span> <span class="o">=</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s1">&#39;a&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">p</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="s1">&#39;b&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">p</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">result</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="c1">// &#39;a&#39;</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'>
</span><span class='line'><span class="kd">let</span> <span class="nx">q</span> <span class="o">=</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s1">&#39;a&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">q</span> <span class="o">=</span> <span class="nx">q</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="s1">&#39;b&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">q</span> <span class="o">=</span> <span class="nx">q</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">result</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="c1">// &#39;b&#39;</span>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这个例子中，因为我们在每次调用 <code>then()</code> 不更新 <code>p</code> 的值，所以我们看不到 <code>'b'</code> 返回。但是每次调用 <code>then()</code> 时更新 <code>q</code>，所以其行为更可预测。</p>

<p>这也适用于错误处理：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">let</span> <span class="nx">p</span> <span class="o">=</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">();</span>
</span><span class='line'><span class="nx">p</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">&quot;whoops!&quot;</span><span class="p">)})</span>
</span><span class='line'><span class="nx">p</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;hello!&#39;</span><span class="p">);</span> <span class="c1">// &#39;hello!&#39;</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'>
</span><span class='line'><span class="kd">let</span> <span class="nx">q</span> <span class="o">=</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">();</span>
</span><span class='line'><span class="nx">q</span> <span class="o">=</span> <span class="nx">q</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">&quot;whoops!&quot;</span><span class="p">)})</span>
</span><span class='line'><span class="nx">q</span> <span class="o">=</span> <span class="nx">q</span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;hello&#39;</span><span class="p">);</span> <span class="c1">// We never reach here</span>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这里，我们期望的是抛出一个错误来打破 Promise 链，但由于没有更新 <code>p</code> 的值，所以第二个 <code>then()</code> 仍会被调用。</p>

<p>有可能在一个 Promise 上多次调用 <code>.then()</code> 有很多理由  ，因为它允许将 Promise 分配到几个新的独立的 Promise 中，但是还没发现真实的使用场景。</p>

<h4>混合使用回调和 Promise</h4>

<p>很容易进入一种陷阱，在使用基于 Promise 库的同时，仍在基于回调的项目中工作。始终避免在 <code>then()</code> 或 <code>catch()</code> 使用回调函数 ，否则 Promise 会吞噬任何后续的错误，将其作为 Promise 链的一部分。例如，以下内容看起来是一个挺合理的方式，使用回调函数来包装一个 Promise：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">getThing</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="nx">callback</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">item</span><span class="p">))</span>
</span><span class='line'>    <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">e</span><span class="p">));</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">getThing</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">thing</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">thing</span><span class="p">);</span>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>这里的问题是，如果有错误，我们会收到关于“Unhandled promise rejection”的警告，即使我们添加了一个 <code>catch()</code> 代码块。这是因为，<code>callback()</code> 在 <code>then()</code> 和 <code>catch()</code> 都会被调用，使之成为 Promise 链的一部分。</p>

<p>如果必须使用回调来包装 Promise，可以使用  <code>setTimeout</code> （或者是 NodeJS 中的 <code>process.nextTick</code>）来打破 Promise：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">getThing</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">api</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">item</span> <span class="o">=&gt;</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">callback</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">item</span><span class="p">)))</span>
</span><span class='line'>    <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">_</span> <span class="o">=&gt;</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">e</span><span class="p">)));</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">getThing</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">thing</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">thing</span><span class="p">);</span>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<h4>不捕获错误</h4>

<p>JavaScript 中的错误处理有点奇怪。虽然支持熟悉的 <code>try/catch</code> 范例，但是没有办法强制调用者以 Java 的方式处理错误。然而，使用回调函数，使用所谓的“errbacks”，即第一个参数是一个错误回调变得很常见。这迫使调用者至少承认错误的可能性。例如，<code>fs</code> 库：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;utf8&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用 Promise，又将很容易忘记需要进行错误处理，特别是对于敏感操作（如文件系统和数据库访问）。目前，如果没有捕获到 reject 的 Promise，将在 NodeJS 中看到非常丑的警告：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='text'><span class='line'>(node:29916) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: whoops!
</span><span class='line'>(node:29916) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
</span></code></pre></td></tr></table></div></figure>


<p>确保在主要的事件循环中任何 Promise 链的末尾添加 <code>catch()</code> 以避免这种情况。</p>

<h2>总结</h2>

<p>希望这是一篇有用的关于常见 Promise 模式和反模式的概述。如果你想了解更多，这里有一些有用的资源：</p>

<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Mozilla 的 ES6 Promise 文档</a></li>
<li><a href="https://developers.google.com/web/fundamentals/getting-started/primers/promises">来自 Google 的 Promise 介绍</a></li>
<li><a href="http://www.datchley.name/es6-promises/">Dave Atchley 的 ES6 Promise 概述</a></li>
</ul>


<p>更多的 Promise <a href="http://www.datchley.name/promise-patterns-anti-patterns/">模式</a>和<a href="https://hackernoon.com/javascript-promises-best-practices-anti-patterns-b32309f65551">反模式</a></p>

<p><a href="https://medium.com/datafire-io">或者阅读来自 DataFire 团队的内容</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[在 Node.js 中使用原生 ES 模块]]></title>
    <link href="https://blog.mirreal.net/blog/2017/09/13/using-es-modules-natively-in-node-dot-js/"/>
    <updated>2017-09-13T16:19:59+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/09/13/using-es-modules-natively-in-node-dot-js</id>
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="http://2ality.com/2017/09/native-esm-node.html">Using ES modules natively in Node.js</a></p>

<p>作者：<a href="https://twitter.com/rauschma">Axel Rauschmayer</a></p></blockquote>

<p>从版本 8.5.0 开始，Node.js 开始支持原生 ES 模块，可以通过命令行选项打开该功能。新功能很大程度上得归功于 <a href="https://twitter.com/bradleymeck">Bradley Farias</a>。</p>

<h2>1.演示</h2>

<p>这个示例的代码目录结构如下：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>esm-demo/
</span><span class='line'>    lib.mjs
</span><span class='line'>    main.mjs</span></code></pre></td></tr></table></div></figure>


<p>lib.mjs：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">export</span> <span class="kd">function</span> <span class="nx">add</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">x</span> <span class="o">+</span> <span class="nx">y</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>main.mjs：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="p">{</span><span class="nx">add</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;./lib.mjs&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Result: &#39;</span><span class="o">+</span><span class="nx">add</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">));</span>
</span></code></pre></td></tr></table></div></figure>


<p>运行演示：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>node --experimental-modules main.mjs
</span><span class='line'>Result: 5
</span></code></pre></td></tr></table></div></figure>


<h2>2.清单：需要注意的事情</h2>

<h3>ES 模块：</h3>

<ul>
<li><p>不能动态导入模块。但是 <a href="http://2ality.com/2017/01/import-operator.html">动态 import()</a> 的相关工作正在进行中，应该很快就能提供支持。</p></li>
<li><p>没有元变量，如 <code>__dirname</code> 和 <code>__filename</code>。但是，有一个的类似功能的提案：“<a href="https://github.com/tc39/proposal-import-meta">import.meta</a>”。看起来可能是这样：</p></li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="kr">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>现在所有模块标识符都是 URL（这部分在 Node.js 是新增的）：

<ul>
<li>文件 - 带文件扩展名的相对路径： <code>../util/tools.mjs</code></li>
<li>库 - 没有文件扩展名，也没有路径 <code>lodash</code></li>
<li>如何更好地使 npm 库在浏览器中也可用（不使用 bundler）仍有待观察。一种可能性是引入 RequireJS 风格的配置数据，将路径映射到实际路径。目前，在浏览器中使用 bare path 的模块标识符是非法的。</li>
</ul>
</li>
</ul>


<h3>与 CJS 模块的互操作性</h3>

<ul>
<li>你可以导入 CJS 模块，但它们总是只有默认的导出 - 即 <code>module.exports</code> 的值。让 CJS 模块支持命名导出已经在做了，但可能需要一段时间。如果你能帮忙，<a href="https://twitter.com/bradleymeck/status/906210545145184257">可以来做</a>。</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="nx">fs1</span> <span class="nx">from</span> <span class="s1">&#39;fs&#39;</span><span class="p">;</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">fs1</span><span class="p">).</span><span class="nx">length</span><span class="p">);</span> <span class="c1">// 86</span>
</span><span class='line'>
</span><span class='line'><span class="kr">import</span> <span class="o">*</span> <span class="nx">as</span> <span class="nx">fs2</span> <span class="nx">from</span> <span class="s1">&#39;fs&#39;</span><span class="p">;</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">fs2</span><span class="p">));</span> <span class="c1">// [&#39;default&#39;]</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>不能在 ES 模块中使用 require()。主要原因是：

<ul>
<li>路径解析工作稍有不同：ESM 不支持 <code>NODE_PATH</code> 和 <code>require.extensions</code>。而且，它的标识符始终是 URL 也会导致一些细微差异。</li>
<li>ES 模块始终以异步方式加载，这确保了与 Web 的最大兼容性。这种加载风格并不能通过 require() 混合使用同步加载 CJS 模块。</li>
<li>禁止同步模块加载也可以为 Top-level await 导入 ES 模块保留后路（一个当前正在考虑的功能）。</li>
</ul>
</li>
</ul>


<h2>3.早期版本的 Node.js 上的 ES 模块</h2>

<p>如果要在 8.5.0 之前的 Node.js 版本上使用 ES 模块，请参阅 John-David Dalton 的 <a href="https://github.com/standard-things/esm">@std/esm</a>。</p>

<p>提示：如果不启用任何可解锁的额外功能，将在 Node.js 保持 100％ 兼容原生 ES 模块.</p>

<h2>FAQ</h2>

<h3>什么时候可以不带命令行选项使用ES 模块？</h3>

<p>目前的计划是在 Node.js 10 LTS 中默认可使用 ES 模块。</p>

<h2>进一步阅读  </h2>

<p>有关 Node.js 和浏览器中 ES 模块的更多信息：</p>

<ul>
<li>“<a href="http://2ality.com/2017/01/babel-esm-spec-mode.html">Making transpiled ES modules more spec-compliant</a>” [using ES modules natively vs. transpiling them via Babel]</li>
<li>“<a href="http://2ality.com/2017/05/es-module-specifiers.html">Module specifiers: what’s new with ES modules?</a>” [Why <code>.mjs</code>? How are module specifiers resolved? Etc.]</li>
<li>“<a href="http://exploringjs.com/es6/ch_modules.html">Modules</a>” [in-depth chapter on ES modules in “Exploring ES6”]</li>
</ul>


<p>即将到来的 ECMAScript 提案：</p>

<ul>
<li>博客: “<a href="http://2ality.com/2017/01/import-operator.html">ES proposal: <code>import()</code> – dynamically importing ES modules</a>”</li>
<li>提案: “<a href="https://github.com/tc39/proposal-import-meta"><code>import.meta</code></a>”</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[在工作中学习]]></title>
    <link href="https://blog.mirreal.net/blog/2017/09/02/learning-at-work/"/>
    <updated>2017-09-02T21:49:52+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/09/02/learning-at-work</id>
    <content type="html"><![CDATA[<blockquote><p>原文；<a href="https://jvns.ca/blog/2017/08/06/learning-at-work/">Learning at work</a></p>

<p>作者：Julia Evans</p>

<p>谈点：学习是一件没有尽头的事，虽然在工作中很多时候被淹没在没玩没了的需求里面，加班是常态，或许很多人不知不觉放弃学习的心思。这篇文章中提到的一些方法我很赞同，很多点也是我的做事方式，这里也希望所有人都能找到适合自己的学习方式。</p></blockquote>

<p>在 Twitter 上我提了一个问题，“你是如何花时间用于自身学习”（<a href="https://twitter.com/b0rk/status/887111177062555648">这个推文</a>)，这些回答有一些相同之处：</p>

<ul>
<li>阅读博客文章</li>
<li>去参加业界大会</li>
<li>读书</li>
<li>在洗碗时看一些演讲</li>
<li>在一些不那么重要的项目里运用你想要学习的技术</li>
</ul>


<p>这些方法都挺管用的。为了个人职业发展，在工作之外花时间学习新技能是非常普遍的事，在这方面我也有不少经验。</p>

<p>另一方面，我知道有一些很厉害的程序员在工作之外完全不写代码。于是我开始思考，如果你想变得更牛，但又不想工作之外花太多额外时间要怎么办？</p>

<p>这些是我以及在 Twitter 上的朋友提到的一些观点，并且都是能在工作时候做的事情。</p>

<h2>不要学习工作之外的编程语言/框架</h2>

<p>这一点听起来有些消极，但确实还是很有用的。对于学习编程语言，我的观点是：</p>

<ul>
<li>我很熟悉几种编程语言（python，scala，ruby）</li>
<li>学习新的编程语言需要相当长的时间</li>
<li>我不想把空闲时间花在上面</li>
</ul>


<p>最近我在做一些 Go 方面的工作，还挺有趣的，我也喜欢做这件事。但是又觉得在上面花费太多个人时间，所以显得不是那么有趣了。通过编写代码学习编程，阅读别人的代码掌握一些模式，然后自己审查代码，然而这些都不是必须的，因为完全可以在工作中去做这些事情。</p>

<h2>选择一些可以从中学习的项目</h2>

<p>这些是我在过去三年学到的一些东西：</p>

<ul>
<li>Scala/Ruby/Go</li>
<li>hadoop/mapreduce/scalding</li>
<li>如何使用 Java 并发库，以及如何配置一个 Java 程序</li>
<li>关于各种 AWS 服务的工作原理</li>
<li>很多机器学习的东西</li>
<li>网络/ CDN / TLS 如何工作</li>
<li>docker/容器/rkt/kubernetes</li>
<li>服务发现 / DNS / jenkins</li>
</ul>


<p>一个关于如何选择项目的例子：如果在工作使用一个不能很好并行的程序，这时候问题就来了。我可以选择去问写这个程序的人为什么不使用并行编程，但是如果我也很想学习一下并行编程的话，就可以自己去完成这件事。所以后来我就 <a href="https://jvns.ca/blog/2016/03/29/thread-pools-part-ii-i-love-blocking/">学到了如何在 Java 中使用线程池</a>。</p>

<p>我只为此工作了几天，但却因此学到了新东西。</p>

<p>现在我正在从事 Kubernetes 相关的工作，当时选择它并不是因为可以在这里面学到很多东西。但是我确实学到很多有关分布式系统的东西，还在工作中使用 Go 语言，我觉得这很很棒。</p>

<p>当人们说像“嘿，我们使用 X 技术，需要有相关经验的人才能在这里工作”时，这其实相当愚蠢。现在我在网络/ puppet / kubernetes / docker / AWS 上花了很多时间，在这项工作之前没有任何相关工作经验。</p>

<h2>观察那些高级人员是如何做事的</h2>

<p>我会观察那些我敬佩的人在工作中的做事方式，然后尝试模仿他们或者是向他们寻求建议。例如，当 <a href="http://onemogin.com/">Cory</a> 加入时，我注意到，在引进新技术的时候，他会这样做：</p>

<ul>
<li>找到另一个有类似问题需要解决的团队</li>
<li>与他们合作，确保技术真正解决问题</li>
</ul>


<p>现在我正在开展一个新项目，一直在考虑这些工作可以帮到谁以及如何帮助，这样做就会感觉好很多。</p>

<h2>阅读每个 pull request</h2>

<p>在这个主题下引用两个我很喜欢的观点：</p>

<blockquote><p>我在一个小团队，所以会阅读所有的 pull request，直到完全了解问题和解决方案</p></blockquote>

<p>和</p>

<blockquote><p>我也是一样！我跟踪检查，看看人们如何解决各种问题</p></blockquote>

<p>事实上，我并没有阅读团队中的每一个 pull request。但是那确实很有用，通过跟踪别人在这方面做的工作来学习这个领域的东西。</p>

<p>但我并不是能完全做到。我曾经从事机器学习方面的工作，发现理论也很有趣，就想跟踪人们在这方面的情况，但对我来说有太多要注意的东西。我只能尽量注意那些比较接近我的东西，像是其中网络团队的一些工作。</p>

<h2>阅读源代码</h2>

<blockquote><p>阅读我用到的源代码对我而言是一个大块头。了解它在内部在做什么，主要是为什么可以通过某种方式工作。</p></blockquote>

<p>这是很重要的一点，也非常重要。很多库、框架、工具并没有很好的文档，在没有阅读源代码的情况下根本无法了解其工作原理。</p>

<h2>跟进你无法解决的 bug</h2>

<p>有时我会遇到一些自己没办法解决的 bug。后来，可能别人会找到解决方法，在这时候，就值得花时间去搞清楚具体的解决方法是什么，以及他们如何想出来的。</p>

<p>例如，最近有一个我没有调试出来的网络问题，刚好有人在上周搞清楚这个问题。现在想一想，大概明白造成这个 bug 的原因，但是我并不清楚他们用什么工具来获取调试所需的信息。当我重新开始工作时，必须确保我真的搞清楚这个问题，这样下次就可以做得更好。</p>

<p>Jessica Kerr 评论道</p>

<blockquote><p>每当我在故障排除时，除了解决这个问题，还会更深入或更广泛地延伸开去。</p></blockquote>

<p>我也喜欢这个回复：</p>

<blockquote><p>有时候，想解决一个与工作有关的问题，但并没有真的在实际工作中发生，只是看看我能不能在某方面有所突破。</p></blockquote>

<h2>运用好通勤时间</h2>

<p>对我而言，其实并没有通勤时间。但很多人提到他们会利用通勤时间来听播客/阅读报纸/阅读有趣的文章。这似乎是一个很不错的方式，来做一些你感兴趣的事情。</p>

<h2>花时间在工作中学习</h2>

<p>Twitter上有人说“我希望每天可以花 1 小时来学习”。我的观点是，我的工作就是要在工作日中抽出时间来学习东西。像现在我正在工作中使用 Kubernetes，这是一个很复杂的系统需要很长时间才能理解，我得花时间了解它是如何工作的。例如，在开始的时候，没什么目的地在做集群测试，只是想了解容器的网络如何工作的，同时也在项目上取得进展。</p>

<p>这可能对我来说挺容易的，因为我的工作跟别人隔得很远，没有人真的知道我具体在做什么，他们只是关心在大方面做的是什么。</p>

<p>实际上，如果要是提前多花点时间阅读，可能效果会更好。就像我刚刚在阅读 Kelsey Hightower 的“learn kubernetes the hard way”的文档，读完不需花太长时间，而且其中有一个关于如何设置一个集群的很好的点，这样就可以很节省我很多时间。</p>

<p>关于这个点，有些人还要想得更远。比如，我的朋友 Dan 就提到好几次，说他喜欢在工作中阅读技术书籍。最初觉得这是一件令人惊讶的事情，但它确实很有用。事实上，有很多跟我工作相关的书籍，找不到理由为什么不能在工作中阅读它们。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript 中的匿名递归]]></title>
    <link href="https://blog.mirreal.net/blog/2017/08/09/anonymous-recursion-in-javascript/"/>
    <updated>2017-08-09T14:54:22+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/08/09/anonymous-recursion-in-javascript</id>
    <content type="html"><![CDATA[<blockquote><p>原文：<a href="https://dev.to/simov/anonymous-recursion-in-javascript">Anonymous Recursion in JavaScript</a></p>

<p>作者：Simeon Velichkov</p></blockquote>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span>
</span><span class='line'>  <span class="p">(</span>
</span><span class='line'>    <span class="p">(</span><span class="nx">f</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">f</span><span class="p">(</span><span class="nx">f</span><span class="p">)</span>
</span><span class='line'>  <span class="p">)</span>
</span><span class='line'>  <span class="p">(</span>
</span><span class='line'>    <span class="p">(</span><span class="nx">f</span><span class="p">)</span> <span class="o">=&gt;</span>
</span><span class='line'>      <span class="p">(</span><span class="nx">l</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">l</span><span class="p">)</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">l</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="nx">f</span><span class="p">(</span><span class="nx">f</span><span class="p">)(</span><span class="nx">l</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">l</span><span class="p">)</span>
</span><span class='line'>      <span class="p">}</span>
</span><span class='line'>  <span class="p">)</span>
</span><span class='line'><span class="p">)</span>
</span><span class='line'><span class="p">(</span>
</span><span class='line'>  <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>是的，这就是想要分享给大家的一个有趣的示例。这个例子包含以下特性：<a href="https://en.wikipedia.org/wiki/Closure_(computer_programming">闭包</a>)，<a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression">自执行函数</a>，<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions">箭头函数</a>，<a href="https://en.wikipedia.org/wiki/Functional_programming">函数式编程</a> 和 <a href="https://en.wikipedia.org/wiki/Anonymous_recursion">匿名递归</a>。</p>

<p>你可以复制粘贴上述代码到浏览器控制台，会看到打印结果如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">]</span>
</span><span class='line'><span class="p">[</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">]</span>
</span><span class='line'><span class="p">[</span> <span class="mi">3</span> <span class="p">]</span>
</span><span class='line'><span class="p">[]</span>
</span><span class='line'><span class="p">[]</span>
</span><span class='line'><span class="p">[</span> <span class="mi">3</span> <span class="p">]</span>
</span><span class='line'><span class="p">[</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">]</span>
</span><span class='line'><span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">]</span>
</span></code></pre></td></tr></table></div></figure>


<p>说到函数式编程，这里有一个使用 <a href="https://en.wikipedia.org/wiki/Scheme_(programming_language">Scheme</a>) (JavaScript 借鉴过的其中一门语言)编写的类似例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='scheme'><span class='line'><span class="p">(</span>
</span><span class='line'>  <span class="p">(</span>
</span><span class='line'>    <span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">f</span><span class="p">)</span> <span class="p">(</span><span class="nf">f</span> <span class="nv">f</span><span class="p">))</span>
</span><span class='line'>    <span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">f</span><span class="p">)</span>
</span><span class='line'>      <span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">l</span><span class="p">)</span>
</span><span class='line'>        <span class="p">(</span><span class="nf">print</span> <span class="nv">l</span><span class="p">)</span>
</span><span class='line'>        <span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">not </span><span class="p">(</span><span class="nb">null? </span><span class="nv">l</span><span class="p">))</span> <span class="p">((</span><span class="nf">f</span> <span class="nv">f</span><span class="p">)</span> <span class="p">(</span><span class="nb">cdr </span><span class="nv">l</span><span class="p">)))</span>
</span><span class='line'>        <span class="p">(</span><span class="nf">print</span> <span class="nv">l</span><span class="p">)</span>
</span><span class='line'>      <span class="p">)</span>
</span><span class='line'>    <span class="p">)</span>
</span><span class='line'>  <span class="p">)</span>
</span><span class='line'>  <span class="o">&#39;</span><span class="p">(</span><span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<h2>Unwind</h2>

<p>像其他很多编程语言一样，函数调用是通过在函数名称后添加括号 <code>()</code> 来完成的：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">foo</span> <span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s1">&#39;hey&#39;</span> <span class="p">}</span>
</span><span class='line'><span class="nx">foo</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>


<p>在 JavaScript 中我们可以使用括号包裹任意数量的表达式：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span><span class="s1">&#39;hey&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="o">+</span><span class="mi">5</span><span class="p">,</span> <span class="s1">&#39;dev.to&#39;</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>上面代码返回结果是 <code>'dev.to'</code>，原因是 JavaScript 返回最后一个表达式作为结果。</p>

<p>使用括号 <code>()</code> 包裹一个匿名函数表示其结果就是 <a href="https://en.wikipedia.org/wiki/Anonymous_function">匿名函数</a> 本身。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s1">&#39;hey&#39;</span> <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>这本身并没有用处，因为匿名函数没有命名，无法被引用，除非在初始化的时候立即调用它。</p>

<p>就像是普通函数一样，我们可以在其后面添加括号 <code>()</code> 来进行调用。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s1">&#39;hey&#39;</span> <span class="p">})()</span>
</span></code></pre></td></tr></table></div></figure>


<p>也可以使用箭头函数：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(()</span> <span class="o">=&gt;</span> <span class="s1">&#39;hey&#39;</span><span class="p">)()</span>
</span></code></pre></td></tr></table></div></figure>


<p>同样地，在匿名函数后添加括号 <code>()</code> 来执行函数，这被称为 <a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression">自执行函数</a>。</p>

<h2>闭包</h2>

<p><a href="https://en.wikipedia.org/wiki/Closure_(computer_programming">闭包</a>) 指的是函数和该函数声明词法环境的组合。结合 <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions">箭头功能</a>，我们可以定义如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="p">(</span><span class="nx">hi</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">dev</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">hi</span> <span class="o">+</span> <span class="s1">&#39; &#39;</span> <span class="o">+</span> <span class="nx">dev</span>
</span></code></pre></td></tr></table></div></figure>


<p>在控制台调用上述函数会打印 <code>hey dev.to</code>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">foo</span><span class="p">(</span><span class="s1">&#39;hey&#39;</span><span class="p">)(</span><span class="s1">&#39;dev.to&#39;</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>注意，我们可以在内部函数作用域访问外部函数的参数 <code>hi</code>。</p>

<p>以下代码跟上述代码一样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">foo</span> <span class="p">(</span><span class="nx">hi</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">dev</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">hi</span> <span class="o">+</span> <span class="s1">&#39; &#39;</span> <span class="o">+</span> <span class="nx">dev</span> <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>自执行的版本如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span>
</span><span class='line'>  <span class="p">(</span><span class="nx">hi</span><span class="p">)</span> <span class="o">=&gt;</span>
</span><span class='line'>    <span class="p">(</span>
</span><span class='line'>      <span class="p">(</span><span class="nx">dev</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="err">`</span><span class="nx">$</span><span class="p">{</span><span class="nx">hi</span><span class="p">}</span> <span class="nx">$</span><span class="p">{</span><span class="nx">dev</span><span class="p">}</span><span class="err">`</span>
</span><span class='line'>    <span class="p">)</span>
</span><span class='line'>    <span class="p">(</span><span class="s1">&#39;dev.to&#39;</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="s1">&#39;hey&#39;</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>首先，将 <code>hey</code> 作为参数 <code>hi</code> 的值传给最外层作用域的函数，然后这个函数返回另一个自执行函数。<code>dev.to</code> 作为参数 <code>dev</code> 的值传给内部函数，最后这个函数返回最终值：<code>'hey dev.to'</code>。</p>

<h2>再深入一点</h2>

<p>这个一个上述自执行函数的修改版本：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span>
</span><span class='line'>  <span class="p">(</span>
</span><span class='line'>    <span class="p">(</span><span class="nx">dev</span><span class="p">)</span> <span class="o">=&gt;</span>
</span><span class='line'>      <span class="p">(</span><span class="nx">hi</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="err">`</span><span class="nx">$</span><span class="p">{</span><span class="nx">hi</span><span class="p">}</span> <span class="nx">$</span><span class="p">{</span><span class="nx">dev</span><span class="p">}</span><span class="err">`</span>
</span><span class='line'>  <span class="p">)</span>
</span><span class='line'>  <span class="p">(</span><span class="s1">&#39;dev.to&#39;</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="s1">&#39;hey&#39;</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>需要注意的是，<a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression">自执行函数</a> 和 <a href="https://en.wikipedia.org/wiki/Closure_(computer_programming">闭包</a>) 用作初始化和封装状态，接下来我们来看另外一个例子。</p>

<h2>匿名递归</h2>

<p>回到我们最初的例子，这次加点注释：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span>
</span><span class='line'>  <span class="p">(</span>
</span><span class='line'>    <span class="p">(</span><span class="nx">f</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">f</span><span class="p">(</span><span class="nx">f</span><span class="p">)</span> <span class="c1">// 3.</span>
</span><span class='line'>  <span class="p">)</span>
</span><span class='line'>  <span class="p">(</span>
</span><span class='line'>    <span class="p">(</span><span class="nx">f</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="c1">// 2.</span>
</span><span class='line'>      <span class="p">(</span><span class="nx">l</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="c1">// 4.</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">l</span><span class="p">)</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="nx">l</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="nx">f</span><span class="p">(</span><span class="nx">f</span><span class="p">)(</span><span class="nx">l</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">l</span><span class="p">)</span>
</span><span class='line'>      <span class="p">}</span>
</span><span class='line'>  <span class="p">)</span>
</span><span class='line'><span class="p">)</span>
</span><span class='line'><span class="p">(</span>
</span><span class='line'>  <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span> <span class="c1">// 1.</span>
</span><span class='line'><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<ol>
<li>输入函数 <code>[1, 2, 3]</code> 传给最外层作用域</li>
<li>整个函数作为参数传给上面函数</li>
<li>这个函数接收下面函数作为参数 <code>f</code> 的值，然后自身调用</li>
<li><code>2.</code>将被调用被作为 <code>3.</code>的结果然后返回函数 <code>4.</code> ，该函数是满足最外层作用域的函数，因此接收输入数组作为 <code>l</code> 参数的值</li>
</ol>


<p>至于结果为什么是那样子，原因是在递归内部有一个对函数 <code>f</code> 的引用来接收输入数组 <code>l</code>。所以能那样调用：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">f</span><span class="p">(</span><span class="nx">f</span><span class="p">)(</span><span class="nx">l</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span></code></pre></td></tr></table></div></figure>


<p>注意，<code>f</code> 是一个闭包，所以我们只需要调用它就可以访问到操作输入数组的最里面的函数。</p>

<p>为了说明目的，第一个 <code>console.log(l)</code> 语句表示递归自上而下，第二个语句表示递归自下而上。</p>

<h2>结论</h2>

<p>希望你喜欢这篇文章，并从中学到了新的东西。闭包、自执行函数、函数式编程模式不是黑魔法。它们遵循一套易于理解和玩乐的简单原则。</p>

<p>话虽如此，你必须培养自己何时使用它们，何时不用的这样一种感觉。如果你的代码变得难以维护，那这可能会成为重构中一些好点子。</p>

<p>然而，理解这些基本技术对于创建清晰优雅的解决方案以及提升自我是至关重要的。</p>

<p>Happy Coding！</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[面向初学者的高阶组件介绍]]></title>
    <link href="https://blog.mirreal.net/blog/2017/07/19/higher-order-components-for-beginners/"/>
    <updated>2017-07-19T18:59:37+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/07/19/higher-order-components-for-beginners</id>
    <content type="html"><![CDATA[<blockquote><p>作者：Brandon Newton</p>

<p>原文：<a href="https://btnwtn.com/articles/higher-order-components-for-beginners">Higher-Order Components (HOCs) for Beginners</a></p>

<p>谈点：一篇面向初学者的 HOC 介绍。高阶组件听起来挺唬人的，只看名字恐怕不是那么容易明白究竟是何物，而且通常来讲高阶组件并不是组件，而是接受组件作为参数，并且返回组件的函数。早期利用 ES5 的 mixin 语法来做的事，基本都可以使用高阶组件代替，而且能做的还有更多。</p></blockquote>

<h2>前言</h2>

<p>写这篇文章的起因是其他关于高阶组件（Higher-Order Components）的文章，包含官方文档，都令初学者感到相当困惑。我知道有高阶组件这样一个东西，但不知道它到底有什么用。所以，想通过一篇文章来对高阶组件有一个更好的理解。</p>

<p>在此之前，我们需要先来讲一下 JavaScript 中的函数。</p>

<h2>ES6 箭头函数简介</h2>

<p>接下来将提供一些箭头函数的简单示例，如果之前没有使用过，可以认为它们与普通函数基本一致。下面的代码会展示箭头函数与普通函数的区别。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="mi">42</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// same as:</span>
</span><span class='line'><span class="p">()</span> <span class="o">=&gt;</span> <span class="mi">42</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// same as:</span>
</span><span class='line'><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="mi">42</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">function</span> <span class="nx">person</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="nx">name</span> <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// same as:</span>
</span><span class='line'><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="nx">name</span> <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>阅读 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">MDN 的箭头函数文档</a> 了解更多信息。</p>

<h2>作为值的函数与部分调用</h2>

<p>就像是数字、字符串、布尔值 一样，函数也是值，意味着可以像传递其他数据一样传递函数，可以将函数作为参数传递给另外一个函数。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">execute</span> <span class="o">=</span> <span class="p">(</span><span class="nx">someFunction</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">someFunction</span><span class="p">()</span>
</span><span class='line'>
</span><span class='line'><span class="nx">execute</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">alert</span><span class="p">(</span><span class="s1">&#39;Executed&#39;</span><span class="p">))</span>
</span></code></pre></td></tr></table></div></figure>


<p>也可以在在函数中返回一个函数：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">getOne</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="mi">1</span>
</span><span class='line'>
</span><span class='line'><span class="nx">getOne</span><span class="p">()()</span>
</span></code></pre></td></tr></table></div></figure>


<p>之所以在 <code>getOne</code> 后面有两个 <code>()</code> ，是因为第一个返回的返回值是一个函数。如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">getOne</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="mi">1</span>
</span><span class='line'>
</span><span class='line'><span class="nx">getOne</span>
</span><span class='line'><span class="c1">//=&gt; () =&gt; () =&gt; 1</span>
</span><span class='line'>
</span><span class='line'><span class="nx">getOne</span><span class="p">()</span>
</span><span class='line'><span class="c1">//=&gt; () =&gt; 1</span>
</span><span class='line'>
</span><span class='line'><span class="nx">getOne</span><span class="p">()()</span>
</span><span class='line'><span class="c1">//=&gt; 1</span>
</span></code></pre></td></tr></table></div></figure>


<p>从函数返回函数可以帮助我们追踪初始输入函数。例如，下面的函数接受一个数字作为参数，并返回一个将该参数乘以新参数的函数：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">multiply</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">y</span>
</span><span class='line'>
</span><span class='line'><span class="nx">multiply</span><span class="p">(</span><span class="mi">5</span><span class="p">)(</span><span class="mi">20</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>这个示例跟上述 <code>getOne</code> 一样，在下面这个例子，让 x = 5，y = 20。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">multiply</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">y</span>
</span><span class='line'>
</span><span class='line'><span class="nx">multiply</span>
</span><span class='line'><span class="c1">//=&gt; (x) =&gt; (y) =&gt; x * y</span>
</span><span class='line'>
</span><span class='line'><span class="nx">multiply</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span><span class='line'><span class="c1">//=&gt; (y) =&gt; 5 * y</span>
</span><span class='line'>
</span><span class='line'><span class="nx">multiply</span><span class="p">(</span><span class="mi">5</span><span class="p">)(</span><span class="mi">20</span><span class="p">)</span>
</span><span class='line'><span class="c1">//=&gt; 5 * 20</span>
</span></code></pre></td></tr></table></div></figure>


<p>在只传入一个参数调用 <code>multiply</code> 函数时，即部分调用该函数。比如，<code>multiply(5)</code> 讲得到一个将其输入值乘以 5 的函数，<code>multiply(7)</code> 将得到一个将其输入值乘以 7 的函数。依此类推。通过部分调用可以创建一个预定义功能的新函数：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">multiply</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">y</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">y</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">multiplyByFive</span> <span class="o">=</span> <span class="nx">multiply</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">multiplyBy100</span> <span class="o">=</span> <span class="nx">multiply</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="nx">multiplyByFive</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
</span><span class='line'><span class="c1">//=&gt; 100</span>
</span><span class='line'><span class="nx">multiply</span><span class="p">(</span><span class="mi">5</span><span class="p">)(</span><span class="mi">20</span><span class="p">)</span>
</span><span class='line'><span class="c1">//=&gt; 100</span>
</span><span class='line'>
</span><span class='line'><span class="nx">multiplyBy100</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span><span class='line'><span class="c1">//=&gt; 500</span>
</span><span class='line'><span class="nx">multiply</span><span class="p">(</span><span class="mi">100</span><span class="p">)(</span><span class="mi">5</span><span class="p">)</span>
</span><span class='line'><span class="c1">//=&gt; 500</span>
</span></code></pre></td></tr></table></div></figure>


<p>一开始看起来似乎没什么用，但是，通过部分调用这种方式可以编写可读性更高，更易于理解的代码。举个例子，可以用一种更清晰的方式来代替 <a href="https://www.styled-components.com/docs/basics#adapting-based-on-props">style-components</a> 的函数插入语法。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// before</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">Button</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">button</span><span class="err">`</span>
</span><span class='line'>  <span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="o">:</span> <span class="nx">$</span><span class="p">{({</span> <span class="nx">theme</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">bgColor</span><span class="p">}</span>
</span><span class='line'>  <span class="nx">color</span><span class="o">:</span> <span class="nx">$</span><span class="p">{({</span> <span class="nx">theme</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">textColor</span><span class="p">}</span>
</span><span class='line'><span class="err">`</span>
</span><span class='line'>
</span><span class='line'><span class="o">&lt;</span><span class="nx">Button</span> <span class="nx">theme</span><span class="o">=</span><span class="p">{</span><span class="nx">themes</span><span class="p">.</span><span class="nx">primary</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Submit</span><span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span><span class='line'><span class="c1">// after</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">fromTheme</span> <span class="o">=</span> <span class="p">(</span><span class="nx">prop</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span> <span class="nx">theme</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">theme</span><span class="p">[</span><span class="nx">prop</span><span class="p">]</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">Button</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">button</span><span class="err">`</span>
</span><span class='line'>  <span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="o">:</span> <span class="nx">$</span><span class="p">{</span><span class="nx">fromTheme</span><span class="p">(</span><span class="s2">&quot;bgColor&quot;</span><span class="p">)}</span>
</span><span class='line'>  <span class="nx">color</span><span class="o">:</span> <span class="nx">$</span><span class="p">{</span><span class="nx">fromTheme</span><span class="p">(</span><span class="s2">&quot;textColor&quot;</span><span class="p">)}</span>
</span><span class='line'><span class="err">`</span>
</span><span class='line'>
</span><span class='line'><span class="o">&lt;</span><span class="nx">Button</span> <span class="nx">theme</span><span class="o">=</span><span class="p">{</span><span class="nx">themes</span><span class="p">.</span><span class="nx">primary</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Submit</span><span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>我们创建一个接受一个字符串作为参数的函数 <code>fromTheme("textColor")</code>：它返回一个接受具有 <code>theme</code> 属性的对象的函数：<code>({ theme }) =&gt; theme[prop]</code>，然后再通过初始传入的字符串 <code>"textColor"</code> 进行查找。我们可以做得更多，写类似的 <code>backgroundColor</code> 和 <code>textColor</code> 这种部分调用 <code>fromTheme</code> 的函数：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">fromTheme</span> <span class="o">=</span> <span class="p">(</span><span class="nx">prop</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span> <span class="nx">theme</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">theme</span><span class="p">[</span><span class="nx">prop</span><span class="p">]</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">backgroundColor</span> <span class="o">=</span> <span class="nx">fromTheme</span><span class="p">(</span><span class="s2">&quot;bgColor&quot;</span><span class="p">)</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">textColor</span> <span class="o">=</span> <span class="nx">fromTheme</span><span class="p">(</span><span class="s2">&quot;textColor&quot;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">Button</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">button</span><span class="err">`</span>
</span><span class='line'>  <span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="o">:</span> <span class="nx">$</span><span class="p">{</span><span class="nx">backgroundColor</span><span class="p">}</span>
</span><span class='line'>  <span class="nx">color</span><span class="o">:</span> <span class="nx">$</span><span class="p">{</span><span class="nx">textColor</span><span class="p">}</span>
</span><span class='line'><span class="err">`</span>
</span><span class='line'>
</span><span class='line'><span class="o">&lt;</span><span class="nx">Button</span> <span class="nx">theme</span><span class="o">=</span><span class="p">{</span><span class="nx">themes</span><span class="p">.</span><span class="nx">primary</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Submit</span><span class="o">&lt;</span><span class="err">/Button&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>高阶函数</h2>

<p>高阶函数的定义是，接受函数作为参数的函数。如果曾经使用过类似 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a> 这样的函数，可能已经很熟悉高阶函数。如果不熟悉 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a>，它是一个数组遍历的方法，接受一个函数作为参数应用到数组中的每个元素。例如，可以像这样对一个数组作平方：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">square</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">x</span>
</span><span class='line'>
</span><span class='line'><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span><span class="nx">map</span><span class="p">(</span><span class="nx">square</span><span class="p">)</span>
</span><span class='line'><span class="c1">//=&gt; [ 1, 4, 9 ]</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以实现一个我们自己的 <code>map</code> 版本来说明这个概念：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">map</span> <span class="o">=</span> <span class="p">(</span><span class="nx">fn</span><span class="p">,</span> <span class="nx">array</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">mappedArray</span> <span class="o">=</span> <span class="p">[]</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">array</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">mappedArray</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span>
</span><span class='line'>      <span class="c1">// apply fn with the current element of the array</span>
</span><span class='line'>      <span class="nx">fn</span><span class="p">(</span><span class="nx">array</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
</span><span class='line'>    <span class="p">)</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">return</span> <span class="nx">mappedArray</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>然后再使用我们的 map 版本来对一个数组作平方：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">square</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">x</span>
</span><span class='line'>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">map</span><span class="p">(</span><span class="nx">square</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]))</span>
</span><span class='line'><span class="c1">//=&gt; [ 1, 4, 9, 16, 25 ]</span>
</span></code></pre></td></tr></table></div></figure>


<blockquote><p>译者注：我们也可以将 map 方法从对象中解耦出来：</p>

<pre><code class="javascript">const map = (fn, array) =&gt; Array.prototype.map.call(array, fn)
</code></pre>

<p>这样也可以像上述例子一样调用。
或者更函数式的做法，再来点柯里化：</p>

<pre><code class="javascript">const map = array =&gt; fn =&gt; Array.prototype.map.call(array, fn)
</code></pre></blockquote>

<p>或者是返回一个 <code>&lt;li&gt;</code>的 React 元素数组：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">HeroList</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">heroes</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">ul</span><span class="o">&gt;</span>
</span><span class='line'>    <span class="p">{</span><span class="nx">map</span><span class="p">((</span><span class="nx">hero</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>      <span class="o">&lt;</span><span class="nx">li</span> <span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">hero</span><span class="p">}</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">hero</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/li&gt;</span>
</span><span class='line'>    <span class="p">),</span> <span class="nx">heroes</span><span class="p">)}</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="err">/ul&gt;</span>
</span><span class='line'><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="o">&lt;</span><span class="nx">HeroList</span> <span class="nx">heroes</span><span class="o">=</span><span class="p">[</span>
</span><span class='line'>  <span class="s2">&quot;Wonder Woman&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="s2">&quot;Black Widow&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="s2">&quot;Spider Man&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="s2">&quot;Storm&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="s2">&quot;Deadpool&quot;</span>
</span><span class='line'><span class="p">]</span><span class="o">/&gt;</span>
</span><span class='line'><span class="cm">/*=&gt; (</span>
</span><span class='line'><span class="cm">  &lt;ul&gt;</span>
</span><span class='line'><span class="cm">    &lt;li&gt;Wonder Woman&lt;/li&gt;</span>
</span><span class='line'><span class="cm">    &lt;li&gt;Black Widow&lt;/li&gt;</span>
</span><span class='line'><span class="cm">    &lt;li&gt;Spider Man&lt;/li&gt;</span>
</span><span class='line'><span class="cm">    &lt;li&gt;Storm&lt;/li&gt;</span>
</span><span class='line'><span class="cm">    &lt;li&gt;Deadpool&lt;/li&gt;</span>
</span><span class='line'><span class="cm">  &lt;/ul&gt;</span>
</span><span class='line'><span class="cm">)*/</span>
</span></code></pre></td></tr></table></div></figure>


<h2>高阶组件</h2>

<p>我们知道，高阶函数是接受函数作为参数的函数。在 React 中，任何返回 <a href="https://facebook.github.io/react/docs/jsx-in-depth.html">JSX</a> 的函数都被称为无状态函数组件，简称为函数组件。基本的函数组件如下所示：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">Title</span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/h1&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="o">&lt;</span><span class="nx">Title</span><span class="o">&gt;</span><span class="nx">Higher</span><span class="o">-</span><span class="nx">Order</span> <span class="nx">Components</span><span class="p">(</span><span class="nx">HOCs</span><span class="p">)</span> <span class="k">for</span> <span class="nx">React</span> <span class="nx">Newbies</span><span class="o">&lt;</span><span class="err">/Title&gt;</span>
</span><span class='line'><span class="c1">//=&gt; &lt;h1&gt;Higher-Order Components(HOCs) for React Newbies&lt;/h1&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>高阶组件则是<em>接受组件作为参数并返回组件的函数</em>。如何使用传入组件完全取决于你，甚至可以完全忽视它：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// Technically an HOC</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">ignore</span> <span class="o">=</span> <span class="p">(</span><span class="nx">anything</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;:</span><span class="p">)</span><span class="o">&lt;</span><span class="err">/h1&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">IgnoreHeroList</span> <span class="o">=</span> <span class="nx">ignore</span><span class="p">(</span><span class="nx">HeroList</span><span class="p">)</span>
</span><span class='line'><span class="o">&lt;</span><span class="nx">IgnoreHeroList</span> <span class="o">/&gt;</span>
</span><span class='line'><span class="c1">//=&gt; &lt;h1&gt;:)&lt;/h1&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以编写一个将输入转换成大写的 HOC：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">yell</span> <span class="o">=</span> <span class="p">(</span><span class="nx">PassedComponent</span><span class="p">)</span> <span class="o">=&gt;</span>
</span><span class='line'>  <span class="p">({</span> <span class="nx">children</span><span class="p">,</span> <span class="p">...</span><span class="nx">props</span> <span class="p">})</span> <span class="o">=&gt;</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="nx">PassedComponent</span> <span class="p">{...</span><span class="nx">props</span><span class="p">}</span><span class="o">&gt;</span>
</span><span class='line'>      <span class="p">{</span><span class="nx">children</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">()}</span><span class="o">!</span>
</span><span class='line'>    <span class="o">&lt;</span><span class="err">/PassedComponent&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">Title</span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/h1&gt;</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">AngryTitle</span> <span class="o">=</span> <span class="nx">yell</span><span class="p">(</span><span class="nx">Title</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="o">&lt;</span><span class="nx">AngryTitle</span><span class="o">&gt;</span><span class="nx">Whatever</span><span class="o">&lt;</span><span class="err">/AngryTitle&gt;</span>
</span><span class='line'><span class="c1">//=&gt; &lt;h1&gt;WHATEVER!&lt;/h1&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>你也可以返回一个有状态组件，因为 JavaScript 中的类不过是函数的语法糖。这样就可以使用到 React 生命周期的方法，比如 <code>componentDidMount</code>。这是 HOCs 真正有用的地方。我们现在可以做一些稍微有趣点的事，比如将 HTTP 请求的结果传递给函数组件。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">withGists</span> <span class="o">=</span> <span class="p">(</span><span class="nx">PassedComponent</span><span class="p">)</span> <span class="o">=&gt;</span>
</span><span class='line'>  <span class="kr">class</span> <span class="nx">WithGists</span> <span class="kr">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>      <span class="nx">gists</span><span class="o">:</span> <span class="p">[]</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">componentDidMount</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>      <span class="nx">fetch</span><span class="p">(</span><span class="s2">&quot;https://api.github.com/gists/public&quot;</span><span class="p">)</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">r</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span><span class='line'>      <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">gists</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span>
</span><span class='line'>        <span class="nx">gists</span><span class="o">:</span> <span class="nx">gists</span>
</span><span class='line'>      <span class="p">}))</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">return</span> <span class="p">(</span>
</span><span class='line'>        <span class="o">&lt;</span><span class="nx">PassedComponent</span>
</span><span class='line'>          <span class="p">{...</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">}</span>
</span><span class='line'>          <span class="nx">gists</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">gists</span><span class="p">}</span>
</span><span class='line'>        <span class="o">/&gt;</span>
</span><span class='line'>      <span class="p">)</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">Gists</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">gists</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">&lt;</span><span class="nx">pre</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">gists</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)}</span><span class="o">&lt;</span><span class="err">/pre&gt;</span>
</span><span class='line'><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">GistsList</span> <span class="o">=</span> <span class="nx">withGists</span><span class="p">(</span><span class="nx">Gists</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="o">&lt;</span><span class="nx">GistsList</span> <span class="o">/&gt;</span>
</span><span class='line'><span class="c1">//=&gt; Before api request finishes:</span>
</span><span class='line'><span class="c1">// &lt;Gists gists={[]} /&gt;</span>
</span><span class='line'><span class="c1">//</span>
</span><span class='line'><span class="c1">//=&gt; After api request finishes:</span>
</span><span class='line'><span class="c1">// &lt;Gists gists={[</span>
</span><span class='line'><span class="c1">//  { /* … */ },</span>
</span><span class='line'><span class="c1">//  { /* … */ },</span>
</span><span class='line'><span class="c1">//  { /* … */ }</span>
</span><span class='line'><span class="c1">// ]} /&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>withGists</code> 会传递 gist api 调用的结果，并且你可以在任何组件上使用。<a href="https://codesandbox.io/embed/o2YpJnpDj">点击这里</a> 可以看到一个更加完整的例子。</p>

<h2>结论：高阶组件是 🔥🔥🔥</h2>

<p>react-redux 也是使用 HOC， <a href="https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options">connect</a> 将应用 store 的值传递到“已连接” 的组件。它还会执行一些错误检查和组件生命周期优化，如果手动完成将导致编写大量重复代码。</p>

<p>如果你发现自己在不同地方编写了大量的代码，那么也可以将代码重构成可重用的 HOC。</p>

<p>HOCs 非常具有表现力，可以使用它们创造很多很酷的东西。</p>

<p>尽可能地保持你的 HOC 简单，<em>不要编写需要阅读长篇大论才能理解的代码</em>。</p>

<h3>附加练习</h3>

<p>下面有一些练习，来巩固对 HOC 的理解：</p>

<ul>
<li>写一个反转其输入的 HOC</li>
<li>编写一个HOC，将 API 中的数据提供给组件</li>
<li>写一个HOC来实现 <a href="https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate"><code>shouldComponentUpdate</code></a>，<a href="https://facebook.github.io/react/docs/optimizing-performance.html#avoid-reconciliation">以避免更新</a>。</li>
<li>编写一个 HOC，使用 <a href="https://facebook.github.io/react/docs/react-api.html#react.children.toarray"><code>React.Children.toArray</code></a> 对传入组件子元素进行排序。</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[CSS Animations vs Web Animations API]]></title>
    <link href="https://blog.mirreal.net/blog/2017/06/26/css-animations-vs-web-animations-api-slash/"/>
    <updated>2017-06-26T20:47:08+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/06/26/css-animations-vs-web-animations-api-slash</id>
    <content type="html"><![CDATA[<blockquote><p>作者：Ollie Williams</p>

<p>原文：<a href="https://css-tricks.com/css-animations-vs-web-animations-api/">CSS Animations vs Web Animations API</a></p></blockquote>

<p>在  JavaScript 有一个原生动画 API 叫 Web Animations API，在这篇文章中简称为 WAAPI。MDN 上已经有 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API">很好的文档</a>，而且，Dan Wilson 为此写了 <a href="http://danielcwilson.com/blog/2015/07/animations-part-1/">一个很棒的文章系列</a>。</p>

<p>在本文中，我们一起来对比一下 WAAPI 和 CSS 动画。</p>

<h2>关于浏览器支持</h2>

<p>尽管浏览器原生支持仍然有限，但 WAAPI 有一个全面和强大的 <a href="https://github.com/web-animations/web-animations-js">polyfill</a>，使得现在就能在生产环境使用。</p>

<p>同样地，可以在 <a href="https://github.com/web-animations/web-animations-js">Can I Use</a> 查看浏览器兼容性数据。然而，这并没有提供很好的信息来支持 WAAPI 的所有子功能。这里有一个检查工具：</p>

<p>See the Pen <a href="https://codepen.io/danwilson/pen/xGBKVq/">WAAPI Browser Support Test</a> by Dan Wilson (<a href="https://codepen.io/danwilson">@danwilson</a>) on <a href="https://codepen.io/">CodePen</a>.</p>

<p>要想再没有 polyfill 的情况下体验所有功能，请使用 Firefox Nightly。</p>

<h2>WAAPI 的基础知识</h2>

<p>如果你曾经使用 jQuery  的 <code>.animate()</code>，那么应该会觉得 WAAPI 的基本语法看起来很熟悉。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.animate-me&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">element</span><span class="p">.</span><span class="nx">animate</span><span class="p">(</span><span class="nx">keyframes</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>animate</code> 方法接受两个参数：关键帧和持续时间。与 jQuery 相比的优势是，不仅是浏览器内置，而且性能也更好。</p>

<p>第一个参数，关键帧，是一个对象数组，每个对象都是动画中的一个关键帧。看这个简单的例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">keyframes</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>  <span class="p">{</span> <span class="nx">opacity</span><span class="o">:</span> <span class="mi">0</span> <span class="p">},</span>
</span><span class='line'>  <span class="p">{</span> <span class="nx">opacity</span><span class="o">:</span> <span class="mi">1</span> <span class="p">}</span>
</span><span class='line'><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>第二个参数，持续时间，指的是想要动画持续多久，在上面的例子中是 1000 毫秒。接下来看一个更令人兴奋的例子。</p>

<h2>用 WAAPI 重新创建一个 animista 的 CSS 动画</h2>

<p>这里有一些从 <a href="http://animista.net/">animista</a> 拉取的 CSS 代码，被称为 slide-in-blurred-top 的入场动画。看起来很漂亮</p>

<p>在 <a href="http://animista.net/play/entrances/slide-in-blurred/slide-in-blurred-top">实际PERF</a> 比这个 GIF 效果好很多。</p>

<p>以下是 CSS 中的关键帧：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='css'><span class='line'><span class="nt">0</span><span class="o">%</span> <span class="p">{</span>
</span><span class='line'>  <span class="n">transform</span><span class="o">:</span> <span class="n">translateY</span><span class="p">(</span><span class="m">-1000px</span><span class="p">)</span> <span class="n">scaleY</span><span class="p">(</span><span class="m">2</span><span class="o">.</span><span class="m">5</span><span class="p">)</span> <span class="n">scaleX</span><span class="p">(</span><span class="o">.</span><span class="m">2</span><span class="p">);</span>
</span><span class='line'>  <span class="n">transform</span><span class="o">-</span><span class="n">origin</span><span class="o">:</span> <span class="m">50%</span> <span class="m">0</span><span class="p">;</span>
</span><span class='line'>  <span class="n">filter</span><span class="o">:</span> <span class="n">blur</span><span class="p">(</span><span class="m">40px</span><span class="p">);</span>
</span><span class='line'>  <span class="k">opacity</span><span class="o">:</span> <span class="m">0</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="nt">100</span><span class="o">%</span> <span class="p">{</span>
</span><span class='line'>  <span class="n">transform</span><span class="o">:</span> <span class="n">translateY</span><span class="p">(</span><span class="m">0</span><span class="p">)</span> <span class="n">scaleY</span><span class="p">(</span><span class="m">1</span><span class="p">)</span> <span class="n">scaleX</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
</span><span class='line'>  <span class="n">transform</span><span class="o">-</span><span class="n">origin</span><span class="o">:</span> <span class="m">50%</span> <span class="m">50%</span><span class="p">;</span>
</span><span class='line'>  <span class="n">filter</span><span class="o">:</span> <span class="n">blur</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
</span><span class='line'>  <span class="k">opacity</span><span class="o">:</span> <span class="m">1</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>在 WAAPI 中代码基本相同：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">keyframes</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>  <span class="p">{</span>
</span><span class='line'>    <span class="nx">transform</span><span class="o">:</span> <span class="s1">&#39;translateY(-1000px) scaleY(2.5) scaleX(.2)&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">transformOrigin</span><span class="o">:</span> <span class="s1">&#39;50% 0&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">filter</span><span class="o">:</span> <span class="s1">&#39;blur(40px)&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">opacity</span><span class="o">:</span> <span class="mi">0</span>
</span><span class='line'>  <span class="p">},</span>
</span><span class='line'>  <span class="p">{</span>
</span><span class='line'>    <span class="nx">transform</span><span class="o">:</span> <span class="s1">&#39;translateY(0) scaleY(1) scaleX(1)&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">transformOrigin</span><span class="o">:</span> <span class="s1">&#39;50% 50%&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">filter</span><span class="o">:</span> <span class="s1">&#39;blur(0)&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">opacity</span><span class="o">:</span> <span class="mi">1</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以看出，将关键帧应用到需要动画的元素上是多么容易：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">element</span><span class="p">.</span><span class="nx">animate</span><span class="p">(</span><span class="nx">keyframes</span><span class="p">,</span> <span class="mi">700</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>为了简单起见，只指定了持续时间。但是，我们可以使用这个第二个参数来传递更多的选项，至少也应该指定一个缓动效果。以下是所有可用选项的完整列表，其中包含一些示例值：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">iterations</span><span class="o">:</span> <span class="kc">Infinity</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">iterationStart</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">delay</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">endDelay</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">direction</span><span class="o">:</span> <span class="s1">&#39;alternate&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">duration</span><span class="o">:</span> <span class="mi">700</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">fill</span><span class="o">:</span> <span class="s1">&#39;forwards&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">easing</span><span class="o">:</span> <span class="s1">&#39;ease-out&#39;</span><span class="p">,</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="nx">element</span><span class="p">.</span><span class="nx">animate</span><span class="p">(</span><span class="nx">keyframes</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>加上这些选项，我们的动画将从头开始，没有任何延迟，在动画完成后往返循环播放。</p>

<p>不爽的是，对于熟悉 CSS 动画的人来说，一些术语跟我们习惯的有所不同。好处是打字会更快一些！</p>

<ul>
<li>使用 <code>easing</code> 而不是 <code>animation-timing-function</code></li>
<li>不是 <code>animation-iteration-count</code>，而是 <code>iterations</code>。如果我们希望动画永远重复，使用 <code>Infinity</code> 而不是 <code>infinite</code>。有点混乱， <code>Infinity</code> 不带引号。<code>Infinity</code> 是一个 JavaScript 关键字，而其他值都是字符串。</li>
<li>我们使用毫秒而不是秒，对于之前写过许多 JavaScript 的人来说，这应该是一样的。（你也可以在 CSS 动画中使用毫秒数，但很少有人使用。）</li>
</ul>


<p>我们来仔细看看一个选项：<code>iterationStart</code>。</p>

<p>当我第一次碰到 <code>iterationStart</code> 有点困惑。为什么要从指定的迭代开始，而不是只要减少迭代次数？当使用十进制数时，此选项非常有用。例如，可以将其设置为  <code>.5</code>，动画将开始一半。要做一个完整的动画需要两个一半，所以如果迭代次数设置为 1，并且将 <code>iterationStart</code> 设置为  <code>.5</code>，动画将从一半到动画结束播放，然后从动画开头开始，结束于中间！</p>

<p>值得注意的是，也可以将迭代次数设置为小于 1。例如：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">option</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">iterations</span><span class="o">:</span> <span class="p">.</span><span class="mi">5</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">iterationStart</span><span class="o">:</span> <span class="p">.</span><span class="mi">5</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>这样，动画会从中间开始，一直播放到最后。</p>

<p>endDelay：如果要将多个动画串在一起，但是希望在一个动画的结尾和后续动画的开始之间存在差距，这时 endDelay 就很有用。这是一个有用的视频，由 Patrick Brosset 来解释。</p>

<p><a href="https://www.youtube.com/embed/hWe-qukNrN8">一个 YouTube 的视频</a></p>

<h2>缓动（easing）</h2>

<p>在任何动画中，缓动都是最重要的元素之一。WAAPI 为我们提供了两种不同的方式设置缓动 - 在我们的关键帧数组或我们的选项对象内。</p>

<p>在 CSS 中，如果使用 <code>animation-timing-function: ease-in-out</code> 你可能会认为动画会缓慢开始，然后缓慢结束。实际上，这些缓动应用在关键帧之间，而不是整个动画。这可以对动画的感觉进行细粒度的控制。WAAPI 也提供这种功能。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">keyframes</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>  <span class="p">{</span> <span class="nx">opacity</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">easing</span><span class="o">:</span> <span class="s1">&#39;ease-in&#39;</span> <span class="p">},</span>
</span><span class='line'>  <span class="p">{</span> <span class="nx">opacity</span><span class="o">:</span> <span class="mf">0.5</span><span class="p">,</span> <span class="nx">easing</span><span class="o">:</span> <span class="s1">&#39;ease-out&#39;</span> <span class="p">},</span>
</span><span class='line'>  <span class="p">{</span> <span class="nx">opacity</span><span class="o">:</span> <span class="mi">1</span> <span class="p">}</span>
</span><span class='line'><span class="p">]</span>
</span></code></pre></td></tr></table></div></figure>


<p>值得注意的是，在 CSS 和 WAAPI 中，不应该传入最后一帧的缓动值，因为这将不起作用。可是很多人会犯这种错误。</p>

<p>有时候，在整个动画中添加缓动效果更为直观。这在 CSS 是不可能的，但现在可以在 WAAPI 中实现。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">duration</span><span class="o">:</span> <span class="mi">1000</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">easing</span><span class="o">:</span> <span class="s1">&#39;ease-in-out&#39;</span><span class="p">,</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以看到这两种缓动在 CodePen 上的区别：</p>

<p><a href="https://codepen.io/cssgrid/pen/OmrVeQ">点我查看</a></p>

<h2>缓动 vs 线性</h2>

<p>值得注意的是 CSS 动画和 WAAPI 之间的另一个区别：<strong>在 CSS 中 默认值是 ease，而在 WAAPI 默认是 linear。</strong> ease 实际上是 <code>ease-in-out</code> 的一个版本，当你想偷懒时这是一个非常好的选择。同时，线性代表致命的沉闷和无生命 - 一致的速度看起来机械和不自然。它被选为默认值，可能因为它是最中立的选项。然而，在使用 WAAPI 时，更好是使用缓动，以免动画看起来很乏味和机械。</p>

<h2>性能</h2>

<p>WAAPI 提供与 CSS 动画相同的性能改进，尽管这并不意味着一定就是平滑的动画。</p>

<p>希望这个 API 的性能优化做到，使我们可以避免使用 <code>will-change</code>和 <code>translateZ</code> 成为可能。但是，至少在目前的浏览器实现中，这些属性在处理性能问题方面仍然是有帮助，有必要的。</p>

<p>但是，如果你的动画有延迟，则无需担心使用 <code>will-change</code>。web animations 规范的主要作者对 <a href="https://damp-lake-50659.herokuapp.com/">Animation for Work Slack community</a> 提出了一些有趣的建议，希望他不介意我在这里重复：</p>

<blockquote><p>如果有一个正向的延迟，不需要使用 <code>will-change</code>，因为浏览器将在延迟开始时进行分层，当动画启动时，它将准备就绪。</p></blockquote>

<h2>WAAPI 对战 CSS 动画？</h2>

<p>WAAPI 为我们提供了一套已经在 CSS 中实现的 JavaScript 语法。然而，它们不应该被视为对手。如果我们坚持使用 CSS 完成动画和转换，那么我们可以在 WAAPI 进行动画交互。</p>

<h2>动画对象</h2>

<p><code>.animate()</code> 方法不仅处理元素的动画，它也返回一些东西。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">myAnimation</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">animate</span><span class="p">(</span><span class="nx">keyframes</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>在控制台中查看的动画对象</p>

<p>如果我们在控制台中查看返回值，会发现这是一个动画对象。这为我们提供了各种各样的功能，其中一些是不言自明，比如 <code>myAnimation.pause()</code>。通过更改 <code>animation-play-state</code> 属性，我们可以通过 CSS 动画实现类似的结果，但 WAAPI 语法比 <code>element.style.animationPlayState = "paused"</code> 更简洁。我们也可以通过 <code>myAnimation.reverse()</code> 轻松反转动画，同样地，跟我们使用脚本更改 CSS 的 <code>animation-direction</code> 属性相比，稍微有点进步。</p>

<p>然而，到目前为止，使用 JavaScript 操作 <code>@keyframe</code> 并不是件容易的的事。即使是重新启动动画这样简单的事，也是需要一些技巧的，就像 Chris Coyier <a href="https://css-tricks.com/restart-css-animation/">先前写过的那样</a>。使用 WAAPI，我们可以简单地使用 <code>myAnimation.play()</code> ，如果动画已经完成，将从一开始就重播动画，或者如果我们暂停播放，则从中间迭代播放动画。</p>

<p>我们甚至可以轻松地改变动画的速度。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">myAnimation</span><span class="p">.</span><span class="nx">playbackRate</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// speed it up</span>
</span><span class='line'><span class="nx">myAnimation</span><span class="p">.</span><span class="nx">playbackRate</span> <span class="o">=</span> <span class="p">.</span><span class="mi">4</span><span class="p">;</span> <span class="c1">// use a number less than one to slow it down</span>
</span></code></pre></td></tr></table></div></figure>


<h2>getAnimations()</h2>

<p>这个方法将返回所有动画对象的数组，包含使用 WAAPI 定义的动画和 CSS 转换或动画。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">element</span><span class="p">.</span><span class="nx">getAnimations</span><span class="p">()</span> <span class="c1">// returns any animations or transitions applied to our element using  CSS or WAAPI</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果你喜欢使用 CSS 来定义和使用动画，<code>getAnimations()</code>  允许 API​​ 与 <code>@keyframes</code> 结合使用。你可以继续使用 CSS 进行大部分动画工作，然后在需要 API 时获得使用 API 的优势。</p>

<p>即使一个 DOM 元素只使用到一个动画，<code>getAnimations()</code> 也将始终返回一个数组。我们使用那个单一的动画对象来处理。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">h2</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">&quot;h2&quot;</span><span class="p">);</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">myCSSAnimation</span> <span class="o">=</span> <span class="nx">h2</span><span class="p">.</span><span class="nx">getAnimations</span><span class="p">()[</span><span class="mi">0</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>我们也可以在 CSS 动画中使用 web animation API :)</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">myCSSAnimation</span><span class="p">.</span><span class="nx">playbackRate</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
</span><span class='line'><span class="nx">myCSSAnimation</span><span class="p">.</span><span class="nx">reverse</span><span class="p">();</span>
</span></code></pre></td></tr></table></div></figure>


<h2>Promise 和 Event</h2>

<p>很多通过 CSS 触发的事件，现在我们已经可以使用 JavaScript 代码来完成：   <code>animationstart</code>，<code>animationend</code>，<code>animationiteration</code> 和 <code>transitionend</code>。之前经常需要监听动画或转换的结束，以便从 DOM 中删除应用的元素。</p>

<p>在动画对象可以使用 WAAPI 来完成 <code>animationend</code> 或 <code>transitionend</code> 做的事情：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">myAnimation</span><span class="p">.</span><span class="nx">onfinish</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">element</span><span class="p">.</span><span class="nx">remove</span><span class="p">();</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>WAAPI 为我们提供了两个选择：event 和 promise。动画对象的 <code>.finished</code> 方法会返回一个在动画结束时的 promise。下面这段代码是上面例子的 promise 版本：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">myAnimation</span><span class="p">.</span><span class="nx">finished</span><span class="p">.</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span>
</span><span class='line'>  <span class="nx">element</span><span class="p">.</span><span class="nx">remove</span><span class="p">())</span>
</span></code></pre></td></tr></table></div></figure>


<p>我们来看看来自 Mozilla 开发者网络中的一个稍微复杂点的例子。<code>Promise.all</code> 接受一个 promise 的数组，一旦所有 promise 完成才会运行回调函数。可以看出，<code>element.getAnimations()</code> 返回的是一个动画对象数组。我们可以将数组中的所有动画对象 map 到每个动画对象的 <code>.finished</code>上，这样就获得需要的 promise 数组。</p>

<p>在这个例子中，只有在页面上的所有动画完成后，我们的函数才能运行。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">getAnimations</span><span class="p">().</span><span class="nx">map</span><span class="p">(</span><span class="nx">animation</span> <span class="o">=&gt;</span>
</span><span class='line'>  <span class="nx">animation</span><span class="p">.</span><span class="nx">finished</span><span class="p">)).</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// do something cool</span>
</span><span class='line'>  <span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<h2>未来</h2>

<p>本文中提到的功能只是一个开始。从目前的规范和实施来看，未来会有一个很强大动画 API。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript 异常的防范与监控]]></title>
    <link href="https://blog.mirreal.net/blog/2017/06/21/javascript-stack-trace/"/>
    <updated>2017-06-21T23:12:00+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/06/21/javascript-stack-trace</id>
    <content type="html"><![CDATA[<p>一套完善的前端体系应少不了异常统计与监控，即使有足够的质量保证体系，难免会出现一些意料之外的事，尤其是在复杂的网路环境和运行环境之下。为了保证代码的健壮性以及页面的稳定性，我们从多个方面来做异常的防范和监控。</p>

<h2>三种思路</h2>

<h3>主动防御</h3>

<p>对于我们操作的数据，尤其是由 API 接口返回的，时常会有一个很复杂的深层嵌套的数据结构。为了代码的健壮性，很多时候需要对每一层访问都作空值判断，就像这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">props</span><span class="p">.</span><span class="nx">user</span> <span class="o">&amp;&amp;</span>
</span><span class='line'><span class="nx">props</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">posts</span> <span class="o">&amp;&amp;</span>
</span><span class='line'><span class="nx">props</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">posts</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&amp;&amp;</span>
</span><span class='line'><span class="nx">props</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">posts</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">comments</span> <span class="o">&amp;&amp;</span>
</span><span class='line'><span class="nx">props</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">posts</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">comments</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></code></pre></td></tr></table></div></figure>


<p>类似的代码大家可能都写过，没写过大概也见到别人写过。看起来确实相当地不美观，有句话说得很棒：</p>

<p><strong>The opposite of beautiful is not ugly, but wrong.</strong></p>

<p>我们得找到一种，更简单、更优雅、更安全的方式来处理这种情形。参考这篇文章：<a href="https://medium.com/javascript-inside/safely-accessing-deeply-nested-values-in-javascript-99bf72a0855a">Safely Accessing Deeply Nested Values In JavaScript</a>，文章提到借助 Ramda、Lenses、Lodash 以及 Immutable.js 等类库的方式，并提供一个非常简洁明了的原生解决方案：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">function</span> <span class="nx">getIn</span><span class="p">(</span><span class="nx">p</span><span class="p">,</span> <span class="nx">o</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">p</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">xs</span><span class="p">,</span> <span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="p">(</span><span class="nx">xs</span> <span class="o">&amp;&amp;</span> <span class="nx">xs</span><span class="p">[</span><span class="nx">x</span><span class="p">])</span> <span class="o">?</span> <span class="nx">xs</span><span class="p">[</span><span class="nx">x</span><span class="p">]</span> <span class="o">:</span> <span class="kc">null</span><span class="p">;</span>
</span><span class='line'>    <span class="p">},</span> <span class="nx">o</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>接下来我们这样访问就可以了：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">getIn</span><span class="p">([</span><span class="s1">&#39;user&#39;</span><span class="p">,</span> <span class="s1">&#39;posts&#39;</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s1">&#39;comments&#39;</span><span class="p">],</span> <span class="nx">props</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果正常访问到，则返回对应的值，否则返回 <code>null</code>。</p>

<p>这里提供的只是主动防御的一种情形，关于如何编写更安全的代码这里不作深入展开。</p>

<h3>全局监控</h3>

<p>浏览器提供 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalEventHandlers/onerror"><code>window.onerror</code></a> API 来帮助我们进行全局的错误监控：</p>

<ul>
<li>当 JavaScript 运行时错误（包括语法错误）发生时，会执行 `window.onerror()&#8220;</li>
<li>当一项资源（如 <code>&lt;img&gt;</code> 或 <code>&lt;script&gt;</code> ）加载失败，能被单一的 <code>window.addEventListener</code> 捕获</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="cm">/**</span>
</span><span class='line'><span class="cm"> * @param  {String} message 错误信息</span>
</span><span class='line'><span class="cm"> * @param  {String} source  发生错误的脚本URL</span>
</span><span class='line'><span class="cm"> * @param  {Number} lineno  发生错误的行号</span>
</span><span class='line'><span class="cm"> * @param  {Number} colno   发生错误的列号</span>
</span><span class='line'><span class="cm"> * @param  {Object} error   Error对象</span>
</span><span class='line'><span class="cm"> */</span>
</span><span class='line'><span class="nb">window</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">source</span><span class="p">,</span> <span class="nx">lineno</span><span class="p">,</span> <span class="nx">colno</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// ...</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>其中 error 对象包含详细的错误堆栈信息，在 IE9 以前，没有这个参数。</p>

<h3>针对性捕获 try..catch</h3>

<p>可以通过 try..catch 来主动抓取错误，想要对一段代码 try..catch，我们可以这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="k">try</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// ...</span>
</span><span class='line'><span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">handler</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>对一个函数做 try..catch 封装:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">tryify</span><span class="p">(</span><span class="nx">func</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">try</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">return</span> <span class="nx">func</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">)</span>
</span><span class='line'>    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>      <span class="nx">handleError</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>      <span class="k">throw</span> <span class="nx">error</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h2>为什么是 script error</h2>

<p>方案已经明确，但还有一些问题。在查看 JavaScript 错误统计时，发现 80% 以上都是 &ldquo;script error&#8221;。原来，当加载自不同域的脚本中发生语法错误时，为避免信息泄露，语法错误的细节将不会报告，而代之简单的 &#8220;Script error.&rdquo;</p>

<p>而在大多数情况下，我们的静态资源放在专门的 CDN 服务器上，跟站点并不在一个域，所以如果只是简单的抓取，只会得到一堆意义不大的 <code>script error</code></p>

<p>解决方案：</p>

<ul>
<li>添加 CORS 支持</li>
<li>使用 try..catch</li>
</ul>


<h3>添加 CORS 支持</h3>

<p>需要做两点：</p>

<p>1.在 script 便签添加 crossorigin，默认值 <code>crossorigin="anonymous"</code></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;//xxx.com/example.js&quot;</span> <span class="na">crossorigin</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>在 require.js 里提供一个 <a href="https://github.com/requirejs/requirejs/blob/e97fe4dd894d8a07712156e17cefa28b954a9c3e/require.js#L1946">onNodeCreated hook</a>，供我们提供扩展，要添加 crossorigin 属性，如下所示：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'><span class="c1">// 如果在 require.js 加载之前定义了 requirejs，require.js 会将其作为一个对象传入 requirejs.config</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">requirejs</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">onNodeCreated</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">node</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">&#39;crossorigin&#39;</span><span class="p">,</span> <span class="s1">&#39;anonymous&#39;</span><span class="p">)</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;//xxx.com/require.js&quot;</span> <span class="na">charset=</span><span class="s">&quot;utf-8&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>在 2.2.0 版本以上可用（很遗憾的是，目前的集成解决方案版本刚好低于这个版本）。</p>

<p>2.同时在 CDN 服务器增加响应头 <code>access-control-allow-orgin</code>，配置允许访问 CORS 的域，否则浏览器直接将禁止加载。</p>

<h3>try..catch</h3>

<p>这一点上面也有提到，算是一种比较通用，可定制强的方案。当然，在性能上也会有一些损耗。</p>

<p>综合考虑，try..catch 通用性更好，但由于其在性能方面的一些损耗，CORS 优于 try..catch</p>

<h2>一个监控小工具</h2>

<p>随后，介绍一个 JavaScript stack trace 的小工具：<a href="https://github.com/CurtisCBS/monitor">https://github.com/CurtisCBS/monitor</a> ，工具由 <a href="https://github.com/CurtisCBS">Curtis</a> 和 <a href="https://github.com/mirreal">mirreal</a> 共同完成。</p>

<p>主要是用于捕获页面 JavaScript 异常报错，捕获异常类型包含:</p>

<ul>
<li>JavaScript runtime 异常捕捉 √</li>
<li>静态资源 load faided 异常捕捉 √</li>
<li>console.error 的异常捕获 √</li>
<li>try..catch 错误捕获 √</li>
</ul>


<p>使用方式也很简单，但使用 script mode 引入文件后，调用 init 函数，进行初始化配置和监听</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;//unpkg.com/jstracker@latest/dist/jstracker.js&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'>  <span class="nx">jstracker</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
</span><span class='line'>    <span class="nx">delay</span><span class="o">:</span> <span class="mi">1000</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">maxError</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">sampling</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">report</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">errorLogs</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>      <span class="nx">console</span><span class="p">.</span><span class="nx">table</span><span class="p">(</span><span class="nx">errorLogs</span><span class="p">)</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>  <span class="p">})</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果是使用 module mode，如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// npm install jstracker --save</span>
</span><span class='line'><span class="kr">import</span> <span class="nx">jstracker</span> <span class="nx">from</span> <span class="s1">&#39;jstracker&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">jstracker</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
</span><span class='line'>  <span class="nx">concat</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">report</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">errorLogs</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// console.log(&#39;send&#39;)</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果要使用 try..catch 捕获，jstracker 暴露出一个 <code>tryJS</code> 对象，可以处理 try..catch 包装，就像这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">import</span> <span class="nx">jstracker</span> <span class="nx">from</span> <span class="s1">&#39;jstracker&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">this</span><span class="p">.</span><span class="nx">handleSelect</span> <span class="o">=</span> <span class="nx">jstracker</span><span class="p">.</span><span class="nx">tryJS</span><span class="p">.</span><span class="nx">wrap</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">handleSelect</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>所有错误信息统一由 report 函数处理，可以在此之上做数据处理：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// ubt.js</span>
</span><span class='line'><span class="kr">import</span> <span class="nx">jstracker</span> <span class="nx">from</span> <span class="s1">&#39;jstracker&#39;</span><span class="p">;</span>
</span><span class='line'><span class="kr">import</span> <span class="nx">utility</span> <span class="nx">from</span> <span class="s1">&#39;utility&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">jstracker</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
</span><span class='line'>    <span class="nx">concat</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">report</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">errorLogs</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="kr">const</span> <span class="nx">errorLog</span> <span class="o">=</span> <span class="nx">errorLogs</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>        <span class="nx">errorLog</span><span class="p">.</span><span class="nx">ua</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">navigator</span><span class="p">.</span><span class="nx">userAgent</span><span class="p">;</span>
</span><span class='line'>        <span class="nx">ubtTracker</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">errorLog</span><span class="p">);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">ubtTracker</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">key</span><span class="o">:</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">UBT_JS_TRACKER</span><span class="o">:</span> <span class="s1">&#39;xxxx-xxxx-xxxx&#39;</span>
</span><span class='line'>    <span class="p">},</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">send</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nb">window</span><span class="p">[</span><span class="s1">&#39;__bfi&#39;</span><span class="p">]</span> <span class="o">===</span> <span class="s1">&#39;undefined&#39;</span><span class="p">)</span> <span class="nb">window</span><span class="p">[</span><span class="s1">&#39;__bfi&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[];</span>
</span><span class='line'>
</span><span class='line'>        <span class="kr">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">utility</span><span class="p">.</span><span class="nx">deserializeUrl</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span><span class='line'>        <span class="nb">window</span><span class="p">[</span><span class="s1">&#39;__bfi&#39;</span><span class="p">].</span><span class="nx">push</span><span class="p">([</span><span class="s1">&#39;_tracklog&#39;</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">key</span><span class="p">.</span><span class="nx">UBT_JS_TRACKER</span><span class="p">,</span> <span class="nx">value</span><span class="p">]);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="kd">function</span> <span class="nx">wrapContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">func</span> <span class="k">in</span> <span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">ctx</span><span class="p">[</span><span class="nx">func</span><span class="p">]</span> <span class="o">=</span> <span class="nx">jstracker</span><span class="p">.</span><span class="nx">tryJS</span><span class="p">.</span><span class="nx">wrap</span><span class="p">(</span><span class="nx">ctx</span><span class="p">[</span><span class="nx">func</span><span class="p">]);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kr">export</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">wrapContext</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">ubtTracker</span><span class="p">,</span>
</span><span class='line'>    <span class="nx">jstracker</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<h2>概述</h2>

<p>作为开发者以及项目维护者的身份，我们应当编写更安全健壮的代码。但由于环境的多样性，无论再完善的测试，code review 都难免都所疏漏，我们需要一套监控系统来完善整个前端体系。</p>

<p>在监控的时候，出于同源安全策略无法拿到准确的错误信息，在此，有两种解决方案：</p>

<ul>
<li>增加 CORS 支持</li>
<li>使用 try..catch 进行异常捕获</li>
</ul>


<p>最后，我们对整个监控工作封装了一个基础的核心，可以监控 JavaScript Runtime 异常，资源加载异常，以及 try..catch 捕获异常等，并给出一个实际工作中的示例。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[喜欢用 Git 做的一些小事]]></title>
    <link href="https://blog.mirreal.net/blog/2017/06/12/little-things-i-like-to-do-with-git/"/>
    <updated>2017-06-12T18:00:00+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/06/12/little-things-i-like-to-do-with-git</id>
    <content type="html"><![CDATA[<blockquote><p>作者：<a href="https://twitter.com/csswizardry">@csswizardry</a></p>

<p>原文：<a href="https://csswizardry.com/2017/05/little-things-i-like-to-do-with-git/">Little Things I Like to Do with Git</a></p>

<p>随便说点：这篇文章主要从管理者的角度谈论了使用 git 的心得，使用大量篇幅介绍 <code>git log</code> 的一些方法和技巧。</p>

<p>同样地，发现很多人其实并没有深入全面地去了解过 git 的用法，作为一名开发人员，大多数时候只要会使用 <code>git pull</code>，<code>git add</code>，<code>git commit</code>，<code>git push</code> 似乎就足够，还有很大一部分人只使用特定的图形化工具。但事实上真的是这样吗，可能在遇到某个稍微高级一点的问题或者需求就手足无措。对于某个特定个体而言，很多场景我们未必会遇到，即使碰到也可以现场寻求搜索工具的帮助，这也是一种学习方式，无意否定这种方式，但最大的问题就是只见一叶而难以窥见森林。在这里推荐一个小工具 <a href="https://github.com/Gazler/githug">githug</a>，通过一种比较轻松的游戏的方式来一探全貌。</p></blockquote>

<p>在跟我的朋友 Tim 聊天的时候，谈到我有多喜欢 Git。作为经常使用的一个工具，它强大而优雅。在这里，介绍一下我个人使用得最多，同时也是最有用的一些小技巧。</p>

<h2>管理者面板</h2>

<p>无论你认为在工作中的游戏化（gamification）和竞争是好是坏，对于这个话题在不同的时间可能是完全不同的结论。但如果你对团队成员在项目中的提交数量感兴趣，使用 <code>shortlog</code> 就可以找到答案：</p>

<p>（请忽略我）</p>

<p>（我只是占位的）</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git shortlog -sn
</span><span class='line'>    <span class="m">80</span>  Harry Roberts
</span><span class='line'>    <span class="m">34</span>  Samantha Peters
</span><span class='line'>     <span class="m">3</span>  Tom Smith
</span></code></pre></td></tr></table></div></figure>


<p><code>shortlog</code> 可以视作对 <code>git log</code> 的概要。</p>

<ul>
<li><code>-s</code>选项将隐藏提交描述，仅提供提交计数摘要</li>
<li><code>-n</code> 选项将根据每个作者的提交数对输出进行排序，而不是默认的按作者字母顺序。</li>
</ul>


<p>上面显示的是项目生命周期的所有提交，但是如果想查看在特定时间内的情况，可以使用 <code>--since</code> 和 <code>--until</code> 选项：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git shortlog -sn --since<span class="o">=</span><span class="s1">&#39;10 weeks&#39;</span> --until<span class="o">=</span><span class="s1">&#39;2 weeks&#39;</span>
</span><span class='line'>    <span class="m">59</span>  Harry Roberts
</span><span class='line'>    <span class="m">24</span>  Samantha Peters
</span></code></pre></td></tr></table></div></figure>


<p>我为此配置了别名 <code>$ git stats</code></p>

<h2>责任人</h2>

<p>Git 有一个非常有用的 <code>blame</code> 功能，允许我们查看特定代码段的负责开发人员：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="c"># See who last changed lines 5 through 10 of the buttons’ CSS:</span>
</span><span class='line'><span class="nv">$ </span>git blame -L5,10 _components.buttons.scss
</span></code></pre></td></tr></table></div></figure>


<p>这一条放在这里讲好像有点过头，像是我们在找开发人员哪些地方做错了。但也不完全是这样，另一方面，他们可能已经做了一些我们想要了解的特别厉害或是印象深刻的事情。我们原本会问，哇！我之前还没有看到这个功能，还想知道是谁做的。</p>

<p>由于是从 SVN 转到 Git，我使用 <code>praise</code> 作为 <code>blame</code> 的别名，这样二者都可以使用：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git config --global alias.praise blame
</span></code></pre></td></tr></table></div></figure>


<p>即，我也可以这样做：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="c"># Find out who implemented Resource Hints and buy them a coffee:</span>
</span><span class='line'><span class="nv">$ </span>git praise -L18,23 _includes/head.html
</span></code></pre></td></tr></table></div></figure>


<p>只是一点小变化，但效果不错。</p>

<h2>隐藏空白提示</h2>

<p>当使用 <code>diff</code> 或 <code>show</code> 查看具有大量空白变化的版本对比时，会有很多视觉噪音干扰我们，使得很难看到更重要的变化内容。</p>

<p>幸运的是，去除这种空白提示非常容易，在 <code>git diff</code> 和 <code>git show</code> 使用 <code>-w</code> 选项就可以轻松搞定。比如，之前：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'> a <span class="o">{</span>
</span><span class='line'>   color: <span class="nv">$color</span>-links<span class="p">;</span>
</span><span class='line'>
</span><span class='line'>-<span class="p">&amp;</span>:hover <span class="o">{</span>
</span><span class='line'>-  color: <span class="nv">$color</span>-links-hover<span class="p">;</span>
</span><span class='line'>-<span class="o">}</span>
</span><span class='line'>+  <span class="p">&amp;</span>:hover <span class="o">{</span>
</span><span class='line'>+    color: <span class="nv">$color</span>-links-hover<span class="p">;</span>
</span><span class='line'>+    text-decoration: underline<span class="p">;</span>
</span><span class='line'>+  <span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用 <code>-w</code> 之后：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'> a <span class="o">{</span>
</span><span class='line'>   color: <span class="nv">$color</span>-links<span class="p">;</span>
</span><span class='line'>
</span><span class='line'>   <span class="p">&amp;</span>:hover <span class="o">{</span>
</span><span class='line'>     color: <span class="nv">$color</span>-links-hover<span class="p">;</span>
</span><span class='line'>+    text-decoration: underline<span class="p">;</span>
</span><span class='line'>   <span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>现在可以很容易看出，唯一有意义的变化是增加了 <code>text-decoration: underline;</code>，而其余​​的 <code>diff</code> 是有点误导性的。</p>

<h2>仅显示单词的变化而不是整行</h2>

<p>写代码跟写文章不同，查看变化的单词而不是整行通常会更有用; 这在编辑 markdown 文档时尤其有用，就像现在。</p>

<p>幸运的是，我们只要使用 <code>--word-diff</code> 选项就能显示单词的变化：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git diff --word-diff
</span></code></pre></td></tr></table></div></figure>


<p>跟不使用 <code>--word-diff</code> 选项的区别还是很大的：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>-My friend Tom recently gave an excellent talk
</span><span class='line'>+My good friend Tom gave an excellent talk
</span></code></pre></td></tr></table></div></figure>


<p>如果启用 <code>--word-diff</code>，我们能得到更便于理解和更有用的概览：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>My <span class="o">{</span>+good+<span class="o">}</span> friend Tom <span class="o">[</span>-recently-<span class="o">]</span> gave an excellent talk
</span></code></pre></td></tr></table></div></figure>


<p>注意只有变化的文本被突出显示（通过 <code>{+ +}</code> 和 <code>[- -]</code>)</p>

<h2>查看最近工作的分支</h2>

<p>在任何给定的项目，在许多不同的分支之间切换是很常见的，并且跟踪它们可能相当棘手。我们可以让 Git 帮助我们解决这个问题：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git <span class="k">for</span>-each-ref --count<span class="o">=</span><span class="m">10</span> --sort<span class="o">=</span>-committerdate refs/heads/ --format<span class="o">=</span><span class="s2">&quot;%(refname:short)&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>通过这个命令可以知道最近在工作的 10（&ndash;count=10）个分支，按照上次工作的时间排序。只显示本地分支（<code>refs/heads/</code>），并通过 <code>--format</code> 选项获得更友好的呈现方式。</p>

<p>这是一个有点冗长的命令，所以我为此配置别名 <code>$ git recent</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>git config --global alias.recent <span class="s2">&quot;for-each-ref --count=10 --sort=-committerdate refs/heads/ --format=\&quot;%(refname:short)\&quot;&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>看到每个人都在做什么</h2>

<p>有时候，特别是对于团队领导，了解团队成员在所有分支的行为概览是很有用的。再一次地，Git 可以让这一切变得很容易：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --all --oneline --no-merges
</span></code></pre></td></tr></table></div></figure>


<p>这可以得到一份关于所有人的日志报告简化版（带有 <code>--no-merges</code> 选项）</p>

<p>我们也可以通过 <code>--since</code> 选项来限制返回的提交数量：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --all --since<span class="o">=</span><span class="s1">&#39;2 weeks&#39;</span> --oneline --no-merges
</span></code></pre></td></tr></table></div></figure>


<p>这样我们可以看到，在过去的两个星期里，每个人都在做什么。</p>

<p>可以配置一个别名 <code>$ git overview</code></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>git config --global alias.overview <span class="s2">&quot;log --all --since=&#39;2 weeks&#39; --oneline --no-merges&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>提醒你自己已经做了什么</h2>

<p>当你回到一个比较旧的项目，或是在长时间休息之后回到办公室，可能不知道你最后在做什么工作，这种情况时常发生。我们可以通过 Git 轻松获得我们在项目中的工作情况：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --all --oneline --no-merges --author<span class="o">=</span>&lt;your email address&gt;
</span></code></pre></td></tr></table></div></figure>


<p>和上一条很类似，只是我们将日志限制于我们自己的提交，也可以增加 <code>--since</code> 限制。</p>

<p>这也有一个别名 <code>$ git recap</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>git config --global alias.recap <span class="s2">&quot;log --all --oneline --no-merges --author=name@mail.com&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>今天的工作</h2>

<p>同样地，不在这里讨论如何衡量开发人员的生产力，但我觉得让客户知道我在任何一天的工作情况是很有用的。不是要你保留完成任务的详细列表，我们可以使用 Git 获取所有这些信息：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --since<span class="o">=</span>00:00:00 --all --no-merges --oneline --author<span class="o">=</span>&lt;your email address&gt;
</span></code></pre></td></tr></table></div></figure>


<p>这将记录（<code>log</code>） 你工作的所有（<code>--all</code>）分支，谁（<code>--author</code>）从（<code>--since</code>）午夜开始都做了什么，（不包括合并提交 <code>--no-merges</code>），并提供一个简单的一行 （<code>--oneline</code>） 概述。</p>

<p>我有这个别名 <code>$ git today</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>git config --global alias.today <span class="s2">&quot;log --since=00:00:00 --all --no-merges --oneline --author=name@mail.com&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>生成更改日志</h2>

<p>维护一份 CHANGELOG 可能有点乏味，我们必须查看自上次发布以来所做的所有工作，然后提取其中有用的部分。幸运的是，我们可以使用 Git 来给我们一个好的开头：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --oneline --no-merges &lt;last tag&gt;..HEAD
</span></code></pre></td></tr></table></div></figure>


<p>注意：<code>HEAD</code> 是可选的，如果你省略（即&hellip; &ndash;no-merges <last tag>..），<code>HEAD</code> 会是隐含的，当然这样可以节省几次敲击键盘的时间。</p>

<p>这将创建一个简化的日志，显示最后一个发布版本和 <code>HEAD</code> 之间的所有提交（不包括合并提交）。</p>

<p>例如：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --oneline --no-merges 1.0.0..
</span><span class='line'>1257b95 <span class="o">[</span>refs <span class="c">#00019] Bump version</span>
</span><span class='line'>2b9b28e <span class="o">[</span>refs <span class="c">#00019] Add auto width class</span>
</span><span class='line'>17b8eb1 <span class="o">[</span>refs <span class="c">#00015] Tidy up README.md</span>
</span><span class='line'>bbe7d05 <span class="o">[</span>refs <span class="c">#00012] Rename Supercell main mixin</span>
</span></code></pre></td></tr></table></div></figure>


<p>这告诉我，自从上次发布（1.0.0）到当前项目状态（<code>HEAD</code>），已经完成哪些工作。这对于 CHANGELOG 来说是一个很好的参考。</p>

<p>注意：不仅仅适用于 tag，还可以使用提交哈希。</p>

<h2>检查需要拉取哪些变化</h2>

<p>如果你在一段时间内不在项目，可能需要先检查上游的变更，然后再将这些更新下载到本地分支。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --oneline --no-merges HEAD..&lt;remote&gt;/&lt;branch&gt;
</span></code></pre></td></tr></table></div></figure>


<p>注意：同样地，<code>HEAD</code> 在这里是可选的，省略将使其隐含。</p>

<p>例如，让我们来看看你在度假时在特性分支做了什么：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git checkout feature/fonts
</span><span class='line'><span class="nv">$ </span>git fetch
</span><span class='line'><span class="nv">$ </span>git log --oneline --no-merges ..origin/feature/fonts
</span></code></pre></td></tr></table></div></figure>


<p>我使用这个别名 <code>$ git upstream</code>。</p>

<h2>检查即将上传的内容</h2>

<p>最好的情况是可以经常提交和上传，但如果某种原因导致有大量的本地提交尚未上传，可以快速回顾一下都是什么。</p>

<p>为了做到这一点，我们反转之前的命令就能轻松实现：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --oneline --no-merges &lt;remote&gt;/&lt;branch&gt;..HEAD
</span></code></pre></td></tr></table></div></figure>


<p>例如：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git fetch
</span><span class='line'><span class="nv">$ </span>git log --oneline --no-merges origin/feature/fonts..HEAD
</span></code></pre></td></tr></table></div></figure>


<p>注意：同样地，<code>HEAD</code> 在这里是可选的，省略将使其隐含。</p>

<p>这将记录 <code>HEAD</code> 需要上传到 <code>&lt;remote&gt;/&lt;branch&gt;</code> 的提交。</p>

<p>我使用这个别名 <code>$ git local</code>。</p>

<h2>查看复杂日志</h2>

<p>上面的每一个例子都使用简化的日志，因为只想快速了解发生了什么。对于更多细节，我使用带有 <code>--graph</code> 选项的日志和一些额外的选项：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'><span class="nv">$ </span>git log --graph --all --decorate --stat --date<span class="o">=</span>iso
</span></code></pre></td></tr></table></div></figure>


<p>这将给出所有（<code>--all</code>）分支基于 <code>--graph</code> 的提交记录 <code>--stat</code>（添加，删除）日志。<code>--decorate</code> 选项会告诉我们提交信息适用于那些分支，还包含一个更加严格的日期格式。</p>

<p>我使用这个别名 <code>$ git graph</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>git config --global alias.graph <span class="s2">&quot;log --graph --all --decorate --stat --date=iso&quot;</span>
</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[移除在 ESM 模式生成的严格模式]]></title>
    <link href="https://blog.mirreal.net/blog/2017/06/05/remove-strict-node/"/>
    <updated>2017-06-05T18:00:00+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/06/05/remove-strict-node</id>
    <content type="html"><![CDATA[<h2>起源</h2>

<p>项目升级构建方式，以前的代码采用 AMD 风格组织代码，并使用 <code>r.js</code> 完成打包优化工作。后续成员选择采用 ES6 的风格编写代码，然后 webpack + babel 完成打包构建。迁移工作并不如想象中那么容易，终于完成各种配置，测试时候发现问题：抛出一个语法错误。错误很快被定位，属于历史代码的不规范写法，之所以现在暴露出来是因为新的构建方式会加入 <code>"use strict"</code>。</p>

<h2>严格模式</h2>

<p>在编写代码的过程中，我们使用 <code>strict mode</code> 是一种很好的方式，严格模式会将 JavaScript 陷阱直接变成明显的错误，比如未声明导致的全局变量，可以让我们开发过程中就发现错误。</p>

<p>众所周知，JavaScript 这门语言之前存在大量不好的设计，使用严格模式意味着在采用一种限制性更高的方式编写代码，同时更 “安全”。但这里所谓的安全在生产环境又可能是另外一回事，尤其是对于那些上了年纪的历史代码，我们更倾向于在生产环境去除严格模式。</p>

<h2>ES2015+</h2>

<p>现在大部分人已经在使用更强大，更具有表现力的 <code>ES2015+</code> 编写代码。可能还会使用到 babel 来转化成用于生产环境运行的代码，在使用 <code>preset es2015</code> 时，会自动加入 <code>“use strict”</code>。</p>

<p>babel 的处理方式是将 ES2015 模块转换成 <code>CommonJS</code> 格式的，然后再统一处理，即 <code>transform-es2015-modules-commonjs</code>，这个插件位于 <code>preset es2015</code> 中，并且依赖于 <code>transform-strict-mode</code></p>

<h2>也许你会想去掉严格模式</h2>

<p>也许你会想去掉严格模式，毕竟对于一些历史代码，很难预测加入严格模式会导致什么异常。</p>

<!-- 可以从两个方面来考虑： -->


<ul>
<li>第一，在处理过程中去掉 <code>"use strict"</code>，这里不用我们自己去写了，借助这个插件 <code>transform-remove-strict-mode</code></li>
<li>第二，不用 <code>transform-es2015-modules-commonjs</code>，可以使用 <code>webpack 2</code> 直接处理 ES6 模块</li>
</ul>


<h3>方案一</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>npm install babel-plugin-transform-remove-strict-mode --save-dev
</span></code></pre></td></tr></table></div></figure>


<p>修改 <code>.babelrc</code></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='json'><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="nt">&quot;presets&quot;</span><span class="p">:</span> <span class="p">[</span>
</span><span class='line'>        <span class="p">[</span><span class="s2">&quot;es2015&quot;</span><span class="p">],</span>
</span><span class='line'>        <span class="s2">&quot;stage-0&quot;</span><span class="p">,</span>
</span><span class='line'>        <span class="s2">&quot;react&quot;</span>
</span><span class='line'>    <span class="p">],</span>
</span><span class='line'>    <span class="nt">&quot;plugins&quot;</span><span class="p">:</span> <span class="p">[</span>
</span><span class='line'>        <span class="s2">&quot;transform-remove-strict-mode&quot;</span>
</span><span class='line'>    <span class="p">]</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h3>方案二</h3>

<blockquote><p><a href="https://babeljs.io/docs/plugins/preset-es2015/#optionsmodules">modules</a></p>

<p><code>"amd" | "umd" | "systemjs" | "commonjs" | false</code>, defaults to <code>"commonjs"</code>.</p>

<p>Enable transformation of ES6 module syntax to another module type.</p>

<p>Setting this to <code>false</code> will not transform modules.</p></blockquote>

<p>我们去掉 <code>preset es2015</code> 的模块处理，改由 <code>webpack</code> 来处理：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='json'><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="nt">&quot;presets&quot;</span><span class="p">:</span> <span class="p">[</span>
</span><span class='line'>        <span class="p">[</span><span class="s2">&quot;es2015&quot;</span><span class="p">,</span> <span class="p">{</span> <span class="nt">&quot;modules&quot;</span><span class="p">:</span> <span class="kc">false</span> <span class="p">}],</span>
</span><span class='line'>        <span class="s2">&quot;stage-0&quot;</span><span class="p">,</span>
</span><span class='line'>        <span class="s2">&quot;react&quot;</span>
</span><span class='line'>    <span class="p">]</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>这种方案也能解决之前问题，因为之前存在问题的模块使用 ES5 编写的，使用 AMD 风格。</p>

<p>但是，参考 <a href="http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code">http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code</a> ，在 ES6 语法下，模块系统以及 class 等仍然都是工作在 <code>strict mode</code> 下的，而 webpack2 的也确实是这样处理的。</p>

<p>实现对语言本身是没问题的，使用新语法意味着我们要抛弃掉以前一些设计不好的地方，编写高质量的代码。</p>

<h3>对比</h3>

<p>相比而言，方案二更合适，因为方案一处理的过程实际是删除所有<code>"use strict"</code>，有时候未必符合你本来的意愿。</p>

<p>采用方案二还有一些好处，比如可以使用 <code>webpack2</code> 已经支持的 <code>tree shaking</code> 优化技术，因为这项技术基于 <code>ES6 Modules</code>，得让 <code>webpack</code> 直接处理才能使用。</p>

<h2>后续</h2>

<p>这种属于历史遗留问题，比如不小心引入的全局变量，类似的代码质量问题还不少。</p>

<p>所以，我们使用 lint 工具来帮助我们避免这种问题，对于代码质量的要求必须苛刻。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[编写现代 JavaScript 代码]]></title>
    <link href="https://blog.mirreal.net/blog/2017/06/02/writing-modern-javascript-code/"/>
    <updated>2017-06-02T18:00:00+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/06/02/writing-modern-javascript-code</id>
    <content type="html"><![CDATA[<blockquote><p>原文作者：<a href="https://twitter.com/scastiel">Sébastien Castiel</a></p>

<p>原文链接：<a href="https://dev.to/scastiel/writing-modern-javascript-code">Writing modern JavaScript code</a></p>

<p>说点什么：这是一篇很朴素的文章，讲的道理都懂，但实际上，在工作中遇到类似的情形却未必如此，编写可维护，可阅读，更安全的代码是我们应有的责任。</p></blockquote>

<p>是不是还认为 JavaScript 是一门用于在光标悬浮时改变页面元素的语言？这些日子已经不复存在，每一种语言都在随着时间推移而发展，我们使用语言的方式同样也在发展。看一下你一两年前写的代码：会感到羞愧吗？如果是的话，这篇文章应该很适合你。</p>

<p>这里会列出一些所谓的最佳实践，目的是让你的 JavaScript 代码更容易编写，阅读和维护。</p>

<h2>使用可格式化代码的 linter</h2>

<p>第一个建议是使用 linter 工具，可以帮助你检查在不同文件是否遵守一致的规则，尤其是当不同开发人员在同一个项目上工作：缩进，括号中的空格，替换 <code>==</code> 为 <code>===</code> &hellip;</p>

<p>但更重要的是，尽可能使用 linter 工具自动修复代码。<a href="http://eslint.org/">ESLint</a> 就做得很好（带有 <code>--fix</code> 选项），而且与所有主流 IDE 完美集成，可以在保存时自动修复文件。</p>

<p>还可以使用 <a href="https://github.com/prettier/prettier">Prettier</a>，不过这款工具更注重格式化而不是静态检查，但处理后的结果基本相同。</p>

<p>下一步将介绍与 linter 工具一起使用的规则：</p>

<h2>为你的 linter 定制现代化的规则</h2>

<p>如果不知道你的代码需要什么样的规则，可以参考：<a href="https://standardjs.com/">StandardJS</a>。这是一个<strong>非常</strong>严格的 linter，无法修改配置，但里面的每一条规则已经越来越多地被社区接纳。比如：</p>

<ul>
<li>使用 2 个空格缩进（我曾经使用 4 个空格，但实际使用起来 2 个空格很不错）</li>
<li>不使用分号（一开始可能会觉得奇怪，但几天后就再也回不去了）</li>
<li>在关键字（如 if）和花括号使用空格，在括号不使用空格</li>
<li><a href="https://standardjs.com/rules.html">等等</a>。</li>
</ul>


<p>StandardJS 是一个独立的 Node 模块，可以进行 lint 和修复代码，但如果要在现有的大型项目中使用，并且想要停用一些规则（因为有些地方可能需要作大量修改），还可以使用 <a href="https://github.com/feross/eslint-config-standard">ESLint 预定配置</a>。比如，我就停用了规则 <a href="http://eslint.org/docs/rules/no-mixed-operators">no-mixed-operators</a> 和 <a href="https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md">import / no-webpack-loader-syntax</a>。</p>

<h2>使用 ES2015+ 的新特性</h2>

<p>如果你在使用 JavaScript 开发，根本没办法不听说 ES2015 +（或 ES6，ES7 &hellip;）的特性。有的已经是我离不开的：</p>

<ul>
<li>箭头函数：对于函数式编程，比如写 <code>x =&gt; x * 2</code> 这样的函数非常有用（见下一点）</li>
<li>类：停止使用原型函数，使用类更酷炫（但不要滥用，JavaScript 比任何面向对象的语言好多了）</li>
<li>对数组和对象的操作：</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">doSomething</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">doSomethingElse</span><span class="p">()</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">b</span> <span class="o">=</span> <span class="nx">doSomethingWithA</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">otherResults</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">c</span><span class="o">:</span> <span class="s1">&#39;😺&#39;</span><span class="p">,</span> <span class="nx">d</span><span class="o">:</span> <span class="s1">&#39;🐶&#39;</span> <span class="p">}</span>
</span><span class='line'>  <span class="k">return</span> <span class="p">{</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="p">...</span><span class="nx">otherResults</span> <span class="p">}</span> <span class="c1">// equivalent to { a: a, b: b }</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="kr">const</span> <span class="p">{</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">c</span><span class="p">,</span> <span class="p">...</span><span class="nx">rest</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">doSomething</span><span class="p">()</span> <span class="c1">// Also works with arrays!</span>
</span><span class='line'><span class="c1">// `rest` looks like { b: ..., d: &#39;🐶&#39; }</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>使用 <code>async/await</code> 编写更简单的异步处理：</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// Please try to write the same code with classic promises ;)</span>
</span><span class='line'><span class="nx">async</span> <span class="kd">function</span> <span class="nx">doSomething</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">getValueForA</span><span class="p">()</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">b</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">getValueForBFromA</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span>
</span><span class='line'>  <span class="kr">const</span> <span class="p">[</span><span class="nx">c</span><span class="p">,</span> <span class="nx">d</span><span class="p">]</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span>
</span><span class='line'>    <span class="c1">// parallel execution</span>
</span><span class='line'>    <span class="nx">getValueForC</span><span class="p">(),</span> <span class="nx">getValueForDFromB</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
</span><span class='line'>  <span class="p">])</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">calculateTotal</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">c</span><span class="p">,</span> <span class="nx">d</span><span class="p">)</span>
</span><span class='line'>  <span class="k">return</span> <span class="nx">total</span> <span class="o">/</span> <span class="mi">1000</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>想知道如何使用这些特性呢？<a href="https://blog.castiel.me/posts/002-use-the-coolest-es6-features-everywhere.html">我的另一篇文章能给一些建议</a>。（顺便说一下，使用最新版本的 Node.js，可能不再需要 Babel 就能使用这些新特性）</p>

<h2>使用函数式编程</h2>

<p>函数式编程最近很热门，取得不少成就，而且不仅仅是在 JavaScript 中。为什么呢？函数式编程能使代码更具可预测性，确定性，更安全，一旦习惯这种方式，代码会更容易维护。这里有一些简单的建议：</p>

<p>首先，停止使用 for 循环，在大多数（可能是所有？）情况下根本不需要。例如：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[{</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;first&#39;</span><span class="p">,</span> <span class="nx">value</span><span class="o">:</span> <span class="mi">13</span> <span class="p">},</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;second&#39;</span><span class="p">,</span> <span class="nx">value</span><span class="o">:</span> <span class="mi">7</span> <span class="p">}]</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Instead of:</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="p">{}</span>
</span><span class='line'><span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="kr">const</span> <span class="nx">calculatedValue</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">10</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">calculatedValue</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">res</span><span class="p">[</span><span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">name</span><span class="p">]</span> <span class="o">=</span> <span class="nx">calculatedValue</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Prefer:</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="nx">arr</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">elem</span> <span class="o">=&gt;</span> <span class="p">({</span> <span class="nx">name</span><span class="o">:</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">calculatedValue</span><span class="o">:</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">10</span> <span class="p">}))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">elem</span> <span class="o">=&gt;</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">calculatedValue</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="p">)</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">acc</span><span class="p">,</span> <span class="nx">elem</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
</span><span class='line'>    <span class="p">[</span><span class="nx">elem</span><span class="p">.</span><span class="nx">name</span><span class="p">]</span><span class="o">:</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">calculatedValue</span><span class="p">,</span>
</span><span class='line'>    <span class="p">...</span><span class="nx">acc</span>
</span><span class='line'>  <span class="p">}),</span> <span class="p">{})</span>
</span></code></pre></td></tr></table></div></figure>


<p>好吧，这实际上是一个非常极端的例子，对于不习惯函数式编程的人而言，可能看起来更加复杂。但我们可以稍微简化一下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">enrichElementWithCalculatedValue</span> <span class="o">=</span>
</span><span class='line'>  <span class="nx">elem</span> <span class="o">=&gt;</span> <span class="p">({</span> <span class="nx">name</span><span class="o">:</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">calculatedValue</span><span class="o">:</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">10</span> <span class="p">})</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">filterElementsByValue</span> <span class="o">=</span> <span class="nx">value</span> <span class="o">=&gt;</span>
</span><span class='line'>  <span class="nx">elem</span> <span class="o">=&gt;</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">calculatedValue</span> <span class="o">&gt;</span> <span class="nx">value</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">aggregateElementInObject</span> <span class="o">=</span> <span class="p">(</span><span class="nx">acc</span><span class="p">,</span> <span class="nx">elem</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
</span><span class='line'>  <span class="p">[</span><span class="nx">elem</span><span class="p">.</span><span class="nx">name</span><span class="p">]</span><span class="o">:</span> <span class="nx">elem</span><span class="p">.</span><span class="nx">calculatedValue</span><span class="p">,</span>
</span><span class='line'>  <span class="p">...</span><span class="nx">acc</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="nx">arr</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">enrichElementWithCalculatedValue</span><span class="p">)</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">filterElementsByValue</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</span><span class='line'>  <span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="nx">aggregateElementInObject</span><span class="p">,</span> <span class="p">{})</span>
</span></code></pre></td></tr></table></div></figure>


<p>在这里，我们定义了三个函数，其功能基本上与其名字一致。第二个建议：创建局部函数（即使是在已经存在的函数中）来说明代码的功能，不需要使用注释。</p>

<p>注意，三个局部函数不修改它们的执行上下文。没有外部变量被修改，没有其他服务被调用&hellip;在函数式编程中，它们被称为<em>纯函数</em>。纯函数具有很大的优势：</p>

<ul>
<li>很容易测试，因为从给定参数只有一个可能的结果，不管被调用了多少次;</li>
<li>无论应用状态如何，都能保证相同的结果;</li>
<li>应用状态在函数调用之前和之后保持不变。</li>
</ul>


<p>所以我的第三个建议是：尽可能地使用纯函数。</p>

<h2>其他的一些建议</h2>

<ul>
<li>习惯于使用异步代码，并多使用 promise，看看 <a href="http://reactivex.io/rxjs/">RxJS</a> 的 observales（有<a href="http://reactivex.io/learnrx/">一个很棒的教程关于从函数式编程到响应式编程</a>）</li>
<li>写测试！这应该是很明显的，但是据我所知很多项目都有未经测试的代码，尽管测试 JavaScript（前端或后端）并不困难。</li>
<li>使用最新的语言特性：比如不要再写 <code>arr.indexOf(elem) !== -1</code>，而应该写成 <code>arr.includes(elem)</code>。</li>
<li>大量阅读技术文章：<a href="https://www.reddit.com/r/javascript/">JavaScript subreddit</a> 是了解目前社区最酷做法的一个很好的来源。</li>
</ul>


<p>总而言之，最好的建议就是：<strong>总是重构你的代码</strong>。比如改进你一年前写过的模块？借此机会，用 <code>const</code> 取代 <code>var</code>，使用箭头函数或 <code>async/await</code> 简化代码&hellip;&hellip;和你喜欢的代码工作一件很愉悦的事。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript 模块化现状]]></title>
    <link href="https://blog.mirreal.net/blog/2017/05/29/the-state-of-javascript/"/>
    <updated>2017-05-29T18:00:00+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/05/29/the-state-of-javascript</id>
    <content type="html"><![CDATA[<blockquote><p>原文作者：Johannes Ewald <a href="https://twitter.com/Jhnnns">@Jhnnns</a></p>

<p>原文链接：<a href="https://medium.com/webpack/the-state-of-javascript-modules-4636d1774358">The state of JavaScript modules</a></p>

<p>已获原作者授权翻译及发布</p></blockquote>

<p>ESM, CJS, UMD, AMD — 到底应该选择哪一个？</p>

<p>最近 <a href="https://twitter.com/addyosmani/status/859296190323597313">在 twitter</a> 上有很多关于 <a href="http://2ality.com/2014/09/es6-modules-final.html">ES Module</a> 现状的讨论，<a href="https://twitter.com/bradleymeck/status/863061949021650944">尤其是在 Node.js 上</a>，他们计划引入新的文件扩展名 <code>*.mjs</code>。人们有足够理由对此感到 <a href="https://twitter.com/tankredhase/status/861864123922907136">担忧和不确定</a>，因为这个话题异常复杂，接下来会尽力阐述清楚问题。</p>

<h2>来自远古的恐惧</h2>

<p>大多数前端开发者应该还记得 <a href="https://medium.com/@trek/last-week-i-had-a-small-meltdown-on-twitter-about-npms-future-plans-around-front-end-packaging-b424dd8d367a">Javascript 依赖管理的黑暗时期</a>。那个时候，你需要把一个库复制粘贴到 vendor 文件夹，然后作为一个全局变量引入，要自己去按次序组合所有东西，可能还要管理命名空间。</p>

<p>在过去的那些年，我们能深刻体会到公共模块格式化和中央模块管理的价值。</p>

<p>在今天，不管是发布还是使用一个库都要容易得多，只需要使用 <code>npm publish</code> 和 <code>npm install</code> 命令就行。这就是人们会那么紧张两种模块系统兼容性问题的原因：他们不想失去已有的舒适区。</p>

<p>接下来我会解释和总结现有实现的情况，以及为什么 Node 生态迁移到 ES Module（ESM）会那么难。在最后，总结这些变化对 webpack 使用者和模块作者有什么影响。</p>

<h2>现有实现</h2>

<p>目前，ESM 有三种方式的实现：</p>

<ul>
<li>浏览器</li>
<li>webpack 以及类似的构建工具</li>
<li>Node（未完成，<a href="https://twitter.com/rauschma/status/866334160218095617">但可能在年底作为一个实验功能</a>）</li>
</ul>


<p>为了更好地理解现在的讨论，首先要知道 ES2015 包含两种模式：</p>

<ul>
<li><code>script</code> 用于具有全局命名空间的常规脚本</li>
<li><code>module</code> 用于具有明确导入和导出的模块化代码</li>
</ul>


<p>如果你试图在 <code>script</code> 标签使用 <code>import</code> 或者 <code>export</code> 语句，会抛出一个 SyntaxError。这种语句在全局环境下没有任何意义。另一方面，<code>module</code> 模式即意味着<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Strict_mode">严格模式</a>，禁止使用某些语言特性，比如 <code>with</code> 语句。因此，需要在脚本被解析和执行之前定义模式。</p>

<h2>浏览器中的 ESM</h2>

<p>截至到 2017 年 5 月，所有主流浏览器都开始做了 ESM 的实现工作。不过，大部分仍处于在实验性质。这里不会做详细介绍，因为 <a href="https://jakearchibald.com/2017/es-modules-in-browsers/">Jake Archibald 已经写了一篇很厉害的文章</a>。</p>

<p>除了一些小的困难，在浏览器中实现起来非常容易，因为以前并没有模块系统。想要指定 <code>module</code> 模式，需要在 <code>script</code> 标签添加 <code>type="module"</code> 属性，如下所示：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">&quot;module&quot;</span> <span class="na">src=</span><span class="s">&quot;main.js&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>在一个模块中，现在只能使用有效的 <code>URL</code> 作为模块标识符。模块标识符是用于 require 或 import 其他模块的字符串。为了确保未来兼容 CJS 模块标识符，“纯” 导入标识符（如 <code>import "lodash"</code>）现在还不支持。模块标识符必须是绝对 <code>URL</code> 或者是以 <code>/</code>，  <code>./</code>,  <code>../</code> 开头：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// Supported:</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span><span class="nx">foo</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;https://jakearchibald.com/utils/bar.js&#39;</span><span class="p">;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span><span class="nx">foo</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;/utils/bar.js&#39;</span><span class="p">;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span><span class="nx">foo</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;./bar.js&#39;</span><span class="p">;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span><span class="nx">foo</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;../bar.js&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Not supported:</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span><span class="nx">foo</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;bar.js&#39;</span><span class="p">;</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span><span class="nx">foo</span><span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;utils/bar.js&#39;</span><span class="p">;</span>
</span><span class='line'><span class="c1">// Example from https://jakearchibald.com/2017/es-modules-in-browsers/</span>
</span></code></pre></td></tr></table></div></figure>


<p>同样需要注意的是，一旦处在一个模块中，每个导入也将被解析为 <code>module</code>，而且没有办法 <code>import</code> 一个 <code>script</code>。</p>

<h2>ESM 与 webpack</h2>

<p>类似 webpack 这样的构建工具通常会尝试用 <code>module</code> 模式解析代码，有问题再切回到 <code>script</code> 模式。这些工具最终会生成一段 <code>script</code>，通常是在一定程度上模拟 CJS 和 ESM 行为的模块运行时。</p>

<p>我们以这两个简单的 ESM 为例：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// a.js</span>
</span><span class='line'><span class="kr">export</span> <span class="kd">let</span> <span class="nx">number</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
</span><span class='line'><span class="kr">export</span> <span class="kd">function</span> <span class="nx">incr</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">number</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// test.js</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">number</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&quot;./a&quot;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">number</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>webpack 使用函数包装器封装模块范围和对象引用来模拟 <a href="http://2ality.com/2015/07/es6-module-exports.html">ESM 实时绑定</a>。每次编译，还包括一个模块运行时，负责引导和缓存模块。此外，将模块标识转换为数字模块 ID。这样可以减少打包的大小和引导时间。</p>

<p>这是什么意思呢？我们来看看编译输出：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">modules</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// This is the module runtime.</span>
</span><span class='line'>    <span class="c1">// It&#39;s only included once per compilation.</span>
</span><span class='line'>    <span class="c1">// Other chunks share the same runtime.</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">installedModules</span> <span class="o">=</span> <span class="p">{};</span>
</span><span class='line'>    <span class="c1">// The require function</span>
</span><span class='line'>    <span class="kd">function</span> <span class="nx">__webpack_require__</span><span class="p">(</span><span class="nx">moduleId</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="p">...</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="p">...</span>
</span><span class='line'>    <span class="c1">// Load entry module and return exports</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">__webpack_require__</span><span class="p">(</span><span class="nx">__webpack_require__</span><span class="p">.</span><span class="nx">s</span> <span class="o">=</span> <span class="mi">1</span><span class="p">);</span>
</span><span class='line'><span class="p">})</span>
</span><span class='line'><span class="p">([</span> <span class="c1">// An array that maps module ids to functions</span>
</span><span class='line'>    <span class="c1">// a.js as module id 0</span>
</span><span class='line'>    <span class="kd">function</span> <span class="p">(</span><span class="nx">module</span><span class="p">,</span> <span class="nx">__webpack_exports__</span><span class="p">,</span> <span class="nx">__webpack_require__</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="s2">&quot;use strict&quot;</span><span class="p">;</span>
</span><span class='line'>        <span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">__webpack_exports__</span><span class="p">,</span> <span class="s2">&quot;a&quot;</span><span class="p">,</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">configurable</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>            <span class="nx">enumerable</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span><span class='line'>            <span class="nx">get</span><span class="o">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">number</span>
</span><span class='line'>        <span class="p">});</span>
</span><span class='line'>
</span><span class='line'>        <span class="kd">let</span> <span class="nx">number</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>        <span class="kd">function</span> <span class="nx">incr</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">number</span><span class="o">++</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>    <span class="p">},</span>
</span><span class='line'>    <span class="c1">// test.js as module id 1</span>
</span><span class='line'>    <span class="kd">function</span> <span class="p">(</span><span class="nx">module</span><span class="p">,</span> <span class="nx">__webpack_exports__</span><span class="p">,</span> <span class="nx">__webpack_require__</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="s2">&quot;use strict&quot;</span><span class="p">;</span>
</span><span class='line'>        <span class="kd">var</span> <span class="nx">__WEBPACK_IMPORTED_MODULE_0__a__</span> <span class="o">=</span> <span class="nx">__webpack_require__</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>        <span class="c1">// Object reference as &quot;live binding&quot;</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">__WEBPACK_IMPORTED_MODULE_0__a__</span><span class="p">[</span><span class="s2">&quot;a&quot;</span> <span class="cm">/* number */</span><span class="p">]);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">]);</span>
</span></code></pre></td></tr></table></div></figure>


<p>简化的 webpack 输出，模拟 ES Modules 行为</p>

<p>结果已经简化并删除了一些与此示例无关的代码。你会发现，webpack 在 <code>exports</code> 对象上将所有 <code>export</code> 语句替换成 <code>Object.defineProperty</code>，并使用属性访问器替换对引入值的所有引用。还要注意每个 ESM 开始时的 <code>"use strict"</code> 指令，这是由 webpack 自动添加，在 ESM 中必须是严格模式。</p>

<p>这种实现只是模拟，因为它试图模仿 ESM 和 CJS 的行为 &ndash; 但不是与其完全保持一致。比如，这种模拟并不符合某些边缘情况。看下面这个模块：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>如果你通过加上 <code>babel-preset-es2015</code> 的 Babel 来运行，结果是：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="s2">&quot;use strict&quot;</span><span class="p">;</span>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="kc">undefined</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>从输出结果可以看出，Babel 假设默认是 ESM，因为 <code>module</code> 模式即代表严格模式，在严格模式下会将 <code>this</code> 初始化为 <code>undefined</code>。</p>

<p>然而，使用 webpack，结果是：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">module</span><span class="p">,</span> <span class="nx">exports</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>


<p>在引导模块时，<code>this</code> 将指向 <code>exports</code> ，与 Node.js 使用的 CJS 行为一致。这是因为语法上不确定是 <code>script</code> 还是 <code>module</code>，解析器无法判断该模块是 ESM 还是 CJS。在不明确的时候，webpack 会模拟 CJS，因为它仍然是最受欢迎的模块风格。</p>

<p>这种模拟其实已经包含了很多情况，因为模块作者通常会避免这种代码。然而，“很多情况”对于像 Node.js 这样的平台是不够的，因为它需要保证所有有效的 JavaScript 代码都能正常运行。</p>

<h2>Node.js 中的 ESM</h2>

<p>Node.js 在执行 ESM 时遇到了麻烦，因为仍然需要支持 CJS，语法看起来相似，但运行时行为完全不同。<a href="https://github.com/nodejs/CTC">Node.js 核心技术委员会</a>（CTC）成员 James M Snell 撰写了<a href="https://hackernoon.com/node-js-tc-39-and-modules-a1118aecf95e">一篇很好的文章来解释 CJS 与 ESM 之间的差异</a>。</p>

<p>归结起来，CJS 是一个动态模块系统，ESM 是静态模块系统。</p>

<h3>CJS</h3>

<ul>
<li>允许动态同步 <code>require()</code></li>
<li>导出仅在模块执行后才知道</li>
<li>导出可以在模块初始化后添加，替换和删除</li>
</ul>


<h3>ESM</h3>

<ul>
<li>只允许静态同步 <code>import</code></li>
<li>在模块执行之前，导入和导出已经关联</li>
<li>导入和导出是不可变的</li>
</ul>


<p>由于  CJS 早于 ES2015，所以一直在 <code>script</code> 模式下解析，封装通过使用函数包装器实现。在 Node.js 中加载 CJS，实际上会执行与此类似的代码：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">module</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">exports</span><span class="o">:</span> <span class="p">{}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">require</span> <span class="o">=</span> <span class="nx">makeRequireFunction</span><span class="p">();</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">filename</span> <span class="o">=</span> <span class="s2">&quot;...&quot;</span><span class="p">;</span>
</span><span class='line'><span class="kr">const</span> <span class="nx">dirname</span> <span class="o">=</span> <span class="s2">&quot;...&quot;</span><span class="p">;</span>
</span><span class='line'><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">exports</span><span class="p">,</span> <span class="nx">require</span><span class="p">,</span> <span class="nx">module</span><span class="p">,</span> <span class="nx">__filename</span><span class="p">,</span> <span class="nx">__dirname</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'><span class="cm">/* YOUR CODE */</span>
</span><span class='line'><span class="p">})(</span><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="p">,</span> <span class="nx">require</span><span class="p">,</span> <span class="nx">module</span><span class="p">,</span> <span class="nx">filename</span><span class="p">,</span> <span class="nx">dirname</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>对 Node.js 的 CommonJS 模块的简单函数包装</p>

<p>问题出现了，将两个模块系统集成到同一个运行时时，ESM 和 CJS 之间的循环依赖可能会迅速导致类似死锁的情况。</p>

<p>而且，由于现有 CJS 模块数量庞大，也不能直接放弃对 CJS 的支持。为了避免 Node.js 生态的中断，有两点已经很明显：</p>

<ul>
<li>现有的 CJS 代码必须以相同的方式继续工作</li>
<li>两个模块系统都必须同时且尽可能无缝地工作</li>
</ul>


<h3>目前的权衡</h3>

<p>2017 年 3 月，经过几个月的讨论，CTC 终于找到了一种解决问题的办法。由于在 ES 规范和引擎不改变的情况下无法进行无缝集成，<a href="https://github.com/bmeck/node-eps/blob/a1eab9bf023bbe13a79ddb18f0622a5d57215f9b/002-es-modules.md">CTC 决定开始一些权衡之后的实现工作</a>：</p>

<h4>1.ESM 必须是 <code>*.mjs</code> 文件扩展名</h4>

<p>这是由于上面提及的模糊语法问题，无法通过解析来确切知晓 JavaScript 代码是什么类型。为了 Node.js 向后兼容的目标，作者需要加入一种新模式。<a href="https://github.com/nodejs/node/wiki/ES6-Module-Detection-in-Node#detection-problem">已经有关于各种替代品的讨论</a>，但使用不同文件扩展名是解决目前问题的最佳权衡。</p>

<h4>2.CJS 只能异步导入 ESM import()</h4>

<p>Node.js 将异步加载 ESM，以便尽可能接近浏览器的行为。因此，同步的 <code>require()</code> 在 ESM 是不可能的，并且依赖于 ESM 的每个功能都需要异步：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kr">const</span> <span class="nx">driverPromise</span> <span class="o">=</span> <span class="kr">import</span><span class="p">(</span><span class="s2">&quot;dbdriver&quot;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="nx">exports</span><span class="p">.</span><span class="nx">readFromDb</span> <span class="o">=</span> <span class="nx">async</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'>   <span class="k">return</span> <span class="p">(</span><span class="nx">await</span> <span class="nx">driverPromise</span><span class="p">).</span><span class="nx">read</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
</span><span class='line'><span class="p">};</span>
</span></code></pre></td></tr></table></div></figure>


<h4>3. CJS 向 ESM 暴露一个不可变的默认导出</h4>

<p>使用 Babel 或 Webpack，我们通常将 CJS 重构为 ESM，如下所示：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="c1">// CJS</span>
</span><span class='line'><span class="kr">const</span> <span class="p">{</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&quot;c&quot;</span><span class="p">);</span>
</span><span class='line'><span class="c1">// ESM</span>
</span><span class='line'><span class="kr">import</span> <span class="p">{</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&quot;c&quot;</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>再一次地，他们的语法看起来很相似，但忽略了 CJS 中没有命名导出的事实。只有一个叫做 <code>default</code> 的导出，等同于在 CJS 模块完成计算后一个不可变的 <code>module.exports</code> 。从技术上讲，有可能将 <code>module.exports</code> 解构成命名导入，但这需要对标准作更大的变更。<a href="https://github.com/bmeck/node-eps/blob/a1eab9bf023bbe13a79ddb18f0622a5d57215f9b/002-es-modules.md#461-default-imports">这就是现在 CTC 决定采取这种方式的原因</a>。</p>

<h4>4.模块范围的变量类似 <code>module</code>，<code>require</code> 以及 <code>__filename</code> 在 ESM 不存在</h4>

<p>Node.js 和浏览器会实现一些 ESM 的特性，<a href="https://github.com/whatwg/html/issues/1013">但标准化过程仍在进行中</a>。</p>

<p>鉴于将 CJS 和 ESM 集成到一个运行时的工程挑战，CTC 在评估边缘情况和权衡方面做了非常好的工作。比如使用不同的文件扩展名是就是一个很简单的解决方案。</p>

<p>实际上，一个文件扩展名可以认为是一个二进制文件如何解释的提示。如果一个 <code>module</code> 不是 <code>script</code>，我们应该使用不同的文件扩展名。其他工具（如 linter 或 IDE ）也可以获取相同信息。</p>

<p>当然，引入新的文件扩展名有成本，但是一旦服务器和其他应用程序确认 <code>*.mjs</code> 为JavaScript，我们很快就会忘记这个争议。</p>

<h2>将 * .mjs 作为 Node.js 的 Python 3？</h2>

<p>考虑到所有这些限制，人们可能会问，这种过渡将对现在的生态造成什么样的损害。虽然 CTC 会努力解决问题，但社区如何采用这一点仍然存在很大不确定性。这种不确定性 <a href="https://twitter.com/sindresorhus/status/861987349529452545">被众多知名的 NPM 模块作者</a> 再次强调，他们声称将不会在模块中使用 <code>*.mjs</code>。</p>

<p><a href="http://blog.thezerobit.com/2014/05/25/python-3-is-killing-python.html">Python 3 is killing Python</a></p>

<p>很难预测社区如何反应，但是应该不会对现在的生态造成大破坏，甚至能看到从 CJS 平稳过渡到 ESM。主要有两个原因：</p>

<h3>1.与 CJS 严格向后兼容</h3>

<p>那些不喜欢 ESM 的模块作者可以继续使用 CJS，保证自己不被排挤出局。这样他们自己的代码不会受到采用 ESM 的影响，降低迁移到另一个运行时的可能性，让 NPM 迁移到新生态变得容易。从 CJS 到 ESM 的重构给包维护者带来额外工作，不能指望所有人都有时间。</p>

<h3>2. CJS 在 ESM 中的无缝整合</h3>

<p>从 ESM 导入 CJS 模块非常简单。需要注意的是，CJS 仅导出一个默认值。一旦处于 ESM，甚至可能根本不会注意到依赖关系使用的模块风格，尤其是与在 CJS 中使用 <code>await import()</code>相比。</p>

<p>由于 ESM 的这个优点以及其他有点，比如开箱即用的 <a href="https://webpack.js.org/guides/tree-shaking/">tree shaking</a> 和浏览器兼容性，预计在未来几年内，我们可以看到向 ESM 的缓慢而稳定的过渡。CJS 的特性，如动态 <code>require()</code> 和猴子补丁导出，在 Node.js 社区一直是有争议的，不比 ESM 带来的好处。</p>

<h2>这些对我来说意味着什么？</h2>

<p>因为最近这些事情，很容易对目前存在的所有选择和限制感到困惑。在接下来，整理了开发人员面临的典型问题以及我们的回答：</p>

<h3>现在需要重构现有的代码吗？</h3>

<p><strong>不需要</strong>。Node.js 才刚刚开始实现 ESM，仍然有大量的工作要做。<a href="https://medium.com/the-node-js-collection/an-update-on-es6-modules-in-node-js-42c958b890c">James M Snell 预计至少还需要一年时间</a>，还有很多变化的余地，所以现在重构是不安全的。</p>

<h3>应该在新代码中使用 ESM 吗？</h3>

<ul>
<li><strong>如果你已经有或者打算使用像 webpack 这样的构建工具，答案是肯定的</strong>。这将更容易完成代码库的过渡，并使 tree shaking 成为可能。但要小心：一旦 Node.js 支持原生 ESM，可能需要重构其中的一些部分。</li>
<li><strong>如果你正在编写一个库，答案是也肯定的</strong>，你的模块使用者将受益于 tree shaking。</li>
<li><strong>如果你不想进行构建操作，或者正在编写一个 Node.js 应用程序，还是用 CJS 吧</strong>。</li>
</ul>


<h3>现在应该使用 .mjs 吗？</h3>

<p><strong>不要这样做</strong>，目前没有什么好处，工具支持依然薄弱。建议一旦原生 ESM 支持登陆 Node.js，尽快开始迁移。记住，<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">浏览器只关心 MIME 类型，而不是文件扩展名</a>。</p>

<h3>应该关心浏览器兼容性吗？</h3>

<p>是的，需要在一定程度上关注这个问题。 不应该在导入语句中省略 <code>.js</code> 扩展名，因为浏览器需要完整的 URL，无法像 Node.js 这样执行路径查询。同样，应该避免 <code>index.js</code> 文件。不过，人们并不会很快在浏览器中使用 NPM 软件包，因为仍然不能 bare 导入。</p>

<h3>作为库作者该怎么办？</h3>

<p>用 ESM 编写代码，并使用 <a href="https://rollupjs.org/">Rollup</a> 或 Webpack 转换成单个 CJS 模块，然后在 <code>package.json</code> 将 <code>main</code> 字段指向此 CJS 包，并将 <a href="https://github.com/rollup/rollup/wiki/pkg.module"><code>module</code></a> 字段指向原始 ESM。如果还使用 ESM 之外的其他新语言功能，则应编译成 ES5，并提供 CJS 和 ESM 的打包。这样，你的库用户仍然可以从 tree shaking 获利而无需对代码进行转换。</p>

<p>看一下这些完成 tree shaking 的模块</p>

<h2>总结</h2>

<p>关于 ES 模块有很多不确定性。由于目前 Node.js 在实现上的权衡，开发人员担心可能会破坏 Node.js 的生态。</p>

<p>这些还不会发生，有两个原因：<strong>CJS 的严格的后向兼容和 CJS 在 ESM 中的无缝集成</strong>。</p>

<p>在 Node.js 发布原生 ESM 支持之前，应该仍然使用 Rollup 和 Webpack 等工具。它们在一定程度上模拟了 ESM 环境，但要注意它们不完全符合规范。此外，使用打包仍然是个<a href="https://peerigon.github.io/talks/2016-08-26-jsconf-is-future-frontend-tooling/#36">很好的选择</a>，一旦可以在浏览器中使用 NPM 软件包。</p>

<p>我们 webpack 团队正在努力做一些工作，帮助开发者平稳过渡。为了这个目标，我们计划在 Node.js 的 ESM 支持成熟后，模拟 Node.js 导入 CJS 的方式。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JS/React 开发者的 Atom 终极配置]]></title>
    <link href="https://blog.mirreal.net/blog/2017/05/28/my-atom-editor-setup-for-js-react/"/>
    <updated>2017-05-28T18:00:00+08:00</updated>
    <id>https://blog.mirreal.net/blog/2017/05/28/my-atom-editor-setup-for-js-react</id>
    <content type="html"><![CDATA[<blockquote><p>原文作者：Elad Ossadon</p>

<p>原文链接：<a href="https://medium.com/productivity-freak/my-atom-editor-setup-for-js-react-9726cd69ad20">The Ultimate Atom Editor Setup (+for JS/React)</a></p></blockquote>

<p>根据多年以来不断完善 Sublime Text 配置的经验，决定这次给 Atom 也来一个大改造。这个过程花费了好几个月的时间，但成果还是很卓有成效的，我现在非常满意这份配置。</p>

<p>这份清单将分为实用工具和 React 专用，并涉及到快捷键绑定。</p>

<h2>实用工具</h2>

<h3><a href="https://atom.io/packages/atom-beautify">atom-beautify</a></h3>

<p>可以在 Atom 中 美化 HTML，CSS，JavaScript，PHP，Python，Ruby，Java，C，C ++，C＃，Objective-C，CoffeeScript，TypeScript，Coldfusion，SQL等。</p>

<p>快捷键：<code>⌃+⌥+B</code></p>

<h3><a href="https://atom.io/packages/prettier-atom">prettier-atom</a></h3>

<p>使用 Prettier 来格式化 JavaScript 代码，配有强大的 ESlint 集成。</p>

<p>快捷键：<code>⌃+⌥+F</code></p>

<h3><a href="https://atom.io/packages/atom-transpose">atom-transpose</a></h3>

<p>Atom 的转置更像是字符串翻转。在 Sublime 可以将选中的两个字符串进行交换，看起来更有用。</p>

<p>快捷键：<code>⌥+T</code></p>

<h3><a href="https://atom.io/packages/case-keep-replace">case-keep-replace</a></h3>

<p>使用这个插件可以在替换文本的时候可以保留原来的命名风格。</p>

<p>快捷键：<code>⌘+⌃+R</code></p>

<h3><a href="https://atom.io/packages/change-case">change-case</a></h3>

<p>一个可以快速改变当前选择文本命名方式的工具。比如可以从 <code>camelCase</code> 转换到 <code>snake_case</code> 等。</p>

<p>快捷键：<code>⌘+K ⌘+C/S</code></p>

<h3><a href="https://atom.io/packages/copy-path">copy-path</a></h3>

<p>可以灵活地复制文件路径。</p>

<h3><a href="https://atom.io/packages/duplicate-line-or-selection">duplicate-line-or-selection</a></h3>

<p>重复选择文本或者重复一行，跟 Sublime Text 的行为一致，Atom 可以重复一整行。</p>

<p>快捷键：<code>⌘+⇧+D</code></p>

<h3><a href="https://atom.io/packages/editorconfig">editorconfig</a></h3>

<p>帮助开发人员在不同的编辑器之间保持一致的编码风格。</p>

<h3><a href="https://atom.io/packages/file-icons">file-icons</a></h3>

<p>Atom 的文件特定图标插件，便于区分不同文件类型。</p>

<h3><a href="https://atom.io/packages/git-plus">git-plus</a></h3>

<p>VIM 风格的 git 插件，在没有终端命令行的时候进行提交等其他 git 操作。</p>

<h3><a href="https://atom.io/packages/highlight-selected">highlight-selected</a></h3>

<p>通过双击一个词来高亮整个文件相同的词。</p>

<h3><a href="https://atom.io/packages/local-history">local-history</a></h3>

<p>用于维护本地文件历史的插件（对代码文件进行更改的历史记录）。</p>

<h3><a href="https://atom.io/packages/project-manager">project-manager</a></h3>

<p>轻松访问所有项目，还能对项目特定设置和选项进行管理。</p>

<p>快捷键：<code>⌘+⌃+P</code></p>

<h3><a href="https://atom.io/packages/atom-reveal-file-in-finder">atom-reveal-file-in-finder</a></h3>

<p>可以在工作区或者文件选项卡上打开文件到 Finder 上，快捷命令已经添加到 <code>⌘+⇧*+P</code> 。</p>

<p>快捷键：<code>⌘+⌃+P</code></p>

<h3><a href="https://atom.io/packages/related">related</a></h3>

<p>related 提供了访问与当前打开的文件相关的文件的快速方式。 例如，在 <code>.js</code> 和 <code>.spec.js</code> 文件之间切换。</p>

<p>快捷键：<code>⌘+⇧+ R</code></p>

<p>我的 JS 关联配置 (Menu > Packages > Related > Edit related patterns):</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>'([^\\/]+)(?!\\.spec).js(x?)$': [
</span><span class='line'>  'tests/$1.spec.js$2#create',
</span><span class='line'>]
</span><span class='line'>'tests/(.+).spec.js(x?)$': [
</span><span class='line'>  '$1.js$2',
</span><span class='line'>]</span></code></pre></td></tr></table></div></figure>


<h3><a href="https://atom.io/packages/set-syntax">set-syntax</a></h3>

<p>一种简单的命令方式来设置当前文件语法，与 Sublime 类似。</p>

<p>快捷键：<code>⌘+⇧+P</code></p>

<h3><a href="https://atom.io/packages/sort-lines">sort-lines</a></h3>

<p>排序/删除重复行。</p>

<h3><a href="https://atom.io/packages/Sublime-Style-Column-Selection">sublime-style-column-selection</a></h3>

<p>alt +单击跨行选择文本块，每行都有插入符号。</p>

<p>快捷键：<code>⌥+Drag</code></p>

<h3><a href="https://atom.io/packages/tab-foldername-index">tab-foldername-index</a></h3>

<p>可以替换 TAB 标签内容的插件，在打开相同文件名的文件时保证更高的可读性。</p>

<h3><a href="https://atom.io/packages/sync-settings">sync-settings</a></h3>

<p>跨 Atom 实例同步设置，键盘映射，用户样式，初始化脚本，代码段和已安装的软件包。 我将所有设置备份到 Gist 并在工作/个人计算机之间进行同步。</p>

<h3><a href="https://atom.io/packages/toggle-quotes">toggle-quotes</a></h3>

<p>快速切换字符串的单引号和双引号。</p>

<p>快捷键：<code>⌘+⇧+’</code></p>

<h3><a href="https://atom.io/packages/atom-spotify2">atom-spotify2</a></h3>

<p>在 Atom 状态栏中显示在 Spotify 中当前播放歌曲。 不是必要的，但很有趣。</p>

<h2>HTML/CSS/JS/React Specific Packages</h2>

<h3><a href="https://atom.io/packages/atom-ternjs">atom-ternjs</a></h3>

<p>使用 Tern 为 Atom 提供 JavaScript 代码智能提示，支持 ES5，ES6，ES7，Node.js，jQuery，Angular等。</p>

<h3><a href="https://atom.io/packages/atom-wrap-in-tag">atom-wrap-in-tag</a></h3>

<p>为选择的文本增加标签。</p>

<p>快捷键：<code>⌥+⇧+W</code></p>

<h3><a href="https://atom.io/packages/autoclose-html">autoclose-html</a></h3>

<p>自动添加关闭标签。</p>

<h3><a href="https://atom.io/packages/autocomplete-modules">autocomplete-modules</a></h3>

<p>自动补全 <code>require/import</code> 声明。</p>

<h3><a href="https://atom.io/packages/color-picker">color-picker</a></h3>

<p>很厉害的颜色选择器。</p>

<p>快捷键：<code>⌘+⇧+D</code></p>

<h3><a href="https://atom.io/packages/docblockr">docblockr</a></h3>

<p>更容易的方式写文档注释。</p>

<p>使用方式：<code>/** &lt;tab&gt;</code></p>

<h3><a href="https://atom.io/packages/emmet">emmet</a></h3>

<p>一个大大提高 HTML 和 CSS 工作流程的插件。 <a href="http://emmet.io/">关于 Emmet</a></p>

<h3><a href="https://atom.io/packages/emmet-jsx-css-modules">emmet-jsx-css-modules</a></h3>

<p>适用于 css 模块的 emmet 工具。 <code>.foo</code> 现在将扩展为 <code>&lt;div className = {style.foo}&gt; &lt;/ div&gt;</code>，而不是 <code>&lt;div className =“foo”&gt; &lt;/ div&gt;</code>。</p>

<h3><a href="https://atom.io/packages/es6-javascript">es6-javascript</a></h3>

<p>一组专注 ES6，用于优化现代 JavaScript 开发生产力的命令集， 目标是符合 <a href="https://github.com/airbnb/javascript">Airbnb 推荐的代码规范</a>。</p>

<h3><a href="https://atom.io/packages/js-hyperclick">js-hyperclick</a> &amp; <a href="https://atom.io/packages/hyperclick">hyperclick</a></h3>

<p>点击跳到变量或者 import 定义，js-hyperclick 依赖于 hyperclick。</p>

<h3><a href="https://atom.io/packages/pigments">pigments</a></h3>

<p>在项目文件中显示颜色。</p>

<h3><a href="https://atom.io/packages/linter-eslint">linter-eslint</a></h3>

<p>插件 <a href="https://github.com/AtomLinter/Linter">Linter</a> 为 <a href="http://eslint.org/">eslint</a> 提供 UI 接口，用于对 JavaScript 文件进行静态检查。</p>

<h3><a href="https://atom.io/packages/tree-view-copy-relative-path">tree-view-copy-relative-path</a></h3>

<p>允许从 tree view 复制文件的相对路径。</p>

<h3><a href="https://atom.io/packages/lodash-snippets">lodash-snippets</a></h3>

<p>在 Atom 中快速使用 lodash 的代码提示。</p>

<h3><a href="https://atom.io/packages/language-babel">language-babel</a></h3>

<p>支持 JavaScript ES201x，React JSX，Flow和GraphQL语法。</p>

<h3><a href="https://atom.io/packages/react-es7-snippets">react-es7-snippets</a></h3>

<p>React ES7 snippets for atom</p>

<h3><a href="https://atom.io/packages/atom-jest-snippets">atom-jest-snippets</a></h3>

<p>Jest 测试提示</p>

<h2>我的主题</h2>

<h3>UI Theme: <a href="https://atom.io/themes/one-dark-ui">one-dark-ui</a></h3>

<h3>Syntax Theme: <a href="https://atom.io/themes/dracula-theme">dracula-theme</a></h3>

<h2>Install EVERYTHING!</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>apm install atom-beautify prettier-atom atom-spotify2 atom-transpose case-keep-replace change-case copy-path duplicate-line-or-selection editorconfig file-icons git-plus highlight-selected local-history project-manager related set-syntax atom-reveal-file-in-finder sort-lines sublime-style-column-selection tab-foldername-index sync-settings toggle-quotes atom-wrap-in-tag atom-ternjs autoclose-html autocomplete-modules color-picker docblockr emmet emmet-jsx-css-modules es6-javascript js-hyperclick hyperclick pigments linter-eslint tree-view-copy-relative-path lodash-snippets language-babel react-es7-snippets atom-jest-snippets one-dark-ui dracula-theme</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[科学上网的方法和原理]]></title>
    <link href="https://blog.mirreal.net/blog/2016/06/10/the-method-and-principle-of-breaking-the-wall/"/>
    <updated>2016-06-10T18:15:50+08:00</updated>
    <id>https://blog.mirreal.net/blog/2016/06/10/the-method-and-principle-of-breaking-the-wall</id>
    <content type="html"><![CDATA[<p>逗你呢，这里没有你想要的东西。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[使用 ES6 的浏览器兼容性问题]]></title>
    <link href="https://blog.mirreal.net/blog/2016/05/14/compatibility-problem-with-es6/"/>
    <updated>2016-05-14T16:35:34+08:00</updated>
    <id>https://blog.mirreal.net/blog/2016/05/14/compatibility-problem-with-es6</id>
    <content type="html"><![CDATA[<p>以前对浏览器兼容性问题只是大概知道一些点，没想到这次真正着手去做的时候，还是碰到了很多问题。刚开始的时候一边解决问题，一边想着：用 IE8 的都是神经病，到后来，我发现完了，I LOVE IE。</p>

<h2>0x00 起源</h2>

<p>在这次做小蜜 PC 版的时候，由于早于 PC 版，无线版已经重新设计了全新版，做了很多架构上的优化调整。所以在做的时候把无线版的前端架构拿了过来，主要的考虑就是品牌和功能保持跟无线版统一的同时，技术上也可相互支持以及组件复用。</p>

<p>无线版整个架构设计是同事做的，技术上主要采用 ES6 + Webpack + Babel 的方式，由于项目的独特性和特殊需求，并没有使用任何框架，只引入 zepto 作为一个标准支撑库。</p>

<p>而 PC 版的架构跟无线版基本保持一致，主要是把 zepto 换成了 jQuery。</p>

<p>下面是一些基本的开发依赖：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='json'><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="nt">&quot;devDependencies&quot;</span><span class="p">:</span> <span class="p">{</span>
</span><span class='line'>    <span class="nt">&quot;babel-core&quot;</span><span class="p">:</span> <span class="s2">&quot;~6.3.15&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;babel-loader&quot;</span><span class="p">:</span> <span class="s2">&quot;~6.2.0&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;babel-preset-es2015&quot;</span><span class="p">:</span> <span class="s2">&quot;~6.3.13&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;babel-preset-stage-0&quot;</span><span class="p">:</span> <span class="s2">&quot;~6.3.13&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;babel-runtime&quot;</span><span class="p">:</span> <span class="s2">&quot;~6.3.13&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;extract-text-webpack-plugin&quot;</span><span class="p">:</span> <span class="s2">&quot;~0.9.1&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;less-loader&quot;</span><span class="p">:</span> <span class="s2">&quot;~2.2.1&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;nunjucks-loader&quot;</span><span class="p">:</span> <span class="s2">&quot;~1.0.7&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;style-loader&quot;</span><span class="p">:</span> <span class="s2">&quot;~0.10.2&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;webpack&quot;</span><span class="p">:</span> <span class="s2">&quot;~1.12.9&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="nt">&quot;webpack-dev-server&quot;</span><span class="p">:</span> <span class="s2">&quot;^1.10.1&quot;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h2>0x01 polyfill</h2>

<p>由于 Babel 默认只转换转各种 ES2015 语法，而不转换新的 API，比如 Promise，以及 Object.assign、Array.from 这些新方法，这时我们需要提供一些 ployfill 来模拟出这样一个提供原生支持功能的浏览器环境。</p>

<p>主要有两种方式：<code>babel-runtime</code> 和 <code>babel-polyfill</code>。</p>

<h3>babel-runtime</h3>

<p>babel-runtime 的作用是模拟 ES2015 环境，包含各种分散的 polyfill 模块，我们可以在自己的模块里单独引入，比如 promise：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">import</span> <span class="s1">&#39;babel-runtime/core-js/promise&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>它们不会在全局环境添加未实现的方法，只是这样手动引用每个 polyfill 会非常低效，我们可以借助 <code>Runtime transform</code> 插件来自动化处理这一切。</p>

<p>首先使用 npm 安装：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>npm install babel-plugin-transform-runtime --save-dev
</span></code></pre></td></tr></table></div></figure>


<p>然后在 webpack 配置文件的 babel-loader 增加选项：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">loader</span><span class="o">:</span> <span class="p">[</span><span class="s2">&quot;babel-loader&quot;</span><span class="p">],</span>
</span><span class='line'><span class="nx">query</span><span class="o">:</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
</span><span class='line'>  <span class="s2">&quot;transform-runtime&quot;</span>
</span><span class='line'>  <span class="p">],</span>
</span><span class='line'>  <span class="nx">presets</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;es2015&#39;</span><span class="p">,</span> <span class="s1">&#39;stage-0&#39;</span><span class="p">]</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h3>babel-polyfill</h3>

<p>而 <code>babel-polyfill</code> 是针对全局环境的，引入它浏览器就好像具备了规范里定义的完整的特性，一旦引入，就会跑一个 <code>babel-polyfill</code> 实例。用法如下：</p>

<p>1.安装 babel-polyfill</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='sh'><span class='line'>npm install babel-polyfill --save
</span></code></pre></td></tr></table></div></figure>


<p>2.在入口文件中引用：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">import</span> <span class="s1">&#39;babel-polyfill&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<h3>小结：</h3>

<p>其实做到这些，在大部分浏览器就可以正常跑了，但我们做的是一个用户环境很不确定的产品，对一些年代久远但又不容忽视的运行环境，比如 IE8，我们做的还不够。</p>

<p>接下来将开始讲述我们在兼容性方面遇到的一些问题，和解决方法。</p>

<h2>0x02 开始在 IE8 运行</h2>

<p>最开始做的时候并没有针对 IE 做一些兼容性方面的处理，结果在 IE8 上一跑一堆问题。</p>

<p>第一步，我们把 <code>jQuery</code> 换成 1.12.1 ，因为 2.X 已经不再支持 IE8。</p>

<p>但并没有像我们想象中的那样，只是简单换一下 <code>jQuery</code> 版本就可以正常运行了。</p>

<h2>0x03 default or catch</h2>

<p>这是遇到的第一个问题。在兼容性测试过程中，对下面的代码：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">function</span> <span class="nx">_interopRequireDefault</span><span class="p">(</span><span class="nx">obj</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">obj</span> <span class="o">&amp;&amp;</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">__esModule</span> <span class="o">?</span> <span class="nx">obj</span> <span class="o">:</span> <span class="p">{</span> <span class="k">default</span><span class="o">:</span> <span class="nx">obj</span> <span class="p">};</span> <span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>或者这种：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">_main2</span><span class="p">.</span><span class="k">default</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>在 IE8 下会直接报”缺少标识符、字符串或数字”的错。</p>

<p>我们得在对象的属性上加 <code>''</code> 才可以。就像下面这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">function</span> <span class="nx">_interopRequireDefault</span><span class="p">(</span><span class="nx">obj</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">return</span> <span class="nx">obj</span> <span class="o">&amp;&amp;</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">__esModule</span> <span class="o">?</span> <span class="nx">obj</span> <span class="o">:</span> <span class="p">{</span> <span class="err">‘</span><span class="k">default</span><span class="err">’</span><span class="o">:</span> <span class="nx">obj</span> <span class="p">};</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">_main2</span><span class="p">[</span><span class="s1">&#39;default&#39;</span><span class="p">];</span>
</span></code></pre></td></tr></table></div></figure>


<p>至于原因，并不是 IE8 下对象的属性必须得加 <code>''</code> 才行，而是 <code>default</code> 的问题，作为一个关键字，同样的问题还包括 <code>catch</code>。</p>

<p>这两种情况，可以通过使用 <code>transform-es3-property-literals</code> 和 <code>transform-es3-member-expression-literals</code> 这两个插件搞定。</p>

<p>总之，在平时写代码的时候避免使用关键字，或者保留字作为对象的属性值，尤其是在习惯不加引号的情况下。相关讨论：<a href="https://github.com/airbnb/javascript/issues/61">Allow reserved words for properties</a></p>

<h2>0x04 es5-shim、es5-sham</h2>

<p>为了兼容像 IE8 这样的老版本浏览器，我们引入 <code>es5-shim</code> 作为 polyfill。</p>

<p>但在遇到 <code>Object.defineProperty</code> 仍提示 &ldquo;对象不支持此操作&rdquo;</p>

<blockquote><p>As currently implemented, the Object.defineProperty shim will not install on IE8 because IE8 already has such a method. However, the built-in IE8 method only works when applied to DOM objects.</p></blockquote>

<p>其实 es5-shim 明确说明，这个方法的 polyfill 在 IE8 会失败，因为 IE8 已经有个同名的方法，但只是用于 DOM 对象。</p>

<p>同样的问题还包括 <code>Object.create</code>，上述问题可以再引入 es5-sham 解决.</p>

<h2>0x05 addEventListener</h2>

<p>项目中有部分代码直接使用 <code>addEventListener</code> 这个 API，但在 IE8 下的事件绑定并不是这个方法。</p>

<p>这个问题很容易解决，也无需去写额外的 polyfill。我们已经把 jQuery 换成 1.x，所以只需把代码中 <code>addEventListener</code> 换成 <code>jQuery</code> 的写法就 Okay 了。</p>

<p><code>jQuery</code> 其实为我们封装了很多 API，并做了很多兼容性的封装，类似的只要使用封装好的就可以了。</p>

<h2>0x06 无法获取未定义或 null 引用的属性</h2>

<p>这个问题是在特定场景下【转人工】出现的，出现问题的不是 IE8，而是 IE9 和 IE10。</p>

<p>原因是 ocs 实例创建失败，因为没有调用父类的构造函数。</p>

<p>通过安装 <code>transform-es2015-classes</code> 和 <code>transform-proto-to-assign</code> 解决。</p>

<p>在配置项加上这两个插件的配置：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="s2">&quot;plugins&quot;</span><span class="o">:</span> <span class="p">[</span>
</span><span class='line'>      <span class="p">[</span><span class="s2">&quot;transform-es2015-classes&quot;</span><span class="p">,</span> <span class="p">{</span> <span class="s2">&quot;loose&quot;</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}],</span>
</span><span class='line'>      <span class="s2">&quot;transform-proto-to-assign&quot;</span>
</span><span class='line'>
</span><span class='line'>  <span class="p">]</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h2>0x07 postMessage</h2>

<p>虽然 <code>postMessage</code> 是 HTML5 的特性，但 IE8 和 Firefox3 很早就实现了这个 API，当然，跟后来的标准并不一致。这其实也不能怪 IE8。</p>

<blockquote><p>The postMessage method is supported in Internet Explorer from version 8, Firefox from version 3 and Opera from version 9.5.</p></blockquote>

<p>我们可能会这样去使用：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">parent</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">({</span><span class="nx">success</span><span class="o">:</span> <span class="s1">&#39;ok&#39;</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="err">‘</span><span class="nx">mirreal</span><span class="err">’</span><span class="p">},</span> <span class="err">‘</span><span class="o">*</span><span class="err">’</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>但是为了兼容 IE8，我们得转成字符串：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">parent</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span><span class="nx">success</span><span class="o">:</span> <span class="s1">&#39;ok&#39;</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">&quot;mirreal&quot;</span><span class="p">}),</span> <span class="err">‘</span><span class="o">*</span><span class="err">’</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>另外一个需要注意的点是：在 IE8 下 <code>window.postMessage</code> 是同步的。</p>

<blockquote><p>window.postMessage is syncronouse in IE 8</p></blockquote>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">syncronouse</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class='line'><span class="nb">window</span><span class="p">.</span><span class="nx">onmessage</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">syncronouse</span><span class="p">);</span> <span class="c1">// 在 IE8 下会在控制台打印 true</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'><span class="nb">window</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="s1">&#39;test&#39;</span><span class="p">,</span> <span class="s1">&#39;*&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">syncronouse</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>0x08 IE8/IE9 的控制台</h2>

<p>遇到一个奇怪的问题，在刚开始遇到的时候（其实搞清楚原因，好像也挺正常的），小蜜在 IE8 IE9 无法加载。在 IE8 那个古老浏览器的左下角，好像也是唯一会在页面提示脚本错误的浏览器，提示 <code>script error</code>。</p>

<p>第一反应就是应该又是某个函数在 IE 下不支持，准备打开控制台看看到底哪里报错，结果却什么事都没有了，页面竟然顺畅地加载出来了，这下该怎么调试好呢？</p>

<p>开始思考：什么东西是依赖控制台而存在的，到底会是什么呢。。。其实就是控制台本身。</p>

<p>原因就是我们在代码中添加了一些控制信息会打印在控制台，而 IE8/IE9 要开启 IE Dev Tools 才能使用 <code>console</code> 对象。</p>

<p>切忌把 IE8/9 想成 Chrome/Firefox，以为永远有 <code>window.console</code> 可用.终于，IE10 改邪归正，<code>console</code> 不再像段誉的六脉神剑时有时无。</p>

<blockquote><p>console.log is there in IE8, but the console object isn&rsquo;t created until you open DevTools. Therefore, a call to console.log may result in an error, for example if it occurs on page load before you have a chance to open the dev tools.</p></blockquote>

<p>但只要 IE8/9 还在一天，console 检查还是不能少的</p>

<p>事实上，IE8/9 从未死去，所以，得写成这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">console</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;log here&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>要是有一堆 <code>console.log</code>, <code>console.count</code>, <code>console.error</code>, <code>console.time</code>, <code>console.profile</code>，&hellip; 这样去写，那还不把人写到恶心死。</p>

<p>写个简单的 console polyfill 吧，检测是否存在 <code>console</code>，不存在可以创见一个同名的空方法达到不报错的目的。当然，生产环境的代码其实也不会有那么多奇奇怪怪的 <code>console</code>。</p>

<h2>0x09 定义文档兼容性</h2>

<p><code>X-UA-Compatible</code> 当初是针对 IE8 新加的一个配置。用于为 IE8 指定不同的页面渲染模式，比如使用 IE7 兼容模式，或者是采用最新的引擎。</p>

<p>现在基本也不需要前者的降级模式，更多的是写入 <code>IE=edge</code> 支持最新特性。而 <code>chrome=1</code> 则会激活 Google Chrome Frame，前提是你的 IE 安装过这个插件。</p>

<p>有什么用呢，当然有用，有些 API 是作为新特性存在于 IE8 中的，比如 <code>JSON</code>，不开启的话就用不了。</p>

<h3>为什么要用 X-UA-Compatible？</h3>

<p>在 IE8 刚推出的时候，很多网页由于重构的问题，无法适应较高级的浏览器，所以使用 <code>X-UA-Compatible</code> 强制 IE8 采用低版本方式渲染。</p>

<p>比如：使用下面这段代码后，开发者无需考虑网页是否兼容 IE8 浏览器，只要确保网页在 IE6、IE7 下的表现就可以了。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">&quot;X-UA-Compatible&quot;</span> <span class="na">content=</span><span class="s">&quot;IE=EmulateIE7&quot;</span> <span class="nt">/&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>而这段代码：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">&quot;X-UA-Compatible&quot;</span> <span class="na">content=</span><span class="s">&quot;IE=edge,chrome=1&quot;</span> <span class="nt">/&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>IE=edge</code> 告诉 IE 使用最新的引擎渲染网页，<code>chrome=1</code> 则可以激活 Chrome Frame[1]。</p>

<h2>0x0a 条件注释 or 条件编译</h2>

<p>最后说说 IE 的条件注释，用法如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='text'><span class='line'>!    [if !IE]    The NOT operator. This is placed immediately in front of the feature, operator, or subexpression to reverse the Boolean meaning of the expression.
</span><span class='line'>
</span><span class='line'>lt    [if lt IE 5.5]  The less-than operator. Returns true if the first argument is less than the second argument.
</span><span class='line'>
</span><span class='line'>lte   [if lte IE 6]   The less-than or equal operator. Returns true if the first argument is less than or equal to the second argument.
</span><span class='line'>
</span><span class='line'>gt    [if gt IE 5]    The greater-than operator. Returns true if the first argument is greater than the second argument.
</span><span class='line'>
</span><span class='line'>gte   [if gte IE 7]   The greater-than or equal operator. Returns true if the first argument is greater than or equal to the second argument.
</span><span class='line'>
</span><span class='line'>( )   [if !(IE 7)]    Subexpression operators. Used in conjunction with boolean operators to create more complex expressions.
</span><span class='line'>
</span><span class='line'>&amp; [if (gt IE 5)&amp;(lt IE 7)]    The AND operator. Returns true if all subexpressions evaluate to true
</span><span class='line'>
</span><span class='line'>| [if (IE 6)|(IE 7)]  The OR operator. Returns true if any of the subexpressions evaluates to true.
</span></code></pre></td></tr></table></div></figure>


<p>另外一个类似的东西是在 Javascript 中的条件编译（conditional compilation）。我们可以使用这段简单的代码来做浏览器嗅探：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">isIE</span> <span class="o">=</span> <span class="cm">/*@cc_on!@*/</span><span class="kc">false</span>
</span></code></pre></td></tr></table></div></figure>


<p>在其他浏览器中，false 前的被视为注释，而在 IE 中，<code>/*@cc_on .... @*/</code> 之间的部分可以被 IE 识别并作为程序执行，同时启用 IE 的条件编译。</p>

<p>常用变量如下：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='text'><span class='line'>* @_win32 如果在 Win32 系统上运行，则为 true。
</span><span class='line'>* @_win16 如果在 Win16 系统上运行，则为 true。
</span><span class='line'>* @_mac 如果在 Apple Macintosh 系统上运行，则为 true。
</span><span class='line'>* @_alpha 如果在 DEC Alpha 处理器上运行，则为 true。
</span><span class='line'>* @_x86 如果在 Intel 处理器上运行，则为 true。
</span><span class='line'>* @_mc680x0 如果在 Motorola 680x0 处理器上运行，则为 true。
</span><span class='line'>* @_PowerPC 如果在 Motorola PowerPC 处理器上运行，则为 true。
</span><span class='line'>* @_jscript 始终为 true。
</span><span class='line'>* @_jscript_build 包含 JavaScript 脚本引擎的生成号。
</span><span class='line'>* @_jscript_version 包含 major.minor 格式的 JavaScript 版本号。
</span></code></pre></td></tr></table></div></figure>


<blockquote><p>Internet Explorer 11 之前的所有版本的 Internet Explorer 都支持条件编译。  从 Internet Explorer 11 标准模式开始，Windows 8.x 应用商店应用不支持条件编译。</p></blockquote>

<h2>后：</h2>

<p>之前一直在做移动端的开发，没想到做 PC 端也会遇到这么多的兼容性问题。不同于移动端设备的繁杂和不确定性，PC 版的兼容更侧重于对特定浏览器的特性的了解，相比而言更为明确，而非因为某一款手机的诡异表现。</p>

<h2>参考文档：</h2>

<p><a href="https://github.com/airbnb/javascript/issues/61">Allow reserved words for properties</a></p>

<p><a href="https://github.com/es-shims/es5-shim/issues/5">IE8 defineProperty/getOwnPropertyDescriptor clash with shim</a></p>

<p><a href="http://babeljs.io/docs/plugins/transform-runtime/">Runtime transform</a></p>

<p><a href="https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/definitions.js">babel-plugin-transform-runtime definitions</a></p>

<p><a href="https://github.com/babel/babelify/issues/133">super() not calling parent&rsquo;s constructor on IE9</a></p>

<p><a href="http://help.dottoro.com/ljgheukc.php">postMessage method (window) Javascript</a></p>

<p><a href="https://msdn.microsoft.com/library/gg589530(v=vs.85">使用 F12 工具控制台查看错误和状态</a>.aspx)</p>

<p><a href="https://msdn.microsoft.com/zh-cn/library/cc288325(v=vs.85">定义文档兼容性</a>.aspx)</p>

<p><a href="https://msdn.microsoft.com/zh-cn/library/121hztk3(v=vs.94">条件编译 (JavaScript)</a>.aspx)</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[为什么是 JSONP]]></title>
    <link href="https://blog.mirreal.net/blog/2016/04/01/what-is-jsonp-and-why/"/>
    <updated>2016-04-01T21:06:58+08:00</updated>
    <id>https://blog.mirreal.net/blog/2016/04/01/what-is-jsonp-and-why</id>
    <content type="html"><![CDATA[<!-- # JSONP，跨域，安全策略，以及 WHY

服务器端、浏览器端

原生实现，包装 Api

同步请求？异步请求

文件上传

安全，跨域问题 -->


<h2>AJAX、JSON、JSONP</h2>

<p>在 WEB 开发中，经常见到诸如 AJAX、JSON、JSONP 这些词，但这三种东西到底是什么，又有什么关系和区别。</p>

<h3>AJAX （Asynchronous JavaScript + XML）</h3>

<blockquote><p>Ajax isn’t a technology. It’s really several technologies, each flourishing in its own right, coming together in powerful new ways. Ajax incorporates:</p><p>* standards-based presentation using XHTML and CSS;<br/>* dynamic display and interaction using the Document Object Model;<br/>* data interchange and manipulation using XML and XSLT;<br/>* asynchronous data retrieval using XMLHttpRequest;<br/>* and JavaScript binding everything together.</p><footer><strong>Jesse James Garrett,</strong> <cite><a href='http://adaptivepath.org/ideas/ajax-new-approach-web-applications/'>Ajax: A New Approach to Web Applications</a></cite></footer></blockquote>


<p>异步 JavaScript + XML, 本身不是一种技术, 是在2005年由 Jesse James Garrett 提出的一个术语, 描述了一种结合使用大量已有技术的方式, 包括: HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 还有最重要的 XMLHttpRequest 对象.</p>

<p>尽管在 AJAX 中 X 代表 XML, 但现在 JSON 使用的更多，因为 JSON 具有很多优势，比如更轻量并且是 JavaScript 的一部分. 在 AJAX 模型中 JSON 和 XML 都用于承载信息.</p>

<h3>JSON（Javascript Object Notation）</h3>

<p>JSON 是一种轻量级的数据交换格式。由道格拉斯·克罗克福特（Douglas Crockford）在 2012 年发明，并逐渐取代 XML 成为事实上的数据交换格式标准。</p>

<p>JSON 基于 JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。但采用完全独立于语言的文本格式，并使用了类似于 C 语言家族的习惯。</p>

<p>在 JSON 中，一共 6 种数据类型：</p>

<ul>
<li>number：跟 Javascript 的数值一致，除去未曾使用的八进制与十六进制格式，和一些编码细节</li>
<li>boolean：<code>true</code> 和 <code>false</code></li>
<li>string：是由双引号包围的任意数量Unicode字符的集合，使用反斜线转义</li>
<li>null：<code>null</code></li>
<li>array：数组是值（value）的有序集合。一个数组以“[”（左中括号）开始，“]”（右中括号）结束，值之间使用“,”（逗号）分隔</li>
<li>object：对象是一个无序的“‘名称/值’对”集合。一个对象以“{”（左括号）开始，“}”（右括号）结束，每个“名称”后跟一个“:”（冒号）；“‘名称/值’ 对”之间使用“,”（逗号）分隔</li>
</ul>


<p>以及上面的任意组合。</p>

<p>在 javascript 中有一个全局的对象 JSON，包含两个方法 <code>JSON.stringify()</code> 和 <code>JSON.parse()</code>，用于序列化和解析 JSON。</p>

<h3>JSONP（JSON with Padding）</h3>

<p>最初是开发者为了解决跨域问题搞出来的一个颇为奇怪的东西，产生原因和名字一样古怪，光听名字恐怕没几个人知道说的是个什么东西。</p>

<p>因为 ajax 请求收到同源策略的限制不允许跨域访问，而在实际开发中又常常会有类似的需求。</p>

<p>刚好 <code>&lt;script&gt;</code> 标签可以引用其他站点的静态资源，想想我们有时候在站点引入的数据统计类的 js。</p>

<p>但我们要的是数据，而不是一段静态代码，怎么办？</p>

<p>这还不简单吗，让服务器动态生成 js ，再把数据放进去不就可以吗。为了区分不数据，还需要针对返回的数据做一个标识，其实就是在数据外面包裹一个函数名。</p>

<p>然后需要浏览器端预先设置好这样一个函数，返回的数据就相当于一次执行过程，对获取数据的处理。</p>

<h3>总结</h3>

<ol>
<li>AJAX 是一类技术的集合，其中最重要的是 <code>XMLHttpRequest</code></li>
<li>JSON 是一个数据交换格式</li>
<li>JSONP 是为了解决跨域问题搞出来的一种获取数据的方式</li>
</ol>


<h2>下面举个栗子</h2>

<h3>服务器</h3>

<p>这里使用 node 返回一段简单的数据。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="cm">/**</span>
</span><span class='line'><span class="cm"> * 一个简单的 http 服务器,返回 json 数据</span>
</span><span class='line'><span class="cm"> * 跟 node 主页上的那个经典例子没太大差别</span>
</span><span class='line'><span class="cm"> */</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;http&#39;</span><span class="p">);</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">urllib</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;url&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">host</span> <span class="o">=</span> <span class="s1">&#39;127.0.0.1&#39;</span><span class="p">;</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">port</span> <span class="o">=</span> <span class="mi">9999</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">{</span><span class="s1">&#39;name&#39;</span><span class="o">:</span> <span class="s1">&#39;Mirreal&#39;</span><span class="p">,</span> <span class="s1">&#39;age&#39;</span><span class="o">:</span> <span class="s1">&#39;24&#39;</span><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">params</span> <span class="o">=</span> <span class="nx">urllib</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">url</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">params</span><span class="p">.</span><span class="nx">query</span> <span class="o">&amp;&amp;</span> <span class="nx">params</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">str</span> <span class="o">=</span>  <span class="nx">params</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">callback</span> <span class="o">+</span> <span class="s1">&#39;(&#39;</span> <span class="o">+</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;)&#39;</span><span class="p">;</span> <span class="c1">// jsonp</span>
</span><span class='line'>    <span class="nx">res</span><span class="p">.</span><span class="nx">writeHead</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="p">{</span> <span class="s1">&#39;Content-Type&#39;</span><span class="o">:</span> <span class="s1">&#39;application/javascript&#39;</span> <span class="p">});</span>
</span><span class='line'>    <span class="nx">res</span><span class="p">.</span><span class="nx">end</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span>
</span><span class='line'>  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">res</span><span class="p">.</span><span class="nx">end</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span> <span class="c1">// 普通的json</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="p">}).</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="nx">host</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;server is listening on port &#39;</span> <span class="o">+</span> <span class="nx">port</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>


<h3>浏览器</h3>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// zepto 的写法</span>
</span><span class='line'><span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
</span><span class='line'>  <span class="nx">type</span><span class="o">:</span> <span class="s1">&#39;GET&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">url</span><span class="o">:</span> <span class="s1">&#39;http://127.0.0.1:9999&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">_input_charset</span><span class="o">:</span> <span class="s1">&#39;utf-8&#39;</span> <span class="p">},</span>
</span><span class='line'>  <span class="nx">dataType</span><span class="o">:</span> <span class="s1">&#39;jsonp&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">timeout</span><span class="o">:</span> <span class="mi">300</span><span class="p">,</span>
</span><span class='line'>  <span class="nx">context</span><span class="o">:</span> <span class="nx">$</span><span class="p">(</span><span class="s1">&#39;body&#39;</span><span class="p">),</span>
</span><span class='line'>  <span class="nx">success</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">){</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
</span><span class='line'>  <span class="p">},</span>
</span><span class='line'>  <span class="nx">error</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">xhr</span><span class="p">,</span> <span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Ajax error!&#39;</span><span class="p">)</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>


<p>这样就很轻松的通过 JSONP 的方式获取到数据，我们也不需要关心里面究竟是怎么一回事，但经常会有人问起像“为什么 jsonp 不能使用 POST 方法”的问题，其实稍微了解一下 JSONP 的原理，这种问题完全就不存在了。</p>

<p>虽然像 jQuery 这类库将 jsonp 封装到 Ajax 上，但准确来讲是不对的。因为 jsonp 只是通过动态地通过 <code>&lt;script&gt;</code> 标签去请求一段 js 代码（或者叫数据），而非使用 XMLHttpRequest ，原理就像下面这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="cm">/**</span>
</span><span class='line'><span class="cm"> * 对 jsonp 的一种简单封装</span>
</span><span class='line'><span class="cm"> * @param {Object} options</span>
</span><span class='line'><span class="cm"> * @returns null</span>
</span><span class='line'><span class="cm"> */</span>
</span><span class='line'><span class="kd">function</span> <span class="nx">getJsonp</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">callbackName</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">callbackName</span><span class="p">;</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">url</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">scriptElem</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;script&#39;</span><span class="p">);</span>
</span><span class='line'>  <span class="nx">scriptElem</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">&#39;src&#39;</span><span class="p">,</span> <span class="nx">url</span> <span class="o">+</span> <span class="s1">&#39;?callback=&#39;</span> <span class="o">+</span> <span class="nx">callbackName</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">scriptElem</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">delete</span> <span class="nb">window</span><span class="p">[</span><span class="nx">callbackName</span><span class="p">];</span>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
</span><span class='line'>  <span class="p">};</span>
</span><span class='line'>
</span><span class='line'>  <span class="nx">scriptElem</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="s1">&#39;load error&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">delete</span> <span class="nb">window</span><span class="p">[</span><span class="nx">callbackName</span><span class="p">];</span>
</span><span class='line'>    <span class="k">this</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
</span><span class='line'>  <span class="p">};</span>
</span><span class='line'>
</span><span class='line'>  <span class="nb">window</span><span class="p">[</span><span class="nx">callbackName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">success</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// 调用</span>
</span><span class='line'>  <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">scriptElem</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>这段代码对 JSONP 进行一层简单包装，调用也很简单：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">getJsonp</span><span class="p">({</span>
</span><span class='line'>  <span class="s1">&#39;url&#39;</span><span class="o">:</span> <span class="s1">&#39;http://127.0.0.1:9999/&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="s1">&#39;callbackName&#39;</span><span class="o">:</span> <span class="s1">&#39;log&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="s1">&#39;success&#39;</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;我是回调函数,我拿到数据了&#39;</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>


<p>看上去代码还挺长的，实际上核心代码不多，分三步：</p>

<h4>1.创建一个 <code>&lt;script&gt;</code> 标签，并设置其 url</h4>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">scriptElem</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;script&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">scriptElem</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">&#39;src&#39;</span><span class="p">,</span> <span class="nx">url</span> <span class="o">+</span> <span class="s1">&#39;?callback=&#39;</span> <span class="o">+</span> <span class="nx">callbackName</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<h4>2.设置回调函数</h4>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">window</span><span class="p">[</span><span class="nx">callbackName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">success</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>这里简单处理，直接把传入的回调函数设置成全局的</p>

<h4>3.调用</h4>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">scriptElem</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<p>实际上就是把 <code>&lt;script&gt;</code> 加到 html 文档中，这样就会去加载标签的内容，也就是一个 js 文件。</p>

<p>但通常现实中跑的代码内容会更多，包含一些错误控制、参数拼接、超时处理、性能安全等方面的，但它仍然清楚地描述 JSONP 的原理。</p>

<h2>安全</h2>

<p>早期的浏览器处于安全层面的考量，使用同源策略，限制了一个源（origin）中加载文本或脚本与来自其它源（origin）中资源的交互方式。</p>

<p>但是随着互联网的发展催生了跨域访问进行数据交互的需求，于是 JSONP 就产生了，以及后来的 CORS 机制，允许 XMLHttpRequest 对象发起跨域的请求。</p>

<p>但是另一方面，也增加了安全风险，我们在使用的时候应当更加谨慎小心，防止 XSS、CSRF 等攻击。</p>

<!-- JSONP 的安全问题通常属于 CSRF（ Cross-site request forgery 跨站请求伪造）攻击范畴。 -->


<h2>其他</h2>

<h3>数据预览</h3>

<p>之前碰到一个问题，为什么调用一些接口返回的数据无法使用 Chrome 预览，我自己写测试接口的时候也碰到过。其实这里完全没有什么技术可言，只是因为没有在 response 头部加上 <code>Content-Type: application/javascript</code>，仅此而已。</p>

<h2>参考文档</h2>

<ul>
<li><a href="https://developer.mozilla.org/zh-CN/docs/AJAX">AJAX | MDN</a></li>
<li>Jesse James Garrett 的文章：<a href="http://adaptivepath.org/ideas/ajax-new-approach-web-applications/">Ajax: A New Approach to Web Applications</a></li>
<li><a href="http://www.json.org/json-zh.html">JSON</a></li>
</ul>

]]></content>
  </entry>
  
</feed>
