vue2.x源码解析(第三部分)

这一章节是接着上一章节的,主要是讲解合并结果的.

默认的工具函数defaultStrat

const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

可以看到其很简单就是当childVal参数!== undefined时返回childVal否则返回parentVal,这个是没有对应的属性名处理函数默认的处理函数。

处理各项的工具函数集合strats

const strats = config.optionMergeStrategies

可以看到strats变量获取的是配置对象中的optionMergeStrategies属性,这个属性值就是一个空对象,下面来看看都为其添加了那些属性。

el属性和propsData属性处理函数

if (process.env.NODE_ENV !== 'production') {
  /*提示你 el 选项或者 propsData 选项只能在使用 new 操作符创建实例的时候可用*/
  strats.el = strats.propsData = function (parent, child, vm, key) {
    if (!vm) {
      warn(
        `option "${key}" can only be used during instance ` +
        'creation with the `new` keyword.'
      )
    }
    return defaultStrat(parent, child)
  }
}

上面这段代码必须在非生产环境下,才会执行.该函数当没有Vue实例传入是会提示警告信息' el 选项或者 propsData 选项只能在使用 new 操作符创建实例的时候可用'.最后执行的还是 defaultStrat函数

data属性处理函数

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
){
  //判断是否有vue实例
  if (!vm) {
    //没有实例并且childVal不是函数提示警告(这就是vue单文件中data要是函数的原因)
    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)
}
1、当没有传入实例时。
    `childVal`参数存在时并且`childVal`参数不属于`function`类型时提示错误信息'“data”选项应该是一个在组件定义中返回每个实例值的函数。',
    然后返回`parentVal`参数
    
    当`childVal`不参数或者`childVal`参数属于`function`类型时,执行mergeDataOrFn函数,2个参数就是处理data选项函数透传进来的。
    
    总结:为什么一定要是函数,是为了保证每个组件实例间都有唯一一个数据副本,避免组件间数据互相影响。
2、当有实例传入时
    也是执行mergeDataOrFn函数,只不过是把实例也透传进去了

这个mergeDataOrFn函数很多地方都用到了,等看完所有的strats中的处理函数在分析这个函数。

watch属性处理函数

export const nativeWatch = ({}).watch
strats.watch = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
){
  // work around Firefox's Object.prototype.watch...
  //Firefox浏览器的对象原型自带watch属性
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  /* istanbul ignore if */
  //不存在childVal,直接返回原型为parentVal || null的空对象
  if (!childVal) return Object.create(parentVal || null)
  //非生产环境对childVal不为对象时警告提示
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  //不存在parentVal直接返回childVal
  if (!parentVal) return childVal
  const ret = {}
  //合并parentVal到一个新的空对象中
  extend(ret, parentVal)
  //循环childVal
  for (const key in childVal) {
    //缓存与新对象中key名相同的属性的值
    let parent = ret[key]
    //缓存childVal[key]的值
    const child = childVal[key]
    //parent && parent不为数组则将parent转化为数组
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    //存在parent,则将childVal[key]值拼接进parent数组中
    //不存在parent,则判断childVal[key]的值是否为数组
          //是: 返回缓存childVal[key]值的child
          //不是: 将childVal[key]的值转换成数组返回
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  //返回这个新对象
  return ret
}

function assertObjectType (name: string, value: any, vm: ?Component) {
  //不是对象报警告
  if (!isPlainObject(value)) {
    warn(
      `Invalid value for option "${name}": expected an Object, ` +
      `but got ${toRawType(value)}.`,
      vm
    )
  }
}

上面这代码我们依次执行下去: 因为Firefox浏览器的对象原型自带watch属性,所以有可能parentVal参数或childVal参数是空对象,那么就得把这个值变 为undefined,以便不影响后面的代码。接着往后看,当没传childVal参数时,如果存在parentVal参数直接返回以parentVal对象为原型的对象否则就是原型 为空的空对象;传了childVal参数时: 在非生产环境下判断watch选项值是否为纯对象,不为纯对象时提示警告信息 'watch选项给的不是纯对象'。不存在parentVal参数时,直接返回childVal参数。初始化一个空对象ret变量,将parentVal参数中的属性赋值到 ret变量中,循环childVal参数的属性名,将ret变量中的该属性值和childVal参数中的该属性值合并到数组中。

总结: 将watch选项中的相同的属性名的值合并在一个数组中,所以如果你监听了某个数据的变化,刚好其值数组中有多个函数,那么全都会执行一遍。那也就是说watch选项 的属性值可以写成数组形式、对象形式、函数

parentVal = {
    count () {
        console.log(5);
    }
}
childVal = {
    count () {
        console.log(6);
    },
    num () {
        console.log(7)
    }
}
// 处理过后
ret = {
    count: [
        () => console.log(5),
        () => console.log(6)
    ],
    num: [
       () => console.log(7) 
    ]
}

题外话

一句一句解析下来太烦了,而且没有说明意义,就直接将这个函数干了什么吧,并举个例子吧!

props、methods、inject、computed属性处理函数

这四个属性引用的都是用一个处理函数.

strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
){
  //参数childVal存在并且不是生产环境,检测childVal是否为对象
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  //不存在parentVal参数,直接返回childVal
  if (!parentVal) return childVal
  const ret = Object.create(null)
  //将parentVal对象合并到一个原型为空的空对象中
  extend(ret, parentVal)
  //存在childVal参数,那就将childVal对象合并进ret对象
  if (childVal) extend(ret, childVal)
  return ret
}

