Vue常见面试题

首先,对于vue中的一些书写规范以及常见问题,建议大家优先阅读 vue风格指南;这里面有很多面试中的常见点。

下面来看常见的面试题:

1、v-for和v-if哪个优先级更高?如果两者同时出现,应该怎么优化得到更好的性能?
源码位置:src/compiler/codegen/index.js

官方说法:
永远不要把 v-if 和 v-for 同时用在同一个元素上。
一般我们在两种常见的情况下会倾向于这样做:

  • 为了过滤一个列表中的项目 (比如 v-for=“user in users” v-if=“user.isActive”)。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
  • 为了避免渲染本应该被隐藏的列表 (比如 v-for=“user in users” v-if=“shouldShowUsers”)。这种情形下,请将 v-if 移动至容器元素上 (比如 ul、ol)。
// 1-1代码:
// 当v-if和v-for处于同一元素时,
<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
// 以上代码运行结果如下js一样,每次循环进行if判断:
this.users.map(function (user) {
  if (user.isActive) {
    return user.name
  }
})

总结:

  1. 首先,源码中,代码执行优先判断了v-for,其次判断的是v-if,所以v-for的优先级比v-if高;官方更是明确说明v-for优先级高于v-if。
  2. 其次,v-for和v-if可以一起使用,并不会出现什么错误,一样可以渲染列表;只是因为v-for的优先级高,所以当这两个指令在同一个标签元素上面时,每次渲染都会先执行循环再判断条件,浪费了性能,如1-1代码
  3. 为避免出现性能浪费,所以官方给出了以上两种解决方式;一种通过计算属性,先进行筛选,再渲染;另一种就是分开写在不同的元素上面。

详情参阅 vue风格指南:避免-v-if-和-v-for-用在一起必要

2、Vue组件data为什么必须是个函数而Vue的根实例则没有此限制?
源码位置:src\core\instance\state.js - initData()  函数每次执行都会返回全新data对象实例

总结:

  • 一个Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例,这样做不合理;
  • 采用函数形式定义,在initData时会将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,是因为根实例只有一个,不需要担心这种情况。
3、你知道vue中key的作用和工作原理吗?说说你对它的理解。
源码位置:src\core\vdom\patch.js - updateChildren() 

总结:

  1. key的作用主要是为了提高更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得这个patch过程更加高效,减少DOM操作量,提升性能。
  2. 另外,若不设置key还可能在列表更新时引发一些隐蔽的bug。
  3. vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
4、怎么理解vue中的diff算法?
源码分析1:必要性,lifecycle.js - mountComponent() 
组件中可能存在很多个data中的key使用。

源码分析2:执行方式,patch.js - patchVnode() 
patchVnode是diff发生的地方。

源码分析3:高效性,patch.js - updateChildren()

diff算法整体策略:深度优先、同层比较。

同层比较,深度优先
总结:

  1. diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比,将变化的地方更新在真实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度为O(n)。
  2. vue2中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到发生变化的地方。
  3. vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染结果newVnode,此过程称为patch。
  4. diff过程整体遵循深度优先,同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同,做4次比对尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助key通常可以非常精准找到相同节点,因此整个patch过程非常高效。
5、谈一谈对vue组件化的理解
回答总体思路: 
组件化定义、优点、使用场景和注意事项等方面展开陈述,同时要强调vue中组件化的一些特点。

组件定义源码:src\core\global-api\assets.js
vue-loader会编译template为render函数,最终导出的依然是组件配置对象。

组件化优点 :lifecycle.js - mountComponent()
组件、Watcher、渲染函数和更新函数之间的关系

组件化实现:
构造函数:src\core\global-api\extend.js 
实例化及挂载:src\core\vdom\patch.js - createElm() 

总结:

  1. 组件是独立和可复用的代码组织单元。组件系统是vue核心特性之一,它让开发者能使用小型、独立和通用可复用的组件构建大型应用。
  2. 组件化开发能大幅度提高应用开发效率、测试性、复用性等。
  3. 组件使用按分类有:页面组件、业务组件、通用组件。
  4. vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于VueComponent,扩展于Vue。
  5. vue中常见组件化技术有:属性prop、自定义事件、插槽等,它们主要用于组件通信、扩展等。
  6. 合理的划分组件,有助于提升应用性能。
  7. 组件应该是高内聚、低耦合的。
  8. 遵循单向数据流原则,父=>子。
6、谈一谈对vue设计原则的理解?

vue官网定义:

  • 渐进式JavaScript框架
  • 易用、灵活、高效
  • 渐进式JavaScript框架:与其他大型框架不同的是,Vue被设计为自底层向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue也完全能够为复杂的单页应用程序提供驱动。
  • 易用性:vue提供数据响应式、声明模板式语法和基于配置的组件系统等核心特性。这些使我们只需要关注应用的核心业务即可,只要会写js、html、css就能轻松编写vue应用。
  • 灵活性:渐进式框架的最大优点就是灵活,如果应用足够小,我们可能仅需要vue核心特性即可完成功能;随着应用规模不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli等库和工具,不管应用体积;而学习难度是一个逐渐增加的平和曲线。
  • 高效性:超快的虚拟DOM和diff算法使我们的应用拥有最佳的性能表现。追求高效的过程还在继续,vue3中引入Proxy对数据响应式改进以及编译器中对于静态内容编译的改进都会让vue更加高效。
