Vue笔记
1. Vue的MVVM模型
- M(模型Model):对应data中的数据
- V(视图View):模版
- VM(视图模型ViewModel):Vue实例对象
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
1.1 MVC
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。
1.2 MVP
MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。这样 View 层和 Model 层耦合在一起,当项目逻辑变得复杂的时候,可能会造成代码的混乱,并且可能会对代码的复用性造成一些问题。
MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只知道 Model 的接口,因此它没有办法控制 View 层的更新,MVP 模式中,View 层的接口暴露给了 Presenter 因此可以在Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还包含了其他的响应逻辑。
2. 双向数据绑定原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发响应的监听回调来渲染视图。
原理:
- 我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。
- 因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
- 接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数。
- 此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
具体步骤:
- 1、需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
- 2、compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- 3、Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
(1)在自身实例化时往属性订阅器(dep)里面添加自己
(2)自身必须有一个update()方法
(3)待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调。 - 4、MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
注意:
- 响应式和双向数据绑定不一样。
- 响应式是Vue的核心特性之一,数据驱动视图,我们修改数据,视图会随之响应更新。
- 双向数据绑定通常是指我们使用的v-model指令的实现,是Vue的一个特性,也可以说是一个input事件和value的语法糖。 Vue通过v-model指令为组件添加上input事件处理和value属性的赋值。
3. 使用Object.defineProperty进行数据劫持有什么缺点
在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或给对象新增属性,这些都不能触发组件的重新渲染,因为Object.defineProperty不能拦截到这些操作。
1 | let arr = [1, 2, 3] |
对于数组而言,大部分操作都是拦截不到的,只是Vue内部通过重写函数的方式解决了这个问题。
在Vue3中已经不使用这种方式了,而是通过Proxy对对象进行代理,从而实现数据劫持。
使用Proxy的好处是它可以监听到任何方式的数据改变,唯一的缺点是兼容性问题,Proxy是ES6语法。
4. computed和watch的区别
4.1 comuted
computed属性是基于其依赖的响应式数据进行计算得出的属性,它会根据依赖的数据变化自动更新。computed属性是缓存的,只有在依赖的数据发生变化时才会重新计算,否则会直接返回缓存的结果。这使得computed适合用于对简单的数据进行计算或格式化操作。
适合用于基于响应式数据进行计算得出属性的场景,并具有缓存机制。
4.2 watch
相比之下,watch属性用于观察某个数据的变化并执行相应的操作。watch属性允许你对特定数据的变化做出响应,可以执行异步操作或复杂的逻辑。更多的是观察作用,无缓存性。
适合用于监听数据的变化并执行特定操作的场景,适合执行异步操作或复杂逻辑。
5. Vue mixins混入和extends继承
5.1 mixins
5.1.1 作用
mixins的作用时减少data、methods、钩子的重复作用
5.1.2 例子
假设我们需要在每个组件上添加name和time。在created时打出提示,并在beforeDestoryed报出存活时间。
新建log.js。 把重复的部分提出来。
1 | const log = { |
在需要应用的组件里使用
1 | <template> |
虽然此处,两个组件用可以通过this.name引用mixins中定义的name,但是两个组件引用的并不是同一个,而是各自创建了一个新的。如果在组件中定义相同的data,则此处会引用组件中的name,而非mixins中的。
5.1.3 选项合并
- 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
- 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
- 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
- 混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
1 | // 为自定义的选项 'myOption' 注入一个处理器。 |
5.2 extends
- vue的extends和mixins类似,通过暴露一个extends对象到组件中使用。
- extends会比mixins先执行。执行顺序:extends > mixins > 组件
- extends只能暴露一个extends对象,暴露多个extends不会执行。
5.3 总结
- extends传入的是对象写法,而mixins是数组写法
- mixins可以混入多个mixin,extends只能继承一个
- 优先级Vue.extend > extends > mixins
- 都是对父组件的扩充,包括methods、components、钩子等
- 触发钩子函数时,先调用extends,mixins的函数,再调用父组件的函数
- 虽然也能在创建mixin时添加data、template属性,但当父组件也拥有此属性时以父为准
- data、methods内函数、components和directives等键值对格式的对象均以父组件/实例为准
6. v-if和v-for为什么不能一起用
6.1 原因
原因:v-for 的执行优先级要比 v-if 要高
在v-for循环中,每循环一次,都需要进行v-if判断。如果循环1000次也要判断1000次,而v-if的判断相对的比较耗费性能。大量的判断会极大的降低项目质量。
6.2 如何避免
方法一
可以将v-if写在v-for的外层,这样就可以让v-if优先判断
1 | <template> |
方法二
当v-if的判断条件依赖于v-for的某个值时(item,index),则可以使用如下方法
这种方法不在dom上去判断是否显示,直接在计算属性(computed)上做好过滤,dom上(template中)直接遍历已经被过滤之后的计算属性。从计算成本上来说,在计算属性中过滤会比在dom中判断是否显示更低。
1 | <template> |
6.3 总结
- v-if不能和v-for一起使用的原因是v-for的优先级比v-if高,一起使用会造成性能浪费
- 解决方案有两种,把v-if放在v-for的外层或者把需要v-for的属性先从计算属性中过滤一次
- v-if和v-for的优先级问题在vue3中不需要考虑,vue3更新了v-if和v-for的优先级,使v-if的优先级高于v-for
7. Vue和React的区别
7.1 核心思想不同
- Vue的核心思想是尽可能的降低前端开发的门槛,是一个灵活易用的渐进式双向绑定的MVVM框架。
- React的核心思想是声明式渲染和组件化、单向数据流,React既不属于MVC也不属于MVVM架构。
7.2 组件写法上不同
- Vue的组件写法是通过template的单文件组件格式。
- React的组件写法是JSX+inline style,也就是把HTML和CSS全部写进JavaScript中。
7.3 Diff算法不同
- vue对比节点,如果节点元素类型相同,但是className不同,认为是不同类型的元素,会进行删除重建,但是react则会认为是同类型的节点,只会修改节点属性。
- vue的列表比对采用的是首尾指针法,而react采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到最后一个,从这点上来说vue的对比方式更加高效。
7.4 响应式原理不同
- React主要是通过setState()方法来更新状态,状态更新之后,组件也会重新渲染。
- vue会遍历data数据对象,使用Object.definedProperty()将每个属性都转换为getter和setter,每个Vue组件实例都有一个对应的watcher实例,在组件初次渲染的时候会记录组件用到了哪些数据,当数据发生改变的时候,会触发setter方法,并通知所有依赖这个数据的watcher实例调用update方法去触发组件的compile渲染方法,进行渲染数据。
7.5 封装程度不同
- 封装程度,vue封装程度更高,内置多个指令和数据双向绑定。
- react封装度比较低,适合扩展。
8. vue2和vue3的区别
8.1 生命周期
- vue3的生命周期在vue2生命周期名称前加上了“on”。
- Vue3 在组合式API(Composition API,下面展开)中使用生命周期钩子时需要先引入,而 Vue2 在选项API(Options API)中可以直接调用生命周期钩子,如下所示。
1 | // vue3 |
1 | // vue2 |
8.2 多根节点
- vue2中,在模板中如果使用多个根节点时会报错。
- vue3中,支持多个根节点,也就是 fragment。
8.3 Composition API
Vue2 是选项API(Options API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
8.4 异步组件Suspense
Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。
使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
1 | <template> |
在 List 组件(有可能是异步组件,也有可能是组件内部处理逻辑或查找操作过多导致加载过慢等)未加载完成前,显示 Loading…(即 fallback 插槽内容),加载完成时显示自身(即 default 插槽内容)。
8.5 Teleport
Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。
1 | <button @click="dialogVisible = true">显示弹窗</button> |
8.6 响应式原理
Vue2 响应式原理基础是 Object.defineProperty;Vue3 响应式原理基础是 Proxy。
那 Vue3 为何会抛弃它呢?那肯定是因为它存在某些局限性。
主要原因:无法监听对象或数组新增、删除的元素。
局限性:
(1)、对象/数组的新增、删除
(2)、监测 .length 修改
(3)、Map、Set、WeakMap、WeakSet 的支持