关于vue数据绑定的极简原理
dep.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* 对订阅者进行收集、存储和通知
*
* @class Dep (name)
*/
export default class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
// 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理
this.subs.forEach(sub => sub.update());
}
}
Dep.target = null;
Dep类用来实例一个个的收集器,每个收集器用来存储对单个数据的订阅者,当该项数据发生变化时统一的去通知他们Dep.target用来暂存当前订阅者
observer.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import Dep from './dep';
function defineReactive(obj, key, val) {
let dep = new Dep();
// 给当前属性的值添加监听
observer(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('get value: ', val)
if (Dep.target) {
dep.addSub(Dep.target)
}
return val;
},
set: (newVal) => {
if (val === newVal) {
console.log('无需更新')
return;
}
console.log('new value setted: ', newVal)
val = newVal;
dep.notify();
}
})
}
export function observer(value) {
if (!value || typeof value !== 'object') {
return
}
Object.keys(value).forEach(key => defineReactive(value, key, value[key]));
}
- 核心方法Object.defineProperty定义对象的属性访问器,从而达到对数据改变的监听。
observer首先判断数据是否是对象,Object.keys遍历对象的每个字段对其执行defineReactive添加监听defineReactive首先进来实例化Dep,单个数据监听的收集器。这里再次对值observer实际上是类似递归,数据对象深层嵌套时继续监听。enumerable、configurable可枚举、可配置。get获取属性时,会把监听者添加到dep中,这边实际上是个闭包。dep收集器会常驻内存保留着。- 当数据
set设置时首先判断新值newVal是否与与旧值val相等,相等则无需更新,这边的旧值val也就是形参val也属于闭包。新旧值不等时则接下来由dep.notify来通知所有的监听者
watcher.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import Dep from './dep';
export default class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm; //vue数据主体
this.expOrFn = expOrFn; // 监听的字段
this.cb = cb; // 数据变化后的回调
this.val = this.get(); // 初始化获取数据
}
// 订阅数据更新时调用
update() {
let val = this.get();
this.val = val;
this.cb.call(this.vm, this.val);
}
get() {
// 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
Dep.target = this;
let val = this.vm._data[this.expOrFn];
Dep.target = null;
return val;
}
}
get获取此刻的数据,首先会将自身实例挂在到Dep.target,其目的是访问this.vm._data[this.expOrFn]数据时触发监听器observer.js中,然后再重置Dep.target;update方法,数据更新后会由dep通知触发。从而执行回调this.cb,且把新值this.val传进回调,该方法是我们的最终目的,更新视图view;
vue.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27import Watcher from './watcher';
import { observer } from './observer';
export default class Vue {
constructor(options = {}) {
this.$options = options;
this._data = this.$options.data;
Object.keys(this._data).forEach(key => this._proxy(key));
observer(this._data);
}
$watch(expOrFn, cb) {
new Watcher(this, expOrFn, cb);
}
_proxy(key) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get: () => this._data[key],
set: (val) => {
this._data[key] = val;
}
})
}
}
- 为使用方便把数据的监听同样添加到
Vue实例中,通过$watch方法来定义监听的字段以及回调
main.js1
2
3
4
5
6
7
8
9
10
11
12
13import Vue from './vue';
let demo = new Vue({
data: {
name: 'cheche'
}
})
demo.$watch("name", (value) => {
console.log("【update view111】: ", value);
})
demo.name = "meihao";
- 最后的使用示例