Vue面试题合集(原理深入,持续更新)

2023Vue面试题剖析原理


1.谈谈你对Vue的理解

Vue是一套用于构建用户界面的渐进式框架,Vue的核心只关注视图层

声明式框架

声明式代码更加简单,不需要关注实现,按照要求填写代码就可以

MVVM模式

目的:职责划分、分层管理
但Vue没有完全遵循MVVM模型,因为可以使用ref来操控数据

采用虚拟DOM

传统页面更新,拼接一个完整的字符串,innerHTML需要全部重新渲染,添加虚拟DOM后,可以比较新旧虚拟节点,找到变化再进行更新。

可以跨平台

虚拟DOM比真实DOM少很多属性,减少性能消耗

区分编译时(打包)和运行时(浏览器)

Vue的核心渲染就是调用渲染方法(render)将虚拟DOM渲染成真实DOM(缺点是虚拟DOM编写麻烦)

专门写个编译时可以将模板编译成虚拟DOM(在构建时候编译性能更高,不需要再运行的时候进行编译)

组件化

实现高内聚、低耦合、单向数据流

  • 组件化开发能大幅提高应用开发
  • 降低更新范围,只重新渲染变化的组件

2.淡淡你对SPA的理解

基本概念

  • SPA(single-page application)单页面应用,默认情况下编写Vue、React都只有一个html页面,并且提供一个挂载点,最终页面打包后会在此页面引入对应的资源。(页面的渲染全部是有JS动态进行渲染的)切换页面时通过监听路由变化,渲染对应的页面。Client Side Rendering,客户端渲染CSR
  • MPA(Multi-page application)多页面应用,多个html页面。每个页面必须重复加载,js,css等先关资源。(服务端返回完整的html,同时数据也可以在后端进行获取一并返回“模板引擎”)多页面应用跳转需要整页资源刷新。Server Side Rendering,服务端渲染 SSR

优缺点

在这里插入图片描述

  • 用户体验好,快,内容的改变不需要重新加载整个页面,服务端压力小
  • SPA应用不利于搜索引擎的爬取
  • 首次渲染速度相对较慢(第一次返回空html,需要再次请求首屏数据)白屏时间长

白屏时间长问题解决方案

  • 静态页面预渲染(Static Site Generation ,SSG),在构建时产生完整的html页面。就是在打包的时候,先将页面放到浏览器中运行一下,将html保存起来,但仅适合静态且页面变化率不高的网站
  • SSR+CSR的方式,首屏采用服务端渲染的方式,后续交互采用客户端渲染方式(NuxtJS)

3.Vue中为什么要引入虚拟DOM

基本概念

基本上所有框架都引入了虚拟DOM来对真实DOM进行抽象,也就是现在大家所熟知的VNode和VDom

  • Virtual DOM就是用用js对象来描述真实DOM,是对真实DOM的抽象,由于直接操作DOM性能低但是js层的操作效率高,可以将DOM操作转化成对象操作,最终通过diff算法对比差异进行更新DOM,来减少对真实DOM的操作
  • 虚拟DOM不依赖真实平台环境从而也可以实现跨平台

VDOM是如何生成的

  • 在Vue中常常会为组件编写模板-template
  • 这个模板会被编译器编译为渲染函数-render
  • 接下来的挂载过程中调用render函数,返回的对象就是虚拟DOM
  • 在后续的patch过程中进一步转化为真实DOM

VDOM如何做diff算法

  • 挂载过程结束后,会记录第一次生成的VDOM - oldVnode
  • 当响应式数据发生变化时,将会引起组件重新render,此时就会生成新的VDOM - newVnode
  • 使用oldVnode和newVnode做diff操作,将更改的部分映射到真实DOM上,从而转换为最小量的DOM操作,高效更新视图

4.Vue组件化的理解

组件化好处:高内聚、可重用、可组合

  • 组件化开发能大幅提高应用开发效率、测试性、复用性等
  • 降低更新范围,只重新渲染变化的组件

补充:

  • Vue中每个组件都有一个渲染函数,watch(Vue2),effect(Vue3)
  • 数据是响应式的,数据变化后会执行watcher或effect
  • 组件要合理的划分,如果不拆分组件,那更新的时候页面都要重新更新
  • 如果过分的拆分组件会导致watcher,effect产生过多,也会造成性能浪费

5.Vue有数据劫持进行精准探测数据变化,为啥还需要diff

Vue内部设计原因导致,Vue设计的是每个组件一个watcher(渲染watcher),没有采用一个属性对应一个watcher,这样会导致大量watcher的产生而且浪费内存,如果粒度过低也无法精准探测变化,所以采用diff算法+组件级watcher

6.对响应式数据的理解

如何实现响应式数据

数组和对象类型当值变化时如何劫持到。对象内部通过defineReactive方法,使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。多层对象是通过递归来实现劫持,Vue3则采用proxy

