Vue2与Vue3响应式原理对比
Vue2.x 响应式原理
Vue2.x 响应式:
- 实现原理
- 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截( 数据劫持 )
- 数组类型:通过重写数组方法,并作为拦截器挂载到数组对象与数组原型之间,来实现拦截。
- 存在问题
- 对于对象类型,直接操作对象新增属性或删除属性,界面不会更新,因为Vue无法监听到
- 对于数组类型,直接通过下标修改数组元素,界面不会更新。直接修改数组长度,也不会生效。Vue 同样无法监听到
- 解决办法:
- 通过 this.$set() 或 Vue.set() 来实现对于对象类型的属性新增。通过 this.$delete 来删除对象类型中的属性。
- 通过可以通过 this.$set() 或 Vue.set() 来实现对于数组类型的数组元素修改。也可以通过 调用拦截器中的数组方法来改变数组元素,例如:splice
Vue3.x的响应式
因为Vue3重新改写了底层响应式原理,那我们大胆猜测一下,Vue3 应该修复了 Vue2.x 版本的问题,也就是说 Vue3 能够直接监听到对于对象的新增属性或删除属性,同时还能直接通过数组下标来操作数组,或通过 length 属性直接改变数组长度,而不用依赖于 $set 方法、$delete 方法或 splice 方法。
新增或删除对象中的属性:
<template><button @click="addSex">点击新增 sex 属性</button><button @click="deleteName">点击删除 name 属性</button>
</template>let person = reactive({name: "al",
});function addSex() {person.sex = '男' // Proxy(Object){name:'al',sex:'男'}
}function deleteName() {delete person.name // Proxy(Object){sex:'男'}
}
新增或删除数组中的元素:由此可以证明,只要是能改变原数组的方法,在操作数组之后,都会被 Vue3 所监听到
let person = reactive({hobby:['吃饭','睡觉']
});// 新增第三个元素
function updateHobby() {person.hobby[2] = '打豆豆' // Proxy(Object) {hobby: ['吃饭', '睡觉']}
}//删除第一个元素
function deleteArr() {// 通过shift 删除第一个元素person.hobby.shift() // Proxy(Object) {hobby: ['睡觉', '打豆豆']}// 通过splice 删除第一个元素person.hobby.splice(0,1) // Proxy(Object) {hobby: ['睡觉', '打豆豆']}// 通过pop删除最后一个元素person.hobby.pop() // Proxy(Object) {hobby: ['吃饭','睡觉']}}
通过数组下标改变数组元素
let person = reactive({hobby:['吃饭','睡觉']
});//通过数组下标改变数组元素
function changeHobby (){person.hobby[0] = '学习' // Proxy(Object) {hobby: ['学习', '睡觉']}
}
通过length直接改变数组长度
function changeLength() {person.hobby.length = 1 //["吃饭"] 数组 length 变为1
}function changeLength() {person.hobby.length = 4 //[ "吃饭", "睡觉", null, null ] 数组 length 变为4
}
Vue3响应式方式区分
上面说了这么多Vue3响应式的使用方式,我们发现 Vue3 针对于 Vue2 的痛点,Vue3给出了解决办法,那我们现在来看一下,Vue3 是怎么解决这些问题的。
首先,Vue3 存在两种响应式转化的方式,分别是 ref()函数 和 reactive()函数。
ref() 函数能接收基本数据类型,也能接收引用类型的数据,所以Vue3推荐使用 ref() 函数来实现响应式。
reactive() 函数只能接受引用类型数据,不能转化基本类型数据。
所以 ref() 函数和 reactive()函数进行响应式转化的底层原理其实是不一样的。
ref()函数实现响应式
- 在调用 ref() 函数时,会先调用 createRef() 函数。
export function ref(value) {return createRef(value, false); }
- 然后在 createRef() 函数中 判断当前接收的参数是否为 ref 对象,如果是,则直接返回该响应式数据,避免重复转化,如果不是,则调用 new RefImpl() 构造函数生成 RefImpl 引用对象
// 接收两个参数, // 第一个参数 rawValue 是需要转化为 ref 的原始值, // 第二个参数 shallow 是一个布尔值,标识是否是浅层响应,如果为 true,则只处理表面层次的响应式,而不会递归处理嵌套对象function createRef(rawValue, shallow) {// 判断需要转化的数据是否已经是 ref 对象,如果是,则直接返回该数据,避免重复转化// 但是 isRef 函数并不会进行深度响应式判断,如果对一个深度响应式对象再次使用 ref 或 reactive可能会导致嵌套的代理对象,if (isRef(rawValue)) {return rawValue;}// 如果不是 ref 对象,则调用 RefImpl 构造函数生成新的 RefImpl 引用对象// 同时传递 rawValue 和 shallow 来初始化响应式数据以及确定相应深度return new RefImpl(rawValue, shallow); }
-
RefImpl 类:是 ref 对象的实际实现。主要包括
-
存储原始值以及响应值:_rawValue 存储原始值,_value存储响应值
-
响应式处理:通过 shallow 来决定是否进行深层响应式数据处理
-
依赖收集与分发:在 get 函数中通过
trackRefValue
函数来收集依赖。在set函数中通过triggerRefValue
函数通知依赖更新。 -
RefImpl 类解析
class RefImpl {private _value: any; // 用来存储响应值private _rawValue: any; // 用来存储原始值public dep?: Dep = undefined; // 用来收集分发依赖public readonly __v_isRef = true; //是否只读,暂不考虑// 接收 new RefImpl() 传递过来的 rawValue 和 shallow constructor(value, public readonly __v_isShallow: boolean) {// 判断是否需要深层响应,如果不用,直接返回 Value 值,如果需要深层响应,则调用 toRaw 函数解除 value 的响应式,将其转化为原始值,以保证后续的深层响应this._rawValue = __v_isShallow ? value : toRaw(value);// 判断是否需要深层响应,如果不用,则直接返回Value,不做响应式处理。如果需要深层响应,则调用 reactive 函数进行深层响应this._value = __v_isShallow ? value : reactive(value);}get value() {// 收集依赖trackRefValue(this);// 返回响应式数据return this._value;}set value(newVal) {// 将 newVal 转化为原始值,并于初始原始值比较,若不同,则准备更新数据,渲染页面,分发依赖if (hasChanged(toRaw(newVal), this._rawValue)) {//判断是否需要深层响应,如果不用,直接返回 newVal 值,如果需要深层响应,则调用 toRaw 函数解除 newVal 的响应式,将其转化为原始值,以保证后续的深层响应this._rawValue = this.__v_isShallow ? newVal : toRaw(newVal);// 判断是否需要深层响应,如果不用,则直接返回Value,不做响应式处理。如果需要深层响应,则调用 reactive 函数进行深层响应this._value = this.__v_isShallow ? newVal : reactive(newVal);// 分发依赖,通知更新triggerRefValue(this);}} }
-
trackRefValue() 函数:用来收集依赖
// 接收参数 ref ,也就是当前 refImpl 引用实例对象 function trackRefValue(ref) {// 判断当前是否处于依赖收集状态,在 Vue2.x 中,相当于 window.target 。一般用来判断当前是否有活跃的响应式副作用正在运行if (isTracking()) {// ref.dep 是 RefImpl 实例对象上的一个属性,相当于 Vue2.x中的 Dep 类,用来收集或分发依赖// 判断 ref.dep 是否存在。若存在则直接使用,若不存在,则通过 createDep 函数创建一个新的依赖集合并赋值给 ref.dep ,然后使用// 将当前活跃的副作用(effect)添加到 ref.dep 中,以便在将来 ref 值变化时能够触发这些副作用。trackEffects(ref.dep || (ref.dep = createDep()));} }
-
triggerRefValue()函数:用来分发依赖
function triggerRefValue(ref) {// ref.dep:依赖集合。如果存在依赖集合,则继续进行触发操作。if (ref.dep) {// 遍历并执行 ref.dep 中的所有副作用(effect),以响应 ref 值的变化。这个函数会通知所有依赖于 ref 值的副作用重新运行。类似于 Vue2.x中的 nofiny() triggerEffects(ref.dep);} }
-
如果需要深层响应转化,则需要用到 reactive() 函数,这里需要重点说明一下,
reactive()
函数的设计是单例模式,也就是说:对同一个对象多次调用reactive()
函数,返回的都是同一个代理对象。对一个代理对象调用reactive()
函数,总会返回代理对象自身。所以如果 ref 函数接收的是一个 Proxy代理对象的话,调用 reactive 函数之后,返回的还是本身的 Proxy 代理对象,并不会重复转化一次。这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理对象。
-
reactive 函数的响应式原理
上面说到了 在 ref函数中如果接收了一个对象,且需要深层响应的话,就会调用 reactive 函数来进行响应式转化,那我们现在来看看 reactive 函数转化响应式数据的原理
首先,在了解 reactive 函数的原理时,我们需要了解reactive 函数的基本概念,可以参考我的上一篇博文--reactive()函数。在这篇博文中,我大概讲了一下 reactive 函数是怎么通过 Proxy 代理对象以及 Reflect 对象来实现响应式操作的。现在,让我们完善一下 reactive函数的响应式原理吧。
import { isObject, toRawType } from '@vue/shared';
import { mutableHandlers } from './baseHandlers';
import { ReactiveFlags, reactiveMap } from './reactive';export function reactive(target) {// 判断 target 是否是一个对象if (!isObject(target)) {return target;}// 如果 target 已经是一个响应式对象,直接返回它if (target[ReactiveFlags.IS_REACTIVE]) {return target;}// 如果已经存在对应的 Proxy 对象,直接返回缓存的 Proxy 对象const existingProxy = reactiveMap.get(target);if (existingProxy) {return existingProxy;}// 否则创建一个新的 Proxy 对象const proxy = new Proxy(target, mutableHandlers);// 缓存创建的 Proxy 对象,避免重复创建,这就是单例模式--reactive函数对于Proxy代理对象返回的是其本身reactiveMap.set(target, proxy);return proxy;
}
到了这一步,已经完成了数据代理,通过对Proxy代理对象的操作,可以同步影响源对象。这时我们就需要进行数据监测了,而这一步其实就是在 mutableHandlers 对象之中。
const proxy = new Proxy(target, mutableHandlers);
mutableHandlers
是 Proxy
代理的核心,它定义了各种操作的拦截器,如 get
、set
、has
、deleteProperty
等。在 Vue 3 中,get
和 set
是最重要的两个拦截器:为了方便解释,我把 mutableHandlers
中的所有属性方法全部抽离出来了,真正源码不是这样的。
import { track, trigger } from './effect';
import { toRaw, reactive, readonly } from './reactive';
import { isObject, hasOwn, isSymbol, hasChanged } from '@vue/shared';
import { ReactiveFlags, toReactive, toReadonly } from './reactive';const get = createGetter();
const set = createSetter();
const deleteProperty = createDeleteProperty();
const has = createHas();
const ownKeys = createOwnKeys();export const mutableHandlers = {get,set,deleteProperty,has,ownKeys
};
到这里我们能了解 mutableHandlers
对象中基本都有些什么,然后就需要对每个方法进行深入解析了。
在介绍方法之前,我们了解一些概念
- 副作用函数(Effect):使用了响应式数据的函数,例如组件的渲染函数。当数据发生变化时,这些函数需要重新执行以更新视图或计算新的值。
- 依赖关系:在 Vue 3 中,每个响应式属性都可能有多个副作用函数依赖于它。依赖关系在
track
函数中被收集,而在trigger
函数中被使用。 - 全局状态(activeEffect):Vue 3 的响应式系统中有一个全局状态(activeEffect),用于保存当前正在执行的副作用函数。当我们读取响应式数据时,Vue 会检查是否存在当前副作用函数,并将其与数据的依赖关联起来。在组件渲染过程中,activeEffect 会指向当前的渲染函数,从而实现对所有使用到的响应式数据的依赖收集。
get方法:get拦截器负责处理对对象属性的读取操作。这是 Vue 响应式系统中最重要的部分之一,因为它涉及到依赖追踪。
function createGetter() {return function get(target, key, receiver) {// Reflect.get(target, key, receiver): 使用 Reflect.get 来获取源对象属性的值。// 这是现代 JavaScript 中获取属性值的标准方式,可以避免一些特殊情况下的错误。// 可以参考上一篇博文--reactive函数const res = Reflect.get(target, key, receiver);// 依赖收集 track(target, 'get', key): 调用 track 函数来进行依赖收集。// 这使得 Vue 能够追踪哪些组件依赖于这个属性,以便在属性变化时触发重新渲染。track(target, 'get', key);// 如果属性值是对象类型,则递归地将其转化为响应式对象,实现深度响应式。if (isObject(res)) {return reactive(res); // 深度响应式}// 如果属性值是基本类型,则直接返回return res;};
}
set方法:set
拦截器负责处理对象属性的修改操作,这是触发依赖更新的关键部分。
function createSetter() {return function set(target, key, value, receiver) {// 获取上一次的属性值const oldValue = target[key];// Reflect.set(): 使用 Reflect.set 来设置对象属性的值,与get一致,返回Boolean值来判断是否设置成功const result = Reflect.set(target, key, value, receiver);// 检查新值与旧值是否不同,只有在值确实发生变化时才触发更新。if (hasChanged(value, oldValue)) {// 调用 trigger 函数触发响应式更新,通知所有依赖该属性的副作用函数(如渲染函数)重新运行。trigger(target, 'set', key, value);}// 返回设置的结果状态,true or falsereturn result;};
}
deleteProperty
:拦截 delete
操作,在删除对象属性时触发响应式更新。
function deleteProperty(target, key) {// 检查 target 是否具有 key 属性const hadKey = hasOwn(target, key); // 删除 target 的 key 属性,与get 和 set 方法类似const result = Reflect.deleteProperty(target, key); // 如果删除成功,并且 key 属性确实存在if (result && hadKey) { // 触发响应式系统的更新通知trigger(target, 'delete', key); }// 返回删除操作的结果(true 或 false)return result;
},
has:用于检查一个对象是否拥有某个属性,相当于in 操作符 。
has(target, key) {// 判断 target 对象中是否存在属性key,返回 Boolean 值const result = Reflect.has(target, key);// 收集依赖track(target, 'has', key);// 返回结果return result;},
ownKeys
:Reflect.ownKeys
方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。
function ownKeys(target) {// 依赖收集,标记为数组类型track(target, 'iterate', 'array');// 返回所有键--是一个数组return Reflect.ownKeys(target);}
上面是操作对象的方法,其中 track 则是收集依赖的方法,trigger则是分发依赖通知更新的方法,下面简单介绍一下这两个方法是如何工作的。
track:收集依赖,track
函数 会将 target
、key
和当前的副作用函数关联起来,这样在 key
对应的属性值发生变化时,Vue 就能找到所有依赖这个属性的副作用函数,并触发它们重新执行。
function track(target, type, key) {// 首先检查当前是否有正在执行的副作用函数。如果没有,则不需要进行依赖收集。// 副作用函数一般在 Vue 的响应式系统运行时注册,比如在组件的渲染过程中,Vue 会把当前的渲染函数注册为全局的副作用函数。if (!isTracking()) return;// targetMap 是一个全局的 WeakMap,用于存储所有的响应式对象及其依赖关系。// 从 targetMap 中获取当前 target 对象的依赖图(即 depsMap),如果不存在,则创建一个新的 Map。//depsMap 是一个 Map,用于存储 target 对象中每个属性(即 key)的依赖集合(dep)let depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}// dep 是一个 Set,用于存储依赖于某个特定属性 key 的所有副作用函数。// 从 depsMap 中获取 key 的依赖集合 dep,如果不存在,则创建一个新的 Set。let dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = createDep()));}// 将当前的副作用函数添加到 dep 中// 这一步是将当前正在执行的副作用函数(如渲染函数)添加到依赖集合中,以便在这个 key 发生变化时,能够重新执行这些副作用函数。trackEffects(dep);
}
trigger:
当响应式对象的属性值发生变化时,Vue 会通过调用 trigger
函数来通知依赖该属性的所有副作用函数(如组件渲染函数、计算属性等)重新执行,从而实现视图的自动更新。
function trigger(target, type, key, newValue, oldValue) {// targetMap 是全局存储所有响应式对象及其依赖关系的 WeakMap。targetMap 的键是响应式对象 target,值是一个 Map,这个 Map 存储了该对象每个属性 key 的依赖集合。// depsMap 是当前 target 对象的依赖图,里面存储了 key 与其依赖的副作用函数的映射关系。// 如果 depsMap 不存在,说明这个 target 对象没有被任何副作用函数依赖,直接返回。const depsMap = targetMap.get(target);if (!depsMap) {return;}// effects 是一个 Set,用于去重并存储所有需要触发的副作用函数。这样可以避免重复触发相同的副作用函数。const effects = new Set();// 如果是 SET 操作,收集相关副作用函数, 从 depsMap 中获取与 key 相关的副作用函数集合,并将其添加到 effects 中。if (key !== undefined) {addEffects(depsMap.get(key));}// addEffects 函数检查是否存在与当前 key 相关的副作用函数,如果存在,它们将被添加到 effects 集合中。const addEffects = (effectsToAdd) => {if (effectsToAdd) {effectsToAdd.forEach(effect => effects.add(effect));}};// 如果操作类型是 'add'、'delete' 或 'set',而且对象是数组或其他特殊数据结构,还需要处理特殊的依赖关系。例如,操作数组的 length 属性时,需要触发依赖于 length 的副作用函数。if (type === 'add' || type === 'delete' || type === 'set') {addEffects(depsMap.get(Array.isArray(target) ? 'length' : ''));}// 遍历 effects 集合,并执行其中的每个副作用函数// 如果副作用函数有一个 scheduler 调度器(通常是用来调度执行顺序的),则调用调度器,否则直接执行副作用函数。effects.forEach(effect => {if (effect.options.scheduler) {effect.options.scheduler(effect);} else {effect();}});
}
trackEffects 收集依赖
通过源码我们可以知道,不论是ref还是reactive,在收集依赖时,最终都使用了 trackEffects 函数,其作用是将当前正在执行的副作用函数(例如渲染函数、计算属性等)添加到某个响应式数据的依赖集合中。这是依赖收集的一部分,目的是在数据变化时触发相关的副作用函数进行更新。
function trackEffects(dep) {// 获取当前正在执行的副作用函数let shouldTrack = shouldTrackEffect(dep);if (shouldTrack) {// 将当前的副作用函数 activeEffect 添加到 dep 中。这一步建立了属性与副作用函数之间的单向关联,即这个属性知道有哪些副作用函数依赖于它dep.add(activeEffect);// 同时将 dep 添加到 activeEffect 的 deps 数组中。这一步建立了副作用函数与属性之间的双向关联,即这个副作用函数知道它依赖于哪些属性。activeEffect.deps.push(dep);}
}// 检查当前的副作用函数是否已经被添加到 dep 中,以避免重复添加。
function shouldTrackEffect(dep) {// 如果 dep 中已经存在 activeEffect,则返回 false,表示不需要重复添加;否则返回 true。return !dep.has(activeEffect);
}
在 if 判断中,Vue3实现了 双向依赖管理,这样做的好处是
- 避免重复添加:由于
dep
是一个Set
,它会自动去重,确保每个副作用函数只会被添加一次。 - 双向清理:在副作用函数停止依赖某个属性时,可以通过清理
activeEffect.deps
中的dep
,从而移除双向关联,避免内存泄漏。
同样的,在这里我们也需要理解一些基本概念,那就是:dep
、 effect、activeEffect
、activeEffect.deps
- dep:是一个
Set
集合,用来存储依赖某个响应式属性的所有副作用函数。 effect:
是一个包装了副作用逻辑的函数(例如渲染函数、计算属性等)。当effect
函数被执行时,它会触发响应式数据的读取,从而进行依赖收集。activeEffect:
是一个全局变量,用于指向当前正在运行的副作用函数。每当副作用函数被执行时,activeEffect
就会被设置为当前的副作用函数,从而在依赖收集时可以将这个函数与相应的响应式属性关联起来。- activeEffect.deps:
activeEffect.deps
是一个数组,用来存储当前副作用函数依赖的所有dep
集合。也就是说,activeEffect.deps
中的每一个元素都是一个dep
,而每个dep
都包含了当前副作用函数所依赖的响应式属性。
举一个栗子就是:假设我们有一个响应式对象和一个依赖于这个对象的副作用函数:
const state = reactive({ count: 0 });const effectFn = effect(() => {console.log(state.count);
});
在执行 effectFn
的过程中,Vue 会执行以下操作:
-
依赖收集:
effectFn
会读取state.count
,因此state.count
对应的dep
集合会被收集,并且effectFn
会被添加到这个dep
集合中。 -
记录依赖:同时,Vue 还会将这个
dep
集合添加到activeEffect.deps
中,以便将来进行依赖关系的清理。 -
触发更新:当
state.count
的值发生变化时,Vue 会遍历dep
集合中的所有副作用函数,并触发它们重新执行。 -
清理旧的依赖:如果在下一次执行
effectFn
之前,某些依赖关系已经不再存在(例如副作用函数不再依赖某个属性),Vue 会通过遍历activeEffect.deps
来清理这些不再需要的依赖。
总结
Vue2的响应式
- 原理:通过Object.definePropoty() 实现对数据每个属性的劫持,通过get和set实现了响应式
- 问题:对于数组数据无法通过数组下标直接操作,无法通过 length直接设置数组长度,无法 直接给对象或数组添加属性
- 解决:通过 vm.$set() 或 this.set() 来对对象或数组进行操作
Vue3响应式
- 原理:Ref响应式还是通过Object.definePropoty()对数据劫持,通过get和set实现响应式。reactive则是通过 Proxy对数据进行代理劫持,实现响应式。然后通过Reflect实现对源数据的操作
- 优势:Proxy是实现对象的监听,而不是对某个属性的监听。而且是惰性的,嵌套对象只有在被访问时才会被转化为响应式。这种方式避免了不必要的性能开销,尤其是在处理大型数据结构时。
相关文章:

Vue2与Vue3响应式原理对比
Vue2.x 响应式原理 Vue2.x 响应式: 实现原理 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截( 数据劫持 )数组类型:通过重写数组方法,并作为拦截器挂载到数组对象与数组原型之间,来实现拦截。 存在…...

Android系统Android.bp文件详解
文章目录 1. 基本语法结构2. 常见模块类型3. 模块属性常见属性包括: 4. 具体示例5. 高级功能5.1. 条件编译5.2. 变量定义与使用5.3. 模块继承 6. 总结 Android.bp 是 Android 构建系统(Android Build System)中的配置文件,用于描述…...

eNSP 华为静态路由配置
R1: <Huawei>system-view [Huawei]sysname R1 [R1]int g0/0/0 //进入g0/0/0端口 [R1-GigabitEthernet0/0/0]ip address 192.168.1.1 24 //给端口配置IP地址和子网掩码 [R1-GigabitEthernet0/0/0]int g0/0/1 [R1-GigabitEthernet0/0/1]ip addr…...

Type-C PD芯片:引领智能充电与数据传输的新时代
随着科技的飞速发展,智能设备已经成为我们日常生活中不可或缺的一部分。无论是智能手机、平板电脑、笔记本电脑,还是智能家居设备,都需要高效、安全、便捷的充电与数据传输解决方案。在这样的背景下,Type-C PD(Power D…...

天气查询 免费
免费的前提是需要有高德地图key 前去申请一个key 调用IP查询 | 高德控制台 ------ 申请key之后调用下面的接口或者查看官方文档 api地址: restapi.amap.com/v3/weather/weatherInfo 天气查询-基础 API 文档-开发指南-Web服务 API | 高德地图API 参数名 含义 规…...

VC 与 VS(visual studio) 的对应版本
VC 与 VS 对应版本的关系: VC9:对应的是 Visual Studio 2008 版本。在这个版本中,开发环境提供了一系列的新特性和改进,为开发者提供了更高效的编程体验。例如,增强了对 C 标准的支持,优化了调试工具等。 …...

Qt使用lupdate工具生成.ts文件
Qt提供了lupdate工具,用于从源代码中提取需要翻译的字符串【1】,并生成或更新.ts文件 注解【1】:使用tr()函数(或者QCoreApplication::translate()等其他相关的翻译函数)来标记所有需要翻译的文本。例如: …...

编程-设计模式 1:工厂方法模式
设计模式 1:工厂方法模式 定义与目的 定义:工厂方法模式定义了一个创建对象的接口,但允许子类决定实例化哪一个类。工厂方法让一个类的实例化延迟到其子类。目的:提供一种方式来封装对象创建的过程,使得客户端不需要…...

Linux 快速构建LAMP环境
目录 部署方式: 基础环境准备: 1.安装Apache服务 (1)安装Apache (2)安装一些Apache的扩展包 2.安装PHP语言 (1)下载php软件仓库 (2)指定php安装版本…...

【C/C++】语言基础知识总复习
文章目录 1. 指针1.1 数组和指针1.2 函数指针1.3 const 和 指针、static、#define、typedef1.4 指针和引用的异同1.5 sizeof与strlen 2. 库函数及其模拟实现3. 自定义类型4. 数据存储5. 编译链接过程6. C入门基础6.1 函数重载6.2 引用和指针6.3 建议使用const、inline、enum去替…...

【漏洞修复】Tomcat中间件漏洞
1.CVE-2017-12615 抓包上传一句话木马 密码passwd 2.后台弱口令部署war包 先用弱口令登录网站后台 制作war包 将172.jsp压缩成.zip文件,修改后缀为.war 上传 蚁剑链接 3.CVE-2020-1938 Python2 CVE-2020-1938.py IP -p 端口 -f 要读取的文件 漏洞修复…...

10.动态路由绑定怎么做
为什么要动态路由绑定 因为,如果我们的导航栏没有这个权限,输入对应网址,一样可以获取对应的页面,为了解决这个问题,有两种解决方案,一种是动态路由绑定(导航有多少个,就有多少个路由,在路由修改之前,先进行一个导航路由的加载和路由的动态绑定,然后看是否有这个路由,有就跳转…...

操作ArkTS页面跳转及路由相关心得
本文为JS老狗原创。 当前端不得不关注的点:路由,今天聊一聊鸿蒙相关的一点心得。 总体上套路不意外,基本就是(尤其是Web)前端那些事:维护路由表、跳转带参数、历史堆栈操作,等等。 历史原因&…...

Vue2-低版本编译兼容-基础语法-data-methods-双向数据绑定v-model
文章目录 1.安装编译命令2.低版本兼容3.vue2响应式数据3.1.data定义3.2.双向数据绑定v-model3.3.单向数据绑定v-bind4.方法methods5.子组件向父组件传值6.父组件向子组件传值1.安装编译命令 命令行工具 vue create zhiliaoplugins8824barcodebatch cd zhiliaoplugins8824barc…...

提取“c语言的函数定义“脚本
------------------------------------------------------------ author: hjjdebug date: 2024年 08月 11日 星期日 16:35:31 CST description: 提取c语言的函数定义脚本 ------------------------------------------------------------ c 文件中包含很多函数定义, 我想在每…...

pytorch学习(十二):对现有的模型进行修改
以VGG16为例: VGG((features): Sequential((0): Conv2d(3, 64, kernel_size(3, 3), stride(1, 1), padding(1, 1))(1): ReLU(inplaceTrue)(2): Conv2d(64, 64, kernel_size(3, 3), stride(1, 1), padding(1, 1))(3): ReLU(inplaceTrue)(4): MaxPool2d(kernel_size2…...

服务器虚拟内存是什么?虚拟内存怎么设置?
服务器虚拟内存是计算机系统内存管理的一种重要技术,它允许应用程序认为它们拥有连续且完整的内存地址空间,而实际上这些内存空间是由多个物理内存碎片和外部磁盘存储器上的空间共同组成的。当物理内存(RAM)不足时,系统…...

深度学习入门指南(1) - 从chatgpt入手
2012年,加拿大多伦多大学的Hinton教授带领他的两个学生Alex和Ilya一起用AlexNet撞开了深度学习的大门,从此人类走入了深度学习时代。 2015年,这个第二作者80后Ilya Sutskever参与创建了openai公司。现在Ilya是openai的首席科学家,…...

Python学习笔记(六)
""" 演示对序列进行切片操作 """ # 切片;从一个序列中,取出一个子序列 # 语法[起始下标:结束下标:步长] # 这三个都不写也行,视为从头到尾步长为1 # 起始下标不写,视作从头开…...

大数据安全规划总体方案(45页PPT)
方案介绍: 大数据安全规划总体方案的制定,旨在应对当前大数据环境中存在的各类安全风险,包括但不限于数据泄露、数据篡改、非法访问等。通过构建完善的安全防护体系,保障大数据在采集、存储、处理、传输、共享等全生命周期中的安…...

第20周:Pytorch文本分类入门
目录 前言 一、前期准备 1.1 环境安装导入包 1.2 加载数据 1.3 构建词典 1.4 生成数据批次和迭代器 二、准备模型 2.1 定义模型 2.2 定义示例 2.3 定义训练函数与评估函数 三、训练模型 3.1 拆分数据集并运行模型 3.2 使用测试数据集评估模型 总结 前言 …...

记一次 SpringBoot2.x 配置 Fastjson请求报 internal server 500
1.遇到的问题 报错springboot从2.1.16升级到2.5.15,之后就报500内部错误,后面调用都是正常的,就考虑转换有错。 接口返回错误: 2.解决办法 因为我用了fastjson,需要转换下,目前可能理解就是springboot-we…...

OSPF笔记
OSPF:开放式最短路径优先协议 使用范围:IGP 协议算法特点:链路状态型路由协议,SPF算法 协议是否传递网络掩码:传递网络掩码 协议封装:基于ip协议封装,协议号为89 一,ospf特点 1…...

IOC容器初始化流程
IOC容器初始化流程 一、概要1.准备上下文prepareRefresh()2. 获取beanFactory:obtainFreshBeanFactory()3. 准备beanFactory:prepareBeanFactory(beanFactory)4. 后置处理:postProcessBeanFactory()5. 调用bean工厂后置处理器:invokeBeanFactoryPostProcessors()6. 注册bea…...

第二季度云计算市场份额榜单:微软下滑,谷歌上升,AWS仍保持领先
2024 年第二季度,随着企业云支出达到 790 亿美元的新高,三大云计算巨头微软、谷歌云和 AWS的全球云市场份额发生了变化。 根据新的市场数据,以下是 2024 年第二季度全球云市场份额结果和六大世界领先者,其中包括 AWS、阿里巴巴、…...

三点确定圆心算法推导
已知a,b,c三点求过这三点的圆心坐标 a ( x 1 , y 1 ) a(x_1, y_1) a(x1,y1) 、 b ( x 2 , y 2 ) b(x_2, y_2) b(x2,y2) 、 c ( x 3 , y 3 ) c(x_3, y_3) c(x3,y3) 确认三点是否共线 叉积计算方式 v → ( X 1 , Y 1 ) u → ( X 2 , Y 2 ) X 1 Y 2 − X 2 Y 1 \…...

神经网络 (NN) TensorFlow Playground在线应用程序
神经网络 (NN) 历史上最重要的发现之一是神经网络 (NN) 的强大功能。 在神经网络中,称为神经元的许多数据层被添加在一起或相互堆叠以计算新的数据级别。 常用的简称: DNN 深度神经网络CNN 卷积神经网络RNN 循环神经网络 神经元 科学家一致认为&am…...

腾讯课堂 离线m3u8.sqlite转成视频
为了广大腾讯课堂用户对于购买的课程不能正常离线播放,构成知识付费损失,故出此文档。 重点:完全免费!!!完全免费!!!完全免费!!! 怎么…...

Linux多路转接
文章目录 IO模型多路转接select 和 pollepoll IO模型 在还在学习语言的阶段,C里使用cin,或者是C使用scanf的时候,总是要等着我们输入数据才执行,这种IO是阻塞IO。下面是比较正式的说法。 阻塞IO: 在内核将数据准备好之前…...

IDEA导入Maven项目的流程配置以常见问题解决
1. 前言 本文主要围绕着在IDEA中导入新Maven项目后的配置及常见问题解决来展开说说。相关的部分软件如下: IntelliJ IDEA 2021.1JDK 1.8Window 2. 导入Maven项目及配置 2.1 导入Maven项目 下面介绍了直接打开本地项目和导入git上的项目两种导入Maven方式。 1…...