前言
我觉得每个人可能都有过看源码的想法吧,也包括我。因为看源码不光能使自己对这个库更加熟悉,还能学习到作者强大的思想,久而久之,自己的水平和思想也会有明显的提升的。
但对于我来说,之前从来没有阅读过源码,想阅读源码却不敢迈出那一步,因为一个成熟的库有着太多的方法、逻辑,阅读起来可能会比较困难,但人总要勇于尝试的嘛,于是我就准备把 Vuex
的源码 clone
下来,没有别的原因,只是因为这个库体积比较小,算上注释,核心代码只有1000行不到,我觉得非常适合第一次阅读源码的人拿来练手
说干就干,我就先在 github
上给自己列了一个计划表,预计 15
天看完源码并完成总结,然后每天记录一下当天的收获
不过最后的结果倒是出乎我的意料,阅读源码加上整理总结只用了8天左右的时间
在阅读源码之前,我是先去看了一下 Vuex
的官方文档,算是一种回顾、查漏补缺,我也非常建议这样做,因为你看源码,你就会看到这个库里面所有的内容,那么你连这个库都没用明白呢,阅读源码的难度无形之中又增加了嘛!即先会熟练使用这个库的各个方法(尽管你并不知道为何这么使用),再在阅读源码的过程中看到相应的代码时联想到那个方法的使用,两者相互结合,对于源码的理解就变得容易许多了
🔥 源码解析
对于源码的所有注释和理解我都收录在我 github
的 Vuex-Analysis
仓库里了,想要看更详细的注释的,可以 fork
下来参考一下(点击文末的 「阅读原文」 跳转我的仓库地址)
接下来本文就按照我当时阅读源码的思路,一步一步详细地讲解,希望大家耐心看完,谢谢啦~
一、源码目录结构分析
整个 Vuex
的源码文件非常多,我们直接看最主要的文件,即 src
文件夹中的内容,结构示例如下:
├── src ├── module // 与模块相关的操作 │ ├── module-collection.js // 用于收集并注册根模块以及嵌套模块 │ └── module.js // 定义Module类,存储模块内的一些信息,例如: state... │ ├── plugins // 一些插件 │ ├── devtool.js // 开发调试插件 │ └── logger.js // │ ├── helpers.js // 辅助函数,例如:mapState、mapGetters、mapMutations... ├── index.cjs.js // commonjs 打包入口 ├── index.js // 入口文件 ├── index.mjs // es6 module 打包入口 ├── mixin.js // 将vuex实例挂载到全局Vue的$store上 ├── store.js // 核心文件,定义了Store类 └── util.js // 提供一些工具函数,例如: deepCopy、isPromise、isObject...
二、源码阅读
1. 查看工具函数
首先我个人觉得肯定是要看一下 util.js
,这里面存放的是源码中频繁用到的工具函数,所以我觉得要最先了解一下每个函数的作用是什么
/** * Get the first item that pass the test * by second argument function * * @param {Array} list * @param {Function} f * @return {*} */ // 找到数组list中第一个符合要求的元素 export function find (list, f) { return list.filter(f)[0] } /** * 深拷贝 * * @param {*} obj * @param {Array<Object>} cache * @return {*} */ export function deepCopy (obj, cache = []) { // just return if obj is immutable value if (obj === null || typeof obj !== 'object') { return obj } // if obj is hit, it is in circular structure const hit = find(cache, c => c.original === obj) if (hit) { return hit.copy } const copy = Array.isArray(obj) ? [] : {} // put the copy into cache at first // because we want to refer it in recursive deepCopy cache.push({ original: obj, copy }) Object.keys(obj).forEach(key => { copy[key] = deepCopy(obj[key], cache) }) return copy } // 遍历obj对象的每个属性的值 export function forEachValue (obj, fn) { Object.keys(obj).forEach(key => fn(obj[key], key)) } // 判断是否为对象(排除null) export function isObject (obj) { return obj !== null && typeof obj === 'object' } // 判断是否为Promise对象 export function isPromise (val) { return val && typeof val.then === 'function' } // 断言 export function assert (condition, msg) { if (!condition) throw new Error(`[vuex] ${msg}`) } // 保留原始参数的闭包函数 export function partial (fn, arg) { return function () { return fn(arg) } }
每个函数的作用我都写上了注释,稍微阅读一下应该可以明白其作用
2. 入口文件
最主要的代码都在 src
目录下,所以以下提到的文件都是默认 src
目录下的文件
首先,肯定从入口文件 index.js
开始看,但能发现的是,还有 index.cjs
和 index.mjs
,这两者分别是 commonjs
和 es6 module
的打包入口,我们就不用管了
import { Store, install } from './store' import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers' import createLogger from './plugins/logger' export default { Store, install, version: '__VERSION__', mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers, createLogger } export { Store, install, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers, createLogger }
从入口文件中可以看到,主要导出了 Store
类 、install
方法以及一些辅助函数(mapState、mapMutations、mapGetters...)
那么我们主要看的就是 vuex
的核心代码,即 store.js
,可以看到 Store
类就出自于这个文件
3. Store类的实现
整个 Store
类的主要逻辑都在它的构造函数 constructor
中,因此我们就从 constructor
中分步去捋逻辑、看代码
3.1 存放类的状态
首先是定义了一些实例状态,用于存放模块、mutations
、actions
、getters
缓存等东西
const { plugins = [], strict = false } = options // 生成Store类的入参 this._committing = false // 表示提交的状态,当通过mutations方法改变state时,该状态为true,state值改变完后,该状态变为false; 在严格模式下会监听state值的改变,当改变时,_committing为false时,会发出警告,即表明state值的改变不是经过mutations的 this._actions = Object.create(null) // 用于记录所有存在的actions方法名称(包括全局的和命名空间内的,且允许重复定义) this._actionSubscribers = [] // 存放actions方法订阅的回调函数 this._mutations = Object.create(null) // 用于记录所有存在的的mutations方法名称(包括全局的和命名空间内的,且允许重复定义) this._wrappedGetters = Object.create(null) // 收集所有模块包装后的的getters(包括全局的和命名空间内的,但不允许重复定义) this._modules = new ModuleCollection(options) // 根据传入的options配置,注册各个模块,此时只是注册、建立好了各个模块的关系,已经定义了各个模块的state状态,但getters、mutations等方法暂未注册 this._modulesNamespaceMap = Object.create(null) // 存储定义了命名空间的模块 this._subscribers = [] // 存放mutations方法订阅的回调 this._watcherVM = new Vue() // 用于监听state、getters this._makeLocalGettersCache = Object.create(null) // getters的本地缓存
关于各个变量状态的作用都写在这了,其中只有 this._modules = new ModuleCollection(option)
执行了一些操作,其作用就是进行「模块递归收集」,根据 ModuleCollection
的来源,我们移步到 ./module/module-collection.js
文件