复制文本
1 2 3 4 5 6 7 8 9 10 11 12 13 function copyText (text ) { let textareaEl = document .createElement ('textarea' ); textareaEl.style .cssText = 'position: absolute;opacity: 0;' ; textareaEl.value = text; document .body .appendChild (textareaEl); textareaEl.select (); document .execCommand ('copy' ); textareaEl.remove (); }
悬停激活
1 2 3 4 5 6 7 8 9 10 function hoverActive (el ) { for (const childrenEl of [...el.parentNode .children ]) { childrenEl.classList .remove ('active' ); } el.classList .add ('active' ); }
处理滚动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function handleScroll (el, direction = 'right' , step = 250 ) { let list = el.parentNode .querySelector ('.list' ); if (direction === 'up' ) list.scrollTo ({ top : list.scrollTop - step, behavior : "smooth" }); if (direction === 'down' ) list.scrollTo ({ top : list.scrollTop + step, behavior : "smooth" }); if (direction === 'left' ) list.scrollTo ({ left : list.scrollLeft - step, behavior : "smooth" }); if (direction === 'right' ) list.scrollTo ({ left : list.scrollLeft + step, behavior : "smooth" }); }
切换Tab
1 2 3 4 5 6 7 8 9 10 11 12 13 function switchTab (el, index ) { let tabsEl = el.parentNode ; let contentEl = el.parentNode .parentNode .querySelector ('.content' ); for (const item of [...tabsEl.children ]) item.classList .remove ('active' ); tabsEl.children [index].classList .add ('active' ); for (const item of [...contentEl.children ]) item.classList .remove ('active' ); contentEl.children [index].classList .add ('active' ); }
改变页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function changePage (el, action ) { let parentEl = el.parentNode .parentNode ; let paginationEl = el.parentNode ; let contentItems = [...parentEl.querySelectorAll (':scope > .content > *' )]; let paginationItems = [...paginationEl.querySelectorAll ('.item' )]; let currentIndex = 0 ; contentItems.some ((item, key ) => { if (item.classList .contains ('active' )) { currentIndex = key; return true ; } }); for (const item of contentItems) item.classList .remove ('active' ); for (const item of paginationItems) item.classList .remove ('active' ); if (action === 'previous' ) { contentItems[getIndex (currentIndex - 1 )].classList .add ('active' ); paginationItems[getIndex (currentIndex - 1 )].classList .add ('active' ); } else if (action === 'next' ) { contentItems[getIndex (currentIndex + 1 )].classList .add ('active' ); paginationItems[getIndex (currentIndex + 1 )].classList .add ('active' ); } else if (action.constructor === Number ) { contentItems[getIndex (action)].classList .add ('active' ); paginationItems[getIndex (action)].classList .add ('active' ); } function getIndex (index ) { if (index > paginationItems.length - 1 ) index = 0 ; else if (index < 0 ) index = paginationItems.length - 1 ; return index; } }
自适应的rem单位,用于适配各种移动设备
1 2 3 4 5 6 7 8 9 10 (function ( ) { function setRemUnit ( ) { const baseSize = 1 ; const designWidth = 750 ; const scale = window .innerWidth / designWidth; document .documentElement .style .fontSize = baseSize * Math .min (scale, 2 ) + 'px' ; } setRemUnit (); window .addEventListener ('resize' , setRemUnit); })();
检测浏览器设备,并切换至对应域名
1 2 3 4 5 6 7 if (new RegExp ('iPhone|iPad|iPod|Android|Mobile' , 'i' ).test (window .navigator .userAgent )) { if (new RegExp ('^https?:\\/\\/(?!m\\.)' , 'i' ).test (window .location .href )) window .location .assign (window .location .href .replace (new RegExp ('^(https?:\\/\\/)(.*)' , 'i' ), '$1m.$2' )); } else { if (new RegExp ('^https?:\\/\\/m\\.' , 'i' ).test (window .location .href )) window .location .assign (window .location .href .replace (new RegExp ('^(https?:\\/\\/)m\\.' , 'i' ), '$1' )); }
判断值是否为 null 或 undefined
1 2 3 4 5 6 7 8 9 function isNull (value ) { if (value === null || value === undefined ) return true ; return false ; }
空置合并:相同于 ??(空置合并运算符)
适用于无法使用 ??
的时候,可通过该Util实现相同作用
1 2 3 4 5 6 7 8 9 10 function nullMerge (value, replaceNull = value ) { if (value === null || value === undefined ) return replaceNull; return value; }
可选链:相同于 ?.(可选链操作符)
适用于无法使用 ?.
的时候,可通过该Util实现相同作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function optionalChain (object ) { if (typeof object !== 'object' ) return undefined ; return new Proxy (object, { get : (object, propertyKey ) => { let propertyKeyArray = propertyKey.replace (new RegExp ('([^\\/]|^)\\?\\.' , 'g' ), '$1{delimiter}' ).split ('{delimiter}' ); propertyKeyArray.forEach ((item, index ) => propertyKeyArray[index] = item.replace (/\/\?\./g , '?.' )); return propertyKeyArray.reduce ((a, b ) => (a || object)?.[b], object); } }); }
日期格式化
常用的日期格式化,如果有更复杂的需求可通过 Moment.js 库获得更好的日期支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function dateFormat (dateParams = new Date ().getTime(), format = 'yyyy-MM-dd HH:mm:ss' ) { let date = new Date (dateParams); let dateObject = { "y+" : date.getFullYear (), "M+" : date.getMonth () + 1 , "d+" : date.getDate (), "H+" : date.getHours (), "m+" : date.getMinutes (), "s+" : date.getSeconds (), "S+" : date.getMilliseconds (), "q+" : Math .floor ((date.getMonth () + 3 ) / 3 ), }; let matchArray = []; for (let key in dateObject) { matchArray = format.match (new RegExp (`(${key} )` , 'g' )); if (matchArray && matchArray.length ) { let fillZero = '' ; for (let i = 0 ; i < matchArray[0 ].length ; i++) { fillZero = fillZero + '0' ; } if (/S+/g .test (matchArray[0 ])) format = format.replace (matchArray[0 ], dateObject[key]); format = format.replace (matchArray[0 ], (fillZero + dateObject[key]).substring (("" + dateObject[key]).length )); } } return format; }
输入数字限制
项目中常常要求输入框的数字限制,咱干脆就一次整个方法,方便处理输入数字时的各种要求限制,已经过不少维护改良。仅需填写对应规则参数就能轻松解决各种限制要求
1 2 3 4 5 6 7 8 <el-input placeholder ="请输入" v-model ="item.number" @input ="inputNumberLimit({ target: [item, 'number'], isPositiveNumber: true, decimalPlaces: 2, maxValue: 2147483647, });" clearable > </el-input >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 function inputNumberLimit (rule ) { rule = Object .assign ({ target : [{}, '' ], isPositiveNumber : false , isInteger : false , digit : -1 , decimalPlaces : -1 , isNotEmpty : false , }, rule); if (rule.isPositiveNumber ) { if (rule.isInteger ) rule.target [0 ][rule.target [1 ]] = String (rule.target [0 ][rule.target [1 ]]).replace (/\D/g , '' ); else rule.target [0 ][rule.target [1 ]] = String (rule.target [0 ][rule.target [1 ]]).replace (/[^\d|^\.]/g , '' ); } else { if (rule.isInteger ) rule.target [0 ][rule.target [1 ]] = String (rule.target [0 ][rule.target [1 ]]).replace (/[^-|\d]/g , '' ); else rule.target [0 ][rule.target [1 ]] = String (rule.target [0 ][rule.target [1 ]]).replace (/[^-|\d|^\.]/g , '' ); } if (rule.isNotEmpty ) rule.target [0 ][rule.target [1 ]] = String (rule.target [0 ][rule.target [1 ]]) .replace (/^(-|--)$/ , '0' ); rule.target [0 ][rule.target [1 ]] = String (rule.target [0 ][rule.target [1 ]]) .replace (/^(0-)$/ , '-0' ) .replace (/(.)-+/g , '$1' ) .replace (/^(\.)(.*)$/ , '0.$2' ) .replace (/^(-\.)(.*)$/ , '-0.$2' ) .replace (/^(0)(-|\d+).*$/ , '$2' ) .replace (/^(-0)(\d+).*$/ , '-$2' ); if (rule.digit !== -1 ) { let numberSplitArray = String (rule.target [0 ][rule.target [1 ]]).split ('.' ); if (numberSplitArray.length === 2 ) { numberSplitArray[0 ] = numberSplitArray[0 ].replace (new RegExp (`^(-|\\+)?(\\d{${rule.digit} }).*$` ), '$1$2' ); rule.target [0 ][rule.target [1 ]] = numberSplitArray[0 ] + (numberSplitArray[1 ] ? ('.' + numberSplitArray[1 ]) : '.' ); } else { rule.target [0 ][rule.target [1 ]] = numberSplitArray[0 ].replace (new RegExp (`^(-|\\+)?(\\d{${rule.digit} }).*$` ), '$1$2' ); } } if (rule.decimalPlaces !== -1 ) rule.target [0 ][rule.target [1 ]] = String (rule.target [0 ][rule.target [1 ]]).replace (new RegExp (`^(-|\\+)?(\\d+)\\.(\\d{${rule.decimalPlaces} }).*$` ), '$1$2.$3' ); if (rule.maxValue != null && (rule.target [0 ][rule.target [1 ]] > rule.maxValue )) rule.target [0 ][rule.target [1 ]] = String (rule.maxValue ); if (rule.minValue != null && (rule.target [0 ][rule.target [1 ]] < rule.minValue )) rule.target [0 ][rule.target [1 ]] = String (rule.minValue ); if (String (rule.target [0 ][rule.target [1 ]]).match (/\./g ) && String (rule.target [0 ][rule.target [1 ]]).match (/\./g ).length > 1 ) rule.target [0 ][rule.target [1 ]] = '' ; if (rule.isNotEmpty && (rule.target [0 ][rule.target [1 ]] == null || rule.target [0 ][rule.target [1 ]] === '' )) rule.target [0 ][rule.target [1 ]] = '0' ; }
数字四舍五入
四舍五入为什么要封装一个方法?因为常用的 toFixed()
和 Math.round()
并没有达到预期
toFixed()
把数字转换为字符串,结果的小数点后有指定位数的数字(进行四舍六入五考虑,五后非零则进一,五后皆零看奇偶,五前偶舍奇进一)
经测试发现,并没有完全遵守银行家舍入规则,可能存在兼容性问题
所以如果需要四舍五入保留两位小数可以这样写:(Math.round(1.555 * 100) / 100).toFixed(2) // 1.56
Math.round()
处理负数的问题:Math.round(-1.5); // -1
解决方法(如果是负数的话就转成正数四舍五入后再转回来):Math.round(-1.5 * -1) * -1; // -2
所以才封装了这么一个方法,已解决以上问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function numberRound (number, decimalPlaces = 0 , isFillZero = true ) { let flag = Number (number) < 0 ? -1 : 1 ; let operand = '1' ; for (let i = 0 ; i < decimalPlaces; i++) operand = operand + '0' ; let result = Math .round (number * flag * operand) / operand * flag; if (isFillZero) return result.toFixed (decimalPlaces); return String (result); }
所以看到这,是不是觉得自己已经写过不少 toFixed()
和 Math.round()
感到麻了。。
数字的处理还是要精确的好,一定要重视哦,特别是关于统计重要的数额。
小数计算精度问题
小数之间的计算也有精度问题,比如 0.1 + 0.2 = 0.30000000000000004
这个不是 JavaScript
独有的问题,Java C++ C# Python
都有这个问题,都是采用的 IEEE 754 (二进制浮点数算术标准)
原因:因为计算机只认识二进制,所以需要把数字转成二进制计算再转成十进制,这个过程中出现了无限循环,所以造成超出部分被截取,具体计算过程网上有大量说明,咱就不陈述了
推荐食用 decimal.js 能精度计算,或者自行保留几位小数也行。
Java的话 new BigDecimal("0.1").add(BigDecimal.valueOf(0.2))
这样就行
数位分级格式化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function digitGradeFormat (digit, intervalNum = 3 ) { let digitSplitArr = String (digit).split ('.' ); let count = Math .floor (digitSplitArr[0 ].length / intervalNum); for (let i = 0 ; i < count; i++) { if (digitSplitArr[0 ].length > intervalNum) { let commaIndex = digitSplitArr[0 ].indexOf (',' ), newCommaIndex; if (commaIndex !== -1 ) newCommaIndex = commaIndex - intervalNum; else newCommaIndex = digitSplitArr[0 ].length - intervalNum; if (newCommaIndex !== 0 && digitSplitArr[0 ][newCommaIndex - 1 ] !== '-' ) { digitSplitArr[0 ] = digitSplitArr[0 ].split ('' ); digitSplitArr[0 ].splice (newCommaIndex, 0 , ',' ); digitSplitArr[0 ] = digitSplitArr[0 ].join ('' ); } if (digitSplitArr.length === 1 ) digit = digitSplitArr[0 ]; else digit = digitSplitArr[0 ] + '.' + digitSplitArr[1 ]; } } return digit; }
Url参数格式化
不够用的话,推荐一个更好的依赖:https://github.com/ljharb/qs
1 2 3 4 5 6 7 8 9 10 11 12 function urlParamsFormat (params ) { let urlParams = '' ; for (const key of Object .keys (params)) { urlParams = urlParams + key + '=' + (typeof params[key] === 'string' ? encodeURIComponent (params[key]) : encodeURIComponent (JSON .stringify (params[key]))) + '&' ; } return urlParams.replace (/&$/ , '' ); }
深拷贝对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function deepCloneObject (objectOrArray ) { let newObjectOrArray = {}; if (objectOrArray === null ) newObjectOrArray = null ; else if (objectOrArray === undefined ) newObjectOrArray = undefined ; else if (objectOrArray.constructor === Array ) newObjectOrArray = []; else if (objectOrArray.constructor !== Object ) return objectOrArray; for (let key in objectOrArray) { let value = objectOrArray[key]; newObjectOrArray[key] = typeof value === 'object' ? deepCloneObject (value) : value; } return newObjectOrArray; }
什么是浅拷贝和深拷贝?
浅拷贝,深度的属性还是会指向原来的对象
1 2 newObj = Object .assign ({}, obj); newObj = {...obj};
深拷贝,完完全全的新对象
newObj = JSON.parse(JSON.stringify(obj)); // 不推荐,有的类型解析不到
如果json里面有时间对象,则序列化结果:时间对象=>字符串的形式
如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {}
如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失
如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null
如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor
如果对象中存在循环引用的情况也无法实现深拷贝
以上,如果拷贝的对象不涉及上面的情况,可以使用 JSON.parse(JSON.stringify(obj)); 实现深拷贝。否则就用这个函数吧
获取通用唯一识别码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function getUUID ( ) { let d = new Date ().getTime (); if (window && window .performance && typeof window .performance .now === "function" ) { d += performance.now (); } let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' .replace (/[xy]/g , function (c ) { let r = (d + Math .random () * 16 ) % 16 | 0 ; d = Math .floor (d / 16 ); return (c == 'x' ? r : (r & 0x3 | 0x8 )).toString (16 ); }); return uuid; }
获取中国区域级联选项
依赖:https://github.com/modood/Administrative-divisions-of-China
中华人民共和国行政区划(五级):省级、地级、县级、乡级和村级。
数据来源民政部、国家统计局:https://www.mca.gov.cn/article/sj/xzqh/1980/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import pcasCode from 'china-division/dist/pcas-code.json' ;function getChinaAreaCascadeOptions (level = 4 , labelField = 'name' , valueField = 'name' ) { if (level < 1 ) return []; return (function recursion (data, currentLevel ) { let options = []; for (const item of data) { let option = { label : item[labelField], value : item[valueField], } if (item?.children ?.length && currentLevel < level) { option.children = recursion (item.children , currentLevel + 1 ); } options.push (option); } return options; })(pcasCode, 1 ); }
枚举的用法
1 2 3 4 5 6 7 8 9 10 11 12 let testEnum = { enum : [ {key : '测试键0' , value : '测试值0' }, {key : '测试键1' , value : '测试值1' }, {key : '测试键2' , value : '测试值2' }, ], get : (key, value ) => testEnum.enum .find (i => i[key] === value) ?? {}, } console .log ( testEnum.get ('key' , '测试键1' ).value );
平铺的对象数组递归为多级结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 let data = [ { "id" : "001" , "parentId" : null , }, { "id" : "002" , "parentId" : null , }, { "id" : "003" , "parentId" : "001" , }, { "id" : "004" , "parentId" : "003" , }, { "id" : "005" , "parentId" : "003" , }, ]; let newData = [];data.forEach ((item, index, array ) => { if (!item.parentId ) { newData.push ((function recursion (item ) { let children = array.filter (i => i.parentId === item.id ) || []; children.forEach (_item => recursion (_item)); item.children = children; return item; })(item)); } });
评估
满足不了?推荐一个依赖:https://github.com/bplok20010/eval5 ,支持浏览器、node.js、小程序等 JavaScript 运行环境
1 2 3 4 5 6 7 8 9 function evaluate (expression ) { if (typeof (expression) === 'string' ) return Function ('"use strict"; return (' + expression + ')' )(); return ; }
下载Blob响应
当通过 AJAX
请求带有 Content-Disposition 响应头的 Blob
时,是不会触发浏览器自动下载附件的功能的,需要自行创建可下载链接,然后通过 iconv-lite 对响应中 filename
键的 UTF-8
值进行解码得到文件名后下载。具体实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import iconv from "iconv-lite" ;import { ElMessage } from 'element-plus' ;function downloadBlobResponse (res ) { let link = document .createElement ('a' ); link.href = URL .createObjectURL (res.data ); link.target = '_self' ; link.download = iconv.decode (res.headers ['Content-Disposition' ]?.replace (/.*filename=(.*)/ , '$1' ) || res.headers ['content-disposition' ]?.replace (/.*filename=(.*)/ , '$1' ) || '' , 'UTF-8' ); if (link.download ) { link.click (); return ; } else if (res.data .type === 'application/json' ) { res.data .text ().then (res => { let resObj = JSON .parse (res); ElMessage .error ({message : resObj.desc || resObj.msg || resObj.message || '服务器内部错误' , showClose : true }); }); } else { ElMessage .error ({message : '服务器内部错误' , showClose : true }); } throw '' ; }
下载重命名文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function downloadRenameFile (url, fileRename, isKeepFileType = true ) { let fileType = '' ; if (isKeepFileType) { let fileNameSplitArr = new URL (url).pathname .split ('.' ); if (fileNameSplitArr.length > 1 ) { fileType = fileNameSplitArr[fileNameSplitArr.length - 1 ]; } } let _url = url.replace ('https://xxx-file-123456789.cos.ap-beijing.myqcloud.com' , '/os-xxx-file' ); return new Promise ((resolve, reject ) => { new Promise ((resolve, reject ) => { let xhr = new XMLHttpRequest (); xhr.open ('get' , _url, true ); xhr.responseType = 'blob' ; xhr.onload = e => { if (xhr.status >= 200 && xhr.status < 300 ) { resolve (xhr.response ); } else { reject (); } } xhr.send (); }).then (res => { let link = document .createElement ('a' ); link.href = URL .createObjectURL (res); link.target = '_self' ; link.download = fileRename + (fileType ? ('.' + fileType) : '' ); link.click (); resolve (); }).catch (err => { reject (err); }); }); }
上传文件到腾讯云对象存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 function uploadFileToOs (file ) { return new Promise (async (resolve, reject) => { try { await file.arrayBuffer (); } catch (e) { if (e.message === 'The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.' ) { Message .error ({message : '文件已被更改,请重新选择文件' , showClose : true }); } else { Message .error ({message : '文件读取失败' , showClose : true }); } return reject (); } let fileMD5 = '' ; await new Promise ((resolve, reject ) => { bmf.md5 (file, (err, md5 ) => { if (err) reject (); fileMD5 = md5; resolve (); }); }).catch (err => { Message .error ('获取文件md5失败' ); reject (); }); await getUploadOsAuth ({ param : { fileName : file.name || '' , md5 : fileMD5, } }).then (res => { if (res && res.data ) { let authorization = res.data ; let uploadUrl = authorization.url ; if (process.env .NODE_ENV === 'development' ) uploadUrl = uploadUrl.replace ('https://XXX-file-123456789.cos.ap-beijing.myqcloud.com' , '/os-XXX-file' ); new Promise ((resolve, reject ) => { let xhr = new XMLHttpRequest (); xhr.open ('put' , uploadUrl, true ); xhr.setRequestHeader ('Content-Type' , file.type ); xhr.setRequestHeader ('Authorization' , authorization.additionalHeaders .Authorization ); xhr.onload = e => { if (xhr.status >= 200 && xhr.status < 300 ) { resolve (xhr.response ); } else { reject (); } } xhr.send (file); }).then (res => { resolve (authorization.url ); }).catch (err => { Message .error ('上传失败' ); reject (err); }); } else { Message .error ('上传授权失败' ); reject (); } }).catch (err => { reject (err); }); }); }
CookieUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class CookieUtils { static get (sKey ) { return decodeURIComponent (document .cookie .replace (new RegExp ("(?:(?:^|.*;)\\s*" + encodeURIComponent (sKey).replace (/[-.+*]/g , "\\$&" ) + "\\s*\\=\\s*([^;]*).*$)|^.*$" ), "$1" )) || null ; } static set (sKey, sValue, vEnd, sPath, sDomain, bSecure ) { if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i .test (sKey)) { return false ; } let sExpires = "" ; if (vEnd) { switch (vEnd.constructor ) { case Number : sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd; break ; case String : sExpires = "; expires=" + vEnd; break ; case Date : sExpires = "; expires=" + vEnd.toUTCString (); break ; } } document .cookie = encodeURIComponent (sKey) + "=" + encodeURIComponent (sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "" ) + (sPath ? "; path=" + sPath : "" ) + (bSecure ? "; secure" : "" ); return true ; } static remove (sKey, sPath, sDomain ) { if (!sKey || !this .has (sKey)) { return false ; } document .cookie = encodeURIComponent (sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "" ) + (sPath ? "; path=" + sPath : "" ); return true ; } static has (sKey ) { return (new RegExp ("(?:^|;\\s*)" + encodeURIComponent (sKey).replace (/[-.+*]/g , "\\$&" ) + "\\s*\\=" )).test (document .cookie ); } static keys ( ) { let aKeys = document .cookie .replace (new RegExp ('((?:^|\\s*;)[^\\=]*)(?:;|$)|^\\s*|\\s*(?:\\=[^;]*)?(?:\\1|$)' , 'g' ), "" ).split (/\s*(?:\=[^;]*)?;\s*/ ); for (let nIdx = 0 ; nIdx < aKeys.length ; nIdx++) { aKeys[nIdx] = decodeURIComponent (aKeys[nIdx]); } return aKeys; } }