JavaScript中apply、call、bind的区别与实战应用

作者:异步漫步时间:2026-10-30 12:23:38浏览:507

本文深入解析JavaScript中改变函数this指向的三个核心方法: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 对象(因为 JavaScript 中一切皆对象)。call 要求将参数按顺序逐个传递,而 apply 则要求将参数放在一个数组中传递。

在 JavaScript 中,函数的参数数量通常是不固定的。因此,如果参数数量明确已知,使用 call 更为合适。

如果参数数量不确定,则使用 apply,并将参数放入数组后传递。当参数数量不确定时,函数内部也可以通过 arguments 这个伪数组来遍历所有参数。

apply与call的常见应用场景

为了加深理解,下面列举一些常用用法:

数组之间追加


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] */

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


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 来使用其方法。

验证是否是数组(前提是 toString() 方法没有被重写过)


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

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


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() 方法会创建一个新函数,称为绑定函数。当调用这个绑定函数时,绑定函数会以创建它时传入 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的巧妙使用,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持!

作者最新文章