Vue3源码中使用到的基础工具函数
介绍
在日常的开发业务代码中,我们经常会使用一些相同的代码,这个时候,我们就会对他进行封装,比如自带的fetch
,比如第三方的axios
,比如我们经常用到的获取日期,不管是自己封装还是饮用三方的dayjs
Moment.js
都会使我们的开发效率大大增加
vue3也是如此做的
环境准备
Google Chrome: 92.0.4515.131
git clone https://github.com/vuejs/vue-next.git
cd vue-next
npm install --global yarn
yarn
yarn build
// 关于node之类的操作都是为了最终打包出来一个shared.esm-bundler.js文件
// 找到 vue-next/packages/shared/dist/shared.esm-bundler.js
也可以直接下载shared.esm-bundler.js
新建Test.html
引入shared.esm-bundler.js
即环境准备完成
makeMap
这个工具函数看起来很无厘头,根据注释创建一个map,返回一个函数检查是否有这个值,我们仔细看一下并且用一下,就会发现,不愧是写了一个框架的男人封装出来的函数
// 创建一个map并返回一个函数,检查是否存在key在map里面
function makeMap(str, expectsLowerCase) {
// 这里为什么使用Object.create(null)不是直接{}?
// 因为Object.create(null)是最纯净的对象,不会有任何的东西,比如constructor,hasOwnProperty等等
// 这使得我们不管有意还是无意,都不会影响任何东西
const map = Object.create(null)
const list = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
// 这里为什么要用!!
// 因为这里返回的只是true || false
// 有内容即'true' or true返回true,null ''fasle之类的则返回false
return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}
关于Object.create(null)
和{}
的区别,具体可以看这里
我们直接看他怎么用
// 这里囊括了所有的html标签
const SVG_TAGS =
'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' +
'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' +
'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' +
'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' +
'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' +
'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' +
'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' +
'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' +
'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' +
'text,textPath,title,tspan,unknown,use,view'
const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS)
// 这样就可以直接传入字符串,判断是否是标签
// 比如isHTMLTag('input') --> true
const isKnownHtmlAttr = /*#__PURE__*/ makeMap(
`accept,accept-charset,accesskey,action,align,allow,alt,async,` +
`autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,` +
`border,buffered,capture,challenge,charset,checked,cite,class,code,` +
`codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,` +
`coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,` +
`disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,` +
`formaction,formenctype,formmethod,formnovalidate,formtarget,headers,` +
`height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,` +
`ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,` +
`manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,` +
`open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,` +
`referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,` +
`selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,` +
`start,step,style,summary,tabindex,target,title,translate,type,usemap,` +
`value,width,wrap`
)
// isKnownHtmlAttr('charset') --> true
babelParserDefaultPlugins
const babelParserDefaultPlugins = [
'bigInt',
'optionalChaining',
'nullishCoalescingOperator'
]
// babel默认插件
EMPTY_OBJ
创建一个空的对象,如果不是生产环境则freeze(冻结)这个对象,不能修改
生产环境想修改也改不了(手动狗头)
const EMPTY_OBJ = process.env.NODE_ENV !== 'production' ? Object.freeze({}) : {}
EMPTY_ARR
创建一个空的对象,如果不是生产环境则freeze(冻结)这个数组,不能修改
const EMPTY_ARR = process.env.NODE_ENV !== 'production' ? Object.freeze([]) : []
NOOP
返回空函数
const NOOP = () => {}
NO
返回false
const NO = () => false
isOn
判断字符串是否on
开头,且 on
后首字母不是小写字母
const onRE = /^on[^a-z]/
const isOn = key => onRE.test(key)
匹配输入字符串的开始位置,当该符号在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合。要匹配 ^ 字符本身,使用 ^。
isModelListener
是否是onUpdate:
开头
const isModelListener = key => key.startsWith('onUpdate:')
remove
删除数组的元素el
const remove = (arr, el) => {
const i = arr.indexOf(el)
if (i > -1) {
arr.splice(i, 1)
}
}
hasOwn
是否是自己本身拥有的属性
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (val, key) => hasOwnProperty.call(val, key)
isArray
是否是数组
const isArray = Array.isArray
isMap、isSet
判断是不是Map、Set对象
关于ES6Map、Set的详细介绍,可以看菜鸟教程MapSet
const isMap = val => toTypeString(val) === '[object Map]'
const isSet = val => toTypeString(val) === '[object Set]'
isDate、isFunction、isString、isSymbol
基本的判断数据类型函数
const isDate = val => val instanceof Date
const isFunction = val => typeof val === 'function'
const isString = val => typeof val === 'string'
const isSymbol = val => typeof val === 'symbol'
isObject
判断是否是对象
这个排除null是因为null typeof --> 'object'
这里并不能判定数组和对象的区别,typeof [] --> 'object'
const isObject = val => val !== null && typeof val === 'object'
isPromise
Promise其实是一个对象
有reject,resolve,then,all,rece,catch方法,这也是封装Promise的基本功能
所以他这里判断是否是Promise首先判断他是否是对象,再判断是否有then和catch方法
const isPromise = val => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}
toTypeString
call 是一个函数,第一个参数是 执行函数里面 this 指向
这个函数用来获取传入参数的类型
const objectToString = Object.prototype.toString
const toTypeString = value => objectToString.call(value)
Object.prototype.toString.call(123)
// --> '[object Number]'
Object.prototype.toString.call(new Promise((res, rej) => {}))
// --> '[object Promise]'
toRawType
截取toTypeString
返回的类型slice(start,end),负数的时候为倒数第几位
const toRawType = value => {
return toTypeString(value).slice(8, -1)
}
toRawType(123)
// --> 'Number'
toRawType('123')
// --> 'String'
isPlainObject
判断是否是纯粹的对象
const isPlainObject = val => toTypeString(val) === '[object Object]'
isPlainObject({})
// --> true
isPlainObject([])
// --> false
注意这里和isObject的区别,isObject并不能判定[]是否是对象还是数组,这里可以
isIntegerKey
判断一个字符串是否是10进制的正整数
const isIntegerKey = key =>
isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key
cacheStringFunction
缓存字符串的函数
const cacheStringFunction = fn => {
const cache = Object.create(null)
return str => {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
}
这个函数咋一看不是很看得懂,就是分开来看就明了了,传入一个函数,返回一个函数,是不是和makeMap
有点像,而且这里还使用了闭包进行了缓存,避免多次重复处理字符串
直接看他怎么用
// 首字母转大写
const camelizeRE = /-(\w)/g
const camelize = cacheStringFunction((str) => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});
hasChanged
value和oldVlaue是否改变
const hasChanged = (value, oldValue) => !Object.is(value, oldValue)
这里我们详细介绍Object.is()
和==
、===
的区别
// ==
// 等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较
console.log('1' == 1)
// --> true
// ===
// 恒等,严格比较运算符,不做类型转换,类型不同就是不等
// 判定方式:
// 1、如果类型不同,就不相等
// 2、如果两个都是数值,并且是同一个值,那么相等
// 3、如果两个都是字符串,每个位置的字符都一样,那么相等;否则不相等
// 4、如果两个值都是同样的Boolean值,那么相等
// 5、如果两个值都引用同一个对象或函数,那么相等,即两个对象的物理地址也必须保持一致;否则不相等
// 6、如果两个值都是null,或者都是undefined,那么相等
console.log('1' === 1)
// --> fasle
// Object.is()
// Object.is()和===判定基本一致,但是有两个区别
// 1、+0不等于-0
// 2、NaN等于自身
console.log(+0 === -0)
// --> true
console.log(NaN === NaN)
// --> false
Object.is(+0, -0)
// --> false
object.is(NaN, NaN)
// --> true
invokeArrayFns
传入一个函数组成的数组,执行每一个函数,函数的参数是arg
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
def
对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
const def = (obj, key, value) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
})
}
configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。enumerable
使对象的属性是可以枚举的
toNumber
函数解析一个参数(必要时先转换为字符串)并返回一个浮点数。如果转换为NaN
则说明不是Number类型,则返回原来的值
const toNumber = val => {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
getGlobalThis
let _globalThis
const getGlobalThis = () => {
return (_globalThis ||
(_globalThis =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {}));
};
获取全局 this
指向
初次执行肯定是 _globalThis
是 undefined
。所以会执行后面的赋值语句
如果存在 globalThis
就用 globalThis
如果存在self
,就用self
。在 Web Worker
中不能访问到 window
对象,但是我们却能通过 self
访问到 Worker
环境中的全局对象
如果存在window
,就用window
如果存在global
,就用global
。Node
环境下,使用global