7、你了解哪些vue性能优化方法?
  1. 路由懒加载。

  2. keep-alive缓存页面。

  3. 使用v-show复用DOM。

  4. v-for遍历同时避免使用v-if。

  5. 长列表性能优化:

    • 如果列表是纯粹的数据显示,不会有任何改变,就不需要做响应化;
    • 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域内容;参考:vue-virtual-scroller、vue-virtual-scroll-list 。
  6. 事件的销毁:vue组件销毁时。会自动解绑它的全部指令以及事件监听器,但是仅限于组件本身的事件;使用beforeDestroy生命周期函数。

  7. 图片懒加载:图片过多,为了页面的加载速度,未出现在可视区域的图片先不做加载,滚动到可视区后再加载;参考:vue-lazyload。

  8. 第三方插件按需引入;像element-ui这样的第三方库可以按需引入避免体积过大。

  9. 无状态组件标记为函数式组件,给template标签添加属性functional。

  10. 子组件分割。

  11. 变量本地化。

8、vue中组件之间的通信方式
通信方式使用场景可以分为三类:
 1. 父子组件通信
 2. 兄弟组件通信
 3. 跨层组件通讯

通信方式:

  1. props 父子
  2. $emit/$on(事件总线$bus) 全局
  3. vuex 全局
  4. $parent/$children 父子
  5. $attrs/$listeners 跨层
  6. provide/inject 跨层

总结:

  1. 父组件通过props向子组件传递值,子组件通过$emit向父组件发送数据,父组件通过v-on/@来触发接收数据方法。
  2. vue实例作为事件总线用来触发事件和监听事件,可以通过此种方式进行组件间通信,包括:父子组件通信、兄弟组件通信、跨层组件通信。
// 创建bus文件并引入
import bus from './bus'
// 监听sendData事件
mounted(){
	bus.$on('sendData',(val)=>{this.msg = val})
}
// 触发sendData事件
methods:{
	sendMsg(){
		bus.$emit('sendData','传递的值')
	}
}
  1. vuex:全局状态管理
    在这里插入图片描述
    核心概念:
    state:vuex中的唯一数据源,获取多个state可以使用...mapState
    getter:可以理解为计算属性,getter的返回值根据他的依赖缓存起来,依赖发生变化才重新计算,获取多个getter可以使用...mapGetters
    mutation:mutation对象里面存储的都是同步函数,是进行state状态修改的地方;同步函数有两个参数,第一个是state,第二个是修改后的值。
    action:action和mutation都是修改状态,不同之处在于:
    (1) action不能直接修改数据,需要通过mutation间接修改数据;
    (2) action可以包含异步操作,mutation只能进行同步操作;
    (3) action中的回调函数第一个参数是context,一个与store实例具有相同属性方法的对象;
    (4) action通过store.dispatch触发,mutation通过store.commit提交;
    module:由于使用单一状态树,应用的所有状态集中到比较大的对象,当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决以上问题,vuex允许我们将store分割成模块,每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块从上至下进行同样方式分割。

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

  1. $parent / $children与 ref
    (1) $parent / $children:访问父/子实例
    (2) ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果在子组件上使用,引用就指向组件实例
    以上两种方式都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。
<component-a ref="comA"></component-a>
// 获取组件component-a的实例
const comA = this.$refs.comA;
  1. $attrs/$listeners只做数据传递,不做中间处理
    (1) $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定(class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
    (2) $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。

    简单来说,$attrs$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性, $listeners里存放的是父组件中绑定的非原生事件。

  2. provide/inject 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
    注:provide和inject主要为高阶插件/组件库提供用例,能在业务中熟练运用,可以达到事半功 倍的效果!

9、vue为什么要求组件模版只能有一个根元素?
 - new Vue({el:'#app'}) 
 - 单文件组件中,template下的元素div,其实就是"树"状数据结构中的"根"。 
 - diff算法要求的,源码中,patch.js里patchVnode()。

总结:
(1) 实例化Vue时,我们会寻找一个带有id的元素当做挂载对象,这个元素就相当于“根节点”,如果有多个带id的元素,Vue如何确定应当挂载到哪个el上面?如下代码:

<body> 
    <div id='app'></div>
</body> 
<script> 
    var vm = new Vue({
        el:'#app'
    })
</script> 

(2) 组件模板中会要求template标签下有一个div元素,div元素就相当于“根”元素;

<template>
<div>

</div> 
</template> 

而template这个标签有三个特性:

  • 隐藏性:该标签不会显示在页面的任何地方,即便里面有多少内容,它永远都是隐藏的状态,设置了dispaly:none
  • 任意性:该标签可以写在任意地方,甚至是head、body、script标签内;
  • 无效性:该标签里的任何HTML内容都是无效的,不会起任何作用;只能使用innerHTML来获取到里面的内容;

一个单文件组件就是一个vue实例,如果template下有多个div,那么如何指定vue实例的根入口?为了让组件可以正常生成一个vue实例,这个div会自然处理成程序的入口,通过这个根节点,来递归遍历整个vue树下的节点,并处理为vdom,最后再渲染成真正的HTML,插入到正确的位置。

(3) 源码中diff算法要求每个单文件组件需要有一个唯一的根元素。

10、你知道nextTick的原理吗?
nextTick官方文档的解释,它可以在DOM更新完毕之后执行一个回调

Vue-深入响应式原理:异步更新队列

总结:

  1. vue用异步队列的方式来控制DOM更新和nextTick回调先后执行;
  2. microtask因为其高优先级特性,能确保队列任务中的微任务在一次事件循环前被执行完;
  3. 因为兼容性问题,vue不得不做了microtask向macrotask(setTimeout)的降级方案;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值