原创

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()时,我们可以将附加的参数作为参数传递,而不是将参数填充到数组中。

正文到此结束
本文目录