Vue2处理缺陷

  • 在Vue2的时候使用defineProperty来进行数据的劫持,需要对属性进行重写添加getter和setter,性能较差
  • 当新增属性和删除属性时无法监控变化,需要通过$set,$delete实现
  • 数组不采用defineProperty来进行劫持(浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理
  • 对于ES6中新产生的Map和Set不支持

Vue3实现响应式数据

通过proxy,没有重写属性,只是做了一层代理,而且拦截的是对象,而不是对象内的属性,因此可以监测新增属性和删除属性时的变化,且可以监测数组变化

而且当对象是多层时,不会一上来就进行递归。当不去取对象中数组时,数组是不会被代理的,是一种懒代理

7.Vue如何监测数组变化

实现数组拦截

  • 数组考虑性能原因没有用defineProperty对数组的每一项进行拦截,而是选择重写数组方法(push、pop、shift、unshift、splice、sort、reverse)
  • 数组中如果是对象属性类型也会进行递归劫持

数组拦截的不足

  • 数组的索引和长度变化是无法监控到的

8.Vue如何进行依赖收集

Vue2依赖收集

  • 每个属性都有自己的dep属性,存放所依赖的watcher(这个watcher会放在全局下,以方便该属性是在那个页面上渲染的),当属性变化后会通知自己对应的watcher去更新
  • 默认在初始化时会调用render函数,此时会触发属性依赖收集dep.depend
  • 当属性发生修改时会触发watcher更新dep.notify( )

Vue3依赖收集

  • Vue3中会通过Map结构将属性和effect映射起来,某个属性和effect直接做映射表,没有dep
  • 默认在初始化时会调用render函数,此时会触发属性依赖收集track
  • 当属性发生修改时会找到对应的effect列表依次执行trigger

9.Vue.set方法如何实现

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

  • 开发环境下,set的第一个参数target若没定义或是基础类型则报错
  • 如果是数组,则调用重写的splice方法,这样可以更新视图
  • 如果是对象本身的属性,则直接添加即可
  • 如果是Vue实例或根数据data时报错,更新_data无意义
  • 如果不是响应式的也不需要将其定义成响应式属性

当选择新增属性时,可以考虑使用对象合并的方式实现

10.Vue中的v-show和v-if的理解

基本概念

  • v-if如果条件不成立,不会渲染当前指令所在节点的dom元素,从当前dom树中删除该节点,源代码三元表达式
  • v-show只是切换当前dom的显示或隐藏,display:none,另注意和opacity、visivibility区别,源代码转为指令
  • Vue2中v-for优先级更高,Vue3中v-if优先级更高

如何选择

  • v-if可以阻断内部代码是否执行,如果条件不成立不会执行内部逻辑
  • 如果页面逻辑在第一次加载的时候已经被确认后续不会频繁更新则采用v-if

11.computed和watch区别

Vue2中有三种watcher(渲染watcher、计算属性watcher、用户watcher)
Vue3中有三种effect(渲染effect、计算属性effect、用户effect)

computed

  • 计算属性仅当用户取值时才会执行对应的方法
  • computed属性是具备缓存的,源码内部维护一个dirty属性,依赖的值不发生变化,对其取值的计算属性方法不会重新执行
  • 计算属性可以简化模板中复杂表达式
  • 计算属性中不支持异步逻辑
  • computed属性是可以在模板中使用的

watch

watch则是监控值的变化,当值发生变化时调用对应的回调函数。经常用于监控某个值的变化,进行一些操作。(异步要注意竞态问题)

Vue3提供了onCleanup函数,让用户更加方便使用也解决了清理问题

12.Ref和Reactive的区别

ref和reactive是Vue3数据响应式中非常重要的两个概念

  • reactive用于处理对象类型的数据响应式,底层采用的是new Proxy( ),但解构会丧失响应式
  • ref通常用于处理单值的响应式,ref主要解决原始值的响应式问题,底层采用的是Object.defineProperty( )实现的,基本类型包装成对象
const state = reactive({name:"张三",age:ref(18)})
console.log(state.age)  // 18,此处不需要.value,reactive会自动拆包

13.watch和watchEffect的区别

  • watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数
  • watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数
const effect = new ReactiveEffect(getter, scheduler)
effect.run()

//getter函数
watchEffect(()=>{
    app.innerHTML = state.name //数据变化后,会调用scheduler,内部会再次触发effect.run(),重新运行getter
})

//1.getter函数  2.cb函数(回调函数)
watch(
    ()=>state.name, //数据变化后,会调用scheduler,内部会调用cb
    (newVal,oldVal) => {}
)

14.Vue中如何将template转换成render函数

Vue中含有模板编译的功能,它的主要作用是将用户编写的template编译成js中可执行的render函数

  • 将template模板转换为AST语法树 – parseHTML
  • 对静态语法做静态标记 – markup diff来做优化的静态节点跳过diff算法
  • 重新生成代码 – codeGen

Vue3中的模板编译,做了很多优化操作,Vue2仅仅是标记了静态节点而已

15.new Vue( )过程中发生了什么

  • 在new Vue( )的时候内部会进行初始化操作
  • 内部会初始化组件绑定的事件,初始化组件的父子关系$parent、$children、$root
  • 初始化响应式数据data、computed、props、watch、method。同时也初始化了provie和inject方法。内部会对数据进行劫持,对象采用defineProperty数组方法重写
  • 再看用户是否传入了el属性和template或者render。render的优先级更高,如果用户写的是template,会做模板编译(三部曲)。最终就拿到了render函数
  • 内部挂载的时候会产生一个watcher,调用render函数会触发依赖收集。内部还会给所有的响应式数据增加dep属性,让属性记录当前的watcher(用户后续修改的时候可以触发watcher重新渲染)
  • Vue更新的时候采用虚拟DOM的方式进行diff算法更新

16.Vue.observable理解

在非父子组件通信时,可以使用eventBus或者使用状态管理工具,但是功能不复杂的时候,可以使用Vue.observable,创建一个响应式对象,来实现数据共享

17.v-if和v-for的优先级

v-for和v-if避免在同一个标签中使用。如果遇到需要同时使用时可以考虑写成计算属性的方式

  • 在Vue2解析时,先解析v-for,再解析v-if。会导致先循环后在对每一项进行判断,浪费性能
  • 在Vue3解析时,v-if的优先级高于v-for

18.生命周期有哪些

Vue2中的生命周期

Vue.js 的组件生命周期可以分为创建、更新和销毁三个阶段。下面是 Vue.js 2.x 版本的生命周期钩子函数列表及其触发时机:

  1. 创建阶段:
    • beforeCreate:在实例初始化之前,数据观测 (data observer) 和事件配置之前被调用。
    • created:在实例创建完成后被立即调用。此时实例已完成以下配置:数据观测 (data observer)、属性和方法的运算、watch/event 事件回调。但是挂载阶段还未开始,$el 属性目前不可见。
  2. 挂载阶段:
    • beforeMount:在挂载开始之前被调用,此时模板编译/抽象语法树转换已完成,但是尚未进行挂载操作。
    • mounted:在挂载完成后被调用,此时实例已经被完全挂载到 DOM 中。可以执行依赖于 DOM 的操作。
  3. 更新阶段:
    • beforeUpdate:在更新之前被调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在此钩子中对更新之前的状态进行修改。
    • updated:在更新完成后被调用,组件 DOM 已经更新,可以执行依赖于更新后 DOM 的操作。注意避免无限循环更新的情况。
  4. 销毁阶段:
    • beforeDestory:在卸载之前被调用。在这个阶段,实例仍然完全可用。
    • Destoryed:在实例卸载后被调用。此时,Vue 实例完全销毁,所有的事件监听器被移除,所有的子实例也被销毁。

除了上述生命周期钩子函数,Vue 2.x 还提供了一些其他的钩子函数,如activated、deactivated、errorCaptured 等,用于处理组件的动态组件切换、错误处理等特殊场景。

需要注意的是,以上钩子函数是 Vue.js 2.x 版本的生命周期,在 Vue.js 3.x 版本中发生了一些变化。Vue.js 3.x 使用了 Composition API,它提供了一种新的组件组织方式,并引入了 setup 函数作为组件的入口点。在 Vue.js 3.x 中,生命周期钩子函数的命名和触发时机也有所改变。详细的生命周期钩子函数列表和用法,请参考 Vue.js 的官方文档。

Vue2和Vue3生命周期对比

在这里插入图片描述

Vue3中新增了,组合式API:生命周期钩子,但是不存在onBeforeCreate和onCreated钩子

19.Vue中的diff算法原理

diff概念

Vue基于虚拟DOM做更新。diff算法的核心是比较两个虚拟节点的差异。Vue的diff算法是平级比较的,不考虑跨级比较的情况。内部采用深度递归+双指针的方式进行比较。

diff比较流程

  1. 先比较是否是相同节点(key和tag)
  2. 相同节点比较属性,并复用老节点(将老的虚拟dom复用给新的虚拟节点dom)
  3. 比较儿子节点,考虑老节点和新节点儿子的情况
    • 老的没子节点,现在有子节点,直接插入新的子节点
    • 老的有子节点,新的没子节点,直接删除页面节点
    • 老的子节点是文本,新的子节点是文本,直接更新文本节点即可
    • 老的子节点是一个列表,新的子节点也是一个列表,更新子节点
  4. 优化比较:两个子节点为列表:头头、尾尾、头尾、尾头,而乱序时创建映射表去复用
  5. 比较查找进行复用

注:Vue3中采用最长递增子序列来实现diff优化

20.Vue中key的作用和原理

key的概念

  • key的特殊属性主要用在Vue虚拟DOM算法,在新旧节点对比时辨别VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。
    • 例如:以下两个输入框,输入数据并切换时,数据仍保留,只是类型发生变化了,可加key破坏复用
<input type="text" v-if="!isPassword">
<input type="password" v-else>
  • 当Vue正在更新使用v-for渲染元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

key的作用

  • Vue在patch的过程中,通过key可以判断两个虚拟节点是否是相同节点(可以复用老节点)
  • 无key会导致更新的时候出问题(逆序添加,逆序删除等破坏顺序操作)
  • 尽量不要采用索引作为key

21.Vue.use作用和原理

use概念

安装Vue.js插件时,如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。install方法调用时,会将Vue作为参数传入,这样插件中就不需要依赖Vue了。如:Vuex,Vue-Router

插件的功能

  • 添加全局指令、全局过滤器、全局组件
  • 通过全局混入来添加一些组件选项
  • 添加Vue实例方法,通过把他们添加到Vue.prototype上实现

实现原理

Vue.use = function(plugin:Function | Object){
    //插件缓存
    const installedPlugins = this._installedPlugins || (this._installedPlugins = [])
    if(installedPlugins.indexOf(plugin) > -1){
        //如果已经有插件,直接返回
        return this
    }

    //添加参数
    const args = toArray(arguments, 1) //除了第一项,其它的参数整合成数组
    args.unshift(this) //将Vue放入数组中
    if(typeof plugin.install === 'function'){
        //调用install方法
        plugin.install.apply(plugin, args)
    }else if(typeof plugin === 'function'){
        //直接调用方法
        plugin.apply(null, args)
    }
    installedPlugins.push(plugin) //缓存插件
    return this
}

22.Vue.extend方法的作用

Vue.extend概念

使用基础Vue构造器,创建一个“子类”。参数是一个包含组件选项的对象。 data选项是特例,需要注意在Vue.extend( )中它必须是函数。

var Profile = Vue.extend({
    template:"<p>{{firstName}}{{lastName}} aka {{alias}}</p>",
    data:function(){
        return {
            firstName:'water',
            lastName:'white',
            alias:'Hi'
        }
    }
})
//创建Profile实例,并挂载到一个元素上
new Profile().$mount("#mount-point")

new Vue().$mount()

原理分析

  • 所有的组件创建时都会调用Vue.extend方法进行创建
  • 有了此方法可以手动挂载组件去指定节点
  • 后端存储的字符串模板,可以通过Vue.extend方法将其、进行渲染,但是需要引入编译时

23.Vue组件中data为什么必须是函数

  • 根实例对象data可以是对象也可以是函数“单例”,不会产生数据污染情况
  • 组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染,所以要通过工厂函数返回全新的data作为组件and数据源

24.函数式组件的优势

  • 函数组件的特性:无状态、无生命周期、无this,但是性能高。正常组件是一个类继承了Vue,函数式组件就是普通的函数,没有new的过程。
  • 最终就是将返回的虚拟DOM变成真实DOM替换对应的组件
  • 函数式组件不会被记录在组件的父子关系中,在Vue3中因为所有的组件都不用new了,所以在性能上没有了优势

25.Vue中过滤器和使用场景

过滤器实质不改变原始数据,只是对数据进行加工处理后放回过滤的数据再进行调用处理,可理解诶为纯函数

{{message | filterA("arg1","arg2") | filterB("arg1", "arg2")}}
Vue.filter('filterA', function(value){
    //返回处理后的值
})
Vue.filter('filterB', function(value){
    //返回处理后的值
})

常见场景:单位转换、千分符、文本格式化、时间格式化操作。但可使用方法代替,因此Vue3中废弃

<p>{{format(number)}}</p>
const format = (n) =>{
    return parseFloat(n).toFixed(2)
}

26.v-once的使用场景

v-once概念

v-once是Vue中内置指令,只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

可用在:单个元素、父节点、组件、列表

vue3.2之后,增加了v-memo指令,通过依赖列表的方式控制页面渲染,如果依赖的属性(valueA,valueB)没有改变,则默认渲染缓存的结果,类似于计算属性

<div>
    <div v-memo="[valueA,valueB]">
        <div class="box" v-for="item in arr" :key="item">{{item}}</div>
    </div>
</div>

27.Vue.mixin的使用场景和原理

Vue.mixin概念

mixin可以用来扩展组件,将公共逻辑进行抽离。在需要改逻辑时进行“混入”,采用策略模式针对不同的属性进行合并。如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准

mixin(换入对象)中有很多缺陷:“命名冲突问题”,“数据来源问题”,Vue3中采用组合式API提取公共逻辑非常方便(hook函数)。

混入方式

分为全局混入和局部混入。一般情况下全局混入用于编写插件(如:Vuex,Vue-Router),局部混入用于复用逻辑。

minx合并策略

核心:对象的合并处理

  • props、methods、inject、computed同名时会被替换(组件为主)
  • data会被合并(组件为主)
  • 生命周期和watch方法会被合并成队列
  • components、directives、filters会在原型链上叠加(子找到父的)

组件的扩展除了mixin外还有一个属性叫extends,但不常用

28.Vue中slot如何实现及什么时候使用

插槽概念

插槽设计来源于Web Components规范草案,利用slot进行占位,在使用组件时,组件标签内部内容会分发到对应的slot中

什么时候使用

通过插槽可以让用户更好的对组件进行扩展和定制化。可以通过具名插槽指定渲染的位置,常用的组件例如:弹框组件、布局组件、表格组件、树组件…

插槽的分类和原理

  • 默认插槽
  • 具名插槽
  • 作用域插槽

本质区别:普通插槽,渲染在父级;作用域插槽,渲染在组件内部

29.双向数据绑定的理解和原理

双向数据绑定的概念

Vue中双向数据绑定靠的是执行v-model,可以绑定一个动态值到视图上,同时修改视图能改变数据对应的值(能修改的视图就是表单组件)经常会听到一句话:v-model是value+input的语法糖

表单元素中的v-model

内部会根据标签的不同解析出不同的语法。并且有“额外”的处理逻辑

  • 例如:文本框会被解析为value+input事件
  • 例如:复选框会被解析为checked+change事件

组件中的v-model

组件上的v-model默认会利用名为value的prop和名为input的事件。对于组件而言v-model就是value+input的语法糖。可用于组件中数据的双向绑定。

如果组件中有多个数据想做双向数据绑定怎么办?很遗憾在Vue2中不支持使用多个v-model(可使用.sync语法),Vue3中可以通过以下方式进行绑定

<my v-model:a="a" v-model:b="b" v-model:c="c"></my>

30.Vue中.sync修饰符的作用

在有些情况下,需要对一个prop进行“双向绑定”,这时可以使用.sync来实现。v-model默认只能双向绑定一个属性,可用过.sync修饰符绑定多个属性。

<my :a.sync="a" :b.sync="b"></my>

Vue3中.sync语法已被移除

31.Vue中递归组件理解

在Vue中,递归组件是指组件内部包含自身的一种组件结构。递归组件的概念和使用场景常常涉及到树形结构、无限级列表等需要动态生成和展示层次结构的情况。

递归组件的实现方式如下:

  1. 在组件定义中,通过 name 属性给组件命名,以便在组件内部引用自身。
<template>
  <div>
    <!-- 组件内容 -->
    <my-component></my-component>
  </div>
</template>
<script>
export default {
  name: 'MyComponent'
}
</script>
  1. 在组件的模板中,可以使用组件的名称作为自定义组件的标签。
    在递归组件中,通常会使用递归地引用自身的方式来生成层次结构。通过在组件模板中嵌套自身的组件标签,可以实现递归的效果。
<template>
  <div>
    <!-- 组件内容 -->
    <my-component></my-component>
  </div>
</template>

在上述示例中,my-component 组件被嵌套在自身的模板中,从而形成递归结构。

递归组件的应用场景包括:

  • 树形结构:递归组件可以用于展示树形结构的数据,每个节点都使用相同的组件来表示。通过递归组件,可以方便地处理不同层级的节点。

  • 无限级列表:递归组件也适用于展示无限级列表,例如导航菜单、评论列表等。每个列表项都可以使用相同的组件来表示,通过递归组件可以处理不同层级的列表项。

需要注意的是,在使用递归组件时,需要谨防无限循环的情况。确保递归调用的条件是能够终止的,例如基于数据的条件判断来控制递归的停止。

总结而言,递归组件是指组件内部包含自身的一种组件结构。通过在组件模板中嵌套自身的组件标签,可以实现递归的效果。递归组件适用于树形结构、无限级列表等需要动态生成和展示层次结构的情况。

32.组件中写name选项好处及作用

  • 增加name选项会在components属性中增加组件本身,实现组价的递归调用
  • 可以标识组件的具体名称方便调试和查找对应组件
  • 组件的缓存、组件的全局注册
  • $childred.filter(item=>item.$options.name === ‘xxx’)
Sub.options.components[name] = Sub //子组件会通过name属性,将自己也注册到组件中

33.Vue常用的修饰符及应用场景

  • 表单修饰符:lazy、trim、number
  • 事件修饰符:stop、prevent、self、once、capture、passive、native
  • 鼠标按键修饰符:left、right、middle
  • 键值修饰符对keyCode处理
  • .sync修饰符

34.Vue中异步组件的作用及原理

异步组件概念

Vue允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。推荐做法是将异步组件和webpack的code-splitting功能一起配合使用。

主要用于大组件的拆分,在路由中也是经常使用的

异步组件的写法

  • 回调写法
{
   components:{
       "my-component":(resolve,reject)=>{
           setTimeout(function(){
               resolve({
                   render(h){
                       return h('div','hello')
                   }
               })
           },1000)
        }
    }
}
  • Promise写法
{
  components:{
       "my-component":()=>import("./components/test.vue")
   }
}
  • 对象写法
const AsyncComponent = () =>({
    // 需要加载的组件(应该是一个promise对象)
    component:import("./MyComponent.vue"),
    // 异步组件加载时使用的组件
    loading:LoadingComponent,
    // 加载失败时使用的组件
    error:ErrorComponent,
    // 展示加载时组件的延时时间,默认是200毫秒
    delay:200,
    // 如果提供了超时时间且组件加载也超时了,则使用加载失败时使用的组件,默认是:‘Infinity’
    timeout:3000,
})

异步组件原理

  • 默认渲染异步占位符节点
  • 组件加载完毕后调用$forceUpdate强制更新,渲染加载完毕后的组件

35.nextTick的作用和原理

nextTick概念

  • Vue中视图更新是异步的,使用nextTick方法可以保证用户定义的逻辑在更新后执行
  • 可用于获取更新后的DOM。多次调用nextTick会被合并
  • nextTick本身不是异步的方法
<template>
    <div id="counter">{{count}}</div>
</template>

<script>
    export default {
        name:'App',
        components:{},
        data(){
            return {
                count:0
            }
        }
        mounted(){
            // [flush刷新页面,用户定义]
            this.count = 100
            this.$nextTick(()=>{
                console.log(document.getElementById("counter").innerHTML)
            })
        }
    }
</script>

nextTick原理

作用是底层就是一个批处理,把用户定义和内置的flush刷新都放到一个数组里,依次去执行。上述代码中先运行了内置的flush刷新页面,因此可以获取到更新后的数值,若把this.$nextTick( )放到上面,则其值为0

36.keep-alive平时在哪里使用

keep-alive概念

keep-alive是Vue中内置组件,能在组件切换过程中缓存组件的实例,而不是销毁它们。
在组件再次更新激活时可通过缓存的实例,拿到之前渲染的DOM进行渲染,无需重新生成节点。

使用场景

  • 动态组件可以采用keep-alive进行缓存
<keep-alive :include="whiteList" :exclude="blackList" :max="count">
    <component :is="component"></component>
</keep-alive>
  • 在路由中使用keep-alive
<keep-alive :include="whiteList" :exclude="blackList" :max="count">
    <router-view></router-view>
</keep-alive>

也可以通过meta属性指定那些页面需要缓存,哪些不需要

<div id="app">
    <keep-alive>
        <!-- 需要缓存的视图组件 -->
        <router-view v-if="Sroute.meta.keepAlive"></router-view>
    </keep-alive>
        <!-- 不需要缓存的视图组件 -->
        <router-view v-if="!Sroute.meta.keepAlive"></router-view> 
</div>

keep-alive原理

  • 超过了最大max数量,会保存最近要缓存的组件实例,移除最初缓存的组件
  • 最近最久未使用算法LRU
  • 定义一个缓存映射对象和缓存key,缓存了就不会再初始化了

keep-alive中数据更新问题

由于缓存不再初始化了,因此一些created、mounted、updated钩子不再触发,组件数据发生变化了,也不会更新,而是采用上次缓存的结果。有以下两种解决方案:

  • beforeRouteEnter:在有Vue-Router的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){ 
    next (vm=>{
        vm.getData() // 获取数据
    })
}
  • activated:在keep-alive缓存的组件被激活的时候,都会执行activated钩子
