前端常用的函数封装js篇

845 阅读5分钟

前言

我们在开发前端项目的时候时常会有一些相似的功能和需求,如果大量的ctrl+c and ctrl+v会造成大量的代码冗余,好的解决办法是我们自己有一个公共函数库和样式库用到的时候我们引用一下就好,这里我收集了一些常用的可复用的js和css跟大家分享一下。

全面的类型判断

由于typeof有无法分辨数组和对象的原因我们使用toString会更准确一些

function getType(data) {
  var toString = Object.prototype.toString;
  var dataType = data instanceof Element ?
    "element" // 为了统一DOM节点类型输出
    :
    toString.call(data).replace(/\[object\s(.+)\]/, "$1").toLowerCase()
  return dataType
};

数组去重

es6的情况使用Array.from(new Set(arr))会更简洁一些

function distinctArray(val) {
  if (!Array.isArray(val)) {
    console.log('type error!');
    return;
  }

  var array = [];
  for (var i = 0; i < val.length; i++) {
    if (array.indexOf(val[i]) === -1) {
      array.push(val[i]);
    }
  }
  return array;
}

截取路径后面参数,转成json对象

function getRequest() {
  var url = decodeURI(decodeURI(location.search)); //获取url中"?"符后的字串,使用了两次decodeRUI解码
  var theRequest = new Object();
  if (url.indexOf("?") != -1) {
    var str = url.substr(1);
    var strs = str.split("&");
    for (var i = 0; i < strs.length; i++) {
      theRequest[strs[i].split("=")[0]] = (strs[i].split("=")[1]);
    }
  }
  return theRequest;
}

Date添加自定义函数,时间格式转换

Date.prototype.format = function (fmt) {
  var o = {
    "M+": this.getMonth() + 1, //月份
    "d+": this.getDate(), //日
    "h+": this.getHours(), //小时
    "m+": this.getMinutes(), //分
    "s+": this.getSeconds(), //秒
    "q+": Math.floor((this.getMonth() + 3) / 3), //季度
    "S": this.getMilliseconds() //毫秒
  };
  if (!fmt) {
    fmt = 'yyyyMMddhhmmss';
  }
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
  }
  for (var k in o) {
    if (new RegExp("(" + k + ")").test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
    }
  }
  return fmt;
}

获取星期几,转中文

function getWeek(date, fmt) {
  fmt = fmt || '周'
  //横杠转换 iOS不支持
  date = date.replace(/-/g, '/');
  // 判断date是否是时间格式
  if (date && new Date(date) != 'Invalid Date') {
    var week = new Date(date).getDay();
    if (week == 0) {
      fmt += "日";
    } else if (week == 1) {
      fmt += "一";
    } else if (week == 2) {
      fmt += "二";
    } else if (week == 3) {
      fmt += "三";
    } else if (week == 4) {
      fmt += "四";
    } else if (week == 5) {
      fmt += "五";
    } else if (week == 6) {
      fmt = "六";
    }
  } else {
    fmt = '非正常时间格式'
  }
  return fmt
}

深拷贝

在JS中,数据类型分为基本数据类型和引用数据类型两种,对于基本数据类型来说,它的值直接存储在栈内存中,而对于引用类型来说,它在栈内存中仅仅存储了一个引用,而真正的数据存储在堆内存中

对于引用类型来说,我们将 obj1 赋予 obj2 的时候,我们其实仅仅只是将 obj1 存储在栈堆中的的引用赋予了 obj2 ,而两个对象此时指向的是在堆内存中的同一个数据,所以当我们修改任意一个值的时候,修改的都是堆内存中的数据,而不是引用,所以只要修改了,同样引用的对象的值也自然而然的发生了改变

防止出现两个对象相同的引用,出现修改一个对象影响到另一个对象的问题,我们使用深拷贝来解决这个问题

function deepCopy(arr) {
  var obj = arr instanceof Array ? [] : {};
  for (var item in arr) {
    if (arr[item] != "null" && arr[item] != null) {
      if (typeof arr[item] === "object") {
        obj[item] = qfyDeepCopy(arr[item]);
      } else {
        obj[item] = arr[item];
      }
    }
  }
  return obj;
}

数组排序

这里封装的是快排时间复杂度O(nlogn)比平常的冒泡排序O(n2)会好一些。

function quicklySort(brr) {
  if (!Array.isArray(brr)) {
    console.log('type error!');
    return;
  }
  if (brr.length <= 1) { //当数组brr的长度 <= 1 的时候,以为这brr这个数组只有一个数或者是个空数组,那么久直接返回,不需要往下进行
    return brr
  }
  var centerIndex = Math.floor(brr.length / 2); //获取中间数的下标
  var centerNum = brr.splice(centerIndex, 1)[0]; //通过中间数的下标来找中的数
  var left = [],
    right = []; //建立左右两个空的数组
  for (var i = 0; i < brr.length; i++) { //进行for循环
    if (brr[i] < centerNum) { //当下标所对应的数小于中间是数的时候,推入左边数组的最后边
      left.push(brr[i])
    } else { //不符合条件的推入右边数组的后边
      right.push(brr[i])
    }
  }
  // console.log(left,right)
  return quicklySort(left).concat([centerNum], quicklySort(right)) //用concat将左边数组、中间数和右边数组连接起来
}

