夠用就好:提升工做效率的代碼片斷

我相信你必定遇到過在開發一些功能的時候可能被卡在某個功能函數上,當你遇到這些問題的時候你可能會花上不少時間去百度Google,或者直接引入一個第三方庫,但是搜索的結果不知道靠不靠譜,會不會有坑(沒考慮全面的地方),若是直接引入庫,你又在糾結是否是會致使文件過大。javascript

網絡上有不少代碼片斷的文章,好比很是著名的30秒,可是不少內容包含了過於簡單的代碼,或者在實際需求中用不上的代碼,亦或是有些代碼沒有說明存在的問題。java

下面列舉了一些我經常使用的代碼片斷,它們並不完美,可是短小精煉,而且能解決你80%以上的需求,對於不能處理的問題和可能用到的場景我會作說明。node

PS:因爲是自用片斷,爲了簡短,大量使用了拓展運算,三目運算,箭頭函數並省略了全部能省略的return,這可能致使有些代碼看起來不易理解,你能夠自行轉換成if語句或者帶有return的函數來幫助你理解,git

節流防抖

最簡單的防抖和定時器節流,日常優化個頁面滾動縮放,文本框輸入徹底夠用,比較大的問題是從事件觸發到函數相應存在一個時間差,就是你設置的那個延遲時間,若是沒有這方面的需求直接用下面這個短的就能夠了。github

const debounce = (func,wait = 50)=> {
  let timer = null;
  return function(...args){
    if(timer) clearTimeout(timer);
    timer = setTimeout(()=>func.apply(this,args),wait);
  }
}
const throttle = (func,wait = 50)=> {
    let timer = null;
    return function (...args) {
        if(!timer){
            timer = setTimeout(()=>{
                func.apply(this,args);
                timer = null;
            },wait);
        }
    }
}
複製代碼

若是你須要控制的比較精細,好比是否在開始時當即執行,是否在結束後再次調用,那麼可使用下面這個版本web

  • leading : Boolean 是否使用第一次執行
  • trailing : Boolean 是否使用中止觸發的回調執行
const throttle = (func,wait = 50,opts = {})=>{
  let preTime = 0,timer = null,{ leading = true, trailing = true } = opts;
  let throttled function (...args) {
    let now = Date.now();
    if(!leading && !preTime)preTime = now;
    if(now - preTime >= wait || preTime > now){
      if(timer){
        clearTimeout(timer);
        timer = null;
      }
      preTime = now;
      func.apply(this,args);
    }else if(!timer && trailing){
      timer = setTimeout(()=>{
        preTime = Date.now();
        timer = null;
        func.apply(this,args)
      },wait - now + preTime);
    }
  }
  throttled.cancel = ()=> {
    clearTimeout(timer);
    timer = null;
    preTime = 0;
	};
  return throttled;
}
const debounce = (func,wait = 50,opts = {})=> {
  let timer = null,result,{leading = false}=opts;
  let debounced function(...args){
    if(timer) clearTimeout(timer);
    if(leading){
      let callNow = !timer;
      timer = setTimeout(()=>timer = null,wait);
      if(callNow) result = func.apply(this,args);
    }else{
      timer = setTimeout(()=>func.apply(this,args),wait);
    }
    return result;
  }
  debounced.cancel = ()=>{
    clearTimeout(timer);
    timer = null;
  };
  return debounced
}
複製代碼

節流防抖的功能和用途就不說了,上面的版本存在的問題是沒有加入最大等待時間的控制,由於幾乎用不上。面試

深拷貝

const clone=(target, map = new WeakMap())=>{
    if (typeof target === 'object') {
        const isArray = Array.isArray(target);
        let cloneTarget = isArray ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        const keys = isArray ? undefined : Object.keys(target);
        forEach(keys || target, (value, key) => {
            if (keys) key = value;
            cloneTarget[key] = clone2(target[key], map);
        });
        return cloneTarget;
    }
    return target;
}
複製代碼

