一、浏览器渲染引擎
主要模块
* 一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块
* HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树。抽象html结构,把html结构生产对应的树状对象。
* CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施
* Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,javascript引擎能够解释javascript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息,从而改变渲染的结果。
* 布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型
* 绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果
>备注:文档对象模型(Document Object Model,简称DOM)
大致的渲染过程
* 浏览器渲染页面的整个过程:浏览器会从上到下解析文档。
1. 遇见 HTML 标记,调用HTML解析器解析为对应的 token (一个token就是一个标签文本的序列化)并构建 DOM 树(就是一块内存,保存着tokens,建立它们之间的关系)。
2. 遇见 style/link 标记调用相应解析器处理CSS标记,并构建出CSS样式树。
3. 遇见 script 标记 调用javascript引擎 处理script标记、绑定事件、修改DOM树/CSS树等
4. 将 DOM树 与 CSS树 合并成一个渲染树。
5. 根据渲染树来渲染,以计算每个节点的几何信息(这一过程需要依赖GPU)。布局
6. 最终将各个节点绘制到屏幕上。绘制
>以上这些模块依赖很多其他的基础模块,包括要使用到网络 存储 2D/3D图像 音频视频解码器 和 图片解码器。
>所以渲染引擎中还会包括如何使用这些依赖模块的部分。
配图浏览器渲染过程:
web前后端整体图实:
如果说html内容比较多,超过64kb(浏览器一次请求拿到64kb的数据),浏览器的解析是接收一点数据解析一点,这就是html的异步解析。
二、阻塞渲染
1.关于css阻塞:
声明:只有link引入的外部css才能够产生阻塞。
1.style标签中的样式:
(1). 由html解析器进行解析;
(2). 不阻塞浏览器渲染(可能会产生“闪屏现象”);如果样式特别多,页面结构出来了,css还没有解析完成,刚开始只有结构没有样式,样式解析完后在加上样式;
(3). 不阻塞DOM解析;
2.link引入的外部css样式(推荐使用的方式):
(1). 由CSS解析器进行解析,并且是同步解析的。假如6万多行样式,都在改一个标签的样式,最终结构是蓝色,css解析器会给出最后的结构蓝色去渲染。
(2). 阻塞浏览器渲染(可以利用这种阻塞避免“闪屏现象”)。
(3). 阻塞其后面的js语句的执行
(4). 不阻塞DOM的解析(绝大多数浏览器的工作方式),由于css是由css解析器解析的,DOM是由html解析器解析的,两者各干各的,这种工作模式效率更高一点。
注意:避免在link标签后面插入脚本,会阻塞脚本的执行。
解析css的同时,可以加载js,加载回来后,如果css没有解析完成,js不能执行。因为js可以改变样式和dom结构,如果说不阻塞js的执行,js可能会把dom移除,这个时候css的解析将会失去意义。这也是js要放在body最后的原因之一,方式css的解析阻碍js的执行。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>外部css样式</title>
<link rel="stylesheet" type="text/css" href="css/index.css"/>
<script src="index.js"></script>
</head>
<body>
<img src="img/01.jpg"/>
<!--
1.link进来的样式,是由css解析器去解析,并且是同步解析的。
2.css解析器会阻塞页面的渲染。(也可以说link进来的外部样式会阻塞页面渲染,利用他避免闪屏)
3.推荐使用link方式去引入样式。
4. 阻塞其后面的js语句的执行
-->
</body>
</html>
3.优化核心理念:尽可能快的提高外部css加载速度
(1).使用CDN节点进行外部资源加速。
(2).对css进行压缩(利用打包工具,比如webpack,gulp等)。
(3).减少http请求数,将多个css文件合并。
(4).优化样式表的代码
2.关于js阻塞:
1.阻塞后续DOM解析:
原因:浏览器不知道后续脚本的内容,如果先去解析了下面的DOM,而随后的js删除了后面所有的DOM,
那么浏览器就做了无用功,浏览器无法预估脚本里面具体做了什么操作,例如像document.write这种操作,索性全部停住,等脚本执行完了,浏览器再继续向下解析DOM。
2.阻塞页面渲染:
原因:js中也可以给DOM设置样式,浏览器等该脚本执行完毕,渲染出一个最终结果,避免做无用功。
3.阻塞后续js的执行:
原因:维护依赖关系,例如:必须先引入jQuery再引入bootstrap
3 实践
验证link 引入的css阻碍浏览器的渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 400px;
height: 400px;
background: gray;
}
</style>
<!-- 验证外部css的解析阻碍浏览器的渲染 文件3s后返回 -->
<link rel="stylesheet" href="css/sleep-index.css">
</head>
<body>
<div class="box"></div>
</body>
</html>
<!-- 如果说外链接的css不阻塞浏览器的渲染,页面将会先渲染灰色,3s后再旭渲染为红色
事实是页面会等待3s后直接渲染为红色,这说明在解析外部css的同时,浏览器没有渲染。
-->
const http = require('http')
const url = require('url')
const fs = require('fs')
const path = require('path')
function sleep() {
return new Promise((resolve) => {
setTimeout(resolve, 3000)
})
}
http.createServer(async function(req, res) {
let url = req.url;
console.log(url)
let result = {}, ContentType = '', time;
if(url === '/') {
result = await new Promise(function(resolve, reject){
fs.readFile(path.join(__dirname, 'index.html'), 'utf-8', function(err, file) {
if(err) reject(err)
ContentType = "text/html";
resolve(file);
})
})
}
if(/css/.test(url)) {
const urlArr = url.match(/sleep-/)
if(urlArr) { time = true}
url = url.replace(/sleep-/, '')
result = await new Promise(function(resolve, reject) {
fs.readFile(path.join(__dirname, url), function(err, file) {
if(err) reject(err)
ContentType = "text/css";
resolve(file);
})
})
}
if(time) {
await sleep()
}
res.writeHead(200, {"Content-Type": ContentType})
res.write(result)
res.end()
}).listen(8081, function() {
console.log('8081')
})
defer的作用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 400px;
height: 400px;
background: gray;
}
</style>
<script src="js/logDiv.js"></script>
</head>
<body>
<div class="box"></div>
</body>
</html>
js内容
var divs = document.querySelector('div')
console.log(divs)
打印为null,js的加载和执行阻碍dom的解析,这个时候div还没有解析出来,所以会是null。
修改js内容:
window.onload = function() {
var divs = document.querySelector('div')
console.log(divs)
}
这里可以正常打印为div,换下面的也可以正常打印;DOMContentLoaded事件 在dom解析完成后触发
window.addEventListener('DOMContentLoaded', function() {
var divs = document.querySelector('div')
console.log(divs)
})
使用defer, 紧随页面dom解析之后,在DOMContentLoaded事件触发之前执行。
<script defer src="js/logDiv.js"></script>
window.addEventListener('DOMContentLoaded', function() {
console.log('dom解析完成')
})
var divs = document.querySelector('div')
console.log(divs)
// <div class="box"></div
// dom解析完成
验证js阻塞
js放在头部,服务端延迟5s后返回,页面5s后才开始出现内容,空白时间5s以上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box1,.box2, .box3, .box4 {
width: 400px;
height: 400px;
}
.box1 {
background: indigo;
}
.box2 {
background: gray;
}
.box3 {
background: honeydew;
}
.box4 {
background: indianred;
}
</style>
<script>
window.onload = function() {
console.log('页面渲染完成')
}
</script>
<script src="js/sleep-logDiv.js"></script>
</head>
<body>
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
<div class="box4"></div>
</body>
</html>
注意:把js代码放在body的标签的最后面,</body>的前面,情况就完全不一样了。
给js也增加延迟加载功能,启动服务器后,会看到页面瞬间渲染完成,js在5s后输出内容
if(/js/.test(url)) {
const urlArr = url.match(/sleep-/)
if(urlArr) { time = true}
url = url.replace(/sleep-/, '')
result = await new Promise(function(resolve, reject) {
fs.readFile(path.join(__dirname, url), function(err, file) {
if(err) reject(err)
ContentType = "text/js"
resolve(file);
})
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box1,.box2, .box3, .box4 {
width: 400px;
height: 400px;
}
.box1 {
background: indigo;
}
.box2 {
background: gray;
}
.box3 {
background: honeydew;
}
.box4 {
background: indianred;
}
</style>
</head>
<body>
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
<div class="box4"></div>
<script src="js/sleep-logDiv.js"></script>
</body>
</html>
js文件内容:
var divs = document.querySelector('div')
console.log(divs)
既然dom的解析完成是遇到</html>,那么在</html>之前的外部请求不就照样阻塞dom树的形成么,那么在这个js下载执行完成前,应该一直空白的,然而实际效果是页面上很快出现了带有背景色的几个box,然后控制台过了很久才有输出。
因为现代浏览器除了dom解析完成之后渲染外,为了提高用户体验,会有个first paint。部分内容将被解析并显示,也就是浏览器能够渲染不完整的dom
3.备注
【备注1】:css的解析和js的执行是互斥的(互相排斥),css解析的时候js停止执行,js执行的时候css停止解析。
【备注2】:无论css阻塞,还是js阻塞,都不会阻塞浏览器加载外部资源(图片、视频、样式、脚本等)
原因:浏览器始终处于一种:“先把请求发出去”的工作模式,只要是涉及到网络请求的内容,
无论是:图片、样式、脚本,都会先发送请求去获取资源,至于资源到本地之后什么时候用,
由浏览器自己协调。这种做法效率很高。
【备注3】:WebKit 和 Firefox 都进行了【预解析】这项优化。在执行js脚本时,浏览器的其他线程会预解析文档的其余部分,
找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,
从而提高总体速度。请注意,预解析器不会修改 DOM 树
在上述的过程中,网页在加载和渲染过程中会触发“DOMContentLoaded”和“onload”事件
分别是在DOM树构建(解析)完成之后,以及DOM树构建完并且网页所依赖的资源都加载完之后
* 上面介绍的是一个完整的渲染过程,但现代网页很多都是动态的,这意味着在渲染完成之后,由于网页的动画或者用户的交互,
浏览器其实一直在不停地重复执行渲染过程。(重绘重排),以上的数字表示的是基本顺序,这不是严格一致的,
这个过程可能重复也可能交叉
三 CSS图层,重排,重绘
css图层
浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。
在渲染DOM的时候,浏览器所做的工作实际上是:
1. 获取DOM后分割为多个图层
2. 对每个图层的节点计算样式结果 (Recalculate style--样式重计算)
3. 为每个节点生成图形和位置 (Layout--布局,重排,回流)
4. 将每个节点绘制填充到图层位图中 (Paint--重绘)
5. 图层作为纹理上传至GPU
6. 组合多个图层到页面上生成最终屏幕图像 (Composite Layers--图层重组)
图层创建的条件
Chrome浏览器满足以下任意情况就会创建图层:
1. 拥有具有3D变换的CSS属性
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#wrap{
width: 200px;
height: 200px;
background: pink;
/*transform: translateZ(0);*/
}
</style>
</head>
<body>
<div id="wrap">
</div>
</body>
</html>
加上transform: translateZ(0); 形成两个图层。
2. 使用加速视频解码的<video>节点
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<video src="./media/test.mp4" width="400" height="400" controls></video>
</body>
</html>
3. <canvas>节点
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
background: gray;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
</body>
<script type="text/javascript">
window.onload=function(){
var canvas = document.querySelector("canvas");
if(canvas.getContext){
var ctx = canvas.getContext("2d");
ctx.fillRect(0,0,100,100);
ctx.strokeRect(100,100,100,100);
}
}
</script>
</html>
4. CSS3动画的节点
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
@keyframes move{
from{
background-position: 0 0;
}
to{
background-position: -576px 0;
}
}
#wrap{
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%,-50%,0);
width: 48px;
height: 48px;
background: url(./media/animation.png);
animation: move .5s infinite steps(12,start);
}
</style>
</head>
<body>
<div id="wrap">
</div>
</body>
</html>
5. 拥有CSS加速属性的元素(will-change)
开启GPU硬件加速
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#wrap{
width: 200px;
height: 200px;
background: pink;
will-change:transform
}
</style>
</head>
<body>
<div id="wrap">
</div>
</body>
</html>
重绘(Repaint)
重绘是一个元素外观的改变所触发的浏览器行为,例如改变outline、背景色等属性。浏览器会根据元素的新属性重新绘制,
使元素呈现新的外观。重绘不会带来重新布局,所以并不一定伴随重排。
需要注意的是:重绘重排都是以图层为单位,如果图层中某个元素需要重绘,那么整个图层都需要重绘。
所以为了提高性能,我们应该让这些“变化的东西”拥有一个自己一个图层,
不过好在绝大多数的浏览器自己会为CSS3动画的节点自动创建图层。
重排(Reflow 又称:回流)
渲染对象在创建完成并添加到渲染树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排
"重绘"不一定需要"重排",比如改变某个网页元素的颜色,就只会触发"重绘",不会触发"重排",因为布局没有改变。
"重排"大多数情况下会导致"重绘",比如改变一个网页元素的位置,就会同时触发"重排"和"重绘",因为布局改变了。
触发重绘的属性
* color * background * outline-color
* border-style * background-image * outline
* border-radius * background-position * outline-style
* visibility * background-repeat * outline-width
* text-decoration * background-size * box-shadow
触发重排(回流)的属性
* width * top * text-align
* height * bottom * overflow-y
* padding * left * font-weight
* margin * right * overflow
* display * position * font-family
* border-width * float * line-height
* border * clear * vertival-align
* min-height * white-space
常见的触发重排的操作
Reflow(重排) 的成本比 Repaint(重绘) 的成本高很多很多。
一个结点的 Reflow 很有可能导致子结点,甚至父点以及同级结点的 Reflow。
在一些高性能的电脑上也许还没什么,但是如果 Reflow 发生在手机上,那么这个过程是非常痛苦和耗电的。
所以,下面这些动作有很大可能会是成本比较高的。
当你增加、删除、修改 DOM 结点时,会导致 Reflow , Repaint。
当你移动 DOM 的位置
当你修改 CSS 样式的时候。
当你 Resize 窗口的时候(移动端没有这个问题,因为移动端的缩放没有影响布局视口)
当你修改网页的默认字体时。
【获取某些属性时(width,height...)!!!!!】
注:display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发生位置变化。
优化方案(重绘重排)
我们已知:浏览器渲染页面时经历了如下“细致”的环节:
1. 计算需要被加载到节点上的样式结果(Recalculate style--样式重计算)
2. 为每个节点生成图形和位置(Layout--重排或回流)
3. 将每个节点填充到图层中(Paint--重绘)
4. 组合图层到页面上(Composite Layers--图层重组)
如果我们需要提升性能,需要做的就是减少浏览器在运行时所需要做的工作,即:尽量减少1234步。
【具体优化方案如下】:
1.元素位置移动变换时尽量使用CSS3的transform来代替对top left等的操作
变换(transform)和透明度(opacity)的改变仅仅影响图层的组合
top left
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#test{
position: relative;
/* transform: translateX(0); */
width: 100px;
height: 100px;
background: pink;
}
</style>
</head>
<body>
<div id="test">
</div>
</body>
<script type="text/javascript">
var testNode = document.querySelector("#test");
setTimeout(function(){
testNode.style.left="100px";
// testNode.style.transform="translateX(100px)";
},2000);
</script>
</html>
对比 translateX
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#test{
/* position: relative; */
transform: translateX(0);
width: 100px;
height: 100px;
background: pink;
}
</style>
</head>
<body>
<div id="test">
</div>
</body>
<script type="text/javascript">
var testNode = document.querySelector("#test");
setTimeout(function(){
// testNode.style.left="100px";
testNode.style.transform="translateX(100px)";
},2000);
</script>
</html>
2.【使用opacity来代替visibility】
(1).使用visibility不触发重排,但是依然重绘。
(2).直接使用opacity即触发重绘,又触发重排(GPU底层设计如此!)。
(3).opacity配合图层使用,即不触发重绘也不触发重排。(目前是触发重绘的)
原因:
透明度的改变时,GPU在绘画时只是简单的降低之前已经画好的纹理的alpha值来达到效果,并不需要整体的重绘。
不过这个前提是这个被修改opacity本身必须是一个图层。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
<style type="text/css">
#test{
/* visibility: visible; */
width: 100px;
height: 100px;
background: pink;
opacity: 1;
will-change: transform;
}
</style>
</head>
<body>
<div id="test">
</div>
</body>
<script type="text/javascript">
var testNode = document.querySelector("#test");
setTimeout(function(){
//testNode.style.visibility="hidden";
testNode.style.opacity=0;
},2000);
</script>
</html>
不触发重排,触发了重绘,但是这个重绘是比较节省的,只在移动块自己的图层上进行。
3.【不要使用table布局】
table-cell
4.将【多次改变样式属性的操作合并成一次】操作
不要一条一条地修改DOM的样式,预先定义好class,然后修改DOM的className
5.【将DOM离线后再修改】
由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。
如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。
6.【利用文档碎片】(documentFragment)------vue使用了该种方式提升性能。
dom节点变动会复制一份放入内存,也就是抽象为dom树,对象,只是修改对象,最后再映射为dom。
7.【不要把获取某些DOM节点的属性值放在一个循环里当成循环的变量】
当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列,比如:
1. offsetTop, offsetLeft, offsetWidth, offsetHeight
2. scrollTop/Left/Width/Height
3. clientTop/Left/Width/Height
4. width,height
当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要刷新内部队列,
因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,
浏览器都会强行刷新渲染队列。
8.动画实现过程中,启用GPU硬件加速:transform: tranlateZ(0)
9.为动画元素新建图层,提高动画元素的z-index
10.编写动画时,尽量使用如下的API
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>定时器动画</title>
<style>
* {
margin:0;
padding:0;
}
#test {
width: 200px;
height: 200px;
background: skyblue;
}
</style>
</head>
<body>
<div id="test"></div>
<!--定时器动画-->
<script type="text/javascript">
let test = document.getElementById('test')
let index = 0
let timeId = setInterval(()=>{
index++
test.style.transform = `translateX(${index}px)`
},16)
setTimeout(()=>{
clearInterval(timeId)
},2000)
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>requestAnimationFrame动画</title>
<style>
* {
margin:0;
padding:0;
}
#test {
width: 200px;
height: 200px;
background: green;
position: absolute;
left: 0;
right: 0;
}
</style>
</head>
<body>
<div id="test">
</div>
<!--requestAnimationFrame动画.html-->
<script type="text/javascript">
let test = document.getElementById('test')
let index = 0
let id
function move() {
index++
test.style.transform = `translateX(${index}px)`
id = requestAnimationFrame(move)
}
id = requestAnimationFrame(move)
//两秒钟后动画要停止
setTimeout(()=>{
cancelAnimationFrame(id)
},2000)
</script>
</body>
</html>
requestAnimationFrame----请求动画帧
1.window.requestAnimationFrame()
说明:该方法会告诉浏览器在下一次重绘重排之前调用你所指定的函数
1.参数:该方法使用一个回调函数作为参数,这个回调函数会在浏览器下一次重绘之前调用。
回调函数会被自动传入一个参数,DOMHighResTimeStamp,标识requestAnimationFrame()开始触发回调函数的当前时间
2.返回值:
一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。
备注:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
2.window.cancelAnimationFrame(requestID)
取消一个先前通过调用window.requestAnimationFrame()方法添加到计划中的动画帧请求。
requestID是先前调用window.requestAnimationFrame()方法时返回的值,它是一个时间标识,用法与定时器的id类似。
四 CDN
什么是CDN?工作原理是什么?
网站通常将其所有的服务器都放在同一个地方,当用户群增加时,公司就必须在多个地理位置不同的服务器上部署内容
为了缩短http请求的时间,我们应该把大量的静态资源放置的离用户近一点。
内容发布网络CDN(Content Delivery Networks)
CDN是一组分布在多个不同地理位置的web服务器,用于更加有效的向用户发布内容
基本思路:
- 尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
- 通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,
- CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息
- 将用户的请求重新导向离用户最近的服务节点上。
基础架构:
最简单的CDN网络由一个DNS服务器和几台缓存服务器组成
1.用户输入的url,会经过DNS解析“翻译”成对应的ip地址,从而找到CDN专用的服务器。
2.CDN“拿到”用户的IP地址,随后和区域负载均衡设备配合,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。
3.上述步骤中的“选择”依据
(1).选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;
(2).根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;
(3).查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。
一般公司会架设多个CDN服务器。
五 浏览器存储
Cookie, SessionStorage, LocalStorage这三者都可以被用来在浏览器端存储数据,而且都是字符串类型的键值对!
注意:session和SessionStorage不是一个概念!!!在服务端有一种存储方式叫做:session会话存储,常常被简称session
后期Node课程中会对cookie和后端所使用的session会话存储进行详细讲解
session:会话
SessionStorage:浏览器端用于存储数据的容器,常常被前端人员简称为session。
session会话存储:服务器端一种存储数据的方式,常常被后端人员简称为session。
Web Storage
SessionStorage和LocalStorage都是浏览器本地存储,统称为Web Storage,存储内容大小一般支持5-10MB
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
相关API:
1. xxxxxStorage.setItem('key', 'value');
该方法接受一个键名和值作为参数,将会把键值对添加到存储中,如果键名存在,则更新其对应的值。
2. var data = xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。
3. xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。
4. xxxxxStorage.clear()
调用该方法会清空存储中的所有键名
备注:SessionStorage存储的内容会随着浏览器窗口关闭而消失。
LocalStorage存储的内容,需要手动清除才会消失。
storage事件: 跨页签通信
1. Storage对象发生变化时触发(即创建/更新/删除数据项时,Storage.clear() 只会触发一次)
2. 在同一个页面内发生的改变不会起作用
3. 在相同域名下的其他页面发生的改变才会起作用。(修改的页面不会触发事件,与它共享的页面会触发事件)
key : 修改或删除的key值,如果调用clear(),为null
newValue : 新设置的值,如果调用clear(),为null
oldValue : 调用改变前的value值,如果调用clear(),为null
url : 触发该脚本变化的文档的url
storageArea : 当前的storage对象
使用方法:
window.addEventListener('storage',function (event) {
//此处写具体业务逻辑
})
下面的两个html需要处在同一个域名下。
1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据同步1</title>
</head>
<body>
<input type="text" id="input">
<script type="text/javascript">
/*
* storage事件:
当Storage 对象发生变化时,即创建/更新/删除数据项时被触发
修改的页面不会触发事件,与它共享的页面会触发事件
key : 修改或删除的key值,如果调用clear(),为null
newValue : 新设置的值,如果调用clear(),为null
oldValue : 调用改变前的value值,如果调用clear(),为null
url : 触发该脚本变化的文档的url
storageArea : 当前的storage对象
需求:修改一个页面的数据,另一个页面能够及时响应,如:电商网站的多个页面之间如何通信
* */
let inputNode = document.getElementById('input')
inputNode.onblur = function () {
localStorage.setItem('data',inputNode.value)
}
</script>
</body>
</html>
2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据同步2</title>
</head>
<body>
<input type="text" id="input2">
<script type="text/javascript">
let inputNode = document.getElementById('input2')
window.addEventListener('storage',function (event) {
console.log(event)
inputNode.value = event.newValue
//inputNode.value = localStorage.getItem('data')
})
</script>
</body>
</html>