DEV Community

HowToCodejs
HowToCodejs

Posted on • Updated on

An Overview of JavaScript Functions

image
Visit howtocodejs.com to code along with the examples

We can wax poetics about functions and their uses all day. Instead, let's explore life with and without functions.

Life without Functions

let pets = 35;
let owners = 15;
let petsPerOwner = pets / owners;
//======Pet Info Form
let answer = prompt("how many pets do you have?");
//============
 // update based on answer, add new owner
pets += answer / 1; //  coerce string into number
owners += 1; // register new owner
petsPerOwner = pets / owners;

//test
`There are now ${petsPerOwner} pets per owner at Pet Nirvana `;

Enter fullscreen mode Exit fullscreen mode

Is that easier to read than this?

Life with functions

let pets = 35;
let owners = 15;
let petsPerOwner = average(pets, owners);
let answer = prompt("how many pets do you have?");

registerPets(answer);
registerOwner();
updateAvg(); // update based on answer, add new owner
console.log(`There are now ${petsPerOwner} pets per owner at Pet Nirvana `);


function average(total, number){
  return total / number;
}
function registerPets(newNum){
  pets += Number(newNum); // register new pet(s)
}
function registerOwner(){
  ++owners;
}
function updateAvg(){
  petsPerOwner = Math.ceil(average(pets, owners)); // find new average, round up
}
Enter fullscreen mode Exit fullscreen mode

Besides legibility, you can also see how much easier it is to do our job when we have all these built-in functions provided for us. Math.ceil rounds up and log() helps us debug code. Also, notice how the first example still uses a function for pure necessity.

Without functions, there is no JavaScript, at least all the good parts of JavaScript that we know and love.

Anatomy of a Function

function multiply(x, y){
  return x * y;
}

function // keyword for decleration
multiply // function name
(x,y)   // parameters
return x * y; // a return statement allows
              //the function to produce value

Enter fullscreen mode Exit fullscreen mode

A function has a parameter or parameters. We can name them whatever we like, just like variables. Though, we should think of parameters more like references rather than storage. We're telling the function that we're expecting some variable or data type to be plugged into this space by the user. We then operate on the parameter names within the body of the function.

More times than not, you'll want to make sure you return your expected result. Not doing so will produce undefined when you invoke the function. If you intend to use your function to set value, include the return keyword.

Return

The return statement can return any data type.

Numbers:

return 2;
Enter fullscreen mode Exit fullscreen mode

Strings:

return "hello";
Enter fullscreen mode Exit fullscreen mode

Null:

return null;
Enter fullscreen mode Exit fullscreen mode

Undefined:

return undefined;
Enter fullscreen mode Exit fullscreen mode

Arrays:

return [1,2,3];
Enter fullscreen mode Exit fullscreen mode

Objects:

return {one: 1, two: 2, three: 3};
Enter fullscreen mode Exit fullscreen mode

Functions:

return function(){
  return "I'm in a function";
}
Enter fullscreen mode Exit fullscreen mode

Invoking a function

You invoke a function by adding () to its name. If the function requires parameters, you must enter them or you'll get an error.

function multiply(x, y){
  return x * y;
}
multiply(2,2); // 4
Enter fullscreen mode Exit fullscreen mode

You can invoke a function before its declaration and it'll still work. This is called hoisting.

multiply(2,2); // 4

function multiply(x, y){
  return x * y;
}
Enter fullscreen mode Exit fullscreen mode

Function notations

When a landmark or a thing is significant in any human language, there's often more than one way to declare its name.

Fun fact: In Classical Arabic, there are hundreds of ways to name a camel.

Similarly, functions are so important to JavaScript that there are numerous names for them depending on the context in which they're used.

Function Declaration

You have the tried and true function declaration:

function greet(){
  return 'hello';
}

// we can the call or invoke this functions

greet(); // 'hello'

Enter fullscreen mode Exit fullscreen mode

Function Expression

You also have a function expression. It's called a function expression because you're assigning a function to a variable:

let greet = function(){
  return 'hello';
}

// we can still call or invoke this functions

greet(); // 'hello'

Enter fullscreen mode Exit fullscreen mode

One important thing to note is that hoisting does not work with function expressions.

greet(); // undefined

let greet = function(){
  return 'hello';
}
Enter fullscreen mode Exit fullscreen mode

Anonymous Functions

The function keyword(function()) without a name after it is called an anonymous function. Es6 introduced a new way to write an anonymous function. Instead of using the function keyword, you can delete it and add the arrow operator => to the parenthesis.

let greet = ()=>{
  return 'hello';
}


Enter fullscreen mode Exit fullscreen mode

For the most part, the difference in syntax was introduced to satisfy purists who are fond of writing minimal code. Though, the arrow function does introduce auto binding. Instead of getting overly technical, we'll show you what auto binding is later.

Anonymous functions are versatile. You can set them as a value to a key in an object literal:

let person = {
  name: "Mark",
  greet: function(){
    return 'hello' + ' ' +  this.name;   
  }
}; // end of object literal

person.greet();
Enter fullscreen mode Exit fullscreen mode

Note: this refers to person within our anonymous function. We could have just as easily wrote person.name.

Callback Functions

Anonymous functions can also go in a parameter. Doing so turns the anonymous function into what's called a callback.

//here's a function expression
let greet = (callback, times)=>{
  for(let cnt=0; cnt < times; cnt ++){
      console.log(callback()); //it doesn't return.
                              //This will cause a side effect
  }
}


//here's our anonymous func AKA callback
greet(()=>{return 'hello'}, 3);
//we could have written it like this:
greet(function(){return 'hello'}, 3);
Enter fullscreen mode Exit fullscreen mode

Note: Remember when we talked about the anatomy of a function? When you define a function, you create a mock up. The callback just takes advantage of that because we can wait for the function to arrive. We're telling the interpreter that we want to invoke the function when it arrives by attaching () to our parameter name.

Closures

A function within a function is called a closure:

// We have two functions. One is named outie and the other is named closure *wink* *wink*
function outie(){
  // this is closure's first and only outer scope
  function closure(){
   // this is closure's local scope
  }
}
Enter fullscreen mode Exit fullscreen mode

If you've been playing around with callbacks, you might have guessed correctly that a callback is also a closure. At some point during its existence, it gets called within another function.

Context:'
Now that we've started nesting functions, we should address context. Functions create their own context, which effects the this keyword, but if we wrote a closure within an anonymous function, this would refer to our function. Thus, we'd get undefined.

Here's an example:

 let person = {
  name: "Mark",
  greet: function(){    
    return function(){
          return 'hello' + ' ' +  this.name;  
    }      
  }
}
// double invoke ()() can invoke a returned closure
person.greet()();// >'hello undefined'
Enter fullscreen mode Exit fullscreen mode

To fix the problem, developers just set this to a variable to preserve the context. In other words, we're binding this. Starting to see what auto binding may entail?:

//code excerpt
greet: function(){
  let self = this;   
  return function(){
        return 'hello' + ' ' +  self.name;  
  }      
}
//end of excerpt
Enter fullscreen mode Exit fullscreen mode

An alternate solution is to explicitly call bind(this) on the closing bracket of a function.

//code excerpt
greet: function(){
  return function(){
        return 'hello' + ' ' +  this.name;  
  }.bind(this)      
}
//end of excerpt
Enter fullscreen mode Exit fullscreen mode

It looks ugly, but it works.

Pro Tip: Remember the new ()=> syntax? The example above gives a good example of why we need auto binding. Before, you had to remember to bind this in a variable like we had to do earlier. Now, you just use the new syntax and, wala!, you have a functioning this keyword. Try it out by rewriting the closure.

The final solution is to use the Es6 arrow function.

//code excerpt
greet: function(){
  let self = this;   
  return ()=>{
        return 'hello' + ' ' +  this.name;  
  }      
}
//end of excerpt
Enter fullscreen mode Exit fullscreen mode

