Skip to content

Instantly share code, notes, and snippets.

@ckipp01
Last active August 22, 2019 07:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ckipp01/45fafccecf06c982938c0e7206cd4529 to your computer and use it in GitHub Desktop.
Save ckipp01/45fafccecf06c982938c0e7206cd4529 to your computer and use it in GitHub Desktop.
Javascript function examples
'use strict'
/*
This is meant to be a concise explanation on the difference between
functions declaration, function expressions, and es6 fat arrow functions
You should be able to just clone this or paste it in a file and run `node <filname>`
*/
/*
Ex. 1
Traditionally, you probably would have seen function declarations
the most. That's what you are seeing below if you find where add5
is defined. Key features of a function declaration is that the function
is hoisted. This means that the function declaration is loaded before any
code is executed and available in your program before you reach the line
where it's defined.
*/
log('Ex. 1', add5(3))
/*
Ex. 2
The biggest difference with a function expression is that the function
definition is not hoisted. Therefore when we try to call expressionAdd5
below it won't be able to call the function until after creation, meaning that
it's not available until the line of code that creates it has ran.
*/
try {
expressionAdd5(2)
} catch (err) {
log('Ex. 2', err.message)
}
/*
Ex. 3
es6 arrow function expressions are also not hoisted.
Therefore calling es6ArrowAdd5 here will also result
in a reference error
*/
try {
es6ArrowAdd5(2)
} catch (err) {
log('Ex. 3', err.message)
}
// function declaration
function add5 (num) {
return num + 5
}
// function expression
const expressionAdd5 = function (num) {
return num + 5
}
// es6 function expresssion / es6 arrow function / fat arrow funciton
const es6ArrowAdd5 = num => {
return num + 5
}
/*
Ex. 4 & Ex. 5
Now that we've passed where the two function expressions have been
created above and assigned to variables, we can reference them below
with no issue
*/
log('Ex. 4', expressionAdd5(3))
log('Ex. 5', es6ArrowAdd5(3))
/*
Ex. 6
There are also two ways to write a pre-es6 function expression.
The way we did it above with expressionAdd5 was actually an
anonymous function expression that was assigned to the variable
expressionAdd5. You could also do this with a named function expression
like below. Calling them will result in exactly the same thing.
*/
const namedAdd5 = function add5WithName (num) {
return num + 5
}
log('Ex. 6', namedAdd5(3))
/*
Ex. 7
es6 arrow functions are always anonymous. There is no such thing as
a named es6 function expression. One thing to keep in mind with
anonymous functions is that they do save a bit of key strokes, but
they do make debugging a bit harder because they are anonymous.
You will see two different ways of writing es6 arrow functions.
One will have an explicit return like you see in the es6ArrowAdd5, but
that is only necessary when you have a block statement. If you don't, the
return is implicit. For example, the function below is equivelent to es6ArrowAdd5
and a bit more concise.
*/
const implicitReturnEs6Add5 = num => num + 5
log('Ex. 7', implicitReturnEs6Add5(3))
/*
One of the largest differences between es6 arrow functions and both function
declarations and regular function expressions is that es6 functions have a lexical
this binding. This means that the value of `this` inside of the function is
determined by where the arrow functions is defined, now where it is used.
*/
/*
Ex. 8
You'll notice below that when we call update on the myOldObject
the this is referring the value defined within the object and updates it
*/
const myOldObject = {
value: 3,
update: function () {
this.value++
}
}
/*
Ex. 9
However, if you try to with an arrow funciton, the this is lexically
scoped meaning it only has access to the closure created by the update
function, not the object value.
*/
const myNewObject = {
value: 3,
update: () => {
this.value++
}
}
log('Ex. 8a', myOldObject.value)
myOldObject.update()
log('Ex. 8b', myOldObject.value)
log('Ex. 9a', myNewObject.value)
myNewObject.update()
log('Ex. 9b', myNewObject.value)
/*
Notes on choosing which type
While different people may have differnt opinions, and it heavily
depends on the context, here are a couple of places you should maybe
consider not using arrow functions
1) Object methods since the this isn't bound to anything and will
inherit the value of this from its parent scope
2) Callback functions with event handlers
The function expression vs declaration choice is a bit different as you
need to think about whether or not you need hoisting. While hoisting seems
nice you are also polluting your global scope, whereas using function
expressions avoid that.
This is in no way an exhaustive list of the types of functions that you
have available to you, but this should at least give you a good idea of
the difference between function declarations and expressions. For further
reading and exploring, feel free to check out any of the below topics all
related to functions.
1) Immediately-Invoked Function Expression (IIFE)
2) Recursive functions
3) Curried functions
3) Factory functions
4) Object Constructors
5) Classes
*/
// When ran you should see the following results
/*
❯ node function-examples.js
Ex. 1 --> 8
Ex. 2 --> expressionAdd5 is not defined
Ex. 3 --> es6ArrowAdd5 is not defined
Ex. 4 --> 8
Ex. 5 --> 8
Ex. 6 --> 8
Ex. 7 --> 8
Ex. 8a --> 3
Ex. 8b --> 4
Ex. 9a --> 3
Ex. 9b --> 3
*/
// helpers
function log (name, functionOrVal) {
console.log(`${name} --> ${functionOrVal}`)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment