从输入URL到页面加载的过程

先梳理下主干流程:

  1. 从浏览器接收url到开启网络请求线程
  2. 开启网络线程到发出一个完整的http请求
  3. 从服务器接收到请求到对应后台接收到请求
  4. 后台和前台的http交互
  5. 缓存问题,http的缓存
  6. 浏览器接收到http数据包后的解析流程
  7. CSS的可视化格式模型
  8. JS引擎解析过程

1. 从浏览器接收url到开启网络请求线程

多进程的浏览器

浏览器是多进程的,有一个主控进程,以及每一个tab页面都会新开一个进程(某些情况下多个tab会合并进程)。

多线程的浏览器内核

每一个tab页面可以看做是浏览器内核进程,然后这个进程是多线程的,它有几大类子线程:

可以看到,里面的JS引擎是内核进程中的一个线程,这也是为什么常说JS引擎是单线程的。

解析URL

输入URL后,会进行解析(URL的本质就是统一资源定位符)

网络请求都是单独的线程

每次网络请求时都需要开辟单独的线程进行,譬如如果URL解析到http协议,就会新建一个网络线程去处理资源下载。

 

2.开启网络线程到发出一个完整的http请求

这一部分主要内容包括:dns查询、tcp/ip请求构建、五层因特网协议栈 等。

如果输入的是域名,需要进行dns解析成ip,大致流程:

  1. 找 dns 缓存(只有一分钟)
  2. 若1找不到,找操作系统自身的 dns 缓存
  3. 若2找不到,读取本地的host
  4. 如果本地没有,浏览器发起一个dns的系统调用(1. 宽带运营商服务器查找本身缓存  2. 运营商服务器发起一个迭代dns解析请求)

tcp/ip请求

http的本质就是tcp/ip请求。

需要了解 三次握手规则建立连接 及 断开连接时的 四次挥手。

三次握手的步骤:

  • 客户端:hello,你是server么?
  • 服务端:hello,我是server,你是client么?
  • 客户端:yes,我是client

建立连接成功后,接下来就正式传输数据。

然后,待断开连接时,需要进行四次挥手

四次挥手的步骤:

  • 主机A:我已经关闭了向你那边的主动通道了,只能被动接收了
  • 主机B:收到通道关闭的信息
  • 主机B:那我也告诉你,我这边向你的主动通道也关闭了
  • 主机A:最后收到数据,之后双方无法通信

为何需要四次挥手呢?

TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。

tcp/ip的并发限制

浏览器对同一域名下并发的tcp连接是有限的(2-10个不等)

而且http1.0中往往一个资源下载就需要对应一个tcp/ip请求

get和post的区别

get和post虽然本质都是tcp/ip,但两者除了在http层面外,在tcp/ip层面也有区别。

get会产生一个tcp数据包,post两个。

具体就是:

  • get请求时,浏览器会把headers和data一起发送出去,服务器响应200(返回数据)
  • post请求时,浏览器先发送headers,服务器响应100 continue,浏览器再发送data,服务器响应200(返回数据)

五层因特网协议栈

从客户端发出http请求到服务器接收,中间会经过一系列的流程。

从应用层的发送http请求,到传输层通过三次握手建立tcp/ip连接,再到网络层的ip寻址,再到数据链路层的封装成帧,最后到物理层的利用物理介质传输。

五层因特尔协议栈其实就是:

  1. 应用层(dns,http)dns解析成ip并发送http请求
  2. 传输层(tcp,udp)建立tcp连接(三次握手)
  3. 网络层(ip,ARP)ip寻址
  4. 数据链路层(PPP)封装成帧
  5. 物理层(利用物理介质传输比特流)物理传输(然后传输的时候通过双绞线,电磁波等各种介质)

 

3.从服务器接收到请求到对应后台接收到请求

负载均衡

用户发起的请求都指向调度服务器(反向代理服务器,譬如安装了nginx控制负载均衡),然后调度服务器根据实际的调度算法,分配不同的请求给对应集群中的服务器执行,然后调度器等待实际服务器的HTTP响应,并将它反馈给用户。

 

4.后台和前台的http交互

前后端交互时,http报文作为信息的载体。

http报文结构

报文一般包括了:通用头部、请求/响应头部、请求/响应体

通用头部

  • Request Url:请求的web服务器地址
  • Request Method:请求方式(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE)
  • Status Code:请求的返回状态码
  • Remote Address:请求的远程服务器地址(会转成IP)

 

