Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试官:为什么data属性是一个函数而不是一个对象? #9

Open
febobo opened this issue Dec 10, 2020 · 13 comments
Open

Comments

@febobo
Copy link
Owner

febobo commented Dec 10, 2020

No description provided.

@febobo
Copy link
Owner Author

febobo commented Dec 11, 2020

面试官:为什么data属性是一个函数而不是一个对象?

image.png

一、实例和组件定义data的区别

vue实例的时候定义data属性既可以是一个对象,也可以是一个函数

const app = new Vue({
    el:"#app",
    // 对象格式
    data:{
        foo:"foo"
    },
    // 函数格式
    data(){
        return {
             foo:"foo"
        }
    }
})

组件中定义data属性,只能是一个函数

如果为组件data直接定义为一个对象

Vue.component('component1',{
    template:`<div>组件</div>`,
    data:{
        foo:"foo"
    }
})

则会得到警告信息

image.png

警告说明:返回的data应该是一个函数在每一个组件实例中

二、组件data定义函数与对象的区别

上面讲到组件data必须是一个函数,不知道大家有没有思考过这是为什么呢?

在我们定义好一个组件的时候,vue最终都会通过Vue.extend()构成组件实例

这里我们模仿组件构造函数,定义data属性,采用对象的形式

function Component(){
 
}
Component.prototype.data = {
	count : 0
}

创建两个组件实例

const componentA = new Component()
const componentB = new Component()

修改componentA组件data属性的值,componentB中的值也发生了改变

console.log(componentB.data.count)  // 0
componentA.data.count = 1
console.log(componentB.data.count)  // 1

产生这样的原因这是两者共用了同一个内存地址,componentA修改的内容,同样对componentB产生了影响

如果我们采用函数的形式,则不会出现这种情况(函数返回的对象内存地址并不相同)

function Component(){
	this.data = this.data()
}
Component.prototype.data = function (){
    return {
   		count : 0
    }
}

修改componentA组件data属性的值,componentB中的值不受影响

console.log(componentB.data.count)  // 0
componentA.data.count = 1
console.log(componentB.data.count)  // 0

vue组件可能会有很多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染

三、原理分析

首先可以看看vue初始化data的代码,data的定义可以是函数也可以是对象

源码位置:/vue-dev/src/core/instance/state.js

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
    ...
}

data既能是object也能是function,那为什么还会出现上文警告呢?

别急,继续看下文

组件在创建的时候,会进行选项的合并

源码位置:/vue-dev/src/core/util/options.js

自定义组件会进入mergeOptions进行选项合并

Vue.prototype._init = function (options?: Object) {
    ...
    // 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
      )
    }
    ...
  }

定义data会进行数据校验

源码位置:/vue-dev/src/core/instance/init.js

这时候vm实例为undefined,进入if判断,若data类型不是function,则出现警告提示

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必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

@ufresh2013
Copy link

问个问题,为什么React的state不需要返回一个函数?

@ibearye
Copy link

ibearye commented Aug 15, 2021

问个问题,为什么React的state不需要返回一个函数?

类组件,你看看你写state的时候加static了吗?state又不是静态属性,不在原型上,在实例对象上,都是独立的

@beautifulwhale
Copy link

回答的真好 确实是在每一个组件上

@betteryangjie
Copy link

为什么不只支持函数呢?支持对象的意义何在?有什么场景是只能传对象,不能传函数的吗?

@toupil111
Copy link

意思就是定义成函数,函数会有各自的域不会影响其他的 ,而定义对象他们会指向一个地址会互相污染干扰

@fengshangjun
Copy link

因为设置成函数的 然后return出去会形成闭包。

@zy445566
Copy link

zy445566 commented Mar 8, 2024

感觉文章还有一点误导,其实文章想表达的是“怕同一个data对象,应用到了两个不同的组件中,任意一个组件被修改,另一个组件也会受影响”吧

@u1in
Copy link

u1in commented Mar 22, 2024

为什么data要挂在prototype上呢?不能每个类实例维护自己的data吗?

@ryan0562
Copy link

ryan0562 commented Mar 22, 2024

为什么data要挂在prototype上呢?不能每个类实例维护自己的data吗?

本来就是实例维护自己的data啊...
只是内存指向了同一个地址

@u1in
Copy link

u1in commented Mar 22, 2024

为什么data要挂在prototype上呢?不能每个类实例维护自己的data吗?

本来就是实例维护自己的data啊...

function Component(){
 
}
Component.prototype.data = {
	count : 0
}

我是对上述这段代码提出疑问,为什么不是

function Component(){
  this.data = {
      count: 0
  }
}

出于什么考量吗

@ryan0562
Copy link

为什么data要挂在prototype上呢?不能每个类实例维护自己的data吗?

本来就是实例维护自己的data啊...

function Component(){
 
}
Component.prototype.data = {
	count : 0
}

我是对上述这段代码提出疑问,为什么不是

function Component(){
  this.data = {
      count: 0
  }
}

出于什么考量吗

确实,vue在构建实例的时候将data的归属放在实例里,而不是构造函数内部,是不是就不存在这个问题了
就我个人理解,创建vue组件时,先会创建$data,然后再将data赋值给$data,所以才会存在这样的缺陷

@mslynn
Copy link

mslynn commented Jun 16, 2024

看源码就知道啦 做成函数是为了形成闭包啦 这样就可以避免产生数据污染

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests