js武器库

打造自己的 JavaScript 武器库

2017-12-14 SlaneYang JavaScript

自己打造一把趁手的武器,高效率完成前端业务代码。

前言

作为战斗在业务一线的前端,要想少加班,就要想办法提高工作效率。这里提一个小点,我们在业务开发过程中,经常会重复用到 日期格式化、 url参数转对象、 浏览器类型判断、 节流函数等一类函数,这些工具类函数,基本上在每个项目都会用到,为避免不同项目多次复制粘贴的麻烦,我们可以统一封装,发布到 npm,以提高开发效率。

这里,笔者已经封装并发布了自己的武器库 outils,如果你对本项目感兴趣,欢迎 star 本项目。当然你也可以在本项目的基础上封装自己的武器库。

常用函数汇总

这里先分类整理下,之前项目中多次用到的工具函数。

1.Array

1.1 arrayEqual

  1. /**

  2. *

  3. * @desc 判断两个数组是否相等

  4. * @param {Array} arr1

  5. * @param {Array} arr2

  6. * @return {Boolean}

  7. */

  8. function arrayEqual(arr1, arr2) {

  9.    if (arr1 === arr2) return true;

  10.    if (arr1.length != arr2.length) return false;

  11.    for (var i = 0; i < arr1.length; ++i) {

  12.        if (arr1[i] !== arr2[i]) return false;

  13.    }

  14.    return true;

  15. }

2.Class

2.1 addClass

  1. /**

  2. *

  3. * @desc   为元素添加class

  4. * @param  {HTMLElement} ele

  5. * @param  {String} cls

  6. */

  7.  

  8. var hasClass = require('./hasClass');

  9.  

  10. function addClass(ele, cls) {

  11.    if (!hasClass(ele, cls)) {

  12.        ele.className += ' ' + cls;

  13.    }

  14. }

2.2 hasClass

  1. /**

  2. *

  3. * @desc 判断元素是否有某个class

  4. * @param {HTMLElement} ele

  5. * @param {String} cls

  6. * @return {Boolean}

  7. */

  8. function hasClass(ele, cls) {

  9.    return (new RegExp('(\\s|^)' + cls + '(\\s|$)')).test(ele.className);

  10. }

2.3 removeClass

  1. /**

  2. *

  3. * @desc 为元素移除class

  4. * @param {HTMLElement} ele

  5. * @param {String} cls

  6. */

  7.  

  8. var hasClass = require('./hasClass');

  9.  

  10. function removeClass(ele, cls) {

  11.    if (hasClass(ele, cls)) {

  12.        var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');

  13.        ele.className = ele.className.replace(reg, ' ');

  14.    }

  15. }

3.Cookie

3.1 getCookie

  1. /**

  2. *

  3. * @desc 根据name读取cookie

  4. * @param  {String} name

  5. * @return {String}

  6. */

  7. function getCookie(name) {

  8.    var arr = document.cookie.replace(/\s/g, "").split(';');

  9.    for (var i = 0; i < arr.length; i++) {

  10.        var tempArr = arr[i].split('=');

  11.        if (tempArr[0] == name) {

  12.            return decodeURIComponent(tempArr[1]);

  13.        }

  14.    }

  15.    return '';

  16. }

3.2 removeCookie

  1. var setCookie = require('./setCookie');

  2. /**

  3. *

  4. * @desc 根据name删除cookie

  5. * @param  {String} name

  6. */

  7. function removeCookie(name) {

  8.    // 设置已过期,系统会立刻删除cookie

  9.    setCookie(name, '1', -1);

  10. }

3.3 setCookie

  1. /**

  2. *

  3. * @desc  设置Cookie

  4. * @param {String} name

  5. * @param {String} value

  6. * @param {Number} days

  7. */

  8. function setCookie(name, value, days) {

  9.    var date = new Date();

  10.    date.setDate(date.getDate() + days);

  11.    document.cookie = name + '=' + value + ';expires=' + date;

  12. }

4.Device

