前端VUE面试题总结

目录

VUEX

vue-router

前端路由原理

路由模式

hash模式

history模式

路由传参

路由跳转

vue的路由守卫

路由相关问题

脚手架rem适配

封装api请求

多环境变量

webpack配置

vue的双项数据绑定

vue虚拟dom,diff算法

vue的组件化开发

组件通信

slot插槽

vue的data为什么是一个函数

vue的常用指令

vue的自定义指令

vue中的修改符     

vue的生命周期

vue中的watch

computed,methods,watch的区别

过滤器filter

keep-alive

v-for为什么要使用key

$nextTick()的作用

vue修改数据后页面不刷新


VUEX

什么是vuex

Vuex:是vue的状态管理工具,就是管理公共数据的,这个数据在任何组件都能使用

vuex的持久化数据

安装插件

npm i vuex-persistedstate --save

 在store/index.js 中引入

import  vuexPersist  from  "vuex-persistedstate"

配置

plugins: [ new vuexPersist({  storage: window.localStorage,   }).plugin,  ],

vuex的五大核心

state

状态 存放数据,相当于data

mutations

修改state的地方,而且只有它有权利修改state

getters

相当于计算属性

actions

在vuex中执行异步操作的地方

modules

模块 当state比较多的时候进行分模块管理

state的使用       this.$store.state.xxx  

建议使用计算属性,这样在数据改变时会自动更新     

mutations的使用     this.$store.commit ( ‘事件名’ , 传的参数)

mutations 里的事件有两个参数,第一个必须是 state ,就是vuex的state, 第二个参数是传的值

actions的使用   

  里面的每个事件都有一个 context 参数,是一个关联 vuex 上下文的实例

  可以在 vue 页面里通过 this.$store.dispath('事件名') 提交 actions

vuex的辅助函数

辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的

都有什么辅助函数

mapState  mapActions  mapMutations  mapGetters

在vue页面引入辅助函数

import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'

注意事项:  

mapState与mapGetters 推荐在计算属性里 使用, 可以直接做data数据使用

 computed: {
    ...mapState(["shopData"]),
  },

其他辅助函数在 methods 里

methods: {
    ...mapActions(["getData"]),
    ...mapMutations(["add"]),
  }

点此查看详细的模块化与辅助函数的使用

vue-router

前端路由原理

路由就是用来解析URL以及调用对应的控制器,在不重新请求页面的情况下,更新页面的视图

路由模式

路由分为hash模式与history模式

hash与history的切换

在router的index.js文件中,有一个new Router(),里面的mode默认为hash模式,可以在这里更改为history模式

const router = new VueRouter({
    routes,
    mode:'hash'  //可在这里更改为history模式
})

hash模式

hash模式:在浏览器中符号的“#”,以及#后面的字符称之为hash

http://localhost:8080/#/login

特点

1 hash不会被包含在http请求中,因此,改变hash不会重新加载页面

2 hashchange 用来监听hash改变的事件

3 每一次改变,都会在浏览器中添加一个历史记录

方法

HashHistory.push     跳转路由,添加历史记录栈

HashHistory.replace()  跳转路由,直接替换当前页面,不会添加历史
 

history模式

http://localhost:8080/home

特点

1 history模式的URL要与后端的URL一样,后端如果没有对应路由,则会返回404错误

