【Vue】面试题合集

203 阅读12分钟

Vue的hash模式和history模式有什么区别?

  1. 显示差别:
    • hash模式的路由地址上有#号
    • history模式没有
  2. 在做回车刷新时:
    • hash模式会加重对应页面
    • history模式会报错404
  3. 浏览器支持:
    • hash模式支持低版本浏览器
    • history模式不支持低版本浏览器,因为是H5新增的API
  4. 场景适用:都适用于单页面应用
    • hash模式不会重新加载页面
    • history模式需要后端配合支持,否则会出现大量404
  5. 历史记录:
    • hash模式无历史记录
    • history模式有历史记录,H5新增了pushstate和replacestate去修改历史记录,并不会立刻发送请求
  6. 后台配置:
    • hash模式不需要
    • history模式需要

说一下Vue的动态路由

要在路由设置里设置meta属性,扩展权限相关的字段,在路由导航首位通过判断这个权限表示,实现路由的动态增加和调整

根据用户登录的账号,返回用户角色

前端再根据角色,跟路由表的meta.role进行匹配

把匹配搭配的路由形成可访问的路由

computed 和 watch 的区别

  • 属性类型
    • computed是计算属性
    • watch是监听属性,监听的data中数据的变化
  • 缓存
    • computed是支持缓存。依赖的属性值发生变化,计算属性才会重新计算,否则用缓存
    • watch不支持缓存
  • 是否支持异步
    • computed不支持异步
    • watch可以操作
  • 第一次加载
    • computed是第一次加载就监听
    • watch是第一次加载默认不监听,当immediate的值设置为true时,就会监听
  • 关键字
    • computed函数中必须有return
    • watch不需要

vuex在什么场景去使用?属性有哪些?

  1. 属性

    • state 存储变量
    • getters state的计算属性
    • mutations 提交更新数据的方法
    • actions 和mutations差不多,他是提交mutations来修改属性,可以包括异步操作
    • modules 模块化vuex
  2. 使用场景

    用户的个人信息、购物车模块、订单模块

【Vue2】【Vuex】状态管理方式 - 掘金 (juejin.cn)

vue的双向绑定原理是什么?

通过数据劫持和发布订阅者模式来实现,同时利用 Object.defineProperty() 劫持各个属性的setter和getter,在数据发生改变的时候发布消息给订阅者,触发对应的监听回调渲染视图,也就是说数据和视图是同步的。

数据发生改变,数据跟着发生改变;视图改变,数据也会发生改变

  1. 第一步:需要 observer 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter
  2. 第二步:compile模板解析指令,把模板中的变量替换成数据,然后初始化渲染视图,同时把每个指令对应的节点绑定上更新函数,参加订阅者,如果数据变化,收到通知,更新视图
  3. Watcher订阅者是Observer和Compile之间的通信桥梁,作用:
    • 在自身实例化的时候往订阅器内添加自己
    • 自身要有一个update()的方法
    • 等等属性变动时,调用自身的update方法,触发compile这种的回调
  4. 第四步,MVVM作为数据绑定的入口,整合了Observer、Compile和watcher三者,整合了 observer、compile和watcher三者,通过observer来监听自己的数据变化,通过compile解析模板指令,最后利用watcher把observer和compile联系起来,最终达到数据更新视图更新,视图更新数据更新的效果

【拓展】

  • Vue2
    • 使用Object.definedProperty()

      Object.definedProperty() 是 es5 的属性,其通过 get() 和 set() 方法劫持数据的读写。

    • 对data数据进行遍历

    • 针对数组需要额外处理

  • Vue3
    • Proxy()

      Proxy() 是ES6的构造函数,用于拦截外界对目标对象的读写。不需要进行遍历

    • ref()、reactive()

      ref()、reactive()随时可以添加

    • 不需要对数组进行额外处理

      Proxy() 可以实现对数组的监听

了解diff算法和虚拟DOM吗?

  • 虚拟DOM:描述元素和元素之间的关系,创建一个JS对象

    如果组件内有响应的数据,数据发生改变的时候,render函数会生成一个新的虚拟DOM,这个新的虚拟DOM会和旧的虚拟DOM进行对比,找到需要修改的虚拟DOM内容,然后去对应的真实DOM中修改

  • diff算法:就是虚拟DOM的比对时使用的,返回一个patch对象,这个对象的作用就是存储两个节点不同的地方,最后用patch里记录的信息进行更新真实DOM

    算法运行步骤:

    • JS对象表示真实的DOM结构,要生成一个虚拟DOM,再用虚拟DOM构建一个真实DOM树,渲染到页面
    • 状态改变生成新的虚拟DOM,根就得跟虚拟DOM进行比对,这个比对的过程就是diff算法,利用patch记录差异
    • 把记录的差异用在第一个虚拟DOM生成的真实DOM上,就更新了

