浅析new Vue()时发生了什么
在项目中我们引入了 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 # 共享代码
一、new Vue 过程解析
1、Vue 构造函数
当 new Vue(options)
时调用的是 src/core/instance/index.js
文件中的Vue函数,源码如下:
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) { //vue 函数
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) //调用了这个函数 这个函数是定在原型上的
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
当我们新建一个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 方法中主要做了下边几件事情:
(1)合并配置:_init 的参数 options 就是 new Vue(options) 时传入的选项,在这里通过 mergeOptions 方法把 options 合并到 vm.$options 中。合并配置项,比如路由,状态管理,渲染函数
new Vue({
store: store,
router: router,
render: h => h(App),
}).$mount('#app')
(2)调用 initLifecycle(vm)、initEvents(vm)、initRender(vm)、initState(vm) 函数进行生命周期的初始化、事件中心的初始化、渲染的初始化、data、props、computed、watch的初始化等等。
其中 initState 就是将vue实例中的data、props、computed、watch等数据项做进一步得处理,其实就是做代理以及转化成可观测对象。
(3)另外,我们可以看到在 initState(vm) 执行之前,我们执行了 beforeCreate 方法,在 initState(vm) 执行之后,我们执行了 created 方法。因此在 beforeCreate 方法中,我们无法直接引用data,method,computed,watch等在 initState(vm) 中才开始存在的属性。
(4)检测是否有el属性,有的话就调用 vm.$mount(vm.$options.el) 进行挂载渲染成真实dom。
3、initState方法如下:
从源码可以看出,initState就是将vue实例中的data,method,computed,watch等数据项做进一步得处理,其实就是做代理以及转化成可观测对象。
数据处理完成之后就将数据挂载到指定的钩子上:vm.$mount(vm.$options.el);
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)
}
}
二、小结
1、vue只能通过new关键字初始化,new Vue() 时做了什么呢?
2、合并传入的options
3、初始化生命周期
4、初始化事件中心
5、初始化渲染
6、执行 beforeCreate 钩子
7、初始化data
8、初始化props
9、初始化computed
10、初始化watch
11、执行 created 钩子
12、最后检测是否有el属性,有的话就把上面渲染好的代码挂载到页面上
放一张很流行解说vue数据响应式的图儿
那么 new Vue() 实际上就是做的:new Vue() -- init -- $mount 间的过程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律