当前位置 : 主页 > 编程语言 > 其它开发 >

超全vue面试题整理

来源:互联网 收集:自由互联 发布时间:2022-06-03
整理了现阶段遇到的和做过的vue面试题,分享出来,自我感觉挺全面的哦,希望给大家一些帮助,一起进步! vue基础 vue是什么 是一个动态构建用户界面的渐进式JavaScript框架。用来创建
整理了现阶段遇到的和做过的vue面试题,分享出来,自我感觉挺全面的哦,希望给大家一些帮助,一起进步! vue基础 vue是什么

是一个动态构建用户界面的渐进式JavaScript框架。用来创建单页应用的 web 应用框架。

优势:Vue 是一个轻量级框架,只关注图层,是一个构建数据的视图集合,大小只有十几KB。vue简单易学,而且通过 MVVM 思想实现了数据的双向绑定,让开发者不用再操作 dom 对象,有更多时间去思考业务逻辑。而且 vue是组件化的,通过组件,将单页应用中的各个模块拆分成单独的组件,提高了复用性。在更新视图的时候,还提供了虚拟节点,将新旧虚拟节点进行对比,然后更新视图。

 

Vue与React
  • 相同点

    • 都有组件化思想

    • 都是数据驱动视图

    • 都支持服务端渲染

    • 都有虚拟DOM

  • 不同点

    • 数据流向不同。前者是双向数据流,后者是单向数据流。

    • 数据变化的实现原理不同。前者使用的是可变的数据,后者使用的是不可变的数据。

    • diff算法不同。前者使用双指针,边对比,边更新 DOM。后者主要使用 diff 队列保存需要更新的一些 DOM,然后得到patch 树,在统一进行批量更新 DOM。

 

MVC与MVVM

MVC

  • M(模型层):处理应用程序数据逻辑的部分(存数据、取数据)

  • V(视图层):处理数据显示的部分(页面展示、Dom操作)

  • C(控制层):处理用户交互的部分(控制模型层与视图层的关联)

MVVM

  • M(模型层):处理数据与业务逻辑的部分

  • V(视图层):负责数据展示的部分

  • VM(视图模型层):负责从模型层监听数据的变化从而更新视图层,用来处理用户交互操作的部分

两者最大区别:MVVM 实现了模型层与视图层的自动同步,当数据发生变化时,不用手动操作DOM元素来改变视图层的显示,而是改变了数据对应的视图层自动更新

 

双向数据绑定(可以看我之前写的vue2双向数据绑定的源码解析https://blog.csdn.net/weixin_51642358/article/details/124878452

vue 采用的是数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() (vue3 通过 Proxy进行劫持)来劫持各个属性的 gettersetter 方法,然后再数据变动的时候,发送消息给订阅者,触发相应的监听回调。主要步骤:

  • 先使用 数据监听器Observe 对数据对象上的所有属性都添加上 getter 和 setter 方法。这样如果数据有发生变动的话,就能拿到最新值。

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

  • 创建一个 订阅者 watcher ,在自身实例化时,往属性订阅器dep)中添加自己,然后一旦属性变动,就会调用 dep.notice() 方法通知 对应的 watcher 调用 update() 方法进行更新,从而更新视图。

  • MVVM 作为数据绑定的入口,整合了 Observe、Compile、Watcher三者。通过 Observe 监听 模型层的数据变化,通过 Compile 来编译解析 模板指令,最后通过 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,可以收到属性的变化通知并执行相应的函数,从而达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model 变更的双向绑定效果。

 

使⽤ Object.defineProperty() 来进⾏数据劫持有什么缺点

在对⼀些属性进⾏操作时,使⽤这种⽅法⽆法拦截,⽐如通过下标⽅式修改数组数据或者给对象新增属 性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来 说,对于数组⽽⾔,⼤部分操作都是拦截不到的,只是 Vue 内部通过重写函数的⽅式解决了这个问题。

 

