1.同一个git仓库,如何区分不同开发者权限
在同一个 Git 仓库中区分不同开发者的权限,主要依赖于代码托管平台(如 GitHub、GitLab、Gitee、Bitbucket 等)提供的权限管理功能,而非 Git 本身(Git 是分布式版本控制系统,本身不直接管理权限,仅通过分支保护等机制间接控制)。以下是具体实现方式:
一、核心权限管理方式:基于平台的角色与权限分配
主流代码托管平台都支持通过 “角色” 划分权限,不同角色拥有不同操作权限(如读、写、管理等)。以 GitHub 和 GitLab 为例,常见角色及权限如下:
| 平台 | 角色(从低到高) | 核心权限 |
|---|---|---|
| GitHub | 访客(Guest)→ 报告者(Reporter)→ 开发者(Developer)→ 维护者(Maintainer)→ 所有者(Owner) | - 访客:仅查看公共内容;- 开发者:可提交代码、创建分支;- 维护者:可合并 PR、管理分支保护;- 所有者:完全控制仓库(包括删除仓库)。 |
| GitLab | 访客(Guest)→ 报告者(Reporter)→ 开发者(Developer)→ 维护者(Maintainer)→ 所有者(Owner) | 类似 GitHub,额外支持更细粒度的权限(如是否允许强制推送、是否允许删除分支等)。 |
| Gitee | 观察者 → 开发者 → 管理员 → 所有者 | 基本逻辑与上述平台一致,适配国内用户习惯。 |
二、具体操作:如何配置开发者权限
以 GitHub 为例,步骤如下:
-
进入仓库权限设置仓库主页 →
Settings→Access→Collaborators(协作者)或Teams(团队,适合多开发者管理)。 -
添加开发者并分配角色
- 单个开发者:通过邮箱或用户名添加,选择角色(如
Developer或Maintainer)。 - 团队管理:先在组织(Organization)中创建团队(如 “前端组”“后端组”),分配角色给团队,再将开发者加入对应团队(适合多人协作,批量管理权限)。
- 单个开发者:通过邮箱或用户名添加,选择角色(如
-
权限生效被添加的开发者会收到邀请,接受后即可按分配的角色操作仓库(如
Developer可推送代码到非保护分支,Maintainer可审批并合并 PR)。
三、进阶:通过分支保护强化权限控制
即使同是 “开发者” 角色,也可通过分支保护规则限制对关键分支(如 main、dev)的操作,实现更细粒度的权限区分:
-
设置分支保护仓库 →
Settings→Branches→Add rule(添加分支规则),指定需要保护的分支(如main)。 -
常用保护规则(按需配置)
- 禁止直接推送:不允许任何人直接向
main分支推送代码,必须通过 PR(Pull Request)提交。 - 强制代码审查:PR 必须经过指定数量的审核者(如 1 名维护者)批准后才能合并。
- 禁止强制推送:防止开发者用
git push --force覆盖历史提交。 - 指定可合并者:仅允许特定角色(如
Maintainer)或用户合并 PR 到保护分支。
例如:让 “开发者” 只能提交 PR 到
main分支,但无法直接合并,必须由 “维护者” 审核通过后才能合并。 - 禁止直接推送:不允许任何人直接向
四、企业级场景:结合 LDAP/SSO 与精细化权限
在大型团队或企业中,通常会结合以下方式进一步管理权限:
-
统一身份认证通过 LDAP(轻量目录访问协议)或 SSO(单点登录)将企业内部账号与代码平台关联,集中管理用户身份(避免重复创建账号)。
-
基于项目 / 模块的权限划分对于多模块仓库,可通过平台的 “代码所有者(CODEOWNERS)” 功能,指定某模块的代码只能由特定团队 / 开发者审核(如
src/frontend/目录的 PR 必须由前端团队审核)。-
实现方式:在仓库根目录创建
CODEOWNERS文件,格式如下:plaintext
# CODEOWNERS 文件 src/frontend/ @frontend-team # 前端目录由 frontend-team 团队审核 src/backend/ @backend-lead # 后端目录由 backend-lead 个人审核
-
五、核心原则总结
- 最小权限原则:给开发者分配 “刚好够用” 的权限(如普通开发者仅需
Developer角色,无需Maintainer)。 - 关键分支保护:对
main等生产环境分支严格限制,通过 PR + 审核机制控制代码合并。 - 团队化管理:通过 “团队” 而非单个用户分配权限,简化人员变动时的权限调整(如员工离职时只需从团队中移除)。
不同平台的操作细节略有差异,但核心逻辑一致:通过 “角色分配 + 分支保护 + 审核机制” 三层控制,实现开发者权限的精细化区分。
-
最小权限原则:只授予完成工作所需的最小权限
-
代码审查:强制要求代码审查,特别是对主分支的修改
-
分支策略:
main:受保护,只能通过 PR 合并develop:开发分支,团队负责人可合并feature/*:功能分支,开发者有完全权限
-
自动化检查:通过 CI/CD 自动化权限验证
-
定期审计:定期审查权限设置和访问日志
-
文档化:明确记录各角色的权限范围
选择哪种方案取决于你的团队规模、安全要求和使用的 Git 服务平台。对于小型团队,GitHub/GitLab 的内置功能通常足够;对于大型企业,可能需要 Gerrit + LDAP 的完整解决方案。
2.有没有发布过npm包,本地如何调试npm包,npm link
| 方法 | 适用场景 | 优点 | 注意事项 |
|---|---|---|---|
npm link | 开发阶段频繁修改、需要热更新 | 软链接,源码变动实时同步-1-3-5 | 可能遇到依赖重复问题(如React多实例)-7-8 |
npm pack + 本地安装 | 测试最终发布包,模拟真实安装 | 最接近线上发布后的体验,能暴露入口、依赖问题-7 | 每次修改需重新打包和安装-7 |
yalc | 解决npm link依赖问题 | 模拟发布流程,更好地处理依赖关系-8 | 需全局安装,多一个工具链-8 |
| Monorepo (workspace) | 大型项目,多包协同开发 | 依赖管理统一,适合复杂项目结构-7 | 配置有一定复杂度-7 |
🔗 详解 npm link 与操作步骤
npm link 的核心是在系统中创建软链接(符号链接) ,使你能在本地项目中引用并实时测试另一个尚未发布的本地npm包-3-6。
工作原理
- 在npm包目录执行
npm link,它会在全局的node_modules目录下创建一个软链接,指向你的本地包-6-9-10。 - 在测试项目目录执行
npm link <package-name>,它会从全局node_modules找到这个包,并在当前项目的node_modules里创建指向全局链接的软链接-6-9-10。这样就形成了一个完整的链:测试项目 → 全局链接 → 本地包源码。
具体操作步骤
-
创建包的全局链接:
进入你正在开发的npm包的根目录,执行:bash
复制下载
npm link -
在项目中链接包:
进入你的测试项目根目录,执行:bash
复制下载
npm link <package-name> -
验证与使用:
你可以到测试项目的node_modules目录下查看,对应的包应该是一个快捷方式(在Linux/macOS终端中显示为链接,在Windows文件管理器中也可能有特殊图标)-9。之后,你就可以在测试项目中像使用普通安装的包一样引入和使用这个本地包了-3-5。 -
取消链接:
调试完成后,在测试项目目录下执行:bash
复制下载
npm unlink <package-name>
⚠️ npm link 常见问题与解决
-
依赖问题与重复实例:这是
npm link最常遇到的问题。如果你的包和测试项目都依赖了同一个库(例如react),它们可能会分别加载自己node_modules下的版本,导致出现"Invalid hook call"这类错误-7-8。 -
类型声明文件不生效:在TypeScript项目中,链接后可能找不到类型定义。
- 解决方案:检查包的
package.json,确保"types"字段(例如"types": "dist/index.d.ts")正确指向了编译后的声明文件位置-7。有时需要重启你的IDE或开发服务器。
- 解决方案:检查包的
🛠️ 其他调试方法
1. 使用 npm pack (推荐用于最终测试)
这个方法会生成一个.tgz压缩包,模拟了发布到npm registry的包-7。
bash
复制下载
# 在npm包目录下
npm run build # 先构建你的包
npm pack # 生成 .tgz 文件
# 在测试项目目录下
npm install /path/to/your/package/your-package-1.0.0.tgz
优点是能最真实地模拟实际安装场景,有助于发现package.json配置(如入口文件、依赖声明)等问题-7。缺点是每次修改都需要重复以上步骤。
2. 使用 yalc (解决依赖问题)
yalc 可以看作一个功能更强的 npm link,它会把包的依赖也提升到项目层面,有助于避免依赖重复-8。
-
全局安装
yalc:bash
复制下载
npm i yalc -g -
在包目录发布:
bash
复制下载
yalc publish -
在项目目录链接:
bash
复制下载
yalc link <package-name>包有更新时,在包目录执行
yalc push,项目就能收到更新-8。
💎 总结
如何选择本地调试方法,主要看你的需求:
- 对于日常开发和高频调试,
npm link因其实时性是首选,但需留意潜在的依赖冲突。 - 在准备发布版本前,强烈建议用
npm pack做一次完整测试,确保万无一失-7。 - 如果饱受
npm link的依赖问题困扰,不妨试试yalc-8。 - 对于大型 monorepo 项目,利用 workspace 功能管理依赖会更高效-7。
3.http2新特性,为啥没有阻塞
HTTP/2 的以下几个核心特性:
1. 二进制分帧
这是 HTTP/2 所有高级功能的基础。它不再使用 HTTP/1.x 的纯文本格式(难以解析且容易出错),而是将通信分解为更小的消息和帧,并采用二进制格式对其进行编码。
- 帧:最小的通信单位,每个帧都有一个流ID,用于标识它属于哪个流。
- 消息:一个完整的请求或响应,由一个或多个帧组成(例如 HEADERS 帧和 DATA 帧)。
这种二进制分帧层,使得多路复用成为可能。
2. 多路复用
基于二进制分帧,HTTP/2 可以在一个单一的 TCP 连接上同时交错发送多个请求和响应。
- 每个请求/响应都被分配一个唯一的流ID。
- 帧可以乱序发送,但接收方可以通过流ID将它们重新组装成完整的消息。
- 这意味着: 服务器可以同时处理多个请求,哪个资源的响应先准备好,就可以先发送哪个资源的帧回给浏览器。慢响应不会再阻塞快响应。
这就解决了 HTTP/1.1 的队头阻塞问题。
3. 流优先级
客户端可以在发送请求时指定流的优先级(依赖关系和权重),服务器可以根据这些优先级来决定分配资源的顺序。这确保了更重要的资源(如 HTML、CSS)被优先传输,提升页面加载性能。
4. 服务器推送
服务器可以“预测”客户端的需求,在客户端明确请求之前,就主动向客户端推送资源。例如,当客户端请求 index.html 时,服务器可以主动把 style.css 和 app.js 推送给客户端,避免额外的请求往返。
5. HPACK 头部压缩
HTTP/1.x 的头部是纯文本且不压缩的,尤其是带有 cookie 的头部,体积很大。HTTP/2 使用专门的 HPACK 压缩算法来压缩头部,减少了开销。
重要的澄清:HTTP/2 并非完全没有阻塞
虽然 HTTP/2 解决了 应用层(HTTP)的队头阻塞,但它仍然运行在 TCP 协议之上。而 TCP 是一个按顺序传输字节流的协议。
- TCP 层队头阻塞:如果 TCP 数据包在传输过程中丢失或延迟,那么整个 TCP 连接必须等待这个丢失的包被重传和确认,即使后续的包中包含了其他 HTTP 流的完整数据。这个丢失的包就会阻塞所有流的进度。
- 情况严重性:在网络丢包率较高的环境下,TCP 队头阻塞的影响会变得明显,HTTP/2 的性能优势可能会被抵消一部分。
这正是 HTTP/3 和 QUIC 协议要解决的问题。HTTP/3 弃用了 TCP,转而基于 UDP 实现了新的 QUIC 传输协议。QUIC 在传输层原生实现了多路复用,使得每个流都是独立的,单个数据包的丢失只会影响该包所属的流,而不会阻塞其他流。
总结对比
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 传输协议 | TCP | TCP | QUIC (over UDP) |
| 连接管理 | 多个连接 | 单个连接 | 单个连接 |
| 资源传输 | 顺序、阻塞 | 多路复用、非阻塞 | 多路复用、非阻塞 |
| 队头阻塞 | 应用层和传输层都有 | 仅传输层有 | 基本解决 |
| 头部压缩 | 无 | HPACK | QPACK |
| 建立连接 | TCP握手 + TLS握手 | TCP握手 + TLS握手 | QUIC握手(1 RTT/0 RTT) |
结论:
HTTP/2 通过 二进制分帧 和 多路复用 技术,在 一个 TCP 连接 上实现了并发交互,彻底解决了 HTTP/1.1 层面的队头阻塞问题,使得网络连接的使用效率得到了极大的提升,感觉上“没有阻塞了”。但它仍然受制于底层 TCP 协议的局限性,而这个局限性则由 HTTP/3 来最终解决。
4.vue hooks和mixin区别
Vue 中的 Hooks(组合式 API) 和 Mixin(混入) 都是用于代码复用的方案,但在设计理念、使用方式和解决的问题上有本质区别。尤其是 Vue 3 推出的组合式 API(Hooks 风格),正是为了弥补 Mixin 的缺陷而设计的。
核心区别对比
| 维度 | Mixin(混入) | Hooks(组合式 API) |
|---|---|---|
| 设计理念 | 基于 “选项合并”,将多个组件的选项(如 data、methods)合并到一个组件中。 | 基于 “逻辑组合”,将独立的逻辑封装为函数,通过调用函数在组件中复用逻辑。 |
| 逻辑组织 | 按 “选项类型” 拆分(如 data 放数据,methods 放方法),分散在不同选项中。 | 按 “功能逻辑” 聚合(如一个 Hooks 包含某功能的所有数据、方法、生命周期),逻辑内聚。 |
| 命名冲突 | 容易冲突(如两个 Mixin 定义同名 data 或方法,会被覆盖或合并,难以追溯)。 | 无冲突风险(通过变量名显式引用,由开发者主动管理命名)。 |
| 逻辑间依赖 | 难以处理 Mixin 之间的依赖关系(隐式依赖,代码晦涩)。 | 可通过函数参数和返回值显式传递依赖,逻辑关系清晰。 |
| 类型支持 | 对 TypeScript 支持差(合并选项的类型难以推断)。 | 天然支持 TypeScript,类型推断清晰,IDE 友好。 |
| 代码可读性 | 组件中引入多个 Mixin 后,难以区分哪些逻辑来自哪个 Mixin(“黑盒复用”)。 | 逻辑来源明确(通过函数调用引入),代码追踪清晰(“白盒复用”)。 |
详细对比说明
1. 代码复用方式
-
Mixin:通过
mixins: [mixinA, mixinB]引入,Vue 会自动合并 Mixin 中的选项与组件自身选项。例如:javascript
运行
// mixin.js export const logMixin = { data() { return { count: 0 }; }, methods: { increment() { this.count++; } }, mounted() { console.log('mixin mounted'); } }; // 组件中使用 import { logMixin } from './mixin'; export default { mixins: [logMixin], mounted() { console.log('component mounted'); } // 会与 mixin 的 mounted 合并执行 };合并规则复杂(如
data浅合并,methods同名覆盖,生命周期钩子数组化),且逻辑分散在不同选项中。 -
Hooks:将逻辑封装为一个函数,组件通过调用函数获取逻辑相关的变量和方法,完全由开发者控制如何使用。例如:
javascript
运行
// useCounter.js(Hooks) import { ref, onMounted } from 'vue'; export function useCounter() { const count = ref(0); const increment = () => { count.value++; }; onMounted(() => { console.log('hooks mounted'); }); return { count, increment }; // 暴露需要的变量和方法 } // 组件中使用 import { useCounter } from './useCounter'; export default { setup() { const { count, increment } = useCounter(); // 显式获取逻辑 onMounted(() => { console.log('component mounted'); }); return { count, increment }; } };逻辑集中在一个函数内,引入和使用都显式可见。
2. 命名冲突问题
-
Mixin:多个 Mixin 或 Mixin 与组件自身若有同名的
data、methods或生命周期,会触发合并规则(如方法覆盖、数据浅合并),且冲突发生时难以定位来源。例如:javascript
运行
// mixinA.js export const mixinA = { data() { return { name: 'A' }; } }; // mixinB.js export const mixinB = { data() { return { name: 'B' }; } }; // 组件中同时引入,最终 name 会是 'B'(后引入的覆盖先引入的) export default { mixins: [mixinA, mixinB] }; -
Hooks:逻辑通过变量名显式接收,是否重名由开发者决定,不存在隐式覆盖。例如:
javascript
运行
// 引入两个可能有冲突的 Hooks 时,可通过重命名避免冲突 const { count: counter1 } = useCounter(); const { count: counter2 } = useCounter(); // 完全独立,无冲突
3. 逻辑依赖与可读性
- Mixin:Mixin 之间若有依赖(如
mixinB依赖mixinA的数据),这种依赖是隐式的,代码中无法直接看出,维护时需要跳转多个文件分析,可读性差。 - Hooks:逻辑依赖通过函数参数显式传递,例如
useUser(auth)表示useUser依赖auth逻辑,依赖关系一目了然。同时,组件中调用的 Hooks 清晰展示了复用的逻辑,代码追踪更简单。
4. 类型支持(Vue 3 + TypeScript)
- Mixin:Mixin 的选项合并机制导致 TypeScript 难以推断组件最终的类型(如
this的类型可能被多个 Mixin 修改),类型提示不准确,容易出现类型错误。 - Hooks:函数式设计天然适配 TypeScript,Hooks 的返回值类型、参数类型都可明确定义,组件中使用时能获得精确的类型提示,减少类型错误。
何时使用?
- Mixin:仅在 Vue 2 项目中,且复用逻辑简单、无复杂依赖时使用(不推荐在 Vue 3 中使用)。
- Hooks:Vue 3 首选方案,尤其是复杂逻辑复用、需要明确依赖关系或使用 TypeScript 时,完全替代 Mixin。
总结
Mixin 是 Vue 2 时代的产物,基于 “选项合并” 实现复用,存在命名冲突、依赖模糊、类型支持差等问题;而 Hooks(组合式 API)基于 “逻辑组合”,通过函数封装逻辑,解决了 Mixin 的所有缺陷,是 Vue 3 中更优的代码复用方案。随着 Vue 3 的普及,Mixin 已逐渐被 Hooks 取代。
5.vue3新特性
6.性能优化
7.兼容性问题
8.手机1px问题如何解决
9.自适应如何做
自适应设计(Adaptive Design)的核心是让页面在不同设备(手机、平板、PC 等)上都能合理展示,提升用户体验。常见实现方案有 响应式布局、媒体查询、弹性布局 等,结合具体场景可组合使用。以下是详细实现方法:
一、核心原则:从 “固定尺寸” 到 “弹性适配”
- 避免固定像素(px) :优先使用相对单位(
%、em、rem、vw/vh)。 - 流式布局:让元素宽度随容器自适应,而非固定宽度。
- 断点设计:针对不同设备尺寸(如手机、平板、桌面)定义布局规则。
二、具体实现方案
1. 基础:使用相对单位
-
%(百分比) :基于父元素尺寸的比例(如width: 50%表示占父元素宽度的一半)。css
.container { width: 100%; max-width: 1200px; margin: 0 auto; } /* 最大宽度限制,避免大屏过宽 */ .left { width: 30%; float: left; } .right { width: 70%; float: right; } -
rem(根元素字体大小) :基于<html>标签的font-size(适合字体和整体缩放)。css
html { font-size: 16px; } /* 1rem = 16px(默认) */ @media (max-width: 768px) { html { font-size: 14px; } /* 小屏幕缩小字体 */ } .title { font-size: 1.5rem; } /* 24px(大屏)/21px(小屏) */ -
vw/vh(视口单位) :基于浏览器视口尺寸(1vw = 视口宽度的1%,1vh = 视口高度的1%)。css
.banner { height: 50vh; } /* 高度始终为屏幕的一半 */ .box { width: 80vw; max-width: 600px; } /* 宽度为屏幕80%,最大600px */
2. 核心:媒体查询(@media)实现断点适配
媒体查询是响应式设计的基础,通过检测设备宽度(或其他特性)应用不同样式。常见断点参考:
- 移动端:
max-width: 767px - 平板:
min-width: 768px and max-width: 1023px - 桌面:
min-width: 1024px
css
/* 基础样式(默认适配移动端) */
.nav { display: none; } /* 移动端隐藏导航栏 */
.menu-btn { display: block; } /* 显示汉堡菜单 */
/* 平板及以上设备 */
@media (min-width: 768px) {
.nav { display: flex; } /* 显示导航栏 */
.menu-btn { display: none; } /* 隐藏汉堡菜单 */
}
/* 桌面设备 */
@media (min-width: 1024px) {
.container { padding: 0 20px; } /* 增加内边距 */
}
技巧:采用 “移动优先” 策略(先写移动端样式,再用 min-width 扩展大屏样式),避免样式覆盖混乱。
3. 弹性布局:Flexbox(一维布局)
Flexbox 适合处理行 / 列内的元素对齐、分配空间,是自适应布局的核心工具。
css
/* 导航栏在不同屏幕的自适应 */
.nav {
display: flex;
flex-wrap: wrap; /* 空间不足时自动换行 */
justify-content: space-between; /* 元素两端对齐 */
align-items: center; /* 垂直居中 */
}
.nav-item {
flex: 1; /* 平均分配剩余空间 */
min-width: 120px; /* 最小宽度,避免过窄 */
}
4. 网格布局:Grid(二维布局)
Grid 适合整体页面布局(如多列网格),在复杂布局中比 Flexbox 更高效。
css
/* 响应式网格:大屏3列,中屏2列,小屏1列 */
.grid {
display: grid;
grid-template-columns: 1fr; /* 默认1列 */
gap: 16px;
}
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(2, 1fr); } /* 2列 */
}
@media (min-width: 1024px) {
.grid { grid-template-columns: repeat(3, 1fr); } /* 3列 */
}
5. 图片自适应
图片是常见的适配难点,需避免在小屏显示过大图片或在大屏模糊。
css
/* 图片自适应容器,不超出父元素 */
img {
max-width: 100%; /* 宽度不超过容器 */
height: auto; /* 高度自动,保持比例 */
}
/* 不同屏幕加载不同尺寸图片(推荐) */
<picture>
<source srcset="large.jpg" media="(min-width: 1024px)"> <!-- 大屏用大图 -->
<source srcset="medium.jpg" media="(min-width: 768px)"> <!-- 中屏用中图 -->
<img src="small.jpg" alt="自适应图片"> <!-- 小屏用小图(默认) -->
</picture>
6. 框架工具:快速实现自适应
-
UI 框架:Bootstrap(栅格系统)、Tailwind CSS(响应式工具类)等提供现成的自适应组件。例:Tailwind 响应式类(
sm:小屏,md:中屏,lg:大屏):html
预览
<div class="w-full md:w-1/2 lg:w-1/3"> <!-- 小屏占满宽,中屏半宽,大屏1/3宽 --> </div> -
栅格系统:将页面宽度分为 12 等份,通过类名控制元素占比(如
col-md-6表示中屏占 6/12)。
三、常见问题与解决方案
- 内容溢出:用
overflow: auto处理小屏内容溢出,或通过媒体查询调整字体 / 间距。 - 触摸目标大小:移动端按钮 / 链接最小尺寸建议
48px × 48px,避免点击困难。 - 横屏适配:通过
@media (orientation: landscape)单独处理横屏样式。
总结
自适应设计的核心流程:
- 用 相对单位 替代固定像素;
- 用 媒体查询 定义不同设备的断点样式;
- 用 Flexbox/Grid 实现弹性布局;
- 结合 图片自适应 和 框架工具 提升效率。
通过这些方法,可让页面在从手机到大屏的所有设备上都保持良好的布局和交互体验。
10.强缓存和协商缓存怎么区分,发了几次请求
强缓存和协商缓存的核心区别是 是否需要与服务器通信验证缓存有效性,对应的请求次数也因此不同,用一句话总结:强缓存命中则 0 次请求,协商缓存无论命中与否都至少 1 次请求(仅返回状态码,不返回资源体)。
一、核心区别:强缓存 vs 协商缓存
| 维度 | 强缓存 | 协商缓存 |
|---|---|---|
| 核心机制 | 客户端直接判断缓存是否过期,不发请求 | 客户端需发请求到服务器,由服务器判断缓存是否有效 |
| 判断依据 | 本地缓存的 Expires 或 Cache-Control 字段 | 服务器返回的 Last-Modified/If-Modified-Since 或 ETag/If-None-Match 字段 |
| 请求次数 | 缓存命中:0 次;未命中 / 过期:1 次(请求资源) | 无论命中与否:1 次(仅验证缓存,命中则返回 304,不返回资源体) |
| 服务器参与 | 不参与(仅首次请求后交互) | 必须参与(每次验证都需服务器响应) |
| 状态码 | 命中:200 OK(from disk cache/memory cache);未命中:200 OK | 命中:304 Not Modified;未命中:200 OK |
| 更新灵活性 | 较低(依赖过期时间,无法实时更新) | 较高(服务器可实时决定是否使用缓存) |
二、具体逻辑与请求次数拆解
1. 强缓存:优先本地判断,跳过服务器
-
首次请求:客户端无缓存,发送 1 次请求到服务器。服务器返回资源时,在响应头带上
Cache-Control(如max-age=3600,表示缓存 1 小时)或Expires(绝对过期时间)。 -
后续请求:
- 若缓存未过期(本地判断
max-age未到或当前时间 <Expires):命中强缓存,客户端直接使用本地缓存,不发送任何请求(0 次)。 - 若缓存已过期:强缓存失效,客户端发送 1 次请求,重新获取资源(同首次请求)。
- 若缓存未过期(本地判断
2. 协商缓存:必须问服务器,验证后再用
-
首次请求:客户端无缓存,发送 1 次请求到服务器。服务器返回资源时,在响应头带上
Last-Modified(资源最后修改时间)和ETag(资源唯一标识,如哈希值)。 -
后续请求:
-
客户端发送 1 次 “验证请求”,在请求头带上
If-Modified-Since(对应Last-Modified)或If-None-Match(对应ETag)。 -
服务器验证:
- 缓存有效(资源未修改):返回
304 Not Modified,不返回资源体(协商缓存命中,仅 1 次请求)。 - 缓存无效(资源已修改):返回
200 OK和新资源(协商缓存未命中,仅 1 次请求,且返回完整资源)。
- 缓存有效(资源未修改):返回
-
三、关键补充:两者的配合使用
实际场景中,强缓存和协商缓存通常一起生效,流程如下:
- 客户端先判断强缓存是否命中(优先用
Cache-Control,因优先级高于Expires)。 - 强缓存命中 → 直接用本地缓存(0 次请求)。
- 强缓存过期 → 触发协商缓存,发送验证请求到服务器(1 次请求)。
- 服务器判断缓存有效 → 304(命中协商缓存);无效 → 200(返回新资源)。
总结
- 强缓存:“自给自足”,命中则 0 次请求,效率高但灵活性低。
- 协商缓存:“请示服务器”,必发 1 次请求,灵活性高但需服务器参与。
- 核心区分点:是否需要与服务器通信,以及对应的请求次数和状态码。
最佳实践:
- 静态资源(JS/CSS/图片)使用强缓存 + 文件名哈希
- HTML文件使用协商缓存确保及时更新
- API接口根据需求使用
no-cache或no-store
11.vue和react区别
Vue 和 React 作为当前最流行的两大前端框架,在设计理念、API 风格、生态等方面有显著差异,以下从核心维度对比分析:
一、设计理念:“渐进式” vs “组件化”
- Vue:强调 “渐进式框架” ,核心库只关注视图层(View),开发者可根据需求逐步引入路由(Vue Router)、状态管理(Vuex/Pinia)等工具,灵活度高,适合从简单页面到复杂应用的渐进式开发。设计目标是 “易用性” ,尽量降低学习成本,模板语法接近原生 HTML,对新手友好。
- React:以 “组件化” 和 “声明式” 为核心,将 UI 拆分为独立可复用的组件,通过 JSX 实现 “HTML in JS”,强调用函数式思维描述 UI(如纯函数组件、状态不可变)。设计目标是 “灵活性和性能” ,更偏向底层库,生态工具(路由 React Router、状态管理 Redux)需额外整合,适合构建复杂大型应用。
二、核心语法:模板 vs JSX
-
Vue:
-
推荐使用 HTML 模板 描述 UI(
.vue单文件组件的<template>部分),模板中可直接使用指令(如v-if、v-for、v-bind)处理逻辑和绑定,语法贴近原生 HTML。 -
示例:
vue
<template> <div> <p v-if="show">{{ message }}</p> <button @click="handleClick">点击</button> </div> </template> <script> export default { data() { return { show: true, message: 'Hello' } }, methods: { handleClick() { this.show = !this.show; } } } </script> -
也支持 JSX,但非官方推荐,模板是主流方案。
-
-
React:
-
强制使用 JSX(JavaScript XML)描述 UI,将 HTML 逻辑嵌入 JavaScript 中,通过 JavaScript 表达式处理条件渲染、循环等(如
{condition && <p>...</p>}、{list.map(...)})。 -
示例:
jsx
function App() { const [show, setShow] = React.useState(true); const [message] = React.useState('Hello'); const handleClick = () => setShow(!show); return ( <div> {show && <p>{message}</p>} <button onClick={handleClick}>点击</button> </div> ); } -
JSX 本质是
React.createElement的语法糖,完全融入 JavaScript 生态。
-
三、状态管理与响应式
-
Vue:
- 响应式系统基于 Proxy(Vue 3)/Object.defineProperty(Vue 2) ,自动追踪数据依赖,数据变化时自动更新 DOM(双向绑定是常见特性,如
v-model简化表单处理)。 - 组件内状态通过
data()返回对象,修改时直接赋值(this.count = 1),无需手动触发更新。 - 全局状态管理:Vuex/Pinia(专为 Vue 设计,与框架深度集成)。
- 响应式系统基于 Proxy(Vue 3)/Object.defineProperty(Vue 2) ,自动追踪数据依赖,数据变化时自动更新 DOM(双向绑定是常见特性,如
-
React:
- 状态管理基于 “状态不可变” 原则,组件内状态通过
useState或useReducer定义,修改时必须通过 setter 函数(setCount(prev => prev + 1)),且不会自动触发子组件更新(需通过 props 传递或上下文 Context)。 - 不支持双向绑定,表单处理需手动绑定
value和onChange。 - 全局状态管理:Redux、MobX、Zustand 等(第三方库,与 React 松耦合)。
- 状态管理基于 “状态不可变” 原则,组件内状态通过
四、虚拟 DOM 与渲染优化
-
Vue:
- 虚拟 DOM 结合 编译时优化(Vue 3 核心提升):模板编译阶段会标记静态节点、计算依赖(Patch Flags),运行时只更新变化的部分,减少 diff 开销。
- 无需手动优化,框架自动处理大部分场景。
-
React:
- 虚拟 DOM 依赖 运行时 diff,默认会对整个组件树进行比对,可能存在冗余计算。
- 需手动使用
React.memo、useMemo、useCallback等 API 优化渲染性能(避免不必要的重渲染)。
五、生态与社区
-
Vue:
- 生态 “官方主导” ,路由(Vue Router)、状态管理(Pinia)、构建工具(Vite)等核心工具由官方维护,一致性高,文档清晰(中文文档友好)。
- 适合中小型项目、快速开发,国内企业(阿里、腾讯等)使用广泛。
-
React:
- 生态 “社区驱动” ,官方只维护核心库,路由、状态管理等依赖社区方案,灵活性高但需自行选型。
- 适合大型复杂应用(如 Facebook、Instagram),在国外及前端工程师中普及率更高,与 React Native 无缝衔接(跨端开发优势)。
六、学习曲线
- Vue:入门简单,模板语法直观,指令和 API 设计贴近开发者直觉,适合新手快速上手;深入学习(如响应式原理、编译优化)有一定难度,但整体门槛较低。
- React:入门需理解 JSX、函数式编程、状态不可变等概念,初期可能因 “一切皆 JavaScript” 感到抽象;但掌握后灵活性更高,尤其适合熟悉 JavaScript 进阶特性的开发者。
总结:核心差异对比表
| 维度 | Vue | React |
|---|---|---|
| 核心理念 | 渐进式框架,易用性优先 | 组件化 + 声明式,灵活性优先 |
| UI 描述方式 | 模板(HTML 为主)+ 指令 | JSX(JavaScript 为主) |
| 响应式机制 | 自动追踪依赖(Proxy/Object.defineProperty) | 手动触发更新(setState/useState) |
| 状态管理 | 双向绑定(v-model),官方 Pinia | 单向数据流,第三方库(Redux 等) |
| 渲染优化 | 编译时自动优化 | 需手动使用 memo/useMemo 等 |
| 生态 | 官方主导,一致性高 | 社区驱动,灵活性高 |
| 跨端能力 | Vue Native(相对较弱) | React Native(成熟稳定) |
选择建议:追求快速开发、低学习成本选 Vue;偏好 JavaScript 深度整合、构建大型应用或跨端开发选 React。
12.jsx和vue模板编译区别
JSX(React 等框架使用)和 Vue 模板(.vue 文件中的 <template>)是两种不同的 UI 描述方式,它们的编译逻辑、目标产物和优化策略有显著差异,核心区别体现在 “与 JavaScript 的融合程度” 和 “编译时优化的侧重点” 上。
一、编译目标:从 “描述” 到 “可执行代码”
两者最终都会被编译为操作虚拟 DOM(或真实 DOM)的 JavaScript 代码,但路径和产物形式不同:
1. JSX 的编译:“HTML-in-JS” → 函数调用
JSX 本质是 React.createElement(或框架等效 API)的语法糖,编译过程是将 XML -like 语法直接转换为 JavaScript 函数调用,完全融入 JavaScript 执行流。
例如,一段 React JSX:
jsx
<div className="container">
{show && <p>Hello {name}</p>}
<button onClick={handleClick}>Click</button>
</div>
编译后(简化版):
javascript
运行
React.createElement(
"div",
{ className: "container" },
show && React.createElement("p", null, "Hello ", name),
React.createElement("button", { onClick: handleClick }, "Click")
);
核心特点:
- 编译过程仅负责语法转换(JSX → 函数调用),不涉及逻辑分析或优化。
- 最终产物是动态执行的 JavaScript 代码,UI 逻辑(条件、循环)完全依赖 JavaScript 本身的语法(
&&、map等)。
2. Vue 模板的编译:“HTML 模板” → 优化后的渲染函数
Vue 模板是独立的 HTML 片段,编译过程更复杂,会经过 解析(Parse)→ 优化(Optimize)→ 生成(Generate) 三个阶段,最终产出带有静态优化标记的渲染函数。
例如,一段 Vue 模板:
vue
<template>
<div class="container">
<p v-if="show">Hello {{ name }}</p>
<button @click="handleClick">Click</button>
</div>
</template>
编译后(Vue 3 简化版):
javascript
运行
function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", { class: "container" }, [
_ctx.show
? (_openBlock(), _createElementBlock("p", null, "Hello " + _toDisplayString(_ctx.name), 1 /* TEXT */))
: _createCommentVNode("v-if", true),
(_openBlock(), _createElementBlock("button", { onClick: _ctx.handleClick }, "Click", 8 /* PROPS */, ["onClick"]))
]))
}
核心特点:
- 编译时会对模板进行静态分析(如标记静态节点、计算依赖),生成带有 “补丁标志(Patch Flags)” 的代码(如示例中的
1 /* TEXT */表示该节点仅文本内容可能变化)。 - 最终产物是经过优化的渲染函数,运行时会利用编译时信息减少虚拟 DOM 的 diff 开销。
二、核心差异:编译逻辑与优化策略
| 维度 | JSX 编译 | Vue 模板编译 |
|---|---|---|
| 语法融合度 | 与 JavaScript 完全融合(JSX 是 JS 的扩展) | 与 HTML 更贴近,通过指令(v-if、@click)扩展 HTML |
| 逻辑处理方式 | 依赖 JavaScript 原生语法(if、map 等) | 依赖框架指令(v-if、v-for 等),编译时转换为逻辑代码 |
| 编译时优化 | 几乎无优化(仅语法转换) | 深度优化:静态节点标记、补丁标志、静态提升等 |
| 动态性与灵活性 | 极高(可在 JSX 中嵌入任意 JS 逻辑) | 受模板语法限制(动态性较低,但更可控) |
| 运行时依赖 | 依赖框架的 createElement 等 API | 依赖框架的虚拟 DOM 运行时(结合编译时信息优化) |
三、关键细节对比
1. 动态内容处理
-
JSX:通过
{ }嵌入任意 JavaScript 表达式(变量、函数调用、三元运算等),动态性完全由 JS 控制。编译时不分析表达式内容,仅将其作为参数传入createElement。jsx
// 任意 JS 逻辑可直接嵌入 <p>{ user ? user.name : 'Guest' }</p> -
Vue 模板:通过
{{ }}嵌入表达式,且表达式受限于 Vue 模板语法规则(不能使用if语句、复杂逻辑需通过computed或方法)。编译时会分析表达式依赖的变量(如user.name),标记为动态节点并关联响应式系统。vue
<!-- 表达式需符合 Vue 模板语法 --> <p>{{ user?.name || 'Guest' }}</p>
2. 静态内容优化
-
JSX:编译时不区分静态和动态内容,所有节点都会在运行时参与虚拟 DOM diff。例如,静态文本
<p>Hello</p>每次渲染都会重新创建虚拟节点。 -
Vue 模板:编译时会识别静态内容(如不包含指令或插值的节点),将其标记为静态节点并 “提升” 为常量,避免运行时重复创建。例如:
vue
<template> <div> <p>静态文本</p> <!-- 编译时标记为静态节点 --> <p>{{ dynamicText }}</p> <!-- 动态节点 --> </div> </template>编译后,静态节点会被缓存,运行时仅 diff 动态节点,大幅提升性能。
3. 事件与属性绑定
-
JSX:事件和属性通过
camelCase语法绑定(如onClick、className),直接映射为 JavaScript 对象的键值对,编译时仅转换语法格式。jsx
<button onClick={handleClick} style={{ color: 'red' }}>Click</button> -
Vue 模板:事件通过
@指令(如@click)、属性通过v-bind(或:)绑定,编译时会将这些指令转换为框架特定的处理逻辑(如事件委托、属性过滤)。vue
<button @click="handleClick" :style="{ color: 'red' }">Click</button>例如,Vue 会对
@click进行事件修饰符(.stop、.prevent)处理,编译时生成对应的逻辑代码。
四、总结:设计理念的差异
- JSX 是 “JavaScript 优先” 的设计,将 UI 描述完全融入 JS 生态,灵活度高但依赖开发者手动优化性能(如
React.memo)。编译过程简单,仅做语法转换,不介入逻辑分析。 - Vue 模板 是 “HTML 优先” 的设计,通过扩展 HTML 语法降低学习成本,同时借助编译时的静态分析实现自动化性能优化。编译过程复杂,深度整合框架的响应式和虚拟 DOM 系统,让开发者无需关注底层优化。
选择哪种方式,本质是在 “灵活性” 和 “开发效率 + 自动化优化” 之间做权衡 ——JSX 适合需要高度动态逻辑的场景,Vue 模板适合追求简洁和性能开箱即用的场景。
13.diff算法
Diff 算法总结对比
| 方面 | React | Vue 2 | Vue 3 |
|---|---|---|---|
| 核心策略 | 同级比较 + key 优化 | 双端比较 | 最长递增子序列 |
| 时间复杂度 | O(n) | O(n) | O(n) |
| 优化重点 | 手动 key 优化 | 自动双端优化 | 编译时优化 + LIS |
| 移动操作 | 相对较多 | 中等 | 最少 |
| 开发体验 | 需要关注 key | 自动优化较多 | 自动优化最多 |
Diff 算法是前端框架(如 Vue、React)中虚拟 DOM(Virtual DOM)的核心技术,用于高效比对新旧虚拟 DOM 树的差异,最终只更新变化的部分到真实 DOM,从而减少 DOM 操作开销,提升性能。
简单来说,Diff 算法的目标是:找到两个棵旧虚拟 DOM 树和新虚拟 DOM 树,快速找到它们的差异,并生成最小化的更新操作。
一、为什么需要 Diff 算法?
- 真实 DOM 操作非常昂贵(修改 DOM 会触发重排 / 重绘)。
- 虚拟 DOM 是 JavaScript 对象,比对 JavaScript 对象的成本远比对真实 DOM 低得多。
- Diff 算法通过计算虚拟 DOM 的差异,只更新必要的真实 DOM,避免全量重渲染。
二、Diff 算法的核心挑战
- 复杂度:传统的树形结构比对算法时间复杂度是
O(n³)(n 为节点数),性能太差。 - 前端场景优化:前端 UI 渲染有特殊规律(如列表节点通常有相同结构、很少跨层级移动节点),可基于这些规律简化算法。
三、主流框架(Vue/React)的 Diff 策略(同层比对)
为降低复杂度,Vue 和 React 的 Diff 算法都采用 “同层比对” 策略(只比对同一层级的节点,不跨层级比对),时间复杂度优化到 O(n),核心规则如下:
1. 第一步:判断节点是否 “可复用”
比对同一层级的新旧节点时,首先通过 “key” 判断节点是否为同一节点(可复用):
- 有 key:通过
key精确匹配(key必须唯一,如列表项的 ID)。 - 无 key:通过节点类型(如标签名
div/p)匹配(可能导致错误复用,如列表项顺序变化时)。
示例:旧节点:[<li key="1">A</li>, <li key="2">B</li>]新节点:[<li key="2">B</li>, <li key="1">A</li>]通过 key 匹配,发现节点只是交换位置,可复用,只需移动 DOM 位置。
2. 第二步:同层节点比对(分情况处理)
同一层级的节点列表比对,分三种情况:
(1)节点类型不同 → 直接替换
若新旧节点类型不同(如旧节点是 div,新节点是 p),则直接销毁旧节点,创建并插入新节点。
js
// 旧节点
<div>旧内容</div>
// 新节点
<p>新内容</p>
// Diff 结果:销毁 div,创建并插入 p
(2)节点类型相同 → 更新属性和子节点
若节点类型相同(如都是 div),则:
- 先更新节点属性(如
class、style、onClick等)。 - 再递归比对其子节点(进入下一层级的 Diff)。
js
// 旧节点
<div class="old" onClick={fn1}>
<span>旧文本</span>
</div>
// 新节点
<div class="new" onClick={fn2}>
<span>新文本</span>
</div>
// Diff 结果:
// 1. 更新 div 的 class 和 onClick 属性
// 2. 递归比对子节点 span,更新其文本内容
(3)列表节点比对(最复杂,核心优化点)
对于列表(如 v-for 或 map 生成的节点),通过 key 定位相同节点,减少操作:
- 新增节点:新节点的
key在旧列表中不存在 → 插入新节点。 - 删除节点:旧节点的
key在新列表中不存在 → 移除旧节点。 - 移动节点:节点
key存在但位置变化 → 移动节点(而非删除再创建)。
示例:旧列表:[A(key=1), B(key=2), C(key=3)]新列表:[B(key=2), C(key=3), D(key=4)]Diff 结果:A 被删除,D 被插入,B 和 C 位置不变(复用)。
四、Vue 和 React Diff 算法的细节差异
虽然核心策略一致,但两者在列表比对的优化上有区别:
1. Vue 3 的 Diff 优化(双端比对 + 最长递增子序列)
- 双端比对:同时从列表的头部、尾部开始比对,快速找到可复用节点。
- 最长递增子序列:对于需要移动的节点,通过算法找到最长无需移动的子序列,减少移动次数(适合列表排序、筛选场景)。
2. React 的 Diff 优化(单方向比对 + key 依赖)
- 从左到右依次比对,遇到不匹配的节点时,通过
key查找可复用节点,若找不到则创建新节点。 - 对
key的依赖更强,若无key或key不唯一,可能导致频繁创建 / 销毁节点(性能差)。
五、总结:Diff 算法的核心要点
- 同层比对:只比对同一层级节点,降低复杂度到
O(n)。 - key 的作用:通过
key唯一标识节点,实现精准复用,减少不必要的创建 / 销毁。 - 分情况处理:节点类型不同则替换,类型相同则更新属性并递归比对子节点。
- 列表优化:通过
key处理新增、删除、移动,是 Diff 算法的核心难点和优化点。
理解 Diff 算法有助于写出更优的代码(如给列表项设置唯一 key),避免因 Diff 效率低导致的性能问题。