Vue 面试题

126 阅读19分钟

Vue

Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。

Vue 是一个典型的 MVVM 模型的框架。MVVM 是 Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对 View 和 ViewModel 的双向数据绑定。这使得 ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定。

优点:

  • 易于学习和使用:Vue提供了一个平滑的学习曲线,使其适用于初学者和专业开发人员。它有着丰富的文档和教程,并且有着庞大的社区支持。
  • 高性能:Vue使用虚拟DOM来提高应用的性能和渲染效率。虚拟DOM是一个轻量级的JavaScript对象,它是真实DOM的抽象表示。当组件的状态发生变化时,Vue会根据新的状态创建一个新的虚拟DOM树。然后,Vue会使用一个高效的算法来比较新旧虚拟DOM树,计算出最小的更新操作来更新真实DOM。
  • 灵活性:Vue非常灵活,可以与现有项目无缝集成。它提供了许多高级功能,如计算属性、侦听器和过渡效果等,可以帮助开发人员更快地构建复杂的应用程序。

缺点:

  • 生态系统不够成熟:相比其他前端框架,Vue的生态系统不够成熟。它缺少一些高质量的插件和工具,这可能会影响开发人员的工作效率。
  • 文档不足:尽管Vue有着丰富的文档和教程,但由于其快速发展,有时文档可能不够完整或过时。
  • 学习曲线陡峭:尽管Vue相对容易学习,但要真正掌握它并开发复杂的应用程序仍然需要一定的时间和精力。

既然提到了 mvvm,那么就简单说一下 MVC 以及 MVVM 和 MVC 之间的区别:

MVC 和 MVVM 都是一种设计模式,它们都旨在将应用程序分成不同的部分,以便更好地管理和维护。

MVC

MVC 是 Model-View-Controller 的缩写,它将应用程序分成三个部分:Model 负责存储数据和业务逻辑,View 负责展示数据,Controller 负责接收用户输入并更新 Model 和 View。在 MVC 模式中,View 和 Model 是相互独立的,它们之间通过 Controller 来进行通信。

优点:

  1. 耦合度低:MVC 的三个部件(Model、View 和 Controller)是相互独立的,改变其中一个不会影响其他两个。
  2. 重用性高:多个视图可以使用同一个模型。
  3. 可维护性高:由于各个部件之间的分离,MVC 模式下的应用程序更容易维护。

缺点:

  1. 不适合小型项目开发。
  2. 视图与控制器联系过于紧密,妨碍了它们的独立重用。
MVVM

MVVM 是 Model-View-ViewModel 的缩写,它也将应用程序分成三个部分:Model 负责存储数据和业务逻辑,View 负责展示数据,ViewModel 则负责连接 View 和 Model。与 MVC 不同的是,在 MVVM 模式中,View 和 ViewModel 之间有着双向数据绑定的联系。这意味着当 ViewModel 中的数据发生变化时,View 会自动更新;而当 View 中的数据发生变化时,ViewModel 也会自动更新。

优点:

  1. 低耦合:视图(View)可以独立于 Model 变化和修改,一个 Model 可以绑定到不同的 View 上。当 View 变化时,Model 可以不变化;当 Model 变化时,View 也可以不变。
  2. 可重用性:你可以把一些视图逻辑放在一个 Model 里面,让很多 View 重用这段视图逻辑。
  3. 独立开发:双向数据绑定的模式实现了 View 和 Model 的自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要一直操作 DOM。

缺点:

  1. 增加了代码复杂度,并且对于简单的应用来说可能会显得过于繁琐。
  2. 由于 MVVM 模式依赖于双向数据绑定,因此它也可能会带来一些性能问题。

总之,MVC 和 MVVM 的主要区别在于它们对 View 和 Model 之间通信方式的不同处理。MVC 通过 Controller 来进行通信,而 MVVM 则通过双向数据绑定来实现通信。这两种模式各有优缺点,具体使用哪种模式取决于具体的应用场景。

底层实现原理

Vue 的底层实现原理主要包括数据双向绑定虚拟 DOM两部分。

