本文档整理了前端面试中常见的手写代码题目,包含完整的实现代码、详细说明和测试用例。
数组扁平化
将多维数组转换为一维数组。
方法一:外部函数+内部递归辅助函数
function flattenArray(arr) {
const result = [];
function flatten(element) {
if (!Array.isArray(element)) {
result.push(element);
} else {
for (let i = 0; i < element.length; i++) {
flatten(element[i]);
}
}
}
flatten(arr);
return result;
}
优点:性能较好,只创建一个结果数组 缺点:代码稍长,使用了嵌套函数
方法二:使用reduce和递归
function flattenArrayES6(arr) {
return arr.reduce((acc, curr) => {
return acc.concat(Array.isArray(curr) ? flattenArrayES6(curr) : curr);
}, []);
}
优点:代码简洁,符合函数式编程风格 缺点:每次递归都会创建新数组,性能较差
方法三:直接递归+concat
function flatten(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result.push(item);
}
}
return result;
}
优点:实现直观,逻辑清晰 缺点:每次递归调用concat都会创建新数组
测试用例
const nestedArray = [1, [2, [3, 4, 5]], 6, [7]];
console.log('原始数组:', nestedArray);
console.log('方法1结果:', flattenArray(nestedArray));
console.log('方法2结果:', flattenArrayES6(nestedArray));
console.log('方法3结果:', flatten(nestedArray));
性能对比:方法1 > 方法3 > 方法2 代码简洁度:方法2 > 方法3 > 方法1
函数节流和防抖
函数节流 (Throttle)
在指定时间内,无论调用多少次,函数最多执行一次。
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
应用场景:滚动事件、窗口调整、鼠标移动等高频触发的事件
函数防抖 (Debounce)
在连续多次调用后,只执行最后一次调用或第一次调用。
function debounce(fn, delay, immediate = false) {
let timeout = null;
return function(...args) {
const context = this;
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, delay);
if (callNow) fn.apply(context, args);
} else {
timeout = setTimeout(() => {
fn.apply(context, args);
}, delay);
}
};
}
应用场景:搜索输入、表单验证、窗口调整完成后执行操作
加强版节流
结合节流和防抖的特性,既保证立即响应,又限制执行频率。
function throttleEnhanced(fn, delay) {
let lastTime = 0;
let timeout = null;
return function(...args) {
const now = Date.now();
const remaining = delay - (now - lastTime);
const context = this;
if (timeout) clearTimeout(timeout);
if (remaining <= 0) {
fn.apply(context, args);
lastTime = now;
} else {
timeout = setTimeout(() => {
fn.apply(context, args);
lastTime = Date.now();
}, remaining);
}
};
}
测试用例
function testFunction(type) {
console.log(`${type} 函数执行了,时间: ${new Date().toLocaleTimeString()}`);
}
// 节流测试
const throttledFn = throttle(() => testFunction('节流'), 1000);
// 防抖测试(非立即执行)
const debouncedFn = debounce(() => testFunction('防抖(非立即)'), 1000);
// 防抖测试(立即执行)
const debouncedImmediateFn = debounce(() => testFunction('防抖(立即)'), 1000, true);
// 加强版节流测试
const enhancedThrottledFn = throttleEnhanced(() => testFunction('加强版节流'), 1000);
手写Promise
符合Promise/A+规范的简化版本实现。Promise是一种异步编程解决方案,用于处理异步操作的结果。
基础实现
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
return this.then(
value => MyPromise.resolve(onFinally()).then(() => value),
reason => MyPromise.resolve(onFinally()).then(() => { throw reason; })
);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
静态方法
Promise的静态方法用于处理多个Promise实例,提供不同的组合策略。
// 静态resolve方法 - 创建一个已解析的Promise
static resolve(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise((resolve, reject) => {
resolve(value);
});
}
// 静态reject方法 - 创建一个已拒绝的Promise
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
// 静态all方法 - 所有Promise都成功时成功,任何一个失败时立即失败
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
if (promises.length === 0) {
resolve(results);
return;
}
for (let i = 0; i < promises.length; i++) {
MyPromise.resolve(promises[i]).then(
value => {
results[i] = value;
count++;
if (count === promises.length) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
}
});
}
// 静态race方法 - 返回第一个完成的Promise的结果
static race(promises) {
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
MyPromise.resolve(promises[i]).then(
value => resolve(value),
reason => reject(reason)
);
}
});
}
// 静态allSettled方法(ES2020) - 等待所有Promise完成,无论成功或失败
static allSettled(promises) {
return new MyPromise((resolve) => {
const results = [];
let count = 0;
if (promises.length === 0) {
resolve(results);
return;
}
for (let i = 0; i < promises.length; i++) {
MyPromise.resolve(promises[i]).then(
value => {
results[i] = { status: FULFILLED, value };
count++;
if (count === promises.length) {
resolve(results);
}
},
reason => {
results[i] = { status: REJECTED, reason };
count++;
if (count === promises.length) {
resolve(results);
}
}
);
}
});
}
// 静态any方法(ES2021) - 只要有一个Promise成功就成功,所有都失败才失败
static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let count = 0;
if (promises.length === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}
for (let i = 0; i < promises.length; i++) {
MyPromise.resolve(promises[i]).then(
value => {
resolve(value);
},
reason => {
errors[i] = reason;
count++;
if (count === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
}
});
}
静态方法对比
| 方法 | 成功条件 | 失败条件 | 返回值 |
|---|---|---|---|
Promise.all() | 所有Promise都成功 | 任何一个Promise失败 | 成功值的数组 |
Promise.race() | 第一个完成的Promise | 第一个完成的Promise | 第一个完成的结果 |
Promise.allSettled() | 所有Promise都完成 | 不会失败 | 包含所有结果的数组 |
Promise.any() | 至少一个Promise成功 | 所有Promise都失败 | 第一个成功的结果 |
Promise.resolve() | 总是成功 | 不会失败 | 包装后的值 |
Promise.reject() | 不会成功 | 总是失败 | 包装后的错误 |
DOM渲染
将虚拟DOM渲染为真实DOM。
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === "number") {
vnode = String(vnode);
}
// 字符串类型直接就是文本节点
if (typeof vnode === "string") {
try {
const textNode = document.createTextNode(vnode);
return textNode;
} catch (e) {
console.warn('DOM渲染函数需要在浏览器环境下运行');
return { type: 'text', content: vnode };
}
}
// 普通DOM元素
try {
const element = document.createElement(vnode.tag);
// 添加属性
if (vnode.attrs) {
for (let key in vnode.attrs) {
const value = vnode.attrs[key];
element.setAttribute(key, value);
}
}
// 处理子节点
if (vnode.children && vnode.children.length) {
vnode.children.forEach(child => {
const childElement = _render(child);
element.appendChild(childElement);
});
}
return element;
} catch (e) {
console.warn('DOM渲染函数需要在浏览器环境下运行');
return {
type: 'element',
tag: vnode.tag,
attrs: vnode.attrs,
children: vnode.children ? vnode.children.map(child => _render(child)) : []
};
}
}
测试用例
const vDom = {
tag: 'DIV',
attrs: {
id: 'app'
},
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
};
// 在浏览器环境下测试
const result = _render(vDom);
console.log('渲染结果:', result);
树结构转换
树转数组
function treeToArray(tree) {
let result = [];
let queue = [];
result.push({ id: tree.id, name: tree.name, parentId: 0 });
queue.unshift({ id: tree.id, name: tree.name, children: tree.children });
while (queue.length > 0) {
const currentNode = queue.shift();
if (currentNode.children && currentNode.children.length > 0) {
for (let i = 0; i < currentNode.children.length; i++) {
const childNode = currentNode.children[i];
const { id, name } = childNode;
queue.unshift(childNode);
result.push({
id,
name,
parentId: currentNode.id
});
}
}
}
return result;
}
数组转树
function arrayToTree(arr) {
let map = new Map();
let result = null;
for (let i = 0; i < arr.length; i++) {
const { id, name } = arr[i];
const children = [];
map.set(id, { id, name, children });
}
for (let i = 0; i < arr.length; i++) {
const { parentId, id } = arr[i];
if (parentId !== 0) {
const parentNode = map.get(parentId);
parentNode.children.push(map.get(id));
} else {
result = map.get(id);
}
}
return result;
}
测试用例
const testTree = {
id: 1,
name: "部门A",
children: [
{
id: 2,
name: "部门B",
children: [
{
id: 4,
name: "部门D",
children: [
{ id: 7, name: "部门G", children: [] },
{ id: 8, name: "部门H", children: [] }
]
},
{ id: 5, name: "部门E", children: [] }
]
},
{
id: 3,
name: "部门C",
children: [
{ id: 6, name: "部门F", children: [] }
]
}
]
};
const testArray = [
{ id: 1, name: '部门A', parentId: 0 },
{ id: 2, name: '部门B', parentId: 1 },
{ id: 3, name: '部门C', parentId: 1 },
{ id: 4, name: '部门D', parentId: 2 },
{ id: 5, name: '部门E', parentId: 2 },
{ id: 6, name: '部门F', parentId: 3 },
{ id: 7, name: '部门G', parentId: 4 },
{ id: 8, name: '部门H', parentId: 4 }
];
console.log('树转数组结果:', treeToArray(testTree));
console.log('数组转树结果:', arrayToTree(testArray));
版本比较
function compareVersion(arr) {
let copyArr = [...arr];
copyArr.sort((a, b) => {
const tempA = a.split('.');
const tempB = b.split('.');
const maxLen = Math.max(tempA.length, tempB.length);
for (let i = 0; i < maxLen; i++) {
const targetA = +tempA[i] || 0;
const targetB = +tempB[i] || 0;
if (targetA === targetB) continue;
return targetA - targetB;
}
return 0;
});
return copyArr;
}
// 测试
const versions = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'];
console.log('版本比较结果:', compareVersion(versions));
深拷贝
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj);
const cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
函数柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用示例
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6