新浦京81707con > 首页 > 精心收集的,什么是函数组合

原标题:精心收集的,什么是函数组合

浏览次数:200 时间:2019-05-02

克服 JavaScript 面试:什么是函数组合

2017/01/30 · JavaScript · 2 评论 · 函数

原稿出处: Eric Elliott   译文出处:众成翻译   

图片 1

谷歌(Google) 数据主导管道 — Jorge Jorquera — (CC-BY-NC-ND-二.0)

“克制 JavaScript 面试”是笔者写的一文山会海小说,来支援面试者企图他们在面试 JavaScript 中、高职少将恐怕会遇到的有的标题。这一个标题自身自个儿在面试中也时不时会问。

函数式编制程序正在接管 JavaScript 世界。就在几年前,只有个别 JavaScript 程序猿知道函数式编制程序是如何。不过,在过去 3年内,我所见到的各样大型应用程序代码库都大方用到了函数式编制程序观念。

函数组合便是构成两到七个函数来生成四个新函数的经过。将函数组合在一同,就如将壹连串管道扣合在一齐,让多少流过同样。

轻巧易行,函数 fg 的构成能够被定义为 f(g(x)),从内到外(从右到左)求值。也正是说,求值顺序是:

  1. x
  2. g
  3. f

下边大家在代码中更中远距离观望一下这些概念。即便你想把用户的全名调换为 U福特ExplorerL Slug,给各样用户多少个个人音讯页面。为了促成此供给,你必要阅历一体系的步骤:

  1. 将人名依照空格分拆(split)到三个数组中
  2. 将姓名映射(map)为题写
  3. 用破折号连接(join)
  4. 编码 URI 组件

如下是三个简单的贯彻:

JavaScript

const toSlug = input => encodeURIComponent( input.split(' ') .map(str => str.toLowerCase()) .join('-') );

1
2
3
4
5
const toSlug = input => encodeURIComponent(
  input.split(' ')
    .map(str => str.toLowerCase())
    .join('-')
);

还不赖…可是壹旦作者报告您可读性还是能更强一点会怎么样啊?

壹旦每种操作都有二个对应的可组成的函数。上述代码就足以被写为:

JavaScript

const toSlug = input => encodeURIComponent( join('-')( map(toLowerCase)( split(' ')( input ) ) ) ); console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

1
2
3
4
5
6
7
8
9
10
11
const toSlug = input => encodeURIComponent(
  join('-')(
    map(toLowerCase)(
      split(' ')(
        input
      )
    )
  )
);
 
console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

那看起来比大家的首先次尝试更难读懂,但是先忍一下,大家将要消除。

为了兑现上述代码,我们将整合两种常用的工具,比方 split()join()map()。如下是促成:

JavaScript

const curry = fn => (...args) => fn.bind(null, ...args); const map = curry((fn, arr) => arr.map(fn)); const join = curry((str, arr) => arr.join(str)); const toLowerCase = str => str.toLowerCase(); const split = curry((splitOn, str) => str.split(splitOn));

1
2
3
4
5
6
7
8
9
const curry = fn => (...args) => fn.bind(null, ...args);
 
const map = curry((fn, arr) => arr.map(fn));
 
const join = curry((str, arr) => arr.join(str));
 
const toLowerCase = str => str.toLowerCase();
 
const split = curry((splitOn, str) => str.split(splitOn));

除了 toLowerCase() 外,全体这么些函数经产品测试的本子都足以从 Lodash/fp 中获得。可以像这么导入它们:

JavaScript

import { curry, map, join, split } from 'lodash/fp';

1
import { curry, map, join, split } from 'lodash/fp';

也足以像那样导入:

JavaScript

const curry = require('lodash/fp/curry'); const map = require('lodash/fp/map'); //...

1
2
3
const curry = require('lodash/fp/curry');
const map = require('lodash/fp/map');
//...

此处小编偷了点懒。注意那几个 curry 从技巧上来讲,并不是三个的确的柯里化函数。真正的柯里化函数总会生成三个一元函数。这里的 curry 只是一个偏函数应用。请参考“柯里化和偏函数应用之间的分别是如何?”那篇小说。不过,这里只是为了演示用途,我们就把它看作二个真正的柯里化函数好了。

回去大家的 toSlug() 落成,这里有部分东西确实让本人很烦:

JavaScript

const toSlug = input => encodeURIComponent( join('-')( map(toLowerCase)( split(' ')( input ) ) ) ); console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

1
2
3
4
5
6
7
8
9
10
11
const toSlug = input => encodeURIComponent(
  join('-')(
    map(toLowerCase)(
      split(' ')(
        input
      )
    )
  )
);
 
console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

对自家的话,这里的嵌套太多了,读起来有些令人摸不着头脑。大家得以用3个会活动组合这一个函数的函数来扁平化嵌套,正是说,那个函数会从二个函数获得输出,并自动将它传递给下二个函数作为输入,直到获得终极值结束。

细想一下,好像数组中有一个函数能够做大概的事务。那个函数正是 reduce(),它用一系列值为参数,对各样值应用二个函数,最终累加成三个结实。值作者也足以函数。但是 reduce() 是从左到右递减,为了合作上边的组成行为,我们须要它从右到左缩减。

好事情是刚刚数组也有二个 reduceRight() 方法可以干那事:

JavaScript

const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

1
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

.reduce() 一样,数组的 .reduceRight() 方法包罗3个 reducer 函数和多少个开端值(x)为参数。我们能够用它从右到左迭代数组,将函数依次使用到各种数组成分上,最终收获累加值(v)。

compose,咱们就能够没有须要嵌套来重写上边的组合:

JavaScript

const toSlug = compose( encodeURIComponent, join('-'), map(toLowerCase), split(' ') ); console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

1
2
3
4
5
6
7
8
const toSlug = compose(
  encodeURIComponent,
  join('-'),
  map(toLowerCase),
  split(' ')
);
 
console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

自然,lodash/fp 也提供了 compose()

JavaScript

import { compose } from 'lodash/fp';

1
import { compose } from 'lodash/fp';

或者:

JavaScript

const compose = require('lodash/fp/compose');

1
const compose = require('lodash/fp/compose');

当以数学格局的重组从内到外的角度来思量时,compose 是没有错的。不过,如若想以从左到右的相继的角度来合计,又该如何是好呢?

还有别的壹种格局,通常号称 pipe()。Lodash 称之为 flow():

JavaScript

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x); const fn1 = s => s.toLowerCase(); const fn2 = s => s.split('').reverse().join(''); const fn3 = s => s '!' const newFunc = pipe(fn1, fn2, fn3); const result = newFunc('Time'); // emit!

1
2
3
4
5
6
7
8
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
 
const fn1 = s => s.toLowerCase();
const fn2 = s => s.split('').reverse().join('');
const fn3 = s => s '!'
 
const newFunc = pipe(fn1, fn2, fn3);
const result = newFunc('Time'); // emit!

可以见到,那几个达成与 compose() 差不离完全1致。唯一的分歧之处是,这里是用 .reduce(),而不是 .reduceRight(),正是从左到右缩减,而不是从右到左。

上边大家来看望用 pipe() 实现的 toSlug() 函数:

JavaScript

const toSlug = pipe( split(' '), map(toLowerCase), join('-'), encodeURIComponent ); console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

1
2
3
4
5
6
7
8
const toSlug = pipe(
  split(' '),
  map(toLowerCase),
  join('-'),
  encodeURIComponent
);
 
console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

对此本身来讲,那要更易于读懂一些。

骨灰级的函数式程序猿用函数组合定义他们的整个应用程序。而自个儿平日用它来打消一时半刻变量。仔细看看 pipe() 版本的 toSlug(),你会开采部分非凡之处。

在命令式编制程序中,在有个别变量上施行调换时,在转移的种种步骤中都会找到对变量的引用。而地点的 pipe() 完结是用无点的风骨写的,正是说完全找不到它要操作的参数。

笔者平日将管道(pipe)用在像单元测试和 Redux 状态 reducer 那类事情上,用来驱除中间变量。中间变量的存在只用来保存八个操作到下3个操作之间的一时半刻值。

那玩意儿初始听起来会相比较奇特,然则随着你用它练习,会开掘在函数式编制程序中,你是在和1对壹抽象、广义的函数打交道,而在那样的函数中,事物的名目没那么重大。名称只会难以。你会起始把变量当作是剩下的规范。

正是说,笔者感觉无点风格也许会被用过头。它可能会变得太密集,较难知晓。不过1旦你搞糊涂了,这里有二个小秘籍…你能够应用 flow 来追踪是怎么回事:

JavaScript

const trace = curry((label, x) => { console.log(`== ${ label }: ${ x }`); return x; });

1
2
3
4
const trace = curry((label, x) => {
  console.log(`== ${ label }:  ${ x }`);
  return x;
});

一般来讲是你用它来跟踪的办法:

JavaScript

const toSlug = pipe( trace('input'), split(' '), map(toLowerCase), trace('after map'), join('-'), encodeURIComponent ); console.log(toSlug('JS Cheerleader')); // '== input: JS Cheerleader' // '== after map: js,cheerleader' // 'js-cheerleader'

1
2
3
4
5
6
7
8
9
10
11
12
13
const toSlug = pipe(
  trace('input'),
  split(' '),
  map(toLowerCase),
  trace('after map'),
  join('-'),
  encodeURIComponent
);
 
console.log(toSlug('JS Cheerleader'));
// '== input:  JS Cheerleader'
// '== after map:  js,cheerleader'
// 'js-cheerleader'

trace() 只是更通用的 tap() 的壹种卓殊格局,它能够令你对流过管道的每一种值实践一些表现。精晓了么?管道(Pipe)?水阀(Tap)?能够像上面那样编写 tap()

JavaScript

const tap = curry((fn, x) => { fn(x); return x; });

1
2
3
4
const tap = curry((fn, x) => {
  fn(x);
  return x;
});

目前您能够看出为嘛 trace() 只是多少个非同一般情况下的 tap() 了:

JavaScript

const trace = label => { return tap(x => console.log(`== ${ label }: ${ x }`)); };

1
2
3
const trace = label => {
  return tap(x => console.log(`== ${ label }:  ${ x }`));
};

你应该开首对函数式编制程序是怎么体统,以及偏函数应用柯里化如何与函数组合协作,来援助你编写可读性更加强的次第有点感到了。

1 赞 9 收藏 2 评论

图片 2

原文:

原文:

作者:Chalarangelo

作者:Chalarangelo

译者:IT168  www.toutiao.com/i6498962961288135182

译者:IT168  www.toutiao.com/i6498962961288135182

 

 

该类型来自于 Github 用户 Chalarangelo,近期已在 Github 上得到了 四千多Star,精心搜罗了多达 48 个有效的 JavaScript 代码片段,该用户的代码能够让程序员在 30 秒以至越来越少的时刻内领悟这么些常常使用的根底算法,来看望那一个 JavaScript 代码都传达出了什么吧!

该类型来自于 Github 用户 Chalarangelo,近年来已在 Github 上获得了 五千多Star,精心搜聚了多达 4八 个有效的 JavaScript 代码片段,该用户的代码可以让程序员在 30 秒以至越来越少的时日内精晓那个平时使用的基础算法,来看望那些 JavaScript 代码都传达出了怎么吧!

 

 

图片 3

图片 4

Anagrams of string(带有重复项)

Anagrams of string(带有重复项)

 

 

采取递归。对于给定字符串中的每种字母,为字母创立字谜。使用map()将字母与每部分字谜组合,然后采纳reduce()将具有字谜组合到三个数组中,最焦点情状是字符串长度等于二或壹。

选取递归。对于给定字符串中的每一种字母,为字母创建字谜。使用map()将字母与每部分字谜组合,然后采纳reduce()将享有字谜组合到叁个数组中,最主旨景况是字符串长度等于二或1。

 

 

const anagrams = str => {

  if (str.length <= 2) return str.length === 2 ? [str, str[1] str[0]] : [str];

  return str.split('').reduce((acc, letter, i) =>

    acc.concat(anagrams(str.slice(0, i) str.slice(i 1)).map(val => letter val)), []);

};

// anagrams('abc') -> ['abc','acb','bac','bca','cab','cba']

const anagrams = str => {

  if (str.length <= 2) return str.length === 2 ? [str, str[1] str[0]] : [str];

  return str.split('').reduce((acc, letter, i) =>

    acc.concat(anagrams(str.slice(0, i) str.slice(i 1)).map(val => letter val)), []);

};

// anagrams('abc') -> ['abc','acb','bac','bca','cab','cba']

 

 

数组平平均数量

数组平平均数量

 

 

动用reduce()将各样值增添到累加器,初阶值为0,总和除以数主管度。

利用reduce()将各样值增添到累加器,早先值为0,总和除以数COO度。

 

 

const average = arr => arr.reduce((acc, val) => acc val, 0) / arr.length;

// average([1,2,3]) -> 2

const average = arr => arr.reduce((acc, val) => acc val, 0) / arr.length;

// average([1,2,3]) -> 2

 

 

大写每一种单词的首字母

大写种种单词的首字母

 

 

利用replace()相称每一种单词的率先个字符,并采纳toUpperCase()来将其大写。

动用replace()相称各类单词的首先个字符,并选取toUpperCase()来将其大写。

 

 

const capitalizeEveryWord = str => str.replace(/[a-z]/g, char => char.toUpperCase());

// capitalizeEveryWord('hello world!') -> 'Hello World!'

const capitalizeEveryWord = str => str.replace(/[a-z]/g, char => char.toUpperCase());

// capitalizeEveryWord('hello world!') -> 'Hello World!'

 

 

首字母大写

首字母大写

 

 

动用slice(0,一)和toUpperCase()大写第2个假名,slice(壹)获取字符串的其他部分。 省略lowerRest参数以维持字符串的别的部分不变,或将其设置为true以转换为小写。(注意:那和上3个演示不是同样件专门的学业)

使用slice(0,一)和toUpperCase()大写第3个假名,slice(一)获取字符串的别的部分。 省略lowerRest参数以保全字符串的其他部分不改变,或将其设置为true以转移为小写。(注意:那和上3个示范不是同样件业务)

 

 

const capitalize = (str, lowerRest = false) =>

  str.slice(0, 1).toUpperCase() (lowerRest ? str.slice(1).toLowerCase() : str.slice(1));

// capitalize('myName', true) -> 'Myname'

const capitalize = (str, lowerRest = false) =>

  str.slice(0, 1).toUpperCase() (lowerRest ? str.slice(1).toLowerCase() : str.slice(1));

// capitalize('myName', true) -> 'Myname'

 

 

反省回文

自己探讨回文

 

 

将字符串转变为toLowerCase(),并动用replace()从中删除非字母的字符。然后,将其转移为tolowerCase(),将('')拆分为单身字符,reverse(),join(''),与原来的非反转字符串进行相比,然后将其转移为tolowerCase()。

将字符串转变为toLowerCase(),并选用replace()从中删除非字母的字符。然后,将其改变为tolowerCase(),将('')拆分为单独字符,reverse(),join(''),与原本的非反转字符串进行比较,然后将其转移为tolowerCase()。

 

 

const palindrome = str => {

  const s = str.toLowerCase().replace(/[W_]/g,'');

  return s === s.split('').reverse().join('');

}

// palindrome('taco cat') -> true

const palindrome = str => {

  const s = str.toLowerCase().replace(/[W_]/g,'');

  return s === s.split('').reverse().join('');

}

// palindrome('taco cat') -> true

 

 

计数数组中值的面世次数

计数数组中值的产出次数

 

 

每便遇到数组中的特定值时,使用reduce()来递增计数器。

每一趟遇到数组中的特定值时,使用reduce()来递增计数器。

 

 

const countOccurrences = (arr, value) => arr.reduce((a, v) => v === value ? a 1 : a 0, 0);

// countOccurrences([1,1,2,1,2,3], 1) -> 3

const countOccurrences = (arr, value) => arr.reduce((a, v) => v === value ? a 1 : a 0, 0);

// countOccurrences([1,1,2,1,2,3], 1) -> 3

 

 

当前URL

当前URL

 

 

使用window.location.href来收获当前URAV四L。

选择window.location.href来获得当前U君越L。

 

 

const currentUrl = _ => window.location.href;

// currentUrl() -> ''

const currentUrl = _ => window.location.href;

// currentUrl() -> ''

 

 

Curry

Curry

 

 

动用递归。如若提供的参数(args)数量丰盛,则调用传递函数f,不然重临3个curried函数f。

利用递归。假若提供的参数(args)数量丰富,则调用传递函数f,否则重临1个curried函数f。

 

 

const curry = (fn, arity = fn.length, ...args) =>

  arity <= args.length

    ? fn(...args)

    : curry.bind(null, fn, arity, ...args);

// curry(Math.pow)(2)(10) -> 1024

// curry(Math.min, 3)(10)(50)(2) -> 2

const curry = (fn, arity = fn.length, ...args) =>

  arity <= args.length

    ? fn(...args)

    : curry.bind(null, fn, arity, ...args);

// curry(Math.pow)(2)(10) -> 1024

// curry(Math.min, 3)(10)(50)(2) -> 2

 

 

Deep flatten array

Deep flatten array

 

 

动用递归,使用reduce()来收获具备不是数组的因素,flatten每种成分都以数组。

采取递归,使用reduce()来赢得具备不是数组的成分,flatten每种成分都是数组。

 

 

const deepFlatten = arr =>

  arr.reduce((a, v) => a.concat(Array.isArray(v) ? deepFlatten(v) : v), []);

// deepFlatten([1,[2],[[3],4],5]) -> [1,2,3,4,5]

const deepFlatten = arr =>

  arr.reduce((a, v) => a.concat(Array.isArray(v) ? deepFlatten(v) : v), []);

// deepFlatten([1,[2],[[3],4],5]) -> [1,2,3,4,5]

 

 

数组之间的分别

数组之间的区分

 

 

从b制造一个Set,然后在a上运用Array.filter(),只保留b中不包含的值。

从b创设贰个Set,然后在a上应用Array.filter(),只保留b中不包蕴的值。

 

 

 

const difference = (a, b) => { const s = new Set(b); return a.filter(x => !s.has(x)); };

// difference([1,2,3], [1,2]) -> [3]

 

const difference = (a, b) => { const s = new Set(b); return a.filter(x => !s.has(x)); };

// difference([1,2,3], [1,2]) -> [3]

 

 

两点时期的离开

两点时期的相距

 

 

动用Math.hypot()总括两点时期的欧几Reade距离。

应用Math.hypot()总结两点之间的欧几Reade距离。

 

 

const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);

// distance(1,1, 2,3) -> 2.23606797749979

const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);

// distance(1,1, 2,3) -> 2.23606797749979

 

 

能够按数字整除

能够按数字整除

 

 

采用模运算符(%)来检查余数是还是不是等于0。

利用模运算符(%)来检查余数是或不是等于0。

 

 

const isDivisible = (dividend, divisor) => dividend % divisor === 0;

// isDivisible(6,3) -> true

const isDivisible = (dividend, divisor) => dividend % divisor === 0;

// isDivisible(6,3) -> true

 

 

转义正则表达式

转义正则表达式

 

 

选拔replace()来转义特殊字符。

应用replace()来转义特殊字符。

 

 

const escapeRegExp = str => str.replace(/[.* ?^${}()|[]]/g, '$&');

// escapeRegExp('(test)') -> (test)

const escapeRegExp = str => str.replace(/[.* ?^${}()|[]]/g, '$&');

// escapeRegExp('(test)') -> (test)

 

 

偶数或奇数

偶数或奇数

 

 

采用Math.abs()将逻辑扩大为负数,使用模(%)运算符进行检讨。 如若数字是偶数,则赶回true;假设数字是奇数,则赶回false。

行使Math.abs()将逻辑扩充为负数,使用模(%)运算符实行自己抵触。 倘诺数字是偶数,则赶回true;借使数字是奇数,则赶回false。

 

 

const isEven = num => num % 2 === 0;

// isEven(3) -> false

const isEven = num => num % 2 === 0;

// isEven(3) -> false

 

 

阶乘

阶乘

 

 

采纳递归。若是n小于或等于一,则赶回①。不然重回n和n - 壹的阶乘的乘积。

选用递归。尽管n小于或等于1,则赶回一。不然重返n和n - 一的阶乘的乘积。

 

 

const factorial = n => n <= 1 ? 1 : n * factorial(n - 1);

// factorial(6) -> 720

const factorial = n => n <= 1 ? 1 : n * factorial(n - 1);

// factorial(6) -> 720

 

 

斐波那契数组生成器

斐波那契数组生成器

 

 

创制多个特定长度的空数组,初步化前八个值(0和壹)。使用Array.reduce()向数组中增添值,前面包车型地铁一个数等于前边五个数相加之和(前多个除此而外)。

创造二个特定长度的空数组,早先化前四个值(0和一)。使用Array.reduce()向数组中加多值,前边的一个数等于后面五个数相加之和(前五个除此之外)。

本文由新浦京81707con发布于首页,转载请注明出处:精心收集的,什么是函数组合

关键词: 新浦京81707con javascript js

上一篇:浏览器大战能消停吗,标准终于完工了

下一篇:没有了