vue源码探索之初始化
程序员文章站
2024-01-03 21:47:34
...
vue 初始化
在使用vue的过程中,使用new vue 是个必须的过程,但是在这个过程中发生了些什么,今天主要就是来探索这部分的内容,下面是整体结构图。
开始部分
通常我们使用下面这段代码来生成一个vue实例,用于页面渲染,其中我们提供的配置对象中包含vue常见的几个属性,el、data、computed、methods等
var demo = new Vue({
el: '#demo',
props: {
name: {
type: String,
default: 'jerry'
}
},
data: {
treeData: data
},
computed: {
dataLength () {
return this.treeData.children.length
}
},
methods: {
clickLength () {
console.log('click data leength: ', this.dataLength())
}
}
})
vue 的开始部分很简单,只有一个叫做Vue的构造函数,在new Vue的过程中调用Vue原型上的_init方法,对新建的对象进行初始化
function Vue (options) {
// 如果直接执行Vue,当作一个函数来执行的情况下 开发环境中给出警告 需要通过new 来创建对象, 并且在 ES6 语法中此时的this为空,代码也会报错 如下图所示
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 在开发环境不能当作函数来调用,只能使用new创建新对象
有了Vue的构造函数之后,在真正开始创建对象之前还需要初始化这个构造函数本身的一些属性和方法
构造函数初始化部分
构造函数的初始化分为以下五个部分
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
initMixin(Vue)
function initMixin (Vue) {
Vue.prototype._init = function (options) {
// 场景不同分别执行不同的方法,根据options内部的_isComponent值确定,具体情况后面会讲到,在使用new vue创建对象的过程中使用的是第二个函数,它会将继承过来的options与当前的options合并处理
initInternalComponent(vm, options)||resolveConstructorOptions(vm.constructor)
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
vm.$mount(vm.$options.el)
}
}
这个函数的作用是初始化 Vue 的 _init 方法,该方法内部实现的功能主要由以下几部分
- 合并配置项,得到合并之后的 options,用于最终创建Vue的示例,主要使用的方法有两个:initInternalComponent 和 resolveConstructorOptions
- 初始化生命周期
initLifecycle(vm),这个函数的作用是在vue示例上创建需要后续需要使用到的parent children root refs等属性,并且将vue生命周期状态相关的变量初始化
vm.$parent = parent // 父节点
vm.$root = parent ? parent.$root : vm // 跟节点
vm.$children = [] // 子节点列表
vm.$refs = {} // refs 引用数组
vm._watcher = null // 观察者对象
vm._inactive = null
vm._directInactive = false
vm._isMounted = false // 是否挂载
vm._isDestroyed = false // 是否销毁
vm._isBeingDestroyed = false // 是否正在销毁
- 初始化绑定事件
initEvents(vm)
这个函数的作用是使用on once remove 将从父级继承来的事件绑定在元素上
实现once 的方法是使用必报保存需要执行的目标元素,并返回真正的绑定函数,并且在执行之后立即销毁这个绑定的事件
function createOnceHandler (event, fn) {
const _target = target
return function onceHandler () {
const res = fn.apply(null, arguments)
if (res !== null) {
_target.$off(event, onceHandler)
}
}
}
在new vue的过程中,因为options项中不会有事件监听的信息,所以不会事件的绑定,并且此时还未执行eventsMixin创建事件绑定所需要的方法
- 初始化render函数
initRender(vm)
这个方法主要实现的将vue示例渲染到dom元素上的过程,包括- 初始化插槽和作用域插槽 $slots 和 $scopedSlots
- 初始化虚拟节点 _vnode = null
- 初始化创建 虚拟dom 元素的方法 vm._c 和 vm.$createElement
这部分内容较多,放到后面作为专门探索的部分 - 初始化设置元素的属性 $attrs
- 初始化设置元素监听的事件 $listeners
- 触发vue生命周期钩子函数 beforeCreate
callHook(vm, 'beforeCreate')
- 初始化依赖注入 (依赖注入部分的内容放到后面)
- initData 初始化vue对象的数据,包括以下五个方面
- initProps(vm, opts.props)
- initMethods(vm, opts.methods)
- initData(vm)
observe(data, true) 使用observe方法使data中的数据编程响应式的 - initComputed(vm, opts.computed)
- initWatch(vm, opts.watch)
- toggleObserving 方法能够切换数据是否响应的状态,在初始化props的过程中需要关闭响应
- 初始化provide
- 触发vue生命周期 created 函数
callHook(vm, ‘created’) - 最后执行vue的el指定的dom元素上
vm.options.el)
stateMixin(Vue)
这一步的作用是在Vue的原型上增加props、delete、$watch
Vue.prototype.$watch = function (expOrFn, cb, options) {
// expOrFn 分为两种情况 一种是字符串 一种是一个函数
// 如果是字符串 则截取字符串中的变量,得到getter
this.getter = parsePath(expOrFn)
// 如果是函数 则函数执行结果为 getter
this.getter = expOrFn
// cb 回调函数也分两种情况 对应两种写法
// 当cb是一个函数时 例如 v.$watch('age', () => {console.log(arguments), {}}),vue直接创建一个新的 watcher
new Watcher(vm, expOrFn, cb, options)
// 当cb 时一个{}类型的对象时,对应另一种写法,例如
v.$watch('age', {handler: () => { console.log(111)}, deep: true}, {})
// vue 会将cb这个对象中的handler取出来作为真正的回调函数,重新掉一遍函数$watch
vm.$watch(expOrFn, cb.handler, options)
}
// cb对象中handler也可以时一个字符串,对应的是vm对象上的某个方法,如下图
cb对象中handler也可以时一个字符串,对应的是vm对象上的某个方法,如下图
eventsMixin(Vue)
这一步给Vue的原型增加了事件绑定和解绑相关的工具函数
- $on
(vm._events[event] || (vm._events[event] = [])).push(fn)
- $once
使用闭包保存下vue示例对象vm 绑定一个新的函数,函数内部执行绑定的函数并且解绑
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
- $emit
将 vm._events[event] 中存储的方法依次执行一遍 - $off
如果off方法没有提供需要解绑的函数,则情况事件下所有的方法,否则只解绑指定的方法
let cbs = vm._events[event]
// 不传需要解绑的方法 视为解绑此事件的所有事件函数
if (!fn) {
vm._events[event] = null
return vm
}
// 否则解绑指定的事件函数
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
lifecycleMixin(Vue)
这一步为Vue的原型增加生命周期相关方法
- _update
setActiveInstance(vm): 将全局变量 activeInstance 临时保存并替换为当前的vue实例 vm ,并返回恢复原来 activeInstance 原值的方法
export function setActiveInstance(vm: Component) {
// 临时存储原始值
const prevActiveInstance = activeInstance
activeInstance = vm
// 对外提供复原原始值的方法
return () => {
activeInstance = prevActiveInstance
}
}
这个方法跟虚拟dom关系比较紧密,放到虚拟dom部分详细探索
- $forceUpdate
调用vue实例上的watcher对象,强制跟新所有依赖 - $destroy
触发生命周期钩子函数 beforeDestroy 和 destroyed,并且释放vue对象的引用
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
renderMixin(Vue)
这一步的作用是为vue原型上增加渲染相关的函数
- installRenderHelpers(Vue.prototype)
为vue原型增加渲染需要的函数方法,作为后续研究的一项暂时保留 - $nextTick
内部实现方式有四种,依赖执行环境的支持情况:- Promise
- MutationObserver
- setImmediate
- setTimeout(callback, 0)
- _render
创建虚拟 dom 节点 vnode,暂时保留