运行环境和性能优化

130 阅读3分钟

运行环境和性能优化

浏览器从输入url到渲染出页面的整个过程

  1. 加载资源

我们都知道浏览器加载资源内容包括html、js、css代码媒体文件(图片、视频)等,那么加载这些资源的过程是怎么样的呢?

  • DNS解析域名->IP地址
  • 浏览器根据IP地址向服务器发起HTTP请求
  • 服务器处理请求,并返回浏览器

1657942762908.png

1657942786008.png

  1. 渲染界面

  • 根据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代码。

render.png

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>

打印结果:

1644285928399.png

性能优化

核心思想就是:空间换时间

  • 多使用内存,缓存或其他方法
  • 减少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跨站请求伪造