该内容主要整理关于 Vue 的相关面试题,其他内容面试题请移步至 最新最全的前端面试题集锦 查看。
关于 Vue3.0 的相关面试题,请移步至 Vue3.0 篇 查看。

目录


一、Vue经典面试题(附答案)

1. 什么是 MVVM

MVVMModel-View-ViewModel 的缩写。MVVM 是一种设计思想。Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成 UI 展现出来;ViewModel 是一个同步 ViewModel 的对象。

MVVM 架构下,ViewModel 之间并没有直接的联系,而是通过ViewModel 进行交互,ModelViewModel 之间的交互是双向的, 因此View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到View 上。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接起来,而 ViewModel 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

2. MVVMMVC 区别?它和其它框架(jQuery)的区别是什么?哪些场景适用?

MVCMVVM 其实区别并不大,都是一种设计思想。主要就是 MVCController 演变成 MVVM 中的 viewModelMVVM 主要解决了 MVC 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。

和其它框架区别:Vue 数据驱动,通过数据来显示视图层而不是节点操作。

场景:数据操作比较多、频繁的场景,更加便捷。

3. Vue 的优点是什么?

  • 低耦合:视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
  • 可重用性:可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。
  • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel);设计人员可以专注于页面设计。
  • 可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel来写。

4. Vue 组件之间的传值?

详细文档:Vue 之 父子组件通信与事件触发

  • 父 => 子 通信
    • 父组件通过在子组件标签上绑定 :data = data 方式定义传值
    • 子组件通过 props 方法接受数据
  • 子 => 父 通信
    • 子组件通过 this.$emit 方法传递参数

5. Vue 路由之间跳转?

详细文档:Vue 之 路由跳转传参方式详解

  1. 声明式路由导航(<router-link>

  2. 编程式路由导航(js的方式)

    this.$router.push():跳转到指定 url 路径,并向 history 栈中添加一个记录,点击后退会返回到上一个页面 ==>> 队列的方式(先进先出)

    this.$router.replace():跳转到指定 url 路径,但是 history 栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面) ==>> 栈的方式(先进后出)

    this.$router.back():请求(返回)上一个记录路由

    this.$router.go(n):向前或者向后跳转n个页面,n可为正整数或负整数