4.1 getExplore

  1. /**

  2. *

  3. * @desc 获取浏览器类型和版本

  4. * @return {String}

  5. */

  6. function getExplore() {

  7.    var sys = {},

  8.        ua = navigator.userAgent.toLowerCase(),

  9.        s;

  10.    (s = ua.match(/rv:([\d.]+)\) like gecko/)) ? sys.ie = s[1]:

  11.        (s = ua.match(/msie ([\d\.]+)/)) ? sys.ie = s[1] :

  12.        (s = ua.match(/edge\/([\d\.]+)/)) ? sys.edge = s[1] :

  13.        (s = ua.match(/firefox\/([\d\.]+)/)) ? sys.firefox = s[1] :

  14.        (s = ua.match(/(?:opera|opr).([\d\.]+)/)) ? sys.opera = s[1] :

  15.        (s = ua.match(/chrome\/([\d\.]+)/)) ? sys.chrome = s[1] :

  16.        (s = ua.match(/version\/([\d\.]+).*safari/)) ? sys.safari = s[1] : 0;

  17.    // 根据关系进行判断

  18.    if (sys.ie) return ('IE: ' + sys.ie)

  19.    if (sys.edge) return ('EDGE: ' + sys.edge)

  20.    if (sys.firefox) return ('Firefox: ' + sys.firefox)

  21.    if (sys.chrome) return ('Chrome: ' + sys.chrome)

  22.    if (sys.opera) return ('Opera: ' + sys.opera)

  23.    if (sys.safari) return ('Safari: ' + sys.safari)

  24.    return 'Unkonwn'

  25. }

4.2 getOS

  1. /**

  2. *

  3. * @desc 获取操作系统类型

  4. * @return {String}

  5. */

  6. function getOS() {

  7.    var userAgent = 'navigator' in window && 'userAgent' in navigator && navigator.userAgent.toLowerCase() || '';

  8.    var vendor = 'navigator' in window && 'vendor' in navigator && navigator.vendor.toLowerCase() || '';

  9.    var appVersion = 'navigator' in window && 'appVersion' in navigator && navigator.appVersion.toLowerCase() || '';

  10.  

  11.    if (/mac/i.test(appVersion)) return 'MacOSX'

  12.    if (/win/i.test(appVersion)) return 'windows'

  13.    if (/linux/i.test(appVersion)) return 'linux'

  14.    if (/iphone/i.test(userAgent) || /ipad/i.test(userAgent) || /ipod/i.test(userAgent)) 'ios'

  15.    if (/android/i.test(userAgent)) return 'android'

  16.    if (/win/i.test(appVersion) && /phone/i.test(userAgent)) return 'windowsPhone'

  17. }

5.Dom

