前言Vue源码中封装了很多工具函数。学习这些功能,一方面学习大佬们的实现方法,另一方面复习一下基础知识。希望大家在日常工作中,可以将简单的功能也自己封装起来,提高编码能力。本次涉及的工具函数1-16在Vue3源码中,路径为core/packages/shared/src/index.ts。17-22Vue2源码中,路径为vue/src/shared/util.ts。1.EMPTY_OBJ空对象constEMPTY_OBJ=__DEV__?Object.freeze({}):{}注意:Object.freeze只能浅冻结。如果属性是对象,修改属性的属性不能冻结constobj={name:'张三',info:{a:1,b:2}};Object.freeze(obj);obj。name='李四';console.log(obj);//{name:'张三',info:{a:1,b:2}}obj.info.a=66;console.log(obj);//{name:'张三',info:{a:66,b:2}}在源码中的使用:可以看出基本是作为初始化或者掩饰使用,导致疑惑:用的地方有的是options,有的是props,同一个对象用在不同的地方,不会有问题吗?首先,后面很多初始化操作都会重新赋值,EMPTY_OBJ只是作为一个占位符。其次,由于Object.freeze,EMPTY_OBJ不能被修改,所以对这个对象的任何引用都不会受到影响。判断为__DEV__(process.env.NODE_ENV!=='production')时为什么要用Object.freeze?Object.freeze更多的被Vue源码开发者在调试时使用。它可以报告错误以防止对空对象进行操作并更快地找到源代码问题。所以开发环境最终会避免EMPTY_OBJ的赋值,所以在生产环境使用Object.freeze意义不大。2.EMPTY_ARR空数组constEMPTY_ARR=__DEV__?Object.freeze([]):[]3.NOOP空函数constNOOP=()=>{}仍然用作封面和占位符:4.NO总是返回false的函数constNO=()=>false源码中使用:5.isOn判断字符串是否以on开头,on后首字母不是小写字母constonRE=/^on[^a-z]/;constisOn=(key)=>onRE。test(key);//示例isOn('onChange');//trueisOn('onchange');//falseisOn('on3change');//真6。类型判断constisArray=Array.isArrayconstisFunction=(val)=>typeofval==='function'constisString=(val)=>typeofval==='string'constisSymbol=(val)=>typeofval==='symbol'constisObject=(val)=>val!==null&&typeofval==='object'consttoTypeString=(value)=>Object.prototype.toString.call(value)constisMap=(val)=>toTypeString(val)==='[objectMap]'constisSet=(val)=>toTypeString(val)==='[objectSet]'constisDate=(val)=>toTypeString(val)==='[objectDate]'constisPlainObject=(val)=>Object.prototype.toString.call(val)==='[objectObject]'//isPlainObject判断是否为普通对象(不包括常规对象、数组、日期、newBoolean、newNumber、newString等特殊对象)isObject([])//trueisPlainObject([])//falseconstisPromise=(val)=>{returnisObject(val)&&isFunction(val.then)&&isFunction(val.catch)}7.toRawType提取数据原始类型consttoRawType=(value)=>{returnObject.prototype。toString.call(value).slice(8,-1)}//示例toRawType('');'字符串'toRawType([]);'Array'源码中的使用:8.isIntegerKey判断是否为数字串constisIntegerKey=(key)=>isString(key)&&key!=='NaN'&&key[0]!=='-'&&''+parseInt(key,10)===键;//示例:isIntegerKey('a');//falseisIntegerKey('0');//trueisIntegerKey('011');//falseisIntegerKey('11');//trueisIntegerKey('-11');//falseisIntegerKey(11);//falseisIntegerKey('NaN');//错误9。makeMap将字符串分隔成map,区分大小写,返回一个函数判断map中是否包含keyfunctionmakeMap(str,expectsLowerCase){constmap=Object.create(null);常量列表=str.split(',');for(leti=0;i!!map[val.toLowerCase()]:val=>!!map[val];}10.isReservedProp是保留属性constisReservedProp=/*#__PURE__*/makeMap(//前导逗号是故意的,所以空字符串""也包括在内',key,ref,ref_for,ref_key,'+'onVnodeBeforeMount,onVnodeMounted,'+'onVnodeBeforeUpdate,onVnodeUpdated,'+'onVnodeBeforeUnmount,onVnodeUnmounted');//['','key','ref','ref_for','ref_key','onVnodeBeforeMount','onVnodeMounted','onVnodeBeforeUpdate','onVnodeUpdated','onVnodeBeforeUnmount','onVnodeUnmounted']//示例isReservedProp('钥匙');//trueisReservedProp('onVnodeBeforeMount');//true('onVnodeBeforeMount');//true('onVnodeBeforeMount');//true('onVnodeBeforeMount');//true('onVnodeBeforeMount');');//trueisReservedProp('');//false如果有/*#__PURE__*/标志,则表示它是一个纯函数。如果不调用,打包工具会直接通过tree-shaking删除,减少代码量11.isBuiltInDirective是不是内置指令constisBuiltInDirective=/*#__PURE__*/makeMap('bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo')12.cacheStringFunction把函数变成可以缓存结果的函数constcacheStringFunction=(fn)=>{constcache=Object.create(null);return((str)=>{consthit=cache[str];returnhit||(cache[str]=fn(str));});};13.camelize&hyphenatehyphen和camelCase互换constcamelizeRE=/-(\w)/g;constcamelize=cacheStringFunction((str)=>{returnstr.replace(camelizeRE,(_,c)=>(c?c.toUpperCase():''));});//刷新版本constcamelize=str=>str.replace(camelizeRE,(_,c)=>{returnc?c.toUpperCase():'';});//示例:on-click-a=>onClickAcamelize('on-click-a');consthyphenateRE=/\B([A-Z])/g;consthyphenate=cacheStringFunction((str)=>str.replace(hyphenateRE,'-$1').toLowerCase());//刷新版本consthyphenate=str=>str.replace(hyphenateRE,'-$1').toLowerCase();//模仿驼色写作consthyphenate=str=>str.replace(hyphenateRE,(_,c)=>{returnc?`-${c.toLowerCase()}`:'';});//示例:onClickA=>on-click-ahyphenate('onClickA');14.hasChanged判断是否有变化consthasChanged=(value,oldValue)=>value!==oldValue&&(value===value||oldValue===oldValue);//例hasChanged(1,1);//falsehasChanged(1,2);//truehasChanged(+0,-0);//falsehasChanged(NaN,NaN);//false//场景:watch监控值未更改//extendsObject.is&===Object.is(+0,-0);//假Object.is(NaN,NaN);//true+0===-0//trueNaN===NaN//false15.invokeArrayFns执行数组中的函数constinvokeArrayFns=(fns,arg)=>{for(leti=0;i{constn=parseFloat(val);返回isNaN(n)?val:n;};toNumber('111');//111toNumber('a111');//'a111'toNumber('11a11');//'11'toNumber(NaN);//NaN//isNaNvsNumber.isNaN//isNaN判断是否是数字isNotaNumber//Number.isNaN判断是否为NaNisNaN(NaN);//trueisNaN('a');//trueNumber.isNaN(NaN);//trueNumber.isNaN('a');//false//polyfillforNumber.isNaN(!Number.isNaN){Number.isNaN=function(n){//方法1return(window.isNaN(n)&&typeofn==='number');//方法二利用了只有NaN不等于自身的性质returnn!==n;};}17.isPrimitive是否为原始数据functionisPrimitive(value){return(typeofvalue==='string'||typeofvalue==='number'||typeofvalue==='symbol'||typeofvalue==='boolean')}18.isValidArrayIndex是一个有效的数组下标,整数而不是无限函数isValidArrayIndex(val){constn=parseFloat(String(val))returnn>=0&&Math.floor(n)===n&&isFinite(val)}//isFinite作为如果参数为NaN、正无穷大或负无穷大,则返回false,否则返回true19。bind兼容的绑定函数functionpolyfillBind(fn,ctx){functionboundFn(a){constl=arguments.lengthreturnl?l>1?fn.apply(ctx,参数):fn.call(ctx,a):fn.call(ctx)}boundFn._length=fn.lengthreturnboundFn}functionnativeBind(fn,ctx){returnfn.bind(ctx)}constbind=Function.prototype.bind?nativeBind:polyfillBind20。toArray将类数组转换为数组函数toArray(list,start){start=start||0leti=list.length-startconstret=newArray(i)while(i--){ret[i]=list[i+start]}returnret}21。once只执行一次functiononce(fn){letcalled=falsereturnfunction(){if(!called){called=truefn.apply(this,arguments)}}}22.isNative是原生系统函数functionisNative(Ctor){returntypeofCtor==='function'&&/nativecode/.test(Ctor.toString())}参考资料初学者也可以了解Vue3中实用的基础工具功能源码,从Number.isNaN和isNaN的区别说起