6. vue.cli 中怎样使用自定义组件?遇到过哪些问题?

  1. components 目录新建组件文件(indexPage.vue
  2. 在需要用的页面(组件)中导入该组件:import indexPage from 'src/components/indexPage.vue'
  3. 注入到vue子组件的 components 属性上面,components:{ indexPage }
  4. template 视图 view 中使用该组件。

7. Vue 如何实现按需加载配合 webpack 设置?

webpack 中提供了 require.ensure() 来实现按需加载。

以前引入路由是通过 import 这样的方式引入,现在改为 const 定义的方式进行引入。

  • 页面不按需加载引入方式:
    import home from '../../common/home.vue'
    
  • 页面按需加载引入方式:
    const home = r => require.ensure( [], () => r (require('../../common/home.vue')))
    

8. keep-alive 组件有什么作用?

作用:包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态避免重新渲染

keep-alive 是 vue 的内置组件,而这个组件的作用就是能够缓存不活动的组件。一般情况下,组件进行切换的时候,默认是会进行销毁的,如果我们有需求,在某个组件切换后不进行销毁,而是保存之前的状态,那么就可以利用 keep-alive 来实现。

keep-alive 上有两个属性,可以对字符串或正则表达式进行匹配,匹配到的组件会被缓存。

  • include 值为字符串或者正则表达式匹配的组件 name 会被缓存。(缓存匹配到的组件)
  • exclude 值为字符串或正则表达式匹配的组件 name 不会被缓存。(排除匹配到的组件)

其拥有两个独立的生命周期钩子函数 activeddeactived,使用 keep-alive 包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactived 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

9. 说下 Vue 生命周期钩子函数?

每个vue实例在被创建时都要经过一系列的初始化过程。
所有的生命周期钩子自动绑定 this 上下文到实例中,因此可以在函数中访问数据,对属性和方法进行运算。这意味着不能使用箭头函数来定义一个生命周期方法【这是因为箭头函数绑定了父上下文,因此this与你期待的 Vue 实例不同】。

vue的生命周期图:
vue的生命周期图

阶段一:Vue实例创建阶段

  • beforeCreate
    Vue实例在内存中刚被创建,this 变量还不能使用,数据对象(data)和方法(methods)未初始化,watcher 中的事件都不能获得到;

  • created
    实例已经在内存中创建好,数据和方法已经初始化完成,但是模板还未编译,页面还是没有内容,还不能对 dom 节点进行操作(此时访问 this.$elthis.$refs.xxx 都是 undefined

  • beforeMounte
    找到对应的 template 模板,编译成 render 函数,转换成虚拟 dom,此时模板已经编译完成,数据未挂载到页面,也就是说在这个阶段你可以看到标签间的双花括号,数据还未渲染到页面中;

  • render : h=>h(App)
    beforeMounte 之后和 mounted 之前,还有渲染 render 函数,它的作用是把模板渲染成虚拟 dom。

  • mounted
    模板编译好了,虚拟 dom 渲染成真正的 dom 标签,数据渲染到页面,此时Vue实例已经创建完毕,如果没有其他操作的话,Vue实例会静静的躺在内存中,一动不动。
    一般会在 mounted 中来渲染从后端获取的数据。(页面初始化时,如果有操作 dom 的事件一般也会放在 mounted 钩子函数中。当然,也可以放在 create 中,前提需使用 this.$nextTick(function(){}),在回调函数中操作dom。)

阶段二:Vue实例运行阶段

  • beforeUpdate
    数据依赖改变或者用 $forceUpdata 强制刷新时,对象 data 中的数据已经更改(虚拟 dom 已经重新渲染),但是 页面中的值还是原来,未改变,因为此时还未开始渲染 dom

  • update
    此时 data 中的数据和页面更新完毕,页面已经被重新渲染。

在实际开发中,一般会用监听器 watch 来代替上边2个方法,因为 watch 会知道是哪一个数据变化。

阶段三:Vue实例销毁阶段

  • beforeDestroy
    实例销毁前使用,在此刻实例还是可用的。

  • destroyed
    Vue实例被销毁,观察者、子组件、事件监听被清除(页面数据不会消失,只不过是响应式无效了)。

10. v-showv-if 指令的共同点和不同点?

  • 共同点:
    • 都是对元素进行显示隐藏;
  • 不同点:
    • v-show 指令是通过修改元素的 display 的 CSS属性让其显示或者隐藏;
    • v-if 指令是直接销毁和重建 DOM 达到让元素显示和隐藏的效果;
    • 使用 v-show 会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if 更加合理。

11. 如何让 CSS 只在当前组件中起作用?

将当前组件的 <style> 修改为 <style scoped>,添加 scoped 属性。

12. Vue 中引入组件的步骤?

  1. 采用 ES6 的 import ... from ... 语法或 CommonJS 的 require() 方法引入组件;

  2. 对组件进行注册 components:{ my-component }

  3. 使用组件 <my-component> </my-component>

13. 指令 v-el 的作用是什么?

提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例。

14. 在 Vue 中使用插件的步骤?

  1. 采用 ES6 的 import ... from ... 语法或 CommonJS 的 require() 方法引入插件。
  2. 使用全局方法 Vue.use( plugin ) 使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })

15. active-class 是哪个组件的属性?

active-classvue-router 模块的 router-link 组件中的属性,用来做选中样式的切换;

16. computedwatch 的区别?

相同点:

  • 计算属性 computed 和监听器 watch 都可以观察属性的变化从而做出响应

不同点:

  • 计算属性 computed 更多是作为缓存功能的观察者,它可以将一个或者多个 data 的属性进行复杂的计算生成一个新的值,提供给渲染函数使用,当依赖的属性变化时,computed 不会立即重新计算生成新的值,而是先标记为当前数据,当下次 computed 被获取时候,才会进行重新计算并返回。也就是说:computed 只有当依赖的数据变化时才会计算,会缓存数据。

  • 监听器 watch 并不具备缓存性,监听器 watch 提供一个监听函数,当监听的属性发生变化时,会立即执行该函数。watch 更适用于数据变化时的异步操作。

17. 为什么组件的 data 必须是一个函数?

一个组件可能在很多地方使用,也就是会创建很多个实例,如果 data 是一个对象的话,对象是引用类型,一个实例修改了 data 会影响到其他实例,所以 data 必须使用函数,为每一个实例创建一个属于自己的 data,使其同一个组件的不同实例互不影响。


