~/personal-notes · frontend · since 2023

代码调试笔记

—— 把 bug 修成经验,把经验写成字

这是我的个人技术知识库:记录我在软件开发 —— 特别是前端技术领域的学习心得与实战经验。 这里有亲测好用的代码片段、主流框架的使用技巧,以及那些熬到深夜才定位到的疑难问题排查过程。

0
篇笔记
0+
段代码片段
0
例排查实录
0+
小时调试时长
TODO:修完这个 bug 就睡觉 (该 flag 已连续推迟 47 天)
debug — zsh — 80×24
LN 01 // about

关于本站

把踩过的坑,变成脚下的路。

这里是我的个人技术记录站点,主要记录我在软件开发 —— 特别是前端技术领域 —— 的学习心得与实战经验。 内容包括一些实用的代码片段、框架使用技巧,以及疑难问题的排查过程,旨在作为个人的知识库。

它首先是写给自己的复盘笔记:好记性不如烂笔头,踩过的坑只有记下来才不算白踩。 同一个问题在不同的项目里总会再遇见一次,那时候这份笔记就是最快的答案。 如果这些记录能顺便帮到正在经历同样问题的你,那就是它意外的价值。

声明 —— 本站为纯个人性质站点,不涉及任何商业交易与付费服务; 所有内容仅代表个人经验与理解,如有谬误,欢迎交流指正。
author.js
// 站长画像.ts
const author = {
  nickname: '夜航西飞',
  role: '前端开发者',
  stack: ['Vue', 'React', 'TypeScript', 'CSS'],
  recordingSince: 2023,
  motto: '把踩过的坑,变成脚下的路',
  commercial: false, // 个人站点 · 无付费服务
}
LN 47 // notes

最新笔记

按时间倒序排列 · git log --oneline --reverse

2026-08-18
Vue 3

watch 侦听器的五种姿势与三个坑

从 watch 到 watchEffect,再到源码注释里的那句警告:不要在回调里修改被侦听的值。五种用法各有归属,三个坑我都替你踩过了。

8 min->
2026-08-12
CSS

记一次诡异的样式失效:优先级连环坑

三层 !important 都压不住的样式,最后败给了一条被遗忘的属性选择器。附:一份我背下来的优先级速查表。

6 min->
2026-08-05
React置顶

Hooks 闭包陷阱:setState 为什么拿到的是旧值

函数组件的每次渲染都是一次快照,你以为的 state,其实是上个世界的它。用四段最小复现代码讲透闭包捕获。

10 min->
2026-07-28
TypeScript

类型体操入门:从 Partial 到条件类型

泛型不是玄学,是可推导的逻辑。从五个内置工具类型的实现开始,一步步推导出属于自己的 MyPick。

12 min->
2026-07-15
性能优化

首屏白屏 3 秒排查实录

从 Performance 面板里一根刺眼的长任务开始,依次揪出三个阻塞主线程的元凶,最终白屏时间压到 400ms。

9 min->
2026-07-02
算法

虚拟列表从零实现:让万条数据丝滑滚动

不依赖组件库,用两百行代码讲透虚拟滚动的全部本质:可视区域、缓冲区与绝对定位的数学。

15 min->
LN 132 // snippets

代码片段库

从笔记里挑出来、被我自己复制次数最多的几段。

debounce.js
useLocalStorage.ts
modern-css.css
// 防抖:输入停止 500ms 后才触发搜索请求
function debounce(fn, delay = 300) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 使用:搜索框输入防抖
searchInput.addEventListener('input', debounce(doSearch, 500))
片段 001· 出自笔记《搜索框防抖的边界情况》· 复制 37 次
// 组合式函数:让 ref 与 localStorage 自动同步
import { ref, watch } from 'vue'
export function useLocalStorage(key, initial) {
const stored = localStorage.getItem(key)
const value = ref(stored ? JSON.parse(stored) : initial)
watch(value, (v) => {
localStorage.setItem(key, JSON.stringify(v))
}, { deep: true })
return value
}
片段 002· 出自笔记《Vue3 组合式函数清单》· 复制 29 次
/* :has() —— 不写一行 JS 的「父级选择器」 */
/* 卡片带图时,自动切换为图文布局 */
.card:has(img) {
display: grid;
grid-template-columns: 120px 1fr;
}
/* 复选框选中后,整张卡片自动高亮 */
.option:has(input:checked) {
border-color: #ffb454;
background: rgba(255, 180, 84, .06);
}
片段 003· 出自笔记《现代 CSS 三件套》· 复制 21 次
LN 208 // tips

框架技巧

用更少的代码,做更多的事。

Vue 3.4

defineModel

双向绑定不再需要 props + emit 的样板代码,一个宏搞定子组件 v-model,连类型都是自动推导的。

const open = defineModel()
React 18

useTransition

把昂贵的状态更新标记为「过渡」,输入框不再跟着一起卡顿,列表渲染再大也能保持响应。

const [isPending, start] = useTransition()
CSS

:has() 选择器

不写一行 JavaScript,实现真正的「父级选择器」。表单联动、卡片高亮,从此优雅到不像话。

.card:has(input:checked)
TS 4.9

satisfies 操作符

既校验类型是否匹配,又保留字面量的精确推断。是时候和 as 断言说再见了。

const cfg = {...} satisfies Config
LN 289 // debug

疑难排查实录

每个 bug 都有编号、症状、根因与结论 · status: all resolved

BUG-2026-0812

点击事件偶发失效

已解决
症状列表项点击约 5% 概率无响应,且无法稳定复现。
根因蒙层动画结束后未移除 DOM,透明遮罩悄悄拦截了点击。
方案监听 animationend 事件后 remove(),并加了 e2e 回归用例。
耗时 2.5h复盘笔记 →
BUG-2026-0730

页面内存持续上涨

已解决
症状看板页停留两小时,内存从 80MB 一路涨到 600MB。
根因WebSocket 每次重连都新建定时器与监听器,旧的从未清理。
方案onUnmounted 统一清理,重连前先关闭旧连接,Memory 面板验证通过。
BUG-2026-0721

移动端 1px 边框时隐时现

已解决
症状部分安卓机型上边框随机消失,同一页面时好时坏。
根因transform 缩放在非整数 DPR 下被四舍五入,渲染被吞。
方案改用 box-shadow 模拟 1px,并统一封装 hairline 组件。