Vue面试题

!! vue2/3响应式、vue-router、vuex、keep-alive、pinia、NextTick、虚拟dom、diff算法、ref和reactive !!

1、什么是vue?

vue是一个用于构建用户界面的渐进式JavaScript框架,也是一个创建单页应用的Web应用框架;使用数据响应、组件化开发思想、虚拟dom、mvvm理念等等;

数据响应:

Vue.js实现了响应式数据绑定机制,即当数据发生变化时,相关的视图会自动更新。这一特性使得开发者无需手动操作DOM,而是专注于数据的操作,使得开发更加高效。

mvvm:

MVVM 是一种用于构建用户界面的软件架构模式,它将应用程序的逻辑和用户界面进行分离,以提高代码的可维护性和可测试性,MVVM 代表着 Model-View-ViewModel;

Model(模型):负责封装应用程序的数据和业务逻辑。模型表示应用程序的数据结构、状态和操作,通常包括数据获取、验证、持久化等功能

View(视图):负责展示用户界面。视图是用户所看到和操作的界面,它通常由HTML、XML、XAML等表示

ViewModel(视图模型):连接模型和视图之间的桥梁。视图模型具有与视图相对应的属性、命令和方法,用于展示数据、处理用户交互,并根据业务逻辑更新模型。

组件化开发思想:

Vue.js鼓励使用组件化的开发模式,将应用划分为多个小组件,每个组件负责一块特定的功能。组件可以嵌套使用,每个组件都有自己的状态和行为,便于代码的复用和维护

虚拟dom:

Vue.js使用虚拟DOM来提高页面渲染性能。虚拟DOM是真实DOM的内存映射,当数据变化时,Vue.js会先更新虚拟DOM,然后将新旧虚拟DOM进行比较,最终只更新真实DOM中发生变化的部分,减少了不必要的DOM操作,提高了页面渲染效率

2、vue2和vue3的区别?

数据响应:

Vue 3通过使用Proxy对象和依赖收集技术,实现了更高效、更灵活的响应式数据双向绑定机制。这种改进使得Vue 3在性能和开发体验上都有了显著的提升

Vue 2

数据拦截observe/definreactive==>模版编译==>watcher,dep

vue2的响应式原理通过 Object.defineProperty 这个 API 来实现的;

(1)、数据劫持:在 Vue 初始化时,Vue 会遍历 data 中的所有属性,并使用 Object.defineProperty 把这些属性转化为 getter 和 setter

(2)、Getter:在 getter 中,Vue 会收集对数据的依赖。当一个属性被读取时,会触发 getter,然后把对应的依赖 (Watcher) 添加到依赖列表中

(3)、Setter:在 setter 中,Vue 会监听到属性的变化。当一个属性被赋新值时,会触发 setter,然后通知依赖列表中的 Watcher 执行更新

(4)、依赖收集和触发更新:Watcher 这个机制用来收集依赖和触发更新。每个组件实例都会对应一个 Watcher 对象,用来管理该组件所依赖的属性。当一个组件中的数据被访问时,会触发对应的 getter,然后把该组件的 Watcher 添加到依赖列表中。当某个依赖的数据发生变化时,会触发对应的 setter,然后通知依赖列表中的所有 Watcher 执行更新操作

vue3

配合reflect基本可以拦截所有操作;

vue3的响应式原理是通过proxy代理实现的;在目标对象之前架设了一层拦截,外界对该对象的访问,都必须通过这层拦截,通过这种机制,我们可以对外界的访问进行过滤和改写;

1、在Vue 3中,使用reactive函数来创建一个响应式的数据对象。这个函数接收一个普通的JavaScript对象,并将其转化为Proxy对象。Proxy对象允许我们在访问和修改对象属性时拦截并执行自定义的操作

2、当我们访问响应式数据对象的属性时,Proxy对象会拦截这个操作,并建立一个依赖关系,将这个属性和正在访问它的地方关联起来

3、当我们修改响应式数据对象的属性时,Proxy对象同样会拦截这个操作,并通知所有依赖于这个属性的地方进行更新

4、为了更高效地进行依赖追踪,Vue 3使用了一个称为“依赖收集”的技术。在访问响应式数据对象的属性时,Vue 3会将正在访问这个属性的地方收集起来,并建立一个依赖关系图。当属性发生变化时,Vue 3会根据这个依赖关系图,只更新那些真正依赖于这个属性的地方

proxy响应式主要配合Reflect进行数据响应的,reflect基本包含proxy中的所有方法

diff算法:

vue2是通过双端比较的策略,而vue采用的是链表结构的策略;vue3中对diff算法进行了优化,新增了静态提升,他会给每个dom节点都添加一个静态id,然后在比较过程中如果没有发生变化,就不会进行计算,可以节省很大的计算开销

TypeScript支持:

vue3默认支持typeScript语法,typeScript语法提供了类型声明,可以让我们在开发过程中更容易的找出错误和代码规范编写

CompositionApi:

vue3引入了组合式api,相比于vue2的选项式api组合式api可以让开发者更灵活的组织和重用组件逻辑,可以将组件的逻辑按照功能划分为多个函数

TreeShaking:

TreeShaking是通过构建工具(webpack)来实现的,vue脚手架中集成webpack,主要用于消除未使用的代码,减小最终打包文件的体积,由于vue3是组合式Api支持按需加载可以更契合Treeshaking;

Fragments(片段)和teleport(传送门):

fragments允许你返回多个相邻的元素,在开发过程中,可以遍写多个相邻的元素,而不用包裹在一个父元素中,在编译时会自动的给他们包裹一个父元素

Teleport:是用于将组件的内容渲染到dom中的不同位置的特性,它里边有一个to属性,比如说to=”body“,就是吧这个节点数据发送到body上,可以更灵活运用组件代码

3、什么是SPA?

定义:

SPA(single-page application),翻译过来就是单页面应用;SPA是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索

优点:

具有桌面应用的即时性、网站的可移植性和可访问性

用户体验好、快,内容的改变不需要重新加载整个页面

良好的前后端分离,分工更明确

缺点:

不利于搜索引擎的抓取

首次渲染速度相对较慢

4、vuex的原理,与pinia的比较?

vuex
定义:

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

五大核心属性:
      1. state:基本数据(数据源存放地)
      2. getters:从基本数据派生出来的数据;
      3. mutations:提交更改数据的方法,同步
      4. action:像一个装饰器,包裹mutations,使之可以异步
      5. modules:模块化Vuex

优点:统一管理数据;

缺点:如果使用不当就会造成全局数据紊乱;

实现:
// 目标1:实现Store类,管理state(响应式的),commit方法和dispatch方法
// 目标2:封装一个插件,使用更容易使用
let Vue;

class Store {
  constructor(options) {
    // 定义响应式的state
    // this.$store.state.xx
    // 借鸡生蛋
    this._vm = new Vue({
      data: {
        $$state: options.state
      }
    })
    
    this._mutations = options.mutations
    this._actions = options.actions

    // 绑定this指向
    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }

  // 只读
  get state() {
    return this._vm._data.$$state
  }

  set state(val) {
    console.error('不能直接赋值呀,请换别的方式!!天王盖地虎!!');
    
  }
  
  // 实现commit方法,可以修改state
  commit(type, payload) {
    // 拿出mutations中的处理函数执行它
    const entry = this._mutations[type]
    if (!entry) {
      console.error('未知mutaion类型');
      return
    }

    entry(this.state, payload)
  }

  dispatch(type, payload) {
    const entry = this._actions[type]

    if (!entry) {
      console.error('未知action类型');
      return
    }

    // 上下文可以传递当前store实例进去即可
    entry(this, payload)
  }
}

function install(_Vue){
  Vue = _Vue

  // 混入store实例
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    }
  })
}

// { Store, install }相当于Vuex
// 它必须实现install方法
export default { Store, install }
pinia?

Pinia是一个基于Vue 3的状态管理库,它提供了类似于Vuex的全局状态管理功能,但在某些方面有一些不同之处。以下是一些Pinia相对于Vuex的特点

类型安全:Pinia使用了TypeScript并且完全支持类型推断,这使得在开发过程中更容易进行类型检查和代码提示

轻量级:Pinia具有非常小的体积,并且没有任何依赖,因此它可以更轻松地集成到你的项目中

Composition API:Pinia与Vue 3的Composition API紧密集成,使得在组合式函数中更容易使用和管理状态

插件机制:Pinia支持插件机制,允许你在应用中添加自定义功能,例如数据持久化、路由导航守卫等

pinia中只有state、getters和actions;

state是一个回调函数。用于存放数据

actions同时支持异步和同步任务;

getters:计算属性用于计算和获取state的数据

