搞懂 call、apply 和 bind:JavaScript 中函数调用的三把利器(适合小白入门)

在 JavaScript 的世界中,callapplybind 是三个非常重要的方法,它们都属于 Function.prototype 上的方法。通过这三个方法,我们可以控制函数内部的 this 指向,也可以实现一些非常实用的功能。

但对很多前端新手来说,这三个方法看起来很像,却又难以区分。今天我们就来从头讲起,用最通俗易懂的语言和实际例子,让你彻底搞清楚它们之间的区别与用途。


一、先理解一个核心概念:函数中的 this

在讲解 callapplybind 之前,我们首先要明白一个关键点:

在 JavaScript 中,函数内部的 this 关键字指向谁,取决于函数如何被调用,而不是它在哪里定义。

例如:

javascript 复制代码
const person = {
    name: "小明",
    sayHello: function () {
        console.log("你好,我是" + this.name);
    }
};

person.sayHello(); // 输出:你好,我是小明

这里的 this 指向的是 person 对象,因为函数是作为对象的方法被调用的。

但如果这样写:

javascript 复制代码
const sayHello = person.sayHello;
sayHello(); // 输出:你好,我是undefined(非严格模式下可能为 window)

这时候 this 就不再指向 person,而是指向全局对象(如浏览器中的 window),所以输出了 undefined

这就引出了我们今天要讲的内容:如何手动控制函数内部的 this?

答案就是:使用 callapplybind


二、call 方法详解

定义:

call() 方法用于立即调用一个函数,并指定该函数内部的 this 值,还可以传入参数。

语法:

javascript 复制代码
function.call(thisArg, arg1, arg2, ...)
  • thisArg:你希望函数内部的 this 指向的对象。
  • arg1, arg2,...:传给函数的参数列表。

实际案例:

场景:借用其他对象的方法

假设我们有两个对象:

javascript 复制代码
const dog = {
    name: '旺财',
    speak: function (emotion) {
        console.log(this.name + '说:汪汪!我感到' + emotion);
    }
};

const cat = {
    name: '咪咪'
};

现在我们想让 cat 也"说话",但又不想重复写一个 speak 函数,就可以用 call 来"借"一下:

javascript 复制代码
dog.speak.call(cat, '开心');
// 输出:咪咪说:汪汪!我感到开心

这里,我们把 dog.speak 这个函数拿出来,用 call 把它的 this 改成 cat,并传了一个参数 "开心"


三、apply 方法详解

定义:

apply() 的作用和 call() 类似,也是用来改变函数内部的 this 指向 ,不同之处在于参数传递方式不同

语法:

javascript 复制代码
function.apply(thisArg, [argsArray])
  • thisArg:同上。
  • argsArray:一个数组或类数组,表示函数的参数。

实际案例:

场景:求数组中的最大值

我们知道,JavaScript 的 Math.max() 可以接收多个数字参数,但不能直接传一个数组进去:

javascript 复制代码
const numbers = [3, 7, 2, 9, 5];
console.log(Math.max(numbers)); // 输出 NaN

这时候可以用 apply 来解决这个问题:

javascript 复制代码
console.log(Math.max.apply(null, numbers)); // 输出:9

解释:我们把 numbers 数组作为参数传给了 Math.max,虽然第一个参数 null 没有用到(因为 Math.max 不依赖 this),但这正是 apply 的标准用法。


四、bind 方法详解

定义:

bind() 方法不会立即执行函数,而是返回一个新的函数 ,这个新函数内部的 this 已经绑定为你指定的对象。

语法:

javascript 复制代码
function.bind(thisArg, arg1, arg2, ...)
  • 返回的是一个绑定好 this 的新函数,需要后续调用才会执行。

实际案例:

场景:事件监听器中保持 this 指向

在开发中,我们经常会遇到这样的问题:

javascript 复制代码
const button = document.getElementById('myButton');

const user = {
    name: '张三',
    clickHandler: function () {
        console.log(this.name + '点击了按钮');
    }
};

button.addEventListener('click', user.clickHandler);

这时候你会发现,点击按钮后输出的是 undefined 点击了按钮,因为 this 指向的是 button 元素,而不是 user