5.1 getScrollTop

  1. /**

  2. *

  3. * @desc 获取滚动条距顶部的距离

  4. */

  5. function getScrollTop() {

  6.    return (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;

  7. }

5.2 offset

  1. /**

  2. *

  3. * @desc  获取一个元素的距离文档(document)的位置,类似jQ中的offset()

  4. * @param {HTMLElement} ele

  5. * @returns { {left: number, top: number} }

  6. */

  7. function offset(ele) {

  8.    var pos = {

  9.        left: 0,

  10.        top: 0

  11.    };

  12.    while (ele) {

  13.        pos.left += ele.offsetLeft;

  14.        pos.top += ele.offsetTop;

  15.        ele = ele.offsetParent;

  16.    };

  17.    return pos;

  18. }

5.3 scrollTo

  1. var getScrollTop = require('./getScrollTop');

  2. var setScrollTop = require('./setScrollTop');

  3. var requestAnimFrame = (function () {

  4.    return window.requestAnimationFrame ||

  5.        window.webkitRequestAnimationFrame ||

  6.        window.mozRequestAnimationFrame ||

  7.        function (callback) {

  8.            window.setTimeout(callback, 1000 / 60);

  9.        };

  10. })();

  11. /**

  12. *

  13. * @desc  在${duration}时间内,滚动条平滑滚动到${to}指定位置

  14. * @param {Number} to

  15. * @param {Number} duration

  16. */

  17. function scrollTo(to, duration) {

  18.    if (duration < 0) {

  19.        setScrollTop(to);

  20.        return

  21.    }

  22.    var diff = to - getScrollTop();

  23.    if (diff === 0) return

  24.    var step = diff / duration * 10;

  25.    requestAnimationFrame(

  26.        function () {

  27.            if (Math.abs(step) > Math.abs(diff)) {

  28.                setScrollTop(getScrollTop() + diff);

  29.                return;

  30.            }

  31.            setScrollTop(getScrollTop() + step);

  32.            if (diff > 0 && getScrollTop() >= to || diff < 0 && getScrollTop() <= to) {

  33.                return;

  34.            }

  35.            scrollTo(to, duration - 16);

  36.        });

  37. }

5.4 setScrollTop

  1. /**

  2. *

  3. * @desc 设置滚动条距顶部的距离

  4. */

  5. function setScrollTop(value) {

  6.    window.scrollTo(0, value);

  7.    return value;

  8. }

6.Keycode

6.1 getKeyName

  1. var keyCodeMap = {

  2.    8: 'Backspace',

  3.    9: 'Tab',

  4.    13: 'Enter',

  5.    16: 'Shift',

  6.    17: 'Ctrl',

  7.    18: 'Alt',

  8.    19: 'Pause',

  9.    20: 'Caps Lock',

  10.    27: 'Escape',

  11.    32: 'Space',

  12.    33: 'Page Up',

  13.    34: 'Page Down',

  14.    35: 'End',

  15.    36: 'Home',

  16.    37: 'Left',

  17.    38: 'Up',

  18.    39: 'Right',

  19.    40: 'Down',

  20.    42: 'Print Screen',

  21.    45: 'Insert',

  22.    46: 'Delete',

  23.  

  24.    48: '0',

  25.    49: '1',

  26.    50: '2',

  27.    51: '3',

  28.    52: '4',

  29.    53: '5',

  30.    54: '6',

  31.    55: '7',

  32.    56: '8',

  33.    57: '9',

  34.  

  35.    65: 'A',

  36.    66: 'B',

  37.    67: 'C',

  38.    68: 'D',

  39.    69: 'E',

  40.    70: 'F',

  41.    71: 'G',

  42.    72: 'H',

  43.    73: 'I',

  44.    74: 'J',

  45.    75: 'K',

  46.    76: 'L',

  47.    77: 'M',

  48.    78: 'N',

  49.    79: 'O',

  50.    80: 'P',

  51.    81: 'Q',

  52.    82: 'R',

  53.    83: 'S',

  54.    84: 'T',

  55.    85: 'U',

  56.    86: 'V',

  57.    87: 'W',

  58.    88: 'X',

  59.    89: 'Y',

  60.    90: 'Z',

  61.  

  62.    91: 'Windows',

  63.    93: 'Right Click',

  64.  

  65.    96: 'Numpad 0',

  66.    97: 'Numpad 1',

  67.    98: 'Numpad 2',

  68.    99: 'Numpad 3',

  69.    100: 'Numpad 4',

  70.    101: 'Numpad 5',

  71.    102: 'Numpad 6',

  72.    103: 'Numpad 7',

  73.    104: 'Numpad 8',

  74.    105: 'Numpad 9',

  75.    106: 'Numpad *',

  76.    107: 'Numpad +',

  77.    109: 'Numpad -',

  78.    110: 'Numpad .',

  79.    111: 'Numpad /',

  80.  

  81.    112: 'F1',

  82.    113: 'F2',

  83.    114: 'F3',

  84.    115: 'F4',

  85.    116: 'F5',

  86.    117: 'F6',

  87.    118: 'F7',

  88.    119: 'F8',

  89.    120: 'F9',

  90.    121: 'F10',

  91.    122: 'F11',

  92.    123: 'F12',

  93.  

  94.    144: 'Num Lock',

  95.    145: 'Scroll Lock',

  96.    182: 'My Computer',

  97.    183: 'My Calculator',

  98.    186: ';',

  99.    187: '=',

  100.    188: ',',

  101.    189: '-',

  102.    190: '.',

  103.    191: '/',

  104.    192: '`',

  105.    219: '[',

  106.    220: '\\',

  107.    221: ']',

  108.    222: '\''

  109. };

  110. /**

  111. * @desc 根据keycode获得键名

  112. * @param  {Number} keycode

  113. * @return {String}

  114. */

  115. function getKeyName(keycode) {

  116.    if (keyCodeMap[keycode]) {

  117.        return keyCodeMap[keycode];

  118.    } else {

  119.        console.log('Unknow Key(Key Code:' + keycode + ')');

  120.        return '';

  121.    }

  122. };

7.Object

7.1 deepClone

  1. /**

  2. * @desc 深拷贝,支持常见类型

  3. * @param {Any} values

  4. */

  5. function deepClone(values) {

  6.    var copy;

  7.  

  8.    // Handle the 3 simple types, and null or undefined

  9.    if (null == values || "object" != typeof values) return values;

  10.  

  11.    // Handle Date

  12.    if (values instanceof Date) {

  13.        copy = new Date();

  14.        copy.setTime(values.getTime());

  15.        return copy;

  16.    }

  17.  

  18.    // Handle Array

  19.    if (values instanceof Array) {

  20.        copy = [];

  21.        for (var i = 0, len = values.length; i < len; i++) {

  22.            copy[i] = deepClone(values[i]);

  23.        }

  24.        return copy;

  25.    }

  26.  

  27.    // Handle Object

  28.    if (values instanceof Object) {

  29.        copy = {};

  30.        for (var attr in values) {

  31.            if (values.hasOwnProperty(attr)) copy[attr] = deepClone(values[attr]);

  32.        }

  33.        return copy;

  34.    }

  35.  

  36.    throw new Error("Unable to copy values! Its type isn't supported.");

  37. }

7.2 isEmptyObject

  1. /**

  2. *

  3. * @desc   判断`obj`是否为空

  4. * @param  {Object} obj

  5. * @return {Boolean}

  6. */

  7. function isEmptyObject(obj) {

  8.    if (!obj || typeof obj !== 'object' || Array.isArray(obj))

  9.        return false

  10.    return !Object.keys(obj).length

  11. }

8.Random

8.1 randomColor

  1. /**

  2. *

  3. * @desc 随机生成颜色

  4. * @return {String}

  5. */

  6. function randomColor() {

  7.    return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6);

  8. }

8.2 randomNum 

  1. /**

  2. *

  3. * @desc 生成指定范围随机数

  4. * @param  {Number} min

  5. * @param  {Number} max

  6. * @return {Number}

  7. */

  8. function randomNum(min, max) {

  9.    return Math.floor(min + Math.random() * (max - min));

  10. }

9.Regexp

9.1 isEmail

  1. /**

  2. *

  3. * @desc   判断是否为邮箱地址

  4. * @param  {String}  str

  5. * @return {Boolean}

  6. */

  7. function isEmail(str) {

  8.    return /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(str);

  9. }

9.2 isIdCard

  1. /**

  2. *

  3. * @desc  判断是否为身份证号

  4. * @param  {String|Number} str

  5. * @return {Boolean}

  6. */

  7. function isIdCard(str) {

  8.    return /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/.test(str)

  9. }

9.3 isPhoneNum

  1. /**

  2. *

  3. * @desc   判断是否为手机号

  4. * @param  {String|Number} str

  5. * @return {Boolean}

  6. */

  7. function isPhoneNum(str) {

  8.    return /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/.test(str)

  9. }

9.4 isUrl

  1. /**

  2. *

  3. * @desc   判断是否为URL地址

  4. * @param  {String} str

  5. * @return {Boolean}

  6. */

  7. function isUrl(str) {

  8.    return /[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/i.test(str);

  9. }

10.String

10.1 digitUppercase

  1. /**

  2. *

  3. * @desc   现金额转大写

  4. * @param  {Number} n

  5. * @return {String}

  6. */

  7. function digitUppercase(n) {

  8.    var fraction = ['角', '分'];

  9.    var digit = [

  10.        '零', '壹', '贰', '叁', '肆',

  11.        '伍', '陆', '柒', '捌', '玖'

  12.    ];

  13.    var unit = [

  14.        ['元', '万', '亿'],

  15.        ['', '拾', '佰', '仟']

  16.    ];

  17.    var head = n < 0 ? '欠' : '';

  18.    n = Math.abs(n);

  19.    var s = '';

  20.    for (var i = 0; i < fraction.length; i++) {

  21.        s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');

  22.    }

  23.    s = s || '整';

  24.    n = Math.floor(n);

  25.    for (var i = 0; i < unit[0].length && n > 0; i++) {

  26.        var p = '';

  27.        for (var j = 0; j < unit[1].length && n > 0; j++) {

  28.            p = digit[n % 10] + unit[1][j] + p;

  29.            n = Math.floor(n / 10);

  30.        }

  31.        s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;

  32.    }

  33.    return head + s.replace(/(零.)*零元/, '元')

  34.        .replace(/(零.)+/g, '零')

  35.        .replace(/^整$/, '零元整');

  36. };

11.Support

11.1 isSupportWebP

  1. /**

  2. *

  3. * @desc 判断浏览器是否支持webP格式图片

  4. * @return {Boolean}

  5. */

  6. function isSupportWebP() {

  7.    return !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0;

  8. }

12.Time

12.1 formatPassTime

  1. /**

  2. * @desc   格式化${startTime}距现在的已过时间

  3. * @param  {Date} startTime

  4. * @return {String}

  5. */

  6. function formatPassTime(startTime) {

  7.    var currentTime = Date.parse(new Date()),

  8.        time = currentTime - startTime,

  9.        day = parseInt(time / (1000 * 60 * 60 * 24)),

  10.        hour = parseInt(time / (1000 * 60 * 60)),

  11.        min = parseInt(time / (1000 * 60)),

  12.        month = parseInt(day / 30),

  13.        year = parseInt(month / 12);

  14.    if (year) return year + "年前"

  15.    if (month) return month + "个月前"

  16.    if (day) return day + "天前"

  17.    if (hour) return hour + "小时前"

  18.    if (min) return min + "分钟前"

  19.    else return '刚刚'

  20. }

12.2 formatRemainTime

  1. /**

  2. *

  3. * @desc   格式化现在距${endTime}的剩余时间

  4. * @param  {Date} endTime  

  5. * @return {String}

  6. */

  7. function formatRemainTime(endTime) {

  8.    var startDate = new Date(); //开始时间

  9.    var endDate = new Date(endTime); //结束时间

  10.    var t = endDate.getTime() - startDate.getTime(); //时间差

  11.    var d = 0,

  12.        h = 0,

  13.        m = 0,

  14.        s = 0;

  15.    if (t >= 0) {

  16.        d = Math.floor(t / 1000 / 3600 / 24);

  17.        h = Math.floor(t / 1000 / 60 / 60 % 24);

  18.        m = Math.floor(t / 1000 / 60 % 60);

  19.        s = Math.floor(t / 1000 % 60);

  20.    }

  21.    return d + "天 " + h + "小时 " + m + "分钟 " + s + "秒";

  22. }

13.Url

13.1 parseQueryString

  1. /**

  2. *

  3. * @desc   url参数转对象

  4. * @param  {String} url  default: window.location.href

  5. * @return {Object}

  6. */

  7. function parseQueryString(url) {

  8.    url = url == null ? window.location.href : url

  9.    var search = url.substring(url.lastIndexOf('?') + 1)

  10.    if (!search) {

  11.        return {}

  12.    }

  13.    return JSON.parse('{"' + decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')

  14. }

13.2 stringfyQueryString

  1. /**

  2. *

  3. * @desc   对象序列化

  4. * @param  {Object} obj

  5. * @return {String}

  6. */

  7. function stringfyQueryString(obj) {

  8.    if (!obj) return '';

  9.    var pairs = [];

  10.  

  11.    for (var key in obj) {

  12.        var value = obj[key];

  13.  

  14.        if (value instanceof Array) {

  15.            for (var i = 0; i < value.length; ++i) {

  16.                pairs.push(encodeURIComponent(key + '[' + i + ']') + '=' + encodeURIComponent(value[i]));

  17.            }

  18.            continue;

  19.        }

  20.  

  21.        pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));

  22.    }

  23.  

  24.    return pairs.join('&');

  25. }

14.Function

14.1 throttle

  1. /**

  2. * @desc   函数节流。

  3. * 适用于限制`resize`和`scroll`等函数的调用频率

  4. *

  5. * @param  {Number}    delay          0 或者更大的毫秒数。 对于事件回调,大约100或250毫秒(或更高)的延迟是最有用的。

  6. * @param  {Boolean}   noTrailing     可选,默认为false。

  7. *                                    如果noTrailing为true,当节流函数被调用,每过`delay`毫秒`callback`也将执行一次。

  8. *                                    如果noTrailing为false或者未传入,`callback`将在最后一次调用节流函数后再执行一次.

  9. *                                    (延迟`delay`毫秒之后,节流函数没有被调用,内部计数器会复位)

  10. * @param  {Function}  callback       延迟毫秒后执行的函数。`this`上下文和所有参数都是按原样传递的,

  11. *                                    执行去节流功能时,调用`callback`。

  12. * @param  {Boolean}   debounceMode   如果`debounceMode`为true,`clear`在`delay`ms后执行。

  13. *                                    如果debounceMode是false,`callback`在`delay` ms之后执行。

  14. *

  15. * @return {Function}  新的节流函数

  16. */

  17. function throttle(delay, noTrailing, callback, debounceMode) {

  18.  

  19.    // After wrapper has stopped being called, this timeout ensures that

  20.    // `callback` is executed at the proper times in `throttle` and `end`

  21.    // debounce modes.

  22.    var timeoutID;

  23.  

  24.    // Keep track of the last time `callback` was executed.

  25.    var lastExec = 0;

  26.  

  27.    // `noTrailing` defaults to falsy.

  28.    if (typeof noTrailing !== 'boolean') {

  29.        debounceMode = callback;

  30.        callback = noTrailing;

  31.        noTrailing = undefined;

  32.    }

  33.  

  34.    // The `wrapper` function encapsulates all of the throttling / debouncing

  35.    // functionality and when executed will limit the rate at which `callback`

  36.    // is executed.

  37.    function wrapper() {

  38.  

  39.        var self = this;

  40.        var elapsed = Number(new Date()) - lastExec;

  41.        var args = arguments;

  42.  

  43.        // Execute `callback` and update the `lastExec` timestamp.

  44.        function exec() {

  45.            lastExec = Number(new Date());

  46.            callback.apply(self, args);

  47.        }

  48.  

  49.        // If `debounceMode` is true (at begin) this is used to clear the flag

  50.        // to allow future `callback` executions.

  51.        function clear() {

  52.            timeoutID = undefined;

  53.        }

  54.  

  55.        if (debounceMode && !timeoutID) {

  56.            // Since `wrapper` is being called for the first time and

  57.            // `debounceMode` is true (at begin), execute `callback`.

  58.            exec();

  59.        }

  60.  

  61.        // Clear any existing timeout.

  62.        if (timeoutID) {

  63.            clearTimeout(timeoutID);

  64.        }

  65.  

  66.        if (debounceMode === undefined && elapsed > delay) {

  67.            // In throttle mode, if `delay` time has been exceeded, execute

  68.            // `callback`.

  69.            exec();

  70.  

  71.        } else if (noTrailing !== true) {

  72.            // In trailing throttle mode, since `delay` time has not been

  73.            // exceeded, schedule `callback` to execute `delay` ms after most

  74.            // recent execution.

  75.            //

  76.            // If `debounceMode` is true (at begin), schedule `clear` to execute

  77.            // after `delay` ms.

  78.            //

  79.            // If `debounceMode` is false (at end), schedule `callback` to

  80.            // execute after `delay` ms.

  81.            timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);

  82.        }

  83.  

  84.    }

  85.  

  86.    // Return the wrapper function.

  87.    return wrapper;

  88.  

  89. };

14.2 debounce

  1. /**

  2. * @desc 函数防抖

  3. * 与throttle不同的是,debounce保证一个函数在多少毫秒内不再被触发,只会执行一次,

  4. * 要么在第一次调用return的防抖函数时执行,要么在延迟指定毫秒后调用。

  5. * @example 适用场景:如在线编辑的自动存储防抖。

  6. * @param  {Number}   delay         0或者更大的毫秒数。 对于事件回调,大约100或250毫秒(或更高)的延迟是最有用的。

  7. * @param  {Boolean}  atBegin       可选,默认为false。

  8. *                                  如果`atBegin`为false或未传入,回调函数则在第一次调用return的防抖函数后延迟指定毫秒调用。

  9.                                    如果`atBegin`为true,回调函数则在第一次调用return的防抖函数时直接执行

  10. * @param  {Function} callback      延迟毫秒后执行的函数。`this`上下文和所有参数都是按原样传递的,

  11. *                                  执行去抖动功能时,,调用`callback`。

  12. *

  13. * @return {Function} 新的防抖函数。

  14. */

  15. var throttle = require('./throttle');

  16. function debounce(delay, atBegin, callback) {

  17.    return callback === undefined ? throttle(delay, atBegin, false) : throttle(delay, callback, atBegin !== false);

  18. };

封装

