logo 30 秒就能理解的 JavaScript 代码片段(30 seconds of code) 精心挑选的有用的 JavaScript 代码片段,你可以在30秒或更短的时间内理解。(最后更新时间:2018-01-15)

 

Adapter

call

给定一个 key 和一个 set 作为参数,给定上下文时调用它们。主要用于函数组合。

使用闭包以存储的参数调用存储的 key 。

const call = (key, ...args) => context => context[key](...args);
Promise.resolve([1, 2, 3])
  .then(call('map', x => 2 * x))
  .then(console.log); //[ 2, 4, 6 ]
const map = call.bind(null, 'map');
Promise.resolve([1, 2, 3])
  .then(map(x => 2 * x))
  .then(console.log); //[ 2, 4, 6 ]

collectInto - 将接受数组的函数改变为可变参数函数

将接受数组的函数改变为可变参数函数。

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

const collectInto = fn => (...args) => fn(args);
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);

flip - 翻转函数参数

flip 接受一个函数参数,然后将该函数第一个参数作为最后一个参数。(注:翻转参数)

返回一个接受可变参数输入的闭包,并且在应用其余参数之前将最后一个参数作为第一个参数。

const flip = fn => (...args) => fn(args.pop(), ...args);
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

pipeFunctions - 执行从左到右的函数组合

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

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

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

promisify - 柯里化一个 Promise 函数

转换一个异步函数,以返回一个 promise 。

使用柯里化返回一个函数,这个函数返回一个调用原始函数的 Promise 。 使用 ...rest 运算符传入所有参数。

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

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

spreadOver - 将参数数组映射到该函数的输入

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

使用闭包和展开运算符 (...) 将参数数组映射到函数的输入。

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

Array

chunk - 数组分块

把一个数组分块成指定大小的小数组。

使用 Array.from() 创建一个新的数组,它的长度就是生成 chunk(块) 的数量。 使用 Array.slice() 将新数组的每个元素映射到长度为 size 的 chunk 中。 如果原始数组不能均匀分割,最后的 chunk 将包含剩余的元素。

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

compact - 过滤掉数组中所有假值元素

从数组中移除 falsey 值元素。

使用 Array.filter() 过滤掉数组中所有 假值元素(false, null, 0, "", undefined, 和 NaN)。

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

countBy - 返回每个分组数组中元素的数量

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

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

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

countOccurrences - 计数数组中某个值的出现次数

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

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

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

deepFlatten - 深度平铺数组

深度平铺一个数组。

使用递归。 通过空数组([]) 使用 Array.concat() ,结合 展开运算符( ... ) 来平铺数组。 递归平铺每个数组元素。

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

difference - 数组比较

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

根据数组 b 创建一个 Set 对象,然后在数组 a 上使用 Array.filter() 方法,过滤出数组 b 中不包含的值。

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

differenceWith - 通过比较函数比较两个数组的差异

过滤出数组中比较函数不返回 true 的所有值。 类似于difference ,除了接受一个 comparator (比较函数)。

使用 Array.filter()Array.findIndex() 来查找合适的值。

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

distinctValuesOfArray - 数组去重

返回数组的所有不同值。

使用 ES6 的 Set...rest 操作符剔除重复的值。

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

dropElements - 删除数组中的元素

删除数组中的元素,直到传递的函数返回 true 。 返回数组中的其余元素。

循环访问数组,使用 Array.slice() 在数组中从第一个元素开始删除,直到函数的返回值为 true。 返回其余的元素。

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

dropRight - 从右开始删除数组元素

返回从右开始删除 n 个元素的新数组。

检查 n 是否小于给定数组的长度,并且使用 Array.slice() 来从右开始删除指定数量的元素。

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

everyNth - 获得数组中的每个第 n 个元素

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

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

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

filterNonUnique - 过滤掉数组中的非唯一值

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

使用 Array.filter() 滤除掉非唯一值,使数组仅包含唯一值。

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

findLast

返回 提供的函数返回真(truthy)值的最后一个元素。

使用 Array.filter() 移除 fn 返回 falsey 值的元素,Array.slice(-1) 得到最后一个元素。

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

flatten - 平铺数组

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

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

const flatten = (arr, depth = 1) =>
  depth != 1
    ? arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v, depth - 1) : v), [])
    : arr.reduce((a, v) => a.concat(v), []);
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.slice(0) 克隆给定的数组,Array.reverse() 反转数组,Array.forEach() 遍历这个反向数组。

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

groupBy - 数组分组

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

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

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;
  }, {});
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] 返回传递数组的第一个元素。

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

indexOfAll - 返回指定元素的所有索引

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

使用 Array.forEach() 循环元素和 Array.push() 来存储匹配元素的索引。 返回索引数组。

const indexOfAll = (arr, val) => {
  const indices = [];
  arr.forEach((el, i) => el === val && indices.push(i));
  return indices;
};
indexOfAll([1, 2, 3, 1, 2, 3], 1); // [0,3]
indexOfAll([1, 2, 3], 4); // []

initial - 排除数组中最后一个元素

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

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

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

initialize2DArray - 初始化一个二维数组

初始化一个给定行数和列数,及值的二维数组。

使用 Array.map() 生成 h 行,其中每个行都是一个长度为 w 的新数组。 如果未提供值 val ,则默认为 null

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

initializeArrayWithRange - 初始化特定范围的数字数组

初始化一个数组,该数组包含指定范围内的数字,包括 startend ,数字间隔为 step

使用 Array.from(Math.ceil((end+1-start)/step)) 创建一个所需长度的数组(元素的数量等于 (end-start)/step 或者 (end+1-start)/step 包括 end ), 用 Array.map() 填充在这个范围内要求的值。 你可以省略 start 来使用默认值 0。 您可以省略 step 使用默认值 1

const initializeArrayWithRange = (end, start = 0, step = 1) =>
  Array.from({ length: Math.ceil((end + 1 - start) / step) }).map((v, i) => i * step + start);
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]

initializeArrayWithValues - 初始化特定范围和值的数组

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

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

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

intersection - 数组交集

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

根据数组 b 创建一个 Set 对象,然后在数组 a 上使用 Array.filter() 方法,只保留数组 b 中也包含的值。

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

isSorted - 是否为排序数组

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

计算前两个元素的排序方向 direction 。 使用 Object.entries() 来循环使用数组对象并对它们进行比较。 如果 direction 改变,则返回 0 ,如果直到最后一个元素, direction 没有改变,则返回 direction

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

join - 将数组的所有元素拼接成一个字符串

将数组的所有元素拼接成一个字符串并返回此字符串。 使用分隔符和结束分隔符。

使用 Array.reduce() 将元素拼接成一个字符串。 省略第二个参数 separator ,则默认使用分隔符','。 省略第三个参数 end ,默认使用与separator相同的值。

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,
    ''
  );
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"

last - 获取数组的最后一个元素

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

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

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

longestItem

获取任何数量的可迭代对象或具有 length 属性的对象,并返回最长的一个。

使用 Array.sort()length 对所有参数进行排序,返回第一个(最长)元素。

const longestItem = (...vals) => [...vals].sort((a, b) => b.length - a.length)[0];
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 - 将数组的值映射到对象

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

使用一个匿名的内部函数作用域来声明一个 undefined 的内存空间,使用闭包来存储返回值。 使用一个新的 Array 来存储带有函数映射的数组和一个逗号运算符来返回第二个步骤,而不需要从一个上下文移动到另一个上下文(由于闭包和操作顺序)。

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

maxN - 返回数组中N个最大元素

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

结合使用Array.sort() 与展开操作符(...) ,创建一个数组的浅克隆,并按降序排列。 使用 Array.slice() 以获得指定的元素个数。 忽略第二个参数 n ,默认获取单个元素(以数组的形式)。

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

minN - 返回数组中N个最小元素

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

结合使用Array.sort() 与展开操作符(...) ,创建一个数组的浅克隆,并按降序排列。 使用 Array.slice() 以获得指定的元素个数。 忽略第二个参数 n ,默认获取单个元素(以数组的形式)。

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

nthElement - 获取数组的第N个元素

返回数组的第n个元素。

使用 Array.slice() 获取数组的第 n 个元素。如果索引超出范围,则返回 [] 。省略第二个参数 n ,将得到数组的第一个元素。

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

partition - 数组元素分组

根据所提供的函数对每个元素进行迭代,将这些元素分成两个数组。

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

const partition = (arr, fn) =>
  arr.reduce(
    (acc, val, i, arr) => {
      acc[fn(val, i, arr) ? 0 : 1].push(val);
      return acc;
    },
    [[], []]
  );
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 }]]

pick - 提取

从对象中提取出与给定键对应的键值对。

如果 key 存在于 obj 中,使用Array.reduce() 只 过滤/萃取与给定键对应的键-值对对象。

const pick = (obj, arr) =>
  arr.reduce((acc, curr) => (curr in obj && (acc[curr] = obj[curr]), acc), {});
pick({ a: 1, b: '2', c: 3 }, ['a', 'c']); // { 'a': 1, 'c': 3 }

pull - 删除数组中指定的值

改变原始数组,过滤掉指定的值。

使用 Array.filter()Array.includes() 来剔除指定的值。使用 Array.length = 0 将数组中的长度重置为零,并且通过 Array.push() 只使用 pulled 值重新填充数组。

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

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));
};
let myArray = ['a', 'b', 'c', 'a', 'b', 'c'];
pull(myArray, 'a', 'c'); // myArray = [ 'b', 'b' ]

pullAtIndex - 删除数组中指定索引的值

改变原始数组,过滤掉指定索引的值。

使用 Array.filter()Array.includes() 来剔除指定的值。使用 Array.length = 0 将数组中的长度重置为零, 并且通过 Array.push() 只使用 pulled 值重新填充数组。使用 Array.push() 来跟踪 pulled 值。

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;
};
let myArray = ['a', 'b', 'c', 'd'];
let pulled = pullAtIndex(myArray, [1, 3]); // myArray = [ 'a', 'c' ] , pulled = [ 'b', 'd' ]

