正规代做毕业设计的网站,公司做分享网站好吗,建筑工程网站建站方案,郑州制作网站软件从诞生之初谈起#xff0c;从命令式到声明式#xff0c;Web开发的演变之路 Web开发的起源与jQuery的统治 在Web开发的早期阶段#xff0c;操作DOM元素主要依赖命令式编程。当时#xff0c;jQuery因其易用性而广受欢迎。使用jQuery#xff0c;开发者通过具体的命令操作DOM从命令式到声明式Web开发的演变之路 Web开发的起源与jQuery的统治 在Web开发的早期阶段操作DOM元素主要依赖命令式编程。当时jQuery因其易用性而广受欢迎。使用jQuery开发者通过具体的命令操作DOM比如 // jQuery 示例
$(ol li).click(function() {});
let li $(li我是一个列表项/li);
$(ol).append(li); 这种方式虽然直观但随着应用复杂度的提高代码管理变得越来越困难。 声明式编程的兴起React与JSX 2013年Facebook的Jordan Walke提出了一个革命性的想法将FaceBook在2010年开发的XHP功能迁移到JavaScript借助JSX扩展形成新的编码风格。与命令式不同声明式编程不再关注如何操作DOM而是描述希望DOM是什么样子。例如 // React组件示例
const Component (ul{data.map(item MyItem data{item} /)}/ul
); 声明式编程使得组件化开发成为可能极大地提高了代码的可维护性和可扩展性。 虚拟DOM的出现 React的一个关键概念是虚拟DOMVirtual 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 FiberReact 16引入了React Fiber。通过优先级分割的中断机制改善了因虚拟DOM树深度遍历造成的主进程阻塞问题。 Vue 3Vue的创始人尤雨溪在“Vue 3的设计”中提到他致力于寻找突破虚拟DOM的性能瓶颈。 SvelteSvelte的作者Rich Harris提出了“虚拟DOM纯属开销”的观点强调在某些情况或频繁更新下虚拟DOM数据驱动模型带来的不必要开销。 2024年的虚拟DOM还需要吗 当前非虚拟DOM框架的主力Svelte 虚拟DOM的现状 目前虚拟DOM仍然是主流框架中的主导技术。React持续在迭代中探索更合理的调度模式而Vue3专注于优化虚拟DOM的diff算法。ivi和Inferno在虚拟DOM框架的性能前沿领先。尽管虚拟DOM在主流框架中仍占主导地位但像Svelte和Solidjs这样的非虚拟DOM框架开始将它们的新模式引入公众视野。 Svelte的创新 Rich HarrisSvelte和rollup的作者将他在代码打包策略上的专长带入了JavaScript框架领域。 理念“最好的API是根本没有API” —— Rich Harris Svelte3Svelte3经过重大改变成为一个更轻量级、语法更简洁、代码量更少的JavaScript框架用于实现响应性。 Svelte在编译阶段直接将声明式代码转换为更高效的命令式代码并减少运行时代码。例如 scriptlet count 0;function handleClick() {count 1;}$: {console.log(当前计数为 ${count});}
/scriptdiv classx-three-year on:click{handleClick}div classno-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能够生成更少的代码来实现相同的功能。无需虚拟DOMSvelte避免了虚拟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 createSignalT(value?: T,options?: SignalOptionsT | undefined
): SignalT | undefined {options options ? Object.assign({}, signalOptions, options) : signalOptions;const s: SignalStateT | 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: SetterT | 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 SignalStateT extends SourceMapValue {value: T;observers: Computationany[] | null;observerSlots: number[] | null;tValue?: T;comparator?: (prev: T, next: T) boolean;
} Computation在全局作用域中有一个Listener用来临时存储类型为Computation的观察者。在组件渲染createRenderEffect或调用createEffect时通过updateComputation方法为全局Listener赋值为后续的依赖跟踪打下基础。 let Listener: Computationany | null null; export interface ComputationInit, Next extends Init Init extends Owner {fn: EffectFunctionInit, Next;state: ComputationState;tState?: ComputationState;sources: SignalStateNext[] | null;sourceSlots: number[] | null;value?: Init;updatedAt: number | null;pure: boolean;user?: boolean;suspense?: SuspenseContextType;
} function updateComputation(node: Computationany) {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 Memoany)? (node as Memoany).tValue: node.value,time);
//...Listener listener;Owner owner;
} 由于信号的读取通过函数调用获取数据。 div classno-open style{{ color: blue }}{count: ${count()}}/div 信号的读取和写入 读取信号在任何地方读取SignalState时都会调用readSignal函数。当前临时存储在全局上下文中的“观察者”Listener指引用SignalState的地方将被放入其观察者数组中观察者源将指向当前信号实现数据绑定。最后返回相应的SignalState值。 export function readSignal(this: SignalStateany | Memoany) {const runningTransition Transition Transition.running;if ((this as Memoany).sources (runningTransition ? (this as Memoany).tState : (this as Memoany).state)) {if ((runningTransition ? (this as Memoany).tState : (this as Memoany).state) STALE)updateComputation(this as Memoany);else {const updates Updates;Updates null;runUpdates(() lookUpstream(this as Memoany), 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: SignalStateany | Memoany, 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 Memoany).observers) markDownstream(o as Memoany);}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 classx-three-year onClick{() setCount((pre) pre 1)}div classno-openfoobar/divdiv classno-open{count()}/div/div);
} 在这个例子中我们有一个计数器每次点击时count会增加。这是通过setCount函数实现的它是createSignal的一部分。 点击事件触发更新过程 当点击事件发生时会触发setCount进而触发writeSignal的行为如之前所述。这会导致updateComputation被触发接着进行readSignal以获取SignalState。整个调用栈过程如下 7、Solid中需注意的几点 属性的解构和合并 在Solid中有一些特别需要注意的地方特别是关于属性props的处理 不能直接解构和合并响应式属性不能直接使用剩余rest和展开spread语法来解构和合并响应式属性数据。这是因为通过浅拷贝形式的解构同样Object.assign方法也不可用进行的拷贝会割断信号的更新导致其失去响应性并脱离跟踪范围。 解构会导致失去响应性直接解构会导致解构出的值失去响应性并从跟踪中分离。通常在Solid的原语或JSX之外访问props对象上的属性可能导致失去响应性。此外像展开操作符和Object.assign这样的函数也可能导致失去响应性。 示例 //no
function Other({count}) {return (divdiv{count}/div/div);
}//yes
function Other(props) {return (divdiv{props.count}/div/div);
}function App() {const [count, setCount] createSignal(0);return (div classx-three-year onClick{() setCount((pre: any) pre 1)}div classno-openfoobar/divdiv classno-open{count()}/divOther count{count()}/Other/div);
} //yes
function Other({count}) {return (divdiv{count()}/div/div);
}function App() {const [count, setCount] createSignal(0);return (div classx-three-year onClick{() setCount((pre: any) pre 1)}div classno-openfoobar/divdiv classno-open{count()}/divOther 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下载次数查询网站 目前State of js 只有 2022 年的数据仅供参考但从数据中可以看出React、Vue、Angular 在使用量上仍然占据主导地位。但从满意度来看已经出现了两大非虚拟DOM主力。 Svelte和Solid超越虚拟DOM的前端框架 Svelte和Solid的崛起不仅标志着虚拟DOM的淡出更多的编译时任务的加入也展示了开发的新可能性。这两个框架都不使用虚拟DOM但提供了高效的更新机制和优化的编译过程。 性能比较 根据最新的js-framework-benchmarkChrome 119 — OSX数据Svelte和Solid在性能上相似。在DOM操作时间方面Solid似乎表现更佳而Svelte在内存使用和启动时间方面有更好的数据。 与其他框架的对比 这边我提取了 js-framework-benchmark (Chrome 119 — OSX) 的公开状态选取了 ivi、Inferno、Solid、Svelte、Vue、React 进行整体对比。从结果来看Svelte 和 Solid 的表现略好于大家熟知的 Vue 和 React。但相比像 ivi、Inferno 这样以性能着称的虚拟 DOM 框架并没有什么优势。 国外大佬 Ryan Carniato在他的研究《DOM渲染的最快方法》一文中使用标签模板和HyperScript作为Solid的渲染模板并将其与其他在js-framework-benchmark上表现良好的JavaScript框架进行了比较。结果显示虚拟DOM框架和非虚拟DOM框架的性能相似特别是一些高性能的虚拟DOM框架。 最终结果表明虚拟DOM框架和非虚拟DOM框架具有相似的性能严格来说这是针对一些高性能的虚拟DOM框架。因此没有最好的技术。在历史上不断修改和优化的过程中虚拟 DOM 的速度并不慢。不断探索是对技术最大的尊重。 结束 在2024年的前端框架领域我们看到了多样化的技术选择和不断的创新。每种技术都有其适用的场景和优势开发者应根据项目的具体需求和上下文来选择最适合的框架。虚拟DOM的主导地位表明它在许多情况下仍然是一个有效的选择但Svelte和Solid等新兴框架的出现也为开发者提供了更多的选择和可能性。在前端开发的世界里不断的学习和适应新技术是每个开发者的必经之路。