# ES6 中的高阶函数

高阶函数是执行以下一项或两项操作的函数

  • 将一个或多个函数作为参数
  • 返回一个函数作为结果

下面代码会发生什么呢?

const has = p => o => o.hasOwnProperty(p);
const sortBy = p => (a, b) => a[p] > b[p];
1
2

# 理解语法

为了说明如何使用箭头编写高阶函数,让我们看一个经典示例:add。在 ES5 中,它看起来像这样:

function add(x){
  return function(y){
    return y + x;
  };
}

var addTwo = add(2);
addTwo(3);          // => 5
add(10)(11);        // => 21
1
2
3
4
5
6
7
8
9

我们的 add 函数接受 x 并返回一个接受 y 的函数,该函数返回 y + x。我们如何用箭头函数写这个?我们知道

  • 箭头函数定义是一个表达式
  • 箭头函数隐式返回单个表达式的结果

所以我们必须做的就是让我们的箭头函数体成为另一个箭头函数,因此:我们的add用es6表示如下

const add = x => y => y + x;
const add2 = add(2);
add2(4); 
add(8)(7);       
1
2
3
4

# 对我们的用户进行排序

# 使用高阶函数

const has = p => o => o.hasOwnProperty(p);
const sortBy = p => (a, b) => a[p] > b[p];

let result;
let users = [
  { name: 'Qian', age: 27, pets : ['Bao'], title : 'Consultant' },
  { name: 'Zeynep', age: 19, pets : ['Civelek', 'Muazzam'] },
  { name: 'Yael', age: 52, title : 'VP of Engineering'}
];

result = users
  .filter(has('pets'))
  .sort(sortBy('age'));

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 不使用高阶函数

const has = p => o => o.hasOwnProperty(p);
const sortBy = p => (a, b) => a[p] > b[p];

let result;
let users = [
  { name: 'Qian', age: 27, pets : ['Bao'], title : 'Consultant' },
  { name: 'Zeynep', age: 19, pets : ['Civelek', 'Muazzam'] },
  { name: 'Yael', age: 52, title : 'VP of Engineering'}
];

result = users
  .filter(x => x.hasOwnProperty('pets'))
  .sort((a, b) => a.age > b.age);
1
2
3
4
5
6
7
8
9
10
11
12
13

# 为啥这样很有用呢?

原因如下

  • 它减少了重复的代码
  • 更高的代码复用率
  • 它增加了代码含义的清晰度

假如我们想要筛选有pets和title的数据,我们可以这样写

result = users
  .filter(x => x.hasOwnProperty('pets'))
  .filter(x => x.hasOwnProperty('title'))
1
2
3

上面的代码只是混乱:它没有增加清晰度,只是为了阅读和写作。与使用我们的 has 函数的相同代码进行比较:

result = users
  .filter(has('pets'))
  .filter(has('title'))
1
2
3

上面的写法更短也更容易写,这样可以减少拼写错误。我认为这段代码也更加清晰,因为它的用途一目了然。

# 更近一步

让我们创建一个函数,该函数将生成一个过滤器函数,用于检查对象是否具有具有特定值的键。

我们的函数检查了一个键,但是为了检查值,我们的过滤器函数需要知道两件事(键和值)

//[p]roperty, [v]alue, [o]bject:
const is = p => v => o => o.hasOwnProperty(p) && o[p] == v;

// broken down:
// outer:  p => [inner1 function, uses p]
// inner1: v => [inner2 function, uses p and v]
// inner2: o => o.hasOwnProperty(p) && o[p] = v;

1
2
3
4
5
6
7
8

所以我们名为“is”的新函数做了三件事

  • 获取一个属性名称并返回一个函数
  • 获取一个值并返回一个函数
  • 获取一个对象并测试该对象是否具有指定值的属性,最后返回一个布尔值。

下面是一个使用它来过滤我们的用户的例子


const titleIs = is('title');
// titleIs == v => o => o.hasOwnProperty('title') && o['title'] == v;

const isContractor = titleIs('Contractor');
// isContractor == o => o.hasOwnProperty('contractor') && o['title'] == 'Contractor';

let contractors = users.filter(isContractor);
let developers  = users.filter(titleIs('Developer'));

let user = {name: 'Viola', age: 50, title: 'Actress', pets: ['Zak']};
isEmployed(user);   // true
isContractor(user); // false

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 注意书写风格

对比下面两个函数,那个更易读

const i = x => y => z => h(x)(y) && y[x] == z;

const is = prop => val => obj => has(prop)(obj) && obj[prop] == val;
1
2
3

编写单行函数时有一种趋势,即以牺牲可读性为代价,尽可能简洁。但是简短而无意义的名称使函数看起来很简单,但是难以理解。

所以可以使用更有意义的函数名或者参数名。

# 还有一件事

如果您想按年龄降序而不是升序排序怎么办?或者找出谁不是员工? 我们是否必须编写新的实用函数 sortByDesc 和 notHas?不我们没有。 我们可以将返回布尔值的函数包装起来,用一个函数来反转布尔值


//take args, pass them thru to function x, invert the result of x
const invert = x => (...args) => !x(...args);
const noPets = invert(hasPets);

let petlessUsersOldestFirst = users
  .filter(noPets)
  .sort(invert(sortBy('age')));
1
2
3
4
5
6
7
8