pullAtValue - 删除数组中指定的值,返回删除的元素

改变原始数组,过滤出指定的值。 返回删除的元素。

使用 Array.filter()Array.includes() 来剔除指定的值。使用 Array.length = 0 将数组中的长度重置为零, 并且通过 Array.push() 只使用 pulled 值重新填充数组。使用 Array.push() 来跟踪 pulled 值。

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;
};
let myArray = ['a', 'b', 'c', 'd'];
let pulled = pullAtValue(myArray, ['b', 'd']); // myArray = [ 'a', 'c' ] , pulled = [ 'b', 'd' ]

reducedFilter - 过滤对象数组

根据条件过滤一个对象数组,同时过滤掉未指定的键(key)。

使用 Array.filter() 根据断言 fn 过滤数组,以便返回条件为真值(truthy)的对象。 在过滤出来的数组上,使用 Array.map()Array.reduce() 返回新的对象来过滤掉 keys 参数中未提供的键。

const reducedFilter = (data, keys, fn) =>
  data.filter(fn).map(el =>
    keys.reduce((acc, key) => {
      acc[key] = el[key];
      return acc;
    }, {})
  );
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'}]

remove - 移除数组中的元素

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

使用 Array.filter()Array.reduce() 来查找返回真值的数组元素,使用 Array.splice() 来移除元素。 func 有三个参数(value, index, array)。

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

sample - 数组取样,随机获取数组中的一个元素

从数组中随机返回一个元素。

使用 Math.random() 生成一个随机数,乘以 length,并使用 Math.floor() 舍去小数获得到最接近的整数。这个方法也适用于字符串。

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

sampleSize - 从数组中随机获取 n 个元素

array 中获取 n 个唯一键随机元素。

使用Fisher-Yates算法 对数组进行打乱。 使用 Array.slice() 获取第一个 n 元素。 省略第二个参数,n 从数组中随机取得 1 个元素。

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);
};
sampleSize([1, 2, 3], 2); // [3,1]
sampleSize([1, 2, 3], 4); // [2,3,1]

shuffle - 随机排列数组

随机排列指定数组的值,返回一个新的数组。

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

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;
};
const foo = [1, 2, 3];
shuffle(foo); // [2,3,1], foo = [1,2,3]

similarity - 获取数组交集

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

使用 Array.filter() 移除不在 values 中的值,使用 Array.includes() 确定。

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

sortedIndex - 指定值应插入到数组中的最低索引位置

返回指定值应插入到数组中的最低索引位置,以保持其排序顺序。

检查数组是否按降序(松散地)排序。 使用 Array.findIndex() 来找到元素应该被插入的合适的索引位置。

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;
};
sortedIndex([5, 3, 2, 1], 4); // 1
sortedIndex([30, 50], 40); // 1

symmetricDifference - 数组差集

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

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

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))];
};
symmetricDifference([1, 2, 3], [1, 2, 4]); // [3,4]

tail - 返回剔除第一个元素后的数组

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

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

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

take - 数组切片,返回前N个元素的数组

创建一个数组切片,从arr数组的起始元素开始提取n个元素。

使用 Array.slice() 创建一个数组包含第一个元素开始,到 n 个元素结束的数组。

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

takeRight - 数组切片,返回后N个元素的数组

创建一个数组切片,从 arr 数组的最后一个元素开始向前提取n个元素。

使用 Array.slice() 来创建一个从第 n 个元素开始从末尾的数组。

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

union - 数组合集

返回两个数组中的任何一个元素。

用数组 ab 的所有值创建一个 Set 对象,并转换成一个数组。

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

without - 从数组中排除给定值

从数组中排除给定值。

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

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

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

zip - 创建一个分组元素数组

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

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

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]);
  });
};
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.reduce() 来决定结果对象的结构。

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

Browser

arrayToHtmlList - 数组转换为 html 标签列表

将给定的数组元素转换为 <code><li></code> 标签并将其附加到给定 id 的列表中。

使用 Array.map()document.querySelector() 来创建一个html 标签列表。

const arrayToHtmlList = (arr, listID) =>
  arr.map(item => (document.querySelector('#' + listID).innerHTML += `<li>${item}</li>`));
arrayToHtmlList(['item 1', 'item 2'], 'myListID');

bottomVisible - 页面的底部是否可见

如果页面底部可见,则返回 true ,否则返回 false

使用 scrollYscrollHeightclientHeight 来判断页面的底部是否可见。

const bottomVisible = () =>
  document.documentElement.clientHeight + window.scrollY >=
  (document.documentElement.scrollHeight || document.documentElement.clientHeight);
bottomVisible(); // true

copyToClipboard - 复制到剪贴板advanced

将一个字符串复制到剪贴板。 仅作为用户操作的结果(即,在 click 事件侦听器中)。

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

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);
  }
};
copyToClipboard('Lorem ipsum'); // 'Lorem ipsum' copied to clipboard.

createElement - 创建元素

根据一个字符串创建一个元素(不附加到 document )。 如果给定的字符串包含多个元素,则只返回第一个元素。

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

const createElement = str => {
  const el = document.createElement('div');
  el.innerHTML = str;
  return el.firstElementChild;
};
const el = createElement(
  `<div class="container">
    <p>Hello!</p>
  </div>`
);
console.log(el.className); // 'container'

createEventHub - 创建事件中转advanced

使用 emitonoff 方法创建一个 pub/sub (publish–subscribe) 事件中转。

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

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);
  }
});
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 - 获取当前页面URL

返回当前页面URL。

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

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

detectDeviceType - 检测设备类型

检测网站是否正在移动设备或台式机/笔记本电脑上打开。

使用正则表达式来测试 navigator.userAgent 属性以确定打开设备是移动设备还是台式机/笔记本电脑。

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

elementIsVisibleInViewport - 判断元素是否在可视窗口可见

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

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

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;
};
// 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)

getScrollPosition - 获取滚动条位置

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

如果浏览器支持 pageXOffsetpageYOffset ,那么请使用 pageXOffsetpageYOffset ,否则请使用 scrollLeftscrollTop 。 你可以省略 el 参数,默认值为 window

const getScrollPosition = (el = window) => ({
  x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
  y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
});
getScrollPosition(); // {x: 0, y: 200}

getStyle - 获取元素样式

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

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

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

hasClass - 判断元素是否具有指定的样式类

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

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

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

hide - 隐藏所有指定的元素

隐藏所有指定的元素。

使用展开运算符(...)和 Array.forEach()display: none 应用于每个指定的元素。

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

httpsRedirect - HTTPS重定向

如果其当前使用 HTTP 访问,则将页面重定向到 HTTPS 中。 另外,按下后退按钮不会将其退回到历史记录中的HTTP页面。

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

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

off - 移除事件侦听器

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

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

const off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts);
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() 将一个事件监听器添加到一个元素。 如果提供了 选项对象(opts) 的 target 属性,确保事件目标匹配指定的目标元素,然后通过提供正确的 this 上下文来调用回调。 返回一个对自定义委派函数的引用,以便与 off 一起使用。 忽略 opts ,则默认为非委派行为,并且事件冒泡。

const 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;
};
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 - 当用户输入类型改变执行回调advanced

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

使用两个事件监听器。首先假设 mouse 输入并将 touchstart 事件监听器绑定到 document 上。在 touchstart 上,添加一个 mousemove 事件监听器来侦听连续两个 mousemove 事件在 20ms 内触发,使用 performance.now()。 在任何一种情况下,输入类型将作为参数运行回调。

const 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);
  });
};
onUserInputChange(type => {
  console.log('The user is now using', type, 'as an input method.');
});

redirect - 重定向到URL

重定向到一个 URL 。

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

const redirect = (url, asLink = true) =>
  asLink ? (window.location.href = url) : window.location.replace(url);
redirect('https://google.com');

runAsync - 运行异步函数advanced

通过使用 Web Worker 在单独的线程中运行一个函数,允许长时间运行的函数不会阻塞 UI。

使用 Blob 对象URL创建一个新的Worker,其内容应该是所提供函数的字符串化版本。 立即发送回调用函数的返回值。 返回一个 promise ,监听 onmessageonerror 事件并解析 worker 发回的数据,或者抛出一个错误。

const runAsync = fn => {
  const blob = `var fn = ${fn.toString()}; postMessage(fn());`;
  const worker = new Worker(
    URL.createObjectURL(new Blob([blob]), {
      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();
    };
  });
};
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() 来实现滚动动画。

const scrollToTop = () => {
  const c = document.documentElement.scrollTop || document.body.scrollTop;
  if (c > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  }
};
scrollToTop();

setStyle - 设置CSS样式

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

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

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

show - 显示所有指定的元素

显示所有指定的元素。

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

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

toggleClass - 切换一个元素的样式类

切换一个元素的样式类。

使用 element.classList.toggle() 来切换元素中指定样式类。

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

UUIDGeneratorBrowser - 在浏览器中生成一个 UUID

在浏览器中生成一个 UUID。

使用 crypto API 生成一个 UUID,符合RFC4122 版本 4 。

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

Date

formatDuration - 返回毫秒数的可读格式

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

用适当的值来划分ms,以获得 dayhourminutesecondmillisecond 的适当值。 通过 Array.filter() 使用 Object.entries() 只保留非零值。 使用 Array.map() 为每个值创建字符串,并且适当复数化。 使用 String.join(', ') 将这些值组合成一个字符串。

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(val => val[1] + ' ' + (val[1] !== 1 ? val[0] + 's' : val[0]))
    .join(', ');
};
formatDuration(1001); // '1 second, 1 millisecond'
formatDuration(34325055574); // '397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds'

getDaysDiffBetweenDates - 获取两个日期之间相差的天数

返回两个日期之间相差的天数。

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

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

tomorrow - 明天

以字符串形式返回明天日期表示。

使用 new Date() 获取今天的日期,加上 86400000 秒(24小时),使用 Date.toISOString() 将 Date 对象转换为字符串。

