运行环境和性能优化
浏览器从输入url到渲染出页面的整个过程
-
加载资源
我们都知道浏览器加载资源内容包括
html、js、css代码
,媒体文件(图片、视频)
等,那么加载这些资源的过程是怎么样的呢?
- DNS解析域名->IP地址
- 浏览器根据IP地址向服务器发起HTTP请求
- 服务器处理请求,并返回浏览器
-
渲染界面
- 根据html代码生成DOM Tree
- 根据css代码生成CSSOM
- 将DOM Tree和CSSOM整合形成render tree
- 根据render tree渲染页面
- 遇到script则暂停渲染,优先加载执行js代码
- 直至把render tree渲染完成
在了解这个过程后我们就可以理解为什么写css一般放在head中,就是因为如果放在底部,在html代码生成DOM tree后浏览器可能就开始渲染出页面,后面css生成CSSOM后又会再次渲染页面,导致重复渲染。同理理解script放在body最后是因为遇到script会暂停渲染,我们为了让页面先更快渲染出来,后面在执行js代码。
window.onload和DOMContentLoaded的区别
load: 当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load
事件。
DOMContentLoaded:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载。
还是写一个例子来看看代码的打印顺序,理解一下两者的不同
<!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>
</head>
<body>
<div id="app">
<p>测试文本</p>
<img id="img"
src="http://imgoss.cnu.cc/2201/16/d11195a741ca3b7187b9bb27b33e8503.jpg?width=6329&height=4219&x-oss-process=style/cover280"
alt="">
</div>
</body>
<script>
document.getElementById('img').addEventListener('load', () => {
console.log('img onload');
})
window.addEventListener('load', () => {
console.log('window onload');
})
window.addEventListener('DOMContentLoaded', () => {
console.log('window DOMContentLoaded');
})
</script>
</html>
打印结果:
性能优化
核心思想就是:空间换时间
- 多使用内存,缓存或其他方法
- 减少cpu计算量,减少网络加载耗时
加载方面优化
- 减少代码体积:压缩代码
- 减少访问次数:合并代码、ssr服务端渲染、缓存
- 静态资源加hash后缀,根据文件内容计算hash
- 文件内容不变,则hash不变,则url不变
- url和文件不变,则自动触发http缓存机制 返回304
- 服务器端渲染:将网页和数据一起加载 一起渲染
- 非srr: 非ssr先加载网页,在加载数据,在渲染数据
- 以前的jsp asp php 现在的vue react ssr
- 更快的网络:cdn
渲染方面优化
- css放在head里,js放在后面
- 尽早开始执行js(用DOMContentLoaded)
- 懒加载(上滑加载)
- dom查询进行缓存
- 多次查询同一dom,可以缓存变量
- 频繁dom操作,合并到一起插入dom结构
- 防抖和节流
手写防抖 debounce
场景:监听一个输入框,文字变化后触发change事件,直接用keyup事件,则会频繁触发change事件
防抖:用户输入结束或暂停时,才会触发change事件
/**
* 通用版防抖函数
* @param {function} fn - 要被执行的方法, 相隔多长时间要被执行的方法
* @param {number} delay - 间隔时间, 相隔多长时间调用一次对应方法
* @return function
*/
function debounce(fn, delay = 500) {
let timer = null
return function () {
if (timer) {
clearInterval(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
使用例子:
input.addEventListener('keyup', debounce(function (e) {
console.log(e.target.value);
}, 1000))
手写节流 throttle
场景:拖拽一个元素时,要随时拿到该元素被拖拽的位置,直接用drag事件,则会频繁触发,很容易卡顿
节流:无论拖拽速度多快,都会每隔固定时间触发一次
/**
* 通用版节流函数
* @param {function} fn - 要被执行的方法, 相隔多长时间要被执行的方法
* @param {number} delay - 间隔时间, 相隔多长时间调用一次对应方法
* @return function
*/
function throttle(fn, delay = 300) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
使用例子:
app.addEventListener('drag', throttle(function (e) {
console.log(e.clientX, e.clientY);
}, 100))
安全方面
常见的web前端攻击方式
- XSS跨站请求攻击
- XSRF跨站请求伪造