前端面试题之vue

227 阅读13分钟

1、vue 生命周期

系统自带有8个生周期

  1. beforeCreate 实例创建之前,还不能访问data的属性
  2. created 实例创建完成,可以访问data的属性、
  3. beforeMount
  4. mouted
  5. beforeUpdate
  6. updated
  7. beforeDestory
  8. destoryed

一旦进入到页面或者组件,会执行哪些生命周期,顺序

会执行前4个

在那个阶段有elDOM)和el(DOM) 和 data

  • beforeCreate:什么也没有
  • created: 有data,没有$el,
  • beforeMount: 有data,没有$el
  • mouted: 什么都有 data和$el

如果加入了keep-alive会多两个生命周期

  • activated (组件激活时调用),
  • deactivated (组件停用时调用)

如果加入keep-alive,第一次进入组件会执行哪些生命周期

  • beforeCreate
  • created
  • beforeMount
  • mouted
  • activated

如果加入keep-alive,第n次进入组件会执行哪些生命周期

  • activated 因为组件已经被缓存

2、谈谈你对keep-alive的了解

是什么

vue 系统自带的一个组件,功能是用来缓存组件,好处是提升性能

使用场景

用来缓存组件,提升项目性能,具体实现比如:首页进入详情页,如果用户在首页每次点击都是相同的,那么详情页就不用请求了,直接缓存起来,如果点击是不同的,那么直接请求。

3、v-if 和v-show区别

展示形式不同

  • v-if 是创建一个dom节点
  • v-show 是css控制显示隐藏,display:none,block

使用场景不同

  • 初次加载v-if比v-show好,页面不会做加载盒子
  • 频繁切换v-show比v-if好,创建和删除的开销太大,显示和隐藏的开销较小

4、v-if和v-for的优先级

  • v-for的优先级比v-if的高 veu源码中体现的,function genElement

5、ref是什么

  • 来获取Dom的

6、nextTick实现原理

  • 获取更新后的dom内容
  • 是异步的

nextTick方法主要是使用了宏任务和微任务,定义一个异步方法,多次调用nextTick会将方法存在队列中,通过这个异步方法清空当前队列。所以这个nextTick方法就是异步方法

这句话说的很乱,典型的让面试官忍不住想要深挖一探究竟的回答。(因为一听你就不是真的懂)

正确的流程应该是先去 嗅探环境,依次去检测

Promise的then -> MutationObserver的回调函数 -> setImmediate -> setTimeout 是否存在,找到存在的就使用它,以此来确定回调函数队列是以哪个 api 来异步执行。

nextTick 函数接受到一个 callback 函数的时候,先不去调用它,而是把它 push 到一个全局的 queue 队列中,等待下一个任务队列的时候再一次性的把这个 queue 里的函数依次执行。

这个队列可能是 microTask 队列,也可能是 macroTask 队列,前两个 api 属于微任务队列,后两个 api 属于宏任务队列。

7、scopde原理

作用

  • 让样式在本组件中生效,不影响其他组件

原理

  • 给节点添加自定义属性,然后css根据属性选择器添加样式

8、vue中如何做样式的穿透

scss

  • 父元素 /deep/ 子元素 (class名字)

stylus

  • 父元素 >>> 子元素

9、组件之间的传值通讯

父组件 传值 子组件

- props
- ref

子组件 传值 父组件

- $emit

兄弟之间传值

- eventBus

父组件 传值给 子孙组件

  • provide/inject

vuex

props和refref和children和provide/inject的主要区别:

  • props 侧重于数据的传递,并不能获取子组件里的属性和方法,适用于自定义内容的使用场景

  • $ref 侧重于获取子组件里的属性和方法,并不是太适合传递数据,并且 ref 常用于获取dom元素,起到选择器的作用

  • $children 侧重于获取所有的直接子组件,得到的是一个无序的数组,并不太适合向多个子组件传递数据

  • provide/inject 侧重于在开发高阶插件/组件库时使用,并不推荐用于普通应用程序代码中

10、computed、methods、watch有什么区别

methods

  • methods中, 没有缓存,不管数据有没有变化,只要触发事件,就会调用函数

computed

  • 是计算属性,有缓存,只有依赖的数据发生了变化,才会重新计算
  • 不支持异步,当Computed中有异步操作时,无法监听数据的变化

watch

  • watch 是监听 无缓存性,数据变化时,它就会触发相应的操作
  • watch 支持异步监听
  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值

运用场景

  • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
  • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

11、props和data的优先级谁高

  • props > methosd > data > computed > watch

12、vuex

状态管理仓库。

管理的是数据,方法,某一个方法,或者数据会在很多组件去使用,可以封装一个方法,放到vuex中,然后让不同的组件去用

(1)有哪些属性

  • state 存放数据 (类似data)
  • getter 计算属性 (类似computed)
  • mutations 存放方法 (类似methods 存放方法)
  • action 提交mutations
  • modules 模块化,把以上四个属性在细分,让仓库更改管理

(2)action和mutation有什么区别

  • action 提交的是 mutation,而不是直接变更状态。mutation可以直接变更状态

  • action 可以包含任意异步操作。mutation只能是同步操作

  • 提交方式不同,action 是用this.store.dispatch(actionname,data)来提交。mutation是用this.store.dispatch(action_name,data)来提交。mutation是用this.store.commit('SET_NUMBER',10)来提交。

(3) State

  • 存数据的地方,所有数据都存在state里面

(4) getter和辅助函数mapGetters

计算属性,

示例

// store.js

import Vue from "vue"
import Vuex from "vuex"


export default new Vuex.Store({
    state: {
        atr: '您好',
        arr: ['a','b','c']
    },
    getters: {
        changeArr(state){
            return state.arr.join('=')
        }
    },
    mutations:{},
    actions:{}
})

// vue 组件
<template>
    <div>
    {{str}}
    {{changeArr }}
    </div>
</template>
<script>
    import {mapState, mapGetters} from 'vuex'
    export default{
        computed: {
            ...mapState(['str']),
            ...mapGetters(['changeArr'])
        }
    }
</script>

(5) Mutations和辅助函数mapMutations

  • mutations里面存的就是一些操作数据的方法。
  • 同步
// 页面使用

vue 组件需要引入import { mapMutations} from 'vuex'

在mtehods中引入

methosd: {
    ...mapMutations(['方法名'])
}

(6) Actions

  • actions 包含一些方法,actions不能直接改变state里的数据,通过提交mutations,mutations里面包含的才是具体操作数据的方法
  • 异步

使用方法同Mutations

vuex是单向数据流还是双向数据流

  • vuex是单向数据流,页面不能直接修改state里面数据

vuex如何做持久化存储

vuex本身不是持久化存储,刷新页面就会初始化

  • 使用localStorage自己写
  • 使用vuex-persist 插件

vue设置代理

代理来解决跨域问题的

// vue.confiig.js

module.exports = {
    devServer: {
        proxy: '代理的url'
    }
}

13、介绍一下SPA(single page web application)及SPA有什么缺点

  • SPA单页面应用

缺点

  • SEO优化不好,因为打包后只有一个html页面
  • 性能不是特别好

14、路由的hash和history模式的区别

hash模式

  • hash模式是开发中默认的模式,它的URL带着一个#,例如:www.abc.com/#/vue,它的hash值就是#/vue

  • 特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。

history模式

  • history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
  • 特点: 当使用history模式时,URL就像这样:abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。 API: history api可以分为两大部分,切换历史状态和修改历史状态:

15、vue路由传值

显示传参,query传参,url可以看到参数

// query传参 http://localhost:8080/about?id=1
this.$router.push({
    path: '/about',
    query: {
        id: 1
    }
})

// 拿参数

this.$route.query.id

隐式传参, params传参,url看不到参数

// query传参 http://localhost:8080/about
this.$router.push({
    name: 'about',
    params: {
        id: 1
    }
})

// 拿参数

this.$route.params.id

16、路由导航守卫有哪些

  • 全局守卫
  • 组件守卫
  • 独享守卫 (单个路由守卫)

全局守卫 守卫所有的页面 写在 router/index.js

1. beforeEach((to,from,next)=>{})
2. afterEach((to,from,next)=>{}) 

