close

「JavaScript學習筆記」系列文章為小弟我閱讀"Eloquent JavaScript"的筆記,網路上或許已有相同類型的文章,但本文主要目的還是個人筆記用途,或是作者未來腦袋年久失修開始漏東漏西的時候當成快速查閱的工具書使用。所以一些寫給程式入門者的說明不會是本文的重點(原本是這樣想的,但是後來發現其實應該也蠻好懂的,因為純粹的翻成中文比較快…),有需要的話請查閱原文吧!與我一樣有一點程式基礎,正要開始學習的朋友歡迎參考,如果內容有任何錯誤之處,敬請留言或來信指正,謝謝!

JavaScript學習筆記2 - Functions

原始文章連結:http://eloquentjavascript.net/chapter3.html

 

1. Background

  在程式中經常需要在不同地方做一樣的事,但每一次重覆撰寫需要的陳述是乏味且容易出錯的。函數就是因此需求而生:他們就像是一些罐裝的程式,在程式需要的時候就可以拿出來使用。例如在螢幕上印出字串需要一些陳述才能完成,因此我們就有了「print」函數,我們可以簡單的使用「print("Aleph")」來完成。

  將程式僅僅視為一段封裝的程式其實並不公平。當需要的時候,他們可以扮演單純的函數、演算法、(註:接下來的部分不知道怎麼翻,先列出來)「indirections」、「abstractions」、「decisions」、「modules」、「continuations」、「data structures」以及其他角色。在撰寫重要的程式時能夠有效的使用函數是一個必要的技能。這個章節提供關於這個主題的介紹,在第六章中將有更深入的討論。

 

2. Pure Functions

  純函數一開始是指那些我們在數學中使用的那些稱為函數的東西,例如正弦(Cosine)或是取絕對值,就是一個有一個參數的純函數。而加法則是有兩個參數的純函數。純函數的特性是:輸入相同的值總是會得到相同的結果,並且沒有任何的side effect。他們僅僅接收一些參數,基於參數回傳一個值,除此之外什麼都不做。

  在javascript中,加法是一個運算子,但他也可以被包裝成函數,像這樣(乍看之下沒什麼意義,但在某些情況下這是相當有用的):

add」是函數的名稱,a與b是兩個參數的名稱,回傳a+b是函數的主體。

  我們使用關鍵字「function」來建立一個新的函式,其後會接著一個變數名稱,該函式會儲存在這個變數內。在變數之後接著是一個參數列表,最後則是函數主體。不像if或while迴圈後的陳述可以是單行陳述,宣告函數時後方的大括號「{}」是不可省略的

  後面接著一個表達式的關鍵字「return」,是用來決定這個函數的回傳值的。當函數中遇到return時,程式會立即跳出目前的函數,並且回傳一個值給呼叫這個函數的程式。當return陳述中不包含表達式時,這會導致函數回傳undefined。函數的主體中可以包含多個陳述,以下是一個計算power的函數:

  在函數中創造一個變數(result)並更新他的值是side effect。呃,我剛剛是不是才說純函數沒有side effect?由於創造在函數中的變數只會存在於這個函數中。否則程式設計師就必須要面對必須給程式中每一個變數不同名稱的問題。因為result只存在於power之中,這個變數只會存活到函數回傳一個值為止,因此從這個角度來看,我們可以說它沒有side effect。

練習:建立一個absolute函數,回傳參數的絕對值。

  純函數有兩個非常好的特性,他們很容易理解,也很容易使用。如果你不確定函式的結果,由於它們不需要依賴任何「context」因此非常簡單,可以在console中直接呼叫測試。撰寫自動測試的程式測試他們也很容易。非純函數,基於各種不同的因素,可能會回傳不同的值,並且也會有可能很難以測試與理解的side effect。

