本文旨在用最通俗的语言讲述最枯燥的基本知识javascript
面试过前端的老铁都知道,对于前端,面试官喜欢一开始先问些HTML5新增元素啊特性啊,或者是js闭包啊原型啊,或者是css垂直水平居中怎么实现啊之类的基础问题,当你能滚瓜烂熟的回答这些以后,面试官脸上会划过一丝诡异的笑容,而后晴转多云,故做深沉的清一下嗓子问:从用户输入URL到浏览器呈现页面通过了哪些过程?若是你懂,巴拉巴拉回答了一堆,他又接着问:那网页具体是如何渲染出来的呢?若是你还懂,又巴拉巴拉的回答了一堆,他还会继续问:那你有哪些网页性能优化的经验呢?当你还能巴拉巴拉的回答了一堆以后,面试官这下内心就有逼数了,转而去问你一些和技术无关的七大姑八大姨之类的事情,这时候,你就能够欢呼你的offer基本已经到手了。css
那么各位问题来了,真正轮到你去面试的时候
你可否很好的回到这些问题呢?html
- 用户输入URL回车以后,浏览器到底作了啥?
- 页面渲染的完整流程是怎样的?
- 前端性能优化有哪些经验?
若是不能,那咱们往下走:
(有人会疑惑说不是讲前端吗?为毛要讲TCP、DNS这些与前端无关的知识?别慌咯,跟着文章走吧,多学无害!)前端
文章提纲:java
- TCP
- UDP
- 套接字socket
- HTTP协议
- DNS解析
- HTTP请求发起和响应
- 页面渲染的过程
- 页面的性能优化
TCP:Transmission Control Protocol, 传输控制协议,是一种面向链接的、可靠的、基于字节流的传输层通讯协议。
说的这么专业,有啥用呢?
先来举个栗子吧
还记得小时候咱们作的纸杯电话么?两个纸杯用一条绳子连到一块儿,两个各拿一个纸杯把线拉直,一个对着纸杯讲,一个用耳朵对着纸杯听。web
这其实就是一种最简单的链接通讯,两人经过一根线链接起来,声音从这边的纸杯发出经过线传输到另外一个纸杯接收,扩展到如今家家户户都有的固定电话也是如此,它的通讯也是创建在双方可接受而且信任的基础上进行,如:面试
- A拿起电话,拨通0775-6532122,开始呼叫B
- B听到电话声响起,拿起电话,此时A收到B已经拿起电话的声音
- 双方开始讲话。
回到咱们的tcp协议,其实它和上面所说的电话协议差很少,只不过电话的协议是服务于电话通讯,而tcp是服务于网络通信的一种协议,相似的,通信双方创建一次tcp链接,也须要通过三个步骤(握手)。编程
- 客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认。
- 服务器收到syn包,必须确认客户的SYN(ack=j+1),同时本身也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
- 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
上面几个唧唧歪歪的英文看的有点懵逼,翻译一下吧:
(你们最好记一下这些状态码,在服务器链接数的性能优化中会常常用到)后端
SYN:synchronous 创建联机
ACK:acknowledgement 确认
SYN_SENT:请求链接
SYN_RECV:服务端被动打开后,接收到了客户端的SYN而且发送了ACK时的状态。再进一步接收到客户端的ACK就进入ESTABLISHED状态。浏览器
值得注意的是:tcp在握手过程当中并不携带数据,(就像你打电话给酒店订房时,在确认对方是酒店客服人员以前,你也不会立刻把身份证号码报给他吧?),而是在三次握手完成以后,才会进行数据传送。
至于它的应用场景,实际上是根据它自己的特色而定的,好比对网络通信质量有要求,须要保证数据准确性时,就须要用到TCP协议了,如HTTP、ftp等文件传输协议、或一些邮件传输协议(SMTP、pop等)
(UDP协议并不是本文须要重点着笔的内容,可是讲到TCP了,做为他的互补兄弟,在此掠过一笔)
UDP :User Datagram Protocol 用户数据报协议
相比于TCP的面向链接须要反复确认的繁琐步骤,UDP是一中性格特立独行而且主观性超强的非面向链接的协议,使用udp协议常常通讯并不须要创建链接,它只是负责把数据尽量快的发送出去,简单粗暴,而且不可靠,而在接收端,UDP把每一个消息断放入队列中,接收端程序从队列中读取数据。
有人会说,UDP协议这么不可靠,为啥还会造出来呢?
话说回来,天底下没有无用之人,只有你不懂用的人而已,虽然UDP不可靠,可是它的传输速度快,效率高,在一些对数据准确性要求不高的场景,UDP就变得颇有用了,好比qq语音、qq视频。
为何要说嵌套字?
那是由于就像前面说的,TCP或UDP都是一种协议,也就是计算机网络通讯中在传输层的一种协议,简单地说,就是一种约定,就像合做双方的合同同样,而后合同是死的,只有履行合同才是实质性的行动,所以不管是TCP仍是UDP要产生做用,都须要有实际的行为去执行才能体现协议的做用,
那么,有什么办法让这些协议做用呢?
这就要说到socket了。
socket:也叫嵌套字 ,是一组实现TCP/UDP通讯的接口API,也就是说不管TCP仍是UDP,经过对scoket的编程,均可以实现TCP/UCP通讯,做为一个通讯链的句柄,它包含网络通讯必备的5种信息:
- 链接使用的协议
- 本地主机的IP地址
- 本地进程的协议端口
- 远地主机的IP地址
- 远地进程的协议端口
可见,socket包含了通讯本方和对方的ip和端口以及链接使用的协议(TCP/UDP)。通讯双方中的一方(暂称:客户端)经过scoket(嵌套字)对另外一方(暂称:服务端)发起链接请求,服务端在网络上监听请求,当收到客户端发来的请求以后,根据socket里携带的信息,定位到客户端,就相应请求,把socket描述发给客户端,双方确认以后链接就创建了。
所以套接字之间的链接过程有三个步骤:
- 服务器监听:服务器实时监控网络状态等待客户端发来的链接请求
- 客户端请求:客户端根据远程主机服务器的IP地址和协议端口向其发起链接请求
- 链接确认:服务端收到套接字的链接请求以后,就响应请求,把服务端套接字描述发给客户端,客户端收到后一旦确认,则双方创建链接,进行数据交互。
一般状况下socket链接就是TCP链接,所以socket链接一旦创建,通信双方开始互发数据进行通讯,直到其中一方或双方断开链接为止。
socket在即时通信(qq等各类聊天软件)等应用上应用普遍。
HTTP协议:Hypertext Transfer Protocol 也叫超文本传送协议 ,它是一种基于TCP/IP协议栈、在表示层和应用层上的协议(TCP在传输层的协议),通俗一点说就是:
- TCP/IP是位于传输层上的一种协议,用于在网络中传输数据;
- HTTP协议是应用层协议,基于TCP协议,用于包装数据,程序使用它进行通讯,能够简单高效的处理通讯中数据的传输和识别处理
而在如今应用很是普遍的HTTP链接则是创建在HTTP协议上的、处于应用层中的一种具体应用。
上面说到socket链接一旦创建就保持链接状态,而HTTP链接则不同,它基于tcp协议的短链接,也就是客户端发起请求,服务器响应请求以后,链接就会自动断开,不会一直保持。
前面讲了tcp、udp、http…等等都是为了讲一个具体问题而作的知识点铺垫,那就是:咱们开发的web应用中请求的发起和响应,是一个怎样的底层原理。
咱们都知道,web应用绝大部分都是经过HTTP来进行请求的,而URL则是HTTP用来作链接创建和传输数据的一种具体实现,所以在此要简单讲一下URL。
URL:Uniform Resource Locator 统一资源定位符。说白了就是网络上用来标识具体资源的一个地址,包含了用户查找该资源的信息,HTTP使用它来传输数据和创建链接
一个URL有如下组成部分:
- 协议
- 服务器地址(域名或IP+端口)
- 路径
- 文件名
好比:https://www.baidu.com/index.html
其中
- https://是一种协议 固然,HTTP也是 ftp也是…
- www.baidu.com是服务器地址,固然你知道百度的IP也能够,例如我用ping命令获得百度的ip
14.215.177.39,那么我能够用http://14.215.177.39打开百度- index.html包含了路径和文件名,固然一般index.html是能够省略的,因此你打开百度时,并无看到这个。
DNS:Domain Name Server,域名服务器。
是进行域名(domain name)和与之相对应的IP地址 (IP address)转换的服务器。DNS中保存了一张域名(domain name)和与之相对应的IP地址 (IP address)的表,以解析消息的域名。
在平时咱们进行开发时,后端提供的接口地址一般是有IP地址加上端口号(8080什么鬼的)组成的,可是当咱们把网站发布出去时,一般都须要把IP改为用域名。
为何呢?
你想一想哦,好比谷歌的地址是89.12.21.221:9090,百度的地址是132.21.33.221:8766。。。
这么一看你根本没有欲望是记住这些乱七八糟的数字吧?
可是域名就不同了,好比谷歌的google.com,百度的baidu.com 是否是一遍就记住了呢?
因此为了处理这个问题,就须要用域名去映射IP地址,达到易记易用的目的。
所以,当用户在浏览器输入https://www.baidu.com回车时,它经历了如下步骤:
- 浏览器根据地址去自己缓存中查找dns解析记录,若是有,则直接返回IP地址,不然浏览器会查找操做系统中(hosts文件)是否有该域名的dns解析记录,若是有则返回。
- 若是浏览器缓存和操做系统hosts中均无该域名的dns解析记录,或者已通过期,此时就会向域名服务器发起请求来解析这个域名。
- 请求会先到LDNS(本地域名服务器),让它来尝试解析这个域名,若是LDNS也解析不了,则直接到根域名解析器请求解析
- 根域名服务器给LDNS返回一个所查询余的主域名服务器(gTLDServer)地址。
- 此时LDNS再向上一步返回的gTLD服务器发起解析请求。
- gTLD服务器接收到解析请求后查找并返回此域名对应的Name Server域名服务器的地址,这个Name Server一般就是你注册的域名服务器(好比阿里dns、腾讯dns等)
- Name Server域名服务器会查询存储的域名和IP的映射关系表,正常状况下都根据域名获得目标IP记录,连同一个TTL值返回给DNS Server域名服务器
- 返回该域名对应的IP和TTL值,Local DNS Server会缓存这个域名和IP的对应关系,缓存的时间有TTL值控制。
- 把解析的结果返回给用户,用户根据TTL值缓存在本地系统缓存中,域名解析过程结束。
若是这篇文章的主题是网络通讯,那到这里已经能够告一段落了,但今天咱们要讲的是web应用中请求的发起和响应以及页面渲染的原理,所以以上只是铺垫。
在一个web程序开发中,通常都有前端和后端之分,前端负责向后端请求数据和展现页面,后端负责接收请求和作出响应发回给前端,他们之间的协做的桥梁是什么呢?
是API
API是什么?不就是一个URL吗?
URL又是啥呢?上面说到就是HTTP链接的一种具体的载体
所以,
不管对于前端或者是后端,理解HTTP,不管是对自身对编程的理解,仍是和同事协做,都是好处大大的,
下面,根据上面各个知识点的理解,咱们来整理一下并解决一下上面提到的第一个问题:
从用户输入URL,到浏览器呈现给用户页面,通过了什么过程
- 用户输入URL,浏览器获取到URL
- 浏览器(应用层)进行DNS解析(若是输入的是IP地址,此步骤省略)
- 根据解析出的IP地址+端口,浏览器(应用层)发起HTTP请求,请求中携带(请求头header(也可细分为请求行和请求头)、请求体body),
header包含:
- 请求的方法(get、post、put..)
- 协议(http、https、ftp、sftp…)
- 目标url(具体的请求路径已经文件名)
- 一些必要信息(缓存、cookie之类)
body包含:
- 请求的内容
- 请求到达传输层,tcp协议为传输报文提供可靠的字节流传输服务,它经过三次握手等手段来保证传输过程当中的安全可靠。经过对大块数据的分割成一个个报文段的方式提供给大量数据的便携传输。
- 到网络层, 网络层经过ARP寻址获得接收方的Mac地址,IP协议把在传输层被分割成一个个数据包传送接收方。
- 数据到达数据链路层,请求阶段完成
- 接收方在数据链路层收到数据包以后,层层传递到应用层,接收方应用程序就得到到请求报文。
- 接收方收到发送方的HTTP请求以后,进行请求文件资源(如HTML页面)的寻找并响应报文
- 发送方收到响应报文后,若是报文中的状态码表示请求成功,则接受返回的资源(如HTML文件),进行页面渲染。
当一个请求的发起和响应都完成以后,浏览器就会收到响应内容,但浏览器收到的是一串串的代码或URL连接,怎么把这些代码转化成用户能够看得懂的界面呈现出来,就是浏览器的工做了。
目前市场上的浏览器已经不下百种,各个浏览器根据内核又能够分红几大类,每一类浏览器对页面的渲染原理和过程有所差别。
但总的来讲,各个浏览器渲染页面都基本遵循以下图的流程:
图中有几处英文词汇可能很差理解,不要紧,先作一下解释:
- HTML parser:HTML解析器,其本质是将HTML文本解释成DOM tree。
- CSS parser:CSS解析器,其本质是讲DOM中各元素对象加入样式信息
- JavaScript引擎:专门处理JavaScript脚本的虚拟机,其本质是解析JS代码而且把逻辑(HTML和CSS的操做)应用到布局中,从而按程序要的要求呈现相应的结果
- DOM tree:文档对象模型树,也就是浏览器经过HTMLparser解析HTML页面生成的HTML树状结构以及相应的接口。
- render tree:渲染树,也就是浏览器引擎经过DOM Tree和CSS Rule Tree构建出来的一个树状结构,和dom tree不同的是,它只有要最终呈现出来的内容,像或者带有display:none的节点是不存在render tree中的。
- layout:也叫reflow 重排,渲染中的一种行为。当rendertree中任一节点的几何尺寸发生改变了,render tree都会从新布局。
- repaint:重绘,渲染中的一种行为。render tree中任一元素样式属性(几何尺寸没改变)发生改变了,render tree都会从新画,好比字体颜色、背景等变化。
因此,根据关键词汇的解释以及顺着流程图的流程,能够总结出,浏览器解析渲染页面主要包括如下过程:
- 浏览器经过HTMLParser根据深度遍历的原则把HTML解析成DOM Tree。
- 将CSS解析成CSS Rule Tree(CSSOM Tree)。
- 根据DOM树和CSSOM树来构造render Tree。
- layout:根据获得的render tree来计算全部节点在屏幕的位置。
- paint:遍历render树,并调用硬件图形API来绘制每一个节点。
对于页面渲染基本上这样就是一个的流程,看完以后,有没有什么感受在实际编码中能够优化的点呢?没有吧?由于不少细节都没有讲述,所以为了找到可优化的点,在此对页面渲染过程的几个关键步骤作一下陈述:
上面讲到,HTML解析是浏览器的HTML解析器把HTML解析成dom tree,而在解析过程,浏览器根据HTML文件的结构从上到下解析html,HTML元素是以深度优先的方式解析,而script、link、style等标签会使解析过程产生阻塞,阻塞的状况有:
- 外部样式会阻塞内部脚本的执行。
- 外部样式与外部脚本并行加载,但外部样式会阻塞外部脚本执行。
- 若是外部脚本带有async属性,则外部脚本的加载与执行不受外部样式影响
- 若是link标签是动态建立(js生成),无论有无async属性,都不会阻塞外部脚本的加载与执行。
CSS Parser做用就是将不少个CSS文件中的样式合并解析出具备树形结构Style Rules,在对样式解析的过程当中,默认CSS选择器是从右往左进行解析的。至于为何是从右到左,而不是从左到右、也是不会从左到左…
下面举个栗子来讲一下:
假如如今有这样的一个样式:
1#parent .ch1 .dh1 {}
2.fh1 .ch1 .dh1{}
3.ah1 .ch1 .eh1 {}
4#parent .fh1 {}
5.ch1 .dh1{}
复制代码
咱们来比较从左到右和从右到左两种方式的结果:
- 右边的tree复杂度要比左边的低
- 右边的tree公用样式重合度比左边的低
- 右边的tree从根开始的节点数要比左边的少
可能光看这几点没看出什么问题,但你要知道:浏览器中的css解析器负责css的解析,并为每一个节点计算出样式,所以虽然css解析器要作的事情很少,但要每一个节点都要进行遍历查找计算,计算量极大,所以解析的方式是决定其性能的关键点。
就如
1#parant .a{}
2和
3.a{}
复制代码
估计绝大多数人都会认为前者要比后者性能更优,其实否则,在解析过程当中
#paran .a{}意味着css解析器要先找到#parent再找到他下面的.a所在节点
然后者能够直接定位到.a{}所以哪种方式更优,显而易见。
浏览器解析HTML时,当遇到<script>标签就会当即解析脚本,同时阻塞解析文档直到脚本执行完毕(你可能问为何要这样设计,明显啊,脚本的执行是改变css和dom,会形成render tree不停的重绘和重排的),而当<script>是引入外部js文件时,会阻塞到js文件下载完成而且执行完成为止(除非加了defer或者async属性)。脚本在解析过程当中将对dom或css的操做解析出来加入到DOM Tree和cssom中。
把这些度讲完以后,对于性能优化的点,相信你们内心都有点X数了吧,下面简单总结一下平常开发过程当中经常使用的性能优化的地方:
- 优化选择器路径:健全的css选择器当然是能让开发看起来更清晰,而后对于css的解析来讲倒是个很大的性能问题,所以相比于 .a .b .c{} ,更倾向于你们写.c{}。
- 压缩文件:尽量的压缩你的css文件大小,减小资源下载的负担。
- 选择器合并:把有共同的属性内容的一系列选择器组合到一块儿,能压缩空间和资源开销
- 精准样式:尽量减小没必要要的属性设置,好比你只要设置{padding-left:10px}的值,那就避免{padding:0 0 0 10px}这样的写法
- 雪碧图:在合理的地方把一些小的图标合并到一张图中,这样全部的图片只须要一次请求,而后经过定位的方式获取相应的图标,这样能避免一个图标一次请求的资源浪费。
- 避免通配符:.a .b *{} 像这样的选择器,根据从右到左的解析顺序在解析过程当中遇到通配符(*)回去遍历整个dom的,这样性能问题就大大的了。
- 少用Float:Float在渲染时计算量比较大,尽可能减小使用。
- 0值去单位:对于为0的值,尽可能不要加单位,增长兼容性
- 尽量把script标签放到body以后,避免页面须要等待js执行完成以后dom才能继续执行,最大程度保证页面尽快的展现出来。
- 尽量合并script代码,
- css能干的事情,尽可能不要用JavaScript来干。毕竟JavaScript的解析执行过于直接和粗暴,而css效率更高。
- 尽量压缩的js文件,减小资源下载的负担
- 尽量避免在js中逐条操做dom样式,尽量预约义好css样式,而后经过改变样式名来修改dom样式,这样集中式的操做能减小reflow或repaint的次数。
- 尽量少的在js中建立dom,而是预先埋到HTML中用display:none来隐藏,在js中按需调用,减小js对dom的暴力操做。
- 避免再HTML中直接写css代码。
- 使用Viewport加速页面的渲染。
- 使用语义化标签,减小css的代码,增长可读性和SEO。
- 减小标签的使用,dom解析是一个大量遍历的过程,减小无必要的标签,能下降遍历的次数。
- 避免src、href等的值为空。
- 减小dns查询的次数。
以上就是文章的全部内容,总的来讲,入门的文章是领人入门,进阶的文章带人进阶,就像Java的书会有入门教程和进阶教程同样,这个文章里边写的大部分知识点都是为了让读者对页面请求和呈现有一个铺垫和总体的认知,因为涉及的知识点过多,每一个知识点拎出来均可以写一本书,因此你们把本文做为一个引路文,须要对某个知识点进行深刻研究时再找相关书籍研究,不喜勿喷。
以为本文对你有帮助?请分享给更多人
关注「编程无界」,提高装逼技能![]()