写点什么

vue 高频面试题合集(一)附答案

  • 2022 年 8 月 11 日
    浙江
  • 本文字数:6512 字

    阅读完需:约 21 分钟

vue3.0 特性你有什么了解的吗?

Vue 3.0 正走在发布的路上,Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0 增加以下这些新特性:


(1)监测机制的改变


3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:


  • 只能监测属性,不能监测对象

  • 检测属性的添加和删除;

  • 检测数组索引和长度的变更;

  • 支持 Map、Set、WeakMap 和 WeakSet。


新的 observer 还提供了以下特性:


  • 用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。

  • 默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。

  • 更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。

  • 不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。

  • 更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。


(2)模板


模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。


同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。


(3)对象式的组件声明方式


vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。


此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。


(4)其它方面的更改


vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:


  • 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。

  • 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。

  • 基于 treeshaking 优化,提供了更多的内置功能。

v-model 可以被用在自定义组件上吗?如果可以,如何使用?

可以。v-model 实际上是一个语法糖,如:


<input v-model="searchText">复制代码
复制代码


实际上相当于:


<input  v-bind:value="searchText"  v-on:input="searchText = $event.target.value">复制代码
复制代码


用在自定义组件上也是同理:


<custom-input v-model="searchText">复制代码
复制代码


相当于:


<custom-input  v-bind:value="searchText"  v-on:input="searchText = $event"></custom-input>复制代码
复制代码


显然,custom-input 与父组件的交互如下:


  1. 父组件将searchText变量传入 custom-input 组件,使用的 prop 名为value

  2. custom-input 组件向父组件传出名为input的事件,父组件将接收到的值赋值给searchText


所以,custom-input 组件的实现应该类似于这样:


Vue.component('custom-input', {  props: ['value'],  template: `    <input      v-bind:value="value"      v-on:input="$emit('input', $event.target.value)"    >  `})复制代码
复制代码

Vue 中 computed 和 watch 有什么区别?

计算属性 computed:(1)支持缓存,只有依赖数据发生变化时,才会重新进行计算函数;(2)计算属性内不支持异步操作;(3)计算属性的函数中都有一个 get(默认具有,获取计算属性)和 set(手动添加,设置计算属性)方法;(4)计算属性是自动监听依赖值的变化,从而动态返回内容。


侦听属性 watch:(1)不支持缓存,只要数据发生变化,就会执行侦听函数;(2)侦听属性内支持异步操作;(3)侦听属性的值可以是一个对象,接收 handler 回调,deep,immediate 三个属性;(3)监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些其他事情

Vue 模版编译原理知道吗,能简单说一下吗?

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


  • 生成 AST 树

  • 优化

  • codegen


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


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


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

keep-alive 使用场景和原理

keep-alive 是 Vue 内置的一个组件,可以实现组件缓存,当组件切换时不会对当前组件进行卸载。


  • 常用的两个属性 include/exclude,允许组件有条件的进行缓存。

  • 两个生命周期 activated/deactivated,用来得知当前组件是否处于活跃状态。

  • keep-alive 的中还运用了 LRU(最近最少使用) 算法,选择最近最久未使用的组件予以淘汰。

vue 中使用了哪些设计模式

1.工厂模式 - 传入参数即可创建实例


虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode


2.单例模式 - 整个程序有且仅有一个实例


vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉


3.发布-订阅模式 (vue 事件机制)


4.观察者模式 (响应式数据原理)


5.装饰模式: (@装饰器的用法)


6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略

v-show 与 v-if 有什么区别?

v-if真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。


v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。


所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。

Vue-router 路由有哪些模式?

一般有两种模式:(1)hash 模式:后面的 hash 值的变化,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变化会触发 hashchange 事件。(2)history 模式:利用了 HTML5 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。

Vue 模板编译原理

Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步


第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
复制代码

Vue 初始化页面闪动问题如何解决?

出现该问题是因为在 Vue 代码尚未被解析之前,尚无法控制页面中 DOM 的显示,所以会看见模板字符串等代码。解决方案是,在 css 代码中添加 v-cloak 规则,同时在待编译的标签上添加 v-cloak 属性:


[v-cloak] { display: none; }
<div v-cloak> {{ message }}</div>
复制代码