activated(){
	this.getData() //获取数据
	}

37.自定义指令的应用场景

指令的概念

Vue中除了内置指令之外,同时Vue也允许用户注册自定义指令来对Vue进行扩展。指令的目的在于可以将操作DOM的逻辑进行复用

指令的生命周期

  • bind:只调用一次,指令第一次绑定到元素时调用,可以进行一次性的初始化设置
  • inserted:被绑定的元素插入父节点时调用(保证父节点存在,但不一定已被插入文档中,因为组件渲染是父–>子–>孙)
  • update:所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前,指令的值可以发生改变了,也可能没有
  • componentUpdated:指令所在组件的VNnode及其子VNode全部更新后调用
  • unbind:只调用一次,指令与元素解绑时调用

常见的指令

  • 图片懒加载:v-lazy(操作DOM,图片的src)
  • 防抖:v-debounce
  • 按钮权限:v-has
  • 拖拽指令:v-draggable、v-mousemove等
  • 点击事件处理:v-click-outside

38.Vue中使用了那些设计模式

  • 单例模式:整个程序有且仅有一个实例,如:Vuex中的store
  • 工厂模式:传入参数即可创建实例,如:createElement方法
  • 发布订阅模式:订阅者把自己想订阅的事件注册到调度中心,当该事件触发的时候,发布者发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码,如:事件绑定和事件触发、EventBus
  • 观察者模式:watcher & dep的关系,自动不用手动触发
  • 代理模式:给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用
  • 装饰模式:Vue2中装饰器的用法,对功能进行增强@
  • 中介模式:一个行为设计,通过提供一个统一的接口让系统的不同部分进行通信,如Vuex
  • 策略模式:对象有某个行为,但是在不同的场景中,该行为有不同的实现方案
  • 外观模式:提供了统一的接口,用来访问子系统中的一群接口

