虚拟DOM已死?|TW洞见

Posted 思特沃克

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虚拟DOM已死?|TW洞见相关的知识,希望对你有一定的参考价值。


杨博
ThoughtWorks

本文转载自InfoQ:http://www.infoq.com/cn/articles/more-than-react-part03

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

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

1

ReactJS虚拟DOM的缺点

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

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

这样做有两大缺点:

  • 每次 state 更改,render 函数都要生成完整的虚拟 DOM,哪怕 state 改动很小,render函数也会完整计算一遍。如果 render 函数很复杂,这个过程就会白白浪费很多计算资源。

  • ReactJS 框架比较虚拟 DOM 差异的过程,既慢又容易出错。比如,你想要在某个 <ul> 列表的顶部插入一项 <li> ,那么 ReactJS 框架会误以为你修改了 <ul> 的每一项 <li>,然后在尾部插入了一个 <li>

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

2

 AngularJS的脏检查

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

类似 AngularJS 的脏检查算法和 ReactJS 有一样的缺点,无法得知状态修改的意图,必须完整重新计算View 模板。除此之外,AngularJS 更新 DOM 的范围往往会比实际所需大得多,所以会比 ReactJS 还要慢。


3

Binding.scala的精确数据绑定

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

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

@dom val i: Binding[Int] = 1
@dom def f: Binding[Int] = 100
@dom val s: Binding[String] = "content"

@dom 既可用于 val 也可以用于 def ,可以表达包括 Int 、 String 在内的任何数据类型。除此之外,@dom 方法还可以直接编写 Xhtml,比如:

@dom val comment: Binding[Comment] = <!-- This is a HTML Comment -->
@dom val br: Binding[HTMLBRElement] = <br/>
@dom val seq: Binding[BindingSeq[HTMLBRElement]] = <br/><br/>

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

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

val i: Var[Int] = Var(0)
@dom val j: Binding[Int] = 2
@dom val k: Binding[Int] = i.bind * j.bind
@dom val div: Binding[HTMLDivElement] = <div>{ k.bind.toString }</div>

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

比如:

val count = Var(0)

@dom def status: Binding[String] = {
val startTime = new Date
"本页面初始化的时间是" + startTime.toString + "。按钮被按过" + count.bind.toString + "次。按钮最后一次按下的时间是" + (new Date).toString
}

@dom def render = {
<div>
{ status.bind }
<button onclick={ event: Event => count := count.get + 1 }>更新状态</button>
</div>
}

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

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

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

4

结论

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

(点击可查看清晰大图)

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

相关链接:



- 《More than React》系列 -


点击[阅读原文]到洞见网站查看文章全部内容&绿字部分

本文版权属ThoughtWorks公司所有,如需转载请在后台留言联系。

以上是关于虚拟DOM已死?|TW洞见的主要内容,如果未能解决你的问题,请参考以下文章

TW洞见|也谈响应式编程

前后端分离,谁值得拥有? | TW洞见

TW洞见 | Sam Newman:康威定律

TW洞见 | 重构是每个程序员的洗髓经

高效快速地加载 AngularJS 视图|TW洞见

展望2016,REACT.JS 最佳实践 | TW洞见