如何像高级 JavaScript 开发人员一样为一般流程编写高阶函数

一些编码人员可能会直接更改原始功能以达到某种目的。嗯,这是初级开发人员常用的方法,也是一种直观的方法。

但在很多情况下,它并不是最好的解决方案,并且有一些缺点。在今天的内容中,我将通过示例为您介绍一些通用的解决方案。

1、once

很多时候,我们想要一个只执行一次的函数。

比如,我们开发网页的时候,总会有一些提交表单的按钮。当用户点击按钮时,会触发它的 onclick 事件。

<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
</head>
<body>
 <div>
   <input type="text" username>
   <input type="password" password>
   <button id="submit">submmit</button>
 </div>
 <script> document.getElementById('submit').onclick = function(){
     console.log("sending data to the server")
   } </script>
</body>
</html></pre>

为了简化演示问题,该示例仅记录一条消息,而不是向服务器发送数据。

但这里有一个问题:由于网络延迟,我们无法立即为用户显示结果。然后用户可能继续点击该按钮并多次向服务器提交表单。

所以,我们需要解决这个问题,你的解决方案是什么?

一个常见的解决方案是在用户第一次单击按钮后禁用该按钮。

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">document.getElementById('submit').onclick = function()
 document.getElementById('submit').disabled = true
 console.log("sending data to the server")
}</pre>

嗯,这个解决方案没有问题。

另外,我们有一个不同的解决方案:

<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
</head>
<body>
 <div>
   <input type="text" username>
   <input type="password" password>
   <button id="submit">submmit</button>
 </div>
 <script> let hasSubmit = false
   document.getElementById('submit').onclick = function(){
     if(hasSubmit) return;
     console.log("sending data to the server")
     hasSubmit = true
   } </script>
</body>
</html></pre>

在这个解决方案中,我们使用一个标志来记录该函数之前是否已执行过。

如果我们使用图表来表示程序,它可能是这样的:

但是,我们能否为所有此类问题找到一个通用的解决方案?

让我们继续一个类似的例子。很多时候,我们的程序中有一个init函数。

可以使用这个函数来设置变量、读取配置等。这个函数应该只执行一次。为了确保它只执行一次并避免意外,我们可以对函数进行一些更改:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">let init = function(){
 console.log('init the enviorment')
}</pre>

我们可以使用这个函数来设置变量、读取配置等。这个函数应该只执行一次。为了确保它只执行一次并避免意外,我们可以对函数进行一些更改:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">let hasInitialized = false
let init = function(){
 if(hasInitialized) return;
 console.log('init the enviorment')
 hasInitialized = true
}</pre>

好的,init函数只会初始化环境一次。

我们还可以将程序绘制成图表。

你发现表单提交和初始化函数有一些共同点吗?是的,他们的程序非常相似!

如果我们做高级抽象,流程应该是这样的:

如果该函数之前已被调用是一般程序。我们可以编写一个高阶函数来密封这个过程。

这是一次函数的实现:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function once(func) {
 let hasExecuted = false;
 let result;
 return function () {
   if (hasExecuted) return result;
   hasExecuted = true;
   result = func.apply(this, arguments);
   func = null;
   return result;
 };
}</pre>

现在,使用 once 函数,我们可以轻松地归档执行一次函数的目的。

提交一次:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">document.getElementById('submit').onclick = once(function()
 console.log("sending data to the server")
})</pre>

初始化一次:

好的,我们使用 once 函数来解决我们的需求。

使用 once 函数的核心思想是什么?

正如我在标题中提到的:我们将一般过程抽象为高阶函数。程序——只执行一次函数——是一个通用过程。它会被多次使用。如果我们不做抽象,我们就必须在不同的函数中为相同的逻辑重复编写代码。

如果我们使用 once 函数,有很多好处:

  • 我们不需要改变原来的功能。
  • 保留业务逻辑和执行逻辑的分隔符,这样代码会更易于维护。
  • 一次函数是一个可重用的函数。

2、cache

让我们来看另一个例子。

如果有这样的一个功能:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function compute(str) {    
   // Suppose the calculation in the funtion is very time consuming 
   console.log('2000ms have passed')
   return str.toUpperCase()
}</pre>

(其实这个案例我是从 Vue 源码中学到的。)

我们要缓存函数操作的结果。稍后调用时,如果参数相同,则不再执行该函数,而是直接返回缓存中的结果。我们能做什么?

这里有一个建议:当你需要增强一个函数时,不要试图直接修改它,考虑先写一个通用的高阶函数来包装它。

缓存函数结果的一般过程是什么?这是一个流程:

这是缓存结果的实现:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function cached(fn){
 // Create an object to store the results returned after each function execution.
 const cache = Object.create(null);
 // Returns the wrapped function
 return function cachedFn (str) {
   // If the cache is not hit, the function will be executed
   if ( !cache[str] ) {
       let result = fn(str);
       // Store the result of the function execution in the cache
       cache[str] = result;
   }
   return cache[str]
 }
}</pre>

现在我们可以使用这个缓存函数来增强 cumpute 函数:

我们做这个抽象并不是为了炫耀技巧,其实这样的缓存功能用途广泛。

我们知道,有一个著名的序列叫做斐波那契数列。

<pre class="prettyprint hljs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...</pre>

快速浏览后,您可以很容易地注意到序列的模式是每个值都是前 2 个值的总和,这意味着对于 N=5 → 2+3 或在数学中:

<pre class="prettyprint hljs lisp" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">F(n) = F(n-1) + F(n-2)</pre>

现在我们要写一个函数:

给定一个数字N返回斐波那契数列的索引值。

怎么写函数?

