Skip to content

call、apply 与 bind

callapplybind 都用于显式指定函数调用时的 this。它们解决的是“这次调用应该把 this 绑定给谁”。

三者分别做什么

  • call:立即调用,参数逐个传入
  • apply:立即调用,参数以数组形式传入
  • bind:不立即调用,返回一个绑定后的新函数
js
function greet(message) {
  console.log(message, this.name)
}

const user = { name: 'Lee' }

greet.call(user, 'hi')
greet.apply(user, ['hello'])

const bound = greet.bind(user, 'welcome')
bound()

为什么需要它们

最常见的原因是函数被单独传递后,原来的 this 绑定丢了。

js
const user = {
  name: 'Lee',
  sayHi() {
    console.log(this.name)
  },
}

const fn = user.sayHi
fn() // this 丢失

这时可以显式绑定:

js
fn.call(user)

call 的核心思路

call 可以理解成:临时把函数挂到目标对象上,再以“对象方法调用”的方式执行。

js
Function.prototype.myCall = function (ctx, ...args) {
  const target = ctx == null ? globalThis : Object(ctx)
  const key = Symbol('fn')

  target[key] = this
  const result = target[key](...args)
  delete target[key]

  return result
}

这里有几个关键点:

  • this 指向当前被调用的函数
  • null / undefined 需要兜到底层全局对象
  • 原始值需要先装箱成对象
  • 临时属性名通常用 Symbol 避免冲突

applycall 的区别

实现思路几乎一样,主要区别只是参数形式:

js
Function.prototype.myApply = function (ctx, args = []) {
  const target = ctx == null ? globalThis : Object(ctx)
  const key = Symbol('fn')

  target[key] = this
  const result = target[key](...args)
  delete target[key]

  return result
}

所以 apply 更适合“参数本来就是数组”的场景。

bind 的关键点

bindcall / apply 最大的不同是:它不会立刻执行,而是返回一个新函数。

js
Function.prototype.myBind = function (ctx, ...args) {
  const fn = this

  return function (...restArgs) {
    return fn.apply(ctx, [...args, ...restArgs])
  }
}

这让它特别适合:

  • 回调传递前先固定 this
  • 预填部分参数

bind 还涉及预置参数

js
function add(a, b) {
  return a + b
}

const add10 = add.bind(null, 10)
console.log(add10(5)) // 15

这里 10 在绑定时就已经被提前放进参数列表了。

手写 bind 时最容易漏的点

一个更完整的 bind 还要处理 new 调用场景:

js
Function.prototype.myBind = function (ctx, ...args) {
  const fn = this

  return function (...restArgs) {
    if (new.target) {
      return new fn(...args, ...restArgs)
    }

    return fn.apply(ctx, [...args, ...restArgs])
  }
}

因为绑定后的函数仍然可能被拿去当构造函数使用。

bind 不能简单理解成“永久改函数”

它不会修改原函数本身,而是返回一个新的包装函数。

js
const bound = greet.bind(user)

console.log(bound === greet) // false

使用边界

手写实现时需要注意:

  • 这里只是在模拟核心思路,不等于完整复刻规范细节
  • 真正的原生行为还涉及函数长度、原型链、构造器语义等细节
  • 日常面试题实现通常只要求掌握“绑定思路”,不是完全对齐规范

使用建议

  • 需要立刻调用时,用 callapply
  • 参数已经是数组时,apply 更顺手。
  • 需要返回包装函数时,用 bind
  • 手写实现时,先抓住“临时挂载执行”和“返回新函数”这两个核心模型。

基于 MIT 许可发布