vuex的响应式处理

vuex是vue的状态管理工具

vue中可以直接触发 methods 中的方法,vuex是不可以的。未来处理异步,当触发事件的时候,会通过dispatch来访问actions中的方法,actions中的commit会触发mutations中的方法从而修改state里的值,通过getter把数据更新到视图中

Vue.use(vuex),调用install方法,通过applyMixin(vue)在任意组件内执行 this.$store 就可以访问到 store 对象。

vuex的state是响应式的,借助的就是vue的data,state存到vue实例组件的data中

vuex刷新数据会丢失吗?怎么解决?

页面肯定会重新获取数据,页面也会丢失数据

  1. 将数据直接保存在浏览器缓存里(cookie、localstorage、sessionstorage)

  2. 页面刷新的时候,再次请求数据,达到动态更新的方法

    监听浏览器的刷新数据,在舒心前将数据保存到 sessionstorage 里,刷新请求数据,请求到了用vuex,如果没有那就用 sessionstorage 里的数据

vue中遍历全局的方法有哪些?

  1. 普通遍历:对象.forEach();
  2. 对元素统一操作:对象.map();
  3. 查找符合条件的元素,对象.filter();
  4. 查询符合条件的元素,返回索引:对象.findIndex();

new Vue() 做了什么?

new Vue() 是创建一个新的Vue实例,我们可以从以下几个步骤去分析new Vue() 整个过程都干了些什么?

首先第一步就是“初始化”,在该步骤中,会对组件实例进行初始化操作。

  • 第一步:初始化
    • 首先是进行一些必要的初始化操作:合并配置项、生命周期钩子函数、监听事件和render函数。因为Vue框架规定,每个实例,必须有规定的生命周期函数等,所以在一开始,我们需要将这几项初始化好。
    • 接着就是执行钩子函数 beforeCreate
    • 此时就可以初始化注入函数、状态
    • 接着就继续执行钩子函数created

当需要的东西都已初始化完毕,那么下一步,我们就需要将该组件实例转换成VNode,并将VNode转换为真实DOM,并挂载到页面上。这一步我们可以叫“挂载”

  • 第二步:挂载

    • 挂载前,需要先确认render函数存在与否,template存在与否,如果它们不存在就需要做一些处理
    • 等这些都处理完毕后,就可以执行钩子函数beforeMount
    • 接着,我们就需要为更新机制做些准备工作,比如:初始化updateComponentwatcherOptions,待准备工作完成,就需要进行更新机制的形成了
    • 到此为止,我们的组件实例就已经完成挂载操作,然后执行钩子函数mounted

    需要特别注意的是更新机制形成的过程,updateComponentwatcherOptions的作用:

    • updateComponent,在初始化它时,我们的创建VNode和生成真实DOM这两工作,就是在这里完成的
    • watcherOptions,初始化它后,在页面更新前会调用before函数,因此会触发钩子beforeCreate

    接下来,我们说说生成VNode节点这个工作:如果组件实例不满足一定条件的话,就会直接返回一个空的VNode,满足了就会将组件实例转换成对应的VNode,并返回

    此时我们已经得到该组件实例对应的VNode,需要将它们转换为真实DOM,并挂载到页面上。在转换之前,需要将旧状态的el、VNode和活动实例都备份一份下来,然后获取组件的最新信息,并更新指向。这里最重要的是patch算法,也就是我们日常所说的diff算法,它的规则是这样的:

    • 新节点不存在,老节点存在,调用 destroy,销毁老节点

    • 如果 oldVnode 是真实元素,则表示首次渲染,创建新节点,并插入 body,然后移除老节点

    • 如果 oldVnode 不是真实元素,则表示更新阶段,执行 patchVnode

到此,真实DOM就被创建渲染完成。

其实,我们是不是也可以用建房子来举个简单的栗子:

建房子之前,我们是不是要先跟我们的小伙伴沟通好建房子所需的人力、材料等等信息的安排,这就是初始化

接下来,我们是不是应该商量这个房子的构造、地基等信息,画出房子的模型图,根据用户的实际需求一遍一遍的优化,这个过程是不是就可以算是生成VNode。

等我们确定了房子的模型,下一步就是正式开始建房子,打地基、垒墙、上梁、装修等等工作,是不是就是真实DOM的创建、渲染?

【Vue】new Vue()做了什么?

KeepAlive有什么作用?会触发什么生命周期钩子?为什么有的会触发有的不会触发?最近最少使用算法

  • KeepAlive的作用

    • keepalive的作用:就是KeepAlive内部组件,多个动态组件切换时,缓存被移除的组件。当再次回到被移除组件,就不需要重新加载,可以减少重复的加载和渲染

    • KeepAlive的好处:就是一方面可以避免重复加载渲染组件带来的开销,另一方面可以保留组件的状态,当我们再次回到组件内,页面状态还是离开前的状态。最重要的是,可以防止重复加载渲染DOM,减少加载时间和性能消耗,从而提高用户体验性

    • KeepAlive的缺点:最主要的是当组件里面包含大量内容的时候就会占用更多的内容,相当于是在用空间换时间

    • 特别注意点:KeepAlive内部组件使用了iframe的时候,切换组件,iframe依然是会卸载的。理由如下:

      因为iframe是在组件内独立开辟了一个窗口,这个窗口很多东西是不受到父级组件的影响的,当它父级组件进入缓存的时候,iframe就自动卸载。这个问题的解决方案最简单的就是在回到该组件的时候重新挂载iframe

      想了解其它解决方案可以看我这篇文章:【Vue】页面使用了KeepAlive缓存机制后,切换标签含有iframe标签的界面只有最后被打开的页面有iframe正常展示 - 掘金 (juejin.cn)

  • 触发的生命周期 KeepAlive会触发 activateddeactivated 这两个钩子函数,并且这两个钩子在服务器渲染期间不被调用

    • activated:初始化页面和组件再次被激活都会触发
    • deactivated:在缓存的组件停用和卸载页面时都会触发

    另外,还需要我们注意的几点是:

    • KeepAlive不会再函数式组件中正常工作,因为它们没有缓存实例。
    • 事件挂载的方法等,只执行一次的可以放在created或mounted中,需要每次进去都执行的方法可以放在 actived中
    • KeepAlive是一个内置组件,一个抽象组件,与 template有点相似,都会有实际的标签
    • Vnode的 cache 构建,是在 KeepAlive 组件的 mounted 和 updated 两个生命周期通过 cacheSubtree 方法构建
    • 被KeepAlive包裹的组件不会生成真正的DOM节点。因为在Vue初始化生命周期的时候,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件。在KeepAlive中,设置了abstract: true,那Vue就会跳过该组件实例。最后构建的组件树中就不会包含 KeepAlive 组件,那么由组件树渲染成的DOM树自然也不会有KeepAlive相关的节点
  • KeepAlive的原理: 在created钩子函数调用时将需要缓存的Vnode节点保存在this.cache中,或者是在页面渲染时,发现this.cache中已缓存对应的Vnode,就直接取缓存的Vnode实例渲染

    KeepAlive的具体实现步骤如下:

    • 第一步,获取KeepAlive包裹着的第一个子组件对象及组件名

    • 第二步,根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。如果不匹配就直接返回组件实例(Vnode),否则执行第三步

    • 第三步:根据组件ID和tag生成缓存key,并在缓存对象中查找是否一缓存过该组件实例。如果存在,就直接取出缓存值,并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步

    • 第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过就根据LRU置换策略删除最久未使用的实例(即下标为0的那个key)

    • 第五步:将该组件实例的keepAlive属性值设置为true。

    这里面最重要的就是 LRU(Least recently Used)算法

    LRU(Least recently Used)算法:

    LRU(Least recently Used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

    KeepAlive 的实现正是用到了 LRU策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]

    【Vue】【内置组件】KeepAlive - 掘金 (juejin.cn)

怎么设计一个modal组件

modal组件,我们首先需要确认modal组件的构成有哪些?按照从外到内,从上到下的顺序,我们可以划分这几部分:遮罩层、标题、主体内容和底部按钮

modal组件的构成确定后,我们可以想想这个组件需要满足的设计有哪些?

  • modal组件挂载
    • 支持用户自定义组件挂载位置,默认挂载在body上
  • 遮罩层
    • 要支持是否显示遮罩层
    • 支持并暴露出单击遮罩层关闭弹窗的接口,也需要支持用户可关闭此功能
    • 支持用户自定义遮罩层样式
  • 弹窗
    • 支持用户自定义弹窗样式(宽度、高度等)和位置
    • 支持弹窗在页面中居中显示
  • 标题
    • 支持文本
    • 支持用户传入一段html代码
    • 支持用户不要标题块
  • 内容
    • 支持文本
    • 支持用户传入一段html代码
  • 底部
    • 默认两个按钮“取消”和“确定”,并暴露这两按钮的回调
    • 支持用户自定义这两个按钮的文本、显示和样式属性等的控制
    • 支持用户自定义底部内容
  • 右上角的关闭小图标
    • 支持用户自定义小图标
    • 支持用户控制小图标的显示与否
    • 暴露该按钮的回调
  • 关闭弹窗
    • 支持用户可配置关闭时是否销毁modal里的子元素
    • 支持用户可通过 ESC 键关闭弹窗

手摸手实现一个轻量级可扩展的模态框(Modal)组件 - 掘金

slot(插槽)的理解,和使用场景有哪些?

slot,Vue的slot跟原生的slot存在差异,Vue中的是对原生的进行了一些功能拓展。

slot本质上是返回VNode的函数

slot的作用,有以下几点:

  • 允许我们将组件的内容分发到其子组件中,以实现灵活的组件复用和自定义布局
  • 通过slot,父组件可以向子组件传递内容,这样可以根据需求定制组件的外观和行为。因为slot的作用域在父组件,不在子组件,能访问到父组件的数据,不能访问到子组件的数据
  • slot提供了一种强大的方式来创建通用组件,使其更具可定制性

另外就是,slot的种类也有以下几种:

  • 默认插槽:没有给插槽设置给name设置值,父组件的内容会直接被塞入默认插槽中。因为当我们不曾给name设置值时,它的默认值就是"default"
  • 具名插槽:就是插槽的name,我们设置了特定值,一个组件中可以设置多个具名插槽。会将父组件中的内容分发到特定的插槽位置,以满足不同情况下的布局需求
  • 作用域插槽:Vue提供了作用域插槽,许父组件传递数据到插槽内部,这样子组件可以访问和渲染这些数据。

最后,就是插槽的使用场景。因为通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理

如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情

通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用

比如布局组件、表格列、下拉选、弹框显示内容等

Vue的响应式原理

注意注意注意!!!响应式和双向绑定是两个东西!!!

响应式就是数据发生变化,触发页面重新渲染。

我们Vue2和Vue3实现响应式的原理是不同的。Vue2 是使用的 object.defineProperty的方式,Vue3 是使用的 Proxy方式。为啥Vue3的会跟Vue2的不同呢?主要理由如下:

  • object.defineProperty 的实现是重写了 setget。而且,这个方法只能遍历对象属性进行劫持。也无法监听到对象和数组的变化的,所以为了监听对象的变化提供了两个全局方法$set$delete;为了监听数组的变化重写了数组的几个常用API(push、splice...)。在实现时,还需要在初始化的时候遍历data,对每个数据进行处理,当data中的数据多或者层级很深的时候,就会出现白屏的情况
  • Proxy是可以直接劫持整个对象,并返回一个新对象,所以我们可以只操作新的对象达到响应式的目的。所以Vue2中无法监听数组和对象的,或者白屏的问题都不会出现在Vu3中。唯一算得上是缺点的就是在浏览器上的兼容性会差一点,不支持IE9以下的

【Vue】响应式 - 掘金

Vue的双向绑定原理

注意注意注意!!!响应式和双向绑定是两个东西!!!

Vue的双向绑定是采用的MVVM架构模式,所以它的构成是这三部分:数据层、视图层和业务逻辑层。

其中 业务逻辑层(ViewModel)是框架最核心的存在,它负责将数据与视图关联起来,所以它的主要职责就是:在数据变化后更新视图,在视图变化后更新数据。如果打个比如,那就是张三和李四两人之间的传话筒。然后就是ViewModel的主要组成:是监听器(Observe)和解析器(compiler)

双向绑定的大致流程是这样的:

1)在 初始化的时候,会对data做一个遍历,进行响应化处理,目的是为了劫持监听所有属性,这个过程是发生在 observe(监听器)里的

2)同时对模板执行搬移(compiler),找到其中动态绑定的数据,从 data 中获取并初始化视图,这个过程发生在 解析器(compiler)中

3)同时定义一个更新函数和 Watcher,将来对应数据变化时 Watcher 会调用函数

4)由于 data 的某个可以在一个视图中可能出现多次,所以每个key都需要一个管家dep来管理多个 watcher

5)将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有 watcher 执行更新函数

6aeee94ce0e341aea6f82f8f63b662d3_tplv-k3u1fbpfcp-jj-mark_3024_0_0_0_q75.webp

Vue的双向绑定的指令只有一个 v-model.async这个修饰符也可以实现双向绑定

【Vue】数据绑定方式 - 双向绑定

为什么会出现 nextTick?底层是怎么实现的?

nextTick 是DOM下次更新刷新的工具方法,本质上是执行延迟回调的钩子,接受一个回调函数作为参数,在下次DOM更新循环结束之后执行延迟回调。之所以我们需要 nextTick,理由很简单,跟Vue的异步更新机制有关,因为在我们刷新响应式数据后,Vue为了性能等多方面因素考虑,并不会立即更新该数据对应的DOM,而是会将多个更新一次执行,一次此时我们拿到的DOM还是旧状态,如果需要最新的DOM状态,就需要在更新后再去获取DOM。那么怎么才能知道DOM已经更新了呢?那就需要 nextTick 这样的API

nextTick 它的原理是这样的,在初始化时,会创建一个回调数据和异步延迟函数。然后在执行 nextTick 时,将注册的回调函数放入回调函数队列中,如果此时没有回调任务在执行,就执行该回调任务。如果没有传入回调函数就直接返回一个 promise。

所以在使用的时候,我们可以采用传入回调函数和 async/await 这两种方式。

另外就是,执行异步操作队列可能是宏任务,也可能是微任务,这个是根据我们当前环境的实际情况而定的。

【Vue】【全局API - 通用】nextTick

Vue 的生命周期有哪些?每个钩子可以做什么?父子组件钩子的执行顺序

Vue的生命周期我们可以按照以下几个阶段去划分:

  • 创建前后有 beforeCreate 和 created。

    • beforeCreated 表示组件实例被创建之初。因为是在实例还未挂载之前执行,所以此时要想使用组件实例,需要通过回调next,通常用于开发中执行一些初始化任务
    • created 表示组件实例已经完成创建。此时组件初始化完毕,各种数据都可以使用,但是需要注意的是此时依然还是不能获取组件实例上下文。通常用于无依赖的异步数据获取
  • 挂载前后有 beforeMount 和 mounted

    • beforeMount 表示组件挂载,此时尚未执行渲、更新,DOM也未创建,此时可以在渲染前最后一次更改数据,还不会触发钩子函数
    • mounted 表格组件挂载完毕。此时初始化结束,DOM已创建,可用于访问数据和DOM元素
  • 更新前后有 beforeUpdata 和 updated

    • beforeUpdate 表示data数据发生变化之前,所以在这里我们可以用来获取数据发生变化之前的数据状态

    • updated 表示data数据更新后,我们可以在这里获取数据的最新状态

    另外,我们需要知道的是,不是所有的data数据发生变化,都会触发这两个钩子。因为这两钩子是针对视图层数据,简单点理解,就是data中的响应式数据才会触发,或者也可以说是直接在标签中使用了的data数据

  • 销毁前后有 beforeDestory 和 destoryed

    • beforeDestory 组件实例销毁之前,此时我们仍然可以使用data和method。因此我们可以在这里销毁订阅定时器等
    • destoryed 组件实例销毁之后,此时data和method已经不可以使用,可以用来定时和监听的取消
  • 特殊场景 activated、deactivated 和 errorCaptured

    • activated 只有在 KeepAlive 组件中才会使用到的钩子,表示缓存组件激活。让页面初始化或再次进到该页面时,就会触发
    • deactivated 也是只有在 KeepAlive 组件中才会使用到的钩子,表示缓存组件停用。离开这个页面就会触发
    • errorCaptured 捕获一个来自子孙组件的错误时被调用

另外,就是在Vue3还新增了两生命周期onRenderTrackedonRenderTriggered,它们都是调试钩子,简单的理解就是只在开发调试的时候才会触发执行。

  • onRenderTracked 的作用就是在每次触发页面重新传染的时候自动执行,是会追踪每个值
  • onRenderTriggered 的作用就是当响应式依赖发生变更触发可组件渲染的时候调用,是精准追击,方便开发针对性调试

【Vue】生命周期 - 掘金 (juejin.cn)

组件的data为什么是一个函数?data里的同名数据怎么处理?

是因为如果是使用对象的方式,不同组件的data属性值共用了同一个内存地址,会导致组件之间的data属性相互干扰。而且,另一方面也是因为组件不能直接访问Vue实例中定义的数据。就算是可以访问,若将所有的数据都放在Vue实例中,Vue实例就会变得非常臃肿,所以组件应该有自己保存数据的地方

源码实现上,是两种方式都可以,但是在组件进行创建的时候,会进行选项的合并,定义data会进行数据校验,此时vm实例为undefined,若data类型不是 function,救护出现警告

这不是说,data在所有情况下都不适合用对象来定义,在根实例对象data可以是对象也可以是函数,因为根实例是单例,不会产生数据污染的情况。

组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

【Vue】【选项API】状态选项 - data