关于vue.js:vue2子组件中定义的data为什么必须是一个function

是什么

需要:当初咱们须要在一个组件中定义一个name变量,并且把这个变量显示在界面上,代码:

<template>
  <div>
    {{ name }}
  </div>
</template>
<script>
export default {
  data() {
    return {
      name: 'syz'
    }
  }
}
</script> 

那么问题来了,为什么组件中的data须要是一个函数呢,能够将data申明为一个对象吗,这样不是更间接吗?代码:

<template>
  <div>
    {{ name }}
  </div>
</template>
<script>
export default {
  data: {
    name: 'syz'
  }
}
</script> 

如果是下面这种写法,不出意外的话,在vs code data处将会呈现红线报错 data property in component must be a function,提醒咱们data必须是一个function。

为什么

好,重点来了,为什么呢?起因就是:通过函数返回数据对象,保障了每个组件实例都有一个惟一的数据正本,防止了组件间数据相互影响。

先上论断:

每个子组件注册后会被解决成一个VueComponent实例,相似上面的MyVueComponent(...), data属性会挂在他的原型链下面

当子组件中的data是一个Object时,代码:

function MyVueComponent(){
  
}
MyVueComponent.prototype.data = {
  name:'jack',
  age:22,
}
const componentA = new MyVueComponent();
const componentB = new MyVueComponent();
componentA.data.age=55;
console.log(componentA.data.age,componentB.data.age) // 55,55

能够看到,当componentA.data.age赋值为55时,componentB.data.age也被更改为55了

当子组件的data是一个function时,代码:

function MyVueComponent(){
  this.data = this.data()
}
MyVueComponent.prototype.data = function() {
    return  {
      age:22
    }
}
const componentA = new MyVueComponent();
const componentB = new MyVueComponent();
componentA.data.age=55;
console.log(componentA.data.age,componentB.data.age) // 55,22

能够看到,componentA.data.age更改后,并不会影响到componentB.data.age的值,也就是咱们下面说的,防止了组件间数据相互影响

留神: 防止组件中数据相互影响,除了将data定义为function,还须要在data中返回一个全新的Object(领有独立的内存地址),咱们晓得,js中对象的赋值是援用传递,代码:

const myData = {
  age:22
}
function MyVueComponent(){
  this.data = this.data()
}
MyVueComponent.prototype.data = function() {
    return myData
}
const componentA = new MyVueComponent();
const componentB = new MyVueComponent();
componentA.data.age=55;
console.log(componentA.data.age,componentB.data.age) // 55,55

能够看到,尽管咱们将data定义成function了,然而在datareturn了对象myData,这里是援用传递,后续在批改data中的值时,其实批改的是同一个内存地址的数据,所以还是存在数据相互影响了。

再看细节:

当咱们须要在一个父组件中应用子组件时,以全局子组件为例子,代码:

// app.vue
<template>
  <div id="app">
    {{name}}
  </div>
</template>
<script>
export default {
  name: 'App',
  data() {
    return {
      name: 'haha'
    }
  }
}
</script>
// main.js
import Vue from 'vue/dist/vue.esm.js'
import App from './App.vue'
Vue.component('app', App)
new Vue({
  el: '#app',
  template: '<app></app>'
})

首先,执行Vue.component(…)注册子组件

调用Vue.component()这个办法,看一下Vue.component()的外部实现:

var ASSET_TYPES = [
  'component',
  'directive',
  'filter'
];
function initAssetRegisters (Vue) {
  ASSET_TYPES.forEach(function (type) {
    Vue[type] = function (
      id,
      definition
    ) {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id);
        }
        // 要害逻辑
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id;
          definition = this.options._base.extend(definition);
        }
        // ...略去局部无关代码
        this.options[type + 's'][id] = definition;
        return definition
      }
    };
  });
}

Vue初始化时会执行initAssetRegisters(...)这个办法,那么Vue.component(...)就进去了。

definition参数就是咱们在下面例子中调用Vue.component(...)传进来的第二个参数,也就是:

{
  name: 'App',
  data() {
    return {
      name: 'haha'
    }
  }
}

接着会执行definition = this.options._base.extend(definition),会调用Vue.extend(...)将内部传进来的对象通过各种解决,最初结构出一个VueComponent的实例对象,当然,咱们传进来的data对象也挂在外面,在浏览器debugger看下过程数据会比较清楚。代码:

Vue.extend = function (extendOptions) {
    extendOptions = extendOptions || {};
    var Super = this;
    var SuperId = Super.cid;
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    var name = extendOptions.name || Super.options.name;
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name);
    }
    var Sub = function VueComponent (options) {
      this._init(options);
    };
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    Sub.cid = cid++;
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );
    Sub['super'] = Super;
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type];
    });
    if (name) {
      Sub.options.components[name] = Sub;
    }
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    Sub.sealedOptions = extend({}, Sub.options);
    cachedCtors[SuperId] = Sub;
    return Sub
};

接着,执行new Vue(…)

代码:

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

执行程序:this._init(...) -> initState(vm) ,也就是在initState(vm) 这个办法中会去初始化组件中data数据,看下代码:

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
}

看下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
    )
  }
  // ...略去局部无关代码
}

能够看到,在第二行代码,会先判断data是不是一个function
如果是function,接着调用getData(...)办法获取数据。
如果不是function,就间接获取组件中的data数据。

那什么状况下data不是一个function呢?
当咱们应用new Vue(...) 实例的组件(个别是根节点)是不会被复用的。
因而也就不存在组件中数据相互影响的问题了。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理