Book of Coding


Functions as reference types

Functions are also objects in JavaScript, so it is possible to use functions like “normal” variables as arguments or as return values of other functions. Like objects, functions also have methods that can be called on corresponding function objects.


1. Use functions as arguments

  • Functions can be used in JavaScript as the argument of another function:
  • Complete Code - Examples/Part_243/main.js...
    
     // Definition of the function that expects another function as an argument
     function functionA(f) {
         console.log('Function A Start'); // Call the passed function
         f();
         console.log('Function A End');
       }
       function functionB() {
         console.log('Function B Start');
         console.log('Function B End');
       }
       // Call with a function via its name
       functionA(functionB);
       // functionA(functionB()); // This does not work.
    
       // Call with an anonymous function as argument
       functionA(function() {
         console.log('Anonymous function Start');
         console.log('Anonymous function End');
       });
    
     /* output:
     Function A Start
     Function B Start        
     Function B End
     Function A End
     Function A Start        
     Anonymous function Start
     Anonymous function End  
     Function A End
     */
                                

    The function functionA() is defined here, which expects another function as the first parameter and calls it. If another function is to be passed to this function, there are various options. On the one hand, functions can be passed via the corresponding variable that references this function. In this case, the variable functionB, which was implicitly created when the function functionB() was created. The other option is to define the function directly at the point where the argument is expected, i.e. in the argument list of the function that is called.


  • Pass a function as a parameter:
  • The next code shows a somewhat more sensible application example in which the functions are called for all elements of an array.

    Complete Code - Examples/Part_244/main.js...
    
     const numbers = [2,3,4,5];
     function any(array, f) {
       for(let i=0; i<array.length; i++) {
         f(array[i]);
       }
     }
     function print(item) {
       console.log('Item: ' + item);
     }
     function printModulo(item) {
       console.log(item + ' % 2 = ' + item % 2);
     }
     any(numbers, print); 
     /*
     output:
     Item: 2
     Item: 3  
     Item: 4  
     Item: 5 
     */
     any(numbers, printModulo);
     /*
     output:
     2 % 2 = 0
     3 % 2 = 1
     4 % 2 = 0
     5 % 2 = 1
     */
                                

    First, a function with the name any() is defined, which expects an array as the first argument and a function as the second argument, whereby this function is then called for each element in the array. In addition, two further functions are defined. The function print(), which outputs the passed argument to the console, and the function printModulo(), which outputs the modulo-2 calculation for the argument passed here. These two functions are then used as arguments for the any() function. You can see from the output that for each element in the array numbers, the function print() is called first and then the function printModulo() is called for each element.


forEach() method

The functionality implied in the any() function in the previous example works even more simply with the forEach() method provided by JavaScript. This method executes a function passed to it for each element in the array or calls it with the respective element as an argument.

Complete Code - Examples/Part_245/main.js...

 const numbers = [2,3,4,5];
 function print(item) {
   console.log('Item: ' + item);
 }
 function modulo(item) {
   console.log(item + ' % 2 = ' + item % 2);
 }
 numbers.forEach(print);
 numbers.forEach(modulo);

 /* output:
 Item: 2
 Item: 3
 Item: 4
 Item: 5
 2 % 2 = 0
 3 % 2 = 1
 4 % 2 = 0
 5 % 2 = 1
 */
                            

2. Use functions as return value

Functions can also be used as the return value of another function:

Complete Code - Examples/Part_246/main.js...

 function createAddFunction() {
   return function(x, y) {
     return x + y;
   }
 }
 const addFunction1 = createAddFunction();
 const addFunction2 = createAddFunction();
 console.log(addFunction1(22, 42));         // 64
 console.log(addFunction2(27, 13));         // 40
                        

The createAddFunction() function returns an anonymous function here, which in turn calculates the result of two numbers. The createAddFunction() function can then be called normally. The result can be assigned to different variables addFunction1 and addFunction2, which then represent different function objects and can be called as usual via their respective names.


Concatenation of function calls

If a function returns another function as a return value, you can also call the returned function directly without first assigning it to a variable:

Complete Code - Examples/Part_247/main.js...

 function sayHi() {
   console.log('Hello');
   return function() {
     console.log('World');
     return function() {
       console.log('My name is Rick Sample.');
     }
   }
 }

 sayHi();        // Call the “outer” function
 sayHi()();      // Call the “outer” and “middle” function
 sayHi()()();    // Call all functions

 /* output:
 Hello

 Hello
 World

 Hello
 World
 My name is Rick Sample.
 */
                            

The function sayHi outputs the word Hello on the console and returns an anonymous function that outputs the word World and also returns an anonymous function that in turn outputs the message My name is Rick Sample, on the console. If you now call the outermost function via sayHi(), only the first message is output. Calling sayHi()(), on the other hand, ensures that not only the outermost function, but also the function that is returned by it (middle function) is called and thus the second message is output. In turn, sayHi()()() is used to call the innermost function and thus output all three messages.


