限时领|《AI 百问百答》专栏课+实体书(包邮)! 了解详情
写点什么

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

  • 2016-09-22
  • 本文字数:5515 字

    阅读完需:约 18 分钟

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

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

ReactJS 虚拟 DOM 的缺点

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

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

这样做有两大缺点:

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

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

AngularJS 的脏检查

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

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

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 生成的 Comment HTMLBRElement 是 HTML Node 的派生类。而不是 XML Node

每个 @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>
}

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

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

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

结论

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

 

AngularJS

ReactJS

Binding.scala

渲染机制

基于脏检查的定值算法

虚拟 DOM

精确数据绑定

数据变更时的运算步骤

  • 重复检查数据是否更改

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

  • 重新生成整个虚拟 DOM

  • 比较新旧虚拟 DOM 的差异

  • 根据差异更新页面

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

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

不准

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

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

很多

很多

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

总体性能

非常差

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

相关链接

More than React 系列文章

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

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

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

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

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

作者简介

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


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

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

2016-09-22 10:549121

评论

发布
暂无评论
发现更多内容

《黑客之道》kali Linux之NMAP高级使用技巧和漏洞扫描发现

学神来啦

Linux 渗透 kali nmap

百度鸿鹄芯片落地首款量产车吉利博越X 智能车机体验惊艳成都车展

百度开发者中心

百度 车联网 鸿鹄芯片

ReplacingMergeTree:实现Clickhouse数据更新

华为云开发者联盟

数据 事务 Clickhouse 数据更新 OLAP数据库

接口文档工具yapi的安装

小鲍侃java

11月日更

linux lsquic 编译

webrtc developer

极客时间【架构实战营】第二期 模块七作业

Geek_91606e

架构实战营

阿里云视频云,用技术普惠打造平民化“虚拟人”

阿里云CloudImagine

人工智能 阿里云 视频云 数字人 虚拟人

腾讯安全姬生利:云原生环境下的“密码即服务”

腾讯安全云鼎实验室

云原生 加密

《个保法》施行 | App 隐私合规检测双十一尝鲜仅需99元

蚂蚁集团移动开发平台 mPaaS

隐私保护 移动开发 监管合规

恒源云(GPUSHARE)_CAN: 借助数据分布提升分类性能

恒源云

深度学习

四种 AI 技术方案,教你拥有自己的 Avatar 形象

阿里云CloudImagine

人工智能 阿里云 计算机视觉 视频云 元宇宙

Github上线仅六天,收获Star超55K+,这套笔记能拿下90%以上面试

Java redis spring 程序员 架构

百度智能云与雅量商业智能携手,加速零售行业智能化升级

百度开发者中心

百度智能云 零售行业

“智慧粮仓”守卫中国饭碗

ThingJS数字孪生引擎

大前端 物联网 可视化

Hexo个人博客快速部署到Gitee&Coding详细教程

老表

Hexo gitee CODING 博客配置 11月日更

科大讯飞联袂伯俊科技进军3C零售,构建发展新格局

科技热闻

牛掰!“基础-中级-高级”Java程序员面试集结,看完献出我的膝盖

Java spring 程序员 JVM hashmap

GitHub点击量破百万访问,不愧是被称阿里神作的JDK源码笔记

Sakura

Java 源码 架构 jdk 面试

看完这篇SpringBoot让我在阿里成功涨薪40%,感谢

Java 编程 程序员 程序人生 springboot

行业白皮书发布!百度智慧城市助力城市“双碳”目标达成

百度开发者中心

人工智能 智慧城市

网课录屏用什么软件好?Camtasia极简操作,课程重点一目了然

淋雨

Camtaisa

顶会VLDB‘22论文解读:CAE-ENSEMBLE算法

华为云开发者联盟

算法 时间序列 华为云数据库 深度神经网络

TDengine在浙商银行微服务监控中的实践

TDengine

tdengine 后端 时序数据库

架构实战营-总结

哈希

百度Apollo参编首批汽车信息安全国家标准,跻身车联网信息安全第一梯队

百度开发者中心

Apollo

万字长文解密数据异构最佳实践(含完整代码实现)!!

冰河

MySQL 数据库 canal 数据同步 数据异构

你需要知道的 19 个 console 实用调试技巧

CRMEB

CSS JavaScript DOM console crmeb

百度智能云人脸采集SDK通过CFCA权威安全测评

百度开发者中心

安全 sdk

【高并发】开篇:线程与多线程

冰河

Java 并发编程 多线程 高并发 异步编程

常用的Nmap脚本及使用实例

喀拉峻

网络安全 信息安全 渗透测试 脚本 nmap

GitHub远程免密连接详解,还顺手解决了RPC失败HTTP413

老表

GitHub RPC HTTP 11月日更

More than React(三)虚拟DOM已死?_JavaScript_杨博_InfoQ精选文章