注: 为了直观的看到 Vue3 的实现逻辑, 本文移除了边缘情况处理、兼容处理、DEV环境的特殊逻辑等, 只保留了核心逻辑

vue-next/reactivity 实现了 Vue3 的响应性, reactivity 提供了以下接口:

export {
ref, // 代理基本类型
shallowRef, // ref 的浅代理模式
isRef, // 判断一个值是否是 ref
toRef, // 把响应式对象的某个 key 转为 ref
toRefs, // 把响应式对象的所有 key 转为 ref
unref, // 返回 ref.value 属性
proxyRefs,
customRef, // 自行实现 ref
triggerRef, // 触发 customRef
Ref, // 类型声明
ToRefs, // 类型声明
UnwrapRef, // 类型声明
ShallowUnwrapRef, // 类型声明
RefUnwrapBailTypes // 类型声明
} from './ref'
export {
reactive, // 生成响应式对象
readonly, // 生成只读对象
isReactive, // 判断值是否是响应式对象
isReadonly, // 判断值是否是只读对象
isProxy, // 判断值是否是 proxy
shallowReactive, // 生成浅响应式对象
shallowReadonly, // 生成浅只读对象
markRaw, // 让数据不可被代理
toRaw, // 获取代理对象的原始对象
ReactiveFlags, // 类型声明
DeepReadonly // 类型声明
} from './reactive'
export {
computed, // 计算属性
ComputedRef, // 类型声明
WritableComputedRef, // 类型声明
WritableComputedOptions, // 类型声明
ComputedGetter, // 类型声明
ComputedSetter // 类型声明
} from './computed'
export {
effect, // 定义副作用函数, 返回 effect 本身, 称为 runner
stop, // 停止 runner
track, // 收集 effect 到 Vue3 内部的 targetMap 变量
trigger, // 执行 targetMap 变量存储的 effects
enableTracking, // 开始依赖收集
pauseTracking, // 停止依赖收集
resetTracking, // 重置依赖收集状态
ITERATE_KEY, // 固定参数
ReactiveEffect, // 类型声明
ReactiveEffectOptions, // 类型声明
DebuggerEvent // 类型声明
} from './effect'
export {
TrackOpTypes, // track 方法的 type 参数的枚举值
TriggerOpTypes // trigger 方法的 type 参数的枚举值
} from './operations'

一、名词解释

  • target: 普通的 JS 对象

  • reactive: @vue/reactivity 提供的函数, 接收一个对象, 并返回一个 代理对象, 即响应式对象

  • shallowReactive: @vue/reactivity 提供的函数, 用来定义浅响应对象

  • readonly:@vue/reactivity 提供的函数, 用来定义只读对象

  • shallowReadonly: @vue/reactivity 提供的函数, 用来定义浅只读对象

  • handlers: Proxy 对象暴露的钩子函数, 有 get()set()deleteProperty()ownKeys() 等, 可以参考MDN

  • targetMap: @vue/reactivity 内部变量, 存储了所有依赖

  • effect: @vue/reactivit 提供的函数, 用于定义副作用, effect(fn, options) 的参数就是副作用函数

  • watchEffect: @vue/runtime-core 提供的函数, 基于 effect 实现

  • track: @vue/reactivity 内部函数, 用于收集依赖

  • trigger: @vue/reactivity 内部函数, 用于消费依赖

  • scheduler: effect 的调度器, 允许用户自行实现

二、Vue3 实现响应式的思路

先看下边的流程简图, 图中 Vue 代码的功能是: 每隔一秒在 idBoxdiv 中输出当前时间

在开始梳理 Vue3 实现响应式的步骤之前, 要先简单理解 effect, effect 是响应式系统的核心, 而响应式系统又是 Vue3 的核心

上图中从 tracktargetMap 的黄色箭头, 和从 targetMaptrigger 的白色箭头, 就是 effect 函数要处理的环节

effect 函数的语法为:

effect(fn, options)

effect 接收两个参数, 第一个必填参数 fn 是副作用函数

第二个选填 options 的参数定义如下:

export interface ReactiveEffectOptions {
lazy?: boolean // 是否延迟触发 effect
scheduler?: (job: ReactiveEffect) => void // 调度函数
onTrack?: (event: DebuggerEvent) => void // 追踪时触发
onTrigger?: (event: DebuggerEvent) => void // 触发回调时触发
onStop?: () => void // 停止监听时触发
allowRecurse?: boolean // 是否允许递归
}

下边从流程图中左上角的 Vue 代码开始

第 1 步

通过 reactive 方法将 target 对象转为响应式对象, reactive 方法的实现方法如下:

import { mutableHandlers } from './baseHandlers'
import { mutableCollectionHandlers } from './collectionHandlers' const reactiveMap = new WeakMap<Target, any>()
const readonlyMap = new WeakMap<Target, any>() export function reactive(target: object) {
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
} function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const targetType = getTargetType(target) // 先忽略, 上边例子中, targetType 的值为: 1
const proxy = new Proxy(
target,
targetType === 2 ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}

reactive 方法携带 target 对象和 mutableHandlersmutableCollectionHandlers 调用 createReactiveObject 方法, 这两个 handers 先忽略

createReactiveObject 方法通过 reactiveMap 变量缓存了一份响应式对象, reactiveMapreadonlyMap 变量是文件内部的变量, 相当于文件级别的闭包变量

其中 targetType 有三种枚举值: 0 代表不合法, 1 代表普通对象, 2 代表集合, 图中例子中, targetType 的值为 1, 对于 { text: '' } 这个普通对象传进 reactive() 方法时, 使用 baseHandlers 提供的 mutableHandlers

最后调用 Proxy 方法将 target 转为响应式对象, 其中 "响应" 体现在 handers 里, 可以这样理解: reactive = Proxy (target, handlers)

第 2 步

mutableHandlers 负责挂载 getsetdeletePropertyhasownKeys 这五个方法到响应式对象上

其中 gethasownKeys 负责收集依赖, setdeleteProperty 负责消费依赖

响应式对象的 gethasownKeys 方法被触发时, 会调用 createGetter 方法, createGetter 的实现如下:

function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}

{ text: '' } 这个普通JS对象传到 createGetter 时, key 的值为: text, res 的值为: String 类型, 如果 res 的值为 Object 类型则会递归调用, 将 res 转为响应式对象

createGetter 方法的目的是触发 track 方法, 对应本文的第 3 步

响应式对象的 setdeleteProperty 方法被触发时, 会调用 createSetter 方法, createSetter 的实现如下:

function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
const result = Reflect.set(target, key, value, receiver)
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
return result
}
}

createSetter 方法的目的是触发 trigger 方法, 对应本文的第 4 步

第 3 步

这一步是整个响应式系统最关键的一步, 即我们常说的依赖收集, 依赖收集的概念很简单, 就是把 响应式数据副作用函数 建立联系

文章一开始流程图的例子中, 就是把 target 对象和 document.getElementById("Box").innerText = date.text; 这个副作用函数建立关联, 这个 "关联" 指的就是上边提到的 targetMap 变量, 后边会详细描述一下 targetMap 对象的结构

第 2 步介绍了 createGetter 方法的核心是调用 track 方法, track 方法由 @/vue/reativity/src/effect.ts 提供, 下面看一下 track 的实现:

const targetMap = new WeakMap<any, KeyToDepMap>()

// target: { text: '' }
// type: get
// key: text
export function track(target: object, type: TrackOpTypes, key: unknown) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}

track 方法我们能看到 targetMap 这个闭包变量上储存了所有的 effect, 换句话说是把能影响到 target 的副作用函数收集到 targetMap 变量中

targetMap 是个 WeakMap, WeakMap 和 Map 的区别在于 WeakMap 的键只能是对象, 用 WeakMap 而不用 Map 是因为 Proxy 对象不能代理普通数据类型

targetMap 的结构:

const targetMap = {
[target]: {
[key1]: [effect1, effect2, effect3, ...],
[key2]: [effect1, effect2, effect3, ...]
}
}

{ text: '' } 这个target 传进来时, targetMap 的结构是:

// 上边例子中用来在 id 为 Box 的 div 中输出当前时间的副作用函数
const effect = () => {
document.getElementById("Box").innerText = date.text;
}; const target = {
"{ text: '' }": {
"text": [effect]
}
}

举三个例子, 来分析一下 targetMap 的结构, 第一个例子是多个 target 情况:

<script>
import { effect, reactive } from "@vue/reactivity"; const target1 = { language: "JavaScript"};
const target2 = { language: "Go"};
const target3 = { language: "Python"};
const r1 = reactive(target1);
const r2 = reactive(target2);
const r3 = reactive(target3); // effect1
effect(() => {
console.log(r1.language);
}); // effect2
effect(() => {
console.log(r2.language);
}); // effect3
effect(() => {
console.log(r3.language);
}); // effect4
effect(() => {
console.log(r1.language);
console.log(r2.language);
console.log(r3.language);
});
</script>

这种情况下 targetMap 的构成是:

const effect1 = () => {
console.log(r1.language);
};
const effect2 = () => {
console.log(r2.language);
};
const effect3 = () => {
console.log(r3.language);
};
const effect4 = () => {
console.log(r1.language);
console.log(r2.language);
console.log(r3.language);
}; const targetMap = {
'{"language":"JavaScript"}': {
"language": [effect1, effect4]
},
'{"language":"Go"}': {
"language": [effect2, effect4]
},
'{"language":"Python"}': {
"language": [effect3, effect4]
}
}

第二个例子是单个 target 多个属性时:

import { effect, reactive } from "@vue/reactivity";
const target = { name: "rmlzy", age: "27", email: "rmlzy@outlook.com"};
const user = reactive(target); effect(() => {
console.log(user.name);
console.log(user.age);
console.log(user.email);
});

这种情况下 targetMap 的构成是:

const effect = () => {
console.log(user.name);
console.log(user.age);
console.log(user.email);
}; const targetMap = {
'{"name":"rmlzy","age":"27","email":"rmlzy@outlook.com"}': {
"name": [effect],
"age": [effect],
"email": [effect]
}
}

第三个例子是多维对象时:

import { effect, reactive } from "@vue/reactivity";
const target = {
name: "rmlzy",
skills: {
frontend: ["JS", "TS"],
backend: ["Node", "Python", "Go"]
}
};
const user = reactive(target); // effect1
effect(() => {
console.log(user.name);
}); // effect2
effect(() => {
console.log(user.skills);
}); // effect3
effect(() => {
console.log(user.skills.frontend);
}); // effect4
effect(() => {
console.log(user.skills.frontend[0]);
});

这种情况下 targetMap 的构成是:

const effect1 = () => {
console.log(user.name);
};
const effect2 = () => {
console.log(user.skills);
};
const effect3 = () => {
console.log(user.skills.frontend);
};
const effect4 = () => {
console.log(user.skills.frontend[0]);
}; const targetMap = {
'{"name":"rmlzy","skills":{"frontend":["JS","TS"],"backend":["Node","Python","Go"]}}': {
"name": [effect1],
"skills": [effect2, effect3, effect4]
},
'{"frontend":["JS","TS"],"backend":["Node","Python","Go"]}': {
"frontend": [effect3, effect4]
}
}

第 4 步

第 3 步的目的是收集依赖, 这一步的目的是消费依赖

这里要注意, 只有当 target 代理对象的 get 方法被触发时, 才会真正执行 track, 换句话说, 没有地方需要 get target 对象时, target 没有依赖, 也就没有收集依赖一说

下边的例子中只是把 target 转换为了响应式对象, 并没有触发依赖收集, targetMap 是空的

const target = {"text": ""};
const date = reactive(target);
effect(() => {
date.text = new Date().toString();
});

