一些常用的工具函数

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 }
View Code

 

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);
    }
}
View Code

 

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();
    }
}
View Code

 

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
    }
}
View Code

  

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
  }
View Code

  

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);
}
View Code

  

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')]
}
View Code

 

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.");
}
View Code

 

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
}
View Code

 

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"
    }
]    
View Code

 

11. html 转码、解码

var parseHtml = {
  encode: function(str) {
    var reg = /([&<>"'])/g;
    var codeMap = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      '\'': '&#39;'
    };
    return str == undefined ? '' : (str + '').replace(reg, function(s, c) {
      return codeMap[c];
    }) 
  },
  decode: function(str) {
    var reg = /(&amp;|&lt;|&gt;|&quot;|&#39;)/g;
    var codeMap = {
      '&amp;': '&',
      '&lt;': '<',
      '&gt;': '>',
      '&quot;': '"',
      '&#39;': '\''
    };
    return str == undefined ? '' : (str + '').replace(reg, function(s, c) {
      return codeMap[c];
    })
  }
}
View Code

 

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);
View Code

 

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')
View Code

 

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)
        })
      })
    }
View Code

 

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;
};
View Code

 

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;
}
View Code

 

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();
        }
      });
    }
  }
}
View Code

 

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;
}
View Code

 

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;
  }
}
View Code

 

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];
}
View Code

 

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);
  }
}
View Code

 

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
}
View Code

 

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];
}
View Code

 

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);
    }
  }
}
View Code

 

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);
    };
  });
}
View Code

 

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
    }
}
View Code

 

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
    }
}
View Code

 

posted @ 2020-12-07 17:49  芋头圆  阅读(24)  评论(0编辑  收藏  举报