面试官:为什么前端打包出来的静态文件名字是一串 Hash 值 ❓❓❓

我们正在参加一个淘宝的比赛,如果对你有帮助,可以点击该链接进行换肤,为我们投上你的宝贵一票:

换肤地址

直接点击使用皮肤就可以啦,谢谢大家的支持!!!

前端打包后,静态文件的名字被改成一串 Hash 值(例如 app.abc123.js 或 style.abcdef.css),主要是为了缓存管理和性能优化。这是现代前端工程中常见的做法,通常由打包工具(如 Webpack、Vite 等)自动处理。

接下来我们来详细讲解一下这个知识点。

启发式缓存

在 Web 应用和浏览器缓存中,服务器通常会通过 HTTP 头部信息(如 Cache-Control、Expires)明确指示一个资源可以缓存多长时间。但有时这些指示可能缺失,或者某些资源的缓存控制信息不完整,客户端就会依赖启发式规则来确定该资源的缓存时长。这种规则可能基于资源的特征、文件类型,或者历史经验等。

启发式缓存的工作原理基于以下几个步骤:

  1. 资源请求:客户端请求某个资源,如果该资源没有明确的缓存过期时间,系统会选择启发式缓存。

  2. 估算缓存时间:基于启发式规则(通常与资源的响应头信息或者资源类型相关),估算资源应该缓存的时间。例如,系统可能会依据资源最后修改的时间、文件类型等来推断合适的缓存时间。

  3. 缓存存储:估算出缓存的时长后,客户端会将该资源存储在缓存中,直到缓存时间过期为止。

  4. 过期后重新请求:当缓存时间到期后,客户端将重新发出请求来获取最新的资源。

它的主要使用场景有以下两个方面:

  1. 无明确缓存指示的资源:很多静态资源(例如图片、CSS 文件、JavaScript 文件)可能缺乏明确的 Cache-Control 或 Expires 指令。在这种情况下,启发式缓存会基于资源的类型、最后修改时间等规则来估计缓存时长。

  2. 动态内容:某些动态生成的内容(例如 API 返回的数据)没有明确的缓存控制头,但服务器返回的内容在一定时间内不会频繁更新。启发式缓存可以帮助提高性能,减少重复的网络请求。

启发式缓存使用的规则因平台或浏览器实现不同而有所差异,但常见的启发式规则包括:

  1. 基于 Last-Modified 头估算:如果资源包含 Last-Modified 头,浏览器或缓存代理通常会基于该时间来计算缓存过期时间。一个典型的规则是将 Last-Modified 的时间距离当前时间的一小部分(比如 10%)作为缓存时间。例如资源最后修改时间是 2 天前,系统可以设置一个启发式缓存时间为 2天 * 10% = 4.8小时

  2. 基于文件类型:不同类型的资源可以采用不同的启发式缓存策略。例如:图片、字体等静态资源通常可以缓存更长时间(如 1 天到 1 周),而 JavaScript、CSS 等资源,虽然也是静态的,但由于与功能直接相关,缓存时间可能会短一些(如数小时到一天)。

  3. 缺省时间设定:如果无法基于其他头部信息推断,系统可能会采用默认的缓存时间,比如 1 小时或 24 小时。

浏览器默认缓存

当用户首次访问网站并请求 index.html 文件时,浏览器会同时解析并加载其中引用的 JavaScript、CSS 等静态资源。但浏览器已经考虑到了用户的体验:如果每次访问都重新请求这些静态资源,不仅加载时间变长,服务器压力也会增加,严重影响用户体验。为了优化这一过程,浏览器会默认缓存已请求过的静态文件,这种默认的缓存机制就是启发式缓存。除非明确设置了 no-store,否则浏览器会自动缓存静态资源,避免重复下载,加快页面加载速度。

通过给文件名加上 Hash 值(通常是文件内容的 Hash),一旦文件内容发生变化,文件名也会改变。浏览器会识别出这是一个新的文件,从而重新加载最新版本的文件,而不是使用旧的缓存文件。

例子:

  1. 第一次构建生成:app.abc123.js

  2. 修改代码后构建:app.def456.js

其中的 abc123 和 def456 就是基于文件内容生成的 Hash 值。具体来说,Hash 值是对文件内容进行哈希算法(如 MD5、SHA-256)处理生成的一个字符串。这个字符串独特地表示了文件的内容。如果文件内容有任何变化,生成的 Hash 值也会不同。

这样,文件名不同,浏览器会重新请求最新的文件。在没有使用 Hash 的情况下,如果文件名不变而内容变了,可能会导致缓存污染问题。浏览器可能还会继续使用老版本的文件,导致用户访问的页面无法正确展示新功能或修复的 bug。

通过文件名中的 Hash,可以确保浏览器总是加载最新的资源,避免老版本的缓存文件污染应用。

Hash 值的作用

那既然知道了浏览器会有默认的缓存,当加载静态资源的时候会命中启发式缓存并缓存到本地。那如果我们重新部署前端包的时候,如何去请求新的静态资源呢,而不是缓存的静态资源?这时候就得用到 hash 值了。

下面模拟了掘金网站的静态资源获取,当请求静态资源的时候,实际访问的是服务器中静态资源存放的位置:

20240925093513

返回即是当前请求静态资源的具体内容:

20240925093700

第一次访问时,浏览器会请求服务器的资源并将其缓存到本地,比如 a035f68.js 文件会被缓存到浏览器的磁盘或内存中。接下来,当用户刷新页面时,浏览器会优先从缓存中读取资源(如从 disk cache 或 memory cache),以加快加载速度。

然而,如果前端重新部署后,假设 a035f68.js 这个文件名称保持不变,浏览器就无法知道这个文件已经更新了,因为浏览器默认会使用缓存中的资源,除非缓存已过期或被明确设置为不缓存。这种情况下,浏览器不会主动去请求服务器上的最新资源,导致页面无法加载到最新的内容,影响用户体验。

浏览器的缓存机制是通过资源的文件名来判断的。如果文件名没有发生变化,并且缓存策略允许缓存,且缓存未过期,那么浏览器将直接使用缓存中的资源。相反,如果文件名发生了变化,或者缓存设置要求重新验证资源,浏览器才会去服务器请求最新的静态资源,确保用户看到的是最新的内容。

优化后的描述重点强调了浏览器是通过资源名称、缓存策略和过期时间来判断是否使用缓存还是请求服务器资源的,这也是为什么前端打包后使用带有 Hash 值的文件名来保证资源更新。

第三方库如何处理

对第三方库的 Hash 处理主要涉及缓存优化和避免不必要的重新下载。这类库(如 React、Lodash 等)通常不会频繁更改,因此你希望尽可能利用缓存,但在库版本升级时,确保能获取最新的版本。

为了更好地处理第三方库的 Hash,你可以将第三方库(如 React、Lodash 等)打包到单独的文件中,而不与业务代码混合。通常可以通过 Webpack 的 splitChunks 插件或类似工具将库代码和应用代码分开。这样做的好处是:

  1. 第三方库文件名的 Hash 值只与库的内容相关,而与业务代码无关。

  2. 如果业务代码更新了,而第三方库没有变化,浏览器可以继续使用缓存中的第三方库文件,不必重新下载。

在 Webpack 中,可以通过以下方式配置 splitChunks:

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },
};

这会将第三方库打包成一个单独的 vendors.[hash].js 文件,避免每次业务代码变更时都重新生成第三方库的 Hash。

另一种策略是将常见的第三方库通过 CDN 加载,而不包含在项目的打包文件中。这么做可以让这些库由 CDN 提供缓存,并且减少你本地项目的打包体积。例如,React、Vue、jQuery 等非常稳定的库都可以直接通过 CDN 引入。

在 Webpack 中,使用 externals 来避免将第三方库打包到项目中:

module.exports = {
  externals: {
    react: "React",
    "react-dom": "ReactDOM",
  },
};

在 HTML 文件中通过 CDN 引入:

<script src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js">script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js">script>

这样,React 和 ReactDOM 就会直接从 CDN 加载,不会打包进最终生成的 JS 文件中。

对于第三方库,文件名的 Hash 是基于文件内容生成的,因此库的版本一旦发生变化,Hash 值也会发生变化。为了确保 Hash 稳定且合理,你可以通过锁定第三方库的版本来控制库文件的变化。

在 package.json 中锁定依赖的版本号,例如:

{
  "dependencies": {
    "react": "^17.0.0",
    "lodash": "^4.17.21"
  }
}

使用 package-lock.json 或 yarn.lock 文件确保构建环境的一致性,防止库的版本随意变动,导致每次打包的 Hash 都不一致。

通过锁定库的版本,如果库内容没有变动,Hash 也不会变化,从而浏览器可以继续使用缓存中的版本。

如果第三方库发生了更新(例如,你升级了 React 版本),生成的文件名的 Hash 值自然会发生变化。这时,浏览器会请求新的文件,而不是使用缓存中的旧版本。

这种机制可以确保在你明确升级第三方库时,浏览器会自动加载最新的版本,而不会被缓存机制阻挡。这也是为什么通过文件名 Hash 控制缓存是非常有效的方式:只有文件内容实际改变时,Hash 才会变化,而如果没有更新,文件名就保持不变,缓存继续有效。

总结

前端打包时使用 Hash 值作为静态文件名,主要是为了缓存优化、版本管理和避免缓存污染。当文件内容发生变化时,打包工具会生成不同的 Hash 值,确保文件名唯一,从而强制浏览器加载最新版本的资源,避免加载旧缓存文件引发的问题。同时,如果文件内容没有变化,文件名保持不变,浏览器可以继续使用缓存中的资源,从而减少网络请求,提升加载性能和用户体验。通过这种方式,前端应用可以高效地管理静态资源,保证用户始终访问到最新内容。

最后分享两个我的两个开源项目,它们分别是:

这两个项目都会一直维护的,如果你想参与或者交流学习,可以加我微信 yunmz777 如果你也喜欢,欢迎 star 🚗🚗🚗

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20853,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?