资源预请求和预加载 (非首屏加载)
<link rel="preload" href="main.js" as="script"/>
<link rel="prefetch" href="style.css" as="style"/>
<!-- 预加载字体 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预加载CSS -->
<link rel="preload" href="critical.css" as="style">
preload 优先级较高,对页面非常重要的资源提前加载,资源真正被使用的时候立即执行
prefetch 优先级较低,利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面
(非首页)所需要的资源,以便加快后续页面的打开速度
preload 特点:
1)preload 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件;
2)preload 加载的 JS 脚本其加载和执行的过程是分离的,即 preload 会预加载相应的脚本代码,待到需要时自行调用
prefetch 特点:
1)pretch 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少5分钟(无论资源是否可以缓存)
2)当页面跳转时,未完成的 prefetch 请求不会被中断
js资源的异步加载(首屏加载)
async,defer 对js资源的异步引入,不阻塞html解析,async js加载完成引入,defer html解析完引入 defer是按引入 顺序加载
JS 的加载方式
<script src="index.js"></script>
正常情况下JS会阻塞dom渲染,浏览器必须等待index.js加载和执行完成后才能去做其它事情
async 模式
<script async src="index.js"></script>
async模式下,它的加载是异步的JS不会阻塞DOM的渲染,async加载是无顺序的,当它加载结束,JS会立即执行
使用场景:若该JS资源与DOM元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用async模式,比如埋点统计
defer模式
<script defer src="index.js"></script>
defer模式下,JS的加载也是异步的,defer资源会在 DOMContentLoaded执行之前,并且defer是有顺序的加载
如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后顺序执行,即便是后面的 script 资源先返回
懒加载(Lazy Load)
<!-- 原生懒加载 -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy">
<!-- Intersection Observer API -->
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('[data-src]').forEach(img => observer.observe(img));
</script>
资源打包
Tree Shaking
// webpack.config.js
module.exports = {
optimization: {
usedExports: true,
},
};
代码分割
// 动态导入(React示例)
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
// Webpack自动分割
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
}
Service Worker缓存
// sw.js
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open('v1').then(cache =>
cache.addAll(['/main.css', '/app.js']))
);
});
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request).then(response =>
response || fetch(e.request))
);
});