功能:對三種最多見的需求作處理算法

  1. 可以實現數組和對象的深拷貝
  2. 可以處理基礎數據類型
  3. 可以處理循環引用,並觸發內存回收

不足:數組

  1. 沒法處理一些常見對象,如Date Error Function等
  2. 沒法處理Map Set等對象
  3. 沒法處理Symbol類型的數據
  4. 沒法處理DOM節點的拷貝
  5. 。。。

提示:老司機都知道,完整的深拷貝只有在面試中會遇到,實際項目中90%你只會用到普通對象的深拷貝,甚至連循環引用都不多。瀏覽器

單次執行函數

只能執行一次的函數,以後再調用這個函數,將返回一次最後調用fn的結果

const once =(fn,rs,n=2)=>(...args)=>(--n>0? rs=fn.apply(this,args): fn = undefined,rs)
複製代碼

N次執行函數

調用次數不超過 n 次。 以後再調用這個函數,將返回一次最後調用fn的結果

const before = (n,fn,rs)=>(...args)=>(--n>0? rs=fn.apply(this,args): fn = undefined,rs)
複製代碼

特別備註:上面的寫法實際上是不安全的,徹底是爲了簡單而把變量寫進參數,這在你意外多傳參數的時候可能會對外部變量產生影響,除非你在使用時很是明確的知道其影響,不然請使用下面的形式。

const before=(n, fn)=>{
  let rs;
  return function() {
    --n>0
      ? rs=fn.apply(this, arguments)
			: fn = undefined
    return rs;
  };
}
const once=(fn)=>before(2,fn)
複製代碼

執行N次函數

我很是喜歡用於測試數據時的一個方法

const times = (num,fn=i=>i) =>Array.from({ length: num }).reduce((rs, _, index) =>rs.concat(fn(index)), []);
//times(5) => [0,1,2,3,4]
//times(5,i=>`user${i}`) => [ 'user0', 'user1', 'user2', 'user3', 'user4' ]
複製代碼

get

你確定遇到過訪問對象屬性的時候報錯的狀況,使用下面的get函數能夠幫助你更安全的訪問對象屬性。

const get= (obj,path,rs) => path.replace(/\[([^\[\]]*)\]/g, '.$1.').split('.').filter(t => t !== '').reduce((a,b) => a&&a[b],obj);
//get({a:[1,2,{b:3}]},'a[2].b.user.age') ==>undefined
複製代碼

若是你想了解更多安全訪問數組的方法的話能夠查看這裏---靈犀一指

拓展數組方法到對象

數組的every,some,filter,forEach,map是開發中的利器,但遺憾的是隻能對數組使用,lodash等工具函數提供了同名的方法能夠同時做用於數組和對象,你可使用下面的拓展函數把這些方法拓展到其餘對象上,默認拓展到Object的原型上,能夠像數組同樣直接使用,若是拓展到其餘對象上,能夠像lodash同樣將目標對象做爲第一個參數,回調函數做爲第二個參數使用。

const extendATO=(nameSpace=Object.prototype)=>{
  ['every','some','filter','forEach','map'].forEach(methodName=>{
    nameSpace[methodName]=function(...args){
      let fn=args[nameSpace===Object.prototype?0:1]
      let obj=nameSpace===Object.prototype?this:args[0]
      let values=Object.values(obj)
      let keys=Object.keys(obj)
      return keys[methodName](function(value,index,obj){
        return fn(values[index],keys[index],obj)
      })
    }
  })
}
//extendATO()
//({a:1,b:2,c:0}).every(value=>value) => false
//({a:1,b:2,c:0}).map((value,key)=>key+1) => ['a1','b1','c1']
//let _={}
//extendATO(_)
//_.map({a:1,b:2},value=>value+1) =>[2,3]
複製代碼

數組的扁平化

正常的遞歸

