遇到前端题目,我现在会先问自己这四个问题

备考面试的过程里,我发现一件有意思的事:很多题目表面上考的是不同的知识点,但解题的起点其实是一样的------先搞清楚题目的结构,再选对应的模型

后来我把这个过程提炼成四个问题,每次看到新题目,不管是面试题还是实际业务需求,都先把这四个问题过一遍。这篇文章把这四个问题展开来讲,每个配上真实的代码场景,是我整理这套思考框架的过程记录。


为什么需要判断框架

先说一个让我意识到这件事重要性的场景。

同样是"批量操作",有人问的是"批量审批,允许部分失败",有人问的是"批量上传,限制同时最多 3 个"。表面上都是批量,但前者的核心是容错并发Promise.allSettled),后者的核心是背压控制(并发池)。如果只靠关键词匹配,看到"批量"就想到同一个答案,就会答错。

判断框架的作用是:在选答案之前,先把题目的维度看清楚

四个问题的完整决策树


问题一:这里的数据是什么形状?

数据的形状决定了应该用什么数据结构和遍历方式。常见的两个场景:

场景 A:需要聚合/分组 → Map + 复合 key

触发信号是"按 X 统计 Y"、"分组"、"去重后计数"。

javascript 复制代码
// 环境:浏览器 / Node.js
// 场景:统计每个用户每个月的消费金额

const records = [
  { userId: 'u1', month: '2024-01', amount: 300 },
  { userId: 'u1', month: '2024-01', amount: 200 },
  { userId: 'u1', month: '2024-02', amount: 150 },
  { userId: 'u2', month: '2024-01', amount: 400 },
];

// ✅ 复合 key + Map:O(1) 查询,结构清晰
function aggregateByUserAndMonth(records) {
  const map = new Map();

  for (const record of records) {
    const key = `${record.userId}|${record.month}`;
    map.set(key, (map.get(key) ?? 0) + record.amount);
  }

  return map;
}

const result = aggregateByUserAndMonth(records);
console.log(result.get('u1|2024-01')); // 500
console.log(result.get('u2|2024-01')); // 400

为什么不用嵌套 reduce? 嵌套 reduce 能跑,但读起来难受------你要先理解"外层在做什么"再理解"内层在做什么"。Map + 复合 key 把两个维度拍平,逻辑是线性的,写的人和读的人都轻松。

场景 B:树形/嵌套结构 → 递归 + 状态提升

触发信号是"组织架构"、"分类目录"、"嵌套评论"。

javascript 复制代码
// 环境:浏览器(React)
// 场景:部门树,展开状态集中管理,子节点只负责渲染

// 关键决策:展开状态存在父组件的 Set 里,不存在每个节点自己身上
// 好处:全部展开/折叠只需要操作父组件的 Set,不需要广播给所有节点

function DeptTree({ data }) {
  const [expandedIds, setExpandedIds] = useState(new Set());

  const toggle = useCallback((id) => {
    setExpandedIds((prev) => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  }, []);

  return data.map((node) => (
    <DeptNode
      key={node.id}
      node={node}
      expandedIds={expandedIds}
      onToggle={toggle}
    />
  ));
}

// 递归节点:纯渲染,不持有状态
const DeptNode = React.memo(function DeptNode({ node, expandedIds, onToggle }) {
  const isExpanded = expandedIds.has(node.id);
  const hasChildren = node.children?.length > 0;

  return (
    <div style={{ paddingLeft: 16 }}>
      <div onClick={() => hasChildren && onToggle(node.id)}>
        {hasChildren ? (isExpanded ? '▼' : '▶') : '·'} {node.name}
      </div>
      {isExpanded && hasChildren && node.children.map((child) => (
        <DeptNode
          key={child.id}
          node={child}
          expandedIds={expandedIds}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
});

问题二:这里的时间维度是什么?

"时间维度"是指:这个操作有没有等待、顺序、取消、重试的需求?有时间维度的题,核心都在异步控制。

场景 A:多个操作需要同时进行 → 并发模型

javascript 复制代码
// 环境:浏览器 / Node.js
// 场景:页面初始化需要同时请求三个接口

// 需要全部成功才能渲染 → Promise.all(一个失败全部失败)
async function initDashboard() {
  const [user, orders, stats] = await Promise.all([
    fetchUser(),
    fetchOrders(),
    fetchStats(),
  ]);
  return { user, orders, stats };
}

// 允许部分失败,每条都要有结果 → Promise.allSettled
async function batchExport(orderIds) {
  const tasks = orderIds.map((id) =>
    exportOrder(id)
      .then(() => ({ id, ok: true }))
      .catch((err) => ({ id, ok: false, reason: err.message }))
  );

  const results = await Promise.allSettled(tasks);
  return results.map((r) => (r.status === 'fulfilled' ? r.value : r.reason));
}

场景 B:需要限制并发数 → 并发池(背压控制)

javascript 复制代码
// 环境:浏览器 / Node.js
// 场景:批量上传 100 个文件,同时最多 3 个并发
// 核心思路:维护一个"正在执行"的集合,完成一个立刻补充下一个

async function asyncPool(limit, tasks) {
  const results = new Array(tasks.length);
  const executing = new Set();

  for (let i = 0; i < tasks.length; i++) {
    const task = tasks[i];

    const p = Promise.resolve().then(() => task()).then((result) => {
      results[i] = result;
      executing.delete(p); // 完成后从执行集合移除
    });

    executing.add(p);

    // 达到上限时,等待任意一个完成再继续
    if (executing.size >= limit) {
      await Promise.race(executing);
    }
  }

  // 等待所有剩余任务完成
  await Promise.all(executing);
  return results;
}

// 使用示例
const uploadTasks = files.map((file) => () => uploadFile(file));
const results = await asyncPool(3, uploadTasks);

为什么此处的 const p 需要在 Promise.resolve().then() 中执行 task?

核心原因:控制执行时机

javascript 复制代码
for (const p of promises) {
  // ❌ 直接执行 - 会立即开始所有任务
  const result = p.task();
  
  // ✅ 延迟执行 - 按顺序控制并发
  const result = Promise.resolve().then(() => p.task());
}

具体场景对比

场景 1:直接执行(无 Promise.resolve().then)
javascript 复制代码
const tasks = [task1, task2, task3];

for (const task of tasks) {
  const p = task();  // 立即执行!
  running.add(p);
}
// 结果:3 个任务同时开始执行(并发爆炸)
场景 2:使用 Promise.resolve().then(延迟执行)
javascript 复制代码
const tasks = [task1, task2, task3];

for (const task of tasks) {
  const p = Promise.resolve().then(() => task());  // 延迟到微任务
  running.add(p);
}
// 结果:先注册所有延迟任务,再按事件循环执行

关键机制:事件循环

scss 复制代码
同步代码(for 循环)→ 微任务队列(Promise.then)→ 执行 task()
     ↓                        ↓                    ↓
  立即执行               注册回调但不执行        真正开始执行

实际用途:并发控制

这种模式常见于 限制并发数 的场景:

javascript 复制代码
async function* asyncPool(concurrency, tasks) {
  const executing = new Set();
  
  for (const task of tasks) {
    // 关键:延迟执行,让控制器先"占坑"
    const p = Promise.resolve().then(() => task());
    executing.add(p);
    
    // 清理完成的
    p.then(() => executing.delete(p));
    
    // 控制并发:满了就等一个完成
    if (executing.size >= concurrency) {
      await Promise.race(executing);
    }
    
    yield p;
  }
  
  await Promise.all(executing);
}

// 使用:最大 2 个并发
for await (const result of asyncPool(2, heavyTasks)) {
  console.log(result);
}

一句话总结

Promise.resolve().then()同步创建异步执行 分离,让你有机会在任务真正开始前做控制(如限制并发、错误处理、取消等)。

如果没有这层包装,任务会在 for 循环遍历时就立即执行,失去了控制的机会。

场景 C:有取消需求 → AbortController + ref

javascript 复制代码
// 环境:浏览器(React)
// 场景:搜索框,用户快速输入时取消上一个请求,只保留最新一个

function useSearch(query) {
  const abortRef = useRef(null);
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!query.trim()) return;

    // 取消上一个还在进行的请求
    abortRef.current?.abort();
    const controller = new AbortController();
    abortRef.current = controller;

    fetch(`/api/search?q=${encodeURIComponent(query)}`, {
      signal: controller.signal,
    })
      .then((res) => res.json())
      .then(setResults)
      .catch((err) => {
        if (err.name !== 'AbortError') console.error(err);
        // AbortError 是主动取消,不是错误,静默处理
      });

    return () => controller.abort();
  }, [query]);

  return results;
}

场景 D:有重试需求 → 指数退避

javascript 复制代码
// 环境:浏览器 / Node.js
// 场景:请求失败后重试,每次等待时间翻倍,避免持续打服务器

async function fetchWithRetry(url, options = {}, retries = 3) {
  const { baseDelay = 1000, ...fetchOptions } = options;

  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      const res = await fetch(url, fetchOptions);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.json();
    } catch (err) {
      // 最后一次也失败,直接抛出
      if (attempt === retries) throw err;

      // 指数退避:1s → 2s → 4s,加随机抖动避免多个客户端同时重试
      const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 500;
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}

问题三:这里谁需要感知变化?

这个问题专门针对 React 组件里的状态设计。搞清楚"谁需要感知",能避免大量不必要的重渲染。

场景 A:变化只有局部需要感知 → 状态下沉 + 精确订阅

javascript 复制代码
// 环境:浏览器(React)
// 场景:50 个订单卡片,只有状态变化的那张需要重渲染
// 依赖:zustand

import { create } from 'zustand';

const useOrderStore = create((set) => ({
  statuses: {}, // { [orderId]: status }
  update: (orderId, status) =>
    set((state) => ({
      statuses: { ...state.statuses, [orderId]: status },
    })),
}));

// 每个 OrderCard 精确订阅自己那条
// 其他订单状态变化时,这个组件完全不感知,不重渲染
const OrderCard = React.memo(function OrderCard({ orderId }) {
  const status = useOrderStore(
    useCallback((state) => state.statuses[orderId], [orderId])
  );

  return (
    <div className={`card status-${status}`}>
      Order {orderId}: {status}
    </div>
  );
});

场景 A-2:从零手写一个精确订阅 store

用 Zustand 能解决问题,但理解"精确订阅为什么能工作",需要从零实现一次。这道题是一个很好的切入点:实现一个点赞 store,要求每个 item 的订阅者只在自己那条数据变化时才被通知。

javascript 复制代码
// 环境:浏览器 / Node.js
// 场景:轻量点赞 store,精确订阅------只通知真正变化的那条

function createLikeStore(initialLikes = {}) {
  // 状态:点赞数据的真实来源
  const likes = { ...initialLikes };

  // 订阅表:每个 itemId 对应一个 listener Set
  // 用 Map<itemId, Set<listener>> 而不是 Map<itemId, listener[]>
  // 原因:Set 保证同一个 listener 不会被重复添加,且删除是 O(1)
  const listenersById = new Map();

  return {
    // 返回快照(浅拷贝),防止外部直接修改内部状态
    getState() {
      return { ...likes };
    },

    toggleLike(itemId) {
      const nextValue = !likes[itemId];
      likes[itemId] = nextValue;

      // 精确通知:只触发这个 itemId 的订阅者,其他 item 的订阅者完全不感知
      const listeners = listenersById.get(itemId);
      if (listeners) {
        for (const listener of listeners) {
          listener(nextValue);
        }
      }

      return nextValue;
    },

    subscribeById(itemId, listener) {
      // 懒初始化:第一次有人订阅这个 itemId 时才创建 Set
      if (!listenersById.has(itemId)) {
        listenersById.set(itemId, new Set());
      }
      listenersById.get(itemId).add(listener);

      // 返回卸载函数------这个设计的必要性见下方说明
      return () => {
        const listeners = listenersById.get(itemId);
        if (!listeners) return;

        listeners.delete(listener);

        // 当这个 itemId 没有任何订阅者时,清理 Map 中的条目
        // 防止 Map 无限增长(内存泄漏)
        if (listeners.size === 0) {
          listenersById.delete(itemId);
        }
      };
    },
  };
}

为什么 subscribeById 必须返回卸载函数?

这是这道题里最值得单独理解的设计决策。

想象一个商品列表页,有 100 个商品卡片,每个都订阅了自己的点赞状态。用户从这个页面跳转到详情页,列表页的 100 个组件全部卸载。如果没有卸载函数:

javascript 复制代码
// 没有卸载函数时会发生什么

// 组件挂载时订阅
useEffect(() => {
  store.subscribeById(itemId, (nextValue) => {
    setIsLiked(nextValue); // 更新已卸载组件的 state
  });
  // 没有返回卸载函数
}, [itemId]);

// 问题一:内存泄漏
// listenersById 里仍然持有 100 个已卸载组件的 listener 引用
// 这 100 个闭包无法被垃圾回收,页面停留越久内存占用越高

// 问题二:调用已卸载组件的 setState
// 用户在详情页点了某个商品的赞,store 触发通知
// 那个已卸载的 listener 还在,被调用后尝试 setIsLiked
// React 会报警告:Can't perform a React state update on an unmounted component

有了卸载函数,就能在 useEffect 的 cleanup 里调用它:

javascript 复制代码
// 环境:浏览器(React)
// 场景:组件卸载时自动清理订阅,避免内存泄漏

function LikeButton({ itemId, store }) {
  const [isLiked, setIsLiked] = useState(
    () => store.getState()[itemId] ?? false
  );

  useEffect(() => {
    // 订阅这个 item 的状态变化
    const unsubscribe = store.subscribeById(itemId, (nextValue) => {
      setIsLiked(nextValue);
    });

    // cleanup:组件卸载时调用 unsubscribe
    // → listener 从 Set 中删除
    // → 如果这个 itemId 没有其他订阅者,Map 中的条目也被清理
    return unsubscribe;
  }, [itemId, store]);

  return (
    <button onClick={() => store.toggleLike(itemId)}>
      {isLiked ? '❤️' : '🤍'}
    </button>
  );
}

这个模式在 React 生态里几乎无处不在------useEffect 返回的 cleanup 函数,本质上就是在调用"注册时拿到的卸载句柄"。Redux 的 store.subscribe、RxJS 的 subscription.unsubscribe、浏览器原生的 removeEventListener,都是同一个模式:注册时拿到句柄,卸载时用句柄清理

这也是为什么"cleanup 对称原则"(问题四的场景 C)和这里的"卸载函数设计"是同一个底层思想------你开了什么,就要有对应的关闭路径。

场景 B:变化需要跨 render 保留,但不触发渲染 → useRef

判断标准只有一句话:这个值变化时需要更新 UI 吗? 需要 → useState,不需要 → useRef

javascript 复制代码
// 环境:浏览器(React)
// 场景:轮询实现,timer id 变化不需要触发渲染

function usePolling(fetchFn, interval = 5000) {
  const timerRef = useRef(null);     // timer id → 不需要触发渲染 → useRef
  const [data, setData] = useState(null); // 数据 → 需要更新 UI → useState

  useEffect(() => {
    const tick = async () => {
      const result = await fetchFn();
      setData(result);
    };

    tick(); // 立即执行一次
    timerRef.current = setInterval(tick, interval);

    // cleanup:组件卸载时停止轮询
    return () => clearInterval(timerRef.current);
  }, [fetchFn, interval]);

  return data;
}

场景 C:派生状态 → 不要单独存一份 state

javascript 复制代码
// 环境:浏览器(React)
// 场景:购物车,"是否全选"可以从 items 推导,不需要单独管理

function Cart() {
  const [items, setItems] = useState([
    { id: 1, name: 'Item A', selected: true },
    { id: 2, name: 'Item B', selected: false },
  ]);

  // ✅ 派生状态:从 items 计算,不单独存 state
  // 避免 items 和 isAllSelected 不同步的 bug
  const isAllSelected = items.every((item) => item.selected);
  const selectedCount = items.filter((item) => item.selected).length;

  const toggleAll = () => {
    setItems((prev) =>
      prev.map((item) => ({ ...item, selected: !isAllSelected }))
    );
  };

  return (
    <div>
      <label>
        <input type="checkbox" checked={isAllSelected} onChange={toggleAll} />
        全选(已选 {selectedCount} 件)
      </label>
      {/* 渲染列表 */}
    </div>
  );
}

问题四:这里可能出什么错?

最后这个问题是防御性思维------在写"正常路径"的代码之前,先想清楚异常路径。

场景 A:部分操作可能失败 → allSettled + 结果收集

javascript 复制代码
// 环境:浏览器 / Node.js
// 场景:批量发送通知,有些用户可能发送失败,需要知道哪些失败了

async function sendBatchNotifications(userIds, message) {
  const tasks = userIds.map((userId) =>
    sendNotification(userId, message)
      .then(() => ({ userId, success: true }))
      .catch((err) => ({ userId, success: false, error: err.message }))
  );

  const settled = await Promise.allSettled(tasks);

  const succeeded = [];
  const failed = [];

  for (const result of settled) {
    // allSettled 不会 reject,每个结果都是 fulfilled
    // value 里才是我们自己封装的成功/失败信息
    if (result.status === 'fulfilled' && result.value.success) {
      succeeded.push(result.value.userId);
    } else {
      failed.push(
        result.status === 'fulfilled'
          ? result.value
          : { userId: 'unknown', error: result.reason }
      );
    }
  }

  return { succeeded, failed };
}

场景 B:异步操作可能竞态 → 标记最新请求

竞态的本质是:多个异步操作在同一个"目标"上竞争,需要决定哪个的结果有效,过期的丢弃。

处理竞态有三种方案,力度递增:

复制代码
方案一:isCurrent 标记      结果回来了,但我忽略它(被动丢弃,React 组件内)
方案二:requestId 计数器    结果回来了,但我忽略它(被动丢弃,框架无关)
方案三:AbortController    结果还没回来,我让它停止(主动取消)

方案一: isCurrent ------ React 组件内的局部标记

javascript 复制代码
// 环境:浏览器(React)
// 场景:Tab 切换时快速加载不同内容,只展示最后一次请求的结果

function useTabData(activeTab) {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 标记活在这次 effect 的闭包里,只属于"这一次"
    // 每次 activeTab 变化,产生新的闭包和新的 isCurrent
    let isCurrent = true;

    fetchTabData(activeTab).then((result) => {
      if (isCurrent) setData(result); // 过期就不更新
    });

    return () => {
      isCurrent = false; // cleanup 让这次请求过期
    };
  }, [activeTab]);

  return data;
}

isCurrent二值凭证------只有有效/无效两种状态。标记的生命周期和单次 effect 绑定,cleanup 执行就过期,下次 effect 重新产生一个新的。

方案二:requestId 计数器 --- 框架无关的通用包装

javascript 复制代码
// 环境:浏览器 / Node.js
// 场景:包装任意异步函数,只让最新一次调用的结果被应用
// 可在 React 组件外使用,适合封装成通用工具

function createLatestOnlyRunner(asyncFn) {
  // 计数器活在外层闭包,跨所有调用共享
  // 每次新调用让计数器加一,旧调用的 id 永久地比最新 id 小
  let latestRequestId = 0;

  return async (...args) => {
    const requestId = ++latestRequestId; // 拿到这次调用的"身份 id"

    try {
      const value = await asyncFn(...args);

      // 结果回来时,检查自己是不是还是最新的那次
      if (requestId !== latestRequestId) {
        return { applied: false }; // 已过期,丢弃结果
      }
      return { applied: true, value };

    } catch (e) {
      // 失败时也要判断:是最新请求的失败才需要处理
      if (requestId !== latestRequestId) {
        return { applied: false }; // 过期请求的失败,静默丢弃
      }
      throw e; // 最新请求的失败,正常抛出
    }
  };
}

// 使用示例:搜索场景
const searchRunner = createLatestOnlyRunner(fetchSearchResults);

async function handleSearch(query) {
  const result = await searchRunner(query);
  if (result.applied) {
    renderResults(result.value); // 只有最新请求的结果才渲染
  }
  // applied: false 的直接忽略
}

requestId单调递增的数字凭证------通过大小关系自动判断新旧,不需要手动让旧的过期,新调用天然让旧调用失效。标记的生命周期和 runner 整个生命周期绑定。

两种方案的核心差异对比

bash 复制代码
                    isCurrent 方案              requestId 方案
标记存在哪里         useEffect 闭包(局部)       外层闭包(全局共享)
生命周期             和单次 effect 绑定           和 runner 整个生命周期绑定
过期机制             cleanup 主动设为 false        新调用让旧 id 自然过期
能否在组件外用        不能(依赖 useEffect)        能(纯函数,任何地方可用)
适合场景             React 组件内的异步请求        通用工具函数、非 React 环境

两者的底层思想完全相同:给每次操作打上身份凭证,结果回来时验证凭证是否仍然是最新的。 差异只在凭证的形态和管理方式。

方案三:AbortController --- 主动取消,不等结果回来

javascript 复制代码
// 环境:浏览器(React)
// 场景:搜索框,用户继续输入时直接取消上一个网络请求
// 不只是忽略结果,而是中止请求,节省服务器和网络资源

