精华 ES6 高阶箭头函数
发布于 8 年前 作者 pfcoder 18438 次浏览 来自 分享

摘译自:https://strongloop.com/strongblog/higher-order-functions-in-es6easy-as-a-b-c/ 先来看下高阶函数定义:

  • 接受1个或多个函数作为参数
  • 返回函数类型

常规ES6箭头函数用法:(返回值类型) const square = x => x * x; 高阶写法:

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

理解语法

ES5实现高阶函数,也叫柯里化:

function add(x){
  return function(y){
    return y + x;
  };
}
 
var addTwo = add(2);
addTwo(3);          // => 5
add(10)(11);        // => 21

add函数接受x,返回一个函数接受y返回y+x。如何用箭头函数实现同样功能呢?我们知道:

  • 箭头函数体是表达式,并且
  • 箭头函数隐式返回表达式

所以为了实现高阶函数,我们可以使箭头函数的函数体为另一个箭头函数:

const add = x => y => y + x;
// outer function: x => [inner function, uses x]
// inner function: y => y + x;

这样我们可以创建一个绑定了x的内部函数:

const add2 = add(2);// returns [inner function] where x = 2
add2(4);            // returns 6: exec inner with y = 4, x = 2
add(8)(7);          // 15

这个例子看起来没什么实际作用,但是很好的展示了如何在返回的函数中引用外部参数。

用户排序例子

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'));

上述代码利用了Array的sort 和 filter 方法,这两个方法都接收一个函数参数,此处我们利用了箭头函数的高阶函数写法返回需要的函数。 对比下直接传入函数的写法:

result = users
  .filter(x => x.hasOwnProperty('pets')) //pass Function to filter
  .sort((a, b) => a.age > b.age);        //pass Function to sort

高阶函数写法:

result = users
  .filter(has('pets'))  //pass Function to filter
  .sort(sortBy('age')); //pass Function to sort

优势在哪?

  • 减少代码重复
  • 提高代码重用性
  • 更容易阅读的代码

假设我们想列出有pets和title的用户,可以采用如下传统写法:

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

采用高阶函数写法:

result = users
  .filter(has('pets'))
  .filter(has('title'))
  ...

可以明显感受到高阶写法更容易写和维护。

更进一步

假设想实现一个过滤器函数完成如下功能:判断一个对象是否包含指定值的key。之前的has函数用于检查对象key,我们需要在此基础上加入值的检查:

//[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;

所以我们的新函数is做了下面三件事:

  • 接收属性名返回函数…
  • 接收值返回函数…
  • 接收对象,并判读该对象是否有指定的属性名和值,并返回boolean

下面是一个使用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

关于命名习惯

下面的写法需要你花点时间去理解其含义: const i = x => y => z => h(x)(y) && y[x] == z; 使用一些更明确含义的参数命名: const is = prop => val => obj => has(prop)(obj) && obj[prop] == val;

继续

如果我们想进一步提供排序功能,但是仅改为降序排列,或者列出不包含某属性的用户,我们需要重新实现诸如 sortByDesc 和 notHas这样的新函数吗?答案是不需要,对于最终返回结果是boolean值的高阶函数,我们可以对其进行取反包装,如下:

//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')));

结论

函数式编程受到越来越多的重视,ES6的箭头函数提供了更简洁方便的JavaScript实现方式,如果你还没有看到这种方法的广泛使用,那么可以预见在未来几个月函数式编程会伴随ES6的普及变得更为流行。即使你不太喜欢这种方式,那么理解高阶函数也是非常有必要的。

15 回复

总算有点明白什么是科里化了

嵌套太多很难阅读啊,我一直以为这个和C#的lamda表达式一样,这种写法是不是更好理解

function foo() {
   return () => {
      return () => {
         return () => {
            console.log("id:", this.id);
         };
      };
   };
}

还有箭头函数并不绑定 this,arguments,super(ES6),抑或 new.target(ES6),这些function里的属性在里面都没有,会找父作用域的值,这个才是最大的变化

越来越晦涩了

const has = p => o => o.hasOwnProperty§; 不如 const has = p,o => o.hasOwnProperty§; 看得清楚

@yakczh 没看明白你的写法

@yakczh 你这个写法是错的, const has = p => o => o.hasOwnProperty; 是为了让 han(‘pet’) 勾出 o => o.hasOwnProperty§ 这个匿名函数,即: function( o ){ return o.hasOwnProperty(‘pet’) } 这个结构才是 filter的匿名回调里需要的格式

好文,多谢翻译

话说把可读性弄得这样真的好么。。。

来自酷炫的 CNodeMD

代码首先是给人看的,箭头函数用在一些简单的地方,简化代码结构是好事,但是强行过度使用,让代码阅读起来生涩难懂,就没有意义了。

其实和标题的箭头函数没啥关系啊= = 讲的是函数式编程

其实说到箭头函数,用它来做Y组合子看起来更爽

const Y = proc => (x => proc(y => (x(x))(y)))(x => proc(y => (x(x))(y)));

这样真的好理解吗

@xiashulin 上面的例子太极端了,哈哈,保证可读性优先,其次才是炫技

还有箭头函数的上下文 是如何确定的,以及何时适用并没有说明. 补充大漠的文章 ES6箭头函数

回到顶部