该函数的作用: 同样在非生产环境下,当childVal存在 && 不为对象时提示警告信息 'props或methods或inject或computed选项给的不是纯对象'。没有parentVal返回childVal。将parentVal中的属性值复制到 原型为空的ret对象变量中,当存在childVal时,将childVal中的各个属性赋值到ret对象变量,如果该属性名已存在则直接替换。

parentVal = {
    count: {
        type: Number,
        default: 3
    }
}
childVal = {
    count: {},
    num: {
        type: Number,
        default: 4
    }
}
/// 处理后
ret = {
    count: {},
    num: {
        type: Number,
        default: 4
    }
}

生命周期钩子选项处理函数

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]
LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
){
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

以上是生命周期钩子函数选项处理函数,可以看到最后都执行了mergeHook函数,该函数的作用起始就是将所有的钩子函数合并在一个数组中

  parentVal = [
       () => console.log('我是mounted')
   ] 
   childVal = () => console.log('我是mounted1')
   // 处理后
   ret = [
       () => console.log('我是mounted'),
       () => console.log('我是mounted1')
   ]

component、directive、filter

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})
function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
){
  //__proto__是每个对象都有的一个属性,而prototype是函数才会有的属性
  //parentVal存在则创建原型为parentVal的对象
  const res = Object.create(parentVal || null)
  if (childVal) {
    //在非生产环境监测childVal是否为对象,并进行相应的警告提示
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

作用同样是对相同属性的替换和propsmethodsinjectcomputed属性处理函数处理的结果差不多,这里就不举例了

provide属性处理函数

这个处理函数其实就是mergeDataOrFn,刚好在讲data时说要最后讲的。

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
) {
  //不存在vue实例
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    //不存在需要childVal直接返回parentVal
    if (!childVal) {
      return parentVal
    }
    //不存在需要parentVal直接返回childVal
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    //当三者都没有时 || childVal和parentVal都存在时,返回下面这个函数
    return function mergedDataFn () {
      //如果参数为函数则传入的实际参数是函数返回的值,否则直接传入这个参数
      //下面这个函数返回深度合并过的childVal或childVal函数的值
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  }
  //存在vue实例
  else {
    return function mergedInstanceDataFn () {
      // instance merge
      //缓存childVal或childVal为函数时返回的值
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      //缓存parentVal或parentVal为函数时返回的值
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      //instanceData是否存在,存在则合并这两个对象,不存在返回默认的defaultData
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

可以看到如果其分两种情况:

1、存在实例: 直接返回mergedInstanceDataFn这个函数

这里先假设执行了这个函数: 获取childVal(函数返回的值或对象)为instanceData变量、获取parentVal(函数返回的值或对象)为defaultData变量,
instanceData变量有值执行mergeData函数,参数为instanceData变量和defaultData变量;没有值则直接返回instanceData变量

2、不存在实例: 没有childVal参数返回parentVal;没有parentVal返回childVal参数;如果两者都存在则返回mergedDataFn函数

这里先假设执行了这个函数: mergeData函数的两个参数就是(函数childVal或childVal对象)和(函数parentVal或parentVal对象)

以上两个最后都有可能执行mergeData函数,那么就来看下这个函数都做了什么?

function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal
  const keys = Object.keys(from)
  for (let i = 0; i < keys.length; i++) {
    key = keys[i] //缓存form的key名
    toVal = to[key] //缓存to[key]的值
    fromVal = from[key] //缓存from[key]的值
    //key不存在to对象上,将key和key值设置到to对象上
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
      /*
        判断to[key]和from[key]的值是否都为对象
          1、是: 递归这个函数
          2、否: 继续下一个循环,直到退出循环
      */
      mergeData(toVal, fromVal)
    }
  }
  return to
}

form代表parentVal,to代表childVal 首先这个函数的作用是传入参数from深度合并到传入参数to(属性值为对象值采用递归方式),如果to参数的原型链上存在from参数中的属性, 那么to参数的原型链中的这个属性值会被替换成参数from的这个属性值。这个函数中set函数的作用就是这个,举个例子:

function data() {
    function cs() {
        this.num = 'num1';
    }
    cs.prototype = {
        count: 'count2'
    }
    return new cs();
}
to = data();
from = {
    count: 'count5',
    sum: 'sum6'
}
// 处理过后
to = {
   count: 'count5',
   sum: 'sum6',
   num: 'num1'
}

总结

最后options对象中处理dataprovide的值有可能是函数以外,另外的选项的值都已经明确。

 

下一章节我们来说说 拦截器

详细信息可访问https://github.com/maomao93/vue-2.x/tree/master/personal

posted @ 2020-12-21 10:38  疾风_剑豪  阅读(178)  评论(0编辑  收藏  举报