除了对上面这些常用函数进行封装, 最重要的是支持合理化的引入,这里我们使用 webpack统一打包成 UMD 通用模块规范,支持 webpack、 RequireJS、 SeaJS等模块加载器,亦或直接通过 <script>标签引入。

但这样,还是不能让人满意。因为完整引入整个库,略显浪费,我们不可能用到所有的函数。那么,就支持按需引入

1. 目录结构说明

  1.  .babelrc

  2.  .gitignore

  3.  .travis.yml

  4.  LICENSE

  5.  package.json

  6.  README.md

  7.  setCookie.js  // 拷贝到根路径的函数模块,方便按需加载

  8.  setScrollTop.js

  9.  stringfyQueryString.js

  10.   ...

  11.   ...

  12.  

  13. ├─min

  14.      outils.min.js  // 所有函数统一打包生成的全量压缩包

  15.      

  16. ├─script  // 本项目开发脚本目录

  17.      build.js  // 打包构建脚本

  18.      test.js  // 测试脚本

  19.      webpack.conf.js  // webpack打包配置文件

  20.      

  21. ├─src // 源码目录

  22.    index.js  // webpack入口文件

  23.    

  24.  ├─array

  25.        

  26.  ├─class

  27.        

  28.  ├─cookie

  29.        

  30.  ├─device

  31.        

  32.  ├─dom

  33.        

  34.  ├─keycode

  35.        

  36.  ├─object

  37.        

  38.  ├─random

  39.        

  40.  ├─regexp

  41.        

  42.  ├─string

  43.        

  44.  ├─support

  45.        

  46.  ├─time

  47.        

  48.  └─url

  49.          

  50. └─test // 测试用例目录

  51.      array.test.js

  52.      class.test.js

  53.      cookie.test.js

  54.      device.test.js

  55.      dom.test.js

  56.      index.html

  57.      keycode.test.js

  58.      object.test.js

  59.      random.test.js

  60.      regexp.test.js

  61.      string.test.js

  62.      support.test.js

  63.      time.test.js

  64.      url.test.js

  65.      

  66.    └─_lib // 测试所用到的第三方库

  67.            mocha.css

  68.            mocha.js

  69.            power-assert.js

2. 构建脚本

这里主要说明一下项目中 build.js 的构建过程 第一步,构建全量压缩包,先删除 min目录中之前的 outils.min.js,后通过 webpack打包并保存新的压缩包至 min目录中:

  1.    ......

  2.    ......

  3.    // 删除旧的全量压缩包

  4.    rm(path.resolve(rootPath, 'min', `${pkg.name}.min.js`), err => {

  5.        if (err) throw (err)

  6.        webpack(config, function (err, stats) {

  7.            if (err) throw (err)

  8.            building.stop()

  9.            process.stdout.write(stats.toString({

  10.                colors: true,

  11.                modules: false,

  12.                children: false,

  13.                chunks: false,

  14.                chunkModules: false

  15.            }) + '\n\n')

  16.            resolve()

  17.            console.log(chalk.cyan('  Build complete.\n'))

  18.        })

  19.    })

  20.    ......

  21.    ......

第二步,拷贝函数模块至根目录,先删除根目录中之前的函数模块,后拷贝 src下面一层目录的所有 js文件至根目录。这么做的目的是,拷贝到根路径,在引入的时候,直接 require('outils/<方法名>')即可,缩短引入的路径,也算是提高点效率。

  1. // 替换模块文件

  2.    ......

  3.    ......

  4.    // 先删除根目录中之前的函数模块

  5.    rm('*.js', err => {

  6.        if (err) throw (err)

  7.        let folderList = fs.readdirSync(path.resolve(rootPath, 'src'))

  8.        folderList.forEach((item, index) => {

  9.            // 拷贝`src`下面一层目录的所有`js`文件至根目录

  10.            copy(`src/${item}/*.js`, rootPath, function (err, files) {

  11.                if (err) throw err;

  12.                if (index === folderList.length - 1) {

  13.                    console.log(chalk.cyan('  Copy complete.\n'))

  14.                    copying.stop()

  15.                }

  16.            })

  17.        })

  18.    })

  19.    ......

  20.    ......

3. 书写测试用例

俗话说,不写测试用例的前端不是一个好程序员。那就不能怂,就是干。

