Vue响应式数据的实现原理(手写副作用函数的存储和执行过程)
1.命令式和声明式框架
命令式框架关注过程
声明式框架关注结果(底层对命令式的DOM获取和修改进行了封装)
2.vue2 Object.defineProperty()双向绑定的实现
<body><div id="app"><input type="text" /><h1></h1><button>按钮</button> </div>
</body>
<script>// vue2实现双绑const input = document.getElementsByTagName('input')[0]const h1 = document.getElementsByTagName('h1')[0]const btn = document.getElementsByTagName('button')[0]let data = { text: '' }// input框输入数据,h1数据和text一致;实现点击按钮时,h1 标签数据和input框数据同时更改Object.defineProperty(data, 'text', {get() {return data['text'];},set(value) {// 获取到值后将h1后的内容设置为texth1.innerText = value;input.value = value;return true;}});input.oninput = function (e) {data.text = e.target.value;}btn.onclick = function () {data.text = "你好"}
</script>
3.同样页面Vu3实现 new Proxy()
// vue3实现双绑const input = document.getElementsByTagName('input')[0]const h1 = document.getElementsByTagName('h1')[0]const btn = document.getElementsByTagName('button')[0]let data = { text: '' }let obj = new Proxy(data, {get(target, property) {return target[property]},set(target, property, value) {h1.innerText = value;input.value = value;return true;}})input.oninput = function (e) {obj.text = e.target.value;}btn.onclick = function () {obj.text = "你好"}
4.响应式数据的基本实现
响应式数据的关键是拦截对象属性的设置和读取操作
const data = { text: '' }
function effect () {document.body.innerText = data.text
}
5. vue2与vue3响应式数据实现区别
- vue2的实现:当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用Object.defineProperty() 把这些 property 全部转为 getter/setter。
- vue3的实现:当我们从一个组件的 data 函数中返回一个普通的 JavaScript 对象时,Vue 会将该对象包裹在一个带有 get 和 set 处理程序的Proxy 中
6.vue3 proxy的简单实现响应式数据拦截
// 初始数据
const data = { text: '' }
// 存储副作用函数的桶
const bucket = new Set()
// 对数据进行代理
const obj = new Proxy(data, {get(target, key) {bucket.add(effect)return target[key]},set(target, key, newVal) {target[key] = newValbucket.forEach(fn => fn())return true}
})function effect () {document.body.innerText = obj.text
}effect()setTimeout(() => {obj.text = '你好'
}, 1000)
7.简单实现中出现的问题
思考一下 这一段代码的问题。
- 1. 副作用函数的名称被写死
- 2. 没有建立副作用函数和目标字段之前明确的关系
副作用函数的名称被写死——解决:
名称写死问题:真实情况下副作用函数不可能只有一个,那如果有多个函数时,每个函数执行都会调用一个副作用函数。比如,设置obj.a = 2,同样会调用set方法中的bucket.forEach(fn => fn())方法。
通用一个副作用作用函数,将执行DOM修改的函数以闭包形式(回调函数)传入副作用函数,这样每次返回的都不是同一个函数
let activeEffectfunction effect(fn) {activeEffect = fnfn()
}effect(() => {document.body.innerText = obj.text
})const obj = new Proxy(data, {get(target, key) {if (activeEffect) {bucket.add(activeEffect)}return target[key]},set(target, key, newVal) {target[key] = newValbucket.forEach(fn => fn())return true}
})
没有建立副作用函数和目标字段之前明确的关系——解决:
以上代码对每个属性都绑定了同一个副作用函数,实际上真实需要的时,修改text时,调用他自己的函数,修改a时又调用a对应的函数
解决:
- 使用Map键值数据结构(分两层)进行存储副作用函数,将每个数据对象对应一个map的键;
- 一个数据对象下不同属性又存储到一个Map数据下,这个map的值则存储的副作用函数(Set形式存)
- 使用时通过获取对象的map(数据对象)下的对应属性key(每个对象的key)的值的set数据(针对每个属性操作的副作用函数)并进行遍历执行即可

const obj = new Proxy(data, {get(target, key) {console.log(activeEffect, 'activeEffect')// 没有副作用函数,容错处理 直接returnif (!activeEffect) return target[key]// 判断桶中是否已经存在key与target的关联关系let depsMap = bucket.get(target)// 创建一个新的Map结构与 target 关联if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 判断当前Map数据中是否存在key与effect的关联关系let deps = depsMap.get(key)// 不存在的话 则新建一个Set 与 key关联if (!deps) {depsMap.set(key, (deps = new Set()))}// 最后将当前激活的副作用函数添加到bucket中deps.add(activeEffect)return target[key]},set (target, key, newVal) {target[key] = newVal// 获取bucket下对应的数据const depsMap = bucket.get(target)if (!depsMap) return// 根据key 获取副作用的执行函数const effects = depsMap.get(key)// 执行副作用函数effects && effects.forEach(fn => fn())}
})effect(() => {document.body.innerText = obj.text
})
effect(() => {document.title = obj.a
})
8.完全实现——我的测试
const data = { text: '这是obj.title', a:'vue响应式数据的实现原理' }// 存储副作用函数的桶const bucket = new Map()// 存储副作用函数的变量let activeEffect;// 对数据进行代理const obj = new Proxy(data, {get(target, key) {// 设置副作用函数到map数据的桶中// 判断不存在activeEffect直接返回if (!activeEffect) return target[key];// 存在activeEffect时将副作用函数设置到桶中let targetMap = bucket.get(target); //target对象存在才能判断key是否存在(targetMap既是bucket的值targetMap = new Map()又是keyMap的键的定义)if (!targetMap) {bucket.set(target, (targetMap = new Map()));}let keyMap = targetMap.get(key); // keyMap既是targetMap的值又是if (!keyMap) {targetMap.set(key, (keyMap = new Set()))}keyMap.add(activeEffect); //副作用函数最终是存在Set结构里面的return target[key]},set(target, key, newVal) {target[key] = newVal;//获取桶里面的对象Map的key的map集合的值,即所有的副作用函数进行循环执行let targetMap = bucket.get(target);if (!targetMap) return;let effects = targetMap.get(key);effects.forEach(fn => fn())return true}})function effect(fn) {// 将函数赋值给activeEffect,数据劫持到activeEffect有值时,会将其设置到bucket存储副作用的桶中,当拦截到数据更改时再获取对应函数并执行activeEffect = fn;fn();}// 副作用函数执行一次effect(() => {document.body.innerText = obj.text})effect(() => {document.title = obj.a})
相关文章:
Vue响应式数据的实现原理(手写副作用函数的存储和执行过程)
1.命令式和声明式框架 命令式框架关注过程 声明式框架关注结果(底层对命令式的DOM获取和修改进行了封装) 2.vue2 Object.defineProperty()双向绑定的实现 <body><div id"app"><input type"text" /><h1>…...
内核进程的调度与进程切换
进程被创建到了链表中,如何再进行进一步的调用和调用? 进程调度 void schedule(void); 进程调度 switch_to(next); 进程切换函数 void schedule(void) {int i,next,c;struct task_struct ** p;/* check alarm, wake up any i…...
docker-rabbitmq 安装依赖
出现的问题如下: channel error; protocol method: #method(reply-code404, reply-textNOT_FOUND - no channel error; protocol method: #method<channel.close>(reply-code404, reply-textNOT_FOUND - no 查看rabbitmq 客户端是否存在如…...
(1)(1.9) HC-SR04声纳
文章目录 前言 1 连接到自动驾驶仪 2 参数说明 前言 HC-SR04 声纳是一种价格低廉但量程很短(最远只有 2m)的测距仪,主要设计用于室内,但也成功地在室外的 Copter 上使用过。极短的测距范围使其用途有限。 !Warning…...
06 MIT线性代数-列空间和零空间 Column space Nullspace
1. Vector space Vector space requirements vw and c v are in the space, all combs c v d w are in the space 但是“子空间”和“子集”的概念有区别,所有元素都在原空间之内就可称之为子集,但是要满足对线性运算封闭的子集才能成为子空间 中 2 …...
【每日一题Day360】LC1465切割后面积最大的蛋糕 | 贪心
切割后面积最大的蛋糕【LC1465】 矩形蛋糕的高度为 h 且宽度为 w,给你两个整数数组 horizontalCuts 和 verticalCuts,其中: horizontalCuts[i] 是从矩形蛋糕顶部到第 i 个水平切口的距离verticalCuts[j] 是从矩形蛋糕的左侧到第 j 个竖直切口…...
中国地名信息库
地名是社会基本公共信息,是历史文化的重要载体。 2014年至2018年,国家启动实施并完成了第二次全国地名普查工作,全国共计采集地名1320多万条,修测标绘地名图2.4万多幅,新设更新地名标志68万多块,普遍建立了…...
网络时代下的声音之路:如何在中央新闻媒体发布网评稿
在当今数字时代,信息传播已经变得更加便捷和广泛。各大中央新闻媒体平台为民众提供了一个发布观点、表达意见的平台。在这个背景下,撰写并发布网评稿成为了一种重要的社会参与方式。根据媒介易软文发稿平台的总结,下面是探讨如何在各大中央新…...
Selenium中WebDriver最新Chrome驱动安装教程
😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。…...
云原生Docker数据管理
目录 Docker的数据管理 数据卷 数据卷容器 容器互联 容器中管理数据主要有两种方式: 数据卷(Data Volumes)数据卷容器(Data Volume Dontainers) Docker的数据管理 数据卷 数据卷是一个供容器使用的特殊目录&a…...
endnote设置
问题1:参考文献的tab太长 首先要在endnote里面这样设置,file->output->edit "XXX" 保存之后,在word更新目录。 在word里面设置悬挂缩进 结果: Endnote参考编号与参考文献距离太远怎么调整 endnote 文献对齐方式…...
计算机网络整理-简称缩写【期末复习|考研复习】
文章目录 前言一、物理层1.1 FDM频分复用 Frequency-division multiplexing1.2 TDM时分复用 Time-division multiplexing1.3 WDM波分复用 Wavelength Division Multiplexing1.4 Hub 集线器1.5 FSK频移键控 Frequency-shift keying 二、数据链路层2.1 GBN回退N步协议 Go Back N …...
Flink Hive Catalog操作案例
在此对Flink读写Hive表操作进行逐步记录,需要指出的是,其中操作Hive分区表和非分区表的DDL有所不同,以下分别记录。 基础环境 Hive-3.1.3 Flink-1.17.1 基本操作与准备 1、上传依赖jar包到flink/lib目录下 cp flink-sql-connector-hive-…...
NSSCTF做题第9页(3)
[GKCTF 2020]CheckIN 代码审计 这段代码定义了一个名为ClassName的类,并在脚本的最后创建了一个ClassName类的实例。 在ClassName类的构造函数中,首先通过调用$this->x()方法获取了请求参数$_REQUEST中的值,并将其赋值给$this->code属性…...
从瀑布模式到水母模式:ChatGPT如何赋能软件研发全流程【文末送书五本】
从瀑布模式到水母模式:ChatGPT如何赋能软件研发全流程 前言内容简介购买链接作者简介专家推荐读者对象参与方式往期赠书回 🏘️🏘️个人简介:以山河作礼。 🎖️🎖️:Python领域新星创作者,CSDN实…...
设置使用LibreOffice作为默认程序打开word、excel等文档
以win7为例。打开控制面板,点击程序: 点击“设置默认程序”: 左侧选中LibreOffice,然后在右下方点击“选择此程序的默认值”: 然后根据自己的需要勾选就行了:...
创新领航 | 竹云参编《基于区块链的数据资产评估实施指南》正式发布!
10月25日,由深圳数宝数据服务股份有限公司和深圳职业技术大学提出,中国科学院深圳先进技术研究院、中国电子技术标准化研究院、中国(天津)自由贸易试验区政策与产业创新发展局、网络空间治理与数字经济法治(长三角&…...
【Docker】Linux网桥连接多个命名空间
veth实现了点对点的虚拟连接,可以通过veth连接两个namespace,如果我们需要将3个或者多个namespace接入同一个二层网络时,就不能只使用veth了。 在物理网络中,如果需要连接多个主机,我们会使用bridge(网桥&…...
ES6新特性:let关键字详解
文章目录 1 声明提升2 作用域3 重复声明 在JavaScript中,let 和 var 都是声明变量的关键字,但在用法和作用域方面有一些区别。 let 是ES6引入的新的声明变量的关键字,它与 var 相比,更加严格,语法更加规范,…...
鸿运主动安全监控云平台任意文件下载漏洞复现 [附POC]
文章目录 鸿运主动安全监控云平台任意文件下载漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 鸿运主动安全监控云平台任意文件下载漏洞复现 [附POC] 0x01 前言 免责声明:请勿利用文章内的相关技术…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...
