总结一下vue中的高频面试题,平时看到的知识比较散,自己正好也需要汇总一下自己的知识盲点,为接下来的面试做准备。
SSR SPA和MPA
Server-Side Rendering
我们称其为SSR
,意为服务端渲染。
在普通的SPA中,一般是将框架及网站页面代码发送到浏览器,然后在浏览器中生成和操作DOM
(这里也是第一次访问SPA网站在同等带宽及网络延迟下比传统的在后端生成HTML发送到浏览器要更慢的主要原因),但其实也可以将SPA应用打包到服务器上,在服务器上渲染出HTML,发送到浏览器,这样的HTML页面还不具备交互能力,所以还需要与SPA框架配合,合理地运用SSR技术,不仅能一定程度上解决首屏慢的问题,还能获得更好的SEO。
由服务侧完成页面的 HTML
结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。
Vue SSR
是一个在SPA
上进行改良的服务端渲染- 通过
Vue SSR
渲染的页面,需要在客户端激活才能实现交互 Vue SSR
将包含两部分:服务端渲染的首屏,包含交互的SPA
主要解决了以下两种问题:
- seo:搜索引擎优先爬取页面
HTML
结构,使用ssr
时,服务端已经生成了和业务想关联的HTML
,有利于seo
- 首屏呈现渲染:用户无需等待页面所有
js
加载完成就可以看到页面视图(压力来到了服务器,所以需要权衡哪些用服务端渲染,哪些交给客户端)
SPA是单页面应用,而MPA是多页面应用,single-page
and multi-pages
SPA仅有一个主页面,通过动态重写主页面来与用户实现交互;
MPA有多个主页面,当访问另一个页面时,要重新加载css等文件。
单页面与多页面的对比
单页应用优缺点
优点:
- 具有桌面应用的即时性、网站的可移植性和可访问性
- 用户体验好、快,内容的改变不需要重新加载整个页面
- 良好的前后端分离,分工更明确
缺点:
- 不利于搜索引擎的抓取:
SPA是客户端渲染,通过加载执行JS来创建DOM元素构建页面,但是爬虫只是请求静态资源,不会执行JS文件,所以抓取不到DOM结构,也分析不出来有用的信息 - 首次渲染速度相对较慢:
用户首次加载需要先下载SPA框架及应用程序的代码,然后再渲染页面。
总结:如何给SPA做SEO:SSR
前后端分离降低了前端和后端的耦合度,提高了开发效率;
SPA是前后端分离中前端的一种解决方案;
SEO对与很多网站很重要而普通的SPA又不利于SEO;
SSR的出现一定程度上解决了SPA中首屏慢的问题,又极大减少了普通SPA对于SEO的不利影响。
v-show和v-if
两者作用都是控制元素的显隐
- 当表达式为
true
的时候,都会占据页面的位置 - 当表达式都为
false
时,都不会占据页面位置\
不同之处:
-
控制手段不同
v-show
隐藏则是为该元素添加css--display:none
,dom
元素依旧还在。v-if
显示隐藏是将dom
元素整个添加或删除 -
编译过程不同
v-if
切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show
只是简单的基于css切换 -
编译条件不同
v-show
由false
变为true
的时候不会触发组件的生命周期。
v-if
由false
变为true
的时候,触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由true
变为false
的时候触发组件的beforeDestory
、destoryed
方法。
总结:
v-if
相比 v-show
开销更大的(直接操作dom
节点增加与删除);如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
v-if和v-for哪个优先级更高
- 在
Vue 2
中,v-for
优先于v-if
被解析;但在Vue 3
中,则完全相反,v-if
的优先级高于v-for
。 v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回true
值的时候被渲染。v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用item in items
形式的特殊语法,要设置key
值,并且保证每个key
值是独一无二的,这便于diff
算法进行优化两者在用法上
注意:1. 把 v-if
和 v-for
同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)
要避免出现这种情况,则在外层嵌套template
(页面渲染不生成dom
节点),在这一层进行v-if判断,然后在内部进行v-for循环
key的作用?
-
key的作用主要是为了更高效的更新虚拟DOM。
-
diff 算法需要比对虚拟 dom 的修改,然后异步的渲染到页面中,当出现大量相同的标签时,vnode 会首先判断 key 和标签名是否一致,如果一致再去判断子节点一致,使用 key 可以帮助 diff 算法提升判断的速度,在页面重新渲染时更快消耗更少。
实际使用中在渲染一组列表时key必须设置,而且必须是唯一标识。
NextTick
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
Vue
在更新 DOM
时是异步执行的。当数据发生变化,Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新。如果我们一直修改相同数据,异步操作队列还会进行去重
等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行DOM
的更新。
举个例子
```
for(let i=0; i<100000; i++){
num = i
}
```
如果没有 nextTick
更新机制,那么 num
每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick
机制,只需要更新一次。
如果想要在修改数据后立刻得到更新后的DOM
结构,可以使用Vue.nextTick()
。
组件内使用 vm.$nextTick()
实例方法只需要通过this.$nextTick()
,并且回调函数中的 this
将自动绑定到当前的 Vue
实例上
mixin
mixin
(混入),提供了一种非常灵活的方式,来分发 Vue
组件中的可复用功能。
本质其实就是一个js
对象,它可以包含我们组件中任意功能选项,如data
、components
、methods
、created
、computed
等等
我们只要将共用的功能以对象的方式传入 mixins
选项中,当组件使用 mixins
对象时所有mixins
对象的选项都将被混入该组件本身的选项中来
比如弹窗提示;alert警告
keep-alive
keep-alive
是vue
中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM
keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated
与deactivated
):
- 首次进入组件时:
beforeRouteEnter
>beforeCreate
>created
>mounted
>activated
> … … >beforeRouteLeave
>deactivated
- 再次进入组件时:
beforeRouteEnter
>activated
> … … >beforeRouteLeave
>deactivated
缓存后如何获取数据
看上方钩子即可:解决方案可以有以下两种:
- beforeRouteEnter
- actived
常用修饰符
vue
中修饰符分为以下五种:
-
表单修饰符
lazy:在我们填完信息,光标离开标签的时候,才会将值赋予给`value`,也就是在`change`事件之后再 进行信息同步 trim:自动过滤用户输入的首空格字符,而中间的空格不会过滤 number:自动将用户的输入值转为数值类型
-
事件修饰符
stop:阻止事件冒泡 prevent:阻止了事件的默认行为,如URL点击跳转 self:只当在 `event.target` 是当前元素自身时触发处理函数 once:绑定了事件以后只能触发一次 capture:使事件触发从包含这个元素的顶层开始往下触发 passive:在移动端,当我们在监听元素滚动事件的时候,会一直触发`onscroll`事件会让我们 的网页变卡,因此我们使用这个修饰符的时候,相当于给`onscroll`事件整了一个 `.lazy`修饰符 native:让组件变成像`html`内置标签那样监听根元素的原生事件
-
鼠标按键修饰符
鼠标按钮修饰符针对的就是左键、右键、中键点击
-
键值修饰符
键盘修饰符是用来修饰键盘事件(
onkeyup
,onkeydown
) -
v-bind修饰符
自定义指令
v-
开头的行内属性,都是指令,不同的指令可以完成或实现不同的功能
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue
也允许注册自定义指令。
全局注册主要是通过Vue.directive
方法进行注册,Vue.directive
第一个参数是指令的名字(不需要写上v-
前缀),第二个参数可以是对象数据,也可以是一个指令函数。
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
})
局部注册通过在组件options
选项中设置directive
属性
使用方法如下
<input v-focus />
几个常见的自定义指令
- 表单防止重复提交
- 图片懒加载
- 一键 Copy的功能
过滤器(Vue3中已废弃)
vue3中,官方建议:用方法调用或计算属性替换过滤器。
推断可能原因是是vue3要精简代码,并且filter功能重复,filter能实现的功能,methods和计算属性基本上也可以实现。把filter这方面的vue源码给删掉,这样的话,更加方便维护。
过滤器实质不改变原始数据,只是对数据进行加工处理后返回过滤后的数据再进行调用处理,也可以理解其为一个纯函数,不过Vue3
中已废弃filter
- 部过滤器优先于全局过滤器被调用
- 一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右
平时开发中,需要用到过滤器的地方有很多,比如单位转换、数字打点、文本格式化、时间格式化之类的等。
虚拟DOM
由于在浏览器中操作 DOM 是很昂贵的。频繁的操作 DOM,会产⽣⼀定的性能问题. 所以在vue中将真实的DOM节点抽离成⼀个虚拟的DOM树,这个虚拟的DOM树就是虚拟DOM 。
虚拟DOM将DOM树转换成一个JS对象树,diff算法逐层比较,删除,添加操作,但是,如果有多个相同的元素,可能会浪费性能,所以,react和vue-for引入key值进行区分。
vue
是通过createElement
生成VNode
虚拟DOM的优点
- 可以减少DOM操作:搭配diff算法,
- 能跨平台渲染:虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
DIff 算法(重点)
diff
算法是一种通过同层的树节点进行比较的高效算法,它避免了对树进行逐层搜索遍历,所以时间复杂度只有 O(n)。
有两个特点
- 比较只会在同层级进行, 不会跨层级比较。
- 在 diff 比较的过程中,循环从两边向中间收拢。 (
仔细看看
)
vue项目中封装axios(重点)
axios
是一个轻量的 HTTP
客户端。
基于 XMLHttpRequest
服务来执行 HTTP
请求,支持丰富的配置,支持 Promise
,支持浏览器端和 Node.js
端。
有以下几个特性:
- 从浏览器中创建
XMLHttpRequests
- 从
node.js
创建http
请求 - 支持
Promise
API - 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换
JSON
数据 - 客户端支持防御
XSRF
为什么要封装axios?
每发起一次HTTP
请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍。
重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。为了提高我们的代码质量,我们应该在项目中二次封装一下 axios
再使用。
请求拦截器: 根据请求的请求头设定,来决定哪些请求可以访问
请求拦截器可以在每个请求里加上token,做了统一处理后维护起来也方便
响应拦截器: 这块就是根据 后端返回来的状态码判定执行不同业务
响应拦截器可以在接收到响应后先做一层操作,如根据状态码判断登录状态、授权
Vue实例挂载的过程
-
new Vue
的时候调用会调用_init
方法- 定义
$set
、$get
、$delete
、$watch
等方法 - 定义
$on
、$off
、$emit
、$off
等事件 - 定义
_update
、$forceUpdate
、$destroy
生命周期
- 定义
-
调用
$mount
进行页面的挂载 -
挂载的时候主要是通过
mountComponent
方法 -
定义
updateComponent
更新函数 -
执行
render
生成虚拟DOM
-
_update
将虚拟DOM
生成真实DOM
结构,并且渲染到页面中
生命周期
beforeCreate -> created
- 初始化
vue
实例,进行数据观测
created
- 完成数据观测,属性与方法的运算,
watch
、event
事件回调的配置 - 可调用
methods
中的方法,访问和修改data数据触发响应式渲染dom
,可通过computed
和watch
完成数据计算 - 此时
vm.$el
并没有被创建
created -> beforeMount
- 判断是否存在
el
选项,若不存在则停止编译,直到调用vm.$mount(el)
才会继续编译 - 优先级:
render
>template
>outerHTML
vm.el
获取到的是挂载DOM
的
beforeMount
- 在此阶段可获取到
vm.el
- 此阶段
vm.el
虽已完成DOM初始化,但并未挂载在el
选项上
beforeMount -> mounted
- 此阶段
vm.el
完成挂载,vm.$el
生成的DOM
替换了el
选项所对应的DOM
mounted
vm.el
已完成DOM
的挂载与渲染,此刻打印vm.$el
,发现之前的挂载点及内容已被替换成新的DOM
beforeUpdate
- 更新的数据必须是被渲染在模板上的(
el
、template
、render
之一) - 此时
view
层还未更新 - 若在
beforeUpdate
中再次修改数据,不会再次触发更新方法
updated
- 完成
view
层的更新 - 若在
updated
中再次修改数据,会再次触发更新方法(beforeUpdate
、updated
)
beforeDestroy
- 实例被销毁前调用,此时实例属性与方法仍可访问
destroyed
- 完全销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
- 并不能清除DOM,仅仅销毁实例
双向绑定(重点)
单向绑定:把Model
绑定到View
,当我们用JavaScript
代码更新Model
时,View
就会自动更新。
双向绑定:在单向绑定的基础上,用户更新了View
,Model
的数据也自动被更新了,这种情况就是双向绑定。
双向绑定由三个重要部分构成
- 数据层(Model):应用的数据及业务逻辑
- 视图层(View):应用的展示效果,各类UI组件
- 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
ViewModel
它的主要职责就是:数据变化后更新视图,视图变化后更新数据。
它还有两个主要部分组成
监听器(Observer):对所有数据的属性进行监听
解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
流程(重点)
new Vue()
首先执行初始化,对data
执行响应化处理,这个过程发生Observe
中- 同时对模板执行编译,找到其中动态绑定的数据,从
data
中获取并初始化视图,这个过程发生在Compile
中 - 同时定义⼀个更新函数和
Watcher
,将来对应数据变化时Watcher
会调用更新函数 - 由于
data
的某个key
在⼀个视图中可能出现多次,所以每个key
都需要⼀个管家Dep
来管理多个Watcher
- 将来data中数据⼀旦发生变化,会首先找到对应的
Dep
,通知所有Watcher
执行更新函数
组件化
1.什么是组件化
一句话来说就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue
中每一个.vue
文件都可以视为一个组件
2.组件化的优势
- 降低整个系统的耦合度
- 调试方便
- 提高可维护性
vue中解决跨域问题
跨域的问题之前我总结过几个方法。
在vue
项目中,主要针对CORS
或Proxy
这两种方案进行展开
CORS
CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应
CORS
实现起来非常方便,只需要增加一些 HTTP
头,让服务器能声明允许的访问来源,只要后端实现了 CORS
,就实现了跨域
Proxy
代理(Proxy)也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。
vue项目本地开发完成后部署到服务器后报404
场景: vue
项目在本地时运行正常,但部署到服务器中,刷新页面,出现了404错误
HTTP 404 错误意味着链接指向的资源不存在,问题在于为什么不存在?且为什么只有history
模式下会出现这个问题?
Vue
是属于单页应用(single-page application)
而SPA
是一种网络应用程序或网站的模型,所有用户交互是通过动态重写当前页面,即不管应用有多少页面,构建物都只会产出一个index.html
。
当我们在地址栏输入 www.xxx.com
时,这时会打开我们 dist
目录下的 index.html
文件,然后我们在跳转路由进入到 www.xxx.com/login
关键在这里,当我们在 website.com/login
页执行刷新操作,nginx location
是没有相关配置的,所以就会出现 404 的情况
为什么hash模式下没有问题
router hash
模式是用符号#表示的,如 website.com/#/login
, hash
的值为 #/login
它的特点在于:hash
虽然出现在 URL
中,但不会被包括在 HTTP
请求中,对服务端完全没有影响,因此改变 hash
不会重新加载页面
hash
模式下,仅 hash
符号之前的内容会被包含在请求中,如 website.com/#/login
只有 website.com
会被包含在请求中 ,因此对于服务端来说,即使没有配置location
,也不会返回404错误。
解决方案
产生问题的本质是因为路由是通过JS来执行视图切换的,当进入到子路由时刷新页面,web
容器没有相对应的页面此时会出现404,所以只需要配置将任意页面都重定向到 index.html
。
这么做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html
文件
为了避免这种情况,你应该在 Vue
应用里面覆盖所有的路由情况,然后在给出一个 404 页面
vue3和vue2的一些主要区别。
-
速度更快
-
体积减少:通过
webpack
的tree-shaking
功能,可以将无用模块“剪辑”,仅打包需要的 -
更易维护:
compositon Api
-
更接近原生
-
更易使用:响应式
Api
暴露出来 -
更好的Typescript支持
Vue 3 中需要关注的一些新功能包括:
- framents: 在
Vue3.x
中,组件现在支持有多个根节点 - Teleport:
- composition Api: 组合式
api
,通过这种形式,我们能够更加容易维护我们的代码,将相同功能的变量进行一个集中式的管理 - createRenderer:构建自定义渲染器,能够将
vue
的开发模型扩展到其他平台
其他改变
destroyed
生命周期选项被重命名为unmounted
beforeDestroy
生命周期选项被重命名为beforeUnmount
[prop default
工厂函数不再有权访问this
是上下文- 自定义指令 API 已更改为与组件生命周期一致
data
应始终声明为函数- 来自
mixin
的data
选项现在可简单地合并 attribute
强制策略已更改- 一些过渡
class
被重命名 - 组建 watch 选项和实例方法
$watch
不再支持以点分隔的字符串路径。请改用计算属性函数作为参数。 <template>
没有特殊指令的标记 (v-if/else-if/else
、v-for
或v-slot
) 现在被视为普通元素,并将生成原生的<template>
元素,而不是渲染其内部内容。- 在
Vue 2.x
中,应用根容器的outerHTML
将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。Vue 3.x
现在使用应用容器的innerHTML
,这意味着容器本身不再被视为模板的一部分。
移除的 API(重点)
keyCode
支持作为v-on
的修饰符$on
,$off
和$once
实例方法- 过滤
filter
- 内联模板
attribute
$destroy
实例方法。用户不应再手动管理单个Vue
组件的生命周期。