跳到主要内容

FAQ?

semver规范

SemVer规范是指“语义化版本”规范,它是一种版本标记约定,旨在使软件版本号更加清晰、有意义和易于比较。

SemVer 标准的版本格式为 MAJOR.MINOR.PATCH。在这个版本号格式中:

MAJOR 表示主版本号,定义了不向后兼容的 API 或功能改变; MINOR 表示次版本号,定义了向后兼容的新功能; PATCH 表示修订版本号,定义了向后兼容的问题修正。 除了这三个标准号码外, SemVer 还支持在版本号后添加预发布信息和版本元信息。预发布信息是指版本号后面的连字符和标识符(如 1.0.0-alpha.1),用于标识尚处于开发或测试阶段的软件版本。版本元数据是指版本号后面的加号和标识符(例如 1.2.3+build.1234),用于为版本号添加附加信息,如构建 ID 或 Git SHA-1 校验和等。

SemVer 标准还定义了版本号比较规则,使得版本号可以按照严格的语义化方式进行比较。例如,一个版本号大于另一个版本号,当且仅当它的主版本号更高、次版本号相等的情况下,或者它的主版本号相等、次版本号更高,修订版本号相等的情况下,或者它的主版本号、次版本号相等、修订版本号更高的情况下。

在软件的开发、维护和分发过程中,使用 SemVer 标准来标记版本号,可以使得版本控制更加清晰、可靠和可预测。

如何写一个babel插件?

Babel 插件本质上是一个函数,它返回一个带有 visitor 属性的对象,visitor 用于遍历和操作抽象语法树(AST)。编写一个 Babel 插件的基本流程如下:

1. 基本结构

一个 Babel 插件的基本结构如下:

module.exports = function (babel) {
const { types: t } = babel;
return {
name: 'my-babel-plugin',
visitor: {
// 在这里定义对 AST 节点的访问逻辑
}
};
};
  • babel.types:提供构造、校验 AST 节点的工具方法(通常简写为 t)。
  • visitor:访问器对象,键名为 AST 节点类型(如 IdentifierFunctionDeclaration),值为处理函数。
  • name:插件名称(Babel 7 推荐填写)。

2. 一个完整示例:将变量名 foo 改为 bar

module.exports = function ({ types: t }) {
return {
name: 'rename-foo-to-bar',
visitor: {
Identifier(path) {
if (path.node.name === 'foo') {
path.replaceWith(t.identifier('bar'));
}
}
}
};
};

3. path 对象常用方法

  • path.node:获取当前节点。
  • path.replaceWith(node):替换当前节点。
  • path.remove():删除当前节点。
  • path.insertAfter(node) / path.insertBefore(node):在当前节点后/前插入节点。
  • path.parentPath:访问父节点。
  • path.skip():跳过子节点的遍历。

4. 插件配置项

插件可以通过 options 接收配置:

// babel.config.js
module.exports = {
plugins: [
['./my-plugin', { from: 'foo', to: 'bar' }]
]
};

// plugin
module.exports = function ({ types: t }, opts) {
return {
visitor: {
Identifier(path) {
if (path.node.name === opts.from) {
path.replaceWith(t.identifier(opts.to));
}
}
}
};
};

5. 调试与测试

  • 使用 @babel/parser 将源代码解析为 AST 进行查看:parser.parse(code, { sourceType: 'module' })
  • 使用 babel-plugin-tester@babel/coretransformSync 编写测试。
  • 借助 AST Explorer 直观查看节点结构,是编写插件的重要工具。

6. 注意事项

  • 节点的修改应该通过 path API 操作,而不是直接修改 path.node 的属性(除非明确需要)。
  • 创建节点要使用 t.xxx(),避免直接写对象字面量,以确保 type 等字段正确。
  • 如果只是读取分析,可在 visitor 中调用 path.skip() 以提升性能。

webpack和gulp区别(模块化与流的区别)

Webpack 是一个模块化打包工具。它的主要功能是分析项目中的模块依赖关系,然后将这些模块打包成一些浏览器可以直接使用的静态资源文件(例如 JavaScript、CSS、图片等)。Webpack 非常适合用于构建大型的、模块化的 Web 应用程序,它可以自动化打包和压缩代码,并提供一些常用插件和工具库,以便于开发者进行高效的开发、测试和部署。

相比之下,Gulp 是一个流式构建工具,它通过指定一系列任务来处理项目中的源文件。每一个任务都以源文件和目标文件为输入和输出,通过流的方式将数据从一个任务传递到另一个任务。Gulp 对于处理复杂的任务和构建管道非常有用。它提供了大量的插件和工具库,可以帮助开发者解决常见的 Web 开发任务,例如编译 Sass、压缩图片、打包 CSS 和 JavaScript 等。

总之,Webpack 适合处理模块化的 Web 应用程序,而 Gulp 适合处理流式的 Web 开发任务,例如代码压缩、图片优化等。在实际项目中,可以根据实际情况选择合适的工具,或者将它们结合起来使用,以便于更高效地构建和维护 Web 应用程序。

SWC和babel的区别

