vue2 数据响应式原理模拟继续优化
上次简单的模拟了数据响应式,当我们第一次修改值依赖函数会执行一次,再修改一次值依赖函数会执行两次,这需要做下去重,去掉重复绑定的依赖。采用的方法是给每个dev实例添加一个唯一标识id,通过Set去重。
代码如下:dep实例添加一个移除方法,和id属性
let uid = 0;
export default class Dep {
static target = null; // 全局正在执行的函数
constructor() {
this.sub = []; // 存储依赖的函数
this.id = uid++; // 创建标识的唯一ID,去重,避免一个属性会依赖多个相同的函数
}
addSub(sub) {
this.sub.push(sub);
}
remove(sub) {
//移除依赖
let index = this.sub.indexOf(sub);
index > -1 ? this.sub.splice(index, 1) : "";
}
depend() {
if (Dep.target) {
// 委托给 Dep.target 去调用 addSub
Dep.target.addDep(this);
}
}
notify() {
// 循环执行依赖的
this.sub.map((item) => item.update());
}
}
watcher 添加addDep方法,判断ID是否存在,存在则不添加
import Dep from "./dep";
export class Watcher {
constructor(sub) {
this.getter = sub; // 当前执行的函数
this.depids = new Set(); // set拥有 has 函数可以判断是否存在某个 id,存储dep的唯一ID
this.get();
}
get() {
Dep.target = this;
let value;
try {
value = this.getter.call(); // 执行函数
} catch (e) {
throw e;
}
return value;
}
addDep(dep) {
//如果当前函数已绑定不在添加
if (!this.depids.has(dep.id)) {
this.depids.add(dep.id);
this.dev.push(dep);
dep.addSub(this); // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中
}
}
// 重置每次新的依赖
cleanupDeps() {
//判断旧的dev与最新的dev的依赖是否减少,如果新的减少了,则减少的那的dev实例移除当前函数依赖
let index = this.oldDev.length;
while (index--) {
let id = this.oldDev[index].id;
if (!this.depids.has(id)) {
//则移除当前函数依赖
this.oldDev[index].remove(this);
}
}
// 新的dev 和id 给旧的,新的重置
this.oldDepids = this.depids;
this.oldDev = this.dev;
this.depids.clear();
this.dev = [];
}
// 触发函数方法
update() {
this.get();
}
}
observer.js 改变了新增依赖的方法
import Dep from "./dep";
function defineReactive(obj, key, val) {
// 获取当前属性有没有自定义方法;
let property = Object.getOwnPropertyDescriptor(obj, key);
// 判断当前属性有没有自定义get set 方法
let getter = property && property.get;
let setter = property && property.set;
// 没有val值,去obj里面的
if ((!getter || setter) && arguments.length == 2) {
val = obj[key];
}
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function () {
//添加依赖函数
if (Dep.target) {
// dep.addSub(Dep.target);
dep.depend();
}
//如果有自定义方法则调用
let value = getter ? getter.call(obj) : val;
return value;
},
set: function (newVal) {
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
//执行依赖当前属性的依赖函数
dep.notify();
},
});
}
function observer(data) {
let keys = Object.keys(data);
for (let key of keys) {
defineReactive(data, key);
}
}
export { observer };
这样再重复调用只会执行一次
import { observer } from "./observer";
import { Watcher } from "./watcher";
const data = {
test: "aa",
test1: "cc",
};
const updateData = () => {
console.log(data.test);
};
//Object.defineProperty 劫持数据 将数据变成响应式
observer(data);
// 注入依赖函数
new Watcher(updateData);
data.test = "bb";
data.test = ‘hh’;
执行下面代码发现test为false后,再修改test1,依赖的函数还是会执行,因为test1这个dev在注入依赖函数时候就存储这个方法,所以我们要做一个依赖的重置更新,每次watcher执行将旧的dev 和新的dev 对比,如果新的减少了,则减少的那个dev调用remove方法移除当前的依赖函数。
import { observer } from "./observer";
import { Watcher } from "./watcher";
const data = {
test: "aa",
test1: "cc",
};
const updateData = () => {
console.log(data.test ? data.test1 : "测试");
};
//Object.defineProperty 劫持数据 将数据变成响应式
observer(data);
// 注入依赖函数
new Watcher(updateData);
data.test = "bb";
data.test = false;
data.test1 = "kkk"; // 上一步已经返回了false,函数还是触发了
watche.jsr修改如下:定义新的和旧的存储dev和dev.id 的属性,dev新增当前依赖函数后,watcher都会新旧属性判断下,watcher如果有不依赖的dev,dev就移除当前的函数。再执行上面的代码,修改test1就不会触发updateData 方法了。
import Dep from "./dep";
export class Watcher {
constructor(sub) {
this.getter = sub; // 当前执行的函数
this.depids = new Set(); // set拥有 has 函数可以判断是否存在某个 id,存储dep的唯一ID
this.dev = []; // 存储每个函数依赖的dep
this.oldDepids = new Set();
this.oldDev = [];
this.get();
}
get() {
Dep.target = this;
let value;
try {
value = this.getter.call(); // 执行函数
} catch (e) {
throw e;
} finally {
this.cleanupDeps();
}
return value;
}
addDep(dep) {
//如果当前函数已绑定不在添加
// if (!this.depids.has(dep.id)) {
// this.depids.add(dep.id);
// this.dev.push(dep);
// dep.addSub(this); // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中
// }
if (!this.depids.has(dep.id)) {
this.depids.add(dep.id);
this.dev.push(dep);
if (!this.oldDepids.has(dep.id)) {
dep.addSub(this); // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中
}
}
}
// 重置每次新的依赖
cleanupDeps() {
//判断旧的dev与最新的dev的依赖是否减少,如果新的减少了,则减少的那的dev实例移除当前函数依赖
let index = this.oldDev.length;
while (index--) {
let id = this.oldDev[index].id;
if (!this.depids.has(id)) {
//则移除当前函数依赖
this.oldDev[index].remove(this);
}
}
// 新的列表赋值给旧的,新的列表清空
let tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
}
// 触发函数方法
update() {
this.get();
}
}

浙公网安备 33010602011771号