第十六章-运行环境
运行环境
- 运行环境即浏览器(server端有node.js)
- 下载网页代码,渲染出页面,期间可能会执行若干 JS 逻辑
- 要保证代码在浏览器中:稳定且高效
- 了解网页的加载过程
- 性能优化和体验优化
- 安全问题
网页加载过程
题目:
- 从输入 url 到渲染出页面的整个过程
- window.onload 和 DOMContentLoaded 的区别
知识点:
- 加载资源的形式
- 加载资源的过程
- 渲染页面的过程
资源的形式
- html 代码
- 媒体文件,如图片、视频等
- javascript、css
加载过程
- DNS 解析: 将域名解析为 ip 地址
- 浏览器根据 IP 地址向服务器发起 http 请求(三次握手)
- 服务器处理 http 请求,并返回给浏览器
渲染过程1
- 根据 HTML 代码生成 DOMTree
- 根据CSS代码生成 CSSOM
- 将DOM Tree 和 CSSOM 整合形成 Render Tree
渲染过程2
- 根据 Render Tree 渲染页面
- 遇到
<script>
则暂停渲染(js代码中可能会有改变页面内容的操作),优先加载并执行 JS 代码,完成JS之后继续 - 直到把 Render Tree 渲染完成
为什么建议把 css 放在 head 中?
DOM Tree 从上到下执行,如果 css 没有放到头部,而是放到了尾部,会先 DOM 结构渲染出来而没有样式,直到遇到 css 才会将 css 与 DOM tree 结合
为什么建议把 js 放在 body 最后?
js代码会阻塞页面的渲染
window.onload 和 DOMContentLoaded 的区别
// 页面的全部资源加载完后才会执行,包括图片、视频等
window.addEventListener('load', function () {
})
// DOM 渲染完成即可执行,此时图片、视频还可能没有加载完成
document.addEventListener('DOMContentLoaded', function () {
})
性能优化的方式
性能优化是一个综合性问题,没有标准答案,但要求尽量全面,某些细节问题可能会单独提问:手写防抖、节流
性能优化原则
- 多使用内存、缓存或者其他方法
- 减少 CPU 计算量,减少网络加载耗时
- 适用于所有的编程的性能优化 -- 空间换时间(每个标签页放一个进程)
从何入手
- 让加载更快
- 让渲染更快
让加载更快
- 减少资源体积:压缩代码(webpack、gulp)。gzip(服务端压缩)
- 减少访问次数:合并代码(webpack),SSR服务端渲染,缓存,精灵图
- 使用更快的网络:CDN(内容分发网络)
让渲染更快
- CSS 放在 head, JS 放在 body 最下面
- 尽早开始执行 JS, 用
DOMContentLoaded
触发 - 懒加载(图片懒加载,上滑/下拉加载更多)
- 对 DOM 查询进行缓存
- 频繁 DOM 操作,合并到一起插入 DOM 结构
- 节流 throttle 防抖 debounce
缓存:配置 webpack 压缩时 hash 值,根据文件内容计算 hash 值。文件内容不变,则hash 不变,则 url 不变。url 和文件不变,则会自动触发 http 缓存机制,返回 304(资源未被修改,可以使用本地缓存)
SSR:(SSR)服务端渲染,将网页和数据一起加载,一起渲染;非SSR(前后端分离),先加载网页,再加载数据,然后渲染数据。早先的 JSP ASP PHP,现在 Vue React SSR。
懒加载:
<img id='img' src="loading.png" data-realsrc="read.png" />
<script>
let img1 = document.getElementById('img1')
img1.src = img1.getAttribute('data-realsrc')
</script>
手写防抖(debounce)
- 监听一个输入框,文字变化后触发 change 事件
- 直接用 keyup 事件,则会频繁触发 change 事件
- 防抖:用户输入结束或者暂停时,才会触发 change 事件
const input1 = document.getElementById('input1')
let timer = null
input1.addEventListener('keyup',function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
// 模拟触发 change 事件
console.log(iput1.value)
// 清空定时器
timer = null
}, 500)
})
封装防抖函数
// 封装防抖函数
function debounce(fn, delay = 500) {
// timer 是闭包中的
let timer = null
// 返回一个函数
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
input1.addEventListener('keyup', debounce(function () {
console.log(input1.value)
}, 500))
手写节流(throttle)
- 拖拽一个元素时,要随时拿到该元素被拖拽的位置
- 直接用 drag 事件,则会频繁触发,很容易导致卡顿
- 节流:无论拖拽速度多快,都会每隔一段时间触发(100ms)
const div1 = document.getElementById('div1')
let timer = null
div1.addEventListener('drag', function (e) {
if (timer) {
return
}
timer = setTimeout(() => {
console.log(e.offsetX, e.offsetY)
timer = null
}, 100)
})
封装节流函数
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)
}, 100))
安全
问题:常见的web前端攻击方式有哪些?
- XSS 跨站请求攻击
- XSRF 跨站请求伪造
XSS 攻击
- 一个博客网站,我发表一篇博客,其中嵌套
<script>
脚本 - 脚本内容:获取cookie,发送到我的服务器(服务器配合跨域)
- 发布这篇博客,有人查看它,我轻松收割访问者的 cookie
XSS 预防
- 替换特殊字符,如
<
变为<
,>
变为>
<script>
变为<script>
,直接显示不会作为脚本执行- 前端要替换,后端也要替换,都做总不会有错
- npm 有个 xss 预防攻击的包
XSRF 攻击
- 你正在购物,看中了某个商品,商品id是100
- 付费的接口是 xxx.com/pay?id=100
- 我是攻击者,我看中了一个商品,id 是 200
- 我向你发送一封电子邮件,邮件标题很吸引人
- 但邮件正文中隐藏着
<img src='xxx.com/pay?id=200'>
- 你一查看邮件,就帮我购买了 id 是200的商品
XSRF攻击预防
- 使用 post 接口
- 增加验证,例如密码、短信验证码、指纹等
- 不常发生