【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 {
  // 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函数基础
第一章地址:函数编程简介

你可能感兴趣的:(【JavaScript ES6 函数式编程入门经典】读书笔记(第三章))