Eloquent JavaScript 笔记 五: High-Order Functions
来源:互联网 发布:流行网络用语文言文 编辑:程序博客网 时间:2024/06/05 08:14
High-order function 中文译作“高阶函数”。不论是英文名还是中文名,都不直观,单纯通过名字无法想象它是个什么东西。其实它就是 “把function作为参数,或者返回值为function的函数”。
函数,本质上来讲,是把一组行为抽象成一个概念。这种抽象有两个好处:
一、便于理解代码的意图(前提是有一个合适的函数名)。
二、便于复用。
既然函数是一个概念,或者说,是一个抽象、一个变量,那么,把它作为其他函数的参数也就没什么奇怪的了。
高阶函数、函数式编程、lamda表达式等,其实都是一码事。而且,已经成了开发语言的标配。swift、kotlin这些新秀就不必说了,比较“现代”的开发语言对此都有完整的支持。
当年用C语言时,偶尔也会定义函数类型的变量,一直记不清楚需要写几个括号。
1. Abstraction
例子,对1-10 求和。
方法一:
var total = 0, count = 1;while (count <= 10) { total += count; count += 1;}console.log(total);
方法二:
console.log(sum(range(1, 10)));
方法二的写法更清晰,不过,实际上用到的代码更多,因为sum和range这两个函数的代码要超过“方法一”的代码行数。
虽然方法二的代码会多一些,但我们更倾向于用这种方法。 因为,它抽象出来两个概念:range 和 sum,让代码更清晰,更不容易出Bug。
2. Abstracting Array Traversal
例子,逐个打印数组中的元素:
var array = [1, 2, 3];for (var i = 0; i < array.length; i++) { var current = array[i]; console.log(current);}
我们可以抽象出来一个概念:logEach
function logEach(array) { for (var i=0; i<array.length; i++) { console.log(array[i]); }}
这样,以后再遍历打印别的数组时,就可以用这个函数了,不用每次写那么些代码。
但,这个logEach的通用性比较差,只能log,通过增加一个参数action,我们可以让它更通用一些。
function forEach(array, action) { for (var i = 0; i < array.length; i++) { action(array[i]); }}forEach(["Wampeter", "Foma", "Granfalloon"], console.log);
这个例子还是在逐个打印数组元素。
看一个不一样的:求和。
var numbers = [1, 2, 3, 4, 5], sum = 0;forEach(numbers, function(number) { sum += number;});console.log(sum);
等等,跨度有点大,function(number) 这个number是哪来的?
forEach 的第二个参数是个action,也就是这个function:
function(number) { sum += number;}
在forEach内部,给action函数传递的参数是 array[i] ,所以,这个number 就是数组的一个元素。
3. Higher-Order Functions
把function作为参数,或者返回值为function的函数,叫做higher-order function。
例1:
function greaterThan(n) { return function(m) { return m > n; };}var greaterThan10 = greaterThan(10);console.log(greaterThan10(11));
例2:
function noisy(f) { return function(arg) { console.log("calling with", arg); var val = f(arg); console.log("called with", arg, "- got", val); return val; };}noisy(Boolean)(0);// → calling with 0// → called with 0 - got false
例3:
function unless(test, then) { if (!test) then();}function repeat(times, body) { for (var i = 0; i < times; i++) body(i);}repeat(3, function(n) { unless(n % 2, function() { console.log(n, "is even"); });});// → 0 is even// → 2 is even
4. JSON
JavaScript Object Notation
JSON.stringify( )
JSON.parse( )
本章源码中有个ancestry.js, 包含这个文件之后,可以用 JSON.parse(ANCESTRY_FILE) 得到一个家族树中所有人员的数组。
var ancestry = JSON.parse(ANCESTRY_FILE);
后面代码中遇到的ancestry 变量都是这个。
5. Filtering an Array
用test函数来过滤一个数组,所有通过test的元素会构建一个新的数组。
function filter(array, test) { var passed = []; for (var i = 0; i < array.length; i++) { if (test(array[i])) passed.push(array[i]); } return passed;}ancestry.filter(function(person){ return person.father == "Carel Haverbeke";});
6. Transforming with Map
用transform函数,改变数组元素的类型。
function map(array, transform) { var mapped = []; for (var i = 0; i < array.length; i++) { mapped.push(transform(array[i])); } return mapped;}ancestry.map(function(person) { return person.name; });
7. Summarizing with Reduce
function reduce(array, combine, start) { var current = start; for (var i = 0; i < array.length; i++) { current = combine(current, array[i]); } return current;}[1,2,3,4].reduce(function(a,b) { return a+b;});ancestry.reduce(function(min, cur) { if(cur.born < min.born) return cur; else return min;});
JavaScript 标准库中的reduce方法,提供了一个便利的功能,那就是,可以省略 start 参数。 默认把数组的第一个参数作为start。
8. Composability
function average(array) { function plus(a, b) { return a + b; } return array.reduce(plus) / array.length;}function age(p) { return p.died - p.born; }function male(p) { return p.sex == "m"; }function female(p) { return p.sex == "f"; }var maleAverageAge = average(ancestry.filter(male).map(age));var femaleAverageAge = average(ancestry.filter(female).map(age));
很神奇啊,写了多年的代码,已经习惯了从问题域出发,按照人类思维一行一行的顺序写代码。 要做到这种抽象层次,还要做很多练习才行。
9. The Cost
相比于直接使用循环,上面的方法比较低效,因为,它们增加了很多的函数调用,函数调用本身会增加内存和CPU的负载。
但,现在的计算机都非常快,这些性能上的损耗可以忽略不计。
10. Binding
每个function 都有一个bind方法。 这句话有点奇怪哈。 可以认为 function 是一种对象,它也有一些内置的成员函数,其中一个成员函数叫bind。
bind会生成一个函数。本质上来讲,bind是另外一种使用函数的方法,这一章讲的不清楚,没看懂,下一章还会仔细讲。先看个例子:
var theSet = ["Carel Haverbeke", "Maria van Brussel", "Donald Duck"];function isInSet(set, person) { return set.indexOf(person.name) > -1;}console.log(ancestry.filter(function(person) { return isInSet(theSet, person);}));// → [{name: "Maria van Brussel", …},// {name: "Carel Haverbeke", …}]console.log(ancestry.filter(isInSet.bind(null, theSet)));// → … same result
11. 练习一、Flattening
使用reduce,把一个多维数组转换成一维数组。例如:
var arrays = [[1, 2, 3], [4, 5], [6]];
// → [1, 2, 3, 4, 5, 6]
arrays.reduce(function(a,b){
return a.concat(b);
});
12. 练习二、计算母子年龄差的平均值
用reduce计算平均值:
function average(array) { function plus(a, b) { return a + b; } return array.reduce(plus) / array.length;}
用forEach生成一个 name - person 映射,便于查找母亲:
var byName = {};ancestry.forEach(function(person) { byName[person.name] = person;});
计算一个人的母子年龄差:
function diff(person) { var mother = byName[person.mother]; if (mother) { return person.born - mother.born; } else return null;}
用map 把person转换成母子年龄差,用filter过滤掉找不到母亲的人,然后计算平均值:
var diff = average(ancestry.map(diff).filter(function (diffAge) { return diffAge != null;}));console.log(diff);
13. 练习三、计算家族树中每个世纪的平均寿命
var centuryGroup = [];ancestry.forEach(function (person) { var century = Math.ceil(person.died/100); var age = person.died-person.born; if (century in centuryGroup) { centuryGroup[century].push(age); } else { centuryGroup[century] = [age]; }});for(var century in centuryGroup) { console.log(century + ': ' + average(centuryGroup[century]));}
14. 练习四、every and some
针对array,JavaScript标准库中提供了两个函数every 和 some。
例如:
[2,3,NaN].some(isNaN);
[2,3,4].every(isNaN);
自己写代码实现这两个函数,但不要作为array的方法,而是把array作为它们的参数。
function some(array, action) { var ret = false; for (var i=0; i<array.length; i++) { if (action(array[i])) { ret = true; break; } } return ret;}function every(array, action) { var ret = true; for (var i=0; i<array.length; i++) { if (!action(array[i])) { ret = false; break; } } return ret;}
0 0
- Eloquent JavaScript 笔记 五: High-Order Functions
- Eloquent JavaScript 笔记 三: Functions
- 《Eloquent JavaScript》笔记--函数;
- Eloquent JavaScript 笔记 十: Modules
- Eloquent JavaScript 笔记 十三:DOM
- Eloquent JavaScript 笔记 十七:HTTP
- Javascript—Higher Order Functions
- 《Eloquent JavaScript》笔记--对象与数组
- 《Eloquent JavaScript》笔记--程序的结构;
- Eloquent JavaScript 笔记 二:Program Structure
- Eloquent JavaScript 笔记 四:Objects and Arrays
- Eloquent JavaScript 笔记 七: Electronic Life
- Eloquent JavaScript 笔记 十一:A Programming Language
- Eloquent JavaScript 笔记 十四:Handling Event
- Eloquent JavaScript 笔记 十五:A Platform Game
- Eloquent JavaScript 笔记 十六:Drawing on Canvas
- Eloquent JavaScript 笔记 十九:Node.js
- Eloquent JavaScript 笔记 二十:略有遗憾
- opencv 常用数据格式
- 个人笔记整理-java设计模式之单例模式
- LeetCode刷题(C++)——Add Two Numbers(Medium)
- 如何让 PHP json_encode 函数不转义中文?
- hybird app中使用百度地图定位并获得详细地址(逆地址解析)
- Eloquent JavaScript 笔记 五: High-Order Functions
- 踏进性能测试的路,踩遍各种性能的坑
- 如何用R画折线图,散点图,平滑曲线图
- Android4.4中不能发送SD卡就绪广播
- 【软考】设计模式-桥接模式
- 二分贪心 D
- HDU1525 Euclid's Game (找规律博弈)
- Qt快速入门第三版章节3.2.3标准对话框练习
- HBase Java API 使用示例