66~84 【低频 · 中高级/专项考】
useRequest请求Hook | 生/熟/秒:
useEffect 拉数,loading/data/error 三态,AbortController 取消,依赖变化 abort 上一次。
function useRequest(url) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const ctrl = new AbortController();
setLoading(true);
fetch(url, { signal: ctrl.signal })
.then(r => r.json())
.then(setData, setError)
.finally(() => setLoading(false));
return () => ctrl.abort();
}, [url]);
return { data, loading, error };
}useRedux简易状态管理 | 生/熟/秒:
createStore(reducer) 返回 getState/dispatch/subscribe;useSyncExternalStore 订阅(React18)。
function createStore(reducer, init) {
let state = init;
const listeners = new Set();
return {
getState: () => state,
dispatch(action) {
state = reducer(state, action);
listeners.forEach(fn => fn());
},
subscribe(fn) {
listeners.add(fn);
return () => listeners.delete(fn);
},
};
}inline-block空格问题 | 生/熟/秒:
标签间换行会产生空白文本节点。解法:font-size:0 父级再还原子级;flex 布局;HTML 紧贴写;margin-left:-4px(不推荐)。
.parent { font-size: 0; }
.child { display: inline-block; font-size: 14px; vertical-align: top; }Tailwind实现常见组件 | 生/熟/秒:
记几个原子类组合:按钮 rounded px-4 py-2 bg-blue-600 text-white hover:opacity-90;卡片 rounded-lg shadow p-4 bg-white;输入 border rounded px-3 py-2 focus:ring-2。
<button class="rounded-lg px-4 py-2 bg-indigo-600 text-white hover:bg-indigo-500 active:scale-95 transition">
Submit
</button>路径总和 | 生/熟/秒:
DFS:targetSum 从根减到叶子为 0 即 true。分治:pathSum = left + right。
function hasPathSum(root, target) {
if (!root) return false;
if (!root.left && !root.right) return target === root.val;
return hasPathSum(root.left, target - root.val) || hasPathSum(root.right, target - root.val);
}路径字符串转树 | 生/熟/秒:
按 / 分段,用栈或 reduce 维护当前节点,逐段创建 children 映射。
function pathsToTree(paths) {
const root = { name: '', children: {} };
paths.forEach(p => {
const parts = p.split('/').filter(Boolean);
let cur = root;
for (const name of parts) {
cur.children[name] ||= { name, children: {} };
cur = cur.children[name];
}
});
return root;
}按缩进构造树 | 生/熟/秒:
每行解析 indent + label,栈深度与缩进对齐:缩进小则 pop,当前节点挂到栈顶 children。
function indentToTree(lines) {
const root = { label: 'root', children: [] };
const stack = [{ indent: -1, node: root }];
lines.forEach(line => {
const indent = line.match(/^\s*/)[0].length;
const label = line.trim();
while (stack.length && stack.at(-1).indent >= indent) stack.pop();
const node = { label, children: [] };
stack.at(-1).node.children.push(node);
stack.push({ indent, node });
});
return root.children;
}课程表(图的环检测) | 生/熟/秒:
拓扑排序:入度表 + 队列,每弹出一条边入度减一;若处理结点数 < n 则有环。或 DFS 三色标记。
function canFinish(numCourses, prerequisites) {
const g = Array.from({ length: numCourses }, () => []);
const indeg = Array(numCourses).fill(0);
for (const [a, b] of prerequisites) {
g[b].push(a);
indeg[a]++;
}
const q = indeg.map((v, i) => (v === 0 ? i : -1)).filter(i => i >= 0);
let done = 0;
while (q.length) {
const u = q.shift();
done++;
for (const v of g[u]) {
if (--indeg[v] === 0) q.push(v);
}
}
return done === numCourses;
}CascadeSelect级联选择 | 生/熟/秒:
树形数据 + 当前选中路径数组;每一列 options = 上一选中节点的 children;选中后截断后续层级。
function getColumns(tree, pathIds) {
const cols = [];
let level = tree;
cols.push(level);
for (const id of pathIds) {
const node = level.find(n => n.id === id);
if (!node?.children?.length) break;
level = node.children;
cols.push(level);
}
return cols;
}虚拟列表 | 生/熟/秒:
只渲染 viewport/itemHeight 条 + buffer,transform: translateY(startIndex * itemHeight) 顶对齐,滚动事件更新 startIndex。
function virtualList({ total, itemHeight, viewHeight, scrollTop }) {
const start = Math.floor(scrollTop / itemHeight);
const visible = Math.ceil(viewHeight / itemHeight);
const end = Math.min(total, start + visible + 2);
return { start, end, offsetY: start * itemHeight };
}LoggerDebug闭包陷阱修复 | 生/熟/秒:
for (var i=0;i<n;i++) setTimeout(()=>log(i)) 全打 n。改 let、传参 setTimeout(fn, t, i)、或包 IIFE。
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), i * 1000);
}NumberToggle数字小数点切换 | 生/熟/秒:
维护 locale 或 fraction 状态,格式化时在 1,234.56 与 1234,56(欧陆)间切换;本质是 Intl.NumberFormat 不同 locale。
function formatToggle(num, useCommaDecimal) {
return new Intl.NumberFormat(useCommaDecimal ? 'de-DE' : 'en-US', {
minimumFractionDigits: 2,
}).format(num);
}懒加载组件React.lazy+Suspense | 生/熟/秒:
lazy(() => import('./X')) 返回组件,Suspense fallback 包一层处理加载态。路由级代码分割同理。
const Other = React.lazy(() => import('./Other.jsx'));
function App() {
return (
<React.Suspense fallback={<div>loading</div>}>
<Other />
</React.Suspense>
);
}foreach.js(图片原题) | 生/熟/秒:
自写 Array.prototype.forEach:thisArg、下标从 0 到 length-1、稀疏数组要 hasOwn/in 判断(视规范)。
Array.prototype.myForEach = function (fn, thisArg) {
for (let i = 0; i < this.length; i++) {
if (i in this) fn.call(thisArg, this[i], i, this);
}
};类数组转数组.js(图片原题) | 生/熟/秒:
Array.from / 展开 [...args] / slice.call 老兼容。
const toArray = (like) => Array.from(like);
const toArray2 = (like) => [].slice.call(like);原型链/原型式/寄生式/构造函数继承(图片原题) | 生/熟/秒:
构造函数:Parent.call(this) 拷实例属性;原型式:Object.create(obj) 当原型;寄生式:工厂里增强再返回;寄生组合:Object.create(Parent.prototype) + Child.prototype.constructor。
function parasiticCombo(Child, Parent) {
const proto = Object.create(Parent.prototype);
proto.constructor = Child;
Child.prototype = proto;
}手写调度器.js(图片原题) | 生/熟/秒:
最大并发 limit,队列 pending,run 里 active++ 执行任务完成后 active-- 再 next()。
class Scheduler {
constructor(limit) {
this.limit = limit;
this.active = 0;
this.queue = [];
}
add(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.run();
});
}
async run() {
if (this.active >= this.limit || !this.queue.length) return;
this.active++;
const { task, resolve, reject } = this.queue.shift();
try {
resolve(await task());
} catch (e) {
reject(e);
} finally {
this.active--;
this.run();
}
}
}ast.js(图片原题) | 生/熟/秒:
递归下降:词法分析拆 token,语法分析按文法生成 AST 节点 {type, value, children};遍历 AST 用 visitor 模式 enter/leave。
function walk(ast, visitor) {
visitor(ast);
(ast.children || []).forEach(c => walk(c, visitor));
}TreeNode/数组转树相关(图片原题) | 生/熟/秒:
map 存 id → node,第一遍挂 children 空数组,第二遍 parentId 连边,根进 roots。与「数组转树」同一套。
function listToTree(items, { idKey = 'id', pKey = 'parentId', rootVal = null } = {}) {
const map = {};
const roots = [];
items.forEach(item => (map[item[idKey]] = { ...item, children: [] }));
items.forEach(item => {
if (item[pKey] === rootVal) roots.push(map[item[idKey]]);
else map[item[pKey]].children.push(map[item[idKey]]);
});
return roots;
}