npm package: @instaceof/b-util
Description: common use js tools lib
Author: bai
Latest version: 2.0.0

##结构

# .eslintignore  eslint 验证过滤

## Install

```bash
npm i @instaceof/b-util  
```

[comment]: <> (npm使用)
```javascript


//1、
import {isArray,isString} from '@instaceof/b-util';
console.log(isArray([])); // ture
console.log(isString('String')); // ture

//2、
import B from '@instaceof/b-util';
console.dir(B);
console.log(B.isArray([])); // ture
console.log(B.isString('String')); // ture

//3、
 import isArray from '@instaceof/b-util/isArray';  // [each,inArray,indexArray,lastIndex,addDays,addYear,formatDate,getweek,now,nowTick,later]
 import isString from '@instaceof/b-util/isString';
 console.log(isArray([])); // ture
 console.log(isString('String')); // ture

//4、
import isNo from '@instaceof/b-util/isNo';  //  [arrayModel,dateModel,functionModel,isNo,link,media,objectModel,string,validate]
console.log(isNo)
console.log(isNo.isArray([])); // ture
console.log(isNo.isString('String')); // ture

```
examples
``` examples
src
├── arrayModel
│   ├── each.js
│   ├── inArray.js
│   ├── indexArray.js
│   └── lastIndex.js
├── dateModel
│   ├── addDays.js
│   ├── addYear.js
│   ├── formatDate.js
│   ├── getweek.js
│   ├── now.js
│   └── nowTick.js
├── functionModel
│   └── later.js
├── isNo
│   ├── is.js
│   ├── isArray.js
│   ├── isBoolean.js
│   ├── isDate.js
│   ├── isEmptyObject.js
│   ├── isFunction.js
│   ├── isHttps.js
│   ├── isNull.js
│   ├── isNumber.js
│   ├── isObject.js
│   ├── isRegExp.js
│   ├── isString.js
│   └── isUndefined.js
├── link
│   ├── cookie.js
│   ├── localStorage.js
│   ├── sessionStorage.js
│   ├── uri.js
│   ├── urlAddParam.js
│   ├── urlDecode.js
│   └── urlEncode.js
├── media
│   └── UA.js
├── objectModel
│   ├── _mix.js
│   ├── cleanObject.js
│   ├── clone.js
│   ├── keys.js
│   └── mix.js
├── string
│   └── strTemplate.js
└── validate
    ├── isBankCard.js
    ├── isIdCard.js
    ├── isPhoneNum.js
    └── validateMobile.js
```
## API
[See this dir](https://github.com/instanceofs/B.js/tree/master/dist) 
```
├── arrayModel
          /**
           * 循环操作
           * @param object
           * @param fn
           * @param context
           * @returns {*}
           */
│   ├── each.js
          /**
           * 检查是否包含在数组中
           * @param item
           * @param arr
           * @returns {boolean}
           * inArray(item,arr)
           */
│   ├── inArray.js
          /**
           * 检查指定的参数在数组中的位置  不存在 返回-1
           * @param item
           * @param arr
           * @param fromIndex
           * @returns {*}
           * indexArray(item, arr, fromIndex)
           **/
│   ├── indexArray.js
           /**
            * 与 index类似 arr倒序计算位置
            * @param item
            * @param arr
            * @param fromIndex
            * @returns {*}
            * lastIndex (item, arr, fromIndex)
            */
│   └── lastIndex.js
├── dateModel
            /**
             * 添加天数
             * @param date
             * @param days
             * @returns {*}
             * addDays(date, days)  
             */
│   ├── addDays.js
            /**
             * 添加年
             * @param date
             * @param days
             * @returns {*}
             * addYear(date, year)
             */
│   ├── addYear.js
            /**
             * 格式化时间
             * @param date
             * @param strFormat   //yyyy.MM.dd hh:mm:ss q   月(M)、日(d)、小时(h)、分(m) 、秒(s)、  毫秒(S) 季度(q)
             * @param sms  默认秒  // ms 毫秒  s 秒
             * @returns {*}
             * formatDate(date, strFormat, sms)
             */
│   ├── formatDate.js
            /**
             * 获取当前星期日期
             * @param date
             * @param isChEn  // 默认中文  'en'  英文
             * @returns {string}
             * getweek (date, isChEn)
             */
│   ├── getweek.js
            /**
             * 现在的时间
             * @returns {Date}
             * now()
             */
│   ├── now.js
            /**
             * 现在的时间
             * @returns {Date}
             * now()
             */
│   └── nowTick.js
├── functionModel
        /**
         * 定时器
         * @param method
         * @param time
         * @param isInterval
         * @param context
         * @param data
         * @returns {{cancel: cancel, timer: number, isInterval: *}}
         * timer:0   
         * timer= later(()=>{console.log(500) }, 500); // 500毫秒后执行  
         * timer= later(() => {}, 1000, true);  // 1000秒 循环执行
         * timer.cancel(); // 关闭定时器
         */
│   └── later.js
├── isNo
│   ├── is.js
          /**
           * 数组类型判断
           * @param obj
           * @returns {*|boolean}
           * isArray(obj)
           */      
│   ├── isArray.js
            /**
             * 布尔类型判断
             * @param obj
             * @returns {boolean|*|Boolean}
             * isBoolean(obj)  
             */
│   ├── isBoolean.js
            /**
             * 时间类型判断
             * @param obj
             * @returns {boolean|*|Boolean}
             * isDate(obj)
             */
│   ├── isDate.js
            /**
             * 对象是否为空判断
             * @param obj
             * @returns {boolean|*}
             * isEmptyObject(obj)
             */
│   ├── isEmptyObject.js
            /**
             * fun 类型判断
             * @param obj
             * @returns {*|boolean}
             * isFunction(obj)
             */
│   ├── isFunction.js
            /**
             * 判断是否是https地址
             * @param obj // 默认 window.document.location.protocol
             * @returns {boolean}
             * isHttps(obj)
             */
│   ├── isHttps.js
            /**
             * null类型判断
             * @param obj
             * @returns {*|boolean}
             * isNull(obj)
             */
│   ├── isNull.js
            /**
             * 数字类型判断
             * @param obj
             * @returns {*|boolean}
             * isNumber(obj)
             */
│   ├── isNumber.js
            /**
             * 对象类型判断
             * @param obj
             * @returns {*|boolean}
             * isObject(obj)
             */
│   ├── isObject.js
            /**
             * 是否是正则表达式判断
             * @param obj
             * @returns {*|boolean}
             * isRegExp(obj)
             */
│   ├── isRegExp.js
            /**
             * 字符串类型判断
             * @param obj
             * @returns {*|boolean}
             * isString(obj)
             */
│   ├── isString.js
            /**
             * undefined 类型判断
             * @param obj
             * @returns {*|boolean}
             * isUndefined(obj)
             */
│   └── isUndefined.js
├── link
            /**
             * cookie操作
             * @type {{set: cookie.set, get: cookie.get, clear: cookie.clear}}
             * cookie.set(name, value, minutes, domain)  
             * cookie.get(name)
             * cookie.clear(name, domain)
             */
│   ├── cookie.js
            /**
             * 本地储存-浏览器关闭销毁
             * @type {{set: set, get: (function(*): any|string), clear: clear}}
             * sessionStorage.set(name, val)
             * sessionStorage.get(name)
             * sessionStorage.clear(name)
             */
│   ├── localStorage.js
            /**
             * 本地储存-永久
             * @type {{set: set, get: (function(*=): any|string), clear: clear}}
             * localStorage.set(name, val)
             * localStorage.get(name)
             * localStorage.clear(name)
             */
│   ├── sessionStorage.js
            /**
             * 获取页面参数
             * @param url // 默认 location.href
             * @returns {*}
             * uri(url)
             * uri() // 全部参数
             * uri().name  // 获取指定name参数
             */
│   ├── uri.js
            /**
             * 传入对象返回url参数
             * @param data
             * @param iSquestionMark // true 默认带 ？   false 不带？
             * @returns {string|string|string}
             * urlAddParam({url1:'url1',url2:'url2'})
             */
│   ├── urlAddParam.js
            /**
             * url解码
             * @param  url
             * @param {String} url 这是url的一部分被解码.
             * @return {String} 解码的url部分字符串
             *  urlAddParam=(data, iSquestionMark=true)
             * urlAddParam({url1:'url1',url2:'url2'})
             */
│   ├── urlDecode.js
            /**
             * url编码
             * @param  url
             * @return {String} 已编码的url部分字符串
             * urlEncode(url)
             */
│   └── urlEncode.js
├── media
        /**
         * 终端识别
         * @type {{trident: undefined, opera: undefined, chrome: undefined, safari: undefined, os: undefined, phantomjs: undefined, firefox: undefined, android: undefined, mobile: undefined, nodejs: undefined, webkit: undefined, ios: undefined, iphone: undefined, ipod: undefined, core: undefined, gecko: undefined, shell: undefined, ipad: undefined, ie: undefined, presto: undefined, ieMode: undefined}}   
         * UA() 
         */
│   └── UA.js
├── objectModel
            /**
             * 对象混合
             * @param target
             * @param resource
             * @private
             * _mix(target, resource)
             */
│   ├──  _mix.js
        /**
         * 扩展
         * @param target    当前对象
         * @param resource  资源对象
         * @param overwrite 是否重写
         * @param whiteList 白名单
         * @param deep      是否深度复制
         * mix (target, resource, overwrite, whiteList, deep)
         */
│   ├──  mix.js
            /**
             * 去除对象中value为空(null,undefined,'')的属性,举个栗子：
             * @param object
             * @returns {{}|*}
             * B.cleanObject({"name":'','go':'123456'}
             */
│   ├── cleanObject.js
            /**
             * 克隆对象
             * @param obj
             * @returns {*}
             * clone(obj)
             */
│   ├── clone.js
            /**
             * 获取对象属性名
             * keys(obj)
             */
│   └── keys.js
├── string
            /**
             * 待删除 
             * 替换模块字符串中的占位符
             * ，例如s = <span id="aaa">{{a}}</span>
             * val = {a:111}
             * 结果为：<span id="aaa">111</span>
             * 如果，
             * val = [{a:111}, {a:222}]
             * split = <br/>
             * 结果为：
             * <span id="aaa">111</span><br/><span id="aaa">222</span>
             *
             * @param {string} s 模板字符串，例如<span>{{b}}</span>
             * @param {Object|array} val 值
             * @param {string} split 数组分割字符
             */
│   └── strTemplate.js
└── validate
    /**
     * 国际标准的银行卡号（储蓄卡和信用卡），注意：不包括各个银行的企业账户
     * @param str
     * @returns {boolean}
     * isIdCard(str)
     */
    ├── isBankCard.js
    /**
     * 中国的身份证号码
     * isIdCard(str)
     */
    ├── isIdCard.js
    /**
     * 中国三大运营商的电话号码
     * isPhoneNum(str)
     */
    ├── isPhoneNum.js
    /**
     * 手机号码格式验证
     * @param rule
     * @param value
     * @param callback
     * validateMobile(rule, value, callback)
     */
    └── validateMobile.js
```



# bai-util.js
[![npm](https://www.dgg.red/img/logo-dark.ad1e94fc.png)](https://www.npmjs.com/package/@instaceof/b-util)


A small JavaScript utility library.   

Package with `rollup.js`, exported as `iife amd cmd cjs umd es6`  module.  

Support most modern browsers, not include `IE`. Support Node 8+.  


框架搭建教程：<a href="https://www.dgg.red/">打造一个类似于lodash的现代前端工具库</a>



## API
[See this dir](https://github.com/instanceofs/B.js/tree/master/dist)    



## License

Released under the MIT Licenses.

参考博客：https://www.dgg.red/

```
一、分析借鉴目前最主流的前端工具库

我分析了github上多个前端工具库的设计，以lodash为例说明：

lodash的工程，有master、npm、npm-package、amd分支，以及 多种类型的tag：

4.17.10（umd）

4.17.10-npm

4.17.10-es

4.17.10-amd

这 4个分支 + 4个tag 的代码甚至工程结构，都不太一样。

我目前还看不明白它这么多套代码，是分开维护的，还是只维护一套，其他都是自动生成的？

因为所有8个分支的配置我都检查过了，并没有发现自动生成多份代码的相关配置。

而且lodash工程里面并没有babel的配置，难道lodash各个版本的代码是分开维护的？



本来想模仿lodash的工程脚手架，无奈看了半天看不懂。但是它的功能我基本清楚了：

1、umd模块，分为 lodash-core.js 和 lodash.js 、lodash-fp.js 三部分，提供了 未压缩包和压缩包（貌似没有看到source map）。

  lodash-fp 即 （Functional Programming）函数式编程，对于_.map等部分方法，lodash提供了FP版本。

2、npm模块（也是CommonJS版），有各个主功能模块(比如array)，都是require和module.export形式，除了lodash.js提供了min压缩版，其他都是源码无压缩。

3、AMD版，全部都是源码无压缩，有主功能模块(比如array)，都是define引入/导出模式，但是没有lodash.js统一入口模块。

4、es模块，全部都是源码无压缩，主功能模块全部提供了 default引入形式（例如array.default.js）以便支持 import array from 'lodash/array'。

5、npm模块，分成很多子项目，每一个函数都可以单独install。



除了lodash，我还分析了vue和react的工程构建方式，并学习了rollup、jest等。



二、设计前端工具库



我想达到的目的：

1、要支持 es6、node-cjs 和 浏览器 script直接引入，暂时不单独考虑支持amd；

2、只维护一套代码，其他均有工具自动生成；

3、浏览器版本的，支持source map，其他版本都是源码形式提供；

4、cjs、es6版本，支持按函数和主功能模块(例如array、date)引入，es6版本还提供对import xxx 这种形式的支持；

5、浏览器版本的，暂时不分模块，只能支持全部引入，后面根据情况可以提供core等精简版本。



关于源码的书写和维护：

目前大多数工具库，都是用的es6语法，我看vue源码是用的带type类型的es6（好像叫flow），lodash源码好像是es6(master分支)，而react用的好像也是自家的flow，只有谷歌的angular2用的typescript。

所以我考虑采用 es6来编写，但不排除以后换成ts。



具体做法：



使用es6语法来编写工具类，并把源代码原原本本提供出去，同时生成一份umd格式的，包含源码、压缩包和source map。

整个target(dist)内容示例如下：

// 所有函数
isArray.js
isString.js
...
collection.js // 主功能模块
collection.default.js // xxx.default.js用于支持es6的直接import
...
bai-util.esm.js // 主模块es源码，相当于 indexArray.js、main.js
bai-util.esm.default.js // xxx.default.js用于支持es6的直接import xxx from 'xxx.js'，并且用它来生成umd版本
bai-util.js // umd版本的源码，根据bai-util.esm.default.js自动生成
bai-util.min.js // umd版本压缩后的代码
bai-util.min.map // umd版本压缩代码的source map


xxx.default.js 说明

以collection.js为例：

collection.default.js代码如下：

import toMapKey from './toMapKey.js';
import toMapValue from './toMapValue.js';
// 支持单个引入 import { toMapKey, toMapValue } from 'collection.default'
// 也可以像这样全部引入 import * as coll from 'collection.default';
export default {
toMapKey,
toMapValue,
}
collection.js代码如下：

// 支持单个引入 import { toMapKey, toMapValue } from 'collection'
export { default as toMapKey } from './toMapKey.js';
export { default as toMapValue } from './toMapValue.js';
 
// 支持default（默认）引入 import coll from 'collection'
export { default } from './collection.default.js';
即 collection.default.js 只是供 collection.js 来引入的，其作用是为它提供 export default 的功能，以支持能直接 import coll from 'collection'。



上面是target(dist)的目录，那么源码src目录应该是怎样呢？

维护源码的时候，应该分模块，比如 集合相关的工具函数，放到 src/collection/ 目录下

src / 

|--collection / 

|  |--- toMapValue.js

|  |--- toMapKey.js

|  |---  ......

|--array / 

|-- .....

|--bai-util.js

|_______________

构建的时候，将所有模块都拷贝到target(dist)目录下，同时自动生成各模块的主module和总module。



关于构建工具

经我广泛的研究，结论如下：

1、应用类的工程，用webpack来打包；

2、工具类的工程，用rollup来打包，例如vue和react，都是用的rollup来打包。



所以，这个工程，决定用rollup来打包，而不是webpack。

rollup打包出来，代码更干净，就像自己写的代码一样，而webpack打包出来，会添加很多其他的代码。



其他功能

1、单元测试

使用jest来做单元测试。



2、持续集成

使用 travis。



git代码地址：https://github.com/instanceofs/B.js

npm仓库地址：https://www.npmjs.com/package/@instaceof/b-util



三、架构和技术说明



技术列表如下：



核心技术：

babel
rollup
jest
terser
eslint


辅助技术

cross-env
commitlint // 检查git commit，要求其符合规范
semantic-release
husky // 拦截不符合规范的git commit和push
travis-deploy-once
 
codecov
jshint
editorconfig
browserslist


技术说明：



使用rollup构建

配置在根目录下面的 rollup.config.js 



在使用 rollup 构建之前，会先准备打包文件，会执行node build/prepare

build/prepare.js的基本作用是 把 src 下面的文件拷贝 到 tmp 目录，同时生成 子模块的js，例如 bai-util.esm.js、collection.js、collection.default.js

同时，会修正js中import的路径。



需要说明的是，rollup.config.js配置中的

globals(),

builtins(),

localResolve(),

resolve()

commonjs()

这些应该暂时还没用到。

export出的配置，是一个数组，不同的input文件，输出配置不一样，

例如 对于 tmp/main.js （实际上为bai-util.esm.js）文件，输出了 umd、iife、cjs 三种格式，

同时，在bai-util.esm.js的基础上，再次转换成  bai-util.es.js （es6 module的es5语法的文件），之所以要转换成 es5语法，其初衷是想它被其他模块引入时，不需要babel转换也能直接使用，bai-util.es.js会被当做本js库的package.json的main入口。（其实，我也不确定这样做到底有多少意义），具体参见这篇文章的描述：https://loveky.github.io/2018/02/26/tree-shaking-and-pkg.module/



其配置文档，参见：

深入学习rollup来进行打包

JS打包工具rollup——完全入门指南

使用模块化工具打包自己开发的JS库

https://rollupjs.org/guide/zh#npm-packages

https://github.com/rollup/rollup



使用terser压缩es6语法的 js

terser是uglify-es6的增强版，因为uglify不支持es6，而uglify-es6又停止更新了，故推出了terser

terser支持和rollup集成，但是我在rollup打包过程不包含js压缩，为更灵活的控制 js压缩，我单独写了个脚本 ，执行node build/minify来处理压缩。



我是用的terser默认的配置，只是添加了 sourcemap的生成。



terser使用文档如下：

https://github.com/terser-js/terser



babel 及 browserslist 配置

rollup构建时，引入了  rollup-plugin-babel 插件，会调用 babel 对js进行处理。

babel的配置文件为：.babelrc.js

我使用 的是 babel 7（@babel/core@7.2.0），其配置和 babel 6有很大的不一样。

目前，只配置了一个babel-preset-env，其说明参见：

https://babeljs.io/docs/en/babel-preset-env/#how-it-works

https://www.babeljs.cn/docs/setup#installation

另外，babel 默认使用 .browserslistrc 的配置，其内容如下：

> 5%
current node
not dead
等价于 在package.json中配置 [ "browserslist": "> 5%, current node, not dead" ]

对于browserslist的使用，参见其说明文档：https://github.com/browserslist/browserslist



jest 单元测试配置

配置放在 jest.config.js 文件中

主要设置了 "verbose": true 以报告每个测试的执行情况，显示执行时间。

其配置文档参见：https://jestjs.io/docs/zh-Hans/configuration



注意，jest 需要依赖 babel-jest，要依赖babel 6，需要安装 babel 6 和 babel 7的桥接。

babel-jest，文档：https://www.npmjs.com/package/babel-jest

文档中说，如果使用babel 7的话，需要安装babel 6~7的桥接版本'babel-core@^7.0.0-bridge'



单元测试的代码，统一放在了 __test__ 目录下，这个是 jest 默认扫描的目录之一（该目录下面的js文件都会执行），无需配置。



注意，jest 测试代码，我也用的是 es6语法。



IDE（VS Code）使用 eslint 插件检查和自动更正 js 代码



在 vs code 编辑器中 安装了 eslint 插件，

并在IDE中配置了如下（setting.json）：

{
  "terminal.integrated.shell.windows": "D:\\C\\Program Files\\Git\\bin\\bash.exe",
  "eslint.autoFixOnSave": true,
  "eslint.options": {
    "plugins": ["html"],
    "extensions": [".js", ".vue"]
  },
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "vue",
      "autoFix": true
    },
    {
      "language": "html",
      "autoFix": true
    }
  ],
  "vue-peek.supportedLanguages": ["vue"],
  "vue-peek.targetFileExtensions": [".vue", ".js"],
 
  // 开启保存时自动format，一定要配合下面的format组件一起使用
  "editor.formatOnSave": true,
    // 使用prettier来format代码，相关配置如下：
  "prettier.singleQuote": true,
  "prettier.semi": false,
  "vetur.format.defaultFormatterOptions": {
    "prettier": {
      "singleQuote": true,
      "semi": false
    }
  },
  // 使用js-beautify替换prettier
  // "vetur.format.defaultFormatter.html": "js-beautify-html",
    "explorer.autoReveal": false,
  "workbench.editor.enablePreview": false,
  "terminal.integrated.scrollback": 10000,
  "javascript.updateImportsOnFileMove.enabled": "always",
  "files.associations": {
    ".jshintrc": "jsonc",
    ".eslintrc": "jsonc"
  }
}
注意到上面的，files.associations 这个配置是为了 在json中支持注释。



另外，我配置了 .eslintignore，用来排除 部分目录和代码，不进行eslint 校验。



另外，我安装了jshint插件（本工程无需这个插件），需要在工程根目录 新建 .jshintrc 文件，内容如下;

{
 "undef": true,
 "unused": true,
 "esversion": 6,
 "asi": true
}
jshint详细配置参见：https://jshint.com/docs/options/



命令行使用 eslint 检查 js 代码

运行 eslint --ext .js ./src 即可检查 js 源码是否符合规范。

eslint的配置文件为 .eslintrc，内容如下：

{
  "extends": "airbnb-base",
  "env": {
    "jest": true,
    "browser": true,
    "node": true,
    "es6": true
  },
  "rules": {
    "semi": [0, "never"],
    "semi-spacing": [2, {
      "before": false,
      "after": true
    }]
  }
}
semi": [0, "never"] 代表，js无 分号结尾，0代表有分号结尾也不报错。

其配置说明参见：

https://eslint.org/docs/rules/

https://blog.csdn.net/helpzp2008/article/details/51507428



使用 commitlint 检查 git commit规范

使用的@commitlint这个插件，其配置文件commitlint.config.js 如下：

module.exports = {
  extends: ['@commitlint/config-angular']
};
其允许的注释前缀如下：

['build',
  'ci',
  'docs',
  'feat',
  'fix',
  'perf',
  'refactor',
  'revert',
  'style',
  'test'
]

feat: feature缩写，用户相关的新功能，构建脚本功能除外
fix: 修改 bug
perf: 更改代码，以提高性能（在不影响代码内部行为的前提下，对程序性能进行优化）
refactor: 为了可读性或者性能，在不改变原有功能的前提下做的修改
docs: 文档的变更
style: 代码格式修改, 注意不是 css 修改（例如分号修改）
test: 增加或重构了测试代码，但没有增加产品代码
build: 影响项目构建或依赖项修改
revert: 恢复上一次提交
ci: 持续集成相关文件修改
chore: 更新构建脚本，但是不会更新产品代码
release: 发布新版本
workflow: 工作流相关文件修改

例如 git commit -a -m 'build: refactor' 是合格的注释。



具体参见：

https://www.npmjs.com/package/@commitlint/config-angular

https://github.com/webpack-contrib/terser-webpack-plugin/blob/master/commitlint.config.js



使用 IDE 的 EditorConfig 插件规范 文件文本格式

vscode 请安装插件：CTRL+SHIFT+X 搜索 EditorConfig 并安装，

然后在项目中新建 .editorconfig 文件，内容如下：

root = true
 
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
 
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
这个工具的一个重要作用是，将新建文件和保存的文件，自动更正 为 lf 换行符。（不然的话，windows默认的换行符是 crlf）
```