33-js-concepts之9. 高阶函数

1.概念

一个函数接受另一个函数作为参数,这就是高阶函数。

e.g.

function add(x, y, f) {
    return f(x) + f(y);
}
add(-5, 6, Math.abs); // 11

2. ES5中新增的Array方法

1) forEach

[1, 2, 3, 4].forEach(console.log);
// 1 0 [1, 2, 3, 4]
// 2 1 [1, 2, 3, 4]
// 3 2 [1, 2, 3, 4]
// 4 3 [1, 2, 3, 4]

由此可见,forEach参数中的回调函数接收三个参数,分别是value, index, array,即元素值、元素索引、数组本身。对此,我们有:
( 注意index在第二个参数位置)

var sum = 0;

[1, 2, 3, 4].forEach(function (item, index, array) {
  console.log(array[index] == item); // true
  sum += item;
});

alert(sum); // 10  

forEach除了接收一个回调函数,还可以接收一个上下文参数(可选,默认是全局对象window),即:

array.forEach(callback,[ thisObject])

var database = {
  users: ["张含韵", "江一燕", "李小璐"],
  sendEmail: function (user) {
    if (this.isValidUser(user)) {
      console.log("你好," + user);
    } else {
      console.log("抱歉,"+ user +",你不是本家人");  
    }
  },
  isValidUser: function (user) {
    return /^张/.test(user);
  }
};

// 给每个人发邮件
database.users.forEach(  // database.users中人遍历
  database.sendEmail,    // 发送邮件
  database               // 使用database代替上面标红的this
);

// 结果:
// 你好,张含韵
// 抱歉,江一燕,你不是本家人
// 抱歉,李小璐,你不是本家人

2) map

即“映射”,callback要有返回值,否则会返回一个全是undefined的数组。

array.map(callback,[ thisObject]);
可以利用map获得对象数组中的特定属性值:

var users = [
  {name: "张含韵", "email": "zhang@email.com"},
  {name: "江一燕",   "email": "jiang@email.com"},
  {name: "李小璐",  "email": "li@email.com"}
];

var emails = users.map(function (user) { return user.email; });

console.log(emails.join(", ")); // zhang@email.com, jiang@email.com, li@email.com

3) filter

返回过滤后的新数组。callback函数需要返回值,弱等于Boolean值。

var data = [0, 1, 2, 3];
var arrayFilter = data.filter(function(item) {
    return item;
});
console.log(arrayFilter); // [1, 2, 3]

var emailsZhang = users
  // 获得邮件
  .map(function (user) { return user.email; })
  // 筛选出zhang开头的邮件
  .filter(function(email) {  return /^zhang/.test(email); });

console.log(emailsZhang.join(", ")); // zhang@email.com

4) some

数组中的某些项是否符合条件(至少1个),只要发现一个符合立刻返回true。返回值为true或false。

var scores = [5, 8, 3, 10];
var current = 7;

function higherThanCurrent(score) {
  return score > current;
}

if (scores.some(higherThanCurrent)) {
  alert("朕准了!");
}
// 朕准了!

5) every

和some相反,要全部符合条件才返回true,否则返回false。

if (scores.every(higherThanCurrent)) {
  console.log("朕准了!");
} else {
  console.log("来人,拖出去斩了!");        
}
 // 来人,拖出去斩了!

6) indexOf

7) lastIndexOf

8) reduce

callback函数接受4个参数:之前值当前值索引值以及数组本身。initialValue参数可选,表示初始值。若指定,则当作最初使用的previous值;如果缺省,则使用数组的第一个元素作为previous初始值,同时current往后排一位,相比有initialValue值少一次迭代。

array.reduce(callback[, initialValue])

var sum = [1, 2, 3, 4].reduce(function (previous, current, index, array) {
  return previous + current;
});

console.log(sum); // 10

说明:

因为initialValue不存在,因此一开始的previous值等于数组的第一个元素。
从而current值在第一次调用的时候就是2.
最后两个参数为索引值index以及数组本身array。

// 初始设置
previous = initialValue = 1, current = 2

// 第一次迭代
previous = (1 + 2) =  3, current = 3

// 第二次迭代
previous = (3 + 3) =  6, current = 4

// 第三次迭代
previous = (6 + 4) =  10, current = undefined (退出)

二维数组扁平化:

var matrix = [
  [1, 2],
  [3, 4],
  [5, 6]
];

// 二维数组扁平化
var flatten = matrix.reduce(function (previous, current) {
  return previous.concat(current);
});

console.log(flatten); // [1, 2, 3, 4, 5, 6]

类似的reduceRight,从数组末尾开始:

var data = [1, 2, 3, 4];
var specialDiff = data.reduceRight(function (previous, current, index) {
  if (index == 0) {
    return previous + current;
  }
  return previous - current;
});

console.log(specialDiff); // 0

// 初始设置
index = 3, previous = initialValue = 4, current = 3

// 第一次迭代
index = 2, previous = (4- 3) = 1, current = 2

