虽然之前介绍过什么是mvvm了,今天更深入的讲一讲mvvm框架
简述 MVVM
MVVM,即 Model-View-ViewModel,model代表数据模型,view代表视图,viewmodel
是view
连接model
通信的桥梁,当数据发生改变时会通知到视图层渲染,视图变化的时候也会通知数据发生改变。
Vue 底层实现原理
vue.js采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Observer(数据监听器):Observer的核心是通过Object.defineProperty()来监听数据的变动,这个函数内部可以定义一个setter方法和getter方法,当数据发生变化是会触发setter,此时observer就会通知订阅者(watcher)
watcher(订阅者):watcher订阅者作为observer和compile之间通信的桥梁,,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里添加自己
- 自身必须有一个update()方法
- 待属性变动dep.notice()通知时,能调用自身的upload()方法,并触发compile中绑定的回调 compile(指令解析器):compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新视图
Vue 的生命周期
created阶段
:vue实例被创建
beforeCreate
:创建前,data和methods中的数据还没有初始化
created
:创建完毕,data中有值,未挂载到dommount阶段
:vue实例被挂载到dom节点beforeMount
:开始挂载钩子,此时模板数据还没有生成
mounted
:dom有数据,可以操作domupdate阶段
:当vue实例里面的data数据发生改变时触发组件的重新渲染beforeDestory
:实例被销毁前,此时可以手动销毁一些方法
destoryed
:销毁完毕。
组件生命周期
生命周期(父子组件)父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件mounted --> 父组件 mounted --> 父组件beforeUpdate --> 子组件beforeDestory --> 子组件destoryed --> 父组件updated
computed 与 watch
- watch
watch属性监听
是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用。 - computed
computed计算属性
属性的结果会被缓存,当computed中的函数所依赖的属性没有发生改变时,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性发生变化时才会重新计算,主要当做属性来使用,computed
中的函数必须用return
返回最终的结果。
那么什么时候用watch,什么时候用computed呢?
当一个属性受多个属性影响时,使用 computed ;
当一条数据影响多条数据的时候用 watch
在既能使用 computed 又能用 watch 的时候,推荐使用 computed,重点重点重点在 computed 的缓存功能 computed 计算属性是用来生命是的描述一个值依赖了其他的值
watch 监听的是已经在 data 中定义的变量,变量变化就会触发 watch,有可能影响性能消耗。
组件中的 data 为什么是一个函数
- 一个组件被复用多次的话就会创建多个实例,本质上这些实例用的都是同一个构造函数;
- 如果data是对象的话,对象属于引用类型,会影响到所有实例,为了保证不同组件之间的data不冲突,data必须是一个函数;
为什么 v-for 和 v-if 不建议一块使用
当v-for
和v-if
在一个节点时,v-for
的优先级比v-if
高,这意味着v-if
将在每一个v-for中运行,如果要遍历的数据量很大,而真正要展示的数据很少时,会造成很大的性能浪费!
注意:
3.x
版本中v-if
总是优先于v-for
生效。但是由于语法上存在歧义,建议避免在同一元素上同时使用二者!
key 的作用
- key 的作用是为了在diff算法执行时更快地找到对应的节点,提高diff速度,更高效的更新虚拟dom vue 和 react 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数中,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点。如果没有找到就认为是一个新增的节点,但是如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种是map映射,另一种是遍历查找。
- 为了在数据变化时强制更新组件,以避免“就地复用”带来的副作用。
当vuejs用
v-for
更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,vue将不会移动dom元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染的每个元素。key值重复会造成渲染错误!
Vue组件的通信方式
- 我们常用的方法有
props/$emit
用来父子间的通信 - 兄弟组件或其他自定义组件中通信一般使用
$emit/$on
- 有时也会使用
Event Bus
实现跨组件通信Vue.prototype.$bus = new Vue()
自定义事件 - vuex中跨级组件通信一般使用
$attrs/$listeners
、Provide/inject
nextTick
nextTick是什么?
- 因为 vue 采用的
异步更新
策略,当监听到数据发生变化的时候不会立即更新DOM,而是开启一个任务队列,并缓存在同一事件循环中的所有数据变更。 - 这种做法的好处是可以将多次数据更新合并成一次,减少操作DOM的次数,提升性能。
什么时候使用nextTick?
想要操作基于最新数据生成的DOM
时,这个操作就应该放在nextTick的回调中
nextTick的实现原理是什么?
将传入的回调函数包装成异步任务,异步任务又分为宏任务和微任务,为了尽快执行所以优先选择微任务 ~
nextTick提供 四种 异步方法:
- Promise.the
- MutationObserver
- setImmediate
- setTimeout