Vue2源码【9】收获总结

Vue2源码系列终于要完结了。最后总结一下阅读过程中的一些收获,主要分为4个方面:

“架构设计方面”、“目录和模块设计”、“编程具体模块的实现”、“怎么看源码”

架构设计方面

目录和模块设计

浏览器端的 Vue.js 和服务端的 Vue.js 所共享的模块抽象成一个单独的目录。这大概就是“动静分离”吧,本文后面还有讲到。

  • 路径别名:这里找了一个文件专门来放置全局的路径别名。一般都是和path.resolve模块结合使用。
  • core模块+分层结构:其实vue就是在core模块完成了初始化,然后导出到不同的模块进行一层一层对vue的修饰和包装。这种项目结构看源码会浪费很多时间抓不到重点。所以大致列举一下在不同的文件增加了什么属性就行了。
    • 一般只看index的文件就可以了,其他的无非是该层内的修饰和资源配置。

编程具体模块的实现

src/core/instance/index.js

【1】构造函数为什么不直接用class:

在这里,我们终于看到了 Vue 的庐山真面目,它实际上就是一个用 Function 实现的类,我们只能通过 new Vue 去实例化它。

有些同学看到这不禁想问,为何 Vue 不用 ES6 的 Class 去实现呢?我们往后看这里有很多 xxxMixin 的函数调用,并把 Vue 当参数传入,它们的功能都是给 Vue 的 prototype 上扩展一些方法(这里具体的细节会在之后的文章介绍,这里不展开),Vue 按功能把这些扩展分散到多个模块中去实现,而不是在一个模块里实现所有,这种方式是用 Class 难以实现的。这么做的好处是非常方便代码的维护和管理,这种编程技巧也非常值得我们去学习。这样是容易把代码拆分到不同的文件中。

一方面需要拓展原型上的方法,另一方面,后面利用initGlobalAPI来拓展这个函数的静态属性或者静态方法。

【2】清晰的初始化大函数划分

在initMixin里面,我们能看到这种清晰的结构:

initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');

实际上还是需要这些大函数相互之间没有数据上的关联或者耦合,否则这个逻辑很难debug。

【3】JSON化

// scripts/config.js
const builds = {
  // ...一个大JSON
}
function genConfig() {}
const getAllBuilds = () => Object.keys(builds).map(genConfig)

为什么有这样的设计呢?就是一开始先定义好了builds的格式就是JSON的,其他的代码围绕它做配置。

这里genConfig其实就是把builds处理成Rollup能接受的JSON参数的格式。

【4】装饰者模式

const mount = Vue.prototype.$mount // 暂存keeper,来自src/platforms/web/runtime/index.js
Vue.prototype.$mount = function () {
  // 各种判断
  return mount.call(this, el, hydrating)
}

【5】函数参数的重载:

function createElement (context, tag, data, children, normalizationType) {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }

这里参数根据data的情况,把所有“对参数位的捕获”往后挪。

【6】一个简单好用的once函数

// Ensure a function is called only once.
export function once (fn: Function): Function {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}

【7】createPatchFunction 的柯里化实现“动静分离” #一些启发 从我的文章摘抄过来

我们可以思考一下为何 Vue 源码绕了这么一大圈,把相关代码分散到各个目录:

前面也介绍过,patch 是平台相关的,在 Web 和 Weex 环境,它们把虚拟 DOM 映射到 “平台 DOM” 的方法是不同的,并且对 “DOM” 包括的属性模块创建和更新也不尽相同。**因此每个平台都有各自的 nodeOpsmodules,**它们的代码需要托管在 src/platforms 这个大目录下。

而不同平台的 patch 的主要逻辑部分是相同的,所以这部分公共的部分托管在 core 这个大目录下。

综上所述:差异化部分只需要通过参数来区别,这里利用闭包实现函数柯里化,通过 createPatchFunction 把差异化参数提前传入固化。好处是:

1先在外部判断需要什么类型的patch,然后选择把一些依赖通过参数传递进去。而不是在patch函数内部实现一大堆判断逻辑

2不用每次调用 patch 的时候都传入 nodeOpsmodules

怎么看源码

找到学习路线,别人已经分好的模块。还有别人先探知过程中会执行那些函数,都是脏活累活。

vue npm包dist几个没有压缩的文件还有src文件,可能是为了方便调试的,这几个文件内容差别不大。

npm包怎么被一个项目所引用,知识要具备。然后打一个debugger,断点会有调用栈call stack信息的,点击是能够翻阅怎么调用进来的。

区分阶段:比如搜到 createCompiler,然后打断点打印 code.render,这是表示解析ast之后的字符串。ast生成过程可以先不管细节。src/instance/render-helpers/index.js可以看到压缩前后的函数名对应关系。

看辅助函数(而且该辅助函数if else逻辑比较多)的技巧

翻开test目录,看该辅助还是的“单元测试”

文章目录
  1. 架构设计方面
    1. 目录和模块设计
  2. 编程具体模块的实现
  3. 怎么看源码
    1. 看辅助函数(而且该辅助函数if else逻辑比较多)的技巧