JavaScript 中 bind、call、apply 的区别与实现

146 阅读2分钟

核心区别

方法执行时机参数形式返回值典型场景
bind延迟执行逐个参数新绑定函数回调函数绑定上下文
call立即执行参数列表原函数返回值明确参数数量的调用
apply立即执行数组/类数组原函数返回值动态参数数量的调用

方法详解

1. bind

语法

const boundFn = originalFn.bind(thisArg, arg1, arg2, ...)

特性

  • 不立即执行:返回一个绑定了 this 的新函数
  • 参数预置:支持提前固定部分参数
  • 不可覆盖:新函数的 this 无法被二次修改

示例

const user = { name: "John" };

function showMessage(text) {
  console.log(`${text}, ${this.name}!`);
}

const boundShow = showMessage.bind(user, "Hello");
boundShow(); // "Hello, John!"

2. call

语法

const result = originalFn.call(thisArg, arg1, arg2, ...)

特性

  • 立即执行:直接调用原函数
  • 参数逐个传递:适合已知参数数量的场景
  • 灵活性强:常用于借用对象方法

示例

const calculator = {
  sum(a, b) {
    return a + b;
  }
};

function multiply(c) {
  return this.sum(2, 3) * c;
}

console.log(multiply.call(calculator, 5)); // (2+3)*5 = 25

3. apply

语法

const result = originalFn.apply(thisArg, [arg1, arg2, ...])

特性

  • 立即执行:与 call 相同
  • 数组传参:适合动态参数场景
  • 性能优化:某些引擎对数组参数有优化

示例

Math.max.apply(null, [1, 5, 3]); // 5
Array.prototype.push.apply(arr1, arr2); // 合并数组

手写实现

实现 bind

Function.prototype.myBind = function(context, ...args) {
  const self = this;
  return function(...innerArgs) {
    return self.apply(context, args.concat(innerArgs));
  };
};

// 测试
const boundFn = sum.myBind({}, 2);
console.log(boundFn(3)); // 5 (当 sum = (a,b) => a+b 时)

实现 call

Function.prototype.myCall = function(context, ...args) {
  context = context || globalThis; // 浏览器环境用 window
  const key = Symbol('tempFn');
  context[key] = this;
  const result = context[key](...args);
  delete context[key];
  return result;
};

// 测试
function test(a) { return this.x + a }
console.log(test.myCall({x:10}, 5)); // 15

实现 apply

Function.prototype.myApply = function(context, argsArray = []) {
  context = context || globalThis;
  const key = Symbol('tempFn');
  context[key] = this;
  const result = context[key](...argsArray);
  delete context[key];
  return result;
};

// 测试
function test(a, b) { return this.x + a + b }
console.log(test.myApply({x:10}, [2, 3])); // 15

总结

  1. 选择原则

    • 需要函数延迟执行 → bind
    • 立即调用且参数确定 → call
    • 立即调用且参数动态 → apply
  2. 底层原理

    • 本质上都是通过隐式绑定 this 实现
    • 现代引擎对这三个方法有深度优化
  3. 进阶技巧

    • 使用 bind 实现函数柯里化
    • 通过 apply 实现数组合并等操作
    • 使用 call 实现伪数组转换(如 Array.prototype.slice.call(arguments)

掌握这三个方法,能显著提升对 JavaScript 执行上下文的理解与控制能力!