本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
学习目标
- 学习 vue 3 源码编译;
- 学习优秀开源项目的代码,应用到自己的项目;
- Vue 3 源码 shared 模块中的几十个实用工具函数;
源码地址
本地编译shared模块
可参考代码贡献指南
- 克隆官方项目
git clone git@github.com:vuejs/core.git
建议安装ni来帮助使用不同的包管理器在repo之间进行切换。Ni还提供了方便的nr命令,可以更容易地运行NPM脚本。
- 安装项目依赖包
方式一: 使用ni
npm i -g @antfu/ni
ni
# npm install
# yarn install
# pnpm install
方式二:直接使用pnpm
pnpm i
- 打包shared模块
// 打包所有包
nr build
// 打包某个模块 nr build packagename
nr build shared
vue包含的模块包如图:
模块依赖关系如图
此时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可以知道值的具体类型,包含Map和Set,他们是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 == NaN和NaN === 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 指向。
初次执行肯定是 _globalThis 是 undefined。所以会执行后面的赋值语句。
如果存在 globalThis 就用 globalThis。globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。MDN globalThis
如果存在self,就用self。在 Web Worker 中不能访问到 window 对象,但是我们却能通过 self 访问到 Worker 环境中的全局对象。
如果存在window,就用window。
如果存在global,就用global。Node环境下,使用global。
如果都不存在,使用空对象。可能是微信小程序环境下。
下次执行就直接返回 _globalThis,缓存全局对象,这里值得学习。
总结
- get到一个新的包管理工具,随意切换包管理工具。
ni了解一下,蛮好用的! - 基础的函数要灵活使用,虽然基础,但是不注意的话就会遇到未知的bug,所以对于基础还是要细致的学习。比如
parseFloat、globalThis。