Note: Using the arrow function on the outer anonymous function destroys context. Because the arrow function binds automatically, you will be binding this to a context outside of the person object. So, this.person would no longer work.

IIFE

A function that calls itself is called an Immediately Invoked Function Expression(IIFE).

(function(){
  return 'hello'; //'hello'
}());
Enter fullscreen mode Exit fullscreen mode

You can still do anything that you can do with other functions. You can set parameters and use the "invoker" () to feed in data.

(function(name){
  return name;   // 'hi'
}("hi"));
Enter fullscreen mode Exit fullscreen mode

You can set an IIFE to a variable, but you have to declare the name. You don't have to invoke it though.

var greet =
(function(name){
  return name;   
}("hi"));

greet // 'hi'
Enter fullscreen mode Exit fullscreen mode

Function Mania

We can use IFFE's and closures, combined with anonymous functions, to create an android.

//function expression
let android = (function(){
    //==private
    this.name = "Mark VI";
    //declaration
    function addStrings(){
       return "hello" + " " + this.name;
    }
    function setName(name){
      this.name = name;
    }
    //==public: we're just returning an object.
    return {  //anonymous functions
       setName:(name)=>{
          return setName(name);
        },    
        greet: ()=>{
            return addStrings();
        }
    }
}());//IIFE

android.setName("Raj");
android.greet(); //'Hello, I'm Raj'
Enter fullscreen mode Exit fullscreen mode

The code above takes advantage of all that functions give us to produce a functioning object. It manages its own state, meaning that any changes we make will be saved. So, if we set a new name, and tell the android to greet us, it will greet us with that new name. That's some powerful stuff! We will learn more about object oriented programming in another chapter.

Note: Oftentimes, developers wrap JavaScript code with an IFFE if they want their code to run without having to be triggered by an event.

Summary

It may be hard to keep track of all of these different type of functions, so let's list the different function types.

  • Declared functions
  • Anonymous functions
  • Callbacks
  • Closures
  • Immediately Invoked Function Expressions

Challenge: write a program that utilizes all of these different functions

Top comments (8)

Collapse
 
dallgoot profile image
dallgoot

on binding:

//code excerpt
greet: function(){
  let self = this;   
  return function(){
        return 'hello' + ' ' +  self.name;  
  }.bind(this)      
}
//end of excerpt

i think you forgot to remove the self and replace it with this, since it is bound ?

Collapse
 
howtocodejs profile image
HowToCodejs

You are absolutely correct. The mistake has been corrected. Thanks!

Collapse
 
dallgoot profile image
dallgoot

same thing on the Es6 arrow function ;)
i won't annoy you anymore ;)

Thread Thread
 
howtocodejs profile image
HowToCodejs

Don't worry. Feedback is welcome!

Collapse
 
lexlohr profile image
Alex Lohr

The syntax _=>... will take an argument _ and is not special in any way, since the underscore is considered a normal variable name character. You could also write a=>... to the same effect.

To prove there is no difference, try the following:

(_=>{}).length // 1
(a=>{}).length // 1
(()=>{}).length // 0
Collapse
 
howtocodejs profile image
HowToCodejs

Thanks for the heads up!

Collapse
 
rouzbeh84 profile image
rouzbeh

No offense at all. This is mostly correct but i think it should still be proof read. from the callback section to the iife code. for instance this block

greet: function(){
  let self = this;   
  return ()=>{
        return 'hello' + ' ' +  self.name;  
  }      
}

would no longer need the self hack as function like here:

let person = {

  name: "Mark",
  greet: function(){
    return () => `hello ${this.name}`;  
    }      
  }

Casually tossing in 'hoisting' when discussing function declaration/expression may not be great for beginners?

Collapse
 
howtocodejs profile image
HowToCodejs • Edited

Thanks. Forgot to fix that.

Well, hoisting is kind of overrated. It's not quantum physics. Though, I do get your point. In an overview of functions, I felt that it was necessary to touch on the nuances of some of these function types. It just so happens that hoisting is one of them.