Vue3源码阅读:通用工具函数(4)

294 阅读6分钟

前言

今天我们来聊聊vue3中封装的一些工具类函数。

通过观摩学习也有助于开阔自己的一些思维想法,顺便也加深自己的对于js一些属性方法的使用特性。

源码文件目录:packages/shared/src/index.ts

1、EMPTY_OBJ: 空对象,dev环境的话是冻结的空对象

export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__ ? Object.freeze({}) : {}
  • Object.freeze(obj):冻结一个对象,不可修改、删除、新增。详细文档说明
  • Object.isFrozen(obj):判断一个对象是否被冻结,返回true、false

如果Object.freeze的对象的某个属性还是object的话,那这个属性里面某个字段还是可以修改的。

简单点说:Object.freeze只能冻结对象的第一层。

2、EMPTY_ARR: 空数组,dev环境的话是冻结的空数组

export const EMPTY_ARR = __DEV__ ? Object.freeze([]) : []

3、NOOP: 空函数

export const NOOP = () => {}

作用:方便判断、压缩代码

如果是function(){},就不方便压缩

4、NO: 永远返回false的函数

export const NO = () => false

作用:方便压缩代码

5、isOn: 判断是否以on开头,并且on后面首字母不可以是a-z的小写字母

const onRE = /^on[^a-z]/
export const isOn = (key: string) => onRE.test(key)

isOn('on123') // true
isOn('onClick') // true
isOn('onclick') // false
  • ^: 以什么什么开头
  • ^on: 以on开头
  • [^]: 排除 ^ 后面的跟的逻辑
  • [^a-z]:不包含a-z的小写字母

详细正则表达式可参考:JS正则表达式完整版

6、isModelListener: 监听器,判断是否以:onUpdate: 开头

export const isModelListener = (key: string) => key.startsWith('onUpdate:')
  • startsWith:是否以一个给定的字符串开头,返回true、false
  • endsWith:是否以一个给定的字符串结尾,返回true、false

7、extend: 合并

export const extend = Object.assign

Object.assign合并的时候,会影响第一个参数的数据,例:

const person = {
    name: '张三',
    age: 25,
    family: {sister: '张姐姐'}
}

const person2 = {
    name: '李四',
    family: {
        brother: '李弟弟'
    }
}

const person3 = Object.assign(person, person2)

console.log(person)     // { name: '李四', age: 25, family: { brother: '李弟弟' } }
console.log(person2)    // { name: '李四', family: { brother: '李弟弟' } }
console.log(person3)    // { name: '李四', age: 25, family: { brother: '李弟弟' } }

通过上面示例,我们发现personperson3数据都变成一样了,person2没变化。

8、remove: 删除数组的某一项

export const remove = <T>(arr: T[], el: T) => {
  const i = arr.indexOf(el)
  if (i > -1) {
    arr.splice(i, 1)
  }
}

9、hasOwn: 判断是不是自己本身所拥有的属性,会忽略掉那些从原型链上继承到的属性

const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
  val: object,
  key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)

上面代码用了Object.prototype.hasOwnProperty.call,为什么不直接 xx.hasOwnProperty呢?

  1. __proto__ 是浏览器自己实现的原型写法
  2. JavaScript 并没有保护 hasOwnProperty 这个属性名,因此,当某个对象可能自有一个占用该属性名的属性时,就需要使用外部的 hasOwnProperty 获得正确的结果。例:
    • ({}).hasOwnProperty.call(foo, 'bar'); // 但是这种方式会新建一个对象
    • Object.prototype.hasOwnProperty.call(foo, 'bar'); // 这种最优,不会新建对象
    • 详细文档说明

10、isArray: 判断是否是数组

export const isArray = Array.isArray

11、isMap: 判断是否Map对象

export const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === '[object Map]'

Map: 类似于对象,也是键值对集合,但是Map可以是任意类型

MapWeakMap的区别:

  1. WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
  2. WeakMapkey所引用的对象都是弱引用,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。
  3. 由于WeakMap的成员随时可能被垃圾回收机制回收,成员的数量不稳定,所以没有size属性。
  4. 没有clear()方法
  5. 不能遍历(即没有keys()values()entries()方法)

12、isSet: 判断是否Set集合

export const isSet = (val: unknown): val is Set<any> => toTypeString(val) === '[object Set]'

Set:类似于数组,但是每一项的值都是唯一的。

所以我们可以利用这个特性做数组去重:Array.from(new Set([1,2,3,4,1,3]))

SetWeakSet的区别:

  1. WeakSet的成员只能是对象,而不能是其他类型的值。
  2. WeakSet中的对象都是弱引用,其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存
  3. WeakSet没有size属性
  4. WeakSet不能遍历

13、isDate: 判断是否Date对象

export const isDate = (val: unknown): val is Date => val instanceof Date

14、isFunction: 判断是否是函数

export const isFunction = (val: unknown): val is Function => typeof val === 'function'

