如下引官网原文:当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,由于组件可能被用来建立多个实例。若是 data 仍然是一个纯粹的对象,则全部的实例将共享引用同一个数据对象!经过提供 data 函数,每次建立一个新实例后,咱们可以调用 data 函数,从而返回初始数据的一个全新副本数据对象。html
最近来面试的不少人。我都会问这个问题“vue中,为何data是一个方法返回一个对象,而不是直接赋给一个对象”,只有少数人会回答出是怕重复建立实例形成多实例共享一个数据对象。更多的人回答是不知道,或者是官方文档要求这么写就这么写了。vue
其实这个问题的考点无非就是对vue的熟悉状况,挖掘应聘者的自驱学习能力,对技术的求知欲。这样的人每每技术成长快,具有很强的独立解决问题能力。也是各个技术团队都喜欢的一种人。面试
首先在vue的源码中,有这样的处理:vuex
// vue/src/core/instance/state.js
function initData (vm: Component) {
var 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
);
}
...
}
复制代码
显然,vue是支持将一个对象做为vue构造参数中data属性的值而且,若是data是方法的话,也会先取得内部返回的对象结果。而且在vuex中又存在这样的用法:api
// vuex/src/store.js
function resetStoreVM (store, state, hot) {
...
const silent = Vue.config.silent
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
...
}
复制代码
这是怎么回事呢?既然支持,又不让咱们用,并且当咱们在一个vue文件中,直接给一个data赋予一个对象则会引发红色警告:babel
[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions. 复制代码
这个警告来自于Vue源码中的vue/src/core/util/options.jsdom
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)
}
复制代码
首先咱们须要了解在vue文件的代码被实例化成vue组件的过程须要经历下面这些步骤:ide
在4中由于使用了mergeOptions,进而触发了对data的类型验证,也就显示了之初的那个警告。函数
那么为一个对象的属性赋予一个对象真的就会形成共享对象么?让咱们看下面的代码:学习
class A {
constructor(opt) {
this.opt = opt;
}
update() {
this.opt.data.a++;
}
notify() {
console.log(this.opt);
}
}
复制代码
咱们用这个类来虚拟化Vue的构造。而后进行测试:
// test
let c = new A({ data: { a: 1 }});
let d = new A({ data: { a: 1 }});
c.update();
d.update();
c.notify(); // Object data: a: 2
复制代码
咱们经过字面量的方式来为构造参数传入一个对象属性,然而咱们惊奇的发现,其实并无发生共享引用的问题。这是什么鬼?
哦,不对,咱们一般在使用vue的时候是在vue文件中export出一个对象,而后这个对象会在vue-loader的时候被编译传入到模版编译后的render函数中。那么咱们换一个方法来作一个实验:
// test.js文件,用于虚拟vue文件导出的vue options对象
export default {
data: {
a: 1
}
}
// index.js
let a = new A(test);
let b = new A(test);
a.update();
b.update();
a.notify(); // Object data: a: 3
复制代码
什么?在这里产生了vue文档中提到的共享引用的问题。这是为何呢?
缘由在于vue的编译过程以及引入的import过程,经过babel编译,test.js会被转化为es5语法的js文件:
var Re = {
data: {
a: 1
}
};
var Oe = function () {
function e(t) {
Object(i["a"])(this, e), this.opt = t
}
return Object(o["a"])(e, [{
key: "update",
value: function () {
this.opt.data.a++
}
}, {
key: "notify",
value: function () {
console.log(this.opt)
}
}]), e
}(),
Fe = new Oe(Re),
Ne = new Oe(Re);
Fe.update(), Ne.update(), Fe.notify();
var $e = new Oe({
data: {
a: 1
}
}),
Ve = new Oe({
data: {
a: 1
}
});
$e.update(), Ve.update(), $e.notify(),
复制代码
What?原来咱们的每个vue文件通过babel编译,将导出的对象直接替换成了一个对象变量,而后将这个变量传入到对应的组件构造函数中。所以,也就产生了引用共享的问题(全部js对象皆引用)。
因为vue源码并无通读,所以若有错误请指教