系列
简介
本篇主要是自定义了日常中经常需要的JS方法或Api (例如: 节流/防抖,柯里化,字符串格式转换等), 并已提供API源码。
Animater (节流/防抖)
API 列表
| Api Scope | Api Name | Api Description |
|---|
| Animater | debounce | 防抖动函数 |
| Animater | throttle | 节流函数, debounce函数的变种 |
API 使用
import js from 'js-simpler'
const wait = 500
const func = () => console.log('print log')
js.debounce(func, wait)
js.debounce(func, wait, { leading: true, trailing: true })
js.debounce(func, wait, { leading: true, trailing: true, maxWait: 1000 })
js.throttle(func, wait)
js.throttle(func, wait, { leading: true, trailing: true })
API 源码
import { isUndef } from './~Nullable'
import { isObject } from './-Object'
import { isFunction } from './-Function'
import { isFiniteNumber } from './-Number'
import { toFiniteNumber } from './-Number'
const debounce = (func: Function, wait: number, options: { leading?: boolean; trailing?: boolean; maxWait?: number; } = {}) => {
let result: any
let timerId: any
let lastArgs: any
let lastThis: any
let lastCallTime = 0
let lastInvokeTime = 0
if (!isFunction(func)) {
throw new TypeError('#<func> is not a function')
}
if (!isObject(options)) {
options = {}
}
wait = Math.max(toFiniteNumber(wait), 0)
const leading = options.leading === true
const trailing = options.trailing !== false
const useFrame = wait < 20 && typeof window.requestAnimationFrame === 'function'
const maxWait = isFiniteNumber(options.maxWait) && Math.max(options.maxWait, wait)
function timerExpired() {
const time = Date.now()
if (shouldInvoke(time)) {
return trailingEdge(time)
}
useFrame
? timerId = window.requestAnimationFrame(timerExpired)
: timerId = setTimeout(timerExpired, remainingWait(time))
}
function remainingWait(time: number) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime
const remainingCallTime = Math.max(wait - timeSinceLastCall, 0)
const remainingInvokeTime = Math.max(+maxWait! - timeSinceLastInvoke, 0)
return maxWait === false ? remainingCallTime : Math.min(remainingCallTime, remainingInvokeTime)
}
function shouldInvoke(time: number) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime
return (
lastCallTime === 0 ||
timeSinceLastCall < 0 ||
timeSinceLastCall >= wait ||
(maxWait !== false && timeSinceLastInvoke >= maxWait)
)
}
function leadingEdge(time: number) {
if (leading) {
return invokeFunc(time)
}
lastInvokeTime = time
useFrame
? timerId = window.requestAnimationFrame(timerExpired)
: timerId = setTimeout(timerExpired, wait)
return result
}
function trailingEdge(time: number) {
timerId = undefined
if (trailing && lastArgs) {
return invokeFunc(time)
}
lastArgs = lastThis = undefined
return result
}
function invokeFunc(time: number) {
const args = lastArgs
const context = lastThis
lastInvokeTime = time
lastArgs = lastThis = undefined
result = func.apply(context, args)
return result
}
function cancel() {
if (!isUndef(timerId)) {
useFrame
? window.cancelAnimationFrame(timerId)
: clearTimeout(timerId)
}
lastArgs = lastThis = timerId = undefined
lastCallTime = lastInvokeTime = 0
}
function flush() {
return isUndef(timerId) ? result : trailingEdge(Date.now())
}
function debounced(this: any, ...rest: any[]) {
const time = Date.now()
const isInvoking = shouldInvoke(time)
lastThis = this
lastArgs = rest
lastCallTime = time
if (isInvoking && isUndef(timerId)) {
return leadingEdge(lastCallTime)
}
if (isInvoking && maxWait !== false) {
useFrame || clearTimeout(timerId)
useFrame && window.cancelAnimationFrame(timerId)
useFrame && (timerId = window.requestAnimationFrame(timerExpired))
useFrame || (timerId = setTimeout(timerExpired, remainingWait(time)))
return invokeFunc(lastCallTime)
}
if (isUndef(timerId)) {
useFrame
? timerId = window.requestAnimationFrame(timerExpired)
: timerId = setTimeout(timerExpired, wait)
}
return result
}
debounced.cancel = cancel
debounced.flush = flush
return debounced
}
const throttle = (func: Function, wait: number, options: { leading?: boolean; trailing?: boolean } = {}) => {
const leading = !isObject(options) || options.leading !== false
const trailing = !isObject(options) || options.trailing !== false
if (!isFunction(func)) {
throw new TypeError('#<func> is not a function')
}
return debounce(func, wait, {
maxWait: wait,
leading: leading,
trailing: trailing
})
}
Currylize (柯里化)
API 列表
| Api Scope | Api Name | Api Description |
|---|
| Currylize | curry | 柯里化 新的函数, 函数参数数量 <=5 个时, 支持 typescript 类型提示 |
API 使用
import js from 'js-simpler'
const x = 30
const y = 20
const m = 10
const n = 5
const fn = (x, y, m, n) => {
return x * y - y * m - n
}
const _fn_ = js.curry(fn)
_fn_(js.curry, 20)(js.curry, js.curry, 5)(30, 10)
_fn_(js.curry, js.curry, 10)(js.curry, 20)(30, 5)
_fn_(js.curry, js.curry, 10)(30, 20)(5)
_fn_(30, 20, 10, 5)
API 源码
import { isFunction } from './-Function'
import { isInteger } from './-Number'
export interface Curry {
<T1, R>(func: (this: any, t1: T1) => R, length?: number): CurryFn1<T1, R>;
<T1, T2, R>(func: (this: any, t1: T1, t2: T2) => R, length?: number): CurryFn2<T1, T2, R>;
<T1, T2, T3, R>(func: (this: any, t1: T1, t2: T2, t3: T3) => R, length?: number): CurryFn3<T1, T2, T3, R>;
<T1, T2, T3, T4, R>(func: (this: any, t1: T1, t2: T2, t3: T3, t4: T4) => R, length?: number): CurryFn4<T1, T2, T3, T4, R>;
<T1, T2, T3, T4, T5, R>(func: (this: any, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => R, length?: number): CurryFn5<T1, T2, T3, T4, T5, R>;
(func: (this: any, ...args: any[]) => any, length?: number): (this: any, ...args: any[]) => any
}
export interface CurryFn1<T1, R> {
(this: any): CurryFn1<T1, R>;
(this: any, t1: T1): R;
}
export interface CurryFn2<T1, T2, R> {
(this: any): CurryFn2<T1, T2, R>;
(this: any, t1: T1): CurryFn1<T2, R>;
(this: any, t1: Curry, t2: T2): CurryFn1<T1, R>;
(this: any, t1: T1, t2: T2): R;
}
export interface CurryFn3<T1, T2, T3, R> {
(this: any): CurryFn3<T1, T2, T3, R>;
(this: any, t1: T1): CurryFn2<T2, T3, R>;
(this: any, t1: Curry, t2: T2): CurryFn2<T1, T3, R>;
(this: any, t1: T1, t2: T2): CurryFn1<T3, R>;
(this: any, t1: Curry, t2: Curry, t3: T3): CurryFn2<T1, T2, R>;
(this: any, t1: T1, t2: Curry, t3: T3): CurryFn1<T2, R>;
(this: any, t1: Curry, t2: T2, t3: T3): CurryFn1<T1, R>;
(this: any, t1: T1, t2: T2, t3: T3): R;
}
export interface CurryFn4<T1, T2, T3, T4, R> {
(this: any): CurryFn4<T1, T2, T3, T4, R>;
(this: any, t1: T1): CurryFn3<T2, T3, T4, R>;
(this: any, t1: Curry, t2: T2): CurryFn3<T1, T3, T4, R>;
(this: any, t1: T1, t2: T2): CurryFn2<T3, T4, R>;
(this: any, t1: Curry, t2: Curry, t3: T3): CurryFn3<T1, T2, T4, R>;
(this: any, t1: Curry, t2: Curry, t3: T3): CurryFn2<T2, T4, R>;
(this: any, t1: Curry, t2: T2, t3: T3): CurryFn2<T1, T4, R>;
(this: any, t1: T1, t2: T2, t3: T3): CurryFn1<T4, R>;
(this: any, t1: Curry, t2: Curry, t3: Curry, t4: T4): CurryFn3<T1, T2, T3, R>;
(this: any, t1: T1, t2: Curry, t3: Curry, t4: T4): CurryFn2<T2, T3, R>;
(this: any, t1: Curry, t2: T2, t3: Curry, t4: T4): CurryFn2<T1, T3, R>;
(this: any, t1: Curry, t2: Curry, t3: T3, t4: T4): CurryFn2<T1, T2, R>;
(this: any, t1: T1, t2: T2, t3: Curry, t4: T4): CurryFn1<T3, R>;
(this: any, t1: T1, t2: Curry, t3: T3, t4: T4): CurryFn1<T2, R>;
(this: any, t1: Curry, t2: T2, t3: T3, t4: T4): CurryFn1<T1, R>;
(this: any, t1: T1, t2: T2, t3: T3, t4: T4): R;
}
export interface CurryFn5<T1, T2, T3, T4, T5, R> {
(this: any): CurryFn5<T1, T2, T3, T4, T5, R>;
(this: any, t1: T1): CurryFn4<T2, T3, T4, T5, R>;
(this: any, t1: Curry, t2: T2): CurryFn4<T1, T3, T4, T5, R>;
(this: any, t1: T1, t2: T2): CurryFn3<T3, T4, T5, R>;
(this: any, t1: Curry, t2: Curry, t3: T3): CurryFn4<T1, T2, T4, T5, R>;
(this: any, t1: T1, t2: Curry, t3: T3): CurryFn3<T2, T4, T5, R>;
(this: any, t1: Curry, t2: T2, t3: T3): CurryFn3<T1, T4, T5, R>;
(this: any, t1: T1, t2: T2, t3: T3): CurryFn2<T4, T5, R>;
(this: any, t1: Curry, t2: Curry, t3: Curry, t4: T4): CurryFn4<T1, T2, T3, T5, R>;
(this: any, t1: T1, t2: Curry, t3: Curry, t4: T4): CurryFn3<T2, T3, T5, R>;
(this: any, t1: Curry, t2: T2, t3: Curry, t4: T4): CurryFn3<T1, T3, T5, R>;
(this: any, t1: Curry, t2: Curry, t3: T3, t4: T4): CurryFn3<T1, T2, T5, R>;
(this: any, t1: T1, t2: T2, t3: Curry, t4: T4): CurryFn2<T3, T5, R>;
(this: any, t1: T1, t2: Curry, t3: T3, t4: T4): CurryFn2<T2, T5, R>;
(this: any, t1: Curry, t2: T2, t3: T3, t4: T4): CurryFn2<T1, T5, R>;
(this: any, t1: T1, t2: T2, t3: T3, t4: T4): CurryFn1<T5, R>;
(this: any, t1: Curry, t2: Curry, t3: Curry, t4: Curry, t5: T5): CurryFn4<T1, T2, T3, T4, R>;
(this: any, t1: T1, t2: Curry, t3: Curry, t4: Curry, t5: T5): CurryFn3<T2, T3, T4, R>;
(this: any, t1: Curry, t2: T2, t3: Curry, t4: Curry, t5: T5): CurryFn3<T1, T3, T4, R>;
(this: any, t1: Curry, t2: Curry, t3: T3, t4: Curry, t5: T5): CurryFn3<T1, T2, T4, R>;
(this: any, t1: Curry, t2: Curry, t3: Curry, t4: T4, t5: T5): CurryFn3<T1, T2, T3, R>;
(this: any, t1: T1, t2: T2, t3: Curry, t4: Curry, t5: T5): CurryFn2<T3, T4, R>;
(this: any, t1: T1, t2: Curry, t3: T3, t4: Curry, t5: T5): CurryFn2<T2, T4, R>;
(this: any, t1: T1, t2: Curry, t3: Curry, t4: T4, t5: T5): CurryFn2<T2, T3, R>;
(this: any, t1: Curry, t2: T2, t3: T3, t4: Curry, t5: T5): CurryFn2<T1, T4, R>;
(this: any, t1: Curry, t2: T2, t3: Curry, t4: T4, t5: T5): CurryFn2<T1, T3, R>;
(this: any, t1: Curry, t2: Curry, t3: T3, t4: T4, t5: T5): CurryFn2<T1, T2, R>;
(this: any, t1: T1, t2: T2, t3: T3, t4: Curry, t5: T5): CurryFn1<T4, R>;
(this: any, t1: T1, t2: T2, t3: Curry, t4: T4, t5: T5): CurryFn1<T3, R>;
(this: any, t1: T1, t2: Curry, t3: T3, t4: T4, t5: T5): CurryFn1<T2, R>;
(this: any, t1: Curry, t2: T2, t3: T3, t4: T4, t5: T5): CurryFn1<T1, R>;
(this: any, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5): R;
}
export const curry: Curry = function(this: any, fn: Function, length?: number) {
if (!isFunction(fn)) {
throw new TypeError('#<fn> is not a function')
}
const currying = (fn: Function, length: number, holder: any, params: any[], holders: any[]) => {
const wrapper = (...rest: any[]) => {
const _params = params.slice()
const _holders = holders.slice()
rest.forEach(arg => {
if (!holders.length && arg === holder) {
_params.push(arg)
_holders.push(_params.length - 1)
return
}
if (!holders.length && arg !== holder) {
_params.push(arg)
return
}
if (holders.length && arg !== holder) {
const index = holders.shift()
_holders.splice(_holders.indexOf(index), 1)
_params[index] = arg
return
}
holders.shift()
})
const isPass = (
_params.length >= length &&
_params.slice(0, length).every(v => v !== holder)
)
return !isPass ? currying(fn, length, holder, _params, _holders) : fn.apply(this, _params)
}
return wrapper
}
return currying(fn, isInteger(length) && length >= 0 ? length : fn.length, curry, [], [])
}
Formater (字符串格式转换)
API 列表
| Api Scope | Api Name | Api Description |
|---|
| Formater | lowerCase | 转换成 小写字符串 |
| Formater | upperCase | 转换成 大写字符串 |
| Formater | camelCase | 转换成 驼峰式字符串 |
| Formater | underCase | 转换成 下划线字符串 |
| Formater | hyphenCase | 转换成 连字符字符串 |
API 使用
import js from 'js-simpler'
js.lowerCase('Text_Content')
js.lowerCase('text_content')
js.upperCase('Text_Content')
js.upperCase('text_content')
js.camelCase('text_content')
js.camelCase('_text_content')
js.camelCase('-text-content')
js.camelCase('text_content', true)
js.camelCase('_text_content', true)
js.camelCase('-text-content', true)
js.underCase('textContent')
js.underCase('TextContent')
js.underCase('_text_content')
js.underCase('-text-content')
js.underCase('TextContent', true)
js.underCase('_text_content', true)
js.underCase('-text-content', true)
js.hyphenCase('textContent')
js.hyphenCase('TextContent')
js.hyphenCase('_text_content')
js.hyphenCase('-text-content')
js.hyphenCase('TextContent', true)
js.hyphenCase('_text_content', true)
js.hyphenCase('-text-content', true)
API 源码
import { isNonEmptyString } from './-String'
export const lowerCase = <T = any>(string: T): T => {
return (isNonEmptyString(string) ? string.replace(/[A-Z]/g, t1 => t1 && t1.toLowerCase()) : string) as T
}
export const upperCase = <T = any>(string: T): T => {
return (isNonEmptyString(string) ? string.replace(/[a-z]/g, t1 => t1 && t1.toUpperCase()) : string) as T
}
export const camelCase = <T = any>(string: T, first = false): T => {
return (
first === true
? isNonEmptyString(string) ? string.replace(/(^|[_-])([a-z])/g, (_t1, _t2, t3) => t3 && t3.toUpperCase()) : string
: isNonEmptyString(string) ? string.replace(/[_-]([a-z])/g, (_t1, t2) => t2 && t2.toUpperCase()) : string
) as T
}
export const underCase = <T = any>(string: T, first = false): T => {
return (
first === true
? isNonEmptyString(string) ? string.replace(/([A-Z])/g, '_$1').replace(/([_-])([a-zA-Z])/g, '_$2').toLowerCase().replace(/^[_-]+/, '') : string
: isNonEmptyString(string) ? string.replace(/([A-Z])/g, '_$1').replace(/([_-])([a-zA-Z])/g, '_$2').toLowerCase() : string
) as T
}
export const hyphenCase = <T = any>(string: T, first = false): T => {
return (
first === true
? isNonEmptyString(string) ? string.replace(/([A-Z])/g, '-$1').replace(/([_-])([a-zA-Z])/g, '-$2').toLowerCase().replace(/^[_-]+/, '') : string
: isNonEmptyString(string) ? string.replace(/([A-Z])/g, '-$1').replace(/([_-])([a-zA-Z])/g, '-$2').toLowerCase() : string
) as T
}
Generalize (克隆/合并/比较)
API 列表
| Api Scope | Api Name | Api Description |
|---|
| Generalize | omit | 克隆某个对象, 并剔除 omits 选项中的字段, 属于clone函数变种 |
| Generalize | pick | 克隆某个对象, 仅保留在 picks 选项中的字段, 与omit函数相反 |
| Generalize | equal | 用于比两个值是否相等, 如果两个对象想深度比对, 可设置第三个参数为true |
| Generalize | clone | 克隆某个对象, 默认是浅拷贝, 深度克隆可设置第二个参数为true |
| Generalize | assign | 合并多个对象到某一对象, 类似Object.assgin, 设置最后一个参数为true, 即可深度assign |
| Generalize | deepAssign | assign 函数的deep版本 |
| Generalize | deepClone | clone 函数的deep版本 |
| Generalize | deepEqual | equal 函数的deep版本 |
API 使用
import js from 'js-simpler'
const omit_obj = {
a: 'a',
b: 'b',
c: { a: 'a', b: 'b' }
}
const omit_arr1 = [/a/]
const omit_arr2 = ['a']
js.omit(omit_obj, omit_arr1)
js.omit(omit_obj, omit_arr2, true)
const pick_obj = {
a: 'a',
b: 'b',
c: { a: 'a', b: 'b' }
}
const pick_arr = ['a', /c/]
js.pick(pick_obj, pick_arr)
js.pick(pick_obj, pick_arr, true)
const clone_obj = {
a: 'a',
b: 'b',
c: { a: 'a', b: 'b' }
}
js.clone(clone_obj, true)
const equal_obj1 = {
a: 'a',
b: 'b',
c: { a: 'a', b: 'b' }
}
const equal_obj2 = {
a: 'a',
b: 'b',
c: { a: 'a', b: 'b' }
}
js.equal(equal_obj1, equal_obj2)
js.equal(equal_obj1, equal_obj2, true)
const assign_target1 = { a: "a1", b: { b1: "b1" } }
const assign_source1 = { a: "a2", b: { b2: "b2" }, c: "c2" }
const assign_target2 = { a: "a1", b: { b1: "b1" } }
const assign_source2 = { a: "a2", b: { b2: "b2" }, c: "c2" }
js.assign(assign_target1, assign_source1)
js.assign(assign_target2, assign_source2, true)
API 源码
import { isFiniteNumber, isNumber, isNaN } from './-Number'
import { isWeakMap } from './-WeakMap'
import { isUndef } from './~Nullable'
import { isRegExp } from './-RegExp'
import { isObject } from './-Object'
import { isString } from './-String'
import { isArray } from './-Array'
import { isDate } from './-Date'
import { isMap } from './-Map'
import { isSet } from './-Set'
export type DeepType = boolean | number
export type FilterType = string | number | RegExp
export type FilterTypes = Array<string | number | RegExp>
export type CloneOptionsType = { omits?: FilterTypes; picks?: FilterTypes; cache?: WeakMap<object, unknown>; deep?: DeepType; }
export type EqualOptionsType = { strict?: FilterTypes; include?:FilterTypes; exclude?: FilterTypes; deep?: DeepType; }
export const omit = <T = unknown>(val: T, arr?: FilterTypes | FilterType, deep: DeepType = false): T => {
return clone(val, { omits: isArray(arr) ? arr : !isUndef(arr) ? [arr] : [], deep })
}
export const pick = <T = unknown>(val: T, arr?: FilterTypes | FilterType, deep: DeepType = false): T => {
return clone(val, { picks: isArray(arr) ? arr : !isUndef(arr) ? [arr] : [], deep })
}
export const equal = (one: unknown, two: unknown, opts: EqualOptionsType | DeepType = false): boolean => {
if (one === two) {
return true
}
if (Object.is(one, two)) {
return true
}
if (isFiniteNumber(one) && isFiniteNumber(two)) {
return Math.abs(one - two) < Number.EPSILON
}
if (isRegExp(one) && isRegExp(two)) {
return one.source === two.source && one.flags === two.flags && one.lastIndex === two.lastIndex
}
if (isObject(one) && isObject(two)) {
if (Object.keys(one).length !== Object.keys(two).length) {
return false
}
const deep = isObject(opts) && !isUndef(opts.deep) ? opts.deep : opts
const strict = isObject(opts) && isArray(opts.strict) ? opts.strict.filter(key => isRegExp(key) || isFiniteNumber(key) || isString(key)) : []
const exclude = isObject(opts) && isArray(opts.exclude) ? opts.exclude.filter(key => isRegExp(key) || isFiniteNumber(key) || isString(key)) : []
const include = isObject(opts) && isArray(opts.include) ? opts.include.filter(key => isRegExp(key) || isFiniteNumber(key) || isString(key)) : [/(?:)/]
for (const key of Object.keys(one)) {
const val1 = one[key]
const val2 = two[key]
const level = isNumber(deep) ? deep : deep === true ? Infinity : 0
const stricted = strict.length > 0 && strict.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))
const excluded = exclude.length > 0 && exclude.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))
const included = include.length === 0 || include.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))
if (excluded || !included) {
continue
}
if (val1 === val2) {
continue
}
if (stricted) {
return false
}
const reuslt = level >= 1
? equal(val1, val2, level - 1)
: false
if (!reuslt) {
return false
}
}
return true
}
if (isArray(one) && isArray(two)) {
if (one.length !== two.length) {
return false
}
const deep = isObject(opts) && !isUndef(opts.deep) ? opts.deep : opts
const strict = isObject(opts) && isArray(opts.strict) ? opts.strict.filter(key => isRegExp(key) || isFiniteNumber(key) || isString(key)) : []
const exclude = isObject(opts) && isArray(opts.exclude) ? opts.exclude.filter(key => isRegExp(key) || isFiniteNumber(key) || isString(key)) : []
const include = isObject(opts) && isArray(opts.include) ? opts.include.filter(key => isRegExp(key) || isFiniteNumber(key) || isString(key)) : [/(?:)/]
for (const key of one.keys()) {
const val1 = one[key]
const val2 = two[key]
const level = isNumber(deep) ? deep : deep === true ? Infinity : 0
const stricted = strict.length > 0 && strict.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))
const excluded = exclude.length > 0 && exclude.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))
const included = include.length === 0 || include.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))
if (excluded || !included) {
continue
}
if (val1 === val2) {
continue
}
if (stricted) {
return false
}
const reuslt = level >= 1
? equal(val1, val2, level - 1)
: false
if (!reuslt) {
return false
}
}
return true
}
if (isDate(one) && isDate(two)) {
return +one === +two
}
if (isMap(one) && isMap(two)) {
return one.size === two.size && equal([...one.entries()], [...two.entries()], opts)
}
if (isSet(one) && isSet(two)) {
return one.size === two.size && equal([...one.values()], [...two.values()], opts)
}
return false
}
export const clone = <T = unknown>(val: T, opts: CloneOptionsType | DeepType = false) : T => {
const deep = isObject(opts) && !isUndef(opts.deep) ? opts.deep : opts
const omits = isObject(opts) && isArray(opts.omits) ? opts.omits.filter(key => isRegExp(key) || isString(key) || isFiniteNumber(key)) : []
const picks = isObject(opts) && isArray(opts.picks) ? opts.picks.filter(key => isRegExp(key) || isString(key) || isFiniteNumber(key)) : []
const cache = isObject(opts) && isWeakMap(opts.cache) ? opts.cache : new WeakMap()
const level = isNumber(deep) ? deep : deep === true ? Infinity : 1
const taking = (val: any): any[] => {
return isObject(val) ? Object.entries(val) : val.entries()
}
const cloning = (val: T, level: number): T => {
const value: any = isArray(val) ? [] : {}
for (const [key, source] of taking(val)) {
if (picks.length > 0 && !picks.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))) {
continue
}
if (omits.length > 0 && omits.some(k => isRegExp(k) ? k.test(String(key)) : String(k) === String(key))) {
continue
}
let record = source
const isCopySet = isSet(source)
const isCopyMap = isMap(source)
const isCopyDate = isDate(source)
const isCopyArray = isArray(source)
const isCopyObject = isObject(source)
const isCopyRegExp = isRegExp(source)
if (!isCopySet && !isCopyMap && !isCopyDate && !isCopyArray && !isCopyObject && !isCopyRegExp) {
isArray(value) ? value.push(record) : value[key] = record
continue
}
if (level > 1 && !cache.has(source)) {
cache.set(source, clone(source, { omits, picks, cache, deep: level - 1 }))
}
if (level > 1 && cache.has(source)) {
record = cache.get(source)
}
isArray(value) ? value.push(record) : value[key] = record
}
return value as T
}
if (isNaN(level) || level < 1) {
return val
}
if (isRegExp(val)) {
const flags = val.flags
const source = val.source
const regexp = new RegExp(source, flags)
regexp.lastIndex = val.lastIndex
return regexp as T
}
if (isObject(val)) {
return cloning(val, level)
}
if (isArray(val)) {
return cloning(val, level)
}
if (isDate(val)) {
return new Date(+val) as T
}
if (isMap(val)) {
const maps = Array.from(val.entries()) as any
return new Map(cloning(maps, level) as any) as T
}
if (isSet(val)) {
const sets = Array.from(val.values()) as any
return new Set(cloning(sets, level) as any) as T
}
return val
}
export const assign = <T = unknown>(val: T, ...rest: any[]) => {
const empty = {}
const cache = new WeakMap()
const state = rest.slice(-1)[0]
const level = isNumber(state) ? state : state === true ? Infinity : 1
const taking = (val: any) => {
return isObject(val) ? Object.entries(val) : val.entries()
}
const merging = (val: T, obj: T, level: number) => {
const refly: any = isMap(val) ? Array.from(val.entries()) : isSet(val) ? Array.from(val.values()) : val
const newly: any = isMap(obj) ? Array.from(obj.entries()) : isSet(obj) ? Array.from(obj.values()) : obj
for (const [key, source] of taking(newly)) {
if (level < 1) {
refly[key] = source
continue
}
const isCopySet = isSet(source)
const isCopyMap = isMap(source)
const isCopyDate = isDate(source)
const isCopyArray = isArray(source)
const isCopyObject = isObject(source)
const isCopyRegExp = isRegExp(source)
const isNotSameType = empty.toString.call(refly[key]) !== empty.toString.call(source)
if (!isCopySet && !isCopyMap && !isCopyDate && !isCopyArray && !isCopyObject && !isCopyRegExp) {
refly[key] = source
continue
}
if (!cache.has(source)) {
cache.set(source, clone(source, { deep: level, cache }))
}
if (isNotSameType) {
refly[key] = cache.get(source)
continue
}
merging(refly[key], newly[key], level - 1)
}
if (isMap(val) || isSet(val)) {
val.clear()
}
if (isMap(val)) {
for (const [key, source] of refly) {
val.set(key, source)
}
}
if (isSet(val)) {
for (const source of refly) {
val.add(source)
}
}
}
if (isObject(val) || isArray(val) || isMap(val) || isSet(val)) {
for (const obj of rest) {
if (empty.toString.call(val) !== empty.toString.call(obj)) {
continue
}
merging(val, obj, level - 1)
}
}
return val
}
export const deepAssign = <T = unknown>(val: T, ...rest: any[]): T => {
return assign<T>(val, ...rest, true)
}
export const deepClone = <T = unknown>(val: T, opts: CloneOptionsType = {}): T => {
return clone<T>(val, { ...opts, deep: true })
}
export const deepEqual = (one: unknown, two: unknown, opts: EqualOptionsType = {}): boolean => {
return equal(one, two, { ...opts, deep: true })
}
Nullable (null/undefined判断)
API 列表
| Api Scope | Api Name | Api Description |
|---|
| Nullable | isNull | 是否是 null |
| Nullable | isUndef | 是否是 undefined |
| Nullable | isNullable | 是否是 null或undefined |
API 使用
import js from 'js-simpler'
js.isNull(null)
js.isNull(undefined)
js.isUndef(null)
js.isUndef(undefined)
js.isNullable(null)
js.isNullable(undefined)
API 源码
export const isNull = (val: unknown): val is null => {
return Object.prototype.toString.call(val) === '[object Null]'
}
export const isUndef = (val: unknown): val is undefined => {
return Object.prototype.toString.call(val) === '[object Undefined]'
}
export const isNullable = (val: unknown): val is null | undefined => {
return isNull(val) || isUndef(val)
}
Uniquer (uuid生成器)
API 列表
| Api Scope | Api Name | Api Description |
|---|
| Uniquer | uniquer | 生成 uuid或指定格式的UniqueId |
API 使用
import js from 'js-simpler'
js.uniquer()
js.uniquer({ radix: 8 })
js.uniquer({ format: '???-??' })
API 源码
import { isFunction } from './-Function'
import { isArray } from './-Array'
import { isSet } from './-Set'
export type UniquerOptions = {
radix?: 2 | 8 | 10 | 16 | 26 | 36;
format?: string | null;
random?: '?' | '*' | '#';
usedUniques?: Array<string> | Set<string> | null;
listenCacherHandler?: ListenCacherHandler | null;
reduplicateHandler?: ReduplicateHandler | null;
reduplicateExit?: boolean | null;
onlyUpdate?: boolean | null;
}
export type RandomizeOptions = {
bytes: string[];
max: number;
min: number;
}
export type CacherOptions = Set<string>
export type ListenCacherHandler = (options: CacherOptions) => void;
export type ReduplicateHandler = (options: UniquerOptions) => UniquerOptions;
export type Randomize = (options: RandomizeOptions) => string;
export type Uniquer = (options?: UniquerOptions) => string;
const Cacher: CacherOptions = new Set([''])
export const uniquer: Uniquer = (options = {}) => {
const onlyUpdate = options.onlyUpdate === true
const usedUniques = isArray(options.usedUniques) || isSet(options.usedUniques) ? options.usedUniques : []
const isExitOnRegenerate = options.reduplicateExit !== false || !isFunction(options.reduplicateHandler)
const listenCacherHandler = isFunction(options.listenCacherHandler) ? options.listenCacherHandler : (_: CacherOptions) => {}
const reduplicateHandler = isFunction(options.reduplicateHandler) ? options.reduplicateHandler : undefined
const radix = options.radix !== undefined ? options.radix : 16
const random = options.random !== undefined ? options.random : '?'
const format = options.format !== undefined ? options.format : ('????????-????-[1-5]???-[8-b]???-????????????').replace(/\?/g, random)
if (![2, 8, 10, 16, 26, 36].includes(radix)) {
throw new Error('#<Options.radix> is not in [2, 8, 10, 16, 26, 36]')
}
if (!['?', '*', '#'].includes(random)) {
throw new Error('#<Options.random> is not in ["?", "*", "#"]')
}
if (String(format) !== format) {
throw new Error('#<Options.format> is not string')
}
if (usedUniques instanceof Array) {
usedUniques.forEach(key => typeof key === 'string' && Cacher.add(key.trim()))
listenCacherHandler(new Set(Cacher))
}
if (usedUniques instanceof Set) {
usedUniques.forEach(key => typeof key === 'string' && Cacher.add(key.trim()))
listenCacherHandler(new Set(Cacher))
}
if (onlyUpdate === true) {
return ''
}
let unique = ''
let tryCount = 10
let regenerate = true
const byteOffset = radix === 26 ? 10 : 0
const characters = Array.from({ length: 36 }, (_, key) => key.toString(36))
const appendCacher = Cacher.add.bind(Cacher)
const randomizer: Randomize = opt => {
const min = opt.min
const max = opt.max
const bytes = opt.bytes
return bytes[Math.random() * (max - min + 1) + min | 0]
}
while (regenerate && tryCount-- > 0) {
const template = format.replace(/\[([^\]]+?)\]/g, (_, group: string) => {
const caches: Set<string> = new Set()
const append = caches.add.bind(caches)
const splits = group.toLowerCase().split(/\s*,\s*/g)
const filters = splits.filter(str => /^[a-zA-Z0-9\s/|\-*?#=:;]+$/ui.test(str))
const isRange = (str: string) => /^\s*[a-zA-Z0-9]\s*-\s*[a-zA-Z0-9]\s*$/.test(str)
const collects = filters.reduce((caches, string) => {
if (isRange(string.trim())) {
const str1 = string.trim().split(/\s*-\s*/)[0]
const str2 = string.trim().split(/\s*-\s*/)[1]
const key1 = characters.indexOf(str1.trim())
const key2 = characters.indexOf(str2.trim())
const first = Math.min(key1, key2)
const second = Math.max(key1, key2) + 1
characters.slice(first, second).forEach(append)
}
if (!isRange(string.trim())) {
append(string.trim())
}
return caches
}, caches)
const temp = Array.from(collects)
const bytes = temp.filter(every => !!every)
return randomizer({
bytes,
max: bytes.length - 1,
min: 0
})
})
const min = 0
const max = radix - 1
const bytes = characters.slice(byteOffset)
unique = [...template.toLowerCase()]
.filter(str => /^[a-zA-Z0-9\s/|\-*?#=:;]+$/ui.test(str))
.map(v => v === random ? randomizer({ bytes, max, min }) : v)
.join('')
.trim()
if (!Cacher.has(unique)) {
regenerate = false
appendCacher(unique)
listenCacherHandler(new Set(Cacher))
}
}
if (regenerate && isExitOnRegenerate) {
throw new Error('[Uniquer generate unique] is Reduplicated')
}
if (regenerate && !isExitOnRegenerate) {
const newOptions = reduplicateHandler!(options)
const overOptions = { reduplicateExit: true }
return uniquer({
...options,
...newOptions,
...overOptions
})
}
return unique
}
Tween (动画Tween算法)
API 列表
| Api Scope | Api Name | Api Description |
|---|
| Tween | * | Tween动画算法 |
API 使用
暂无
API 源码
查看