Vue面试真题演练

不去面试看了千遍间隔太久也宛如一遍,面试前看一遍宛如千遍,临阵磨枪不快也光,赶紧冲,收藏+关注每天睡前看一遍催眠又深刻,梦里梦外面试不迷路

1.v-show和v-if的区别

  • v-show:通过CSS display控制显示和隐藏
  • v-if:组价真正的渲染和销毁,而不是显示和隐藏
  • 频繁切换显示状态时候我们就用v-show,否则就用v-if

2.为何在v-for中用key

  • 必须用key,且不能是index和random
  • diff算法中通过tag和key来判断,是否是sameNode
  • 减少渲染次数,提升渲染性能

3.描述Vue组件的生命周期(父子组件)

  • 挂载
    • beforeCreate
    • created
    • beforeMount
    • mounted
  • 更新
    • beforeUpdate
    • updated
  • 销毁
    • beforeDestroy
    • Destroyed
钩子函数 过程
beforeCreate 在实例初始化之后, 数据观测和事件配置之前被调用此时data和methods以及页面的DOM结构都没有初始化什么都做不了
created 在实例创建完成后被立即调用此时data 和 methods已经可以使用但是页面还没有渲染出来
beforeMount 在挂载开始之前被调用此时页面上还看不到真实数据只是一个模板页面而已
mouted el被新创建的vm.$el替换, 并挂载到实例上去之后调用该钩子。数据已经真实渲染到页面上在这个钩子函数里面我们可以使用一些第三方的插件
beforeUpdate 数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的
update 由于数据更改导致的虚拟DOM重新渲染和打补丁, 在这之后会调用该钩子。页面上数据已经替换成最新的
beforeDestroy 实例销毁之前调用
destroyed 实例销毁后调用

4.vue组件如何通讯(常见)

  • 父子组价props和this.$emit
  • 自定义事件event. <math xmlns="http://www.w3.org/1998/Math/MathML"> n o 、 e v e n t . no、event. </math>no、event.off、event.$emit
  • vuex
  • 更多请看:vue2.x的10种组件间通信方式

5.描述组件渲染和更新的过程

  • 初次渲染的过程
    • 解析模板为render函数
    • 触发响应式,监听data属性getter setter
    • 执行render函数,生成vnode,patch(elem,vnode)
    • 执行render会触发getter
  • 更新过程
    • 修改data,触发setter
    • 重新执行render函数,生成newVnode
    • patch(vnode,newVnode),diff算法会算差异

6.双向数据绑定v-model的实现原理

3个步骤,实现数据的双向绑定:

vue的双向绑定实现原理juejin.cn/post/684490...

vue 双向数据绑定实现原理juejin.cn/post/684490...

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

vue是采用数据劫持结合发布者-订阅者模式的方式,通过**Object.defineProperty()**来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。

v-model原理其实就是给input事件绑定oninput事件 就会立刻调用底层对象对应的setter方法 改变data里的属性的值 从而实现双向数据绑定

vue单项数据绑定原理

单项绑定过程:变量变了,由set发通知给watcher,watcher告知虚拟DOM树,叫它该比较了,我这有值变了,于是生成新的dom树进行一个比较,然后逐级分类比较,比较出哪个元素发生变化就把这个元素更新到页面,这就是单项数据绑定原理。

vue双向数据绑定原理

7.对MVVM的理解

8.computed有什么特点

  • computed有缓存,data不变则不会重新计算

  • 提高性能

  • 计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题

  • 计算属性是基于它们的响应式依赖进行缓存的

  • 在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示

    • 比如我们有firstNamelastName两个变量,我们需要显示完整的名称。
    • 但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}} {{lastName}}

methods和computed区别

  • methods里面的数据不管发没发生变化, 只要调用了都会执行函数(有的时候数据没发生变化我们不希望调用函数)
  • computed计算属性会进行缓存, 如果数据没发生变化,函数只会被调用一次(数据发生变化才会调用函数)

总结:

  • methods不管数据发没发生变化都会调用函数
  • computed只有在依赖数据发生变化时才回调函数

2.侦听器 watch

侦听器的应用场景:数据变化时执行异步开销较大的操作 (eg:我们可以使用watch来进行路由的监听)

注意: watch 中的属性,一定是data 中 已经存在的数据

watch例子🌰codesandbox.io/s/vuebaseus...

9.为何组件data必须是一个函数

我们需要先复习下原型链的知识,其实这个问题取决于 js ,而并非是 vue 。

因为根本上.vue文件被编译出来是个class是一个类,每个地方使用组价的时候相当于对class实例化,实例化时候执行这个data()

基于此,我们来看看这个问题:

ini 复制代码
function Component(){
 
}
Component.prototype.data = {
    name:'jack',
    age:22,
}
var componentA = new Component();
var componentB = new Component();
componentA.data.age=55;
console.log(componentA,componentB)
复制代码

此时,componentA 和 componentB data之间指向了同一个内存地址,age 都变成了 55, 导致了问题!

接下来很好解释为什么 vue 组件需要 function 了:

javascript 复制代码
function Component(){
 this.data = this.data()
}
Component.prototype.data = function (){
    return {
    name:'jack',
    age:22,
}
}
var componentA = new Component();
var componentB = new Component();
componentA.data.age=55;
console.log(componentA,componentB)
复制代码

此时,componentA 和 componentB data之间相互独立, age 分别是 55 和 22 ,没有问题!

这样每一个实例的data属性都是独立的,不会相互影响了。所以,你现在知道为什么vue组件的data必须是函数了吧。这都是因为js本身的特性带来的,跟vue本身设计无关。其实vue不应该把这个方法名取为data(),应该叫setData或其他更容易立即的方法名。

10.ajax请求应该放在哪个生命周期

  • mounted
  • js是单线程,ajax异步获取数据
  • 放在mounted之前没有用,只会让逻辑更加混乱

11.如何将组件所有props传递给子组件

  • $props

12.如何自己实现v-model

