置顶

30 seconds of Javascript

在Gitgub上看见一个好玩的项目 30 seconds of code,js真好玩。

🔌 适配

ary

创建一个最多可以接受n个参数的函数。
调用提供的函数,fn,最多n个参数。
运用Array.prototype.slice(0,n)和展开运算符

const ary = (fn, n) => (...args) => fn(...args.slice(0, n));

🌰栗子:

const firstTwoMax = ary(Math.max, 2);
[[2, 6, 'a'], [8, 4, 6], [10]].map(x => firstTwoMax(...x));  // [6, 8, 10]

over

创建一个函数,该函数使用它接收的参数调用每个提供的函数并返回结果。
使用Array.prototype.map()Function.prototype.apply()将每个函数应用于给定的参数。

const ary = (fn, n) => (...args) => fn(...args.slice(0, n));

🌰栗子:

const firstTwoMax = ary(Math.max, 2);
[[2, 6, 'a'], [8, 4, 6], [10]].map(x => firstTwoMax(...x));  // [6, 8, 10]

unary

创建只接受一个参数的函数。
调用提供的函数fn, 只返回第一个参数。

const unary = fn => val => fn(val);

🌰栗子:

['6', '8', '10'].map(unary(parseInt)); // [6, 8, 10]

📚 Array

all

如果为集合中的所有元素提供的函数返回true,则返回true,否则返回false
使用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

allEqual

判断数组的所有元素是否相等。
使用Array.prototype.every()判断数组的所有元素是否与第一个元素相等。

const allEqual = arr => arr.every(val => val === arr[0]);

🌰栗子:

allEqual([1, 2, 3, 4, 5, 6]); // false
allEqual([1, 1, 1, 1]); // true

some

如果为集合中的至少一个元素提供的函数返回true,则返回true,否则返回false
使用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

bifurcate

将值拆分为两组。 如果过滤器中的元素是真的,则集合中的对应元素属于第一组; 否则,它属于第二组。
使用Array.prototype.reduce()Array.prototype.push()根据过滤器向数组添加元素。

const bifurcate = (arr, filter) =>
  arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[], []]);

🌰栗子:

bifurcate(['beep', 'boop', 'foo', 'bar'], [true, true, false, true]); 
// [ ['beep', 'boop', 'bar'], ['foo'] ]

chunk

将数组分割为指定大小的数组。
使用Array.from()创建一个新数组,该数组符合指定大小。 使用Array.prototype.slice()将新数组的每个元素映射到一个大小的数组中。 如果原始数组无法均匀分割,则最终的数组包含其余元素。

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

使用Array.prototype.filter() 从数组中删除 (false, null, 0, “”, undefined, and 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.prototype.map()将数组的值映射到函数或属性名称。 使用Array.prototype.reduce()创建一个对象,其中的键是从映射结果生成的。

const countBy = (arr, fn) =>
  arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => {
    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.prototype.reduce()递增器计算数组每个元素出现的次数。

const countOccurrences = (arr, val) => 
arr.reduce((a, v) => (v === val ? a + 1 : a), 0);

🌰栗子:

countOccurrences([1, 1, 2, 1, 2, 3], 1); // 3

deepFlatten

深度展平一维数组。
运用递归。使用Array.prototype.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]
[1, [2], [[3], 4], 5].toString().split(',') //  ✅ [1,2,3,4,5]

filterNonUnique

过滤掉数组中的非唯一值。
使用Array.prototype.filter()用于仅包含唯一值的数组。

const filterNonUnique = arr => 
arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));

filterNonUnique([1, 2, 2, 3, 4, 4, 5]); // [1, 3, 5]

forEachRight

从数组的最后一个元素开始,为每个数组元素执行一次提供的函数。
使用Array.prototype.slice(0)克隆给定的数组,
使用Array.prototype.reverse()来反转它,
使用Array.prototype.forEach()/迭代反向数组。

const forEachRight = (arr, callback) =>
  arr
    .slice(0)
    .reverse()
    .forEach(callback);

🌰栗子:

forEachRight([1, 2, 3, 4], val => console.log(val)); // '4', '3', '2', '1'

indexOfAll

返回数组中val的所有索引。 如果val不存在,则返回[]。
使用Array.prototype.reduce()循环元素并存储匹配元素的索引,返回索引数组。

const indexOfAll = (arr, val) =>
arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), []);

🌰栗子:

indexOfAll([1, 2, 3, 1, 2, 3], 1); // [0,3]
indexOfAll([1, 2, 3], 4); // []

initialize2DArray

初始化给定宽度和高度的2D数组。
使用Array.prototype.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]]
initialize2DArray(1, 2); // [[null], [null]]

initializeArrayWithValues

使用指定的值初始化并填充数组。
使用Array(n)创建所需长度的数组,fill(v)填充。 如果val未传,默认值为0。

const initializeArrayWithValues = (n, val = 0) => Array(n).fill(val);

🌰栗子:

initializeArrayWithValues(5, 2); // [2, 2, 2, 2, 2]

mapObject

使用函数将数组的值映射到对象,其中键 - 值对由原始值作为键和映射值组成。
使用匿名内部函数作用域来声明未定义的内存空间,使用闭包来存储返回值。
使用新数组来存储数组,其中包含函数的数据集,并使用逗号运算符返回第二步,
而无需从一个上下文移动到另一个上下文(由于闭包和操作顺序)。

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 }

similarity

返回两个数组中都出现的元素数组。
使用Array.prototype.filter()过滤不属于数组的值,使用Array.prototype.includes判断。

const similarity = (arr, values) => arr.filter(v => values.includes(v));

🌰栗子:

similarity([1, 2, 3], [1, 2, 4]); // [1, 2]

union

返回两个数组中任何一个中只存在一次元素的数组。

使用a和b的所有值创建一个Set并用Array.from()转换为数组。

const union = (a, b) => Array.from(new Set([...a, ...b]));

🌰栗子:

union([1, 2, 3], [4, 3, 2]); // [1,2,3,4]

🌐 Browser

detectDeviceType

检测网站是否在移动设备或台式机/笔记本电脑中打开。
使用正则表达式测试navigator.userAgent属性,以确定设备是移动设备还是台式机/笔记本电脑。

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

🌰栗子:

detectDeviceType();  // Desktop

elementContains

如果父元素包含子元素,则返回true,否则返回false
先检查父元素与子元素不是同一元素,再使用parent.contains(child)检查父元素是否包含子元素。

const elementContains = (parent, child) => 
parent !== child && parent.contains(child);

🌰栗子:

_query (tag){
  return document.querySelector(tag)
}
elementContains(_query('head'),  _query('title'));  // true
elementContains(_query('body'),  _query('body')); // false

getScrollPosition

返回当前页面的滚动位置。
如果已定义,则使用pageXOffsetpageYOffset,否则使用scrollLeftscrollTop。 您可以省略el以使用窗口的默认值。

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

⏱️ Date

formatDuration

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

ms除以适当的值,以获得白天,小时,分钟,秒和毫秒的可读值。
使用 Object.entries()Array.prototype.filter()仅保留非零值。
使用Array.prototype.map()为每个值创建字符串。
使用String.prototype.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(([key, val]) => `${val} ${key}${val !== 1 ? 's' : ''}`)
    .join(', ');
};

🌰栗子:

formatDuration(1001); // '1 second, 1 millisecond'
formatDuration(34325055574); 
// '397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds'

🎛️ Function

debounce

const debounce = (fn, ms = 0) => {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), ms);
  };
};

🌰栗子:

window.addEventListener(
  'resize',
  debounce(() => {
    console.log(window.innerWidth);
    console.log(window.innerHeight);
  }, 250)
); // Will log the window dimensions at most every 250ms

throttle

const throttle = (fn, wait) => {
  let inThrottle, lastFn, lastTime;
  return function() {
    const context = this,
      args = arguments;
    if (!inThrottle) {
      fn.apply(context, args);
      lastTime = Date.now();
      inThrottle = true;
    } else {
      clearTimeout(lastFn);
      lastFn = setTimeout(function() {
        if (Date.now() - lastTime >= wait) {
          fn.apply(context, args);
          lastTime = Date.now();
        }
      }, Math.max(wait - (Date.now() - lastTime), 0));
    }
  };
};

🌰栗子:

window.addEventListener(
  'resize',
  throttle(function(evt) {
    console.log(window.innerWidth);
    console.log(window.innerHeight);
  }, 250)
); // Will log the window dimensions at most every 250ms

➗ Math

distance

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

const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);

🌰栗子:

distance(1, 1, 2, 3); // 2.23606797749979

factorial

使用递归计算数字的阶乘。
如果n小于或等于1,则返回1。否则,返回n的乘积和n - 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

gcd

计算两个或多个数字/数组之间的最大公约数。
内部_gcd函数使用递归计算返回yGCD和除法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

randomIntArrayInRange

返回指定范围内的n个随机整数的数组。
使用Array.from()创建一个特定长度的数组,Math.random()生成一个范围内的随机数,使用Math.floor向下取整。

const randomIntArrayInRange = (min, max, n = 1) =>
  Array.from({ length: n }, () => 
  Math.floor(Math.random() * (max - min + 1)) + min);

🌰栗子:

randomIntArrayInRange(12, 35, 10); // [ 34, 14, 27, 17, 30, 27, 20, 26, 21, 14 ]

sum

返回数组的总和。
使用Array.prototype.reduce()将每个值累加,使用值0初始化。

const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);

🌰栗子:

sum(1, 2, 3, 4); // 10
sum(...[1, 2, 3, 4]); // 10

🗃️ Object

deepClone

创建对象的深克隆。
使用Object.assign()和空对象({})创建原始的浅层克隆。
使用Object.keys()Array.prototype.forEach()来确定需要深度克隆的键值对。

const deepClone = obj => {
  let clone = Object.assign({}, obj);
  Object.keys(clone).forEach(
    key => (clone[key] = typeof obj[key] === 'object'
     ? deepClone(obj[key]) 
     : obj[key])
  );
  return Array.isArray(obj) 
  ? (clone.length = obj.length) && Array.from(clone) 
  : clone;
};

