4 個(gè)JavaScript 中一流函數(shù)的日常用例

英文 | https://medium.com/codex/4-daily-use-cases-of-first-class-functions-in-javascript-17b7079a6217
翻譯 | 楊小愛
var sum = function(a, b) {return a + b;}var total = sum(10, 1);
如果我們是第一次閱讀,這個(gè)定義會(huì)有點(diǎn)混亂。然而,事實(shí)是我們?cè)诓恢榈那闆r下使用了它。
AddEventListener — 學(xué)習(xí) JavaScript 的第一課
過去,引入 JavaScript 是為了向網(wǎng)站添加動(dòng)態(tài)行為。例如,我們希望在用戶單擊按鈕時(shí)更改文本。當(dāng)有人學(xué)習(xí) JavaScript 時(shí),這是第一行代碼。
<html><body><p id="text">The text will change when you click on the button</p><button type="button" id="btn">Click me!</button><script>let btn = document.getElementById("btn");let text = document.getElementById("text");btn.addEventListener("click", function() {text.innerHTML = "New text!"});</script></body></html>
在第 9 行,我們將函數(shù)作為參數(shù)傳遞給 addEventListener 方法。該功能與按鈕的“單擊”事件相關(guān)聯(lián)。當(dāng)事件被觸發(fā)時(shí),該函數(shù)將運(yùn)行。
讓我們好奇——第 1 部分
要了解該功能的作用,讓我們考慮一下它不可用的語言。無論使用何種編程語言,添加動(dòng)態(tài)行為在 UI 開發(fā)中都很常見。如果我們不能將函數(shù)作為參數(shù)傳遞怎么辦?我期待您在留言區(qū)與我們分享您的看法。
發(fā)送 HTTP 請(qǐng)求——JavaScript 中的常見任務(wù)
我以Axios為例。它是最流行的用于發(fā)送 HTTP 請(qǐng)求的 JavaScript 庫(kù)之一。在一個(gè)項(xiàng)目中,我們可能需要添加一些常用的配置。
例如,要將 JWT 發(fā)送到服務(wù)器,我們希望將標(biāo)頭 Authorization 添加到所有請(qǐng)求。因此,我們需要一個(gè)函數(shù)來抓取 JWT 并將其添加到標(biāo)題中。
可以使用攔截器來完成。
// Add jwt to all requests using interceptorsaxios.interceptors.request.use(function (config) {const jwt = globalStore.getJWT(); // assume the token is saved in a global store.config.headers["Authorization"] = `Bearer ${jwt}`;return config;}, null, { synchronous: true });
同樣,我們傳遞 2 個(gè)函數(shù)作為 use 方法的參數(shù)。第一個(gè)函數(shù)在請(qǐng)求的標(biāo)頭中設(shè)置令牌。
如果出現(xiàn)錯(cuò)誤,則第二個(gè)函數(shù)將運(yùn)行(為簡(jiǎn)單起見,我們沒有在此處定義它)。Axios在處理一個(gè)請(qǐng)求時(shí),會(huì)一一運(yùn)行所有的攔截器,將用戶的配置轉(zhuǎn)化為完整配置。
然后它將請(qǐng)求發(fā)送到服務(wù)器。
Axios 處理攔截器的方式很好地說明了 JavaScript 中的一流函數(shù)。
// ------------------------------- inside inteceptor's use method ------------------------//InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {this.handlers.push({fulfilled: fulfilled,rejected: rejected,synchronous: options ? options.synchronous : false,runWhen: options ? options.runWhen : null});return this.handlers.length - 1;};// -------------------------------- process interceptors ---------------------------------//// filter out skipped interceptorsvar requestInterceptorChain = [];var synchronousRequestInterceptors = true;this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {return;}synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);});// ------------------------------- execute synchronous interceptors ---------------------//var newConfig = config;while (requestInterceptorChain.length) {var onFulfilled = requestInterceptorChain.shift();var onRejected = requestInterceptorChain.shift();try {newConfig = onFulfilled(newConfig);} catch (error) {onRejected(error);break;}}
在第 23 行,我們?cè)?usemethod 中傳遞的已完成和被拒絕的函數(shù)被添加到 requestInterceptorChain。我們將函數(shù)存儲(chǔ)在數(shù)組中。
然后 Axios 將運(yùn)行它們中的每一個(gè)。在 while 循環(huán)中,您可以看到函數(shù)被分配給要調(diào)用的變量。
讓我們好奇——第 2 部分
發(fā)送 HTTP 請(qǐng)求不限于前端開發(fā)。在開發(fā)后端部分時(shí),我們可能需要向外部服務(wù)器發(fā)送請(qǐng)求。您能否向我們解釋一下如何以您的首選語言處理 HTTP 請(qǐng)求配置?歡迎在留言區(qū)分享您的想法。
在 Node.js 中處理 HTTP 請(qǐng)求
使用 Node.js,我們可以使用 JavaScript 開發(fā)后端部分。后端開發(fā)是關(guān)于處理 HTTP 請(qǐng)求,即:接收它們,解析它們,找到正確的答案,并響應(yīng)客戶端。Node.js 最常用的框架之一是 Express.js。
該框架使用中間件來完成上述任務(wù)。以下是 Express 官方頁面中中間件的定義:
中間件函數(shù)是可以訪問請(qǐng)求對(duì)象 (req)、響應(yīng)對(duì)象 (res) 和應(yīng)用程序請(qǐng)求-響應(yīng)循環(huán)中的下一個(gè)中間件函數(shù)的函數(shù)。
您可以在下面看到中間件的示例。
var express = require('express');var app = express();app.use('/users', function (req, res, next) {// assume only authenticated users can access to /users routeif(!req.user) {// non authenticated usersres.json({status : "failed", message: "Please login!"});return;}// if users is authenticated, go to the next middlewarenext();});
中間件函數(shù)在 use 方法中傳遞。反過來,它接受另一個(gè)函數(shù) next 作為參數(shù)。最后調(diào)用 next 函數(shù),將控制權(quán)傳遞給堆棧中的以下中間件。
Express 因其簡(jiǎn)單性而廣受歡迎并被廣泛使用。“一個(gè) Express 應(yīng)用程序本質(zhì)上是一系列中間件函數(shù)調(diào)用?!?盡管看起來微不足道,但 Express 的中間件可以幫助我們完成 Web 服務(wù)器的所有任務(wù):記錄請(qǐng)求、壓縮響應(yīng)、設(shè)置 cookie、防止 XSS 攻擊……僅舉幾例。
讓我們?cè)俅魏闷妫?/span>
HTTP 請(qǐng)求在其他后端框架中是如何處理的?您能將它與 Express 中間件進(jìn)行比較嗎?每種方法的優(yōu)點(diǎn)/缺點(diǎn)是什么?你看,有很多問題要研究!
最后但并非最不重要的——JavaScript 中的回調(diào)地獄
如您所知,JavaScript 是單線程的。但它提供了一種有效的機(jī)制來處理長(zhǎng)時(shí)間運(yùn)行的任務(wù)。我們可以立即開始下一個(gè)任務(wù),而不是等待任務(wù)完成,并定義前一個(gè)任務(wù)完成后我們需要做什么。這就是回調(diào)函數(shù)的來源——定義在長(zhǎng)時(shí)間運(yùn)行的任務(wù)后應(yīng)該運(yùn)行什么。
import { readFile } from 'fs';readFile('/etc/passwd', (err, data) => {if (err) throw err;console.log(data);});
回調(diào)函數(shù)為我們提供了一個(gè)強(qiáng)大的工具來處理 I/O 綁定的應(yīng)用程序。然而,任何好事如果被濫用都會(huì)變壞。您可以查看下面的示例。
fs.readdir(source, function (err, files) {if (err) {console.log('Error finding files: ' + err)} else {files.forEach(function (filename, fileIndex) {console.log(filename)gm(source + filename).size(function (err, values) {if (err) {console.log('Error identifying file size: ' + err)} else {console.log(filename + ' : ' + values)aspect = (values.width / values.height)widths.forEach(function (width, widthIndex) {height = Math.round(width / aspect)console.log('resizing ' + filename + 'to ' + height + 'x' + height)this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {if (err) console.log('Error writing file: ' + err)})}.bind(this))}})})}})
多個(gè)回調(diào)函數(shù)和 if/else 語句使代碼難以理解。如果我們添加更多邏輯,它在未來可能變得不可維護(hù)。由于這個(gè)問題,引入了更新的功能。Promise 似乎可以幫助我們編寫一個(gè)更簡(jiǎn)潔的程序。Async/await 關(guān)鍵字允許我們編寫看起來像同步代碼的異步代碼。
總結(jié)
在這篇文章中,我向您展示了一些在 JavaScript 中使用“一流函數(shù)”的真實(shí)示例。
我們每天都使用此功能并認(rèn)為這是理所當(dāng)然的。通過這些例子,我希望您能看到這個(gè)特性為我們提供的一些很酷的東西。
我也給你留下了很多問題。好奇心是幫助我們成長(zhǎng)的特征之一。
很高興在評(píng)論區(qū)看到您的回答,讓我們互相學(xué)習(xí)。您也可以將文章分享給其他語言的開發(fā)者進(jìn)行討論。
感謝您的閱讀!
學(xué)習(xí)更多技能
請(qǐng)點(diǎn)擊下方公眾號(hào)
![]()

