打造自己的 JavaScript 库

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

常用函数汇总

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

1.Array

1.1 arrayEqual

/**
*
* @desc 判断两个数组是否相等
* @param {Array} arr1
* @param {Array} arr2
* @return {Boolean}
*/
function arrayEqual(arr1, arr2) {
    if (arr1 === arr2) return true ;
    if (arr1.length != arr2.length) return false ;
    for ( var i = 0 ; i < arr1.length; ++i) {
        if (arr1[i] !== arr2[i]) return false ;
   }
    return true ;
}

2.Class

2.1 addClass

/**
*
* @desc   为元素添加class
* @param  {HTMLElement} ele
* @param  {String} cls
*/

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

function addClass(ele, cls) {
    if (!hasClass(ele, cls)) {
       ele.className += ' ' + cls;
   }
}

2.2 hasClass

/**
*
* @desc 判断元素是否有某个class
* @param {HTMLElement} ele
* @param {String} cls
* @return {Boolean}
*/
function hasClass(ele, cls) {
    return ( new RegExp ( '(\\s|^)' + cls + '(\\s|$)' )).test(ele.className);
}

2.3 removeClass

/**
*
* @desc 为元素移除class
* @param {HTMLElement} ele
* @param {String} cls
*/

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

function removeClass(ele, cls) {
    if (hasClass(ele, cls)) {
        var reg = new RegExp ( '(\\s|^)' + cls + '(\\s|$)' );
       ele.className = ele.className.replace(reg, ' ' );
   }
}

3.Cookie

3.1 getCookie

/**
*
* @desc 根据name读取cookie
* @param  {String} name
* @return {String}
*/
function getCookie(name) {
    var arr = document.cookie.replace( /\s/ g, "" ).split( ';' );
    for ( var i = 0 ; i < arr.length; i++) {
        var tempArr = arr[i].split( '=' );
        if (tempArr[ 0 ] == name) {
            return decodeURIComponent(tempArr[ 1 ]);
       }
   }
    return '' ;
}

3.2 removeCookie

var setCookie = require ( './setCookie' );
/**
*
* @desc 根据name删除cookie
* @param  {String} name
*/
function removeCookie(name) {
    // 设置已过期,系统会立刻删除cookie
   setCookie(name, '1' , - 1 );
}

3.3 setCookie

/**
*
* @desc  设置Cookie
* @param {String} name
* @param {String} value
* @param {Number} days
*/
function setCookie(name, value, days) {
    var date = new Date ();
   date.setDate(date.getDate() + days);
   document.cookie = name + '=' + value + ';expires=' + date;
}

4.Device

4.1 getExplore

/**
*
* @desc 获取浏览器类型和版本
* @return {String}
*/
function getExplore() {
    var sys = {},
       ua = navigator.userAgent.toLowerCase(),
       s;
   (s = ua.match( /rv:([\d.]+)\) like gecko/ )) ? sys.ie = s[ 1 ]:
       (s = ua.match( /msie ([\d\.]+)/ )) ? sys.ie = s[ 1 ] :
       (s = ua.match( /edge\/([\d\.]+)/ )) ? sys.edge = s[ 1 ] :
       (s = ua.match( /firefox\/([\d\.]+)/ )) ? sys.firefox = s[ 1 ] :
       (s = ua.match( /(?:opera|opr).([\d\.]+)/ )) ? sys.opera = s[ 1 ] :
       (s = ua.match( /chrome\/([\d\.]+)/ )) ? sys.chrome = s[ 1 ] :
       (s = ua.match( /version\/([\d\.]+).*safari/ )) ? sys.safari = s[ 1 ] : 0 ;
    // 根据关系进行判断
    if (sys.ie) return ( 'IE: ' + sys.ie)
    if (sys.edge) return ( 'EDGE: ' + sys.edge)
    if (sys.firefox) return ( 'Firefox: ' + sys.firefox)
    if (sys.chrome) return ( 'Chrome: ' + sys.chrome)
    if (sys.opera) return ( 'Opera: ' + sys.opera)
    if (sys.safari) return ( 'Safari: ' + sys.safari)
    return 'Unkonwn'
}