数据双向绑定是指当数据发生变化时,视图会自动更新;而当视图发生变化时,数据也会自动更新。 Vue 实现数据双向绑定的方式是通过数据劫持发布订阅模式相结合。

  • 数据劫持:Vue 会拦截 data 对象中所有属性的读取和写入操作。在 Vue 2.x 版本中,数据劫持是通过 Object.defineProperty() 方法实现的;而在 Vue 3.x 版本中,数据劫持则是通过 Proxy 对象实现的。
  • 发布订阅模式:当我们修改 data 中的某个属性时,Vue 会通知所有订阅了该属性变化的观察者(Watcher),并执行相应的回调函数。这些回调函数通常会更新视图,以保证视图与数据保持同步。

虚拟 DOM 是一种用 JavaScript 对象表示 DOM 的技术。它可以让我们在不直接操作 DOM 的情况下更新视图。Vue 在更新视图时会先生成一个新的虚拟 DOM 树,然后将新旧虚拟 DOM 树进行对比,找出它们之间的差异。最后,Vue 会根据这些差异来更新真实的 DOM 树。这个过程被称为“patching”。

使用虚拟DOM有以下几个好处:

  1. 提高渲染性能:直接操作真实DOM通常是非常慢的,因为浏览器需要执行很多额外的工作,如样式计算、布局和重绘。使用虚拟DOM可以减少对真实DOM的操作次数,从而提高渲染性能。
  2. 跨平台:虚拟DOM是一个抽象层,它可以运行在任何支持JavaScript的平台上。这意味着你可以使用Vue来构建跨平台应用,如桌面应用、移动应用和Web应用。
  3. 更容易测试:由于虚拟DOM是一个纯粹的数据结构,它更容易进行测试和调试。

相对于手动操作真实DOM,使用虚拟DOM通常可以获得更好的性能。但这并不是绝对的,因为虚拟DOM也有一些开销,如创建虚拟DOM树和计算差异。在某些情况下,手动操作真实DOM可能会更快。但总体来说,使用虚拟DOM可以让我们更容易地构建高性能和跨平台的应用。

生命周期

Vue 的生命周期指的是 Vue 实例从创建到销毁的整个过程。在这个过程中,Vue 实例会经历一系列的生命周期钩子函数,这些钩子函数可以让我们在特定的时刻执行特定的操作。

  • beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用。
    created:在实例创建完成后被立即调用。此时,实例已完成以下配置:数据观测、属性和方法的运算、watch/event 事件回调。但是,挂载阶段还没开始,$el 属性目前不可见。
  • beforeMount:在挂载开始之前被调用。相关的 render 函数首次被调用。
    mounted:在 el 被新创建的 vm.el替换,并挂载到实例上去之后调用。如果根实例挂载了一个文档内元素,当mounted被调用时,vm.el 替换,并挂载到实例上去之后调用。如果根实例挂载了一个文档内元素,当 mounted 被调用时,vm.el 也在文档内。
  • beforeUpdate:在数据更新之前调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM。
    updated:在由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
  • beforeDestroy:在实例销毁之前调用。此时实例仍然完全可用。
    destroyed:在实例销毁之后调用。此时,所有的指令绑定都被解除,所有的事件监听器都被移除,所有的子实例也都被销毁。

Vuex

Vuex是一个专为Vue.js应用程序开发的状态管理模式+库。使用Vuex时,每一个Vuex应用的核心就是store(仓库)。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。它可以帮助我们管理共享状态,解决多组件数据通信问题。

20200828105911465.png

简单来说,Vuex就像一个容器,它包含了你的应用中大部分的状态。当Vue组件从store中读取状态时,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。

你可以通过store.state来获取状态对象,并通过store.commit方法触发状态变更。在Vue组件中,可以通过this.$store访问store实例,但不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交mutation,而非直接改变store.state.count。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