保留小数点后几位(不四舍五入)默认两位

js中取小数点后两位方法最常用的就是toFixed方法了,但是toFixed会对保留小数进行四舍五入,如果我们有需求不进行四舍五入就得自己封装个保留小数的函数了。

/*
 * @Description: 保留小数点后几位(不四舍五入)默认两位
 * @param {Float} x 要处理的小数
 * @param {Float} num 保留几位
 */
// 备注:parseFloat、parseInt有问题。parseFloat('1a')==1
function qfyToDecimal(x, num) {
  // var f = parseFloat(x); 
  x = (x + '').trim(); //转成字符串 去空格
  var f = x / 1;
  // num = parseInt(num) || 2;
  num = num / 1 || 2;
  if (isNaN(f) || x === '' || isNaN(num) || num % 1 != 0) {
    console.log('type error!');
    return;
  }
  var divisorNum = Math.pow(10, num)
  var f = Math.round(x * divisorNum) / divisorNum;
  var s = f.toString();
  var rs = s.indexOf('.');
  if (rs < 0) {
    rs = s.length;
    s += '.';
  }
  while (s.length <= rs + num) {
    s += '0';
  }
  return s;
}

字符替换

/*
 * @Description: 字符替换
 * @param {String} str 要处理的字符串
 * @param {String} l 表示你将要替换的字符
 * @param {String} r 表示你想要替换的字符
 */
function qfyStrReplace(str, l, r) {
  if (typeof str != "string") {
    console.log('type error!');
    return;
  }
  var reg = new RegExp(l, 'g') // g表示全部替换,默认替换第一个
  str = str.replace(reg, r)
  return str
}

在字符串指定位置插入字符

方法有多种:

  1. 先使用split将字符串分割成数组,然后在指定位置索引上添加字符,最后使用join将数组转换成字符串
  2. 使用silce方法分别将字符0到指定索引和指定索引到字符串尾巴的字符串分割出来,再讲插入字符拼接在两个字符串中间

出于代码简洁性考虑这里提供slice方法:

/*
 * @Description: 在字符串指定位置插入字符
 * @param {String} character 原字符串
 * @param {Number} site 要插入的字符的位置
 * @param {String} newStr 想要插入的字符
 */
function insertStr(character, site, newStr) {
  if (typeof character != "string") {
    console.log('type error!');
    return;
  }
  return character.slice(0, site) + newStr + character.slice(site);
}

身份证号验证

这里只检查身份证号码是否符合规范,包括长度,类型。

身份证号码规范:身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X

/*
 * @Description: 身份证号验证
 * @param {String} val 需要验证的号码
 */
function checkCardNo(val) {
  var reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
  return reg.test(val)
}

浏览器判断

function qfyParseUA() {
  var u = navigator.userAgent;
  var u2 = navigator.userAgent.toLowerCase();
  return { //移动终端浏览器版本信息
    trident: u.indexOf('Trident') > -1, //IE内核
    presto: u.indexOf('Presto') > -1, //opera内核
    webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
    gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
    mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
    ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
    android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或uc浏览器
    iPhone: u.indexOf('iPhone') > -1, //是否为iPhone或者QQHD浏览器
    iPad: u.indexOf('iPad') > -1, //是否iPad
    webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
    iosv: u.substr(u.indexOf('iPhone OS') + 9, 3),
    weixin: u2.match(/MicroMessenger/i) == "micromessenger",
    ali: u.indexOf('AliApp') > -1,
  };
}

动态加载JS

/*
 * @param {String} url JS路径
 * @param {Function} callback JS加载完回调方法
 */
function qfyLoadScript(url, callback) {
  var script = document.createElement("script");
  script.type = "text/javascript";
  if (typeof (callback) != "undefined" && typeof callback == "function") {
    if (script.readyState) {
      script.onreadystatechange = function () {
        if (script.readyState == "loaded" || script.readyState == "complete") {
          script.onreadystatechange = null;
          callback();
        }
      };
    } else {
      script.onload = function () {
        callback();
      };
    }
  }
  script.src = url;
  document.body.appendChild(script);
}

移动端自适应

使用rem做移动自适应,rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单位,1rem = 根元素的字体大小的单位,比如html{font-size:20px;},那么1rem = 20px。

由于UI移动端一般用iphone6来做图,所有我们已iphone6的宽度375为基数来设置需要自适应元素的rem值,然后根据机型宽度和iphone6宽度的比例来设置根元素的字体大小。

