JavaScript中apply、call与bind的核心用法与区别详解

作者:代码拾荒时间:2026-10-29 16:15:04浏览:221

本文深入解析JavaScript中apply、call和bind三个方法的用法。它们均用于改变函数执行时的this指向,但存在参数传递方式、返回值及执行时机的差异。文章通过数组追加、获取极值、伪数组转换及事件处理等实例,详细对比了三者的适用场景与实现原理,帮助开发者掌握动态上下文切换的技巧。

本文旨在清晰阐述JavaScript中apply、call和bind的核心概念,并通过列举实际应用场景来加深理解与记忆。

apply与call基础

apply与call基础

在JavaScript中,call和apply的存在意义在于改变函数运行时的上下文(context),即动态调整函数内部this的指向。

JavaScript的一个显著特性是函数拥有「定义时上下文」和「运行时上下文」,且运行时上下文是可以被改变的。

首先通过一段代码示例进行说明:


function fruits() {}
fruits.prototype = {
  color: "red",
  say: function() {
    console.log("My color is " + this.color);
  }
}
var apple = new fruits;
apple.say();  //My color is red

假设存在对象banana = {color : "yellow"},若不想为其重新定义say方法,可以通过call或apply借用apple对象的say方法:


banana = {
  color: "yellow"
}
apple.say.call(banana);   //My color is yellow
apple.say.apply(banana);  //My color is yellow

由此可见,call和apply用于动态改变this指向。当某个对象(如banana)缺少某方法,而其他对象(如apple)拥有该方法时,可借助call或apply调用其他对象的方法进行操作。

apply与call的区别

apply与call的区别

apply和call的功能完全一致,主要区别在于接收参数的方式不同。例如定义如下函数:


var func = function(arg1, arg2) { 
};

可通过以下方式调用:


func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])

其中this指代想要指定的上下文,可以是任何JavaScript对象。call要求参数按顺序逐个传递,而apply则将参数封装在数组中传递。

若参数数量明确已知,使用call更为合适。

若参数数量不确定,则使用apply,并将参数push进数组后传递。函数内部也可通过arguments数组遍历所有参数。

为巩固记忆,以下列举常见用法:

常见应用场景

1、数组之间追加


var array1 = [12 , "foo" , {name "Joe"} , -2458]; 
var array2 = ["Doe" , 555 , 100]; 
Array.prototype.push.apply(array1, array2); 
/* array1 值为 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

2、获取数组中的最大值和最小值


var numbers = [5, 458 , 120 , -215 ]; 
var maxInNumbers = Math.max.apply(Math, numbers),  //458
  maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458

Number类型本身没有max方法,但Math对象有,因此可借助call或apply调用其方法。

3、验证对象是否为数组(前提:toString()方法未被重写)


functionisArray(obj){ 
  return Object.prototype.toString.call(obj) === '[object Array]' ;
}

4、类(伪)数组使用数组方法


var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

JavaScript中存在一种名为伪数组的对象结构。例如arguments对象,以及getElementsByTagName、document.childNodes等方法返回的NodeList对象,均属于伪数组,无法直接应用Array下的push、pop等方法。

但可通过Array.prototype.slice.call将其转换为具有length属性的真数组,从而使domNodes能够使用Array下的所有方法。

深入理解apply与call的应用

深入理解apply与call的应用

接下来通过一道面试题,更深入地理解apply和call。

定义一个log方法,用于代理console.log方法,常见解法如下:


function log(msg) {
 console.log(msg);
}
log(1);  //1
log(1,2);  //1

上述方法可满足基本需求,但当传入参数个数不确定时则失效。此时应考虑使用apply或call,由于参数数量不确定,使用apply是最佳选择,方法如下:


function log(){
 console.log.apply(console, arguments);
};
log(1);  //1
log(1,2);  //1 2

进一步要求为每个log消息添加"(app)"前缀,例如:


log("hello world");  //(app)hello world

如何优雅实现?需意识到arguments是伪数组,通过Array.prototype.slice.call转化为标准数组,再使用unshift方法,如下所示:


function log(){
 var args = Array.prototype.slice.call(arguments);
 args.unshift('(app)');
 console.log.apply(console, args);
};

bind方法

bind方法

介绍完apply和call后,讨论bind。bind()方法与apply和call类似,同样用于改变函数体内this的指向。

MDN解释指出:bind()方法会创建一个新函数,称为绑定函数。调用该绑定函数时,其this为创建时传入bind()的第一个参数;后续参数加上原函数运行时的参数按顺序作为原函数的参数来调用。

直接查看具体用法。在常见的单体模式中,通常使用_this、that或self保存this,以便在上下文改变后继续引用。例如:


var foo = {
  bar : 1,
  eventBind: function(){
    var _this = this;
    $('.someClass').on('click',function(event) {
      /* Act on the event */
      console.log(_this.bar);   //1
    });
  }
}

由于JavaScript的机制,上下文环境在eventBind:function(){}过渡到$('.someClass').on('click',function(event){})时发生改变,上述使用变量保存this的方式有效且无问题。当然,使用bind()可更优雅地解决:


var foo = {
  bar : 1,
  eventBind: function(){
    $('.someClass').on('click',function(event) {
      /* Act on the event */
      console.log(this.bar);   //1
    }.bind(this));
  }
}

在上述代码中,bind()创建了一个函数。当该click事件绑定时,其this关键词被设置为传入bind()的参数。因此,这里传入期望的上下文this(即foo)到bind()中。当回调函数执行时,this便指向foo对象。再举一个简单的例子:


var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3

这里创建了新函数func。使用bind()创建绑定函数后,执行时其this被设置为foo,而非调用bar()时的全局作用域。

有一个有趣的问题:如果连续bind()两次,或连续bind()三次,输出值是什么?例如:


var bar = function(){
  console.log(this.x);
}
var foo = {
  x:3
}
var sed = {
  x:4
}
var func = bar.bind(foo).bind(sed);
func(); //?
 
var fiv = {
  x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //?

答案是,两次调用均输出3,而非预期的4和5。原因是,在JavaScript中,多次bind()是无效的。深层原因在于,bind()的实现相当于在内部封装了call/apply,第二次bind()相当于再次封装第一次bind(),因此第二次及后续的bind()无法生效。

apply、call与bind的比较

apply、call与bind的比较

那么,apply、call和bind三者相比,有何异同?何时使用apply或call,何时使用bind?一个简单的例子:


var obj = {
  x: 81,
};
var foo = {
  getX: function() {
    return this.x;
  }
}
console.log(foo.getX.bind(obj)()); //81
console.log(foo.getX.call(obj));  //81
console.log(foo.getX.apply(obj));  //81

三个输出均为81,但注意使用bind()的写法后多了对括号。

区别在于:若希望改变上下文环境后并非立即执行,而是回调执行时,使用bind()方法;而apply/call则会立即执行函数。

再总结一下:

apply、call、bind三者均用于改变函数this对象的指向; apply、call、bind三者的第一个参数均为this要指向的对象,即指定的上下文; apply、call、bind三者均可利用后续参数传参;

bind返回对应函数,便于稍后调用;apply、call则是立即调用。

好了,本文关于JavaScript中apply、call和bind用法的相关教程到此结束,希望对大家有所帮助。

作者最新文章