美文网首页
打造自己的 JavaScript 武器库

打造自己的 JavaScript 武器库

作者: Calvin李 | 来源:发表于2017-12-17 13:20 被阅读0次

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

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

    常用函数汇总

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

    1.Array

    1.1 arrayEqual

    /**

    *

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

    * @param {Array} arr1

    * @param {Array} arr2

    * @return {Boolean}

    */

    functionarrayEqual(arr1, arr2) {

    if(arr1 === arr2)returntrue;

    if(arr1.length != arr2.length)returnfalse;

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

    if(arr1[i] !== arr2[i])returnfalse;

    }

    returntrue;

    }

    2.Class

    2.1 addClass

    /**

    *

    * @desc   为元素添加class

    * @param  {HTMLElement} ele

    * @param  {String} cls

    */

    varhasClass =require('./hasClass');

    functionaddClass(ele, cls) {

    if(!hasClass(ele, cls)) {

    ele.className +=' '+ cls;

    }

    }

    2.2 hasClass

    /**

    *

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

    * @param {HTMLElement} ele

    * @param {String} cls

    * @return {Boolean}

    */

    functionhasClass(ele, cls) {

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

    }

    2.3 removeClass

    /**

    *

    * @desc 为元素移除class

    * @param {HTMLElement} ele

    * @param {String} cls

    */

    varhasClass =require('./hasClass');

    functionremoveClass(ele, cls) {

    if(hasClass(ele, cls)) {

    varreg =newRegExp('(\\s|^)'+ cls +'(\\s|$)');

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

    }

    }

    3.Cookie

    3.1 getCookie

    /**

    *

    * @desc 根据name读取cookie

    * @param  {String} name

    * @return {String}

    */

    functiongetCookie(name) {

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

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

    vartempArr = arr[i].split('=');

    if(tempArr[0] == name) {

    returndecodeURIComponent(tempArr[1]);

    }

    }

    return'';

    }

    3.2 removeCookie

    varsetCookie =require('./setCookie');

    /**

    *

    * @desc 根据name删除cookie

    * @param  {String} name

    */

    functionremoveCookie(name) {

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

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

    }

    3.3 setCookie

    /**

    *

    * @desc  设置Cookie

    * @param {String} name

    * @param {String} value

    * @param {Number} days

    */

    functionsetCookie(name, value, days) {

    vardate =newDate();

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

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

    }

    4.Device

    4.1 getExplore

    /**

    *

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

    * @return {String}

    */

    functiongetExplore() {

    varsys = {},

    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}

    */

    functiongetOS() {

    varuserAgent ='navigator'inwindow &&'userAgent'innavigator && navigator.userAgent.toLowerCase() ||'';

    varvendor ='navigator'inwindow &&'vendor'innavigator && navigator.vendor.toLowerCase() ||'';

    varappVersion ='navigator'inwindow &&'appVersion'innavigator && 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 获取滚动条距顶部的距离

    */

    functiongetScrollTop() {

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

    }

    5.2 offset

    /**

    *

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

    * @param {HTMLElement} ele

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

    */

    functionoffset(ele) {

    varpos = {

    left:0,

    top:0

    };

    while(ele) {

    pos.left += ele.offsetLeft;

    pos.top += ele.offsetTop;

    ele = ele.offsetParent;

    };

    returnpos;

    }

    5.3 scrollTo

    vargetScrollTop =require('./getScrollTop');

    varsetScrollTop =require('./setScrollTop');

    varrequestAnimFrame = (function() {

    returnwindow.requestAnimationFrame ||

    window.webkitRequestAnimationFrame ||

    window.mozRequestAnimationFrame ||

    function(callback) {

    window.setTimeout(callback,1000/60);

    };

    })();

    /**

    *

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

    * @param {Number} to

    * @param {Number} duration

    */

    functionscrollTo(to, duration) {

    if(duration <0) {

    setScrollTop(to);

    return

    }

    vardiff = to - getScrollTop();

    if(diff ===0)return

    varstep = 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 设置滚动条距顶部的距离

    */

    functionsetScrollTop(value) {

    window.scrollTo(0, value);

    returnvalue;

    }

    6.Keycode

    6.1 getKeyName

    varkeyCodeMap = {

    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}

    */

    functiongetKeyName(keycode) {

    if(keyCodeMap[keycode]) {

    returnkeyCodeMap[keycode];

    }else{

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

    return'';

    }

    };

    7.Object

    7.1 deepClone

    /**

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

    * @param {Any} values

    */

    functiondeepClone(values) {

    varcopy;

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

    if(null== values ||"object"!=typeofvalues)returnvalues;

    // Handle Date

    if(valuesinstanceofDate) {

    copy =newDate();

    copy.setTime(values.getTime());

    returncopy;

    }

    // Handle Array

    if(valuesinstanceofArray) {

    copy = [];

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

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

    }

    returncopy;

    }

    // Handle Object

    if(valuesinstanceofObject) {

    copy = {};

    for(varattrinvalues) {

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

    }

    returncopy;

    }

    thrownewError("Unable to copy values! Its type isn't supported.");

    }

    7.2 isEmptyObject

    /**

    *

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

    * @param  {Object} obj

    * @return {Boolean}

    */

    functionisEmptyObject(obj) {

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

    returnfalse

    return!Object.keys(obj).length

    }

    8.Random

    8.1 randomColor

    /**

    *

    * @desc 随机生成颜色

    * @return {String}

    */

    functionrandomColor() {

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

    }

    8.2 randomNum

    /**

    *

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

    * @param  {Number} min

    * @param  {Number} max

    * @return {Number}

    */

    functionrandomNum(min, max) {

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

    }

    9.Regexp

    9.1 isEmail

    /**

    *

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

    * @param  {String}  str

    * @return {Boolean}

    */

    functionisEmail(str) {

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

    }

    9.2 isIdCard

    /**

    *

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

    * @param  {String|Number} str

    * @return {Boolean}

    */

    functionisIdCard(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}

    */

    functionisPhoneNum(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}

    */

    functionisUrl(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}

    */

    functiondigitUppercase(n) {

    varfraction = ['角','分'];

    vardigit = [

    '零','壹','贰','叁','肆',

    '伍','陆','柒','捌','玖'

    ];

    varunit = [

    ['元','万','亿'],

    ['','拾','佰','仟']

    ];

    varhead = n <0?'欠':'';

    n =Math.abs(n);

    vars ='';

    for(vari =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(vari =0; i < unit[0].length && n >0; i++) {

    varp ='';

    for(varj =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;

    }

    returnhead + s.replace(/(零.)*零元/,'元')

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

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

    };

    11.Support

    11.1 isSupportWebP

    /**

    *

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

    * @return {Boolean}

    */

    functionisSupportWebP() {

    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}

    */

    functionformatPassTime(startTime) {

    varcurrentTime =Date.parse(newDate()),

    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)returnyear +"年前"

    if(month)returnmonth +"个月前"

    if(day)returnday +"天前"

    if(hour)returnhour +"小时前"

    if(min)returnmin +"分钟前"

    elsereturn'刚刚'

    }

    12.2 formatRemainTime

    /**

    *

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

    * @param  {Date} endTime

    * @return {String}

    */

    functionformatRemainTime(endTime) {

    varstartDate =newDate();//开始时间

    varendDate =newDate(endTime);//结束时间

    vart = endDate.getTime() - startDate.getTime();//时间差

    vard =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);

    }

    returnd +"天 "+ h +"小时 "+ m +"分钟 "+ s +"秒";

    }

    13.Url

    13.1 parseQueryString

    /**

    *

    * @desc   url参数转对象

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

    * @return {Object}

    */

    functionparseQueryString(url) {

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

    varsearch = url.substring(url.lastIndexOf('?') +1)

    if(!search) {

    return{}

    }

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

    }

    13.2 stringfyQueryString

    /**

    *

    * @desc   对象序列化

    * @param  {Object} obj

    * @return {String}

    */

    functionstringfyQueryString(obj) {

    if(!obj)return'';

    varpairs = [];

    for(varkeyinobj) {

    varvalue = obj[key];

    if(valueinstanceofArray) {

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

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

    }

    continue;

    }

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

    }

    returnpairs.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}  新的节流函数

    */

    functionthrottle(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.

    vartimeoutID;

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

    varlastExec =0;

    // `noTrailing` defaults to falsy.

    if(typeofnoTrailing !=='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.

    functionwrapper() {

    varself=this;

    varelapsed =Number(newDate()) - lastExec;

    varargs = arguments;

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

    functionexec() {

    lastExec =Number(newDate());

    callback.apply(self, args);

    }

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

    // to allow future `callback` executions.

    functionclear() {

    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();

    }elseif(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.

    returnwrapper;

    };

    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} 新的防抖函数。

    */

    varthrottle =require('./throttle');

    functiondebounce(delay, atBegin, callback) {

    returncallback ===undefined? throttle(delay, atBegin,false) : throttle(delay, callback, atBegin !==false);

    };

    封装

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

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

    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)

    letfolderList = fs.readdirSync(path.resolve(rootPath,'src'))

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

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

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

    if(err)throwerr;

    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}

    */

    functionisNaN(value) {

    returnvalue !== 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就大功告成了。

    注意:向

    npm发包,要把镜像源切到 www.npmjs.com ,使用cnpm等第三方镜像源会报错。

    使用

    1.浏览器

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

    varOS = outils.getOS()

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

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

    先使用npm安装outils。

    $ npm install --save-dev outils

    // 完整引入

    constoutils =require('outils')

    constOS = outils.getOS()

    推荐使用方法

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

    constgetOS =require('outils/getOS')

    constOS = getOS()

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

    importgetOSfrom'outils/getOS'

    // 或

    import{ getOS }from"outils";

    总结

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

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

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

    相关文章

      网友评论

          本文标题:打造自己的 JavaScript 武器库

          本文链接:https://www.haomeiwen.com/subject/eoqtixtx.html