够用就好:提升工做效率的代码片断

我相信你必定遇到过在开发一些功能的时候可能被卡在某个功能函数上,当你遇到这些问题的时候你可能会花上不少时间去百度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 }
复制代码

有些太简单的经常使用代码我以为没什么养分,网上相似的文章也不少就没拿出来,你以为还有什么经常使用的代码,能够给我留言我会更新到咱们的技术博客中。

相关文章
相关标签/搜索