核心思路:采用「问题场景→分析过程→解决方案→优化迭代→价值体现」的结构化叙述,结合具体案例说明。
示例:
在某电商项目中,发现首屏加载时间长达 8s(通过 Lighthouse 检测),用户流失率超 30%。
-
问题定位:
- 排查发现:第三方 SDK(如统计、客服)阻塞渲染,首屏包含 12 张未优化的大图,JS 打包体积达 2.3MB;
- 工具验证:通过 Chrome Performance 面板确认第三方脚本为长任务,Network 面板显示图片未启用压缩和懒加载。
-
解决方案:
- 第三方 SDK 异步加载:将非核心 SDK(如客服)改为
dynamic import,延迟到首屏渲染后加载;
- 图片优化:采用 webp 格式 +
srcset响应式加载,首屏仅加载可视区图片(懒加载);
- 代码分割:路由懒加载(
() => import('./Page.vue')),将主包体积降至 800KB。
-
结果:首屏加载时间降至 2.5s,用户流失率下降至 12%,核心转化指标提升 18%。
总结:解决问题的关键是「数据驱动定位瓶颈」+「分层优化(资源加载 / 代码体积 / 执行效率)」,过程需记录每步尝试与结果(如笔记记录各优化措施的具体收益),便于复盘和复用。
1. 为什么需要事件循环?
- 单线程必要性:JS 需操作 DOM,多线程会导致冲突(如同时修改同一 DOM),故设计为单线程;
- 非阻塞需求:单线程若同步执行所有任务(如网络请求)会导致卡顿,因此通过事件循环实现异步非阻塞。
2. 宏任务(Macro Task)与微任务(Micro Task)
- 宏任务:优先级较低,包含
script(整体代码)、setTimeout、setInterval、I/O、UI 渲染等;
- 微任务:优先级较高,包含
Promise.then/catch/finally、MutationObserver、queueMicrotask等。
为什么需要微任务?
单一任务队列无法区分优先级,微任务用于处理「高优先级异步操作」(如 Promise 回调需在 UI 渲染前执行),避免被低优先级宏任务(如定时器)阻塞。
3. 浏览器事件循环流程
- 执行当前宏任务(如
script整体代码);
- 执行所有微任务队列中的任务(按顺序清空);
- 执行 UI 渲染(若有);
- 取下一个宏任务,重复 1-3。
4. Node 与浏览器事件循环的差异
- Node 事件循环阶段:
timers(定时器)→ pending callbacks(延迟 I/O 回调)→ idle/prepare(系统用)→ poll(轮询 I/O)→ check(setImmediate)→ close callbacks(关闭回调);
- 执行顺序差异:
- Node 10 前:执行完一个阶段的所有宏任务→执行
process.nextTick→执行微任务;
- Node 11 后:与浏览器一致(每执行一个宏任务→清空微任务)。
5. 代码执行顺序示例
console.log('1');
setTimeout(() => console.log('2'), 0);
new Promise(resolve => {
console.log('3');
resolve();
}).then(() => console.log('4'));
console.log('5');
1. 基本概念
- 事件捕获:事件从最外层(
window)向目标元素传播(从上到下);
- 目标阶段:事件到达触发事件的元素;
- 事件冒泡:事件从目标元素向最外层(
window)传播(从下到上)。
2. window.addEventListener 的默认阶段
默认监听冒泡阶段(useCapture: false),若需监听捕获阶段,需设置useCapture: true:
window.addEventListener('click', () => console.log('捕获'), true);
window.addEventListener('click', () => console.log('冒泡'), false);
3. 应用场景与事件委托
4. 封禁用户点击拦截方案
利用事件捕获在最顶层拦截所有点击(比冒泡更早触发):
if (user.banned) {
window.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
alert('您已被封禁,无法操作');
}, true);
}
1. 基本概念
- 防抖(Debounce):持续触发事件时,仅在最后一次触发后延迟执行一次(如搜索输入联想);
- 节流(Throttle):持续触发事件时,每隔固定时间执行一次(如滚动加载、窗口 resize)。
2. 适用场景
- 防抖:搜索框输入校验、按钮防重复点击;
- 节流:滚动加载数据、拖拽尺寸计算、高频点击统计。
3. 节流实现(时间戳 + 定时器版)
const throttle = (func, wait) => {
let lastTime = 0;
let timer = null;
return function (...args) {
const now = Date.now();
const remaining = wait - (now - lastTime);
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = now;
func.apply(this, args);
}
else if (!timer) {
timer = setTimeout(() => {
timer = null;
lastTime = Date.now();
func.apply(this, args);
}, remaining);
}
};
};
1. 缓存 Promise 结果(装饰器写法)
const cachePromise = (fn) => {
const cache = new Map();
return async (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = await fn(...args);
cache.set(key, result);
return result;
};
};
const fetchData = cachePromise(async (id) => {
const res = await fetch(`/api/data?id=${id}`);
return res.json();
});
2. 实现 Promise.all
Promise.myAll = (iterable) => {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
const len = iterable.length;
if (len === 0) return resolve(results);
iterable.forEach((item, index) => {
Promise.resolve(item)
.then((res) => {
results[index] = res;
completed++;
if (completed === len) {
resolve(results);
}
})
.catch(reject);
});
});
};
3. Promise 并发控制
const promiseLimit = (tasks, limit) => {
return new Promise((resolve) => {
const results = [];
let index = 0;
let running = 0;
const run = () => {
if (index >= tasks.length && running === 0) {
return resolve(results);
}
while (index < tasks.length && running < limit) {
const task = tasks[index];
const i = index;
running++;
index++;
Promise.resolve(task())
.then((res) => {
results[i] = res;
})
.catch((err) => {
results[i] = err;
})
.finally(() => {
running--;
run();
});
}
};
run();
});
};
问题:计算柱状图能接住的雨水总量(每个柱子宽度为 1)。
解法:双指针法(时间 O (n),空间 O (1))
- 核心思路:雨水高度由「左侧最高柱」和「右侧最高柱」的最小值决定,用双指针分别从左右遍历,计算每列可接雨水。
const trap = (height) => {
let left = 0, right = height.length - 1;
let leftMax = 0, rightMax = 0;
let res = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
res += leftMax - height[left];
}
left++;
} else {
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
res += rightMax - height[right];
}
right--;
}
}
return res;
};
console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1]));
console.log(trap([4,2,0,3,2,5]));
- 输入输出结合:学习后通过博客、笔记或讲解给他人检验理解(如用费曼学习法);
- 问题驱动学习:遇到问题先独立排查(如利用调试工具),再查资料,最后总结解决方案;
- 工程化思维:不仅关注功能实现,还需考虑性能、可维护性、兼容性(如写组件时设计合理的 API);
- 沟通协作:与产品、后端明确需求边界,用技术方案对齐预期(如提前评估实现成本和风险)。
前端开发的核心竞争力不仅是技术深度,更是「用技术解决实际问题」的能力。