web前端常用方法收集

web前端常用方法收集

2023年09月09日 阅读:50 字数:1361 阅读时长:3 分钟

写个笔记收集记录下工作中常用的一些前端方法,持续更新~

1. 字符串操作

1.1. 数字千分位展示

将数字或数字字符串转换为千分位展示,支持保留小数点,如123,456,789.666

/**
 * 数字千分位展示并显示n位小数
 * @param {Number | String} num 需要格式化的值
 * @param {Number} precision 保留几位小数,不传小数不处理
 * @returns {String} 返回格式化的值
 */
function formatNumber(num, precision) {
  // 判断是否为数字
  if (!isNaN(parseFloat(num)) && isFinite(num)) {
    let parts;
    num = Number(num);
    // 处理小数点位数
    num = (
      typeof precision !== "undefined" ? num.toFixed(precision) : num
    ).toString();
    // 分离数字的小数部分和整数部分
    parts = num.split(".");
    parts[0] = parts[0].toString().replace(/(?!^)(?=(\d{3})+$)/g, ",");
    return parts.join(".");
  }
  return "";
}

1.2. 生成随机字符串

生成随机字符串,可以指定生成字符串的字典

/**
 * 生成指定个数的随机字符串
 * @param {Number} n 生成的字符串个数
 * @param {String} str 生戯后的字符中可含有的字符
 * @returns {String}
 */
function randomStr(
  n,
  str = "abcdefghigklmnopqrstuvexyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-+%$"
) {
  let res = "";
  const len = str.length;
  for (let i = 0; i < n; i++) {
    res += str[Math.floor(Math.random() * len)];
  }
  return res;
}

2. 数据结构相关

2.1. 二维数组转树形数组

根据parent_id将数组转换为树

/**
   * 二维数组转树形数组
   * @param {Array} data 原二维数组
   * @param {Number} parent_id 父节点id
   * @returns {Array} 树形数组 [{id:1,children:[{id:2},{id:3,children:[{id:4}]}]}]
   */
export function convertToTree (data, key = 'parent_id', value = 0) {
  data = JSON.parse(JSON.stringify(data))
  const tree = []
  let temp
  for (let i = 0; i < data.length; i++) {
    const item = data[i]
    if (item[key] === value) {
      temp = convertToTree(data, key, item.id)
      if (temp.length > 0) {
        item.children = temp
      }
      tree.push(item)
    }
  }
  return tree
}

3. 浏览器相关

3.1. localStorage

浏览器本地存储获取、设置、更新、删除,更新操作只支持Object

/**
 * 获取localStorage
 * @param {String} key localStorage键名
 * @returns {Object} 格式化的json数据
 */
export function getLocalStorage (key) {
  const data = localStorage.getItem(key)
  let result
  try {
    result = data ? JSON.parse(data) : {}
  } catch {
    result = ''
  }
  return result
}

/**
 * 设置localStorage
 * @param {String} key localStorage键名
 * @param {Object} data 数据
 */
export function setLocalStorage (key, data) {
  const format = typeof data === 'object' ? JSON.stringify(data) : data
  localStorage.setItem(key, format)
}

/**
 * 更新localStorage
 * @param {String} key localStorage键名
 * @param {Object} data 数据
 */
export function updateLocalStorage (key, data) {
  const source = JSON.parse(localStorage.getItem(key))
  if (typeof source === 'object' && typeof data === 'object') {
    const format = Object.assign(source, data)
    localStorage.setItem(key, JSON.stringify(format))
  } else {
    localStorage.setItem(key, data)
  }
}

/**
 * 移除localStorage
 * @param {String} key localStorage键名
 */
export function removeLocalStorage(key) {
  localStorage.removeItem(key)
}

3.2. WebSocket

实现连接、发送数据、发送心跳包、接收消息、断线重连、消息队列、关闭连接,并提供wss、ws协议适配

// websocket 公共类
// 实现连接、发送数据、发送心跳包、接收消息、断线重连、关闭连接
const sleep = (duration) => new Promise(resolve => setTimeout(resolve, duration));

class SocketClient {
  /**
   * @param {String} options.url websocket地址
   * @param {String} options.protocol 协议
   * @param {Number} options.heartBeatTimeout 发送心跳包的间隔,单位ms
   * @param {Boolean} options.automaticReconnect 是否断线重连
   * @param {Function} options.openHandler onopen回调
   * @param {Function} options.errorHandler onerror回调
   * @param {Function} options.closeHandler onclose回调
   * @param {Function} options.messageHandler onmessage回调
   */
  constructor(options) {
    this.isClosing = false
    this.reconnectCount = 0
    this.options = options
    this.socketInstance = null
    this.messageQueue = []
  }

  /**
   * CONNECTING: 0, 正在链接中
   * OPEN: 1, 已经链接并且可以通讯
   * CLOSING: 2, 连接正在关闭
   * CLOSED: 3 连接已关闭或者没有链接成功
   */
  get readyState() {
    return this.socketInstance?.readyState
  }