5.http的缓存

缓存可以简单的划分成两种类型:强缓存(200 from cache)与 协商缓存(304)。

区别简述如下:

  • 强缓存(200 from cache)时,浏览器如果判断本地缓存未过期,就直接使用,无需发起http请求
  • 协商缓存(304)时,浏览器会向服务器发起http请求,然后服务器告诉浏览器文件未改变,让浏览器使用本地缓存

对于协商缓存,使用 ctrl + F5 强制刷新可以使得缓存无效。但是对于强缓存,在未过期时,必须更新资源路径才能发起新的请求。

缓存头部简述

通过不同的http头部控制强缓存和协商缓存

属于强缓存控制的:

  • (http1.1)Cache-Control/Max-Age

  • (http1.0)Pragma/Expires

属于协商缓存控制的:

  • (http1.1)If-None-Match/E-tag

  • (http1.0)If-Modified-Since/Last-Modified

HTML页面中也有一个meta标签可以控制缓存方案——Pragma

<META HTTP-EQUIV="Pragma" CONTENT="no-cache">

 

6.浏览器接收到http数据包后的解析流程

  1. 解析HTML,构建DOM树
  2. 解析CSS,生成CSS规则树
  3. 合并DOM树和CSS规则树,生成render树
  4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  5. 绘制render树(paint),绘制页面像素信息
  6. 浏览器会将各层的信息发给GPU,GPU会将各层合成(composite),显示在屏幕上

解析HTML,构建DOM树

Bytes → characters → tokens → nodes → DOM
  1. Conversion转换:浏览器将获得的HTML内容(Bytes)基于他的编码转换为单个字符
  2. Tokenizing分词:浏览器按照HTML规范标准将这些字符转换为不同的标记token。每个token都有自己独特的含义以及规则集
  3. Lexing词法分析:分词的结果是得到一堆的token,此时把他们转换为对象,这些对象分别定义他们的属性和规则
  4. DOM构建:因为HTML标记定义的就是不同标签之间的关系,这个关系就像是一个树形结构一样。

解析CSS,生成CSS规则树

Bytes → characters → tokens → nodes → CSSOM

 

构建渲染树

当DOM树和CSSOM都有了后,就要开始构建渲染树了。一般来说,渲染树和DOM树相对应的,但不是严格意义上的一一对应。因为有一些不可见的DOM元素不会插入到渲染树中,如head这种不可见的标签或者 display: none 等。

渲染

有了render树,接下来就是开始渲染,基本流程如下:

途中重要的四个步骤:

  1. 计算CSS样式
  2. 构建渲染树
  3. 布局,主要定位坐标和大小,是否换行,各种position overflow z-index属性
  4. 绘制,将图像绘制出来

图中的线与箭头代表通过js动态修改了DOM或CSS,导致了重新布局(Layout)或渲染(Repaint)。

这里Layout和Repaint的概念是有区别的:

  • Layout,也称为Reflow,即回流。一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树
  • Repaint,即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的时候(如背景色、文字颜色等),此时只需要应用新样式绘制这个元素就可以了

什么会引起回流?

  1. 页面渲染初始化
  2. DOM结构改变,比如删除了某个节点
  3. render树变化,比如减少了padding
  4. 窗口resize
  5. 最复杂的一种:获取某些属性,引发回流

很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流,但是除了render树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效,包括:

  1. offset(Top/Left/Width/Height)
  2. scroll(Top/Left/Width/Height)
  3. cilent(Top/Left/Width/Height)
  4. width,height
  5. 调用了getComputedStyle()或者IE的currentStyle

回流一定伴随着重绘,重绘却可以单独出现。所以有一些优化方案:

  • 减少逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新
  • 避免循环操作dom,创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document
  • 避免多次读取offset等属性。无法避免则将它们缓存到变量
  • 将复杂的元素绝对定位或固定定位,使得它脱离文档流,否则回流代价会很高

注意:改变字体大小会引发回流

资源外链的下载

这里将遇到的静态资源分为一下几大类(未列举所有):

  • CSS样式资源

  • JS脚本资源

  • img图片类资源

当遇到上述的外链时,会单独开启一个下载线程去下载资源(http1.1中是每一个资源的下载都要开启一个http请求,对应一个tcp/ip链接)。

遇到CSS样式资源

CSS资源的处理有几个特定:

  • CSS下载时异步,不会阻塞浏览器构建DOM树
  • 但是会阻塞渲染,也就是在构建render时,会等到css下载解析完毕后才进行(这点与浏览器优化有关,防止css规则不断改变,避免了重复的构建)
  • 有例外, media query声明的CSS是不会阻塞渲染的

遇到JS脚本资源

JS脚本资源的处理有几个特点:

  • 阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML

  • 浏览器的优化,一般现代浏览器有优化,在脚本阻塞时,也会继续下载其它资源(当然有并发上限),但是虽然脚本可以并行下载,解析过程仍然是阻塞的,也就是说必须这个脚本执行完毕后才会接下来的解析,并行下载只是一种优化而已

  • defer与async,普通的脚本是会阻塞浏览器解析的,但是可以加上defer或async属性,这样脚本就变成异步了,可以等到解析完毕后再执行

注意,defer和async是有区别的: defer是延迟执行,而async是异步执行。

  • async是异步执行,异步下载完毕后就会执行,不确保执行顺序,一定在 onload前,但不确定在 DOMContentLoaded事件的前或后

  • defer是延迟执行,在浏览器看起来的效果像是将脚本放在了 body后面一样(虽然按规范应该是在 DOMContentLoaded事件前,但实际上不同浏览器的优化效果不一样,也有可能在它后面)

遇到img图片类资源

遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有src的地方。

loaded和domcontentloaded

  • DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片(譬如如果有async加载的脚本就不一定完成)

  • load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了

 

7.CSS的可视化格式模型

  • CSS中规定每一个元素都有自己的盒子模型(相当于规定了这个元素如何显示)

  • 然后可视化格式模型则是把这些盒子按照规则摆放到页面上,也就是如何布局

  • 换句话说,盒子模型规定了怎么在页面里摆放盒子,盒子的相互作用等等

CSS的可视化格式模型就是规定了浏览器在页面中如何处理文档树。

关键字:

  • 包含块(Containing Block)

  • 控制框(Controlling Box)

  • BFC(Block Formatting Context)

  • IFC(Inline Formatting Context)

  • 定位体系

  • 浮动

  • ...

另外,CSS有三种定位机制: 普通流浮动绝对定位,如无特别提及,下文中都是针对普通流中的。

包含块(Containing Block)

一个元素的box的定位和尺寸,会与某一矩形框有关,这个框就称之为包含块。元素会为它的子孙元素创建包含块,但是,并不是说元素的包含块就是它的父元素,元素的包含块与它的祖先元素的样式等有关系。

譬如:

  • 根元素是最顶端的元素,它没有父节点,它的包含块就是初始包含块

  • static和relative的包含块由它最近的块级、单元格或者行内块祖先元素的内容框(content)创建

  • fixed的包含块是当前可视窗口

  • absolute的包含块由它最近的position 属性为 absolute、 relative或者 fixed的祖先元素创建

    • 如果其祖先元素是行内元素,则包含块取决于其祖先元素的 direction特性

    • 如果祖先元素不是行内元素,那么包含块的区域应该是祖先元素的内边距边界

控制框(Controlling Box)

块级元素和块框以及行内元素和行框的相关概念。

块框:

  • 块级元素会生成一个块框( BlockBox),块框会占据一整行,用来包含子box和生成的内容

  • 块框同时也是一个块包含框( ContainingBox),里面要么只包含块框,要么只包含行内框(不能混杂),如果块框内部有块级元素也有行内元素,那么行内元素会被匿名块框包围

行内框

  • 一个行内元素生成一个行内框

  • 行内元素能排在一行,允许左右有其它元素

display属性的影响