最简单的解决方案是递归解决方案:

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">}function fibonacci(num) {
 if (num <= 1) return 1;
 return fibonacci(num - 1) + fibonacci(num - 2);
}</pre>

但是这个实现很耗时,如果 num 大于 35,您将等待一段时间才能得到结果。

但是如果我们使用缓存函数来重构实现,我们会得到一个高性能的函数。

<pre class="prettyprint hljs lisp" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">let cachedFibonacci = cached(function(num){
 if(num <= 1) return 1;
 return cachedFibonacci(num - 1) + cachedFibonacci(num - 2)
})</pre>

3、intercept

让我们继续。

假设您是一个库的维护者,并且您将在未来弃用一个名为 request 的旧 API。

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function request(){
 console.log('request to server')
}</pre>

在当前版本中,您希望通过记录消息来警告用户 API 将被弃用。

那你会怎么做?

最糟糕的方法是在函数中添加一个 console.warn 语句:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function request(){
 console.warn(`The request will be deprecated in the future`)
 console.log('request to server')
}</pre>

为什么这是最糟糕的解决方案?

您必须找到所有已弃用的 API 并对其进行修改。这是一个非常繁琐的过程,而且很容易导致错误。如果没有必要,不要更改现有功能。

如果我们用图来表示程序,那就是:

正如我们在前面内容中所做的那样,我们可以为该过程编写一个高阶函数。

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function deprecate(fn, newApi) {
 return function() {
   console.log( `The ${fn.name} will be deprecated. Please use the ${newApi} instead.`);
   return fn.apply(this, arguments);
 }
}</pre>

然后我们可以对我们的项目做一些改变:

,如果您的库的用户调用请求函数,他们将收到一条消息。

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// index.js
importre request from './request';
const _request = deprecate(request, 'fetch');
export {
 request: _request
}
现在</pre>

好的,让我们继续一个类似的例子。

我们有一个 fetch 函数来向服务器发送请求。它将返回 HTML 文本或 JSON 格式的文本。

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">var fetch = function(url){
 let responseContent = null
 console.log(`fetching ${url}`)
 if(Math.random() < 0.5){
   return '<html><body>hello world</body></html>'
 } else {
   return '{"name": "bytefish"}'
 }
}</pre>

我们现在要做的是,如果我们发现响应结果是 JSON 格式的字符串,我们将其转换为 JSON 对象。如果是其他格式的字符串,则不进行处理。我应该怎么办?

老规矩,先画个图:

具体原理已经解释过很多次了,这里我直接给出一个高阶函数:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function toJSON(fn) {
 return function() {
   let res = fn.apply(this, arguments)
   try{
     let json = JSON.parse(res)
     return json
   } catch(e){
     return res
   }
 }
}</pre>

用法:

这两个例子有点简单。但附近还有一个更重要的想法。

  • derecate功能旨在在执行原始功能之前执行某些操作。
  • toJSON函数旨在执行原始函数后执行某些操作。

我们能把这个过程抽象成一个新的高阶函数吗?

我们当然可以。

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function intercept(fn, {before = null, after = null}) {
 return function () {
   if(before != null) {
     before.apply(this, arguments)
   }
   const result = fn.apply(this, arguments)
   if(after != null){
     after.call(this, result)
   }
   return result
 };
}</pre>

如果你之前用过 Axios 这个著名的 HTTP 请求库,你就会知道 Axios 有一个拦截器 API 供用户拦截请求和响应。

4、Batch

好的,这是我们的最后一个例子。

这是一个将输入加倍的函数。

<pre class="prettyprint hljs lua" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function double(num){
 return num * 2
}</pre>

嗯,很简单的功能,只是为了演示。

如果我们想让这个函数接受一个数组作为参数,那么将数组中所有元素的值加倍,然后返回一个新数组。你怎么写代码?

我们可以这样写:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function double(nums){
 return nums.map(num =>  num * 2)
}</pre>

确实可以这样写。

但遗憾的是,JavaScript 没有函数重载,后者的函数会覆盖前者。为了让我们的double函数同时处理两种参数类型,我们必须在函数体中做出判断:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function double(arg){
 if(Array.isArray(arg)){
   return nums.map(num =>  num * 2)
 }
 return num * 2
}</pre>

我们想要的是为所有这些问题创建一个通用的解决方案:一个高阶函数,可以标记一个函数来处理单个参数或类似数组的参数。

这是一个实现:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">function batch(fn) {
 return function(subject, ...args) {
   if(Array.isArray(subject)) {
     return subject.map((s) => {
       return fn.call(this, s, ...args);
     });
   }
   return fn.call(this, subject, ...args);
 }
}</pre>

总结

我想,我举的例子已经够多了。无论是once,cache,intercept还是batch,它们都对某个进程进行了一些抽象。

  • 我们想要一个只执行一次的函数,我们可以用 abstract once。
  • 我们想要一个函数来缓存相应参数的结果,我们可以 abstract cache 。
  • 我们想要一个在执行前后做某事的函数,我们 可以 abstract intercept。
  • 我们想要一个通过参数类型改变其执行流程的函数,我们可以 abstract batch。
  • 它们都遵循一个共同的范式:即使用高阶函数来abstract 任何一般过程。

Nested

恩,我想提的最后一件事:如果有必要,我们可以嵌套这些高阶函数。

假设我们不仅要缓存计算函数的结果,还要在执行它之前记录它的参数,并在执行它之后记录它的结果。然后,我们还想让它能够处理多重参数。我们可以这样写:

<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">let computedEnhance = batch(intercept(cached(computed), {
 before: arg => {
   console.log(`processing ${arg}`)
 },
 after: res => {
   console.log(`returned ${res}`)
 }
}))</pre>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容