前言
在 Vue
源码中,封装了很多工具函数,学习这些函数,一方面学习大佬们的实现形式,另一方面是复习基础知识,心愿大家在日常工作中,简略的函数也能够本人封装,进步编码能力。
本次波及的工具函数 1-16 在 Vue3
的源码中,门路是 core/packages/shared/src/index.ts。
17-22 在 Vue2
的源码中,门路是 vue/src/shared/util.ts。
1. EMPTY_OBJ 空对象
const EMPTY_OBJ = __DEV__ ? Object.freeze({}) : {}
留神:Object.freeze
只能浅解冻,如果属性是对象,对属性的属性的批改就无奈解冻了
const obj = { 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 空数组
const EMPTY_ARR = __DEV__ ? Object.freeze([]) : []
3. NOOP 空函数
const NOOP = () => {}
仍旧作为兜底和占位应用:
4. NO 永远返回 false 的函数
const NO = () => false
源码中的应用:
5. isOn 判断字符串是不是 on 结尾,并且 on 后首字母不是小写字母
const onRE = /^on[^a-z]/;const isOn = (key) => onRE.test(key);// 示例isOn('onChange'); // trueisOn('onchange'); // falseisOn('on3change'); // true
6. 类型判断
const isArray = Array.isArrayconst isFunction = (val) => typeof val === 'function'const isString = (val) => typeof val === 'string'const isSymbol = (val) => typeof val === 'symbol'const isObject = (val) => val !== null && typeof val === 'object'const toTypeString = (value) => Object.prototype.toString.call(value)const isMap = (val) => toTypeString(val) === '[object Map]'const isSet = (val) => toTypeString(val) === '[object Set]'const isDate = (val) => toTypeString(val) === '[object Date]'const isPlainObject = (val) => Object.prototype.toString.call(val) === '[object Object]'// isPlainObject 判断是不是一般对象(排除正则、数组、日期、new Boolean、new Number、new String 这些非凡的对象)isObject([]) // trueisPlainObject([]) // falseconst isPromise = (val) => { return isObject(val) && isFunction(val.then) && isFunction(val.catch)}
7. toRawType 提取数据原始类型
const toRawType = (value) => { return Object.prototype.toString.call(value).slice(8, -1)}// 示例toRawType(''); 'String'toRawType([]); 'Array'
源码中的应用:
8. isIntegerKey 判断是不是数字型的字符串
const isIntegerKey = (key) => isString(key) && key !== 'NaN' && key[0] !== '-' && '' + parseInt(key, 10) === key; // 例子:isIntegerKey('a'); // falseisIntegerKey('0'); // trueisIntegerKey('011'); // falseisIntegerKey('11'); // trueisIntegerKey('-11'); // falseisIntegerKey(11); // falseisIntegerKey('NaN'); // false
9. makeMap 将字符串分隔成 map,辨别大小写,返回一个函数来判断 map 中是否含有某个 key
function makeMap(str, expectsLowerCase) { const map = Object.create(null); const list = str.split(','); for (let i = 0; i < list.length; i++) { map[list[i]] = true; } return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val];}
10. isReservedProp 是否是保留属性
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'); // ['', 'key', 'ref', 'ref_for', 'ref_key', 'onVnodeBeforeMount', 'onVnodeMounted', 'onVnodeBeforeUpdate', 'onVnodeUpdated', 'onVnodeBeforeUnmount', 'onVnodeUnmounted']// 示例isReservedProp('key'); // trueisReservedProp('onVnodeBeforeMount'); // trueisReservedProp(''); // trueisReservedProp(' '); // false
如果有 /*#__PURE__*/
这个标记,阐明他是纯函数,如果没有调用它,打包工具会间接通 tree-shaking
把它删除,缩小代码体积。
11. isBuiltInDirective 是否是内置指令
const isBuiltInDirective = /*#__PURE__*/ makeMap( 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo')
12. cacheStringFunction 将函数变为可缓存后果的函数
const cacheStringFunction = (fn) => { const cache = Object.create(null); return ((str) => { const hit = cache[str]; return hit || (cache[str] = fn(str)); });};
13. camelize & hyphenate 连字符与驼峰互转
const camelizeRE = /-(\w)/g;const camelize = cacheStringFunction((str) => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));});// 清新版const camelize = str => str.replace(camelizeRE, (_, c) => { return c ? c.toUpperCase() : '';});// 举例:on-click-a => onClickAcamelize('on-click-a');const hyphenateRE = /\B([A-Z])/g;const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());// 清新版const hyphenate = str => str.replace(hyphenateRE, '-$1').toLowerCase();// 仿照 camelize 写法const hyphenate = str => str.replace(hyphenateRE, (_, c) => { return c ? `-${c.toLowerCase()}` : '';});// 举例:onClickA => on-click-ahyphenate('onClickA');
14. hasChanged 判断是不是有变动
const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);// 示例hasChanged(1, 1); // falsehasChanged(1, 2); // truehasChanged(+0, -0); // falsehasChanged(NaN, NaN); // false// 场景:watch 监测值是不是变动了// 扩大 Object.is & ===Object.is(+0, -0); // false Object.is(NaN, NaN); // true+0 === -0 // trueNaN === NaN // false
15. invokeArrayFns 执行数组里的函数
const invokeArrayFns = (fns, arg) => { for (let i = 0; i < fns.length; i++) { fns[i](arg); }};// 示例const arr = [ function(val){ console.log(val + '张三'); }, function(val){ console.log(val + '李四'); }, function(val){ console.log(val + '王五'); },]invokeArrayFns(arr, '我是:');
源码中的应用:
16. toNumber 转数字
const toNumber = (val) => { const n = parseFloat(val); return isNaN(n) ? val : n;};toNumber('111'); // 111toNumber('a111'); // 'a111'toNumber('11a11'); // '11'toNumber(NaN); // NaN// isNaN vs Number.isNaN// isNaN 判断是不是数字 is Not a Number// Number.isNaN 判断是不是 NaNisNaN(NaN); // trueisNaN('a'); // trueNumber.isNaN(NaN); // trueNumber.isNaN('a'); // false// Number.isNaN 的 polyfillif (!Number.isNaN) { Number.isNaN = function (n) { // 办法一 return (window.isNaN(n) && typeof n === 'number'); // 办法二 利用只有 NaN 不跟本人相等的个性 return n !== n; };}
17. isPrimitive 是否为原始数据
function isPrimitive(value) { return ( typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean' )}
18. isValidArrayIndex 是否为无效的数组下标,整数并且不是无穷大
function isValidArrayIndex(val) { const n = parseFloat(String(val)) return n >= 0 && Math.floor(n) === n && isFinite(val)}// isFinite 如果参数是 NaN,正无穷大或者负无穷大,会返回 false,其余返回 true
19. bind 能兼容的bind函数
function polyfillBind(fn, ctx) { function boundFn(a) { const l = arguments.length return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx) } boundFn._length = fn.length return boundFn}function nativeBind(fn, ctx) { return fn.bind(ctx)}const bind = Function.prototype.bind ? nativeBind : polyfillBind
20. toArray 类数组转化为数组
function toArray(list, start) { start = start || 0 let i = list.length - start const ret = new Array(i) while (i--) { ret[i] = list[i + start] } return ret}
21. once 只执行一次
function once(fn) { let called = false return function () { if (!called) { called = true fn.apply(this, arguments) } }}
22. isNative 是否为原生零碎函数
function isNative(Ctor) { return typeof Ctor === 'function' && /native code/.test(Ctor.toString())}
参考资料
- 初学者也能看懂的 Vue3 源码中那些实用的根底工具函数
- 从 Number.isNaN 与 isNaN 的区别说起