Vue面试题
本片文章将会每周不定期更新不定数量面试题,毕竟质量才是关键。
实践是检验真理的唯一标准!看题不记题,看题要做题!
面试是一个学习交流的过程,抱着虚心求教的心,人无完人,祝你变得更优秀!
1. Vue 中 Key 的理解
作用
- key 是虚拟 DOM 中的唯一标识
- 使 Diff 算法更快的找到对应的节点,从而提高 Diff 的运行效率
- Vue 存在
就地更新
策略,默认是高效的,当该策略不产生副作用时,建议不要使用 key,这样可以取得更好的性能【只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。】 - 有相同父元素的子元素必须有独特的 key
- Vue 存在
- 保证相同标签名元素的过度切换(使 Vue 可以区分,否则只会替换其内部属性而不会触发过渡效果)
- 使 Diff 算法更快的找到对应的节点,从而提高 Diff 的运行效率
场景
完整地触发组件的生命周期钩子
1
2
3
4<!-- text 变化就会重新渲染,diff 依靠 key 识别是否为新元素 -->
<transition>
<span :key="text">{{ text }}</span>
</transition>
触发过渡效果
原理
- 采用 Diff 算法
- 通过 patchVnode 方法对比新旧 vnode(深度优先,同层比较)
- 通过 updateChildren 方法更新(首尾交叉对比)
分析方法
- 下载源码
- 写个小例子打断点找到源码位置(比如写个列表)
- 源码位置:
src/core/vdompatch.js
- patchVnode 函数(Diff 发生的地方)
- updateChildren 函数
2. Vue 中 Diff 算法的理解
作用
- Diff 算法是为虚拟 DOM 的框架而生的,可以精确定位所改变的元素使组件更新更加准确与快速
- 源码 mountComponent 函数中,可以看出每挂载一个组件就会产生一个新 Watcher,为了保证 Vue 组件更新的准确性,采用 Diff 也就可以理解了
- Diff 算法比较方式:深度优先,同层比较
- 触发时机:数据响应式触发 setter => setter 会触发通知 => 通知的方式就是将 watcher 添加到异步更新队列 => 事件循环结束则清空队列(watcher 执行他们的更新函数) => 更新函数执行时调用组件渲染函数和组件的更新函数 => 重新渲染最新的 VDom => 组件实例执行更新函数
- 打补丁:新旧 VDom 对比就叫做 patch
分析方法
- 源码位置:
src/instance/lifecycle.js
- mountComponent 函数
- 每个组件都有一个 watcher,为了精确的更新,Diff 算法就很有必要了
- mountComponent 函数
- 源码位置:
src/core/vdompatch.js
- patchVnode 函数(深度优先,同层比较,Diff 发生的地方,源码见1)
- updateChildren 函数(首位交叉对比,算法核心,源码见1)
3. MVC、MVP、MVVM 的理解
共同点:解决代码杂糅问题。
区别:MVP 是 MVC2.0,MVVM 是 MVC3.0。
- Web 1.0 时代
- 早期开发,前后端未分离。虽然开发快,但代码较混乱(jsp,php,.net)。业务逻辑越复杂,单文件代码量越大,项目越难维护;
- MVC 的诞生就是解决这一些问题,将项目拆分成 Model、View 和 Controller 三大部分。
- Model:保存应用数据,与后端数据进行同步;
- View:将 Model 中的数据可视化,用户可在此进行操作;
- Controler:业务逻辑,根据用户操作修改 Model 中的数据。
- MVC 刚诞生时仅用于后端,比如 spring、Structs 等框架,前端只是 View 部分(前端输出静态页面交给后端,后端人员使用模板语法做整合)。
- 优点
- 低耦合
- 可复用
- 易维护
- 缺点
- 增加系统结构,实现较复杂,需要考虑代码功能划分,最好用于大点的项目,否则得不偿失;
- View 与 Controller 联系过于紧密。拆分不够完全,很难独立复用;
- View 对 Model 的低效率访问。Model 中接口不同,View 可能需要多次调用才能拿全数据;
- Web 2.0 时代
- Google 的 Gmail 出现,伴生的 ajax 可使前后台进行交互;
- 前后端分离,前端专注 HTML、CSS、JS,后端专注数据服务,协同开发,效率提高;
- 前端通过 ajax 拥有了使页面局部刷新的能力,减轻了后端的负载和流量消耗,用户体验更佳;
- 专职前端出现,页面内容越来越多,前端代码量变大,项目难维护。
- MVP 模式
- Model:提供数据;
- View:负责显示;
- Presenter:负责 Model 与 View 之间的通信;
- MVC 衍变而来。
- 优点
- View 与 Model 完全隔离;
- Presenter 与 View 具体实现技术无关。
- 缺点
- 增加了代码复杂度
- Presenter 中除了有 Controller 代码,还有大量 Model 与 View 的手动同步逻辑代码,难维护;
- Presenter 与 View 的耦合度较高。
- Web 3.0 时代
- 前端 MVC 模式出现(将后端 MVC 中的 V 拆为前端的 MVC);
- 前端 MVVM 模式出现(Angular、React、Vue)。
- Model-View-ViewModel
- 优点
- 低耦合
- 可重用
- 独立开发
- 可测试(测试 viewModel)
4. Vue 设计原则(理念)
- 渐进式 JavaScript 框架;
- 自底向上逐层应用(有着足够庞大的生态去应对多种需求,无论是简单的静态页,还是需要路由跳转,更或者全局状态管理都可以满足);
- 核心库(DeclarativeRendering、ComponsentSystem)只关注视图层。
- 易用:没有学习过 Vue,只要你是前端,看着开发文档就可以上手,学习曲线很平滑,模板语法太友善了;
- 灵活:渐进式,可以是构建用户界面的 JavaScript 库,也可以是一套完整的框架;
- 高效:Vue2 引入了虚拟 DOM 和 Diff 算法,Vue3 引入 Proxy 对数据响应式改进。
5. Vue 组件化的理解
定义
- 将复杂系统解耦,拆分成多个功能模块,并根据业务进行重组复用;
- 组件化其实就是一个做积木搭积木的过程;
- Vue 的核心特性之一。
使用场景
页面元素多样,业务逻辑复杂,功能模块可拆分。
优点
- 功能复用,协同开发,提高开发效率;
- 单元测试;
- 可针对指定组件进行更新,提高性能(
lifecycle.js - mountComponent()
,一个组件一个 watcher); - 扩展性高。
注意事项
- 组件应该是高内聚、低耦合的(任性复用);
- 合理的拆分组件才能将组件化的优点最大化;
- 数据传递时尽量遵循单向数据流原则,避免关系混乱。
Vue 组件化特点
- 声明组件:Vue.component();
- 单文件组件(vue-loader 将 template 编译成 render 函数);
- 组件配置 => VDOM => DOM;(通过
patch.js - createElm()
实例化及挂载) - 属性 prop;
- 自定义事件;
- 插槽;
- 双向数据绑定。
6. Vue 组件之间的通讯方式
父子组件
- 父组件向子组件传值
- props
- ref(直接访问 DOM 元素)
- $children(做封装后使用,避免依赖 DOM)
- 子组件向父组件传值
- $emit / $on
- $parent(做封装后使用,避免依赖 DOM)
兄弟组件
- $parent
- $root
- eventbus
- vuex
跨层级关系
- eventbus
- $attrs / $listeners(在子组件中处理非属性值的传递)
- provide/inject(单向,祖辈向后代传值)
- vuex
7. Vue 组件模板只能有一个根元素
- 实例化 Vue 创建组件时,el 选项只能定一个根元素(入口);
- 单文件组件为一个 Vue 实例,一个 Vue 实例只能定一个根元素;
- Diff 算法比较时需要有相同的根。
8. Vue 组件中 data 选项必须为函数而根实例无此限制
- Vue 组件
- 官网有说明当在组件中使用 data property 的时候 (除了 new Vue 外的任何地方),它的值必须是返回一个对象的函数;
- Vue 组件中的 data 不是函数时无法通过 Vue 检测,提示需要将 data 改为函数;
- Vue 组件可能存在多个实例;
- 以对象形式定义 data 会导致它们公用一个 data 从而产生数据污染;
- 以函数形式定义 data,由于函数的闭包特性,不会产生数据污染;
- Vue 根实例
- 根实例只有一个,不会产生数据污染,所以没限制,也不需要做这种限制。
9. v-if 和 v-for 优先级
- 官网有说明
v-for
比v-if
具有更高的优先级; - 写个
v-for
和v-if
同级的情况,打印下app.$options.render
,看下渲染函数; - 源码
src/compiler/codegen/index.js
genElement() 函数中判断条件for
是高于if
的; - 如果两个条件需要同时作用,可以先将
v-if
提到v-for
的上层,减少每次循环都做判断导致的性能消耗; - 如果
v-for
和v-if
必须作用在一个标签上,那就应该考虑列表数组中数据的筛选了,v-for
作用的应该是渲染数组(这么理解的话,就不会产生v-for
和v-if
必须作用在一个标签上的情况)。
10. vue-router 中保护指定路由的安全
通过导航守卫(钩子函数)在路由跳转时先判断条件(是否登录,是否有权限)再决定是否跳转
- 全局守卫
- 触发时机:全局有导航发生时
- 无法获取组件实例
- 全局前置守卫(beforeEach(to, from, next))
- to:目标路由
- from:当前路由
- next:高阶函数,处理传过来的参数,返回一个设置的 hook 来决定导航后续该怎么做
- 不传参:正常放行
- 传参
- next(false):未登录、无权限时阻止导航
- next(path):传递 path 字符串可以重定向到一个新地址
- 全局解析守卫(beforeResolve(to, from, next))
- 全局后置钩子(afterEach(to, from))
- 路由独享守卫(beforeEnter(to, from, next))
- 触发时机:发生与当前路由相关的导航时
- 无法获取组件实例
- 组件内的守卫
- 触发时机:用到当前组件时
- 可以获取组件实例
- beforeRouteEnter(to, from, next)
- 导航确认前调用
- 不能获取组件实例
this
- 组件实例还没被创建
- beforeRouteUpdate(to, from, next)
- 导航更新时调用(例如:复用组件时,路径由
test/01
跳转至test/02
时,就会调用此函数)
- 导航更新时调用(例如:复用组件时,路径由
- beforeRouteLeave(to, from, next)
- 导航离开前调用(例如:填完表单还未保存就离开,执行此函数让用户进行确认是否离开)
导航解析流程:
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
11. nextTick 的理解
定义
nextTick 是 Vue 的一个全局 API,由于 Vue 的异步更新策略导致我们对数据的直接修改不会立刻体现在 DOM 中,在这种情况下使用 nextTick 方法即可解决。
作用
由于 Vue 在更新 DOM 时是异步执行。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick 方法会在队列中加入一个回调函数,确保函数在前面的 DOM 操作完成后调用。
使用方法
1 |
|
原理
将我们传入 nextTick 中的函数加入到 callbacks 里,然后使用 timerFunc 函数异步调用,Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
12. Vue 的响应式
定义
监听数据变化(更改数据) 并 作出响应(更新 DOM)。
作用
MVVM 框架中解决的一个核心问题就是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,数据响应式处理就是为了解决这个问题。
原理
优缺点
- 优点
- 只操作数据,不用频繁的操作 DOM,既降低了开发难度,又提升了开发效率(Diff 算法)
- 缺点
- 初始化时递归遍历会造成性能损失
Vue3 中响应式的新变化
13. Vue 性能优化
路由懒加载
1 | const router = new VueRouter({ |
keep-alive缓存页面
1 | <template> |
合理的利用v-show替换v-if
v-show 的原理是使用 display 属性实现的隐藏显示,v-if 的原理是删除创建,所以你懂得~
v-for与v-if同级时
官方文档建议在同时需要使用 v-for 和 v-if 时,把 v-if 提到外层,避免每次循环都进行条件判断,但是有时业务逻辑就需要你在循环项做判断,我们可以使用 computed 对需遍历数组先过滤一遍。
长列表做虚拟滚动
使用 vue-virtual-scroller 对长列表进行区域渲染(DOM 复用,只对内容进行更新)。
1 | <recycle-scroller |
订阅后要在组件销毁时取消订阅
Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。
1 | created() { |
图片懒加载
图片过多时,使用 vue-lazyload 仅加载当前可视区域的图片。
1 | <img v-lazy="/static/img/1.png" /> |
第三方库做按需引入策略
项目开发中仅依赖第三方库中的部分组件或方法,按需引入避免体积过大。
1 | import { Button, Select } from 'element-ui' |
无状态组件标记为函数时组件
1 | <template functional> |
子组件分割
对组件内部渲染频繁的部分进行拆分,使其单独管理自身渲染,减少不必要的渲染。
变量本地化
频繁调用的数据存储为变量,减少引用次数。
1 | const a = this.a; |
14. Vue3 新特性
- 更快
- 虚拟 DOM 重写
- 组件快速路径(Component fast path)
- 单个调用(Monomorphic calls)
- 子节点类型检测(Children type detection)
- 优化 slots 生成
- 静态树提升
- 静态属性提升
- 基于 Proxy 的响应式系统
- 虚拟 DOM 重写
- 更小
- 通过摇树优化核心库体积
- 更易维护
- TypeScript + 模块化
- 更加友好
- 跨平台:编译器核心和运行时核心与平台无关,使 Vue 更容易在多平台使用
- 更易使用
- 改进对 TypeScript 的支持,编辑器可以提供更明确地类型检查和错误警告
- 更好的调试支持
- 独立的响应化模块
- Composition API
理解才能更好的使用!
原文作者: ShanYi Hui
原文链接: http://huishanyi.club/2020/05/29/前端面试题/Vue面试题/
版权声明: 转载请注明出处(必须保留作者署名及链接)