关于javascript:封装小技巧is-系列方法的封装

在我的项目开发中,咱们时常会遇到判断某个变量是否为一个有效值,或者依据变量的类型,依据不同的类型进行不同的操作的状况。

比方最常见的,判断一个变量是否为 Truthy 值(什么是 Truthy 值):

if (value !== null && value !== undefined) {
  // 搞事件
}

咋看之下,也就短短两个语句,但这个事件须要进行 10 次、100 次的时候,或者你会开始想到封装:

function isDefined(value) {
  return value !== undefined && value !== null
}

既然有了这个想法,何不一干到底,咱们就间接来封装一个本人的 is 办法库。

下列的办法,将应用规范的 TypeScript 编写,你将会看到:泛型、类型谓词 is

一些惯例的 is 办法

通过类型谓词 is 能够在 TypeScript 收窄类型,帮忙更好的类型推断,这里不开展。

判断 TruthyFalsy

// 能够思考一下 value !== undefined 和 typeof value !== 'undefined' 有什么区别?
// null 呢?
function isDefined<T = unknown>(value: T | undefined | null): value is T {
  return value !== undefined && value !== null
}

function isNull(value: unknown): value is null | undefined {
  return value === undefined || value === null
}

判断其余根本类型(除了 nullundefined):

function isNumber(value: unknown): value is number {
  return typeof value === 'number'
}

// 发问:NaN 是不是一个根本类型呢?
function isNaN(value: unknown): value is number {
  return Number.isNaN(value)
}

function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function isBoolean(value: unknown): value is boolean {
  return typeof value === 'boolean'
}

// 严格判断 true
function isTrue(value: unknown): value is true {
  return value === true
}

// 严格判断 false
function isFalse(value: unknown): value is false {
  return value === false
}

// 别忘了 Symbol
function isSymbol(value: unknown): value is symbol {
  return typeof value === 'symbol'
}

// 还有一个根本类型,它是谁呢?

除开根本类型后,接下来就是一些常见对象类型的判断的,在这个之前能够先思考一个问题:

typeof object === 'object' 能不能无效的判断一个变量是否为对象呢?

从狭义上来讲,只有这个成立,那该变量的确是一个对象,但这往往不是咱们所须要和冀望的,因为这样并不能辨别数组 [] 和 对象 {} 的区别,包含一些其余对象如 Date

所以咱们要借助一个大家都晓得的绕一点的形式来判断:Object.prototype.toString,这里咱们就间接上了,不晓得具体原理的请自行搜寻。

常见对象的判断:

// 存一下,缩小对象属性的读取
const toString = Object.prototype.toString

function is(value: unknown, type: string) {
  return toString.call(value) === `[object ${type}]`
}

// 这里能够思考对象类型的收窄,用 Record<string, any> 是否适合?
function isObject<T extends Record<string, any> = Record<string, any>>(
  value: unknown
): value is T {
  return is(value, 'Object')
}

// 数组能够应用原生的办法取得更高的效率
function isArray(value: unknown): value is any[] {
  return Array.isArray(value)
}

// 插播一个 function
function isFunction(value: unknown): value is (...any: any[]) => any {
  return typeof value === 'function'
}

// 补充下面被忘记的 BigInt 根本类型
function isBigInt(value: unknown): value is bigint {
  return typeof value === 'bigint'
}

// 这里如果想要同时反对 PromiseLike 的类型收窄的话要怎么写呢?
function isPromise(value: unknown): value is Promise<any> {
  return (
    !!value &&
    typeof (value as any).then === 'function' &&
    typeof (value as any).catch === 'function'
  )
}

function isSet(value: unknown): value is Set<any> {
  return is(value, 'Set')
}

function isMap(value: unknown): value is Map<any, any> {
  return is(value, 'Map')
}

function isDate(value: unknown): value is Date {
  return is(value, 'Date')
}

function isRegExp(value: unknown): value is RegExp {
  return is(value, 'RegExp')
}

留神到这里独自封装了一个 is 办法,这个办法是能够进行任意的拓展的,比方想判断一些自定义类的时候,能够基于该 is 再封装(下面的办法都是这个准则):

function isMyClass(value: unknown): value is MyClass {
  return is(value, 'MyClass')
}

一些不太惯例的 is 办法

除了一些类型的判断,咱们时常会有呈现像是判断该变量是否为一个 Empty 值:

什么是 Empty 指的,惯例一点来讲就是包含:空数组、空字符串、空 Map、空 Set、空对象 {}

function isEmpty(value: unknown) {
  if (Array.isArray(value) || typeof value === 'string') {
    return value.length === 0
  }

  if (value instanceof Map || value instanceof Set) {
    return value.size === 0
  }

  if (isObject(value)) {
    return Object.keys(value).length === 0
  }

  return false
}

还有一个比拟常见的场景是,判断某个变量是否是否个对象的键值,咱们能够借助 Object.prototype.hasOwnProperty 来判断:

const hasOwnProperty = Object.prototype.hasOwnProperty

function has(value: Record<string, any>, key: string | symbol): key is keyof typeof value {
  return hasOwnProperty.call(value, key)
}

整合一下

好了,到此为止一个蕴含了根本类型和一些常见类型的 is 函数库就功败垂成了,最初附上一份整合后的残缺代码,大家能够在这个根底上做一些本人的拓展(应该没人须要纯 js 版本的吧):

const toString = Object.prototype.toString
const hasOwnProperty = Object.prototype.hasOwnProperty

export function is(value: unknown, type: string) {
  return toString.call(value) === `[object ${type}]`
}

export function has(value: Record<string, any>, key: string | symbol): key is keyof typeof value {
  return hasOwnProperty.call(value, key)
}

export function isDefined<T = unknown>(value: T | undefined | null): value is T {
  return value !== undefined && value !== null
}

export function isNull(value: unknown): value is null | undefined {
  return value === undefined || value === null
}

export function isNumber(value: unknown): value is number {
  return typeof value === 'number'
}

export function isNaN(value: unknown): value is number {
  return Number.isNaN(value)
}

export function isString(value: unknown): value is string {
  return typeof value === 'string'
}

export function isBoolean(value: unknown): value is boolean {
  return typeof value === 'boolean'
}

export function isTrue(value: unknown): value is true {
  return value === true
}

export function isFalse(value: unknown): value is false {
  return value === false
}

export function isSymbol(value: unknown): value is symbol {
  return typeof value === 'symbol'
}

export function isBigInt(value: unknown): value is bigint {
  return typeof value === 'bigint'
}

export function isArray(value: unknown): value is any[] {
  return Array.isArray(value)
}

export function isObject<T extends Record<string, any> = Record<string, any>>(
  value: unknown
): value is T {
  return is(value, 'Object')
}

export function isPromise(value: unknown): value is Promise<any> {
  return (
    !!value &&
    typeof (value as any).then === 'function' &&
    typeof (value as any).catch === 'function'
  )
}

export function isFunction(value: unknown): value is (...any: any[]) => any {
  return typeof value === 'function'
}

export function isSet(value: unknown): value is Set<any> {
  return is(value, 'Set')
}

export function isMap(value: unknown): value is Map<any, any> {
  return is(value, 'Map')
}

export function isDate(value: unknown): value is Date {
  return is(value, 'Date')
}

export function isRegExp(value: unknown): value is RegExp {
  return is(value, 'RegExp')
}

export function isEmpty(value: unknown) {
  if (Array.isArray(value) || typeof value === 'string') {
    return value.length === 0
  }

  if (value instanceof Map || value instanceof Set) {
    return value.size === 0
  }

  if (isObject(value)) {
    return Object.keys(value).length === 0
  }

  return false
}

一些碎碎念

最近回忆了这几年的工作,发现自己封装过各种各样的工具函数,但很多都是零零散散地遍布在我的项目中。

也是出于整顿和温习的目标,想着分享一下本人写过的一些货色,于是便尝试写了这篇文章,心愿能帮忙到一些人。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理