打造自己的 JavaScript 武器库

前言

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

常用函数汇总

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

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

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

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

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

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

}

s=s||'整';

n=Math.floor(n);

for(vari=0;i0;i++){

varp='';

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

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目录下,大家可以作一定参考。

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

发布

首先放到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";

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

推荐阅读更多精彩内容