作者一直在使用vue的框架开发各种管理台界面,最近随着产品页面越来越多,组件使用的数据越来越多时,页面的卡顿和掉帧很厉害,促使作者去解读一下vue源码
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)
}
//原型中添加_init方法
initMixin(Vue)
//原型中添加$set,$delete,$watch方法和$data,$props属性
stateMixin(Vue)
//原型中添加$on,$off,$once,$emit事件相关方法
eventsMixin(Vue)
//原型中添加_update,$forceUpdate,$destroy组件更新摧毁方法
lifecycleMixin(Vue)
//原型中添加$nextTick,_render以及render函数执行所需方法
renderMixin(Vue)
export default Vue
每一个vue 组件,都是由这个vue构造器创建出来的,从构造函数中可以看出来,Vue的constructor执行了 this._init(), 其他生命周期函数,都是通过函数往Vue构造函数和原型是上挂在方法和参数。
下面我们看看this._init做了什么
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
//_uid标志vue实例标识符
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if 性能埋点*/
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
//vue标志,避免被Observe观察
vm._isVue = true
//将传入的options与构造函数本身的options进行合并(插件都是默认配置和传入的配置进行合并的策略)
if (options && options._isComponent) {
//动态的options合并相当慢,这里只有需要处理一些特殊的参数属性
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),//获取当前构造函数的基本options
options || {},
vm
)
}
//代理vm实例属性的直接获取和for in行为,当某个属性获取不到时提示错误便于开发
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm) //初始化vue实例生命周期相关的属性
initEvents(vm) //初始化事件相关属性,若存在父监听事件,则添加到该实例上
initRender(vm) //初始化render渲染所需的slots 渲染函数等
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) //对props,methods,data,computed,watch进行初始化
initProvide(vm) // resolve provide after data/props
callHook(vm, '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)
}
//实例绑定到对应DOM元素上(组件构造函数没有el的,$mount过程不在这里)
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
从注释已经可以看得非常明白了,_init函数做了什么,下面我们里面几个关键函数
初始化state
initState 函数,对生命周期中的 props、methods、data、computed、watch选项进行初始化。
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)
}
}
initProps,initData,initComputed,就是把数据注册为响应式的,为数据声明getter和setter ,主要是通过 defineReactive(props, key, value)
如何收集依赖
在initState以后,vue组件的数据已经全部声明为响应式,那render函数又是如何收集Vue的组件中,对数据的依赖,在介绍依赖前,需要先介绍一下Vue里面两个特殊类,Dep类和Watcher类。
Dep类: 主要记录该属性,有哪些Watcher依赖该数据
Watcher类: 该类的Dep中,有一个数据发生变化,Watcher类就会执行update函数
const component = {
data () {
return {
message: ''
}
}
}
messag数据,有一个Dep类,记录这个属性被3个watcher类。
接下来,看页面的render是如何收集依赖
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
//建立一个watcher对象,并声明为render watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
申明一个watcher类,注册类每次update执行的方法updateComponent, 在new Watcher时,watcher构造函数中会访问一下get方法,然后调用 render方法,完成依赖收集。