对于复杂页面,为了将用户关注的内容尽可能快渲染出来,至少有两种方式:
一、Facebook 的 BigPipe 方式。先输出页面整体布局,然后逐步输出脚本块,一边输出一边执行,将内容渲染回页面布局中。这样可以让服务端的运算、网络传输和浏览器端的渲染变成并行。BigPipe 最主要解决的问题是服务端的运算时间,当服务端的运算时间大于 300 ~ 500ms 时才能体现出优势。当服务端响应非常快(小于 100ms),BigPipe 退化为下面要讲的 BigRender.
二、淘宝商品详情页的 BigRender 方式。淘宝的商品详情页,服务端平均响应时间为 52ms, 采用 BigPipe chunked 输出意义不大。这次优化主要在浏览器端。页面下载完毕后,要经过 Tokenization — Tree Construction — Rendering. 要让首屏尽快出来,得给浏览器减轻渲染首屏的工作量。可以从两方面入手:
对于 BigPipe 来说,初始输出的只有页面布局,DOM 节点数不多。首屏的 DOM 节点数主要取决于首屏脚本块中,字符串化的 html 代码:
big_pipe.onPageletArrive({ "content": { /* data */ } })
这种方式下,页面中的 DOM 节点是逐步增加的。尚未渲染的 DOM 节点,不会影响 TTI 区域。
对于 BigRender 来说,减少 DOM 节点数的方式有:
最容易想到的一种方式是学习 Facebook 好榜样,用 js 字符串来存放:
<script>
var data = "<p>some data</p>...";
</script>
这种方式对于 BigRender 来说,并不是很好:
把代码规范做好,把校验工作做好,再加上预处理和缓存,js 字符串的方式也是非常不错的。但对于淘宝详情页来说,目前用 js 字符串的方式需要做的改动比较多,增加的服务器消耗不少,不是很合适。
我们这次优化的目标是:
为了便于获取注释内容,添加一层包裹:
<div id="comment-data"><!--
html code
--></div>
这样,获取代码很简单:
var htmlCode = document.getElementById('comment-data').childNodes[0].nodeValue;
缺点是:
当 html code 很大时,替换的效率不高。依赖特殊标记的替换理论上也不完美。
还有什么存放方式呢?
HTML 元素分为五大类:
显然,Void elements 和 Foreign elements 不适合用来存放 html 代码。
对于 Normal elements, 里面的 < 字符会被当做 tag open 来解析,有一个方式是通过 display:none 来避免渲染:
<div style="display:none">
html code
</div>
这样做,减少的只是可见的 DOM 节点数,DOM 总数依旧不变。Tokenization — Tree Construction 等操作的耗时并没减少。
我们将重点放到 Raw text elements 和 RCDATA elements 上来。
先了解下 CDATA(Character Data) 的相关知识点。
在 XML 中,不包含子元素的元素的内容默认必须是 PCDATA(Parsed Character Data):
<data><p>some text</p></data>
“Parsed” 是指 < 和 & 字符要转换成 < 和 & 实体字符形式。如果不想写一大堆 &xx;, 可以直接标记为 CDATA:
<data><![CDATA[<p>some text</p>]]></data>
这是 XML 的习惯,很严格,但对用户并不友好。在 HTML 中,如果要兼容 XML, 得像如下一样:
<script>
//<![CDATA[
var t = "<p>";
//]]>
</script>
增加的 <
这是一个典型的淘宝详情页的首屏时间趋势图。可看出,首屏时间从优化前的 3s 降低到了优化后的 1.5s 左右,快了一倍!
更深度的优化需要对页面内容(包括脚本)做进一步的细粒度模块化,区分出优先级,然后根据需求,灵活自由地控制各个模块的下载和执行等等⋯⋯
这篇博客写得比较杂,关于 BigRender 优化的更多细节,以后有机会再细说。欢迎反馈、拍砖。欢迎业界各位朋友尝试 BigRender 优化,希望国内的站点速度都越来越快!
在业界,script 经常用来存放 template 数据:
<script type="text/template">
<h1>{{title}}</h1>
<p>I am {{name}}...</p>
</script>
绝大部分情况下,template 里不会出现 </script . 这样,服务端和浏览器端都无需做任何 replace, 是目前用来存放 template 的最佳实践。
2011-09-24:举例说明下 textarea 中为何要转义 &. 假设原代码为:
<p>&lt; represents <</p>
<p></p></p>
如果直接放到 textarea 中,
<textarea>
<p>&lt; represents <</p>
<p></p></p>
</textarea>
由于 textarea 是 RCDATA 元素,上面的代码等价于:
<textarea>
<p>< represents <</p>
<p></p></p>
</textarea>
获取 textarea.value,回填到 DOM 树的代码为:
out.innerHTML = textarea.value;
这时页面中的显示效果明显和原来不一样了。如果将所有 & 都转化成 & 则可以保持原样。
注意:理论上,并不需要将所有 & 转化成 &, 只需要将与 HTML 语法冲突的字符串 < > & 中的 & 转化成 & 即可。但这样做,还得处理 &#xx 等数值表示,比如 & 还有 & & 两种表现形式,这样替换起来更麻烦,不如将所有 & 替换成 & 来得快捷高效。

