petite-vue源码剖析-逐行解读@vue-reactivity之effect

Python微信订餐小程序课程视频

https://blog.csdn.net/m0_56069948/article/details/122285951

Python实战量化交易理财系统

https://blog.csdn.net/m0_56069948/article/details/122285941
当我们通过effect将副函数向响应上下文注册后,副作用函数内访问响应式对象时即会自动收集依赖,并在相应的响应式属性发生变化后,自动触发副作用函数的执行。

// ./effect.tsexport funciton effectany>(fn: () => T,options?: ReactiveEffectOptions
): ReactiveEffectRunner {if ((fn as ReactiveEffectRunner).effect) {fn = (fn as ReactiveEffectRunner).effect.fn}const \_effect = new ReactiveEffect(fn)if (options) {extend(\_effect, options)if (options.scope) recordEffectScope(\_effect, options.scope)}// 默认是马上执行副作用函数收集依赖,但可通过lazy属性延迟副作用函数的执行,延迟依赖收集。if (!options || !options.lazy) {\_effect.run()}// 类型为ReactiveEffectRunner的runner是一个绑定this的函数const runner = \_effect.run.bind(\_effect) as ReactiveEffectRunnerrunner.effect = \_effectreturn runner
}

effect函数的代码十分少,主要流程是

  1. 将基于副作用函数构建ReactiveEffect对象
  2. 若为默认模式则马上调用ReactiveEffect对象的run方法执行副作用函数。

不过这里我们有几个疑问

  1. ReactiveEffectRunner是什么?
  2. ReactiveEffect生成的对象究竟是什么?显然ReactiveEffectrun方法才是梦开始的地方,到底它做了些什么?
  3. 针对配置项scoperecordEffectScope的作用?

ReactiveEffectRunner是什么?

// ./effect.ts// ReactiveEffectRunner是一个函数,而且有一个名为effect的属性且其类型为RectiveEffect
export interface ReactiveEffectRunnerany> {(): Teffect: ReactiveEffect
}

ReactiveEffect生成的对象究竟是什么?

// 用于记录位于响应上下文中的effect嵌套层次数
let effectTrackDepth = 0
// 二进制位,每一位用于标识当前effect嵌套层级的依赖收集的启用状态
export left trackOpBit = 1
// 表示最大标记的位数
const maxMarkerBits = 30const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefinedexport class ReactiveEffectany> {// 用于标识副作用函数是否位于响应式上下文中被执行active = true// 副作用函数持有它所在的所有依赖集合的引用,用于从这些依赖集合删除自身deps: Dep[] = []// 默认为false,而true表示若副作用函数体内遇到`foo.bar += 1`则无限递归执行自身,直到爆栈allowRecurse?: booleanconstructor(public fn: () => T,public scheduler: EffectScheduler | null = null,scope?: EffectScope | null) {recordEffectScope(this, scope)}run() {/*** 若当前ReactiveEffect对象脱离响应式上下文,那么其对应的副作用函数被执行时不会再收集依赖,并且其内部访问的响应式对象发生变化时,也会自动触发该副作用函数的执行*/if (!this.active) {return this.fn()}// 若参与响应式上下文则需要先压栈if (!effectStack.includes(this)) {try {// 压栈的同时必须将当前ReactiveEffect对象设置为活跃,即程序栈中当前栈帧的意义。effectStack.push(activeEffect = this)enableTracking()trackOpBit = 1 << ++effectTrackDepthif (effectTrackDepth <= maxMarkerBits) {// 标记已跟踪过的依赖initDepMarkers(this)}else {cleanupEffect(this)}return this.fn()}finally {if (effectTrackDepth <= maxMarkerBits) {/*** 用于对曾经跟踪过,但本次副作用函数执行时没有跟踪的依赖,采取删除操作。* 即,新跟踪的 和 本轮跟踪过的都会被保留。*/finalizeDepMarkers(this)}trackOpBit = 1 << --effectTrackDepthresetTracking()// 最后当然弹栈,把控制权交还给上一个栈帧咯effectStack.pop()const n = effectStack.lengthactiveEffect = n > 0 ? effectStack[n - 1] : undefined }}/*** 让当前ReactiveEffect对象脱离响应式上下文,请记住这是一去不回头的操作哦!*/ stop() {if (this.active) {cleanupEffect(this)this.active = false}}}
}

为应对嵌套effect内部将当前位于响应上下文的ReactiveEffect对象压入栈结构effectStack: ReactiveEffect[],当当前副作用函数执行后再弹出栈。另外,虽然我们通过effect函数将副作用函数注册到响应上下文中,但我们仍能通过调用stop方法让其脱离响应上下文。

function cleanupEffect(effect: ReactiveEffect) {const { deps } = effectif (deps.length) {// 将当前ReactiveEffect对象从它依赖的响应式属性的所有Deps中删除自己,那么当这些响应式属性发生变化时则不会遍历到当前的ReactiveEffect对象for (let i = 0; i < deps.length; ++i) {deps[i].delete(effect)}// 当前ReactiveEffect对象不再参与任何响应了deps.length = 0}
}

在执行副作用函数前和执行后我们会看到分别调用了enableTracking()resetTracking()函数,它们分别表示enableTracking()执行后的代码将启用依赖收集,resetTracking()则表示后面的代码将在恢复之前是否收集依赖的开关执行下去。要理解它们必须结合pauseTracking()和实际场景说明:

let shouldTrack = true
const trackStack: boolean[] = []export function enableTracking() {trackStack.push(shouldTrack)shouldTrack = true
}export function resetTracking() {const last = trackStack.pop()shouldTrack = last === undefined ? true : last
}export function pauseTracking() {trackStack.push(shouldTrack)shouldTrack = false
}

假设我们如下场景

const values = reactive([1,2,3])
effect(() => {values.push(1)
})

由于在执行push时内部会访问代理对象的length属性,并修改length值,因此会导致不断执行该副作用函数直到抛出异常Uncaught RangeError: Maximum call stack size exceeded,就是和(function error(){ error() })()不断调用自身导致栈空间不足一样的。而@vue/reactivity是采用如下方式处理

;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {instrumentations[key] = function (this: unknown[], ...args: unknown[]) {pauseTracking()const res = (toRaw(this) as any)[key].apply(this, args)resetTracking()return res}
})

即通过pauseTracking()暂停push内部的发生意外的依赖收集,即push仅仅会触发以其他形式依赖length属性的副作用函数执行。然后通过resetTracking()恢复到之前的跟踪状态。

最后在执行副作用函数return this.fn()前,居然有几句难以理解的语句

try {trackOpBit = 1 << ++effectTrackDepthif (effectTrackDepth <= maxMarkerBits) {initDepMarkers(this)}else {cleanupEffect(this)}return this.fn()
}
finally {if (effectTrackDepth <= maxMarkerBits) {finalizeDepMarkers(this)}trackOpBit = 1 << --effectTrackDepth
}

我们可以将其简化为

try {cleanupEffect(this)return this.fn()
}
finally {}

为什么在执行副作用函数前需要清理所有依赖呢?我们可以考虑一下如下的情况:

const state = reactive({ show: true, values: [1,2,3] })
effect(() => {if (state.show) {console.log(state.values)}
})
setTimeout(() => {state.values.push(4)
}, 5000)setTimeout(() => {state.show = false
}, 10000)setTimeout(() => {state.values.push(5)
}, 15000)

一开始的时候副作用函数将同时依赖showvalues,5秒后向values追加新值副作用函数马上被触发重新执行,再过10秒后show转变为false,那么if(state.show)无论如何运算都不成立,此时再对values追加新值若副作用函数再次被触发显然除了占用系统资源外,别无用处。
因此,在副作用函数执行前都会先清理所有依赖(cleanupEffect的作用),然后在执行时重新收集。

面对上述情况,先清理所有依赖再重新收集是必须的,但如下情况,这种清理工作反而增加无谓的性能消耗

const state = reactive({ show: true, values: [1,2,3] })
effect(() => {console.log(state.values)
})

@vue/reactivity给我们展示了一个非常优秀的处理方式,那么就是通过标识每个依赖集合的状态(新依赖和已经被收集过),并对新依赖和已经被收集过两个标识进行对比筛选出已被删除的依赖项。

优化无用依赖清理算法

export type Dep = Set<ReactiveEffect> & Trackedmarkerstype TrackedMarkers = {/*** wasTracked的缩写,采用二进制格式,每一位表示不同effect嵌套层级中,该依赖是否已被跟踪过(即在上一轮副作用函数执行时已经被访问过)*/ w: number/*** newTracked的缩写,采用二进制格式,每一位表示不同effect嵌套层级中,该依赖是否为新增(即在本轮副作用函数执行中被访问过)*/ n: number
}export const createDep = (effects) => {const dep = new Set<ReactiveEffect>(effects) as Dep// 虽然TrackedMarkers标识是位于响应式对象属性的依赖集合上,但它每一位仅用于表示当前执行的副作用函数是否曾经访问和正在访问该响应式对象属性dep.w = 0dep.n = 0return dep
}export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0/*** 将当前副作用函数的依赖标记为 `已经被收集`*/
export const initDepMarkers = ({ deps }: ReactiveEffect) => {if (deps.length) {for (let i = 0; i < deps.length; i++) {deps[i].w |= trackOpBit}}
}/*** 用于对曾经跟踪过,但本次副作用函数执行时没有跟踪的依赖,采取删除操作。* 即,新跟踪的 和 本轮跟踪过的都会被保留。*/
export const finalizeDepMarkers = (effect: ReactiveEffect) => {const { deps } = effectif (deps.length) {let ptr = 0for (let i = 0; i < deps.length; i++) {const dep = deps[i]if (wasTracked(dep) && !newTracked(dep)) {// 对于曾经跟踪过,但本次副作用函数执行时没有跟踪的依赖,采取删除操作。dep.delete(effect)}else {// 缩小依赖集合的大小deps[ptr++] = dep}// 将w和n中对应的嵌套层级的二进制位置零,如果缺少这步后续副作用函数重新执行时则无法重新收集依赖。dep.w &= ~trackOpBitdep.n &= ~trackOpBit}// 缩小依赖集合的大小deps.length = ptr}
}
// 在位于响应式上下文执行的副作用函数内,访问响应式对象属性,将通过track收集依赖
export function track(target: object, type: TrackOpTypes, key: unknown) {if (!isTracking()) {return}// targetMap用于存储响应式对象-对象属性的键值对// depsMap用于存储对象属性-副作用函数集合的键值对let depsMap = targetMap.get(target)if (!depsMap) {target.set(target, (depsMap = new Map()))}let dep = depsMap.get(key)if (!dep) {depsMap.set(key, (dep = createDep()))}trackEffects(dep)
}// 收集依赖
export function trackEffects(dep: Dep
) {let shouldTrack = falseif (effectTrackDepth <= maxMarkerBits) {// 如果本轮副作用函数执行过程中已经访问并收集过,则不用再收集该依赖if (!newTracked(dep)) {dep.n |= trackOpBitshouldTrack = !wasTracked(dep)}}else {// 对于全面清理的情况,如果当前副作用函数对应的ReactiveEffect对象不在依赖集合中,则标记为trueshouldTrack = !dep.has(activeEffect!)}if (shouldTrack) {dep.add(activeEffect!)activeEffect!.deps.push(dep)}
}

单单从代码实现角度能难理解这个优化方式,不如我们从实际的例子出发吧!

const runAync = fn => setTimeout(fn, 1000)const state = reactive({ show: true, values: [1,2,3] })
// 1
effect(() => {if (state.show) {console.log(state.values)}
})// 2
runAync(() => {state.values.push(4)
})// 3
runAync(() => {state.show = false
})
  1. 首次执行副作用函数
    a. effectTrackDepth为0,因此1 << ++effectTrackDepth得到的effectTrackDepthtrackOpBit均为1,但由于此时副作用函数还没有收集依赖,因此initDepMarkers函数没有任何效果;
    b. 访问state.show时由于之前没有收集过响应式对象stateshow属性,因此会调用createDep创建wn均为0的依赖集合,并调用trackEffects发现newTracked(dep)为未跟踪过,则将n设置为1,然后开始收集依赖;
    c. 访问state.values会重复第2步的操作;
    d. 由于state.showstate.values都是新跟踪的(n为1),因此在finalizeDepMarkers处理后仍然将副作用函数保留在这两个属性对应的依赖集合中。
  2. 执行state.values.push(4)触发副作用函数变化
    a. effectTrackDepth为0,因此1 << ++effectTrackDepth得到的effectTrackDepthtrackOpBit均为1,此时副作用函数已经收集过依赖,因此initDepMarkers将该副作用函数所在的依赖集合都都标记为已收集过(w为1);
    b. 访问state.show时会调用trackEffects发现newTracked(dep)为未跟踪过(在finalizeDepMarkers中已被置零),则将n设置为1,然后开始收集依赖;
    c. 访问state.values会重复第2步的操作;
    d. 由于state.showstate.values都是新跟踪的(n为1),因此在finalizeDepMarkers处理后仍然将副作用函数保留在这两个属性对应的依赖集合中。
  3. 执行state.show = false触发副作用函数变化
    a. effectTrackDepth为0,因此1 << ++effectTrackDepth得到的effectTrackDepthtrackOpBit均为1,此时副作用函数已经收集过依赖,因此initDepMarkers将该副作用函数所在的依赖集合都都标记为已收集过(w为1);
    b. 访问state.show时会调用trackEffects发现newTracked(dep)为未跟踪过(在finalizeDepMarkers中已被置零),则将n设置为1,然后开始收集依赖;
    c. 由于state.values没有标记为新跟踪的(n为0),因此在finalizeDepMarkers处理后会将副作用函数从state.values对应的依赖集合中移除,仅保留在state.values对应的依赖集合中。

到这里,我想大家已经对这个优化有更深的理解了。那么接下来的问题自然而然就是为什么要硬编码将优化算法启动的嵌套层级设置为maxMarkerBits = 30

SMI优化原理

首先maxMarkerBits = 30表示仅支持effect嵌套31层,注释中描述该值是因为想让JavaScript影响使用SMI。那么什么是SMI呢?

由于ECMAScript标准约定number数字需要转换为64位双精度浮点数处理,但所有数字都用64位存储和处理是十分低效的,所以V8内部采用其它内存表示方式(如32位)然后向外提供64位表现的特性即可。其中数组合法索引范围是[0, 2^32 - 2],V8引擎就是采用32位的方式来存储这些合法的下标数字。另外,所有在[0, 2^32 - 2]内的数字都会优先使用32位二进制补码的方式存储。

针对32位有符号位范围内的整型数字V8为其定义了一种特殊的表示法SMI(非SMI的数字则被定义为HeapNumber),而V8引擎针对SMI启用特殊的优化:当使用SMI内的数字时,引擎不需要为其分配专门的内存实体,并会启用快速整型操作

对于非SMI的数字

let o = {x: 42, // SMIy: 4.2 // HeapNumber
}

内存结构为HeapNumber{ value: 4.2, address: 1 }JSObject{ x: 42, y: 1 },由于x值类型为SMI因此直接存储在对象上,而y为HeapNumber则需要分配一个独立的内存空间存放,并通过指针让对象的y属性指向HeapNumber实例的内存空间。

然而在修改值时,然后x为SMI所以可以原地修改内存中的值,而HeapNumber为不可变,因此必须再分配一个新的内存空间存放新值,并修改o.y中的内存地址。那么在没有启用Mutable HeapNumber时,如下代码将产生1.11.21.33个临时实例。

let o = { x: 1.1 }
for (let i = 0; i < 4; ++i) {o.x += 1;
}

SMI是带符号位的,那么实际存储数字是31位,因此设置maxMarkerBits = 30且通过if (effectTrackDepth <= maxMarkerBits)判断层级,即当effec嵌套到31层时不再使用无用依赖清理优化算法。而优化算法中采用的是二进制位对上一轮已收集和本轮收集的依赖进行比较,从而清理无用依赖。若nw值所占位数超过31位则内部会采用HeapNumber存储,那么在位运算上性能将有所下降。

其实我们还看到若effectTrackDepth等于31时还会执行trackOpBit = 1 << ++effectTrackDepth,这会导致trackOpBitSMI的存储方式转换为HeapNumber,那是不是可以加个判断修改成下面这样呢!

const maxMarkerBit = 1 << 30if (trackOpBit & maxMarkerBit !== 1) {trackOpBit = 1 << ++effectTrackDepth
}

副作用函数触发器-trigger

由于在讲解"优化无用依赖清理算法"时已经对track进行了剖析,因此现在我们直接分析trigger就好了。

export function trigger(target: object,// set, add, delete, cleartype: TriggerOpTypes,key?: unknown,newValue?: unknown,oldValue?: unknown,oldTarget?: Map | Set
) {const depsMap = targetMap.get(target)if (!depsMap) {// 该属性没有被任何副作用函数跟踪过,所以直接返回就好了return}/*** 用于存储将要被触发的副作用函数。* 为什么不直接通过类似depsMap.values().forEach(fn => fn())执行副作用函数呢?* 那是因为副作用函数执行时可能会删除或增加depsMap.values()的元素,导致其中的副作用函数执行异常。* 因此用另一个变量存储将要执行的副作用函数集合,那么执行过程中修改的是depsMap.values()的元素,而正在遍历执行的副作用函数集合结构是稳定的。*/let deps: (Dep | undefined)[] = []if (type === TriggerOpTypes.CLEAR) {// 对象的所有属性值清空,所有依赖该响应式对象的副作用函数都将被触发deps = [...depsMap.values()]}else if (key === 'length' && isArray(target)) {// 若设置length属性,那么依赖length属性和索引值大于等于新的length属性值的元素的副作用函数都会被触发depsMap.forEach((dep, key) => {if (key === 'length' || key >= (newValue as number)) {deps.push(dep)}})}else {// 将依赖该属性的if (key !== void 0) {// 即使插入的是undefined也没有关系deps.push(depsMap.get(key))}/*** 添加间接依赖的副作用函数* 1. 新增数组新值索引大于数组长度时,会导致数组容量被扩充,length属性也会发生变化* 2. 新增或删除Set/WeakSet/Map/WeakMap元素时,需要触发依赖迭代器的副作用函数* 3. 新增或删除Map/WeakMap元素时,需要触发依赖键迭代器的副作用函数* 4. 设置Map/WeakMap元素的值时,需要触发依赖迭代器的副作用函数*/ switch(type) {case TriggerOpTypes.ADD:if (!isArray(target)) {// 对于非数组,则触发通过迭代器遍历的副作用函数deps.push(depsMap.get(ITERATE\_KEY))if (isMap(target)) {deps.push(depsMap.get(MAP\_KEY\_ITERATE\_KEY))}}else if (isIntegerKey(key)) {// 对数组插入新元素,则需要触发依赖length的副作用函数deps.push(depsMap.get('length'))}breakcase TriggerOpTypes.DELETE:if (!isArray(target)) {// 对于非数组,则触发通过迭代器遍历的副作用函数deps.push(depsMap.get(ITERATE\_KEY))if (isMap(target)) {deps.push(depsMap.get(MAP\_KEY\_ITERATE\_KEY))}}breakcase TriggerOpTypes.SET:// 对于Map/WeakMap需要触发依赖迭代器的副作用函数if (isMap(target)) {deps.push(depsMap.get(ITERATE\_KEY))}}if (deps.length === 1) {// 过滤掉undefinedif (deps[0]) {triggerEffects(deps[0])}}else {const effects: ReactiveEffect[] = []// 过滤掉undefinedfor (const dep of deps) {if (dep) {effects.push(...dep)}}triggerEffects(createDep(effects))}}
}export function triggerEffects(dep: Dep | ReactiveEffect[]
) {for (const effect of isArray(dep) ? dep : [...dep]) {/*** 必须保证将要触发的副作用函数(effect)不是当前运行的副作用函数(activeEffect),否则将嵌入无限递归。* 假设存在如下情况* let foo = reactive({ bar: 1 })* effect(() => {* foo.bar = foo.bar + 1* })* 若没有上述的保障,则将会不断递归下去直接爆栈。* * 假如ReactiveEffect对象的allowRecurse设置为true,那么表示不对上述问题作防御。*/ if (effect !== activeEffect || effect.allowRecurse) {if (effect.scheduler) {// 若设置有调度器则调用调用器effect.scheduler()}else {// 立即执行副作用函数effect.run()}}}
}

调度器

在上一节的triggerEffects中我们看到默认采用同步方式执行副作用函数,若要同步执行数十个副作用函数那么势必会影响当前事件循环主逻辑的执行,这时就是调度器闪亮登场的时候了。我们回顾以下petite-vue中提供的调度器吧!

import { effect as rawEffect } from '@vue/reactivity'const effect = (fn) => {const e: ReactiveEffectRunner = rawEffect(fn, {scheduler: () => queueJob(e)})return e
}
// ./scheduler.tslet queued = false
const queue: Function[] = []
const p = Promise.resolve()export const nextTick = (fn: () => void) => p.then(fn)export const queueJob = (job: Function) => {if (!queue.includes(job)) queue.push(job)if (!queued) {queued = truenextTick(flushJobs)}
}const flushJobs = () => {for (const job of queue) {job()}queue.length = 0queued = false
}

副作用函数压入队列中,并将遍历队列执行其中的副作用函数后清空队列的flushJobs压入micro queue。那么当前事件循环主逻辑执行完后,JavaScript引擎将会执行micro queue中的所有任务。

什么是EffectScope

Vue 3.2引入新的Effect scope API,可自动收集setup函数中创建的effectwatchcomputed等,当组件被销毁时自动销毁作用域(scope)和作用域下的这些实例(effectwatchcomputed等)。这个API主要是提供给插件或库开发者们使用的,日常开发不需要用到它。

还记得petite-vue中的context吗?当遇到v-ifv-for就会为每个子分支创建新的block实例和新的context实例,而子分支下的所有ReactiveEffect实例都将统一被对应的context实例管理,当block实例被销毁则会对对应的context实例下的ReactiveEffect实例统统销毁。

block实例对应是DOM树中动态的部分,可以大概对应上Vue组件,而context实例就是这里的EffectScope对象了。

使用示例:

cosnt scope = effectScope()
scope.run(() => {const state = reactive({ value: 1 })effect(() => {console.log(state.value)})
})
scope.stop()

那么effect生成的ReactiveEffect实例是如何和scope关联呢?
那就是ReactiveEffect的构造函数中调用的recordEffectScope(this, scope)

export function recordEffectScope(effect: ReactiveEffect,scope?: EffectScope | null
) {// 默认将activeEffectScope和当前副作用函数绑定scope = scope || activeEffectScopeif (scope && scope.active) {scope.effects.push(effect)}
}

总结

petite-vue中使用*@vue/reactivity的部分算是剖析完成了,也许你会说@vue/reactivity*可不止这些内容啊,这些内容我将会在后续的《vue-lit源码剖析》中更详尽的梳理分析,敬请期待。
下一篇我们将看看eval中是如何使用new Functionwith来构造JavaScript解析执行环境的。
尊重原创,转载请注明来自:https://blog.csdn.net/fsjohnhuang/p/16163888.html肥仔John

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

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

相关文章

string.Format 格式化

1、格式化货币&#xff08;跟系统的环境有关&#xff0c;中文系统默认格式化人民币&#xff0c;英文系统格式化美元&#xff09; string.Format("{0:C}",0.2) 结果为&#xff1a;&#xffe5;0.20 &#xff08;英文操作系统结果&#xff1a;$0.20&#xff09; 默认格…

8步教你打开Android之门 NDK入门教程

这是一篇Android NDK开发的入门教程&#xff0c;在这一教程结束后&#xff0c;你将创建你自己的项目&#xff0c;从Java代码简单地调用原生C语言代码。 本文为一篇外文翻译&#xff0c;我们将介绍如何学习安装 Android NDK 并开始使用它。在这一教程结束后&#xff0c;你将创建…

升级nginx,查看已经安装的模块,并隐藏或者修改版本号

升级前&#xff0c;查看已经安装的版本以及模块[rootmail ~]# /opt/nginx/sbin/nginx -Vnginx version: nginx/0.5.34 built by gcc 3.4.6 20060404 (Red Hat 3.4.6-3)configure arguments: --prefix/opt/nginx --sbin-path/opt/nginx/sbin/nginx --conf-path/opt/nginx/conf/n…

SpringCloudAlibaba微服务docker容器打包和部署示例实战

Python微信订餐小程序课程视频 https://blog.csdn.net/m0_56069948/article/details/122285951 Python实战量化交易理财系统 https://blog.csdn.net/m0_56069948/article/details/122285941 概述 我们使用前面《SpringCloudAlibaba注册中心与配置中心之利器Nacos实战与源码…

MongoVUE的Collections数据不显示的解决方法

问题描述&#xff1a; 使用 mongoDB数据库&#xff0c; 数据添加成功了&#xff0c;使用命令行能查询出来&#xff0c;但在MongoVUE 中数据却不显示 (我使用的是 mongoDB 3.4 的版本) 原因&#xff1a;引擎问题&#xff0c;只要降到2.X版本就可以显示了     3.x默认是wire…

(3)[wp7数据存储] WP7 IsolatedStorage系列篇——通过XmlSerializer读写XML文件 [复制链接]...

发表于 2012-5-17 15:51:07 |只看该作者 |倒序浏览 分享到&#xff1a;本帖最后由 agameboy 于 2012-5-17 17:08 编辑这一篇我们会通过XmlSerializer读写XML文件&#xff0c;跟多的相关文章请参考WP7 IsolatedStorage系列篇&#xff01;需要的命名空间&#xff1a;using System…

SaltStack WEB UI Halite初体验

闲来无聊&#xff0c;话说saltstack webui halite还一直没玩&#xff0c;于是就凑今天体验一把&#xff1b;很多尝鲜的同学都说halite的功能较少&#xff0c;而其也正符合其说明console&#xff0c;不过其UI我还是蛮喜欢的&#xff0c;个人觉得比较清新简洁、挺好下面就来安装体…

Envoy熔断限流实践(二)Rainbond基于RLS服务全局限流

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

UV坐标

1.什么是uv坐标 所有的图象文件都是二维的一个平面。水平方向是U&#xff0c;垂直方向是V&#xff0c;通过这个平面的&#xff0c;二维的UV坐标系。我们可以定位图象上的任意一个象素。但是一个问题是如何把这个二维的平面贴到三维的NURBS表面和多边形表面呢&#xff1f; 对于N…

再说WCF Data Contract KnownTypeAttribute

WCF 中的序列化是用DataContractSerializer,所有被[DataContract]和[DataMemeber]标记的类和属性会被DataContractSerializer序列化。在WCF中使用Contract模式来分辨和指定序列化/反序列化的类型&#xff0c;它是通过http://xmlns/Class这样的命名空间来标识这个序列化的对象的…

pyinotify结合ftplib自动上传新建的文件

应用场景&#xff1a;从国内往国外上传&#xff0c;因国际带宽影响&#xff0c;速度很慢&#xff0c;于是做了一个中转FTP&#xff0c;而自动上传需求也就诞生了。代码地址&#xff1a;https://github.com/coocla/linux/blob/master/ftp/autoupload_ftp.py sftp类型&#xff1…

EFCore 的 DbFirst 模式

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

PHP读取sphinx实例

阅读原文&#xff1a;http://yzswyl.cn/blread-1611.html 1.未采用mysql二进制网络协议的代码&#xff1a; //检查sphinx是否能连接&#xff0c;不能重试两次&#xff0c;能则连接,不用mysql协议,仅供参考 function checkSphinxNoMysql() {$flag true;$retries 0;while ( $fl…

linux使用flock解决crontab任务冲突

Linux的crontab最小的间隔是每分钟执行一次&#xff0c;但是如果在这一分钟之内&#xff0c;之前的命令并没有执行完成呢&#xff1f;这样就会产生冲突。接下来我介绍一个解决冲突的办法&#xff0c;那就是linux的flock文件锁. 格式&#xff1a; flock [-sxun][-w #] fd# flock…

golang bufio解析

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

TAppEncoder的main函数

TAppEncoder是编码器工程&#xff0c;完成视频序列的编码。 运行时&#xff0c;首先调用encmain.cpp中的main函数 main函数中完成的工作主要有初始化encoder类&#xff0c;解析cfg文件&#xff0c;然后调用TAppEncTop::encode函数进入下一层&#xff0c;并且对编码过程进行计时…

【mq】从零开始实现 mq-01-生产者、消费者启动

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

远程连接Ubuntu桌面配置

1、打开终端&#xff1a;依次安装 sudo apt-get install xrdpsudo apt-get install vnc4server tightvncserversudo apt-get install xubuntu-desktop 2、安装完&#xff1a;xubuntu-desktop之后&#xff0c;做如下配置以及启动 roothd-slave2:jvm# echo "xfce4-session&q…

【大话云原生】微服务篇-五星级酒店的服务方式

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

从C#到TypeScript - Generator

从C#到TypeScript - Generator 上篇讲了Promise&#xff0c;Promise的执行需要不停的调用then&#xff0c;虽然比callback要好些&#xff0c;但也显得累赘。所以ES6里添加了Generator来做流程控制&#xff0c;可以更直观的执行Promise&#xff0c;但终级方案还是ES7议案中的asy…