39.Vue中的性能优化

  • 数据层级不易过深,合理设置响应式数据
  • 通过Object.freeze( )方法冻结属性
  • 使用数据时缓存值的结果,不频繁取值
  • 合理设置key属性
  • v-show和v-if的选取
  • 控制组件粒度 --> Vue采用组件级更新
  • 采用异步组件 --> 借助webpack分包的能力
  • 采用函数式组件 --> 函数式组件开销低
  • 使用keep-alive缓存组件
  • 使用v-once
  • 分页、虚拟滚动、时间分片等策略

40.SPA首屏加载速度慢问题

  • 使用路由懒加载、异步组件、组件拆分、减少入口文件体积大小
  • 抽离公共代码,采用splitChunks进行代码分割
  • 组件加载采用按需加载的方式
  • 静态资源缓存,采用HTTP缓存(强缓存、对比缓存),使用localStorage
  • 图片资源的压缩,雪碧图,对小图进行base64编码减少http请求
  • 打包时开启gzip压缩处理,webpack的compression-webpack-plugin插件
  • 静态资源采用CDN提速,终极的手段
  • 使用SSR对首屏做服务端渲染

41.项目中跨域问题解决

跨域是浏览器同源策略导致的,这个是浏览器的行为(协议、域名、端口号)。
服务端和服务端之间进行通信是没有跨域问题的。
跨域的实现方案有很多种,以下是常用的解决跨域问题方法:

  • CORS(Cross-Origin Resource Sharing,跨域资源共享)由服务端设置,允许指定的客户端访问服务器
  • 构建工具中设置反向代理、使用Nginx反向代理
  • 使用webSocket进行通信
  • 搭建BFF(Backend For Frontend )层解决跨域问题