Vue3.0 通过使⽤ Proxy 对对象进⾏代理,从⽽实现数据劫持。它可以完美的监听到任何方式的数据改变,唯⼀的缺点是兼容性的问题,因为 Proxy是 ES6 的语法。

 

computed 与 watch区别
  • 前者支持缓存,只有依赖的数据发生变化时,才会重新计算。后者不支持缓存,只要数据发生变化,就会触发相应操作。

  • 前者不支持异步,有异步就无法监听数据变化。后者支持异步

  • 前者一个属性由另外的属性计算而来的话,这个属性也依赖与另外的属性。

  • 前者的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。

  • 后者监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会出大其他操作,函数有两个的参数:

    • immediate:组件加载立即触发回调函数

    • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。

  • 当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch

 

插槽(slot)

是子组件的一个模板标签元素。

  • 默认插槽:在 slot 没有指定 name 属性值时候,一个默认的显示插槽。

  • 具名插槽:带有 name 属性的 slot, 一个组件可以有多个具名插槽。

  • 作用域插槽:默认插槽、具名插槽的⼀个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不 同点是在⼦组件渲染作⽤域插槽时,可以将⼦组件内部的数据传递给⽗组件,让⽗组件根据⼦组件 的传递过来的数据决定如何渲染该插

 

常见的事件修饰符
  • .stop: 防止冒泡

  • .prevent: 阻止默认行为(链接跳转)

  • .once: 只会触发一次

  • .capture:进行事件捕捉(由外到内)

  • .self:只会触发自己范围内的事件,不包含子元素。

 

v-if和v-show的区别
  • 控制手段不同。前者动态的向DOM树内增加或者删除DOM元素来控制元素的显示与隐藏。后者通过 css 中的 display属性来控制元素的显示与隐藏。

  • 前者支持<template>标签。后者不支持。

  • 编译条件不同。前者只有当第一次初始值为真的话,才会开始编译渲染。后者无论初始值,都会进行编译。

  • 运行场景不同。前者适用于条件很少改变的情况。后者使用与频繁切换的情况。

  • 开销不同。前者有更高的切换开销。后者有更高的初始渲染开销。

 

Vue2给对象添加新属性,界面不刷新

