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 time - 要格式化的日期对象
 * @param template - 格式化模板
 * @returns 返回格式化后的时间字符串
 */
export function formatDate(time: Date, template: string): string {
  let date: Date;
  if (Object.prototype.toString.call(time) === '[object Date]') {
    date = <Date>time;
  } else {
    throw new TypeError('[formateDate]time must be Date or 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 => (value < 10 ? `0${value}` : String(value));

  const templateMap = {
    yy: year.toString().substring(0, 2),
    yyyy: year,
    mm: month,
    MM: fillZero(month),
    dd: dates,
    DD: fillZero(dates),
    hh: hours,
    HH: fillZero(hours),
    ii: minutes,
    II: fillZero(minutes),
    ss: seconds,
    SS: fillZero(seconds),
    ww: day,
  };

  let formated = template;
  Object.keys(templateMap).forEach((key) => {
    const value = String(templateMap[key]);
    formated = formated.replace(key, value);
  });
  return formated;
}
js
/**
 * 根据模板格式化字符串
 * @param time - 要格式化的日期对象
 * @param template - 格式化模板
 * @returns 返回格式化后的时间字符串
 */
export function formatDate(time, template) {
  let date;
  if (Object.prototype.toString.call(time) === '[object Date]') {
    date = time;
  } else {
    throw new TypeError('[formateDate]time must be Date or 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) => (value < 10 ? `0${value}` : String(value));

  const templateMap = {
    yy: year.toString().substring(0, 2),
    yyyy: year,
    mm: month,
    MM: fillZero(month),
    dd: dates,
    DD: fillZero(dates),
    hh: hours,
    HH: fillZero(hours),
    ii: minutes,
    II: fillZero(minutes),
    ss: seconds,
    SS: fillZero(seconds),
    ww: day,
  };

  let formated = template;
  Object.keys(templateMap).forEach((key) => {
    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;
}

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

只执行一次的函数

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

自增id

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

圣杯模式继承

兼容老旧浏览器, 无法使用 class 语法, 一个继承写起来太费劲了

js
var inherit = (function () {
  function Middleman() {}
  return function (child, parent) {
    Middleman.prototype = parent.prototype;
    child.prototype = new Middleman();
    child.prototype.constructor = child;
    child.super_class = parent;
  };
})();

Released under the MIT License.