10个前端手写题精讲:上篇深度解析

作者:4042025.11.13 14:26浏览量:0

简介:前端面试中手写题是考察基础能力的重要环节,本文精选10个高频手写题,分上下篇详细解析实现逻辑与核心要点,助你攻克技术面试难关。

一、前言:手写题考察的核心价值

在前端技术面试中,手写题始终占据重要地位。这类题目不仅能检验开发者对语言特性的理解深度,更能体现其编码规范、边界处理能力和问题拆解思维。本文精选的10个常考题目覆盖数据结构、算法、设计模式等核心领域,每个题目均包含题目解析、实现思路、代码示例、易错点四个维度,帮助读者系统掌握解题方法。

二、基础类型处理:类型判断的终极方案

题目1:实现一个精准的类型判断函数

需求:编写getType函数,返回值的精确类型(如[object Array]而非"object")。

  1. function getType(value) {
  2. return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
  3. }
  4. // 测试用例
  5. getType([]); // "array"
  6. getType(/regex/); // "regexp"

关键点

  1. 使用Object.prototype.toString而非typeof,解决typeof null === "object"的缺陷
  2. 通过slice(8, -1)提取类型字符串(如从"[object Array]"提取"Array"
  3. 兼容所有原始类型和引用类型

题目2:深拷贝实现

需求:实现支持循环引用的深拷贝函数。

  1. function deepClone(obj, hash = new WeakMap()) {
  2. if (obj === null || typeof obj !== 'object') return obj;
  3. if (hash.has(obj)) return hash.get(obj); // 处理循环引用
  4. const cloneObj = Array.isArray(obj) ? [] : {};
  5. hash.set(obj, cloneObj);
  6. for (let key in obj) {
  7. if (obj.hasOwnProperty(key)) {
  8. cloneObj[key] = deepClone(obj[key], hash);
  9. }
  10. }
  11. return cloneObj;
  12. }

优化点

  • 使用WeakMap存储已拷贝对象,避免循环引用导致的堆栈溢出
  • 通过hasOwnProperty过滤原型链属性
  • 区分数组和普通对象的初始化方式

三、函数式编程:高阶函数实现

题目3:柯里化函数实现

需求:实现通用柯里化函数,支持多参数分步调用。

  1. function curry(fn) {
  2. return function curried(...args) {
  3. if (args.length >= fn.length) {
  4. return fn.apply(this, args);
  5. } else {
  6. return function(...args2) {
  7. return curried.apply(this, args.concat(args2));
  8. }
  9. }
  10. }
  11. }
  12. // 使用示例
  13. const sum = curry((a, b, c) => a + b + c);
  14. sum(1)(2)(3); // 6

实现原理

  1. 通过fn.length判断参数是否收集完毕
  2. 递归调用curried函数积累参数
  3. 保持this指向的正确性

题目4:函数组合实现

需求:实现compose函数,从右向左执行多个函数。

  1. function compose(...fns) {
  2. return function(value) {
  3. return fns.reduceRight((acc, fn) => fn(acc), value);
  4. }
  5. }
  6. // 使用示例
  7. const add1 = x => x + 1;
  8. const mul2 = x => x * 2;
  9. compose(mul2, add1)(5); // (5+1)*2 = 12

应用场景

  • 中间件管道处理(如Redux)
  • 数据流转换链
  • 避免嵌套函数调用

四、异步编程:Promise与事件循环

题目5:实现Promise.all

需求:模拟实现Promise.all方法,处理异步数组。

  1. function promiseAll(promises) {
  2. return new Promise((resolve, reject) => {
  3. const results = [];
  4. let count = 0;
  5. promises.forEach((promise, index) => {
  6. Promise.resolve(promise)
  7. .then(value => {
  8. results[index] = value;
  9. count++;
  10. if (count === promises.length) resolve(results);
  11. })
  12. .catch(err => reject(err));
  13. });
  14. });
  15. }

边界处理

  • 空数组直接resolve
  • 非Promise值自动包装
  • 捕获单个Promise的reject

题目6:实现带并发的请求限制器

需求:实现limitRequest函数,控制最大并发数。

  1. function limitRequest(urls, limit = 5) {
  2. return new Promise(resolve => {
  3. const results = [];
  4. let current = 0;
  5. function execute() {
  6. if (current >= urls.length) {
  7. resolve(results);
  8. return;
  9. }
  10. const index = current++;
  11. fetch(urls[index])
  12. .then(res => results[index] = res)
  13. .finally(() => execute()); // 完成一个后立即启动下一个
  14. }
  15. // 启动初始并发
  16. for (let i = 0; i < limit; i++) {
  17. execute();
  18. }
  19. });
  20. }

优化方向

  • 使用队列管理待执行任务
  • 添加超时控制机制
  • 支持动态调整并发数

五、DOM操作:虚拟DOM与事件委托

题目7:简易虚拟DOM实现

需求:实现createElementrender函数,构建虚拟DOM树。

  1. function createElement(type, props, ...children) {
  2. return {
  3. type,
  4. props: {
  5. ...props,
  6. children: children.map(child =>
  7. typeof child === 'object' ? child : createTextElement(child)
  8. )
  9. }
  10. };
  11. }
  12. function createTextElement(text) {
  13. return { type: 'TEXT', props: { nodeValue: text } };
  14. }
  15. function render(element, container) {
  16. const dom = element.type === 'TEXT'
  17. ? document.createTextNode('')
  18. : document.createElement(element.type);
  19. Object.keys(element.props)
  20. .filter(key => key !== 'children')
  21. .forEach(name => dom[name] = element.props[name]);
  22. element.props.children.forEach(child =>
  23. render(child, dom)
  24. );
  25. container.appendChild(dom);
  26. }

核心概念

  • 虚拟DOM树的递归渲染
  • 文本节点的特殊处理
  • 属性设置的过滤机制

题目8:事件委托实现

需求:实现支持动态元素的事件委托。

  1. function delegateEvent(parent, eventType, selector, handler) {
  2. parent.addEventListener(eventType, e => {
  3. let target = e.target;
  4. while (target !== parent) {
  5. if (target.matches(selector)) {
  6. handler.call(target, e);
  7. return;
  8. }
  9. target = target.parentNode;
  10. }
  11. });
  12. }
  13. // 使用示例
  14. delegateEvent(
  15. document.getElementById('list'),
  16. 'click',
  17. '.item',
  18. function(e) { console.log(this.textContent); }
  19. );

优势分析

  • 减少事件监听器数量
  • 动态添加元素自动生效
  • 事件冒泡机制的高效利用

六、设计模式:单例与发布订阅

题目9:单例模式实现

需求:实现线程安全的单例模式。

  1. const Singleton = (function() {
  2. let instance;
  3. function createInstance() {
  4. const object = new Object('Singleton');
  5. // 初始化代码...
  6. return object;
  7. }
  8. return {
  9. getInstance: function() {
  10. if (!instance) {
  11. instance = createInstance();
  12. }
  13. return instance;
  14. }
  15. };
  16. })();

应用场景

题目10:发布订阅模式实现

需求:实现带事件名的发布订阅系统。

  1. class EventEmitter {
  2. constructor() {
  3. this.events = {};
  4. }
  5. on(eventName, callback) {
  6. if (!this.events[eventName]) {
  7. this.events[eventName] = [];
  8. }
  9. this.events[eventName].push(callback);
  10. }
  11. emit(eventName, ...args) {
  12. const callbacks = this.events[eventName] || [];
  13. callbacks.forEach(cb => cb(...args));
  14. }
  15. off(eventName, callback) {
  16. const callbacks = this.events[eventName];
  17. if (callbacks) {
  18. this.events[eventName] = callbacks.filter(
  19. cb => cb !== callback
  20. );
  21. }
  22. }
  23. }

扩展功能

  • 添加一次性监听
  • 支持通配符事件
  • 添加错误处理机制

七、总结与学习建议

掌握这10个手写题需要:

  1. 基础巩固:每日练习1-2个题目,理解底层原理
  2. 代码规范:注意变量命名、错误处理、边界条件
  3. 性能优化:分析时间复杂度和空间复杂度
  4. 场景联想:思考每个题目在实际项目中的应用

建议读者结合《JavaScript高级程序设计》和《你不知道的JavaScript》进行系统学习,同时通过LeetCode前端专题和开源项目贡献提升实战能力。下篇将解析剩余5个进阶题目,包括手写React Hooks、Webpack插件开发等高级内容。