文章目录
源码目录结构
- 这里主要分析
src
目录下文件的主要功能compiler
:与编译相关core
:Vue
核心代码components
:keep-alive
相关代码global-api
:Vue
静态成员相关初始化instance
:Vue
实例相关初始化observer
:响应式相关代码vdom
:虚拟dom
相关代码
platform
:平台相关代码server
:VueSSR
服务端渲染相关代码
Vue的构建版本
- 在执行
npm run build
时,会生成vue
的不同版本,下面我们具体看一下各版本区别
版本 | UMD | CommonJS | ES Module |
---|---|---|---|
完整版 | vue.js | vue.common.js | vue.esm.js |
只包含运行时版 | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js |
完整版 (生产环境) | Vue.min.js | ||
只包含运行时版 (生产环境) | Vue.runtime.min.js |
- 完整版:同时包含编译器和运行时的版本。
- 编译器:用来将模板字符串编译成为
JavaScript
渲染函数的代码,体积大,效率低。 - 运行时:用来创建 Vue 实例、渲染并处理虚拟
DOM
等的代码。体积小,效率高,基本上就是除去编译器的其它一切。 - UMD: 通用的模块版本,支持多种模块方式。
UMD
版本可以通过<script>
标签直接用在浏览器中 - CommonJS(cjs):
CommonJS
配合老的打包工具,比如Browserify
或Webpack1
- ES Modules:从
2.6
开始Vue
会提供两个ES Modules(ESM)
构建文件,为现代打包工具提供的版本。 ESM
格式被设计为可以被静态分析,所以打包工具利用这一点可以进行TreeShaking
,并将用不到的代码排除最终的包
完整版与运行时版本的区别
- 完整版:即需要将
template
转为render
函数,创建Vue
实例,渲染并处理DOM
等代码
<script src="../../dist/vue.min.js"></script>
<div id="app"></div>
<script>
const vm = new Vue({
el: '#app',
template: '<h1>{{msg}}</h1>',
data: {
msg: 'Hello World'
}
})
</script>
- 运行时版本:不需要将
template
转为render
函数,创建Vue
实例,渲染并处理DOM
等代码,此时将vue.min.js
换为运行时版本
<script src="../../dist/vue.runtime.js"></script>
- 此时会出现报错,下面我们将
template
换为render
函数,则可以正常运行,如下:
<script>
const vm = new Vue({
el: '#app',
render(h){
return h('h1', this.msg)
},
data: {
msg: 'Hello World'
}
})
</script>
- 基于
vue-cli
搭建的项目,默认引入的vue
是运行时版本,且是ESM
模块化方式 vue
脚手架创建的Vue
项目中,引用的Vue
版本就是运行时版本:vue.runtime.esm.js
,我们可以在Vue
项目中执行:vue inspect > output.js
,将webpack
配置输出到output.js
文件中查看:
resolve: {
alias: {
'@': 'D:\\work\\vue源码\\code\\01-demo\\src',
vue$: 'vue/dist/vue.runtime.esm.js'
},
}
template与render执行顺序
- 打开文件
src/platform/web/entry-runtime-with-compiler.js
, 找到Vue.prototype.$mount
函数 - 在该函数中,会优先判断是否有
render
选项,如果有,则调用mount
,进行DOM
挂载,如果没有,才会通过template
来生成render
函数 - 因此
render
与template
的优先级为:render > template
, 源码如下:
Vue函数的初始化
- 首先先找到打包文件入口,也就是
src/platform/web/entry-runtime-with-compiler.js
**,该文件主要实现两个功能:
1)重写了$mount
方法,使其可以对template
的字符串,可以转为render
函数
2)注册了Vue.compile()
方法,这个方式就是template
转render
函数的方法
- 但上面的文件并没有进行
Vue
构造函数的定义,因此根据上面引入的Vue
打开src/platform/web/runtime/index
继续分析代码,该文件主要实现三个功能:
1)注册全局指令与组件,全局的指令与组件都在Vue.options.directives
和Vue.options.components
2)注册__patch__
函数,主要用于将虚拟DOM
转为真实DOM
3)注册$mount
方法,用于组件挂载
- 上面文件中的
Vue
引自于core/index
, 在该文件中,主要实现两部分功能:
1)在initGlobalAPI
中为Vue
的构造函数增加一些静态成员
2)给Vue
增加一些与VueSSR
相关的成员
- 最后,我们根据
core/index
中引入的Vue
打开文件src/core/instance/index.js
, 在这个文件中,定义了Vue
函数,其中:
1)在Vue
构造函数中调用了_init
方法,
2)给Vue
中混入了常用的实例成员
Vue静态方法初始化
- 我们在上面说过在
initGlobalAPI
函数中为Vue
的构造函数增加一些静态成员,下面我们来具体看一下initGlobalAPI
函数,在src/core/global-api/index.js
文件中 - 下面两张截图中分别标注除了对应代码的作用,因为在
initGlobalAPI
主要做了以下几件事
1)初始化Vue.config
属性
2)注册set
,delete
,nextTick
3)注册Vue.observable
,该函数主要用于将一个对象编程可响应式
4)初始化Vue.options.components
,Vue.options.directives
,Vue.options.filters
5)存储Vue
构造函数在Vue.options._base
6)通过extend
注册keep-alive
组件
7)注册Vue.use()
用来注册插件
8)注册Vue.mixin()
实现混入
9)注册Vue.extend()
基于传入的options返回一个组件的构造函数
10)注册Vue.directive()
Vue.component()
Vue.filter()
Vue实例成员初始化过程
上面介绍了Vue
静态方法的初始化,现在我们来看一下Vue
实例的初始化,Vue
实例的初始化是在文件src/core/instance/index.js
中
- 1)注册了
vm
的_init()
方法 - 2)初始化了事件相关的方法,
$on/$once/$off/$emit
,其中事件的原理是发布订阅 - 3)初始化生命周期相关的方法,
_update/$forceupdate/$destory
- 4)初始化
render
,$nextTick/_render
Vue实例成员init方法
在上面了解到,在初始化一个Vue
实例的时候,会调用_init
方法,下面我们来简单看一下_init
方法内部的实现
该函数主要用于options
合并,vue
实例初始化,与触发beforeCreated
与created
生命周期的钩子函数
Vue实例成员initState方法
- 下面具体来看一下
initState
内部方法的实现 - 该方法相对比较简单,主要进行了以下操作
1)initProps
:将props
中成员转为为响应式,并注入到vue
实例中
2)将methods
中的方法注入到vue
实例,在注入之前会检查是否有与prop
重名的方法名,及检查methods
的命名规范
3)初始化data
,如果传入data
则调用initData
方法,否则初始化vm._data
,并将其赋值为空对象
4)初始化计算属性computed
与监听器watcher
,并将其注入到vue
实例 - 在
initData
方法中
1)初始化_data
, 如果组件中data
是函数,则调用函数的返回结果,否则直接返回data
2)判断在props
与methods
中是否有重名属性,及检查命名规范
3)将_data
注入到vue
实例中,并将其设置为响应式数据