Skip to content

Latest commit

 

History

History
160 lines (140 loc) · 10.6 KB

快速打开页面内容的hacks.md

File metadata and controls

160 lines (140 loc) · 10.6 KB

几个星期以前,我在希思罗机场飞机起飞之前忙点事情, 然后我发现github的表现有点奇怪:在新页面打开链接 比直接在当前页点击链接打开要快,这是我当时录下的 IMAGE ALT TEXT

background-image: url("https://i.ytimg.com/vi_webp/4zG0AZRZD6Q/sddefault.webp"); 这里我点击了一个链接,然后在一个新页面把这个链接粘贴进去,虽然这个页面是后打开的,但新页面渲染的更快。

show them what you got

当你加载一个页面时,浏览器获取一个网络流,然后加载进html parser中,然后再将转换的内容输出到document中。 这意味着页面可以在下载的同时一点一点的加载,这个页面可能有100k大,但是可以渲染内容即使只接受到20k的数据。 这是一个伟大的、久远的浏览器特性,但是作为开发者我们经常抛弃这种特性。大多数加载时的性能建议可以归结为“你得到多少 就展示多少”,不要停滞,不要等到所有东西都加载好后才展示给用户看。 GitHub关心性能,所以使用服务器端渲染页面。然而在同一个tab页面跳转就是使用javascript重新实现了一遍,就想下面代码这样。。
// …lots of code to reimplement browser navigation…
const response = await fetch('page-data.inc');
const html = await response.text();
document.querySelector('.content').innerHTML = html;
// …loads more code to reimplement browser navigation…

这不符合规则,因为所有的`page-data.inc'会预先下载在所有东西完成之前 服务器端渲染不会以这种方式保存内容,它会以数据流的形式加速渲染。对于GitHub的客户端渲染,大量的javascript代码使这个 变得缓慢。
我在这里只是用GitHub作为一个例子,这种不好的模式几乎在所有的单页面应用内都在使用。
在页面内切换内容有一些优点,尤其是页面内加载了很大很重的scripts,因为你可以更新页面 内容而不用重新部署所有的js。但是我们可不可以那样做而且继续使用streaming?我以前总说 JavaScript不能访问stream parser,但它却是。。

使用iframes和document.write来改善性能

最糟糕的是涉及到了<iframe>,而且这一次用到了<iframe>document.write 但确实实现了以数据流的形式加载页面,代码是这样的:

// Create an iframe:
const iframe = document.createElement('iframe');

// Put it in the document (but hidden):
iframe.style.display = 'none';
document.body.appendChild(iframe);

// Wait for the iframe to be ready:
iframe.onload = () => {
  // Ignore further load events:
  iframe.onload = null;

  // Write a dummy tag:
  iframe.contentDocument.write('<streaming-element>');

  // Get a reference to that element:
  const streamingElement = iframe.contentDocument.querySelector('streaming-element');

  // Pull it out of the iframe & into the parent document:
  document.body.appendChild(streamingElement);

  // Write some more content - this should be done async:
  iframe.contentDocument.write('<p>Hello!</p>');

  // Keep writing content like above, and then when we're done:
  iframe.contentDocument.write('</streaming-element>');
  iframe.contentDocument.close();
};

// Initialise the iframe
iframe.src = '';

尽管<p>Hello!</p>写在了iframe里边,但它出现在父文档里面!这是 因为html parser中保留了一 个打开的元素的堆栈 (stack of open elements 记录html元素的匹配,<xx></xx>,这就是一对打开和关闭的元素)新创建的元素会插入到堆栈中(目测是栈顶,后进后出) 我们怎么操作<streaming-element>并不影响,它就是有效的。
同样的,这个方法处理html比innerHTML更加贴近标准的页面加载解析器。 显然,scripts会父文档的上下文中下载和执行,然而在火狐中根本不执行,原因是script不应该被执行 但是在Edge, Safari和 Chrome 中都适用。
现在我们只需从服务器stream数据,然后在数据到来时使用iframe.contentDocument.write()写入数据。 使用fetch()会非常高效的stream数据,然而Safari并不支持,可以使用XHR来代替。
我做了一个小测试可以和GitHub所采用的方式做比较,下面是基于3g的测试结果

tupian
将内容通过数据流的方式加载进来要比xhr+innerHTML要快1.5s左右,所有内容加载完要提前0.5s,stream意味着浏览器 发现他们会比较早,所以可以一边下载一边渲染。
GitHub能够实现是因为服务器处理html,但是如果你使用一个框架,用自己的方式来表达DOM,这就行不通了, 对于这种情况,下面这种方法是一个折中的做法:

换行符JSON

很多网站使用json的格式传递动态的更新项。不幸的是JSON并不是一个streaming-friendly(流友好)的格式。 虽然有JSON的streaming JSON parsers(JSON格式转streaming格式的工具), 但是这个并不好用。
所以不采用传递成片的JSON:

{
  "Comments": [
    {"author": "Alex", "body": "…"},
    {"author": "Jake", "body": "…"}
  ]
}

而是传递JSON对象在新的一行里:

{"author": "Alex", "body": "…"}
{"author": "Jake", "body": "…"}

这就是“newline-delimited JSON”(换行符JSON),这是它的定义描述。 写一个这样的解析器要容易得多,在2017年我们将可以写出下面这种流的变换以及组合:(这里感觉可以用JSON.stringify())

const response = await fetch('comments.ndjson');
const comments = response.body
  // From bytes to text:
  .pipeThrough(new TextDecoder())
  // Buffer until newlines:
  .pipeThrough(splitStream('\n'))
  // Parse chunks as JSON:
  .pipeThrough(parseJSON());

for await (const comment of comments) {
  // Process each comment and add it to the page:
  // (via whatever template or VDOM you're using)
  addCommentToPage(comment);
}

这里splitStreamparseJSON是可复用的stream的变换,与此同时,为了让大多数浏览器兼容 我们可以从XHR上开始。
再一次的,我给出了一个小测试用例来比较下 面是基于3g的测试结果:

tupian
和传统的JSON相比,ND-JSON将内容呈现在屏幕上要比传统的快1.5s,尽管它并没有iframe这种方式快,它 需要等待JSON完整的加载完才能去创建元素,你会遇到lack-of-streaming(缺少流)的问题如果JSON对象非常大。

不要太早使用单页应用

正如我上面所说的,GitHub写了很代码来解决这个性能问题。在客户端上重新实现链接是很困难的,而且如果这个页面内 要替换的东西比较大的话就不值得这么做了。
如果我们比较一下一个简单的浏览器导航

tupian 。。。打开一个简单的不使用js通过服务器渲染的页面和上面的差不多一样快,除了评论列表,测试页面非常简单, 如果您在页面之间重复了大量复杂的内容,您的结果可能会有所不同(基本上,我的意思是可怕的广告脚本),多测试! 你可能写了很多代码然而只是提升了一点点性能,或者性能更差。