Vue源码探秘之 数据响应式原理
从MVVM模式说开去
模板
<p>我{{age}}岁了</p>
数据变化
this.age++;
数据变化,视图会自动变化
侵入式和非侵入式
尤小右找到了“上帝的钥匙”
Object.defineProperty()
数据劫持
/
数据代理
利用
JavaScript
引擎赋予的功能,检测对象属性变化,仅有“上帝的钥匙”不够,还需要设计一套精密的系统
Object.defineProperty()
方法
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 3
});
Object.defineProperty(obj, 'b', {
value: 5
});
console.log(obj);
console.log(obj.a, obj.b);
Object.defineProperty()
方法可以设置一些额外隐藏的属性。
Object.defineProperty(obj, 'a', {
value: 3,
// 是否可写
writable: false
});
Object.defineProperty(obj, 'b', {
value: 5,
// 是否可以被枚举
enumerable: false
});
getter/setter
这里有一个小坑:用闭包存储
get
和
set
的值
Object.defineProperty(obj, 'a', {
// getter
get() {
console.log('你试图访问obj的a属性');
},
// setter
set() {
console.log('你试图改变obj的a属性');
}
});
console.log(obj.a);
obj.a = 10;
getter/setter
需要变量周转才能工作
var temp;
Object.defineProperty(obj, 'a', {
// getter
get() {
console.log('你试图访问obj的a属性');
return temp;
},
// setter
set(newValue) {
console.log('你试图改变obj的a属性', newValue);
temp = newValue; }
});
使用
defineReactive
函数不需要设置临时变量了,而是用闭包
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可以被配置,比如可以被delete
configurable: true,
// getter
get() {
console.log('你试图访问obj的' + key + '属性');
return val;
},
// setter
set(newValue) {
console.log('你试图改变obj的' + key + '属性', newValue);
if (val === newValue) {
return;
}
val = newValue;
}
});
}
递归侦测对象全部属性
数组的响应式处理
依赖收集
什么是依赖?
•
需要用到数据的地方,称为依赖
•
Vue1.x
,
细粒度
依赖,用到数据的
DOM
都是依赖;
•
Vue2.x
,
中等粒度
依赖,用到数据的
组件
是依赖;
•
在
getter
中收集依赖,在
setter
中触发依赖
Dep
类和
Watcher
类
• 把依赖收集的代码封装成一个Dep
类,它专门用来管理依赖,
每个
Observer
的实例,
成员中都有一个
Dep
的实例;
• Watcher是一个中介,数据发生变化时通过
Watcher
中转,通知组件
•
依赖就是
Watcher
。只有
Watcher
触发的
getter
才会收集依赖,哪个 Watcher触发了
getter
,就把哪个
Watcher
收集到
Dep
中。
•
Dep
使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所 有的Watcher
都通知一遍。
•
代码实现的巧妙之处:
Watcher
把自己设置到全局的一个指定位置, 然后读取数据,因为读取了数据,所以会触发这个数据的getter
。在getter中就能得到当前正在读取数据的
Watcher
,并把这个
Watcher 收集到Dep
中。
根据原理图来介绍整个流程: .
1、首先使用object. defineProperty()中的getter setter作为一个Observer (劫持器)去劫持data对象中的所有属性,在属性set的时候通知Dep (订阅者收集器)去通知相关订阅者。
2、实现一个Watcher (订阅者) , Watcher就是收到Dep数据变化的通知后,会去执行相对应的更新函数来更新视图,同一个数据可能在多处被使用,所以订阅者不止一个;这也是Dep存在的意义,对Watcher集中起来统-管理。
1、首先使用object. defineProperty()中的getter setter作为一个Observer (劫持器)去劫持data对象中的所有属性,在属性set的时候通知Dep (订阅者收集器)去通知相关订阅者。
2、实现一个Watcher (订阅者) , Watcher就是收到Dep数据变化的通知后,会去执行相对应的更新函数来更新视图,同一个数据可能在多处被使用,所以订阅者不止一个;这也是Dep存在的意义,对Watcher集中起来统-管理。
3.、Dep (订阅者收集器) ,里面存放每个数据对应的所有Watcher,当Observer 的set方法被触发时,就调用Dep里面的的notify (通知)方法,逐条去通知所有的Watcher。
4、comple是一个编译器,作用是解析模板指令,扫描和解析wue代码中每一个节点,先将节点转换为碎片化文档DocumentFragment (性能优化,减少重排) ,再-次性append所有节点至目标element内,完成视图的初始化;同时编译器还担当着初始化Watcher的任务,即给Watcher绑定相关的更新团数,最终使Watcher添加到Dep中去。
4、comple是一个编译器,作用是解析模板指令,扫描和解析wue代码中每一个节点,先将节点转换为碎片化文档DocumentFragment (性能优化,减少重排) ,再-次性append所有节点至目标element内,完成视图的初始化;同时编译器还担当着初始化Watcher的任务,即给Watcher绑定相关的更新团数,最终使Watcher添加到Dep中去。
github源码地址:
https://github.com/russ-gao/study-data-reactive