与世无争逍遥一生 發表於 2020-5-11 16:48:00

实战 - JavaScript 函数式编程

<p><span style="font-size: 14px">最近和做技术的朋友聊天的时候,发现自己居然不能将函数式编程思想讲清楚,于是做一次复习</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 16px"><strong>一、函数是“一等公民”</strong></span></p>
<p><span style="font-size: 14px">常常都能听到这么一句话:<span style="color: rgba(128, 0, 0, 1)"><strong>在 JavaScript 中,函数是“一等公民”</strong></span>,这句话到底意味着什么?</span></p>
<p><span style="font-size: 14px">在编程语言中,<span style="color: rgba(128, 0, 0, 1)"><strong>一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量</strong> </span>——&nbsp;Christopher Strachey</span></p>
<p><span style="font-size: 14px">其实在很多传统语言中( 比如 C,JAVA 8 以前 )函数只可以声明和调用,无法像字符串一样作为参数使用</span></p>
<p><span style="font-size: 14px">而&nbsp;JavaScript 中的函数与其他数据类型处于平等地位,这是函数式编程的前提</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 16px"><strong>二、纯函数 (pure functions)</strong></span></p>
<p><span style="font-size: 14px">现在正式接触函数式编程,首先看一个简单的需求:</span></p>
<p><span style="font-size: 14px">有这样的一堆用户信息</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> arr =<span style="color: rgba(0, 0, 0, 1)"> [
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">赵信</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">1</span>, age: <span style="color: rgba(128, 0, 128, 1)">25</span>, high: <span style="color: rgba(128, 0, 128, 1)">176</span>, weight: <span style="color: rgba(128, 0, 128, 1)">62</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">艾希</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">2</span>, age: <span style="color: rgba(128, 0, 128, 1)">23</span>, high: <span style="color: rgba(128, 0, 128, 1)">161</span>, weight: <span style="color: rgba(128, 0, 128, 1)">46</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">阿狸</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">2</span>, age: <span style="color: rgba(128, 0, 128, 1)">27</span>, high: <span style="color: rgba(128, 0, 128, 1)">182</span>, weight: <span style="color: rgba(128, 0, 128, 1)">53</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">盖伦</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">1</span>, age: <span style="color: rgba(128, 0, 128, 1)">27</span>, high: <span style="color: rgba(128, 0, 128, 1)">175</span>, weight: <span style="color: rgba(128, 0, 128, 1)">78</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">沃里克</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">1</span>, age: <span style="color: rgba(128, 0, 128, 1)">42</span>, high: <span style="color: rgba(128, 0, 128, 1)">169</span>, weight: <span style="color: rgba(128, 0, 128, 1)">70</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">安妮</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">2</span>, age: <span style="color: rgba(128, 0, 128, 1)">16</span>, high: <span style="color: rgba(128, 0, 128, 1)">153</span>, weight: <span style="color: rgba(128, 0, 128, 1)">43</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">卡尔玛</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">2</span>, age: <span style="color: rgba(128, 0, 128, 1)">40</span>, high: <span style="color: rgba(128, 0, 128, 1)">168</span>, weight: <span style="color: rgba(128, 0, 128, 1)">48</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">菲兹</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">0</span>, age: <span style="color: rgba(128, 0, 128, 1)">52</span>, high: <span style="color: rgba(128, 0, 128, 1)">163</span>, weight: <span style="color: rgba(128, 0, 128, 1)">50</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">亚索</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">1</span>, age: <span style="color: rgba(128, 0, 128, 1)">35</span>, high: <span style="color: rgba(128, 0, 128, 1)">177</span>, weight: <span style="color: rgba(128, 0, 128, 1)">65</span><span style="color: rgba(0, 0, 0, 1)">},
{name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">锐雯</span><span style="color: rgba(128, 0, 0, 1)">'</span>, gender: <span style="color: rgba(128, 0, 128, 1)">2</span>, age: <span style="color: rgba(128, 0, 128, 1)">33</span>, high: <span style="color: rgba(128, 0, 128, 1)">172</span>, weight: <span style="color: rgba(128, 0, 128, 1)">52</span><span style="color: rgba(0, 0, 0, 1)">},
]</span></span></pre>
</div>
<p><span style="font-size: 14px"><strong><span style="color: rgba(0, 128, 128, 1)">编写一个<strong>过滤</strong>用户信息的函数,统计18岁以上男性有多少人,且记录他们的身高和姓名</span></strong></span></p>
<p><span style="font-size: 14px">也许你会这么写:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> male =<span style="color: rgba(0, 0, 0, 1)"> {
count: </span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">,
list: [],
};

</span><span style="color: rgba(0, 0, 255, 1)">const</span> MIN_AGE = <span style="color: rgba(128, 0, 128, 1)">18</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">const</span> Count = (arr) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">const</span><span style="color: rgba(0, 0, 0, 1)"> item of arr) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>!<span style="color: rgba(0, 0, 0, 1)">item
      </span>|| +item.age &lt; +<span style="color: rgba(0, 0, 0, 1)">MIN_AGE
      </span>|| `${item.gender}` !== <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
    ) { </span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)"> }
    male.count</span>++<span style="color: rgba(0, 0, 0, 1)">;
    male.list.push({
      name: item.name,
      high: item.high,
    });
}
}</span></span></pre>
</div>
<p><span style="font-size: 14px">似乎没什么问题的亚子,我们工作中也会写这样的函数</span></p>
<p><span style="font-size: 14px">但上面的 MIN_AGE、male 都是外部变量(或者说全局变量)</span></p>
<p><span style="font-size: 14px">我们在写业务的时候,这样的写法挑不出什么毛病,但他们都不是<strong>纯函数</strong></span></p>
<p><span style="font-size: 14px">纯函数具备两个特点:</span></p>
<p><span style="color: rgba(128, 0, 0, 1); font-size: 14px"><strong>1. 不依赖外部状态,相同的输入永远得到相同的输出;</strong></span></p>
<p><span style="color: rgba(128, 0, 0, 1); font-size: 14px"><strong>2. 没有副作用,不会修改入参或者全局变量。</strong><span style="color: rgba(128, 128, 128, 1)">&nbsp;// splice 说的就是你!</span></span></p>
<p><span style="font-size: 14px">就上面的例子来说,如果连续执行几次<span style="color: rgba(0, 128, 128, 1)"><strong> Count(<span style="color: rgba(255, 102, 0, 1)">arr</span>)</strong> </span>就会出问题:</span></p>
<p><span style="font-size: 14px"><img src="https://img2020.cnblogs.com/blog/1059788/202005/1059788-20200506194458279-1174169706.png" alt=""></span></p>
<p><span style="font-size: 14px">如果按照纯函数的标准,可以改成这样:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> Count = (arr, min) =&gt;<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>
<span style="color: rgba(0, 0, 255, 1)">const</span> res =<span style="color: rgba(0, 0, 0, 1)"> {
    count: </span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">,
    list: [],
};
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">const</span><span style="color: rgba(0, 0, 0, 1)"> item of arr) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>!<span style="color: rgba(0, 0, 0, 1)">item
      </span>|| +item.age &lt; +min <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用入参而不是全局变量</span>
      || `${item.gender}` !== <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
    ) { </span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)"> }
    res.count</span>++<span style="color: rgba(0, 0, 0, 1)">;
    res.list.push({
      name: item.name,
      high: item.high,
    });
}
</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><span style="color: rgba(0, 0, 0, 1)"> res;
}</span></span></pre>
</div>
<p><span style="font-size: 14px">这样调整之后,函数就实现了完全的自给自足,我们也能很清楚的知道这个函数所依赖的参数是什么</span></p>
<p><span style="font-size: 14px">但仅仅是这样的调整似乎没有什么特别之处,假如我们筛选条件改为体重小于 50kg 的女性,这个函数就需要做许多调整</span></p>
<p><span style="font-size: 14px">别急,我们才刚开始,接下来就打造一个易维护、可读性高的业务函数</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 16px"><strong>三、柯里化 (curry)</strong></span></p>
<p><span style="font-size: 14px">上面的例子其实采用的是命令式编程的思想,关注的是如何一步一步实现当前的需求</span></p>
<p><span style="font-size: 14px">而<strong>函数式编程更像是用一个一个的加工站组合起来的工厂流水线</strong>,他也能实现需求,但更关注的是如何使用加工站</span></p>
<p><span style="font-size: 14px">这个加工站就是柯里化,柯里化的概念很简单:<span style="color: rgba(128, 0, 0, 1)"><strong>将一个多参数函数,转换成一个依次调用的单参数函数</strong></span></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">fun(a, b, c)-&gt;fun(a)(b)(c)</span></pre>
</div>
<p><span style="font-size: 14px">需要注意柯里化和<strong>局部调用</strong>的区别</span></p>
<p><span style="font-size: 14px">局部调用是指:<strong>只传递给函数一部分参数,并返回一个函数去处理剩下的参数</strong></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">fun(a, b, c) -&gt; fun(a)(b, c) / fun(a, b)(c)</span></pre>
</div>
<p><span style="font-size: 14px">不过在实际工作中,由于都是使用工具库(比如 Lodash,Ramda)提供的&nbsp;curry 函数,而这些&nbsp;curry 函数通常既满足柯里化,也满足局部调用,所以这两个概念对实际工作没什么影响</span></p>
<p><span style="font-size: 14px">先从一个简单的例子来认识柯里化,首先声明一个求和函数</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> sum = (x, y, z) =&gt; x + y + z;</span></pre>
</div>
<p><span style="font-size: 14px">然后实现一个简单的 curry 函数(通常我们不会自己去写 curry 函数,而是直接使用各种工具库提供的 curry 函数)</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> curry = (fn) =&gt;<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, 0, 1)"> function recursive(...args) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果args.length &gt;= fn.length则表明传入了足够的参数,此时调用fn并返回</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (args.length &gt;=<span style="color: rgba(0, 0, 0, 1)"> fn.length) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> fn(...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)">return</span> (...newArgs) =&gt;<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)"> 递归调用recursive函数,并返回</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> recursive(...args.concat(newArgs));
      };
    };
};</span></span></pre>
</div>
<p><span style="font-size: 14px">将 sum 函数柯里化</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> Sum = curry(sum);      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> -&gt; <br></span>Sum(10)(11)(12);             <span style="color: rgba(0, 128, 0, 1)">// -&gt; 33<br></span>
<span style="color: rgba(0, 0, 255, 1)">const</span> Sum10 = Sum(<span style="color: rgba(128, 0, 128, 1)">10</span>);       <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> -&gt; </span>
<span style="color: rgba(0, 0, 255, 1)">const</span> Sum10_11 = Sum10(<span style="color: rgba(128, 0, 128, 1)">11</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> -&gt; </span>
Sum10_11(<span style="color: rgba(128, 0, 128, 1)">12</span>);                <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> -&gt; 33<br></span></span></pre>
</div>
<p><span style="font-size: 14px">我们可以直接使用柯里化之后的 Sum 来得到最终结果,也可以基于 Sum 创建出两个特定的单入参函数 Sum10 和 Sum10_11,大大的增强了原本的 sum 函数的灵活性</span></p>
<p><span style="font-size: 14px">而这些单入参函数是<span style="color: rgba(128, 0, 0, 1)"><strong>函数组合</strong></span>的基础。</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 16px"><strong>四、函数组合 (compose)&nbsp;</strong></span></p>
<p><span style="font-size: 14px">如果一个值要经过多个函数才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这就是<strong>函数组合</strong></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> compose = (f, g) =&gt; x =&gt; f(g(x))</span></pre>
</div>
<p><span style="font-size: 14px">以这个极简版的 compose 函数举个例子:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> f = x =&gt; x + <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">const</span> g = x =&gt; x * <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">const</span> fg =<span style="color: rgba(0, 0, 0, 1)"> compose(f, g);
fg(</span><span style="color: rgba(128, 0, 128, 1)">1</span>)<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ----&gt; ?</span></span></pre>
</div>
<p><span style="font-size: 14px">别用控制台调试,能看出 <em>fg(1)</em> 的结果是 3 还是 4 么?</span></p>
<p><span style="font-size: 14px">如果有经过思考,就会发现一个细节:<span style="color: rgba(128, 0, 0, 1)"><strong>函数组合中的函数是倒序执行的,</strong></span>我们的入参是 (f, g),但实际执行的顺序是 g -&gt; f</span></p>
<p><span style="font-size: 14px">现在假设我们有四个工具函数:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">filter18(arr);            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从数组中返回年龄大于18岁的数据</span>
filterMale(arr);          <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从数组中筛选出男性数据并返回新数组</span>
pickNameHeight(arr);      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取数组中的姓名和身高字段并返回新数组<br><span style="color: rgba(0, 0, 0, 1)">log(arr);</span>               // 打印参数</span></span></pre>
</div>
<p><span style="font-size: 14px">按照命令式编程的思路,如果要通过这四个函数实现最初的那个筛选用户信息的需求,就需要这么写:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">log(pickNameHeight(filterMale(filter18(arr))));</span></pre>
</div>
<p><span style="font-size: 14px">看得眼花是不是?使用 compose 试试:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> fun =<span style="color: rgba(0, 0, 0, 1)"> compose(log, pickNameHeight, filterMale, filter18);
fun(arr);</span></span></pre>
</div>
<p><span style="font-size: 14px">现在就清晰多了,通过入参我们能一眼看出这条流水线做了什么</span></p>
<p><span style="font-size: 14px">而且将不同的函数用不同的方式组合,还能得到更多更灵活的函数,这恰恰是函数式编程的魅力所在</span></p>
<p>&nbsp;</p>
<p><span style="font-size: 14px">和 curry 函数一样,我们通常都是直接使用各种工具库提供的 compose 函数</span></p>
<p><span style="font-size: 14px">而这些工具库通常还会提供一个&nbsp;pipe 函数,这个函数的作用 compose 类似,但 pipe 的执行顺序和 compose 相反,会将入参函数从前往后组合</span></p>
<p><span style="font-size: 14px">现在我们掌握了函数式编程的两大利器: curry 和 compose,再回头想想最开始的那个需求吧</span></p>
<p>&nbsp;</p>
<p><span style="font-size: 16px"><strong><span style="color: rgba(0, 0, 0, 1)">五、实战</span></strong></span></p>
<p><span style="font-size: 14px">再来过一遍需求:<span style="color: rgba(0, 128, 128, 1)"><strong>编写一个过滤用户信息的函数,统计18岁以上男性有多少人,且记录他们的身高和姓名</strong></span></span></p>
<p><span style="font-size: 14px">其实我们只需要做三件事,首先过滤出18岁以上的数据,然后过滤出男性,最后获取其身高和姓名</span></p>
<p>&nbsp;</p>
<p><span style="font-size: 14px"><strong><span style="color: rgba(0, 0, 255, 1)">1. 过滤出18岁以上的数据</span></strong>,首先需要实现一个用于比较大小的工具函数</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 校验对象中的某个 key 是否大于临界值 val</span>
<span style="color: rgba(0, 0, 0, 1)">function porpGt(key, val, item) {</span><br><span style="color: rgba(0, 0, 255, 1)">return</span> item &gt;<span style="color: rgba(0, 0, 0, 1)"> val
}</span></span></pre>
</div>
<p><span style="font-size: 14px">将这个函数柯里化,就能得到过滤 18 岁的工具函数</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> cPropGt = curry(porpGt);      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> porpGt(a, b, c) -&gt; cPropGt(a)(b)(c)</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> filter18 = cPropGt(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">age</span><span style="color: rgba(128, 0, 0, 1)">'</span>)(<span style="color: rgba(128, 0, 128, 1)">18</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> cPropGt('age')(18)(item) -&gt; filter18(item)</span>
arr.filter(filter18);               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回 age 大于 18 的数据</span></span></pre>
</div>
<p>&nbsp;</p>
<p><span style="font-size: 14px"><strong><span style="color: rgba(0, 0, 255, 1)">2. 过滤出男性</span></strong>,这需要一个判断等值的工具函数</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断对象中的某个 key 是否等于临界值 val</span>
<span style="color: rgba(0, 0, 0, 1)">function porpEq(key, val, item) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> `${item}` ===<span style="color: rgba(0, 0, 0, 1)"> `${val}`
}</span></span></pre>
</div>
<p><span style="font-size: 14px">同样的执行柯里化,然后得到过滤男性的工具函数</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> cPropEq = curry(porpEq);            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> porpEq(a, b, c) -&gt; cPropEq(a)(b)(c)</span>
<span style="color: rgba(0, 0, 255, 1)">const</span> filterMale = cPropEq(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">gender</span><span style="color: rgba(128, 0, 0, 1)">'</span>)(<span style="color: rgba(128, 0, 128, 1)">1</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> cPropEq('gender')(1)(item) -&gt; filterMale(item)</span>
arr.filter(filterMale);                   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回 gender 等于 1 的数据</span></span></pre>
</div>
<p>&nbsp;</p>
<p><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)"><strong>3. 记录身高和姓名</strong></span>,需要一个从对象中提取值的工具函数</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从对象中提取多个值并返回新的对象</span>
<span style="color: rgba(0, 0, 0, 1)">function pickAll(keys, item) {
</span><span style="color: rgba(0, 0, 255, 1)">const</span> res =<span style="color: rgba(0, 0, 0, 1)"> {};
keys.map(key </span>=&gt; res =<span style="color: rgba(0, 0, 0, 1)"> item);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> res;
}</span></span></pre>
</div>
<p><span style="font-size: 14px">柯里化,并保留 name 和 high 两个字段</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> cPickAll =<span style="color: rgba(0, 0, 0, 1)"> curry(pickAll);
</span><span style="color: rgba(0, 0, 255, 1)">const</span> pickProps = cPickAll([<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">name</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">high</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]);
arr.map(pickProps);   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 只保留 name 和 high</span></span></pre>
</div>
<p>&nbsp;</p>
<p><span style="font-size: 14px">完成这三步之后,如果采用面向对象的写法,可以直接链式调用:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1); font-size: 14px">arr.<span style="color: rgba(128, 0, 0, 1)">filter</span>(filter18)
.<span style="color: rgba(128, 0, 0, 1)">filter</span>(filterMale)
.<span style="color: rgba(128, 0, 0, 1)">map</span>(pickProps)</span></pre>
</div>
<p><span style="font-size: 14px">而如果使用了工具库,通常会带有 filter()、map() 这样的工具函数,其功能和数据的 filter、map 一样,只是调用的方式有些区别</span></p>
<p><span style="font-size: 14px">所以使用工具库的话,就可以很方便的使用函数组合:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1)">const</span> Count =<span style="color: rgba(0, 0, 0, 1)"> compose(
<span style="color: rgba(128, 0, 0, 1)">map</span>(pickProps),
<span style="color: rgba(128, 0, 0, 1)">filter</span>(filterMale),
<span style="color: rgba(128, 0, 0, 1)">filter</span>(filter18),
);
Count(arr);</span></span></pre>
</div>
<p><span style="font-size: 14px">如果需要调整过滤条件,就只需要稍微修改一下工具函数的入参,生成新的工具函数之后再组合即可</span></p>
<p>&nbsp;</p>
<p><span style="font-size: 16px"><strong>六、小结</strong></span></p>
<p><span style="font-size: 14px">函数式编程会让代码显得更清晰,更易维护</span></p>
<p><span style="font-size: 14px">但从上面的例子也可以看出,命令式的写法只进行了一次遍历,而函数式编程的写法却遍历了三次</span></p>
<p><span style="font-size: 14px">所以我想提醒看到这里的小伙伴,函数式编程并不是放之四海皆准的万能药, 甚至在某些性能要求很严格的场合,函数式编程并不是太合适的选择</span></p>
<p><span style="font-size: 14px">我认为命令式编程、面向对象编程、函数式编程之间的关系就像是汽车、轮船、飞机之间的关系一样</span></p>
<p><span style="font-size: 14px">他们之间并不存在绝对的优劣好坏,也许在大部分的场合,飞机的速度会比汽车更快,但在崇山峻岭之间,飞机也没法安然着陆</span></p>
<p><span style="font-size: 14px">多学习一种编程思想,只是多掌握了一门技能,仅此而已。</span></p>
<p><span style="font-size: 14px">&nbsp;</span></p>
<p><span style="font-size: 14px"><strong>参考资料:</strong></span></p>
<p><span style="font-size: 14px">《函数式编程指北》&nbsp;</span></p>
<p><span style="font-size: 14px">《函数式编程入门教程》</span></p><br><br>
来源:https://www.cnblogs.com/wisewrong/p/12531629.html
頁: [1]
查看完整版本: 实战 - JavaScript 函数式编程