(七)Vue 面试题详解(2024)

152 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 7 天,点击查看活动详情

如何理解前端渲染

把数据填充到HTML标签中:模板 + 数据 → 前端渲染 → 静态 HTML 内容

更多关于前端渲染

为何 Vue 采用异步渲染

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

如何理解响应式

Html5 中的响应式

屏幕尺寸的变化导致样式的变化

数据的响应式

数据的变化导致页面内容的变化

Vue 的响应式系统

  • 任何⼀个Vue Component都有⼀个与之对应的Watcher实例
  • Vuedata上的属性会被添加gettersetter属性
  • Vue Component render函数被执⾏的时候,data上会被 触碰(touch),即被getter⽅法会被调用,此时Vue会去记录此Vue component 所依赖的所有data。(这⼀过程被称为依赖收集)
  • data被改动时(主要是用户操作) , 即被setter方法会被调⽤ , 此时Vue会去通知所有依赖于此data的组件去调用他们的render函数进⾏更新

谈谈双向数据绑定 当数据发生变化的时候,视图也就发生变化 ;当视图发生变化的时候,数据也会跟着同步变化

实现数据绑定的方法

发布者-订阅者模式(backdone.js) 脏值检查(angular.js) 数据劫持(vue.js) `Vue 的数据双向绑定是如何实现的 数据劫持

理解:通过Object.defineProperty劫持对象的访问器

你对 MVVM 的理解

MVVM

构成:

  • Model(数据层):数据模型(获取数据的逻辑)
  • View(视图层):视图模板(Vue中指的是各种template
  • ViewModel(控制层):视图适配器(Vue中对应的js,声明绑定的元素及绑定的数据)。暴露给View层需要的数据,并处理View层具体业务逻辑

优点:

  • 分离View(视图)和Model(模型),降低代码耦合,提⾼视图或者逻辑的重⽤性:比如View可以独⽴于Model变化和修改,⼀个ViewModel可以绑定在不同的View上,当View 变化的时候Model可以不变,当Model变化 的时候View也可以不变。你也可以把⼀些视图逻辑放在⼀个ViewModel⾥⾯,让很多View重⽤这段视图逻辑
  • 提⾼可测试性:ViewModel的存在可以帮助开发者更好地编写测试代码
  • ⾃动更新Dom::利⽤双向绑定,数据更新后视图⾃动更新,开发者从繁琐的⼿动Dom中解放

缺点:

  • Bug很难被调试:因为使⽤双向绑定的模式,当你看到界⾯异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得⼀个位置的Bug被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug
  • ⼀个⼤的模块中Model也会很⼤,虽然使⽤⽅便且保证了数据的⼀致性,但是如果⻓期持有不释放内存,就会造成内存的浪费
  • 对于⼤型的图形应⽤程序,视图状态较多,ViewModel的构建和维护的成本都会⽐较⾼

MVC

构成:

  • Model(数据层):程序需要操作的数据或信息(用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法)---- 数据保存
  • View(视图层):提供给用户的操作界面,是程序的外壳 ---- 用户界面
  • Controller(控制层):M 和 V 之间的连接器,用于控制应用程序的流程,及页面的业务逻辑 ---- 业务逻辑

理解:

它的构成可以看做"外观"、"机制"和"功能/数据"这三层结构。实际运作便是视图层传送指令给控制层,控制层调用数据层对象方法完成业务逻辑,并把结果返回到视图层(V-C-M-V-V)

三层紧密联系(对外提供接口),又相互独立(内部变化不影响其它层)

  • 实现模块化
  • 变化不影响其它层,方便维护

MVVM 和 MVC 有什么不同

  • MVVM实现了数据的双向绑定
  • MVVM可维护性更高
  • MVC某些时候性能更好
  • MVC是后端分层开发概念,MVVM是前端视图层的概念,主要关注于视图层分离

实现一个 MVVM(双向绑定)

vue2.0 基础

Vue 中 v-html 会导致哪些问题?

  • 可能会导致xss攻击
  • v-html会替换掉标签内部的子元素

Vue 父子组件生命周期调用顺序

  • 加载渲染过程 :父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted

  • 子组件更新过程 :父beforeUpdate->子beforeUpdate->子updated->父updated

  • 父组件更新过程 :父beforeUpdate->父updated

  • 销毁过程 :父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

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

Vue 组件如何通信? 单向数据流

  • 父子间通信 :父->子通过props 、子-> 父$on$emit(发布订阅)

  • 获取父子组件实例的方式 :$parent$children

  • 在父组件中提供数据子组件进行消费 :Provideinject插件

  • Ref获取实例的方式:调用组件的属性或者方法

  • Event Bus实现跨组件通信 :Vue.prototype.$bus = new Vue

  • Vuex状态管理实现通信 :$attrs $listeners

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

关于这个问题,需要意识到:这是因为js 的特性,跟vue本身设计无关。js本身的面向对象编程是基于原型链和构造函数,我们会注意到,原型链上添加一般都是一个函数方法而不会去添加一个对象。vue实例中的data是一个对象,作为绑定的数据。而这里组件中的data()必须是一个函数,其实应该叫做setData()

现在我们开始一步步理解:

1、关于 js 的数据类型

js 中的数据可以分为基础数据类型和引用数据类型

  • 基础数据类型 如undefindednullNumberStringBooleansymbol,数据存放在栈中;

  • 引用数据类型 即对象数据类型,如:objectarrayfunction,数据存放在堆中,栈中存放的是堆中的引用地址。

2、关于 js 的深浅拷贝

我们来看看深浅拷贝的区别:

浅拷贝:将原对象或原数组的引用直接赋给新对象/新数组,新对象/数组只是原对象的一个引用 深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

3、数据类型与深浅拷贝

  • 基本数据类型是放在栈里面的,它是按值访问,在栈内存中发生复制行为时系统会为新的变量提供新值,所以两个变量互不影响。
  • 引用数据类型是放在堆内存中的,它是按引用访问的,在栈内存中有一个地址是指向堆内存中的引用数据类型的,所以我们拷贝引用数据类型其实就是拷贝了栈内存中的地址,因为地址一样,他们都是指向同一个引用数据类型,所以两个变量会相互影响,这时就必须使用深拷贝了。

4、为什么 data 要用函数?

经过以上理解,我们可以类比引用数据类型Object对象是引用数据类型,如果不用function函数返回,每个组件的data都是内存的同一个地址,如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改。而两个实例应该有自己各自的域才对。 这里补充一点:javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域)。data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。

Watch,Computed 区别与使用场景

computed

  • computed内部的函数在调用时不加()。即vm.属性名,如vm.reversedMessage

  • computed是依赖vmdata的属性变化而变化的,也就是说,当data中的属性发生改变的时候,当前函数才会执行,data中的属性没有改变的时候,当前函数不会执行。

data 和 computed 最核心的区别在于 data 中的属性并不会随赋值变量的改动而改动,而 computed 会。

  • computed 中的函数必须用 return 返回。

  • 注意:在computed中不要对data中的属性进行赋值操作。如果对data中的属性进行赋值操作了,就是 data 中的属性发生改变,从而触发computed中的函数,形成死循环了。

  • computed中的函数所依赖的属性没有发生改变,那么调用当前函数的时候会从缓存中读取。(不会再次计算)

watch

  • watch中的函数名称必须要和data中的属性名一致,因为watch是依赖data中的属性,当data中的属性发生改变的时候,watch中的函数就会执行。

  • watch中的函数有两个参数,前者是newVal最新的值,后者是oldVal输入之前的值。

  • watch中的函数是不需要调用的。

  • watch只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch想要监听引用类型数据的变化,需要进行深度监听。

deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。

"obj.name"(){}    //如果obj的属性太多,这种方法的效率很低,
obj:{handler(newVal){},deep:true}    //用handler+deep的方式进行深度监听。
  • 特殊情况下,watch无法监听到数组的变化,特殊情况就是说更改数组中的数据时,数组已经更改,但是视图没有更新。更改数组必须要用splice()或者$set
this.arr.splice(0, 1, 100); //修改arr中第0项开始的1个数据为100,
this.$set(this.arr, 0, 100); //修改arr第0项值为100。
  • immediate:true页面首次加载的时候做一次监听,组件加载立即触发回调函数执行。

(1)区别

  • 功能上:computed是计算属性;watch是监听一个值的变化,然后执行对应的回调。

  • 是否调用缓存:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取(支持缓存),也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值;而watch在每次监听的值发生变化的时候都会执行回调(不支持缓存)。

  • 是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return

  • 是否支持异步:computed不支持异步,当computed内有异步操作时无效,无法监听数据的变化;watch支持异步。

(2)使用场景区别

computed:比较适合对多个变量或者对象进行处理后返回一个结果值,也就是说多个变量中的某一个值发生了变化,则我们监控的这个值也就会发生变化。当一个属性受多个属性影响的时候(多对一),使用computed-------购物车商品结算。

watch:一般用于异步或者开销较大的操作。当一条数据影响多条数据的时候(一对多),使用watch-------搜索框。

v-show 与 v-if 区别与使用场景

共同点:都能实现元素的显示和隐藏

(1)区别

v-if控制元素是否渲染到页面:v-if是动态的向DOM树内添加或者删除DOM元素

v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

v-show控制元素是否显示(已经渲染到了页面):v-show本质就是标签display设置为none,控制隐藏

v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点

⚫ 补充:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;v-if有更高的切换消耗;v-show有更高的初始渲染消耗;v-if可以搭配template使用,而v-show不行。在使用template时,v-show将失去作用。因为v-show是设置显示与隐藏,而template 是没有实际东西的 dom,所以v-showtemplate联合使用将失效。

(2)使用场景

如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件很少改变,则使用v-if较好,tab中使用较好。

Vue 里的 key 有什么作用,可以用数组的 index 代替么?

(1)VueReact都实现了一套虚拟DOM,使我们可以不直接操作DOM元素,只操作数据便可以重新渲染页面。而隐藏在背后的原理便是其高效的Diff算法。核心假设如下:

  • 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。
  • 同一层级的一组节点,他们可以通过唯一的id进行区分。

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

(2)Vue 中 key 的作用(官网):

image-20220415170010770

(3)总结:当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。只适用于不依赖子组件状态或临时DOM状态 (例如:表单输入值) 的列表渲染输出

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

补充:

  • 在写v-for的时候,都需要给元素加上一个key属性

  • key的主要作用就是用来提高渲染性能的!

  • key属性可以避免数据混乱的情况出现 (如果元素中包含了有临时数据的元素,如果不用key就会产生数据混乱)

(4)不能使用 index 代替 key:给key加上index等于没有加,key默认得就是Index。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

Vue 组件的生命周期

每个生命周期什么时候被调用 ?

  • beforeCreate:在实例初始化之后,数据观测(data observer) 之前被调用。
  • created:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。这里没有$el
  • beforeMount:在挂载开始之前被调用:相关的render函数首次被调用。
  • mounted:el 被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。
  • beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。
  • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。
  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyedVue 实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件 监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

每个生命周期内部可以做什么事?

  • created:实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。
  • mounted:实例已经挂载完成,可以进行一些DOM操作
  • beforeUpdate:可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated:可以执行依赖于DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态, 因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。
  • destroyed `:可以执行一些优化操作,清空定时器,解除绑定事件

何时需要使用beforeDestroy

  • 可能在当前页面中使用了$on方法,那需要在组件销毁前解绑。
  • 清除自己定义的定时器
  • 解除事件的绑定scroll mousemove....

为什么 v-for 和 v-if 不能连用?

v-for会比v-if的优先级高一些,如果连用的话会把v-if 给每个元素都添加一下,会造成性能问题