JS 常用工具函數

個人筆記(部分來源於網絡),持續更新中…


防抖
const debounce = (func, delay, immediate) => {
  return function () {
    clearTimeout(func.timer);
    const _this = this;
    if (immediate) {
      func.apply(_this, arguments);
      return;
    }
    func.timer = setTimeout(() => func.apply(_this, arguments), delay);
  }
};
節流
const throttle = (func, delay) => {
  let timer, last;
  return function () {
    const _this = this;
    const _args = arguments;
    const now = +new Date();
    if (last && now < last + delay) {
      clearTimeout(timer);
      timer = setTimeout(() => {
        last = now;
        func.apply(_this, _args);
      }, delay);
    } else {
      last = now;
      func.apply(this, _args);
    }
  }
};
獲取字符串中的數字
let str = '123abc!@#456';
str = Number(str.replace(/[^0-9]/ig, ''));        // 123456
計算兩個時間相差的時間間隔
/**
 * 計算兩個時間戳相差的時間間隔(最大單位爲分鐘,即兩個時間戳相差的時間使用 分鐘和秒 來表示)
 * @param {number|string} s start
 * @param {number|string} e end
 */
function __getOrderEffctiveTime(s, e) {
  if (typeof s !== 'number') s = new Date(s).getTime();
  if (typeof e !== 'number') e = new Date(e).getTime();
  const diff = e - s,
    dayTimer = 24*3600*1000,
    hourTimer = 3600*1000,
    minuteTimer = 60*1000;
  const day = Math.floor(diff / dayTimer);
  const outDay = diff % dayTimer;
  const hour = Math.floor(outDay / hourTimer);
  const outHour = outDay % hourTimer;
  const minute = Math.floor(outHour / minuteTimer);
  const outMinute = outHour % minuteTimer;
  const second = Math.floor(outMinute / 1000);

  let tempMin = 0;
  if (day > 0) { tempMin = day * 24 * 60; }
  if (hour > 0) { tempMin += hour * 60; }
  if (minute > 0) { tempMin += minute; }
  return {
    minute: tempMin,
    second,
  };
}


console.log(__getOrderEffctiveTime('2020.06.17', '2020.06.18'));	// { minute: 1440, second: 0 }
console.log(__getOrderEffctiveTime('2020.06.17 12:00', '2020.06.18 02:00'));	// { minute: 840, second: 0 }
console.log(__getOrderEffctiveTime('2020.06.17 12:00:30', '2020.06.18 02:00'));		// { minute: 839, second: 30 }

// 當然也可以計算時間戳
時間格式化(時間戳)
// t爲時間戳
function __dateFormater(formater, t) {
  let date = t ? new Date(t) : new Date(),
    Y = date.getFullYear() + '',
    M = date.getMonth() + 1,
    D = date.getDate(),
    H = date.getHours(),
    m = date.getMinutes(),
    s = date.getSeconds();
  return formater.replace(/YYYY|yyyy/g,Y)
    .replace(/YY|yy/g, Y.substr(2,2))
    .replace(/MM/g, (M<10?'0':'') + M)
    .replace(/DD/g, (D<10?'0':'') + D)
    .replace(/HH|hh/g, (H<10?'0':'') + H)
    .replace(/mm/g, (m<10?'0':'') + m)
    .replace(/ss/g, (s<10?'0':'') + s);
}
// dateFormater('YYYY-MM-DD HH:mm', t) ==> 2019-06-26 18:30
// dateFormater('YYYYMMDDHHmm', t) ==> 201906261830
時間格式化(將指定字符串由一種時間格式轉化爲另一種)
// fmtOriginal: 日期的原始格式      fmtFinally: 日期轉換後的格式
function __dateFmt(str, fmtOriginal, fmtFinally) {
  // '19491001' 'YYYYMMDD' 'YYYY年MM月DD日'
  str += '';
  let Y = '';
  if (~(Y = fmtOriginal.indexOf('YYYY'))) {
    Y= str.substr(Y, 4);
    fmtFinally= fmtFinally.replace(/YYYY|yyyy/g,Y);
  } else if (~(Y = fmtOriginal.indexOf('YY'))) {
    Y = str.substr(Y, 2);
    fmtFinally= fmtFinally.replace(/YY|yy/g, Y);
  }

  let k,i
  ['M','D','H','h','m','s'].forEach(s => {
    i = fmtOriginal.indexOf(s+s);
    k = ~i ? str.substr(i, 2) : '';
    fmtFinally= fmtFinally.replace(s+s, k);
  })
  return fmtFinally;
}
// dateFmt('19491001', 'YYYYMMDD', 'YYYY年MM月DD日') ==> 1949年10月01日
// dateFmt('111119491001', '----YYYYMMDD', 'YYYY年MM月DD日') ==> 1949年10月01日
// dateFmt('1949年10月01日', 'YYYY年MM月DD日', 'YYYYMMDD') ==> 19491001

