本文已参与「新人创作礼」活动,一起开启掘金创作之路。
总结一下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请求 - 支持
PromiseAPI - 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换
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生命周期选项被重命名为unmountedbeforeDestroy生命周期选项被重命名为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组件的生命周期。