前端面试题6(Vue)

type
status
date
slug
summary
tags
category
icon
password

1. 什么是 Vue?

回答:
Vue是一个基于MVVM架构的渐进式框架,一般用于构建单页面应用,其特点是声明式渲染响应式
  1. 什么是 MVVM
MVVMModel-View-ViewModel的缩写,是一种软件架构模式。
  • Model:数据模型,负责存储和管理应用程序的数据
  • View:视图,负责展示数据
  • ViewModel:视图模型,负责将数据和视图进行绑定
MVVM架构的优点是:
  • 低偶性ViewModel之间的耦合度低,便于维护和扩展
  • 可复用性:一个ViewModel可以绑定多个View
  • 独立开发ViewModel可以独立开发
  • 测试方便ViewModel可以独立于View进行单元测试
  1. 什么是渐进式框架
渐进式框架是指无需一次性引入全部功能,而是可以根据需要逐步引入的框架。它具有以下特点:
  • 灵活性:可以根据需要选择使用的功能
  • 可扩展性:可以根据需要扩展功能
  • 可维护性:可以根据需要维护功能
  1. 什么是 SPA
SPASingle Page Application的缩写,单页面应用是一种 Web 应用程序或网站,用户在与其交互时不会重新加载整个页面,而是通过动态更新当前页面来实现与用户的交互。
SPA的优点是:
  • 交互体验好SPA通过动态更新当前页面来实现与用户的交互,用户不需要等待整个页面重新加载,从而提高了交互体验。
  • 前后端分离SPA通过Ajax请求获取数据,前后端分离,便于维护和扩展。
SPA的缺点是:
  • 不利于 SEOSPA的内容是通过JavaScript动态生成的,搜索引擎无法抓取,影响SEO
  • 首屏加载慢SPA需要加载大量的JavaScript代码,首屏加载慢。
  • 浏览器历史管理复杂SPA需要手动管理浏览器历史,增加了复杂度。
  • 内存占用较大SPA需要手动管理内存,增加了内存泄漏的风险。
  1. 什么是声明式渲染
声明式渲染是指通过声明的方式来描述数据和视图之间的关系,而不是通过命令的方式来描述数据和视图之间的关系。简单来说就是你只需要告诉框架你的目的,至于它内部如何达成你的目的,你无需关心。
比如原始的JavaScript代码是这样的:
而使用Vue的代码是这样的:
但是在 Vue 中,我们只需要将标签和内容建立联系,然后只修改内容,就可以自动让标签中的内容也更改。不需要关心 Vue 做了什么。

2. Vue 的生命周期

