JavaScript Functions

JavaScript Functions: The Basics

Differences between C++ and JavaScript functions:

  • No type needs to be defined in JavaScript functions - neither in the signature, nor among the arguments!
  • The return keyword is used! Most commonly, we define values/variables to achieve what we want to yield from the function, and then return them.
  • While not a difference, it is very common to pass a function as an argument in JavaScript

Quick Quiz: Scope in JavaScript

The code for the Show Answer below is from one of the answers posted here. The code used in the JavaScript file is currently (slightly) beyond my scope!

  1. What is printed to the console when this code runs?
                                
                                    let animal = "Giant Pacific Octopus";
                                    function observe(){
                                        let animal = "Pajama Squid";
                                        console.log(animal);
                                    }
                                    observe();
                                
    Pajama Squid
  2. What is the result of running the following code?
                                
                                    const creature = "Common Sea Dragon";
     
                                    function scubaDive(){
                                        const creature = "Spanish Dancer"; //A type of sea slug
                                        console.log(creature);
                                    }
     
                                    scubaDive();
                            
    Spanish Dancer! Remember, the two "creature" variables are not in the same scope! If they were, we would see an error!
  3. What two values are printed to the console?
                                
                                    let deadlyAnimal = "Blue-Ringed Octopus";
    
                                    function handleAnimal() {
                                    let deadlyAnimal = "Scorpionfish";
                                    console.log(deadlyAnimal);
                                    }
     
                                    handleAnimal();
                                    console.log(deadlyAnimal)
                            
    Scorpionfish Blue-Ringed Octopus
  4. What JavaScript was used for the answer button? (BONUS!)
    
                            
                            
                                    
        function myFunction($event) {
            console.log(event);
            let temp = event.target;
            let temp1 = event.target.nextElementSibling;
            console.log(temp);
            console.log(temp1);
            if (temp1.className == "answerBlock") {
            temp1.classList.remove("answerBlock");
            temp.innerHTML = 'Show Answer';
            } else {
            temp1.classList.add("answerBlock");
            temp.innerHTML = 'Hide Answer';
            }
        }
    

Regarding Scope and const/let/var

There's an interesting difference between the use of var and let/const in JavaScript. If we use let/const, our variables generally stay within their scopes. For example, in a for-loop for(let i = 0; i < 4; i++), the i variable wouldn't be accessible outside of the block over which it iterates. However, if we use for(var i = 0; i < 4; i++), we would be able to output i later (it, presumably, would be 4).

  • 'var' is function-scoped, meaning that a variable declared with 'var' within a function is only accessible within that function. Variables declared with 'var' are also hoisted, meaning that they are moved to the top of their scope, and can be accessed before they are declared in the code.
  • 'const' is used to declare a variable that cannot be reassigned, while 'let' is used to declare a variable that can be reassigned. Both 'const' and 'let' are block scoped, meaning that a variable declared with 'const' or 'let' within a block is only accessible within that block.
  • In modern JavaScript, it is generally recommended to use 'const' by default, and only use 'let' when you know a variable needs to be reassigned. 'var' is not considered best practice in modern JavaScript, and it is recommended to avoid using it.

Function Expressions

In JavaScript, functions are considered as values! In this language, we can return functions as values, just as we return numbers!

  • Definition: a function expression is a way to define a function and assign it to a variable.
  • The most basic example is shown in the code below. Notice how a function expression can be anonymous, or it can have a named identifier. An anonymous function expression is a function without a name, and it is usually assigned to a variable like below. So, we created a function expression and assigned it to the variable add. In this example, the function function(a, b) { return a + b; } is an anonymous function expression, and it is assigned to the variable add. This function can be invoked by calling add(1, 2), which would return 3.
                            
                                const add = function(a, b) {
                                    return a + b;
                                };
                                console.log(add(1, 2)); 
                        
  • A named function expression is a function that has a name, and it is also assigned to a variable. The name of the function is only accessible within the function's scope. In this example, the function function multiply(a, b) { return a * b; } is a named function expression, and it is assigned to the variable multiply. This function can be invoked by calling multiply(2, 3), which would return 6. It is important to note that a named function expression cannot be invoked before it is defined, unlike function declaration.
                            
                                let multiply = function multiply(a, b) {
                                    return a * b;
                                }
                        
  • Function Expressions are commonly used in JavaScript when a function is defined inside another function, or if the function is used only in a specific scope. It is also useful when you want want to assign a function to a variable and pass it as an argument to another function or return it from a function.

Function Expressions: Advanced Topics

  • Advanced Topic 1: Function expressions could also be used with IIFE(Immediately-Invoked Function Expressions) which allows us to execute a function immediately after it is defined.
  • Advanced Topic 2a: Another way of writing function expressions is by using the arrow function notation, which is more concise and also introduced in ECMAScript 6 (ES6). Here is an example of the same function expression written with arrow function notation:
                            
                                const sum = (a, b) => a + b;
                        
  • Advanced Topic 2b: Function expressions are used frequently in JavaScript, and one of the most common use cases is to pass a function as an argument to another function, like setTimeout or Array.prototype.map. They also can be used for creating closures (see next point!), creating functions on the fly, or to use it as a return value. In this example, we defined two functions multiply and divide as function expressions, and then we defined another function calculator which takes 3 arguments a, b and operator which is a function, and it applies the function to a and b and return the result.
                            
                                const multiply = (a, b) => a * b;
                                const divide = (a, b) => a / b;
                                const calculator = (a, b, operator) => operator(a, b);
                                console.log(calculator(2,3,multiply))
                                console.log(calculator(10,5,divide))
                        
  • Advanced Topic 3: In JavaScript, a closure is a function that has access to the variables in its parent scope, even after the parent function has returned. A closure is created when a function is defined inside another function, and the inner function has access to the variables of the outer function. Here is an example:
                            
                                function outerFunction(x) {
                                    let innerVariable = x;
                                  
                                    return function innerFunction() {
                                      console.log(innerVariable);
                                    }
                                  }
                                  
                                  const myClosure = outerFunction(10);
                                  myClosure(); // logs "10"
                        
    In this example, the outerFunction takes in a parameter x and assigns it to the variable innerVariable. Then it returns the function innerFunction which has access to the innerVariable and it logs the innerVariable value when we call it. We assigned the outerFunction to the variable myClosure and when we call myClosure() it returns the innerFunction and logs the innerVariable value which is 10. Closures are useful for a number of different tasks, such as:
    1. Creating private variables that can only be accessed by privileged functions.
    2. Creating function factories that return different functions with different behavior.
    3. Implementing data privacy and encapsulation
    4. Creating and returning function with an already set context or scope.
    In the above example we are using a closure to create a factory function that returns a new function that has access to the innerVariable of the outer function, even after the outer function has returned.
  • Higher Order Functions

    A higher order function is a function that takes one or more functions as arguments, and/or returns a function as its result.

    In JavaScript, functions are first-class citizens, meaning that they can be treated just like any other data type, such as a number or a string. This means that you can pass functions as arguments to other functions, return functions as the result of other functions, and assign them to variables.

    Let's look at a simple example:

                            
                                function callTwice(func){
                                    func();
                                    func();
                                }
    
                                function rollDie(){
                                    const roll = Math.floor(Math.random() * 6) + 1;
                                    console.log(roll);
                                }
    
                                callTwice(rollDie);
                            
                        
    Here, we're passing the rollDie function as an argument. Important: we use rollDie(), rather than use parentheses! We don't want to pass the return value from the function we're passing: rather, we pass the function itself, which is then called by the calling function! The above example executes the rollDie() function twice.

    Higher Order Functions: Returning Functions

    So, a higher-order function can also return a function!. Here's another example of a simple higher-order function that takes a function and a number as arguments, and returns a new function that multiplies the number by the result of the original function:

                            
                                function multiplyBy(factor) {
                                    return function(number) {
                                      return number * factor;
                                    };
                                  }
                                  
                                  const double = multiplyBy(2);
                                  console.log(double(5)); // logs 10                              
                            
                        
    In this example, multiplyBy is a higher-order function because it takes a function as an argument (in this case, the function is just number) and returns a new function as its result. We assigned the returned function to the variable double and when we call double(5) it returns 5*2=10 Higher-order functions are a powerful feature of JavaScript and are used frequently in functional programming. They allow you to write reusable and composable code that can be easily understood, tested, and maintained. Some examples of higher-order functions that you might use in JavaScript include Array.prototype.map, Array.prototype.filter, Array.prototype.reduce and setTimeout which take a function as an argument and returns a new function as a result. In summary, a higher-order function is a function that either takes one or more functions as arguments or returns a function as a result. They allow you to write reusable and composable code and are widely used in JavaScript.

    In the course, the following example is used:

                            
                            function makeMysteryFunc(){
                                const rand = Math.random();
                                if(rand>0.5){
                                    return function(){
                                        console.log('Congrats!  I'm a good function!');
                                        console.log('You win a million dollars!');
                                    }
                                }else{
                                    return function(){
                                        alert('Uh oh!  You have been infected by a virus!');
                                        alert('Stop trying to close this window!')
                                        alert('Stop trying to close this window!')
                                        alert('Stop trying to close this window!')
                                        alert('Stop trying to close this window!')
                                        alert('Stop trying to close this window!')
                                    }
                                }
                            }
                            
                        
    So, using something like:
                            
                                let mystery = makeMysteryFunc();
                            
                        
    ...would allow us to call mystery() itself, and see what we got: the function was returned by the makeMysteryFunc function!

    A better example is illustrated with this function:

                            
                                function makeBetweenFunct(min,max){
                                    return function(num){
                                        return num>=min && num<=max;
                                    }
                                }
                            
                        
    This function takes in two arguments (min and max), and return a function that can be used to verify if a value falls between the specified min and max values. For example, we could define some specific age ranges (ages correct in 2023*) like so:
                        
                            function (note how the keyword isn't needed)
                            function const genAlpha = makeBetweenFunct(0,13);
                            const genZ = makeBetweenFunct(14,26);
                            const millenial = makeBetweenFunct(27,42);
                            const genX = makeBetweenFunct(43,58);
                            const boomer = makeBetweenFunct(59,77);
                            const silentGen = makeBetweenFunct(78,94);
                            const greatestGen = makeBetweenFunct(95,120);
                        
                    
    We can call these various functions, and check out what they return!
                        
                            genZ(25) returns true!
                            millenial(44) false
                            genX(69) false
                            boomer(100) false
                            silentGen(92) true
                            greatestGen(37) false
                        
                    

    Methods

    In JavaScript, a method is a function that is a property of an object. In C++, a member function is found in a class, and is similar! Both define behavior for objects, and they can be called on an instance of the class or object! (But let's focus on the JavaScript!!!)

    In JavaScript, you can define a method as a property of an object, like this:

                            
                                const myObject = {
                                    myMethod: function() {
                                        // method logic here
                                    }
                                };
    
                                ANOTHER EXAMPLE:
                                const myMath = {
                                    PI:3.14159;
                                    square:function(num){
                                        return num*num;
                                    },
                                    cube:function(num){
                                        return num*num*num;  (Or num**3) ** being like ^
                                    }
                                }
                            
                        
    We can call these using myMath.PI (as one example), or myMath.square(5) (giving, hopefully, 25!).

    One key thing: objects are everywhere in JavaScript! An array is, by itself, technically an object!

    1. Define an object called square, which will hold methods that have to do with the geometry of squares. It should contain two methods, area and perimeter. area should accept the length of a side (all sides are the same in a square) and then return the side squared. perimeter should accept the length of a side and return that side multiplied by 4.
      
                                  
                                  
      const square={
          area:function(num){
              return num*num;
          },
          perimeter:function(num){
              return num*4;
          }
      };

    The this keyword

    this refers to the object that the current function or method is operating on. It is often used to access properties and methods of said current object.

    In this example, the 'sayHello' method uses the this keyword to access the name property of the person object, and prints 'Hello, my name is Bobby'

                            
                                let person = {
                                    name: 'Bobby',
                                    age: 25,
                                    sayHello: function() {
                                        console.log('Hello, my name is '' + this.name);
                                    }
                                };
                                
                                person.sayHello(); // Output: 'Hello, my name is Bobby'                            
                            

    Unfortunately, it's not all that simple! this's value changes depending on how the function is called.

  • When a function is called a method of an object, this refers to the object!
  • When a function is called as a standalone function, this refers to the global object: in the browser, this is the window object.
  • The Window Object?

    The window object can be thought of as the browser window. It's the blobal object in web browsers, and is automatically created by the browser: and available to all JS code running on the page.

    The window object refers to various browser-related functionalities, such as the URL, web page's history, and the ability to create/manipulate browser windows and dialogs.

    It also provides access to the document object, which represents the web page's Document Object Model (DOM), and allows you to manipulate the elements of the page.

    For example, this code uses window.location to redirect the browser to a new URL:

                            
                                window.location = "https://www.backToWorkQuick.com";
                            

    This one uses the window.alert() method to display an alert dialog with a message:

                                
                                    window.alert("Greetings Friend!");
                                

    NOTE: window is only available in the browser environment, and not in other JS environments like node.js

    Try/Catch: Error Handling

    The try...catch statement is comprised of a try block and either a catch block, a finally block, or both. The code in the try block is executed first, and if it throws an exception, the code in the catch block will be executed. The code in the finally block will always be executed before control flow exits the entire construct.

                            
                                try {
                                    nonExistentFunction();
                                  } catch (error) {
                                    console.error(error);
                                    // Expected output: ReferenceError: nonExistentFunction is not defined
                                    // (Note: the exact output may be browser-dependent)
                                  }                              
                            

    Another example:

                                
                                    function yell(msg){
                                        try{
                                            console.log(msg.toUpperCase().repeat(3));
                                        }catch(e){
                                            console.log(e);
                                            console.log("Please pass a string next time!");
                                        }
                                    }