Vuex主要包括以下几个核心模块:

  • State:Vuex使用单一状态树,用一个对象就包含了全部的应用层级状态。每个应用将仅仅包含一个store实例。单一状态树让我们能够直接定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
  • Getter:有时候我们需要从store中的state中派生出一些状态,例如对列表进行过滤并计数。Vuex允许我们在store中定义getter(可以认为是store的计算属性)。就像计算属性一样,getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
  • Mutation:更改Vuex的store中的状态的唯一方法是提交mutation。Vuex中的mutation非常类似于事件:每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受state作为第一个参数。
  • Action:Action类似于mutation,不同在于:Action提交的是mutation,而不是直接变更状态;Action可以包含任意异步操作。Action函数接受一个与store实例具有相同方法和属性的context对象,因此你可以调用context.commit提交一个mutation,或者通过context.state和context.getters来获取state和getters。
  • Module:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决这个问题,Vuex允许我们将store分割成模块(module)。每个模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块。

一些常见的Vuex使用场景包括:用户的个人信息管理模块、电商项目的购物车模块、我的订单模块(订单列表中点击取消订单,然后更新对应的订单列表)、在订单结算页获取需要的优惠券并更新订单优惠信息等。

示例:

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    doubleCount: state => state.count * 2
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    increment(context) {
      context.commit('increment')
    }
  }
})

// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

// App.vue
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count
    }
  },
  methods: {
    increment() {
      this.$store.dispatch('increment')
    }
  }
}
</script>

组件之间的通信方式

除了以上说的 Vuex 进行组件之间的通讯外,常见的组件通讯还有以下几种方式:

  1. props / emit:父组件通过props向子组件传递数据,子组件通过emit:父组件通过props向子组件传递数据,子组件通过emit向父组件传递数据。
    示例:
// 父组件
<template>
  <div>
    <child :msg="msg" @changeMsg="changeMsg"></child>
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
  components: { Child },
  data() {
    return {
      msg: 'Hello'
    }
  },
  methods: {
    changeMsg(newMsg) {
      this.msg = newMsg
    }
  }
}
</script>

// 子组件
<template>
  <div>
    <p>{{ msg }}</p>
    <button @click="changeMsg">Change Msg</button>
  </div>
</template>

<script>
export default {
  props: ['msg'],
  methods: {
    changeMsg() {
      this.$emit('changeMsg', 'Hi')
    }
  }
}
</script>
  1. ref / refs:父组件可以通过refs:父组件可以通过refs获取子组件的实例,从而调用子组件的方法或访问子组件的数据。
    示例:
// 父组件
<template>
  <div>
    <child ref="child"></child>
    <button @click="getChildMsg">Get Child Msg</button>
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
  components: { Child },
  methods: {
    getChildMsg() {
      console.log(this.$refs.child.msg)
    }
  }
}
</script>

// 子组件
<template>
  <div>{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello'
    }
  }
}
</script>
  1. eventBus事件总线(emit/emit / on):可以创建一个空的Vue实例作为事件总线,在组件中通过emit触发事件,在另一个组件中通过emit触发事件,在另一个组件中通过on监听事件,从而实现组件间通信。
    示例:
// eventBus.js
import Vue from 'vue'
export const eventBus = new Vue()

// 组件A
<template>
  <div>
    <button @click="emitEvent">Emit Event</button>
  </div>
</template>

<script>
import { eventBus } from './eventBus.js'
export default {
  methods: {
    emitEvent() {
      eventBus.$emit('myEvent', 'Hello')
    }
  }
}
</script>

// 组件B
<template>
  <div>{{ msg }}</div>
</template>

<script>
import { eventBus } from './eventBus.js'
export default {
  data() {
    return {
      msg: ''
    }
  },
  mounted() {
    eventBus.$on('myEvent', (data) => {
      this.msg = data
    })
  }
}
</script>
  1. parent/parent / children:子组件可以通过parent访问父组件实例,父组件可以通过parent访问父组件实例,父组件可以通过children访问子组件实例。
    示例:
// 父组件
<template>
  <div>
    <child></child>
    <button @click="getChildMsg">Get Child Msg</button>
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
  components: { Child },
  methods: {
    getChildMsg() {
      console.log(this.$children[0].msg)
    }
  }
}
</script>

