在前端面试中,“手撕代码”环节是考察候选人编程能力和思维逻辑的重要部分。以下是一些常见的手撕代码题目及详细解答:
将多维数组转换为一维数组。
方法一:递归
function flatten(arr) {
const result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result.push(...flatten(item)); // 递归展开子数组
} else {
result.push(item);
}
});
return result;
}
方法二:迭代 + 栈
function flatten(arr) {
const result = [];
const stack = [...arr]; // 使用栈模拟递归
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item); // 展开子数组并压入栈
} else {
result.unshift(item); // 保证顺序正确
}
}
return result;
}
实现一个基本的Promise,包含then
、catch
和finally
方法。
class MyPromise {
constructor(executor) {
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
return new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
};
const handleRejected = () => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
};
if (this.status === 'fulfilled') {
setTimeout(handleFulfilled, 0);
} else if (this.status === 'rejected') {
setTimeout(handleRejected, 0);
} else {
this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
count++;
if (count === promises.length) resolve(results);
},
reason => reject(reason)
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(
value => resolve(value),
reason => reject(reason)
);
});
});
}
}
防抖(Debounce):延迟执行,连续触发时重置计时器。
function debounce(func, delay, immediate = false) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
if (immediate && !timer) {
func.apply(this, args);
}
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
节流(Throttle):固定频率执行,忽略中间触发。
function throttle(func, limit) {
let inThrottle;
let lastResult;
return function(...args) {
if (!inThrottle) {
inThrottle = true;
lastResult = func.apply(this, args);
setTimeout(() => inThrottle = false, limit);
}
return lastResult;
};
}
递归复制对象,处理循环引用和特殊对象。
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (map.has(obj)) return map.get(obj);
// 处理特殊对象
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof RegExp) return new RegExp(obj);
// 创建新对象
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
// 递归复制所有属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
call:
Function.prototype.myCall = function(context = window, ...args) {
const fn = Symbol('fn');
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};
apply:
Function.prototype.myApply = function(context = window, args = []) {
const fn = Symbol('fn');
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};
bind:
Function.prototype.myBind = function(context = window, ...args) {
const self = this;
return function(...newArgs) {
return self.apply(context, [...args, ...newArgs]);
};
};
判断对象是否是某个构造函数的实例。
function myInstanceof(obj, Constructor) {
let proto = Object.getPrototypeOf(obj);
const prototype = Constructor.prototype;
while (proto !== null) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(...args));
}
}
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
cb => cb !== callback
);
}
}
once(eventName, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
}
基于Object.defineProperty()实现数据劫持。
class Vue {
constructor(options) {
this.$data = options.data;
this.$options = options;
// 数据响应化
this.observe(this.$data);
// 模拟编译过程
new Compile(options.el, this);
}
observe(obj) {
if (!obj || typeof obj !== 'object') return;
Object.keys(obj).forEach(key => {
this.defineReactive(obj, key, obj[key]);
// 代理data中的属性到Vue实例上
this.proxyData(key);
});
}
defineReactive(obj, key, val) {
// 递归处理嵌套对象
this.observe(val);
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 触发更新
dep.notify();
}
});
}
proxyData(key) {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(newVal) {
this.$data[key] = newVal;
}
});
}
}
// 依赖收集器
class Dep {
constructor() {
this.subs = [];
}
addSub(watcher) {
this.subs.push(watcher);
}
notify() {
this.subs.forEach(watcher => watcher.update());
}
}
// Watcher
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
// 触发getter,收集依赖
Dep.target = this;
this.vm[this.key];
Dep.target = null;
}
update() {
this.cb.call(this.vm, this.vm[this.key]);
}
}
// 模拟编译过程
class Compile {
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
if (this.$el) {
this.compile(this.$el);
}
}
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node);
} else if (this.isTextNode(node)) {
// 处理文本节点
this.compileText(node);
}
// 递归处理子节点
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
compileElement(node) {
const attrs = node.attributes;
Array.from(attrs).forEach(attr => {
const attrName = attr.name;
const exp = attr.value;
if (this.isDirective(attrName)) {
// 处理指令 v-xxx
const dir = attrName.substring(2);
this[dir] && this[dir](node, this.$vm, exp);
} else if (this.isEventDirective(attrName)) {
// 处理事件 @xxx
const dir = attrName.substring(1);
this.eventHandler(node, this.$vm, exp, dir);
}
});
}
compileText(node) {
const text = node.textContent;
const reg = /\{\{(.*)\}\}/;
if (reg.test(text)) {
this.text(node, this.$vm, RegExp.$1.trim());
}
}
// 指令处理函数
text(node, vm, exp) {
this.update(node, vm, exp, 'text');
}
html(node, vm, exp) {
this.update(node, vm, exp, 'html');
}
model(node, vm, exp) {
this.update(node, vm, exp, 'model');
// 双向绑定
node.addEventListener('input', e => {
vm[exp] = e.target.value;
});
}
update(node, vm, exp, dir) {
const updaterFn = this[dir + 'Updater'];
updaterFn && updaterFn(node, vm[exp]);
// 创建Watcher
new Watcher(vm, exp, function(value) {
updaterFn && updaterFn(node, value);
});
}
textUpdater(node, value) {
node.textContent = value;
}
htmlUpdater(node, value) {
node.innerHTML = value;
}
modelUpdater(node, value) {
node.value = value;
}
eventHandler(node, vm, exp, dir) {
const fn = vm.$options.methods && vm.$options.methods[exp];
if (dir && fn) {
node.addEventListener(dir, fn.bind(vm));
}
}
// 辅助方法
isElementNode(node) {
return node.nodeType === 1;
}
isTextNode(node) {
return node.nodeType === 3;
}
isDirective(attr) {
return attr.startsWith('v-');
}
isEventDirective(attr) {
return attr.startsWith('@');
}
}
基于双向链表和哈希表实现最近最少使用(LRU)缓存。
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
this.head = new DLinkedNode();
this.tail = new DLinkedNode();
this.head.next = this.tail;
this.tail.prev = this.head;
}
get(key) {
const node = this.cache.get(key);
if (!node) return -1;
// 移动到链表头部
this.moveToHead(node);
return node.value;
}
put(key, value) {
const node = this.cache.get(key);
if (node) {
// 更新值并移动到头部
node.value = value;
this.moveToHead(node);
} else {
// 创建新节点
const newNode = new DLinkedNode(key, value);
this.cache.set(key, newNode);
this.addToHead(newNode);
if (this.cache.size > this.capacity) {
// 超出容量,移除尾部节点
const tailNode = this.removeTail();
this.cache.delete(tailNode.key);
}
}
}
addToHead(node) {
node.prev = this.head;
node.next = this.head.next;
this.head.next.prev = node;
this.head.next = node;
}
removeNode(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
moveToHead(node) {
this.removeNode(node);
this.addToHead(node);
}
removeTail() {
const node = this.tail.prev;
this.removeNode(node);
return node;
}
}
class DLinkedNode {
constructor(key = 0, value = 0) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
基于分治法实现快速排序。
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] <= pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
// 方法一:ES6 Set
function unique(arr) {
return [...new Set(arr)];
}
// 方法二:双重循环
function unique(arr) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (result.indexOf(arr[i]) === -1) {
result.push(arr[i]);
}
}
return result;
}
// 方法三:filter + indexOf
function unique(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
function jsonStringify(obj) {
if (typeof obj === 'undefined' || typeof obj === 'function') {
return undefined;
}
if (obj === null) {
return 'null';
}
if (typeof obj === 'string') {
return `"${obj}"`;
}
if (typeof obj === 'number' || typeof obj === 'boolean') {
return String(obj);
}
if (obj instanceof Date) {
return `"${obj.toISOString()}"`;
}
if (Array.isArray(obj)) {
const elements = obj.map(item => {
const value = jsonStringify(item);
return value === undefined ? 'null' : value;
});
return `[${elements.join(',')}]`;
}
if (typeof obj === 'object') {
const keys = Object.keys(obj);
const properties = keys.map(key => {
const value = jsonStringify(obj[key]);
if (value !== undefined) {
return `"${key}":${value}`;
}
return null;
}).filter(Boolean);
return `{${properties.join(',')}}`;
}
return String(obj);
}
基于Generator和Promise实现async/await。
function run(gen) {
const iterator = gen();
function iterate(iteration) {
if (iteration.done) return iteration.value;
const promise = iteration.value;
promise.then(result => {
iterate(iterator.next(result));
}).catch(error => {
iterate(iterator.throw(error));
});
}
return iterate(iterator.next());
}
// 使用示例
function fetchData() {
return new Promise(resolve => setTimeout(() => resolve('data'), 1000));
}
const asyncFunction = run(function* () {
try {
const data = yield fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
});
手撕代码题目通常考察基础编程能力、算法思维和对前端框架原理的理解。建议候选人: