【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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:548011

评论

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

PoseiSwap 更新质押系统,并将在 8 月18 日开启“Trident ”快照

EOSdreamer111

Mac端视频动态壁纸 Dynamic Wallpaper 中文最新

胖墩儿不胖y

Mac软件 动态桌面壁纸软件 动态壁纸软件

基于 Vercel & TiDB Serverless 的 chatbot

TiDB 社区干货传送门

社区活动

爆款升级,价格喜人,京东超品日即将上新小度青禾学习手机5G版

新消费日报

消息推送:精准推送,提升运营效果,增添平台活力

MobTech袤博科技

前端 前端开发 消息推送 sdk

PoseiSwap 更新质押系统,并将在 8 月18 日开启“Trident ”快照

股市老人

一点就通,社交源码IOS客户端开发集成SDK

山东布谷网络科技

sdk

Oracle-TiDB迁移-生僻字乱码问题

TiDB 社区干货传送门

迁移 实践案例 应用适配

TiDB 多集群告警监控-中章-融合多集群 Grafana

TiDB 社区干货传送门

监控 集群管理 管理与运维 6.x 实践

大数据平台是什么意思?有什么用?一般包含哪些模块?

行云管家

数据安全 大数据平台

IPQ4019, IPQ6010, and IPQ6018 Revolutionizing Urban Vehicle Connectivity

wallyslilly

IPQ4019 IPQ6010 ipq6018

目前主流大数据平台有哪些?哪些行业需要大数据平台?

行云管家

数据安全 大数据平台

Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析

砖业洋__

spring BeanDefinition BeanDefinitionRegistry BeanDefinition合并

简单理解 TiDB Serverless branching

TiDB 社区干货传送门

数据库前沿趋势

22. 面向对象 - 高阶

茶桁

Python 面向对象 oop

REST 内容协商详解:理论解读与实际应用指南

Liam

程序员 前端 后端 web开发 RESTful API

PCTA 认证考试高分通过经验分享

TiDB 社区干货传送门

社区活动 6.x 实践

ARTS-WEEK1 —— 23.8.14~23.8.20

EchoZhou

我和 TiDB 的故事 | 远近高低各不同

TiDB 社区干货传送门

人物访谈 社区活动

raw图片处理 DxO PhotoLab 6中文直装版

mac大玩家j

图像编辑 Mac软件 编辑图像 raw图像

NineData成功通过AWS FTR认证,一款值得信赖的数据管理工具!

NineData

数据库 AWS 数据管理 NineData FTR 认证

TiDB 源码编译之 TiFlash 篇

TiDB 社区干货传送门

新版本/特性解读 HTAP 场景实践 7.x 实践

TiDB Hackathon 2023 获奖名单新鲜出炉,来看一下都有哪些特别惊喜的项目吧!

TiDB 社区干货传送门

浅析分布式系统之体系结构 基本属性--故障

snlfsnef

浅析分布式系统之体系结构 一致性的实现-- 共识

snlfsnef

分布式 系统设计 数据一致性 共识机制 深入理解分布式共识算法

KaiwuDB CTO 魏可伟:回归用户本位,打造“小而全”的数据库

KaiwuDB

KaiwuDB DTCC 2023

阿里云产品测评赢大奖丨云原生网关 MSE-Higress

阿里巴巴云原生

阿里云 微服务 云原生

Proxifier for Mac(socks5全局代理客户端) v3.8注册版

mac

全局代理客户端 苹果mac Windows软件 Proxifier

华秋硬创联合安创加速器,加速和创新赋能技术驱动型创业者

华秋电子

创业

亚信科技AntDB数据库连年入选《中国DBMS市场指南》代表厂商

亚信AntDB数据库

数据库 AntDB AntDB数据库 企业号 8 月 PK 榜

开发者不需要成为 K8s 专家!!!

北京好雨科技有限公司

Kubernetes 开发者 PaaS #云原生

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