什么是MVVM
MVVM是Model-View-ViewModel的缩写,Model是数据模型,View是UI组件,ViewModel层把Model和View联系起来,数据会自动绑定到ViewModel层并且把数据渲染到页面,当页面更新时,ViewModel层也会更新相应的数据
vue指令的一些缩写
- vue的指令是指用v-开头的特殊属性,后面跟的值期望是js表达式,表达式变化时可以让页面响应式变化。同时可以把表达式的值和某个属性绑定起来,在后面加冒号
<a v-bind:href="url">链接</a>
//这里就是把变量url和a链接的href属性绑定起来了
- 缩写:
//完整写法
<a v-bind:href="url">链接</a>
//缩写
<a :href="url">链接</a>//完整写法
<button v-on:click="clickBtn">按钮</button>
//缩写
<button @click="clickBtn">按钮</button>
组件的生命周期
- beforeCreate。此时没有数据
- created。此时有数据但是没有真实dom
- beforeMount。这个阶段创建完虚拟dom了,在这个钩子包括前面两个钩子里面更新数据都不会触发beforeUpdate和updated钩子。
- mounted。此时将虚拟dom挂载到真实dom上,可以获取到真实dom和$ref
- beforeUpdate。这时会完成响应式数据的更新,但是还没有开始虚拟dom的渲染,在这个钩子更新数据不会导致循环更新。(所以此时数据是新的,视图是旧的)
- updated。这时已经完成了dom的更新,在这里更新数据会导致循环更新
- beforeDestory。组件实例还没有被销毁
- destoryed。组件和子组件实例都已经被销毁了
v-if和v-show的区别
- 控制手段不同:v-if将整个dom添加或删除,v-show只是为dom添加了显示隐藏属性
- 编译过程不同:v-if的切换会导致内部有子组件或事件监听的重建或者销毁,但是v-show只是简单的样式切换
响应式原理理解
- 指:数据更新驱动视图更新,视图变化数据随之响应
- vue2使用Object.defineProperty(),vue3用Proxy。都是在getter里面收集依赖,在setter里面根据依赖变化修改数据
- 为什么vue3要使用Proxy?因为Object.definePrototype()只能拦截到对象已经存在的属性,所以对新增属性无法添加getter和setter,用Proxy可以很好的解决新增属性的拦截
vue2响应式的原理
-
总结
在 data 初始化时,将其属性转换成
getter
和setter
,同时实例化一个Dep
。视图渲染时,getter
触发,Dep
会收集Watcher
;在setter
触发时,Dep
会通知其收集的Watcher
更新视图。Wathcer
在组件挂载时实例化,Watcher
与组件一一对应。 -
详细过程
-
数据劫持。
使用Object.defineProperty(),针对对象属性,就递归把属性拦截下来,添加监听。但是defineProperty方法不能监听到属性的新增和删除,所以针对数组的pop(),push()数据会没有反应,这个可以用vue原生api里面的vue.set,vue.delete方法添加监听,所以vue针对数组也做了拦截,过程为:把数据的隐式原型执行一个指向一个写好的对象,在这个对象里重写数组的相关方法,再把这个对象的隐式原型指向Array构造函数原型
-
模板解析
通过使用compiler类,对app根组件开始遍历,创建空白文档片段,再依次遍历跟组件下的每一个节点,同时对节点类型进行判断,如果是表单元素,即使用了v-model,就监听表单的Input的事件,当绑定的值发生变化,就把新的值放到data对应的属性里面。如果判断当前节点是文本节点,就获取到文本节点使用到的data值,同时生成watcher实例,对用到的data收集依赖。当对节点处理完之后,把它添加到空白文档里面
-
生成依赖
当生成一个watcher实例就是生成一个依赖,然后会获取data的值,放到对应的节点上,获取data某个值的时候就触发了data的getter,在getter里面就要收集依赖(dep)
-
收集依赖
每个响应式数据都会有一个dep,负责两部分:收集页面使用到这个数据的节点;当数据值变得时候,要通知所有依赖更新页面。因为在获取文本节点的时候会调用data里面的值,所以会触发数据的getter,在getter里面给当前dep添加这个节点。在数据更新的时候调用setter,在setter里面通知dep里面的节点更新
-
参考文章:https://juejin.cn/post/7084200479005081608
vue3响应式原理
-
总结
用Proxy搭配Reflect函数,对数据进行代理,设置getter和setter,通过用track收集依赖,当数据更新时,调用trigger使依赖更新
参考文章:https://juejin.cn/post/7001999813344493581
$nextTick
-
理解为:在数据变化导致视图更新完成之后,可以调用这个函数获取最新的页面内容
-
为什么要有nextTick?
dom的更新是一个很消耗性能的过程,如果出现了频繁的数据更新,每次更新都触发setter使视图更新会导致性能降低,所以利用了事件循环来收集起来需要更新的watcher,在浏览器进行下一次宏任务的时候一次性更新所有需要更新的视图
-
原理:
使用了事件的循环(事件循环:这里指的过程是 1. 执行宏任务 2. 执行本次宏任务产生的微任务 3. render(页面渲染) 4. 宏任务),浏览器在每次进行宏任务之后会进行一次页面的渲染,所以vue针对这个空隙,整体完成视图的更新。所以可以收集需要更新的依赖,放到宏任务或者微任务里面异步执行,这里只考虑定时器和promise,因为定时器属于宏任务,如果在定时器里面更新视图,会在更新前进行一次没有意义的视图渲染,但是Promise属于微任务,在本次微任务执行期间进行视图更新效果比较好。
computed和watch的区别
计算属性依据组件数据返回新的值,当依赖数据发生变化时,计算属性也会响应式的变化。可以简化模板中复杂属性的逻辑,提高模板的可维护性。
侦听器侦听到数据的变化,在数据变化的同时执行副作用,所以可以用来进行异步处理。
computed的实现原理
- vue在遍历data完成数据劫持、收集依赖后,开始对computed进行遍历处理,每一个computed属性都会有一个对应的watcher,同时会有自己的setter和getter,当页面编译到计算属性时,会触发getter,在计算属性的getter里面又触发了所需要的属性的getter,就会把计算属性watcher添加到所需要的属性的dep里面。当所需属性更新的时候,就通过dep通知到计算属性更新。当修改了计算属性的值,触发setter,去更新所需属性的setter
- computed里面有两个重要参数:dirty和lazy,其中dirty用来标记依赖属性是否更新,当dirty为true时表示需要重新计算,false表示可以直接取值。lazy是计算属性的默认参数,表示惰性求值,在初始化computed的时候并不会求值
参考文章:https://juejin.cn/post/7125610199666130974
computed和methods的区别
computed有缓存机制,如果依赖属性没有变化就不用重新计算,但是methods里面的方法每次都会重新运行计算
组件里的data为什么是一个函数
- 理解1. 因为一个组件可以被多次实例化,用data函数返回的数据有独立的作用域,如果data是一个对象,就会导致同一个组件的多个实例之间引用同一个数据,产生数据污染
- 理解2. 组件初始化时,针对data会调用initData函数给每个数据添加setter和getter,如果data是一个对象,那么同一组件的多个实例的数据都会被同一个data拦截,当触发setter,会导致所有用到这个数据的组件都更新,会造成更新混乱
v-model的实现原理
v-model实际上是v-bind:value喝@input事件的语法糖,例子:
<input type="text" v-bin:value="inputValue" @input="inputValue = $event.target.value"/>
<input type="text" v-model="iputValue"/>
以上两种效果相同