前言
这是我参与「第四届青训营」笔记创作活动的的第6天
下面就Vue2的数据更新原理来谈一谈😀
数据响应式原理:为什么说数据变了,视图也会变?🧐
MVVM 模型
Vue的开发模式是MVVM,只要数据发生改变,视图也会跟着变化。而且Vue数据变化是非侵入式的。
侵入与非侵入
- 非侵入式:简单来讲,就是单纯的引用javascript原生的API,而不是依赖于框架中所提供的封装好的API。非侵入式的优点是可以再写代码时感受不到框架的存在,这意味着可以增强对代码的复用性。Vue的数据变化就是非侵入式的。
// Vue 数据变化
this.a ++;
- 侵入式:要求用户代码“知道”框架的代码,表现为用户代码需要继承框架提供的类。侵入式让用户代码产生对框架的依赖,这些代码不能在框架外使用,不利于代码的复用。但侵入式可以使用户跟框架更好的结合,更容易更充分的利用框架提供的功能。
// React 数据变化
// 要想改变数据就得调用框架提供的API才行,所以是侵入式
this.setState({
a: this.state.a + 1
})
// 小程序数据变化
this.setData({
a: this.data.a + 1
})
Object.defineProperty()
JavaScript为我们提供了Object.defineProperty(),该方法可以做数据劫持或数据代理,意味着可以检测对象属性变化,所以Vue就是主要以这个方法为核心实现了数据响应式。
// 双向数据绑定原理
let obj = {
name: '张三',
sex: '男'
// age: 19
}
// 需要临时变量number,周转getter和setter,才能正常工作
var number;
Object.defineProperty(obj, 'age', {
// value: 19,
// enumerable: true, // 控制属性是否可被枚举,默认值为false
// writable: true, // 控制属性是否可被修改,默认值为false
// configurable: true, // 控制属性是否可被删除,默认值为false
// 当有人读取person的属性时,get函数(getter)就会被调用,并且返回的就是age的值
get() {
console.log('有人读取age属性')
return number
},
// 当有人修改了person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value) {
console.log('有人修改了age属性,且值是', value)
number = value
}
})
console.log(person.age) // undefined
obj.age = 100 // 有人修改了age属性,且值是 100
console.log(person.age) // 100
我们习惯把上述代码封装为defineReactive,一个好处是用val参数替代临时变量
let obj = {
name: '张三',
sex: '男'
// age: 19
}
function defineReactive(data, key, val) {
Object.defineProperty(person, 'age', {
enumerable: true, // 控制属性是否可被枚举,默认值为false
configurable: true, // 控制属性是否可被删除,默认值为false
// 当有人读取person的属性时,get函数(getter)就会被调用,并且返回的就是age的值
get() {
console.log('有人读取' + key +'属性')
return val
},
// 当有人修改了person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(newValue) {
console.log('有人修改了' + key +'属性,且值是', newValue)
val = newValue
}
})
}
defineReactive(obj, 'age', 10) // 此时age就是响应式的
上述代码能实现数据的响应式,但是对于多层对象的更深层对象属性是侦测不到了(没有响应式效果)如下:
// eg:
// 此时对于m,n就无法有响应式效果
var obj = {
a: {
m: {
n:{
......
}
}
}
}
为了使数据对象上每个属性都是响应式的,那么我们要 递归侦测对象全部属性。
收集依赖
依赖:需要用到数据的地方
-
Vue1.x:细粒度依赖,用到数据的DOM都是依赖
-
Vue2.x:中等粒度依赖,用到数据的组件都是依赖
-
在getter中收集依赖,在setter中触发依赖
- 把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个Observer的实例, 成员中都有一个Dep的实例
- Watcher是一个中介,数据发生变化时通过Watcher中转,通知组件
- 依赖就是Watcher。只有Watcher触发的getter才会收集依赖,哪个 Watcher触发了getter,就把哪个Watcher收集到Dep中
- Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所 有的Watcher都通知一遍
- 代码实现的巧妙之处:Watcher把自己设置到全局的一个指定位置, 然后读取数据,因为读取了数据,所以会触发这个数据的getter。在 getter中就能得到当前正在读取数据的Watcher,并把这个Watcher 收集到Dep中。
总结
多学源码思想,即学即用,歪瑞故得😎😎😎