工具封装
对象深拷贝
//ES5写法//循环对象之前需要检测对象里面的属性值是否是引用值//当发现有引用值的时候需要遍历//不仅判断键值对是否含有引用值,还得判断是对象还是数组//利用递归克隆函数进行再次循环function deepClone(origin, target) {//万一用户不传target参数,自己默认创建空对象var target = target || {},toStr = Object.prototype.toString,arrType = '[object Array]';for (var key in origin) {//排除原型上的属性if (origin.hasOwnProperty(key)) {//判断是否为引用值 同时排除nullif (typeof (origin[key]) === 'object' && origin[key] !== null) {//判断引用值是否为数组类型if (toStr.call(origin[key]) === arrType) {//创建空数组target[key] = [];} else {//引用值是对象//创建空对象target[key] = {};}//递归,再次遍历deepClone(origin[key], target[key]);} else {//这里是递归的出口//遍历第一层 浅拷贝target[key] = origin[key];}}}return target;}// // var person2 = deepClone(person1);
//ES6 - weekmap写法//ES6 WeekMap实现对象深克隆//Map 键名为任意类型//WeekMap 键名只能为对象//本质区别//WeekMap为弱引用,会被垃圾回收,存在意义是希望键名的对象被回收时,键值也被回收掉//而函数移除需要赋值为null才能被垃圾回收//原理: 对象被拷贝过的,再拷贝就会死循环,利用weekmap记录一下是否被拷贝过var oldObj = {a: 1,b: 2,c: {e: 3,f: 4},d: ['5', '6', '7']}/*** 深拷贝对象* @param {*} origin 旧的数据* @hashMap 记录过的weakmap类型数据,唯一hash值,弱引用值是键名*/function deepClone(origin, hashMap = new WeakMap()) {//排除null undefined//排除原始值//null == undefined => true//null === undefined => falseif (origin == undefined || typeof origin !== 'object') {//不做处理返回对象本身return origin;}//Date RegExpif (origin instanceof Date) {return new Date(origin);}if (origin instanceof RegExp) {return new RegExp(origin);}//找到对象键名里面值const hashKey = hashMap.get(origin);//判断是否存在if (hashKey) {//返回本身不做处理return hashKey;}//通过对象的构造器new一个新的实例对象//此方式数组也适用const target = new origin.constructor();//记录保存一下是否有过深拷贝//如果之前拷贝过了就不会再次拷贝hashMap.set(origin, target);//{} []for (let k in origin) {//如果对象里包含本身的属性则拷贝if (origin.hasOwnProperty(k)) {//将原来的对象复制到新的对象//如果原来对象里面的属性也有对象,则递归复制target[k] = deepClone(origin[k], hashMap);}}return target;}console.log(oldObj);//{a: 1, b: 2, c: {e: 3, f: 4}, d: ["5", "6", "7"]}const newObj = deepClone(oldObj);newObj.c.g = 5;newObj.d[3] = '8';console.log(newObj);//{a: 1, b: 2, c: {e: 3, f: 4, g: 5}, d: ["5", "6", "7", "8"]}
类型查找
//封装myTypeof()function myTypeof(val) {var type = typeof (val),toStr = Object.prototype.toString,res = {'[object Array]': 'array','[object Object]': 'object','[object Number]': 'object number','[object String]': 'object string','[object Boolean]': 'object boolean'}if (val === null){return 'null';//object: null {} []//这里返回包括包装类Number, String, Boolean} else if (type === 'object') {var ret = toStr.call(val);return res[ret];//返回原始值//} else {return type;}}
滚动距离
/*** 封装兼容IE8&IE9以下的滚动条距离函数* @返回值 返回左/上滚动距离* 找到滚动距离* getScrollOffset()*/function getScrollOffset() {if (window.pageXOffset) {return {left: window.pageXOffset,top: window.pageYOffset}} else {return {left: document.body.scrollLeft + document.documentElement.scrollLeft,top: document.body.scrollTop + document.documentElement.scrollTop}}}getScrollOffset().top;
可视区域
/*** 封装兼容IE8&IE9以下的可视区域宽高尺寸的函数* 获取可视窗口宽度/高度* 原理:判断模式是否为怪异*/function getViewportSize() {if (window.innerWidth) {return {width: window.innerWidth,height: window.innerHeight}} else {if (document.compatMode === 'BackCompat') {return {width: document.body.clientWidth,height: document.body.clientHeight}} else {return {width: document.documentElement.clientWidth,height: document.documentElement.clientHeight}}}}getViewportSize().width
滚动长度
/*** 封装兼容IE8&IE9以下的滚动尺寸的函数* 获取整个文档的宽度/高度*/function getScrollSize() {if (document.body.scrollWidth) {return {width: document.body.scrollWidth,height: document.body.scrollHeight}} else {return {width: document.documentElement.scrollWidth,height: document.documentElement.scrollHeight}}}getScrollSize().width
查看盒子到页面边缘的距离
/*** 封装合并子盒子与父盒子到左侧/上侧 到 页面左侧/上侧的 距离*/function getElemDocPosition(el) {//找到有定位的父级盒子var parent = el.offsetParent,//找到当前盒子左侧/上侧到页面左侧/上侧的距离offsetLeft = el.offsetLeft,offsetTop = el.offsetTop;// 如果parent存在while (parent) {// 循环出来的parent是定位元素offsetLeft += parent.offsetLeft;offsetTop += parent.offsetTop;//重新赋值parent,找到外层盒子继续加parent = parent.offsetParent;}return {left: offsetLeft,top: offsetTop}}getElemDocPosition(son);//{left: 230, top: 230}
鼠标坐标
/*** 封装页面坐标函数pagePos()* @e 元素* @返回值 页面内的x/y坐标*/function pagePos(e) {//获取滚动条距离//使用获取滚动条距离函数var sLeft = getScrollOffset().left,sTop = getScrollOffset().top,//获取文档偏移//documentElement.clientLeft IE8及以下不存在(undefined)cLeft = document.documentElement.clientLeft || 0,cTop = document.documentElement.clientTop || 0;return {//可视区域坐标 + 滚动条距离 - 偏移距离X: e.clientX + sLeft - cLeft,Y: e.clientY + sTop - cTop}}
鼠标拖拽
/*** 封装拖拽函数*/function elemDrag(elem) {var x, y;//鼠标按下时addEventListener(elem, "mousedown", function (e) {var e = e || window.event;//pagePos()找到鼠标点击的坐标//getStyles()找到目标元素左上顶点到浏览器窗口边缘的left/top值x = pagePos(e).X - getStyles(elem, "left");y = pagePos(e).Y - getStyles(elem, "top");addEventListener(document, "mouseMove", mousemove);addEventListener(document, "mouseUp", mouseup);cancelBubble(e);preventDefaultEvent(e);});//鼠标移动时function mouseMove(e) {var e = e || window.event;//鼠標移動后有新的pagePos().X/Y值//雖然鼠標位置變了,但是計算后的x/y值是不變的//新的pagePos().X/Y值 - x/y = 新目標元素到瀏覽器邊緣的距離elem.style.top = pagePos(e).Y - y + "px";elem.style.left = pagePos(e).X - x + "px";}//鼠标抬起时释放绑定的事件function mouseUp(e) {var e = e || window.event;removeEvent(document, "mousemove", mouseMove);removeEvent(document, "mouseup", mouseUp);}}
元素属性
/*** 获取元素属性* @elem 元素* @prop 属性* @返回值 返回指定元素的属性值*/function getStyles(elem, prop) {//检测getComputedStyle是否存在if (window.getComputedStyle) {//存在,打印具体属性值if (prop) {return parseInt(window.getComputedStyle(elem, null)[prop]);}//不存在,打印集合return window.getComputedStyle(elem, null);} else {if (prop) {return parseInt(elem.currentStyle[prop]);} else {return elem.currentStyle;}}}getStyles(div);getStyles(div, 'height'); //200px
事件函数
/*** 封装兼容低版本的事件绑定处理函数* @el 元素* @type 事件类型* @fn 事件处理函数*/function addEvent(el, type, fn) {if (el.addEventListener) {el.addEventListener(type, fn, false);} else if (el.attachEvent) {el.attachEvent('on' + type, function () {fn.call(el);})} else {el['on' + type] = fn;}}
移除事件
/*** 解除事件处理函数* @el 元素* @type 事件类型* @fn 事件处理函数*/function removeEvent(elem, type, fn) {if (elem.addEventListener) {elem.removeEventListener(type, fn, false);} else if (elem.attachEvent) {elem.detachEvent('on' + type, fn);} else {elem['on' + 'type'] = null;}}
找子元素
//找子元素函数function elemChildren(node) {var temp = {'length': 0,'push': Array.prototype.push,'splice': Array.prototype.splice};var children = node.childNodes,len = children.length,item;for (var i = 0; i < len; i++) {item = children[i];if (item.nodeType === 1) {temp.push(item);}}return temp;}
找父元素
/*** 封装找父级元素的函数* @node 子元素节点* @n 寻找第几个父级元素* @返回值 返回父级元素*/function elemParent(node, n) {var type = typeof (n);if (type === 'undefined') {return node.parentNode;} else if (n <= 0 || type !== 'number') {return undefined;}while (n) {node = node.parentNode;n--;}return node;}
取消冒泡
/*** 兼容性写法封装:* 封装取消冒泡方法*/function cancelBubble(e) {var e = e || window.event;if (e.stopPropagation) {e.stopPropagation();} else {e.cancelBubble = true;}}
/*** 兼容性写法封装:* 封装取消冒泡方法*/function preventDefaultEvent(e) {var e = e || window.event;if (e.preventDefault) {event.preventDefault();} else {event.returnValue = false;}}
文档解析
封装文档解析完毕函数
function domReady(fn) {if (document.addEventListener) {document.addEventListener('DOMContentLoaded', function () {//释放内容document.removeEventListener('DOMContentLoaded', arguments.callee, false);fn();}, false);//兼容IE} else if (document.attachEvent) {document.attachEvent('onreadystatechange', function () {//文档加载完成if (this.readyState === 'complete') {document.attachEvent('onreadystatechange', arguments.callee);fn();}})}//doScroll可以操作滚动条//如果文档没有解析完渲染完会报错if (document.documentElement.doScroll && typeof (window.frameElement) === 'undefined') {try {document.documentElement.doScroll('left');} catch (e) {return setTimeout(arguments.callee, 20);}fn();}}domReady(fn);
行为预测
判断是否在三角区域
/*** 行为预测技术:* 判断是否在三角区域*///判断点是否在一个三角形内function vec(a, b) {return {x: b.x - a.x,y: b.y - a.y}}function vecProduct(v1, v2) {return v1.x * v2.y - v2.x * v1.y;}function sameSymbols(a, b) {return (a ^ b) >= 0;}function pointInTriangle(p, a, b, c) {var PA = vec(p, a),PB = vec(p, b),PC = vec(p, c),R1 = vecProduct(PA, PB),R2 = vecProduct(PB, PC),R3 = vecProduct(PC, PA);return sameSymbols(R1, R2) && sameSymbols(R2, R3);}
模板替换
// 正则规则function regTpl() {// gim 全局大小写不敏感多行return new RegExp(/{{(.*?)}}/, 'gim');}/*** 字符串替换* @tpl 模板* @regExp 匹配规则* @opt 一个对象保存被传的key值** 函数是匹配规则:关键点根据正则匹配{{}}里的某一个key值* @node 关键点 如 {{career}}* @key 就是opt对象里面的属性值*/function setTplToHTML(tpl, regExp, opt) {// ''.replace(替换目标, 替换值)// ''.replace(正则表达式, 函数(node,key){})return tpl.replace(regExp(), function (node, key) {//return被替换的key值return opt[key];});}//调用写法:list += setTplToHTML(tpl, regTpl, {career: item.career,city: item.city,salary: item.salary,img: item.img});
AJAX
/*** AJAX封装** 调用写法一:* $.ajax({* url: 'xxx',* type: 'POST',* dataType: 'JSON',* data: {* status: 1* },* success: function (data) {* console.log(data);* }* })** 调用写法二:* $.post('http://localhost/xxx', { status: 1 }, function (data) { console.log(data); });** 调用写法三:* $.get('http://localhost/xxx?status=1', function (data) { console.log(data); });***///老写法:// var $ = {// ajax: function (opt) {// var url = opt.url;// console.log(url);// },// post: function () {// console.log('post');// },// get: function () {// console.log('get');// }// }// 模块化写法var $ = (function () {//利用_doAjax函数传参拿到局部作用域ajax函数里的形参function _doAjax(opt) {//兼容IE5,IE6var o = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');//IE4及以下if (!o) {throw new Error('您的浏览器不支持异步发起HTTP请求');}//初始化传入的配置var opt = opt || {},// console.log(opt); //{type: "POST"}//初始化请求类型为GETtype = (opt.type || 'GET').toUpperCase(),// console.log(type); //POST// 同步/异步 false/trueasync = '' + opt.async === 'false' ? false : true,url = opt.url,//如果GET请求就为nulldata = opt.data || null,//响应数据dataType = opt.dataType || 'JSON',//后端定义的cb或callback字符串jsonp = opt.jsonp || 'cb',//定义的函数名称方便给后端匹配jsonpCallback = opt.jsonpCallback || 'jQuery' + randomNum() + '_' + new Date().getTime(),timeout = opt.timeout || 30000,error = opt.error || function () {},success = opt.success || function () {},//不管成功或失败都执行complete函数complete = opt.complete || function () {},// 初始化定时器t = null;if (!url) {throw new Error('您没有填写url');}if (dataType.toUpperCase === 'JSONP' && type !== 'GET') {throw new Error('如果dataType为JSONP,请您将type设置为GET');}//格式: xxx.domain.com/xxx.php?jsonp=jsonpCallback//情况1:xxx.domain.com/xxx.php?cb=test//情况2:xxx.domain.com/xxx.php?wd=xxx&cb=testif (dataType.toUpperCase() === 'JSONP') {var oScript = document.createElement('script');oScript.src = url.indexOF('?') === -1 ?//情况1:如果没有? 则加?url + '?' + jsonp + '=' + jsonpCallback ://情况2:如果有? 证明多参数情况下加 & 隔开url + '&' + jsonp + '=' + jsonpCallback;document.body.appendChild(oScript);document.body.removeChild(oScript);//定义函数并挂载到windowwindow[jsonpCallback] = function (data) {//关联ajax里的success函数,执行success函数success(data);}//阻止ajax向下执行return;}/**发送HTTP请求* @type 请求类型* @url 请求地址* @async 异步/同步*/o.open(type, url, async);//超时设置 写法一: 设置超时时间 30s//兼容性不太好// o.ontimeout = 30000;//设置POST请求头//如果为真走后面type === 'POST' && o.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');//如果是GET请求,不用传参数o.send(type === 'GET' ? null : formatDatas(data));//监听响应事件o.onreadystatechange = function () {if (o.readyState === 4) {//请求已完成,且响应已就绪if (o.status >= 200 && o.status < 300 && o.status === 304) {switch (dataType.toUpperCase()) {case 'JSON':// 成功时响应服务器JSON数据success(JSON.parse(o.responseText));break;case 'TEXT':// 成功时响应服务器文本数据success(o.responseText);break;case 'XML':// 成功时响应服务器XML数据success(o.responseXML);break;default:// 默认响应服务器JSON数据success(JSON.parse(o.responseText));}} else {error();}// 无论成功与否都要执行complete函数complete();clearTimeout(t);t = null;o = null;}}// 超时设置 写法一://只要超时就会执行的函数//兼容性不太好// o.ontimeout = function () {// //o对象的所有程序都会中止// o.abort();// //并销毁对象// o = null;// }// 超时设置 写法二:t = setTimeout(function () {//o对象的所有程序都会中止o.abort();clearTimeout(t);t = null;o = null;//抛出错误throw new Error('This request has been timeout for' + url);}, timeout);}//希望将{status:1,flag:2}转为'status=1&flag=2'//格式化传入的data数据function formatDatas(obj) {var str = '';for (var key in obj) {str += key + '=' + obj[key] + '&';}//去掉最后一项的 &//正则规则:以&结尾 替换为空字符return str.replace(/&$/, '');}return {ajax: function (opt) {_doAjax(opt);},get: function (url, dataType, successCB, errorCB, completeCB) {_doAjax({type: 'GET',url: url,dataType: dataType,success: successCB,error: errorCB,complete: completeCB});},post: function (url, data, dataType, successCB, errorCB, completeCB) {_doAjax({type: 'POST',url: url,data: data,dataType: dataType,success: successCB,error: errorCB,complete: completeCB});}}})();//随机生成function randomNum() {var num = '';for (var i = 0; i < 20; i++) {num += Math.floor(Math.random() * 10);}return num;}
替换空格
function trimSpace(str) {return str.replace(/\s+/gim, '');}
事件源
/*** 封装 获取target 函数 并兼容IE* @param {*} e 事件对象* @param {*} mark 标识 判断是tagName 还是 className* @返回值 一个对象包含tar和mark*/function getTarget(e, mark) {var e = e || window.event,tar = e.target || e.srcElement;mark = mark == 'className' ? tar.className : tar.tagName.toLowerCase();return {tar: tar,mark: mark}}
获取节点
/*** 封装获取元素节点函数(类似jquery)* 执行语句写法:* $get('.box')[0]* $get('#box')* $get('div')[2]*/function $get(target) {//找到第一个字符var _s = target.charAt(0),rTarget = target.replace(_s, '');switch (_s) {case '#'://idreturn document.getElementById(rTarget);break;case '.'://classreturn document.getElementsByClassName(rTarget);break;default:return document.getElementsByTagName(target);}}
异步加载
写法一
//阻塞onload执行//异步加载(function () {//阻塞onload执行function async_load() {var s = document.createElement("script");s.type = "text/javascript";s.async = true;s.src = "utils.js";oScript.parentNode.insertBefore(s, oScript);//执行document.body.appendChild(s);}if (window.attachEvent) {window.attachEvent("onload", async_load);} else {window.addEventListener("load", async_load, false);}})();
写法二
// 通过readyState事件判断 -> onreadystatechange输出的值判断页面是否加载完毕 -> IEfunction exec_util_with_loading_script(url, fn) {//异步加载var s = document.createElement('script'),oScript = document.getElementsByTagName('script')[0];s.type = 'text/javascript';//如果存在,则为IE浏览器//readyState为标签返回的状态码if (s.readyState) {s.onreadychange = function () {var state = s.readyState;//onload会一直监听页面加载是否完毕if (state === 'complete' || state === 'loaded') {utils[fn]();}}} else {s.onload = function () {utils[fn]();}}//readyState需要在资源加载完毕后才执行s.src = url;oScript.parentNode.insertBefore(s, oScript);}//调用exec_util_with_loading_script('utils.js', 'test')
时间日期
/*** 格式化时间日期* JavaScript时间单位带毫秒* PHP时间不带毫秒* @ts 时间戳 如1548744604* @type 日期格式 如日期/时间/日期+时间*/function getDateTime(ts, type) {// 转为字符串才有length属性var len = ts.toString().length;//证明是php以秒为单位的格式,需要转为JS单位if (len === 10) {ts = ts * 1000;}//获取年月日时分秒var dt = new Date(ts),y = dt.getFullYear(),//注意:month从0开始m = addZero(dt.getMonth() + 1),d = addZero(dt.getDate()),h = addZero(dt.getHours()),i = addZero(dt.getMinutes()),s = addZero(dt.getSeconds());switch (type) {case 'date':return y + '-' + m + '-' + d;break;case 'time':return h + ':' + i + ':' + s;break;case 'dateTime':return y + '-' + m + '-' + d + ' ' + h + ':' + i + ':' + s;break;default:return y + '-' + m + '-' + d + ' ' + h + ':' + i + ':' + s;}//把一位数字补齐为两位 格式为 1 -> 01function addZero(num) {return num < 10 ? ('0' + num) : num;}}
cookie
/*** 封装cookie增删改查的函数*/var manageCookies = {/*** 设置cookie属性及时间* 例如:document.cookie = 'name=xiaohong;max-age=1000'* 注意:只能逐条设置* @param {*} key 属性名* @param {*} value 属性值* @param {*} expTime 过期时间* @returns*/set: function (key, value, expTime) {document.cookie = key + '=' + value + ';max-age=' + expTime;//实现链式调用return this;},/*** 删除cookie属性* 例如: document.cookie = 'name=xiaohong;max-age=-1'* @param {*} key 被删的属性*/delete: function (key) {return this.set(key, '', -1);},/*** 查询cookie属性* 例如:document.cookie => 'hobby=basketball;sex=male;age=20'* 使用:get('hobby', function(data){console.log(data)})* @param {*} key* @param {*} cb* @returns*/get: function (key, cb) {//将拿到的字符串用冒号分割成为数组//'hobby=basketball;sex=male;age=20'// => ['hobby=basketball','sex=male','age=20']var CookiesArray = document.cookie.split('; ');for (var i = 0; i < CookiesArray.length; i++) {var CookieItem = CookiesArray[i];//循环每一项并用等号分割//['hobby','basketball']//['sex','male']//['age','20']var CookieItemArray = CookieItem.split('=');if (CookieItemArray[0] == key) {cb(CookieItemArray[1]);return this;}}//如果没有cookie里面的属性报undefinedcb(undefined);return this;}}
请求数据
在项目中多个API请求时统一管理
//api > request.js//封装get请求函数const ERR_OK = 200;//柯里化的形式固定第一个参数//http://localhost:8080/area/info?name=广州市function getData(url) {return function (params) {return axios.get(url, params).then(res => {const {status,data} = res;if (status === ERR_OK) {return data;}}).catch(e => {});}}export {getData}
//api > index.jsimport {get} from './request';//它接收一个函数const getCityInfo = get('/area/info');export {getCityInfo}
url参数获取
获取url地址参数并返回一个对象参数
//获取url地址参数function getUrlQueryParams(queryStr) {var result = {};//'?name=广州市&area=tianhe'//[?&]指的是以 ? 或 & 开头//[]指的是 一串非 ? 和 & 的字符 一个或多个// =var reg = /[?&][^?&]+=[^?&]+/g,found = queryStr.match(reg),tem,key,value;//['?name=广州市', '&area=tianhe']//有匹配结果时if (found) {found.forEach((item) => {//去除第一个字符 ? 或 &//以 = 隔开 拿到隔开后的数组 ['name', '广州市']tem = item.substring(1).split('=');key = tem[0];value = tem[1];result[key] = value;});}// console.log(result);//{name: '广州市', area: 'tianhe'}return result;}