42.Vue项目中Axios的封装

  • 设置请求超时时间
  • 根据项目环境设置请求路径
  • 设置请求拦截器,自动添加token
  • 设置响应拦截器,对响应的状态码或数据进行格式化
  • 增添请求队列,实现loading效果
  • 维护取消请求token,在页面切换时通过导航守卫取消上个页面中正在发送的请求

43.Vue权限管理怎么做

  • 登录权限:用户登录后返回token,前端将token保存到本地,作为用户登录的凭证,每次发送请求时会携带token,后端会对token进行验证。当页面刷新时,可以使用token来获取用户权限。
  • 访问权限:根据用户是否登录判断能否访问某个页面,通过路由守卫判断用户是否有此权限
  • 页面权限:前端配置的路由分为两部分:“通用路由配置”和“需要权限的路由配置”。在权限路由中增加访问权限(使用meta)。用户登录后可得到对应的权限列表,通过权限列表筛查出对应符合的路由信息,最后通过addRouters方法,动态添加路由。
  • 按钮权限:按钮权限一般采用自定义指令实现,当用户登录时后端会放回对应的按钮权限,在按钮上使用此指令,指令内部会判断用户是否有此按钮权限,如果没有则会移除按钮。

44.Vue-Router中钩子及流程

  • 导航被触发
  • 在失活的组件里调用beforeRouteLeave守卫
  • 调用全局的beforeEach守卫
  • 在重用的组件里调用beforeRouteUpdate守卫(2.2+)
  • 在路由配置里调用beforeEnter
  • 解析异步路由组件
  • 在被激活的组件里调用beforeRouteEnter
  • 调用全局的beforeResolve守卫(2.5+)
  • 导航被确认
  • 调用全局的afterEach钩子
  • 触发DOM更新
  • 调用beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入

