前端
React
React 的生命周期?
Ref: https://www.runoob.com/react/react-component-life-cycle.html?utm_source=chatgpt.com
React 生命周期主要为三个阶段
- 挂载 / Mounting:组件实例被创建并插入 DOM 中时,依次调用
constructor():构造函数,在组件挂载之前调用,用于初始化状态或绑定事件处理程序static getDerivedStateFromProps(props, state):在调用render方法之前调用,并在初始挂载及后续更新时都会被调用。它应返回一个对象来更新状态,或返回null表示状态不需要更新render():唯一必须实现的方法,返回要渲染的 JSX 元素。componentDidMount():在组件挂载后(插入 DOM 树中)立即调用,适合在此进行数据获取或订阅操作。
- 更新 / Updating:当组件的 props 或 state 发生变化时,会触发更新,依次调用:
static getDerivedStateFromProps(props, state):与挂载阶段相同,在更新时也会被调用shouldComponentUpdate(nextProps, nextState):接收新的 props 和 state,返回布尔值,决定组件是否需要重新渲染。默认返回truerender():重新渲染组件getSnapshotBeforeUpdate(prevProps, prevState):在最近一次渲染输出(提交到 DOM 节点)之前调用,允许组件在 DOM 更新前获取快照componentDidUpdate(prevProps, prevState, snapshot):在组件更新后立即调用,可用于操作 DOM 或进行网络请求
- 卸载 / Unmounting:当组件从 DOM 中移除时,调用以下方法
componentWillUnmount():在组件卸载和销毁之前调用,适合在此进行清理操作,如取消订阅、清除计时器等
懒加载,虚拟列表怎么实现的?
- 占位符设置: 初始时,将需要懒加载的资源(如图片)的
src属性设置为占位符(如一张小的透明图片),并将真实的资源路径存储在自定义属性(如data-src)中。 - 视口监测: 监听浏览器的滚动事件,判断资源元素是否进入视口。
- 资源加载: 当资源元素进入视口时,将其
src属性替换为真实的资源路径,触发加载。
虚拟列表仅渲染视口内可见的列表项,以及视口上下的缓冲区,随着滚动动态更新渲染内容。我在写 Next.GenAI 项目的时候就遇到了对话过长需要渲染很多个 Markdown 块的问题。
- 容器高度计算: 计算整个列表的总高度,以支持滚动。即使只渲染部分列表项,滚动条也能正确反映整个列表的长度。
- 可视区域计算: 根据滚动位置和视口高度,确定当前需要渲染的列表项的起始和结束索引。
- 动态渲染: 仅渲染上述计算得到的列表项,并通过绝对定位将其放置在正确的位置。
PureComponent有了解吗?
Ref: https://react.dev/reference/react/PureComponent
PureComponent 是一个用于优化性能的类组件。它继承自 React.Component,并在内部实现了 shouldComponentUpdate 生命周期方法,对组件的 props 和 state 进行浅层比较(shallow comparison)。当检测到新旧 props 或 state 相等时,组件将跳过重新渲染过程,从而提升性能。
即使内部数据发生变化,但外层引用未变,浅层比较可能无法检测到变化,导致组件不会重新渲染。
在函数组件中,可以使用 React.memo 高阶组件来实现类似于 PureComponent 的性能优化。
React的钩子函数
| Hook | 作用 | 适用场景 |
|---|---|---|
useState | 管理组件状态 | 计数器、表单数据、UI 交互状态 |
useEffect | 执行副作用(网络请求、订阅、DOM 操作) | 监听状态变化、API 请求、订阅事件 |
useContext | 跨组件共享状态 | 主题切换、用户登录信息 |
useReducer | 复杂状态管理(类似 Redux) | 处理多个状态逻辑 |
useRef | 获取 DOM/保存数据不触发渲染 | 操作 input、存储上一次状态 |
useMemo | 缓存计算结果,提高性能 | 依赖数据计算,避免重复渲染 |
useCallback | 缓存函数,提高性能 | 避免组件不必要的重新渲染 |
useLayoutEffect | 同步执行副作用(渲染前) | 获取 DOM 元素位置 |
useImperativeHandle | 自定义暴露给父组件的实例方法 | ref 传递方法 |
useTransition | 优化状态切换的性能 | 处理大列表渲染 |
useDeferredValue | 优化输入框防抖 | 处理搜索框输入 |
react 的状态管理,父子组件之前通信一般用什么
- 最常用:Props 传递,父→子单线数据流;子→父通知使用回调
useContext避免 Prop Drilling:多个组件共享状态,避免逐层传递useReducer适用于复杂的状态管理,多个组件共享状态Redux/Zustand等包:跨多个组件的复杂管理,全局 Storemitt
Javascript / Typescript
promise用过吗,有什么方法
JavaScript 处理异步操作的重要工具
.then()处理resolve.catch()处理reject.finally()都执行- 同时执行多个 Promise
Promise.all()全部成功才返回Promise.race()无论成功失败,返回最快的Promise.allSettled()等待所有执行完,返回每个的状态Promise.any()返回第一个成功的
CSS
有没有用过flex布局,如何实现垂直居中
是的,我用过 flex 布局,它是现代 CSS 中最强大的布局方式之一。要实现垂直居中,可以使用 align-items: center;。
对于多行文本,可能出现高度塌陷,可以尝试 flex-direction: column;
对于未知大小也可以尝试 margin: auto
另:水平居中:justify-content: center
WebRTC
WebRTC 如何实现的 P2P?实际应用下延迟如何?
WebRTC 采用客户端-服务器-客户端模式建立 P2P 连接,主要流程如下
- 信令交换:SDP/ICE
- NAT 穿透:STUN/TURN,ICE机制
- 建立 P2P:UDP/TCP 传输,SRTP加密
我使用 WebRTC 主要是实现的发现对方,正在考虑逐步减轻对 API 服务器的依赖。
并发
Golang
对于传入的请求, Golang 的 I/O 多路复用?
Go 语言的 net 包 默认使用 netpoll 机制,它基于底层的,无需手动调用 epoll/kqueue,只需要用 net.Listener 监听并处理连接。
你的 Golang 项目如何实现的并发优化?有什么别的思路?
我使用了 Goroutine + WaitGroup,由于并发网络请求才是重点,且数据库不要求实时写入严格,所以没有用高性能数据库或者生产者——消费者模型。
对于发送网络请求,因为 HTTP/2 默认支持 TCP 复用,避免了 HTTP/1.1 的队头阻塞问题。我使用了 http.Transport 的连接池进行了请求。配合 Goroutine 取得了非常不错的效果,实现秒级耗时下的 300 数量级的并发。
未来如果使用人数翻倍,可能考虑会用生产者——消费者,因为我们对数据的写入都是覆盖的,除非是保留历史记录。
- Goroutine + WaitGroup:并发请求、管理同步
- Worker Pool:限制并发数,避免资源耗尽
- 数据库连接池:复用连接,减少建连开销
- 批量插入:提高数据库写入效率
- 事务:确保数据一致性
- 生产者-消费者模型:解耦请求和数据库操作
- Redis 缓存:降低数据库查询压力
- 调整 GOMAXPROCS:充分利用 CPU 多核
鉴权
网关统一鉴权的核心目标是在请求进入后端服务之前,由网关集中验证请求的合法性(如身份验证和权限检查)。这种方式可以减少每个微服务的重复鉴权逻辑,提高系统的安全性和可维护性。以下是实现网关统一鉴权的常见方案和步骤:
1. 基于 JWT 的鉴权
JWT(JSON Web Token)是实现网关统一鉴权的常用方式。
流程:
-
用户登录
用户通过登录接口认证后,认证服务生成 JWT 并返回给客户端。JWT 中通常包含用户 ID、角色、权限等信息。{ "sub": "user123", "roles": ["admin", "editor"], "iat": 1516239022, "exp": 1516242622 } -
客户端携带 JWT
客户端在后续请求中将 JWT 放入 HTTP 请求头(通常是Authorization: Bearer <JWT>)。 -
网关验证 JWT
网关拦截请求,提取 JWT 并进行以下验证:- 签名验证:确保 JWT 未被篡改。
- 过期时间验证:检查 JWT 是否在有效期内。
- 权限验证:根据 JWT 中的角色或权限检查用户是否有权访问当前资源。
-
请求转发
如果 JWT 验证通过,网关将请求转发给后端服务;否则返回 401 Unauthorized 或 403 Forbidden。
优点:
- 无状态:网关不需要存储会话信息。
- 高效:JWT 自包含,验证速度快。
- 灵活:JWT 可以携带任意业务需要的声明。
2. 基于 OAuth2 的鉴权
OAuth2 是一种授权框架,适合分布式系统中的鉴权场景。
流程:
-
用户登录
用户通过 OAuth2 授权服务器获取访问令牌(Access Token)。 -
客户端携带 Token
客户端在请求中将 Token 放入 HTTP 请求头(Authorization: Bearer <Token>)。 -
网关验证 Token
网关向 OAuth2 授权服务器验证 Token 的有效性,并获取用户的权限信息。 -
请求转发
如果 Token 验证通过,网关将请求转发给后端服务;否则返回 401 Unauthorized 或 403 Forbidden。
优点:
- 标准化:OAuth2 是广泛使用的标准。
- 安全性:Token 有效期短,支持刷新机制。
- 适用于第三方授权。
3. 基于 API 密钥的鉴权
适用于服务间通信或开放 API 场景。
流程:
-
生成 API 密钥
为每个客户端或服务生成唯一的 API 密钥。 -
客户端携带 API 密钥
客户端在请求中将 API 密钥放入 HTTP 请求头或查询参数。 -
网关验证 API 密钥
网关检查 API 密钥是否有效,并根据密钥关联的权限决定是否允许访问。 -
请求转发
如果 API 密钥验证通过,网关将请求转发给后端服务;否则返回 403 Forbidden。
优点:
- 简单易用。
- 适用于服务间通信。
JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境间安全地传输信息。JWT 是一种紧凑的、URL 安全的方式,可以表示需要在双方之间传输的声明(claims)。这些声明通常用于身份验证和信息交换。
JWT 令牌和传统方式有什么区别?
- 无状态性:JWT是无状态的令牌,不需要在服务器端存储会话信息。相反,JWT令牌中包含了所有必要的信息,如用户身份、权限等。这使得JWT在分布式系统中更加适用,可以方便地进行扩展和跨域访问。
- 安全性:JWT使用密钥对令牌进行签名,确保令牌的完整性和真实性。只有持有正确密钥的服务器才能对令牌进行验证和解析。这种方式比传统的基于会话和Cookie的验证更加安全,有效防止了CSRF(跨站请求伪造)等攻击。
- 跨域支持:JWT令牌可以在不同域之间传递,适用于跨域访问的场景。通过在请求的头部或参数中携带JWT令牌,可以实现无需Cookie的跨域身份验证。
JWT 令牌为什么能解决集群部署,什么是集群部署?
在传统的基于会话和Cookie的身份验证方式中,会话信息通常存储在服务器的内存或数据库中。但在集群部署中,不同服务器之间没有共享的会话信息,这会导致用户在不同服务器之间切换时需要重新登录,或者需要引入额外的共享机制(如Redis),增加了复杂性和性能开销。

而JWT令牌通过在令牌中包含所有必要的身份验证和会话信息,使得服务器无需存储会话信息,从而解决了集群部署中的身份验证和会话管理问题。当用户进行登录认证后,服务器将生成一个JWT令牌并返回给客户端。客户端在后续的请求中携带该令牌,服务器可以通过对令牌进行验证和解析来获取用户身份和权限信息,而无需访问共享的会话存储。
由于JWT令牌是自包含的,服务器可以独立地对令牌进行验证,而不需要依赖其他服务器或共享存储。这使得集群中的每个服务器都可以独立处理请求,提高了系统的可伸缩性和容错性。
JWT 结构
xxx.yyy.zzz
- Header - Base64Url 编码
- 令牌的类型(即 “JWT”)
- 所使用的签名算法(如 HMAC SHA256 或 RSA)
- Payload - Base64Url 编码
- 三种类型
- 注册声明(Registered claims):一些预定义的声明,如
iss(签发者)、exp(过期时间)、sub(主题)、aud(受众)等。iss(Issuer):JWT 的签发者。sub(Subject):JWT 的主题(通常是用户 ID)。aud(Audience):JWT 的目标接收者。exp(Expiration Time):JWT 的过期时间(以 Unix 时间戳表示)。nbf(Not Before):JWT 的生效时间(以 Unix 时间戳表示)。iat(Issued At):JWT 的签发时间(以 Unix 时间戳表示)。jti(JWT ID):JWT 的唯一标识符,用于防止重放攻击。
- 公共声明(Public claims):可以自定义的声明,但应避免与已注册声明冲突。
https://example.com/roles:表示用户的角色。https://example.com/email:表示用户的电子邮件。
- 私有声明(Private claims):用于在同意使用它们的各方之间共享信息。
userId:用户的唯一标识符。isAdmin:用户是否为管理员。
- 注册声明(Registered claims):一些预定义的声明,如
- 三种类型
- Signature
- 签名用于验证消息在传输过程中没有被篡改。签名是使用头部中指定的算法,对编码后的头部、编码后的载荷以及一个密钥进行加密生成的。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT 缺点和泄漏预防?
- 无法撤销:一旦 JWT 签发,除非它过期,否则无法撤销。可以通过使用短暂的有效期或黑名单机制来缓解这个问题。
- 安全性依赖于密钥:如果密钥泄露,JWT 的安全性将受到威胁。
- 泄漏解决
- 及时失效令牌:当检测到JWT令牌泄露或存在风险时,可以立即将令牌标记为失效状态。服务器在接收到带有失效标记的令牌时,会拒绝对其进行任何操作,从而保护用户的身份和数据安全。
- 刷新令牌:JWT令牌通常具有一定的有效期,过期后需要重新获取新的令牌。当检测到令牌泄露时,可以主动刷新令牌,即重新生成一个新的令牌,并将旧令牌标记为失效状态。这样,即使泄露的令牌被恶意使用,也会很快失效,减少了被攻击者滥用的风险。
- 使用黑名单:服务器可以维护一个令牌的黑名单,将泄露的令牌添加到黑名单中。在接收到令牌时,先检查令牌是否在黑名单中,如果在则拒绝操作。这种方法需要服务器维护黑名单的状态,对性能有一定的影响,但可以有效地保护泄露的令牌不被滥用。
Misc
Network
接口怎么做反爬
- 限制请求频率
- 身份验证
- 行为检测:UA/Referer
- 设备指纹(
Fingerprint.js),查看相同指纹多个请求 - 验证码
- IP 代理和检测(
X-Forwarded-For),封禁 - 返回动态内容/混淆
Storage
local storage存什么比较合适呢,session storage呢
- LocalStorage:持久化,不敏感。可以被 JS 直接访问,容易被 XSS 窃取
- 用户设置
- 持久化 Token
- 历史记录
- 自动填充
- 新手引导
- ……
- SessionStorage:临时,不重要。页面关闭/刷新即消失。不可跨标签页,受 XSS 影响。
- 临时 Token
- 中途数据