Webpack: 其他性能优化

概述

  • 前面章节我们已经详细探讨 Webpack 中如何使用分包、代码压缩提升应用执行性能。除此之外,还有不少普适、细碎的方法,能够有效降低应用体积,提升网络分发性能,包括:

    • 使用动态加载,减少首屏资源加载量;
    • 使用 externals、Tree-Shaking、Scope Hoisting 特性,减少应用体积;
    • 正确使用 [hash] 占位符,优化 HTTP 资源缓存效率,等等。
  • 下面我们一一展开,解释每条最佳实践以及背后的逻辑。

动态加载

Webpack 默认会将同一个 Entry 下的所有模块全部打包成一个产物文件 —— 包括那些与页面 关键渲染路径 无关的代码,这会导致页面初始化时需要花费多余时间去下载这部分暂时用不上的代码,影响首屏渲染性能,例如:

import someBigMethod from "./someBigMethod";

document.getElementById("someButton").addEventListener("click", () => {
  someBigMethod();
});

逻辑上,直到点击页面的 someButton 按钮时才会调用 someBigMethod 方法,因此这部分代码没必要出现在首屏资源列表中,此时我们可以使用 Webpack 的动态加载功能将该模块更改为异步导入,修改上述代码:

document.getElementById("someButton").addEventListener("click", async () => {
  // 使用 `import("module")` 动态加载模块
  const someBigMethod = await import("./someBigMethod");
  someBigMethod();
});

此时,重新构建将产生额外的产物文件 src_someBigMethod_js.js,这个文件直到执行 import 语句时 —— 也就是上例 someButton 被点击时才被加载到浏览器,也就不会影响到关键渲染路径了。

动态加载是 Webpack 内置能力之一,我们不需要做任何额外配置就可以通过动态导入语句(importrequire.ensure)轻易实现。但请 注意,这一特性有时候反而会带来一些新的性能问题:一是过度使用会使产物变得过度细碎,产物文件过多,运行时 HTTP 通讯次数也会变多,在 HTTP 1.x 环境下这可能反而会降低网络性能,得不偿失;二是使用时 Webpack 需要在客户端注入一大段用于支持动态加载特性的 Runtime
在这里插入图片描述

这段代码即使经过压缩也高达 2.5KB 左右,如果动态导入的代码量少于这段 Runtime 代码的体积,那就完全是一笔赔本买卖了。

因此,请务必慎重,多数情况下我们没必要为小模块使用动态加载能力!目前社区比较常见的用法是配合 SPA 的前端路由能力实现页面级别的动态加载,例如在 Vue 中:

import { createRouter, createWebHashHistory } from "vue-router";

const Home = () => import("./Home.vue");
const Foo = () => import(/* webpackChunkName: "sub-pages" */ "./Foo.vue");
const Bar = () => import(/* webpackChunkName: "sub-pages" */ "./Bar.vue");

