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

news2025/1/15 20:08:35

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

作者:前端小王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.text
    document.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.text
    document.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,直接 return
        if (!activeEffect) return target[key];
        // 根据 target 从“桶”中取得 depsMap,它也是一个 Map 类型:key --> effects
        let depsMap = bucket.get(target);
        // 如果不存在 depsMap,那么新建一个 Map 并与 target 关联
        if (!depsMap) {
            bucket.set(target, (depsMap = new Map()));
        }
        // 再根据 key 从 depsMap 中取得 deps,它是一个 Set 类型,
        // 里面存储着所有与当前 key 相关联的副作用函数:effects
        let 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 --> effects
        const depsMap = bucket.get(target);
        if (!depsMap) return;

        // 根据 key 取得所有副作用函数 effects
        const 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,直接 return
  if (!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,直接 return
  if (!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);
    // 套一层Set
    const effectsToRun = new Set(effects);
    effectsToRun.forEach(effectFn => effectFn());
}

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

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

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

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

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

相关文章

cad导入su线条不在一个平面怎么办?

解决CAD导入sketchup线条不是共面问题&#xff0c;需要考虑到各个步骤如下&#xff1a; 1&#xff09;检查CAD文件。首先要检查CAD文件&#xff0c;确保线条是连接在一起的&#xff0c;并且看看有没有多余的线&#xff0c;以及是否有子线段没有合并&#xff0c;如果有会导致导入…

AdroitFisherman模块测试日志(2024/6/10)

测试内容 测试AdroitFisherman分发包中SHAUtil模块。 测试用具 Django5.0.3框架&#xff0c;AdroitFisherman0.0.31 项目结构 路由设置 总路由 from django.contrib import admin from django.urls import path,include from Base64Util import urls urlpatterns [path(ad…

猫狗识别(超详细版)(py代码)

猫狗识别&#xff08;一&#xff09; 一、图像识别 1.导入必要的库: import torchimport numpy as npimport torchvisionfrom os import pathfrom torchvision import datasets, modelsimport torch.nn as nnimport torch.optim as optimfrom torch.utils.data import DataL…

【NUCLEO-G071RB】009——HAL库-显示编译时间

NUCLEO-G071RB&#xff1a;009——HAL库-显示编译时间 编译时间设计目标程序修改运行测试 编译时间 这里的编译时间指的是烧录文件的编译时间&#xff0c;它由编译环境的日期和时间共同决定。 设计目标 1、获取编译时间&#xff0c;默认是ASC码格式 2、将编译时间转换为HEX …

哈尔滨等保如何做?

哈尔滨等保测评是确保信息系统安全稳定运行的重要一环&#xff0c;它涉及到对业务、资产、安全技术和安全管理的全面调研和评估。本文将详细阐述哈尔滨等保测评的实施步骤和注意事项&#xff0c;帮助读者更好地理解和执行等保测评工作。 首先&#xff0c;我们需要明确等保测评的…

新品发布 | 捷云等保一体机2.0全新上市,助力中小企业破解等保难题

等保2.0时代&#xff0c;随着网络威胁不断复杂化和组织化&#xff0c;作为网络安全“弱势群体”的中小企业&#xff0c;等保建设工作正面临着安全意识、管理、人才、资金捉襟见肘等问题&#xff0c;主要体现在以下两个方面&#xff1a; 等保建设流程复杂 中小企事业单位缺乏专…

条件概率的理解

P(A)表示A的先验概率 P(B)表示B的先验概率 P(A | B)表示在B发生的情况下&#xff0c;A的条件概率 P(B | A)表示在A发生的情况下&#xff0c;B的条件概率 先验概率是在进行实验之前基于当前知识对结果概率的最佳合理评估。后验概率是在考虑了新信息后&#xff0c;事件发生的修正…

行为树BehaviorTree

主要依托于BehaviorTree.CPP进行介绍。 1 基本概念 1.1 是什么与用来做什么 官网 https://www.behaviortree.dev/docs/learn-the-basics/BT_basics Unlike a Finite State Machine, a behavior Tree is a tree of hierarchical nodes that controls the flow of execution o…

C++:day5

思维导图 例题 #include <iostream> using namespace std; class RMB { private:int yuan;int jiao;int fen;static int count;public:RMB(){count;}RMB(int yuan, int jiao, int fen) : yuan(yuan), jiao(jiao), fen(fen){count;}const RMB operator(const RMB &R)…

基于springboot实现交通管理在线服务系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现交通管理在线服务系统演示 摘要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装交通管理在线服…

上海亚商投顾:微盘股指数涨近5% 超跌低价股集体反弹

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 大小指数6月7日走势分化&#xff0c;沪指全天探底回升&#xff0c;深成指跌近1%&#xff0c;创业板指跌超2%&a…

wordpress轻量免费主题

WordPress建站公司 适合提供WordPress建站服务的公司或个体(个人)工作室使用的WordPress建站公司主题模板。 https://www.jianzhanpress.com/?p545 首屏大图红色简洁wordpress主题 首屏大图红色简洁wordpress主题&#xff0c;非常地高端大气上档次&#xff0c;可用于多个行…

《软件定义安全》之六:SDN和NFV安全实践

第6章 SDN和NFV安全实践 1.基于流的安全防护 1.1 DDoS检测清洗 DDoS检测清洗应用ADS APP的设计思路&#xff1a;借助安全控制平台中流相关的组件&#xff0c;从SDN控制器中获得相应的流量&#xff0c;并根据抗DDoS应用订阅的恶意流特征进行检测&#xff0c;发现恶意流量后&a…

Day 43 keepalived高可用集群

keepalived高可用集群 负载均衡 lb集群 load balance ​ 流量分发 高可用 ha集群 high availability ​ 主要是给服务器做冗余 keepalive 持久连接 保持存活 keepalived 高可用软件名称 红帽有自己的高可用集群套件&#xff1a;RHCS keepalived介绍 ​ keepalived是集…

8.1 基本打印功能

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系本人将于及时删除 在使用“MFC应用”项目模板生成应用程序的过程中&#xff0c;如果在“高级功能”窗口中不取消对打印和打印预览的设置&#xff0c;那么应用程序就已经具备了简单的打印和打…

开源VisualFbeditor中文版,vb7 IDE,VB6升级64位跨平台开发安卓APP,Linux程序

吴涛老矣&#xff0c;社区苦无64位易语言&#xff0c;用注入DLL增强菜单&#xff0c;做成VS一样的界面 终归是治标不治本&#xff0c;一来会报毒&#xff0c;二来闭源20年没更新了 开源的VB7&#xff0c;欢迎易语言的铁粉进群&#xff1a;1032313876 【Freebasic编程语言】编绎…

细说中国自动化集成商100家

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 在当今快速发展的工业自动化领域&#xff0c;中国企业正以其卓越的技术实力和创新能力&#xff0c;在全球市场上占据着越来越重要的位置。为了更…

【MySQL】(基础篇七) —— 通配符和正则表达式

通配符和正则表达式 本章介绍什么是通配符、如何使用通配符以及怎样使用LIKE操作符进行通配搜索&#xff0c;以便对数据进行复杂过滤&#xff1b;如何使用正则表达式来更好地控制数据过滤。 目录 通配符和正则表达式LIKE操作符百分号(%)通配符下划线(_)通配符 通配符使用技巧正…

【APP逆向】央视频播放量增加,逆向全过程解密

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

2024北京通信展:聚焦新一代信息通信技术、产品、应用及解决方案

中国国际信息通信展览会&#xff08;PTEXPO&#xff09;作为工业和信息化部主办的ICT行业盛会&#xff0c;自1990年创办以来&#xff0c;已走过了三十多年的辉煌历程。三十余载光阴荏苒&#xff0c;PT展以其独特的魅力&#xff0c;不仅成为反映信息通信行业发展新成果的重要窗口…