从用户在浏览器地址栏输入网址,到看整个页面,中间都发生了哪些事情?

·  阅读 207

一些概念

关于进程(Process)和线程(Thread)

举个例子:

  • 比如电脑上可以打开很多软件,网页同时开启每一个程序,每一个程序开启就是开启一个进程
  • 每个程序里开启多个任务,每一个任务就是线程

关于单核CPU和多核CPU

  • 单核CPU:同时执行一个任务,多个任务交替完成
  • 多核CPU:并行执行多个任务
  • 浏览器内核:Webkit IE...

浏览器输入URL之后发生的事情

  • A. HTTP请求阶段
  • B. HTTP响应阶段
  • C. 浏览器渲染阶段

A. HTTP请求阶段

第一步 URL解析

http://www.baidu.com:8080/index.html?abc=123&cde=456#heading1

  1. 协议: 用于传输客户端和服务端通信的信息
  • http: 超文本传输协议(除文本外的富媒体资源:视频、图片等)
  • HTTPS: HTTP+SSL(加密传输)
  • FTP:文件传输协议(一般用于文件的上传下载,不属于浏览器)
  1. 域名:服务器地址
  • 顶级域名:baidu.com
  • 一级域名:www.baidu.com
  • 二级域名:h5.baidu.com
  1. 端口号:就是区别同一台服务器上的不同项目(这是nginx配置的)
  • 端口号在0~65535之间
  • HTTP协议默认端口号是80(自己写地址的时候,不加端口号,浏览器会默认加上)
  • HTTPS协议端口号是443
  • FTP协议端口号是21
  1. 请求路径名称: index.html

  2. 查询字符串(问号参数):abc=123&cde=456

  3. 片段标识符(HASH值):#heading1

第二步 缓存检查

客户端拿到服务端的数据后,会把数据缓存一份,缓存的位置在内存缓存或者硬盘缓存

浏览器重启打开一个页面,会查找缓存,先到硬盘缓存查找,没有则关闭网络请求

强缓存与协商缓存 【资源文件的检查】

关于强缓存

  1. 强缓存是服务器设置的,基于响应头的信息返回给客户端(nginx设置)
  2. 客户端浏览器接收到响应后,会自己建立缓存机制

第一次请求时,没有缓存,直接从服务器上获取【建立缓存标识】,客户端拿到内容后,缓存到本地

第二次请求,本地有缓存,会检查缓存有没有过期

  • 没过期 -> 基于缓存信息渲染页面
  • 过期了 -> 重复上一步

但是这样有一个问题是:如果度无端更新的资源文件(项目重新部署版本),会导致用户无法获取到服务器的最新信息

针对这样的情况,前端可以在webpack的output出口文件设置hash值,每次生成的hash值都是不一样的,会保证每次获取的都是最新的缓存 -> index.45121562.js

如果是强缓存,浏览器的network的响应头部分可以看到的响应头信息:

http1.1 => cache-control: max-age 时间

http1.0 expires 时间

关于协商缓存

  • 在强缓存失效的情况下,协商缓存机制触发
  • 客户端与服务端协商

第一次请求,没有任何缓存,直接从服务器获取资源和标识,缓存到客户端本地(成功返回->状态码200)

第二次请求,首先看本地是否有标识

  • 没有 -> 重复上一步骤
  • 有 -> 把标识传递给服务器(这是浏览器操作的),服务器收到结果之后,会做匹配(将部署的标识和客户端传的标识进行比对)
    • 匹配一致 -> 说明没更新,返回状态码304
    • 匹配不一致 -> 说明有更新,把文件信息和标识信息返回给客户端,返回状态码200

客户端判断状态码:

  • 304 -> 把之前缓存的文件渲染
  • 200 -> 按照新的文件信息渲染,并缓存到本地

如果是协商缓存,浏览器的network的响应头部分可以看到的响应头信息:

http1.1 => ETag/If-None-Match 标识

http1.0 => Last-Modified/If-Modified-Since 时间

哪些资源文件不能强缓存?

html文件不能强缓存 -> 用协商缓存,每次都要从服务器获取

css/js/png等文件可以强缓存,也可以协商缓存

所有资源文件的缓存都是nginx配置的

数据缓存

把从服务器获取到的数据进行缓存

通过Ajax/Axios/JQ的Ajax/Fetch 或者跨域方案,从服务器拿到数据

  1. 本地存储:cookie/localStorage/sessionStorage
  2. 本地数据库存储: indexDB

本地存储和本地数据库存储都是存储到物理磁盘,页面关闭重新打开也存在(除了sessionStorage)

  1. vuex/redux:临时存储->页面存储或刷新,缓存信息会消失

实现步骤:

第一次发送数据请求,看本地是否有缓存(或者缓存有没有过期)

本地如果没有缓存;通过AJAX获取数据,再进行数据绑定时,把获取到的值存储到本地(可以手动设置过期时间)

再次发送请求(重新预览页面或者刷新页面):查找本地缓存及是否过期

缓存生效 -> 不再获取缓存数据;反之,重复上一步

方案选择:

  1. 如果存储数据量特别大,用indexDB
  2. LocalStorage 一个源下能存储5MB 永久存储
  3. sessionStorage 是会话存储,页面关闭则存储的信息消失
  4. LocalStorage和sessionStorage不兼容IE8
  5. 同源下,LocalStorage能存储5MB,cookie能存储4kb
  6. cookie有过期时间,清除浏览器垃圾会把cookie清掉(无痕浏览不记录cookie)
  7. LocalStorage为永久存储,如果不手动清除会一直存在(和服务器无关)
  8. 只要本地有cookie,浏览器都会把这些信息发送给服务器(cookie可以兼容IE5)

第三步 DNS解析:把域名解析成IP地址

  • 每次DNS解析过程在20~120ms

第一次请求baidu.com,从没解析过,需要20~120ms从头解析一遍

第一次请求完毕,当前的DNS解析记录会缓存下来(这是浏览器默认的)

第二次请求:会查询缓存信息

  • 递归缓存:会在一下地方查找解析记录:客户端 -> 浏览器缓存 -> 本地host文件 -> 本地DNS解析器缓存 -> 本地DNS服务器缓存 一层一层查找,如果有缓存速度会很快
  • 迭代缓存:如果没有缓存会启动迭代缓存查找: 去DNS服务器查找 -> 去根域名服务器查找 -> 去顶级服务器查找 -> 去权威服务器查找 -> 拿到主机IP
DNS解析优化:

一个产品尽可能少去请求不同的域名(减少DNS的解析次数)

方案一

但是目前开发大部分都是多服务器部署,这样虽然会增加DNS解析次数,但资源分配更合理,提高并发(每台服务器的并发有上限);每个源下同时允许的http并发数6-7个,有助于页面渲染速度

  • web资源服务器 -> nginx/apache [html/css/js]
  • 图片资源服务器 -> 图片消息大,比较消耗性能,包括音视频
  • 数据(接口)服务器:后台程序、数据库
  • 第三方服务器

方案二

  • DNS预解析:dns-prefetch 边渲染边解析
  • GUI渲染线程 -> 自上而下解析代码
  • 利用link的异步加载,在GUI线程渲染页面同时,去实现DNS解析

第四步 三次握手

B. HTTP响应阶段

待更新

  • HTTP状态码
  • 304缓存
  • HTTP报文

第五步 四次挥手

C. 浏览器渲染阶段

第六步 页面渲染

浏览器渲染页面的机制和原理

现代操作系统比如Mac OSX,UNIX,Linux,Windows等,都是支持“多任务”的操作系统

单核CPU执行多任务:操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3……由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样

多核CPU执行多任务:真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行

有些进程还不止同时干一件事,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程

多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样

  1. 解析HTML,生成DOM树,解析CSS,生成CSSOM树
  2. 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
  3. Layout(回流): 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流
  4. Painting(重绘): 根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  5. Display:将像素发送给GPU,展示在页面上
DOM的重绘和回流 Repaint & Reflow
  • 重绘:元素样式的改变(但宽高、大小、位置等不变)

    • 如 outline, visibility, color、background-color等
  • 回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染

    • 如添加或删除可见的DOM元素 ; 元素的位置发生变化; 元素的尺寸发生变化; 内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);
    • 页面一开始渲染的时候(这个无法避免);
    • 因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流....

注意:回流一定会触发重绘,而重绘不一定会回流

前端性能优化之:避免DOM的回流

  • 放弃传统操作dom的时代,基于vue/react开始数据影响视图模式
    • mvvm / mvc / virtual dom / dom diff ......
  • 分离读写操作 (现代的浏览器都有渲染队列的机制)
    • offsetTop、offsetLeft、offsetWidth、offsetHeight、clientTop、clientLeft、clientWidth、clientHeight
    • scrollTop、scrollLeft、scrollWidth、scrollHeight、getComputedStyle、currentStyle....会刷新渲染队列
<style>
#box {
	width: 100px;
	height: 100px;
	background: red;
	border: 10px solid green;
}

.aa {
	width: 200px;
}
</style>

<div id="box"></div>
复制代码
let box = document.getElementById('box');
// 分离读写
// box.style.width = '200px';
// box.style.height = '200px';
// box.style.margin = '10px';
// console.log(box.clientWidth);

// 批量处理
// box.style.cssText='width:200px;height:200px;';
// box.className='aa';

// 缓存处理
// let a=box.clientWidth;
// let b=box.clientHeight;
// box.style.width = a + 10 + 'px';
// box.style.height = b + 10 + 'px';

复制代码
  • 样式集中改变
    • div.style.cssText = 'width:20px;height:20px;'
    • div.className = 'box';
  • 缓存布局信息
    • div.style.left = div.offsetLeft + 1 + 'px';
    • div.style.top = div.offsetTop + 1 + 'px';
    • =>改为
    • var curLeft = div.offsetLeft; var curTop = div.offsetTop;
    • div.style.left = curLeft + 1 + 'px';
    • div.style.top = curTop + 1 + 'px';
  • 元素批量修改
    • 文档碎片:createDocumentFragment
    <style>
    #box {
    	position: absolute;
    	width: 100px;
    	height: 100px;
    	background: red;
    	border: 10px solid green;
    }
    </style>
    <ul id="box">
    </ul>
    复制代码
    // 1.文档碎片
    // let frg = document.createDocumentFragment();
    // for (let i = 0; i < 5; i++) {
    // 	let newLi = document.createElement('li');
    // 	newLi.innerHTML = i;
    // 	//创建的li放到文档碎片中
    // 	frg.appendChild(newLi);
    // }
    // // 一次性把内容放到容器中:引发一次回流
    // box.appendChild(frg);
    // frg = null;
    
    // 字符串拼接
    let str = ``;
    for (let i = 0; i < 5; i++) {
    	str += `<li>${i}</li>`;
    }
    box.innerHTML = str;
    
    // box.style.left='100px';
    box.style.transform='translateX(200px)';
    复制代码
    • 模板字符串拼接
  • 动画效果应用到position属性为absolute或fixed的元素上(脱离文档流)
  • CSS3硬件加速(GPU加速)
    • 比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘;transform \ opacity \ filters ... 这些属性会触发硬件加速,不会引发回流和重绘......
    • 可能会引发的坑:过多使用会占用大量内存,性能消耗严重、有时候会导致字体模糊等
  • 牺牲平滑度换取速度
    • 每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。
    • 每次移动3像素可能看起来平滑度低了,但它不会导致CPU在较慢的机器中抖动
  • 避免table布局和使用css的javascript表达式
分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改