30s内即可理解的JavaScript片段

本文翻译自30-seconds-of-code,可以理解为高阶函数片段。原作者说是30s内就可以理解,而我每一个都看了几分钟。

Adapter

ary

创建一个最多接受n个参数的函数,多余的参数将被忽略。

使用Array.prototype.slice(0,n)和扩展运算符(...),调用提供的函数发fn,并且最多有n个参数。

1
2
3
4
const ary = (fn, n) => (...args) => fn(...args.slice(0, n));
// EXAMPLES
const firstTwoMax = ary(Math.max, 2);
[[2, 6, 'a'], [8, 4, 6], [10]].map(x => firstTwoMax(...x)); // [6, 8, 10]

call

接受一个键值和一组参数,在给定上下文时调用它们。主要用于组合。

使用闭包在调用的时候存储键值和参数。

1
2
3
4
5
6
7
8
9
const call = (key, ...args) => context => context[key](...args);
// EXAMPLES
Promise.resolve([1, 2, 3])
.then(call('map', x => 2 * x))
.then(console.log); //[ 2, 4, 6 ] 能想到这么写的绝对是个人才
const map = call.bind(null, 'map'); // bind不会立即执行,call、apply才会
Promise.resolve([1, 2, 3])
.then(map(x => 2 * x))
.then(console.log); //[ 2, 4, 6 ]

collectInto

将接受数组的函数变为接受变量的函数。

给定一个函数,返回一个闭包,该闭包将所有输入收集到一个接受数组的函数中。

1
2
3
4
5
6
7
const collectInto = fn => (...args) => fn(args);
// EXAMPLES
const Pall = collectInto(Promise.all.bind(Promise));
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = new Promise(resolve => setTimeout(resolve, 2000, 3)); // 这么写很精髓
Pall(p1, p2, p3).then(console.log); // [1, 2, 3] (after about 2 seconds)

flip

flip将函数作为参数,然后把闭包里的第一个参数作为最后一个参数。

返回一个可以接受变量输入的闭包,并展开剩余参数,之后将其变为第一个参数。

1
2
3
4
5
6
7
8
9
const flip = fn => (first, ...rest) => fn(...rest, first);
// EXAMPLES
let a = { name: 'John Smith' };
let b = {};
const mergeFrom = flip(Object.assign);
let mergePerson = mergeFrom.bind(null, a);
mergePerson(b); // == b
b = {};
Object.assign(b, a); // == b

over

创建一个函数,调用参数中所有的函数并返回结果。

使用Array.prototype.map()Function.prototype.apply()将每个函数应用于给定的参数。

1
2
3
4
const over = (...fns) => (...args) => fns.map(fn => fn.apply(null, args));
// EXAMPLES
const minMax = over(Math.min, Math.max);
minMax(1, 2, 3, 4, 5); // [1,5]

overArgs

创建一个函数,调用参数中的函数并将闭包中的参数按提供的函数转换。

使用Array.prototype.map()与扩展运算符(...)将转换后的参数传递给fn

1
2
3
4
5
6
const overArgs = (fn, transforms) => (...args) => fn(...args.map((val, i) => transforms[i](val)));
// EXAMPLES
const square = n => n * n;
const double = n => n * 2;
const fn = overArgs((x, y) => [x, y], [square, double]);
fn(9, 3); // [81, 6]

pipeAsyncFunctions

这个函数实在是太复杂了,稍后再译。

Performs left-to-right function composition for asynchronous functions.

Use Array.prototype.reduce() with the spread operator (...) to perform left-to-right function composition using Promise.then(). The functions can return a combination of: simple values, Promise‘s, or they can be defined as async ones returning through await. All functions must be unary.

1
2
3
4
5
6
7
8
9
10
11
const pipeAsyncFunctions = (...fns) => arg => fns.reduce((p, f) => p.then(f), Promise.resolve(arg));
// EXAMPLES
const sum = pipeAsyncFunctions(
x => x + 1,
x => new Promise(resolve => setTimeout(() => resolve(x + 2), 1000)),
x => x + 3,
async x => (await x) + 4
);
(async () => {
console.log(await sum(5)); // 15 (after one second)
})();

pipeFunctions

按照从左到右的函数顺序执行。

借助Array.prototype.reduce()与扩展运算符(...)实现从左到右的函数合成。第一个(最左边的)函数可以接受一个或多个参数,其他的函数必须是一个参数。

1
2
3
4
5
6
const pipeFunctions = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
// EXAMPLES
const add5 = x => x + 5;
const multiply = (x, y) => x * y;
const multiplyAndAdd5 = pipeFunctions(multiply, add5);
multiplyAndAdd5(5, 2); // 15

promisify

转换异步函数并返回promise。

返回一个调用原始函数后的返回promise的函数。使用...rest运算符传递所有参数。

在Node 8+,你可以使用util.promify

1
2
3
4
5
6
7
const promisify = func => (...args) =>
new Promise((resolve, reject) =>
func(...args, (err, result) => (err ? reject(err) : resolve(result)))
);
// EXAMPLES
const delay = promisify((d, cb) => setTimeout(cb, d));
delay(2000).then(() => console.log('Hi!')); // // Promise resolves after 2s

rearg

创建一个调用所提供函数的函数,其参数按照指定的索引排列。

使用Array.prototype.map( )基于indexes对参数重新排序,并结合扩展运算符(…)将转换后的参数传递给fn

1
2
3
4
5
6
7
8
9
const rearg = (fn, indexes) => (...args) => fn(...indexes.map(i => args[i]));
// EXAMPLES
var rearged = rearg(
function(a, b, c) {
return [a, b, c];
},
[2, 0, 1]
);
rearged('b', 'c', 'a'); // ['a', 'b', 'c']

spreadOver

接受一个函数并返回一个闭包,该闭包接受一组参数并映射到函数的变量。

使用闭包和扩展运算符(...)将数组映射为函数的变量。

1
2
3
4
const spreadOver = fn => argsArr => fn(...argsArr);
// EXAMPLES
const arrayMax = spreadOver(Math.max);
arrayMax([1, 2, 3]); // 3

unary

创建一个只接受一个参数的函数,忽略任何其他参数。

调用提供的函数fn,只传入一个参数。

1
2
3
const unary = fn => val => fn(val);
// EXAMPLES
['6', '8', '10'].map(unary(parseInt)); // [6, 8, 10]

Array

all

如果数组中的所有元素执行给定的函数都返回true,则返回true,否则返回false

使用Array.prototype.very()遍历数组中的所有元素是否执行fn返回true。如果省略第二个参数fn,将使用Boolean函数作为默认值。

1
2
3
4
const all = (arr, fn = Boolean) => arr.every(fn);
// EXAMPLES
all([4, 2, 3], x => x > 1); // true
all([1, 2, 3]); // true

allEqual

检查数组中的所有元素是否相等。

使用Array.prototype.very()检查数组中的所有元素是否与第一个元素相同。

1
2
3
4
const allEqual = arr => arr.every(val => val === arr[0]);
// EXAMPLES
allEqual([1, 2, 3, 4, 5, 6]); // false
allEqual([1, 1, 1, 1]); // true

any

如果数组中至少有一个元素执行给定的函数都返回true,则返回true,否则返回false

使用Array.prototype.some()遍历集合中是否有元素调用fn返回true。省略第二个参数fn,将使用Boolean函数作为默认值。

1
2
3
4
const any = (arr, fn = Boolean) => arr.some(fn);
// EXAMPLES
any([0, 1, 2, 0], x => x >= 2); // true
any([0, 0, 1, 0]); // true

arrayToCSV

将二维数组转换为逗号分隔值(CSV)字符串。

使用Array.prototype.map()Array.prototype.join(delimiter)将单个一维数组(行)组合成字符串。使用Array.prototype.join("\n")将所有行组合成一个CSV字符串,用换行符分隔每行。省略第二个参数delimiter,将使用,作为默认分隔符。

1
2
3
4
5
const arrayToCSV = (arr, delimiter = ',') =>
arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
// EXAMPLES
arrayToCSV([['a', 'b'], ['c', 'd']]); // '"a","b"\n"c","d"'
arrayToCSV([['a', 'b'], ['c', 'd']], ';'); // '"a";"b"\n"c";"d"'

bifurcate

将值分成两组。如果filter函数中的元素是真的,则数组中的相应元素属于第一组;否则,它属于第二组。

使用Array.prototype.reduce()Array.prototype.push()根据filter函数将元素添加到组中。

1
2
3
4
const bifurcate = (arr, filter) =>
arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[], []]); // js括号语法,返回后面的值,写法很6啊
// EXAMPLES
bifurcate(['beep', 'boop', 'foo', 'bar'], [true, true, false, true]); // [ ['beep', 'boop', 'bar'], ['foo'] ]

bifurcateBy

根据给定的函数将值分成两组,该函数可以指定输入数组中的元素属于哪个组。如果函数返回真,则集合元素属于第一组;否则,它属于第二组。

每个元素调用fn得到返回值,使用Array.prototype.reduce()Array.prototype.push()将元素添加到组中。

1
2
3
4
const bifurcateBy = (arr, fn) =>
arr.reduce((acc, val, i) => (acc[fn(val, i) ? 0 : 1].push(val), acc), [[], []]);
// EXAMPLES
bifurcateBy(['beep', 'boop', 'foo', 'bar'], x => x[0] === 'b'); // [ ['beep', 'boop', 'bar'], ['foo'] ]

chunk

将数组分成指定长度的多维数组。

使用Array.from()创建一个新的数组,该数组的长度等于要生成的块的长度。使用Array.prototype.slice()将新数组的元素映射到一个长度为给定长度的块。如果原数组不能被平均分割,则最后一块将包含剩余的元素。

1
2
3
4
5
6
const chunk = (arr, size) =>
Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
// EXAMPLES
chunk([1, 2, 3, 4, 5], 2); // [[1,2],[3,4],[5]]

compact

从数组中删除无效值。

使用Array.prototype.filter()过滤掉无效值(false, null, 0, "", undefined, NaN)

1
2
3
const compact = arr => arr.filter(Boolean);
// EXAMPLES
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]); // [ 1, 2, 3, 'a', 's', 34 ]

countBy

根据给定函数对数组中的元素进行分组,并返回每个组中元素总数。

使用Array.prototype.map()返回数组元素调用函数或属性后的值。使用Array.prototype.reduce()创建一个对象,其中键是根据前面的值生成的。

1
2
3
4
5
6
7
8
const countBy = (arr, fn) =>
arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => {
acc[val] = (acc[val] || 0) + 1;
return acc;
}, {});
// EXAMPLES
countBy([6.1, 4.2, 6.3], Math.floor); // {4: 1, 6: 2}
countBy(['one', 'two', 'three'], 'length'); // {3: 2, 5: 1}

countOccurrences

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

使用Array.prototype.reduce(),每当遇到数组中的特定值时,递增计数器。

1
2
3
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
// EXAMPLES
countOccurrences([1, 1, 2, 1, 2, 3], 1); // 3

deepFlatten

将数组展平。

借助递归。使用Array.prototype.concat结合空数组([])和扩展运算符(...)来展平数组。递归展平作用于数组的每个元素。

1
2
3
const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
// EXAMPLES
deepFlatten([1, [2], [[3], 4], 5]); // [1,2,3,4,5]

difference

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

b创建一个Set,然后在a上使用Array.prototype.filter()来只保留b中不包含的值。

1
2
3
4
5
6
const difference = (a, b) => {
const s = new Set(b);
return a.filter(x => !s.has(x));
};
// EXAMPLES
difference([1, 2, 3], [1, 2, 4]); // [3]

differenceBy

将给定的函数应用于两个数组的每个数组元素后,返回两个数组之间的差异。

通过对b中的每个元素调用fn来创建一个Set,然后将Array.protototype.filter()a中元素调用fn的结果结合使用,只保留先前创建的集合中不包含的值。

1
2
3
4
5
6
7
const differenceBy = (a, b, fn) => {
const s = new Set(b.map(fn));
return a.filter(x => !s.has(fn(x)));
};
// EXAMPLES
differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); // [1.2]
differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], v => v.x); // [ { x: 2 } ]

differenceWith

过滤掉数组中比较函数不返回true的所有值。

使用Array.prototype.filter()Array.prototype.findindex()查找适当的值。

1
2
3
const differenceWith = (arr, val, comp) => arr.filter(a => val.findIndex(b => comp(a, b)) === -1);
// EXAMPLES
differenceWith([1, 1.2, 1.5, 3, 0], [1.9, 3, 0], (a, b) => Math.round(a) === Math.round(b)); // [1, 1.2]