4.2 getOS

/**
*
* @desc 获取操作系统类型
* @return {String}
*/
function getOS() {
    var userAgent = 'navigator' in window && 'userAgent' in navigator && navigator.userAgent.toLowerCase() || '' ;
    var vendor = 'navigator' in window && 'vendor' in navigator && navigator.vendor.toLowerCase() || '' ;
    var appVersion = 'navigator' in window && 'appVersion' in navigator && navigator.appVersion.toLowerCase() || '' ;

    if ( /mac/ i.test(appVersion)) return 'MacOSX'
    if ( /win/ i.test(appVersion)) return 'windows'
    if ( /linux/ i.test(appVersion)) return 'linux'
    if ( /iphone/ i.test(userAgent) || /ipad/ i.test(userAgent) || /ipod/ i.test(userAgent)) 'ios'
    if ( /android/ i.test(userAgent)) return 'android'
    if ( /win/ i.test(appVersion) && /phone/ i.test(userAgent)) return 'windowsPhone'
}

5.Dom

5.1 getScrollTop

/**
*
* @desc 获取滚动条距顶部的距离
*/
function getScrollTop() {
    return (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
}

5.2 offset

/**
*
* @desc  获取一个元素的距离文档(document)的位置,类似jQ中的offset()
* @param {HTMLElement} ele
* @returns { {left: number, top: number} }
*/
function offset(ele) {
    var pos = {
       left: 0 ,
       top: 0
   };
    while (ele) {
       pos.left += ele.offsetLeft;
       pos.top += ele.offsetTop;
       ele = ele.offsetParent;
   };
    return pos;
}

5.3 scrollTo

var getScrollTop = require ( './getScrollTop' );
var setScrollTop = require ( './setScrollTop' );
var requestAnimFrame = ( function () {
    return window.requestAnimationFrame ||
       window.webkitRequestAnimationFrame ||
       window.mozRequestAnimationFrame ||
        function (callback) {
           window.setTimeout(callback, 1000 / 60 );
       };
})();
/**
*
* @desc  在${duration}时间内,滚动条平滑滚动到${to}指定位置
* @param {Number} to
* @param {Number} duration
*/
function scrollTo(to, duration) {
    if (duration < 0 ) {
       setScrollTop(to);
        return
   }
    var diff = to - getScrollTop();
    if (diff === 0 ) return
    var step = diff / duration * 10 ;
   requestAnimationFrame(
        function () {
            if ( Math .abs(step) > Math .abs(diff)) {
               setScrollTop(getScrollTop() + diff);
                return ;
           }
           setScrollTop(getScrollTop() + step);
            if (diff > 0 && getScrollTop() >= to || diff < 0 && getScrollTop() <= to) {
                return ;
           }
           scrollTo(to, duration - 16 );
       });
}

5.4 setScrollTop

/**
*
* @desc 设置滚动条距顶部的距离
*/
function setScrollTop(value) {
   window.scrollTo( 0 , value);
    return value;
}

6.Keycode

6.1 getKeyName

var keyCodeMap = {
    8 : 'Backspace' ,
    9 : 'Tab' ,
    13 : 'Enter' ,
    16 : 'Shift' ,
    17 : 'Ctrl' ,
    18 : 'Alt' ,
    19 : 'Pause' ,
    20 : 'Caps Lock' ,
    27 : 'Escape' ,
    32 : 'Space' ,
    33 : 'Page Up' ,
    34 : 'Page Down' ,
    35 : 'End' ,
    36 : 'Home' ,
    37 : 'Left' ,
    38 : 'Up' ,
    39 : 'Right' ,
    40 : 'Down' ,
    42 : 'Print Screen' ,
    45 : 'Insert' ,
    46 : 'Delete' ,

    48 : '0' ,
    49 : '1' ,
    50 : '2' ,
    51 : '3' ,
    52 : '4' ,
    53 : '5' ,
    54 : '6' ,
    55 : '7' ,
    56 : '8' ,
    57 : '9' ,

    65 : 'A' ,
    66 : 'B' ,
    67 : 'C' ,
    68 : 'D' ,
    69 : 'E' ,
    70 : 'F' ,
    71 : 'G' ,
    72 : 'H' ,
    73 : 'I' ,
    74 : 'J' ,
    75 : 'K' ,
    76 : 'L' ,
    77 : 'M' ,
    78 : 'N' ,
    79 : 'O' ,
    80 : 'P' ,
    81 : 'Q' ,
    82 : 'R' ,
    83 : 'S' ,
    84 : 'T' ,
    85 : 'U' ,
    86 : 'V' ,
    87 : 'W' ,
    88 : 'X' ,
    89 : 'Y' ,
    90 : 'Z' ,

    91 : 'Windows' ,
    93 : 'Right Click' ,

    96 : 'Numpad 0' ,
    97 : 'Numpad 1' ,
    98 : 'Numpad 2' ,
    99 : 'Numpad 3' ,
    100 : 'Numpad 4' ,
    101 : 'Numpad 5' ,
    102 : 'Numpad 6' ,
    103 : 'Numpad 7' ,
    104 : 'Numpad 8' ,
    105 : 'Numpad 9' ,
    106 : 'Numpad *' ,
    107 : 'Numpad +' ,
    109 : 'Numpad -' ,
    110 : 'Numpad .' ,
    111 : 'Numpad /' ,

    112 : 'F1' ,
    113 : 'F2' ,
    114 : 'F3' ,
    115 : 'F4' ,
    116 : 'F5' ,
    117 : 'F6' ,
    118 : 'F7' ,
    119 : 'F8' ,
    120 : 'F9' ,
    121 : 'F10' ,
    122 : 'F11' ,
    123 : 'F12' ,

    144 : 'Num Lock' ,
    145 : 'Scroll Lock' ,
    182 : 'My Computer' ,
    183 : 'My Calculator' ,
    186 : ';' ,
    187 : '=' ,
    188 : ',' ,
    189 : '-' ,
    190 : '.' ,
    191 : '/' ,
    192 : '`' ,
    219 : '[' ,
    220 : '\\' ,
    221 : ']' ,
    222 : '\''
};
/**
* @desc 根据keycode获得键名
* @param  {Number} keycode
* @return {String}
*/
function getKeyName(keycode) {
    if (keyCodeMap[keycode]) {
        return keyCodeMap[keycode];
   } else {
       console.log( 'Unknow Key(Key Code:' + keycode + ')' );
        return '' ;
   }
};

7.Object

7.1 deepClone

/**
* @desc 深拷贝,支持常见类型
* @param {Any} values
*/
function deepClone(values) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if ( null == values || "object" != typeof values) return values;

    // Handle Date
    if (values instanceof Date ) {
       copy = new Date ();
       copy.setTime(values.getTime());
        return copy;
   }

    // Handle Array
    if (values instanceof Array ) {
       copy = [];
        for ( var i = 0 , len = values.length; i < len; i++) {
           copy[i] = deepClone(values[i]);
       }
        return copy;
   }

    // Handle Object
    if (values instanceof Object ) {
       copy = {};
        for ( var attr in values) {
            if (values.hasOwnProperty(attr)) copy[attr] = deepClone(values[attr]);
       }
        return copy;
   }

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

7.2 isEmptyObject

/**
*
* @desc   判断`obj`是否为空
* @param  {Object} obj
* @return {Boolean}
*/
function isEmptyObject(obj) {
    if (!obj || typeof obj !== 'object' || Array .isArray(obj))
        return false
    return ! Object .keys(obj).length
}

8.Random

8.1 randomColor

/**
*
* @desc 随机生成颜色
* @return {String}
*/
function randomColor() {
    return '#' + ( '00000' + ( Math .random() * 0x1000000 << 0 ).toString( 16 )).slice(- 6 );
}

8.2 randomNum 

/**
*
* @desc 生成指定范围随机数
* @param  {Number} min
* @param  {Number} max
* @return {Number}
*/
function randomNum(min, max) {
    return Math .floor(min + Math .random() * (max - min));
}

9.Regexp

9.1 isEmail

/**
*
* @desc   判断是否为邮箱地址
* @param  {String}  str
* @return {Boolean}
*/
function isEmail(str) {
    return /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/ .test(str);
}

9.2 isIdCard

/**
*
* @desc  判断是否为身份证号
* @param  {String|Number} str
* @return {Boolean}
*/
function isIdCard(str) {
    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.3 isPhoneNum

/**
*
* @desc   判断是否为手机号
* @param  {String|Number} str
* @return {Boolean}
*/
function isPhoneNum(str) {
    return /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/ .test(str)
}

9.4 isUrl

/**
*
* @desc   判断是否为URL地址
* @param  {String} str
* @return {Boolean}
*/
function isUrl(str) {
    return /[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/ i.test(str);
}

10.String

10.1 digitUppercase

/**
*
* @desc   现金额转大写
* @param  {Number} n
* @return {String}
*/
function digitUppercase(n) {
    var fraction = [ '角' , '分' ];
    var digit = [
        '零' , '壹' , '贰' , '叁' , '肆' ,
        '伍' , '陆' , '柒' , '捌' , '玖'
   ];
    var unit = [
       [ '元' , '万' , '亿' ],
       [ '' , '拾' , '佰' , '仟' ]
   ];
    var head = n < 0 ? '欠' : '' ;
   n = Math .abs(n);
    var s = '' ;
    for ( var i = 0 ; i < fraction.length; i++) {
       s += (digit[ Math .floor(n * 10 * Math .pow( 10 , i)) % 10 ] + fraction[i]).replace( /零./ , '' );
   }
   s = s || '整' ;
   n = Math .floor(n);
    for ( var i = 0 ; i < unit[ 0 ].length && n > 0 ; i++) {
        var p = '' ;
        for ( var j = 0 ; j < unit[ 1 ].length && n > 0 ; j++) {
           p = digit[n % 10 ] + unit[ 1 ][j] + p;
           n = Math .floor(n / 10 );
       }
       s = p.replace( /(零.)*零$/ , '' ).replace( /^$/ , '零' ) + unit[ 0 ][i] + s;
   }
    return head + s.replace( /(零.)*零元/ , '元' )
       .replace( /(零.)+/ g, '零' )
       .replace( /^整$/ , '零元整' );
};

11.Support

11.1 isSupportWebP

/**
*
* @desc 判断浏览器是否支持webP格式图片
* @return {Boolean}
*/
function isSupportWebP() {
    return !![].map && document.createElement( 'canvas' ).toDataURL( 'image/webp' ).indexOf( 'data:image/webp' ) == 0 ;
}

12.Time

12.1 formatPassTime

/**
* @desc   格式化${startTime}距现在的已过时间
* @param  {Date} startTime
* @return {String}
*/
function formatPassTime(startTime) {
    var currentTime = Date .parse( new Date ()),
       time = currentTime - startTime,
       day = parseInt(time / ( 1000 * 60 * 60 * 24 )),
       hour = parseInt(time / ( 1000 * 60 * 60 )),
       min = parseInt(time / ( 1000 * 60 )),
       month = parseInt(day / 30 ),
       year = parseInt(month / 12 );
    if (year) return year + "年前"
    if (month) return month + "个月前"
    if (day) return day + "天前"
    if (hour) return hour + "小时前"
    if (min) return min + "分钟前"
    else return '刚刚'
}

12.2 formatRemainTime

/**
*
* @desc   格式化现在距${endTime}的剩余时间
* @param  {Date} endTime  
* @return {String}
*/
function formatRemainTime(endTime) {
    var startDate = new Date (); //开始时间
    var endDate = new Date (endTime); //结束时间
    var t = endDate.getTime() - startDate.getTime(); //时间差
    var d = 0 ,
       h = 0 ,
       m = 0 ,
       s = 0 ;
    if (t >= 0 ) {
       d = Math .floor(t / 1000 / 3600 / 24 );
       h = Math .floor(t / 1000 / 60 / 60 % 24 );
       m = Math .floor(t / 1000 / 60 % 60 );
       s = Math .floor(t / 1000 % 60 );
   }
    return d + "天 " + h + "小时 " + m + "分钟 " + s + "秒" ;
}

13.Url

13.1 parseQueryString

/**
*
* @desc   url参数转对象
* @param  {String} url  default: window.location.href
* @return {Object}
*/
function parseQueryString(url) {
   url = url == null ? window.location.href : url
    var search = url.substring(url.lastIndexOf( '?' ) + 1 )
    if (!search) {
        return {}
   }
    return JSON.parse( '{"' + decodeURIComponent(search).replace( /"/ g, '\\"' ).replace( /&/ g, '","' ).replace( /=/ g, '":"' ) + '"}' )
}

13.2 stringfyQueryString

/**
*
* @desc   对象序列化
* @param  {Object} obj
* @return {String}
*/
function stringfyQueryString(obj) {
    if (!obj) return '' ;
    var pairs = [];

    for ( var key in obj) {
        var value = obj[key];

        if (value instanceof Array ) {
            for ( var i = 0 ; i < value.length; ++i) {
               pairs.push(encodeURIComponent(key + '[' + i + ']' ) + '=' + encodeURIComponent(value[i]));
           }
            continue ;
       }

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

    return pairs.join( '&' );
}

14.Function

14.1 throttle

/**
* @desc   函数节流。

* 适用于限制`resize`和`scroll`等函数的调用频率
*
* @param  {Number}    delay          0 或者更大的毫秒数。
对于事件回调,大约100或250毫秒(或更高)的延迟是最有用的。

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

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

*                                    如果noTrailing为false或者未传入,`callback`将在最后一次调用节流函数后再执行一次.
*                                    (延迟`delay`毫秒之后,节流函数没有被调用,内部计数器会复位)
* @param  {Function}  callback       延迟毫秒后执行的函数。
`this`上下文和所有参数都是按原样传递的,

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

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

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

*
* @return {Function}  新的节流函数
*/
function throttle(delay, noTrailing, callback, debounceMode) {

    // After wrapper has stopped being called, this timeout ensures that
    // `callback` is executed at the proper times in `throttle` and `end`
    // debounce modes.
    var timeoutID;

    // Keep track of the last time `callback` was executed.
    var lastExec = 0 ;

    // `noTrailing` defaults to falsy.
    if ( typeof noTrailing !== 'boolean' ) {
       debounceMode = callback;
       callback = noTrailing;
       noTrailing = undefined ;
   }

    // The `wrapper` function encapsulates all of the throttling / debouncing
    // functionality and when executed will limit the rate at which `callback`
    // is executed.
    function wrapper() {

        var self = this ;
        var elapsed = Number ( new Date ()) - lastExec;
        var args = arguments;

        // Execute `callback` and update the `lastExec` timestamp.
        function exec () {
           lastExec = Number ( new Date ());
           callback.apply( self , args);
       }

        // If `debounceMode` is true (at begin) this is used to clear the flag
        // to allow future `callback` executions.
        function clear() {
           timeoutID = undefined ;
       }

        if (debounceMode && !timeoutID) {
            // Since `wrapper` is being called for the first time and
            // `debounceMode` is true (at begin), execute `callback`.
            exec ();
       }

        // Clear any existing timeout.
        if (timeoutID) {
           clearTimeout(timeoutID);
       }

        if (debounceMode === undefined && elapsed > delay) {
            // In throttle mode, if `delay` time has been exceeded, execute
            // `callback`.
            exec ();

       } else if (noTrailing !== true ) {
            // In trailing throttle mode, since `delay` time has not been
            // exceeded, schedule `callback` to execute `delay` ms after most
            // recent execution.
            //
            // If `debounceMode` is true (at begin), schedule `clear` to execute
            // after `delay` ms.
            //
            // If `debounceMode` is false (at end), schedule `callback` to
            // execute after `delay` ms.
           timeoutID = setTimeout(debounceMode ? clear : exec , debounceMode === undefined ? delay - elapsed : delay);
       }

   }

    // Return the wrapper function.
    return wrapper;

};

14.2 debounce

/**
* @desc 函数防抖
* 与throttle不同的是,debounce保证一个函数在多少毫秒内不再被触发,只会执行一次,
* 要么在第一次调用return的防抖函数时执行,要么在延迟指定毫秒后调用。

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

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

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

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

                                   如果`atBegin`为true,回调函数则在第一次调用return的防抖函数时直接执行
* @param  {Function} callback      延迟毫秒后执行的函数。
`this`上下文和所有参数都是按原样传递的,

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

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

*/
var throttle = require ( './throttle' );
function debounce(delay, atBegin, callback) {
    return callback === undefined ? throttle(delay, atBegin, false ) : throttle(delay, callback, atBegin !== false );
};

封装

除了对上面这些常用函数进行封装, 最重要的是支持合理化的引入,这里我们使用 webpack 统一打包成 UMD 通用模块规范,支持 webpack 、 RequireJS 、 SeaJS 等模块加载器,亦或直接通过 <script> 标签引入。
但这样,还是不能让人满意。
因为完整引入整个库,略显浪费,我们不可能用到所有的函数。
那么,就支持 > 按需引入 吧

1.目录结构说明

│  .babelrc
│  .gitignore
│  .travis.yml
│  LICENSE
│   package .json
│  README.md
│  setCookie.js   // 拷贝到根路径的函数模块,方便按需加载
│  setScrollTop.js
│  stringfyQueryString.js
│   ...
│   ...
│  
├─min
│      outils.min.js   // 所有函数统一打包生成的全量压缩包
│      
├─script   // 本项目开发脚本目录
│      build.js   // 打包构建脚本
│      test.js   // 测试脚本
│      webpack.conf.js   // webpack打包配置文件
│      
├─src // 源码目录
│  │  index.js   // webpack入口文件
│  │  
│  ├─array
│  │      
│  ├─ class
│  │      
│  ├─cookie
│  │      
│  ├─device
│  │      
│  ├─dom
│  │      
│  ├─keycode
│  │      
│  ├─ object
│  │      
│  ├─random
│  │      
│  ├─regexp
│  │      
│  ├─ string
│  │      
│  ├─support
│  │      
│  ├─time
│  │      
│  └─url
│          
└─test // 测试用例目录
   │  array.test.js
   │   class .test.js
   │  cookie.test.js
   │  device.test.js
   │  dom.test.js
   │  index.html
   │  keycode.test.js
   │   object .test.js
   │  random.test.js
   │  regexp.test.js
   │   string .test.js
   │  support.test.js
   │  time.test.js
   │  url.test.js
   │  
   └─_lib // 测试所用到的第三方库
           mocha.css
           mocha.js
           power- assert .js

2.构建脚本

这里主要说明一下项目中 build.js 的构建过程 量压缩包,先删除 min 目录中之前的 outils.min.js ,后通过 webpack 打包并保存新的压缩包至 min 目录中:    ......
   ......
    // 删除旧的全量压缩包
   rm(path.resolve(rootPath, 'min' , `${pkg.name}.min.js` ), err => {
        if (err) throw (err)
       webpack(config, function (err, stats) {
            if (err) throw (err)
           building.stop()
           process.stdout.write(stats.toString({
               colors: true ,
               modules: false ,
               children: false ,
               chunks: false ,
               chunkModules: false
           }) + '\n\n' )
           resolve()
           console.log(chalk.cyan( '  Build complete.\n' ))
       })
   })
   ......
   ...... 第二步,拷贝函数模块至根目录,先删除根目录中之前的函数模块,后拷贝 src 下面一层目录的所有 js 文件至根目录。
这么做的目的是,拷贝到根路径,在引入的时候,直接 require('outils/<方法名>') 即可,缩短引入的路径,也算是提高点效率。
// 替换模块文件
   ......
   ......
    // 先删除根目录中之前的函数模块
   rm( '*.js' , err => {
        if (err) throw (err)
        let folderList = fs.readdirSync(path.resolve(rootPath, 'src' ))
       folderList.forEach((item, index) => {
            // 拷贝`src`下面一层目录的所有`js`文件至根目录
           copy( `src/${item}/*.js` , rootPath, function (err, files) {
                if (err) throw err;
                if (index === folderList.length - 1 ) {
                   console.log(chalk.cyan( '  Copy complete.\n' ))
                   copying.stop()
               }
           })
       })
   })
   ......
   ......

3.书写测试用例

俗话说,不写测试用例的前端不是一个好程序员。
那就不能怂,就是干。
但是因为时间关系,本项目暂时通过项目中的 test.js ,启动了一个 koa 静态服务器,来加载 mocha 网页端的测试页面,让笔者书写项目时,可以在本地对函数功能进行测试。
travis-ci 配合 Github 来做持续化构建,自动发布到 npm 。
改用 karma , mocha , power-assert 做单元测试,使用 Coverage 测试覆盖率。
这一部分,后续更新。
这里给大家推荐一个好用的断言库 power-assert ,这个库记住 assert(value,[message]) 一个API就基本无敌,从此再也不用担心记不住断言库的API。
本项目的所有测试用例都在 test 目录下,大家可以作一定参考。

发布

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

1.添加自己的函数

在 src 目录下,新建分类目录或者选择一个分类,在子文件夹中添加函数模块文件(建议一个小功能保存为一个JS文件)。
/**
*
* @desc   判断是否NaN
* @param  {Any} value
* @return {Boolean}
*/
function isNaN(value) {    
    return value !== value;
};

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

2.单元测试

在 test 文件新建测试用例 describe( '#isNaN()' , function () {
   it( `outils.isNaN(NaN) should return true` , function () {
        assert (outils.isNaN( NaN ))
   })
   it( `outils.isNaN('value') should return false` , function () {
        assert .notEqual(outils.isNaN( NaN ))
   })
}) 然后记得在 test/index.html 中引入之前创建的测试用例脚本。

3.测试并打包

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

4.发布到  npm

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

使用

1.浏览器

直接下载 min 目录下的 outils.min.js ,通过 <script> 标签引入。
  <script src = "outils.min.js" ></script>
  <script>
      var OS = outils.getOS()
  </script> 注意: 本仓库代码会持续更新,如果你需要不同版本的增量压缩包或源码,请到 github Release 页面下载对应版本号的代码。

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

先使用 npm 安装 outils 。
$ npm install --save-dev outils // 完整引入
const outils = require ( 'outils' )
const OS = outils.getOS() > 推荐使用方法 // 按需引入require('outils/<方法名>')
const getOS = require ( 'outils/getOS' )
const OS = getOS() 当然,你的开发环境有 babel 编译 ES6 语法的话,也可以这样使用: import getOS from 'outils/getOS'
// 或
import { getOS } from "outils" ;

总结

这里只是简单封装,发布到 npm 上,省去下次复制粘贴的功夫,或者直接Goole的时间。
如果笔者的库中,没有你常用的函数,或者你有更好的建议,欢迎来本项目的 Github Issues 交流,如果觉得不错,欢迎 star本项目。
当然,更好的建议是 fork 本项目,或者直接新建自己的项目,添加自己 > 想要的 、 常用的 、 记不住的 函数,甚至是可以抽象出来的功能,封装成自己顺手、熟悉的库。
这样才能打造出你自己的武器库,瞬间提高你的单兵作战(开发)能力。
工欲善其事必先利其器。