【Vue2.0源码学习】生命周期篇-初始化阶段(initInjections)

文章目录

    • 1. 前言
    • 2. initInjections函数分析
      • resolveInject函数分析
    • 3. 总结

1. 前言

本篇文章介绍生命周期初始化阶段所调用的第四个初始化函数——initInjections。从函数名字上来看,该函数是用来初始化实例中的inject选项的。说到inject选项,那必然离不开provide选项,这两个选项都是成对出现的,它们的作用是:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。并且

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 SymbolReflect.ownKeys 的环境下可工作。

inject 选项应该是:

  • 一个字符串数组,或
  • 一个对象,对象的 key 是本地的绑定名,value 是:
    • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
    • 一个对象,该对象的:
      • from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
      • default 属性是降级情况下使用的 value

这两个选项在我们日常开发中使用的频率不是很高,但是在一些组件库中使用的很频繁,官方文档给出了使用示例,如下:

// 父级组件提供 'foo'
var Parent = {provide: {foo: 'bar'},// ...
}// 子组件注入 'foo'
var Child = {inject: ['foo'],created () {console.log(this.foo) // => "bar"}// ...
}

利用 ES2015 Symbols、函数 provide 和对象 inject

const s = Symbol()const Provider = {provide () {return {[s]: 'foo'}}
}const Child = {inject: { s },// ...
}

接下来 2 个例子只工作在 Vue 2.2.1 或更高版本。低于这个版本时,注入的值会在 propsdata 初始化之后得到。

使用一个注入的值作为一个属性的默认值:

const Child = {inject: ['foo'],props: {bar: {default () {return this.foo}}}
}

使用一个注入的值作为数据入口:

const Child = {inject: ['foo'],data () {return {bar: this.foo}}
}

在 2.5.0+ 的注入可以通过设置默认值使其变成可选项:

const Child = {inject: {foo: { default: 'foo' }}
}

如果它需要从一个不同名字的属性注入,则使用 from 来表示其源属性:

const Child = {inject: {foo: {from: 'bar',default: 'foo'}}
}

与 prop 的默认值类似,你需要对非原始值使用一个工厂方法:

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

总结起来一句话就是:父组件可以使用provide选项给自己的下游子孙组件内注入一些数据,在下游子孙组件中可以使用inject选项来接收这些数据以便为自己所用。

另外,这里有一点需要注意:provideinject 选项绑定的数据不是响应式的。

了解了他们的作用及使用方法后,我们就来看下initInjections函数是如何来初始化inject选项的。

2. initInjections函数分析

分析之前,我们先说一个问题,细心的同学可能会发现,既然inject选项和provide选项都是成对出现的,那为什么在初始化的时候不一起初始化呢?为什么在init函数中调用initInjections函数和initProvide函数之间穿插一个initState函数呢?

其实不然,在官方文档示例中说了,provide选项注入的值作为数据入口,如下:

const Child = {inject: ['foo'],data () {return {bar: this.foo}}
}

这里所说的数据就是我们通常所写datapropswatchcomputedmethod,所以inject选项接收到注入的值有可能被以上这些数据所使用到,所以在初始化完inject后需要先初始化这些数据,然后才能再初始化provide,所以在调用initInjections函数对inject初始化完之后需要先调用initState函数对数据进行初始化,最后再调用initProvide函数对provide进行初始化。

OK,接下来我们就来分析initInjections函数的具体原理,该函数定义位于源码的src/core/instance/inject.js中,如下:

export function initInjections (vm: Component) {const result = resolveInject(vm.$options.inject, vm)if (result) {toggleObserving(false)Object.keys(result).forEach(key => {defineReactive(vm, key, result[key])}toggleObserving(true)}
}export let shouldObserve: boolean = true
export function toggleObserving (value: boolean) {shouldObserve = value
}

可以看到,initInjections函数的逻辑并不复杂,首先调用resolveInjectinject选项中的数据转化成键值对的形式赋给result,如官方文档给出的例子,那么result应为如下样子:

// 父级组件提供 'foo'
var Parent = {provide: {foo: 'bar'}
}// 子组件注入 'foo'
var Child = {inject: ['foo'],
}// result
result = {'foo':'bar'
}

然后遍历result中的每一对键值,调用defineReactive函数将其添加当前实例上,如下:

if (result) {toggleObserving(false)Object.keys(result).forEach(key => {defineReactive(vm, key, result[key])}toggleObserving(true)
}

此处有一个地方需要注意,在把result中的键值添加到当前实例上之前,会先调用toggleObserving(false),而这个函数内部是把shouldObserve = false,这是为了告诉defineReactive函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式,这个就呼应了官方文档在介绍provideinject 选项用法的时候所提示的:

provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

initInjections函数的逻辑就介绍完了,接下来我们看看resolveInject函数内部是如何把inject 选项中数据转换成键值对的。

resolveInject函数分析

我们知道,inject 选项中的每一个数据key都是由其上游父级组件提供的,所以我们应该把每一个数据key从当前组件起,不断的向上游父级组件中查找该数据key对应的值,直到找到为止。如果在上游所有父级组件中没找到,那么就看在inject 选项是否为该数据key设置了默认值,如果设置了就使用默认值,如果没有设置,则抛出异常。

OK,以上是我们的分析,下面我们就来看下resolveInject函数的源码,验证我们的分析,源码如下:

export function resolveInject (inject: any, vm: Component): ?Object {if (inject) {const result = Object.create(null)const keys =  Object.keys(inject)for (let i = 0; i < keys.length; i++) {const key = keys[i]const provideKey = inject[key].fromlet source = vmwhile (source) {if (source._provided && hasOwn(source._provided, provideKey)) {result[key] = source._provided[provideKey]break}source = source.$parent}if (!source) {if ('default' in inject[key]) {const provideDefault = inject[key].defaultresult[key] = typeof provideDefault === 'function'? provideDefault.call(vm): provideDefault} else if (process.env.NODE_ENV !== 'production') {warn(`Injection "${key}" not found`, vm)}}}return result}
}

在分析函数源码之前,我们对照着官网给出的示例,这样会比较好理解一些。

var Parent = {provide: {foo: 'bar'},// ...
}
const Child = {inject: {foo: {from: 'bar',default: () => [1, 2, 3]}}
}

可以看到,在函数源码中,首先创建一个空对象result,用来存储inject 选项中的数据key及其对应的值,作为最后的返回结果。

然后获取当前inject 选项中的所有key,然后遍历每一个key,拿到每一个keyfrom属性记作provideKeyprovideKey就是上游父级组件提供的源属性,然后开启一个while循环,从当前组件起,不断的向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中,如下:

for (let i = 0; i < keys.length; i++) {const key = keys[i]const provideKey = inject[key].fromlet source = vmwhile (source) {if (source._provided && hasOwn(source._provided, provideKey)) {result[key] = source._provided[provideKey]break}source = source.$parent}
}

如果没有找到,那么就看inject 选项中当前的数据key是否设置了默认值,即是否有default属性,如果有的话,则拿到这个默认值,官方文档示例中说了,默认值可以为一个工厂函数,所以当默认值是函数的时候,就去该函数的返回值,否则就取默认值本身。如果没有设置默认值,则抛出异常。如下:

if (!source) {if ('default' in inject[key]) {const provideDefault = inject[key].defaultresult[key] = typeof provideDefault === 'function'? provideDefault.call(vm): provideDefault} else if (process.env.NODE_ENV !== 'production') {warn(`Injection "${key}" not found`, vm)}
}

最后将result返回。这就是resolveInject函数的所有逻辑。

此时你可能会有个疑问,官方文档中说inject 选项可以是一个字符串数组,也可以是一个对象,在上面的代码中只看见了处理当为对象的情况,那如果是字符串数组呢?怎么没有处理呢?

其实在初始化阶段_init函数在合并属性的时候还调用了一个将inject 选项数据规范化的函数normalizeInject,该函数的作用是将以下这三种写法:

// 写法一
var Child = {inject: ['foo']
}// 写法二
const Child = {inject: {foo: { default: 'xxx' }}
}// 写法三
const Child = {inject: {foo}
}

统统转换成以下规范化格式:

const Child = {inject: {foo: {from: 'foo',default: 'xxx'  //如果有默认的值就有default属性}}
}

这样做的目的是,不管用户使用了何种写法,统统将其转化成一种便于集中处理的写法。

该函数的定义位于源码的src/core/util/options.js中,如下:

function normalizeInject (options: Object, vm: ?Component) {const inject = options.injectif (!inject) returnconst normalized = options.inject = {}if (Array.isArray(inject)) {for (let i = 0; i < inject.length; i++) {normalized[inject[i]] = { from: inject[i] }}} else if (isPlainObject(inject)) {for (const key in inject) {const val = inject[key]normalized[key] = isPlainObject(val)? extend({ from: key }, val): { from: val }}} else if (process.env.NODE_ENV !== 'production') {warn(`Invalid value for option "inject": expected an Array or an Object, ` +`but got ${toRawType(inject)}.`,vm)}
}

该函数的逻辑并不复杂,如果用户给inject选项传入的是一个字符串数组(写法一),那么就遍历该数组,把数组的每一项变成

inject:{foo:{from:'foo'}
}

如果给inject选项传入的是一个对象,那就遍历对象中的每一个key,给写法二形式的key对应的值扩展{ from: key },变成:

inject:{foo:{from: 'foo',default: 'xxx'}
}

将写法三形式的key对应的值变成:

inject:{foo:{from: 'foo'}
}

总之一句话就是把各种写法转换成一种规范化写法,便于集中处理。

3. 总结

本篇文章介绍生命周期初始化阶段所调用的第四个初始化函数——initInjections。该函数是用来初始化inject选项的。

由于inject选项在日常开发中使用频率不高,所以首先我们先根据官方文档回顾了该选项的作用及使用方法。

接着,我们分析了initInjections函数的内部实现原理,分析了是根据inject选项中的数据key是如何自底向上查找上游父级组件所注入的对应的值。

另外,对inject选项的规范化函数normalizeInject也进行了分析,Vue为用户提供了自由多种的写法,其内部是将各种写法最后进行统一规范化处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/67969.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

labview卸载重装碰到的问题

目录 labeiw卸载重装过程当中总是碰到一些问题&#xff0c;记录一下解决办法&#xff0c;碰到后好查找。 个人推荐卸载 用laview自带的卸载软件进行卸载&#xff0c;卸载的比较干净。 卸载完全第一步 启动 labview自带的卸载软件进行卸载。一般进行完这一步&#xff0c;就…

设计模式-9--迭代器模式(Iterator Pattern)

一、什么是迭代器模式 迭代器模式&#xff08;Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;用于提供一种统一的方式来访问一个聚合对象中的各个元素&#xff0c;而不需要暴露该聚合对象的内部结构。迭代器模式将遍历集合的责任从集合对象中分离出来&#xf…

【深入解析spring cloud gateway】07 自定义异常返回报文

Servlet的HttpResponse对象&#xff0c;返回响应报文&#xff0c;一般是这么写的&#xff0c;通过输出流直接就可以将返回报文输出。 OutputStream out response.getOutputStream(); out.write("输出的内容"); out.flush();在filter中如果发生异常&#xff08;例如…

Java中的网络编程------基于Socket的TCP编程和基于UDP的网络编程,netstat指令

Socket 在Java中&#xff0c;Socket是一种用于网络通信的编程接口&#xff0c;它允许不同计算机之间的程序进行数据交换和通信。Socket使得网络应用程序能够通过TCP或UDP协议在不同主机之间建立连接、发送数据和接收数据。以下是Socket的基本介绍&#xff1a; Socket类型&…

1775_树莓派3B键盘映射错误解决

全部学习汇总&#xff1a; GitHub - GreyZhang/little_bits_of_raspberry_pi: my hacking trip about raspberry pi. 入手树莓派3B之后用了没有多长时间&#xff0c;最初的这段时间感觉想让它代替我的PC机是不肯能的。性能先不说&#xff0c;我完全没有找到当初在我的笔记本上使…

css网格布局

css网格布局 常用属性 display: grid; //开启网格grid-template-columns: 2fr 1fr 1fr 1fr 1fr; //设置多少列每列宽度grid-gap: 10px; // 设置表格之间间距grid-template-rows: 50px 50px 50px 50px; // 设置多少行 每行的高度grid-column : 1 //占据位置 占据1格grid-colu…

ChatGPT分析日本排放核污水对世界的影响

文章目录 1 背景2 环境影响3 健康影响4 国际关系影响5 应对措施 近段时间被日本排放核污水到海里的消息刷屏了&#xff0c;这一举措引发了广泛的关注和担忧。本文结合ChatGPT来分析这件事的前因后果、会对世界造成的影响、以及应对措施。 1 背景 受2011年发生的大地震及海啸影响…

机器人中的数值优化(九)——拟牛顿方法(下)、BB方法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

某物联网数智化园区行业基于 KubeSphere 的云原生实践

公司简介 作为物联网 数智化园区一体化解决方案提供商&#xff0c;我们致力于为大中型园区、停车场提供软硬件平台&#xff0c;帮助园区运营者实现数字化、智能化运营。 在使用 K8s 之前我们使用传统的方式部署上线&#xff0c;使用 spug&#xff08;一款轻量级无 Agent 的自…

网络版五子棋C++实现

目录 1.项目介绍 2.开发环境 3.核心技术 4.环境搭建 5.WebSocketpp介绍 5.1WebSocketpp是什么 5.2为什么使用WebSocketpp 5.3原理解析&#xff1a; 5.4WebSocketpp主要特性 6.WebSocketpp使用 7.JsonCpp使用 8.MySQL API 9.项目模块设计以及流程图 10.封装日志宏…

NewStarCTF 2022 web方向题解 wp

----------WEEK1---------- BUU NewStarCTF 公开赛赛道 WEEK1 [NotPHP] 先看题目&#xff0c;要传参加绕过。 分析一下代码&#xff1a;首先get一个datadata://test/plain,Wel…。然后key1和2用数组可以绕过。num2077a可以绕过弱类型。eval()中的php语句被#注释了&#xff0c…

【jvm】jvm系统线程

目录 一、虚拟机线程二、周期任务线程三、GC线程四、编译线程五、信号调度线程 一、虚拟机线程 1.这种线程的操作是需要jvm达到安全点才会出现 2.这些操作必须在不同的线程中发生的原因是它们都需要jvm达到安全点&#xff0c;这样堆才不会变化 3.这种线程的执行类型包括stop-th…

并发编程的故事——JUC

JUC 文章目录 JUC一、Semaphore二、CountDownLatch三、线程安全类 一、Semaphore 为什么需要用到Semaphore? 限流 Semaphore的场景&#xff1f; 秒杀商品的时候&#xff0c;不能够让那些没有秒杀成功的线程进入&#xff0c;只有占了坑位的才可以使用&#xff0c;这里可以用re…

【Hello Algorithm】二叉树相关算法

本篇博客介绍&#xff1a;介绍二叉树的相关算法 二叉树相关算法 二叉树结构遍历二叉树递归序二叉树的交集非递归方式实现二叉树遍历二叉树的层序遍历 二叉树难题二叉树的序列化和反序列化lc431求二叉树最宽的层二叉树的后继节点谷歌面试题 二叉树结构 如果对于二叉树的结构还有…

Linux命令执行完成提醒

有些命令任务执行时间较长&#xff0c;可以让其执行完成时发出声音来提示。 如下&#xff1a; ls && echo -e "\a" 前一条命令成功执行后&#xff0c;会发出声音。 如果当前不在Iterm2窗口里&#xff0c;还会弹窗提示。

2023新版医保目录明细(药品查询)

查询医保目录的主要目的是为了了解医保政策对于特定医疗服务、药品和医疗器械的覆盖范围和支付标准。大众可以通过查看医保目录可以确定哪些药品可以被医保支付以及报销的比例和限额&#xff1b;医药从业者可通过查看医保目录可以即使了解医保政策的变化&#xff0c;便于做出相…

15种下载文件的方法文件下载方法汇总超大文件下载

15种下载文件的方法&文件下载方法汇总&超大文件下载 15种下载文件的方法Pentesters经常将文件上传到受感染的盒子以帮助进行权限提升&#xff0c;或者保持在计算机上的存在。本博客将介绍将文件从您的计算机移动到受感染系统的15种不同方法。对于那些在盒子上存在且需要…

python语言性能不适合在移动端做图色模拟开发,推荐用lua

##python --100次找色用时92120毫秒 from airscript.screen import FindColors from time import time t1time() for i in range(100):pFindColors("888,888,#000000").rect(0,0,1080,2400).find() t2time() print(f用时{round((t2-t1)*1000)}毫秒)# 用时92120毫秒…

基于jeecg-boot的flowable流程审批时增加下一个审批人设置

更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 因为有时…

【linux】定时任务讲解

文章目录 一. 在某时刻只执行一次&#xff1a;at1. 设置定时任务2. 查看和删除定时任务 二. 周期性执行任务&#xff1a;cron1. 启动crond进程2. 编辑定时任务3. 查看和删除4. 用户权限4.1. 黑名单4.2指定用户 三. /etc/crontab的管理 一. 在某时刻只执行一次&#xff1a;at 1…