forked from opentiny/tiny-vue
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:
parent
63720a07fa
commit
dedc9f1276
|
|
@ -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 如果元素包含该类名则返回true,否则返回false
|
||||
*/
|
||||
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.style,找不到则调用getComputedStyle(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 样式属性名或样式对象。当它是对象时,遍历所有属性;当它是字符串时,需要传入第3个参数value
|
||||
* @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 如果元素完全在容器内部则返回true,否则返回false
|
||||
*/
|
||||
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 如果是元素节点则返回true,否则返回false
|
||||
*/
|
||||
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 包含onMounted、ref和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 如果元素处于隐藏状态则返回true,否则返回false
|
||||
*/
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果是纯粹的对象类型则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果是函数类型则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果是简单对象则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果对象为空则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果是有效数字则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果是数值则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果是日期类型则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果两个值相同则返回true,否则返回false
|
||||
*
|
||||
* @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} - 如果是正则表达式则返回true,否则返回false
|
||||
*
|
||||
* @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对象则返回true,否则返回false
|
||||
*
|
||||
* @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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue