【推荐收藏】初学者可以玩转的 50 个 JavaScript 函数

2,929 阅读12分钟

前言

前段时间,看到一个很不错的 GitHub 仓库 30-seconds-of-code,该仓库的核心理念是:“你只要花 30 秒,就可以掌握开发中需要的函数”。其中,30 秒意味着每个函数的实现会出乎你意料的精简,这个过程不可避免地会涉及一些 JavaScript 的小技巧(奇淫巧技)。

并且, 30-seconds-of-code 将 JavaScript 函数分为了三个等级:初级、中等、高级。那么,本文摘取并翻译了其中前 50 个初级水平的 JavaScript 函数分享给大家。

正文开始~

1. all

检查提供的函数是否对数组中的所有值都返回 true

  • 使用 Array.prototype.every() 来测试数组的所有元素是否都在 fn 中都返回 true
  • 省略第二个参数 fn,使用 Boolean 来作为默认值
const all = (arr, fn = Boolean) => arr.every(fn);
all([4, 2, 3], x => x > 1); // true
all([1, 2, 3]); // true

2. allEqual

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

  • 使用 Array.prototype.every() 来检查数组中的所有元素是否都和第一个元素一样。
  • 使用严格的比较运算符来比较数组中的元素,这不会考虑 NaN 的自不相等。
const allEqual = arr => arr.every(val => val === arr[0]);
allEqual([1, 2, 3, 4, 5, 6]); // false
allEqual([1, 1, 1, 1]); // true

3. and

检测是否两个参数都为 true

  • 在两个给定的值上面使用逻辑且 && 运算符。
const and = (a, b) => a && b;
and(true, true); // true
and(true, false); // false
and(false, false); // false

4. any

检查所提供的函数是否为数组中至少一个元素返回 ture

  • 使用 Array.prototype.some() 来测试是否数组中有元素基于 fn 返回 true
  • 省略第二个参数 fn,会将 Boolean 作为默认值。
const any = (arr, fn = Boolean) => arr.some(fn);
any([0, 1, 2, 0], x => x >= 2); // true
any([0, 0, 1, 0]); // true

5. approximatelyEqual

检查两个数字是否大致相等。

  • 使用 Math.abs() 将两个数的绝对值之差和 epsilon 比较。
  • 省略第三个参数 epsilon 时,将会使用 0.001 来作为默认值。
const approximatelyEqual = (v1, v2, epsilon = 0.001) =>
  Math.abs(v1 - v2) < epsilon;
approximatelyEqual(Math.PI / 2.0, 1.5708); // true

6. arithmeticProgression

在算术级数中创建一个数字数组,从给定的正整数开始,一直到指定的限制。

  • 使用 Array.from() 来创建一个所需长度(lim/n)的数组,并且使用 map 函数来给它填充指定范围内所需要的值。
const arithmeticProgression  = (n, lim) => 
  Array.from({ length: Math.ceil(lim / n) }, (_, i) => (i + 1) * n );
arithmeticProgression(5, 25); // [5, 10, 15, 20, 25]

7. atob

解码用 base-64 编码的字符串数据。

  • 根据给定的 base-64 编码的字符串创建一个 Buffer,并且使用 Buffer.toString('binary') 来返回解码后的字符串。
const atob = str => Buffer.from(str, 'base64').toString('binary');
atob('Zm9vYmFy'); // 'foobar'

8. average

计算两个或者多个数字的平均值。

  • 使用 Array.prototype.reduce() 添加每个值到访问器中,初始值为 8
  • 通过数组的长度划分数组。
const average = (...nums) =>
  nums.reduce((acc, val) => acc + val, 0) / nums.length;
average(...[1, 2, 3]); // 2
average(1, 2, 3); // 2

10. binomialCoefficient

计算从 n 项中选择 k 项不重复且无顺序的方法数。

  • 使用 Number.isNaN() 来检查两个值中是否存在 isNaN
  • 检查 k 是否小于 0,大于或等于 n,等于 1n-1,并返回合适的结果。
  • 检查 n-k 是否小于 k,并相应的交换它们的值。
  • 2k 循环,并计算二项式系数。
  • 使用 Math.round() 来考虑计算中的舍入误差。
const binomialCoefficient = (n, k) => {
  if (Number.isNaN(n) || Number.isNaN(k)) return NaN;
  if (k < 0 || k > n) return 0;
  if (k === 0 || k === n) return 1;
  if (k === 1 || k === n - 1) return n;
  if (n - k < k) k = n - k;
  let res = n;
  for (let j = 2; j <= k; j++) res *= (n - j + 1) / j;
  return Math.round(res);
};
binomialCoefficient(8, 2); // 28

11. both

检查两个给定的函数是否为给定参数都返回 true

  • 对提供的 args 调用两个函数的结果,并使用逻辑与(&&)运算符
const both = (f, g) => (...args) => f(...args) && g(...args);
const isEven = num => num % 2 === 0;
const isPositive = num => num > 0;
const isPositiveEven = both(isEven, isPositive);
isPositiveEven(4); // true
isPositiveEven(-2); // false

12. btoa

从字符串对象创建一个 base 64 编码的 ASCII 字符串,其中字符串中的每个字符都被视为二进制数据中的一个字节。

  • 使用二进制编码为给定的字符串创建一个 Buffer,并且使用 Buffer.toString('base') 来返回编码后的字符串。
const btoa = str => Buffer.from(str, 'binary').toString('base64');
btoa('foobar'); // 'Zm9vYmFy'

需要注意的是,Buffer 对象需要在 Node 环境下使用。

13. byteSize

以字节为单位返回一个字符串。

  • 将给定的字符串转化为 Blob 对象
  • 以字节为单位使用 Blob.size 来获取字符串的长度。
const byteSize = str => new Blob([str]).size;
byteSize('😀'); // 4
byteSize('Hello World'); // 11

14. castArray

如果提供的值不是数组,则将它转为数组。

  • 使用 Array.prototype.isArray() 来确定 val 是否为数组,并按原样返回或相应地封装在数组中。
const castArray = val => (Array.isArray(val) ? val : [val]);
castArray('foo'); // ['foo']
castArray([1]); // [1]

15. celsiusToFahrenheit

将摄氏温度转为华氏度。

  • 遵循转换方程 F = 1.8 * C + 32
const celsiusToFahrenheit = degrees => 1.8 * degrees + 32;
celsiusToFahrenheit(33); // 91.4

16. 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

17. coalesce

返回第一个定义了的不为 null 的参数

  • 使用 Array.prototype.find()Array.prototype.includes() 来发现第一个不等于 undefinednull 的值。
const coalesce = (...args) => args.find(v => ![undefined, null].includes(v));
coalesce(null, undefined, '', NaN, 'Waldo'); // ''

18. compact

移除数组中的假值。

  • 使用 Array.prototype.filter() 来过滤假值(falsenull0""undefinedNaN)。
const compact = arr => arr.filter(Boolean);
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]); 
// [ 1, 2, 3, 'a', 's', 34 ]

19. compactWhitespace

压缩字符串中的空白。

  • 使用 String.prototype.replace() 和正则表达式来将所有出现的 2 个或更多空白字符串替换为单个空格。
const compactWhitespace = str => str.replace(/\s{2,}/g, ' ');
compactWhitespace('Lorem    Ipsum'); // 'Lorem Ipsum'
compactWhitespace('Lorem \n Ipsum'); // 'Lorem Ipsum'

20. complement

返回一个函数,该函数是给定函数 fn 的逻辑补码。

  • 在提供 args 参数调用 fn 的结果使用逻辑运算符 !
const complement = fn => (...args) => !fn(...args);
const isEven = num => num % 2 === 0;
const isOdd = complement(isEven);
isOdd(2); // false
isOdd(3); // true