// 一般的也可以用正則來實現
// '1949年10月01日'.replace(/(\d{4})年(\d{2})月(\d{2})日/, '$1-$2-$3') ==> 1949-10-01
格式化金額(添加千位符)
// __isNaN 方法參考本文章
function __handleAmount(num) {
  if (String(num).indexOf(',') !== -1) return num;		// 已經格式化
  if (__isNaN(num)) return Number(num);		// NaN
  num = parseFloat(num).toFixed(2).toString().split('.');
  num[0] = num[0].replace(/(\d)(?=(\d{3})+$)/g, '$1,');
  return num.join('.');
}
獲取 url 中的參數
function __getUrlParam(url) {
  let arrObj = url.split("?");
  let params = Object.create(null)
  if (arrObj.length > 1) {
    arrObj = arrObj[1].split('&');
    arrObj.forEach(item => {
      item = item.split('=');
      params[item[0]] = item[1];
    })
  }
  return params;
}
let url = document.location.toString();
getUrlParam(url);      // ?a=1&b=2&c=3 ==> {a: "1", b: "2", c: "3"}
判斷是否是 Number

原生的 isNaN 有個怪異行爲,如果 isNaN 函數的參數不是 Number 類型, isNaN 函數會首先嚐試將這個參數轉換爲數值,然後纔會對轉換後的結果是否是 NaN 進行判斷。因此,對於能被強制轉換爲有效的非 NaN 數值來說(空字符串和布爾值分別會被強制轉換爲數值 0 和 1),會返回 false 值。
具體請參考:MDN: isNaN()

function __isNaN(v) {
  return !(typeof v === 'string' || typeof v === 'number') || isNaN(v)
}
獲取數組中非 NaN 的最大值和最小值

Math.max() 會返回給定的一組數字中的最大值,但如果給定的參數中至少有一個參數無法被轉換成數字,則會返回 NaN。

// 最大值
function __getMax(arr) {
  arr = arr.filter(item => !_isNaN(item));
  return arr.length ? Math.max.apply(null, arr) : undefined;
}
// __getMax([1, 2, '11', null, 'fdf', []]) ==> 11

// 最小值
function __getMin(arr) {
  arr = arr.filter(item => !_isNaN(item));
  return arr.length ? Math.min.apply(null, arr) : undefined;
}
// min([1, 2, '11', null, 'fdf', []]) ==> 1
獲取兩個數值間的隨機數
// 兩個值不分大小先後
function __random(lower, upper) {
    lower = +lower || 0;
    upper = +upper || 0;
    return Math.random() * (upper - lower) + lower;
}
// random(0, 0.5) ==> 0.3567039135734613
// random(2, 1) ===> 1.6718418553475423
// random(-2, -1) ==> -1.4474325452361945
性能分析
window.onload = function() {
  setTimeout(function() {
    let t = performance.timing, report = new Object(), rt = report.timer = new Object(), rm = report.memory = new Object();
    rt.dns = { name: 'DNS查詢耗時', v: (t.domainLookupEnd - t.domainLookupStart).toFixed(0) };
    rt.tcp = { name: 'TCP鏈接耗時', v: (t.connectEnd - t.connectStart).toFixed(0) };
    rt.request = { name: 'request請求耗時', v: (t.responseEnd - t.responseStart).toFixed(0) };
    rt.dom = { name: '解析dom樹耗時', v: (t.domComplete - t.domInteractive).toFixed(0) };
    rt.whiteScreen = { name: '白屏時間', v: (t.responseStart - t.navigationStart).toFixed(0) };
    rt.domReady = { name: 'domready時間', v: (t.domContentLoadedEventEnd - t.navigationStart).toFixed(0) };
    rt.onload = { name: 'onload時間', v: (t.loadEventEnd - t.navigationStart).toFixed(0) };
    if (t = performance.memory) {
      rm.jsPercent = (t.usedJSHeapSize / t.totalJSHeapSize * 100).toFixed(2) + '%';
    }
    console.log('performance report: ',report);
  }, 0);
}
禁止某些鍵盤事件
document.addEventListener('keydown', function(event) {
  return !(
    112 == event.keyCode ||   //F1
    123 == event.keyCode ||   //F12
    event.ctrlKey && 82 == event.keyCode ||    //ctrl + R
    event.ctrlKey && 78 == event.keyCode ||    //ctrl + N
    event.shiftKey && 121 == event.keyCode ||   //shift + F10
    event.altKey && 115 == event.keyCode ||  //alt + F4
    "A" == event.srcElement.tagName && event.shiftKey    //shift + 點擊a標籤
  ) || (event.returnValue = false);
});
禁止右鍵、選擇、複製
['contextmenu', 'selectstart', 'copy'].forEach(function(ev) {
    document.addEventListener(ev, function(event) {
        return event.returnValue = false;
    })
});
全屏 / 退出全屏
// 全屏
function __toFullScreen() {
    let elem = document.body;
    elem.webkitRequestFullScreen
    ? elem.webkitRequestFullScreen()
    : elem.mozRequestFullScreen
    ? elem.mozRequestFullScreen()
    : elem.msRequestFullscreen
    ? elem.msRequestFullscreen()
    : elem.requestFullScreen
    ? elem.requestFullScreen()
    : console.error('瀏覽器不支持全屏');
}
// 退出全屏
function __exitFullscreen(){
    let elem = parent.document;
    elem.webkitCancelFullScreen
    ? elem.webkitCancelFullScreen()
    : elem.mozCancelFullScreen
    ? elem.mozCancelFullScreen()
    : elem.cancelFullScreen
    ? elem.cancelFullScreen()
    : elem.msExitFullscreen
    ? elem.msExitFullscreen()
    : elem.exitFullscreen
    ? elem.exitFullscreen()
    : console.error('瀏覽器不支持');
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章