30秒就能理解的JavaScript优秀代码,什么是函数组合

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

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

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

澳门新葡亰平台官网 1

Google 数据中心管道 — Jorge Jorquera — (CC-BY-NC-ND-2.0)

“征服 JavaScript 面试”是我写的一系列文章,来帮助面试者准备他们在面试
JavaScript
中、高级职位中将可能会遇到的一些问题。这些问题我自己在面试中也经常会问。

函数式编程正在接管 JavaScript 世界。就在几年前,只有少数 JavaScript
程序员知道函数式编程是什么。然而,在过去 3
年内,我所看到的每个大型应用程序代码库都大量用到了函数式编程理念。

函数组合就是组合两到多个函数来生成一个新函数的过程。将函数组合在一起,就像将一连串管道扣合在一起,让数据流过一样。

简而言之,函数 fg 的组合可以被定义为
f(g(x)),从内到外(从右到左)求值。也就是说,求值顺序是:

  1. x
  2. g
  3. f

下面我们在代码中更近距离观察一下这个概念。假如你想把用户的全名转换为 URL
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’

对我来说,这里的嵌套太多了,读起来有点让人摸不着头脑。我们可以用一个会自动组合这些函数的函数来扁平化嵌套,就是说,这个函数会从一个函数得到输出,并自动将它传递给下一个函数作为输入,直到得到最终值为止。

细想一下,好像数组中有一个函数可以做差不多的事情。这个函数就是
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() 方法带有一个 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()
几乎完全一样。唯一的不同之处是,这里是用 .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
这类事情上,用来消除中间变量。中间变量的存在只用来保存一个操作到下一个操作之间的临时值。

这玩意开始听起来会比较古怪,不过随着你用它练习,会发现在函数式编程中,你是在和相当抽象、广义的函数打交道,而在这样的函数中,事物的名称没那么重要。名称只会碍事。你会开始把变量当作是多余的样板。

就是说,我认为无点风格可能会被用过头。它可能会变得太密集,较难理解。但是如果你搞糊涂了,这里有一个小窍门…你可以利用
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

数组

arrayMax

返回数组中的最大值。

将Math.max()与扩展运算符 (…) 结合使用以获取数组中的最大值。

const arrayMax = arr => Math.max(…arr);

// arrayMax([10, 1, 5]) -> 10

arrayMin

返回数组中的最小值。

将Math.min()与扩展运算符 (…) 结合使用以获取数组中的最小值。

const arrayMin = arr => Math.min(…arr);

// arrayMin([10, 1, 5]) -> 1

chunk

将数组块划分为指定大小的较小数组。

使用Array.from()创建新的数组,
这符合将生成的区块数。使用Array.slice()将新数组的每个元素映射到size长度的区块。如果原始数组不能均匀拆分,
则最终的块将包含剩余的元素。

const chunk = (arr, size) =>

Array.from({length: Math.ceil(arr.length / size)}, (v, i) =>
arr.slice(i * size, i * size + size));

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

compact

从数组中移除 falsey 值。

使用Array.filter()筛选出 falsey 值 (false、null、0、””、undefined和NaN).

const compact = (arr) => arr.filter(Boolean);

// compact([0, 1, false, 2, ”, 3, ‘a’, ‘e’*23, NaN, ‘s’, 34]) ->
[ 1, 2, 3, ‘a’, ‘s’, 34 ]

countOccurrences

计算数组中值的出现次数。

使用Array.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

deepFlatten

深拼合数组。

使用递归。使用Array.concat()与空数组 ([]) 和跨页运算符 (…)
来拼合数组。递归拼合作为数组的每个元素。

const deepFlatten = arr => [].concat(…arr.map(v =>
Array.isArray(v) ? deepFlatten(v) : v));

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

difference

返回两个数组之间的差异。

从b创建Set, 然后使用Array.filter() on 只保留a b中不包含的值.

const difference = (a, b) => { const s = newSet(b); returna.filter(x
=> !s.has(x)); };

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

distinctValuesOfArray

返回数组的所有不同值。

使用 ES6 Set和…rest运算符放弃所有重复的值。

const distinctValuesOfArray = arr => […newSet(arr)];

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

dropElements

移除数组中的元素, 直到传递的函数返回true。返回数组中的其余元素。
在数组中循环, 使用Array.shift()将数组的第一个元素除去,
直到函数的返回值为true。返回其余元素。

const dropElements = (arr, func) => {

while(arr.length > 0 && !func(arr[0])) arr.shift();

returnarr;

};

// dropElements([1, 2, 3, 4], n => n >= 3) -> [3,4]

everyNth

返回数组中的每个第 n 个元素。

使用Array.filter()创建一个包含给定数组的每个第 n 个元素的新数组。

const everyNth = (arr, nth) => arr.filter((e, i) => i % nth ===
0);

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

filterNonUnique

筛选出数组中的非唯一值。

对于只包含唯一值的数组, 请使用Array.filter()。

const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) ===
arr.lastIndexOf(i));

// filterNonUnique([1,2,2,3,4,4,5]澳门新葡亰平台官网 ,) -> [1,3,5]

flatten

拼合数组。

使用Array.reduce()获取数组中的所有元素和concat()以拼合它们。

const flatten = arr => arr.reduce((a, v) => a.concat(v), []);

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

flattenDepth

将数组向上拼合到指定深度。

使用递归, 递减depth,
每层深度为1。使用Array.reduce()和Array.concat()来合并元素或数组。基本情况下,
对于等于1的depth停止递归。省略第二个元素,depth仅拼合到1的深度
(单个拼合)。

const flattenDepth = (arr, depth = 1) =>

depth != 1 ? arr.reduce((a, v) => a.concat(Array.isArray(v) ?
flattenDepth(v, depth – 1) : v), [])

: arr.reduce((a, v) => a.concat(v), []);

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

groupby

根据给定函数对数组元素进行分组。

使用Array.map()将数组的值映射到函数或属性名。使用Array.reduce()创建一个对象,
其中的键是从映射的结果生成的。

const groupBy = (arr, func) =>

arr.map(typeoffunc === ‘function’? func : val => val[func])

.reduce((acc, val, i) => { acc[val] = (acc[val] ||
[]).concat(arr[i]); returnacc; }, {});

// groupBy([6.1, 4.2, 6.3], Math.floor) -> {4: [4.2], 6: [6.1,
6.3]}

// groupBy([‘one’, ‘two’, ‘three’], ‘length’) -> {3: [‘one’,
‘two’], 5: [‘three’]}

head

返回列表的头。

使用arr[0]可返回传递的数组的第一个元素。

const head = arr => arr[0];

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

initial

返回除最后一个数组之外的所有元素。

使用 “arr.slice(0,-1)” 返回数组的最后一个元素。

const initial = arr => arr.slice(0, -1);

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

initializeArrayWithRange

初始化包含指定范围内的数字的数组。

使用Array(end-start)创建所需长度的数组Array.map()以填充区域中所需的值。可以省略start以使用默认值0.

const initializeArrayWithRange = (end, start = 0) =>

Array.from({ length: end – start }).map((v, i) => i + start);

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

initializeArrayWithValues

初始化并填充具有指定值的数组。

使用Array(n)创建所需长度的数组,fill(v)以填充所需的值。可以省略value以使用默认值0.

const initializeArrayWithValues = (n, value = 0) =>
Array(n).fill(value);

// initializeArrayWithValues(5, 2) -> [2,2,2,2,2]

intersection

返回两个数组中存在的元素的列表。

从b创建Set, 然后使用Array.filter()on a只保留b中包含的值.

const intersection = (a, b) => { const s = newSet(b);
returna.filter(x => s.has(x)); };

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

last

返回数组中的最后一个元素。

使用arr.length – 1可计算给定数组的最后一个元素的索引并返回它。

const last = arr => arr[arr.length – 1];

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

mapObject

使用函数将数组的值映射到对象, 其中键值对由原始值作为键和映射值组成。

使用匿名内部函数范围来声明未定义的内存空间,
使用闭包来存储返回值。使用新的Array可将该数组与函数的映射放在其数据集上,
而逗号运算符返回第二个步骤, 而不需要从一个上下文移动到另一个环境
(由于关闭和操作顺序)。

const mapObject = (arr, fn) =>

(a => (a = [arr, arr.map(fn)], a[0].reduce( (acc,val,ind) =>
(acc[val] = a[1][ind], acc), {}) )) ( );

/*

const squareIt = arr => mapObject(arr, a => a*a)

squareIt([1,2,3]) // { 1: 1, 2: 4, 3: 9 }

*/

nthElement

返回数组的第 n 个元素。

使用Array.slice()可获取包含第 n 个元素的数组。如果索引超出界限,
则返回[]。省略第二个参数n, 以获取数组的第一个元素。

const nthElement = (arr, n=0) => (n>0? arr.slice(n,n+1) :
arr.slice(n))[0];

