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服务器渲染;
- 静态化;
- 预渲染;
- 骨架屏;
- 解决办法:
- 首屏加载速度慢:加载时,将所有的css,js文件都进行加载;
- 不支持IE678(IE也不用了😂);
- 有些数据不需要响应式,但必须写在data里,影响加载速度和性能;
- Vue的响应式是通过Object.defineProperty:
- 无法监听ES6的Set、Map变化;
- 无法监听Class类型的数据;
- 属性的新加或删除无法监听;
- 数组元素的增加和删除无法监听;
- 不利于SEO优化:
2. Vue最大优势
- 是一款轻量级框架,简单易学,数据双向绑定,虚拟DOM,组件化,数据和结构相分离,运行速度快等等;
- 文档都是中文的,入门教程很多,上手简单;
- Vue是单页面应用,使页面局部刷新,不用每次跳转页面都去请求所有的数据和DOM,加快了访问速度和提升用户体验;
- 相比传统的页面通过超链接实现页面的切换和跳转,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-for
优先级高于v-if
;- 当
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正在更新已渲染的元素列表时,它默认采用“就地复用”策略。
- 列表数据修改的时候,它会根据key值去判断某个值是否修改,如果修改了,则在重新渲染这一项,否则复用旧节点;
- key:
- 一段唯一不重复的数字或字符串;
- 为了更高效的更新虚拟DOM;
- 是给v-for循环生成标签办法唯一标识的;
- key不会出现在真实DOM中;
- 为什么不能使用索引?
- 因为索引是连续的,如果删除其中一个会导致最后一个被删除;
- 当我们在删除的时候,key根据数据来把新旧的DOM做对比,删除key不存在的对应的标签。
11. v-for后面为什么要加 :key?
- 在重复渲染的dom上加
key
,可以对dom进行回收利用,标记每一个dom用来判断dom节点是否更新; - 比对时,第一步,如果发现没有key会立即更新,创建新的dom,如果发现有key,第二部,就会比对dom结构,如果dom结构发生变化,那么仍然是就地更新,如果dom结构也相同,那么第三步,就回去比较
key
利用diff算法,看看是否可以重复利用该dom;
12. 你知道 dom diff算法吗?
- dom结构的对比主要使用的是
updateChildren
方法,4个if-else
判断,头头比,尾尾比,头尾比,尾头比,因为diff主要比对是在循环的过程中,而循环需要依赖数组,数组的操作会引起视图变化的主要是从头增删、从尾增删、或者反转,所以着4个比较的命中会比较高如果匹配,新旧的索引都++,不匹配,旧的dom起始索引会++,直到起始索引大与等于结束索引,那么新的起始索引与旧的所有索引就算比较完成,等到新的dom起始索引比结束索引大了,整个对比就算结束;
13. diff算法比较机制
- 根元素变化:删除DOM树重新建立;
- 根元素未变:
- 顺序改变:
- 更新属性;
- 子元素/子元素内容改变:
- 按照key比较,如果没有key或者key是索引,尝试就地更新;
- 如果key是id,新旧虚拟DOM做对比,共有的部分不发生变化,没有的就在对应的位置插入DOM节点;
- 顺序改变:
- key使用规范:
- 有id用id,没有id用索引;
- 一段唯一不重复的数字或字符串。
14. 说说你对vue的template编辑的理解?
- 简单来说就是先转换成 AST树,经过
generate
得到render
函数,render
函数的返回值是 VNode; - 首先,通过
compile
编译器把template
编译成AST
语法树(abstract syntax tree
即源代码的抽象语法结构的树状表现形式),compile
是createCompiler
的返回值,createCompiler
是用以创建编译器的。另外compile
还负责合并option
。 - 然后,
AST
会经过generate
(将 AST 语法树转化成 render funtion 字符串的过程)得到render
函数,render
的返回值是VNode
,VNode 是 Vue 的虚拟 DOM 节点,里面有(标签名、子节点、文本等等)
15. 怎么自定指令,有哪些钩子函数,对应的有哪些入参?
- 全局注册:
- 在 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() ➡ 只调用一次,指令与该元素解绑时调用。
- 钩子函数参数:
16. 怎么封装一个组件,具体的过程是咋样的
- 组件提升了整个项目的开发效率,能够把页面抽离成相对独立的模块。解决了传统项目开发过程中的效率低、难维护、复用性差等问题;
- 组件的封装我们可以看作是一个函数的封装:
- 根据业务需求,把页面中可复用的template、script、style,抽离到一个单独的 .vue文件 中,实现复用;
- 上述步骤完成之后,还需要考虑入参和出参:
- 入参:使用 props机制;
- 出参:
- 使用 $emit机制;
- 使用 .sync修饰符(见5);
- 使用 v-model语法糖实现;
- 针对一些标签不确定的地方,我们可以使用插槽实现;
- 至此,组件的封装完成。
- 组件的使用步骤:
- 封装组件;
- 导入组件:
import 组件对象 from '组件路径'
- 注册组件;
- 全局注册:
Vue.component('组件名', 组件对象)
- 局部注册(常用):
- 使用组件:组件标签;
- 全局注册:
17. Vue组件中 data 为什么是个函数?
- Vue中每个组件都是一个实例;
- 组件共享data属性,当data的值是同一个引用数据类型的值时,改变其中一个会影响其他的;
- 组件中的data写成一个函数,数据以函数返回值的形式定义,这样每复用一次组件,就会返回一份最新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护自己的数据;
- 单纯的写成对象的形式,就会使得所有的组件实例共享一份data,就会造成一变全变的结果。
18. Vue组件如何进行传值
- 组件通信就是组件间的传值,无非就是三种情况:
- 父传子、子传父、任意组件间传值;
- 父传子
props
,子传父emit
; - vue提供的方法能实现传值的还有:
$attr
获取传递的所有属性,$refs
获取组件实例,可以进行传值; v-mdoel
和sync
修饰符也能进行传值;- 还有主流的
vuex
,只要写在vuex里面的数据,任意组件都可以进行访问; - 还有一些非主流的,比如
localStorage、sessionStorage、indexDB、WebSQL
这种浏览器缓存里,也可以做到这种效果,不过这种方式有很大的弊端,数据不是响应式的,一般没什么人使用; - 还有一种就是,vuex的数据是挂载在vue对象实例上的,而vue实例对象是挂载在window上的,所以也可以直接挂载到window对象,也能拿到使用;
- 基本上就这些传值的方式了;
19. Vue该如何实现组件的缓存?
- 为什么需要组件缓存?
- 在面向组件开发中,会把整个项目拆分成多个业务组件,按照需求对组件进行整合;
- 存在组件频繁切换的问题,在这个过程中,组件的实例都是在不断的销毁和创建,很是消耗性能,并且如果需要该组件的数据的话,我们是获取不到的,所以需要对组件的状态进行缓存。
- 怎么实现组件的缓存?
- 使用
keep-alive
标签包裹需要被缓存的组件,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染; - 优点:提高渲染性能,提升用户体验。
- 使用
- 简单的说: 比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用
<keep-alive></keep-alive>
进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染
20. 关于keep-alive说法
keep-alive
可以通过include
属性,匹配要进行缓存的组件;- 当组件在
keep-alive
内被切换,它的activated
和deactivated
这两个钩子函数将会被执行; max
属性控制最多可以缓存几个组件,一旦这个数字达到了,在新实例被创建之前,已缓存的组件中,最久没有被访问的实例会被销毁。
21. Vue中 keep-alive 的作用
keep-alive
是Vue内置的一个组件,可以使被包裹的组件保留状态,或避免重新渲染。一旦使用keep-alive
激活组件,此时mounted
、created
等钩子函数只会在第一次进入组件时调用,当再次切换回来时将不会调用。此时如果我们还想在每次切换时做一些事情,就需要用到另外的周期函数,actived
和deactived
,这两个钩子函数只有被keep-alive
包裹后才会调用。
22. 组件中写 name 选项有什么用?
- 项目中使用
keep-alive
时,可以搭配组件 name 进行组件的缓存; - 使用插槽时,
name
属性可以作为占位标签的名字,供template
使用; Vue-devtools
调试工具里显示的组件名称是由Vue组件内部的name
属性决定的。
23. 谈谈对 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()
获取该对象的所有属性名,进行循环销毁。
- 单个定时器:直接调用
24. 第一次页面加载会触发哪几个钩子函数?
beforeCreate()
、created()
、beforeMount()
、mounted()
- 第一次页面加载需要将data数据和methods方法挂载到Vue实例上,并且需要将虚拟DOM变成真实DOM。
25. 父组件嵌套一个子组件,第一次加载的时候,钩子函数的触发顺序
- 父创建前后,父挂在前,子创建前后,子挂载前后,父挂载后;
26. 怎样理解Vue的单向数据流
- 数据从父组件传递给子组件,只能单向绑定;
- 子组件内部不能直接修改从父组件传递过来的数据;
- 所有的prop都使得其父子prop之间形成一个单向下行绑定:
- 父级prop的更新会向下流动到子组件中,但是反过来不行;
- 这样会防止从子组件意外改变父组件的状态,从而导致应用的数据流向难以理解;
- 额外的,每次父组件发生更新时,子组件中所有的prop都将会刷新为最新的值;
- 这意味着你不应该在一个子组件内部改变prop,如果这样做了,Vue会在浏览器的控制台中发出警告;
- 子组件想修改时,只能通过
$emit()
派发一个自定义事件,父组件接收后,由父组件修改。
27. style 上加 scoped属性的原理 及 样式穿透原理
- 什么是
scoped
- 在组件中,为了使样式私有化,不对全局造成污染,可以在
style
标签上添加scoped
属性,以标示它只局限于当前组件;
- 在组件中,为了使样式私有化,不对全局造成污染,可以在
- 原理:
- 给当前组件添加
data-v-
开头的8位随机哈希值的属性; - Vue中scoped属性的效果主要通过PostCSS转译实现:既PostCSS给当前组件内的所有标签添加一个唯一不重复的动态属性,然后,给选择器额外添加一个属性选择器(这个属性选择器是添加到原有选择的后面的)。
- 给当前组件添加
- 样式穿透的原理:
- 和 scoped 的原理类似;
- 区别:
- scoped是给当前文件的每一个选择器的后面 添加 属性选择器;
- 样式穿透是给对应的选择器的最前面 添加 属性选择器;
28. Vue响应式数据原理(双向数据绑定原理 / mvvm原理)
- 原理就是数据劫持 + 发布订阅,数据变化引起视图变化的原理是:
- 数据劫持可以使用
Object.definePrototype
进行监听模型数据变化,当数据变化时会触发set
函数; - 发布订阅则是解析模板指令的时候,watcher收集所有模板用到的数据,添加为订阅者,当数据更新时,会通知所有订阅者调用自身的
update
进行试图的更新; - 试图变化引起数据更新的原理是:在视图层绑定
input
事件,监听到事件触发时,修改对应value
值,就可以进行数据的更新了;
- 数据劫持可以使用
- 实例代码:
let input = document.querySelector('input')
let p = document.querySelector('p')
//定义⼀个对象
let obj = {}
let value = ''
Object.defineProperty(obj,'txt',{
//obj.txt属性赋值⽅法,同时为input、p⽂本赋同⼀个值
set(val) {//数据更新引起视图变化
value = val
input.value = val
p.innerHTML = val
},
//获取txt属性的⽅法
get() {
return value;
}
})
//监听事件 触发的时候会给obj.txt重新赋值,从⽽实现双向绑定
input.addEventListener('keyup',(e)=>{//视图变化更新数据
obj.txt = e.target.value;
})
29. computed、methods、watch之间的区别
computed
:计算属性- 会被挂载到Vue实例身上;
- 一个计算属性的值,依赖于另外的数据属性计算而来,当依赖发生变化的时候,计算属性也会发生变化;
- 计算属性具有缓存性,基于依赖的值进行缓存,依赖不发生变化,都直接从缓存中取结果;当依赖发生变化,函数会自动执行,并把最新的结果再次缓存;
- 所有
getter
和setter
的this
上下文自动地绑定为Vue实例; - 不能写异步代码
- 定义的函数接收return的结果,return属于同步执行,是没办法拿到异步请求的结果的
methods
:- 会被挂载到Vue实例身上;
- 方法中的this指向Vue实例;
- 不具有缓存特性;
watch
:- 观察和响应Vue实例上的数据变动;
- Vue实例将会在实例化时调用
$watch
,遍历warch
对象的每一个属性;
- 三者的加载顺序:
computed
是在html dom加载后马上执行的;methods
则必须要有一定的触发条件才能执行;watch
用于观察Vue实例上的数据变动;
- 默认加载的时候:
- 先
computed
后watch
,不执行methods
;
- 先
- 触发某一事件后:
- 先
computed
再methods
再到watch
;
- 先
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">
- 原理:
v-model
其实是个语法糖,相当于用v-bind
动态绑定了value
属性实现数据驱动视图,然后用input
事件把视图的值再更新给绑定的value
,v-model
主要是给表单元素使用,有的时候封装组件也可以使用v-model
绑定数据,比如定义一个组件,用props
接收value
,再用$emit()
给input
传值,这个组件就可以被绑定v-model
了;
- 🎯 如何使用 v-model 封装组件?
32. v-model 和 sync修饰符的区别
- 首先都是用来双向绑定数据的,
v-model
只能使用一次,sync
修饰符可以给任何一个属性使用,v-model
传入的是input
事件,而sync
修饰符传入的是update:属性名事件
;
33. 为什么更新了数据却不能操作dom?如何解决?
- 数据更新是同步的,视图更新是异步的,数据更新会发起一个更新视图的微任务,试图不会立即更新,所以不能在修改完数据之后立刻操作dom,如果试图没有更新可能导致dom获取不到,需要将操作dom的代码使用
$nextTick()
包裹,也转换成异步,就可以在更新完dom后实现dom操作;
34. 什么是 Vue.nextTick()原理是什么?
$nextTick
是在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM,意思是 等你DOM加载完毕以后再去调用nextTick()
里面的数据内容。- 原理:
- nextTick方法主要使用了宏任务和微任务,定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空队列。
- 作用:
- nextTick用于下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用nextTick用于下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用nextTick,则可以在回调中获取更新后的DOM。
$nextTick
本质是返回一个Promise
;
35. Vue中template的编译过程
vue template
模板编译的过程经过parse()
生成ast(抽象语法树)
,optimize
对静态节点优化,generate()
生成render
字符串之后调用new Watcher()
函数,用来监听数据的变化,render
函数就是数据监听的回调所调用的,其结果便是重新生成Vnode,如果是数据的更新,那么Vnode会与数据改变之前的Vnode做diff,对内容做改动之后,就会更新到我们真正的DOM;
36. new Vue的时候做了什么事
- 初始化:
- 执行
init
操作。包括但不限于initLifecycle、initState
等;
- 执行
- 挂载:
- 执行
mounted
,进行元素挂载;
- 执行
- 编译:
compiler
对template
属性进行编译,通过vue-loader
处理生成render
函数;
- 渲染:
- 执行
render
函数,生成vnode
;
- 执行
- 补丁:
patch
。新旧vnode
经过diff
后,渲染到真实dom上;
37. 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)
})
38. 对axios进行二次封装
import axios from 'axios'
const request = axios.create({
baseURL: '',
timeout: 5000
})
// 参数是个函数
// 请求拦截器
request.interceptors.request.use()
// 响应拦截器
request.interceptors.response.use()
export default request
39. Vue中的路由模式
- hash模式:
- url带有#号,#以及#后面的称为hash,用
window.location.hash
读取,对浏览器安全无用; - 通过
onhashchange
事件,监听url修改; - 特点:
- hash虽然在url中,但不被包括在http请求中;
- 用来指导浏览器动作,对服务端安全无用,hash不会重加载页面;
- url带有#号,#以及#后面的称为hash,用
- history模式:
- url没有#号;
- history采用H5的新特性,且提供了两个新方法
pushState()
和replaceState()
可以对浏览器历史记录栈进行修改,以及popState
事件可以监听到状态变更;
- 切换模式:
- 在
new VueRouter()
里面,增加mode
属性,属性值为hash
或history
。
- 在
40. 路由配置项常用的属性
path ➡ 展示的url
component ➡ 和path对应的组件路径(可以使用 () => import('组件路径'))
name ➡ 命名路由
children ➡ 子路由的配置对象(路由嵌套)
props ➡ 路由解耦
redirect ➡ 重定向路由
meta ➡ 路由元信息
41. 路由跳转的方式
- 声明式导航:
<router-link to="需要跳转到页面的路径" />
- 编程式导航:
this.$router.push() ➡ 跳转到指定的url,并在history中添加记录,点击回退到上一个页面
this.$router.go(n) ➡ 向前或向后跳转n个页面,n可以是正数也可以是负数
this.$router.replace() ➡ 跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面
this.$router.back() ➡ 回退到上一个页面
42. 路由传参的方式有哪些
- Vue-Router传参可以分为两大类,分别是编程式导航和声明式导航;
- 编程式导航:
- 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>
- 命名路由:
43. $route
和 $router
的区别
- 都是在注册路由的时候,提供的两个全局对象
$route
:- 路由信息对象;
- 包括
path、hash、query、params、name
等路由信息参数,表示当前激活的路由对象;
$router
:- VueRouter的实例;
- 相当于一个全局的路由对象,里面包含很多属性和子对象,如history对象,经常用的跳转链接就可以用
this.$router.push()
会往history栈中添加一个新的记录,返回上一个history也是使用this,$router.go()
等方法。
44. query 和 params之间的区别
query
:- 和
path
配合使用; - 接收参数的时候,使用
this.$route.query.属性名
- 和
params
:- 和
name
配合使用; - 接收参数的时候,使用
this.$route.params.属性名
- 和
45. Vue-Router 有哪几种路由守卫
- 全局前置守卫:
beforeEach()
- 全局后置守卫:
afterEach()
- 单个路由独享的钩子:
beforeEnter()
- 组件路由守卫相关的钩子:
beforeRouterEnter()
beforeRouterUpdate()
beforeRouterLeave()
- 入参:
to
:即将要进入的目标路由对象(去哪里)form
:当前导航即将离开的路由对象next
:调用该方法,才能进入下一个钩子函数(afterEach)next()
:通过;next('url')
:跳到指定地址
router.beforeEach(async (to, from, next) => {
// 相关逻辑
// next()必须执行,但是参数可能不同
})
46. 如何实现动态路由?
- 路由一般分为静态路由和动态路由:
- 静态路由:
- 也可以叫做白名单路由,指的是不需要权限就能访问的页面,一般都是提前定义好的;
- 动态路由:
- 需要前端进行处理,一般都是根据后台返回的路由匹配规则,然后在所有的路由中,可以利用路由的name属性与权限点匹配的规则,筛选出可以访问的路由,通过
router.addRoutes
添加就可以了;
- 需要前端进行处理,一般都是根据后台返回的路由匹配规则,然后在所有的路由中,可以利用路由的name属性与权限点匹配的规则,筛选出可以访问的路由,通过
47. 如何监测动态路由的变化
- 可以通过
watch
方法对$route
进行监听,或者通过导航守卫的钩子函数beforeRouteUpdate(to, from, next)
来监听它的变化。
48. 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>
49. 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
的重新渲染。
50. 你是怎么认识Vuex的?
- Vuex可以来理解为一种开发模式或框架,通过状态集中管理驱动组件的变化;
- 应用级的状态几种放在
state
中; - 改变状态的方式是提交
mutations
,这里必须是同步的; - 异步逻辑应该封装在
action
中;
51. 为什么使用Vuex
- 项目比较大,以往的关系组件通信进行数据的传递很麻烦,不方便;
- 状态的统一管理,实现数据的共享;
- 实现非关联组件之间的通信。
52. Vuex的5个核心属性
- 分别是:
module、state、mutation、action、getter
;
- modules:
- 模块化;
- 其实只是解决了当
state
中很复杂臃肿的时候,module
可以将store
分隔成模块,每个模块拥有自己的state、mutation、action、getter
;
- state:
- 状态管理(定义变量);
- 在
state
中定义我们需要管理的数据,只有在这里定义了,在Vue的组件中才能获取到定义数据的状态; - 使用:
- 不使用辅助函数:
this.$store.state.模块名.变量名
- 辅助函数:
- 不使用辅助函数:
- mutations:
- 同步修改state;
- 定义在mutations中的方法,有两个入参,分别是:
state
和payload
- 使用:
- 不使用辅助函数:
this.$store.commit('模块名/方法名', 需要传递的数据)
- 辅助函数:
- 不使用辅助函数:
- actions:
- 异步修改state,使用commit方法将数据提交给mutations中的方法进行修改;
- 定义在actions中的方法,有两个入参,分别是:
context
和payload
- 使用:
- 不使用辅助函数:
this.$store.dispatch('模块名/方法名', 传递的数据)
- 不使用辅助函数:
- getters:
- 类似于Vue的计算属性;
- 当我们需要从
store
的state
中派生一些状态,那么我们就需要使用getter
; getter
会接受state
作为第一个参数,而且getter
的返回值会根据它的依赖被缓存起来,只有getter
中的依赖值发生改变的时候才会被重新计算;- getters中的方法有两个入参,分别是:
state
和payload
- 在srore下新建getters.js文件
53. 为什么需要对Vuex持久化
- vuex中的数据在页面刷新后就没有了;
- 要实现数据的长久保存,可以通过浏览器的本地存储能力实现
- cookie
- localStorage
54. Vuex可以直接修改state的值吗
- 可以直接修改,但是及其不推荐;
state
的修改必须在mutation
来修改,否则无法被devtools
所监测,无法监测数据的来源,无法保存状态快照,也就无法实现时间漫游 / 回滚之类的操作。
55. Vuex的mutation和action之间的区别是什么
- 功能不同:
mutation
:专注于修改state
,理论上是修改state
的唯一途径;action
:专注于业务代码,可以发异步请求;
- 限制:
mutation
:必须同步执行;action
:可以异步,但不能直接修改state
;
56. 为什么Vuex的mutation不能做异步操作
- Vuex中所有的状态更新的唯一途径都是
mutation
,异步操作通过Action
来提交mutation
实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用; - 每个
mutation
执行完成后都会对应一个新的状态变更,这样devtools
就可以打个快照存下来,否则无法被devtools
所监测; - 如果
mutation
支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
57. Vue单页面应用的优缺点
- 优点:
- 内容的改变不需要重新加载整个页面,web应用更具响应性;
- 没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并没有“闪烁”现象;
- 相对服务器压力小,服务器只用出数据就可以,不用展示逻辑和页面合成,吞吐能力会提高几倍;
- 良好的前后端分离。后端不再负责模板渲染、输出页面工作。后端API通用化,既一套后端程序代码,不用修改可以用于Web界面、手机、平板等多种客户端。
- 缺点:
- 首次加载耗时比较长;
- SEO问题,不利于百度,360等搜索引擎收录;
- 容易造成Css命名冲突;
- 前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂程度很高,需要一定的技能水平和开发成本。
58. 如何对首屏加载实现优化
- 把不常用的库放到index.html中,使用cdn引入;
- 使用懒加载;
- Vue组件避免全局注册;
- 使用更轻量级的工具库;
- 预渲染;
- 减少http请求;
- 开启gzip压缩。
59. 对SPA单页面的理解,它的优缺点分别是什么?
- 单页Web应用是一种特殊的Web应用。他将所有的活动局限于一个web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript和CSS,一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用JavaScript动态的变换HTML的内容,从而实现UI与用户的交互。由于避免了页面的重新加载,SPA可以提供较为流畅的用户体验。得益于Ajax,我们可以实现无跳转刷新,又多亏了浏览器的history机制,我们用hash的变化从而可以实现推动界面变化,从而模拟元素客户端的单页面切换效果;
- 优点:
- 单页面应用的内容的改变不需要重新加载整个页面,Web应用更具响应性;
- 单页面没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并没有“闪烁”现象;
- 单页面应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面和声,吞吐能力会提高几倍;
- 良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,既同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。
- 缺点:
- 首次加载耗时比较多;
- 不利于SEO优化;
- 各个浏览器的版本兼容性不一样;
- 业务随着代码量增加而增加,不利于首屏优化;
- 前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂程度很高,需要一定的技能水平和开发成本。
60. 项目优化
- 减少http请求;
- 减少DOM操作;
- 使用JSON格式来进行数据的交换;
- 使用CDN加速;
- 优化代码,减少代码体积;
- 服务端渲染。
61. VNode是什么,什么是虚拟DOM?
- VNode是什么:
- VNode 是 JavaScript 对象,VNode 表示 Virtual DOM,用 JavaScript 对象来
描述真实的 DOM,把 DOM 标签、属性、内容都变成对象的属性。就像使用 JavaScript对象对一种动物进行说明一样;
- VNode 是 JavaScript 对象,VNode 表示 Virtual DOM,用 JavaScript 对象来
- VNode的作用:
- 通过 render 将 template 模版描述成 VNode,然后进行一系列操作之后形成真实的DOM进行挂载;
- VNode的优点:
- 兼容性强,不受执行环境的影响。VNode 因为是 JS 对象,不管 Node 还是浏览器,都可以统一操作,从而获得了服务端渲染、原生渲染、手写渲染函数等能力;
- 减少操作 DOM,任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新 DOM,不需要频繁操作 DOM,从而提高页面性能;
- 什么是虚拟DOM?
- 文档对象模型或 DOM 定义了一个接口,该接口允许 JavaScript 之类的语言访问和操作 HTML 文档。元素由树中的节点表示,并且接口允许我们操纵它们。但是此接口需要付出代价,大量非常频繁的 DOM 操作会使页面速度变。
- Vue 通过在内存中实现文档结构的虚拟表示来解决此问题,其中虚拟节点(VNode)表示 DOM 树中的节点。当需要操纵时,可以在虚拟 DOM 的 内存中执行计算和操作,而不是在真实 DOM 上进行操纵。这自然会更快,并且允许虚拟 DOM 算法计算出最优化的方式来更新实际 DOM 结构,一旦计算出,就将其应用于实际的 DOM 树,这就提高了性能,这就是为什么基于虚拟 DOM 的框架(例如 Vue 和 React)如此突出的原因。
62. 如何对Vue首屏加载实现优化
- 把不常改变的库放到
index.html
中,通过cdn
导入; - Vue路由的懒加载;
- Vue组件尽量不要全局引入;
- 使用更轻量级的工具库;
- 开启gzip压缩;
- 首页单独做服务端渲染;
63. Vue中怎么重置data
- 要初始化
data
中的数据,可以使用Object.assign()
方法,实现重置 data 中的数据: Object.assign()
方法基本定义:Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象赋值到目标对象,他将返回目标对象;- 用法:
Object.assign(target, ...sources)
;- 第一个参数是目标对象;
- 后面的参数是源对象,可以有多个,就是将源对象属性复制到目标对象,返回目标对象;
- 具体实现:
- 使用
Object.assign()
,vm.$data
可以获取当前状态下的data
,
vm.$options.data(this)
可以获取到组件初始化状态下的data
, 复制Object.assign(this.$data, this.$options.data(this))
; - ❗ 注意加 this,不然取不到
data(){ a: this.methodA }
中的this.methodA
。
- 使用
64. Vue2 和 Vue3 的区别?
- API不同:
- Vue2采用选项式API,对于代码阅读、维护、逻辑复用不是太友好(可以通过minix实现逻辑复用);
- Vue3采用组合式API,功能代码组合在一起,方便维护、逻辑复用;
- 使用:
- Vue2:配合vuex,对于TS的支持不是很好;
- Vue3:配合pinia,底层是使用TS重构的,对TS支持很好;
- 根节点数目:
- 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()
- v3性能比v2快:
- v3采用静态标记,给静态节点打上标记,diff的过程只对比动态节点,忽略静态节点;
- 虚拟dom的创建:
- v2中,数据发生变化,会将模板进行重编译,生成所有的虚拟dom;
- v3中,数据发生变化,看该dom是否参与更新,如果参与更新,创建新的虚拟dom,如果不参与更新,直接复用上一次的虚拟dom;