JavaScript手写题终极清单(含高频考题+参考实现+评分标准)

第一章:JavaScript手写题终极清单(含高频考题+参考实现+评分标准)

JavaScript 手写题是前端面试中的核心考察环节,重点评估候选人对语言机制的理解深度与编码规范。掌握常见题目及其高质量实现,有助于在技术评审中脱颖而出。

实现一个防抖函数 debounce

防抖广泛应用于搜索框、窗口 resize 等高频触发场景,确保函数在连续调用中仅执行最后一次。
function debounce(fn, delay) {
  let timer = null; // 闭包保存定时器
  return function (...args) {
    const context = this; // 保持 this 指向
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args); // 延迟执行原函数
    }, delay);
  };
}
使用方式:const debouncedFn = debounce(myFunction, 300);,每次调用将重置计时。

实现一个节流函数 throttle

节流控制函数在指定时间间隔内最多执行一次,适用于滚动事件等性能敏感场景。
function throttle(fn, delay) {
  let lastExecTime = 0;
  return function (...args) {
    const context = this;
    const now = Date.now();
    if (now - lastExecTime > delay) {
      fn.apply(context, args);
      lastExecTime = now;
    }
  };
}

常见手写题分类与评分标准

  • 代码正确性:逻辑无误,边界处理完整(如 null、异步)
  • this 指向处理:正确使用 apply/call 绑定上下文
  • 闭包与内存:合理利用闭包,避免内存泄漏
  • 扩展能力:支持立即执行、取消功能等加分项
题目类型出现频率建议掌握度
防抖/节流高频熟练手写 + 场景说明
深拷贝高频处理循环引用、函数、Symbol
Promisify中频理解回调转 Promise 原理

第二章:基础语法与核心概念手写实现

2.1 数据类型判断与深浅拷贝实现

在JavaScript中,准确判断数据类型是实现拷贝操作的前提。常用方法包括 typeofinstanceofObject.prototype.toString.call(),其中后者能精确识别内置对象类型。
常见数据类型判断对比
typeoftoString结果
[]"object""[object Array]"
{}"object""[object Object]"
null"object""[object Null]"
浅拷贝与深拷贝实现
浅拷贝仅复制对象第一层属性,深层仍引用原值:
const shallowCopy = obj => Object.assign({}, obj);
// 或使用扩展运算符
const copy = { ...obj };
该方法适用于无嵌套引用的简单对象。 深拷贝需递归复制所有层级,避免引用共享:
function deepClone(obj, seen = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (seen.has(obj)) return seen.get(obj); // 防止循环引用
  const clone = Array.isArray(obj) ? [] : {};
  seen.set(obj, clone);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], seen);
    }
  }
  return clone;
}
该实现通过 WeakMap 跟踪已访问对象,确保复杂结构(如循环引用)也能安全复制。

2.2 函数柯里化与参数重载编码实践

函数柯里化的基本实现
柯里化是将多参数函数转换为一系列单参数函数的技术,提升函数的复用性与组合能力。

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function (...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
上述代码中,curry 函数通过判断已传参数数量与目标函数期望参数数量(fn.length)的关系,决定是否继续返回新函数。当参数足够时执行原函数。
参数重载的模拟策略
JavaScript 不支持传统重载,可通过参数类型判断实现逻辑分支:
  • 检查 arguments 长度或参数类型
  • 使用默认值与解构赋值增强灵活性
  • 结合柯里化实现“伪重载”效果

2.3 手写call、apply、bind方法并分析差异

手写call方法
Function.prototype.myCall = function(context, ...args) {
  context = context || window;
  const fnSymbol = Symbol('fn');
  context[fnSymbol] = this;
  const result = context[fnSymbol](...args);
  delete context[fnSymbol];
  return result;
};
通过将函数作为上下文对象的临时方法调用,实现this绑定。使用Symbol避免属性冲突,执行后立即删除。
手写apply方法
Function.prototype.myApply = function(context, argsArray) {
  context = context || window;
  const fnSymbol = Symbol('fn');
  context[fnSymbol] = this;
  let result;
  if (argsArray) {
    result = context[fnSymbol](...argsArray);
  } else {
    result = context[fnSymbol]();
  }
  delete context[fnSymbol];
  return result;
};
与call类似,区别在于第二个参数为数组形式传参。
手写bind方法
Function.prototype.myBind = function(context, ...bindArgs) {
  const fn = this;
  const boundFn = function(...callArgs) {
    return fn.apply(
      this instanceof boundFn ? this : context,
      bindArgs.concat(callArgs)
    );
  };
  boundFn.prototype = Object.create(this.prototype);
  return boundFn;
};
bind返回一个绑定this和预置参数的新函数,并支持new调用时的原型继承。
三者核心差异对比
方法立即执行参数形式可否延迟调用
call逐个参数
apply参数数组
bind可预设参数

2.4 实现new操作符与instanceof原理

模拟实现 new 操作符

new 操作符用于创建一个用户自定义类型的实例。其核心逻辑可分解为四步:

  1. 创建一个新对象;
  2. 将构造函数的原型赋给新对象的隐式原型;
  3. 执行构造函数,this 指向新对象;
  4. 若构造函数返回对象,则返回该对象,否则返回新对象。
function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype);
  const result = Constructor.apply(obj, args);
  return result instanceof Object ? result : obj;
}

上述代码中,Object.create 绑定原型链,apply 执行构造函数并绑定 this,最后判断返回值类型。

instanceof 的底层机制

instanceof 通过原型链向上查找,判断构造函数的 prototype 是否出现在对象的原型链中。

function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left);
  while (proto) {
    if (proto === right.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

该实现模拟了原型链遍历过程,确保类型判断准确可靠。

2.5 模拟实现JSON.stringify与JSON.parse

简易版JSON.stringify实现
function jsonStringify(obj) {
  if (obj === null) return "null";
  if (typeof obj === "string") return `"${obj}"`;
  if (typeof obj !== "object") return String(obj);
  if (Array.isArray(obj)) {
    return `[${obj.map(jsonStringify).join(",")}]`;
  }
  const keys = Object.keys(obj);
  const pairs = keys.map(key => `"${key}":${jsonStringify(obj[key])}`);
  return `{${pairs.join(",")}}`;
}
该函数递归处理基本类型、数组和对象,字符串添加引号,对象键名强制转为字符串。
简易版JSON.parse实现
  • 利用JavaScript引擎的原生能力:通过new Function构造函数解析字符串
  • 安全性较低,仅用于理解原理
function jsonParse(str) {
  return new Function(`return ${str}`)();
}
该实现将JSON字符串包裹在return语句中,通过动态函数执行返回对应JS值。

第三章:异步编程与事件循环高频题解析

3.1 手写Promise核心功能(then、catch、finally)

实现一个简易Promise,需定义三种状态:PENDINGFULFILLEDREJECTED,并通过回调队列处理异步逻辑。
核心结构设计
class MyPromise {
  constructor(executor) {
    this.status = 'PENDING';
    this.value = null;
    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.value = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}
上述代码中,executor 立即执行,resolvereject 控制状态迁移,确保状态不可逆。
链式调用支持
then 方法返回新Promise,实现链式调用。支持异步回调注册与值穿透,是Promise核心机制之一。

3.2 实现Promise.all与Promise.race容错机制

在并发控制中,Promise.allPromise.race 是常用工具,但默认行为不具备容错性。为增强稳定性,需手动实现错误处理机制。
Promise.all 容错封装
Promise.allSettled = function(promises) {
  return Promise.all(promises.map(p => 
    p.then(value => ({ status: 'fulfilled', value }))
      .catch(reason => ({ status: 'rejected', reason }))
  ));
};
该实现将所有 Promise 的结果统一包装为 {status, value/reason} 格式,确保即使部分失败也不会中断整体执行。
Promise.race 超时控制
  • 通过包装超时逻辑,防止长期挂起
  • 利用 Promise.race 实现优先返回最快结果
const withTimeout = (promise, ms) => {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
};
此模式广泛应用于网络请求超时控制,提升系统响应确定性。

3.3 基于发布订阅模式实现EventEmitter

在事件驱动架构中,发布订阅模式是核心通信机制。通过构建一个轻量级的 EventEmitter 类,可以实现对象间解耦的事件通知。
核心API设计
主要包含三个方法:`on` 订阅事件、`emit` 发布事件、`off` 取消订阅。

class EventEmitter {
  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(cb => cb(...args));
    }
  }

  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
}
上述代码中,`events` 对象以事件名为键存储回调数组;`on` 添加监听器,`emit` 触发对应事件的所有回调,`off` 移除指定监听函数,确保内存可回收。
应用场景
  • 组件间通信(如UI组件与状态管理)
  • 异步任务结果通知
  • 日志系统事件广播

第四章:设计模式与DOM操作类手写题

4.1 单例模式与观察者模式的JavaScript实现

在前端架构设计中,单例模式确保一个类仅有一个实例,并提供全局访问点。适用于状态管理、配置中心等场景。
单例模式实现
class Singleton {
    constructor() {
        if (Singleton.instance) return Singleton.instance;
        Singleton.instance = this;
        this.data = 'shared state';
    }
}
// 使用:const instance1 = new Singleton(); const instance2 = new Singleton();
上述代码通过静态属性跟踪实例状态,若已存在则返回原实例,保证唯一性。
观察者模式实现
该模式建立一对多依赖关系,当主体状态变化时,所有观察者自动更新。
  • Subject(主题):维护观察者列表,提供订阅与通知接口
  • Observer(观察者):实现更新方法,响应主题通知
class Subject {
    constructor() {
        this.observers = [];
    }
    subscribe(observer) {
        this.observers.push(observer);
    }
    notify(data) {
        this.observers.forEach(observer => observer.update(data));
    }
}
调用 notify 时遍历执行每个观察者的 update 方法,实现松耦合通信机制。

4.2 防抖与节流函数的高级写法与应用场景

防抖函数的高级实现
防抖(Debounce)确保在事件频繁触发时,只执行最后一次操作。适用于搜索框输入、窗口调整等场景。
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func.apply(this, args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}
该实现利用闭包保存定时器,每次调用重置延迟。参数 `func` 为原函数,`wait` 为延迟毫秒数。
节流函数的时间戳控制
节流(Throttle)保证函数在指定时间间隔内最多执行一次,常用于滚动监听。
  • 使用时间戳方式判断是否达到执行周期
  • 相比定时器方案,响应更及时

4.3 手写模板引擎与虚拟DOM生成逻辑

实现一个轻量级模板引擎,核心是将字符串模板解析为抽象语法树(AST),再转换为虚拟DOM节点。首先通过正则匹配插值表达式 {{ }} 和指令标签,构建树形结构。
模板解析流程
  • 词法分析:将模板字符串拆分为标记流(tokens)
  • 语法分析:根据嵌套关系生成AST节点
  • 代码生成:遍历AST输出渲染函数
function parse(template) {
  const tokens = template.split(/({{.*?}})/); // 分割文本与表达式
  return tokens.map(token => {
    if (token.startsWith('{{') && token.endsWith('}}')) {
      return { type: 'expression', value: token.slice(2, -2).trim() };
    }
    return { type: 'text', value: token };
  });
}
上述代码实现基础词法解析,将模板分解为文本和表达式节点。每种节点类型后续可映射到对应的虚拟DOM结构,为动态更新提供依据。
虚拟DOM构造
通过JavaScript对象描述真实DOM结构,提升更新性能。
属性说明
tag元素标签名
props属性键值对
children子虚拟节点数组

4.4 实现一个简易版MVVM双向绑定

核心原理概述
MVVM的双向绑定依赖于数据劫持与观察者模式。通过Object.defineProperty监听数据变化,并在视图层输入时同步更新模型。
代码实现
function observe(data) {
  if (typeof data !== 'object') return;
  Object.keys(data).forEach(key => {
    let value = data[key];
    const dep = [];
    observe(value); // 深度监听
    Object.defineProperty(data, key, {
      get() { return value; },
      set(newVal) {
        value = newVal;
        dep.forEach(fn => fn(newVal));
      }
    });
  });
}
上述代码递归劫持对象属性,每个属性维护一个依赖收集数组dep,当数据变更时通知所有订阅者。
视图绑定示例
  • 初始化时将数据渲染到input元素
  • 绑定input事件,实时更新JS对象
  • 触发setter,通知DOM重新渲染

第五章:总结与面试应对策略

掌握核心知识点的串联能力
在准备分布式系统相关面试时,候选人需具备将CAP理论、一致性算法与实际架构设计结合的能力。例如,在设计一个高可用订单系统时,可基于最终一致性模型选择Kafka进行异步数据同步。
高频问题实战解析
  • 如何在Paxos和Raft之间做技术选型?关键在于运维复杂度与日志可读性。
  • 面对网络分区,AP系统如何保障数据不丢失?可通过本地队列+重试机制实现。
  • ZooKeeper是否适合用作服务配置中心?以下是典型使用场景示例:

// 使用etcd监听配置变更
cli, _ := clientv3.New(clientv3.Config{
  Endpoints:   []string{"localhost:2379"},
  DialTimeout: 5 * time.Second,
})

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

resp, _ := cli.Get(ctx, "service/config")
fmt.Println("Current config:", string(resp.Kvs[0].Value))

// 监听后续变更
watchCh := cli.Watch(context.Background(), "service/config")
for wr := range watchCh {
  for _, ev := range wr.Events {
    fmt.Println("Config updated:", string(ev.Kv.Value))
  }
}
系统设计题应答框架
步骤操作要点
需求澄清明确QPS、数据规模、一致性要求
接口设计定义核心API与数据结构
架构选型选择注册中心、通信协议、存储引擎
容错设计加入熔断、限流、选举机制
第三方支付功能的技术人员;尤其适合从事电商、在线教育、SaaS类项目开发的工程师。; 使用场景及目标:① 实现微信与支付宝的Native、网页/APP等主流支付方式接入;② 掌握支付过程中关键的安全机制如签名验签、证书管理与敏感信息保护;③ 构建完整的支付闭环,包括下单、支付、异步通知、订单状态更新、退款与对账功能;④ 通过定时任务处理内容支付超时与概要状态不一致问:本文详细讲解了Java,提升系统健壮性。; 阅读应用接入支付宝和建议:建议结合官方文档与沙微信支付的全流程,涵盖支付产品介绍、开发环境搭建箱环境边学边练,重点关注、安全机制、配置管理、签名核心API调用及验签逻辑、异步通知的幂等处理实际代码实现。重点与异常边界情况;包括商户号与AppID获取、API注意生产环境中的密密钥与证书配置钥安全与接口调用频率控制、使用官方SDK进行支付。下单、异步通知处理、订单查询、退款、账单下载等功能,并深入解析签名与验签、加密解密、内网穿透等关键技术环节,帮助开发者构建安全可靠的支付系统。; 适合人群:具备一定Java开发基础,熟悉Spring框架和HTTP协议,有1-3年工作经验的后端研发人员或希望快速掌握第三方支付集成的开发者。; 使用场景及目标:① 实现微信支付Native模式与支付宝PC网页支付的接入;② 掌握支付过程中核心的安全机制如签名验签、证书管理、敏感数据加密;③ 处理支付结果异步通知、订单状态核对、定时任务补偿、退款及对账等生产级功能; 阅读建议:建议结合文档中的代码示例与官方API文档同步实践,重点关注支付流程的状态一致性控制、幂等性处理和异常边界情况,建议在沙箱环境中完成全流程测试后再上线。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值