Skip to content

26~45 【高频 · 基础掌握】

Promise.allSettled | 生/熟/秒:

全部 settle 才 resolve,结果数组每项 {status, value|reason}。不短路,和 all 不同。

js
function promiseAllSettled(promises) {
  return Promise.all(
    promises.map(p =>
      Promise.resolve(p).then(
        value => ({ status: 'fulfilled', value }),
        reason => ({ status: 'rejected', reason })
      )
    )
  );
}

sleep | 生/熟/秒:

new Promise + setTimeout 一行封装延迟。

js
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// await sleep(1000);

千分位格式化 | 生/熟/秒:

正则从后往前每三位加逗号;或 toLocaleString

js
function formatThousands(num) {
  const [int, dec] = String(num).split('.');
  const intFmt = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  return dec ? `${intFmt}.${dec}` : intFmt;
}
// formatThousands(1234567.89) => "1,234,567.89"

URL解析 | 生/熟/秒:

优先 URL API;手写则 split 协议、host、pathname、search、hash。

js
function parseUrl(href) {
  const u = new URL(href);
  const query = {};
  u.searchParams.forEach((v, k) => { query[k] = v; });
  return {
    protocol: u.protocol,
    host: u.host,
    pathname: u.pathname,
    search: u.search,
    hash: u.hash,
    query,
  };
}

时间格式化 | 生/熟/秒:

补零函数 + 取年月日时分秒拼接。注意月份 +1。

js
function formatDate(date = new Date(), tpl = 'YYYY-MM-DD HH:mm:ss') {
  const pad = n => String(n).padStart(2, '0');
  const map = {
    YYYY: date.getFullYear(),
    MM: pad(date.getMonth() + 1),
    DD: pad(date.getDate()),
    HH: pad(date.getHours()),
    mm: pad(date.getMinutes()),
    ss: pad(date.getSeconds()),
  };
  return tpl.replace(/YYYY|MM|DD|HH|mm|ss/g, m => map[m]);
}

Omit/Pick(JS+TS类型) | 生/熟/秒:

Pick 选 key 子集;Omit = 解构去掉不要的 key。TS 用 Pick<T,K> / Omit<T,K>

js
function pick(obj, keys) {
  return keys.reduce((acc, k) => (k in obj && (acc[k] = obj[k]), acc), {});
}
function omit(obj, keys) {
  const set = new Set(keys);
  return Object.keys(obj).reduce((acc, k) => (set.has(k) ? acc : (acc[k] = obj[k], acc)), {});
}
ts
type PickUser = Pick<User, 'id' | 'name'>;
type OmitUser = Omit<User, 'password'>;

文字截断 | 生/熟/秒:

超长加 ,按字符长度 slice;中英文混排可换 Intl.Segmenter(了解即可)。

js
function truncate(str, max, suffix = '…') {
  if (str.length <= max) return str;
  return str.slice(0, max - suffix.length) + suffix;
}

隐藏元素的方式 | 生/熟/秒:

display:none 不占位;visibility:hidden 占位不可点;opacity:0 占位可点(需 pointer-events:none);position 移出视口;height:0;overflow:hidden

css
.hide-a { display: none; }
.hide-b { visibility: hidden; }
.hide-c { opacity: 0; pointer-events: none; }
.hide-d { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

CSS画三角形 | 生/熟/秒:

宽高 0 + 三边 transparent + 一边有色。

css
.tri {
  width: 0;
  height: 0;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-bottom: 12px solid #333; /* 朝下三角 */
}

Counter计数器 | 生/熟/秒:

闭包保存 count,返回 inc/dec/get。

js
function createCounter(init = 0) {
  let count = init;
  return {
    inc: (n = 1) => (count += n),
    dec: (n = 1) => (count -= n),
    get: () => count,
    reset: () => (count = init),
  };
}

TodoList | 生/熟/秒:

状态数组 + id,增删改查过滤。面试写数据结构 + 纯函数即可。

js
let todos = [];
let id = 1;
function addTodo(text) {
  todos.push({ id: id++, text, done: false });
}
function toggleTodo(id) {
  const t = todos.find(x => x.id === id);
  if (t) t.done = !t.done;
}
function removeTodo(id) {
  todos = todos.filter(x => x.id !== id);
}

CountDown倒计时 | 生/熟/秒:

先打一拍当前秒数,再 setInterval 每秒减一并回调,到 0 清定时器。要更准可用 setTimeout 链减漂移。

js
function countdown(seconds, onTick, onEnd) {
  let left = seconds;
  onTick(left);
  const timer = setInterval(() => {
    left--;
    onTick(left);
    if (left <= 0) {
      clearInterval(timer);
      onEnd && onEnd();
    }
  }, 1000);
  return () => clearInterval(timer);
}

模拟useState | 生/熟/秒:

数组存 state,cursor 记当前 hook 顺序,每次 render 重置 cursor。简版演示用模块级变量。

js
let stateSlot = [];
let cursor = 0;
function useState(initial) {
  const c = cursor++;
  if (stateSlot[c] === undefined) stateSlot[c] = typeof initial === 'function' ? initial() : initial;
  const setState = (v) => {
    stateSlot[c] = typeof v === 'function' ? v(stateSlot[c]) : v;
    render(); // 触发重渲染
  };
  return [stateSlot[c], setState];
}
function render() { cursor = 0; /* 重新执行组件函数 */ }

useDebounce防抖Hook | 生/熟/秒:

useEffect + useRef 存 timer,依赖变则防抖执行。

js
function useDebounce(value, delay) {
  const [debounced, setDebounced] = React.useState(value);
  React.useEffect(() => {
    const t = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(t);
  }, [value, delay]);
  return debounced;
}

useThrottle节流Hook | 生/熟/秒:

useRef 记上次时间或 trailing timer,useEffect 里包一层节流更新 state。

js
function useThrottle(value, interval) {
  const [throttled, setThrottled] = React.useState(value);
  const last = React.useRef(Date.now());
  React.useEffect(() => {
    const now = Date.now();
    if (now - last.current >= interval) {
      last.current = now;
      setThrottled(value);
    } else {
      const t = setTimeout(() => {
        last.current = Date.now();
        setThrottled(value);
      }, interval - (now - last.current));
      return () => clearTimeout(t);
    }
  }, [value, interval]);
  return throttled;
}

Promise.any | 生/熟/秒:

任一 fulfilled 就 resolve;全 reject 才 reject(AggregateError)。与 race 区别:race 第一个 settle 含 reject。

js
function promiseAny(promises) {
  if (!promises.length) return Promise.reject(new AggregateError([], 'All rejected'));
  const errs = [];
  let remaining = promises.length;
  return new Promise((resolve, reject) => {
    promises.forEach((p, i) => {
      Promise.resolve(p).then(resolve, err => {
        errs[i] = err;
        if (--remaining === 0) reject(new AggregateError(errs, 'All rejected'));
      });
    });
  });
}

并发控制 | 生/熟/秒:

limit 个 worker 抢索引,各自 while 拉下一个 task,全部完成 Promise.all 收口。

js
async function parallelLimit(tasks, limit) {
  const results = [];
  let i = 0;
  async function worker() {
    while (i < tasks.length) {
      const idx = i++;
      results[idx] = await tasks[idx]();
    }
  }
  const n = Math.min(limit, tasks.length) || 0;
  await Promise.all(Array.from({ length: n }, worker));
  return results;
}

retry重试+超时控制 | 生/熟/秒:

循环尝试 catch 累加次数;超时用 Promise.race 包一层 reject。

js
async function withTimeout(promise, ms) {
  let t;
  const timeout = new Promise((_, rej) => { t = setTimeout(() => rej(new Error('timeout')), ms); });
  try {
    return await Promise.race([promise, timeout]);
  } finally {
    clearTimeout(t);
  }
}

async function retry(fn, { times = 3, delay = 0 } = {}) {
  let last;
  for (let i = 0; i < times; i++) {
    try { return await fn(); } catch (e) { last = e; if (delay) await new Promise(r => setTimeout(r, delay)); }
  }
  throw last;
}

树转数组 | 生/熟/秒:

DFS 先序遍历 push,带 levelparentId 视题目;BFS 用队列。

js
function treeToList(root) {
  const res = [];
  function dfs(node) {
    if (!node) return;
    const { children, ...rest } = node;
    res.push(rest);
    (children || []).forEach(dfs);
  }
  dfs(root);
  return res;
}

二叉树遍历 | 生/熟/秒:

前中后序递归一行基线;非递归用栈模拟。层序用队列。

js
function preorder(root, acc = []) {
  if (!root) return acc;
  acc.push(root.val);
  preorder(root.left, acc);
  preorder(root.right, acc);
  return acc;
}

function inorder(root, acc = []) {
  if (!root) return acc;
  inorder(root.left, acc);
  acc.push(root.val);
  inorder(root.right, acc);
  return acc;
}

function levelOrder(root) {
  if (!root) return [];
  const q = [root], res = [];
  while (q.length) {
    const n = q.shift();
    res.push(n.val);
    if (n.left) q.push(n.left);
    if (n.right) q.push(n.right);
  }
  return res;
}