本文旨在用最通俗的语言讲述最枯燥的基础知识。面试过前端的都知道,对于前端,面试官喜欢先问一些HTML5的新元素和特性,或者js闭包,原型,或者css如何实现纵横居中之类的基础问题,当你能流利的回答这些问题的时候,面试官的脸上就会露出诡异的笑容,然后太阳转阴,假装be深深清了清嗓子问道:从用户输入网址到浏览器渲染页面,经过了哪些过程?懂不懂,巴拉巴拉回答了一堆问题,然后他又问:网页是怎么渲染的?如果你还看得懂,而他已经回答了一堆废话废话,他会继续问:你在网页性能优化方面有什么经验?当你能回答一大堆blahblahblahblah的时候,面试官就不好意思转而问你一些跟技术无关的东西了。这时候你就可以欢呼了,你的offer基本到了。那么问题来了,各位,真的轮到你去面试的时候,你能不能好好的回到这些问题呢?用户输入网址并回车后,浏览器到底做了什么?页面渲染的完整过程是怎样的?您在前端性能优化方面有哪些经验?如果没有,那我们再往下看:(有的人会疑惑,这不是讲前端的吗?为什么要讲TCP、DNS等与前端无关的知识?别慌,跟着文章学多了无妨!)文章大纲:TCPPUDPsocketsocketHTTP协议DNS分析HTTP请求发起和响应页面渲染过程页面性能优化TCP连接TCP:TransmissionControlProtocol,传输控制协议,是面向连接的,可靠的、基于字节流的传输层通信协议。这么专业有什么用?先举个栗子。还记得我们小时候做的纸杯手机吗?两个纸杯用绳子连在一起,两人各拿一个纸杯理顺线头,一个对着纸杯说话,一个用耳朵听纸杯。这其实就是最简单的连接通信。两个人被一根电线连接起来。声音从这里的纸杯发出,通过电线传到另一个纸杯。它扩展到现在家家户户都有的固定电话。它的沟通也是建立在相互接受和信任的基础上的。例如:A拿起电话,拨通0775-6532122,开始呼叫BB听到来电声音,接起电话。这时,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:同步建立在线ACK:acknowledgment确认SYN_SENT:请求连接SYN_RECV:服务器被动开启后,收到客户端的SYN,发送ACK。进一步收到客户端的ACK后,进入ESTABLISHED状态。值得注意的是,在握手过程中tcp是不携带数据的,(就像你打电话给酒店订房,在确认对方是酒店客服之前,你不会马上把身份证号报给他?),但是在三次握手完成后,数据才会被传输。至于它的应用场景,其实还是要看它自身的特点。例如,当对网络通信质量有要求,需要保证数据的准确性时,就需要用到TCP协议,比如HTTP、ftp等文件传输协议,或者一些邮件传输协议(SMTP、pop、etc.)UDP协议(UDP协议不是本文的重点,但是说到TCP,作为他的补充兄弟,这里快刷刷)UDP:UserDatagramProtocolUserDatagram与TCP的面向连接的协议相比,需要反复的确认和繁琐的步骤,UDP是一种非面向连接的协议,具有独特的个性和很强的主观性。使用UDP协议的频繁通信不需要建立连接。它只负责将数据尽快发送出去,简单粗暴,不可靠。在接收端,UDP将每条消息放入队列,接收端程序从队列中读取数据。有人会说,UDP协议这么不靠谱,为什么还要创建呢?话说回来,世界上没有不会用的人,只有不会用的人。UDP虽然不可靠,但传输速度快、效率高。在一些对数据精度要求不高的场景下,UDP就变得非常有用,比如qq语音,qq视频。socketsocket为什么说嵌套词?那是因为前面说过,TCP或者UDP是一种协议,也就是计算机网络通信中传输层的协议。简单的说,就是一个协议,就像双方之间的合同一样,然后合同就死了,只有合同的履行才是实质性的动作。因此,无论是TCP还是UDP,要想有效,都需要实际的行为来执行,才能体现协议的作用。那么,有没有办法让这些协议发挥作用呢?这是关于套接字的。Socket:也叫嵌套词,是一组实现TCP/UDP通信的接口API,也就是说,不管是TCP还是UDP,通过对socket进行编程,就可以实现TCP/UCP通信。作为一个通信链的句柄,包含了网络通信所必需的5种信息:连接使用的协议本地主机的IP地址本地进程的协议端口远程主机的IP地址进程的协议端口远程进程可见,socket中包含通讯方和对方的ip和端口以及连接使用的协议(TCP/UDP)。通信的一方(暂称:client)通过socket(嵌套词)向另一方(暂称:server)发起连接请求,server在网络上监听该请求。当收到客户端的请求后,根据socket中携带的信息定位到客户端,并根据相应的请求将socket描述发送给客户端,双方确认后建立连接。因此,socket之间的连接过程分为三个步骤:服务器监听:服务器实时监听网络状态,等待客户端的连接请求客户端请求:客户端根据需要向远程主机服务器发起连接请求它的IP地址和协议端口连接确认:服务器收到套接字连接请求后,响应请求,将服务器套接字描述发送给客户端。一旦客户端收到确认,双方就建立连接并交换数据。通常,套接字连接是TCP连接,所以一旦建立套接字连接,通信双方就开始向对方发送数据进行通信,直到一方或双方断开连接。Socket广泛应用于即时通讯(qq等聊天软件)等应用中。HTTP协议HTTP协议:HypertextTransferProtocol,又称超文本传输??协议,是一种基于TCP/IP协议栈,在表现层和应用层(TCP协议在传输层)的协议,通俗地说:TCP/IP是位于传输层的协议,用于在网络中传输数据;HTTP协议是一个应用层协议,基于TCP协议,用于封装数据,程序使用它进行通信,可以简单高效地处理通信中的数据。应用层基于HTTP协议。上面说了,socket连接一旦建立,就会一直保持连接状态,但是HTTP连接就不一样了。它基于tcp协议的短连接,即客户端发起请求。服务器响应请求后,连接会自动断开,不会永远保持。.URL前面提到的tcp、udp、http等都是针对一个具体问题的知识点,即:我们开发的web应用中请求发起和响应的底层原理是什么。我们都知道大部分的Web应用都是通过HTTP请求的,而URL是HTTP的一种具体实现,用于建立连接和传输数据,所以这里简单说一下URL。URL:统一资源定位器统一资源定位器。说白了,就是用来标识网络上特定资源的地址,包括用户搜索该资源的信息。HTTP使用它来传输数据和建立连接。一个URL有以下几部分组成:协议服务器地址(域名或IP+端口)路径文件例如:https://www.baidu.com/index.html其中https://是一个协议,当然HTTP是还有ftp...www.baidu.com是服务器地址,当然你知道百度的IP也是有的,比如我用ping命令得到百度的ip14.215.177.39,那么就可以用http://14.215.177.39打开百度index.html包含路径和文件名,当然index.html一般可以省略,所以打开百度的时候没有看到这个。DNSDNS:DomainNameServer,域名服务器。它是将域名(domainname)和对应的IP地址(IPaddress)进行转换的服务器。DNS存储一个域名和对应的IP地址表,用于解析消息的域名。我们平时开发的时候,后端提供的接口地址一般都是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无法解析,则直接到根域名解析器请求解析。根域名服务器将返回一个查询到的主域名服务器(gTLDServer)地址。此时LDNS向上一步返回的gTLD服务器发起解析请求。gTLD服务器收到解析请求后,查找并返回该域名对应的NameServer域名服务器地址。这个NameServer一般就是你注册的域名服务器(比如阿里dns,腾讯dns等)。NameServer域名服务器会查询存储的域名和IP映射关系表,正常情况下根据域名获取目标IP记录,连同一个TTL值返回给DNSServer。域名服务器返回域名对应的IP和TTL值。LocalDNSServer会缓存域名和IP的对应关系。缓存时间由TTL值控制。解析结果返回给用户,用户根据TTL值缓存在本地系统缓存中,域名解析过程结束。HTTP请求发起与响应如果本文的主题是网络通信,到这里就可以告一段落了,但是今天我们要讲的是web应用和页面渲染中请求发起与响应的原理,所以以上只是一个伏笔。在Web程序的开发中,一般有前端和后端。前端负责向后端请求数据和展示页面,后端负责接收请求并做出响应并返回给前端。他们之间协作的桥梁是什么?什么是API?不就是一个网址吗?什么是网址?上面说的是HTTP连接的具体载体。因此,无论是对于前端还是后端,了解HTTP,无论是对于自己理解编程,还是与同事协作,都是大有裨益的。接下来,根据上面了解的知识点,我们来梳理解决上面提到的第一个问题:从用户输入网址到浏览器将页面呈现给用户,从用户输入网址到现在经过了哪些过程URL,浏览器获取URL应用层)进行DNS解析(如果输入的是IP地址,则此步骤省略)根据解析后的IP地址+端口,浏览器(应用层)发起HTTP请求,请求carrys(requestheaderheader(也可以细分为Requestline和requestheader),requestbody),header包含:请求方法(get,post,put..)协议(http,https,ftp,sftp...)targeturl(具体请求路径和文件名)一些必要的信息(缓存、cookie等)body包含:请求的内容到达传输层,tcp协议为传输报文提供可靠的字节流传输服务,它采用三次握手等方式保证传输过程的安全可靠。它通过将大数据块划分为消息段来提供大量数据的可移植传输。到网络层,网络层通过ARP寻址获得接收方的Mac地址,IP协议在传输层将数据包分成一个个数据包发送给接收方。数据到达数据链路层,请求阶段完成。接收方在数据链路层接收到数据包后,逐层传递给应用层,接收方应用程序获得请求报文。接收方收到发送方的HTTP请求后,搜索请求的文件资源(如HTML页面)并响应该消息。发送方收到响应消息后,如果消息中的状态码表示请求成功,则接受返回的资源(如HTML文件)进行页面渲染。页面渲染当发起请求响应完成后,浏览器会收到响应内容,但是浏览器收到的是一串代码或者URL链接,如何将这些代码转换成用户可以看懂的界面呈现,这是浏览器的工作。目前市面上的浏览器不下百种,每种浏览器又可以根据内核分为几类。每种类型的浏览器都有不同的渲染页面的原理和过程。但总的来说,各个浏览器渲染页面基本都遵循下图所示的流程:图中有几个英文单词可能不太好理解,没关系,先解释一下:HTMLparser:HTMLparser,其本质是将HTML文本解释为DOM树。CSSparser:CSS解析器,其本质是为DOM中的每个元素对象添加样式信息JavaScriptengine:专门处理JavaScript脚本的虚拟机,其本质是解析JS代码并将逻辑(HTML和CSS操作)应用于布局,从而根据程序的要求呈现相应的结果。DOM树:DocumentObjectModelTree,即浏览器通过HTMLparser解析HTML页面生成的HTML树结构和相应的界面。Rendertree:渲染树,即浏览器引擎通过DOMTree和CSSRuleTree构建的树结构。与dom树不同的是,它只有最终要呈现的内容,像
或者Display:none这样的节点在渲染树中是不存在的。布局:也叫回流重排,渲染中的一种行为。当rendertree中任一节点的几何尺寸发生变化时,rendertree将重新布局。repaint:重绘,渲染中的一种行为。如果rendertree中任意一个元素的style属性(几何尺寸没有变化),rendertree都会被重绘,比如字体颜色和背景发生变化。因此,根据关键字的解释和下面流程图的过程,可以得出浏览器解析和渲染页面主要包括以下过程:浏览器根据深度遍历。将CSS解析为CSS规则树(CSSOM树)。根据DOM树和CSSOM树构造renderTree。layout:根据得到的渲染树计算所有节点在屏幕上的位置。paint:遍历渲染树,调用硬件图形API绘制每个节点。前端性能优化基本上就是一个页??面渲染的过程。看完之后,在实际编码中有没有可以优化的地方?不?因为很多细节没有描述,所以为了找到可以优化的点,这里介绍一下页面渲染过程中的一些关键步骤:1.HTML解析:上面说了HTML解析就是浏览器的HTML解析器,将HTML进行转换它被解析成一棵dom树,在解析过程中,浏览器根据html文件的结构,从上到下解析html。HTML元素以深度优先的方式解析,script、link、style等标签会阻塞解析过程,阻塞有外部样式阻塞内部脚本执行的情况。外部样式与外部脚本并行加载,但外部样式会阻止外部脚本执行。如果外部脚本有async属性,则外部脚本的加载和执行不会受到外部样式的影响。如果link标签是动态创建的(js生成的),不管有没有async属性,都不会阻塞外部脚本的加载和执行。2、CSS解析:CSSParser的作用是将众多CSS文件中的样式合并解析成树状结构的StyleRules。在样式解析的过程中,默认的CSS选择器是从右向左解析的。至于为什么是从右到左,不是从左到右,也不会是从左到左……举个栗子:如果现在有这样的样式:#parent.ch1.dh1{}.fh1.ch1.dh1{}.ah1.ch1.eh1{}#parent.fh1{}.ch1.dh1{}让我们从左到右和从右到左比较一下结果:从两张图中你可以看出几个比较要点:右边的树的复杂度比左边的要低。右侧树的共同样式重合度低于左侧。从右边树的根开始的节点数比左边少。也许你只看这几点,看不出来。有什么不对,但是你需要知道:浏览器中的css解析器负责解析css并为每个节点计算样式,所以虽然css解析器没有什么可做的,但它需要为每个节点做遍历查找计算的计算量非常大,所以解析的方式是决定其性能的关键。就像#parant.a{}和.a{}一样,估计大部分人会认为前者的性能比后者好。事实上,并非如此。在解析的过程中,#paran.a{}意味着css解析器首先要找到#parent在他下面找到.a的节点,后者可以直接定位到.a{},所以哪种方式更好是显而易见的。3、脚本执行:浏览器在解析HTML时,遇到导入外部js文件时,会阻塞,直到js文件下载执行完毕(除非添加了defer或async属性)。在解析过程中,脚本会解析对dom或css的操作,并添加到DOMTree和cssom中。性能优化说完这几个度,相信大家对于性能优化心里都有点X数了。这里简单总结一下日常开发过程中常用的性能优化:1.针对css:优化选择器路径:一个好的css选择器固然可以让开发看起来更清晰,但是对于css解析来说是一个很大的性能问题,所以相对于.a.b.c{},大家更倾向于写成.c{}。压缩文件:尽可能压缩你的css文件大小,以减少资源下载的负担。SelectorMerging:将一系列具有共同属性内容的选择器组合在一起,可以压缩空间和资源开销PreciseStyle:尽量减少不必要的属性设置,例如只需要设置{padding-left:10px}的值,然后避免{padding:00010px}精灵图:将一些小图标在合理的地方合并到一张图片中,这样所有的图片只需要一次请求,然后通过定位获取对应的图标,可以避免一次请求一个图标的资源浪费.避免通配符:.a.b*{}这样的选择器,按照从右到左的解析顺序,在解析的过程中遇到通配符(*)就回头遍历整个dom,所以性能问题会很大。少用Float:Float在渲染时计算量比较大,尽量少用。0valuetounit:对于0的值,尽量不要加单位增加兼容性2.对于JavaScript:script标签尽量放在body后面,避免页面需要等待js执行完成在DOM可以继续执行之前,要保证页面尽快显示。尽量合并脚本代码,尽量不要用JavaScript来做css能做的事情。毕竟JavaScript的解析和执行过于直接粗暴,而CSS效率更高。尽量压缩js文件,减少资源下载的负担。尽量避免在js中一个一个操作dom样式,尽可能预定义css样式,然后通过更改样式名修改dom样式,这样集中操作可以减少reflow或者repaints的次数。尽量少在js中创建dom,而是提前埋在html中,用display:none隐藏,在js中按需调用,减少js对dom的暴力操作。3.对于HTML:避免直接在HTML中编写css代码。使用Viewport来加速页面的渲染。使用语义标签减少css代码,增加可读性和SEO。减少标签的使用,dom解析是一个遍历很多的过程,减少不必要的标签可以减少遍历次数。src、href等避免空值。减少dns查询次数。以上就是文章的全部内容。一般来说,入门篇是带领人入门,进阶篇是带领人走向高级。就像Java书籍有入门教程和高级教程一样。其中一些知识点是为了让读者对页面请求和呈现有一个铺垫和整体的认识。因为涉及到的知识点太多,每个知识点都可以拿出来写一本书,所以大家以本文为指导,当需要对某个知识点进行深入研究时,可以找相关书籍进行学习研究。如果您不喜欢它,请不要喷它。觉得这篇文章对您有帮助?请分享给更多人关注《编程无界》,提升你的造作技能