JavaScript 一面:call / apply / bind 用法与区别
在 JavaScript 一面里,
call/apply/bind基本属于「必考清单」。
这一篇不打算背参数签名,而是想搞清楚几件事:这三个方法到底在解决什么问题、它们之间具体有什么差异,以及在手写题和场景题里各自怎么用。
它们到底在解决什么问题?
可以先把 call / apply / bind 放在同一个背景下理解:
- JavaScript 里的
this不是在定义阶段就决定好的,而是在调用时由调用方式决定。 - 很多时候,我们希望:
- 临时把某个函数「借给」另一个对象用;
- 或者手动把
this绑定到一个固定对象上,而不是由调用点自然决定。
Function.prototype.call / apply / bind 做的事,本质上只有一件:
- 显式指定函数执行时的
this,并控制参数传递方式和调用时机。
在这个视角下,再看它们三个的区别,会清晰很多。
call / apply / bind 的核心区别(一句话版)
先给一个面试时可以快速说出来的对比:
fn.call(thisArg, arg1, arg2, ...)- 立即调用
fn,this指向thisArg,参数「按顺序逐个列出」。
- 立即调用
fn.apply(thisArg, [arg1, arg2, ...])- 立即调用
fn,this指向thisArg,参数「以数组形式传入」。
- 立即调用
fn.bind(thisArg, arg1, arg2, ...)- 不立即调用,返回一个新的函数:
- 这个新函数内部的
this永远指向thisArg; - 前置参数会被「预先绑定」,后续调用时再补充剩余参数。
- 这个新函数内部的
- 不立即调用,返回一个新的函数:
用一句简化版总结:
- call / apply:改 this + 立刻执行;
- bind:改 this + 返回一个「稍后再执行」的新函数。
call:改 this + 立即执行(参数逐个传)
1. 基本用法
签名大致可以理解为:
1func.call(thisArg, arg1, arg2, ...);
典型场景有两个:
- 1)函数借用
1function greet(greeting) {
2 console.log(greeting + ', ' + this.name);
3}
4
5const user = { name: 'Alice' };
6greet.call(user, 'Hello'); // Hello, Alice
- 2)类数组借用数组方法(ES6 之前常见)
1function demo() {
2 const args = Array.prototype.slice.call(arguments);
3 console.log(args);
4}
2. 面试里常见问法
- 「如何用
call改变函数执行时的this?」 - 「怎么用
call借用数组方法处理arguments?」
答题时,给出一句解释 + 一个简短示例即可,不需要堆很多 API 细节。
apply:改 this + 立即执行(参数数组传)
1. 基本用法
签名大致可以理解为:
1func.apply(thisArg, [arg1, arg2, ...]);
和 call 的唯一区别在于 参数的传递方式:
call:一个个列出来;apply:把参数放进一个数组里。
2. 典型使用场景
- 1)参数本来就在数组里
1const nums = [1, 2, 3];
2
3function sum(a, b, c) {
4 return a + b + c;
5}
6
7const result = sum.apply(null, nums); // 6
- 2)和内置函数配合(如 Math.max)
1const arr = [3, 9, 1];
2const max = Math.max.apply(null, arr); // 9
(在有展开运算符之后,Math.max(...arr) 更常见,但一面题里仍会以 apply 为切入点考 this 和参数传递。)
3. 面试里的区分点
高频问题通常是:
- 「
call和apply的区别是什么?」 - 答:都是修改
this并立即执行,区别只在于参数是「逐个传」还是「数组传」。
bind:改 this + 返回新函数(柯里化常用工具)
1. 基本用法
签名可以理解为:
1const newFn = func.bind(thisArg, arg1, arg2, ...);
2// 之后再通过 newFn(...) 执行
特点:
- 立刻返回一个新函数,不立即执行。
- 这个新函数内部的
this已经被绑定到thisArg,后续不会被普通调用方式改变。 - 传给
bind的参数会作为前置参数,后续调用时再补充剩余参数。
2. 常见场景
- 1)配合事件监听,固定 this 和部分参数
1const obj = {
2 x: 42,
3 handleClick(y) {
4 console.log(this.x, y);
5 },
6};
7
8const btn = document.querySelector('button');
9btn.addEventListener('click', obj.handleClick.bind(obj, 'clicked'));
- 2)模拟函数柯里化效果
1function add(a, b) {
2 return a + b;
3}
4
5const addFive = add.bind(null, 5);
6console.log(addFive(3)); // 8
3. bind 和 call/apply 的本质差异
可以用一句话概括:
- call/apply 是「当场执行」,bind 是「生成一个预设 this 和部分参数的新函数,等以后再执行」。
在一面里,面试官更看重你能不能说出「返回新函数」这个关键点,以及知道常见的事件绑定和柯里化场景。
常见题型:手写 bind / call / apply 思路
很多公司会要求你「简单实现一个 bind」,或者「实现一个简化版 call / apply」。这里不展开完整代码,只讲关键思路,方便你在面试时组织答案。
1. 手写 call 的核心思路
- 如果
thisArg不是对象(或者是null/undefined),要把它转成对应的包装对象或默认指向全局(严格模式有差异)。 - 暂时在
thisArg上挂一个函数属性,指向当前要调用的函数。 - 用传入的参数调用这个临时方法,拿到返回值。
- 删除临时属性,返回结果。
答题时,可以用口头版描述:
- 「我会把目标函数临时挂到
thisArg上执行一次,然后删除这个属性,这样this指向的就是thisArg了。」
2. 手写 bind 的核心思路
bind 的考点略多一些,关键点包括:
- 返回的是一个新函数,而不是直接执行。
- 需要保存:
- 绑定的 this(
thisArg); - 绑定时传入的「前置参数」。
- 绑定的 this(
- 新函数被调用时:
- 再次接收一批「后续参数」;
- 把前置参数 + 后续参数拼接起来,一起用
call或apply调用原函数。
- 更完整的实现还会处理
new调用时的情况,但一面里不一定要求到这个深度。
典型场景题:this、事件、回调里的用法
除了直接问区别,call/apply/bind 还常作为 this 题的配角出现。
1. 「如何在回调里保证 this 指向某个对象?」
可以给出几种做法,然后重点讲 bind:
- 传统做法:
const that = this;,在内部用that。 - 箭头函数:箭头函数不绑定自己的
this,继承自定义时环境。 bind:在传入回调前,用fn.bind(obj)固定 this。
示例性回答:
- 「如果希望在回调中始终指向某个对象,我会在传入回调时就用
bind绑定 this,比如btn.addEventListener('click', this.handleClick.bind(this))。」
2. 「如何借用别的对象的方法?」
典型就是:
Array.prototype.forEach.call(nodeList, callback)之类的写法。
答题时可以说:
- 「我会直接用
Array.prototype.someMethod.call(obj, ...),把obj当成 this 来借用数组方法。」
小结:一面里如何讲清 call / apply / bind?
可以准备一个自己顺口的回答模板,大致包含这几个点:
- 先用一句话说明它们的共同点:
- 「都是用来显式指定函数执行时的 this。」
- 再用一句话分别说区别:
- 「
call和apply都是修改 this 后立即执行,区别在于参数是一个个给还是用数组给;bind则是不执行,返回一个绑定了 this 和部分参数的新函数。」
- 「
- 最后补上 1–2 个场景:
- 「比如借用数组方法处理类数组,可以用
call/apply;在事件回调或异步回调里需要固定 this 时,我会用bind或箭头函数。」
- 「比如借用数组方法处理类数组,可以用
按照这个框架去答,大多数关于 call/apply/bind 的一面问题都能比较完整地覆盖到。