本文介绍: 参考答案bind: 只调用一次指令第一次绑定元素时调用,用这个钩子函数可以定义一个绑定执行一次的初始化动作inserted: 被绑定元素插入节点时调用(父节点存在即可调用,不必存在document 中)。update: 被绑定元素所在的模板更新时调用,而不论绑定是否变化。通过比较更新前后绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。

1. 谈一谈对 MVVM理解

参考答案

2. 说一下 Vue 的优点

参考答案

Vue一个构建数据驱动Web 界面的渐进式框架

Vue目标通过可能简单API 实现响应的数据绑定和组合视图组件核心一个响应的数据绑定系统

关于 Vue 的优点,主要有响应编程组件开发虚拟 DOM

响应编程

这里响应式不是 @media 媒体查询中的响应式布局,而是指 Vue自动页面中某些数据的变化做出响应。这也就是 Vue 最大的优点,通过 MVVM 思想实现数据的双向绑定,让开发者不用再操作 DOM 对象,有更多的时间思考业务逻辑

组件开发

Vue 通过组件,把一个单页应用中的各种模块拆分一个一个单独的组件component)中,我们只要先在父级应用中写好各种组件标签(占坑),并且在组件标签中写好要传入组件的参数(就像给函数传入参数一样,这个参数叫做组件的属性),然后再分别写好各种组件的实现(填坑),然后整个应用就算做完了。

组件化开发的优点:提高开发效率、方便重复使用简化调试步骤提升整个项目可维护性、便于协同开发。

虚拟 DOM

传统开发中,用 JQuery 或者原生JavaScript DOM 操作函数DOM 进行频繁操作时候浏览器要不停的渲染新的 DOM 树,导致在性能上面的开销特别的高。

Virtual DOM 则是虚拟 DOM英文简单来说,他就是一种可以预先通过 JavaScript 进行各种计算,把最终的 DOM 操作计算出来并优化,由于这个 DOM 操作属于预处理操作,并没有真实的操作 DOM,所以叫做虚拟 DOM最后计算完毕才真正将 DOM 操作提交,将 DOM 操作变化反映到 DOM 树上。

3. 解释一下对 Vue 生命周期理解

参考答案

什么vue 生命周期

对于 vue 来讲,生命周期就是一个 vue 实例创建销毁过程

vue 生命周期作用什么

生命周期的过程中会运行着一些叫做生命周期函数,给予了开发者不同的生命周期阶段添加业务代码能力

其实回调是一个概念,当系统执行到某处时,检查是否hook(钩子),有的话就会执行回调。

通俗的说,hook 就是在程序运行中,在某个特定的位置框架开发者设计好了一个钩子来告诉我们当前程序已经运行到特定的位置了,会触发一个回调函数,并提供给我们,让我们可以在生命周期的特定阶段进行相关业务代码的编写

vue 生命周期有几个阶段

可以总共分为 8阶段创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

第一次页面加载会触发哪几个钩子

会触发 4钩子,分别是:beforeCreate、created、beforeMount、mounted

DOM 渲染在哪个周期就已经完成

DOM 渲染是在 mounted 阶段完成,此阶段真实的 DOM 挂载完毕,数据完成双向绑定,可以访问DOM 节点

多组件(父子组件)中生命周期的调用顺序说一下

组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。组件的销毁操作是先父后子,销毁完成的顺序是先子后父。

4. Vue 实现双向数据绑定原理什么

参考答案:

Vue2.x 采用数据劫持结合发布订阅模式PubSub 模式)的方式,通过 Object.defineProperty 来劫持各个属性setter、getter,在数据变动发布消息订阅者,触发相应的监听回调。

当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性访问修改通知变化。

Vue 的数据双向绑定整合ObserverCompileWatcher 三者,通过 Observer监听自己model 的数据变化,通过 Compile解析编译模板指令,最终利用 Watcher 搭起 ObserverCompile 之间通信桥梁,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据 model 变更的双向绑定效果

Vue3.x 放弃了 Object.defineProperty ,使用 ES6 原生Proxy,来解决以前使用 Object.defineProperty存在的一些问题

5. 说一下对 Vue2.x 响应式原理理解

参考答案:

Vue初始化数据时,会使用 Object.defineProperty 重新定义 data 中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的 watcher),如果属性发生变化会通知相关依赖进行更新操作(发布订阅)。

(可以参阅前面4 题答案)

6. 说一下在 Vue2.x如何检测数组的变化?

参考答案:

Vue2.x 中实现检测数组变化的方法,是数组的常用方法进行了重写Vuedata 中的数组进行了原型重写指向自己定义数组原型方法。这样当调用数组 api 时,可以通知依赖更新。如果数组包含引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。

流程:

  1. 初始化传入 data 数据执行 initData
  2. 将数据进行观测 new Observer
  3. 将数组原型方法指向重写原型
  4. 深度观察数组中的引用类型

有两种情况无法检测到数组的变化。

不过这两种场景都有对应解决方案

利用索引设置数组项的替代方案

//使用该方法进行更新视图
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)

修改数组的长度的替代方案

//使用该方法进行更新视图
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

7. Vue3.x 响应式数据

参考答案:

Vue3.x 响应式数据原理什么

Vue 2 中,响应式原理就是使用的 Object.defineProperty 来实现的。但是在 Vue 3.0采用Proxy,抛弃了 Object.defineProperty 方法。

究其原因,主要是以下几点:

Proxy 只会代理对象的第一层,那么 Vue3 又是怎样处理这个问题的呢?

判断当前 Reflect.get返回值是否Object,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次 get/set,那么如何防止触发多次呢?

我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger

8. v-model 双向绑定的原理是什么

参考答案:

v-model 本质就是 :value + input 方法的语法糖。可以通过 model 属性的 propevent 属性来进行自定义原生v-model,会根据标签不同生成不同的事件和属性。

例如:

输入框为例,当用户在输入框输入内容时,会触发 input 事件,从而更新 value。而 value 的改变同样会更新视图,这就是 vue 中的双向绑定。双向绑定的原理,其实思路如下

首先要对数据进行劫持监听,所以我们需要设置一个监听器 Observer用来监听所有属性。如果属性发上变化了,就需要告诉订阅Watcher是否需要更新。

因为订阅者是有很多个,所以我们需要有一个消息订阅器 Dep 来专门收集这些订阅者,然后监听器 Observer 和订阅者 Watcher 之间进行统一管理的。

接着,我们还需要有一个指令解析器 Compile,对每个节点元素进行扫描解析,将相关指令对应初始化成一个订阅者 Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者 Watcher收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

因此接下去我们执行以下 3步骤,实现数据的双向绑定:

  1. 实现一个监听器 Observer用来劫持并监听所有属性,如果有变动的,就通知订阅者。

  2. 实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

  3. 实现一个解析器 Compile,可以扫描解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

流程图如下

img

9. vue2.xvuex3.x 渲染器diff 算法分别说一下?

参考答案:

简单来说,diff 算法有以下过程

正常 Diff 两个树的时间复杂度O(n^3),但实际情况下我们很少会进行跨层级的移动 DOM,所以 VueDiff 进行了优化,从O(n^3) -> O(n),只有当新旧 children 都为多个子节点时才需要用核心Diff 算法进行同层级比较。

Vue2 的核心 Diff 算法采用双端比较的算法,同时从新旧 children 的两端开始进行比较,借助 key 值找到可复用的节点,再进行相关操作。相比 ReactDiff 算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

Vue3.x 借鉴了 ivi 算法和 inferno 算法

创建 VNode 时就确定其类型,以及在 mount/patch 的过程中采用运算来判断一个 VNode类型,在这个基础之上再配合核心的 Diff 算法,使得性能上较 Vue2.x 有了提升。该算法中还运用了动态规划的思想求解最长递归序列

10. vue 组件的参数传递

参考答案:

解释一下父组件与子组件传值实现过程

  • 父组件传给子组件:子组件通过 props 方法接受数据

  • 子组件传给父组件:使用自定义事件,自组件通过 $emit 方法触发父组件的方法来传递参数

非父子组件的数据传递,兄弟组件传值是如何实现的

eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。

此外,总结 vue 中的组件通信方式常见使用场景可以分为三类:

11. Vue路由实现

参考答案:

解释 hash 模式history 模式的实现原理

# 后面 hash 值的变化,不会导致浏览器服务器发出请求浏览器不发出请求,就不会刷新页面;通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。

history 模式的实现,主要是 HTML5 标准发布两个 APIpushStatereplaceState,这两个 API 可以在改变 URL,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。

两种模式区别

说一下

r

o

u

t

e

r

router* 与 *

routerroute区别

$route 对象表示当前的路由信息包含了当前 URL 解析得到的信息包含当前的路径参数query 对象等。

$route 对象出现在多个地方:

$router 对象是全局路由的实例,是 router 构造方法实例

$router 对象常用的方法有:

vueRouter 有哪几种导航守卫?

  • 全局前置/钩子:beforeEach、beforeR-esolve、afterEach

  • 路由独享的守卫:beforeEnter

  • 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

解释一下 vueRouter完整的导航解析流程是什么

一次完整的导航解析流程如下

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

12. vuex 是什么?怎么使用它?什么场景下我们会使用到 vuex

参考答案:

vuex 是什么

vuex 是一个专为 Vue 应用程序开发的状态管理器采用中式存储管理应用的所有组件的状态。每一个 vuex 应用的核心就是 store仓库)。“store基本上就是一个容器,它包含着应用中大部分状态 (state)。

为什么需要 vuex

由于组件只维护自身的状态(data),组件创建时或者路由切换时,组件会被初始化,从而导致 data 也随之销毁。

使用方法

main.js 引入 store注入。只用来读取状态集中放在 store 中, 改变状态方式提交 mutations,这是个同步的事物,异步逻辑应该封装action 中。

什么场景下会使用到 vuex

如果是 vue 的小型应用,那么没有必要使用 vuex,这个时候使用 vuex 反而会带来负担。组件之间的状态传递使用 props自定义事件来传递即可

但是如果涉及到 vue 的大型应用,那么就需要类似于 vuex 这样的集中管理状态的状态机管理所有组件的状态。例如登录状态、加入购物车音乐播放等,总之只要是开发 vue 的大型应用,都推荐使用 vuex 来管理所有组件状态。

13. 说一下 v-forv-show区别

参考答案:

14. 如何让 CSS 值在当前的组件中起作用

参考答案:

vue 文件中的 style 标签上,有一个特殊的属性:scoped。当一个 style 标签拥有 scoped 属性时,它的 CSS 样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有 style 标签全部加上了 scoped,相当于实现了样式模块化

scoped 的实现原理

vue 中的 scoped 属性的效果主要通过 PostCSS 转译实现的。PostCSS 给一个组件中的所有 DOM 添加了一个独一无二的动态属性,然后,给 CSS 选择器额外加一个对应的属性选择器选择该组件中 DOM,这种做法使得样式只作用于含有该属性的 DOM,即组件内部 DOM

例如:

转译前

<template>
  <div class="example">hi</div>
</template>

<style scoped>
.example {
  color: red;
}
</style>

转译后:

<template>
  <div class="example" data-v-5558831a>hi</div>
</template>

<style>
.example[data-v-5558831a] {
  color: red;
}
</style>

15. keep-alive 相关

参考答案:

keepalive 组件是 vue 的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive 内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。

keep-alive 具有 includeexclude 属性,通过它们可以控制哪些组件进入缓存。另外它还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存

keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后

在具体的实现上,keep-alive 在内部维护了一个 key 数组和一个缓存对象

// keep-alive 内部的声明周期函数
created () {
    this.cache = Object.create(null)
    this.keys = []
}

key 数组记录目前缓存的组件 key 值,如果组件没有指定 key 值,则会为其自动生成一个唯一的 key 值

cache 对象以 key 值为键,vnode 为值,用于缓存组件对应的虚拟 DOM

在 keep-alive 的渲染函数中,其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。

当缓存数量超过 max 数值时,keep-alive 会移除掉 key 数组的第一个元素。

16. Vue 中如何进行组件的使用?Vue 如何实现全局组件的注册

参考答案:

要使用组件,首先需要使用 import引入组件,然后components 属性中注册组件,之后就可以在模板中使用组件了。

可以使用 Vue.component 方法来实现全局组件的注册

17. vue-cli 工程相关

参考答案:

构建 vue-cli 工程都用到了哪些技术?他们作用分别是什么?

  1. vue.js:vue-cli 工程的核心,主要特点是双向数据绑定和组件系统
  2. vue-router:vue 官方推荐使用的路由框架
  3. vuex:专为 Vue.js 应用项目开发的状态管理器,主要用于维护 vue 组件间共用的一些 变量 和 方法。
  4. axios(或者 fetchajax):用于发起 GET 、或 POST 等 http请求基于 Promise 设计
  5. vux等:一个专为vue设计的移动端UI组件库。
  6. webpack模块加载和vue-cli工程打包器。
  7. eslint:代码规范工具

vue-cli 工程常用的 npm 命令有哪些?

下载 node_modules 资源包的命令npm install

启动 vue-cli 开发环境npm命令npm run dev

vue-cli 生成 生产环境部署资源npm命令npm run build

用于查看 vue-cli 生产环境部署资源文件大小npm命令npm run buildreport

18. nextTick作用是什么?他的实现原理是什么?

参考答案:

作用vue 更新 DOM异步更新的,数据变化,DOM 的更新不会马上完成,nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。

实现原理:nextTick 主要使用了宏任务和微任务。根据执行环境分别尝试采用

原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务。

19. 说一下 Vue SSR 的实现原理

参考答案:

20. Vue 组件的 data 为什么必须是函数

参考答案:

组件中的 data 写成一个函数,数据以函数返回值形式定义。这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果

21. 说一下 Vuecomputed 的实现原理

参考答案:

当组件实例触发生命周期函数 beforeCreate 后,它会做一系列事情,其中就包括对 computed处理

它会遍历 computed 配置中的所有属性,为每一个属性创建一个 Watcher 对象,并传入一个函数,该函数的本质其实就是 computed 配置中的 getter,这样一来,getter 运行过程中就会收集依赖

但是和渲染函数不同,为计算属性创建的 Watcher 不会立即执行,因为要考虑到该计算属性是否会被渲染函数使用,如果没有使用,就不会得到执行。因此,在创建 Watcher 的时候,它使用了 lazy 配置,lazy 配置可以让 Watcher 不会立即执行。

收到 lazy影响Watcher 内部会保存两个关键属性来实现缓存,一个是 value,一个是 dirty

value 属性用于保存 Watcher 运行结果,受 lazy影响,该值在最开始是 undefined

dirty 属性用于指示当前的 value 是否已经过时了,即是否为脏值,受 lazy影响,该值在最开始是 true

Watcher 创建好后,vue 会使用代理模式,将计算属性挂载到组件实例中

读取计算属性时,vue 检查其对应的 Watcher 是否是脏值,如果是,则运行函数,计算依赖,并得到对应的值,保存Watchervalue 中,然后设置 dirtyfalse,然后返回

如果 dirtyfalse,则直接返回 watchervalue

巧妙的是,在依赖收集时,被依赖的数据不仅会收集到计算属性的 Watcher,还会收集到组件的 Watcher

当计算属性的依赖变化时,会先触发计算属性的 Watcher 执行,此时,它只需设置 dirtytrue 即可,不做任何处理

由于依赖同时会收集到组件的 Watcher,因此组件会重新渲染,而重新渲染时又读取到了计算属性,由于计算属性目前已为 dirty,因此会重新运行 getter 进行运算

而对于计算属性的 setter,则极其简单,当设置计算属性时,直接运行 setter 即可

22. 说一下 Vue complier 的实现原理是什么样的?

参考答案:

在使用 vue 的时候,我们有两种方式来创建我们的 HTML 页面,第一种情况,也是大多情况下,我们会使用模板 template方式,因为这更易读易懂也是官方推荐的方法;第二种情况是使用 render 函数来生成 HTML,它比 template 更接近最终结果

complier 的主要作用是解析模板,生成渲染模板的 render, 而 render 的作用主要是为了生成 VNode

complier 主要分为 3 大块:

23. vue 如何快速定位那个组件出现性能问题

参考答案:

timeline ⼯具。 通过 timeline查看每个函数的调⽤时常,定位出哪个函数的问题,从⽽能判断哪个组件出了问题

24. Proxy 相比 defineProperty 的优势在哪里

参考答案:

Vue3.x 改用 Proxy 替代 Object.defineProperty

原因在于 Object.defineProperty 本身存在的一些问题

  • Object.defineProperty 只能劫持对象属性的 gettersetter 方法。
  • Object.definedProperty支持数组(可以监听数组,不过数组方法无法监听自己重写),更准确的说是不支持数组的各种 API(所以 Vue 重写了数组方法。

而相比 Object.definePropertyProxy 的优点在于:

  • Proxy 是直接代理劫持整个对象。
  • Proxy 可以直接监听对象和数组的变化,并且有多达 13拦截方法。

目前,Object.definedProperty 唯一Proxy 好的一点就是兼容性,不过 Proxy标准也受到浏览器厂商重点持续性能优化当中

25. VueAngular 以及 React区别是什么?

参考答案:

这种题目开放题目,一般是面试过程中面试官口头来提问,不太可能出现在笔试试卷里面

关于 Vue 和其他框架的不同,官方专门写了一篇文档,从性能体积、灵活性等多个方面来进行了说明

详细可以参阅:https://cn.vuejs.org/v2/guide/comparison.html

建议面试前通读一遍该篇文档,然后进行适当的总结

26. 说一下 watchcomputed区别是什么?以及他们的使用场景分别是什么?

参考答案:

区别

  1. 都是观察数据变化的(相同
  2. 计算属性将会混入到 vue 的实例中,所以需要监听自定义变量watch 监听 data 、props 里面数据的变化;
  3. computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
  4. watch 支持异步computed 不支持;
  5. watch一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
  6. watch 监听函数接收两个参数,第一个最新值,第二个输入之前的值;
  7. computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值return

watch 的 参数:

  • deep深度监听
  • immediate :组件加载立即触发回调函数执行

computed 缓存原理:

conputed本质是一个惰性的观察者;当计算数存在于 data 或者 props里时会被警告

vue 初次运行会对 computed 属性做初始化处理initComputed),初始化的时候会对每一个 computed 属性用 watcher 包装起来 ,这里面会生成一个 dirty 属性值为 true;然后执行 defineComputed 函数来计算,计算之后会将 dirty 值变为 false这里会根据 dirty 值来判断是否需要重新计算;如果属性依赖的数据发生变化,computed 的 watcher 会把 dirty 变为 true,这样就会重新计算 computed 属性的值。

27. scoped 是如何实现样式穿透的?

参考答案:

首先说一下什么场景下需要 scoped 样式穿透

在很多项目中,会出现这么一种情况,即:引用第三方组件,需要在组件中局部修改第三方组件的样式,而又不想去除 scoped 属性造成组件之间的样式污染。此时只能通过特殊方式穿透 scoped

三种常用的方法来实现样式穿透

方法一

使用 ::v-deep 操作符( >>> 的别名)

如果希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,可以使用 >>> 操作符

<style scoped>
    .a >>> .b { /* ... */ }
</style>

上述代码将会编译成

.a[data-v-f3f3eg9] .b { /* ... */ }

后面的类名没有 data 属性,所以能选到子组件里面类名

有些像 Sass 之类的预处理器无法正确解析 >>>,所以需要使用 ::v-deep 操作符来代替。

方法二

定义一个含有 scoped 属性的 style 标签之外,再定义一个不含有 scoped 属性的 style 标签,即在一个 vue 组件中定义一个全局的 style 标签,一个含有作用域style 标签:

<style>
/* global styles */
</style>

<style scoped>
/* local styles */
</style>

此时,我们只需要将修改第三方样式的 css 写在第一个 style即可

方法三

上面的方法一需要单独书写一个不含有 scoped 属性的 style 标签,可能会造成全局样式的污染。

推荐方式是在组件的外层 DOM 上添加唯一class 来区分不同组件,在书写样式时就可以正常针对针对这部分 DOM 书写样式。

28. 说一下 ref 的作用是什么?

参考答案:

ref 的作用是被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。其特点是:

  • 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
  • 如果用在子组件上,引用就指向组件实例

所以常见的使用场景有:

  1. 基本用法,本页面获取 DOM 元素
  2. 取子组件中的 data
  3. 调用子组件中的方法

29. 说一下你知道vue 修饰符都有哪些?

参考答案:

vue修饰符可以分为 3 类:

事件修饰符

事件处理程序中调用 event.preventDefaultevent.stopPropagation 方法是非常常见需求。尽管可以在 methods 中轻松实现这点,但更好方式是:methods 只有纯粹的数据逻辑,而不是去处理 DOM 事件细节

为了解决这个问题vuev-on 提供了事件修饰符。通过由点 . 表示指令后缀来调用修饰符。

常见的事件修饰如下

按键修饰

除了事件修饰符以外,在 vue 中还提供了有鼠标修饰符,键值修饰符,系统修饰符等功能。

表单修饰符

vue 同样也为表单控件也提供了修饰符,常见的有 .lazy.number.trim

30. 如何实现 vue 项目中的性能优化?

参考答案:

编码阶段

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

SEO 优化

  • 预渲染
  • 服务端渲染 SSR

打包优化

用户体验

  • 骨架屏
  • PWA

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

31. Vue.extendVue.component区别是什么?

参考答案:

Vue.extend 用于创建一个基于 Vue 构造函数的“子类”,其参数应为一个包含组件选项的对象。

Vue.component 用来注册全局组件。

32. vue 中的 spa 应用如何优化首屏加载速度?

参考答案:

优化首屏加载可以从这几个方面开始:

33. 移动端如何实现一个比较友好的 header 组件

参考答案:

Header 一般分为左、中、右三个部分,分为三个区域设计,中间为主标题,每个页面的标题肯定不同,所以可以通过 vue props的方式做成可配置对外进行暴露,左侧大部分页面可能都是回退按钮,但是样式和内容不尽相同右侧一般都是具有功能性的操作按钮,所以左右两侧可以通过 vue slot 插槽的方式对外暴露以实现多样化,同时也可以提供 default slot 默认插槽来统一页风格

34. 既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 监测差异 ?

参考答案:

现代前端框架有两种方式侦测变化,一种是 pull,一种是 push

pull

代表React,我们可以回忆一下 React 是如何侦测到变化的。

我们通常会用 setState API 显式更新,然后 React 会进行一层层的 Virtual Dom Diff 操作找出差异,然后 PatchDOM 上,React 从一开始就不知道到底是哪发生了变化,只是知道「有变化了」,然后再进行比较暴力的 Diff 操作查找「哪发生变化了」,另外一个代表就是 Angular 的脏检查操作。

push

Vue 的响应式系统则是 push代表,当 Vue 程序初始化的时候就会对数据 data 进行依赖的收集,一但数据发生变化,响应式系统就会立刻得知,因此 Vue 是一开始就知道是「在哪发生变化了」

但是这又会产生一个问题,通常绑定一个数据就需要一个 Watcher,一但我们的绑定细粒度过高就会产生大量的 Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化,因此 Vue设计选择中等细粒度的方案,在组件级别进行 push 侦测的方式,也就是那套响应式系统

通常我们会第一时间侦测到发生变化的组件,然后在组件内部进行 Virtual Dom Diff 获取更加具体的差异,而 Virtual Dom Diff 则是 pull 操作,Vuepush + pull 结合的方式进行变化侦测的。

35. Vue 为什么没有类似于 ReactshouldComponentUpdate 的生命周期?

参考答案:

根本原因是 VueReact 的变化侦测方式有所不同

Reactpull 的方式侦测变化,当 React 知道发生变化后,会使用 Virtual Dom Diff 进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要用 shouldComponentUpdate 进行手动操作来减少 diff,从而提高程整体的性能。

Vuepull+push 的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在 push 的阶段并不需要手动控制 diff,而组件内部采用diff 方式实际上是可以引入类似于 shouldComponentUpdate 相关生命周期的,但是通常合理大小的组件不会有过量的 diff手动优化的价值有限,因此目前 Vue 并没有考虑引入 shouldComponentUpdate 这种手动优化的生命周期。

36. Vue 中的 Key 的作用是什么?

参考答案:

key 的作用主要是为了高效的更新虚拟 DOM。另外 vue 中在使用相同签名元素的过渡切换时,也会使用到 key 属性,其目的也是为了让 vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果

解析:

其实不只是 vuereact 中在执行列表渲染时也会要求给每个组件添加上 key 这个属性。

要解释 key 的作用,不得不先介绍一下虚拟 DOMDiff 算法了。

我们知道,vuereact 都实现了一套虚拟 DOM,使我们可以不直接操作 DOM 元素,只操作数据便可以重新渲染页面。而隐藏在背后的原理便是其高效的 Diff 算法。

vuereact 的虚拟 DOMDiff 算法大致相同,其核心有以下两点

基于以上这两点,使得虚拟 DOMDiff 算法的复杂度O(n^3) 降到了 O(n)

image-20210821142057777

当页面的数据发生变化时,Diff 算法只会比较同一层级的节点:

  • 如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。
  • 如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。

当某一层有很多相同的节点时,也就是列表节点时,Diff 算法的更新过程默认情况下也是遵循以上原则

比如一下这个情况:

img

我们希望可以在 BC 之间加一FDiff 算法默认执行起来是这样的:

img

即把 C 更新成 FD 更新成 CE 更新成 D最后再插入 E

是不是很没有效率

所以我们需要使用 key 来给每个节点做一个唯一标识Diff 算法就可以正确识别此节点,找到正确位置区插入新的节点。

img

37. 你的接口请求一般放在哪个生命周期中?为什么要这样做?

参考答案:

接口请求可以放在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中data 已经创建,可以将服务端端返回的数据进行赋值

但是推荐created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

  • 能更快获取到服务端数据,减少页面 loading 时间
  • SSR 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于代码的一致性
  • created 是在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。如果在 mounted 钩子函数中请求数据可能导致页面闪屏问题

38. 说一下你对 vue 事件绑定原理的理解

参考答案:

vue 中的事件绑定是有两种,一种是原生的事件绑定,另一种是组件的事件绑定。

原生的事件绑定在普通元素上是通过 @click 进行绑定,在组件上是通过 @click.native 进行绑定,组件中的 nativeOn 是等价于 on 的。组件的事件绑定的 @click 是 vue 中自定义的 $on 方法来实现的,必须有 $emit 才可以触发。

原生事件绑定原理

runtime下的patch.js中createPatchFunction执行了之后再赋值patch

createPatchFunction方法有两个参数,分别是nodeOps存放操作dom节点的方法和modulesmodules是有两个数组拼接起来的,modules拼接完的数组中有一个元素就是events,事件添加就发生在这里

events元素关联的就是events.js文件,在events中有一个updateDOMListeners方法,在events文件的结尾导出了一个对象,然后对象有一个属性叫做create,这个属性关联的就是updateDOMListeners方法。

在执行createPatchFunction方法时,就会将这两个参数传入,在createPatchFunction方法中接收了一个参数backend,在该方法中一开始进行backend解构,就是上面的nodeOpsmodules参数,解构完之后进入for循环。

在createPatchFunction开头定义了一个cbs对象。for循环遍历一个叫hooks的数组。hooks文件开头定义的一个数组,其中包括有create,for循环就是在cbs上定义一系列和hooks元素相同的属性,然后键值是一个数组,然后数组内容modules里面的一些内容。这时就把events文件中导出来的create属性放在了cbs上。

当我们进入首次渲染的时候,会执行到patch函数里面的createElm方法,这个方法中就会调用invokeCreateHooks函数,用来处理事件系统,这里就是真正准备进行原生事件绑定的入口。invokeCreateHooks方法中,遍历cbs.create数组里面的内容。然后把cbs.create里面的函数全部都执行一次,在cbs.create其中一个函数就是updateDOMListeners。

updateDOMListeners就是用来添加事件的方法,在这方法中会根据vnode判断是否有定义一个点击事件。如果没有点击事件就return。有的话就继续执行,给on进行赋值,然后进行一些赋值操作,将vnode.elm赋值target,elm这个属性就是指向vnode所对应的真实dom节点,这里就是把我们要绑定事件的dom结点进行缓存,接下来执行updateListeners方法。在接下来执行updateListeners方法中调用了一个add的方法,然后在app方法中通过原生addEventListener把事件绑定到dom上。

组件事件绑定原理

在组件实例初始化会调用initMixin方法中的Vue.prototype._init,在init函数中,会通过initInternalComponent方法初始化组件信息,将自定义的组件事件放到_parentListeners上,下来就会调用initEvents来初始化组件事件,在initEvents中会实例上添加一个 _event对象,用于保存自定义事件,然后获取到 父组件给 子组件绑定的自定义事件,也就是刚才在初始化组件信息的时候将自定义的组件事件放在了_parentListeners上,这时候vm.$options._parentListeners就是自定义的事件。

最后进行判断,如果有自定义的组件事件就执行updateComponentListeners方法进行事件绑定,在updateComponentListeners方法中会调用updateListeners方法,并传传一个add方法进行执行,这个add方法里就是$on方法。

39. 说一下 vue 模版编译的原理是什么

参考答案:

简单说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经历以下阶段:

  • 生成 AST
  • 优化
  • codegen

首先解析模版,生成 AST 语法树(一种用 JavaScript 对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的 DOM 也不会变化。那么优化过程就是深度遍历 AST 树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最后一步是将优化后AST转换为可执行的代码。

可以参阅前面22 题。

40. deleteVue.delete 删除数组的区别是什么?

参考答案:

delete 只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
Vue.delete 是直接将元素从数组中完全删除,改变了数组其他元素的键值。

41. v-on 可以实现监听多个方法么?

参考答案:

可以监听多个方法。关于监听多个方法提供了几种不同的写法

写法一:<div v-on="{ 事件类型: 事件处理函数, 事件类型: 事件处理函数 }"></div>
写法二:<div @事件类型=“事件处理函数 @事件类型=“事件处理函数></div>
写法三:在一个事件里面书写多个事件处理函数
<div @事件类型=“事件处理函数1,事件处理函数2”></div>
写法四:在事件处理函数内部调用其他的函数

示例代码如下

<template>
  <div>
    <!-- v-on在vue2.x中测试,以下两种均可-->
    <button v-on="{ mouseenter: onEnter, mouseleave: onLeave }">
      鼠标进来1
    </button>
    <button @mouseenter="onEnter" @mouseleave="onLeave">鼠标进来2</button>

    <!-- 一个事件绑定多个函数,按顺序执行,这里分隔函数可以用逗号也可以用分号-->
    <button @click="a(), b()">点我ab</button>
    <button @click="one()">点我onetwothree</button>
  </div>
</template>
<script>
export default {
  methods: {
    //这里es6对象里函数写法
    a() {
      console.log("a");
    },
    b() {
      console.log("b");
    },
    one() {
      console.log("one");
      this.two();
      this.three();
    },
    two() {
      console.log("two");
    },
    three() {
      console.log("three");
    },
    onEnter() {
      console.log("mouse enter");
    },
    onLeave() {
      console.log("mouse leave");
    },
  },
};
</script>

42. vue 的数据为什么频繁变化但只会更新一次?

参考答案:

这是因为 vueDOM 更新是一个异步操作,在数据更新后会首先被 set 钩子监听到,但是不会马上执行 DOM 更新,而是在下一轮循环中执行更新。

具体实现是 vue 中实现了一个 queue 队列用于存放本次事件循环中的所有 watcher 更新,并且同一个 watcher 的更新只会被推入队列一次,并在本轮事件循环的微任务执行结束后执行此更新(UI Render 阶段),这就是 DOM 只会更新一次的原因。

这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,vue 刷新队列并执行实际 (已去重的) 工作vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

43. 说一下 vuecomputedmethods 的区别是什么?

参考答案:

首先从表现形式上面来看, computedmethods 的区别大致有下面 4 点:

  1. 在使用时,computed 当做属性使用,而 methods 则当做方法调用
  2. computed 可以具有 gettersetter,因此可以赋值,而 methods 不行
  3. computed 无法接收多个参数,而 methods 可以
  4. computed 具有缓存,而 methods 没有

而如果从底层来看的话, computedmethods底层实现上面还有很大的区别。

vuemethods 的处理比较简单,只需要遍历 methods 配置中的每个属性,将其对应的函数使用 bind 绑定当前组件实例后复制其引用到组件实例中即可

vuecomputed 的处理会稍微复杂一些。

具体可以参阅前面21 题。

44. Vue 中要获取当前时间你会放到 computed 还是 methods 里?(抖音直播)

参考答案:

放在 computed 里面。因为 computed 只有在它的相关依赖发生改变时才会重新求值。相比而言,方法只要发生重新渲染,methods 调用总会执行所有函数。

45. 在给 vue 中的元素设置 key 值时可以使用 Mathrandom 方法么?

参考答案:

random 是生成随机数,有一定概率多个 item 会生成相同的值,不能保证唯一

如果是根据数据来生成 item,数据具有 id 属性,那么就可以使用 id 来作为 key

如果不是根据数据生成 item,那么最好的方式就是使用时间戳来作为 key。或者使用诸如 uuid 之类的库来生成唯一id

46. 插槽作用域插槽的区别是什么?

参考答案:

插槽的作用是子组件提供了可替换模板,父组件可以更换模板的内容。

作用域插槽给了子组件将数据返给父组件的能力,子组件一样可以复用,同时父组件也可以重新组织内容和样式。

47. vue 中相同逻辑如何进行抽离?

参考答案:

可以使用 vue 里面的混入(mixin)技术。混入(mixin)提供了一种非常灵活的方式,来将 vue 中相同的业务逻辑进行抽离。

例如:

  • data 中有很多是公用数据
  • 引用封装好的组件也都是一样的
  • methods、watch、computed 中也都有大量的重复代码

当然这个时候可以将所有的代码重复去写来实现功能,但是我们并不不推荐使用这种方式,无论是工作量工作效率和后期维护来说都是不建议的,这个时候 mixin 就可以大展身手了。

一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。说白了就是给每个生命周期,函数等等中间加入一些公共逻辑。

混入技术特点

48. 如何监听 pushstatereplacestate 的变化呢?

参考答案:

History.replaceStatepushState 不会触发 popstate 事件,所以我们可以通过在方法中创建一个新的全局事件来实现 pushstatereplacestate 变化的监听。

具体做法为:

var _wr = function(type) {
  var orig = history[type];
  return function() {
      var rv = orig.apply(this, arguments);
     var e = new Event(type);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return rv;
  };
};
history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');

这样就创建了 2 个全新的事件,事件名为 pushStatereplaceState,我们就可以在全局监听:

window.addEventListener('replaceState', function(e) {
 console.log('THEY DID IT AGAIN! replaceState 111111');
});
window.addEventListener('pushState', function(e) {
 console.log('THEY DID IT AGAIN! pushState 2222222');
});

这样就可以监听到 pushStatereplaceState 行为

49. 说一下 vue3.0 是如何变得更快的?

参考答案:

优化 Diff 算法

相比 Vue 2Vue 3 采用了更加优化的渲染策略去掉不必要的虚拟 DOM 树遍历和属性比较,因为这在更新期间往往会产生最大的性能开销。

这里有三个主要的优化:

  • 首先,在 DOM级别

在没有动态改变节点结构的模板指令(例如 v-ifv-for)的情况下,节点结构保持完全静态

当更新节点时,不再需要递归遍历 DOM 树。所有的动态绑定部分将在一个平面数组中跟踪。这种优化通过将需要执行的树遍历量减少一个数量级来规避虚拟 DOM 的大部分开销。

编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。

例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。

综合起来,这些技术大大改进了渲染更新基准Vue 3.0 有时占用CPU 时间不到 Vue 2 的十分之一。

体积变小

重写后的 Vue 支持了 treeshaking,像修剪树叶一样把不需要的东西给修剪掉,使 Vue 3.0 的体积更小。

需要的模块才会打入到包里,优化后Vue 3.0打包体积只有原来的一半(13kb)。哪怕把所有的功能都引入进来也只有 23kb,依然比 Vue 2.x 更小。像 keep-alive、transition 甚至 v-for 等功能都可以按需引入。

并且 Vue 3.0 优化了打包方法,使得打包后的 bundle 的体积也更小。

官方所给出的一份惊艳的数据:打包大小减少 41%,初次渲染快 55%,更新快 133%内存使用减少 54%

50. 说一说自定义指令有哪些生命周期?

参考答案:

自定义指令的生命周期,有 5 个事件钩子,可以设置指令在某一个事件发生时的具体行为

  • bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作
  • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
  • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。
  • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
  • unbind: 只调用一次, 指令与元素解绑时调用。

钩子函数的参数 (包括 el,bindingvnodeoldVnode)

  • el: 指令所绑定的元素,可以用来直接操作 DOM 。
  • binding: 一个对象,包含以下属性:name: 指令名、value: 指令的绑定值、oldValue: 指令绑定的前一个值、expression: 绑定值的字符串形式、arg: 传给指令的参数、modifiers: 一个包含修饰符的对象。
  • vnode: Vue 编译生成的虚拟节点。
  • oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

51. 说一说相比 vue3.x 对比 vue2.x 变化

参考答案:

  1. 源码组织方式变化:使用 TS 重写
  2. 支持 Composition API:基于函数的API,更加灵活组织组件逻辑(vue2用的是options api
  3. 响应式系统提升:Vue3中响应式数据原理改成proxy,可监听动态新增删除属性,以及数组变化
  4. 编译优化:vue2通过标记静态根节点优化diff,Vue3 标记提升所有静态根节点,diff的时候只需要对比动态节点内容
  5. 打包体积优化:移除了一些不常用的apiinlinetemplate、filter
  6. 生命周期的变化:使用setup代替了之前的beforeCreate和created
  7. Vue3 的 template 模板支持多个根标签
  8. Vuex状态管理:创建实例的方式改变,Vue2为new Store , Vue3为createStore
  9. Route 获取页面实例与路由信息:vue2通过this获取router实例,vue3通过使用 getCurrentInstance/ userRoute和userRouter方法获取当前组件实例
  10. Props 的使用变化:vue2 通过 this 获取 props 里面的内容,vue3 直接通过 props
  11. 父子组件传值:vue3 在向父组件传回数据时,如使用的自定义名称,如 backData,则需要在 emits 中定义一下

52. vue 为什么采用异步渲染

参考答案:

因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染;所以为了性能考虑Vue 会在本轮数据更新后,再去异步更新视图。

异步渲染的原理:

  1. 调用 notify( ) 方法,通知 watcher 进行更新操作
  2. 依次调用 watcher 的 update 方法
  3. 对 watcher 进行去重操作(通过id)放到队列
  4. 执行完后异步清空这个队列,nextTick(flushSchedulerQueue)进行批量更新操作

53. 组件中写 name 选项有哪些好处

参考答案:

  1. 可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )
  2. 可以通过 name 属性实现缓存功能(keep-alive
  3. 可以通过 name识别组件(跨级组件通信时非常重要)
  4. 使用 vue-devtools 调试工具显示的组见名称是由 vue 中组件 name 决定的

原文地址:https://blog.csdn.net/wanghaoyingand/article/details/129824882

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_33608.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注