1. 使用 import
时,Webpack 如何处理 node_modules
中的依赖?
-
依赖解析:
- Webpack 遇到
import
时,利用resolve
配置查找依赖。 - 如果是第三方依赖(
node_modules
),Webpack 会优先查找其主入口(package.json
中的main
或module
字段)。
- Webpack 遇到
-
模块打包:
- ESM 模块:支持 Tree Shaking,按需打包。
- CommonJS 模块:打包时将整个模块引入。
- UMD/AMD 模块:转换为 Webpack 兼容的模块。
-
优化策略:
- 利用
externals
排除某些依赖(如 React、Lodash),使用 CDN 引入。 - 利用
splitChunks
提取公共依赖,减少重复打包。
- 利用
2. Webpack 中的 bundle、chunk 和 module 是什么?请分别介绍
-
Module(模块):
- 代码的最小单元(JS 文件、CSS 文件、图片等)。
- Webpack 将模块视为独立的代码块,进行依赖分析和打包。
-
Chunk(代码块):
- 由多个模块组成,是打包过程中被分割的代码片段。
- 用于实现按需加载和代码分割。
-
Bundle(包):
- Webpack 最终输出的文件,是浏览器可执行的代码。
- Bundle 可以包含一个或多个 Chunk。
3. 常见的 Webpack Loader 有哪些?
babel-loader
:将 ES6+ 转换为兼容浏览器的 JS。css-loader
:解析 CSS 文件中的@import
和url()
。style-loader
:将 CSS 注入到 HTML 的<style>
标签中。file-loader
:处理文件导入(图片、字体等),输出文件路径。url-loader
:类似file-loader
,但支持将小文件转换为 base64。ts-loader
:将 TypeScript 编译为 JavaScript。html-loader
:处理 HTML 文件中的资源引用。sass-loader
/less-loader
:编译 SCSS/SASS 或 LESS 文件为 CSS。
4. Webpack 和 Rollup、Parcel 构建工具有什么区别?
Webpack:
- 优点:
- 功能强大,生态完善(插件、Loader 丰富)。
- 支持复杂项目的模块化管理和优化。
- 强大的社区支持和企业级应用。
- 缺点:
- 配置复杂,上手难度较高。
- 构建速度较慢。
Rollup:
- 优点:
- 专注于 ES 模块,输出更简洁高效的包。
- 支持 Tree Shaking 效率高,适合库开发。
- 缺点:
- 插件生态不如 Webpack。
- 对复杂项目支持较弱。
Parcel:
- 优点:
- 零配置,上手简单。
- 内置模块化和优化,速度快。
- 缺点:
- 社区和生态较小。
- 对大型项目的灵活性较低。
5. 如何使用 webpack-dev-server
监控文件编译?
- 安装
webpack-dev-server
:npm install webpack-dev-server --save-dev
- 配置开发服务器:
- 在
webpack.config.js
中配置:devServer: { static: './dist', hot: true, open: true, },
- 在
- 启动服务:
- 添加脚本:
"scripts": { "start": "webpack serve" }
- 运行:
npm start
- 添加脚本:
6. 常用的前端性能分析工具有哪些?
- 浏览器开发者工具:分析加载时间、网络请求、资源大小等。
- Lighthouse:Google 提供的性能分析工具。
- WebPageTest:分析网页加载时间和性能瓶颈。
- Fiddler:查看 HTTP 请求详细信息。
- Bundle Analyzer:可视化 Webpack 打包结果,分析依赖大小。
- PerfBar:实时性能监测工具。
- PageSpeed Insights:Google 提供的性能诊断和优化建议。
7. 前端项目中如何配置 HTTP 缓存机制?
- 强缓存:
- 设置
Cache-Control
:Cache-Control: max-age=31536000, immutable
- 添加文件名哈希值:
app.[contenthash].js
- 设置
- 协商缓存:
- 使用
ETag
:ETag: "file-hash"
- 使用
Last-Modified
:Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
- 使用
- 分离动态和静态资源:
- 动态资源:短期缓存(如 API 数据)。
- 静态资源:长期缓存。
8. 如何优化 Webpack 的打包速度?
- 减少打包范围:
- 使用
exclude
排除不必要的文件。 - 配置
resolve.alias
和resolve.extensions
。
- 使用
- 使用缓存:
- 开启
cache
选项:cache: { type: 'filesystem', }
- 开启
- 多线程并行构建:
- 使用
thread-loader
或HappyPack
提升速度。
- 使用
- 按需加载:
- 使用动态导入(
import()
)分离不必要的代码。
- 使用动态导入(
- 减少插件和 Loader 数量:
- 移除无用插件,优化 Loader 执行顺序。
9. 有哪些前端网站性能优化的关键点?
- 网络优化:
- 使用 CDN 分发静态资源。
- 启用 HTTP/2。
- 资源优化:
- 压缩 JS、CSS 和图片。
- 使用 WebP 格式图片。
- 缓存机制:
- 设置强缓存和协商缓存。
- 配置文件名的哈希值。
- 代码优化:
- 按需加载,减少首屏资源大小。
- Tree Shaking 移除未使用代码。
- 用户体验优化:
- 提供骨架屏,减少感知加载时间。
10. 前端项目中,如何优化首屏渲染时间?
- 服务端渲染(SSR):
- 提前渲染 HTML,减少浏览器解析时间。
- 资源预加载:
- 使用
link rel="preload"
提前加载关键资源。
- 使用
- 减少阻塞:
- 将 CSS 放在
<head>
中,JS 放在<body>
末尾或使用defer
。
- 将 CSS 放在
- 按需加载:
- 动态导入非关键模块。
- 优化图片加载:
- 使用懒加载(
loading="lazy"
)。
- 使用懒加载(
11. 前端如何处理后端接口一次性返回的超大树形结构数据?
-
懒加载:
- 按需请求子节点数据(分页或递归)。
- 示例:
function fetchNode(nodeId) { return fetch(`/api/nodes/${nodeId}`).then(res => res.json()); }
-
虚拟列表:
- 使用
react-window
或类似技术只渲染可视区域内的节点。
- 使用
-
数据裁剪:
- 根据需求在后端过滤掉无用数据。
-
数据分页:
- 将数据分块返回,避免一次性加载所有内容。
-
数据压缩:
- 后端压缩数据(如 JSON 压缩),前端解压后使用。
如何在页面内一次性渲染10万条数据,并保证页面不卡顿?
在页面内一次性渲染10万条数据是一个极具挑战性的任务,因为直接渲染这么多数据会迅速消耗浏览器资源,造成性能瓶颈。为了保证页面不卡顿,可以考虑以下方法:
- 分批渲染:将数据分成小块,逐一渲染。这可以减轻浏览器的即时负担,避免一次性加载大量数据导致的页面卡顿。
- 虚拟滚动:使用虚拟滚动技术,只渲染可见区域的数据。当用户滚动页面时,动态加载并渲染新的数据块。这可以显著减少DOM节点的数量,提高渲染性能。
如何在浏览器中执行100万个任务,并保证页面不卡顿?
在浏览器中执行大量任务时,为了避免页面卡顿,可以采取以下策略:
- 使用Web Workers:将耗时的任务分配给Web Workers执行。Web Workers是运行在后台的JavaScript,不会阻塞页面的渲染和交互。
- 任务拆分与队列管理:将任务拆分成多个小任务,并使用任务队列进行管理。通过控制任务的执行顺序和并发数量,可以避免页面卡顿。
- 优化算法和数据结构:选择高效的算法和数据结构来执行任务,减少不必要的计算和资源消耗。
在网页中有大量图片时,如何优化图像加载以提高页面加载速度?
在网页中优化图像加载以提高页面加载速度的方法包括:
- 压缩图片:对图片进行压缩处理,减小图片大小,从而加快加载速度。
- 使用WebP格式:采用WebP等更高效的图片格式来减少图片大小。
- 图片懒加载:只加载用户当前可见区域的图片,当用户滚动到新的区域时,再加载该区域的图片。这可以减少初始加载时的图片数量,提高加载速度。
- 使用CDN加速:将图片存储在CDN上,利用CDN的分布式缓存和加速功能,提高图片的加载速度。
Vue性能优化的方法有哪些?
Vue性能优化的方法包括但不限于:
- 使用v-show替换v-if:在需要频繁切换显示状态时,使用v-show代替v-if,因为v-show只是通过CSS控制元素的显示与隐藏,而v-if是通过操作DOM来控制元素的显示与隐藏,频繁操作DOM会导致性能下降。
- 避免v-for与v-if一起使用:当v-for和v-if结合使用时,Vue需要在每次渲染时都重新计算列表中每个元素是否满足v-if的条件,这会导致不必要的性能开销。推荐的做法是将v-if放在v-for的包裹元素内部,以减少不必要的遍历次数。
- 合理使用watch和computed:watch适用于执行异步或开销较大的操作,或者需要对数据变化作出特定响应的场景;computed用于根据已有的响应式数据计算出一个新的值,并会自动缓存,只在相关响应式数据改变时进行重新计算。合理使用这两个功能可以让代码更易读和更高效。
- 组件缓存:使用keep-alive组件缓存动态组件的状态,避免重复渲染组件,提高性能和用户体验。
- 路由懒加载:通过路由懒加载,只有在使用到对应的页面时才会加载相关的代码,减少初始加载的代码量,提升网页的加载速度。
- 按需引入:对于大型库或组件库,按需引入需要的模块,避免全局引入导致的性能问题。
- 代码分割与压缩:使用Webpack等工具进行代码分割和压缩,减少文件大小,提高加载速度。
v-if和v-for在Vue中的优先级是什么?如果它们同时出现,应该如何优化以获得更好的性能?
在Vue中,v-for的优先级高于v-if。这意味着在同一个元素上使用v-if和v-for时,v-for将首先被解析,然后是v-if。
当v-if和v-for同时出现时,为了获得更好的性能,推荐的做法是将v-if放在v-for的包裹元素内部,而不是直接放在v-for的元素上。这样可以减少不必要的遍历次数和条件判断,提高渲染性能。
使用Vue渲染大量数据时,如何进行优化?
使用Vue渲染大量数据时,可以采取以下优化策略:
- 列表分页:将大量数据分页加载,只渲染当前页的数据,减少初始加载时间和内存占用。
- 虚拟滚动:使用虚拟滚动技术,只渲染可见区域的数据,提高渲染性能。
- 懒加载:对于需要异步加载的数据,使用懒加载方式,避免一次性加载大量数据导致的页面卡顿。
- 数据缓存:对于静态的大量数据,可以将其缓存到本地或使用浏览器的缓存机制,避免每次都从服务器获取数据。
- 使用异步更新:当需要更新大量数据时,使用异步更新的方式,将更新操作放在下一个事件循环中执行,避免阻塞主线程。
Vue如何缓存当前组件?缓存后如何更新?
在Vue中,可以使用keep-alive组件来缓存当前组件。keep-alive是一个抽象组件,可以将其包裹在需要缓存的组件外部,从而实现组件的缓存。
缓存后,如果需要更新组件,可以通过改变组件的props或触发组件内部的事件来更新组件的状态。此外,也可以使用activated和deactivated生命周期钩子来处理组件激活和停用时的逻辑。
如何解决Vue打包时vendor文件过大的问题?
解决Vue打包时vendor文件过大的问题,可以采取以下方法:
- 使用路由懒加载:通过路由懒加载,将不同路由对应的代码进行分割,实现按需加载,减少初始加载的代码量。
- 分析并优化依赖:使用webpack-bundle-analyzer等工具分析打包后的文件,找出占比较大的依赖,并尝试优化或替换这些依赖。
- 按需引入:对于大型库或组件库,按需引入需要的模块,避免全局引入导致的性能问题。
- 压缩和混淆代码:在打包时,使用压缩和混淆代码的选项,减少文件大小。
如何解决SPA单页应用首屏加载速度慢的问题?
解决SPA单页应用首屏加载速度慢的问题,可以采取以下方法:
- 优化路由懒加载:将SPA中不同路由对应的代码进行分割,实现懒加载,减少初始加载的代码量。
- 压缩和优化资源:对JavaScript、CSS和图片等资源进行压缩和优化,减少文件大小。
- 使用CDN加速:将静态资源存储在CDN上,利用CDN的分布式缓存和加速功能,提高资源的加载速度。
- 减少HTTP请求:合并和压缩文件,减少HTTP请求的数量和大小。
- 优化代码和算法:选择高效的算法和数据结构,减少不必要的计算和资源消耗。
综上所述,通过采取一系列优化措施,可以显著提高Vue应用的性能和用户体验。
如何在页面内一次性渲染10万条数据,并保证页面不卡顿?
在页面内一次性渲染10万条数据是一个极具挑战性的任务,因为直接渲染这么多数据会迅速消耗浏览器资源,造成性能瓶颈。为了保证页面不卡顿,可以考虑以下方法:
- 分批渲染:将数据分成小块,逐一渲染。这可以减轻浏览器的即时负担,避免一次性加载大量数据导致的页面卡顿。
- 虚拟滚动:使用虚拟滚动技术,只渲染可见区域的数据。当用户滚动页面时,动态加载并渲染新的数据块。这可以显著减少DOM节点的数量,提高渲染性能。
如何在浏览器中执行100万个任务,并保证页面不卡顿?
在浏览器中执行大量任务时,为了避免页面卡顿,可以采取以下策略:
- 使用Web Workers:将耗时的任务分配给Web Workers执行。Web Workers是运行在后台的JavaScript,不会阻塞页面的渲染和交互。
- 任务拆分与队列管理:将任务拆分成多个小任务,并使用任务队列进行管理。通过控制任务的执行顺序和并发数量,可以避免页面卡顿。
- 优化算法和数据结构:选择高效的算法和数据结构来执行任务,减少不必要的计算和资源消耗。
在网页中有大量图片时,如何优化图像加载以提高页面加载速度?
在网页中优化图像加载以提高页面加载速度的方法包括:
- 压缩图片:对图片进行压缩处理,减小图片大小,从而加快加载速度。
- 使用WebP格式:采用WebP等更高效的图片格式来减少图片大小。
- 图片懒加载:只加载用户当前可见区域的图片,当用户滚动到新的区域时,再加载该区域的图片。这可以减少初始加载时的图片数量,提高加载速度。
- 使用CDN加速:将图片存储在CDN上,利用CDN的分布式缓存和加速功能,提高图片的加载速度。
Vue性能优化的方法有哪些?
Vue性能优化的方法包括但不限于:
- 使用v-show替换v-if:在需要频繁切换显示状态时,使用v-show代替v-if,因为v-show只是通过CSS控制元素的显示与隐藏,而v-if是通过操作DOM来控制元素的显示与隐藏,频繁操作DOM会导致性能下降。
- 避免v-for与v-if一起使用:当v-for和v-if结合使用时,Vue需要在每次渲染时都重新计算列表中每个元素是否满足v-if的条件,这会导致不必要的性能开销。推荐的做法是将v-if放在v-for的包裹元素内部,以减少不必要的遍历次数。
- 合理使用watch和computed:watch适用于执行异步或开销较大的操作,或者需要对数据变化作出特定响应的场景;computed用于根据已有的响应式数据计算出一个新的值,并会自动缓存,只在相关响应式数据改变时进行重新计算。合理使用这两个功能可以让代码更易读和更高效。
- 组件缓存:使用keep-alive组件缓存动态组件的状态,避免重复渲染组件,提高性能和用户体验。
- 路由懒加载:通过路由懒加载,只有在使用到对应的页面时才会加载相关的代码,减少初始加载的代码量,提升网页的加载速度。
- 按需引入:对于大型库或组件库,按需引入需要的模块,避免全局引入导致的性能问题。
- 代码分割与压缩:使用Webpack等工具进行代码分割和压缩,减少文件大小,提高加载速度。
v-if和v-for在Vue中的优先级是什么?如果它们同时出现,应该如何优化以获得更好的性能?
在Vue中,v-for的优先级高于v-if。这意味着在同一个元素上使用v-if和v-for时,v-for将首先被解析,然后是v-if。
当v-if和v-for同时出现时,为了获得更好的性能,推荐的做法是将v-if放在v-for的包裹元素内部,而不是直接放在v-for的元素上。这样可以减少不必要的遍历次数和条件判断,提高渲染性能。
使用Vue渲染大量数据时,如何进行优化?
使用Vue渲染大量数据时,可以采取以下优化策略:
- 列表分页:将大量数据分页加载,只渲染当前页的数据,减少初始加载时间和内存占用。
- 虚拟滚动:使用虚拟滚动技术,只渲染可见区域的数据,提高渲染性能。
- 懒加载:对于需要异步加载的数据,使用懒加载方式,避免一次性加载大量数据导致的页面卡顿。
- 数据缓存:对于静态的大量数据,可以将其缓存到本地或使用浏览器的缓存机制,避免每次都从服务器获取数据。
- 使用异步更新:当需要更新大量数据时,使用异步更新的方式,将更新操作放在下一个事件循环中执行,避免阻塞主线程。
Vue如何缓存当前组件?缓存后如何更新?
在Vue中,可以使用keep-alive组件来缓存当前组件。keep-alive是一个抽象组件,可以将其包裹在需要缓存的组件外部,从而实现组件的缓存。
缓存后,如果需要更新组件,可以通过改变组件的props或触发组件内部的事件来更新组件的状态。此外,也可以使用activated和deactivated生命周期钩子来处理组件激活和停用时的逻辑。
如何解决Vue打包时vendor文件过大的问题?
解决Vue打包时vendor文件过大的问题,可以采取以下方法:
- 使用路由懒加载:通过路由懒加载,将不同路由对应的代码进行分割,实现按需加载,减少初始加载的代码量。
- 分析并优化依赖:使用webpack-bundle-analyzer等工具分析打包后的文件,找出占比较大的依赖,并尝试优化或替换这些依赖。
- 按需引入:对于大型库或组件库,按需引入需要的模块,避免全局引入导致的性能问题。
- 压缩和混淆代码:在打包时,使用压缩和混淆代码的选项,减少文件大小。
如何解决SPA单页应用首屏加载速度慢的问题?
解决SPA单页应用首屏加载速度慢的问题,可以采取以下方法:
- 优化路由懒加载:将SPA中不同路由对应的代码进行分割,实现懒加载,减少初始加载的代码量。
- 压缩和优化资源:对JavaScript、CSS和图片等资源进行压缩和优化,减少文件大小。
- 使用CDN加速:将静态资源存储在CDN上,利用CDN的分布式缓存和加速功能,提高资源的加载速度。
- 减少HTTP请求:合并和压缩文件,减少HTTP请求的数量和大小。
- 优化代码和算法:选择高效的算法和数据结构,减少不必要的计算和资源消耗。
综上所述,通过采取一系列优化措施,可以显著提高Vue应用的性能和用户体验。
1. 什么是单点登录(SSO),前端如何实现单点登录?
单点登录(SSO) 是一种认证机制,用户只需登录一次,就可以访问属于不同系统的多个应用,无需重复登录。
前端实现单点登录的方法:
-
使用共享 Cookie:
- 通过顶级域名共享 Cookie,比如在
example.com
和sub.example.com
间共享。 - 前端通过 Cookie 获取用户认证信息,检查是否已登录。
- 通过顶级域名共享 Cookie,比如在
-
Token 机制(推荐):
- 用户登录时,后端返回 JWT 或其他 Token。
- Token 保存在
localStorage
或sessionStorage
,前端每次请求将 Token 放入 HTTP 头。 - 实现跨系统登录时,统一认证中心管理 Token,前端检测登录状态。
-
OAuth 2.0:
- 使用授权码模式,前端重定向用户到认证中心获取授权,返回
access_token
后完成登录。 - 常用于单点登录和第三方登录。
- 使用授权码模式,前端重定向用户到认证中心获取授权,返回
2. 如何解决前端 SPA 应用首屏加载速度慢的问题?
-
代码分割与懒加载:
- 使用 Webpack/Vite 的动态导入(
import()
)按需加载首屏需要的资源。 - 懒加载非首屏资源,比如图片、第三方库等。
- 使用 Webpack/Vite 的动态导入(
-
SSR/SSG:
- 使用服务端渲染(SSR)或静态生成(SSG)生成首屏 HTML,前端接管后续交互逻辑。
-
预渲染与骨架屏:
- 提供骨架屏,替代空白页面,提高用户感知性能。
- 使用工具(如
React Snapshot
)生成静态 HTML。
-
优化网络请求:
- 启用 HTTP/2,减少请求延迟。
- 配置 CDN 缓存,加快静态资源加载速度。
- 减少 API 请求次数,使用合并或 GraphQL。
-
资源压缩和优化:
- 压缩 JS、CSS 和图片,开启 Gzip/Brotli。
- 使用 Tree Shaking 和 Scope Hoisting 减小打包体积。
3. 如何设计一款能够统计前端页面请求耗时的工具?
功能设计:
-
基础功能:
- 统计页面加载时间(白屏时间、首屏时间、总加载时间)。
- 统计每个请求的耗时,包括 DNS 查询、连接时间、服务器响应时间等。
-
前端实现步骤:
- 利用
Performance
API 获取页面性能指标:const { timing } = performance; const loadTime = timing.loadEventEnd - timing.navigationStart; const whiteScreenTime = timing.domLoading - timing.navigationStart;
- 使用
XMLHttpRequest
或fetch
统计请求时间:const start = Date.now(); fetch(url).then(() => { console.log('Request time:', Date.now() - start); });
- 提供 SDK 接口,支持自定义上报。
- 利用
-
上报机制:
- 使用
beacon
上报数据,确保页面关闭时仍可发送。 - 数据存储在后台,分析生成统计报表。
- 使用
4. 如何设计和优化秒杀系统的前端?
-
性能优化:
- 静态资源预加载:提前加载页面的必要资源。
- 长连接(WebSocket):用于获取实时库存状态和倒计时。
- 请求合并:通过队列合并多用户的抢购请求,减少服务器压力。
-
页面设计:
- 倒计时组件显示抢购时间。
- 库存状态的动态展示(实时更新库存)。
-
防刷设计:
- 前端限制频繁点击,增加按钮冷却时间。
- 加入验证码防止恶意点击。
-
用户体验优化:
- 使用骨架屏加载页面。
- 抢购成功后即时提示,失败时给出重试或候补信息。
5. 如何在前端页面无限滚动加载内容时自动回收上面的内容?
-
虚拟列表技术:
- 渲染只在可视区域内的元素,并移除视窗外的元素。
- 使用库:
react-window
、react-virtualized
。
-
Intersection Observer:
- 监听滚动事件,动态加载新的内容。
- 移除超出视图范围的 DOM 节点,回收资源。
const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (!entry.isIntersecting) { entry.target.remove(); } }); });
6. 如何在前端实现文件的断点续传,并确保大文件安全可上传?
-
分片上传:
- 将文件分片(如 1MB 一片),每片上传时带有序号和校验信息。
- 使用
File.slice()
方法分割文件:const blob = file.slice(start, end);
-
断点记录:
- 前端记录已上传的分片,上传失败时重新上传。
- 后端保存分片上传的状态(如 MD5 校验)。
-
加密校验:
- 文件上传前进行 MD5 哈希,上传完成后校验完整性。
- 使用 HTTPS 确保传输安全。
-
续传机制:
- 检查服务端是否存在已上传的分片(通过唯一标识),只上传缺失部分。
7. 前端如何实现基于 WebSocket 的实时聊天功能,支持多用户在线聊天并展示消息通知?
-
建立 WebSocket 连接:
- 使用 WebSocket API:
const ws = new WebSocket('wss://example.com/chat'); ws.onmessage = (event) => console.log('Message:', event.data);
- 使用 WebSocket API:
-
多用户支持:
- 使用房间概念,按用户分组聊天。
- 消息结构包含
userId
、roomId
等信息。
-
消息通知:
- 使用浏览器通知(
Notification API
):if (Notification.permission === 'granted') { new Notification('New message', { body: message }); }
- 增加未读消息数提醒。
- 使用浏览器通知(
-
消息可靠性:
- 引入心跳机制检测连接状态。
- 使用消息队列或 Redis 实现消息持久化。
8. 你会如何设计前端日志埋点 SDK?
-
SDK 功能设计:
- 采集用户行为(点击、页面停留时间、浏览路径等)。
- 捕获错误日志。
- 支持自定义事件上报。
-
实现步骤:
- 日志采集:
- 使用事件监听(
addEventListener
)采集用户行为。 - 使用
Performance
和Error
捕获性能数据和异常信息。
- 使用事件监听(
- 日志存储:
- 将日志存储在内存中,达到一定量时批量上报。
- 上报机制:
- 使用
navigator.sendBeacon
或fetch
进行非阻塞上传。
- 使用
- 日志采集:
-
日志分析:
- 后端搭建分析平台,支持按用户、事件、时间过滤查看日志。
9. 前端如何给网页增加水印,并且如何防止水印被移除?
-
实现水印:
- 使用
canvas
动态生成水印:const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.font = '20px Arial'; ctx.fillText('Watermark', 50, 50); document.body.style.backgroundImage = `url(${canvas.toDataURL()})`;
- 或直接在 DOM 中插入水印元素。
- 使用
-
防止水印被移除:
- 监控 DOM:
- 使用
MutationObserver
监控 DOM 树,发现水印被移除时重新添加。
- 使用
- 样式强化:
- 通过
z-index
、透明度等方式固定水印,不易被覆盖。
- 通过
- 加密混淆:
- 将水印代码与业务逻辑混淆,增加破解难度。
- 监控 DOM:
Vue 2和Vue 3的区别及Vue 3的更新与设计目标、优化
Vue 2和Vue 3的区别:
- 双向数据绑定:Vue 2使用ES5的Object.defineProperty进行劫持,而Vue 3使用ES6的Proxy进行数据代理,提供了更高效的变更侦测和访问拦截。
- 组件结构:Vue 2不支持碎片(Fragments),即每个组件必须有一个根节点,而Vue 3允许组件有多个根节点。
- API类型:Vue 2使用选项类型API(Options API),而Vue 3引入了合成型API(Composition API),使代码逻辑复用更方便,逻辑组织更加清晰。
- 数据建立:Vue 2将数据放入data属性中,而Vue 3使用setup()方法来建立数据。
- 组件特性:Vue 3增加了Teleport组件,允许将子组件渲染到指定的DOM节点外,还引入了Fragments(片段)等新的语法特性。
Vue 3的更新:
- 性能提升:对Virtual DOM进行了优化,引入了静态提升、缓存等技术。
- Composition API:提供了更灵活的方式来组织组件逻辑,并增强了TypeScript支持。
- Teleport组件:允许将子组件渲染到指定的DOM节点外,提升了DOM结构的可管理性。
- Fragments:支持一个组件返回多个根节点,使模板结构更灵活。
- 全局API变更:采用了更加模块化的方式,如Vue.component变为app.component,Vue.use变为app.use,并引入了createApp方法来创建Vue应用实例。
Vue 3的设计目标:
- 提升框架的运行时性能,尤其在渲染、响应式系统和内存管理上。
- 提供新的Composition API,使代码逻辑复用更方便,逻辑组织更加清晰。
- 加强TypeScript支持,提供更强的类型推导和代码提示。
- 改进Vue CLI和开发工具,提供更强大的调试、热重载和构建体验。
- 引入新的组件模型,支持更多的配置方式,尤其是在组合多个逻辑和功能时。
Vue 3在设计过程中做的优化:
- 响应式系统优化:使用Proxy替代Object.defineProperty,提供更高效的变更侦测和访问拦截。
- 编译优化:引入静态模板提升技术,将模板编译为更简洁、更高效的渲染函数。
- 初始化优化:避免Vue2中的原型链查找操作,减少不必要的初始化工作和内存开销。
- Tree-shaking支持:支持更好的Tree-shaking,减少打包体积,提高应用的加载速度。
- 虚拟DOM优化:采用静态标记的方式来跳过静态节点的比对和更新,提高渲染性能。
性能提升的主要体现
Vue 3的性能提升主要体现在响应式系统优化、编译优化、初始化优化、Tree-shaking支持和虚拟DOM优化等方面。这些优化使得Vue 3在处理大量数据、复杂组件以及提高应用加载速度等方面表现出更强大的能力。
设计模式相关问答
什么是设计模式?为什么要用?
设计模式是软件工程中用于解决特定问题的一系列最佳实践。它们是经过时间考验的、被广泛认可的软件设计经验,可以帮助开发者在面对常见问题时做出更好的设计决策。使用设计模式可以提高代码的可重用性、灵活性、可维护性和可扩展性。
最常见的几种设计模式及其应用场景:
- 单例模式:确保一个类只有一个实例,并提供一个全局访问点。常用于管理全局资源,如数据库连接池、日志记录器等。
- 工厂模式:定义一个创建对象的接口,但让子类决定要实例化的类是哪一个。常用于创建具有共同接口的对象,但具体实现可能不同的场景。
- 观察者模式:定义了一个主题和多个观察者之间的关系。当主题发生变化时,它会通知所有注册的观察者并更新它们。常用于实现事件驱动的系统,如GUI框架、消息通知系统等。
- 策略模式:定义了一系列算法,并将它们封装起来,使它们可以互换。常用于实现多种算法或策略的场景,如排序算法、支付策略等。
策略模式:
定义了一系列算法,并将它们封装起来,使它们可以互换。策略模式使得算法可以独立于使用它的客户端而变化。一般用在需要实现多种算法或策略,并且这些算法或策略可以互换使用的场景。
模板方法模式:
定义了一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变算法的结构即可重定义算法的某些特定步骤。一般用在需要将一个复杂的过程分解为多个步骤,并且这些步骤在不同的子类中可能有不同的实现方式的场景。
代理模式:
为其他对象提供一种代理以控制对这个对象的访问。代理对象可以在客户端和目标对象之间起到中介的作用,降低系统的耦合度。一般用在需要为某个对象提供一个代理以控制对该对象的访问,或者需要增强某个对象的功能的场景。
原型模式:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式允许一个对象通过复制另一个对象来创建新的实例。一般用在需要创建大量相似对象的场景,或者对象的创建过程比较复杂且耗时的场景。
简单工厂模式的工作原理:
简单工厂模式通过一个工厂类来创建对象,而不需要客户端直接实例化对象。客户端只需要传入一个参数(通常是所需对象的类型或名称),工厂类就会根据这个参数来创建相应的对象并返回。这样,客户端就不需要关心对象的创建过程,只需要知道如何调用工厂类来获取所需的对象即可。
前端实现扫码登录功能
前端实现扫码登录功能通常涉及以下几个步骤:
- 生成二维码:前端通过调用后端接口生成一个包含登录信息的二维码。
- 扫码识别:用户使用手机等移动设备扫描该二维码,并在移动设备上确认登录。
- 后端验证:后端接收到移动设备的登录确认后,验证登录信息并生成一个登录凭证(如token)。
- 前端登录:前端通过调用后端接口并使用该登录凭证进行登录验证,如果验证成功,则用户登录成功。
请注意,具体实现过程可能因项目需求和后端接口的不同而有所差异。
如何在前端实现一个实时自动补全搜索框?
在前端实现实时自动补全搜索框(通常称为“自动补全”或“自动建议”)通常涉及以下几个步骤:
- 监听输入框事件:监听用户在输入框中的输入事件(如
input
或keyup
)。 - 发送请求到后端:将用户输入的关键词发送到后端,后端返回相关的建议列表。
- 显示建议列表:在前端显示从后端返回的建议列表。
- 处理用户选择:当用户从建议列表中选择一个选项时,更新输入框的值。
为了提高性能和用户体验,可以使用防抖(debounce)或节流(throttle)技术来减少请求的频率。
如何在前端实现国际化,并根据用户设置自动切换语言?
前端国际化通常涉及以下步骤:
- 准备多语言资源:创建包含不同语言文本的JSON文件或对象。
- 选择语言:根据用户设置或浏览器语言自动选择语言,或使用选择器让用户手动选择。
- 加载语言资源:动态加载所选语言的资源文件。
- 渲染界面:使用所选语言的资源来渲染界面文本。
可以使用第三方库(如react-i18next
、vue-i18n
)来简化这个过程。
常见的登录鉴权方式有哪些?各自的优缺点是?
-
Session-Cookie:
- 优点:简单、易于理解和实现。
- 缺点:依赖于服务器端的Session存储,容易受到CSRF攻击,且跨域问题复杂。
-
Token-Based(JWT, OAuth):
- 优点:无状态、跨域方便、安全性较高(特别是JWT)。
- 缺点:Token需要妥善存储和管理,JWT的过期和刷新机制需要额外处理。
-
OAuth2:
- 优点:适用于第三方授权,如微信、QQ登录,用户体验较好。
- 缺点:实现复杂,涉及多个系统之间的交互和安全性问题。
-
SSO(单点登录):
- 优点:用户只需一次登录即可访问多个系统,提高用户体验。
- 缺点:实现和维护成本较高,需要统一的认证中心。
前端开发中的 Live-Reload 自动刷新与 HMR 热模块替换有什么区别?
- Live-Reload:每次代码更改后,浏览器会自动刷新整个页面来显示更改。
- HMR(Hot Module Replacement):允许在运行时替换、添加或删除模块,而无需重新加载整个页面。它只更新更改的部分,提供更平滑的开发体验。
如何使用 Webpack 处理内联 CSS?
可以使用style-loader
将CSS作为<style>
标签内联到HTML文档中。在Webpack配置文件中,可以这样配置:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
};
如何使用 Webpack 解决开发环境中的跨域问题?
在开发环境中,可以使用webpack-dev-server
的proxy
选项来设置代理服务器,从而解决跨域问题。例如:
devServer: {
proxy: {
'/api': {
target: 'http://backend-server.com',
changeOrigin: true,
},
},
},
这样,当前端请求/api
开头的URL时,webpack-dev-server
会将其代理到http://backend-server.com
。
前端开发中如何优化网络请求和资源加载?具体怎么实现?
- 合并和压缩资源:使用Webpack等工具合并和压缩CSS、JavaScript文件。
- 代码拆分:使用Webpack的代码拆分功能(Code Splitting),按需加载代码。
- 缓存:利用HTTP缓存头(如
Cache-Control
、Expires
)和浏览器缓存机制。 - CDN:将静态资源部署到CDN上,加快资源加载速度。
- 懒加载:对于图片等资源,使用懒加载技术,只在需要时加载。
- 减少HTTP请求:合并CSS和JavaScript文件,使用雪碧图等技术减少图片请求。
如何使用 Webpack 优化产出代码?
- Tree Shaking:确保Webpack配置中启用了Tree Shaking,以移除未使用的代码。
- 代码压缩:使用
TerserPlugin
等插件对代码进行压缩。 - Scope Hoisting:虽然Webpack 4及更高版本已经默认启用了Scope Hoisting(也称为模块连接),但它仍然有助于减少输出文件的大小。
- 分离开发环境和生产环境配置:为开发环境和生产环境创建不同的Webpack配置文件,以便在生产环境中启用所有优化选项。
Webpack中,常见的图片处理加载器有哪些?
- file-loader:将文件输出到输出目录,并返回文件的URL。
- url-loader:类似于
file-loader
,但可以将小文件转换为base64编码的URL,以减少HTTP请求。 - image-webpack-loader:用于在Webpack中处理图像文件(如压缩、优化等)。
- imagemin-mozjpeg、imagemin-optipng等:作为插件与
image-webpack-loader
一起使用,以压缩JPEG和PNG图像。
如何使用 Webpack 和 Localstorage 实现静态资源的离线缓存?
- 将静态资源打包成单个文件:使用Webpack将静态资源(如JavaScript、CSS、图片)打包成一个或多个文件。
- 在本地存储资源:在应用程序启动时,检查Localstorage中是否已存储这些资源。如果没有,则从服务器加载它们,并将它们存储在Localstorage中。
- 从本地存储加载资源:如果Localstorage中已存储资源,则直接从Localstorage中加载它们,而不是从服务器加载。
注意:由于浏览器对Localstorage的存储大小和类型有限制(如只能存储字符串),因此可能需要将资源编码为字符串(如使用Base64编码)后再存储。此外,还需要考虑资源的版本控制和更新机制,以确保用户始终使用最新版本的资源。
1. 什么是 Webpack?它有什么作用?
Webpack 是一个现代 JavaScript 应用的静态模块打包工具。
它的作用主要包括:
- 模块化管理:将项目的各种资源(JS、CSS、图片等)作为模块进行处理。
- 代码打包:把模块编译为适合浏览器执行的文件。
- 性能优化:通过代码分割、Tree Shaking、压缩等提升项目性能。
- 开发体验优化:通过热更新、代理、编译错误提示等工具提升开发效率。
- 兼容性处理:将现代 JavaScript 转换为兼容老旧浏览器的代码。
2. 如何使用 Webpack 进行前端性能优化?
- 代码分割:
- 使用
optimization.splitChunks
将公共代码抽离到单独的文件中。
- 使用
- Tree Shaking:
- 通过
sideEffects: false
移除未使用的模块。
- 通过
- 异步加载:
- 动态导入模块,按需加载(
import()
)。
- 动态导入模块,按需加载(
- 资源压缩:
- 使用
TerserPlugin
压缩 JavaScript,css-minimizer-webpack-plugin
压缩 CSS。
- 使用
- 图片优化:
- 使用
image-minimizer-webpack-plugin
压缩图片。
- 使用
- 缓存优化:
- 文件名带有哈希值(如
[contenthash]
)实现长效缓存。
- 文件名带有哈希值(如
3. 如何在 Webpack 中实现条件组件的按需打包?
- 动态导入:
- 根据条件动态加载组件:
if (condition) { import('./ComponentA').then(ComponentA => { new ComponentA.default(); }); }
- 根据条件动态加载组件:
require.context
:- 按条件动态引入:
const context = require.context('./components', false, /\.js$/); const module = condition ? context('./ComponentA.js') : context('./ComponentB.js');
- 按条件动态引入:
DefinePlugin
:- 定义环境变量,基于环境选择加载:
if (process.env.NODE_ENV === 'production') { import('./ProdComponent'); } else { import('./DevComponent'); }
- 定义环境变量,基于环境选择加载:
4. 前端如何使用 Webpack 进行高效分包优化?
-
optimization.splitChunks
:- 提取公共依赖库:
optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all', }, }, }, }
- 提取公共依赖库:
-
动态加载(按需加载):
- 使用
import()
分割业务代码为独立文件。
- 使用
-
第三方库分包:
- 将第三方库(如 React、Lodash)独立打包,减小主文件体积。
-
按路由分包:
- 配合 React 的
React.lazy()
或 Vue 的路由懒加载实现。
- 配合 React 的
5. 前端项目中资源的缓存配置策略有哪些?
- 静态资源哈希命名:
- 在文件名中添加
contenthash
:output: { filename: '[name].[contenthash].js', }
- 在文件名中添加
- 合理的 Cache-Control 设置:
- 配置
max-age
和immutable
以确保长期有效缓存。
- 配置
- 分离动态和静态资源:
- 动态资源使用短期缓存,静态资源使用长期缓存。
- 服务端支持:
- 使用 ETag 和 Last-Modified 提供资源变更校验。
6. 如何优化 Webpack 打包后文件的体积?
- Tree Shaking:
- 配置
sideEffects: false
,去除未使用的代码。
- 配置
- 减少 Polyfill:
- 使用
@babel/preset-env
按需加载。
- 使用
- 按需引入库:
- 使用插件如
babel-plugin-import
实现按需引入组件。
- 使用插件如
- 代码压缩:
- 配置
TerserPlugin
压缩 JS,使用 CSS 压缩插件。
- 配置
- 图片优化:
- 使用
image-minimizer-webpack-plugin
减少图片体积。
- 使用
- 移除多余插件:
- 避免引入不必要的插件和模块。
7. 同一前端页面的 3 个组件请求同一个 API 并发送了 3 次请求,如何优化?
-
请求合并:
- 使用一个公共服务模块共享请求:
let result; const fetchData = async () => { if (!result) { result = fetch('/api').then(res => res.json()); } return result; };
- 使用一个公共服务模块共享请求:
-
共享状态:
- 使用状态管理工具(如 Redux、MobX、Pinia)缓存请求结果。
-
缓存层:
- 利用浏览器缓存或 IndexedDB 存储常用数据,避免重复请求。
8. 什么是 Webpack 的 bundle、chunk 和 module?分别有什么作用?
-
Module(模块):
- Webpack 中的最小代码单元,可以是 JS 文件、CSS 文件、图片等。
- 作用:模块是打包的基本单元。
-
Chunk(代码块):
- 由多个模块组成,用于按需加载或代码分割。
- 作用:实现代码的动态加载和优化。
-
Bundle(包):
- Webpack 打包生成的最终文件。
- 作用:供浏览器直接加载运行。
9. Webpack 插件底层的实现原理是什么?
-
Webpack 插件是一个类或函数:
- 插件通过
apply(compiler)
方法接入 Webpack。 - 通过监听 Webpack 的生命周期钩子(如
emit
、compile
)执行自定义逻辑。
- 插件通过
-
工作流程:
- Webpack 初始化时,调用插件的
apply
方法。 - 插件在特定钩子阶段注入自定义逻辑,操作 Webpack 的内部数据结构(如 AST 或文件列表)。
- Webpack 初始化时,调用插件的
-
简单示例:
class MyPlugin { apply(compiler) { compiler.hooks.emit.tap('MyPlugin', compilation => { console.log('Emit phase'); }); } }
10. Core Web Vitals 是什么?它包括哪些指标?
Core Web Vitals 是 Google 推出的评估网页用户体验的关键性能指标集,用于衡量加载性能、交互性和视觉稳定性。
包括以下指标:
-
LCP(Largest Contentful Paint):
- 测量最大内容元素的加载时间,目标小于 2.5 秒。
-
FID(First Input Delay):
- 测量用户与页面首次交互的响应时间,目标小于 100 毫秒。
-
CLS(Cumulative Layout Shift):
- 测量页面视觉内容的稳定性,目标小于 0.1。
-
其他相关指标:
- TTFB(Time to First Byte):首字节时间。
- INP(Interaction to Next Paint):全交互响应性测量(实验性)。
img
标签的srcset
属性的作用
srcset
属性允许开发者为不同设备或分辨率提供多个图像选项,优化加载的图片以适应设备的屏幕大小和分辨率。这提高了性能和用户体验。
示例:
<img src="default.jpg"
srcset="small.jpg 480w, medium.jpg 1024w, large.jpg 1600w"
sizes="(max-width: 600px) 480px,
(max-width: 1200px) 1024px,
1600px"
alt="Example image">
srcset
:定义图片文件及其宽度说明。例如,small.jpg 480w
表示small.jpg
的宽度为 480 像素。sizes
:定义显示图片的逻辑宽度条件,例如480px
表示当屏幕宽度小于 600px 时,选择 480px 的图片。
浏览器会根据设备的屏幕分辨率和宽度自动选择最合适的图片。
CSS 中可继承与不可继承属性有哪些
- 可继承属性:如字体相关的属性(font-family、font-size、font-weight等)、文本相关的属性(text-align、text-indent、line-height等)、颜色相关的属性(color)以及列表相关的属性(list-style-type、list-style-position等)。
- 不可继承属性:如盒子模型相关的属性(width、height、margin、padding、border等)、定位相关的属性(position、top、right、bottom、left、float、clear等)、显示和可见性相关的属性(display、visibility等)以及背景相关的属性(background-color、background-image等)。
如何计算 CSS 的优先级
CSS 的优先级计算基于选择器的类型和数量,具体规则如下:
- 内联样式(如style属性中的样式)的优先级最高。
- ID选择器(如#id)的优先级次于内联样式,但高于其他选择器。
- 类选择器(如.class)、属性选择器(如[type=“text”])和伪类选择器(如:hover)的优先级相同。
- 元素选择器(如div、p)和伪元素选择器(如::before、::after)的优先级最低。
此外,如果两个选择器的优先级相同,则后定义的样式会覆盖先定义的样式(即“后来者居上”原则)。同时,可以使用!important
声明来提高某个样式规则的优先级,使其高于其他任何非!important
的样式规则。但需要注意的是,过度使用!important
可能会导致样式表难以维护。
有哪些 CSS 选择器及其介绍
CSS 选择器用于选择需要应用样式的HTML元素,常见的CSS选择器包括:
- 元素选择器:选择HTML文档中的特定类型的元素(如
div
、p
等)。 - 类选择器:选择具有特定类属性的HTML元素(如
.myClass
)。类选择器前面有一个点号(.
)。 - ID选择器:选择具有特定ID属性的HTML元素(如
#myId
)。ID选择器前面有一个井号(#
),且ID在HTML文档中应该是唯一的。 - 属性选择器:选择具有特定属性的HTML元素(如
[type="text"]
)。属性选择器使用方括号[]
来包含属性名和可选的属性值。 - 伪类选择器:选择处于特定状态的HTML元素(如
:hover
、:first-child
等)。伪类选择器以冒号(:
)开头。 - 伪元素选择器:选择文档树中不存在的、但被CSS渲染为元素的部分(如
::before
、::after
等)。伪元素选择器以双冒号(::
)开头(CSS3规范),但在某些旧版浏览器中可能使用单冒号(:
)。 - 后代选择器:选择某个元素的后代元素(如
div p
,选择所有位于<div>
元素内的<p>
元素)。 - 子选择器:选择某个元素的直接子元素(如
ul > li
,选择所有直接位于<ul>
元素下的<li>
元素)。 - 相邻兄弟选择器:选择紧接在另一个元素后的兄弟元素(如
h1 + p
,选择紧接在<h1>
元素后的第一个<p>
元素)。 - 通用选择器:选择所有元素(如
*
)。
Canvas 和 SVG 的区别
Canvas和SVG都是用于在网页上绘制图形的技术,但它们有以下区别:
- 绘图方式:Canvas使用JavaScript来绘制图形,而SVG则是使用XML来描述图形。
- 图形质量:Canvas绘制的图形是像素级的,因此放大或缩小时可能会出现模糊。而SVG绘制的图形是矢量图形,无论放大或缩小都能保持清晰。
- 交互性:Canvas更适合用于需要频繁更新和重绘的图形,如游戏或动画。而SVG则更适合用于静态或较少变化的图形,如图标或图表。
- 兼容性:Canvas在大多数现代浏览器中都有良好的支持,而SVG在某些旧版浏览器中可能存在兼容性问题。
HTML 中,title 与 h1 标签的区别
- title标签:位于HTML文档的
<head>
部分,用于定义文档的标题。这个标题通常显示在浏览器的标签栏或标题栏上,并且也是搜索引擎在索引网页时使用的关键信息之一。 - h1标签:位于HTML文档的
<body>
部分,用于定义文档中最重要的标题或章节标题。在网页内容中,h1标签通常只使用一次,表示页面的主标题。h1标签对于搜索引擎优化(SEO)和网页的可读性都很重要。
HTML 行内元素、块级元素、空(void)元素
- 行内元素:不会独占一行,只占据其必要的宽度。常见的行内元素包括
<a>
(链接)、<span>
(文本容器)、<img>
(图像)、<br>
(换行符)等。 - 块级元素:会独占一行,并扩展到其父元素的整个宽度。常见的块级元素包括
<div>
(块级容器)、<p>
(段落)、<h1>
至<h6>
(标题)、<ul>
(无序列表)、<ol>
(有序列表)等。 - 空(void)元素:也称为自闭合元素或空标签,它们没有闭合标签,且通常没有内容。常见的空元素包括
<br>
(换行)、<hr>
(水平线)、<img>
(图像)、<input>
(输入字段)等。
HTML 的 src 和 href 属性
- src属性:通常用于
<img>
、<script>
、<video>
、<audio>
等标签中,指定资源(如图像、脚本文件、视频文件等)的路径。 - href属性:通常用于
<a>
、<link>
等标签中,指定链接目标的URL。在<a>
标签中,href属性定义了超链接的目标地址;在<link>
标签中,href属性则用于指定外部资源(如CSS文件)的路径。
什么是 HTML 语义化
HTML语义化是指根据内容的结构和含义(内容语义化),选择合适的HTML标签(代码语义化),以更好地表达内容的意义和层次。使用语义化标签可以让搜索引擎更容易理解网页内容,有利于搜索引擎优化(SEO);同时,语义化标签也有助于提高网页的可访问性,使屏幕阅读器等辅助技术能够更好地理解和朗读网页内容。此外,语义化标签还有助于提高代码的可读性和可维护性。
DOCTYPE(文档类型)的作用
DOCTYPE是HTML文档中的声明,用于指定HTML的版本和解析方式。DOCTYPE声明的目的是告诉浏览器应该使用哪种HTML或XHTML规范来解析文档。正确的DOCTYPE声明对于确保网页在浏览器中正确显示和渲染非常重要。如果缺少了DOCTYPE声明或者声明不正确,浏览器可能会以兼容模式(quirks mode)来解析文档,这可能会导致一些意料之外的布局和样式问题。
HTML 的 script 标签中 defer 和 async 有什么区别
在HTML的<script>
标签中,defer
和async
都是用于控制脚本加载和执行的属性,但它们有以下区别:
- defer属性:带有
defer
属性的脚本会异步加载,并在整个页面解析完成后、在DOMContentLoaded事件触发之前执行。这意味着,带有defer
属性的脚本会按照它们在HTML文档中出现的顺序执行。此外,defer
属性只适用于外部脚本(即带有src
属性的<script>
标签)。 - async属性:带有
async
属性的脚本也会异步加载,但一旦脚本加载完成,就会立即执行,而不等待整个页面解析完成。这意味着,带有async
属性的脚本可能会打乱页面中其他脚本的执行顺序。与defer
属性不同,async
属性适用于外部脚本和内联脚本(即不带有src
属性的<script>
标签)。
常用的 HTML meta 标签
常用的HTML meta标签包括:
<meta charset="UTF-8">
:定义文档的字符编码。<meta name="viewport" content="width=device-width, initial-scale=1.0">
:用于控制视口(viewport)的大小和缩放比例,以适应不同设备的屏幕尺寸。<meta name="description" content="网页描述内容">
:提供网页的描述信息,有助于搜索引擎索引和显示。<meta name="keywords" content="关键词1,关键词2,关键词3">
:提供网页的关键词信息,虽然现代搜索引擎对关键词标签的依赖程度较低,但仍然可以作为补充信息。<meta name="author" content="作者名">
:定义网页的作者信息。<meta name="robots" content="index,follow">
:控制搜索引擎对网页的索引和跟踪行为。
HTML5 相比于 HTML 的更新
HTML5是HTML的最新版本,相比于之前的HTML版本,它带来了许多新功能和改进,具体包括:
- 语义化标签:HTML5引入了大量语义化的标签,如
<header>
、<footer>
、<nav>
、<article>
、<aside>
等,这些标签使得HTML结构更加清晰,便于搜索引擎理解和用户阅读。 - 多媒体支持:HTML5直接支持
<video>
和<audio>
标签,无需插件即可播放多媒体内容。 - 表单增强:HTML5引入了许多新的表单元素和输入类型,如
<datalist>
、`’
1. 什么是 React 中的受控组件? 它的应用场景是什么?
-
受控组件:
受控组件是指 React 中通过组件的state
来管理表单元素(如<input>
、<textarea>
)的值,其值受组件state
控制。特点:
- 表单元素的值由
state
决定,受控于 React。 - 用户输入触发
onChange
事件,更新state
,从而引发组件重新渲染。
示例:
function ControlledInput() { const [value, setValue] = React.useState(""); return ( <input type="text" value={value} onChange={(e) => setValue(e.target.value)} /> ); }
- 表单元素的值由
-
应用场景:
- 表单验证:实时验证用户输入,动态显示错误信息。
- 动态渲染:根据输入动态更新 UI,例如搜索功能。
- 统一管理数据:表单数据存储在组件
state
中,便于提交时统一处理。
2. 为什么 React 使用虚拟 DOM 来提高性能?
React 使用虚拟 DOM(Virtual DOM)提高性能的主要原因是优化了实际 DOM 的操作。
-
虚拟 DOM 工作原理:
- React 维护一个内存中的虚拟 DOM。
- 当组件状态或属性变化时,React 根据变化生成新的虚拟 DOM。
- React 对比新旧虚拟 DOM,计算出最小的变化(Diffing)。
- React 只更新必要的真实 DOM。
-
性能优势:
- 减少直接 DOM 操作:实际 DOM 操作昂贵,虚拟 DOM 在内存中处理效率更高。
- 批量更新:将多次 DOM 更新合并为一次操作。
- 跨平台:虚拟 DOM 是平台无关的,可以用于 Web、Mobile 等多种场景。
3. Redux 如何实现多个组件之间的通信?
-
Redux 的核心机制:
- 中央状态管理:Redux 使用一个全局的
store
保存状态。 - 单向数据流:
- 组件通过
dispatch(action)
触发状态更新。 - 状态更新后,
store
通知所有订阅的组件。
- 组件通过
- 订阅和更新:
- 组件使用
connect
(或useSelector
)从store
中读取状态。 - 状态变化时,订阅的组件会重新渲染。
- 组件使用
- 中央状态管理:Redux 使用一个全局的
-
多个组件通信的实现:
- 通过共享
store
状态。 - 组件 A 更新状态,组件 B 通过订阅获取更新后的状态。
- 通过共享
4. 多个组件使用相同状态时如何进行管理?
Redux 提供了集中式状态管理工具,通过以下方式管理多个组件的相同状态:
- 状态存储:
- 将多个组件需要共享的状态存储在
store
中。
- 将多个组件需要共享的状态存储在
- 拆分 Reducers:
- 使用
combineReducers
拆分逻辑,按模块组织状态。
- 使用
- 按需订阅:
- 组件使用
useSelector
选择需要的状态,避免不必要的重渲染。
- 组件使用
- 中间件:
- 使用中间件(如
redux-thunk
)处理异步逻辑,保持状态一致性。
- 使用中间件(如
5. 用户从输入网址到网页显示期间发生了什么?(从网络角度)
-
DNS 解析:
- 用户输入网址(如
www.example.com
),浏览器通过 DNS 解析获取服务器 IP 地址。
- 用户输入网址(如
-
建立 TCP 连接:
- 浏览器通过三次握手与服务器建立 TCP 连接。
-
发送 HTTP 请求:
- 浏览器向服务器发送 HTTP 请求,包含请求头和其他信息。
-
服务器处理请求:
- 服务器接收到请求后,处理逻辑并生成响应(如 HTML 文件)。
-
返回 HTTP 响应:
- 服务器返回包含内容的 HTTP 响应,可能包括 HTML、CSS、JS 和图片等。
-
浏览器渲染页面:
- 浏览器解析 HTML,构建 DOM 树。
- 解析 CSS,构建 CSSOM 树。
- 合并 DOM 和 CSSOM,生成渲染树。
- 根据渲染树进行布局和绘制。
-
执行 JS:
- 浏览器解析并执行 JavaScript,更新页面内容或行为。
-
加载资源:
- 浏览器根据 HTML 和 CSS 中的引用加载资源(如图片、字体等)。
-
页面展示:
- 浏览器将渲染的结果显示给用户。
React Router 的 history 模式中,push 和 replace 方法有什么区别?
在 React Router 的 history 模式中,push
和 replace
方法都用于在浏览历史记录中添加或更改条目,但它们有不同的行为:
-
push:将一个新条目推入历史堆栈中。这类似于用户点击浏览器中的链接或在新标签页中打开页面。当用户点击浏览器的后退按钮时,可以返回到前一个页面。
-
replace:用当前条目替换历史堆栈中的当前条目。这不会增加历史堆栈的长度。当用户点击浏览器的后退按钮时,他们将不会返回到被替换的页面,而是直接跳转到前一个存在的页面(如果存在)。
如果 React 组件的属性没有传值,它的默认值是什么?
如果 React 组件的属性(props)没有传值,那么该属性的值将是 undefined
。如果你希望为某个属性提供默认值,可以在组件内部使用逻辑运算符(如 ||
或 ??
)或解构赋值时的默认值来实现。
React 中除了在构造函数中绑定 this,还有其他绑定 this 的方式么?
是的,React 中除了在构造函数中绑定 this
,还有其他几种绑定 this
的方式:
- 箭头函数:在 JSX 中或组件的方法中使用箭头函数可以自动绑定
this
到当前组件实例。 - 类属性方法:在类定义中直接使用箭头函数作为方法(但请注意,这种方法在类字段提案被完全支持之前可能不是所有环境都可用)。
- 在事件处理程序中绑定:在将方法传递给事件处理程序时,可以在调用时绑定
this
(但这通常不推荐,因为它会在每次渲染时都创建一个新的函数实例)。 - 使用外部库(如
bind-decorator
):可以使用第三方库来简化this
的绑定过程。
为什么在 React 中遍历时不建议使用索引作为唯一的 key 值?
在 React 中,当使用列表渲染(如使用 map
方法遍历数组并返回 JSX 元素)时,每个元素都需要一个唯一的 key
属性来帮助 React 识别哪些元素是唯一的、哪些元素被修改、添加或删除了。虽然可以使用索引作为 key
,但这通常不是最佳实践,因为:
- 当列表项的顺序发生变化时,即使内容没有变化,使用索引作为
key
也会导致所有列表项重新渲染,因为索引已经改变了。 - 如果列表项可以被添加、删除或移动,使用索引作为
key
可能会导致状态管理上的问题,因为索引不再是稳定的标识符。
更好的做法是使用每个列表项的唯一标识符(如 ID)作为 key
。
React Router 中的 Router 组件有几种类型?
React Router 提供了几种不同类型的 Router
组件,以适应不同的应用场景:
- BrowserRouter:用于在支持 HTML5 历史 API 的环境中(如现代浏览器)提供 URL 的管理功能。它使用浏览器的历史记录堆栈来管理 URL 的变化。
- HashRouter:使用 URL 中的哈希(#)部分来管理历史记录。它适用于不支持 HTML5 历史 API 的环境(如旧版浏览器)或当你不希望服务器配置来支持干净的 URL 时。
- MemoryRouter:在内存中管理历史记录堆栈,不依赖于浏览器的 URL。它通常用于测试或服务器端渲染。
- StaticRouter:一个用于服务器端渲染的
Router
,它不会监听或修改 URL。它通常与ReactDOMServer.renderToString
一起使用。
在 React 的 render 函数中,是否可以直接写 if else 判断?为什么?
在 React 的 render
函数中,你不能直接写 JavaScript 的 if-else
语句,因为 JSX 需要返回有效的 React 元素或 null
,而 if-else
语句本身不返回这些值。但是,你可以在 render
方法中使用条件(三元)运算符、逻辑运算符或直接在 JSX 中嵌入表达式来实现条件渲染。
例如,你可以使用三元运算符:
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? <LoggedInComponent /> : <LoginComponent />}
</div>
);
}
或者,你可以在 JSX 中使用逻辑与(&&
)运算符:
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn && <LoggedInComponent />}
{!isLoggedIn && <LoginComponent />}
</div>
);
}
如何在 React 项目中引入图片?哪种方式更好?
在 React 项目中引入图片有几种方式:
- 直接作为 JSX 中的
src
属性:你可以将图片文件放在项目的public
文件夹中,并在 JSX 中直接使用相对路径作为img
标签的src
属性。 - 使用
import
语句:你可以将图片文件作为模块导入,并在 JSX 中使用导入的变量作为src
属性。这种方法允许你使用 Webpack 等构建工具来处理图片(如压缩、优化等)。 - 使用 CSS/Sass 等预处理器:你可以将图片作为背景图像在 CSS/Sass 文件中引入。
通常,使用 import
语句来引入图片是更好的方式,因为它允许你利用构建工具的功能来优化图片,并且可以在代码中更容易地引用和管理图片资源。
在 React 的 JSX 中,属性是否可以被覆盖?覆盖的原则是什么?
在 React 的 JSX 中,属性(props)是可以被覆盖的,但覆盖的原则取决于属性的来源和组件的层次结构。
- 父组件传递给子组件的属性:当父组件将属性传递给子组件时,如果子组件在其 JSX 中为该属性提供了不同的值,则子组件中的值将覆盖父组件传递的值。
- 组件自身的默认属性:如果组件在其内部定义了默认属性,并且这些属性在 JSX 中被显式地设置了不同的值,则显式设置的值将覆盖默认值。
- 属性合并:对于某些属性(如
style
和className
),React 允许你通过对象合并的方式将它们组合在一起,而不是简单地覆盖。例如,你可以将父组件传递的style
对象与组件内部的style
对象合并在一起。
总的来说,属性覆盖的原则是基于属性的来源和组件的层次结构来确定的,并且 React 提供了一些机制来允许属性合并而不是简单的覆盖。
1. Node.js 中同步和异步代码的区别
-
同步代码:
- 按照代码的顺序执行,后续代码必须等待前面的代码执行完毕。
- 阻塞操作:执行期间会阻塞其他任务。
示例:
const fs = require('fs'); const data = fs.readFileSync('file.txt', 'utf-8'); // 阻塞 console.log(data); console.log('This will run after file read');
-
异步代码:
- 不会阻塞代码执行。异步操作会立即返回,通过回调函数、Promise 或 async/await 在操作完成后处理结果。
- 非阻塞:其他代码可以继续运行。
示例:
const fs = require('fs'); fs.readFile('file.txt', 'utf-8', (err, data) => { if (err) throw err; console.log(data); }); console.log('This will run before file read');
2. Node.js 中的 Buffer 对象是什么? 它有什么作用?
-
Buffer 对象:
Node.js 提供的 Buffer 对象用于操作二进制数据。它是类数组的对象,专为处理数据流中不可变的字节数据设计。 -
作用:
- 处理 I/O 操作(如文件读写、网络通信)中的二进制数据。
- 处理字符编码转换(如 UTF-8 与 Base64)。
-
示例:
const buf = Buffer.from('Hello, World!', 'utf-8'); console.log(buf); // 输出 <Buffer 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21> console.log(buf.toString()); // 输出 "Hello, World!"
3. Node.js 中的 process 对象是什么? 它有哪些常用属性?
-
process
对象:
process
是 Node.js 提供的全局对象,用于与当前运行的 Node.js 进程交互。 -
常用属性和方法:
process.argv
:获取命令行参数。console.log(process.argv); // 输出 ["node", "script.js", "arg1", "arg2"]
process.env
:获取环境变量。console.log(process.env.PATH);
process.cwd()
:返回当前工作目录。console.log(process.cwd());
process.exit([code])
:退出当前进程。process.exit(0); // 退出并返回状态码 0
process.nextTick()
:将回调函数添加到事件循环的下一阶段。
4. Node.js 中的 require 和 import 有什么区别?
特性 | require | import |
---|---|---|
标准 | CommonJS 模块规范 | ES6 模块规范 |
用法 | 动态引入模块 | 静态引入模块 |
运行时支持 | Node.js 原生支持 | 需要 type: "module" 或 esm 支持 |
加载行为 | 模块在第一次调用时加载 | 静态编译时加载 |
示例 | const module = require('x') | import module from 'x' |
5. Node.js 中的回调、Promise 和 async/await 有什么区别?
特性 | 回调 | Promise | async/await |
---|---|---|---|
核心思想 | 通过回调函数处理异步结果 | 链式调用处理异步结果 | 语法糖,基于 Promise |
代码风格 | 嵌套调用(容易回调地狱) | 平展链式结构 | 更接近同步代码结构 |
错误处理 | 需要手动检查并传递错误 | .catch() 捕获错误 | try...catch 捕获错误 |
示例 | fs.readFile(callback) | readFile().then() | const data = await readFile() |
6. Node.js 中的回调函数是什么? 请举例说明
-
回调函数:
一个函数作为参数传递给另一个函数,并在异步操作完成后执行。 -
示例:
const fs = require('fs'); fs.readFile('file.txt', 'utf-8', (err, data) => { if (err) throw err; console.log(data); // 在文件读取完成后执行 });
7. Node.js 中的定时器函数 setImmediate()
和 setTimeout()
有什么区别?
-
setImmediate()
:- 在事件循环的当前阶段完成后立即执行。
- 常用于 I/O 回调完成后的执行。
-
setTimeout()
:- 在指定的时间后执行。
- 常用于延迟任务。
-
优先级:
process.nextTick()
>setImmediate()
>setTimeout()
。 -
示例:
setTimeout(() => console.log('Timeout'), 0); setImmediate(() => console.log('Immediate')); console.log('Sync'); // 输出顺序:Sync -> Immediate -> Timeout
8. Node.js 的 process.nextTick()
有什么作用?
-
作用:
将回调函数添加到当前事件循环的下一次迭代中,比setImmediate()
优先级更高。 -
示例:
process.nextTick(() => console.log('Next Tick')); console.log('Sync'); // 输出顺序:Sync -> Next Tick
-
适用场景:
用于在当前任务完成后立即执行短时间任务。
9. 什么是 Node.js 中的事件发射器(EventEmitter)?它有什么作用?如何使用?
-
EventEmitter
:
Node.js 中的事件机制,用于实现事件的监听和触发。 -
作用:
- 允许模块之间基于事件进行通信。
- 常用于流、服务器等需要异步事件处理的场景。
-
示例:
const EventEmitter = require('events'); const emitter = new EventEmitter(); // 注册事件监听器 emitter.on('greet', (name) => console.log(`Hello, ${name}`)); // 触发事件 emitter.emit('greet', 'World');
10. 什么是 Node.js 中的事件循环?
-
事件循环:
Node.js 使用的机制,用于处理异步操作。事件循环是一个单线程的执行模型,协调异步 I/O 操作、回调、定时器和事件。 -
工作原理:
- Timers 阶段:执行
setTimeout
和setInterval
的回调。 - Pending Callbacks 阶段:执行 I/O 回调。
- Idle, Prepare 阶段:内部操作。
- Poll 阶段:执行新的 I/O 操作。
- Check 阶段:执行
setImmediate
。 - Close Callbacks 阶段:处理关闭事件的回调。
- Timers 阶段:执行
-
示例:
setTimeout(() => console.log('Timeout'), 0); setImmediate(() => console.log('Immediate')); process.nextTick(() => console.log('Next Tick')); // 输出顺序:Next Tick -> Immediate -> Timeout
Node.js 中的模块加载机制
Node.js的模块加载机制是其核心功能之一,它允许开发者将代码分割成多个模块,以便更好地组织和管理。Node.js的模块分为核心模块和文件模块两类:
-
核心模块:Node.js标准中提供的模块,如fs、http、net等。这些模块被编译成了二进制代码,具有最高的加载优先级。
-
文件模块:存储为单独的文件或文件夹的模块,可能是JS代码、JSON或编译好的C/C++代码。文件模块的加载有两种方式:按路径加载和查找node_modules文件夹。
- 按路径加载:如果require参数以“/”开头,表示以绝对路径查找;如果以“./”或“…/”开头,表示以相对路径查找。
- 查找node_modules文件夹:如果require参数不以“/”、“./”或“…/”开头,且不是核心模块,Node.js会在当前目录下的node_modules文件夹中查找模块,如果未找到,则向上层目录继续查找,直到根目录。
Node.js 全局对象
在Node.js中,全局对象指的是global,所有全局变量(除了global本身以外)都是global对象的属性。在Node.js中,我们不能在最外层定义变量,因为所有用户代码都是属于当前模块的,而模块本身不是最外层上下文。
Node.js 的事件循环机制
Node.js采用事件驱动和异步I/O的方式,实现了一个单线程、高并发的JavaScript运行时环境。其事件循环机制是其异步架构的核心,它不断地检查事件队列,并处理事件队列中的事件,如文件读写、网络请求等。事件循环的实现依赖于以下几个关键组件:
- V8引擎:用于解析JavaScript语法。
- Node API:为上层模块提供系统调用,一般是由C语言来实现,和操作系统进行交互。
- libuv:是跨平台的底层封装,实现了事件循环、文件操作等,是Node.js实现异步的核心。
当主线程空闲时,它会开始循环事件队列,检查是否有要处理的事件。如果是非I/O任务,主线程会亲自处理,并通过回调函数返回到上层调用;如果是I/O任务,则从线程池中拿出一个线程来处理这个事件,并指定回调函数。当线程中的I/O任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环。
npm
npm是Node.js官方提供的包管理工具,用于Node.js包的发布、传播、依赖控制。使用npm,可以方便地下载、安装、升级、删除包,也可以发布并维护自己的包。npm提供了命令行工具,允许用户从npm服务器下载别人编写的第三方包到本地使用,或者上传自己编写的包到npm服务器供别人使用。
使用npm管理项目依赖的步骤如下:
- 初始化项目:在项目根目录下运行
npm init
命令,根据提示输入项目信息,生成package.json文件。 - 安装依赖:使用
npm install 包名
命令安装项目所需的第三方包,这些包会被添加到package.json文件的dependencies字段中。 - 更新依赖:使用
npm update 包名
命令更新项目中的第三方包。 - 删除依赖:使用
npm uninstall 包名
命令删除项目中的第三方包。
package.json 文件
package.json是Node.js项目的描述文件,记录了项目的名称、版本、依赖等信息。它的作用包括:
- 定义项目信息:如项目名称、版本、作者、gitHub地址等。
- 定义脚本命令:如npm run build或自定义命令。
- 区分项目依赖和开发依赖:项目依赖在生产和开发阶段都需要,开发依赖仅用于开发阶段。
非阻塞I/O
非阻塞I/O是一种处理I/O操作的方式,它允许程序在等待I/O操作完成时继续执行其他任务,从而提高应用程序的响应性和吞吐量。Node.js采用了非阻塞I/O模型,这是其异步事件驱动架构的核心。Node.js使用libuv库来处理I/O操作,libuv是一个跨平台的异步I/O库,它在内部使用了多种技术来实现非阻塞I/O,包括事件循环、异步I/O操作、回调函数和线程池等。
在 Node.js 中创建一个简单的 HTTP 服务器
在Node.js中创建一个简单的HTTP服务器,可以使用Node.js内置的http模块。以下是一个简单的示例代码:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
在 Node.js 中处理 HTTP 请求的路由
在Node.js中处理HTTP请求的路由,可以通过比较请求的路径和URL来实现。以下是一个简单的示例代码,展示了如何处理不同的路由:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Home Page\n');
} else if (req.url === '/about') {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('About Page\n');
} else {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.end('Not Found\n');
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
在 Node.js 中解析 JSON 数据
在Node.js中解析JSON数据,可以使用JSON.parse()方法。以下是一个简单的示例代码,展示了如何解析JSON数据:
const fs = require('fs');
fs.readFile('data.json', 'utf8', (err, data) => {
if (err) throw err;
const jsonObj = JSON.parse(data);
console.log(jsonObj);
});
在 Node.js 中进行高效的日志处理,避免影响性能
在Node.js中进行高效的日志处理,可以考虑以下几点:
- 使用异步写入:避免阻塞事件循环,影响性能。可以使用fs.appendFile()的异步版本或引入第三方日志库(如winston、bunyan等)。
- 控制日志级别:根据需求设置不同的日志级别(如info、warn、error等),以便在需要时快速定位问题。
- 日志分割:按照时间、大小等因素对日志文件进行分割,避免单个日志文件过大或过于复杂。
- 日志轮转:定期轮转日志文件,避免日志文件无限增长。
- 远程日志:将日志发送到远程服务器或日志管理系统,以便集中管理和分析。
以上内容涵盖了Node.js的模块加载机制、全局对象、事件循环机制、npm的使用、package.json文件的作用、非阻塞I/O的实现、HTTP服务器的创建、HTTP请求路由的处理、JSON数据的解析以及高效的日志处理等方面。希望这些信息能帮助你更好地理解和使用Node.js。
计算机网络常见面试题:
计算机网络面试(一)
计算机网络面试(二)
计算机网络速成:
计算机网络速成一
计算机网络速成二
计算机网络速成三
2. HTTP 1.0 和 2.0 的区别
-
连接复用:
- HTTP/1.0 使用短连接(默认每个请求创建一个 TCP 连接)。
- HTTP/2.0 支持多路复用,一个 TCP 连接可以并发处理多个请求和响应。
-
头部压缩:
- HTTP/1.0 没有对请求头压缩。
- HTTP/2.0 使用 HPACK 算法对头部信息进行压缩,减少网络带宽占用。
-
二进制分帧:
- HTTP/1.0 使用纯文本协议传输数据。
- HTTP/2.0 使用二进制格式传输,数据分为帧(Frame),使传输更高效。
-
服务器推送:
- HTTP/1.0 不支持服务器推送。
- HTTP/2.0 允许服务器主动推送资源到客户端,减少等待时间。
3. TCP 连接是什么
TCP(传输控制协议,Transmission Control Protocol)是一种面向连接的、可靠的传输协议。TCP 连接是指通过三次握手建立的通信链路,用于保证数据的有序传输和完整性。
- 核心特点:
- 可靠传输:确保数据包按序到达且无丢失。
- 双向通信:通信双方可以同时发送和接收数据。
- 面向连接:需要在通信开始前建立连接(三次握手),并在结束时释放连接(四次挥手)。
TCP 主要用于需要高可靠性的应用场景,如文件传输、电子邮件、网页浏览等。
4. HTTP 2.0 和 3.0 的区别
-
传输层协议:
- HTTP/2.0 基于 TCP。
- HTTP/3.0 基于 QUIC(一种基于 UDP 的协议),提高了传输速度和效率。
-
连接管理:
- HTTP/2.0 使用 TCP,多路复用解决了大部分队头阻塞问题,但仍受 TCP 队头阻塞的影响。
- HTTP/3.0 通过 QUIC 协议完全避免了队头阻塞问题。
-
传输速度:
- HTTP/2.0 的建立连接过程包括 TCP 握手和 TLS 握手,延迟较高。
- HTTP/3.0 将加密(TLS)集成到 QUIC,减少了连接建立的延迟。
-
数据恢复:
- HTTP/2.0 中丢失一个数据包会影响整个 TCP 流。
- HTTP/3.0 的每个流独立处理丢包问题,不影响其他流。
5. HTTP 和 HTTPS 的区别
-
协议层次:
- HTTP 是超文本传输协议,默认端口 80。
- HTTPS 是在 HTTP 之上加入了 SSL/TLS 协议,用于加密通信,默认端口 443。
-
安全性:
- HTTP 的数据是明文传输,容易被窃听、篡改。
- HTTPS 对数据进行加密,提供身份验证和完整性,防止窃听和篡改。
-
性能影响:
- HTTPS 需要进行加密解密,会增加一些计算开销。
- 现代优化手段(如 TLS 1.3)使得性能影响微乎其微。
6. TCP 是用来解决什么问题
TCP 主要解决以下问题:
- 数据可靠性:
- 保证数据包按顺序到达。
- 丢失的数据包可以重传。
- 流量控制:
- 避免发送方传输速度过快导致接收方无法处理。
- 拥塞控制:
- 防止网络过载。
- 双向通信:
- 提供面向连接的双向可靠通信。
7. TCP 和 UDP 的区别
-
连接性:
- TCP 是面向连接的,需要建立连接(三次握手)。
- UDP 是无连接的,不需要握手,直接发送数据。
-
可靠性:
- TCP 提供可靠传输,数据丢失时可重传。
- UDP 不提供保证,可能出现数据丢失。
-
速度:
- TCP 较慢,适合需要可靠传输的场景(如文件传输、网页)。
- UDP 较快,适合对时延敏感的场景(如实时视频、游戏)。
-
数据传输:
- TCP 按序传输,保证数据完整性。
- UDP 不保证数据顺序。
8. TCP 的粘包和拆包问题
- 粘包:多条数据在传输过程中被接收方合并为一条。
- 拆包:一条数据在传输过程中被分成多条。
原因:
- 粘包:
- 发送方将多条数据连续发送,接收方一次性读取了多条。
- 发送数据长度小于发送缓冲区的大小。
- 拆包:
- 数据过大,超过了缓冲区大小,被拆分成多次发送。
解决方法:
3. 固定消息长度:预设每条消息的固定长度。
4. 分隔符:在每条消息后添加特殊的分隔符(如换行符)。
5. 消息头:消息开头加上长度字段,告知接收方本条消息的长度。
1. TCP 的三次握手
TCP 的三次握手是建立连接的过程,用来确保通信双方已经准备好,能可靠地进行数据传输。
- 第一步:客户端发送 SYN 报文,表示请求建立连接,同时初始序列号(Seq=x)。
- 第二步:服务器收到后,返回 SYN+ACK 报文,表示同意建立连接,同时发送自己的初始序列号(Seq=y),并确认客户端的序列号(Ack=x+1)。
- 第三步:客户端收到后,再次发送 ACK 报文,确认服务器的序列号(Ack=y+1),连接建立成功。
三次握手的目的:
- 确保双方的发送能力和接收能力正常。
- 同步序列号,初始化双方的数据传输状态。
2. TCP 的四次挥手
TCP 的四次挥手是断开连接的过程,确保双方都能安全地释放资源。
- 第一步:客户端发送 FIN 报文,表示不再发送数据,但还能接收数据。
- 第二步:服务器收到后,返回 ACK 报文,表示已接收到客户端的 FIN。
- 第三步:服务器发送 FIN 报文,表示不再发送数据。
- 第四步:客户端收到后,返回 ACK 报文,确认断开连接。
断开连接比建立连接多两次通信,因为 TCP 是全双工通信,双方需要分别确认对方不再发送数据。
3. TCP 为什么需要 TIME WAIT 状态
-
TIME WAIT 的作用:
- 确保最后的 ACK 报文被对方收到:如果服务器未收到 ACK,会重发 FIN。TIME WAIT 确保客户端能接收并处理这些重发的 FIN。
- 防止旧连接数据干扰新连接:等待足够时间,让网络中可能残留的旧数据包完全消失。
-
TIME WAIT 的持续时间:
通常是 2 倍的最大报文生存时间(2MSL)。
4. TCP 超时重传机制
-
解决的问题:
当数据包在传输中丢失或未及时收到确认时,通过超时重传机制重新发送,确保数据可靠传输。 -
机制流程:
- 发送数据包后启动计时器。
- 若在超时时间内未收到 ACK,重新发送数据。
- 每次超时重传会增大等待时间(指数退避算法)。
-
典型场景:
网络拥塞、传输路径中断等导致的确认丢失。
5. TCP 滑动窗口的作用
TCP 滑动窗口是一种流量控制机制,用于动态调整发送方能发送的最大数据量,以避免接收方或网络过载。
-
工作原理:
- 窗口大小由接收方设置,告知发送方当前可接收的数据量。
- 窗口随接收的确认数据滑动,允许发送方发送更多数据。
-
作用:
- 提高传输效率:允许发送方在等待 ACK 的同时继续发送数据。
- 避免拥塞:通过动态调整窗口大小控制发送速率。
6. TCP/IP 四层模型
TCP/IP 是网络通信协议的基础模型,共分为四层:
- 应用层:
- 提供应用程序间的通信接口。
- 协议:HTTP、FTP、SMTP、DNS。
- 传输层:
- 提供端到端的可靠或非可靠传输。
- 协议:TCP、UDP。
- 网络层:
- 负责路由和数据包的转发。
- 协议:IP、ICMP。
- 链路层:
- 负责数据帧的封装和物理传输。
- 包括以太网、Wi-Fi 等。
7. OSI 七层模型
OSI 是理论上的网络通信模型,共分为七层:
- 物理层:
- 传输比特流,定义硬件接口。
- 如网线、光纤。
- 数据链路层:
- 负责帧的传输和差错检测。
- 如以太网协议。
- 网络层:
- 负责寻址和路由选择。
- 如 IP 协议。
- 传输层:
- 提供端到端通信。
- 如 TCP、UDP。
- 会话层:
- 管理会话和数据同步。
- 表示层:
- 负责数据格式转换和加密。
- 应用层:
- 提供用户接口。
- 如 HTTP、FTP。
8. Cookie、Session、Token 的区别
特性 | Cookie | Session | Token |
---|---|---|---|
存储位置 | 浏览器(客户端) | 服务器 | 客户端,可能会临时存储在本地 |
状态保持 | 无状态(需要每次随请求发送) | 有状态(服务器存储会话信息) | 无状态(包含自身验证信息) |
生命周期 | 可持久(设定过期时间) | 短暂(默认关闭浏览器即失效) | 取决于设计,通常有过期时间 |
安全性 | 较低,容易被窃取和伪造 | 较高,数据存储在服务器端 | 较高,加密且不依赖服务器状态 |
使用场景 | 简单用户偏好、保持登录状态 | 用户认证、状态管理 | 分布式系统、API 调用 |
- Cookie:客户端存储数据,常用于保存用户偏好或会话信息。
- Session:服务器存储会话状态,客户端用 Cookie 或其他方式传递会话 ID。
- Token:自包含的加密数据,客户端携带 Token 即可完成身份验证,无需依赖服务器存储会话状态。