# 浏览器基础
# 体系结构
![协议](/img/browser/协议. png)
![五层网络结构](/img/browser/五层网络结构. png)
![OSI七层模型](/img/browser/OSI七层模型. png)
# 页面加载过程
![页面加载过程](/img/browser/页面加载过程. png)
1. 在浏览器中输入url 2. DNS解析域名
获取域名对应到IP地址
3. TCP传输报文(三次握手)
建立连接
4. 客户端发送 HTTP 请求
如果是 HTTPS 还需要建立SSL。如果是 HTTP2 则会先判断是否支持 HTTP2,否则降级到 HTTP1. x
5. IP协议查询MAC地址
IP协议把TCP分割好的各种数据包传送给接收方;ARP协议可以将IP地址解析成对应的MAC地址
6. 数据到达数据链路层
客户端发送请求的阶段结束
7. 服务器接收数据
接收端的服务器在链路层接收到数据包,再层层向上直到应用层将分段的数据包重新组成原来的HTTP请求报文
8. 服务器响应请求并返回结果
把不同请求结合配置文件委托给服务器上处理相应请求的程序进行处理,然后返回后台程序处理产生的结果作为响应
9. 浏览器渲染页面
- TCP四次挥手
断开连接
- 浏览器发生异步请求
# 渲染页面过程
![页面渲染]([/img/browser/页面渲染. png])
1. 根据 HTML 解析 DOM 树
字节 -> 字符串 -> 解析Token -> 编译Node -> DOM
DOM 树解析的过程是一个深度优先遍历。即先构建当前节点的所有子节点,再构建下一个兄弟节点
若遇到 script 标签,则 DOM 树的构建会暂停,直至脚本执行完毕
2. 根据 CSS 解析生成 CSS 规则树
字节 -> 字符串 -> 解析Token -> 编译Node -> CSSOM
浏览器在 CSS 规则树生成之前不会进行渲染
3. 结合 DOM 树和 CSS 规则树,生成渲染树
渲染树和DOM树到区别在于:过滤掉Head和display:none这样的节点
4. 据渲染树计算每一个节点的信息
- 布局:通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸
- 回流:在布局完成后,发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染
5. 根据计算好的信息绘制页面
- 重绘:某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的重绘。
- 回流:某个元素的尺寸发生了变化,则需重新计算渲染树,重新渲染。
绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上
6. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕
# CSS为什么从右往左解析
为了避免对所有元素进行遍历,总的来说从右往左进行解析还是会比从左往右解析要少很多次的匹配. 从右往左进行匹配的时候,匹配的全部是DOM元素的父节点,而从左往右进行匹配的时候时候,匹配的全部是DOM元素的子节点,这样就避免了HTML与CSS没有下载完需要进行等待的情形。
# 内联base64图片和/img标签加载图片的优缺点?适用场景?
内联base64优点:
- 减少http请求次数
- 做为背景平铺类的图片使用内联图片的时候不会影响加载速度。
内联base64缺点:
- 浏览器不会缓存内联图片资源
- 兼容性较差,只支持ie8以上浏览器
- 超过1000kb的图片,base64编码会使图片大小增大,导致网页整体下载速度减慢
小于等于1000kb大小且没有兼容性限制,不需要缓存图片资源的情况下选择内联base64图片。其余选择/img标签
# 浏览器加载CSS文件的过程?
文件加载过程整体按照html文档的顺序加载。
特点:
- css文件是并行下载的。
- 如果CSS文件在head中,CSS文件不会阻塞DOM树的解析,但是会阻塞DOM树的渲染。
- 如果CSS文件在body中,CSS文件不仅会阻塞DOM树的解析,还会阻塞DOM树的渲染,css文件也会阻塞后面js文件加载。
- css的下载不会阻塞后面js的下载,但是js下载完成后,被阻塞执行
- 如果 js 去请求 css 的信息,这个时候 css 还没加载完毕,就会导致取到的信息错误。为了防止这一点,css 会阻塞 js 的解析。谷歌做了优化,只有脚本去访问的样式会受到尚未加载解析的 css 影响时,才会禁止此脚本
我们可以通过 media query 来进行优化。比如:这样如果小于 750px ,则不会加载该 css
>
<link href=”something.css” rel=”stylesheet” media=”(min-width: 750px)”>
2
# 浏览器加载JS文件的过程?
现代浏览器会并行加载js文件,但是按照书写顺序执行代码。加载或者执行js时会阻塞对标签的解析,也就是阻塞了dom树的形成,只有等到js执行完毕,浏览器才会继续解析标签。没有dom树,浏览器就无法渲染,所以当加载很大的js文件时,可以看到页面很长时间是一片空白。
通常把script内容放在body最后,这样脚本文件不会阻止其他资源的下载。
# 浏览器加载图片/音频等文件的过程?
图片视频不会阻塞DOM树的渲染,执行后面的渲染的同时向服务器请求下载 。下载完毕后,由于图片视频占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码。
# async & defer
# async
下载完就执行: <script src="js/bar.js" async></script>
该属性标注的脚本是异步脚本,即异步下载脚本时,不会阻塞文档解析,但是一旦下载完成后,立即执行,阻塞文档解析。
- defer属性表示延迟脚本的执行,等到整个文档解析完再执行
- defer属性能延迟执行,但是不会延迟下载,浏览器遇到script就立即下载脚本
- 文档解析完成时,脚本被执行,此时也会触发domcontentloaded事件,优先执行脚本
- 多个标签添加defer属性,执行顺序仍然是按书写顺序
# defer
渲染完再执行: <script src="js/bar.js" defer></script>
该属性标注的脚本是延迟脚本,使得浏览器延迟脚本的执行,也就是说,脚本会被异步下载但是不会被执行,直到文档的载入和解析完成,并可以操作,脚本才会被执行
- async属性的作用是让浏览器异步加载脚本文件。在加载脚本文件的时候,浏览器能继续标签的解析。
- 异步脚本一定会在load事件之前执行,但可能会在domcontentloaded事件之前或者之后执行。
- 异步脚本之间的执行顺序不确定,可能不会按照书写顺序执行
# 相同点:
- 加载文件时不阻塞页面渲染。
- 对于inline的script无效。
- 使用这两个属性的脚本中不能调用document. write方法。
- 有脚本的onload的事件回调。
# 不同点:
- 用了async:不会阻碍文档的解析,但是一旦加载了就会立即执行,不能保证执行的顺序,因为async的脚本执行顺序是没有保证的,因此要确认脚本间没有依赖关系。 用了defer :读到了,也不会立即执行,要等文档都读取完毕,再去执行。
- 同时使用async和defer属性,后者不起作用,浏览器行为由async属性决定
# 如何突破浏览器同时下载数量限制?
# 重排与重绘
重排必导致重绘。
# 重排
重排又称回流或布局(Layout/Reflow),一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树。
产生因素:
页面渲染初始化
DOM结构改变,比如新作或删除了某个节点
render树变化,比如减少了padding ,border,fontSIze等
激活CSS伪类(例如::hover)
窗口resize
获取某些属性,引发回流。
很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流, 但是除了render树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效。
- offset(Top/Left/Width/Height)
- scroll(Top/Left/Width/Height)
- cilent(Top/Left/Width/Height)
- width, height
- 调用了
getComputedStyle()
或者IE的currentStyle
scrollIntoView()
、scrollIntoViewIfNeeded()
getBoundingClientRect()
scrollTo()
# 重绘
重绘(Repaint)表示元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了。
# 优化
重排的成本开销要高于重绘,而且一个节点的重排往往回导致子节点以及同级节点的重排。重排必引起重绘,所以重排优化方案中一般都包括重绘,我们尽可能尽量避免重排。
减少逐项更改样式
最好一次性更改style,或者将样式定义为class并一次性更新,尽可能在DOM树的最末端改变class。CSS 选择符从右往左匹配查找,避免 DOM 深度过深
避免循环操作dom
创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window. document
避免多次读取offset等属性。
无法避免则将它们缓存到变量
将复杂的元素绝对定位或固定定位,
假如使得它脱离文档流,否则回流代价会很高
使用 translate 替代 top
使用 visibility 替换 display: none
使用效果一致的前提,visibility只会引起重绘,display会引发回流(改变了布局)
尽量不使用 table 布局
可能很小的一个小改动会造成整个 table 的重新布局
动画实现的速度的选择,动画速度越快,回流次数越多
也可以选择使用 requestAnimationFrame
将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。
比如对于 video 标签,浏览器会自动将该节点变为图层
将动画效果应用到position属性为absolute或fixed的元素上
# 强缓存与弱缓存
![缓存](/img/browser/缓存. png)
浏览器缓存分为强缓存与弱缓存。
浏览器在请求某一资源时先获取该资源缓存的header信息,判断是否命中强缓存(cache-control和expires信息),若命中直接从缓存中获取资源信息(包括缓存header信息),本次请求不与服务器进行通信。
如果没有命中强缓存,浏览器会发送请求到服务器,请求会携带第一次请求返回的有关缓存的header字段信息(Last-Modified/If-Modified-Since和Etag/If-None-Match),由服务器根据请求中的相关header信息来比对结果是否协商缓存命中;若命中,则服务器返回新的响应header信息更新缓存中的对应header信息,但是并不返回资源内容,它会告知浏览器可以直接从缓存获取;否则返回最新的资源内容。
# 强缓存/本地缓存
浏览器如果判断本地缓存未过期,就直接使用,无需发起http请求 CSS&JS&图片。标志状态码:200
# Pragma/Expires(http1. 0)
Pragma有两个字段Pragma和Expires。
- Pragma的值为no-cache时,表示禁用缓存
- Expires的值是一个GMT时间,表示该缓存的有效时间。
优先级:
Pragma > Cache-Control > Expires
如果当成一件商品来说,max-age代表的是有效期,需要和生产日期一起计算;Expires则直接代表过期时间
# Cache-Control/Max-Age(http1. 1)
根据 max-age
的相对时间来判断是否命中强缓存。 max-age
设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)
其他属性
public: 服务器端和浏览器端都能缓存
private: 只能浏览器端缓存
no-cache: 强制浏览器在使用cache拷贝之前先提交一个http请求到源服务器进行确认。http请求没有减少,会减少一个响应体(文件内容), 这种选项类似弱缓存。
only-if-cached: 表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。
no-store: 不缓存
must-revalidate: 缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
# Max-Age相比Expires
- Expires使用的是服务器端的时间,当客户端时间和服务端不同步那这样,可能就会出问题了,造成了浏览器本地的缓存无用或者一直无法过期所以一般http1. 1后不推荐使用Expires
- Max-Age使用的是客户端本地时间的计算
- 同时启用了Cache-Control与Expires,Cache-Control优先级高。
# 如何清除
强缓存,在未过期时,更新页面中引用的资源路径,让浏览器主动放弃缓存,加载新资源。比如:文件命名带上hash值 a?v=1
# 协商缓存/弱缓存
浏览器会向服务端发起http请求,文件未改变时,服务器告诉让浏览器使用本地缓存,否则重新加载。标志状态码:304。 If-None-Match/E-tag,If-Modified-Since/Last-Modified都是成对出现。
# If-Modified-Since/Last-Modified(http1. 0)
过程:
1. 浏览器第一次发请求,服务器在返回的 respone 的 header 加上 Last-Modified,表示资源的最后修改时间 2. 再次请求资源,在 requset 的 header 加上 If-Modified-Since ,值就是上一次请求返回的 Last-Modified 值 3. 服务器根据请求传过来的值判断资源在该日期后资源是否有更新,没有则返回 304,有变化就正常返回资源内容,更新 Last-Modified 的值 4. 浏览器如果拿到304 从缓存加载资源,否则直接从服务器加载资源
服务器上次传来的Last-Modified与本次传过去的If-Modified-Since时一样的值。
如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1. 1 出现了 ETag来解决该问题
# If-None-Match/E-tag(http1. 1)
两个的值是由服务器生成对每个资源对唯一标识字符串。
![etag](/img/browser/etag. png)
过程与 If-Modified-Since/Last-Modified
类似,与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化。
优先级:
E-tag > Last-Modified
# 为什么Last-Modified后还要新增Etag
Last-Modified:表明服务端的文件最后何时改变。它有些缺陷存在:
- 只能精确到1s,当某些文件修改非常频繁,比如在秒以下的时间内进行修改时无法准确判断
- 有些服务端的文件会周期性的改变,内容并不改变仅仅改变的修改时间,这个时候会导致缓存失效重新下载
- 某些服务器不能精确的得到文件的最后修改时间
E-tag:是一种指纹机制,没有精确时间的限制,只要文件变E-tag就变。
如果同时带有E-tag和Last-Modified,服务端会优先检查E-tag
# 为什么协商缓存属性成对出现
协商缓存是浏览器判断资源是否可用,所以需要两个标识,第一个是第一次请求的响应头带上某个字段(Last-Modified或者Etag),第二个则是后续请求带上的对应请求字段(If-Modified-Since或者If-None-Match),两个标识一起出现才有意义,单独则没有什么效果。
# 如何清除
Ctrl + F5强制刷新可以使得缓存无效
# 区别
缓存类型 | 状态码 | 发送请求到服务器 |
---|---|---|
强缓存 | 200(from cache) | 否,直接从本地缓存取 |
协商缓存 | 304(not modified) | 是,通过服务器来告知缓存是否可用 |
用户操作 | 强缓存 | 协商缓存 |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进后退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
ctrl + F5 | 无效 | 无效 |
一般来说HTML使用协商缓存,CSS&JS&图片:使用强缓存,文件命名带上hash值。协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义。
大部分web服务器都默认开启协商缓存,而且是同时启用【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】
# 优缺点
优点:
- 减少了不必要的数据传输,节省带宽
- 减少服务器的负担,提升网站性能
- 加快了客户端加载网页的速度
- 用户体验友好
缺点:资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕。
HTML页面中也有一个meta标签可以控制缓存方案-Pragma: <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
支持情况不佳,譬如缓存代理服务器肯定不支持
# 其余缓存
强缓存和协商缓存属于浏览器缓存,缓存还有以下几种:
# 代理服务器缓存
代理服务器缓存就是一个共享缓存,不只为一个用户服务,经常为大量用户使用,因此在减少相应时间和带宽使用方面很有效:因为同一个缓存可能会被重用多次。
# 网关缓存
称为代理缓存或反向代理缓存,网关也是一个中间服务器,网关缓存一般是网站管理员自己部署,从让网站拥有更好的性能
CDNS(网络内容分发商)分布网关缓存到整个(或部分)互联网上,并出售缓存服务给需要的网站,比如国内的七牛云、又拍云都有这种服务
# 数据库缓存
指当我们的应用极其复杂,表自然也很繁杂,我们必须进行频繁的进行数据库查询,这样可能导致数据库不堪重负,一个好的办法就是将查询后的数据放到内存中,下一次查询直接从内存中取就好了
# 缓存技术
# Cookie
HTTP协议本身是无状态的,服务器无法判断用户身份。Cookie实际上是一小段key-value格式的文本信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。Cookie主要用于用户信息的存储。
内存大小受限,根据浏览器不同约为4KB
具有生命周期,根据设不设定expires的状态分为:
- 会话cookie
- 持久cookie
满足同源策略
访问玩zhidao. baidu. com 再访问wenku. baidu. com还需要重新登陆百度账号吗? 解决办法: 设置document. domain = ‘baidu. com’; 让页面属于这个基础域名下(那么此页面和任何二级域名为baidu. com的)
过程
1. 客户端发送一个请求到服务器 2. 服务器发送一个HttpResponse响应到客户端,其中包含Set-Cookie的头部 3. 客户端保存cookie,之后向服务器发送请求时,HttpRequest请求中会包含一个Cookie的头部 4. 服务器返回响应数据
缺点
- cookie会被附加在每个HTTP请求增加了流量。
- cookie是明文传递的存在安全性成问题,使用全站HTTPS来解决。
- Cookie的大小限制在4KB左右。存储大小受限。
# 属性
Cookie以 NAME=VALUE 键值对存在, 可以设置要保存的 Key/Value,需要注意这里的 NAME不可重复。
Expires 过期时间
用于设置的某个时间点后该 Cookie 就会失效。
expire
值是一个时间,过了这个时间,该cookie就失效了。
maxAge
用maxAge指定当前cookie是在多长时间之后而失效。
- maxAge属性为正数:该Cookie会在maxAge秒之后自动失效;
- maxAge属性为负数:该Cookie只是一个临时Cookie,不会被持久化,仅在本浏览器窗口或者本窗口打开的子窗口中有效,关闭浏览器后该Cookie立即失效;
- maxAge为0: 立即删除Cookie
Domain域
表示当前cookie所属于哪个域或子域下面。
set-Cookie响应首部添加一个Domain属性来控制哪些站点可以看到那个cookie。对于服务器返回的Set-Cookie中,如果没有指定Domain的值,那么其Domain的值是默认为当前所提交的http的请求所对应的主域名的。比如访问 http://www.baidu.com,返回一个cookie,没有指名domain值,那么其为值为默认的www. baidu. com。
Path
表示cookie的所属路径。这个属性设置的url且带有这个前缀的url路径都是有效的。
Secure
表示该cookie只能用https传输。一般用于包含认证信息的cookie,要求传输此cookie的时候,必须用https传输。
httponly
表示此cookie必须用于http或https传输。这意味着,浏览器脚本,比如javascript中,是不允许访问操作此cookie的。
# 安全隐患
网络窃听
网络上的流量可以被网络上任何计算机拦截,特别是未加密的开放式WIFI。这种流量包含在普通的未加密的HTTP清求上发送Cookie。在未加密的情况下,攻击者可以读取网络上的其他用户的信息,包含HTTP Cookie的全部内容,以便进行中间的攻击。比如:拦截cookie来冒充用户身份执行恶意任务(银行转账等)。
解决方法:服务器可以设置secure属性的cookie,这样就只能通过https的方式来发送cookies了。
DNS缓存中毒
攻击者使用DNS中毒来创建一个虚拟的NDS服务h123456. www. demo. com指向攻击者服务器的ip地址。然后攻击者可以从服务器 h123456. www. demo. com/img_01. png 发布图片。用户访问这个图片,由于 www. demo. com和h123456. www. demo. com是同一个子域,所以浏览器会把用户的与www. demo. com相关的cookie都会发送到h123456. www. demo. com这个服务器上,这样攻击者就会拿到用户的cookie搞事情
一般情况下是不会发生这种情况,通常是网络供应商错误。
跨站点脚本XSS与跨站请求伪造CSRF
详见web安全
# 手写cookie
封装
//获得cookie 的值
function cookie(name) {
var cookieArray = document.cookie.split("; "); //得到分割的cookie名值对
for (var i = 0; i < cookieArray.length; i++) {
var arr = cookieArray[i].split("="); //将名和值分开
if (arr[0] == name) return unescape(arr[1]); //如果是指定的cookie,则返回它的值
}
return "";
}
function getCookie(objName) { //获取指定名称的cookie的值
var arrStr = document.cookie.split("; ");
for (var i = 0; i < arrStr.length; i++) {
var temp = arrStr[i].split("=");
if (temp[0] == objName) return unescape(temp[1]);
}
}
function addCookie(objName, objValue, objHours) { //添加cookie
var str = objName + "=" + escape(objValue);
if (objHours > 0) { //为时不设定过期时间,浏览器关闭时cookie自动消失
var date = new Date();
var ms = objHours * 3600 * 1000;
date.setTime(date.getTime() + ms);
str += "; expires=" + date.toGMTString();
}
document.cookie = str;
}
function SetCookie(name, value) //两个参数,一个是cookie的名子,一个是值
{
var Days = 30; //此 cookie 将被保存 30 天
var exp = new Date(); //new Date("December 31, 9998");
exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
}
function getCookie(name) //取cookies函数
{
var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
if (arr != null) return unescape(arr[2]);
return null;
}
function delCookie(name) //删除cookie
{
var exp = new Date();
exp.setTime(exp.getTime() - 1);
var cval = getCookie(name);
if (cval != null) {
document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
使用
// 1.新增cookie:
$.cookie('cookieName', 'cookieValue');
注: 如果没有设置cookie的有效期, 则cookie默认在浏览器关闭前都有效, 故被称为 "会话cookie"。
// 创建一个cookie并设置有效时间为7天:
$.cookie('cookieName', 'cookieValue', {
expires: 7
});
// 创建一个cookie并设置cookie的有效路径:
$.cookie('cookieName', 'cookieValue', {
expires: 7,
path: '/'
});
// 2.读取cookie:
$.cookie('cookieName'); // 若cookie存在则返回'cookieValue';若cookie不存在则返回null
// 3.删除cookie:把ncookie的值设为null即可
$.cookie('the_cookie', null);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 优化
减少Cookie大小
每次请求都会带上对应的 Cookie,减少 Cookie 大小可以降低其对响应速度的影响
- 去除不必要的 Cookie
- 尽量压缩 Cookie 大小
- 注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain
- 设置合适的过期时间
静态资源使用无Cookie域名
静态资源一般无需使用 Cookie,可以把它们放在使用二级域名或者专门域名的无 Cookie 服务器上,降低 Cookie 传送的造成的流量浪费,提高响应速度
# LocalStorage
LocalStorage的数据将一直保存在浏览器内,直到用户清除浏览器缓存数据为止, 容量为5MB。
# LocalStorage和cookie的差别
- localStorage容量为5MB,cookie的4K并且大多数一个站点最多保存20个cookie
- localStorage会可以将第一次请求的数据直接存储到本地,这个相当于一个5M大小的前端页面的数据库,相比于cookie可以节约带宽
- localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭;sessionStorage:仅在当前浏览器窗口关闭之前有效。
- 在IE8以上的IE版本才支持localStorage这个属性,需要用try/catch来捕获/处理异常
- 浏览器将localStorage的值类型限定为string类型,所以需要进行常见的JSON对象类型转换
- localStorage在浏览器的隐私模式下面是不可读取的
- localStorage如果存储内容多的话会消耗内存空间,会导致页面变卡
- localStorage不能被爬虫抓取到
# 使用
// 新增修改
localStorage.setItem("key", "value");
// 获取
var name = localStorage.getItem("key");
// 删除
localStorage.removeItem("key");
// 清空所有信息
localStorage.clear()
2
3
4
5
6
7
8
考虑浏览器问题时需要增加判断
if (!window.localStorage) {
alert("浏览器不支持localstorage");
return false;
} else {
//主逻辑业务
}
2
3
4
5
6
# 多页面实时通讯
A页面改变,要求B页面同时改变,可以通过 window.onstorage
window.addEventListener('storage', () => {});
来实现多页面通讯。
如果浏览器同时打开一个域名下面的多个页面,当其中的一个页面改变 sessionStorage 或 localStorage 的数据时,其他所有页面的 storage 事件会被触发,而原始页面并不触发 storage 事件。
window.onstorage = function(e) {
console.log('The ' + e.key + ' key has been changed from ' + e.oldValue + ' to ' + e.newValue + '.');
};
2
3
# 设置过期时间
localstorage原生是不支持设置过期时间的, 需要自己封装。
function set(key, value) {
var curtime = new Date().getTime(); //获取当前时间
localStorage.setItem(key, JSON.stringify({
val: value,
time: curtime
})); //转换成json字符串序列
}
function get(key, exp) //exp是设置的过期时间
{
var val = localStorage.getItem(key); //获取存储的元素
var dataobj = JSON.parse(val); //解析出json对象
//如果当前时间-减去存储的元素在创建时候设置的时间 > 过期时间
if (new Date().getTime() - dataobj.time > exp) {
console.log("expires"); //提示过期
} else {
console.log("val=" + dataobj.val);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# SessionStorage
SessionStorage的其他属性同LocalStorage, 不同是的当页面关闭时会随之清除。sessionStorage不同的浏览器窗口中不能共享;localstorage和cookie在所有同源窗口中都是共享的。
# 使用
// 新增修改
sessionStorage.setItem("key", "value");
// 获取
var name = sessionStorage.getItem("key");
// 删除
sessionStorage.removeItem("key");
2
3
4
5
6
# 存储
题目:cookie 和 localStorage 有何区别?
# cookie
cookie 本身不是用来做服务器端存储的(计算机领域有很多这种“狗拿耗子”的例子,例如 CSS 中的 float),它是设计用来在服务器和客户端进行信息传递的,因此我们的每个 HTTP 请求都带着 cookie。但是 cookie 也具备浏览器端存储的能力(例如记住用户名和密码),因此就被开发者用上了。
使用起来也非常简单, document.cookie = ....
即可。
但是 cookie 有它致命的缺点:
- 存储量太小,只有 4KB
- 所有 HTTP 请求都带着,会影响获取资源的效率
- API 简单,需要封装才能用
# localStorage 和 sessionStorage
后来,HTML5 标准就带来了 sessionStorage
和 localStorage
,先拿 localStorage
来说,它是专门为了浏览器端缓存而设计的。其优点有:
- 存储量增大到 5MB
- 不会带到 HTTP 请求中
- API 适用于数据存储
localStorage.setItem(key, value)
localStorage.getItem(key)
sessionStorage
的区别就在于它是根据 session 过去时间而实现,而 localStorage
会永久有效,应用场景不同。例如,一些需要及时失效的重要信息放在 sessionStorage
中,一些不重要但是不经常设置的信息,放在 localStorage
中。
另外告诉大家一个小技巧,针对 localStorage.setItem
,使用时尽量加入到 try-catch
中,某些浏览器是禁用这个 API 的,要注意。
# Service Worker
主要是为了提高web app的用户体验,可以实现离线应用消息推送等等一系列的功能, 可以看做是一个独立于浏览器的Javascript代理脚本, 在离线状态下也能提供基本的功能。 出于安全性的考虑,Service Worker 只能在https协议下使用
# 性能检测
# 白屏时间
白屏时间是用户首次看到内容的时间,也叫做首次渲染时间,chrome 高版本有 firstPaintTime 接口来获取这个耗时.
# 首屏时间
首屏时间的统计比较复杂,因为涉及图片等多种元素及异步渲染等方式。观察加载视图可发现,影响首屏的主要因素的图片的加载。通过统计首屏内图片的加载时间便可以获取首屏渲染完成的时间。
统计流程:
1. 首屏位置调用 API 开始统计 2. 绑定首屏内所有图片的 load 事件 3. 页面加载完后判断图片是否在首屏内,找出加载最慢的一张 4. 计算首屏时间
# 用户可操作时间
用户可操作默认可以统计domready时间,因为通常会在这时候绑定事件操作。对于使用了模块化异步加载的 JS 可以在代码中去主动标记重要 JS 的加载时间,这也是产品指标的统计方式。
# 总下载时间
总下载时间默认可以统计onload时间,这样可以统计同步加载的资源全部加载完的耗时。如果页面中存在很多异步渲染,可以将异步渲染全部完成的时间作为总下载时间
# 工具
# chrome工具Performance 面板
CPU 图标和 Summary 图都是按照“类型”给我们提供性能信息,而 Main 火焰图则将粒度细化到了每一个函数的调用。到底是从哪个过程开始出问题、是哪个函数拖了后腿、又是哪个事件触发了这个函数,这些具体的、细致的问题都将在 Main 火焰图中得到解答。
# LightHouse工具或Audits 面板
根据评分细项给出改进意见,逐条改进 使用文档
# window. performance
time属性可以查看关键点的时间戳,计算性能时间指标.
- DNS查询耗时
domainLookupEnd - domainLookupStart
- TCP链接耗时
connectEnd - connectStart
- request请求耗时
responseEnd - responseStart
- 解析dom树耗时
domComplete - domInteractive
- 白屏时间
responseStart - navigationStart
- domready时间(用户可操作时间节点)
domContentLoadedEventEnd - navigationStart
- onload时间(总下载时间)
loadEventEnd - navigationStart
# 跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域,这是出于浏览器的同源策略限制。
域名不同
例如:www. qietuniu. com 和www. qietunius. com 即为不同的域名
二级域名相同,子域名不同
例如:www. qietuniu. ct. com 和www. qietunius. ct. com 为子域不同
协议不同
例如:http与https
端口不同
例如:www. qietuniu. com:8888和www. qietuniu. com:8080
# 场景
- 资源跳转
<a>
链接、重定向、表单提交
- 资源嵌入:
<link>
、 <script>
、 <img>
、 <frame>
等dom标签,还有样式中 background:url()
、 @font-face()
等文件外链
脚本请求
前端调用后台服务接口发起的ajax请求、dom和js对象的跨域操作等
跨域的三大要素是浏览器的限制、跨域、XHR(XHRHttpRequest)请求,缺一不可,也就是说破坏其中一个因素就可以成功访问到数据
# 跨域
题目:如何实现跨域?
浏览器中有 同源策略 ,即一个域下的页面中,无法通过 Ajax 获取到其他域的接口。例如有一个接口 http://m.juejin.com/course/ajaxcourserecom?cid=459
,你自己的一个页面 http://www.yourname.com/page1.html
中的 Ajax 无法获取这个接口。这正是命中了“同源策略”。如果浏览器哪些地方忽略了同源策略,那就是浏览器的安全漏洞,需要紧急修复。
url 哪些地方不同算作跨域?
- 协议
- 域名
- 端口
但是 HTML 中几个标签能逃避过同源策略—— <script src="xxx">
、 </img src="xxxx"/>
、 <link href="xxxx">
,这三个标签的 src/href
可以加载其他域的资源,不受同源策略限制。
因此,这使得这三个标签可以做一些特殊的事情。
</img>
可以做打点统计,因为统计方并不一定是同域的,在讲解 JS 基础知识异步的时候有过代码示例。除了能跨域之外,</img>
几乎没有浏览器兼容问题,它是一个非常古老的标签。<script>
和<link>
可以使用 CDN,CDN 基本都是其他域的链接。- 另外
<script>
还可以实现 JSONP,能获取其他域接口的信息,接下来马上讲解。
但是请注意,所有的跨域请求方式,最终都需要信息提供方来做出相应的支持和改动,也就是要经过信息提供方的同意才行,否则接收方是无法得到它们的信息的,浏览器是不允许的。
# 解决跨域 - JSONP
首先,有一个概念你要明白,例如访问 http://coding.m.juejin.com/classindex.html
的时候,服务器端就一定有一个 classindex.html
文件吗?—— 不一定,服务器可以拿到这个请求,动态生成一个文件,然后返回。 同理, <script src="http://coding.m.juejin.com/api.js">
也不一定加载一个服务器端的静态文件,服务器也可以动态生成文件并返回。OK,接下来正式开始。
例如我们的网站和掘金网,肯定不是一个域。我们需要掘金网提供一个接口,供我们来获取。首先,我们在自己的页面这样定义
< script >
window.callback = function(data) {
// 这是我们跨域得到信息
console.log(data)
} <
/script>
2
3
4
5
6
然后掘金网给我提供了一个 http://coding.m.juejin.com/api.js
,内容如下(之前说过,服务器可动态生成内容)
callback({
x: 100,
y: 200
})
2
3
4
最后我们在页面中加入 <script src="http://coding.m.juejin.com/api.js"></script>
,那么这个js加载之后,就会执行内容,我们就得到内容了。
# 解决跨域 - 服务器端设置 http header
这是需要在服务器端设置的,作为前端工程师我们不用详细掌握,但是要知道有这么个解决方案。而且,现在推崇的跨域解决方案是这一种,比 JSONP 简单许多。
response.setHeader("Access-Control-Allow-Origin", "http://m.juejin.com/"); // 第二个参数填写允许跨域的域名称,不建议直接写 "*"
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
2
3
4
5
6
7