const flatten = (list) => list.reduce((acc, value) => acc.concat(value), []);
複製代碼

比較騷可是效率奇高的操做

const flatten = (list) =>JSON.parse(`[${JSON.stringify(list).replace(/\[|\]/g, '')}]`);
複製代碼

注:此方法沒法處理null,undefined和循環引用等問題,更多數組扁平化的操做能夠看這裏--大力金剛掌

類型判斷

const is=(type,obj)=>new RegExp(type,'i').test(Object.prototype.toString.call(obj).slice(8,-1))
//is('string','sdfsdfds') => true
//is('array',[]) =>true
//is('array|number',5) =>true 
複製代碼

中序遍歷二叉樹

const inorderTraversal=root=>(root===null)?[...inorderTraversal(root.left),root.value,...inorderTraversal(root.right)]:[]
複製代碼

數組全部的組合

兩種相似的思路,不一樣的寫法,返回不一樣的排列方式,你能夠選擇你喜歡的

30s提供的方法,子集中包含一個空數組,但這一般不是咱們須要的,並且子集的排序是顛倒的。

const powerset = (arr=[]) =>arr.reduce((rs, item) => rs.concat(rs.map(r => [item].concat(r))), [[]])
//powerset([1,2,3]) =>[ [], [ 1 ], [ 2 ], [ 2, 1 ], [ 3 ], [ 3, 1 ], [ 3, 2 ], [ 3, 2, 1 ] ]
複製代碼

我經常使用的方法,不包含空數組,返回的組合排序看起來更正常一點,強迫症福利。

const powerset=(arr=[])=>arr.reduce((rs,item)=>[...rs,...rs.slice().map(i=>i.concat(item)),[item]],[])
//powerset([1,2,3]) =>[ [ 1 ], [ 1, 2 ], [ 2 ], [ 1, 3 ], [ 1, 2, 3 ], [ 2, 3 ], [ 3 ] ]
複製代碼

多個數組的交叉組合

一般你在網上看到的都是兩個數組的交叉組合,可是實際項目中更多的是多個數組的交叉組合,若是你在作SKU或者商品組合的需求的時候可能會急需下面這個方法

const xprod=(...lists)=>lists.reduce((rs,arrItem)=>rs.length
  ? rs.reduce((acc,item)=>arrItem.reduce((acc,value)=>acc.concat([[...item,value]]),acc),[])
  : arrItem,[''])
//xprod(['red','blue'],['36','37','38'],['男','女'])
[ [ 'red', '36', '男' ],
  [ 'red', '36', '女' ],
  [ 'red', '37', '男' ],
  [ 'red', '37', '女' ],
  [ 'red', '38', '男' ],
  [ 'red', '38', '女' ],
  [ 'blue', '36', '男' ],
  [ 'blue', '36', '女' ],
  [ 'blue', '37', '男' ],
  [ 'blue', '37', '女' ],
  [ 'blue', '38', '男' ],
  [ 'blue', '38', '女' ] ]
複製代碼

全排列

const permutations = arr =>  arr.length <= 2
    ? (arr.length === 2 ? [arr, [arr[1], arr[0]]] : arr)
    : arr.reduce( (acc, item, i) => acc.concat( permutations([...arr.slice(0, i), ...arr.slice(i + 1)]).map(val => [item, ...val]) ), [] )
//permutations([1,2,3]) => [ [ 2, 3, 1 ],[ 3, 2, 1 ],[ 1, 3, 2 ],[ 3, 1, 2 ],[ 1, 2, 3 ],[ 2, 1, 3 ] ]
複製代碼

另外一種常見的全排列是給定一個字符串,而後進行全拍列,對上面的函數稍加改造就能夠了

const stringPermutations = str => str.length <= 2
    ? (str.length === 2 ? [str, str[1] + str[0]] : [str])
    : str.split('').reduce((acc, letter, i) =>
      acc.concat(stringPermutations(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)), [])