怎么解决?用 bind

javascript 复制代码
button.addEventListener('click', user.clickHandler.bind(user));

这样,无论点击多少次,都会正确输出 "张三点击了按钮"


五、call、apply 和 bind 的区别总结

特性 call apply bind
是否立即执行 否(返回一个新函数)
参数形式 逐个传参 数组传参 逐个传参
用途 强制绑定 this 并立即调用 调用函数并传入数组参数 创建一个绑定好 this 的新函数供以后调用

六、综合对比图解

ini 复制代码
call       => func.call(obj, arg1, arg2)
apply      => func.apply(obj, [arg1, arg2])
bind       => const newFunc = func.bind(obj, arg1, arg2); newFunc()

七、常见应用场景对比表

应用场景 推荐使用 说明
借用方法(如猫说话) call / apply 控制 this 并立即执行
求最大值/最小值 apply 参数是数组时更方便
绑定事件处理函数 bind 避免 this 指向错误
提前绑定部分参数 bind 可以预设一部分参数
替换 this 执行函数 call / apply 根据参数形式选择其中一个

八、进阶技巧:偏函数应用(Partial Application)

利用 bind,我们还可以实现一种叫做偏函数的技术,即提前绑定一部分参数,生成一个新函数。

javascript 复制代码
function multiply(a, b) {
    return a * b;
}

const double = multiply.bind(null, 2);

console.log(double(5)); // 输出:10

在这个例子中,我们把 multiply 函数的第一个参数固定为 2,创建了一个新的 double 函数,专门用来计算某个数乘以 2。


九、面试常问题:call、apply 和 bind 的底层原理是什么?

虽然这超出了本文的重点,但简单提一下可以帮助你更好地理解:

  • callapply 的本质是:临时将函数赋值给某个对象,然后调用这个对象的方法 ,从而改变 this
  • bind 的本质是:返回一个包装函数,里面封装了原函数和 this 的绑定逻辑

如果你感兴趣,可以进一步学习这些方法的 polyfill 实现。


十、结语:掌握 call、apply、bind 是成为高级前端的必经之路

这三个方法看似简单,却在 JavaScript 开发中无处不在。它们帮助我们解决了:

  • 函数上下文混乱的问题
  • 参数传递不灵活的问题
  • 函数复用性差的问题

理解它们的区别和适用场景,不仅能提升你的代码质量,也能让你在面试中脱颖而出。

如果你能熟练运用 callapplybind,那么恭喜你,你已经迈出了通往高级前端工程师的重要一步!


如果你还想了解它们在 ES6/ESNext 中的新特性,或者想看看在 Vue、React 等框架中是如何使用它们的,欢迎继续关注我的博客更新


建议练习题目:

  1. 写一个函数,使用 call 让两个不同的对象都能调用同一个方法。
  2. 使用 apply 求数组中的最大值和最小值。
  3. 使用 bind 绑定事件处理器,并测试 this 是否正确指向。
  4. bind 实现一个加法函数的偏函数版本。
相关推荐
ssshooter2 小时前
看完就懂 useSyncExternalStore
前端·javascript·react.js
Live000004 小时前
在鸿蒙中使用 Repeat 渲染嵌套列表,修改内层列表的一个元素,页面不会更新
前端·javascript·react native
柳杉4 小时前
使用Ai从零开发智慧水利态势感知大屏(开源)
前端·javascript·数据可视化
球球pick小樱花4 小时前
游戏官网前端工具库:海内外案例解析
前端·javascript·css
喝水的长颈鹿4 小时前
【大白话前端 02】网页从解析到绘制的全流程
前端·javascript
用户14536981458784 小时前
VersionCheck.js - 让前端版本更新变得简单优雅
前端·javascript
codingWhat4 小时前
整理「祖传」代码,就是在开发脚手架?
前端·javascript·node.js
码路飞4 小时前
写了个 AI 聊天页面,被 5 种流式格式折腾了一整天 😭
javascript·python
Lee川4 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
颜酱5 小时前
单调队列:滑动窗口极值问题的最优解(通用模板版)
javascript·后端·算法