Skip to content

判断各种数据

ts
// cache toString
const toTagString = Object.prototype.toString;
const FuncToString = Function.prototype.toString;

// get value object tag like: [object Array]
export function getObjectTag(value: unknown): string {
  return toTagString.call(value);
}

// value is object or not
export function isObject(value: unknown): boolean {
  return value !== null && typeof value === "object";
}

// value instance of Object or not
export function isPlainObject(value: unknown): boolean {
  return getObjectTag(value) === "[object Object]";
}

// value is number or not
export function isNumber(value: unknown): boolean {
  return typeof value === "number";
}

// value is string or not
export function isString(value: unknown): boolean {
  return typeof value === "string";
}

// value is undefined or not
export function isUndefind(value: unknown): boolean {
  return value === undefined;
}

// value is null or not
export function isNull(value: unknown): boolean {
  return value === null;
}

// value is null or undefined
export function isNullable(value: unknown): boolean {
  return Boolean(value === null || value === undefined);
}

// value is boolean or not
export function isBoolean(value: unknown): boolean {
  return typeof value === "boolean";
}

// value is symbol or not
export function isSymbol(value: unknown): boolean {
  return typeof value === "symbol";
}

// value is function or not
export function isFunction(value: unknown): boolean {
  return typeof value === "function";
}
export const isCallable = isFunction;

// value is isNative function or not
export function isNativeFunction(func: Function): boolean {
  return isFunction(func) && FuncToString.call(func).includes("[native code]");
}

// value is promise like or not
export function isPromise(p: unknown): boolean {
  return isObject(p) && isFunction(p.then);
}

// value is ES6 native promise or not
export function isNativePromise(p: unknown): boolean {
  return isObject(p) && getObjectTag(p) === "[object Promise]";
}

// value instance of Date or not
export function isDate(value: unknown): boolean {
  return getObjectTag(value) === "[object Date]";
}

// value instance of RegExp or not
export function isRegexp(value: unknown): boolean {
  return getObjectTag(value) === "[object RegExp]";
}

// isMap & isWeakMap
export function isMap(value: unknown): boolean {
  return isObject(value) && getObjectTag(value) === "[object Map]";
}
export function isWeakMap(value: unknown): boolean {
  return isObject(value) && getObjectTag(value) === "[object WeakMap]";
}

// isSet & isWeakSet
export function isSet(value: unknown): boolean {
  return isObject(value) && getObjectTag(value) === "[object Set]";
}
export function isWeakSet(value: unknown): boolean {
  return isObject(value) && getObjectTag(value) === "[object WeakSet]";
}

// value is Element Node or not
export function isElement(value: unknown): boolean {
  return isObject(value) && value.nodeType === 1 && !isPlainObject(value);
}

// is errors object, it's includes
// Error EvalError RangeError ReferenceError SyntaxError TypeError URIError
export function isError(value: unknown): boolean {
  if (!isObject(value)) {
    return false;
  }
  const tag = getObjectTag(value);
  return Boolean(
    tag === "[object Error]" ||
      tag === "[object DOMException]" ||
      (typeof value.message === "string" &&
        typeof value.name === "string" &&
        !isPlainObject(value))
  );
}

// is length property number or not
export function isLength(len: number) {
  return len >= 0 && Number.isSafeInteger(len);
}

// isArray / isArrayLike
export const isArray = Array.isArray;
export function isArrayLike<T extends { length: number }>(value: T): boolean {
  if (isFunction(value)) {
    return false;
  }
  return isLength(value.length);
}

// value is File | Blob object or not
export function isFile(value: unknown) {
  const tag = getObjectTag(value);
  return tag === "[object Blob]" || tag === "[object File]";
}

// is empty Array/Object/Set/Map values
export function isEmpty(value: any): boolean {
  if (isPlainObject(value)) {
    return Object.keys(value).length === 0;
  }

  if (isString(value) || Array.isArray(value) || isArrayLike(value)) {
    return value.length === 0;
  }

  if (isMap(value) || isSet(value) || isFile(value)) {
    return value.size === 0;
  }
  return false;
}

// is primitive value or not
function isPrimitive(value) {
  var primitiveTags = [
    "string",
    "number",
    "boolean",
    "undefined",
    "bigint",
    "symbol",
  ];
  return value === null || primitiveTags.indexOf(typeofstr) !== -1;
}

// isNode
export function isNode() {
  return getObjectTag(globalThis) === "[object global]" && isUndefind(window);
}

// isBrowser
export function isBrowser() {
  return window && getObjectTag(window) === "[object Window]";
}
js
// cache toString
const toTagString = Object.prototype.toString;
const FuncToString = Function.prototype.toString;

// get value object tag like: [object Array]
export function getObjectTag(value) {
  return toTagString.call(value);
}

// value is object or not
export function isObject(value) {
  return value !== null && typeof value === "object";
}

// value instance of Object or not
export function isPlainObject(value) {
  return getObjectTag(value) === "[object Object]";
}

// value is number or not
export function isNumber(value) {
  return typeof value === "number";
}

// value is string or not
export function isString(value) {
  return typeof value === "string";
}

// value is undefined or not
export function isUndefind(value) {
  return value === undefined;
}

// value is null or not
export function isNull(value) {
  return value === null;
}

// value is null or undefined
export function isNullable(value) {
  return Boolean(value === null || value === undefined);
}

// value is boolean or not
export function isBoolean(value) {
  return typeof value === "boolean";
}

// value is symbol or not
export function isSymbol(value) {
  return typeof value === "symbol";
}

// value is function or not
export function isFunction(value) {
  return typeof value === "function";
}
export const isCallable = isFunction;

// value is isNative function or not
export function isNativeFunction(func) {
  return isFunction(func) && FuncToString.call(func).includes("[native code]");
}

// value is promise like or not
export function isPromise(p) {
  return isObject(p) && isFunction(p.then);
}

// value is ES6 native promise or not
export function isNativePromise(p) {
  return isObject(p) && getObjectTag(p) === "[object Promise]";
}

// value instance of Date or not
export function isDate(value) {
  return getObjectTag(value) === "[object Date]";
}

// value instance of RegExp or not
export function isRegexp(value) {
  return getObjectTag(value) === "[object RegExp]";
}

// isMap & isWeakMap
export function isMap(value) {
  return isObject(value) && getObjectTag(value) === "[object Map]";
}
export function isWeakMap(value) {
  return isObject(value) && getObjectTag(value) === "[object WeakMap]";
}

// isSet & isWeakSet
export function isSet(value) {
  return isObject(value) && getObjectTag(value) === "[object Set]";
}
export function isWeakSet(value) {
  return isObject(value) && getObjectTag(value) === "[object WeakSet]";
}

// value is Element Node or not
export function isElement(value) {
  return isObject(value) && value.nodeType === 1 && !isPlainObject(value);
}

// is errors object, it's includes
// Error EvalError RangeError ReferenceError SyntaxError TypeError URIError
export function isError(value) {
  if (!isObject(value)) {
    return false;
  }
  const tag = getObjectTag(value);
  return Boolean(
    tag === "[object Error]" ||
      tag === "[object DOMException]" ||
      (typeof value.message === "string" &&
        typeof value.name === "string" &&
        !isPlainObject(value))
  );
}

// is length property number or not
export function isLength(len) {
  return len >= 0 && Number.isSafeInteger(len);
}

// isArray / isArrayLike
export const isArray = Array.isArray;
export function isArrayLike(value) {
  if (isFunction(value)) {
    return false;
  }
  return isLength(value.length);
}

// value is File | Blob object or not
export function isFile(value) {
  const tag = getObjectTag(value);
  return tag === "[object Blob]" || tag === "[object File]";
}

// is empty Array/Object/Set/Map values
export function isEmpty(value) {
  if (isPlainObject(value)) {
    return Object.keys(value).length === 0;
  }

  if (isString(value) || Array.isArray(value) || isArrayLike(value)) {
    return value.length === 0;
  }

  if (isMap(value) || isSet(value) || isFile(value)) {
    return value.size === 0;
  }
  return false;
}