// store.js
import {defineStore} from 'pinia';
export const useStore = defineStore('main',{
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
  },
})

// main.js

import Vue from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import { useStore } from './store'

Vue.use(createPinia())

new Vue({
  render: h => h(App),
  provide: { store: useStore() }, // 将 store 注入到全局
}).$mount('#app')
  
// 组件中使用
<!-- Counter.vue -->
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

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

5、vue的生命周期?

定义:实例从创建到销毁的过程就是生命周期,即指从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程

beforeCreate 、created 、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed

beforeCreate

beforeCreate

在实例初始化之后,数据观测(data observer) 之前被调用。

created

created

实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el

beforeMount

beforeMount

在挂载开始之前被调用:相关的 render 函数首次被调用

mounted

mounted

el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子

beforeUpdate

beforeUpdate

组件数据更新之前调用,发生在虚拟 DOM 打补丁之前

updated

updated

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子

beforeDestroy

beforeUnmount

实例销毁之前调用。在这一步,实例仍然完全可用

destroyed

unmounted

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

6、vue组件当中data为什么是一个函数

在Vue组件中,data必须是一个函数,这是因为每个Vue组件都是一个Vue实例,如果data不是一个函数而是一个对象,那么所有的实例将共享同一个数据对象,一旦一个组件修改了这个数据对象,其他所有的组件都会受到影响,这显然是我们不希望看到的

而如果data是一个函数,那么每个Vue组件实例都会调用这个函数,返回一个全新的数据对象,这样每个组件实例都有自己独立的数据对象,互不影响,这就保证了组件的独立性和可复用性

为什么我们定义数据时使用data定义而访问的时候不需要使用this.data.属性进行访问呢?

因为vue在内部间接的做了一个数据代理,在生成数据时他会在内部生成一个_data属性就是data里的所有值,并且呢会给vue实例对象中循环添加_data里的所有键值对数据,而在底层数据响应使用了object.defineProperty做了数据劫持,当我们访问vue实例对象中的内容时,访问的就是_data的属性内容,当我们修改vue实例对象时,修改的也是_data的属性内容。

7、vue组件通信的方式?

    • 父传子:通过v-bind传递给子组件,子组件内使用props属性进行接收
<!-- 父组件 -->
<ChildComponent :message="parentMsg" />
  <!-- 子组件 -->
   props: {
    // 定义名为 message 的 props 属性,用于接收父组件传递的数据
    message: String
  }
    • 子传父:子组件通过$emit将方法传递给父组件,父组件使用@传递的方法名称=“父组件定义的方法”接收并使用
<!-- 子组件 -->
handleClick() {
      // 使用 $emit 方法触发名为 child-click 的事件,并传递一个参数
      this.$emit('child-click', 'Data from child component');
    }
<!-- 父组件     -->
    <!-- 使用 ChildComponent 组件,并监听子组件触发的 child-click 事件 -->
    <ChildComponent @child-click="handleChildClick" />

<script>
export default {
  data() {
    return {
      dataFromChild: '' // 用于存储来自子组件的数据
    };
  },
  methods: {
    // 定义名为 handleChildClick 的方法,用于接收子组件传递的数据
    handleChildClick(data) {
      // 将子组件传递过来的数据存储在 dataFromChild 中
      console.log(data);
    }
  }
};
</script>
    • v-mobel:使用v-mobel给子组件绑定变量,然后子组件使用props接收,但是props只能命名value且只能传递一个值
<!-- 父组件 -->
<ChildComponent v-model="parentValue" />
  <!-- 子组件 -->
  <div>
    <!-- 子组件内使用 props 接收来自父组件的值 -->
    <input :value="value" @input="$emit('input', $event.target.value)" />
  </div>
  <script>
   defineProps(['parentValue']);
  </script>
   
    • .sync修饰符:通过要传递的属性.sync传递给子组件,通过使用props:['model']接收,名称是固定的,它可以传递多个参数
<!-- 父组件 -->
<ChildComponent :model.sync="parentModel" :color.sync="parentColor" />
 <!-- 子组件  -->
<input :value="model" :style="{ color: color }" />
<script>
defineProps(['model','color'])
</script>
    • 跨组件通信:provide和inject在父组件中定一个provide属性,然后再其他的组件内去使用inject["属性/方法"]进行接收
// ParentComponent.vue
import { ref,provide } from 'vue'; //在父组件中引入provide
let data = ref(124); //定义数据
provide('data', data); //使用provide以key,value的形式为所有的子组件提供数据

