前端面试题6(Vue)
type
status
date
slug
summary
tags
category
icon
password
1. 什么是 Vue?
回答:
Vue
是一个基于MVVM
架构的渐进式框架,一般用于构建单页面应用,其特点是声明式渲染和响应式。- 什么是 MVVM
MVVM
是Model-View-ViewModel
的缩写,是一种软件架构模式。Model
:数据模型,负责存储和管理应用程序的数据
View
:视图,负责展示数据
ViewModel
:视图模型,负责将数据和视图进行绑定
MVVM
架构的优点是:- 低偶性:
View
和Model
之间的耦合度低,便于维护和扩展
- 可复用性:一个
ViewModel
可以绑定多个View
- 独立开发:
View
和Model
可以独立开发
- 测试方便:
ViewModel
可以独立于View
进行单元测试
- 什么是渐进式框架
渐进式框架
是指无需一次性引入全部功能,而是可以根据需要逐步引入的框架。它具有以下特点:- 灵活性:可以根据需要选择使用的功能
- 可扩展性:可以根据需要扩展功能
- 可维护性:可以根据需要维护功能
- 什么是 SPA
SPA
是Single Page Application
的缩写,单页面应用是一种 Web 应用程序或网站,用户在与其交互时不会重新加载整个页面,而是通过动态更新当前页面来实现与用户的交互。SPA
的优点是:- 交互体验好:
SPA
通过动态更新当前页面来实现与用户的交互,用户不需要等待整个页面重新加载,从而提高了交互体验。
- 前后端分离:
SPA
通过Ajax
请求获取数据,前后端分离,便于维护和扩展。
SPA
的缺点是:- 不利于 SEO:
SPA
的内容是通过JavaScript
动态生成的,搜索引擎无法抓取,影响SEO
。
- 首屏加载慢:
SPA
需要加载大量的JavaScript
代码,首屏加载慢。
- 浏览器历史管理复杂:
SPA
需要手动管理浏览器历史,增加了复杂度。
- 内存占用较大:
SPA
需要手动管理内存,增加了内存泄漏的风险。
- 什么是声明式渲染
声明式渲染
是指通过声明的方式来描述数据和视图之间的关系,而不是通过命令的方式来描述数据和视图之间的关系。简单来说就是你只需要告诉框架你的目的,至于它内部如何达成你的目的,你无需关心。比如原始的
JavaScript
代码是这样的:而使用
Vue
的代码是这样的:但是在 Vue 中,我们只需要将标签和内容建立联系,然后只修改内容,就可以自动让标签中的内容也更改。不需要关心 Vue 做了什么。
2. Vue 的生命周期
回答:
Vue2
beforeCreate
:实例初始化之后,数据观测data
和事件配置methods
之前被调用
created
:实例创建完成后被立即调用,此时数据观测和事件配置已完成
beforeMount
:在挂载开始之前被调用,相关的 render 函数首次被调用
mounted
:实例被挂载后调用,此时 DOM 已经渲染完成
beforeUpdate
:数据更新时调用,发生在虚拟 DOM 打补丁之前
updated
:由于数据更改,导致的虚拟 DOM 重新渲染和打补丁之后调用
beforeDestroy
:实例销毁之前调用,此时实例仍然完全可用
destroyed
:实例销毁后调用,所有的事件监听器被移除,所有的子实例被销毁
activated
:被 keep-alive 缓存的组件激活时调用
deactivated
:被 keep-alive 缓存的组件停用时调用
Vue3
- 在
Vue3
中,去掉了beforeCreate
、created
两个生命周期函数,用setup
来替代。`
beforeDestroy
、destroyed
两个生命周期函数更名为onBeforeUnmount
、onUnmounted
。
- 其它生命周期函数并没有改变,只是在每个生命周期函数前面加上了一个
on
。
3. keep-alive
回答:
keep-alive
是一个内置组件,用于缓存不活动的组件实例,从而避免重新渲染。简单来说就是用keep-alive
包裹的组件在切换时不会被销毁,而是被缓存起来。keep-alive
有 3 个属性:include
:字符串或正则表达式,只有匹配的组件会被缓存
exclude
:字符串或正则表达式,匹配的组件不会被缓存
max
:数字,最多可以缓存多少个组件实例
include
和exclude
传入的是组件的name
。如果没有name
,则会使用导入时定义在components
中的名称。keep-alive
在各个生命周期函数中的表现:created
:会创建一个keep-alive
实例(空对象)
render
:获取<slot>
的内容,取第一个组件(这也是为什么在keep-alive
中写多个组件也只会取第一个的原因)。然后我们获取组件的name
,和include/exclude
进行匹配,决定是否缓存, 如果不需要则直接返回Vnode
,如果需要缓存就执行下一步。缓存时,判断Vnode
有没有key
,如果没有会根据tag
和cid
生成,然后判断key
是否存在缓存中,如果不存在就将key
和Vnode
作为一组数据进行缓存,如果存在,就用当前Vnode
替换缓存中的数据。最后返回Vnode
。每次命中缓存时会调整组件在缓存中的位置,移动到末尾,如果设置了max
,当缓存数量超出范围会删除第一项。
mounted
:对include/exclude
监听,发生变化时说明需要缓存的组件发生变化,这时候就需要去遍历缓存,判断需要删除或者缓存的组件,最后处理缓存。
destroyed
:此阶段会销毁keep-alive
组件,会删除全部的以缓存组件。
4. Vue 中常见的指令
回答:
v-bind
:用于绑定一个或多个属性,语法糖是:
,在Vue3
中也可以作用于<style>
中的css
属性.
v-model
:用于双向绑定,绑定的元素数据发生变化data
也会改变,data
发生变化元素展示的数据也会随之改变。
v-on
:用于绑定事件,如click
、input
、scroll
等事件,语法糖是@
。
v-if/v-else-if/v-else
:条件渲染,用逻辑判断来决定元素的展示或隐藏。
v-show
:条件展示,同样也是通过Boolean
值来判断元素的显示,内部实现是css
的display
属性。
v-html
:可以插入HTML
模板语句,一般用在富文本当中。缺点也很明显,容易受到 XSS 攻击。
v-for
:循环渲染,可以渲染多个相同的元素。
v-slot
:用于绑定插槽,传入对应的插槽名进行绑定。语法糖是#
。
v-pre
:跳过编译,可以跳过Vue
的编译阶段。比如标签中使用{{}}
插值语句时,可跳过编译,当普通字符处理,最后直接展示{{}}
。
v-once
:只渲染一次,指定的标签渲染一次之后会视为静态元素,跳过更新,达到性能优化。
v-memo
:缓存,传入数组,当下次更新时会比对数组的值,假如发生变化则更新,没有发生变化则不会更新。只传入[]
空数组时和v-once
一样。通常和v-for
配合使用,达到性能优化。
5. v-if 和 v-show
回答:
v-if
和v-show
都是条件判断渲染元素,进一步区分,前者是渲染,后者是展示。主要是根据它们的内部实现,v-if
会在编译阶段添加类似if(true/false) { render(Vnode) }
来实现条件渲染,false
时元素也会在DOM
中消失,所以也造成在频繁切换的场景性能花销变大,更加适合在只渲染一次的场景,减少DOM
结构。v-else
则是通过css
的display
属性,当display:none
隐藏,display:block
展示,元素在display:none
时也不会销毁,所以更适用频繁切换场景。6. 修饰符
回答:
- 事件修饰符
.stop
:阻止事件冒泡。
.prevent
:阻止默认事件。
.self
:只有事件在自身元素触发时调用函数。
.capture
:捕获事件,内部元素的事件在被内部元素处理之前,先被外部元素处理。
.once
:事件只触发一次。
.passive
:一般用于触摸事件的监听器,可以改善移动设备的滚动性能。
- 按键修饰符
按键修饰符一般对于键盘的按键,种类很多。
.enter
:Enter
。
.esc
:Esc
。
.meta
:Windows
是Win
,Mac
是Command
。
- ...
- 鼠标按键修饰符
.left
:鼠标左键。
.right
:鼠标右键。
.middle
:鼠标中键。
- Vue3 中移除的修饰符
.sync
:是v-bind:xxx
和@input:xxx
的语法糖,在 Vue3 中已移除,使用v-model
代替。
7. 组件间的传参
回答:
- 父子组件通信
- 父传子(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 谁的优先级更高
回答:
在
Vue2
中v-for
高于v-if
,Vue3
中相反。所以在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-for
的key
。11. data 为什么必须是一个函数
回答:
data
作为组件的属性,而且一个组件可以被多个组件调用,假如以对象形式会出现变量污染的情况,所以使用函数创建函数作用域,这样组件被其他组件调用时会生成一个私有作用域,防止了数据污染的情况。12. v-model 的原理
回答:
- Vue2
- 对于原生表单元素,
v-model
实际上被编译成了一个:value
的绑定和一个@input
的事件监听。例如:
被转换为:
- Vue3
- 默认绑定名称的变化
- 对于自定义组件,Vue3 默认将
v-model
映射为: prop
名称为modelValue
。- 事件名称为
update:modelValue
。 - 例如:
实际上被编译转换为:
- 支持多个 v-model
- Vue3 允许在一个组件上使用多个
v-model
绑定,通过传递参数来指定不同的 prop 与事件。例如: - 这里:
v-model:modelValue
对应 propmodelValue
和事件update:modelValue
。v-model:checked
对应 propchecked
和事件update:checked
。
13. computed 和 watch 的作用是什么,有什么区别?
回答:
- computed
- 作用:
- 用于声明基于响应式数据的计算属性,自动根据依赖的数据进行计算,并将结果缓存起来。
- 当依赖的响应式数据没有变化时,
computed
属性不会重新计算,从而提高性能。 - 特点:
- 缓存性:
computed
的结果是缓存的,只有当它依赖的数据发生变化时才会重新计算。 - 同步计算:计算属性是同步执行的。
- 适用场景:当你需要基于已有数据计算出新的数据,并且这个数据在模板中多次使用时,推荐使用
computed
。
- watch
- 作用:
- 用于监听某个或某些响应式数据的变化,当数据变化时执行回调函数,通常用于处理异步操作或开销较大的操作。
- 适合在数据变化时执行副作用操作,如发送 AJAX 请求、手动操作 DOM 或进行复杂逻辑处理。
- 特点:
- 无缓存性:每次数据变化都会触发回调函数,不会对结果进行缓存。
- 异步支持:可以处理异步任务或延迟操作。
- 适用场景:当你需要在数据变化时执行额外操作(例如数据格式化、请求新数据或动画触发等),使用
watch
更合适。
- 区别
- 缓存 vs 非缓存:
computed
:具有缓存机制,依赖的数据不变时不会重复计算。watch
:每次数据变化都会触发回调,无缓存机制。
- 用途:
computed
:用于声明依赖其他响应式数据的计算属性,适合需要在模板中直接使用并依赖缓存的场景。watch
:用于观察数据变化并执行副作用操作,适合处理异步或复杂逻辑任务。
- 执行方式:
computed
:同步计算属性值。watch
:可以处理异步任务,允许在数据变化时执行额外操作。
14. Vue3 有哪些变动。
回答:
- 响应式原理的变动,由
bject.defineProperty
变为Proxy
;
- diff 算法优化;
- 删除
filters
和mixin
,用methods
和hooks
替代;
- 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 的响应式原理。
回答:
- 什么是响应式
响应式就是在我们修改数据之后,无需手动触发视图更新,视图会自动更新。
- Vue2 响应式实现
Vue2 中,响应式系统是通过依次遍历
data
返回的对象,将里面每一个属性通过 Object.defineProperty
进行定义,然后在属性描述符中添加 get/set
,实现 getter/setter
方法,在访问属性时,在 getter
函数中收集依赖(记录哪些方法或变量在使用这个属性),在修改属性时,在 setter
函数中派发依赖(将收集到的依赖依次更新),从而达到响应式。- 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 的区别是什么?
回答:
- reactive
从源码可以看出,
reactive
需要传入一个对象,如果是一个只读的对象,那就返回原对象,否则就调用 createReactiveObject()
函数,返回一个经过 Proxy
代理的对象。- ref
从源码可以看出,创建一个
ref
变量,如果传入的变量已经是一个 ref
变量了,就直接返回这个变量,如果不是就会返回一个 new RefImpl()
,new
这个类又做了什么呢?其实就是把我们传入的变量进行 toReactive
然后赋值给 this._value
,当我们访问 value
的时候,通过 get
函数,给我们返回了 this._value
。说白了就是给我们的变量包裹了一层对象,然后转变成了 reactive 对象。- 区别
既然都是用 reactive 实现的,为什么不都用 reactive 呢?这就要说到它们的区别了。
- Vue3 的响应式原理是基于
Proxy
的,而Proxy
只能代理对象,如果我们要实现一个基本数据类型的响应式怎么办呢?只能通过将它变成对象的方式;
reactive
只能传入一个对象,而 ref 可以传入任何类型;
ref
声明的变量,我们在访问时,除了模板之外,必须使用xxx.value
,而reactive
不用;
reactive
声明的变量可能会造成响应式丢失,这也是为什么官方更推荐使用 ref 的原因;
- reactive 响应式丢失
乍一看这种方式挺正常的,但是这种方式会引起响应式丢失,第一次使用
reactive
创建对象 obj
时,obj
是一个正常的被 Proxy
代理的对象,它是响应式的。但是当我们给 obj
重新赋值时,相当于改变了 obj
的内存地址,此时 obj
变成了一个非响应式的普通对象,于是造成了响应式丢失。21. SPA 页面首页白屏如何优化。
回答:
SPA 页面首页白屏的原因是因为所有资源都需要在首页加载,因此优化首页白屏就是要优化首页资源的加载。
- 第三方库如果能进行按需引入就采用按需引入,如果不行可以采取 CDN 的方式引入;
- 尽量减少图片资源,使用字体图标或精灵图,对大图使用 TinyPng 对图片资源进行压缩,并且使用 CDN 引入图片;
- 代码层面,检查首页代码是否有长耗时的同步任务阻塞了页面的渲染;
- 开启 gzip 压缩;
- 打包出来的
index.html
文件中的script
标签,使用defer
异步加载或者放到body
之后;
- 利用
webpack
等打包工具进行分包,避免首页一次性加载太多资源;
Loading...