创见博客
JavaScript 中的 call、apply 与 bind 方法
七崽爱吃小饼干2025/12/30阅读 5专栏 JavaScript

一、核心共性

call、apply、bind 这三个方法均为 Function 原型上的方法(所有函数都可以直接调用),它们的核心作用完全一致:手动改变(绑定)函数执行时的 this 指向,让函数可以在指定的上下文环境中执行。

二、各自特性与用法

1. call 方法

  • 执行特性:调用 call 后,函数会立即执行。
  • 参数传递:第一个参数是要绑定的 this 指向(若传递 null/undefined,在非严格模式下 this 会指向全局对象:浏览器中是 window,Node.js 中是 global);从第二个参数开始,是单个的、依次传递的参数列表(即逐个传入函数所需的参数)。
  • 语法格式:函数名.call(thisArg, arg1, arg2, ..., argN)
  • 代码示例:
javascript
// 定义一个函数
function sayHello(greet, punctuation) {
  console.log(`${greet},我是${this.name}${punctuation}`);
}

// 定义上下文对象
const person = { name: "张三" };

// 使用 call 绑定 this 并立即执行,逐个传递参数
sayHello.call(person, "你好", "!"); // 输出:你好,我是张三!
sayHello.call(null, "哈喽", "~"); // 非严格模式下 this 指向 window,若 window 无 name 属性则输出:哈喽,我是undefined~

2. apply 方法

  • 执行特性:与 call 一致,调用后函数会立即执行。
  • 参数传递:第一个参数同样是要绑定的 this 指向;第二个参数是一个数组(或类数组对象),数组中的每一项对应函数的一个参数(无需逐个拆分参数,适合参数数量不固定或已存在于数组中的场景)。
  • 语法格式:函数名.apply(thisArg, [arg1, arg2, ..., argN])
  • 代码示例:
javascript
// 复用上面的 sayHello 函数和 person 对象
const args = ["早上好", "。"];

// 使用 apply 绑定 this 并立即执行,数组传递参数
sayHello.apply(person, args); // 输出:早上好,我是张三。
// 也可以直接传递字面量数组
sayHello.apply(person, ["晚上好", "!"]); // 输出:晚上好,我是张三!

3. bind 方法

  • 执行特性:与 call、apply 不同,bind 调用后不会立即执行函数,而是返回一个永久绑定了该 this 指向的新函数(后续调用这个新函数时,this 不会再改变,除非使用 call/apply 再次修改)。
  • 参数传递:第一个参数是要绑定的 this 指向;从第二个参数开始,可以预先传递部分参数(柯里化),后续调用新函数时,只需传递剩余参数即可。
  • 语法格式:const 新函数 = 函数名.bind(thisArg, arg1, arg2, ..., argN)
  • 代码示例:
javascript
// 复用上面的 sayHello 函数和 person 对象
// 使用 bind 绑定 this,预先传递第一个参数 "嗨",返回新函数
const bindSayHello = sayHello.bind(person, "嗨");

// 后续调用新函数,只需传递剩余参数
bindSayHello("!"); // 输出:嗨,我是张三!
bindSayHello("~"); // 输出:嗨,我是张三~

// 新函数的 this 已固定,无法通过普通调用改变
const otherPerson = { name: "李四" };
bindSayHello.call(otherPerson, "?"); // 仍输出:嗨,我是张三?(this 未改变)

三、核心区别总结

特性callapplybind
函数执行时机立即执行立即执行不立即执行,返回新函数
参数传递方式逐个传递参数列表传递参数数组/类数组逐个传递(支持预先传参)
返回值函数执行后的返回值函数执行后的返回值绑定 this 后的新函数

四、典型使用场景

你想了解 call、apply、bind 三个方法的实际业务使用场景,它们各自有明确的适用场景,下面结合具体可运行的代码示例详细说明:

1.call 方法的实际使用场景

call 适合参数数量明确、需要立即执行函数,且常用来「借用方法」或「统一函数执行上下文」的场景。

构造函数继承(辅助实现属性继承)

在 ES6 class 出现之前,通过构造函数实现继承时,可使用 call 调用父构造函数,实现子实例继承父构造函数的属性。

javascript
// 父构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 子构造函数
function Student(name, age, score) {
  // 借用 Person 构造函数,将 this 绑定为 Student 实例,继承 name 和 age 属性
  Person.call(this, name, age);
  this.score = score; // 子实例自身属性
}

const student1 = new Student("小明", 18, 95);
console.log(student1); // Student { name: "小明", age: 18, score: 95 }

2. apply 方法的实际使用场景

apply 最核心的优势是支持以数组/类数组传递参数,适合参数已存在于数组中、或参数数量不固定的场景,且函数需要立即执行。

求数组的最大值/最小值(解决 Math 方法不支持数组参数问题)

Math.max() 和 Math.min() 只支持逐个传入参数,不支持直接传入数组,通过 apply 可快速传递数组参数实现需求。

javascript
// 数组求最大值
const nums1 = [12, 45, 8, 23, 56, 9];
const maxNum = Math.max.apply(null, nums1); // 等价于 Math.max(12,45,8,23,56,9)
console.log("数组最大值:", maxNum); // 56

// 数组求最小值
const nums2 = [3, 7, 1, 9, 4, 6];
const minNum = Math.min.apply(null, nums2); // 等价于 Math.min(3,7,1,9,4,6)
console.log("数组最小值:", minNum); // 1

// 补充:ES6 扩展运算符(...)可替代此场景,但 apply 是传统兼容方案
const maxNumES6 = Math.max(...nums1);
console.log(maxNumES6); // 56(效果一致)

三、bind 方法的实际使用场景

bind 的核心特性是不立即执行函数,返回一个永久绑定 this 指向的新函数,适合需要延迟执行、且需要固定 this 上下文的场景。

场景1:事件处理函数中固定 this 指向

DOM 事件触发时,事件处理函数的 this 默认指向触发事件的 DOM 元素,若需要让 this 指向自定义对象,可通过 bind 绑定。

javascript
// 自定义上下文对象
const user = {
  username: "小红",
  sayHello: function() {
    console.log(`${this.username} 点击了按钮`);
  }
};

// 获取按钮元素
const btn = document.querySelector("button");

// 绑定 this 为 user,返回新函数作为事件处理函数(点击时才执行)
btn.addEventListener("click", user.sayHello.bind(user));

// 若直接绑定 user.sayHello,点击时 this 指向 btn,会输出 undefined 点击了按钮
// btn.addEventListener("click", user.sayHello); // 错误用法

场景2:定时器/延时器中固定 this 指向

定时器 setTimeout/setInterval 的回调函数中,this 默认指向全局对象(浏览器中是 window),通过 bind 可固定其 this 指向自定义上下文。

javascript
const person = {
  name: "小李",
  delayedSay: function() {
    // 延迟 1 秒执行,通过 bind 绑定 this 为 person
    setTimeout(function() {
      console.log(`我是${this.name},延迟 1 秒执行`);
    }.bind(this), 1000);
  }
};

person.delayedSay(); // 1 秒后输出:我是小李,延迟 1 秒执行

// 若不使用 bind,this 指向 window,会输出:我是undefined,延迟 1 秒执行

场景3:函数柯里化(预先传递部分参数,延迟传递剩余参数)

bind 支持预先传递部分参数,返回的新函数只需接收剩余参数即可执行,这是实现函数柯里化的常用方式之一。

javascript
// 原始函数:计算三个数的和
function add(a, b, c) {
  return a + b + c;
}

// 使用 bind 预先传递第一个参数 10,返回新函数
const add10 = add.bind(null, 10);

// 后续调用新函数,只需传递剩余两个参数
console.log(add10(20, 30)); // 60(10+20+30)
console.log(add10(5, 15)); // 30(10+5+15)

// 进一步柯里化:预先传递前两个参数
const add10And20 = add.bind(null, 10, 20);
console.log(add10And20(50)); // 80(10+20+50)

五、总结

  1. call:参数明确需逐个传递、需立即执行,核心场景是「方法借用」(类数组操作)、「构造函数属性继承」。
  2. apply:参数已在数组中、需立即执行,核心场景是「数组求最大/最小值」、「批量传递数组参数」。
  3. bind:需延迟执行(事件/定时器)、需固定 this 指向,核心场景是「事件处理」、「定时器上下文绑定」、「函数柯里化」。
  4. 三者核心都是改变 this 指向,区别仅在于「执行时机」和「参数传递方式」,可根据实际场景灵活选择。
评论
0/100