一些常用的工具函数
1. 下载文件到本地
1 function downLoadFile(data,fileName){ 2 if (!data) { 3 return 4 } 5 var blob = new Blob([data]); // <!document><head><meta charset="utf-8"></head><body><h1>测试</h1></body> 6 if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE 7 window.navigator.msSaveOrOpenBlob(blob,fileName); 8 }else{ 9 let url = window.URL.createObjectURL(blob); 10 let link = document.createElement('a'); 11 link.style.display = 'none'; 12 link.href = url; 13 link.setAttribute('download', fileName); 14 15 document.body.appendChild(link); 16 link.click(); 17 } 18 }
2. 复制内容到剪贴板:
// 复制内容到剪贴板 function copyToClip(text, callback) { if(document.execCommand('Copy')){ //创建input var inputZ = document.createElement('input'); //添加Id,用于后续操作 inputZ.setAttribute('id','inputCopy'); //获取当前链接 inputZ.value = text; //创建的input添加到body document.body.appendChild(inputZ); //选中input中的值 document.getElementById('inputCopy').select(); //把值复制下来 document.execCommand('Copy') //删除添加的input document.body.removeChild(inputZ); // 成功回調1 typeof callback === 'function' && callback(1); }else{ // 失敗回調2 typeof callback === 'function' && callback(2); } }
3. 全屏和退出全屏
// 全屏 function fullscreen(elem) { var docElm = elem || document.documentElement; if (docElm.requestFullscreen) { docElm.requestFullscreen(); } else if (docElm.mozRequestFullScreen) { docElm.mozRequestFullScreen(); } else if (docElm.webkitRequestFullScreen) { docElm.webkitRequestFullScreen(); } else if (docElm.msRequestFullscreen) { docElm.msRequestFullscreen(); } } // 退出全屏 function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } }
4. 获取设备信息
// from: vue.js function getPlatForm () { var inBrowser = typeof window !== 'undefined'; var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); var UA = inBrowser && window.navigator.userAgent.toLowerCase(); var isIE = UA && /msie|trident/.test(UA); var isIE9 = UA && UA.indexOf('msie 9.0') > 0; var isEdge = UA && UA.indexOf('edge/') > 0; var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; var isPhantomJS = UA && /phantomjs/.test(UA); var isFF = UA && UA.match(/firefox\/(\d+)/); return { inBrowser, inWeex, weexPlatform, UA, isIE, isIE9, isEdge, isAndroid, isIOS, isChrome, isPhantomJS, isFF } }
5. 日期格式化
function dateFormat(data, fmt) { !fmt && (fmt = 'yyyy/MM/dd hh:mm:ss') if (typeof data === 'number') { data = data.toString().substr(0, 13) } if (typeof data === 'string') { data = new Date(parseInt(data)) } let o = { 'M+': data.getMonth() + 1, // 月份 'd+': data.getDate(), // 日 'h+': data.getHours(), // 小时 'm+': data.getMinutes(), // 分 's+': data.getSeconds(), // 秒 'q+': Math.floor((data.getMonth() + 3) / 3), // 季度 S: data.getMilliseconds() // 毫秒 } if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (data.getFullYear() + '').substr(4 - RegExp.$1.length)) } for (let 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 }
6. 字符串格式化
function compile(template){ var evalExpr = /<%=(.+?)%>/g; var expr = /<%([\s\S]+?)%>/g; template = template .replace(evalExpr, '`); \n echo( $1 );\n echo(`') .replace(expr, '`);\n $1 \n echo(`'); template = 'echo(`' + template + '`);'; var script = '\n' + 'var output = "";\n' + 'function echo(html){ \n' + 'output += html; \n' + '};\n' + template + '\n' + 'return output; \n'; return new Function('data', script); }
7. 获取周/根据周获取日期范围
// 获取周: 一个月最多可以跨 6周, 31天, 首尾恰好位于起始时间 // from: https://stackoverflow.com/questions/9045868/javascript-date-getweek function getWeek(timeStr) { var _this = new Date(timeStr); // We have to compare against the first monday of the year not the 01/01 // 60*60*24*1000 = 86400000 // 'onejan_next_monday_time' reffers to the miliseconds of the next monday after 01/01 var day_miliseconds = 86400000, onejan = new Date(_this.getFullYear(), 0, 1, 0, 0, 0), onejan_day = (onejan.getDay() == 0) ? 7 : onejan.getDay(), days_for_next_monday = (8 - onejan_day), onejan_next_monday_time = onejan.getTime() + (days_for_next_monday * day_miliseconds), // If one jan is not a monday, get the first monday of the year first_monday_year_time = (onejan_day > 1) ? onejan_next_monday_time : onejan.getTime(), this_date = new Date(_this.getFullYear(), _this.getMonth(), _this.getDate(), 0, 0, 0), // This at 00:00:00 this_time = this_date.getTime(), days_from_first_monday = Math.round(((this_time - first_monday_year_time) / day_miliseconds)); var first_monday_year = new Date(first_monday_year_time); // We add 1 to "days_from_first_monday" because if "days_from_first_monday" is *7, // then 7/7 = 1, and as we are 7 days from first monday, // we should be in week number 2 instead of week number 1 (7/7=1) // We consider week number as 52 when "days_from_first_monday" is lower than 0, // that means the actual week started before the first monday so that means we are on the firsts // days of the year (ex: we are on Friday 01/01, then "days_from_first_monday"=-3, // so friday 01/01 is part of week number 52 from past year) // "days_from_first_monday<=364" because (364+1)/7 == 52, if we are on day 365, then (365+1)/7 >= 52 (Math.ceil(366/7)=53) and thats wrong return (days_from_first_monday >= 0 && days_from_first_monday < 364) ? Math.ceil((days_from_first_monday + 1) / 7) : 52; } // 对 week 的一步反运算, 计算情况: 2019,3: 2019年第3周, 对应是哪天到哪天; function getWeekRange(year, week, tpl) { var day_miliseconds = 86400000, onejan = new Date(year, 0, 1, 0, 0, 0), onejan_day = (onejan.getDay() == 0) ? 7 : onejan.getDay(), days_for_next_monday = (8 - onejan_day), onejan_next_monday_time = onejan.getTime() + (days_for_next_monday * day_miliseconds), first_monday_year_time = (onejan_day > 1) ? onejan_next_monday_time : onejan.getTime(), target_week_monday_time = first_monday_year_time + day_miliseconds * 7 * (week - 1), target_week_sunday_time = target_week_monday_time + day_miliseconds * 6 - 1000; return [formatDate(target_week_monday_time, tpl || 'yyyy-MM-dd'), formatDate(target_week_sunday_time, tpl || 'yyyy-MM-dd')] }
8. 深拷贝
// 深拷贝 function deepCopy(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = deepCopy(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]); } return copy; } if (isPrimitiveValue(obj)) { copy = obj; return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }
9. 是否相同对象
function deepEqual(x, y) { var core_toString = Object.prototype.toString var ta = core_toString.call(x) var tb = core_toString.call(y) if (x === y) { return true } if (ta !== tb) { return false } if (!(typeof x == 'object' && x != null) || !(typeof y == 'object' && y != null)) { return false } if (Object.keys(x).length != Object.keys(y).length) { return false } for (var prop in x) { if (y.hasOwnProperty(prop)) { if (!deepEqual(x[prop], y[prop])) { return false } } else { return false } } return ta === '[object Date]' ? x.valueOf() == y.valueOf() : true }
10. 找出两个对象的差异字段
function diff(lobj, robj, change, path, key) { var ltype = type(lobj), rtype = type(robj); var ldefined = ltype !== 'undefined' var rdefined = rtype !== 'undefined' path = path || [] var currentPath = path.slice(0) if (typeof key !== 'undefined' && key !== null) { currentPath.push(key) } if (!ldefined && rdefined) { change.push({ type: 'add', path: currentPath, rhs: robj }) } else if (ldefined && !rdefined) { change.push({ type: 'delete', path: currentPath, lhs: lobj }) } else if (ltype !== rtype) { change.push({ type: 'edit', path: currentPath, lhs: lobj, rhs:robj }) } else { if (isPrimaty(lobj)) { if (lobj !== robj) { change.push({ type: 'edit', path: currentPath, lhs: lobj, rhs: robj }) } } else if (ltype === 'object') { var aKeys = Object.keys(lobj) var pKeys = Object.keys(robj) var ckeys = unique(aKeys.concat(pKeys)) var p; for (var i=0; i<ckeys.length; i++) { diff(lobj[ckeys[i]], robj[ckeys[i]], change, currentPath, ckeys[i]) } } else if (ltype === 'array') { var maxLen = Math.max.apply(null, [lobj.length, robj.length]); for(var i=0; i<maxLen; i++) { diff(lobj[i], robj[i], change, currentPath, i) } // Date, ExpExc 等 } else if (ltype === 'date'){ if (lobj.valueOf() !== robj.valueOf()) { change.push({ type: 'edit', path: currentPath, lhs: lobj, rhs: robj }) } } } } 辅助方法: // 简单实现 function unique (arr) { var res = []; for(var i = 0; i < arr.length; i++) { if (res.indexOf(arr[i]) === -1) { res.push(arr[i]) } } return res } function isPrimaty (value) { return ( typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'undefined' || typeof value === 'symbol' || typeof value === 'bigint' || (typeof value === 'object' && value === null) ) } function type(input) { return ({}).toString.call(input).slice(8, -1).toLowerCase(); } 示例: var obj1 = { a: 123, b: { b1: 12, b2: 22, b3: '123' }, c: [1, 4], d: { c1: 4 }, e: null, f: new Date() } var obj2 = { a: 123, b: { b1: 12, b2: 33 }, c: [4, 1], d: { c1: 4 }, f: new Date('2020-12-30') } var res = [] diff(obj1, obj2, res); res => [ { "type":"edit", "path":["b","b2"], "lhs":22, "rhs":33 },{ "type":"delete", "path":["b","b3"], "lhs":"123" },{ "type":"delete", "path":["e"], "lhs":null },{ "type":"edit", "path":["f"], "lhs":"2020-07-02T08:28:00.640Z", "rhs":"2020-12-30T00:00:00.000Z" } ]
11. html 转码、解码
var parseHtml = { encode: function(str) { var reg = /([&<>"'])/g; var codeMap = { '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' }; return str == undefined ? '' : (str + '').replace(reg, function(s, c) { return codeMap[c]; }) }, decode: function(str) { var reg = /(&|<|>|"|')/g; var codeMap = { '&': '&', '<': '<', '>': '>', '"': '"', ''': '\'' }; return str == undefined ? '' : (str + '').replace(reg, function(s, c) { return codeMap[c]; }) } }
12. 判断给定时间是否处于时间区间内
// 判断时间是否处于时间区间内; --兼容 UTC时区; function isTimeInRange(timeStamp, t1, t2, isNight, utcOffset) { var date = getTime(timeStamp) var minuteMS = 60000; var hourMS = 3600000; var _timeStamp = date.getTime() - utcOffset * minuteMS; var hour = _timeStamp % (24 * hourMS) / hourMS ^ 0; var minute = _timeStamp % hourMS / minuteMS ^ 0; var time1 = t1.split(/[:/-]/) var time2 = t2.split(/[:/-]/) if (isNight) { return compare([0, 0], [hour, minute], '<') && compare(time1, [hour, minute], '>') || compare(time2, [hour, minute], '<') && compare(['23', '59'], [hour, minute], '>') } else { return compare(time1, [hour, minute], '<') && compare(time2, [hour, minute], '>') } } function compare(arr1, arr2, lt) { if (lt === '>') { if (arr1[0] > arr2[0]) { return true } else if (arr1[0] == arr2[0]){ return arr1[1] >= arr2[1] } else { return false } } else if (lt === '<') { if (arr1[0] < arr2[0]) { return true } else if (arr1[0] == arr2[0]){ return arr1[1] <= arr2[1] } else { return false } } } function getTime(v) { if (Number.isInteger(v)) { if (v.length < 10) { return new Date(v * 1000) } else { return new Date(v) } } else { if (Date.parse(v)) { return new Date(v) } else { return new Date(0) } } } var arr = ["2020-12-31 08:21:11", "2020-12-31 14:22:41", "2020-12-30 23:57:33", "2021-01-01 00:14:03"]; var arr2 = arr.map(v => new Date(v).getTime()); var startTime = '12:12'; var endTime = '15:01'; var utcOffset = 0; var res = arr.map(v => isTimeInRange(v, startTime, endTime, false, utcOffset)); var res2 = arr2.map(v => isTimeInRange(v, startTime, endTime, true, utcOffset)); console.log('结果:::', res); console.log('结果:::', res2);
13. 节流和防抖
function throller(delay, callback) { var time = Date.now() function exec() { var now = Date.now(); var args = arguments var context = this; if (now - time > delay) { callback.apply(context, args) time = now } } return exec } function debounce(delay, callback, isBegin) { var time = Date.now(); var timeoutId, stop function exec() { var dis = Date.now() - time var context = this; var args = arguments; if (isBegin) { if (dis > delay && !stop) { callback.apply(context, args) stop = true } } else { if (timeoutId) { clearTimeout(timeoutId) } timeoutId = setTimeout(function() { callback.apply(context, args) }, delay) } } return exec } function conError1(...args) { console.log('conError1, 参数与上下文', this, args) } function conError2(...args) { console.log('conError2', this, args) } // 300 毫秒, 不重复执行, 默认执行最后一个 var throlFn = throller(400, conError1) var debounceFn = debounce(600, conError2, false) var now = Date.now() var tid function start(fn, context, ...args) { tid = setTimeout(function() { var ts = Date.now(); if (ts - now > 2000) { clearTimeout(tid) } else { fn.apply(context, args) start(fn, context, ...args) } }, 100) } start(throlFn, {a: 123}, 'a', '123') start(debounceFn, {b: 234}, 'b', '234')
14. 异步校验
async checkValidateState() { var allComps = this.workTypeDic[this.workType] var i = 0, len = allComps.length, cur, validateStates = [] for (; i < len; i++) { cur = this.$refs[allComps[i]][0] if (cur.validate) { validateStates.push(await cur.validate()) } } return Promise.all(validateStates) }, var validate = await this.checkValidateState() if ( validate.some(function(v) { return !v }) ) { // 存在校验未通过的组件 return } validate() { return new Promise((resolve, reject) => { this.$refs.form.validate(res => { resolve(res) }) }) }
15. 获取对象值路径对应的值
function getValueByPath(object, prop) { prop = prop || ''; const paths = prop.split('.'); let current = object; let result = null; for (let i = 0, j = paths.length; i < j; i++) { const path = paths[i]; if (!current) break; if (i === j - 1) { result = current[path]; break; } current = current[path]; } return result; };
16. 获取日历-某年某月的所有天数( 可选择是否填充临近月 )
function getCalendarDays(cyear, cmonth, fillPreAndNext, weekOffset) { var year = cyear, fill = fillPreAndNext, // 是否填充临近月天数 month = cmonth - 1, weekStart = weekOffset || 0, // 起始天, 默认周日:0 list = [], theMonthFirst = new Date(year, month, 1), // theMonthFirstDay = theMonthFirst.getDate(), theMonthLast = new Date(year, month + 1, 0), theMonthLastDay = theMonthLast.getDate(); var theMonthFirstWeekday = theMonthFirst.getDay(), theMonthLastWeekDay = theMonthLast.getDay(), beforeLen = theMonthFirstWeekday - weekStart < 0 ? 7 + (theMonthFirstWeekday - weekStart) : theMonthFirstWeekday - weekStart, afterLen = (6 - (theMonthLastWeekDay - weekStart)) % 7; // 上月 if (fill) { for (; beforeLen > 0; beforeLen--) { list.push({ disabled: true, type: "pre", value: new Date(theMonthFirst).setDate(1 - beforeLen) }); } } else { for (; beforeLen > 0; beforeLen--) { list.push({ disabled: true, type: "pre", value: null }); } } // 当前月 for (var i = 1; i <= theMonthLastDay; i++) { list.push({ disabled: false, type: "now", value: new Date(theMonthFirst).setDate(1 + (i - 1)) }); } // 下月 if (fill) { for (i = 0; i < afterLen; i++) { list.push({ disabled: true, type: "next", value: new Date(theMonthFirst).setDate(theMonthLastDay + i + 1) }); } } else { for (i = 0; i < afterLen; i++) { list.push({ disabled: true, type: "next", value: null }); } } var res = [], time = Math.ceil(list.length / 7), cur; for (i = 0; i < time; i++) { cur = []; for (var k = 0; k < 7; k++) { if (list.length) { cur.push(list.shift()); } } res.push(cur); } return res; }
17. 以队列的形式执行批量任务
export class Quene { constructor(limit, async, nextWaiting, attachBatch, attachEnd) { this.quene = []; this.count = 0; this.allCount = 0; this.limit = limit || Infinity; this.waiting = nextWaiting || 0; this.async = async || false; this.result = []; this.attachEnd = attachEnd || false this.attachBatch = attachBatch || false } enquene(tasks) { if (Array.isArray(tasks)) { tasks.forEach((v) => { typeof v === "function" && this.quene.push(v); }); this.allCount += tasks.length } else if (typeof tasks === "function") { this.quene.push(tasks); this.allCount++ } } dequene() { return this.quene.shift(); } asyncInvoke(fn) { return new Promise((resolve, reject) => { const _this = this; const transfer = async function() { try { const res = await fn(_this.result); _this.result.push(res); } catch (e) { console.error(`函数 ${fn} 执行错误`, e); } resolve(); }; transfer(); }); } onBatchEnd() { console.error('quene onBatchEnd not implemented') } onAllEnd() { console.error('quene onAllEnd not implemented') } run() { if (this.async) { for (let i = 0; i < this.limit; i++) { const task = this.dequene(); if (!task) { this.result.length = 0; return; } if (this.allCount === 0) { return } this.asyncInvoke(task).then(() => { this.count++; this.allCount--; if (this.allCount === 0) { if (this.count) { this.attachBatch && this.onBatchEnd(); } this.attachEnd && this.onAllEnd(); return } if (this.count === this.limit) { this.attachBatch && this.onBatchEnd(); setTimeout(() => { this.count = 0; this.result = []; this.run(); }, this.waiting); } }); } } else { const task = this.dequene(); if (!task) { this.result.length = 0; return; } if (this.allCount === 0) { return } this.asyncInvoke(task).then(() => { this.count++; this.allCount--; if (this.allCount === 0) { if (this.count) { this.attachBatch && this.onBatchEnd(); } this.attachEnd && this.onAllEnd(); return } if (this.count === this.limit) { this.attachBatch && this.onBatchEnd(); setTimeout(() => { this.run(); this.count = 0; this.result = []; }, this.waiting); } else { this.run(); } }); } } }
18. 获取 url 查询参
function getUrlParams(url, key) { var urlStr = url.split("?")[1]; var obj = {}; if (!urlStr) { return key ? obj[key] : obj; } var paramsArr = urlStr.split("&"); for (var i = 0, len = paramsArr.length; i < len; i++) { var arr = paramsArr[i].split("="); obj[arr[0]] = decodeURIComponent(arr[1]); } return key ? obj[key] : obj; }
19. 任务轮询
export class Task { constructor(interval, fn, mode, params, maxInvokeTimes) { this.timer = []; this.callCount = 0; this.mode = mode; this.maxCall = mode === "animation" ? Infinity : maxInvokeTimes || Infinity; this.dynamicControl = { callback: fn, params: params, interval: interval || 1000, stop: false, }; } start(immediate) { const task = () => { if (this.callCount >= this.maxCall || this.dynamicControl.stop) { return; } this.callCount++; try { this.dynamicControl.callback(this.dynamicControl.params); } catch (e) { console.error("收集到错误", e); } if (this.timer.length) { this.stop(); this.timer.length = 0; } this.timer.push( this.mode === "animation" ? requestAnimationFrame(task) : setTimeout(task, this.dynamicControl.interval) ); }; if (immediate) { task(); } else { this.timer.push( this.mode === "animation" ? requestAnimationFrame(task) : setTimeout(task, this.dynamicControl.interval) ); } } stop() { if (this.timer && this.timer.length) { if (this.mode === "animation") { this.timer.forEach((timerId) => { cancelAnimationFrame(timerId); }); } else { this.timer.forEach((timerId) => { clearTimeout(timerId); }); } } } clear() { this.stop(); this.timer = null; this.callCount = null; this.maxCall = null; this.dynamicControl = null; } }
20. 16进制颜色值转 RGB
function hexToRgb(hexColor) { // 移除十六进制颜色值中的 '#' 符号 hexColor = hexColor.replace(/^#/, ""); // 将十六进制颜色值转换为RGB var bigint = parseInt(hexColor, 16); var r = (bigint >> 16) & 255; var g = (bigint >> 8) & 255; var b = bigint & 255; return [r, g, b]; }
21. 获取字符串所占字节长度
function getCharsByteLength(str) { if (typeof TextEncoder === 'function') { const encoder = new TextEncoder('utf-8'); const bytes = encoder.encode(str); return bytes.length; } else { const encoder = new TextEncoderPolyfill(); const encodedBytes = encoder.encode(str); // 获取字节长度 return encodedBytes.length; } } /** * 一个简单的 TextEncoder 的 polyfill; 以下为 utf-8 编码获取字节数的简易实现 * 0000-0000 007F 0xxxxxxx 0080-0000 07FF 110xxxxx 10xxxxxx 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ class TextEncoderPolyfill { encode(str) { const codePoints = Array.from(str).map(char => char.codePointAt(0)); const bytes = []; for (const codePoint of codePoints) { if (codePoint < 0x80) { bytes.push(codePoint); } else if (codePoint < 0x800) { bytes.push(0xC0 | (codePoint >> 6), 0x80 | (codePoint & 0x3F)); } else if (codePoint < 0x10000) { bytes.push(0xE0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 0x3F), 0x80 | (codePoint & 0x3F)); } else { bytes.push(0xF0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 0x3F), 0x80 | ((codePoint >> 6) & 0x3F), 0x80 | (codePoint & 0x3F)); } } return new Uint8Array(bytes); } }
22. 数值口语化
function formatNumberToLocale(number, suffix = '') { if (emptyValus.includes(number)) { return '' } const formatter = new Intl.NumberFormat('zh-CN', { style: 'decimal', maximumFractionDigits: 4, minimumFractionDigits: 0, notation: 'compact', compactDisplay: 'short' }) const valStr = formatter.format(+number) return valStr + suffix }
23. 文件大小格式化
/** * 计算数值 a 以 b为底的对数 */ function getLog(a, b) { // 使用 Math.log() 计算以 e 为底的对数 var naturalLog = Math.log(a); // 使用 Math.log() 计算以 b 为底的对数,并除以以 e 为底的对数 var baseLog = naturalLog / Math.log(b); // 返回结果 return baseLog; } /** * 转换文件大小 */ function getFileSie(v) { if (["", undefined, null].includes(v)) { return ""; } const size = Number(v); if (!Number.isFinite(size)) { return ""; } if (size === 0) { return 0 + "B"; } const units = { 0: "B", 1: "K", 2: "M", 3: "G", 4: "T", 5: "P" }; const log1024 = getLog(size, 1024); const unit = Math.floor(log1024); const _size = size / Math.pow(1024, Math.floor(log1024)); return (Number.isInteger(_size) ? _size : _size.toFixed(2)) + units[unit]; }
24. 遍历树常用方法
/** * 在树结构中寻找匹配的节点 * @param treeNodes: Object | Array, 要遍历的节点 * @param matchRule: Function, 匹配的规则函数 * @param childrenKey: String, 子节点的 key */ function findNodeInTree(treeNodes, matchRule, childrenKey) { let res = undefined; const traval = function (node, match, child = "children") { if (res) { return; } if (isPlainObject(node)) { if (match(node)) { res = node; } else { if (node[child]) { traval(node[child], match, child); } } } else if (Array.isArray(node)) { for (let i = 0; i < node.length; i++) { traval(node[i], match, child); } } }; traval(treeNodes, matchRule, childrenKey); return res; } /** * 遍历树节点 * @param treeNode: Object | Array, 要遍历的节点 * @param callback: Function 遍历每个节点时, 要执行的函数 * @param child: String, 子节点的 key * @param initLevel: Number 设置节点遍历层级初始值, 默认为 0 */ function travalNode(treeNode, callback, child = "children", initLevel = 0) { if (isPlainObject(treeNode)) { typeof callback === "function" && callback(treeNode, initLevel); if (treeNode[child]) { travalNode(treeNode[child], callback, child, initLevel + 1); } } else if (Array.isArray(treeNode)) { for (let i = 0; i < treeNode.length; i++) { travalNode(treeNode[i], callback, child, initLevel); } } }
25. base64 格式和 File 对象的互转
/** * 将 base64 内容转为 File */ function transBase64DataToFile(base64Data, name, mimeType) { let binaryString = window.atob(base64Data); let binaryLen = binaryString.length; let bytes = new Uint8Array(binaryLen); for (var i = 0; i < binaryLen; i++) { bytes[i] = binaryString.charCodeAt(i); } const blob = new Blob([bytes], mimeType || {}); return new File([blob], name, { type: blob.type }); } /** * 获取文件内容并转为 base64 格式 */ function getFileBase64(file) { return new Promise(function (resolve, reject) { const reader = new FileReader(); let imgResult = ""; reader.readAsDataURL(file); reader.onload = function () { imgResult = reader.result; }; reader.onerror = function (error) { reject(error); }; reader.onloadend = function () { resolve(imgResult); }; }); }
26. 可编辑元素, 插入文本辅助方法
class EditDivControl { constructor(domSelector) { this.latestPosition = null this.dom = document.querySelector(domSelector) this.recordFocusPosition = this.recordFocusPosition.bind(this) this.emitChange = this.emitChange.bind(this) if (this.dom) { this.dom.setAttribute('contenteditable', 'true') this.bindEvent() } } bindEvent() { this.dom.addEventListener('click', this.recordFocusPosition) this.dom.addEventListener('keyup', this.recordFocusPosition) this.dom.addEventListener('input', this.emitChange) } removeEvent() { this.dom.removeEventListener('click', this.recordFocusPosition) this.dom.removeEventListener('keyup', this.recordFocusPosition) this.dom.removeEventListener('input', this.emitChange) } emitChange() { const val = this.getValue() if (typeof this.onInput === 'function') { this.onInput(val) } } focus() { this.dom.focus() } recordFocusPosition() { // 获取选定对象 var selection = document.getSelection(); // 设置最后光标对象 this.latestPosition = selection.getRangeAt(0); } insertText(insertText) { const elem = this.dom elem.focus() var selection = document.getSelection(); if (this.latestPosition) { // 清除所有选区 selection.removeAllRanges() // 添加最后光标还原之前的状态 selection.addRange(this.latestPosition) } // 如果选定对象范围是编辑框范围 if (selection.anchorNode.nodeName != "#text") { // 创建文本节点进行插入 var emojiText = document.createTextNode(insertText); // 如果文本框的子元素大于0,则表示有其他元素,则按照位置插入文本节点 if (elem.childNodes.length > 0) { for (var i = 0; i < elem.childNodes.length; i++) { if (i == selection.anchorOffset) { elem.insertBefore(emojiText, elem.childNodes[i]); } } } else { // 否则直接插入一个文本元素 elem.appendChild(emojiText); } // 创建新的光标对象 let range = document.createRange(); // 将光标对象的范围界定为新建的表情节点 range.selectNodeContents(emojiText); // 定位光标位置在表情节点的最大长度位置 range.setStart(emojiText, emojiText.length); // 将选区折叠为一个光标 range.collapse(true); // 清除所有光标对象 selection.removeAllRanges(); // 添加新的光标对象 selection.addRange(range); // 如果选定对象范围是文本节点 } else { // 获取光标对象 let range = selection.getRangeAt(0); // 获取光标对象的范围界定对象,一般就是textNode对象 var textNode = range.startContainer; // 获取光标位置 var rangeStartOffset = this.latestPosition ? range.startOffset : textNode.length; // 在光标位置处插入新的表情内容 textNode.insertData(rangeStartOffset, insertText); // 添加了新内容,将光标移动到新的位置 range.setStart(textNode, rangeStartOffset + insertText.length); // 将选区折叠为一个光标 range.collapse(true); // 清除所有光标对象 selection.removeAllRanges(); // 添加新的光标对象 selection.addRange(range); } // 记录最后光标对象 this.lastEditPosition = selection.getRangeAt(0); } setValue(text, option = {}) { if (option && option.decodeHtml) { this.dom.innerText = decodeHtml(text) } else { this.dom.innerText = text } } getValue(option = {}) { const innerText = this.dom.innerText if (option && option.encodeHtml) { return encodeHtml(innerText) } else { return innerText } } dispose() { this.removeEvent() this.dom = null this.latestPosition = null } }
27. 计算文本宽度
// 计算文本宽度 function calcTextWidth(text, font, context) { if (context) { context.font = font; return context.measureText(text).width; } else { var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); context.font = font; var metrics = context.measureText(text); canvas = null; return metrics.width } }