🌰栗子:

const a = { foo: 'bar', obj: { a: 1, b: 2 } };
const b = deepClone(a); // a !== b, a.obj !== b.obj

flattenObject

展平对象。
使用Object.keys(obj)Array.prototype.reduce()结合将每个叶节点转换为展平路径节点。

const flattenObject = (obj, prefix = '') =>
  Object.keys(obj).reduce((acc, k) => {
    const pre = prefix.length ? prefix + '.' : '';
    if (typeof obj[k] === 'object') {
    Object.assign(acc, flattenObject(obj[k], pre + k))
    };
    else acc[pre + k] = obj[k];
    return acc;
  }, {});

🌰栗子:

flattenObject({ a: { b: { c: 1 } }, d: 1 }); // { 'a.b.c': 1, d: 1 }

mapKeys

创建一个对象,其中包含通过为每个键运行提供的函数生成的键以及与提供的对象相同的值。
使用Object.keys(obj)迭代对象的键。
使用Array.prototype.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.prototype.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 }

objectFromPairs

根据给定的键值对创建对象。
使用Array.prototype.reduce()创建对象。

const objectFromPairs = arr => 
arr.reduce((a, [key, val]) => ((a[key] = val), a), {});

🌰栗子:

objectFromPairs([['a', 1], ['b', 2]]); // {a: 1, b: 2}

objectToPairs

从对象创建键值对数组的数组。
使用Object.keys()Array.prototype.map()迭代对象的键并生成数组。

const objectToPairs = obj => Object.keys(obj).map(k => [k, obj[k]]);

🌰栗子:

objectToPairs({ a: 1, b: 2 }); // [ ['a', 1], ['b', 2] ]

📜 String

fromCamelCase

转换字符串。
使用String.prototype.replace()删除下划线,连字符和空格,并将单词转换为camelcase。 省略第二个参数以使用_的默认分隔符。

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'

isAnagram

检查字符串是否是另一个字符串的字谜。
使用String.toLowerCase()String.prototype.replace()删除不必要的字符。
使用String.prototype.split(‘’)Array.prototype.sort()Array.prototype.join(‘’)检查是否相等。

const isAnagram = (str1, str2) => {
  const normalize = str =>
    str
      .toLowerCase()
      .replace(/[^a-z0-9]/gi, '')
      .split('')
      .sort()
      .join('');
  return normalize(str1) === normalize(str2);
};

🌰栗子:

isAnagram('iceman', 'cinema'); // true

mask

使用指定的掩码字符替换除最后一个字符数之外的所有字符。
使用String.prototype.slice()来获取将保持未屏蔽的字符部分。
使用String.padStart()以掩码字符填充字符串的开头,直到原始长度。

const mask = (cc, num = 4, mask = '*') => 
`${cc}`.slice(-num).padStart(`${cc}`.length, mask);
};

🌰栗子:

mask(1234567890); // '******7890'
mask(1234567890, 3); // '*******890'
mask(1234567890, -4, '$'); // '$$$$567890'

reverseString

反转字符串。
使用展开运算符(…)和Array.prototype.reverse()来反转字符串中字符的顺序。
使用String.prototype.join(‘’)转换成字符串。

const reverseString = str => [...str].reverse().join('');
};

🌰栗子:

reverseString('foobar'); // 'raboof'

stripHTMLTags

从字符串中删除HTML / XML标记。
使用正则表达式从字符串中删除HTML / XML标记。

const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');
};

🌰栗子:

stripHTMLTags('<p><em>lorem</em> <em>ipsum</em></p>'); 
// 'lorem ipsum'

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('IAmListeningToFMWhileLoadingDifferentURLOnMyBrowser'); 
// "i-am-listening-to-fm-while-loading-different-url-on-my-browser"

words

将给定的字符串转换为单词数组。
使用String.prototype.split()与默认为非字母作为正则使用,以转换为字符串数组。
使用Array.prototype.filter()删除任何空字符串。 默认的正则为/[^a-zA-Z-]+/

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"]

🔧 Utility

RGBToHex

RGB值转换为颜色代码。
使用按位左移运算符(<<)toString(16)
使用String.padStart(6,’0’)将RGB参数转换为十六进制字符串,以获得6位十六进制值。

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

🌰栗子:

RGBToHex(255, 165, 1); // 'ffa501'

toDecimalMarkc

使用toLocaleString()将浮点运算转换为Decimal标记形式——使用逗号分隔字符串与数字。

const toDecimalMark = num => num.toLocaleString('en-US');

🌰栗子:

toDecimalMark(12305030388.9087); // "12,305,030,388.909"

validateNumber

如果给定值是数字,则返回true,否则返回false。
使用!isNaN()parseFloat()判断参数是否为数字。
使用isFinite()判断数字是否有限数字。
使用Number()判断类型是否为数字。

const validateNumber = n => 
!isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;

🌰栗子:

validateNumber('10'); // true

yesNo

如果字符串是y / yes则返回true,如果字符串是n / no,则返回false
使用RegExp.test()检查字符串是否为y / yesn / nodef默认为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