【源码共读】第2期 | vue3 工具函数

前言

1. EMPTY_OBJ空对象

const EMPTY_OBJ: { readonly [key: string]: any } = (process.env.NODE_ENV !== 'production')
  ? Object.freeze({})
  : {}

知识点:

  • { readonly [key: string]: any }表示EMPTY_OBJ是一个对象并且属性是只读的,其索引签名是字符串形式的,值可以是任意值。
const obj:{ readonly [key: string]: any } = {
    name:'acwrong',
    age:18
}





// obj.name = 'hello' error


console.log(obj.name)
console.log(obj.age)
  • Object.freeze():作用是冻结对象,使其最外层属性不能被更改
const EMPTY_OBJ_1 = Object.freeze({});
EMPTY_OBJ_1.name = 'acwrong';
console.log(EMPTY_OBJ_1.name); // undefined


const EMPTY_OBJ_2 = Object.freeze({ props: { name: 'acwrong' } });
EMPTY_OBJ_2.props.name = 'ACWRONG';
EMPTY_OBJ_2.props2 = 'props2';
console.log(EMPTY_OBJ_2.props.name); // 'ACWRONG'
console.log(EMPTY_OBJ_2.props2); // undefined
console.log(EMPTY_OBJ_2);

2. EMPTY_ARR 空数组

const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];

3. NOOP空函数

const NOOP = () => {}

使用场景:

  1. 可以用于判断
  2. 方便代码压缩

4. 永远返回false的函数

const NO = () => false
//可以用做全局变量来调用,也方便进行代码压缩

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

const onRE = /^on[^a-z]/
export const isOn = (key: string) => onRE.test(key)
  • ^表示以….为开头,^on表示以on开头
  • 而当^放在方括号[]中就表示反向匹配,如[^a-z]表示不在a-z内的字符

6.isModelListener监听器

判断字符串是不是以onUpdate:为开头,重点在于掌握startsWith方法的使用。

const isModelListener = (key:string)=>key.startsWith('onUpdate:')

7. extent浅拷贝合并对象

const extend = Object.assign



const data = {name:'acwrong'}
const data2 = {age:18}
const newData = extend(data,data2) //{name:'acwrong',age:18}
console.log(data === newData) //true

8. 封装移除数组中的值为某个数的函数

const remove = <T>(arr:T[],el:T) =>{
    const i = arr.indexOf(el)
    if(i > -1){
      arr.splice(i,1)
    }
}


// 例子:
const arr = [1, 2, 3];
remove(arr, 3);
console.log(arr); // [1, 2]

splice 是一个很耗性能的方法。删除数组中的一项,其他元素都要移动位置。

9.hasOwn判断自身所拥有的属性

const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
  val: object,
  key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)
  • key is keyof typeof val的意思:TypeScript – 简单易懂的 keyof typeof 分析 – 掘金
  • 为什么不直接使用obj.hasOwnProperty(key),而要大费周章的自己封装一个呢?这是为了防止开发者意外的修改obj.hasOwnProperty方法,而通过使用 hasOwnProperty.call(val, key) 的形式,我们可以确保始终调用原始的 hasOwnProperty 方法,而不受对象自身可能存在的同名属性的影响,这种写法确保了 hasOwn 函数能够准确地调用原始的 hasOwnProperty 方法,而不受潜在的同名属性的干扰,以确保准确性和可靠性。

10. isArray

const isArray = Array.isArray;



isArray([]); //true
const fakerArr = {__proto__:Array.prototype,length:0}
isArray(fakerArr) // false
  • isArray传入一个类数组会返回false

11. isMap判断是不是Map对象

const isMap = (val:unknown):val is Map<any,any> => Object.prototype.toString.call(val) === "[object Map]"



const isMap = (val: unknown): val is Map<any, any> =>
  toTypeString(val) === '[object Map]'



const objectToString = Object.prototype.toString
const toTypeString = (value: unknown): string =>
  objectToString.call(value)

思考面试题:Map和Object的区别?Map – JavaScript | MDN
Map的键可以是各种类型的值,而Object的键只能是字符串或者symbol。

12. isSet判断是不是Set对象

const isSet = (val: unknown): val is Set<any> =>
  toTypeString(val) === '[object Set]'

13. isDate判断是不是Date对象

const isDate = (val: unknown): val is Date => val instanceof Date

14. isFunction判断是不是函数

const isFunction = (val: unknown): val is Function =>
  typeof val === 'function'

15. isString判断是不是字符串

const isString = (val: unknown): val is string => typeof val === 'string'

16. isSymbol判断是不是Symbol

const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'

17. isObject判断是不是对象

const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object'

Record的概念:Documentation – Utility Types

?18. isPromise判断是不是Promise

const isPromise = (val) => {
    return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};

或者使用:

if (Promise.resolve(obj) === obj) {
  // 对象是 Promise 对象或类 Promise 的对象
} else {
  // 对象不是 Promise 对象或类 Promise 的对象
}



Promise.resolve() 方法将传入的参数转换为一个 Promise 对象。如果传入的对象本身就是 Promise 对象,Promise.resolve() 将返回原始的 Promise 对象。因此,通过比较 Promise.resolve(obj) 和 obj,我们可以判断 obj 是否是 Promise 对象。

19. objectTostring+toTypeString对象转字符串

const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);

20. toRawType 对象转字符串方便类型判断

const toRawType = (value: unknown): string => {
  // extract "RawType" from strings like "[object RawType]"
  return toTypeString(value).slice(8, -1)
}


使用该函数,我们可以判断Array等数据类型。

