当前位置: 首页 > news >正文

Vue3 reactive响应式原理详解

Reactive响应式原理

文件路径:core-main\packages\reactivity\src

核心方法

reactive

  • 文件路径:core-main\packages\reactivity\src\reactive.ts
  • 显然下一步要看createReactiveObject是怎么实现的。
  • 此处传参注意到还有mutableHandlers、mutableCollectionHandlers,后续再讨论mutableHandlers
import {
  mutableHandlers,
  readonlyHandlers,
  shallowReactiveHandlers,
  shallowReadonlyHandlers
} from './baseHandlers'

export const reactiveMap = new WeakMap<Target, any>()

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

createReactiveObject

  • 文件路径:core-main\packages\reactivity\src\reactive.ts
  • 作用:创建响应式对象。
  • 参数:
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>, // Object、Array使用这个作为handler
  collectionHandlers: ProxyHandler<any>, // Map、Set、WeakMap、WeakSet使用这个作为handler
  proxyMap: WeakMap<Target, any>
  • 返回:proxy实例或target。
  • 完整的createReactiveObject源码
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}
  • 执行过程:
  1. 如果目标值不为对象类型(即非数组或对象)则直接返回目标值。
// 补充utils
export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object'

// 源码
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  1. 检查是否经过了响应式,响应式处理后target会多一个__v_raw属性(raw生的,即原始数据),用于存放原始数据。
    • 补充:有时不需要追踪和触发更新视图,通过toRaw获取__v_raw属性可以获得原数据。
// toRaw
export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW] // 读取原始数据__v_raw
  return raw ? toRaw(raw) : observed
}
  // 检查是否已经过响应式处理
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  1. 每生成一个proxy都会将目标值和target映射到全局变量proxyMap:Map,防止重复new一个proxy实例。因此,下一步需要检查是否有对应的proxy,proxyMap.get(target),存在则获取并返回,无则new一个实例再映射到proxyMap上,最后再返回。
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
  • 此处仅讨论target为Array或Object的情况,此时handlers采用的是baseHandlers,即接下来解析的mutableHandlers对象。

mutableHandlers对象(baseHandlers)

  • 路径:core-main\packages\reactivity\src\baseHandlers.ts
  • 作用:作为new Proxy时作为第二个参数作为handlers传入(此处需要自行补充proxy知识点)。
const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
createGetter
  • 功能:返回一个get函数,通过传入不同的参数可以生成不同的getter。
  • 完整的createGetter源码
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)

    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
  • getter的执行过程
  1. 先特殊处理一些属性的访问(这些属性对优化或避免进一步响应式处理起到作用)。
    • ReactiveFlags.IS_REACTIVE即__v_isReactive是否为响应式数据,跟isReadonly相反,默认返回true。
    • ReactiveFlags.IS_READONLY即__v_isReadonly,不赘述
    • ReactiveFlags.IS_SHALLOW即__v_isShallow,true则该数据不做深层次响应式。
    • ReactiveFlags.RAW即__v_raw,返回原始数据。
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }
  1. 如果是数组,key则为对应的索引,能访问到的话直接返回对应的项。
    • 此处用使用arrayInstrumentations(数组工具)来代理数组处理。
    • arrayInstrumentations本质是一个对象,对象上有数组的方法’includes’, ‘indexOf’, ‘lastIndexOf’,‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’,再实现原数组的功能上实现追踪依赖的功能。
    const targetIsArray = isArray(target)

    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
  1. 非只读类型的对象,会触发track取追踪数据,收集依赖。此处track是依赖追踪的关键步骤,下边会展开讲。
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
  1. 获取get的结果res,对特殊类型进行特殊返回,如果当前访问的值为对象,则需要递归进行处理。
	const res = Reflect.get(target, key, receiver)
	if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }
    
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res

createSetter

  • 功能:返回一个set函数,通过传入不同的参数可以生成不同的setter。
  • 完整的createSetter源码
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
    if (!shallow) {
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}
  • setter的执行过程:
  1. 原值为只读类型 或 原值为ref处理的值但赋的新值不是一个ref,不让修改其值,return false。
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
  1. 针对shallow(浅层监听的进行特殊处理)
    if (!shallow) {
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
  1. 数组类型,要判断其索引值(即key)是否存在,确定trigger触发ADD还是SET。对象则直接SET即可。
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }

track

  • 描述:一般在响应式数据被获取时会自动触发,用于收集依赖Dep。
  • 完整track源码
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    trackEffects(dep, eventInfo)
  }
}
  • 过程:
  1. 查看此处获取是否需要收集依赖,收集的标志shouldTrack && activeEffect
    • 此处activeEffect: ReactiveEffect | undefined是个全局变量,默认是未赋值的。当有DOM节点或函数访问到该数据时,activeEffect才会有值,此时才会需要进行依赖收集。
    • activeEffect有值时,值仅可能是ReactiveEffect的实例。Class ReactiveEffect是依赖收集和触发重要的媒介。
  1. 全局定义一个targetMap: Map,通过target可以快速获取的一个depsMap。利用depsMap可以通过key快去获取Deps即一个Set。
// 数据结构如下
targerMap: Map {
	target1 = depsMap {
		key1: Deps即Set,
		key2 = Set {
			0: ReactiveEffect实例,
			1: ReactiveEffect实例
		}
    },
    target2...
}
  1. 调用trackEffects将当前的activeEffect放入到Deps中,完成依赖收集的过程。
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    // 重点在以下两行
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack({
        effect: activeEffect!,
        ...debuggerEventExtraInfo!
      })
    }
  }
}

trigger

  • 跟track类似,先找到当前的target对应的depsMap,再用key找到对应的Deps。
  • 根据trigger类型CLEAR、SET或ADD,将多个Deps合并成一个数组或将单个Deps传给triggerEffects进行调用。
  • 完整的trigger源码:
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    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)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

triggerEffects
  • 由于dep可以是单个deps类型为Set或者多个deps组合成数组,因此统一处理成数组。在循环调用triggerEffect去执行即可。
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}
triggerEffect
  • 调用ReactiveEffect.run,实现数据或视图更新。
function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

class ReactiveEffect

  • 核心属性:ative默认为true,deps实现多对多的关系,fn视图或数据触发更新的函数。
  • 核心方法:run
    • 由于active默认为true,新实例调用run时会追踪依赖。
    • false时不会追踪依赖,只需要调用fn触发更新。
    • 将当前实例赋值给全局变量reactiveEffect。这个变量的作用在于上边讲track方法时,要根据当前reactiveEffect是否有值来确定是否track追踪依赖。
run() {
 	if (!this.active) {
      return this.fn()
    }
    ...
    activeEffect = this
    return this.fn()
    ...
}

effect

  • 重点
    • 假设响应式数据A,所有的视图或数据依赖到A数据时,可以称这些视图或数据为依赖,即dep。
    • 当这些dep或数据使用到数据A时,会调用effect来生成一个ReactiveEffect实例,生成实例的时候传入当前数据或视图的更新函数fn。const _effect = new ReactiveEffect(fn)
    • 执行_effect.runreactiveEffect.run
	// 核心功能
	const _effect = new ReactiveEffect(fn)
	...
	_effect.run()
    • run做了两件事
      • 将当前实例赋值给全局变量reactiveEffect
      • 调用fn。
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    trackEffects(dep, eventInfo)
  }
}
  • 完整effect源码:
export function effect<T = any>(
  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)
  }
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

完整的过程

  • 例子:obj是一个经过reactive处理的对象。createOrUpdateDom是一个模拟依赖追踪、控制视图变化的更新函数。
const obj = reactive({
  info: {
    aa: "1",
    bb: "2",
  },
});

effect(createOrUpdateDom);

setTimeout(() => {
  obj.info.aa = "33333";
}, 1000);

function createOrUpdateDom(value: string = obj.info.aa) {
  var $div = document.querySelector("div") || document.createElement("div");
  $div.innerText = value;
  document.body.appendChild($div);
}
  1. 先对obj进行reactive。生成obj各层级的baseHandler(包含getter和setter),baseHandler作为第二个参数传入生成proxy实例
  2. 调用effect传入fn即createOrUpdateDom。
  • 生成一个ReactiveEffect实例,再调用run