第 2 步介绍了 createSetter 方法的核心是调用 trigger 方法, trigger 方法由 @/vue/reativity/src/effect.ts 提供, 下面看一下 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
}
const effects = new Set<ReactiveEffect>()
if (isMap(target)) {
effects.add(depsMap.get(ITERATE_KEY))
}
const run = (effect: ReactiveEffect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}

trigger 的实现很简单, 先把 target 相关的 effect 汇总到 effects 数组中, 然后调用 effects.forEach(run) 执行所有的副作用函数

再回顾一下 effect 方法的定义: effect(fn, options), 其中 options 有个可选属性叫 scheduler, 从上边 run 函数也可以看到 scheduler 的作用是让用户自定义如何执行副作用函数

第 5 步

又回到了本文最开始讲的 effect, effect 函数的实现如下:

export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}

effect 的核心是调用 createReactiveEffect 方法

可以看到 options.lazy 默认为 false 会直接执行 effect, 当设置为 true 时, 会返回 effect 由用户手动触发

createReactiveEffect 函数的实现如下:

const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}

首先定义了 effect 是个普通的 function, 先看后边 effect 函数挂载的属性:

effect.id = uid++ // 自增ID, 每个 effect 唯一的ID
effect.allowRecurse = !!options.allowRecurse // 是否允许递归
effect._isEffect = true // 特殊标记
effect.active = true // 激活状态
effect.deps = [] // 依赖数组
effect.raw = fn // 缓存一份用户传入的副作用函数
effect.options = options // 缓存一份用户传入的配置

isEffect 函数用来判断值是否是 effect, 就是根据上边 _isEffect 变量判断的, isEffect 函数实现如下:

function isEffect(fn) {
return fn && fn._isEffect === true;
}

再来看 effect 的核心逻辑:

cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}

effectStack 用数组实现栈, activeEffect 是当前生效的 effect

先执行 cleanup(effect):

function cleanup(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}

cleanup 的目的是清空 effect.deps, deps 是持有该 effect 的依赖数组, deps 的结构如下

清除完依赖后, 开始重新收集依赖, 把当前 effect 追加到 effectStack, 将 activeEffect 设置为当前的 effect, 然后调用 fn 并且返回 fn() 的结果

第 4 步提过到: "只有当 target 代理对象的 get 方法被触发时, 才会真正执行 track", 至此才是真正的触发了 target代理对象的 get 方法, 执行了track 方法然后收集到了依赖

等到 fn 执行结束, finally 阶段, 把当前的 effect 弹出, 恢复 effectStack 和 activeEffect, Vue3 整个响应式的流程到此结束

三、知识点

activeEffect 的作用

我的理解是为了暴露给 onTrack 方法, 来整体看一下 activeEffect 出现的地方:

let activeEffect;

function effect(fn, options = EMPTY_OBJ) {
const effect = createReactiveEffect(fn, options);
return effect;
} function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
// 省略部分代码 ...
try {
activeEffect = effect;
return fn();
}
finally {
activeEffect = effectStack[effectStack.length - 1];
}
};
// 省略部分代码 ...
return effect;
} function track(target, type, key) {
if (activeEffect === undefined) {
return;
}
let dep = targetMap.get(target).get(key); // dep 是存储 effect 的 Set 数组
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
if (activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
});
}
}
}
  1. fn 执行前, activeEffect 被赋值为当前 effect

  2. fn 执行时的依赖收集阶段, 获取 targetMap 中的 dep (存储 effect 的 Set 数组), 并暴露给 options.onTrack 接口

effect 和 stop

@vue/reactivity 提供了 stop 函数, effect 可以被 stop 函数终止

const obj = reactive({ foo: 0 });

const runner = effect(() => {
console.log(obj.foo);
}); // effect 被执行一次, 输出 0 // obj.foo 被赋值一次, effect 被执行一次, 输出 1
obj.foo ++; // 停止 effect
stop(runner); // effect 不会被触发, 无输出
obj.foo ++;

watchEffect 和 effect

  1. watchEffect 来自 @vue/runtime-core, effect 来自 @vue/reactivity
  2. watchEffect 基于 effect 实现
  3. watchEffect 会维护与组件实例的关系, 如果组件被卸载, watchEffect 会被 stop, 而 effect 不会被 stop

watchEffect 和 invalidate

watchEffect 接收的副作用函数, 会携带一个 onInvalidate 的回调函数作为参数, 这个回调函数会在副作用无效时执行

watchEffect(async (onInvalidate) => {
let valid = true;
onInvalidate(() => {
valid = false;
});
const data = await fetch(obj.foo);
if (valid) {
// 获取到 data
} else {
// 丢弃
}
});

ref

JS数据类型:

  • 基本类型: String、Number、Boolean、Null、Undefined、Symbol
  • 引用数据类型: Object、Array、Function

因为 Proxy 只能代理对象, reactive 函数的核心又是 Proxy, 所以 reactive 不能代理基本类型

对于基本类型需要用 ref 函数将基本类型转为对象:

class RefImpl<T> {
private _value: T public readonly __v_isRef = true constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
} get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
} set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}

其中 __v_isRef 参数用来标志当前值是 ref 类型, isRef 的实现如下:

export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}

这样做有个缺点, 需要多取一层 .value:

const myRef = ref(0);
effect(() => {
console.log(myRef.value);
});
myRef.value = 1;

这也是 Vue ref 语法糖提案的原因, 可以参考 如何评价 Vue 的 ref 语法糖提案?

reactive 和 shallowReactive

shallowReactive 用来定义浅响应数据, 深层次的对象值是非响应式的:

const target = {
foo: {
bar: 1
}
};
const obj = shallowReactive(target); effect(() => {
console.log(obj.foo.bar);
}); obj.foo.bar = 2; // 无效, reactive 则有效
obj.foo = { bar: 2 }; // 有效

readonly 和 shallowReadonly

类似 shallowReactive, 深层次的对象值是可以被修改的

markRaw 和 toRaw

markRaw 的作用是让数据不可被代理, 所有携带 __v_skip 属性, 并且值为 true 的数据都会被跳过:

export function markRaw<T extends object>(value: T): T {
def(value, ReactiveFlags.SKIP, true)
return value
}

toRaw 的作用是获取代理对象的原始对象:

const obj = {};
const reactiveProxy = reactive(obj);
console.log(toRaw(reactiveProxy) === obj); // true

computed

const myRef = ref(0);
const myRefComputed = computed(() => {
return myRef.value * 2;
});
effect(() => {
console.log(myRef.value * 2);
});

myRef 值变化时, computed 会执行一次, effect 会执行一次

myRef 值未变化时, computed 不会执行, effect 依旧会执行


如果你有问题欢迎留言和我交流, 阅读原文

Vue3 源码之 reactivity的更多相关文章

  1. Typescript | Vue3源码系列

    TypeScript 是开源的,TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript.编译出来的 JavaScript 可以运行在任何浏览器上.TypeS ...

  2. vue3源码难学,先从petite-vue开始吧

    如今这个世道,作为一个有几年工作经验的前端,不学点框架源码都感觉要被抛弃了,react或vue要能吹吹牛吧,最好能造个轮子,听说vue3源码好学点,那么学学vue3,但是学起来还是那么费劲,感觉快放弃 ...

  3. vue3源码node的问题

    下载vue3源码后,下载依赖时,node的版本需要在10.0.0以上,并且不同的vue3里面的插件的配置对版本依赖还不同,14.0.0以上的版本基本都不支持win7了, win7系统可以安装12.0. ...

  4. Vue3源码分析之 Ref 与 ReactiveEffect

    Vue3中的响应式实现原理 完整 js版本简易源码 在最底部 ref 与 reactive 是Vue3中的两个定义响应式对象的API,其中reactive是通过 Proxy 来实现的,它返回对象的响应 ...

  5. Vue3源码分析之Diff算法

    Diff 算法源码(结合源码写的简易版本) 备注:文章后面有详细解析,先简单浏览一遍整体代码,更容易阅读 // Vue3 中的 diff 算法 // 模拟节点 const { oldVirtualDo ...

  6. Vue3源码解析(computed-计算属性)

    作者:秦志英 前言 上一篇文章中我们分析了Vue3响应式的整个流程,本篇文章我们将分析Vue3中的computed计算属性是如何实现的. 在Vue2中我们已经对计算属性了解的很清楚了,在Vue3中提供 ...

  7. Vue3源码分析之微任务队列

    参考资料:https://zh.javascript.info/microtask-queue#wei-ren-wu-dui-lie-microtaskqueue 简化版 Vue3 中的 微任务队列实 ...

  8. Vue3全局APi解析-源码学习

    本文章共5314字,预计阅读时间5-15分钟. 前言 不知不觉Vue-next的版本已经来到了3.1.2,最近对照着源码学习Vue3的全局Api,边学习边整理了下来,希望可以和大家一起进步. 我们以官 ...

  9. Vue3中的响应式对象Reactive源码分析

    Vue3中的响应式对象Reactive源码分析 ReactiveEffect.js 中的 trackEffects函数 及 ReactiveEffect类 在Ref随笔中已经介绍,在本文中不做赘述 本 ...

随机推荐

  1. 现代JavaScript—ES6+中的Imports,Exports,Let,Const和Promise

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://www.freecodecamp.org/news/learn-modern-jav ...

  2. Abp小试牛刀之 图片上传

    图片上传是很常见的功能,里面有些固定的操作也可以沉淀下来. 本文记录使用Abp vNext做图片上传的姿势. 本文的技术核心与Abp无关,Abp只是手段! 目标 上传图片----->预览图片-- ...

  3. AWS中国区使用https访问部署在S3上的网站

    问题描述 最近一个项目需要通过https的方式访问部署在S3上的网站,通过搜索引擎找到一篇文章,可以在AWS Global实现整个过程.但是目前AWS中国区有限制,CloudFront不能使用AWS ...

  4. request常用方法servlet初步

    1 package com.ycw.newservlet; 2 3 import java.io.IOException; 4 import javax.servlet.ServletExceptio ...

  5. 【Azure Application Insights】在Azure Function中启用Application Insights后,如何配置不输出某些日志到AI 的Trace中

    问题描述 基于.NET Core的Function App如果配置了Application Insights之后,每有一个函数被执行,则在Application Insights中的Logs中的tra ...

  6. docker容器中布置静态网站

    docker容器中布置静态网站(基于云服务器ubuntu系统) 服务器准备(ubuntu) docker nginx 静态网页制作 浏览器测试 服务器布置 这里推荐使用云服务器(阿里云.华为云.腾讯云 ...

  7. java中网络通信 Scoket

    在客户/服务器通信模式中,客户端需要主动建立与服务器连接的Socket,服务器端收到客户端的连接请求,也会创建与客户端连接的Socket.Socket可以看做是通信连接两端的收发器,客户端和服务店都通 ...

  8. Beta冲刺--总结随笔

    一.项目预期计划 时间 (天) 预期计划 完成情况 1-2 登录注册页面美化 完成 3-5 完善寻/失物登记以及管理页面 完成 6-9 实现剩下的用户管理.我的账号等页面 50% 9-10 最终测试与 ...

  9. windows7 错误0xc00000ba;无法进入系统;

    事件背景:电脑windows7 错误0xc00000ba无法进系统:无法进入安全模式:无法进入最后一次正确配置: 事件处理:提示系统文件丢失,注册表异常:知乎有人提及更换WS2_32.DLL; 使用P ...

  10. 在vscode中配置sass savepath

    1.先在VSCode上面安装插件:Live Sass Compiler 2.创建好scss文件夹文件和css文件夹 3.然后在VSCode的控制台上打开Live sass watching模式(控制台 ...