feat(utils): use cursor to add comments, ts type declarations, and vitest test cases to utils functions (#3138)

* feat(utils): use cursor to add comments and vitest test cases to utils functions

* feat(utils): 添加类型声明

* test(utils/type): 优化单元测试
This commit is contained in:
ajaxzheng 2025-03-21 14:36:50 +08:00 committed by GitHub
parent 63720a07fa
commit dedc9f1276
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 608 additions and 160 deletions

View File

@ -17,33 +17,66 @@ import { isServer } from '../globalConfig'
const SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g
const MOZ_HACK_REGEXP = /^moz([A-Z])/
/** style
* moz : - _ , camelCase("moz:moz_abc:def-hjk_lmnOpqRst") = MozMozAbcDefHjkLmnOpqRst
/**
*
* style的名字 moz : - _
* camelCase("moz:moz_abc:def-hjk_lmnOpqRst") = MozMozAbcDefHjkLmnOpqRst
* @param name
* @returns
*/
const camelCase = (name: string) =>
const camelCase = (name: string): string =>
name
.replace(SPECIAL_CHARS_REGEXP, (_, separator, letter, offset) => (offset ? letter.toUpperCase() : letter))
.replace(SPECIAL_CHARS_REGEXP, (_: string, separator: string, letter: string, offset: number) =>
offset ? letter.toUpperCase() : letter
)
.replace(MOZ_HACK_REGEXP, 'Moz$1')
/** 绑定事件 */
export const on = (el: EventTarget, event: any, handler: (this: HTMLElement, ev: any) => any, options = false) => {
/**
*
* @param el DOM元素
* @param event
* @param handler
* @param options false
*/
export const on = (
el: EventTarget,
event: string,
handler: (this: HTMLElement, ev: Event) => any,
options: boolean | AddEventListenerOptions = false
): void => {
if (el && event && handler) {
el.addEventListener(event, handler, options)
}
}
/** 移除事件 */
export const off = (el: EventTarget, event: any, handler: (this: HTMLElement, ev: any) => any, options = false) => {
/**
*
* @param el DOM元素
* @param event
* @param handler
* @param options false
*/
export const off = (
el: EventTarget,
event: string,
handler: (this: HTMLElement, ev: Event) => any,
options: boolean | EventListenerOptions = false
): void => {
if (el && event) {
el.removeEventListener(event, handler, options)
}
}
/** 执行一次就立即移除事件 */
export const once = (el: HTMLElement, event: any, fn: (this: HTMLElement, ev: any) => any) => {
const listener = function () {
/**
*
* @param el DOM元素
* @param event
* @param fn
*/
export const once = (el: HTMLElement, event: string, fn: (this: HTMLElement, ev: Event) => any): void => {
const listener = function (this: HTMLElement, ev: Event): void {
if (fn) {
// eslint-disable-next-line prefer-rest-params
fn.apply(this, arguments)
fn.call(this, ev)
}
off(el, event, listener)
@ -52,8 +85,14 @@ export const once = (el: HTMLElement, event: any, fn: (this: HTMLElement, ev: an
on(el, event, listener)
}
/** 判断是否有class, 只能查询单个类名, 且不能有空格 */
export const hasClass = (el: HTMLElement, clazz: string) => {
/**
*
*
* @param el DOM元素
* @param clazz
* @returns truefalse
*/
export const hasClass = (el: HTMLElement, clazz: string): boolean => {
if (!el || !clazz) {
return false
}
@ -65,34 +104,52 @@ export const hasClass = (el: HTMLElement, clazz: string) => {
if (el.classList) {
return el.classList.contains(clazz)
}
return false
}
/** 给el添加一组classes, clazz 允许为用空格分隔的多个类名 */
export const addClass = (el: HTMLElement, clazz = '') => {
/**
*
* clazz允许为用空格分隔的多个类名
* @param el DOM元素
* @param clazz
*/
export const addClass = (el: HTMLElement, clazz = ''): void => {
if (!el) {
return
}
const classes = clazz.split(' ').filter((name) => name)
const classes: string[] = clazz.split(' ').filter((name: string) => name)
classes.forEach((clsName) => el.classList.add(clsName))
classes.forEach((clsName: string) => el.classList.add(clsName))
}
/** 移除el上的classes clazz 允许为用空格分隔的多个类名 */
export const removeClass = (el: HTMLElement, clazz: string) => {
/**
*
* clazz允许为用空格分隔的多个类名
* @param el DOM元素
* @param clazz
*/
export const removeClass = (el: HTMLElement, clazz: string): void => {
if (!el || !clazz) {
return
}
const classes = clazz.split(' ').filter((name) => name)
const classes: string[] = clazz.split(' ').filter((name: string) => name)
classes.forEach((clsName) => el.classList.remove(clsName))
classes.forEach((clsName: string) => el.classList.remove(clsName))
}
/** 查询元素的style的值。 优先找el.style, 找不到则调用getComputedStyle(el) */
export const getStyle = (el: HTMLElement, styleName: string) => {
/**
*
* el.stylegetComputedStyle(el)
* @param el DOM元素
* @param styleName
* @returns
*/
export const getStyle = (el: HTMLElement, styleName: string): string | null | undefined => {
if (isServer) {
return
return undefined
}
if (!el || !styleName) {
return null
@ -105,21 +162,24 @@ export const getStyle = (el: HTMLElement, styleName: string) => {
}
try {
if (el.style[styleName]) {
return el.style[styleName]
if (el.style[styleName as any]) {
return el.style[styleName as any]
}
const computed = window.getComputedStyle(el)
return computed ? computed[styleName] : null
const computed: CSSStyleDeclaration = window.getComputedStyle(el)
return computed ? computed[styleName as any] : null
} catch (e) {
return el.style[styleName]
return el.style[styleName as any]
}
}
/** style
* @param name 3 value
/**
*
* @param el DOM元素
* @param name 3value
* @param value name为字符串时使用
*/
export const setStyle = (el: HTMLElement, name: string | object, value?: any) => {
export const setStyle = (el: HTMLElement, name: string | Record<string, any>, value?: any): void => {
if (!el || !name) {
return
}
@ -133,24 +193,28 @@ export const setStyle = (el: HTMLElement, name: string | object, value?: any) =>
} else {
name = camelCase(name)
el.style[name as string] = value
el.style[name as any] = value
}
}
/** style TINY_NO_USED
* @param vertical true时overflow-y属性 false时overflow-x属性 overflow属性
/**
*
* @param el DOM元素
* @param vertical true时只判断overflow-y属性false时只判断overflow-x属性overflow属性
* @returns null或undefined
*/
export const isScroll = (el: HTMLElement, vertical?: boolean) => {
export const isScroll = (el: HTMLElement, vertical?: boolean): RegExpMatchArray | null | undefined => {
if (isServer) {
return
return undefined
}
/**
* 它的值为false: 当vertical = null / undefinded
* true: vertical =true /false
/**
*
* 它的值为false: 当vertical = null / undefinded
* 它的值为true: 当vertical = true / false
*/
const determinedDirection = !isNull(vertical)
let overflow
let overflow: string | null | undefined
if (determinedDirection) {
overflow = vertical ? getStyle(el, 'overflow-y') : getStyle(el, 'overflow-x')
@ -158,44 +222,53 @@ export const isScroll = (el: HTMLElement, vertical?: boolean) => {
overflow = getStyle(el, 'overflow')
}
return overflow.match(/(scroll|auto)/)
return overflow ? overflow.match(/(scroll|auto)/) : null
}
/**
* @param vertical true时overflow-y属性 false时overflow-x属性 overflow属性
/**
*
* @param el DOM元素
* @param vertical true时只判断overflow-y属性false时只判断overflow-x属性overflow属性
* @returns
*/
export const getScrollContainer = (el: HTMLElement, vertical?: boolean) => {
export const getScrollContainer = (el: HTMLElement, vertical?: boolean): Window | HTMLElement | undefined => {
if (isServer) {
return
}
let parent = el
let parent: HTMLElement | Window | Node = el
while (parent) {
if (~[window, document, document.documentElement].indexOf(parent)) {
if (~[window, document, document.documentElement].indexOf(parent as any)) {
return window
}
if (isScroll(parent, vertical)) {
return parent
if (isScroll(parent as HTMLElement, vertical)) {
return parent as HTMLElement
}
parent = parent.parentNode as any
parent = (parent as HTMLElement).parentNode as any
}
return parent
return parent as HTMLElement
}
/** 判断是否 el 完全在 container 中。 四个边有重合都不行,必须完全在里面。 */
export const isInContainer = (el: HTMLElement, container: HTMLElement) => {
/**
*
*
* @param el DOM元素
* @param container
* @returns truefalse
*/
export const isInContainer = (el: HTMLElement, container: HTMLElement): boolean => {
if (isServer || !el || !container) {
return false
}
const elRect = el.getBoundingClientRect()
let containerRect
const elRect: DOMRect = el.getBoundingClientRect()
let containerRect: { top: number; right: number; bottom: number; left: number }
if (~[window, document, document.documentElement].indexOf(container) || isNull(container)) {
if (~[window, document, document.documentElement].indexOf(container as any) || isNull(container)) {
containerRect = {
top: 0,
right: window.innerWidth,
@ -214,16 +287,23 @@ export const isInContainer = (el: HTMLElement, container: HTMLElement) => {
)
}
/**
* @returns scrollTop document body的滚动位置
* @returns scrollLeft document body的滚动位置
* @returns visibleHeight
* @returns visibleWidth
/**
*
* @returns
* - scrollTop: document或body的垂直滚动位置
* - scrollLeft: document或body的水平滚动位置
* - visibleHeight: 可视区高度
* - visibleWidth: 可视区宽度
*/
export const getDomNode = () => {
const viewportWindow = globalConfig.viewportWindow || window
let documentElement = viewportWindow.document.documentElement
let bodyElem = viewportWindow.document.body
export const getDomNode = (): {
scrollTop: number
scrollLeft: number
visibleHeight: number
visibleWidth: number
} => {
const viewportWindow: Window = globalConfig.viewportWindow || window
let documentElement: HTMLElement = viewportWindow.document.documentElement
let bodyElem: HTMLElement = viewportWindow.document.body
return {
scrollTop: documentElement.scrollTop || bodyElem.scrollTop,
@ -233,15 +313,30 @@ export const getDomNode = () => {
}
}
export const getScrollTop = (el) => {
const top = 'scrollTop' in el ? el.scrollTop : el.pageYOffset
/**
*
* iOS滚动反弹导致的负scrollTop值
* @param el DOM元素
* @returns 0
*/
export const getScrollTop = (el: HTMLElement | Window): number => {
const top: number = 'scrollTop' in el ? (el as HTMLElement).scrollTop : (el as Window).pageYOffset
// iOS scroll bounce cause minus scrollTop
return Math.max(top, 0)
}
export const stopPropagation = (event) => event.stopPropagation()
/**
*
* @param event
*/
export const stopPropagation = (event: Event): void => event.stopPropagation()
export const preventDefault = (event, isStopPropagation) => {
/**
*
* @param event
* @param isStopPropagation
*/
export const preventDefault = (event: Event, isStopPropagation?: boolean): void => {
/* istanbul ignore else */
if (typeof event.cancelable !== 'boolean' || event.cancelable) {
event.preventDefault()
@ -253,31 +348,61 @@ export const preventDefault = (event, isStopPropagation) => {
}
const overflowScrollReg = /scroll|auto|overlay/i
const defaultRoot = isServer ? undefined : window
const defaultRoot: Window | undefined = isServer ? undefined : window
const isElement = (node) => node.tagName !== 'HTML' && node.tagName !== 'BODY' && node.nodeType === 1
/**
*
* @param node DOM节点
* @returns truefalse
*/
const isElement = (node: Node): boolean => {
const element = node as Element
return element.tagName !== 'HTML' && element.tagName !== 'BODY' && node.nodeType === 1
}
export const getScrollParent = (el, root = defaultRoot) => {
let node = el
/**
*
* @param el DOM元素
* @param root window
* @returns root
*/
export const getScrollParent = (
el: HTMLElement,
root: Window | HTMLElement | undefined = defaultRoot
): Window | HTMLElement | null => {
let node: Node | null = el
while (node && node !== root && isElement(node)) {
const { overflowY } = window.getComputedStyle(node)
const { overflowY }: CSSStyleDeclaration = window.getComputedStyle(node as HTMLElement)
if (overflowScrollReg.test(overflowY)) {
return node
return node as HTMLElement
}
node = node.parentNode
}
return root
return root || null
}
interface Hooks {
onMounted: (callback: () => void) => void
ref: <T>() => { value?: T }
watch: <T>(source: { value?: T }, callback: () => void) => void
}
/**
*
* @param hooks onMountedref和watch的对象
* @returns ref
*/
export const useScrollParent =
({ onMounted, ref, watch }) =>
(elRef, root = defaultRoot) => {
const scrollParent = ref()
const setScrollParent = () => (scrollParent.value = getScrollParent(elRef.value, root))
({ onMounted, ref, watch }: Hooks) =>
<T extends HTMLElement>(elRef: { value?: T }, root: Window | HTMLElement | undefined = defaultRoot) => {
const scrollParent = ref<Window | HTMLElement | null>()
const setScrollParent = (): void => {
scrollParent.value = elRef.value ? getScrollParent(elRef.value, root) : null
}
watch(elRef, setScrollParent)
onMounted(() => elRef.value && setScrollParent())
@ -285,18 +410,23 @@ export const useScrollParent =
return scrollParent
}
// 判断body的后代元素是否是隐藏的
export const isDisplayNone = (elm) => {
/**
*
* display和position属性
* @param elm DOM元素
* @returns truefalse
*/
export const isDisplayNone = (elm: HTMLElement | null): boolean => {
if (isServer) return false
if (elm) {
const computedStyle = getComputedStyle(elm)
const computedStyle: CSSStyleDeclaration = getComputedStyle(elm)
if (computedStyle.getPropertyValue('position') === 'fixed') {
if (computedStyle.getPropertyValue('display') === 'none') {
return true
} else if (elm.parentNode !== document.body) {
return isDisplayNone(elm.parentNode)
return isDisplayNone(elm.parentNode as HTMLElement)
}
} else {
return elm.offsetParent === null

View File

@ -0,0 +1,198 @@
import { describe, expect, it } from 'vitest'
import {
isNull,
typeOf,
isObject,
isFunction,
isPlainObject,
isEmptyObject,
isNumber,
isNumeric,
isDate,
isSame,
isRegExp,
isPromise
} from '..'
describe('类型判断工具函数测试', () => {
describe('isNull', () => {
it('应该正确判断null和undefined', () => {
expect(isNull(null)).toBe(true)
expect(isNull(undefined)).toBe(true)
expect(isNull('')).toBe(false)
expect(isNull(0)).toBe(false)
expect(isNull(false)).toBe(false)
expect(isNull({})).toBe(false)
})
})
describe('typeOf', () => {
it('应该正确返回各种类型的字符串表示', () => {
expect(typeOf(undefined)).toBe('undefined')
expect(typeOf(null)).toBe('null')
expect(typeOf(true)).toBe('boolean')
expect(typeOf(3)).toBe('number')
expect(typeOf('test')).toBe('string')
expect(typeOf(function () {})).toBe('function')
expect(typeOf(async function () {})).toBe('asyncFunction')
expect(typeOf([])).toBe('array')
expect(typeOf(new Date())).toBe('date')
expect(typeOf(new Error('测试错误'))).toBe('error')
expect(typeOf(/test/)).toBe('regExp')
expect(typeOf({})).toBe('object')
})
})
describe('isObject', () => {
it('应该正确判断对象类型', () => {
expect(isObject({})).toBe(true)
expect(isObject({})).toBe(true)
expect(isObject(null)).toBe(false)
expect(isObject(undefined)).toBe(false)
expect(isObject([])).toBe(false)
expect(isObject(new Date())).toBe(false)
expect(isObject(function () {})).toBe(false)
})
})
describe('isFunction', () => {
it('应该正确判断函数类型', () => {
expect(isFunction(function () {})).toBe(true)
expect(isFunction(async function () {})).toBe(true)
expect(isFunction(() => {})).toBe(true)
expect(isFunction(async () => {})).toBe(true)
expect(isFunction({})).toBe(false)
expect(isFunction(null)).toBe(false)
expect(isFunction(undefined)).toBe(false)
})
})
describe('isPlainObject', () => {
it('应该正确判断纯粹的对象', () => {
expect(isPlainObject({})).toBe(true)
expect(isPlainObject({})).toBe(true)
expect(isPlainObject(Object.create(null))).toBe(true)
expect(isPlainObject([])).toBe(false)
expect(isPlainObject(new Date())).toBe(false)
expect(isPlainObject(null)).toBe(false)
expect(isPlainObject(undefined)).toBe(false)
// 自定义类的实例不是纯粹对象
class TestClass {}
expect(isPlainObject(new TestClass())).toBe(false)
})
})
describe('isEmptyObject', () => {
it('应该正确判断空对象', () => {
expect(isEmptyObject({})).toBe(true)
expect(isEmptyObject([])).toBe(true)
expect(isEmptyObject({ a: 1 })).toBe(false)
expect(isEmptyObject([1, 2])).toBe(false)
expect(isEmptyObject(null)).toBe(true)
expect(isEmptyObject(undefined)).toBe(true)
})
})
describe('isNumber', () => {
it('应该正确判断数字类型', () => {
expect(isNumber(1)).toBe(true)
expect(isNumber(0)).toBe(true)
expect(isNumber(-1)).toBe(true)
expect(isNumber(1.5)).toBe(true)
expect(isNumber(NaN)).toBe(false) // NaN不是有效数字
expect(isNumber(Infinity)).toBe(false) // Infinity不是有效数字
expect(isNumber('1')).toBe(false)
expect(isNumber(null)).toBe(false)
expect(isNumber(undefined)).toBe(false)
})
})
describe('isNumeric', () => {
it('应该正确判断数值', () => {
expect(isNumeric('-10')).toBe(true)
expect(isNumeric('16')).toBe(true)
expect(isNumeric(0xff)).toBe(true)
expect(isNumeric('0xFF')).toBe(true)
expect(isNumeric('8e5')).toBe(true)
expect(isNumeric(3.1415)).toBe(true)
expect(isNumeric(+10)).toBe(true)
expect(isNumeric('')).toBe(false)
expect(isNumeric({})).toBe(false)
expect(isNumeric(NaN)).toBe(false)
expect(isNumeric(null)).toBe(false)
expect(isNumeric(true)).toBe(false)
expect(isNumeric(Infinity)).toBe(false)
expect(isNumeric(undefined)).toBe(false)
})
})
describe('isDate', () => {
it('应该正确判断日期类型', () => {
expect(isDate(new Date())).toBe(true)
expect(isDate(new Date('2023-01-01'))).toBe(true)
expect(isDate(Date.now())).toBe(false)
expect(isDate('2023-01-01')).toBe(false)
expect(isDate({})).toBe(false)
expect(isDate(null)).toBe(false)
expect(isDate(undefined)).toBe(false)
})
})
describe('isSame', () => {
it('应该正确判断两个值是否相同', () => {
expect(isSame(1, 1)).toBe(true)
expect(isSame('a', 'a')).toBe(true)
expect(isSame(true, true)).toBe(true)
expect(isSame(null, null)).toBe(true)
expect(isSame(undefined, undefined)).toBe(true)
expect(isSame(NaN, NaN)).toBe(true) // 特殊情况NaN === NaN 应该为 true
expect(isSame({}, {})).toBe(false) // 引用不同
expect(isSame([], [])).toBe(false) // 引用不同
expect(isSame(1, '1')).toBe(false) // 类型不同
expect(isSame(0, false)).toBe(false) // 类型不同
})
})
describe('isRegExp', () => {
const testCases = [
{ input: /test/, expected: true, description: '字面量正则表达式应该返回true' },
{ input: /test/i, expected: true, description: '带修饰符的正则表达式应该返回true' },
{ input: /\d+/g, expected: true, description: '带特殊字符和修饰符的正则表达式应该返回true' },
{ input: '/test/', expected: false, description: '字符串不应该被识别为正则表达式' },
{ input: {}, expected: false, description: '空对象不应该被识别为正则表达式' },
{
input: { source: 'test', flags: 'g' },
expected: false,
description: '类似正则表达式的对象不应该被识别为正则表达式'
},
{ input: null, expected: false, description: 'null不应该被识别为正则表达式' },
{ input: undefined, expected: false, description: 'undefined不应该被识别为正则表达式' },
{ input: 123, expected: false, description: '数字不应该被识别为正则表达式' },
{ input: true, expected: false, description: '布尔值不应该被识别为正则表达式' }
]
it.each(testCases)('$description', ({ input, expected }) => {
expect(isRegExp(input)).toBe(expected)
})
})
describe('isPromise', () => {
it('应该正确判断Promise对象', () => {
expect(isPromise(Promise.resolve())).toBe(true)
expect(isPromise(new Promise(() => {}))).toBe(true)
// 模拟Promise-like对象
const promiseLike = {
then: () => {},
catch: () => {}
}
expect(isPromise(promiseLike)).toBe(true)
expect(isPromise({})).toBe(false)
expect(isPromise({ then: () => {} })).toBe(false) // 缺少catch方法
expect(isPromise(null)).toBe(false)
expect(isPromise(undefined)).toBe(false)
})
})
})

View File

@ -10,14 +10,41 @@
*
*/
export const toString = Object.prototype.toString
export const hasOwn = Object.prototype.hasOwnProperty
/**
*
* Object.prototype.toString
*/
export const toString: () => string = Object.prototype.toString
const getProto = Object.getPrototypeOf
const fnToString = hasOwn.toString
const ObjectFunctionString = fnToString.call(Object)
/**
*
* Object.prototype.hasOwnProperty
*/
export const hasOwn: (prop: PropertyKey) => boolean = Object.prototype.hasOwnProperty
const class2type = {
/**
*
* Object.getPrototypeOf
*/
const getProto: <T>(obj: T) => object | null = Object.getPrototypeOf
/**
*
*
*/
const fnToString: () => string = hasOwn.toString
/**
* Object函数的字符串表示
*
*/
const ObjectFunctionString: string = fnToString.call(Object)
/**
*
* Object.prototype.toString的结果映射为对应的类型字符串
*/
const class2type: Record<string, string> = {
'[object Error]': 'error',
'[object Object]': 'object',
'[object RegExp]': 'regExp',
@ -30,78 +57,119 @@ const class2type = {
'[object Boolean]': 'boolean'
}
/** 判断是否为 null / undefined */
export const isNull = (x: any) => x === null || x === undefined
/**
* null undefined
*
* @param {any} x -
* @returns {boolean} - null或undefined则返回truefalse
*
* @example
* isNull(null) // true
* isNull(undefined) // true
* isNull(0) // false
* isNull('') // false
*/
export const isNull = (x: any): boolean => x === null || x === undefined
/**
* JavaScript
*
* undefined null'undefined''null'
*
*
* 'object'
*
* typeOf( undefined ) === 'undefined'
* typeOf() === 'undefined'
* typeOf( window.notDefined ) === 'undefined'
* typeOf( null ) === 'null'
* typeOf( true ) === 'boolean'
* typeOf( 3 ) === 'number'
* typeOf( "test" ) === 'string'
* typeOf( function (){} ) === 'function'
* typeOf( [] ) === 'array'
* typeOf( new Date() ) === 'date'
* typeOf( new Error() ) === 'error'
* typeOf( /test/ ) === 'regExp'
* @param {any} obj -
* @returns {string} -
*
* @example
* typeOf(undefined) // 'undefined'
* typeOf(null) // 'null'
* typeOf(true) // 'boolean'
* typeOf(3) // 'number'
* typeOf("test") // 'string'
* typeOf(function(){}) // 'function'
* typeOf([]) // 'array'
* typeOf(new Date()) // 'date'
* typeOf(new Error()) // 'error'
* typeOf(/test/) // 'regExp'
* typeOf({}) // 'object'
*/
export const typeOf: (obj: any) => string = (obj) =>
isNull(obj) ? String(obj) : class2type[toString.call(obj)] || 'object'
export const typeOf = (obj: any): string => (isNull(obj) ? String(obj) : class2type[toString.call(obj)] || 'object')
/**
* object
* {}new Object创建的对象
*
* isObject({}) // true
* @param {any} obj -
* @returns {boolean} - truefalse
*
* @example
* isObject({}) // true
* isObject(new Object()) // true
* isObject([]) // false
* isObject(null) // false
*/
export const isObject = (obj: any) => typeOf(obj) === 'object'
export const isObject = (obj: any): boolean => typeOf(obj) === 'object'
/**
* function
*
*
* isObject(function (){) // true
* @param {any} fn -
* @returns {boolean} - truefalse
*
* @example
* isFunction(function(){}) // true
* isFunction(async function(){}) // true
* isFunction(() => {}) // true
* isFunction({}) // false
*/
export const isFunction = (fn: any) => ['asyncFunction', 'function'].includes(typeOf(fn))
export const isFunction = (fn: any): boolean => ['asyncFunction', 'function'].includes(typeOf(fn))
/**
*
*
*
* HTML window '{}' 'new Object'
* HTML window
* '{}' 'new Object' null的对象
*
* let obj = {}
* isPlainObject(obj) //true
* @param {any} obj -
* @returns {boolean} - truefalse
*
* @example
* isPlainObject({}) // true
* isPlainObject(new Object()) // true
* isPlainObject(Object.create(null)) // true
* isPlainObject([]) // false
* isPlainObject(new Date()) // false
*/
export const isPlainObject = (obj: any) => {
export const isPlainObject = (obj: any): boolean => {
if (!obj || toString.call(obj) !== '[object Object]') {
return false
}
const proto = getProto(obj)
const proto: object | null = getProto(obj)
if (!proto) {
return true
}
const Ctor = hasOwn.call(proto, 'constructor') && proto.constructor
const Ctor: any = hasOwn.call(proto, 'constructor') && proto.constructor
return typeof Ctor === 'function' && fnToString.call(Ctor) === ObjectFunctionString
}
/**
*
*
*
* let obj = {}
* isEmptyObject(obj) // true
*
* true
*
* @param {any} obj -
* @returns {boolean} - truefalse
*
* @example
* isEmptyObject({}) // true
* isEmptyObject([]) // true
* isEmptyObject({a: 1}) // false
* isEmptyObject([1, 2]) // false
*/
export const isEmptyObject = (obj: any) => {
const type = typeOf(obj)
export const isEmptyObject = (obj: any): boolean => {
const type: string = typeOf(obj)
if (type === 'object' || type === 'array') {
for (const name in obj) {
@ -115,52 +183,104 @@ export const isEmptyObject = (obj: any) => {
}
/**
*
*
*
* isNumber(369) // true
* NaN和Infinity不被视为有效数字
*
* @param {any} value -
* @returns {boolean} - truefalse
*
* @example
* isNumber(369) // true
* isNumber(0) // true
* isNumber(-1.5) // true
* isNumber(NaN) // false
* isNumber(Infinity) // false
* isNumber('123') // false
*/
export const isNumber = (value: any) => typeof value === 'number' && isFinite(value)
export const isNumber = (value: any): boolean => typeof value === 'number' && isFinite(value)
/**
*
*
*
* isNumeric('-10') // true
* isNumeric(16) // true
* isNumeric(0xFF) // true
* isNumeric('0xFF') // true
* isNumeric('8e5') // true
* isNumeric(3.1415) // true
* isNumeric(+10) // true
* isNumeric('') // false
* isNumeric({}) // false
* isNumeric(NaN) // false
* isNumeric(null) // false
* isNumeric(true) // false
* isNumeric(Infinity) // false
* isNumeric(undefined) // false
* @param {any} value -
* @returns {boolean} - truefalse
*
* @example
* isNumeric('-10') // true
* isNumeric(16) // true
* isNumeric(0xFF) // true
* isNumeric('0xFF') // true
* isNumeric('8e5') // true
* isNumeric(3.1415) // true
* isNumeric(+10) // true
* isNumeric('') // false
* isNumeric({}) // false
* isNumeric(NaN) // false
* isNumeric(null) // false
* isNumeric(true) // false
* isNumeric(Infinity) // false
* isNumeric(undefined) // false
*/
export const isNumeric = (value: any) => value - parseFloat(value) >= 0
export const isNumeric = (value: any): boolean => value - parseFloat(value) >= 0
/**
*
*
*
* let date = new Date()
* isDate(date) // true
* @param {any} value -
* @returns {boolean} - truefalse
*
* @example
* isDate(new Date()) // true
* isDate(Date.now()) // false
* isDate('2023-01-01') // false
*/
export const isDate = (value) => typeOf(value) === 'date'
export const isDate = (value: any): boolean => typeOf(value) === 'date'
/**
*
*
*
* JavaScript NaN === NaN false ===
* NaN的情况JavaScript中NaN !== NaN
*
* isSame(1, 1) // true
* isSame(NaN, NaN) // true
* @param {any} x -
* @param {any} y -
* @returns {boolean} - truefalse
*
* @example
* isSame(1, 1) // true
* isSame(NaN, NaN) // true
* isSame('a', 'a') // true
* isSame({}, {}) // false (引用不同)
* isSame(1, '1') // false (类型不同)
*/
export const isSame = (x: any, y: any) =>
export const isSame = (x: any, y: any): boolean =>
x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y))
/** 判断是否是正则表达式 */
export const isRegExp = (value: any) => typeOf(value) === 'regExp'
/**
*
*
* @param {any} value -
* @returns {boolean} - truefalse
*
* @example
* isRegExp(/test/) // true
* isRegExp(new RegExp('test')) // true
* isRegExp('/test/') // false (这是字符串)
*/
export const isRegExp = (value: any): boolean => typeOf(value) === 'regExp'
export const isPromise = (val) => isObject(val) && isFunction(val.then) && isFunction(val.catch)
/**
* Promise对象或类Promise对象
*
* Promise对象需要有then和catch方法
*
* @param {any} val -
* @returns {boolean} - Promise对象则返回truefalse
*
* @example
* isPromise(Promise.resolve()) // true
* isPromise(new Promise(() => {})) // true
* isPromise({then: () => {}, catch: () => {}}) // true
* isPromise({then: () => {}}) // false (缺少catch方法)
*/
export const isPromise = (val: any): boolean => isObject(val) && isFunction(val.then) && isFunction(val.catch)