TOC
前言
最近一直在看 Vue.js 源码,大约花了半个多月的时间阅读,有非常多的感悟,所以在这里希望能和大家分享一下我的成果。
首先我认为 Vue.js 这种牛逼框架的源码绝对值得一读,无论是你想知道底层实现,还是想偷学编程技巧,我相信都有会很多的收获,但是其实 Vue.js 的源码阅读起来并不是没有成本的,尽管已经做了很多可读性上的优化,不过阅读早期依旧会比较懵逼,所以来讨论一下整个项目的结构,以及推荐的阅读顺序。
目录结构
src
├── compiler // 模板编译器 也就是从模板字符串到render函数的转换
├── core // Vue的核心
│ ├── components // 内建缓存组件
│ │ ├── index.js
│ │ └── keep-alive.js
│ ├── config.js
│ ├── global-api // 对Vue.js扩展一些全局API 方便进行插件化的增强
│ │ ├── assets.js // 全局component filter 指令注册
│ │ ├── extend.js // Vue.extend
│ │ ├── index.js // 注册全局API的入口
│ │ ├── mixin.js // Vue.mixin
│ │ └── use.js // use会调用install方法 可以进行自动化的注册 一般第三方组件都会用这个来进行注册 实际上调用的还是assets里面的方法
│ ├── index.js // 一个完整Vue构造函数的最终出口
│ ├── instance // Vue实例的各个组成部分
│ │ ├── events.js // 用来绑定实例的父子组件事件监听
│ │ ├── index.js // 入口
│ │ ├── init.js // 创建的时候初始化这里的所有东西
│ │ ├── inject.js // provide/inject 官方推荐组件库使用 使用起来可能会造成一些阅读上的额外开销
│ │ ├── lifecycle.js // 生命周期 管理一个组件的创建 更新 等等
│ │ ├── proxy.js // 在render函数中将通过proxy获取实例的属性 方便进行报错
│ │ ├── render-helpers // render函数中调用的函数 可以将模板转换出的函数调用转换成vnode
│ │ │ ├── bind-object-listeners.js
│ │ │ ├── bind-object-props.js
│ │ │ ├── check-keycodes.js
│ │ │ ├── index.js
│ │ │ ├── render-list.js
│ │ │ ├── render-slot.js
│ │ │ ├── render-static.js
│ │ │ ├── resolve-filter.js
│ │ │ └── resolve-slots.js
│ │ ├── render.js // 为组件构建_render方法 他的作用是再次调用$vm.options.render方法生成Vnode并返回
│ │ └── state.js // 管理各种数据 增加数据监听 依赖收集等
│ ├── observer // Vue数据监听的核心
│ │ ├── array.js // 对数组进行监听 原理是重写数组的原型 监听数组的所有会导致content变动的方法
│ │ ├── dep.js // 依赖收集 为每一个监听单元(一个属性)建立一个dep对象 并重写getter setter方法
│ │ ├── index.js // 入口
│ │ ├── scheduler.js // watcher run 调度器 避免疯狂刷新
│ │ ├── traverse.js // 深度watcher功能 如果需要 会自动遍历一个对象的所有内部属性 从而让dep收集完整的依赖
│ │ └── watcher.js // 依赖收集与更改响应的核心 通过dep来收集依赖到某个关联watcher 随后set时 触发这个管理watcher 后面会详细的讲 超级牛逼
│ ├── util // 一些工具 这个就略过了
│ │ ├── debug.js
│ │ ├── env.js
│ │ ├── error.js
│ │ ├── index.js
│ │ ├── lang.js
│ │ ├── next-tick.js
│ │ ├── options.js
│ │ ├── perf.js
│ │ └── props.js
│ └── vdom
│ ├── create-component.js // render函数创建组件的方法 to Vnode
│ ├── create-element.js // render函数创建普通元素 to Vnode
│ ├── create-functional-component.js // 函数式组件(其实就类似于react的函数组件 单渲染组件 自己没啥状态的) to Vnode
│ ├── helpers
│ │ ├── extract-props.js // 将父组件的PropsData属性 加载到自己的_props里
│ │ ├── get-first-component-child.js // 一个小工具 从子元素从找到第一个组件
│ │ ├── index.js // 入口
│ │ ├── is-async-placeholder.js // 工具
│ │ ├── merge-hook.js // 可以把合并Vnode的HOOK 高阶函数的玩法 有点像 FP里的 compose
│ │ ├── normalize-children.js // 规范化children 有些情况下会比较特殊 必须重新格式化称vnode数组
│ │ ├── resolve-async-component.js // 异步组件
│ │ └── update-listeners.js // 更新一个组件的监听器 会顺便绑定到_event属性上
│ ├── modules
│ │ ├── directives.js // 指令
│ │ ├── index.js
│ │ └── ref.js // ref
│ ├── patch.js // 从Vnode 到真正的DOM 就是这个Path在进行操作 递归实例化都在这里
│ └── vnode.js
├── platforms
│ ├── web
│ │ ├── entry-compiler.js
│ │ ├── entry-runtime-with-compiler.js
│ │ ├── entry-runtime.js // 一般来说 阅读从这里开始比较好(从web端的角度上说)
│ │ ├── entry-server-basic-renderer.js
│ │ ├── entry-server-renderer.js
│ │ ├── runtime
│ │ │ ├── class-util.js
│ │ │ ├── components
│ │ │ │ ├── index.js
│ │ │ │ ├── transition-group.js
│ │ │ │ └── transition.js
│ │ │ ├── directives
│ │ │ │ ├── index.js
│ │ │ │ ├── model.js
│ │ │ │ └── show.js
│ │ │ ├── index.js
│ │ │ ├── modules
│ │ │ │ ├── attrs.js
│ │ │ │ ├── class.js
│ │ │ │ ├── dom-props.js
│ │ │ │ ├── events.js
│ │ │ │ ├── index.js
│ │ │ │ ├── style.js
│ │ │ │ └── transition.js
│ │ │ ├── node-ops.js // 把DOM操作独立出来 方便移植到各个平台上
阅读前的准备
flow - Flow 是 facebook 出的 JS 静态检查工具,这东西 Vue 的作者已经隐晦的说很垃圾了,目前 3.0 正在用 TS 进行重构,所以当成普通 JS 看也没事,无非就是一些类型标注
建立一个 vue 项目,边看边输出,边测试,边打断点,爽感加倍
要有耐心,慢慢看,进度慢难理解很正常
注释非常重要 一定要仔细看
别人的理解永远是配菜,自己的理解才是主菜
善用搜索功能 JS 的点击跳转到定义的功能真的不好用 有些时候就得手动搜索
推荐的阅读顺序
也就是推荐一下,大家实际阅读的时候应该是反复跳跃性的,根本没办法真的强调顺序进行阅读。
一 Web 端的入口
/src/platforms/web/entry-runtime.js
是打包时的最终出口,推荐作为入口点. Vue.js 用了核心与底层 DOM 操作 分离的方法进行开发,这意味着能够方便的移植到多平台。
简单来说 一个 Vue 其实可以被认为是 Vue 核心+底层 DOM 操作的组合
Vue 实例
通过路线,你应该可以摸到 /src/core/instance/index.js
,在这里是一个 Vue 构造函数的组装,可以清晰的看到各个组成部分,你可以顺着这个路线看看 Vue 的实例上都拥有那些功能,他们的用途是什么。
这里的重点我认为有如下几点
- render 方法 render 就是获取 VNode,然后交给组件的 update
- 组件的 options 上下文
- 数据的依赖收集过程
递归挂载
接下来你可以对 Vue 的挂载过程进行阅读 /src/core/vdom
,理解递归渲染,子组件不断实例化的过程,以及当 DOM 更新到时候,vue 是如何进行 diff 的。
平台相关组件
阅读如 keep-alive ,transition 的一些组件的原理
非常牛逼的东西
这些东西我认为是 Vue.js 相比其他 MVVM 框架不同的地方,实现都非常的巧妙.
- 依赖收集机制,按需收集,watcher 堆栈模式,数组的监听
- diff 算法 双端对比算法 可能是最简单最容易理解的 diff 算法 大部分情况下也很高效
- nextTick 的实现
- patch 的过程 diff 与更新同时进行
其实我本来写了一些东西,关于 Vue 依赖收集的实现,写了一半发现实在太难写了,还不如直接看源码来的实惠,所以放弃了。大家还是靠自己看来的最好!