在 JavaScript 中,callapplybind是 Function.prototype 上的三个重要方法,它们的核心作用都是改变函数执行时的 this 指向,但在用法和场景上又存在明显差异。无论是日常开发还是面试,这三个方法都是高频考点。本文将从用法、差异对比、面试题解析三个维度,帮你彻底搞懂它们。

一、逐个击破:call、apply、bind 的详细用法

要掌握三者的区别,首先得明确每个方法的基本语法和使用场景。它们的第一个参数都是 “要绑定给 this 的值”,但后续参数的传递方式、函数是否立即执行等特性各不相同。

1. call 方法:逐个传递参数,立即执行

call的作用是立即调用函数,并将函数内的this绑定到第一个参数指定的对象上,后续参数则逐个传入函数。

语法
function.call(thisArg, arg1, arg2, ..., argN)
  • thisArg:函数执行时绑定的this值。如果为nullundefined,在非严格模式下this会指向全局对象(浏览器中是window,Node.js 中是global),严格模式下仍为null/undefined

  • arg1, arg2,…:传递给函数的参数,需逐个列出。

  • 返回值:函数执行后的返回值。

示例:改变 this 指向并传参
// 定义一个函数,打印this和参数

function printInfo(age, job) {

 console.log(`姓名:${this.name},年龄:${age},职业:${job}`);

}

// 定义一个对象,作为this的绑定目标

const person = { name: "张三" };

// 使用call调用函数:this绑定到person,参数逐个传入

printInfo.call(person, 25, "前端工程师");

// 输出:姓名:张三,年龄:25,职业:前端工程师

2. apply 方法:数组传递参数,立即执行

applycall的核心逻辑完全一致 ——立即调用函数并改变 this 指向,唯一的区别是:apply的第二个参数必须是数组(或类数组对象,如 arguments),数组中的元素会作为参数传递给函数。

语法
function.apply(thisArg, [argsArray])
  • thisArg:同call,绑定给this的值。

  • argsArray:数组或类数组对象,内部元素会作为参数传递给函数;若无需传参,可传null或空数组。

  • 返回值:函数执行后的返回值。

示例:数组参数的场景

当我们需要传递的参数本身就是数组时,apply会比call更简洁(无需手动拆解数组):

// 复用上面的printInfo函数和person对象

const info = [28, "产品经理"]; // 参数数组

// 使用apply调用函数:第二个参数直接传数组

printInfo.apply(person, info);

// 输出:姓名:张三,年龄:28,职业:产品经理
经典场景:求数组最大值

Math.max()方法不支持直接传入数组,但用apply可以轻松解决:

const numbers = [10, 5, 20, 8];

const max = Math.max.apply(Math, numbers); // 等同于Math.max(10,5,20,8)

console.log(max); // 输出:20

3. bind 方法:绑定 this,返回新函数(不立即执行)

bind与前两者的最大区别是:不立即执行函数,而是返回一个新的函数,新函数的this被永久绑定到bind的第一个参数上,后续参数会作为 “预设参数” 传递给新函数。

语法
const newFunction = function.bind(thisArg, arg1, arg2, ..., argN)
  • thisArg:绑定给新函数的this值(一旦绑定,后续无法通过call/apply修改)。

  • arg1, arg2,…:预设参数,会在新函数调用时,排在实际传入参数的前面。

  • 返回值:绑定了this和预设参数的新函数。

示例 1:绑定 this,后续调用
// 复用printInfo函数和person对象

const boundPrint = printInfo.bind(person); // 返回新函数,this已绑定person

// 后续需要时调用新函数,传入参数

boundPrint(30, "后端工程师");

// 输出:姓名:张三,年龄:30,职业:后端工程师
示例 2:预设参数(函数柯里化)

bind的预设参数特性可用于 “函数柯里化”(将多参数函数拆分为单参数函数):

// 定义一个加法函数

function add(a, b) {

 return a + b;

}

// 用bind预设第一个参数a=10,返回新函数

const add10 = add.bind(null, 10);

// 调用新函数时,只需传第二个参数b

console.log(add10(5)); // 10+5=15

console.log(add10(8)); // 10+8=18

二、横向对比:call、apply、bind 的核心差异

掌握了单个方法的用法后,我们通过表格直观对比三者的核心差异,避免混淆:

