深入理解Vue3.js响应式系统基础逻辑

如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~

作者:前端小王hs

阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主

此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来

书籍:《Vue.js设计与实现》 作者:霍春阳

本篇博文将在书第4.1节至4.4节的基础上进一步解析,附加了测试的代码运行示例,以及对书籍中提到的ES6中的数据结构及其特点进行阐述,方便正在学习Vue3想分析Vue3源码的朋友快速阅读

如有帮助,不胜荣幸

如何实现响应式系统

在书籍的第4章开始,作者向我们从0到1的揭露如何设计一款完善的响应系统,其原理是基于ES6提出的Proxy对象。我们知道,当我们使用proxy去代理某一个对象时,在读取修改代理对象的属性过程中,会触发get()set()函数,并执行当中的逻辑,那么最简单的响应系统就是基于此去实现的。

本节对应书籍中的4.1节至4.3节

副作用函数

在书中,作者提到了副作用函数,指的是会产生副作用的函数(说了又好像没说),例如下面这句代码

function effect() {document.body.innerText = 'hello vue3'
}

当执行函数effect()时,我们的页面会出现hello vue3的字样,但是假设在这段代码之前,有其他函数正在读取或者修改document.body.innerText,且document.body.innerText并不为hello vue3,那么这段代码无疑会对其他的函数造成影响,如下图所示:

副作用函数造成的影响

这就是副作用函数

响应式数据

先来看一段代码

const obj = { text: 'hello world' }
function effect() {// effect 函数的执行会读取 obj.textdocument.body.innerText = obj.text
}

我们知道,如果执行effect()会读取obj.text,并将页面内容设为hello world

那假设修改了obj.text的值,并且会重新执行effect(),那么页面的内容不就自动更新了吗?

这就是响应式数据的构想

响应式数据的实现——proxy

通过前面的介绍,可以得知在读取的时候会调用proxy.get(),那么就可以在读取阶段通过全局变量将effect()存起来,然后当修改即调用proxy.set()方法时,再把存起来的effect()拿出来执行,那么这就实现了最基本的响应式数据

如果读者不了解proxy,可以看下面这个例子

proxy例子

在图中先是定义了一个名为data的对象,该对象包含一个属性text,属性值为1;其次是通过new proxy定义了一个代理对象obj,然后执行了读取obj.text和自增obj.text的操作

很多初学者容易混淆的是,分不清谁是谁代理,谁又是代理对象

请看图,笔者没有通过data去访问text,而是通过obj去访问,那么是不是obj代理了data?所以obj被称为代理对象,而data代理的对象

换句话说,只有通过obj去访问data的属性,才会触发get()set()

这就是proxy的基础应用

响应式数据的实现——过程