// is primitive value or not
function isPrimitive(value) {
  var primitiveTags = [
    "string",
    "number",
    "boolean",
    "undefined",
    "bigint",
    "symbol",
  ];
  return value === null || primitiveTags.indexOf(typeofstr) !== -1;
}

// isNode
export function isNode() {
  return getObjectTag(globalThis) === "[object global]" && isUndefind(window);
}

// isBrowser
export function isBrowser() {
  return window && getObjectTag(window) === "[object Window]";
}

字符串格式处理

ts
/**
 * 驼峰式字符串
 * @param str - 要处理的字符串
 * @param firstUpper - 第一个字符是否大写
 * @param separators - 分割符号
 * @returns 处理后的字符串
 */
export function camelCase(
  str: string,
  firstUpper: boolean = false,
  separators: string[] = ["-", "_"]
): string {
  if (str.length < 2) {
    return firstUpper ? str : str.toUpperCase();
  }
  let res = "";
  let shouldUpperCase = false;
  for (let i = 0, len = str.length; i < len; i++) {
    let char = str[i];
    if (separators.includes(char)) {
      shouldUpperCase = true;
      continue;
    }
    if (shouldUpperCase || (i === 0 && firstUpper)) {
      char = char.toUpperCase();
    }
    res += char;
    shouldUpperCase = false;
  }

  return res;
}

/**
 * 根据驼峰式字符串切分新的字符串
 * @param str - 要处理的字符串
 * @param separator - 切分符号
 * @returns 处理完的字符串
 */
function splitCamelCase(str: string, separator: string): string {
  if (separator.length > 1) {
    separator = separator[0];
  }

  const isUpperCacse = (char: string) => char === char.toUpperCase();
  let char;
  let res = "";
  for (let i = 0, len = str.length; i < len; i++) {
    char = str[i];
    if (isUpperCacse(char)) {
      if (i > 0) {
        res += separator;
      }
      res += char.toLowerCase();
    } else {
      res += char;
    }
  }
  return res;
}

// 下划线 snake_case
export const snakeCase = (str: string): string => splitCamelCase(str, "_");

// 中横线 kebab-case
export const kebabCase = (str: string): string => splitCamelCase(str, "-");

数组分页

ts
/**
 * Paging Arrays
 * @params {array} array
 * @params {number} size default 1
 * @return {Array<any[]>} result
 * @throw TypeError
 */
export function chunk<T>(array: T[], size: number = 1): Array<T[]> {
  const chunkSize = size >> 0; // convert to number

  const len = array.length;
  if (len === 0 || chunkSize === 0) {
    return [];
  }

  if (len <= chunkSize) {
    return [array];
  }

  const pages = Math.ceil(len / chunkSize);
  const result: T[][] = new Array(pages);
  for (let i = 0; i < pages; i++) {
    result[i] = array.slice(i * chunkSize, (i + 1) * chunkSize);
  }
  return result;
}
js
/**
 * Paging Arrays
 * @params {array} array
 * @params {number} size default 1
 * @return {Array<any[]>} result
 * @throw TypeError
 */
export function chunk(array, size = 1) {
  const chunkSize = size >> 0; // convert to number

  const len = array.length;
  if (len === 0 || chunkSize === 0) {
    return [];
  }

  if (len <= chunkSize) {
    return [array];
  }

  const pages = Math.ceil(len / chunkSize);
  const result = new Array(pages);
  for (let i = 0; i < pages; i++) {
    result[i] = array.slice(i * chunkSize, (i + 1) * chunkSize);
  }
  return result;
}

防抖

ts
/**
 * 函数防抖
 * @param func - 要防止抖动的函数
 * @param wait - 触发频率(等待时间)
 * @param isImmediate - 第一次是否立即调用
 * @param thisArg - 调用函数的时候 this 指向
 * @returns
 */
export function debounce(
  func: Function,
  wait: number = 100,
  isImmediate = false,
  thisArg?: object
): (...args: any[]) => void {
  let timer: NodeJS.Timeout;
  let shouldExecute = Boolean(isImmediate);

  return function (...args: unknown[]): void {
    timer && clearTimeout(timer);
    if (shouldExecute) {
      func.apply(thisArg, ...args);
      shouldExecute = false;
    } else {
      timer = setTimeout(func.bind(thisArg, ...args), wait);
    }
  };
}
js
/**
 * 函数防抖
 * @param func - 要防止抖动的函数
 * @param wait - 触发频率(等待时间)
 * @param isImmediate - 第一次是否立即调用
 * @param thisArg - 调用函数的时候 this 指向
 * @returns
 */