SWC和Babel都是JavaScript编译器,但它们之间仍有一些区别。

SWC代表Superfast WebAssembly Compiler。它是一个较新的编译器,专为速度而设计。它使用WebAssembly进行编译,并且可以处理大多数Babel可以处理的相同类型的语法。SWC还内置了许多传统编译器中存在的优化,可以提供更快和更紧凑的输出。

Babel是更成熟的编译器,已经存在了很长时间。 它使用JavaScript进行编译,并且可以处理的语法比SWC更广泛。 Babel具有非常活跃的社区和生态系统,支持各种插件和配置选项。

因此,SWC适用于需要快速编译速度的项目和对输出大小有要求的项目,而Babel适用于对语法支持更广泛,有更多的可扩展性和适应性的项目。

选mocha还是jest

Mocha:

  • Mocha 是一个灵活的测试框架,它提供了强大的功能和丰富的插件生态系统。
  • Mocha 不依赖于断言库或模拟库,这使得您可以根据自己的喜好选择适合的工具。
  • Mocha 支持异步测试,包括使用回调函数、Promises 或 async/await。
  • Mocha 对于编写大型测试套件和复杂的测试场景非常适用。

Jest:

  • Jest 是一个全功能的 JavaScript 测试框架,提供了内置的断言库、模拟功能和覆盖率报告。
  • Jest 具有简单的设置和使用,适合于快速启动项目和编写小型测试套件。
  • Jest 具有零配置的模拟功能,使得模拟复杂的依赖关系变得更加容易。
  • Jest 集成了覆盖率报告,并提供易于理解的可视化报告。

根据项目的特点和需求,您可以考虑以下因素来做出选择:

  • 项目规模和复杂度:如果您的项目很大且复杂,涉及许多异步操作和定制需求,那么 Mocha 可能更适合。
  • 快速启动和简单性:如果您希望快速启动测试并且不想花费太多时间进行配置,Jest 可能更适合。
  • 插件和生态系统:根据您可能需要的插件和扩展功能,了解 Mocha 和 Jest 的生态系统,以确定哪个框架提供更好的支持。
  • 社区和支持:查看每个框架的社区支持、更新频率和文档质量,这些因素都可以对选择产生影响。

无论您选择 Mocha 还是 Jest,重要的是在项目中使用适合您团队和需求的测试框架,并编写高质量的测试来确保代码的可靠性和稳定性。

本地开发端口映射

一般来说,如果你想在本地开发环境中进行端口映射,可以尝试以下几种方法:

  1. 使用代理服务器:你可以在本地启动一个代理服务器,将请求转发到指定的端口。常见的代理服务器工具有nginxhttp-proxy-middleware等,你可以根据自己的需求选择合适的工具进行配置。

  2. 使用反向代理:在一些开发服务器工具(如webpack-dev-serverhttp-server等)中,提供了反向代理的功能,你可以通过配置反向代理来实现端口映射。

  3. 使用浏览器插件:有一些浏览器插件可以帮助你实现端口映射,比如 Chrome 浏览器中的插件 SwitchyOmega 或者 Proxy SwitchySharp

MVVM、MVC、MVP 的区别

MVVM、MVC、MVP 是三种常见的软件架构模式,用于组织和管理前端或后端应用程序的代码。它们的区别如下:

  1. MVC(Model-View-Controller):

    • Model(模型):表示数据和业务逻辑。
    • View(视图):负责展示数据,与用户进行交互。
    • Controller(控制器):处理用户输入,更新模型和视图之间的关系。
    • 特点:模型和视图是分离的,通过控制器进行交互。主要用于后端开发,将应用程序分为三个不同的部分,每个部分有各自的职责。
  2. MVP(Model-View-Presenter):

    • Model(模型):表示数据和业务逻辑。
    • View(视图):负责展示数据,与用户进行交互。
    • Presenter(展示器):从模型中获取数据,并将数据传递给视图。处理视图的事件和用户输入。
    • 特点:Presenter 充当了控制器和视图之间的中介角色,将数据的获取和处理逻辑从视图中分离出来。主要用于前端开发。
  3. MVVM(Model-View-ViewModel):

    • Model(模型):表示数据和业务逻辑。
    • View(视图):负责展示数据,与用户进行交互。
    • ViewModel(视图模型):连接视图和模型,负责处理视图和模型之间的数据绑定、事件处理等。
    • 特点:视图和模型之间通过双向数据绑定实现自动更新。ViewModel 将视图的状态和行为抽象为数据模型,使得视图和模型之间的耦合度降低。主要用于前端开发。

总结:

  • MVC 是将应用程序分为模型、视图和控制器,主要用于后端开发。
  • MVP 将模型和视图分离,并通过展示器进行交互,主要用于前端开发。
  • MVVM 在 MVP 的基础上引入了双向数据绑定,将视图和模型的关系进一步解耦。

这些架构模式旨在提供一种组织代码的方式,以实现可维护、可扩展和可测试的应用程序。具体选择哪种架构模式取决于应用程序的需求、开发团队的偏好以及技术栈的限制。

webpack 与 grunt、gulp 的不同?

Grunt、Gulp 是基于任务运⾏的⼯具: 它们会⾃动执⾏指定的任务, 就像流⽔线,把资源放上去然后通过不同插件进⾏加⼯,它们包含活 跃的社区,丰富的插件,能⽅便的打造各种⼯作流。 Webpack 是基于模块化打包的⼯具: ⾃动化处理模块,webpack 把⼀ 切当成模块,当 webpack 处理应⽤程序时,它会递归地构建⼀个依 赖关系图 (dependency graph),其中包含应⽤程序需要的每个模块, 然后将所有这些模块打包成⼀个或多个 bundle。 因此这是完全不同的两类⼯具,⽽现在主流的⽅式是⽤npm script 代 替 Grunt、Gulp,npm script 同样可以打造任务流。

webpack、rollup、parcel 优劣?

webpack 适⽤于⼤型复杂的前端站点构建: webpack 有强⼤的 loader 和插件⽣态,打包后的⽂件实际上就是⼀个⽴即执⾏函数,这个⽴即 执⾏函数接收⼀个参数,这个参数是模块对象,键为各个模块的路径, 值为模块内容。⽴即执⾏函数内部则处理模块之间的引⽤,执⾏模块 等,这种情况更适合⽂件依赖复杂的应⽤开发。

rollup 适⽤于基础库的打包,如 vue、d3 等: Rollup 就是将各个模 块打包进⼀个⽂件中,并且通过 Tree-shaking 来删除⽆⽤的代码, 可以最⼤程度上降低代码体积,但是rollup没有webpack如此多的的 如代码分割、按需加载等⾼级功能,其更聚焦于库的打包,因此更适 合库的开发。

parcel 适⽤于简单的实验性项⽬: 他可以满⾜低⻔槛的快速看到效 果,但是⽣态差、报错信息不够全⾯都是他的硬伤,除了⼀些玩具项 ⽬或者实验项⽬不建议使⽤。

有哪些常⻅的 Loader?

  1. babel-loader:用于将 ES6+ 代码转换为兼容的 JavaScript 代码,以便在旧版本浏览器中运行。

  2. css-loader:用于加载和处理 CSS 文件,支持处理 CSS 文件中的 @importurl() 引用。

  3. style-loader:将 CSS 代码注入到页面的 <style> 标签中,使其生效。

  4. sass-loader:用于加载和处理 SASS/SCSS 文件,将其转换为 CSS 代码。

  5. less-loader:用于加载和处理 LESS 文件,将其转换为 CSS 代码。

  6. file-loader:用于处理文件资源(如图片、字体等),根据配置将文件移动到输出目录,并返回文件的路径。

  7. url-loader:与 file-loader 类似,但还可以根据文件大小将文件转换为 Base64 字符串嵌入到代码中,以减少 HTTP 请求。

  8. eslint-loader:用于在构建过程中进行代码的静态检查,可以配合 ESLint 使用。

  9. ts-loader:用于将 TypeScript 代码转换为 JavaScript 代码。

  10. postcss-loader:使用 PostCSS 对 CSS 进行后处理,可以实现自动添加前缀、压缩等功能。

有哪些常⻅的 Plugin?

  1. HtmlWebpackPlugin:用于生成 HTML 文件,并自动将生成的 bundle 文件注入到 HTML 文件中。

  2. MiniCssExtractPlugin:将 CSS 从 JavaScript 中提取出来,生成单独的 CSS 文件。

  3. CleanWebpackPlugin:在每次构建前清理输出目录。

  4. CopyWebpackPlugin:用于复制文件或文件夹到构建目录。

  5. DefinePlugin:允许在代码中定义全局常量,可以在开发和生产环境下使用不同的配置。

  6. HotModuleReplacementPlugin:启用热模块替换功能,使代码更新后能够在浏览器中实时展示,无需刷新页面。

  7. ProvidePlugin:自动加载模块,将某些模块作为全局变量在所有模块中可用。

  8. webpack-bundle-analyzer:分析打包结果,可视化地展示每个模块的大小和依赖关系,帮助优化构建产物。

  9. CompressionWebpackPlugin:在构建过程中对静态资源进行压缩,以减小文件体积,提高加载速度。

  10. ImageminWebpackPlugin:用于压缩图片资源,减小图片文件的大小。

bundle,chunk,module 是什么?

在 webpack 中,bundle、chunk 和 module 是三个重要的概念,它们分别代表着不同的概念和组织方式:

  1. Bundle(打包文件):一个 bundle 是由 webpack 构建出来的最终文件,它包含了应用程序的代码和资源。在开发过程中,可以将一个应用程序分割成多个 bundle,每个 bundle 包含一组相关的模块。

  2. Chunk(代码块):一个 chunk 是 webpack 在打包过程中的中间产物,它表示着一个或多个模块的集合。Webpack 根据入口文件和代码的依赖关系,将代码分割成多个 chunk。在默认配置下,一个入口文件对应一个 chunk。

  3. Module(模块):一个 module 是指应用程序中的一个模块,可以是一个 JavaScript 文件、一个 CSS 文件、一个图片文件等。Webpack 将应用程序中的所有文件都视为模块,并对它们进行处理和组织。