vue 复制代码
<template>
    <!-- 例如:vue 颜色选择 -->
    <input type="text"
        :value="text1"
        @input="$emit('change1', $event.target.value)"
    >
    <!--
        1. 上面的 input 使用了 :value 而不是 v-model
        2. 上面的 change1 和 model.event1 要对应起来
        3. text1 属性对应起来
    -->
</template>

<script>
export default {
    model: {
        prop: 'text1', // 对应 props text1
        event: 'change1'
    },
    props: {
        text1: String,
        default() {
            return ''
        }
    }
}
</script>

13.多个组件有相同的逻辑,如何抽离?mixin

mixin

mixin缺点:变量来源不明确(隐式传入),不利于阅读,使代码变得难以维护;多个mixins的生命周期会融合到一起运行,但是同名属性、同名方法无法融合,可能会导致冲突;mixins和组件可能出现多对多的关系,复杂度较高(即一个组件可以引用多个mixins,一个mixins也可以被多个组件引用)。

Vue2/Vue3中的代码逻辑复用对比(mixins、自定义hook)juejin.cn/post/694978...

eg:

vue 复制代码
<div v-if="editAuthJudge(pageDetails)" class="canvas-config-item" @click="showDebugModal">
  <span class="item-label"><i class="mtdicon-monitor"></i>本地调试</span>      
</div>
<div v-if="actionsMap['revoke'] && editAuthJudge(pageDetails)" @click="handleRevokeConfig">
  <mtd-tooltip content="撤销修改" placement="top">
    <span class="item-label"><i class="mtdicon-undo-o"></i>撤销</span>
  </mtd-tooltip>
</div>
<div v-if="actionsMap['revoke'] && editAuthJudge(pageDetails)" :class="['canvas-config-item', sdk.activeVersionIndex === 0 ? 'disabled' : '']" @click="handleRecoveryConfig">
  <mtd-tooltip content="恢复修改" placement="top">
    <span class="item-label"><i class="mtdicon-redo"></i>恢复</span>
  </mtd-tooltip>
</div>
import editAuthJudge from "../components/mixins/editAuthJudge";
export default {
  name: 'CanvasConfigPanel',
  mixins: [editAuthJudge],
  inject: {
    zero: {
      default: () => ({})
    }
  },
  props: {
    pageDetails: {
        type: Object,
        default: () => {}
    },
    actions: {
      type: Array,
      default: () => {
        return ['device', 'scale', 'code', 'revoke', 'preview', 'clear', 'import', 'export', 'showLine', 'hideLine']
      }
    }
  },

14.何时要是用异步组件?

  • 道理和webpack的按需加载是一样的
  • 加载大组件
  • 路由异步加载

15.何时需要是用keep-alive?

  • 缓存组件,不需要重复渲染
  • 如多个静态tab页的切换
  • 优化性能

16.何时需要是用beforeDestory?

  • 解除自定义事件event.$off,否则容易造成内存泄露
  • 清除定时器
  • 解绑自定义的DOM事件(addEventLisenner),如window scroll等

17.什么是作用域插槽

ScopedSlotDemo

xml 复制代码
<template>
    <a :href="url">
        <slot :slotData="website">
            {{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
        </slot>
    </a>
</template>

<script>
export default {
    props: ['url'],
    data() {
        return {
            website: {
                url: 'http://wangEditor.com/',
                title: 'wangEditor',
                subTitle: '轻量级富文本编辑器'
            }
        }
    }
}
</script>
javascript 复制代码
<ScopedSlotDemo :url="website.url">
  <template v-slot="slotProps">
  	{{slotProps.slotData.title}}
  </template>
</ScopedSlotDemo>
import ScopedSlotDemo from './ScopedSlotDemo'

18.Vuex中action和mutation有何区别

  • action中处理异步,mutation不可以处理异步
  • mutation做原子操作(每次做一个)
  • action可以整合多个mutation

19.Vue-router常用的路由模式

  • hash
  • H5 history(需要服务端支持)
  • 原理
  • 两者比较

20.如何配置Vue-router异步加载

javascript 复制代码
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [{ path: '/foo', component: Foo }]
})

21.请用vnode描述一个DOM结构

22.监听data变化的核心API是什么?

核心API------Object.defineProperty(obj, prop, descriptor)

  • 监听对象、监听数组
  • 复杂对象、深度监听

Object.defineProperty缺点:

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增属性/删除属性(Vue.set Vue.delete)
  • 无法原生监听数组,需要特殊处理

23.Vue如何监听数组变化?

  • Object.defineProperty不能监听数组变化
  • 重新定义原型,重写push pop等方法,实现监听
  • Proxy可以原生支持数组变化

24.请描述响应式原理

监听data变化

组件渲染和更新的过程

25.diff算法的时间复杂度

  • O(n)
  • 在O(n^3)基础上做了一些调整

26.简述diff算法的过程

使用开始vdom

  • patch(elem,vnode) 和 patch(vnode,newVnode)
  • patchVode 和 addVnodes 和 removeVnodes
  • updateChildren(key的重要性)

27.vue为何是异步渲染,$nextTick何用?

  • 异步渲染(以及合并data()修改),以提高渲染性能
  • $nextTick 在DOM更新哇之后,触发回调
  • 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
vue 复制代码
<template>
  <div id="app">
    <ul ref="ul1">
        <li v-for="(item, index) in list" :key="index">
            {{item}}
        </li>
    </ul>
    <button @click="addItem">添加一项</button>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
      return {
        list: ['a', 'b', 'c']
      }
  },
  methods: {
    addItem() {
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)

        // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
        // 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
        this.$nextTick(() => {
          // 获取 DOM 元素
          const ulElem = this.$refs.ul1
          // eslint-disable-next-line
          console.log( ulElem.childNodes.length )
        })
    }
  }
}
</script>

28.Vue常见性能优化

  • 合理使用v-show 和 v-if
  • 合理使用 computed 有缓存 提高性能
  • v-for 中使用 key,以及避免和v-if同时使用
  • 自定义事件、DOM事件及时销毁,避免内存泄露,页面越用越卡
  • 合理使用异步组件,大的第三方组价
  • 合理使用keep-alive,不要重复渲染时候缓存下来
  • data 层级不要太深,因为响应式监听,深度监听计算深度多,页面会卡
  • 使用vue-loader在开发环境做模板编译(预编译)
  • webpack层面的优化
  • 前端通用的性能优化,如图片懒加载
  • 使用SSR

29.vue父子组件生命周期执行顺序,请完整表述

单一组件中:

beforeCreate→created→beforeMounted→mounted→beforeUpdate→update→beforeDestroy→destroyed

父子组件中:

常用钩子简易版:父create→子created→子mounted→父mounted

①加载渲染过程:

父beforeCreate→父created→父beforeMounted→子beforeCreate→子created→子beforeMounted→子mounted→父mounted

②更新过程:

父beforeUpdate→子beforeUpdate→子update→父update

③销毁过程:

父beforeDestroy→子beforeDestroy→子destroyed→父destroyed

注意问题:

在父组件调用接口数据,通过props传递给子组件,接口响应是异步的,子组件无论在哪个钩子都取不到数据。因为子组件的mounted都执行完之后,父组件的请求才返回数据。会导致,从父组件传递给子组件的数据是undefined。

解决:

①在渲染子组件时候加上一个条件,dataList是父组件调用接口返回的数据。当有数据的时候在去渲染子组件。这样就会形成天然的阻塞。在父组件的created中的请求返回数据后,才会执行子组件的created,mounted。最后执行父组件的mounted。

ini 复制代码
<div class="test">
    <children v-if="dataList" :data="dataList" ></children>
</div>

②在子组件中watch监听,父组件获取到的值变化,子组件就可以监听到

kotlin 复制代码
watch:{
    data:{
      deep:true,
      handler:function(newVal,oldVal) {
        this.$nextTick(() => {
          this.data = newVal
        })
      }
    },
}

每个生命周期具体适用哪些场景

钩子函数 过程
beforeCreate 创建前,在实例初始化之后, 数据观测和事件配置之前被调用此时data和methods以及页面的DOM结构都没有初始化什么都做不了。data,computed,watch,methods 上的方法和数据均不能访问。可以在这里加个loading事件
created 创建后,在实例创建完成后被立即调用,此时data、props、computed 和 methods已经可以使用但是页面还没有渲染出来。①可访问data computed watch methods上的方法数据②初始化完成时的事件写在这里,异步请求也适宜在这里调用(请求不宜过多,避免白屏时间太长)③可以在这里结束loading事件,还做一些初始化,实现函数自执行。④未挂载DOM,若在此阶段进行DOM操作一定要放在Vue.nextTick()的回调函数中
beforeMount **挂载前,**在挂载开始之前被调用此时页面上还看不到真实数据只是一个模板页面而已。但vue挂载的根节点已经创建,下面vue对DOM的操作将围绕这个根元素继续进行。beforeMount这个阶段是过渡性的,一般一个项目只能用到一两次。
mouted **挂载,**完成创建vm. <math xmlns="http://www.w3.org/1998/Math/MathML"> e l ,和双向绑定。 e l 被新创建的 v m . el,和双向绑定。el被新创建的vm. </math>el,和双向绑定。el被新创建的vm.el替换, 并挂载到实例上去之后调用该钩子。①数据已经真实渲染到页面上在这个钩子函数里面我们可以使用一些第三方的插件。②完成挂载DOM和渲染,可在mounted钩子函数中对挂载的DOM进行操作。③可在这发起后端请求,拿回数据,配合路由钩子做一些事情。
beforeUpdate **数据更新前,数据驱动DOM。**数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的。①可在更新前访问现有的DOM,如手动移出添加的事件监听器。
update 数据更新后,完成虚拟DOM的重新渲染和打补丁。由于数据更改导致的虚拟DOM重新渲染和打补丁, 在这之后会调用该钩子。页面上数据已经替换成最新的。①组件DOM已完成更新,可执行依赖的DOM操作。②注意:不要在此函数中操作数据(修改属性),会陷入死循环。
activated 在使用vue-router时有时需要使用**<keep-alive></keep-alive>**来缓存组件状态,这个时候created钩子就不会被重复调用了。
deactivated <keep-alive></keep-alive>组件被移除时使用
beforeDestroy 实例销毁之前调用①可做一些删除提示,如:您确定删除xx吗?
destroyed 实例销毁后调用。前组件已被删除,销毁监听事件,组件、事件、子实例也被销毁。

juejin.cn/post/684490...

juejin.cn/post/684490...

juejin.cn/post/684490...

30.vue slot和slot-scope的理解

让用户可以扩展组件,去更好的复用组件和对其做定制化处理。eg:布局组件、表格列、卡片

  • 默认插槽|单个插槽|匿名插槽:子: 父:内容直接在子组件的标签内写入内容即可

  • 具名插槽 :子:<slot name="up"> 父:

    内容
    是在默认插槽的基础上加上slot属性,值为子组件插槽name属性值

  • 作用域插槽|带数据的插槽 :子:<slot name="up" :data ="data"> 父:;通过slot-scope获取子组件的信息,在内容中使用。这里可以用解构语法去直接获取想要的属性。

    • **原理:**slot本质上是返回VNode的函数,一般情况下,Vue中的组件要渲染到页面上需要经过template→render function→VNode→DOM的过程。组件挂载的本质就是执行渲染函数得到VNode,至于data/props/computed这些属性都是给VNode提供数据来源
    • 作用域插槽适合的场景是至少包含三级以上的组件层级,是一种优秀的组件化方案!

juejin.cn/post/684490...

juejin.cn/post/684490...

juejin.cn/post/684490...

31.vue3和vue2优缺点(开发、打包、上线等环节)

①生命周期的变化

Vue2.X Vue3
beforeCreate 使用setup()
created 使用setup()
beforeMounted onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured

②使用proxy代替defineProperty

Vue2.X-defineProperty Vue3-proxy
vue为什么对数组对象的深层监听无法实现,因为组件每次渲染都是将data里的数据通过defineProperty进行响应式或者双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染 defineProperty只能响应首次渲染时候的属性,Proxy需要的是整体,不需要关心里面有什么属性,而且Proxy的配置项有13种,可以做更细致的事情,这是之前的defineProperty无法达到的
vue2.x之所以只能兼容到IE8就是因为defineProperty无法兼容IE8,其他浏览器也会存在轻微兼容问题 proxy的话除了IE,其他浏览器都兼容,这次vue3还是使用了它,说明vue3直接放弃了IE的兼容考虑,个人感觉已经没人用IE
Object.defineProperty( Obj, 'name', { enumerable: true, //可枚举 configurable: true, //可配置 // writable:true, //跟可配置不能同时存在 // value:'name', //可写死直 get: function () { return def }, set: function ( val ) { def = val } } ) //两个参数,对象,13个配置项 const handler = { get: function(obj, prop) { return prop in obj ? obj[prop] : 37; }, set:function(){ }, ...13个配置项 }; const p = new Proxy({}, handler); p.a = 1; p.b = undefined; console.log(p.a, p.b); // 1, undefined console.log('c' in p, p.c); // false, 37

③diff算法的提升

Vue2.X-diff Vue3-diff
vue2.x提供类似于HTML的模板语法,但是,它是将模板编译成渲染函数来返回虚拟DOM树Vue框架通过递归遍历两个虚拟DOM树,并比较每个节点上的每个属性,来确定实际DOM的哪些部分需要更新。由于现代JavaScript引擎执行的高级优化,这种有点暴力的算法通常非常快速,但是**DOM的更新仍然涉及许多不必要的CPU工作** DOM树级别 :在没有动态改变节点结构的模板指令(例如v-if和v-for)的情况下,节点结构保持完全静态。如果我们将一个模板分成由这些结构指令分隔的嵌套"块",则每个块中的节点结构将再次完全静态。当我们更新块中的节点时,我们不再需要递归遍历DOM树 - 该块内的动态绑定可以在一个平面数组中跟踪。这种优化通过将需要执行的树遍历量减少一个数量级来规避虚拟DOM的大部分开销。②编译器:积极检测模版中的静态节点、子树、数据对象 ,并在生成的代码中将他们提升到渲染函数之外。这样可以避免每次渲染时重新创建这些对象,从而打打提高内存使用率并减少垃圾回收的频率。③元素级别 :编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。Vue 3有时占用的CPU时间不到Vue 2的十分之一

④typeScript的支持

Vue2.X-js Vue3-ts

⑤打包的体积变化

Vue2.X-23kb Vue3-10kb
vue2官方说的运行时打包师23k,但这只是没安装依赖的时候,随着依赖包和框架特性的增多,有时候不必要的,未使用的代码文件都被打包了进去,所以后期项目大了,打包文件会特别多还很大。 Vue 3中,我们通过将大多数全局API和内部帮助程序移动到Javascriptmodule.exports属性上实现这一点。这允许现代模式下的module bundler能够静态地分析模块依赖关系,并删除与未使用的module.exports属性相关的代码。模板编译器还生成了对树抖动友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。

vue3组件基本结构分析

vue 复制代码
//dom 里的东西基本上都是没有变的
<template>
  <h1>{{ msg }}</h1>
  <button @click="increment">
    count: {{ state.count }}, double: {{ state.double }},three:{{ three }},refnum:{{refnum}}
  </button>
</template>

<script>
//这里就是Vue3的组合Api了,这里跟react的 import { useState ,useEffect } from 'react' 有些类似,需要用啥引啥
import {ref, reactive, computed ,watchEffect,watch} from "vue";
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  //上面对比的时候说过,setup相当于beforeCreate 和created,简单理解就是初始化
  setup() { 
  	//这里通过reactive使state成为相应状态(后面会详细介绍)
    const state = reactive({
      count: 0,
      //计算属性computed的使用更灵活了
      double: computed(() => state.count * 2),
    });
    //computed也可以单独拿出来使用
    const three = computed(() => state.count * 3)
    //ref跟reactive作用一样都是用来数据相应的,ref的颗粒度更小(后面详细对比)
	const refnum = ref()
   //这里的watchEffect只要里面的变量发生了改变就会执行,并且第一次渲染会立即执行,没有变化前后返回参数,无法监听整个reactive
    watchEffect(() => {
      refnum.value = state.count;
      console.log(state, "watchEffect");
    });
    //watch里第一个参数是监听需要的变量,第二个是执行的回调函数,
    watch(refnum,(a,b)=>{
      console.log(a,b,'watch,a,b')
    })
    //所有的方法里再也不需要用this了,这是很爽的
    function increment() {
      state.count++;
    }
   	//组中模板中需要的变量,都要通过return给暴露出去,就像当初data({return { } }) 是一样的
    return {
      state,
      increment,
      three,
      refnum
    };
  },
};
</script>

vue3生命周期的使用

vue 复制代码
<script>
import {
  reactive,
  computed,
  onMounted,
  onBeforeMount,
  onBeforeUpdate,
  onUpdated,
  onUnmounted,
  onBeforeUnmount,
} from "vue";

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2),
    });
    function increment() {
      state.count++;
    }
    onUpdated(() => {
      console.log("onUpdated");
    });
    onUnmounted(() => {
      console.log("onUnmounted");
    });
    onBeforeUnmount(() => {
      console.log("onBeforeUnmount");
    });
    onBeforeUpdate(() => {
      console.log("onBeforeUpdate1");
    });
    onMounted(() => {
      console.log("onMounted");
    });
    onBeforeMount(() => {
      console.log("onBeforeMount");
    });
    console.log("setup");
    return {
      state,
      increment,
    };
  },
};
</script>
执行顺序
1.setup
2.onBeforeMount
3.onMounted
4.onBeforeUpdate
5.onUpdated

Vue3组件API的使用

①setup

<template><br/> <img alt="Vue logo" src="./assets/logo.png" /><br/> <HelloWorld msg="Baby张 Vue3 RC" /><br/> //这里传参给子组件<br/></template><br/><script><br/>import HelloWorld from "./components/HelloWorld.vue";<br/>import { provide } from "vue";<br/>export default {<br/> name: "App",<br/> components: {<br/> HelloWorld,<br/> }<br/>};<br/></script> //props 接收的父组件传的参数,这就有点像react的props了 //ctx 这个参数表示的当前对象实例,也就个是变相的this setup(props,ctx){ console.log(props.msg, ctx, "app-setup"); } 如果你还想要更多当前组件相关的属性,还可以从组合Api 里引用 getCurrentInstance import {getCurrentInstance } from "vue"; const all = getCurrentInstance() console.log(all, "app-setup");
复制代码

②ref、toRef、toRefs

ref:生成值类型的响应式数据;用于模版和reactive;通过.value修改值

javascript 复制代码
<template>
  <p>ref demo {{ageRef}}{{state.name}}</p>
</template>
import { ref , toRef , toRefs } from 'vue'
export default {
setup(){
  const ageRef = ref(20) // 值类型 响应式
  const nameRef = ref('小圆脸儿')

  const state= reactive({
  	name:nameRef
  })

	setTimeout(()=>{
		console.log('ageRef',ageRef.value)
		ageRef.value = 25 //.value 修改值
		nameRef.value = '晓雪'
  },1000)
  return {
    ageRef,
    state
  }
 }
}

<template>
  <p ref="elemRef">我是一行文字</p>
