05月31, 2017

【译】用ES6替换lodash的10大特征

原文链接:https://www.sitepoint.com/lodash-features-replace-es6/

Lodash是目前最火的npm安装包,但是如果你正在使用ES6,可能就不需要上lodash了。在本文中,我们将利用ES6的箭头函数和其他新特征覆盖最常见的应用场景。

1. Map, Filter, Reduce

这些集合方法很优雅的处理数据,广受支持,我们将使用剪头函数来替代Lodash提供的这些功能。

_.map([1, 2, 3], function(n) { return n * 3; });
// [3, 6, 9]
_.reduce([1, 2, 3], function(total, n) { return total + n; }, 0);
// 6
_.filter([1, 2, 3], function(n) { return n <= 2; });
// [1, 2]

// 使用ES6之后

[1, 2, 3].map(n => n * 3);
[1, 2, 3].reduce((total, n) => total + n);
[1, 2, 3].filter(n => n <= 2);

这个示例只演示了部分功能,如果你用ES6,还可以使用find, some, every 以及 reduceRight 等方法。

2. Head & Tail

ES6的解构语法可以让我们很轻松的访问列表的头尾。

_.head([1, 2, 3]);
// 1
_.tail([1, 2, 3]);
// [2, 3]

// 使用ES6之后

const [head, ...tail] = [1, 2, 3];
`

同样可以很轻松访问前后段结构。

_.initial([1, 2, 3]);
// -> [1, 2]
_.last([1, 2, 3]);
// 3

// 使用ES6之后

const [last, ...initial] = [1, 2, 3].reverse();

如果你觉得reverse方法改变了原有数据的值,你可以使用展开操作符(...target)复制数据然后再调用reverse方法

const xs = [1, 2, 3];
const [last, ...initial] = [...xs].reverse();

3. Rest & Spread

使用rest参数和spread展开操作符可以实现定义和调用动态变参函数。ES6引入这个语法真是大杀器。

var say = _.rest(function(what, names) {
  var last = _.last(names);
  var initial = _.initial(names);
  var finalSeparator = (_.size(names) > 1 ? ', & ' : '');
  return what + ' ' + initial.join(', ') +
    finalSeparator + _.last(names);
});

say('hello', 'fred', 'barney', 'pebbles');
// "hello fred, barney, & pebbles"

// 使用ES6之后

const say = (what, ...names) => {
  const [last, ...initial] = names.reverse();
  const finalSeparator = (names.length > 1 ? ', &' : '');
  return `${what} ${initial.join(', ')} ${finalSeparator} ${last}`;
};

say('hello', 'fred', 'barney', 'pebbles');
// "hello fred, barney, & pebbles"

4. Curry

没有TypeScript或者Flow这样高级语言,要实现函数签名curry是非常困难的。当我们接受一个curry函数的时候内心是崩溃的,想要知道到底支持多少个参数?调用链的下一个参数是什么?完全一头懵逼。箭头函数显式的定义方式提高了可读性,拯救了curry函数。

function add(a, b) {
  return a + b;
}
var curriedAdd = _.curry(add);
var add2 = curriedAdd(2);
add2(1);
// 3

// 使用ES6之后

const add = a => b => a + b;
const add2 = add(2);
add2(1);
// 3

这种显式的curry剪头函数也方便了调试。

var lodashAdd = _.curry(function(a, b) {
  return a + b;
});
var add3 = lodashAdd(3);
console.log(add3.length)
// 0
console.log(add3);
//function wrapper() {
//  var length = arguments.length,
//  args = Array(length),
//  index = length;
//
//  while (index--) {
//    args[index] = arguments[index];
//  }…

// 使用ES6之后

const es6Add = a => b => a + b;
const add3 = es6Add(3);
console.log(add3.length);
// 1
console.log(add3);
// function b => a + b

如果我们还在使用lodash/fp或者ramda这样的函数式库,我们也可以使用箭头移除对lodash风格的依赖。

_.map(_.prop('name'))(people);

// 使用ES6之后

people.map(person => person.name);

5. Partial

就像curry特征,使用剪头函数同样也可以使partial简单易懂。

var greet = function(greeting, name) {
  return greeting + ' ' + name;
};

var sayHelloTo = _.partial(greet, 'hello');
sayHelloTo('fred');
// "hello fred"

// 使用ES6之后

const sayHelloTo = name => greet('hello', name);
sayHelloTo('fred');
// "hello fred"

也可以使用rest参数和spread操作符

const sayHelloTo = (name, ...args) => greet('hello', name, ...args);
sayHelloTo('fred', 1, 2, 3);
// "hello fred"

6. Operators

Lodash重新实现了运算符函数,并提供了一整套函数,这样运算符函数就可以传递到其他集合类方法中。

其实在大多数情况下,使用箭头函数就足够简单了,我们完全可以直接改为使用剪头函数。

_.eq(3, 3);
// true
_.add(10, 1);
// 11
_.map([1, 2, 3], function(n) {
  return _.multiply(n, 10);
});
// [10, 20, 30]
_.reduce([1, 2, 3], _.add);
// 6

// 使用ES6之后

3 === 3
10 + 1
[1, 2, 3].map(n => n * 10);
[1, 2, 3].reduce((total, n) => total + n);
`

