前端高频面试题--Vue

发布于:2024-04-27 ⋅ 阅读:(11) ⋅ 点赞:(0)

1.说说对vue的理解,有什么特点

Vue 是一个渐进式的JavaScript 框架,用于构建用户页面.它被广泛用于构建单页面应用程序(SPA)Vue 的优点如下:

  • 易于学习:Vue 的文档清晰且易于理解,对于初学者而言很容易上手。
  • 响应式数据绑定:Vue 支持响应式数据绑定,这意味着当数据发生变化时,视图将自动更新。
  • 组件化开发:Vue 采用组件化开发方式,这使得开发人员可以构建可重用的组件,从而提高代码的可维护性和可扩展性。
  • 可扩展性:Vue 具有很强的可扩展性,它提供了许多可用的插件和组件来帮助开发人员实现特定的功能。 社区支持:Vue 有一个活跃的社区和许多可用的资源,这使得开发人员可以轻松地找到帮助和解决方案。

Vue 的缺点:

  • 性能问题:与 Angular等框架相比,Vue 的性能可能稍逊一筹。尽管 Vue 在大多数情况下表现良好,但在处理大量数据或复杂的渲染路径时,可能会导致性能下降。
  • 路由管理:虽然 Vue Router对于大多数应用程序来说已经足够好了,但在处理大型应用程序时,可能会需要更强大的路由管理解决方案。
  • 移动端支持:尽管 Vue 对移动端支持良好,但与其他专门针对移动设备设计的框架(如 ReactNative或Ionic)相比,Vue 在移动端性能可能略有不足。大规模应用下的性能优化:Vue在大规模、复杂的应用下可能需要进行更多的优化和定制,这可能需要额外的学习和工作。

总的来说,Vue 是一种功能强大、易于学习和使用的JavaScript 框架,适用于各种大小和类型的项目。它的优点使其成为许多开发人员的首选工具而其缺点则可以通过一些策略和优化来避免或减轻。

2.vue和react的区别

  1. 模板渲染方式不同:Vue 和React 使用不同的模板语法.Vue使用的是基于模板的语法,可以直观的看到模板中的 JSX 代码而 React 则是将模板中的语法封装在函数中,通过调用函数来渲染组件.
  2. 数据流:Vue 是响应式的数据双向绑定系统,支持组件间的数据双向流动.而 React 则采用单向数据流,即从父组件到子组件的数据传递,不支持子组件修改父组件的 数据.(两种组件间父子通信问题)
  3. 框架本质:Vue 是一个 MVVM框架,它的核心是一个响应式的数据模型,通过数据绑定和组件之间的通信实现视图和模型的分离.而 React 是一个前端组件化框架,他将 U 组件视为独立的模块并使用 props 和 state 来管理 (MVVM和react 中的 props 和state 问题)
  4. 渲染过程:Vue 在渲染过程中会跟踪每一个组件的依赖关系不需要重新渲染整个组件树.而React 在应用的状态被改变时,全部子组件都会重新渲染.
  5. 监听数据变化卖现原理:Vue 通过 getter 和 setter 以及一些函数的劫持能精确的知道数据变化.而 React 则是通过 diff 算法,将新的数据和老的数据作对比,然后重新渲染.(getter和 setter l以及 diff 问题)

综合来看,Vue 更注重视图和数据的双向绑定以及组件之间的数据流管理,而 React 更注重组件化和状态管理

3.什么是虚拟Dom

虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念,意为将目标所需的 UI 通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的DOM与之保持同步。具体来说,虚拟 DOM 是由一系列的 JavaScript 对象组成的树状结构,每个对象代表着一个DOM元素,包括元素的标签名、属性、子节点等信息。虚拟 DOM 中的每个节点都是一个 JavaScript 对象,它们可以轻松地被创建、更新和销毁,而不涉及到实际的DOM操作。

主要作用

虚拟 DOM 的主要作用是在数据发生变化时,通过与上一次渲染的虚拟 DOM 进行对比,找出发生变化的部分,并最小化地更新实际 DOM。这种方式可以减少实际 DOM 操作的次数,从而提高页面渲染的性能和效率。
总的来说,虚拟 DOM 是一种用 JavaScript 对象模拟真实 DOM 结构和状态的技术,它通过在内存中操作虚拟 DOM 树来减少实际 DOM 操作,从而提高页面的性能和用户体验。

4.diff算法

在 Vue 3 中,diff(差异比较)是指在进行虚拟 DOM 更新时,对比新旧虚拟 DOM 树的差异,然后只对实际发生变化的部分进行更新,以尽可能地减少对真实 DOM 的操作,提高页面的性能和效率。diff整体策略为:深度优先,同层比较。也就是说,比较只会在同层级进行, 不会跨层级比较;比较的过程中,循环从两边向中间收拢。

流程解析

Diff 算法的实现流程可以概括为以下几个步骤:

  1. 比较根节点: 首先,对比新旧虚拟 DOM 树的根节点,判断它们是否相同。

  2. 逐层对比子节点: 如果根节点相同,则逐层对比子节点。

    • 比较子节点类型:

      • 如果节点类型不同,则直接替换整个节点。
      • 如果节点类型相同,继续对比节点的属性和事件。
    • 对比子节点列表:

      • 通过双指针法对比新旧节点列表,查找相同位置的节点。
      • 如果节点相同,进行递归对比子节点。
      • 如果节点不同,根据情况执行插入、删除或移动节点的操作。
  3. 处理新增、删除和移动的节点:

    • 如果新节点列表中存在旧节点列表中没有的节点,执行新增操作。
    • 如果旧节点列表中存在新节点列表中没有的节点,执行删除操作。
    • 如果新旧节点列表中都存在相同的节点,但顺序不同,执行移动节点的操作。
  4. 更新节点属性和事件:

    • 如果节点相同但属性或事件发生了变化,更新节点的属性和事件。
  5. 递归对比子节点:

    • 如果节点类型相同且是容器节点(例如 div、ul 等),则递归对比子节点。

总的来说,Diff 算法的核心思想是Diff就是将新老虚拟DOM的不同点找到并生成一个补丁,并根据这个补丁生成更新操作,以最小化对实际 DOM 的操作,提高页面渲染的性能和效率。通过深度优先、同层比较的策略,Diff 算法能够高效地处理虚拟 DOM 树的更新,使得页面在数据变化时能够快速响应并更新对应的视图。

5.vue生命周期,以及作用

作用:在特定的时间点,执行某些特定的操作。
Vue生命周期一共分为四个阶段:

  • 初始化  =>   创建组件    =>  beforeCreate   created
  • 挂载   =>  渲染显示组件  =>    beforeMount   mouted
  • 更新   =>  修改了变量  =>   触发视图刷新   =>  beforeUpdate   updated
  • 销毁   =>  切换页面   =>  会把组件对象从内存删除   => beforeDestory  destoryed

详细介绍

  • beforeCreate:在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法

  • create:data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作

  • beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的

  • mounted:执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行

  • beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步

  • updated:页面显示的数据和data中的数据已经保持同步了,都是最新的

  • beforeDestory:Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁

  • destroyed: 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。 image.png 当页面第一次页面加载时会触发哪几个钩子函数?答:beforeCreate、created、beforeMount、mouted这几个钩子函数

父子组件生命周期顺序

  • 加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
  • 更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated
  • 销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

6.watch和computed的区别

  • computed:

    • 计算属性,依赖其他属性值,且值具备缓存的特性。只有它依赖的属性值发生改变,下一次获取的值才会重新计算。
    • 当一个属性受多个属性影响的时候就需要用到computed
    • 适用于数值计算,并且依赖于其他属性时。因为可以利用缓存特性,避免每次获取值,都需要重新计算。购物车商品结算的时候
  • watch:

    • 当一条数据影响多条数据的时候就需要用watch
    • 观察属性,监听属性值变动。每当属性值发生变化,都会执行相应的回调。

7.双向绑定原理

  • 实现一个监听器Observer: 对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()对属性都加上setter和getter。

  • 实现一个解析器Compile: 解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

  • 实现一个订阅者Watcher: Watcher订阅者是Observer和Complie之间通信的桥梁,主要的任务是订阅Observer中的属性值变化的消息,当收到属性值变化的消息时,触发解析器Complie中对应的更新函数。

  • 实现一个订阅器Dep: 订阅器采用发布订阅-订阅 设计模式,用来收集订阅者Watcher,对监听器Observer和订阅者Watcher进行统一管理。

总结

  1. 数据响应式是指通过数据驱动DOM视图的变化,是单向的过程,而双向数据绑定的数据和DOM是一个双向的关系。
  2. 数据响应式没有通过监听器来实现,而双向数据绑定通过监听器来监听组件属性的变化

8.Vue3.0 里为什么要用 Proxy API 替代 defineProperty API?

Object.defineProperty

定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

通过defineProperty 两个属性,getset

  • get:属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值

  • set:属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

小结

  • 检测不到对象属性的添加和删除
  • 数组API方法无法监听到
  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

proxy

Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了

ES6系列中,我们详细讲解过Proxy的使用,就不再述说了

总结

  • Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
  • Object.defineProperty只能遍历对象属性进行劫持
  • Proxy 不兼容IE,也没有 polyfilldefineProperty 能支持到IE9
  • Proxy可以直接监听数组的变化(pushshiftsplice
  • Proxy有多达13种拦截方法,不限于applyownKeysdeletePropertyhas等等,这是Object.defineProperty不具备的

正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外setdelete方法)

9.Vue2和Vue3的组件通信方式

方式 Vue2 Vue3
父传子 props props
子传父 $emit emits
父传子 $attrs attrs
子传父 $listeners 无(合并到 attrs方式)
父传子 provide/inject provide/inject
子组件访问父组件 $parent
父组件访问子组件 $children
父组件访问子组件 $ref expose&ref
兄弟传值 EventBus mitt

props:props是组件通信中最常用的通信方式之一。父组件通过v-bind传入,子组件通过props接收,(props中数据流是单项的,即子组件不可改变父组件传来的值 在组合式API中,如果想在子组件中用其它变量接收props的值时需要使用toRef将props中的属性转为响应式。)

emit: 子组件可以通过emit发布一个事件并传递一些参数,父组件通过v-on进行这个事件的监听

ttrs和listeners: 子组件使用$attrs可以获得父组件除了props传递的属性和特性绑定属性 (class和 style)之外的所有属性。

provide/inject

  • provide:是一个对象,或者是一个返回对象的函数。里面包含要给子孙后代属性
  • inject:一个字符串数组,或者是一个对象。获取父组件或更高层次的组件provide的值,既在任何后代组件都可以通过inject获得

parent/children

  • $parent: 子组件获取父组件Vue实例,可以获取父组件的属性方法等
  • $children: 父组件获取子组件Vue实例,是一个数组,是直接儿子的集- 合,但并不保证子组件的顺序

expose&ref:$refs可以直接获取元素属性,同时也可以直接获取子组件实例

EventBus/mitt

  • 兄弟组件通信可以通过一个事件中心EventBus实现,既新建一个Vue实例来进行事件的监听,触发和销毁。
  • 在Vue3中没有了EventBus兄弟组件通信,但是现在有了一个替代的方案mitt.js,原理还是 EventBus

Vuex或者Pinia: 组件还可以借助Vuex或者Pinia状态管理工具进行通信(但是组件之间的通信一般不建议这样做,因为这样就会出现组件不能复用的问题)。

10.Vue中的$nextTick有什么作用?底层如何实现?

nextTick:是vue内置的一个api,在页面渲染完成后执行回调函数中的逻辑。接受一个回调函数作为参数。nextTick 是异步操作,且属于微任务。  所以会比 类似于setTimeout这样的延迟函数 更快执行。

function myNextTick(fn) {
    let app = document.getElementById('app')

    // 使用MutationObserver方法监听dom
    var observerOptions = {
        childList: true, // 观察目标子节点的变化,是否有添加或者删除
        attributes: true, // 观察属性变动
        subtree: true, // 观察后代节点,默认为 false
      };
    // 要保证fn在dom更新完成后再调用
    // 创建一个DOM监听器    
    let observer = new MutationObserver((el,obs) => {
        //当被监听的DOM节点更新完成时,该回调会触发
        fn()
    })
    //将目标节点和callback绑定,并定义observerOptions来配置需要监听dom的哪些变化
    observer.observe(app,observerOptions)
}

11.keep-alive的作用

keep-alive 是缓存不活动的组件官方文档

  1. 把不活动的组件实例保存在内存中,而不是直接将其销毁
  2. 是一个抽象组件,不会被渲染到真实 DOM 中,也不会出现在父组件中

keep-alive 当作标签使用包惠组件和路由出口

生命周期actived 和deactived

keep-alive 之所以能缓存是因为两个钩子函数 actived 和deactived,当组件激活就会触发 actived,组件失活就会触发deactived

因为不加 keep-alive,组件不会被缓存,每次切换组件就会造成数据重新加载和页面的重新渲染,造成不必要的性能消耗(数据重新加载、页面重新渲染)两个属性

  • include(白名单)需要缓存的组件(传递一个包含多个字符串或正则表达式的数组)
  • exclude(黑名单)不需要缓存的组件(传递一个包含多个字符串或正则表达式的数组) 黑名单优先级比白名单高

12.为什么data属性是一个函数而不是一个对象

官方解析:Vue 实例的数据对象。Vue 会递归地把 data 的 property 转换为 getter/setter,从而让 data 的 property 能够响应数据变化。当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。

  1. 首先我们需要了解data两种不同的类型有什么区别:
  •  当我们组件中的data写成一个函数时,数据是以函数返回值形式定义的,这样每复用一次data,都会返回一份新的data,拥有自己的作用域,不会产生数据污染。
  •  当我们组件中的data写成一个对象时,对象是引用数据类型,它就会共用一个内存地址,在多次使用该组件时,改变其中一个组件的值会影响全部使用该组件的值。

 2.理解组件中的 data 必须是一个函数: 

  •  在vue中一个组件可能会被其他的组件引用,为了防止多个组件实例对象之间共用一个data,产生数据污染。将data定义成一个函数,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响initData时会将其作为工厂函数都会返回全新data对象。