前言
JavaScript 是一门非常强大的编程语言,它支持多种编程范式,包括面向对象编程。在 JavaScript 中,我们可以使用 call() 和 apply() 两个方法来调用函数并且改变函数的上下文。这两个方法在 JavaScript 中是非常常用的,但是很多新手对它们的理解还不够深入。在本文中,我们将详细介绍 call() 和 apply() 的区别与用法,帮助读者更好地理解它们。
正文内容
一、call() 和 apply() 的作用
在 JavaScript 中,函数是一等公民,我们可以像使用其他类型的变量一样使用函数。我们可以将函数赋值给变量,将函数作为参数传递给其他函数,或者将函数作为返回值返回给其他函数。但是,函数的上下文是不能像变量一样随意传递的。函数的上下文是指函数内部 this 关键字的指向。在 JavaScript 中,函数的上下文默认是全局对象,但是我们可以通过 call() 和 apply() 方法来改变函数的上下文。
call() 和 apply() 方法的作用都是调用一个函数,并且改变函数的上下文。它们的语法如下:
代码语言:js复制function.call(thisArg, arg1, arg2, ...)
function.apply(thisArg, [argsArray])
其中,thisArg 表示要改变的函数上下文,arg1、arg2、... 表示函数的参数列表。如果使用 apply() 方法,则参数列表需要以数组的形式传递。这两个方法的区别在于参数的传递方式不同。
二、call() 和 apply() 的区别
call() 和 apply() 的区别在于参数的传递方式不同。call() 方法的参数是一个一个传递的,而 apply() 方法的参数需要以数组的形式传递。下面我们来看一个例子:
代码语言:js复制function sayHello(name, age) {
console.log(`Hello, my name is ${name}, I'm ${age} years old.`);
}
sayHello.call(null, 'Tom', 18);
sayHello.apply(null, ['Tom', 18]);
在这个例子中,我们定义了一个函数 sayHello,它接受两个参数 name 和 age。我们使用 call() 和 apply() 方法分别调用这个函数,并且传递相同的参数。在 call() 方法中,我们将参数一个一个传递,而在 apply() 方法中,我们将参数放在一个数组中传递。这两个方法的输出结果是相同的:
代码语言:js复制Hello, my name is Tom, I'm 18 years old.
除了参数的传递方式不同,call() 和 apply() 还有一些细微的差别。下面我们来详细介绍一下这些差别。
1. 参数传递方式不同
我们已经看到了,call() 和 apply() 方法的参数传递方式不同。这是两个方法最明显的区别。
2. 参数个数不同
在 JavaScript 中,函数的参数个数是可以变化的。我们可以定义一个函数,不传递任何参数,也可以定义一个函数,传递多个参数。在这种情况下,call() 和 apply() 方法的行为也是不同的。
当我们使用 call() 方法调用一个函数时,可以传递任意个数的参数。如果传递的参数个数不足,那么剩余的参数将会被忽略。如果传递的参数个数超过函数定义时的参数个数,那么多余的参数将会被忽略。
当我们使用 apply() 方法调用一个函数时,需要将参数放在一个数组中传递。如果数组中的元素个数不足,那么剩余的参数将会被设置为 undefined。如果数组中的元素个数超过函数定义时的参数个数,那么多余的元素将会被忽略。
下面我们来看一个例子:
代码语言:js复制function sayHello(name, age) {
console.log(`Hello, my name is ${name}, I'm ${age} years old.`);
}
sayHello.call(null, 'Tom', 18, 'male');
sayHello.apply(null, ['Tom', 18, 'male']);
在这个例子中,我们定义了一个函数 sayHello,它接受两个参数 name 和 age。我们使用 call() 和 apply() 方法分别调用这个函数,并且传递了三个参数。在 call() 方法中,我们传递了三个参数,但是第三个参数被忽略了。在 apply() 方法中,我们将参数放在一个数组中传递,但是第三个元素被忽略了。这两个方法的输出结果是相同的:
代码语言:js复制Hello, my name is Tom, I'm 18 years old.
3. thisArg 参数的处理方式不同
在 JavaScript 中,thisArg 参数是用来指定函数的上下文的。在 call() 方法中,thisArg 参数可以为任意值,包括 null 和 undefined。在 apply() 方法中,thisArg 参数必须为对象,如果传递的不是对象,则会自动转换为对象。
下面我们来看一个例子:
代码语言:js复制function sayHello(name, age) {
console.log(`Hello, my name is ${name}, I'm ${age} years old. My gender is ${this.gender}.`);
}
var person = {
gender: 'male'
};
sayHello.call(person, 'Tom', 18);
sayHello.apply(person, ['Tom', 18]);
在这个例子中,我们定义了一个函数 sayHello,它接受两个参数 name 和 age,还使用了 this 关键字来引用 gender 属性。我们定义了一个对象 person,它有一个 gender 属性。我们使用 call() 和 apply() 方法分别调用这个函数,并且将 person 对象作为 thisArg 参数传递。在 call() 方法中,我们将 person 对象直接传递给了 thisArg 参数。在 apply() 方法中,我们将 person 对象放在一个数组中传递。这两个方法的输出结果是相同的:
代码语言:js复制Hello, my name is Tom, I'm 18 years old. My gender is male.
4. 性能不同
在 JavaScript 中,函数的调用是有一定的开销的。每次调用函数,都需要将函数压入调用栈,然后执行函数体,最后将函数弹出调用栈。在这个过程中,会产生一定的开销。在这种情况下,call() 和 apply() 方法的性能也是不同的。
在大多数情况下,使用 call() 方法调用函数的性能要比使用 apply() 方法调用函数的性能要好。这是因为 call() 方法只需要将参数一个一个传递,而 apply() 方法需要将参数放在一个数组中传递,这会产生一定的开销。但是,这种差别在实际应用中并不是很明显,只有在调用函数的次数非常多的情况下才会产生明显的影响。
三、call() 和 apply() 的用途
call() 和 apply() 方法的用途非常广泛,下面我们来看一些常见的用途。
1. 改变函数的上下文
call() 和 apply() 方法最常见的用途就是改变函数的上下文。在 JavaScript 中,函数的上下文默认是全局对象,但是我们可以使用 call() 和 apply() 方法来将函数的上下文改变为其他对象。这个特性非常有用,可以让我们在不改变函数原有代码的情况下,改变函数的行为。
下面我们来看一个例子:
代码语言:js复制var person1 = {
name: 'Tom',
sayHello: function() {
console.log(`Hello, my name is ${this.name}.`);
}
};
var person2 = {
name: 'Jerry'
};
person1.sayHello(); // 输出:Hello, my name is Tom.
person1.sayHello.call(person2); // 输出:Hello, my name is Jerry.
在这个例子中,我们定义了一个对象 person1,它有一个 sayHello 方法,可以输出自己的名字。我们还定义了一个对象 person2,它也有一个 name 属性。我们使用 call() 方法调用 person1 的 sayHello 方法,并且将 person2 对象作为 thisArg 参数传递。这样,sayHello 方法的上下文就变成了 person2 对象,输出结果也相应地改变了。
2. 借用其他对象的方法
在 JavaScript 中,对象可以通过原型链继承其他对象的方法。但是,有时候我们需要借用其他对象的方法,而不是继承它们。这种情况下,call() 和 apply() 方法就可以派上用场了。
下面我们来看一个例子:
代码语言:js复制var person = {
name: 'Tom',
sayHello: function() {
console.log(`Hello, my name is ${this.name}.`);
}
};
var dog = {
name: 'Snoopy'
};
person.sayHello.call(dog); // 输出:Hello, my name is Snoopy.
在这个例子中,我们定义了一个对象 person,它有一个 sayHello 方法,可以输出自己的名字。我们还定义了一个对象 dog,它也有一个 name 属性。我们使用 call() 方法调用 person 的 sayHello 方法,并且将 dog 对象作为 thisArg 参数传递。这样,sayHello 方法的上下文就变成了 dog 对象,输出结果也相应地改变了。
3. 调用函数并传递参数
除了改变函数的上下文之外,call() 和 apply() 方法还可以调用函数并且传递参数。这种情况下,我们可以使用 call() 方法传递多个参数,也可以使用 apply() 方法将参数放在一个数组中传递。
下面我们来看一个例子:
代码语言:js复制function sum(a, b, c) {
console.log(`The sum of ${a}, ${b} and ${c} is ${a b c}.`);
}
sum.call(null, 1, 2, 3);
sum.apply(null, [1, 2, 3]);
在这个例子中,我们定义了一个函数 sum,它接受三个参数,可以计算它们的和。我们使用 call() 和 apply() 方法分别调用这个函数,并且传递相同的参数。在 call() 方法中,我们将参数一个一个传递,而在 apply() 方法中,我们将参数放在一个数组中传递。这两个方法的输出结果是相同的:
代码语言:js复制The sum of 1, 2 and 3 is 6.
总结
在 JavaScript 中,call() 和 apply() 方法是非常常用的。它们可以改变函数的上下文,借用其他对象的方法,以及调用函数并且传递参数。这两个方法的区别在于参数的传递方式不同。在大多数情况下,使用 call() 方法调用函数的性能要比使用 apply() 方法调用函数的性能要好。但是,这种差别在实际应用中并不是很明显,只有在调用函数的次数非常多的情况下才会产生明显的影响。
在使用 call() 和 apply() 方法时,需要注意参数的传递方式,以及 thisArg 参数的处理方式。如果不理解这些细节,很容易出现错误。因此,我们需要认真学习这两个方法的用法,掌握它们的细节,才能更好地使用它们。
我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!