JavaScript 手写实现

手写一些工具函数

订阅发布模型

/**
 * 事件触发器
 * @mode 订阅发布模型
 */
export class EventEmitter {
  private handlers: { [key: string]: Function[] } = {};

  /**
   * 订阅事件
   * @param eventName string
   * @param handler Function
   * @example eventEmitter.on('event1', handler);
   */
  on(eventName: string, handler: Function) {
    if (!this.handlers[eventName]) {
      this.handlers[eventName] = [];
    }
    this.handlers[eventName].push(handler);
  }

  /**
   * 发布事件
   * @param eventName string
   * @param args any[]
   * @example eventEmitter.emit('event1');
   */
  emit(eventName: string, ...args: any[]) {
    const handlers = this.handlers[eventName];
    if (handlers && handlers.length) {
      handlers.forEach((handler) => handler.apply(this, args));
    }
  }

  /**
   * 取消订阅
   * @param eventName string
   * @param handler Function
   * @example emitter.off('event1', handler) // 取消事件 event1 全部订阅
   * emitter.off('event1'); // 取消事件 event1 全部订阅
   * emitter.off('*'); // 取消全部事件订阅
   */
  off(eventName: string, handler?: Function) {
    const handlers = this.handlers[eventName];
    if (eventName === '*') {
      this.handlers = {}
    } else if (!handler) {
      delete this.handlers[eventName]
    } else if (handlers && handlers.length) {
      const index = handlers.indexOf(handler);
      if (index !== -1) {
        handlers.splice(index, 1);
      }
    }

  }
}

防抖函数

/**
 * 防抖函数可以限制一个函数的调用频率,即在一定时间内只能调用一次
 * @param callback - 需要被防抖的函数
 * @param delay - 限制函数调用频率的时间间隔
 * @returns 被防抖处理后的函数
 * @example const debouncedFunc = debounce(myFunction, 1000);
 * debouncedFunc(); // myFunction 将不会在这次调用时执行,因为距离上一次调用不到 1000 毫秒
 * debouncedFunc(); // myFunction 将不会在这次调用时执行,因为距离上一次调用不到 1000 毫秒
 *
 * 防抖函数和节流函数都可以限制一个函数的调用频率,但是它们的实现方式不同。
 * 防抖函数会在函数被调用后,等待一定时间(即delay参数指定的时间),如果在这段时间内函数再次被调用,那么计时器就会被重置,重新等待delay时间。只有在delay时间内没有再次调用函数,才会执行函数。这种方式适用于一些需要等待用户停止操作后才执行的函数,比如搜索框输入联想。
 * 节流函数则是在一定时间内只能调用一次函数。如果在这段时间内函数再次被调用,那么函数不会被执行,直到时间间隔超过delay参数指定的时间。这种方式适用于一些需要限制调用频率的函数,比如滚动事件。
 */
export function debounce(callback: Function, delay: number): (...args: any[]) => void {
  let timer: number;
  return function (this: any, ...args: any[]) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      callback.apply(this, args);
    }, delay);
  };
}

节流函数

/**
 * 节流函数可以限制一个函数的调用频率,即在一定时间内只能调用一次
 * @param callback - 需要被节流的函数
 * @param delay - 限制函数调用频率的时间间隔
 * @returns 被节流处理后的函数
 * @example const throttledFunc = throttle(myFunction, 1000);
 * throttledFunc(); // myFunction 将会在第一次调用时立即执行
 * throttledFunc(); // myFunction 将不会在这次调用时执行,因为距离上一次调用不到 1000 毫秒
 */

export function throttle(callback: Function, delay: number): (...args: any[]) => void {
  let lastTime = 0;
  return function (this: any, ...args: any[]) {
    const now = new Date().getTime();
    if (now - lastTime >= delay) {
      lastTime = now;
      callback.apply(this, args);
    }
  };
}

一些 DOM 操作

const DOM = window.document

/**
 * 获取绝对链接地址
 * @param path
 */
export const getAbsoluteUrl = (path: string): string => {
  const anchor: HTMLAnchorElement = <HTMLAnchorElement>DOM.createElement('A')
  anchor.href = path
  return anchor.href
}

/**
 * 设置页面标题
 * @param title {string}
 */
export const setTitle = (title: string): void => {
  DOM.title = title
}

/**
 * 设置页面 REM 布局
 * @param designWidth 设计宽
 * @param split 分宽数
 */
export const setREMLayout = (designWidth: number = 750, split: number = 100, isSetViewport = true): ((pixel: number) => string) => {
  const DOC_ELE = <HTMLElement>DOM.documentElement
  let dprDesignWidth = designWidth

  if (isSetViewport) {
    const DPR = window.devicePixelRatio || 1
    dprDesignWidth *= DPR
    const SCALE = (1 / DPR).toFixed(2)
    // Set "viewport" to HD display
    let viewport = DOM.querySelector('meta[name="viewport"]')
    if (!viewport) {
      viewport = DOM.createElement('meta')
      viewport.setAttribute('name', 'viewport')
    }
    viewport.setAttribute('content', `width=device-width, initial-scale=${SCALE}, maximum-scale=${SCALE}, minimum-scale=${SCALE}, user-scalable=0`)
    // Set "data-dpr" for css hack
    DOC_ELE.setAttribute('data-dpr', <string><any>DPR)
    // Set body's fontSize to default font normal
    DOM.body.style.fontSize = 16 * DPR + 'px'
  }

  const IDEAL_WIDTH = DOC_ELE.clientWidth > dprDesignWidth ? dprDesignWidth : DOC_ELE.clientWidth
  DOC_ELE.style.fontSize = (IDEAL_WIDTH / (designWidth / split)).toFixed(4) + 'px'

  return getPx2RemFunc(split)
}

/**
 * 生成 px2rem 函数
 * @param split
 */
export const getPx2RemFunc = (split: number): ((pixel: number) => string) => (pixel: number) => (pixel / split).toFixed(4) + 'rem'

设备环境识别

const UA: string = navigator.userAgent

const isInUA = (value: string) => (new RegExp(value, 'i')).test(UA)

export const isIOS = (): boolean => isInUA('(iPhone)|(iPad)|(iPod)')

export const isIPhone = (): boolean => isInUA('iPhone')

export const isIPad = (): boolean => isInUA('iPad')

export const isIPod = (): boolean => isInUA('iPod')

export const isAndroid = (): boolean => isInUA('Android')

export const isWeChat = (): boolean => isInUA('MicroMessenger')

类型识别


export const isString = (value: any) => typeof value === 'string'

export const isNumber = (value: any) => typeof value === 'number'

export const isBoolean = (value: any) => typeof value === 'boolean'

export const isNull = (value: any) => value === null

export const isUndefined = (value: any) => value === void 0

export const isObject = (value: any) => !isNull(value) && typeof value === 'object'

export const isJSONObject = (value: any) => {
  if (isObject(value)) {
    try {
      JSON.stringify(value)
      return true
    } catch (error) {
      return false
    }
  }
  return false
}

正则

/**
 * 网络 IPv4 地址
 * @example 192.168.0.1
 */
export const IPV4_RULE = /^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/;

/**
 * 网络 IPv6 地址
 * @example 2001:0db8:85a3:0000:0000:8a2e:0370:7334
 */
const IPV6_RULE = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;

/**
 * 中国内地手机号码
 * @example 18818188888
 */
export const CHINESE_PHONE_RULE = /^1[3-9]\d{9}$/;

/**
 * 中国内地座机号码
 * @example 028-999999999
 */
export const CHINESE_LANDLINE_RULE = /^0\d{2,3}-\d{7,8}$/;

/**
 * 中国内地邮政编码
 * @example 600000
 */