由於純函數是自給自足的,他們在大多數狀況中都較非純函數有用且有意義。舉個例子,「show」函數的效用必須依靠螢幕上一塊用於輸出的特殊區域,如果沒有這個區域,那麼這個函數就是沒有用的;我們可以想像一個相關的函數,叫他「format」好了,他需要一個參數並且會回傳一個表達這個值的字串。這個函數比show在更多情況中有用。

  當然,format無法解決show能解決的問題,並且沒任何純函數可以解決這個問題,因為它需要side effect。在許多情況裡,非純函數就是你需要的;而在其他狀況裡,或許可以使用純函數解決問題,但是非純函數會相對更方便或有效率。因此,當某些東西可以以純函式的形式表達,就這麼寫吧!但是千萬不要覺得撰寫非純函數是不乾淨的。

  有side effect的函數並不一定需要包含「return」陳述。如果沒有「return」,則這函數回傳「undefined」,例如

 

3. Lexical Scoping

  函數的參數會被視為內部變數,系統會參照函數被呼叫時一起傳入的參數。並且就像其他在函數中建立的變數一樣,他們不存在於外部。與最上層的環境相比,他們是被函數呼叫所創造的小型的、區域的環境。當我們在函數中尋找變數時,系統會先檢查所在環境是否存在該變數,只有當變數不存在該環境時才會檢查上層的環境。這使得函數中的區域變數可以"遮罩"(shadow)上層具有相同名稱的變數。(範例)

在區域環境中的變數只有在這個函數中的程式看得到。如果這個函數A中再呼叫另一個函數B,則函數B不會看到函數A中的變數。(範例)

  然而,這是一個微妙但非常重要的現象,當一個函數是在另一個函數中被定義,則他的區域環境將會基於建立他的函式的區域環境,而並非最上層的環境。(範例)

  也就是說,在函數中哪一個變數是可見的是由該函數定義的區域來決定。所有定義在此函數的上層的變數對這個函數而言都是可見的,這代表著此函數主體包含的變數以及所有在這個函數定義外每一層(一直到最上層)的變數對此函數而言都是可見的。這個定義參數可見度的方法稱為lexical scoping

 

3.1 Scope of Blocks

  Javascript中的block不會創造新的scope,看看以下例子

但是block中的「something」事實上與外部的參考了同一個變數。雖然我們可以使用block,但這完全是沒有意義的。多數人認為這是Javascript設計者的設計錯誤,並且ECMAScript Harmoney將會加入一些定義block內變數的方法(關鍵字「let」)。

 

3.2 Closure

  「parentFunction()」回傳一個內部函數,接著在程式末端呼叫它。顯然在呼叫「child()」時parentFunction早已執行完畢,但擁有「local」值的區域環境變數此時仍然存在,且「childFunction」仍然在使用他。這個現象稱為「closure(閉包)」。簡言之,closure就是當定義函數時使用了非內部定義的變數,這些被使用的「閒置變數」就會被closure關閉而繼續存活(參考openhome的JavaScript Essence: 閉包)

 

3.3 Synthesize

  Lexical scoping的特性可以讓我們組合(Synthesize)函數,藉由使用一些函數內的變數,在其內部定義的函數可以被客製成不同的函數。假設我們需要一些類似但有些微不同的函數,一個對參數加二,一個對參數加五…(範例):

  在最上層定義的函數只會在最上層的環境中執行,但在另一個函數中定義的函數,則可以保留對定義該函數當時的環境的存取權限(Closure的概念)。上例中的「add」函數是呼叫「makeAddFunction」時建立,並且保留了一個具有特定值「amount」參數的環境。add封裝了該環境與回傳值「number+amount」,然後被外部函數回傳。

  當這些回傳的函數(addTwo或addFive)被呼叫時,建立了一個有傳入之number值的新環境,做為其保留之環境的子環境(amount有當時建立的值)。接著這一兩個值被相加後回傳。

 

(未完待續)

arrow
arrow
    全站熱搜

    Hubert 發表在 痞客邦 留言(1) 人氣()