const tomorrow = () => new Date(new Date().getTime() + 86400000).toISOString().split('T')[0];
tomorrow(); // 2017-12-27 (if current date is 2017-12-26)

Function

chainAsync - 链式调用异步函数

链式调用异步函数。

循环遍历包含异步事件的函数数组,每次异步事件完成后调用 next

const chainAsync = fns => {
  let curr = 0;
  const next = () => fns[curr++](next);
  next();
};
chainAsync([
  next => {
    console.log('0 seconds');
    setTimeout(next, 1000);
  },
  next => {
    console.log('1 second');
  }
]);

compose - 函数式编程术语:函数组合

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

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

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
const add5 = x => x + 5;
const multiply = (x, y) => x * y;
const multiplyAndAdd5 = compose(add5, multiply);
multiplyAndAdd5(5, 2); // 15

curry - 函数式编程术语:柯里化

柯里化一个函数。

使用递归。 如果提供的参数(args)数量足够,调用传递函数 fn 。否则返回一个柯里化后的函数 fn ,期望剩下的参数。如果你想柯里化一个接受可变参数数量的函数(可变参数数量的函数,例如 Math.min() ),你可以选择将参数个数传递给第二个参数 arity

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

defer - 延迟调用

延迟调用一个函数,直到当前调用堆栈已经清除。

使用 setTimeout() ,超时时间为1ms,将新事件添加到浏览器事件队列,并允许渲染引擎完成其工作。使用展开 (...) 运算符为函数提供任意数量的参数。

const defer = (fn, ...args) => setTimeout(fn, 1, ...args);
// 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 属性将方法名称记录到控制台的 debug 通道。

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

memoize - 返回 memoized(缓存的)函数

返回 memoized(缓存的)函数。

使用 Object.create(null) 创建一个没有 Object.prototype 的空对象(这样如果输入值类似 'hasOwnProperty',那么这些属性就不会被解析)。 通过首先检查该特定输入值的函数输出是否已经被缓存,如果没有,则返回一个函数,该函数将作为单个参数提供给 memoized 函数。

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;
};
// 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 - 否定断言

否定断言函数。

接受一个断言函数,并用它的参数应用逻辑非运算符 (!) 。

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

once - 确保函数只被调用一次

确保函数只被调用一次。

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

const once = fn => {
  let called = false;
  return function(...args) {
    if (called) return;
    called = true;
    return fn.apply(this, args);
  };
};
const startApp = function(event) {
  console.log(this, event); // document.body, MouseEvent
};
document.body.addEventListener('click', once(startApp)); // only runs `startApp` once upon click

runPromisesInSeries - 运行连续的 promises

运行连续的 promise。

使用 Array.reduce() 通过创建 promise 链来运行连续的 promises,其中每个 promise 在 resolved 时返回下一个 promise 。

const runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());
const delay = d => new Promise(r => setTimeout(r, d));
runPromisesInSeries([() => delay(1000), () => delay(2000)]); // Executes each promise sequentially, taking a total of 3 seconds to complete

sleep - 休眠,延迟执行异步函数

延迟异步函数的执行。

延迟执行 async 函数的一部分,通过把它放到 sleep 状态,返回一个 Promise

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

Math

average - 求数字或数组的平均值

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

使用 Array.reduce() 将每个值累加到初始值 0 的累加器, 除以数组长度 length

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

averageBy - 根据函数映射每个元素,然后求数组的平均值

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

使用 Array.map() 将每个元素映射到由 fn 返回的值,Array.reduce() 将每个值累加到累加器,用 0 作为累加器的初始值,再除以数组的 length

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

clampNumber - 返回限制范围内的值

返回由边界值 ab 限定范围内的 num

如果 num 在限定范围内,则返回 num 。 否则,返回范围内最接近的数字。

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

digitize - 将数字转化为整数数组

将数字转换为数字数组。

将数字转换为字符串,使用展开运算符 (...) 构建一个数组。 使用 Array.map()parseInt() 将每个值转换为整数。

const digitize = n => [...`${n}`].map(i => parseInt(i));
digitize(123); // [1, 2, 3]

distance - 两点之间的欧氏距离

返回两点之间的欧氏距离。

使用 Math.hypot() 计算两点之间的欧氏距离( Euclidean distance)。

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

elo - 等级评分算法advanced

使用 Elo 评分系统 计算两个或两个以上对手之间的新评分。它需要一个预定义数组,并返回一个包含事后评级的数组。 数组应该从最高评分到最低评分排序(赢家 -> 失败者)。

使用指数 ** 操作符和数学运算符来计算预期分数(获胜几率),并计算每个对手的新评级。对每个对手计算新的评分。 循环评分,使用每个排列组合,以成对方式计算每个玩家的 Elo 评分。 忽略第二个参数,使用默认的 k-factor 为 32。

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)];
  } else {
    for (let i = 0; i < ratings.length; i++) {
      let j = i;
      while (j < ratings.length - 1) {
        [ratings[i], ratings[j + 1]] = elo([ratings[i], ratings[j + 1]], kFactor);
        j++;
      }
    }
  }
  return ratings;
};
// 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 。否则返回 nn - 1 的阶乘。如果 n 是负数,则会引发异常。

const factorial = n =>
  n < 0
    ? (() => {
        throw new TypeError('Negative numbers are not allowed!');
      })()
    : n <= 1 ? 1 : n * factorial(n - 1);
factorial(6); // 720

fibonacci - 生成斐波纳契数组

生成一个包含 斐波纳契(fibonacci)数组,直到该数组有第 n 元素。

创建一个指定长度的空数组,初始化前两个值( 01 )。使用 Array.reduce() 向数组中添加值,该值是最后两个值的和,前两个值除外。

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

gcd - 最大公约数

计算两个或两个以上数字/数字数组的最大公约数。

内部的 _gcd 函数使用递归。基本情况是,当 y 等于 0 的情况下,返回 x 。否则,返回 y 的最大公约数和x / y的其余数。

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

geometricProgression - 创建一个指定范围的数字数组,后一个数是前一个数的 N 倍

初始化一个包含指定范围中数字的数组,包含 startend,两个元素之间的比例是step(后一个数是前一个数的step 倍)。 如果 step 等于 1 则返回一个错误。

使用Array.from()Math.log()Math.floor() 来创建一个所需长度的数组,使用 Array.map() 来填充所需的值。 省略第二个参数 start,使用默认值1。 省略第三个参数 step,使用默认值2

const geometricProgression = (end, start = 1, step = 2) =>
  Array.from({ length: Math.floor(Math.log(end / start) / Math.log(step)) + 1 }).map(
    (v, i) => start * step ** i
  );
geometricProgression(256); // [1, 2, 4, 8, 16, 32, 64, 128, 256]
geometricProgression(256, 3); // [3, 6, 12, 24, 48, 96, 192]
geometricProgression(256, 1, 4); // [1, 4, 16, 64, 256]

hammingDistance - 汉明距离

计算两个值之间的汉明距离。

使用XOR运算符( ^ )查找这两个数字之间的位差,使用 toString(2) 转换为二进制字符串。使用 match(/1/g) 计算并返回字符串中 1 的数量。

const hammingDistance = (num1, num2) => ((num1 ^ num2).toString(2).match(/1/g) || '').length;
hammingDistance(2, 3); // 1

注:在信息论中,两个等长字符串之间的汉明距离(英语:Hamming distance)是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。- 维基百科

inRange - 检查某一数字是否在给定指定的范围内

检查给定的数字是否在给定范围内。

使用算术比较来检查给定的数字是否在指定的范围内。如果没有指定第三个参数 end ,则范围被认为是从 0start

const inRange = (n, start, end = null) => {
  if (end && start > end) end = [start, (start = end)][0];
  return end == null ? n >= 0 && n < start : n >= start && n < end;
};
inRange(3, 2, 5); // true
inRange(3, 4); // true
inRange(2, 3, 5); // false
inrange(3, 2); // false

isDivisible - 是否可以被某个数整除

检查第一个数字参数是否可被第二个数字整除。

使用模运算符(%)来检查余数是否等于 0

const isDivisible = (dividend, divisor) => dividend % divisor === 0;
isDivisible(6, 3); // true

isEven - 判断为偶数

如果给定的数字是偶数,则返回 true ,否则返回 false

使用模运算符(%)来检查数字是奇数还是偶数。如果数字是偶数,则返回 true ,如果是奇数,则返回 false

const isEven = num => num % 2 === 0;
isEven(3); // false

isPrime - 判断是否为素数

检查提供的整数是否为素数。

检查数字从 2 到给定数字的平方根。 如果它们中的任何一个可以整除给定的数字,则返回 false ,否则返回 true ,除非数字小于 2

const isPrime = num => {
  const boundary = Math.floor(Math.sqrt(num));
  for (var i = 2; i <= boundary; i++) if (num % i == 0) return false;
  return num >= 2;
};
isPrime(11); // true

lcm - 最小公倍数

返回两个或两个以上数字的最小公倍数。

使用最大公约数(GCD)公式和 lcm(x,y) = x * y / gcd(x,y) 来确定最小公倍数。 GCD公式使用递归。

const lcm = (...arr) => {
  const gcd = (x, y) => (!y ? x : gcd(y, x % y));
  const _lcm = (x, y) => x * y / gcd(x, y);
  return [...arr].reduce((a, b) => _lcm(a, b));
};
lcm(12, 7); // 84
lcm(...[1, 3, 4, 5]); // 60

luhnCheck - Luhn 算法检查

Luhn 算法 的实现,用来验证各种识别码,如信用卡号码,IMEI码,国家提供商标识符号码等。

使用 String.split('')Array.reverse()Array.map() 结合 parseInt() 得到一个数字数组。 使用 Array.splice(0,1) 获取最后一位数字。 使用 Array.reduce() 来实现 Luhn 算法。 如果 sum 能被 10 整除,则返回 true ;否则返回 false

