整理一些实际开发中用到的方法

300 阅读4分钟

正则相关

金额千分位分隔符

    num = 1234567.01
    regNum = String(num).replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, "$1,")
    console.log(regNum); //1,234,567.01

小数的校验

    const floatReg = /^[-\+]?\d+(\.\d+)?$/

    const floatNum = '-1234.68'
    const errorNum = '-1234.6.8'
    console.log(floatReg.test(floatNum)) // true
    console.log(floatReg.test(errorNum)) // false

校验手机号码

    const phoneReg = /^[1][3-9][0-9]{9}$/
    const phone1 = '13032326767'
    console.log(phoneReg.test(phone1)) // true
    const phone2 = '12830172038'
    console.log(phoneReg.test(phone2)) // false

身份证号码的校验

const sfzReg = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/

const sfzStr1 = '415106199601013214'
console.log(sfzReg.test(sfzStr1)) // true

const sfzStr2 = '21078119830121218X'
console.log(sfzReg.test(sfzStr2)) // true

邮箱格式校验

const emailReg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/

const emailSina = 'pass_1@sina.com' // 新浪邮箱
const email163 = 'fe_vue@163.com' // 163邮箱
const notEmail = 'test.com' // 错误邮箱
console.log(emailReg.test(emailSina)) // true
console.log(emailReg.test(email163)) // true
console.log(emailReg.test(notEmail)) // false

密码校验

长度为8-16位,包含大小写字母、数字和特殊字符

const passwordReg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,16}$/

const passwordStr = 'w123W!@4123'
const illegalStr = 'w123W4123412'

console.log(passwordReg.test(passwordStr)); // true
console.log(passwordReg.test(illegalStr)); // false

检测文件格式

const checkFileName = arr => new RegExp(`(${arr.map(name => `.${name}`).join('|')})$`)

// 检测是否为 .txt, .png 或者 .md 文件格式
const filenameReg = checkFileName(['txt', 'png', 'md'])

const jpgFile = 'sun.jpg'
const pngFile = 'sun.png'
const txtFile = 'sun.txt'
const mdFile = 'sun.md'

console.log(filenameReg.test(jpgFile)) // false
console.log(filenameReg.test(pngFile)) // true
console.log(filenameReg.test(txtFile)) // true
console.log(filenameReg.test(mdFile)) // true

清除对象中的空值

向后台请求数据时,入参是一个对象,对象中有空值时需要清除掉.(看前后端约定,也可以后台同学做处理,不过最好前后端都处理)

/**
 * 判断值是否为空
 */
 const isVoid = (value) =>
  value === undefined || value === null || value === ""

 const cleanObject = (object) => {
  if (!object) {
    return {}
  }
  const result = { ...object };
  const excute = obj => Object.keys(obj).forEach((key) => {
    const value = obj[key]
    typeof value === 'object' && value !== null && excute(value)
    isVoid(value) &&  delete obj[key]
  })
  excute(result)
  return result
}

防抖

平时开发中,会有很多场景会频繁触发事件,比如说搜索框实时发请求. 短时间内多次触发同一事件,只执行最后一次,中间的不执行。

/**
 * 非立即执行版
 */
function debounce(func, wait) {
    let timer;
    return function() {
      // this 指向
      let context = this; 
      let args = arguments; 
      if (timer) clearTimeout(timer);
      timer = setTimeout(function() {
        func.apply(context, args)
        timer = null
      }, wait)
    }
}

立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果

/**
 * 立即执行版
 */
 function debounce(func, wait) {
  let timer
  return function() {
    // this 指向
    let context = this
    let args = arguments
    if (timer) clearTimeout(timer)
    let callNow = !timer
    timer = setTimeout(() => {
      timer = null
    }, wait)
    callNow && func.call(context, ...args)
  }
}

合成版本