function setRem(remNum) {
  if (typeof remNum != "number") {
    remNum = 20
  }
  (function (doc, win) {
    var docEl = doc.documentElement,
      resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
      recalc = function () {
        var clientWidth = docEl.clientWidth; //win.screen.width;//
        if (!clientWidth) return;
        docEl.style.fontSize = remNum * (clientWidth / 375) + 'px';
      };
    if (!doc.addEventListener) return;
    win.addEventListener(resizeEvt, recalc, false);
    doc.addEventListener('DOMContentLoaded', recalc, false);
  })(document, window);
}

控制input文本框只能输入整数

<input onkeyup="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{this.value=this.value.replace(/\D/g,'')}" onafterpaste="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{this.value=this.value.replace(/\D/g,'')}">

图片转base64

因为网页中使用base64格式的图片时,不用再请求服务器调用图片资源,减少了服务器访问次数,所以我们有时候会将图片转成base64格式

/**
*   url 图片链接或者是blob对象 
*   callback 回调函数
*/
function getImgToBase64(url, callback) {
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext('2d');
  var img = new Image(); //通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片,相比 createElement() 创建 <img> 省去了 append(),也就避免了文档冗余和污染 
  img.crossOrigin = 'Anonymous'; //要先确保图片完整获取到,这是个异步事件 
  img.onload = function () {
    canvas.height = img.height; //确保canvas的尺寸和图片一样 
    canvas.width = img.width;
    ctx.drawImage(img, 0, 0); //将图片绘制到canvas中 
    var dataURL = canvas.toDataURL('image/png'); //转换图片为dataURL,传第二个参数可压缩图片,前提是图片格式jpeg或者webp格式的 
    callback(dataURL); //调用回调函数 
    canvas = null;
  };
  img.src = url;
}

base64转file

适用场景:图片的base64编码转成file文件的功能,然后再上传至服务器。

/**
*   base64 base64
*   filename 转换后的文件名
*/
base64ToFile = (base64, filename )=> {
  let arr = base64.split(',')
  let mime = arr[0].match(/:(.*?);/)[1] 
  let suffix = mime.split('/')[1] // 图片后缀
  let bstr = atob(arr[1])
  let n = bstr.length
  let u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new File([u8arr], `${filename}.${suffix}`, { type: mime })
}

base64转blob

Blob 对象表示一个 Blob,它是不可变的原始数据的类似文件的对象;它们可以作为文本或二进制数据读取,也可以转换为 ReadableStream,因此可以使用其方法来处理数据。

Blob 可以表示不一定是 JavaScript 原生格式的数据。 File 接口基于 Blob,继承了 Blob 功能并将其扩展以支持用户系统上的文件。

所以base64转blob跟base64转file方法类似

base64ToBlob = base64 => {
  let arr = base64.split(','),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
};

file转base64

适用场景:通常用于把图片资源转成base64格式

/**
 * file 图片文件
 * 返回图片的Base64数据
 */
fileToBase64 = file => {
  let reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = function (e) {
    return e.target.result
  };
}

阻止事件冒泡

适用场景:一个按钮绑定了一个click事件同时它的父元素中也有绑定click事件,这时点击触发了按钮的click事件会出现冒泡式的触发其父元素的click事件,这种情况我们就得阻止按钮的事件冒泡。

function stopBubble(e) { 
    //如果提供了事件对象,则这是一个非IE浏览器 
    if ( e && e.stopPropagation ) 
        //因此它支持W3C的stopPropagation()方法 
        e.stopPropagation(); 
    else 
        //否则,我们需要使用IE的方式来取消事件冒泡 
        window.event.cancelBubble = true; 
}

阻止默认事件

适用场景:比如这样的一个需求 点击取消a标签按钮的时候会触发一些js动作,但是不能让这个a标签产生跳转行为,所以需要在点击取消的时候阻止默认事件,防止其跳转。

function stopDefault( e ) { 
    //阻止默认浏览器动作(W3C) 
    if ( e && e.preventDefault ) 
        e.preventDefault(); 
    //IE中阻止函数器默认动作的方式 
    else 
        window.event.returnValue = false; 
    return false; 
}

节流

函数节流是指一定时间内js方法只跑一次,比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。

适用场景:

  1. 拖拽场景,固定时间只执行一次,防超高频次触发。
  2. 缩放场景,监控浏览器resize,scroll。
function throttle(fn, wait) { 
    var firstTime = true,timer = null
    return function () {
        var _this = this
        if (firstTime) {
            fn.apply(_this, arguments)
            firstTime = false
        }
        if (timer) return
        // 一段时间触发一次
        timer = setTimeout(function () {
            clearTimeout(timer)
            // clearTimeout只是取消setTimeout,timer还要另外取空
            timer = null
            fn.apply(_this, arguments)
        }, wait)
    }
}

防抖

函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次,比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。

适用场景:

  1. 按钮提交场景,防止多次提交,只提交最后一次。
  2. 搜索框联想场景,防止联想发送请求,只发送最后一次输入。
function debounce(fn,wait){
    var timer = null;
    return function(){
        if(timer !== null){
            clearTimeout(timer);
        }
        timer = setTimeout(fn,wait);
    }
}