响应式原理
官网图
源码图
源码标注图
白话
- 从 new Vue 开始,首先通过 get、set 监听 Data 中的数据变化,同时创建 Dep 用来搜集使用该 Data 的 Watcher。
- 编译模板,创建 Watcher,并将 Dep.target 标识为当前 Watcher。
- 编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后调用 Dep.addSub 将 Watcher 搜集起来。
- 数据更新时,会触发 Data 的 set 方法,然后调用 Dep.notify 通知所有使用到该 Data 的 Watcher 去更新 DOM。
参考
Object.defineProperty() - JavaScript | MDN
为什么Vue3.0使用Proxy实现数据监听(defineProperty表示不背这个锅)
Vue生命周期函数
beforeCreate
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。
created
已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。
data 和 methods都已经被初始化好了。
beforeMount
在挂载开始之前被调用:相关的 render 函数首次被调用。(遇到子组件,进入子组件开始初始化)
执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
mounted
实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。 执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。如果我们想要通过插件操作页面上的DOM节点,最早可以在这个阶段中进行
beforeUpdate
当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
updated
页面显示的数据和data中的数据已经保持同步了,都是最新的
beforeDestroy
Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods,指令,过滤器……都是处于可用状态,还没有真正被销毁
destroyed
这个时候上所有的 data 和 methods,指令,过滤器……都是处于不可用状态,组件已经被销毁了。
activated
被 keep-alive 缓存的组件激活时调用。
deactivated
被 keep-alive 缓存的组件停用时调用
Vue中父子组件生命周期执行顺序
父beforeCreate
↓
父created
↓
父beforeMount(子beforeCreate->子created->子beforeMount->子mounted)
↓
父mounted
异步更新队列
原理
- Vue 在更新 DOM 时是异步执行的。
- 只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
- 如果同一个 watcher 被多次触发,只会被推入到队列中一次
- 当你设置
vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新
使用
为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)
写法
因为 $nextTick() 返回一个 Promise 对象,所里以下3种都可以
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
this.$nextTick().then(f1,f2)
await this.$nextTick()
程序化的侦听器
事件:hook:+vue生命周期钩子
this.$once('hook:beforeDestroy', function () { picker.destroy(); timer=null })
解惑
vue为何无法监听对象的属性的改变?
Object.defineProperty()
拦截对象属性的值的变化 √
删除或者添加新的属性的 ×
vue为什么无法监听数组索引的变化,但是数组方法却可以删除或者添加新的属性
Object.defineProperty()是可以对Array的索引进行拦截的- 但是Vue考虑到性能与成本不值得,所有放弃到了这个特性
- Vue对shift,pop,unshift,push,splice,sort,reverse方法进行了“重写”(只针对data里配置的数组生效),达到数据响应式的效果
Vue3.0使用Proxy实现响应式,有什么优点和缺点
优点:
- Proxy可劫持属性的删除和新增,有13种针对目标对象的捕捉器方法
- Proxy不用层层遍历属性,就可实现深度劫持
- 可代理所有类型的对象
- 不修改源对象,返回一个新的代理对象
缺点:
- 兼容性不好,babel的polyfill库也只能实现一部分捕捉方法
为什么 vue 组件中的 data 必须是函数
因为vue组件可能被复用,当复用的时候,就相当于创建了一个组件的实例,如果data是一个对象,组件实例间会共享同一data对象,如果是data函数的话,data获取就是独立的数据对象。 !!data 中数据可能会被复用,要保证不同组件调用的时候数据是相同的
vue中key属性的作用
- 在vue data变化之后,通过watcher的update方法,执行reader函数,生成vdom,
- 进行vdom diff,递归的进行同级节点比较,
- 同级节点进行diff children
- 如果以上逻辑都匹配不到,再把所有旧子节点的 key 做一个映射表,然后用新 vnode 的 key 去找出在旧节点中可以复用的位置。
- 如果key命中,就使用这个vnode和旧vnode进行
patchVnode,就可以直接复用。不用重新创建新的dom,减少开销(原生dom创建会有很多事件并且会触发回流)
// 节点判断
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
)
)
vue中key为什么不能用index和随机数
- 因为数据顺序变化后,一样的index确是不一样的值,进行patchVnode时还是不能复用,失去了key本该有的意义
- 随机数在diff的时候会认为都是全新的vnode,那样就造成,本来可以复用的都要重写渲染,无意义