Using The .apply.bind() Combo in Javascript
November 9th, 2014 byContext in Javascript is a powerful concept that is very particular to the language, and is usually determined by how a function is invoked. Being able to control it can open the spectrum for very interesting patterns. One way you can achieve context manipulation is by using the .bind() method, which allows us to create a new function with a new context (or ‘this’). Another way of changing the context is by using the .apply() method. This one rather than returning a function, it calls the function with a different context passed as a first argument, and an array as the second.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var obj = { label: 'the sum is ' }; function addUp(a, b, c){ var sum = a + b + c; return this.label + sum; } // .bind(), return a new function with a different the context calculateSum = addUp.bind(obj); calculateSum(4, 4, 4); // returns: the sum is 12 // .apply(), directly call the method passing context and array: addUp.apply(obj, [4, 4, 4]); // returns: the sum is 12 |
To make this more interesting, I want to create a function that combines the features of both methods. To achieve this, we can call .bind() on top of apply, resulting in a function which is basically .apply() with its first argument fixed to whatever we want to. In this case
obj.
1 2 3 |
var addNumbers = Function.prototype.apply.bind(addUp, obj); addNumbers([5, 5, 5]); // 15 |
So now we have both functionalities mixed into one function. The context being applied and the ability to pass an array that will get deconstructed into separate arguments. Let’s look at a bit more elaborated example now.
Simulating a cake maker
To play a bit more with the apply.bind combo, we’re gonna create a factory that generates methods to simulate a basic process of baking cakes. First we’re gonna define a list of base ingredients that we’ll be re-using each time we want to bake a cake, so we don’t have to add them every time. For the list of common ingredients, we’ll define an object that contains an array that will be shared among all generated objects. The key here will be to inject the list of ingredients as context to the cakeMaker function. So each time we create a new cake, that list of base ingredients will always be available by default.
1 2 3 |
var sharedData = { ingredients: ['all-purpose flour', 'unsalted butter', 'sugar'] }; |
We’ll now define the main function that holds all the necessary methods to execute each part of the baking process.
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 |
function CakeFactory(cakeType, people, calories){ var ingredients = this.ingredients; return { // adds cake specific ingredients based on the cake type add : function(moreIngredients){ ingredients = ingredients.concat(moreIngredients); return this; }, // run through the list of ingredients and mix them mix : function(){ var output = ''; ingredients.forEach(function(item){ output +=item + ', '; }); console.log('Mixed ingredients: ' + output.slice(0, -2)); return this; }, bake : function(){ console.log('Your ' + cakeType + ' with ' + calories + ' calories for ' + people + ' people, is now baking'); } }; } |
Next up, via the same methodology of the last example, the function will get that list of common attributes injected via the ‘apply.bind’ to make them available across any generated function as part of their context.
1 2 |
// create factory var cakeMaker = Function.prototype.apply.bind(CakeFactory, sharedData); |
With the created generator, we can now create different types of cakes based on a set of initial preferences and additional ingredients.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var croqueCake = cakeMaker(['Croquembouche', 5, 350]); var additionalIngredients = ['chocolate', 'almond', 'caramel']; croqueCake.add(additionalIngredients).mix().bake(); // returns: // "Mixed ingredients: all-purpose flour, unsalted butter, sugar, chocolate, almond, caramel" // "Your Croquembouche with 350 calories for 5 people is baking" // A new one var croqueCake = cakeMaker(['BirthdayCake', 10, 450]); var additionalIngredients = ['vanilla', 'strawberries', 'cream']; croqueCake.add(additionalIngredients).mix().bake(); // returns: // "Mixed ingredients: all-purpose flour, unsalted butter, sugar, vanilla, strawberries, cream" // "Your BirthdayCake with 450 calories for 10 people is baking" |