javascript常用工具函数总结

前言

  • 以下代码来自:自己写的、工作项目框架上用到的、其他框架源码上的、网上看到的。
  • 主要是作为工具函数,服务于框架业务,自身不依赖于其他框架类库,部分使用到es6/es7的语法使用时要注意转码
  • 虽然尽量在函数中做了错误情况的处理,仍有可能出现报错的情况(不定期完善)
1. 获取url上的参数
  /**
   *获取url上的参数
   * @return {object}
   * @example
   * getRequest()  getRequest().paramA
   */
function getRequest() {
    var searchString = window.location.search.substring(1),
        params = searchString.split("&"),
        hash = {};
    if (searchString == "") return {};
    for (var i = 0; i < params.length; i++) {
        var pos = params[i].indexOf('=');
        if (pos == -1) { continue; }
        var paraName = params[i].substring(0, pos),
            paraValue = params[i].substring(pos + 1);
        hash[paraName] = paraValue;
    }
    return hash;
}
  • 返回一个对象,将url上的参数以键值对的形式存储在返回结果中,如果url上没参数,则返回空对象
2. 追加url参数
  /**
   * 追加url参数
   * @param {string} url url参数
   * @param {string|object} key 名字或者对象
   * @param {string} value 值
   * @return {string} 返回新的url
   * @example
   * appendQuery('lechebang.com', 'id', 3);
   * appendQuery('lechebang.com?key=value', { cityId: 2, cityName: '北京'});
   */
  function appendQuery (url, key, value) {
    var options = key;
    if (typeof options == 'string') {
      options = {};
      options[key] = value;
    }
    options = $.param(options);
    if (url.includes('?')) {
      url += '&' + options
    } else {
      url += '?' + options
    }
    return url;
  }
  • 传入一个url和需要添加的参数键值对,需添加的参数可以直接传对象格式。会判断原url上是否有参数,没有的话就加'?',返回添加参数后的url。
3. 计算两个日期的时间差
  /**
   * 计算两个日期时间的时间差
   * @param {Date, Date}  date1 date2
   * @return {object | null} 
   * @example
   * getDiff(new Date('2017-09-08'), new Date())
   */
function getDiff(date1, date2) {
     if (!date1.getTime || !date2.getTime) return null
     var ms = (date1.getTime() - date2.getTime());
     var day1 = Math.round(ms / 24 / 3600 / 1000),
         hh1 = Math.round((ms / 3600 / 1000) % 24),
         mm1 = Math.round((ms / 1000 / 60) % 60),
         ss1 = Math.round((ms / 1000) % 60);
     return {
         day: day1,
         hour: hh1,
         minute: mm1,
         second: ss1
      };
}
  • 传入两个Date日期对象,返回一个对象,其属性值day、hour、minute、second分别表示相差天数、小时、分钟、秒。结果以Math.round()取整,如果结果为负,则表示第一个日期在第二个日期前面
4. 将canvas转化为image图片格式
  /**
   * 将canvas转化为image格式
   * @param {string}  cId
   * @return {object HTMLImageElement} 
   * @example
   * canvasToImg('canvas')  canvasToImg('#canvarsId')
   */
function canvasToImg(cId){
    let canvas = document.querySelector(cId)
    if (!canvas || !canvas.toDataURL) return new Image()
    let imgData = canvas.toDataURL('image/png'),
        imgs= new Image();
        imgs.src=imgData;
    return imgs
}
  • 传入一个css选择器,函数根据选择器查询canvas节点,然后返回该canvas的image格式节点,如果查找不到则返回一个空的image。原理是将canvas转化为base64编码,toDataURL方法貌似是canvas节点独有的,然后新建一个src是这个base64编码的图片。
  • ps:什么情况下需要做这种转换呢?目前我知道的一个就是canvas在移动端无法长按保存到手机。
5. 生成随机guid
  /**
   * 生成一个唯一的guid
   * @return {string}
   * @example
   * // 7f603b20-17ff-4f47-aeb9-e7996de04939
   * util.guid();
   * @see http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
   */
  function guid () {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    });
  }
  • 这个方法用于生成一个随机guid,可以将生成的guid视为全局唯一的(生成两个相同id的情况很少)。guid似乎在前端用的比较少,目前项目用到就是在每次请求后端接口时调用此方法,生成一个guid传过去。
