友情园 發表於 2020-3-30 16:04:00

《JavaScript 模式》读书笔记(4)— 函数5

<p>  这一篇是函数部分的最后一篇。我们来聊聊Curry化。</p>
<p><span style="font-size: 18px"><strong>十、Curry<br></strong></span></p>
<p><strong>  </strong>这部分我们主要讨论Curry化和部分函数应用的内容。但是在深入讨论之前,我们需要先了解一下函数应用的含义。</p>
<p>&nbsp;</p>
<p><span style="font-size: 16px"><strong>函数应用</strong></span></p>
<p><strong>  </strong>在一些纯粹的函数式编程语言中,函数并不描述为被调用(即called或invoked),而是描述为应用(applied)。在JavaScript中,我们可以做同样的事情,使用方法Function.prototype.apply()来应用函数,这是由于JavaScript中的函数实际上是对象,并且它们还具有如下方法。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义函数</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> sayHi = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(who) {
    console.log(</span>"Hello" + (who? ", " + who : "") + "!"<span style="color: rgba(0, 0, 0, 1)">);
};

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 调用函数</span>
sayHi(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出"Hello"</span>
sayHi('world'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出"Hello, world!"</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 应用函数</span>
sayHi.apply(<span style="color: rgba(0, 0, 255, 1)">null</span>, ["hello"]); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出"Hello, hello!"</span></pre>
</div>
<p>  正如上面的例子所看到的,调用(invoking)函数和应用(applying)函数可以得到完全相同的结果。<strong>apply()带有两个参数:第一个参数为将要绑定到该函数内部this的一个对象,而第二个参数是一个数组或多个参数变量,这些参数将变成可用于该函数内部的类似数组的arguments对象。如果第一个参数为null(空),那么this将指向全局对象,此时得到的结果就恰好如同调用一个非指定对象时的方法。</strong></p>
<p><strong>  </strong>当函数是一个对象的方法时,此时不能传递null引用。这种情况下,这里的对象将成为apply()的第一个参数:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义函数</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> alien=<span style="color: rgba(0, 0, 0, 1)"> {
    sayHi: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(who) {
      console.log(</span>"Hello" + (who? ", " + who : "") + "!"<span style="color: rgba(0, 0, 0, 1)">);
    }
}
alien.sayHi(</span>'world'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出"Hello, world!"</span>
sayHi.apply(alien, ["humans"]); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出"Hello, humans!"</span></pre>
</div>
<p>  在上面的代码中,sayHi()内部的this指向了alien对象。而在之前的例子中,this指向了全局对象。</p>
<p>  正如上面的两个例子所展示的那样,这些都表明我们考虑的“调用函数”并不只是“句法糖(syntactic sugar)”,而是等价于函数应用。</p>
<p>  请注意,除了apply()以外,Function.prototype对象还有一个call()方法,但是这仍然只是建立在apply()之上的语法糖而已。有时候最好使用该语法糖:即当函数仅带有一个参数时,可以根据实际情况避免创建只有一个元素的数组的工作。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在这种情况下,第二种更有效率,节省了一个数组</span>
sayHi.apply(alien,["humans"<span style="color: rgba(0, 0, 0, 1)">]);
sayHi.call(alien,</span>"humans");</pre>
</div>
<p>&nbsp;</p>
<p><span style="font-size: 16px"><strong>部分应用</strong></span></p>
<p><strong>  现在我们知道,调用函数实际上就是将一个参数集合应用到一个函数中,那有没有可能只传递部分参数,而不是所有参数?</strong>这种情况就和手动处理一个数学函数所常采用的方法是相似的。假定有一个函数add()用以将两个数字加在一起:x和y。下面的代码片段展示了给定x值为5,且y值为4的情况下的解决方案。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 出于演示的目的</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> 并不是合法的JavaScript</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> add(x,y) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 有以下函数</span>
add(5,4<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第1步,替换一个参数</span>
<span style="color: rgba(0, 0, 255, 1)">function</span> add(5<span style="color: rgba(0, 0, 0, 1)">, y){
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> 5 +<span style="color: rgba(0, 0, 0, 1)"> y;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第2步,替换其他参数</span>
<span style="color: rgba(0, 0, 255, 1)">function</span> add(5, 4<span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> 5 + 4<span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p>  <strong>再提醒一遍,第1、2步的代码是不合法的,仅演示目的。</strong></p>
<p><strong>  </strong>上面的代码段演示了如何手工解决部分函数应用的问题。可以获取第一个参数的值,并且在整个函数中用已知的值5替代未知的x,然后重复同样的步骤直至用完了所有的参数。</p>
<p>  对这个例子中的步骤1可以称为部分应用(partial application),即我们金鹰用了第一个参数。当执行部分应用时,并不会获得结果,相反会获得另一个函数。</p>
<p>  下面的代码片段演示了家乡的partialApply()方法的使用示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> add = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (x,y) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
};

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 完全应用</span>
add.apply(<span style="color: rgba(0, 0, 255, 1)">null</span>,); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 9</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 部分应用</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> newadd = add.partialApply(<span style="color: rgba(0, 0, 255, 1)">null</span>,);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 应用一个参数到新函数中</span>
newadd.apply(<span style="color: rgba(0, 0, 255, 1)">null</span>,); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 9</span></pre>
</div>
<p>  如上面的代码所示,部分应用向我们提供了另一个新函数,随后再以其他参数调用该函数。这种运行方式实际上与add(5)(4)有一些类似,这是由于add(5)返回了一个可在后来用(4)来调用的函数。</p>
<p>  <strong>此外,我们所熟悉的add(5, 4)调用方式可能并不像是“句法糖(syntactic sugar)”,相反,使用add(5)(4)才像是“<strong>句法糖(syntactic sugar)</strong>”。</strong></p>
<p><strong>  </strong>现在,返回到现实,JavaScript中并没有partialApply()方法和函数,默认情况下也并不会出现与上面类似的行为。但是可以构造出这些函数,因为JavaScript的动态性足够支持这种行为。</p>
<p>  <strong>使函数理解并处理部分应用的过程就成为Curry过程(Currying)。</strong></p>
<p>&nbsp;</p>
<p><span style="font-size: 16px"><strong>Curry化</strong></span></p>
<p><strong>  </strong>这里的curry源于数学家Haskell Curry的名字。Curry化是一个转换过程,即我们执行函数转换的过程。那么,我们如何Curry化一个函数?其他的函数式语言可能已经将这种Curry化转换构建到语言本身中,并且所有的函数已经默认转换过,在JavaScript中,可以将add()函数修改成一个用于处理部分应用的Curry化函数。</p>
<p>  下面,我们来看个例子:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> curry化的add()函数</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> 接受部分参数列表</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> add(x,y) {
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> oldx = x,oldy =<span style="color: rgba(0, 0, 0, 1)"> y;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">typeof</span> oldy === 'undefined') { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 部分</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(newy) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> oldx +<span style="color: rgba(0, 0, 0, 1)"> newy;
      };
    }
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 完全应用</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 测试</span>
console.log(<span style="color: rgba(0, 0, 255, 1)">typeof</span> add(5)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出“function”</span>
add(3)(4); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> 创建并存储一个新函数</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> add2000 = add(2000<span style="color: rgba(0, 0, 0, 1)">);
add2000(</span>19); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出2010</span></pre>
</div>
<p>  在上面的代码段中,当第一次调用add()时,它为返回的内部函数创建了一个闭包。该闭包将原始的x和y值存储到私有变量oldx和oldy中。第一个私有变量oldx将在内部函数执行的时候使用。如果没有部分应用,并且同时传递x和y值,该函数则继续执行,并简单将其相加。这种add()实现与实际需求相比显得比较冗长,在这里只是出于演示的目的这样实现。下面将显示一个更为精简的实现版本。其中并没有oldx和oldy,仅是因为原始x隐式的存储在闭包中,并且还将y作为局部变量复用,而不是像之前那样创建一个新的变量newy:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> curry化的add()函数</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> 接受部分参数列表</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> add(x, y) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">typeof</span> y === 'undefined') { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">部分</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(y) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
      };
    }
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 完全应用</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
}</span></pre>
</div>
<p>  在这些例子中,函数add()本身负责处理部分应用。但是能够以更通用的方式执行相同给的任务么?也就是说,是否可以将任意的函数转换成一个新的可以接收部分参数的函数?</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> schonfinkelize(fn) {
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> slice =<span style="color: rgba(0, 0, 0, 1)"> Array.prototype.slice,
      stored_args </span>= slice.call(arguments,1<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> new_args =<span style="color: rgba(0, 0, 0, 1)"> slice.call(arguments),
            args </span>=<span style="color: rgba(0, 0, 0, 1)"> stored_args.concat(new_args);
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> fn.apply(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,args);
    }
}</span></pre>
</div>
<p>  schonfinkelize()函数可能不应该有这么复杂,只是由于JavaScript中arguments并不是一个真实的数组。从Array.prototype中借用slice()方法可以帮助我们将arguments变成一个数组,并且使用该数组更加方便。当schonfinkelize()第一次调用时,它存储了一个指向slice()方法的私有引用(名为slice),并且还存储了调用该方法后的参数(存入stored_args中),该方法仅剥离了第一个参数,这是因为第一个参数是将被curry化的函数。然后,schonfinkelize()返回了一个新函数。当这个新函数被调用时,它访问了已经私有存储的参数stored_args以及slice引用。这个新函数必须将原有的部分应用参数(stored_args)合并到新参数(new_args),然后再将它们应用到原始函数fn中(也仅在闭包中私有可用)。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>  我们来测试下上面的转换方法:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> schonfinkelize(fn) {
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> slice =<span style="color: rgba(0, 0, 0, 1)"> Array.prototype.slice,
      stored_args </span>= slice.call(arguments,1<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> new_args =<span style="color: rgba(0, 0, 0, 1)"> slice.call(arguments),
            args </span>=<span style="color: rgba(0, 0, 0, 1)"> stored_args.concat(new_args);
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> fn.apply(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,args);
    }
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 普通函数</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> add(x, y){
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将一个函数curry化并获得一个新的函数</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> newadd = schonfinkelize(add,5<span style="color: rgba(0, 0, 0, 1)">);
console.log(newadd(</span>4)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出9</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 另一种选择,直接调用新函数</span>
console.log(schonfinkelize(add,6)(7)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出13</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转换函数并不局限于单个参数或者单步Curry化</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)"> 普通函数</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> addSome(a, b, c, d, e) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> a + b + c + d +<span style="color: rgba(0, 0, 0, 1)"> e;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可运行于任意数量的参数</span>
console.log(schonfinkelize(addSome,1,2,3)(5,5<span style="color: rgba(0, 0, 0, 1)">));

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 两步curry化</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> addOne = schonfinkelize(addSome,1<span style="color: rgba(0, 0, 0, 1)">);
console.log(addOne(</span>10,10,10,10)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">41</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> addSix = schonfinkelize(addOne,2,3<span style="color: rgba(0, 0, 0, 1)">);
console.log(addSix(</span>5,5)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 16</span></pre>
</div>
<p>  上面是完整的例子和测试。</p>
<p>  那什么时候适合使用Curry化呢?当发现正在调用同一个函数,并且传递的参数绝大多数都是相同的,那么该函数可能是用于Curry化的一个很好的候选参数。可以通过将一个函数集合部分应用到函数中,从而动态创建一个新函数。这个新函数将会保存重复的参数(因此,不必每次都传递这些参数),并且还会使用预填充原始函数所期望的完整参数列表。</p>
<p>&nbsp;</p>
<p><span style="font-size: 18px"><strong>小结</strong></span></p>
<p><strong>  </strong>在JavaScript中,有关函数的部分是十分重要的,我们本系列文章相关的主要函数部分已经到此告一段落了。本篇讨论了有关函数的背景和术语。学习了JavaScript中两个重要的特征。即:</p>
<ul>
<li><strong>函数是第一类对象,可以作为带有属性和方法的值以及参数进行传递。</strong></li>
<li><strong>函数提供了局部作用域,而其他打括号并不能提供这种局部作用域(当然现在的let是可以的)。此外还需要记住的是,声明的局部变量可被提升到局部作用域的顶部。</strong></li>
</ul>
<p>  创建函数的语法包括:</p>
<ul>
<li><strong>1.&nbsp; 函数命名表达式。</strong></li>
<li><strong>2. 函数表达式(与上面的相同,但是缺少一个名字),通常也称为匿名函数。</strong></li>
<li><strong>3. 函数声明,与其他语言中的函数的语法类似。</strong></li>
</ul>
<p>  在涵盖了函数的背景和语法之后,我们学习了一些有用的模式:</p>
<p>  1、API模式,它们可以帮助您为函数提供更好且更整洁的接口:</p>
<p>    <strong>回调模式:将函数作为参数进行传递。</strong></p>
<p><strong>    配置对象:有助于保持受到控制的函数的参数数量。</strong></p>
<p><strong>    返回函数:当一个函数的返回值是另一个函数时。</strong></p>
<p><strong>    Curry化:当新函数是基于现有函数,并加上部分参数列表创建时。</strong></p>
<p><strong>  </strong>2、初始化模式,它们可以帮助您在不污染全局命名空间的情况下,使用临时变量以一种更加整洁、结构化的方式执行初始化以及设置任务(当涉及web网页和应用程序时是非常普遍的)。这些模式包括:</p>
<p>    <strong>即时函数:只要定义之后就立即执行。</strong></p>
<p><strong>    即时对象初始化:匿名对象组织了初始化任务,提供了可被立即调用的方法。</strong></p>
<p><strong>    初始化时分支:帮助分支代码在初始化代码执行过程中仅检测一次,这与以后在程序生命周期内多次检测相反。</strong></p>
<p><strong>  </strong>3、性能模式,可以帮助加速代码运行,这些模式包括:</p>
<p>    <strong>备忘模式:使用函数属性以便使得计算过的值无须再次计算。</strong></p>
<p><strong>    自定义模式:以新的主体重写本身,以使得在第二次或以后调用时仅需执行更少的工作。</strong></p>
<p>&nbsp;</p>
<p>  好了,函数部分到此结束了。我们下面会开始学习对象模式部分。加油!fighting!</p>
<p>&nbsp;</p>
<p>  </p>

</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:Zaking,转载请注明原文链接:https://www.cnblogs.com/zaking/p/12592217.html</p><br><br>
来源:https://www.cnblogs.com/zaking/p/12592217.html
頁: [1]
查看完整版本: 《JavaScript 模式》读书笔记(4)— 函数5