|
诞生之初从命令式到声明式在上古流行的字符串拼接时代,jQuery一家独大,当时jQuery的语法还是停留在那种命令式DOM操作之中,$("ol li").click(function() {})let li = $("我是一个li");$("ol").append(li);而在2013年,Facebook的JordanWalke提出来了:把2010年FaceBook做出来的XHP的拓展功能迁移到Javascript中,形成以JSX作为拓展的新编码形式,并且把写法由命令式转变为声明式,像这样://声明一个 data列表const Component = ( {data.map(item => )} );而在声明式框架的建立之时,需要DOM操作这种“行为”,交给框架处理,并引发一些思考:既然DOM操作集中交给框架了,那框架岂不是可以去“批处理”DOM操作,更好的减少开销?既然开始写声明式了,那如何让数据和DOM关联起来?如果每次数据发生变化,该如何监听数据源?虚拟DOM乍现计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决. --------DavidWheeler而当时虚拟DOM,也就是在代码和实际DOM操作,由框架做了一层中间层,从而实现代码->虚拟DOM树->真实DOM树;这个概念是由React率先开拓,随后被许多不同的框架采用,并且当时有一本书《高性能的javascript》,具体在第三章开头,里面有个观点就是:DOMscriptingisexpensive,andit'sacommonperformancebottleneckinrichwebapplications而前React核心团队PeteHunt也在2013年时,对React的宣传演讲中吐槽了一波重复性DOM操作的“巨大开销”: 《重新思考典范实例的意义》。img这套虚拟DOM的优势在于:打开函数式UI编程的大门,使得组件抽象化,使得代码更易维护跨平台,因为虚拟DOM本质上只是一个Javascript对象,作为抽象层还能提供给其他应用使用,比如小程序、IOS应用、Android应用等。数据绑定,更新视图时,减少DOM操作:可以将多次DOM操作合并为一次操作,比如添加100个节点原来是一个一个添加,现在是一次性添加,减少浏览器回流(比如1000个节点的DOM操作,合并为1次,进行批处理)const fragment = document.createDocumentFragment();for(let i = 0; i letcount=0; functionhandleClick(){ count+=1; } $:{ console.log(`thecurrentcountis${count}`); } {`当前count{count}`}我们可以看到通过基本的声明,我便得到了一个响应式的变量,继而通过点击事件的绑定,得到一个通过点击驱动视图数据的普通组件而此时通过Svelte的编译后会自动给响应式数据打上标记$invalidatefunction instance($self, $props, $invalidate) { let count = 0; function handleClick() { $invalidate(0, count += 1); } $self.$.update = () => { if ($self.$.dirty & /*count*/ 1) { $: { console.log(`the current count is ${count}`); } } }; return [count, handleClick];}VueVapormode尤雨溪曾在知乎上提及过Vue2时期引入虚拟DOM的问题(Vue的理念问题)React的vdom其实性能不怎么样。Vue2.0引入vdom的主要原因是vdom把渲染过程抽象化了,从而使得组件的抽象能力也得到提升,并且可以适配DOM以外的渲染目标。这一点是借鉴React毫无争议继Svelte将预编译这一套带入大众视野之后,Vue3在编译时也有自身的编译优化----“带编译时信息的虚拟DOM”,详情可以在官网的介绍中查看,其实也就是在编译阶段针对部分静态节点附带上编译信息,使得在虚拟DOM树遍历阶段减少不必要的开销,一定程度上优化了虚拟DOM带来的问题。而在2022年稀土掘金开发者大会上,尤雨溪《2022前端生态趋势》在演讲中便提及到对“无虚拟DOM”的探索——Vuevapor模式。虽然这并不是信号的必要特征,但如今这个概念经常与细粒度订阅和更新的渲染模型一起讨论。由于使用了虚拟DOM,Vue目前依靠编译器来实现类似的优化。然而,我们也在探索一种新的受Solid启发的编译策略(VaporMode),它不依赖于虚拟DOM,而是更多地利用Vue的内置响应性系统。这种预编译模式性能上先不说,首先体积上肯定是更偏向轻量级,其实也属于vue对未来前端框架的趋势一种新探索。SolidjsSoidjs,你也可以叫它Solid,它和Svelte同理,二者都是基于编译的响应式系统,Solidjs的颗粒度响应是通订阅发布模式进行数据驱动的,并且曾在 js-framework-benchmark斩获榜首而以性能出名,其语法更接近React,对React重度用户较为友好。我们在Solid的官方playground上可以看到框架在编译阶段将jsx->html的输出结果:Solid在官网上标为:“真正的响应式”,与其说是真正的响应式,倒不如说像React,是根据状态变化,更改虚拟DOM,重新render(也有可能是父组件更新),对比起来Solidjs、Svelte响应单独针对的是数据级别的粒度,React响应的体量是组件级别的粒度。下面我们来看看,Solidjs的“颗粒度响应”是的设计与实现。createSignal主要看下createSignal的状态管理,很多文章会以为Solid用的是基于Proxy的响应式,实则不然,只是部分API用了Proxy,其响应式还是用的Knockout那一套发布订阅的数据响应。首先我们得先知道2个重要的角色类型:SignalState、Computation信号主要通过一个对象存储,类型为typeSignalStatevalue:当前的值observers:观察者数组,类型为typeComputationobserverSlots:观察者对象在数组的位置comparator:比较器,通过比较则更改value,默认false,浅比较export function createSignal( value?: T, options?: SignalOptions): Signal { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s: SignalState = { 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 = (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];}export interface SignalState extends SourceMapValue { value: T; observers: Computation
|
|