vue核心面试题:组件中的data为什么是一个函数

我心飞翔 分类:vue

一、总结

1.vue中组件是用来复用的,为了防止data复用,将其定义为函数。

2.vue组件中的data数据都应该是相互隔离,互不影响的,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。

3.当我们将组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,拥有自己的作用域,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。

4.当我们组件的date单纯的写成对象形式,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。

二、代码分析:

vue每次会通过组件创建出一个构造函数,每个实例都是通过这个构造函数new出来的

假如data是一个对象,将这个对象放到这个放到原型上去

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改为一个函数

// 这样就可以保证每个组件调用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方法

    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)

// 将两个对象合并到一个对象中
// 包括生命周期的合并,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)

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一次,不用考虑复用。

回复

我来回复
  • 暂无回复内容