手写 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确保清理
相关推荐
二川bro4 分钟前
详细解析 cesiumViewer.render() 和 requestAnimationFrame(render)
前端
前端付豪8 分钟前
必知Node应用性能提升及API test 接口测试
前端·react.js·node.js
boooooooom12 分钟前
手写简易Vue响应式:基于Proxy + effect的核心实现
javascript·vue.js
王同学 学出来17 分钟前
vue+nodejs项目在服务器实现docker部署
服务器·前端·vue.js·docker·node.js
一道雷22 分钟前
让 Vant 弹出层适配 Uniapp Webview 返回键
前端·vue.js·前端框架
bug总结32 分钟前
uniapp+动态设置顶部导航栏使用详解
java·前端·javascript
晴殇i34 分钟前
深入理解MessageChannel:JS双向通信的高效解决方案
前端·javascript·程序员
毕设十刻36 分钟前
基于Vue的民宿管理系统st4rf(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
kkkAloha39 分钟前
倒计时 | setInterval
前端·javascript·vue.js
VT.馒头44 分钟前
【力扣】2622. 有时间限制的缓存
javascript·算法·leetcode·缓存·typescript