3.2 继续完善的Vue.js响应式系统

news2024/12/27 13:19:48

前文提要:
3.0 响应式系统的设计与实现

3.1 一个稍微完善的Vue.js响应式系统

1、解决副作用函数的死循环问题

在解决了分支的切换的问题,此时还有一个代码死循环的问题,其这个死循环很容易触发,如下代码:

const data = {ok: true, text: 'hello world'}
const obj = new Proxy(data, {
    get: (target, key, receiver) => {
        track(target, key, receiver)
        return Reflect.get(target, key, receiver)
    },
    set: (target, key, value, receiver) => {
        Reflect.set(target, key, value, receiver)
        trigger(target, key)
        return true
    }
}); 

effect(()=>{
    console.log(obj.ok)
})
obj.ok = true

function trigger(target, key) {
    const depsMap = bucket.get(target)
    if(!depsMap) return
    const effects = depsMap.get(key)
    // 问题出在这里
    deps && deps.forEach(fn => fn())
} 

其实问题就出在trigger函数执行的过程中,这里对于obj.ok的改变导致了副作用函数执行,在执行的过程中有console.log(obj.ok),这里再次访问了obj.ok,则将副作用函数又插回了副作用函数的执行队列中,效果相当于

const set = new Set();
set.add('one');
set.forEach(e => {
    console.log(e)
    set.delete(e);
    set.add('one')
})

set删除值之后,再插入相同的值也会让forEach一直执行。

其实解决的方案很简单,可以在执行时将deps内的值给取出来,放在新的一个Set中,然后遍历新的Set,这样如果有新加入的值会放入deps中,而不是一边执行一边插入。

function trigger(target, key) {
    const depsMap = bucket.get(target)
    if(!depsMap) return
    const effects = depsMap.get(key)
    // 重新放入,再执行
    const effectsToRun = new Set(effects)
    // 将副作用函数追踪下来,防止出现set在删除时插入新值
    effectsToRun.forEach(effectFn => effectFn())
} 

2、解决在effect函数嵌套时出现的问题

effect函数嵌套是很常见的,比如我们嵌套的组件,父子组件都绑定了副作用函数,如:

// 当发生嵌套时
// Bar组件
const Bar = {
    render() {
        return...
    }
}

// Foo组件为Bar的父组件
const Foo = () => {
    render() {
        return <Bar />
    }
}

// 此时的effect发生了嵌套,相当于
effect(() => {  
    Foo.render() 
    effect(() => {
        Bar.render
    })
})

对于我们上面举例的副作用函数潜逃做个实验,实验代码如下

// 其余代码......

function effect(fn) {
    const effectFn = () => {
      activeEffect = effectFn
      //先清除再执行,自然就形成了分支切换
      cleanup(effectFn)
      fn()
    }
    effectFn.deps = []
    effectFn()
}

// 这里是重点
effect(() => {
    console.log('effect 1 has beeen executed')
    effect(() => {
        console.log('effect 2 has beeen executed')
        temp2 = obj.ok
    })
    temp1 = obj.text
})

obj.text = '1'

此时我们期望执行为触发两次外层的的effect函数,即打印结果为

effect 1 has beeen executed
effect 2 has beeen executed
effect 1 has beeen executed
effect 2 has beeen executed

在建立时能够触发一次外层effect函数,然后obj.text = 1会再次执行外层的effect函数,但是实际的结果是什么呢

effect 1 has beeen executed
effect 2 has beeen executed
effect 2 has beeen executed

这个明显和我们的期望不同,第一次外层effect函数执行输出的前两句是符合预期的,但是第二次修改obj.text触发的却是内层的effect函数。也就是说绑定的obj.text 的副作用函数变成了内层函数。

分析导致这个结果的原因还是看effect实现的过程:

let activeEffect = null

function effect(fn) {
    const effectFn = () => {
      activeEffect = effectFn
      //先清除再执行,自然就形成了分支切换
      cleanup(effectFn)
      fn()
    }
    effectFn.deps = []
    effectFn()
}

在第二层effect执行的时候activeEffect赋值为obj.ok的副作用函数,当第二层effect函数执行完之后,回到第一层时的activeEffect指向的还是第二层的effectFn,所以此时obj.text的副作用函数就成了第二层的effect函数。

其实这个过程就是函数递归时,avtiveEffect的执行也在递归过程中逐步指向内层的副作用函数,但是当递归出来的时候,activeEffect并没有从内层向外层恢复。这个问题实质上就是一个入栈出栈的问题,解决也很轻松,我们可以使用一个栈来记录副作用函数递归时每一层的指向,这样在递归出来时副作用函数指向的就是栈顶元素,代码如下:

const effectStack = []
function effect(fn) {
    const effectFn = () => {
      activeEffect = effectFn
      //先清除再执行,自然就形成了分支切换
      cleanup(effectFn)

      // 用栈记录下当前的辅作用函数  
      effectStack.push(effectFn)
      fn()
      effectStack.pop()
      // 递归出来时改变activeEffect指向
      activeEffect = effectStack[effectStack.length-1]
    }
    effectFn.deps = []
    effectFn()
}

其过程如图:
进栈出栈过程

3、解决无限递归的问题

假设我们副作用函数的代码如下:

effect(() => {
    obj.ok = obj.ok || 1
})

这个副作用函数存在的问题是对obj.ok进行了访问,又对他赋值,这会在effectFn中同时触发triggertrack,在trigger中又会触发effectFn这样下去就形成了一个死递归,报错信息为:RangeError: Maximum call stack size exceeded

解决思路就是在trigger中判断一下,就根据这个死递归的过程,在执行追踪的时候查看副作用函数activeEffect和当前调用函数是否相同,相同则跳过。

function trigger(target, key) {
    const depsMap = bucket.get(target)
    if(!depsMap) return
    const effects = depsMap.get(key)
    const effectsToRun = new Set()
    effects && effects.forEach(effectFn => {
        // 判断正在执行的副作用函数和当前的即将执行的函数是否相同
        if(effectFn !== activeEffect) 
            effectsToRun.add(effectFn)
    })
    // 将副作用函数追踪下来,防止出现set在删除时插入新值
    effectsToRun.forEach(effectFn => effectFn())
} 

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

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

相关文章

Netty Incubator Codec QUIC 0.0.41.Final 发布

导读Netty Incubator Codec QUIC 是一款基于 QUIC 协议的编解码器&#xff0c;为 Netty 提供了 QUIC 协议的支持。 近日&#xff0c;该团队发布了 0.0.41.Final 版本&#xff0c;这是一个错误修复版本&#xff0c;主要包括以下变化: 允许在派发前通过添加到读完队列来合并刷新…

嵌入式软件测试笔记3 | 嵌入式软件测试开发的多V模型

3 | 嵌入式软件测试开发的多V模型 1 简单的多V模型2 迭代与并行开发2.1 开发模型2.2 嵌入式开发过程的复杂性 3 多V模型中的测试活动3.1 测试活动和因素3.2 模型开发周期中与测试相关的元素分配3.3 原型开发周期中与测试相关的元素分配3.4 最终产品开发周期中与测试相关的元素分…

NineData x 华为云正式上线

6月5日&#xff0c;NineData 企业级 SQL 开发平台正式成为华为云“联营联运”商品。通过联营联运模式&#xff0c;双方将在产品、解决方案和生态等多个方面开展深度合作&#xff0c;共同提供高效、智能、安全的数据管理服务&#xff0c;帮助客户轻松构建一站式云端数据库管理平…

【随想录】一篇水文

前排许愿池: 我是一个没有梦想的咸鱼捏 自从知道成电优营了也不给offer之后 遂开始摆烂了(哈哈) 以及看了一下数据 好像前期存的资本够多的话 后面还是能混混的 however,已经快过去2/3了 前排致谢: 感谢好人一姐的助力 果然人是靠别人活着的 或者说伟人是站在巨人…

基于显扬科技3D视觉相机的芯片外观检测系统

Part.1 行业背景 电子元器件制造业是我国的支柱产业之一&#xff0c;具有产量大、技术投入高的特点&#xff0c;因此产品质量把控与生产成本优化是电子行业关注的发展重点。 芯片作为电子元器件中的核心组成部分&#xff0c;在现代社会被广泛应用&#xff0c;在芯片生产制造过…

Redis经典五大数据类型源码及底层实现

Redis经典五大数据类型源码及底层实现 一 面试题引入二 Redis数据类型的底层数据结构三 redis是字典数据库&#xff0c;KV键值对到底是什么&#xff1f;3.1 怎样实现键值对&#xff08;key-value&#xff09;数据库的&#xff1f;3.2 redisObject结构的作用3.3 RedisObject各字…

微信支付商户接入指引(企业)

目录 一、官方指引二、申请规则三、申请流程&#xff08;一&#xff09;提交资料&#xff08;二&#xff09;签署协议&#xff08;三&#xff09;绑定场景 一、官方指引 https://kf.qq.com/faq/210423UrIRB7210423by6fQn.html 二、申请规则 1、微信支付商家仅面向企业、个体…

三分钟告诉你录音实时转写软件哪个好

真的不想录音笔记实时转写软件有哪些吗 录音实时转写软件免费有哪些&#xff1f;录音实时转写软件推荐 实时录音转文字软件哪个好&#xff1f;录音实时转写软件分享 录音实时转写软件哪个好&#xff1f;录音实时转写软件盘点 让你知道视频配音文字转语音软件有哪些 让你不…

耗时1周整理了网络安全学习路线,非常详细!

前言 这一期就出一个怎么学习网络安全的学习路线和方法&#xff0c;觉得有用的话三连收藏下 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xff0c;有些人会倒在学习linux系统及命令的路上…

selenium基础语法

文章目录 selenium基础语法1.定位页面元素2.元素的操作1) 模拟键盘输入(send_keys)2) 点击操作(click)3) 清除去对象输入的文本内容(clear)4) 获取文本(gettext) 3. 等待4. 信息打印5. 窗口6. 导航7. 弹窗8. 鼠标和弹窗9.选择框10.文件上传11.屏幕截图 selenium基础语法 1.定位…

面试官问:kafka为什么如此之快?

前言 天下武功&#xff0c;唯快不破。同样的&#xff0c;kafka在消息队列领域&#xff0c;也是非常快的&#xff0c;这里的块指的是kafka在单位时间搬运的数据量大小&#xff0c;也就是吞吐量&#xff0c;下图是搬运网上的一个性能测试结果&#xff0c;在同步发送场景下&#…

OpenStack介绍

OpenStack 1. OpenStack1.1 简介1.2 OpenStack和KVM有什么关系和区别&#xff1f;1.3 编写语言 2. 主要模块介绍2.1 OpenStack计算设施 - Nova1. API服务器&#xff08;nova-api&#xff09;2. 消息队列&#xff08;Rabbit MQ Server&#xff09;3. 运算工作站&#xff08;nova…

LibTorch部署图像处理相关算法详细教程(附代码)

深度学习图像处理相关代码LibTorch部署详细教程 前言LibTorch简介LibTorch环境安装及问题解决LibTorch涉及的Tensor基本操作张量初始化张量变形张量截取张量间操作 部署过程测试环境推理过程代码Demo扩展部分 前言 本文写于调研深度学习部署方法工作中&#xff0c;需要将图像分…

如何将PDF转Excel并保持原有格式不变?分享三个方法给大家!

在日常办公中&#xff0c;我们常常面临一个问题&#xff1a;领导给我们发了一个PDF文件&#xff0c;并要求我们尽快修改其中的表格数据。然而&#xff0c;当我们将PDF文件转换成Excel格式时&#xff0c;经常会出现文件排版错乱的情况。时间的紧迫和数据的混乱可能会让我们感到十…

【FTP】FTP被动模式跨网传输失败

FTP被动模式所需的端口21、20、60000-60050 假如端口20未开策略的话&#xff0c;造成传输失败 需要在FTP服务端用户配置文件里设置pasv_address&#xff08;对外的ip地址&#xff09; 特此记录一下&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;…

NLP(2)N-gram language Model (缺了一些平滑的方式介绍)

文章目录 N-gram Language ModelTrigram Example存在的问题smoothingLaplacian &#xff08;add-one&#xff09; smoothing案例 1案例 2 Add-k smoothingAbsolute Discounting案例 Interpolation 在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;语言模型&#xff08…

机器鸟实现扇动翅膀功能

1. 功能说明 本文示例将实现机器鸟扇动翅膀的功能。 2. 结构说明 鸟类的翅膀主要由肩关节、肘关节、腕关节组成&#xff0c;本样机利用组合机构设计机器鸟的扑翼机构。 拥有两个关节的机器鸟扑翼机构结构图 单侧翅膀 双翅 尾部 整机 3. 电子硬件 在这个示例中&#xff0c;我们…

使用Optuna进行PyTorch模型的超参数调优

Optuna是一个开源的超参数优化框架&#xff0c;Optuna与框架无关&#xff0c;可以在任何机器学习或深度学习框架中使用它。本文将以表格数据为例&#xff0c;使用Optuna对PyTorch模型进行超参数调优。 Optuna可以使用python pip安装&#xff0c;如pip install Optuna。也可以使…

【Spring】— 动态SQL :<foreach>元素、<bind>元素

目录 <foreach>元素<bind>元素 <foreach>元素 MyBatis中已经提供了一种用于数组和集合循环遍历的方式&#xff0c;那就是使用<foreach>元素。假设在一个用户表中有1000条数据&#xff0c;现在需要将id值小于100的用户信息全部查询出来&#xff0c;就可…

MFC(十三)多个对话框

设置向导模式 1.打开类视图&#xff0c;右键项目--->类向导-->添加Cpropsheet类&#xff0c;命名为mypropsheet CPropertySheet 是 MFC 的一个类&#xff0c;用于创建包含多个属性页的对话框。它可以使用内置的向导模式&#xff0c;向用户显示“下一步”和“上一步”按…