popState(window.onpopstate用来监听history改变的事件

方法

history.pushState()   跳转路由,添加历史记录栈

history.replaceState()   跳转路由,直接替换当前页面,不会添加历史

路由传参

query,params,动态路由传参

(1)  params只能使用name  query可以使用name和path  

(2)  使用params传参刷新后丢失,而query传参刷新后不会丢失

(3)  params在地址栏中不会显示,query会显示

(4)  params可以和动态路由一起使用,query不可以

// query 传参
this.$router.push({ path:'/user', query:{ userId:5 } }) 
// params 传参
this.$router.push({ name:'user', params:{ userId:5 } }) 
// 动态路由传参 
 path: 'config/:lawCode/salary/:lawVersionCode',
 name: 'basicLawSalary',
 this.$router.push(`config/${this.lawCode}/salary/${this.lawVersionCode}`)
// 动态路由获取
 this.$route.params.lawCode

路由跳转

导航式路由

<router-link to='/'>

编程式路由

router.push( )

将有记录存放在历史栈中,点击后退,将返回上一级的记录

router-link to 就是调用的push

router.replace()

不会存放历史栈,而是替换了当前的历史

可以在router-link中添加replace或router.push对象中添加replace:true 激活当前功能

router.go(-3)

()里的参数为整数,指的是在历史栈中前进或后退的步数

-1为返回上一历史,0为刷新页面,1为返回下一历史

vue的路由守卫

路由守卫分为全局守卫,路由独享守卫,组件内守卫

每个守卫接收三个参数 

to        即将要进入的目标路由对象

from   当前导航正要离开的路由

next   钩子函数,里面定义参数,确认下一步路由要做什么

全局守卫

router.beforeEach 全局前置守卫  最合适用在判断进入的页面是否需要登录
router.beforeResolve

全局解析守卫  

在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用

router.afterEach 全局后置钩子  一般用来设置动态的请求头   再这里next()是多余的

点击查看全局守卫具体应用

路由独享守卫  beforeEnter

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

组件内守卫

beforeRouteEnter

进组组件前的守卫  

注意:在这里是拿不到vue的this的,但是可以通过传一个回调给 next来访问组件实例

next ( vm=> { console.log(vm) } )    

vm就是vue的组件实例

通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消

beforeRouteUpdate 路由更新时的守卫  
beforeRouteLeave 离开组件时的守卫  

完整的导航解析流程

1   在失活的组件里调用 beforeRouteLeave 组件离开守卫
2   调用全局的 router.beforeEach 全局前置守卫
3   在重用的组件里调用 beforeRouteUpdate 组件更新守卫
4   在路由配置里调用 beforeEnter 路由独享守卫
5   解析异步路由组件。
6   在被激活的组件里调用 beforeRouteEnter 组件进入守卫
7   调用全局的 router.beforeResolve 全局解析守卫
8   导航被确认。
9   调用全局的 router.afterEach 全局后置钩子
10 触发 DOM 更新。
11  调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

路由相关问题

点击查看vue路由跳转同一页面报错问题

脚手架rem适配

脚手架的rem适配

封装api请求

封装api请求

多环境变量

多环境变量

webpack配置

webpack可以进行配置请求跨域,alais别名,打包配置

打包配置包括:更改路径,去掉生产环境的sourceMap资源映射文件,去除console.log打印以及注释,使用CDN 加速优化,对资源文件进行压缩,图片压缩,公共代码抽离,打包分析

点击查看具体配置代码

vue的双项数据绑定

vue2

结合发布者-订阅者模式,通过Object.defineProperty()来劫持各个属性的setter,getter  在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图,实现数据和视图同步。

具体步骤

第一步: 需要observer(观察者)对数据对象进行递归遍历,包括子属性对象的属性,都加上 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之间的通信桥梁,达到数据变化,视图变化的同步更新
 

vue3

利用es6的proxy的set和get方法来实现数据的劫持,进一步结合vue的watcher的update方法来实现双项数据绑定

v3相比于v2的优点

  1. 直接监听数组类型的数据变化
  2. 监听的目标为对象本身,不需要遍历每个属性,有一定的性能提升
  3. 可直接实现对象属性的新增/删除

vue虚拟dom,diff算法

什么是虚拟DOM

虚拟DOM就是用普通的js对象来描述真实DOM结构,这个对象有标签名(tag),属性(attrs),子元素对象(children)三个属性,通过vue的render()函数把虚拟的DOM转换为真实的DOM,在通过appendChild()添加至页面

为什么使用虚拟DOM

创建真实DOM成本比较高,频繁操作dom是一种比较大的开销,用虚拟dom成本比较低

虚拟DOM的性能不一定高于常规DOM,虚拟DOM和Diff算法的出现是为了解决由命令式编程转变为函数式编程、数据驱动后所带来的性能问题的

使用了虚拟DOM只是减少用户操作dom,其实还是会操作真实dom的

使用虚拟DOM更有益于diff算法的对比

vue2的diff算法

  1. 用 js 对象结构构建一个真正的 DOM树,插到文档当中
  2. 当状态变更的时候,重新构造一棵新的虚拟DOM。
  3. 然后用新的虚拟DOM和旧的虚拟DOM进行比较 所有dom都对比,把所记录的差异应用到所构建的真正的 DOM 树上,视图就更新了

vue3的diff算法

  1 也就是在生成VNode的同时打上标记,在这个基础上再进行核心的diff算法

  2 对于不参与更新的元素,做静态标记并提示,只会被创建一次,在渲染时直接复用

vue的组件化开发

组件开发的好处

1 减少代码的体积与重复性

2 容易维护,容易修改

3 封装好的组件,容易复用,使用简单

4 一个页面,分组件共同开发,更符合团队合作

如何封装组件

1 组件分为页面级组件与子组件。页面级组件就是view文件夹中的组件

2 新建一个components文件夹,用来放置我们的子组件

3 一个子组件,也是一个vue的文件

4 想要使用这些子组件,需要根据路径引入子组件,同时在components里注册

5 将子组件放置在想要放置的页面的位置中,注意使用驼峰命名法

组件的使用场景

1 头部input的搜索框

2 页面中的相同模块,例如,头部底部的模块,导航条,面包屑,通过插槽修改数据

3 轮播图的封装,返回顶部按钮,阶梯导航等功能

组件的样式穿透

css样式穿透  >>>       sass样式穿透 /deep/

组件之间的传值与组件使用插槽

详情请看下面的内容

组件通信

父传子

1 自定义属性传值

在父组件的子组件标签上绑定一个属性,挂载要传输的变量。在子组件中通过props来接受数据

prop

props可以是数组也可以是对象,接受的数据可以直接使用数组接受,也可以使用对象形式来判别数据类型,是否是必传值,与默认值

props:{ 
  name:{ 
     type:string,  //数据类型
     default:'000',//默认值,只有值是undefined或者不传属性的时候才会生效 
     required:true,  //是否必须传递  } 
}

注意:引用数据类型的默认值需要通过一个函数返回才能使用 ,否则vue的逻辑会报错

default: () => { return []/{}  }

prop的特性

1 prop形成的数据是单向流,父组件数据改变会影响子组件,但子组件不会改变父组件

2 不能在子组件中,直接修改prop传递过来的值,不然会报警告

3 prop的验证就是定义传递的数据类型,是否是必传值与默认值

4 prop没有验证时,组件可以接受任意的值,如果名称相同,prop数据会顶替原本的数据

provide传值

父组件使用 provide:{  } 定义数据

子组件使用 inject:[  ]  接受数据

注意:provide提供的数组 在它的任意后代组件中都可以使用

this.$parent

子组件中通过 this.$parent 找到父组件,获取父组件所有的数据以及方法

子传父

1 自定义事件

在父组件的子组件标签上通过绑定自定义事件,接受子组件传递过来的事件。子组件通过$emit触发父组件上的自定义事件,发送参数

this.$children

组件中通过 this.$children 找到组件,获取的是一个包含所有子组件的类数组集合

非父子组件传值

1 在main.js定义一个全局的bus,bus为一个Vue的实例化对象

 Vue.prototype.$bus = new Vue()

需要传递的数据的页面调用事件并传递数据

this.$bus.$emit('getData', data:this.data)

需要接受数据的页面创建事件并获取数据

this.$bus.$on('getData', (data) => {  this.msg = data  })

4beforeDestory中关闭监听事件

beforeDestory(){
this.$bus.$off('getData')
}

注意:运行的顺序是,先在接受数据的页面创建事件,然后才是传递数据的页面调用事件

每次的传值都会创建一个事件,过多会消耗内存

slot插槽

插槽分为匿名插槽,具名插槽,作用域插槽

匿名插槽

子组件内部,设置 <slot /> ,就可以使用匿名插槽,数据将方式在插槽对应的位置,如果有多个匿名插槽,将会每个插槽都有一份数据渲染

匿名插槽其实也是有名字的,为 default ,通常都是不写的

具名插槽

通过给插槽命名,以此来区分多个插槽

命名:v-slot:name  可简写为  #name

V2的使用

<div slot=‘left’> </div>
或
<template v-slot:left> </template>

V3的使用

<template v-slot:left> </template>

作用域插槽

通过自定义属性,来预备插槽所使用的数据

子组件中

<slot name="z" :say1="say1" :say2="say2"></slot>

子组件标签中

<template #z="{say1, say2}">  //解构作用域插槽的数据
   <div>{
   
   {say1}}</div>
</template>

vue的data为什么是一个函数

根实例对象data可以是对象也可以是函数(根实例是单例),不会造成数据冲突问题

而组件实例的data都是同一个引用数据,当该组件作为公共组件共享使用,如果它是一个对象,会报错,并且一个地方的data更改,所有的data一起改变,相反,如果data是一个函数,每个实例的data都在闭包中,就不会各自影响了。

vue的常用指令

v-if

根据条件判断元素的添加与删除,是惰性的,一开始条件不成立,就不会创建,

有更高的切换消耗,适应于条件不太可能改变的情况

v-else

是搭配v-if使用的,它必须紧跟在v-if或者v-else-if后面,否则不起作用

v-show

根据条件判断元素的显示隐藏,他是通过添加与删除 display:none 来控制元素

有更高的初始渲染消耗,更适用于频繁的状态切换

v-for

v-for是根据遍历数据来进行渲染,要配合key使用。要注意的是当v-for和v-if同处于一个节点时,v-for的优先级比v-if更高,不能与 v-if 同时使用,除非使用 template 空标签包裹

v-on 用来绑定一个事件或者方法,可以简写为@click
v-bind

用来绑定元素的属性,一般用于绑定class值或style样式,可以简写为

绑定class的三种方式:

1、对象型 ‘{red:isred}’

2、三元型 ‘isred?“red”:“blue”’

3、数组型 ‘[{red:“isred”},{blue:“isblue”}]

v-model 使用与表单元素,实现双项数据绑定,本质为自定义事件@input和自定义属性:value的集合
v-html  用于渲染富文本,即带有html的字符串

vue的自定义指令

vue允许我们设置自定义指令,自定义指令分为局部自定义指令与与全局自定义指令

局部自定义指令directives,定义在组件内部,只能在当前组件使用

全局自定义指令Vue.directive作用在全局

自定义指令的参数

el

所绑定的元素,可以直接操作

binding

一个对象,包含以下属性: name: 指令名,不包括 v- 前缀

Vnode

当前节点信息

v2的自定义指令钩子函数

bind

只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置

inserted

被绑定元素插入父节点时调用

update

所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前

componentUpdated

指令所在组件的 VNode 及其子 VNode 全部更新后调用

unbind

只调用一次,指令与元素解绑时调用

v3的自定义指令钩子函数

created        

创建后调用

beforeMount

自定义指令绑定到 DOM 后调用. 只调用一次

mounted

自定义指令所在DOM, 插入到父 DOM 后调用

beforeUpdate

自定义指令所在 DOM, 更新之前调用

updated

组件VNode 和子VNode 更新后执行

beforeUnmount

销毁前

unmounted

销毁后

使用场景

输入框自动聚焦

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
<input v-focus>

相对时间的转换  例:5分钟前

<span v-relativeTime="time"></span>
new Vue({
  el: '#app',
  data: {
    time: 1565753400000
  }
})

Vue.directive('relativeTime', {
  bind(el, binding) {
    // Time.getFormatTime() 方法,自行补充
    el.innerHTML = Time.getFormatTime(binding.value)
    el.__timeout__ = setInterval(() => {
      el.innerHTML = Time.getFormatTime(binding.value)
    }, 6000)
  },
  unbind(el) {
    clearInterval(el.innerHTML)
    delete el.__timeout__
  }
})

传入一个颜色,自动为全局背景色

let app = createApp(App)
// 全局自定义指令
app.directive('color', {
    created(el, binding) {
        el.style.background = binding.value;
    },
    updated(el, binding) {
        el.style.background = binding.value;
    }
})

vue中的修改符     

事件修饰符

.stop

阻止事件冒泡

.capture

触发事件捕获

.self

当事件作用在元素本身时才会触发

.once

只触发一次

.prevent

阻止默认事件

·passive

告诉浏览器你不想阻止事件的默认行为

·trim

自动过滤用户输入的首尾空格

v-model的修饰符

 .lazy <input v-model.lazy="msg">  转变为在change事件再同步
.number 用法同上  自动将用户的输入值转化为数值类型
.trim 用法同上  自动过滤用户输入的首尾空格

 键盘事件修饰符,支持多个按键

keyup.13 / enter 点击回车键
.delete 删除或退格键
.up    .down  .left   .right 上下左右按键
@keydown.ctrl.13 同时按下Ctrl + enter时触发

element或子组件的修饰符

对于组件库与子组件来说,都是封装好的事件,书写事件将会成为一个子传父

使用 @click.native ,可以解决这个问题

vue的生命周期

beforeCreate

实例创建前,目前只有一些实例本身的事件和生命周期函数,无法拿到dom和data数据

created

实例创建后,是最早可以使用data和methods中数据的钩子函数。仍然无法拿到dom

beforeMount

在created与beforeMount之间,是用来形成虚拟dom

DOM挂载前,但挂载的是之前的虚拟dom

mounted

DOM挂载后,在此刻dom渲染完毕,页面和内存的数据已经同步,最早的可以获取DOM结构的位置

beforeUptate

更新前,当data的数据被渲染在DOM上,并且发生改变才会执行这个钩子函数

updated

更新后,在此刻内存和页面都是最新的

beforeDestroy

销毁前,即将销毁data和methods中的数据,但再此时还是可以使用的,可以做一些释放内存的操作,同时也是最后可以获取实例与dom的钩子函数

destroyed

已经销毁完毕

activated

被包含在 keep-alive 中创建的组件才会有这个钩子,组件激活时触发

deactivated

被包含在 keep-alive 中创建的组件才会有这个钩子,组件失活时触发

vue中的watch

watch,数据监听,用于监听数据变化,当对应数据变化时触发

应用场景

watch{
 str:{ 
  handler(newVal){},  //引用数据类型的新旧值是一致的,所以是只有一个新数据就可
  deep:ture,          //是否深度监听
  immediate:true     //是否开启初始监听
 }	
}

如果不需要开启深度监听与初始监听,可简写为

watch{
 str(newVal){}	
}

computed,methods,watch的区别

computed是计算属性,对应数据变化,自动计算,需要return返出最终的值,拥有缓存,数据不变时,调用缓存

methods是vue中的事件与方法,需要手动调用才能触发

watch是数据监听,不需要return返回值,数据变化自动监听,可判别变化前后的差异

过滤器filter

过滤器是对 即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据,需要使用return返回最终的值

过滤器分为局部过滤器和全局过滤器,使用方法是在要过滤的数据后添加  

keep-alive

keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。

在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性

被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated(组件激活时使用) 与 deactivated(组价离开时调用)                    

如果需要缓存整个项目,直接在app.vue中用keep-alive包裹router-view即可要缓存部分页面需要在路由地址配置中,在meta属性中添加一个状态,在app.vue中判断一包裹的router-view即可

v-for为什么要使用key

key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点,如果不使用key的话,不影响正常的使用,但是会在控制台报警告

$nextTick()的作用

作用

$nextTick()是在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

使用场景

你在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,因为在此时还拿不到dom节点

如果是一个input框,在让他显示之后,立即让他自动获取焦点,是无法实现的,因为vue的render()函数将虚拟dom转换真实dom的时候,是异步的,无法在当时直接获取到dom,因此,可以使用this.$nextTick(),再里面书写获取焦点的代码,就可以实现这个功能了

vue修改数据后页面不刷新

<template>
  <div>
	<p v-for="(value,key) in item" :key="key">
	    {
   
   { value }}
	</p>
	<button @click="addProperty">动态添加新属性</button>
  </div>
</template>

<script>
export default {
 data:()=>{
       item:{
            oldProperty:"旧值"
        }
    },
    methods:{
        addProperty(){
            this.item.newProperty = "新值"  // 为items添加新属性
            console.log(this.items)  // 输出带有newProperty的items
        }
    }

};
</script>

问题:同上所属,在直接给一个对象添加新属性并赋值时,是可以在控制台打印出最新的信息的,但是,这个数据,是无法自动更新至视图的

原因:vue2是使用Object.defineProperty来实现数据的双项数据绑定的,他是给所有的属性添加了set和get,而这样直接的添加新属性并赋值的操作,这个新的属性上,是没有绑定set的,不能触发set,就不能实现双项数据的绑定

Vue 不允许在已经创建的实例上动态添加新的响应式属性

解决方法

1 使用 Vue.$set() 添加新属性  适用于添加少量属性

this.$set(this.item, "newProperty", "张三");

2 使用 Object.assign() ,创建一个新的对象,合并原对象和一个包含新增属性的对象  适用于添加大量属性

this.item = Object.assign({},this.item,{newProperty:'新值'})

$forceUpdate  强制刷新,但不建议这样

this.item.newProperty = "新值" 
this.$forceUpdate();

猜你喜欢

转载自blog.csdn.net/hjdjhh/article/details/122427693