前端面试题之Vue篇(下)

351 阅读3分钟

vue.png

2022前端面试系列:

(1)前端面试题之HTML篇

(2)前端面试题之CSS篇

(3)前端面试题之js篇

(4)前端面试题之Vue篇(上)

一、路由

1.1 路由的hash和history模式的对比

  • hash模式:在hash模式下,# 就是hash符号会出现在url中。当url改变时, hash不会重加载页面,通过监听hash的变化来执行js代码,从而实现页面的改变;
  • history模式:只要刷新这个url就会请求服务器,每一次路由改变都会重新加载

history模式在生产环境下,刷新页面会出现 404 ,解决办法是在  nginx 中配置按顺序检查参数中的资源是否存在,如果都没有找到,让 nginx内部重定向到项目首页 try_files $uri $uri/ /index.html;

1.2 如何获取页面的hash变化

(1)监听$route的变化

// 监听,当路由发生变化的时候执行
watch: {
  $route: {
    handler: function(val, oldVal){
      console.log(val);
    },
    // 深度观察监听
    deep: true
  }
},

(2)window.location.hash读取#值 window.location.hash 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。

1.3 $route 和$router 的区别

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

1.4 如何进行路由跳转?如何获取传过来的动态参数?

(1)param方式

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

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

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

参数获取:

this.$route.params.userid

(2)query方式

// 方法1:
<router-link :to="{ name: 'users', query: { id: 123 }}">按钮</router-link>

// 方法2:
this.$router.push({ name: 'users', query:{ id: 123 }})

// 方法3:
<router-link :to="{ path: '/user', query: { id: 123 }}">按钮</router-link>

// 方法4:
this.$router.push({ path: '/user', query:{ id: 123 }})

// 方法5:
this.$router.push('/user?id=' + myId)

获取参数:

this.$route.query.id

1.5 Vue-Router导航守卫

  1. 全局路由钩子

vue-router全局有三个路由钩子;

  • router.beforeEach 全局前置守卫 进入路由之前
  • router.beforeResolve 全局解析守卫(2.5.0+)在 beforeRouteEnter 调用之后调用
  • router.afterEach 全局后置钩子 进入路由之后

具体使用∶

beforeEach(判断是否登录了,没登录就跳转到登录页)

router.beforeEach((to, from, next) => {  
    let ifInfo = Vue.prototype.$common.getSession('userData');  // 判断是否登录的存储信息
    if (!ifInfo) { 
        // sessionStorage里没有储存user信息    
        if (to.path == '/') { 
            //如果是登录页面路径,就直接next()      
            next();    
        } else { 
            //不然就跳转到登录      
            Message.warning("请重新登录!");     
            window.location.href = Vue.prototype.$loginUrl;    
        }  
    } else {    
        return next();  
    }
})

afterEach (跳转之后滚动条回到顶部)

router.afterEach((to, from) => {  
    // 跳转之后滚动条回到顶部  
    window.scrollTo(0,0);
});

2. 单个路由独享钩子

beforeEnter 如果不想全局配置守卫的话,可以为某些路由单独配置守卫,有三个参数∶ to、from、next

export default [    
    {        
        path: '/',        
        name: 'login',        
        component: login,        
        beforeEnter: (to, from, next) => {          
            console.log('即将进入登录页面')          
            next()        
        }    
    }
]

3. 组件内钩子

beforeRouteUpdate、beforeRouteEnter、beforeRouteLeave

这三个钩子都有三个参数∶to、from、next

  • beforeRouteEnter∶ 进入组件前触发
  • beforeRouteUpdate∶ 当前地址改变并且被改组件被复用时触发,举例来说,带有动态参数的路径foo/∶id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的foa组件,这个钩子在这种情况下就会被调用
  • beforeRouteLeave∶ 离开组件被调用

4.6 Vue-router跳转和location.href有什么区别

  • 使用 location.href= /url 来跳转,简单方便,但是刷新了页面;

  • 使用 history.pushState( /url ) ,无刷新页面,静态跳转;

  • 引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为vue-router就是用了 history.pushState() ,尤其是在history模式下。

二、vuex

2.1 Vuex 的原理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化。

Components建立起了一个完整的生态圈,包括开发中的API调用一环。

(1)核心流程中的主要功能:

  • Vue Components 是 vue 组件,组件会触发(dispatch)一些事件或动作,也就是图中的 Actions;
  • 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;
  • 然后 Mutations 就去改变(Mutate)State 中的数据;
  • 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。

(2)各模块在核心流程中的主要功能:

  • Vue Components∶ Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
  • dispatch∶操作行为触发方法,是唯一能执行action的方法。
  • actions∶ 操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
  • commit∶状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
  • mutations∶状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
  • state∶ 页面状态管理容器对象。集中存储Vuecomponents中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
  • getters∶ state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

2.2 Vuex中action和mutation的区别

mutation中的操作是一系列的同步函数,用于修改state中的变量的的状态。当使用vuex时需要通过commit来提交需要操作的内容。mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      state.count++      // 变更状态
    }
  }
})

当触发一个类型为 increment 的 mutation 时,需要调用此函数:

store.commit('increment')

而Action类似于mutation,不同点在于:

  • Action 可以包含任意异步操作。
  • Action 提交的是 mutation,而不是直接变更状态。
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。 所以,两者的不同点如下:

  • Mutation专注于修改State,理论上是修改State的唯一途径;Action业务代码、异步请求。
  • Mutation:必须同步执行;Action:可以异步,但不能直接操作State。
  • 在视图更新时,先触发actions,actions再触发mutation
  • mutation的参数是state,它包含store中的数据;store的参数是context,它是 state 的父级,包含 state、getters

2.3 Vuex 和 localStorage的区别

(1)最重要的区别

  • vuex存储在内存中
  • localstorage 则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要 JSON的stringify和parse方法进行处理。 读取内存比读取硬盘速度要快

(2)应用场景

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。
  • localstorage是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。
  • Vuex能做到数据的响应式,localstorage不能

(3)永久性

刷新页面时vuex存储的值会丢失,localstorage不会。

注意: 对于不变的数据确实可以用localstorage可以代替vuex,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage无法做到。

2.4 Vuex有哪几种属性?

有五种,分别是 state、 getter、mutation 、action、 module

  • state => 基本数据(数据源存放地)
  • getters => 从基本数据派生出来的数据
  • mutations => 提交更改数据的方法,同步
  • actions => 像一个装饰器,包裹mutations,使之可以异步。
  • modules => 模块化Vuex

2.5 为什么 Vuex的mutation不能做异步操作?

  • Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。

  • 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

三、vue性能优化

3.1 对SSR的理解

SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端

SSR的优势:

  • 更好的SEO
  • 首屏加载速度更快

SSR的缺点:

  • 开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
  • 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
  • 更多的服务端负载。

3.2 vue初始化页面闪动问题

使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是还是有必要让解决这个问题的。

首先:在css里加上以下代码:

[v-cloak] {    display: none;}

如果没有彻底解决问题,则在根元素加上style="display: none;" :style="{display: 'block'}"

3.3 Vue的性能优化有哪些

(1)编码阶段

  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
  • v-if和v-for不能连用
  • 如果需要使用v-for给每项元素绑定事件时使用事件代理
  • SPA 页面采用keep-alive缓存组件
  • 在更多的情况下,使用v-if替代v-show
  • key保证唯一
  • 使用路由懒加载、异步组件
  • 防抖、节流
  • 第三方模块按需导入
  • 长列表滚动到可视区域动态加载
  • 图片懒加载

(2)SEO优化

  • 预渲染
  • 服务端渲染SSR

(3)打包优化

  • 压缩代码
  • Tree Shaking/Scope Hoisting
  • 使用cdn加载第三方模块
  • 多线程打包happypack
  • splitChunks抽离公共文件
  • sourceMap优化

(4)用户体验

  • 骨架屏
  • PWA
  • 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

四、Vue3.0

4.1 Vue3与Vue2的区别?

  1. vue3支持大多数Vue2的特性
  2. Vue3中设计了一套强大的组合API代替了option API,复用性更强了
  3. vue3更好的支持TS
  4. 重要:Vue3中使用了Proxy配合Reflect代替了Vue中的Object.defineProperty()方法实现数据的响应式(数据代理)
  5. 重写了虚拟DOM,速度更快了
  6. 新的组件:Fragment(片段)/Teleport(瞬移)/Suspense(不确定)

4.2 defineProperty和proxy的区别

Vue 在实例初始化时遍历 data 中的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。这样当追踪数据发生变化时,setter 会被自动调用。

Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

但是这样做有以下问题:

  • 加或删除对象的属性时,Vue 检测不到。因为添加或删除的对象没有在初始化进行响应式处理,只能通过$set 来调用Object.defineProperty()处理。
  • 无法监控到数组下标和长度的变化。

Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。相对于Object.defineProperty(),其有以下特点:

  • Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。
  • Proxy 可以监听数组的变化。

4.3 Vue 3.0 中的 Vue Composition API?

在 Vue2 中,代码是 Options API 风格的,也就是通过填充 (option) data、methods、computed 等属性来完成一个 Vue 组件。这种风格使得 Vue 相对于 React极为容易上手,同时也造成了几个问题:

  1. 由于 Options API 不够灵活的开发方式,使得Vue开发缺乏优雅的方法来在组件间共用代码。
  2. Vue 组件过于依赖this上下文,Vue 背后的一些小技巧使得 Vue 组件的开发看起来与 JavaScript 的开发原则相悖,比如在methods 中的this竟然指向组件实例来不指向methods所在的对象。这也使得 TypeScript 在Vue2 中很不好用。

于是在 Vue3 中,舍弃了 Options API,转而投向 Composition API。Composition API本质上是将 Options API 背后的机制暴露给用户直接使用,这样用户就拥有了更多的灵活性,也使得 Vue3 更适合于 TypeScript 结合。