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]