JavaScript 中的 Function.apply 和 Function.call
1. 函数是对象
JavaScript中的每个函数都有许多附加的方法,包括toString()、call()和apply()。如果您觉得函数怎么可能有自己的方法,这听起来很奇怪,但是请记住,JavaScript中的每个函数都是一个对象。
您可能还想知道函数和方法之间的区别。我相信描述符的函数和方法只是一个JavaScript约定。
函数是独立的(例如,有一个alert()函数),而方法在对象的字典中是函数,我们通过对象引用调用它们。
例如,每个JavaScript对象都有一个toString()方法,我们可以在函数对象上使用toString()方法来查看它的源代码:
function foo()
{
alert('x');
}
alert(foo.toString());
上面的执行结果是(请注意,这是一个字符串)
function foo() { alert('x'); }
2. Call()
因为函数是对象,它们可以有自己的属性和方法,我们可以像对待数据一样对待它们。
“函数当做数据”是很重要的,但是现在我们将重点讨论函数的两个方法:apply()和它的对应函数:call()。
让我们从下面的代码开始:
var x = 10;
function f()
{
alert(this.x);
}
f();
在这里,我们有一个名为f()的全局函数。f()使用this这个关键字来引用x,但是注意我们不通过对象的实例调用函数。
那么这个引用是什么对象呢? 这将引用全局对象。全局对象是我们定义变量x的地方,上面的代码是工作的,在对话框中显示值10。
call()和apply()都是我们可以用来在方法调用期间分配这个指针的方法。
例如,下面是我们如何使用call()方法:
var x = 10;
var o = { x: 15 };
function f()
{
alert(this.x);
}
f();
f.call(o);
f()的第一个调用将显示10的值,因为这引用了全局对象。
但是,第二个调用(通过 call() 方法))将显示值15。call()方法调用该函数,并将其第一个参数作为函数的主体内的这个指针。换句话说,我们已经告诉运行时,在函数f()内部执行时,要引用的对象是什么。
摆弄这个指针可能听起来很滑稽,甚至是有悖常理的,对c++、Java和c#程序员来说都是如此。
我们还可以通过call()将参数传递给目标函数:
var x = 10;
var o = { x: 15 };
function f(message)
{
alert(message);
alert(this.x);
}
f("invoking f");
f.call(o, "invoking f via call");
3. Apply()
apply()方法与call()相同,但是apply()需要一个数组作为第二个参数。该数组表示目标方法的参数。
var x = 10;
var o = { x: 15 };
function f(message)
{
alert(message);
alert(this.x);
}
f("invoking f");
f.apply(o, ["invoking f through apply"]);
apply()方法是有用的,因为我们可以不关心目标方法的签名。可以使用apply()将所有额外参数通过数组传递给目标方法。
var o = { x: 15 };
function f1(message1)
{
alert(message1 + this.x);
}
function f2(message1, message2)
{
alert(message1 + (this.x * this.x) + message2);
}
function g(object, func, args)
{
func.apply(object, args);
}
g(o, f1, ["the value of x = "]);
g(o, f2, ["the value of x squared = ", ". Wow!"]);
4. 参数
上面的方法是有效的,但是很笨拙,用户必须把参数输入到一个数组中。幸运的是,有一种方法可以简化语法,但是我们必须引入一个更重要的主题:参数标识符。
在JavaScript中,每个函数本质上都有一个可变长度参数列表。即使函数只使用一个参数,也可以将5个参数传递给函数。下面的操作没有错误,并显示“H”:
function f(message)
{
alert(message);
}
f("H", "e", "l", "l", "o");
如果我们确实想从f()中访问其他参数,我们可以使用参数关键字。参数引用一个参数对象,它有一个长度属性,感觉就像一个数组。
function f(message)
{
// message param is the same as arguments[0]
// 注意,下面是从 1 开始拼接的,因为message和 下标为 0 的数据是相同的
for(var i = 1; i < arguments.length; i++)
{
message += arguments[i];
}
alert(message);
}
// this will say "Hello"
f("H", "e", "l", "l", "o");
你知道,从技术上讲,arguments 不是一个数组,即使它的特征非常像一个数组。arguments 有一个长度属性,但没有 split、 push 或 pop 方法。
在我们之前的g()函数中,我们可以做的是将参数[1]后面的传入参数复制到我们传递的数组对象中。
var o = { x: 15 };
function f(message1, message2)
{
alert(message1 + (this.x * this.x) + message2);
}
function g(object, func)
{
// arguments[0] == object
// arguments[1] == func
var args = []; // empty array
// copy all other arguments we want to "pass through"
for(var i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
func.apply(object, args);
}
g(o, f, "The value of x squared = ", ". Wow!");
当我们调用g()时,我们可以将附加的参数作为参数传递,而不是将参数填充到数组中。