[前端面试]vue八股

mvvm 与mvc模式的区别

是三种不同的软件架构模式,都体现了关注点分离的思想,其不同点如下

mvc

  • model 数据层,负责与数据库或远程服务器交互,操作数据

  • view 视图层,负责用户界面的呈现,不包含任何业务逻辑,仅显示从model获取的数据

  • controller 控制器层,协调model与view,处理用户输入并更新Model与View

mvp模式

  • Model:与 MVC 中相同,负责数据的管理。
  • View:与 MVC 中相同,负责显示内容。
  • Presenter:充当中介者,从 Model 获取数据并更新 View,而且能够处理复杂的逻辑,减轻 View 的负担。

mvvm

  • model 数据层,负责数据的管理
  • view 视图层,显示用户界面
  • ViewModel 连接model与view,通过数据绑定,view自动更新响应一切Model的变化,从而显著简化代码量

vue中的mvvm

  • Model:数据状态(在 Vue 中通过 data 属性定义)。
  • View:模板(在 Vue 中通过 HTML、模板语法和 {{ }} 插值)。
  • ViewModel:Vue 实例,它连接了 Model 和 View,通过双向数据绑定(Vue 的核心功能之一)使得 View 会自动更新以响应 Model 的变化。

补充

1)MVC:最早提出的设计模式之一,广泛应用于 Web 开发,如传统的 Java EE、ASP.NET 应用中。典型的例子是使用 Spring MVC 框架进行 Java Web 开发,View 层通常使用 JSP 或 Thymeleaf。

2)MVP:在 Android 开发中使用较多。在 Android 中,由于 Activity 和 Fragment 既承担了逻辑又负责了 UI 渲染,使用 MVP 模式可以通过 Presenter 来分离这些责任,使得代码更易维护和测试。

3)MVVM:不仅在 Vue 中使用,而且在其他现代前端框架如 Angular 和 React(虽然更偏向于 Flux/Redux 架构)中也有类似的实现。MVVM 使得开发者可以专注于业务逻辑和数据流,框架会处理具体的 DOM 操作和视图更新。Vue 提供了简洁的双向数据绑定,Angular 则使用了复杂的依赖注入和数据绑定,而 React 虽然采用单向数据流,但通过 Hooks、Context API 也能实现类似 MVVM 的效果。

vue2 与vue3 的区别

Vue 内部根据功能可以被分为三个大的模块:响应性 reactivite、运行时 runtime、编辑器 compiler

响应式

首先先来说 响应性 reactivite

vue2 的响应性主要依赖 Object.defineProperty 进行实现,但是 Object.defineProperty 只能监听 指定对象的指定属性的 getter 行为和 setter 行为,那么这样在某些情况下就会出现问题。

什么问题呢?

比如说:我们在 data 中声明了一个对象 person ,但是在后期为 person 增加了新的属性,那么这个新的属性就会失去响应性。想要解决这个问题其实也非常的简单,可以通过 Vue.$set 方法来增加 指定对象指定属性的响应性。但是这样的一种方式,在 Vue 的自动响应性机制中是不合理。

所以在 Vue3 中,Vue 引入了反射和代理的概念,所谓反射指的是 Reflect,所谓代理指的是 Proxy。我们可以利用 Proxy 直接代理一个普通对象,得到一个 proxy 实例 的代理对象。在 vue3 中,这个过程通过 reactive 这个方法进行实现。

但是 proxy 只能实现代理复杂数据类型,所以 vue 额外提供了 ref 方法,用来处理简单数据类型的响应性。ref 本质上并没有进行数据的监听,而是构建了一个 RefImpl 的类,通过 setget 标记了 value 函数,以此来进行的实现。所以 ref 必须要通过 .value 进行触发,之所以要这么做本质是调用 value 方法

运行时

接下来是运行时 runtime

所谓的运行时,大多数时候指的是 renderer 渲染器,渲染器本质上是一个对象,内部主要三个方法 render、hydrate、createApp ,其中 render 主要处理渲染逻辑,hydrate 主要处理服务端渲染逻辑,而 createApp 就是创建 vue 实例的方法。

这里咱们主要来说 render 渲染函数vue3 中为了保证宿主环境与渲染逻辑的分离,把所有与宿主环境相关的逻辑进行了抽离,通过接口的形式进行传递。这样做的目的其实是为了解绑宿主环境与渲染逻辑,以保证 vue 在非浏览器端的宿主环境下可以正常渲染。

编译器

再往下是 编辑器 compiler

vue 中的 compiler 其实是一个 DSL(特定领域下专用语言编辑器) ,其目的是为了把 template 模板 编译成 render 函数。 逻辑主要是分成了三大步: parse、transform 和 generate。其中 parse 的作用是为了把 template 转化为 AST(抽象语法树)transform 可以把 AST(抽象语法树) 转化为 JavaScript AST,最后由 generateJavaScript AST 通过转化为 render 函数。转化的过程中会涉及到一些稍微复杂的概念,比如 有限自动状态机 这个就不再这里展开说了。

除此之外,还有一些其他的变化。比如 vue3 新增的 composition APIcomposition APIvue3.0vue3.2 中会有一些不同的呈现,比如说:最初的 composition APIsetup 函数作为入口函数, setup 函数必须返回两种类型的值:第一是对象,第二是函数。

setup 函数返回对象时,对象中的数据或方法可以在 template 中被使用。当 setup 函数返回函数时,函数会被作为 render 函数。

但是这种 setup 函数的形式并不好,因为所有的逻辑都集中在 setup 函数中,很容易出现一个巨大的 setup 函数,我们把它叫做巨石(屎山)函数。所以 vue 3.2 的时候,新增了一个 script setup 的语法糖,尝试解决这个问题。目前来看 script setup 的呈现还是非常不错的。

组合式API 与选项式API
optionAPI
通过data ,methods,computed等选项来组织代码

  • vue自动将data转化为响应式数据

  • this.$refs 访问模版引用

compositionAPI

  • 通过setup函数,将逻辑按照功能拆分为多个可复用的函数或变量,增强模块化与可阅读性

  • 可以自定义钩子函数,方便复用与组合不同的功能模块

  • 显式引入生命周期钩子才能使用

  • 显示使用ref/reactive 创建响应式数据

vue 与react 的区别

1)核心思想

  • React:主要关注组件,采用函数式编程和声明式编程思想。组件可以通过函数或类来定义。
  • Vue:强调简洁和易用性,依赖模板语法。主要使用声明式渲染和响应式系统。

2)数据绑定

  • React:采用单向数据流,即数据从父组件流向子组件,基于 state 和 props。
  • Vue:支持双向数据绑定(v-model),数据可以在组件和视图之间双向流动。

3)组件生命周期

  • React:提供了一组生命周期方法(如 componentDidMount, componentDidUpdate, componentWillUnmount)。
  • Vue:同样提供了一套生命周期钩子(如 created, mounted, updated, destroyed)且相对更直观。

4)模板和 JSX

  • React:使用 JSX 语法,允许在 JS 代码内编写类似 XML 的模板。
  • Vue:使用模板语法,更类 HTML。不强制使用 JSX,但也支持 JSX。

5)状态管理

  • React:通常结合 Redux 或 MobX 来进行状态管理,React 本身也有 Context API。
  • Vue:通常使用 Vuex 进行状态管理,Vuex 更加集成和官方支持。

ref 与 reactive 的区别

推荐使用ref
在这里插入图片描述

  • ref 用于将基本类型的数据(如字符串、数字,布尔值等)和引用数据类型(对象) 转换为响应式数据。使用 ref 定义的数据可以通过 .value 属性访问和修改。
  • reactive 用于将对象转换为响应式数据,包括复杂的嵌套对象和数组。使用 reactive 定义的数据可以直接访问和修改属性。

reactive 有限值类型

reactive 只能声明引用数据类型
ref 能声明基本数据类型,也能声明引用数据类型

reactive使用不当失去响应式

通常在页面数据回显时,需要将AJAX请求获取的对象直接赋值给响应式对象,如果操作不当就导致reactive声明的对象失去响应

reactive对象赋值方式会失去响应式

给reactive的响应式对象赋值普通对象会失去响应

let state = reactive({ count: 0 })
//这个赋值将导致state失去响应
state = {count: 1}

给reactive的响应式对象赋值响应式对象也会失去响应

<template>
  {{state}}
</template><stcirpt setup>
const state = reactive({ count: 0 })
 //nextTick异步方法中修改state的值
nextTick(() => {
  //并不会触发修改DOM  ,说明失去响应了
  state = reactive({ count: 11 });
});
</stcirpt>

reactive对象 如何赋值

Object.assign

let state = reactive({ count: 0 })
// state =  {count:1}   state失去响应
state = Object.assign(state , {count:1})

属性一个一个赋值

let state = reactive({ count: 0 })
//state={count:1}
state.conut = 1 

为什么赋值对象ref不会失去响应式

ref 定义的数据(包括对象)时,返回的对象是一个包装过的简单值,而不是原始值的引用;

reactive定义数据(必须是对象),reactive返回的对象是对原始对象的引用

如何解开reactive响应式

对象属性赋值给变量

let state = reactive({ count: 0 })
//赋值
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++
console.log(state.count) //0

对象解构

let state = reactive({ count: 0 })
//普通解构count 和 state.count 失去了响应性连接
let { count } = state 
count++ // state.count值依旧是0

不解开响应式

const state = reactive({ count: 0 })
//使用toRefs解构,后的属性为ref的响应式变量
let { count } = toRefs(state)
count.value++ // state.count值改变为1

watch 与 watch Effect 区别

在 Vue 3 中,watchwatchEffect 都是用于观察和反应式响应数据变化的工具,但它们有一些明显的区别。

1)watch:它用于监听某个特定的响应式数据或多个响应式数据的变化。你需要显式地指定要监听的属性。如果监听到变化,你可以执行某个回调函数。比如在监听用户输入、API 数据变化时特别有用。

  • 当你有多个数据需要监听时,watch 非常方便。通过设置第一个参数为要监听的响应式数据,第二个参数为回调函数即可。
  • 它提供了深度监听选项(deep)和立即执行选项(immediate),这是当数据结构复杂或需要首次运行回调时极其有用的特性。
watch(() => state.value, 
	  (newValue, oldValue) => {
		  //do somthing
      }, 
     { immediate: true, deep: true }
     );

2)watchEffect:它会在其依赖的响应式数据发生变化时自动重新执行整个函数。这通常用于一些副作用操作,比如直接更新 DOM,或者在组件生命周期内需要立刻响应的逻辑。

  • 更适合用于简单、直接的响应式追踪,不需要明确指定依赖的数据。它自动追踪函数内部使用的所有响应式数据。
  • 通常在函数需要立即执行,或者依赖项频繁变化的情况下使用。
watchEffect(() => {
  const result = state.value + 10;
  console.log(result); 
});

选择的时候一般遵循以下原则:

  • 在明确知道要监听的是哪些响应式数据,并且这个数据可能是复杂的对象或数组时,使用 watch
  • 在希望自动依赖追踪的情况下,或需要立即执行函数时,使用 watchEffect

vue2 与 vue3 生命周期

在这里插入图片描述

在这里插入图片描述

父子组件生命周期的执行顺序

当父子组件嵌套时,父组件和子组件各拥有各自独立的钩子函数,这些父子组件的这些钩子是如何交融执行,且执行顺序又是怎样的呢?

  1. 加载渲染过程

    • 父组件:beforeCreate -> created -> beforeMount
    • 子组件:beforeCreate -> created -> beforeMount -> mounted
    • 父组件:mounted(此时父组件会等待子组件完成挂载后才进行自己的挂载)

    在这个过程中,我们可以看到父组件在渲染完成之后并不是马上挂载,而是先等待子组件创建、渲染、挂载完成之后再去挂载。创建实例是从外到内的(即先创建父组件实例,再创建子组件实例),而渲染是从内到外的(即先渲染子组件,再渲染父组件)。

  2. 更新过程

    • 当子组件中数据改变时:父组件 beforeUpdate -> 子组件 beforeUpdate -> 子组件 updated -> 父组件 updated
    • 当父组件中数据改变时(且影响到子组件):父组件 beforeUpdate -> 父组件 updated(此时如果子组件也依赖于这个数据,则子组件也会触发相应的更新过程,但由于是父组件先更新,所以子组件的更新会在父组件之后)
  3. 销毁过程

    • 父组件 beforeDestroy -> 子组件 beforeDestroy -> 子组件 destroyed -> 父组件 destroyed

    在这个过程中,父组件会先触发beforeDestroy钩子函数,然后子组件依次触发beforeDestroy和destroyed钩子函数,最后父组件触发destroyed钩子函数。

vue3 的Composition API 相对于2的options API 有什么优势

Vue 3 中的 Composition API 和 Vue 2.x 的 Options API 主要区别在于设计思想和实现方式。简单来说,Composition API 提供了一种更灵活、可复用性更高的方式来组织代码和实现组件功能,而 Options API 则是基于选项的语法结构来定义组件。

1)组织方式

  • Options API 使用的是一种声明式的方式,通过 data、methods、computed 等选项来组织代码。
  • Composition API 则是通过 setup 函数,将逻辑按功能模块拆分为多个可复用的函数或变量,更具模块化和可读性。

2)复用性

  • 在 Options API 中,复用逻辑主要通过 mixins 和 HOC(高阶组件)来实现,但这些方式可能会导致命名冲突和逻辑碎片化等问题。
  • Composition API 可以使用钩子函数和自定义钩子,从而方便地复用和组合不同的功能模块,减少了命名冲突的可能性。

3)TypeScript 友好

  • Options API 对 TypeScript 的支持相对较弱,类型推断和代码提示方面表现不佳。
  • Composition API 在设计之初就考虑了对 TypeScript 的友好支持,使得开发者可以更方便地进行类型检查和推断。

扩展知识

除了上述的主要区别,这里再拓展一些与 Composition API 和 Options API 相关的知识点:

1)生命周期钩子: 在 Options API 中,我们有 created、mounted 等声明式的生命周期钩子。而在 Composition API 中,这些生命周期钩子需要通过 import { onMounted, onCreated } from 'vue' 来显式引入和调用。这使得状态逻辑可以更加集中和简洁。

2)响应式系统: 在 Options API 中,Vue 会自动将 data 转化为响应式数据,而在 Composition API 中,我们需要显式地使用 refreactive 创建响应式数据。例如:

const count = ref(0); const state = reactive({ count: 0 });

3)模板引用: 在 Options API 中,我们可以通过 this.$refs 来访问模板引用,而在 Composition API 中,可以使用 ref 组合式 API,更加直观和灵活。例如:

const myDiv = ref(null); onMounted(() => { console.log(myDiv.value); // DOM 元素 });

4)** Vue 生态系统的支持**: 虽然 Composition API 是 Vue 3 引入的新特性,但 Vue 核心团队一直在努力确保它与 Vue 2.x 生态系统中的插件和库兼容。因此,我们可以在现有项目中逐步过渡到 Composition API,而不必完全抛弃已有的代码和工具。

组件通信有什么方式

1)父组件与子组件之间通过props$emit进行通信。 2)兄弟组件之间通过事件总线(Event Bus)进行通信。 3)通过Vuex进行全局状态管理和通信。
4)使用provideinject在上下代组件间传递数据。 5)使用$refs直接访问子组件实例。

Vue的双向数据绑定如何实现

model改变,视图更新
视图改变,model更新

实现

在 Vue 中,双向绑定通常是通过 v-model 指令来实现的。Vue 的双向绑定是基于数据的响应式系统,通过数据劫持和观察者模式来实现的。

具体原理如下:
1)数据劫持:Vue 使用 Object.defineProperty() 方法为每个对象属性添加 getter 和 setter,用来劫持数据的读写操作。
2)依赖收集:在执行 getter 时,Vue 将当前的依赖(通常是一个渲染函数或计算属性)收集起来。
3)更新视图:当数据发生变化时,setter 会触发依赖的更新,通知相关的观察者,并更新视图。

为什么3用proxy代替2的defineProperty

Vue 3 使用 Proxy API 替代 defineProperty API 是因为 Proxy API 提供了更强大的功能和更高的性能。相比 defineProperty,
Proxy 能更全面地监听和捕获对对象的操作。
Proxy 能处理对象新增或删除属性的情况,以及对数组等复杂数据类型进行监听。
而 defineProperty 仅能拦截已存在的属性变化,对新属性和数组操作无能为力。

扩展知识

1)性能提升

  • defineProperty 需要遍历每个属性并为其定义 getter 和 setter,这对于大型对象或深层嵌套的对象来说可能会带来较大的性能开销。
  • Proxy 直接劫持整个对象的操作,不需要逐一处理对象的每个属性,因此性能更优。

2)功能更强大

  • defineProperty 只能监听对象中的具体属性,而 Proxy 可以捕获更多类型的操作,如对象的属性读取(get)、设置(set)、删除(deleteProperty)、函数调用(apply)等。
  • Proxy 支持对对象新增属性和删除属性的监听,而 defineProperty 只能在属性已经存在的情况下奏效,这对动态对象和数组尤其重要。

3)对数组的原生支持

  • 在 Vue 2 中,数组的一些变异方法(比如 push、pop 等)不能被对象的 defineProperty 拦截,因此 Vue 2 需要手动覆盖这些方法以实现金刚的重活动小时。
  • Proxy 可以直接监听所有数组的操作,比如 length 变化、索引值的增减等,使得定义响应更直观简洁。

4)语法更简洁

  • 使用 Proxy 替代 defineProperty 可以使代码变得更加直观和简洁。同时,也减少了开发者手动处理属性的繁琐步骤,提高了开发效率。

5)浏览器兼容性与未来展望

  • Proxy 是 ECMAScript 2015 (ES6) 的一部分,现代浏览器已经基本全面支持 Proxy。 虽然在过去 defineProperty 更易于平稳过渡,但随着时间推移, Proxy 已成为更理想的选择。

什么是虚拟dom?解析过程

描述ui的js对象

当数据变化,vue会根据新的数据创建新的虚拟dom

然后比较新旧虚拟dom,得到最小变动应用到真实dom上

1)初次渲染

创建虚拟dom树,将虚拟dom映射到真实dom上

2)状态更新

组件状态变化,生成新的虚拟dom树

3)Diff算法

对比新旧虚拟dom树

通过diff算法找出差异

4)Patch 更新

将差异一次性更新到真实的dom上,减少dom操作次数

1)构建虚拟 DOM:在运行时,Vue 会将模板转换为虚拟 DOM,这个虚拟 DOM 是一个 JavaScript 对象,用来表示真实的 DOM 结构。
2)比对虚拟 DOM:当数据变更时,Vue 会重新生成一个新的虚拟 DOM 树。然后,Vue 会将新旧虚拟 DOM 树进行比较(也叫 diff)。
3)更新真实 DOM:根据 diff 运算的结果,Vue 将针对变更部分生成真实的 DOM 更新逻辑,并且将这些变更应用到真实的 DOM 上。

Vue中key的作用

帮助vue快速找到虚拟dom中的元素

key 属性的主要作用是对元素加上了一个唯一标识,当vue进行diff算法时,如果子元素带有key,vue会通过key快速定位并对比新旧节点,准确地复用或更新节点

1)为什么需要 key:

  • 提高性能key 帮助 Vue 更快速地找到虚拟 DOM 中的元素,避免不必要的重新渲染。
  • 确保一致性:如果不使用 key,Vue 在处理新旧节点时,可能会复用错误的节点,造成状态混乱。比如输入框中已有输入值,但因为 DOM 复用错误,导致输入框显示错误内容。

2)diff 算法中的 key:

  • 静态节点和动态节点:静态节点的内容不会改变,所以不需要 key。但在列表渲染等动态节点中,key 是至关重要的。
  • 比对规则:Vue 的 diff 算法会先对比新旧虚拟节点的 key,如果 key 不一样,直接判定两个节点不相同,避免逐一比对子节点,显著提升性能。

3)其他场景中的 key:

  • v-for 列表渲染:在列表渲染时,key 是必不可少的。通常使用列表项的唯一标识来作为 key,比如数据库中的 id。
  • dom 元素重用:当条件渲染(如 v-if)时,通过设置 key,可以强制 Vue 创建新的元素,而不是复用现有元素。

4)Vue 警告提示:

  • Vue 在检测到列表渲染中没有 key 时,会在开发环境下给出警告,提示你最好设置 key,以避免性能问题和渲染错误。

diff算法

diff 算法会对比新旧两棵 Virtual DOM 树的差异,然后只更新必要的部分,从而减少 DOM 操作的次数。Vue 中的 diff 算法包括以下几个步骤:

  • 新旧节点的比较 diff 算法会首先比较新旧节点是否相同,如果相同,则继续比较子节点;如果不同,则进行下一步操作。

  • 对子节点进行比较 对新旧节点的子节点进行比较,具体分为以下四种情况:

    • 新节点没有子节点,旧节点有子节点:直接删除旧节点的子节点
    • 旧节点没有子节点,新节点有子节点:直接添加新节点的子节点
    • 新旧节点都有子节点:继续比较子节点
    • 新旧节点都有相同的子节点:对相同的子节点进行递归比较
  • 对旧节点多余的子节点进行删除 如果旧节点的子节点比新节点的子节点多,那么对于多余的子节点,直接进行删除。

在 diff 算法中,由于只更新必要的部分,所以可以大大提高 DOM 操作的效率。
这也是 Vue 可以实现高效渲染的重要原因之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值