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
的类,通过 set
和 get
标记了 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
,最后由 generate
把 JavaScript AST
通过转化为 render 函数
。转化的过程中会涉及到一些稍微复杂的概念,比如 有限自动状态机 这个就不再这里展开说了。
除此之外,还有一些其他的变化。比如 vue3
新增的 composition API
。 composition API
在 vue3.0
和 vue3.2
中会有一些不同的呈现,比如说:最初的 composition API
以 setup
函数作为入口函数, 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 中,watch
和 watchEffect
都是用于观察和反应式响应数据变化的工具,但它们有一些明显的区别。
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 生命周期
父子组件生命周期的执行顺序
当父子组件嵌套时,父组件和子组件各拥有各自独立的钩子函数,这些父子组件的这些钩子是如何交融执行,且执行顺序又是怎样的呢?
-
加载渲染过程:
- 父组件:beforeCreate -> created -> beforeMount
- 子组件:beforeCreate -> created -> beforeMount -> mounted
- 父组件:mounted(此时父组件会等待子组件完成挂载后才进行自己的挂载)
在这个过程中,我们可以看到父组件在渲染完成之后并不是马上挂载,而是先等待子组件创建、渲染、挂载完成之后再去挂载。创建实例是从外到内的(即先创建父组件实例,再创建子组件实例),而渲染是从内到外的(即先渲染子组件,再渲染父组件)。
-
更新过程:
- 当子组件中数据改变时:父组件 beforeUpdate -> 子组件 beforeUpdate -> 子组件 updated -> 父组件 updated
- 当父组件中数据改变时(且影响到子组件):父组件 beforeUpdate -> 父组件 updated(此时如果子组件也依赖于这个数据,则子组件也会触发相应的更新过程,但由于是父组件先更新,所以子组件的更新会在父组件之后)
-
销毁过程:
- 父组件 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 中,我们需要显式地使用 ref
和 reactive
创建响应式数据。例如:
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)使用provide
和inject
在上下代组件间传递数据。 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 可以实现高效渲染的重要原因之一。