webDevInterviewQuestions/algorithm/04-practical-scenarios.md
2026-01-19 10:21:18 +08:00

275 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 实际场景与逻辑题
## 1. 并发控制(限制并发请求数)
> **场景**批量上传文件如100张图片每次最多5个并发、网络爬虫控制请求频率、避免压垂服务器。
> **解决**:大量异步任务同时发起导致浏览器卡顿或服务端拒绝服务。
```js
async function limitConcurrency(tasks, limit) {
const results = []; // 存储所有任务结果
const executing = []; // 当前正在执行的任务
for (const [index, task] of tasks.entries()) {
// 包装任务为 Promise
const p = Promise.resolve().then(() => task()).then(res => {
results[index] = res; // 存储结果,保证顺序
executing.splice(executing.indexOf(p), 1); // 完成后从执行列表移除
});
executing.push(p);
// 如果达到并发上限,等待任一任务完成
if (executing.length >= limit) {
await Promise.race(executing);
}
}
await Promise.all(executing); // 等待剩余任务完成
return results;
}
// 使用示例:批量请求,最多 3 个并发
const tasks = urls.map(url => () => fetch(url));
await limitConcurrency(tasks, 3);
```
### 异步任务调度器
```js
class Scheduler {
constructor(limit) {
this.limit = limit; // 最大并发数
this.queue = []; // 等待队列
this.running = 0; // 当前运行中的任务数
}
// 添加任务,返回 Promise
add(promiseCreator) {
return new Promise((resolve, reject) => {
// 将任务和对应的 resolve/reject 存入队列
this.queue.push({ promiseCreator, resolve, reject });
this.run(); // 尝试执行
});
}
// 执行队列中的任务
run() {
// 未达上限且队列有任务时,取出执行
while (this.running < this.limit && this.queue.length) {
const { promiseCreator, resolve, reject } = this.queue.shift();
this.running++;
promiseCreator()
.then(resolve, reject)
.finally(() => { this.running--; this.run(); }); // 完成后继续执行下一个
}
}
}
```
---
## 2. 缓存函数Memoization
> **场景**计算密集型函数结果缓存如斐波那契、API 请求缓存、React useMemo 原理。
> **解决**:相同输入重复计算浪费性能,用空间换时间。
```js
// 基础版:永久缓存
function memoize(fn) {
const cache = new Map(); // 缓存存储
return function(...args) {
const key = JSON.stringify(args); // 参数序列化作为 key
if (cache.has(key)) return cache.get(key); // 命中缓存
const result = fn.apply(this, args); // 执行原函数
cache.set(key, result); // 存入缓存
return result;
};
}
// 进阶版:支持过期时间 TTL
function memoizeWithTTL(fn, ttl = 60000) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
const cached = cache.get(key);
// 检查缓存是否存在且未过期
if (cached && Date.now() - cached.time < ttl) {
return cached.value;
}
const result = fn.apply(this, args);
cache.set(key, { value: result, time: Date.now() }); // 存储值和时间戳
return result;
};
}
```
---
## 3. 解析 URL 参数
> **场景**从链接提取分享参数、跟踪渠道来源utm_source、路由参数解析。
> **解决**:将 URL 查询字符串转换为结构化对象,方便业务使用。
```js
// 方法1字符串分割法
function parseQuery(url) {
const query = url.split('?')[1] || ''; // 取 ? 后的部分
return query.split('&').reduce((acc, pair) => {
const [key, value] = pair.split('=').map(decodeURIComponent); // 解码
if (key) {
// 处理重复 key转为数组
acc[key] = acc[key]
? [].concat(acc[key], value)
: value;
}
return acc;
}, {});
}
// 方法2正则表达式
function parseQueryRegex(url) {
const result = {};
// 匹配 ?key=value 或 &key=value
url.replace(/[?&]([^=&#]+)=([^&#]*)/g, (_, key, value) => {
result[decodeURIComponent(key)] = decodeURIComponent(value);
});
return result;
}
// 示例: "https://example.com?a=1&b=2&a=3"
// => { a: ['1', '3'], b: '2' } // a 有两个值
```
---
## 4. DOM 查找最近公共祖先
> **场景**:事件委托的目标元素判断、富文本编辑器选区处理、拖拽边界计算。
> **解决**:在 DOM 树中找到两个节点的最近共同父级。
```js
function findCommonAncestor(node1, node2) {
const ancestors = new Set(); // 存储 node1 的所有祖先
// 第一步:收集 node1 的所有祖先节点
let current = node1;
while (current) {
ancestors.add(current);
current = current.parentNode;
}
// 第二步:遍历 node2 的祖先,找第一个在 ancestors 中的
current = node2;
while (current) {
if (ancestors.has(current)) return current; // 找到公共祖先
current = current.parentNode;
}
return null; // 无公共祖先
}
// 原生方法(现代浏览器)
// node1.compareDocumentPosition(node2)
```
---
## 5. 懒加载/无限滚动
> **场景**:电商商品列表、社交信息流、图片画廊、新闻列表。
> **解决**:首屏加载慢、一次性加载大量数据卡顿的问题。
```js
// 使用 IntersectionObserver 实现懒加载
function lazyLoad(selector) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) { // 元素进入可视区域
const img = entry.target;
img.src = img.dataset.src; // 将 data-src 赋值给 src
observer.unobserve(img); // 停止观察该元素
}
});
});
// 观察所有带指定选择器的图片
document.querySelectorAll(selector).forEach(img => observer.observe(img));
}
// 无限滚动:滚动到底部时加载更多
function infiniteScroll(container, loadMore) {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) loadMore(); // 触底则加载更多
});
// 创建底部哨兵元素
const sentinel = document.createElement('div');
container.appendChild(sentinel);
observer.observe(sentinel); // 观察哨兵元素
}
```
---
## 6. LRU 缓存
> **场景**浏览器缓存淘汰、图片缓存池、Redis 内存管理、keep-alive 组件缓存。
> **解决**:内存有限时如何淘汰最久未使用的数据,保留热点数据。
```js
// LRU (Least Recently Used) 最近最少使用缓存
// 利用 Map 的有序性:插入顺序即为访问顺序
class LRUCache {
constructor(capacity) {
this.capacity = capacity; // 最大容量
this.cache = new Map(); // Map 保持插入顺序
}
get(key) {
if (!this.cache.has(key)) return -1;
const value = this.cache.get(key);
// 访问后移动到最后(标记为最近使用)
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
if (this.cache.has(key)) this.cache.delete(key); // 已存在则先删除
this.cache.set(key, value); // 重新插入到最后
if (this.cache.size > this.capacity) {
// 超出容量删除最早插入的Map 的第一个 key
this.cache.delete(this.cache.keys().next().value);
}
}
}
```
---
## 7. 大数相加
> **场景**:订单号/交易号处理、金融精确计算、超出 JS Number 范围的计算。
> **解决**JS 数字最大安全整数 2^53-1 限制,大数运算精度丢失问题。
```js
// 模拟竖式加法,从个位开始相加
function addBigNumbers(a, b) {
const maxLen = Math.max(a.length, b.length);
// 对齐长度,前面补 0
a = a.padStart(maxLen, '0');
b = b.padStart(maxLen, '0');
let carry = 0, result = ''; // carry 进位
for (let i = maxLen - 1; i >= 0; i--) {
const sum = +a[i] + +b[i] + carry; // 当前位相加
result = (sum % 10) + result; // 取个位数
carry = Math.floor(sum / 10); // 计算进位
}
return carry ? carry + result : result; // 最高位有进位则补上
}
// 示例: addBigNumbers("12345678901234567890", "98765432109876543210")
```