源码学习——vue3中的那些工具函数

600 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

**这是源码共读的第2期,链接:juejin.cn/post/708499…

前言

前面写了vue2和axios中使用的工具函数,学习这些通用的工具函数,可以让我们在日常开发中实际使用,而且可以锻炼自己抽离通用函数的能力,怎样组织自己的工具函数库。本篇文章来学习下vue3中的一些工具函数

有很多函数在vue2中已经学习过,本篇文章就介绍一些不曾学过,或有新体会的函数。

函数学习

noop 空函数

const NOOP = () => {}

为什么要定义一个空函数呢?作用有两个:1、方便判断。2、方便压缩。

isIntegerKey 判断是不是数字型的字符串key值

const isIntegerKey = (key) => isString(key) &&
    key !== 'NaN' &&
    key[0] !== '-' &&
    '' + parseInt(key, 10) === key;
    
// 用于这种类型
const obj = {
   1: 'a',
   2: 'b',
   3: 'c'
};
// 事例
isIntegerKey('a'); // false
isIntegerKey('0'); // true
isIntegerKey('011'); // false
isIntegerKey('NaN'); // false
  • 判断的key是以10进制来进行计算的

makeMap 检查是否存在key值

传入一个以逗号分隔的字符串,生成一个map对象,并返回一个函数检测传入的key值是否在这个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];
}

以这个函数为基础,产出具体的判断函数。 isGloballyWhitelistedisSpecialBooleanAttrisNoUnitNumericStylePropisKnownHtmlAttrisHTMLTagisReservedProp等等。 这里以isReservedProp,来看下使用

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

这个判断方案值得学习,可能在业务中这种判断的情况不多,但不妨碍我们去学习理解如何更好的组织代码,如何去抽象我们的逻辑。

cacheStringFunction 缓存处理结果

const cacheStringFunction = (fn) => {
    const cache = Object.create(null);
    return ((str) => {
        const hit = cache[str];
        return hit || (cache[str] = fn(str));
    });
};
  • 这个处理也是很实用的,我们很多不变数据需要进行二次处理,利用闭包将结果缓存,减少冗余处理。

正则处理字符串

camelize 连字符转小驼峰

const camelizeRE = /-(\w)/g;
/**
 * @private
 */
const camelize = cacheStringFunction((str) => {
    return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});
// camelize('on-click'); // onClick
  • -(\w)\w是0-9a-zA-Z_ 数字、大小写字母和下划线组成
  • 这里利用cacheStringFunction缓存处理结果,相同入参下,第二次可以直接获取缓存的数据,而不用再次处理。
  • 正则替换。replace的回调函数中的参数_就是我们匹配到的结果,这里_'_c'。而第二个参数c就是小括号捕获到的内容也就是'c'。将匹配的结果替换为捕获的值的大写,就将连字符转为小驼峰了。

hyphenateRE 驼峰转连字符

const hyphenateRE = /\B([A-Z])/g;
/**
 * @private
 */
const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());
// hyphenate('onClick'); // on-click
  • \B 非单词边界。这个正在/\B([A-Z])/g就是匹配所有的 非单词边界+大写字母。
  • 看上面的字符串'onClick''nC'之间包含一个非单词边界,即匹配到的内容是:(无界)C
  • str.replace(hyphenateRE, '-$1')就是把匹配到的内容替换为前面带连字符的内容,也就是上面的(无界)C替换成'-C';
  • 这时拿到的内容就是'on-Click',然后利用toLowerCase方法将所有字母都小写,最终得到'on-click';

什么是单词边界和非单词边界。

\b 单词边界

匹配一个单词的边界,也就是单词和空格的位置。不同则为界,界左和界右肯定不是相同类型的

image.png

  • 边界只匹配位置,不匹配字符,也就是只是个边界。可以看上图粉色竖线标记的地方,
  • 如上面的单词This左右各一个边界。

\B 非单词边界

匹配非单词边界,也就是不是单词和空格的位置

image.png

  • 看上图,也就是粉色竖线标记的位置。
  • 看This这个单词,匹配到3处非单词边界,都是字母挨着字母,字母和字母是同类,同类无界,但匹配非界就匹配到了。

capitalize 首字母大写

const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
capitalize('click'); // Click

利用charAt方法获取字母的第一个字符转为大小,然后利用字符串的slice方法,将之后的字符截取拼接成最后的字符串。

toHandlerKey 处理事件key

const toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : ``);
// toHandlerKey('click'); // onClick

hasChanged

const hasChanged = (value, oldValue) => !Object.is(value, oldValue);

这里用到了Object.is这个方法。它与严格比较(===)的行为基本一致。不同之处一个是+0和-0不相等,另一个就是NaN跟NaN相等。

Object.is(NaN, NaN); // true
NaN === NaN; // false

Object.is(+0, -0); // false
+0 === -0; // true

invokeArrayFns 顺序调用数组里的方法

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

这里没有看源码中的使用,感觉是一种多函数的调度用法,通过数组遍历顺序执行。

收获

  • 正则相关。了解到单词边界和非单词边界的正则含义及应用。
  • Object.is() 方法的学习。
  • invokeArrayFns这个方法让我想到在业务中可以处理一些调度方案,一系列的函数,如何组织它们的执行,就可以利用数组的方式将其组织起来。