// ChildComponent.vue
import { inject } from 'vue' //在子组件/后代组件内引入inject
let data = inject('data') ; //使用inject函数进行接收
console.log(data.value) 

8、vue-router的原理?

Vue Router是Vue.js官方提供的路由管理库,用于实现前端路由。它通过改变URL来实现不同页面之间的切换,同时还提供了导航守卫、动态路由、嵌套路由等功能。

路由分为hash路由和history路由

hash:
    • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
    • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制 hash 的切换;
    • location.hash来获取hash值;
    • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)
history:
    • pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
    • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
    • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)

Vue Router的原理可以简单概括为以下几个步骤:

      • 作为一个插件存在:实现VueRouter类和install方法
      • 实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转
      • 监控url变化:监听hashchange或popstate事件
      • 响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示
// 我们的插件:
// 1.实现一个Router类并挂载期实例
// 2.实现两个全局组件router-link和router-view
let Vue;

class VueRouter {
  // 核心任务:
  // 1.监听url变化
  constructor(options) {
    this.$options = options;

    // 缓存path和route映射关系
    // 这样找组件更快
    this.routeMap = {}
    this.$options.routes.forEach(route => {
      this.routeMap[route.path] = route
    })

    // 数据响应式
    // 定义一个响应式的current,则如果他变了,那么使用它的组件会rerender
    Vue.util.defineReactive(this, 'current', '')

    // 请确保onHashChange中this指向当前实例
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }

  onHashChange() {
    // console.log(window.location.hash);
    this.current = window.location.hash.slice(1) || '/'
  }
}

// 插件需要实现install方法
// 接收一个参数,Vue构造函数,主要用于数据响应式
VueRouter.install = function (_Vue) {
  // 保存Vue构造函数在VueRouter中使用
  Vue = _Vue

  // 任务1:使用混入来做router挂载这件事情
  Vue.mixin({
    beforeCreate() {
      // 只有根实例才有router选项
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }

    }
  })

  // 任务2:实现两个全局组件
  // router-link: 生成一个a标签,在url后面添加#
  // <a href="#/about">aaaa</a>
  // <router-link to="/about">aaa</router-link>
  Vue.component('router-link', {
    props: {
      to: {
        type: String,
        required: true
      },
    },
    render(h) {
      // h(tag, props, children)
      return h('a',
        { attrs: { href: '#' + this.to } },
        this.$slots.default
      )
      // 使用jsx
      // return <a href={'#'+this.to}>{this.$slots.default}</a>
    }
  })
  Vue.component('router-view', {
    render(h) {
      // 根据current获取组件并render
      // current怎么获取?
      // console.log('render',this.$router.current);
      // 获取要渲染的组件
      let component = null
      const { routeMap, current } = this.$router
      if (routeMap[current]) {
        component = routeMap[current].component
      }
      return h(component)
    }
  })
}

export default VueRouter
 

9、vue虚拟dom和key?

在Vue.js中,虚拟DOM(Virtual DOM)是一种用JavaScript对象来表示真实DOM的轻量级数据结构。它的作用是在页面更新时,通过比较新旧虚拟DOM的差异,最小化地更新实际的DOM,提高页面渲染的效率。
key是用来标识虚拟DOM节点的唯一性的属性。在Vue.js中,当有多个相同类型的子节点需要进行循环渲染时,需要为每个节点设置唯一的key值。

key的作用主要有以下几点:

  1. 提高更新效率:通过key,Vue.js能够更准确地追踪每个节点的变化,减少不必要的DOM操作。
  2. 维持组件状态:当有相同类型的子组件需要进行循环渲染时,Vue.js会根据key值来判断是否需要销毁旧的组件实例,创建新的组件实例,从而保证每个组件的状态是独立的。
  3. 提供稳定的顺序:在某些情况下,我们可能希望保持循环渲染的子节点的稳定顺序,而不是让它们根据数据的变化而随机重新排序。设置key可以确保每个子节点的顺序是稳定的。

10、vue的diff算法?

vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式+双指针(头尾都加指针)的方式进行比较;

vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数获得最新的虚拟DOM,然后执行patch函数,并传入新旧两次虚拟DOM,通过比对两者找到变化的地方,最后将其转化为对应的DOM操作

  • 同级比较: Vue 只会比较同级的节点,不会跨层级比较。
  • 唯一标识: Vue 使用每个节点的唯一标识(key)来区分节点,从而减少操作的次数。
  • 就地复用: 当节点的 key 相同时,Vue 会将其就地复用,而不是销毁和重新创建。
  • 深度遍历: 会先比较Vue 会比较组件的根节点,并递归地比较其子节点。
  • 异步更新: Vue 在数据变化后,会通过异步更新的方式进行批量更新,以提高性能

vue3中对diff算法进行了优化,新增了静态提升,他会给每个dom节点都添加一个静态标记 ,然后在比较过程中如果没有发生变化,就不会进行计算,可以节省很大的计算开销;Vue3中采用最长递增子序列实现diff算法

最长递增子序列:

1.动态规划;

2.二分查找:在有序的列表中不断地折半查找,高效的方式找到最优值;

3.贪心算法:在每一步做选择的时候,始终选择局部最优解;

4.回溯修正:构建反向链表,每个节点记录上一个节点位置,最后回溯修正;

11、$nextTick的原理?

定义:在dom更新之后延迟执行回调函数;$nextTick 是 Vue.js 中的一个异步更新方法,它的原理涉及到 Vue.js 的更新机制和事件循环。

    • 事件循环
      • JavaScript 是单线程的,它通过事件循环来处理异步任务。在事件循环中,分为宏任务(例如 setTimeout、setInterval)和微任务(例如 Promise.then、MutationObserver)。
    • $nextTick 的作用
      • 当需要在 Vue.js 更新队列中的所有变化都应用到 DOM 后执行一些操作时,可以使用 $nextTick 方法。
      • $nextTick 会在 Vue.js 更新队列中的所有 DOM 更新完成之后执行传入的回调函数。
    • 原理分析
      • 当调用 $nextTick 方法时,Vue.js 会将传入的回调函数放入一个微任务队列中。
      • 在当前执行栈执行完毕后,事件循环会检查微任务队列,并将其中的任务依次执行。
      • 因此,当所有的 DOM 更新都完成后,Vue.js 将会执行 $nextTick 中的回调函数

12、插槽slot?

定义:插槽是一种用于在组件之间传递内容的机制,插槽允许你定义的一些带有预定义位置的占位符,然后在组件的父组件中去填充,使得父组件可以向子组件传递内容,子组件既可以在特定的位置渲染这些内容

分为默认插槽、具名插槽和作用域插槽

1.默认插槽:

它是基本的插槽形式,它允许父组件在子组件中插入任意内容

<!-- 子组件child.vue -->
<template>
    <slot>
      <p>插槽后备的内容</p>
    </slot>
</template>
<!-- 父组件 -->
<Child>
  <div>默认插槽</div>  
</Child>
2.具名插槽:

具名插槽允许父组件在子组件中定义多个插槽,并分别向这些插槽插入内容

<!-- 子组件child -->
<template>
    <slot>插槽后备的内容</slot>
  <slot name="content">插槽后备的内容</slot>
</template>
<!-- 父组件 -->
<child>
    <template v-slot:default>具名插槽</template>
    <!-- 具名插槽⽤插槽名做参数 -->
    <template v-slot:content>内容...</template>
</child>
3.作用域插槽:

作用域插槽允许子组件向插槽传递数据,让父组件能够访问子组件的数据。这通过在插槽上使用属性绑定的方式实现,

<!-- 子组件 child -->
<template> 
  <slot name="footer" testProps="子组件的值">
          <h3>没传footer插槽</h3>
    </slot>
</template>

<!-- 父组件 -->
<child> 
    <!-- 把v-slot的值指定为作⽤域上下⽂对象 -->
    <template v-slot:default="slotProps">
      来⾃⼦组件数据:{{slotProps.testProps}}
    </template>
    <template #default="slotProps">
      来⾃⼦组件数据:{{slotProps.testProps}}
    </template>
</child>

13、缓存组件 keep-alive?

定义:

keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM

keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

属性:

keep-alive可以设置以下props属性:

    • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
    • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
    • max - 数字。最多可以缓存多少组件实例
<keep-alive include="a,b" max="4">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/" max="4">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']" max="4">
  <component :is="view"></component>
