Vue2 面试题 (2023-09-22更新)
基础
Vue2.0 兼容 ie 哪个版本?
不支持 ie8,部分兼容 ie9,完全兼容 ie10
因为 vue 的响应式原理是基于 es5 的 Object.defineProperty
这个方法不支持 ie8 及以下
解释下 MVVM 模式?
MVVM 是 Model-View-ViewModel 的缩写
model 数据层:处理业务逻辑和数据
view 视图层:UI 视图,负责数据的展示
viewModel 负责监听 model 中数据的改变并控制视图的更新,处理用户操作
Vue 的优点?缺点?
优点:
- 数据驱动 (自动计算属性和追踪依赖的表达式)
- 组件化 (可以复用,解耦的组件来构建页面)
- 轻量(代码量小,不依赖其他库,大小只有几十 kb)
- 渐进式
- 虚拟 DOM
- 单页面路由
- 数据与视图分离
缺点:
- 单页面不利于 seo
- 不支持 ie8 及以下
- 首屏加载时间长
Vue 的两个核心?
- 数据驱动
- 组件化
数据
为什么 data 是一个函数而不是对象?
解释:
当组件中的 data
使用对象时,因为对象是引用类型,所有组件实例的 data
都会指向同一个内存地址。在其中一个组件中修改数据,其他组件的数据也会发生变化
而使用返回对象的函数,每次返回一个新的对象,函数返回的对象内存地址并不相同。则不会存在这个问题
下面用代码举例说明:
我们模仿一个组件构造函数,定义 data
属性,采用对象的形式
function Component() {}
Component.prototype.data = {
count: 0
}
创建两个组件实例:
const componentA = new Component()
const componentB = new Component()
修改componentA
组件data
属性的值,componentB
中的值也会发生变化
console.log(componentB.data.count) // 0
componentA.data.count = 1
console.log(componentB.data.count) // 1
通过代码我们能看出:vue
组件就是一个 vue
实例,通过构造函数来创建。每个实例都会继承原型上的方法属性。data
就是 vue
原型上的一个属性,当是对象的时候,每个实例的 data
就会指向同一个内存地址,每个组件之间的数据就会相互影响
如果我们采用函数的形式,则不会出现这种问题(函数返回的对象内存地址并不相同)
function Component() {
this.data = this.data()
}
Component.prototype.data = function () {
return {
count: 0
}
}
修改componentA
组件data
属性的值,componentB
中的值不会受影响
console.log(componentB.data.count) // 0
componentA.data.count = 1
console.log(componentB.data.count) // 0
动态给 data 添加一个属性会发生什么?怎么解决?
现象: 数据会更新,但视图不会更新
原因: 动态添加的属性没有通过 Object.defineProperty
设置成响应式数据
data 中哪些对象操作无法监听?为什么?怎么解决?
- 对象属性的添加、删除
原因: 因为 vue
的响应式原理是通过 Object.defineProperty
来实现的。只能劫持到对属性的获取或修改,无法追踪新增和删除属性。
解决方法:
- 属性添加可以使用
$set
this.$set(this.obj, 'key', newValue)
- 属性删除可以使用
$delete
this.$delete(obj, key)
data 中哪些数组操作无法监听?为什么?怎么解决?
- 利用索引直接设置一个数组项时:
this.arr[inx] = 1
- 直接修改数组的长度:
this.arr.length = 10
原因:Vue 没有对数组的属性进行劫持
解决办法:
this.arr.splice(inx, 1, 新数据)
this.$set(this.arr, inx, 新数据)
怎么重置数据?
- 重置整个 data
Object.assign(this.$data, this.$options.data())
// this.$data 用于获取Vue实例的data选项
// this.$options 用于获取Vue实例的初始化选项
- 重置单个对象数据(比如重置 form 表单数据)
Object.assign(this.formData, this.$options.data().formData)
不需要响应式的数据应该怎么处理?
- 使用
Object.freeze
data() {
return {
list: Object.freeze([{count: 1}])
}
}
- 在 data 外定义
data(){
this.list = [{count: 1}]
return {}
}
watch 和 computed 的区别?
- watch 适合一个数据影响多个数据。当需要在数据变化时:执行异步操作或开销较大的操作时(搜索)
- computed 适合一个数据受多个数据影响。是基于他的响应式依赖进行缓存的,只有相关响应式依赖属性发生变化才会重新计算(计算价格)
watch 怎么初始化的时候被立即调用?
在选项参数中添加 immediate: true
将立即以表达式的当前值触发回调
watch: {
count: {
handler(newVal, oldVal) {
// ...
},
immediate: true
}
}
watch 怎么深度监听对象变化?
在选项参数中添加 deep: true
watch: {
count: {
handler(newVal, oldVal){
// ...
},
deep: true
}
}
computed 中的属性名和 data 中的属性名可以相同么?
不能同名,因为 computed 属性、data 属性、props 属性都会被挂载 vm 实例上
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
data 属性名和 methods 的属性名可以相同么?
不能同名,有限制,会报错
let data = vm.$options.data
const keys = Object.keys(data)
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
}
指令
列举常用的指令有哪些?
v-for
、v-if
、v-show
、v-bind(:)
、v-on(@)
、v-model
、v-else
、v-else-if
、v-html
、v-text
、v-once
列举表单修饰符和事件修饰符
事件修饰符:
.stop
阻止事件冒泡.prevent
阻止默认事件(a 标签、浏览器鼠标事件、form 表单提交).capture
事件默认是冒泡,capture 作用是捕获.self
只有绑定点击事件的本身才会触发事件.once
监听一个自定义事件,只会触发一次,一旦触发后,监听器就会被移除.native
加在自定义组件的事件上,保证事件能执行(解决给组件绑定自定义事件无效).sync
父组件传值加上这个修饰符,子组件可直接修改传过来的值
表单修饰符:
.lazy
改变输入框的值时 value 不会改变,当失去焦点的时候。v-model 绑定的值 value 才会改变.trim
去掉 v-model 绑定的值首尾空格.number
将值转换成数字(先输入字符串和先输入数字,是两种情况,先输入数字的话,只取前面数字部分,先输入字母的话,number 修饰符无效)
v-if 和 v-show 的区别?
-
相同点:都可以控制元素的显示和隐藏
-
不同点
v-if
是通过创建和销毁元素来达到显示隐藏v-show
是通过css
的display
属性来达到显示隐藏
-
性能消耗
v-if
有更高的切换消耗,v-show
有更高的初始化渲染消耗,频繁显示隐藏用v-show
v-if 和 v-for 的优先级是什么?
两者作用于同一标签时,v-for
的优先级比 v-if
高。
尽量不要把 v-if
和 v-for
使用同一个元素上,会带来性能方面的浪费(每次渲染都会先循环再判断)。可以使用 computed
进行过滤
v-for 遍历,是按照什么顺序遍历的?如果保证顺序?
按照 Object.keys()
的顺序遍历
转成数组保证顺序
v-model 的实现原理?
v-model 是分别利用了 v-bind 绑定 value 的值,v-on 绑定 input 事件,通过$event 进行赋值
<input :value="data" @input="data=$event.target.value" />
v-once 有什么用?
只渲染元素和组件一次。下次渲染,会被视为静态内容并跳过
内置组件
谈谈你对 keep-alive 的了解?
含义:keep-alive
是一个抽象组件,他自身不会渲染成一个 DOM 元素,也不会出现在父子组件中
作用:使用 keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们
属性介绍:include
、exclude
、max
- include:缓存白名单,包含的组件会被缓存
- exclude:缓存黑名单,包含的组件不会被缓存
- max:最多可以缓存多少组件实例,一旦达到这个数字,在新实例被创建前,已缓存组件中最久没有被访问的实例会被销毁掉
keep-alive
的基本用法:
<keep-alive>
<component :is="view"></component>
</keep-alive>
使用includes
和 exclude
:
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
keep-alive 的原理?
keep-alive 是一个组件,组件有三个属性:include
、exclude
、max
在其内部维护了一个 key 数组和一个缓存对象:key
数组记录目前缓存的组件key
值,如果组件没有指定key
值,会自动生成一个唯一的key
值。缓存对象 cache
,会以key
值为键,vnode 为值,用于缓存组件对应的虚拟 DOM
在mounted
中会监听include
和exclude
属性,进行组件的缓存处理,如果发生变化会动态的添加和删除属性
在keep-alive
的渲染函数中,会判断当前渲染的vnode
是否有对应的缓存,如果有会从缓存中读取对应的组件实例,如果没有就会把他缓存
当缓存的数量超过max
设置的数值时,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉
为什么 keep-alive,不会渲染成真正的 DOM 节点
vue
底层中有一个 abstract
属性,会根据该属性决定是否忽略某个组件
在 keep-alive
中设置了 abstract:true
表示该组件为抽象组件,不会被渲染 DOM,跳过该组件实例
跟 keep-alive 相关的生命周期有哪些?
activated
keep-alive
组件激活时调用deactivated
keep-alive
组件停用时调用
以上钩子服务端渲染期间不被调用
组件通信
组件通信都有什么方式?
props
和$emit
$parent
、$children
、$refs
provide
、reject
$attrs
、$listeners
envetBus
vuex
父子组件通信?
props 和 $emit
- 父传子:父组件在子组件标签上绑定数据,子组件设置 props 来接收
- 子传父:子组件通过
$emit
触发自定义事件,第二个参数为传递的数据,父组件绑定自定义事件来监听传过来的数据
prop 验证的 type 类型有哪几种?
String
、 Number
、Boolean
、 Array
、 Object
、 Date
、 Function
、 Symbol
子组件怎么直接修改父组件传过来的数据?
父组件在传值的时候加上.sync
,子组件在自定义事件加上update:
// 父组件
<children :count.sync="count"/>
// 子组件
this.$emit('update:count')
依赖注入怎么使用?解决了什么问题?
provide 和 inject
作用:解决了嵌套组件的通信问题
使用:
- 在祖先组件定义 provide 属性,返回向下传递的值
- 在后代组件中通过 inject 接收传过来的值
注意:传递的值需要是对象才能实现响应式,如果是基本类型就无法实现响应式
// 父组件
export default {
provide() {
return {
count: 1
}
}
}
// 子组件
export default {
inject: ['count']
}
生命周期
Vue 生命周期有哪些?
beforeCreate
Vue 实例创建前:数据初始化还没有完成,像 data、props 等属性还无法访问created
Vue 实例创建后:完成了数据观测(data、props、computed 等),属性和方法(method)的运算。完成了数据代理beforeMount
实例挂载前:生成了 Watcher,调用 render 函数,生成虚拟 DOM,但是还没转换成真实 DOMmounted
实例挂载后:真实 DOM 挂载完毕beforeUpdate
数据更新前调用:新的虚拟 DOM 生成,但是还没有对比打补丁updated
数据更新后调用:新旧虚拟 DOM 对比打补丁,进行真实 DOM 的更新beforeDestory
实例销毁前调用:此时实例仍然完全可用,可以访问数据destoryed
实例销毁之后调用:此时实例的所有东西都会解绑,所有的事件监听器都会被移除,所有的子实例也会被销毁
父子组件 渲染 生命周期顺序
父beforeCreate
→ 父created
→ 父beforeMount
→ 子beforeCreate
→ 子created
→ 子beforeMount
→ 子mounted
→ 父mounted
父子组件 更新 生命周期顺序
父beforeUpdate
→ 子beforeUpdate
→ 子updated
→ 父updated
父子组件 销毁 生命周期顺序
父beforeDestory
→ 子beforeDestory
→ 子destoryed
→ 父destoryed
在 created 和 mounted,这两个生命周期中请求数据有什么区别?
在created
中,页面视图未出现,如果请求信息过多,会阻塞渲染,页面会长时间处于白屏状态
在mounted
中,页面已经渲染完成,则不会有这种情况
第一次页面加载会触发哪几个钩子?
beforeCreate、 created、 beforeMount、 mounted
DOM 渲染是在那个生命周期完成的?
在 mounted 生命周期
内置 API
$nextTick 有什么作用?
将$nextTick
中的回调延迟到下次 DOM
更新循环结束之后执行。
Vue
采用的是异步更新策略,同一事件循环内,多次修改,会统一进行一次视图更新,所以数据一更新,视图还未更新,所以拿到的还是上一次的旧数据
$nextTick 的实现原理?
$nextTick
主要使用异步队列来控制
Vue
数据响应过程:数据更新 → 通知 Watcher → 更新 DOM,其中的数据更新 DOM 是个微任务
$nextTick
就是将回调函数添加到了更新 DOM
的 microTask
队列中,确保代码在 DOM
更新后执行
根据浏览器的渲染机制,渲染线程是在微任务执行完成之后执行的,渲染线程还没运行,怎么获取到 dom 的呢?
渲染线程只是把 DOM 树渲染成 UI 而已。
Vue 更新 DOM 之后,在 DOM 树里,新的 DOM 已经存在,就可以拿到新的 DOM 了
属性
key 的作用?
给元素添加了标识,优化复用对比策略,提高渲染性能
Vue 为了高效渲染元素,默认会复用已有元素而不是重新渲染。为元素添加 key,当列表数据发生变化,会根据 key 找到已渲染的元素进行复用,对顺序不匹配的元素进行位置的调整
如果不使用 key,默认采用“就地更新”策略,如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素
CSS
为什么 style 上加 scoped 能实现样式私有化?
Vue
通过 DOM
结构以及 css
样式上,加上唯一的标记data-v-xxxxx
,保证唯一,达到样式私有化,不污染全局的作用
dom 上的 data-v-xxxxx 是做什么的?(和上面问题一样,只是问法不一样)
这是在 Vue
中,写 css
时使用 scoped
标记产生的,保证了各个组件中的 css
不相互影响,给每个组件都做了唯一标记
深度选择器
- >>>
只能作用于 css,使用了预处理语言的 css 不生效
<style>
>>> .el-input {
width: 100px;
}
</style>
- /deep/ (只能在 vue2 中使用)
只能作用于 less 或者 sass
<style lang="less">
/deep/ .el-input {
width: 100px;
}
</style>
- ::v-deep (目前 vue2 和 vue3 都可使用)
只能作用于 less 或者 sass
<style lang="less">
::v-deep .el-input {
width: 100px;
}
</style>
- :v-deep() (vue3 中使用)
<style lang="less">
:v-deep(.el-input) {
width: 100px;
}
</style>
怎么动态绑定 class?
- 对象语法
<div :class="{'active': flag}"></div>
<script>
data(){
return {
flag: true
}
}
</script>
- 数组语法(三元表达式)
<div :class="[flag ? 'active' : 'default']"></div>
<script>
data(){
return {
flag: true
}
}
</script>
- 对象和计算属性直接返回
<div :class="createClass"></div>
<script>
data(){
return {
flag: true
}
},
computed: {
createClass(){
return {
active: this.flag
}
}
}
</script>
原理
配置 proxy 为什么能解决跨域问题?
跨域问题是存在于浏览器端,服务器与服务器之间的请求不存在跨域问题
配置 proxy 相当于在本地创建了虚拟服务器,使用虚拟服务器去代理请求
Vue 数据响应式原理?
Vue2
采用数据劫持结合发布订阅的方式,通过 Object.defineProperty
劫持属性,在数据变化时,发布消息给订阅者,触发相应的监听回调
在 Vue
实例初始化的时候,会对数据进行递归遍历,通过 Object.defineProperty
对数据属性进行代理。
这样做的作用是:
- 当渲染视图的时候,会先编译
Vue
中的template
模板,解析模板指令,将模板中的变量替换成数据,这样就会触发 getter 进行依赖收集,依赖收集是指将订阅者Watcher
存放到订阅器Dep
里面 - 当数据发生变化时,会触发 setter,setter 会通知订阅器
Dep
,订阅器会循环遍历之前收集到的Watcher
,告诉他们自己的值改变了,需要重新渲染视图,这时候Watcher
就会调用自身的update
来更新视图
进阶
为什么 Vue 中 data 更新后,不能立即获取到 DOM?
受异步更新策略影响,Vue 在修改数据后,视图不会立即更新,而是等同一事件循环的所有数据变更完成之后,在统一进行视图更新
讲一下 Vue 实例挂载过程?
说说 Vue 模板的编译过程?
Vue 模板编译(Vue template compilation)是指将 template 模板转换为可执行的 JavaScript 代码的过程
Vue 模板编译分为以下几个阶段:
- 解析代码(Parsing):这个阶段主要是解析器(Parser)解析模板中的 HTML 标签、文本内容、指令等,生成抽象语法树(AST Tree)
- 优化阶段:遍历 AST 树,找出其中的静态节点,打上标记,(作用是:diff 算法会直接跳过静态节点,从而减少了比较的过程,优化了 patch(对比打补丁)的性能)
- 生成代码(Code Generation):将 AST Tree 转化为可执行的 JavaScript 代码。代码生成器遍历 AST Tree 生成对应的渲染字符串和渲染函数,这个渲染函数主要是在实例挂载和 DOM 更新时被调用
- 运行时(RunTime):当 Vue 实例被创建并挂载到 DOM 上时,渲染函数会被调用,生成对应 DOM 结构。当数据发生变化时,渲染函数会重新执行,更新 DOM,在此过程中,Vue 会使用虚拟 DOM 技术来减少实际的 DOM 操作次数,提高性能
watch、methods 中的属性和方法,可以使用箭头函数定义么?
不可以,this 会是 undefined
所有的生命周期钩子,内置方法,自动绑定 this 上下文到实例中,因此可以访问数据,对属性和方法的运算。
箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例
复用性、扩展、组合
组件和插件的区别?
1. 编写方式的不同
- 组件编写:最常见的就是 vue 单文件的这种格式,每个.vue 文件都可以看成是一个组件
- 插件编写:会暴露一个 install 方法,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
2. 注册方式的不同
- 组件注册:分为全局注册和局部注册,通过
Vue.component(component)
- 插件注册:通过
Vue.use(plugin)
3. 使用场景
- 组件 Component:解耦页面,拆分和封装页面公共的业务模块
- 插件 Plugin:扩展 Vue 的功能
谈谈你对自定义指令的理解?
指令本质是装饰器,是 Vue 对 HTML 元素的扩展,增加自定义功能
自定义指令五个生命周期:
- bind 只调用一次,指定一个绑定到元素时调用
- inserted 被绑定元素插入父节点时调用
- update 被绑定元素所在的模块更新时调用
- componentUpdated 被绑定元素所在模块完成一次更新时调用
- unbind 只调用一次,指令与元素解绑时调用
混入(mixins)
- 局部混入
const user = {
data() {
return {
userName: 'Jane'
}
}
}
export default {
mixins: [user]
}
-
全局混入
2.1. 新建 mixin.jsimport Vue from 'vue' Vue.mixin({ data() { return { userName: 'Sasa' } } })
2.2 在 main.js 中引用
import '@/mixin/mixin.js'
混入(mixins)的作用?有什么需要注意的地方?
作用:用来抽离公共的数据和方法,提高代码复用性
需要注意如下:
- 同名函数合并为数组,都会执行,先执行 mixin 中的同名函数
- 同名数组会进行替换,组件内的数据会将 mixin 的进行覆盖
- 应该尽量只在页面的父组件(包含所有页面的组件)中使用混合。尽量不通过使用
Vue.mixin()
全局设置,通过这种方式,会为所有组件导入它,混入的方法会被执行多次
mixin 可以定义公用的变量和方法,mixin 中的数据和方法是独立的,每个组件中的 mixin 实例都是不一样的,不存在相互影响
其他
属性书写顺序
export default {
name: '',
mixins: [],
components: {},
props: {},
data() {},
computed: {},
watch: {},
created() {},
mounted() {},
destroyed() {},
methods: {}
}
Vue 和 jquery 的区别?
- jquery 是直接操作 DOM。Vue 数据和视图分离,不直接操作 DOM,只需要操作数据即可
- jquery 操作的是真实 DOM,操作行为也很频繁。Vue 利用虚拟 DOM 技术,提高了更新 DOM 的性能
- Vue 集成的一些库,比如 Vuex、Vue-Router 等,可以提高开发效率
assets 和 static 的区别?
相同点:两者都存放静态资源文件的
不同点:assets 存放的文件,在打包的时候,会进行打包压缩体积。
statis 存放的文件不会进行打包压缩,而是存入指定的目录,因为跳过了这一流程,在打包时会提高一定效率,但因此也会占用服务器更大空间
name 选项有什么作用?
- 递归组件。调用自身时使用
is
和component
时使用keep-alive
中,include
和exclude
使用
怎么强制刷新组件?
- this.$forceUpdate()
- 组件加上 key,变化 key 的值
组件在什么时候会被销毁
- 没有使用
keep-alive
,进行路由切换 v-if = "fasle"
this.$destory()
Vue 组件里写的原生 addEventListener 事件,需要手动销毁么?为什么?需要注意什么?
需要在 beforeDestory()
中销毁,不然会造成多次绑定和内存泄露
注意事项:
-
执行函数必须使用外部函数
window.addEventListener('resize', this.onResize)
-
执行函数不能是匿名函数
window.addEventListener('resize', () => { this.onResize })
-
执行函数不能改变 this 指向
window.addEventListener('resize', this.onResize(this))
-
addEventListener 和 removeEventListener 第三个参数必须一致
window.addEventListener('resize', this.onResize, true) window.removeEventListener('resize', this.onResize, true)
在 Vue 事件中是如何使用 event 对象的?
@click="onClick($event)"
事件参数中传入 event
对象
在 Vue 事件中传入$event,使用 $event.target 和 $event.currentTarget 有什么区别?
- $event.currentTarget 指向事件所绑定的元素
- $event.target 指向事件触发的元素
什么是虚拟 DOM?
虚拟 DOM
是对真实 DOM
的抽象,用 JavaScript
对象作为一个容器,用对象的属性来描述节点,最终通过一系列操作来映射到真实 DOM
上
为什么需要虚拟 DOM?
操作真实 DOM
是很慢的,哪怕一个最简单的 div
也会包含很多属性
操作 DOM
的代价是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验
举例
比如用 jquery
,操作的都是真实 DOM
当在一次操作时,需要更新 10 个 DOM
节点,浏览器没那么智能,收到第一个更新 DOM
请求后,会马上执行流程,最终会执行 10 次
而通过 VNode
,同样更新 10 个 DOM
节点,虚拟 DOM
不会立即操作 DOM
,而是通过 diff
算法比较需要更新的地方,将这 10 次更新的内容保存到本地的一个 js
对象中,最终将这个 js
对象一次性更新到 DOM
树上,避免了大量的无用计算
本文来自博客园,作者:时光凉忆,转载请注明原文链接:https://www.cnblogs.com/naturl/p/11088016.html