深圳幻海软件技术有限公司 欢迎您!

22个Vue 源码中的工具函数

2023-02-28

前言本次涉及的工具函数1-16在Vue3的源码中,路径是core/packages/shared/src/index.ts。17-22在Vue2的源码中,路径是vue/src/shared/util.ts。1、EMPTY_OBJ空对象复制constEMPTY_OBJ=__DEV__?Object.f

前言

本次涉及的工具函数 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({})
 : {}
  • 1.
  • 2.
  • 3.

注意:

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 } }
  • 1.
  • 2.
  • 3.

源码中的使用:

可以看出基本都是作为初始化或者兜底使用,由此产生疑问:

  • 使用的地方有的是 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([]) : []
  • 1.

3、 NOOP 空函数

const NOOP = () => {}
  • 1.

依旧作为兜底和占位使用:

4、 NO 永远返回 false 的函数

const NO = () => false
  • 1.

源码中的使用:

5、isOn 判断字符串是不是 on 开头,并且 on 后首字母不是小写字母

const onRE = /^on[^a-z]/;const isOn = (key) => onRE.test(key);// 示例isOn('onChange'); // trueisOn('onchange'); // falseisOn('on3change'); // true
  • 1.

6、类型判断

const isArray = Array.isArray
const 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)
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

QQ6W5" id="hdf2c2dd-haJQQ6W5">7、 toRawType 提取数据原始类型

const toRawType = (value) => {  return Object.prototype.toString.call(value).slice(8, -1)
}// 示例toRawType('');  'String'toRawType([]);  'Array'
  • 1.
  • 2.

源码中的使用:

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

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];
}
  • 1.
  • 2.
  • 3.
  • 4.

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
  • 1.
  • 2.
  • 3.
  • 4.

如果有 /*#__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'
)
  • 1.
  • 2.

12、 cacheStringFunction 将函数变为可缓存结果的函数

const cacheStringFunction = (fn) => {    const cache = Object.create(null);    return ((str) => {        const hit = cache[str];        return hit || (cache[str] = fn(str));
   });
};
  • 1.
  • 2.
  • 3.

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');
  • 1.
  • 2.
  • 3.
  • 4.

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
  • 1.
  • 2.

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, '我是:');
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

源码中的使用:

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;
   };
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

17、isPrimitive 是否为原始数据

function isPrimitive(value) {  return (    typeof value === 'string' ||    typeof value === 'number' ||    typeof value === 'symbol' ||    typeof value === 'boolean'
 )
}
  • 1.
  • 2.
  • 3.

18、 isValidArrayIndex 是否为有效的数组下标,整数并且不是无穷大

function isValidArrayIndex(val) {  const n = parseFloat(String(val))  return n >= 0 && Math.floor(n) === n && isFinite(val)
}// isFinite 如果参数是 NaN,正无穷大或者负无穷大,会返回 false,其他返回 true
  • 1.
  • 2.

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

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
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

21、 once 只执行一次

function once(fn) {  let called = false
 return function () {    if (!called) {
     called = true
     fn.apply(this, arguments)
   }
 }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

22、 isNative 是否为原生系统函数

function isNative(Ctor) {  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
  • 1.
  • 2.