Vue3源码工具函数

159 阅读7分钟

1.babelParserDefaultPlugins babel的解析插件

constbabelParserDefaultPlugins= [
  'bigInt',
  'optionalChaining',
  'nullishCoalescingOperator'
] as const
  • 用于给babel解析的插件定义
  • 其中 as const 是const断言,不加这个会导致ts推断为string[]类型,使用时一旦进行对该数组拆分,会导致ts编译器报错,需要使用as const断言使数组成为只读属性,便于后续数组操作

2.EMPTY_OBJ只读空对象

export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
  ? Object.freeze({})
  : {}
  • __DEV__为环境变量,在rollup.config.js中定义,分别在 218 , 253 ,可以查看

  • Object.freeze方法,可以将一个对象"冻结",仅限于外层,如果里面嵌套其他对象的话,需要再次Object.freeze

  • 在后续使用方便判断,方便使用

3.EMPTY_ARR只读空数组

export const EMPTY_ARR = __DEV__ ? Object.freeze([]) : []
  • __DEV__为环境变量,同上,同样只导出了静态的空数组
  • 在后续使用方便判断,方便使用

4.NOOP空函数

export const NOOP = () => {}
  • 定义空函数,后续使用很方便,可以减少代码量

5.NO只返回false的函数

export const NO = () => false
  • 只返回false的函数,个人感觉就是为了语义环境设置的

6.isOn判断是否是on开头,后面跟着的字母大小写

const onRE = /^on[^a-z]/
export const isOn = (key: string) => onRE.test(key)
  • onRE正则,其中^查看是否on开头,[^a-z]判断是否为大写
  • isOn为判断函数,这种正则推测大概率为compile模块服务,用于编译事件

7.isModelListener判断开头是否为onUpdate:

export const isModelListener = (key: string) => key.startsWith('onUpdate:')
  • 用来判断是否使用"onUpdate:"开头
  • startsWith()返回boolean属性,相对的有endsWith(),参数为空字符串,还是会返回true的

8.extend合并方法

export const extend = Object.assign
  • 用于对象的合并方法,如果后面的对象中的key与前面的重复,则覆盖

9.remove删除数组中的某一项

export const remove = <T>(arr:T[],el:T)=>{
  const i = arr.indexOf(el)
  if(i>-1){
  	arr.splice(i,1)
  }
}
  • 传入数组,需要删除的值,不是纯函数,会修改原数组
  • splice方法因为在改变数组时,同事会改变其他值的位置,效率并不高

10.hasOwn查看对象本身是否具有该属性

const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
  val: object,
  key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)
  • hasOwnProperty可查看对象是否拥有该属性,该方法会忽略掉那些从原型链上继承到的属性
  • key is keyof typeof val这句还是很具有迷惑性的,在ts中,类型"String"不能作为索引类型,key会隐性为any类型,any不能作为索引,当我们输入如key的时候是希望填写该对象中已有属性,补充这一句即可避免

11.isArray判元素是否为数组

export const isArray = Array.isArray
  • 判断元素是否为数组,方便使用

12.objectToString对原型上的toString方法

export const objectToString = Object.protoType.toString
  • 将对象转为字符串

13.toTypeString对象转为字符串

export const toTypeString = (value:unknown):string => {objectToString.call(value)
  • 调用上一条objectToString方法使用call将执行的函数的this指向改为传入对象
  • unknown表示暂时未知的类型,可以赋值,但是不能调用属性和方法,使用是需要先断言

14.isMap判断传入对象是否为Map对象

export const isMap = (val:unknown):val is Map<any, any> => {toTypeString(val) === '[object Map]'
  • 调用toTypeString方法,判断是否为Map对象,给unknown加断言

15.isSet判断传入对象是否为Set对象

export const isSet = (val:unknown):val is Set<any> => toTypeString(val) === '[object Set]'
  • 调用toTypeString方法,判断是否为Set对象,给unknown加断言

16.isDate判断该对象是否为Date对象

export const isSet = (val:unknown):val is Date => val instanceof Date
  • 判断是否为Date对象,给unknown加断言

17.isFunction判断是否为函数

export const isFunction = (val:unknown):val is Function => typeof val === 'function'
  • 判断是否为Function函数,给unknown加断言

18.isString判断是否为字符串

export const isString = (val:unknown):val is string => typeof val === 'string'
  • 判断是否为string字符串,给unknown加断言

19.isSymbol判断是否为symbol类型

export const isSymbol = (val:unknown):val is symbol => typeof val === 'symbol'
  • 判断是否为symbol类型,给unknown加断言

20.isObject判断是否为对象类型

export const isObject = (val:unknown):val is Record<any,any> => 
	val!==null && typeof val === 'object'
  • 判断是否为object,其中断言为对象类型,Record是一个很好表达键值的高级类型用法
  • typeof null会返回object,null本身就是对象的空指针状态

21.toRawType将对象转为字符串后,去掉 [object xxx]

export const toRawType = (val:unknown):string => {
	return toTypeString(val).slice(8,-1)
}
  • 将传入对象转为字符串后,截取去掉[object xxx],这样会直接返回类型,不需再去截取[object ]判断

22.toPlainObjece判断当前对象是否是纯粹的对象

export const toPlainObjece = (val:unknown):val is object =>
	toTypeString(val) === '[object object]'
  • 判断当前对象是否为一个纯粹的对象,isObject中使用typeof,判断数组时也会为object

23.isIntegerKey判断当前的值是否为一个字符串类型的数字

export const isIntegerKey = (key:unknown)=>
	isString(key) &&
  key !== 'NaN' &&
  key[0] !=== '-' &&
  '' + parseInt(key,10) === key
  • 判断当前的值是否为一个字符串类型的数字
  • 判断是否为字符串,NaN,负数,转为10进制的数

24.makeMap与isReservedProp,根据字符串生成键值对

function makeMap(str:string,expectsLowerCase?:boolean):(key:string)=>boolean{
	const map:Record<string,boolean> = Object.create(null)
  const list:Array<string> = str.split(',')
  for(let i = 0;i<list.length;i++){
  	map[list[i]] = true
  }
  return expectsLowerCase ? val => !!map[val.toLowerCase()]:val => map[val]
}

export const isReservedProp = makeMap(
	// the leading comma is intentional so empty string "" is also included
  ',key,ref,' +
    'onVnodeBeforeMount,onVnodeMounted,' +
    'onVnodeBeforeUpdate,onVnodeUpdated,' +
    'onVnodeBeforeUnmount,onVnodeUnmounted'
)
  • makeMap通过既定的格式传入字符串,之后使用split方法分为数组

  • 通过循环遍历的方式,改变为一组键值对,使用了Record的类型约束表示

  • 根据exectsLowerCase方式表示是否区分大小写,并返回一个可判断是否存在的函数

  • 可写成函数柯力化的方式进一步优化

25.cacheStringFunction缓存函数

export const cacheStringFunction = <T extends (str:string)=>string>(fn:T):T=>{
	const cache = Object.create(null)
  return ((str:string)=>{
  	const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }) as any
}
  • 接收一个函数为参数,在内部使用闭包的方式记录下此函数
  • 下一次调用的时候会查看cache中是否存在,存在直接导出该函数的执行结果

26.正则方法

const camelizeRE = /-(\w)/g
/**
 * @private template中连字符与驼峰自动转化
 */
export const camelize = cacheStringFunction((str: string): string => {
  return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
})

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

/**
 * @private 首字母转为大写
 */
export const capitalize = cacheStringFunction(
  (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
)

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

27.hasChanged判断两个值之间时候有变化

export const hasChange = (value:any,oldValue:any):boolean =>
	!Object.is(value,oldValue)
  • Object.is()判断值是否相等,类似于===
  • ===无法判断NaN === NaN = false ,+0 === -0 = true的情况,需要接触Object.is()方法

28.invokeArrayFns循环调用数组里的方法

export const invokeArrayFns = (fns:Function[],arg?:any) =>{
	for(let i = 0; i < fns.length; i++){
  	fns[i](arg)
  }
}
  • 循环调用数组中的方法

29.def定义一个可删除不可枚举的对象

export const def = (obj:object,key:string|symbol,value:any)=>{
	Object.defineProperty(obj,key,{
  	configurable:true,
    enumerable:false,
    value
  })
}
  • 在Object.defineProperty中对于属性的描述有多个,分别为configurable(是否可删除),enumerable(是否可被for in循环枚举),writable(是否可修改),value(访问该属性时获取的值),get,set存取
  • 其中get,set已经定义了该属性的存取性,会与上面的方案产生冲突

30.toNumber转为数字

export const toNumber = ( val : any ) =>{
	const n = parseInt(val)
  return isNaN(n) ? val : n
}
  • 将传入数据转为数字的工厂函数

31.getGlobalThis获取全局的this指向

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
        : {})
  )
}
  • 获取全局的this指向

  • 第一次执行时,_globalThis为undefined,那么就向下寻找

  • 从self中寻找,self为Workes线程,没有window全局对象,需要在里面寻找

  • self不存在的话,那就再window中寻找

  • 直到最后进行赋值,以后调用可直接返回

总结感受

  1. vue3的工具函数融合了非常多的基础知识,es6,原型等等,试一次很好的复习
  2. 很多功能完全碎片化,简洁,极大的减少了代码量,看到了一种全新的编程方式
  3. TS方面学会了,Record<T,U>类型,实战中可以节约很多类型定义的开销
  4. 通过最后的getGlobalThis方法,也去查找了self方法,了解了Workes的相关内容

参考文章

初学者也能看懂的 Vue3 源码中那些实用的基础工具函数 若川,源码于都活动领导人.我们的川神

了解JS中的全局对象window.self和全局作用域self Workes相关,文章最后self的解释

Object.prototype.hasOwnProperty()相关知识,文章中大量用到

TS中的unknown类型 文章中的unknow类型应用解释

TypeScript 之 Record 对象常用的Record的类型

Promise的泛型T(Promise)的含义 关于Promise的泛型定义解释