结合源码,Vue组件data为什么必须是个函数而Vue的根实例则没有此限制

源码:src\core\instance\state.js-initData()
函数每次执行都会返回全新data对象实例

1.测试代码

<body>
  <div id="demo">
    <comp>comp>
    <comp>comp>
  div>
  <script src="../dist/vue.js">script>
  <script>
    Vue.component('comp',{
      template:'
{{connter}}
'
, data:{counter:0} }) //创建实例 const app = new Vue({ el:'#demo' })
script> body>

2.源码分析

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
    )
  }

如果data是函数,则执行之并将其结果作为data选项的值,否则使用用户设置的data。
如果每一个组件的data都使用的是一个对象,则用户设置的data就会作为vue实例上的data选项。由于Vue.component这个方法在声明组件的时候只执行一次,每一次初始化的时候,拿到的data选项都指向同一个地方,一个组件的不同实例之间用的数据就共享了,产生了数据污染。

根组件为什么可以?因为根组件实例是单例不可能出现多例

3.结论

Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况。

4.结合源码来一遍

我在看源码的时候在数据初始化代码那,我发现它会检测data的形式,从而去执行它具体的执行方式。另外的话,由于根实例在创建的时候,可能在合并选项的时候,只有根实例才会拿到实例vm,所以他可以躲过data选项的校验,而如果是一个普通的组件的话,由于它当时不存在实例,所以它没有办法躲过校验的if逻辑,所以要检测它data的类型。这个在options.js源码中可以查找到

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }

你可能感兴趣的:(vue)