前端面试题(六)前端性能优化篇

网络相关

DNS 预解析

DNS 解析也是须要时间的,能够经过预解析的方式来预先得到域名所对应的 IP。javascript

<link rel="dns-prefetch" href="//yuchengkai.cn">
复制代码

缓存

缓存对于前端性能优化来讲是个很重要的点,良好的缓存策略能够下降资源的重复加载提升网页的总体加载速度。html

一般浏览器缓存策略分为两种:强缓存和协商缓存。前端

强缓存

实现强缓存能够经过两种响应头实现:ExpiresCache-Control 。强缓存表示在缓存期间不须要请求,state code 为 200java

Expires: Wed, 22 Oct 2018 08:41:00 GMT
复制代码

Expires 是 HTTP / 1.0 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过时,须要再次请求。而且 Expires 受限于本地时间,若是修改了本地时间,可能会形成缓存失效。面试

Cache-control: max-age=30
复制代码

Cache-Control 出现于 HTTP / 1.1,优先级高于 Expires 。该属性表示资源会在 30 秒后过时,须要再次请求。算法

协商缓存

若是缓存过时了,咱们就可使用协商缓存来解决问题。协商缓存须要请求,若是缓存有效会返回 304。跨域

协商缓存须要客户端和服务端共同实现,和强缓存同样,也有两种实现方式。浏览器

Last-Modified 和 If-Modified-Since

Last-Modified 表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。缓存

可是若是在本地打开缓存文件,就会形成 Last-Modified 被修改,因此在 HTTP / 1.1 出现了 ETag性能优化

ETag 和 If-None-Match

ETag 相似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变更,有变更的话就将新的资源发送回来。而且 ETag 优先级比 Last-Modified 高。

选择合适的缓存策略

对于大部分的场景均可以使用强缓存配合协商缓存解决,可是在一些特殊的地方可能须要选择特殊的缓存策略

  • 对于某些不须要缓存的资源,可使用 Cache-control: no-store ,表示该资源不须要缓存
  • 对于频繁变更的资源,可使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,可是每次都会发送请求询问资源是否更新。
  • 对于代码文件来讲,一般使用 Cache-Control: max-age=31536000 并配合策略缓存使用,而后对文件进行指纹处理,一旦文件名变更就会马上下载新的文件。

使用 HTTP / 2.0

由于浏览器会有并发请求限制,在 HTTP / 1.1 时代,每一个请求都须要创建和断开,消耗了好几个 RTT 时间,而且因为 TCP 慢启动的缘由,加载体积大的文件会须要更多的时间。

在 HTTP / 2.0 中引入了多路复用,可以让多个请求使用同一个 TCP 连接,极大的加快了网页的加载速度。而且还支持 Header 压缩,进一步的减小了请求的数据大小。

更详细的内容你能够查看 该小节

预加载

在开发中,可能会遇到这样的状况。有些资源不须要立刻用到,可是但愿尽早获取,这时候就可使用预加载。

预加载实际上是声明式的 fetch ,强制浏览器请求资源,而且不会阻塞 onload 事件,可使用如下代码开启预加载

<link rel="preload" href="http://example.com">
复制代码

预加载能够必定程度上下降首屏的加载时间,由于能够将一些不影响首屏但重要的文件延后加载,惟一缺点就是兼容性很差。

预渲染

能够经过预渲染将下载的文件预先在后台渲染,可使用如下代码开启预渲染

<link rel="prerender" href="http://example.com"> 
复制代码

预渲染虽然能够提升页面的加载速度,可是要确保该页面百分百会被用户在以后打开,不然就白白浪费资源去渲染

优化渲染过程

对于代码层面的优化,你能够查阅浏览器系列中的 相关内容

懒执行

懒执行就是将某些逻辑延迟到使用时再计算。该技术能够用于首屏优化,对于某些耗时逻辑并不须要在首屏就使用的,就可使用懒执行。懒执行须要唤醒,通常能够经过定时器或者事件的调用来唤醒。

懒加载

懒加载就是将不关键的资源延后加载。

懒加载的原理就是只加载自定义区域(一般是可视区域,但也能够是即将进入可视区域)内须要加载的东西。对于图片来讲,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。

懒加载不只能够用于图片,也可使用在别的资源上。好比进入可视区域才开始播放视频等等。

文件优化

图片优化

计算图片大小

对于一张 100 * 100 像素的图片来讲,图像上有 10000 个像素点,若是每一个像素的值是 RGBA 存储的话,那么也就是说每一个像素有 4 个通道,每一个通道 1 个字节(8 位 = 1个字节),因此该图片大小大概为 39KB(10000 * 1 * 4 / 1024)。

可是在实际项目中,一张图片可能并不须要使用那么多颜色去显示,咱们能够经过减小每一个像素的调色板来相应缩小图片的大小。

了解了如何计算图片大小的知识,那么对于如何优化图片,想必你们已经有 2 个思路了:

  • 减小像素点
  • 减小每一个像素点可以显示的颜色

图片加载优化

  1. 不用图片。不少时候会使用到不少修饰类图片,其实这类修饰图片彻底能够用 CSS 去代替。
  2. 对于移动端来讲,屏幕宽度就那么点,彻底没有必要去加载原图浪费带宽。通常图片都用 CDN 加载,能够计算出适配屏幕的宽度,而后去请求相应裁剪好的图片。
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 选择正确的图片格式:
    • 对于可以显示 WebP 格式的浏览器尽可能使用 WebP 格式。由于 WebP 格式具备更好的图像数据压缩算法,能带来更小的图片体积,并且拥有肉眼识别无差别的图像质量,缺点就是兼容性并很差
    • 小图使用 PNG,其实对于大部分图标这类图片,彻底可使用 SVG 代替
    • 照片使用 JPEG

其余文件优化

  • CSS 文件放在 head
  • 服务端开启文件压缩功能
  • script 标签放在 body 底部,由于 JS 文件执行会阻塞渲染。固然也能够把 script 标签放在任意位置而后加上 defer ,表示该文件会并行下载,可是会放到 HTML 解析完成后顺序执行。对于没有任何依赖的 JS 文件能够加上 async ,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行。
  • 执行 JS 代码过长会卡住渲染,对于须要不少时间计算的代码能够考虑使用 WebworkerWebworker 可让咱们另开一个线程执行脚本而不影响渲染。

CDN

静态资源尽可能使用 CDN 加载,因为浏览器对于单个域名有并发请求上限,能够考虑使用多个 CDN 域名。对于 CDN 加载静态资源须要注意 CDN 域名要与主站不一样,不然每次请求都会带上主站的 Cookie。

其余

使用 Webpack 优化项目

  • 对于 Webpack4,打包项目使用 production 模式,这样会自动开启代码压缩
  • 使用 ES6 模块来开启 tree shaking,这个技术能够移除没有使用的代码
  • 优化图片,对于小图可使用 base64 的方式写入文件中
  • 按照路由拆分代码,实现按需加载
  • 给打包出来的文件名添加哈希,实现浏览器缓存文件

监控

对于代码运行错误,一般的办法是使用 window.onerror 拦截报错。该方法能拦截到大部分的详细报错信息,可是也有例外

  • 对于跨域的代码运行错误会显示 Script error. 对于这种状况咱们须要给 script 标签添加 crossorigin 属性
  • 对于某些浏览器可能不会显示调用栈信息,这种状况能够经过 arguments.callee.caller 来作栈递归

对于异步代码来讲,可使用 catch 的方式捕获错误。好比 Promise 能够直接使用 catch 函数,async await 可使用 try catch

可是要注意线上运行的代码都是压缩过的,须要在打包时生成 sourceMap 文件便于 debug。

对于捕获的错误须要上传给服务器,一般能够经过 img 标签的 src 发起一个请求。

面试题

如何渲染几万条数据并不卡住界面

这道题考察了如何在不卡住页面的状况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就能够经过 requestAnimationFrame 来每 16 ms 刷新一次。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <ul>控件</ul>
  <script> setTimeout(() => { // 插入十万条数据 const total = 100000 // 一次插入 20 条,若是以为性能很差就减小 const once = 20 // 渲染数据总共须要几回 const loopCount = total / once let countOfRender = 0 let ul = document.querySelector("ul"); function add() { // 优化性能,插入不会形成回流 const fragment = document.createDocumentFragment(); for (let i = 0; i < once; i++) { const li = document.createElement("li"); li.innerText = Math.floor(Math.random() * total); fragment.appendChild(li); } ul.appendChild(fragment); countOfRender += 1; loop(); } function loop() { if (countOfRender < loopCount) { window.requestAnimationFrame(add); } } loop(); }, 0); </script>
</body>
</html>
复制代码
相关文章
相关标签/搜索