display的几个属性也可以影响不同框的生成:

  • block,元素生成一个块框

  • inline,元素产生一个或多个的行内框

  • inline-block,元素产生一个行内级块框,行内块框的内部会被当作块块来格式化,而此元素本身会被当作行内级框来格式化(这也是为什么会产生 BFC

  • none,不生成框,不再格式化结构中,当然了,另一个 visibility:hidden则会产生一个不可见的框

总结

  • 如果一个框里,有一个块级元素,那么这个框里的内容都会被当作块框来进行格式化,因为只要出现了块级元素,就会将里面的内容分块几块,每一块独占一行(出现行内可以用匿名块框解决)

  • 如果一个框里,没有任何块级元素,那么这个框里的内容会被当成行内框来格式化,因为里面的内容是按照顺序成行的排列

BFC(Block Formatting Context)

BFC规则:

在块格式化上下文中,每一个元素左外边与包含块的左边相接触(对于从右到左的格式化,右外边接触右边),即使存在浮动也是如此(所以浮动元素正常会直接贴近它的包含块的左边,与普通元素重合),除非这个元素也创建了一个新的BFC。

总结几点BFC特点:

  1. 内部 box在垂直方向,一个接一个的放置

  2. box的垂直方向由 margin决定,属于同一个BFC的两个box间的margin会重叠

  3. BFC区域不会与 floatbox重叠(可用于排版)

  4. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此

  5. 计算BFC的高度时,浮动元素也参与计算(不会浮动坍塌)

如何触发BFC?

  1. 根元素

  2. float属性不为 none

  3. position为 absolute或 fixed

  4. display为 inline-blockflexinline-flex, table, table-cell, table-caption

  5. overflow不为 visible

这里提下, display:table,它本身不产生BFC,但是它会产生匿名框(包含 display:table-cell的框),而这个匿名框产生BFC。更多请自行网上搜索。

IFC(Inline Formatting Context)

IFC即行内框产生的格式上下文。

IFC规则

在行内格式化上下文中,框一个接一个地水平排列,起点是包含块的顶部。水平方向上的 margin,border 和 padding 在框之间得到保留,框在垂直方向上可以以不同的方式对齐:它们的顶部或底部对齐,或根据其中文字的基线对齐。

行框

包含那些框的长方形区域,会形成一行,叫做行框。行框的宽度由它的包含块和其中的浮动元素决定,高度的确定由行高度计算规则决定。

行框的规则:

  • 如果几个行内框在水平方向无法放入一个行框内,它们可以分配在两个或多个垂直堆叠的行框中(即行内框的分割)

  • 行框在堆叠时没有垂直方向上的分割且永不重叠

  • 行框的高度总是足够容纳所包含的所有框。不过,它可能高于它包含的最高的框(例如,框对齐会引起基线对齐)

  • 行框的左边接触到其包含块的左边,右边接触到其包含块的右边

 

8.JS引擎解析过程

JS的解释阶段

JS是解释型语言,所以它无需提前编译,而是由解释器实时运行

引擎对JS的处理过程可以简述如下:

  1. 读取代码,进行词法分析,然后将代码分解成词元
  2. 对词元进行语法分析,然后将代码整理成语法树
  3. 使用翻译器,将代码转为字节码
  4. 使用字节码解释器,将字节码转为机器码

最终计算机执行的就是机器码。为了提高运行速度,现代浏览器一般采用即时编译( JIT-JustInTimecompiler)。即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存( inlinecache),这样整个程序的运行速度能得到显著提升。而且,不同浏览器策略可能还不同,有的浏览器就省略了字节码的翻译步骤,直接转为机器码(如chrome的v8)。

总结起来可以认为是: 核心的 JIT编译器将源码编译成机器码运行

JS的预处理阶段

上述将的是解释器的整体过程,这里再提下在正式执行JS前,还会有一个预处理阶段(譬如变量提升,分号补全等)。

预处理阶段会做一些事情,确保JS可以正确执行,这里仅提部分:

分号补全

JS执行是需要分号的,但为什么以下语句却可以正常运行呢?

console.log('a')
console.log('b')

原因就是JS解释器有一个Semicolon Insertion规则,它会按照一定规则,在适当的位置补充分号。

譬如列举几条自动加分号的规则:

  • 当有换行符(包括含有换行符的多行注释),并且下一个 token没法跟前面的语法匹配时,会自动补分号。

  • 当有 }时,如果缺少分号,会补分号。

  • 程序源代码结束时,如果缺少分号,会补分号。

于是,上述的代码就变成了:

console.log('a');
console.log('b');

所以可以正常运行。

当然了,这里有一个经典的例子:

function b () {
    return
    {
        a: 'a'
    };
}

 由于分号补全机制,所以它变成了:

function b () {
    return;
    {
        a: 'a'
    };
}

所以运行后是 undefined

变量提升

一般包括函数提升和变量提升。

a = 1;
b();
function b () {
    console.log('b');
}
var a;

经过变量提升后,就变成:

function b () {
    console.log('b');
}
var a;
a = 1;
b();

转载自这里:https://mp.weixin.qq.com/s/qMsf4DcMhn2cf0fXC-PLVA

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值