一、前言
使用的vue版本是2.6.12
在项目中我们引入了Vue:import Vue from 'vue'
。那么问题是vue到底从哪里来的?从node_modules中来。在node_modules路径下存在vue文件夹,vue文件夹中存在一个package.json文件。在这个文件中存在两个配置字段,它们都是程序的主入口文件。
"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",
其中module的优先级大于main的优先级。在module不存在时,main对应的配置项就是主入口文件。可以看到 dist/vue.runtime.esm.js
才是主入口文件。
为了方便,我们还是去GitHub上下载vue的源代码到本地查看 https://github.com/vuejs/vue
下载完成后,我们在编辑器打开,它的目录结构如下:
其中Vue.js 的源码都在 src ⽬录下,源码的⽬录结构如下:
src
├── compiler # 编译相关
├── core # 核⼼代码
├── platforms # 不同平台的⽀持
├── server # 服务端渲染
├── sfc # .vue ⽂件解析
├── shared # 共享代码
二、过程解析
1、Vue构造函数
当 new Vue(options)
时调用的是 src/core/instance/index.js
文件中的Vue函数,源码如下:
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
当我们新建一个Vue实例时候,会判断如果当前的环境不是生产环境,并且如果在调用Vue的时候,没有用new操作符,就会调用warn函数,抛出一个警告,告诉你Vue是一个构造函数,需要用new操作符去调用。这个warn函数并不是单纯的console.warn。
接下来,把 options
作为参数调用 _init
方法。options 就是调用 new Vue时候传入的参数。可以看到Vue构造函数的核心代码只有一行:this._init(options)
在Vue构造函数后边,还有几句代码:
function Vue (options) {
...
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
在Vue的构造函数定义之后,有一系列方法会被立即调用。这些方法主要用来给Vue函数添加一些原型属性和方法的。然后我们去看一下在构造函数中调用的 _init()
2、Vue.prototype._init
上边我们可以看到 Vue 只能通过 new 关键字初始化,然后会调⽤this._init
⽅法, 该⽅法在 src/core/instance/init.js
中定义。
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// 首先缓存当前的上下文到 vm变量中,方便之后调用。
const vm: Component = this
// 然后设置 _uid属性。_uid属性是唯一的。
// 当触发init方法,新建 Vue实例时(当渲染组件时也会触发)uid都会递增。
vm._uid = uid++
let startTag, endTag
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${
vm._uid}`
endTag = `vue-perf-end:${
vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
// 有子组件时,options._isComponent才会为true
if (options && options._isComponent) {
// optimize internal component instantiation(实例)
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
// 优化组件实例,因为动态选项合并很慢,并且也没有组件的选项需要特殊对待
// 优化components属性
initInternalComponent(vm, options)
} else {
// 传入的options和vue自身的options进行合并
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm) // 初始化一些和生命周期相关的内容
initEvents(vm) // 初始化事件相关属性,当有父组件的方法绑定在子组件时候,供子组件调用
initRender(vm) // 添加slot属性
callHook(vm, 'beforeCreate') // 调用beforeCreate钩子
initState(vm) // 初始化数据,进行双向绑定 state/props
initProvide(vm) // resolve provide after data/props 注入provider的值到子组件中
callHook(vm, 'created') // 调用created钩子
/* istanbul ignore if */
// 计算覆盖率时忽略下列代码
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${
vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 把模板转换成render函数
}
}
}
_init
方法中主要做了下边几件事情:
- 合并配置:
_init
的参数options
就是new Vue(options)
时传入的选项,在这里通过mergeOptions
方法把options
合并到vm.$options
中。 - 调用
initLifecycle(vm)
、initEvents(vm)
、initRender(vm)
、initState(vm)
函数进行生命周期的初始化、事件中心的初始化、渲染的初始化、data、props、computed、watch的初始化等等。其中initState
就是将vue实例中的data、props、computed、watch等数据项做进一步得处理,其实就是做代理以及转化成可观测对象。 - 另外,我们可以看到在
initState(vm)
执行之前,我们执行了beforeCreate
方法,在initState(vm)
执行之后,我们执行了created
方法。因此在beforeCreate
方法中,我们无法直接引用data,method,computed,watch等在initState(vm)
中才开始存在的属性。 - 检测是否有el属性,有的话就调用
vm.$mount(vm.$options.el)
进行挂载渲染成真实dom。
initState方法如下:
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {
}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
3、小结
- vue只能通过new关键字初始化
- 合并传入的options
- 初始化生命周期
- 初始化事件中心
- 初始化渲染
- 初始化data
- 初始化props
- 初始化computed
- 初始化watch
- 最后检测是否有el属性,有的话就把上面渲染好的代码挂载到页面上
三、参考资料
前端学习交流QQ群,群内学习讨论的氛围很好,大佬云集,期待您的加入:862748629 点击加入