摘要:從某些方面來講,這章回顧的函數(shù)知識并不是針對函數(shù)式編程者,非函數(shù)式編程者同樣需要了解。什么是函數(shù)針對函數(shù)式編程,很自然而然的我會(huì)想到從函數(shù)開始。如果你計(jì)劃使用函數(shù)式編程,你應(yīng)該盡可能多地使用函數(shù),而不是程序。指的是一個(gè)函數(shù)聲明的形參數(shù)量。
原文地址:Functional-Light-JS
原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者
第 2 章:函數(shù)基礎(chǔ)關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),是 JavaScript 中最嚴(yán)謹(jǐn)?shù)倪壿嫛=?jīng)過捶打磨練,成就了本書的中文版。本書包含了函數(shù)式編程之精髓,希望可以幫助大家在學(xué)習(xí)函數(shù)式編程的道路上走的更順暢。比心。
譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao
函數(shù)式編程不是僅僅用 function 這個(gè)關(guān)鍵詞來編程。如果真這么簡單,那我這本書可以到此為止了!重點(diǎn)在于:函數(shù)是函數(shù)式編程的核心。這也是如何使用函數(shù)(function)才能使我們的代碼具有函數(shù)式(functional)的方法。
然而,你真的明白函數(shù)的含義嗎?
在這一章,我們將會(huì)介紹函數(shù)的基礎(chǔ)知識,為閱讀本書的后續(xù)章節(jié)打下基礎(chǔ)。從某些方面來講,這章回顧的函數(shù)知識并不是針對函數(shù)式編程者,非函數(shù)式編程者同樣需要了解。但如果我們想要充分、全面地學(xué)習(xí)函數(shù)式編程的概念,我們需要從里到外地理解函數(shù)。
請做好準(zhǔn)備,因?yàn)檫€有好多你未知的函數(shù)知識。
什么是函數(shù)?針對函數(shù)式編程,很自然而然的我會(huì)想到從函數(shù)開始。這太明顯不過了,但是我認(rèn)為我們需要扎實(shí)地走好旅程的第一步。
所以......什么是函數(shù)?
簡要的數(shù)學(xué)回顧我知道我曾說過,離數(shù)學(xué)越遠(yuǎn)越好,但是讓我們暫且忍一小段時(shí)間,在這段時(shí)間里,我們會(huì)盡快地回顧在代數(shù)中一些函數(shù)和圖像的基本知識。
你還記得你在學(xué)校里學(xué)習(xí)任何有關(guān) f(x) 的知識嗎?還有方程 y = f(x) ?
現(xiàn)有方程式定義如下:f(x) = 2x2 + 3。這個(gè)方程有什么意義?它對應(yīng)的圖像是什么樣的呢?如下圖:
你可以注意到:對于 x 取任意值,例如 2,帶入方程后會(huì)得到 11。這里的 11 代表函數(shù)的返回值,更簡單來說就是 y 值。
根據(jù)上述,現(xiàn)在有一個(gè)點(diǎn) (2,11) 在圖像的曲線上,并且當(dāng)我們有一個(gè) x 值,我們都能獲得一個(gè)對應(yīng)的 y 值。把兩個(gè)值組合就能得到一個(gè)點(diǎn)的坐標(biāo),例如 (0,3), (-1,5)。當(dāng)把所有的這些點(diǎn)放在一起,就會(huì)獲得這個(gè)拋物線方程的圖像,如上圖所示。
所以,這些和函數(shù)式編程有什么關(guān)系?
在數(shù)學(xué)中,函數(shù)總是獲取一些輸入值,然后給出一個(gè)輸出值。你能聽到一個(gè)函數(shù)式編程的術(shù)語叫做“態(tài)射”:這是一個(gè)優(yōu)雅的方式來描述一組值和另一組值的映射關(guān)系,就像一個(gè)函數(shù)的輸入值與輸出值之間的關(guān)聯(lián)關(guān)系。
在代數(shù)數(shù)學(xué)中,那些輸入值和輸出值經(jīng)常代表著繪制坐標(biāo)的一部分。不過,在我們的程序中,我們可以定義函數(shù)有各種的輸入和輸出值,并且它們不需要和繪制在圖表上的曲線有任何關(guān)系。
函數(shù) vs 程序為什么所有的討論都圍繞數(shù)學(xué)和圖像?因?yàn)樵谀撤N程度上,函數(shù)式編程就是使用在數(shù)學(xué)意義上的方程作為函數(shù)。
你可能會(huì)習(xí)以為常地認(rèn)為函數(shù)就是程序。它們之間的區(qū)別是什么?程序就是一個(gè)任意的功能集合。它或許有許多個(gè)輸入值,或許沒有。它或許有一個(gè)輸出值( return 值),或許沒有。
而函數(shù)則是接收輸入值,并明確地 return 值。
如果你計(jì)劃使用函數(shù)式編程,你應(yīng)該盡可能多地使用函數(shù),而不是程序。你所有編寫的 function 應(yīng)該接收輸入值,并且返回輸出值。這么做的原因是多方面的,我們將會(huì)在后面的書中來介紹的。
函數(shù)輸入從上述的定義出發(fā),所有的函數(shù)都需要輸入。
你有時(shí)聽人們把函數(shù)的輸入值稱為 “arguments” 或者 “parameters” 。所以它到底是什么?
arguments 是你輸入的值(實(shí)參), parameters 是函數(shù)中的命名變量(形參),用于接收函數(shù)的輸入值。例子如下:
function foo(x,y) { // .. } var a = 3; foo( a, a * 2 );
a 和 a * 2(即為 6)是函數(shù) foo(..) 調(diào)用的 arguments。x 和 y 是 parameters,用于接收參數(shù)值(分別為 3 和 6 )。
注意: 在 JavaScript 中,實(shí)參的個(gè)數(shù)沒必要完全符合形參的個(gè)數(shù)。如果你傳入許多個(gè)實(shí)參,而且多過你所聲明的形參,這些值仍然會(huì)原封不動(dòng)地被傳入。你可以通過不同的方式去訪問,包含了你以前可能聽過的老辦法 —— arguments 對象。反之,你傳入少于聲明形參個(gè)數(shù)的實(shí)參,所有缺少的參數(shù)將會(huì)被賦予 undefined 變量,意味著你仍然可以在函數(shù)作用域中使用它,但值是 undefined。
輸入計(jì)數(shù)一個(gè)函數(shù)所“期望”的實(shí)參個(gè)數(shù)是取決于已聲明的形參個(gè)數(shù),即你希望傳入多少參數(shù)。
function foo(x,y,z) { // .. }
foo(..) 期望三個(gè)實(shí)參,因?yàn)樗暶髁巳齻€(gè)形參。這里有一個(gè)特殊的術(shù)語:Arity。Arity 指的是一個(gè)函數(shù)聲明的形參數(shù)量。 foo(..) 的 Arity 是 3。
你可能需要在程序運(yùn)行時(shí)獲取函數(shù)的 Arity,使用函數(shù)的 length 屬性即可。
function foo(x,y,z) { // .. } foo.length; // 3
在執(zhí)行時(shí)要確定 Arity 的一個(gè)原因是:一段代碼接受一個(gè)函數(shù)的指針引用,有可能這個(gè)引用指向不同來源,我們要根據(jù)這些來源的 Arity 傳入不同的參數(shù)值。
舉個(gè)例子,如果 fn 可能指向的函數(shù)分別期望 1、2 或 3 個(gè)參數(shù),但你只希望把變量 x 放在最后的位置傳入:
// fn 是一些函數(shù)的引用 // x 是存在的值 if (fn.length == 1) { fn( x ); } else if (fn.length == 2) { fn( undefined, x ); } else if (fn.length == 3) { fn( undefined, undefined, x ); }
提示: 函數(shù)的 length 屬性是一個(gè)只讀屬性,并且它是在最初聲明函數(shù)的時(shí)候就被確定了。它應(yīng)該當(dāng)做用來描述如何使用該函數(shù)的一個(gè)基本元數(shù)據(jù)。
需要注意的是,某些參數(shù)列表的變量會(huì)讓 length 屬性變得不同于你的預(yù)期。別緊張,我們將會(huì)在后續(xù)的章節(jié)逐一解釋這些特性(引入 ES6):
function foo(x,y = 2) { // .. } function bar(x,...args) { // .. } function baz( {a,b} ) { // .. } foo.length; // 1 bar.length; // 1 baz.length; // 1
如果你使用這些形式的參數(shù),你或許會(huì)被函數(shù)的 length 值嚇一跳。
那我們怎么得到當(dāng)前函數(shù)調(diào)用時(shí)所接收到的實(shí)參個(gè)數(shù)呢?這在以前非常簡單,但現(xiàn)在情況稍微復(fù)雜了一些。每一個(gè)函數(shù)都有一個(gè) arguments 對象(類數(shù)組)存放需要傳入的參數(shù)。你可以通過 arguments 的 length 值來找出有多少傳入的參數(shù):
function foo(x,y,z) { console.log( arguments.length ); // 2 } foo( 3, 4 );
由于 ES5(特別是嚴(yán)格模式下)的 arguments 不被一些人認(rèn)同,很多人盡可能地避免使用。盡管如此,它永遠(yuǎn)不會(huì)被移除,這是因?yàn)樵?JS 中我們“永遠(yuǎn)不會(huì)”因?yàn)楸憷远奚蚝蟮募嫒菪裕疫€是強(qiáng)烈建議不要去使用它。
然而,當(dāng)你需要知道參數(shù)個(gè)數(shù)的時(shí)候,arguments.length 還是可以用的。在未來版本的 JS 或許會(huì)新增特性來替代 arguments.length,如果成真,那么我們可以完全把 arguments 拋諸腦后。
請注意:不要通過 arguments[1] 訪問參數(shù)的位置。只要記住 arguments.length。
除此之外,你或許想知道如何訪問那些超出聲明的參數(shù)?這個(gè)問題我一會(huì)兒會(huì)告訴你,不過你先要問自己的問題是,“為什么我想要知道這個(gè)?”。認(rèn)真地思考一段時(shí)間。
發(fā)生這種情況應(yīng)該是非常罕見的。因?yàn)檫@不會(huì)是你日常需要的,也不會(huì)是你編寫函數(shù)時(shí)所必要的東西。如果這種情況真的發(fā)生,你應(yīng)該花 20 分鐘來試著重新設(shè)計(jì)函數(shù),或者命名那些多出來的參數(shù)。
帶有可變數(shù)量參數(shù)的函數(shù)被稱為 variadic。有些人更喜歡這樣的函數(shù)設(shè)計(jì),不過你會(huì)發(fā)現(xiàn),這正是函數(shù)式編程者想要避免的。
好了,上面的重點(diǎn)已經(jīng)講得夠多了。
例如,當(dāng)你需要像數(shù)組那樣訪問參數(shù),很有可能的原因是你想要獲取的參數(shù)沒有在一個(gè)規(guī)范的位置。我們?nèi)绾翁幚恚?/p>
ES6 救星來了!讓我們用 ... 操作符聲明我們的函數(shù),也被當(dāng)做 “spread”、“rest” 或者 “gather” (我比較偏愛)提及。
function foo(x,y,z,...args) { // .. }
看到參數(shù)列表中的 ...args 了嗎?那就是 ES6 用來告訴解析引擎獲取所有剩余的未命名參數(shù),并把它們放在一個(gè)真實(shí)的命名為 args 的數(shù)組。args 無論是不是空的,它永遠(yuǎn)是一個(gè)數(shù)組。但它不包含已經(jīng)命名的 x,y 和 z 參數(shù),只會(huì)包含超出前三個(gè)值的傳入?yún)?shù)。
function foo(x,y,z,...args) { console.log( x, y, z, args ); } foo(); // undefined undefined undefined [] foo( 1, 2, 3 ); // 1 2 3 [] foo( 1, 2, 3, 4 ); // 1 2 3 [ 4 ] foo( 1, 2, 3, 4, 5 ); // 1 2 3 [ 4, 5 ]
所以,如果你誠心想要設(shè)計(jì)一個(gè)函數(shù),并且計(jì)算出任意傳入?yún)?shù)的個(gè)數(shù),那就在最后用 ...args (或任何你喜歡的名稱)。現(xiàn)在你有一個(gè)真正的、好用的數(shù)組來獲取這些參數(shù)值了。
你需要注意的是: 4 所在的位置是 args 的第 0 個(gè),不是在第 3 個(gè)位置。它的 length 值也不包含 1、2 和 3,...args 剩下所有的值, 但不包括 x、y 和 z。
你甚至可以直接在參數(shù)列中使用 ... 操作符,沒有其他正式聲明的參數(shù)也沒關(guān)系:
function foo(...args) { // .. }
現(xiàn)在 args 是一個(gè)由參數(shù)組成的完整數(shù)組,你可以盡情使用 args.length 來獲取傳入的參數(shù)。你也可以安全地使用 args[1] 或者 args[317]。當(dāng)然,別真的傳 318 個(gè)參數(shù)!
說到 ES6 的好,你肯定想知道一些小秘訣。在這里將會(huì)介紹一些,更多的內(nèi)容推薦你閱讀《You Don"t Know JS: ES6 & Beyond》這本書的第 2 章。
關(guān)于實(shí)參的小技巧如果你希望調(diào)用函數(shù)的時(shí)候只傳一個(gè)數(shù)組代替之前的多個(gè)參數(shù),該怎么辦?
function foo(...args) { console.log( args[3] ); } var arr = [ 1, 2, 3, 4, 5 ]; foo( ...arr ); // 4
我們的新朋友 ... 在這里被使用到了,但不僅僅在形參列表,在函數(shù)調(diào)用的時(shí)候,同樣使用在實(shí)參列表。在這里的情況有所不同:在形參列表,它把實(shí)參整合。在實(shí)參列表,它把實(shí)參展開。所以 arr 的內(nèi)容是以函數(shù) foo(..) 引用的多帶帶參數(shù)進(jìn)行展開。你能理解傳入一個(gè)引用值和傳入整個(gè) arr 數(shù)組兩者之間的不同了嗎?
順帶一提,多個(gè)值和 ... 是可以相互交錯(cuò)放置的,如下:
var arr = [ 2 ]; foo( 1, ...arr, 3, ...[4,5] ); // 4
在對稱的意義上來考慮 ... :在值列表的情況,它會(huì)展開。在賦值的情況,它就像形參列表一樣,因?yàn)閷?shí)參會(huì)賦值到形參上。
無論采取什么行為, ... 都會(huì)讓實(shí)參數(shù)組更容易操作。那些我們使用實(shí)參數(shù)組 slice(..),concat(..) 和 apply(..) 的日子已經(jīng)過去了。
關(guān)于形參的小技巧在 ES6 中,形參可以聲明默認(rèn)值。當(dāng)形參沒有傳入到實(shí)參中,或者傳入值是 undefined,會(huì)進(jìn)行默認(rèn)賦值的操作。
思考下面代碼:
function foo(x = 3) { console.log( x ); } foo(); // 3 foo( undefined ); // 3 foo( null ); // null foo( 0 ); // 0
注意: 我們不會(huì)更加詳細(xì)地解釋了,但是默認(rèn)值表達(dá)式是惰性的,這意味著僅當(dāng)需要的時(shí)候,它才會(huì)被計(jì)算。它同樣也可以是一些有效的 JS 表達(dá)式,甚至一個(gè)函數(shù)引用。許多非常酷的小技巧用到了這個(gè)方法。例如,你可以這樣在你的參數(shù)列聲明 x = required(),并且在函數(shù) required() 中 拋出 "This argument is required." 來確信總有人用你指定的實(shí)參或形參來引用你的函數(shù)。
另一個(gè)我們可以在參數(shù)中使用的 ES6 技巧,被稱為“解構(gòu)”。在這里我們只會(huì)簡單一提,因?yàn)橐f清這個(gè)話題實(shí)在太過繁雜。在這里推薦《ES6 & Beyond》這本書了解更多信息。
還記得我們之前提到的可以接受 318 個(gè)參數(shù)的 foo(..) 嗎?
function foo(...args) { // .. } foo( ...[1,2,3] );
如果我們想要把函數(shù)內(nèi)的參數(shù)從一個(gè)個(gè)多帶帶的參數(shù)值替換為一個(gè)數(shù)組,應(yīng)該怎么做?這里有兩個(gè) ... 的寫法:
function foo(args) { // .. } foo( [1,2,3] );
這個(gè)非常簡單。但如果我們想要命名傳入數(shù)組的第 1、2 個(gè)值,該怎么做?我們不能用多帶帶傳入?yún)?shù)的辦法了,所以這似乎看起來無能為力。不過解構(gòu)可以回答這個(gè)問題:
function foo( [x,y,...args] = [] ) { // .. } foo( [1,2,3] );
你看到了在參數(shù)列出現(xiàn)的 [ .. ] 了嗎?這就是數(shù)組解構(gòu)。解構(gòu)是通過你期望的模式來描述數(shù)據(jù)(對象,數(shù)組等),并分配(賦值)值的一種方式。
在這里例子中,解構(gòu)告訴解析器,一個(gè)數(shù)組應(yīng)該出現(xiàn)的賦值位置(即參數(shù))。這種模式是:拿出數(shù)組中的第一個(gè)值,并且賦值給局部參數(shù)變量 x,第二個(gè)賦值給 y,剩下的則組成 args。
你可以通過自己手動(dòng)處理達(dá)到同樣的效果:
function foo(params) { var x = params[0]; var y = params[1]; var args = params.slice( 2 ); // .. }
現(xiàn)在我們可以發(fā)現(xiàn),在我們這本書中要多次提到的第一條原則:聲明性代碼通常比命令式代碼更干凈。
聲明式代碼,如同之前代碼片段里的解構(gòu),強(qiáng)調(diào)一段代碼的輸出結(jié)果。命令式代碼,像剛才我們自己手動(dòng)賦值的例子,注重的是如何得到結(jié)果。如果你稍晚再讀這一段代碼,你必須在腦子里面再執(zhí)行一遍才能得到你想要的結(jié)果。這個(gè)結(jié)果是編寫在這兒,但是不是直接可見的。
只要可能,無論我們的語言和我們的庫或框架允許我們達(dá)到什么程度,我們都應(yīng)該盡可能使用聲明性的和自解釋的代碼。
正如我們可以解構(gòu)的數(shù)組,我們可以解構(gòu)的對象參數(shù):
function foo( {x,y} = {} ) { console.log( x, y ); } foo( { y: 3 } ); // undefined 3
我們傳入一個(gè)對象作為一個(gè)參數(shù),它解構(gòu)成兩個(gè)獨(dú)立的參數(shù)變量 x 和 y,從傳入的對象中分配相應(yīng)屬性名的值。我們不在意屬性值 x 到底存不存在對象上,如果不存在,它最終會(huì)如你所想被賦值為 undefined。
但是我希望你注意:對象解構(gòu)的部分參數(shù)是將要傳入 foo(..) 的對象。
現(xiàn)在有一個(gè)正常可用的調(diào)用現(xiàn)場 foo(undefined,3),它用于映射實(shí)參到形參。我們試著把 3 放到第二個(gè)位置,分配給 y。但是在新的調(diào)用現(xiàn)場上用到了參數(shù)解構(gòu),一個(gè)簡單的對象屬性代表了實(shí)參 3 應(yīng)該分配給形參(y)。
我們不需要操心 x 應(yīng)該放在哪個(gè)調(diào)用現(xiàn)場。因?yàn)槭聦?shí)上,我們不用去關(guān)心 x,我們只需要省略它,而不是分配 undefined 值。
有一些語言對這樣的操作有一個(gè)直接的特性:命名參數(shù)。換句話說,在調(diào)用現(xiàn)場,通過標(biāo)記輸入值來告訴它映射關(guān)系。JavaScript 沒有命名參數(shù),不過退而求其次,參數(shù)對象解構(gòu)是一個(gè)選擇。
使用對象解構(gòu)來傳入多個(gè)匿名參數(shù)是函數(shù)式編程的優(yōu)勢,這個(gè)優(yōu)勢在于使用一個(gè)參數(shù)(對象)的函數(shù)能更容易接受另一個(gè)函數(shù)的單個(gè)輸出。這點(diǎn)會(huì)在后面討論到。
回想一下,術(shù)語 Arity 是指期望函數(shù)接收多少個(gè)參數(shù)。Arity 為 1 的函數(shù)也被稱為一元函數(shù)。在函數(shù)式編程中,我們希望我們的函數(shù)在任何的情況下是一元的,有時(shí)我們甚至?xí)褂酶鞣N技巧來將高 Arity 的函數(shù)都轉(zhuǎn)換為一元的形式。
注意: 在第 3 章,我們將重新討論命名參數(shù)的解構(gòu)技巧,并使用它來處理關(guān)于參數(shù)排序的問題。
隨著輸入而變化的函數(shù)思考以下函數(shù)
function foo(x,y) { if (typeof x == "number" && typeof y == "number") { return x * y; } else { return x + y; } }
明顯地,這個(gè)函數(shù)會(huì)根據(jù)你傳入的值而有所不同。
舉例:
foo( 3, 4 ); // 12 foo( "3", 4 ); // "34"
程序員這樣定義函數(shù)的原因之一是,更容易通過同一個(gè)函數(shù)來重載不同的功能。最廣為人知的例子就是 jQuery 提供的 $(..)。"$" 函數(shù)大約有十幾種不同的功能 —— 從 DOM 元素查找,到 DOM 元素創(chuàng)建,到等待 “DOMContentLoaded” 事件后,執(zhí)行一個(gè)函數(shù),這些都取決于你傳遞給它的參數(shù)。
上述函數(shù),顯而易見的優(yōu)勢是 API 變少了(僅僅是一個(gè) $(..) 函數(shù)),但缺點(diǎn)體現(xiàn)在閱讀代碼上,你必須仔細(xì)檢查傳遞的內(nèi)容,理解一個(gè)函數(shù)調(diào)用將做什么。
通過不同的輸入值讓一個(gè)函數(shù)重載擁有不同的行為的技巧叫做特定多態(tài)(ad hoc polymorphism)。
這種設(shè)計(jì)模式的另一個(gè)表現(xiàn)形式就是在不同的情況下,使函數(shù)具有不同的輸出(在下一章節(jié)會(huì)提到)。
警告: 要對方便的誘惑有警惕之心。因?yàn)槟憧梢酝ㄟ^這種方式設(shè)計(jì)一個(gè)函數(shù),即使可以立即使用,但這個(gè)設(shè)計(jì)的長期成本可能會(huì)讓你后悔。
函數(shù)輸出在 JavaScript 中,函數(shù)只會(huì)返回一個(gè)值。下面的三個(gè)函數(shù)都有相同的 return 操作。
function foo() {} function bar() { return; } function baz() { return undefined; }
如果你沒有 return 值,或者你使用 return;,那么則會(huì)隱式地返回 undefined 值。
如果想要盡可能靠近函數(shù)式編程的定義:使用函數(shù)而非程序,那么我們的函數(shù)必須永遠(yuǎn)有返回值。這也意味著他們必須明確地 return 一個(gè)值,通常這個(gè)值也不是 undefined。
一個(gè) return 的表達(dá)式僅能夠返回一個(gè)值。所以,如果你需要返回多個(gè)值,切實(shí)可行的辦法就是把你需要返回的值放到一個(gè)復(fù)合值當(dāng)中去,例如數(shù)組、對象:
function foo() { var retValue1 = 11; var retValue2 = 31; return [ retValue1, retValue2 ]; }
解構(gòu)方法可以使用于解構(gòu)對象或者數(shù)組類型的參數(shù),也可以使用在平時(shí)的賦值當(dāng)中:
function foo() { var retValue1 = 11; var retValue2 = 31; return [ retValue1, retValue2 ]; } var [ x, y ] = foo(); console.log( x + y ); // 42
將多個(gè)值集合成一個(gè)數(shù)組(或?qū)ο螅┳鰹榉祷刂担缓笤俳鈽?gòu)回不同的值,這無形中讓一個(gè)函數(shù)能有多個(gè)輸出結(jié)果。
提示: 在這里我十分建議你花一點(diǎn)時(shí)間來思考:是否需要避免函數(shù)有可重構(gòu)的多個(gè)輸出?或許將這個(gè)函數(shù)分為兩個(gè)或更多個(gè)更小的單用途函數(shù)。有時(shí)會(huì)需要這么做,有時(shí)可能不需要,但你應(yīng)該至少考慮一下。
提前 returnreturn 語句不僅僅是從函數(shù)中返回一個(gè)值,它也是一個(gè)流量控制結(jié)構(gòu),它可以結(jié)束函數(shù)的執(zhí)行。因此,具有多個(gè) return 語句的函數(shù)具有多個(gè)可能的退出點(diǎn),這意味著如果輸出的路徑很多,可能難以讀取并理解函數(shù)的輸出行為。
思考以下:
function foo(x) { if (x > 10) return x + 1; var y = x / 2; if (y > 3) { if (x % 2 == 0) return x; } if (y > 1) return y; return x; }
突擊測驗(yàn):不要作弊也不要在瀏覽器中運(yùn)行這段代碼,請思考 foo(2) 返回什么? foo(4) 返回什么? foo(8), foo(12) 呢?
你對自己的回答有多少信心?你付出多少精力來獲得答案?我錯(cuò)了兩次后,我試圖仔細(xì)思考并且寫下來!
我認(rèn)為在許多可讀性的問題上,是因?yàn)槲覀儾粌H使用 return 返回不同的值,更把它作為一個(gè)流控制結(jié)構(gòu)——在某些情況下可以提前退出一個(gè)函數(shù)的執(zhí)行。我們顯然有更好的方法來編寫流控制( if 邏輯等),也有辦法使輸出路徑更加明顯。
注意: 突擊測驗(yàn)的答案是:2,2,8 和 13。
思考以下版本的代碼:
function foo(x) { var retValue; if (retValue == undefined && x > 10) { retValue = x + 1; } var y = x / 2; if (y > 3) { if (retValue == undefined && x % 2 == 0) { retValue = x; } } if (retValue == undefined && y > 1) { retValue = y; } if (retValue == undefined) { retValue = x; } return retValue; }
這個(gè)版本毫無疑問是更冗長的。但是在邏輯上,我認(rèn)為這比上面的代碼更容易理解。因?yàn)樵诿總€(gè) retValue 可以被設(shè)置的分支, 這里都有個(gè)守護(hù)者以確保 retValue 沒有被設(shè)置過才執(zhí)行。
相比在函數(shù)中提早使用 return,我們更應(yīng)該用常用的流控制( if 邏輯 )來控制 retValue 的賦值。到最后,我們 return retValue。
我不是說,你只能有一個(gè) return,或你不應(yīng)該提早 return,我只是認(rèn)為在定義函數(shù)時(shí),最好不要用 return 來實(shí)現(xiàn)流控制,這樣會(huì)創(chuàng)造更多的隱含意義。嘗試找出最明確的表達(dá)邏輯的方式,這往往是最好的辦法。
未 return 的輸出有個(gè)技巧你可能在你的大多數(shù)代碼里面使用過,并且有可能你自己并沒有特別意識到,那就是讓一個(gè)函數(shù)通過改變函數(shù)體外的變量產(chǎn)出一些值。
還記得我們之前提到的函數(shù)f(x) = 2x2 + 3嗎?我們可以在 JS 中這樣定義:
var y; function foo(x) { y = (2 * Math.pow( x, 2 )) + 3; } foo( 2 ); y; // 11
我知道這是一個(gè)無聊的例子。我們完全可以用 return 來返回,而不是賦值給 y:
function foo(x) { return (2 * Math.pow( x, 2 )) + 3; } var y = foo( 2 ); y; // 11
這兩個(gè)函數(shù)完成相同的任務(wù)。我們有什么理由要從中挑一個(gè)嗎?是的,絕對有。
解釋這兩者不同的一種方法是,后一個(gè)版本中的 return 表示一個(gè)顯式輸出,而前者的 y 賦值是一個(gè)隱式輸出。在這種情況下,你可能已經(jīng)猜到了:通常,開發(fā)人員喜歡顯式模式而不是隱式模式。
但是,改變一個(gè)外部作用域的變量,就像我們在 foo(..) 中所做的賦值 y 一樣,只是實(shí)現(xiàn)隱式輸出的一種方式。一個(gè)更微妙的例子是通過引用對非局部值進(jìn)行更改。
思考:
function sum(list) { var total = 0; for (let i = 0; i < list.length; i++) { if (!list[i]) list[i] = 0; total = total + list[i]; } return total; } var nums = [ 1, 3, 9, 27, , 84 ]; sum( nums ); // 124
很明顯,這個(gè)函數(shù)輸出為 124,我們也非常明確地 return 了。但你是否發(fā)現(xiàn)其他的輸出?查看代碼,并檢查 nums 數(shù)組。你發(fā)現(xiàn)區(qū)別了嗎?
為了填補(bǔ) 4 位置的空值 undefined,這里使用了 0 代替。盡管我們在局部操作 list 參數(shù)變量,但我們?nèi)匀挥绊懥送獠康臄?shù)組。
為什么?因?yàn)?list 使用了 nums 的引用,不是對 [1,3,9,..] 的值復(fù)制,而是引用復(fù)制。因?yàn)?JS 對數(shù)組、對象和函數(shù)都使用引用和引用復(fù)制,我們可以很容易地從函數(shù)中創(chuàng)建輸出,即使是無心的。
這個(gè)隱式函數(shù)輸出在函數(shù)式編程中有一個(gè)特殊的名稱:副作用。當(dāng)然,沒有副作用的函數(shù)也有一個(gè)特殊的名稱:純函數(shù)。我們將在以后的章節(jié)討論這些,但關(guān)鍵是我們應(yīng)該喜歡純函數(shù),并且要盡可能地避免副作用。
函數(shù)功能函數(shù)是可以接受并且返回任何類型的值。一個(gè)函數(shù)如果可以接受或返回一個(gè)甚至多個(gè)函數(shù),它被叫做高階函數(shù)。
思考:
function forEach(list,fn) { for (let i = 0; i < list.length; i++) { fn( list[i] ); } } forEach( [1,2,3,4,5], function each(val){ console.log( val ); } ); // 1 2 3 4 5
forEach(..) 就是一個(gè)高階函數(shù),因?yàn)樗梢越邮芤粋€(gè)函數(shù)作為參數(shù)。
一個(gè)高階函數(shù)同樣可以把一個(gè)函數(shù)作為輸出,像這樣:
function foo() { var fn = function inner(msg){ console.log( msg ); }; return fn; } var f = foo(); f( "Hello!" ); // Hello!
return 不是“輸出”函數(shù)的唯一辦法。
function foo() { var fn = function inner(msg){ console.log( msg ); }; bar( fn ); } function bar(func) { func( "Hello!" ); } foo(); // Hello!
將其他函數(shù)視為值的函數(shù)是高階函數(shù)的定義。函數(shù)式編程者們應(yīng)該學(xué)會(huì)這樣寫!
保持作用域在所有編程,尤其是函數(shù)式編程中,最強(qiáng)大的就是:當(dāng)一個(gè)函數(shù)內(nèi)部存在另一個(gè)函數(shù)的作用域時(shí),對當(dāng)前函數(shù)進(jìn)行操作。當(dāng)內(nèi)部函數(shù)從外部函數(shù)引用變量,這被稱作閉包。
實(shí)際上,閉包是它可以記錄并且訪問它作用域外的變量,甚至當(dāng)這個(gè)函數(shù)在不同的作用域被執(zhí)行。
思考:
function foo(msg) { var fn = function inner(){ console.log( msg ); }; return fn; } var helloFn = foo( "Hello!" ); helloFn(); // Hello!
處于 foo(..) 函數(shù)作用域中的 msg 參數(shù)變量是可以在內(nèi)部函數(shù)中被引用的。當(dāng) foo(..) 執(zhí)行時(shí),并且內(nèi)部函數(shù)被創(chuàng)建,函數(shù)可以獲取 msg 變量,即使 return 后仍可被訪問。
雖然我們有函數(shù)內(nèi)部引用 helloFn,現(xiàn)在 foo(..) 執(zhí)行后,作用域應(yīng)該回收,這也意味著 msg 也不存在了。不過這個(gè)情況并不會(huì)發(fā)生,函數(shù)內(nèi)部會(huì)因?yàn)殚]包的關(guān)系,將 msg 保留下來。只要內(nèi)部函數(shù)(現(xiàn)在被處在不同作用域的 helloFn 引用)存在, msg 就會(huì)一直被保留。
讓我們看看閉包作用的一些例子:
function person(id) { var randNumber = Math.random(); return function identify(){ console.log( "I am " + id + ": " + randNumber ); }; } var fred = person( "Fred" ); var susan = person( "Susan" ); fred(); // I am Fred: 0.8331252801601532 susan(); // I am Susan: 0.3940753308893741
identify() 函數(shù)內(nèi)部有兩個(gè)閉包變量,參數(shù) id 和 randNumber。
閉包不僅限于獲取變量的原始值:它不僅僅是快照,而是直接鏈接。你可以更新該值,并在下次訪問時(shí)獲取更新后的值。
function runningCounter(start) { var val = start; return function current(increment = 1){ val = val + increment; return val; }; } var score = runningCounter( 0 ); score(); // 1 score(); // 2 score( 13 ); // 15
警告: 我們將在之后的段落中介紹更多。不過在這個(gè)例子中,你需要盡可能避免使用閉包來記錄狀態(tài)更改(val)。
如果你需要設(shè)置兩個(gè)輸入,一個(gè)你已經(jīng)知道,另一個(gè)還需要后面才能知道,你可以使用閉包來記錄第一個(gè)輸入值:
function makeAdder(x) { return function sum(y){ return x + y; }; } //我們已經(jīng)分別知道作為第一個(gè)輸入的 10 和 37 var addTo10 = makeAdder( 10 ); var addTo37 = makeAdder( 37 ); // 緊接著,我們指定第二個(gè)參數(shù) addTo10( 3 ); // 13 addTo10( 90 ); // 100 addTo37( 13 ); // 50
通常, sum(..) 函數(shù)會(huì)一起接收 x 和 y 并相加。但是在這個(gè)例子中,我們接收并且首先記錄(通過閉包) x 的值,然后等待 y 被指定。
注意: 在連續(xù)函數(shù)調(diào)用中指定輸入,這種技巧在函數(shù)式編程中非常普遍,并且有兩種形式:偏函數(shù)應(yīng)用和柯里化。我們稍后會(huì)在文中深入討論。
當(dāng)然,因?yàn)楹瘮?shù)如果只是 JS 中的值,我們可以通過閉包來記住函數(shù)值。
function formatter(formatFn) { return function inner(str){ return formatFn( str ); }; } var lower = formatter( function formatting(v){ return v.toLowerCase(); } ); var upperFirst = formatter( function formatting(v){ return v[0].toUpperCase() + v.substr( 1 ).toLowerCase(); } ); lower( "WOW" ); // wow upperFirst( "hello" ); // Hello
函數(shù)式編程并不是在我們的代碼中分配或重復(fù) toUpperCase() 和 toLowerCase() 邏輯,而是鼓勵(lì)我們用優(yōu)雅的封裝方式來創(chuàng)建簡單的函數(shù)。
具體來說,我們創(chuàng)建兩個(gè)簡單的一元函數(shù) lower(..) 和 upperFirst(..),因?yàn)檫@些函數(shù)在我們程序中,更容易與其他函數(shù)配合使用。
提示: 你知道如何讓 upperFirst(..) 使用 lower(..) 嗎?
我們將在本書的后續(xù)中大量使用閉包。如果拋開整個(gè)編程來說,它可能是所有函數(shù)式編程中最重要的基礎(chǔ)。希望你能用得舒服!
句法在我們函數(shù)入門開始之前,讓我們花點(diǎn)時(shí)間來討論它的語法。
不同于本書中的許多其他部分,本節(jié)中的討論主要是意見和偏好,無論你是否同意這里提出的觀點(diǎn)或采取相反的觀點(diǎn)。這些想法是非常主觀的,盡管許多人似乎對此非常執(zhí)著。不過最終,都由你決定。
什么是名稱?在語法上,函數(shù)聲明需要包含一個(gè)名稱:
function helloMyNameIs() { // .. }
但是函數(shù)表達(dá)式可以命名或者匿名:
foo( function namedFunctionExpr(){ // .. } ); bar( function(){ // <-- 這就是匿名的! // .. } );
順便說一句,匿名的意思是什么?具體來說,函數(shù)具有一個(gè) name 的屬性,用于保存函數(shù)在語法上設(shè)定名稱的字符串值,例如 "helloMyNameIs" 或 "FunctionExpr"。 這個(gè)name 屬性特別用于 JS 環(huán)境的控制臺或開發(fā)工具。當(dāng)我們在堆棧軌跡中追蹤(通常來自異常)時(shí),這個(gè)屬性可以列出該函數(shù)。
而匿名函數(shù)通常顯示為:(anonymous function)。
如果你曾經(jīng)試著在一個(gè)異常的堆棧軌跡中調(diào)試一個(gè) JS 程序,你可能已經(jīng)發(fā)現(xiàn)痛苦了:看到 (anonymous function) 出現(xiàn)。這個(gè)列表?xiàng)l目不給開發(fā)人員任何關(guān)于異常來源路徑的線索。它沒有給我們開發(fā)者提供任何幫助。
如果你命名了你的函數(shù)表達(dá)式,名稱將會(huì)一直被使用。所以如果你使用了一個(gè)良好的名稱 handleProfileClicks 來取代 foo,你將會(huì)在堆棧軌跡中獲得更多的信息。
在 ES6 中,匿名表達(dá)式可以通過名稱引用來獲得名稱。思考:
var x = function(){}; x.name; // x
如果解析器能夠猜到你可能希望函數(shù)采用什么名稱,那么它將會(huì)繼續(xù)下去。
但請注意,并不是所有的句法形式都可以用名稱引用。最常見的地方是函數(shù)表達(dá)式是函數(shù)調(diào)用的參數(shù):
function foo(fn) { console.log( fn.name ); } var x = function(){}; foo( x ); // x foo( function(){} ); //
當(dāng)名稱不能直接從周圍的語法中被推斷時(shí),它仍會(huì)是一個(gè)空字符串。這樣的函數(shù)將在堆棧軌跡中的被報(bào)告為一個(gè) (anonymous function)。
除了調(diào)試問題之外,函數(shù)被命名還有一個(gè)其他好處。首先,句法名稱(又稱詞匯名)是可以被函數(shù)內(nèi)部的自引用。自引用是遞歸(同步和異步)所必需的,也有助于事件處理。
思考這些不同的情況:
// 同步情況: function findPropIn(propName,obj) { if (obj == undefined || typeof obj != "object") return; if (propName in obj) { return obj[propName]; } else { let props = Object.keys( obj ); for (let i = 0; i < props.length; i++) { let ret = findPropIn( propName, obj[props[i]] ); if (ret !== undefined) { return ret; } } } }
// 異步情況: setTimeout( function waitForIt(){ // it 存在了嗎? if (!o.it) { // 再試一次 setTimeout( waitForIt, 100 ); } }, 100 );
// 事件處理未綁定 document.getElementById( "onceBtn" ) .addEventListener( "click", function handleClick(evt){ // 未綁定的 event evt.target.removeEventListener( "click", handleClick, false ); // .. }, false );
在這些情況下,使用命名函數(shù)的函數(shù)名引用,是一種有用和可靠的在自身內(nèi)部自引用的方式。
此外,即使在單行函數(shù)的簡單情況下,命名它們往往會(huì)使代碼更加明了,從而讓以前沒有閱讀過的人更容易閱讀:
people.map( function getPreferredName(person){ return person.nicknames[0] || person.firstName; } ) // ..
光看函數(shù) getPreferredName(..) 的代碼,并不能很明確告訴我們這里的操作是什么意圖。但有名稱就可以增加代碼可讀性。
經(jīng)常使用匿名函數(shù)表達(dá)式的另一個(gè)地方是 IIFE (立即執(zhí)行函數(shù)表達(dá)式):
(function(){ // 我是 IIFE! })();
你幾乎從沒看到為 IIFE 函數(shù)來命名,但他們應(yīng)該命名。為什么?我們剛剛提到過的原因:堆棧軌跡調(diào)試,可靠的自我引用和可讀性。如果你想不出你的 IIFE 應(yīng)該叫什么,請至少使用 IIFE:
(function IIFE(){ // 現(xiàn)在你真的知道我叫 IIFE! })();
我有許多個(gè)理由可以解釋命名函數(shù)比匿名函數(shù)更可取。事實(shí)上,我甚至認(rèn)為匿名函數(shù)都是不可取的。相比命名函數(shù),他們沒有任何優(yōu)勢。
寫匿名功能非常容易,因?yàn)槲覀兺耆挥迷谙朊Q這件事上費(fèi)神費(fèi)力。
誠實(shí)來講,我也像大家一樣在這個(gè)地方犯錯(cuò)。我不喜歡在起名稱這件事上浪費(fèi)時(shí)間。我能想到命名一個(gè)函數(shù)的前 3 或 4 個(gè)名字通常是不好的。我必須反復(fù)思考這個(gè)命名。這個(gè)時(shí)候,我寧愿只是用一個(gè)匿名函數(shù)表達(dá)。
但是,我們把易寫性拿來與易讀性做交換,這不是一個(gè)好選擇。因?yàn)閼卸幌霝槟愕暮瘮?shù)命名,這是常見的使用匿名功能的借口。
命名所有單個(gè)函數(shù)。如果你對著你寫的函數(shù),想不出一個(gè)好名稱,我明確告訴你,那是你并沒有完全理解這個(gè)函數(shù)的目的——或者來說它的目的太廣泛或太抽象。你需要重新設(shè)計(jì)功能,直到它更清楚。從這個(gè)角度說,一個(gè)名稱會(huì)更明白清晰。
從我自己的經(jīng)驗(yàn)中證明,在思考名稱的過程中,我會(huì)更好地了解它,甚至重構(gòu)其設(shè)計(jì),以提高可讀性和可維護(hù)性。這些時(shí)間的投入是值得的。
沒有 function 的函數(shù)到目前為止,我們一直在使用完整的規(guī)范語法功能。但是相信你也對新的 ES6 => 箭頭函數(shù)語法有所耳聞。
比較:
people.map( function getPreferredName(person){ return person.nicknames[0] || person.firstName; } ) // .. people.map( person => person.nicknames[0] || person.firstName );
哇!
關(guān)鍵字 function 沒了,return,() 括號,{} 花括號和 ; 分號也是這樣。所有這一切,都是我們與一個(gè)胖箭頭做了交易: =>。
但還有另一件事我們忽略了。 你發(fā)現(xiàn)了嗎?getPreferredName 函數(shù)名也沒了。
那就對了。 => 箭頭函數(shù)是詞法匿名的。沒有辦法合理地為它提供一個(gè)名字。他們的名字可以像常規(guī)函數(shù)一樣被推斷,但是,最常見的函數(shù)表達(dá)式值作為參數(shù)的情況將不會(huì)起任何作用了。
假設(shè) person.nicknames 因?yàn)橐恍┰驔]有被定義,一個(gè)異常將會(huì)被拋出,意味著這個(gè) (anonymous function) 將會(huì)在追蹤堆棧的最上層。啊!
=> 箭頭函數(shù)的匿名性是 => 的阿喀琉斯之踵。這讓我不能遵守剛剛所說的命名原則了:閱讀困難,調(diào)試?yán)щy,無法自我引用。
但是,這還不夠糟糕,要面對的另一個(gè)問題是,如果你的函數(shù)定義有不同的場景,那么你必須要一大堆細(xì)微差別的語句來實(shí)現(xiàn)。我不會(huì)在這里詳細(xì)介紹所有,但會(huì)簡要地說:
people.map( person => person.nicknames[0] || person.firstName ); // 多個(gè)參數(shù)? 需要 ( ) people.map( (person,idx) => person.nicknames[0] || person.firstName ); // 解構(gòu)參數(shù)? 需要 ( ) people.map( ({ person }) => person.nicknames[0] || person.firstName ); // 默認(rèn)參數(shù)? 需要 ( ) people.map( (person = {}) => person.nicknames[0] || person.firstName ); // 返回對象? 需要 ( ) people.map( person => ({ preferredName: person.nicknames[0] || person.firstName }) );
在函數(shù)式編程中, => 令人興奮的地方在于它幾乎完全遵循函數(shù)的數(shù)學(xué)符號,特別是像 Haskell 這樣的函數(shù)式編程語言。=> 箭頭函數(shù)語法甚至可以用于數(shù)學(xué)交流。
我們進(jìn)一步地來深挖,我建議使用 => 的論點(diǎn)是,通過使用更輕量級的語法,可以減少函數(shù)之間的視覺邊界,也讓我們使用偷懶的方式來使用它,這也是函數(shù)式編程者的另一個(gè)愛好。
我認(rèn)為大多數(shù)的函數(shù)式編程者都會(huì)對此睜只眼閉只眼。他們喜歡匿名函數(shù),喜歡簡潔語法。但是像我之前說過的那樣:這都由你決定。
注意: 雖然我不喜歡在我的應(yīng)用程序中使用 =>,但我們將在本書的其余部分多次使用它,特別是當(dāng)我們介紹典型的函數(shù)式編程實(shí)戰(zhàn)時(shí),它能簡化、優(yōu)化代碼片段中的空間。不過,增強(qiáng)或減弱代碼的可讀性也取決你自己做的決定。
來說說 This ?如果您不熟悉 JavaScript 中的 this 綁定規(guī)則,我建議去看我寫的《You Don"t Know JS: this & Object Prototypes》。 出于這章的需要,我會(huì)假定你知道在一個(gè)函數(shù)調(diào)用(四種方式之一)中 this 是什么。但是如果你依然對 this 感到迷惑,告訴你個(gè)好消息,接下來我們會(huì)總結(jié)在函數(shù)式編程中你不應(yīng)當(dāng)使用 this。
JavaScript 的 function 有一個(gè) this 關(guān)鍵字,每個(gè)函數(shù)調(diào)用都會(huì)自動(dòng)綁定。this 關(guān)鍵字有許多不同的方式描述,但我更喜歡說它提供了一個(gè)對象上下文來使該函數(shù)運(yùn)行。
this 是函數(shù)的一個(gè)隱式的輸入?yún)?shù)。
思考:
function sum() { return this.x + this.y; } var context = { x: 1, y: 2 }; sum.call( context ); // 3 context.sum = sum; context.sum(); // 3 var s = sum.bind( context ); s(); // 3
當(dāng)然,如果 this 能夠隱式地輸入到一個(gè)函數(shù)當(dāng)中去,同樣的,對象也可以作為顯式參數(shù)傳入:
function sum(ctx) { return ctx.x + ctx.y; } var context = { x: 1, y: 2 }; sum( context );
這樣的代碼更簡單,在函數(shù)式編程中也更容易處理:當(dāng)顯性輸入值時(shí),我們很容易將多個(gè)函數(shù)組合在一起, 或者使用下一章輸入適配技巧。然而當(dāng)我們做同樣的事使用隱性輸入時(shí),根據(jù)不同的場景,有時(shí)候會(huì)難處理,有時(shí)候甚至不可能做到。
還有一些技巧,是基于 this 完成的,例如原型授權(quán)(在《this & Object Prototypes》一書中也詳細(xì)介紹):
var Auth = { authorize() { var credentials = this.username + ":" + this.password; this.send( credentials, resp => { if (resp.error) this.displayError( resp.error ); else this.displaySuccess(); } ); }, send(/* .. */) { // .. } }; var Login = Object.assign( Object.create( Auth ), { doLogin(user,pw) { this.username = user; this.password = pw; this.authorize(); }, displayError(err) { // .. }, displaySuccess() { // .. } } ); Login.doLogin( "fred", "123456" );
注意: Object.assign(..) 是一個(gè) ES6+ 的實(shí)用工具,它用來將屬性從一個(gè)或者多個(gè)源對象淺拷貝到目標(biāo)對象: Object.assign( target, source1, ... )。
這段代碼的作用是:現(xiàn)在我們有兩個(gè)獨(dú)立的對象 Login 和 Auth,其中 Login 執(zhí)行原型授權(quán)給 Auth。通過委托和隱式的 this 共享上下文對象,這兩個(gè)對象在 this.authorize() 函數(shù)調(diào)用期間實(shí)際上是組合的,所以這個(gè) this 上的屬性或方法可以與 Auth.authorize(..) 動(dòng)態(tài)共享 this。
this 因?yàn)楦鞣N原因,不符合函數(shù)式編程的原則。其中一個(gè)明顯的問題是隱式 this 共享。但我們可以更加顯式地,更靠向函數(shù)式編程的方向:
// .. authorize(ctx) { var credentials = ctx.username + ":" + ctx.password; Auth.send( credentials, function onResp(resp){ if (resp.error) ctx.displayError( resp.error ); else ctx.displaySuccess(); } ); } // .. doLogin(user,pw) { Auth.authorize( { username: user, password: pw } ); } // ..
從我的角度來看,問題不在于使用對象來進(jìn)行操作,而是我們試圖使用隱式輸入取代顯式輸入。當(dāng)我戴上名為函數(shù)式編程的帽子時(shí),我應(yīng)該把 this 放回衣架上。
總結(jié)函數(shù)是強(qiáng)大的。
現(xiàn)在,讓我們清楚地理解什么是函數(shù):它不僅僅是一個(gè)語句或者操作的集合,而且需要一個(gè)或多個(gè)輸入(理想情況下只需一個(gè)!)和一個(gè)輸出。
函數(shù)內(nèi)部的函數(shù)可以取到閉包外部變量,并記住它們以備日后使用。這是所有程序設(shè)計(jì)中最重要的概念之一,也是函數(shù)式編程的基礎(chǔ)。
要警惕匿名函數(shù),特別是 => 箭頭函數(shù)。雖然在編程時(shí)用起來很方便,但是會(huì)對增加代碼閱讀的負(fù)擔(dān)。我們學(xué)習(xí)函數(shù)式編程的全部理由是為了書寫更具可讀性的代碼,所以不要趕時(shí)髦去用匿名函數(shù)。
別用 this 敏感的函數(shù)。這不需要理由。
【上一章】翻譯連載 |《JavaScript 輕量級函數(shù)式編程》- 第 1 章:為什么使用函數(shù)式編程?
【下一章】翻譯連載 |《JavaScript 輕量級函數(shù)式編程》- 第3章:管理函數(shù)的輸入
iKcamp原創(chuàng)新書《移動(dòng)Web前端高效開發(fā)實(shí)戰(zhàn)》已在亞馬遜、京東、當(dāng)當(dāng)開售。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/85067.html
摘要:我稱之為輕量級函數(shù)式編程。序眾所周知,我是一個(gè)函數(shù)式編程迷。函數(shù)式編程有很多種定義。本書是你開啟函數(shù)式編程旅途的絕佳起點(diǎn)。事實(shí)上,已經(jīng)有很多從頭到尾正確的方式介紹函數(shù)式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后):阿希、blueken、brucecham、...
摘要:所以我覺得函數(shù)式編程領(lǐng)域更像學(xué)者的領(lǐng)域。函數(shù)式編程的原則是完善的,經(jīng)過了深入的研究和審查,并且可以被驗(yàn)證。函數(shù)式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數(shù)式編程編程者會(huì)認(rèn)為形式主義本身有助于學(xué)習(xí)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液...
摘要:一旦我們滿足了基本條件值為,我們將不再調(diào)用遞歸函數(shù),只是有效地執(zhí)行了。遞歸深諳函數(shù)式編程之精髓,最被廣泛引證的原因是,在調(diào)用棧中,遞歸把大部分顯式狀態(tài)跟蹤換為了隱式狀態(tài)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;...
摘要:把數(shù)據(jù)的流向想象成糖果工廠的一條傳送帶,每一次操作其實(shí)都是冷卻切割包裝糖果中的一步。在該章節(jié)中,我們將會(huì)用糖果工廠的類比來解釋什么是組合。糖果工廠靠這套流程運(yùn)營的很成功,但是和所有的商業(yè)公司一樣,管理者們需要不停的尋找增長點(diǎn)。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌...
摘要:這就是積極的函數(shù)式編程。上一章翻譯連載第章遞歸下輕量級函數(shù)式編程你不知道的姊妹篇原創(chuàng)新書移動(dòng)前端高效開發(fā)實(shí)戰(zhàn)已在亞馬遜京東當(dāng)當(dāng)開售。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;分享,是 CSS 里最閃耀的一瞥;總...
閱讀 2470·2021-11-17 09:33
閱讀 757·2021-11-04 16:13
閱讀 1329·2021-10-14 09:50
閱讀 691·2019-08-30 15:53
閱讀 3657·2019-08-30 14:18
閱讀 3268·2019-08-30 14:14
閱讀 2093·2019-08-30 12:46
閱讀 3178·2019-08-26 14:05