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