回答:
  1. Vue2
  • beforeCreate:实例初始化之后,数据观测data和事件配置methods之前被调用
  • created:实例创建完成后被立即调用,此时数据观测和事件配置已完成
  • beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用
  • mounted:实例被挂载后调用,此时 DOM 已经渲染完成
  • beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前
  • updated:由于数据更改,导致的虚拟 DOM 重新渲染和打补丁之后调用
  • beforeDestroy:实例销毁之前调用,此时实例仍然完全可用
  • destroyed:实例销毁后调用,所有的事件监听器被移除,所有的子实例被销毁
  • activated:被 keep-alive 缓存的组件激活时调用
  • deactivated:被 keep-alive 缓存的组件停用时调用
  1. Vue3
  • Vue3中,去掉了beforeCreatecreated两个生命周期函数,用setup来替代。`
  • beforeDestroydestroyed两个生命周期函数更名为onBeforeUnmountonUnmounted
  • 其它生命周期函数并没有改变,只是在每个生命周期函数前面加上了一个 on

3. keep-alive

回答:
keep-alive是一个内置组件,用于缓存不活动的组件实例,从而避免重新渲染。简单来说就是用keep-alive包裹的组件在切换时不会被销毁,而是被缓存起来。
keep-alive有 3 个属性:
  • include:字符串或正则表达式,只有匹配的组件会被缓存
  • exclude:字符串或正则表达式,匹配的组件不会被缓存
  • max:数字,最多可以缓存多少个组件实例
includeexclude传入的是组件的name。如果没有name,则会使用导入时定义在components中的名称。
keep-alive在各个生命周期函数中的表现:
  1. created:会创建一个keep-alive实例(空对象)
  1. render:获取<slot>的内容,取第一个组件(这也是为什么在keep-alive中写多个组件也只会取第一个的原因)。然后我们获取组件的name,和include/exclude进行匹配,决定是否缓存, 如果不需要则直接返回Vnode,如果需要缓存就执行下一步。缓存时,判断Vnode有没有key,如果没有会根据tagcid生成,然后判断key是否存在缓存中,如果不存在就将keyVnode作为一组数据进行缓存,如果存在,就用当前Vnode替换缓存中的数据。最后返回Vnode。每次命中缓存时会调整组件在缓存中的位置,移动到末尾,如果设置了max,当缓存数量超出范围会删除第一项
  1. mounted:对include/exclude监听,发生变化时说明需要缓存的组件发生变化,这时候就需要去遍历缓存,判断需要删除或者缓存的组件,最后处理缓存。
  1. destroyed:此阶段会销毁keep-alive组件,会删除全部的以缓存组件。

4. Vue 中常见的指令

回答:
  1. v-bind:用于绑定一个或多个属性,语法糖是:,在Vue3中也可以作用于<style>中的css属性.
  1. v-model:用于双向绑定,绑定的元素数据发生变化data也会改变,data发生变化元素展示的数据也会随之改变。
  1. v-on:用于绑定事件,如clickinputscroll等事件,语法糖是@
  1. v-if/v-else-if/v-else:条件渲染,用逻辑判断来决定元素的展示或隐藏。
  1. v-show:条件展示,同样也是通过Boolean值来判断元素的显示,内部实现是cssdisplay属性。
  1. v-html:可以插入HTML模板语句,一般用在富文本当中。缺点也很明显,容易受到 XSS 攻击。
  1. v-for:循环渲染,可以渲染多个相同的元素。
  1. v-slot:用于绑定插槽,传入对应的插槽名进行绑定。语法糖是#
  1. v-pre:跳过编译,可以跳过Vue的编译阶段。比如标签中使用{{}}插值语句时,可跳过编译,当普通字符处理,最后直接展示{{}}
  1. v-once:只渲染一次,指定的标签渲染一次之后会视为静态元素,跳过更新,达到性能优化。
  1. v-memo:缓存,传入数组,当下次更新时会比对数组的值,假如发生变化则更新,没有发生变化则不会更新。只传入[]空数组时和v-once一样。通常和v-for配合使用,达到性能优化。

5. v-if 和 v-show

回答:
v-ifv-show都是条件判断渲染元素,进一步区分,前者是渲染,后者是展示。主要是根据它们的内部实现,v-if会在编译阶段添加类似if(true/false) { render(Vnode) }来实现条件渲染,false时元素也会在DOM中消失,所以也造成在频繁切换的场景性能花销变大,更加适合在只渲染一次的场景,减少DOM结构。v-else则是通过cssdisplay属性,当display:none隐藏,display:block展示,元素在display:none时也不会销毁,所以更适用频繁切换场景。

6. 修饰符

回答:
  1. 事件修饰符
  • .stop:阻止事件冒泡。
  • .prevent:阻止默认事件。
  • .self:只有事件在自身元素触发时调用函数。
  • .capture:捕获事件,内部元素的事件在被内部元素处理之前,先被外部元素处理。
  • .once:事件只触发一次。
  • .passive:一般用于触摸事件的监听器,可以改善移动设备的滚动性能。
  1. 按键修饰符
按键修饰符一般对于键盘的按键,种类很多。
  • .enterEnter
  • .escEsc
  • .metaWindowsWinMacCommand
  • ...
  1. 鼠标按键修饰符
  • .left:鼠标左键。
  • .right:鼠标右键。
  • .middle:鼠标中键。
  1. Vue3 中移除的修饰符
  • .sync:是v-bind:xxx@input:xxx的语法糖,在 Vue3 中已移除,使用v-model代替。

7. 组件间的传参

回答:
  1. 父子组件通信
  • 父传子(Props):父组件通过子组件上使用props属性传递数据。
  • 子传父(自定义事件):子组件通过$emit触发自定义事件,父组件通过监听该事件获取数据。
  • 兄弟组件通信
    • 通过共同的父组件: 兄弟组件可以通过将数据提升到共同的父组件来实现传参,再由父组件将数据以 props 的形式传递给另一个子组件。
    • Event Bus(事件总线):在 Vue2 中,有时可以通过一个空的 Vue 实例作为中央事件总线,但在 Vue3 中这种方式不再推荐。
    • 状态管理(Vuex、Pinia):使用全局状态管理工具,在任意组件中访问和修改状态。
  • 跨级(非直接关系组件)通信
    • Provide / Inject:适用于祖孙组件之间的通信。祖先组件使用 provide 提供数据,后代组件使用 inject 接收
    • 全局状态管理:使用 Vuex 或 Pinia,可以在任何组件中访问共享状态。
    • Composition API:使用 Vue3 的 Composition API,可以创建共享的响应式状态(如使用 ref 或 reactive)并在多个组件中引用。

8. v-if 和 v-for 谁的优先级更高

回答:
Vue2v-for高于v-ifVue3中相反。所以在Vue2中会先渲染然后在判断,这种方式不好,所以Vue3中改成了v-if高于v-for。相当于给v-for外包一层v-if,所以两个在同一标签中同时使用会报错。

9. v-for 的 key

回答:
v-for循环中,key用来标识元素,当在更新阶段时会匹配对比key值相同新旧元素决定是否更新,如果没有key则会根据顺序对比,所以合理的使用key可以提升更新时的渲染性能。

10. 为什么不建议使用 index 作为 v-for 的 key

回答:
假设循环渲染了 4 个标签,使用对于的index作为key,那此时的key分别为0,1,2,3。现在从数组头部插入一条数据,key就变成了0,1,2,3,4,原本的0,1,2,3,4对应现在的1,2,3,4,但是在进行更新时对比key值会变成旧的0和新的0以此类推,虽然最后的数据显示正确,但是影响了渲染性能。这就是为什么不建议使用index作为v-forkey

11. data 为什么必须是一个函数

回答:
data作为组件的属性,而且一个组件可以被多个组件调用,假如以对象形式会出现变量污染的情况,所以使用函数创建函数作用域,这样组件被其他组件调用时会生成一个私有作用域,防止了数据污染的情况。

12. v-model 的原理

回答:
  1. Vue2
  • 对于原生表单元素v-model实际上被编译成了一个:value的绑定和一个@input的事件监听。例如:
被转换为:
  1. Vue3
  • 默认绑定名称的变化
    • 对于自定义组件,Vue3 默认将 v-model 映射为:
      • prop 名称为 modelValue
      • 事件名称为 update:modelValue
    • 例如:
      • 实际上被编译转换为:
  • 支持多个 v-model
    • Vue3 允许在一个组件上使用多个 v-model 绑定,通过传递参数来指定不同的 prop 与事件。例如:
    • 这里:
      • v-model:modelValue 对应 prop modelValue 和事件 update:modelValue
      • v-model:checked 对应 prop checked 和事件 update:checked

13. computed 和 watch 的作用是什么,有什么区别?

回答:
  1. computed
    1. 作用:
        • 用于声明基于响应式数据的计算属性,自动根据依赖的数据进行计算,并将结果缓存起来。
        • 当依赖的响应式数据没有变化时,computed 属性不会重新计算,从而提高性能。
    2. 特点:
        • 缓存性computed 的结果是缓存的,只有当它依赖的数据发生变化时才会重新计算。
        • 同步计算:计算属性是同步执行的。
        • 适用场景:当你需要基于已有数据计算出新的数据,并且这个数据在模板中多次使用时,推荐使用 computed
  1. watch
    1. 作用:
        • 用于监听某个或某些响应式数据的变化,当数据变化时执行回调函数,通常用于处理异步操作开销较大的操作
        • 适合在数据变化时执行副作用操作,如发送 AJAX 请求、手动操作 DOM 或进行复杂逻辑处理。
    2. 特点:
        • 无缓存性:每次数据变化都会触发回调函数,不会对结果进行缓存。
        • 异步支持:可以处理异步任务或延迟操作。
        • 适用场景:当你需要在数据变化时执行额外操作(例如数据格式化、请求新数据或动画触发等),使用 watch 更合适。
  1. 区别
  • 缓存 vs 非缓存
    • computed:具有缓存机制,依赖的数据不变时不会重复计算。
    • watch:每次数据变化都会触发回调,无缓存机制。
  • 用途
    • computed:用于声明依赖其他响应式数据的计算属性,适合需要在模板中直接使用并依赖缓存的场景。
    • watch:用于观察数据变化并执行副作用操作,适合处理异步或复杂逻辑任务。
  • 执行方式
    • computed:同步计算属性值。
    • watch:可以处理异步任务,允许在数据变化时执行额外操作。

14. Vue3 有哪些变动。

回答:
  • 响应式原理的变动,由 bject.defineProperty 变为 Proxy
  • diff 算法优化;
  • 删除 filtersmixin,用 methodshooks 替代;
  • options API 转变为 composition API;
  • 新增 Fragment 内置组件,可以在不引入多余 DOM 的情况下,包裹并渲染多个子元素,这也是为什么 vue3 的模板不需要有一个根节点了;
  • 新增 Teleport 内置组件,它可以将内容绑定到指定的节点之下,比如有些弹窗,我们想让它作为 body 的子元素,而不是嵌套太深,就可以使用该组件包裹,然后设置 to="body";
  • 新增 Suspense 内置组件,该组件的作用是等待,该组件内部有两个插槽,默认插槽为 default,另一个为 fallback,我们可以将即将展示的东西放入默认插槽,在它还没有值时,展示 fallback 插槽的内容,这目前还是一项实验性特性;
  • 移除了$children$listeners 等属性;
  • v-model 可以传参;
  • 更好的支持 TypeScript;

15. setup 的优点。

回答:
  • 更简洁的代码;
  • 和 ts 更好的融合;
  • 更好的运行时性能;
  • 更好的 ide 类型推导;

16. Vue2 和 Vue3 的响应式原理。

回答:
  1. 什么是响应式
响应式就是在我们修改数据之后,无需手动触发视图更新,视图会自动更新。
  1. Vue2 响应式实现
Vue2 中,响应式系统是通过依次遍历 data 返回的对象,将里面每一个属性通过 Object.defineProperty 进行定义,然后在属性描述符中添加 get/set,实现 getter/setter 方法,在访问属性时,在 getter 函数中收集依赖(记录哪些方法或变量在使用这个属性),在修改属性时,在 setter 函数中派发依赖(将收集到的依赖依次更新),从而达到响应式。
  1. Vue3 响应式实现
Vue3 中,响应式系统是通过 ES6 中的 Proxy 实现对一个对象的代理,然后设置 handler.get/handler.set,在对代理对象进行操作时,可以触发 get/set,和 Object.defineProperty 类似,get 中实现收集依赖,在 set 中实现派发依赖,从而达到响应式的效果。

17. Vue3 为什么要改为 Proxy 实现响应式?

回答:
  • Object.defineProperty()实现响应式的缺点
    • Object.defineProperty 只能监听对象的基础类型属性,假如属性是对象则需要递归遍历每一项,这样造成性能低。
    • 只能对已有属性监听,Vue2 中会在 create 阶段遍历 data 的每一个属性进行监听,后面我们已 obj.x 的形式添加属性,此属性不再是响应式,所以 Vue2 给出了 this.$set 的方式。
    • 不能监听数组长度的变化,所以 Vue2 重写了数组的部分方法来实现响应式。
  • Proxy
    • 解决了第一个问题,不在需要递归遍历。第二参数 hander 提供了 13 种方法,能够监听各种操作。
    • 解决第二个问题,可以用 obj.x 的形式添加属性,新属性依旧可以监听。
    • 解决第三个问题,Proxy 不仅可以监听数组索引值的变化,还能监听原型方法。

18. this.$set()的实现原理。

回答:
  • $set 是用来解决对对象或数组添加新属性时没有响应式的问题,也可以用来修改现有属性。其接受三个参数:
    • target:需要添加/修改属性的对象。
    • key:属性的键名。
    • val:属性的值。
  • 第一步会先判断数据类型。
  • 是数组的话且 key 值合法,就会调用内部的数组重写方法来添加或替换数据,因为重写方法会触发响应式。
  • 如果是对象的话,要设置的属性本来不存在,就需要通过Object.defineProperty 来定义一个响应式的属性。

19. Vue2 和 Vue3 的 diff 算法有什么不同?

回答:
  • Vue 2.x 使用的是双向指针遍历的算法,也就是通过逐层比对新旧虚拟 DOM 树节点的方式来计算出更新需要做的最小操作集合。但这种算法的缺点是,由于遍历是从左到右、从上到下进行的,当发生节点删除或移动时,会导致其它节点位置的计算出现错误,因此会造成大量无效的重新渲染。
  • Vue 3.x 使用了经过优化的单向遍历算法,也就是只扫描新虚拟 DOM 树上的节点,判断是否需要更新,跳过不需要更新的节点,进一步减少了不必要的操作。此外,在虚拟 DOM 创建后,Vue 3 会缓存虚拟 DOM 节点的描述信息,以便于复用,这也会带来性能上的优势。同时,Vue 3 还引入了静态提升技术,在编译时将一些静态的节点及其子节点预先处理成 HTML 字符串,大大提升了渲染性能。

20. $nextTick 的作用是什么,原理是什么?

回答:
  • $nextTick 是 Vue.js 提供的一个异步更新 DOM 的方法。因为 Vue 的更新是异步的,如果你想在改变某个属性之后立即去操作 DOM,可能结果并不是你想要的,而 nextTick 允许你在当前 DOM 更新循环结束之后执行一个回调函数,这样可以确保在回调函数中操作的 DOM 是最新的。
  • Vue3 中的 nextTick 只是利用了 promise 来实现的,而 nextTick 的回调其实就像相当于放在 Promise.then() 中来执行的。

21. Vue3 中 ref 和 reactive 的区别是什么?

回答:
  1. reactive
从源码可以看出,reactive 需要传入一个对象,如果是一个只读的对象,那就返回原对象,否则就调用 createReactiveObject() 函数,返回一个经过 Proxy 代理的对象。
  1. ref
从源码可以看出,创建一个 ref 变量,如果传入的变量已经是一个 ref 变量了,就直接返回这个变量,如果不是就会返回一个 new RefImpl()new 这个类又做了什么呢?其实就是把我们传入的变量进行 toReactive 然后赋值给 this._value,当我们访问 value 的时候,通过 get 函数,给我们返回了 this._value。说白了就是给我们的变量包裹了一层对象,然后转变成了 reactive 对象。
  1. 区别
既然都是用 reactive 实现的,为什么不都用 reactive 呢?这就要说到它们的区别了。
  • Vue3 的响应式原理是基于 Proxy 的,而 Proxy 只能代理对象,如果我们要实现一个基本数据类型的响应式怎么办呢?只能通过将它变成对象的方式;
  • reactive 只能传入一个对象,而 ref 可以传入任何类型
  • ref 声明的变量,我们在访问时,除了模板之外,必须使用 xxx.value,而 reactive 不用;
  • reactive 声明的变量可能会造成响应式丢失,这也是为什么官方更推荐使用 ref 的原因;
  1. reactive 响应式丢失
乍一看这种方式挺正常的,但是这种方式会引起响应式丢失,第一次使用 reactive 创建对象 obj 时,obj 是一个正常的被 Proxy 代理的对象,它是响应式的。但是当我们给 obj 重新赋值时,相当于改变了 obj 的内存地址,此时 obj 变成了一个非响应式的普通对象,于是造成了响应式丢失。

21. SPA 页面首页白屏如何优化。

回答:
SPA 页面首页白屏的原因是因为所有资源都需要在首页加载,因此优化首页白屏就是要优化首页资源的加载。
  • 第三方库如果能进行按需引入就采用按需引入,如果不行可以采取 CDN 的方式引入
  • 尽量减少图片资源,使用字体图标或精灵图,对大图使用 TinyPng 对图片资源进行压缩,并且使用 CDN 引入图片
  • 代码层面,检查首页代码是否有长耗时的同步任务阻塞了页面的渲染
  • 开启 gzip 压缩
  • 打包出来的 index.html 文件中的 script 标签,使用 defer 异步加载或者放到 body 之后
  • 利用 webpack 等打包工具进行分包,避免首页一次性加载太多资源;
Loading...

© Charlie Chan 2021-2025