谈谈对 keep-alive 的了解

keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。常用的2个属性 include/exclude ,2个生命周期 activated deactivated

nextTick 使用场景和原理

nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法


相关代码如下


let callbacks = [];let pending = false;function flushCallbacks() {  pending = false; //把标志还原为false  // 依次执行回调  for (let i = 0; i < callbacks.length; i++) {    callbacks[i]();  }}let timerFunc; //定义异步方法  采用优雅降级if (typeof Promise !== "undefined") {  // 如果支持promise  const p = Promise.resolve();  timerFunc = () => {    p.then(flushCallbacks);  };} else if (typeof MutationObserver !== "undefined") {  // MutationObserver 主要是监听dom变化 也是一个异步方法  let counter = 1;  const observer = new MutationObserver(flushCallbacks);  const textNode = document.createTextNode(String(counter));  observer.observe(textNode, {    characterData: true,  });  timerFunc = () => {    counter = (counter + 1) % 2;    textNode.data = String(counter);  };} else if (typeof setImmediate !== "undefined") {  // 如果前面都不支持 判断setImmediate  timerFunc = () => {    setImmediate(flushCallbacks);  };} else {  // 最后降级采用setTimeout  timerFunc = () => {    setTimeout(flushCallbacks, 0);  };}
export function nextTick(cb) { // 除了渲染watcher 还有用户自己手动调用的nextTick 一起被收集到数组 callbacks.push(cb); if (!pending) { // 如果多次调用nextTick 只会执行一次异步 等异步队列清空之后再把标志变为false pending = true; timerFunc(); }}
复制代码

那 vue 中是如何检测数组变化的呢?

数组就是使用 object.defineProperty 重新定义数组的每一项,那能引起数组变化的方法我们都是知道的, pop push shift unshift splice sort reverse 这七种,只要这些方法执行改了数组内容,我就更新内容就好了,是不是很好理解。


  1. 是用来函数劫持的方式,重写了数组方法,具体呢就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新。

  2. 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象才能进行观测,观测过的也不会进行观测)


vue3:改用 proxy ,可直接监听对象数组的变化。

虚拟 DOM 的优缺点?

优点:


  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;

  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;

  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。


缺点:


  • 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

Vue 中的 key 到底有什么用?

key 是给每一个 vnode 的唯一 id,依靠 key,我们的 diff 操作可以更准确、更快速 (对于简单列表页渲染来说 diff 节点也更快,但会产生一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。)


diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行比对,从而找到相应旧节点.


更准确 : 因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确,如果不加 key,会导致之前节点的状态被保留下来,会产生一系列的 bug。


更快速 : key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1)

Vue 中的 key 到底有什么用?

key是为 Vue 中的 vnode 标记的唯一 id,通过这个 key,我们的 diff 操作可以更准确、更快速


diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.


diff 程可以概括为:oldCh 和 newCh 各有两个头尾的变量 StartIdx 和 EndIdx,它们的 2 个变量相互比较,一共有 4 种比较方式。如果 4 种比较都没匹配,如果设置了 key,就会用 key 进行比较,在比较的过程中,变量会往中间靠,一旦 StartIdx>EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.


  • 准确: 如果不加key,那么 vue 会选择复用节点(Vue 的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的 bug.

  • 快速: key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1).

写过自定义指令吗 原理是什么

指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。


自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind


1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
5. unbind:只调用一次,指令与元素解绑时调用。
复制代码


原理


1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性


2.通过 genDirectives 生成指令代码


3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子


4.当执行指令对应钩子函数时,调用对应指令定义的方法

Vue 为什么没有类似于 React 中 shouldComponentUpdate 的生命周期?

考点: Vue 的变化侦测原理


前置知识: 依赖收集、虚拟 DOM、响应式系统


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


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


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

computed 的实现原理

computed 本质是一个惰性求值的观察者。


computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。


其内部通过 this.dirty 属性标记计算属性是否需要重新求值。


当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,


computed watcher 通过 this.dep.subs.length 判断有没有订阅者,


有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)


没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)

用户头像

还未添加个人签名 2022.07.31 加入

还未添加个人简介

评论

发布
暂无评论
vue高频面试题合集(一)附答案_Vue_helloworld1024fd_InfoQ写作社区