Vue响应式数据的实现原理(手写副作用函数的存储和执行过程)

news2024/10/5 15:22:51

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后的内容设置为text
                h1.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] = newVal
        bucket.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 activeEffect

function effect(fn) {
    activeEffect = fn
    fn()
}

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] = newVal
        bucket.forEach(fn => fn())
        return true
    }
})

没有建立副作用函数和目标字段之前明确的关系——解决:

以上代码对每个属性都绑定了同一个副作用函数,实际上真实需要的时,修改text时,调用他自己的函数,修改a时又调用a对应的函数

解决:

  1. 使用Map键值数据结构(分两层)进行存储副作用函数,将每个数据对象对应一个map的键;
  2. 一个数据对象下不同属性又存储到一个Map数据下,这个map的值则存储的副作用函数(Set形式存)
  3. 使用时通过获取对象的map(数据对象)下的对应属性key(每个对象的key)的值的set数据(针对每个属性操作的副作用函数)并进行遍历执行即可

const obj = new Proxy(data, {
    get(target, key) {
        console.log(activeEffect, 'activeEffect')
        // 没有副作用函数,容错处理 直接return
        if (!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
        })

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1136885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ni-IDA琼脂糖凝胶FF-------可用于纯化带组氨酸标签(His-Tag)的重组蛋白

品 名&#xff1a;Ni-IDA琼脂糖凝胶FF(Nickel Iminodiacetic acid Pharose Fast Flow, Ni-IDA Pharose FF) 规 格&#xff1a;10 ml&#xff0c;100 ml&#xff0c;1L&#xff0c;1 ml预装柱&#xff0c;5 ml预装柱 贮 存&#xff1a;20%乙醇&#xff0c;2-25℃ 运…

国产信号发生器 1442/1442A射频信号发生器

信号发生器 1442/A射频信号发生器 1442系列射频信号发生器是一款针对通信、电子等射频应用而设计开发的产品。覆盖了所有的常用射频频段。它采用模块化结构设计&#xff0c;全中文界面、大屏幕菜单控制&#xff0c;其输出信号相位噪声极低&#xff0c;频率分辨率和准确度高&am…

遥感语义分割、变化检测论文小trick合集(持续更新)

目录 &#x1f497;&#x1f497;1.影像融合机制 &#x1f497;&#x1f497;2.上下文聚合模块 &#x1f497;&#x1f497;3.adapter即插即用模块 &#x1f497;&#x1f497;1.影像融合机制 参考【多源特征自适应融合网络的高分遥感影像语义分割】文章中的“多源特征自适应…

注意力机制QKV在GAT(Graph Attention Network)的体现

注意力机制其实并没有规定 Q、K、V 的具体来源&#xff0c;GAT是规定了一套Q、K、V&#xff0c;自注意力是规定了另一套Q、K、V。核心其实只要计算满足下图的矩阵形式计算流程就是所谓的注意力机制了。学过注意力机制的应该都看的明白。 在自注意力机制self-attention中&…

竞赛选题 深度学习卷积神经网络的花卉识别

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基…

IP地址:网络层的介绍

我们花费很大的篇幅讲解了应用层的TCP和UDP协议。现在我们进入到网络层的学习&#xff0c;重点要学习的就是IP协议。 对于IP协议来说&#xff0c;重要的有IPv4和IPv6协议&#xff0c;我们重点介绍IPv4协议。 IP报头 4位版本&#xff1a; 此处的取值只有4和6&#xff0c;代表…

Redis安全之从入门到花式利用

0x00 安全研究思路 正常安全研究思路大致可以是这样&#xff1a; 正常功能&#xff0c;为什么这个功能会导致漏洞&#xff0c;怎么使用不会有漏洞&#xff0c;开发为什么会这么写如何攻击&#xff0c;攻击会遇到什么情况什么限制如何解决如何武器化如何防御&#xff0c;在什么…

uniapp开发小程序—picker结合后台数据实现二级联动的选择

一、效果图 二、完整代码 <template><view><picker mode"multiSelector" change"bindMultiPickerChange" columnchange"bindMultiPickerColumnChange":value"multiIndex" :range"multiArray"><view c…

做外贸为何离不开WhatsApp?一文解封、养号、引流、促单全攻略!

WhatsApp在国际贸易中的地位无法忽视。它是一种即时通讯工具&#xff0c;也是外贸从业者的得力助手。但同时&#xff0c;使用WhatsApp也伴随着一些问题&#xff0c;如账号被封、如何养号、引流和促单。这篇文章将为你详细解答这些问题&#xff0c;让你更好地利用WhatsApp&#…

个人企业项目招投标小程序开发

项目招投标小程序开发 针对个人企业招投标开发的小程序。 程序基本能力&#xff1a;用户缴纳保证发布招标信息&#xff0c;然后商家进行认证成功后可以对招标发起投标&#xff0c;投标过程也需要缴纳保证金&#xff0c;招标结束或者下架保证金将全部退回到用户账号里面。 招…

生物芯片技术-原理、应用与未来发展

生物芯片技术-原理、应用与未来发展 一、引言 随着科技的不断发展&#xff0c;生物芯片技术已成为生物医药领域的重要支柱。这种技术运用微电子和微机械工艺&#xff0c;将生物分子、细胞、组织等生命活性物质固定在硅片、玻璃片、塑料片等固相基质上&#xff0c;实现生物信息…

filebeat7.10上传日志到ES7.14

filebeat版本&#xff1a;filebeat-7.10.0 版本&#xff1a;filebeat-7.10.0-linux-x86_64.tar.gz filebeat7.10上传日志到ES7.14 1、下载filebeat wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.10.0-linux-x86_64.tar.gz 2、安装filebeat ta…

从果蔬到乳制品,探索食品微生物对肠道健康的影响

谷禾健康 俗话说病从口入&#xff0c;饮食对人体具有重要的影响&#xff0c;蔬菜和水果作为每日饮食中必不可少的成分&#xff0c;为人类提供了重要的营养物质&#xff0c;包括各种必需的维生素和矿物质。 此外&#xff0c;蔬菜和水果上栖息着数量惊人的微生物&#xff0c;高度…

ASO优化之通过页面的优化来提升排名

应用商店优化是一个持续优化应用列表的过程&#xff0c;从而让我们的应用更容易被目标受众发现。通过实施ASO&#xff0c;我们可以在竞争激烈的应用市场中有效竞争&#xff0c;并为我们的应用带来自然流量。 1、添加关键词。 进行关键词研究&#xff0c;从而确定与应用程序功能…

【JavaSE语法】数据类型与变量

一、字面常量 常量即程序运行期间&#xff0c;固定不变,不可修改的量称为常量 public class Demo {public static void main(String[] args) {System.out.println("hello World!");System.out.println(100);System.out.println(3.14);System.out.println(A);System…

【欧拉函数】CF1731E

Problem - E - Codeforces 题意 思路 对于 k 次操作&#xff0c;gcd(u, v) k 1&#xff0c;代价的贡献就是二元组 (u, v)的个数 * (k 1) 那么就要我们求二元组个数 这个是个很经典的欧拉函数的套路&#xff0c;可以用线性筛把欧拉函数求出来&#xff0c;然后求个前缀和 …

动手学深度学习——第四次

梯度下降是在机器学习中用于寻找最佳结果&#xff08;即曲线最小值&#xff09;的一种迭代优化算法。 最小化loss&#xff0c;只需要将参数沿着梯度相反的方向前进一个步长&#xff0c;就可以实现目标函数&#xff08;loss function&#xff09;的下降。这个步长 η \etaη 又称…

在antd里面渲染MarkDown并且自定义一个锚点目录TOC(重点解决导航目录不跟随文档滚动的问题)

一、整体思路 由于有很多很长的文档需要渲染&#xff0c;我觉得用MarkDown的方式会比较适合管理&#xff0c;所以这两天测试了一下在antd里面集成MarkDown的渲染模块。 总体思路参考&#xff1a; https://blog.csdn.net/Sakuraaaa_/article/details/128400497 感恩大佬的倾情付…

技术分享 | 某下一代防火墙远程命令执行漏洞分析及防护绕过

0x01 概述 最近&#xff0c;某下一代防火墙曝光了远程代码执行漏洞。此漏洞通过绕过身份认证和注入 cookie 的方式来执行系统命令&#xff0c;公开的利用方式受到诸多限制且命令执行无回显&#xff0c;并且当目标机器不出网时&#xff0c;该漏洞利用方式便无法发挥作用&#x…

C语言——有 15 个数按由大到小顺序存放在一个数组中,输入一个数,要求用折半查找法找出该数是数组中第几个元素的值。如果该数不在数组中,则输出“无此数”

完整代码&#xff1a; /* 有 15 个数按由大到小顺序存放在一个数组中&#xff0c;输入一个数&#xff0c;要求用折半查找法找出 该数是数组中第几个元素的值。如果该数不在数组中&#xff0c;则输出“无此数”。*/ #include<stdio.h>//折半查找法&#xff0c;n是查找的那…