网页加载过程
- 加载资源的形式
- html代码
- 媒体文件,如图片、视频等
- JavaScript css
- 加载资源的过程
- DNS解析:域名 -> IP地址
- 浏览器根据IP地址向服务器发起http请求,三次握手建立TCP连接(包含三次握手、四次挥手)
- 三次握手
- 客户端发送请求到服务器,服务器知道客户端发送,自己接收正常。SYN=1,seq=x
- 服务器发给客户端,客户端知道自己发送、接收正常,服务器接收、发送正常。ACK=1,ack=x+1,SYN=1,seq=y
- 客户端发给服务器:服务器知道客户端发送,接收正常,自己接收,发送也正常。seq=x+1,ACK=1,ack=y+1
- 三次握手图
- 四次挥手
- 客户端请求断开FIN,seq=u
- 服务器确认客户端的断开请求ACK,ack=u+1,seq=v
- 服务器请求断开FIN,seq=w,ACK,ack=u+1
- 客户端确认服务器的断开ACK,ack=w+1,seq=u+1
- 四次挥手图
- 三次握手
- 服务器处理http请求,并返回给浏览器
- 渲染页面的过程
- 根据 HTML 代码生成 DOM Tree
- 根据 CSS 代码生成 CSSOM
- 将 DOM Tree 和 CSSOM 整合形成 Render Tree
- 根据 Render Tree渲染页面
- 遇到
<script>则暂停渲染,优先加载并执行JS代码,完成再继续 - Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上。
- window.onload 和 DOMContentLoaded
window.addEventListener('load',function(){ // 页面的全部资源加载完才会执行,包括图片、视频等 }) document.addEventListener('DOMContentLoaded',function(){ // DOM 渲染完即可执行,此时图片、视频还可能没有加载完 }) - 回流和重绘
- 何时发生回流
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
- 页面一开始渲染的时候(这肯定避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
- 何时发生重绘(回流一定会触发重绘):
- 当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
- 有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。
- 以下属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,**最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。
clientWidth、clientHeight、clientTop、clientLeftoffsetWidth、offsetHeight、offsetTop、offsetLeftscrollWidth、scrollHeight、scrollTop、scrollLeftwidth、heightgetComputedStyle()getBoundingClientRect()
- 如何避免触发回流和重绘
- CSS:
- 避免使用table布局。
- 尽可能在DOM树的最末端改变class。
- 避免设置多层内联样式。
- 将动画效果应用到
position属性为absolute或fixed的元素上 - 避免使用CSS表达式(例如:
calc()) - CSS3硬件加速(GPU加速)
- JavaScript:
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性
- 避免频繁操作DOM,创建一个
documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中 - 也可以先为元素设置
display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘 - 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流
- CSS:
- 何时发生回流
性能优化
- 性能优化原则
- 多使用内存、缓存或其他方法
- 减少CPU计算量,减少网络加载耗时
- (适用于所有编程的性能优化 —— 空间换时间)
- 让加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码、合并图片(雪碧图)、SSR服务器端渲染、缓存
- 使用更快的网络:CDN
- 让渲染更快
- CSS放在head中,JS放在body最下面
- 尽早开始执行JS,用DOMContentLoaded触发
- 懒加载(图片懒加载,上滑加载更多)
- 对DOM查询进行缓存
- 频繁DOM操作,合并到一起插入DOM结构
- 节流throttle 防抖debounce (让渲染更加流畅)
体验优化
防抖debounce
- 触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间;
- 多应用于 用户输入,防止频繁触发change事件
- 手写debounce函数
// 防抖 function debounce(fn, delay = 500){ // timer 是闭包中 let timer = null return function(){ if(timer){ clearTimeout(timer) } timer = setTimeout(()=>{ console.log('this',this) fn.apply(this,arguments) timer = null },delay) } } input1.addEventListener('keyup',debounce(function(e){ console.log(e.target) console.log(input1.value) },600))
节流throttle
- 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
- 多应用于 拖拽元素 获取位置
- 手写throttle函数
const div1 = document.getElementById('div1') function throttle(fn,delay=100){ let timer = null return function(){ if(timer) return timer = setTimeout(() => { fn.apply(this,arguments) timer = null }, delay); } } div1.addEventListener('drag',throttle(function(e){ console.log(e.offsetX,e.offsetY); }))
常见的web前端攻击方式
XSS 跨站请求攻击
- 模拟个XSS攻击场景
一个博客网站,我发表了一篇博客,其中嵌入<script>脚本 脚本内容:获取cookie,发送到我的服务器(服务器配合跨域) 发布这篇博客,有人查看他,可以轻松收割访问者的cookie - 如何预防XSS攻击
- 替换特殊字符,如<变为 < >变为 >
<script>变为<script>,直接显示,而不会不作为脚本执行- 前端要替换,后端也要替换,都做总不会有错
<script> alert(111) </script>
XSRF 跨站请求伪造
- 模拟个XSRF攻击场景
你正在购物,看中了某个商品,商品id是100, 付费接口是xxx.com/pay?id=100 ,但没有任何验证 我是攻击者,我看中了一个商品,id是200 我向你发送了一封电子邮件,邮件标题很吸引人 但邮件正文隐藏着<img src=xxx.com/pay?id=200 /> 你一查看邮件,就帮我买了id是200的商品 - 如何预防XSRF攻击
- 使用post接口
- 增加验证,例如密码、短信验证码、指纹等