//stringPermutations('abc') => [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ]
複製代碼

分組統計

const groupBy = (arr, fn) => arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val, i) => (acc[val] = (acc[val] || []).concat(arr[i]), acc), {})
複製代碼

常見的需求:後臺給你一個城市列表,要按照所在省份分組顯示,或者給你一個list要按照某種規則作成樹形菜單。

const countBy = (arr, fn) => arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => (acc[val] = (acc[val] || 0) + 1 , acc), {})
複製代碼

常見的需求:和上面差很少

「真正」的數組亂序

是否是每次數組亂序你用的都是Math.random()-.5,其實這個並非真正的隨機,若是你要用這樣的算法作個年會抽獎程序,可能會在一幫屌絲現場review代碼的時候被打死,試試Fisher–Yates隨機。

const shuffle=(arr)=>{
  let len=arr.length;
  while (len){
    let index = Math.floor(Math.random() * len--);
    [arr[index],arr[len]]=[arr[len],arr[index]]
  }
  return arr;
}
複製代碼

注:嚴格意義上講,沒有絕對的隨機,咱們只要保證全部組合出現的頻率差異不大就能夠了。若是你須要瞭解隨機算法更詳細的知識能夠看我以前的講解--洗牌算法和隨機排序

差別過濾

之後續數組作過濾,返回第一個數組獨有的內容

const difference = (...args) => args.reduce((pre,next)=>pre.filter(x=>!new Set(next).has(x)))

const differenceBy = (...args) => {
  let lastArg=args[args.length - 1]
  let fn=typeof lastArg === 'function' ? lastArg : i=>i
  return args.reduce((pre,next)=>typeof next ==='function'
    ? pre
    : pre.filter(x=>!new Set(next.map(fn)).has(fn(x))))
};

const differenceWith = (...args) => {
  let lastArg=args[args.length - 1]
  let fn=typeof lastArg === 'function' ? lastArg : (a,b)=>b
  return args.reduce((pre,next)=>typeof next ==='function'
    ? pre
    : pre.filter(a => !~next.findIndex(b => fn(a, b))))
}
複製代碼

若是你的後臺有多個service,並且後臺人員不給你作數據整合的時候,你可能很是須要這個過濾方法,舉個例子:

item給你一堆商品,coupon給你一堆不能用券的商品,activity給你一堆不能參加活動的商品,如今讓你在前臺展現剩餘的商品。

ps:常常有人搞不清by和with的區別,by是先使用函數進行處理而後比較,回調接收的是一個參數,with是直接用兩個值進行比較,回調接收的是兩個參數

特別提醒30s(包括有不少抄來的文章)提供的differenceBy會有下面幾個問題,使用的時候必定要注意

  1. 一次只能處理兩個數組
  2. by方法會返回被回調函數修改過的結果,這必定不是你想要的,我不清楚做者爲何這麼寫,由於在對稱過濾symmetricDifferenceBy和交集過濾intersectionBy的源碼中不存在這個問題
  3. 不傳回調函數的時候會報錯

以上這些問題在lodash和本文給你提供的片斷中都不存在

交集過濾

有差別過濾就必定有交集過濾,需求和實現都差很少,就很少描述了,一樣幫你處理好了多數組和默認函數的狀況。

const intersection = (...args) => args.reduce((pre,next)=>pre.filter(x=>new Set(next).has(x)))


const intersectionBy = (...args) => {
  let lastArg=args[args.length - 1]
  let fn=typeof lastArg === 'function' ? lastArg : i=>i
  return args.reduce((pre,next)=>typeof next ==='function'
    ? pre
    : pre.filter(x=>new Set(next.map(fn)).has(fn(x))))
};

const intersectionWith = (...args) => {
  let lastArg=args[args.length - 1]
  let fn=typeof lastArg === 'function' ? lastArg : (a,b)=>b
  return args.reduce((pre,next)=>typeof next ==='function'
    ? pre
    : pre.filter(a => ~next.findIndex(b => fn(a, b))))
}
複製代碼

我本身使用的時候會把上面這6個函數合併成一個函數fuckJava(arrlist,fn,b=true),而後經過回調的參數和布爾類型的true false來實現對應的功能,不過不建議在項目裏這樣使用,由於其餘開發人員可能會弄亂,關於名字,你能夠想一想何時你會用到這個函數。感興趣的能夠本身封裝一下。

限定範圍隨機數

const range = (min, max) => Math.random() * (max - min) + min;
複製代碼

若是須要隨機整數能夠再Math.floor一下或者像下面這樣再封裝一下

const rangeInt=(min,max)=>Math.floor(range(min,max))
複製代碼

隨機顏色

const randomHex=()=>'#'+Math.random().toString(16).slice(2,8)
複製代碼

注:大約有100億分之一的機率會出錯,和你連續被雷劈兩個月的機率差很少,建議監控一下這方法,出錯的時候買張彩票。

顏色互轉

const rgbToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0');
const hexToRgb=(hex)=>{
  let bigint = parseInt(hex, 16);
  let r = (bigint >> 16) & 255;
  let g = (bigint >> 8) & 255;
  let b = bigint & 255;
  return [r,g,b]
}
複製代碼

注:輸入和輸出都是不帶#的

includes

這是一個小技巧,使用~能夠減小if中對-1的判斷,相比if(index!==-1)我更喜歡寫成if(~index),其實就是由於懶

const includes=(target,value)=>!!~target.indexOf(value)
複製代碼

管道函數

使用reduce或reduceRight實現正向(pipe)或者逆向(compose)的管道函數

const pipe = (...functions) => (initialValue) =>
  functions.reduce((value, fn) => fn(value), initialValue);

const compose = (...functions) => (initialValue) =>
  functions.reduceRight((value, fn) => fn(value), initialValue);
複製代碼

合理的使用管道函數能夠極大的提高開發效率和並精簡代碼

柯里化

正反兩個方向的柯里化

const curry = (fn, arity = fn.length, ...args) => arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args)
複製代碼
const uncurry = (fn, n = 1) => (...args) => (acc => args => args.reduce((x, y) => x(y), acc))(fn)(args.slice(0, n))
複製代碼

緩存函數

我的認爲這個至關有用,除了能解決性能上的問題外還能夠解決eslint不容許函數重載的問題。

const memoize = fn => {
  const cache = new Map();
  const cached = function(val) {
    return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
  };
  cached.cache = cache;
  return cached;
};
複製代碼

計算全部位數之和

大多數人下意識的想法應該是進行逐位累加,可是其實有更好的方式

const rootSum = (n)=>(n-1)%9+1
複製代碼

數字前置填充0

不少的數據展現須要保持數字的位數相同,不足的時候在前面填充0

const fill0=(value,len=1)=>(Array(len).join(0)+value).slice(-Math.max(len,value.toString().length))
複製代碼

爲何不用padStart?第一是給你提供個思路,第二是有些老舊項目無法加babel

下面是padstart版本

const fill0=(value,len)=>`${value}`.padStart(len,0)
複製代碼

銀行卡校驗

也叫模10算法,若是你的項目裏須要校驗銀行卡的時候很是有用

const luhnCheck = num => {
  let arr = (num + '')
    .split('')
    .reverse()
    .map(x => parseInt(x));
  let lastDigit = arr.splice(0, 1)[0];
  let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + ((val * 2) % 9) || 9), 0);
  sum += lastDigit;
  return sum % 10 === 0;
};
複製代碼

url參數互轉

const objectToQueryString = queryParameters => queryParameters
    ? Object.entries(queryParameters).reduce((queryString, [key, val], index) => {
        const symbol = index === 0 ? '?' : '&';
        queryString += typeof val === 'string' ? `${symbol}${key}=${val}` : '';
        return queryString;
      }, '')
    : ''
