Vue2响应式系统之set和delete
作者:windliang
1、数组集
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: [1, 2], }; observe(data); const updateComponent = () => { console.log(data.list); }; new Watcher(updateComponent); list[0] = 3;
list[0]
会触发 的重新执行吗?updateComponent
可以先思考一下。
答案是否定的,数组我们只能通过重写的 、 等方法去触发更新,详见push
splice
Vue2响应式系统之数组 。
如果我们想要替换数组某个元素的话可以转一下弯,通过 去实现。splice
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: [1, 2], }; observe(data); const updateComponent = () => { console.log(data.list); }; new Watcher(updateComponent); // list[0] = 3; data.list.splice(0, 1, 3);
每次这样写太麻烦了,我们可以提供一个 方法供用户使用。set
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ export function set(target, key, val) { if (Array.isArray(target)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val; } // targe 是对象的情况 // ... }
然后我们直接使用 方法就可以了。set
import { observe, set } from "./reactive"; import Watcher from "./watcher"; const data = { list: [1, 2], }; observe(data); const updateComponent = () => { console.log(data.list); }; new Watcher(updateComponent); // list[0] = 3; // data.list.splice(0, 1, 3); set(data.list, 0, 3);
2、数组 del
同数组 ,我们顺便提供一个 的方法,支持数组响应式的删除某个元素。set
del
/** * Delete a property and trigger change if necessary. */ export function del(target, key) { if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return; } // targe 是对象的情况 // ... }
3、对象 set
import { observe, set, del } from "./reactive"; import Watcher from "./watcher"; const data = { obj: { a: 1, b: 2, }, }; observe(data); const updateComponent = () => { const c = data.obj.c ? data.obj.c : 0; console.log(data.obj.a + data.obj.b + c); }; new Watcher(updateComponent); data.obj.c = 3;
updateComponent
方法中虽然使用了 的 属性,但是在调用 之前, 中并没有 属性,所以 属性不是响应式的。obj
c
observe
data.obj
c
c
当我们修改 的值的时候,并不会触发 的执行。data.obj.c
updateComponent
如果想要变成响应式的话,一种方法就是在最开始就定义 属性。c
const data = { obj: { a: 1, b: 2, c: null, }, }; observe(data); const updateComponent = () => { const c = data.obj.c ? data.obj.c : 0; console.log(data.obj.a + data.obj.b + c); }; new Watcher(updateComponent); data.obj.c = 3;
另一种方法就是通过 去设置新的属性了,在 中我们可以将新添加的属性设置为响应式的。set
set
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ export function set(target, key, val) { if (Array.isArray(target)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val; } // targe 是对象的情况 // key 在 target 中已经存在 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } const ob = target.__ob__; // target 不是响应式数据 if (!ob) { target[key] = val; return val; } // 将当前 key 变为响应式的 defineReactive(target, key, val); return val; }
回到我们之前的程序:
import { observe, set, del } from "./reactive"; import Watcher from "./watcher"; const data = { obj: { a: 1, b: 2, }, }; observe(data); const updateComponent = () => { const c = data.obj.c ? data.obj.c : 0; console.log(data.obj.a + data.obj.b + c); }; const ob = new Watcher(updateComponent); set(data.obj, "c", 3);
虽然通过 增加了属性,但是此时 并不会重新触发,原因的话我们看下依赖图。set
Watcher
虽然属性 拥有了 对象,但由于没有调用过依赖属性 的 ,所以它并没有收集到依赖。c
Dep
c
Watcher
当然我们可以 完手动调用一次相应的 。set
Watcher
const data = { obj: { a: 1, b: 2, }, }; observe(data); const updateComponent = () => { const c = data.obj.c ? data.obj.c : 0; console.log(data.obj.a + data.obj.b + c); }; const ob = new Watcher(updateComponent); set(data.obj, "c", 3); ob.update(); // 手动调用 Watcher data.obj.c = 4;
这样的话,当执行 的时候就会触发 的执行了。data.obj.c = 4
Watcher
那么我们能将触发相应的 的逻辑放到 函数中吗?Watcher
set
可以看到 里也有个 ,这个其实当时是为数组准备的,参考 obj
Dep
Vue2响应式系统之数组,但 的 什么都没收集。obj
dep
我们修改一下代码让它也收集一下:
export function defineReactive(obj, key, val, shallow) { const property = Object.getOwnPropertyDescriptor(obj, key); // 读取用户可能自己定义了的 get、set const getter = property && property.get; const setter = property && property.set; // val 没有传进来话进行手动赋值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { /******新位置 *************************/ childOb.dep.depend(); /**********************************/ if (Array.isArray(value)) { // childOb.dep.depend(); //原来的位置 dependArray(value); } } } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); }, }); } function dependArray(value) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i]; /******新位置 *************************/ e && e.__ob__ && e.__ob__.dep.depend(); /**********************************/ if (Array.isArray(e)) { // e && e.__ob__ && e.__ob__.dep.depend(); // 原位置 dependArray(e); } } }
因为读取 属性,一定先会读取 属性,即 。 也同理。a
obj
data.obj.a
b
所以通过上边的修改, 的 会收集到它的所有属性的依赖,也就是这里的 、 的依赖,但因为 和 的依赖是相同的,所以收集到一个依赖。obj
dep
a
b
a
b
但其实我们并不知道 被哪些 依赖,我们只知道和 同属于一个对象的 和 被哪些 依赖,但大概率 也会被其中的 依赖。c
Watcher
c
a
b
Watcher
c
Watcher
所以我们可以在 中手动执行一下 的 ,依赖 的 大概率会被执行,相应的 也会成功收集到依赖。set
obj
Dep
c
Watcher
c
export function set(target, key, val) { if (Array.isArray(target)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val; } // targe 是对象的情况 // key 在 target 中已经存在 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } const ob = target.__ob__; // target 不是响应式数据 if (!ob) { target[key] = val; return val; } defineReactive(target, key, val); /******新增 *************************/ ob.dep.notify() /************************************/ return val; }
回到最开始的代码:
const data = { obj: { a: 1, b: 2, }, }; observe(data); const updateComponent = () => { const c = data.obj.c ? data.obj.c : 0; console.log(data.obj.a + data.obj.b + c); }; const ob = new Watcher(updateComponent); set(data.obj, "c", 3);
执行完后 除了变为响应式的,也成功触发了 执行,并且收集到了 。c
Watcher
Watcher
此时如果修改 的值,也会成功触发 的执行了。c
Watcher
4、对象 del
有了上边的了解,删除就很好解决了。
如果要删除 属性,删除后执行下它相应的 就可以。但 的 是存在闭包中的,我们并不能拿到。a
Dep
a
Dep
退而求其次,我们可以去执行 属性所在的对象 的 就可以了。a
obj
Dep
/** * Delete a property and trigger change if necessary. */ export function del(target, key) { if (Array.isArray(target)) { target.splice(key, 1); return; } // targe 是对象的情况 const ob = target.__ob__; if (!hasOwn(target, key)) { return; } delete target[key]; if (!ob) { return; } ob.dep.notify(); }
5、总结
通过为对象收集依赖,将对象、数组的修改、删除也变成响应式的了,同时为用户提供了 和 方法。
到此这篇关于Vue2响应式系统之set和delete的文章就介绍到这了,更多相关Vue2 set和delete内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!