【源码学习】探索 vue3 基础工具函数之旅【2】

简介

本文档记录了我参加 vue3源码中的基础工具函数 源码共读的过程中的学习和思考。

参与目的:

  • 探索源码,理解工作原理
  • 学习和成长
  • 提升技术水平

作为一名前端开发者,参与这次源码共读活动。这是一个很好的机会,与志同道合的开发者一起深入探索源码、理解工作原理,并在过程中学习和成长。

源码分析

1、源码准备

1.1、学习shared模块

可参考代码贡献指南 对应的文件路径是:vue-next/packages/shared/src/index.ts

也可以用github1s访问,速度更快。github1s packages/shared/src/index.ts

1.2、按照项目指南 打包构建代码

我是直接使用的若川老师的源码。

如果不想克隆以下源码,也可以自己打包,可以参考以下要求进行打包构建。
Node.js 版本要求是 10+
Yarn 的版本要求是 1.x Yarn 1.x 可自行下载

源码克隆
bash 复制代码
#若川老师源码
git clone https://github.com/lxchuan12/vue-next-analysis.git
cd vue-next-analysis/vue-next

#官网源码
git clone https://github.com/vuejs/vue-next.git 
cd vue-next 

#全局安装yarn
npm install --global yarn 
#安装工具包
yarn # install the dependencies of the project 
#打包
yarn build

node -v 查看电脑上的node.js版本

克隆源码可以得到 vue-next/packages/shared/dist/shared.esm-bundler.js,文件是纯js文件。

2、工具函数

本文主要按照源码 vue-next/packages/shared/src/index.ts 的顺序来学习的

是通过ts文件,查看使用函数的位置。同时在VSCode运行调试JS代码,并使用了韩老师写的code runner插件实现的共读调试

2.1、babelParserDefaultPlugins babel 解析默认插件

javascript 复制代码
const babelParserDefaultPlugins = [
    'bigInt',
    'optionalChaining', 
    'nullishCoalescingOperator' 
];

2.2、EMPTY_OBJ 空对象

javascript 复制代码
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
? Object.freeze({})
: {};

通常是一个预先定义好的空对象常量,用于在需要一个空对象作为默认值、占位符或者在某些特定场景下优化性能时使用。

通过Object.freeze 是 冻结对象,注:冻结的对象最外层是无法修改的

2.3、### EMPTY_ARR 空数组

javascript 复制代码
const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];

是一个预先定义好的空数组常量,通常用于初始化变量、作为默认值或者在特定场景下优化性能,所以生产环境一般使用 []

2.4、NOOP 空函数

javascript 复制代码
export const NOOP = () => {}

表示一个空函数,它不执行任何操作,仅仅是为了满足某种需要函数的接口设计或者用于占位等场景

2.5、NO 永远返回 false 的函数

javascript 复制代码
const NO = () => false;

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

javascript 复制代码
const onRE = /^on[^a-z]/;
const isOn = (key) => onRE.test(key);

onRE 是正则。^符号在开头,表示是什么开头。而在其他地方是指非。

$符合在结尾,表示是以什么结尾。

[^a-z]是指不是az的小写字母。

注:插播一条若川老师推荐的正则迷你书,感兴趣的可以看一下

老姚:《JavaScript 正则表达式迷你书》问世了!

2.7、isModelListener 监听器

判断字符串是不是以onUpdate:开头

javascript 复制代码
const isModelListener = (key) => key.startsWith('onUpdate:');

是一个用于检测或验证某个对象是否符合模型变更监听器接口的函数。在某些编程框架或数据驱动的应用中,模型对象的数据变化时,可能会触发一系列事件或者通知给注册了这些变化事件的监听器。

可以参考阅读以下内容 ES6入门教程:字符串的新增方法

2.8、extend 继承 合并

javascript 复制代码
const extend = Object.assign;

这个函数的主要目的是将一个或多个源对象(source object)的所有可枚举属性复制到目标对象(target object)上

2.9、remove 移除数组的一项

javascript 复制代码
const remove = (arr, el) => {
   const i = arr.indexOf(el);
   if (i > -1) { arr.splice(i, 1); 
  } 
};

Vue3 源码中并未直接提供一个名为 remove 的函数来移除数组的一项,但在 Vue2 中有一个 $set$delete 方法可以间接实现数组元素的添加和删除。而在 Vue3 中,你可以直接使用 JavaScript 内置的数组方法如 splice 来实现

在 Vue3 的响应式编程中,由于它采用 Proxy 实现了深度监听,所以直接操作数组的这些原生方法(如 pushpopshiftunshiftsplice 等)会触发视图更新

2.10、 hasOwn 是不是自己本身所拥有的属性

javascript 复制代码
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);