对比维度 call apply bind
执行时机 立即执行函数 立即执行函数 不立即执行,返回新函数
参数传递方式 第二个参数开始,逐个传入 第二个参数是数组(或类数组) 第二个参数开始,逐个预设参数
返回值 函数执行后的返回值 函数执行后的返回值 绑定了 this 的新函数
this 修改 临时修改一次执行的 this 临时修改一次执行的 this 永久绑定 this(新函数无法修改)
适用场景 参数数量固定时 参数已存在于数组中时 需延迟执行(如定时器、事件)

三、面试题实战:从理论到应用

理解了基础后,我们结合高频面试题,看看实际场景中如何运用这些知识。

面试题 1:用 call/apply 实现 bind 方法

题目要求:手动实现一个myBind方法,功能与原生bind一致。

思路分析

  1. myBind是函数的方法,需挂载到Function.prototype上;

  2. 接收thisArg和预设参数,返回一个新函数;

  3. 新函数调用时,需将this绑定到thisArg,并合并预设参数和实际参数;

  4. 需处理新函数被new关键字调用的场景(此时this应指向实例,而非thisArg)。

实现代码

Function.prototype.myBind = function (thisArg, ...presetArgs) {

 const self = this; // 保存原函数(调用myBind的函数)

 // 返回新函数

 const boundFunction = function (...actualArgs) {

   // 合并参数:预设参数 + 实际参数

   const args = [...presetArgs, ...actualArgs];

   // 判断是否用new调用:this是否为boundFunction的实例

   if (this instanceof boundFunction) {

     // 用new调用时,this指向实例,原函数作为构造函数执行

     return new self(...args);

   } else {

     // 普通调用,用call绑定thisArg

     return self.call(thisArg, ...args);

   }

 };

 // 让新函数的原型继承原函数的原型(保证new实例的原型链正确)

 boundFunction.prototype = Object.create(self.prototype);

 return boundFunction;

};

// 测试

function Person(name, age) {

 this.name = name;

 this.age = age;

}

const BoundPerson = Person.myBind(null, "李四");

const p = new BoundPerson(26);

console.log(p.name); // 李四(预设参数)

console.log(p.age); // 26(实际参数)

面试题 2:解释下面代码的输出结果

题目代码

const obj = {

 name: "A",

 fn: function () {

   console.log(this.name);

 }

};

const fn = obj.fn;

setTimeout(fn.bind(obj), 1000); // 1

setTimeout(obj.fn.call(obj), 1000); // 2

setTimeout(() => obj.fn.apply(obj), 1000); // 3

问题:1 秒后,三个setTimeout分别输出什么?

分析与答案

  • 1 处(bind)fn.bind(obj)返回新函数,1 秒后执行新函数,this绑定obj,输出A

  • 2 处(call)obj.fn.call(obj)立即执行(call 是立即执行),此时会先输出A,1 秒后setTimeout执行的是call的返回值(undefined),无额外输出;

  • 3 处(apply):箭头函数延迟 1 秒执行,执行时调用obj.fn.apply(obj)this绑定obj,输出A

最终输出:先立即输出A(来自 2 处),1 秒后输出A(1 处)和A(3 处)。

面试题 3:如何将类数组对象转为真正的数组?

题目要求:列举至少两种将类数组对象(如arguments、DOM 元素集合)转为数组的方法,其中一种需用到call/apply

答案

  1. Array.prototype.slice.call()
function fn() {

 const args = Array.prototype.slice.call(arguments); // 类数组转数组

 console.log(Array.isArray(args)); // true

}

fn(1,2,3);
  1. Array.from()(ES6):
const doms = document.querySelectorAll("div"); // DOM类数组

const domArray = Array.from(doms);
  1. 用扩展运算符(ES6):
function fn() {

 const args = [...arguments];

}

四、总结

callapplybind的核心是 “改变 this 指向”,但因执行时机和参数传递方式的差异,适用场景各不相同:

  • 立即执行且参数固定 → 用call

  • 立即执行且参数在数组中 → 用apply

  • 延迟执行(如定时器、事件回调) → 用bind

掌握它们不仅能提升代码灵活性(如复用函数、处理 this 丢失问题),更是面试中的 “加分项”。建议结合本文的示例和面试题多动手实践,才能真正融会贯通。

更多推荐