搞懂 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 实现一个加法函数的偏函数版本。
相关推荐
超人不会飛几秒前
就着HTTP聊聊SSE的前世今生
前端·javascript·http
蓝胖子的多啦A梦3 分钟前
Vue+element 日期时间组件选择器精确到分钟,禁止选秒的配置
前端·javascript·vue.js·elementui·时间选选择器·样式修改
夏天想6 分钟前
vue2+elementui使用compressorjs压缩上传的图片
前端·javascript·elementui
The_cute_cat7 分钟前
JavaScript的初步学习
开发语言·javascript·学习
海天胜景10 分钟前
vue3 el-table 列增加 自定义排序逻辑
javascript·vue.js·elementui
烛阴25 分钟前
XPath 进阶:掌握高级选择器与路径表达式
前端·javascript
独立开阀者_FwtCoder35 分钟前
URL地址末尾加不加 "/" 有什么区别
前端·javascript·github
独立开阀者_FwtCoder38 分钟前
Vue3 新特性:原来watch 也能“暂停”和“恢复”了!
前端·javascript·github
前端小巷子1 小时前
跨域问题解决方案:开发代理
前端·javascript·面试
JohnYan1 小时前
Bun技术评估 - 07 S3
javascript·后端·bun