6. 获取一个月份的天数
  function isLeapYear (year) {
    if (year % 100 === 0) {
      if (year % 400 === 0) {
        return true;
      }
    } else if (year % 4 === 0) {
      return true;
    }
    return false;
  }
  /**
   * 获取某个月份有多少天
   * @return {number}
   * @param {string | number}  year month
   * @example
   * getDaysInMonth(2017, 9)
   */
  function getDaysInMonth (year, month) {
    return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
  }
  • 传入一个年份和月份,返回该月有多少天,其中也包含了一个isLeapYear方法来判断是否是闰年,应该在实现日历或者日期选择组件时用的到
7. 过滤对象属性
  /**
   * 过滤选出一个对象的某些属性
   * @param{object, array} obj key
   * @return{object}
   * @example
   * pick(obj, [key1, key2])
   */
  function pick (obj, keys) {
    let result = {}
    if (!obj || !keys.forEach) {
      return result
    }
    keys.forEach((item) => {
      if (obj.hasOwnProperty(item)) {
        result[item] = obj[item]
      }
    })
    return result
  }
  • 传入一个对象和一个数组,遍历数组,如果传入对象有数组中包含的属性,则提取出来,返回一个过滤提取出来的属性组成的对象。
  • 这个方法用处挺多的,比如从一个接口拿到了结果对象,可能上面很多属性你暂时用不到的,就可以用这个方法进行过滤。比如以前我用vue写项目的时候,就经常把接口返回的一个对象作为vue的data属性去渲染页面,这个时候这个对象是动态绑定在vue实例上面的,但是其中有些属性页面渲染根本用不到,还得去监听这些属性,白白浪费性能,这种时候将接口返回的对象筛选一下再赋给vue的data就显得合理多了。
8. 判断是否是一个对象
  /**
   * 判断传入参数是否是一个合法对象
   * @param{object} obj 
   * @return{object}
   * @example
   * isObject (null) isObject (() => {} )
   */
function isObject (obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
  }
  • 判断传入参数是否是一个合法对象,function类型均返回true,null返回false(typeof null 返回 object)
9. 判断一个函数是否是native code
  /**
   * 判断传入函数是否在当前环境下得到支持
   * @param{function} Ctor 
   * @return{boolean}
   * @example
   * isNative (window.Symbol)  isNative (window.Promise)
   */
function isNative (Ctor) {
  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
  • javascript原生自带的方法使用toString转成字符串时会包含‘native code’字段,比如Math.max.toString()就返回"function max() { [native code] }"。
  • 这个方法可以判断某些原生特性是否被当前浏览器支持。比如isNative (window.Promise)在chrome中返回true,因为chrome支持原生es6语法,放到ie里面就只能返回false了~
10. 深度克隆对象
  /**
   * 返回传入对象的一个深度克隆的副本对象
   * @param{object} obj 
   * @return{object}
   * @example
   * cloneDeep(obj)
   */
  function cloneDeep (obj) {
    if (!isObject(obj)) return obj;
    let result
    if (Array.isArray(obj)) {
      result = []
      obj.forEach((item) => {
        result.push(cloneDeep(item))
      })
      return result
    }
    result = {}
    for (let key in obj) {
      let item = obj[key]
      if (_.isObject(item)) {
        result[key] = cloneDeep(item)
      } else {
        result[key] = item
      }
    }

    return result
  }
  • 首先对传入参数进行判断,不是合法对象则直接返回其本身。然后再遍历其属性,属性中包含对象的会再次递归调用cloneDeep方法,浅克隆的话可以直接用Object.assign()方法。
11. 获取两个地点的实际距离
/**
   * 获取两个高德坐标的距离, 后一个点,不传,默认为用户坐标
   * @return {null|number} 距离多少米,没有定位信息,返回null
   * @example
   * getDistance(31.282055633974, 121.379623888259)
   */
  function getDistance (endLat, endLon, startLat, startLon) {
    if (!startLat) {
      let address = Lizard.state.address

      if (address && address.lat) {
        startLat = address.lat
        startLon = address.lon
      }
    }

    // 没有定位
    if (!startLat) {
      return null
    }

    const PI = Math.PI
    let lon1 = (PI / 180) * startLon
    let lon2 = (PI / 180) * endLon 
    let lat1 = (PI / 180) * startLat  
    let lat2 = (PI / 180) * endLat 
    // 地球半径  
    let R = 6378.137;  

    // 两点间距离 km,如果想要米的话,结果*1000就可以了  
    let d = Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)) * R;  

    return parseInt(d * 1000, 10)
  }
  • 依次传入两个点的经纬度,然后计算得出两个点的距离,单位为km。这个方法可能实际应用的比较少,我这里还是写出来,作为一种前端计算距离的方法(在能拿得到定位的情况下)。
