call()
和 apply()
使用给定的 this
值和参数来调用指定的函数。
Function.prototype.call()
Function.prototype.apply()
它两的功能几乎相同,只是参数不同。
Copy // call() 接受参数列表
fn .call ( this , 'apples' , 'bananas' );
// apply() 接受单个参数数组/类数组对象
fn .apply ( this , [ 'apples' , 'bananas' ]);
fn .apply ( this , arguments);
fn .apply ( this , { 'length' : 2 , '0' : 'eat' , '1' : 'bananas' });
再配合函数的 arguments
参数和剩余参数 (...args) => {}
,以及展开语法 ...
,这两个函数可以说是一模一样。文中的所有例子,call()
和 apply()
都可以相互替代。
1. 语法
Copy // call
call ();
call (thisArg);
call (thisArg , arg1 , /* …, */ argN);
// apply
apply (thisArg);
apply (thisArg , argsArray);
参数 thisArg
:在调用函数时使用的 this
值
参数列表 arg1, ..., argN
(可选),an argument list
参数数组或类数组 argsArray
(可选),a single array of arguments
类数组对象:有 length
属性,支持整数下标索引 []
返回值:使用指定的 this
值和参数调用函数的结果
对于一个现有的函数,我们可以给它分配一个不同的 this
对象来进行调用。
有了 call()
和 apply()
,一个对象的函数或方法就能被另外一个不同的对象来调用了,这样就能让我们只编写一次方法,然后在另一个对象中直接使用。
2. 常见用法
2.1 对象的构造函数
使用 call
可以链接到对象的构造函数(类似于 Java)。
Copy // Product (对象)(的构造)函数
function Product (name , price) {
this .name = name;
this .price = price;
}
function Food (name , price) {
Product .call ( this , name , price); // 调用 Product()
this .category = "food" ;
}
function Toy (name , price) {
Product .call ( this , name , price); // 调用 Product()
this .category = "toy" ;
}
const rice = new Food ( "米饭" , 5 );
console .log ( rice .name , rice .price , rice .category); // 米饭 5 food
console .log (rice instanceof Food ); // true
console .log (rice instanceof Product ); // false
const robot = new Toy ( "机器人" , 40 );
console .log ( robot .name , robot .price , robot .category); // 机器人 40 toy
console .log (robot instanceof Toy ); // true
console .log (robot instanceof Product ); // false
以上代码,完成了 Food 和 Toy 对 Product 的继承。
2.2 调用函数
eg1. 调用函数,并指定上下文
Copy function hi () {
console .log ( ` ${ this .animal } 通常睡 ${ this .sleepDuration } ` );
}
const obj = {
animal : "cats" ,
sleepDuration : "12 hours"
};
hi .call (obj); // cats 通常睡 12 hours
eg2. 调用函数,且没有传第一个参数
Copy var x = "outer" ;
function display () {
console .log ( this .x);
}
display .call ();
// 非严格模式,输出 outer
// 严格模式,TypeError: Cannot read properties of undefined (reading 'x')
eg3. 调用匿名函数
Copy const animals = [
{ species : '熊猫' , name : '阿宝' } ,
{ species : '狮子' , name : '辛巴' }
];
function log (i) {
this . print = function () {
console .log ( `# ${ i } ${ this .species } : ${ this .name } ` );
}
this .print ();
}
for ( let i = 0 ; i < animals . length ; i ++ ) {
log .call (animals[i] , i);
}
// #0 熊猫: 阿宝
// #1 狮子: 辛巴
2.3 合并数组
我们可以使用 Array.prototype.push()
把一个或多个元素添加到数组的末尾。但如果给它传一个数组, push()
将会把这个数组作为一个整体(即单个元素),而不是依次添加数组里的元素。
而已有的 Array.prototype.concat()
是创建并返回一个新数组,而不是在现有的数组上追加。
在这种情况下,我们就可以使用 apply()
将数组隐式地“展开”成一系列的参数列表。
Copy const array = [ "a" , "b" ];
const elements = [ 0 , 1 , 2 ];
// apply()
array . push .apply (array , elements);
console .info (array); // ['a', 'b', 0, 1, 2]
// 等价写法
array .push ( ... elements);
console .info (array); // ['a', 'b', 0, 1, 2, 0, 1, 2]
2.4 内置函数
巧妙地使用 apply()
可以让我们更灵活地使用内置函数。
Copy // 找出数组里的 max 和 min
const numbers = [ 3 , 10 , 9 , 1 , 8 ];
// 1. 传统做法之循环
let max = - Infinity ;
let min = + Infinity ;
for ( let num of numbers) {
if (num > max) max = num;
if (num < min) min = num;
}
console .log (max , min);
// 2. apply() 和 call()
max = Math . max .apply ( null , numbers);
min = Math . min .apply ( null , numbers);
console .log (max , min); // 10 1
max = Math . max .call ( null , ... numbers);
min = Math . min .call ( null , ... numbers);
console .log (max , min); // 10 1
// 3. spread syntax
max = Math .max ( ... numbers);
min = Math .min ( ... numbers);
console .log (max , min); // 10 1
需要注意的是,通过这种方式使用 apply()
和 call()
,可能会超出 JavaScript 引擎的参数长度限制,比如 JavaScriptCore 引擎的硬编码参数限制是 65536。所以若会超长,可以考虑先将数组分块。
3. 主要参考