【若川视野 x 源码共读】第2期 | vue3 工具函数

474 阅读4分钟

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

学习目标

  1. 学习 vue 3 源码编译;
  2. 学习优秀开源项目的代码,应用到自己的项目;
  3. Vue 3 源码 shared 模块中的几十个实用工具函数;

源码地址

github1s.com/vuejs/core/…

本地编译shared模块

可参考代码贡献指南

  1. 克隆官方项目
git clone git@github.com:vuejs/core.git
  1. 环境准备 Node.js version 16+ ,  PNPM version 7+ .

建议安装ni来帮助使用不同的包管理器在repo之间进行切换。Ni还提供了方便的nr命令,可以更容易地运行NPM脚本。

  1. 安装项目依赖包
方式一: 使用ni
npm i -g @antfu/ni

ni
# npm install
# yarn install
# pnpm install

方式二:直接使用pnpm

pnpm i

  1. 打包shared模块
// 打包所有包
nr build
// 打包某个模块 nr build packagename
nr build shared

vue包含的模块包如图:

image.png

模块依赖关系如图

image.png

此时shared模块下会多出dist目录,其中包含packages/shared/dist/shared.esm-bundler.js,这就是我们要学习的内容。

工具函数解析

1. isModelListener 是否为v-model监听函数

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

startsWith语法

用来判断当前字符串是否以另外一个给定的子字符串开头,并根据判断结果返回 true 或 false

str.startsWith(searchString[, position])

2. isMap & isSet

const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const isMap = (val) => toTypeString(val) === '[object Map]';
const isSet = (val) => toTypeString(val) === '[object Set]';

Object.prototype.toString可以知道值的具体类型,包含MapSet,他们是ES6的新数据结构,Set类似数组,但值唯一,Map类似对象,键值对结构,但键的类型不限于字符串。

3.isIntegerKey是否为int类型的字符串

条件:1.字符串 2. 不是NaN 3. 不以-开头 4. 与十进制值相等

const isIntegerKey = (key) => isString(key) &&
    key !== 'NaN' &&
    key[0] !== '-' &&
    '' + parseInt(key, 10) === key;

parseInt(string, radix)  解析一个字符串并返回指定基数的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数。

4. hasChanged 判断值是否被改变 包括NaN

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

Object.is() 判断两个值是否相等 满足以下条件则相等

  • 都是 undefined

  • 都是 null

  • 都是 true 或都是 false

  • 都是相同长度、相同字符、按相同顺序排列的字符串

  • 都是相同对象(意味着都是同一个对象的值引用)

  • 都是数字且

    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都是同一个值,非零且都不是 NaN 与 == 不同的是, Object.is 不会强制转换两边的值。

与 === 也不同, 它们对待有符号的零和 NaN 不同。

关于相等判断

5. toNumber

转换为浮点数,结果是NaN则返回原值

const toNumber = (val) => {
    const n = parseFloat(val);
    return isNaN(n) ? val : n;
};

parseFloat() 函数解析一个参数(必要时先转换为字符串)并返回一个浮点数。

// 自动忽略前后空格
parseFloat('  3.14  ');  // 3.14
 // 第二个.之后会被忽略
parseFloat('3.14.23'); // 3.14
 // 包含字符串 返回已解析的部分
parseFloat('3.14some non-digit characters'); // 3.14
// 对象有toString方法时,则先调用将其转换为字符串,然后parseFloat
parseFloat({ toString: function() { return "3.14" } });  // 3.14
parseFloat(Infinity); // Infinity
parseFloat(); // NaN
parseFloat(''); // NaN
parseFloat('s'); // NaN

Number(value) 进行更严谨的解析

Number() // 0
Number('') // 0
Number('1.23') // 1.23
Number('1.23ss') // NaN 只要参数带有无效字符就会被转换为NaN

isNaN 是全局函数,与ES6的Number.isNaN()的作用相同。因为NaN == NaNNaN === NaN都返回false。所以单独检测。

如果isNaN(x)返回false,那么x在任何算数表达式中都不会使表达式等于NaN;如果返回true,x会使所有算数表达式返回NaN。

// polyfill
var isNaN = function(value) {
    var n = Number(value);
    return n !== n;
};

6.getGlobalThis 获取当前环境的全局对象

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

获取全局 this 指向。

初次执行肯定是 _globalThisundefined。所以会执行后面的赋值语句。

如果存在 globalThis 就用 globalThisglobalThis 提供了一个标准的方式来获取不同环境下的全局 this  对象(也就是全局对象自身)。MDN globalThis

如果存在self,就用self。在 Web Worker 中不能访问到 window 对象,但是我们却能通过 self 访问到 Worker 环境中的全局对象。

如果存在window,就用window

如果存在global,就用globalNode环境下,使用global

如果都不存在,使用空对象。可能是微信小程序环境下。

下次执行就直接返回 _globalThis,缓存全局对象,这里值得学习。

总结

  1. get到一个新的包管理工具,随意切换包管理工具。ni了解一下,蛮好用的!
  2. 基础的函数要灵活使用,虽然基础,但是不注意的话就会遇到未知的bug,所以对于基础还是要细致的学习。比如parseFloatglobalThis