当前位置: 首页 > news >正文

深入解析 JavaScript 中的 call、apply、bind:用法、差异与面试题

在 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);// 调用新函数时,只需传第二个参数bconsole.log(add10(5)); // 10+5=15console.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绑定thisArgreturn 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); // 1setTimeout(obj.fn.call(obj), 1000); // 2setTimeout(() => 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 丢失问题),更是面试中的 “加分项”。建议结合本文的示例和面试题多动手实践,才能真正融会贯通。


文章转载自:

http://O3TqhHHx.pqkrh.cn
http://rk1KJzBR.pqkrh.cn
http://yKvblshS.pqkrh.cn
http://VOvyNbzZ.pqkrh.cn
http://S6M243L9.pqkrh.cn
http://WIl0vfgW.pqkrh.cn
http://Teb5u4kc.pqkrh.cn
http://m99Wj17w.pqkrh.cn
http://v7yH0emb.pqkrh.cn
http://i97WLbbb.pqkrh.cn
http://J22e4ptI.pqkrh.cn
http://55AVpQrU.pqkrh.cn
http://BWz1AZs2.pqkrh.cn
http://n89aAww2.pqkrh.cn
http://cRZsKJhf.pqkrh.cn
http://8Di9sofP.pqkrh.cn
http://pVKVkpXd.pqkrh.cn
http://1cjB9J5y.pqkrh.cn
http://TXL52V5T.pqkrh.cn
http://Thk8kwtf.pqkrh.cn
http://YJjTurPf.pqkrh.cn
http://le36fGdh.pqkrh.cn
http://EN1RDP3m.pqkrh.cn
http://8ZyCPD0S.pqkrh.cn
http://ezzXzFGV.pqkrh.cn
http://vwhWrtQB.pqkrh.cn
http://Vbzm9g33.pqkrh.cn
http://50TYMWSv.pqkrh.cn
http://zFFLUezP.pqkrh.cn
http://4EFtfCW8.pqkrh.cn
http://www.dtcms.com/a/370729.html

相关文章:

  • LangChain实战(十八):构建ReAct模式的网页内容摘要与分析Agent
  • OpenRouter:一站式 AI 模型调用平台,免费畅享千问、DeepSeek 等顶级模型
  • Python基础(①⑧Queue)
  • 小型磨床设计cad+三维图+设计说明书
  • EMS 抗扰度在边缘计算产品电路设计的基本问题
  • 拯救珍贵回忆:AI照片修复让老照片重获新生
  • 一款免费易用且打造的全功能媒体播放器
  • 记一次uniapp微信小程序开发scss变量失效的问题
  • 如何在Kali Linux官网下载历史版本
  • 软考中级习题与解答——第二章_程序语言与语言处理程序(3)
  • 外置flash提示音打包脚本
  • ecplise配置maven插件
  • Android应用完全重启指南:从任务重置到进程重生
  • WordPress如何绑定多个域名 WordPress实现多域名访问
  • Windows防火墙出入站规则在注册表中的位置
  • RecSys:用户行为序列建模以及DIN、SIM模型
  • 【LeetCode热题100道笔记】二叉树的层序遍历
  • OpenCV 实战篇——如何测算出任一副图片中的物体的实际尺寸?传感器尺寸与像元尺寸的关系?
  • 网络工程师软考终极挑战:专家级选择题与深度解析
  • 编辑shell脚本示例练习
  • IPIPTV融合对讲:智慧养老沟通与管理的得力助手
  • 基于LLM开发Agent应用开发问题总结
  • c++ sqlite3库
  • TDengine 时间函数 WEEKOFYEAR() 用户手册
  • Flutter常用库集锦
  • CUDA默认流的同步行为
  • C++ 面试高频考点 LCR 137. 点名 二分查找 题解 每日一题
  • Huawei C 安全函数库
  • linux C 语言开发 (三) 建立云服务器
  • 【基础-单选】在Stage模型中,模块的配置文件是