drop

返回一个从左边移除n个元素后的新数组。

使用Array.prototype.slice()从左侧移除指定数量的元素。

1
2
3
4
5
const drop = (arr, n = 1) => arr.slice(n);
// EXAMPLES
drop([1, 2, 3]); // [2,3]
drop([1, 2, 3], 2); // [3]
drop([1, 2, 3], 42); // []

dropRight

返回一个从右边移除n个元素后的新数组。

使用Array.prototype.slice()从右侧移除指定数量的元素。

1
2
3
4
5
const drop = (arr, n = 1) => arr.slice(0, -n);
// EXAMPLES
dropRight([1, 2, 3]); // [1,2]
dropRight([1, 2, 3], 2); // [1]
dropRight([1, 2, 3], 42); // []

dropRightWhile

从数组末尾移除元素,直到传递的函数返回true。返回数组中的剩余元素。

使用Array.protototype.slice()循环数组,直到函数返回值为true。返回剩余元素。

1
2
3
4
5
6
const dropRightWhile = (arr, func) => {
while (arr.length > 0 && !func(arr[arr.length - 1])) arr = arr.slice(0, -1);
return arr;
};
// EXAMPLES
dropRightWhile([1, 2, 3, 4], n => n < 3); // [1, 2]

dropWhile

从数组开头移除元素,直到传递的函数返回true。返回数组中的剩余元素。

循环数组,使用Array.prototype.slice()删除数组的第一个元素,直到函数返回值为true。返回剩余元素。

1
2
3
4
5
6
const dropWhile = (arr, func) => {
while (arr.length > 0 && !func(arr[0])) arr = arr.slice(1);
return arr;
};
// EXAMPLES
dropWhile([1, 2, 3, 4], n => n >= 3); // [3,4]

everyNth

返回数组中顺序为nth倍数的元素。

使用Array.prototype.filter()创建一个新数组,该数组包含给定数组中顺序为nth倍数的元素。

1
2
3
const everyNth = (arr, nth) => arr.filter((e, i) => i % nth === nth - 1);
// EXAMPLES
everyNth([1, 2, 3, 4, 5, 6], 2); // [ 2, 4, 6 ]

filterNonUnique

过滤掉数组中的非唯一值。

使用Array.prototype.filter()创建包含唯一值的数组。

1
2
3
const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
// EXAMPLES
filterNonUnique([1, 2, 2, 3, 4, 4, 5]); // [1, 3, 5]

filterNonUniqueBy

基于提供的比较器函数过滤掉数组中的非唯一值。

基于比较器函数fn,对只包含唯一值的数组使用Array.prototype.filter()Array. prototype.every()。比较器函数接受四个参数:两个被比较元素的值及其索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
const filterNonUniqueBy = (arr, fn) =>
arr.filter((v, i) => arr.every((x, j) => (i === j) === fn(v, x, i, j)));
// EXAMPLES
filterNonUniqueBy(
[
{ id: 0, value: 'a' },
{ id: 1, value: 'b' },
{ id: 2, value: 'c' },
{ id: 1, value: 'd' },
{ id: 0, value: 'e' }
],
(a, b) => a.id == b.id
); // [ { id: 2, value: 'c' } ]

findLast

返回调用所给函数返回真值的最后一个元素。

使用Array.prototype.filter()过滤fn返回假值的元素,使用Array.prototype.pop()获取最后一个元素。

1
2
3
const findLast = (arr, fn) => arr.filter(fn).pop();
// EXAMPLES
findLast([1, 2, 3, 4], n => n % 2 === 1); // 3

findLastIndex

返回调用所给函数返回真值的最后一个元素的索引。

使用Array.prototype.map()将每个元素与其索引和值映射到一个数组。使用Array.prototype.filter()过滤fn返回假值的元素,使用Array.prototype.pop()获取最后一个元素。

1
2
3
4
5
6
7
const findLastIndex = (arr, fn) =>
arr
.map((val, i) => [i, val])
.filter(([i, val]) => fn(val, i, arr))
.pop()[0];
// EXAMPLES
findLastIndex([1, 2, 3, 4], n => n % 2 === 1); // 2 (index of the value 3)

flatten

将数组展平到指定的深度。

使用递归,对于每一深度级别,深度递减1。使用Array.prototype.reduce()Array.prototype.concat()合并元素或数组。一般情况下,如果深度等于1则停止递归。省略第二个参数,depth仅展平到深度1 (单个展平)。

1
2
3
4
5
6
// 理解起来有点困难
const flatten = (arr, depth = 1) =>
arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), []);
// EXAMPLES
flatten([1, [2], 3, 4]); // [1, 2, 3, 4]
flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8]

forEachRight

从数组的最后一个元素开始,为每个数组元素执行给定的函数。

使用Array.protototype.slice(0)克隆给定的数组,使用Array.protototype.reverse()反转数组,使用Array.protototype.foreach()循环反向数组。

1
2
3
4
5
6
7
const forEachRight = (arr, callback) =>
arr
.slice(0)
.reverse()
.forEach(callback);
// EXAMPLES
forEachRight([1, 2, 3, 4], val => console.log(val)); // '4', '3', '2', '1'

groupBy

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

使用Array.prototype.map()对数组元素调用到函数或属性。使用Array.protototype .reduce()创建一个对象,其中的键是根据前面结果生成的。

1
2
3
4
5
6
7
8
const groupBy = (arr, fn) =>
arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val, i) => {
acc[val] = (acc[val] || []).concat(arr[i]);
return acc;
}, {});
// EXAMPLES
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']}

返回数组的第一个元素。

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

1
2
3
const head = arr => arr[0];
// EXAMPLES
head([1, 2, 3]); // 1

indexOfAll

返回数组中val出现的所有索引。如果val从未出现,返回[]

使用Array.prototype.reduce()循环元素并存储匹配元素的索引。返回索引数组。

1
2
3
4
const indexOfAll = (arr, val) => arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), []);
// EXAMPLES
indexOfAll([1, 2, 3, 1, 2, 3], 1); // [0,3]
indexOfAll([1, 2, 3], 4); // []

initial

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

使用arr.slice(0, -1)来返回除最后一个元素以外的所有元素。

1
2
3
const initial = arr => arr.slice(0, -1);
// EXAMPLES
initial([1, 2, 3]); // [1,2]

initialize2DArray

生成指定行列和值的二维数组。

使用Array.prototype.map()生成h行长度为w并填入值的数组。如果没有给定值,则默认为null

1
2
3
4
const initialize2DArray = (w, h, val = null) =>
Array.from({ length: h }).map(() => Array.from({ length: w }).fill(val));
// EXAMPLES
initialize2DArray(2, 2, 0); // [[0,0], [0,0]]

initializeArrayWithRange

初始化一个数组,该数组包含指定范围内的数字,并且按照指定步长递增。

使用Array.from()创建所需长度的数组(结束-开始+1)/步长,并在给定范围内用所需值填充数组。可以省略开始值默认是0。也可以省略步长来使用默认值1。

1
2
3
4
5
6
const initializeArrayWithRange = (end, start = 0, step = 1) =>
Array.from({ length: Math.ceil((end - start + 1) / step) }, (v, i) => i * step + start);
// EXAMPLES
initializeArrayWithRange(5); // [0,1,2,3,4,5]
initializeArrayWithRange(7, 3); // [3,4,5,6,7]
initializeArrayWithRange(9, 0, 2); // [0,2,4,6,8]

initializeArrayWithRangeRight

初始化一个数组,该数组包含指定范围内的数字(反向),并且按照指定步长递减。

使用Array.from(Math.ceil((end+1-start)/step))创建所需长度的数组(长度等于(结束-开始)/步长或(结束+1-开始)/步长 (包含结束)),并在给定范围内用所需值填充数组。可以省略开始值默认是0。也可以省略步长来使用默认值1。

1
2
3
4
5
6
7
8
const initializeArrayWithRangeRight = (end, start = 0, step = 1) =>
Array.from({ length: Math.ceil((end + 1 - start) / step) }).map(
(v, i, arr) => (arr.length - i - 1) * step + start
);
// EXAMPLES
initializeArrayWithRangeRight(5); // [5,4,3,2,1,0]
initializeArrayWithRangeRight(7, 3); // [7,6,5,4,3]
initializeArrayWithRangeRight(9, 0, 2); // [8,6,4,2,0]

initializeArrayWithValues

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

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

1
2
3
const initializeArrayWithValues = (n, val = 0) => Array(n).fill(val);
// EXAMPLES
initializeArrayWithValues(5, 2); // [2, 2, 2, 2, 2]

initializeNDArray

创建具有给定值的n维数组。

使用递归。用Array.prototype.map()生成行数组,其中每一行都是使用initializendaray初始化的新数组。

1
2
3
4
5
6
7
const initializeNDArray = (val, ...args) =>
args.length === 0
? val
: Array.from({ length: args[0] }).map(() => initializeNDArray(val, ...args.slice(1)));
// EXAMPLES
initializeNDArray(1, 3); // [1,1,1]
initializeNDArray(5, 2, 2, 2); // [[[5,5],[5,5]],[[5,5],[5,5]]]

intersection

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

b创建一个集合,然后在a上使用Array.prototype.filter()来只保留b中包含的值。

1
2
3
4
5
6
const intersection = (a, b) => {
const s = new Set(b);
return a.filter(x => s.has(x));
};
// EXAMPLES
intersection([1, 2, 3], [4, 3, 2]); // [2, 3]

intersectionBy

将给定的函数应用于两个数组中的每个数组元素后,返回执行函数后第一个数组和第二个数组中相同的值。

b中的所有元素执行fn后创建一个集合,然后在a上使用Array.prototype.filter()来保留将执行fn后结果在b中的元素。

1
2
3
4
5
6
const intersectionBy = (a, b, fn) => {
const s = new Set(b.map(fn));
return a.filter(x => s.has(fn(x)));
};
// EXAMPLES
intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); // [2.1]

intersectionWith

使用给定的函数返回两个数组中都通过的元素数组。

使用Array.prototype.filter()Array.prototype.findindex()与所提供的函数结合使用来确定公共值。

1
2
3
const intersectionWith = (a, b, comp) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1);
// EXAMPLES
intersectionWith([1, 1.2, 1.5, 3, 0], [1.9, 3, 0, 3.9], (a, b) => Math.round(a) === Math.round(b)); // [1.5, 3, 0]

isSorted

如果数组按升序排序,则返回1;如果数组按降序排序,则返回-1;如果数组未排序,则返回0。

计算前两个元素的排序方向。使用Object.entries()循环数组元素并成对比较它们。如果方向改变,返回0;如果到达最后一个元素,返回排序方向。

1
2
3
4
5
6
7
8
9
10
11
12
const isSorted = arr => {
let direction = -(arr[0] - arr[1]);
for (let [i, val] of arr.entries()) {
direction = !direction ? -(arr[i - 1] - arr[i]) : direction;
if (i === arr.length - 1) return !direction ? 0 : direction;
else if ((val - arr[i + 1]) * direction > 0) return 0;
}
};
// EXAMPLES
isSorted([0, 1, 2, 2]); // 1
isSorted([4, 3, 2]); // -1
isSorted([4, 3, 5]); // 0

join

将数组中的所有元素连接成一个字符串并返回该字符串。使用起始分隔符和末端分隔符。

使用Array.prototype.reduce()将元素组合成一个字符串。省略第二个参数separator时,将使用,作为默认分隔符。省略第三个参数end时,默认情况下使用与separator相同的值分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const join = (arr, separator = ',', end = separator) =>
arr.reduce(
(acc, val, i) =>
i === arr.length - 2
? acc + val + end
: i === arr.length - 1
? acc + val
: acc + val + separator,
''
);
// EXAMPLES
join(['pen', 'pineapple', 'apple', 'pen'], ',', '&'); // "pen,pineapple,apple&pen"
join(['pen', 'pineapple', 'apple', 'pen'], ','); // "pen,pineapple,apple,pen"
join(['pen', 'pineapple', 'apple', 'pen']); // "pen,pineapple,apple,pen"

JSONtoCSV

将对象数组转换为仅包含指定columns的逗号分隔值(CSV)字符串。

使用Array.prototype.join(分隔符)columns中的所有名称组合起来,创建第一行。使用Array.prototype.map()Array.prototype.reduce()为每个对象创建一行,用空字符串替换不存在的值,只映射columns中的值。使用Array.prototype.join('\n')将所有行组合成一个字符串。省略第三个参数delimiter,将会使用默认分隔符,