const getURLParameters = url => (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce( (a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a), {} );
複製代碼

注:獲取URL參數的時候若是存在單頁哈希參數會出現問題。

駝峯與連字符互轉

const toKebabCase = str =>
  str &&
  str.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
    .map(x => x.toLowerCase())
    .join('-');
const toCamelCase = str => {
  let s =
    str &&
    str.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
      .map(x => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
      .join('');
  return s.slice(0, 1).toLowerCase() + s.slice(1);
};
複製代碼

這兩個方法在動態生成樣式表的時候很是有用

HTML正反編碼

xss攻擊瞭解一下

const escapeHTML = str =>
  str.replace(
    /[&<>'"]/g,
    tag =>
      ({
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
      }[tag] || tag)
  );
const unescapeHTML = str =>
  str.replace(
    /&amp;|&lt;|&gt;|&#39;|&quot;/g,
    tag =>
      ({
        '&amp;': '&',
        '&lt;': '<',
        '&gt;': '>',
        '&#39;': "'",
        '&quot;': '"'
      }[tag] || tag)
  );
複製代碼

在NODE下獲取全部文件或文件夾

const path = require('path')
const fs = require('fs')
const getFiles=(filePath, deep = true)=>fs.readdirSync(filePath).reduce((rs, i) => {
  let tpath = path.join(filePath, i)
  return rs.concat(
    fs.statSync(tpath).isDirectory()
      ? (deep ? getFiles(tpath) : [])
      : { path: tpath, name: i, folderName: path.basename(filePath) }
  )
}, [])
const getFolders=(filePath, deep = true)=>fs.readdirSync(filePath).reduce((rs, i) => {
  let tpath = path.join(filePath, i)
  return fs.statSync(tpath).isDirectory()
  ? rs.concat({ path: tpath, name: i }, deep ? getFolders(tpath, deep) : [])
  : rs
}, [])
複製代碼

功能:返回一個數組,包含文件或文件夾的路徑和名稱,這兩個屬性是最經常使用的,若是須要其餘的能夠自行添加

設備檢測

const isMoble=()=>/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
複製代碼

獲取滾動位置

const getScrollPosition = (el = window) => ({
  x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
  y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
});
複製代碼

判斷瀏覽器環境

const isBrowser = () => ![typeof window, typeof document].includes('undefined');
複製代碼

當你用到這個方法的時候多半是須要進行服務端渲染了,此時下面這段東西能幫你大忙,用的時候直接導入就好了。

const doc = (typeof document === 'undefined') ? {
  body: {},
  addEventListener() {},
  removeEventListener() {},
  activeElement: {
    blur() {},
    nodeName: ''
  },
  querySelector() {
    return null
  },
  querySelectorAll() {
    return []
  },
  getElementById() {
    return null
  },
  createEvent() {
    return {
      initEvent() {}
    }
  },
  createElement() {
    return {
      children: [],
      childNodes: [],
      style: {},
      setAttribute() {},
      getElementsByTagName() {
        return []
      }
    }
  },
  location: { hash: '' }
} : document; // eslint-disable-line
const win = (typeof window === 'undefined') ? {
  doc,
  navigator: {
    userAgent: ''
  },
  location: {},
  history: {},
  CustomEvent: function CustomEvent() {
    return this
  },
  addEventListener() {},
  removeEventListener() {},
  getComputedStyle() {
    return {
      getPropertyValue() {
        return ''
      }
    }
  },
  Image() {},
  Date() {},
  screen: {},
  setTimeout() {},
  clearTimeout() {}
} : window; // eslint-disable-line
export { win as window, doc as document }
複製代碼

有些太簡單的經常使用代碼我以爲沒什麼養分,網上相似的文章也不少就沒拿出來,你以爲還有什麼經常使用的代碼,能夠給我留言我會更新到咱們的技術博客中。

相關文章
相關標籤/搜索