call、apply、bind 有什么区别?它们如何改变 this?
简化版
call 和 apply 会立即调用函数并指定 this,区别是参数传递形式不同;bind 不立即调用,而是返回一个 this 被绑定的新函数。它们本质上都是显式指定函数执行时的 this。
详细版
- call 接收参数列表:fn.call(obj, a, b)。
- apply 接收参数数组或类数组:fn.apply(obj, [a, b])。
- bind 返回新函数:const g = fn.bind(obj, a),之后再调用。
- 严格模式下 this 可以是传入的原始值或 undefined;非严格模式下 null/undefined 会指向全局对象。
- bind 后的函数作为构造函数使用时,new 绑定优先级高于 bind 绑定的 this。
完整版教学
记忆钩子:call/apply 是“立刻借用方法”,bind 是“先借好,稍后再用”。
一、这题真正考什么
记忆钩子:call/apply 是“立刻借用方法”,bind 是“先借好,稍后再用”。
面试官想看的不是你能不能背一个术语,而是能不能解释 JavaScript 为什么会出现这些看似反直觉的结果。答题时要从执行模型、规范规则和工程边界三个角度展开。
二、底层机制怎么理解
JavaScript 函数的 this 不是由定义位置决定,而是由调用方式决定。call、apply、bind 提供了显式绑定 this 的能力,让一个函数可以在指定对象上下文中执行。它们常用于方法借用、函数柯里化、事件回调固定上下文和兼容旧代码。
JavaScript 高频题经常围绕“运行时如何决定结果”展开。只要能把创建阶段、调用方式、查找链路、转换规则或内存可达性说清楚,就能把题目从记忆题讲成原理题。
三、带数字或代码的小推演
Math.max.apply(null, [3, 7, 2]) 会把数组展开给 Math.max;handler.bind(vm) 可以把事件回调里的 this 固定到组件实例。若 bind 预置 2 个参数,后续调用再传 3 个参数,最终参数会按预置在前、调用在后的顺序合并。
下面把核心规则压成几行,复习时可以按顺序手推一遍:
fn.call(thisArg, arg1, arg2)
fn.apply(thisArg, [arg1, arg2])
const bound = fn.bind(thisArg, presetArg)
new bound() 时 this 指向新对象,而不是 thisArg
四、执行流程怎么走
可以把这道题放到下面的运行流程里看:
普通函数调用
|
默认绑定/隐式绑定决定 this
|
使用 call/apply
|
立即以指定 this 执行
|
使用 bind
|
返回绑定 this 和预置参数的新函数
流程图的价值是把“现象”还原成“步骤”。面试追问通常就是换一段代码让你预测输出,按流程推比靠直觉猜更稳。
五、对比表格
| 方法 | 是否立即执行 | 参数形式 |
|---|---|---|
| call | 是 | 参数逐个传 |
| apply | 是 | 数组或类数组 |
| bind | 否 | 返回新函数,可预置参数 |
| 箭头函数 | 不适用普通 this 绑定 | this 来自外层词法环境 |
对比题要主动说适用场景和边界。JavaScript 的很多机制都不是“好或坏”,而是历史兼容、运行时灵活性和工程可维护性之间的折中。
六、工程实践和排查思路
- 箭头函数没有自己的 this,call/apply/bind 不能改变它的 this。
- bind 会返回新函数,removeEventListener 时必须保留同一个函数引用。
- 遇到输出预测题时,不要只报结果,要按创建、查找、调用、转换、执行顺序解释。
- 生产代码要优先选择可读性强、边界清晰的写法,把语言怪异点限制在工具函数或明确封装里。
七、常见误区与追问
- 误区:call、apply、bind 只是参数写法不同。 要补上规范规则、执行阶段和反例,避免用一句经验话术盖过真正原因。
- 误区:bind 后 this 永远不能被 new 改变。 要补上规范规则、执行阶段和反例,避免用一句经验话术盖过真正原因。
- 误区:箭头函数也可以用 bind 改 this。 要补上规范规则、执行阶段和反例,避免用一句经验话术盖过真正原因。
- 追问:bind 为什么可以实现函数柯里化? 先给规则,再用一小段代码或内存/调用示意解释为什么。
- 追问:call/apply 在方法借用中怎么用? 先给规则,再用一小段代码或内存/调用示意解释为什么。
- 追问:手写 bind 时为什么要处理 new? 先给规则,再用一小段代码或内存/调用示意解释为什么。
八、加强记忆
三者按“立即不立即、参数怎么传、this 优先级”记。call 逐个传,apply 数组传,bind 返回函数;箭头函数和 new 是两个必须单独说明的边界。
复习 JavaScript 基础题时,可以固定按“创建阶段、执行阶段、查找链路、调用方式、类型转换、工程边界”六步组织答案。这个框架能覆盖大多数输出题和原理题。