VUE3 源码寻宝(一) - 大佬写的「类型判断」

173 阅读6分钟

内容概要:

  • 源码寻宝的初衷
  • 源码学习- VUE3 中的数据类型判断
  • 总结

一、源码寻宝的初衷

1、源码-让我痛苦的天书

说实话第一次读VUE3.0源码的体验确实一点都不好, 之前听同事说读源码能了解框架的精髓,学习作者框架思想,提升的编程思路等,面试也会涉及源码。 抱着研究武功秘籍的心态,激动的打开源码,惊奇的发现里面几千个函数,层层调用,各种设计模式,然后我就蒙蔽了,别说看框架思路, 就连多进入几层函数调用之后,都会迷失,一团乱麻,找不到之前读到哪了, 源码虽然很重要,但带给我的是望而却步和遥不可及,痛苦和枯燥扑面而来。 不知道有没有同感的小伙伴,难道只有我是这样吗?

2、换个角度 柳暗花明

辗转反侧,框架博大精深,虽痛苦 但不能放弃,整体设计思想,各种牛X架构看不懂就看不懂吧,如果在框架中能学到一个零碎的小知识点也行,再大的框架在实现也会涉及到各种小细节和常用的基础功能, 比如 变量声明、 类型判断、错误处理,参数校验,分支逻辑处理等等,这些功能既常用又实用, 能学习到VUE大佬能如何实现这些基础功能,对后续的编码也会有很大帮助吧,抱着不贪多不放弃,学一点算一点的心态, 再看起源码来果然轻松多了。

3-不放弃-小收获:

抱着好奇的心态,在源码中东看西逛,还真发现有一些超级的实用的小收获,我想陆续分享给出来,一起交流进步!

二、源码学习- VUE3 中的数据类型判断

今天和大家一起来分享学习 VUE3 源码中几乎任何项目都必用的逻辑-- 判断数据类型, 来看看大佬们是如何来实现的吧!

1. 源码位置

  1. 使用命令: npm i vue 安装 Vue
  2. 找到文件目录 node_modules/vue/dist/vue.global.js, 里面封装了大量的 vue 方法
  3. 位置:找到 343-366 行
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
const isArray = Array.isArray;
const isMap = (val) => toTypeString(val) === "[object Map]";
const isSet = (val) => toTypeString(val) === "[object Set]";
const isDate = (val) => toTypeString(val) === "[object Date]";
const isFunction = (val) => typeof val === "function";
const isString = (val) => typeof val === "string";
const isSymbol = (val) => typeof val === "symbol";
const isObject = (val) => val !== null && typeof val === "object";
const isPromise = (val) => {
  return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const toRawType = (value) => {
  // extract "RawType" from strings like "[object RawType]"
  return toTypeString(value).slice(8, -1);
};
const isPlainObject = (val) => toTypeString(val) === "[object Object]";
const isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key;

上面的代码看起来是不是通俗易懂, 就算你是刚入门的小白, 也有兴趣读下去吧,简单又超级实用, 甚至可以直接拿到项目中使用, 这样看源码谁能不爱呢, 接下来一起来从源码中汲取营养吧!

2. 源码解读(个人观点,欢迎指正)

既然是大佬写的代码,一定有很多值得学习的地方,下面就依我小白的视角,分成片段来解读吧!

片段1:

const hasOwnProperty = Object.prototype.hasOwnProperty; // 检测属性是否为对象的自有属性
const objectToString = Object.prototype.toString; 
const isArray = Array.isArray; // 判断是否为数组

上面三行代码,作者将对象的原型上的 hasOwnProperty()、toString()方法, 数组的静态方法 isArray(), 赋值给了对应的变量, 这么写看似有点多此一举,会有什么好处呢?

  1. 上面三个方法在Vue源码中频繁使用,赋值给变量后, 调用更方便, 代码更简洁, 不需要每次都书写繁杂的x.y.z...
  2. hasOwnProperty,objectToString, isArray 有很好的语义性, 提高源码的可读性

片段2:

const isFunction = (val) => typeof val === "function";  // 判断是否为函数
const isString = (val) => typeof val === "string"; // 判断是否为字符串
const isSymbol = (val) => typeof val === "symbol"; // 判断是否为symbol类型

上面三行代码,使用了typeof 来判断数据类型, 我记得 typeof 可以准确的判断基础数据类型(字符串、数字、布尔、symbol等),引用类型出来函数,其他的都返回object,所以代码实现了 函数、数字、symbol类型的判断,

  • 但这里我有疑问了?大佬们为啥 没有用这种方式来 判断基础类型中的数字和布尔类型呢?经过了一系列的查缺补漏,我想是这样的吧:
  1. typeOf 判断 NaN 的结果也是nunber, 判断有效数字时,是有必要排除NaN的, 这里不妨看下源码中415行实现的toNumber()方法
// 处理数字时需要考虑NaN 的情况 
const toNumber = (val) => {
    const n = parseFloat(val);  // 将传入的参数转成浮点型 
    return isNaN(n) ? val : n;  // 参数非数字的情况下,会转成NaN,结果非数字直接返回原来的值,如果数字则返回数值
};
  1. 布尔类型为什么没有实现呢? 布尔数据本身就是用于判断条件的,直接交给if-else, for/while循环去判断不就好了,还要自己判断自己吗? 哈哈哈...

片段3:

const isObject = (val) => val !== null && typeof val === "object";  // 判断是否为对象 
const isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key;  // 判断是否为字符串类型的数字
  1. typeof null 的结果也是object ,只是历史原因导致的, 判断对象时注意一下
  2. isIntegerKey() 是在判断key是否为十进制的正整数
  3. 知识点:parseInt()的第二个参数 表示进制

片段4:

const hasOwn = (val, key) => hasOwnProperty.call(val, key); // 判断val 自身是否用于 key属性
const toTypeString = (value) => objectToString.call(value); // 获取value的字符串类型 [object XXXX], XXXX就是类型

上面的代码,通过call函数调用, 将this执行call的第一个参数, 这样使传入的任何数据,都可以使用上面的两个功能了,666

片段5:

const isMap = (val) => toTypeString(val) === "[object Map]"; // 判断Map类型
const isSet = (val) => toTypeString(val) === "[object Set]"; // 判断Set类型
const isDate = (val) => toTypeString(val) === "[object Date]"; // 判断Date类型
const isPlainObject = (val) => toTypeString(val) === "[object Object]"; // 判断object类型
const toRawType = (value) => {
  // extract "RawType" from strings like "[object RawType]"
  return toTypeString(value).slice(8, -1);
};

上面的代码, 本质都是利用了Object.prototype.toString().call() 来实现的,toRawType()函数进行了再次封装,可以获取传入数据的类型字符串

片段6:

// 判断是否为Promise对象 
const isPromise = (val) => {
    return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};

上面的函数判断是否为Promise类型,个人认为这是一个比较宽泛的判断,如果是一个对象,这个对象有then()、catch()方法,行了,这个的话 伪Promise对象也会被判断成功的,这样的会有更好的拓展性,不仅能支持原生的Promise,也可以支持符合Promise调用规范的其他对象哦

三、总结

到此为止,Vue3.0常用类型判断部分我们就解读完啦,这样看来源码是不是也没有那么难,并且还能学习到大佬们优秀的编程习惯和技巧,顺便也能收集点好用的工具函数库(HAHAHA,这是我最爱干的), 何乐不为呢,有兴趣的小伙伴们一起加入进来吧, 下一篇准备给大家分享 Vue源码中封装的超强大超好用的 判断任意类型相等的 looseEqual函数,敬请期待吧!