在使用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)
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 方法,该方法内部实现的功能主要由以下几部分
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 // 是否正在销毁
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创建事件绑定所需要的方法
callHook(vm, 'beforeCreate')
这一步的作用是在Vue的原型上增加 d a t a 、 data、 data、props、 s e t 、 set、 set、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对象上的某个方法,如下图
这一步给Vue的原型增加了事件绑定和解绑相关的工具函数
(vm._events[event] || (vm._events[event] = [])).push(fn)
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
}
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
}
}
这一步为Vue的原型增加生命周期相关方法
export function setActiveInstance(vm: Component) {
// 临时存储原始值
const prevActiveInstance = activeInstance
activeInstance = vm
// 对外提供复原原始值的方法
return () => {
activeInstance = prevActiveInstance
}
}
这个方法跟虚拟dom关系比较紧密,放到虚拟dom部分详细探索
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
这一步的作用是为vue原型上增加渲染相关的函数