总结来说,module 是 webpack 对应用程序中每个文件的抽象,chunk 是在构建过程中的中间产物,它表示着一组相关的模块,而 bundle 是最终构建出来的文件,它包含了应用程序的代码和资源。在实际开发中,可以通过配置 entry、output、splitChunks 等选项来控制 bundle 和 chunk 的生成和组织方式,以满足项目的需求和优化目标。

Loader 和 Plugin 的不同?

Loader 和 Plugin 是 webpack 中两个不同的概念,它们在构建过程中扮演不同的角色:

  1. Loader(加载器):Loader 用于告诉 webpack 如何处理非 JavaScript 文件(模块)。它们作为 webpack 构建过程中的转换器,将不同类型的文件转换为模块,使得这些文件能够被应用程序所使用。Loader 在模块的加载阶段被执行,可以对模块的源代码进行处理,例如将 ES6+ 代码转换为 ES5、处理 CSS、处理图片等。Loader 在 webpack 的配置中以函数或字符串的形式进行配置。

  2. Plugin(插件):Plugin 用于扩展 webpack 的功能,它通过在构建过程的不同阶段插入自定义逻辑,来完成各种额外的任务。插件可以用于资源的优化、文件的生成、环境变量的注入、代码的分析等。Plugin 通过 webpack 的插件系统实现,它可以监听 webpack 构建过程中的事件,并在适当的时机执行自定义的逻辑。Plugin 的配置项是一个实例化对象。

总结来说,Loader 主要处理模块的转换和加载,而 Plugin 则用于扩展 webpack 的功能,添加额外的处理逻辑。Loader 和 Plugin 在 webpack 的配置文件中通过不同的配置项进行配置和使用。通过合理使用 Loader 和 Plugin,可以实现对不同类型文件的处理和构建过程的扩展,以满足项目的需求和优化目标。

webpack 热更新的实现原理?

webpack 的热更新(Hot Module Replacement,HMR)是一种在开发过程中实现实时更新代码和模块的机制。它能够使得在不刷新整个页面的情况下,将修改的代码和模块部分更新到正在运行的应用程序中。

以下是 webpack 热更新的基本实现原理:

  1. Webpack Dev Server:webpack-dev-server 是一个基于 Express 的开发服务器,它使用了 webpack 的编译和打包能力,配合热更新实现了实时刷新和模块热替换的功能。

  2. HMR Runtime:Webpack 在编译过程中会生成用于热更新的 HMR Runtime 代码。这段代码会注入到客户端应用程序中,负责处理热更新的逻辑。

  3. WebSocket 通信:Webpack Dev Server 和客户端之间通过 WebSocket 建立长连接,实现实时的双向通信。

  4. 构建更新的代码块:当一个模块发生变化时,Webpack Dev Server 会重新编译该模块,并生成一个更新的代码块。

  5. 发送更新通知:Webpack Dev Server 通过 WebSocket 向客户端发送更新通知,告知客户端有哪些模块发生了变化。

  6. 客户端更新处理:客户端收到更新通知后,根据更新的代码块信息,使用 HMR Runtime 在运行时更新相应的模块。

  7. 应用程序更新:经过热更新处理后,客户端应用程序的界面和状态会实时更新,而不需要刷新整个页面。

总结来说,webpack 的热更新通过与 Webpack Dev Server 的配合,使用 HMR Runtime 和 WebSocket 实现了实时刷新和模块热替换的功能。它能够提高开发效率,减少开发过程中的刷新和重建时间,同时保持应用程序的状态和用户交互。开发者可以在 webpack 的配置中启用热更新功能,以便在开发过程中更快地进行代码修改和调试。

Babel 的原理是什么?

Babel 是一个广泛使用的 JavaScript 编译器,用于将高版本的 JavaScript 代码转换为向后兼容的低版本代码。它的主要原理可以总结为以下几个步骤:

  1. 解析(Parsing):Babel 首先将输入的源代码解析为抽象语法树(AST),它会将代码解析成一种结构化的数据表示形式,以便于后续的处理和转换。

  2. 转换(Transformation):Babel 将 AST 进行遍历,并根据预先定义的插件或预设规则,对其中的节点进行转换。这些转换可以是将新语法转换为旧版本的 JavaScript 代码,应用编译器的优化规则,添加特定平台的兼容性补丁等。转换过程中可以根据需要进行增删改查的操作,使得开发者可以自定义需要的转换逻辑。

  3. 生成(Code Generation):经过转换后,Babel 将修改后的 AST 重新生成为目标代码,即转换后的 JavaScript 代码。生成的过程中会将 AST 转化为字符串形式的代码,并进行必要的格式化和缩进等处理,最终生成输出文件。

Babel 的核心原理是通过解析源代码、应用一系列插件或预设规则,对 AST 进行转换,并最终生成目标代码。通过这个过程,开发者可以使用最新的 JavaScript 特性和语法,而不必担心兼容性问题,因为 Babel 能够将其转换为较低版本的 JavaScript 代码,以确保在不同的浏览器或环境中都能够正常运行。Babel 的灵活性和可扩展性使得它成为了现代 JavaScript 开发中不可或缺的工具之一。

使用缓存注意事项

在使用缓存时,以下是一些需要注意的重要事项:

  1. 缓存一致性:确保缓存中的数据与源数据的一致性。如果源数据发生了变化,缓存应该及时更新,以避免使用过期或无效的数据。

  2. 缓存策略:选择合适的缓存策略以满足应用需求。不同的数据可能需要不同的缓存策略,包括缓存的过期时间、缓存更新机制等。

  3. 缓存失效处理:当缓存失效时,需要考虑如何处理。可以重新加载数据,更新缓存,或者向用户显示错误信息。确保在缓存失效时能够正确处理,以避免不一致的数据或错误的结果。

  4. 缓存容量限制:对缓存进行合理的容量管理,避免过多的数据存储在缓存中导致内存占用过大。可以考虑使用淘汰策略,如最近最少使用(LRU)等,来管理缓存中的数据。

  5. 缓存安全性:对于敏感数据或对安全性要求较高的数据,需要谨慎处理缓存。确保适当的数据加密、访问控制和权限验证,以保护缓存中的数据安全。

  6. 缓存的性能测试和监控:定期进行性能测试和监控,以确保缓存机制对系统性能的实际提升,并及时发现和解决缓存相关的性能问题。

  7. 清理过期缓存:定期清理过期的缓存,以释放内存资源并确保缓存的及时更新。

  8. 考虑缓存击穿和缓存雪崩:缓存击穿是指在高并发情况下,一个缓存失效导致大量请求直接访问后端数据库或服务,造成性能问题。缓存雪崩是指缓存中大量数据同时过期,导致大量请求直接访问后端,造成系统崩溃。需要采取措施防止和应对这些问题,如使用互斥锁、设置合适的缓存过期时间等。

总之,使用缓存需要综合考虑数据一致性、缓存策略、缓存失效处理、安全性、性能等方面的因素。合理而谨慎地使用缓存,可以显著提高系统性能和用户体验。

oss 和 cdn 什么区别

阿里云 OSS(Object Storage Service)和 CDN(Content Delivery Network)是两种不同的云服务,用于不同的用途,但在某些情况下它们可以一起使用来提供更好的性能和用户体验。

OSS(Object Storage Service):

OSS 是一个用于存储和管理对象(如文件、图片、视频等)的云存储服务。它的主要特点是高可用性、可扩展性和安全性。你可以将各种类型的文件上传到 OSS 存储桶中,并通过生成的 URL 进行访问。OSS 适合用于数据的长期存储、备份、归档等需求。

CDN(Content Delivery Network):

CDN 是一种用于加速内容传输的网络架构。CDN 的主要目标是提高用户访问网站或应用时的加载速度和响应时间。CDN 通过在全球分布的节点上缓存内容(如图片、CSS、JavaScript 等静态资源),使用户能够从离他们更近的节点访问这些资源,从而减少了加载时间。CDN 还可以通过减少源服务器的负载,提高整体的可用性和稳定性。

区别:

  1. 用途不同: OSS 主要用于存储和管理数据对象,而 CDN 主要用于加速静态资源的分发。

  2. 数据存储 vs. 加速分发: OSS 提供数据存储服务,将数据存储在云端;CDN 提供数据加速分发服务,将静态资源缓存在分布式节点上,加速用户访问。

  3. 数据类型: OSS 可以存储各种类型的对象数据,而 CDN 主要用于缓存静态资源,如图片、CSS、JavaScript 等。

  4. 定价模式: OSS 通常按照存储容量和网络流量计费,而 CDN 则按照流量计费。

  5. 协同使用: 在一些场景下,你可以将 OSS 和 CDN 结合使用,通过将存储在 OSS 中的静态资源缓存到 CDN 节点上,从而更有效地提供内容加速。

总之,OSS 和 CDN 是两种不同的服务,分别用于数据存储和内容加速,但它们可以在某些情况下协同使用,以提供更好的性能和用户体验。

Spring Boot开发中,经常听到的PO、VO、DAO、BO、DTO、POJO到底是什么?

这些是 Java/Spring 开发中常见的分层模型与对象命名约定,用于在不同层级中区分对象的职责:

1. POJO(Plain Ordinary Java Object)

简单传统的 Java 对象,没有继承任何类、没有实现任何接口、没有任何注解。是其他概念的基础,强调“简单”。

2. PO(Persistent Object)

持久化对象,与数据库表结构一一对应。通常一个表对应一个 PO,字段与表的列对应。常见于 ORM 框架(如 MyBatis、Hibernate)映射的结果对象。

public class UserPO {
private Long id;
private String name;
private Date createdAt;
// getter / setter
}

3. DAO(Data Access Object)

数据访问对象,封装对数据库的访问逻辑。它对外提供接口,内部实现 CRUD,隐藏底层数据源细节。

public interface UserDAO {
UserPO findById(Long id);
void insert(UserPO user);
}

4. DTO(Data Transfer Object)

数据传输对象,用于服务层与控制层之间、或不同微服务之间的数据传输。只包含传输所需字段,避免把 PO 直接暴露造成字段泄露或过度耦合。

public class UserDTO {
private Long id;
private String name;
}

5. BO(Business Object)

业务对象,封装业务逻辑和业务状态。通常由多个 PO/DTO 组合而成,承载一个完整的业务概念。

public class OrderBO {
private OrderPO order;
private List<ItemBO> items;
private BigDecimal totalPrice; // 计算逻辑
}

6. VO(View Object / Value Object)

  • View Object:视图对象,专门用于向前端/视图层返回数据,通常经过裁剪和格式化。
  • Value Object:值对象(DDD 概念),无唯一标识、不可变,如 MoneyAddress
public class UserVO {
private Long id;
private String displayName;
}

总结表

缩写全称主要用途
POJOPlain Ordinary Java Object基础简单对象
POPersistent Object与数据库表对应
DAOData Access Object封装数据库访问
DTOData Transfer Object层间/服务间数据传输
BOBusiness Object封装业务逻辑
VOView Object / Value Object视图返回 / 值对象

典型流转数据库 → PO → DAO → Service(BO) → Controller(DTO/VO) → 前端

多线程和多进程哪个更高效

不能简单地说谁更高效,要结合任务类型、资源开销、并发模型等因素综合判断。

1. 关键区别

维度多进程多线程
内存空间独立地址空间,不共享共享同一进程的内存
创建/切换开销大(需复制进程上下文)小(仅切换栈和寄存器)
通信成本高(管道、消息队列、共享内存、Socket)低(直接读写共享变量)
稳定性一个进程崩溃不影响其他一个线程崩溃可能导致整个进程退出
并发数较少(受进程开销限制)较多
GIL(Python)不受 GIL 影响,可真正并行 CPU受 GIL 限制,CPU 密集型无法并行

2. CPU 密集型任务

  • 计算、压缩、加密、视频编码等场景。
  • 多进程更能利用多核 CPU 真正并行计算。在 Python 等有 GIL 的语言中,多线程无法并行执行 CPU 密集任务,应选多进程。
  • Node.js 本身单线程,可通过 worker_threads 启用多线程,或 child_process 启用多进程。

3. I/O 密集型任务

  • 网络、磁盘、数据库等场景。
  • 由于大部分时间在等待 I/O,多线程/协程已足够高效,切换开销更小。
  • Node.js 的事件循环、Go 的 goroutine、Python 的 asyncio 都是针对 I/O 密集场景的高并发方案。

4. 实际选择建议

  • 优先多线程/协程:I/O 密集场景、需要大量并发连接、共享内存较多。
  • 优先多进程:CPU 密集且需要真正并行、对稳定性要求高(一个崩溃不影响其他)、隔离沙箱(如浏览器多进程、Node.js cluster)。
  • 混合模型:如 Nginx 多进程 + 每进程内异步 I/O;浏览器多进程(每 tab 一个进程)+ 进程内多线程。

5. 结论

没有绝对的高效,只有适合的场景。多线程创建/切换开销小、通信方便,更适合 I/O 密集与轻量任务;多进程隔离性好、能真正利用多核,更适合 CPU 密集与高稳定性场景。

进程线程死锁

死锁(Deadlock)是指两个或两个以上的进程/线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力干预,它们都无法推进。

1. 死锁产生的四个必要条件(缺一不可)

  1. 互斥条件:资源在某一时刻只能被一个进程/线程占有。
  2. 请求与保持条件:进程已占有部分资源,又请求新资源,在等待时不释放已占资源。
  3. 不剥夺条件:进程已获得的资源不能被强行剥夺,只能主动释放。
  4. 循环等待条件:存在一个进程-资源的环形等待链。

2. 示例

线程 A 持有锁 1,请求锁 2;线程 B 持有锁 2,请求锁 1。两者互相等待,形成死锁。

// Node.js / Java 伪代码
// 线程 A
lock1.acquire();
// ... 此时线程 B 已经拿到 lock2
lock2.acquire(); // 阻塞等待

// 线程 B
lock2.acquire();
lock1.acquire(); // 阻塞等待

3. 预防策略(破坏四个条件之一)

  • 破坏请求与保持:一次性申请所有需要的资源。
  • 破坏不剥夺:申请不到新资源时主动释放已占资源(活锁风险)。
  • 破坏循环等待:对资源进行有序编号,按序申请。
  • 资源静态分配:进程开始前一次性分配,运行期间不再申请新资源。

4. 避免策略(运行时判断)

  • 银行家算法:在分配前判断分配后系统是否处于安全状态,只在安全状态下才分配。
  • 需要知道进程的最大资源需求,开销较大,实际应用受限。

5. 检测与解除

  • 资源分配图:周期性检测是否有环。
  • 解除方法:剥夺资源、回滚进程、杀掉进程(按优先级或代价最小原则)。

6. 工程实践要点

  • 固定加锁顺序:始终按同一顺序获取多个锁,最简单有效。
  • 使用超时tryLock(timeout) 获取不到就回退并释放已持有锁。
  • 死锁检测工具:Java 的 jstack、Node.js 的 Inspector、数据库的死锁检测回滚。
  • 减少锁粒度:使用细粒度锁、读写锁、无锁结构(CAS、ConcurrentHashMap)以降低竞争。

restful 接口规范是什么?

REST(Representational State Transfer,表现层状态转化)是一种基于 HTTP 协议的 Web API 设计风格。RESTful 是符合 REST 规范的接口设计实践,核心思想是:一切皆资源,通过 HTTP 动词操作资源,通过 URL 定位资源

1. 核心原则

  • 资源导向:URL 表示资源,使用名词,不用动词。
  • 统一接口:使用 HTTP 标准方法表达操作语义。
  • 无状态:每个请求必须包含所有信息,服务器不保存客户端状态。
  • 使用 HTTP 状态码:用标准状态码表达结果,不要在 body 中用自定义码覆盖语义。
  • 数据格式:通常使用 JSON,并通过 Content-Type: application/json 标注。

2. URL 规范

  • 使用名词复数:/users/orders
  • 通过路径表达层级与关系:/users/123/orders 表示用户 123 的订单。
  • 不在 URL 中放动词:错误示例 /getUser?id=123,正确 /users/123
  • 查询参数用于过滤、分页、排序:/users?role=admin&page=1&size=20&sort=-created_at

3. HTTP 动词语义

方法语义幂等示例
GET查询资源GET /users/123
POST创建资源POST /users
PUT全量替换资源PUT /users/123
PATCH部分更新资源PATCH /users/123
DELETE删除资源DELETE /users/123
  • 幂等性:多次执行结果相同。GET、PUT、DELETE 是幂等的,POST 和 PATCH 不是。

4. 状态码使用

状态码含义
200 OK查询/更新成功
201 Created创建成功
204 No Content删除成功(无返回体)
400 Bad Request参数错误
401 Unauthorized未认证
403 Forbidden无权限
404 Not Found资源不存在
409 Conflict资源冲突
422 Unprocessable Entity语义错误
500 Internal Server Error服务器错误

5. 响应结构示例

// 成功
{
"data": { "id": 123, "name": "tom" },
"message": "ok"
}
// 错误
{
"error": "InvalidParameter",
"message": "id must be a number"
}

6. 版本控制

通过 URL 路径或 Header 标注版本:

  • 路径方式:/v1/users/v2/users(最常见)。
  • Header 方式:Accept: application/vnd.myapp.v2+json

7. 其他要点

  • 使用 HATEOAS 在响应中返回相关链接,引导客户端下一步操作。
  • 对列表返回分页元信息(totalpagesize)。
  • 安全性:HTTPS、认证(Token/OAuth2)、限流、CORS 配置。

说说sourcemap的原理?

Source Map 是一种映射文件,它记录了“压缩/编译后代码”与“源代码”之间的位置对应关系,使得浏览器/调试工具能够在报错时定位到源码位置,方便调试。

1. 文件结构

一个 source map 文件本质上是一个 JSON,主要字段:

{
"version": 3,
"sources": ["src/index.js", "src/util.js"],
"sourcesContent": ["const a = 1;", "..."],
"names": ["add", "a", "b"],
"mappings": "AAAA,SAASA,IAAMC,EACf",
"file": "bundle.js",
"sourceRoot": "../"
}
  • version:source map 规范版本,目前为 3。
  • sources:源文件路径列表。
  • sourcesContent:源文件内容(可选,便于调试时不依赖原文件)。
  • names:源码中的标识符列表。
  • mappings:核心,使用 Base64 VLQ 编码的位置映射。
  • file:生成文件名。
  • sourceRoot:源文件根路径前缀。

2. mappings 字段的原理

mappings 是用分号 ; 分隔的行映射,每行对应生成代码的一行;每行内用逗号 , 分隔的段,每段表示一个映射。每个段由 1、4 或 5 个 VLQ 编码的数字组成:

  1. 生成代码的列号(以 0 为基准,相对前一段累加)。
  2. 源文件的索引(在 sources 中的下标,相对前一段累加)。
  3. 源代码的行号(相对前一段累加)。
  4. 源代码的列号(相对前一段累加)。
  5. (可选)names 数组中的索引(相对前一段累加)。

所有数字都使用 VLQ(Variable-Length Quantity) 编码后 Base64 表示,相对值累加以减小体积。

3. 引用方式

在生成文件末尾添加注释:

//# sourceMappingURL=bundle.js.map

浏览器开发者工具检测到该注释后会加载对应 .map 文件,并据此把堆栈、断点映射回源码。

4. 生成流程

源代码 → 词法/语法分析生成 AST → 生成目标代码时同时记录每个 token 在源码中的位置 → 把这些位置关系按 VLQ 编码写入 mappings

5. 安全与生产实践

  • source map 会暴露源码,生产环境通常不上传到 CDN,而是上传到错误监控平台(如 Sentry)。
  • 使用 hidden-source-map:生成 map 但不在产物中添加 sourceMappingURL 注释,避免泄露。
  • eval-cheap-module-source-map 等开发模式侧重速度;source-map 侧重准确性。
  • 调试工具(Chrome DevTools、VS Code)通过 source map 实现“在源码上打断点、看源码堆栈”的体验。

说说你对SPA的理解

SPA(Single Page Application,单页应用)是一种 Web 应用程序模型,它只有一个 HTML 页面,所有的功能与交互都在该页面内通过 JavaScript 动态渲染和切换,不再通过整页刷新来导航。

1. 核心特征

  • 单 HTML 入口:首次加载一个 HTML 后,后续路由切换由 JS 接管。
  • 前端路由:通过 History API(pushState/popstate)或 Hash 路由切换视图,不向后端请求新页面。
  • 组件化渲染:以组件为单元,根据路由动态挂载/卸载,常见框架有 React、Vue、Angular。
  • 数据通过 API 获取:页面切换时通过 AJAX/Fetch 请求后端接口,返回 JSON 后在前端渲染。

2. 工作流程

  1. 用户访问 URL → 服务器返回一个空壳 HTML(含 <div id="app"> 和 JS bundle)。
  2. 浏览器加载并执行 JS → 框架启动 → 前端路由解析当前 URL → 渲染对应组件。
  3. 用户点击导航 → 前端路由拦截 → 更新视图(可能按需拉取数据),不触发整页刷新。

3. 优点

  • 体验流畅:路由切换无白屏闪烁,接近原生应用。
  • 前后端分离:后端只提供 API,前端独立部署、迭代。
  • 资源共享:首屏后只传输数据,减少带宽和服务端渲染开销。
  • 状态保持:组件切换间可保持状态(如滚动位置、表单输入)。

4. 缺点

  • 首屏慢:需要下载并执行较大 JS bundle,白屏时间较长。
  • SEO 不友好:原始 HTML 几乎为空,搜索引擎爬虫难以索引内容。
  • 内存占用高:长时间运行、组件未正确卸载会累积内存。
  • 路由复杂度:需自行处理前进/后退、滚动恢复、权限等。

5. 常见优化方案

  • 代码分割 + 路由懒加载:按需加载路由组件,减小首屏体积。
  • 预加载/预取:空闲时预取下一页资源。
  • SSR/SSG:服务端渲染或静态生成,兼顾 SEO 与首屏(如 Next.js、Nuxt)。
  • 骨架屏/Loading:缓解白屏体验。
  • Service Worker 缓存:离线可用、二次访问加速。

6. 与 MPA 对比

维度SPAMPA(多页应用)
路由前端控制服务端控制
切换体验无刷新整页刷新
首屏较慢较快
SEO较弱较好
开发模式前后端分离模板渲染

总结:SPA 通过前端路由和组件化带来流畅体验与高效的开发模式,但首屏与 SEO 是其固有挑战,工程上通常配合代码分割、SSR/SSG 等手段补足。

单页应用如何提高加载速度?

提高单页应用(SPA)加载速度可以通过以下几种方法实现:

  1. 代码分割
    • 动态导入:使用动态导入(import())将应用代码拆分成多个文件,按需加载。这样可以减少初始加载时的 JavaScript 文件大小。
    • 路由懒加载:对不同的路由组件进行懒加载,只有在用户访问特定路由时才加载相关组件。
  2. 优化资源
    • 压缩和混淆:使用工具(如 Webpack、Terser)压缩和混淆 JavaScript 和 CSS 文件,减少文件大小。
    • 图像优化:对图像进行压缩和优化,使用适当的格式和尺寸,并考虑使用响应式图像或懒加载技术。
  3. 缓存策略
    • 浏览器缓存:使用缓存策略(如 HTTP 缓存头)来缓存静态资源,减少重复加载。
    • Service Worker:使用 Service Worker 实现离线缓存和更复杂的缓存策略。
  4. 使用 CDN
    • 内容分发网络:将静态资源(如 JavaScript、CSS、图像)托管在 CDN 上,利用 CDN 的全球分发能力,提高资源加载速度。
  5. 减少 HTTP 请求
    • 合并文件:合并多个 CSS 和 JavaScript 文件,减少 HTTP 请求次数。
    • 使用内联资源:对于小的 CSS 和 JavaScript 资源,可以考虑内联到 HTML 文件中。
  6. 懒加载和预加载
    • 懒加载:懒加载资源(如图片和组件),仅在需要时加载,减少初始加载的负担。
    • 预加载:预加载关键资源,如在用户可能访问的页面上预加载必要的 JavaScript 和 CSS 文件。
  7. 性能优化
    • 代码优化:避免不必要的重新渲染和计算,优化渲染性能。
    • 异步操作:尽量将耗时的操作(如数据获取)放在异步任务中,避免阻塞主线程。
  8. 分析和监控
    • 性能分析:使用工具(如 Lighthouse、WebPageTest)分析应用性能,识别瓶颈并进行优化。
    • 实时监控:监控应用性能指标,及时发现和解决性能问题。

通过以上方法,可以显著提高单页应用的加载速度和整体性能。