vue2 数据响应式原理模拟继续优化
上次简单的模拟了数据响应式,当我们第一次修改值依赖函数会执行一次,再修改一次值依赖函数会执行两次,这需要做下去重,去掉重复绑定的依赖。采用的方法是给每个dev实例添加一个唯一标识id,通过Set去重。
代码如下:dep实例添加一个移除方法,和id属性
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 | 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是否存在,存在则不添加
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 | 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 改变了新增依赖的方法
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 | 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 }; |
这样再重复调用只会执行一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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方法移除当前的依赖函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 方法了。
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 | 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 ); } } } // 触发函数方法 update() { this .get(); } } |
// 新的列表赋值给旧的,新的列表清空
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;
分类:
vue
, vue / vue2源码学习
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具