
一些編碼人員可能會(huì)直接更改原始功能以達(dá)到某種目的。嗯,這是初級(jí)開發(fā)人員常用的方法,也是一種直觀的方法。
但在很多情況下,它并不是最好的解決方案,并且有一些缺點(diǎn)。在今天的內(nèi)容中,我將通過示例為您介紹一些通用的解決方案。
1、once
很多時(shí)候,我們想要一個(gè)只執(zhí)行一次的函數(shù)。
比如,我們開發(fā)網(wǎng)頁的時(shí)候,總會(huì)有一些提交表單的按鈕。當(dāng)用戶點(diǎn)擊按鈕時(shí),會(huì)觸發(fā)它的 onclick 事件。
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
為了簡化演示問題,該示例僅記錄一條消息,而不是向服務(wù)器發(fā)送數(shù)據(jù)。
但這里有一個(gè)問題:由于網(wǎng)絡(luò)延遲,我們無法立即為用戶顯示結(jié)果。然后用戶可能繼續(xù)點(diǎn)擊該按鈕并多次向服務(wù)器提交表單。

所以,我們需要解決這個(gè)問題,你的解決方案是什么?
一個(gè)常見的解決方案是在用戶第一次單擊按鈕后禁用該按鈕。
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>

嗯,這個(gè)解決方案沒有問題。
另外,我們有一個(gè)不同的解決方案:
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
在這個(gè)解決方案中,我們使用一個(gè)標(biāo)志來記錄該函數(shù)之前是否已執(zhí)行過。

如果我們使用圖表來表示程序,它可能是這樣的:

但是,我們能否為所有此類問題找到一個(gè)通用的解決方案?
讓我們繼續(xù)一個(gè)類似的例子。很多時(shí)候,我們的程序中有一個(gè)init函數(shù)。
可以使用這個(gè)函數(shù)來設(shè)置變量、讀取配置等。這個(gè)函數(shù)應(yīng)該只執(zhí)行一次。為了確保它只執(zhí)行一次并避免意外,我們可以對(duì)函數(shù)進(jìn)行一些更改:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
我們可以使用這個(gè)函數(shù)來設(shè)置變量、讀取配置等。這個(gè)函數(shù)應(yīng)該只執(zhí)行一次。為了確保它只執(zhí)行一次并避免意外,我們可以對(duì)函數(shù)進(jìn)行一些更改:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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函數(shù)只會(huì)初始化環(huán)境一次。

我們還可以將程序繪制成圖表。

你發(fā)現(xiàn)表單提交和初始化函數(shù)有一些共同點(diǎn)嗎?是的,他們的程序非常相似!
如果我們做高級(jí)抽象,流程應(yīng)該是這樣的:

如果該函數(shù)之前已被調(diào)用是一般程序。我們可以編寫一個(gè)高階函數(shù)來密封這個(gè)過程。
這是一次函數(shù)的實(shí)現(xiàn):
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
現(xiàn)在,使用 once 函數(shù),我們可以輕松地歸檔執(zhí)行一次函數(shù)的目的。
提交一次:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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 函數(shù)來解決我們的需求。
使用 once 函數(shù)的核心思想是什么?
正如我在標(biāo)題中提到的:我們將一般過程抽象為高階函數(shù)。程序——只執(zhí)行一次函數(shù)——是一個(gè)通用過程。它會(huì)被多次使用。如果我們不做抽象,我們就必須在不同的函數(shù)中為相同的邏輯重復(fù)編寫代碼。
如果我們使用 once 函數(shù),有很多好處:
- 我們不需要改變?cè)瓉淼墓δ堋?/li>
- 保留業(yè)務(wù)邏輯和執(zhí)行邏輯的分隔符,這樣代碼會(huì)更易于維護(hù)。
- 一次函數(shù)是一個(gè)可重用的函數(shù)。
2、cache
讓我們來看另一個(gè)例子。
如果有這樣的一個(gè)功能:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
(其實(shí)這個(gè)案例我是從 Vue 源碼中學(xué)到的。)
我們要緩存函數(shù)操作的結(jié)果。稍后調(diào)用時(shí),如果參數(shù)相同,則不再執(zhí)行該函數(shù),而是直接返回緩存中的結(jié)果。我們能做什么?
這里有一個(gè)建議:當(dāng)你需要增強(qiáng)一個(gè)函數(shù)時(shí),不要試圖直接修改它,考慮先寫一個(gè)通用的高階函數(shù)來包裝它。
緩存函數(shù)結(jié)果的一般過程是什么?這是一個(gè)流程:

這是緩存結(jié)果的實(shí)現(xiàn):
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
現(xiàn)在我們可以使用這個(gè)緩存函數(shù)來增強(qiáng) cumpute 函數(shù):

我們做這個(gè)抽象并不是為了炫耀技巧,其實(shí)這樣的緩存功能用途廣泛。
我們知道,有一個(gè)著名的序列叫做斐波那契數(shù)列。
<pre class="prettyprint hljs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
快速瀏覽后,您可以很容易地注意到序列的模式是每個(gè)值都是前 2 個(gè)值的總和,這意味著對(duì)于 N=5 → 2+3 或在數(shù)學(xué)中:
<pre class="prettyprint hljs lisp" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
現(xiàn)在我們要寫一個(gè)函數(shù):
給定一個(gè)數(shù)字N返回斐波那契數(shù)列的索引值。
怎么寫函數(shù)?
最簡單的解決方案是遞歸解決方案:
<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
但是這個(gè)實(shí)現(xiàn)很耗時(shí),如果 num 大于 35,您將等待一段時(shí)間才能得到結(jié)果。

但是如果我們使用緩存函數(shù)來重構(gòu)實(shí)現(xiàn),我們會(huì)得到一個(gè)高性能的函數(shù)。
<pre class="prettyprint hljs lisp" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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
讓我們繼續(xù)。
假設(shè)您是一個(gè)庫的維護(hù)者,并且您將在未來?xiàng)売靡粋€(gè)名為 request 的舊 API。
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
在當(dāng)前版本中,您希望通過記錄消息來警告用戶 API 將被棄用。
那你會(huì)怎么做?
最糟糕的方法是在函數(shù)中添加一個(gè) console.warn 語句:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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 并對(duì)其進(jìn)行修改。這是一個(gè)非常繁瑣的過程,而且很容易導(dǎo)致錯(cuò)誤。如果沒有必要,不要更改現(xiàn)有功能。
如果我們用圖來表示程序,那就是:

正如我們?cè)谇懊鎯?nèi)容中所做的那樣,我們可以為該過程編寫一個(gè)高階函數(shù)。
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
然后我們可以對(duì)我們的項(xiàng)目做一些改變:
,如果您的庫的用戶調(diào)用請(qǐng)求函數(shù),他們將收到一條消息。
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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
}
現(xiàn)在</pre>
好的,讓我們繼續(xù)一個(gè)類似的例子。
我們有一個(gè) fetch 函數(shù)來向服務(wù)器發(fā)送請(qǐng)求。它將返回 HTML 文本或 JSON 格式的文本。
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
我們現(xiàn)在要做的是,如果我們發(fā)現(xiàn)響應(yīng)結(jié)果是 JSON 格式的字符串,我們將其轉(zhuǎn)換為 JSON 對(duì)象。如果是其他格式的字符串,則不進(jìn)行處理。我應(yīng)該怎么辦?
老規(guī)矩,先畫個(gè)圖:

具體原理已經(jīng)解釋過很多次了,這里我直接給出一個(gè)高階函數(shù):
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
用法:

這兩個(gè)例子有點(diǎn)簡單。但附近還有一個(gè)更重要的想法。
- derecate功能旨在在執(zhí)行原始功能之前執(zhí)行某些操作。
- toJSON函數(shù)旨在執(zhí)行原始函數(shù)后執(zhí)行某些操作。

我們能把這個(gè)過程抽象成一個(gè)新的高階函數(shù)嗎?
我們當(dāng)然可以。
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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 這個(gè)著名的 HTTP 請(qǐng)求庫,你就會(huì)知道 Axios 有一個(gè)攔截器 API 供用戶攔截請(qǐng)求和響應(yīng)。
4、Batch
好的,這是我們的最后一個(gè)例子。
這是一個(gè)將輸入加倍的函數(shù)。
<pre class="prettyprint hljs lua" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
嗯,很簡單的功能,只是為了演示。
如果我們想讓這個(gè)函數(shù)接受一個(gè)數(shù)組作為參數(shù),那么將數(shù)組中所有元素的值加倍,然后返回一個(gè)新數(shù)組。你怎么寫代碼?
我們可以這樣寫:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
確實(shí)可以這樣寫。
但遺憾的是,JavaScript 沒有函數(shù)重載,后者的函數(shù)會(huì)覆蓋前者。為了讓我們的double函數(shù)同時(shí)處理兩種參數(shù)類型,我們必須在函數(shù)體中做出判斷:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>
我們想要的是為所有這些問題創(chuàng)建一個(gè)通用的解決方案:一個(gè)高階函數(shù),可以標(biāo)記一個(gè)函數(shù)來處理單個(gè)參數(shù)或類似數(shù)組的參數(shù)。

這是一個(gè)實(shí)現(xiàn):
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>

總結(jié)
我想,我舉的例子已經(jīng)夠多了。無論是once,cache,intercept還是batch,它們都對(duì)某個(gè)進(jìn)程進(jìn)行了一些抽象。
- 我們想要一個(gè)只執(zhí)行一次的函數(shù),我們可以用 abstract once。
- 我們想要一個(gè)函數(shù)來緩存相應(yīng)參數(shù)的結(jié)果,我們可以 abstract cache 。
- 我們想要一個(gè)在執(zhí)行前后做某事的函數(shù),我們 可以 abstract intercept。
- 我們想要一個(gè)通過參數(shù)類型改變其執(zhí)行流程的函數(shù),我們可以 abstract batch。
- 它們都遵循一個(gè)共同的范式:即使用高階函數(shù)來abstract 任何一般過程。
Nested
恩,我想提的最后一件事:如果有必要,我們可以嵌套這些高階函數(shù)。

假設(shè)我們不僅要緩存計(jì)算函數(shù)的結(jié)果,還要在執(zhí)行它之前記錄它的參數(shù),并在執(zhí)行它之后記錄它的結(jié)果。然后,我們還想讓它能夠處理多重參數(shù)。我們可以這樣寫:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", 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>