Vue2是通过 Object.defineProperty 来实现数据响应式的。(简单源码讲解:https://blog.csdn.net/weixin_51642358/article/details/124878452)在我们访问旧属性的时候,都会触发 gettersetter 方法,进而进行页面的刷新。但在我们添加新属性的时候,没有通过 Object.defineProperty 设置成响应式数据,所以也就无法触发事件属性的拦截,也就无法进行页面的刷新了。

 

Vue3是用 proxy 进行数据响应式的,直接动态添加新属性还是可以实现数据响应式。

 

解决方案:

  • Vue.set(target, propertyName/index, value ) 通过Vue.set() 向响应式对象中添加一个 property ,并确保这个新的 property 是响应式的,而且还会触发视图的更新。

  • Object.assign() 直接使用这个方法添加到对象的新属性还是不会触发更新。 需要创建一个新对象,然后合并原对象和混入对象的属性

 

v-model实现原理

v-model实际上是一个语法糖,它的实现主要包括属性绑定事件监听两部分

  • 当作用于表单元素上

    • 动态绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动态把 message设置为当前DOM的value值

  • 作用在组件上

    • 在⾃定义组件中,v-model 默认会利⽤名为 value 的 prop和名为 input 的事件

    • 本质是一个父子组件通信的语法糖,通过prop和$.emit实现。因此父组件 v-model 语法糖本质上可以修改为:

      <child :value="message"@input="function(e){message = e}"></child>

 

data为什么是⼀个函数⽽不是对象
  • 在根实例对象中,data可以是一个函数也可以是一个对象,因为根实例是单例的,不会造成数据污染

  • 在组件实例对象中,data必须是一个函数,防止多个组件实例对象之间共用一个data,会产生数据污染。如果data是函数的话,initData 时会将其作为工厂函数都会返回全新的 data 对象。

 

nextTick

Vue 在更新 DOM 的时候是异步更新的。当数据发生变化的时候,nextTick 会开启一个异步更新队列,视图需要等待队列中所有数据变化完成后,在统一进行更新。(简单源码解析:https://blog.csdn.net/weixin_51642358/article/details/124878452

 

Mixin

Mixin是面向对象程序设计语言中的类,通常作为功能模块使用。其他类可以直接访问Mixin类的方法而不用称为其子类。本质上就是一个 JS 对象,包含我们组件中任意功能选项,如 data、methods等。

我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来。

注意事项

  • 当组件存在与 Mixin 对象相同的选项时候,合并时组件选项会覆盖 Mixin 的选项

  • 如果生命周期钩子有相同选项时,会合并成一个数组,然后先执行 mixin 的钩子,在执行组件的钩子

优点:增加代码的复用性

 

Vue 中 key 的原理

简而言之 ,key 就是每个虚拟DOM节点的 唯一ID,也是 diff 的一种优化策略,可以根据 key ,更准确、更快的找到对应的 虚拟 DOM 节点。

在虚拟节点中,key就是虚拟 DOM 对象的标识,当数据变化时,Vue 就会根据新数据生成一个新的虚拟DOM

,随后 Vue 会根据这个 key 进行 新旧虚拟DOM 的差异对比。

对比规则

  • 旧虚拟DOM 找到和新虚拟DOM相同的 key

    • 若内容没有发生改变,直接使用之前的真实DOM

    • 若内容发生变化,则生成一个新的真实DOM,然后替换掉页面中的真实DOM

  • 旧虚拟DOM 没找到和新虚拟DOM相同的 key

    • 直接创建新的真实DOM,渲染到页面上

 

diff算法

diff 算法是一种通过同层的树节点进行比较的高效算法。

特点:

  • 比较只会在同层级进行,不会跨层级比较。

  • 比较的过程中,循环从两边向中间进行比较。

当数据发生改变时,set方法会调用 Dep.notify 通知所有 订阅者 watcher,订阅者就会调用 patch 给真实的 DOM 打补丁,更新响应视图。

patch 函数前两个参数位为 oldVnodeVnode ,主要做了四个判断:

  • 没有新节点: 直接触发旧节点的 destory 钩子

  • 没有旧节点: 说明是页面刚初始化,不需要比较,直接调用 createElm

  • 通过 sameVnode 判断新旧节点是否一样

    • 新旧节点不一样 :直接创建新节点,删除旧节点,不在进行深度比较

    • 新旧节点一样 :直接调用 patchVnode 去处理这两个节点

      • 找到对应的真实dom,称为el

      • 如果都有文本节点且不相等,将el文本节点设置为Vnode的文本节点

      • 如果oldVnode有子节点而VNode没有,则删除el子节点

      • 如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el

      • 如果两者都有子节点,则执行updateChildren函数比较子节点

 

常见的Vue性能优化
  • 路由懒加载

  • keep-alive 缓存页面

  • 使用 v-show 复用 DOM

  • v-for 遍历避免同时使用 v-if

  • 长列表性能优化:单纯的展示,不做改变,就不需要做响应化。

  • 图片懒加载

  • 第三方组件按需引入

  • SSR

 

keep-alive

在动态组件切换的过程中,组件的实例都是重新创建的。keep-alive 包裹动态组件时,会缓存不活动的组件实例,就是缓存组件内部状态,避免重新渲染。

  • 三个属性

    • include:字符串或正则表达式,只有名称匹配的组件才会被缓存

    • exclude:字符串或正则表达式,任何名称匹配的组件都不会被缓存

    • max:数字,最多可以缓存多少组件实例

  • 优点:

    • 较少的CPU和内存的使⽤(由于同时打开的连接的减少了);

    • 降低拥塞控制 (TCP连接减少了);

    • 减少了后续请求的延迟(⽆需再进⾏握⼿);

    • 报告错误⽆需关闭TCP连接

  • 缺点

    • 长时间的 TCP 连接,会导致系统资源无效占用,浪费系统资源。

 

 

生命周期

 

说⼀下Vue的⽣命周期

Vue实例从开始创建、初始化数据、编译模板、挂载DOM --> 渲染、更新 -->渲染、卸载 称为 Vue的一个完整的生命周期。

  • beforeCreate(创建前)

    数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据

  • created(创建后) :

    实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。常用于异步数据获取

  • beforeMount(挂载前):

    在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置∶ 编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。

  • mounted(挂载后):

    el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。

  • beforeUpdate(更新前):

    响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染

  • updated(更新后) :

    在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

  • beforeDestroy(销毁前):

    实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。可用于一些定时器或订阅的取消。

  • destroyed(销毁后):

    实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

 

created和mounted的区别
  • created:在模板渲染成html前调⽤,即通常初始化某些属性值,然后再渲染成视图。

  • mounted:在模板渲染成html后调⽤,通常是初始化⻚⾯完成后,再对html的dom节点进⾏⼀些需要的操作。

 

在 created 请求异步数据的优点
  • 能更快获取到服务端数据,减少⻚⾯加载时间,⽤户体验更好;

  • SSR不⽀持 beforeMount 、mounted 钩⼦函数,放在 created 中有助于⼀致性;

 

 

组件通信

 

父子组件通信
  • props: 父组件向子组件传递数据

    • 子组件设置props属性,定义接收父组件传递过来的参数

    • 父组件在使用子组件标签中通过字面量来传递值

    //Father.vue
    <Children name="jack" age=18/>

    //Children.vue
    props:{  
       // 字符串形式  
    name:String // 接收的类型参数  
       // 对象形式  
       age:{    
           type:Number, // 接收的类型为数值  
           defaule:18,  // 默认值为18  
          require:true // age属性必须传递  
      }  
    }
  • $emit: 子组件向父组件传递数据

    • 子组件通过$emit触发自定义事件,$emit第二个参数为传递的数值

    • 父组件绑定监听器获取到子组件传递过来的参数

    //Chilfen.vue
    this.$emit('add', good)

    //Father.vue
    <Children @add="cartAdd($event)" />
  • ref:

    • 父组件在使用子组件的时候设置ref

    • 父组件通过设置子组件ref来获取数据

    //Father.vue
    <Children ref="foo" />  

    this.$refs.foo  // 获取子组件实例,通过子组件实例我们就能拿到对应的数据  

     

兄弟组件传参
  • EventBus

    • 创建一个中央事件总线EventBus

    • 兄弟组件通过$emit触发自定义事件,$emit第二个参数为传递的数值

    • 另一个兄弟组件通过$on监听自定义事件

    Bus.js

    // 创建一个中央时间总线类  
    class Bus {  
     constructor() {  
       this.callbacks = {};   // 存放事件的名字  
    }  
     $on(name, fn) {  
       this.callbacks[name] = this.callbacks[name] || [];  
       this.callbacks[name].push(fn);  
    }  
     $emit(name, args) {  
       if (this.callbacks[name]) {  
         this.callbacks[name].forEach((cb) => cb(args));  
      }  
    }  
    }  
     
    // main.js  
    Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  
    // 另一种方式  
    Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能
    //Children1.vue
    this.$bus.$emit('foo')  

    //Children2.vue
    this.$bus.$on('foo', this.handle)  
  • $parent 或$ root

    • 通过共同祖辈$parent或者$root搭建通信桥连

    //Children1.vue
    this.$parent.on('add',this.add)

    //Children2.vue
    this.$parent.emit('add')

 

祖孙与后代组件之间的通信
  • $attrs 与$ listeners

    • 设置批量向下传属性$attrs$listeners

    • 包含了父级作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。

    • 可以通过 v-bind="$attrs" 传⼊内部组件

    // child:并未在props中声明foo  
    <p>{{$attrs.foo}}</p>  
     
    // parent  
    <HelloWorld foo="foo"/>
    // 给Grandson隔代传值,communication/index.vue  
    <Child2 msg="chuanzhi" @some-event="onSomeEvent"></Child2>

    // Child2做展开
    <Grandson v-bind="$attrs" v-on="$listeners"></Grandson>

    // Grandson使⽤
    <div @click="$emit('some-event', 'msg from grandson')">
    {{msg}}
    </div>
  • provide 与 inject

    • 在祖先组件定义provide属性,返回传递的值

    • 在后代组件通过inject接收组件传递过来的值

    祖先组件

    provide(){  
    return {
    foo:'foo'
    }
    }

    后代组件

    inject:['foo'] // 获取到祖先组件传递过来的值 

 

非关系组件间之间的通信
  • vuex : 存储共享变量的容器

    • state用来存放共享变量的地方

    • getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值

    • mutations用来存放修改state的方法。

    • actions也是用来存放修改state的方法,不过action是在mutations的基础上进行,不能直接进行修改。常用来做一些异步操作

 

 

路由

 

路由的 hash 和 history 模式
  • hash模式: 开发中默认的模式, url中带着 #

    • 原理: hash模式的主要原理就是onhashchange()事件

    window.onhashchange = function(event){
    console.log(event.oldURL, event.newURL);
    let hash = location.hash.slice(1);
    }

使⽤onhashchange()事件的,在⻚⾯的hash值发⽣变化时,⽆需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现⻚⾯的前进和后退。虽然是没有请求后端服务器,但是⻚⾯的hash值和对应的URL关联起来了。

  • history模式:history 模式的 URL中没有 #,他使用的是传统的路由分发模式,在用户输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。

    • 特点: history 模式的 URL中没有 #,会好看一点。history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。

    • API: history api可以分为两大部分,切换历史状态和修改历史状态:

      • 修改历史状态:包括了 HTML5 History Interface 中新增的 pushState() 和replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。

      • 切换历史状态: 包括forward()、back()、go()三个方法,对应浏览器的前进,后退,跳转操作

 

  • 对比:

    • 调用history.pushState()相比于直接修改hash,存在以下优势:

    • pushState()设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,因此只能设置与当前URL同文档的URL

    • pushState()设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中

    • pushState()通过stateObject参数可以添加任意类型的数据到记录中;而hash只可添加短字符串

    • 虽然history模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。 如果想要切换到history模式,前后端都要进行配置(后端配置比较复杂)。

 

$route 和$router 的区别
  • $route 是用来获取路由信息的。$ route是一个跳转的路由对象(路由信息对象)。

//常用的属性
$route.path 字符串,相当于当前页面的绝对路径。

$route.params 对象,包含路由中的动态片段和全匹配片段的键值对,不会拼接到路由的url后面

$route.query 对象,包含路由中查询参数的键值对。会拼接到路由url后面

$route.router 路由规则所属的路由器

$route.name 当前路由的名字,如果没有使用具体路径,则名字为空
  • $router 是用来操作路由的。 $router是VueRouter的一个实例,他包含了所有的路由,包括路由的跳转方法,钩子函数等,也包含一些子对象(例如history)

//常用的方法
this.$router.push()

this.$router.replace()

this.$router.go()

this.$router.forward()

this.$router.back()

 

路由跳转和链接跳转区别
  • 使用链接跳转比较简单,但是刷新了页面。

  • 使用路由跳转,不会刷新页面

 

params和query区别
  • query使用 path 引入,params使用 name 引入

  • query会在浏览器地址栏中显示参数,params则不显示

  • query 刷新不会丢失里面的数据,params 刷新会丢失里面的数据

 

vue导航守卫
  • 全局守卫:router.beforeEach

    • 任何路由跳转到另外一个路由的时候都会触发,而且是跳转前触发的。

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
// ...
})
  • to :要去的路由对象

  • from:要离开的路由对象

  • next:存放方法,判断是否能够跳转成功。

    • next() 能跳转成功

    • next(false) :跳转失败,中断跳转。

    • next({path:“/login”}) :跳转失败。跳转到指定的路径。

 

vuex  
上一篇:切面操作:实现简单版的中间件
下一篇:没有了
网友评论