前言
想学习一个框架的源码最好从初始化流程开始逐步深入,本篇幅主要介绍vue的初始化流程Trust me 绝对完整
建议
本篇幅可能需要阅读大概半个小时左右可以泡上一杯雀巢或者一杯花茶慢慢看,但是我觉得花上一些时间弄懂这些还是值得的
开始我们的操作,接下来会有大量的源码图片
1.github.com/vuejs/vue 可以去这个地址把源码克隆下来
区分目录结构
阅读源码之前我们需要先把工具准备好
源码下载下来之后需要生成sourcemap映射源码文件 方便我们在浏览器断点调试
1.npm i //下载包的时候如果看到phantom.js终止就可以这个下载特别慢重点是咱们也用不到
2.npm i -g rollup
3.修改dev脚本 "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:webfull-dev"
4.npm run dev
5.之后会看到dist目录下生成了vue.js.map文件
现在就可以开始调试了
- scripts 文件内有很多命令我们重点关注TARGET的值
- dev命令指向的是
web-full-dev
我们可以全局搜一下 - 这时候应该来到了
scripts/config.js
里面可以从这里开始看
这里就是根据环境来做区分该引用那些js,比如我们在
vue-cli
脚手架中用到的后缀就是esm
的js,通常这类js是不会携带编译器
因为在webpack中vue-loader
会在打包的时候帮我们编译,我们现在调试的vue.js
是带编译器的版本
我们可以看到
web-full-dev
引用的entry是web/entry-runtime-with-compiler.js
需要进入到这个文件 我用的是vs codecommand+p
全局搜文件名字就可以找到
- 扩展
$mount
- 内部判断了vue挂载的优先级 大家都知道 vue挂载有几种方式
render > template > el
就是在这里区分的优先级 - 接下来调用
compileToFunctions
函数将vue模板
转化成render
渲染函数 (内部大概流程是将模板转化成抽象语法树ast之后将ast代码生成为可执行的代码具体细节以后会讲 本次只关心渲染的流程) - 将render函数挂载到options上方便后续调用
这里只看到了vue的
$mount
扩展我们继续往后找Vue函数 查看runtime/index
-
将
patch
函数挂载到Vue的原型上后面的流程diff
计算的时候会用到该函数 -
$mount
函数内部执行mountComponent
挂载函数 -
new Watcher
将更新函数传到watcher中等后续依赖注入完成调用_render渲染函数渲染虚拟dom -
我们继续看
core/index
创建完成之后会回来继续看mountComponent
挂载的过程 -
初始化全局api 例如
use、componet、delete、set、nexttick
等 -
继续往后查找
instance/index
-
在这个函数中我们就看到了
Vue
函数的定义还有其他初始化方法 -
接下来看
initMixin
方法内部干了什么 -
首先初始化
_init
方法也就是外部Vue函数里面调用的 -
函数内部会进行选项合并主要是用户在
new Vue
传进来的选项和Vue
本身的选项做个合并 -
在这里我们看到执行了
$mount
并且是有个判断的vm.$options.el
存在会执行这个挂载,这就证明了在外部new Vue
的时候只要传了el
后面不跟mount也能挂载因为在这个位置还会隐式挂载一次 -
之后我们继续看下面几个初始化的方法
initLifecycle
- 挂载
$paren、$root、$children、$refs
等属性
initEvents
- 挂载
events
事件
initRender
- 挂载
$slots、$createElement
等方法 (我们在使用render
挂载的时候 参数里面的h调用的就是此方法)
allHook(vm, 'beforeCreate')
执行beforeCreate生命周期 在这里我们就能知道 上面执行的在当前生命周期都能获取到
initInjections 依赖注入
props、data
initState 这里就是将传进来的数据变为响应式的过程
initProvide 依赖注入完成
initState
- 区分
props> methods> data
设置属性的优先级 - 将数据变为响应式
- 之后调用
initData
继续看一下initData做了什么事
- 判断
date
是否是函数还是对象执行不同的操作 - 判断
props
和methods
保证没有重复性 - 调用
proxy
做代理使this可以直接访问到Vue
实例上的属性 - 调用
observe
将数据变为响应式
observe内部实现
- 首先会判断当前传进来的变量是否加过响应式加过就返回否则继续执行
- 调用
Observer
类将数据变为响应式并且创建依赖
Observer类
- 创建一个
Dep
管理当前依赖 (这里我们可以叫大管家dep后续会讲到为什么是大管家) - 区分数组还是对象做不同的响应式处理
- 对象的情况直接调用
walk
方法 - 数组的情况需要判断是否有原型 (老的ie是没有的)
- 在数组有原型的情况直接调
protoAugment
进行原型覆盖(这里主要是为了数组的增删改因为Object.defineProperty在语言层面检测不到数组的变化所以需要自己覆盖下数组的原型使用自己的方法检测
) - 在数组没有原型的情况调
copyAugment
复制一份
我们先看对象的情况是怎么处理的
- 直接调用walk将数据循环遍历调用defineReactive
- 我们看defineReactive做了些什么操作
- 每创建一个响应式对象,这里就会创建一个
dep
管理当前自己的依赖 这里的dep我们可以看成 小管家dep只管理自己当前的依赖 - 这里就是通过
Object.defineProperty
将数据变成响应式的过程并且和watcher
创建依赖关系 - 到这里依赖就已经注入完成创建完毕了,接下来就会执行
$mount
中的mountComponent
方法 - 我们继续接着上面的
mountComponent
挂载开始讲
看下 mountComponent方法内部做了啥
- 将更新函数赋值给
updateComponent
new Watcher
将更新函数传到watcher中等后续依赖注入完成调用_render
渲染函数渲染vdom
Watcher类内部
- 调用
this.get
方法 get
方法内部会执行getter
方法这里的getter
方法也就是Watcher
类接收的第二个参数_render
渲染函数如果是在浏览器打断点调试的话到这里就可以看到页面初始化完成了
接下来在看一下依赖收集过程(但是注意这段是触发get的时候才会执行的)
-
dep
内部有几个方法addSub
是将依赖放到当前数组中方便之后notify
进行更新 -
defineReactiv
e中触发的方法的depend
方法就是dep
中的depend
-
notify
方法就是更新的时候调的批量更新subs
中的依赖就可以(subs中大家看到的update方法是在watcher中关联的时候创建的这里涉及到更新过程之后会讲解) -
depend
方法的内部Dep.target
指向的就是Watcher
类我们在来看一下watcher
类里面的方法 -
addDep
方法里面我们可以看到和外面的dep
做了关联保证了以后的更新流程这里就是依赖收集的过程(但是注意依赖收集初始化的时候是不会做的当你用的时候才会触发get
做依赖收集这里只是介绍)
update
方法同步的时候会走run
函数run
函数内部调用的也是get
方法
我们在来看一下get方法内部
get
方法内部主要调用的是getter
方法getter
是在new Watcher
的时候传进来的是上面提到的_render
渲染函数
我们可以再来看一下
watcher
类的第二个参数就是我们说的getter
,而这里接收的参数就是上面mountComponent
里面执行的挂载的时候传进来的_render
所以最后run函数内部调用的是get执行的是render渲染函数
有个问题说明一下 上面提到的大管家dep和小管家dep
- 大管家
dep
的出现主要是为了弥补Object.defineProperty
语言层面的不足vue
里面有两个方法set
和delete
是用来更新data
属性的 这两个方法在内部用的时候更新完成之后就会调用一下大管家dep
进行数据更新- 小管家
dep
就比较简单只是为了管理当前的依赖 和watcher
之间产生对应依赖关系方便以后更新操作
vue的初始渲染流程到这里就讲完了我们在串一遍
1.从Vue函数内部的this._init开始看看this._init具体做了啥
- 合并选项
- 初始化全局api和方法
- 挂载$mount
2.之后看initState方法
- props>methods>data优先级判断
- 调用initData方法
3.initData方法
- 判断date是否是函数还是对象执行不同的操作
- 判断prose和methods保证没有重复性
- 调用proxy做代理使this可以直接访问到实例上的属性
- 调用observe将数据变为响应式
4.observe方法
- 首先判断数据是否是响应式的如果是直接返回否则继续执行
- 调用Observer类将数据变为响应式
5.Observer类
- 创建dep管理当前依赖
- 区分数组还是对象做不同的响应式处理
- 数组的情况需要判断是否有原型 (老的ie是没有的)
- 有原型的情况直接调protoAugment进行原型覆盖
- 没有原型的情况调copyAugment复制一份
6.defineReactive方法
- 创建一个dep这里是小管家dep
- 这里就是通过Object.defineProperty将数据变成响应式的过程也是依赖收集的过程只是在get被触发的时候才会做
- 以上步骤完成之后就是创建完成了,之后会调用 最开始$mount 内部的 mountComponent执行挂载过程
7.mountComponent方法
- 将更新函数赋值给updateComponent
- new Watcher 将更新函数传到watcher中等后续依赖注入完成调用_render渲染函数渲染虚拟dom
8.Watcher类内部
- 1.调用get
- 2.get内部调用getter方法(getter就是new Watcher传进来的更新函数到此更新完成)
到这里本篇幅就算结束了之后会有nexttick
、render
函数渲染原理包括全局组件注册过程来袭
创作不易如果觉得有帮助的话点个赞吧😁