但是因为时间关系,本项目暂时通过项目中的 test.js ,启动了一个 koa静态服务器,来加载 mocha网页端的测试页面,让笔者书写项目时,可以在本地对函数功能进行测试。 但是后续将使用 travis-ci配合 Github来做持续化构建,自动发布到 npm。改用 karma, mocha, power-assert做单元测试,使用 Coverage测试覆盖率。这一部分,后续更新。

这里给大家推荐一个好用的断言库 power-assert ,这个库记住 assert(value, [message])一个 API 就基本无敌,从此再也不用担心记不住断言库的 API。

本项目的所有测试用例都在 test目录下,大家可以作一定参考。

更新:单元测试,已使用 karma, mocha, power-assert,使用 Coverage测试覆盖率,并集成 travis-ci 配合 Github来做持续化构建,可以参考本项目的 travis配置文件 .travis.yml 和 karma的配置文件 karma.conf.js 。

发布

首先放到 Github托管一下,当然你也可以直接 fork 本项目,然后再加入你自己的函数。 以笔者项目,举个栗子:

1. 添加自己的函数

在 src目录下,新建分类目录或者选择一个分类,在子文件夹中添加函数模块文件(建议一个小功能保存为一个 JS 文件)。

  1. /**

  2. *

  3. * @desc   判断是否NaN

  4. * @param  {Any} value

  5. * @return {Boolean}

  6. */

  7. function isNaN(value) {    

  8.    return value !== value;

  9. };

  10.  

  11. modules.export = isNaN

然后记得在 src/index.js文件中暴露 isNaN函数

2. 单元测试

在 test文件新建测试用例

  1. describe('#isNaN()', function () {

  2.    it(`outils.isNaN(NaN) should return true`, function () {

  3.        assert(outils.isNaN(NaN))

  4.    })

  5.    it(`outils.isNaN('value') should return false`, function () {

  6.        assert.notEqual(outils.isNaN(NaN))

  7.    })

  8. })

~~然后记得在 test/index.html中引入之前创建的测试用例脚本。~~

3. 测试并打包

执行 npm run test,看所有的测试用例是否通过。如果没有问题,执行 npm run build构建,之后提交到个人的 github 仓库即可。

4. 发布到 npm

在 www.npmjs.com 注册账号,修改本地 package.json中的 name、 version、 author等信息,最后 npm publish就大功告成了。 注意:向 npm发包,要把镜像源切到 www.npmjs.com ,使用 cnpm等第三方镜像源会报错。

使用

1. 浏览器

直接下载 min目录下的 outils.min.js ,通过 <script>标签引入。

  1.  <script src="outils.min.js"></script>

  2.  <script>

  3.      var OS = outils.getOS()

  4.  </script>

注意: 本仓库代码会持续更新,如果你需要不同版本的增量压缩包或源码,请到 github Release 页面下载对应版本号的代码。

2.Webpack、RequireJS、SeaJS 等模块加载器

先使用 npm安装 outils

  1. $ npm install --save-dev outils

  1. // 完整引入

  2. const outils = require('outils')

  3. const OS = outils.getOS()

推荐使用方法

  1. // 按需引入require('outils/<方法名>')

  2. const getOS = require('outils/getOS')

  3. const OS = getOS()

当然,你的开发环境有 babel编译 ES6语法的话,也可以这样使用:

  1. import getOS from 'outils/getOS'

  2. // 或

  3. import { getOS } from "outils";

总结

这里只是简单封装,发布到 npm上,省去下次复制粘贴的功夫,或者直接 Goole 的时间。如果笔者的库中,没有你常用的函数,或者你有更好的建议,欢迎来本项目的 Github Issues 交流,如果觉得不错,欢迎 star 本项目。

当然,更好的建议是 fork 本项目,或者直接新建自己的项目,添加自己 想要的 、常用的 、记不住的 函数,甚至是可以抽象出来的功能,封装成自己顺手、熟悉的库。 这样才能打造出你自己的武器库,瞬间提高你的单兵作战(开发)能力。

工欲善其事必先利其器。有了属于自己的这把利器,希望加班也会变成奢望。O(∩_∩)O 哈哈~

 

原文:https://segmentfault.com/a/1190000011966867

posted @ 2017-12-15 09:16  越来越好888  阅读(204)  评论(0编辑  收藏  举报