// 基础页面
const routes = [
  { path: "/bar", name: "Bar", component: Bar },
  { path: "/foo", name: "Foo", component: Foo },
  { path: "/", name: "Home", component: Home },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;
  • 建议观察开发者工具 Network 面板的网络通讯情况

  • 示例中,Home、Foo、Bar 三个组件均通过 import() 语句动态导入,这使得仅当页面切换到相应路由时才会加载对应组件代码。另外,FooBar 组件的导入语句比较特殊:

    import(/* webpackChunkName: "sub-pages" */ "./Bar.vue");
    
  • webpackChunkName 用于指定该异步模块的 Chunk 名称,相同 Chunk 名称的模块最终会打包在一起,这一特性能帮助开发者将一些关联度较高,或比较细碎的模块合并到同一个产物文件,能够用于管理最终产物数量。

HTTP 缓存优化

注意,Webpack 只是一个工程化构建工具,没有能力决定应用最终在网络分发时的缓存规则,但我们可以调整产物文件的名称(通过 Hash)与内容(通过 Code Splitting),使其更适配 HTTP 持久化缓存策略。下面接着聊聊文件名 Hash 规则

  • 提示:Hash 是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数,不同明文计算出的摘要值不同,所以常常被用作内容唯一标识。

Webpack 提供了一种模板字符串(Template String)能力,用于根据构建情况动态拼接产物文件名称(output.filename),规则稍微有点复杂,但从性能角度看,比较值得关注的是其中的几个 Hash 占位符,包括:

  • [fullhash]:整个项目的内容 Hash 值,项目中任意模块变化都会产生新的 fullhash
  • [chunkhash]:产物对应 Chunk 的 Hash,Chunk 中任意模块变化都会产生新的 chunkhash
  • [contenthash]:产物内容 Hash 值,仅当产物内容发生变化时才会产生新的 contenthash,因此实用性较高。

用法很简单,只需要在 output.filename 值中插入相应占位符即可,如 "[name]-[contenthash].js"。我们来看个完整例子,假设对于下述源码结构:

src/
├── index.css
├── index.js
└── foo.js

之后,使用下述配置:

module.exports = {
  // ...
  entry: { index: "./src/index.js", foo: "./src/foo.js" },
  output: {
    filename: "[name]-[contenthash].js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [new MiniCssExtractPlugin({ filename: "[name]-[contenthash].css" })],
};

示例包含 index.jsfoo.js 两个入口,且分别在 ouput.filenameMiniCssExtractPlugin.filename 中使用 [contenthash] 占位符,最终构建结果:
在这里插入图片描述

  • 提示:也可以通过占位符传入 Hash 位数,如 [contenthash:7] ,即可限定生成的 Hash 长度。

可以看到每个产物文件名都会带上一段由产物内容计算出的唯一 Hash 值,文件内容不变,Hash 也不会变化,这就很适合用作 HTTP 持久缓存 资源:

# HTTP Response header

Cache-Control: max-age=31536000

此时,产物文件不会被重复下载,一直到文件内容发生变化,引起 Hash 变化生成不同 URL 路径之后,才需要请求新的资源文件,能有效提升网络性能,因此,生产环境下应尽量使用 [contenthash] 生成有版本意义的文件名。

Hash 规则很好用,不过有一个边际 Case 需要注意:异步模块变化会引起主 Chunk Hash 同步发生变化,例如对于下面这种模块关系:

在这里插入图片描述

构建后将生成入口 index.js 与异步模块 async-a.js 两个 Chunk 对应的产物:
在这里插入图片描述

此时,若异步模块 async-a 或其子模块 sync-c 发生变化,理论上应该只会影响 src_async-a 的 Hash 值,但实际效果却是:
在这里插入图片描述

父级 Chunk(index)也受到了影响,生成新的 Hash 值,这是因为在 index 中需要记录异步 Chunk 的真实路径:
在这里插入图片描述
异步 Chunk 的路径变化自然也就导致了父级 Chunk 内容变化,此时可以用 optimization.runtimeChunk 将这部分代码抽取为单独的 Runtime Chunk,例如:

module.exports = {
  entry: { index: "./src/index.js" },
  mode: "development",
  devtool: false,
  output: {
    filename: "[name]-[contenthash].js",
    path: path.resolve(__dirname, "dist")
  },
  // 将运行时代码抽取到 `runtime` 文件中
  optimization: { runtimeChunk: { name: "runtime" } },
};
  • 注意: Initial Chunk、Async Chunk、Runtime Chunk 这三种不同的概念

之后,async-a.js 模块的变更只会影响 Runtime Chunk 内容,不再影响主 Chunk

在这里插入图片描述

综上,建议至少为生成环境启动 [contenthash] 功能,并搭配 optimization.runtimeChunk 将运行时代码抽离为单独产物文件。

使用外置依赖

设想一个场景,假如我们手头上有 10 个用 React 构建的 SPA 应用,这 10 个应用都需要各自安装、打包、部署、分发同一套相似的 React 基础依赖,最终用户在访问这些应用时也需要重复加载相同基础包代码,那有没有办法节省这部分流量呢?有 —— 使用 Webpack 的 externals 特性。

externals 的主要作用是将部分模块排除在 Webpack 打包系统之外,例如:

module.exports = {
  // ...
  externals: {
    lodash: "_",
  },
};

使用上述配置后,Webpack 会 预设 运行环境中已经内置 Lodash 库 —— 无论是通过 CDN 还是其它方式注入,所以不需要再将这些模块打包到产物中:
在这里插入图片描述

  • 提示:externals 不仅适用于优化产物性能,在特定环境下还能用于跳过若干运行时模块,例如 Node 中的 fs/net 等,避免将这部分源码错误打包进 Bundle,详情可参考 webpack-node-externals 工具。

注意,使用 externals 时必须确保这些外置依赖代码已经被正确注入到上下文环境中,这在 Web 应用中通常可以通过 CDN 方式实现,例如:

module.exports = {
  // ...
  externals: {
    react: "React",
    lodash: "_",
  },
  plugins: [
    new HtmlWebpackPlugin({
      templateContent: `
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Webpack App</title>
  <script defer crossorigin src="//unpkg.com/react@18/umd/react.development.js"></script>
  <script defer crossorigin src="//unpkg.com/lodash@4.17.21/lodash.min.js"></script>
</head>
<body>
  <div id="app" />
</body>
</html>
  `,
    }),
  ],
};

示例中,externals 声明了 reactlodash 两个外置依赖,并在后续的 html-webpack-plugin 模板中注入这两个模块的 CDN 引用,以此构成完整 Web 应用。

虽然结果上看浏览器还是得消耗这部分流量,但结合 CDN 系统特性,一是能够就近获取资源,缩短网络通讯链路;二是能够将资源分发任务前置到节点服务器,减轻原服务器 QPS 负担;三是用户访问不同站点能共享同一份 CDN 资源副本。所以网络性能效果往往会比重复打包好很多。

使用 Tree-Shaking 删除多余模块导出

Tree-Shaking 较早前由 Rich Harris 在 Rollup 中率先实现,Webpack 自 2.0 版本开始接入,是一种基于 ES Module 规范的 Dead Code Elimination 技术,它会在运行过程中静态分析模块之间的导入导出,判断哪些模块导出值没有被其它模块使用 —— 相当于模块层面的 Dead Code,并将其删除。

在 Webpack 中,启动 Tree Shaking 功能必须同时满足两个条件:

  • 配置 optimization.usedExportstrue,标记模块导入导出列表;
  • 启动代码优化功能,可以通过如下方式实现:
    • 配置 mode = production
    • 配置 optimization.minimize = true
    • 提供 optimization.minimizer 数组

例如:

// webpack.config.js
module.exports = {
  mode: "production",
  optimization: {
    usedExports: true,
  },
};

之后,Webpack 会对所有使用 ESM 方案的模块启动 Tree-Shaking,例如对于下面的代码:

// index.js
import {bar} from './bar';
console.log(bar);

// bar.js
export const bar = 'bar';
export const foo = 'foo';

bar.js 模块导出了 barfoo ,但只有 bar 值被 index 模块使用,经过 Tree Shaking 处理后,foo 变量会被视作无用代码删除,最终有效的代码结构:

// index.js
import {bar} from './bar';
console.log(bar);

// bar.js
export const bar = 'bar';

在后面章节中我们会展开讲解 Tree-Shaking 的实现细节及注意事项,此处先略过。

使用 Scope Hoisting 合并模块

默认情况下 Webpack 会将模块打包成一个个单独的函数,例如:

// common.js
export default "common";

// index.js
import common from './common';
console.log(common);

经过 Webpack 打包后会生成:

"./src/common.js":
  ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
     const __WEBPACK_DEFAULT_EXPORT__ = ("common");
     __webpack_require__.d(__webpack_exports__, {
      /* harmony export */
      "default": () => (__WEBPACK_DEFAULT_EXPORT__)
      /* harmony export */
    });
  }),