// nthElement([‘a’,’b’,’c’],1) -> ‘b’

// nthElement([‘a’,’b’,’b’],-3) -> ‘a’

pick

从对象中选取对应于给定键的键值对。

使用Array.reduce()将筛选/选取的密钥转换回具有相应键值对的对象 (如果在
obj 中存在该键)。

const pick = (obj, arr) =>

arr.reduce((acc, curr) => (curr inobj && (acc[curr] = obj[curr]),
acc), {});

// pick({ ‘a’: 1, ‘b’: ‘2’, ‘c’: 3 }, [‘a’, ‘c’]) -> { ‘a’: 1, ‘c’:
3 }

pull

对原始数组进行变异, 以筛选出指定的值。

使用Array.filter()和Array.includes()来拉出不需要的值。使用Array.length =
0可将传入的数组中的长度重置为零, 并将其设置为Array.push(),
以便仅使用所提取的值填充它。

const pull = (arr, …args) => {

let pulled = arr.filter((v, i) => !args.includes(v));

arr.length = 0; pulled.forEach(v => arr.push(v));

};

// let myArray = [‘a’, ‘b’, ‘c’, ‘a’, ‘b’, ‘c’];

// pull(myArray, ‘a’, ‘c’);

// console.log(myArray) -> [ ‘b’, ‘b’ ]

remove

从数组中移除给定函数返回false的元素. 使用Array.filter()查找返回 truthy
值的数组元素和Array.reduce()以使用Array.splice()删除元素。使用三参数
(func value, index, array调用函数).

const remove = (arr, func) =>

Array.isArray(arr) ? arr.filter(func).reduce((acc, val) => {

arr.splice(arr.indexOf(val), 1); returnacc.concat(val);

}, [])

: [];

// remove([1, 2, 3, 4], n => n % 2 == 0) -> [2, 4]

sample

返回数组中的随机元素。

使用Math.random()生成一个随机数, 将它与length相乘,
并使用数学将其舍入到最接近的整数Math.floor()。此方法也适用于字符串。

const sample = arr => arr[Math.floor(Math.random() * arr.length)];

// sample([3, 7, 9, 11]) -> 9

shuffle

随机数组值的顺序。

使用Array.sort()可在比较器中使用Math.random()重新排序元素。

const shuffle = arr => arr.sort(() => Math.random() – 0.5);

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

similarity

返回两个数组中都显示的元素的数组。

使用filter()可删除不属于values的值, 使用includes()确定.

const similarity = (arr, values) => arr.filter(v =>
values.includes(v));

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

symmetricDifference

返回两个数组之间的对称差。

从每个数组创建一个Set, 然后对它们中的每一个都使用Array.filter(),
以便只保留其他值中不包含的数值。

const symmetricDifference = (a, b) => {

const sA = newSet(a), sB = newSet(b);

return[…a.filter(x => !sB.has(x)), …b.filter(x =>
!sA.has(x))];

}

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

tail

返回数组中的所有元素, 除第一个。

如果数组的length大于1, 则返回arr.slice(1), 否则返回整个数组。

const tail = arr => arr.length > 1 ? arr.slice(1) : arr;

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

// tail([1]) -> [1]

take

返回一个数组, 其中 n 个元素从开始处移除。

使用Array.slice()创建数组的切片, 其中包含从开始处取出的n元素。

const take = (arr, n = 1) => arr.slice(0, n);

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

// take([1, 2, 3], 0) -> []

takeRight

返回一个数组, 其中 n 个元素从末尾移除。

使用Array.slice()创建数组的切片, 其中包含从末尾取出的n元素。

const takeRight = (arr, n = 1) => arr.slice(arr.length – n,
arr.length);

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

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

union

返回在两个数组中的任意一个中存在的每个元素。

创建一个Set, 其中包含a和b的所有值, 并将其转换为数组。

const union = (a, b) => Array.from(newSet([…a, …b]));

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

without

筛选出数组中具有指定值之一的元素。

使用Array.filter()创建不包括的数组 (使用!Array.includes()) 所有给定值。

const without = (arr, …args) => arr.filter(v =>
!args.includes(v));

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

zip

创建基于原始数组中的位置分组的元素数组。

使用Math.max.apply()获取参数中最长的数组。创建一个以该长度为返回值的数组,
并使用 map 函数创建一个分组元素的数组Array.from()如果参数数组的长度不同,
则在未找到任何值的情况下使用undefined。