3. Standard methods for each function

Functions are objects, which means that they can contain methods. By default, each function already provides three methods: apply(), bind() and call().


Bind objects with the bind() method

Within a function, this refers to the context in which the function is called, not the context in which it was defined. However, this behavior is not always appropriate. If you pass an object method that accesses this as an argument to a function, for example as a callback handler (a function that is called by the function to which it is passed), this can lead to a runtime error.

Complete Code - Examples/Part_248/main.js...

 const button = {
   handler : null,
 // Function that expects a callback handler
   onClick : function(handler) {
     this.handler = handler;
   },
   click : function() {
     this.handler();
   }
 };
 const handler = {
   log: function() {
     console.log("Button clicked.");
   },
 // Object method that is registered below as a callback handler
   handle: function() {
     this.log();
   }
 }
 // Register the callback handler
 button.onClick(handler.handle);
 // Implicit activation of the callback handler
 button.click();
                            

The program ends with “TypeError:Object #<Object> has no method ‘log’”. The problem is the passing of handler.handle as a callback function to the onClick() method of button. As soon as this function is called within click() (this.handler()), this within the function does not refer to the object handler, but to the object button. And since this object does not have a log() method, the runtime error occurs.

The bind() function can be used to bind this for a function to a specific object. The bind() method is called on the corresponding function, whereby the object that represents the execution context is passed as an argument. If the function itself has parameters, these are simply appended at the end.

Complete Code - Examples/Part_249/main.js...

 const button = {
   handler : null,
 // Function that expects a callback handler
   onClick : function(handler) {
     this.handler = handler;
   },
   click : function() {
     this.handler();
   }
 };
 const handler = {
   log: function() {
     console.log("Button clicked.");
   },
 // Object method that is registered below as a callback handler
   handle: function() {
     this.log();
   }
 }
 // Register the callback handler
 button.onClick(handler.handle.bind(handler));
 // Implicit activation of the callback handler
 button.click();

 /* output:
 Button clicked.
 */
                            

As a result, bind() returns a new function object, but the execution context is bound to the passed object.

It looks clearer like this:

Complete Code - Examples/Part_250/main.js...

 ...
 // Register the callback handler
 const boundFunction = handler.handle.bind(handler);
 button.onClick(boundFunction);
 // Implicit activation of the callback handler
 button.click();
                            

The above problem with the runtime error can alternatively be solved using an anonymous function that is passed as a callback and controls the call to the handler object.

Complete Code - Examples/Part_251/main.js...

 ...
 // Register the callback handler
 button.onClick(function() {
   handler.handle();
 });
                            

This works because handle() is called on the handler object and this therefore refers to handler.


Call functions via the call() method

With the call() method, it is also possible to define the call context of a function. However, with call() the corresponding function is called directly and a new function object is not created as with bind(). The execution context is passed as the first argument, and further arguments can optionally be defined for the function to be called.

A common use case for using call() is to iterate over the arguments object using the forEach() method. However, this method only applies to real arrays, but arguments is not a real array and therefore the forEach method is not available or would result in an error:

Complete Code - Examples/Part_252/main.js...

 function logNames() {
     console.log(arguments);
     /* 
     arguments.forEach(function(argument) {
         console.log(argument);
     });

     output: Error: arguments is not an array 
     */
   }
   logNames("Rick", "Morty");    // output: [Arguments] { '0': 'Rick', '1': 'Morty' }
                                

However, the functionality of the forEach() method can also be used for array-like objects such as arguments. This technique is called Function Borrowing or Method Borrowing, which means something like “borrowing functions/methods”.

The easiest way to iterate over an array-like object arguments is by borrowing the method forEach():

Complete Code - Examples/Part_253/main.js...

 function logNames() {
     Array.prototype.forEach.call(arguments, function(argument) {
       console.log(argument);
     });
   }
   logNames("Rick", "Morty");    

 /*  output: 
 Rick 
 Morty
 */ 
                                

Here, the global array object or its prototype is first accessed via Array.prototype. This prototype contains all the methods that are available to the array instances as part of prototypical inheritance. The method forEach() is one of these, whereby you can access this method via Array.prototype.forEach without calling it. The actual call is only made by calling the method call(). The first argument of call() defines the quoting context, in this case the arguments object. The second argument is the one that is passed on to the forEach() method, i.e. a callback function that is called for each element in the array (here arguments object).


Call functions via the apply() method

The apply() method works in a similar way to call(). The only difference is that the arguments of the called function are not passed as individual arguments, but collected as an array:

Complete Code - Examples/Part_254/main.js...

 function logNames() {
     Array.prototype.forEach.apply(arguments, [
       function(argument) {
         console.log(argument);
       }
     ]);
   }
   logNames("Rick", "Morty");

 /* output:
 Rick
 Morty
 */ 
                                

apply() expects an array as the second argument instead of individual values.


Related links: