Eloquent JavaScript #05# higher-order functions
索引:
- Notes
- 高階函數
- forEach
- filter
- map
- reduce
- some
- findIndex
- 重寫課本示例代碼
- Excercises
- Flattening
- Your own loop
- Everything
- Dominant writing direction
Notes
1、高階函數的概念:Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions
2、自己yy的高階函數優勢:
- 更少的代碼。有效減少鍵盤磨損
- 傳統編程是復用對象、方法,高階函數則是復用一種更加抽象的模式
- 對於理解、熟練運用高階函數的人而言,采用高階函數比傳統代碼有更好的可讀性
高階函數是函數式編程的一種特性,當然js並不是函數編程語言,對於函數式編程可以參考—— 什麽是函數式編程思維? - 用心閣的回答
3、代碼收集:
① 高階函數示例1
function greaterThan(n) { return m => m > n; } let greaterThan10 = greaterThan(10); console.log(greaterThan10(11)); // → true
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
② 高階函數示例2
function noisy(f) { return (...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
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
③ 高階函數示例3
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
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
④ 高階函數forEach,功能類似於for/of循環。
["A", "B"].forEach(l => console.log(l)); // → A // → B
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
⑤ 用於過濾數據的高階函數
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", …}, …]
標準數組方法:
console.log(SCRIPTS.filter(s => s.direction == "ttb")); // → [{name: "Mongolian", …}, …]
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
⑥ filter會生成過濾掉一些數據的新數組,map則從原有的數組構造一個長度相同、但數據經過轉化的新數組。
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", …]
map同樣是一個標準的數組方法。
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
⑦ 高階函數reduce(或者說fold):通過對數組中的元素逐個進行某種累加運算產出一個結果。例如說求和sum(array),翻譯成高階函數就是reduce(array, (a, b) => a + b, 0)
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
標準數組方法:
console.log([1, 2, 3, 4].reduce((a, b) => a + b)); // → 10
reduce用於比較最後得出一個結果:
function characterCount(script) { return script.ranges.reduce((count, [from, to]) => { return count + (to - from); }, 0); } console.log(SCRIPTS.reduce((a, b) => { return characterCount(a) < characterCount(b) ? b : a; })); // → {name: "Han", …}
更簡單的例子:
[1, 23, 21, 12, 3, 5].reduce((a, b) => a > b ? a : b); // → 23
[1, 23, 21, 12, 3, 5].reduce((a, b) => { console.log(`compare ${a} with ${b}`); return a > b ? a : b; }); // → compare 1 with 23 // → compare 23 with 21 // → compare 23 with 12 // → compare 23 with 3 // → compare 23 with 5 // → 23
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
⑧ 高階函數some
[2, 3, 2, 1, 2, 5, 7].some(a => a > 7); // → false [2, 3, 2, 1, 2, 5, 7].some(a => a >= 7); // → true [2, 3, 2, 1, 2, 5, 7].some(a => { console.log(`start testing ${a}`); return a == 1; }); // → start testing 2 // → start testing 3 // → start testing 2 // → start testing 1 // → true
測試數組中的元素,只要其中任何一個滿足就返回true
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
⑨ 高階函數findIndex
[2, 3, 2, 1, 2, 5, 7].findIndex(a => a > 7); // → -1 [2, 3, 2, 1, 2, 5, 7].findIndex(a => a >= 7); // → 6 [2, 3, 2, 1, 2, 5, 7].findIndex(a => { console.log(`start testing ${a}`); return a == 1; }); // → start testing 2 // → start testing 3 // → start testing 2 // → start testing 1 // → 3
4、重寫課本示例代碼(統計文本中字符的腳本歸屬分布)
數據:http://eloquentjavascript.net/code/scripts.js
數據格式:
{ name: "Adlam", ranges: [[125184, 125259], [125264, 125274], [125278, 125280]], direction: "rtl", year: 1987, living: true, link: "https://en.wikipedia.org/wiki/Fula_alphabets#Adlam_alphabet" }
主代碼:
/** * 字符是由各種字符腳本處理的 * 通過每個字符的編碼可以找到該字符的腳本歸屬 * 例如編碼為1~100的字符由a腳本處理 * 編碼為300~400、500~700的由b腳本處理 */ /** * 課本上的函數一般直接用“代表返回值的名詞”做函數名 * 而java中這種情況一般用“get+代表返回值的名詞”做函數名 * 如果覺得某個函數意義模糊,可以自行在函數前面腦補動詞 */ const textToCodes = (text) => { let result = []; for (let char of text) { result.push(char.codePointAt(0)); } return result; }; const codeToScriptName = (code) => { let result = null; for (let script of SCRIPTS) { if (script.ranges.some(([from, to]) => { return code >= from && code < to; })) { result = script.name; break; } } return result; }; const elementCounts = (arr) => { let result = []; for (let element of arr) { let index = result.findIndex(a => a.label == element); if (index == -1) { result.push({label: element, count: 1}); } else { result[index].count++; } } return result; }; const scriptsRate = (text) => { let scriptNames = textToCodes(text).map(code => codeToScriptName(code)); let total = scriptNames.length; let scriptCounts = elementCounts(scriptNames); return scriptCounts.map(({label, count}) => { return { label: label == null ? "none" : label, rate: (count / total).toFixed(2) }; }); }; const main = () => { let text = ‘英國的狗說"woof", 俄羅斯的狗說"тяв"‘; console.log(scriptsRate(text).map(item => { return `${item.rate * 100}% ${item.label}`; }).join(", ")); }; main(); // → 46% Han, 25% none, 17% Latin, 13% Cyrillic
Exercises
① Flattening
let arrays = [[1, 2, 3], [4, 5], [6]]; arrays.reduce((sum, x) => { console.log(`concat ${sum} and ${x}`); return sum.concat(x); }); // → concat 1,2,3 and 4,5 // → concat 1,2,3,4,5 and 6 // → [1, 2, 3, 4, 5, 6]
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
② Your own loop
const loop = (initVal, test, update, body) => { let count = 0; for (let i = initVal; test(i); i = update(i)) { body(i); } }; loop(3, n => n > 0, n => n - 1, console.log); // → 3 // → 2 // → 1
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
③ Everything
版本1:
function every(array, test) { for (let x of array) { if (!test(x)) return false; } return true; } console.log(every([1, 3, 5], n => n < 10)); // → true console.log(every([2, 4, 16], n => n < 10)); // → false console.log(every([], n => n < 10)); // → true
版本2:
function every(array, test) { return !array.some(x => !test(x)); } console.log(every([1, 3, 5], n => n < 10)); // → true console.log(every([2, 4, 16], n => n < 10)); // → false console.log(every([], n => n < 10)); // → true
--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --
④ Dominant writing direction
PS. 要用到示例代碼中的數據。
const textToCodes = (text) => { let result = []; for (let char of text) { result.push(char.codePointAt(0)); } return result; }; const codeToDirection = (code) => { let result = null; for (let script of SCRIPTS) { if (script.ranges.some(([from, to]) => { return code >= from && code < to; })) { result = script.direction; break; } } return result; }; const elementCounts = (arr) => { let result = []; for (let element of arr) { let index = result.findIndex(a => a.label == element); if (index == -1) { result.push({label: element, count: 1}); } else { result[index].count++; } } return result; }; function dominantDirection(text) { let derections = textToCodes(text).map(code => codeToDirection(code)); let derectionCounts = elementCounts(derections).filter(x => x.label != null); return derectionCounts.reduce((a, b) => { return a.count > b.count ? a : b; }).label; } console.log(dominantDirection("Hello!")); // → ltr console.log(dominantDirection("Hey, ???? ?????")); // → rtl
Eloquent JavaScript #05# higher-order functions