More than React(三)虚拟DOM已死?

本系列的上一篇文章组件对复用性有害?探讨了如何在前端开发中编写可复用的界面元素。本篇文章中将从性能和算法的角度比较 Binding.scala 和其他框架的渲染机制。

\u0026#xD;\u0026#xD;

Binding.scala 实现了一套精确数据绑定机制,通过在模板中使用 bind 和 for/yield 来渲染页面。你可能用过一些其他 Web 框架,大多使用脏检查或者虚拟 DOM 机制。和它们相比,Binding.scala 的精确数据绑定机制使用更简单、代码更健壮、性能更高。

\u0026#xD;\u0026#xD;

ReactJS 虚拟 DOM 的缺点

\u0026#xD;\u0026#xD;

比如, ReactJS 使用虚拟 DOM 机制,让前端开发者为每个组件提供一个 render 函数。render 函数把 props 和state 转换成 ReactJS 的虚拟 DOM,然后 ReactJS 框架根据 render 返回的虚拟 DOM 创建相同结构的真实 DOM.

\u0026#xD;\u0026#xD;

每当 state 更改时,ReactJS 框架重新调用 render 函数,获取新的虚拟 DOM 。然后,框架会比较上次生成的虚拟 DOM 和新的虚拟 DOM 有哪些差异,然后把差异应用到真实 DOM 上。

\u0026#xD;\u0026#xD;

这样做有两大缺点:

\u0026#xD;\u0026#xD;
  1. 每次 state 更改,render 函数都要生成完整的虚拟 DOM. 哪怕 state 改动很小,render函数也会完整计算一遍。如果 render 函数很复杂,这个过程就白白浪费了很多计算资源。\u0026#xD;\t
  2. ReactJS 框架比较虚拟 DOM 差异的过程,既慢又容易出错。比如,假如你想要在某个 \u0026lt;ul\u0026gt; 列表的顶部插入一项\u0026lt;li\u0026gt; ,那么 ReactJS 框架会误以为你修改了 \u0026lt;ul\u0026gt; 的每一项 \u0026lt;li\u0026gt;,然后在尾部插入了一个 \u0026lt;li\u0026gt;。\u0026#xD;

这是因为 ReactJS 收到的新旧两个虚拟 DOM 之间相互独立,ReactJS 并不知道数据源发生了什么操作,只能根据新旧两个虚拟 DOM 来猜测需要执行的操作。自动的猜测算法既不准又慢,必须要前端开发者手动提供 key 属性、shouldComponentUpdate 方法、componentDidUpdate 方法或者 componentWillUpdate 等方法才能帮助 ReactJS 框架猜对。

\u0026#xD;\u0026#xD;

AngularJS 的脏检查

\u0026#xD;\u0026#xD;

除了类似 ReactJS 的虚拟 DOM 机制,其他流行的框架,比如 AngularJS 还会使用基于脏检查的定值算法来渲染页面。

\u0026#xD;\u0026#xD;

脏检查算法和 ReactJS 有一样的缺点,无法得知状态修改的意图,这使得 AugularJS 必须反复执行$digest轮循、反复检查各个ng-controller中的各个变量。除此之外,AngularJS 更新 DOM 的范围往往会比实际所需大得多,所以会比 ReactJS 还要慢。

\u0026#xD;\u0026#xD;

Binding.scala 的精确数据绑定

\u0026#xD;\u0026#xD;

Binding.scala 使用精确数据绑定算法来渲染 DOM 。

\u0026#xD;\u0026#xD;

在 Binding.scala 中,你可以用 @dom 注解声明数据绑定表达式。@dom 会自动把 = 之后的代码包装成 Binding 类型。

\u0026#xD;\u0026#xD;

比如:

\u0026#xD;\u0026#xD;
\u0026#xD;@dom val i: Binding[Int] = 1\u0026#xD;@dom def f: Binding[Int] = 100\u0026#xD;@dom val s: Binding[String] = \"content\"
\u0026#xD;\u0026#xD;

@dom 既可用于 val 也可以用于 def ,可以表达包括 Int 、 String 在内的任何数据类型。

\u0026#xD;\u0026#xD;

除此之外,@dom 方法还可以直接编写 XHTML,比如:

\u0026#xD;\u0026#xD;
\u0026#xD;@dom val comment: Binding[Comment] = \u0026lt;!-- This is a HTML Comment --\u0026gt;\u0026#xD;@dom val br: Binding[HTMLBRElement] = \u0026lt;br/\u0026gt;\u0026#xD;@dom val seq: Binding[BindingSeq[HTMLBRElement]] = \u0026lt;br/\u0026gt;\u0026lt;br/\u0026gt;
\u0026#xD;\u0026#xD;

这些 XHTML 生成的 CommentHTMLBRElement 是 HTML Node 的派生类。而不是 XML Node

\u0026#xD;\u0026#xD;

每个 @dom 方法都可以依赖其他数据绑定表达式:

\u0026#xD;\u0026#xD;
\u0026#xD;val i: Var[Int] = Var(0)\u0026#xD;@dom val j: Binding[Int] = 2\u0026#xD;@dom val k: Binding[Int] = i.bind * j.bind\u0026#xD;@dom val div: Binding[HTMLDivElement] = \u0026lt;div\u0026gt;{ k.bind.toString }\u0026lt;/div\u0026gt;
\u0026#xD;\u0026#xD;

通过这种方式,你可以编写 XHTML 模板把数据源映射为 XHTML 页面。这种精确的映射关系,描述了数据之间的关系,而不是 ReactJS 的 render 函数那样描述运算过程。所以当数据发生改变时,只有受影响的部分代码才会重新计算,而不需要重新计算整个 @dom 方法。

\u0026#xD;\u0026#xD;

比如:

\u0026#xD;\u0026#xD;
\u0026#xD;val count = Var(0)\u0026#xD;\u0026#xD;@dom def status: Binding[String] = {\u0026#xD;  val startTime = new Date\u0026#xD;  \"本页面初始化的时间是\" + startTime.toString + \"。按钮被按过\" + count.bind.toString + \"次。按钮最后一次按下的时间是\" + (new Date).toString\u0026#xD;}\u0026#xD;\u0026#xD;@dom def render = {\u0026#xD;  \u0026lt;div\u0026gt;\u0026#xD;    { status.bind }\u0026#xD;    \u0026lt;button onclick={ event: Event =\u0026gt; count := count.get + 1 }\u0026gt;更新状态\u0026lt;/button\u0026gt;\u0026#xD;  \u0026lt;/div\u0026gt;\u0026#xD;}
\u0026#xD;\u0026#xD;

以上代码可以在ScalaFiddle实际运行一下试试。

\u0026#xD;\u0026#xD;

注意,status 并不是一个普通的函数,而是描述变量之间关系的特殊表达式,每次渲染时只执行其中一部分代码。比如,当 count 改变时,只有位于 count.bind 以后的代码才会重新计算。由于 val startTime = new Date 位于count.bind 之前,并不会重新计算,所以会一直保持为打开网页首次执行时的初始值。

\u0026#xD;\u0026#xD;

有些人在学习 ReactJS 或者 AngularJS 时,需要学习 key 、 shouldComponentUpdate 、 $apply 、 $digest 等复杂概念。这些概念在 Binding.scala 中根本不存在。因为 Binding.scala 的 @dom 方法描述的是变量之间的关系。所以,Binding.scala 框架知道精确数据绑定关系,可以自动检测出需要更新的最小部分。

\u0026#xD;\u0026#xD;
结论
\u0026#xD;\u0026#xD;

本文比较了虚拟 DOM 、脏检查和精确数据绑定三种渲染机制。

\u0026#xD;\u0026#xD;
 \u0026#xD;\t\t\t

AngularJS

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

ReactJS

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

Binding.scala

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

渲染机制

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

基于脏检查的定值算法

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

虚拟DOM

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

精确数据绑定

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

数据变更时的运算步骤

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t
  • \u0026#xD;\t\t\t\t

    重复检查数据是否更改

    \u0026#xD;\t\t\t\t\u0026#xD;\t\t\t\t
  • \u0026#xD;\t\t\t\t

    大范围更新页面,哪怕这部分页面根本没有修改

    \u0026#xD;\t\t\t\t\u0026#xD;\t\t\t
\u0026#xD;\t\t\t
  • \u0026#xD;\t\t\t\t

    重新生成整个虚拟DOM

    \u0026#xD;\t\t\t\t\u0026#xD;\t\t\t\t
  • \u0026#xD;\t\t\t\t

    比较新旧虚拟DOM的差异

    \u0026#xD;\t\t\t\t\u0026#xD;\t\t\t\t
  • \u0026#xD;\t\t\t\t

    根据差异更新页面

    \u0026#xD;\t\t\t\t\u0026#xD;\t\t\t
\u0026#xD;\t\t\t
  • \u0026#xD;\t\t\t\t

    直接根据数据映射关系,更新最小范围页面

    \u0026#xD;\t\t\t\t\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

检测页面更新范围的准确性

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

不准

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

默认情况下不准,需要人工提供`key`和`shouldComponentUpdate`才能准一点

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

需要前端工程师理解多少API和概念才能正确更新页面

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

很多

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

很多

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

只有`@dom`和`bind`两个概念

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

总体性能

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

非常差

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

\u0026#xD;\t\t\t
\u0026#xD;\t\t\t

\u0026#xD;\t\t\t

这三种机制中,Binding.scala 的精确数据绑定机制概念更少,功能更强,性能更高。我将在下一篇文章中介绍 Binding.scala 如何在渲染 HTML 时静态检查语法错误和语义错误,从而避免 bug 。

\u0026#xD;\u0026#xD;

相关链接

\u0026#xD;\u0026#xD;

More than React 系列文章

\u0026#xD;\u0026#xD;

《More than React(一)为什么ReactJS不适合复杂交互的前端项目?》

\u0026#xD;\u0026#xD;

《More than React(二)组件对复用性有害?》

\u0026#xD;\u0026#xD;

《More than React(三)虚拟DOM已死?》

\u0026#xD;\u0026#xD;

《More than React(四)HTML也可以静态编译?》

\u0026#xD;\u0026#xD;

《More than React(五)异步编程真的好吗?》

\u0026#xD;\u0026#xD;

作者简介

\u0026#xD;\u0026#xD;

杨博是 Haxe 和 Scala 社区的活跃贡献者,发起和维护的开源项目包括 protoc-gen-as3Stateless Futurehaxe-continuationFastringEachMicrobuilderBinding.scala 。杨博曾在网易任主程序和项目经理,开发过多款游戏。现在ThoughtWorks任Lead Consultant,为客户提供移动、互联网、大数据、人工智能和深度学习领域的解决方案。

\u0026#xD;\u0026#xD;

感谢张凯峰对本文的策划,韩婷对本文的审校。

\u0026#xD;\u0026#xD;

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值