https://hangar.runway7.net/javascript/difference-call-apply
https://javascript.info/bind
call
When invoking functions with call:
1) the 1st object is used as the ‘this’
2) additional arguments used with call will act as the function’s parameters.
Note that the first argument to call () sets the ‘this’ value. The other arguments after the first argument are passed as parameters to the avg () function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
//global variable var avgScore = "global aveScore"; //global function function ave (arrayOfScores) { console.log("--> ave"); var sumOfScores = 0; for (score in arrayOfScores) { console.log("adding score: " + arrayOfScores[score]); sumOfScores += score; } // whenever we declare a property on this object in a function, // it is bound to the global object this.aveScore = sumOfScores / arrayOfScores.length; console.log("ave: " + sumOfScores / arrayOfScores.length); console.log("<-- ave"); } var gameController = { scores : [20, 34, 55, 46, 77], avgScore: null }; //ave(gameController.scores); //console.log("global.aveScore: " + global.aveScore); // 236.8 //console.log("gameController.aveScore: " + gameController.aveScore); //undefined // using CALL // first parameter is used as the reference to this // second parameter acts as the 1st argument passed in ave.call(gameController, gameController.scores); console.log("global.aveScore: " + global.aveScore); // undefined console.log("gameController.aveScore: " + gameController.aveScore); // 246.8 |
another example for call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
"use strict" var person1 = {name: 'Marvin', age: 42, size: '2xM'}; var person2 = {name: 'Zaphod', age: 42000000000, size: '1xS'}; var sayHello = function(){ console.log('Hello, ' + this.name); }; var sayGoodbye = function(){ console.log('Goodbye, ' + this.name); }; sayHello.call(person1); sayGoodbye.call(person2); sayHello.apply(person1); sayGoodbye.apply(person2); |
The functions sayHello, sayGoodybye are run with the ‘this’ context set to objects person1 and person2. The ‘this’ in the function will reference those objects.
1 2 3 4 5 |
sayHello.call(person1); sayGoodbye.call(person2); sayHello.apply(person1); sayGoodbye.apply(person2); |
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.
In other words, both call and apply perform very similar functions: they execute a function in the context, or scope, of the first argument that you pass to them. Also, they’re both functions that can only be called on other functions.
The difference is when you want to seed this call with a set of arguments. Say you want to make a say() method that’s a little more dynamic:
call would be:
1 2 3 4 5 6 |
var say = function(greeting){ alert(greeting + ', ' + this.name); }; say.call(person1, 'Hello'); say.call(person2, 'Goodbye'); |
It runs the function in the context of the first argument, and subsequent arguments are passed in to the function to work with. So how does it work with more than one argument?
1 2 3 4 5 6 7 |
var update = function(name, age, size) { this.name = name; this.age = age; this.size = size; }; update.call(person1, 'Slarty', 200, '1xM'); |
difference between call and apply
Both can be called on functions, which they run in the context of the first argument. In call the subsequent arguments are passed in to the function as they are, while apply expects the second argument to be an array that it unpacks as arguments for the called function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
"use strict" var person1 = {name: 'Marvin', age: 42, size: '2xM'}; var person2 = {name: 'Zaphod', age: 42000000000, size: '1xS'}; var sayHello = function(){ console.log('Hello, ' + this.name); }; var sayGoodbye = function(){ console.log('Goodbye, ' + this.name); }; sayHello.call(person1); sayGoodbye.call(person2); sayHello.apply(person1); sayGoodbye.apply(person2); var update = function(name, age, size) { this.name = name; this.age = age; this.size = size; console.log(name + ", " + age + ", " + size); }; update.call(person1, 'Slarty', 200, '1xM'); // parameters update.apply(person1, ["Ricky", 37, "1xL"]); // array |
Bind
Problem: Losing “this”
We already know that in JavaScript it’s easy to lose this. Once a method is passed somewhere separately from the object – ‘this’ is lost.
Here’s how it may happen with setTimeout:
1 2 3 4 5 6 7 8 9 10 11 |
"use strict" let user = { firstName: "John", sayHi() { console.log("Hello, " + this.firstName); } }; let f = user.sayHi; f(); // "this" is undefined |
As we can see, the output shows not “John” as this.firstName, but undefined!
That’s because f got the function user.sayHi, separately from the object. The last line can be rewritten as:
solution:
REMEMBER that functions bind objects. Hence, we get the function, bind an object to it, and run that function with ().
1 2 3 4 5 6 7 8 9 10 |
let user = { firstName: "John" }; function func() { alert(this.firstName); } let funcUser = func.bind(user); funcUser(); // John |
Here func.bind(user) as a “bound variant” of func, with fixed this=user.
parameters are passed as usual
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
"use strict" let user = { firstName: "John" }; function func(phrase) { console.log(phrase + ', ' + this.firstName); } // bind this to user let funcUser = func.bind(user); funcUser("Hello"); |
Now let’s try with an object method. The object is user, its method is sayHi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
"use strict" let user = { firstName: "John", sayHi() { console.log("Hello, " + this.firstName); } }; let user2 = { firstName: "Ricky" } let sayHi = user.sayHi.bind(user2); // (*) sayHi(); setTimeout(sayHi, 1000); |
In the line (*) we take the method user.sayHi and bind it to user. The sayHi is a “bound” function, that can be called alone or passed to setTimeout – doesn’t matter, the context will be right.
Here we have the object ‘user’, its function ‘say’. ‘say’ has 1 argument ‘phrase’ which can be passed in normally after the bind.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
"use strict" let user = { firstName: "John", say(phrase) { console.log(phrase + ", " + this.firstName); } }; let say = user.say.bind(user); // first bind the object say("Hello"); // Hello, John ("Hello" argument is passed to say) say("Bye"); // Bye, John ("Bye" is passed to say) |
Binding all functions in an object
If an object “user” has many methods and we plan to actively pass it around, then we could bind them all in a loop.
We use a for loop to go through all the keys in the object user. When the typekey is a function, it means we have stepped up to a function object. That means we have the reference to the function object. In turn, we then use the bind method to bind it to the object we want.
1 2 3 4 5 |
for (let key in user) { if (typeof user[key] == 'function') { user[key] = user[key].bind(user); } } |
A function cannot be re-bound
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
"use strict" function Ricky() { this.name = "Ricky"; this.sayHi = function () { console.log( this.name ); } } var ricky = new Ricky(); ricky.sayHi(); // bind ricky's say Hi to another object with property name john var bound = ricky.sayHi.bind({ name: "John" }); bound(); var bound2 = bound.bind({ name: "Alice" }); bound2(); // still John |
The exotic bound function object returned by f.bind(…) remembers the context (and arguments if provided) only at creation time.
Note that the bind from a function to an object will always hold. It’s the returned object that CANNOT be bind again.
In the below example, we see that we try to bind var bound again, but the output is still John. However, calling bind on a function will always give you a successful bind.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// bind ricky's say Hi to another object with property name john var bound = ricky.sayHi.bind({ name: "John" }); var bound2 = bound.bind({ name: "Alice" }); bound2(); // John bound2 = ricky.sayHi.bind({ name: "Alice" }); bound2(); // alice |