二、Vuex 基础面试题(附答案)

1. Vuex 是什么?怎么使用?哪种功能场景使用它?

Vuex 官网的解释是:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

使用:

  1. 安装 vuex 依赖
  2. 项目目录 src 下新建 store 目录和 store.js 文件
  3. main.js 中引用 vueximport store from './store/store.js';(就可以在组件使用 this.$store 来调用方法)
  4. 在组件中使用

场景:单页应用中,组件之间的状态,音乐播放、登录状态、加入购物车等。

2. Vuex 有哪几种属性?作用?

Vuex 属性有五种,分别是 StateGetterMutationActionModule

  • State

    Vuex 就是一个仓库,仓库里面放了很多对象。其中 state 就是数据源存放地,对应于一般 Vue 对象里面的 data

    state 里面存放的数据是响应式的,Vue 组件从 store 中读取数据,若是store 中的数据发生改变,依赖这个数据的组件也会发生更新。

    可以通过 mapState 把全局 stategetters 映射到当前组件的 computed 计算属性中使用。

  • Getter

    getters 可以对 State 进行计算操作,它就是 Store 的计算属性。

    虽然在组件内也可以做计算属性,但是 getters 可以在多组件之间复用,如果一个状态只在一个组件内使用,可以不用 getters

  • Mutation

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

    Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

    Mutation 就是提供存储设置 state 数据的方法。

  • Action

    Action 类似于 mutation

    不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。

  • Module

    当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

    为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块 module ——从上至下进行同样方式的分割。

3. Vuex 内部如何调用?

且看官方的一个示例图:
在这里插入图片描述
从上图可以很好的看出这几个属性之间的调用关系(不过官方图没有画出getters的使用)

  1. 组件 Vue Component 通过 dispatch 来调用 actions 提供的方法;
  2. actions 除了可以和 api 打交道外,还可以通过 commit 来调用 mutations 提供的方法;
  3. 最后 mutaions 将数据保存到 state 中;
  4. 当然,Vue Components 还可以通过 getters 提供的方法获取 state 中的数据。

4. 不使用 Vuex 会带来什么问题?

  1. 可维护性会下降:想修改数据要维护三个地方(state active mution);
  2. 可读性会下降:因为一个组件里的数据,根本就看不出来是从哪来的;
  3. 增加耦合:大量的上传派发,会让耦合性大大增加,Vue 用 Component 本意就是为了减少耦合,现在这么用,和组件化的初衷相背。

三、Vue-Router 基础面试题(附答案)

1. 如何定义 vue-router 动态路由以及如何获取传过来的动态参数?

详细文档:Vue 之 路由跳转传参方式详解

2. vue-router 有哪几种导航钩子?

详细文档:Vue-Router 之 路由导航守卫钩子详解

3. 在 vue-router 中使用 active-class 的方法?在使用过程中碰到过什么问题?

使用 active-class 有两种方法:

  1. 直接在路由js文件中配置 linkActiveClass

    export default new Router({
      linkActiveClass: 'active'
    })
    
  2. <router-link> 中写入 active-class

    <router-link to="/home" class="menu-home" active-class="active">首页</router-link>
    

如果使用第二种方法添加 active-class,跳转到my页面后,两个router-link始终都会有选中样式,代码如下:

<div class="menu-btn">
  <router-link to="/" class="menu-home" active-class="active">
    首页
  </router-link>
</div>
<div class="menu-btn">
  <router-link to="/my" class="menu-my" active-class="active">
    我的
  </router-link>
</div>

原因:可能是因为 to="/" 引起的,active-class 选择样式时根据路由中的路径去匹配,然后显示。例如在my页面中,路由为 localhost:8080/#/my,那么to="/"to="/my" 都可以匹配到,所以都会激活选中样式。

解决方案:要解决问题也有两种方式,都是通过加入一个 exact 属性

  1. 直接在路由js文件中配置 linkExactActiveClass

    export default new Router({
      linkExactActiveClass: 'active',
    })
    
  2. <router-link> 中写入 exact

    <router-link to="/" class="menu-home" active-class="active" exact>首页</router-link>
    
  3. 也可以在路由中加入重定向

    <router-link to="/home" class="menu-home" active-class="active" exact>首页</router-link>
    // 路由js中加入重定向
    {
      path: '/',
      redirect: '/home'
    }
    

四、Vue 高级面试题(附答案)

1. 介绍一下 Vue 的响应式系统?

Vue 为 MVVM 框架,当数据模型 data 变化时,页面视图会得到响应更新,其原理对 datagetter/setter 方法进行拦截(Object.defineProperty 或者Proxy),利用发布订阅的设计模式,在 getter 方法中进行订阅,在 setter 方法中发布通知,让所有订阅者完成响应。

在响应式系统中,Vue 会为数据模型 data 的每一个属性新建一个订阅中心作为发布者,而监听器 watch、计算属性 computed、视图渲染 template/render三个角色同时作为订阅者,对于监听器 watch,会直接订阅观察监听的属性,对于计算属性 computed 和视图渲染 template/render,如果内部执行获取了 data 的某个属性,就会执行该属性的 getter 方法,然后自动完成对该属性的订阅,当属性被修改时,就会执行该属性的 setter 方法,从而完成该属性的发布通知,通知所有订阅者进行更新。

2. 说出至少4种 Vue 当中的指令和它的用法?Vue如何创建自定义指令?

  • v-if:判断是否隐藏;
  • v-for:数据循环遍历;
  • v-bind:class:绑定一个属性;
  • v-model:实现双向绑定;

Vue如何创建自定义指令?

详细文档:Vue进阶 之 自定义指令详解

3. vue-loader 是什么?用途有哪些?

  • vue-loader 是解析 .vue 文件的一个加载器。

  • 用途:js 可以写 es6、style 样式可以 scsslesstemplate 可以加 jade等。

4. scss 是什么?在 vue.cli 中的安装使用步骤是?有哪几大特性?

  • scsscss 的预编译语言。
  • 使用步骤:
    1. 先装 css-loadernode-loadersass-loader 等加载器模块;
    2. build 目录找到 webpack.base.config.js,在 extends 属性中加一个拓展 .scss
    3. 在同一个文件,配置一个 module 属性;
    4. 然后在组件的 style 标签加上 lang 属性 ,例如:lang=”scss”
  • 特性:
    • 可以用变量,例如($变量名称 = 值);
    • 可以用混合器;
    • 可以嵌套;

5. 为什么要使用 key

  • 答案1:当有相同标签名的元素切换时,为避免渲染问题,需要通过 key 特性设置唯一的值,来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。
  • 答案2:key 是给每一个 vnode 的唯一 id,可以依靠 key,更准确,更快的拿到 oldVnode 中对应的 vnode 节点。

6. 为什么避免 v-ifv-for 一起用?

Vue 处理指令时,v-forv-if 具有更高的优先级,通过 v-if 移动到容器元素,不会再重复遍历列表中的每个值。取而代之的是,只检查它一次,且不会在 v-if 为否的时候运算 v-for

7. VNode 是什么?虚拟 DOM 是什么?

VNodeVue 在页面上渲染的节点,及其子节点称为“虚拟节点 ( Virtual Node)”,简写为“VNode”。“虚拟 DOM” 是由 Vue 组件树建立起来的整个 VNode 树的称呼。

8. 动态绑定 Class 有几种方式?

详细文档:Vue进阶 之 动态绑定Class 详解

9. Vue 插槽 slot 是什么?作用?原理?

  • 定义:slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。

  • slot 又分三类:默认插槽,具名插槽和作用域插槽。

    • 默认插槽:又名匿名插槽,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只能有一个匿名插槽。
    • 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件可以出现多个具名插槽。
    • 作用域插槽 slot-scope:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
  • 使用步骤:

    1. 子组件中定义插槽 <slot></slot>
    2. 父组件使用子组件时往插槽写入代码。
  • 实现原理:

    • 当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在vm.$slot 中,默认插槽为 vm.$slot.default,具名插槽为 vm.$slot.xxxxxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 $slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

10. SSR 有了解吗?原理是什么?

  • 服务端渲染 SSR
    在客户端请求服务器的时候,服务器到数据库中获取到相关的数据,并且在服务器内部将 Vue 组件渲染成 HTML,并且将数据、HTML一并返回给客户端,这个在服务器将数据和组件转化为 HTML 的过程,叫做服务端渲染SSR
  • 客户端激活
    而当客户端拿到服务器渲染的 HTML 和数据之后,由于数据已经有了,客户端不需要再一次请求数据,而只需要将数据同步到组件或者 Vuex 内部即可。除了数据以外,HTML 结构也已经有了,客户端在渲染组件的时候,也只需要将 HTML 的 DOM 节点映射到 Virtual DOM 即可,不需要重新创建DOM 节点,这个将数据和 HTML 同步的过程,又叫做客户端激活。
  • 使用 SSR 的利弊:
    • 优点:

      • 有利于 SEO 优化:其实就是有利于爬虫来爬你的页面,因为部分页面爬虫是不支持执行 JavaScript 的,这种不支持执行 JavaScript 的爬虫抓取到的非 SSR 的页面会是一个空的 HTML 页面,而有了 SSR 以后,这些爬虫就可以获取到完整的 HTML 结构的数据,进而收录到搜索引擎中,有利于 SEO 优化。
      • 白屏时间更短:相对于客户端渲染,服务端渲染在浏览器请求 URL之后已经得到了一个带有数据的 HTML 文本,浏览器只需要解析 HTML,直接构建 DOM 树就可以。而客户端渲染,需要先得到一个空的 HTML 页面,这个时候页面已经进入白屏,之后还需要经过加载并执行 JavaScript、请求后端服务器获取数据、JavaScript 渲染页面几个过程才可以看到最后的页面。特别是在复杂应用中,由于需要加载 JavaScript 脚本,越是复杂的应用,需要加载的 JavaScript 脚本就越多、越大,这会导致应用的首屏加载时间非常长,进而降低了体验感。
    • 弊端:

      • 代码复杂度增加:为了实现服务端渲染,应用代码中需要兼容服务端和客户端两种运行情况,而一部分依赖的外部扩展库却只能在客户端运行,需要对其进行特殊处理,才能在服务器渲染应用程序中运行。
      • 需要更多的服务器负载均衡:由于服务器增加了渲染 HTML 的需求,使得原本只需要输出静态资源文件的服务,新增了数据获取的IO和渲染 HTML 的 CPU 占用,如果流量突然暴增,有可能导致服务器 down机,因此需要使用响应的缓存策略和准备相应的服务器负载。
      • 涉及构建设置和部署的更多要求:与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。

      所以在使用服务端渲染 SSR 之前,需要开发者考虑投入产出比,比如大部分应用系统都不需要 SEO,而且首屏时间并没有非常的慢,如果使用 SSR 反而小题大做了。

11. Vue 事件绑定原理说一下?

Vue 中通过 v-on 或其语法糖 @ 指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成 AST(抽象语法树),生成 render 函数后并执行得到VNodeVNode 生成真实 DOM 节点或者组件时候使用 addEventListener 方法进行事件绑定。

12. Vue 模板渲染的原理是什么?

Vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所以需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。

模板编译又分三个阶段,解析parse,优化 optimize,生成 generate,最终生成可执行函数 render

  • 解析parse阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST
  • 优化 optimize阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。
  • 生成 generate阶段:将最终的 AST 转化为 render 函数字符串。

13. template 预编译是什么?

对于 Vue 组件来说,模板编译只会在组件实例化的时候编译一次,生成渲染函数之后在也不会进行编译。因此,编译对组件的 runtime 是一种性能损耗。

而模板编译的目的仅仅是将 template 转化为 render function,这个过程,正好可以在项目构建的过程中完成,这样可以让实际组件在 runtime 时直接跳过模板渲染,进而提升性能,这个在项目构建的编译 template 的过程,就是预编译。

14. templatejsx 的有什么分别?

对于 runtime 来说,只需要保证组件存在 render 函数即可,而我们有了预编译之后,我们只需要保证构建过程中生成 render 函数就可以。

webpack 中,我们使用 vue-loader 编译 .vue 文件,内部依赖的 vue-template-compiler 模块,在 webpack 构建过程中,将 template 预编译成 render 函数。

与 React 类似,在添加了 jsx 的语法糖解析器 babel-plugin-transform-vue-jsx 之后,就可以直接手写 render 函数。

所以,templatejsx 的都是 render 的一种表现形式,不同的是:

JSX 相对于 template 而言,具有更高的灵活性,在复杂的组件中,更具有优势,而 template 虽然显得有些呆滞,但是 template 在代码结构上更符合视图与逻辑分离的习惯,更简单、更直观、更好维护。

15. 说一下什么是 Virtual DOM