function effect(fn) {
	const _effect = new ReactiveEffect(fn)
	_effect.run()
}
  • ReactiveEffect.run将当前实例赋值给全局变量reactiveEffect,再执行fn
run() {
    // 此处的this指向ReactiveEffect实例
	reactiveEffect = this
	return fn()
}
  1. 执行fn即自定义的更新函数createOrUpdateDom。访问到了obj.info.aa。
function createOrUpdateDom(value: string = obj.info.aa) {...}
  1. 触发obj.info.aa的getter,getter的过程中又会触发track
  • 注:此处不考虑触发obj.info的getter(比较复杂的情况)
function createGetter(...) {
	return function get(...) {
		track(...)
		return ...
	}
}
  1. 第2步中ReactiveEffect.run调用后将当前ReactiveEffect实例赋值给全局变量reactiveEffect。track中访问到该变量,意味着该数据需要追踪
  • 追踪过程:找到obj.info.aa的依赖集合deps(类型为Set),将依赖放进去,完成数据的追踪,即依赖的搜集
    • 用target从targetMap中找到depsMap
    • 用key从depsMap中找到对应的deps
    • 调用trackEffect,执行deps.add(reactiveEffect)
  1. 一秒后setTimeout起作用了,修改了obj.info.aa。此时会触发baseHandler中的setter
  2. setter修改数据后会触发trigger,trigger会根据触发的类型(ADD、SET、CLEAR等)处理出要更新的依赖deps或数组ReactiveEffect[],调用triggerEffects(deps)
  3. triggerEffects会根据传入参数deps的类型,将单个依赖(Set类型)统一处理成数组。再遍历调用triggerEffect
function triggerEffects(deps: Dep | ReactiveEffect[]) {
	const effects = Array.isArray(deps) ? deps : [...deps]
	for (let effect of effects) triggerEffect(effect)
}
  1. triggerEffect传入的参数是ReactiveEffect的实例,调用ReactiveEffect.run()
function triggerEffect(effect: ReactiveEffect) {
  ...
  effect.run()
  ...
}
  1. run则会继续调用fn即createOrUpdateDom,访问最新的obj.info.aa,完成视图更新。

相关文章:

  • 基于HAL库的STM32的串口DMA发送数据(解决只发送一次数据)及DMA+空闲中断接受数据
  • 【图灵MySQL】SQL底层执行原理详解
  • 【电磁】基于Matlab求解瞬变电磁TEM层状介质正演
  • Unity使用新输入系统InputSystem制作飞机大战Demo(敌人生成管理器UI场景跳转)
  • Python 文件存储:pickle 和 json 库的使用
  • 【推荐收藏】matplotlib 制作的动态条形图其实很好看
  • 计算机组成原理 ------ 存储系统(1)
  • Open3D (C++) 基于投影点密度的建筑物立面提取
  • SpringCloud Alibaba系列 Nacos(一)
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • JS的精髓,事件详解
  • 高等数学(第七版)同济大学 习题8-6 个人解答
  • 【Linux】进程地址空间
  • 【计算机组成原理】输入/输出系统(四)—— I/O方式
  • 让GPU跑的更快
  • 【译】JS基础算法脚本:字符串结尾
  • Angular4 模板式表单用法以及验证
  • express + mock 让前后台并行开发
  • Java教程_软件开发基础
  • JS实现简单的MVC模式开发小游戏
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • MobX
  • mockjs让前端开发独立于后端
  • mysql外键的使用
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • nodejs调试方法
  • Node项目之评分系统(二)- 数据库设计
  • PAT A1050
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • python学习笔记 - ThreadLocal
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • use Google search engine
  • 关于字符编码你应该知道的事情
  • 跨域
  • 问题之ssh中Host key verification failed的解决
  • 最近的计划
  • 组复制官方翻译九、Group Replication Technical Details
  • ​香农与信息论三大定律
  • (5)STL算法之复制
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (转) 深度模型优化性能 调参
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET Core 项目指定SDK版本
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?
  • .NET 材料检测系统崩溃分析
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .net 验证控件和javaScript的冲突问题
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题