const luhnCheck = num => {
  let arr = (num + '')
    .split('')
    .reverse()
    .map(x => parseInt(x));
  let lastDigit = arr.splice(0, 1)[0];
  let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + (val * 2) % 9 || 9), 0);
  sum += lastDigit;
  return sum % 10 === 0;
};
luhnCheck('4485275742308327'); // true
luhnCheck(6011329933655299); //  false
luhnCheck(123456789); // false

maxBy - 根据函数映射每个元素,然后返回数组的最大值

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

使用 Array.map() 将每个元素映射到由fn,然后用 Math.max() 返回的值来获取最大值。

const maxBy = (arr, fn) => Math.max(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
maxBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], o => o.n); // 8
maxBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n'); // 8

median - 获取数字数组的中值

返回数字数组的中值。

找到数字数组的中间值,使用 Array.sort() 对值进行排序。 如果 length 是奇数,则返回中间值数字,否则返回两个中间值数值的平均值。

const median = arr => {
  const mid = Math.floor(arr.length / 2),
    nums = [...arr].sort((a, b) => a - b);
  return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
};
median([5, 6, 50, 1, -5]); // 5

minBy - 根据函数映射每个元素,然后返回数组的最小值

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

使用 Array.map() 将每个元素映射到由fn,然后用 Math.min() 返回的值来获取最小值。

const minBy = (arr, fn) => Math.min(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
minBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], o => o.n); // 8
minBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n'); // 8

percentile - 百分比表示数组中有多少数字小于或等于给定值

使用百分比表示给定数组中有多少个数字小于或等于给定值。

使用 Array.reduce() 来计算有多少个数字小于等于该值,并用百分比表示。

const percentile = (arr, val) =>
  100 * arr.reduce((acc, v) => acc + (v < val ? 1 : 0) + (v === val ? 0.5 : 0), 0) / arr.length;
percentile([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 6); // 55

powerset - 幂集

返回给定数组的 powerset(幂集)。

使用 Array.reduce()Array.map() 结合来遍历元素,并将其组合成一个包含所有排列组合的数组。

const powerset = arr => arr.reduce((a, v) => a.concat(a.map(r => [v].concat(r))), [[]]);
powerset([1, 2]); // [[], [1], [2], [2,1]]

primes - 生成质数数组

使用 Eratosthenes 筛选法生成达到给定数的质数。

生成一个从 2 开始到给定数的数组。 使用 Array.filter() 过滤掉能被从 2 开始到给定数的任意值整除的数。

const primes = num => {
  let arr = Array.from({ length: num - 1 }).map((x, i) => i + 2),
    sqroot = Math.floor(Math.sqrt(num)),
    numsTillSqroot = Array.from({ length: sqroot - 1 }).map((x, i) => i + 2);
  numsTillSqroot.forEach(x => (arr = arr.filter(y => y % x !== 0 || y == x)));
  return arr;
};
primes(10); // [2,3,5,7]

注:

埃拉托斯特尼筛选法(希腊语:κόσκινον Ἐρατοσθένους,英语:sieve of Eratosthenes ),简称埃氏筛,是一种简单且年代久远的筛法,用来找出一定范围内所有的素数。所使用的原理是从2开始,将每个素数的各个倍数,标记成合数。一个素数的各个倍数,是一个差为此素数本身的等差数列。此为这个筛法和试除法不同的关键之处,后者是以素数来测试每个待测数能否被整除。埃拉托斯特尼筛法是列出所有小素数最有效的方法之一,其名字来自于古希腊数学家埃拉托斯特尼,并且被描述在尼科马库斯所著Introduction to Arithmetic中。-维基百科

randomIntegerInRange - 在指定的范围内生成一个随机整数

返回指定范围内的随机整数。

使用 Math.random() 生成一个随机数并将其映射到所需的范围,使用 Math.floor() 使其成为一个整数。

const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
randomIntegerInRange(0, 5); // 2

randomNumberInRange - 在指定的范围内生成一个随机数

返回指定范围内的一个随机数。

使用 Math.random() 生成一个随机值,使用乘法将其映射到所需的范围。

const randomNumberInRange = (min, max) => Math.random() * (max - min) + min;
randomNumberInRange(2, 10); // 6.0211363285087005

round - 精确的几位小数

将数字四舍五入到指定的小数位数。

使用 Math.round() 和模板字面量将数字四舍五入为指定的小数位数。 省略第二个参数 decimals ,数字将被四舍五入到一个整数。

const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
round(1.005, 2); // 1.01

sbdm - 散列算法

将输入的字符串散列成一个整数。

使用 String.split('')Array.reduce() 创建输入字符串的散列,利用位移。

const sdbm = str => {
  let arr = str.split('');
  return arr.reduce(
    (hashCode, currentVal) =>
      (hashCode = currentVal.charCodeAt(0) + (hashCode << 6) + (hashCode << 16) - hashCode),
    0
  );
};
sdbm('name'); // -3521204949

standardDeviation - 标准偏差

返回数组数组的标准偏差。

使用 Array.reduce() 来计算均值,方差已经值的方差之和,方差的值,然后确定标准偏差。 您可以省略第二个参数来获取样本标准偏差,或将其设置为 true 以获得总体标准偏差。

const standardDeviation = (arr, usePopulation = false) => {
  const mean = arr.reduce((acc, val) => acc + val, 0) / arr.length;
  return Math.sqrt(
    arr.reduce((acc, val) => acc.concat((val - mean) ** 2), []).reduce((acc, val) => acc + val, 0) /
      (arr.length - (usePopulation ? 0 : 1))
  );
};
standardDeviation([10, 2, 38, 23, 38, 23, 21]); // 13.284434142114991 (sample)
standardDeviation([10, 2, 38, 23, 38, 23, 21], true); // 12.29899614287479 (population)

sum - 数字数组求和

返回两个或两个以上数字/数字数组中元素之和。

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

const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);
sum(...[1, 2, 3, 4]); // 10

sumBy - 根据函数映射每个元素,然后返回数组的和

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

使用 Array.map() 将每个元素映射到由 fn 返回的值,Array.reduce() 将每个值添加到一个累加器,并且累加器初始值为 0

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

sumPower - 幂累加

返回从 startend(包括两端)所有数字的权重总和。

使用 Array.fill() 创建一个包含目标范围内所有数字的数组,Array.map() 和指数运算符(**)来将它们求 power 次幂,并且使用 Array.reduce() 把它们累加在一起。 省略第二个参数power,使用默认的2。 省略第三个参数start,使用默认的初始值1

const sumPower = (end, power = 2, start = 1) =>
  Array(end + 1 - start)
    .fill(0)
    .map((x, i) => (i + start) ** power)
    .reduce((a, b) => a + b, 0);
sumPower(10); // 385
sumPower(10, 3); //3025
sumPower(10, 3, 5); //2925

toSafeInteger - 将值转换为安全整数

将值转换为安全整数。

使用 Math.max()Math.min() 来找到最接近的安全值。 使用 Math.round() 转换为一个整数。

const toSafeInteger = num =>
  Math.round(Math.max(Math.min(num, Number.MAX_SAFE_INTEGER), Number.MIN_SAFE_INTEGER));
toSafeInteger('3.2'); // 3
toSafeInteger(Infinity); // 9007199254740991

Node

colorize - 在控制台中以彩色方式打印文本

将特殊字符添加到文本中,以在控制台中以彩色方式打印(与 console.log() 结合使用)。

使用模板字面量和特殊字符将相应的颜色代码添加到字符串输出中。 对于背景颜色,添加一个特殊字符,在字符串的末尾重置背景颜色。

const colorize = (...args) => ({
  black: `\x1b[30m${args.join(' ')}`,
  red: `\x1b[31m${args.join(' ')}`,
  green: `\x1b[32m${args.join(' ')}`,
  yellow: `\x1b[33m${args.join(' ')}`,
  blue: `\x1b[34m${args.join(' ')}`,
  magenta: `\x1b[35m${args.join(' ')}`,
  cyan: `\x1b[36m${args.join(' ')}`,
  white: `\x1b[37m${args.join(' ')}`,
  bgBlack: `\x1b[40m${args.join(' ')}\x1b[0m`,
  bgRed: `\x1b[41m${args.join(' ')}\x1b[0m`,
  bgGreen: `\x1b[42m${args.join(' ')}\x1b[0m`,
  bgYellow: `\x1b[43m${args.join(' ')}\x1b[0m`,
  bgBlue: `\x1b[44m${args.join(' ')}\x1b[0m`,
  bgMagenta: `\x1b[45m${args.join(' ')}\x1b[0m`,
  bgCyan: `\x1b[46m${args.join(' ')}\x1b[0m`,
  bgWhite: `\x1b[47m${args.join(' ')}\x1b[0m`
});
console.log(colorize('foo').red); // 'foo' (red letters)
console.log(colorize('foo', 'bar').bgBlue); // 'foo bar' (blue background)
console.log(colorize(colorize('foo').yellow, colorize('foo').green).bgWhite); // 'foo bar' (first word in yellow letters, second word in green letters, white background for both)

hasFlags - 进程是否包含指定的 flags

检查当前进程的参数是否包含指定的标志。

使用 Array.every()Array.includes() 来检查 process.argv 是否包含所有指定的标志。 使用正则表达式来测试指定的标志是否以 --- 作为前缀并相应地添加前缀。

const hasFlags = (...flags) =>
  flags.every(flag => process.argv.includes(/^-{1,2}/.test(flag) ? flag : '--' + flag));
// node myScript.js -s --test --cool=true
hasFlags('-s'); // true
hasFlags('--test', 'cool=true', '-s'); // true
hasFlags('special'); // false

isTravisCI - 当前环境是否是 Travis CI

检查当前环境是否是Travis CI

检查当前环境是否具有TRAVISCI环境变量(reference)。

const isTravisCI = () => 'TRAVIS' in process.env && 'CI' in process.env;
isTravisCI(); // true (if code is running on Travis CI)

