12月離職后,打算在年后再找工作了,最近陸陸續續的看了黃軼老師的vue源碼解析,趁着還有幾天過年時間記錄一下。
目標:vue如何實現通過this.key,就能直接訪問data,props,methods,里面的key。
源碼:當我們在項目中new Vue實例后會執行Vue構造函數的_init方法,並執行一些混入
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) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
_init方法定義在initMixin方法中,在Vue原型上定義了_init方法,其他邏輯暫時不做分析,這里我們看到 將Vue實例賦值給了vm,然后合並options,然后執行了一些初始化方法,最后掛載方法。
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ // 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 { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } vm._self = vm 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') if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
我們能夠通過this.key訪問是在initState方法執行時,里面執行了initData方法
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) } }
initData方法實現如下,首先將data賦值給vm._data,然后判斷data,props,methods里是否重名,然后調用proxy方法,最后調用observe方法將其變成響應式對象,這里暫不分析
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}// proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (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)) { proxy(vm, `_data`, key) } } // observe data observe(data, true /* asRootData */) }
proxy方法實現就是更改defineProperty里的屬性,將其的getter和setter,代理到sourceKey上,也就是我們上面說的vm._data上,然后我們就可以通過this.key訪問data,props,message里面的key了
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } export 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) }