手写 call、apply、bind 的实现

Call的实现

基本实现思路:
  1. 将函数设置为对象的属性
  2. 执行该函数
  3. 删除该属性
  4. 返回执行结果
javascript 复制代码
Function.prototype.myCall = function (context, ...args) {
  // 如果 context 为 null 或者 undefined, 则指向全局对象 (浏览器中是window)
  context = context || globalThis;

  // 防止覆盖原有属性,使用 Symbol 作为唯一键
  const fnKey = Symbol("fn");

  // 将当前函数作为 context 的方法
  context[fnKey] = this;

  // 执行函数
  const result = context[fnKey](...args);

  // 删除临时添加的方法
  delete context[fnKey];

  return result;
};

// 更严谨的版本 (考虑更多边界情况)
Function.prototype.myCall = function (context = globalThis, ...args) {
  // 确保 context 是对象(如果不是,转换为对象包装)
  if (context === null || context === undefined) {
    context = globalThis;
  } else {
    context = Object(context);
  }

  // 使用唯一键
  const fnKey = Symbol("fn");

  // 将当前函数绑定到context
  context[fnKey] = this;

  try {
    // 执行函数
    return context[fnKey](...args);
  } finally {
    // 确保删除临时属性
    delete context[fnKey];
  }
};

apply的实现

applycall类似,只是第二个参数是数组

javascript 复制代码
Function.prototype.myApply = function (context, argsArray) {
  // 处理 context
  context = context || globalThis;

  // 如果argsArray不是数组或者类数组,则当作空数组处理
  if (
    !Array.isArray(argsArray) &&
    !(argsArray && typeof argsArray === "object" && "length" in argsArray)
  ) {
    argsArray = [];
  }

  // 创建唯一键
  const fnKey = Symbol("fn");

  // 将函数绑定到 context
  context[fnKey] = this;

  try {
    // 执行函数,使用展开运算符传递参数
    const result = context[fnKey](...argsArray);
    return result;
  } finally {
    // 清理
    delete context[fnKey];
  }
};

// 更简洁的版本(基于myCall)
Function.prototype.myApply = function (context, argsArray) {
  context = context || globalThis;
  argsArray = argsArray || [];

  // 使用 myCall 的实现
  const fnKey = Symbol("fn");
  context[fnKey] = this;

  try {
    return context[fnKey](...argsArray);
  } finally {
    delete context[fnKey];
  }
};

bind的实现

bind返回一个新函数,需要处理更多边界情况

javascript 复制代码
Function.prototype.myBind = function (context, ...bindArgs) {
  // 保存原函数
  const originalFunc = this;

  // 确保 context 是对象
  context = context || globalThis;

  // 返回的绑定函数
  const boundFunc = function (...callArgs) {
    // 判断是否通过 new 调用
    const isNewCall = this instanceof boundFunc;

    // 如果是 new 调用,this指向新创建的对象,而不是 context
    const thisContext = isNewCall ? this : context;

    // 合并参数
    const allArgs = bindArgs.concat(callArgs);

    // 执行原函数
    return originalFunc.apply(thisContext, allArgs);
  };

  // 维护原型关系 (为了支持 new 操作)
  // 使用一个空函数作为中介,避免直接修改boundFunc.prototype影响originalFunc.prototype
  const TempFunc = function () {};
  TempFunc.prototype = originalFunc.prototype;
  boundFunc.prototype = new TempFunc();

  return boundFunc;
};

更完整的 bind 实现(支持更多特性)

javascript 复制代码
Function.prototype.myBind = function (context, ...bindArgs) {
  // 保存原函数
  const originalFunc = this;

  // 判断是否是构造函数
  if (typeof originalFunc !== "function") {
    throw new TypeError("Function.prototype.bind called on non-function");
  }

  // 返回的绑定函数
  const boundFunc = function (...callArgs) {
    // 判断是否通过 new 调用
    // 通过 new 调用时,this应该是 boundFunc 的实例
    const isNewCall = this instanceof boundFunc;

    // 确定执行上下文
    const thisContext = isNewCall ? this : Object(context || globalThis);

    // 合并参数
    const allArgs = bindArgs.concat(callArgs);

    // 调用原函数
    return originalFunc.apply(thisContext, allArgs);
  };

  // 维护原型链
  if (originalFunc.prototype) {
    // 使用 Object.create 来创建原型链, 避免直接修改
    boundFunc.prototype = Object.create(originalFunc.prototype);
    // 修正 constructor 指向
    boundFunc.prototype.constructor = boundFunc;
  }

  // 保留原函数的长度(可选,非标准)
  try {
    // 计算绑定函数的length属性(原函数参数个数 - 绑定的参数个数)
    const originalLength = originalFunc.length;
    const bindArgsLength = bindArgs.length;
    const remainingArgs = Math.max(originalLength - bindArgsLength, 0);

    // 使用 Object.defineProperty 来定义不可枚举的 length 属性
    Object.defineProperty(boundFunc, "length", {
      value: remainingArgs,
      writable: false,
      enumerable: false,
      configurable: true,
    });
  } catch (e) {
    // 忽略错误
  }

  // 保留原函数的name属性(可选)
  try {
    Object.defineProperty(boundFunc, "name", {
      value: `bound ${originalFunc.name || ""}`,
      writable: false,
      enumerable: false,
      configurable: true,
    });
  } catch (e) {
    // 忽略错误
  }

  return boundFunc;
};

ES6类实现版本

javascript 复制代码
class FunctionUtils {
  static callPolyfill(fn, context, ...args) {
    const fnKey = Symbol("fn");
    const target = context || globalThis;

    // 确保 context 是对象
    const contextObj =
      target === null || target === undefined ? globalThis : Object(target);

    context[fnKey] = fn;
    try {
      return contextObj[fnKey](...args);
    } finally {
      delete contextObj[fnKey];
    }
  }

  static applyPolyfill(fn, context, argsArray) {
    return this.callPolyfill(fn, context, ...(argsArray || []));
  }

  static bindPolyfill(fn, context, ...bindArgs) {
    return function (...callArgs) {
      const isNewCall = this instanceof fn;
      const thisContext = isNewCall ? this : context || globalThis;
      const allArgs = bindArgs.concat(callArgs);

      return fn.apply(thisContext, allArgs);
    };
  }
}

// 使用示例
function test(a, b) {
  console.log(this.value + a + b);
}

const obj = { value: 10 };
FunctionUtils.callPolyfill(test, obj, 1, 2); // 13
FunctionUtils.applyPolyfill(test, obj, [1, 2]); // 13

const boundTest = FunctionUtils.bindPolyfill(test, obj, 1);
boundTest(2); // 13

总结

  1. call/apply的核心:将函数临时添加到目标对象上,然后调用它
  2. bind的核心:返回一个新函数,闭包保存原函数、上下文和预置参数
  3. new操作符的处理:bind返回的函数被new调用时,this应该指向新创建的实例
  4. 原型链维护:bind返回的函数应该能正确继承原函数的原型
  5. 边界情况:处理null/undefined上下文、参数类型等
  6. 性能考虑:使用Symbol避免属性名冲突,使用try-finally确保清理
相关推荐
科杰智能制造2 小时前
纯前端html、js实现人脸检测和表情检测,可直接在浏览器使用
前端·javascript·html
每天吃饭的羊2 小时前
组件库的有些点击事件是name-click这是如何分装de
前端·javascript·vue.js
x***01062 小时前
SpringSecurity+jwt实现权限认证功能
android·前端·后端
1024肥宅2 小时前
防抖(Debounce)
前端·javascript·ecmascript 6
1024肥宅2 小时前
节流(Throttle)
前端·javascript·ecmascript 6
大怪v2 小时前
【Virtual World 02】两点一线!!!
javascript·css·html
by__csdn2 小时前
Vue2纯前端图形验证码实现详解+源码
前端·javascript·typescript·vue·状态模式·css3·canva可画
Gomiko3 小时前
JavaScript基础(八):函数
开发语言·javascript·ecmascript
w***37513 小时前
Spring 核心技术解析【纯干货版】- Ⅶ:Spring 切面编程模块 Spring-Instrument 模块精讲
前端·数据库·spring