面试常考的高频手写js题汇总
2025.09.19 12:47浏览量:0简介:本文汇总了前端面试中高频出现的手写JavaScript题目,涵盖基础类型判断、深拷贝、事件总线、防抖节流等核心场景,提供解题思路与代码实现,助力开发者系统掌握面试必备技能。
面试常考的高频手写JS题汇总
在前端技术面试中,手写JavaScript代码是考察开发者基础能力的重要环节。这类题目不仅检验对语言特性的理解,更能体现问题拆解与工程化思维。本文系统梳理了8类高频手写题,结合实际场景与边界条件分析,帮助开发者构建完整的解题框架。
一、基础类型判断
1.1 类型检测的终极方案
传统typeof
存在局限性(如typeof null === 'object'
),而instanceof
无法检测原始类型。更可靠的方案是结合Object.prototype.toString
:
function getType(target) {
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
}
// 测试用例
console.log(getType([])); // 'array'
console.log(getType(null)); // 'null'
该方法通过调用对象的toString
方法获取内部[[Class]]
属性,覆盖所有JS类型。
1.2 数组判断的优化实现
判断变量是否为数组时,需考虑跨框架环境:
function isArray(target) {
// 优先使用ES5标准方法
if (Array.isArray) return Array.isArray(target);
// 兼容性回退
return Object.prototype.toString.call(target) === '[object Array]';
}
现代工程中建议直接使用Array.isArray()
,此实现展示了渐进增强思想。
二、深拷贝实现
2.1 基础版递归实现
function deepClone(obj, hash = new WeakMap()) {
// 处理基本类型和函数
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
// 处理Date和RegExp
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 创建对应类型的实例
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
// 递归拷贝属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
// 处理Symbol属性
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (let key of symbolKeys) {
cloneObj[key] = deepClone(obj[key], hash);
}
return cloneObj;
}
该实现完整处理了:
- 原始类型直接返回
- 循环引用检测(使用WeakMap避免内存泄漏)
- 特殊对象(Date/RegExp)
- 继承属性过滤(hasOwnProperty)
- Symbol属性拷贝
2.2 性能优化方案
对于大型对象,可采用迭代+栈的方式避免递归爆栈:
function deepCloneIterative(obj) {
const stack = [{ source: obj, target: {} }];
const map = new WeakMap();
while (stack.length) {
const { source, target } = stack.pop();
if (map.has(source)) {
continue;
}
map.set(source, target);
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null) {
stack.push({
source: source[key],
target: Array.isArray(source[key]) ? [] : {}
});
} else {
target[key] = source[key];
}
}
}
}
return target;
}
三、事件总线实现
3.1 发布-订阅模式
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => {
callback(...args);
});
}
}
off(event, callback) {
if (!this.events[event]) return;
if (callback) {
this.events[event] = this.events[event].filter(
cb => cb !== callback
);
} else {
delete this.events[event];
}
}
once(event, callback) {
const onceCallback = (...args) => {
callback(...args);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
}
}
关键设计点:
- 使用对象存储事件队列
- 支持链式调用(可扩展)
once
方法的实现技巧- 参数解构传递
四、防抖与节流
4.1 防抖(debounce)
function debounce(fn, delay, immediate = false) {
let timer = null;
return function(...args) {
const context = this;
if (immediate && !timer) {
fn.apply(context, args);
}
clearTimeout(timer);
timer = setTimeout(() => {
if (!immediate) {
fn.apply(context, args);
}
timer = null;
}, delay);
};
}
参数说明:
immediate
控制是否立即执行首次调用- 使用闭包保存timer引用
- 箭头函数保持this指向
4.2 节流(throttle)
function throttle(fn, delay) {
let lastTime = 0;
let timer = null;
return function(...args) {
const context = this;
const now = Date.now();
const remaining = delay - (now - lastTime);
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = now;
fn.apply(context, args);
} else if (!timer) {
timer = setTimeout(() => {
lastTime = Date.now();
timer = null;
fn.apply(context, args);
}, remaining);
}
};
}
时间戳+定时器组合实现:
- 首次立即执行
- 末次延迟执行
- 动态计算剩余时间
五、Promise实现
5.1 基础版Promise
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
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 (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
实现要点:
- 三种状态管理
- 异步执行回调(使用setTimeout)
- 链式调用处理
- 值穿透解析
- 循环引用检测
六、柯里化实现
6.1 通用柯里化函数
function curry(fn, args = []) {
return function(...newArgs) {
const allArgs = [...args, ...newArgs];
if (allArgs.length >= fn.length) {
return fn.apply(this, allArgs);
} else {
return curry.call(this, fn, allArgs);
}
};
}
// 使用示例
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
关键特性:
- 参数收集与阈值判断
- 递归调用保持this指向
- 支持分步传参
七、函数组合
7.1 compose函数实现
function compose(...funcs) {
if (funcs.length === 0) return arg => arg;
if (funcs.length === 1) return funcs[0];
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
// 使用示例
const add5 = x => x + 5;
const multiply3 = x => x * 3;
const divide2 = x => x / 2;
const operation = compose(divide2, multiply3, add5);
console.log(operation(10)); // ((10+5)*3)/2 = 22.5
实现要点:
- 边界条件处理
- 使用reduce实现从右向左的组合
- 支持多参数传递(通过…args)
八、手写AJAX
8.1 原生XMLHttpRequest实现
function ajax(options) {
const { url, method = 'GET', data = null, async = true, headers = {} } = options;
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, async);
// 设置请求头
Object.keys(headers).forEach(key => {
xhr.setRequestHeader(key, headers[key]);
});
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
status: xhr.status,
data: xhr.responseText,
headers: xhr.getAllResponseHeaders()
});
} else {
reject({
status: xhr.status,
error: xhr.statusText
});
}
};
xhr.onerror = function() {
reject({
error: 'Network Error'
});
};
// 处理请求体
if (data) {
if (typeof data === 'object') {
data = JSON.stringify(data);
if (!headers['Content-Type']) {
xhr.setRequestHeader('Content-Type', 'application/json');
}
}
xhr.send(data);
} else {
xhr.send();
}
});
}
关键实现:
- Promise封装
- 请求头动态设置
- 状态码范围判断
- JSON数据序列化
- 错误处理机制
面试应对策略
- 代码规范:保持一致的缩进和命名风格
- 边界处理:主动考虑null/undefined等特殊情况
- 性能优化:在实现中体现对性能的考虑(如循环引用检测)
- 沟通技巧:
- 先实现基础功能,再逐步优化
- 对不确定的部分明确说明假设条件
- 主动讨论可能的扩展场景
总结
高频手写题考察的是开发者对语言特性的深度理解和工程化思维。建议通过以下方式提升:
- 建立类型判断、异步处理等核心模块的模板库
- 针对每个实现编写完整的测试用例
- 理解底层原理(如事件循环、原型链)
- 关注ES6+新特性(如Proxy、Reflect)的应用
掌握这些核心题目后,建议进一步研究源码级实现(如lodash的深拷贝方案),这将在高级面试中形成差异化优势。
发表评论
登录后可评论,请前往 登录 或 注册