这个问题最基础的是先理解数据的引用类型(懂的人都懂)。vue 中组件的实例的创建都会先执行其构造函数,其中会执行 vue 的 init 方法进行组件的初始化,在initState 中会进行 data 的初始化,即initData()。
在源码中:src\core\instance\state.js -initData() 中 函数每次执行都会返回全新的 data 对象。
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 */)
}
关于 data 的核心代码如下:
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
- 如果 data 是函数,则执行工厂函数返回一个新的 data 对象
- 否则直接返回这个 data 对象。会导致所有的组件实例共用一个 状态对象,造成数据污染。
当然,vue 在框架中也加入了 data 的类型检测,发出报错提醒。
对于根实例,它是单例的,全局只会创建一个,所以不会出现上述问题。代码上:在 init 中进行 mergeOptions 的时候,会进行判断是否为自定义组件。
// 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
)
}
在初始化组件的构造函数的过程中,会执行 Vue.extend 方法 进行 mergeOptions 选项合并,在合并的过程中,执行 mergeField 时会进行 data 的合并策略,即strats策略对象,在 src\core\util\options.js 中代码如下:
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
在 data 的合并检测中会对 组件 data 进行判断并进行报错提醒,此时会根据 vm 区分跟实例和自定义组件,根实例才会有vm,所以在初始化跟实例的时候会跳过该检测逻辑!!!(单例不需要该逻辑限制)
结论
- Vue 组件可能存在多个实例,如果使用对象的形式定义 data,则会导致它们共用一个 data 对象,会造成该对象污染;
- 采用函数形式定义,在 initData 时会将其作为工厂函数返回全新的 data 对象,有效避免多实例之间的状态污染问题。
- Vue 跟实例创建过程中,不存在该限制,因为根实例只能有一个,不需要担心这种情况。