  /**
   * 获取服务端地址
   * @param {String} address
   * @returns {String}
   */
  formatServerUrl(address) {
    if (!/^ws|http(s)?:\/\//.test(address)) {
      // 如果开头没有指定协议
      const { protocol, host } = location
      let targetProtocol = protocol === 'https:' ? `wss://` : 'ws://'
      if (address.startsWith('/')) {
        // 如果开头是/,则同源
        targetProtocol += host
      }
      return targetProtocol + address
    }
    return address
  }

  /**
   * 建立socket连接
   */
  connect() {
    const { url, protocol, heartBeatTimeout, openHandler, errorHandler, closeHandler  } = this.options

    this.socketInstance = new WebSocket(this.formatServerUrl(url), protocol)

    this.socketInstance.onopen = () => {
      console.log('connect!')
      this.reconnectCount = 0
      this.isClosing = false
      this.listenMessage()
      
      // 发送心跳包
      if (heartBeatTimeout && !isNaN(+heartBeatTimeout)) {
        this.startHeartbeat(heartBeatTimeout)
      }

      // 在重新连接后,发送消息队列中存储的消息
      while (this.messageQueue.length > 0) {
        const message = this.messageQueue.shift();
        this.sendMessage(message);
      }

      typeof openHandler === 'function' && openHandler()
    }

    this.socketInstance.onerror = () => {
      console.error('WebSocket Error!')

      typeof errorHandler === 'function' && errorHandler()
    }

    this.socketInstance.onclose = () => {
      console.log('close!')

      this.stopHeartbeat()

      // 断线重连
      this.reconnect()

      typeof closeHandler === 'function' && closeHandler()
    }
  }

  /**
   * 发送信息
   * @param {String|ArrayBuffer|Blob|ArrayBufferView} message
   */
  sendMessage(message) {
    if (this.readyState === WebSocket.OPEN) {
      this.socketInstance.send(message)
    } else {
      if (this.messageQueue.length < 10) {
        this.messageQueue.push(message)
      } else {
        console.log('消息队列已满,请等待连接成功后再发送')
      }
    }
  }

  /**
   * 发送心跳包
   * @param {Number} timeout 发送心跳包的间隔,单位ms
   */
  startHeartbeat(timeout) {
    this.stopHeartbeat()

    if (this.readyState !== WebSocket.OPEN) {
      return
    }

    this.heartBeatIntervalId = setInterval(() => {
      this.sendMessage('heartbeat')
    }, timeout)
  }

  /**
   * 停止心跳包定时器
   */
  stopHeartbeat() {
    clearInterval(this.heartBeatIntervalId)
  }

  /**
   * 断线重连
   */
  async reconnect() {
    const { automaticReconnect } = this.options
  
    if (automaticReconnect && !this.isClosing && this.reconnectCount < 4 && this.readyState === WebSocket.CLOSED) {
      // 重连4次,超过后不再重试,每次等待时间参考SignalR
      const timeout = [0, 2000, 10000, 30000][this.reconnectCount] || 10000
      await sleep(timeout)
      console.log('reconnecting...')
      this.connect()
      this.reconnectCount++
    }
  }

  listenMessage() {
    const { messageHandler } = this.options

    this.socketInstance.onmessage = msg => {
      typeof messageHandler === 'function' && messageHandler(msg)
    }
  }

  close() {
    this.stopHeartbeat()

    this.isClosing = true
    this.socketInstance.close()
  }
}

4. 其他工具集

4.1. 操作dom

添加、移除、判断是否包含classname

/**
 * 判断元素是否有className
 * @param {Object[Element]} el
 * @param {String} cls className
 * @returns {Boolean}
 */
export function hasClass (el, cls) {
  if (!el || !cls) { return false }
  if (cls.includes(' ')) { throw new Error('className should not contain space.') }
  if (el.classList) {
    return el.classList.contains(cls)
  } else {
    return (' ' + el.className + ' ').includes(' ' + cls + ' ')
  }
}

/**
 * 给元素添加className
 * @param {Object[Element]} el
 * @param {String} cls className
 */
export function addClass (el, cls) {
  if (!el) { return }
  let curClass = el.className
  const classes = (cls || '').split(' ')

  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i]
    if (!clsName) { continue }

    if (el.classList) {
      el.classList.add(clsName)
    } else if (!hasClass(el, clsName)) {
      curClass += ' ' + clsName
    }
  }
  if (!el.classList) {
    el.setAttribute('class', curClass)
  }
}

/**
 * 给元素移除className
 * @param {Object[Element]} el
 * @param {String} cls className
 */
export function removeClass (el, cls) {
  if (!el || !cls) { return }
  const classes = cls.split(' ')
  let curClass = ' ' + el.className + ' '

  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i]
    if (!clsName) { continue }

    if (el.classList) {
      el.classList.remove(clsName)
    } else if (hasClass(el, clsName)) {
      curClass = curClass.replace(' ' + clsName + ' ', ' ')
    }
  }
  if (!el.classList) {
    el.setAttribute('class', trim(curClass))
  }
}

4.2. 睡眠函数

等待一定时间后再执行

export function sleep (wait) {
  return new Promise(resolve => setTimeout(resolve, wait))
}

推荐阅读

恰饭区

评论区 (0)

0/500

还没有评论,快来抢第一吧