Skip to content

46~65 【中频 · 有时间掌握】

DOM树遍历 | 生/熟/秒:

深度:递归 childNodes/children;广度:队列。注意 childNodes 含文本节点,元素节点用 children

js
function walkDFS(root, cb) {
  if (!root) return;
  cb(root);
  [...root.children].forEach(child => walkDFS(child, cb));
}

function walkBFS(root, cb) {
  if (!root) return;
  const q = [root];
  while (q.length) {
    const n = q.shift();
    cb(n);
    q.push(...n.children);
  }
}

lodash set | 生/熟/秒:

路径转数组,逐层 cur = cur[key],不存在则补 {}[](数字 key)。

js
function set(obj, path, value) {
  const keys = Array.isArray(path) ? path : path.replace(/\[(\d+)]/g, '.$1').split('.');
  let cur = obj;
  for (let i = 0; i < keys.length - 1; i++) {
    const k = keys[i];
    const next = keys[i + 1];
    if (cur[k] == null) cur[k] = String(Number(next)) === next ? [] : {};
    cur = cur[k];
  }
  cur[keys.at(-1)] = value;
  return obj;
}

对象扁平化flattenObj | 生/熟/秒:

递归,key 用 . 拼接;数组下标用 [i].i 统一风格。

js
function flattenObj(obj, prefix = '') {
  const res = {};
  for (const k of Object.keys(obj)) {
    const key = prefix ? `${prefix}.${k}` : k;
    const v = obj[k];
    if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
      Object.assign(res, flattenObj(v, key));
    } else {
      res[key] = v;
    }
  }
  return res;
}

compose/pipe | 生/熟/秒:

compose 从右到左包一层:reduceRight;pipe 从左到右 reduce

js
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
// compose(f,g,h)(x) === f(g(h(x)))

红绿灯循环 | 生/熟/秒:

async + while(true) + sleep,红绿黄顺序 await sleep(ms)

js
async function trafficLight() {
  while (true) {
    console.log('red'); await sleep(3000);
    console.log('green'); await sleep(2000);
    console.log('yellow'); await sleep(1000);
  }
}

Promisify | 生/熟/秒:

Node 风格 (err, res) => 回调包成 Promise;注意 this 绑定原对象。

js
function promisify(fn) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      fn.call(this, ...args, (err, res) => (err ? reject(err) : resolve(res)));
    });
  };
}

CodingMan | 生/熟/秒:

链式返回 this,内部用 queue = Promise.resolve()sleep/eatexecute 返回队列 Promise。

js
class CodingMan {
  constructor(name) {
    this.name = name;
    this.queue = Promise.resolve();
  }
  sleep(sec) {
    this.queue = this.queue.then(() => new Promise(r => setTimeout(r, sec * 1000)));
    return this;
  }
  eat(food) {
    this.queue = this.queue.then(() => console.log(`${this.name} eat ${food}`));
    return this;
  }
  execute() {
    return this.queue;
  }
}

驼峰转换 | 生/熟/秒:

snake_casecamelCase:分割 _/- 后首字母大写拼接;反向则正则大写前插 _

js
function snakeToCamel(s) {
  return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
}
function camelToSnake(s) {
  return s.replace(/[A-Z]/g, m => '_' + m.toLowerCase());
}

大数相加 | 生/熟/秒:

字符串从末位逐位加,维护 carry,长度不等补 0。

js
function addBig(a, b) {
  let i = a.length - 1, j = b.length - 1, carry = 0, res = '';
  while (i >= 0 || j >= 0 || carry) {
    const x = +a[i--] || 0, y = +b[j--] || 0;
    const sum = x + y + carry;
    res = (sum % 10) + res;
    carry = (sum / 10) | 0;
  }
  return res.replace(/^0+/, '') || '0';
}

setTimeout⇄setInterval | 生/熟/秒:

setInterval 回调若阻塞会堆叠;用 setTimeout 递归可等上次结束再排下次。清除:clearTimeout / clearInterval

js
function myInterval(fn, delay) {
  let timer;
  function loop() {
    timer = setTimeout(() => {
      fn();
      loop();
    }, delay);
  }
  loop();
  return () => clearTimeout(timer);
}

randomInt | 生/熟/秒:

[min,max] 整数:Math.floor(Math.random() * (max - min + 1)) + min

js
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

getCookie | 生/熟/秒:

document.cookie; 分割,再按 = 取 key;注意 decodeURIComponent

js
function getCookie(name) {
  const hit = document.cookie.split('; ').find(row => row.startsWith(name + '='));
  return hit ? decodeURIComponent(hit.split('=').slice(1).join('=')) : '';
}

访问次数统计 | 生/熟/秒:

Map<id, count> 自增;带过期可加 { count, expire } 或定时清理。

js
const visitMap = new Map();
function recordVisit(id) {
  visitMap.set(id, (visitMap.get(id) || 0) + 1);
}
function getVisit(id) {
  return visitMap.get(id) || 0;
}

去除最少字符 | 生/熟/秒:

经典:删最少括号使串合法。栈记索引,最后栈里为需删下标;或一次遍历计数左右括号。

js
function minRemoveToValid(s) {
  const arr = s.split('');
  const stack = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === '(') stack.push(i);
    else if (arr[i] === ')') stack.length ? stack.pop() : (arr[i] = '');
  }
  stack.forEach(i => (arr[i] = ''));
  return arr.join('');
}

提取HTML文字 | 生/熟/秒:

DOMParser 解析后 textContent;纯字符串可用正则去标签(弱严谨)。

js
function stripHtml(html) {
  const doc = new DOMParser().parseFromString(html, 'text/html');
  return doc.body.textContent || '';
}

Calculator计算器 | 生/熟/秒:

双栈:数字栈 + 运算符栈,遇数入栈,遇符比较优先级,*/ 高于 +-,括号递归或单独处理。

js
function calculate(s) {
  const tokens = s.replace(/\s/g, '').match(/(\d+|[+\-*/()])/g) || [];
  let i = 0;
  function expr() {
    let num = term();
    while (tokens[i] === '+' || tokens[i] === '-') {
      const op = tokens[i++];
      num += op === '+' ? term() : -term();
    }
    return num;
  }
  function term() {
    let num = factor();
    while (tokens[i] === '*' || tokens[i] === '/') {
      const op = tokens[i++];
      num = op === '*' ? num * factor() : num / factor();
    }
    return num;
  }
  function factor() {
    if (tokens[i] === '(') {
      i++;
      const v = expr();
      i++; // )
      return v;
    }
    return +tokens[i++];
  }
  return expr();
}

LazyImage图片懒加载 | 生/熟/秒:

data-src 占位,IntersectionObserver 进视口再赋 srcunobserve

js
function lazyLoad(img) {
  const io = new IntersectionObserver(entries => {
    entries.forEach(e => {
      if (!e.isIntersecting) return;
      const el = e.target;
      el.src = el.dataset.src;
      io.unobserve(el);
    });
  });
  io.observe(img);
}

CustomAxios简易请求封装 | 生/熟/秒:

fetch 包一层:baseURLheadersinterceptors.request/response 链式改 config / data。

js
function createHttp({ baseURL = '', interceptors = {} } = {}) {
  const chain = async (config) => {
    config = (await interceptors.request?.(config)) || config;
    let { url, method = 'GET', data, headers = {} } = config;
    const res = await fetch(baseURL + url, {
      method,
      headers: { 'Content-Type': 'application/json', ...headers },
      body: data ? JSON.stringify(data) : undefined,
    });
    let body = await res.json();
    body = (await interceptors.response?.(body)) ?? body;
    return body;
  };
  return { get: url => chain({ url }), post: (url, data) => chain({ url, method: 'POST', data }) };
}

useUpdateEffect跳过首次执行 | 生/熟/秒:

useRef 记是否首渲,useEffect 里首渲只翻 ref,之后才真正执行 effect。

js
function useUpdateEffect(effect, deps) {
  const first = React.useRef(true);
  React.useEffect(() => {
    if (first.current) {
      first.current = false;
      return;
    }
    return effect();
  }, deps);
}

usePrevious获取上一次的值 | 生/熟/秒:

每次 render 先读出 ref 里上一次的值,再把本次 value 写回 ref,返回的是「上一轮」。

js
function usePrevious(value) {
  const ref = React.useRef(value);
  const prev = ref.current;
  ref.current = value;
  return prev;
}