新聞中心
一些編碼人員可能會直接更改原始功能以達到某種目的。嗯,這是初級開發(fā)人員常用的方法,也是一種直觀的方法。

創(chuàng)新互聯(lián)為客戶提供專業(yè)的成都網(wǎng)站設(shè)計、做網(wǎng)站、程序、域名、空間一條龍服務(wù),提供基于WEB的系統(tǒng)開發(fā). 服務(wù)項目涵蓋了網(wǎng)頁設(shè)計、網(wǎng)站程序開發(fā)、WEB系統(tǒng)開發(fā)、微信二次開發(fā)、移動網(wǎng)站建設(shè)等網(wǎng)站方面業(yè)務(wù)。
但在很多情況下,它并不是最好的解決方案,并且有一些缺點。在今天的內(nèi)容中,我將通過示例為您介紹一些通用的解決方案。
1、once
很多時候,我們想要一個只執(zhí)行一次的函數(shù)。
比如,我們開發(fā)網(wǎng)頁的時候,總會有一些提交表單的按鈕。當用戶點擊按鈕時,會觸發(fā)它的 onclick 事件。
Document
為了簡化演示問題,該示例僅記錄一條消息,而不是向服務(wù)器發(fā)送數(shù)據(jù)。
但這里有一個問題:由于網(wǎng)絡(luò)延遲,我們無法立即為用戶顯示結(jié)果。然后用戶可能繼續(xù)點擊該按鈕并多次向服務(wù)器提交表單。
所以,我們需要解決這個問題,你的解決方案是什么?
一個常見的解決方案是在用戶第一次單擊按鈕后禁用該按鈕。
document.getElementById('submit').onclick = function()
document.getElementById('submit').disabled = true
console.log("sending data to the server")
}
嗯,這個解決方案沒有問題。
另外,我們有一個不同的解決方案:
Document
在這個解決方案中,我們使用一個標志來記錄該函數(shù)之前是否已執(zhí)行過。
如果我們使用圖表來表示程序,它可能是這樣的:
但是,我們能否為所有此類問題找到一個通用的解決方案?
讓我們繼續(xù)一個類似的例子。很多時候,我們的程序中有一個init函數(shù)。
可以使用這個函數(shù)來設(shè)置變量、讀取配置等。這個函數(shù)應(yīng)該只執(zhí)行一次。為了確保它只執(zhí)行一次并避免意外,我們可以對函數(shù)進行一些更改:
let init = function(){
console.log('init the enviorment')
}我們可以使用這個函數(shù)來設(shè)置變量、讀取配置等。這個函數(shù)應(yīng)該只執(zhí)行一次。為了確保它只執(zhí)行一次并避免意外,我們可以對函數(shù)進行一些更改:
let hasInitialized = false
let init = function(){
if(hasInitialized) return;
console.log('init the enviorment')
hasInitialized = true
}
好的,init函數(shù)只會初始化環(huán)境一次。
我們還可以將程序繪制成圖表。
你發(fā)現(xiàn)表單提交和初始化函數(shù)有一些共同點嗎?是的,他們的程序非常相似!
如果我們做高級抽象,流程應(yīng)該是這樣的:
如果該函數(shù)之前已被調(diào)用是一般程序。我們可以編寫一個高階函數(shù)來密封這個過程。
這是一次函數(shù)的實現(xiàn):
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;
};
}現(xiàn)在,使用 once 函數(shù),我們可以輕松地歸檔執(zhí)行一次函數(shù)的目的。
提交一次:
document.getElementById('submit').onclick = once(function()
console.log("sending data to the server")
})
初始化一次:
好的,我們使用 once 函數(shù)來解決我們的需求。
使用 once 函數(shù)的核心思想是什么?
正如我在標題中提到的:我們將一般過程抽象為高階函數(shù)。程序——只執(zhí)行一次函數(shù)——是一個通用過程。它會被多次使用。如果我們不做抽象,我們就必須在不同的函數(shù)中為相同的邏輯重復編寫代碼。
如果我們使用 once 函數(shù),有很多好處:
- 我們不需要改變原來的功能。
- 保留業(yè)務(wù)邏輯和執(zhí)行邏輯的分隔符,這樣代碼會更易于維護。
- 一次函數(shù)是一個可重用的函數(shù)。
2、cache
讓我們來看另一個例子。
如果有這樣的一個功能:
function compute(str) {
// Suppose the calculation in the funtion is very time consuming
console.log('2000ms have passed')
return str.toUpperCase()
}(其實這個案例我是從 Vue 源碼中學到的。)
我們要緩存函數(shù)操作的結(jié)果。稍后調(diào)用時,如果參數(shù)相同,則不再執(zhí)行該函數(shù),而是直接返回緩存中的結(jié)果。我們能做什么?
這里有一個建議:當你需要增強一個函數(shù)時,不要試圖直接修改它,考慮先寫一個通用的高階函數(shù)來包裝它。
緩存函數(shù)結(jié)果的一般過程是什么?這是一個流程:
這是緩存結(jié)果的實現(xiàn):
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]
}
}現(xiàn)在我們可以使用這個緩存函數(shù)來增強 cumpute 函數(shù):
我們做這個抽象并不是為了炫耀技巧,其實這樣的緩存功能用途廣泛。
我們知道,有一個著名的序列叫做斐波那契數(shù)列。
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
快速瀏覽后,您可以很容易地注意到序列的模式是每個值都是前 2 個值的總和,這意味著對于 N=5 → 2+3 或在數(shù)學中:
F(n) = F(n-1) + F(n-2)
現(xiàn)在我們要寫一個函數(shù):
給定一個數(shù)字N返回斐波那契數(shù)列的索引值。
怎么寫函數(shù)?
最簡單的解決方案是遞歸解決方案:
}function fibonacci(num) {
if (num <= 1) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
}但是這個實現(xiàn)很耗時,如果 num 大于 35,您將等待一段時間才能得到結(jié)果。
但是如果我們使用緩存函數(shù)來重構(gòu)實現(xiàn),我們會得到一個高性能的函數(shù)。
let cachedFibonacci = cached(function(num){
if(num <= 1) return 1;
return cachedFibonacci(num - 1) + cachedFibonacci(num - 2)
})
3、intercept
讓我們繼續(xù)。
假設(shè)您是一個庫的維護者,并且您將在未來棄用一個名為 request 的舊 API。
function request(){
console.log('request to server')
}在當前版本中,您希望通過記錄消息來警告用戶 API 將被棄用。
那你會怎么做?
最糟糕的方法是在函數(shù)中添加一個 console.warn 語句:
function request(){
console.warn(`The request will be deprecated in the future`)
console.log('request to server')
}為什么這是最糟糕的解決方案?
您必須找到所有已棄用的 API 并對其進行修改。這是一個非常繁瑣的過程,而且很容易導致錯誤。如果沒有必要,不要更改現(xiàn)有功能。
如果我們用圖來表示程序,那就是:
正如我們在前面內(nèi)容中所做的那樣,我們可以為該過程編寫一個高階函數(shù)。
function deprecate(fn, newApi) {
return function() {
console.log( `The ${fn.name} will be deprecated. Please use the ${newApi} instead.`);
return fn.apply(this, arguments);
}
}然后我們可以對我們的項目做一些改變:
,如果您的庫的用戶調(diào)用請求函數(shù),他們將收到一條消息。
// index.js
importre request from './request';
const _request = deprecate(request, 'fetch');
export {
request: _request
}
現(xiàn)在
好的,讓我們繼續(xù)一個類似的例子。
我們有一個 fetch 函數(shù)來向服務(wù)器發(fā)送請求。它將返回 HTML 文本或 JSON 格式的文本。
var fetch = function(url){
let responseContent = null
console.log(`fetching ${url}`)
if(Math.random() < 0.5){
return 'hello world'
} else {
return '{"name": "bytefish"}'
}
}我們現(xiàn)在要做的是,如果我們發(fā)現(xiàn)響應(yīng)結(jié)果是 JSON 格式的字符串,我們將其轉(zhuǎn)換為 JSON 對象。如果是其他格式的字符串,則不進行處理。我應(yīng)該怎么辦?
老規(guī)矩,先畫個圖:
具體原理已經(jīng)解釋過很多次了,這里我直接給出一個高階函數(shù):
function toJSON(fn) {
return function() {
let res = fn.apply(this, arguments)
try{
let json = JSON.parse(res)
return json
} catch(e){
return res
}
}
}用法:
這兩個例子有點簡單。但附近還有一個更重要的想法。
- derecate功能旨在在執(zhí)行原始功能之前執(zhí)行某些操作。
- toJSON函數(shù)旨在執(zhí)行原始函數(shù)后執(zhí)行某些操作。
我們能把這個過程抽象成一個新的高階函數(shù)嗎?
我們當然可以。
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
};
}如果你之前用過 Axios 這個著名的 HTTP 請求庫,你就會知道 Axios 有一個攔截器 API 供用戶攔截請求和響應(yīng)。
4、Batch
好的,這是我們的最后一個例子。
這是一個將輸入加倍的函數(shù)。
function double(num){
return num * 2
}嗯,很簡單的功能,只是為了演示。
如果我們想讓這個函數(shù)接受一個數(shù)組作為參數(shù),那么將數(shù)組中所有元素的值加倍,然后返回一個新數(shù)組。你怎么寫代碼?
我們可以這樣寫:
function double(nums){
return nums.map(num => num * 2)
}確實可以這樣寫。
但遺憾的是,JavaScript 沒有函數(shù)重載,后者的函數(shù)會覆蓋前者。為了讓我們的double函數(shù)同時處理兩種參數(shù)類型,我們必須在函數(shù)體中做出判斷:
function double(arg){
if(Array.isArray(arg)){
return nums.map(num => num * 2)
}
return num * 2
}我們想要的是為所有這些問題創(chuàng)建一個通用的解決方案:一個高階函數(shù),可以標記一個函數(shù)來處理單個參數(shù)或類似數(shù)組的參數(shù)。
這是一個實現(xiàn):
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);
}
}
總結(jié)
我想,我舉的例子已經(jīng)夠多了。無論是once,cache,intercept還是batch,它們都對某個進程進行了一些抽象。
- 我們想要一個只執(zhí)行一次的函數(shù),我們可以用 abstract once。
- 我們想要一個函數(shù)來緩存相應(yīng)參數(shù)的結(jié)果,我們可以 abstract cache 。
- 我們想要一個在執(zhí)行前后做某事的函數(shù),我們 可以 abstract intercept。
- 我們想要一個通過參數(shù)類型改變其執(zhí)行流程的函數(shù),我們可以 abstract batch。
- 它們都遵循一個共同的范式:即使用高階函數(shù)來abstract 任何一般過程。
Nested
恩,我想提的最后一件事:如果有必要,我們可以嵌套這些高階函數(shù)。
假設(shè)我們不僅要緩存計算函數(shù)的結(jié)果,還要在執(zhí)行它之前記錄它的參數(shù),并在執(zhí)行它之后記錄它的結(jié)果。然后,我們還想讓它能夠處理多重參數(shù)。我們可以這樣寫:
let computedEnhance = batch(intercept(cached(computed), {
before: arg => {
console.log(`processing ${arg}`)
},
after: res => {
console.log(`returned ${res}`)
}
})) 網(wǎng)站欄目:如何像高級JavaScript開發(fā)人員一樣為一般流程編寫高階函數(shù)
文章源于:http://www.5511xx.com/article/cojscso.html


咨詢
建站咨詢
