vector 不初始化时什么状态_Vue原理解析(三):初始化时created之前做了什么?...

让我们继续this._init()的初始化之旅,接下来又会执行这样的三个初始化方法:

initInjections(vm)
initState(vm)
initProvide(vm)

5. initInjections(vm): 主要作用是初始化inject,可以访问到对应的依赖。

injectprovide这里需要简单的提一下,这是vue@2.2版本添加的一对需要一起使用的API,它允许父级组件向它之后的所有子孙组件提供依赖,让子孙组件无论嵌套多深都可以访问到,很cool有木有~

  • provide:提供一个对象或是返回一个对象的函数。
  • inject:是一个字符串数组或对象。

这一对APIvue官网有给出两条食用提示:

provideinject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
  • 大概是因为会让组件数据层级关系变的混乱的缘故,但在开发组件库时会很好使。
provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
  • 有个小技巧,这里可以将根组件data内定义的属性提供给子孙组件,这样在不借助vuex的情况下就可以实现简单的全局状态管理,还是很cool的~
app.vue 根组件export default {provide() {return {app: this}},data() {return {info: 'hello world!'}}
}child.vue 子孙组件export default {inject: ['app'],methods: {handleClick() {this.app.info = 'hello vue!'}}
}

一但触发handleClick事件之后,无论嵌套多深的子孙组件只要是使用了inject注入this.app.info变量的地方都会被响应,这就完成了简易的vuex。更多的示例大家可以去vue的官网翻阅,这里就不码字了,现在我们来分析下这么cool的功能它究竟是怎么实现的~

虽然injectprovide是成对使用的,但是二者在内部是分开初始化的。从上面三个初始化方法就能看出,先初始化inject,然后初始化props/data状态相关,最后初始化provide。这样做的目的是可以在props/data中使用inject内所注入的内容。

我们首先来看一下初始化inject时的方法定义:

export function initInjections(vm) {const result = resolveInject(vm.$options.inject, vm) // 找结果...
}

vm.$options.inject为之前合并后得到的用户自定义的inject,然后使用resolveInject方法找到我们想要的结果,我们看下resolveInject方法的定义:

