以下的内容全是搬运,甚至大块的搬运,没办法,我也创造不出来,我就是看着写得好,我又理解了,就想手抄一下,加深印象?
部署优化
docker优化
最近读光神的小册子,他提到了一个docker多阶段构建,我去搜了搜。
找到了我以前看过的山月大佬的docker部署系列。
【前端部署第四篇】使用 Docker 构建缓存及多阶段构建优化单页应用
他在构建阶段的优化提到了两点,一个是时间,一个是空间。
-
时间上:可以利用yarn缓存,如果 yarn.lock 内容没有变化,则不会重新依赖安装,当然这一步是利用缓存还不涉及多阶段构建
FROM node:14-alpine as builder WORKDIR /code # 单独分离 package.json,是为了安装依赖可最大限度利用缓存 ADD package.json yarn.lock /code/ # 此时,yarn 可以利用缓存,如果 yarn.lock 内容没有变化,则不会重新依赖安装 RUN yarn ADD . /code RUN npm run build CMD npx serve -s build EXPOSE 3000
提到yarn.lock文件,就想起npm、yarn、pnpm的区别,知识真的会串起来
-
空间上:
我们的目标静态资源,完全不需要依赖于 node.js 环境进行服务化,而 node.js 环境将造成极大的资源浪费。我们可以使用多阶段构建进行优化(只保留最后一个阶段构建好的镜像),最终使用 nginx 进行服务化。
- 第一阶段 Node 镜像: 使用 node 镜像对单页应用进行构建,生成静态资源
- 第二阶段 Nginx 镜像: 使用 nginx 镜像对单页应用的静态资源进行服务化
# 第一步利用node镜像构建出静态资源
FROM node:14-alpine as builder
# 工作目录为 code
WORKDIR /code
ADD package.json yarn.lock /code/
# 在 code中生成 node_modules
RUN yarn
# 将文件价复制到code中
ADD . /code
# 打包出静态资源
RUN npm run build
# 选择更小体积的基础镜像!
FROM nginx:alpine
# 将静态资源挂载到 nginx工作目录下面
COPY --from=builder code/build /usr/share/nginx/html
图片加载优化
多图片如何加载?
懒加载
推荐文章 前端性能优化之图片懒加载
这是我第一次知道懒加载原理。
懒加载其实就是看到哪里才开始加载。
主要就是用到两个api,一个是 document.body.clientHeight
获取视口高度、一个是getBoundingClientRect
获取元素相对于视口的位置(x,y)
function lazyload() {
let viewHeight = document.body.clientHeight //获取可视区高度
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item, index) => {
if (item.dataset.src === '') return
// 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
let rect = item.getBoundingClientRect()
// 如果元素的底部距离视口的高度大于0(我理解的就是图片有高度)
// 并且图片的顶部距离视口的顶部小于视口的高度,也就是图片出现当前视口中
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')
}
})
}
作者还帮我们复习了一下节流函数,来逐行解释一下。
function throttle(fn, delay) {
let timer
let prevTime
// 经典闭包 timer 和 prevTime 的值不会被销毁
return function (...args) {
// 每次函数被执行 都要重新赋值当前的currTime
const currTime = Date.now()
// 赋值当前的上下文(此处应该是windows)
const context = this
// 第一次执行当前函数时,会执行这个if语句,也就是将当前的currTime赋值过去
if (!prevTime) prevTime = currTime
// 每次进入都要清除上一个定时器,防止开启多个定时器以及内存泄漏
clearTimeout(timer)
// 如果说已经过了200ms,就重新执行里面的函数,否则进不来
if (currTime - prevTime > delay) {
// 重新执行的时候重新赋值 prevTime 为后面的判断做准备
prevTime = currTime
// 执行fn函数
fn.apply(context, args)
// 清除掉上一个定时器 (但我觉得这里没必要?)
clearTimeout(timer)
return
}
// 每次触发都是等待200ms后执行
timer = setTimeout(function () {
// 重新赋值 prevTime 为后面的判断做准备
prevTime = Date.now()
// 清除当前定时器
timer = null
// 执行函数
fn.apply(context, args)
}, delay)
}
}
== 我又晕了 逐行解释完我也不理解这玩意,反正就是200ms内只执行第一次就完了
IntersectionObserver
原来有个API能很好的帮助我们去实现图片的懒加载
具体文章看这里?
探秘神奇的IntersectionObserver:释放网页性能的黑科技! – 掘金 (juejin.cn)
// 获取所有的需要监听的对象
const imgs = document.querySelectorAll("img[data-src]");
const config = {
rootMargin: "0px",
threshold: 0,
};
// 初始化一个 IntersectionObserver 实例
let observer = new IntersectionObserver((entries, self) => {
// entries 就是所有的 被当前实例observe的对象组成的数组,这里也就是我们12张图片
entries.forEach((entry) => {
// 被观察的对象会自动新增isIntersecting,如果是true,就说明在可视区域内
if (entry.isIntersecting) {
let img = entry.target;
let src = img.dataset.src;
// 渲染图片
if (src) {
img.src = src;
img.removeAttribute("data-src");
}
// 停止观察
self.unobserve(entry.target);
}
});
});
// 观察要监听的对象
imgs.forEach((image) => {
observer.observe(image);
});
预加载
推荐文章
preload、prefetch、preconnect 和 dns-prefetch 知多少 – 掘金 (juejin.cn)
preload
提前加载当前页面需要的内容,不会阻塞onload事件
应用案例:
一、 提前加载css中的font字体
当页面中使用了自定义字体的时候,就必须在 CSS
中引入该字体,而由于字体必须要等到浏览器下载完且解析该 CSS
文件的时候才开始下载,所以对应页面上该字体处可能会出现闪动的现象,为了避免这种现象的出现,就可以使用 preload
来提前加载字体,type
可以用来指定具体的字体类型,加载字体必须指定 crossorigin
属性,否则会导致字体被加载两次。
<link rel="preload" as="font" crossorigin type="font/woff2" href="myfont.woff2">
二、 预加载css文件
作者介绍了两个css文件,一个是critical.css
,另一个是non-critical.css
。加载首屏时,我们可以把首屏可视区域的样式抽离出来,写到内置的style
标签中。这样就可以让首屏快速的进行样式渲染。让不那么重要的css,进行预加载(proload),这样加载完成后也可以马上渲染到页面上。
三、 结合媒体查询预加载响应式图片
preload
甚至还可以结合媒体查询加载对应尺寸下的资源,对于以下代码当可视区域尺寸小于 600px
的时候会提前加载这张图片。
<link rel="preload" as="image" href="someimage.jpg" media="(max-width: 600px)">
四、结合 Webpack
预加载 JS
模块
import(/* webpackPreload: true */ 'ChartingLibrary');
prefetch
preload
用于提前加载用于当前页面的资源,而 prefetch
则是用于加载未来(比如下一个页面)会用到的资源,并且告诉浏览器在空闲的时候去下载,它会将下载资源的优先级降到最低。
比如在首页配置如下代码:
<link rel="prefetch" as="script" href="https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js">
我们会在页面中看到该脚本的下载优先级已经被降低为 Lowest
:
当资源被下载完成后,会被存到浏览器缓存中,当从首页跳转到页面 A 的时候,假如页面 A 中引入了该脚本,那么浏览器会直接从 prefetch cache
中读取该资源,从而实现资源加载优化。
网络优化
当浏览器向服务器请求一个资源的时候,需要建立连接,而建立一个安全的连接需要经历以下 3 个步骤:
- 查询域名并将其解析成 IP 地址(DNS Lookup);
- 建立和服务器的连接(Initial connection);
- 加密连接以确保安全(SSL);
以上 3 个步骤浏览器都需要和服务器进行通信,而这一来一往的请求和响应势必会耗费不少时间。
preconnect
当我们的站点需要对别的域下的资源进行请求的时候,就需要和那个域建立连接,然后才能开始下载资源,如果我都已经知道了是和哪个域进行通信,就可以先建立连接,然后等需要进行资源请求的时候就可以直接进行下载了。
假设当前站点是 https://a.com
,这个站点的主页需要请求 https://b.com/b.js
这个资源。对比正常请求和配置了 preconnect
时候的请求,它们在请求时间轴上看到的表现是不一样的:
通过如下配置可以提前建立和 https://b.com
这个域的连接:
<link rel="preconnect" href="https://b.com">
通过 preconnect
提早建立和第三方源的连接,可以将资源的加载时间缩短 100ms ~ 500ms,这个时间虽然看起来微不足道,但是它是实实在在的优化了页面的性能,提升了用户的体验。
通过 preconnect 和别的域建立连接后,应该尽快的使用它,因为浏览器会关闭所有在 10 秒内未使用的连接。不必要的预连接会延迟其他重要资源,因此要限制 preconnect 连接域的数量
应用场景
场景一:
当知道资源是来源于哪个源下,但是对于加载哪个资源不是很明确的时候,比如对于如下这些资源:
它们要嘛是动态的,要嘛是根据不同环境携带不同参数,所以它们很适合用 preconnect
进行加载。
场景二:
如果页面上有流媒体,但是没那么快播放,又希望当按下播放按钮的时候可以越快开始越好,此时就可以使用 preconnect
预建立连接,节省一段时间。
如果用 preconnect
预建立连接的资源是一个字体文件,那么也是需要加上 crossorigin
属性。
dns-prefetch
通常我们记住一个网站都是通过它的域名,但是对于服务器来说,它是通过 IP 来记住它们的。浏览器使用 DNS
来将站点转成 IP 地址,这个是建立连接的第一步,而这一步骤通常需要花费的时间大概是 20ms ~ 120ms。因此,可以通过 dns-prefetch
来节省这一步骤的时间。
居然能通过 preconnect
来减少整个建立连接的时间,那为什么还需要 dns-prefetch
来减少建立连接中第一步 DNS 查找解析的时间呢?
假如页面引入了许多第三方域下的资源,而如果它们都通过 preconnect
来预建立连接,其实这样的优化效果反而不好,甚至可能变差,所以这个时候就有另外一个方案,那就是对于最关键的连接使用 preconnect
,而其他的则可以用 dns-prefetch
。
可以按照如下方式配置 dns-prefetch
:
<link rel="dns-prefetch" href="https://cdn.bootcss.com">
就记录这些应付下面试吧