JSONToFile - 将 JSON 写到文件

将 JSON 对象写入文件。

使用 fs.writeFile(),模板字面量 和 JSON.stringify()json 对象写入到 .json 文件中。

const fs = require('fs');
const JSONToFile = (obj, filename) =>
  fs.writeFile(`${filename}.json`, JSON.stringify(obj, null, 2));
JSONToFile({ test: 'is passed' }, 'testJsonFile'); // writes the object to 'testJsonFile.json'

readFileLines - 逐行读取文件内容

返回一个数组,数组中每个元素是指定文件中的每一行。

fs node 包中使用 readFileSync 函数指定文件中创建一个 Buffer 。使用 toString(encoding) 函数将 buffer 转换为字符串。通过逐行 split 文件内容(每个\n)根据文件内容创建一个数组。

const fs = require('fs');
const readFileLines = filename =>
  fs
    .readFileSync(filename)
    .toString('UTF8')
    .split('\n');
/*
contents of test.txt :
  line1
  line2
  line3
  ___________________________
*/
let arr = readFileLines('test.txt');
console.log(arr); // ['line1', 'line2', 'line3']

untildify - 将波浪符号路径转换为绝对路径

将波浪符号(~)路径转换为绝对路径。

通过一个正则表达式使用 String.replace()OS.homedir() 来用主目录替换路径起始处的

const untildify = str => str.replace(/^~($|\/|\\)/, `${require('os').homedir()}$1`);
untildify('~/node'); // '/Users/aUser/node'

UUIDGeneratorNode - 在 Node.JS 中生成一个 UUID

在浏览器中生成一个 UUID。

使用 crypto API 生成一个 UUID,符合RFC4122 版本 4 。

const crypto = require('crypto');
const UUIDGeneratorNode = () =>
  ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
    (c ^ (crypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16)
  );
UUIDGeneratorNode(); // '79c7c136-60ee-40a2-beb2-856f1feabefc'

Object

cleanObj - 从对象中删除属性

从 JSON 对象删除除了指定属性之外的任何其他属性。

使用 Object.keys() 方法遍历给定的 JSON 对象并删除给定数组中不包含的 keys 。 如果你传递一个特殊的键 childIndicator ,它也会深入地将这个函数应用到内部对象中。

const cleanObj = (obj, keysToKeep = [], childIndicator) => {
  Object.keys(obj).forEach(key => {
    if (key === childIndicator) {
      cleanObj(obj[key], keysToKeep, childIndicator);
    } else if (!keysToKeep.includes(key)) {
      delete obj[key];
    }
  });
  return obj;
};
const testObj = { a: 1, b: 2, children: { a: 1, b: 2 } };
cleanObj(testObj, ['a'], 'children'); // { a: 1, children : { a: 1}}

functions - 返回一个函数属性名称数组

从一个对象的自身(是否继承可选)可枚举属性返回一个函数属性名称数组。

使用 Object.keys(obj) 来迭代对象的自身属性。 如果 inheritedtrue ,则使用 Object.get.PrototypeOf(obj) 来获得对象的继承属性。 使用 Array.filter() 只保留那些函数的属性。 省略第二个参数 inherited,默认情况下不包含继承的属性。

const functions = (obj, inherited = false) =>
  (inherited
    ? [...Object.keys(obj), ...Object.keys(Object.getPrototypeOf(obj))]
    : Object.keys(obj)
  ).filter(key => typeof obj[key] === 'function');
function Foo() {
  this.a = () => 1;
  this.b = () => 2;
}
Foo.prototype.c = () => 3;
functions(new Foo()); // ['a', 'b']
functions(new Foo(), true); // ['a', 'b', 'c']

invertKeyValues - 反转对象的键值对

反转对象的键值对,而不会改变它。

使用 Object.keys()Array.reduce() 来反转对象的键值对。

const invertKeyValues = obj =>
  Object.keys(obj).reduce((acc, key) => {
    acc[obj[key]] = key;
    return acc;
  }, {});
invertKeyValues({ name: 'John', age: 20 }); // { 20: 'age', John: 'name' }

lowercaseKeys - 小写对象属性名

根据指定对象创建一个新对象,使其所有的 key 都小写。

使用 Object.keys()Array.reduce() 根据指定对象创建一个新对象。 使用 String.toLowerCase() 将原始对象中的每个 key 转换为小写。

const lowercaseKeys = obj =>
  Object.keys(obj).reduce((acc, key) => {
    acc[key.toLowerCase()] = obj[key];
    return acc;
  }, {});
const myObj = { Name: 'Adam', sUrnAME: 'Smith' };
const myObjLower = lowercaseKeys(myObj); // {name: 'Adam', surname: 'Smith'};

mapKeys - 根据提供函数生成的键生成一个新对象

使用提供函数生成的键与提供的对象相同的值创建一个对象。

使用 Object.keys(obj) 来迭代对象的键。 使用 Array.reduce() 创建一个具有相同值的新对象,并使用 fn 来映射键。

const mapKeys = (obj, fn) =>
  Object.keys(obj).reduce((acc, k) => {
    acc[fn(obj[k], k, obj)] = obj[k];
    return acc;
  }, {});
mapKeys({ a: 1, b: 2 }, (val, key) => key + val); // { a1: 1, b2: 2 }

mapValues - 根据提供函数返回的值映射一个新对象

使用提供的对象相同的键创建一个对象,对应值是通过每个值运行提供函数后生成的值。

使用 Object.keys(obj) 来迭代对象的键。 使用 Array.reduce() 创建一个具有相同键 和 fn 映射值的新对象。

const mapValues = (obj, fn) =>
  Object.keys(obj).reduce((acc, k) => {
    acc[k] = fn(obj[k], k, obj);
    return acc;
  }, {});
const users = {
  fred: { user: 'fred', age: 40 },
  pebbles: { user: 'pebbles', age: 1 }
};
mapValues(users, u => u.age); // { fred: 40, pebbles: 1 }

merge - 合并对象

组合两个或两个以上的对象,创建一个新对象。

使用 Array.reduce()Object.keys(obj) 结合来遍历所有对象和键。 使用 hasOwnProperty()Array.concat() 为存在与多个对象中的键添加值。

const merge = (...objs) =>
  [...objs].reduce(
    (acc, obj) =>
      Object.keys(obj).reduce((a, k) => {
        acc[k] = acc.hasOwnProperty(k) ? [].concat(acc[k]).concat(obj[k]) : obj[k];
        return acc;
      }, {}),
    {}
  );
const object = {
  a: [{ x: 2 }, { y: 4 }],
  b: 1
};
const other = {
  a: { z: 3 },
  b: [2, 3],
  c: 'foo'
};
merge(object, other); // { a: [ { x: 2 }, { y: 4 }, { z: 3 } ], b: [ 1, 2, 3 ], c: 'foo' }

objectFromPairs - 根据键值对创建对象

根据给定的键值对创建一个对象。

使用 Array.reduce() 来创建和组合键值对。

const objectFromPairs = arr => arr.reduce((a, v) => ((a[v[0]] = v[1]), a), {});
objectFromPairs([['a', 1], ['b', 2]]); // {a: 1, b: 2}

objectToPairs - 对象转化为键值对

根据一个对象中创建一个键-值对数组。

使用 Object.keys()Array.map() 遍历对象的键并生成一个包含键值对的数组。

const objectToPairs = obj => Object.keys(obj).map(k => [k, obj[k]]);
objectToPairs({ a: 1, b: 2 }); // [['a',1],['b',2]]

orderBy - 排序对象数组

返回按属性(props)和顺序(orders)排序的对象数组。

在默认值为 0props 数组上使用 Array.sort()Array.reduce(),根据传递的顺序使用数组解构来交换属性位置。 如果没有传递 orders 数组,那么默认情况下它会按 'asc' 排序(升序) 。

const orderBy = (arr, props, orders) =>
  [...arr].sort((a, b) =>
    props.reduce((acc, prop, i) => {
      if (acc === 0) {
        const [p1, p2] = orders && orders[i] === 'desc' ? [b[prop], a[prop]] : [a[prop], b[prop]];
        acc = p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
      }
      return acc;
    }, 0)
  );
const users = [
  { name: 'fred', age: 48 },
  { name: 'barney', age: 36 },
  { name: 'fred', age: 40 }
];
orderBy(users, ['name', 'age'], ['asc', 'desc']); // [{name: 'barney', age: 36}, {name: 'fred', age: 48}, {name: 'fred', age: 40}]
orderBy(users, ['name', 'age']); // [{name: 'barney', age: 36}, {name: 'fred', age: 40}, {name: 'fred', age: 48}]

select - 选择对象中的属性值

从对象中检索出给定选择器指定的一组属性。

对每个选择器使用 Array.map() ,使用 String.split('.') 来分割每个选择器,并使用 Array.reduce() 来获取它所指示的值。

const select = (from, ...selectors) =>
  [...selectors].map(s => s.split('.').reduce((prev, cur) => prev && prev[cur], from));
const obj = { selector: { to: { val: 'val to select' } } };
select(obj, 'selector.to.val'); // ['val to select']
select(obj, 'selector.to.val', 'selector.to'); // ['val to select', { val: 'val to select' }]

shallowClone - 浅拷贝对象

创建一个对象的浅拷贝。

使用 Object.assign() 和一个空对象({})来创建原始对象的浅拷贝。

const shallowClone = obj => Object.assign({}, obj);
const a = { x: true, y: 1 };
const b = shallowClone(a); // a !== b

size - 获取数组,对象或字符串的大小。

获取数组,对象或字符串的大小。

Get type of val (array, object or string). Use length property for arrays. Use length or size value if available or number of keys for objects. Use size of a Blob object created from val for strings.

获取 valarrayobjectstring)的类型。 对于数组使用 length 属性。 对于对象,使用 lengthsize 如果可用的话,或者对象的键的数量。 对于字符串,使用根据 val 创建的Blob对象size

通过 split('') 将字符串拆分成字符数组并返回其长度。

const size = val =>
  Array.isArray(val)
    ? val.length
    : val && typeof val === 'object'
      ? val.size || val.length || Object.keys(val).length
      : typeof val === 'string' ? new Blob([val]).size : 0;
size([1, 2, 3, 4, 5]); // 5
size('size'); // 4
size({ one: 1, two: 2, three: 3 }); // 3

transform - 转换一个对象

对累加器和对象中的每个键(从左到右)应用一个函数。

使用 Object.keys(obj) 遍历对象中的每个键,Array.reduce() 调用对指定的累加器应用指定的函数。

const transform = (obj, fn, acc) => Object.keys(obj).reduce((a, k) => fn(a, obj[k], k, obj), acc);
transform(
  { a: 1, b: 2, c: 1 },
  (r, v, k) => {
    (r[v] || (r[v] = [])).push(k);
    return r;
  },
  {}
); // { '1': ['a', 'c'], '2': ['b'] }

truthCheckCollection - 集合中对象属性的真值检查

断言集合中所有元素(第一个参数)的指定属性(第二个参数)是否为真值(truthy)。

使用 Array.every() 来检查每个传递的对象是否具有指定的属性,以及是否返回一个真值。

const truthCheckCollection = (collection, pre) => collection.every(obj => obj[pre]);
truthCheckCollection([{ user: 'Tinky-Winky', sex: 'male' }, { user: 'Dipsy', sex: 'male' }], 'sex'); // true

String

anagrams - 颠倒字母顺序重新排列组合

⚠️ 警告: 这个函数的执行时间随着每个字符呈指数级增长。 超过8到10个字符的任何内容都会导致浏览器挂起,因为它试图解决所有不同的组合。

生成一个颠倒字母顺序的字符串,返回一个排列组合数组(包含重复)。

使用递归。 颠倒给定字符串中的每个字母,创建所有排列组合数组。 使用 Array.map() 将字母与每个部分字母组合在一起,然后使用 Array.reduce() 将所有字母组合到一个数组中。 基本情况是字符串 length 等于 21

const anagrams = str => {
  if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str];
  return str
    .split('')
    .reduce(
      (acc, letter, i) =>
        acc.concat(anagrams(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)),
      []
    );
};
anagrams('abc'); // ['abc','acb','bac','bca','cab','cba']

byteSize - 获取字符串的字节长度

返回字符串的字节长度。

将给定的字符串转换为Blob Object并查找其 size

const byteSize = str => new Blob([str]).size;
byteSize('😀'); // 4
byteSize('Hello World'); // 11

capitalize - 首字母大写

将字符串首字母大写。

使用数组 解构(destructuring) 和 String.toUpperCase() 大写第一个字母,用 ...rest 获得第一个字母后字符数组,然后 Array.join('') 再次使它成为一个字符串。 省略 lowerRest 参数以保持字符串的剩余部分不变,或者将其设置为 true 这将会使字符串的剩余部分都转换为小写。

const capitalize = ([first, ...rest], lowerRest = false) =>
  first.toUpperCase() + (lowerRest ? rest.join('').toLowerCase() : rest.join(''));
capitalize('fooBar'); // 'FooBar'
capitalize('fooBar', true); // 'Foobar'

capitalizeEveryWord - 大写每个单词的首字母

将字符串中每个单词的首字母大写。

