📝 高频 JavaScript 手写题解析

35 阅读3分钟

🔥 核心手写题解析

1. 防抖与节流函数

​应用场景​​:搜索框输入联想、滚动事件优化

// 防抖:多次触发只执行最后一次[5](@ref)
function debounce(func, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer); // 清除旧定时器
    timer = setTimeout(() => {
      func.apply(this, args); // 保留 this 指向
    }, delay);
  };
}

// 节流:固定时间间隔执行[5](@ref)
function throttle(func, delay) {
  let timer;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null; // 重置状态
      }, delay);
    }
  };
}

2. 柯里化函数实现

核心思想​​:分步传递参数,实现函数复用

function curry(fn) {
  return function curried(...args) {
    // 参数足够时执行,否则返回新函数
    return args.length >= fn.length 
      ? fn.apply(this, args)
      : (...newArgs) => curried(...args, ...newArgs);
  };
}

// 示例:支持多参数累加
const curryAdd = curry((a, b, c) => a + b + c);
console.log(curryAdd(1)(2)(3)); // 6

3. 原型链方法实现

3.1 call/apply/bind 三兄弟

// call:逐个传递参数
Function.prototype.myCall = function (context, ...args) {
  context = context || window;
  const fnSymbol = Symbol('fn');
  context[fnSymbol] = this; // this 指向调用函数
  const res = context[fnSymbol](...args);
  delete context[fnSymbol];
  return res;
};

// apply:数组形式传参[5](@ref)
Function.prototype.myApply = function (context, args) {
  // 实现逻辑类似 call,需判断参数是否为数组
};

// bind:返回绑定 this 的新函数
      // bind 改变this并返回一个新函数,参数为逗号分隔的参数列表,支持new操作符
      Function.prototype.myBind = function (context, ...args) {
        const self = this;
        function BoundFunction(...newArgs) {
          const isNew = new.target !== undefined;
          const isT = isNew ? this : context;
          let countArgs = args.concat(newArgs);
          return self._excute(isT, countArgs);
        }

        function F() {}
        F.prototype = self.prototype;

        self._excute = function (c, a) {
          const fnSymbol = Symbol("fn");
          c[fnSymbol] = self;
          const res = c[fnSymbol](...a);
          delete c[fnSymbol];
          return res;
        };

        return BoundFunction;
      };

4. 深拷贝终极方案

解决痛点​​:循环引用、特殊对象处理

      function deepClone(obj, hash = new WeakMap()) {
        if (!obj || typeof obj !== "object") {
          return obj;
        }
        if (hash.get(obj)) return hash.get(obj);
        if (obj instanceof Date) return new Date(obj);
        if (obj instanceof RegExp) return new RegExp(obj);
        const clone = Array.isArray(obj) ? [] : {};
        hash.set(obj, clone);

        for (const item in obj) {
          if (obj.hasOwnProperty(item)) {
            clone[item] = deepClone(obj[item], hash);
          }
        }
        return clone;
      }

🚀 进阶手写题精选

5. EventBus 事件总线

功能亮点​​:支持 once/off/异步触发

      class EventBus {
        constructor() {
          this.events = new Map();
        }

        on(eventName, callback) {
          if (!this.events.has(eventName)) {
            this.events.set(eventName, []);
          }
          this.events.get(eventName).push(callback);

          return () => this.off(eventName, callback);
        }

        off(eventName, callback) {
          const event = this.events.get(eventName);

          if (event) {
            this.events.set(
              eventName,
              event.filter((fn) => fn !== callback)
            );
          }
        }

        emit(eventName, ...args) {
          const event = this.events.get(eventName);

          if (event) {
            event.forEach((fn) => {
              setTimeout(() => {
                fn(...args);
              }, 0);
            });
          }
        }

        once(eventName, callback) {
          const onWrapper = (...args) => {
            callback(...args);
            this.off(eventName, onWrapper);
          };

          return this.on(eventName, onWrapper); // 返回一个取消监听的函数,方便后续调用
        }
      }

6. 倒计时精准控制

核心方案​​:解决客户端时间误差问题

      class countDown {
        constructor(endTime, serverTime, endCallback) {
          this.endTime = new Date(endTime).getTime();
          this.serverTime = new Date(serverTime).getTime();
          this.clientTime = Date.now();
          this.timeDiff = this.serverTime - this.clientTime;
          this.animationFrameId = null;
          this.timeRun = false;
          this.endCallback = endCallback;
        }

        start() {
          if (this.timeRun) return;
          this.timeRun = true;

          const update = () => {
            let now = Date.now() + this.timeDiff;
            let timeRemind = this.endTime - now;
            console.log(timeRemind); // 打印剩余时间,单位为毫秒

            if (timeRemind <= 0) {
              this.stop();
              this.endCallback?.();
              return;
            }

            const day = Math.floor(timeRemind / (1000 * 60 * 60 * 24));
            timeRemind %= 1000 * 60 * 60 * 24;
            const hour = Math.floor(timeRemind / (1000 * 60 * 60));
            timeRemind %= 1000 * 60 * 60;
            const minute = Math.floor(timeRemind / (1000 * 60));
            timeRemind %= 1000 * 60;
            const second = Math.floor(timeRemind / 1000);

            console.log(`${day}${hour}${minute}${second}秒`);

            this.animationFrameId = requestAnimationFrame(update);
          };

          this.animationFrameId = requestAnimationFrame(update);
        }

        stop() {
          cancelAnimationFrame(this.animationFrameId);
          this.timeRun = false;
        }
      }

6. 任务重试与最大失败次数控制

核心方案​​:事件失败触发重试,最大次数之后抛出异常

      async function executeTasks(tasks, retries) {
        const result = [];

        for (const task of tasks) {
          let errCount = 0,
            lastErr = null;

          while (errCount <= retries) {
            try {
              const res = await task();
              result.push(res);
              break;
            } catch (error) {
              lastErr = error;
              errCount++;
              if (errCount > retries) {
                throw lastErr;
              }
            }
          }
        }

        return result;
      }

7. 实现一个链式调用

      class Cat {
        #taskQueue = [];

        constructor() {
          Promise.resolve().then(() => this.#runQueue());
        }

        async #runQueue() {
          for (const task of this.#taskQueue) {
            await task();
          }
        }

        sleep(time) {
          this.#taskQueue.push(() => {
            return new Promise((resolve) => {
              setTimeout(resolve, time);
            });
          });
          return this;
        }

        bye(msg) {
          this.#taskQueue.push(() => {
            return new Promise((resolve) => {
              console.log(msg);
              resolve();
            });
          });
          return this;
        }

        say(msg) {
          this.#taskQueue.push(() => {
            return new Promise((resolve) => {
              console.log(msg);
              resolve();
            });
          });
          return this;
        }
      }
      const cat = new Cat();
      cat.say("Hello").sleep(3000).bye("bye");

8. map语法糖功能实现

      Array.prototype.myMap = function (callback) {
        const result = [];
        for (let i = 0, len = this.length; i < len; ++i) {
          if (this.hasOwnProperty(i)) {
            result.push(callback(this[i], i, this));
          }
        }
        return result;
      };

9. Instance语法糖功能实现

      function myInstance(obj, constructor) {
        if (!obj || typeof obj !== "object") return false;
        if (
          typeof constructor.prototype !== "function" ||
          constructor.prototype === undefined
        )
          return false;
        let curProto = Object.getPrototypeOf(obj);
        while (curProto !== null) {
          if (curProto === constructor.prototype) {
            return true;
          }

          curProto = Object.getPrototypeOf(curProto);
        }

        return false;
      }