美文网首页IT技术
首屏加载优化

首屏加载优化

作者: 狐尼克朱迪 | 来源:发表于2016-10-14 14:34 被阅读1460次

    概述

    页面加载的方式包括以下几种:

    1. 直接同步加载
      一次把服务端的渲染内容加载到浏览器,当页面内容比较少时可以考虑这种方式。
    2. 滚动同步加载
      服务端渲染首屏的内容,其他屏幕的内容放到textarea或者注释中,滚动时再渲染其他屏的内容,此种比较适合。
    3. 异步加载
      服务端渲染主 layout ,加载到客户端,通过 AJAX 获取其他页面内容,然后在客户端渲染。此种和淘宝无线 H5 的方案类似。
    4. 滚动异步加载
      服务端渲染首屏内容,加载到客户端,滚动时再通过 AJAX 获取次屏内容
    5. 分块加载
      服务端支持 chunk 输出,分块将内容传输到客户端,客户端渲染。

    对比上述几种方式,1和2并不能加快首屏加载的速度;3和4需要通过ajax获取其余的内容,但是对首屏加载是有益的;5是最优方案,在Node中对应的是Bigpipe

    分块传输

    在讨论Bigpipe前,需要了解其技术支撑:分块传输编码。分块传输编码对应http中字段是:Transfer-Encoding,它是HTTP1.1版本中引入的新技术,目的是在已经建立的tcp连接上持续传递内容。

    Persistent Connection

    通过持久连接(persistent connection),TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive。由于浏览器和服务器实现的问题,现在还需设置Connection: keep-alive去表明当前为长连接,但协议已经不需要了。
    客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接,也可以通过Connection: close明确指明去关闭链接。

    Content-Length

    一个TCP连接现在可以传送多个回应,势必就要有一种机制,区分数据包是属于哪一个回应的,这就是Content-length字段的作用,声明本次回应的数据长度。
    在1.0版中,Content-Length字段不是必需的,因为浏览器发现服务器关闭了TCP连接,就表明收到的数据包已经全了。

    在HTTP1.1协议下,如果在请求时不指定Content-Length会有什么情况呢?如下程序:

      require('net').createServer(function(sock) {
        sock.on('data', function(data) {
            sock.write('HTTP/1.1 200 OK\r\n');
            sock.write('Content-Length: 5\r\n'); // 指定数据包长度 考虑:1. 不添加此行程序  2. 小于实际报文长度 3. 大于实际报文长度
            sock.write('\r\n');
            sock.write('hello world!');
            // sock.destroy();
        });
    }).listen(9090, '127.0.0.1');
    

    如手动断开连接,那么不论我们怎么设置Content-Length,请求很快完成,只是浏览器能不能正常获取到内容。
    如不手动断开连接,会有以下三种情况:

    1. 不添加Content-Length,客户端会一直等待;
    2. Content-Length设置的长度小于或等于实际报文长度,请求顺利完成,但是内容不全;
    3. Content-Length设置的长度大于实际报文长度,客户端会一直等待。

    TTFB(Time To First Byte)是web性能优化一个很重要的指标,它代表客户端从发送请求到收到第一个字节所花费的时间。TTFB越短, 意味着用户可以越早看到页面内容,体验越好。通过上面的分析可知,为了让客户端顺利收到响应内容,需要一个正确的 Content-Length值,而为了计算此值需要服务端缓存所有内容,这就和TTFB背道而驰。在 HTTP 报文中,实体一定要在头部之后,顺序不能颠倒,为此我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界

    Transfer-Encoding: chunked

    分块传输的基本思想是:服务器产生一块数据,就发送一块,采用流模式(stream)取代缓存模式(buffer)。每个非空的数据块之前,会有一个16进制的数值,表示这个块的长度。最后是一个大小为0的块,就表示本次回应的数据发送完了:

    HTTP/1.1 200 OK
    Content-Type: text/plain
    Transfer-Encoding: chunked
    25
    This is the data in the first chunk
    1C
    and this is the second one
    3
    con
    0
    

    下面是采用Nodejs模拟Transfer-Encoding: chunked的例子:

    require('net').createServer(function(sock) {
        sock.on('data', function(data) {
            sock.write('HTTP/1.1 200 OK\r\n');
            sock.write('Transfer-Encoding: chunked\r\n');
            sock.write('\r\n');
    
            sock.write('b\r\n');
            sock.write('01234567890\r\n');
    
            sock.write('5\r\n');
            sock.write('12345\r\n');
    
            sock.write('0\r\n');
            sock.write('\r\n');
        });
    }).listen(9090, '0.0.0.0');
    

    实战

    Nodejs的http封装本身是按Transfer-Encoding: chunked进行传输的,如下面的例子:

    var http = require('http');
    http.createServer(function (request, response){
      response.writeHead(200, {'Content-Type': 'text/html'});
      response.write('hello');
      response.write(' world ');
      response.write('~ ');
      setTimeout(function(){
        response.write(' 大家好 ');
      }, 3000);
      // response.end();
    }).listen(9090, "127.0.0.1");
    

    我们虽然把response.end()注释掉,但是客户端还是能得到内容,而且在hello world输出后的3秒能接收到大家好这三字。可以说借助Node比较简单的实现了分块传输。
    Express、koa等实现分块传输的思想是类似的,具体实现方式可以参考文章:新版卖家中心 Bigpipe 实践(二)

    参考文章

    新版卖家中心 Bigpipe 实践(一)
    新版卖家中心 Bigpipe 实践(二)
    HTTP 协议中的 Transfer-Encoding
    HTTP 协议入门

    相关文章

      网友评论

        本文标题:首屏加载优化

        本文链接:https://www.haomeiwen.com/subject/yrwcyttx.html