46~65 【中频 · 有时间掌握】
DOM树遍历 | 生/熟/秒:
深度:递归 childNodes/children;广度:队列。注意 childNodes 含文本节点,元素节点用 children。
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)。
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 统一风格。
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。
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)。
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 绑定原对象。
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/eat,execute 返回队列 Promise。
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_case → camelCase:分割 _/- 后首字母大写拼接;反向则正则大写前插 _。
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。
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。
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。
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}getCookie | 生/熟/秒:
document.cookie 按 ; 分割,再按 = 取 key;注意 decodeURIComponent。
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 } 或定时清理。
const visitMap = new Map();
function recordVisit(id) {
visitMap.set(id, (visitMap.get(id) || 0) + 1);
}
function getVisit(id) {
return visitMap.get(id) || 0;
}去除最少字符 | 生/熟/秒:
经典:删最少括号使串合法。栈记索引,最后栈里为需删下标;或一次遍历计数左右括号。
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;纯字符串可用正则去标签(弱严谨)。
function stripHtml(html) {
const doc = new DOMParser().parseFromString(html, 'text/html');
return doc.body.textContent || '';
}Calculator计算器 | 生/熟/秒:
双栈:数字栈 + 运算符栈,遇数入栈,遇符比较优先级,*/ 高于 +-,括号递归或单独处理。
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 进视口再赋 src 并 unobserve。
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 包一层:baseURL、headers、interceptors.request/response 链式改 config / data。
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。
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,返回的是「上一轮」。
function usePrevious(value) {
const ref = React.useRef(value);
const prev = ref.current;
ref.current = value;
return prev;
}