从未看过源码,到底该如何入手?分享一次完整的源码阅读过程(一)

简介: 源码阅读过程。

前言


我觉得每个人可能都有过看源码的想法吧,也包括我。因为看源码不光能使自己对这个库更加熟悉,还能学习到作者强大的思想,久而久之,自己的水平和思想也会有明显的提升的。


但对于我来说,之前从来没有阅读过源码,想阅读源码却不敢迈出那一步,因为一个成熟的库有着太多的方法、逻辑,阅读起来可能会比较困难,但人总要勇于尝试的嘛,于是我就准备把 Vuex 的源码 clone 下来,没有别的原因,只是因为这个库体积比较小,算上注释,核心代码只有1000行不到,我觉得非常适合第一次阅读源码的人拿来练手


说干就干,我就先在 github 上给自己列了一个计划表,预计 15 天看完源码并完成总结,然后每天记录一下当天的收获


470b63a61205e004de321104dec3726d.png


不过最后的结果倒是出乎我的意料,阅读源码加上整理总结只用了8天左右的时间


在阅读源码之前,我是先去看了一下 Vuex 的官方文档,算是一种回顾、查漏补缺,我也非常建议这样做,因为你看源码,你就会看到这个库里面所有的内容,那么你连这个库都没用明白呢,阅读源码的难度无形之中又增加了嘛!即先会熟练使用这个库的各个方法(尽管你并不知道为何这么使用),再在阅读源码的过程中看到相应的代码时联想到那个方法的使用,两者相互结合,对于源码的理解就变得容易许多了


🔥 源码解析


对于源码的所有注释和理解我都收录在我 githubVuex-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.cjsindex.mjs ,这两者分别是 commonjses6 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 存放类的状态


首先是定义了一些实例状态,用于存放模块、mutationsactionsgetters 缓存等东西


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 文件

相关文章
|
存储 前端开发 JavaScript
AntV X6源码探究简析
AntV是蚂蚁金服全新一代数据可视化解决方案,其中X6主要用于解决图编辑领域相关的解决方案,其是一款图编辑引擎,内置了一下编辑器所需的功能及组件等,本文旨在通过简要分析x6源码来对图编辑领域的一些底层引擎进行一个大致了解,同时也为团队中需要进行基于X6编辑引擎进行构建的图编辑器提供一些侧面了解,在碰到问题时可以较快的找到问题点。
389 0
|
3月前
|
存储 前端开发 JavaScript
PixiJS源码分析系列: 第一章 从最简单的例子入手
PixiJS源码分析系列: 第一章 从最简单的例子入手
|
6月前
|
安全 网络安全 网络架构
网络开发过程详细知识点
网络开发过程详细知识点
56 0
|
负载均衡 前端开发 Java
阿里面试:看过框架源码吗?举例说明一下
阿里面试:看过框架源码吗?举例说明一下
128 0
|
前端开发 NoSQL 数据库
项目重点知识点详解
项目重点知识点详解
|
XML 设计模式 缓存
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
29626 6
面试必问系列之最强源码分析,带你一步步弄清楚Spring如何解决循环依赖
|
IDE 测试技术 API
聊聊我的源码阅读方法
本次代码阅读的项目来自 500lines 的子项目 web-server。 500 Lines or Less不仅是一个项目,也是一本同名书,有源码,也有文字介绍。这个项目由多个独立的章节组成,每个章节由领域大牛试图用 500 行或者更少(500 or less)的代码,让读者了解一个功能或需求的简单实现。
160 0
聊聊我的源码阅读方法
|
XML 缓存 JSON
看SpringCloudEureka源码前懂得这些知识事半功倍
看SpringCloudEureka源码前懂得这些知识事半功倍
看SpringCloudEureka源码前懂得这些知识事半功倍
|
存储 缓存 JSON
tinydb 源码阅读
TinyDB是一个小型,简单易用,面向文档的数据库;代码仅1800行,纯python编写。TinyDB项目大小刚好,学习它可以了解NOSQL数据库的实现。
435 0
tinydb 源码阅读