手写题考的不是背代码,而是边界处理与代码品味。这份清单给可直接运行的实现,并标出面试官最常追问的坑。
先讲签名与思路,再写主干、补边界、说局限。
| 维度 | 面试官看什么 |
|---|---|
| 正确性 | 空值、循环引用、类型分支是否覆盖 |
| 可读性 | 命名、拆分、注释是否点到要害 |
| 健壮性 | 非法入参是否处理 |
this/参数丢失;计时器句柄未重置。function debounce(fn, wait = 300, immediate = false) {
let timer = null;
return function debounced(...args) {
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => { timer = null; if (!immediate) fn.apply(this, args); }, wait);
if (callNow) fn.apply(this, args);
};
}
function throttle(fn, wait = 300, { leading = true, trailing = true } = {}) {
let last = 0, timer = null, ctx = null, args = null;
const invoke = (now) => { last = now; fn.apply(ctx, args); timer = args = ctx = null; };
return function (...a) {
const now = Date.now(); if (!last && !leading) last = now;
const remain = wait - (now - last); ctx = this; args = a;
if (remain <= 0) { clearTimeout(timer); invoke(now); }
else if (!timer && trailing) timer = setTimeout(() => invoke(Date.now()), remain);
};
}
Date/RegExp/Map/Set 变空对象;原型丢失。WeakMap 为什么不用 Map?function deepClone(value, map = new WeakMap()) {
if (value === null || typeof value !== 'object') return value;
if (map.has(value)) return map.get(value);
if (value instanceof Date) return new Date(value);
if (value instanceof RegExp) return new RegExp(value.source, value.flags);
const target = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
map.set(value, target);
if (value instanceof Map) value.forEach((v, k) => target.set(deepClone(k, map), deepClone(v, map)));
else if (value instanceof Set) value.forEach((v) => target.add(deepClone(v, map)));
else for (const k of Reflect.ownKeys(value)) target[k] = deepClone(value[k], map);
return target;
}
thisctx 为 null 要指向 globalThis;原始值要装箱;临时键用 Symbol。ctx.fn?Function.prototype.myCall = function (ctx, ...args) {
ctx = ctx == null ? globalThis : Object(ctx);
const key = Symbol('fn');
Object.defineProperty(ctx, key, { value: this, configurable: true });
const ret = ctx[key](...args); delete ctx[key];
return ret;
};
Function.prototype.myApply = function (ctx, args = []) { return this.myCall(ctx, ...args); };
Function.prototype.myBind = function (ctx, ...preset) {
const fn = this;
function bound(...args) { return fn.apply(this instanceof bound ? this : ctx, [...preset, ...args]); }
bound.prototype = Object.create(fn.prototype || null);
return bound;
};
new.target 有什么用?function myNew(Ctor, ...args) {
if (typeof Ctor !== 'function') throw new TypeError('not a constructor');
const obj = Object.create(Ctor.prototype);
const ret = Ctor.apply(obj, args);
return ret && (typeof ret === 'object' || typeof ret === 'function') ? ret : obj;
}
Ctor.prototype。false;右操作数必须是函数。Symbol.hasInstance 如何自定义?和 isPrototypeOf 的区别?function myInstanceof(obj, Ctor) {
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) return false;
let proto = Object.getPrototypeOf(obj);
while (proto) { if (proto === Ctor.prototype) return true; proto = Object.getPrototypeOf(proto); }
return false;
}
fn.length 不含默认参数与 rest;丢失 this。add(1)(2)(3) 与 add(1, 2)(3) 都要支持,怎么写?function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args);
return (...rest) => curried.apply(this, [...args, ...rest]);
};
}
class EventEmitter {
constructor() { this.map = new Map(); }
on(type, fn) {
if (!this.map.has(type)) this.map.set(type, new Set());
this.map.get(type).add(fn); return () => this.off(type, fn);
}
off(type, fn) { this.map.get(type)?.delete(fn); }
once(type, fn) { const wrap = (...a) => { this.off(type, wrap); fn(...a); }; return this.on(type, wrap); }
emit(type, ...args) { [...(this.map.get(type) || [])].forEach((fn) => fn(...args)); }
}
then 链,回调异步,支持值穿透。then 返回新实例;回调进微任务。class MyPromise {
constructor(executor) {
this.state = 'pending'; this.value = undefined; this.cbs = [];
const reject = (e) => this.settle('rejected', e);
const resolve = (v) => {
if (v === this) throw new TypeError('chaining cycle');
if (v instanceof MyPromise) return v.then(resolve, reject);
this.settle('fulfilled', v);
};
try { executor(resolve, reject); } catch (e) { reject(e); }
}
settle(state, value) {
if (this.state !== 'pending') return;
this.state = state; this.value = value;
queueMicrotask(() => this.cbs.splice(0).forEach((run) => run()));
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.cbs.push(() => {
const fn = this.state === 'fulfilled' ? onFulfilled : onRejected;
if (typeof fn !== 'function') return this.state === 'fulfilled' ? resolve(this.value) : reject(this.value);
try { resolve(fn(this.value)); } catch (e) { reject(e); }
});
if (this.state !== 'pending') queueMicrotask(() => this.cbs.splice(0).forEach((run) => run()));
});
}
catch(onRejected) { return this.then(null, onRejected); }
}
all 任一失败即失败;allSettled 永远成功。race 与 any 的区别?const promiseAll = (list) => new Promise((resolve, reject) => {
const items = Array.from(list), result = new Array(items.length); let count = 0;
if (!items.length) return resolve(result);
items.forEach((item, i) => {
Promise.resolve(item).then((v) => { result[i] = v; if (++count === items.length) resolve(result); }, reject);
});
});
const promiseAllSettled = (list) => Promise.all(Array.from(list, (item) => Promise.resolve(item).then(
(value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
depth 为 0 要浅拷贝而非返回原引用。function flatten(arr, depth = Infinity) {
if (depth < 1) return arr.slice();
return arr.reduce((acc, item) => acc.concat(Array.isArray(item) ? flatten(item, depth - 1) : item), []);
}
const unique = (arr) => [...new Set(arr)];
const uniqueBy = (arr, keyOf) => {
const seen = new Set();
return arr.filter((item) => { const k = keyOf(item); if (seen.has(k)) return false; seen.add(k); return true; });
};
proto 为 null 时不能直接 new;要修正 constructor。class extends 编译后是什么?function myCreate(proto) {
if (proto !== null && typeof proto !== 'object' && typeof proto !== 'function') throw new TypeError('bad proto');
function F() {}
F.prototype = proto; return new F();
}
function inherit(Child, Parent) {
Child.prototype = myCreate(Parent.prototype);
Child.prototype.constructor = Child;
Object.setPrototypeOf(Child, Parent);
}
get 命中要刷新顺序;容量为 0 要直接返回。Map 怎么手写?class LRUCache {
constructor(capacity) { this.capacity = capacity; this.map = new Map(); }
get(key) {
if (!this.map.has(key)) return -1;
const value = this.map.get(key);
this.map.delete(key); this.map.set(key, value);
return value;
}
put(key, value) {
this.map.delete(key); this.map.set(key, value);
if (this.map.size > this.capacity) this.map.delete(this.map.keys().next().value);
}
}