前言
- 目前这套面试题只适合 初级前端,后面会进行深层次补充和拓展以及Vue2源代码的讲解(虽然Vue2今年开始不维护了,但是我的面试题不会止步,冲冲冲)。
- 在面试的过程中,一定要清楚哪些该说哪些不该说,如果一个知识点不太清楚,就不要做过多的解释,一笔带过就可以。
- 给孩子点点关注吧!😭
Vue2相关面试题
1. 谈谈对Vue的理解
-
Vue是一种用于构建用户页面的渐进式JavaScript框架,也是一个创建SPA单页面应用的Web应用框架,Vue的核心是数据驱动试图,通过组件内特定的方法实现视图和模型的交互。
-
特性:(看自己要不要对每一项特性进行解释,如果解释可能会牵扯出很多面试题)
-
数据驱动试图:
- MVVM是一种双向数据绑定的模式,用ViewModel来建立起Model数据层和View视图层的连接,数据和视图的改变是双向的。
- 可能会问:MVVM 和 MVC 的区别?(见4)
-
组件开发:
- 组件可以在项目中直接进行复用,出现问题时可以实现快速定位,能够提高代码可复用性和可维护性。
- 可能会问:如何封装一个组件?(见后面)
-
指令系统:
- Vue内置很多 v- 系列的指令,可以响应式的作用于DOM,比如v-if条件渲染,v-for列表渲染,v-model双向数据绑定等。
- 可能会问:自定义封装指令的一些知识(见后面)
-
-
缺点:
- 不利于SEO优化:
- 解决办法:
- SSR服务器渲染;
- 静态化;
- 预渲染;
- 使用Phantomjs针对爬虫做处理;
- 解决办法:
- 首屏加载速度慢:加载时,将所有的css,js文件及逆行加载;
- 不支持IE678(IE也不用了😂);
- 有些数据不需要响应式,但必须写在data里,影响加载速度和性能;
- Vue的响应式是通过Object.defineProperty:
- 无法监听ES6的Set、Map变化;
- 无法监听Class类型的数据;
- 属性的新加或删除无法监听;
- 数组元素的增加和删除无法监听;
- 不利于SEO优化:
2. Vue最大优势
- 轻量级框架,简单易学,数据双向绑定,虚拟DOM,组件化;
- 文档都是中文的,入门教程很多,上手简单;
- Vue是单页面应用,使页面局部刷新,不用每次跳转页面都去请求所有的数据和DOM,加快了访问速度和提升用户体验;
- 相比传统的页面通过超链接实现页面的切换和跳转,Vue使用路由,不会刷新页面;
- 使用Vue编写出来的界面效果本身就是响应式的,这种网页在各种设备上都能显示出非常好看的效果;
- 第三方UI库使用起来非常方便,节省了很多开发时间,从而提升了开发效率。
3. Vue 和 jQuery 的区别是什么?
-
jQuery:
- 应该算是一个插件,里面封装了各种简单易用的方法,它的本质就是使用更少的代码操作DOM节点,它是使用选择器获取DOM对象,对其进行赋值、取值、事件绑定等操作,对数据的操作依赖于对应的DOM对象;
-
Vue:
- 一套渐进式的框架,拥有自己的规则体系和语法,特别是MVVM的设计思想,让数据和视图进行双向绑定,极少操作DOM,对数据进行操作不再依赖于对应的DOM对象。
4. MVVM 和 MVC 区别是什么?
-
MVC:
- 一种设计模式,是Model数据模型,View视图,Controller控制器,在控制器这层里面编写代码,控制数据和视图进行关联,MVC是单向通信;
-
MVVM:
-
既Model-View-ViewModel的简写(模型-视图-视图模型),VM是整个设计模式的核心,是用来连接视图和模型的桥梁;
-
有两个方向:
- 首先:模型转换为视图,将从后端请求回来的数据转换为网页;
- 实现方式:数据绑定;
- 其次,视图转换为模型,将网页转化为后端的数据;
- 实现方式:监听DOM事件。
- 这两个方向都实现的,我们称为数据的双向绑定。
- 首先:模型转换为视图,将从后端请求回来的数据转换为网页;
-
-
区别:
- MVC是单向通信,MVVM是双向通信;
- 主要是MVC中的Controller演变成了MVVM中的VM,MVVM主要解决了MVC中大量的DOM操作导致的页面渲染性能降低,加载速度慢,影响用户体验。
5. Vue常用修饰符
- 事件修饰符:
.stop => 阻止事件冒泡
.prevent => 阻止事件默认行为
.once => 程序运行期间,事件处理函数只执行一次
.native => 原生事件(使用组件库的时候可能会用到)
- 按键修饰符:
.enter ➡ 监测Enter键
.esc ➡ 监测ESC键
- v-model修饰符:
.number ➡ 尝试用parseFloat转数字
.trim ➡ 去除字符串首尾两侧的空白字符
.lazy ➡ 内容改变并且失去焦点触发
- 特殊:
.sync ➡ 可以在子组件内部直接修改父组件的值
格式:
父组件:<子组件 变量名.sync="数据属性"/>
this.$emit('update:对应的属性名', 值)
注意:此处的 update:对应的属性名 不能有空格
见下图
具体使用如下:
- 父组件:
<template>
<div class="home">
<HelloWorld
msg="Welcome to Your Vue.js App"
:type.sync="type"
:arr.sync="arr"
:obj.sync="obj"
/>
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "HomeView",
data() {
return {
type: 1,
arr: [1, 2, 3],
obj: {
a: 1,
b: 2,
},
};
},
components: {
HelloWorld,
},
};
</script>
- 子组件:
<template>
<div class="hello">
<button @click="updateData">改变数据</button>
<span> {{ type }} </span>
<span> {{ arr }} </span>
<span> {{ obj }} </span>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
type: {
type: Number,
require: true,
},
arr: {
type: Array,
require: true,
},
obj: {
type: Object,
require: true,
},
},
methods: {
updateData() {
this.$emit("update:type", 2);
this.$emit("update:arr", [4, 5, 6]);
this.$emit("update:obj", {
a: "张三",
b: "李四",
});
},
},
};
</script>
6. Vue常用指令
v-bind:属性名 = "表达式" ➡ 给标签动态赋值
简写::属性名 = "表达式"
v-on:事件名 = "少量代码 / 函数名 / 函数名(实参)" ➡ 绑定事件
简写: @事件名 = "少量代码 / 函数名 / 函数名(实参)"
v-model = "表达式" ➡ 将表单元素的 value属性 和 Vue数据属性 进行双向绑定
可以使用v-model语法糖实现组件传值
v-for = "(值, 索引) in 目标结构" ➡ 循环列表
v-show = "表达式" ➡ 控制元素显示隐藏
v-if = "表达式" ➡ 控制元素显示隐藏
v-slot ➡ 插槽
7. v-show 和 v-if 的区别
-
共同点:
- 都可以控制元素的显示和隐藏(效果一样);
-
区别:
-
原理不同:
- v-show:本质就是通过CSS属性来让元素显示隐藏(display: none;);
- v-if:动态的 向DOM树 添加 或 删除 元素;
-
编译条件不同:
- v-show:不管条件真假与否,都会编译,如果是false,会将display设置为none,但它也编译了;
- v-if:初始值为false,就不会编译;
-
性能不同:
- v-show:
- 只编译一次,后面就是控制CSS;
- 产生更大的首次加载消耗;
- v-if:
- 不停的销毁和创建实例;
- 产生更大的切换消耗;
- v-show:
-
优先级不同:
- v-if > v-show
-
8. 为什么避免 v-if 和 v-for 一起使用,非要在一起使用该怎么办?
- Vue避免同时使用v-if和v-for是因为这样会影响性能。
- 当v-if和v-for同时存在时,v-for会先执行,然后才考虑v-if条件。如果列表很长,这样会导致不必要的计算,影响渲染性能。
- 一起使用:
- 可以先写if,再使用template标签包裹需要循环的内容,讲循环写在template标签上;
- 如果非要在一起使用,可以考虑使用计算属性来优化性能。计算属性可以先根据条件筛选出需要渲染的数据,然后再使用v-for渲染,这样可以避免不需要的计算和喧染,提高性能。
<template>
<div>
<div v-for="item in filteredList" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{ id: 1, name: "Apple", show: true },
{ id: 2, name: "Banana", show: false },
{ id: 3, name: "Orange", show: true },
{ id: 4, name: "Grape", show: false },
],
};
},
computed: {
filteredList() {
return this.list.filter((item) => item.show);
},
},
};
</script>
9. 数组更新有时候v-for不渲染
-
因为Vue内部只能监测数组 顺序 / 位置 / 数量 的改变;
-
如果是某个值被重新赋值或者使用了不改变原始数组的方法,Vue是监测不到的;
- 可能会问:改变原始数组和不改变原始数组的方法有哪些?
-
针对上述问题,有两种解决方案:
- 某个值被重新赋值:
this.$set(更新的目标结构, 改变元素的索引 / 对象的属性, 更新的值)
- 使用不改变原始数组的方法:
- 用得到的新数组替换旧数组。
- 某个值被重新赋值:
10. Vue中 key 的作用
-
当Vue用v-for正在更新已经渲染的元素列表时,它默认采用“就地复用”策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地复用此处每个元素,并且确保它在特定索引下显示已经渲染过的元素。
-
key:
- 一段唯一不重复的数字或字符串;
- 为了更高效的更新虚拟DOM;
- 是给v-for循环生成标签办法唯一标识的;
- 只做数据展示,不写key是没有任何影响的;
- key不会出现在真实DOM中;
-
为什么不能使用索引?
- 因为索引是连续的,如果删除其中一个会导致最后一个被删除;
- 当我们再删除的时候,key再根据数据来把新旧的DOM做对比,删除key不存在的对应的标签。
11. diff算法比较机制
-
根元素变化:
-
根元素未变:
- 顺序改变:
- 更新属性;
- 子元素/子元素内容改变:
- 按照key比较,如果没有key或者key是索引,尝试就地更新;
- 如果key是id,新旧虚拟DOM做对比,共有的部分不发生变化,没有的就在对应的位置插入DOM节点;
- 顺序改变:
-
key使用规范:
- 有id用id,没有id用索引;
- 一段唯一不重复的数字或字符串。
12. 怎么自定指令,有哪些钩子函数,对应的有哪些入参?
- 全局注册:
- 在 Vue对象 的
directive
方法里面有两个参数,一个指令名称,一个回调 / 对象; - 如果是个对象,在对象内部必须指定
inserted
方法。
- 在 Vue对象 的
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
- 局部注册:
- 组件中接受一个
directives
选项。
- 组件中接受一个
// 注册局部指令
directives: {
// 指令名
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
- 钩子函数:
bind() ➡ 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置;
inserted() ➡ 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中);
update() ➡ 所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前,指令的值可能发生变化,也肯能没有。但是你可以通过比较前后的值来忽略不必要的模板更新;
componentUpdated() ➡ 指令所在组建的VNode及其子VNode全部更新后调用;
unbind() ➡ 只调用一次,指令与该元素解绑时调用。
- 钩子函数参数:
13. 怎么封装一个组件,具体的过程是咋样的
-
组件提升了整个项目的开发效率,能够把页面抽离成相对独立的模块。解决了传统项目开发过程中的效率低、难维护、复用性差等问题;
-
组件的封装我们可以看作是一个函数的封装:
- 根据业务需求,把页面中可复用的template、script、style,抽离到一个单独的 .vue文件 中,实现复用;
- 上述步骤完成之后,还需要考虑入参和出参:
- 入参:使用 props机制;
- 出参:
- 使用 $emit机制;
- 使用 .sync修饰符(见5);
- 使用 v-model语法糖实现;
- 针对一些标签不确定的地方,我们可以使用插槽实现;
- 至此,组件的封装完成。
-
组件的使用步骤:
- 封装组件;
- 导入组件:
import 组件对象 from '组件路径'
- 注册组件;
- 全局注册:
Vue.component('组件名', 组件对象)
- 局部注册(常用):
- 使用组件:组件标签;
- 全局注册:
14. Vue组件中 data 为什么是个函数?
- Vue中每个组件都是一个实例;
- 组件共享data属性,当data的值是同一个引用数据类型的值时,改变其中一个其他的都会收到影响;
- 组件中的data写成一个函数,数据以函数返回值的形式定义,这样每复用一次组件,就会返回一份最新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护自己的数据;
- 单纯的写成对象的形式,就会使得所有的组件实例共享一份data,就会造成一变全变的结果。
15. Vue组件如何进行传值
-
props
和$emit
:- 父组件向子组件传递数据通过
props
传递的; - 子组件传递数据给父组件是通过
$emit
触发事件;
- 父组件向子组件传递数据通过
-
attrs
和$listeners
; -
中央事件总线bus:
- 上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系,这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过
bus.$emit
触发事件,bus.$on
监听触发事件。
- 上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系,这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过
-
provide
和inject
:- 父组件通过provider来提供变量,然后再子组件中通过inject来注入变量。不论子组件多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。
-
v-model
:- 父组件通过v-model传递值给子组件时,会自动传递一个
value
的prop属性,在子组件中通过this.$emit('input', value)
自动修改v-model绑定的值,父组件中也不用定义input事件。
- 父组件通过v-model传递值给子组件时,会自动传递一个
-
$parent
和$children
; -
boradcast
和dispatch
; -
Vuex
:- Vuex主要处理组件之间的数据交互,如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这些方法可能不利于项目的维护,Vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。
16. 组件中写 name 选项有什么用?
- 项目中使用
keep-alive
时,可以搭配组件 name 进行组件的缓存; - 使用插槽时,
name
属性可以作为占位标签的名字,供template
使用; Vue-devtools
调试工具里显示的组件名称是由Vue组件内部的name
属性决定的。
17. Vue该如何实现组件的缓存?
-
为什么需要组件缓存?
- 在面向组件开发中,会把整个项目拆分成多个业务组件,按照需求对组件进行整合;
- 存在组件频繁切换的问题,在这个过程中,组件的实例都是在不断的销毁和创建,很是消耗性能,并且如果需要该组件的数据的话,我们是获取不到的,所以需要对组件的状态进行缓存。
-
怎么实现组件的缓存?
- 使用
keep-alive
标签包裹需要被缓存的组件,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染; - 优点:提高渲染性能,提升用户体验。
- 使用
18. Vue中 keep-alive 的作用
keep-alive
是Vue内置的一个组件,可以使被包裹的组件保留状态,或避免重新渲染。一旦使用keep-alive
激活组件,此时mounted
、created
等钩子函数只会在第一次进入组件时调用,当再次切换回来时将不会调用。此时如果我们还想在每次切换时做一些事情,就需要用到另外的周期函数,actived
和deactived
,这两个钩子函数只有被keep-alive
包裹后才会调用。
19. 谈谈对 Vue 生命周期的理解
-
Vue实例从创建到销毁的整个过程,就是Vue的生命周期(四个阶段 + 八个钩子函数);
-
初始化阶段:
beforeCreate()
:- 此时,data数据和methids方法还没有挂载到Vue实例身上,无法使用(如果使用了,会报错,undefined);
created()
:- data数据和methods方法已经挂载到Vue实例身上,可以正常使用;
- 使用场景:发起异步请求(可以更早的获取数据,渲染页面);
-
挂载阶段:
beforeMount()
:- 将App.vue文件中的所有标签编译完毕(知识编译完毕,还没有变成真实DOM);
mounted()
:- 虚拟DOM变成真实DOM,此时可以获取DOM节点;
-
更新阶段:
beforeUpdate()
:、- data数据变化后更新,此时数据是最新的,但是DOM节点还不是最新的;
updated()
:- 当组件渲染完毕后执行,此时可以获取最新的DOM内容;
-
销毁阶段:
beforeDestroy()
:- 这一步,Vue实例仍然可以使用;
destroyed()
:- 实例销毁后调用,该钩子函数被调用后,对应的Vue实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
- 使用场景:销毁定时器;
- 单个定时器:直接调用
clearInterval()
或clearTimeout()
进行销毁; - 多个定时器:在data声明一个对象,使用
Object.keys()
获取该对象的所有属性名,进行循环销毁。
- 单个定时器:直接调用
20. 第一次页面加载会触发哪几个钩子函数?
beforeCreate()
、created()
、beforeMount()
、mounted()
- 第一次页面加载需要将data数据和methods方法挂载到Vue实例上v,并且需要将虚拟DOM变成真实DOM。
21. 父组件嵌套一个子组件,第一次加载的时候,钩子函数的触发顺序
父beforeCreate() ➡ 父created() ➡ 父beforeMount() ➡ 子beforeCreate() ➡ 子created() ➡ 子beforeMount() ➡ 子mounted() ➡父mounted()
22. 怎样理解Vue的单向数据流
-
数据从父组件传递给子组件,只能单向绑定;
-
子组件内部不能直接修改从父组件传递过来的数据;
-
所有的prop都使得其父子prop之间形成一个单向下行绑定:
- 父级prop的更新会向下流动到子组件中,但是反过来不行;
-
这样会防止从子组件意外改变父组件的状态,从而导致应用的数据流向难以理解;
-
额外的,每次父组件发生更新时,子组件中所有的prop都将会刷新为最新的值;
-
这意味着你不应该在一个子组件内部改变prop,如果这样做了,Vue会在浏览器的控制台中发出警告;
-
子组件向修改时,只能通过$emit()派发一个自定义事件,父组件接收后,由父组件修改。
22. 组件中写 name 选项有什么用?
- 项目中使用
keep-alive
时,可以搭配组件name进行组件缓存; - 使用插槽时,name属性可作为占位标签的名字,供template使用;
- Vue-devtools调试工具里显示的组件名称是由Vue组件内部的name属性决定的。
23. Vue该如何实现组件缓存
- 为什么需要组件缓存?
- 在面向组件开发中,会把整个项目拆分成多个业务组件,按照需求对组件进行整合;
- 组件频繁切换过程中,组件的实例都是在不断的销毁和创建,很消耗性能,并且如果需要该组件的数据的话,我们时获取不到的,所以需要对组件进行缓存;
- 怎样实现组件缓存?
- 使用
keep-alive
标签包裹需要被缓存的组件,会缓存不活动的组件实例,主要用域保留组件状态或避免重新渲染; - 优点:提高渲染性能,提升用户体验。
- 使用
24. 对Vue生命周期的理解
- Vue实例从创建到销毁的整个过程,就是Vue的生命周期(四个阶段 + 八对钩子函数)
- 初始化阶段:
beforeCreate()
:- 在实例化之后,数据的观测和事件的配置之前的时候调用,此时组件的选项对象还未创建,el和data并未初始化,因此无法访问methods、data、computed等上的方法和数据;
created()
:- 在创建之后调用,data数据和methods方法已经挂载到Vue实例身上,可以正常使用,通常在这个钩子函数里面会发起异步请求(原因:更早的拿到数据,更早渲染页面)。
- 挂载阶段:
beforeMount()
:- 在这个阶段是获取不到dom操作的,把data里面的数据和模板生成html,完成了data等初始化,但此时还没有挂载html页面上(此时是虚拟dom);
mounted()
:- 用于挂载之后使用,在这个时候可以获取dom操作,比如可以获取到ref等,操作的dom。
- 更新阶段:
beforeUpdate()
:- 在数据更新之前被调用,发生在虚拟DOM重新渲染,可以在该钩子中进一步地更改状态,不会触发重复渲染过程;
updated()
:- 在由于数据更改导致的虚拟DOM重新渲染会调用,调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作,然后在大多数情况下,应该避免在此期间更改状态,因为这可能导致更新无限循环,但是再服务端渲染。期间不能调用,可以用于监听某些数据的时候
- 销毁阶段:
beforeDestroy()
:- 在这个时候,Vue实例还可以正常使用;
destroyed()
:- 在实例销毁之后调用,调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。
25. 第一次页面加载会触发哪几个钩子函数?
beforeCreate、created、beforeMount、mounted
- 第一次页面加载需要将data数据和methods方法挂载到Vue实例上,并且需要将虚拟DOM变成真实DOM。
26. 父组件包裹子组件,他们的生命周期执行顺序是什么?
- 父创建前后,父挂载前,子创建前后,子挂载前后,父挂载后。
27. style 上加 scoped属性的原理
- 什么是
scoped
- 在组件中,为了使样式私有化,不对全局造成污染,可以在
style
标签上添加scoped
属性,以标示它只局限于当前组件;
- 在组件中,为了使样式私有化,不对全局造成污染,可以在
- 原理:
- 给当前组件添加
data-
开头的8位随机哈希值的属性; - Vue中scoped属性的效果主要通过PostCSS转译实现:既PostCSS给当前组件内的所有标签添加一个唯一不重复的动态属性,然后,给选择器额外添加一个属性选择器。
- 给当前组件添加
28. Vue响应式数据原理
- 主要是利用通过了
Object.defineProperty()
的方法里面的setter
和getter
方法的观察者模式来实现的; - 在组件初始化的时会给每一个data属性注册
getter
和serter
,然后再给自己new一个Watcher对象
,此时Watcher对象
会立即调用render函数
去生成虚拟DOM; - 再调用
render
的时候,就会需要用到data的属性值,此时会触发getter函数
,将当前Watcher函数注册进sub里; - 当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知他们去重新渲染组件;
Object.defineProperty(对象, 属性值, {
setter() {
// xxx
},
getter() {
// xxx
return xxx
}
})
29. computed、methods、computed之间的区别
computed
:计算属性- 会被挂载到Vue实例身上;
- 一个计算属性的值,依赖于另外的数据属性计算而来,当依赖发生变化的时候,计算属性也会发生变化;
- 计算属性具有缓存性,基于依赖的值进行缓存,依赖不发生变化,都直接从缓存中取结果;当依赖发生变化,函数会自动执行,并把最新的结果再次缓存;
- 不能写异步代码
- 定义的函数接收return的结果,return属于同步执行,是没办法拿到异步请求的结果的
methods
:- 会被挂载到Vue实例身上;
- 方法中的this指向Vue实例;
- 不具有缓存特性;
watch
:- 观察和响应Vue实例上的数据变动;
- 能写异步代码;
30. 说一下$root
、$parent
、$refs
$root
,和$parent
都能访问父组件的属性和方法,区别在于如果存在多级子组件,通过parent
访问得到的就是它最近一级的父组件,通过root
访问得到的就是根父组件。- 通过在子组件标签定义的
ref
属性,在父组件中可以使用$refs
访问子组件实例。
31. v-model原理
<input v-model="username" type="text">
<input type="text" :value="username" @input="username = $event.target.value">
- 原理:
- 将元素的value属性和Vue数据属性进行双向绑定
- 标签上绑定input事件,在该事件内,将输入框中的值赋给vue数据属性;
- 在标签上使用v-bind命令给value属性绑定Vue数据属性。
text
和textarea
元素使用value属性
和input事件
checkbox
和radio
元素使用checked
和change事件
32. 关于keep-alive说法
keep-alive
可以通过include
属性,匹配要进行缓存的组件;- 当组件在
keep-alive
内被切换,它的activated
和deactivated
这两个钩子函数将会被执行; max
属性控制最多可以缓存几个组件,一旦这个数字达到了,在新实例被创建之前,已缓存的组件中,最久没有被访问的实例会被销毁。
33. Vue中template的编译过程
vue template
模板编译的过程经过parse()
生成ast(抽象语法树)
,optimize
对静态节点优化,generate()
生成render
字符串之后调用new Watcher()
函数,用来监听数据的变化,render
函数就是数据监听的回调所调用的,其结果便是重新生成Vnode,如果是数据的更新,那么Vnode会与数据改变之前的Vnode做diff,对内容做改动之后,就会更新到我们真正的DOM。
34. 什么是 Vue.nextTick()?原理是什么
-
$nextTick
是在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM,意思是 等你DOM加载完毕以后再去调用nextTick()
里面的数据内容。 -
原理:
- nextTick方法主要使用了宏任务和微任务,定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空队列。
-
作用:
- nextTick用于下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用nextTick用于下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用nextTick,则可以在回调中获取更新后的DOM。
-
$nextTick本质是返回一个Promise
35. new Vue的时候做了什么事
- 可以看看这位大佬写的
36. axios是什么,怎么使用,描述使用它实现登录功能的流程
- axios是请求后台资源的模块。通过
npm install axios -S
来安装,在大多数情况下我们需要封装拦截器,在实现登录的过程中我们一般在请求拦截器中来加入token,在响应拦截器中通过后端返回的状态码来对返回的数据进行不同的处理。
// TODO 封装请求拦截器
axios.interceptors.request.use(config => {
// TODO 在发送请求之前做一些事
return config
}, error => {
// TODO 处理请求错误
return Promise.reject(error)
})
// TODO 封装响应拦截器
axios.interceptors.response.use(response => {
// TODO 位于2xx范围内的任何状态码都会触发此函数
// TODO 对响应数据做处理
return response
}, error => {
// TODO 任何超出2xx范围的状态码都会触发此函数
// TODO 做一些响应错误的事情
return Promise.reject(error)
})
37. 对axios进行二次封装
import axios from 'axios'
const request = axios.create({
baseURL: '',
timeout: 5000
})
// 参数是个函数
// 请求拦截器
request.interceptors.request.use()
// 响应拦截器
request.interceptors.response.use()
export default request
38. Vue中的路由模式
- hash模式:
- url带有#号,#以及#后面的称为hash,用
window.location.hash
读取,对浏览器安全无用; - 通过
onhashchange
事件,监听url修改; - 特点:
- 前端访问,#后面的变化不会经过服务器;
- hash虽然在url中,但不被包括在http请求中;
- url带有#号,#以及#后面的称为hash,用
- history模式:
- url没有#号;
- history采用H5的新特性,且提供了两个新方法
pushState()
和replaceState()
可以对浏览器历史记录栈进行修改,以及popState
事件的监听到状态变更; - 特点:
- 正常访问,后端访问,url的变化都会经过服务器
- 切换模式:
- 在
new VueRouter()
里面,增加model
属性,属性值为hash
或history
。
- 在
39. 路由配置项常用的属性
path ➡ 展示的url
component ➡ 和path对应的组件路径(可以使用 () => import('组件路径'))
name ➡ 命名路由
children ➡ 子路由的配置对象(路由嵌套)
props ➡ 路由解耦
redirect ➡ 重定向路由
meta ➡ 路由元信息
40. 路由跳转的方式
- 声明式导航:
<router-link to="需要跳转到页面的路径" />
- 编程式导航:
this.$router.push() ➡ 跳转到指定的url,并在history中添加记录,点击回退到上一个页面
this.$router.go(n) ➡ 向前或向后跳转n个页面,n可以是正数也可以是负数
this.$router.replace() ➡ 跳转到指定的url,但是history中不会添加记录
this.$router.back() ➡ 回退到上一个页面
41. 路由传参的方式有哪些
- 编程式导航:
- name 和 params 搭配传参
this.$router.push({ name: 'news', params: { userId: 123} })
- path 和 query 搭配传参
this.$router.push({ path: '/news', query: { userId: 123 } })
- 声明式导航:
-
命名路由:
<router-link to="{ name: 'news', params: { userId: 123 } }"></router-link>
-
查询参数:
<router-link to="{ path: '/news', query: { userId: 123 } }"></router-link>
-
42. $route
和 $router
的区别
- 都是在注册路由的时候,提供的两个全局对象
$route
:- 路由信息对象,包括path、hash、query、params、name等路由信息参数,标识当前激活的路由对象;
$router
:VueRouter
的实例,相当于一个全局的路由对象,里面包含很多属性和子对象,如history对象,将常用的跳转连接可以用this.$router.push()
会往history栈中添加一个新的记录,this,$router.go()
等方法。
43. query 和 params之间的区别
query
:- 和
path
配合使用; - 接收参数的时候,使用
this,$route.query.属性名
- 和
params
:- 和
name
配合使用; - 接收参数的时候,使用
this,$route.params.属性名
- 和
44. Vue-Router 有哪几种路由守卫
- 全局前置守卫:
beforeEach()
- 全局后置守卫:
afterEach()
- 全局解析守卫:
beforeResolve()
- 路由独享守卫:
beforeEnter()
- 单个路由独享的钩子:
beforeEbter()
- 组件路由守卫相关的钩子:
beforeRouterEnter()
beforeRouterUpdate()
beforeRouterLeave()
- 入参:
to
:即将要进入的目标路由对象(去哪里)form
:当前导航即将离开的路由对象next
:调用该方法,才能进入下一个钩子函数(afterEach)next()
:通过;next('url')
:跳到指定地址
router.beforeEach(async (to, from, next) => {
// 相关逻辑
// next()必须执行,但是参数可能不同
})
45. 如何监测动态路由的变化
- 可以通过
watch
方法对$route
进行监听,或者通过导航守卫的钩子函数beforeRouteUpdate
来监听它的变化。
46. Vue-Router中的router-link上v-slot属性怎么使用
router-link
通过一个作用域插槽暴露底层的定制能力。这是一个更高阶的API。主要面向库作者,但也可以为开发者提供便利,多数情况用在一个类似NavLink这样的自定义组件里;- 有时我们可能想把激活的
class
应用到一个外部元素而不是<a>
标签本身,这时可以在一个router-link
中包裹该元素并使用v-slot
属性来创建链接:
<router-link
to="/foo"
custom
v-slot="{ href, route, navgiate, isActive, isExactActive }"
>
<li
:class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active' ]"
>
<a :href="href" @click="navigate">{{ route.fullPath }}</a>
</li>
</router-link>
47. Vue路由实现的底层原理
- 在Vue中利用数据劫持
defineProperty
在原型prototype
上初始化一些getter
,分别是:router
代表当前Router的实例;route
代表当前Router的新信息。
- 在
install
中也全局注册了router-view, router-link
,其中的Vue.util.defineReactive
,这是Vue里面观察者劫持数据的方法,劫持_route
,当_route
触发setter方法的时候,则会通知到依赖的组件; - 接下来在init中,会挂载判断是路由的模式,是history或hash,点击行为按钮,调用hashchange或popstate的同时更新
_route
,_route
的更新会触发route-view
的重新渲染。
48. 为什么使用Vuex
- 项目比较大,以往的关系组件通信进行数据的传递很麻烦,不方便;
- 状态的统一管理,实现数据的共享;
- 实现非关联组件之间的通信。
49. Vuex的5个核心属性
modules
:模块化state
:- 状态管理(定义变量);
- 使用:
- 不使用辅助函数:
this.$store.state.模块名.变量名
- 辅助函数:
- 不使用辅助函数:
mutations
:- 同步修改state;
- 定义在mutations中的方法,有两个入参,分别是:
state
和payload
- 使用:
- 不使用辅助函数:
this.$store.commit('模块名/方法名', 需要传递的数据)
- 辅助函数:
- 不使用辅助函数:
actions
:- 异步修改state,使用commit方法将数据提交给mutations中的方法进行修改;
- 定义在actions中的方法,有两个入参,分别是:
context
和payload
- 使用:
- 不使用辅助函数:
this.$store.dispatch('模块名/方法名', 传递的数据)
- 不使用辅助函数:
getters
:- 相当于Vue的计算属性,
- getters中的方法有两个入参,分别是:
state
和payload
- 在srore下新建getters.js文件
50. 为什么需要对Vuex持久化
- vuex中的数据在页面刷新后就没有了;
- 要实现数据的长久保存,可以通过浏览器的本地存储能力实现
- cookie
- localStorage
51. Vuex可以直接修改state的值吗
- 可以直接修改,但是及其不推荐;
state
的修改必须在mutation
来修改,否则无法被devtools
所监测,无法监测数据的来源,无法保存状态快照,也就无法实现时间漫游 / 回滚之类的操作。
52. 为什么Vuex的mutation不能做异步操作
- Vuex中所有的状态更新的唯一途径都是
mutation
,异步操作通过Action
来提交mutation
实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用; - 每个
mutation
执行完成后都会对应一个新的状态变更,这样devtools
就可以打个快照存下来,否则无法被devtools
所监测; - 如果
mutation
支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
53. Vue单页面应用的优缺点
- 优点:
- 内容的改变不需要重新加载整个页面,web应用更具响应性;
- 没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并没有“闪烁”现象;
- 相对服务器压力小,服务器只用出数据就可以,不用展示逻辑和页面合成,吞吐能力会提高几倍;
- 良好的前后端分离。后端不再负责模板渲染、输出页面工作。后端API通用化,既一套后端程序代码,不用修改可以用于Web界面、手机、平板等多种客户端。
- 缺点:
- 首次加载耗时比较长;
- SEO问题,不利于百度,360等搜索引擎收录;
- 容易造成Css命名冲突;
- 前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂程度很高,需要一定的技能水平和开发成本。
54. 如何对首屏加载实现优化
- 把不常用的库放到index.html中,使用cdn引入;
- 使用懒加载;
- Vue组件避免全局注册;
- 使用更轻量级的工具库;
- 预渲染;
- 减少http请求;
- 开启gzip压缩。
55. 对SPA单页面的理解,它的优缺点分别是什么?
-
单页Web应用是一种特殊的Web应用。他将所有的活动局限于一个web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript和CSS,一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用JavaScript动态的变换HTML的内容,从而实现UI与用户的交互。由于避免了页面的重新加载,SPA可以提供较为流畅的用户体验。得益于Ajax,我们可以实现无跳转刷新,又多亏了浏览器的history机制,我们用hash的变化从而可以实现推动界面变化,从而模拟元素客户端的单页面切换效果;
-
优点:
-
单页面应用的内容的改变不需要重新加载整个页面,Web应用更具响应性;
-
单页面没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并没有“闪烁”现象;
-
单页面应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面和声,吞吐能力会提高几倍;
-
良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,既同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。
-
-
缺点:
- 首次加载耗时比较多;
- 不利于SEO优化;
- 各个浏览器的版本兼容性不一样;
- 业务随着代码量增加而增加,不利于首屏优化;
- 前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂程度很高,需要一定的技能水平和开发成本。
56. 项目优化
- 减少http请求;
- 减少DOM操作;
- 使用JSON格式来进行数据的交换;
- 使用CDN加速;
- 优化代码,减少代码体积;
- 服务端渲染。
57. Vue2 和 Vue3 的区别?
-
根节点数目:
- Vue2只能有一个根节点;
- 因为vdom是一棵单根树型结构,pacth方法在遍历的时候从根节点开始遍历,他要求只有一个根节点,组件也会转换为一个vdom,自然应该满足这个要求。
- Vue3可以有多个根节点;
- Vue3中引入了
Fragment
的概念,这是一个抽象的节点,如果发现组件是多个根节点,就创建一个 Fragment节点,把多个根节点作为它的children。将来patch的时候,如果发现是一个Fragment节点,则直接遍历children创建或更新。
- Vue3中引入了
- Vue2只能有一个根节点;
-
数据双向绑定原理不同:
- Vue2采用Object.defineProperty
- Vue3采用ES6新增的Proxy
-
生命周期不同:
Vue2 ==> Vue3
beforeCreate() ==> setup()
Created() ==> setup()
beforeMount() ==> onBeforeMount()
mounted() ==> onMounted()
beforeUpdate() ==> onBeforeUpdate()
updated() ==> onUpdated()
beforeDestroyed() ==> onBeforeUnmount()
destroyed() ==> onUnmounted()
activated() ==> onActivated()
deactivated() ==> onDeactivated()
注意:
setup 要比 beforeCreate 更早执行
setup 有两个入参,proxy + context
如果解构proxy,得到的数据会丢失响应式
如果还想保持响应式,可以使用 toRefs() 或者 toRef()
import { toRefs, toRef } from 'vue'
export default {
setup(props) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}