export function resolveInject (inject, vm) {if (inject) {const result = Object.create(null)const keys = Object.keys(inject)  //省略Symbol情况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)) { //hasOwn为是否有result[key] = source._provided[provideKey]break}source = source.$parent}... vue@2.5后新增设置inject默认参数相关逻辑}return result}
}

首先定义一个result返回找到的结果。接下来使用双循环查找,外层的for循环会遍历inject的每一项,然后再内层使用while循环自底向上的查找inject该项的父级是否有提供对应的依赖。

Ps:这里可能有人会有疑问,之前inject的定义明明是数组,这里怎么可以通过Object.keys取值?这是因为上一章再做options合并时,也会对参数进行格式化,如props的格式,定义为数组也会被转为对象格式,inject被定义时是这样的:

定义时:
{inject: ['app']
}格式化后:
{inject: {app: {from: 'app'}}
}

书接上文,source就是当前的实例,而source._provided内保存的就是当前provide提供的值。首先从当前实例查找,接着将它的父组件实例赋值给source,在它的父组件查找。找到后使用break跳出循环,将搜索的结果赋值给result,接着查找下一个。

Ps:可能有人又会有疑问,这个时候是先初始化的inject再初始化的provide,怎么访问父级的provide了?它根本就没初始化阿,这个时候我们就要再思考下了,因为vue是组件式的,首先就会初始化父组件,然后才是初始化子组件,所以这个时候是有source._provided属性的。

找到了想到的结果之后,我们补全之前initInjections的定义:

export function initInjections(vm) {const result = resolveInject(vm.$options.inject, vm)if(result) { // 如果有结果toggleObserving(false)  // 刻意为之不被响应式Object.keys(result).forEach(key => {...defineReactive(vm, key, result[key])})toggleObserving(true)}
}

如果有搜索结果,首先会调用toggleObserving(false),具体实现不用理会,只用知道这个方法的作用是设置一个标志位,将决定defineReactive()方法是否将它的第三个参数设置为响应式数据,也就是决定result[key]这个值是否会被设置为响应式数据,这里的参数为false,只是在vm下挂载key对应普通的值,不过这样就可以在当前实例使用this访问到inject内对应的依赖项了,设置完毕之后再调用toggleObserving(true),改变标志位,让defineReactive()可以设置第三个参数为响应式数据(defineReactive是响应式原理很重要的方法,这里了解即可),也就是它该有的样子。以上就是inject实现的相关原理,一句话来说就是,首先遍历每一项,然后挨个遍历每一项父级是否有依赖。

6. initState(vm): 初始化会被使用到的状态,状态包括propsmethodsdatacomputedwatch五个选项。

首先看下initState(vm)方法的定义:

export function initState(vm) {...const opts = vm.$optionsif(opts.props) initProps(vm, opts.props)if(opts.methods) initMethods(vm, opts.methods)if(opts.data) initData(vm)...if(opts.computed) initComputed(vm, opts.computed)if(opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
}

现在这里的话只会介绍前面三类状态的初始化做了什么,也就是propsmethodsdata,因为computedwatch会涉及到响应式相关的watcher,这里先略过。接下来我们依次有请这三位的初始化方法登场:

6.1 initProps (vm, propsOptions):
  • 主要作用是检测子组件接受的值是否符合规则,以及让对应的值可以用this直接访问。
function initProps(vm, propsOptions) {  // 第二个参数为验证规则const propsData = vm.$options.propsData || {}  // props具体的值const props = vm._props = {}  // 存放propsconst isRoot = !vm.$parent // 是否是根节点if (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {const value = validateProp(key, propsOptions, propsData, vm)defineReactive(props, key, value)if (!(key in vm)) {proxy(vm, `_props`, key)}}toggleObserving(true)
}

我们知道props是作为父组件向子组件通信的重要方式,而initProps内的第二个参数propsOptions,就是当前实例也就是通信角色里的子组件,它所定义的接受参数的规则。子组件的props规则是可以使用数组形式的定义的,不过再经过合并options之后会被格式化为对象的形式:

定义时:
{props: ['name', 'age']
}格式化后:
{name: {type: null},age: {type: null}
}

所以在定义props规则时,直接使用对象格式吧,这也是更好的书写规范。

知道了规则之后,接下来需要知道父组件传递给子组件具体的值,它以对象的格式被放在vm.$options.propsData内,这也是合并options时得到的。接下来在实例下定义了一个空对象vm._props,它的作用是将符合规格的值挂载到它下面。isRoot的作用是判断当前组件是否是根组件,如果不是就不将props的转为响应式数据。

接下来遍历格式化后的props验证规则,通过validateProp方法验证规则并得到相应的值,将得到的值挂载到vm._props下。这个时候就可以通过this._props访问到props内定义的值了:

props: ['name'],
methods: {handleClick() {console.log(this._props.name)}
}

不过直接访问内部的私有变量这种方式并不友好,所以vue内部做了一层代理,将对this.name的访问转而为对this._props.name的访问。这里的proxy需要介绍下,因为之后的data也会使用到,看下它的定义:

格式化了一下:
export function proxy(target, sourceKey, key) {Object.defineProperty(target, key, {enumerable: true,configurable: true,get: function () {return this[sourceKey][key]},set: function () {this[sourceKey][key] = val}})
}

其实很简单,只是定义一个对象值的get方法,读取时让其返回另外的一个值,这里就完成了props的初始化。

6.2 initMethods (vm, methods):
  • 主要作用是将methods内的方法挂载到this下。
function initMethods(vm, methods) {const props = vm.$options.propsfor(const key in methods) {if(methods[key] == null) {  // methods[key] === null || methods[key] === undefined 的简写warn(`只定义了key而没有相应的value`)}if(props && hasOwn(props, key)) {warn(`方法名和props的key重名了`)}if((key in vm) && isReserved(key)) {warn(`方法名已经存在而且以_或$开头`)}vm[key] = methods[key] == null? noop  // 空函数: bind(methods[key], vm)  //  相当于methods[key].bind(vm)}
}

methods的初始化相较而言就简单了很多。不过它也有很多边界情况,如只定义了key而没有方法具体的实现、keyprops重名了、key已经存在且命名不规范,以_$开头,至于为什么不行,我们第一章的时候有说明了。最后将methods内的方法挂载到this下,就完成了methods的初始化。

6.3 initData (vm):
  • 主要作用是初始化data,还是老套路,挂载到this下。有个重要的点,之所以data内的数据是响应式的,是在这里初始化的,这个大家得有个印象~。
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm) // 通过data.call(vm, vm)得到返回的对象: data || {}if (!isPlainObject(data)) { // 如果不是一个对象格式data = {}warn(`data得是一个对象`)}const keys = Object.keys(data)const props = vm.$options.props  // 得到propsconst methods = vm.$options.methods  // 得到methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (methods && hasOwn(methods, key)) {warn(`和methods内的方法重名了`)}if (props && hasOwn(props, key)) {warn(`和props内的key重名了`)} else if (!isReserved(key)) { // key不能以_或$开头proxy(vm, `_data`, key)}}observe(data, true)
}

首先通过vm.$options.data得到用户定义的data,如果是function格式就执行它,并返回执行之后的结果,否则返回data{},将结果赋值给vm._data这个私有属性。和props一样的套路,最后用来做一层代理,如果得到的结果不是对象格式就是报错了。

然后遍历data内的每一项,不能和methods以及props内的key重名,然后使用proxy做一层代理。注意最后会执行一个方法observe(data, true),它的作用了是递归的让data内的每一项数据都变成响应式的。

其实不难发现它们仨主要做的事情差不多,首先不要相互之间有重名,然后可以被this直接访问到。

7. initProvide(vm): 主要作用是初始化provide为子组件提供依赖。

provide选项应该是一个对象或是函数,所以对它取值即可,就像取data内的值类似,看下它的定义:

export function initProvide (vm) {const provide = vm.$options.provideif (provide) {vm._provided = typeof provide === 'function'? provide.call(vm): provide}
}

首先通过vm.$options.provide取得用户定义的provide选项,如果是一个function类型就执行一下,得到返回后的结果,将其赋值给了vm._provided私有属性,所以子组件在初始化inject时就可以访问到父组件提供的依赖了;如果不是function类型就直接返回定义的provide

8. callHook(vm, 'created'): 执行用户定义的created钩子函数,有mixin混入的也一并执行。

终于我们越过了created钩子函数,还是分别用一句话来介绍它们主要都干了什么事:

  • initInjections(vm):让子组件inject的项可以访问到正确的值
  • initState(vm):将组件定义的状态挂载到this下。
  • initProvide(vm):初始化父组件提供的provide依赖。
  • created:执行组件的created钩子函数

初始化的阶段算是告一段落了,接下来我们会进入组件的挂载阶段。按照惯例我们还是以一道vue容易被问道的面试题作为本章的结束吧~:

面试官微笑而又不失礼貌的问道:
  • 请问methods内的方法可以使用箭头函数么,会造成什么样的结果?

怼回去:

  • 是不可以使用箭头函数的,因为箭头函数的this是定义时就绑定的。在vue的内部,methods内每个方法的上下文是当前的vm组件实例,methods[key].bind(vm),而如果使用使用箭头函数,函数的上下文就变成了父级的上下文,也就是undefined了,结果就是通过undefined访问任何变量都会报错。

顺手点个赞或关注呗,找起来也方便~

胡成:你可能会用的上的一个vue功能组件库,持续完善中...​zhuanlan.zhihu.com

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

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

相关文章

switch 字符串 java_JDK7新特性switch支持字符串

在JDK7中,switch语句的判断条件增加了对字符串类型的支持。由于字符串的操作在编程中使用频繁,这个新特性的出现为Java编程带来了便利。接下来通过一个案例演示一下在switch语句中使用字符串进行匹配。public class Example {public static void main(String[] args) {String w…

cisco packet tracer路由器配置_【干货】思科交换机路由器怎么配置密码?

今天带大家看看如何在思科的交换机路由器当中配置安全特性&#xff0c;也就是密码的配置方式。在学习配置之前&#xff0c;我们先回顾一下密码相关知识。密码学是研究信息系统安全保密的科学。人类有记载的通信密码始于公元前400年&#xff0c;古希腊人是置换密码学的发明者。密…

perl 哈希数组的哈希_使用哈希检查两个数组是否相似

perl 哈希数组的哈希Prerequisite: Hashing data structure 先决条件&#xff1a; 哈希数据结构 Problem statement: 问题陈述&#xff1a; Check whether two arrays are similar or not using the hash table. The arrays are of the same size. 使用哈希表检查两个数组是否…

codevs3872 邮递员送信(SPFA)

邮递员送信 时间限制: 1 Sec 内存限制: 64 MB提交: 10 解决: 5[提交][状态][讨论版] 题目描述 有一个邮递员要送东西&#xff0c;邮局在节点1.他总共要送N-1样东西&#xff0c;其目的地分别是2~N。由于这个城市的交通比较繁忙&#xff0c;因此所有的道路都是单行的&#xff0…

java上传csv文件上传_java处理csv文件上传示例详解

前言&#xff1a;示例只是做了一个最最基础的上传csv的示例&#xff0c;如果要引用到代码中去&#xff0c;还需要根据自己的业务自行添加一些逻辑处理。readcsvutil工具类package com.hanfengyeqiao.gjb.utils;import java.io.*;import java.util.*;/*** csv工具类*/public cla…

360更新补丁一直提示正在安装_远程利用POC公布|CVE20200796:微软发布SMBv3协议“蠕虫级”漏洞补丁通告...

更多全球网络安全资讯尽在邑安全www.eansec.com0x00 事件描述2020年3月11日&#xff0c;360CERT监测到有海外厂家发布安全规则通告&#xff0c;通告中描述了一处微软SMBv3协议的内存破坏漏洞&#xff0c;编号CVE-2020-0796&#xff0c;并表示该漏洞无需授权验证即可被远程利用&…

字符串的回文子序列个数_计算给定字符串中回文子序列的数量

字符串的回文子序列个数Problem statement: 问题陈述&#xff1a; Given a string you have to count the total number of palindromic subsequences in the giving string and print the value. 给定一个字符串&#xff0c;您必须计算给定字符串中回文子序列的总数并打印该值…

Linux-破解rhel7-root密码

破解7的密码1.linux16 rd.break2.mount -o remount,rw /sysroot3.chroot /sysroot4.passwd5.touch /.autorelabelexitexit7版本grub菜单加密1.grub2-mkpasswd-pbkdf22.vi /etc/grub.d/40_customset superusers"root"password_pbkdf2 root grub.pbkdf2.sha512.10000.…

适配接口 java_【Java 设计模式】接口型模式--Adapter(适配器)模式

简介&#xff1a;【Java设计模式】接口型模式–Adapter(适配器)模式Adapter模式的宗旨就是&#xff1a;向客户提供接口&#xff0c;并使用现有的类所提供的服务&#xff0c;以满足客户的需求。 或者说&#xff0c;现在有classA的方法满足客户的部分要求&#xff0c;将另一部分需…

deepinu盘制作工具_u盘启动盘制作工具怎么制作 u盘启动盘制作工具制作方法【详细步骤】...

在电脑城很多技术人员都会使用u盘装系统的方法给用户电脑安装系统&#xff0c;他们是怎么操作的呢?其实很简单&#xff0c;就是通过u盘启动盘来安装系统的。而u盘启动盘是需要用 u盘启动盘制作工具 来制作的。那么问题又来了&#xff0c;u盘启动盘制作工具怎么制作呢?下面就给…

openstack私有云_OpenStack-下一代私有云的未来

openstack私有云The OpenStack project is an open source cloud computing platform for all types of clouds, which aims to be simple to implement, massively scalable, and feature rich. Developers and cloud computing technologists from around the world create t…

outlook2010客户端无法预览及保存word,excel问题

outlook2010客户端遇到的EXCEL预览及保存问题今天遇到了一个这样的问题&#xff0c;outlook2010打开以后其他的excel都可以打开预览及保存&#xff0c;这个excel无法预览既保存&#xff0c;经查是outlook2010预览及打开的缓存有限制&#xff0c;超过后就无法预览了&#xff0c;…

python自动化框架pytest pdf_Python 自动化测试框架 unittest 和 pytest 对比

一、用例编写规则1.unittest提供了test cases、test suites、test fixtures、test runner相关的类,让测试更加明确、方便、可控。使用unittest编写用例,必须遵守以下规则:(1)测试文件必须先import unittest(2)测试类必须继承unittest.TestCase(3)测试方法必须以“test_”开头(4…

freemarker的测试结果框架_java必背综合知识点总结(框架篇)

框架篇一、Struts1的运行原理在启动时通过前端总控制器ActionServlet加载struts-config.xml并进行解析&#xff0c;当用户在jsp页面发送请求被struts1的核心控制器ActionServlet接收&#xff0c;ActionServlet在用户请求时将请求参数放到对应的ActionForm对象中的成员变量中&am…

Java SecurityManager checkPackageDefinition()方法与示例

SecurityManager类的checkPackageDefinition()方法 (SecurityManager Class checkPackageDefinition() method) checkPackageDefinition() method is available in java.lang package. checkPackageDefinition()方法在java.lang包中可用。 We call getProperty("package.d…

java容器详解_详解Java 容器(第①篇)——概览

![](http://img.blog.itpub.net/blog/2020/04/02/9d89d3008962c127.png?x-oss-processstyle/bb)容器主要包括 Collection 和 Map 两种&#xff0c;Collection 存储着对象的集合&#xff0c;而 Map 存储着键值对(两个对象)的映射表。# 一、Collection![](https://upload-images…

python图形界面库哪个好_8个必备的Python GUI库

Python GUI 库有很多&#xff0c;下面给大家罗列常用的几种 GUI库。下面介绍的这些GUI框架&#xff0c;能满足大部分开发人员的需要&#xff0c;你可以根据自己的需求&#xff0c;选择合适的GUI库。1. wxPython wxPython 是一个跨平台的 GUI 工具集&#xff0c;是 Python 语言的…

为什么在Python中使用string.join(list)而不是list.join(string)?

join() is a string method and while using it the separator string iterates over an arbitrary sequence, forming string representations of each of the elements, inserting itself between the elements. join()是一个字符串方法&#xff0c;使用它时&#xff0c;分隔…

js的client、scroll、offset详解与兼容性

clientWidth&#xff1a;可视区宽说明&#xff1a;样式宽padding参考&#xff1a;js的client详解 scrollTop : 滚动条滚动距离说明&#xff1a;chrome下他会以为滚动条是文档元素的&#xff0c;所以需要做兼容&#xff1a;var scrollTop document.documentElement.scrollTop |…

88是python语言的整数类型_Python基础数据类型题

Python基础数据类型 题 考试时间&#xff1a;三个小时 满分100分&#xff08;80分以上包含80分及格&#xff09; 1&#xff0c;简述变量命名规范&#xff08;3分&#xff09;1.必须是字母&#xff0c;数字&#xff0c;下划线的任意组合。 2.不能是数字开头 3.不能是python中的关…