①定义一个存储副作用函数的桶bucket(书中表述存储副作用函数的称为

const bucket = new Set()

这里为什么使用Set数据结构?

两个主要原因,一是该对象相关的副作用函数可能有多个;二是Set具备去重的特性

②读取时把effect()存入bucket,修改时取出执行

整体代码如下:

const obj = { text: 'hello world' }
function effect() {// effect 函数的执行会读取 obj.textdocument.body.innerText = obj.text
}// 存储副作用函数的桶  
const bucket = new Set()  // 原始数据  
const data = { text: 'hello world' }  
// 对原始数据的代理  
const obj = new Proxy(data, {  // 拦截读取操作  get(target, key) {  // 将副作用函数 effect 添加到存储副作用函数的桶中  bucket.add(effect)  // 返回属性值  return target[key]  },  // 拦截设置操作  set(target, key, newVal) {  // 设置属性值  target[key] = newVal  // 把副作用函数从桶里取出并执行  bucket.forEach(fn => fn())  // 返回 true 代表设置操作成功  return true  }  
})  // 调用 effect 函数将触发首次执行和添加到bucket中  
effect()  // 当 obj 的属性被修改时,bucket 中的 effect 函数将被执行  
obj.text = 'hello vue3'

但问题来了,就是副作用函数的名字是固定的,书中称为硬编码,或者说假设我们这个对象相关联的副作用函数的名字是其他的如myEffect或者是一个匿名函数,那我们就得手动修改这段代码,在bucket.add(effect)手动修改。这无疑十分麻烦

解决的办法就是定义一个变量,去保存当前执行的副作用函数,那么我们传入bucket的就是这个变量,而不是别的名字或者匿名函数

这其实是一种代理的思想,在代码开发中非常常用。例如存在函数a函数b函数b想拿到函数a的值,但因为函数作用域的原因,所以不能直接从函数b中拿到函数a里的值,那就可以定义一个全局变量,把函数a的值赋值给全局变量,再从函数b中获取全局变量,代码如下:

// 声明一个全局变量
let globalValue;// 函数a,将某个值设置为全局变量
function a(value) {globalValue = value; // 将传入的value设置为全局变量
}// 函数b,从全局变量中获取值
function b() {console.log(globalValue); // 输出全局变量的值
}// 使用函数a设置全局变量的值
a('Hello vue3')// 使用函数b输出全局变量的值
b(); // 输出: Hello vue3

那么响应式数据的代码可修改如下:

// 用一个全局变量存储被注册的副作用函数  
let activeEffect;  // effect 函数用于注册副作用函数  
function effect(fn) {  // 当调用 effect 注册副作用函数时,将副作用函数 fn 赋值给 activeEffect  activeEffect = fn;  // 执行副作用函数  fn();  
}

Proxy.get()里就可以改为下列代码

get(target, key) {  // 将 activeEffect 中存储的副作用函数收集到“桶”中  if (activeEffect) {  bucket.add(activeEffect)  }  return target[key]  
},

问题出现

上述的逻辑看似十分完美,但却存在着隐患

先来看下面这段例子

问题案例1
在这段代码中,执行了obj.noExist,noExist即不存在的意思,这个属性并不存在于data中,但是却依旧导致了get()的读取

现在再来看看书中的例子,代码如下:

effect(  // 匿名副作用函数  () => {  console.log('effect run') // 会打印 2 次document.body.innerText = obj.text}
)setTimeout(() => {// 副作用函数中并没有读取 notExist 属性的值obj.notExist = 'hello vue3'
}, 1000)

从前面的例子我们知道,执行effect()的时候是由obj.text触发的,那么理所应当,只有当修改obj.text应该再次触发该fn

回想一下我们想要的效果,将obj.data的数据显示在页面上,当修改obj.data时,页面的内容也随之更新

但显而易见,setTimeout()的执行却也触发了副作用函数,原理和图中的一样,当使用Proxy对象来代理一个对象时,get()陷阱(trap)会拦截目标对象上任何属性的读取操作,所以在处理过程中也把副作用函数加进了

所以就需要设计一个锁链,将副作用函数(一个或多个)与obj.text关联起来

其实更为确切的说,是将副作用函数(一个或多个)与objtext关联起来,这个text才是主角

那么就需要重新设计bucket了,在这个桶里面除了副作用函数外,还有它关联的属性,那我们要获取到这个关联的属性,就需要知道这个对象

在书中有这么一段原文:
如果用 target 来表示一个代理对象所代理的原始对象,用 key 来表示被操作的字段名,用 effectFn 来表示被注册的副作用函数,那么可以为这三个角色建立如下关系:

target└── key└── effectFn

这是一种特殊的数据结构,也就是bucket新的设计思路

在书中还举例了在不同key,不同effectFn情况下的结构展示,这里不做过多叙述,直接来看解决方案,代码如下:

// 存储副作用函数的桶
const bucket = new WeakMap();const obj = new Proxy(data, {// 拦截读取操作get(target, key) {// 没有 activeEffect,直接 returnif (!activeEffect) return target[key];// 根据 target 从“桶”中取得 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 并与 key 关联if (!deps) {depsMap.set(key, (deps = new Set()));}// 最后将当前激活的副作用函数添加到“桶”里deps.add(activeEffect);// 返回属性值return target[key];},// 拦截设置操作set(target, key, newVal) {// 设置属性值target[key] = newVal;// 根据 target 从桶中取得 depsMap,它是 key --> effectsconst depsMap = bucket.get(target);if (!depsMap) return;// 根据 key 取得所有副作用函数 effectsconst effects = depsMap.get(key);// 执行副作用函数effects && effects.forEach(fn => fn());}
});

在构建数据结构上,使用了WeakMapMapSet三种数据结构

这里简单介绍一下三种数据结构:
我们先从Map介绍起,其类似于对象,但我们知道对象必须是一个字符串,而Map则可以是任意类型的值

其次是WeakMap,其特点是必须是一个对象,并且相对于Map弱引用(Weak意为虚弱的),什么意思呢?假设这个,或者说这个对象,进行了置为null的操作,那么在WeakMap将会消失,同样的,值也会消失。可以看下面这个图例

image.png
可以看到当我们执行了key=null后,就获取不到

最后是Set,这是一个类似数组的结构,但里面的值是唯一,如下图所示

image.png

OK,现在我们再回来看使用这些数据结构的用途

首先,是一个WeakMap类型,存储的结构是:key --> effects(书中注释所示)。但换个角度其实是:target --> depsMap,这个target,就是obj,而depsMap结构是:key --> deps,这个deps是一个Set结构,里面存放了关于这个objkey所对应的effect集合

我们可以从这几段代码更为清晰的看到不同结构之间的联系

const bucket = new WeakMap();
let depsMap = bucket.get(target);
bucket.set(target, (depsMap = new Map()));
let deps = depsMap.get(key);
depsMap.set(key, (deps = new Set()));

所以现在每一个effect都和objkey对应起来了

// WeakMap
bucket = {obj : depsMap
}
// Map
depsMap = {key : deps
}
// Set
deps = [effect1,effect2]

需要注意的是,deps里包含了许多effect,也被称为当前key依赖集合

那么如此设计的话,之前的问题就解决了,还记得问题吗?即使是执行不存在的obj.noExist,当执行时也会再次触发副作用函数的问题,原因是副作用函数没有与obj.text关联起来

那么现在,我们再次测试obj.noExist,可以发现直接返回了undefined,也就是到了Proxy.get()函数的if (!activeEffect) return target[key]就结束了,因此并没有副作用函数与之关联

测试如下图所示:

image.png

VScode主题:Eva theme → Eva Dark

现在再来回答一下为什么要使用WeakMap数据结构,就是假设某个data到后面被回收了,那么存在于里的target --> depsMap将会断开,避免了即使代理的对象回收了,引用还是存在,进而不断增多而导致内存泄漏的问题

那么在4.3节的最后,作者还描述了将Proxy.get()内生成关联的逻辑封装在track函数中,将Proxy.set()内从中获取副作用函数的逻辑封装在trigger函数中的实现,代码如下:

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) return;let 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) return;const effects = depsMap.get(key);effects && effects.forEach(fn => fn());
}

那么在下一节,我们继续来分析书中关于分支导致的问题,以及如何解决

分支切换和cleanup

本节内容对应书中的4.4节,主要利用了Set数据结构引用数据类型的特点

复习一下,Set是一个类似数组的结构,但内容值是唯一的,此外,Set是一个引用数据类型,即保存的是在堆内存中的地址值

场景

我们先来看一下书中提到的场景

const data = { ok: true, text: 'hello world' }
const obj = new Proxy(data, { /* ... */ })effect(function effectFn() {document.body.innerText = obj.ok ? obj.text : 'not' 
})

当把obj.ok修改为false时,按理想情况此时无论如何修改obj.text的值,都不会触发副作用函数,但由于obj.text关联的依赖集合set中,还包含了这个副作用函数,所以还是会触发,当然我们根据代码可知,无论如何变化,页面中显示的值都为not

所以本节讨论的问题就是如何去实现在这种情况下,修改obj.text的值不会触发副作用函数的问题

触发的原因

触发的原因非常简单,当初次执行effect时,不管是ok还是text都把effect收集进了自己的依赖集合,也就是执行时触发了两次get(),如下图所示:

image.png

所以,不管obj.okture还是false,都影响不了修改obj.text就会触发effect的逻辑

解决思路

在执行修改obj.ok时,把依赖集合obj.text断掉,这样当修改完obj.okfalse后,无论怎么修改obj.text,都不会触发副作用函数,因为obj.text的依赖集合已经没有effect

现在我们来看看Vue.js团队是怎么实现的,代码如下:

// 用一个全局变量存储被注册的副作用函数  
let activeEffect;  function effect(fn) {  const effectFn = () => {  // 当 effectFn 执行时,将其设置为当前激活的副作用函数  activeEffect = effectFn;  fn();  };  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合  effectFn.deps = [];  // 执行副作用函数  effectFn();  
}

副作用函数effect中新定义了一个函数effectFn,在这个函数里做了两件事情,一是把自身赋值给activeEffect,第二是执行传进来的副作用函数

其实现在执行effect,就是执行effectFn,所以都可以说是副作用函数

注意,在JavaScript中函数是引用数据类型,是特殊的对象,所以activeEffect和effectFn都指向同一个地址

然后是给effectFn定义了一个数组,最后执行effectFn

接着把视角转移到track中,代码如下:

function track(target, key) {// 没有 activeEffect,直接 returnif (!activeEffect) return;let 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 中deps.add(activeEffect);// deps 就是一个与当前副作用函数存在联系的依赖集合// 将其添加到 activeEffect.deps 数组中 activeEffect.deps.push(deps); // 新增
}

track中,给effectFn/activeEffect的数组里添加了当前副作用函数所在的deps,这是一个Set数据结构,也就是依赖集合

这样做的效果是什么?从effectFn/activeEffect的视角来看,就是把有存在的集合放到了数组

// deps依赖集合 Set数据结构
deps = [effect1,effect2]// 可以理解这是一个二维数组,数组里面的每一项都是一个Set数据结构
effectFn.deps = [
[effectFn1,effectFn2,...],
[effectFn1,effectFn2,...],
...
]

我们要做的是什么?在修改obj.text的时候不触发副作用函数,也就是断掉obj.text与其依赖集合的关系

那这一断掉阶段是在什么时候执行呢?在修改obj.ok的时候执行,也就是修改obj.ok触发Proxy.set()时。我们知道在trigger函数中,会从里根据obj.ok找到对应的依赖集合effectFn/effect去执行,代码如下:

// 在 set 拦截函数内调用 trigger 函数触发变化
function trigger(target, key) {const depsMap = bucket.get(target);if (!depsMap) return;// 这里的 effects 就是 依赖集合const effects = depsMap.get(key);effects && effects.forEach(fn => fn());
}

所以我们需要在执行effect的过程中去实现这个断掉与之对应依赖集合操作

怎么做呢?利用Set数据结构引用数据类型的特性

// 依赖集合deps
effectFn.deps = [deps1,deps2,...]deps1 = [effectFn1,effectFn2,...]
// 从结构上看
effectFn.deps = [set1,set2,...]

在执行effect的过程中遍历effectFn.deps,找到其中的每一项deps[effectFn1,effectFn2,...],使用Set.prototype.delete(value)去删掉当前的effectFn

别忘了!我们保存在effectFn.deps中的每一项deps,指向的地址和保存在里的依赖集合相同

所以逻辑就清晰了

读取时

  1. 读取obj.ok时,activeEffectdeps中保存了当前deps,该deps保存了effectFn
  2. 读取obj.text时,activeEffectdeps中保存了当前deps,该deps保存了effectFn
obj└── ok└── effectFn└── text└── effectFn

那么现在activeEffectdeps就有两个effectFn,但这两个effectFn是同一个函数

修改时

  1. 修改obj.ok
  2. 触发trigger
  3. trigger中从bucket通过obj找到depsMap,再给depsMap传入ok得到deps,拿出里面保存的effectFn执行
  4. 执行effectFn过程中遍历effectFn.deps,从每一项deps或者说依赖集合中删去当前执行的effectFn
  5. 因为effectFn.deps中的每一项deps里保存的deps是指向同一个地址
  6. 所以objokdeps中就没有effectFn
  7. 所以obj.textdeps保存的effectFn也被清除了!
  8. 修改obj.text,由于没有这个effectFn了,所以不会触发执行effectFn

在代码实现过程中,第4步对应下列代码的cleanup(effectFn)

现在我们再来看下对应的实现代码:

// 用一个全局变量存储被注册的副作用函数
let activeEffect;function effect(fn) {const effectFn = () => {// 调用 cleanup 函数完成清除工作cleanup(effectFn); // 新增activeEffect = effectFn;fn();};effectFn.deps = [];effectFn();
}function cleanup(effectFn) {// 遍历 effectFn.deps 数组for (let i = 0; i < effectFn.deps.length; i++) {// deps 是依赖集合const deps = effectFn.deps[i];// 将 effectFn 从依赖集合中移除deps.delete(effectFn);}// 最后需要重置 effectFn.deps 数组effectFn.deps.length = 0;
}

在阅读的时候无不感到Vue.js团队在设计时的高超逻辑思维

但此时还有个小瑕疵,就是在执行effects && effects.forEach(fn => fn());时,取出了effectFn,然后我们知道在effectFn中执行了cleanup操作,但又执行了fn副作用函数

比方说,我们修改了obj.ok,是不是会重新执行副作用函数进行更新?这是响应式系统的初衷,但执行副作用函数又触发了读取操作是不是?那就又执行了deps.add(activeEffect);activeEffect.deps.push(deps);

相当于我们在deps这个Set结构里刚刚删除掉effectFn,下一步又把这个effectFn放进去了,这就导致了无限循环

image.png

然后这个解决方案我认为是第4.4节最为精彩的部分,代码如下:

const set = new Set([1]);  const newSet = new Set(set);  
newSet.forEach(item => {  set.delete(1);  set.add(1);  console.log('遍历中');  
});

Set外面再套一层Set,就避免了无限循环,这是不是很Amazing

用数组的角度看,就是一开始是[1],现在变成了[[1]]

避免循环真实的原因就是newSet只有一个值,所以forEach的回调函数只会执行一次;而假设没被嵌套,在原来的Set中由于删了又增,增了又删,相当于无限个值,所以forEach的回调函数会无限循环

所以在trigger中的代码被修改为:

function trigger(target, key) {const depsMap = bucket.get(target);if (!depsMap) return;const effects = depsMap.get(key);// 套一层Setconst effectsToRun = new Set(effects);effectsToRun.forEach(effectFn => effectFn());
}

至此,《Vue.js设计与实现》4.1节至4.4节就分析完了

谢谢大家的阅读,如有错误的地方请私信笔者

笔者会在近期整理后续章节的笔记发布至博客中,希望大家能多多关注前端小王hs

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

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

相关文章

C++:SLT容器-->stack

C:SLT容器--&#xff1e;stack 1. stack容器2. stack 常用接口 1. stack容器 先进后出&#xff0c;后进先出不允许有遍历行为可以判断容器是否为空可以返回元素的个数 2. stack 常用接口 构造函数 stack<T> stk; // stack采用模板类实现&#xff0c;stack对象的默认构造形…

第1期JAVA社招面试经验月报

面经哥专注互联网社招面试经验分享&#xff0c;关注我&#xff0c;每日推送精选面经&#xff0c;面试前&#xff0c;先找面经哥&#xff5c;面经哥整理了上月30篇面试经历&#xff0c;选取了较为热点高频的面试题供大家参考 基础知识类‍‍‍‍‍ 1、说下双亲委派原则以及类加…

1992-2012年美国西海岸的海面高度异常数据集

Gridded Altimeter Fields with Enhanced Coastal Coverage 具有增强海岸覆盖范围的网格化测高场 简介 具有增强的海岸覆盖范围的网格化高度计场数据产品包含美国西海岸的海面高度异常&#xff08;SSHA 或 SLA&#xff09;以及北纬 35.25 度-48.5 度和东经 227.75 度-248.5 …

Python酷库之旅-开启库房之门

目录 一、库的定义 二、库的组成 三、库的分类 四、如何学好Python库&#xff1f; 五、注意事项 六、推荐阅读 1、Python筑基之旅 2、Python函数之旅 3、Python算法之旅 4、Python魔法之旅 5、 博客个人主页 一、库的定义 在Python中&#xff0c;库(Library)是一个封…

探索智慧机场运营中心解决方案的价值与应用

随着全球航空业的不断发展&#xff0c;机场运营中心的作用日益凸显。智慧机场运营中心解决方案以其高效的管理和智能化的运营模式&#xff0c;成为优化机场运营、提升服务水平的重要工具。本文将深入探讨智慧机场运营中心解决方案的价值与应用&#xff0c;揭示其在机场管理中的…

机器学习常见知识点 2:决策树

文章目录 决策树算法1、决策树树状图2、选择最优决策条件3、决策树算法过程→白话决策树原理决策树构建的基本步骤常见的决策树算法决策树的优缺点 【五分钟机器学习】可视化的决策过程&#xff1a;决策树 Decision Tree 关键词记忆&#xff1a; 纯度、选择最优特征分裂、熵、基…

电脑上的瑞士军刀

一、简介 1、一款专为 Windows 操作系统设计的桌面管理工具&#xff0c;它具备保存和恢复桌面图标位置的功能&#xff0c;使用户能够在各种情况下&#xff0c;如分辨率变动、系统更新或其他原因导致的图标位置混乱后&#xff0c;快速恢复到熟悉的工作环境。它还拥有诸多实用功能…

【Pyqt6 学习笔记】实现串口调试助手,并将接收到数据模拟键盘输出

文章目录 代码示例main.pyscreen_shot_module.pyqrcmd.pyuntitled.pyuntitled.ui 本文内容是 【Pyqt6 学习笔记】DIY一个二维码解析生成小工具的延申&#xff0c;在原来的基础上实现了串口调试助手功能&#xff0c;并利用 pywinauto的 keyboard模块将接收到数据模拟键盘输出…

【递归+二叉树思想+搜索】 Alice and the Cake题解

Alice and the Cake题解 AC记录&#xff1a;记录-洛谷 题面翻译&#xff08;大概就是题目大意&#xff09; 执行恰好 n − 1 n-1 n−1 次操作&#xff0c;每次操作可以选择当前所有蛋糕中满足其重量 w ⩾ 2 w\geqslant 2 w⩾2 的一块&#xff0c;然后将其分为质量分别为 …

手机连接ESP8266的WIFI,进入内置网页,输入要显示的内容,在OLED显示屏上显示文本

连线 OLEDESP8266含义GNDGND地线VCC3V电源SCLD1时钟线SDAD2通信数据线 只支持英文信息的显示和数字。 #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h>#d…

5.大模型高效微调(PEFT)未来发展趋势

PEFT 主流技术分类 UniPELT 探索PEFT 大模型的统一框架&#xff08;2022&#xff09; UIUC 和Meta AI 研究人员发表的UniPELT 提出将不同的PEFT 方法模块化。 通过门控机制学习激活最适合当前数据或任务的方法&#xff0c;尤其是最常见的3大类PEFT 技术&#xff1a; Adapters…

事业单位——被逆袭篇

目录 一、结果 二、考试 三、时间 四、复习 五、总结 一、结果 图1&#xff1a;2024年浙江广播电视集团下属浙江省中波发射管理中心公开招聘笔面试结果 准考证号笔试面试总成绩排名备注107016070.866.48310702416555.44107134390.871.681入围107146869.869.08210715406454.…

征信受损,别再犯傻!

听说你的征信出了点小问题&#xff1f;别急&#xff0c;这事儿说大不大&#xff0c;但也不能掉以轻心。征信&#xff0c;说白了就是你借钱还钱的记录本&#xff0c;一旦它“花”了&#xff0c;借钱可就没那么轻松了。 先来说说这征信“花”了是咋回事 征信“花”了&#xff0c…

18.1 HTTP服务器-极简服务器、请求与响应

1. 极简服务器 大道至简。使用Go语言构建世界上最简单的HTTP服务器&#xff0c;仅需四行代码。 标准库的net/http包提供了多种用于创建HTTP服务器的方法&#xff0c;其中包括&#xff1a; http.HandleFunc("/", rootHandler) 第一参数&#xff1a;访问的url 第二…

【Linux】进程间通信之命名管道

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

109.网络游戏逆向分析与漏洞攻防-装备系统数据分析-商店与捨取窗口数据的处理

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 现在的代码都是依据数据包来写的&#xff0c;如果看不懂代码&#xff0c;就说明没看懂数据包…

【优选算法】详解target类求和问题(附总结)

目录 1.两数求和 题目&#xff1a; 算法思路&#xff1a; 代码&#xff1a; 2.&#xff01;&#xff01;&#xff01;三数之和 题目 算法思路&#xff1a; 代码&#xff1a; 3.四数字和 题目&#xff1a; 算法思路&#xff1a; 代码&#xff1a; 总结&易错点&…

用人工智能写2024年高考作文

目录 用人工智能写2024年高考作文 引用 一、2024年 新课标I卷 作文真题 AI写作范文 二、2024年 全国甲卷 作文真题 AI写作范文 三、2024年 新课标II卷 作文真题 AI写作范文 四、2024年 北京卷 作文真题一 AI写作范文 作文真题二 AI写作范文 作文真题三 AI写作…

php 混合xml js,html 代码报错 ,结束标签关闭, short_open_tag 的作用,php关闭文件结束判断

结束标签关闭, short_open_tag 的作用&#xff0c;php关闭文件结束判断 有时候我们我们会将php&#xff0c;xml&#xff0c;js&#xff0c;html 混合编写 php文件只要开始标签而不要结尾标签? 混合代码看代码 直接运行 yntax error, unexpected version (T_STRING) in php…

验证码识别接口、多种样式验证码识别接口、中英文验证码识别接口

验证码识别接口、多种样式验证码识别接口、中英文验证码识别接口 本文提供一个基于OCR和机器学习的验证码识别接口&#xff0c;能够识别较复杂的中文、英文验证码&#xff0c;在OCR的基础上针对验证码进行算法优化。本接口是收费的&#xff08;最低0.5分1次调用&#xff0c;试…