</template>
import { ref , toRef , toRefs,onMounted } from 'vue'
export default {
  name : 'RefTemplate',
  setup(){
  const elemRef = ref(null)
  
  onMounted(()=>{
    console.log('ref template',elemRef.value.innerHTML)
  })
   return {
   	 elemRef
   }
}

toRef:针对一个响应式(reactive封装)对象的prop;创建一个ref,具有响应式;两者保持引用关系

vue 复制代码
<template>
  <p>toRef demo {{ageRef}}-{{state.name}}{{state.age}}</p>
</template>
import { ref , toRef , toRefs,onMounted } from 'vue'
export default {
  name : 'ToRef',
  setup(){
  	const state = reactive({ //针对一个响应式(reactive封装)对象的prop
      age:20,
      name:'小圆脸儿'
    })

		const ageRef = toRef(state,'age') //创建一个ref,具有响应式
		
		setTimeout(()=>{
      state.age = 25 //.value 修改值
    },1000)

  	setTimeout(()=>{
      state.age = 30 //.value 修改值
    },2000)

    return {
      state,
      ageRef
    }
	}
}

toRefs:将响应式对象(reactive封装)转换为普通对象;对象的每个prop都对应的ref;两者保持引用关系

抽象合成函数时候

javascript 复制代码
<template>
  <p>toRef demo {{name}}{{age}}</p>
</template>
import { ref , toRef , toRefs,onMounted } from 'vue'
export default {
  name : 'ToRef',
  setup(){
  	const state = reactive({ //针对一个响应式(reactive封装)对象
      age:20,
      name:'小圆脸儿'
    })

		const stateAsRefs = toRefs(state) //将响应式对象(reactive封装)转换为普通对象
		
		// const {age:ageRef,name:nameRef} = stateAsRefs // 每个属性,都是ref对象
		
    //return {
    // ...stateAsRefs
    //}
    //return {
   //  ageRef,
   //  nameRef
   // }
    return stateAsRefs
	}
}

ref 就当作简单的响应式变量 toRef 就是把不是响应式的对象转化成响应式 toRefs 就是把响应式的reactive对象,分解成无数的响应式 ref

1.ref响应式的数据,在函数里读取的时候需要 .value获取

  1. dom里不需要我们+value 框架替我们自动解构了
  2. 组件return的时候将 reactive的对象 toRefs ,可以使代码更简洁,又不会丢失数据的响应式

③reactive

上面的 demo 中多多少少都用到了,用法也很简单,就说下注意点:

  • reactive 内部是可以使用计算属性等各种方法,它只是把数据实现响应式而已
  • reactivereturn 的数据最好是用toRefs 转化一下,好处谁用谁知道
  • ref 混合使用时候可以用isRef 判断类型

④watch、watchEffect

watch 需要具体监听参数,watchEffect 不需要传入监听参数

watch 的回调函数跟以前一样有前后对比的参数,watchEffect 啥都没有

watch 只有监听属性变化才执行,watchEffect 第一次会立即执行

watchwatchEffect都无法监听未被绑定的属性

watch 可以直接监听 refreactive 绑定的对象,watchEffect 不可以(ref的值要.value,reactive的值要具体到内部属性),只会执行第一次

⑤函数组件

javascript 复制代码
import { ref, computed, watch, onMounted } from 'vue'

const App = {
  template: `
    <div>
      <span>count is {{ count }}</span>
      <span>plusOne is {{ plusOne }}</span>
      <button @click="increment">count++</button>
    </div>
  `,
  setup() {
    // reactive state
    const count = ref(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}

⑥Other

juejin.cn/post/689229...

juejin.cn/post/684490...

juejin.cn/post/711112...

juejin.cn/post/709857...

juejin.cn/post/711112...

juejin.cn/post/713325...

juejin.cn/post/698906...

juejin.cn/post/698906...

juejin.cn/post/712435...

juejin.cn/post/711568...

juejin.cn/post/694638...

juejin.cn/post/719929...

juejin.cn/post/722142...

juejin.cn/post/706522...

juejin.cn/post/716827...

juejin.cn/post/726074...

32.vue3为什么选择vite?

①vite比webpack更快

在webpack开发时构建时,默认会抓取并构建你的整个应用,然后才能提供服务,这就导致了你的项目中存在任何一个错误(即使当前错误不是首页引用的模块),他依然会影响到你的整个项目构建。所以你的项目越大,构建时间越长,项目启动速度也就越慢。

vite不会在一开始就构建你的整个项目,而是会将引用中的模块区分为依赖和源码(项目代码)两部分,对于源码部分,他会根据路由来拆分代码模块,只会去构建一开始就必须要构建的内容。

同时vite以原生 ESM的方式为浏览器提供源码,让浏览器接管了打包的部分工作。

②简单

Vite 的用法很简单, 执行初始化命令:

sql 复制代码
yarn create @vitejs/app my-vue-app --template vue

就得到了一个预设好的开发环境,可以开始愉快地写 demo 了,Vite 开箱就给你一堆功能,包括 css 预处理器、html 预处理器、hash 命名、异步加载、分包、压缩、HMR 等:

这些功能,作者都按行业最佳实践预设好了,通常不需要用户介入做变更。

Vite 定位就是傻瓜式但强大的构建工具,你专心写好业务代码,早点下班,不用再为了工具费神费力了。

③生态

除了极致的运行性能与简易的使用方法外,Vite 对已有生态的兼容性也不容忽略,主要体现在两个点:

  • 与 Vue 解耦,兼容支持 React、Svelte、Preact、Vanilla 等,这意味着 Vite 可以被应用在大多数现代技术栈中
  • 与 Rollup 极其接近的插件接口,这意味着可以复用 Rollup 生态中大部分已经被反复锤炼的工具

③vite快但是也有缺点

当源码中有commonjs模块加载,那么将会出现模块加载失败的问题。通过依赖预构建的方式解决该问题。

当你首次启动 vite 时,Vite 在本地加载你的站点之前预构建了项目依赖。默认情况下,它是自动且透明地完成的。

这就是 Vite 执行时所做的"依赖预构建"。这个过程有两个目的:

  1. CommonJS 和 UMD 兼容性: 在开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将以 CommonJS 或 UMD 形式提供的依赖项转换为 ES 模块。

    在转换 CommonJS 依赖项时,Vite 会进行智能导入分析,这样即使模块的导出是动态分配的(例如 React),具名导入(named imports)也能正常工作:

    js

    javascript 复制代码
    // 符合预期
    import React, { useState } from 'react'
  2. 性能: 为了提高后续页面的加载性能,Vite将那些具有许多内部模块的 ESM 依赖项转换为单个模块。

    有些包将它们的 ES 模块构建为许多单独的文件,彼此导入。例如,lodash-es 有超过 600 个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!即使服务器能够轻松处理它们,但大量请求会导致浏览器端的网络拥塞,使页面加载变得明显缓慢。

    通过将 lodash-es 预构建成单个模块,现在我们只需要一个HTTP请求!

注意

依赖预构建仅适用于开发模式,并使用 esbuild 将依赖项转换为 ES 模块。在生产构建中,将使用 @rollup/plugin-commonjs

require加载同步,nodejs服务端一般使用require加载模块,一般是一个文件,只需要从本地硬盘中读取,速度比较快。但是在浏览器端就不一样了,文件一般存放在服务器或CDN上,如果使用同步的方式加载一个模块还需要由网络的快慢来决定,可能时间会很长,这样浏览器很容易进去假死状态。所以才有了后面说的AMD和CMD模块化方案,他们都是异步加载的,比较适合浏览器端使用。

CommonJS和ES6模块的区别

  • 因为CommonJSrequire语法是同步的,所以就导致了CommonJS模块规范只适合用在服务端,而ES6模块无论是在浏览器端还是服务端都是可以使用的,但是在服务端中,还需要遵循一些特殊的规则才能使用 ;

  • CommonJS 模块输出的是一个值的拷贝,而ES6 模块输出的是值的引用;

  • CommonJS 模块是运行时加载,而ES6 模块是编译时输出接口,使得对JS的模块进行静态分析成为了可能;

  • 因为两个模块加载机制的不同,所以在对待循环加载的时候,它们会有不同的表现。CommonJS 遇到循环依赖的时候,只会输出已经执行的部分,后续的输出或者变化,是不会影响已经输出的变量。而ES6模块相反,使用import加载一个变量,变量不会被缓存,真正取值的时候就能取到最终的值;

  • 关于模块顶层的this指向问题,在CommonJS 顶层,this指向当前模块;而在ES6模块中,this指向undefined

  • 关于两个模块互相引用的问题,在ES6模块当中,是支持加载CommonJS 模块的。但是反过来,CommonJS 并不能requireES6模块,在NodeJS中,两种模块方案是分开处理的。

    juejin.cn/post/684490...

33.Vue组件传值方式以及使用场景

父传子:props

子传父:子 <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t 、父 emit、父 </math>emit、父on、v-on

兄弟之间:event bus、 <math xmlns="http://www.w3.org/1998/Math/MathML"> r e f s 、 refs、 </math>refs、root、

父取子: <math xmlns="http://www.w3.org/1998/Math/MathML"> r e f :父 t h i s . ref :父 this. </math>ref:父this.refs.child1.msg 、this.$refs.child1.childConsole("hello")

子取父: <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r e n t :子 1 发送: t h i s . parent:子1发送:this. </math>parent:子1发送:this.parent. <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t ( " s e n d " , " 我是子组件 1 " ) ; 子 2 接收: t h i s . emit("send", "我是子组件1"); 子2接收:this. </math>emit("send","我是子组件1");子2接收:this.parent.$on('send',(value)=>{ console.log('child2 received value is :>> ', value); })

父取/调子: <math xmlns="http://www.w3.org/1998/Math/MathML"> c h i l d r e n 父: t h i s . children 父:this. </math>children父:this.children[0]

跨层级:provide/inject

父传子孙后代: <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s 子: attrs 子: </math>attrs子:attrs.value v-bind="$attrs" 孙子:props:value

孙传父爷: <math xmlns="http://www.w3.org/1998/Math/MathML"> l i s t e n e r s 子: t h i s . listeners 子:this. </math>listeners子:this.listeners.event ('hello vue') 父:@event="message" message (value) { console.log ('value :>> ', value); };孙:this. <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t ( " e v e n t " , " h e l l o v u e " ) ; 子: v − o n = " emit("event", "hello vue"); 子:v-on=" </math>emit("event","hellovue");子:v−on="listeners" 父:@event="message"

任意组件通信:vuex

slot-scope v-slot

juejin.cn/post/728710...

34.vue的 <math xmlns="http://www.w3.org/1998/Math/MathML"> n e x t T i c k 和 nextTick 和 </math>nextTick和set原理及使用场景

①set

①-①使用场景

由于JavaScript的限制,Vue无法检测到data中数组和对象的变化,因此也不会触发视图更新

当我们对data中的数组或对象进行修改时 ,有些操作方式是非响应式的 ,Vue检测不到数据更新,因此也不会触发视图更新。此时需要使用Vue.set()进行响应式的数据更新

eg:

kotlin 复制代码
data () {
  return {
    student: {
      name: '',
      sex: ''
    }
  }
}
mounted () { // ------钩子函数,实例挂载之后
  this.student.age = 18
}

mounted () {
  this.$set(this.student,"age", 24)
  // Vue.set
	Vue.set(this.student, "age", 24)
}

受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时对property将属性转为 getter/setter,所以属性property必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。

正确写法:this.$set(this.data,"key",value')

Vue.set(target,key,value)

Vue 不允许动态添加根级响应式属性。

只可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性,例如

csharp 复制代码
var vm=new Vue({
    el:'#test',
    data:{
        //data中已经存在info根属性
        info:{
            name:'小圆脸儿';
        }
    }
});
//给info添加一个年纪属性
// this.user.age = 28 这样是不起作用, 不会被Observeryua
Vue.set(vm.info,'age','28');

①-②Vue.set()和this.$set()实现原理

vue中$set方法对数组和对象的处理本质上的一样的,对新增的值添加响应然后手动触发派发更新。

vm.$set()在new Vue()时候就被注入到Vue的原型上。

源码位置: vue/src/core/instance/index.js

javascript 复制代码
复制代码import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
// 给原型绑定代理属性$props, $data
// 给Vue原型绑定三个实例方法: vm.$watch,vm.$set,vm.$delete
stateMixin(Vue)
// 给Vue原型绑定事件相关的实例方法: vm.$on, vm.$once ,vm.$off , vm.$emit
eventsMixin(Vue)
// 给Vue原型绑定生命周期相关的实例方法: vm.$forceUpdate, vm.destroy, 以及私有方法_update
lifecycleMixin(Vue)
// 给Vue原型绑定生命周期相关的实例方法: vm.$nextTick, 以及私有方法_render, 以及一堆工具方法
renderMixin(Vue)

export default Vue

stateMixin()

ini 复制代码
复制代码...
 Vue.prototype.$set = set
 Vue.prototype.$delete = del
 ...

set()

源码位置: vue/src/core/observer/index.js

$set在Vue中的定义:

javascript 复制代码
function set(target: Array<any> | Object, key: any, val: any): any {
  // ①先判断set的目标是否是undefined 或 null或 基本类型 (因为用户不应该往undefined和基本类型中set东西)
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    // ①result 如果是undefined或基本类型就报错
    warn(
      `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }
  // ②判断了目标是否是数组与key是不是合法的index,合法的index是指值为大于等于0的整数
  if (Array.isArray(target) && isValidArrayIndex(key)) {
     // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
    target.length = Math.max(target.length, key);
    // ②result如果两个条件都成立就对目标数组调用splice方法插入或者修改数组
    target.splice(key, 1, val); //这个splice不是普通的splice,是被vue代理重写过的splice
    return val;
  }
  //对象实现响应
  // ③判断了属性如果在目标对象中直接return结束逻辑 (因为vue只有添加目标对象中原本没有的属性时才会失去响应)
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  // 以上都不成立, 即开始给target创建一个全新的属性
  // 获取Observer实例
  // vue在初始化的时候会将data里的所有属性都变成响应式,如果的值是对象或者数组则会new一个Observer实例储存在__ob__
  const ob = (target: any).__ob__;
   // Vue 实例对象拥有 _isVue 属性, 即不允许给Vue 实例对象添加属性
  // 也不允许Vue.set/$set 函数为根数据对象(vm.$data)添加属性
  // 拿到这个对象的_ob_进行判断,如果不存在就说明是未经过vue初始化的普通对象而不是响应式对象
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid adding reactive properties to a Vue instance or its root $data " +
          "at runtime - declare it upfront in the data option."
      );
    return val;
  }
   // target本身就不是响应式数据, 直接赋值
  if (!ob) {
    target[key] = val;
    return val;
  }
  // 进行响应式处理
  // 手动通过defineReactive为属性添加get方法与set方法实现响应
  defineReactive(ob.value, key, val);
  // 然后手动调用dep里的notify()发布更新
  ob.dep.notify();
  return val;
}

数组实现响应

$set实现数组修改响应的方式是代理重写的数组的一部分方法,接下来我们看一下具体实现

vue中代理重写的不只是splice,有push、pop、shift、unshift、splice、sort、reverse这七个方法

javascript 复制代码
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// vue中代理重写 有push、pop、shift、unshift、splice、sort、reverse
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
function def(obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
    });
}
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
   // 首先执行了const result = original.apply(this, args)执行原本数组的方法并获取它的值
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    // 判断如果是往数组中添加值
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 就将新添加的值也实现响应式
    if (inserted) ob.observeArray(inserted)
    // 最后一步拿到这个数组的_ob_对象对_ob_里的dep进行派发更新
    ob.dep.notify()
    return result
  })
})

工具函数:

javascript 复制代码
// 判断给定变量是否是未定义,当变量值为 null时,也会认为其是未定义
export function isUndef (v: any): boolean %checks {
  return v === undefined || v === null
}

// 判断给定变量是否是原始类型值
export function isPrimitive (value: any): boolean %checks {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}

// 判断给定变量的值是否是有效的数组索引
export function isValidArrayIndex (val: any): boolean {
  const n = parseFloat(String(val))
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}

关于(ob && ob.vmCount)

javascript 复制代码
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 省略...
  if (asRootData && ob) {
    // vue已经被Observer了,并且是根数据对象, vmCount才会++
    ob.vmCount++
  }
  return ob
}

在初始化Vue的过程中有

javascript 复制代码
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    //opts.data为对象属性
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initData(vm)

javascript 复制代码
function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  
  // 省略...

  // observe data
  observe(data, true /* asRootData */)
}

nextTick

②-①使用场景:

nextTick 方法是在 Vue.js 中常见的一种异步更新 DOM 的机制。它的原理是利用 JavaScript 的事件循环机制以及浏览器的渲染流程来实现延迟执行 DOM 更新操作。

它的出现主要是为了解决 Vue 的异步更新导致的 DOM 更新后的操作问题。

在 Vue 中,数据的变化会触发重新渲染 DOM,但实际上,Vue 的数据更新是异步的。也就是说,当我们修改了 Vue 实例的数据后,并不会立即进行 DOM 更新,而是在下一个事件循环中才会进行。

这个异步更新机制的设计是为了优化性能。Vue 会对进行多次数据变化进行合并,然后在下一个事件循环中进行一次性的 DOM 更新,从而减少不必要的 DOM 操作,提高性能。

然而,由于异步更新的机制,有时候可能在修改数据后需要立即执行一些 DOM 操作,例如获取到更新后的 DOM 元素、更新后的样式计算、触发一些特定事件等。这时候就需要使用 nextTick 方法了。

nextTick 方法是 Vue 提供的一个实用工具,它能够将回调函数延迟到下一个 DOM 更新循环之后执行。也就是说,通过 nextTick 方法,我们可以确保在 DOM 更新完成后执行某些操作。

使用 nextTick 方法经常用来解决以下问题:

  • 获取更新后的 DOM 元素
  • 更新后的样式计算
  • 触发一些特定事件

综上所述,nextTick 的出现解决了 Vue 的异步更新机制导致的 DOM 更新后的操作问题,使我们能够在正确的时机执行对应的操作,提高开发效率和灵活性。

②-②实现原理:

  • 将传递的回调函数放入 callbacks 全局数组中
  • 调用 timerFunc 函数,在浏览器的异步队列中放入刷新callbacks(flashcallbacks ) 函数 ,延迟执行 (根据运行环境判断将flashcallbacks() 放入宏任务或者是微任务队列, 使得 flashcallbacks 被延迟调用)
  • 事件循环到了微任务或者宏任务,依次遍历执行 callbacks 数组中的所有函数

延迟调用优先级如下:

Promise.then > MutationObserver > setImmediate > setTimeout

MutationObserver 是H5 新加的一个功能,其功能是监听 DOM 节点的变动,在所有 DOM 变动完成后,执行回调函数。

Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和Dom操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

nextTick实现原理详情:juejin.cn/post/725617...

juejin.cn/post/684490...

juejin.cn/post/684490...

juejin.cn/post/717618...

35.vue更新策略及diff算法

更新过程

  • 修改data,触发setter
  • 重新执行render函数,生成newVnode
  • patch(vnode,newVnode),diff算法会算差异

diff算法

使用开始vdom 虚拟DOM 。虚拟DOM算法 = 虚拟DOM + Diff算法

diff算法:新旧虚拟DOM对比,找出更改的虚拟节点,并更新虚拟节点所对应的真实节点,实现精准的更新真实DOM,进而提高效率。

使用虚拟DOM算法耗损计算:总耗损=虚拟DOM增删改+真实DOM差异增删改(diff算法效率)+重绘与重排(较少)

直接操作DOM的耗损计算:总耗损=真实DOM完全增删改+重绘与重排(较多)

  • 同层对比:深度优先算法 patch(elem,vnode) 和 patch(vnode,newVnode)
  • 对比流程:patchVode 和 addVnodes 和 removeVnodes
  • 对比子节点,并更新:updateChildren(key的重要性)

juejin.cn/post/699495...

juejin.cn/post/691937...

juejin.cn/post/699058...

36.mixins和vuex的优缺点

Mixins 是我们可以重用的代码块

vuex源码就是调用了Vue.mixin,Vuex的双向绑定通过调用 new Vue实现,然后通过 Vue.mixin 注入到Vue组件的生命周期中,再通过劫持state.get将数据放入组件中

37.Vue动态加载异步组件原理及应用场景

核心原理就是 nextTick 实现异步队列 前提是需要理解 js 事件循环机制

juejin.cn/post/696122...

38.你都做过哪些 Vue 的性能优化

这里只列举针对 Vue 的性能优化 整个项目的性能优化是一个大工程 可以另写一篇性能优化的文章 哈哈

  • 对象层级不要过深,否则性能就会差
  • 不需要响应式的数据不要放到 data 中(可以用 Object.freeze() 冻结数据)
  • v-if 和 v-show 区分使用场景
  • computed 和 watch 区分使用场景
  • v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if
  • 大数据列表和表格性能优化-虚拟列表/虚拟表格
  • 防止内部泄漏,组件销毁后把全局变量和事件销毁
  • 图片懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 适当采用 keep-alive 缓存组件
  • 防抖、节流运用
  • 服务端渲染 SSR or 预渲染

其他Vue真题:juejin.cn/post/696122...

其他Vue详解合集如下:

  1. Vue基本使用 1
  2. vue2.x的10种组件间通信方式 2
  3. Vue2.X的使用 3
  4. Vue面试真题演练 4 当前文档
  5. Vue3 5 更新中

感谢阅读

「❤️关注+点赞+收藏+评论+转发❤️」,创作不易,鼓励笔者创作更好的文章

相关推荐
Redstone Monstrosity几秒前
字节二面
前端·面试
东方翱翔8 分钟前
CSS的三种基本选择器
前端·css
Fan_web30 分钟前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei196238 分钟前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝39 分钟前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby
前端西瓜哥1 小时前
贝塞尔曲线算法:求贝塞尔曲线和直线的交点
前端·算法
又写了一天BUG1 小时前
npm install安装缓慢及npm更换源
前端·npm·node.js
cc蒲公英1 小时前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel