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

clampNumber

在边界值ab指定的包含范围内的更接近的num

如果num落在该范围内,则返回num。否则,返回范围内最近的数字。

1
2
3
4
const clampNumber = (num, a, b) => Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
// EXAMPLES
clampNumber(2, 3, 5); // 3
clampNumber(1, -1, -5); // -1

degreesToRads

将角度从角度转换为弧度。

使用Math.PI和弧度公式将角度从度数转换为弧度。

1
2
3
const degreesToRads = deg => (deg * Math.PI) / 180.0;
// EXAMPLES
degreesToRads(90.0); // ~1.5708

digitize

将数字转换为数字数组。

使用扩展运算符(...)来构建数组。使用Array.prototype.map()parseInt()将每个值转换为整数。

1
2
3
const digitize = n => [...`${n}`].map(i => parseInt(i)); // 还能这么展开
// EXAMPLES
digitize(123); // [1, 2, 3]

distance

返回两点之间的距离。

使用Math.hypot()计算两点之间的欧几里德距离。

1
2
3
const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
// EXAMPLES
distance(1, 1, 2, 3); // 2.23606797749979

elo

使用Elo评分系统计算两个或更多对手之间的评分。它接受一个预评分数组,并返回一个后评分的数组。数组从最优表现者到最差表现者排序(赢家->输家)。

使用指数**运算符和数学运算符计算预期分数(获胜机会)。计算每个对手的评分。循环评分,使用成对的方式计算每个玩家的Elo后评级。省略第二个参数,使用默认的KFactor32。

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
const elo = ([...ratings], kFactor = 32, selfRating) => {
const [a, b] = ratings;
const expectedScore = (self, opponent) => 1 / (1 + 10 ** ((opponent - self) / 400));
const newRating = (rating, i) =>
(selfRating || rating) + kFactor * (i - expectedScore(i ? a : b, i ? b : a));
if (ratings.length === 2) return [newRating(a, 1), newRating(b, 0)];

for (let i = 0, len = ratings.length; i < len; i++) {
let j = i;
while (j < len - 1) {
j++;
[ratings[i], ratings[j]] = elo([ratings[i], ratings[j]], kFactor);
}
}
return ratings;
};
// EXAMPLES
// Standard 1v1s
elo([1200, 1200]); // [1216, 1184]
elo([1200, 1200], 64); // [1232, 1168]
// 4 player FFA, all same rank
elo([1200, 1200, 1200, 1200]).map(Math.round); // [1246, 1215, 1185, 1154]
/*
For teams, each rating can adjusted based on own team's average rating vs.
average rating of opposing team, with the score being added to their
own individual rating by supplying it as the third argument.
*/

factorial

计算一个数的阶乘。

使用递归。如果n小于或等于1,返回1。否则,返回n的乘积和n - 1的阶乘。如果n是负数,则引发异常。

1
2
3
4
5
6
7
8
9
10
const factorial = n =>
n < 0
? (() => {
throw new TypeError('Negative numbers are not allowed!');
})()
: n <= 1
? 1
: n * factorial(n - 1);
// EXAMPLES
factorial(6); // 720

fibonacci

生成一个数组,包含n个项的斐波那契序列。

先创建指定长度的空数组,初始化前两个值(01)。使用Array.prototype.reduce()将值添加到数组中,使用除前两个值之外的最后两个值的总和。

1
2
3
4
5
6
7
const fibonacci = n =>
Array.from({ length: n }).reduce(
(acc, val, i) => acc.concat(i > 1 ? acc[i - 1] + acc[i - 2] : i),
[]
);
// EXAMPLES
fibonacci(6); // [0, 1, 1, 2, 3, 5]

gcd

计算两个或多个数字/数组之间的最大公约数。

内部_gcd函数使用了递归。当y等于0时返回x。否则,返回yx%y执行gcd结果。

1
2
3
4
5
6
7
const gcd = (...arr) => {
const _gcd = (x, y) => (!y ? x : gcd(y, x % y));
return [...arr].reduce((a, b) => _gcd(a, b));
};
// EXAMPLES
gcd(8, 36); // 4
gcd(...[12, 8, 32]); // 4

geometricProgression