export function debounce(
  func,
  wait = 100,
  isImmediate = false,
  thisArg = null
) {
  let timer;
  let shouldExecute = Boolean(isImmediate);

  return function (...args) {
    timer && clearTimeout(timer);
    if (shouldExecute) {
      func.apply(thisArg, ...args);
      shouldExecute = false;
    } else {
      timer = setTimeout(func.bind(thisArg, ...args), wait);
    }
  };
}

节流

ts
export function throttle(
  func: Function,
  wait: number = 100,
  thisArg?: object
): (...args: any[]) => void {
  let timer: NodeJS.Timeout;
  let startTime = Date.now();
  return function (...args: any[]): void {
    timer && clearTimeout(timer);
    const nowTime = Date.now();
    if (startTime + wait >= nowTime) {
      func.apply(thisArg, args);
    } else {
      timer = setTimeout(func.bind(thisArg, ...args), wait);
    }
  };
}
js
export function throttle(func, wait = 100, thisArg = null) {
  let timer;
  let startTime = Date.now();
  return function (...args) {
    timer && clearTimeout(timer);
    const nowTime = Date.now();
    if (startTime + wait >= nowTime) {
      func.apply(thisArg, args);
    } else {
      timer = setTimeout(func.bind(thisArg, ...args), wait);
    }
  };
}

柯理化

js
function curry(fn, ...preArgs) {
  const countArgs = fn.length;
  return function (...args) {
    const items = [].concat(...preArgs, ...args);
    if (items.length < countArgs) {
      return curry.call(this, fn, items);
    } else {
      return fn.apply(null, items);
    }
  };
}

遍历对象

ts
/**
 * forIn 遍历对象
 * @template T extends object - 要遍历的对象
 * @template K extends keyof T - 对象所有的 key
 * @param obj - 要遍历的对象
 * @param callback - 遍历处理函数
 * @param onlyOwner - 是否只遍历自身的属性
 */
export function forIn<T extends object, K extends keyof T>(
  obj: T,
  callback: (key: K, value: T[K], source: T) => void,
  onlyOwner: boolean = true
): void {
  for (let key in obj) {
    if (onlyOwner) {
      /* @ts-ignore */
      obj.hasOwnProperty(key) && callback(key, obj[key], obj);
    } else {
      /* @ts-ignore */
      callback(key, obj[key], obj);
    }
  }
}
js
/**
 * forIn 遍历对象
 * @template T extends object - 要遍历的对象
 * @template K extends keyof T - 对象所有的 key
 * @param obj - 要遍历的对象
 * @param callback - 遍历处理函数
 * @param onlyOwner - 是否只遍历自身的属性
 */
export function forIn(obj, callback, onlyOwner = true) {
  for (let key in obj) {
    if (onlyOwner) {
      /* @ts-ignore */
      obj.hasownProperty(key) && callback(key, obj[key], obj);
    } else {
      /* @ts-ignore */
      callback(key, obj[key], obj);
    }
  }
}

格式化日期对象

虽然有很多处理日期的库 moment.jsdayjs, 但是最基本的格式化还是得会

ts
/**
 * 根据模板字符串格式化日期对象
 * @param {Date} date
 * @param {string} template
 * @returns {string}
 */
export function formatDate(date: Date, template: string): string {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const dates = date.getDate();
  const hours = date.getHours();
  const minutes = date.getMinutes();
  const seconds = date.getSeconds();
  const day = date.getDay() + 1;

  const fillZero = (value: number): string => {
    return value < 10 ? `0${value}` : String(value);
  };

  const templateMap = {
    "{y}": year.toString().slice(-2),
    "{Y}": year,
    "{m}": month,
    "{M}": fillZero(month),
    "{d}": dates,
    "{D}": fillZero(dates),
    "{h}": hours,
    "{H}": fillZero(hours),
    "{i}": minutes,
    "{I}": fillZero(minutes),
    "{s}": seconds,
    "{S}": fillZero(seconds),
    "{w}": day,
  };

  let formated = template;
  const keys = Object.keys(templateMap) as Array<keyof typeof templateMap>;
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    const value = String(templateMap[key]);
    formated = formated.replace(key, value);
  }
  return formated;
}
js
/**
 * 根据模板字符串格式化日期对象
 * @param {Date} date
 * @param {string} template
 * @returns {string}
 */
export function formatDate(date, template) {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const dates = date.getDate();
  const hours = date.getHours();
  const minutes = date.getMinutes();
  const seconds = date.getSeconds();
  const day = date.getDay() + 1;

  const fillZero = (value) => {
    return value < 10 ? `0${value}` : String(value);
  };

  const templateMap = {
    "{y}": year.toString().slice(-2),
    "{Y}": year,
    "{m}": month,
    "{M}": fillZero(month),
    "{d}": dates,
    "{D}": fillZero(dates),
    "{h}": hours,
    "{H}": fillZero(hours),
    "{i}": minutes,
    "{I}": fillZero(minutes),
    "{s}": seconds,
    "{S}": fillZero(seconds),
    "{w}": day,
  };

  let formated = template;
  const keys = Object.keys(templateMap);
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    const value = String(templateMap[key]);
    formated = formated.replace(key, value);
  }
  return formated;
}

获取随机字符串/颜色字符串

ts
/**
 * get a random string id
 * @return {string}
 */
export function randomString(): string {
  return Math.random().toString(16).substring(2);
}

/**
 * get a random hex color string
 * @return {string}
 */
export function getRandomHexColor(): string {
  return "#" + Math.floor(Math.random() * 0xffffff).toString(16);
}
js
/**
 * get a random string id
 * @return {string}
 */
export function randomString() {
  return Math.random().toString(16).substring(2);
}

/**
 * get a random hex color string
 * @return {string}
 */
export function getRandomHexColor() {
  return "#" + Math.floor(Math.random() * 0xffffff).toString(16);
}

获取一组数据的平均值

ts
/**
 * 获取一组可迭代数据(number类型)的平均值,忽略不是数字的元素
 * @param values - 可迭代数据
 * @returns number
 */
export function meanOf(values: Iterable<number>): number {
  let sum = 0;
  let numbersTotal = 0;
  for (const item of values) {
    if (typeof item !== "number") {
      continue;
    }
    sum += item;
    numbersTotal++;
  }
  return sum / numbersTotal;
}
js
/**
 * 获取一组可迭代数据(number类型)的平均值,忽略不是数字的元素
 * @param values - 可迭代数据
 * @returns number
 */
export function meanOf(values) {
  let sum = 0;
  let numbersTotal = 0;
  for (const item of values) {
    if (typeof item !== "number") {
      continue;
    }
    sum += item;
    numbersTotal++;
  }
  return sum / numbersTotal;
}

函数缓存

ts
interface KeyMakerFunc {
  (...args: any[]): string;
}

interface MemorizedFunc {
  cacheMap: Map<string, unknown>;
  (...args: unknown[]): unknown;
}

export function isObject(value: unknown): boolean {
  return value !== null && typeof value === "object";
}
const __toStringTag = Object.prototype.toString;
export function getObjectTag(value: unknown): string {
  return __toStringTag.call(value);
}
export function isPromise(p: unknown): boolean {
  return isObject(p) && getObjectTag(p) === "[object Promise]";
}

/**
 * 记忆函数
 * @param func - 要缓存接过的函数
 * @param keyMaker - 生成缓存 key 的函数
 * @returns 返回缓存后的函数
 */
export function useMemorize(
  func: CallableFunction,
  keyMaker: KeyMakerFunc = JSON.stringify
): MemorizedFunc {
  const memorized: MemorizedFunc = (...args: unknown[]) => {
    const key = keyMaker({ name: func.name, args });
    if (!memorized.cacheMap.has(key)) {
      memorized.cacheMap.set(key, func(...args));
    }
    return memorized.cacheMap.get(key);
  };
  memorized.cacheMap = new Map();
  return memorized;
}