Virtual DOMDOM 节点在 JavaScript 中的一种抽象数据结构,之所以需要虚拟DOM,是因为浏览器中操作 DOM 的代价比较昂贵,频繁操作 DOM 会产生性能问题。虚拟 DOM 的作用是在每一次响应式数据发生变化引起页面重渲染时,Vue 对比更新前后的虚拟 DOM,匹配找出尽可能少的需要更新的真实 DOM,从而达到提升性能的目的。

16. 介绍一下 Vue 中的 Diff 算法?

我们使用了 Virtual DOM 来进行真实 DOM 的渲染,在页面更新的时候,也不能全量地将整棵 Virtual DOM 进行渲染,而是去渲染改变的部分,这时候就需要一个计算 Virtual DOM 树改变部分的算法了,这个算法就是 Diff算法。

在对比新老虚拟 DOM 时:

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

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

17. nextTick 实现原理?

参考文章:vue核心面试题(nextTick实现原理)

18. vue 如何实现按需加载?

vue 实现按需加载有三种方式:

  1. vue-router 配置 resolverequire加载 (vue异步组件技术)

    这种方式就是 下一个组件生成一个js文件
    路由配制如下:

    {
        path: '/demo',
        name: 'Demo',
        component: resolve => require(['../components/Demo'], resolve)
    }
    
  2. es6 提案的 import() 方法

    官方文档 :路由懒加载

    // 下面2行代码,没有指定 webpackChunkName,每个组件打包成一个js文件。
    const ImportFuncDemo1 = () => import('../components/ImportFuncDemo1')
    const ImportFuncDemo2 = () => import('../components/ImportFuncDemo2')
    
    // 下面2行代码,指定了相同的 webpackChunkName,会合并打包成一个js文件。
    const ImportFuncDemo1 = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo1')
    const ImportFuncDemo2 = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo2')
    
    export default new Router({
        routes: [
            {
                path: '/importfuncdemo1',
                name: 'ImportFuncDemo1',
                component: ImportFuncDemo1
            },
            {
                path: '/importfuncdemo2',
                name: 'ImportFuncDemo2',
                component: ImportFuncDemo2
            }
        ]
    })
    
  3. webpack 提供的 resolve + require.ensure()

    vue-router 配置路由,使用 webpackrequire.ensure 技术,也可以实现按需加载。

    这种情况下,多个路由指定相同的 chunkName,会合并打包成一个js文件。

    • 常规不按需加载的引入方式:
      import home from '../../common/home.vue'
    • require.ensure() 引入方式:
      {
          path: '/promisedemo',
          name: 'PromiseDemo',
          component: resolve => require.ensure([], () => resolve(require('../components/PromiseDemo')), 'demo')
      },
      {
          path: '/hello',
          name: 'Hello',
          component: resolve => require.ensure([], () => resolve(require('../components/Hello')), 'demo')
      }
      // 这两个路由最终会打包成一个demo.js文件
      

五、说说 Vue2.0 和 Vue3.0 有什么区别?

关于 Vue 3.0 的更多面试题,我也进行了整理,请移步至:面试题 Vue3.0 篇

a. 重构响应式系统,使用 Proxy 替换 Object.defineProperty,使用 Proxy 优势:

  • 可直接监听数组类型的数据变化;
  • 监听的目标为对象本身,不需要像 Object.defineProperty 一样遍历每个属性,有一定的性能提升;
  • 可拦截 applyownKeyshas 等13种方法,而 Object.defineProperty 不行;
  • 可以直接实现对象属性的新增/删除。

b. 新增 Composition API,更好的逻辑复用和代码组织。

c. 重构 Virtual DOM

  • 模板编译时的优化,将一些静态节点编译成常量;
  • slot 优化,将 slot 编译为 lazy 函数,将 slot 的渲染的决定权交给子组件;
  • 模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)。

d. 代码结构调整,更便于 Tree shaking,使得体积更小。

e. 使用 Typescript 替换 Flow


持续更新中。。。欢迎大家关注

12个vue高频原理面试题(附分析)

参考文章:
1、No Silver Bullet:https://blog.csdn.net/sunhuaqiang1/article/details/89450535
2、亲爱的阿乾:https://segmentfault.com/a/1190000038848131
感谢原作者辛苦付出

Logo

前往低代码交流专区

更多推荐