前面我们讲到了_init函数的执行流程,简单回顾下:
本章咱们主要讲解initState函数的处理过程,咱们先看下init的主函数
function initState(vm: Component) {vm._watchers = []const opts = vm.$optionsif (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)}
}
看上面代码,先声明了一个_watchers的空数组;然后依次判断传递进来的options是否包含系列参数;依次执行initProps、initMethods、initData、initComputed、initWatch。
initProps函数主要是处理传进来的props对象,但是这个props对象是在上一篇文章中讲到的normalizeProps函数处理后的对象,不是传递进来的原对象。来看下initProps的代码:
function initProps(vm: Component, propsOptions: Object) {const propsData = vm.$options.propsData || {}const props = vm._props = {}const keys = vm.$options._propKeys = []const isRoot = !vm.$parentif (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {keys.push(key)const value = validateProp(key, propsOptions, propsData, vm)defineReactive(props, key, value)if (!(key in vm)) {proxy(vm, `_props`, key)}}toggleObserving(true)
}
上面代码解读:
function proxy(target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter() {return this[sourceKey][key]}sharedPropertyDefinition.set = function proxySetter(val) {this[sourceKey][key] = val}Object.defineProperty(target, key, sharedPropertyDefinition)
}
initMethods方法是用来处理传递进来的methods参数,把methods绑定到当前实例上面
function initMethods(vm: Component, methods: Object) {const props = vm.$options.propsfor (const key in methods) {if (process.env.NODE_ENV !== 'production') {if (typeof methods[key] !== 'function') {warn(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` + `Did you reference the function correctly?`, vm)}if (props && hasOwn(props, key)) {warn(`Method "${key}" has already been defined as a prop.`, vm)}if ((key in vm) && isReserved(key)) {warn(`Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.`)}}vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
}
上面代码解读:
第一步获取了props;
第二步遍历methods;
参考 Vue面试题详细解答
initData方法是用来处理传递进来的data参数,添加监听
function initData(vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}const keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`, vm)}}if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm)} else if (!isReserved(key)) {// 实现代理,可以this.massage 来进行访问this._data.messageproxy(vm, `_data`, key)}}observe(data, true /* asRootData */)
}
上面代码解读:
initComputed是用来处理传进来的computed参数
function initComputed(vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)const isSSR = isServerRendering()for (const key in computed) {const userDef = computed[key]const getter = typeof userDef === 'function' ? userDef : userDef.getif (!isSSR) {watchers[key] = new Watcher(vm,getter || noop,noop,{lazy: true})}if (!(key in vm)) {defineComputed(vm, key, userDef)}}
}
initComputed方法解读:
function defineComputed(target: any, key: string, userDef: Object | Function) {const shouldCache = !isServerRendering()if (typeof userDef === 'function') {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): createGetterInvoker(userDef)sharedPropertyDefinition.set = noop} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): createGetterInvoker(userDef.get): noopsharedPropertyDefinition.set = userDef.set || noop}Object.defineProperty(target, key, sharedPropertyDefinition)
}
defineComputed方法解读:
下面来看下createGetterInvoker:
function createGetterInvoker(fn) {return function computedGetter() {return fn.call(this, this)}
}
上面代码直接返回了一个函数,函数内部调用的是传递进来的fn函数,fn函数是从defineComputed传进来的,值为userDef或者userDef.get。
下面来看下createComputedGetter:
function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key]if (watcher) {if (watcher.dirty) {watcher.evaluate()}if (Dep.target) {watcher.depend()}return watcher.value}}
}
上面代码返回了一个computedGetter的函数,函数内部分析:
initWatch是用来处理传进来的watch参数。
function initWatch(vm: Component, watch: Object) {for (const key in watch) {const handler = watch[key]if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i])}} else {createWatcher(vm, key, handler)}}
}
initWatch函数解读:
遍历watch,根据key获取handler,handler为数组遍历执行createWatcher,不为数组直接执行createWatcher;
来看下createWatcher:
function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {if (isPlainObject(handler)) {options = handlerhandler = handler.handler}if (typeof handler === 'string') {handler = vm[handler]}return vm.$watch(expOrFn, handler, options)
}
createWatcher代码解读:
watch: {telephone: function (newValue, oldValue) {console.log('telephone')},name: 'printName',message: ['printName', 'printValue'],address: [{handler: function (newValue, oldValue) {console.log('address')}}]},methods: {printName(newValue, oldValue) {console.log('printName')},printValue(newValue, oldValue) {console.log('printValue')}}
现在咱们来看下watch的实现,watch是Vue原型上的方法,主流程篇简单提了一下,流程图上面看到$watch是在statesMixin函数里面给Vue挂载到原型对象上的。
Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {const vm: Component = thisif (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}options = options || {}options.user = trueconst watcher = new Watcher(vm, expOrFn, cb, options)if (options.immediate) {try {cb.call(vm, watcher.value)} catch (error) {handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)}}return function unwatchFn() {watcher.teardown()}
}
上面代码就是$watch函数的实现,咱们一步步来看下。