使用 ``String.replace()来匹配每个单词的第一个字符,并使用String.toUpperCase()` 来将其大写。

const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
capitalizeEveryWord('hello world!'); // 'Hello World!'

decapitalize - 将字符串的第一个字母(大写字母)变成小写

将一个字符串的第一个字母(大写字母)变成小写。

使用数组解构和 String.toLowerCase() 将第一个字母变成小写,...rest 是获取第一个字母之后字符数组,然后使用 Array.join('') 使其再次拼接成为字符串。 省略 upperRest 参数来保持字符串的其余部分不变,或者将其设置为 true 来将字符串的其余部分转换为大写。

const decapitalize = ([first, ...rest], upperRest = false) =>
  first.toLowerCase() + (upperRest ? rest.join('').toUpperCase() : rest.join(''));
decapitalize('FooBar'); // 'fooBar'
decapitalize('FooBar', true); // 'fOOBAR'

escapeHTML - 转义HTML字符串

转义一个字符串,以用于HTML。

使用带有正则表达式的 String.replace() 来匹配需要转义的字符,使用一个回调函数使用字典(对象)替换每个字符为器关联的 HTML 实体字符。

const escapeHTML = str =>
  str.replace(
    /[&<>'"]/g,
    tag =>
      ({
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
      }[tag] || tag)
  );
escapeHTML('<a href="#">Me & you</a>'); // '&lt;a href=&quot;#&quot;&gt;Me &amp; you&lt;/a&gt;'

escapeRegExp - 转义正则表达式

转义字符串以在正则表达式中使用。

使用 String.replace() 来转义特殊字符。

const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
escapeRegExp('(test)'); // \\(test\\)

fromCamelCase - 转换驼峰字符串

转换驼峰拼写的字符串为特定格式。

使用 String.replace() 去除下划线,连字符和空格,并将驼峰拼写格式的单词转换为全小写。省略第二个参数 separator ,默认使用_分隔符。

const fromCamelCase = (str, separator = '_') =>
  str
    .replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2')
    .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2')
    .toLowerCase();
fromCamelCase('someDatabaseFieldName', ' '); // 'some database field name'
fromCamelCase('someLabelThatNeedsToBeCamelized', '-'); // 'some-label-that-needs-to-be-camelized'
fromCamelCase('someJavascriptProperty', '_'); // 'some_javascript_property'

isAbsoluteURL - 是否为绝对地址

如果给定的字符串是绝对URL,则返回 true ;否则返回 false

使用正则表达式来测试字符串是否是绝对URL。

const isAbsoluteURL = str => /^[a-z][a-z0-9+.-]*:/.test(str);
isAbsoluteURL('https://google.com'); // true
isAbsoluteURL('ftp://www.myserver.net'); // true
isAbsoluteURL('/foo/bar'); // false

isLowerCase - 检查字符串是否小写

检查字符串是否小写。

将给定的字符串转换为小写,使用 String.toLowerCase() 并将其与原始值进行比较。

const isLowerCase = str => str === str.toLowerCase();
isLowerCase('abc'); // true
isLowerCase('a3@$'); // true
isLowerCase('Ab4'); // false

isUpperCase - 检查一个字符串是否大写

检查一个字符串是否大写。

将给定的字符串转换为大写,使用 String.toUpperCase() 并将其与原始值进行比较。

const isUpperCase = str => str === str.toUpperCase();
isUpperCase('ABC'); // true
isLowerCase('A3@$'); // true
isLowerCase('aB4'); // false

mask - 使用 * 遮蔽字符串

用指定的掩码字符替换除最后 num 个字符以外的所有字符。

使用 String.slice() 获取需要被遮蔽的字符部分,并用正则表达式替换每个字符。 将遮蔽的字符与字符串的其余未遮蔽部分拼接起来。 省略第二个参数num,默认值为4,也就是说最后4个字符不被遮蔽。 如果num是负数,则不被遮蔽的字符将从字符串的开头算起。 省略第三个参数 mask ,默认使用字符'*'作为掩码遮蔽。

const mask = (cc, num = 4, mask = '*') =>
  ('' + cc).slice(0, -num).replace(/./g, mask) + ('' + cc).slice(-num);
mask(1234567890); // '******7890'
mask(1234567890, 3); // '*******890'
mask(1234567890, -4, '$'); // '$$$$567890'

palindrome - 检查回文

如果给定的字符串是回文,则返回 true ;否则返回 false

使用 toLowerCase() 转换字符串,并使用 String.replace() 从中删除非字母数字字符。 然后,在将其转换为 String.tolowerCase() 之后,将 String.split('') 为单独的字符,Array.reverse()String.join('')并与原始非反转字符串进行比较。

const palindrome = str => {
  const s = str.toLowerCase().replace(/[\W_]/g, '');
  return (
    s ===
    s
      .split('')
      .reverse()
      .join('')
  );
};
palindrome('taco cat'); // true

注:回文就是一句话正着读和反着读一模一样。

pluralize - 基于输入数字返回单词的单数或复数形式

基于输入数字返回单词的单数或复数形式。如果第一个参数是一个 object,那么它将使用一个闭包来返回一个函数,该函数可以自动实现多个单词,如果所提供的字典包含这个单词,它不会简单地以 s 结尾。

如果 num-1 或者 1,返回单词的单数形式。如果 num 是任何其他数字,返回复数形式。忽略第三个参数 plural,默认使单词 + s,或在必要时提供自定义的复数单词。如果第一个参数是一个 object ,则通过返回一个函数来使用闭包,该函数可以使用提供的字典来解析单词的正确复数形式。

const pluralize = (val, word, plural = word + 's') => {
  const _pluralize = (num, word, plural = word + 's') =>
    [1, -1].includes(Number(num)) ? word : plural;
  if (typeof val === 'object') return (num, word) => _pluralize(num, word, val[word]);
  return _pluralize(val, word, plural);
};
pluralize(0, 'apple'); // 'apples'
pluralize(1, 'apple'); // 'apple'
pluralize(2, 'apple'); // 'apples'
pluralize(2, 'person', 'people'); // 'people'

const PLURALS = {
  person: 'people',
  radius: 'radii'
};
const autoPluralize = pluralize(PLURALS);
autoPluralize(2, 'person'); // 'people'

reverseString - 反转一个字符串

反转一个字符串。

使用展开操作符(...) 和 Array.reverse() 来反转字符串中字符的顺序。使用 String.join('')合并字符串。

const reverseString = str => [...str].reverse().join('');
reverseString('foobar'); // 'raboof'

sortCharactersInString - 按字母顺序排列字符串中的字符

按字母顺序排序字符串中的字符。

使用扩展运算符(...),Array.sort()String.localeCompare()str 中的字符进行排序,使用 String.join('') 重新组合。

const sortCharactersInString = str =>
  [...str]
    .sort((a, b) => a.localeCompare(b))
    .join('');
sortCharactersInString('cabbage'); // 'aabbceg'

splitLines - 将多行字符串拆分为数组

将多行字符串根据行拆分为数组。

使用 String.split() 和一个正则表达式来匹配换行符并创建一个数组。

const splitLines = str => str.split(/\r?\n/);
splitLines('This\nis a\nmultiline\nstring.\n'); // ['This', 'is a', 'multiline', 'string.' , '']

toCamelCase - 转换为驼峰格式

将字符串转换为驼峰格式(camelcase)。

将字符串分解成单词,并将它们每个单词的第一个字母大写,重新拼接。使用一个正则表达式。

const toCamelCase = str => {
  let s =
    str &&
    str
      .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
      .map(x => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
      .join('');
  return s.slice(0, 1).toLowerCase() + s.slice(1);
};
toCamelCase('some_database_field_name'); // 'someDatabaseFieldName'
toCamelCase('Some label that needs to be camelized'); // 'someLabelThatNeedsToBeCamelized'
toCamelCase('some-javascript-property'); // 'someJavascriptProperty'
toCamelCase('some-mixed_string with spaces_underscores-and-hyphens'); // 'someMixedStringWithSpacesUnderscoresAndHyphens'

toKebabCase - 转化为连字符拼接格式

将字符串转换为连字符格式。

将字符串分解为单词,并添加-分隔符拼接。使用一个正则表达式。

const toKebabCase = str =>
  str &&
  str
    .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
    .map(x => x.toLowerCase())
    .join('-');
toKebabCase('camelCase'); // 'camel-case'
toKebabCase('some text'); // 'some-text'
toKebabCase('some-mixed_string With spaces_underscores-and-hyphens'); // 'some-mixed-string-with-spaces-underscores-and-hyphens'
toKebabCase('AllThe-small Things'); // "all-the-small-things"
toKebabCase('IAmListeningToFMWhileLoadingDifferentURLOnMyBrowserAndAlsoEditingSomeXMLAndHTML'); // "i-am-listening-to-fm-while-loading-different-url-on-my-browser-and-also-editing-xml-and-html"

toSnakeCase - 转化为下划线拼接格式

将一个字符串转换为下划线拼接格式。

将字符串拆分为单词,并添加_分隔符拼接。使用一个正则表达式。

const toSnakeCase = str => {
  str &&
    str
      .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
      .map(x => x.toLowerCase())
      .join('_');
};
toSnakeCase('camelCase'); // 'camel_case'
toSnakeCase('some text'); // 'some_text'
toSnakeCase('some-mixed_string With spaces_underscores-and-hyphens'); // 'some_mixed_string_with_spaces_underscores_and_hyphens'
toSnakeCase('AllThe-small Things'); // "all_the_smal_things"
toSnakeCase('IAmListeningToFMWhileLoadingDifferentURLOnMyBrowserAndAlsoEditingSomeXMLAndHTML'); // "i_am_listening_to_fm_while_loading_different_url_on_my_browser_and_also_editing_some_xml_and_html"

truncateString - 截断一个字符串

根据指定的长度截断字符串。

确定字符串的 length 是否大于 num。 返回截断所需长度的字符串,用 '...' 附加到结尾或原始字符串。

const truncateString = (str, num) =>
  str.length > num ? str.slice(0, num > 3 ? num - 3 : num) + '...' : str;
truncateString('boomerang', 7); // 'boom...'

unescapeHTML - 反转义 HTML 字符

反转义 HTML 字符。

使用带有正则表达式的 String.replace() 来匹配需要被转义的字符,使用一个回调函数使用字典(对象)替换每个 HTML 实体字符为其关联的非转义字符。

const unescapeHTML = str =>
  str.replace(
    /&amp;|&lt;|&gt;|&#39;|&quot;/g,
    tag =>
      ({
        '&amp;': '&',
        '&lt;': '<',
        '&gt;': '>',
        '&#39;': "'",
        '&quot;': '"'
      }[tag] || tag)
  );
unescapeHTML('&lt;a href=&quot;#&quot;&gt;Me &amp; you&lt;/a&gt;'); // '<a href="#">Me & you</a>'

words - 将字符串转换为单词数组

将给定的字符串转换为单词数组。

使用 String.split() 提供的模式(pattern)(默认为非 alpha 作为正则表达式)来转换为一个字符串数组。 使用 Array.filter() 去除任何空字符。 省略第二个参数来使用默认的正则表达式。

const words = (str, pattern = /[^a-zA-Z-]+/) => str.split(pattern).filter(Boolean);
words('I love javaScript!!'); // ["I", "love", "javaScript"]
words('python, javaScript & coffee'); // ["python", "javaScript", "coffee"]

Type

getType - 获取原生类型的值

返回值的原生类型。

返回值小写的构造函数名称,如果值为 undefinednull ,则返回 "undefined""null"

const getType = v =>
  v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
getType(new Set([1, 2, 3])); // 'set'

isArray - 是否为数组

检查给定的参数是否是一个数组。

使用 Array.isArray() 来检查一个值是否为一个数组。

const isArray = val => Array.isArray(val);
isArray([1]); // true

isArrayLike - 是否为类数组

检查提供的参数是否是类数组(即可迭代)。

使用展开运算符 (...) 检查提供的参数是否可以在 try... catch 块中进行迭代,并使用逗号运算符(,)返回适当的值。

const isArrayLike = val => {
  try {
    return [...val], true;
  } catch (e) {
    return false;
  }
};
isArrayLike(document.querySelectorAll('.className')); // true
isArrayLike('abc'); // true
isArrayLike(null); // false

isBoolean - 是否为布尔值

检查给定的参数是否是一个原生的布尔值。

使用 typeof 来检查一个值是否为一个布尔值。

const isBoolean = val => typeof val === 'boolean';
isBoolean(null); // false
isBoolean(false); // true

isFunction - 是否为函数

检查给定的参数是否是一个函数。

使用 typeof 来检查一个值是否为一个函数。

const isFunction = val => typeof val === 'function';
isFunction('x'); // false
isFunction(x => x); // true

isNull - 是否为Null

如果指定的值为 null ,则返回 true ;否则返回 false

使用严格等号运算符来检查 val 值是否等于 null

const isNull = val => val === null;
isNull(null); // true

isNumber - 是否为数字

检查给定的参数是否是一个数字。

使用 typeof 来检查一个值是否为一个数字。

const isNumber = val => typeof val === 'number';
isNumber('1'); // false
isNumber(1); // true

isObject - 是否为对象

如果传递的值是一个对象,则返回一个布尔值。

使用 Object 构造函数为给定值创建对象包装。 如果该值为 nullundefined ,则创建并返回一个空对象。 否则,返回一个对应于给定值的类型的对象。

const isObject = obj => obj === Object(obj);
isObject([1, 2, 3, 4]); // true
isObject([]); // true
isObject(['Hello!']); // true
isObject({ a: 1 }); // true
isObject({}); // true
isObject(true); // false

isPrimitive - 判断值是否为原始值

返回一个布尔值,确定专递的值是否为原始值。

在不是原始类型的字符串数组上使用 Array.includes() , 使用 typeof 提供类型。 由于 typeof null 被求值为 'object',所以需要直接比较。

const isPrimitive = val => !['object', 'function'].includes(typeof val) || val === null;
isPrimitive(null); // true
isPrimitive(50); // true
isPrimitive('Hello!'); // true
isPrimitive(false); // true
isPrimitive(Symbol()); // true
isPrimitive([]); // false

isPromiseLike - 是否为类 Promise

如果一个对象看起来像一个Promise),则返回 true,否则返回 false

检查对象是不为 null ,它的 typeof 是否匹配 objectfunction ,如果它有.then属性,这也是一个 function

const isPromiseLike = obj =>
  obj !== null &&
  (typeof obj === 'object' || typeof obj === 'function') &&
  typeof obj.then === 'function';
isPromiseLike({
  then: function() {
    return '';
  }
}); // true
isPromiseLike(null); // false
isPromiseLike({}); // false

isString - 判断是否为字符串

检查给定的参数是否是一个字符串。

使用 typeof 来检查一个值是否为一个字符串。

const isString = val => typeof val === 'string';
isString('10'); // true

isSymbol - 判断是否为symbol

检查给定的参数是否是一个 symbol。

使用 typeof 来检查一个值是否为一个 symbol 。

const isSymbol = val => typeof val === 'symbol';
isSymbol(Symbol('x')); // true

isValidJSON - 是否是有效的JSON

检查提供的参数是否是有效的JSON。

使用 JSON.parse()try... catch 块来检查提供的参数是否是有效的JSON。

const isValidJSON = obj => {
  try {
    JSON.parse(obj);
    return true;
  } catch (e) {
    return false;
  }
};
isValidJSON('{"name":"Adam","age":20}'); // true
isValidJSON('{"name":"Adam",age:"20"}'); // false
isValidJSON(null); // true

Utility

cloneRegExp - 克隆正则表达式

克隆一个正则表达式。

使用 new RegExp()RegExp.sourceRegExp.flags 来克隆给定的正则表达式。

const cloneRegExp = regExp => new RegExp(regExp.source, regExp.flags);
const regExp = /lorem ipsum/gi;
const regExp2 = cloneRegExp(regExp); // /lorem ipsum/gi

coalesce - 返回第一个非空/未定义的参数

返回第一个 non-null/undefined 的参数。

使用 Array.find() 返回第一个非 null / undefined 的参数。

const coalesce = (...args) => args.find(_ => ![undefined, null].includes(_));
coalesce(null, undefined, '', NaN, 'Waldo'); // ""

coalesceFactory - 自定义的 coalesce 函数

返回自定义的 coalesce 函数,该函数返回从提供的参数验证函数返回 true 的第一个参数。

使用 Array.find() 返回从提供的参数验证函数返回 true 的第一个参数。

const coalesceFactory = valid => (...args) => args.find(valid);
const customCoalesce = coalesceFactory(_ => ![null, undefined, '', NaN].includes(_));
customCoalesce(undefined, null, NaN, '', 'Waldo'); // "Waldo"

extendHex - 将 3 位数的颜色代码转换为 6 位数的表现形式

将 3 位数的颜色代码扩展为 6 位数的颜色代码。

使用 Array.map(), String.split()Array.join() 加入映射数组,将 3 位 RGB 十六进制颜色代码转换为 6 位数形式。 Array.slice() 用于从字符串开始删除#,因为输出中已经默认添加了。

const extendHex = shortHex =>
  '#' +
  shortHex
    .slice(shortHex.startsWith('#') ? 1 : 0)
    .split('')
    .map(x => x + x)
    .join('');
extendHex('#03f'); // '#0033ff'
extendHex('05a'); // '#0055aa'

getURLParameters - 网址参数

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

通过适当的正则表达式,使用 String.match() 来获得所有的键值对, Array.reduce() 来映射和组合成一个单一的对象。 将 location.search 作为参数传递给当前 url

const getURLParameters = url =>
  url
    .match(/([^?=&]+)(=([^&]*))/g)
    .reduce((a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a), {});
getURLParameters('http://url.com/page?name=Adam&surname=Smith'); // {name: 'Adam', surname: 'Smith'}

hexToRGB - Hex转RGB或者RGBAadvanced

将颜色代码转换为 rgb() 字符串。或者,如果提供了 alpha 值,则将颜色代码转换为 rgba() 字符串。

使用 &(和)运算符,按位右移运算符和掩码位将十六进制颜色代码(带或不带前缀 #)转换为 RGB值字符串。如果是3位数的颜色代码,首先将其转换为6位数的颜色代码。如果一个 alpha 值和 6 位十六进制颜色代码一起提供,则返回 rgba() 字符串。

const hexToRGB = hex => {
  let alpha = false,
    h = hex.slice(hex.startsWith('#') ? 1 : 0);
  if (h.length === 3) h = [...h].map(x => x + x).join('');
  else if (h.length === 8) alpha = true;
  h = parseInt(h, 16);
  return (
    'rgb' +
    (alpha ? 'a' : '') +
    '(' +
    (h >>> (alpha ? 24 : 16)) +
    ', ' +
    ((h & (alpha ? 0x00ff0000 : 0x00ff00)) >>> (alpha ? 16 : 8)) +
    ', ' +
    ((h & (alpha ? 0x0000ff00 : 0x0000ff)) >>> (alpha ? 8 : 0)) +
    (alpha ? `, ${h & 0x000000ff}` : '') +
    ')'
  );
};
hexToRGB('#27ae60ff'); // 'rgba(39, 174, 96, 255)'
hexToRGB('27ae60'); // 'rgb(39, 174, 96)'
hexToRGB('#fff'); // 'rgb(255, 255, 255)'

httpGet - GET 请求

向传递的 URL 发出一个 GET 请求。

使用 XMLHttpRequest web API 向给定的 url 发出 get 请求。 通过调用给定的 callbackresponseText 来处理 onload 事件。 通过运行提供的 err 函数,处理onerror事件。 省略第四个参数 err ,默认将错误记录到控制台的 error 流。

const httpGet = (url, callback, err = console.error) => {
  const request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.onload = () => callback(request.responseText);
  request.onerror = () => err(request);
  request.send();
};
httpGet(
  'https://jsonplaceholder.typicode.com/posts/1',
  console.log
); /* 
Logs: {
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
*/

httpPost - POST 请求

对传递的 URL 发出一个 POST 请求。

使用 XMLHttpRequest web api 对给定的url 发出一个 post 请求。 用 setRequestHeader 方法设置 HTTP 请求头的值。 通过调用给定的 callbackresponseText 来处理 onload 事件。 通过运行提供的 err 函数,处理onerror事件。 省略第三个参数 data ,不发送数据到提供的 url 。 省略第四个参数 err ,默认将错误记录到控制台的 error 流。

const httpPost = (url, callback, data = null, err = console.error) => {
  const request = new XMLHttpRequest();
  request.open('POST', url, true);
  request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
  request.onload = () => callback(request.responseText);
  request.onerror = () => err(request);
  request.send(data);
};
const newPost = {
  userId: 1,
  id: 1337,
  title: 'Foo',
  body: 'bar bar bar'
};
const data = JSON.stringify(newPost);
httpPost(
  'https://jsonplaceholder.typicode.com/posts',
  console.log,
  data
); /*
Logs: {
  "userId": 1,
  "id": 1337,
  "title": "Foo",
  "body": "bar bar bar"
}
*/

parseCookie - 返回所有 cookie 的 name-value 对的对象

解析 HTTP Cookie 标头字符串并返回所有 cookie 的 name-value 对的对象。

使用 String.split(';') 将键值对彼此分开。 使用 Array.map()String.split('=') 将键与每对中的值分开。 使用 Array.reduce()decodeURIComponent() 创建一个包含所有键值对的对象。

const parseCookie = str =>
  str
    .split(';')
    .map(v => v.split('='))
    .reduce((acc, v) => {
      acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
      return acc;
    }, {});
parseCookie('foo=bar; equation=E%3Dmc%5E2'); // { foo: 'bar', equation: 'E=mc^2' }

prettyBytes - 美化字节数

将字节数转换为可读的字符串。

根据存取字节数常用的单位构建一个数组字典。 使用 Number.toPrecision() 将数字截断为一定数量的数字。 将构建、美化好的字符串返回,并考虑提供的选项以及是否为负值。 省略第二个参数 precision,使用默认精度为’3的数字。 省略第三个参数addSpace`,默认情况下在数字和单位之间添加空格。

