浏览器的渲染过程
1. 根据html文档构建dom树,过程中如果遇到js/css/image等静态资源,就向服务器端请求下载
2. 将css文件解析成css规则树(style Rules)
3. 将二者关联生成渲染树(rendering tree)
4. 布局(layout)计算每个元素的精确位置和大小
5. 根据计算好的信息绘制(paint)整个页面
- js的加载和执行会阻塞dom树的解析,原因是js代码可能会修改dom元素,所以一般将js文件放在body底部
- css的加载不会阻塞dom树的解析,但会影响render树的生成。所以将css文件放在head,使css的加载和dom树的
解析能够同步进行,加快render树的生成速度
重绘和回流
1. reflow在渲染过程中称为回流。主要用来确定元素的几何属性,需要大量的计算。每改变一个元素的几何属性,都会发生一次回流
会引起回流的操作:
- 页面的首次渲染
- 浏览器窗口大小发生变化
- 元素的位置或者大小发生改变
- 增加或删除dom元素
- 激活伪类(如:hover)
- 操作class属性
- 设置style属性的值
- 查询某些属性,或者调用某些方法(如offsetWidth和offsetHeight、scrollTo()、getBoundingClientRect()等)
2. repaint称为重绘,发生在reflow之后,确定了元素的几何属性后,便开始将元素绘制在屏幕上,repaint的过程
就是将元素的颜色、背景等属性绘制出来。每改变一次元素的颜色等属性,就会发生一次重绘
会引起重绘的操作:
- 当元素的样式改动并不会改变他在文档中的位置和大小,会产生重绘,如color,background-color,visibility
优化:
- 避免频繁改变样式,最好一次性改写style属性,或者定义为class,一次性改class属性
- 避免频繁操作dom。使用documentFragment,在它上面进行dom操作,最后添加到文档中。或者先给元素设置display:none, 操作结束再显示出来
- 避免频繁读取会引起回流、重绘的方法、属性
- 尽可能在dom树的末端改变class,尽可能影响少的节点。例如,应避免改变包装节点的class去影响子节点的显示
- 避免设置多层内联样式,样式应该合并在一个外部class,这样class属性被改变时,仅会产生一个回流
- 将动画效果应用到position属性为absolute和fixed的元素上,因为不会影响其他元素的布局
- 避免使用css表达式如cal()
浏览器做的性能优化:维护一个队列,把所有引起回流和重绘的操作放到这个队列里,当队列中的操作达到一定数量或者间隔一定的时间再执行
性能优化
往往需要结合计算机网路、前端框架、构建工具、浏览器相关技术以及开发者自己的代码
1. 优化加载时间和速度
- 使用缓存
- 使用CDN分布式缓存
- 压缩资源,减少文件体积大小
- 优化图片,使用WebP格式的图片,占存储空间更小,压缩体积只有jpeg的三分之二(有损压缩)
- 使用http2,提供是更高效的资源传输方式,减少了延迟和连接数量
2. 优化渲染性能
- 减少重绘和回流,避免频繁的操作dom和样式更改,使用requestAnimationFrame进行动画和页面更新
- 虚拟dom,使用虚拟dom来减少真实dom的操作
3. 优化js的执行
- 异步和延迟执行:使用setTimeout、async/await来控制脚本的执行时机
- 代码分割,使用动态导入来分割代码,实现按需加载
- 避免长时间运行的任务:将耗时的js任务分解成小块
- 比较耗时的工作可以放到Worker中去
4. 优化css
- 使用css sprites,将多个小图标合并成一个大图,减少http请求
- 减少css选择器的复杂度,并没深度嵌套和复杂的组合选择器
- 利用css变量:减少重复的样式代码
5. 优化资源加载策略
a. preload和prefetch
- preload告诉浏览器预先加载当前页面需要的资源,提高这些资源的请求优先级
- prefetch在浏览器空闲的时候去加载一些在未来可能会用到的资源,通常用于加载非首页的其他页面的资源
- 如果从加载优先级上看,preload会提升请求资源的优先级,prefetch会把资源的优先级放到最低(当浏览器空闲时才会去加载)
b. 将css文件放在head,js文件放在body底部
- 由于浏览器的渲染机制,浏览器为了避免样式变化时重新渲染页面,会阻止内容逐步呈现,浏览器会等待所有css文件加载完成之后一次性渲染页面,这样如果css文件放在底部,就可能出现白屏,用户体验差很差。放在head,由于css的加载不会阻塞dom树的解析,两者可以同时进行,提高效率。
- 将js文件放在body底部,因为js的加载和执行会阻塞dom树的解析
c. 延迟资源加载(懒加载)
cookie和session
- cookie是浏览器端用来保存用户信息的一种机制,用来记录用户的一些信息,也是实现session的一种方式(应用场景:自动登录)
- session是在服务器端保存的一种数据结构,用来跟踪用户状态,数据可以保存在文件、集群、数据库中(默认在客户端用cookie保存sessionid)
web安全
- xss(跨站脚本攻击),想尽一切办法把代码嵌入到页面中,以达到窃取数据或者破坏的目的
- 反射型xss,发生请求时,xss代码出现在url中,作为参数提交到服务器,服务器接收并响应请求,不经过服务器
- 存储型xss,经过服务器,经过数据库。(常见的有提交评论时注入代码)
解决方法:对用户的输入进行处理,只允许输入合法的值,如果不能对数据进行严格过滤,要对数据进行转义html entity编码
- csrf,跨站请求伪造,网站可以给任意其他网站发送请求,虽然得不到响应,但请求已经发送到服务器上,如果不做防范的话,可能会执行请求,产生不可逆转的操作
防范:
1. 在请求时附加refer验证、token验证
2. 阻止第三方网站请求
3.
- csp,网页安全策略,由浏览器实现。本质是建立白名单制度,告诉浏览器哪些外部资源可以加载执行
通常有2种方式设置csp:
1. 设置http头部信息的csp字段
2. 通过meta标签来设置,例如<meta http-equiv="Content-Security-Policy">
浏览器输入网址后发生了什么
1. 浏览器接收url
2. 将url与缓存对比,如果请求的页面在缓存中且未过期,则直接使用缓存文件
3. 如果网络地址不是一个ip地址,需要通过DNS解析返回一个ip地址
- DNS数据库是域名和ip地址相互映射的一个分布式数据库
- 运行在UDP协议上,无需连接,时效性好
- DNS查询:先检查本地host文件 > DNS解析器缓存 > DNS服务器
4. 浏览器与服务器通过3次握手简历tcp链接
5. 浏览器向服务器发送http请求,服务器接收到请求,进行处理并返回响应
数据通过应用层、传输层、网络层、数据链路层、物理层到达下一个目的地
- 应用层,为应用进程提供服务
- 传输层,实现端到端的通信,加tcp首部封装层数据包
- 网络层,转发分组并路由选择,加ip首部封装成ip分组
- 数据链路层:相邻节点间的数据传输,加首部(mac地址)和尾部封装成帧
- 物理层,数据以比特流的形式在物理媒介中传输
6. 浏览器接收http响应,并根据http header状态作出不同处理
7. 如果是可以缓存的,这个响应会被缓存起来
8. 解析并渲染
9. 关闭tcp连接
缓存
缓存分为强缓存和协商缓存,强缓存不能缓存地址栏中访问的文件,协商缓存可以缓存地址栏中访问的文件
- 强缓存(由服务器设置过期时间,在该时间到期之前,浏览器从本地缓存中获取资源)
1. expire, 在响应头加上expires,在服务器端进行配置:expires: 1h
(存在问题:expires基于客户时间计算的,浏览器时间和服务器时间可能不一致,基本使用cache-ctrol判断)
2. Cache-Control,在响应头中加入Cache-Control字段,用max-age保存一个相对时间,表示浏览器接收到文件后在多少秒内有效
Cache-Control: max-age=<seconds> //seconds是缓存是时间,单位秒
使用缓存需要满足的条件:
1. 缓存时间未过期
2. url未发生变化
3. 请求头中没有Cache-Control: no-cache 或Pragma: no-cache(强制刷新会在请求头中添加Cache-Control: no-cache)
- 协商缓存:如果缓存过期,浏览器会向服务端发送请求询问资源是否真正过期,这就是协商缓存
1. last-Modified,第一次请求会在响应头中返回Last-Modified,浏览器请求时发送If-Modified-Since字段,服务器用这个字段来判断资源是否过期,如果过期,则返回新的资源给浏览器。如果没过期则返回304状态码,让浏览器继续使用缓存
存在问题:如果修改的时间变化而实际内容没有变化,服务器会再次将资源发给浏览器,所以一般使用Etag进行判断更好
2. Etag,浏览器利用响应头的Etag(资源的实体标识符(哈希字符串))来设置缓存,并在下次请求的时候携带If-none-match,服务器根据浏览器发送过来的if-None-Match判断Etag是否变化
Tip:协商缓存的过程需要发送http请求,如果返回304则继续使用缓存,对于移动端来说应当尽量避免304,对于很少改动的静态文件,可以在文件名上加上版本号,并设置cache-control的max-age为一年半载,这样只有当文件版本更新的时候,才会向服务端发送请求
虚拟列表
https://github.com/dwqs/blog/issues/70
websocket
websocket,可以在浏览器和服务器之间建立交互式通信会话,例如浏览器可以向服务器发送消息,并通过监听Message事件接受响应,使用这种方式无需通过轮询服务器的方式获得响应。
三次握手和四次挥手
三次握手:
1. 客户端发送syn码给服务器,要求建立连接
2. 服务器发送syn+ack给客户端
3. 客户端再次发送ack给服务端,服务端验证ack没问题,建立连接
4次挥手:
1. 客户端发送FIN(结束报文)给服务器,通知服务器数据传输完毕
2. 服务器接受到了,发送ack给客户端,此时数据还没传输完成。
3. 服务器接收完成,发送FIN给客户端,通知传输数据完成
4. 客户端发送ACK,进入Time_Wait状态,服务器和客户端关闭连接
get和post的区别
本质上都是tcp链接,并没有差别
但是由于http的规定,在具体应用的时候会有点差别
1.get请求参数通过url传递,post放在body里
2.get请求传送的参数长度有限制,body没有
http协议没有对url和body的长度做限制,做限制的一般是浏览器和服务器,比如ie长度限制为2083字节(2k+35),
服务器处理长url要消耗更多的资源,为了性能考虑,会限制url的长度
3.get请求是幂等操作,多次请求是没有副作用的,post不是
4.get请求只能进行url编码,而post支持多种编码方式(application/json application/x-www-urlencode)
5.get请求的参数会被保留在浏览器的历史记录里,post不会
6.对于参数类型,get只接受ASCII字符,post没有限制
7.get比post更不安全,因为参数直接暴露在url上,从传输角度来看,都是不安全的,因为用的是同一个传输层协议。只有使用htttps才能加密安全
参考:https:
解决跨域的的方法
协议+域名/地址+端口都相同才属于同源
跨域问题是浏览器对ajax请求的一种安全限制,能有效阻止跨站攻击
1.jsonp,核心是加载script标签,只支持get请求,不能发post请求,优势是支持老浏览器
2.跨域资源共享cors,Acess-Control-Allow-Origin允许哪些域名下面的js请求此资源,在服务器端进行控制是否允许跨域,缺点,会产生额外的请求
3.Nginx,反向代理,利用nginx将对真实服务器的请求转移到本机服务器来避免浏览器的同源策略
http状态码
1xx,代表请求已被接收,需要继续处理,这类一般是临时响应
2xx,代表请求已被服务器成功接收
-200请求已成功,请求所需要的响应头或数据体将随此响应返回
3xx,表示要完成请求需要进一步操作,这类状态码一般用来重定向
-301,永久重定向
-302,临时重定向
-304,协商缓存,告诉浏览器使用缓存数据,返回信息只有头部信息,没有内容
4xx,代表客户端可能的错误
-400,服务器不理解请求的语法
-401,未授权的请求
-403,服务器拒绝请求
-404,服务器未找到对应资源
5xx,代表服务器在处理请求时发生错误或异常
-500,服务内部错误,服务器发生错误,无法完成请求
-502,错误网关
-503,服务器不可用,(服务器由于超载或者停机维护)
-504,网关超时
-505,服务器不支持请求中使用的http协议版本
http2.0和1.0的区别
http1.0,无状态无连接的应用层协议。
- 无状态,服务器不跟踪不记录请求过的状态
- 无连接,每次请求都需要建立tcp链接,服务器处理完请求时立即断开链接
缺点:
- 无法复用连接
- 队头阻塞,http/1.0规定必须在上一个请求响应到达之后才进行下一个请求
http1.1:
- 长连接,HTTP/1.1增加了个Connection字段,通过设置Keep-alive可以保持连接不断开,如果客户端想关闭连接,
可以在请求头中携带Connection:false告知服务器关闭请求
- 支持请求管道化,一个tcp连接上可以传送多个http请求和响应,减少建立和关闭连接的消耗和延迟。(服务端必须按照客户端请求的先后次序返回结果,以保证客户端能区分每次请求的响应内容)
- 每个单独网页的文件的请求和响应仍需要使用各自的tcp连接,http1.1还允许客户端不用等待上一次结果返回就可以发出下一次请求
缺点:
- 无法解决队头阻塞的问题(服务端必须按照客户端请求的先后次序返回结果,以保证客户端能区分每次请求的响应内容),同时‘管道化’技术存在各种各样问题,很多浏览器不支持,开启条件也很苛刻,实际上好像没有什么用处。
http2.0:
- 请求和响应数据分割为更小的帧,采用二进制格式编码传输,而不是http1.0的文本格式,体积更小,解析起来更高效
- 多路复用,在一个tcp连接上可以进行多次请求和响应
- 同一个域名下所有的通信都可以在单个连接上完成
- 单个连接可以承载任意数量的双向数据流
- 数据流以帧的形式发送,多个帧之间可以乱序发送,根据帧首部的信息进行重新组装
- 在HTTP/2中,每个请求都可以带一个31bit的优先值,0表示最高优先级, 数值越大优先级越低。
- 强制走https
- 服务器可以主动向客户端推送资源
- 头部压缩,通过字典压缩,既避免了重复header的传输,又减小了传输大小
options预检请求
在跨域的情况下,在浏览器发起复杂请求时,浏览器需要用options请求先发起一个预检请求,根服务器确认是否支持该跨域请求,服务器允许之后才发起实际的http请求。
- 简单请求,一般来说请求方法是get/post/head,http头不超出Accept/Accept-Language/Content-Language/Content-type仅限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
- 复杂请求,不符合简单请求条件的请求被浏览器判定为复杂请求,可能对服务器数据产生副作用的请求(特别是get以外的请求,如put/delete/connect)
cookie和session
cookie,http是无状态的,服务器不会记录用户状态,无法辨认请求是不是同一个人,是服务器发送给浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器发请求时被携带并发送到服务器上
- cookie是不可跨域的
session,是保存在服务器端的一个数据结构,用来跟踪用户的状态,可以保存在文件、集群数据库中,是记录服务器和客户端会话状态的机制
-session是基于cookie实现的,默认在客户端用cookie保存sessionId,
两者区别:
- session比cookie安全,session存在服务器端,cookie存在客户端
- 存取值的类型不同,cookie只支持字符串数据,session支持任意数据类型
- 有效期不同,cookie可以设置长时间保持,比如经常使用的默认登录功能;session一般失效时间比较短,默认情况下客户端关闭或者超时(一般30分钟无操作)都会失效
- 存储大小不同,单个cookie最多保存4k,session可存储数据远高于cookie。
Token 是令牌,用户信息被加密到token中,访问资源接口(API)时所需要的凭证,服务器收到token后解密就知道是哪个用户,一般存放于localstorage、sessionstorage,甚至cookie中
cookie和localstorage以及sessionstorage
生命周期,
- cookie,一般由服务器生成,可设置失效的时间。如果是浏览器端生成cookie,默认是关掉浏览器失效
- localstorage,一般永久保存,除非被手动清除
- sessionstorage,仅在当前会话下有效,关闭页面或者浏览器后被清除
存放数据大小,
- cookie,4k左右
- localstorage, 5MB
- sessionstorage, 5MB
与服务器端通信,
- cookie,每次都会携带在http头中,如果使用cooki保存过多数据会带来性能问题
- localstorage和sessionstorage,保存在浏览器端,不参与和服务器的通信
http和websocket
websocket,可以在浏览器和服务器之间建立交互式会话,例如浏览器向服务器发送请求,并通过监听Messaga事件接受响应,使用这种方式,无需通过轮询服务器的方式获得响应
http,非持久连接