暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Vue vs Svelte: 两者实现机制有何不同?

浮生知记 2021-09-27
2665

Svelte 是什么?


Svelte 作为一个新的 JavaScript 框架,在web开发领域引起了轰动。你可能在某篇文章听说过 Svelte,对它描述最多的词汇应该是“非常快”。


简而言之,一个 app 如果由 Svelte 编写确实比用其他框架在生产环境的表现快一些(这些框架可以包括 jQuery)。这个“快”,我指的是在生产环境中下载bundle 、在浏览器中加载bundle 以及每次响应数据更新时更改 UI 所需的时间。(本文主要介绍最后一个部分)


强大的性能依赖 Svelte 的实现方法。


简单说,Svelte 并不是一个框架,它更像是个编译器。从技术原理上来说,Svelte 代码并不是原生 js 代码。编译器只是接受 Svelte 代码,然后生成生产环境所需要的 js 代码。与自己所熟悉的知识进行对比,会更好的帮助你理解新的概念。因此在本文,我们会对 Vue 以及 Svelte 这两个框架的实现方式进行对比。




代码示例


我们先写一些代码,然后再深入对比这两个框架的细节。




Vue

可以从 Github 拉取 Vue 示例仓库。

这样,在你的电脑上就会生成一个 my-vue-app 的项目。


运行方式如下:


这个 Vue 应用就会在 localhost:8080 运行




Svelte


我们会运行另外的 git clone 命令去获取 Svelte 示例应用(不一样的分支,不一样的文件夹名而已)



你也可以运行下面的命令,自己创建一个新的 Svelte 应用:



这个命令会从 Svelte 官方 Github 下载一个 Svelte 项目


运行方式如下:


Svelte 应用会在 localhost:5000 运行



这两个示例项目交互逻辑一致:

  • 如果你输入你的姓和名,应用会显示你的全名

  • 点击 reset 按钮,可以清空之前的输入


现在,让我们开始比较这两者吧!




Code


Vue 应用的代码主要在 App.vue 这个文件里面(路径:/my-vue-app/src/App.vue)



Svelte 应用的代码主要在 App.svelte 文件(路径:/my-svelte-app/src/App.svelte)



相比之下,尽管 Vue 应用的代码更多,但它代码的可读性更佳一些。


Svelte 应用的代码更加简洁,但对于新手来说,还是需要一些理解成本。


两者有一些共性:

  • 两个响应式变量 firstName 以及 lastName

  • 变量 fullName 依赖于两个响应式变量

  • 两个输入框对应着这两个响应式变量

  • 一个按钮,绑定了重置变量的事件


下一步,我们来一起理解下 Svelte 应用代码的具体含义。



Svelte 简介


上面应用中,最明显的应该是两个响应式变量:


📃 /my-svelte-app/src/App.svelte



重新赋值给这两个变量(例如 reset 方法),组件会重新渲染:


📃 /my-svelte-app/src/App.svelte



firstName 以及 lastName 看上去就是普通的 js 变量,实际上它们属于被监听的“变量”。再次,Svelte 代码并不是真实的 js 代码


另一个比较不一样的是 fullName 的定义:


📃 /my-svelte-app/src/App.svelte


$ 符是用来创建一个叫做 fullName 的计算变量,只要 firstName 或者 lastName 其中一个发生变化,fullName 会根据这两个变量跟着发生变化。如果部署用 $ 符, fullName 在初始化之后便不会发生变化。


下一步,来对比下 Svelte 应用与 Vue 应用的模板语法:



只有指令不一样,但两者代码风格上非常相似。


这两种框架中的事件处理类似,只是在Vue中我们必须使用this关键字来访问状态(如果我们使用Vue的composition api,就不必使用this关键字)




你可能已经注意到了,代码中没有使用 props。我们来给 Svelte 应用添加个吧。


props 的语法只需要使用 export 和 let:


📃 /my-svelte-app/src/App.svelte


使用了 export 关键词,不代表要暴露任何数据,相反的,将数据作为 props 暴露出去。


可以设置 props 默认值:


📃 /my-svelte-app/src/App.svelte


使用这个 props 来控制 reset 按钮的渲染。


📃 /my-svelte-app/src/App.svelte



浏览器刷新下,你会发现 reset 按钮消失了,原因是 canReset 默认为 false:

如果想要 reset 按钮重新出现,我们需要设置一下 canReset 这个 prop。可以在 main.js 中设置一下。


直接新增 canReset 这个 props 属性:


📃 /my-svelte-app/src/main.js

(你可以把 name 属性去掉,这只是个示例)


返回浏览器,reset 按钮出来了:


Svelte 的语法看上去很像 js,结合编译器之后,会发生很多神奇的变化。




Svelte 响应式原理(提前编译)


上文示例中,Svelte 变量无需用一个特殊的函数包裹就能实现响应式,具体是指 firstName、lastName、fullName。


📃 /my-svelte-app/src/App.svelte



这个示例中,只要重新赋值 firstName 或者 lastName,fullName 会自动更新。只要这三个变量中一个更新,HTML 就会自动更新。当然,props 变量 canRest 传入的值发生变化时,HTML 也会更新。


在提示一下,这不是真正的 js 代码,在 js 中,变量定义之后是不会自动完成响应的。在其它框架,我们需要使用特定的 api 来声明响应式数据。例如在 Vue 中,我们需要 data() 或者 composition api 中的 ref();在 React 中,我们需要使用 useState。


实际使用中,Svelte 应用代码经过编译后才会上线,我们在编写 Svelte 应用时,只需要使用普通的赋值语句,编译器会自动帮我们生成响应式变量的代码




接下来,让我们检查下 Svelte 实际生成的代码,看下一个普通的变量是怎么变成响应式变量。


查看 runtime 代码,只需要打开 Chrome 的调试台,点击 Sounrce tab。


然后你就能看到 sidebar 中有一个 build 文件夹。文件夹内,有一个 bundle.js 文件,这里就是运行时的 js 代码。



代码很多,我们只需要关注 instance 方法:


📃 /bundle/bundle.js


以上是精简之后的代码,看起来有点乱,没事,我们一起“搞”下。


最感兴趣的应该是 reset 函数了,因为在这其中,我们能发现响应式数据的秘密了。

看起来,响应式的秘密都在 $$invalidate 这个函数中了。


每次重新赋值之后,$$invalidate 函数通知其他代码,某个变量已经更新了,它应该做一些 “dirty” 标记了(bundle 文件中有个 make_dirty 函数)。


每一个 $$invalidate 调用时会传入一个变量 ID,上文中,1 代表 firstName,2 代表 lastName。在 $$self.$inject_state 中,你能找到所有的变量以及对应的 ID。


📃 /bundle/bundle.js


只要有一个变量被标记为 “dirty”,update 函数就会调用:


📃 /bundle/bundle.js


当其中一个变量发生变化,Svelte 就会调用 update 方法去“刷新”数据流。这就是 Svelte 内部的小秘密。




位运算


update函数实际上是利用按位操作来决定是否需要更新 fullName。位运算利用 0和1 来进行相关操作。位运算可以更快地执行一系列设计多个响应式变量的条件判断与逻辑运算。


例如,数字6以二进制形式标识:110


110 可以表示我们所有响应式变量的唯一组合(按照 ID 从右向左):


Svelte将使用这个 “脏” 配置以及一个位运算符(&),来确定是否必须更新fullName变量。我们现在不打算讨论复杂的位运算。简而言之,当一个或多个红色变量(上图中)被标记为“dirty”时,6(或110)才会使判断条件为真。


总结一下,每当一个响应式变量发生改变,$$invalidate 将被调用,然后将调用 update 函数,以确保依赖于计算属性的任何变量也得到更新(例如,上文中的 fullName)


看到这里,你会发现,编译是 Svelte 框架高性能的关键。


接下来,让我们看看Vue是的原理。




Vue 响应式原理(运行时)


与大多数其他前端框架一样,Vue主要是一个运行时工作的 js 框架。这意味着Vue的响应式数据并不是你在应用中定义的所有变量。


为了实现运行时的响应数据(而不是像 Svelte 那样的编译时),Vue需要一种方法来通知何时一个响应式变量发生了变化,并根据变化重新渲染 UI。


Vue 的实现依赖于基础的原生 js 特性以及 DOM diff 算法。


Vue 的响应式原理类似:

  1. Vue 将为每个特定对象进行特殊设置(Vue2 使用 ES5 中的 get/set,Vue3 中使用 ES6 中的 proxy/reflect 特性)

  2. 定义完成之后,每个对象将会在被调用时调用 getter/setter 或者 proxy/relect 特定处理器

  3. 对象属性被成功访问之后,处理器会记录下访问了什么属性。例如,调用 this.firstName 会重新设置计算属性 fullName。在框架内部,这种关系被称为 “dep”;意味着,fullName 依赖 firstName

  4. 每当 firstName 值发生更改时(如在reset函数中),它将触发 firstName 的处理程序。处理程序确保依赖于 firstName 的所有值也得到更新

  5. 最后,Vue 将基于所有新数据计算出一个虚拟 DOM。虚拟 DOM 是真实  DOM 的轻量级表示。然后 Vue 将新的虚拟 DOM 树与以前的虚拟 DOM 树进行比较,以确定真实 DOM 的哪一部分需要更改。虚拟 DOM 的使用是为了防止对真实 DOM 进行不必要的操作


相比于 Vue,Svelte 不会在运行时进行数据的响应式处理,这就是为什么 Svelte 快!


还有一些其它的框架也是使用类似 Svelte 的原理,先预编译响应式数据,比如 Elm,但是 Elm 在运行时依赖虚拟 DOM,它的性能水平不及 Svelte。


性能快并不在于是不是使用了一个编译器,在于最终的代码被编译成什么。Svelte 应用代码被直接编译成操作 DOM 的代码,不需要在每次值更新的时候运行 DOM diff 算法,这就是它快的原因。


你可以认为 Svelte 编译器是一个非常高效和可靠的程序员,它可以扫描Svelte 代码并为你编写一个普通的 JavaScript 版本的应用程序。因此,运行 Svelte 应用程序与十年前制作的 jQuery 应用程序没有太大区别,但Svelte中的源代码更易于维护。




番外


Svelte 超高的性能让其在前端界崭露头角,引入了编译器的概念,让前端生态有了更多的可能性。如果极端性能会影响开发者的框架选择,那么相信主流框架一定会跟进。

文章转载自浮生知记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论