VUE面试题总结

882 阅读9分钟

由于网上有的答案我个人觉得不太完善,所以自己总结下面试题

1. 为什么data是个函数?

组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果

2. vue组件通讯有哪几种方式

  • 组件通讯解决了什么?
    • vue中,每个组件之间的都有独自的作用域,组件间的数据是无法共享的
    • 但实际开发工作中我们尝尝需要让组件共享数据,这也是组件通信的目的
  • 组件间通信的分类可以分为以下
    • 父子组件之间的通信
    • 兄弟之间的通信
    • 祖孙与后代组件之间的通信
    • 非关系组件之间之间的通信

3. 组件间通信的方案

  • 整理vue中8种通信方案
    • 通过props传递
    • 通过 $emit 触发自定义事件
    • 使用ref
    • EventBus
    • parent root
    • attrs & listeners
    • Provide & inject
    • vuex
  • props传递数据

image.png

  • 适用场景: 父组件传递数据给子组件
  • 子组件设置props属性,定义接收父组件传递过来的参数
  • 父组件在使用子组件标签中通过字面量来传递值 children.vue
props: {
    name: String, // 字符串形式
    age: { // 对象形式
        type: Number, // 接收的类型为数值
        default: 18, // 默认值为18 
        require: true // age 属性为必须传递
    }
}

Father.vue

<Children name='jack' age=20 />
  • $emit 触发自定义事件

    • 适用场景:子组件传递数据给父组件
    • 子组件通过emit触发自定义事件,emit触发自定义事件,emit第二个参数为传递的数值
    • 父组件绑定监听获取到子组件传递过来的参数 Children.vue
    this.$emit('add', good)
    

    Father.vue

    <Children @add="cartAdd($event)"/>
    
  • ref

    • 父组件在使用子组件的时候设置ref
    • 父组件通过设置子组件ref来获取数据

父组件

<Children ref="foo">

this.$refs.foo // 获取子组件的实例,通过子组件实例我们就能拿到对应的数据
  • EventBus

    • 使用场景: 兄弟组件传值
    • 创建一个中央时间总线EventBus
    • 兄弟组件通过emit触发自定义事件,emit触发自定义事件, emit第二个参数为传递的数值
    • 另一个兄弟组件通过$on监听自定义事件

    原理: 它作为一种事件的发布订阅模式,一直活跃在各种代码框架中 EventBus 化了各个组件之间进行通信的复杂度,其工作原理在于对事件的监听与手动触发

    EventBus的优缺点 1.缺点

  • 大家都知道vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。 如果业务有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理EventBus在项目中的关系。通常会用到,在vue页面销毁时,同时移除EventBus事件监听。 由于是都使用一个Vue实例,所以容易出现重复触发的情景,两个页面都定义了同一个事件名,并且没有用$off销毁(常出现在路由切换时)。

  1. 优点
    1. 解决了多层组件之间繁琐的事件传播。
    1. 使用原理十分简单,代码量少

    Bus.js

    // 创建一个中央时间总线类
    class Bus {
        constructor() {
            this.callbacks = {} // 存放事件的名字
        }
    }
    $on(name, fn) {
        this.callbacks[name] = this.callback[name] || []
        this.callbacks[name].push(fn);
    }
    
    $emit(name, args) {
        if (this.callbacks[name]) {
        this.callbacks[name].forEach(cb => cb(args))}
    }
    
    // main.js
    Vue.prototype.$bus = new Bus(); // 将$bus挂载到vue实例的原型上
    // 另外一种方式 
    Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能
    

    Children1.vue

    this.$bus.$emit('foo');
    

    Children2.vue

    this.$bus.$on('foo', this.handle);
    

    parent root

    通过共同祖辈$parent或者$root搭建通信桥梁 兄弟组件

    this.$parent.on('add', this.add)
    

    另一个兄弟组件

    this.$parent.emit('add')
    

4. Vue 的生命周期方法有哪些 一般在哪一步发请求

  • beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问

  • created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有el,如果非要想与Dom进行交互,可以通过vm.el,如果非要想与 Dom 进行交互,可以通过 vm.nextTick 来访问 Dom

  • beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。

  • mounted 在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点

  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁(patch)之前。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程

  • updated 发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新,该钩子在服务器端渲染期间不被调用。

  • beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。我们可以在这时进行善后收尾工作,比如清除计时器。

  • destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

  • activated keep-alive 专属,组件被激活时调用

  • deactivated keep-alive 专属,组件被销毁时调用

异步请求在哪一步发起

可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

  • 能更快获取到服务端数据,减少页面 loading 时间;
  • ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

5 v-if和v-show的区别

  • v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
  • v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
  • 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
  • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

