Vue 有一个非常有趣的功能,就是我们所有传进去的 data
、methods
或者 props
,都会挂载到 Vue 实例上, 我们可以通过 this.xxx
的简单做法来进行访问。那么,这到底是怎么实现的呢?
首先可以来看看源码部分。相关的源码实现就在 src\core\instance\state.js
文件下。
由于 methods
和data
还有 props
的实现不一致,因此这里简单拉出来单独讲。
首先把目光聚焦在 initMethods
上。
function initMethods (vm: Component, methods: Object) { const props = vm.$options.props for (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) } }
这里主要执行了这么几步骤:
vm.$options
上的 props
。methods
上的属性, 分别判断是否是函数、是否 props
上已经有了相同属性名、是否和现有 Vue
实例方法冲突。vm[key]
上进行代理,并且通过 bind
方法 绑定 this
。这里主要是最后一个步骤,使得我们能够直接在 Vue
实例上访问 methods
中的方法。
本章节从 data 的初始化上入手,相关方法为 initData
。
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // 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 */) }
简单的看一下,initData 主要执行了:
data
。data
赋值到 vm._data
上。data
中的 key
,判断是否在 methods
和 props
中存在同样的 key
, 存在则报个 warnning
。proxy
函数代理key
。其中,步骤三是因为 methods 和 props 的属性最终也可以直接通过 Vue 实例进行访问,因此我们需要确保 key 的唯一性。
而步骤四才是本章的核心函数,它实现了 vm.xxx
对vm._data.xxx
的访问。当然 props
也是同理。
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) }
可以清楚的看到,首先定义了一个 属性描述符 sharedPropertyDefinition
,get
和 set
初始指定为一个空函数。而proxy 方法 接受三个参数,分别是:
_data
,就传入 "_data"
。key
。在 proxy
中,sharedPropertyDefinition
的 get
和 set
方法会被重写,并通过 Object.defineProperty(target, key, sharedPropertyDefinition)
来进行属性的代理。
那么结合到代码中的实现,在 initData
阶段,我们执行了 proxy(vm, '_data', key)
,那么,对于这里而言,target
事实上就是 vm
, 而 key
则是 vm._data
中的属性 key
。通过重写后的 get
和 set
方法不难看出,假设 a
是 data
中的数据,那么当我们访问 vm.a
的时候,实际上访问的是 vm._data.a
;而当我们赋值 vm.a = 1
的时候,实际上会代理到 vm._data.a = 1
上去。
至此,proxy
已经真相了,而 props
也同样通过 proxy
来代理属性的访问。
proxy(vm, `_props`, key)
这里我们不主张直接通过 vm._data.xxx
的方式来进行操作,一方面下划线从规范来讲属于私有属性,是不允许被直接访问的;另一方面, 直接访问 _data
可能会造成一些不可预见的 bug,比方说新增属性不会经过响应式处理。
学过一个东西,肯定要自己造个轮子来简单验证一下。直接上代码:
// vue 如何通过 this.xxx 访问 this._data.xxx const noop = () => {} const sharedPropertypeDefinition = { enumerable: true, configurable: true, get: noop, set: noop } function proxy (target, sourceKey, key) { sharedPropertypeDefinition.get = function proxyGet () { return target[sourceKey][key] } sharedPropertypeDefinition.set = function proxySet (val) { target[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertypeDefinition) } function Vue (data) { this._data = data Object.keys(this._data).forEach(key => { proxy(this, '_data', key) }) } const vueIns = new Vue({a: 1, b: 2}) console.log(vueIns.a) console.log(vueIns.b) vueIns.a = 111 vueIns.b = 222 console.log(vueIns.a) console.log(vueIns.b)
data
、methods
和 props
上的属性。我们可以直接通过 this.key
的方式来进行访问和操作。data
、methods
和 props
上的属性具有唯一性。Object.defineProperty
的方式来进行属性代理。个人博客 在线阅读直通车