# Eloquent JavaScript 3rd edition (2018) 第五章~Higher-Order Functions --- tags: Javascript relate --- ###### tags: `Javascript` > Tzu-li and Tzu-ssu were boasting about the size of their latest programs. ‘Two-hundred thousand lines,’ said Tzu-li, ‘not counting comments!’ Tzu-ssu responded, ‘Pssh, mine is almost a million lines already.’ Master Yuan-Ma said, ‘My best program has five hundred lines.’ Hearing this, Tzu-li and Tzu-ssu were enlightened. > > Master Yuan-Ma, The Book of Programming > There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. > > C.A.R. Hoare, 1980 ACM Turing Award Lecture 一開始的前言的部分比較了這兩個部分很明顯的作者傾向我們使用後者,雖然the functions `sum` and `range`裡面同樣包含了loop,但是它們只表達一個簡單的想法,還是比程式一中整個邏輯簡單許多。 ```javascript= // 程式一 let total = 0, count = 1; while (count <= 10) { total += count; count += 1; } console.log(total); //程式二 console.log(sum(range(1, 10))); ``` ## Abstraction(翻作擷取比較好) > 擷取的定義: > > 在電腦科學中,是將資料與程式,以它的語意來呈現出它的外觀,但是隱藏起它的實作細節。 這邊用作豌豆湯的食譜撰寫來舉例: 第一種是全部列出來一步一步做: > Put 1 cup of dried peas per person into a container. Add water until the peas are well covered. Leave the peas in water for at least 12 hours. Take the peas out of the water and put them in a cooking pan. Add 4 cups of water per person. Cover the pan and keep the peas simmering for two hours. Take half an onion per person. Cut it into pieces with a knife. Add it to the peas. Take a stalk of celery per person. Cut it into pieces with a knife. Add it to the peas. Take a carrot per person. Cut it into pieces. With a knife! Add it to the peas. Cook for 10 more minutes. 第二種就是抽象化: > Per person: 1 cup dried split peas, half a chopped onion, a stalk of celery, and a carrot. > > Soak peas for 12 hours. Simmer for 2 hours in 4 cups of water (per person). Chop and add vegetables. Cook for 10 more minutes. 很明顯的第二種就是比較簡潔好讀,但是其中比較多專有名詞並且有些地方寫的相對簡潔不明白,用這樣的方式來減少資訊含量就是Abstraction ### Abstracting repetition function就是個很好的abstractoin的範例: 把一件事情做N次的方法丟進function裡面很常見長這樣 ```javascript= function repeatLog(n) { for (let i = 0; i < n; i++) { console.log(i); } } repeatLog(10); ``` 這邊讓repeat變成一個function,裡面的action就是放入functions的地方例子在下方Higher-order functions ```javascript= function repeat(n, action) {// 這個的action就可以放入其他functions來動作 for (let i = 0; i < n; i++) { action(i); // 或是functions做為return來動作 } } repeat(3, console.log); // → 0 // → 1 // → 2 ``` 這會是一個比較好擷取模式,擷取了上面的funciotn來做重複印出的這個行為 ```javascript= let labels = []; repeat(5, i => { labels.push(`Unit ${i + 1}`); }); console.log(labels); // → ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"] ``` ## Higher-order functions functions在不同的functions上面工作不管是作為arguments或是returning就是Higher-order functions,這個詞彙來自數學一個對待functions跟其他value更為嚴謹的環境。 下方的程式碼就是很好的例子functions操作在return的位置: ```javascript= function noisy(f) { return (...args) => {// (...args)這邊表示不確定數量的參數並且會被視為陣列 console.log("calling with", args); let result = f(...args); console.log("called with", args, ", returned", result); return result; }; } noisy(Math.min)(3, 2, 1); // → calling with [3, 2, 1] // → called with [3, 2, 1] , returned 1 ``` 甚至可以操作出新的control flow ```javascript= function unless(test, then) { if (!test) then(); } repeat(3, n => { unless(n % 2 == 1, () => { console.log(n, "is even"); }); }); // → 0 is even // → 2 is even ``` 一個內建的array method forEach就是個 higher-order function的最好例子: ```javascript= ["A", "B"].forEach(l => console.log(l)); // → A // → B ``` ## Script data set 這個地方只是要表明資料處理是higher-order function發光發熱的地方像是萬國碼或是語言系統 ## Filtering arrays 下方test的這個參數是個function的值,並且由此決定哪個elements會被包含`script => script.living`條件是現存的script會保留在陣列中,其他的會被return `passed掉只留下通過test`的elements在新的陣列裡面 ```javascript= function filter(array, test) { let passed = []; for (let element of array) { if (test(element)) { passed.push(element); } } return passed; } console.log(filter(SCRIPTS, script => script.living)); // → [{name: "Adlam", …}, …] ``` 上面那一大串是為了解釋給我們看才寫出來不然一般會是如下方一樣簡潔: ```javascript= console.log(SCRIPTS.filter(s => s.direction == "ttb")); // → [{name: "Mongolian", …}, …] ``` ## Transforming with map 有些時候再讀這些文檔,它們用名字去分類會是個比較好檢視的方法。 這時候就可以用到map這個method,並且用它來transform array變成一個新的array從使用過map之後的資料轉變成的。 ```javascript= function map(array, transform) { let mapped = []; for (let element of array) { mapped.push(transform(element)); } return mapped; } let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl"); console.log(map(rtlScripts, s => s.name)); // → ["Adlam", "Arabic", "Imperial Aramaic", …] ``` 跟forEach、filter、map一樣都是array的methods ## Summarizing with reduce 另一個常見的運算是從array中計算單一的值使用reduce 這邊解構一下reduce: 簡單來說就是一個把array內容加總的概念 ```javascript= function reduce(array, combine, start) { let current = start; for (let element of array) { current = combine(current, element); } return current; } console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0)); // → 10 ``` ## Composability 下面兩段程式碼代表了 A擁有較好的可讀性以及是個比較好的Higher-order functions B如果放在大量運算的資料上面會跑得比較快,因為他單純在處理數字 程式A ```javascript= function average(array) { return array.reduce((a, b) => a + b) / array.length; } console.log(Math.round(average( SCRIPTS.filter(s => s.living).map(s => s.year)))); // → 1165 console.log(Math.round(average( SCRIPTS.filter(s => !s.living).map(s => s.year)))); // → 204 ``` 程式B ```javascript= let total = 0, count = 0; for (let script of SCRIPTS) { if (script.living) { total += script.year; count += 1; } } console.log(Math.round(total / count)); // → 1165 ``` ## Strings and character codes ### character codes取得所在的文字array 使用`some` method,它是另一個Higher-order functions,他會測試出來受檢測的value哪個是true在array之中。 ```javascript= function characterScript(code) { for (let script of SCRIPTS) { if (script.ranges.some(([from, to]) => { return code >= from && code < to; })) { return script; } } return null; } console.log(characterScript(121)); // → {name: "Latin", …} ``` ### 從string取得character codes * charCodeAt 只有取得code unit而不是整個chatacter codes * codePointAt 後來才加入不過它可以取得整個chatacter codes ## Recognizing text 看無 ## Summary `forEach` to loop over the elements in an array. `filter` method returns a new array containing only the elements that pass the predicate function. Transforming an array by putting each element through a function is done with `map`. `some` method tests whether any element matches a given predicate function. `findIndex` finds the position of the first element that matches a predicate. `reduce` to combine all the elements in an array into a single value. ## Exercises 先跳過看無