首先让我们从最简单的一个实例Vue入手:
const app = new Vue({ // options 传入一个选项obj.这个obj即对于这个vue实例的初始化 })
通过查阅文档,我们可以知道这个options可以接受:
选项/数据
data
props
propsData(方便测试使用)
computed
methods
watch
选项 / DOM
选项 / 生命周期钩子
选项 / 资源
选项 / 杂项
具体未展开的内容请自行查阅相关文档,接下来让我们来看看传入的选项/数据是如何管理数据之间的相互依赖的。
const app = new Vue({ el: '#app', props: { a: { type: Object, default () { return { key1: 'a', key2: { a: 'b' } } } } }, data: { msg1: 'Hello world!', arr: { arr1: 1 } }, watch: { a (newVal, oldVal) { console.log(newVal, oldVal) } }, methods: { go () { console.log('This is simple demo') } } })
我们使用Vue这个构造函数去实例化了一个vue实例app。传入了props, data, watch, methods等属性。在实例化的过程中,Vue提供的构造函数就使用我们传入的options去完成数据的依赖管理,初始化的过程只有一次,但是在你自己的程序当中,数据的依赖管理的次数不止一次。
那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) } // 对Vue这个class进行mixin,即在原型上添加方法 // Vue.prototype.* = function () {} initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
当我们调用new Vue的时候,事实上就调用的Vue原型上的_init方法.
// 原型上提供_init方法,新建一个vue实例并传入options参数 Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { // 将传入的这些options选项挂载到vm.$options属性上 vm.$options = mergeOptions( // components/filter/directive resolveConstructorOptions(vm.constructor), // this._init()传入的options options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 自身的实例 // 接下来所有的操作都是在这个实例上添加方法 initLifecycle(vm) // lifecycle初始化 initEvents(vm) // events初始化 vm._events, 主要是提供vm实例上的$on/$emit/$off/$off等方法 initRender(vm) // 初始化渲染函数,在vm上绑定$createElement方法 callHook(vm, 'beforeCreate') // 钩子函数的执行, beforeCreate initInjections(vm) // resolve injections before data/props initState(vm) // Observe data添加对data的监听, 将data转化为getters/setters initProvide(vm) // resolve provide after data/props callHook(vm, 'created') // 钩子函数的执行, created // vm挂载的根元素 if (vm.$options.el) { vm.$mount(vm.$options.el) } }
其中在this._init()方法中调用initState(vm),完成对vm这个实例的数据的监听,也是本文所要展开说的具体内容。
export function initState (vm: Component) { // 首先在vm上初始化一个_watchers数组,缓存这个vm上的所有watcher vm._watchers = [] // 获取options,包括在new Vue传入的,同时还包括了Vue所继承的options const opts = vm.$options // 初始化props属性 if (opts.props) initProps(vm, opts.props) // 初始化methods属性 if (opts.methods) initMethods(vm, opts.methods) // 初始化data属性 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } // 初始化computed属性 if (opts.computed) initComputed(vm, opts.computed) // 初始化watch属性 if (opts.watch) initWatch(vm, opts.watch) }
initProps
我们在实例化app的时候,在构造函数里面传入的options中有props属性:
props: { a: { type: Object, default () { return { key1: 'a', key2: { a: 'b' } } } } }
function initProps (vm: Component, propsOptions: Object) { // propsData主要是为了方便测试使用 const propsData = vm.$options.propsData || {} // 新建vm._props对象,可以通过app实例去访问 const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. // 缓存的prop key const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted observerState.shouldConvert = isRoot for (const key in propsOptions) { // this._init传入的options中的props属性 keys.push(key) // 注意这个validateProp方法,不仅完成了prop属性类型验证的,同时将prop的值都转化为了getter/setter,并返回一个observer const value = validateProp(key, propsOptions, propsData, vm) // 将这个key对应的值转化为getter/setter defineReactive(props, key, value) // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 如果在vm这个实例上没有key属性,那么就通过proxy转化为proxyGetter/proxySetter, 并挂载到vm实例上,可以通过app._props[key]这种形式去访问 if (!(key in vm)) { proxy(vm, `_props`, key) } } observerState.shouldConvert = true }
接下来看下validateProp(key, propsOptions, propsData, vm)方法内部到底发生了什么。