今天和大家讲讲在 Vue 源码中很重要的一个方法 — mergeOptions(),该方法的作用是合并 options,主要在以下三个地方会被使用到:

  1. Vue 实例初始化时,用于构造函数 options 和当前实例 options 的合并。
  2. Vue.mixin 全局方法内部直接调用 mergeOptions() 方法实现功能。
  3. Vue.extend 全局方法内部会调用 mergeOptions() 实现父级 options 和传递配置 options 的合并。

这里所说的 options 存在于 Vue 构造函数中,保存着一些资源,例如:data、watch、methods、filters、props、生命周期函数等。

options 除了存在于构造函数中,我们在 new Vue({}) 时传递的对象、Vue.mixin({}) 传递的对象、Vue.extend({}) 传递的对象也都是 options。

mergeOptions() 方法就用于合并上面所说的这些对象。在上面的第一种情况中,合并完成的对象会被赋值到 Vue 实例的 $options 属性上,$options 属性保存着对应 Vue 实例能够使用的各种资源,该属性会在后续的实例初始化被用到,关于 Vue 实例的初始化我会在后续单独写一篇文章进行解析。至于第二种和第三种情况,则会将传递进来的 options 和构造函数的 options 进行合并,并将合并完成的对象重新赋值到构造函数的 options 属性上,这能够实现一种全局注入的效果,导致后续通过该构造函数生成的 Vue 实例能够使用这些全局注入的资源。

mergeOptions() 方法定义在 src/core/util/options.js 文件中,我们开始看源码。

1,mergeOptions() 方法总览

首先总览一下 mergeOptions() 方法的全部源码,接下来,我们一步步进行源码解析。

/*** 将两个 options 对象合并成一个*/
export function mergeOptions (parent: Object,child: Object,vm?: Component
): Object {if (process.env.NODE_ENV !== 'production') {// 对配置对象的 components 字段进行检测checkComponents(child)}if (typeof child === 'function') {child = child.options}// 标准化 child 中的 props、inject、directivesnormalizeProps(child, vm)normalizeInject(child, vm)normalizeDirectives(child)// 对应官方文档点击这里:https://cn.vuejs.org/v2/api/#extends// 配置选项中可以使用 extends 配置项,如果使用了该配置项的话,底层则递归调用 mergeOptions 方法,// 对 parent 和 extendsFrom 中的配置项进行合并const extendsFrom = child.extendsif (extendsFrom) {parent = mergeOptions(parent, extendsFrom, vm)}// 对应官方文档点击这里:https://cn.vuejs.org/v2/api/#mixins// 配置选项中可以使用 mixins 配置项,如果使用了该配置项的话,底层则递归调用 mergeOptions 方法,// 对 parent 和 child.mixins[i] 中的配置项进行合并if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm)}}// 开始进行真正的合并逻辑const options = {}let keyfor (key in parent) {mergeField(key)}for (key in child) {// 如果当前遍历的 key 在 parent 中不存在的话,再执行 mergeField(key)if (!hasOwn(parent, key)) {mergeField(key)}}// 用于合并某一个 key 的方法function mergeField (key) {// strats 是指合并策略集// strat 是指特定选项的合并策略,是一个函数const strat = strats[key] || defaultStrat// 使用这个合并策略对 parent 和 child 中指定的 key 进行合并options[key] = strat(parent[key], child[key], vm, key)}return options
}

2,检查配置项 components 中的组件名是否规范

export function mergeOptions (parent: Object,child: Object,vm?: Component
): Object {if (process.env.NODE_ENV !== 'production') {// 对配置对象的 components 字段进行检测checkComponents(child)}............
}

检查组件名称是否规范使用了 checkComponents 方法,我们看下该方法的源码。

/*** 验证组件名称*/
function checkComponents (options: Object) {// 对配置对象的 components 字段进行遍历for (const key in options.components) {const lower = key.toLowerCase()// 判断组件是不是 Vue 中内置的组件(slot,component)// 判断组件是不是 HTML 预留的标签// 如果满足的话,打印出警告if (isBuiltInTag(lower) || config.isReservedTag(lower)) {warn('Do not use built-in or reserved HTML elements as component ' +'id: ' + key)}}
}

使用 for in 遍历 options.components 拿到组件的名称,然后将组件的名称都变成小写的形式,最后调用 isBuiltInTag() 方法和 isReservedTag() 方法判断组件名是否规范,如果不规范的话,则打印出警告。

isBuiltInTag() 方法用于判断组件名是不是 Vue 内置的标签,源码如下:

/*** Check if a tag is a built-in tag.*/
export const isBuiltInTag = makeMap('slot,component', true)

isReservedTag() 用于判断组件名是不是 HTML 预留的标签,源码如下:

export const isHTMLTag = makeMap('html,body,base,head,link,meta,style,title,' +'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +'s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +'embed,object,param,source,canvas,script,noscript,del,ins,' +'caption,col,colgroup,table,thead,tbody,td,th,tr,' +'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +'output,progress,select,textarea,' +'details,dialog,menu,menuitem,summary,' +'content,element,shadow,template,blockquote,iframe,tfoot'
)// this map is intentionally selective, only covering SVG elements that may
// contain child elements.
export const isSVG = makeMap('svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',true
)export const isReservedTag = (tag: string): ?boolean => {return isHTMLTag(tag) || isSVG(tag)
}

3,标准化 props、inject、directives

export function mergeOptions (parent: Object,child: Object,vm?: Component
): Object {............// 标准化 child 中的 props、inject、directivesnormalizeProps(child, vm)normalizeInject(child, vm)normalizeDirectives(child)............
}

这三个函数所做的事情很简单,就是将 options 中配置的 props、inject、directives 进行标准化,将简略的配置转换成完备的对象形式,例如:

3-1,将 props: ['age'] 转换成

props: {age: {type: Number,default: 0,required: true,validator: function (value) {return value >= 0}}
}

3-2,将 inject: ['foo'] 转换成

inject: {foo: {from: 'bar',default: () => [1, 2, 3]}
}

3-3,将 directives 的函数简写形式转换成对象形式

directives: {'color-swatch': function (el, binding) {el.style.backgroundColor = binding.value}
}

转换成

directives: {'color-swatch': {bind: function (el, binding) {el.style.backgroundColor = binding.value},update: function (el, binding) {el.style.backgroundColor = binding.value}}
}

4,extends 和 mixins 选项的处理

这部分看注释即可。

export function mergeOptions (parent: Object,child: Object,vm?: Component
): Object {............// 对应官方文档点击这里:https://cn.vuejs.org/v2/api/#extends// 配置选项中可以使用 extends 配置项,如果使用了该配置项的话,底层则递归调用 mergeOptions 方法,// 对 parent 和 extendsFrom 中的配置项进行合并const extendsFrom = child.extendsif (extendsFrom) {parent = mergeOptions(parent, extendsFrom, vm)}// 对应官方文档点击这里:https://cn.vuejs.org/v2/api/#mixins// 配置选项中可以使用 mixins 配置项,如果使用了该配置项的话,底层则递归调用 mergeOptions 方法,// 对 parent 和 child.mixins[i] 中的配置项进行合并if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm)}}............
}

5,开始真正的合并处理

export function mergeOptions (parent: Object,child: Object,vm?: Component
): Object {............// 开始进行真正的合并逻辑const options = {}let keyfor (key in parent) {mergeField(key)}for (key in child) {// 如果当前遍历的 key 在 parent 中不存在的话,再执行 mergeField(key)if (!hasOwn(parent, key)) {mergeField(key)}}// 用于合并某一个 key 的方法function mergeField (key) {// strats 是指合并策略集// strat 是指特定选项的合并策略,是一个函数const strat = strats[key] || defaultStrat// 使用这个合并策略对 parent 和 child 中指定的 key 进行合并options[key] = strat(parent[key], child[key], vm, key)}return options
}

首先创建一个 options 对象,这个对象就是最终返回的合并对象。

接下来遍历 parent options 对象,以遍历到的 key 作为参数执行 mergeField 方法,这样的话,parent options 对象中的所有 key 都已经处理到 options 对象中了。

然后遍历 child options 对象,这一遍遍历是为了处理 child options 对象中独有的 key,内部的处理也是以 key 作为参数执行 mergeField 方法。

上面两处都使用到了 mergeField 方法,我们细致研究一下。

5-1,mergeField() 方法

// 用于合并 parent options 和 child options 对象中某一个 key 的方法
function mergeField (key) {// strats 是指合并策略集// strat 是指特定选项的合并策略,是一个函数const strat = strats[key] || defaultStrat// 使用这个合并策略对 parent 和 child 中指定的 key 进行合并options[key] = strat(parent[key], child[key], vm, key)
}

该方法的作用是:用于合并 parent options 和 child options 对象中某一个 key。

在 Vue 中,不同的配置项有不同的合并策略,这些合并策略都存储在 strats 对象中,strats 对象的数据结构如下所示:

strats = {data: function(){},watch: function(){},props: function(){},methods: function(){},created: function(){},mounted: function(){},......
}

该对象中的 key 对应 options 中的配置项,value 是一个个函数,这些函数用于具体配置项的合并。

在 mergeField 方法中,首先根据 key 从 strats 对象中获取指定的合并策略函数,然后调用合并策略函数合并 parent[key] 和 child[key],并将合并的结果赋值到 options[key]。

为了更好的理解,我们看下生命周期配置选项的合并策略函数。

5-2,生命周期配置选项的合并策略函数

生命周期配置选项的合并策略函数是 mergeHook() 方法,虽然一眼看上去很复杂,使用了嵌套的三元表达式,但是如果仔细看的话,其实很简单,详细的源码解释都在注释中,看注释即可理解。

/*** Hooks and props are merged as arrays.*/
function mergeHook (parentVal: ?Array<Function>,childVal: ?Function | ?Array<Function>
): ?Array<Function> {// 嵌套的三元运算符// 如果 childVal 没有定义的话,直接返回 parentValreturn childVal// childVal 是定义了的,接下来看 parentVal 是否定义? parentVal// parentVal 定义了的话,使用 concat 函数连接两者,concat 函数的参数既能是数组,也能是单个的元素? parentVal.concat(childVal)// childVal 定义了,而 parentVal 没有定义,此时只要返回 childVal 即可// 不过要看 childVal 是否是数组类型。如果是的话,直接返回,不是的话,包装成数组再返回: Array.isArray(childVal)? childVal: [childVal]: parentVal// 这种返回结果由两种变量决定,每个变量各有两种变化,共有四种组合的情形可以使用三元运算符简洁的实现// 很值得我们借鉴和学习
}// 生命周期函数数组
// const LIFECYCLE_HOOKS = [
//   'beforeCreate',
//   'created',
//   'beforeMount',
//   'mounted',
//   'beforeUpdate',
//   'updated',
//   'beforeDestroy',
//   'destroyed',
//   'activated',
//   'deactivated',
//   'errorCaptured'
// ]
// 生命周期函数的合并策略
LIFECYCLE_HOOKS.forEach(hook => {strats[hook] = mergeHook
})

6,结语

mergeOptions() 方法就讲到这,下一篇文章解析 Vue.extend() 的内部实现原理。

Vue源码阅读(28):mergeOptions() 方法源码解析相关推荐

  1. 深入java并发包源码(三)AQS独占方法源码分析

    深入java并发包源码(一)简介 深入java并发包源码(二)AQS的介绍与使用 深入java并发包源码(三)AQS独占方法源码分析 AQS 的实现原理 学完用 AQS 自定义一个锁以后,我们可以来看 ...

  2. java经典源码 阅读_公开!阿里甩出“源码阅读指南”,原来源码才是最经典的学习范例...

    我们为啥要阅读源码? 为什么面试要问源码?为什么我们Java程序员要去看源码?相信大多数程序员看到源码第一感觉都是:枯燥无味,费力不讨好!要不是为了"涨薪"我才不去看这个鬼东西!但 ...

  3. android源码阅读笔记1-配置源码路径/阅读源码方法讨论

    开始之前 android studio中配置android源码路径 android studio中有源码的路径,你只需要打开SDK Manager下载源码然后重启android studio即可查看源 ...

  4. Nacos源码阅读开篇之下载源码

    文章目录 Nacos源码阅读开篇 看源码的方法 nacos服务注册与发现源码剖析 nacos核心功能点 nacos服务端原理 nacos 客户端原理 下载Nacos源码 配置单机启动 Nacos源码阅 ...

  5. tomcat源码阅读之Server和Service接口解析

    tomcat中的服务器组件接口是Server接口,服务接口是Service,Server接口表示Catalina的整个servlet引擎,囊括了所有的组件,提供了一种优雅的方式来启动/关闭Catali ...

  6. MP4学习(四)ts-mp4源码阅读(2)MP4的解析流程

    MP4的解析流程 常见的MP4结构图 MP4 box的定义 MP4也是一个容器 /* ** MP4容器定义,包含: ** ftyp.moov.mdat */ static mp4_atom_handl ...

  7. 免费下载中国地图china.js,在VUE使用echarts-map全国地图的方法和源码

    记得有次需要用到中国地图,在官网找了没有,找了很多地方提供的下载都是需要注册或消费点什么,在这里免费分享中国地图china.js插件,使用前记得安装或者引入echarts噢... china.js下载 ...

  8. 大神手把手教源码阅读的方法、误区以及三种境界

    丁威 中间件兴趣圈 读完需要 1 分钟 速读仅需 1 分钟 在技术职场中普遍存在如下几种现象: 对待工作中所使用的技术不需要阅读源码,只需在开发过程中能够熟练运用就行 看源码太费时间,而且容易忘记,如 ...

  9. 源码阅读:AFNetworking(十六)——UIWebView+AFNetworking

    该文章阅读的AFNetworking的版本为3.2.0. 这个分类提供了对请求周期进行控制的方法,包括进度监控.成功和失败的回调. 1.接口文件 1.1.属性 /**网络会话管理者对象*/ @prop ...

  10. 源码阅读:SDWebImage(六)——SDWebImageCoderHelper

    该文章阅读的SDWebImage的版本为4.3.3. 这个类提供了四个方法,这四个方法可分为两类,一类是动图处理,一类是图像方向处理. 1.私有函数 先来看一下这个类里的两个函数 /**这个函数是计算 ...

最新文章

  1. python中几种推导式的特殊用法
  2. java 表单请求_java模拟表单请求
  3. 计算机组成原理(一)计算机系统概述
  4. python浅蓝色对应的代码_浅蓝色Python模块不在m上工作
  5. Flutter下拉刷新,上拉加载更多数据
  6. flex 修改生成html,CSS Flex –动画教程
  7. 引导页闪屏界面设计灵感
  8. 如何装系统,如何装kali linux系统
  9. 怎么在gitLab代码拉到本地
  10. Codeforces Round #449 (Div. 2) B Chtholly's request (预处理)
  11. 方正台式计算机保护卡密码忘记了,方正电脑E系列忘记还原卡密码处理方法
  12. 模板字符串+JS模板引擎+vue,三者之间的字符串比较
  13. Salesforce基础知识学习Day05
  14. iOS图片加载策略的简单实现
  15. 无显示器主机配置服务器
  16. 华为实验跨交换机不同vlan通信
  17. 【面试】前端面试之开发性能篇
  18. 京东“竖亥小车”秒测商品尺寸重量
  19. Python标准库time
  20. Pnp即插即用设备驱动 自动安装程序 C C++实现

热门文章

  1. 初识c语言思维导图及大纲 (内含思维导图图片及pdf版下载链接)
  2. 计算机桌面应用程序图标不见了怎么办,桌面程序图标不见了,桌面软件快捷方式不见了怎么办?...
  3. 分隔符中的分页符与分节符
  4. linux roundup函数记录
  5. hardfault常见原因_stm32 HardFault_Handler调试及问题查找方法
  6. TRICKLE轻量级的用户空间带宽控制管理工具
  7. 明光杂感之四:足球与情境觉知(上)
  8. java坦克大战 需求分析,Java版坦克大战游戏的设计与实现(含录像)_JAVA
  9. 163邮箱的登陆页面是什么样的有几种登陆方式?163邮箱手机版登陆
  10. vs2019,C#,MySQL创建图书管理系统7(用户借/还书)