export const CHINESE_POSTAL_CODE_RULE = /^[1-9]\d{5}$/;

/**
 * 中国居民身份证号码
 * @example 511321200001011238
 */
export const CHINESE_ID_RULE = /^[1-9]\d{5}(18|19|20)\d{2}(0\d|1[0-2])([0-2]\d|3[0-1])\d{3}[\dX]$/;

/**
 * 中国车牌号
 * @example 川A88888
 */
export const CHINESE_LICENSE_PLATE_RULE = /^[\u4e00-\u9fa5][A-Z][A-Z_0-9]{5}$/;

/**
 * 中国内地用户主流邮箱地址 qq.com 163.com 126.com 139.com foxmail.com gmail.com outlook.com icloud.com
 * @example qq.com
 */
export const CHINESE_MAIN_MAIL_RULE = /^(qq|163|126|139|foxmail|gmail|outlook|icloud)\.com$/;

/**
 * 迅雷链接正则
 * @example thunder://afoia812na98f0a123123 thunderx://afoia812na98f0a123123
 */
export const THUNDERBOLT_RULE = /^thunderx?:\/\/[a-zA-Z\d]+=$/;

/**
 * 软件版本号
 * @example 1.0.0
 */
export const SOFTWARE_VERSION_RULE = /^\d+(\.\d+){0,2}$/;

/**
 * 通用邮箱地址
 * @example me@jiluo.cc
 */
export const MAIL_RULE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

/**
 * HTML 注释
 * @example <!-- header -->
 */
export const HTML_COMMENT_RULE = /<!--[\s\S]*?-->/;

// 图片文件格式(后缀)
export const IMAGE = /^(jpe?g|png|gif|bmp)$/i;

/**
 * 视频文件格式(后缀)
 * @example mp4
 */
export const VIDEO_RULE = /^(mp4|avi|mov|wmv|flv|mkv)$/i;

/**
 * 音频文件格式(后缀)
 * @example mp3
 */
export const AUDIO_RULE = /^(mp3|wav|ogg|flac)$/i;

/**
 * 文件名
 */
export const FILE_NAME_RULE = /^[^\\/:*?"<>|]+$/;

/**
 * GUID/UUID
 * @example 01234567-89ab-cdef-0123-456789abcdef
 */
export const UUID_RULE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

/**
 * 简体中文汉字以及标点符号
 * @example 你好
 */
export const SIMPLIFIED_CHINESE_RULE = /^[\u4e00-\u9fa5,。、;:?!]+$/;

/**
 * 繁体中文汉字以及标点符号
 * 美麗
 */
export const TRADITIONAL_CHINESE_RULE = /^[\u4e00-\u9fa5,。、;:?!「」『』【】《》()[]{}]+$/;

/**
 * 小数
 * @example 0.1 -0.1 1.0 -1.0 1.23 -1.23
 */
export const DECIMAL_RULE = /^-?\d+(\.\d+)?$/;

/**
 * 整数
 * @example 0 1 2 3 -1 -2 -3
 */
export const INTEGER_RULE = /^-?\d+$/;

/**
 * QQ 号;5 至 11 位数字
 * @example 153583876 305384014
 */
export const QQ_RULE = /^[1-9][0-9]{4,10}$/;

/**
 * 微信号;6 至 20 位;字母、数字、减号、下划线组合;以字母开头
 * @example jiluo-cc
 */
export const WE_CHAT_RULE = /^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/;

/**
 * CSS 颜色值(16 进制);支持 3 位或 6 位
 * @example #fff #ffffff
 */
export const CSS_COLOR_RULE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;

/**
 * URL 地址
 * @example https://www.dongxi.dev
 */
export const URL_RULE = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/;

/**
 * 域名(不包含非英文域名)
 * @example dongxi.dev
 */
export const DOMAIN_RULE = /^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$/;


export const trim = (string: string) => string.replace(/^\s+|\s+$/gm, '')