// 第二次迭代
index = 1, previous = (1 - 2) = -1, current = 1

// 第三次迭代
index = 0, previous = (-1 + 1) = 0, current = undefined (退出)

3.Reducer深入

首先是基本函数:

var words = [ "You", "have", "written", "something", "very", "interesting" ];

function strUppercase(str) { return str.toUpperCase(); }

function isLongEnough(str) {
    return str.length >= 5;
}

function isShortEnough(str) {
    return str.length <= 10;
}

要对数组过滤,可以直接map、连续filter,但显得过于重复,且每个 filter(..) 方法都会产生一个单独的 observable 值,数据量大时就会出现性能问题。所以可以使用reduce,initialValue设为空数组[]作为初始的list:

function strUppercaseReducer(list,str) {
    return list.concat( [strUppercase( str )] );
}

function isLongEnoughReducer(list,str) {
    if (isLongEnough( str )) return list.concat( [str] );
    return list;
}

function isShortEnoughReducer(list,str) {
    if (isShortEnough( str )) return list.concat( [str] );
    return list;
}

除了判断函数不同其他基本相同,所以可以利用闭包把判断函数作为参数,建立一个filterReducer,同理还有mapReducer:

function filterReducer(predicateFn) {
  return function reducer(list, val) {
    if (predicateFn(val)) {
      return list.concat( [val] );
    }
    return list;
  }
}
var isLongEnoughReducer = filterReducer( isLongEnough );
var isShortEnoughReducer = filterReducer( isShortEnough );

function mapReducer(fn) {
  return function reducer(list, val) {
    return list.concat( [fn(val)] );
  }
}
var strToUppercaseReducer = mapReducer( strUppercase );

调用链就可以写成这样:

words
.reduce( strUppercaseReducer, [] )
.reduce( isLongEnoughReducer, [] )
.reduce( isShortEnough, [] )
.reduce( strConcat, "" );
// "WRITTENSOMETHING"

提取共用组合逻辑

仔细观察上面的 mapReducer(..) 和 filterReducer(..) 函数。你发现共享功能了吗?
这部分:

return list.concat( .. );
// 或者
return list;

所以还可以再提取出一个listCombination函数出来:

function listCombination(list,val) { return list.concat( [ val ] ); }

所以最新的filterReducer和mapReducer如下:

function filterReducer(predicateFn) {
  return function reducer(list, val) {
    if (predicateFn(val)) {
      return listCombination( list, val );
    }
    return list;
  }
}

function mapReducer(fn) {
  return function reducer(list, val) {
    return listCombination( list, fn(val) );
  }
}

进一步,可以使用不同的类似listCombination的工具函数,所以再对reducer进行改造如下:

function filterReducer(predicateFn,combinationFn) {
    return function reducer(list,val){
        if (predicateFn( val )) return combinationFn( list, val );
        return list;
    };
}

function mapReducer(mapperFn,combinationFn) {
    return function reducer(list,val){
        return combinationFn( list, mapperFn( val ) );
    };
}

var strToUppercaseReducer = mapReducer( strUppercase, listCombination );
var isLongEnoughReducer = filterReducer( isLongEnough, listCombination );
var isShortEnoughReducer = filterReducer( isShortEnough, listCombination );

将这些实用函数定义为接收两个参数而不是一个参数不太方便组合,因此我们使用我们的 curry(..) (柯里化)方法:

var curriedMapReducer = curry( function mapReducer(mapperFn,combinationFn){
    return function reducer(list,val){
        return combinationFn( list, mapperFn( val ) );
    };
} );

var curriedFilterReducer = curry( function filterReducer(predicateFn,combinationFn){
    return function reducer(list,val){
        if (predicateFn( val )) return combinationFn( list, val );
        return list;
    };
} );

var strToUppercaseReducer =
    curriedMapReducer( strUppercase )( listCombination );
var isLongEnoughReducer =
    curriedFilterReducer( isLongEnough )( listCombination );
var isShortEnoughReducer =
    curriedFilterReducer( isShortEnough )( listCombination );

参考资料:
ES5中新增的Array方法详细说明
翻译连载 | 附录 A:Transducing(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容

  • The JavaScriptArrayobject is a global object thatis used ...
    skycolor阅读 564评论 0 0
  • 创建数组 arr.length--- title: js正则表达式categories: javascriptda...
    angelwgh阅读 1,391评论 0 2
  • 有目的的选择是功利吗? 深圳已经连续下雨一个月了,空气粘腻到每个人都无形增加了几分火气,开了空调的小房间里,...
    尘间北阅读 429评论 0 2
  • 图层的几个坐标系 对于iOS来说,坐标系的(0,0)点在左上角,就是越往下,Y值越大。越往右,X值越大。 一个图层...
    LeeMystique阅读 16,683评论 1 53
  • 慈母手中线,游子身上衣。母亲,在大多数人的心中,都是最温暖柔软的一部分。 我的母亲,也不例外。每次想起她,...
    笨笨姐妹阅读 373评论 0 3