在 Vue 中,以$、_开头的属性,例如:$private、_private;它们是不会被 Vue 代理。
所以在 this 上是访问不到它们的,watch 监听 $private、_private 也是不会生效的。
如果要访问这些属性,可以通过 this.$data、this._data 中访问到这些属性。同样的,可以通过 watch $data.$private、_data.$private 监听到它的变化。
源码梳理
proxy _data:代理 _data
在我们的 vue 文件中定义: data( ) => ( { name: 1, _private: 0 } )
Vue 内部代码执行:new Vue( ) -> initState -> initData
initData( )
initData() 方法会对实例上的 data:vm._data 代理。
通过 isReserved() 方法判断 data 的 key 是否以$、_开头,如果不是以$、_开头就对执行 proxy(vm, '_data', key) 方法。
while 循环至 key === name 时执行 proxy;key === _private 时不会执行 proxy;
# vue/src/core/instance/state.js
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 */)
}
isReserved ( )
isReserved() 方法:工具函数。判断 str 是否以 $、_ 开头。
# vue/src/core/util/lang.js
/**
* Check if a string starts with $ or _
*/
export function isReserved (str: string): boolean {
const c = (str + '').charCodeAt(0)
return c === 0x24 || c === 0x5F
}
proxy( )
参数:
- target:vm ( this );
- sourceKey:_data;
- key:data 中的 key,即 name;
proxy() 方法:将 vm._data 上的属性代理至 vm: this 上。
通过 Object.defineProperty 为 target:this 上绑定了 key,访问 this.name 相当于是访问 this[sourceKey][key],即 this._data.name;所以赋值 this.name = 1,也是为 this._data.name 赋值。
# vue/src/core/instance/state.js
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)
}
由于 isReserved( ) 方法只 proxy 了 name,并没有 proxy _private。
所以此时,this = { name: 1, _data: { name: 1, _private: 0 } }
observe _data:响应式
observe(data, true),即 observe(vm._data, true)。对 vm._data 对象观察,设置为响应式的。
# vue/src/core/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// code...
// proxy data
// observe data
observe(data, true /* asRootData */)
}
observe( )
参数:
- value:vm._data
调用 new Observer(value)
# vue/src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
class Observer
walk( ) 方法遍历 value:vm._data 的 key,将所有的属性设置为响应式的。
# vue/src/core/observer/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
defineReactive( )
通过 Object.defineProperty 为 obj:vm._data 的 key 设置 set、get
# vue/src/core/observer/index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
虽然 _private 没有被 proxy 代理到 this 上,但是它仍然被设置为响应式的。
_data 与 $data
# 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)
}
stateMixin(Vue)
stateMixin( )
stateMixin( ) 方法是为 Vue 的原型 prototype 上绑定 $data,即 this._data;所以$data 和 _data 的指向是一样的。
# vue/src/core/instance/state.js
export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const 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()
}
}
}
所以可以通过 _data、$data 方式访问到 $、_ 的属性。
总结:
-
data 中以 $、_ 开头的属性不会被代理到 this 上,但是可以通过 _data、$data 访问
-
因为 this 上没有 $、_ 的属性,所以 watch 也无法监听 $、_ 属性的变化
-
由于 vm._data 被 defineReactive 处理过,所以可以通过 watch _data.$xxx、_data._xxx 监听属性的变化