hasOwn 是 Vue3 源码中的一个工具函数,用于判断对象是否拥有某个自身属性(不包括原型链上的属性)。这个函数主要用于避免在检查对象属性时触发 getter,它接收两个参数:一个对象和一个属性名。如果对象直接拥有指定的属性(非继承自原型),则返回 true,否则返回 false

2.11、isArray 判断数组

javascript 复制代码
const isArray = Array.isArray;

在 Vue3 源码中,并没有直接定义名为 isArray 的工具函数,因为 JavaScript 自身已经提供了这个全局方法。

2.12、isMap 判断是不是 Map 对象

javascript 复制代码
const isMap = (val) => toTypeString(val) === '[object Map]';

isMap 函数用于判断一个值是否为 JavaScript 的 Map 对象。在 Vue3 源码中虽然没有直接提供 isMap 函数,但你可以根据以下示例实现:

2.13、isSet 判断是不是 Set 对象

javascript 复制代码
const isSet = (val) => toTypeString(val) === '[object Set]';

// 使用示例
let mySet = new Set();
console.log(isSet(mySet)); // 输出: true
console.log(isSet([])); // 输出: false

Set本身是一个构造函数,用来生成 Set 数据结构。

2.14、isDate 判断是不是 Date 对象

javascript 复制代码
const isDate = (val) => val instanceof Date;

isDate 函数用于判断一个值是否为 JavaScript 的 Date 对象。这个函数通过 instanceof 操作符来检查传入的值是否是 Date 类型的实例

2.15、isFunction 判断是不是函数

javascript 复制代码
const isFunction = (val) => typeof val === 'function';

isFunction 函数用于判断一个值是否为 JavaScript 的函数类型。这个函数通过 typeof 操作符来检查传入的值是否为 'function' 类型

2.16、isString 判断是不是字符串

javascript 复制代码
onst isString = (val) => typeof val === 'string';
// 例子: isString(123) // false
// 例子: isString('123') // true

sString 函数用于判断一个值是否为 JavaScript 的字符串类型

2.17、isSymbol 判断是不是 Symbol

javascript 复制代码
const isSymbol = (val) => typeof val === 'symbol';

// 使用示例
console.log(isSymbol(Symbol('key'))); // 输出: true
console.log(isSymbol('not a symbol')); // 输出: false

let sym = Symbol();
console.log(isSymbol(sym)); // 输出: true

通过 typeof 操作符来检查传入的值是否为 'symbol' 类型

2.18、isObject 判断是不是对象

javascript 复制代码
const isObject = (val) => val !== null && typeof val === 'object';

// 使用示例
console.log(isObject({ a: 1 })); // 输出: true
console.log(isObject([1, 2, 3])); // 输出: true
console.log(isObject(function() {})); // 输出: true
console.log(isObject('not an object')); // 输出: false
console.log(isObject(null)); // 输出: false

let obj = {};
console.log(isObject(obj)); // 输出: true

用于判断一个值是否为 JavaScript 的对象类型(包括普通对象、数组、函数等,但不包括 null)。这个函数首先检查传入的值是否不等于 null,然后通过 typeof 操作符确认其类型是否为 'object'

2.19、isPromise 判断是不是 Promise

javascript 复制代码
const isPromise = (val) => { 
   return isObject(val) && isFunction(val.then) && isFunction(val.catch); 
};

用于判断一个值是否为 JavaScript 的 Promise 对象。这个函数首先检查传入的值是否为对象且不为 null(使用 isObject(val)),然后进一步确认该对象是否具有 thencatch 方法,并且这两个方法都必须是函数类型(使用 isFunction(val.then)isFunction(val.catch)

2.20、objectToString 对象转字符串

javascript 复制代码
const objectToString = Object.prototype.toString;

2.21、toTypeString 对象转字符串

javascript 复制代码
const toTypeString = (value) => objectToString.call(value);

// 使用示例
console.log(toTypeString({})); // 输出: "[object Object]"
console.log(toTypeString([])); // 输出: "[object Array]"

call 是一个函数,第一个参数是 执行函数里面 this 指向

2.22、toRawType 对象转字符串 截取后几位

javascript 复制代码
const toRawType = (value) => {
     // extract "RawType" from strings like "[object RawType]" 
     return toTypeString(value).slice(8, -1); 
 };
 
// 使用示例
console.log(toRawType({})); // 输出: "Object"
console.log(toRawType([])); // 输出: "Array"

toTypeString(value) 来获取一个值的详细类型字符串,然后截取其中的原始类型名称部分

2.23、isPlainObject 判断是不是纯粹的对象

javascript 复制代码
const isPlainObject = (val) => toTypeString(val) === '[object Object]';

isPlainObject 函数用于判断一个值是否为普通对象(即由 {}new Object() 创建的对象,不包括数组、函数、null、Date 等其他类型)。它通过调用 toTypeString(val) 并检查返回结果是否等于 [object Object] 来完成判断

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

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

// 使用示例
console.log(isIntegerKey('123')); // 输出: true
console.log(isIntegerKey('-123')); // 输出: false(这里假设只接受非负整数)
console.log(isIntegerKey('abc')); // 输出: false
console.log(isIntegerKey('0')); // 输出: true
console.log(isIntegerKey('NaN')); // 输出: false

isIntegerKey 函数用于判断一个字符串是否表示一个整数值。函数执行以下检查:

  1. 使用 isString(key) 验证输入的 key 是否为字符串类型。
  2. 检查字符串是否不等于 'NaN',因为这不是一个有效的整数。
  3. 确保字符串的第一个字符不是减号(-),因为在 JavaScript 中负数也可以表示整数,但在这里可能仅希望处理非负整数键值。
  4. 最后,将字符串转换为整数并用字符串形式比较原始输入。如果转换后的整数字符串与原始输入相同,则认为这个键是一个整数值。

2.25、makeMap && isReservedProp

javascript 复制代码
/** * 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. 
*/ 
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]; 
} 
      const isReservedProp = /*#__PURE__*/ makeMap( 
      // the leading comma is intentional so empty string "" is also included 
      ',key,ref,' + 'onVnodeBeforeMount,onVnodeMounted,' + 
      'onVnodeBeforeUpdate,onVnodeUpdated,' + 
      'onVnodeBeforeUnmount,onVnodeUnmounted'
      );

makeMap 函数内部:

  1. 创建一个空的 map 对象来存储键值对。
  2. str 字符串按逗号分割成 list 数组。
  3. 遍历 list 数组,并将每个元素作为键添加到 map 中,其值设为 true
  4. 根据 expectsLowerCase 参数创建并返回一个新函数:
    • 如果 expectsLowerCasetrue,则返回的函数会接收一个参数 val,将其转换为小写后查找在 map 中是否存在;
    • 如果 expectsLowerCasefalse 或未提供,则返回的函数直接接收一个参数 val 查找在 map 中是否存在。

然后,通过调用 makeMap 函数创建了 isReservedProp 变量,它是一个用来判断 Vue 组件属性是否为保留属性(reserved prop)的函数。在这个例子中,提供的字符串包含了 Vue 生命周期钩子以及 "key" 和 "ref" 等特殊属性名。这意味着当我们使用 isReservedProp 函数检查一个属性名时,如果该属性名出现在上述列表中,函数将返回 true,否则返回 false

2.26、 cacheStringFunction 缓存

javascript 复制代码
const cacheStringFunction = (fn) => {
   const cache = Object.create(null); 
   return ((str) => { 
     const hit = cache[str]; 
     return hit || (cache[str] = fn(str)
     ); 
    });
   };

cacheStringFunction 是一种优化策略,通常用于缓存字符串操作结果以提高性能

2.27、hasChanged 判断是不是有变化

javascript 复制代码
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);

hasChanged 函数在 Vue3 源码中并未直接出现,但在响应式原理中,Vue 会判断一个依赖属性值是否发生了变化。这种逻辑可以体现在 reactiveref 等响应式数据对象的更新检测机制中。

例如,在 Vue3 的响应式系统内部(以 reactive 为例),当一个被代理的对象属性被访问时,它会被追踪并记录到对应的依赖收集链路中;当这个属性值发生改变时,Vue 会触发相应的副作用函数执行,这些副作用函数通常关联着视图的更新或其他计算属性的重新求值。

2.28、invokeArrayFns 执行数组里的函数

javascript 复制代码
const invokeArrayFns = (fns, arg) => { 
   for (let i = 0; i < fns.length; i++) {
   fns[i](arg); 
  } 
 };
 
// 例子
const callbacks = [
  (arg) => console.log('Callback 1:', arg),
  (arg) => console.log('Callback 2:', arg),
  (arg) => console.log('Callback 3:', arg)
];

// 调用 invokeArrayFns 并传入回调函数数组和参数
invokeArrayFns(callbacks, 'Hello, World!');

// 输出:
// Callback 1: Hello, World!
// Callback 2: Hello, World!
// Callback 3: Hello, World!

invokeArrayFns 函数用于遍历一个函数数组,并依次调用每个函数并将相同的参数传递给它们

2.29、def 定义对象属性

javascript 复制代码
const def = (obj, key, value) => { 
  Object.defineProperty(obj, key, { 
  configurable: true, enumerable: false, value
  }); 
 };

def 函数是用于定义对象属性的一个便捷工具,它使用 Object.defineProperty 方法来设置对象的属性,并且指定了该属性的一些特性。在 Vue3 源码中(或与之相关的库)可能会用到这样的函数,用于实现一些特定需求。

这个函数接收三个参数:

  1. obj:要定义属性的对象。
  2. key:要定义的属性名。
  3. value:要赋予该属性的值。 通过 Object.defineProperty 设置的属性具有以下特性:
  • configurable: true 表示可以修改属性的描述符,或者可以删除该属性。
  • enumerable: false 表示该属性不会出现在 for...in 循环和 Object.keys() 中。
  • value 被赋值为传入的第三个参数,即所要定义的属性值。

2.30、toNumber 转数字

javascript 复制代码
const toNumber = (val) => { 
   const n = parseFloat(val);
   return isNaN(n) ? val : n; 
  };
  
// 示例用法:
console.log(toNumber('123')); // 输出: 123 (转换成功)
console.log(toNumber('abc')); // 输出: "abc" (转换失败,返回原始值)
console.log(toNumber('3.14')); // 输出: 3.14 (转换成功)
console.log(toNumber(null)); // 输出: 0 (转换失败,但 parseFloat 返回的是 0,所以返回 0)
console.log(toNumber(undefined)); // 输出: NaN (转换失败,但 parseFloat 返回的是 NaN,所以返回原始值 undefined)

toNumber 函数的作用是将传入的值转换为数字类型。函数内部首先使用 parseFloat 方法尝试将输入值转换为浮点数,然后通过 isNaN 函数检查转换后的结果是否为 NaN(Not a Number),即无法表示为数字的情况。

如果转换成功且得到的是一个有效的数字,则返回这个数字;否则,说明原始值无法有效转换为数字,函数则返回原始值本身。

2.31、getGlobalThis 全局对象

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

getGlobalThis 函数的主要目的是获取全局对象(global object),即当前执行环境下的全局上下文。在不同的 JavaScript 环境中,全局对象可能有不同的名称:在浏览器环境中是 window,在 Node.js 中是 global,而在现代浏览器和一些支持 ES6 的环境中则是 globalThis。 函数首先检查是否已经存在 _globalThis 变量缓存了全局对象,如果存在则直接返回;否则,它会按照以下顺序依次尝试查找并赋值给 _globalThis

  1. 检查是否存在全局变量 globalThis
  2. 如果不存在,则检查是否存在全局变量 self(通常在 Web Workers 和浏览器环境下可用)。
  3. 再次检查是否存在全局变量 window(仅在浏览器环境下可用)。
  4. 最后检查是否存在全局变量 global(通常在 Node.js 环境下可用)

总结

在通过本次 Vue3 源码中的基础工具函数源码共读的过程中,我了解了以下要点:

  1. 类型判断 :Vue3 源码中包含了多种用于检查变量类型的工具函数,如 isObjectisStringisFunctionisArrayisMapisSet 等。这些函数利用 JavaScript 内置的 typeofinstanceof 或者 Object.prototype.toString.call() 方法来准确识别各种数据类型。

  2. 对象操作工具

    • hasOwn(obj, key):检查对象自身(不包括原型链)是否具有某个属性。
    • def(obj, key, value):使用 Object.defineProperty 定义对象的属性,并控制其可配置性、可枚举性和值。
  3. 字符串处理与转换

    • camelize(str):将短横线分隔的字符串转为驼峰式命名。
    • toTypeString(value)toRawType(value):获取并解析值的类型字符串,以进行更精确的类型判断。
  4. 数组和集合操作

    • 虽然没有直接提供 remove 函数移除数组元素,但可以通过原生的 Array.prototype.splice 方法实现。
    • 类似的,提供了 isMapisSet 函数来判断一个值是否为 Map 或 Set 对象。
  5. 响应式系统相关

    • reactiverefcomputed 等函数构建了 Vue3 的响应式核心,它们内部会使用到很多基础工具函数来确保数据变化时视图更新。
  6. 缓存策略

    • 虽未找到名为 cacheStringFunction 的具体函数,但在 Vue3 源码中有类似的思想,即通过映射或缓存机制优化重复计算,提升性能。
  7. 执行回调函数

    • invokeArrayFns(fns, arg) 用于遍历并同步执行数组中的所有函数,传入相同的参数。
  8. 全局上下文获取

    • getGlobalThis() 函数用于在不同环境(浏览器、Node.js 等)下兼容地获取全局对象。

通过对 Vue3 源码中的基础工具函数进行深入阅读,不仅能够了解 Vue3 内部的工作原理,还能学习到如何高效地编写高质量且具有跨环境兼容性的工具函数。

相关推荐
Fan_web9 分钟前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常10 分钟前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr1 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho2 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常3 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记4 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java4 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele4 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范