An Overview of JavaScript Functions
HowToCodejs
Posted on May 31, 2018
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 `;
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
}
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
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;
Strings:
return "hello";
Null:
return null;
Undefined:
return undefined;
Arrays:
return [1,2,3];
Objects:
return {one: 1, two: 2, three: 3};
Functions:
return function(){
return "I'm in a function";
}
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
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;
}
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'
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'
One important thing to note is that hoisting does not work with function expressions.
greet(); // undefined
let greet = function(){
return 'hello';
}
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';
}
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();
Note:
this
refers toperson
within our anonymous function. We could have just as easily wroteperson.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);
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
}
}
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'
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
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
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 bindthis
in a variable like we had to do earlier. Now, you just use the new syntax and, wala!, you have a functioningthis
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
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 theperson
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'
}());
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"));
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'
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'
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
Posted on May 31, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024