Skip to content

常用方法

getDataType

该方法用于获取参数数据类型,接收一个任意类型的参数,返回该参数的数据类型,小写字母形式。

ts
import { getDataType } from '...'

getDataType('') // string
getDataType(1) // number
getDataType(true) // boolean
getDataType([]) // array
getDataType({}) // object
方法源码
ts
/**
 * 获取传入数据的类型,返回小写字符串格式
 */
export const getDataType = (obj: unknown): string => {
  let res = Object.prototype.toString.call(obj).split(' ')[1];
  res = res.substring(0, res.length - 1).toLowerCase();
  return res;
};

debounce

该方法用于事件防抖,接收一个函数、一个延迟时间和一个是否立即执行一次的布尔值,返回一个新函数,新函数在延迟时间内多次调用时,只会执行最后一次调用。

ts
import { debounce } from '...'

const fn = debounce(() => console.log('debounce'), 1000)
fn()
fn() // 1秒后输出 debounce
方法源码
ts
/**
 * @description 防抖函数,一定时间内多次触发,只执行最后触发的一次,可能永远不会执行
 * @param fn 需要防抖的函数
 * @param delay 间隔时间,默认200ms,单位ms
 * @param immediate 第一次是否立即执行,默认false
 * @returns
 */
export const debounce = <T = unknown>(
  fn: (...args: T[]) => void,
  delay: number = 200,
  immediate: boolean = false
) => {
  let timer: NodeJS.Timeout | null = null;
  let isFirst = true;
  return function (this: any, ...args: T[]) {
    timer && clearTimeout(timer);

    if (immediate && isFirst) {
      isFirst = false;
      fn.apply(this, args);
      return;
    }

    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};

throttle

该方法用于事件节流,接收一个函数和一个延迟时间,返回一个新函数,新函数在延迟时间内多次调用时,只会执行第一次调用。

ts
import { throttle } from '...'

const fn = throttle(() => console.log('throttle'), 1000)
fn() // 立即输出 throttle
fn() // 间隔时间小于1s,不会执行
方法源码
ts
/**
 * @description 节流函数,触发一次后,下次触发需要间隔一定的时间
 * @param fn 需要执行的函数
 * @param delay 间隔时间,默认1s,单位ms
 * @param options
 * @returns
 */
export const throttle = <T = unknown>(
  fn: (...args: T[]) => void,
  delay: number = 1000,
  options: { leading?: boolean; trailing?: boolean } = { leading: true, trailing: false }
) => {
  let timer: NodeJS.Timeout | null = null;
  let previous = 0;
  let context: any = null;
  let argsCache: T[] | null = null;

  const leading = options.leading !== undefined ? options.leading : true;
  const trailing = options.trailing !== undefined ? options.trailing : false;

  return function (this: any, ...args: T[]) {
    const now = Date.now();

    if (!previous && !leading) {
      previous = now;
    }

    const remaining = delay - (now - previous);
    context = this;
    argsCache = args;

    if (remaining <= 0 || remaining > delay) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      previous = now;
      fn.apply(context, args);
      argsCache = null;
    } else if (!timer && trailing) {
      timer = setTimeout(() => {
        timer = null;
        previous = leading ? Date.now() : 0;
        if (argsCache) {
          fn.apply(context, argsCache);
          argsCache = null;
        }
      }, remaining);
    }
  };
};

curry

柯理化方法,该方法接收一个函数,返回一个新函数,新函数接收的参数数量不足时,会返回一个新函数,直到参数数量足够执行原函数。

ts
import { curry } from '...'

const add = curry((a: number, b: number, c: number) => a + b + c)
add(1)(2)(3) // 6
add(1, 2)(3) // 6
add(1)(2, 3) // 6
方法源码
ts
export interface ICurryBack<T = unknown, B = unknown> {
  (...params: T[]): B | ICurryBack<T, B>;
}
/**
 * 柯里化:接收一个函数,返回一个新函数,新函数接收的参数数量不足时,会返回一个新函数,直到参数数量足够执行原函数
 */
export const curry = <T = unknown, B = unknown>(
  fn: Function,
  ...args: T[]
): B | ICurryBack<T, B> => {
  const length = fn.length;

  if (args.length < length) {
    return function (...params: T[]) {
      return curry(fn, ...args, ...params);
    };
  }

  return fn(...args);
};

compose

组合方法,多用于函数式编程,接收多个函数,返回一个新函数,新函数会将参数依次传入每个函数,并将结果传入下一个函数。

ts
import { compose } from '...'

const fn = compose(
  (a: number) => a + 1,
  (a: number) => a * 2
)
fn(1) // 3
方法源码
ts
/**
 * 函数式编程实现,从右往左执行,函数返回值会传给下一个执行的函数
 */
export const compose = <T = unknown>(...funcs: Function[]) => {
  if (funcs.length === 0) {
    return (arg: T) => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce((a, b) => {
    return (...args: T[]) => {
      return a(b(...args));
    };
  });
};

deepClone

深拷贝方法,接收一个对象,返回该对象的深拷贝,支持Map、Set、RegExp、Date、Function类型、循环引用与简单数据类型。

ts
import { deepClone } from '...'

const obj = { a: 1, b: { c: 2 } }
const clone = deepClone(obj)
obj === clone // false
obj.b === clone.b // false
方法源码
ts
/**
 * 深拷贝实现(支持Map、Set、RegExp、Date、Function类型和循环引用,不支持symbol属性)
 */
export const deepClone = <T = unknown>(obj: T) => {
  /**
   * 用来保存引用关系,解决循环引用问题
   */
  const copyObj: any = {};

  const clone = (data: any): T => {
    if (!(data instanceof Object)) {
      return data;
    }

    const newObj: any = Array.isArray(data) ? [] : {};

    for (const key in data) {
      if (!data.hasOwnProperty(key)) {
        continue;
      }

      if (!(data[key] instanceof Object)) {
        newObj[key] = data[key];
        continue;
      }

      if (data[key] instanceof Date) {
        newObj[key] = new Date(data[key].getTime());
        continue;
      }

      if (data[key] instanceof RegExp) {
        newObj[key] = new RegExp(data[key]);
        continue;
      }

      if (data[key] instanceof Function) {
        // 处理es6简写方法名的问题,例如:{hi() {return 1;}}
        const funcStr = data[key].toString().replace(/^function/, '');
        newObj[key] = new Function(`return function ${funcStr}`)();
        continue;
      }

      if (data[key] instanceof Map) {
        newObj[key] = new Map();

        data[key].forEach((val: any, mapKey: any) => {
          if (!(mapKey instanceof Object) && !(val instanceof Object)) {
            newObj[key].set(mapKey, val);
          } else {
            newObj[key].set(clone(mapKey), clone(val));
          }
        });

        continue;
      }

      if (data[key] instanceof Set) {
        newObj[key] = new Set();
        data[key].forEach((val: any) => {
          if (!(val instanceof Object)) {
            newObj[key].add(val);
          } else {
            newObj[key].add(clone(val));
          }
        });

        continue;
      }

      // 判断是否为循环引用
      if (copyObj[key] === data[key]) {
        newObj[key] = data[key];
        continue;
      }
      copyObj[key] = data[key];

      newObj[key] = clone(data[key]);
    }

    return newObj;
  };

  return clone(obj);
};

getQueryString

该方法接收一个对象与一个是否编码的布尔值,返回一个拼接的查询字符串。

ts
import { getQueryString } from '...'

getQueryString({ a: 1, b: 2, c: null }) // ?a=1&b=2
getQueryString({ a: 1, b: 2, c: '哈哈' }) // ?a=1&b=2&c=%E5%93%88%E5%93%88
方法源码
ts
/**
 * @description 拼接查询字符串
 * @param data
 * @param isEncode 是否使用encodeURIComponent()将值编码,默认true
 * @returns
 */
export const getQueryString = (
  data: Record<string, string | number | boolean | null | undefined>,
  isEncode = true
) => {
  let str = '?';

  for (const key in data) {
    if (data[key] === null || data[key] === '' || data[key] === undefined) {
      continue;
    }

    const value = isEncode
      ? encodeURIComponent(data[key] as string | number | boolean)
      : data[key];

    str += `${key}=${value}&`;
  }

  return str.substring(0, str.length - 1);
};

formatTime

该方法接收一个Date对象与一个格式字符串,返回格式化时间字符串。格式字符串,默认:yyyy/MM/dd HH:mm:ss。yy: 输出两位数的年份, h:输出12小时制,H:输出24小时制,M:月份,m:分钟,一位字母则不补零。

ts
import { formatTime } from '...'

formatTime() // 2024/06/01 12:56:55
formatTime(undefined, 'yy-M-d H:mm:ss') // 24-6-1 12:59:12
方法源码
ts
/**
 * @description 格式化时间
 * @param {Date} [date] Date对象,默认当前时间
 * @param {String} [format] 输出格式字符串,默认:yyyy/MM/dd HH:mm:ss。yy: 输出两位数的年份,
 * h:输出12小时制,H:输出24小时制,M:月份,m:分钟,一位字母则不补零
 * @returns {String}
 */
export const formatTime = (
  date: Date = new Date(),
  format: string = 'yyyy/MM/dd HH:mm:ss'
): string => {
  if (!format) {
    return format;
  }

  const strategies = {
    yy: () => {
      return date.getFullYear().toString().substring(2, 4);
    },
    yyyy: () => {
      return date.getFullYear().toString();
    },
    M: () => {
      const month = date.getMonth() + 1;
      return month.toString();
    },
    MM: () => {
      const month = date.getMonth() + 1;
      return month.toString().padStart(2, '0');
    },
    d: () => {
      return date.getDate().toString();
    },
    dd: () => {
      return date.getDate().toString().padStart(2, '0');
    },
    h: () => {
      let hours = date.getHours();
      if (hours > 12) {
        hours -= 12;
      }

      return hours.toString();
    },
    hh: () => {
      let hours = date.getHours();
      if (hours > 12) {
        hours -= 12;
      }

      return hours.toString().padStart(2, '0');
    },
    H: () => {
      return date.getHours().toString();
    },
    HH: () => {
      return date.getHours().toString().padStart(2, '0');
    },
    m: () => {
      return date.getMinutes().toString();
    },
    mm: () => {
      return date.getMinutes().toString().padStart(2, '0');
    },
    s: () => {
      return date.getSeconds().toString();
    },
    ss: () => {
      return date.getSeconds().toString().padStart(2, '0');
    }
  };
  type IKey = keyof typeof strategies;

  const replaceFn = (val: string): string => {
    let func = strategies[val as IKey];
    if (func instanceof Function) {
      return func();
    }

    func = strategies[val.toLowerCase() as IKey];
    if (func instanceof Function) {
      return func();
    }

    // 没有匹配的方法,返回原字符串
    return val;
  };

  return format.replace(/([Yy]{2,4}|[M]+|[Dd]+|[Hh]+|[m]+|[Ss]+)/g, replaceFn);
};

idleDetection

网页空闲监测方法,接收一个回调函数与一个空闲时间,返回一个IdleDetection对象,该对象有startDetectionstopDetectionrestartDetection方法。

ts
import { idleDetection } from '...'

const idle = idleDetection(() => console.log('idle'), 1000)
idle.startDetection() // 用户空闲1秒后输出 idle
方法源码
ts
/**
 * 页面空闲检测
 * @param callback
 * @param timeout 时长,默认60s,单位:秒
 * @returns
 */
export const idleDetection = (callback: () => void, timeout = 60) => {
  let pageTimer: NodeJS.Timeout | undefined;
  let beginTime = 0;

  const onClearTimer = () => {
    pageTimer && clearTimeout(pageTimer);
    pageTimer = undefined;
  };
  const onStartTimer = () => {
    onClearTimer();
    beginTime = Date.now();
    pageTimer = setTimeout(() => {
      callback();
    }, timeout * 1000);
  };

  const onPageVisibility = () => {
    onClearTimer();

    if (document.visibilityState !== 'visible') {
      return;
    }

    const currentTime = Date.now();
    if (currentTime - beginTime >= timeout * 1000) {
      callback();
      return;
    }

    pageTimer = setTimeout(() => {
      callback();
    }, timeout * 1000 - (currentTime - beginTime));
  };

  const startDetection = () => {
    onStartTimer();
    document.addEventListener('keydown', onStartTimer);
    document.addEventListener('mousemove', onStartTimer);
    document.addEventListener('visibilitychange', onPageVisibility);
  };

  const stopDetection = () => {
    onClearTimer();
    document.removeEventListener('keydown', onStartTimer);
    document.removeEventListener('mousemove', onStartTimer);
    document.removeEventListener('visibilitychange', onPageVisibility);
  };

  const restartDetection = () => {
    startDetection();
    stopDetection();
  };

  return {
    startDetection,
    stopDetection,
    restartDetection
  };
};

deepFindItem

深度查找树形结构中的某个节点,接收一个数组对象、一个查找函数与一个子元素键名,返回查找到的第一个节点。

ts
import { deepFindItem } from '...'

const arr = [{ a: 1, children: [{ a: 2 }] }]
deepFindItem(arr, (item) => item.a === 2, 'children') // { a: 2 }
方法源码
ts
/**
 * @description 深度查找树形结构中的某个节点
 * @param data 树形结构数据
 * @param compare 查找条件函数,返回 true 时表示找到
 * @param childrenKey 子节点的 key,默认 'children'
 * @returns
 */
export const deepFindItem = <T extends Record<string, any>>(
  data: T[],
  compare: (value: T) => boolean,
  childrenKey = 'children'
): T | undefined => {
  const queue: T[] = [...data]

  while (queue.length > 0) {
    const item = queue.shift()!

    if (compare(item)) {
      return item
    }

    if (item[childrenKey]?.length) {
      queue.push(...item[childrenKey])
    }
  }

  return undefined
}

deepMap

深度遍历树形结构,接收一个数组对象、一个映射函数与一个子元素键名,返回一个新的数组对象。映射函数接收一个参数:当前节点。

ts
import { deepMap } from '...'

const arr = [{ a: 1, b: 2, children: [{ a: 2, b: 4 }]}]
const newArr = deepMap(arr, (v) => {
  return {
    a: v.a * 2,
    b: v.b * 2,
    children: v.children
  }
}) // result: [{ a: 2, b: 4, children: [{ a: 4, b: 8 }]}]
方法源码
ts
/**
 * @description 深度遍历树形结构
 * @param data 树形结构数据
 * @param callback 回调函数,接收当前节点
 * @param childrenKey 子节点的 key,默认 'children'
 * @returns 返回新的树形结构数据
 */
export const deepMap = <T extends Record<any, any>>(
  data: T[],
  callback: (value: T) => T,
  childrenKey = 'children'
): T[] => {
  const result: T[] = []
  const queue: { node: T; parent: T | null; index: number }[] = data.map((node, index) => ({
    node,
    parent: null,
    index
  }))

  while (queue.length > 0) {
    const { node, parent, index } = queue.shift()!
    const newNode = callback({ ...node })

    if (parent === null) {
      result[index] = newNode
    } else {
      parent[childrenKey][index] = newNode
    }

    if (Array.isArray(newNode[childrenKey]) && newNode[childrenKey].length > 0) {
      queue.push(
        ...newNode[childrenKey].map((child: T, idx: number) => ({
          node: child,
          parent: newNode,
          index: idx
        }))
      )
    }
  }

  return result
}

deepForeach

树结构的广度优先遍历,接收一个数组对象、一个遍历函数与一个子元素键名。遍历函数接收一个参数:当前节点。

ts
import { deepForeach } from '...'

const arr = [{ a: 1, b: 2, children: [{ a: 2, b: 4 }]}]
deepForeach(arr, (v) => {
  console.log(v) // { a: 1, b: 2, children: [...] } => { a: 2, b: 4 }
})
方法源码
ts
/**
 * @description 树结构的广度优先遍历
 * @param data 树形结构数据
 * @param callback 回调函数,接收当前节点
 * @param childrenKey 子节点的 key,默认 'children'
 */
export const deepForeach = <T extends Record<string, any>>(
  data: T[],
  callback: (value: T) => void,
  childrenKey = 'children'
) => {
  if (!data?.length) return

  const queue: T[] = [...data]
  while (queue.length > 0) {
    const node = queue.shift()!
    callback(node)

    if (Array.isArray(node[childrenKey]) && node[childrenKey].length > 0) {
      queue.push(...node[childrenKey])
    }
  }
}

getSystemTheme

该方法用于获取系统主题,返回字符串lightdark

ts
import { getSystemTheme } from '...'
const theme = getSystemTheme()
方法源码
ts
export const getSystemTheme = () => {
  if (!window.matchMedia) {
    const date = new Date();
    const hours = date.getHours();

    if (hours >= 7 && hours < 19) {
      return 'light';
    }
    return 'dark';
  }

  const systemTheme = window.matchMedia('(prefers-color-scheme: dark)');
  const theme = systemTheme.matches ? 'dark' : 'light';
  return theme;
};

followSystemTheme

该方法用于自动跟随系统主题,接收一个回调函数参数。系统主题变化时,调用回调并传入当前主题。

ts
import { followSystemTheme } from '...'
const theme = followSystemTheme((theme) => {
  console.log(theme) // 'light' / 'dark'
})
方法源码
ts
export const followSystemTheme = (
  callback: (theme: 'dark' | 'light') => void
) => {
  if (!window.matchMedia) {
    const date = new Date();
    const hours = date.getHours();
    const time = date.getTime();
    let delay = 0;
    let mode: 'light' | 'dark' = 'light';

    if (hours >= 0 && hours < 7) {
      const nextTime = date.setHours(7);
      delay = nextTime - time;
    } else if (hours >= 7 && hours < 19) {
      const nextTime = date.setHours(19);
      delay = nextTime - time;
      mode = 'dark';
    } else {
      const nextTime = date.setHours(23, 59, 59, 999) + 7 * 60 * 60 * 1000;
      delay = nextTime - time;
    }

    setTimeout(() => {
      callback(mode);
    }, delay);
    return;
  }

  const systemTheme = window.matchMedia('(prefers-color-scheme: dark)');
  systemTheme.addEventListener('change', (e) => {
    const theme = e.matches ? 'dark' : 'light';
    callback(theme);
  });
};

Released under the MIT License.