function useSearch(query) {
  const abortRef = useRef(null);
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!query.trim()) return;

    abortRef.current?.abort(); // 取消上一个请求
    const controller = new AbortController();
    abortRef.current = controller;

    fetch(`/api/search?q=${encodeURIComponent(query)}`, {
      signal: controller.signal,
    })
      .then((res) => res.json())
      .then(setResults)
      .catch((err) => {
        if (err.name !== 'AbortError') console.error(err);
      });

    return () => controller.abort();
  }, [query]);

  return results;
}

AbortController 是主动取消凭证 ------不只是标记"这个结果过期了",而是直接发出取消信号,让网络层停止这个请求。代价更低(请求未完成就终止),但依赖 fetch 原生支持 signal 参数。

三种方案的选择建议

复制代码
在 React 组件里,请求和组件生命周期绑定
  → isCurrent 标记,最简单,足够用

需要在组件外复用,或包装第三方异步函数
  → createLatestOnlyRunner,框架无关,可测试

请求体积大、耗时长,取消能节省明显资源
  → AbortController,主动中止,力度最强

场景 C:副作用可能泄漏 → cleanup 对称原则

javascript 复制代码
// 环境:浏览器(React)
// 场景:WebSocket 连接,组件卸载时必须关闭,否则内存泄漏

function useOrderMonitor(orderId) {
  const [status, setStatus] = useState(null);

  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}`);

    ws.onmessage = (event) => {
      const { status } = JSON.parse(event.data);
      setStatus(status);
    };

    // 启动了 WebSocket → cleanup 里就关闭 WebSocket
    // 对称原则:你在 effect 里做了什么,cleanup 里就撤销什么
    return () => ws.close(1000, 'component unmounted');
  }, [orderId]);

  return status;
}

场景 D:数据可能过期 → TTL + 重新验证

javascript 复制代码
// 环境:浏览器
// 场景:缓存接口数据,超过有效期后重新请求

const cache = new Map(); // key → { data, timestamp }
const CACHE_TTL = 5 * 60 * 1000; // 5 分钟

async function fetchWithCache(url) {
  const cached = cache.get(url);

  if (cached) {
    const isExpired = Date.now() - cached.timestamp > CACHE_TTL;
    if (!isExpired) return cached.data; // 缓存有效,直接返回
  }

  // 缓存不存在或已过期,重新请求
  const data = await fetch(url).then((r) => r.json());
  cache.set(url, { data, timestamp: Date.now() });
  return data;
}

延伸与发散

整理这四个问题的过程里,我发现它们之间有一些有趣的交叉:

问题三和问题四的交叉 :竞态问题(问题四)本质上也是"谁应该感知变化"的问题(问题三)------当两个异步操作都想更新同一个状态时,你需要决定"只有最新的那个有资格更新"。isCurrent 标记和 AbortController 都是在回答这个问题。

问题二和问题四的交叉:需要取消和需要重试,本质上都是"时间维度里的异常路径"。一个是"有更新的请求来了,旧的不需要了";一个是"这个请求失败了,隔一段时间再试"。两者都是对异步操作生命周期的管理。

还有一类题目这四个问题都没有完全覆盖:乐观更新。先更新 UI,再发请求,失败时回滚。这既涉及"谁感知变化"(状态管理),也涉及"可能出什么错"(失败回滚),是一个跨维度的场景。这个模型值得单独展开讨论。


小结

这四个问题不是一套算法,不能保证"输入题目,输出答案"。它们更像是一套减少遗漏的检查清单------帮助你在开始写代码之前,把题目的关键维度都想到。

实际使用时,四个问题往往不是顺序回答的,而是同时浮现的。看到"批量上传",同时会想到"时间维度(并发)"和"可能出错(部分失败)"。这种并行思考是熟练之后自然发生的,不需要刻意按顺序走。

如果你有其他觉得有用的判断维度,欢迎交流------这套框架本身也是在持续迭代的。


参考资料

相关推荐
Wect2 小时前
JS 手撕:对象创建、继承全解析
前端·javascript·面试
一江寒逸2 小时前
零基础从入门到精通MySQL(附加篇):面试八股文全集
数据库·mysql·面试
PeterMap2 小时前
Vue.js全面解析:从入门到上手,前端新手的首选框架
前端·vue.js
3秒一个大2 小时前
深入理解 JS 中的栈与堆:从内存模型到数据结构,再谈内存泄漏
前端·javascript·数据结构
Mr_Xuhhh2 小时前
深入Java多线程进阶:从锁策略到并发工具全解析
前端·数据库·python
阿捞22 小时前
Inertia.js 持久布局实现原理
前端·javascript·html
不会写DN2 小时前
如何在纯前端中通过手势交互来控制星球的转动
前端·交互
zjeweler2 小时前
网安护网面试-2-国誉护网面试
web安全·网络安全·面试·职场和发展·护网行动·护网面试
liliangcsdn3 小时前
sentence-transformer如何离线加载和使用模型
开发语言·前端·php