12. 加载图片(promise封装)
/**
   * 传入图片url,返回一个promise对象,加载成功时resolve
   * @return {Promise} 
   * @example
   * loadImg(url).then(console.log('加载完成')).catch(err => {console.log(err)})
   */
  function loadImg (url) {
    return new Promise((resolve, reject) => {
      let img = new Image()

      img.addEventListener('load', function() {
        resolve([img.width, img.height])
      }, false)

      img.addEventListener('error', reject, false)

      img.src = url
    })
  }
  • 这个函数参考自ECMAScript 6 入门,算是promise的一个很经典的应用实例,加载完成后promise对象resolve,同时返回图片的宽高,以便后面调整样式等等。加载失败则返回reject,所以再调用这个方法后需要用catch方法来捕捉异常。
13. 重复字符串n次
/**
   * 传入字符串,和重复次数,返回结果字符串
   * @return {string} 
   * @param{string, number} str n 
   * @example
   * loadImg(url).then(console.log('加载完成')).catch(err => {console.log(err)})
   */
  const repeat = (str, n) => {
    let res = ''
    while (n) {
      if (n % 2 === 1) res += str
      if (n > 1) str += str
      n >>= 1
    }
    return res
  }
  • 这个函数在vue源码里看到的,用到了位运算符,如果让我来实现的话可能就马上想到重复n次,而上面这个函数执行时的时间复杂度差不多只是n/2。
14. 变量是否以'$'或者'_'开头
/**
   * 传入字符串,判断是否以'$'或者'_'开头
   * @return {Boolean} 
   * @param{string} str
   * @example
   * isReserved (‘$’)  isReserved (‘param’)
   */
function isReserved (str) {
  var c = (str + '').charCodeAt(0);
  return c === 0x24 || c === 0x5F
}
  • 同样是vue源码里的方法,判断变量是否以'$'或者'_'开头,charCodeAt(index)方法会返回字符串对应index位置的字符的Unicode编码,'$'对应的Unicode编码就是36,这里用的是十六进制表示方法就是0x24。要判断变量名以什么字符开头或结尾,将上面方法略微修改即可实现。
  • ps:vue中哪里用到这个方法了呢,我去看了下就是在初始化vue实例的data对象时会判断,如果你的vue实例data里面命名了以'$'或者'_'的对象,是不会把这个属性重新定义其get/set的。简单点说,如果你在vue的data里定义一个命名为$data的属性,再在模板里渲染{{$data}},就会报错的。
15. promise扩展-finally
/**
   * 在一个promise链调用结尾调用finally,传入一个函数,无论最后promise的状态是什么总会执行该函数
   * @param{function} callback
   * @example
   *  server.listen(0).then(function () { // run test }).finally(server.stop);
   */
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value).catch(e => {console.error(e)}),
    reason => P.resolve(callback()).then(() => { throw reason }).catch(e => {console.error(e)})
  );
};
  • 参考自ECMAScript 6 入门,对于promise对象的一个扩展方法,不论前面的promise状态是什么总会调用callback函数。
  • 我在原代码的基础上添加了新的catch,用于兼容callback执行时返回了一个状态为reject的promise的情况。
16. 判断dom元素是否在当前视窗内可见
/**
   * 传入第一个参数dom元素判断它在当前视窗是否可见
   * 第二个参数默认为false可缺省,为true的时候表示该元素部分可见就会返回true
   * @param {object,Boolean}
   * @example
   *  elementIsVisibleInViewport(document.querySelector('div'), true)
   */
const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
  const { top, left, bottom, right } = el.getBoundingClientRect();
  const { innerHeight, innerWidth } = window;
  return partiallyVisible
    ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
        ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
    : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};

结语

  • 再次补充
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,283评论 5 22
  • 你不知道JS:异步 第三章:Promises 在第二章,我们指出了采用回调来表达异步和管理并发时的两种主要不足:缺...
    purple_force阅读 2,035评论 0 4
  • 在线阅读 http://interview.poetries.top[http://interview.poetr...
    程序员poetry阅读 114,104评论 24 450
  • 如果你想处理文件的某一行数据,可以使用for和in-lines 如果你想判断是否“hello”出现在一个文件里,你...
    jarod_chan阅读 288评论 0 0
  • 上学时最爱东门外那些隐藏在小小门店的美味。虽然已经毕业好久了,但是那些记忆中的味道从来没有忘却。对于穷学生的我们来...
    猪小妹的白日梦阅读 452评论 0 0