4.3 设计一个完善的响应式系统

关键词

响应系统的工作流程:

  • 读取操作发生时,将副作用函数收集到“桶”中。
  • 设置操作发生时,从“桶”中取出副作用函数并执行。
  • activeEffect 全局变量当做中介存储副作用函数。
  • 重新定义effect函数 =>变成了注册副作用函数的函数

笔记

改善4.2缺陷逻辑

  1. 改善4.2中的缺陷,希望副作用函数的名字改变或者是匿名函数都能正常收集到Set存储桶中。因此需要提供一个注册副作用函数的机制
  2. 副作用函数已存储到activeEffect中,所以get拦截函数应该把activeEffect收集到Set存储桶中,如此响应式系统就不依赖副作用函数的名字了。
// 用一个全局变量存储被注册的副作用函数
let activeEffect
// effect 函数用于注册副作用函数
// 参数fn 就是将要注册的副作用函数
function effect(fn) {// 当调用effect 注册副作用函数时,将副作用函数fn 赋值给actvieEffectactiveEffect = fn// 执行副作用函数fn()
}
// 使用一个匿名的副作用函数作为 effect 函数的参数
// 首先会把匿名的副作用函数 fn 赋值给全局变量 activeEffect
// 接着执行被注册的匿名副作用函数 fn,会触发响应式数据 obj.text 的读取操作,进而触发代理对象 Proxy 的 get 拦截函数
effect(// 一个匿名的副作用函数() => {document.body.innerText = obj.text}
)

代理对象改动

// 代理对象 Proxy
const obj = new Proxy(data, {get(target, key) {// 将 activeEffect 中存储的副作用函数收集到“桶”中if (activeEffect) {  // 新增bucket.add(activeEffect)  // 新增}  // 新增return target[key]},set(target, key, newVal) {target[key] = newValbucket.forEach(fn => fn())return true}
})

修改后存在问题 :不存在属性依然执行

当我们修改响应式数据obj 不存在的一个属性的时候,匿名副作用函数依然会执行

effect(// 一个匿名的副作用函数() => {console.log('effct run') // 会打印两次document.body.innerText = obj.text;}
);setTimeout(()=>{obj.noExist = 'hello vue3'
})

完整代码 demo (如下)

const bucket = new Set();
const data = { text: "hello world" };// 用一个全局变量存储被注册的副作用函数
let activeEffect;
// effect 函数用于注册副作用函数
// 参数fn 就是将要注册的副作用函数
function effect(fn) {// 当调用effect 注册副作用函数时,将副作用函数fn 赋值给actvieEffectactiveEffect = fn;// 执行副作用函数fn();
}// 代理对象 Proxy
const obj = new Proxy(data, {get(target, key) {// 将 activeEffect 中存储的副作用函数收集到“桶”中if (activeEffect) {// 新增bucket.add(activeEffect); // 新增} // 新增return target[key];},set(target, key, newVal) {target[key] = newVal;bucket.forEach((fn) => fn());return true;}
});// 使用一个匿名的副作用函数作为 effect 函数的参数
// 首先会把匿名的副作用函数 fn 赋值给全局变量 activeEffect
// 接着执行被注册的匿名副作用函数 fn,会触发响应式数据 obj.text 的读取操作,进而触发代理对象 Proxy 的 get 拦截函数
effect(// 一个匿名的副作用函数() => {console.log('effct run') // 会打印两次document.body.innerText = obj.text;}
);setTimeout(()=>{obj.noExist = 'hello vue3'
})

根本原因:没有在副作用函数与被操作的目标字段之间建立明确的联系

也就是说,当我们读取或者设置时,无论操作哪一个属性,那么get和set方法都会执行

解决方案: 修改数据结构

观察代码,并思考如何设计数据结构。(树型结构)

effect(function effectFn() {document.body.innerText = obj.text
})

在这段代码中存在三个角色:

  • 被操作(读取)的代理对象 obj
  • 被操作(读取)的字段名 text
  • 使用 effect 函数注册的副作用函数 effectFn

树形结构(三种情况举例)

使用WeakMap 代替Set作为桶的数据结构

// 存储副作用函数的桶
const bucket = new WeakMap()

修改get/set 拦截器

const obj = new Proxy(data,{get(target,key){// 没有activeEffect直接returnif(!activeEffect){return target[key]}// 根据target 从WeakMap桶中取得depsMap,它也是一个Map类型:key-->effectslet depsMap = bucket.get(target)// 如果不存在depsMap 则新建一个Map与target关联if(!depsMap) {bucket.set(target,(depsMap = new Map()))}// 根据key 从 depsMap 中取得deps,它是一个Set类型// 里面存储这所有与当前Key相关联的副作用函数effectslet deps = depsMap.get(key)// 如果deps不存在 同样新建一个Set并关联if(!deps) {depsMap.set(key,(deps = new Set()))}// 最后将当前激活的副作用函数添加到WeakMap 桶里deps.add(activeEffect)// 返回属性值return target[key]},set(target,key,newVal){// 设置属性值target[key] = newVal// 根据target 从WeakMap桶中取得depsMap 它是key -->effectsconst depsMap = bucket.get(target)if(!depsMap) { return }//根据key 获取所有副作用函数effectsconst effects = depsMap.get(key)// 执行副作用函数effects && effects.forEach(fn=>fn())}
})

WeakMap、Map 和 Set 之间的关系

为什么要使用 WeakMap(WeakMap与Map的区别)

WeakMap 对 key 是弱引用,不影响垃圾回收机制,不会造成内存泄露,自动回收。

立即执行函数执行完毕后,Map的key foo 依然保持着对 foo 对象的引用,而WeakMap 的key bar 属性就不能访问了。

更多参见底部延伸阅读

const map = new Map();
const weakmap = new WeakMap();
(function(){const foo = {foo: 1};const bar = {bar: 2};map.set(foo, 1);weakmap.set(bar, 2);
})()

优化代码 封装内部逻辑 trigger 触发 track 追踪

代码优化,封装get和set内把副作用函数放入桶内和执行副作用函数的逻辑

const obj = new Proxy(data, {// 拦截读取操作get(target, key) {// 将副作用函数 activeEffect 添加到存储副作用函数的桶中track(target, key)// 返回属性值return target[key]},// 拦截设置操作set(target, key, newVal) {// 设置属性值target[key] = newVal// 把副作用函数从桶里取出并执行trigger(target, key)}
})
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {// 没有 activeEffect,直接 returnif (!activeEffect) returnlet depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)
}
// 在 set 拦截函数内调用 trigger 函数触发变化
function trigger(target, key) {const depsMap = bucket.get(target)if (!depsMap) returnconst effects = depsMap.get(key)effects && effects.forEach(fn => fn())
}

延伸阅读

阮一峰 Es6 WeakMap

张鑫旭 JS WeakMap应该什么时候使用

4.3 设计一个完善的响应式系统相关推荐

  1. (6K字!)从零实现vue3响应式系统!

    在介绍响应式系统之前,先了解一些概念. 副作用函数 能产生副作用的函数就是副作用函数. 何为副作用?在我理解,就是会可能对其他的除了自身以外的数据造成变化的的函数.比如修改了全局变量,修改了引用的参数 ...

  2. 深入剖析Vue源码 - 响应式系统构建(上)

    从这一小节开始,正式进入Vue源码的核心,也是难点之一,响应式系统的构建.这一节将作为分析响应式构建过程源码的入门,主要分为两大块,第一块是针对响应式数据props,methods,data,comp ...

  3. 经典网页设计:10个响应式设计的国外购物网站

    今天我想与大家分享电子商务主题的网站设计,更精确地说是为设计在线商店提供新思想.每个人都知道移动技术的市场发展迅速,已经很难找到一个人没有手机的人了.响应设计给我们提供了一个巨大的机遇:站点的访问量的 ...

  4. 响应式网页设计代码_消除响应式网站建设设计中的缺陷

    在过去的5年里,移动流量的份额增长了20%,现在响应式网站设计已经被认为是理所当然的了.到2020年,您可以通过手机几乎可以访问任何网站,它会很好用.大多数用户如果看到一个网站在他们的智能手机或平板电 ...

  5. UI设计中如何做响应式设计与自适应设计

    UI设计中如何做响应式设计与自适应设计?由于科技在不断的发展,小伙伴们上网就不单单只依靠台式电脑了,还有平板电脑笔记本电脑都是可供大家选择的.面对不同的屏幕分辨率网站是如何进行适配的呢?今天AAA教育 ...

  6. HTML5期末大作业:旅游网站设计——简单大气的响应式旅游网页(5页) HTML+CSS+JavaScript...

    HTML5期末大作业:旅游网站设计--简单大气的响应式旅游网页(5页) HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机毕设网页设计源码 常见网页 ...

  7. html网页设计期末大作业——响应式化妆品护肤品网页(11页) web课程设计 网页规划与设计

    html网页设计期末大作业--响应式化妆品护肤品网页(11页) web课程设计 网页规划与设计 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶叶. 家居. ...

  8. HTML5期末大作业:电竞游戏网站设计——电竞游戏介绍响应式网页(7页) HTML+CSS+JavaScript

    HTML5期末大作业:电竞游戏网站设计--电竞游戏介绍响应式网页(7页) HTML+CSS+JavaScript 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. ...

  9. 意外的服务器响应_响应式系统reactive system初探

    初识响应式系统 第一次听到reactive这个词还是在几年前,偶然了解到了Rxjava这个项目,仿佛为我打开了一扇新的大门,Rxjava是ReactiveX的java实现,ReactiveX家族除了R ...

最新文章

  1. android webview js交互,响应webview图片的响应事件
  2. 嵌入式牛人 | 这些单片机编程思想超硬核
  3. 洛谷 P3184 [USACO16DEC]Counting Haybales数草垛
  4. 新一代“土豪专享”机来了!三星W2020通过WiFi联盟认证
  5. Google发布全球首个72量子比特处理器Bristlecone预览
  6. 数据帮助类(DataHelper)
  7. ceisum加载shp格式的城市白模建筑数据
  8. 西工大计算机学院软件工程专硕,念念不忘,必有回响——西北工业大学软件工程专硕...
  9. 2018中国双态运维用户大会现场精彩集锦
  10. 我看过的关于职业规划最好最全面的一篇文章
  11. 本地以图搜图识图工具——嗅图狗!(python3.6实现图像比对指路,代码git)
  12. X1000 Kernel 3.10 Linux V8.2编译
  13. WIn7 解决 - ReadyBoost 未启用,因为该计算机的运行速度足够快
  14. E - Enigma Gym - 101889E dp求可除一个整数的最小数
  15. 矩阵基础概念之行列式与秩
  16. 联盟链系列 - 中间CA颁发证书
  17. 论文阅读笔记:An End-to-End Trainable Neural Network Model with Belief Tracking for Task-Oriented Dialog
  18. 图片采集-输入关键词批量收集图片免费
  19. ADB模拟点击、滑动事件
  20. 领袖的七个非常重要的根性-余世维

热门文章

  1. CentOS 6.X LVM 在线扩容
  2. VSCODE中配置JavaScript编译环境
  3. 阵列卡u盘安装系统步骤_手工安装Windows Server 2008操作系统步骤
  4. 微信小程序微信支付《JSAPI支付》APIV3详细教程
  5. 狂欢过后,万众期待的元宇宙怎么样了?
  6. 常用金属材料基本知识
  7. 什么软件可以图片转文字?这几个软件值得收藏
  8. 豆瓣8.7!BBC这部成人社会禁片,曝光了行业内不能说的秘密
  9. 知识点:PCB板布线流程
  10. WiFi 包分析参考