作为MVVM框架的一种,Vue最为人津津乐道的当是数据与视图的绑定,将直接操作DOM节点变为修改 data
数据,利用 Virtual Dom
来 Diff
对比新旧视图,从而实现更新。不仅如此,还可以通过 Vue.prototype.$watch
来监听 data
的变化并执行回调函数,实现自定义的逻辑。虽然日常的编码运用已经驾轻就熟,但未曾去深究技术背后的实现原理。作为一个好学的程序员,知其然更要知其所以然,本文将从源码的角度来对Vue响应式数据中的观察者模式进行简析。
初始化 Vue 实例
在阅读源码时,因为文件繁多,引用复杂往往使我们不容易抓住重点,这里我们需要找到一个入口文件,从 Vue
构造函数开始,抛开其他无关因素,一步步理解响应式数据的实现原理。首先我们找到 Vue
构造函数:
// src/core/instance/index.js 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) }
// src/core/instance/init.js Vue.prototype._init = function (options) { ... // a flag to avoid this being observed vm._isVue = true // merge options // 初始化vm实例的$options if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } ... initLifecycle(vm) // 梳理实例的parent、root、children和refs,并初始化一些与生命周期相关的实例属性 initEvents(vm) // 初始化实例的listeners initRender(vm) // 初始化插槽,绑定createElement函数的vm实例 callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') if (vm.$options.el) { vm.$mount(vm.$options.el) // 挂载组件到节点 } }
为了方便阅读,我们去除了 flow
类型检查和部分无关代码。可以看到,在实例化Vue组件时,会调用 Vue.prototype._init
,而在方法内部,数据的初始化操作主要在 initState
(这里的 initInjections
和 initProvide
与 initProps
类似,在理解了 initState
原理后自然明白),因此我们重点来关注 initState
。
// src/core/instance/state.js export function initState (vm) { 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) } }
内容版权声明:除非注明,否则皆为本站原创文章。