组件守卫 守卫单个组件

  1. beforeRouteEnter((to,from,next)=>{}) // 路由进入之前

  2. beforeRouteLeave((to,from,next)=>{}) // 路由离开之前

  3. beforeRouteUpdate((to,from,next)=>{}) // 路由更新之前

总结,他们接受一个函数作为参数,函数里面有三个参数,都是to,from,next

路由独享

  1. beforeEnter((to,from,next)=>{})
{ 
   path: '/mainpage', 
   name: 'About', 
   component: About, // 路由独享守卫 
   beforeEnter:(to,from,next) => {
       if(from.name === '/mainpage/about'){
            alert("这是从about来的") 
       }else{
           alert("这不是从about来的") 
       } next(); // 必须调用来进行下一步操作。否则是不会跳转的 
       }
   } 
},

17、vue双向数据绑定原理

Vue 内部通过 Object.defineProperty方法属性拦截的方式,把 data 对象里每个数据的读写转化成 getter/setter,当数据变化时,通知订阅者,触发响应的回调来实现的。

具体过程包括以下几个关键步骤:

  1. 数据劫持:Vue遍历data中的所有属性,使用Object.defineProperty为每个属性设置getter和setter,拦截数据访问和修改。
  2. 依赖收集:在getter中,Vue收集依赖该属性的Watcher实例,建立数据与视图的关联。
  3. 派发更新:在setter中,当数据修改时,Vue通知依赖该数据的Watcher实例进行更新。
  4. Watcher触发更新:Watcher收到通知后,重新执行渲染函数,生成新的虚拟DOM,通过diff算法对比新旧虚拟DOM,仅更新变化的DOM部分。

此外,Vue2的响应式系统还包括一些辅助机制:

vue2针对对象和数组怎么处理的

  • 遍历对象所有可枚举属性,进行getter和setter的劫持
  • 重写数组方法:push、pop、shift、unshift、splice、sort、reverse

问题补充:vue2有哪些不足:

  • 对象新增属性、删除属性,需要使用Vue.set、Vue.delete
  • 不能监听数组下标的变化,需要使用Vue.set
  • 不能监听对象属性的删除,需要使用Vue.delete

18、什么是mvvm框架

是一个软件架构设计模式,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。

image.png (1)View 层

View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。

(2)Model 层

Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。

(3)ViewModel 层

ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。

我们以下通过一个 Vue 实例来说明 MVVM 的具体实现,有 Vue 开发经验的同学应该一目了然:

(1)View 层

<div id="app">
    <p>{{message}}</p>
    <button v-on:click="showMessage()">Click me</button>
</div>

(2)ViewModel 层

var app = new Vue({
    el: '#app',
    data: {  // 用于描述视图状态   
        message: 'Hello Vue!', 
    },
    methods: {  // 用于描述视图行为  
        showMessage(){
            let vm = this;
            alert(vm.message);
        }
    },
    created(){
        let vm = this;
        // Ajax 获取 Model 层的数据
        ajax({
            url: '/your/server/data/api',
            success(res){
                vm.message = res;
            }
        });
    }
})


(3) Model 层

{
    "url": "/your/server/data/api",
    "res": {
        "success": true,
        "name": "IoveC",
        "domain": "www.cnblogs.com"
    }
}


原文章传送门

19、vue中this.$set的使用

为什么用$set

  • Vue无法检测到data中数组和对象的变化,因此也不会触发视图更新

使用方法

  • Vue.set(target,key,value)

Vue不允许动态添加根级响应式属性

使用场景

  • 当我们对data中的数组或对象进行修改时,有些操作方式是非响应式的,Vue检测不到数据更新,因此也不会触发视图更新。此时需要使用Vue.set()进行响应式的数据更新。

20、vue组件data为什么必须是一个函数,而vue的根实例则没有限制?

因为对象是引用类型,当组件被复用时,对象就会被共享,会造成数据混乱

vue 组件中可能存在多个实例,使用对象形式定义data,则会导致多个实例公用一个data对象,那么状态变更将会影响有所组件实例,

  • 采用函数定义,在vue源码中initdata方法中会将其作为一个工厂函数返回全新data对象,有效规避多个实例之间状态污染问题

  • 而vue根实例创建过程中不存在该限制,也是因为根实例只能有一个,不需要担心这种情况

  • 在创建根实例的时候,只能创建一个