/**
 * 异步记忆函数
 * @param func - 要缓存接过的函数
 * @param keyMaker - 生成缓存 key 的函数
 * @returns 返回缓存后的函数
 */
export function useMemorizeAsync(
  func: CallableFunction,
  keyMaker: KeyMakerFunc = JSON.stringify
): MemorizedFunc {
  const memorized: MemorizedFunc = async (...args: unknown[]) => {
    const key = keyMaker({ name: func.name, args });
    if (!memorized.cacheMap.has(key)) {
      const result = func(...args);
      if (isPromise(result)) {
        const value = await result;
        memorized.cacheMap.set(key, value);
      } else {
        memorized.cacheMap.set(key, result);
      }
    }
    memorized.cacheMap.get(key);
  };
  memorized.cacheMap = new Map();
  return memorized;
}
js
/**
 * 函数记忆
 * @param func - 要缓存接过的函数
 * @param keyMaker - 生成缓存 key 的函数
 * @returns 返回缓存后的函数
 */
export function memorize(func, keyMaker = JSON.stringify) {
  const memorized = (...args) => {
    const key = keyMaker({ name: func.name, args });
    if (!memorized.cacheMap.has(key)) {
      memorized.cacheMap.set(key, func(...args));
    }
    return memorized.cacheMap.get(key);
  };
  memorized.cacheMap = new Map();
  return memorized;
}
ts
import { isPromise, useMemorize, useMemorizeAsync } from "@/hooks/useMemorize";

export function delay(cb: CallableFunction, wait: number, ...args: any[]) {
  setTimeout(() => cb(...args), wait);
}

export function commitment(isResolved: boolean) {
  return new Promise((resolve, reject) => {
    if (isResolved) {
      resolve("resolved-value");
    } else {
      reject("rejected-reason");
    }
  });
}

describe("记忆函数", () => {
  it("传入一个函数应该返回一个新的函数", () => {
    const fn = vi.fn();
    const newFn = useMemorize(fn);
    expect(fn).not.toBe(newFn);
  });

  it("使用相同的参数多次调用应该只调用一次", () => {
    const fn = vi.fn();
    expect(fn).not.toBeCalled();

    const handler = useMemorize(fn);

    handler("abc");
    handler("abc");
    handler("abc");
    expect(fn).toBeCalledTimes(1);

    handler("def");
    handler("def");
    handler("def");
    expect(fn).toBeCalledTimes(2);
  });

  it("参数相同, 但是函数名不相同应该不互相影响", () => {
    const fn1 = vi.fn();
    expect(fn1).not.toBeCalled();
    const handler1 = useMemorize(fn1);
    handler1("abc");
    handler1("abc");
    expect(fn1).toBeCalledTimes(1);

    const fn2 = vi.fn();
    expect(fn2).not.toBeCalled();
    const handler2 = useMemorize(fn2);
    handler2("abc");
    handler2("abc");
    expect(fn2).toBeCalledTimes(1);
  });
});

describe("异步记忆函数", () => {
  it("传入一个函数应该返回一个新的函数, 并且新函数是一个异步函数", () => {
    const fn = vi.fn();
    const handler = useMemorizeAsync(fn);
    expect(fn).not.toBe(handler);

    const p = handler();
    expect(isPromise(p)).toBe(true);
  });

  it("传入同步函数, 使用相同的参数多次调用应该只调用一次", async () => {
    const fn = vi.fn();
    expect(fn).not.toBeCalled();

    const handler = useMemorizeAsync(fn);

    await handler("abc");
    await handler("abc");
    await handler("abc");
    expect(fn).toBeCalledTimes(1);

    await handler("def");
    await handler("def");
    await handler("def");
    expect(fn).toBeCalledTimes(2);
  });

  it("传入异步函数,使用相同的参数多次调用应该只调用一次", async () => {
    const fn = vi.fn((r) => r());
    const asyncFunc = () => new Promise((r) => setTimeout(fn(r), 200));

    const handler = useMemorizeAsync(asyncFunc);

    await handler("abc");
    await handler("abc");
    await handler("abc");
    expect(fn).toBeCalledTimes(1);

    await handler("def");
    await handler("def");
    await handler("def");
    expect(fn).toBeCalledTimes(2);
  });
});

