面试常考的高频手写js题汇总
2025.09.19 12:47浏览量:15简介:本文汇总了前端面试中高频出现的手写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和RegExpif (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)); // 6console.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的深拷贝方案),这将在高级面试中形成差异化优势。

发表评论
登录后可评论,请前往 登录 或 注册