09_实现reactive之代理 Set 和 Map

目录

    • 创建代理
    • 建立响应式联系
    • 避免污染原始数据
    • 处理 forEach
    • for...of
    • values 与 keys 方法

Set 和 Map 都有特定的属性和方法来操作自身,因此需要单独处理。

创建代理

我们来看一段案例代码,体验一下和它们的独特之处,如下:

const s = new Set([1, 2, 3])
const sProxy = new Proxy(s, {})console.log(sProxy.size) // TypeError: Method get Set.prototype.size called on incompatible receiver

这段代码的执行结果是报错,大概意思就是“无法再 receiver 上调用不兼容的 get Set.prototype.size 方法”,从这个信息中可以大概看出来是一个 size 是一个访问器属性,如图:

在这里插入图片描述

在这里插入图片描述

所以我们不能使用这个代理去访问,而是使用 target 访问,所以我们可以做出如下的修改:

const s = new Set([1, 2, 3])
const sProxy = new Proxy(s, {get(target, key, receiver) {// 如果读取的是 size 属性,则 receiver 改为 targetif (key === 'size') {return Reflect.get(target, key, target)}// 读取其他属性的行为return Reflect.get(target, key, receiver)}
})console.log(sProxy.size) // 3

那我们来看一下其他行为会不会出现问题,代码如下:

const s = new Set([1, 2, 3])
const sProxy = new Proxy(s, {get(target, key, receiver) {if (key === 'size') {return Reflect.get(target, key, target)}return Reflect.get(target, key, receiver)}
})sProxy.delete(1) // TypeError: Method Set.prototype.delete called on incompatible receiver

也抛出了一个错误,这个错误与上一个错误也非常相似,不同的点在于 size 属性是一个访问器属性,因此 sProxy.size 时是会立即触发 getter 的,此时可以通过修改 receiver 来改变 this 指向。

而 sProxy.delete 是一个方法,sProxy.delete 是访问状态,并没有真的执行,只有当 sProxy.delete(1) 才是真正执行了,但是这个执行是外部的用户操作的,因此就算我们一样设置 Reflect.get(target, key, target),也不行,因为 JavaScript 中 this 的指向通常需要函数调用那一刻才能确定,所以最后还是变成 sProxy 来调用。

找到问题的源头,解决的手段也就呼之欲出了,我们其实只需要改变 this 的指向,而这种改变 this 指向让用户调用这个函数的效果,正好是 bind 方法所具备的,代码如下:

const s = new Set([1, 2, 3])
const sProxy = new Proxy(s, {get(target, key, receiver) {if (key === 'size') {return Reflect.get(target, key, target)}return target[key].bind(target)}
})sProxy.delete(1)
console.log(sProxy) // Set(2) { 2, 3 }

此时,我们的代码就可以正常被代理了,当然,我们目前的 get 是还没融入到之前的系统中的,我们也可以将其融入,如下:

function baseGet(isShallow, isReadonly) {return function get(target, key, receiver) {/* ...省略 */// 在 Vue 的源码中是没有如此直接判断类型的处理的,而是单独抽到了 collectionHandlers 这个文件里面处理,有兴趣的可以自己去查阅一下// 这里我们直接判断类型,来进入单独的分支处理if (getValueType(target) === 'Set') {if (key === 'size') {return Reflect.get(target, key, target)}return target[key].bind(target)}const result = Reflect.get(target, key, receiver)if (isShallow) return resultif (typeof result === 'object' && result !== null) {return isReadonly ? readonly(result) : reactive(result)}return result}
}

此时我们的测试案例就可以写成一个更加简洁的方式,如下:

const s = reactive(new Set([1, 2, 3]))
console.log(s.size) // 3
s.delete(1)
console.log(s.size) // 2const m = reactive(new Map())
m.set('a', 1)
m.set('b', 2)
console.log(m.size) // 2

建立响应式联系

处理好如何代理之后,那我们是否已经可以进行响应式了呢?我们来看一段案例,如下:

const s = reactive(new Set([1, 2, 3]))
effect(() => {console.log('effect:', s.size)
})
s.add(4)

遗憾的是,这里是无法触发副作用函数的,如图:

在这里插入图片描述

我们在前面只是创建了代理,但是并没有收集依赖,所以我们可以进行完善一下,如下:

function baseGet(isShallow, isReadonly) {return function get(target, key, receiver) {/* ...省略 */if (getValueType(target) === 'Set' || getValueType(target) === 'Map') {if (key === 'size') {// 收集依赖track(target, ITERATE_KEY)return Reflect.get(target, key, target)}return target[key].bind(target)}const result = Reflect.get(target, key, receiver)if (isShallow) return resultif (typeof result === 'object' && result !== null) {return isReadonly ? readonly(result) : reactive(result)}return result}
}

这里的 key 之所以使用 ITERATE_KEY,是因为 Set 无论是新增还是删除都会影响 size 属性,收集了响应之后,又应该如何触发呢?按照之前的处理逻辑,add 本身也是一个函数,我们如果想在这里做一些文章的话,只能是在包装一层,从而在我们包装的这一层,来实现我们需要的逻辑,如下:

function baseGet(isShallow, isReadonly) {return function get(target, key, receiver) {/* ...省略 */if (getValueType(target) === 'Set' || getValueType(target) === 'Map') {if (key === 'size') {track(target, ITERATE_KEY)return Reflect.get(target, key, target)}// add 如此,那么其他方法也是如此,因此我们单独提出一个对象来处理return mutableInstrumetations[key]}const result = Reflect.get(target, key, receiver)if (isShallow) return resultif (typeof result === 'object' && result !== null) {return isReadonly ? readonly(result) : reactive(result)}return result}
}

那么我们现在的任务就是完善一下 mutableInstrumetations,如下:

const mutableInstrumetations = {add(value) {// 此时这里 add 方法的 this 依然是 proxy//  - 因为外部使用的时候还是通过代理对象调用的 s.add()//  - 所以我们要拿到原始对象,通过原始对象调用 add 方法const target = this[RAW_KEY]// 拿到原始对象之后,也就省去了 bind,直接通过 target.add() 调用即可//  - 保存得到的结果const result = target.add(value)// 触发依赖-指定类型为 ADDtrigger(target, value, 'ADD')return result}
}

此时再次运行测试案例,结果如图:

在这里插入图片描述

当然,我们这里也还存在优化的地方,比如这个值如果存在了,那么就没有必要再次触发了,如下:

const mutableInstrumetations = {add(value) {const target = this[RAW_KEY]const hasKey = target.has(value)const result = target.add(value)// 不存在则触发 ADD 依赖if (!hasKey) {trigger(target, value, 'ADD')}return result}
}

而在此基础之上,依葫芦画瓢,就可以轻松写出 delete 方法,如下:

const mutableInstrumetations = {delete(value) {const target = this[RAW_KEY]const hasKey = target.has(value)const result = target.delete(value)// 存在才表示正确的删除了,需要触发依赖if (hasKey) {trigger(target, value, 'DELETE')}return result}
}

避免污染原始数据

这个概念可能需要我们用一些例子来举例才能理解其具体是什么,在这其中,我们需要用到 Map 数据类型 get 和 set 方法,也同样,我们需要给他建立对应的响应式联系,如下:

const mutableInstrumetations = {get(key) {const target = this[RAW_KEY]// 检测是否存在这个 keyconst had = target.has(key)// 建立依赖track(target, key)if (had) {// 通过 key 获取值const result = target.get(key)// 如果是对象,则递归处理if (result === 'object' && result !== null) {return reactive(result)} // 不是则直接返回原始值else {return result}}}
}

但是这里还有一些细节是我们没有处理的,在递归处理这里,还需要考虑是否是只读,是否是浅响应的情况,因此,这个 mutableInstrumetations 对象里面定义的方法就需要拿到这 isShallow, isReadonly 两个值,来进行后续的逻辑执行,所以需要进行一些改造,如下:

const mutableInstrumetations = {add() {return function (value) {const target = this[RAW_KEY]const hasKey = target.has(value)const result = target.add(value)if (!hasKey) {trigger(target, value, 'ADD')}return result}},delete() {return function (value) {const target = this[RAW_KEY]const hasKey = target.has(value)const result = target.delete(value)if (hasKey) {trigger(target, value, 'DELETE')}return result}},get(isShallow, isReadonly) {return function (key) {const target = this[RAW_KEY]const had = target.has(key)track(target, key)if (had) {const result = target.get(key)if (typeof result === 'object' && result !== null) {// 这里的逻辑并不需要我们在写一次,在之前就已经处理过了,复用 createReactiveObject 函数即可return createReactiveObject(result, isShallow, isReadonly)} else {return result}}}}
}

这里利用闭包来实现接收 isShallow, isReadonly 的值进行后续的使用,这里修改了,使用 mutableInstrumetations 的地方也需要相应的修改,如下:

function baseGet(isShallow, isReadonly) {return function get(target, key, receiver) {/* ...省略 */if (getValueType(target) === 'Set' || getValueType(target) === 'Map') {if (key === 'size') {track(target, ITERATE_KEY)return Reflect.get(target, key, target)}// 这里也要更新一下使用方式,将数据传递return mutableInstrumetations[key](isShallow, isReadonly)}/* ...省略 */}
}

完成了收集依赖的功能之后,我们还需要进行依赖的触发,如下:

const mutableInstrumetations = {set() {return function (key, value) {const target = this[RAW_KEY]// 检测 key 是否存在const had = target.has(key)// 获取旧值const oldValue = target.get(key)// 设置新值target.set(key, value)// 触发依赖-判断当前 key 是新增还是修改,不存在则表示是新增if (!had) {trigger(target, key, TriggerType.ADD)} else {// 两次值存在变化才触发依赖if (!Object.is(oldValue, value)) {trigger(target, key, TriggerType.SET)}}}}
}

此时,我们就可以编写一段测试代码进行测试,如下:

const m = reactive(new Map([['a', 1]]))
effect(() => {console.log('effect:', m.get('a'))
})
m.set('a', 2)

结果如图:

在这里插入图片描述

此时问题 set 函数就会存在一个问题,我们来看一下下面这段测试代码:

// 原始 map 对象
const m = new Map()
// p1 是 m 的代理对象
const p1 = reactive(m)
// p2 是另一个代理对象
const p2 = reactive(new Map())
// 为 p1 设置一个键值对,值是代理对象 p2
p1.set('p2', p2)effect(() => {// Tip:这里通过原始对象 m 访问 p2,而不是代理对象 p1console.log('effect: ', m.get('p2').size)
})// Tip:这里通过原始对象 m 设置 p2 为一个键值对 foo --> 1
//  - 这里 m.get('p2') 返回的是代理对象 p2(即一个代理好的 map 对象)
m.get('p2').set('foo', 1)

我们来运行一下这段测试代码,如图:

在这里插入图片描述

此时我们就发现了问题,我们使用的是一个 m 原始对象,原始对象为什么会关联起来呢?这个行为是不应该的,原始数据如果具备这个行为,则用户既可以操作原始数据又可以操作响应式数据,那么代码的执行就会造成混乱。

而造成这个问题的原因就在于 set 中,我们是直接运行代码 target.set(key, value) 将 value 设置到了 target 这个原始数据上面,而我们这种把响应式数据设置原始数据上的行为叫做数据污染

解决这个问题也非常简单,在设置的时候检测是否是一个响应式数据,如果是则在获取一下其原始对象数据即可,如下:

const mutableInstrumetations = {set() {return function (key, value) {const target = this[RAW_KEY]const had = target.has(key)const oldValue = target.get(key)// 先获取一下当前 value 的 RAW_KEY 属性是否存在值,存在则表示是代理对象const rawValue = value[RAW_KEY] || valuetarget.set(key, rawValue)if (!had) {trigger(target, key, TriggerType.ADD)} else {if (!Object.is(oldValue, value)) {trigger(target, key, TriggerType.SET)}}}}
}

处理 forEach

遍历这个东西我们已经很熟悉了,在这里以 Map 为例,只要可以影响键值对的熟练都应该建立响应式联系,代码如下:

const mutableInstrumetations = {forEach() {return function (callback) {const target = this[RAW_KEY]// 与 ITERATE_KEY 建立依赖关系track(target, ITERATE_KEY)// 使用原始对象的 forEach 方法target.forEach(callback)}}
}

经过这样处理之后,我们就初步实现了依赖触发,测试代码如下:

const m = reactive(new Map([[{ key: 1 }, { value: 1 }]]))
effect(() => {console.log('effect')m.forEach((value, key) => {console.log('value:', value, 'key:', key)})
})m.set({ key: 2 }, { value: 2 })

结果如图:

在这里插入图片描述

但是目前还是存在一些缺陷,目前我们是直接将 callback 交给原始对象的 forEach 方法,这就将导致回调函数的参数不是一个响应式数据,从而引发下面这个代码无法预期工作:

const key = { key: 1 }
const m = reactive(new Map([[key, new Set([1, 2, 3])]]))effect(() => {console.log('effect')m.forEach((v, k) => {console.log(v.size)})
})// 没有触发副作用函数执行
m.get(key).add(4)

按照预期来说,value(即Set),添加了一个数据,改变了 size 的值,就应该再次触发副作用函数,所以为了解决这一点,我们需要将 callback 的参数变为响应式数据,如下:

const mutableInstrumetations = {forEach() {return function (callback) {const target = this[RAW_KEY]// 转为响应式对象const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}track(target, ITERATE_KEY)target.forEach((v, k) => {callback(wrap(v), wrap(k), this)})}}
}

此时就可以触发了,结果如图:

在这里插入图片描述

当然,forEach 原本还有第二个参数,在这里我们也需要补上,如下:

const mutableInstrumetations = {forEach() {// 接收第二个参数return function (callback, thisArg) {const target = this[RAW_KEY]// 转为响应式对象const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}track(target, ITERATE_KEY)target.forEach((v, k) => {callback.call(thisArg, wrap(v), wrap(k), this)})}}
}

至此,我们还要一项缺陷没有补充完成,那就是与值的响应式联系,与 for…in 不同,for…in 只关心键的数量,而 map 的forEach 还需要关心值,示例代码如下:

const m = reactive(new Map([['key', 1]]))effect(() => {console.log('effect')m.forEach((value, key) => {console.log(value)})
})m.set('key', 2)

此时我们改变了 value,但是并没有触发,而这个操作是属于设置(即 SET),那么就表示就算是 set 操作也要触发与 ITERATE_KEY 相关联的数据,所以我们需要对 trigger 函数在做一些处理,如下:

function trigger(target, key, type, newValue) {// 省略代码if (type === TriggerType.ADD ||type === TriggerType.DELETE ||// 如果操作类型是 SET,且对象是 Map 数据类型,也应该触发 ITERATE_KEY 的副作用函数(type === TriggerType.SET && getValueType(target) === 'Map')) {let iterateDeps = depsMap.get(ITERATE_KEY)addEffects(effetsToRun, iterateDeps)}// 省略代码
}

执行结果如图:

在这里插入图片描述

for…of

这一块涉及的就是迭代器的知识,而且这块的处理与 forEach 都差不多,所以我们就直接写一个初步的方案,如下:

const mutableInstrumetations = {[Symbol.iterator]() {return function () {const target = this[RAW_KEY]const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}// 获取原始迭代器方法const iterator = target[Symbol.iterator]()// 收集依赖track(target, ITERATE_KEY)// 返回自定义的迭代器return {next() {// 调用原始迭代器的 next 方法,获取 value 和 doneconst { value, done } = iterator.next()return {// 如果 value 有值,则进行包裹,map数据的 value 是一个数组,数组中的每一项都是 [key, value]value: value ? [wrap(value[0]), wrap(value[1])] : value,done}}}}}
}

可以看到,整个的过程都是和 forEach 非常的类似的,我们编写一段测试代码,如下:

const m = reactive(new Map([['key1', 'value1'],['key2', 'value2']])
)effect(() => {console.log('effect')for (const [key, value] of m) {console.log(key, value)}
})m.set('key3', 'value3')

结果如图:

在这里插入图片描述

在这里还有个地方需要注意一下,m.entries 方法与 m[Symbol.iterator] 是等价的,所以我们使用同样的代码实现即可,如下:

const mutableInstrumetations = {[Symbol.iterator]: iterationMethod,entries: iterationMethod  
}// 抽离函数,进行复用
function iterationMethod() {return function () {const target = this[RAW_KEY]const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}// 获取原始迭代器方法const iterator = target[Symbol.iterator]()// 收集依赖track(target, ITERATE_KEY)// 返回自定义的迭代器return {next() {// 调用原始迭代器的 next 方法,获取 value 和 doneconst { value, done } = iterator.next()return {// 如果 value 有值,则进行包裹,map数据的 value 是一个数组,数组中的每一项都是 [key, value]value: value ? [wrap(value[0]), wrap(value[1])] : value,done}}}}
}

那么此时我们来进行一下测试,看一下能否正常使用,如下:

const m = reactive(new Map([['key1', 'value1'],['key2', 'value2']])
)// TypeError: m.entries is not a function or its return value is not iterable
for (const [key, value] of m.entries()) {console.log(key, value)
}

此时代码会进行一个报错,意思为 m.entries 返回的不是一个可迭代对象。通过这个错误我们就可以知道,这个返回的对象具备 next 方法,但是不具备 Symbol.iterator 方法,因此不是一个可迭代对象。

Tip:可迭代协议可迭代器协议并不一致,可迭代协议指的是一个对象实现了 Symbol.iterator 方法,而可迭代器协议指的是对象实现了 next 方法,而一个对象是可以同时具备这两者的,例如:

const obj = {// 迭代器协议next(){// ...},// 可迭代协议[Symbol.iterator](){return this}
}

根据这个我们就可以解决我们的问题,只需要返回的对象增加一个可迭代协议即可,代码如下:

// 抽离函数,进行复用
function iterationMethod() {return function () {const target = this[RAW_KEY]const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}const iterator = target[Symbol.iterator]()track(target, ITERATE_KEY)return {next() {const { value, done } = iterator.next()return {value: value ? [wrap(value[0]), wrap(value[1])] : value,done}},[Symbol.iterator]() {return this}}}
}

现在执行就不会在抛出错误了。

values 与 keys 方法

values 方法与 entries 方法的实现也差不多,values 使用 for…of 进行迭代的时候,得到的仅仅是 map 数据的 value,而非与 entries 一样,同时得到 kv。

values 方法实现如下:

const mutableInstrumetations = {[Symbol.iterator]: iterationMethod,entries: iterationMethod,values: valuesIterationMethod
}function valuesIterationMethod() {return function () {const target = this[RAW_KEY]const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}const iterator = target.values()track(target, ITERATE_KEY)return {next() {const { value, done } = iterator.next()return {// 仅会获取 value,所以只需要针对 value 进行包装value: wrap(value),done}},[Symbol.iterator]() {return this}}}
}

keys 的方法也遇 values 实现差不多,如下:

const mutableInstrumetations = {[Symbol.iterator]: iterationMethod,entries: iterationMethod,values: valuesIterationMethod,keys: keysIterationMethod
}function keysIterationMethod() {return function () {const target = this[RAW_KEY]const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}// 更换为 keysconst iterator = target.keys()track(target, ITERATE_KEY)return {next() {const { value, done } = iterator.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}}
}

好像看着很完美,但是这里还存在一个缺陷,我们来看下面这个例子,如下:

const m = reactive(new Map([['key1', 'value1'],['key2', 'value2']])
)effect(() => {console.log('effect')for (const key of m.keys()) {console.log(key)}
})m.set('key2', 'value2-value2')

来看一下执行结果,如图:

在这里插入图片描述

我们只是通过 key 修改了一个值,并没有改动 key 本身啊,按照 keys 方法的语义,我们是不应该再次触发这个副作用函数的,所以我们在这里就要修改一下我们收集依赖的地方,如下:

function keysIterationMethod() {return function () {const target = this[RAW_KEY]const wrap = v => {if (typeof v === 'object' && v !== null) {return reactive(v)}return v}const iterator = target.keys()// 更改依赖建立关系track(target, MAP_KEY_ITERATOR_KEY)return {next() {const { value, done } = iterator.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}}
}function trigger(target, key, type, newValue) {/* ... */// 处理 Map 数据类型的 keys 方法if ((type === TriggerType.ADD || type === TriggerType.DELETE) && getValueType(target) === 'Map') {// 取得 Map 数据类型的 keys 方法对应的副作用函数const iterateEffects = depsMap.get(MAP_KEY_ITERATOR_KEY)addEffects(effetsToRun, iterateEffects)}/* ... */
}

我们现在再编写一段测试代码看一下,如下:

const m = reactive(new Map([['key1', 'value1'],['key2', 'value2']])
)effect(() => {console.log('effect')for (const key of m.keys()) {console.log(key)}
})m.set('key2', 'value2-value2') // 不可触发响应
m.set('key3', 'value3') // 可触发响应

结果如图:

在这里插入图片描述

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

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

相关文章

第二代 GPT-SoVITS V2:解锁语音克隆与合成的无限可能

在 AI 技术蓬勃发展的今天,第二代 GPT-SoVITS V2 如一颗璀璨的明星闪耀登场,为语音处理领域带来了前所未有的变革。它是一款集先进技术与强大功能于一身的声音克隆与语音合成工具,由 RVC 变声器创始人 “花儿不哭” 与 AI 音色转换技术 Sovit…

使用 pydub 的 AudioSegment 获取音频时长 - python 实现

通过使用 pydub 的 AudioSegment 获取音频时长,音频常用格式如 m4a,wav等。 安装 python 库: pip install pydub 获取 m4a 格式的音频时长代码如下,代码如下: #-*-coding:utf-8-*- # date:2024-10 # Author: DataBall - XIAN #…

mac nwjs程序签名公证(其他mac程序也一样适用)

为什么需要公证 mac os14.5之后的系统,如果不对应用进行公证,安装,打开,权限使用上都会存在问题,而且有些问题你强制开启(sudo spctl --master-disable)使用后可能会有另外的问题, …

JSON Web Token (JWT)的简单介绍、验证过程及令牌刷新思路

目录 一、JWT 1、什么是Jwt 2、为什么要使用Jwt 3、应用场景 4.Jwt的组成 4.1、Header 4.2、Payload 4.3、signature 二、Jwt验证过程 1、生成Jwt令牌 2、解析旧的Jwt 3、复制Jwt 4、Jwt有效时间测试 三、Jwt令牌刷新思路 1、配置JwtFilter过滤器 2、登录生成Jwt令…

解决Redis缓存穿透(缓存空对象、布隆过滤器)

文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库 常见的解决方案有两种,分别…

2024年10月24日第一部分AOP编程和自信

测试 Spring通知(前置通知,后置通知,返回通知,异常通知,环绕通知)_前置通知后置通知环绕通知-CSDN博客 一、前置通知 --前置通知 : 在方法执行之前执行的通知 --前置通知使用 Before 注解 , 并将切入点表…

【2024CANN训练营第二季】使用华为云体验AscendC_Sample仓算子运行

环境介绍 NPU:Ascend910B2 环境准备 创建Notebook 华为云选择:【控制台】-【ModelArts】 ModelArts主页选择【开发生产】-【开发空间】-【Notebook】 页面右上角选择【创建Notebook】 选择资源 主要参数 规格:Ascend: 1*ascend-snt…

VS code部署Vue项目Demo

在之前已经在IDEA中部署过vue项目demo。本次在上次基础上进行。 IDEA中Vue的安装和使用【window10】_idea安装vue-CSDN博客 步骤一、安装VSCode 双击安装即可 步骤二:检查npm是否安装 步骤三:检查vue是否安装 (vue create 项目名 只要在v…

【AscendC算子开发】笔记1 算子开发哲学

重看这门课,有很多内容的认识更深了,做一些记录。 为什么不能将网络节点融合 这个问题关联到另一个问题:为什么我们需要激活函数? 使用线性的神经元堆叠得到的方程最后也是线性方程,无法表征非线性的信息&#xff0c…

微信网页授权回调地址放多个参数的方法

https://open.weixin.qq.com/connect/oauth2/authorize?appidAPPID&redirect_uriREDIRECT_URI&response_typecode&scopeSCOPE&stateSTATE#wechat_redirect 跳转后地址 redirect_uri/?codeCODE&stateSTATE。 redirect_uri如果不进行urlencode编码, 跳转后…

C++20中头文件syncstream的使用

<syncstream>是C20中新增加的头文件&#xff0c;提供了对同步输出流的支持&#xff0c;即在多个线程中可安全地进行输出操作&#xff0c;此头文件是Input/Output库的一部分。包括&#xff1a; 1.std::basic_syncbuf&#xff1a;是std::basic_streambuf的包装器(wrapper)&…

《在1688的数字海洋中,如何用API网罗一家店铺的所有商品?》

想象一下&#xff0c;你是一位船长&#xff0c;航行在1688这个电商的数字海洋上。你的任务是探索一家神秘的店铺岛屿&#xff0c;并且用你的API魔法网&#xff0c;网罗岛上所有的商品宝藏。不用担心&#xff0c;即使你不是海贼王&#xff0c;有了代码的力量&#xff0c;你也能成…

【数据结构初阶】二叉树---堆

二叉树-堆的实现 一、树的概念&#xff08;什么是树&#xff09;二、二叉树的概念及结构2.1 二叉树的概念2.2 二叉树的性质2.3 二叉树存储结构 三、二叉树的顺序结构3.1 堆的概念及结构3.2 堆的向下调整算法3.3堆的创建 四、堆的代码实现4.1 堆的初始化4.2 堆的销毁4.3 堆的插入…

ipguard与Ping32如何加密数据防止泄露?让企业信息更安全

在信息化时代&#xff0c;数据安全已成为企业运营的重中之重。数据泄露不仅会导致经济损失&#xff0c;还可能损害企业声誉。因此&#xff0c;选择合适的数据加密工具是保护企业敏感信息的关键。本文将对IPGuard与Ping32这两款加密软件进行探讨&#xff0c;了解它们如何有效加密…

SAP_SD模块-销售订单创建价格扩大10倍问题分析及后续订单价格批量更新问题处理

一、业务背景 我们公司的销售订单&#xff0c;是通过第三方销售管理平台创建好订单后&#xff0c;把表头和行项目数据&#xff0c;定时推送到SAP&#xff1b;SAP通过自定义表ZZT_ORDER_HEAD存放订单表头数据&#xff0c;通过ZZT_ORDER_DETAIL存放行项目数据&#xff1b;然后再用…

git安装-Tortoise git 安装汉化教程

1. 安装git 2. 安装git图形化工具Tortoise git 3. 汉化 Tortoise git 汉化安装包

证件照电子版怎么弄?不花钱制作方法快来学

想要制作免费照证件照&#xff1f;证件照在我们的日常生活中扮演着重要角色&#xff0c;无论是求职、求学还是办理各类证件&#xff0c;都少不了它的身影。 但是&#xff0c;去照相馆拍照不仅耗时&#xff0c;费用也不菲。那么&#xff0c;有没有可能不花一分钱就搞定证件照呢…

互联网系统的微观与宏观架构

互联网系统的架构设计&#xff0c;通常会根据项目的体量、业务场景以及技术需求被划分为微观架构&#xff08;Micro-Architecture&#xff09;和宏观架构&#xff08;Macro-Architecture&#xff09;。这两者的概念与职责既独立又相互关联。本文将通过一些系统案例&#xff0c;…

淘宝API的实战应用:数据驱动增长,实时监控商品信息是关键

数据驱动增长&#xff0c;实时监控商品信息是关键 —— 淘宝API的实战应用 在数字化时代&#xff0c;数据已经成为商业决策的核心。对于电商行业而言&#xff0c;获取准确、实时的数据是保持竞争力的关键。淘宝API接口作为连接淘宝电商平台与外部应用的桥梁&#xff0c;为电商商…

【论文+源码】基于spring boot的垃圾分类网站

创建一个基于Spring Boot的垃圾分类网站涉及多个步骤&#xff0c;包括环境搭建、项目创建、数据库设计、后端服务开发、前端页面设计等。下面我将引导您完成这个过程。 第一步&#xff1a;准备环境 确保您的开发环境中安装了以下工具&#xff1a; Java JDK 8 或更高版本Mav…