三者都是项目的架构模式(不是类的设计模式),即:一个项目的结构,如何分层,不同层负责不同的职责。
1、MVC:
MVC的出现是用在后端(全栈时代)
M:model,模型:
主要完成业务功能,在数据库相关的项目中,数据库的增删改查属于模型(重点)。没有页面,是纯粹的逻辑
V:view,视图:
主要负责数据的显示(HTML+CSS,动态网页(jsp,含有html的php文件))页面的展示和用户的交互。
C:controller,控制器:
主要负责每个业务的核心流程,在项目中体现在路由以及中间件上(nodeJS中的routes文件夹)
2、MVP
MVP是把MVC中的C改成了P。主要限制了M和V之间不能直接通信(互相调用,传递数据)。M和V之间的通信必须经过P。
P:Presenter,表示器
主要用于连接M层、V层,完成Model层与View层的交互,还可以进行业务逻辑的处理。
3、MVVM:
MVVM是把MVP中P改成了VM。主要体现的是M和V之间的双向绑定。View的变动可以同步响应在Model,反之亦然。Vue就是一个MVVM的框架。准确来说,使用Vue框架完成项目时,使用的是MVVM模式。
VM:ViewModel
主要完成M和V的数据通信,并且是双向绑定。而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
总结:所有MV*的项目的架构模式:都是为了完成项目代码的职责分工。
相同点:
都是用来控制dom元素的显示和隐藏。
不同点:
1、原理上:
v-if是 通过 添加和删除dom元素,来 控制dom元素的显示和隐藏。
v-show是 通过 控制dom元素样式的display属性的值,来 控制dom元素的显示和隐藏。
2、性能损耗
v-if:性能损耗主要体现在频繁切换时
v-show:性能损耗主要体现在首次。
3、应用场景:
v-if:用于切换不频繁的场景。
v-show:用户切换频繁的场景。
4、安全性:
v-if:安全性好。(如果dom元素不显示时,在elements里根本看不到)
v-show:安全性不好。(如果dom元素不显示时,在elements里依然可以看到,那么,懂程序 的人,即可以修改)
1、相同点:
都可以监听数据。
2、不同的:
1)、概念:
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,当依赖的属性值发生改变时,才会重新计算 computed 的值,默认是只读的(相当于getter函数),它也可以设置getter和setter函数来完成读和写。
watch:监听器,更多的是观察的作用,每当监听的数据变化时都会执行回调进行后续操作,它只能设置getter。watch默认只监听一层。如果要深度监听,让deep属性为true。
2)、作用:
computed:是为了显示而用,降低了模板上代码复杂度。
watch:属性变化的检测(相当于事件),当属性的值发生变化时,可以调用函数。主要处理异步以及开销比较大的操作。简单粗暴的理解:一个函数的执行时机时机
3)、依赖模板调用:
computed:只能在模板上使用。
watch:不能在模板上使用。
4)、是否异步:
computed:不能有异步,只能同步。
watch:可以有异步。
5)、立即执行:
computed:是立即执行。
watch:默认不是立即执行。如果要立即执行,增加属性:immediate。
1、以后代码时,把函数定义和调用时机分开。
响应式:当js中的数据发生变化时,模板上会对应的发生变化。
1、数据劫持:
目的:当时数据发生变化时,vue框架能够感知到(劫持到)
怎么做的:利用ES5的Object.defineProperty()。当实例化组件时,vue框架内部会把定义在组件里data的所有数据(属性),进行遍历,给每个数据(属性)增加setter和getter函数【同时会做订阅】。当数据发生变化时,会调用setter函数。当获取数据时,会调用getter函数。
2、发布订阅者模式:
目的:当数据发生变化(其实就是调用setter函数)时,会发布给所有订阅者。
怎么做的:当vue组件实例化时,vue框架内部会扫描(阅读)模板,让模板上使用vue语法(指令,{{}}等)的dom元素去订阅对应的数据的变化。然后,当数据发生变化时【调用setter】,会更新所有订阅该数据的模板。
1、数据劫持:用Proxy替换了 Object.defineProperty。Proxy不需要循环,速度比Object.defineProperty快10倍。同时也解决了数组(vue2中用下标的方式改变数组不是响应式的)
2、发布订阅模式:
内部有个Track函数:相当于订阅。
内部有个Trigger函数:相当于发布。
1、双向绑定:视图层变化时,模型层会变化。模型层变化时,视图层会变化。
2、原理: 1)、通过事件和属性完成。
事件完成的:视图层影响模型层
属性完成的是:模型层影响视图层(背后就是响应式原理)
3、vue针对官方标签使用v-model指令,v-model指令针对不同的官方标签使用不同事件和属性。
1)、针对文本框(单行和多行):value属性和input事件。 如果加上修饰符 lazy。事件变成了change事件。
2)、针对radio:使用的checked属性和change事件。同时,需要给radio加上value属性。
3)、针对checkbox:使用的checked属性和change事件。
3.1)、如果应用在多选时,需要给checkbox加上value属性。
3.3)、如果应用在单选时,不需要加。
4)、针对select:使用value属性和change事件。
1、单向数据流是什么:
单向数据流是指父组件可以修改子组件的数据,反之不行(子组件不能修改父组件的数据)。
2、vue框架的单向数据流:
vue框架的props是单向数据流。即:父组件可以修改子组件的props。子组件不能通过修改props的方式来修改父组件的数据(data),否则:
1)、造成数据混乱:如:一个父级组件的数据传递给多个子组件,某个子组件如果通过props修改的父级组件的数据,父级组件再修改其它子组件,其它子组件就会莫名其妙的被修改,造成数据混乱
2)、框架会给程序员报出一个警告(特指:父组件给子组件传递的props来自于父组件的数据)。
3、当父级的数据更新时,子组件的props也会随之更新。
keep-alive是vue提供的组件。
它有三个特性:
1)、组件作用:keep-alive 可以缓存组件及其状态(数据),避免了组件的频繁创建和销毁所带来的性能损耗。,一般结合路由和动态组件一起使用。
2)、组件属性:提供 include 和 exclude 属性。include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高。两者都支持字符串或正则表达式
这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组: 它会根据组件的 name 选项进行匹配,所以组件如果想要条件性地被 KeepAlive 缓存,就必须显式声明一个 name 选项。 //以下代码:表示缓存组件:MyFooter和MySearch
3)、keep-alive会触发两个钩子函数: activated 和 deactivated 。当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
1)、单页面应用的概念
SPA:single page application,单页面应用。
就是整个项目就只有一个html页面(文件),首次加载时,把所有的html,css,js全部加载下来。通过操作dom的删除和创建(添加)来完成页面的切换。
2)、单页面应用优缺点
优点:
1、单页应用相对服务器压力小。【因为:首次、或者只要HTML,CSS和JS加载完毕后,切换页面是不用再去服务器请求HTML,CSS和JS,而是直接操作DOM】
2、局部刷新,所以,用户体验好。【通过删除、添加、修改DOM的方式】 3、前后端分离 4、页面效果会比较炫酷(比如切换页面内容时的转场动画)
缺点:
1、不利于 SEO(Search Engine Optimization)。如:百度,360等搜索引擎收录。 2、初次加载时耗时多(可以使用路由懒加载解决) 3、导航不可用,如果一定要导航需要自行实现前进、后退(vue-router做好了)。页面复杂度提高很多
4、容易造成CSS命名冲突。【用scoped或者BEM的方式解决】
1、什么是虚拟dom和diff算法:
虚拟DOM: 用JS对象模拟的真实DOM,该js对象包含了真实DOM的所有属性和内容,
diff算法:用来比较两个虚拟DOM的不同之处,并在旧的虚拟dom树上打上标记。
2、虚拟DOM和diff算法的作用:
虚拟DOM和diff算法结合起来,用来提升性能。可以减少无效的DOM渲染,即:减少了无效的重排和重绘。
3、步骤(思路,流程)
1)、产生两个虚拟DOM树:newVDom,oldVDom。
2)、oldVDom和真实DOM保持一致
3)、数据变化时,影响的是(操作的是)newVDom
4)、操作newVDom后,通过diff算法对比newVDom和oldVDom的差异,并在oldVDom标注哪些节点要删除,哪些节点要增加,修改
5)、根据oldVDom(上的标记)操作真实的DOM,让真实Dom和oldVDom保持一致
4、diff算法的解释:
逐步解析newVdom的节点,找到它在oldVdom中的位置,
1)、如果找到了,则对比两个节点。
1.1)、两个节点内容不相同,那就打上修改标记,然后移动到下一个DOM元素。
1.2)、两个节点内容相同,直接移动到下一个的DOM元素,
2)、如果没找到,说明是新增节点,则新建一个节点插入到oldVDom。
3)、遍历完成之后如果oldVdom中还 有 没处理过的节点,则说明这些节点在newVdom中被删除了,打上删除标记。
第一个方面:代码层面的优化 v-if 和 v-show 区分使用场景 computed 和 watch 区分使用场景 v-for 遍历必须为 item 添加 key,且避免同时使用 v-if 长列表性能优化 事件的销毁 图片资源懒加载 路由懒加载 第三方插件的按需引入 优化无限列表性能 服务端渲染 SSR or 预渲染 第二个方面:Webpack 层面的优化 Webpack 对图片进行压缩 减少 ES6 转为 ES5 的冗余代码 提取公共代码 模板预编译 提取组件的 CSS 优化 SourceMap 构建结果输出分析 Vue 项目的编译优化 第三个方面:基础的 Web 技术的优化 开启 gzip 压缩 浏览器缓存 CDN 的使用 使用 Chrome Performance 查找性能瓶颈
1)、vue中使用了虚拟dom和diff算法,在使用diff算法进行对比两个虚拟dom树时,是通过标签名和key来标识一个元素的,这样才能区分(识别)不同的元素,提高对比的效率。所以,并不是说只是在v-for中使用key,是任何时候,如果希望唯一标识一个元素,那么,都可以使用key。
2)、vue在做过渡效果时(
,如果两个切换的元素标签一样,而么有使用可以,那么,vue会认为是同一个元素,而不会出现切换效果。
1)、如果没有key,vue只能用标签名区分不同的DOM元素,如果两个DOM元素的位置进行了交换时,vue只会交互内容,而不会交换两个DOM元素。如下示例代码,仔细品读:
Document 书籍列表:
- 书名:{{book.name}}
简单回答:如果不是函数,那么,复用的组件的data共享同一块内存空间。
具体解释:组件的data必须是函数,而且要有返回object(就是vue2对象的data)。当组件复用时,会自动调用该函数,这样的话返回的新对象就是新开辟的空间。这样就保证了每个组件的data是独立的空间。而不会互相影响。即:组件的作用域(应该)是独立的。
1)、父子组件传值:
1)、父---子传:props,ref。vuex,pinia,provide 和 inject
……………………要说详细
2)、子-->父传:emit,vuex,pinia
……………………要说详细
2)、兄弟组件:
1)、子1---》父---》子2
2)、事件总线(event-bus)
原理:使用vue对象的$on和$emit完成。
具体做法:假设SonA给SonB传递数据。新建一个空的vue对象。在SonA里和SonB引入该vue对象。。
$emit:触发事件(在SonA)
$on:绑定事件(在SonB)
3)、vuex,pinia
3)、跨级:
provide 和 inject
vuex,pinia
1、vue-router有两种传参方式:params和query。
2、params
首先,需要在路由配置中使用动态路由匹配 {path: "/路径/:参数名",name:"路由名"}
1)、传【跳转时传】
//声明式: 1)、字符串写法2)、对象写法 //编程式: 同上。
2)、接【路由的组件内部接收】
this.$route.params.参数名
3、query
1)、传
//声明式 //1)、字符串写法://2)、对象写法: //编程式: 同上。
2)、接
this.$route.query.参数名
4、使用场景:
1)、params:多用于传递单个参数
2)、query:在传递多个参数时,建议使用query。
1)、$router是vue-router对象,是创建的vue-router对象,该对象具有路由相关api,如:push(),replace,go(),back(),forward。
2)、$route 是 匹配到的路由对象。当地址栏的路径发生变化时,会匹配某个路由配置。然后,vue-router对象就会在组件上去产生一个$route对象,来保存匹配到的路由的相关信息,包括:传递的参数,路由元信息等(如:path,params,query等等)
1、外观上的区别:
1)、hash有#
2)、history没有#
2、原理上的区别:
1)、hash用的是锚点连接。背后使用location.href和window.hashchange事件。锚点连接自带历史记录。
2)、history背后用的是 history.pushState 来记录页面的历史记录。
3、跟后端有关的区别:
1)、hash和后端没有关系。
2)、history和后端有关系。当地址栏发生变化后,浏览器默认会发送请求给服务器(服务器上的资源:html,css,js,api接口,后端渲染的页面,等等),所以,当前后端不分离时:需要保证前端路径和后端的api或者后端渲染的页面不要重名。另外,前后端分离,前端服务器需要配置一个 针对404页面时,返回index.html。
1、vue组件(实例)的生命周期是:
一个vue组件(实例)从创建,挂载,更新,销毁的整个过程。
2、vue组件(实例)的生命周期有:
四个阶段和八个钩子函数:
1)、创建阶段(数据挂载阶段):做响应式的处理和依赖注入,具体而言:是把data配置项的所有数据挂载到vue组件(实例)上,并做响应式。
在这个阶段的前后会分别调用:beforeCreate和created。
beforeCreate钩子函数里,不能通过vue组件(实例)拿到数据
created钩子函数里,vue组件(实例)上可以拿到数据
2)、模板渲染阶段:把数据渲染到模板上。
在这个阶段的前后会分别调用:beforeMount和mounted。
beforeMount钩子函数里,模板还没有渲染(模板上的内容还是就是程序员写的代码)
mounted钩子函数里,模板已经渲染(模板上的内容就是用户看到的)
3)、模板更新阶段(组件更新):当数据发生变化时,把新的数据渲染到模板上。
在这个阶段的前后会分别调用:beforeUpdate和updated。
beforeUpdate钩子函数里,数据是新的,模板是旧的
updated钩子函数里,模板是新的(模板已经被更新了)
4)、组件销毁阶段:卸载 组件里的watch、子组件、事件监听
强调:组件销毁阶段并不是把vue组件(实例)从内存中释放。
在这个阶段的前后会分别调用:beforeDestory(Vue3是beforeUnmount)和destoryed(vue3是unMounted)。
3、生命周期的使用场景:
1)、created:初始化数据、发送请求等操作在created里调用。
2)、mounted:也是可以发送请求的,如果有些初始化操作需要使用dom,那么,必须放在此处。
3)、beforeDestroy(beforeUnmount):清除定时器,清除事件总线等全局性的数据。
补充:
如果使用了keep-alive包裹组件,那么会经历两个钩子函数:activated,deactivated。组件就不会销毁了。既就是不会调用beforeUnmount和unMounted。
1、初始阶段:
父beforeCreate----》父created---》父beforeMount----》子beforeCreate---》子created--》子beforeMount--》子mounted --》父mounted。
2、更新阶段:
当父组件 数据传递给子组件了,当父组件的数据更新时,子组件也就会更新。
父beforeUpdate---->子beforeUpdate--->子的updated--->父的updated
3、销毁阶段:
父beforeUnmount---->子beforeUnmount--->子的unMounted--->父的unMounted
1、什么是路由守卫
控制组件的跳转,对是否能够进入某个路径对应组件做限制。根据业务逻辑来判定是否可以进入某个组件。
什么时候使用路由守卫:
当进入某个路径,会有限制时。就需要使用路由守卫。
当进入路径时,需要完成通用的业务,也可以使用路由守卫,特别是全局路由守卫
2、路由守卫有哪些分类
1)、全局守卫
1)、前置钩子:beforeEach,当地址栏的路径发生变化时,会先调用该钩子函数,再进行进行路由匹配(路由匹配之前调用)。
2)、后置钩子:afterEach,当路由匹配成功后,先调用该函数,然后才创建组件(路由匹配成功之后)。
2)、路由独享守卫
1)、只有前置:beforeEnter:当匹配到某个指定的路由后,会先调用该函数,然后再创建组件。
3)、组件内部守卫
1)、前置:beforeRouteEnter:当路由匹配成功后,进入组件前,先调用该函数。
2)、路径更新,组件复用:beforeRouteUpdate: 当地址栏路径发生变化,但是进入的组件和上一个组件是同样的情况下,会调用先该函数。如:
3)、离开:beforeRouteLeave:当通过路由的方式离开某个组件前,会调用该函数。
3、路由钩子函数的参数:
to:想去哪个路由,to和$route是同样的对象
from:来自哪个路由对象,from和$route是同样的对象
next:下一步何去何从。
next(true):默认就是true,表示继续前行
next(false):不能前行
next(路径字符串或者对象):跳转到指定的路径。
1、vuex是什么
vuex是一个状态(数据)管理工具,它能够完成组件之间的数据共享(组件的通信)
2、vuex的作用
1)、vuex能够保存全局数据(数据仓库),供整个应用使用
2)、vuex保存的数据是响应式的
3)、vuex保存的数据可以跟踪状态的变化
3、vuex的(核心概念)配置项:
1)、state : 数据仓库 ,存储所有的 共享数据 ,相当于vue组件里的data 2)、Getters : 在state的基础上 派生的数据, 相当于vue组件里 computed 3)、Mutations:修改state的数据时,用mutation,这与跟踪状态 有关系,只能有同步代码 4)、Action:解决mutation里只能有同步代码的问题,action里可以有异步代码
5)、modules:模块化
4、vuex的数据流转
vue组件 派发(dispatch)action。action里提交(commit)mutation,mutation里修改(mutate)state的数据,state的数修改后,会响应式渲染到模板上。
5、模块化怎么使用。
1)、当项目比较大时,所有的全局数据存放在state里,会非常混乱,怎么办?使用module,把数据分门别类的进行处理,即:模块化。 每个模块是一个独立的store。然后由总体的store引入所有的分模块store。
2)、怎么解决(getters,mutations,actions)的重名
1)、
namespaced:true
2)、使用模块中getters,mutations,actions时,前面需要加上模块名:
格式:
模块名/getters或者mutations或者actions的名字
6、辅助函数:
mapState, mapGetters,mapMutations, mapActions
1)、作用:
简化代码:在组件里不需要使用$store 了。
2)、具体使用:
mapState, mapGetters 会映射到组件的computed上
mapMutations, mapActions 会映射到组件的methods里。
一、相同点:
都是用于vue项目开发的状态管理工具
二、不同的:
1、根仓库:
vuex只有一个跟仓库
pinia可以有多个根仓库
2、配置项:
vuex常用的配置项有:state,mutations,actions,getters,modules
pinia常用的配置项有:state,actions,getters,plugins。没有smutations和modules
3、组合式api支持
vuex的组合式api支持的不彻底
pinia里完全支持vue3的组合式api,如果使用组合式api,那么,就不需要使用配置项了。
三、可以再补充细节的区别,查官网。
1)、异步更新队列的目的:
目的是提高性能,避免无效的重复的DOM更新。即:vue中更新数据后,并不会立即更新DOM,而是把数据引起的DOM更新放入到异步更新队列(去重了)里。等待下次事件循环(tick),并在两个tick之间进行UI渲染。这样程序员就不能在更改数据后,立即获取更新后的DOM,也不知道什么时候DOM能够更新。基于此,vue提供了nextTick函数。程序员把操作更新后DOM的代码放入到nextTick的回调函数里。由nextTick内部,在更新完DOM后,调用回调函数。
2)、异步更新队列的原理
vue更新DOM的思路。使用的就是异步更新队列,异步的实现使用了事件循环。使用如下API。
MutationObserver:这是HTML5新增的API。用于监视DOM变动的接口,它可以监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等
另外,考虑到,微任务比宏任务耗时少,浏览器的兼容性。所以,vue中延迟调用优先级如下: Promise > MutationObserver > setImmediate > setTimeout
1)、为什么用Vue.nextTick()
由于vue在状态被修改后,对应的UI渲染是异步的【内部使用了异步更新队列】,而在写vue的代码时,如果希望在状态修改后,使用新的dom时,那么,我们没有办法知道什么时候dom被更新了。vue框架为了适应这种场景,提供了Vue.nextTick()
其实你这样理解的话,更清楚:
首先,要知道,updated钩子函数,是在任何状态发生变化时,都会调用的钩子函数。此钩子函数里拿到的就是最新的dom。
其次,$nextTick(); 是某个数据引起的updated,而不是所有数据引起的updated。
2)、怎么使用。
在数据修改后,立即调用 Vue.nextTick()函数。给nextTick()函数传入回调函数。在回调函数里就能拿到最新的代码。【vue会在dom更新后调用 $nextTick传入的回调函数的代码】
this.msg = "hello" this.$nextTick(()=>{ 操作更新后DOM的代码。 });
1、反向代理解决的问题:跨域
2、反向代理的原理:
把请求发到代理服务器(这个是同源),然后,由代理服务器请求真正的后端api服务器。浏览器看到的是同源,所以,不会出现跨域问题。
3、代码:
1)、代码写在何处:
vite脚手架:vite.config.js ----》server -----》proxy。 vue-cli脚手架:vue.config.js ---> devserver --->proxy。
2)、代码的关键点:
2.1)、请求地址的开始标识。
2.2)、目标路径(真正的后端api服务器地址)
2.3)、是否去掉开始标识。
4、补充一下:如果上线后,需要在nginx配置。