30 second of code 15个有意思的代码段

2,653 阅读5分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

前言

30-seconds 是一个学习系列,基本都是通过简短的代码实现某些功能, 包含

等等,还有很多系列, 我们今天的主题是 30-seconds-of-code , 即简短的代码段。

挑选了我觉得比较有意思或者有意义的15个代码,我们一起开始简短代码之旅吧!

精选

URLJoin

地址拼接。 除此之外,URLSearchParamsURL都可以很好的处理QueryString, 更多详情参见 私藏的这些高级工具函数,你拥有几个?

const URLJoin = (...args) =>
  args
    .join('/')
    .replace(/[\/]+/g, '/')
    .replace(/^(.+):\//, '$1://')
    .replace(/^file:/, 'file:/')
    .replace(/\/(\?|&|#[^!])/g, '$1')
    .replace(/\?/g, '&')
    .replace('&', '?');

示例

URLJoin('http://www.google.com', 'a', '/b/cd', '?foo=123', '?bar=foo');
// 'http://www.google.com/a/b/cd?foo=123&bar=foo'

uncurry

减少嵌套函数调用次数,改变函数的传参方式。 这是反柯里化吗?

这个函数非常灵活,很有意思。

const uncurry = (fn, n = 1) => (...args) => {
  const next = acc => args => args.reduce((x, y) => x(y), acc);
  if (n > args.length) throw new RangeError('Arguments too few!');
  return next(fn)(args.slice(0, n));
};
const add = x => y => z => x + y + z;

const uncurriedAdd1 = uncurry(add, 1);
uncurriedAdd1(1)(2)(3)  // 6

const uncurriedAdd1 = uncurry(add, 2);
uncurriedAdd1(1,2)(3)  // 6

const uncurriedAdd3 = uncurry(add, 3);
uncurriedAdd3(1, 2, 3); // 6

onScrollStop

监听滚动停止事件,当停止滚动的时候,执行回调函数。

需要在停止滚动后,执行某些操作,还是很有用的。

const onScrollStop = callback => {
  let isScrolling;
  window.addEventListener(
    'scroll',
    e => {
      clearTimeout(isScrolling);
      isScrolling = setTimeout(() => {
        callback();
      }, 150);
    },
    false
  );
};
onScrollStop(() => {
  console.log('The user has stopped scrolling');
});

byteSize

返回字符串字节数。

都知道,中文和英文所占的字节数是不一样的,有占两个的,三个的,四个的,一个的。

英文一般一个字节,中文一般三个。

const byteSize = str => new Blob([str]).size;
byteSize('a') // 1
byteSize('س') // 2
byteSize('中') // 3
byteSize('😀'); // 4
byteSize('Hello World'); // 11

这还涉及 Unicode编码的知识,为了验证更多字符,你可以查阅 Unicode对应表

runPromisesInSeries

顺序执行promise, 实际上应该生成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

这个版本,不能传参,我在 私藏的这些高级工具函数,你拥有几个?, 实现了一个带参数版本。

function runPromises(promiseCreators, initData) {
    return promiseCreators
        .reduce((promise, next) => promise
                .then((data) => next(data))
            , Promise.resolve(initData));
}
var promise1 = function (data = 0) {
    return new Promise(resolve => {
        resolve(data + 1000);
    });
}
var promise2 = function (data) {
    return new Promise(resolve => {
        resolve(data -500);
    });
}

runPromises([promise1, promise2], 1).then(res=>console.log(res));  // 501

stringifyCircularJSON

将包含循环引用的 JSON 对象序列化为 JSON 格式。

其思路是使用WeakSet 保存数据进行对比。

const stringifyCircularJSON = obj => {
  const seen = new WeakSet();
  return JSON.stringify(obj, (k, v) => {
    if (v !== null && typeof v === 'object') {
      if (seen.has(v)) return;
      seen.add(v);
    }
    return v;
  });
};

const obj = { n: 42 };
obj.obj = obj;
stringifyCircularJSON(obj); // '{"n": 42}'

UUIDGeneratorBrowser

UUID生成器,除此之外URL.createObjectURL也可以生成UUID。这两种算是比较主流的方式。

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'
function genUUID() {
    const url = URL.createObjectURL(new Blob([]));
    // const uuid = url.split("/").pop();
    const uuid = url.substring(url.lastIndexOf('/')+ 1);
    URL.revokeObjectURL(url);
    return uuid;
}

genUUID() // cd205467-0120-47b0-9444-894736d873c7


addDaysToDate

时间添加天数,返回的是字符串。

const addDaysToDate = (date, n) => {
  const d = new Date(date);
  d.setDate(d.getDate() + n);
  return d.toISOString().split('T')[0];
};
addDaysToDate('2020-10-15', 10); // '2020-10-25'
addDaysToDate('2020-10-15', -10); // '2020-10-05'

compose

从右到左的复合函数。 没记错的话和 redux的compose 极其类似。

毕竟嘛,思路是一样的。

onst 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

deepGet

获取对象的多级属性,属性参数是数组。

const deepGet = (obj, keys) =>
  keys.reduce(
    (xs, x) => (xs && xs[x] !== null && xs[x] !== undefined ? xs[x] : null),
    obj
  );
let index = 2;
const data = {
  foo: {
    foz: [1, 2, 3],
    bar: {
      baz: ['a', 'b', 'c']
    }
  }
};
deepGet(data, ['foo', 'foz', index]); // get 3
deepGet(data, ['foo', 'bar', 'baz', 8, 'foz']); // null

当然 lodash 也提供了 .get, 当然实现的复杂度也高很对,其属性参数用的是字符串。

objectToQueryString

把对象转为 queryString,就这个queryString的转换,就有好几个牛气冲天的库。

下载量过 千万的 query-string 和 500万的 qs, 主要解决两个问题,其中一个就是 toQueryString。

const objectToQueryString = queryParameters => {
  return queryParameters
    ? Object.entries(queryParameters).reduce(
        (queryString, [key, val], index) => {
          const symbol = queryString.length === 0 ? '?' : '&';
          queryString +=
            typeof val === 'string' ? `${symbol}${key}=${val}` : '';
          return queryString;
        },
        ''
      )
    : '';
};
objectToQueryString({ page: '1', size: '2kg', key: undefined });
// '?page=1&size=2kg'

parseCookie

把cookie转为键值对。

作为一个操作前端,操作cookie是日常操作,我相信肯定有不少同志引入了第三方库,其实只需代几码就可以的。

const parseCookie = str =>
  str
    .split(';')
    .map(v => v.split('='))
    .reduce((acc, v) => {
      acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
      return acc;
    }, {});
parseCookie(document.cookie) 
// {_octo: "GH1.1.2059864283.1626332708", tz: "Asia/Shanghai"}

parseCookie('foo=bar; equation=E%3Dmc%5E2');
// { foo: 'bar', equation: 'E=mc^2' }

unfold

使用迭代器函数和初始种子值构建数组。

在造假数据或者随机数据的时候,比较有用。

const unfold = (fn, seed) => {
  let result = [],
    val = [null, seed];
  while ((val = fn(val[1]))) result.push(val[0]);
  return result;
};
var f = n => (n > 50 ? false : [-n, n + 10]);
unfold(f, 10); // [-10, -20, -30, -40, -50]

triggerEvent

触发给定元素上的特定事件,可以选择传递自定义数据。

这个在IE11以及一些低版本是有问题的,低版本使用的是 document.createEvent

const triggerEvent = (el, eventType, detail) =>
  el.dispatchEvent(new CustomEvent(eventType, { detail }));
triggerEvent(document.getElementById('myId'), 'click');
triggerEvent(document.getElementById('myId'), 'click', { username: 'bob' });

repeatGenerator

创建生成器,无限重复给定值。 有点意思,实用场景嘛,也许测试吧。

当然,中途你可以更新值。 这里想告诉大家的是 genetator有入参的概念。

const repeatGenerator = function* (val) {
  let v = val;
  while (true) {
    let newV = yield v;
    if (newV !== undefined) v = newV;
  }
};

const repeater = repeatGenerator(5);
repeater.next(); // { value: 5, done: false }
repeater.next(); // { value: 5, done: false }
repeater.next(4); // { value: 4, done: false }
repeater.next(); // { value: 4, done: false }

写在最后

如果你觉得不错,你的一赞一评就是我前行的最大动力。

技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,一起学习。

参考引用

30-seconds-of-code