const prettyBytes = (num, precision = 3, addSpace = true) => {
  const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  if (Math.abs(num) < 1) return num + (addSpace ? ' ' : '') + UNITS[0];
  const exponent = Math.min(Math.floor(Math.log10(num < 0 ? -num : num) / 3), UNITS.length - 1);
  const n = Number(((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision));
  return (num < 0 ? '-' : '') + n + (addSpace ? ' ' : '') + UNITS[exponent];
};
prettyBytes(1000); // "1 KB"
prettyBytes(-27145424323.5821, 5); // "-27.145 GB"
prettyBytes(123456789, 3, false); // "123MB"

randomHexColorCode - 随机生成 Hex 颜色值

生成一个随机的十六进制颜色代码。

使用 Math.random 生成一个随机的24位(6x4位)十六进制数。 使用位操作符,然后使用 toString(16) 将其转换为十六进制字符串。

const randomHexColorCode = () => {
  let n = ((Math.random() * 0xfffff) | 0).toString(16);
  return '#' + (n.length !== 6 ? ((Math.random() * 0xf) | 0).toString(16) + n : n);
};
randomHexColorCode(); // "#e34155"

RGBToHex - RGB转hex

将 RGB 值转换为颜色代码。

使用按位左移运算符(<<)和 toString(16) 将给定的RGB参数转换为十六进制字符串,然后使用 String.padStart(6,'0') 得到一个6位的十六进制值。

const RGBToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0');
RGBToHex(255, 165, 1); // 'ffa501'

serializeCookie - 将 cookie name-value 对序列化为 Set-Cookie 头字符串

将一个 cookie name-value 对序列化为 Set-Cookie 头字符串。

使用模板字面量和 encodeURIComponent() 来创建合适的字符串。

const serializeCookie = (name, val) => `${encodeURIComponent(name)}=${encodeURIComponent(val)}`;
serializeCookie('foo', 'bar'); // 'foo=bar'

timeTaken - 计算函数执行所花费的时间

计算一个函数执行的时间。

使用 console.time()console.timeEnd() 来测量开始和结束时间之间的差,以确定回调执行的时间。

const timeTaken = callback => {
  console.time('timeTaken');
  const r = callback();
  console.timeEnd('timeTaken');
  return r;
};
timeTaken(() => Math.pow(2, 10)); // 1024, (logged): timeTaken: 0.02099609375ms

toDecimalMark - 将数字转化为千分位格式

使用 toLocaleString() 将浮点数转换为 Decimal mark 格式。 将整数部分转化为用逗号分隔的字符串。

const toDecimalMark = num => num.toLocaleString('en-US');
toDecimalMark(12305030388.9087); // "12,305,030,388.909"

toOrdinalSuffix - 数字序号的后缀

为数字添加序号后缀。

使用模运算符(%)来查找各位和十位的值。查找哪些序号模式数字匹配。如果数字在十位模式中找到,请使用十位的序数。

const toOrdinalSuffix = num => {
  const int = parseInt(num),
    digits = [int % 10, int % 100],
    ordinals = ['st', 'nd', 'rd', 'th'],
    oPattern = [1, 2, 3, 4],
    tPattern = [11, 12, 13, 14, 15, 16, 17, 18, 19];
  return oPattern.includes(digits[0]) && !tPattern.includes(digits[1])
    ? int + ordinals[digits[0] - 1]
    : int + ordinals[3];
};
toOrdinalSuffix('123'); // "123rd"

validateNumber - 数字验证

如果给定值是一个数字,则返回 true ,否则返回false

使用 !isNaN()parseFloat() 来检查参数是否是一个数字。使用 isFinite() 来检查数字是否是有限数。使用 Number() 来检查强制转换是否成立。

const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;
validateNumber('10'); // true

yesNo - YES OR NO

如果字符串为y/yes,则返回 true ;如果字符串为n/no,则返回 false

使用 RegExp.test() 来检查字符串是否为y/yes 或者 n/no。 省略第二个参数,def 将默认答案设置为 no

const yesNo = (val, def = false) =>
  /^(y|yes)$/i.test(val) ? true : /^(n|no)$/i.test(val) ? false : def;
yesNo('Y'); // true
yesNo('yes'); // true
yesNo('No'); // false
yesNo('Foo', true); // true