21. containsWhitespace

检查是否给定的字符串中有空字符。

  • 使用 RegExp.prototype.test() 和合适正则表达式来检查是否给定的字符串包含空字符。
const containsWhitespace = str => /\s/.test(str);
containsWhitespace('lorem'); // false
containsWhitespace('lorem ipsum'); // true

22. copySign

返回第一个数字的绝对值,但是返回的是第二个值的符号。

  • 使用 Math.sign() 来检查是否两个数字有相同的符号。
  • 返回 x 如果它们一样,否则返回 -x
const copySign = (x, y) => Math.sign(x) === Math.sign(y) ? x : -x;
copySign(2, 3); // 2
copySign(2, -3); // -2
copySign(-2, 3); // 2
copySign(-2, -3); // -2

23. createDirIfNotExists

创建一个目录,如果它不存在。

  • 使用 fs.existsSync() 来检查目录是否存在,使用 fs.mkdirSync() 来创建目录。
const fs = require('fs');

const createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined);
createDirIfNotExists('test');
// creates the directory 'test', if it doesn't exist

24. createElement

根据字符串创建元素(没有添加到文档中的) 如果给定的字符串含有多个元素,只会返回第一个。

  • 使用 Document.createElement() 来创建一个新的元素。
  • 使用 Element.innerHTML 将其内部的 HTML 设为提供的字符串参数。
  • 使用 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'

25. currentURL

返回当前的 URL。

  • 使用 Window.location.href 获取当前的 URL。
const currentURL = () => window.location.href;
currentURL(); // 'https://www.google.com/'

26. dayName

Date 对象上获取工作日的名称。

  • 使用 Date.prototype.toLocaleDateString() 以及 { weekday: 'long' } 选项来检索工作日。
  • 使用可选的第二个参数来获取特定语言的名称,或者省略它使用默认的地区设置。
const dayName = (date, locale) =>
  date.toLocaleDateString(locale, { weekday: 'long' });
dayName(new Date()); // 'Saturday'
dayName(new Date('09/23/2020'), 'de-DE'); // 'Samstag'

27. dayOfYear

Date 对象上获取一年中的某一天(数值在 1-366 之间)。

  • 使用 new Date()Date.prototype.getFullYear() 来获取一年中的第一天来作为 Date 对象。
  • date 减去一年中的第一天,并且每天的毫秒数得到结果。
  • 使用 Math.floor() 将得到的数字适当地四舍五入为整数。
const dayOfYear = date =>
  Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
dayOfYear(new Date()); // 272

28. daysAgo

以字符串表现形式计算从今天开始 n 天前的日前。

  • 使用 new Date 来获取当前的日前,Math.abs()Date.prototype.getDate() 相应地更新日前,并且使用 Date.prototype.setDate() 设置结果。
  • 使用 Date.prototype.toISOString() 来会返回 yyyy-mm-dd 格式的字符串。
const daysAgo = n => {
  let d = new Date();
  d.setDate(d.getDate() - Math.abs(n));
  return d.toISOString().split('T')[0];
};
daysAgo(20); // 2020-09-16 (if current date is 2020-10-06)

29. daysFromNow

以字符串表现形式计算从今天算起 n 天后的日期。

  • 使用 new Date() 获取当前的日前,Math.abs()Date.prototype.getDate() 来相应地更新人气,并且使用 Date.prototype.setDate() 来设置结果。
  • 使用 Date.prototype.toISOString() 来返回 yyyy-mm-dd 的格式字符串。
const daysFromNow = n => {
  let d = new Date();
  d.setDate(d.getDate() + Math.abs(n));
  return d.toISOString().split('T')[0];
};
daysFromNow(5); // 2020-10-13 (if current date is 2020-10-08)

30. degreesToRads

将角度从度转为弧。

  • 使用 Math.PI 和度转为弧度的公式来将角度从度转为弧。
const degreesToRads = deg => (deg * Math.PI) / 180.0;
degreesToRads(90.0); // ~1.5708

31. difference

计算两个数组的差异,而不过滤重复的值。

  • 根据 b 创建 Set 集合来获取 b 中的唯一值。
  • a 使用 Array.prototype.filter() 来保留不在 b 中的值,以及使用 Set.prototype.has()
const difference = (a, b) => {
  const s = new Set(b);
  return a.filter(x => !s.has(x));
};
difference([1, 2, 3, 3], [1, 2, 4]); // [3, 3]

32. digitize

将数字转为数字数组,必要时移除它的符号。

  • 使用 Math.abs() 把数字的符号去掉。
  • 将数字转为字符串,使用展开运算符(...)来构造数组。
  • 使用 Array.prototype.map()parseInt() 将每个值转为整型。
const digitize = n => [...`${Math.abs(n)}`].map(i => parseInt(i));
digitize(123); // [1, 2, 3]
digitize(-123); // [1, 2, 3]

33. distance

计算两个点之间的距离。

  • 使用 Math.hypot() 来计算两个点之间的欧几里德距离。
const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
distance(1, 1, 2, 3); // ~2.2361

34. divmod

返回给定数字的商和余数构成的数组。

  • 使用 Math.floor() 来获取 x / y 的商。
  • 使用模运算符来获取 x / y 的余数。
const divmod = (x, y) => [Math.floor(x / y), x % y];
divmod(8, 3); // [2, 2]
divmod(3, 8); // [0, 3]
divmod(5, 5); // [1, 0]

35. drop

创建一个移除了左边起n 个元素的数组。

  • 使用 Array.prototype.slice() 来移除从左边算起制定数字个的元素。
  • 省略最后一个参数 n,默认值为 1
const drop = (arr, n = 1) => arr.slice(n);
drop([1, 2, 3]); // [2, 3]
drop([1, 2, 3], 2); // [3]
drop([1, 2, 3], 42); // []

36. dropRight

创建一个移除了右边起n 个元素的数组。

  • 使用 Array.prototype.slice() 来移除从右边算起制定数字个的元素。
  • 省略最后一个参数 n,默认值为 1
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); // []

37. either

检查对于给定的参数集是否至少有一个函数会返回 true

  • 在使用提供的参赛 args 调用函数的结果上使用逻辑与运算符(||
  • Use the logical or (||) operator on the result of calling the two functions with the supplied args.
const either = (f, g) => (...args) => f(...args) || g(...args);
const isEven = num => num % 2 === 0;
const isPositive = num => num > 0;
const isPositiveOrEven = either(isPositive, isEven);
isPositiveOrEven(4); // true
isPositiveOrEven(3); // true

38. elementIsFocused

检查是否给定的元素是聚焦的。

  • 使用 Document.activeElement 来决定是否给定的元素是聚焦的。
const elementIsFocused = el => (el === document.activeElement);
elementIsFocused(el); // true if the element is focused

39. everyNth

返回数组中每个元素的 nth

  • 使用 Array.prototype.filter() 来创建一个包含给定数组的每个 nth 元素的新数组。
const everyNth = (arr, nth) => arr.filter((e, i) => i % nth === nth - 1);
everyNth([1, 2, 3, 4, 5, 6], 2); // [ 2, 4, 6 ]

40. expandTabs

将 tabs 转为空格,其中每个制表符对应 count 空格。

  • 使用 String.prototype.replace() 、正则表达式、String.prototype.repeat() 来将每个 tab 字符替换为 count 空格。
const expandTabs = (str, count) => str.replace(/\t/g, ' '.repeat(count));
expandTabs('\t\tlorem', 3); // '      lorem'

41. factorial

计算一个数的阶乘。

  • 使用递归。
  • 如果 n 小于或等于 1,则返回 1
  • 否则,返回 n 的乘积和 n-1 的阶乘。
  • 如果 n 是负数的话,则返回 TypeError
const factorial = n =>
  n < 0
    ? (() => {
        throw new TypeError('Negative numbers are not allowed!');
      })()
    : n <= 1
    ? 1
    : n * factorial(n - 1);
factorial(6); // 720

42. fahrenheitToCelsius

将华氏度转为摄氏度。

  • 遵循 C = (F - 32) * 5/9 的转换公式。
const fahrenheitToCelsius = degrees => (degrees - 32) * 5 / 9;
fahrenheitToCelsius(32); // 0

43. filterNonUnique

筛选掉非唯一值来创建数组。

  • 使用 new Set() 和展开运算符 ... 来创建包含在 arr 中唯一值的数组。
  • 使用 Array.prototype.filter() 来创建只包含唯一值的数组。
const filterNonUnique = arr =>
  [...new Set(arr)].filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
filterNonUnique([1, 2, 2, 3, 4, 4, 5]); // [1, 3, 5]

44. filterUnique

使用筛选出来的唯一值创建数组。

  • 使用 new Set()... 展开运算符在 arr 中创建一个唯一值的数组。
  • 使用 Array.prototype.filter() 来创建只包含唯一值的数组。
const filterNonUnique = arr =>
  [...new Set(arr)].filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
filterNonUnique([1, 2, 2, 3, 4, 4, 5]); // [1, 3, 5]

45. findLast

查找给定函数返回 true 的最后一个元素。

  • 使用 Array.prototype.filter() 来移除 fn 返回 false 的元素。
  • 使用 Array.prototype.pop() 来获取过滤后数组的最后一个元素。
const findLast = (arr, fn) => arr.filter(fn).pop();
findLast([1, 2, 3, 4], n => n % 2 === 1); // 3

46. formatNumber

使用本地数字格式化数字。

  • 使用 Number.prototype.toLocaleString() 来将数字转为本地数字格式分隔符。
const formatNumber = num => num.toLocaleString();
formatNumber(123456); // '123,456' in `en-US`
formatNumber(15675436903); // '15.675.436.903' in `de-DE`

47. fromTimestamp

根据 Unix 时间戳创建一个 Date 对象。

  • 通过乘于 1000 来将时间戳转为秒。
  • 使用 new Date() 来创建一个新的 Date 对象。
const fromTimestamp = timestamp => new Date(timestamp * 1000);
fromTimestamp(1602162242); // 2020-10-08T13:04:02.000Z

48. functionName

打印出函数的名称。

  • 使用 console.debug() 和传入的函数的 name 属性来将函数的名称打印到控制台中。
  • 返回给定的函数 fn
const functionName = fn => (console.debug(fn.name), fn);
let m = functionName(Math.max)(5, 6);
// max (logged in debug channel of console)
// m = 6

49. gcd

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

  • 内部的 _gcd 函数会使用递归。
  • 基本情况是当 y 等于 0 时,在本例中,返回 x
  • 否则,返回 y 的GCD和除法 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

50. getAncestors

将元素的所有祖先从文档根返回到给定元素。

  • 使用 Node.parentNodewhile 循环来向上移动元素的祖先树。
  • 使用 Array.prototype.unshift() 将每个新祖先添加到数组的开头。
const getAncestors = el => {
  let ancestors = [];
  while (el) {
    ancestors.unshift(el);
    el = el.parentNode;
  }
  return ancestors;
};
getAncestors(document.querySelector('nav')); 
// [document, html, body, header, nav]

参考

30-seconds-of-code

❤️ 爱心三连击

写作不易,可以的话麻烦点个赞,这会成为我坚持写作的动力,奥力给!!!

我是五柳,喜欢创新、捣鼓源码,专注于 Vue3 源码、Vite 源码、前端工程化等技术领域分享,欢迎关注我的「微信公众号:Code center」。