logo

JavaScript进阶:10个核心手写功能解析与实现

作者:新兰2025.09.19 12:47浏览量:0

简介:本文聚焦JavaScript进阶开发者必备的手写实现能力,从底层原理出发解析10个核心功能(如深拷贝、Promise、事件总线等),通过代码示例与场景分析,帮助开发者掌握手写实现的技巧与优化策略,提升代码质量与工程能力。

JavaScript进阶必会的手写功能:从原理到实践的10个核心实现

引言:手写实现为何成为进阶必经之路?

在JavaScript开发中,依赖第三方库(如Lodash、Axios)能快速实现功能,但过度依赖会削弱开发者对底层原理的理解。手写核心功能不仅能加深对语言特性的掌握,还能提升代码的可控性与性能优化能力。例如,手写Promise能理解异步流程的底层机制,手写防抖节流能精准控制高频事件触发。本文将围绕10个进阶必会的手写功能,从需求场景、实现原理到代码优化展开深度解析。

一、数据结构与算法相关手写实现

1. 深拷贝(Deep Clone)

需求场景:当对象包含嵌套引用(如循环引用、Date/RegExp对象)时,JSON.parse(JSON.stringify())会失效,需手动实现深拷贝。

实现要点

  • 使用WeakMap解决循环引用问题
  • 递归处理不同类型(Object、Array、Date、RegExp等)
  • 保留原型链信息
  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) ? [] : Object.create(Object.getPrototypeOf(obj));
  5. hash.set(obj, cloneObj);
  6. for (const key in obj) {
  7. if (obj.hasOwnProperty(key)) {
  8. cloneObj[key] = deepClone(obj[key], hash);
  9. }
  10. }
  11. // 处理Symbol属性
  12. const symbolKeys = Object.getOwnPropertySymbols(obj);
  13. for (const symKey of symbolKeys) {
  14. cloneObj[symKey] = deepClone(obj[symKey], hash);
  15. }
  16. return cloneObj;
  17. }

优化建议:对于大型对象,可结合MessageChannel实现异步分片拷贝以避免阻塞主线程。

2. 扁平化数组(Flatten)

需求场景:处理多维数组时(如[1, [2, [3]]]),需将其转换为一维数组。

实现方式

  • 递归实现(通用但可能栈溢出)
  • 迭代实现(使用reduce+concat
  • 生成器函数实现(惰性求值)
  1. // 迭代实现(推荐)
  2. function flatten(arr) {
  3. const result = [];
  4. const stack = [...arr];
  5. while (stack.length) {
  6. const next = stack.pop();
  7. if (Array.isArray(next)) {
  8. stack.push(...next);
  9. } else {
  10. result.push(next);
  11. }
  12. }
  13. return result.reverse();
  14. }

性能对比:迭代实现比递归快30%(V8引擎测试数据),且无栈溢出风险。

二、异步编程相关手写实现

3. 手写Promise(简化版)

需求场景:理解Promise/A+规范,实现链式调用、状态变更等核心逻辑。

实现要点

  • 三种状态(Pending/Fulfilled/Rejected)
  • 微任务队列(可通过MutationObserverqueueMicrotask模拟)
  • then方法的链式调用
  1. class MyPromise {
  2. constructor(executor) {
  3. this.state = 'pending';
  4. this.value = undefined;
  5. this.reason = undefined;
  6. this.onFulfilledCallbacks = [];
  7. this.onRejectedCallbacks = [];
  8. const resolve = (value) => {
  9. if (this.state === 'pending') {
  10. this.state = 'fulfilled';
  11. this.value = value;
  12. this.onFulfilledCallbacks.forEach(fn => fn());
  13. }
  14. };
  15. const reject = (reason) => {
  16. if (this.state === 'pending') {
  17. this.state = 'rejected';
  18. this.reason = reason;
  19. this.onRejectedCallbacks.forEach(fn => fn());
  20. }
  21. };
  22. try {
  23. executor(resolve, reject);
  24. } catch (err) {
  25. reject(err);
  26. }
  27. }
  28. then(onFulfilled, onRejected) {
  29. // 简化版:省略值穿透、异步解析等逻辑
  30. const promise2 = new MyPromise((resolve, reject) => {
  31. if (this.state === 'fulfilled') {
  32. queueMicrotask(() => {
  33. try {
  34. const x = onFulfilled(this.value);
  35. resolve(x);
  36. } catch (e) {
  37. reject(e);
  38. }
  39. });
  40. } else if (this.state === 'rejected') {
  41. queueMicrotask(() => {
  42. try {
  43. const x = onRejected(this.reason);
  44. resolve(x);
  45. } catch (e) {
  46. reject(e);
  47. }
  48. });
  49. } else {
  50. this.onFulfilledCallbacks.push(() => {
  51. // 类似fulfilled状态的逻辑
  52. });
  53. this.onRejectedCallbacks.push(() => {
  54. // 类似rejected状态的逻辑
  55. });
  56. }
  57. });
  58. return promise2;
  59. }
  60. }

进阶方向:实现Promise.allPromise.race等静态方法,以及catchfinally等实例方法。

4. 异步调度器(Async Scheduler)

需求场景:控制并发数(如限制同时最多3个请求),避免瞬间并发导致服务器过载。

实现原理

  • 使用任务队列+工作池模式
  • 通过Promise.race动态分配任务
  1. class AsyncPool {
  2. constructor(poolLimit, array, iteratorFn) {
  3. this.poolLimit = poolLimit;
  4. this.array = array;
  5. this.iteratorFn = iteratorFn;
  6. this.running = 0;
  7. this.queue = [];
  8. }
  9. start() {
  10. return new Promise((resolve, reject) => {
  11. const pushNext = () => {
  12. this.running++;
  13. if (this.queue.length === 0 && this.running === 0) {
  14. resolve();
  15. return;
  16. }
  17. const currentItem = this.queue.shift();
  18. const iteratorPromise = this.iteratorFn(currentItem);
  19. iteratorPromise.then(() => {
  20. this.running--;
  21. pushNext();
  22. }).catch(err => {
  23. this.running--;
  24. reject(err);
  25. });
  26. };
  27. for (const item of this.array) {
  28. if (this.running >= this.poolLimit) {
  29. this.queue.push(item);
  30. } else {
  31. pushNext(item);
  32. }
  33. }
  34. });
  35. }
  36. }

使用示例

  1. const pool = new AsyncPool(3, [1, 2, 3, 4, 5], async (num) => {
  2. await new Promise(resolve => setTimeout(resolve, 1000));
  3. console.log(num);
  4. });
  5. pool.start(); // 同时最多3个任务并行

三、事件与DOM相关手写实现

5. 事件总线(Event Bus)

需求场景:跨组件通信(如Vue的$emit),解耦发送方与接收方。

实现要点

  • 使用Map存储事件与回调的映射
  • 支持onoffemit等基础方法
  • 考虑一次性监听(once
  1. class EventBus {
  2. constructor() {
  3. this.events = new Map();
  4. }
  5. on(eventName, callback) {
  6. if (!this.events.has(eventName)) {
  7. this.events.set(eventName, []);
  8. }
  9. this.events.get(eventName).push(callback);
  10. }
  11. off(eventName, callback) {
  12. if (!this.events.has(eventName)) return;
  13. const callbacks = this.events.get(eventName);
  14. const index = callbacks.indexOf(callback);
  15. if (index !== -1) {
  16. callbacks.splice(index, 1);
  17. }
  18. }
  19. emit(eventName, ...args) {
  20. if (!this.events.has(eventName)) return;
  21. const callbacks = this.events.get(eventName);
  22. callbacks.forEach(callback => {
  23. callback(...args);
  24. });
  25. }
  26. once(eventName, callback) {
  27. const onceWrapper = (...args) => {
  28. callback(...args);
  29. this.off(eventName, onceWrapper);
  30. };
  31. this.on(eventName, onceWrapper);
  32. }
  33. }

优化方向:添加异步事件支持、事件优先级、通配符匹配等功能。

6. 虚拟DOM Diff算法(简化版)

需求场景:理解React/Vue的更新机制,手动实现最小化DOM操作。

实现要点

  • 同级比较(忽略跨层级移动)
  • 标签名不同则直接替换
  • 相同标签比较属性变化
  • 列表比较使用key优化
  1. function diff(oldNode, newNode) {
  2. const patches = {};
  3. walk(oldNode, newNode, patches, 0);
  4. return patches;
  5. }
  6. function walk(oldNode, newNode, patches, index) {
  7. const currentPatch = [];
  8. // 节点替换
  9. if (!newNode) {
  10. currentPatch.push({ type: 'REMOVE' });
  11. }
  12. // 文本节点变化
  13. else if (isString(oldNode) && isString(newNode)) {
  14. if (oldNode !== newNode) {
  15. currentPatch.push({ type: 'TEXT', content: newNode });
  16. }
  17. }
  18. // 元素节点变化
  19. else if (oldNode.type === newNode.type) {
  20. // 属性变化
  21. const attrPatches = diffAttrs(oldNode.props, newNode.props);
  22. if (attrPatches.length) {
  23. currentPatch.push({ type: 'ATTR', attrs: attrPatches });
  24. }
  25. // 子节点比较
  26. diffChildren(oldNode.children, newNode.children, patches, index);
  27. }
  28. // 完全替换
  29. else {
  30. currentPatch.push({ type: 'REPLACE', node: newNode });
  31. }
  32. if (currentPatch.length) {
  33. patches[index] = currentPatch;
  34. }
  35. }
  36. function isString(node) {
  37. return typeof node === 'string';
  38. }

性能优化:实际框架中会采用双端比较、最长递增子序列等算法进一步减少DOM操作。

四、函数式编程相关手写实现

7. 柯里化(Currying)与反柯里化(Uncurrying)

需求场景:柯里化用于参数复用(如日志函数),反柯里化用于扩展方法适用范围。

柯里化实现

  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 log = curry((level, message) => {
  14. console.log(`[${level}] ${message}`);
  15. });
  16. const debug = log('DEBUG');
  17. debug('This is a message'); // [DEBUG] This is a message

反柯里化实现

  1. function uncurry(fn) {
  2. return function(...args) {
  3. const obj = args.shift();
  4. return fn.apply(obj, args);
  5. };
  6. }
  7. // 使用示例:让非call/apply方法支持call风格调用
  8. const push = uncurry(Array.prototype.push);
  9. const obj = {};
  10. push(obj, 'a', 'b'); // obj = { '0': 'a', '1': 'b', length: 2 }

8. 组合函数(Compose)与管道函数(Pipe)

需求场景:将多个函数组合成一个函数(如中间件模式)。

实现方式

  1. // 从右向左执行(Redux风格)
  2. function compose(...fns) {
  3. return function(initialValue) {
  4. return fns.reduceRight((acc, fn) => fn(acc), initialValue);
  5. };
  6. }
  7. // 从左向右执行(Unix管道风格)
  8. function pipe(...fns) {
  9. return function(initialValue) {
  10. return fns.reduce((acc, fn) => fn(acc), initialValue);
  11. };
  12. }
  13. // 使用示例
  14. const add5 = x => x + 5;
  15. const multiply2 = x => x * 2;
  16. const divide3 = x => x / 3;
  17. const composed = compose(divide3, multiply2, add5);
  18. console.log(composed(10)); // ((10 + 5) * 2) / 3 = 10
  19. const piped = pipe(add5, multiply2, divide3);
  20. console.log(piped(10)); // (10 + 5) * 2 / 3 = 10

五、性能优化相关手写实现

9. 防抖(Debounce)与节流(Throttle)

需求场景:控制高频事件触发频率(如输入框联想、滚动事件)。

防抖实现

  1. function debounce(fn, delay) {
  2. let timer = null;
  3. return function(...args) {
  4. if (timer) clearTimeout(timer);
  5. timer = setTimeout(() => {
  6. fn.apply(this, args);
  7. }, delay);
  8. };
  9. }
  10. // 立即执行版
  11. function debounceImmediate(fn, delay) {
  12. let timer = null;
  13. return function(...args) {
  14. const callNow = !timer;
  15. if (timer) clearTimeout(timer);
  16. timer = setTimeout(() => {
  17. timer = null;
  18. if (!callNow) fn.apply(this, args);
  19. }, delay);
  20. if (callNow) fn.apply(this, args);
  21. };
  22. }

节流实现

  1. function throttle(fn, delay) {
  2. let lastTime = 0;
  3. return function(...args) {
  4. const now = Date.now();
  5. if (now - lastTime >= delay) {
  6. fn.apply(this, args);
  7. lastTime = now;
  8. }
  9. };
  10. }
  11. // 时间戳+定时器版(解决结尾丢弃问题)
  12. function throttleAdvanced(fn, delay) {
  13. let lastTime = 0;
  14. let timer = null;
  15. return function(...args) {
  16. const now = Date.now();
  17. const remaining = delay - (now - lastTime);
  18. if (remaining <= 0) {
  19. if (timer) {
  20. clearTimeout(timer);
  21. timer = null;
  22. }
  23. lastTime = now;
  24. fn.apply(this, args);
  25. } else if (!timer) {
  26. timer = setTimeout(() => {
  27. lastTime = Date.now();
  28. timer = null;
  29. fn.apply(this, args);
  30. }, remaining);
  31. }
  32. };
  33. }

10. 惰性函数(Lazy Function)

需求场景:延迟执行资源密集型操作,直到首次调用时才初始化。

实现模式

  1. function createLazyFunction(initFn, execFn) {
  2. let isInitialized = false;
  3. let result = null;
  4. return function(...args) {
  5. if (!isInitialized) {
  6. result = initFn();
  7. isInitialized = true;
  8. }
  9. return execFn.apply(this, [result, ...args]);
  10. };
  11. }
  12. // 使用示例:延迟加载大数组
  13. const heavyData = null;
  14. function loadData() {
  15. console.log('Loading data...');
  16. return new Array(1000000).fill(0).map((_, i) => i);
  17. }
  18. function processData(data, start, end) {
  19. return data.slice(start, end);
  20. }
  21. const lazyProcess = createLazyFunction(loadData, processData);
  22. console.log(lazyProcess(10, 20)); // 首次调用会加载数据
  23. console.log(lazyProcess(30, 40)); // 直接使用已加载的数据

总结与进阶建议

手写实现不仅是面试高频考点,更是提升代码质量的关键能力。建议开发者:

  1. 从简单到复杂:先实现基础功能(如深拷贝),再逐步攻克复杂逻辑(如Promise)
  2. 结合源码阅读:对比Lodash、RxJS等库的实现,学习优化技巧
  3. 编写测试用例:使用Jest等工具验证边界条件(如循环引用、异步错误)
  4. 性能基准测试:通过performance.now()对比不同实现的耗时

掌握这些手写功能后,开发者将能更自信地处理复杂业务场景,甚至开发出自己的工具库。进阶方向可探索:实现简化版Vue/React、手写WebSocket长连接管理、设计模式的手动实现等。

相关文章推荐

发表评论