const zip = (…arrays) => {

const maxLength = Math.max(…arrays.map(x => x.length));

returnArray.from({length: maxLength}).map((_, i) => {

returnArray.from({length: arrays.length}, (_, k) =>
arrays[k][i]);

})

}

//zip([‘a’, ‘b’], [1, 2], [true, false]); -> [[‘a’, 1,
true], [‘b’, 2, false]]

//zip([‘a’], [1, 2], [true, false]); -> [[‘a’, 1, true],
[undefined, 2, false]]

浏览器

bottomVisible

如果页的底部可见, 则返回true, 否则为false。

使用scrollY、scrollHeight和clientHeight来确定页面底部是否可见。

const bottomVisible = () =>

document.documentElement.clientHeight + window.scrollY >=
document.documentElement.scrollHeight ||
document.documentElement.clientHeight;

// bottomVisible() -> true

currentURL

返回当前 URL。

使用window.location.href获取当前 URL。

const currentURL = () => window.location.href;

// currentUrl() ->
‘https://google.com’

elementIsVisibleInViewport

如果指定的元素在视区中可见, 则返回true, 否则为false。

使用Element.getBoundingClientRect()和window.inner(Width|Height)值以确定给定元素在视区中是否可见。省略第二个参数以确定该元素是否完全可见,
或指定true以确定它是否部分可见。

const elementIsVisibleInViewport = (el, partiallyVisible = false) =>
{

const { top, left, bottom, right } = el.getBoundingClientRect();

returnpartiallyVisible

? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom <
innerHeight)) &&

((left > 0 && left < innerWidth) || (right > 0 && right <
innerWidth))

: top >= 0 && left >= 0 && bottom <= innerHeight && right <=
innerWidth;

};

// e.g. 100×100 viewport and a 10x10px element at position {top: -1,
left: 0, bottom: 9, right: 10}

// elementIsVisibleInViewport(el) -> false (not fully visible)

// elementIsVisibleInViewport(el, true) -> true (partially visible)

getScrollPosition

返回当前页的滚动位置。

如果已定义, 则使用pageXOffset和pageYOffset,
否则scrollLeft和scrollTop。可以省略el以使用window的默认值.

const getScrollPosition = (el = window) =>

({x: (el.pageXOffset !== undefined) ? el.pageXOffset : el.scrollLeft,

y: (el.pageYOffset !== undefined) ? el.pageYOffset : el.scrollTop});

// getScrollPosition() -> {x: 0, y: 200}

getURLParameters

返回一个包含当前 URL 参数的对象。

使用match()与适当的正则表达式来获取所有键值对,Array.reduce()可将它们映射并合并到单个对象中。将location.search作为要应用于当前url的参数传递.

const getURLParameters = url =>

url.match(/([^?=&]+)(=([^&]*))/g).reduce(

(a, v) => (a[v.slice(0, v.indexOf(‘=’))] = v.slice(v.indexOf(‘=’) +
1), a), {}

);

//
getURLParameters(‘http://url.com/page?name=Adam&surname=Smith’)
-> {name: ‘Adam’, surname: ‘Smith’}

redirect

重定向到指定的 URL。

使用window.location.href或window.location.replace()重定向到url。传递第二个参数以模拟链接单击
(true-默认值) 或 HTTP 重定向 (false).

const redirect = (url, asLink = true) =>

asLink ? window.location.href = url : window.location.replace(url);

//
redirect(‘https://google.com’)

scrollToTop

平滑滚动到页面顶部。

使用document.documentElement.scrollTop或document.body.scrollTop从顶部获取距离。从顶部的距离的一小部分滚动。使用window.requestAnimationFrame()对滚动进行动画处理。

const scrollToTop = () => {

const c = document.documentElement.scrollTop || document.body.scrollTop;

if(c > 0) {

window.requestAnimationFrame(scrollToTop);

window.scrollTo(0, c – c / 8);

}

};

// scrollToTop()

日期

getDaysDiffBetweenDates

返回两个日期之间的差异 (以天为值)。

计算Date对象之间的差异 (以天为)。

const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
(dateFinal – dateInitial) / (1000 * 3600 * 24);

// getDaysDiffBetweenDates(new Date(“2017-12-13”), new
Date(“2017-12-22”)) -> 9

JSONToDate

将 JSON 对象转换为日期。

使用Date(), 将 JSON 格式的日期转换为可读格式 (dd/mm/yyyy日)).

const JSONToDate = arr => {

const dt = newDate(parseInt(arr.toString().substr(6)));

return`${ dt.getDate() }/${ dt.getMonth() + 1 }/${ dt.getFullYear() }`

};

