在 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 的核心差异

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

对比维度callapplybind
执行时机立即执行函数立即执行函数不立即执行,返回新函数
参数传递方式第二个参数开始,逐个传入第二个参数是数组(或类数组)第二个参数开始,逐个预设参数
返回值函数执行后的返回值函数执行后的返回值绑定了 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 丢失问题),更是面试中的 “加分项”。建议结合本文的示例和面试题多动手实践,才能真正融会贯通。

更多推荐