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

2024年虚拟DOM技术将何去何从?

d6923afa3229722729ceeb6adab12485.jpeg

从诞生之初谈起,从命令式到声明式,Web开发的演变之路

Web开发的起源与jQuery的统治

在Web开发的早期阶段,操作DOM元素主要依赖命令式编程。当时,jQuery因其易用性而广受欢迎。使用jQuery,开发者通过具体的命令操作DOM,比如:

// jQuery 示例
$("ol li").click(function() {});
let li = $("<li>我是一个列表项</li>");
$("ol").append(li);

这种方式虽然直观,但随着应用复杂度的提高,代码管理变得越来越困难。

声明式编程的兴起:React与JSX

2f427fc9bc954997432434892ce4cf4e.jpeg

2013年,Facebook的Jordan Walke提出了一个革命性的想法:将FaceBook在2010年开发的XHP功能迁移到JavaScript,借助JSX扩展形成新的编码风格。与命令式不同,声明式编程不再关注如何操作DOM,而是描述希望DOM是什么样子。例如:

// React组件示例
const Component = (<ul>{data.map(item => <MyItem data={item} />)}</ul>
);

声明式编程使得组件化开发成为可能,极大地提高了代码的可维护性和可扩展性。

虚拟DOM的出现

React的一个关键概念是虚拟DOM(Virtual DOM)。虚拟DOM是代码与实际DOM操作之间的中间层。这个概念允许代码先修改虚拟DOM树,然后再映射到真实的DOM树上。

虚拟DOM的优点包括:

  • 促进函数式UI编程:抽象组件,简化代码维护。

  • 跨平台能力:虚拟DOM本质上是JavaScript对象,可用于小程序、iOS、Android等应用的抽象层。

  • 数据绑定减少DOM操作:通过合并多个DOM操作为一次操作,减少浏览器重排。例如:

// 利用DocumentFragment优化DOM操作
const fragment = document.createDocumentFragment();
for(let i = 0; i < 1000; i++) {const div = document.createElement('div');fragment.appendChild(div);
}
const container = document.getElementById('container');
container.appendChild(fragment);
  • 轻量级JavaScript操作进行DOM差异化(Diffing):避免过度查询和存储实际DOM,提高性能。

  • 通过DOM Diff减少不必要的操作:减少页面重排和重绘。

  • 缓存DOM和保存节点状态:优化DOM更新。

通过这些技术的发展和应用,Web前端开发从繁琐的命令式操作转变为更加高效、可维护的声明式编程。虚拟DOM的概念不仅为React所采纳,也被许多其他框架所使用,从而推动了整个前端生态的发展。

虚拟DOM的现状及其争议

虚拟DOM的挑战

虽然虚拟DOM曾是前端开发的一大创新,但随着技术的发展,一些框架开始质疑并摒弃虚拟DOM。这背后的原因主要包括:

  • 首次渲染性能:虚拟DOM在首次渲染大量DOM时,由于额外的计算开销,可能会比直接使用innerHTML插入慢。

  • 内存占用:维护一份虚拟DOM树的内存副本。

  • 频繁更新的开销:在频繁更新时,虚拟DOM需要更多时间进行计算工作。

  • 大型项目的性能成本:即使现代框架进行了优化,比较和计算虚拟DOM的成本依然存在,特别是在构建虚拟DOM树时。

不同框架的应对策略

Uber:例如Uber,通过广泛且手动使用shouldComponentUpdate来最小化渲染调用。

React Fiber:React 16引入了React Fiber。通过优先级分割的中断机制,改善了因虚拟DOM树深度遍历造成的主进程阻塞问题。

Vue 3:Vue的创始人尤雨溪在“Vue 3的设计”中提到,他致力于寻找突破虚拟DOM的性能瓶颈。

Svelte:Svelte的作者Rich Harris提出了“虚拟DOM纯属开销”的观点,强调在某些情况或频繁更新下,虚拟DOM数据驱动模型带来的不必要开销。

2024年的虚拟DOM:还需要吗?

fcac80e0fef842d4f51aba592e000831.jpeg

当前非虚拟DOM框架的主力:Svelte

虚拟DOM的现状

目前,虚拟DOM仍然是主流框架中的主导技术。React持续在迭代中探索更合理的调度模式,而Vue3专注于优化虚拟DOM的diff算法。ivi和Inferno在虚拟DOM框架的性能前沿领先。尽管虚拟DOM在主流框架中仍占主导地位,但像Svelte和Solidjs这样的非虚拟DOM框架开始将它们的新模式引入公众视野。

Svelte的创新

Rich Harris,Svelte和rollup的作者,将他在代码打包策略上的专长带入了JavaScript框架领域。

理念:“最好的API是根本没有API” —— Rich Harris

Svelte3:Svelte3经过重大改变,成为一个更轻量级、语法更简洁、代码量更少的JavaScript框架,用于实现响应性。

Svelte在编译阶段,直接将声明式代码转换为更高效的命令式代码,并减少运行时代码。例如:

<script>let count = 0;function handleClick() {count += 1;}$: {console.log(`当前计数为 ${count}`);}
</script><div class="x-three-year" on:click={handleClick}><div class="no-open" style={{ color: 'blue' }}>{`计数: ${count}`}</div>
</div>

在这个示例中,我们通过基本声明获得了一个响应性变量。然后,通过将其绑定到点击事件,我们得到了一个通过点击驱动视图数据的普通组件。

编译后,Svelte会自动标记响应式数据,例如:

function instance($$self, $$props, $$invalidate) {let count = 0;function handleClick() {$$invalidate(0, count += 1);}$$self.$$.update = () => {if ($$self.$$.dirty & /*count*/ 1) {$: {console.log(`当前计数为 ${count}`);}}};return [count, handleClick];
}

Svelte的优势

Svelte的主要优势在于:

  • 编译时优化:它在构建时而不是运行时处理组件逻辑,将声明式代码编译为高效的命令式代码,从而减少了运行时的开销。

  • 更少的代码:由于编译时优化,Svelte能够生成更少的代码来实现相同的功能。

  • 无需虚拟DOM:Svelte避免了虚拟DOM的使用,直接在编译时将组件转换为优化的JavaScript代码,这减少了运行时的性能开销。

总结来说,Svelte代表了一种新的前端开发范式,通过编译时优化和减少代码量,提供了一种高效、简洁的开发体验。这对于追求性能和简洁性的开发者来说,是一个有吸引力的选择。

Vue的蒸汽模式(Vapor Mode)

概述

Vue3引入了一种新的编译优化策略,称为“蒸汽模式”(Vapor Mode),这是对Svelte预编译概念的响应。蒸汽模式利用编译时信息优化虚拟DOM。这种模式主要体现在编译阶段为一些静态节点附加编译信息,从而在遍历虚拟DOM树时减少不必要的开销,并在一定程度上优化了虚拟DOM带来的问题。

优化的关键点

  • 静态节点优化:在编译阶段,Vue能够识别出模板中的静态节点,并为它们添加特定的编译信息。这意味着在组件更新时,Vue可以跳过这些静态节点的重新渲染,因为它们不会改变。

  • 减少运行时开销:通过在编译时就处理一部分工作,Vue减少了虚拟DOM在运行时的负担。这使得组件在更新时更快,尤其是在处理大型或复杂的DOM结构时。

性能和体积方面的考虑

  • 性能提升:蒸汽模式通过优化静态节点的处理,提高了整体渲染性能。

  • 体积轻量化:这种模式不仅关注性能,也注重于减轻最终打包文件的体积。通过在编译时进行优化,Vue能够生成更加精简的代码。

对前端框架未来的探索

Vue通过引入蒸汽模式,展示了前端框架未来的一个可能趋势:更多地依赖编译时优化,而不仅仅是运行时的动态处理。这种方法不仅提高了性能,还降低了框架的体积,为开发者提供了更高效的开发体验。此外,这也表明了前端技术的不断进步,以及框架开发者对于提高Web应用性能和用户体验的持续追求。

总的来说,Vue的蒸汽模式是对传统虚拟DOM概念的一种重要补充和优化,它在保持虚拟DOM带来的好处的同时,减少了其带来的一些性能开销。这种模式对于那些追求高性能且关注应用大小的项目尤为有益。

Solidjs:一种基于编译的响应式系统

1、Solidjs概述

Solidjs(或称为Solid)是一个类似于Svelte的现代前端框架。它们都基于编译的响应式系统,但在响应性的实现方式上有所不同。Solidjs通过数据驱动的发布-订阅模式来实现细粒度的响应。它因其卓越的性能而闻名,甚至在js-framework-benchmark中排名第一。与此同时,Solidjs的语法更接近于React,对于习惯使用React的开发者而言更为友好。

2、编译阶段的转换

在Solidjs的官方playground中,我们可以看到框架在编译阶段将JSX转换为HTML的输出结果。这一过程体现了Solidjs如何将声明式的代码编译为能够直接操作DOM的命令式代码,从而提高运行时性能。

3、“真正的响应式”

Solidjs在其官网上被标榜为“真正的响应式”。这种响应式并非指React中的虚拟DOM基于状态变化进行修改和重新渲染,而是指Solidjs和Svelte在数据层面上具有更细粒度的响应。相比之下,React是在组件层面上进行响应的。

4、Solidjs的“细粒度响应”设计与实现

Solidjs的“细粒度响应”是指它能够精确地跟踪和响应每个独立的状态变化,而不是整个组件树的变化。这种方法减少了不必要的组件更新和重新渲染,从而提高了性能。

例如,在Solidjs中,当一个状态值改变时,只有依赖于这个状态的部分会重新计算和渲染,而不会影响其他不相关的组件或状态。这种方式使得Solidjs在处理大型应用或复杂交互时具有更高的效率和更好的性能。

5、createSignal 详解

在Solidjs中,createSignal 是实现状态管理和响应式更新的核心。与一些文章误解的不同,Solidjs并不完全基于Proxy来实现响应式,而是依赖类似于Knockout的发布-订阅数据响应系统。createSignal 的关键在于两种角色:SignalState(信号状态)和Computation(计算)。

export function createSignal<T>(value?: T,options?: SignalOptions<T | undefined>
): Signal<T | undefined> {options = options ? Object.assign({}, signalOptions, options) : signalOptions;const s: SignalState<T | undefined> = {value,observers: null,observerSlots: null,comparator: options.equals || undefined};if ("_SOLID_DEV_" && !options.internal) {if (options.name) s.name = options.name;registerGraph(s);}const setter: Setter<T | undefined> = (value?: unknown) => {if (typeof value === "function") {if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);else value = value(s.value);}return writeSignal(s, value);};return [readSignal.bind(s), setter];
}

SignalState 和 Computation 的角色

SignalState:主要存储在类型为SignalState的对象中。它包含当前值(value)、观察者数组(observers,类型为Computation)、观察者在数组中的位置(observerSlots)和比较器(comparator,用于比较变化,默认为浅比较)。

export interface SignalState<T> extends SourceMapValue {value: T;observers: Computation<any>[] | null;observerSlots: number[] | null;tValue?: T;comparator?: (prev: T, next: T) => boolean;
}

Computation:在全局作用域中,有一个Listener用来临时存储类型为Computation的观察者。在组件渲染(createRenderEffect)或调用createEffect时,通过updateComputation方法为全局Listener赋值,为后续的依赖跟踪打下基础。

let Listener: Computation<any> | null = null;
export interface Computation<Init, Next extends Init = Init> extends Owner {fn: EffectFunction<Init, Next>;state: ComputationState;tState?: ComputationState;sources: SignalState<Next>[] | null;sourceSlots: number[] | null;value?: Init;updatedAt: number | null;pure: boolean;user?: boolean;suspense?: SuspenseContextType;
}
function updateComputation(node: Computation<any>) {if (!node.fn) return;cleanNode(node);const owner = Owner,listener = Listener,time = ExecCount;Listener = Owner = node;runComputation(node,Transition && Transition.running && Transition.sources.has(node as Memo<any>)? (node as Memo<any>).tValue: node.value,time);
//...Listener = listener;Owner = owner;
}

由于信号的读取,通过函数调用获取数据。

<div class="no-open" style={{ color: 'blue' }}>{`count: ${count()}`}</div>

信号的读取和写入

读取信号:在任何地方读取SignalState时,都会调用readSignal函数。当前临时存储在全局上下文中的“观察者”Listener(指引用SignalState的地方)将被放入其观察者数组中,观察者源将指向当前信号,实现数据绑定。最后,返回相应的SignalState值。

export function readSignal(this: SignalState<any> | Memo<any>) {const runningTransition = Transition && Transition.running;if ((this as Memo<any>).sources &&(runningTransition ? (this as Memo<any>).tState : (this as Memo<any>).state)) {if ((runningTransition ? (this as Memo<any>).tState : (this as Memo<any>).state) === STALE)updateComputation(this as Memo<any>);else {const updates = Updates;Updates = null;runUpdates(() => lookUpstream(this as Memo<any>), false);Updates = updates;}}if (Listener) {const sSlot = this.observers ? this.observers.length : 0;if (!Listener.sources) {Listener.sources = [this];Listener.sourceSlots = [sSlot];} else {Listener.sources.push(this);Listener.sourceSlots!.push(sSlot);}if (!this.observers) {this.observers = [Listener];this.observerSlots = [Listener.sources.length - 1];} else {this.observers.push(Listener);this.observerSlots!.push(Listener.sources.length - 1);}}if (runningTransition && Transition!.sources.has(this)) return this.tValue;return this.value;
}

写入信号:写入信号时,调用writeSignal函数。在闭包内更改当前SignalState后,遍历在readSignal阶段收集的观察者数组,并将观察者推入当前Effect执行列表。

export function writeSignal(node: SignalState<any> | Memo<any>, value: any, isComp?: boolean) {let current =Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;if (!node.comparator || !node.comparator(current, value)) {if (Transition) {const TransitionRunning = Transition.running;if (TransitionRunning || (!isComp && Transition.sources.has(node))) {Transition.sources.add(node);.tValue = value;}if (!TransitionRunning) node.value = value;} else node.value = value;if (node.observers && node.observers.length) {runUpdates(() => {for (let i = 0; i < node.observers!.length; i += 1) {const o = node.observers![i];const TransitionRunning = Transition && Transition.running;if (TransitionRunning && Transition!.disposed.has(o)) continue;if (TransitionRunning ? !o.tState : !o.state) {if (o.pure) Updates!.push(o);else Effects!.push(o);if ((o as Memo<any>).observers) markDownstream(o as Memo<any>);}if (!TransitionRunning) o.state = STALE;else o.tState = STALE;}if (Updates!.length > 10e5) {Updates = [];if ("_SOLID_DEV_") throw new Error("Potential Infinite Loop Detected.");throw new Error();}}, false);}}return value;
}

消息重新分发:此时,Effect列表保存了当时的观察者。然后遍历并执行runEffects来重新分发消息。在相应的节点(Computation)中,重新执行readSignal函数,此时可以获取最新的数据结果。

6、组件更新机制解析

在Solidjs中,组件的更新和createEffect类似,但组件的引用通过createRenderEffect和updateComputation来进行处理。

示例:App 组件

function App() {const [count, setCount] = createSignal(0);return (<div class="x-three-year" onClick={() => setCount((pre) => pre + 1)}><div class="no-open">foobar</div><div class="no-open">{count()}</div></div>);
}

在这个例子中,我们有一个计数器,每次点击时count会增加。这是通过setCount函数实现的,它是createSignal的一部分。

点击事件触发更新过程

当点击事件发生时,会触发setCount,进而触发writeSignal的行为,如之前所述。这会导致updateComputation被触发,接着进行readSignal以获取SignalState。整个调用栈过程如下:

4a2e6421e86eee47a8659e02d0cc1449.jpeg

7、Solid中需注意的几点

属性的解构和合并

在Solid中,有一些特别需要注意的地方,特别是关于属性(props)的处理:

不能直接解构和合并响应式属性:不能直接使用剩余(rest)和展开(spread)语法来解构和合并响应式属性数据。这是因为通过浅拷贝形式的解构(同样Object.assign方法也不可用)进行的拷贝会割断信号的更新,导致其失去响应性并脱离跟踪范围。

解构会导致失去响应性:直接解构会导致解构出的值失去响应性,并从跟踪中分离。通常,在Solid的原语或JSX之外访问props对象上的属性可能导致失去响应性。此外,像展开操作符和Object.assign这样的函数也可能导致失去响应性。

示例

//no
function Other({count}) {return (<div><div>{count}</div></div>);
}//yes
function Other(props) {return (<div><div>{props.count}</div></div>);
}function App() {const [count, setCount] = createSignal(0);return (<div class="x-three-year" onClick={() => setCount((pre: any) => pre + 1)}><div class="no-open">foobar</div><div class="no-open">{count()}</div><Other count={count()}></Other></div>);
}
//yes
function Other({count}) {return (<div><div>{count()}</div></div>);
}function App() {const [count, setCount] = createSignal(0);return (<div class="x-three-year" onClick={() => setCount((pre: any) => pre + 1)}><div class="no-open">foobar</div><div class="no-open">{count()}</div><Other count={count}></Other></div>);
}

同时,Solid官方也提供了像mergeProps和splitProps这样的API,用于子组件修改响应式props数据。内部它使用Proxy代理来实现动态跟踪。

依赖跟踪的同步性

Solid的依赖跟踪仅适用于同步跟踪。

异步操作中的依赖跟踪问题:如果在createEffect中使用setTimeout来异步直接访问SignalState,将无法跟踪SignalState的更新,如下示例所示:

const [count, setCount] = createSignal(100);createEffect(() => {setTimeout(() => {// 这种方式无法跟踪console.log('count', count());}, 100);
});

这是因为当readSignal函数在这个时候读取Listener时,基本过程已经完成,数据已被清除(Listener = null Owner = null),因此在读取时无法跟踪SignalState。

避免方法:然而,可以通过某些方法避免这种情况。

createEffect(() => {const tempCount = count();setTimeout(() => {console.log('count', tempCount;}, 100);
});

前端框架比较

npm下载次数查询网站

d0fcc345789d51ca83dab4d0bc714913.jpeg

目前,State of js 只有 2022 年的数据(仅供参考),但从数据中可以看出,React、Vue、Angular 在使用量上仍然占据主导地位。但从满意度来看,已经出现了两大非虚拟DOM主力。

59605d3f3aee499e4f61e0e704534896.jpeg

f1e3d50fdc985b061e3bab40d56b4524.jpeg

Svelte和Solid:超越虚拟DOM的前端框架

Svelte和Solid的崛起不仅标志着虚拟DOM的淡出,更多的编译时任务的加入,也展示了开发的新可能性。这两个框架都不使用虚拟DOM,但提供了高效的更新机制和优化的编译过程。

性能比较

根据最新的js-framework-benchmark(Chrome 119 — OSX)数据,Svelte和Solid在性能上相似。在DOM操作时间方面,Solid似乎表现更佳,而Svelte在内存使用和启动时间方面有更好的数据。

2cf71d3b936d7599e67ef298380d5516.jpeg

与其他框架的对比

这边我提取了 js-framework-benchmark (Chrome 119 — OSX) 的公开状态,选取了 ivi、Inferno、Solid、Svelte、Vue、React 进行整体对比。从结果来看,Svelte 和 Solid 的表现略好于大家熟知的 Vue 和 React。但相比像 ivi、Inferno 这样以性能着称的虚拟 DOM 框架,并没有什么优势。

28729c12ddbcdc0ffe636cb5a4a634a5.jpeg

国外大佬 Ryan Carniato在他的研究《DOM渲染的最快方法》一文中,使用标签模板和HyperScript作为Solid的渲染模板,并将其与其他在js-framework-benchmark上表现良好的JavaScript框架进行了比较。结果显示,虚拟DOM框架和非虚拟DOM框架的性能相似,特别是一些高性能的虚拟DOM框架。

最终结果表明,虚拟DOM框架和非虚拟DOM框架具有相似的性能(严格来说,这是针对一些高性能的虚拟DOM框架)。因此,没有最好的技术。在历史上不断修改和优化的过程中,虚拟 DOM 的速度并不慢。不断探索是对技术最大的尊重。

结束

在2024年的前端框架领域,我们看到了多样化的技术选择和不断的创新。每种技术都有其适用的场景和优势,开发者应根据项目的具体需求和上下文来选择最适合的框架。虚拟DOM的主导地位表明它在许多情况下仍然是一个有效的选择,但Svelte和Solid等新兴框架的出现也为开发者提供了更多的选择和可能性。在前端开发的世界里,不断的学习和适应新技术是每个开发者的必经之路。

相关文章:

2024年虚拟DOM技术将何去何从?

从诞生之初谈起&#xff0c;从命令式到声明式&#xff0c;Web开发的演变之路 Web开发的起源与jQuery的统治 在Web开发的早期阶段&#xff0c;操作DOM元素主要依赖命令式编程。当时&#xff0c;jQuery因其易用性而广受欢迎。使用jQuery&#xff0c;开发者通过具体的命令操作DOM&…...

基于51单片机的恒温淋浴器控制电路设计

标题&#xff1a;基于51单片机的智能恒温淋浴器控制系统设计与实现 摘要&#xff1a; 本论文主要探讨了一种基于STC89C51单片机为核心控制器的恒温淋浴器控制系统的详细设计与实现。系统通过集成温度传感器实时监测水温&#xff0c;结合PID算法精确控制加热元件工作状态&#…...

【redis】redis的bind配置

在配置文件redis.conf中&#xff0c;默认的bind 接口是127.0.0.1&#xff0c;也就是本地回环地址。这样的话&#xff0c;访问redis服务只能通过本机的客户端连接&#xff0c;而无法通过远程连接&#xff0c; 这样可以避免将redis服务暴露于危险的网络环境中&#xff0c;防止一些…...

C++ 继承

目录 一、继承的概念及定义 1、继承的概念 2、继承定义 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派生类的默认成员函数 五、继承与友元 六、继承与静态成员 七、复杂的菱形继承及菱形虚拟继承 1、菱形继承 2、虚拟继承 3、例题 八、继承的总结和反思…...

了解ASP.NET Core 中的文件提供程序

写在前面 ASP.NET Core 通过文件提供程序来抽象化文件系统访问。分为物理文件提供程序(PhysicalFileProvider)和清单嵌入的文件提供程序(ManifestEmbeddedFileProvider)还有复合文件提供程序(CompositeFileProvider )&#xff1b;其中PhysicalFileProvider 提供对物理文件系统…...

竞赛保研 基于深度学习的人脸性别年龄识别 - 图像识别 opencv

文章目录 0 前言1 课题描述2 实现效果3 算法实现原理3.1 数据集3.2 深度学习识别算法3.3 特征提取主干网络3.4 总体实现流程 4 具体实现4.1 预训练数据格式4.2 部分实现代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 毕业设计…...

JavaScript音视频,JavaScript简单获取电脑摄像头画面并播放

前言 本章实现JavaScript简单获取电脑摄像头画面并播放的功能 兼容性(不支持Node.js) 需要注意的是,由于涉及到用户的隐私和安全,获取用户媒体设备需要用户的明确同意,并且可能需要在用户的浏览器中启用相关的权限。在某些浏览器中,可能需要用户手动开启摄像头权限。 …...

《JVM由浅入深学习【五】 2024-01-08》JVM由简入深学习提升分享

目录 JVM何时会发生堆内存溢出&#xff1f;1. 堆内存溢出的定义2. 内存泄漏的原因3. 堆内存溢出的常见场景4. JVM参数调优5. 实际案例分析 JVM如何判断对象可以回收1.可达性分析的基本思路2.实际案例3.可以被回收的对象4.拓展&#xff0c; 谈谈 Java 中不同的引用类型? 结语感…...

FastDFS之快速入门、上手

知识概念 分布式文件系统 通过计算机网络将各个物理存储资源连接起来。通过分布式文件系统&#xff0c;将网络上任意资源以逻辑上的树形结构展现&#xff0c;让用户访问网络上的共享文件更见简便。 文件存储的变迁&#xff1a; 直连存储&#xff1a;直接连接与存储&#xf…...

Vue 中的 ref 与 reactive:让你的应用更具响应性(中)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…...

【数据库基础】Mysql与Redis的区别

看到一篇不错的关于“Mysql与Redis的区别”的文章&#xff0c;转过来记录下~ 文章目录 一、数据库类型二、运行机制三、什么是缓存数据库呢&#xff1f;四、优缺点比较五、区别总结六、数据可以全部直接用Redis储存吗&#xff1f;参考资料 一、数据库类型 Redis&#xff1a;NOS…...

JVM工作原理与实战(六):类的生命周期-连接阶段

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、类的生命周期 1.加载&#xff08;Loading&#xff09; 2.连接&#xff08;Linking&#xff09; 3.初始化&#xff08;Initialization&#xff09; 4.使用&#xff08;Using&…...

【OCR】 - Tesseract OCR在Windows系统中安装

Tesseract OCR 在Windows环境下安装Tesseract OCR&#xff08;Optical Character Recognition&#xff09;通常包括以下几个步骤&#xff1a; 下载Tesseract 访问Tesseract的GitHub发布页面&#xff1a;https://github.com/tesseract-ocr/tesseract/releases找到适合你操作系…...

YOLOv8改进 | 损失函数篇 | SlideLoss、FocalLoss分类损失函数助力细节涨点(全网最全)

一、本文介绍 本文给大家带来的是分类损失 SlideLoss、VFLoss、FocalLoss损失函数,我们之前看那的那些IoU都是边界框回归损失,和本文的修改内容并不冲突,所以大家可以知道损失函数分为两种一种是分类损失另一种是边界框回归损失,上一篇文章里面我们总结了过去百分之九十的…...

计算机网络试题——填空题(附答案)

在OSI模型中&#xff0c;第一层是____________层。 答案&#xff1a;物理&#xff08;Physical&#xff09; TCP协议是一种_____________连接的协议。 答案&#xff1a;面向连接&#xff08;Connection-oriented&#xff09; IPv6地址的位数是____________。 答案&#xff1a;1…...

第二证券:股票私募仓位指数创近八周新高

1月8日&#xff0c;A股几大首要指数全线收跌&#xff0c;上证指数收于日内最低点2887.54点&#xff0c;间隔上一年5月份的阶段高点3418.95点现已跌去了15.54%。 不过&#xff0c;虽然商场仍未清晰止跌&#xff0c;私募基金们却现已进场“抄底”。私募排排网最新发布的私募仓位…...

35-javascript基础,引入方式;变量命名规范

html分为三部分&#xff1b;结构html&#xff0c;表现css&#xff0c;行为js&#xff1b;js就是javascript js包含三部分&#xff1a; ECMAScript&#xff1a;简称ES&#xff0c;ES5&#xff0c;ES6核心语法 DOM&#xff1a;获取和操作html元素的标准方法&#xff1b;BOM&am…...

笔试案例2

文章目录 1、笔试案例22、思维导图 1、笔试案例2 09&#xff09;查询学过「张三」老师授课的同学的信息 selects.*,c.cname,t.tname,sc.score from t_mysql_teacher t, t_mysql_course c, t_mysql_student s, t_mysql_score sc where t.tidc.cid and c.cidsc.cid and sc.sids…...

【嵌入式-网络编程】vmware中使用UDP广播失败问题

问题描述&#xff1a; 自己在vmware中搭建了2台虚拟机&#xff0c;虚拟机A向虚拟机A和虚拟机B发送广播信息&#xff0c;接收端在虚拟机A和虚拟机B&#xff0c;这个时候&#xff0c;由于没配置sin.sin_addr.s_addr htonl(INADDR_ANY);&#xff0c;而是配置的inet_pton(AF_INET,…...

2020年认证杯SPSSPRO杯数学建模D题(第二阶段)让电脑桌面飞起来全过程文档及程序

2020年认证杯SPSSPRO杯数学建模 D题 让电脑桌面飞起来 原题再现&#xff1a; 对于一些必须每天使用电脑工作的白领来说&#xff0c;电脑桌面有着非常特殊的意义&#xff0c;通常一些频繁使用或者比较重要的图标会一直保留在桌面上&#xff0c;但是随着时间的推移&#xff0c;…...

vue3 修饰符大全(近万字长文)

系列文章目录 TypeScript 从入门到进阶专栏 文章目录 系列文章目录前言一、事件修饰符&#xff08;Event Modifiers&#xff09;1、.stop&#xff08;阻止事件冒泡&#xff09;2、.prevent&#xff08;阻止事件的默认行为&#xff09;3、.capture&#xff08;使用事件捕获模式…...

HarmonyOS@State装饰器:组件内状态

State装饰器&#xff1a;组件内状态 State装饰的变量&#xff0c;或称为状态变量&#xff0c;一旦变量拥有了状态属性&#xff0c;就和自定义组件的渲染绑定起来。当状态改变时&#xff0c;UI会发生对应的渲染改变。 在状态变量相关装饰器中&#xff0c;State是最基础的&…...

如何让GPT支持中文

上一篇已经讲解了如何构建自己的私人GPT&#xff0c;这一篇主要讲如何让GPT支持中文。 privateGPT 本地部署目前只支持基于llama.cpp 的 gguf格式模型&#xff0c;GGUF 是 llama.cpp 团队于 2023 年 8 月 21 日推出的一种新格式。它是 GGML 的替代品&#xff0c;llama.cpp 不再…...

使用开源通义千问模型(Qwen)搭建自己的大模型服务

目标 1、使用开源的大模型服务搭建属于自己的模型服务&#xff1b; 2、调优自己的大模型&#xff1b; 选型 采用通义千问模型&#xff0c;https://github.com/QwenLM/Qwen 步骤 1、下载模型文件 开源模型库&#xff1a;https://www.modelscope.cn/models mkdir -p /data/…...

Java工程师面试题解析与深度探讨

Java工程师面试题解析与深度探讨 第一部分&#xff1a;引言 Java作为一门广泛应用的编程语言&#xff0c;拥有庞大的生态系统&#xff0c;Java工程师因此成为众多企业追逐的目标。而在Java工程师的招聘中&#xff0c;面试是了解候选人技能和经验的核心环节。本文将深入探讨一…...

Linux下安装JET2

0. 说明&#xff1a; JET2是一个基于Joint Evolutionary Trees的利用序列和结构信息预测蛋白质界面的软件&#xff0c;详情见: http://www.lcqb.upmc.fr/JET2/JET2.html&#xff0c;http://www.lgm.upmc.fr/JET/JET.html 和 https://doi.org/10.1371/journal.pcbi.1004580 本…...

【PostgreSQL】表管理-表继承

PostgreSQL 表继承 PostgreSQL 实现了表继承&#xff0c;这对于数据库设计人员来说是一个有用的工具。&#xff08;SQL&#xff1a;1999 及更高版本定义了类型继承功能&#xff0c;该功能在许多方面与此处描述的功能不同。 让我们从一个例子开始&#xff1a;假设我们正在尝试…...

Dijkstra算法——邻接矩阵实现+路径记录

本文是在下面这篇文章的基础上做了一些补充&#xff0c;增加了路径记录的功能。具体Dijkstra的实现过程可以参考下面的这篇文章。 [jarvan&#xff1a;Dijkstra算法详解 通俗易懂](Dijkstra算法详解 通俗易懂 - jarvan的文章 - 知乎 https://zhuanlan.zhihu.com/p/338414118) …...

Vim基础操作

参考B站UP&#xff1a;正月点灯笼 vim入门教程&#xff08;共3讲&#xff09; 以下总结&#xff0c;部分搬运自评论区&#xff0c;楼主&#xff1a;-不是飞鱼QAQ&#xff0c;修改部分内容。 vim分为 命令 和 编辑 模式 i进入编辑模式&#xff08; - - INSERT - - &#xff09;…...

Mac上安装 Node.js 的版本管理工具 n,以及 n 使用,的使用

安装 最近刚更换 Mac 本进行项目的开发&#xff0c;刚上手 Mac 本还不是很熟练&#xff0c;需要安装 Node.js 的包管理工具 在 Windows 上我是实用的 nvm 来管理的 Node 版本&#xff0c;但是我尝试下载 Nvm &#xff0c;发现下载安装后的 Nvm 无法使用&#xff0c;提示 “Th…...

现在哪个网站可以做外贸/关键词指数

前言 互联网时代的到来&#xff0c;让我们获取知识变得更加简单&#xff0c;理论上讲只要你想学&#xff0c;便会有不尽的知识等你&#xff0c;只要方法得当&#xff0c;够努力&#xff0c;任何人都可以都有可能成为大牛。 自己在努力的基础上&#xff0c;还学习了一些高效的…...

栾城seo整站排名/中国十大企业培训公司

成员对象和封闭类 有成员对象的类叫封闭类 class CTyre //轮胎类 { private:int radius; //半径int width; //宽度 public:CTyre(int r, int w):radius(r), width(w) {} };class CEngine //引擎类 { };class CCar //汽车类&#xff0c;封闭类 { private:int price…...

佛山高端网站制作/怎么做网络平台

最近在复习Zookeeper&#xff0c;leader选举过程&#xff0c;这位博主写的很好&#xff0c;转载一下 https://www.cnblogs.com/leesf456/p/6107600.html 一、前言 前面学习了Zookeeper服务端的相关细节&#xff0c;其中对于集群启动而言&#xff0c;很重要的一部分就是Leader…...

天津网站开发tjniu/百度快照怎么用

用Linux守护进程检测某个程序是否运行 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 环境&#xff1a; 主机:Fedora12 目标板&#xff1a;SC6410 目标板LINUX内核版本&#xff1a;2.6.36 实现功能&#xff1a; 做的一个嵌入式板子开机会自启动一个程序&am…...

wordpress系列文章/seo专业培训中心

问题报错&#xff1a; Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-resources-plugin:2.6:resources 问题原因&#xff1a; m2e在其执行maven的生命周期管理时没有定义该插件 解决办法&#xff1a; 1、eclipse&#xff1a…...

ftontpage如何做网站/搜索引擎优化好做吗

Google I/O大会上&#xff0c;Google向 Android 引入了新 App 动态化框架&#xff08;Android App Bundle, AAB&#xff09;&#xff0c;被看作是对Android未来发展具有颠覆性的动态化解决方案。在给Android App带来便利的同时&#xff0c;也给移动安全领域带来了新的挑战&…...