65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 函数的 apply、call 和 bind

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (4投票s)

2015年10月26日

CPOL

5分钟阅读

viewsIcon

10705

你了解 JavaScript 中的 Apply 和 Call 吗?或者 Bind?它们可能会派上用场!

这周我有点时间紧张,所以会简短一些。上周我谈到了 JavaScript 中的原型,这是一个太少程序员了解的基础 JavaScript 特性。今天我想谈谈一些人们要么不知道,要么感到困惑的特性:apply()、call() 和 bind()。

像往常一样,我在 GitHubapply-call-bind 仓库 中提供了示例。

另外,如果您错过了,我有一个免费的 ReactJS 和 Flux 的 Udemy 课程。只有 50 个名额,所以您最好赶紧获取!

函数调用

所以,让我们来谈谈函数以及如何调用它们。让我们看一个简单的函数和一个简单的调用。

var sum = function (a, b) {
    return a + b;
};
var x = sum(1, 2);
console.log(x);

没有比这更简单的了,x 将具有预期的值 3。让我们稍微复杂一点。我们希望能够添加任意数量的数字!那么我们该如何处理呢?这样如何?

var sum = function (numbers) {
    var result = 0;
    numbers.forEach(function(n) {
        result += n;
    });
    return result;
};
var x = sum([1, 2, 3, 4]);
console.log(x);

这样看起来不错,对吧?不幸的是,现在调用函数变得更加困难,因为我们总是需要一个数组。如果我告诉您还有另一种方法呢?

请记住,这是 JavaScript,而 JavaScript 就是,嗯,很奇怪。我们可以用任意数量的输入参数来调用一个函数。参数太少,函数可能会失败;参数太多,多余的参数将被忽略。
尽管如此,这一切还是有道理的。在每个函数内部,您可以通过一个类数组对象 `arguments` 来检查输入参数,arguments

让我们用 arguments 列表重写 sum 的第一个版本。

var sum = function () {
    return arguments[0] + arguments[1];
};
var x = sum(1, 2);
console.log(x);

但是如果我们能做到这一点,我们就可以使用任意数量的参数!Arguments 是类数组的,这意味着我们可以遍历这些值(但它没有 forEach 方法)。

var sum = function () {
    var result = 0;
    var i = 0;
    for (i; i < arguments.length; i++) {
        result += arguments[i];
    }
    return result;
};
var x = sum(1, 2, 3, 4);
console.log(x);

这相当不错!我们现在可以使用相同的简单语法来处理任意数量或可变数量的输入参数。所以现在我要让人头疼了……我们得到了一个很好的函数,它接受我们习惯的普通简单语法的任意数量的参数,但是……我们想传递一个数组!

Apply

如果我是您的经理,您最多会感到恼火。我要求普通简单的语法,您就照做了,现在我却想要一个数组!?别担心!您的函数很棒,它仍然可以这样做。我们将使用函数原型上的一个函数(这意味着所有函数对象都有这个函数定义),apply

所以这听起来很奇怪……函数有函数?是的,它们有!请记住,JavaScript 中的函数只是一个对象。为了给您一个概念,它看起来是这样的。

myFunc(); // Invocation of a function.
myFunc.someFunc(); // Invocation of a function on a function.

所以,让我们看看这个 apply 函数,它允许您将输入参数作为一个数组传递给任何函数。Apply 有两个输入参数:您想在其上调用函数的对象(或者函数中 `this` 指向的对象),以及包含函数输入参数的数组。

所以,让我们使用 apply 来调用我们的函数 sum。请注意,第一个参数可以是 null,因为我们不是在对象上调用 sum。

var x = sum.apply(null, [1, 2, 3, 4]);
console.log(x);

这很棒,不是吗?让我们看一个不使用 arguments 变量的示例。

var steve = {
    firstName: 'Steve',
    lastName: 'Ballmer',
    doThatThing: function (what) {
        console.log(this.firstName + ' ' + this.lastName
        + ': ' + what + '! ' + what + '! ' + what + '!');
    }
};
steve.doThatThing.apply(null, ['Developers']);
steve.doThatThing.apply(steve, ['Developers']);

所以在这个例子中,doThatThing 函数只是一个常规函数,有一个常规命名的输入参数,我们仍然可以使用 apply 来调用它。在这种情况下,我们也需要传递第一个参数来设置 `this`。如果我们不指定第一个参数,函数中的 firstName 和 lastName 将为 undefined。

我们也可以将另一个变量作为第一个输入参数。

var sander = {
    firstName: 'Sander',
    lastName: 'Rossel'
};
steve.doThatThing.apply(sander, ['Blogs']);

这有点奇怪,不是吗?即使 Sander 没有 doThatThing 函数,我们也可以像 Sander 有一样调用它。正如我们将在下一节中看到的,这可能非常有用的行为!

呼叫

另一个函数,它看起来像 apply,是 call。Call 允许您简单地调用一个函数,但要求您指定充当函数内 `this` 的对象,这与 apply 非常相似。Apply 和 call 之间的唯一区别在于输入参数的提供方式。Apply 需要一个包含值的数组,而 call 需要用逗号分隔的值(就像常规函数调用一样)。

steve.doThatThing.call(null, 'Developers');
steve.doThatThing.call(steve, 'Developers');
steve.doThatThing.call(sander, 'Blogs');

那么您为什么想要这样做呢?它似乎只会使代码变得晦涩难懂……考虑以下场景,您有一个函数,它接受一个回调函数作为输入并调用它。

var someFunc = function (callback) {
    console.log('this is ' + this);
    callback('This');
};
someFunc(steve.doThatThing);

它会打印什么?Undefined undefined……当然,当 doThatThing 被作为 callback() 调用时,`this` 将是全局 Window 对象。我们现在有三个选择……我现在讨论两个,另一分钟给您第三个。
所以,首先,我们可以确保 `this` 不在回调函数中使用。为此,我们需要创建一个闭包,或者一个新函数,它在 steve 上调用 doThatThing,就像它是一个普通函数一样。

someFunc(function (what) {
    steve.doThatThing(what);
});

它有效,但代码有点臃肿,所以我们并不真正想要。所以还有第二个选择,我们允许将 `this` 对象作为输入参数传递给 someFunc。然后我们就可以使用 call(或 apply)在那个对象上调用函数了!

var someFuncThis = function (callback, thisArg) {
    callback.call(thisArg, 'This');
};
someFuncThis(steve.doThatThing, steve);

许多 JavaScript 库和框架,如 jQuery 和 Knockout.js,都使用这种模式,将 `this` 作为可选的输入参数传递。

Bind

如果您一直关注,您会记得我刚才说过有第三种方法来解决回调函数中 `this` 的小问题。除了 apply 和 call 函数,还有一个 bind 函数。Bind 是一个接受对象作为输入参数并返回另一个函数的函数。返回的函数将原始函数与输入对象作为 `this` 上下文一起调用。让我们看一个例子。

someFunc(steve.doThatThing.bind(steve));
someFunc(steve.doThatThing.bind(sander));

即使您的原始 `this` 不是函数本身,它也有效。

var f = steve.doThatThing;
f = f.bind(steve);
someFunc(f);

当将 JavaScript 原生函数作为回调函数传递给第三方库时,您将需要此功能。

someFunc(console.log);
someFunc(console.log.bind(console));

第一次调用会因为“非法调用”而失败,因为 `log` 函数内的 `this` 必须是 console,但在作为回调函数调用时却不是。第二行之所以有效,是因为我们将 `this` 绑定到了 console。

所以这里有三个函数,它们都以与您习惯的略有不同的方式调用函数。然而,对于严肃的 JavaScript 开发,这三个函数都是必不可少的。

这篇博文比以往要短一些,但是,正如他们所说,数量不如质量。希望下周再见!

祝您编码愉快!

帖子 JavaScript 函数的 Apply、Call 和 Bind 最初发布于 Sander's bits

© . All rights reserved.