/**
   * @desc 函数防抖
   * @param func 目标函数
   * @param wait 延迟执行毫秒数
   * @param immediate true - 立即执行, false - 延迟执行
   */
 function debounce(func, wait, immediate = false) {
  let timer
  return function() {
    let context = this,
         args = arguments
    if (timer) clearTimeout(timer);
    if (immediate) {
      let callNow = !timer
      timer = setTimeout(() => {
        timer = null;
      }, wait);
      if (callNow) func.apply(context, args);
    } else {
      timer = setTimeout(() => {
        func.apply(context, args);
        timer = null
      }, wait)
    }
  }
}

节流

平时开发中,会有很多场景会频繁触发事件,比如拖动改变浏览器窗口.

时间戳版本, 首节流

function throttle(func, wait) {
  let previous = 0;
  return function() {
    let now = Date.now();
    let context = this;
    let args = arguments;
    if (now - previous > wait) {
      func.apply(context, args);
      previous = now;
    }
  }
}

定时器版本,尾节流

function throttle(func, wait) {
  let timer
  return function() {
    let context = this
    let args = arguments
    if (!timer) {
      timer = setTimeout(() => {
        timer = null
        func.apply(context, args)
      }, wait)
    }
  }
}

完整版(时间戳 + 定时器)

/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 */
 function throttle(func, wait) {
  let timer = null, previous = 0
  return function() {
    let remainTime = wait - Date.now() - previous
    let context = this
    let args = arguments
    const excute = () => {
      func.apply(context, args)
      previous = Date.now()
    }
    timer && clearTimeout(timer)
    if(remainTime <= 0) {
      excute()
    } else {
      timer = setTimeout(() => {
        excute()
        timer = null
      }, remainTime)
    }
  }
}

深拷贝

const isObject = val => typeof val === "object" && val !== null;

const deepClone = (obj, hash = new WeakMap()) => {
  if (!isObject(obj)) return obj;
  // 日期对象直接返回一个新的日期对象
  if (obj instanceof Date){
    return new Date(obj);
   } 
   //正则对象直接返回一个新的正则对象     
   if (obj instanceof RegExp){
    return new RegExp(obj);     
   }
   //如果循环引用,就用 weakMap 来解决
   if (hash.has(obj)){
    return hash.get(obj);
   }
  const target = Array.isArray(obj) ? [] : {};
  hash.set(obj, target);
  Reflect.ownKeys(obj).forEach(item => {
    if (isObject(obj[item])) {
      target[item] = deepClone(obj[item], hash);
    } else {
      target[item] = obj[item];
    }
  });
  return target;
};

export default deepClone

前端文件下载

通常有2种方式

  1. 后端给一个链接,直接通过 window.open(url)打开即可下载,这种通常适用于这个链接可以在公网访问,不需要token鉴权。
  2. 后端给一个接口,通过传参请求接口,后端返回一个文件流,前端处理文件流,通过a标签下载。
// 要预先安装axios
import axios from 'axios'

export default options => {
    const { url, method = 'get', params, fileName = '' } = options;
    const data = method === 'get' ? { params } : { data: params };
    const config = {
      url,
      method,
      data,
      responseType: "blob",
  };
    return new Promise((resolve, reject) => {
        axios(config)
        .then(res => {
            if (res.data.size > 0) {
                let userFileName = ''
                if (!fileName) {
                    userFileName = res.headers['content-disposition'].split('filename=')[1]
                    if (userFileName && userFileName.includes('"')) userFileName = userFileName.slice(1, -1);
                } else {
                    userFileName = fileName
                }
                // 获取文件格式
                const contentType = res.headers['content-type']
                const blob = new Blob([res.data], { type: contentType })
                const downloadElement = document.createElement('a')
                // 创建下载的链接
                const href = window.URL.createObjectURL(blob) 
                downloadElement.href = href
                // 下载后文件名
                downloadElement.download = !userFileName ? Date.now().toString() : userFileName  
                document.body.appendChild(downloadElement)
                // 点击下载
                downloadElement.click() 
                // 下载完成移除元素
                document.body.removeChild(downloadElement)
                // 释放掉blob对象
                window.URL.revokeObjectURL(href)
                resolve(res)
            } else {
                console.error('Data is null!')
            }
        })
        .catch(function (error) {
            return reject(error)
        })
    })
}