博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入剖析Vue源码 - 选项合并(下)
阅读量:6857 次
发布时间:2019-06-26

本文共 7581 字,大约阅读时间需要 25 分钟。

上一节的末尾,我们介绍了Vue中处理合并选项的思路,概括起来主要有两点,一是当选项存在定义好的默认配置策略时,优先选择默认配置策略,并且根据不同的配置项来合并子父选项; 二是当传入选项不存在默认策略时,处理的原则是有子类配置选项则默认使用子类配置选项,没有则选择父类配置选项。vue中,大部分选项都有其自定义策略,因此本节分析的重点也放在了各种自定义配置策略中(内置资源选项,生命周期钩子选项,el, data, watch, props等)。

首先还是回顾一下选项合并的代码,strat这个对象包含了所以自定义的默认策略。

function mergeOptions ( parent, child, vm ) {  ···  var options = {};  var key;  for (key in parent) {    mergeField(key);  }  for (key in child) {    if (!hasOwn(parent, key)) {      mergeField(key);    }  }  function mergeField (key) {    var strat = strats[key] || defaultStrat; // 如果有自定义选项策略,则使用自定义选项策略,否则选择子类配置选项    options[key] = strat(parent[key], child[key], vm, key);  }  return options}复制代码

1.5 资源选项的自定义策略

在上一节中,我们知道Vue构造函数自身有options的配置选项,分别是components组件, directive指令, filter过滤器,在创建实例之前,程序会将内置组件和内置指令分别挂载到components和directive属性上。

var ASSET_TYPES = [  'component',  'directive',  'filter'];ASSET_TYPES.forEach(function (type) {  Vue.options[type + 's'] = Object.create(null); // Vue构造器拥有的默认资源选项配置});// Vue内置组件var builtInComponents = {  KeepAlive: KeepAlive};var platformComponents = {  Transition: Transition,  TransitionGroup: TransitionGroup};// Vue 内置指令,例如: v-model, v-showvar platformDirectives = {  model: directive,  show: show};// 将_from对象合并到to对象,属性相同时,则覆盖to对象的属性function extend (to, _from) {  for (var key in _from) {    to[key] = _from[key];  }  return to}extend(Vue.options.components, builtInComponents); extend(Vue.options.components, platformComponents); // 扩展内置组件extend(Vue.options.directives, platformDirectives);  // 扩展内置指令复制代码

构造函数的默认资源选项配置如下:

Vue.options = {  components: {    KeepAlive: {}    Transition: {}    TransitionGroup: {}  },  directives: {    model: {inserted: ƒ, componentUpdated: ƒ}    show: {
bind: ƒ, update: ƒ, unbind: ƒ} }, filters: {} _base}复制代码

在实例化Vue,或者实例化子类时,这一类资源选项是如何合并的呢?

// 资源选项自定义合并策略function mergeAssets (parentVal,childVal,vm,key) {  var res = Object.create(parentVal || null); // 创建一个空对象,其原型指向父类的资源选项。  if (childVal) {    assertObjectType(key, childVal, vm); // components,filters,directives选项必须为对象    return extend(res, childVal) // 子类选项赋值给空对象  } else {    return res  }}ASSET_TYPES.forEach(function (type) {  strats[type + 's'] = mergeAssets; // 定义默认策略});复制代码

简单总结一下,对于 directives、filters 以及 components 等资源选项,父类选项将以原型链的形式被处理。子类必须通过原型链才能查找并使用内置组件和内置指令。

1.6 生命周期钩子选项自定义策略

我们知道掌握vue的生命周期钩子是使用vue高效开发组件的重点,这是vue官方的生命周期图

从源码中我们也可以看到vue中有多达12个钩子,而在选项合并的时候,生命周期钩子选项是遵循的以下的规则合并的。

var LIFECYCLE_HOOKS = [  'beforeCreate',  'created',  'beforeMount',  'mounted',  'beforeUpdate',  'updated',  'beforeDestroy',  'destroyed',  'activated',  'deactivated',  'errorCaptured',  'serverPrefetch'];LIFECYCLE_HOOKS.forEach(function (hook) {  strats[hook] = mergeHook; // 对生命周期钩子选项的合并都执行mergeHook策略});function mergeHook (parentVal,childVal) {  var res = childVal    ? parentVal      ? parentVal.concat(childVal)      : Array.isArray(childVal)        ? childVal        : [childVal]    : parentVal; // 1.如果子类和父类都拥有钩子选项,则将子类选项和父类选项合并, 2如果父类不存在钩子选项,子类存在时,则以数组形式返回子类钩子选项, 3.当子类不存在钩子选项时,则以父类选项返回。  return res    ? dedupeHooks(res)    : res}// 防止多个组件实例钩子选项相互影响function dedupeHooks (hooks) {  var res = [];  for (var i = 0; i < hooks.length; i++) {    if (res.indexOf(hooks[i]) === -1) {      res.push(hooks[i]);    }  }  return res}复制代码

简单总结,对于生命周期钩子选项,子类和父类的选项将合并成数组,这样每次执行子类的钩子函数时,父类钩子选项也会执行。

1.7 其他自定义策略

Vue自定义选项策略还有很多,我们继续列举其他几个例子。

1.7.1 el合并

我们只在创建vue的实例时才会执行节点挂载,在子类或者子组件中无法定义el选项,代码实现如下

strats.el = function (parent, child, vm, key) {  if (!vm) {  // 只允许vue实例才拥有el属性,其他子类构造器不允许有el属性    warn(      "option \"" + key + "\" can only be used during instance " +      'creation with the `new` keyword.'    );  }  return defaultStrat(parent, child)};// 用户自定义选项策略var defaultStrat = function (parentVal, childVal) {  return childVal === undefined    ? parentVal    : childVal};复制代码
1.7.2 data合并

另一个合并的重点是data的合并策略,data在vue创建实例时传递的是一个对象,而在组件内部定义时只能传递一个函数,

strats.data = function (parentVal, childVal, vm) {  if (!vm) {    if (!vm) {// 判断是否为Vue创建的实例,否则为子父类的关系      if (childVal && typeof childVal !== 'function') { // 必须保证子类的data类型是一个函数而不是一个对象        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实例时需要传递vm作为函数的第三个参数};复制代码

做了data选项的检验后,重点关注mergeDataOrFn函数的内部逻辑,代码中依然通过vm来区分是否为子类构造器的data合并。

function mergeDataOrFn ( parentVal, childVal, vm ) {  if (!vm) {    if (!childVal) { // 子类不存在data选项,则合并结果为父类data选项      return parentVal    }    if (!parentVal) { // 父类不存在data选项,则合并结果为子类data选项      return childVal    }    return function mergedDataFn () { // data选项在父类和子类同时存在的情况下返回的是一个函数      // 子类实例和父类实例,分别将子类和父类实例中data函数执行后返回的对象传递给mergeData函数做数据合并      return mergeData(        typeof childVal === 'function' ? childVal.call(this, this) : childVal,        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal      )    }  } else {    // vue构造函数实例对象    return function mergedInstanceDataFn () {      var instanceData = typeof childVal === 'function'        ? childVal.call(vm, vm)        : childVal;      var defaultData = typeof parentVal === 'function'        ? parentVal.call(vm, vm)        : parentVal;      if (instanceData) {        // 当实例中传递data选项时,将实例的data对象和Vm构造函数上的data属性选项合并        return mergeData(instanceData, defaultData)      } else {        // 当实例中不传递data时,默认返回Vm构造函数上的data属性选项        return defaultData      }    }  }}复制代码

如何实现数据合并,数据合并时,vue会将数据变化加入响应式系统中,我们先跳过响应式系统的构建部分,只关注单纯的数据合并。数据合并的原则是,将父类的数据整合到子类的数据选项中, 如若父类数据和子类数据冲突时,保留子类数据。

function mergeData (to, from) {  if (!from) { return to }  var key, toVal, fromVal;  var keys = hasSymbol    ? Reflect.ownKeys(from)    : Object.keys(from);  for (var i = 0; i < keys.length; i++) {    key = keys[i];    toVal = to[key];    fromVal = from[key];    if (!hasOwn(to, key)) {      set(to, key, fromVal); // 当子类数据选项不存在父类的选项时,将父类数据合并到子类数据中,并加入响应式系统中。    } else if ( //  处理深层对象,当合并的数据为多层嵌套对象时,需要递归调用mergeData进行比较合并      toVal !== fromVal &&      isPlainObject(toVal) &&      isPlainObject(fromVal)    ) {      mergeData(toVal, fromVal);    }  }  return to}复制代码

思考一个问题,为什么Vue组件的data是一个函数,而不是一个对象呢? 我觉得这样可以方便理解:组件的目的是为了复用,每次通过函数创建相当于在一个独立的内存空间中生成一个data的副本,这样每个组件之间的数据不会互相影响。

1.7.3 watch 选项合并

对于 watch 选项的合并处理,类似于生命周期钩子,只要父选项有相同的观测字段,则合并为数组,在选项改变时同时执行父类选项的监听代码。处理方式和生命钩子选项的区别在于,生命钩子选项必须是函数或者数据,而watch选项则为对象。

strats.watch = function (parentVal,childVal,vm,key) {    if (parentVal === nativeWatch) { parentVal = undefined; }    if (childVal === nativeWatch) { childVal = undefined; }    if (!childVal) { return Object.create(parentVal || null) }    {      assertObjectType(key, childVal, vm);    }    if (!parentVal) { return childVal }    var ret = {};    extend(ret, parentVal);    for (var key$1 in childVal) {      var parent = ret[key$1];      var child = childVal[key$1];      if (parent && !Array.isArray(parent)) {        parent = [parent];      }      ret[key$1] = parent        ? parent.concat(child)        : Array.isArray(child) ? child : [child];    }    return ret  };复制代码
1.7.4 props,methods, inject, computed 合并
// 其他选项合并策略strats.props =strats.methods =strats.inject =strats.computed = function (parentVal,childVal,vm,key) {  if (childVal && "development" !== 'production') {    assertObjectType(key, childVal, vm);  }  if (!parentVal) { return childVal } // 父类不存在该选项,则返回子类的选项  var ret = Object.create(null);  extend(ret, parentVal); //   if (childVal) { extend(ret, childVal); } // 子类选项会覆盖父类选项的值  return ret};复制代码

至此,vue初始化选项合并逻辑分析完毕。

转载于:https://juejin.im/post/5c91e960f265da60f30d44ca

你可能感兴趣的文章
【OpenCV学习】极坐标变换
查看>>
使用open***构建安全***
查看>>
模仿支付宝支付密码框思路
查看>>
docker 容器的网络配置
查看>>
开源监控软件的配置:cacti,nagios
查看>>
Python多线程之threading模块
查看>>
第 6 章 单区域 OSPF
查看>>
Android图片开发内幕--基础篇
查看>>
LVS DR模式
查看>>
ZooKeeper 的安装 以及配置说明
查看>>
iOS10推送通知(本地&远程)/Swift
查看>>
标准交互及UED、埋点及锚应用、外部调用原则。请教一下哪位大佬知道这三个的应用?求助...
查看>>
浅入深谈:一道Python面试题,让我明白了殊途同归,却开始怀疑自己
查看>>
内存时序对应的4个参数你们知道吗?ICMAX一文让你看懂
查看>>
我的U盘故事2
查看>>
《窃听风暴》影评
查看>>
linux下磁盘分区(初级篇)
查看>>
mysql大小写规则校验
查看>>
mysql互主自动化配置脚本
查看>>
nginx的upstream模块
查看>>