6.vue内部指令

  1. v-once: 定义它的元素或者组件只渲染一次,包括元素或组件的所有子节点 首次渲染后不在随着数据的变化重新渲染将被视为静态内容

  2. v-cloak: 这个指令保持在元素上直到关联实例结束编译,解决初始化慢导致页面闪动的最佳实战

  3. v-bind: 绑定属性,动态更新HTML元素上的属性,例如v-bind:class

  4. v-on: 用于监听DOM事件,例如v-on:click v-on:keyup

  5. v-html: 赋值就是变量innerHTML--注意防止xss攻击

  6. v-text: 更新元素的textContent

  7. v-model: 在普通标签

  8. v-if/ v-else/ v-else-if 可以配合template来使用 在render函数里面就是三元表达式

  9. v-show 显示隐藏

  10. v-for 循环指令 编译出来的结果是——l代表渲染列表 优先级比v-if高, 最好不要一起使用 -- 尽量使用计算属性去解决 注意增加唯一key值 -- 不要使用index作为key

  11. v-pre 跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度

7. 怎么理解Vue的单项数据流

数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改,这样会防止从子组件以外改变父级组件的状态,从而导致你的应用的数据流向难以理解

注意: 在子组件直接用v-model 绑定父组件传过来的prop这样是不规范的写法 开发环境会报警告

如果实在要改变父组件的prop值可以在data里面定义一个变量 并用prop的初始化它之后用$emit通知父组件去修改

8. computed和watch区别和运用场景

computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容,它可以设置 getter 和 setter。 watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。 计算属性一般用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑

9. v-if和v-for为什么不建议一起使用?

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级

10 vue2.0响应式数据的原理?

vue2.0原理: 实现了MVVM(双向数据绑定)的原理是通过Object.defineProperty来劫持各个属性的setter和getter, 在数据变动时候发布消息给订阅者,触发相应的监听回调。

  1. 基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式
  2. Object.defineProperty无法检测到对象属性的添加和删除
  3. 由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让vue将他转换为响应式
  4. 深度监听需要一次性递归,对性能影响比较大

vue3.0

  1. 基于Proxy和Reflect,可以原生监听数组,可以监听对象属性的添加和删除。
  2. 不需要一次性遍历data的属性,可以显著提高性能。
  3. 因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11 。

11 . Vue 如何检测数组变化

数组考虑性能原因没有用defineProperty 对数组的每一项进行拦截,而是选择对7种数组(push,shift,pop,splice, unshift, sort, reverse)方法进行重写(AOP切片思想)

所以在vue中修改数组的索引和长度是无法监控到的,需要通过以上7种变异方法修改数组才会触发数组对应的watcher进行更新

12. v-model 原理

v-model 只是语法糖而已

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

13. v-for 为什么要加 key

如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

  1. 更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
  2. 更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

14 vue-router 动态路由是什么,有什么问题?

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:

const User = {
  template: "<div>User</div>",
};

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    { path: "/user/:id", component: User },
  ],
});

q: vue-router组件服用导致路由参数失效怎么办?

解决办法

  1. 通过watch监听路由参数再发请求
watch: {
    '$route': function () {
        this.getData(this.$route.params.xxx)
    }
}
  1. 用:key来阻止”复用“
<router-view :key="$route.fullPath" />

15.谈一下对 vuex 的个人理解

vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue) image.png 主要包括以下几个模块:

State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。 Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。 Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。 Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。 Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

16. Vuex 页面刷新数据丢失怎么解决

需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件 推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中

17 你都做过哪些 Vue 的性能优化

  1. 对象层级不要过深,否则性能就会差
  2. 不需要响应式的数据不要放到 data 中(可以用 Object.freeze() 冻结数据)
  3. v-if 和 v-show 区分使用场景
  4. computed 和 watch 区分使用场景
  5. v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if
  6. 大数据列表和表格性能优化-虚拟列表/虚拟表格
  7. 防止内部泄漏,组件销毁后把全局变量和事件销毁
  8. 图片懒加载
  9. 路由懒加载
  10. 第三方插件的按需引入
  11. 适当采用 keep-alive 缓存组件
  12. 防抖、节流运用
  13. 服务端渲染 SSR or 预渲染

17. 描述组件渲染和更新的过程?

  • 1、vue 组件初次渲染过程

解析模板为 render 函数

触发响应式,监听 data 属性的 getter 和 setter

执行 render 函数, 生成 vnode,patch(elem,vnode)

  • 2、vue 组件更新过程

修改 data, 触发 setter (此前在getter中已被监听)

重新执行 render 函数,生成 newVnode

patch(vnode, newVnode)

18.描述Vue组件的生命周期(有父子组件的情况)?

父beforeCreated ->父 created -> 父 beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

父beforeUpdate-> 子beforeUpdate -> 子updated -> 父updated

  • 父子组件销毁顺序

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed