vue2基础
-
vue2 插件
- Prettier 格式化插件(可以设置自动保存,设置键盘快捷方式ctrl+s进行格式化)
- vscode-icon vscode文件的图标
- Vetur(Ping Wu)
- vue-helper(shenjiaolong)
- Vue 3 Snippets(hollowtree)
-
MVVM模式
- vue是遵循 MVVM 模型(MVVM是一种软件架构的模式)
- MVVM分为三个部分
- M(model):模型层,指得是数据模型,主要负责业务数据相关(ajax请求,数据处理等等)
- V(view):视图层,顾名思义,负责视图相关,细分下来就是html+css层,为了更方便的展示Model层的数据;
- VM:V与M沟通的桥梁,负责监听M或者V的修改,是实现MVVM双向绑定的要点
- ViewModel是Vue.js的核心,它就是Vue实例的设计参考,MVVM支持双向绑定,意思就是当M层数据进行修改时,VM层会监测到变化,并且通知V层进行相应的修改,反之修改V层则会通知M层数据进行修改
-
关闭生产提示
Vue.config.productionTip = false;
-
Vue的基础使用
- 引入vue
- 创建一个模板容器
- 实例化Vue,得到vm对象
- 给Vue的参数传入配置(必填配置项:el,data,template)
-
el配置项(规定挂载的容器):
- 提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。
- 可以是 CSS 选择器(推荐的,并且使用id),也可以是一个 HTMLElement 实例
- vm.$el:Vue 实例使用的根 DOM 元素
-
模板配置项:
- 模板的写法1:使用Vue的template配置项
- 一个字符串模板作为 Vue 实例的虚拟DOM
- template中的字符串模板将会替换挂载的元素。挂载元素的内容都将被忽略
- 模板的写法2:直接在挂载容器中书写
- 如果vue实例在检测没有template配置项的时候,就会把容器的outerHTML作为模板
- 模板的写法1:使用Vue的template配置项
-
data配置项:
- 可以是一个Vue 实例的数据对象,也可以是返回一个初始数据对象的函数
- 作用就是为了给模板提供数据,并且支持数据双向绑定(MVVM)
- 面试题:为什么data要写成一个函数!!!
- 当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。
- 如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!
- 通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
-
vm.$mount方法
- 如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。
- 可以使用 vm.$mount() 手动地挂载一个未挂载的实例。
- 和el的区别是:$mount可以进行延迟挂载
-
vue的插值语法
- Vue在模板中可以使用{{}}来做插值语法
- 插值语法中所有的方法或属性都是去vm的实例对象上找(data中的数据其实也在实例对象上存放了一份[数据代理])
- 插值语法中可以书写实例上的所有的属性
- 插值语法中也可以书写【表达式】最终得到一个值(表达式和语句最大的区别就是表达式有返回值)
vue的指令
-
什么是vue指令
- 指令 (Directives) 是带有 v- 前缀的特殊的模板元素上 attribute
- 指令 attribute 的值预期是插值语法(v-for除外),和{{}}插值语法一致
- 指令主要是响应式地作用于 DOM
-
v-bind指令
- 动态的给模板中的元素绑定一个属性(属性值区域是一个插值语法区域)
- 用法 v-bind:xxx=“”
- v-bind可以简写为一个":",比如 :src=“xxxx”
- 如果v-bind内部需要拼接可以使用以下两种方式
<img :src="publicUrl+'logo.svg'" alt="">
<img :src="
${publicUrl}logo.svg" alt="">
-
v-bind动态绑定style
- 如果某些元素的样式需要用style属性书写,并且是动态样式,那么我们可以使用动态style绑定
- 绑定方式1:字符串拼接形式(不推荐)
- 绑定方式2:对象形式(推荐)
- 绑定方式3:数组形式,内部可以书写多个对象
-
v-bind动态绑定class
- 如果某些元素的样式需要使用class动态绑定,则需要v-bind
- 绑定方式1:字符串形式(类只有一个)
- 绑定方式2:对象形式(类有多个固定个数,都写在对象的key上,值为布尔值,控制布尔值控制类)
- 绑定方式3:数组方式(类有多个不固定个数,但是不确定有哪些,把类都放在数组中即可)
-
v-model的基础使用
- 替代表单上的value属性,可以对表单的值进行双向数据绑定
- 如果数据修改,表单的值就会发生改变,如果表单的值发生改变,数据也会随着改变
- 一般用来收集表单数据
-
v-model的修饰符
- .lazy - 取代 input 监听 change 事件
- .number - 输入字符串转为有效的数字
- .trim - 输入首尾空格过滤
-
v-model收集表单数据
- 收集单选框数据-使用字符串收集
- 收集复选框数据
- 独立使用复选框:使用布尔值收集
- 多个使用复选框:使用数组收集
- 收集下拉列表数据
- 单选:使用字符串收集
- 多选:使用数组收集
- 收集text文本和textarea等都是使用字符串收集
-
v-model的本质
- 使用v-bind:value给表单强制单向绑定一个值
- 使用input事件监听表单内容的改动,在事件中获取表单新输入的值,给data中的数据赋值
<input type="text" :value="msg" @input="msg = $event.target.value" />
-
v-on基础使用
- v-on主要是给元素绑定事件用的 格式是:v-on:xxx=“xxxxxx”
- v-on可以简写为 @xxx=“xxxxx”
- v-on可以接收一个函数,我们的方法和事件函数都要书写在methods配置项中,并且vue把methods中的所有方法的this都统一指向了vm
- v-on的值也可以不书写,直接书写表达式(也是插值语法区域),当事件触发的时候表达式就会执行
-
事件函数的传参和event
- 默认事件函数接受的第一个参数就是event事件对象
- 当事件函数需要传参的时候,直接在函数名后边添加括号即可
- 如果已经传参的函数想要接受event事件对象?在事件的插值语法区域本身就存在一个$event变量,代表的是当前事件的event事件对象
-
事件的修饰符
- .prevent:阻止默认事件
- .stop:阻止传播
- .capture:控制事件在捕获阶段执行
- .once:一次性事件
- .self:只有事件发生在自己身上的时候才会触发
- .enter:按键事件修饰符 回车键
-
条件渲染 v-if
- v-if/v-else-if/v-else
- 当if和else一起混用的时候,中间不要放其他元素
- 当多个元素需要条件渲染的时候,我们还不想多一层结构,则可以使用template元素,他不会在渲染的时候多一层结构,只是起到了一个分组的作用
- 在元素切换的过程中,我们可以通过key属性来管理元素是否复用
-
条件展示元素的选项是 v-show 指令
- v-show 的元素始终会被渲染并保留在 DOM 中。
- v-show 只是简单地切换元素的 CSS 属性 display
- v-show 不支持 元素,也不支持 v-else。
-
v-show和v-if的区别
- v-if 是“真正”的条件渲染,因为它会确保在切换过程中元素或者子组件适当地被销毁和重建。
- v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
- v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
- 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
-
v-for指令
- v-for专门用来做条件渲染
- 需要遍历生成多个哪个元素,就给哪个元素添加v-for
- v-for内部书写格式
xxx in xxxxx
,xxx of xxxxx
,in前边的值是我们创建的变量,只能在当前v-for的区域内生效,in后边的值就是去实例对象上找到的数据, - in前边还能再添加一个值,代表的是当前数据的下标
- v-for必须配合 key属性的时候用,方便在数据修改的时候准确复用元素
-
v-for遍历其他类型数据
- 遍历对象—item就是对象的value index就是对象的key
- 遍历字符串—item就是每一个单独的字符串, index就是下标
- 遍历数字----item就是从1开始到数字的大小,index就是从0开始的下标
-
其他指令
- v-text:更新元素的 textContent,如果要更新部分的 textContent需要使用插值语法(更推荐插值语法替代)
- v-html:更新元素的 innerHTML(推荐插值语法替代)
- v-pre : 跳过这个元素和它的子元素的编译过程。可以用来显示原始 插值标签。跳过大量没有指令的节点会加快编译。
- v-once:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
- v-cloak:这个指令保持在元素上直到vue介入,(我们可以在vue介入之前,通过属性名选择器选择到v-clock的元素,控制样式,一旦vue介入,这个样式会自动失效)
计算属性和watch侦听
-
计算属性
- 当插值中需要的数据是通过计算得来的,我们可以使用计算属性得到值,然后在插值语法中使用
- 计算属性vs插值中直接书写计算:插值中直接书写计算,维护性差,阅读不直接
- 计算属性vs函数封装:函数封装以后,在插值中直接调用,阅读性变好了,但是每次需要用到这个计算好的值的时候,函数都会重新调用并重新计算,性能稍弱
- 计算属性优势:计算属性是基于它们的响应式依赖(计算时需要的其他数据)进行缓存的,第一次计算好的结果会缓存起来,如果响应式的依赖没有发生变化,则每次读取这个计算属性的值都会直接去拿缓存
- 计算属性和普通的data中的数据是一样的,也是直接放在vm身上,可以直接使用
-
计算属性的时候
- 如果一个值是通过其他数据计算得到的,请注意可能要使用计算属性
- 计算属性是写在computed配置项中
- 如果这个计算属性的值是只读的,则直接书写为一个函数,内部return计算的值即可
- 如果这个计算属性是可读可写的,则要把计算属性书写为一个对象,内部拥有get和set方法,当读的时候调用的是get方法,当写的时候调用的是set方法,set方法接受的参数就是被设置的新值
-
watch侦听
- 如果我们想要监听一个数据的变化,从而去做一些事情,我们可以使用侦听属性watch
- 如果我们只是简单的侦听,则可以在watch的配置项中书写函数配置即可,函数接受两个参数,一个是newVal一个是oldVal
- 一旦侦听的是一个对象,并且希望侦听到对象内部的属性发生变化,则watch的配置是一个对象,对象中需要书写watch的配置deep为true(开启深度监听)
- 如果侦听的是一个对象,对象内部属性发生变化,则侦听到新值和旧值是相等的(对象的比较是地址的比较)
- 如果想要设置侦听的时候就直接先执行一次侦听函数,则可以书写一个立即监听的配置项immediate为true
- 如果想要直接侦听对象数据的内部的某个属性,则需要书写字符串形式,比如
"person.age"(){}
-
watch和computed的对比
- watch和computed都可以实现侦听的功能,watch直接侦听某个值,computed是侦听内部计算需要的属性
- computed能完成的watch都可以完成,但是watch完成的,computed不一定可以完成(比如监听以后,执行异步请求数据)
- watch擅长处理的场景:一个数据影响多个数据 ,侧重在【监视】,监视后想要做什么根据逻辑决定
- computed擅长处理的场景:受一个或多个数据影响最后得到一个数据,终点是计算并得到一个数据
修改data数据
-
修改的数据是基本类型
- 基本类型值是不可变的(可被替换)
- 基本类型被替换的时候,是可以触发响应式的
-
修改的数据是对象类型
- 对象类型包括内部的属性值一旦被修改,是具有响应式的
- 对象属性的新增或者删除时没有响应式的!!!(因为vue2的响应式数据原理根本导致的)
- vm.$set()或者Vue.set()可以用来做新增 并且内部帮我们完成响应式
- vm.$delete或者Vue.delete()可以用来做删除,并且内部帮我们完成响应式
-
修改的数据是数组类型
- 如果直接对数组通过下标或者长度等操作,是没有响应式的
- 数组中有7个变更方法(改变了原始数组的值) push pop shift unshift sort reverse splice,vue重写以上7个方法,以上7个方法在完成修改数组的前提下,还会让视图重新渲染,所以我们使用以上7个方法修改数组,数组是具有响应式的
- 相比之下,也有非变更方法,例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组
-
强制更新
- vm.$forceUpdate():迫使 Vue 实例重新渲染
生命周期
-
初始化阶段
-
过程:
- 实例化Vue
- 初始化内部的事件和生命周期系统
- 此时介入一个生命周期函数
beforeCreate
- 初始化数据的注入和数据代理和数据劫持
- 此时介入一个生命周期函数
created
-
生命周期-beforeCreate和created
- 代表的是数据注入\数据代理\数据劫持创建之前和创建之后
- beforeCreate中无法获取数据,在created中就可以获取数据
-
-
模板编译阶段
-
过程:
- 判断有没有el配置项,如果有则继续向下,如果没有则等待$mount方法调用,调用以后继续向下
- 判断有没有template配置项
- 如果有,把template中书写的模板进行编译,编译为虚拟DOM
- 如果没有,把容器el的outerHTML作为模板进行编译,编译为虚拟DOM
-
生命周期—无
-
-
挂载阶段
-
过程:
- 此时先介入一个生命周期函数
beforeMount
- 把模板编译阶段的虚拟DOM转为真实DOM,并替换el容器,我们可以通过vm.$el获取到真实DOM根元素
- 此时介入一个生命周期函数
mounted
- 此时先介入一个生命周期函数
-
生命周期
- beforeMount:在挂载之前,此时展示的是未经Vue编译的结构
- mounted:在挂载之后,此时页面展示的是Vue编译后的结构,我们通常把初始化操作放在mounted中(初始化一个定时器,初始化ajax请求,绑定自定义事件,开启订阅)
-
-
更新阶段
-
过程:
- 我们修改在模板中已经使用的数据才能进到更新阶段
- 介入一个生命周期函数
beforeUpdate
- 根据新的数据生成新的虚拟DOM,然后根据diff算法比较,更新DOM
- 介入一个生命周期函数
updated
-
生命周期
- beforeUpdate:此时数据是新的页面是旧的,其实是在数据更新之后真实DOM更新之前
- updated:此时数据是新的,页面也是新的,是在数据更新后,真实DOM更新之后
-
-
卸载阶段
-
过程
- 当调用vm. d e s t r o y 方 法 的 时 候 进 入 卸 载 阶 段 ( 其 实 当 一 个 组 件 因 为 路 由 或 者 条 件 渲 染 的 情 况 下 , 也 会 进 行 卸 载 , v u e 中 是 不 建 议 我 们 直 接 使 用 destroy方法的时候进入卸载阶段(其实当一个组件因为路由或者条件渲染的情况下,也会进行卸载,vue中是不建议我们直接使用 destroy方法的时候进入卸载阶段(其实当一个组件因为路由或者条件渲染的情况下,也会进行卸载,vue中是不建议我们直接使用destroy卸载的)
- 介入一个生命周期函数
beforeDestroy
- 进入vue的卸载过程:完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器
- 介入一个生命周期函数
destroyed
-
生命周期
- beforeDestroy:此时在组件实例即将销毁之前,在这个位置更新数据没有任何一一,一般我们这里做一些收尾工作(比如清除定时器,取消订阅)
- destroyed:组件实例已经被销毁,这个生命周期基本不用
-
-
生命周期的补充
在runtime版本的vue中(脚手架环境下),项目是打包以后,运行在浏览器的。打包好的文件是没有模板编译的功能的,那么我们在项目中组件切换的时候,那是怎么把这个新组件的模板编译的呢?- 我们上边说的生命周期流程是完整版的vue的流程
- runtime版本的vue是没有模板编译阶段的,因为我们的项目是经过webpack打包编译的,webpack中的vue-loader,会在构建阶段的时候会把vue文件内部的模板使用
vue-template-compiler
包编译为一个渲染函数,当这个组件被挂载的时候,直接会调用渲染函数,而不需要再次模板编译了
-
父子组件的生命周期
-
初始化挂载阶段
父beforeCreate—>父created—>父beforeMount—>儿beforeCreate—>儿created—>儿beforeMount—>儿mounted—>父mounted -
更新阶段
- 如果子组件没有使用父组件更新的数据,则子组件是不会发生更新或者重新渲染的
- 父beforeUpdate—>子beforeUpdate—>子updated—>父updated
-
销毁阶段
- 父beforeDestroy—>子beforeDestroy—>子destroyed—>父destroyed
-
组件
-
在非单文件组件中组件的创建和使用
- 使用Vue.extend方法创建组件,接受一个配置对象作为参数(对象内部的配置和new Vue的配置类似)
- 如果想要使用某个组件,需要在使用者的内部进行注册该组件(使用components配置项注册)
- 注册完成之后,需要在当前组件的模板中,用标签的形式(单标签或者双标签都可以)使用该组件
-
组件的本质
- 使用Vue.extend创建的组件其实是一个
VueComponent构造函数
- 当组件被注册并使用的时候,会实例化这个VueComponent构造函数,得到一个组件实例对象
- 组件实例对象其实就是一个小型的vm对象
- 组件中所有的方法和数据其实都是放在这个组件实例上,组件中的方法的this也都是指向组件实例
- 使用Vue.extend创建的组件其实是一个
-
一个重要的内置关系
- VueComponent类继承Vue类 因为VueComponent可能有多个,Vue一般只有一个. 我们把所有实例公共的方法放在Vue的原型对象上,即可实现所有共享
- 组件实例—> 当前组件的VueComponent.prototype —> Vue.prototyoe —>Object.prototype—>Null
- 未来只要说想让所有的组件都能访问一个属性或者方法,则把这个属性或者方法放在Vue的原型对象上即可
-
创建组件的简写
- 直接创建组件的时候写为配置对象即可
- 当组件被注册时候,vue内部会进行判断当前是对象还是VueComponent构造函数,如果是对象,则内部会调用Vue.extend方法,把对象传递进去作为参数,得到VueComponent构造函数
-
全局注册组件
- 如果组件复用的比较多,我们无须在每一个组件中都要注册公共组件,而是可以全局注册
- 全局注册使用Vue.component(“组件名称”,组件的配置对象)来进行注册
脚手架的使用
-
vue脚手架的安装
- 安装全部的包
npm i -g @vue/cli
,此时电脑中就会有一个全局的命令vue vue -V
查看脚手架版本- 安装脚手架
vue create xxxx
- Manually select features:我们自定义配置
- 下一步选择配置,直接默认选择babel和Linter两个默认(直接敲回车)
- 2.x(选择vue2)
- ESLint with error prevention only(ESlint配置)(默认)
- Lint on save (默认)
- In dedicated config files(默认)
- 安装全部的包
-
脚手架目录
-
public
:公共资源,webpack在打包的时候忽略当前文件夹的内容,而是直接把当前文件夹的内容复制到打包目录 -
src
:我们编写代码的地方,项目的入口文件所在的目录 -
.browserslistrc
:指定我们项目针对的目标浏览器的范围,给babel在转高版本的js到低版本的js的时候,进行的兼容性参考-
1%:有超过全球百分之1的人在使用这个浏览器
-
last 2 versions:表示最少兼容到浏览器的最后两个版本
-
not dead:不兼容已经被淘汰的浏览器
-
-
.eslintrc
:eslint的配置 -
babel.config.js
:babel的配置(主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中) -
jsconfig.json
:1.当前文件所在的目录就是根目录 2. 提升当前项目的开发体验 -
package.json
: 包管理文件 -
vue.config.js
:当前vue脚手架的配置文件
-
-
脚手架vue版本分析
- 脚手架中使用的vue版本是vue.runtime版本,和完整版本的区别在于没有模板的编译功能
- 脚手架中在打包的时候是需要模板编译的,打包好以后就不再需要模板编译了,但是打包好是需要vue的其他功能,所以我们为了节省打包好的文件大小,把模板编译功能从vue的版本中剥离出来
- 脚手架中模板编译的包是
vue-template-compiler
,属于开发依赖,打包好以后是没有这个文件的. - 脚手架中的vue的runtime版本是生产依赖,打包好以后是有这个文件的
- 因为编译的包和之前使用的完整的vue不一致,我们需要在new Vue的时候使用render方法进行把根组件渲染到容器中
- render配置是一个函数,接受一个createElement(h)方法作为参数,h方法接受根组件,编译为VDOM渲染在容器中
-
单文件组件中样式的控制
- 如果直接在单文件组件的style标签中书写样式,则这个样式打包好以后是针对所有的结构生效的
- 如果我们给style标签添加一个 scoped 属性,则当前style中的样式只在当前组件内生效
- scoped原理:
- 给当前组件的所有元素上都添加一个data-v-xxxx的自定义属性(当前组件内一致,组件之间唯一),如果有子组件,则给子组件的最外层容器也添加上一个自定义属性
- 给自己style中所有的选择器都添加一个属性过滤选择器,要求除了满足选择器以外还要拥有刚才给自己组件的元素添加的自定义属性
- 深度选择器
- 我们希望在当前组件中控制后代组件中某个元素的样式,我们不能直接书写后代选择器,因为scoped给所有的选择器都添加了自己的data-v标识,子组件中的元素是没有这个标识的
- 我们希望后代选择器后边的那个选择器不要再添加标识了,这样就可以进入选择了,我们需要把后代选择器替换为
深度选择器 >>>
即可 - scoped不会给
深度选择器>>>
后边的选择添加标识
-
普通环境下使用eslint
npm init
:初始化包管理文件npm i eslint
:安装eslint包eslint --init
:初始化eslint的配置文件- 在eslint的配置文件中的rules对象中书写规则
error
或者2 代表报错warn
或者1 代表警告off
或者0 代表关闭
- 在配置中有一个
extends: "eslint:recommended",
代表默认开启eslint的默认规则 - 使用
eslint 文件名
检测某个js文件是否符合我们书写的条件
-
在脚手架环境配置eslint
npm init
:初始化包管理文件npm i eslint@7
:安装eslint包eslint --init
:初始化eslint的配置文件- 在eslint的配置文件中的rules对象中书写规则
error
或者2 代表报错warn
或者1 代表警告off
或者0 代表关闭
- 配置webpack,在loader中配置eslint-loader@3
const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "build"), filename: "index.js", }, mode: "development", module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "eslint-loader", }, ], }, };
- 当我们使用webpack命令启动项目的时候,会在打包之前进行eslint检测
-
忽略eslint的检查
- 直接关闭eslint(在vue中的vue.config.js配置文件中配置lintOnSave配置项值为false)(不推荐)
- 可以直接在eslint配置文件的rules中对某一个具体的规则进行配置(覆盖\新增)
- 可以使用
/* eslint-disable-next-line xxxx? */
关闭下一行的某个xxxx语法的检查(如果不写xxxx,则关闭下一行所有的语法检查) - 可以使用
/* eslint-disable xxxx*/
关闭当前行之后的所有的关于xxxxx的eslint检查(如果不写xxxx,则关闭所有的语法检查)
vue组件化开发
-
props的基础使用
- props主要是用来做父组件向子组件传递数据的(通过给子组件元素添加属性的方式传递)
- 在子组件内部通过 props配置项(可以是一个数组) 接受父组件传递的props值,并把值放在子组件的组件实例上(模板可以直接去使用)
- 在传递props的时候,如果值是需要计算得到的,请使用v-bind强制绑定
-
props是只读属性
- 我们通过props接收的父组件传递过来的数据,只能读取和使用,不能进行修改!!!!!
- 无论传递过来的是基本类型还是对象还是数组,都要遵守这个只读的约定
-
props的批量传递
- 如果要把一个对象的内部的属性一个个传递到组件中,我们可以直接使用批量传递
- 批量传递props数据使用 v-bind=“对象” 其实就是把对象展开一个个传递进去的
-
接收props的三种写法
- props的值是一个数组
- props的值是一个对象,对象中的key是接收的props属性,值是当前props值的类型限制
- props的值是一个对象,对象的key是接收的props属性,值是关于这个props属性更多限制组成的对象(type:类型 required:必填)
-
props做子传父
- vue是单向数据流(react也是),数据只从由高层的组件流向低层的组件,后代组件不能直接修改祖辈组件的数据
- 我们可以给子组件传递一个props.值为函数,子组件需要通知父组件修改父组件的值的时候,可以调用props接受的函数,父组件就会收到通知,修改自己的值
- 总结:props传递的是一个值–>父传子 props传递的是一个函数—>子传父
-
ref的使用
- ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。
- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
- 如果用在子组件上,引用就指向组件实例(其实是组件通信的一种方式,因为可以在父组件操作子组件的数据)
-
过滤器的使用
- 当一系列的数据要经过固定的格式转换的时候,我们考虑使用过滤器(千位分隔符,所有价格前都加上$,所有数字取小数点后两位等等等)
- 在{{}}插值语法中使用数据的时候,可以在数据后边添加一个管道符| 管道符后跟上一个过滤器
- 局部注册过滤器的时候,使用filters配置项注册
- 过滤器是一个函数,函数接收第一个参数就是当前被过滤的值,过滤器函数return的值就是最后过滤出来的值
- 使用过滤器的时候可以进行传参,在过滤器注册时候,第二个参数开始就是过滤器接收的参数
- 我们可以使用Vue.filter(“过滤器名称”,fn)来注册全局过滤器,在所有组件中都可以直接使用
- 过滤器是可以链式使用的!!!
-
自定义指令的基础使用
- 需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令,所以自定义指令主要用来操作DOM
- 局部注册自定义指令使用directive配置项,每一个指令都是一个函数,函数接受两个参数,一个是当前的DOM,一个是当前指令的详细(里边有一个value属性就是指令的值)
- 我们可以在自定义指令的函数中对底层的DOM操作即可
- 全局注册自定义指令使用 Vue.directive(“指令名称”,fn)
-
自定义指令的生命周期
- bind:初始化指令的时候执行
- inserted:被绑定元素插入父节点时调用
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前
- componentUpdate:指令所在组件的 VNode 及其子 VNode 全部更新后调用
- unbind:只调用一次,指令与元素解绑时调用
- 我们在定义自定义指令的时候直接书写一个函数,则默认是bind方法,如果想写钩子函数,则定义的时候要书写一个对象格式
-
Vue的插件及使用
- 当全局扩展一些内容的话,我们可以把逻辑封装起来,使用插件引入项目
- 常见使用插件的内容:1.全局过滤器 2. 全局自定义指令 3. 全局组件 4. Vue的原型对象上的方法或属性
- 封装插件的时候我们可以暴露一个对象(对象内部必须拥有install方法),或者暴露一个函数,install方法或者这个函数默认接受Vue作为参数
- 我们在入口文件中引入这个插件,并且使用Vue.use()使用插件,如果插件是对象,则默认调用对象的install方法,如果插件是函数,则默认当做是install方法调用,都会传递进入一个Vue作为参数
-
Vue组件通信-自定义事件
- 什么是自定义事件
- 我们如果想要进行子传父的组件通信,可以选用自定义事件
- 给子组件绑定自定义事件,事件函数在父组件中
- 在子组件内部触发自己身上的自定义事件,处在父组件中的事件函数就会执行
- 我们可以在触发自定义事件的时候,传递参数,父组件的事件回调函数就能接到参数
- 绑定事件的方式
- 直接在组件上使用 @xxx=""绑定自定义事件(1.自定义事件名称可以和原生事件同名,但是仍然是自定义事件 2.自定义事件建议命名 xxx-xxx的格式,而不是驼峰写法)
- 我们还可以直接获取到子组件的组件实例,使用 o n 或 者 on或者 on或者once(一次性事件)的方法绑定事件
- 调用事件的方式
- 直接使用 组件实例上的 $emit调用事件即可,第一个参数是事件名称,后边的参数是依次传递的值
- 解绑事件的方式
- 使用组件实例的$off方法进行解绑
- $off(“xx”):解绑某个事件
- $off([]):解绑多个事件
- $off():解绑所有的事件
- $off可以在子组件和父组件中都能使用
- 什么是自定义事件
-
组件通信方式-PubSub
- PubSub是我们使用第三方包进行任意组件通信的
- 思想是pubsub提供了一个对象作为中介,数据的传输方可以给这个中介发布某些固定名称的数据,数据的接收方可以在这个中介订阅某些固定名称的数据,一旦发布新的数据,则订阅方就会收到pubsub的通知,接收数据执行函数
- 发布:PubSub.publish(“name”,data)
- 订阅:PubSub.subscribe(“name”,fn)
-
组件通信–事件总线
-
思路
- 事件总线内部使用自定义事件来完成组件通信的,但是主要做的任意组件通信
- 我们找一个可以被绑定自定义事件的东西(vm),放在所有组件都能访问到的地方(Vue.prototype),起一个名字叫做$bus
- 在接收数据的地方的初始化中(mounted)中,给$bus绑定自定义事件,事件函数在当前组件内部
- 在发送数据的地方给$bus调用自定义事件并传值,则绑定事件的组件就会收到事件调用,并调用函数,最后完成了传值
-
$bus的绑定
- 我们在new Vue的配置中书写一个beforeCreate生命周期函数中书写
Vue.prototype.$bus=this
- 因为内部的组件会在mounted中的绑定事件,所以我们必须在他的mounted之前把$bus绑定上去,所以要在(beforeCreate,created,beforeMount中任选一个绑定即可)
- 我们在new Vue的配置中书写一个beforeCreate生命周期函数中书写
-
-
组件通信-默认插槽
- 在封装组件的时候,如果组件中某一块的结构,需要在组件使用的时候传递过来,我们就使用一个内置组件标签占位,这个 slot 就是插槽的位置
- 我们在使用组件的时候,如果要传递一个插槽,则直接在组件的双标签内部书写结构即可,组件内部的就会展示我们传递的结构
-
组件通信-具名插槽
- 我们封装组件的时候,可能需要被传递多个插槽结构,为了区分我们要给插槽命名,每一个 slot 标签上都可以书写一个 name 属性
- 在使用组件传递插槽的时候,要使用 v-slot:XXXX 形式来定义我们传递到哪一个插槽中
- v-slot 只能写在组件或者 template 标签上
- v-slot 可以简写为#: v-slot:footer --> #footer
-
组件通信-作用域插槽
- 在向组件传递插槽结构的时候,我们想要拿到组件内部的一些数据(不管这个数据怎么来的),这个时候我们就要使用作用域插槽了
- 在组件内部封装的时候,要使用 props 的形式向 slot 标签进行传递数据
- 在向组件插入结构的时候,通过 v-solt:xxx="obj"来接收组件内部传递回来的数据,其中 obj 是对象(包含所有传递过来的数据)
- 接收作用域数据的时候,支持解构,比如 v-slot:xxx=“{content}”
vuex
-
在项目中使用vuex(注入store)
- 下包
npm i vuex@3
- 在src中创建仓库文件
src/store/index.js
- 在仓库文件中 引入Vue和Vuex
- 使用Vuex插件
Vue.use(Vuex)
- 实例化store仓库
new Vuex.store({})
- 暴露这个仓库
- 在入口文件中引入store
- 在vm的配置项中书写一个store配置项,值为引入的store(store配置项会把接收的值以$store属性的方式注入到每一个组件上),这样每个组件就都可以访问store仓库了
- 下包
-
从store中读取值
- 方式1:直接在插值中使用 $store.state.xxx 即可
- 方式2:我们在计算属性中获取到某个值,在插值语法中直接拿到计算属性即可(比方式一更好维护)
- 方式3:使用mapState辅助函数
- mapState接受一个数组或者对象,返回一个对象
- mapState对象内部的方法其实就是我们去读取某个store的数据的 计算属性的方法
- 我们只需要把mapState得到的对象在computed中展开,则默认写好的所有的获取store的值的计算属性的方法
-
mutations修改state的值
- 首先在store的mutations配置对象中封装函数,mutations中的函数是修改state数据的唯一途径,所有每一种修改state的操作都会在mutations中的方法内进行
- mutations中的方法接受一个参数就是state对象,第二个参数是调用这个函数时传递的载荷(payload),我们建议传递载荷的时候使用对象
- 在组件内使用$store上的commit方法来调用mutations中的函数,commit方法接受两个参数,参数1是事件类型(mutations中的函数名),参数2是调用函数时传递的载荷(payload),也就是参数
- 在组件内使用commit方法调用mutations的函数
- 直接在插值中使用
$store.commit(type,payload)
- 在methods中封装函数,函数中写
$store.commit(type,payload)
- 直接使用mapMutations辅助函数(可以接受一个数组或者对象),其实就是帮我们得到一个包含调用mutations方法的一个对象 ,我们直接在methods中展开即可
- 直接在插值中使用
-
在actions中进行异步操作
- 在mutations也可以异步修改state,但是开发者工具不会监听到数据的变化,导致工具和视图的数据不一致
- 我们可以把异步获取数据或者异步操作放在actions中,然后异步操作结束之后,在actions内部提交给mutations对应的函数进行修改数据
- actions接受两个参数,一个是一个阉割版的store对象,内部有commit的方法可以提交mutations,另一个参数就是组件中分发actions时传递的参数payload
- 组件中调用actions的方法
- 直接在插值中使用
$store.dispatch(actionsName,payload)
- 在methods中封装函数
- 直接使用mapActions辅助函数(可以接受一个对象或者数据),其实就是帮我们获得一个包含分发actions方法的一个对象,我们直接在methods中展开即可
- 直接在插值中使用
-
在getters中书写vuex的计算属性
- 有时候我们需要从 store 中的 state 中派生出一些状态,需要使用vuex的getters
- vuex的getters其实类似于vue组件的计算属性
- getters内部的函数接受state作为参数,函数内部必须要返回一个值
- 组件中使用getters的值
- 直接在插值中使用
$store.getters.xxxx
- 在computed中封装函数
- 直接使用mapGetters辅助函数
- 直接在插值中使用
-
Vuex的模块化
- 如果多个功能的数据都保存在state中,则不好维护,数据及操作繁杂
- 我们可以创建多个vuex的模块保存不同的数据
- 过程
- 一个vuex模块其实就是暴露一个对象的文件,对象内部有自己的state mutations getters actions属性
- 在vuex的index中引入模块,并在new Vuex.store()的配置对象中使用modules属性认证模块即可
- 此时的模块只是模块化了state数据,并没有模块化getters mutations actions的东西
- 所以getters mutations actions正常使用,但是state需要
$store.state.模块名称.数据
获取
- 模块的优化
- 我们在暴露vuex模块对象的时候,添加一个属性namespaced:true(开启命名空间)
- 并且在使用辅助函数的时候,直接把辅助函数的添加第一个参数是当前要获取值的模块名称即可
- 如果不使用辅助函数,则在获取state数据的时候,要使用store.state.模块名.数据
- 如果不使用辅助函数,则在获取getters mutations actions的时候,只需要在他们的名字前添加模块名即可,比如’count/increment’
-
vuex数据的持久化
- 面试常问:页面一旦刷新数据会丢失怎么办?—数据持久化
- 手动数据持久化
- 定义一个初始的state的值,给state赋值的时候先去localStorage中读取值,如果没有则得到初始的state的值
- 在所有的mutations方法中,在改变state语句后 重新保存localStorage
- 自动数据持久化
npm install vuex-persistedstate --save
- 在store下的Vue.Store({})中使用插件plugins配置
plugins: [createPersistedState()]
-
使用常量来替代mutations的事件类型
- 可以在
mutation_types
的文件中定义所有模块的mutations事件类型,使用常量 - 在mutations中事件名引入定义常量(注意使用属性名表达式)
- 可以在
路由
-
history模式和hash模式对比
-
hash模式
- hash模式是一种把前端路由的路径用#拼接在真实URL后面的模式。
- #后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发hashchange事件,在hashchange事件中可以切换组件。
- hash模式的浏览器兼容性较好,就是看起来不够优雅。
-
history模式
- history模式用到了HTML5中的history API,允许开发者直接更新浏览器URL地址而不重新发起请求。
- 用到了history API:replaceState、pushState、back、forward和go这个5个方法。
- 如果使用pushState改变了地址,浏览器回退历史记录的时候,视图不会发生改变,我们可以使用window.popState事件监听浏览器历史记录的改变,从而改变视图
- history兼容性不如hash模式,而且浏览器在刷新的时候会按照路径发送真实的资源请求,如果没有额外的配置,则可能会返回404,因此在线上部署基于historyAPI的单页面应用的时候,一定要后端配合支持才行
- 后端配置:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面
-
-
初始化路由引入
- 安装路由
npm i vue-router@3
- 在
router/index.js
中引入Vue和VueRouter - 使用
VueRouter
插件 - 实例化一个VueRouter对象
new VueRouter({配置})
- 把VueRouter暴露出去
- 在入口文件中引入Router,然后在Vue的配置项中使用router选项把 r o u t e 和 route和 route和router注入到每一个组件实例上
- 安装路由
-
配置一级路由
- 在router的配置项中配置routes配置项,值是一个数组,数组包含多个路由映射关系配置对象,其中
path
代表要去匹配的路由地址,component
是展示的路由组件 - 路由组件一般会放在
pages
文件夹中,而components
文件夹一般用来放公共组件 - 我们需要在展示路由组件的地方书写一个
<router-view>
组件,这个组件专门用来渲染路径匹配到的视图组件
- 在router的配置项中配置routes配置项,值是一个数组,数组包含多个路由映射关系配置对象,其中
-
配置二级路由
- 在routes的某一个需要配置二级路由的某个一级路由的配置对象中 添加一个children属性,值是一个数组,数组中可以书写多个二级路由的对象配置
- 在一级路由展示的路由组件中 书写二级路由的占位组件
<router-view>
- 注意二级路由的path要么书写完整路径比如
/home/news
,要么值书写当前path比如:news
-
命名路由
- 有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。
- 可以在创建 Router 实例的时候,在 routes 配置中给某个路由通过name属性设置名称。
-
编程式路由导航
- 我们还可以借助 router 的实例方法
$router.push()
来进行定义导航链接 - 该方法的参数可以是一个字符串路径,比如
$router.push("/home/news")
- 或者一个描述地址的对象,对象内部有name和path属性可以控制导航到哪一个路由,path和name随意使用一种即可,path的值是完整的地址,name的值是路由的命名
- 编程式路由导航的其他方法
router.replace()
:和router.push一致,但是不会留下任何的历史记录router.back()
:历史记录回退1router.forward()
历史记录前进1router.go(n)
历史记录前进或回退n
- 我们还可以借助 router 的实例方法
-
编程式路由导航重复点击bug
- 编程式路由导航重复点击的时候会出现报错,报错的原因是:如果重复导航push或者replace则会返回一个失败的promise实例,但是push原本没有做任何处理,所以浏览器会报错,表示有失败的promise没有被处理
- 解决方式
- push可以接受第二个和第三个参数,分别是导航成功和导航失败的回调函数,如果我们书写了第三个参数处理导航失败(函数内容随意),则解决错误
- 如果push没有书写第二个和第三个参数,则push会返回一个失败的promise,我们可以使用catch(()=>{})来处理这个错误即可解决
- 最终写法,我们可以重写
VueRouter.prototype.push
方法,内部让原始的push都添加上第三个回调函数即可
-
声明式路由导航
- 声明式路由导航,使用router-link组件
- 其实router-link组件内部也是使用的router.push进行导航
- 其中:to属性接受的参数就是push方法接受的location参数,用来控制跳转
- 其中:tag属性有时候想要 渲染成某种标签, 我们使用 tag 指定何种标签,同样它还是会监听点击,触发导航
- 其中:replace:设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录。
- 其中:active-class:设置链接激活时使用的 CSS 类名
-
切换路由模式
- 在router的配置中使用mode配置项,设置为history即可切换为history模式
-
路由重定向
- 在一级路由中,我们可以使用redirect属性进行重定向路由
- 在二级路由中,我们也可以使用redirect属性进行设置默认子路由(直接写在一级路由上设置默认子路由.或者写在二级路由配置中新增一个空的规则,然后重定向)
-
Vue的动态组件
- Vue提供了一个组件,可以用来做动态组件
- 是一个占位符,接受一个is属性,is属性的值是一个 字符串类型的已经被引入和注册的组成的名称,并且is必须要求强制绑定才可以
- 我们可以通过改变is强制绑定的值,来切换替换的组件
-
Vue动态组件的keep-alive
<keep-alive>
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们<keep-alive>
默认缓存所有动态组件- 属性:
include
和exclude
prop 允许组件有条件地缓存include和exclued
写法1逗号分隔字符串:<keep-alive include="Count,Movie">
include和exclued
写法2数组:<keep-alive :include="['Count']">
include和exclued
写法3正则表达式:<keep-alive :include="/Count/g">
-
keep-alive的生命周期函数
- activated:被 keep-alive 缓存的组件激活时调用。
- deactivated:被 keep-alive 缓存的组件失活时调用。
-
在路由中使用keep-alive
- 路由的切换原理也是动态组件
- 所以路由的router-view组件外也可以嵌套keep-alive组件,并可以使用keep-alive的属性及生命周期
-
import方法动态引入模块
- 如果我们想要动态(异步)的引入某个模块,我们需要使用import方法
- import方法返回一个promise对象,用来监听import引入资源的状态,promise的值就是当前的模块
- 它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块
-
vue的异步组件
- 我们如果直接使用import模块化引入vue模块,则webpack在打包构建的过程中,会把所有的js都打包到了一起,但是里边包含了很多我们暂时没有使用的模块,这样包的体积过大,就会造成进入首页的时候需要加载的内容过多,出现长时间的白屏现象
- 我们可以使用异步组件,让webpack打包的时候把异步组件进行分开打包,需要的时候再去加载这个组件
- 异步组件的设置
- Vue 允许你以一个函数的方式定义组件,函数内部使用import方法引入某个模块
- import方法返回promise实例,当import引入成功则promise变成状态,import失败则返回失败的promise实例
- 定义组件的函数需要把import的返回值进行返回,让组件在渲染的时候能够知道渲染状态,及拿到引入的模块
- Vue 只有在这个组件需要被渲染的时候才会触发该函数,且会把结果缓存起来供未来重渲染
- wabpack在打包的时候,遇到import动态引入,则会把import动态引入的资源进行单独打包
-
路由懒加载
- 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
- 结合 Vue 的异步组件 和 Webpack 的代码分割功能 ,轻松实现路由组件的懒加载
- 路由懒加载:路由组件的异步加载(路由组件被单独打包,并且被需要的时候才去加载这个文件)
-
动态路由
- 我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染
- 因为组件一样,但是组件内部要区分对应路由是哪个,所以我们可以在 路由路径中使用“动态路径参数”来达到这个效果
- “动态路径参数”可以有query和params两种写法
-
动态路由的—query传参
- 可以在路由导航的时候传递查询字符串参数
-
- 直接在路由字符串上拼接查询字符串即可
-
- 可以在to为location对象写法时候添加query属性为一个对象,对象内部就是query参数
-
- 在路由组件中通过$route.query来拿到接受的query传参
- 可以在路由导航的时候传递查询字符串参数
-
动态路由的—params传参
- 可以在路由导航的时候在路由后添加params参数
- 必须要在路由配置的path中去书写接受params参数的属性名,格式是
/:xxx/:xxx?
- 在组件中使用
$route.params
来接受params参数 - 注意事项:
- 如果在导航的时候使用location对象形式和params配合使用,则不能使用path导航,并且可选的参数不能是一个空串(要么书写一个null/home/news/study/老王/9999123)
-
路由组件传参
- 除了动态路由可以向组件内部传递params和query参数以外,我们也可以在路由表中使用props属性给某个特定的路由传递参数
- props是一个对象:在组件内部就能通过props属性接受到props对象中传递的数据
- props是布尔值为true:会把当前路由规则接受的params参数通过props的形式传递给组件内部
- props是一个函数:当当前的路由规则被匹配的时候,props函数就会执行,props函数接受当前路由的$route作为参数,props函数的返回值对象中的属性 就是将来在组件内部能够通过props属性获取的值
-
路由的元信息
- 定义路由的时候可以配置 meta 字段
- meta一般是一个对象,meta的数据其实就是路由的元信息(描述当前路由的一个固定信息)
- 我们可以在组件内部通过 $route.meta得到元信息的内容
- 我们可以在组件的外部 通过遍历 routes路由表,得到每一个组件的元信息
-
路由导航守卫
- 全局导航守卫
- 全局前置守卫:router.beforeEach(),所有导航都会最先触发
- 全局解析守卫:router.beforeResolve():在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用(了解)
- 全局后置钩子:router.afterEach()导航已经全部完成,没有next参数
- 路由独享守卫
- 独享守卫:设置在路由表的规则对象中,beforeEnter(),针对某一个路由进行守卫
- 组件内守卫
- 组件内进入时守卫:beforeRouteEnter()刚刚进入当前组件,但是在渲染当前组件前就调用(当前没有this)
- 组件内更新时守卫:beforeRouteUpdate()在当前路由改变,但是该组件被复用时调用
- 组件内离开时守卫:beforeRouteLeave()导航离开该组件的对应路由时调用
- 守卫钩子函数的参数
- to:即将要进入的目标 路由对象
- from :当前导航正要离开的路由
- next: 一定要调用该方法,来确认守卫后导航下一步的行为(1.参数是空代表放行 2:参数是一个地址字符串,代表重新导航到该地址)
- 全局导航守卫
-
路由导航守卫的顺序
- 某个导航被触发
- 先调用失活的组件(离开的组件)内部的
组件内离开时守卫
(如果是动态路由的params参数更新,则触发组件内更新守卫
) - 调用
全局前置守卫
- 调用某个路由的
路由独享守卫
- 调用 组件内的
进入组件时守卫
- 调用
全局解析守卫
- 调用
全局后置钩子
-
监听动态路由的改变
- 动态路由是组件已经复用了,但是传递的动态参数可能会发生变化,我们需要监听动态路由的改变
- 使用组件内路由更新时守卫(beforeRouteUpdate):
- 这个守卫有限制,首先组件复用的时候路由的地址是不变,只有动态参数发生变化,这个时候守卫才会触发
- 守卫中直接获取 r o u t e 的 值 , 还 是 旧 值 , 我 们 需 要 通 过 守 卫 钩 子 的 参 数 t o , 拿 到 最 新 的 route的值,还是旧值,我们需要通过守卫钩子的参数to,拿到最新的 route的值,还是旧值,我们需要通过守卫钩子的参数to,拿到最新的route
- 使用watch监听$route
- 不需要深度监听,因为每次得到的都是一个新的$route对象
- 在watch的回调函数中拿到的就是最新的$route
响应式原理
-
数据代理
- 我们给Vue实例或者组件实例设置的data数据,默认被放在了实例对象的_data属性中了,我们想要操作这个数据的时候都需要添加_data
- 为了更加方便的操作实例上的_data中的数据,我们可以把数据遍历在实例上放一份,但是其实操作实例上的数据,其实就是操作_data中对应的数据
- 过程
- 遍历_data的数据
- 使用Object.defineProperty的方法给实例扩展一个同名的属性名
- 属性值使用getter和setter存取器属性
- 当读取属性的时候,使用getter方法,返回_data的属性值
- 当设置属性值的时候,使用setter方法,设置_data的属性值
-
数据劫持
- 目的:(收集)为了收集获取当前数据的模板的信息,(通知)为了通知收集的所有的模板进行更新数据
- 原理:Object.defineProperty
- 过程
- 遍历_data中的数据,得到属性和值
- 重写_data上的属性,并且书写为存取器数据
- 当取的时候直接返回当前属性的值,并收集当前的模板信息
- 当设置的时候修改当前属性的值,并通知所有的模板进行重新获取最新的值
-
设计模式的观察者模式
- 当一个对象A中的数据被多个对象所依赖
- 并且当被依赖的对象发生改变的时候,会去通知所有的依赖项,我们就把这种觅食称作为观察者模式
- 其中被依赖的对象我们称作目标对象,依赖项我们称作为观察者
-
设计模式的订阅与发布者模式
- 订阅与发布者模式其实是属于观察者模式中的一个细分
- 观察者模式中的观察者被称作为 订阅者,观察者模式中的目标对象被称作为发布者
- 订阅者和发布者中间有一个统一的调度中心(订阅中心)来实现数据数据的通信, 并且订阅者和发布者都是不知道对方的存在的
- 当订阅者订阅数据的时候,来到订阅中心,订阅中心收集所有的订阅者
- 当发布者发布信息的时候,先传递给订阅中心,然后再由订阅中心统一传递给订阅者
-
Vue响应式数据原理总结(响应式原理,数据双向绑定原理)
- 响应式数据原理的意思:当data中的数据被修改的时候视图会随之发生更新
- 主要由3个方面构成
- 数据代理
- 数据劫持
- 发布与订阅者模式
- 详细过程
- 使用Object.defineProperty完成数据代理,把vm的_data上的数据在vm身上代理一份,我们访问或者设置vm上的数据的时候,其实就是访问或者设置vm._data中的数据
- 有一个Observer类(就是发布订阅者模式中的发布者),主要是通过Object.defineProperty给vm的_data上的数据所有层级的属性都重写为getter和setter存取器属性,在getter中建立了dep(订阅中心的实例化对象)和watcher(订阅者的实例化对象)的关系,让dep收集访问当前数据的watcher,在setter中通知所有的watcher进行重新获取数据
- 有一个Dep类(发布订阅模式中的订阅中心,中间人),Dep上有收集所有watcher的方法,和通知所有watcher重新获取数据的方法,数据中每一层需要劫持的属性对象在劫持的时候,会创建一个dep对象(Dep的实例化对象),在劫持的getter中调用dep的收集依赖的depend方法,在setter中调用dep的通知更新的notify方法
- 有一个Watcher类(发布订阅模式中的订阅者),Watcher身上有获取数据的get方法和更新视图的update方法,每一个组件都对应一个Wathcer,Watcher会在第一次获取数据的时候被dep收集到数组中,当dep收到更新要求的时候,dep就会通知所有的watcher实例调用update方法重新获取数据并更新视图