// JSONToDate(/Date(1489525200000)/) -> “14/3/2017”

toEnglishDate

将日期从美国格式转换为英文格式。

使用Date.toISOString()、split(‘T’)和replace()将日期从美式格式转换为英文格式。如果传递的时间不能转换为日期,
则抛出错误。

const toEnglishDate  = (time) =>

{try{returnnewDate(time).toISOString().split(‘T’)[0].replace(/-/g,
‘/’)}catch(e){return}};

// toEnglishDate(’09/21/2010′) -> ’21/09/2010′

功能

chainAsync

链异步函数。

循环遍历包含异步事件的函数数组, 当每个异步事件完成时调用next。

const chainAsync = fns => { let curr = 0; const next = () =>
fns[curr++](next); next(); };

/*

chainAsync([

  next => { console.log(‘0 seconds’); setTimeout(next, 1000); },

  next => { console.log(‘1 second’);  setTimeout(next, 1000); },

  next => { console.log(‘2 seconds’); }

])

compose

执行从右向左的函数组合。

使用Array.reduce()执行从右向左的函数组合。最后一个 (最右边)
的函数可以接受一个或多个参数;其余的函数必须是一元的。

const compose = (…fns) => fns.reduce((f, g) => (…args) =>
f(g(…args)));

/*

const add5 = x => x + 5

const multiply = (x, y) => x * y

const multiplyAndAdd5 = compose(add5, multiply)

multiplyAndAdd5(5, 2) -> 15

*/

curry

Curries a function.

使用递归。如果提供的参数 (变量) 的数量足够, 请调用传递的函数args
f。否则, 返回需要其余参数的扩充函数f。如果你想咖喱一个函数,
接受可变数目的参数 (如Math.min()),
可以选择将参数的个数传递到第二个参数arity(可变函数).

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

functionName

记录函数的名称。

使用console.debug()和传递的方法的name属性将方法的名称记录到控制台的debug通道中。

const functionName = fn => (console.debug(fn.name), fn);

// functionName(Math.max) -> max (logged in debug channel of console)

pipe

执行从左向右的函数组合。

使用Array.reduce()与扩展运算符 (…) 执行从左向右的函数组合。第一个
(最左边的) 函数可以接受一个或多个参数;其余的函数必须是一元的。

const pipeFunctions = (…fns) => fns.reduce((f, g) => (…args)
=> g(f(…args)));

/*

const add5 = x => x + 5

const multiply = (x, y) => x * y

const multiplyAndAdd5 = pipeFunctions(multiply, add5)

multiplyAndAdd5(5, 2) -> 15

*/

promisify

转换异步函数以返回一个承诺。

使用讨好返回一个返回调用原始函数的Promise的函数。使用…rest运算符传入所有参数。
在节点 8 + 中, 可以使用 util.promisify

const promisify = func =>

(…args) =>

newPromise((resolve, reject) =>

func(…args, (err, result) =>

err ? reject(err) : resolve(result))

);

// const delay = promisify((d, cb) => setTimeout(cb, d))

// delay(2000).then(() => console.log(‘Hi!’)) -> Promise resolves
after 2s

runPromisesInSeries

运行一系列的承诺系列。

使用Array.reduce()创建一个承诺链, 每个承诺在解决时返回下一个承诺。

const runPromisesInSeries = ps => ps.reduce((p, next) =>
p.then(next), Promise.resolve());

// const delay = (d) => new Promise(r => setTimeout(r, d))

// runPromisesInSeries([() => delay(1000), () => delay(2000)])
-> executes each promise sequentially, taking a total of 3 seconds to
complete

sleep

延迟异步函数的执行。

延迟执行async函数的一部分, 将其放入休眠状态, 返回Promise.

const sleep = ms => newPromise(resolve => setTimeout(resolve,
ms));

/*

async function sleepyWork() {

  console.log(‘I’m going to sleep for 1 second.’);

  await sleep(1000);

  console.log(‘I woke up after 1 second.’);

}

*/

数学

arrayAverage

返回数字数组的平均值。

使用Array.reduce()将每个值添加到累加器中, 并以0的值初始化,
除以数组的length。

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

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

arraySum

返回一个数字数组的总和。

使用Array.reduce()将每个值添加到累加器中, 并以0值初始化.

const arraySum = arr => arr.reduce((acc, val) => acc + val, 0);

// arraySum([1,2,3,4]) -> 10

发表评论

电子邮件地址不会被公开。 必填项已用*标注