45.Vue-Router几种模式的区别

  • Vue-Router有三种模式:hash、history、abstract
  • abstract模式是在不支持浏览器API环境使用,不依赖于浏览器历史记录
  • hash模式兼容性好但不够美观,hash服务端无法获取,不利于SEO优化。hash+popState/hashChange
  • history模式美观且支持服务端渲染,但刷新会出现404问题,可使用插件CLI webpack history-fallback。historyApi+popState

46.Vue项目本地开发完部署到服务器报404问题

history模式刷新时会向服务器发起请求,服务端若无法响应对应的资源,则出现404问题
解决:当请求对应的地址不存在时,服务端返回首页

47.谈一谈对Vuex的理解

Vuex概念

  • Vuex是一个专门为Vue.js应用程序开发的状态管理模式。采用集中式存储管理应用的所有组件状态。核心就是解决数据的共享问题。
  • 以响应的规则保证状态以一种可预测的方式发生变化。

状态修改

  • 组件中commit( ) --> mutation --> 修改状态
  • 组件中dispatch( ) --> action --> commit( ) --> mutation --> 修改状态

缺点

Vue中store只有一份,复杂的数据需要依赖于模块。Vuex状态是一个树形结构,最终会将模块的状态挂载到跟模块上。

  • 模块和状态的名字冲突
  • 数据不够扁平化、调用的时候过长
  • 更改状态mutation和action的选取
  • 模块需要增加namespaced
  • 对TS支持并不友好

