一、总结
1.vue中组件是用来复用的,为了防止data复用,将其定义为函数。
2.vue组件中的data数据都应该是相互隔离,互不影响的,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。
3.当我们将组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,拥有自己的作用域,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。
4.当我们组件的date单纯的写成对象形式,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。
二、代码分析:
vue每次会通过组件创建出一个构造函数,每个实例都是通过这个构造函数new出来的
假如data是一个对象,将这个对象放到这个放到原型上去
代码语言:javascript复制function VueComponent(){}
VueComponent.prototype.$options = {
data:{name:'three'}
}
let vc1 = new VueComponent();
vc1.$options.data.name = 'six'; // 将vc1实例上的data修改为six
let vc2 = new VueComponent(); // 在new一个新的实例vc2
console.log(vc2.$options.data.name); six
// 输出vc2的data的值是six,这时候发现vc2中的data也被修改了,他们data相互影响
将data改为一个函数
代码语言:javascript复制// 这样就可以保证每个组件调用data返回一个全新的对象,和外部没有关系
function VueComponent(){}
VueComponent.prototype.$options = {
data: () => ({name:'three'})
}
let vc1 = new VueComponent();
let objData = vc1.$options.data()
objData.name = 'six'; // 调用data方法会返回一个对象,用这个对象作为它的属性
console.log(objData)
let vc2 = new VueComponent();
console.log(vc2.$options.data());
三、源码
1.在core/global-api/extend.js中会调用mergeOptions方法
代码语言:javascript复制 const Sub = function VueComponent (options) {
this._init(options)
} // 创建一个子类
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid
Sub.options = mergeOptions( // 调用mergeOptions进行合并
Super.options,
extendOptions
)
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
2.mergeOptions方法(src/core/util/options.js)
代码语言:javascript复制// 将两个对象合并到一个对象中
// 包括生命周期的合并,data的合并等
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
if (!child._base) {
// 对extend和mixins做合并
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i ) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
// 循环父类
for (key in parent) {
mergeField(key)
}
// 循环子类
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// 合并字段
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
3.合并data(src/core/util/options.js)
代码语言:javascript复制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
}
// 如果是函数就调用mergeDataOrFn进行合并
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
4.源码总结:
通过vue.extend方法创建一个子类,创建子类之后会把自己的选项和父类的选项使用mergeOptions方法做一个合并,自己的选项就包含data。在mergeOptions中会调用strats.data对子类的data进行合并,这个方法中首先会判断子类的data进行判断,要求data必须是一个函数,如果不是会报错告诉它这个data应该是一个函数定义,因为每一个组件都会返回一个实例,如果是data就会调用 mergeDataOrFn方法进行合并。然后会合并父类的extend、minin、use方法,最后extend返回的就是这个子类的方法。
补充:
为什么要合并?因为子组件也要有父组件的属性,extend方法是通过一个对象创建了一个构造函数,但是这个构造函数并没有父类的属性,因为它是一个新函数,和之前的Vue构造函数是没有关系的。通过extend产生了一个子函数,这个子函数需要拥有vue实例上的所以东西,它就要做一次合并。
四、为什么new Vue这个里面的data可以放一个对象?
因为这个类创建的实例不会被复用。它只会new一次,不用考虑复用。