</keep-alive>
原理解析:
  1. 缓存组件实例
    • 当使用 keep-alive 包裹一个组件时,该组件的实例会被缓存起来而不被销毁。
    • 这意味着,即使组件被切换或者被隐藏,其实例仍然存在于内存中,而不会重新创建。
  1. 生命周期钩子
    • 当一个组件被包裹在 keep-alive 中时,它的生命周期钩子不会被销毁,而是会触发一些新的钩子事件。
    • activated:当被包裹的组件被激活时调用。
    • deactivated:当被包裹的组件被停用时调用。
  1. 虚拟 DOM
    • Vue 使用虚拟 DOM 来管理组件的渲染和更新。
    • 当一个组件被激活时,Vue 会重新渲染该组件,并将其显示在页面上。
    • 当一个组件被停用时,Vue 不会销毁其实例,而是将其隐藏起来,并保留其在虚拟 DOM 中的状态。
  1. 缓存策略
    • keep-alive 提供了 include 和 exclude 属性,用于指定哪些组件需要被缓存或排除在缓存之外。
    • 可以根据实际需求,通过这些属性来定义更灵活的缓存策略。

14、computed、methods、watch?

在Vue中,computedmethodswatch都是用于处理数据和响应式变化的API,但它们之间有一些区别。

  1. 作用方式不同:
    • computed用于计算属性,它会根据依赖的响应式数据自动计算出一个新值,并缓存起来,只有当依赖的数据发生变化时才会重新计算。
    • methods用于定义普通的方法,它可以执行任意逻辑,但不具有缓存的功能,每次调用都会重新执行。
    • watch用于监听指定数据的变化,并在数据变化时执行回调函数。它可以监听单个数据、多个数据、对象属性或数组元素的变化。
  1. 返回值不同:
    • computed会缓存计算结果,并返回一个响应式对象。
    • methods不会缓存计算结果,并且不会返回任何值。
    • watch不会返回任何值,但可以在回调函数中执行任意逻辑。

综上所述,computedmethodswatch各有其特点,可以根据实际需求选择合适的API。如果需要根据其他响应式数据计算出一个新值,则应使用computed;如果只需要执行一些逻辑来处理数据,则应使用methods;如果需要在数据变化时执行一些异步操作或复杂逻辑,则应使用watch

15、双向数据绑定的原理?

Vue的双向数据绑定是指数据的变化可以同时影响到视图的更新,而视图中输入框的变化也能够反过来更新数据。这种机制使得开发者能够更方便地处理用户输入和数据的同步更新。

Vue中的双向数据绑定是通过使用v-model指令来实现的。具体的工作原理如下:

  1. 当使用v-model指令时,Vue会自动为表单元素(通常是输入框)绑定一个value属性和一个input事件。
  2. 在初始渲染时,将数据模型中的值赋给value属性,从而将数据显示在视图中。
  3. 当用户在输入框中输入内容时,会触发input事件,Vue会监听该事件并将输入框的值更新到数据模型中。
  4. 数据模型中的值发生变化后,Vue会再次将新的值赋给value属性,从而更新视图中的内容。

通过这种方式,数据和视图之间建立了双向的绑定关系,数据的变化可以实时地反映到视图上,而用户在视图中输入的变化也能够及时地更新到数据模型中。

16、vue中数据变了视图不更新?

  1. 数据没有正确声明为响应式:在Vue中,只有通过Vue的data选项声明的数据才是响应式的,才能触发页面的更新。如果你的数据没有正确地声明为Vue实例的data属性,那么当数据发生变化时,页面不会自动更新。
  2. 数据更新时机的问题:Vue在进行数据更新时,可能会采用异步更新的策略,将多个数据的更新合并为一个更新操作,以提高性能。这意味着当你修改数据后,页面不会立即更新,而是在下一个事件循环中才会更新。如果你在数据更新后立即访问DOM,可能会导致页面尚未更新。
  3. 对象/数组变更检测的问题:Vue的响应式系统无法检测到对象属性的添加或删除,以及数组下标的直接修改。如果你直接修改了对象的属性或数组的下标,而没有使用Vue提供的方法(如Vue.set或数组的变异方法),那么页面不会更新。
  4. 计算属性或侦听器的问题:如果你使用了计算属性或侦听器来派生数据,确保它们正确地依赖于响应式数据。如果侦听器或计算属性没有正确依赖于数据,它们可能不会被触发,导致页面不更新

17、vue中mixin混入的理解?

mixin(混入),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。

本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等

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

在Vue中我们可以局部混入全局混入

局部混入
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}
Vue.component('componentA',{
  mixins: [myMixin]
})
全局混入
Vue.mixin({
  created: function () {
    console.log("全局混入")
  }
})

使用全局混入需要特别注意,因为它会影响到每一个组件实例(包括第三方组件)

注意事项:

当组件存在与mixin对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin的选项

但是如果相同选项为生命周期钩子的时候,会合并成一个数组,先执行mixin的钩子,再执行组件的钩子

18、vue修饰符?

在程序世界里,修饰符是用于限定类型以及类型成员的声明的一种符号

在Vue中,修饰符处理了许多DOM事件的细节,让我们不再需要花大量的时间去处理这些烦恼的事情,而能有更多的精力专注于程序的逻辑处理

在vue中修饰符分为以下五种:

      • 表单修饰符
      • 事件修饰符
      • 鼠标按键修饰符
      • 键值修饰符
      • v-bind修饰符
表单修饰符:

我们填写表单的时候用得最多的是input标签,指令用得最多的是v-model

1. lazy:
    • 在我们填完信息,光标离开标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步
<input type="text" v-model.lazy="value">
<p>{{value}}</p>
2. trim:
    • 自动过滤用户输入的首空格字符,而中间的空格不会过滤
<input type="text" v-model.trim="value">
3. number :
    • 自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值
<input v-model.number="age" type="number">
事件修饰符:

事件修饰符是对事件捕获以及目标进行了处理

1. stop

阻止了事件冒泡,相当于调用了event.stopPropagation方法

<div @click="shout(2)">
  <button @click.stop="shout(1)">ok</button>
</div>
//只输出1
2. prevent

阻止了事件的默认行为,相当于调用了event.preventDefault方法

<form v-on:submit.prevent="onSubmit"></form>
3. self

只当在 event.target 是当前元素自身时触发处理函数

<div v-on:click.self="doThat">...</div>
4. once

绑定了事件以后只能触发一次,第二次就不会触发

<button @click.once="shout(1)">ok</button>

鼠标按钮修饰符:

鼠标按钮修饰符针对的就是左键、右键、中键点击

  • left 左键点击
  • right 右键点击
  • middle 中键点击
<button @click.left="shout(1)">ok</button>
<button @click.right="shout(1)">ok</button>
<button @click.middle="shout(1)">ok</button>
键盘修饰符:

键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)

  • 普通键(enter、tab、delete、space、esc、up...)
  • 系统修饰键(ctrl、alt、meta、shift...)
// 只有按键为keyCode的时候才触发
<input type="text" @keyup.keyCode="shout()">
v-bind修饰符:

v-bind修饰符主要是为属性进行操作

1. .sync:

用于父子组件通信

//父亲组件
<comp :myMessage="bar" @update:myMessage="func"></comp>
func(e){
this.bar = e;
}
//子组件js
func2(){
this.$emit('update:myMessage',params);
}

2. prop

设置自定义标签属性,避免暴露数据,防止污染HTML结构

<input id="uid" title="title1" value="1" :index.prop="index">
3. camel

将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox

<svg :viewBox="viewBox"></svg>

19、Vue中如何检测数组变化?

// 调用方法:Vue.set(target, key, value )
// target:要更改的数据源(可以是对象或者数组)
// key:要更改的具体数据
// value :重新赋的值
const vm = new Vue({
        el: '#app',
        data: { // vm._data  
            user: {name:'poetry'}
        }
    });

// Vue.set
Vue.set(vm.user, "age", 20)
// vm.$set,Vue.set的一个别名
vm.$set(vm.user, "age", 20)

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想),所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新

Vue 将 data 中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组api 时,可以通知依赖更新,如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。

let oldArray = Object.create(Array.prototype);
['shift', 'unshift', 'push', 'pop', 'reverse','sort'].forEach(method => {
  oldArray[method] = function() { // 这里可以触发页面更新逻辑
    console.log('method', method)
    Array.prototype[method].call(this,...arguments);
  }
});
let arr = [1,2,3];
arr.__proto__ = oldArray;
arr.unshift(4);

20、vue中如何自定义指令?

vue2:

你可以使用全局方法`Vue.directive`或者在组件选项中的`directives`属性来定义自定义指令。自定义指令可以包含多个钩子函数,如`bind`、`inserted`、`update`、`componentUpdated`和`unbind`。你可以在这些钩子函数中定义指令的行为和逻辑

// 全局自定义指令
Vue.directive('my-directive', {
  bind: function (el, binding, vnode) {
    // 指令绑定时的逻辑
  },
  inserted: function (el, binding, vnode) {
    // 元素插入到DOM时的逻辑
  },
  update: function (el, binding, vnode) {
    // 组件更新时的逻辑
  },
  unbind: function (el, binding, vnode) {
    // 指令解绑时的逻辑
  }
});
// 组件中使用自定义指令
Vue.component('my-component', {
  template: '<div v-my-directive></div>'
});
vue3

在Vue 3中,自定义指令的实现方式有了一些变化。Vue 3使用了Composition API,并引入了`createApp`函数来创建Vue应用实例。你可以使用`app.directive`方法来定义自定义指令。

import { createApp, directive } from 'vue';

const app = createApp({});

// 自定义指令
app.directive('my-directive', {
  mounted(el, binding, vnode) {
    // 指令绑定时的逻辑
  },
  updated(el, binding, vnode) {
    // 元素更新时的逻辑
  },
  unmounted(el, binding, vnode) {
    // 指令解绑时的逻辑
  }
});

app.mount('#app');
区别:

Vue 3中的自定义指令钩子函数发生了变化,`bind`和`inserted`合并为`mounted`钩子函数,`update`和`componentUpdated`合并为`updated`钩子函数。此外,Vue 3中的自定义指令还引入了`unmounted`钩子函数,用于在指令解绑时执行逻辑

21、vue的过滤器?

过滤器实质不改变原始数据,只是对数据进行加工处理后返回过滤后的数据再进行调用处理,我们也可以理解其为一个纯函数

使用:

vue中的过滤器可以用在两个地方:双花括号插值和 v-bind 表达式,过滤器应该被添加在 JavaScript表达式的尾部,由“管道”符号指示

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

注意:当全局过滤器和局部过滤器重名时,会采用局部过滤器

局部过滤器:
filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}
全局过滤器:
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})
<div id="app">
    <p>{{ msg | msgFormat('疯狂','--')}}</p>
</div>

<script>
    // 定义一个 Vue 全局的过滤器,名字叫做  msgFormat
    Vue.filter('msgFormat', function(msg, arg, arg2) {
        // 字符串的  replace 方法,第一个参数,除了可写一个 字符串之外,还可以定义一个正则
        return msg.replace(/单纯/g, arg+arg2)
    })
</script>

22、vue3中组合式api有哪些? 和vue2的选项式api有什么区别?

    • setup 函数:每个组件都必须有一个 setup 函数,它是组合式API的入口点。在 setup 函数中可以定义响应式状态、计算属性、方法、引入其他组合函数等。
    • reactive 函数:用于创建一个响应式的数据对象。它类似于Vue2中的 data 选项,可以让对象的属性具有响应式能力。
    • ref 函数:用于创建一个对象的引用。它类似于Vue2中的 data 选项中的单个数据属性,但是可以确保引用的响应式。
    • computed 函数:用于创建一个计算属性。它类似于Vue2中的 computed 选项,可以根据响应式数据进行计算,生成一个派生的值。
    • watch 函数:用于创建一个观察响应式数据变化的副作用。它类似于Vue2中的 watch 选项,可以根据观察的变量进行一些特定的操作

Vue3的组合式API与Vue2的Options API有以下不同之处:

  • 分离关注点:Vue3的组合式API将组件的逻辑代码分离为可复用的函数,让代码更集中和模块化,而Vue2的Options API将逻辑分散在不同的选项中,难以维护和重用。
  • 更灵活的组织方式:Vue3的组合式API允许将逻辑按照功能进行拆解和组合,提高了代码的可维护性和可读性,而Vue2的Options API需要根据不同的生命周期钩子和选项来组织代码。
  • 更易于测试:由于Vue3的组合式API将逻辑代码集中在函数中,使得逻辑更易于测试,而Vue2的Options API需要在不同的选项和生命周期钩子中进行测试。

23、ref和reactive的区别,底层原理?

    • ref返回的响应式数据在JS中使用需要加上.value才能访问其值,在视图中使用会自动脱ref,不需要.value;ref可以接收对象或数组等非原始值,但内部依然是reactive实现响应式
    • reactive内部使用Proxy代理传入对象并拦截该对象各种操作,从而实现响应式。ref内部封装一个RefImpl类,并设置get value/set value,拦截用户对值的访问,从而实现响应式,使用展开运算符(...)展开reactive返回的响应式对象会使其失去响应性,可以结合toRefs()将值转换为Ref对象之后再展开
  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值