# Vue
响应式原理解析
数据的改变驱动视图的刷新是 Vue
的特性之一 而 其非侵入性的响应性系统 是我们研究的方向 今天我们就来深入探究一波其中的奥秘
# 基础案例
观察基础案例 思考商品价格修改时 更新几处数据以及如何追踪这一切
<div id="app"> <div>单价: {{ product.price }}</div> <div>数量: {{ product.price * product.num }}</div> <div>总价: {{ getTotalPrice() }}</div> <div>优惠单价: {{ getProductSalePrice() }}</div> <div>优惠总价: {{ getSalePrice }}</div> <button @click="addNum">增加数量</button> </div> <script src="https://unpkg.com/vue@next"></script> <script> Vue.createApp({ data() { return { product: { price: 100, num: 2, }, }; }, methods: { addNum() { this.product.num++; }, getTotalPrice() { return this.product.price * this.product.num; }, getProductSalePrice() { return this.product.price * 0.8; }, }, computed: { getSalePrice() { return this.product.price * this.product.num * 0.8; }, }, }).mount('#app'); </script>
1
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
37
# 分析与构建基础响应式数据
JaveScript
的工作方式是过程式的 而不是响应式的 所以 没办法时刻侦听到数据的变化let product = { price: 100, num: 2, }; let totalPrice = 0; let productSalePrice = 0; totalPrice = product.price * product.num; productSalePrice = product.price * 0.8; console.log('更新前的总价:' + totalPrice); console.log('更新前的商品优惠价:' + productSalePrice); product.num++; console.log('更新后的总价:' + totalPrice); console.log('更新后的商品优惠价:' + productSalePrice);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15要想数据的到响应的变化 我们则需要重新求得结果
遇到重复的代码 通常我们都会将其封装成为函数
这里的
getTotalPrice
函数和getProductSalePrice
函数就是 记录函数effect
let product = { price: 100, num: 2, }; let totalPrice = 0; let productSalePrice = 0; function getTotalPrice() { totalPrice = product.price * product.num; } function getProductSalePrice() { productSalePrice = product.price * 0.8; } getTotalPrice(); getProductSalePrice(); console.log('更新前的总价:' + totalPrice); console.log('更新前的商品优惠价:' + productSalePrice); product.num++; getTotalPrice(); getProductSalePrice(); console.log('更新后的总价:' + totalPrice); console.log('更新后的商品优惠价:' + productSalePrice);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
注意观察数据和方法之间的关系
在观察者设计模式中 依赖项会有订阅者 当依赖项改变状态后 订阅者会得到通知
修改
product.price
必须重新运行getTotalPrice
和getProductSalePrice
求得结果修改
product.num
必须重新运行getTotalPrice
求得结果所以 我们要将数据和函数的关联记录下来
// 数据与事件的关系记录表 let depMap = new Object(); // 保存数据与事件关系 function track(key, eventName) { if (depMap[key]) { depMap[key].push(eventName); } else { depMap[key] = [eventName]; } } // 修改数据时触发对应的事件 function trigger(key) { let dep = depMap[key]; if (dep) { dep.forEach(eventName => eventName()); } } let product = { price: 100, num: 2, }; let totalPrice = 0; let productSalePrice = 0; // 获取总价 function getTotalPrice() { totalPrice = product.price * product.num; } // 获取商品优惠价 function getProductSalePrice() { productSalePrice = product.price * 0.8; } // 手动记录关系 track('price', getTotalPrice); track('price', getProductSalePrice); track('num', getTotalPrice); // 运算结果 getTotalPrice(); getProductSalePrice(); console.log('更新前的总价:' + totalPrice); console.log('更新前的商品优惠价:' + productSalePrice); // 手动更新数据 product.price = 200; trigger('price'); console.log('更新后的总价:' + totalPrice); console.log('更新后的商品优惠价:' + productSalePrice); // console.log(depMap); // depMap = { // price: ['getTotalPrice', 'getProductSalePrice'], // num: ['getTotalPrice'], // };
1
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
如果存在多个商品信息如何储存数据和函数的关系
多个商品具有同样的属性 例如 价格
price
数量num
// 数据与事件的关系记录表 const depMap = new Object(); // 保存数据与事件关系 function track(key, eventName) { if (depMap[key]) { depMap[key].push(eventName); } else { depMap[key] = [eventName]; } } // 修改数据时触发对应的事件 function trigger(key) { let dep = depMap[key]; if (dep) { dep.forEach(eventName => eventName()); } } // 商品1 let product1 = { price: 100, num: 2, }; // 商品2 let product2 = { price: 1000, num: 3, }; let totalPrice = 0; let product1SalePrice = 0; let product2SalePrice = 0; // 获取总价 function getTotalPrice() { totalPrice = product1.price * product1.num + product2.price * product2.num; } // 获取 商品1 优惠价 function getProduct1SalePrice() { product1SalePrice = product1.price * 0.8; } // 获取 商品2 优惠价 function getProduct2SalePrice() { product2SalePrice = product2.price * 0.8; } // 手动记录关系 track('price', getTotalPrice); track('price', getProduct1SalePrice); track('price', getProduct2SalePrice); track('num', getTotalPrice); // console.log(depMap); // depMap = { // price: ['getTotalPrice', 'getProduct1SalePrice', 'getProduct2SalePrice'], // num: ['getTotalPrice'], // };
1
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
推荐使用
WeakMap
(opens new window) 数据类型记录将数据和函数的关系下来请问为什么要通过
WeakMap
数据类型而不是Object
// 数据与事件的关系记录表 let depMap = new WeakMap(); // 保存数据与事件关系 function track(dataObj, key, eventName) { let dep = depMap.get(dataObj); if (!dep) { depMap.set(dataObj, (dep = {})); } if (dep[key]) { dep[key].push(eventName); } else { dep[key] = [eventName]; } } // 修改数据时触发对应的事件 function trigger(dataObj, key) { let dep = depMap.get(dataObj); if (dep) { dep[key].forEach(eventName => eventName()); } } let product1 = { price: 100, num: 2, }; let product2 = { price: 2000, num: 3, }; let totalPrice = 0; let product1SalePrice = 0; let product2SalePrice = 0; // 获取总价 function getTotalPrice() { totalPrice = product1.price * product1.num + product2.price * product2.num; } // 获取 商品1 优惠价 function getProduct1SalePrice() { product1SalePrice = product1.price * 0.8; } // 获取 商品2 优惠价 function getProduct2SalePrice() { product2SalePrice = product2.price * 0.8; } // 手动记录关系 track(product1, 'price', getTotalPrice); track(product1, 'num', getTotalPrice); track(product2, 'price', getTotalPrice); track(product2, 'num', getTotalPrice); track(product1, 'price', getProduct1SalePrice); track(product2, 'price', getProduct2SalePrice); // 运算结果 getTotalPrice(); getProduct1SalePrice(); getProduct2SalePrice(); console.log('更新前的总价:' + totalPrice); console.log('更新前的 商品1 优惠价:' + product1SalePrice); console.log('更新前的 商品2 优惠价:' + product2SalePrice); // 手动更新 商品1 数据 product1.price = 200; trigger(product1, 'price'); console.log('更新后的总价:' + totalPrice); console.log('更新后的 商品1 优惠价:' + product1SalePrice); // 手动更新 商品2 数据 product2.price = 3000; trigger(product2, 'price'); console.log('更新后的总价:' + totalPrice); console.log('更新后的 商品2 优惠价:' + product2SalePrice); // console.log(depMap); // depMap = { // product1: { // price: [getTotalPrice, getProduct1SalePrice], // num: [getTotalPrice], // }, // product2: { // price: [getTotalPrice, getProduct2SalePrice], // num: [getTotalPrice], // }, // };
1
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 自动响应式数据
Proxy
(opens new window) 的基本使用let product = { price: 100, num: 2, }; let proxiedProduct = new Proxy(product, { get(target, key, receiver) { return target[key]; }, set(target, key, newVal, receiver) { return (target[key] = newVal); }, }); console.log(proxiedProduct.price); proxiedProduct.price = 200; console.log(proxiedProduct.price);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Reflect
(opens new window) 的基本使用 思考为什么要搭配Reflect
let product = { price: 100, num: 2, }; let proxiedProduct = new Proxy(product, { get(target, key, receiver) { return Reflect.get(target, key, receiver); }, set(target, key, newVal, receiver) { return Reflect.set(target, key, value, receiver); }, }); console.log(proxiedProduct.price); proxiedProduct.price = 200; console.log(proxiedProduct.price);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17使用
Proxy
和Reflect
进行数据拦截器的自动绑定 思考下面几个问题函数
reactive
的作用变量
activeEvent
的作用函数
runEvent
的作用为什么最终
depMap
会出现null
的事件呢如果多次调用
runEvent(getTotalPrice)
获取总价格 那么会出现什么问题// 数据与事件的关系记录表 let depMap = new WeakMap(); // 当前运行的事件 let activeEvent = null; // 保存数据与事件关系 function track(dataObj, key, eventName) { let dep = depMap.get(dataObj); if (!dep) { depMap.set(dataObj, (dep = {})); } if (dep[key]) { dep[key].push(eventName); } else { dep[key] = [eventName]; } } // 修改数据时触发对应的事件 function trigger(dataObj, key) { let dep = depMap.get(dataObj); if (dep) { dep[key].forEach(eventName => eventName()); } } // 设置数据的拦截器 traps function reactive(target) { return new Proxy(target, { get(target, key, receiver) { let result = Reflect.get(target, key, receiver); track(target, key, activeEvent); return result; }, set(target, key, newVal, receiver) { if (target[key] != newVal) { Reflect.set(target, key, newVal, receiver); trigger(target, key); } return newVal; }, }); } // 保存当前运行的函数 function runEvent(eventName) { activeEvent = eventName; activeEvent(); activeEvent = null; } let product = { price: 100, num: 2, }; let totalPrice = 0; let productSalePrice = 0; // 初始化商品的拦截器 let proxiedProduct = reactive(product); // 获取总价 function getTotalPrice() { totalPrice = proxiedProduct.price * proxiedProduct.num; } // 获取商品优惠价 function getProductSalePrice() { productSalePrice = proxiedProduct.price * 0.8; } // 运算结果 runEvent(getTotalPrice); runEvent(getProductSalePrice); console.log('更新前的总价:' + totalPrice); console.log('更新前的商品优惠价:' + productSalePrice); // 更新商品数据 proxiedProduct.price = 200; console.log('更新后的总价:' + totalPrice); console.log('更新后的商品优惠价:' + productSalePrice); // console.log(depMap); // depMap = { // product: { // price: ['getTotalPrice', 'getProductSalePrice', null, null], // num: ['getTotalPrice', null], // }, // };
1
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91reactive
数据双向绑定 最终版设置
proxiedProduct.price = 200
时 会自动触发price
的getTotalPrice
和getProductSalePrice
事件 而事件内部因为获取proxiedProduct
的属性 所以又会触发拦截器里面的get
又一次绑定目前的事件多次调用
runEvent(getTotalPrice)
获取总价格 会触发拦截器里面的get
重复绑定getTotalPrice
事件 所以 我们要使用Set
(opens new window) 自动过滤重复事件
// 数据与事件的关系记录表 let depMap = new WeakMap(); // 当前运行的事件 let activeEvent = null; // 保存数据与事件关系 function track(dataObj, key, eventName) { let dep = depMap.get(dataObj); if (!dep) { depMap.set(dataObj, (dep = {})); } if (!dep[key]) { dep[key] = new Set(); } dep[key].add(eventName); } // 修改数据时触发对应的事件 function trigger(dataObj, key) { let dep = depMap.get(dataObj); if (dep) { dep[key].forEach(eventName => eventName()); } } // 设置数据的拦截器 traps function reactive(target) { return new Proxy(target, { get(target, key, receiver) { if (activeEvent) { track(target, key, activeEvent); } return Reflect.get(target, key, receiver); }, set(target, key, newVal, receiver) { if (target[key] != newVal) { Reflect.set(target, key, newVal, receiver); trigger(target, key); } return newVal; }, }); } // 保存当前运行的函数 function runEvent(eventName) { activeEvent = eventName; activeEvent(); activeEvent = null; } let product = { price: 100, num: 2, }; let totalPrice = 0; let productSalePrice = 0; // 初始化商品的拦截器 let proxiedProduct = reactive(product); // 获取总价 function getTotalPrice() { totalPrice = proxiedProduct.price * proxiedProduct.num; } // 获取商品优惠价 function getProductSalePrice() { productSalePrice = proxiedProduct.price * 0.8; } // 运算结果 runEvent(getTotalPrice); runEvent(getTotalPrice); runEvent(getTotalPrice); runEvent(getProductSalePrice); console.log('更新前的总价:' + totalPrice); console.log('更新前的商品优惠价:' + productSalePrice); // 更新商品数据 proxiedProduct.price = 200; console.log('更新后的总价:' + totalPrice); console.log('更新后的商品优惠价:' + productSalePrice); console.log(depMap); // depMap = { // product: { // price: ['getTotalPrice', 'getProductSalePrice'], // num: ['getTotalPrice'], // }, // };
1
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93Vue2
的响应式原理通过
Object.defineProperty()
(opens new window) 来自动实现发布订阅let depMap = new WeakMap(); let activeEvent = null; function track(dataObj, key, eventName) { let dep = depMap.get(dataObj); if (!dep) { depMap.set(dataObj, (dep = {})); } if (dep[key]) { dep[key].push(eventName); } else { dep[key] = [eventName]; } } function trigger(dataObj, key) { let dep = depMap.get(dataObj); if (dep && dep[key]) { dep[key].forEach(itemEvent => { itemEvent(); }); } } function reactive(dataObj) { let newDataObj = {}; newDataObj.originDataObj = Object.assign({}, dataObj); for (const key in dataObj) { Object.defineProperty(newDataObj, key, { get() { if (activeEvent) { track(this, key, activeEvent); } return this.originDataObj[key]; }, set(newVal) { this.originDataObj[key] = newVal; trigger(this, key); }, }); } return newDataObj; } function runEvent(eventName) { activeEvent = eventName; eventName(); activeEvent = null; } let product = { price: 100, num: 2, }; let definePropertyproduct = reactive(product); let productTotalPrice = 0; let productSalePrice = 0; function getproductTotalPrice() { productTotalPrice = definePropertyproduct.price * definePropertyproduct.num; } function getproductSalePrice() { productSalePrice = definePropertyproduct.price * 0.8; } runEvent(getproductTotalPrice); console.log('更新前的总价:' + productTotalPrice); definePropertyproduct.price = 200; console.log('更新后的总价:' + productTotalPrice); // console.log(depMap);
1
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75