Weekbin

一个厉害的前端新手

LV0
已经发布0篇文章,距离下一等级还需发布1篇文章

Vue 源码精读(一)

为什么一上来不讲 vue 的构造函数,生命周期,响应式原理等等源码,而实选择工具函数呢? 诚然,读源码不可能一蹴而就,更不可能一学而会。我们先把这些简单的一看就懂的工具函数搞定,之后看源码的过程将更加轻松。 工具函数这部分源码不仅仅能学习到良好的编码风格,还能学习到许多巧妙的点,顺便还能增强自我信心,岂不妙哉?

该篇精读 vue 源码中的工具函数(每个函数都读),代码位于 src/shared/utils.js。

1. 语义化单行函数

首先看四个简单的单行函数:isUndef, isDef, isTrue 和 isFalse。

这四个函数虽然相当简单,但目的是通过其函数名就可以看出其语义。在实际编程场景下,我们经常被同事不标准、不规范的命名而折磨。 这种语义化的单行函数,个人觉得还是相当推荐的。

code2

2. 原始值和非原始值

isPrimitive 和 isObject 是功能成对的两个工具函数,用于判断 JSON 对象中的 value 是原始值还是对象。 因为 Array typeof obj === 'obj' 也为 true,故在一般情况的 JSON 中,只要是数组或对象,isObject 就会返回 true; 反之原始值的情况就会返回 false。

code3

3. 精确判断类型

以下三个函数:toRawType、isPlainObject 和 isRegExp 用于精确判断对象类型。

toRawType 在源码中多用于异常处理时精准的报出错误数据的类型(截取 [object Object] 字符中的具体字段如 Object)。toString 的输出结果通常由 Symbol.toStringTag 决定,点击查看详情。不仅仅可以输出基本的数据类型 tag,也可以输出自定义结构的数据类型 tag。

isPlainObject 用于判断对象是否是个”干净“的对象,即 {}。

isRegExp 用于判断对象是否是个正则表达式。

code4

isValidArrayIndex 用于判断入参是否是有效的 index 值。当我们用下标取数组值时,下标是数字和字符串的表现形式是一致的,故这里无论入参是什么都转换成了数字对象做处理(如果入参是 undefined,n将输出 NaN,同样会 return false)。这里调用了以下 String 构造函数,主要是避免异常,避免入参是个 symbol 类型。

isPromise 用于判断一个对象是否满足 thenable 和 catchable 接口,满足则认为其是个 promise 对象。

code5

toString 函数与 Object.prototype.toString 方法不通,不是调用默认的方法,而是判断数据类型,如果是原始值,调用构造函数 String 转化为字符串,如果是数组或是JSON对象,则直接 JSON.stringify。

code6

  1. 高阶函数

熟悉 react 的应该了解一个概念叫高阶组件,接收一个组件为参数,并返回一个新组件。其用意为在不影响原有组件功能的情况下,拓展一些组件的功能。vue 源码中下面两个函数也类似,利用闭包做存储,优化性能。

makeMap 函数返回一个判断函数,规则为第一个参数输入的序列字符串,如果匹配返回 true,如果不匹配返回 false。该函数首先将字符串按照逗号分隔为数组,然后将每一项存储入一个对象,并赋值为 true。之后判断函数调用的时候就会搜索到闭包里的这个对象,如果匹配则返回 true,不匹配则是 undefined。

code7

cached 函数接收一个函数,其利用闭包,存储函数执行的结果,避免重复执行,优化性能。

camelize,capitalize 和 htphenate 函数声明时分别都调用了一次 cached 函数,故他们每个人都执行了一次 Object.create(null),也就是意味着他们有三个独立的闭包 cache,这个缓存各自独立。cache 会记录函数的执行结果,如果匹配到相同的入参,则不会进行运算,会直接返回之前的运行结果;如果没有匹配到直接的结果,会调用函数并将执行结果进行记录。

\w 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]

\B 匹配一个非单词边界。e,g:

'hello world weekbin'.replace(/\B/g, '-')
// "h-e-l-l-o w-o-r-l-d w-e-e-k-b-i-n"

具体的正则相关问题可查看 MDN 正则表达式

code9

5. 降级处理

下面是 bind 函数的降级处理方式,首先是适配器模式,如果 Function.prototype.bind 存在则返回 nativeBind;如果不存在则返回用 call 或者 apply 处理的函数。

code10

6. 数组对象的拓展方法

remove 接收两个参数,数组和特定元素,找到其并删除。但 remove 函数只会删除第一个匹配的元素。

hasOwn 是 Object.prototype.hasOwnProperty 别名,判断对象自身(包括 symbol)是否存在某属性,而不是在原型链上。

code8

toArray 的目的非常简单,就是把类数组结构变成数组结构。不用 for... in 或者拓展运算符的原有是因为防止类数组没有实现 Iterator 接口。

extend 函数的目的就是把 from 对象的属性浅拷贝到 to 上面,属性相同则覆盖。

toObject 的入参是个对象数组,目的是把所有对象的属性都浅拷贝到一个对象上。

code11

7. 风格友好型函数

noop 函数就是什么也不做,但是为了代码的一致性,写一个空函数。

no 函数无论入参是什么,始终返回 false。

identity 函数的存在的原因还是因为代码的一致性,可以理解为空过滤器函数。

code12

8. 辅助型函数

looseEqual 判断两个对象的数据结构(包括键和值)是否相同。

looseEqual 在一个对象中查找是否存在数据结构相同的对象(包括键和值)并返回索引。

once 函数利用闭包保证 fn 只会被执行一次。

code13

up-to-top