// 子组件
<template>
  <div>{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello'
    }
  }
}
</script> 
  1. attrs/attrs/ listeners:attrs包含了父组件中不作为prop被识别且获取的特性绑定,attrs包含了父组件中不作为prop被识别且获取的特性绑定,listeners包含了父组件中的v-on事件监听器。
    示例:
// 父组件
<template>
<div id="app">
   <middle :msg="msg" @changeMsg="changeMsg"></middle> 
</div> 
</template> 

<script> 
import Middle from './Middle.vue' 
export default { 
   components: { Middle }, 
   data() { 
      return { 
         msg: 'Hello' 
      } 
   }, 
   methods: { 
      changeMsg(newMsg) { 
         this.msg = newMsg 
      } 
   } 
} 
</script> 

// 中间组件
<template> 
<div> 
   <child v-bind="$attrs" v-on="$listeners"></child> 
</div> 
</template> 

<script> 
import Child from './Child.vue' 
export default { 
   components: { Child }, 
   inheritAttrs: false // 不继承父组件的属性,避免将属性绑定到根元素上。 
} 
</script> 

// 子组件
<template> 
<div> 
   <p>{{ msg }}</p> 
   <button @click="changeMsg">Change Msg</button> 
</div> 
</template> 

<script> 
export default { 
   props: ['msg'], 
   methods: { 
      changeMsg() { 
         this.$emit('changeMsg', 'Hi') 
      } 
   } 
} 
</script>
  1. provide/inject:祖先组件通过provide提供变量,然后在子孙组件中通过inject来注入变量。
    示例:
// 祖先组件
<template>
  <div>
    <child></child>
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
  components: { Child },
  provide() {
    return {
      msg: 'Hello'
    }
  }
}
</script>

// 子孙组件
<template>
  <div>{{ msg }}</div>
</template>

<script>
export default {
  inject: ['msg']
}
</script>

computed与watch

computed和watch都是Vue实例的选项,用来监听数据变化并执行相应的操作。

computed

computed:计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要相关依赖没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。计算属性默认只有getter,不过在需要时你也可以提供一个setter。
示例:

new Vue({
  el: '#app',
  data: {
    message: 'Hello'
  },
  computed: {
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
  }
})

watch

watch:当你需要在数据变化时执行异步或开销较大的操作时,可以使用watch。watch选项允许我们执行异步操作(访问一个API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。
示例:

new Vue({
  el: '#app',
  data: {
    message: 'Hello'
  },
  watch: {
    message: function (newVal, oldVal) {
      console.log('message changed from', oldVal, 'to', newVal)
    }
  }
})

区别:

  • 计算属性是基于它们的依赖进行缓存的。 只有在相关依赖发生改变时,计算属性才会重新求值。这意味着只要相关依赖没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。相比之下,watch选项中的函数每次都会执行。
  • 计算属性通常用来计算一个值, 这个值是基于它的依赖进行计算的。当你需要根据数据变化来改变数据时,可以使用计算属性。相比之下,watch选项通常用来执行异步操作或开销较大的操作。
  • 计算属性是响应式的, 当它们的依赖发生改变时,它们会自动更新。相比之下,watch选项需要手动设置监听的数据。

总之,当你需要根据数据变化来改变数据时,可以使用计算属性;当你需要根据数据变化来执行异步操作或开销较大的操作时,可以使用watch。

其他

v-if和v-for同时使用在一个元素上的问题

不建议在同一元素上同时使用v-for和v-if。当它们同时存在时,v-for的优先级比v-if更高,这意味着v-if将分别重复运行于每个循环的项上。这可能会导致性能问题,因为在渲染列表时会进行更多的计算。

场景一:如果你想根据条件过滤列表并渲染过滤后的结果,可以将过滤后的结果计算为一个计算属性,然后在v-for中使用这个计算属性:

<template>
  <ul>
    <li v-for="item in filteredItems" :key="item.id">
      {{ item.text }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, text: 'Item 1', show: true },
        { id: 2, text: 'Item 2', show: false },
        { id: 3, text: 'Item 3', show: true }
      ]
    }
  },
  computed: {
    filteredItems() {
      return this.items.filter(item => item.show)
    }
  }
}
</script>

场景二:如果你的目的是有条件地跳过循环的执行,那么可以将v-if放置在外层元素(如<template>)或包装元素上:

<template>
  <ul v-if="shouldShowItems">
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, text: 'Item 1' },
        { id: 2, text: 'Item 2' },
        { id: 3, text: 'Item 3' }
      ],
      shouldShowItems: true
    }
  }
}
</script>

Vue.nextTick的原理及实现

Vue.nextTick是一个全局API,用于在下一次DOM更新循环结束之后延迟执行一个回调函数。它的实现依赖于JavaScript的事件循环和微任务队列。

  • 在Vue 2.x中,Vue.nextTick的实现使用了一个异步队列来存储所有等待执行的回调函数。当一个回调函数被传递给Vue.nextTick时,它会被推入这个异步队列中。然后,Vue会使用一个内部函数来异步刷新这个队列,以便在下一次DOM更新循环结束之后执行所有等待的回调函数。

为了异步刷新队列,Vue会尝试使用原生的Promise.then、MutationObserver或setImmediate来实现异步延迟。如果这些方法都不可用,它会退而使用setTimeout(fn, 0)。

  • 在Vue 3.x中,Vue.nextTick的实现类似于Vue 2.x,但使用了更现代的API来实现异步延迟。它首先尝试使用原生的Promise.then,如果不可用则退而使用setTimeout(fn, 0)。
    示例:
<template>
  <div>
    <p ref="message">{{ message }}</p>
    <button @click="updateMessage">Update</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  methods: {
    updateMessage() {
      this.message = 'Updated'
      console.log(this.$refs.message.textContent) // => 'Hello'
      this.$nextTick(() => {
        console.log(this.$refs.message.textContent) // => 'Updated'
      })
    }
  }
}
</script>

总之,Vue.nextTick的实现依赖于JavaScript的事件循环和微任务队列。它使用一个异步队列来存储所有等待执行的回调函数,并使用原生API或setTimeout来异步刷新这个队列。

组件中data是一个函数的原因

在Vue组件中,data必须是一个函数,而不是一个对象。这是因为当一个组件被多次使用时,每个实例都应该维护一份被返回对象的独立的拷贝。

如果data是一个对象,那么所有组件实例将共享同一个数据对象。这意味着当一个组件实例改变了数据对象时,其他组件实例的数据也会受到影响。

为了避免这个问题,Vue要求组件的data选项必须是一个函数。当一个组件被实例化时,Vue会调用这个函数来获取组件的初始数据。由于每个组件实例都会调用这个函数来获取自己的数据,所以每个组件实例都会维护一份独立的数据拷贝。

前端路由

前端路由是指在单页应用(SPA)中,通过改变URL并不向服务器发送请求,而是通过JavaScript来控制页面内容的切换。这种方式可以让用户在不离开当前页面的情况下,浏览不同的内容。

前端路由通常有两种实现方式:hash模式和history模式。

  • hash模式:在这种模式下,URL中的hash(即#符号后面的部分)用来表示路由状态。当hash发生变化时,浏览器不会向服务器发送请求,而是触发hashchange事件。我们可以监听这个事件,并根据新的hash值来更新页面内容。这种方式兼容性好,但URL中会多出一个#符号,可能会影响美观。
  • history模式:在这种模式下,我们使用HTML5的History API来控制URL的变化。当URL发生变化时,浏览器不会向服务器发送请求,而是触发popstate事件。我们可以监听这个事件,并根据新的URL来更新页面内容。这种方式可以让URL看起来更像传统的URL,但需要服务器端的支持。

Vue diff算法

Vue的diff算法是用来比较新旧虚拟DOM树,计算出最小的更新操作来更新真实DOM的过程。它采用了深度优先遍历和双端比较的策略来优化比较过程,是Vue虚拟DOM实现的核心部分。

Vue的diff算法基于两个假设:

  1. 两个相同标签的元素会产生类似的DOM结构。
  2. 同一层级的一组子节点,它们可以通过唯一的id进行区分。
    基于这两个假设,Vue的diff算法采用了深度优先遍历和双端比较的策略来比较新旧虚拟DOM树。

在比较过程中,Vue会从新旧虚拟DOM树的根节点开始,逐层进行比较。当遇到不同类型的节点时,Vue会直接替换整个节点及其子节点;当遇到相同类型但属性不同的节点时,Vue会更新节点的属性;当遇到相同类型且属性相同但子节点不同的节点时,Vue会递归地比较子节点。

在比较子节点时,Vue会使用双端比较的策略来优化比较过程。它会同时从新旧虚拟DOM树的两端开始比较,如果发现两端的节点相同,则直接移动节点;如果发现两端的节点不同,则继续比较中间部分。这种策略可以有效地减少需要比较的节点数量,从而提高diff算法的性能。

那么我们常常在for循环中要绑定一个key属性值,有什么作用呢?

其实在Vue中,key是一个特殊的属性,用于标识列表渲染中每个节点的唯一性。这是因为在列表渲染中,列表数据可能会发生变化,导致列表项的顺序、数量或内容发生变化。如果没有key属性,Vue将无法准确地确定新旧虚拟DOM树中的节点是否相同,从而无法快速地更新虚拟DOM树。所以它可以帮助Vue更快地更新虚拟DOM树,从而提高应用的性能。

当Vue进行列表渲染时,它需要一种方式来确定新旧虚拟DOM树中的节点是否相同。如果没有key属性,Vue会默认使用“就地更新”的策略,即直接复用旧虚拟DOM树中的节点来更新新虚拟DOM树中的节点。这种方式简单快速,但在某些情况下可能会导致问题。

为了避免这些问题,我们可以使用key属性来为每个节点指定一个唯一的标识。当Vue进行列表渲染时,它会根据key属性来确定新旧虚拟DOM树中的节点是否相同。这样,Vue就可以更快地更新虚拟DOM树,从而提高应用的性能。

keep-alive使用及原理。

keep-alive是Vue的一个内置组件,用于保留组件状态或避免重新渲染。它可以将其包裹的组件缓存起来,当组件切换时不会销毁,而是保留在内存中,以便下次切换回来时可以直接使用。

实现原理:是通过一个缓存对象来存储被缓存的组件实例。当一个组件被切换出去时,它不会被销毁,而是被保存在缓存对象中;当一个组件被切换回来时,keep-alive会先检查缓存对象中是否有这个组件的实例,如果有,则直接使用缓存的实例;如果没有,则创建一个新的实例。

示例:

<template>
  <div>
    <button @click="toggle">Toggle</button>
    <keep-alive>
      <component :is="currentView"></component>
    </keep-alive>
  </div>
</template>

<script>
import Foo from './Foo.vue'
import Bar from './Bar.vue'

export default {
  components: {
    Foo,
    Bar
  },
  data() {
    return {
      currentView: 'Foo'
    }
  },
  methods: {
    toggle() {
      this.currentView = this.currentView === 'Foo' ? 'Bar' : 'Foo'
    }
  }
}
</script>

插槽

插槽(Slot)是Vue的一个功能,用于实现组件的内容分发。它允许你在父组件中定义一些内容,然后将这些内容分发到子组件的指定位置。

默认插槽,具名插槽和匿名插槽:

  • 默认插槽用于分发没有指定名称的内容
  • 具名插槽用于分发指定名称的内容。
  • 匿名插槽是指没有被元素包裹的内容。

示例:

// 子组件
Vue.component('my-component', {
  template: `
    <div>
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `
})

// 父组件
new Vue({
  el: '#app',
  template: `
    <my-component>
      <template v-slot:header>
        <h1>Header</h1>
      </template>
      <p>Content</p>
      <template v-slot:footer>
        <h1>Footer</h1>
      </template>
    </my-component>
  `
})