# Eloquent JavaScript 3rd edition (2018) 第三章~Functions --- tags: Javascript relate --- ###### tags: `Javascript` > People think that computer science is the art of geniuses but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones. > > Donald Knuth ## Defining a function > A function definition is a regular binding where the value of the binding is a function. function的定義是 一個正常的binding但是他的value被指派為function 下方範例就很明顯: 指定binding "square" 為funciton(x) ```javascript= const square = function(x) { return x * x; }; console.log(square(12)); // → 144 ``` function的body一定要被包裹在{}之間就算只有單行的statement function可以有很多參數(parameters)也可以一個都沒有 沒有參數的例子: ```javascript= const makeNoise = function() { console.log("Pling!"); }; makeNoise(); // → Pling! ``` 兩個參數的例子: ```javascript= const power = function(base, exponent) { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; }; console.log(power(2, 10)); // → 1024 ``` return 這個statement確定了functions回復的結果 如果return後面沒有接上expression的話會回復undefined ## Bindings and scopes(範圍) 每個binding都有範圍,也就是binding可以造訪並且有作用的範圍。 有一種binding是全域的,整段程式都可以使用稱作**global** 但是在function裡面的binding只能給function使用稱作 **local bindings** function裡面的binding比較被隔離的感覺並不知道外面的global的環境 一般來說binding只會作用在他所在的block裡面,所以當你把binding設立在loop裡面時,前跟後的程式碼是無法使用它的 下方這個例子可以看出就算global的 n=10寫在上方 halve(100)還是只看它裡面的n的值去做處理: ```javascript= const halve = function(n) { return n / 2; }; let n = 10; console.log(halve(100)); // → 50 console.log(n); // → 10 ``` ## Nested scope 巢狀的範圍簡單來說就是function裡面還有一個function一層一層包裹住呈現巢狀 ```javascript= const hummus = function(factor) { const ingredient = function(amount, unit, name) { let ingredientAmount = amount * factor; if (ingredientAmount > 1) { unit += "s"; } console.log(`${ingredientAmount} ${unit} ${name}`); }; ingredient(1, "can", "chickpeas"); ingredient(0.25, "cup", "tahini"); ingredient(0.25, "cup", "lemon juice"); ingredient(1, "clove", "garlic"); ingredient(2, "tablespoon", "olive oil"); ingredient(0.5, "teaspoon", "cumin"); }; ``` ## Functions as values 一個binding宣告了一個function後如果他不是constant,他可以再設一個新的value: ```javascript= let launchMissiles = function() { missileSystem.launch("now"); }; if (safeMode) { launchMissiles = function() {/* do nothing */}; } ``` ## Declaration notation(宣告符號) 這是一個比較簡短的方式去創造一個function binding ```javascript= function square(x) { return x * x; } ``` 下方的程式碼解釋了這種簡短的寫法(function declaration),不需要遵循從頭到尾這樣的規則,並且可以被所有的程式碼使用,很多時候這樣的自由是有相當意義的。 ```javascript= console.log("The future says:", future()); function future() { return "You'll never have flying cars"; } ``` ## Arrow functions 一邊箭頭用在下面這個地方: 一般來說箭頭使用在參數(parameter)之後,必且後方接著function的body,表達這個參數產生這個結果(body) ```javascript= const power = (base, exponent) => { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; }; ``` 當只有一個參數得時候可以忽略括號(),body的部分如果只有一個表達式(expression)得時候可以這樣簡寫: ```javascript= const square1 = (x) => { return x * x; }; const square2 = x => x * x; ``` 當arrow function沒有參數的時候可以直接寫一(): ```javascript= const horn = () => { console.log("Toot"); }; ``` arrow function就是個讓funcition寫起來更簡潔 ## The call stack 計算機必須記得那些宣告的內容,像是下方例子中的console.log()以及greet都是那些地方 計算機記得要印出東西的內容存放的地方就叫 call stack,當function return的時候計算機會移除最上方的內容並且使用其內容來繼續執行 存放call stack需要消耗電腦容量,當stack變得太多,電腦會顯示“out of stack space” or “too much recursion”. ```javascript= function greet(who) { console.log("Hello " + who); } greet("Harry"); console.log("Bye"); ``` 下方是個無解的蛋先生還是雞先生的問題,電腦一定會滿載 跑出下面這條因為他是個無限的迴圈,同時搞砸這個stack `RangeError: Maximum call stack size exceeded` ``` javascript= function chicken() { return egg(); } function egg() { return chicken(); } console.log(chicken() + " came first."); // → ?? ``` ## Optional Arguments 從下方程式碼可以看出js對於argument的內容放得很寬,會自動抓取他認定的數值去執行。 甚至有可能打錯了,也因為它會自動抓取的關西而沒有發現錯誤。 ```javascript= function square(x) { return x * x; } console.log(square(4, true, "hedgehog")); // → 16 ``` 好的面相有: 讓function可以被不同的argument called ``` javascript= function minus(a, b) { if (b === undefined) return -a; else return a - b; } console.log(minus(10)); // → -10 console.log(minus(10, 5)); // → 5 ``` 下方演示了 (=) 這個operator在參數中出現的功能,當那個有(=)的參數沒有被給予value的時候,(=)後方的值會直接取代進去: ``` javascript= function power(base, exponent = 2) { let result = 1; for (let count = 0; count < exponent; count++) { result *= base; } return result; } console.log(power(4)); // → 16 console.log(power(2, 6)); // → 64 ``` ## Closure(閉包) 閉包是指變數的生命週期只存在於該函式內,一旦離開了函式,該變數就會被回收而不可再利用,且必須在函式內事先宣告。 下面這個情況說明了function裡面local binding`let local = n`就是個閉包請他的內容不會離開function,並且這樣的宣告可以重新創造在每一次的宣告中,然後不一樣的宣告不會踐踏之前的local bindings > This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls can’t trample on one another’s local bindings. ``` javascript= function wrapValue(n) { let local = n; return () => local; } let wrap1 = wrapValue(1); let wrap2 = wrapValue(2); console.log(wrap1()); // → 1 console.log(wrap2()); // → 2 ``` 下方這段程式碼表示了當宣告產生的時候,function的body應該去看環境創造的地方而不是他宣告的地方: ```javascript= function multiplier(factor) { return number => number * factor; // 創造的環境 } let twice = multiplier(2); // 因此他必須帶入factor為2 console.log(twice(5)); // → 10 ``` ## Recursion 當一個funciton宣告自己的時候就是recursion > A function that calls itself is called recursive. 用這個方法寫求冪exponentiation就是個例子: ```javascript= function power(base, exponent) { if (exponent == 0) { return 1; } else { return base * power(base, exponent - 1); } } console.log(power(2, 3)); // → ``` 下面的程式碼是個求解的過程=>如何呈現13: ```javascript= function findSolution(target) { function find(current, history) { if (current == target) { return history; } else if (current > target) { return null; } else { return find(current + 5, `(${history} + 5)`) || find(current * 3, `(${history} * 3)`); } } return find(1, "1"); } console.log(findSolution(13)); // → ((1 * 3) + 5) + 5 ``` 這邊是他如何求出13的過程: ``` javascript= find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "(((1 + 5) + 5) + 5)") too big find(33, "(((1 + 5) + 5) * 3)") too big find(18, "((1 + 5) * 3)") too big find(3, "(1 * 3)") find(8, "((1 * 3) + 5)") find(13, "(((1 * 3) + 5) + 5)") found! ``` ## Growing functions 以下兩種方法用來幫助農家定位家中的牲畜數字,農家希望家中的動物數字都要是三位數,所以當數字是3的時候它們希望可以呈現003,故有兩種呈現在下方: 第一種寫法比較不推薦,`printZeroPaddedWithLabel` 名稱太奇怪難以閱讀理解,結合太多想法太貪心了 ```javascript= function printZeroPaddedWithLabel(number, label) { let numberString = String(number); while (numberString.length < 3) { numberString = "0" + numberString; } console.log(`${numberString} ${label}`); } function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Cows"); printZeroPaddedWithLabel(chickens, "Chickens"); printZeroPaddedWithLabel(pigs, "Pigs"); } printFarmInventory(7, 11, 3); ``` 第二種寫法就很簡潔的只有zeropad來表示加上0這個想法,比較好閱讀之外更有可能把這個function做別的用途泛用性比較廣。 ```javascript= function zeroPad(number, width) { let string = String(number); while (string.length < width) { string = "0" + string; } return string; } function printFarmInventory(cows, chickens, pigs) { console.log(`${zeroPad(cows, 3)} Cows`); console.log(`${zeroPad(chickens, 3)} Chickens`); console.log(`${zeroPad(pigs, 3)} Pigs`); } printFarmInventory(7, 16, 3); ``` 這邊它推薦一種原則,不要讓function名稱太靈活,讓他的概念單一,除了好讀之外也更增加泛用性。 ## Functions and side effects ### 純粹函式(pure function): * 只要每次給定相同的輸入值(例如1與2),就一定會得到相同的輸出值(例如3) * 不會改變原始輸入參數,或是外部的環境,所以沒有副作用 * 不依頼其他外部的狀態(變數之類的) ![](https://i.imgur.com/OGCDA9H.png) ### 不純粹的函式(impure function):它需要依賴外部的狀態值(變數值): 會產生副作用 ![](https://i.imgur.com/exV0GDQ.png) ## Summary 三種寫function的方式: ```javascript= // Define f to hold a function value const f = function(a) { console.log(a + 2); }; // Declare g to be a function function g(a, b) { return a * b * 3.5; } // A less verbose function value let h = a => a % 3; ``` ## Exercises ### Minimum 比出兩個數字哪個比較小使用 寫出一個min的function: ```javascript= function min(a, b) { if (a < b) return a; else return b; } console.log(min(0, 10)); // → 0 console.log(min(0, -10)); // → -10 ``` ### Recursion 看無第三點之後再來! 判斷奇數偶數 藉由三個條件: * 0是偶數 * 1是奇數 * 任意數字-2依舊會是一樣的狀態(奇數還是奇數偶數還是偶數) ```javascript= function isEven(n) { if (n == 0) return true; else if (n == 1) return false; else if (n < 0) return isEven(-n); else return isEven(n - 2); } console.log(isEven(50)); // → true console.log(isEven(75)); // → false console.log(isEven(-1)); // → false ``` ### Bean counting 找出單字間有幾個 字母 比方說B 有幾個 問題一 讓參數只有一個 問題二 參數變兩個 第一個參數是要找的單字 第二個是要找的字母 看得懂但是寫不出來我哭 ``` javascript= function countChar(string, ch) { let counted = 0; for (let i = 0; i < string.length; i++) { if (string[i] == ch) { counted += 1; } } return counted; } function countBs(string) { return countChar(string, "B"); } console.log(countBs("BBC")); // → 2 console.log(countChar("kakkerlak", "k")); // → 4 ```