超全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双向数据绑定的源码解析Vue2手写源码---响应式数据的变化_想学好前端的小宝的博客-CSDN博客

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 来实现数据响应式的。(简单源码讲解:Vue2手写源码---响应式数据的变化_想学好前端的小宝的博客-CSDN博客)在我们访问旧属性的时候,都会触发 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 会开启一个异步更新队列,视图需要等待队列中所有数据变化完成后,在统一进行更新。(简单源码解析:Vue2手写源码---响应式数据的变化_想学好前端的小宝的博客-CSDN博客

Mixin

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

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

注意事项

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

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

优点:增加代码的复用性

什么是虚拟DOM

它是对真实DOM的 抽象,就是使用 js 对象作为基础的树,用对象的属性来描述节点,然后通过一系列的属性将这棵树渲染到页面上。它通过事务处理机制,将多次DOM 修改的结果一次性更新到页面上,从而可以减少页面渲染的次数,减少页面重排、重绘的次数,可以提高渲染性能。

为啥使用虚拟DOM

虚拟DOM就是为了解决浏览器性能问题而被设计出来的。若一次操作DOM中有十次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这十次更新的diff内容保存到本地一个js中,最终将这个js对象一次性attach 到DOM树上,在进行后续操作。避免大量无谓的计算量。且 虚拟DOM 本质上是JavaScript的对象,它可以很⽅便的跨平台操作,⽐如服务端渲染、uniapp等。

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,渲染到页面上

为啥不建议用index作为key

如果仅用于展示的话,使用index作为 key 是没有问题的。但要是存在逆序添加、逆序删除等破坏顺序的操作,就会产生错误的更新。所以建议还是选择每条数据的唯一标识作为 key。

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

Vuex原理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态。每个vuex应用的核心就是一个仓库(store)

  • state:相当于data; 可以通过$store.state.;

  • mutations: 状态改变操作⽅法。是Vuex修改state的唯⼀推荐⽅法,其他修改⽅式在严格模式下 将会报错。该⽅法只能进⾏同步操作,且⽅法名只能全局唯⼀。

  • actions: 异步,通过这个在组键里面调用store.dispatch('increment'),不能直接修改state中的值,只能通过 mutations 进行 修改

  • getters:Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

  • modules:管理 vuex 模块

总结:Vuex 实现了一个单向数据流,在state 中存放数据,然后必须通过 mutations 进行数据的修改,但是只能进行同步的修改,同时呢会提供订阅者模式供外部插件调用获取 state 中的数据更新。然后异步操作需要 通过 actions 进行操作,但是 actions 不能直接修改 state ,还需要通过 mutations 来修改数据。最后就是通过 state 的变化, 渲染到视图上。

mutation 和 action 的区别

  • mutation 都是用来修改 state 的,且是修改 state 的唯一途径。 action 是写一些业务代码、异步请求。

  • mutation 必须是同步执行的。 action :可以异步,但是不能直接操作 state

  • 在视图更新的时候, 会先触发 actions, 然后 actions 再触发 mutation

Vuex属性

  • state :基本数据(数据源存放地)

  • getters :从基本数据派⽣出来的数据

  • mutations:提交更改数据的⽅法,同步

  • actions :像⼀个装饰器,包裹mutations,使之可以异步。

  • modules :模块化Vuex

Vuex 和 localStorage 区别

  • vuex 存储在内存中,localStorage以文件的方式存储在本地,而且只能存储字符串类型的数据,存储对象需要 JSON 的 stringify 和 parse 方法进行处理。读取内存比读取硬盘速度要快。

  • vuex 是专为 Vue.js 应用程序开发的状态管理模式。主要适用于 组件之间的传值。

  • localStroage 是本地存储,是将数据存储到浏览器的方法,一般在跨页面传递数据的时候使用。

  • vuex 能让数据响应式变化,localStroage 不能。

  • 刷新页面时,vuex存储的值会丢失,localStroage不会。

为什么mutation 不能做异步操作

vuex 所有状态更新的唯一途径都是 mutation ,在 mutation 执行完成后,都会得到一个新的状态变更,这样 devtools 就可以打个快照存下来,然后就可以实现 time-travel 了。 如果 mutation 支持异步操作,就没有办法直到状态是什么时候更新的了,就无法进行状态的跟踪,调试就会变得困难。

Vue3

Vue3.0性能提升主要是通过哪几个方面体现的

  • 编译阶段:

    • diff算法优化:在会发生变化的地方增添一个 静态标记 ,下次发生变化的时候就能直接找该地方进行比较。

    • 静态提升:对不参与更新的元素,做 静态提升 ,只会被创建一次,在渲染的时候直接复用。

    • 事件监听缓存:默认情况下绑定事件的行为是一个动态绑定,每次都会去追踪它的变化。开启缓存后,下次进行diff算法的时候就可以直接使用

    • SSR优化:当静态内容大到一定量级的时候,就会使用 createStaticVnode 生成一个 static node,这些静态内容会直接被 innerHTML ,就不需要创建对象,然后根据对象渲染了。

  • 源码体积:vue3源码整体体积,相比 vue2 来说,变小了。还 Tree shanking 了任何函数,仅在用到的时候才打包,没用到的模块都被摇掉了,打包的整体体积变小了。

  • 响应式系统:vue2 使用 defineProperty 来劫持整个对象,然后进行深度遍历所有属性,给每个属性都添加上 getter 和 setter ,实现响应式。 vue3 采用了 proxy 重写了响应式系统。因为 proxy 可以对整个对象进行监听,所以不用深度遍历。

Object.defineProperty 与 Proxy 区别

  • Object.defineProperty 必须要遍历对象每一个属性进行劫持。 proxy 可以劫持整个对象,然后返回一个新的对象,可以直接操作新的对象达到响应式的目的。

  • proxy 可以监听数组的变化。 Object.defineProperty 不行。

  • Object.defineProperty 必须深层遍历嵌套的对象, proxy 不用。

Vue3 的 Composition API 和 Vue2 的 Options API 有什么不同

  • Options API就是选项 API ,通过定义 data、methods、computed等属性与方法,来共同处理页面的逻辑。所以当组件变得复杂时,就会导致对应属性的列表也会变长,这就可能导致组件难以阅读或理解。

  • 而 Composition API ,组件会根据逻辑功能来进行组织,一个功能所定义的所有 API 都会放在一起。这样就算项目功能很多,也能快速找到这个功能所用到的所有 API。

  • 在 Options API 中,在处理单个逻辑点的时候,可能需要不断在相关的代码块进行跳转。在composition API 中,单个逻辑点相关代码都会放在同一个函数中,需要修改的时候,在这个函数里就可以了。

  • 在 Options API 混入大量 mixins 的时候,很可能会发生 命名冲突、数据来源不清晰等问题。

  • composition API 不使用 this,就不会出现 this 指向不明的情况

参考文档:web前端面试 - 面试官系列

Docs

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值