前言
在阅读本篇文章之前建议阅读 【源码导读 】在new Vue()之前,Vue是什么样子的?。
未挂载状态的vue实例,挂载过程将在后面分析。
小提示:配合源码食用更佳美味。
vue构造函数
在new Vue()最开始只干了一件事,this._init()
src/core/instance/index.js
import { initMixin } from './init'
function Vue (options) {
this._init(options)
}
initMixin(Vue)
而这个this._init(),是在initMixin(Vue)中定义的
init
src/core/instance/init.js
筛选出第一次new Vue的代码,阅读注释
// vue实例的唯一id
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 每次创建都会加一,确保唯一性
vm._uid = uid++
// vue实例不应该是一个响应式的,做个标记
vm._isVue = true
// 先忽略 组件相关的
if (options && options._isComponent) {
// ...
} else {
// 合并配置项
vm.$options = mergeOptions(
// 这里是取到之前的默认配置,组件 指令 过滤器等
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._self = vm
// 生命周期初始化
initLifecycle(vm)
// 事件初始化
initEvents(vm)
// render初始化
initRender(vm)
// 调用创建之前的钩子函数
callHook(vm, 'beforeCreate')
// 注入初始化
initInjections(vm) // resolve injections before data/props
// 数据初始化
initState(vm)
// 提供初始化
initProvide(vm) // resolve provide after data/props
// 调用创建完成的钩子函数
callHook(vm, 'created')
// 存在el则默认挂载到el上 不存在的时候不挂载 需要手动挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
可以看到 模块分的很细 我们展开说
生命周期初始化
src/core/instance/lifecycle.js
export function initLifecycle (vm: Component) {
const options = vm.$options
// 找到第一个不是抽象组件的父亲
let parent = options.parent
// 第一次是没有父亲的 不会走这个逻辑
if (parent && !options.abstract) {
// 若没有父亲 并且不是抽象组件 进到这就肯定不是根组件
// 如果是抽象组件并且有父亲 继续向上找 直道找到最上面的一个抽象组件的父亲
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
// 给找到的最上面的抽象组件的孩子push到自己
parent.$children.push(vm)
/* 不太好理解 画个图
* app -> ccc -> ddd -> transition -> eee -> fff
* 比如new Vue(eee) 他就会向上找 直道找到 transition 这个抽象组件
* 图上所描述的ddd的孩子是transition 经过这个操作后ddd的孩子就会多一个eee
*/
}
// 配置父组件
vm.$parent = parent
// 配置子组件
vm.$root = parent ? parent.$root : vm
// 配置孩子是空数组
vm.$children = []
// 配置 refs是空
vm.$refs = {}
// 依赖收集相关 用来更新组件
vm._watcher = null
// 我理解的是他是否已经在用状态 没用的话都不需要更新。请教评论区大佬
vm._inactive = null
// keepAlive但是又调用了destroy的情况下使用
vm._directInactive = false
// 是否挂载
vm._isMounted = false
// 是否销毁
vm._isDestroyed = false
// 是否正在被销毁
vm._isBeingDestroyed = false
}
事件初始化
src/core/instance/events.js
export function initEvents (vm: Component) {
// 用于存放事件
vm._events = Object.create(null)
// hook事件 hook:xxxx
vm._hasHookEvent = false
// 初始化父组件附加事件 <comp @my-click="aaa">
// 参考下图,当new Vue()是组件comp的时候
const listeners = vm.$options._parentListeners
if (listeners) {
// 更新组件的监听 将comp的事件取出来放到自己身上监听
updateComponentListeners(vm, listeners)
}
}
render初始化
export function initRender (vm: Component) {
// 存放虚拟dom
vm._vnode = null
// 缓存静态节点 只渲染元素和组件一次。v-once
vm._staticTrees = null
const options = vm.$options
// 父组件中的占位节点
const parentVnode = vm.$vnode = options._parentVnode
// 父节点实例
const renderContext = parentVnode && parentVnode.context
// 插槽逻辑
vm.$slots = resolveSlots(options._renderChildren, renderContext)
// 插槽作用域
vm.$scopedSlots = emptyObject
// 私有变量 render函数内部使用 创建元素
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 规范化始终应用于公共版本,用于用户编写的呈现函数。 在分析render过程的时候在说 可以简单认为他就是创建元素
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
const parentData = parentVnode && parentVnode.data
// 做代理 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
// 做代理 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
注入数据
在初始化data props 之前初始化 用于覆盖。 在提供之前初始化,在提供数据之前把数据注入进来,用于提供
src/core/instance/inject.js
export function initInjections (vm: Component) {
// 如果 有注入选项就继续向上找提供数据的父亲 知道找到位置 注意是个数组
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 此数据不应该被观察
toggleObserving(false)
Object.keys(result).forEach(key => {
// 响应数据 后续讲此过程
defineReactive(vm, key, result[key])
})
// 此数据可以被观察
toggleObserving(true)
}
}
状态初始化
响应数据是指:数据变化会导致dom变化的数据,观察数据就会响应数据,响应数据、依赖收集后续分析
src/core/instance/state.js
export function initState (vm: Component) {
// 依赖收集相关 后续讲
vm._watchers = []
const opts = vm.$options
// 初始化props
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)
// 初始化wath
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProps
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// 缓存key用
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// 不是根组件需要被转换为响应数据
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
// 类型检查
const value = validateProp(key, propsOptions, propsData, vm)
// 定义响应数据
defineReactive(props, key, value)
// 作用就是 访问this.xxx就能访问到 this.props.xxx
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
初始化方法
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
// 改变this指向 并且 定义到this.xxx就能访问到 this.methods.xxx
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
初始化数据
function initData (vm: Component) {
let data = vm.$options.data
// 把函数转换成data 此处收集依赖相关后续分析
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
}
// 检测重复
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (props && hasOwn(props, key)) {
// 开发环境会提示错误
} else if (!isReserved(key)) {
// 做代理
proxy(vm, `_data`, key)
}
}
// 观察数据 后续
observe(data, true /* asRootData */)
}
提供数据
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
// 把数据放到vm._provided上 用户注入数据时查找
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
系列
- 【源码导读 】在new Vue()之前,Vue是什么样子的?
- 【源码解析】创建Vue实例时干了什么?(本文)
- 【源码分析】Vue的响应数据
- 【敬请期待】...
结尾
感谢各位的阅读,错误是在所难免的,若有错误,或者有更好的理解,请在评论区留言,再次感谢。希望大家相互学习,共同进步。