原理

  • 对于Vuex3核心就是通过new Vue( )创建了一个Vue实例,进行数据共享
  • 对于Vuex4核心就是通过一个响应式对象进行数据共享,reactive( )

48.如何监听Vuex中数据的变化

  • 通过watch监控Vuex中状态的变化
  • 通过store.subscribe监控状态变化

49.页面刷新后Vuex的数据丢失怎么解决

  • 每次获取数据前检测Vuex数据是否存在,不存在则发情求重新拉取数据,存储到Vuex中
  • 采用Vuex持久化插件,将数据存储到localStorage或sessionStorage中

50.mutation和action的区别

  • 在action中可以处理异步逻辑,可以获取数据后将结果提交给mutation,而mutation则是修改state
  • 在action中可以多次进行commit操作,包括action中也可以调用action
  • 在非mutation中修改数据,在严格模式下会发生异常
  • dispatch时会将action包装成promise,而mutation则没进行包装

51.Vuex中的module在什么情况下使用

使用单一状态时,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。
Vuex允许将store分割成模块(module)。每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块。

const moduleA = {
    state: () =>  ({ ...  }),
    mutations: {...  },
    actions: {...  },
    getters: {... }
}
const moduleB = {
    state: () =>  ({ ...  }),
    mutations: { ... },
    actions: {...  }
}
const store = createStore({
    modules: {
        a: modulA,
        b: moduleB
    }
})

store.state.a // moduleA 的状态
store.state.b // moduleB 的状态

52.Vue3中CompositionAPI的优势

  • 在Vue2中采用的是OptionsAPI,需要用户自己提供data,props,methods,computed,watch等属性,用户编写复杂业务逻辑会出现反复横跳的问题,上下滚动查看
  • Vue2中所有的属性都是通过this访问,this存在指向不明确的问题
  • Vue2中很多未使用方法或属性依旧会被打包,并且所有全局API都在Vue对象上公开。而组合式API对tree-shaking更加友好,代码也更容易压缩,用到才会导入,没有则不导入
  • 组件逻辑共享问题,Vue2中采用mixins实现组件之间的逻辑共享,但是会有数据来源不明确,命名冲突的问题。Vue3中采用组合式API提取公共逻辑非常方便
  • 简单组件仍然可以采用选项是API进行编写,组合式API在复杂的逻辑中有着明显的优势

53.Vue3和Vue2的区别(重点)

  • Vue3更注重模块上的拆分,在Vue2中无法单独使用部分模块。需要引入完整的Vue.js,而Vue3中模块之间耦合度低,模块可以独立使用
  • Vue2中很多方法挂载得到了实例中导致没有使用也会被打包(还有很多组件也是)。通过构建工具tree-shaking机制实现按需引入,减少用户打包后体积
  • 在Vue3中允许自定义渲染器,扩展能力强。不会发生以前的事情,改写Vue源码改造渲染方式。扩展更方便
  • 在Vue2使用defineProperty来进行数据的劫持,需要对属性进行重写添加getter和setter,性能差
  • 当新增属性和删除属性时无法监控变化。需要通过$set、$delete
  • 数组不采用defineProperty来进行劫持(浪费性能,对所有索引进行劫持造成性能浪费)需要对数组单独进行处理
  • diff算法进行了优化,同序列比对和最长递增子序列
  • Vue3中模板编译优化,采用PatchFlags优化动态节点,采用BlockTree进行靶向更新
  • 相比Vue2来说Vue3新增了很多新的特性
  • 最重要的变化是组合式API

54.Vue项目中的错误如何处理

errorCaptured钩子

可以捕获来自后代组件的错误,如果全局的config.errorHandler被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报
父组件(errorCaptured) --> 子组件(errorCaptured) --> 孙子组件出错时,错误会一直向上抛。如果errorCaptured中返回false则会阻断传播

全局设置错误处理

如果在组件渲染时出现运行错误,错误将会被传递至全局Vue.config.errorHandler配置函数

Vue.config.errorHandler = (err, vm, info) =>{
console.log(err, vm, info)
}

接口异常处理

instance.interceptors.response.use(
	(res)=>{
	return res.data
	}
	(err) => {
		let res = err.response
		if(res.status >= 400){
			handleError(response) //统一处理接口异常
		}
		return Promise.reject(error)
	}	
)

55.Vue3中模板编译优化

PatchFlags优化

diff算法无法避免新旧虚拟DOM中无用的比较操作,如:静态节点,通过patchFlags来标记动态内容,可以实现快速diff算法
此时生成的虚拟节点多出一个dynamicChildren属性,这个就是block的作用,block可以收集所有后代动态节点。这样后续更新时可以直接跳过静态节点,实现靶向更新。节点个数要一样,如v-for,之前数组为3,之后为5个,则使用diff全量更新

BlockTree

为什么要提出blockTree的概念?问题出在block在收集动态节点时是忽略虚拟DOM树层级的

<div>
    <p v-if="flag">
        <span>{{a}}</span>
    </p>
    <div v-else>
        <span>{{a}}</span>
    </div>
</div>

这时候切换flag的状态将无法从p标签切换到div标签
解决方案:就是将不稳定的结构也当作block来进行处理
所谓不稳定结构就是DOM树的结构可能会发生变化,如:v-if、v-for、Fragment

静态提升

静态提升的节点都是静态的,可以将提升出来的节点字符化。当连续静态节点超过20时,会将静态节点序列化为字符串

缓存函数

开启函数缓存后,函数会被缓存起来,后续可以直接使用

56.Vue3中新特性

  • Composition API
  • SFC Composition API Syntax Sugar(<script setup>)
  • Teleport
  • Fragments
  • Emits Component Option
  • createRenderer API from @vue/runtime-core to create custom renderers
  • SFC State-driven CSS Variables(v-bind in <style>)
  • SFC <\style scoped> can now include global rules or rules that target only slotted content
  • Suspense experimental


在这里插入图片描述

  • 4
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值