function initData (vm: Component) {
// 取到data
let data = vm.$options.data
// 如果data的类型是一个函数,那么将其执行 getData() 并将其结果作为data选项的值,否则就是data
// 此处可以想象一个场景:定义了一个组件comp,这个组件在被声明的时候其实只执行了一次。
// 如果是直接给data: { count: 1 },每一次初始化的时候组件的实例的data都指向同一个地址
// 这样就会造成实例污染,大家用的都是同一个data选项,你改一下我改一下岂不是乱了套。
// 所以vue内部做了处理,如果是组件,那么就只能写data函数,否则会报错。
// 所以vue为什么不让我们在组件里直接给data定义成对象,是为了避免组件多实例之间的相互污染,所以将其作为工厂函数的形式去定义。
// 而为什么在根实例里定义data就可以直接定义为对象呢,因为vue2里的data是单例的,只会出现一个,所以就不存在相互污染的可能性。
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 如果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
)
}
结论
Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致他们公用一个data对象,那么状态变更将会影响所有组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返回给全新的data对象,有效规避多实例之前状态污染问题。而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况的发生。在源码中数据初始化时,发现会检测data的形式从而去执行他的具体的执行方式,另外的话根实例在创建的时候可能在合并选项的时候,他会有实例拿到只有根实例有实例,他可以有效的避免根实例的校验,而一个组件模块在当时可能还没有实例,没法躲过校验的if逻辑,直接会被检测data的类型,所以用户在写代码的时候,其实也没办法在组件中给data定义为对象。