7. Paths

很多lodash函数用字符串或者数组来处理路径问题。我们完全可以使用剪头函数来代替这种处理方式。

var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };

_.at(object, ['a[0].b.c', 'a[1]']);
// [3, 4]
_.at(['a', 'b', 'c'], 0, 2);
// ['a', 'c']

// 使用ES6之后

[
  obj => obj.a[0].b.c,
  obj => obj.a[1]
].map(path => path(object));

[
  arr => arr[0],
  arr => arr[2]
].map(path => path(['a', 'b', 'c']));

因为这些路径是纯函数,我们可以对其进行自由组合。

const getFirstPerson = people => people[0];
const getPostCode = person => person.address.postcode;
const getFirstPostCode = people => getPostCode(getFirstPerson(people));

甚至可以定义接受变参的高阶路径。

const getFirstNPeople = n => people => people.slice(0, n);

const getFirst5People = getFirstNPeople(5);
const getFirst5PostCodes = people => getFirst5People(people).map(getPostCode);

8. Pick

在lodash中可以通过pick从一个目标对象中选择想要的属性。 在ES6中通过解构和简单的对象遍历可以达到相同的结果。

var object = { 'a': 1, 'b': '2', 'c': 3 };

return _.pick(object, ['a', 'c']);
// { a: 1, c: 3 }

// 使用ES6之后

const { a, c } = { a: 1, b: 2, c: 3 };

return { a, c };

9. Constant, Identity, Noop

Lodash提供了一些工具来处理一些特殊场景。

_.constant({ 'a': 1 })();
// { a: 1 }
_.identity({ user: 'fred' });
// { user: 'fred' }
_.noop();
// undefined

在ES6里面完全可以用剪头函数来实现。

const constant = x => () => x;
const identity = x => x;
const noop = () => undefined;

或者换一个方式

(() => ({ a: 1 }))();
// { a: 1 }
(x => x)({ user: 'fred' });
// { user: 'fred' }
(() => undefined)();
// undefined

10. Chaining & Flow

Lodash提供了编写链接语句(Chained Statements)的函数功能。在大部分场景下,lodash内置的这些集合方法实现了链式调用,但是有些情况下,这些方法会修改集合的内部结构,这是不能接受的。

还好,我们可以用ES6的剪头函数数组实现同样的功能。

_([1, 2, 3])
 .tap(function(array) {
   // Mutate input array.
   array.pop();
 })
 .reverse()
 .value();
// [2, 1]

// 使用ES6之后

const pipeline = [
  array => { array.pop(); return array; },
  array => array.reverse()
];

pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);

在这种方式下,我们根本不需要去考虑tap和thru的区别,而是将其封装在一个函数里面,成为一个更通用的工具。

const pipe = functions => data => {
  return functions.reduce(
    (value, func) => func(value),
    data
  );
};

const pipeline = pipe([
  x => x * 2,
  x => x / 3,
  x => x > 5,
  b => !b
]);

pipeline(5);
// true
pipeline(20);
// false
Conclusion

Lodash依然是一个伟大的函数库,这篇文章不是要推翻lodash,而是想给大家一个全新的视角去看待问题,去看看新版JavaScript提供的解决问题的新能力,而在此之前,我们不得不依赖第三方库才能实现。

记得下一次你要发明轮子的时候,想想是不是有更简单的方式来处理。

本文链接:http://www.xiaojichao.com/post/lodash-features-replace-es6.html

-- EOF --

Comments