Skip to content

66~84 【低频 · 中高级/专项考】

useRequest请求Hook | 生/熟/秒:

useEffect 拉数,loading/data/error 三态,AbortController 取消,依赖变化 abort 上一次。

js
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/subscribeuseSyncExternalStore 订阅(React18)。

js
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(不推荐)。

css
.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

html
<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

js
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 映射。

js
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

js
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 三色标记。

js
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;选中后截断后续层级。

js
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

js
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。

js
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), i * 1000);
}

NumberToggle数字小数点切换 | 生/熟/秒:

维护 localefraction 状态,格式化时在 1,234.561234,56(欧陆)间切换;本质是 Intl.NumberFormat 不同 locale

js
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 包一层处理加载态。路由级代码分割同理。

jsx
const Other = React.lazy(() => import('./Other.jsx'));
function App() {
  return (
    <React.Suspense fallback={<div>loading</div>}>
      <Other />
    </React.Suspense>
  );
}

foreach.js(图片原题) | 生/熟/秒:

自写 Array.prototype.forEachthisArg、下标从 0 到 length-1、稀疏数组要 hasOwn/in 判断(视规范)。

js
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 老兼容。

js
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

js
function parasiticCombo(Child, Parent) {
  const proto = Object.create(Parent.prototype);
  proto.constructor = Child;
  Child.prototype = proto;
}

手写调度器.js(图片原题) | 生/熟/秒:

最大并发 limit,队列 pendingrunactive++ 执行任务完成后 active--next()

js
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

js
function walk(ast, visitor) {
  visitor(ast);
  (ast.children || []).forEach(c => walk(c, visitor));
}

TreeNode/数组转树相关(图片原题) | 生/熟/秒:

mapid → node,第一遍挂 children 空数组,第二遍 parentId 连边,根进 roots。与「数组转树」同一套。

js
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;
}