从输入 URL 到页面加载发生了什么?

原文链接

简单总结

1.DNS解析
2.TCP链接
3.发送HTTP请求
4.服务器处理请求并返回HTTP报文
5.浏览器解析渲染页面
6.连接结束

具体过程

DNS解析的过程就是寻找哪台机器上有我们需要资源的过程。当我们在浏览器中输入地址时,例如www.baidu.com,其实这不是百度网站的真正意义上的地址。互联网上每一台计算机的唯一标识是它的IP地址,但是IP不好记忆,用户倾向于用好想的方式来寻找互联网上的其他计算机,也就是上面说的百度网址。所以互联网设计者需要在用户的方便性与可用性上做权衡,这个权衡就是做网址到ip地址的转换,这个过程就是DNS解析。它实际上充当了一个翻译的角色,实现了网址到IP地址的转换。
转换过程
DNS解析是一个递归查询的过程

5401241-2c9e1a71fbe649c5.png

上图是查找 www.google.com的IP地址过程。首先在本地域名服务器中查询IP地址,如果没有,本地域名服务器会向根域名服务器发送一个请求,如果根域名服务器也不存在该域名,本地域名会向com顶级域名服务器发送一个请求,依次类推下去。直到最后本地域名服务器得到google的IP地址并把它缓存到本地,供下次查询使用。因此网址的解析是一个从右向左的过程:. -> .com -> google.com. -> www.google.com.。 根域名服务器是一个点.。事实上,真正的网址是 www.google.com.,这个.对应的就是根域名服务器,默认情况下所有的网址的最后一位都是.,既然是默认情况下,为了方便用户,通常都会省略,浏览器在请求DNS的时候会自动加上
DNS优化
上述请求google的ip地址经历了8个步骤,如果每次请求都经历这么多步骤,太耗时,减少步骤的过程就是DNS缓存。
DNS缓存
DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。

  • 在你的chrome浏览器中输入:chrome://dns/,你可以看到chrome浏览器的DNS缓存。
  • 系统缓存主要存在/etc/hosts(Linux系统)中:
    ...
    DNS负载均衡
    真实的互联网世界背后存在成千上百台服务器,大型的网站甚至更多。但是在用户的眼中,它需要的只是处理他的请求,哪台机器处理请求并不重要。DNS可以返回一个合适的机器的IP给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,这种过程就是DNS负载均衡,又叫做DNS重定向。大家耳熟能详的CDN(Content Delivery Network)就是利用DNS的重定向技术,DNS服务器会返回一个跟用户最接近的点的IP地址给用户,CDN节点的服务器负责响应用户的请求,提供所需的内容。

TCP链接

三次握手四次挥手

HTTPs协议

HTTP报文是包裹在TCP报文中发送的,服务器端收到TCP报文时会解包提取出HTTP报文。但是这个过程中存在一定的风险,HTTP报文是明文,如果中间被截取的话会存在一些信息泄露的风险。那么在进入TCP报文之前对HTTP做一次加密就可以解决这个问题了。HTTPS协议的本质就是HTTP + SSL(or TLS)。在HTTP报文进入TCP报文之前,先使用SSL对HTTP报文进行加密。从网络的层级结构看它位于HTTP协议与TCP协议之间。


5401241-4917509f396d88a5.png

HTTPS过程

HTTPS在传输数据之前需要客户端与服务器进行一个握手(TLS/SSL握手),在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL使用了非对称加密,对称加密以及hash等。具体过程请参考经典的阮一峰先生的博客TLS/SSL握手过程
HTTPS相比于HTTP,虽然提供了安全保证,但是势必会带来一些时间上的损耗,如握手和加密等过程,是否使用HTTPS需要根据具体情况在安全和性能方面做出权衡。

HTTP请求

它主要发生在客户端。发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议发送到服务器指定端口(HTTP协议80/8080, HTTPS协议443)。HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文

5401241-dcb8ef31c1b0038f.png

5401241-a86a36fb998b149c.png

请求行:格式:Method Request-URL HTTP-Version CRLF
例如:GET demo.html HTTP/1.1 (回车+换行)
常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
请求头:请求头允许客户端向服务器传递请求的附加信息和客户端自身信息。常见的请求报头有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。请求头和请求正文之间有回车和换行
请求正文:客户端向服务器传递的数据

服务器处理请求并返回HTTP报文

后端从固定的接口收到TCP报文,对TCP连接处理,对HTTP协议解析,并按照报文格式进一步封装成HTTP Response对象,供上层使用。
HTTP响应报文组成:响应行、响应报头和响应报文
服务器返回给浏览器的文本信息,通常THML,CSS,JS,图片等文件放在响应报文。

浏览器解析渲染页面

1、什么是DOCTYPE及作用

<!DOCTYPE> informs the browser which version of HTML (or XML) you used to write the document. Doctype is a declaration, not a tag; you can also refer to it as "document type declaration", or "DTD" for short.
大致意思是,它是一个声明,通知浏览器使用哪个版本的html来解析document。缩写DTD

2、常用的DOCTYPE声明
  • HTML5
    <!DOCTYPE html>,这样写表示使用标准模式
  • HTML4.01严格模式,包含所有HTML元素和属性,但不包括展示性的和弃用的元素例如(font)。不允许框架集(Framesets)。
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  • HTML 4.01 Transitional包含所有 HTML 元素和属性,包括展示性的和弃用的元素(比如 font)。不允许框架集(Framesets)。
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    -HTML 4.01 Frameset该 DTD 等同于 HTML 4.01 Transitional,但允许框架集内容。
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">

3、回流Reflow和重绘Repaint

Reflow

回流简单讲就是浏览器需要去计算元素的位置大小的过程

触发回流
  • 增加、删除、修改DOM节点会导致回流和重绘
  • 移动DOM位置,例如动画
  • 修改CSS样式的大小
  • 修改字体
  • Resize窗口(有可能引发回流)

重绘

重新绘制,显示在屏幕上。等到位置大小颜色字体等确定下来之后,浏览器开始绘制内容,这就是repaint。

触发重绘
  • 回流发生
  • 颜色改变等
    注意:回流必将引起重绘,而重绘不一定会引起回流。

减少回流和重绘的一个场景,添加多个dom节点的时候,将几个节点先添加到一个总的节点上,然后一次性在加入dom中。

5401241-d9b715014f79152e.png

浏览器边解析边渲染。首先解析HTML文件,构建DOM数,同时解析CSS文件构建样式树,DOM树和样式树合并构成渲染树后,开始布局渲染树并将其绘制到屏幕上。此过程中涉及到reflow(回流)和repaint(重绘)。DOM节点的各个元素都是以盒模型的形式存在,在写都需要浏览器去计算位置和大小等,这个过程就是reflow,等到位置大小颜色字体等确定下来之后,浏览器开始绘制内容,这就是repaint。回流和重绘非常耗性能,有时会造成页面卡顿,破会用户体验,因此要尽可能减少回流和重绘。
5401241-019cec72c33ac9a6.png

JS的解析由浏览器中的JS解析引擎完成。JS是单线程运行,同一时间内只能做一件事,所有的任务需要排队,前一个任务结束才能执行后一个任务。但是又存在有些任务很耗时,例如IO操作,所以得要一种机制可以先执行排在后面的任务,这就是同步任务和异步任务。JS的执行机制可以看做是一个主线程加一个任务队列。同步任务放在主线程上执行,异步任务放在队列中。所有的同步任务在主线程上执行,形成一个执行栈, 异步任务有了运行结果就会在任务队列中放置一个事件。脚本运行时先依次运行执行栈,然后从任务队列中提取事件,运行任务队列中的任务,这个过程不断重复,形成一个 事件循环
浏览器在解析过程中,异步请求外部资源,像图片,js等。异步请求不影响HTML文档加载,但是当文档加载时遇见JS文件,HTML文档会挂起渲染,要等到js文件加载完毕解析执行完毕,才继续渲染HTML。因为浏览器单线程, GUI渲染和js解析引擎互斥,不能同时处理。还有,JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因。CSS文件的加载不影响JS文件的加载,但是却影响JS文件的执行。JS代码执行前浏览器必须保证CSS文件已经下载并加载完毕。
ps:加载渲染慢,提高性能就要web优化。

前端优化的目的是什么 ?
  1. 从用户角度而言,优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。
  2. 从服务商角度而言,优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。
  总之,恰当的优化不仅能够改善站点的用户体验并且能够节省相当的资源利用。
 前端优化的途径有很多,按粒度大致可以分为两类,第一类是页面级别的优化,例如 HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等 ;第二类则是代码级别的优化,例如 Javascript中的DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。

提高页面性能

(1)页面内容
减少HTTP请求

  • 合并js、css等文件
  • 使用css雪碧图将背景图合成一个文件
    减少DNS查询
    用户输入 URL 以后,浏览器首先要查询域名(hostname)对应服务器的 IP 地址,一般需要耗费 20-120 毫秒 时间。DNS 查询完成之前,浏览器无法从服务器下载任何数据。
    基于性能考虑,ISP、局域网、操作系统、浏览器都会有相应的 DNS 缓存机制。
  • IE 缓存 30 分钟,可以通过注册表中 DnsCacheTimeout 项设置;
  • Firefox 混存 1 分钟,通过 network.dnsCacheExpiration 配置;
    首次访问、没有相应的 DNS 缓存时,域名越多,查询时间越长。所以应尽量减少域名数量。但基于并行下载考虑,把资源分布到 2 个域名上(最多不超过 4 个)。这是减少 DNS 查询同时保证并行下载的折衷方案。
    避免重定向
    HTTP 重定向通过 301/302 状态码实现。
HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html

客户端收到服务器的重定向响应后,会根据响应头中 Location 的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。
有时重定向无法避免,在糟糕也比抛出 404 好。虽然通过 HTML meta refresh 和 JavaScript 也能实现,但首选 HTTP 3xx 跳转,以保证浏览器「后退」功能正常工作(也利于 SEO)。

  • 最浪费的重定向经常发生、而且很容易被忽略:URL 末尾应该添加 / 但未添加。比如,访问 http://astrology.yahoo.com/astrology 将被 301 重定向到 http://astrology.yahoo.com/astrology/(注意末尾的 /)。如果使用 Apache,可以通过 Aliasmod_rewriteDirectorySlash 解决这个问题。
  • 网站域名变更:CNAME 结合 Aliasmod_rewrite 或者其他服务器类似功能实现跳转。
    避免404错误,404比301/302还严重
    非核心内容异步加载
    js异步加载方式:动态脚本加载、defer、async
    js异步加载区别:(1)defer是在HTML解析完之后才执行,如果是多个,按照加载的顺序依次执行
    (2)async是在加载完成之后立即执行,如果是多个,执行顺序和加载顺序无关
    CSS异步加载
    动态创建link标签
// 创建link标签`
`const myCSS = document.createElement( "link" );`
`myCSS.rel = "stylesheet";`
myCSS.href = "mystyles.css";
// 插入到header的最后位置
document.head.insertBefore( myCSS,document.head.childNodes[document.head.childNodes.length - 1 ].nextSibling );

第二种方式是将link元素的media属性设置为用户浏览器不匹配的媒体类型(或媒体查询),如media="print",甚至可以是完全不存在的类型media="noexist"。对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。
当然,这么做只是为了实现CSS的异步加载,别忘了在文件加载完成之后,将media的值设为screen或all,从而让浏览器开始解析CSS。
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
第三通过rel属性将link元素标记为alternate可选样式表,也能实现浏览器异步加载。同样别忘了加载完成之后,将rel改回去。
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
四使用rel=“preload”<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">as必须
http协商缓存VS强缓存
使用缓存
服务器返回的HTTP头部,有两种定义缓存的保质期方式

  • Expires:一个绝对日期
  • Cache-Control:max-age=相对时间,单位秒
    5401241-d2386c5ecb47f949.png

    在过期前,缓存可以被任意使用,如果没过期,服务器端的文件改了,只能更改请求地址,强制下载。当过期时间到了,需要在验证。验证方式有2种。
  • 添加If-Modified-Since首部
  • 添加If-None-Match首部(在服务器返回的头部中含有ETag时使用)
    如果内容改变,那么返回200,否则返回304
    5401241-76737050d6c9a674.png

    5401241-13a7a0f7789581e7.png

    注意,服务器的缓存有别于cookies,cookie、localStorage是客户端数据存储
    使用CDN

CONTENT DELIVERY NETWORK,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。举例:京东物流
(3)Cookie
减少cookie大小
Cookie 被用于身份认证、个性化设置等诸多用途。Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。

%去除不必要的 Cookie;
%尽量压缩 Cookie 大小;
%注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain;
%设置合适的过期时间。
(4)CSS
异步加载css,不要使用@import 引入css,减少css嵌套,使用gpu加速动画。gpu支持的css属性:transform、opacity、filter.

.ball-running {
  animation: run-around 4s infinite;
}
//开启gpu速,在gpu中渲染
@keyframes run-around {
  0%: {
    transform: translate(0, 0);
  }

  25% {
    transform: translate(200px, 0);
  }

  50% {
    transform: translate(200px, 200px);
  }

 75% {
    transform: translate(0, 200px);
  }
}

(5)JS
异步加载js脚本,将js脚本放在底部。
(6)DOM
避免回流和重绘
(7)图片
1、合成雪碧图,也就是前面说的压缩,减少http请求
2、图片懒加载,及非重要信息延迟加载。

懒加载的原理

将页面中的一张小图片指向一张固定小图,然后定义data-src(自定义属性data-*),属性指向真正的图片地址,当需要图片显示的时候,把src的链接替换成data-src的地址即可。

图片要指定宽高

//css 
.img{
  width:40px;
height:40px;
}
//html
    <img class='img' src="F:\HTML\LIANXI\imge\hot.gif" alt="" data-src="F:\HTML\LIANXI\imge\vip.gif">
//js
 var img=document.querySelector('.img')
    window.onscroll=function(e){
        var ViewHeight=document.documentElement.clientHeight,
        imgTop=img.getBoundingClientRect().top
        if(imgTop<ViewHeight){
            img.src=img.getAttribute('data-src');
        }
    }

上面这段代码,在监听滚轮事件是,会频繁的触发事件处理程序,不断的计算元素到可视区的top距离,可以使用函数节流来控制请求执行次数。实现起来很简单,就是第一次调用函数,设置定时器,以后每次调用函数先清理计时器在定义计时器。真正需要做的部分在定时器的回掉函数中执行。

    var img=document.querySelector('.img'),ViewHeight=document.documentElement.clientHeight
    function throttle(method,context){
        var timer=null
        clearTimeout(timer)
        timer=setTimeout(function(){
            method.call(context)
        },100)
    }
    function getTop(){
        var imgTop=img.getBoundingClientRect().top
        if(imgTop<ViewHeight){
            img.src=img.getAttribute('data-src');
        }
    }
    window.onscroll=function(e){
        throttle(getTop)
    }

参考资料:CSS性能优化的8个技巧
前端性能优化最佳实践
HTTP权威指南

猜你喜欢

转载自blog.csdn.net/weixin_34417635/article/details/87232697