// 创建根实例
const app = new Vue({
    el: '#app',
    data: {
        counter: 1,
    }
})

21、vue中key的作用和工作原理?说说你对他的理解

  • key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch的过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使整个patch过程更加高效,减少DOM操作量,提高性能。
  • 若不设置key,还可能在列表更新时引发一些隐藏的bug
  • vue中使用相同标签元素的过渡切换时,也可以使用key属性,其目的是为了让vue可以区分他们,否则vue只会替换其内部属性而不触发过渡效果

22、DIFF算法的原理

  • 首先对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点,重新创建节点进行替换
  • 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点,(如果新的children没有子节点,将旧的节点移除)
  • 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。
  • 匹配时,找到相同的子节点,递归比较子节点

在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n3)降低值O(n),也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较

23 、Vue 如何将模板渲染成真实 DOM

24、常见的事件修饰符及其作用

  • .stop: 防止事件冒泡(等同于 JavaScript 中的 event.stopPropagation()
  • .prevent: 防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播)等同于 JavaScript 中的 event.preventDefault()
  • .capture :与事件冒泡的方向相反,事件捕获由外到内;
  • .self :只会触发自己范围内的事件,不包含子元素;
  • .once :只会触发一次。

25、Vue-Router 的懒加载如何实现

非懒加载:

import List from '@/components/list.vue'
const router = new VueRouter({
  routes: [
    { path: '/list', component: List }
  ]
})

(1)方案一(常用):使用箭头函数+import动态加载

const List = () => import('@/components/list.vue')
const router = new VueRouter({
  routes: [
    { path: '/list', component: List }
  ]
})

(2)方案二:使用箭头函数+require动态加载

const router = new Router({
  routes: [
   {
     path: '/list',
     component: resolve => require(['@/components/list'], resolve)
   }
  ]
})

(3)方案三:使用webpack的require.ensure技术,也可以实现按需加载。 这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

// r就是resolve
const List = r => require.ensure([], () => r(require('@/components/list')), 'list');
// 路由也是正常的写法  这种是官方推荐的写的 按模块划分懒加载 
const router = new Router({
  routes: [
  {
    path: '/list',
    component: List,
    name: 'list'
  }
 ]
}))

26、 $route 和$router 的区别

  • $route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数
  • $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。

27、路由跳转

// 方法1:
<router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link

// 方法2:
this.$router.push({name:'users',params:{uname:wade}})

// 方法3:
this.$router.push('/user/' + wade)

28、vue2与vue3的区别

-创建实例vue2是newVUe(),vue3是趁热阿特APP()

  • 源码上vue2是js,vue3是ts
  • vue2是选项是api,vue是组合式api,
  • 响应式:vue2是definePropety,vue3是proxy
  • 生命周期不同:
    • vue2:beforeCreate、created、beforMount、munted、beforeUpdate、updated、beforeDestroy、destroyed;
    • vue3:setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted
  • vue2每个组件必须有一个根节点,vue3支持多节点
  • vue2使用ts需要借助插件
  • vue2支持过滤器, $ on $ off $ once 等方法,Vue 3:移除了过滤器,推荐使用计算属性或方法替代; $ on $ off $ once 被移除,推荐使用 mitt 或 EventEmitterv-model 语法有所调整,支持多个 v-model 绑定

29、父组件怎么监听子组件生命周期

  • vue2 使用 @hook:mounted
  • vue3 使用 @vue:mounted

30、v-for中key的作用

用来标识列表中每个元素的 唯一标识符,key的存在可以帮助vue更高效的更新和渲染dom

31、页面跳转时,滚动到指定模板

  • 在具体页面,mounted构子中滚动到某个模块(可以通过参数判断啥的)
  • scrollBehavior声明路由时,配置dom、top等属性,页面存在指定元素
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: {
      title: 'Home',
      keepAlive: true,
      scrollTo: {
        el: '#about-img',
        top: 0
      }
    }
  },
  {
    path: '/about',
    name: 'About',
    component: About,
  }
];

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (to.meta.scrollTo) {
      return {
        el: to.meta.scrollTo.el,
        top: to.meta.scrollTo.top,
        behavior: 'smooth'
      };
    }
  }
});

export default router;