257 lines
6.2 KiB
Markdown
257 lines
6.2 KiB
Markdown
# 实际场景与逻辑题
|
||
|
||
## 1. 并发控制(限制并发请求数)
|
||
|
||
> **场景**:批量上传文件(如100张图片每次最多5个并发)、网络爬虫控制请求频率、避免压垂服务器。
|
||
> **解决**:大量异步任务同时发起导致浏览器卡顿或服务端拒绝服务。
|
||
|
||
```js
|
||
async function limitConcurrency(tasks, limit) {
|
||
const results = [];
|
||
const executing = [];
|
||
|
||
for (const [index, task] of tasks.entries()) {
|
||
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;
|
||
}
|
||
|
||
// 使用示例
|
||
const tasks = urls.map(url => () => fetch(url));
|
||
await limitConcurrency(tasks, 3); // 最多3个并发
|
||
```
|
||
|
||
### 异步任务调度器
|
||
|
||
```js
|
||
class Scheduler {
|
||
constructor(limit) {
|
||
this.limit = limit;
|
||
this.queue = [];
|
||
this.running = 0;
|
||
}
|
||
|
||
add(promiseCreator) {
|
||
return new Promise((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);
|
||
if (cache.has(key)) return cache.get(key);
|
||
const result = fn.apply(this, args);
|
||
cache.set(key, result);
|
||
return result;
|
||
};
|
||
}
|
||
|
||
// 支持过期时间
|
||
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
|
||
function parseQuery(url) {
|
||
const query = url.split('?')[1] || '';
|
||
return query.split('&').reduce((acc, pair) => {
|
||
const [key, value] = pair.split('=').map(decodeURIComponent);
|
||
if (key) {
|
||
acc[key] = acc[key]
|
||
? [].concat(acc[key], value)
|
||
: value;
|
||
}
|
||
return acc;
|
||
}, {});
|
||
}
|
||
|
||
// 使用正则
|
||
function parseQueryRegex(url) {
|
||
const result = {};
|
||
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' }
|
||
```
|
||
|
||
---
|
||
|
||
## 4. DOM 查找最近公共祖先
|
||
|
||
> **场景**:事件委托的目标元素判断、富文本编辑器选区处理、拖拽边界计算。
|
||
> **解决**:在 DOM 树中找到两个节点的最近共同父级。
|
||
|
||
```js
|
||
function findCommonAncestor(node1, node2) {
|
||
const ancestors = new Set();
|
||
|
||
// 收集 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;
|
||
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
|
||
class LRUCache {
|
||
constructor(capacity) {
|
||
this.capacity = capacity;
|
||
this.cache = new 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) {
|
||
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);
|
||
a = a.padStart(maxLen, '0');
|
||
b = b.padStart(maxLen, '0');
|
||
|
||
let carry = 0, result = '';
|
||
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")
|
||
```
|