1
2
3
4
5
6
7
8
9
10
11
12
13
const JSONtoCSV = (arr, columns, delimiter = ',') =>
[
columns.join(delimiter),
...arr.map(obj =>
columns.reduce(
(acc, key) => `${acc}${!acc.length ? '' : delimiter}"${!obj[key] ? '' : obj[key]}"`,
''
)
)
].join('\n');
// EXAMPLES
JSONtoCSV([{ a: 1, b: 2 }, { a: 3, b: 4, c: 5 }, { a: 6 }, { b: 7 }], ['a', 'b']); // 'a,b\n"1","2"\n"3","4"\n"6",""\n"","7"'
JSONtoCSV([{ a: 1, b: 2 }, { a: 3, b: 4, c: 5 }, { a: 6 }, { b: 7 }], ['a', 'b'], ';'); // 'a;b\n"1";"2"\n"3";"4"\n"6";""\n"";"7"'

last

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

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

1
2
3
const last = arr => arr[arr.length - 1];
// EXAMPLES
last([1, 2, 3]); // 3

longestItem

获取任意数量的可迭代对象或具有length属性的对象,并返回最长的对象。如果多个对象具有相同的长度,则将返回第一个对象。如果未提供参数,则返回undefined

使用Array.prototype.reduce(),比较对象的长度以找到最长的对象。

1
2
3
4
5
6
7
const longestItem = (...vals) => vals.reduce((a, x) => (x.length > a.length ? x : a));
// EXAMPLES
longestItem('this', 'is', 'a', 'testcase'); // 'testcase'
longestItem(...['a', 'ab', 'abc']); // 'abc'
longestItem(...['a', 'ab', 'abc'], 'abcd'); // 'abcd'
longestItem([1, 2, 3], [1, 2], [1, 2, 3, 4, 5]); // [1, 2, 3, 4, 5]
longestItem([1, 2, 3], 'foobar'); // 'foobar'

mapObject

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

使用匿名内部函数声明未定义的内存空间,使用闭包存储返回值。使用一个新数组存储数组,新数组包含函数在传入数组上的映射,并使用逗号运算符返回第二步,而无需从一个上下文移动到另一个上下文(由于闭包和操作顺序)。

1
2
3
4
5
6
7
const mapObject = (arr, fn) =>
(a => (
(a = [arr, arr.map(fn)]), a[0].reduce((acc, val, ind) => ((acc[val] = a[1][ind]), acc), {})
))();
// EXAMPLES
const squareIt = arr => mapObject(arr, a => a * a);
squareIt([1, 2, 3]); // { 1: 1, 2: 4, 3: 9 }

maxN

从提供的数组中返回n个最大的元素。如果n大于或等于提供的数组长度,则返回原始数组(按降序排序)。

Array.prototype.sort()与扩展运算符(...)结合使用浅拷贝数组并按降序排序。使用Array.prototype.slice()获取指定数量的元素。省略第二个参数n,得到只有一个元素的数组。

1
2
3
4
const maxN = (arr, n = 1) => [...arr].sort((a, b) => b - a).slice(0, n);
// EXAMPLES
maxN([1, 2, 3]); // [3]
maxN([1, 2, 3], 2); // [3,2]

minN

从提供的数组中返回n个最小的元素。如果n大于或等于提供的数组长度,则返回原始数组(按升序排序)。

Array.prototype.sort()与扩展运算符(...)结合使用浅拷贝数组并按升序排序。使用Array.prototype.slice()获取指定数量的元素。省略第二个参数n,得到只有一个元素的数组。

1
2
3
4
const minN = (arr, n = 1) => [...arr].sort((a, b) => a - b).slice(0, n);
// EXAMPLES
minN([1, 2, 3]); // [1]
minN([1, 2, 3], 2); // [1,2]

none

如果集合中的所有元素执行给定的函数后都返回false,则返回true,否则返回false。

使用Array.prototype.some()测试集合中的任何元素是否基于fn返回true。省略第二个参数fn,将使用Boolean作为默认值。

1
2
3
4
const none = (arr, fn = Boolean) => !arr.some(fn);
// EXAMPLES
none([0, 1, 3, 0], x => x == 2); // true
none([0, 0, 0]); // true

nthElement

返回数组的第n个元素。

使用Array.prototype.slice()首先获得包含第n个元素的数组。如果索引超出范围,则返回undefined。省略第二个参数n,取数组的第一个元素。

1
2
3
4
onst nthElement = (arr, n = 0) => (n === -1 ? arr.slice(n) : arr.slice(n, n + 1))[0];
// EXAMPLES
nthElement(['a', 'b', 'c'], 1); // 'b'
nthElement(['a', 'b', 'b'], -3); // 'a'

offset

将指定数量的元素移动到数组的末尾。

使用两次Array.prototype.slice()获得指定索引之后的元素和指定索引之前的元素。使用扩展运算符(...)将两者组合成一个数组。如果offset为负数,则元素将从尾部移动到头部。

1
2
3
4
const offset = (arr, offset) => [...arr.slice(offset), ...arr.slice(0, offset)];
// EXAMPLES
offset([1, 2, 3, 4, 5], 2); // [3, 4, 5, 1, 2]
offset([1, 2, 3, 4, 5], -2); // [4, 5, 1, 2, 3]

partition

根据每个元素执行函数后返回的布尔值,将元素分成两类数组。

使用Array.prototype.reduce()创建一个由两个数组组成的数组。使用Array.prototype.push()fn返回true的元素添加到第一个数组,将fn返回false的元素添加到第二个数组。

1
2
3
4
5
6
7
8
9
10
11
const partition = (arr, fn) =>
arr.reduce(
(acc, val, i, arr) => {
acc[fn(val, i, arr) ? 0 : 1].push(val);
return acc;
},
[[], []]
);
// EXAMPLES
const users = [{ user: 'barney', age: 36, active: false }, { user: 'fred', age: 40, active: true }];
partition(users, o => o.active); // [[{ 'user': 'fred', 'age': 40, 'active': true }],[{ 'user': 'barney', 'age': 36, 'active': false }]]

permutations

警告:这个函数的执行时间随着数组元素的增加呈指数增长。当浏览器试图计算所有不同的组合时,超过8到10个条目都会导致浏览器挂起。

生成数组元素的所有组合(包含重复元素)。

使用递归。对给定数组中进行元素遍历,为剩余元素创建所有的组合。使用Array.prototype.map()将该元素与剩余部分排列组合起来,然后使用Array.prototype.reduce()将所有排列组合成一个数组。一般情况是数组长度为2或1。

1
2
3
4
5
6
7
8
9
10
11
12
const permutations = arr => {
if (arr.length <= 2) return arr.length === 2 ? [arr, [arr[1], arr[0]]] : arr;
return arr.reduce(
(acc, item, i) =>
acc.concat(
permutations([...arr.slice(0, i), ...arr.slice(i + 1)]).map(val => [item, ...val])
),
[]
);
};
// EXAMPLES
permutations([1, 33, 5]); // [ [ 1, 33, 5 ], [ 1, 5, 33 ], [ 33, 1, 5 ], [ 33, 5, 1 ], [ 5, 1, 33 ], [ 5, 33, 1 ] ]

pull

变异原数组以过滤掉指定的值。

使用Array.prototype.filter()Array.prototype.include()提取不需要的值。使用Array.protototype.length = 0将将数组的长度重置为零来对数组中传递的值进行变异,并使用Array.prototype.push()重新填充该值。

(对于不改变原始数组的代码片段,请参见without)

1
2
3
4
5
6
7
8
9
const pull = (arr, ...args) => {
let argState = Array.isArray(args[0]) ? args[0] : args;
let pulled = arr.filter((v, i) => !argState.includes(v));
arr.length = 0;
pulled.forEach(v => arr.push(v));
};
// EXAMPLES
let myArray = ['a', 'b', 'c', 'a', 'b', 'c'];
pull(myArray, 'a', 'c'); // myArray = [ 'b', 'b' ]

pullAtIndex

变异原数组以筛选出指定索引处的值。

使用Array.prototype.filter()Array.prototype.include()提取不需要的值。使用Array.protototype.length = 0将数组的长度重置为零来对数组中传递的值进行变异,并使用Array.prototype.push()重新填充该值。使用Array.prototype.push()来跟踪所提取的值。

1
2
3
4
5
6
7
8
9
10
11
12
const pullAtIndex = (arr, pullArr) => {
let removed = [];
let pulled = arr
.map((v, i) => (pullArr.includes(i) ? removed.push(v) : v))
.filter((v, i) => !pullArr.includes(i));
arr.length = 0;
pulled.forEach(v => arr.push(v));
return removed;
};
// EXAMPLES
let myArray = ['a', 'b', 'c', 'd'];
let pulled = pullAtIndex(myArray, [1, 3]); // myArray = [ 'a', 'c' ] , pulled = [ 'b', 'd' ]

pullAtValue

变异原数组以过滤掉指定的值。返回删除的元素。

使用Array.prototype.filter()Array.prototype.include()提取不需要的值。使用Array.protototype.length = 0将数组的长度重置为零来对数组中传递的值进行变异,并使用Array.prototype.push()重新填充该值。使用Array.prototype.push()来跟踪所提取的值。

1
2
3
4
5
6
7
8
9
10
11
const pullAtValue = (arr, pullArr) => {
let removed = [],
pushToRemove = arr.forEach((v, i) => (pullArr.includes(v) ? removed.push(v) : v)),
mutateTo = arr.filter((v, i) => !pullArr.includes(v));
arr.length = 0;
mutateTo.forEach(v => arr.push(v));
return removed;
};
// EXAMPLES
let myArray = ['a', 'b', 'c', 'd'];
let pulled = pullAtValue(myArray, ['b', 'd']); // myArray = [ 'a', 'c' ] , pulled = [ 'b', 'd' ]

pullBy

基于给定的迭代函数,对原始数组进行变异以过滤掉指定的值。

检查函数中是否提供了最后一个参数。使用Array.prototype.map()将迭代函数fn应用于所有数组元素。使用Array.prototype.filter()Array.prototype.include()提取不需要的值。使用Array.protototype.length = 0将数组的长度重置为零来对数组中传递的值进行变异,并使用Array.prototype.push()重新填充该值。

1
2
3
4
5
6
7
8
9
10
11
12
const pullBy = (arr, ...args) => {
const length = args.length;
let fn = length > 1 ? args[length - 1] : undefined;
fn = typeof fn == 'function' ? (args.pop(), fn) : undefined;
let argState = (Array.isArray(args[0]) ? args[0] : args).map(val => fn(val));
let pulled = arr.filter((v, i) => !argState.includes(fn(v)));
arr.length = 0;
pulled.forEach(v => arr.push(v));
};
// EXAMPLES
var myArray = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 1 }];
pullBy(myArray, [{ x: 1 }, { x: 3 }], o => o.x); // myArray = [{ x: 2 }]

reducedFilter

基于条件过滤对象数组,同时过滤掉未指定的键。

使用Array.prototype.filter()根据函数fn过滤数组,返回满足条件的对象。在过滤后的数组上,使用Array.prototype.map()返回新对象,使用Array.prototype.reduce()过滤掉未作为键参数提供的键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const reducedFilter = (data, keys, fn) =>
data.filter(fn).map(el =>
keys.reduce((acc, key) => {
acc[key] = el[key];
return acc;
}, {})
);
// EXAMPLES
const data = [
{
id: 1,
name: 'john',
age: 24
},
{
id: 2,
name: 'mike',
age: 50
}
];

reducedFilter(data, ['id', 'name'], item => item.age > 24); // [{ id: 2, name: 'mike'}]

reduceSuccessive

对累加器和数组中的每个元素应用一个函数(从左到右),返回一个值变大的数组。

使用Array.prototype.reduce()将给定的函数应用于数组,存储每个新的结果。

1
2
3
4
const reduceSuccessive = (arr, fn, acc) =>
arr.reduce((res, val, i, arr) => (res.push(fn(res.slice(-1)[0], val, i, arr)), res), [acc]);
// EXAMPLES
reduceSuccessive([1, 2, 3, 4, 5, 6], (acc, val) => acc + val, 0); // [0, 1, 3, 6, 10, 15, 21]

reduceWhich

应用提供的函数设置比较规则后,返回数组的最小值/最大值。

Array.prototype.reduce()comparator函数结合使用,在数组中取得对应的元素。可以省略第二个参数,使用返回数组中最小元素作为默认参数。

1
2
3
4
5
6
7
8
9
10
// 简直精髓
const reduceWhich = (arr, comparator = (a, b) => a - b) =>
arr.reduce((a, b) => (comparator(a, b) >= 0 ? b : a));
// EXAMPLES
reduceWhich([1, 3, 2]); // 1
reduceWhich([1, 3, 2], (a, b) => b - a); // 3
reduceWhich(
[{ name: 'Tom', age: 12 }, { name: 'Jack', age: 18 }, { name: 'Lucy', age: 9 }],
(a, b) => a.age - b.age
); // {name: "Lucy", age: 9}

reject

接收函数和数组,使用Array.prototype.Filter(),但仅在pred(x) = false时保留x

1
2
3
4
const reject = (pred, array) => array.filter((...args) => !pred(...args));
// EXAMPLES
reject(x => x % 2 === 0, [1, 2, 3, 4, 5]); // [1, 3, 5]
reject(word => word.length > 4, ['Apple', 'Pear', 'Kiwi', 'Banana']); // ['Pear', 'Kiwi']

remove

从数组中移除给定函数返回false的元素。

使用Array.prototype.filter()查找返回真值的数组元素,使用Array.prototype.splice()删除元素。函数是用三个参数(值、索引、数组)调用的。

1
2
3
4
5
6
7
8
9
const remove = (arr, func) =>
Array.isArray(arr)
? arr.filter(func).reduce((acc, val) => {
arr.splice(arr.indexOf(val), 1);
return acc.concat(val);
}, [])
: [];
// EXAMPLES
remove([1, 2, 3, 4], n => n % 2 === 0); // [2, 4]

sample

从数组中返回随机元素

使用Math.random()生成一个随机数,并乘上数组长度,然后使用Math.floor()向下取整。这个方法也适用于字符串。

1
2
3
const sample = arr => arr[Math.floor(Math.random() * arr.length)];
// EXAMPLES
sample([3, 7, 9, 11]); // 9

sampleSize

从数组中以唯一键获取n个随机元素,直到数组结束。

使用Fisher-Yates算法洗牌数组。使用Array.prototype.slice()获得前n个元素。省略第二个参数n,从数组中获取第一个元素。

1
2
3
4
5
6
7
8
9
10
11
const sampleSize = ([...arr], n = 1) => {
let m = arr.length;
while (m) {
const i = Math.floor(Math.random() * m--);
[arr[m], arr[i]] = [arr[i], arr[m]];
}
return arr.slice(0, n);
};
// EXAMPLES
sampleSize([1, 2, 3], 2); // [3,1]
sampleSize([1, 2, 3], 4); // [2,3,1]

shank

Array.prototype.splice()功能相同,但是返回一个新的数组,而不是改变原始数组。

在移除现有元素/添加新元素后,使用Array.prototype.slice()Array.prototype .concat()获得包含新内容的新数组。省略第二个参数index,则从0开始。省略第三个参数DelCount,不删除元素。省略第四个参数元素,不添加新元素。

1
2
3
4
5
6
7
8
9
10
const shank = (arr, index = 0, delCount = 0, ...elements) =>
arr
.slice(0, index)
.concat(elements)
.concat(arr.slice(index + delCount));
// EXAMPLES
const names = ['alpha', 'bravo', 'charlie'];
const namesAndDelta = shank(names, 1, 0, 'delta'); // [ 'alpha', 'delta', 'bravo', 'charlie' ]
const namesNoBravo = shank(names, 1, 1); // [ 'alpha', 'charlie' ]
console.log(names); // ['alpha', 'bravo', 'charlie']

shuffle

随机排列数组值的顺序,返回一个新数组。

使用Fisher-Yates算法对数组元素重新排序。

1
2
3
4
5
6
7
8
9
10
11
const shuffle = ([...arr]) => {
let m = arr.length;
while (m) {
const i = Math.floor(Math.random() * m--);
[arr[m], arr[i]] = [arr[i], arr[m]];
}
return arr;
};
// EXAMPLES
const foo = [1, 2, 3];
shuffle(foo); // [2, 3, 1], foo = [1, 2, 3]

similarity

返回出现在两个数组中的元素数组。

使用Array.prototype.filter()过滤非公共值,这些值是由Array.prototype.include()确定的。

1
2
3
const similarity = (arr, values) => arr.filter(v => values.includes(v));
// EXAMPLES
similarity([1, 2, 3], [1, 2, 4]); // [1, 2]

sortedIndex

返回将值插入数组后仍保持其排序顺序的索引。

检查数组是否按降序排序。使用Array.prototype.findIndex()找到元素应该插入的索引。

1
2
3
4
5
6
7
8
const sortedIndex = (arr, n) => {
const isDescending = arr[0] > arr[arr.length - 1];
const index = arr.findIndex(el => (isDescending ? n >= el : n <= el));
return index === -1 ? arr.length : index;
};
// EXAMPLES
sortedIndex([5, 3, 2, 1], 4); // 1
sortedIndex([30, 50], 40); // 1

sortedIndexBy

基于提供的函数,返回将值插入数组后仍保持其排序顺序的索引。

检查数组是否按降序排序。根据函数fn,使用Array.prototype.findIndex()找到元素应该插入的适当索引。

1
2
3
4
5
6
7
8
const sortedIndexBy = (arr, n, fn) => {
const isDescending = fn(arr[0]) > fn(arr[arr.length - 1]);
const val = fn(n);
const index = arr.findIndex(el => (isDescending ? val >= fn(el) : val <= fn(el)));
return index === -1 ? arr.length : index;
};
// EXAMPLES
sortedIndexBy([{ x: 4 }, { x: 5 }], { x: 4 }, o => o.x); // 0

sortedLastIndex

返回最后索引,在该索引处,值插入数组保持其排序顺序。

检查数组是否按降序排序。使用Array.prototype.reverse()Array.prototype.findIndex()找到元素应该插入的最后一个索引。

1
2
3
4
5
6
7
const sortedLastIndex = (arr, n) => {
const isDescending = arr[0] > arr[arr.length - 1];
const index = arr.reverse().findIndex(el => (isDescending ? n <= el : n >= el));
return index === -1 ? 0 : arr.length - index;
};
// EXAMPLES
sortedLastIndex([10, 20, 30, 30, 40], 30); // 4

sortedLastIndexBy

基于提供的函数,返回当值插入数组后保持其排序顺序的最后索引。

检查数组是否按降序排序。使用Array.prototype.map()将函数应用于数组的所有元素。使用Array.prototype.reverse()Array.prototype.findIndex()根据提供的函数,找到元素应该插入的最后一个索引。

1
2
3
4
5
6
7
8
9
10
11
const sortedLastIndexBy = (arr, n, fn) => {
const isDescending = fn(arr[0]) > fn(arr[arr.length - 1]);
const val = fn(n);
const index = arr
.map(fn)
.reverse()
.findIndex(el => (isDescending ? val <= el : val >= el));
return index === -1 ? 0 : arr.length - index;
};
// EXAMPLES
sortedLastIndexBy([{ x: 4 }, { x: 5 }], { x: 4 }, o => o.x); // 1

stableSort

对数组执行排序,当项目的值相同时,保留项目的初始索引。不改变原始数组,返回一个新数组。

使用Array.prototype.map()将输入数组的每个元素与其对应的索引配对。使用Array.prototype.sort()compare函数对数组进行排序,如果比较的项目相等,则保留它们的初始顺序。使用Array.prototype.map()转换回初始数组。

1
2
3
4
5
6
7
8
const stableSort = (arr, compare) =>
arr
.map((item, index) => ({ item, index }))
.sort((a, b) => compare(a.item, b.item) || a.index - b.index)
.map(({ item }) => item);
// EXAMPLES
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const stable = stableSort(arr, () => 0); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

symmetricDifference

返回两个数组之间的不同的值,没有滤掉重复值。

每个数组创建一个Set,然后在每个数组上使用Array.prototype.filter()来保留另一个数组中不包含的值。

1
2
3
4
5
6
7
8
const symmetricDifference = (a, b) => {
const sA = new Set(a),
sB = new Set(b);
return [...a.filter(x => !sB.has(x)), ...b.filter(x => !sA.has(x))];
};
// EXAMPLES
symmetricDifference([1, 2, 3], [1, 2, 4]); // [3, 4]
symmetricDifference([1, 2, 2], [1, 3, 1]); // [2, 2, 3]

symmetricDifferenceBy

将给定的函数应用于两个数组的每个元素后,返回两个数组之间不同的值。

每个数组元素执行fn后创建一个集合,然后在每个元素上使用Array.prototype.filter()来只保留另一个元素中不包含的值。

1
2
3
4
5
6
7
const symmetricDifferenceBy = (a, b, fn) => {
const sA = new Set(a.map(v => fn(v))),
sB = new Set(b.map(v => fn(v)));
return [...a.filter(x => !sB.has(fn(x))), ...b.filter(x => !sA.has(fn(x)))];
};
// EXAMPLES
symmetricDifferenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); // [ 1.2, 3.4 ]

symmetricDifferenceWith

使用提供的函数作为比较器,返回两个数组之间的不同的值。

使用Array.prototype.filter()Array.prototype.findIndex()查找适当的值。

1
2
3
4
5
6
7
8
9
10
const symmetricDifferenceWith = (arr, val, comp) => [
...arr.filter(a => val.findIndex(b => comp(a, b)) === -1),
...val.filter(a => arr.findIndex(b => comp(a, b)) === -1)
];
// EXAMPLES
symmetricDifferenceWith(
[1, 1.2, 1.5, 3, 0],
[1.9, 3, 0, 3.9],
(a, b) => Math.round(a) === Math.round(b)
); // [1, 1.2, 3.9]

tail

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

如果数组长度大于1,则返回Array.prototype.slice(1),否则返回整个数组。

1
2
3
4
const tail = arr => (arr.length > 1 ? arr.slice(1) : arr);
// EXAMPLES
tail([1, 2, 3]); // [2,3]
tail([1]); // [1]

take

返回从开头移除n个元素后的数组。

使用Array.prototype.slice()返回第n个元素后的元素浅拷贝。

1
2
3
4
const take = (arr, n = 1) => arr.slice(0, n);
// EXAMPLES
take([1, 2, 3], 5); // [1, 2, 3]
take([1, 2, 3], 0); // []

takeRight

返回从末尾数n个元素后的数组。

使用Array.prototype.slice()返回从末尾数n个元素后的元素浅拷贝。

1
2
3
4
const takeRight = (arr, n = 1) => arr.slice(arr.length - n, arr.length);
// EXAMPLES
takeRight([1, 2, 3], 2); // [ 2, 3 ]
takeRight([1, 2, 3]); // [3]

takeRightWhile

从数组末尾移除元素,直到传递的函数返回true。返回移除的元素。

当函数返回false值时,使用Array.prototype.reduceRight()循环遍历数组并累积元素。

1
2
3
4
const takeRightWhile = (arr, func) =>
arr.reduceRight((acc, el) => (func(el) ? acc : [el, ...acc]), []); // reduceRight 从右到左遍历
// EXAMPLES
takeRightWhile([1, 2, 3, 4], n => n < 3); // [3, 4]

takeWhile

正向移除数组中的元素,直到传递的函数返回true。返回移除的元素。

使用for...of循环数组直到函数返回值为true。使用Array.prototype.slice()返回移除的元素。

1
2
3
4
5
6
const takeWhile = (arr, func) => {
for (const [i, val] of arr.entries()) if (func(val)) return arr.slice(0, i);
return arr;
};
// EXAMPLES
takeWhile([1, 2, 3, 4], n => n >= 3); // [1, 2]

toHash

将给定的类数组转化为哈希表(键控数据存储)。

给定一个可迭代的或类似数组的结构,在提供的对象上调用Array.prototype.reduce.call (),跨过它并返回一个对象,该对象由引用值键入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const toHash = (object, key) =>
Array.prototype.reduce.call(
object,
(acc, data, index) => ((acc[!key ? index : data[key]] = data), acc),
{}
);
// EXAMPLES
toHash([4, 3, 2, 1]); // { 0: 4, 1: 3, 2: 2, 3: 1 }
toHash([{ a: 'label' }], 'a'); // { label: { a: 'label' } }
// A more in depth example:
let users = [{ id: 1, first: 'Jon' }, { id: 2, first: 'Joe' }, { id: 3, first: 'Moe' }];
let managers = [{ manager: 1, employees: [2, 3] }];
// We use function here because we want a bindable reference, but a closure referencing the hash would work, too.
managers.forEach(
manager =>
(manager.employees = manager.employees.map(function(id) {
return this[id];
}, toHash(users, 'id')))
);
managers; // [ { manager:1, employees: [ { id: 2, first: "Joe" }, { id: 3, first: "Moe" } ] } ]

