关于Vue常见的面试题(大概率出现)
文章目录
- 关于Vue常见的面试题(大概率出现)
- 1、Vue双向数据绑定。(重中之重)
- 2、vue虚拟dom,diff算法
- 3、组件通信
- 4、Vuex
- 5、 vue-router(路由原理,路由守卫,路由传参)
- 6.vue生命周期
- 7. 自定义指令,自定义过滤器
- 8. 自定义组件
- 9. 常用的指令,修饰符。
- 10. vue2和vue3的区别
- 11. keep-alive
- 12. 多环境变量
- 13. 对axios封装,(url统一管理,axios请求拦截、响应拦截,函数封装)
- 14. element-ui,vant-ui按需引入
- 15. sass配置
- 16. rem、vw/vh设置
- 17. webpack配置(配置跨域,路径别名,打包分析,cdn引入,去掉console.log,单独打包第三方模块,ie兼容,eslint规范,图片压缩)。
1、Vue双向数据绑定。(重中之重)
答:vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
关于VUE双向数据绑定,其核心是
Object.defineProperty()
方法
前面我们说到了Object.defineProperty()
这个方法,那么在这里我就简单的介绍一下Object.defineProperty()
这个方法。
Object.defineProperty()
可以用来修改对象的属性,也可以在对象上新创建一个属性;Object.defineProperty(obj, prop, descriptor)
这个语法总共有三个参数:obj
:要定义其上属性的对象prop
:要定义或者要修改的属性descriptor
:具体的改变方法
- 简单地说,就是用这个方法来定义一个值。当调用时我们使用了它里面的
get
方法,当我们给这个属性赋值时,又用到了它里面的set
方法;
2、vue虚拟dom,diff算法
- 什么是虚拟dom?
所谓的Virtual dom,也就是我们常说的虚拟节点,它是通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM的节点。
- 为什么要使用虚拟dom?
虚拟DOM的出现也是为了减弱频繁的大面积重绘引发的性能问题!
既然知道了什么是虚拟DOM,那为什么在Vue或者React这样的框架中,会考虑采用这样的方式?
其实在我们使用JQuery这样的库的时候,我们不禁会大量操作DOM,那么DOM元素的变化自然会引起页面的回流或者重绘,页面的DOM重排自然会导致页面性能下降,那么如何尽可能的去减少DOM的操作是框架需要考虑的一个重要问题!
- 真实DOM和虚拟DOM的区别
·虚拟DOM不会进行排版与重绘操作
·真实DOM频繁排版与重绘的效率是相当低的
·虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
·虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部
看两端简单的代码,也许会更容易理解:
这是一段真实DOM的代码:
<div>
<p>test</p>
</div>
这是一段虚拟DOM的伪代码:
var Vnode = {
tag: 'div',
children: [
{
tag: 'p', text: 'test' }
]
};
DOM Diff
说完了虚拟dom,我们了解到,这是一种为了尽可能减少页面频繁操作DOM的方式,那么在虚拟DOM中,通过什么方式才能做到呢,接下来便要说说DOM Diff
DOM Diff指的是通过Diff算法去比较虚拟DOM的变化
diff 算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。
换句话说就是
diff 的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁
3、组件通信
众所周知,Vue 主要思想之一就是组件式开发。因此,在实际的项目开发中,肯定会以组件的开发模式进行。形如页面和页面之间需要通信一样,Vue 组件和组件之间肯定也需要互通有无、共享状态。接下来,我们就悉数给大家展示所有 Vue 组件之间的通信方式。
组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
- 父组件传到子组件
父组件是通过props属性给子组件通信的
父组件parent.vue
代码如下:
<template>
<div class="parent">
<h2>{
{
msg }}</h2>
<son :fa-msg="msg"></son> <!-- 子组件绑定faMsg变量,注意驼峰-->
</div>
</template>
<script>
import son from './Son' //引入子组件
export default {
name: 'HelloWorld',
data () {
return {
msg: '父组件',
}
},
components:{
son},
}
</script>
子组件son
代码如下:
子组件通过
$emit
触发父组件上的自定义事件,发送参数
<template>
<div class="son">
<p>{
{
sonMsg }}</p>
<p>子组件接收到内容:{
{
psMsg }}</p>
<!--<input type="text" v-model="user" @change="setUser">-->
<button @click="setUser">传值</button>
</div>
</template>
<script>
export default {
name: "son",
data(){
return {
sonMsg:'子组件',
user:'子传父的内容'
}
},
props:['psMsg'],
methods:{
setUser:function(){
this.$emit('transfer',this.user)//触发transfer方法,this.user 为向父组件传递的数据
}
}
}
</script>
非父子传参 (事件总线)
假设你有两个Vue组件需要通信: A 和 B ,A组件按钮上面绑定了点击事件,发送一则消息,B组件接收。
初始化,全局创建$bus
直接在项目中的main.js中初始化$bus
// main.js
window.$bus=new Vue();
注意,这种方式初始化一个 全局的事件总线
。
发送事件
$bus.$emit("aMsg",'来自A页面的消息')
<!-- A.vue -->
<template>
<button @click="sendMsg()">-</button>
</template>
<script>
//import $bus from "../bus.js";
export default {
methods: {
sendMsg() {
$bus.$emit("aMsg", '来自A页面的消息');
}
}
};
</script>
接下来,我们需要在 B页面 中接收这则消息
接收事件
$bus.$on("事件名",callback)
<!-- IncrementCount.vue -->
<template>
<p>{
{
msg}}</p>
</template>
<script>
//import $bus from "../bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
$bus.$on("aMsg", (msg) => {
// A发送来的消息
this.msg = msg;
});
}
};
</script>
4、Vuex
概念 : Vuex是一个专为Vue.js应用程序开发的状态管理模式
,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex由五部分组成:
- State:
state为
单一状态树
,在state中需要定义我们所需要管理的数组、对象、字符串等等,只有在这里定义了,在vue.js的组件中才能获取你定义的这个对象的状态。
- Getter:
getter有点类似vue.js的计算属性,当我们需要从store的state中派生出一些状态,那么我们就需要使用getter,getter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来,只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算。
- Action:
action可以提交mutation,在action中可以执行store.commit,而且action中可以有任何的异步操作。在页面中如果我们要嗲用这个action,则需要执行
store.dispatch
- Mutation:
更改store中state状态的唯一方法就是提交mutation,就很类似事件。每个mutation都有一个字符串类型的事件类型和一个回调函数,我们需要改变state的值就要在回调函数中改变。我们要执行这个回调函数,那么我们需要执行一个相应的调用方法:
store.commit
。
- Module:
module其实只是解决了当state中很复杂臃肿的时候,module可以将store分割成模块,每个模块中拥有自己的state、mutation、action和getter。
Vuex的高级语法:
- 数据持久化
- 模块化管理数据 (modules)
- 辅助函数(语法糖)
数据持久化:
问题:存储在vuex中的状态,刷新页面,会丢失。 为了解决刷新页面数据丢失,才有了
数据持久化
。
使用数据持久化最简单的方法就是使用插件:
vuex-persistedState
安装:
cnpm install vuex-persistedState -S
使用:
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
state,
mutations,
actions,
getters,
plugins: [createPersistedState({
storage: sessionStorage,
key: "token"
})]//会自动保存创建的状态。刷新还在
})
参数:
storage:存储方式。(sessionStorage,localStarage) key:定义本地存储中的key
模块化管理数据 (modules):
- 什么时候需要用到
模块管理vuex数据
。
当项目庞大,数据信息量特别大的时候,我们可以考虑分模块形式管理数据,比如user模块管理用户信息数据,cart模块管理购物车数据,shop模块管理商品信息数据。
import vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex);
const state= ()=>{
token:''}
const actions = {
set_token({
commit},val){
commit("to_token",val)
}
}
const mutations = {
to_token(state,val){
state.token=val;
}
}
const getters = {
}
//user模块
let user = {
namespaced: true, //一定要开始命名空间。
state: {
userid: 1234 },
actions: {
},
mutations: {
SET_USERID(state, val) {
state.userid = val;
}
},
getters: {
}
}
//购物车数据的模块
let cart = {
namespaced: true,
state: {
userid: 567 },
actions: {
},
mutations: {
},
getters: {
}
}
const store = new Vuex.Store({
state,
mutations,
actions,
getters,
modules: {
user,
cart
},
plugins: [createPersistedState({
storage: sessionStorage,
key: "token"
})]//会自动保存创建的状态。刷新还在
})
export default store
home.vue如何使用:
获取user模块的`userid`
this.$store.state.user.userid
this.$store.commit("SET_USERID",12345)
辅助函数(语法糖)
-
有那几个辅助函数(4大金刚)
mapState
,mapActions
,mapMutations
,mapGetters
-
辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作`的目的
-
如何使用
home.vue
<template>
<div id="">
{
{
token }}
{
{
token - x }}
</div>
</template>
<script>
import {
mapActions, mapGetters, mapMutations, mapState } from 'vuex'
import {
createNamespacedHelpers} from 'vuex'
const {
mapState:mapStateUser,mapActions:mapActionUser,mapMutations:mapMutaionuser} = createNamespacedHelpers('user')
const {
mapState:mapStateCart,mapActions:mapActionCart,mapMutations:mapMutaionCart} = createNamespacedHelpers('cart')
export default {
name: '',
data() {
return {
}
},
computed: {
...mapState({
token: 'token'
}),
...mapGetters(['token-x']),
...mapSateUser(['userid']),
...mapStateCart({
cartid:'userid'})
},
//生命周期 - 创建完成(访问当前this实例)
created() {
this.setToken('123456')
},
//生命周期 - 挂载完成(访问DOM元素)
mounted() {
},
methods: {
...mapActions({
setToken: 'setToken'
}),
...mapMutations(['SET_TOKEN']),
...mapMutaionUser({
setId:"setToken"
})
}
}
</script>
5、 vue-router(路由原理,路由守卫,路由传参)
路由实现原理:
概念:
通过改变URL
,在不重新请求页面
的情况下,更新页面视图。
实现方式:
vue-router通过hash
与History interface
两种方式实现前端路由,更新视图但不重新请求页面”是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式:
1.hash
---- 利用URL中的hash(’#’);
2.利用History interface
在HTML5中的新增方法
6.vue生命周期
简单粗暴:
答: 总共分为8个阶段。创建前/后
, 挂载前/后
,更新前/后
, 销毁前/后
。
创建前/后: beforeCreated
创建前,vue实例的挂载元素$el
和数据对象data
都是undefined
,还未初始化。在created
阶段/创建后,vue实例的数据对象data
有了,$el
还没有。
挂载前/后:beforeMount
挂载前,vue实例的el
和data
都初始化了,但是还没有挂载html 到页面上。在Mounted
阶段/挂载后,模板中的 HTML 渲染到 HTML 页面中,此时一般可以做一些 ajax 操作
,mounted 只会执行一次。
更新前/后: 当data变化
时,会触发beforeUpdate
和updated
方法
销毁前/后: 在destroy
阶段/销毁前,对data的改变不会再触发周期函数,说明此时vue实列已经解除了事件监听和dom绑定
,但是dom结构依然存在。destroyed
阶段,组件销毁
7. 自定义指令,自定义过滤器
Vue 自定义指令如何使用
- 自定义指令分为全局自定义指令和局部自定义指令:
- 全局自定义指令:
使用 Vue.directive()来全局注册指令 - 局部自定义指令:
也可以注册局部指令,组件或 Vue 构造函数中接受一个 directives 的选项
钩子函数
自定义指令定义函数提供了几个钩子函数: - bind
只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作 - inserted
被绑定元素插入父节点时调用( 父节点存在即可调用, 不必存在于
document 中) - update
所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新 - componentUpdated
所在组件的 VNode 及其孩子的 VNode 全部更新时调用 - unbind
只调用一次,指令与元素解绑时调用
参数
钩子函数中有两个参数:
1.el : 指令所绑定的元素,可以用来直接操作 DOM
1.binding : 一个对象,包含以下属性:
name: 指令名,不包括 v- 前缀
value: 指令的绑定值
oldValue: 指令绑定的前一个值(update 和 componentUpdated 钩子中可用。无论值是否改变都可用)
expression: 绑定值的字符串形式
arg: 传给指令的参数
使用场景: 当我们需要对普通的DOM元素进行底层操作时就可以用到自定义指令
全局自定义指令:
//main.js中
// 全局自定义指令
Vue.directive(
'upper-text',{
bind: (el, binding)=> {
console.log(el, binding)
el.innerHTML = binding.value.toUpperCase() //转换大写
}
})
局部自定义指令
//组件中
<template>
<div class="wrapper">
<br />
<span>原数据</span>
<p>{
{
message}}</p>
<br />
<span>全局</span>
<p v-upper-text="message" v-color="'blue'"></p>
<br />
<span>局部</span>
<p v-lower-text="message"></p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'You can start the Internet!'
}
},
computed: {
},
//局部自定义指令
directives: {
'lower-text'(el, binding) {
//el:指令属性所在的标签对象
//binding:包含指令相关信息数据的对象
console.log(el, binding)
el.textContent = binding.value.toLowerCase() //转换小写
},
color:{
//改变颜色
bind(el,binding){
el.style.color = binding.value
}
}
},
}
</script>
8. 自定义组件
简介
组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树
功能
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子
组件注册
组件名
组件名应该始终是多个单词的,根组件 App 除外
这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的
单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)
混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因
- 使用 kebab-case
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用
kebab-case
,例如<my-component-name>
Vue.component('my-component-name', {
/* ... */ })
- 使用 PascalCase
当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说
<my-component-name>
和<MyComponentName>
都是可接受的。注意,尽管如此,直接在 DOM
(即非字符串的模板) 中使用时只有 kebab-case 是有效的
Vue.component('MyComponentName', {
/* ... */ })
全局注册
以上方法都属于全局注册, 也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中, 比如
- HTML
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
- JS
Vue.component('component-a', {
/* ... */ })
Vue.component('component-b', {
/* ... */ })
Vue.component('component-c', {
/* ... */ })
new Vue({
el: '#app' })
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用
局部注册
如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的 components 属性实现局部注册
9. 常用的指令,修饰符。
在vue中提供了一些对于页面 + 数据的更为方便的输出,这些操作就叫做指令,指令中封装了一些DOM行为, 结合属性作为一个暗号, 暗号有对应的值,根据不同的值,框架会进行相关DOM操作的绑定
vue中的指令有很多,我们平时做项目常用的有:
v-if
:是动态的9向DOM树种添加或者删除元素;
v-else
:是搭配v-if使用的,它必须紧跟在v-if或者v-else-if后面,否则不起作用
v-show
: 是通过标签的CSS样式display的值是不是none,来控制显示隐藏
10. vue2和vue3的区别
11. keep-alive
概念:
是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现的父组件链中。
作用:
在组件切换的过程中,把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载事件以及性能消耗,提高用户体验性
原理:
在 created
钩子函数调用时将需要缓存的 VNode
节点保存在this.cache
中/在 render
(页面渲染) 时,如果VNode的 name
符合缓存条件(可以用 include
以及 exclude
控制),则会从 this.cache
中取出之前缓存的 VNode
实例进行渲染
VNode: 虚拟DOM,其实就是一个JS对象
参数:
- include - 字符串或者正则表达式。只有匹配的组件会被缓存。
- exclude - 字符串或正则表达式。然后匹配的组件都不会缓存。
- max - 数字。最多可以缓存多少组件实例
对生命周期函数变化:
被包含在keep-alive中的组件,会多出来两个生命周期的钩子函数,activated
和deactiveated
1.activated
在keep-alive
组件激活时
调用
2.deactivated
在keep-alive
组件离开时
调用
//正常的生命周期:beforeRouterEnter --> created -->mounted --> updated --> destroyed
//使用keepAlive后生命周期:
//首次进入缓存页面 :beforeRouterEnter --> created -->mounted --> updated --> destroyed
//再次进入缓存页面 :beforeRouteEnter --> activated --> deactivated
//注:
//1、这里的activated非常有用,因为页面被缓存时,created,mounted等生命周期均失效,你若想进行一些操作,那么可以在activated内完成(下面会举个栗子:列表页回到上次浏览位置)
//2、activated keep-alive组件激活时调用,该钩子在服务器端渲染期间不被调用。
//3、deactivated keep-alive组件停用时调用,该钩子在服务端渲染期间不被调用
可以结合Router中的meta,来缓存部分页面