Contents:
- Introduction
- Function Expressions vs Function Declarations
- Synchronous vs Asynchronous
- Implicit vs Explicit Return
- Pure vs Impure
- Function Arity
- Functions as First-Class Objects
Introduction
Functions in programming, including JavaScript, are the verbs of code. They are the doers, without which our code would be boring at best, and long-winded at worst. Functions allow us to package our code and deliver it in those nice little packages to whatever might need to use it. Before we begin, let’s look at the anatomy of one particular type of function, known as an anonymous function:
function(userName){ alert('Hello there,'+userName); }
Firstly, we simply use the reserved function
keyword to designate a function. The word used here depends on the language - Python uses the def
keyword, for example - but the underlying structure of functions is pretty similar all over.
Next, we have a list of arguments. In this case, it’s just one argument - userName
- but we could potentially pass more. I’ll talk more about this - as well as some cool things you can do in the latest version of JavaScript - in the Function Arity section below.
Finally, we have the meat of the function, known in programming as a ‘block’. Wikipedia defines ‘block’ as, quite unhelpfully and simply, ‘a lexical structure of source code which is grouped together’. Basically, that just says that all the stuff between those two curly brackets is what the function does. If we changed it to say:
{ callMom(); }
Our function would now, presumably, call Mom.
Finally, note that nested functions are a thing. While this can be a bad thing (if, for example, you call recursive functions or allow them to call themselves), this also allows us to do funky things like this: Math.floor(Math.random()*9999).toString(32)
. Here, we first take a random decimal from 0 to 1. We then multiply that by 9999. Next, we take the floor of that number - that is, the next integer lower than or equal to that decimal - and convert it to a base-32 string. All in all, this allows us to easily spit out a random string of letters and numbers; great if you need a random ID for something!
In the remainder of this post, I’ll look at some various types of functions, as well as a specific feature of ES6/7 that makes functions more adaptable and fun.
Function Expressions vs Function Declarations
There are, generally speaking, two ways of writing a function.
The first is known as the function declaration:
function myFunc(breed){ console.log('My dog is a ',breed,'!') }
The second is the function expression:
var myFunc = function(breed){ console.log('My dog is a ',breed,'!') };
So… what’s the difference? Well, in this case, none. But remember JavaScript hoisting?
Hoist the functions!
While the function expression’s variable (var myFunc
) is hoisted, the actual value of that variable - the function body - isn’t! And that means that if you have the following code:
myFunc('Dave'); var myFunc = function(name){ console.log('In case you didnt know, your name is',name) };
you’ll get the following error (in Chrome; other browsers will have different versions of this error):
Uncaught TypeError: myFunc is not a function
because while the fact that the variable myFunc exists (is ‘defined’) is hoisted, the contents of it aren’t. So by the time we call the function with myFunc('Dave');
, JavaScript thinks that we’re trying to run something that’s not a function! Remember that if neither the contents nor the declaration were hoisted, we’d simply get: Uncaught ReferenceError: myFunc is not defined
.
So… why wouldn’t you wanna use function declarations? Would that just make everything easier? Well, yes, and that can actually be a problem. By using function expressions, functions are defined exactly when they need to be. As such, you can much more easily debug your awesome new app, because you know exactly where to look in the code based on where the functions are failing.
Synchronous vs Asynchronous
In any application that will eventually require some sort of communication with a non-immediately-local resource, you’re gonna have some unpredictability over when that resource will finally get there. Anyone who’s planned a party (or even been to a party) has experienced this issue: when do you start the party so that people don’t get bored waiting, but so that there’s enough, say, cake for the late-comers? The Asynchronous methodology adopts the “when-it’s-ready approach”: You serve people cake when they arrive.
While this approach is pretty obvious in every life, it’s perhaps not so obvious to the beginning programmer whose code, up until now, has been doing things exactly when he tells it to. Let’s look at a synchronous example first, with some pseudo-pseudocode:
var animalList = ['cat','fish','dog','monkey','hippo','apatosaurus'] var myFunc = function(){ //it's a function expression! var theAnimal = syncGetAnAnimal(animalList);//pretend this returns 'dog' console.log('The animal we picked is:',theAnimal); }; myFunc();//run function!
Works great! We pick a random animal (in this case, a dog), and spit out “The animal we picked is: dog”. The code within the function basically just runs from top to bottom.
But what if our list of animals is not part of our app? What if it’s somewhere else on the computer, or worse, somewhere else on some other computer? Again, the pseudo-pseudocode:
var myFunc = function(){ //it's a function expression! var theAnimal = asyncGetAnAnimal('davesawesomeanimalsite.com'); console.log('The animal we picked is:',theAnimal); }; myFunc();
Same thing, right? Al I’ve done is changed the function a bit.
Well… no. You should probably be used to blogs doing that by now, since going “Ah, but wait!” seems to make us devs feel clever or something. Anyway, this function, instead of spitting out what you’d expect - and want! - it to, will just spit out: “The animal we picked is: undefined”. This is because while the asyncGetAnAnimal()
function needs an indeterminate amount of time to get its particular resource, the JavaScript interpreter just keeps going. So while asyncGetAnAnimal()
is off getting its act together, console.log(...)
is already running! It runs before we even have an animal. Now, you may be wondering what happens if we ‘know’ that our delay is extremely small. For example, the setTimeout()
function runs a particular bit of code after a specified delay. It’s great for creating JavaScript alarm clocks to piss off your co-workers. That specified delay can be any number, including zero! So what happens if we run this code?:
console.log('one'); setTimeout(function(){ console.log('two') },0);// console.log('three');
Since it’s a delay of zero milliseconds, JavaScript should be fine with that, right? It should just print ‘one’, then ‘two’, then ‘three’! Yeh, you guessed it: Nope. Since setTimeout()
is an asynchronous function, JavaScript basically says “Okay, that’s async. I’ll do it later” - no matter how long the delay is!
Instead, we need to use what’s called a ‘callback’, which essentially is a way for an asynchronous operation to run when it’s ready to. So if our asyncGetAnAnimal()
function from above takes a year to run, our callback would run when that year’s up. If it takes a millisecond, it’ll run in a millisecond. Many callbacks take the form of arguments passed to their ‘parent’ async function:
myAsyncFunc(function aCallBack(r){ console.log('Response is',r); });
So… what determines whether a function is synchronous or asynchronous? Well, in short, if any of the function’s components - the stuff it calls, etc. - is async, then the function itself is async.
Finally, some common (uses of) async functions:
- Getting data from external resources. Most commonly, using two methods:
- GET method if you basically just want data, and don’t wanna provide a huge amount of data to the external resource
- POST method if you need to provide a lot of data from your side, usually via a POJO (Plain-Old-JavaScript-Object);
- The timer functions
setInterval()
andsetTimeout()
. Remember as per above that these are always asynchronous, even WITH a timer delay of zero! User Interface (UI) functions, such as
window.onclick()
. In this case, the function callback is triggered whenever the user does something. Note that a lot of these functions pass anevent
parameter to their callback:window.onmousemove = function(e){ console.log('mouse is at position',e.offsetX,e.offsetY) };
Implicit vs Explicit Return
One thing functions in any programming language - especially an object-oriented-capable one like JavaScript - are useful for is stringing together various operations. So, for example:
mixDryIngreds().mixWetIngreds().addChocolate().pourInCakePan().bake();
In order for a function’s work to be used by something else, that function needs to return something, using an optional ( gasp! ) return
statement. If it’s not included, the function returns an implied undefined
:
function checkIfDave(name){ if(name==='Dave'){ return true; } }
If the value of the string name
passed to the checkIfDave()
function isn’t ‘Dave’, we obviously aren’t gonna trigger the return true;
bit. Instead, the function will implicitly return undefined
. Since that undefined
is falsey, we can use this to implied return value a conditional!
Pure vs Impure
It sounds like some horrible term for JavaScript eugenics. Either that, or impure functions sound like something that little kids should be shielded from.
Hide your eyes, kids!
However, pure with regards to functions is simply a term for functions that, given the same specific, explicit arguments each time, will spit out the same result each time. The function Math.floor()
is always going to return the 4
, if given the same input 4.3
.
In contrast, an impure function is one that mutates or produces different outputs depending on external resources (and may itself change those external resources):
var pickCat = false; function likesPet(name){ if(pickCat){ console.log(name,'prefers cats!'); }else{ console.log(name,'prefers dogs!') } }
While this function does include an input of name
, which by itself would leave the function pure, its output also depends on the value of an external variable pickCat
. As Eric Elliot says, “pure functions produce no side effects”.
Function Arity
Function Arity is, simply put, the number of arguments that a function can take. In a lot of programming languages, we can do what’s called ‘overloading’ functions by specifying a bunch of identically named functions, all with different numbers of arguments. When the code’s run, the computer will ‘pick’ the appropriate function. However, we can’t do this with JavaScript. Because it’s a scripting language, subsequently defined ‘bodies’ for a particular function would replace previously-defined ones. So if we write:
myFunc(name){...}
and then later on write
myFunc(name,date){...}
the single-argument version of myFunc
would no longer exist!
So how do we deal with JavaScript functions with an unknown number of arguments? Traditionally (pre ES2015/ES6), we’ve had to write functions expecting the maximum number of arguments, and then either allow the un-passed arguments to be undefined, or explicitly define them (if undefined!) within the body of the function. So, my example from above:
var myFunc = function(name,date){ if(!name){ name = 'An unknown person'; } if(!date){ date = 'an unknown date'; } console.log(name,'called myFunc on',date); }
This is fine if we have a maximum number of arguments that could possibly go in the function. However, what if we dont know the number of arguments?
Enter the ES2015 spread operator, which in addition to making your functions look slightly passive-aggressive, allows you to pass in any number of arguments!
var doIt = function(...fine){ console.log('You inputted',fine.join(','),'. Not like I wanted to run anyway.'); }
Basically, the arguments inputted in this doIt
function get converted to an array. And yes, it’s a real array (unlike, say, the results of document.getElementsByTagName()
, which returns an array-like HTMLCollection
that doesn’t have inherent access to a lot of the Array
prototype functions), so you can then do normal array-things to it, like forEach()
, or map()
.
What about arity with NodeJS? Well, it’s relatively easy to pass arguments into a NodeJS module’s function by appending them as flags to the command-line arguments. So:
$node pickAnimalModule
could become
$node pickAnimalModule -onlyMammals
You’d then access these arguments in Node via process.argv
, which is, you guessed it, an array. There are, however, two very important distinctions/caveats here:
- First, the first two items in the array (positions
0
and1
, respectively), are the name/location of the process running your Node stuff (that is, Node itself), and the location of the JavaScript file being run. - Second, the arguments are passed as strings. This means if you want to, say, pass a config object to your Node function, you’ll need to run
JSON.parse
on it at some point.
Finally, while the spread operator and process.argv
solve the problem of how many arguments there are, it doesn’t help with determing what kind of arguments they are. In other words, let’s say we have a function ageGreeting(name,age)
that accepts two arguments - name
and age
- and outputs some sort of greeting based on that age. So if the user inputs Dave
and 300
, it might say “Hi Dave! You’re very old!”. Now, let’s say that, for whatever reason, we need to pass age
in as an object such as:
{years:300}
How would we prevent the user from inputting the arguments in the wrong order? As it’s written, our function wants a string first (name
), and then an object (age
). Feeding these in the wrong order will generate an error. One way to fix this is by using the arguments
parameter, which is part of every JavaScript function, and is simply an array of the arguments given. We can then do some relatively simple type-checking on the items in this arguments
array:
var ageGreetings = function(name,age){ var actualName, actualAge; arguments.forEach(function(arg){ if (typeof arg=='string'){ //this is probably the name! actualName=arg; }else if(typeof arg=='object' && arg.age){ actualAge=arg; } }) //do stuff with the info }
Functions as First-Class Objects
One of the most popular statements used as an explanation of JavaScript is that “functions are first-class objects”. But what does this mean? Does it mean that JavaScript functions sit in the front of the plane, drinking Martinis? Does it mean they’re richer than other functions?
Of course not. It means that, first and foremost, JavaScript functions follow another very famous JavaScript mantra: (almost) Everything is an object.
What this means in terms of functions is two-fold: firstly, functions are, as with any other objects, created with their own constructor. This Function
constructor actually can be called explicitly (i.e., new Function(args)
), but is usually implicitly called via simply writing a function declaration or function expression.
The second result of functions being objects stems from JavaScript’s prototypical inheritance model. Remember from high school biology how organisms are grouped based on similarities, and organisms that share a common group also share common traits? Well, the same’s true for JavaScript ‘groups’ (we call these classes): all members of the Function
class can be ‘called’, all members of the Object
class can have their properties listed via Object.getOwnPropertyNames()
, and so on. So what does this mean in terms of the statement “functions are first-class objects”? It just means that functions in JavaScript are objects. You can pass them around like objects. You can ask for their property names.