union

返回两个数组中不重复的元素。

ab创建一个Set,并转换为数组。

1
2
3
const union = (a, b) => Array.from(new Set([...a, ...b]));
// EXAMPLES
union([1, 2, 3], [4, 3, 2]); // [1,2,3,4]

unionBy

将给定的函数应用于两个数组中的每个数组元素后,返回两个数组中不重复的元素。

通过将fn应用于a的所有值来创建Set。在执行fn后,从a和b中的所有元素创建一个集合,这些元素的值与先前创建的集合中的值不匹配。返回转换为数组的最后一个集合。

1
2
3
4
5
6
const unionBy = (a, b, fn) => {
const s = new Set(a.map(fn));
return Array.from(new Set([...a, ...b.filter(x => !s.has(fn(x)))]));
};
// EXAMPLES
unionBy([2.1], [1.2, 2.3], Math.floor); // [2.1, 1.2]

unionWith

使用提供的比较器函数,返回两个数组中不重复的元素。

创建一个Set,其中包含比较器在a中找不到匹配项的所有a值和b值,使用Array.prototype.findIndex()

1
2
3
4
const unionWith = (a, b, comp) =>
Array.from(new Set([...a, ...b.filter(x => a.findIndex(y => comp(x, y)) === -1)]));
// EXAMPLES
unionWith([1, 1.2, 1.5, 3, 0], [1.9, 3, 0, 3.9], (a, b) => Math.round(a) === Math.round(b)); // [1, 1.2, 1.5, 3, 0, 3.9]

uniqueElements

返回数组的所有唯一值。

使用ES6Set...rest运算符丢弃所有重复的值。

1
2
3
const uniqueElements = arr => [...new Set(arr)];
// EXAMPLES
uniqueElements([1, 2, 2, 3, 4, 4, 5]); // [1, 2, 3, 4, 5]

uniqueElementsBy

基于提供的比较器函数,返回数组的所有唯一值。

使用Array.prototype.reduce()Array.prototype.some(),根据比较器函数fn,返回满足条件的第一次出现的元素。比较器函数有两个参数:正在比较的两个元素的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const uniqueElementsBy = (arr, fn) =>
arr.reduce((acc, v) => {
if (!acc.some(x => fn(v, x))) acc.push(v);
return acc;
}, []);
// EXAMPLES
uniqueElementsBy(
[
{ id: 0, value: 'a' },
{ id: 1, value: 'b' },
{ id: 2, value: 'c' },
{ id: 1, value: 'd' },
{ id: 0, value: 'e' }
],
(a, b) => a.id == b.id
); // [ { id: 0, value: 'a' }, { id: 1, value: 'b' }, { id: 2, value: 'c' } ]

uniqueElementsByRight

基于提供的比较器函数,返回数组的所有唯一值。

使用Array.prototype.reduceRight()Array.prototype.some(),根据比较器函数fn,返回满足条件的第一次出现的元素。比较器函数有两个参数:正在比较的两个元素的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const uniqueElementsByRight = (arr, fn) =>
arr.reduceRight((acc, v) => {
if (!acc.some(x => fn(v, x))) acc.push(v);
return acc;
}, []);
// EXAMPLES
uniqueElementsByRight(
[
{ id: 0, value: 'a' },
{ id: 1, value: 'b' },
{ id: 2, value: 'c' },
{ id: 1, value: 'd' },
{ id: 0, value: 'e' }
],
(a, b) => a.id == b.id
); // [ { id: 0, value: 'e' }, { id: 1, value: 'd' }, { id: 2, value: 'c' } ]

uniqueSymmetricDifference

返回两个数组之间不重复的值。

在每个数组上使用Array.prototype.filter()Array.prototype.includes()来删除另一个数组中包含的值,然后根据结果创建一个集合,删除重复的值。

1
2
3
4
5
6
const uniqueSymmetricDifference = (a, b) => [
...new Set([...a.filter(v => !b.includes(v)), ...b.filter(v => !a.includes(v))])
];
// EXAMPLES
uniqueSymmetricDifference([1, 2, 3], [1, 2, 4]); // [3, 4]
uniqueSymmetricDifference([1, 2, 2], [1, 3, 1]); // [2, 3]

unzip

创建二维数组,将zip生成的数组中的元素取消分组。

使用Math.max.apply()得到最长的数组长度,Array.prototype.map()生成数组。使用Array.prototype.reduce()Array.prototype.forEach()将分组值映射到单个数组。

1
2
3
4
5
6
7
8
9
10
const unzip = arr =>
arr.reduce(
(acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc),
Array.from({
length: Math.max(...arr.map(x => x.length))
}).map(x => [])
);
// EXAMPLES
unzip([['a', 1, true], ['b', 2, false]]); // [['a', 'b'], [1, 2], [true, false]]
unzip([['a', 1, true], ['b', 2]]); // [['a', 'b'], [1, 2], [true]]

unzipWith

创建一个元素数组,将zip生成的数组中的元素取消分组,并应用提供的函数。

使用Math.max.apply()得到最长的数组长度,Array.prototype.map()生成数组。使用Array.prototype.reduce()Array.prototype.forEach()将分组值映射到单个数组。使用Array.prototype.map()和扩展运算符(...)将fn应用于每个单独的元素数组。

1
2
3
4
5
6
7
8
9
10
11
const unzipWith = (arr, fn) =>
arr
.reduce(
(acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc),
Array.from({
length: Math.max(...arr.map(x => x.length))
}).map(x => [])
)
.map(val => fn(...val));
// EXAMPLES
unzipWith([[1, 10, 100], [2, 20, 200]], (...args) => args.reduce((acc, v) => acc + v, 0)); // [3, 30, 300]

without

筛选出数组中不是指定值的元素。

使用Array.prototype.filter()创建一个数组,排除(使用!Array.includes()方法)所有给定值。

(对于改变原始数组的代码片段,请参见pull

1
2
3
const without = (arr, ...args) => arr.filter(v => !args.includes(v));
// EXAMPLES
without([2, 1, 2, 3], 1, 2); // [3]

xProd

通过从数组中创建每个可能的对,在提供的两个数组中创建一个新数组。

使用Array.prototype.reduce(),Array.prototype.map()Array.prototype.concat()从两个数组的元素中产生每一个组合,并将它们保存在新数组中。

1
2
3
const xProd = (a, b) => a.reduce((acc, x) => acc.concat(b.map(y => [x, y])), []);
// EXAMPLES
xProd([1, 2], ['a', 'b']); // [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]

zip

创建元素数组,根据原始数组中的位置分组。

使用Math.max.apply()获取传入数组的最大长度。创建一个长度为最大长度的数组,并使用Array.from()和映射函数创建一个分组数组。如果参数数组的长度不同,则在没值的地方使用undefined

1
2
3
4
5
6
7
8
9
const zip = (...arrays) => {
const maxLength = Math.max(...arrays.map(x => x.length));
return Array.from({ length: maxLength }).map((_, i) => {
return Array.from({ length: arrays.length }, (_, k) => arrays[k][i]);
});
};
// EXAMPLES
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]]

zipObject

给定有效的属性数组和值数组,返回一个将属性与值相关联的对象。

由于对象可以有未定义的值,但不能有未定义的属性,属性数组使用Array. prototype.reduce()来决定结果对象的结构。

1
2
3
4
5
const zipObject = (props, values) =>
props.reduce((obj, prop, index) => ((obj[prop] = values[index]), obj), {});
// EXAMPLES
zipObject(['a', 'b', 'c'], [1, 2]); // {a: 1, b: 2, c: undefined}
zipObject(['a', 'b'], [1, 2, 3]); // {a: 1, b: 2}

zipWith

创建一个数组,根据原始数组中的位置进行分组,并使用函数作为最后一个值来指定分组值的组合方式。

检查提供的最后一个参数是否是函数。使用Math.max()获取参数中数组最大长度。创建一个长度为最大值的数组,并使用Array.from()和映射函数创建一个分组元素数组。如果参数数组的长度不同,则在无值的地方使用undefined。用每个组的元素调用该函数(...group)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const zipWith = (...array) => {
const fn = typeof array[array.length - 1] === 'function' ? array.pop() : undefined;
return Array.from(
{ length: Math.max(...array.map(a => a.length)) },
(_, i) => (fn ? fn(...array.map(a => a[i])) : array.map(a => a[i]))
);
};
// EXAMPLES
zipWith([1, 2], [10, 20], [100, 200], (a, b, c) => a + b + c); // [111,222]
zipWith(
[1, 2, 3],
[10, 20],
[100, 200],
(a, b, c) => (a != null ? a : 'a') + (b != null ? b : 'b') + (c != null ? c : 'c')
); // [111, 222, '3bc']

Browser

arrayToHtmlList

将给定数组元素转换为<li>标签,并将它们追加到给定id的列表中。

使用Array.prototype.map()document.querySelector()和匿名闭包函数创建html标记。

1
2
3
4
5
6
7
const arrayToHtmlList = (arr, listID) =>
(el => (
(el = document.querySelector('#' + listID)),
(el.innerHTML += arr.map(item => `<li>${item}</li>`).join(''))
))();
// EXAMPLES
arrayToHtmlList(['item 1', 'item 2'], 'myListID');

bottomVisible

如果页面到达底部,则返回true,否则返回false

使用scrollYscrollHeightclientHeight来确定页面是否到达底部。

1
2
3
4
5
const bottomVisible = () =>
document.documentElement.clientHeight + window.scrollY >=
(document.documentElement.scrollHeight || document.documentElement.clientHeight);
// EXAMPLES
bottomVisible(); // true

copyToClipboard

⚠️注意:通过使用新的剪贴板异步API,同样的功能可以更容易地实现,这个API仍然是实验性的,但是应该在将来使用,而不是这个片段。点击了解更多信息。

将字符串复制到剪贴板。仅当用户有操作才可用(例如,在点击事件中)。

创建一个<textarea>,用提供的数据填充它,并将其添加到HTML文档中。使用Selection.getRangeAt()存储所选范围(如果有)。使用document.execCommand('copy')复制到剪贴板。之后从HTML文档中删除元素。最后,使用Selection().addRange()以恢复原始选定范围(如果有)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const copyToClipboard = str => {
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
const selected =
document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
el.select();
document.execCommand('copy');
document.body.removeChild(el);
if (selected) {
document.getSelection().removeAllRanges();
document.getSelection().addRange(selected);
}
};
// EXAMPLES
copyToClipboard('Lorem ipsum'); // 'Lorem ipsum' copied to clipboard.

counter

为指定的选择器创建具有指定范围、步长和持续时间的计数器。

检查step正负是否正确,并相应地进行更改。将setInterval()Math.abs()Math.floor()结合使用,计算每次新文本绘制的时间。使用document.querySelector().innerHTML来更新元素的值。省略第四个参数step,使用默认值1。省略第五个参数duration,使用2000毫秒的默认持续时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
const counter = (selector, start, end, step = 1, duration = 2000) => {
let current = start,
_step = (end - start) * step < 0 ? -step : step,
timer = setInterval(() => {
current += _step;
document.querySelector(selector).innerHTML = current;
if (current >= end) document.querySelector(selector).innerHTML = end;
if (current >= end) clearInterval(timer);
}, Math.abs(Math.floor(duration / (end - start))));
return timer;
};
// EXAMPLES
counter('#my-id', 1, 1000, 5, 2000); // Creates a 2-second timer for the element with id="my-id"

createElement

根据字符串创建元素(不将其添加到文档)。如果给定字符串包含多个元素,则返回第一个元素。

使用document.createElement()创建一个新元素。将它的innerHTML设置为参数提供的字符串。使用ParentNode.firstElementChild返回字符串的元素版本。

1
2
3
4
5
6
7
8
9
10
11
12
const createElement = str => {
const el = document.createElement('div');
el.innerHTML = str;
return el.firstElementChild;
};
// EXAMPLES
const el = createElement(
`<div class="container">
<p>Hello!</p>
</div>`
);
console.log(el.className); // 'container'

createEventHub

使用emit、on和off方法创建发布/订阅事件中心。

使用Object.create(null)创建一个不从Object.prototype继承属性的空集线器对象。对于emit,请根据事件参数解析处理程序数组,然后通过将数据作为参数传入,使用Array.prototype.forEach ( )运行每个处理程序数组。对于on,如果事件尚不存在,请为其创建一个数组,然后使用Array.prototype.push ( )将处理程序添加到数组中。对于off,使用数组. prototype.findIndex ( )在事件数组中查找处理程序的索引,并使用数组. prototype.splice ( )将其移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const createEventHub = () => ({
hub: Object.create(null),
emit(event, data) {
(this.hub[event] || []).forEach(handler => handler(data));
},
on(event, handler) {
if (!this.hub[event]) this.hub[event] = [];
this.hub[event].push(handler);
},
off(event, handler) {
const i = (this.hub[event] || []).findIndex(h => h === handler);
if (i > -1) this.hub[event].splice(i, 1);
}
});
// EXAMPLES
const handler = data => console.log(data);
const hub = createEventHub();
let increment = 0;

// Subscribe: listen for different types of events
hub.on('message', handler);
hub.on('message', () => console.log('Message event fired'));
hub.on('increment', () => increment++);

// Publish: emit events to invoke all handlers subscribed to them, passing the data to them as an argument
hub.emit('message', 'hello world'); // logs 'hello world' and 'Message event fired'
hub.emit('message', { hello: 'world' }); // logs the object and 'Message event fired'
hub.emit('increment'); // `increment` variable is now 1

// Unsubscribe: stop a specific handler from listening to the 'message' event
hub.off('message', handler);

currentURL

返回当前网址。

使用window.location.href获取当前网址。

1
2
3
const currentURL = () => window.location.href;
// EXAMPLES
currentURL(); // 'https://google.com'

detectDeviceType

检测网站是在移动设备中打开还是在台式机/笔记本电脑中打开。

使用正则表达式匹配navigator.userAgent属性,以确定设备是移动设备还是台式机/笔记本电脑。

1
2
3
4
5
6
const detectDeviceType = () =>
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
? 'Mobile'
: 'Desktop';
// EXAMPLES
detectDeviceType(); // "Mobile" or "Desktop"

elementContains

如果父元素包含子元素,则返回true,否则返回false

检查父元素和子元素是否不同,使用parent.contains(child)检查父元素是否包含子元素。

1
2
3
4
const elementContains = (parent, child) => parent !== child && parent.contains(child);
// EXAMPLES
elementContains(document.querySelector('head'), document.querySelector('title')); // true
elementContains(document.querySelector('body'), document.querySelector('body')); // false

elementIsVisibleInViewport

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

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

1
2
3
4
5
6
7
8
9
10
11
12
const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
const { top, left, bottom, right } = el.getBoundingClientRect();
const { innerHeight, innerWidth } = window;
return partiallyVisible
? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
: top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};
// EXAMPLES
// e.g. 100x100 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)

getImages

从元素中提取所有图像,并将它们放入数组中

使用Element.prototype.GetElementsBytagName()获取所提供元素中的所有的<img>元素,Array.prototype.map()映射每个元素的src属性,然后创建一个集合来消除重复并返回数组。

1
2
3
4
5
6
7
const getImages = (el, includeDuplicates = false) => {
const images = [...el.getElementsByTagName('img')].map(img => img.getAttribute('src'));
return includeDuplicates ? images : [...new Set(images)];
};
// EXAMPLES
getImages(document, true); // ['image1.jpg', 'image2.png', 'image1.png', '...']
getImages(document, false); // ['image1.jpg', 'image2.png', '...']

getScrollPosition

返回当前页面的滚动位置。

如果有pageXOffsetpageYOffset则使用它们,否则使用scrollLeftscrollTop。省略el使用默认值window

1
2
3
4
5
6
const getScrollPosition = (el = window) => ({
x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
});
// EXAMPLES
getScrollPosition(); // {x: 0, y: 200}

getStyle

返回指定元素的CSS规则值。

使用window.getComputedStyle()获取指定元素的CSS规则值。

1
2
3
const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
// EXAMPLES
getStyle(document.querySelector('p'), 'font-size'); // '16px'

hasClass

如果元素具有指定的类名,则返回true,否则返回false

使用element.classList.contains()检查该元素是否具有指定的类名。

1
2
3
const hasClass = (el, className) => el.classList.contains(className);
// EXAMPLES
hasClass(document.querySelector('p.special'), 'special'); // true

hashBrowser

使用SHA-256算法为传入值创建哈希值。返回一个Promise

使用SubtleCrypto接口为给定值创建哈希。

1
2
3
4
5
6
7
8
9
10
const hashBrowser = val =>
crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(val)).then(h => {
let hexes = [],
view = new DataView(h);
for (let i = 0; i < view.byteLength; i += 4)
hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8));
return hexes.join('');
});
// EXAMPLES
hashBrowser(JSON.stringify({ a: 'a', b: [1, 2, 3, 4], foo: { c: 'bar' } })).then(console.log); // '04aa106279f5977f59f9067fa9712afc4aedc6f5862a8defc34552d8c7206393'

hide

隐藏所有指定的元素。

使用NodeList.prototype.forEach()对指定的每个元素应用display: none

1
2
3
const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
// EXAMPLES
hide(document.querySelectorAll('img')); // Hides all <img> elements on the page

httpsRedirect

如果页面当前是http协议,则将其重定向到https协议。另外,按下后退按钮并不会将它回到http协议页面,因为历史记录中网页已经被替换。

使用location.protocol获取当前正在使用的协议。如果不是HTTPS,使用location.replace()用页面的HTTPS版本替换现有页面。使用location.href获取完整地址,用String.prototype.split()将其拆分,并删除网址的协议部分。

1
2
3
4
5
const httpsRedirect = () => {
if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
};
// EXAMPLES
httpsRedirect(); // If you are on http://mydomain.com, you are redirected to https://mydomain.com

insertAfter

在指定元素的末尾插入一个HTML字符串。

使用参数为afterendel.insertAdjacentHTML()解析htmlString,并将其插入el的末尾。

1
2
3
const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);
// EXAMPLES
insertAfter(document.getElementById('myId'), '<p>after</p>'); // <div id="myId">...</div> <p>after</p>

insertBefore

在指定元素的开头插入一个HTML字符串。

使用参数为beforebeginel.insertAdjacentHTML()解析htmlString,并将其插入el的末尾。

1
2
3
const insertBefore = (el, htmlString) => el.insertAdjacentHTML('beforebegin', htmlString);
// EXAMPLES
insertBefore(document.getElementById('myId'), '<p>before</p>'); // <p>before</p> <div id="myId">...</div>

isBrowserTabFocused

如果当前页面在浏览器页面选项卡为聚焦状态,则返回true,否则返回false

使用Document.hidden属性来检查页面在浏览器选项卡中是可见的还是隐藏的。

1
2
3
const isBrowserTabFocused = () => !document.hidden;
// EXAMPLES
isBrowserTabFocused(); // true

nodeListToArray

NodeList转换为数组。

在新数组中使用扩展运算符将NodeList转换为数组。

1
2
3
const nodeListToArray = nodeList => [...nodeList];
// EXAMPLES
nodeListToArray(document.childNodes); // [ <!DOCTYPE html>, html ]

observeMutations

返回一个新的MutationObserver,并为指定元素上的每次变化提供回调。

使用MutationObserver观察给定元素的变化。使用Array.prototype.forEach()对观察到的每次变化执行回调。省略第三个参数选项,使用默认选项(全部为true)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const observeMutations = (element, callback, options) => {
const observer = new MutationObserver(mutations => mutations.forEach(m => callback(m)));
observer.observe(
element,
Object.assign(
{
childList: true,
attributes: true,
attributeOldValue: true,
characterData: true,
characterDataOldValue: true,
subtree: true
},
options
)
);
return observer;
};
// EXAMPLES
const obs = observeMutations(document, console.log); // Logs all mutations that happen on the page
obs.disconnect(); // Disconnects the observer and stops logging mutations on the page

off

从元素中移除事件侦听器。

使用EventTarget.removeEventListener()从元素中移除事件侦听器。省略第四个参数opts默认false,或者根据添加事件侦听器时使用的选项决定。

1
2
3
4
5
const off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts);
// EXAMPLES
const fn = () => console.log('!');
document.body.addEventListener('click', fn);
off(document.body, 'click', fn); // no longer logs '!' upon clicking on the page

on

将事件侦听器添加到能够使用事件委托的元素中。

使用EventTarget.addEventListener()将事件侦听器添加到元素中。如果options对象提供了target属性,请确保事件目标与指定的目标匹配,然后通过提供正确的this回调。返回对自定义委托器函数的引用,以便可以与off一起使用。省略opts默认为非委托行为和事件冒泡。

1
2
3
4
5
6
7
8
9
10
onst on = (el, evt, fn, opts = {}) => {
const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e);
el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false);
if (opts.target) return delegatorFn;
};
// EXAMPLES
const fn = () => console.log('!');
on(document.body, 'click', fn); // logs '!' upon clicking the body
on(document.body, 'click', fn, { target: 'p' }); // logs '!' upon clicking a `p` element child of the body
on(document.body, 'click', fn, { options: true }); // use capturing instead of bubbling

onUserInputChange

每当用户输入类型改变(mousetouch)时运行回调。根据输入设备启用/禁用代码非常有用。该过程是动态的,适用于混合设备(例如触摸屏笔记本电脑)。

使用两个事件监听器。最初认为mouse输入,并将touchstart事件侦听器绑定到文档。在touchstart上,添加一个mousemove事件侦听器,使用performance.now()监听20毫秒内连续触发的两个mousemove事件。在任何情况下,以输入类型作为参数运行回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
onst onUserInputChange = callback => {
let type = 'mouse',
lastTime = 0;
const mousemoveHandler = () => {
const now = performance.now();
if (now - lastTime < 20)
(type = 'mouse'), callback(type), document.removeEventListener('mousemove', mousemoveHandler);
lastTime = now;
};
document.addEventListener('touchstart', () => {
if (type === 'touch') return;
(type = 'touch'), callback(type), document.addEventListener('mousemove', mousemoveHandler);
});
};
// EXAMPLES
onUserInputChange(type => {
console.log('The user is now using', type, 'as an input method.');
});

prefix

返回浏览器支持的CSS属性的前缀版本(如果需要)。

在前缀字符串数组上使用Array.prototype.findIndex()来测试document.body是否在其CSSStyleDeclaration对象中定义了其中一个字符串,否则返回null。使用String.prototype.charAt()String.prototype.toUpperCase()将属性第一个字母大写,该属性将附加到前缀字符串数组上。

1
2
3
4
5
6
7
8
9
10
const prefix = prop => {
const capitalizedProp = prop.charAt(0).toUpperCase() + prop.slice(1);
const prefixes = ['', 'webkit', 'moz', 'ms', 'o'];
const i = prefixes.findIndex(
prefix => typeof document.body.style[prefix ? prefix + capitalizedProp : prop] !== 'undefined'
);
return i !== -1 ? (i === 0 ? prop : prefixes[i] + capitalizedProp) : null;
};
// EXAMPLES
prefix('appearance'); // 'appearance' on a supported browser, otherwise 'webkitAppearance', 'mozAppearance', 'msAppearance' or 'oAppearance'

recordAnimationFrames

在每个动画帧上调用提供的回调。

使用递归。定义变量runningtrue,持续执行window.requestAnimationFrame(),它调用所提供的回调。返回一个对象有startstop方法,以便手动控制。省略第二个参数autoStart,以便在调用函数时隐式调用start方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const recordAnimationFrames = (callback, autoStart = true) => {
let running = true,
raf;
const stop = () => {
running = false;
cancelAnimationFrame(raf);
};
const start = () => {
running = true;
run();
};
const run = () => {
raf = requestAnimationFrame(() => {
callback();
if (running) run();
});
};
if (autoStart) start();
return { start, stop };
};
// EXAMPLES
const cb = () => console.log('Animation frame fired');
const recorder = recordAnimationFrames(cb); // logs 'Animation frame fired' on each animation frame
recorder.stop(); // stops logging
recorder.start(); // starts again
const recorder2 = recordAnimationFrames(cb, false); // `start` needs to be explicitly called to begin recording frames

redirect

重定向到指定的网址。

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

1
2
3
4
onst redirect = (url, asLink = true) =>
asLink ? (window.location.href = url) : window.location.replace(url);
EXAMPLES
// redirect('https://google.com');

runAsync

通过使用Web Worker在单独的线程中运行函数,允许长时间运行的函数而不阻塞用户界面。

使用Blob对象URL创建一个新的Worker,其内容是所提供函数执行后的返回值。返回一个promise,监听onmessageonerror事件,解析从worker返回的数据,或者抛出一个错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const runAsync = fn => {
const worker = new Worker(
URL.createObjectURL(new Blob([`postMessage((${fn})());`]), {
type: 'application/javascript; charset=utf-8'
})
);
return new Promise((res, rej) => {
worker.onmessage = ({ data }) => {
res(data), worker.terminate();
};
worker.onerror = err => {
rej(err), worker.terminate();
};
});
};
// EXAMPLES
const longRunningFunction = () => {
let result = 0;
for (let i = 0; i < 1000; i++)
for (let j = 0; j < 700; j++) for (let k = 0; k < 300; k++) result = result + i + j + k;

return result;
};
/*
NOTE: Since the function is running in a different context, closures are not supported.
The function supplied to `runAsync` gets stringified, so everything becomes literal.
All variables and functions must be defined inside.
*/
runAsync(longRunningFunction).then(console.log); // 209685000000
runAsync(() => 10 ** 3).then(console.log); // 1000
let outsideVariable = 50;
runAsync(() => typeof outsideVariable).then(console.log); // 'undefined'

scrollToTop

平滑滚动到页面顶部。

使用document.documentElement.ScrollTopdocument.body.scrollTop获取到顶部的距离。使用window.requestAnimationFrame()执行动画滚动。

1
2
3
4
5
6
7
8
9
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop;
if (c > 0) {
window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, c - c / 8);
}
};
// EXAMPLES
scrollToTop();

setStyle

为指定元素设置CSS规则的值。

使用element.style将指定元素的CSS规则值设置为val

1
2
3
const setStyle = (el, ruleName, val) => (el.style[ruleName] = val);
// EXAMPLES
setStyle(document.querySelector('p'), 'font-size', '20px'); // The first <p> element on the page will have a font-size of 20px

show

显示所有指定的元素。

使用扩展运算符(...)和Array.prototype.forEach()来清除指定的每个元素的display属性。

1
2
3
const show = (...el) => [...el].forEach(e => (e.style.display = ''));
// EXAMPLES
show(...document.querySelectorAll('img')); // Shows all <img> elements on the page

smoothScroll

将调用它的元素平滑滚动到浏览器窗口的可见区域。

使用.scrollIntoView方法滚动元素。将{ behavior: 'smooth' }传递给.scrollIntoView,以便平滑滚动。

1
2
3
4
5
6
7
const smoothScroll = element =>
document.querySelector(element).scrollIntoView({
behavior: 'smooth'
});
// EXAMPLES
smoothScroll('#fooBar'); // scrolls smoothly to the element with the id fooBar
smoothScroll('.fooBar'); // scrolls smoothly to the first element with a class of fooBar

toggleClass

切换元素的类。

使用element.classList.toggle()为元素切换指定的类。

1
2
3
const toggleClass = (el, className) => el.classList.toggle(className);
// EXAMPLES
toggleClass(document.querySelector('p.special'), 'special'); // The paragraph will not have the 'special' class anymore

triggerEvent

触发给定元素上的特定事件,可选传递自定义数据。

使用new CustomEvent()按照指定的eventType和详细信息创建事件。使用el.dispatchEvent()在给定元素上触发新创建的事件。如果不想将自定义数据传递给触发事件,请省略第三个参数detail

1
2
3
4
5
const triggerEvent = (el, eventType, detail) =>
el.dispatchEvent(new CustomEvent(eventType, { detail }));
// EXAMPLES
triggerEvent(document.getElementById('myId'), 'click');
triggerEvent(document.getElementById('myId'), 'click', { username: 'bob' });

UUIDGeneratorBrowser

在浏览器中生成UUID。

使用crypto API生成UUID,并且符合RFC4122版本4。

1
2
3
4
5
6
onst UUIDGeneratorBrowser = () =>
([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
);
// EXAMPLES
UUIDGeneratorBrowser(); // '7982fcfe-5721-4632-bede-6000885be57d'

Date

dayOfYear

Date对象获取一年中的某一天的天数。

使用new Date()Date.prototype.getFullYear()获取一年中的第一天作为Date对象,将提供的date中减去该日期,然后除以每天的毫秒得出结果。使用Math.floor()将得到的天数向下取整为整数。

1
2
3
4
const dayOfYear = date =>
Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
// EXAMPLES
dayOfYear(new Date()); // 272

formatDuration

返回给定毫秒数的正常可读格式。

ms除以对应的值,获得dayhourminutesecondmillisecond。将Object.entries()Array.prototype.filter()一起使用,只保留非零值。使用Array.prototype.map()为每个值创建字符串。使用String.prototype.join(', ')将这些值组合成一个字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const formatDuration = ms => {
if (ms < 0) ms = -ms;
const time = {
day: Math.floor(ms / 86400000),
hour: Math.floor(ms / 3600000) % 24,
minute: Math.floor(ms / 60000) % 60,
second: Math.floor(ms / 1000) % 60,
millisecond: Math.floor(ms) % 1000
};
return Object.entries(time)
.filter(val => val[1] !== 0)
.map(([key, val]) => `${val} ${key}${val !== 1 ? 's' : ''}`)
.join(', ');
};
// EXAMPLES
formatDuration(1001); // '1 second, 1 millisecond'
formatDuration(34325055574); // '397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds'

getColonTimeFromDate

Date对象返回HH:MM:SS形式的字符串。

使用Date.prototype.ToomString()String.prototype.slice()获取给定日期对象的HH:MM:SS部分。

1
2
3
const getColonTimeFromDate = date => date.toTimeString().slice(0, 8);
// EXAMPLES
getColonTimeFromDate(new Date()); // "08:38:00"

getDaysDiffBetweenDates

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

计算两个日期对象之间的差值(以天为单位)。

1
2
3
4
const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
(dateFinal - dateInitial) / (1000 * 3600 * 24);
// EXAMPLES
getDaysDiffBetweenDates(new Date('2017-12-13'), new Date('2017-12-22')); // 9

getMeridiemSuffixOfInteger

将整数转换为字符串,根据其值添加ampm

使用取余运算符(%)和条件检查将整数转换为带子午线后缀的字符串12小时格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
const getMeridiemSuffixOfInteger = num =>
num === 0 || num === 24
? 12 + 'am'
: num === 12
? 12 + 'pm'
: num < 12
? (num % 12) + 'am'
: (num % 12) + 'pm';
// EXAMPLES
getMeridiemSuffixOfInteger(0); // "12am"
getMeridiemSuffixOfInteger(11); // "11am"
getMeridiemSuffixOfInteger(13); // "1pm"
getMeridiemSuffixOfInteger(25); // "1pm"

isAfterDate

检查一个日期是否在另一个日期之后。

使用大于运算符(>)检查第一个日期是否在第二个日期之后。

1
2
3
const isAfterDate = (dateA, dateB) => dateA > dateB;
// EXAMPLES
isAfterDate(new Date(2010, 10, 21), new Date(2010, 10, 20)); // true

isBeforeDate

检查一个日期是否在另一个日期之前。

使用小于运算符(<)检查第一个日期是否在第二个日期之前。

1
2
3
const isBeforeDate = (dateA, dateB) => dateA < dateB;
// EXAMPLES
isBeforeDate(new Date(2010, 10, 20), new Date(2010, 10, 21)); // true

isSameDate

检查一个日期是否与另一个日期相同。

使用Date.prototype.toISOString()和严格的相等性检查(===)来检查第一个日期是否与第二个日期相同。

1
2
3
const isSameDate = (dateA, dateB) => dateA.toISOString() === dateB.toISOString();
// EXAMPLES
isSameDate(new Date(2010, 10, 20), new Date(2010, 10, 20)); // true

maxDate

返回给定日期的最大值。

使用Math.max.apply()查找最大的日期,使用new Date()将其转换为Date对象。

1
2
3
4
5
6
7
8
9
const maxDate = (...dates) => new Date(Math.max.apply(null, ...dates));
// EXAMPLES
const array = [
new Date(2017, 4, 13),
new Date(2018, 2, 12),
new Date(2016, 0, 10),
new Date(2016, 0, 9)
];
maxDate(array); // 2018-03-11T22:00:00.000Z

minDate

返回给定日期的最小值。

使用Math.min.apply()查找最小的日期,使用new Date()将其转换为Date对象。

1
2
3
4
5
6
7
8
9
const minDate = (...dates) => new Date(Math.min.apply(null, ...dates));
// EXAMPLES
const array = [
new Date(2017, 4, 13),
new Date(2018, 2, 12),
new Date(2016, 0, 10),
new Date(2016, 0, 9)
];
minDate(array); // 2016-01-08T22:00:00.000Z

tomorrow

返回明天日期的字符串表示。

使用new Date()获取当前日期,将Date.getDate()增加1,并使用Date.setDate()将该值设置为结果。使用Date.prototype.toISOString()返回yyyy-mm-dd格式的字符串。

1
2
3
4
5
6
7
const tomorrow = () => {
let t = new Date();
t.setDate(t.getDate() + 1);
return t.toISOString().split('T')[0];
};
// EXAMPLES
tomorrow(); // 2018-10-19 (if current date is 2018-10-18)

Function

attempt

尝试使用提供的参数调用函数,返回结果或捕获的错误对象。

使用try...catch块返回函数的结果或的错误。

1
2
3
4
5
6
7
8
9
10
11
12
const attempt = (fn, ...args) => {
try {
return fn(...args);
} catch (e) {
return e instanceof Error ? e : new Error(e);
}
};
// EXAMPLES
var elements = attempt(function(selector) {
return document.querySelectorAll(selector);
}, '>_>');
if (elements instanceof Error) elements = []; // elements = []

bind

创建一个在给定上下文中调用fn的函数,可以选择在参数的开头添加任何附加的参数。

返回一个使用Function.prototype.apply()将给定上下文应用于fn的函数。使用Array.prototype.concat()(译者注:实际是展开运算符)将附加的参数添加到提供的参数前。

1
2
3
4
5
6
7
8
const bind = (fn, context, ...boundArgs) => (...args) => fn.apply(context, [...boundArgs, ...args]);
// EXAMPLES
function greet(greeting, punctuation) {
return greeting + ' ' + this.user + punctuation;
}
const freddy = { user: 'fred' };
const freddyBound = bind(greet, freddy);
console.log(freddyBound('hi', '!')); // 'hi fred!'

bindKey

创建一个函数,该函数在对象的给定key处调用方法,可以选择在参数的开头添加任何额外提供的参数。

返回一个使用Function.prototype.apply()context[fn]绑定到context的函数。使用扩展运算符(...)将附加的参数添加到提供的参数前。

1
2
3
4
5
6
7
8
9
10
11
const bindKey = (context, fn, ...boundArgs) => (...args) =>
context[fn].apply(context, [...boundArgs, ...args]);
// EXAMPLES
const freddy = {
user: 'fred',
greet: function(greeting, punctuation) {
return greeting + ' ' + this.user + punctuation;
}
};
const freddyBound = bindKey(freddy, 'greet');
console.log(freddyBound('hi', '!')); // 'hi fred!'

chainAsync

异步函数链式调用。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const chainAsync = fns => {
let curr = 0;
const last = fns[fns.length - 1];
const next = () => {
const fn = fns[curr++];
fn === last ? fn() : fn(next);
};
next();
};
// EXAMPLES
chainAsync([
next => {
console.log('0 seconds');
setTimeout(next, 1000);
},
next => {
console.log('1 second');
setTimeout(next, 1000);
},
() => {
console.log('2 second');
}
]);

checkProp

给定一个predicate函数和一个prop字符串,柯里化函数将传入object的属性给predicate调用。

调用obj上的prop,将其传递给提供的predicate函数,并返回函数执行后的布尔值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const checkProp = (predicate, prop) => obj => !!predicate(obj[prop]);
// EXAMPLES
const lengthIs4 = checkProp(l => l === 4, 'length');
lengthIs4([]); // false
lengthIs4([1,2,3,4]); // true
lengthIs4(new Set([1,2,3,4])); // false (Set uses Size, not length)

const session = { user: {} };
const validUserSession = checkProps(u => u.active && !u.disabled, 'user');

validUserSession(session); // false

session.user.active = true;
validUserSession(session); // true

const noLength(l => l === undefined, 'length');
noLength([]); // false
noLength({}); // true
noLength(new Set()); // true

compose

从右向左的执行函数。

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

1
2
3
4
5
6
7
8
9
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
// EXAMPLES
const add5 = x => x + 5;
const multiply = (x, y) => x * y;
const multiplyAndAdd5 = compose(
add5,
multiply
);
multiplyAndAdd5(5, 2); // 15

composeRight

从左向右的执行函数。

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

1
2
3
4
5
6
const composeRight = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
// EXAMPLES
const add = (x, y) => x + y;
const square = x => x * x;
const addAndSquare = composeRight(add, square);
addAndSquare(1, 2); // 9

converge

接受收敛函数和分支函数列表,并返回每个分支函数执行传入参数的结果,分支函数的结果作为参数传递给收敛函数。

使用Array.prototype.map()和Function.prototype.apply()将每个函数应用于给定的参数。使用扩展运算符(…)用所有其他函数的结果调用coverger。

1
2
3
4
5
6
7
const converge = (converger, fns) => (...args) => converger(...fns.map(fn => fn.apply(null, args)));
// EXAMPLES
const average = converge((a, b) => a / b, [
arr => arr.reduce((a, v) => a + v, 0),
arr => arr.length
]);
average([1, 2, 3, 4, 5, 6, 7]); // 4

curry

柯里化一个函数。

使用递归。如果提供的参数(args)数量足够,则调用传递的函数fn。否则,返回期望其余参数的curried函数fn。如果您想要获取一个接受可变参数数的函数(一个可变函数,例如Math.min()),您可以选择将参数数传递给第二个参数arity。

1
2
3
4
5
const curry = (fn, arity = fn.length, ...args) =>
arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
// EXAMPLES
curry(Math.pow)(2)(10); // 1024
curry(Math.min, 3)(10)(50)(2); // 2

debounce

创建一个去抖函数,将给定的函数经过至少ms毫秒延迟后才会被调用。

每次调用去抖函数时,先使用clearTimeout()清除当前挂起的定时器id,并使用setTimeout()方法创建一个新的定时器,该定时器将调用函数ms毫秒后执行。使用Function.prototype.apply()将上下文应用于函数,并提供必要的参数。省略第二个参数ms,将超时设置为默认值0 ms。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const debounce = (fn, ms = 0) => {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
};
// EXAMPLES
window.addEventListener(
'resize',
debounce(() => {
console.log(window.innerWidth);
console.log(window.innerHeight);
}, 250)
); // Will log the window dimensions at most every 250ms

defer

推迟调用函数,直到当前调用堆栈被清除。

使用超时为1毫秒的setTimeout()方法向浏览器事件队列添加新事件,并允许渲染引擎完成其工作。使用跨页(…)运算符为函数提供任意数量的参数。

1
2
3
4
5
6
7
8
9
const defer = (fn, ...args) => setTimeout(fn, 1, ...args);
// EXAMPLES
// Example A:
defer(console.log, 'a'), console.log('b'); // logs 'b' then 'a'

// Example B:
document.querySelector('#someElement').innerHTML = 'Hello';
longRunningFunction(); // Browser will not update the HTML until this has finished
defer(longRunningFunction); // Browser will update the HTML then run the function

functionName

记录函数的名称。

使用console.debug()和传入函数的name属性,将函数的名称输出到控制台的调试消息中。

1
2
3
const functionName = fn => (console.debug(fn.name), fn);
// EXAMPLES
functionName(Math.max); // max (logged in debug channel of console)

hz

返回一个函数每秒执行的次数。hz是频率的单位,频率单位定义为每秒一个周期。

使用performance.now()获取函数执行前后的毫秒差,以计算执行函数迭代次数所用的时间。通过将毫秒转换为秒并将它除以经过的时间,返回每秒的周期数。省略第二个参数iterations,使用默认值100次迭代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const hz = (fn, iterations = 100) => {
const before = performance.now();
for (let i = 0; i < iterations; i++) fn();
return (1000 * iterations) / (performance.now() - before);
};
// EXAMPLES
// 10,000 element array
const numbers = Array(10000)
.fill()
.map((_, i) => i);

// Test functions with the same goal: sum up the elements in the array
const sumReduce = () => numbers.reduce((acc, n) => acc + n, 0);
const sumForLoop = () => {
let sum = 0;
for (let i = 0; i < numbers.length; i++) sum += numbers[i];
return sum;
};

// `sumForLoop` is nearly 10 times faster
Math.round(hz(sumReduce)); // 572
Math.round(hz(sumForLoop)); // 4784

memoize

返回缓存函数。

通过实例化新的Map对象来创建空缓存。返回一个函数,该函数通过首先检查函数对于该特定输入值的输出是否已经缓存,或者如果没有缓存,则存储并返回它,从而将单个参数提供给memoized函数。必须使用function关键字,以便在必要时允许memoized函数更改其上下文。通过将缓存设置为返回函数的属性,允许访问缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const memoize = fn => {
const cache = new Map();
const cached = function(val) {
return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
};
cached.cache = cache;
return cached;
};
// EXAMPLES
// See the `anagrams` snippet.
const anagramsCached = memoize(anagrams);
anagramsCached('javascript'); // takes a long time
anagramsCached('javascript'); // returns virtually instantly since it's now cached
console.log(anagramsCached.cache); // The cached anagrams map

negate

否定函数。

执行传入函数并应用not运算符(!)。

1
2
3
const negate = func => (...args) => !func(...args);
// EXAMPLES
[1, 2, 3, 4, 5, 6].filter(negate(n => n % 2 === 0)); // [ 1, 3, 5 ]

once

确保函数只被调用一次。

使用闭包,定义一个标志called,并在第一次调用函数时将其设置为true,防止再次调用它。为了允许函数改变其上下文(例如在事件监听器中),必须使用function关键字,并且所提供的函数必须应用上下文。允许使用展开运算符为函数提供任意数量的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
const once = fn => {
let called = false;
return function(...args) {
if (called) return;
called = true;
return fn.apply(this, args);
};
};
// EXAMPLES
const startApp = function(event) {
console.log(this, event); // document.body, MouseEvent
};
document.body.addEventListener('click', once(startApp)); // only runs `startApp` once upon click

partial

创建一个调用fn的函数,该函数在其接收的参数前添加了partials参数。

使用扩展运算符(...)在fn的参数列表前添加新参数partials

1
2
3
4
5
const partial = (fn, ...partials) => (...args) => fn(...partials, ...args);
// EXAMPLES
const greet = (greeting, name) => greeting + ' ' + name + '!';
const greetHello = partial(greet, 'Hello');
greetHello('John'); // 'Hello John!'

partialRight

创建一个调用fn的函数,该函数在其接收的参数后添加了partials参数。

使用扩展运算符(...)在fn的参数列表后添加新参数partials

1
2
3
4
5
const partialRight = (fn, ...partials) => (...args) => fn(...args, ...partials);
// EXAMPLES
const greet = (greeting, name) => greeting + ' ' + name + '!';
const greetJohn = partialRight(greet, 'John');
greetJohn('Hello'); // 'Hello John!'

runPromisesInSeries

执行一串promise。

使用Array.prototype.reduce()创建一个promise链,其中每个promise在解析后返回给下一个promise。

sleep

延迟异步函数的执行。

通过将async函数置于睡眠状态,延迟执行异步函数,返回一个Promise

1
2
3
4
5
6
7
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// EXAMPLES
async function sleepyWork() {
console.log("I'm going to sleep for 1 second.");
await sleep(1000);
console.log('I woke up after 1 second.');
}

throttle

创建一个节流函数,该函数每隔wait毫秒调用一次所提供的函数。

使用setTimeout()方法和clearTimeout()来节流给定的方法fn。使用Function.prototype.apply()将上下文应用于函数,并提供必要的参数。使用Date.now()来跟踪上次调用节流函数的时间。省略第二个参数wait,将定时器设置为默认值0毫秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const throttle = (fn, wait) => {
let inThrottle, lastFn, lastTime;
return function() {
const context = this,
args = arguments;
if (!inThrottle) {
fn.apply(context, args);
lastTime = Date.now();
inThrottle = true;
} else {
clearTimeout(lastFn);
lastFn = setTimeout(function() {
if (Date.now() - lastTime >= wait) {
fn.apply(context, args);
lastTime = Date.now();
}
}, Math.max(wait - (Date.now() - lastTime), 0));
}
};
};
// EXAMPLES
window.addEventListener(
'resize',
throttle(function(evt) {
console.log(window.innerWidth);
console.log(window.innerHeight);
}, 250)
); // Will log the window dimensions at most every 250ms

times

迭代回调n次。

使用Function.call()调用fn n次或者直到它返回false。省略最后一个参数context,将使用undefined对象(或者非严格模式下的全局对象)。

1
2
3
4
5
6
7
8
const times = (n, fn, context = undefined) => {
let i = 0;
while (fn.call(context, i) !== false && ++i < n) {}
};
// EXAMPLES
var output = '';
times(5, i => (output += i));
console.log(output); // 01234

uncurry

反柯里化至深度n。

返回一个可变参函数。对提供的参数使用Array.prototype.reduce()调用函数的每个后续柯里化级别。如果提供的参数的长度小于n,则抛出一个错误。否则,使用Array.prototype.slice(0,n)用给定数量的参数调用fn。省略第二个参数n,默认深度1。

1
2
3
4
5
6
7
8
9
const uncurry = (fn, n = 1) => (...args) => {
const next = acc => args => args.reduce((x, y) => x(y), acc);
if (n > args.length) throw new RangeError('Arguments too few!');
return next(fn)(args.slice(0, n));
};
// EXAMPLES
const add = x => y => z => x + y + z;
const uncurriedAdd = uncurry(add, 3);
uncurriedAdd(1, 2, 3); // 6

unfold

使用迭代函数和初始值构建数组。

使用while循环和Array.prototype.push()重复调用该函数,直到它返回false。迭代器函数接受一个参数(seed),并且必须始终返回一个包含两个元素([valuenextSeed)的数组或false才能终止。

1
2
3
4
5
6
7
8
9
const unfold = (fn, seed) => {
let result = [],
val = [null, seed];
while ((val = fn(val[1]))) result.push(val[0]);
return result;
};
// EXAMPLES
var f = n => (n > 50 ? false : [-n, n + 10]);
unfold(f, 10); // [-10, -20, -30, -40, -50]

when

根据传入的函数测试值x。如果为true,返回fn(x)。否则,返回x

返回一个期望单个值x的函数,该值根据pred返回适当的值。

1
2
3
4
5
const when = (pred, whenTrue) => x => (pred(x) ? whenTrue(x) : x);
// EXAMPLES
const doubleEvenNumbers = when(x => x % 2 === 0, x => x * 2);
doubleEvenNumbers(2); // 4
doubleEvenNumbers(1); // 1

Math

approximatelyEqual

检查两个数字在误差范围是否大致相等。

使用Math.abs()将两个数差值的绝对值与epsilon(译者注:高数中的ε)进行比较。省略第三个参数epsilon,使用默认值0.001

1
2
3
const approximatelyEqual = (v1, v2, epsilon = 0.001) => Math.abs(v1 - v2) < epsilon;
// EXAMPLES
approximatelyEqual(Math.PI / 2.0, 1.5708); // true

average

返回两个或多个数字的平均值。

使用Array.prototype.reduce()将所有值添加到累加器中,用值0初始化,除以数组长度。

1
2
3
4
const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;
// EXAMPLES
average(...[1, 2, 3]); // 2
average(1, 2, 3); // 2

averageBy

使用提供的函数将每个元素映射到一个值后,返回数组的平均值。

使用Array.prototype.map()将每个元素映射到由fn返回的值,用Array.prototype.reduce()将每个值添加到累加器中,并用0初始化,之后除以数组的长度。

1
2
3
4
5
6
const averageBy = (arr, fn) =>
arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0) /
arr.length;
// EXAMPLES
averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], o => o.n); // 5
averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n'); // 5

binomialCoefficient

计算两个整数nk的二项式系数。

使用Number.isNaN()检查两个值中是否有一个是NaN。检查k的值,如果小于0或大于n返回0,如果等于0或等于n返回1,如果等于1或等于n - 1返回n。检查n - k是否小于k,并相应地交换它们的值。从2k循环并计算二项式系数。使用Math.round()计算舍入误差。

1
2
3
4
5
6
7
8
9
10
11
12
const binomialCoefficient = (n, k) => {
if (Number.isNaN(n) || Number.isNaN(k)) return NaN;
if (k < 0 || k > n) return 0;
if (k === 0 || k === n) return 1;
if (k === 1 || k === n - 1) return n;
if (n - k < k) k = n - k;
let res = n;
for (let j = 2; j <= k; j++) res *= (n - j + 1) / j;
return Math.round(res);
};
// EXAMPLES
binomialCoefficient(8, 2); // 28