是什么

需要:当初咱们须要在一个组件中定义一个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.jsimport 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(...) 实例的组件(个别是根节点)是不会被复用的。
因而也就不存在组件中数据相互影响的问题了。