深入理解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.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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

收音机的原理笔记

1. 收音机原理 有线广播&#xff1a;我们听到的声音是通过空气振动进行传播&#xff0c;因此可以通过麦克风&#xff08;话筒&#xff09;将这种机械振动转换为电信号&#xff0c;传到远处&#xff0c;再重新通过扬声器&#xff08;喇叭&#xff09;转换为机械振动&#xff0c…

c++(运算符重载 静态成员)

思维导图&#xff1a; 复习&#xff1a; class Person {friend const Person operator(const Person &L,const Person &R);friend bool operator>(const Person &L,const Person &R);friend Person &operator(Person &L,const Person &R);frie…

【Vue】封装api接口 - 图片验证码接口

**1.目标&#xff1a;**将请求封装成方法&#xff0c;统一存放到 api 模块&#xff0c;与页面分离 2.原因&#xff1a;以前的模式 页面中充斥着请求代码 可阅读性不高 相同的请求没有复用请求没有统一管理 3.期望&#xff1a; 请求与页面逻辑分离相同的请求可以直接复用请求…

2024检索增强生成RAG最新综述

论文地址&#xff1a;https://arxiv.org/abs/2402.19473 项目存储库&#xff1a;https://github.com/hymie122/RAG-Survey 摘要—人工智能生成内容&#xff08;AIGC&#xff09;的发展得益于模型算法的进步、基础模型规模的增加以及大量高质量数据集的可用性。虽然AIGC取得了…

FlashSequence: SORA视频生成长序列任务训练解决方案

作者&#xff1a;黄奕桐、沈雯婷、艾宝乐、王昂、九丰 摘要 我们提出了长序列训练方案 FlashSequence 并集成在 PAI-TorchAcc &#xff08;阿里云机器学习平台开发的Pytorch上的大模型训练加速框架&#xff09;中&#xff0c;该方案能够支持SORA类超长序列模型的高效训练。在…

OpenAI官方Prompt工程指南详解!再也不怕写不好Prompt了!

使用AI聊天、AI写作、还是AI绘图等过程中Prompt具有重要意义。 那么Prompt要怎么写效果才好&#xff1f;有没有标准化的模板可以直接用&#xff1f; 有&#xff0c;OpenAI官方发布了一份提示词工程指南&#xff0c;该指南分享了6大策略即可让AI输出更好的结果。至此&#xff…

行为树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…

​Delphi通过Map文件查找内存地址出错代码所在行​

一 什么是MAP文件 什么是 MAP 文件&#xff1f;简单地讲&#xff0c; MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法&#xff0c;它可以在任何地方、任何时候使用&#xff0c;不需要有额外的程序进行支持。而且&#xff0c;这是唯一能找出程序崩溃的地方…

容器化实践:DevOps环境下的容器交付流程

DevOps的兴起是为了应对市场和消费者对技术应用的不断增长的需求。它的目标是构建一个更快的开发环境&#xff0c;同时保持软件的高质量标准。DevOps还致力于在敏捷开发周期中提升软件的整体品质。这一目标的实现依赖于多种技术、平台和工具的综合运用。 结合容器化技术与DevO…

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

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

C++入门 string(1)

目录 string类简介 string类的常用接口说明 string类对象的常见构造 string类对象的访问及遍历操作 operator[ ] begin end rbegin rend string类简介 string是表示字符串的字符串类该类的接口与常规容器的接口基本相同&#xff0c;再添加了一些专门用来操作string的…

2024年工业设计与制造工程国际会议(ICIDME 2024)

2024年工业设计与制造工程国际会议 2024 International Conference on Industrial Design and Manufacturing Engineering 会议简介 2024年工业设计与制造工程国际会议是一个集结全球工业设计与制造工程领域精英的盛会。本次会议旨在为业界专家、学者、工程技术人员提供一个分享…

偏微分方程算法之抛物型方程差分格式编程示例三(C-N格式)

目录 一、研究问题 二、C++代码 三、结果分析 一、研究问题 已知其精确解为。分别取以下三种步长: ①

人大京仓数据库关闭大小写敏感

人大京仓数据库关闭大小写敏感 1、先删除data&#xff08;Kingbase\ES\V8\&#xff09;文件夹下的所有文件夹 2、接着找到initdb.exe所在位置&#xff0c;我的位置是在这里D:\Kingbase\ES\V8\Server\bin&#xff0c;然后输入cmd,运行一下 initdb -E UTF-8 -D C:\Kingbase\ES…

中国新兴的数字证书品牌——JoySSL

JoySSL是一个基于全球可信顶级根创新推出的新一代https数字证书&#xff0c;也是中国为数不多的自主品牌SSL证书。以下是关于JoySSL的详细介绍&#xff1a; 1 品牌背景&#xff1a; JoySSL是网盾安全旗下的产品&#xff0c;专注于网络安全技术服务、安全防护系统集成、数据安…

Craig Federighi 和 John Giannandrea 在 WWDC 上谈论苹果智能技术

WWDC 主题演讲结束后&#xff0c;苹果公司的克雷格-费德里吉&#xff08;Craig Federighi&#xff09;和约翰-吉安南德雷亚&#xff08;John Giannandrea&#xff09;坐下来&#xff0c;更深入地讨论了苹果智能公司在人工智能方面所做的努力&#xff0c;包括该公司是如何训练模…

【Vue】获取模块内的actions方法

目标&#xff1a; 掌握模块中 action 的调用语法 (同理 - 直接类比 mutation 即可) 注意&#xff1a; 默认模块中的 mutation 和 actions 会被挂载到全局&#xff0c;需要开启命名空间&#xff0c;才会挂载到子模块。 调用语法&#xff1a; 直接通过 store 调用 $store.di…

机器学习笔记:focal loss

1 介绍 Focal Loss 是一种在类别不平衡的情况下改善模型性能的损失函数最初在 2017 年的论文《Focal Loss for Dense Object Detection》中提出这种损失函数主要用于解决在有挑战性的对象检测任务中&#xff0c;易分类的负样本占据主导地位的问题&#xff0c;从而导致模型难以…

C#项目实战

事件 public delegate void NumManipulationHandler(NumEventArgs e); // 基于上面的委托定义事件 public event NumManipulationHandler ChangeNum;public class Program{public static void Main(){NumEvent even new NumEvent(0);even.ChangeNum EventAction.Action;even…

【全篇】C语言从入门到入土

【全篇】C语言从入门到入土 文章目录 【全篇】C语言从入门到入土第一章 前言如何去学习&#xff0c;学习方法论 第二章 初识1.代码编译工具2.c程序的基础框架3.数据的表现形式变量1.要先定义后使用&#xff08;变量名的定义是由自己决定的&#xff0c;一般倾向于顾文生义&#…