只执行一次的函数

ts
/**
 * 自会让函数调用一次
 * @param func - 要调用的函数
 * @param thisArg - func 调用时 this 的指向
 * @returns 新的函数
 */
const caches = new WeakSet();
export function once(func: Function, thisArg?: object): Function {
  return function (...args: unknown[]) {
    if (caches.has(func)) {
      return;
    }
    caches.add(func);
    func.apply(thisArg, args);
  };
}
js
/**
 * 自会让函数调用一次
 * @param func - 要调用的函数
 * @param thisArg - func 调用时 this 的指向
 * @returns 新的函数
 */
const caches = new WeakSet();
export function once(func, thisArg = null) {
  return function (...args) {
    if (caches.has(func)) {
      return;
    }
    caches.add(func);
    func.apply(thisArg, args);
  };
}

获取对象指定 key 的值成为一个新值

ts
/**
 * 在一个对象中挑选一个 key, 然后返回新对象 pick({a:1, b:2}, 'a', 'c')
 * @param source - 源对象
 * @param keys - 挑选的 key
 * @returns 处理后的新对象
 */
export function pick<T extends object, K extends keyof T>(
  source: T,
  ...keys: K[]
): Pick<T, K> {
  const target: any = Object.create(null);
  keys.forEach((key) => {
    target[key] = source[key];
  });
  return target;
}
js
/**
 * 在一个对象中挑选一个 key, 然后返回新对象 pick({a:1, b:2}, 'a', 'c')
 * @param source - 源对象
 * @param keys - 挑选的 key
 * @returns 处理后的新对象
 */
export function pick(source, ...keys) {
  const target = Object.create(null);
  keys.forEach((key) => {
    target[key] = source[key];
  });
  return target;
}

排除对象指定 key 的值返回一个新值

ts
export function exclude<T extends object, K extends keyof T>(
  source: T,
  ...keys: K[]
): Exclude<T, K> {
  const target: any = Object.create(null);
  Object.keys(source).forEach((key) => {
    /* @ts-ignore */
    if (!keys.includes(key)) {
      target[key] = source[key];
    }
  });
  return target;
}
js
export function exclude(source, ...keys) {
  const target = Object.create(null);
  Object.keys(source).forEach((key) => {
    if (!keys.includes(key)) {
      target[key] = source[key];
    }
  });
  return target;
}

返回指定范围的随机数

ts
/**
 * 返回指定范围内的随机数
 * @param min - 最小值
 * @param max - 最大值
 * @returns 返回值
 */
export function range(min: number, max: number): number {
  return Math.floor(Math.random() * (max - min + 1) + min);
}
js
/**
 * 返回指定范围内的随机数
 * @param min - 最小值
 * @param max - 最大值
 * @returns 返回值
 */
export function range(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

打乱数组元素顺序

ts
/**
 * shuffle array items
 * @param array
 * @returns {Array}
 */
export function shuffle<T>(array: T[]): T[] {
  if (array.length < 1) {
    return [];
  }
  return [...array].sort(() => Math.random() - 0.5).reverse();
}
js
/**
 * shuffle array items
 * @param array
 * @returns {Array}
 */
export function shuffle(array) {
  if (array.length < 2) {
    return [];
  }
  return [...array].sort(() => Math.random() - 0.5);
}

uuid

需要兼容低版本浏览器需要安装 uuid

ts
// 生成一个 uuid
import { v4 as uuidv4 } from "uuid";
export function uuid(): string {
  if (typeof window.crypto !== "undefined") {
    return window.crypto.randomUUID(); // v4
  }
  return uuidv4();
}
js
// 生成一个 uuid
import { v4 as uuidv4 } from "uuid";
export function uuid() {
  if (typeof window.crypto !== "undefined") {
    return window.crypto.randomUUID(); // v4
  }
  return uuidv4();
}

自增 id

ts
let __id = 0;
export const $uid = () => ++__id;

Released under the MIT License.