function isArray(value: unknown): boolean {
  return toRawType(value) === 'Array';
}


// 示例用法
const value1 = [1, 2, 3];
console.log(isArray(value1)); // true

const value2 = 'Hello';
console.log(isArray(value2)); // false

21.isPlainObject判断是不是存粹的对象

const isPlainObject = (val: unknown): val is object =>
  toTypeString(val) === '[object Object]'

22. makeMap&&isReservedProp

  • makeMap方法传入一个带有逗号的字符串,利用逗号将其分成一个个子字符串
  • 使用Object.create(null)创建一个对象,然后循环地将每个子字符串作为一个对象的键,且其值全部为true;
  • 返回一个函数,该函数用来检测接收的参数是否是对象中的键。巧妙地利用闭包来延长map对象的生命周期,使其能够复用;并且使用!!来确保map对象中的值一定是布尔类型,主要是为了提高程序的健壮性。
/**
 * Make a map and return a function for checking if a key
 * is in that map.
 * IMPORTANT: all calls of this function must be prefixed with
 * \/\*#\_\_PURE\_\_\*\/
 * So that rollup can tree-shake them if necessary.
 */
export 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]
}

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'
)

// 保留的属性
isReservedProp('key'); // true
isReservedProp('ref'); // true
isReservedProp('onVnodeBeforeMount'); // true
isReservedProp('onVnodeMounted'); // true
isReservedProp('onVnodeBeforeUpdate'); // true
isReservedProp('onVnodeUpdated'); // true
isReservedProp('onVnodeBeforeUnmount'); // true
isReservedProp('onVnodeUnmounted'); // true

shared/src/general.ts下的isBuiltInDirective也用到了makeMap方法:

const isBuiltInDirective = /*#__PURE__*/ makeMap(
  'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo'
)

23. cacheStringFunction 缓存

这个函数和上一个的MakeMap一样,但是接受的参数是函数。
它巧妙地使用闭包来做缓存,还通过传入函数参数的方法来实现代码复用,真是一举两得!

const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
  const cache: Record<string, string> = Object.create(null)
  return ((str: string) => {
    const hit = cache[str]
    return hit || (cache[str] = fn(str))
  }) as T
}

在源码中的后续几个方法中使用到了:

// \w 是 0-9a-zA-Z_ 数字 大小写字母和下划线组成
// () 小括号是 分组捕获
const camelizeRE = /-(\w)/g;
/**
 * @private
 */
// 连字符 - 转驼峰  on-click => onClick
const camelize = cacheStringFunction((str) => {
    return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});
// \B 是指 非 \b 单词边界。
const hyphenateRE = /\B([A-Z])/g;
/**
 * @private
 */

const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());

// 举例:onClick => on-click
const hyphenateResult = hyphenate('onClick');
console.log('hyphenateResult', hyphenateResult); // 'on-click'

/**
 * @private
 */
// 首字母转大写
const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
/**
 * @private
 */
// click => onClick
const toHandlerKey = cacheStringFunction((str) => (str ? `on${capitalize(str)}` : ``));

const result = toHandlerKey('click');
console.log(result, 'result'); // 'onClick'

关于这边的replace方法,这边有介绍String.prototype.replace() – JavaScript | MDN

24. hasChanged判断是不是有变化

const hasChanged = (value: any, oldValue: any): boolean =>
  !Object.is(value, oldValue)

Object.is() 静态方法确定两个值是否为相同值:Object.is() – JavaScript | MDN

25. invokeArrayFns执行数组里的函数

const invokeArrayFns = (fns: Function[], arg?: any) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg)
  }
}



// 例子:这边copy若川的例子
const arr = [
    function(val){
        console.log(val + '的博客地址是:https://lxchuan12.gitee.io');
    },
    function(val){
        console.log('百度搜索 若川 可以找到' + val);
    },
    function(val){
        console.log('微信搜索 若川视野 可以找到关注' + val);
    },
]
invokeArrayFns(arr, '我');

当我们要一批函数时(可能会使用相同的参数),可以使用这种方法比较方便。

26. toNumber转数字

const looseToNumber = (val: any): any => {
  const n = parseFloat(val)
  return isNaN(n) ? val : n
}





toNumber('111'); // 111
toNumber('a111'); // 'a111'
parseFloat('a111'); // NaN
isNaN(NaN); // true

其实 isNaN 本意是判断是不是 NaN 值,但是不准确的。 比如:isNaN('a') 为 true。 所以 ES6 有了 Number.isNaN 这个判断方法,为了弥补这一个API。

Number.isNaN('a')  // false
Number.isNaN(NaN); // true

这边重点关注isNaN和Number.isNaN是不一样的就可以了。

27. globalThis全局对象

let _globalThis: any
const getGlobalThis = (): any => {
  return (
    _globalThis ||
    (_globalThis =
      typeof globalThis !== 'undefined'
        ? globalThis
        : typeof self !== 'undefined'
        ? self
        : typeof window !== 'undefined'
        ? window
        : typeof global !== 'undefined'
        ? global
        : {})
  )
}

在不同的环境下,全局this的不一样的。
全局属性 globalThis 包含全局的 this 值。globalThis – JavaScript | MDN
通过上面的代码,我们就可以在不同的环境下使用不同的getGlobalThis,并且只需要进行一次判断。

总结

通过学习本期源码,学习到了很多实用的函数和编程思想,感受到了闭包的强大之处(如第23个函数做缓存真的是太妙)。还趁此学习一些typescript的相关内容,了解之前没有接触过的API。
接下去会继续阅读源码,继续进步~

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYwKy1wg' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片