【JavaScript ES6 函数式编程入门经典】读书笔记(第三章)

第三章 高阶函数

接受另一个函数作为其参数的函数称为高阶函数(Higher-Order Function,简称HOF)

3.1 理解数据

每种编程语言都有数据类型,这些数据类型能够存储数据并允许程序作用其中。Javascript支持如下几种数据类型:

  • Number
  • String
  • Boolean
  • Object
  • null
  • undefined
    重要的是函数也可作为Javascript的一种数据类型。当一门语言允许函数作为任何其他数据类型使用时,函数被称为一等公民。也就是说函数可被赋值给变量,作为参数传递,也可被其他函数返回。
// 存储函数 把一个函数存入变量
let fn = () => {}
fn就是一个指向函数数据类型的变量。
typeof fn
=> "function"
既然fn是函数的引用,就可以这样调用它:
fn()
// 传递函数 tellType函数
var tellType = (arg) => {
  console.log(typeof arg)
}

let data = 1
tellType(data)
=> number

var dataFn = () => {
  console.log("A function")
}
tellType(dataFn)
=> function

改一下需求,如果参数是函数,tellType就执行该函数
var tellType = (arg) => {
  if (typeof arg === 'function') {
    arg()
  } else {
    console.log(`the passed data is ${arg}`)
  }
}
tellType(dataFn)
=> A function
// 返回函数 返回String的crazy函数
let crazy = () => { return String } // 返回一个指向String函数的函数引用

let fn = crazy()
fn("HOF")
=> HOF
或
crazy() ("HOF")
=> HOF

---
ps: JavaScript 有一个String的内置函数,可以使用该函数创建新的字符串值,
String("HOF")
=> HOF
建议所有返回其他函数的函数顶部使用简单的文档,便于阅读代码,例如上面的crazy函数文档如下
// Fn => String
let crazy = () => { return String }
Fn => String 注释帮助读者理解crazy函数,它执行并返回了另一个指向String的函数。
---

函数可以接受另一函数作为其参数,一些函数不会返回另一个函数,引入高阶函数的定义:
高阶函数是接受函数作为参数并且/或者返回函数作为输出的函数。

3.2 抽象和高阶函数

一般而言,高阶函数通常用于抽象通用的问题,换而言之,高阶函数就是定义抽象。
抽象的定义:在软件工程和计算机科学中,抽象是一种管理计算机系统复杂性的技术,它通过建立一个人与系统进行交互的复杂程序,把更复杂的细节抑制在当前水平之下。程序员应该使用理想的界面(通常定义良好),并且可以添加额外级别的功能,否则处理起来将会很复杂。
抽象让我们专注于预定的目标而无需关心底层的系统概念。

// 通过高阶函数实现抽象
const forEach = (array, fn) => {
  for(let i=0; i<array.length; i++) {
    fn(array[i])
  }
}
例如上面forEach函数抽象出了遍历数组的问题,使用API forEach不需要理解forEach是如何实现遍历的。

注意:在forEach函数中,传入的fn函数被一个参数调用,并作为数组当前的遍历内容。fn(array[i])
调用forEach时可以这样使用
forEach([1, 2, 3], (data) => {
  // data被作为参数从forEach 函数传到当前的函数
})
forEach本质上遍历了数组,那么如何遍历了一个javascript对象呢?
(1)遍历给定对象的所有key
(2)识别key是否属于该对象本身
(3)如果步骤(2)是true,则获取key的值
把这些步骤抽象到一个forEachObject的高阶函数中
const forEachObject = (obj, fn) => {
  for (var property in obj) {
    // 以key和value作为参数调用fn
    fn(property, obj[property])
  }
}
forEachObject接受第一个参数作为Javascript对象(obj),而第二个参数是一个函数fn。它用上面的算法遍历对象,并分别以key和value作为参数调用fn。
let object = {a:1, b:2}
forEachObject(object, (k, v) => {
  console.log(k + ":" + v)
})
=> a:1
=> b:2

注意一个重点,forEach和forEachObject函数都是高阶函数,它们使开发者专注于任务(通过传递相应的函数),而抽象处遍历的部分,举个栗子,以抽象的方式实现对控制流程的处理。

// unless函数,接受一个predicate(值为true或者false)。如果predicate为false,调用fn。
const unless = (predicate, fn) => {
  if (!predicate) 
    fn()
}
有了unless函数,用它来查找一个列表中的偶数
forEach([1, 2, 3, 4, 5, 6, 7], (number) => {
  unless((number % 2), () => {
    console.log(number, " is even")
  })
})
上面执行后输出
2 'is even'
4 'is even'
6 'is even'

如果是获取0-100中的偶数,此处可看另外一个times的高阶函数。它接受一个数字,并根据调用者提供的次数调用传入的函数。times函数如下:

const times = (times, fn) => {
  for (var i = 0; i < times; i++) 
    fn(i)
}
times函数和forEach函数类似,唯一不同是操作的是一个Number而不是Array.
0-100偶数可变成
times(100, function(n) {
  unless(n % 2, function() {
    cosnole.log(n, "is even")
  })
})

3.3 真实的高阶函数

大多数高阶函数都会和闭包一起使用。

  • every 函数
    检查数组内容是否为一个数字、自定义对象或其他类型。通常编写循环方法来解决。现在将这些抽象到一个名为every的函数中,它接受两个参数:一个数组和一个函数。使用传入的函数检查数组的所有元素是否为true
    every()方法,遍历数组每一项,若全部为true,则返回true;
const every = (arr, fn) => {
  let result = true;
  for(let i = 0; i < arr.length; i++) {
    result = result && fn(arr[i])
  }
  return result
}
// 使用for-of循环的every函数
const every = (arr, fn) => {
  let result = true
  for (const value of arr) 
    result = result && fn (value)
  return result
}

此处简单的遍历传入的数组,并使用当前遍历的数组元素内容调用fn,传入的fn需要返回一个布尔值。使用&&运算确保所有的数组内容遵循fn给出的条件。
例如传入一个NaN数组,isNaN作为fn传入,检查给定的数字是否为NaN
every([NaN, NaN, NaN], isNaN)
=> true
every([NaN, NaN, 4], isNaN)
=> false

  • some 函数
    some的工作方式与every恰好相反。如果数组中的一个元素通过传入的函数返回true,some函数就将返回true。some函数也被成为any函数,使用的是|| 而不是 &&
    some()方法,遍历数组的每一项,若其中一项为 true,则返回true;
const some = (arr, fn) => {
  let result = false;
  for (const value of arr)
    result = result || fn(value)
  return result
}

some([NaN, NaN, 4], isNaN)
=> true
some([3, 4, 4], is NaN)
=> false

tips
every 函数和some函数都是低效的实现。every函数应该在遇到第一个不匹配条件的元素时就停止遍历数组,some函数应该在遇到第一个匹配条件的元素时就停止遍历数组。遇到大数组时是低效的。

  • sort函数
    它是javascript的Array原型的内置函数,它接受一个函数作为参数,该函数用于帮助例如
var fruit = ['cherries', 'apples', 'bananas']
fruit.sort()
=>['apples', 'bananas', 'cherries']

接受一个函数作为参数,用于帮助sort函数决定排序逻辑。
arr.sort([compareFun])
该参数为可选参数,如果未提供,元素将被转换为字符串并按照Unicode编码点排序

// compareFun函数的骨架
function compare(a, b) {
  if (根据某种排序标准a小于b) {
    return -1
  }
  if (根据某种排序标准a大于b) {
    return 1
  }
  // a 一定等于b
  return 0
}

抽象一个函数出来,它返回一个函数(HOF也会返回一个函数),例如有这样一个函数sortBy,它允许用户基于传入的属性对对象数组排序

const sortBy = (property) => {
  return (a, b) => {
    var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0
    return result
  }
}

sortBy函数接受一个名为property的参数并返回一个接受两个参数的形函数。
return (a, b) => {}
假设arr有多个属性,我们使用某属性调用sortBy函数,arr.sort(sortBy('xxxxx'))
这将根据xxxxx属性将arr数组从小到大排序,换个xxx属性,arr.sort(sortBy('xxx'))又是以xxx属性将arr从小到大排序,不需要重复写。sortBy函数接受一个属性并返回另一个函数,返回的函数作为compareFun传递给sort函数,持有着property参数值的返回函数之所以能够运行是因为js支持闭包。

3.4 小结

  1. 函数也是一种javascript数据类型,函数能够被存储、传递并像javascript的其他数据类型一样被赋值。
  2. 这种极端的javascript特性允许函数被传递给另一函数,称为高阶函数。高阶函数是接受另一个函数作为参数或返回一个函数的函数。
  3. 高阶函数的运行机制得益于javascript中另一个称为闭包的重要概念。

附上
第二章地址:JavaScript函数基础
第一章地址:函数编程简介

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

推荐阅读更多精彩内容