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

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()函数实现响应式

  1. 在调用 ref() 函数时,会先调用 createRef() 函数。
    export function ref(value) {return createRef(value, false);
    }
  2. 然后在 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);
    }
  3. RefImpl 类:是 ref 对象的实际实现。主要包括

    1. 存储原始值以及响应值:_rawValue 存储原始值,_value存储响应值

    2. 响应式处理:通过 shallow 来决定是否进行深层响应式数据处理

    3. 依赖收集与分发:在 get 函数中通过 trackRefValue 函数来收集依赖。在set函数中通过 triggerRefValue 函数通知依赖更新。

    4. 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);}}
      }
    5. 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()));}
      }
    6. triggerRefValue()函数:用来分发依赖

      function triggerRefValue(ref) {// ref.dep:依赖集合。如果存在依赖集合,则继续进行触发操作。if (ref.dep) {// 遍历并执行 ref.dep 中的所有副作用(effect),以响应 ref 值的变化。这个函数会通知所有依赖于 ref 值的副作用重新运行。类似于 Vue2.x中的 nofiny() triggerEffects(ref.dep);}
      }
      
    7. 如果需要深层响应转化,则需要用到 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);

mutableHandlersProxy 代理的核心,它定义了各种操作的拦截器,如 getsethasdeleteProperty 等。在 Vue 3 中,getset 是最重要的两个拦截器:为了方便解释,我把 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 函数 会将 targetkey 和当前的副作用函数关联起来,这样在 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 会执行以下操作:

  1. 依赖收集effectFn 会读取 state.count,因此 state.count 对应的 dep 集合会被收集,并且 effectFn 会被添加到这个 dep 集合中。

  2. 记录依赖:同时,Vue 还会将这个 dep 集合添加到 activeEffect.deps 中,以便将来进行依赖关系的清理。

  3. 触发更新:当 state.count 的值发生变化时,Vue 会遍历 dep 集合中的所有副作用函数,并触发它们重新执行。

  4. 清理旧的依赖:如果在下一次执行 effectFn 之前,某些依赖关系已经不再存在(例如副作用函数不再依赖某个属性),Vue 会通过遍历 activeEffect.deps 来清理这些不再需要的依赖。

总结

Vue2的响应式

  1. 原理:通过Object.definePropoty() 实现对数据每个属性的劫持,通过get和set实现了响应式
  2. 问题:对于数组数据无法通过数组下标直接操作,无法通过 length直接设置数组长度,无法             直接给对象或数组添加属性
  3. 解决:通过 vm.$set() 或 this.set() 来对对象或数组进行操作

Vue3响应式

  1. 原理:Ref响应式还是通过Object.definePropoty()对数据劫持,通过get和set实现响应式。reactive则是通过 Proxy对数据进行代理劫持,实现响应式。然后通过Reflect实现对源数据的操作
  2. 优势:Proxy是实现对象的监听,而不是对某个属性的监听。而且是惰性的,嵌套对象只有在被访问时才会被转化为响应式。这种方式避免了不必要的性能开销,尤其是在处理大型数据结构时。

相关文章:

Vue2与Vue3响应式原理对比

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

Android系统Android.bp文件详解

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

eNSP 华为静态路由配置

R1&#xff1a; <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芯片:引领智能充电与数据传输的新时代

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

天气查询 免费

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

VC 与 VS(visual studio) 的对应版本

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

Qt使用lupdate工具生成.ts文件

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

编程-设计模式 1:工厂方法模式

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

Linux 快速构建LAMP环境

目录 部署方式&#xff1a; 基础环境准备&#xff1a; 1.安装Apache服务 &#xff08;1&#xff09;安装Apache &#xff08;2&#xff09;安装一些Apache的扩展包 2.安装PHP语言 &#xff08;1&#xff09;下载php软件仓库 &#xff08;2&#xff09;指定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文件&#xff0c;修改后缀为.war 上传 蚁剑链接 3.CVE-2020-1938 Python2 CVE-2020-1938.py IP -p 端口 -f 要读取的文件 漏洞修复&#xf…...

10.动态路由绑定怎么做

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

操作ArkTS页面跳转及路由相关心得

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

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为例&#xff1a; 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…...

服务器虚拟内存是什么?虚拟内存怎么设置?

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

深度学习入门指南(1) - 从chatgpt入手

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

Python学习笔记(六)

""" 演示对序列进行切片操作 """ # 切片&#xff1b;从一个序列中&#xff0c;取出一个子序列 # 语法[起始下标&#xff1a;结束下标&#xff1a;步长] # 这三个都不写也行&#xff0c;视为从头到尾步长为1 # 起始下标不写&#xff0c;视作从头开…...

大数据安全规划总体方案(45页PPT)

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

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...