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)