到目前为止,在服务器端处理一部分或者所有的页面,仍然是避免过多客户端与服务器的交互的主要手段。StackOverflow in 4096 bytes很不错地展示了如何降低和服务器的来回交互次数。作为概念验证的SPA,它理论上可以做到在握手后的第一个TCP连接中完成加载!当然,要做到这些,它使用了SPDY 或者 HTTP/2 server push,因此可以在一个hop里面传输所有客户端可以缓存的代码。


  图3. 使用了内链CSS和JS技术的Stackoverflow in 4096 bytes

  如果我们有一个足够灵活的系统,可以在浏览器和服务器直接共享渲染页面的代码(比如双方都是js),并且提供工具增量的加载脚本和样式,那么 网站 和 Web应用 可以合一而不再是两个模棱两可难以区分的词了:它们本身有一样的UX要素。比如一个博客页面和一个复杂的CRM,都有URL,都需要跳转,都展示数据,本质上并没有太大不同。即便是像数据表格这样复杂的东西,传统上主要是客户端提供的功能来完成对数据的处理,但也首先需要给用户展示那些需要他处理的数据 。降低客户端和服务器交互的次数,对实现我们说的这样的系统非常重要。
  在我看来,我们看到的大量系统上采用了这样那样性能上的权宜之策,是因为整个技术栈的复杂度在不断累加。Javascript和CSS这样的技术是被逐渐加入到系统的,它们的风靡又花了一段时间。尽管有人希望在协议上做出改进,来增强性能(比如SPDY或者QUIC),但应用层显然才是需要改进的地方。
  要理解速度的重要性,去重温一下WWW和HTML创立之初的一些讨论是非常有用的。特别是在1997年提议在HTML里加入img这个标签的时候,Marc Andreessen在下面这个邮件thread里反复强调了提供信息的速度有多么重要:
  “If a document has to be pieced together on the fly, it could get arbitrarily complex, and even if that were limited, we’d certainly start experiencing major hits on performance for documents structured in this way. This essentially throws the **single-hop principle of WWW** out the door (well, IMG does that too, but for a very specific reason and in a very limited sense) — are we sure we want to do that?”
  2. 对用户输入立刻响应
  TL;DR: 我们可以使用JavaScript来掩盖网络的延迟,把它作为设计原则,可以在你自己的应用里面去掉绝大多数的spinner或者loading。使用PJAX和TurboLink的话,你会失去了这些改善用户速度体验的机会。.
  第一个原则里,在描述为什么要尽量减少前端和后端之间数据来回传输的次数时,主要是基于传输速度有理论上限的事实。实际上另一个需要考虑的要素是网络的质量。我们都知道,当网络连接状况不好时,会有数据包需要被重传。所以,你觉得应该一个来回传输完毕的数据,可能实际上要花去好几个。
  在这方面,Javascript正好可以帮上忙:通过客户端的代码来驱动UI,人工的构造出零延迟,可以掩盖网络的延迟,制造一切操作都很顺畅的假象。比如,网页和网页之间是通过超链接,<a>标签,链接在一起的。传统网页上,当一个链接被点击时,浏览器发送一个可能会耗时很久的请求,然后处理请求并把内容呈现给用户。
  但Javascript允许你立刻响应(有些地方把这个叫乐观响应):当一个链接或者按钮被点击时,页面立刻做出响应而不需要去访问网络。这方面的例子是Gmail(包括近Google的新产品Inbox)的”邮件归档”功能。当你点击”归档”,UI上邮件立刻会被显示为归档状态,而服务器的请求和处理是异步进行的。
  再比如,我们处理的是一个表单。也许你觉得一个表单在数据被提交到服务器,处理结果返回之前,不能做太多的事情。但其实当用户完成输入并点击提交的时候,我们可以开始响应了。甚至有些做到的应用,比如Google搜索页面,当用户开始输入的时候,展示搜索结果的页面已经开始渲染了。


  图4. Google在用户输入搜素关键字时开始渲染搜索结果页面

  这种行为被称为 layout adaptation。 它的思路是当前页面知道操作后状态的页面layout,所以在没有数据填充的情况下,它可以过渡到下面那个状态的layout。这样的处理是”乐观”的,是因为有可能后面那个页面的数据一直没有返回,而这时候页面的layout已经画在那里了。
  Google的主页的演进,非常清楚的说明了我们这里强调的第一和第二个原则。
  首先,分析访问www.google.com时TCP连接的包数据可以看到整个首页的数据都被一次性发出来了。整个交互,包括关闭连接,耗时几十毫秒而已。而且,似乎在Google一开始的版本做到了这点。
  在2004年晚些时候, Google标杆性地使用了JavaScript完成输入时动态提示功能(和Gmail一样,也是一个20%创新时间产出的项目),这一功能也启发了很多网站开始大量的使用AJAX:
  Take a look at Google Suggest. Watch the way the suggested terms update as you type, almost instantly with no waiting for pages to reload. Google Suggest and Google Maps are two examples of a new approach to web applications that we at Adaptive Path have been calling Ajax
  到了2010年,Google又推出了及时搜索,也是我们前面看到的效果:当用户输入关键字时,整个页面无需刷新可以展示搜索的结果。
  另一个例子是iOS。在很早期的版本,iPhone要求开发者提供一个default.png图片,用来在应用被加载完成之前显示给用户:


  图5. iPhone OS强制在应用加载前显示一个default.png

  当然,这里OS不是在隐藏网络延迟,而是CPU处理延迟。对于iPhone初期版本来说,这样来弥补硬件的弱点非常重要。当然和网页上使用提前加载一样,这种手法有可能会崩坏:当加载来的数据和default.png不匹配的时候。Marco Arment在2010年对它可能带来的影响进行了 透彻的分析。
  除开处理表单和输入,Javascript还被大量用于处理文件上传。我们可以通过各种前端表现来满足用户上传文件的需求:拖拽,粘贴以及各种file picker。特别是有了HTML5的新API之后,我们可以在文件完成传输前显示它的信息。在Cloudup网站的上传文件中,使用了类似的实现。从图片中可以看到,在用户选择了文件之后,缩略图立刻生成并显示在用户界面上了:


  图6. 在上传完成前图片被显示出来并且加入了虚化效果

  上面的方式都是采用前端技术来制造速度的假象,但这种方式其实在很多地方都被证明是有效的。一个例子是在美国休斯顿