15、isString: 判断是否是字符串

export const isString = (val: unknown): val is string => typeof val === 'string'

16、isSymbol: 判断是否为Symbol

export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'

17、isObject: 判断是否为对象

export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object'

18、isPromise: 判断是否Promise

export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
  return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}

19、toTypeString: 对象转字符串

export const objectToString = Object.prototype.toString
export const toTypeString = (value: unknown): string => objectToString.call(value)

例:

Object.prototype.toString.call('sdf')       //  '[object String]'
Object.prototype.toString.call(123)         //  '[object Number]'
Object.prototype.toString.call(null)        //  '[object Null]'
Object.prototype.toString.call(undefined)   //  '[object Undefined]'
Object.prototype.toString.call(()=>{})      //  '[object Function]'
Object.prototype.toString.call({})          //  '[object Object]'

原理可参考:详细文档说明

20、toRawType: 获取原始类型的字符串

export const toRawType = (value: unknown): string => {
  // extract "RawType" from strings like "[object RawType]"
  return toTypeString(value).slice(8, -1)
}

21、isPlainObject: 是否是原始类型的Object对象

export const isPlainObject = (val: unknown): val is object =>
  toTypeString(val) === '[object Object]'

22、isIntegerKey: 是否是整数类型的字符串键

export const isIntegerKey = (key: unknown) =>
  isString(key) &&
  key !== 'NaN' &&
  key[0] !== '-' &&
  '' + parseInt(key, 10) === key

23、isReservedProp: 是否是保留关键字符,类似于ECMA的保留关键字符的作用

export const isReservedProp = /*#__PURE__*/ makeMap(
  // the leading comma is intentional so empty string "" is also included
  ',key,ref,ref_for,ref_key,' +
    'onVnodeBeforeMount,onVnodeMounted,' +
    'onVnodeBeforeUpdate,onVnodeUpdated,' +
    'onVnodeBeforeUnmount,onVnodeUnmounted'
)

24、isBuiltInDirective: 是否是内置指令

export const isBuiltInDirective = /*#__PURE__*/ makeMap(
  'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo'
)

25、cacheStringFunction: 缓存字符串的函数

const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
  const cache: Record<string, string> = Object.create(null)
  return ((str: string) => {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }) as any
}

26、camelize: "-"连字符转小驼峰

const camelizeRE = /-(\w)/g
/**
 * @private
 */
export const camelize = cacheStringFunction((str: string): string => {
  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
})

27、hyphenate: 大写字母转"-"连字符

const hyphenateRE = /\B([A-Z])/g
/**
 * @private
 */
export const hyphenate = cacheStringFunction((str: string) =>
  str.replace(hyphenateRE, '-$1').toLowerCase()
)

28、capitalize: 首字母转大写

/**
 * @private
 */
export const capitalize = cacheStringFunction(
  (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
)

29、toHandlerKey: 转成带on的写法

/**
 * @private
 */
export const toHandlerKey = cacheStringFunction((str: string) =>
  str ? `on${capitalize(str)}` : ``
)

例: toHandlerKey('click') --> onClick

30、hasChanged:判断值是否有变化

// compare whether a value has changed, accounting for NaN.
export const hasChanged = (value: any, oldValue: any): boolean =>
  !Object.is(value, oldValue)

31、invokeArrayFns: 遍历执行数组里的函数

export const invokeArrayFns = (fns: Function[], arg?: any) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg)
  }
}

32、def: 定义一个不可枚举的对象

export const def = (obj: object, key: string | symbol, value: any) => {
  Object.defineProperty(obj, key, {
    configurable: true,   // 可以删除
    enumerable: false,    // 不可以被枚举
    value
  })
}

Object.defineProperty描述符包含以下几种:

  • configurable: 是否可删除,默认为:false
  • enumerable: 是否可以被枚举(遍历操作的时候),默认为:false
  • value:该属性对应的值,默认为:undefined
  • writable:是否可修改,默认为:false
  • get:获取属性值时所调用的函数,默认为:undefined
  • set:设置属性值时所调用的函数,默认为:undefined

详细文档说明

33、toNumber: 转数字

export const toNumber = (val: any): any => {
  const n = parseFloat(val)
  return isNaN(n) ? val : n
}

34、getGlobalThis: 获取全局对象

let _globalThis: any
export const getGlobalThis = (): any => {
  return (
    _globalThis ||
    (_globalThis =
      typeof globalThis !== 'undefined'
        ? globalThis
        : typeof self !== 'undefined'
        ? self
        : typeof window !== 'undefined'
        ? window
        : typeof global !== 'undefined'
        ? global
        : {})
  )
}
  • globalThis 在 nodejs 环境就是 global
  • globalThis 在 浏览器 环境就是 window
  • 还可能在别的环境(比如某个app内,小程序内等)

globalThis的详细说明

vue3可以自定义渲染器接入到不同的环境中哦