巧记 call, apply, bind 的实现
用法简介
在 JavaScript 中,call
, apply
, 和 bind
都是用来改变函数内部 this
指向的工具。
call: 立即调用函数,参数逐个传递。
函数.call(想绑定的this, arg1, arg2, ...)
apply: 立即调用函数,参数以数组形式传递。
函数.apply(想绑定的this, [arg1, arg2, ...])
bind: 不立即调用,而是返回一个绑定了
this
的新函数,可稍后调用。新函数 = 函数.bind(想绑定的this, arg1, ...)
实现方式
call
, apply
, 和 bind
的实现魔法,都基于同一个简单而强大的思想:将函数“嫁接”到目标对象上,再以方法的形式调用它。
想象一下,我们想让 fn
这个函数在 targetObject
的上下文中执行(即让 fn
内部的 this
指向 targetObject
)。
只需三步,即可记住它们的共同实现原理:
第一步:挂载(嫁接)
将要执行的函数 fn
,变成目标对象 targetObject
的一个临时属性。为了防止覆盖掉 targetObject
已有的同名属性,我们会用一个独一无二的名字(比如 Symbol
)来命名这个临时属性。
// 假设 fn 是我们要执行的函数
// 假设 targetObject 是我们想绑定的 this
const uniqueKey = Symbol(); // 创建一个独一无二的钥匙
targetObject[uniqueKey] = fn; // 把 fn 函数挂载到目标对象上
理解: 此时,fn
已经成为了 targetObject
的一个方法,就像 targetObject.name
是它的属性一样。
第二步:调用
直接通过 targetObject
来调用这个刚刚挂载的临时方法。根据 JavaScript 的规则,当一个函数作为对象的方法被调用时,其内部的 this
自动指向该对象。这样,this
绑定的目的就达到了!
// 调用这个临时方法,并把参数传进去
// `...args` 代表了 call/apply 传递进来的参数
const result = targetObject[uniqueKey](...args);
理解: 执行 targetObject[uniqueKey]()
时,fn 内部的 this
就百分之百是 targetObject
。
第三步:卸载(清理)
函数已经执行完毕,我们不应该修改原始的 targetObject
。因此,需要把刚才添加的临时属性删掉,恢复 targetObject
的原貌。
// 执行完毕后,删除这个临时属性,不留痕跡
delete targetObject[uniqueKey];
理解: “事了拂衣去,深藏功与名”。目标对象恢复原样,但我们已经拿到了函数的执行结果。
总结
call
和apply
的实现,就是完整地执行上述“挂载 -> 调用 -> 卸载”三步曲,并立即返回结果。它们的唯一区别只在于第二步“调用”时传递参数的方式不同。bind
的实现,则是创建了一个新的函数,这个新函数封装了“挂载 -> 调用 -> 卸载”的逻辑,以便在将来被调用时执行。
记住这个核心的三步流程,你就能从根本上理解这三个方法是如何工作的,再也不会混淆它们。