当前位置: 首页 > news >正文

链表运用到响应式中

vue响应式实现原理

第一章 vue 简单的effect实现


前言

上一章我们实现了最简单的effect响应式,当时是有很多地方需要改进的,后面的都是在此基础上进行完善

一、链表

在讲解effect后续的内容之前,我们必须先了解一个数据结构:链表,这是一个非常重要的东西,因为源码里面effect就是依靠链表结构来处理的;
当然我们这里只是简单说一下链表的结构,具体的大家可自行查看

1.链表的结构

链表的结构其实不算复杂,一张图就能看懂
在这里插入图片描述
这就是一个链表,就这么简单,当然,这是一个单向链表,因为只有一个指向下一节点的指针;

const node1 = {data: 10,       // 数据域next: null      // 指针域,初始指向null
};const node2 = {data: 20,next: null
};const node3 = {data: 30,next: null
};// 构建链表关系(节点1 → 节点2 → 节点3)
node1.next = node2;
node2.next = node3;// 此时链表结构为:
// node1 → node2 → node3 → null

用js来表示,就是如上所示;
当然看到单向链表那就有双向链表(vue源码是用的是双向链表),那我们再画张图演示一下
在这里插入图片描述
这就是双向链表,我们用js来表示一下

const node1 = {data: 10,prev: null,  // 前向指针(节点1是头节点,无前驱)next: null   // 后向指针
};const node2 = {data: 20,prev: null,next: null
};const node3 = {data: 30,prev: null,next: null
};// 建立双向连接
node1.next = node2;    // 节点1的next指向节点2
node2.prev = node1;    // 节点2的prev指向节点1
node2.next = node3;    // 节点2的next指向节点3
node3.prev = node2;    // 节点3的prev指向节点2

以上就是链表的简单概述,具体请自行查阅资料

二、将链表使用到effect中

1.问题

上一篇文章我们编写了一个最简单的effect响应式,细心的小伙伴就会发现有问题

 <script type="module">// import {//   effect,//   ref,// } from "./node_modules/vue/dist/vue.esm-browser.prod.js";import { effect, ref } from "./reactive/effect1.js";const name = ref("vue");effect(() => {const time = new Date().getTime();console.log(time);console.log(name.value);});effect(() => {const time = new Date().getTime();console.log(time);console.log(name.value);});setTimeout(() => {name.value = "react";}, 1000);

如果我effect监听两次,那么修改之后effect有且只有第二个effect执行了,为什么呢;

