JavaScript框架对比

来源:互联网 发布:pubmed数据库 编辑:程序博客网 时间:2024/05/20 23:07

如今JavaScript框架三分天下,该学哪些和如何学呢?要知道这些问题,必须要将其进行对比。本文介绍了一种比较方式。本文翻译自Change And Its Detection In JavaScript Frameworks。PS:因为原文写于2015年,有些地方在如今看来有些过时或者改变,但原文思考框架的角度仍然使我感到受益。尽管原文有些长,翻译起来有些麻烦,但我还是坚持翻下来了。但本人知识有限,所以如您发现有翻译的不对的地方,请不吝指正。

JavaScript框架中的状态变化及其检测

在2015年,当谈到JavaScript框架时,我们完全感觉不到匮乏。Angular,Ember,React,Backbone还有它们的一众竞争对手,有太多选择了。

我们可以在很多方面比较这些框架,但我认为它们之间的很重要的不同是管理状态的方式。特别地,了解这些框架在状态随时间改变时做了什么是很有用的。而它们又为我们提供了什么工具来将变化映射到用户界面上呢?

管理应用状态和UI的同步很长时间以来都成为制约UI发展的主要因素,而我们如今有多种方式来处理这个问题。这篇文章将探讨几种方式:Ember的数据绑定,Angular的脏检测,React的虚拟DOM,以及他们同不变数据结构之间的关系。

映射数据

我们讨论的是获取程序内部的数据并把它映射到屏幕的可见部分。我们使用对象、数组、字符串、数组,而最终它们会变成一棵文本段落、表单、链接、按钮和图像树。在web中,前者通常表现为JavaScript数据结构,而后者表现为DOM(文档对象模型)。

我们通常把这个过程叫做渲染,也可以把它视为数据模型到可见的用户界面的映射。当用数据渲染模板时,我们就会得到那些数据的DOM(或HTML)表现。

Model and DOM

它本身是很简单的:虽然将数据模型映射到UI可能并不总是微不足道的,但它基本上仍然是一个直接输入输出的函数。

但当数据随时间变化时,事情就不那么简单了。这可能发生在用户同界面进行交互,或要更新数据的情况。UI需要映射这次改变。另外,因为渲染DOM树的代价是很昂贵的,所以我们需要尽可能只做一点小事就能把改变的数据在屏幕上显示出来。

Model and DOM changed

因为涉及了状态改变,所以这可比只渲染UI一次更困难。解决这个问题的方法也是各个框架的差异之处。

服务器端渲染:重置一切

不存在改变,一切都是永恒的。

在大JavaScript时代之前,我们每次同web应用的交互都会触发一次到服务器的往返。每次点击、表单的提交都意味着网页卸载、向服务器发送请求、服务器返回新的页面,然后浏览器进行渲染。

这里写图片描述

这种方式使得前端并不需要做什么状态管理。就浏览器而言,每次数据变化,都意味着世界的终结。无论数据怎么变化那都是后台需要考虑的事。前端只需要生成HTML和CSS,再添加一点JavaScript润色。

然而这种在前端看来很简单的方式是很慢的。不仅因为每次交互都会产生一次UI的完整的重渲染,而且需要一次完整的从遥远的数据中心的往返导致的远程重渲染。

大部分的应用不会这样做了。我们只是在服务器端初始化状态,然后转换为在前端管理状态。不过仍然有人再做这种模式的改进。

第一代JS:手动重渲染

我不知道应该重渲染什么,你来决定吧。

第一代JS框架,像Backbone.js、Ext JS和Dojo,首次在浏览器中引入了真实数据模型,以代替绑定在DOM上的一些轻量级的脚本。这也意味着我们第一次能够在浏览器中更改状态。数据模型的内容可以改变,我们能将这些改变映射到用户界面中。

虽然这些框架可以使我们把UI代码从模型中分离出来,但还需要我们来管理它们之间的同步。当改变发生时我们可以获取到某些事件,但仍需要我们决定重渲染什么和怎么做。

这里写图片描述

作为应用开发者,我们需要仔细考虑这种模型的性能。因为我们可以按照喜欢的方式控制什么时候更新和更新什么。我们是为了简单而重渲染页面的大幅篇章,还是为了性能只是重渲染更新部分,这值得权衡。

Emberjs:数据绑定

因为我控制模型和视图,所以我准确的知道什么改变了和应该重渲染什么。

因为在应用状态改变时不得不手动判断,这也是导致第一代JavaScript应用有时很复杂的主要因素。很多框架致力于消除这个特定问题。Ember.js就是其中之一。

Ember像Backbone那样在数据发生改变时会从数据模型中发出事件。不同点在于Ember为事件的接收端提供了某些东西。我们把UI绑定道数据模型上,那也意味着有一个连接到UI的更新事件的监听器。当收到一个事件时,监听器知道要做什么更新。

这里写图片描述

这是一种相当有效的机制:尽管初始化时需要作部分绑定工作,但这却让后来的保持同步更简单。当有东西改变时,app中只有确实需要做出改变的部分将激活。

这种方式的最大折中之处在于Ember必须注意数据模型中发生的变化。那意味着我们要将数据保存在继承Ember的API的特殊的对象中,也需要用特殊的setter方法来改变数据。例如我们不能用foo.x=42,而必须要用foo.set('x',42),等等。

未来可能会因为ECMAScript 6 中Proxies的到来获得改善。这让Ember可以用其绑定代码装饰普通对象,以使得与那些对象交互的代码不一定需要遵守setter惯例。

AngularJS:Dirty Checking

我不清楚什么改变了,所以我就检查所有可能需要更新的地方

同Ember一样,Angular也致力于解决当某些地方更新时不得不手动重渲染的问题。然而,它是从另一个角度解决的。

当在Angular模板中关联数据时,如表达式{{foo.x}},Angular不仅会渲染数据,而且会为那个特定值创建监视器。之后,无论应用中在什么时候发生了改变,Angular都会检测监视器中的值是否从上一次发生了更改。如果发生了改变,他就会重渲染UI中的值。运行这种监视器的过程叫做脏检测。

这里写图片描述

这种类型的改变检测最大的好处是我们可以在数据模型中用任何内容了。Angular对此并没有限制——它并不在乎。没有要扩展的对象,也没有要实现的API。

缺点是因为数据模型中没有任何能告知框架改变信息的内置接口,所以框架就不知道变化是否发生和发生在什么地方。那意味着模型需要被外部改变检测到,Angular也确实是这样做的:所有的监视器每次发生任何事情都会运行。点击事件处理,HTTP响应处理和超时,所有这些都会触发digest(负责运行所有监视器的过程)。

时刻运行所有监视器听起来像是性能噩梦,但它出人意料的快。这主要是因为在实际检测到改变发生时并不会发生DOM访问,而纯JavaScript相等检测是廉价的。但当涉及到大型UI或要经常渲染时,额外的性能优化还是需要的。

同Ember类似,Angular会受益于即将到来的标准:特别是,ECMAScript7的Object.observe特性将非常适合Angular,因为它为我们提供了一个原生的API来检测对象属性的改变。尽管它不会覆盖Angular所有需要支持的情况,因为Angular的监视器不只是检测简单的对象属性。

即将到来的Angular2会为变化检测方面带来一些有意思的更新,正如Victor Savkin文章中介绍的那样。2015.7.3更新:详情见 Victor’s ng-conf talk

React:Virtual DOM

我不清楚什么改变了,所以我重渲染一切,看现在有什么不同。

React有许多有意思的特性,但在本文中将主要关注虚拟DOM。

React,像Angular一样,没有将数据模型API强加于我们,它允许我们使用用任何感觉合适的对象和数据结构。那它又是怎么解决的在发生变化时保持UI和数据同步的问题呢?

React做的事情实际上又将我们带回以前并不关注状态改变的服务端渲染的时代:每次当有更新时,都会重新渲染整个UI。这显然能简化UI代码。我们不必太关心React组件中状态的保持。就像服务器端渲染那样,我们渲染一次,就完了。当组件发生改变时,它会再次渲染。因为数据改变而更新组件和初次渲染组件并没有什么不同。

这听起来效率很低,如果就这么结束了,那它确实如此。然而,React的重渲染用了一种特别的方式。

当React UI渲染时,它会首先渲染到虚拟DOM中,虚拟DOM不是真实DOM对象模型,而是一个轻量级的,用普通对象、数组构成的纯JavaScript数据结构,它是真实DOM模型的表现。一个单独的进程之后会根据这个虚拟的DOM结构创建在屏幕上显示的相应的真实的DOM节点。

这里写图片描述

然后,当发生改变时,会从头到尾重新创建虚拟DOM。这个新的虚拟DOM会映射当前新的数据模型的状态。React现在就有了新旧两个虚拟DOM数据结构。他之后会对这两个虚拟DOM做比较,来得到发生变化的集合。也仅有那些改变才会被应用到真实DOM中:添加元素、改变属性等。

这里写图片描述

所以React的最大益处或者说其中一个就是我们不需要追踪改变了。我们只是在每次改变时重新渲染整个UI。虚拟DOM比较算法使得昂贵的DOM操作尽可能的减少。

Om:不变的数据结构

我准确地知道什么不会改变

尽管React的虚拟DOM足够快,但当UI变大、渲染频繁(比如每秒60次)时仍然会遇到性能瓶颈。

原因是每次更新都不得不重新渲染整个(虚拟)DOM,除非像Ember那样在数据模型中引入一些控件来控制变化的发生。

控制变化的一种方式是支持不变的、持久化的数据结构。这似乎很适合React的虚拟DOM,正如Dabid Nolen在基于React和Clojure的Om库中所做的工作所示。

永恒不变的数据正如它的名字显示的那样,我们不能改变它,只能产生一个新的版本。如果想改变对象的某个属性,因为不能直接在存在的对象中更改,所以需要、造一个包含新属性的对象。由于持久化数据结构的工作方式,它事实上比听起来更有效率。

这在变化检测方面意味着,当React组件的状态只包含不变数据时,有一个突破口:当渲染组件时,若本次状态和上次指向相同的数据结构,那么可以跳过重渲染。因为可以利用组件之前的虚拟DOM,和源于其的整个组件树。不需要进一步查看,因为状态没发生改变。

这里写图片描述

像Ember那样,像Om这样的库不能让我们在数据中使用普通的JavaScript对象。要想从中获利,我们必须要使用不可变数据结构构建模型。这样做不是因为这是框架的要求,而是因为这样做确实能使管理应用状态变得简单。使用不可变数据结构的主要益处不是为了获取渲染性能,而是简化应用程序架构。

尽管Om和ClojureScript可作为整合React和不可变数据结构的方式,但这也不是唯一的方式。完全可以使用纯粹的React和像Facebook Immutable-js这样的库。该库的作者Lee Byron,在最近的React.js大会上做了完美的介绍。

我也推荐大家看一下Rich Hickey的持久化数据结构和管理引用,它对这种状态管理方式做了介绍。

我最近也在研究不可变数据,尽管我不能准确预见到它在前端UI架构的到来。但这似乎在发生,Angular团队就在努力增加对其的支持。

总结

变化检测是UI发展中的关键问题,JavaScript框架也使用很多方式来尝试解决这个问题。

EmberJS可在变化发生时检测到,因为它能控制应用的数据模型接口,并能在调用时触发事件。

Angular.js能检测到改变,是通过注册在UI上的所有数据绑定来得之值是否改变。

普通React通过将整个UI重渲染成虚拟DOM然后同之前的作比较来检测到数据改变。改变了什么,就在真实DOM上做相应更新。

不可变数据结构和React配合可以快速标记未改变的组件树,因为组件状态是不允许变动的,从而增强了React的性能。然而这并不是出于性能原因,而是因为它会为我们的应用架构带来积极影响。

原创粉丝点击