"./src/index.js":
  ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
      var _common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./common */ "./src/common.js");
      console.log(_common__WEBPACK_IMPORTED_MODULE_0__)
  })

这种处理方式需要将每一个模块都包裹进一段相似的函数模板代码中,好看是好看,但浪费网络流量啊。为此,Webpack 提供了 Scope Hoisting 功能,用于 将符合条件的多个模块合并到同一个函数空间 中,从而减少产物体积,优化性能。例如上述示例经过 Scope Hoisting 优化后,生成代码:

((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    ;// CONCATENATED MODULE: ./src/common.js
    /* harmony default export */ const common = ("common");
    
    ;// CONCATENATED MODULE: ./src/index.js
    console.log(common);
})

Webpack 提供了三种开启 Scope Hoisting 的方法:

  • 使用 mode = 'production' 开启生产模式;
  • 使用 optimization.concatenateModules 配置项;
  • 直接使用 ModuleConcatenationPlugin 插件。
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
    // 方法1: 将 `mode` 设置为 production,即可开启
    mode: "production",
    // 方法2: 将 `optimization.concatenateModules` 设置为 true
    optimization: {
        concatenateModules: true,
        usedExports: true,
        providedExports: true,
    },
    // 方法3: 直接使用 `ModuleConcatenationPlugin` 插件
    plugins: [new ModuleConcatenationPlugin()]
};

三种方法最终都会调用 ModuleConcatenationPlugin 完成模块分析与合并操作。

与 Tree-Shaking 类似,Scope Hoisting 底层基于 ES Module 方案的 静态特性,推断模块之间的依赖关系,并进一步判断模块与模块能否合并,因此在以下场景下会失效:

非 ESM 模块

遇到 AMD、CMD 一类模块时,由于导入导出内容的动态性,Webpack 无法确保模块合并后不会产生意料之外的副作用,因此会关闭 Scope Hoisting 功能。这一问题在导入 NPM 包尤其常见,许多框架都会自行打包后再上传到 NPM,并且默认导出的是兼容性更佳的 CommonJS 包,因而无法使用 Scope Hoisting 功能,此时可通过 mainFileds 属性尝试引入框架的 ESM 版本:

module.exports = {
  resolve: {
    // 优先使用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
};

模块被多个 Chunk 引用

如果一个模块被多个 Chunk 同时引用,为避免重复打包,Scope Hoisting 同样会失效,例如:

// common.js
export default "common"

// async.js
import common from './common';

// index.js 
import common from './common';
import("./async");

示例中,入口 index.js 与异步模块 async.js 同时依赖 common.js 文件,common.js 无法被合并入任一 Chunk,而是作为生成为单独的作用域,最终打包结果:

 "./src/common.js":
  (() => {
    var __WEBPACK_DEFAULT_EXPORT__ = ("common");
  }),
 "./src/index.js":
  (() => {
    var _common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./common */ "./src/common.js");
    __webpack_require__.e( /*! import() */ "src_async_js").then(__webpack_require__.bind(__webpack_require__, /*! ./async */ "./src/async.js"));
  }),  

监控产物体积

综合最近几章讨论的 Code Splitting、压缩、缓存优化、Tree-Shaking 等技术,不难看出所谓的应用性能优化几乎都与网络有关,这是因为现代计算机网络环境非常复杂、不稳定,虽然有堪比本地磁盘吞吐速度的 5G 网络,但也还存在大量低速 2G、3G 网络用户,整体而言通过网络实现异地数据交换依然是一种相对低效的 IO 手段,有可能成为 Web 应用执行链条中最大的性能瓶颈。

因此,站在生产者角度我们有必要尽可能优化代码在网络上分发的效率,用尽可能少的网络流量交付应用功能。所幸 Webpack 专门为此提供了一套 性能监控方案,当构建生成的产物体积超过阈值时抛出异常警告,以此帮助我们时刻关注资源体积,避免因项目迭代增长带来过大的网络传输,用法:

module.exports = {
  // ...
  performance: {    
    // 设置所有产物体积阈值
    maxAssetSize: 172 * 1024,
    // 设置 entry 产物体积阈值
    maxEntrypointSize: 244 * 1024,
    // 报错方式,支持 `error` | `warning` | false
    hints: "error",
    // 过滤需要监控的文件类型
    assetFilter: function (assetFilename) {
      return assetFilename.endsWith(".js");
    },
  },
};

若此时产物体积超过 172KB,则报错:

在这里插入图片描述

  • 提示:这里的报错不会阻断构建功能, 依然能正常打包出应用产物。

那么我们应该设置多大的阈值呢?这取决于项目具体场景,不过,一个比较好的 经验法则 是确保 关键路径 资源体积始终小于 170KB,超过这个体积就应该使用上面介绍的若干方法做好裁剪优化。

总结

压缩、Tree-Shaking、Scope Hoisting 都在减少产物体积;Code Splitting、外置依赖、[hash] 则有助于提升 HTTP 缓存效率;动态加载则能够确保关键路径最小资源依赖。种种措施各自从不同角度努力优化应用代码在网络上的分发效率,毕竟网络通讯有时候真的很贵!

不过软件世界没有银弹,过度的优化有时候可能带来反效果,建议大家参考 Web Vitals 模型,始终关注、对比应用的线上表现,确保最佳用户体验。

思考,为何 Tree-Shaking 强依赖于 ESM 模块方案?ESM 与其它方案如 AMD、CMD 等,有何区别?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/763519.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Adobe Photoshop 2024 v25.5.1 中文激活版下载以及安装方法教程

软件介绍 Adobe Photoshop 2024 v25.5.1 是Adobe公司的最新版图像处理软件&#xff0c;它提供了强大的图像编辑工具和智能自动化功能&#xff0c;包括图像修复、色彩校正和滤镜效果&#xff0c;以满足专业人士和业余爱好者的需求。这款软件还支持矢量图形设计和实时协作&#…

一维信号短时傅里叶变换域邻域降噪方法(MATLAB)

噪声在人类日常生活中无处不在,其会降低语音信号的质量和可懂度。在低信噪比的恶劣环境中,这种负面影响愈发严重。为了解决这个问题,众多研究人员在过去的几十年里提出了许多降噪算法。 根据原理的不同,降噪算法可大致分为五类:谱减法、最优滤波法、基于统计模型的方法、子空间…

Java案例打印乘法口诀表,三角形

目录 一问题&#xff1a; ​编辑二代码&#xff1a; 三运行结果&#xff1a; 四问题 二代码&#xff1a; 三运行结果&#xff1a; 一问题&#xff1a; 二代码&#xff1a; package 重修;import java.util.Random; import java.util.Scanner;public class first {public …

IDEA中Java源文件编译后class文件中文乱码

文章目录 一、设置 一、设置 路径&#xff1a;File -> Settings -> Bulid, Execution,Deployment -> Compiler -> Java Compiler

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的生日聚会(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

WordPress网站如何做超级菜单(Mega Menu)?

大多数的网站菜单都是像以下这种条状的形式&#xff1a; 这种形式的是比较中规中矩的&#xff0c;大多数网站都在用的。当然还有另外一种菜单的表现形式&#xff0c;我们通常叫做“超级菜单”简称Mega Menu。网站的超级菜单&#xff08;Mega Menu&#xff09;是一种扩展的菜单&…

前端基础:CSS(篇一)

目录 css概述 CSS与HTML的关系 基本语法 行内样式表 代码 运行 内嵌样式表 代码 运行 外部样式表 代码 运行 选择器 标签选择器 代码 运行 id选择器 代码 运行 类选择器 代码 运行 选择器优先问题 通配选择器 选中所有的标签 代码 运行 选择器组…

2-22 基于matlab的NSGA-2求解多目标柔性车间调度算法

基于matlab的NSGA-2求解多目标柔性车间调度算法,计算最大完工时间、计算总延期时长、计算调度方案的总能耗、计算设备总负荷。输出四项结果&#xff0c;多次运行可寻找最佳的调度计划。程序已调通&#xff0c;可直接运行。 2-22 NSGA-2求解多目标柔性车间调度算法 - 小红书 (xi…

Java - 程序员面试笔记记录 实现 - Part2

2.1 输入输出流 流可以被看作一组有序的字节集合&#xff0c;即数据在两个设备间的传输。 字节流&#xff1a;以字节作为单位&#xff0c;读到一个字节就返回一个字节&#xff1b;InputStream & OutputStream。 字符流&#xff1a;使用字节流读到一个到多个字节先查询码…

传输距离3000M|低延迟|48K采样音频传输模块-SA356大功率发射模块

无线音频应用中&#xff0c;远距离音频传输在许多领域具有广泛的应用需求&#xff0c;例如大型会议系统、公共广播、户外活动和音乐演出等。为了满足这些需求&#xff0c;音频传输模块需要具备一些关键特性&#xff0c;包括长距离传输能力、高音质、低延迟、稳定性以及抗干扰能…

【第11章】MyBatis-Plus条件构造器(上)

文章目录 前言一、功能详解1. allEq2. eq3. ne4. gt5. ge6. lt7. le8. between9. notBetween10. like11. notLike12. likeLeft13. likeRight14. notLikeLeft15. notLikeRight16. isNull17. in18. notIn19. inSql20. notInSql21. eqSqlSince 3.5.622. gtSql Since 3.4.3.223. ge…

【CentOS7.6】yum 报错:Could not retrieve mirrorlist http://mirrorlist.centos.org

一、报错 1.报错内容如下 在使用 yum makecache 命令时报错&#xff0c;在 yum install -y xxx 的时候报错等等 [roothcss-ecs-a901 yum.repos.d]# yum makecache Loaded plugins: fastestmirror Determining fastest mirrors Could not retrieve mirrorlist http://mirrorl…

【鸿蒙学习笔记】Column迭代完备

属性含义介绍 Column({ space: 10 }) {Row() {Text(文本描述).size({ width: 80%, height: 60 }).backgroundColor(Color.Red)}.width(90%).height(90).backgroundColor(Color.Yellow) } .width(100%) // 宽度 .height(200) // 高度 .backgroundColor(Color.Pink) // 背景色 .…

【深圳大学算法设计与分析】 实验六 最大流应用问题 FF -> EK -> Dinic

目录 一、实验目的&#xff1a; 二、内容&#xff1a;棒球赛问题 三、实验要求 四、提交要求 ———————— 问题分析解释&#xff1a; ———————— 算法简解&#xff1a; Ford–Fulkerson 增广 Edmonds–Karp 算法 Dinic算法 Dinic和EK的区别&#xff1a; …

STM32第十四课:低功耗模式和RTC实时时钟

文章目录 需求一、低功耗模式1.睡眠模式2.停止模式3.待机模式 二、RTC实现实时时钟1.寄存器配置流程2.标准库开发3.主函数调用 三、需求实现代码 需求 1.实现睡眠模式、停止模式和待机模式。 2.实现RTC实时时间显示。 一、低功耗模式 电源对电子设备的重要性不言而喻&#xff…

【程序大侠传】异步架构应用回调数据接收接口偶发NPE

前序 在这片浩瀚的代码江湖中&#xff0c;各大门派林立&#xff0c;各自修炼独门绝技&#xff0c;江湖中的侠士们分别担任着开发、测试、产品和运维的角色&#xff0c;共同守护着这片数字化的疆域。 开发门派&#xff1a;代码剑宗 代码剑宗的弟子们精通各种编程语言&#xff…

【嵌入式】探索嵌入式世界:在ARM上构建俄罗斯方块游戏的奇妙之旅

文章目录 前言&#xff1a;1. 简介2. 总体设计思路及功能描述2.1 设计思路2.2 功能描述2.3 程序流程图 3. 各部分程序功能及详细说明3.1 游戏界面函数3.1.1 游戏界面中的图片显示3.1.2 游戏开始界面3.1.3 游戏主界面3.1.4 游戏结束广告界面3.1.5 游戏界面中的触摸反馈3.1.6 游戏…

【Spring Boot】基于 JPA 开发的文章管理系统(CRUD)

基于 JPA 开发的文章管理系统&#xff08;CRUD&#xff09; 1.实现文章实体2.实现数据持久层3.实现服务接口和服务接口的实现类3.1 创建服务接口3.2 编写服务接口的实现 4.实现增、删、改、查的控制层 API 功能4.1 获取文章列表4.2 根据 id 获取文章对象4.3 新增4.4 保存4.5 删…

第三届环境工程与可持续能源国际会议(EESE 2024)

随着全球气候变化和环境问题日益严峻&#xff0c;环境工程与可持续能源领域的研究和发展显得尤为重要。第三届环境工程与可持续能源国际会议&#xff08;EESE 2024&#xff09;作为这一领域的重要交流平台&#xff0c;将于2024年10月25日至27日在湖南长沙盛大召开。本次会议将汇…

算法实验2.2、2.3

2.2主要内容 比较快速排序&#xff0c;归并排序以及堆排序算法的时间效率。了解影响算法执行时间的 主要因素以及如何降低算法的执行时间。 #include<iostream> using namespace std; #include<stdio.h> #include<malloc.h> #include<stdlib.h> #inc…