class RefImpl {_v_isRef = true;subs;constructor(value) {this.value = value;}get value() {// 收集依赖// 第二次get之后subs变了if (activeSub) {this.subs = activeSub;}return this._value;}set value(newVal) {// 触发更新this._value = newVal;console.log(123);this.subs?.();}
}
function ref(val) {return new RefImpl(val);
}
let activeSub = null;
function effect(fn) {activeSub = fn;activeSub();activeSub = null;
}
export { effect, ref };

因为effect执行第二次的时候activeSub变成了最后一次执行的effect,所以导致只执行了一次,那么如何解决这个问题呢

2.解决

在vue3前面的版本中,是用数组的方式去处理的,后面考虑到性能问题,就转成的链表形式;
有一篇文章详细的说明了性能问题(但是我忘了文章的地址了 QAQ,如果后面我找到了会补充的)

那现在我们开始改造之前的依赖收集和派发更新的代码

依赖收集

class RefImpl {_v_isRef = true;/*** 订阅者链表头节点*/subs;/***  订阅者链表尾节点*/subsTail;constructor(value) {this.value = value;}get value() {if (activeSub) {trackRef(this);}return this._value;}set value(newVal) {// 触发更新this._value = newVal;// this.subs?.();triggerRef(this);}
}

我们在原来的ref的类中添加两个属性,subs和subsTail,代表的是订阅者头节点和尾节点,然后再get方法中执行依赖收集

以下是trackRef的代码

function trackRef(dep) {// 收集依赖if (activeSub) {const newLink = {sub: activeSub,nextSub: null,prevSub: null,};//   this.subs = newLink;// 尾插法if (dep.subsTail) {dep.subsTail.nextSub = newLink;newLink.prevSub = dep.subsTail;dep.subsTail = newLink;} else {// 没有的话头尾一样dep.subs = newLink;dep.subsTail = newLink;}}
}

当开始收集依赖的时候,我们创建一个新的节点newLink,既然是双向链表那就肯定有上一个节点和下一个节点这两个参数,在这里分别对应nextSub和prevSub,当然还需要记录当前的订阅者(即effect的回调函数);
然后我们默认是用尾插法,即在最后面插入节点,此时我们判断当前的节点是否有尾节点,如果有我们需要进行双向链表的插入
即将该尾节点的下一个节点指向当前节点,将原本的尾节点指向该节点,将该节点的上一个节点指向原来的尾节点
听起来有点绕啊,我们画个图演示一下就明白了
在这里插入图片描述这是默认的双向链表,此时我们需要在节点3中插入节点4,节点3是原本这个链表的尾节点,在这里就是dep.subsTail,我们最后要变成下面这种链表
在这里插入图片描述
那么按照图例所示,是不是需要将节点3(原dep.subsTail 尾节点)的next指向节点4,同时将节点4的prev指向节点3,对应的就是 dep.subsTail.nextSub = newLink;newLink.prevSub = dep.subsTail;,最后节点4变成这个链表的尾节点,就是将原来的dep.subsTail 指向节点4 ,对应的是 dep.subsTail = newLink; 节点4在代码中就是newLink;
上面这种是如果有尾节点的情况,如果我没有呢,就是第一次收集;
此时头尾的节点都是指向同一个节点,即 dep.subs = newLink; dep.subsTail = newLink; 头尾相同
以上是依赖收集的更新

派发更新

派发更新就是遍历这个链表,分别执行链表的sub

function triggerRef(dep) {if (dep.subs) {propagate(dep.subs); //触发更新}
}
function propagate(subs) {//  依次执行let link = subs; //保存头节点let queuedEffects = []; //保存需要执行的effectwhile (link) {// 注意:这里不能直接执行effect,因为effect可能会再次触发set,导致死循环queuedEffects.push(link.sub); //将effect保存到数组中link = link.nextSub; //指向下一个节点}// 依次执行queuedEffects.forEach((effect) => {effect(); //执行effect});
}

这里需要注意的是,不能在遍历的过程中执行effect的回调函数,为什么呢,给个案例就知道了

function propagate(subs) {//  依次执行let link = subs; //保存头节点let queuedEffects = []; //保存需要执行的effectwhile (link) {// 注意:这里不能直接执行effect,因为effect可能会再次触发set,导致死循环link.sub();link = link.nextSub; //指向下一个节点}// 依次执行// queuedEffects.forEach((effect) => {//   effect(); //执行effect// });
}

假设我们在遍历的时候执行effect回调

  <script type="module">// import {//   effect,//   ref,// } from "./node_modules/vue/dist/vue.esm-browser.prod.js";import { effect, ref } from "./reactive/effect2.js";const name = ref("vue");effect(() => {const time = new Date().getTime();console.log(time);name.value = "react";console.log(name.value);});// effect(() => {//   const time = new Date().getTime();//   console.log(time);//   console.log(name.value);// });setTimeout(() => {name.value = "react";}, 1000);

此时我们在effect中修改响应式的值,然后你就会看到
在这里插入图片描述
进入无限循环导致栈溢出,所以我们只能等到循环结束的时候才能执行

总结

以上就是链表运用到响应式中,当然,这个还只是很小且比较容易理解的一部分,后面的部分会很难理解;
希望大家结合源码多看几遍


文章转载自:

http://YDPButiT.znhpg.cn
http://wcIvwg8l.znhpg.cn
http://lEx9uzZa.znhpg.cn
http://MP2Enwn1.znhpg.cn
http://YUayd8J7.znhpg.cn
http://jKpjSmCz.znhpg.cn
http://114a0kqN.znhpg.cn
http://xNLPC4Bw.znhpg.cn
http://qBjeuZ49.znhpg.cn
http://ZdSmcv97.znhpg.cn
http://ymPsmCpY.znhpg.cn
http://fJHlte64.znhpg.cn
http://TZi70WlX.znhpg.cn
http://46uDAhUU.znhpg.cn
http://aaRBGOVJ.znhpg.cn
http://GcsU4sUv.znhpg.cn
http://wnzjIeFq.znhpg.cn
http://ZF35Twur.znhpg.cn
http://vCCi0Wdm.znhpg.cn
http://FQGjrDb9.znhpg.cn
http://GifrckKD.znhpg.cn
http://eXxqt4Pr.znhpg.cn
http://S9MphTGe.znhpg.cn
http://pZIJZDBM.znhpg.cn
http://MX9DOxfK.znhpg.cn
http://GOCes5vO.znhpg.cn
http://2MOHK3M1.znhpg.cn
http://lqTqapCR.znhpg.cn
http://s6xukFQM.znhpg.cn
http://v2IfW2MM.znhpg.cn
http://www.dtcms.com/a/382302.html

相关文章:

  • 自动驾驶中的传感器技术46——Radar(7)
  • Windows_MediaFeaturePack_x64_1903_V1.msu
  • Class56 束搜索
  • 【Redis#10】渐进式遍历 | 数据库管理 | redis_cli | RES
  • Java面试问题记录(三)
  • 在Excel和WPS表格中批量删除数据区域的批注
  • 商品库存扣减方案
  • smartctl Current_Pending_Sector 硬盘待处理扇区
  • 并发和高并发
  • 科技信息差(9.13)
  • 文档长期不更新导致知识过时如何解决
  • Python学习-day9 字典Dictionary
  • Ubuntu22.04更换阿里镜像源,ubuntu更换源
  • 仓颉编程语言青少年基础教程:Struct(结构)类型
  • C语言数据结构实战:从零构建一个高性能的顺序栈
  • 数据链路层总结
  • Linux线程:基于环形队列的生产消费模型
  • 【Ambari监控】高版本 DataGrip 无法使用 Phoenix 驱动
  • 1.架构师——大纲
  • 粒子群算法模型深度解析与实战应用
  • JDK 新特性
  • 数据库可视化面板下载
  • 深入解析:preload与prefetch的区别及最佳实践
  • 【层面一】C#语言基础和核心语法-01(类型系统/面向对象/异常处理)
  • Python核心技术开发指南(061)——初始化方法__init__
  • 用 Go 采集服务器资源指标:从原理到实践
  • MySQL-day2_02
  • 基于springboot+vue开发的会议预约管理系统【50906】
  • 【Ubuntu】sudo apt update出现E :仓库***没有Release文件
  • JavaWeb--day3--AjaxElement路由打包部署