vtils:小巧实用的 JavaScript 工具类库
小巧实用的 JavaScript 工具类库。
https://fjc0k.github.io/vtils/
特性
- 源于日常项目实践,更实用
- 使用 TypeScript 编写,类型友好
- 支持摇树优化(Tree Shaking),只引入使用到的工具
- 浏览器、Node、小程序多端兼容
安装
# yarn yarn add vtils # or, npm npm i vtils --save
使用
在线体验: https://stackblitz.com/edit/vtils
import { inBrowser, shuffle } from 'vtils' if (inBrowser()) { alert('您在浏览器中...') } alert(shuffle([1, 2, 3, 4]))
工具列表
:package:
工具函数
:bulb:
assign
分配来源对象的可枚举属性到目标对象上。
来源对象的应用规则是从左到右,随后的下一个对象的属性会覆盖上一个对象的属性。
assign( {}, { x: 1 }, { y: 2 }, { x: 5, z: 9 }, ) // => { x: 5, y: 2, z: 9 }
:bulb:
base64Decode
返回 base64
解码后的字符串。
base64Decode('dnRpbHM=') // => vtils base64Decode('5Lit5Zu9') // => 中国 base64Decode('8J+RqOKAjfCfkrs=') // => :man::computer:
:bulb:
base64Encode
返回 base64
编码后的字符串。
base64Encode('vtils') // => dnRpbHM= base64Encode('中国') // => 5Lit5Zu9 base64Encode(':man::computer:') // => 8J+RqOKAjfCfkrs=
:bulb:
base64UrlDecode
返回 base64url
解码后的字符串。
base64UrlDecode('dnRpbHM') // => vtils base64UrlDecode('5Lit5Zu9') // => 中国 base64UrlDecode('8J-RqOKAjfCfkrs') // => :man::computer:
:bulb:
base64UrlEncode
返回 base64url
编码后的字符串。
base64UrlEncode('vtils') // => dnRpbHM base64UrlEncode('中国') // => 5Lit5Zu9 base64UrlEncode(':man::computer:') // => 8J-RqOKAjfCfkrs
:bulb:
castArray
如果 value
是数组,直接返回;如果 value
不是数组,返回 [value]
。
castArray([123, 456]) // => [123, 456] castArray(123) // => [123] castArray('hello') // => ['hello'] castArray(null) // => [null]
:bulb:
chunk
将 arr
拆分成多个 size
长度的区块,并将它们组合成一个新数组返回。
如果 arr
无法等分,且设置了 filler
函数,剩余的元素将被 filler
函数的返回值填充。
const arr = [1, 2, 3, 4, 5, 6] chunk(arr, 2) // => [[1, 2], [3, 4], [5, 6]] chunk(arr, 3) // => [[1, 2, 3], [4, 5, 6]] chunk(arr, 4) // => [[1, 2, 3, 4], [5, 6]] chunk(arr, 4, index => index) // => [[1, 2, 3, 4], [5, 6, 0, 1]]
:bulb:
clamp
返回限制在最小值和最大值之间的值。
clamp(50, 0, 100) // => 50 clamp(50, 0, 50) // => 50 clamp(50, 0, 49) // => 49 clamp(50, 51, 100) // => 51
:bulb:
createURIQuery
创建 URI 查询字符串。
createURIQuery({ x: 1, y: 'z' }) // => x=1&y=z
:bulb:
debounce
创建一个去抖函数,将触发频繁的事件合并成一次执行。
该函数被调用后,计时 wait
毫秒后调用 fn
函数。若在 wait
毫秒内该函数再次被调用,则重新开始计时。
一个应用场景:监听输入框的 input
事件发起网络请求。
document.querySelector('#input').oninput = debounce( e => { console.log(e.target.value) }, 500, )
:bulb:
defaultTo
检查 value
是否是 null
、 undefined
、 NaN
,是则返回 defaultValue
,否则返回 value
。
defaultTo(1, 2) // => 1 defaultTo(NaN, 2) // => 2 defaultTo(null, 2) // => 2 defaultTo(undefined, 2) // => 2
:bulb:
endsWith
检查 str
是否以 needle
结尾。
endsWith('hello', 'llo') // => true endsWith('hello', 'he') // => false
:bulb:
fill
使用 value
来填充(替换) arr
,从 start
位置开始, 到 end
位置结束(但不包括 end
位置)。
fill(Array(5), () => 1) // => [1, 1, 1, 1, 1] fill(Array(3), (value, index) => index) // => [0, 1, 2]
:bulb:
flexible
移动端屏幕适配。
:bulb:
forOwn
遍历对象的可枚举属性。若遍历函数返回 false
,遍历会提前退出。
注:基于你传入的 obj
,遍历函数中 key
的类型可能为 number
,但在运行时, key
始终为 string
,因此,你应该始终把 key
当作 string
处理。(为什么会这样? https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208)
forOwn( { x: '1', y: 2 }, (value, key) => { console.log(key, value) } )
:bulb:
getGlobal
获取全局对象。
// 浏览器中 getGlobal() // => window // Node 中 getGlobal() // => global
:bulb:
getType
检测 value
的类型。
getType(1) // => 'Number' getType(true) // => 'Boolean' getType([]) // => 'Array' getType(/hello/) // => 'RegExp'
:bulb:
groupBy
根据 iteratee
返回的值对 data
进行分组。
groupBy( [ { type: 1, name: '石头' }, { type: 3, name: '花生' }, { type: 2, name: '鲸鱼' }, { type: 1, name: '树木' }, { type: 2, name: '鲨鱼' }, ], item => item.type, ) // => { // => 1: [ // => { type: 1, name: '石头' }, // => { type: 1, name: '树木' }, // => ], // => 2: [ // => { type: 2, name: '鲸鱼' }, // => { type: 2, name: '鲨鱼' }, // => ], // => 3: [ // => { type: 3, name: '花生' }, // => ], // => }
:bulb:
has
检查 key
是否是对象 obj
自身的属性。
const obj = { x: 1, 2: 'y' } has(obj, 'x') // => true has(obj, 2) // => true has(obj, 'toString') // => false
:bulb:
ii
立即调用函数并返回其返回值。
注: ii = immediately invoke
ii(() => 1) // => 1
:bulb:
inAndroid
检查是否在 Android
设备中。
// Android 设备中 inAndroid() // => true inAndroid( () => console.log('你在 Android 设备中'), )
:bulb:
inBrowser
检查是否在浏览器环境中。
// 浏览器中 inBrowser() // => true inBrowser( () => console.log('你在浏览器中'), )
:bulb:
inIOS
检查是否在 iOS
设备中。
// iOS 设备中 inIOS() // => true inIOS( () => console.log('你在 iOS 设备中'), )
:bulb:
inNode
检查是否在 Node
环境中。
// Node 中 inNode() // => true inNode( () => console.log('你在 Node 中'), )
:bulb:
inRange
检查 value
是否在某区间内。
// 2 是否在区间 (0, 2) 内 inRange(2, 0, 2, InRangeIntervalType.open) // => false // 2 是否在区间 [0, 2] 内 inRange(2, 0, 2, InRangeIntervalType.closed) // => true // 2 是否在区间 [0, 2) 内 inRange(2, 0, 2, InRangeIntervalType.leftClosedRightOpen) // => false // 2 是否在区间 (0, 2] 内 inRange(2, 0, 2, InRangeIntervalType.leftOpenRightClosed) // => true
:bulb:
inWechatMiniProgram
检查是否在微信小程序环境中。
// 微信小程序中 inWechatMiniProgram() // => true inWechatMiniProgram( () => console.log('你在微信小程序中'), )
:bulb:
inWechatWebview
检查是否在微信浏览器环境中。
// 微信浏览器中 inWechatWebview() // => true inWechatWebview( () => console.log('你在微信浏览器中'), )
:bulb:
includes
检索值 value
是否在数组 arr
中。
includes([1, 2, 3], 1) // => true includes([NaN, 2, 3], NaN) // => true includes([1, 2, 3], 4) // => false
检索可枚举属性值 value
是否在对象 obj
中。
includes({ x: 1, y: 2 }, 1) // => true includes({ x: 1, y: 2 }, 3) // => false
检索值 value
是否在字符串 str
中。
includes('hello', 'h') // => true includes('hello', 'll') // => true includes('hello', '123') // => false
:bulb:
isArguments
检查 value
是否是一个 arguments
对象。
function myFunction() { console.log(isArguments(arguments)) // true }
:bulb:
isArray
检查 value
是否是一个数组。
isArray(['x']) // => true isArray('x') // => false
:bulb:
isBoolean
检查 value
是否是一个布尔值。
isBoolean(true) // => true isBoolean(false) // => true isBoolean('true') // => false
:bulb:
isChineseIDCardNumber
检查 value
是否是合法的中国大陆居民 18
位身份证号码。
isChineseIDCardNumber('123456') // => false
:bulb:
isDate
检查 value
是否是一个日期。
isDate(new Date()) // => true
:bulb:
isEmail
检查 value
是否是一个邮件地址。
isEmail('hello@foo.bar') // => true isEmail('hello@foo') // => false
:bulb:
isEmpty
检查 value
是否是空值,包括: undefined
、 null
、 ''
、 false
、 true
、 []
、 {}
。
isEmpty(undefined) // => true isEmpty(null) // => true isEmpty('') // => true isEmpty(false) // => true isEmpty(true) // => true isEmpty([]) // => true isEmpty({}) // => true
:bulb:
isEqualArray
检查给定的数组的各项是否相等。
isEqualArray([1], [1]) // => true isEqualArray([1], [5]) // => false
:bulb:
isFinite
检查 value
是否是原始有限数值。
isFinite(1) // => true isFinite(Infinity) // => false
:bulb:
isFunction
检查 value
是否是一个函数。
isFunction(() => {}) // => true isFunction(2000) // => false
:bulb:
isHan
检查 value
是否全是汉字。
isHan('hello') // => false isHan('嗨咯') // => true
:bulb:
isInteger
检查 value
是否是一个整数。
isInteger(1) // => true isInteger(1.2) // => false isInteger(-1) // => true
:bulb:
isNaN
检查 value
是否是 NaN
。
isNaN(NaN) // => true isNaN(2) // => false
:bulb:
isNegativeInteger
检查 value
是否是一个负整数。
isNegativeInteger(-1) // => true isNegativeInteger(1) // => false
:bulb:
isNil
检查 value
是否是 null
或 undefined
。
isNil(null) // => true isNil(undefined) // => true
:bulb:
isNull
检查 value
是否是 null
。
isNull(null) // => true
:bulb:
isNumber
检查 value
是否是一个数字。
注: NaN
不被认为是数字。
isNumber(1) // => true isNumber(0.1) // => true isNumber(NaN) // => false
:bulb:
isNumeric
检查 value
是否是一个数值。
注: Infinity
、 -Infinity
、 NaN
不被认为是数值。
isNumeric(1) // => true isNumeric('1') // => true
:bulb:
isObject
检查 value
是否是一个对象。
isObject({}) // => true isObject(() => {}) // => true isObject(null) // => false
:bulb:
isPlainObject
检查 value
是否是一个普通对象。
isPlainObject({}) // => true isPlainObject(Object.create(null)) // => true isPlainObject(() => {}) // => false
:bulb:
isPositiveInteger
检查 value
是否是一个正整数。
isPositiveInteger(1) // => true isPositiveInteger(-1) // => false
:bulb:
isPossibleChineseMobilePhoneNumber
检测 number
是否可能是中国的手机号码。
isPossibleChineseMobilePhoneNumber(18000030000) // => true isPossibleChineseMobilePhoneNumber(10086) // => false
:bulb:
isPossibleChineseName
检测 value
是否可能是中国人的姓名,支持少数名族姓名中间的 ·
号。
isPossibleChineseName('鲁') // => false isPossibleChineseName('鲁迅') // => true isPossibleChineseName('买买提·吐尔逊') // => true
:bulb:
isPromiseLike
检查 value
是否像 Promise
。
isPromiseLike(Promise.resolve()) // => true
:bulb:
isRegExp
检查 value
是否是一个正则对象。
isRegExp(/hello/) // => true isRegExp(new RegExp('hello')) // => true
:bulb:
isString
检查 value
是否是一个字符串。
isString('') // => true isString('hello') // => true
:bulb:
isUndefined
检查 value
是否等于 undefined
。
isUndefined(undefined) // => true isUndefined(void 0) // => true
:bulb:
isUrl
检查 value
是否是一个有效的网址,仅支持 http
、 https
协议,支持 IP
域名。
isUrl('http://foo.bar') // => true isUrl('https://foo.bar/home') // => true
:bulb:
jestExpectEqual
这是一个 jest 测试辅助函数,等同于 expect(actual).toEqual(expected)
,只不过是加上了类型。
:bulb:
keyBy
根据 iteratee
返回的键对 data
进行分组,但只保留最后一个结果。
keyBy( [ { type: 1, name: '石头' }, { type: 3, name: '花生' }, { type: 2, name: '鲸鱼' }, { type: 1, name: '树木' }, { type: 2, name: '鲨鱼' }, ], item => item.type, ) // => { // => 1: { type: 1, name: '树木' }, // => 2: { type: 2, name: '鲨鱼' }, // => 3: { type: 3, name: '花生' }, // => }
:bulb:
keys
返回 obj
的可枚举属性组成的数组。
注:基于你传入的 obj
,返回的 key
的类型可能为 number
,但在运行时, key
始终为 string
,因此,你应该始终把 key
当作 string
处理。(为什么会这样? https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208)
keys({ x: 1, 2: 'y' }) // => ['x', '2'] 或 ['2', 'x']
:bulb:
last
返回数组 arr
的最后一项。
last([1, 2, 3]) // => 3
:bulb:
loadResource
加载图片、代码、样式等资源。
loadResource([ 'https://foo.bar/all.js', 'https://foo.bar/all.css', 'https://foo.bar/logo.png', { type: LoadResourceUrlType.js, path: 'https://s1.foo.bar/js/full', alternatePath: 'https://s2.foo.bar/js/full', }, ]).then(() => { // 资源加载完成后的操作 })
:bulb:
mapValues
映射对象的可枚举属性值为一个新的值。
mapValues( { x: 1, y: 2 }, value => value + 10, ) // => { x: 11, y: 12 }
:bulb:
memoize
函数结果缓存。
let i = 0 const fn = memoize(() => i++) fn() // => 0 fn() // => 0
:bulb:
noop
无操作函数。
noop() // => undefined
:bulb:
omit
创建一个从 obj
中剔除选中的可枚举属性的对象。
omit({ x: 1, y: 2 }, ['x']) // => { y: 2 }
:bulb:
orderBy
允许指定一个或多个规则对数据进行排序。
orderBy( ['x', 'xyz', 'xy'], { iteratee: item => item.length, type: OrderByRuleType.desc, }, ) // => ['xyz', 'xy', 'x']
:bulb:
padEnd
在 str
右侧填充字符。
padEnd('姓名', 4, '*') // => 姓名**
:bulb:
padStart
在 str
左侧填充字符。
padStart('姓名', 4, '*') // => **姓名
:bulb:
parallel
并行执行任务, 同步任务
、 异步任务
皆可。
parallel([ () => 1, async () => 'hello', ]).then(res => { // => [1, 'hello'] })
:bulb:
parseCSSValue
解析 CSS
值的数值和单位。
parseCSSValue('12px') // => { value: 12, unit: 'px' } parseCSSValue(12) // => { value: 12, unit: 'px' } parseCSSValue('12%') // => { value: 12, unit: '%' }
:bulb:
pick
创建一个从 obj
中选中的可枚举属性的对象。
pick({ x: 1, y: 2 }, ['x']) // => { x: 1 }
:bulb:
placeKitten
给定大小获取占位猫咪图片,图片来自: https://placekitten.com/
placeKitten(100) // => https://placekitten.com/100/100
给定宽高获取占位猫咪图片,图片来自: https://placekitten.com/
placeKitten(100, 200) // => https://placekitten.com/100/200
:bulb:
pluck
将数据中每一项的迭代值组合成一个数组返回。
pluck( [{ id: 1, name: 'Jay' }, { id: 2, name: 'Lily' }], item => item.name, ) // => ['Jay', 'Lily']
将数据中每一项的迭代值组合成一个对象返回。
pluck( [{ id: 1, name: 'Jay' }, { id: 2, name: 'Lily' }], item => item.name, item => item.id, ) // => { 1: 'Jay', 2: 'Lily' }
:bulb:
randomString
生成一个随机字符串。
randomString() // => m481rnmse1m
:bulb:
range
创建一个包含从 start
到 end
,但不包含 end
本身范围数字的数组。
range(0, 5) // => [0, 1, 2, 3, 4] range(0, -5, -1) // => [0, -1, -2, -3, -4]
:bulb:
repeat
重复 n
次给定字符串。
repeat('a', 5) // => aaaaa
:bulb:
round
对传入的数字按给定的精度四舍五入后返回。
round(3.456) // => 3 round(3.456, 1) // => 3.5 round(3.456, 2) // => 3.46 round(345, -2) // => 300
:bulb:
roundDown
对传入的数字按给定的精度向下取值后返回。
roundDown(3.456) // => 3 roundDown(3.456, 1) // => 3.4 roundDown(3.456, 2) // => 3.45 roundDown(345, -2) // => 300
:bulb:
roundUp
对传入的数字按给定的精度向上取值后返回。
roundUp(3.456) // => 4 roundUp(3.456, 1) // => 3.5 roundUp(3.456, 2) // => 3.46 roundUp(345, -2) // => 400
:bulb:
safeGet
:bulb:
sample
从数组中随机获取一个元素。
sample([1, 2, 3]) // => 1 或 2 或 3
从对象中随机获取一个可枚举属性的值。
sample({ x: 1, y: 2, z: 3 }) // => 1 或 2 或 3
:bulb:
sequential
顺序执行任务, 同步任务
、 异步任务
皆可。
sequential([ () => 1, async () => 'hello', ]).then(res => { // => [1, 'hello'] })
:bulb:
shuffle
打乱一个数组。
shuffle([1, 2]) // => [1, 2] 或 [2, 1]
:bulb:
startsWith
检查 str
是否以 needle
开头。
startsWith('hello', 'he') // => true startsWith('hello', 'llo') // => false
:bulb:
sum
计算传入值的总和。
sum([1, 2, 3]) // => 6
:bulb:
sumBy
根据 iteratee
返回的结果计算传入值的总和。
sumBy( [ { count: 1 }, { count: 2 }, { count: 3 }, ], item => item.count, ) // => 6
:bulb:
throttle
创建一个节流函数,给函数设置固定的执行速率。
- 该函数首次被调用时,会立即调用
fn
函数,并记录首次调用时间。- 该函数第二次被调用时:
- 如果该次调用时间在首次调用时间的
wait
区间内,timer = setTimeout(操作, 时间差)
;- 该函数再次被调用时:
wait
- 该函数再次被调用时:
- 否则,清除首次调用时间,回到第一步。
- 如果该次调用时间在首次调用时间的
- 该函数第二次被调用时:
一个应用场景:监听窗口的 resize
事件响应相关操作。
window.addEventListener( 'resize', throttle( () => console.log('窗口大小改变后的操作'), 1000, ), )
:bulb:
times
调用函数 n
次,将每次的调用结果存进数组并返回。
times(4, () => { // 这里将会执行 4 次 })
:bulb:
tryGet
尝试执行 accessor
返回值,若其报错,返回默认值 defaultValue
。
const obj = { x: 1 } tryGet(() => obj.x, 2) // => 1 tryGet(() => obj.x.y, 2) // => 2
尝试执行 accessor
返回值,若其报错,返回 undefined
。
const obj = { x: 1 } tryGet(() => obj.x) // => 1 tryGet(() => obj.x.y) // => undefined
:bulb:
unique
将给定的数组去重后返回。
unique([1, 2, 1, 3]) // => [1, 2, 3]
:bulb:
values
返回 obj
自身可枚举属性值组成的数组。
values({ x: 1, 2: 'y' }) // => [1, 'y'] 或 ['y', 1]
:bulb:
wait
等待一段时间。
wait(1000).then(() => { // 等待 1000 毫秒后执行 })
:package:
工具类
:bulb:
Disposer
资源释放器。
const disposer = new Disposer() const timer = setInterval( () => console.log('ok'), 1000, ) disposer.add(() => clearInterval(timer)) document.querySelector('#stop').onclick = () => { disposer.dispose() }
:bulb:
EasyStorage
:bulb:
EasyStorageAdapter
:bulb:
EasyStorageAdapterBrowser
:bulb:
EasyStorageAdapterMemory
:bulb:
EasyStorageAdapterWeapp
微信小程序 Storage
适配器。
由于微信小程序的 wx.getStorageSync
方法对于不存在的项目会返回 空字符串
,导致无法判断项目是否存在,因此,该适配器对存储的内容做了一层封装,以保证相关操作的结果可确定。
:bulb:
EasyStorageDriverBrowserLocalStorage
:bulb:
EasyStorageDriverBrowserSessionStorage
:bulb:
EasyValidator
数据对象验证器。
interface Data { name: string, phoneNumber: string, pass1: string, pass2: string, } const ev = new EasyValidator([ { key: 'name', type: 'chineseName', message: '请输入真实姓名', }, { key: 'phoneNumber', type: 'chineseMobilePhoneNumber', message: '请输入正确的手机号码', }, { key: 'phoneNumber', test: async ({ phoneNumber }, { updateMessage }) => { const result = await checkPhoneNumberAsync(phoneNumber) if (!result.valid) { updateMessage(result.message) return false } }, message: '请输入正确的手机号码' }, { key: 'pass1', test: ({ pass1 }) => pass1.length > 6, message: '密码应大于6位', }, { key: 'pass2', test: ({ pass1, pass2 }) => pass2 === pass1, message: '两次密码应一致', }, ]) ev.validate({ name: '方一一', phoneNumber: '18087030070', pass1: '1234567', pass2: '12345678' }).then(res => { // => { valid: false, unvalidRules: [{ key: 'pass2', test: ({ pass1, pass2 }) => pass2 === pass1, message: '两次密码应一致' }] } })
:bulb:
EventBus
事件巴士,管理事件的发布与订阅。
const bus = new EventBus void, error: (message: string) => void, }>() const unbindSuccessListener = bus.on('success', () => { console.log('成功啦') }) const unbindErrorListener = bus.once('error', message => { console.error(message) }) bus.emit('success') bus.emit('error', '出错啦') unbindSuccessListener() bus.off('error')
:bulb:
Wechat
对微信 JSSDK 的封装。
const wechat = new Wechat() getWechatConfigAsync().then(config => { wechat.config(config) }) wechat.updateShareData({ title: '分享标题', desc: '分享描述', link: '分享链接', imgUrl: '缩略图地址', }) wechat.invoke('scanQRCode').then(res => { // => API 调用结果 })
:package:
工具类型
:bulb:
AnyFunction
任意函数类型。
:bulb:
AnyObject
任意对象类型。
:bulb:
AsyncOrSync
// before type X = PromiseLike | string // after type X = AsyncOrSync
:bulb:
Brand
名义化类型。
type User = { id: Brand, name: string } type Post = { id: Brand, title: string } type UserIdIsNumber = User['id'] extends number ? true: false // => true type PostIdIsNumber = Post['id'] extends number ? true: false // => true type PostIdIsNotUserId = Post['id'] extends User['id'] ? false : true // => true
:bulb:
Defined
从 T
中排除 undefined
类型。
interface User { gender?: 'male' | 'female', } // before type UserGender = Exclude // after type UserGender = Defined
:bulb:
If
条件类型。
type X = 'x' // before type IsX = X extends 'x' ? true : false // after type IsX = If
:bulb:
IsNever
检查 T
是否是 never
类型。
type X = never // before type XIsNever = [X] extends [never] ? true : false // after type XIsNever = IsNever
:bulb:
LiteralUnion
字面量联合类型。
// before: China, American 将得不到类型提示 type Country = 'China' | 'American' | string // after: China, American 将得到类型提示 type Country = LiteralUnion
:bulb:
Merge
合并两个类型,后一个类型的定义将覆盖前一个类型的定义。
type X = Merge // => { x: string, y: number, z: string }
:bulb:
Omit
从接口 T
中去除指定的属性。
type X = Omit // => { y: string }
:bulb:
OneOrMore
// before type X = number | number[] // after type X = OneOrMore
:bulb:
ValueOf
返回接口 T
属性值的类型。
type V = ValueOf // => number | string | boolean
许可
MIT
:copyright:
Jay Fong