vue面试题及答案
# vue 基础面试题
# vue 单页面和多页面区别
SPA 单页面应用(SinglePage Web Application)
优点:页面切换快,用户体验好
缺点:首屏加载速度慢,不易于 SEO
MPA 多页面应用(MultiPage Application)
优点:首屏加载速度快,SEO 效果好
缺点:页面切换慢,用户体验不佳
- 刷新方式
SPA:相关组件切换,页面局部刷新或更改
MPA:整页刷新
- 路由模式
SPA:可以使用 hash,也可以使用 history
MPA:普通链接跳转
- 用户体验
SPA:页面片段间时间的切换快,用户体验良好,当初次加载文件过多时,需要做相关调优。
MPA:页面切换加载缓慢,流畅度不够,用户体验比较差,尤其网速慢的时候
- 转场动画
SPA:容易实现转场动画
MPA:无法实现转场动画
- 数据传递
SPA:容易实现数据传递,方法有很多(通过路由带参数传值,Vuex 传值等等)
MPA:依赖 url 传参,cookie,本地存储
- 搜索引擎优化(SEO)
SPA:需要单独方案,实现较为困难,不利于 SEO 检索,可利用服务器端渲染(SSR)优化
MPA:实现方法容易
- 使用范围
SPA: 高要求的体验度,追求界面流畅的应用
MPA:适用于追求高度支持搜索引擎的应用
- 开发成本
SPA:较高,长需要借助专业的框架
MPA:较低,但也页面代码重复的多
- 维护成本
SPA:相对容易
MPA:相对复杂
- 结构
SPA:一个主页面+许多模块的组件
MPA:许多完整的页面
- 资源文件
SPA:组件公用的资源只需要加载一次
MPA:每个页面都需要自己加载公用的资源
# 说说你对 SPA 单页面的理解,它的优缺点分别是什么?
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
- 优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
- 缺点:
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
# MVVM、MVC、MVP 的区别
MVP 与 MVC 区别:MVP 的模型与视图完全分离,它们之间的通信是通过 Presenter (MVC 中的 Controller)来进行的,所有的交互都发生在 Presenter 内部
MVVM 和 MVP 的区别:MVVM 采用双向绑定(data-binding),View 的变动,自动反映在 ViewModel
# vue 和 react 区别
相同点:数据驱动视图、组件化、使用 Virtual DOM
- 数据流不同。
react 从诞生开始就推崇单向数据流,而 Vue 是双向数据绑定
- 数据变化的实现原理不同。
react 使用的是不可变数据(手动 setState),而 Vue 使用的是可变的数据
- 模板渲染方式的不同
react 是通过 JSX 渲染模板,Vue 是通过一种拓展的 HTML 语法进行渲染
# react 和 vue 的选择
- 如果你喜欢简单和“能用就行”的东西,请选择 Vue
- 如果你想要你的应用尽可能的小和快,请选择 Vue
- 如果你想要用模板搭建应用——选择 Vue
- 如果你打算构建一个大型应用程序,请选择 React
- 如果你想要一个同时适用于 Web 端和原生 APP 的框架,请选择 React
- 如果你想要最大的生态系统,请选择 React
# vue 和 jquery 区别
- vue 双向数据绑定
在 Vue 中数据改变,视图无需刷新即可实时改变,而 jQuery 中数据改变,视图需要用户手动刷新才会改变。
- 性能-减少 dom 的操作
vue 使用了虚拟 dom 技术,能够减少 dom 操作
- 组件化
Vue 组件具有独立的逻辑和功能或界面,同时又能根据规定的接口规则进行相互融合,变成一个完整的应用
Vue 组件的优势就是组件进行重复使用,便于协同开发,提高开发效率。
# Proxy 和 Object.defineProperty 的对比
Proxy 是直接代理对象;而 Object.defineProperty 只能劫持对象的属性,
Proxy 能监听对象的新增和删除操作;Object.defineProperty 不能监听对象的新增和删除操作,通过 Vue.set()和 Vue.delete 来实现响应式的。
Proxy 可以直接监听数组的变化;Object.defineProperty 本身是有监控数组下标变化的能力的,只是在 Vue 的实现中,从性能/体验的性价比考虑
Proxy 支持 13 种拦截操作,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的。
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的;而 Object.defineProperty 只能遍历对象属性直接修改;
Proxy 兼容性差;Object.defineProperty 兼容性好,支持 IE9,
Proxy 有性能问题但是有新标准性能红利,从长远来看,JS 引擎会继续优化 Proxy
# template 与 render 函数对比
相同之处:
render 函数 跟 template 一样都是创建 html 模板
不同之处:
Template 适合逻辑简单,render 适合复杂逻辑。
使用者 template 理解起来相对容易,但灵活性不足;自定义 render 函数灵活性高,但对使用者要求较高。
render 的性能较高,template 性能较低。使用 render 函数渲染没有编译过程,相当于使用者直接将代码给程序。所以,使用它对使用者要求高,且易出现错误
Render 函数的优先级要比 template 的级别要高,但是要注意的是 Mustache(双花括号)语法就不能再次使用
# computed 和 watch 的区别
- 缓存支持
watch 不支持缓存,数据变,直接会触发相应的操作;
computed 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 异步支持
watch 支持异步;
computed 不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化
- 使用场景
watch 当一个属性发生变化时,需要执行对应的操作;一对多或者一对一;
computed 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用 computed
# v-show 与 v-if 有什么区别?
- 手段:
v-if 是通过控制 dom 节点的存在与否来控制元素的显隐; v-show 是通过设置 DOM 元素的 display 样式,block 为显示,none 为隐藏;
- 编译过程:
v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件; v-show 只是简单的基于 css 切换;
- 编译条件:
v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且 DOM 元素保留;
- 性能消耗:
v-if 有更高的切换消耗; v-show 有更高的初始渲染消耗;
使用场景:
如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。v-if 判断是否加载,可以减轻服务器的压力,在需要时加载, 但有更高的切换开销
# vue 组件通信方式
Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。 Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
(1)props / $emit 适用 父子组件通信
这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
(2)ref 与 $parent / $children 适用 父子组件通信
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 $parent / $children:访问父 / 子实例
(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(4)$attrs/$listeners 适用于 隔代组件通信
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。 $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
(5)provide / inject 适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex 适用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
# data 为什么要 return
因为组件是需要被复用的,也就是会创建很多个实例,如果 data 是一个对象的话,对象是引用类型,一个实例修改了 data 会影响到其他实例,所以必须是一个函数,是一个函数的话那么每个实例可以维护一份被返回对象的独立的拷贝,组 件之间的 data 属性值不会互相影响。
追问:那为什么 new Vue 里 data 可以是一个对象?
new Vue 是不会被复用的,所以不存在引用对象的问题。
# 父子组件生命周期钩子函数执行顺序?
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
# vue2 的生命周期钩子
beforeCreate:组件刚被创建,组件属性计算之前
created:组件实例创建完成,属性已绑定, 但真实 dom 还没有生成,$el 还不可用,但是 data 和 method 可用
beforeMount:此时已经完成了模板的编译,在页面挂载开始之前被调用, 相关的 render 函数首次被调用
mounted:已经将编译好的模板,挂载到了页面指定的容器中显示,可操作 DOM(最早)
beforeUpdate:状态更新之前执行此函数,此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,此时还没有开始重新渲染 DOM 节点
updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
beforeDestroy:实例销毁之前调用。在这一步,实例(data,methods,过滤器,指令)仍然完全可用。
使用场景:(日期在我点击查询的时候要存储,刷新就读内存,但是我点击其他页面再进来的时候,这个内存要清空)
destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
activited keep-alive 专属,组件被激活时调用
deactivated keep-alive 专属,组件被销毁时调用
# vue 组件的异步加载
vue 提供的工厂函数
webpack 和 ES6 的
import
返回一个 Promisewebpack 提供的 require.ensure
# vue 中触发视图更新的方法
Vue.set
Vue.delete
数组修改
(1)数组对象直接修改属性,可以触发视图更新
(2)splice 方法修改数组,可以触发视图更新
(3)数组赋值为新数组,可以触发视图更新
this.$forceUpdate()
,强制视图更新
# vue 常见修饰符及作用
# vue 常用自定义指令
权限校验指令 v-premission
防抖指令 v-debounce
图片懒加载 v-LazyLoad
拖拽指令 v-draggable
长按指令 v-longpress
复制粘贴指令 v-copy
参考:https://zhuanlan.zhihu.com/p/337659611 (opens new window)
# 为什么 v-if 和 v-for 不能连用
v-for 的优先级比 v-if 高一些。 因为在 vue 中会优先执行 v-for, 当 v-for 把所有内容全部遍历之后 , v-if 再对已经遍历的元素进行删除 , 造成了加载的浪费 , 所以应该在执行 v-for 之前优先执行 v-if , 可以减少加载的压力 解决办法:
1.当控制 v-if 的元素不存在 v-for 中 , 可以使用 template 包裹住对应的 v-for , 也可以使用父级元素添加 v-if , 可以不加载从而优化性能
2.如果 v-if 控制的元素存在 v-for 中 , 可以通过使用计算器属性来回避 , 比如使用计算器属性在页面加载之前进行一边过滤
# v-html 会导致哪些问题
1.安全性
vue 官方文档说明: 你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。 尽管这看上去像 cross-site scripting 攻击,结果并不会导致什么,但是 HTML 5 中指定不执行由 innerHTML 插入的
2.插值表达式不生效
v-html 更新的是元素的 innerHTML 。内容按普通 HTML 插入, 不会作为 Vue 模板进行编译 。但是有的时候我们需要渲染的 html 片段中有插值表达式,或者按照 Vue 模板语法给 dom 元素绑定了事件. 解决办法:如果试图使用 v-html 组合模板,可以重新考虑是否通过使用组件来替代。
3.scoped 的样式在 v-html 中不起作用
在单文件组件里,scoped 的样式不会应用在 v-html 内部,因为那部分 HTML 没有被 Vue 的模板编译器处理。如果你希望针对 v-html 的内容设置带作用域的 CSS,你可以替换为 CSS Modules 或用一个额外的全局
<style>
元素手动设置类似 BEM 的作用域策略。 解决办法 1:照样使用 scoped,但是我们可以使用深度选择器(>>>) 当你的 vue 项目使用 less 或者 sass 的时候,>>>这个玩意可能会失效,我们用/deep/来代 解决办法 2:单文件组件的 style 标签可以使用多次,可以一个 stlye 标签带 scoped 属性针对当前组件,另外一个 style 标签针对全局生效,但是内部我们采用特殊的命名规则即可,例如 BEM 规则。
# 在.vue 文件中 style 是必须的吗?那 script 是必须的吗?为什么?
都不是必须的
如果是普通组件那么只能是一个静态 html
如果是函数式组件, 那么可以直接使用 props 等函数式组件属性
# 从 0 到 1 自己构架一个 vue 项目,说说有哪些步骤、哪些重要插件、目录结构你会怎么组织
vue-cli 实际上已经很成熟了,目录除了脚手架默认的,
1、一般会额外创建 views,components,api,utils,stores 等;
2、下载重要插件,比如 axios,dayjs(moment 太大),其他的会根据项目需求补充;
3、封装 axios,统一 api 调用风格和基本配置;
4、如果有国际化需求,配置国际化;
5、开发,测试和正式环境配置,一般不同环境 API 接口基础路径会不一样;
2
3
4
5
# vue 中怎么重置 data?
初始状态下设置 data 数据的默认值,重置时直接 object.assign(this.$data, this.$options.data())
说明:
this.$data获取当前状态下的data
this.$options.data()获取该组件初始状态下的 data(即初始默认值)
如果只想修改 data 的某个属性值,可以 this[属性名] = this.$options.data()[属性名],如this.message = this.$options.data().message
# vue 高级面试题
# vue 双向绑定原理
# nextTick 原理
# 虚拟 DOM
# diff 算法
# key 原理
# watch 原理
# computed 原理
computed 本质是一个惰性求值的观察者。 computed 内部实现了一个惰性的 watcher, 也就是 computed watcher, computed watcher 不会立刻求值, 同时持有一个 dep 实例。 其内部通过 this.dirty 属性标记计算属性是否需要重新求值。 当 computed 的依赖状态发生改变时, 就会通知这个惰性的 watcher, computed watcher 通过 this.dep.subs.length 判断有没有订阅者, 有的话, 会重新计算, 然后对比新旧值, 如果变化了, 会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。) 没有的话, 仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)
# scoped 原理
# keep-alive 原理
# vue 项目相关
# 你是怎么处理 vue 项目中的错误的?
后端接口错误 axios 响应拦截器捕捉错误
代码逻辑问题
(1) 设置全局错误处理函数 errorHandler
(2) errorCaptured 生命钩子函数,当捕获到一个来自子孙组件的错误时被调用
# vue-router 面试题
# 三种路由模式
# route 和 router 有什么区别
- route 是“路由信息对象”(是正在跳转的这个路由的局部对象),
包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
- router 是“路由实例”对象
包括了路由的跳转方法,钩子函数,是 VueRouter 的一个对象,通过 Vue.use(VueRouter)和 VueRouter 构造函数得到一个 router 的实例对象