JavaScript中的call()和apply()与bind()有什么区别?

996

我已经知道applycall是相似的函数,它们都可以设置函数的上下文(this)。

不同之处在于我们发送参数的方式(手动 vs 数组)。

问题:

但是什么时候应该使用bind()方法?

var obj = {
  x: 81,
  getX: function() {
    return this.x;
  }
};

alert(obj.getX.bind(obj)());
alert(obj.getX.call(obj));
alert(obj.getX.apply(obj));

jsbin


15
如果有用户在发表回答或投票前查看主题发起人的声望积分,那并不是你的错 :) - Gabriel Llamas
81
call()apply()都是调用函数的方法,但bind()是创建一个新的函数。使用call()时,需要逐个传递参数;而使用apply()时,则需将参数作为数组传入。如需了解更多细节,请查看相关文档,它们可以完整地回答你的问题。 - Nope
4
有点奇怪,居然还没有关于这个的问题:关于这个,这可能是因为在JavaScript 1.8.5 - ECMA-262第5版中添加了bind(),而另外两个函数则已存在于JavaScript 1.3 - ECMA-262第3版中,例如关于它们的问题,Stack Overflow上有像what-is-the-difference-between-call-and-apply这样的问题。我只是猜测,因为我自己也在思考这个问题。 - Nope
这里的“call”和“Array.prototype.slice.call”中的“call”是同一个吗?后者也很难理解。 - David Spector
obj.call(arg) 和 func(arg) 一样吗?只是前者将 "this" 设置为 obj,以实现类实例方法? - David Spector
显示剩余3条评论
24个回答

906
当您希望以后使用特定上下文调用函数时,在事件中使用.bind() 。当您希望立即调用函数并修改上下文时,请使用.call().apply()call/apply会立即调用函数,而bind返回一个函数,稍后执行时,将为调用原始函数设置正确的上下文。通过这种方式,您可以在异步回调和事件中维护上下文。
我经常这样做:
function MyObject(element) {
    this.elm = element;

    element.addEventListener('click', this.onClick.bind(this), false);
};

MyObject.prototype.onClick = function(e) {
     var t=this;  //do something with [t]...
    //without bind the context of this function wouldn't be a MyObject
    //instance as you would normally expect.
};

我在 Node.js 中广泛使用它来处理异步回调,我想要传递一个成员方法,但仍希望上下文是启动异步操作的实例。

bind 的一个简单且天真的实现如下:

Function.prototype.bind = function(ctx) {
    var fn = this;
    return function() {
        fn.apply(ctx, arguments);
    };
};

这只是其中的一部分(例如传递其他参数),但您可以阅读更多相关信息并查看实际实现方式在MDN上


3
没问题,您说得对。返回的“bound”函数可以在以后使用,并且上下文将被保留。 - Chad
7
这正是 bind 返回的内容。 - Chad
5
在函数调用前,你也可以使用 bind 来为偏函数传入参数。 - Andrew Kirkegaard
3
你只是在重新实现bind,没有什么区别。无论哪种方式,你只是用闭包将它包装起来,让其可以访问保存上下文的作用域变量。你的代码基本上就是我发布的polyfill。 - Chad
1
不,arguments是函数内部可用的特殊对象。请参见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments - Chad
显示剩余11条评论

522

他们都将this附加到函数(或对象)中,区别在于函数调用(请参见下文)。

callthis附加到函数并立即执行该函数:

var person = {  
  name: "James Smith",
  hello: function(thing) {
    console.log(this.name + " says hello " + thing);
  }
}

person.hello("world");  // output: "James Smith says hello world"
person.hello.call({ name: "Jim Smith" }, "world"); // output: "Jim Smith says hello world"

bindthis绑定到函数中,需要单独调用,例如:

var person = {  
  name: "James Smith",
  hello: function(thing) {
    console.log(this.name + " says hello " + thing);
  }
}

person.hello("world");  // output: "James Smith says hello world"
var helloFunc = person.hello.bind({ name: "Jim Smith" });
helloFunc("world");  // output: Jim Smith says hello world"

或者像这样:

...    
var helloFunc = person.hello.bind({ name: "Jim Smith" }, "world");
helloFunc();  // output: Jim Smith says hello world"

applycall相似,但是它接受一个类似数组的对象而不是逐个列出参数:

function personContainer() {
  var person = {  
     name: "James Smith",
     hello: function() {
       console.log(this.name + " says hello " + arguments[1]);
     }
  }
  person.hello.apply(person, arguments);
}
personContainer("world", "mars"); // output: "James Smith says hello mars", note: arguments[0] = "world" , arguments[1] = "mars"                                     

2
这是否意味着区别在于Bind是一个闭包? - Gregory R.
3
你刚才通过代码片段向我讲解了函数内部使用的参数特性。建议在代码开头加上 "use strict",以避免覆盖这些保留关键字。+1。 - RBT
@Max同意了;我已经提交了一份编辑,其中“this”在我们使用bind/call/apply之前是错误的或没有意义。 - iono
3
谢谢您的改进建议。我稍微编辑了我的回答。@iono您的建议有一些错误,所以不能批准它,但我在回答中进行了自己的编辑。希望现在更加全面了。 - CuriousSuperhero

321
 

以最简单的形式回答

  • Call 调用函数并允许您逐个传递参数。
  • Apply 调用函数并允许您将参数作为数组传递。
  • Bind 返回一个新函数,允许您传递此数组和任意数量的参数。

应用 vs. 调用 vs. 绑定示例

调用

var person1 = {firstName: 'Jon', lastName: 'Kuperman'};
var person2 = {firstName: 'Kelly', lastName: 'King'};

function say(greeting) {
    console.log(greeting + ' ' + this.firstName + ' ' + this.lastName);
}

say.call(person1, 'Hello'); // Hello Jon Kuperman
say.call(person2, 'Hello'); // Hello Kelly King

申请

var person1 = {firstName: 'Jon', lastName: 'Kuperman'};
var person2 = {firstName: 'Kelly', lastName: 'King'};

function say(greeting) {
    console.log(greeting + ' ' + this.firstName + ' ' + this.lastName);
}

say.apply(person1, ['Hello']); // Hello Jon Kuperman
say.apply(person2, ['Hello']); // Hello Kelly King

绑定

var person1 = {firstName: 'Jon', lastName: 'Kuperman'};
var person2 = {firstName: 'Kelly', lastName: 'King'};

function say() {
    console.log('Hello ' + this.firstName + ' ' + this.lastName);
}

var sayHelloJon = say.bind(person1);
var sayHelloKelly = say.bind(person2);

sayHelloJon(); // Hello Jon Kuperman
sayHelloKelly(); // Hello Kelly King

何时使用每个方法

Call和Apply在很大程度上是可以互换的。只需决定是发送一个数组还是逗号分隔的参数列表更容易。

我总是通过记住"Call为逗号"(即逗号分隔的参数列表),而"Apply为数组"来区分它们。

Bind有点不同,它返回一个新函数。而Call和Apply则立即执行当前函数。

Bind非常有用。我们可以用它来柯里化函数,就像上面的例子一样。我们可以把一个简单的hello函数变成helloJon或helloKelly。我们也可以将它用于像onClick这样的事件中,我们不知道它们何时会被触发,但我们知道它们应该具有哪个上下文。

参考: codeplanet.io


callapply 中,如果方法内部没有 this,那么第一个参数会被赋值为 null 吗? - Daryll Santos
2
根据MDN的说明,thisArg是可选项。它提供了一个函数调用中提供的this值。请注意,这可能不是方法所看到的实际值:如果该方法是非严格模式下的函数,则null和undefined将被替换为全局对象,原始值将被转换为对象。因此,如果您在函数中不使用this,则无关紧要。 - Amit Shah
20
请问需要翻译的是什么语言到中文呢? - drlff
var person1 = {firstName: 'Jon', lastName: 'Kuperman'};function say(greeting) { console.log(greeting + ' ' + this.firstName + ' ' + this.lastName); }say.apply(person1, ['Hello']); // Hello Jon Kuperman 代码完美运行并输出 VM128:4 Hello Jon Kuperman - Pratik Joshi

271

我之前创建了这个关于函数对象、函数调用、call/applybind 的比较:

图像描述

.bind 允许你设置当前的 this 值,并且返回一个新的函数对象,因此你可以在未来执行该函数。


83

简而言之:

bind创建函数,call和apply执行函数,apply需要以数组形式传递参数

详细解释:

假设我们有一个名为multiplication的函数

function multiplication(a,b){
console.log(a*b);
}

让我们使用bind创建一些标准函数

var multiby2 = multiplication.bind(this,2);

现在,multiby2(b)等同于multiplication(2,b)

multiby2(3); //6
multiby2(4); //8

如果我在绑定时同时传递两个参数会发生什么?

var getSixAlways = multiplication.bind(this,3,2);

现在 getSixAlways() 等于 multiplication(3,2);

getSixAlways();//6

即使传递参数,也会返回6; getSixAlways(12); //6

var magicMultiplication = multiplication.bind(this);

这将创建一个新的乘法函数,并将其赋值给magicMultiplication。

哦不,我们将乘法功能隐藏在了magicMultiplication中。

调用magicMultiplication会返回一个空白的function b()

执行时它可以正常工作:magicMultiplication(6,5); //30

那使用call和apply呢?

magicMultiplication.call(this,3,2); //6

magicMultiplication.apply(this,[5,2]); //10


3
讲解得非常清楚明白! - CatalinBerta
11
简单来说,bind 创建函数,callapply 执行函数,而 apply 期望以数组形式传入参数。+1 - Josh Buchea
3
函数b是什么,为什么它是空的? - David Spector
3
@DavidSpector,它并不是功能b。这是一个函数,它采用了一个名为“b”的参数,因为函数“乘法”是使用'a'和'b'作为参数定义的。希望这有所帮助! - indieNik

57

它允许独立于函数被调用的方式设置this的值。在使用回调函数时非常有用:

  function sayHello(){
    alert(this.message);
  }

  var obj = {
     message : "hello"
  };
  setTimeout(sayHello.bind(obj), 1000);
为了用call实现相同的结果,代码应如下所示:
  function sayHello(){
    alert(this.message);
  }

  var obj = {
     message : "hello"
  };
  setTimeout(function(){sayHello.call(obj)}, 1000);

6
你之前展示的使用 .bind() 的方法是错误的。当你使用 fn.bind(obj) 时,会返回另一个函数(而不是之前创建的那个函数)。并且在绑定的函数内部无法更改 this 的值。通常情况下,这种方法用于确保回调函数中的 this 值正确。但在你的例子中 - 结果没有区别。但是要注意 fn !== fn.bind(obj); - ValeriiVasin
2
@InviS,我不理解你的评论-为什么没有区别? - jantimon
3
call 和 apply 的区别在于,使用 call 时参数需要用逗号分隔的字符串形式传递,而 apply 则可以使用数组形式传递参数。其他方面是相同的。 - Ashish Yadav
逗号分隔的字符串吗?只需要将参数以逗号分隔传递即可! - Sudhansu Choudhary

44
所有这些方法背后的主要概念是函数借用
函数借用允许我们在不复制该方法并将其维护在两个不同位置的情况下,使用一个对象的方法在另一个对象上。这可以通过使用 .call()、.apply() 或 .bind() 来实现,它们都用于显式地设置我们正在借用的方法的 this。
  1. Call 立即调用函数,并允许逐个传入参数
  2. Apply 立即调用函数,并允许将参数作为数组传入
  3. Bind 返回一个新函数,您可以随时通过调用函数来调用/执行它

以下是所有这些方法的示例

let name =  {
    firstname : "Arham",
    lastname : "Chowdhury",
}
printFullName =  function(hometown,company){
    console.log(`${this.firstname} ${this.lastname}, ${hometown}, ${company}`)
}

CALL

call方法中的第一个参数(例如name)始终是对(this)变量的引用,后面的参数将是函数变量。

printFullName.call(name,"Mumbai","Taufa");  //Arham Chowdhury, Mumbai, Taufa

申请

申请方法与调用方法相同, 唯一的区别是函数参数以数组列表形式传递。

printFullName.apply(name, ["Mumbai","Taufa"]); //Arham Chowdhury, Mumbai, Taufa

BIND

bind方法与call方法相同,只是bind返回一个函数,可以稍后调用它(不立即调用)。

let printMyNAme = printFullName.bind(name,"Mumbai","Taufa");

printMyNAme();      //Arham Chowdhury, Mumbai, Taufa

printMyNAme() 是调用该函数的函数

以下是 jsfiddle 的链接

https://codepen.io/Arham11/pen/vYNqExp


4
这是一个非常好的解释。 - Mir Stephen
谢谢 @DarioushPD - Arham Chowdhury
3
非常感谢您提供这个好的解释。 - Hamid
3
这很好解释。谢谢@ArhamChowdhury。 - Raheem Mohamed
2
如果我将数组传递给调用方法会发生什么?我已经检查过了,它可以正常工作。您能否解释一下? - Vasanth
@Vasanth,“call”不是用于传递数组参数的好选择。在上面的例子中,如果您使用“call”与数组,则会在末尾附加“undefined”。如果有多个预期参数,则“undefined”将更多。 - Arham Chowdhury

38

Function.prototype.call()Function.prototype.apply() 都可以用给定的 this 值来调用一个函数,并返回该函数的返回值。

Function.prototype.bind() 则创建了一个新的函数并设置给定的 this 值,而不执行该函数,最后返回该函数。

因此,我们来看一个如下所示的函数:

var logProp = function(prop) {
    console.log(this[prop]);
};

现在,让我们来看一个长这样的对象:

var Obj = {
    x : 5,
    y : 10
};

我们可以像这样将函数绑定到对象:

Obj.log = logProp.bind(Obj);

现在,我们可以在代码的任何地方运行 Obj.log

Obj.log('x'); // Output : 5
Obj.log('y'); // Output : 10

当你不仅为 this 绑定一个值,还为它的参数 prop 绑定一个值时,情况变得非常有趣:


Obj.logX = logProp.bind(Obj, 'x');
Obj.logY = logProp.bind(Obj, 'y');

我们现在可以这样做:
Obj.logX(); // Output : 5
Obj.logY(); // Output : 10

28

bind:它将函数与提供的值和上下文绑定在一起,但不执行该函数。要执行函数,您需要调用该函数。

call:它使用提供的上下文和参数执行函数。

apply:它使用提供的上下文和数组形式的参数执行函数。


简单而谦逊! - Mohammed H

21

这里有一篇好的文章,用来说明bind()apply()call()之间的区别,总结如下。

  • bind() allows us to easily set which specific object will be bound to this when a function or method is invoked.

    // This data variable is a global variable​
    var data = [
        {name:"Samantha", age:12},
        {name:"Alexis", age:14}
    ]
    var user = {
        // local data variable​
        data    :[
            {name:"T. Woods", age:37},
            {name:"P. Mickelson", age:43}
        ],
        showData:function (event) {
            var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1​
            console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
        }
    }
    
    // Assign the showData method of the user object to a variable​
    var showDataVar = user.showData;
    showDataVar (); // Samantha 12 (from the global data array, not from the local data array)​
    /*
    This happens because showDataVar () is executed as a global function and use of this inside 
    showDataVar () is bound to the global scope, which is the window object in browsers.
    */
    
    // Bind the showData method to the user object​
    var showDataVar = user.showData.bind (user);
    // Now the we get the value from the user object because the this keyword is bound to the user object​
    showDataVar (); // P. Mickelson 43​
    
  • bind() allow us to borrow methods

    // Here we have a cars object that does not have a method to print its data to the console​
    var cars = {
        data:[
           {name:"Honda Accord", age:14},
           {name:"Tesla Model S", age:2}
       ]
    }
    
    // We can borrow the showData () method from the user object we defined in the last example.​
    // Here we bind the user.showData method to the cars object we just created.​
    cars.showData = user.showData.bind (cars);
    cars.showData (); // Honda Accord 14​
    

    One problem with this example is that we are adding a new method showData on the cars object and we might not want to do that just to borrow a method because the cars object might already have a property or method name showData. We don’t want to overwrite it accidentally. As we will see in our discussion of Apply and Call below, it is best to borrow a method using either the Apply or Call method.

  • bind() allow us to curry a function

    Function Currying, also known as partial function application, is the use of a function (that accept one or more arguments) that returns a new function with some of the arguments already set.

    function greet (gender, age, name) {
        // if a male, use Mr., else use Ms.​
        var salutation = gender === "male" ? "Mr. " : "Ms. ";
        if (age > 25) {
            return "Hello, " + salutation + name + ".";
        }else {
            return "Hey, " + name + ".";
        }
     }
    

    We can use bind() to curry this greet function

    // So we are passing null because we are not using the "this" keyword in our greet function.
    var greetAnAdultMale = greet.bind (null, "male", 45);
    
    greetAnAdultMale ("John Hartlove"); // "Hello, Mr. John Hartlove."
    
    var greetAYoungster = greet.bind (null, "", 16);
    greetAYoungster ("Alex"); // "Hey, Alex."​
    greetAYoungster ("Emma Waterloo"); // "Hey, Emma Waterloo."
    
  • apply() or call() to set this value

    The apply, call, and bind methods are all used to set the this value when invoking a method, and they do it in slightly different ways to allow use direct control and versatility in our JavaScript code.

    The apply and call methods are almost identical when setting the this value except that you pass the function parameters to apply () as an array, while you have to list the parameters individually to pass them to the call () method.

    Here is one example to use call or apply to set this in the callback function.

    // Define an object with some properties and a method​
    // We will later pass the method as a callback function to another function​
    var clientData = {
        id: 094545,
        fullName: "Not Set",
        // setUserName is a method on the clientData object​
        setUserName: function (firstName, lastName)  {
            // this refers to the fullName property in this object​
            this.fullName = firstName + " " + lastName;
        }
    };
    
    function getUserInput (firstName, lastName, callback, callbackObj) {
         // The use of the Apply method below will set the "this" value to callbackObj​
         callback.apply (callbackObj, [firstName, lastName]);
    }
    
    // The clientData object will be used by the Apply method to set the "this" value​
    getUserInput ("Barack", "Obama", clientData.setUserName, clientData);
    // the fullName property on the clientData was correctly set​
    console.log (clientData.fullName); // Barack Obama
    
  • Borrow functions with apply or call

    • Borrow Array methods

      Let’s create an array-like object and borrow some array methods to operate on the our array-like object.

      // An array-like object: note the non-negative integers used as keys​
      var anArrayLikeObj = {0:"Martin", 1:78, 2:67, 3:["Letta", "Marieta", "Pauline"], length:4 };
      
       // Make a quick copy and save the results in a real array:
       // First parameter sets the "this" value​
       var newArray = Array.prototype.slice.call (anArrayLikeObj, 0);
       console.log (newArray); // ["Martin", 78, 67, Array[3]]​
      
       // Search for "Martin" in the array-like object​
       console.log (Array.prototype.indexOf.call (anArrayLikeObj, "Martin") === -1 ? false : true); // true​
      

      Another common case is that convert arguments to array as following

        // We do not define the function with any parameters, yet we can get all the arguments passed to it​
       function doSomething () {
          var args = Array.prototype.slice.call (arguments);
          console.log (args);
       }
      
       doSomething ("Water", "Salt", "Glue"); // ["Water", "Salt", "Glue"]
      
    • Borrow other methods

      var gameController = {
           scores  :[20, 34, 55, 46, 77],
           avgScore:null,
           players :[
                {name:"Tommy", playerID:987, age:23},
                {name:"Pau", playerID:87, age:33}
           ]
       }
       var appController = {
           scores  :[900, 845, 809, 950],
           avgScore:null,
           avg     :function () {
                   var sumOfScores = this.scores.reduce (function (prev, cur, index, array) {
                        return prev + cur;
               });
               this.avgScore = sumOfScores / this.scores.length;
           }
         }
         // Note that we are using the apply () method, so the 2nd argument has to be an array​
         appController.avg.apply (gameController);
         console.log (gameController.avgScore); // 46.4​
         // appController.avgScore is still null; it was not updated, only gameController.avgScore was updated​
         console.log (appController.avgScore); // null​
      
  • Use apply() to execute variable-arity function

Math.max是一个可变参数函数的一个例子。

// We can pass any number of arguments to the Math.max () method​
console.log (Math.max (23, 11, 34, 56)); // 56

但是如果我们有一个数字数组要传递给 Math.max,我们不能这样做:

var allNumbers = [23, 11, 34, 56];
// We cannot pass an array of numbers to the the Math.max method like this​
console.log (Math.max (allNumbers)); // NaN

这就是apply()方法帮助我们执行可变参数函数的地方。与上面不同,我们必须使用apply()传递数字数组,如下所示:
var allNumbers = [23, 11, 34, 56];
// Using the apply () method, we can pass the array of numbers:
console.log (Math.max.apply (null, allNumbers)); // 56

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接