Are you a developer who adamantly hates JavaScript and would give up your firstborn before taking on a project that involves said language? If so, you are not alone. JavaScript seems to be one of those languages that polarizes us into two groups:
TEAM SIMBA
Members of this group believe JS is the rightful king of the web (and possibly server). They believe JS is easy and fun to develop with and mature enough to handle anything you throw at it. They praise the language for its impressive developer community and rich ecosystem of libraries.
TEAM SCAR
Members of this group believe JS is a twisted and deceitful language. They are obstinate that JS is a broken language flawed from its conception and mired by designers and the 90s. They see the language and its associated tooling as too “immature” for any real work. Of course, all JS libraries are just the hyena minions sent upon the web (and now server) to ravage and uglify the beautiful landscape that computer scientists have diligently built over the past six decades.
I believe both groups have valid points. JS can be a bit idiosyncratic at times. I have been burned a few times by many of the questionable “features” of the language. For example, double-equals is not equivalent to ==
present in many C-style languages:
if ("" == 0) //this statement is true... console.log("Seriously?!")
If you are like me, you probably learned C or some language based/inspired by it. The reason behind the behavior above is that ==
actually is an equality operand that performs type-coercion first. But there are also some very neat things going on in JS, and perhaps the best attribute is function-passing. There is great power in the ability to pass functions as arguments — just ask any Scala programmer! While functions are fundamental to JS, they are a topic that confuses a lot of developers, so let’s get started.
Functions
As you may have heard by now, functions in JS are first-class citizens. This means that the language allows functions to be assigned to variables or any other data structure, passed around as arguments to other functions, and returned from functions. Functions can be either a pure function or a non-pure function. Pure functions are equivalent to those in mathematics, they return the same outputs for the same inputs. Non-pure functions are those that produce side-effects. These types of functions are not required to return a value (when you do not explicitly return a value, the function will return undefined
). Let’s take a look at how functions are defined:
function abs (number) { if (number < 0) return -number; return number; } var concat = function (str1, str2, seperator) { var s = seperator = seperator || " "; return (str1 + s + " " + str2); } var character = { name: "Simba", sayHello: function () { console.log("Hello, I'm " + this.name + " and I just can't wait to be king!"); }, pounce: function (pouncee) { console.log(this.name + " has pounced on " + pouncee); } }
Function declarations take on the form:
function optional_name ([optional_argument(s)]) { //function body }
Function expressions take on the form:
var some_name = function ([optional_arguments]) { //function body };
Knowing the difference between function declarations and expressions is important. A function expression differs from a declaration in that the function is assigned to a variable. Each type of function has some unique attributes that you must account for when doing any JS development. Other than the main difference between function types, all functions require { }
around the function body and ( )
to succeed the function keyword and optional function name.
Functions can also be anonymous – a function without a name. Having a function without a name can be extremely convenient. Imagine having to have a name for the most trivial of functions or for defining a callback method. Let’s look at the character object again:
var character = { name: "Simba", sayHello: function () { console.log("Hello, I'm " + this.name + " and I just can't wait to be king!"); }, pounce: function (pouncee) { console.log(this.name + " has pounced on " + pouncee); } }
Notice the anonymous function assigned to pounce? In this context, would it really add more value to rewrite that anonymous function as a named function?
... pounce: function pounce(pouncee) { console.log(this.name + " has pounced on " + pouncee); } ...
Doing this will not make the function pounce
available outside to the global object since it is a function expression. We will discuss scope at a later point in this series. There is also the matter of defining a callback:
function strike (victim, done) { console.log("Getting striking arm ready..."); done(victim) } strike("Simba", function (victim) { console.log("Rafiki has struck " + victim + "!!!"); })
In this example, you can see there is not much point in specifying a name for the function being passed into the strike
; function. Sure this example is a bit contrived and not very useful, but at least it shows some instances where naming a function takes more effort than benefits gained.
Invoking Function
So far, I have only discussed defining functions. But what’s the use of a bunch of function definitions if they aren’t being used? I want to take a moment to talk about calling functions. Unless you’ve been living under Pride Rock (yes, another Lion King reference!) you have undoubtedly seen how to invoke functions. But I will explain, again, just for completeness… The most common way to call your precious functions is to simply write function_name(). See, it’s so easy, even Ed can do it!
Now JS wouldn’t be too interesting if there was only one way to call a function! Most of the confusion fun involved with JS are the many ways to do the same thing. According to Douglas Crockford, aka the JS Guru, there are four ways to call JS functions. The method mentioned previously is the function form. If you have were paying attention to my code examples, you will have noticed I defined a function as part of my object character
.
var character = { name: "Simba", sayHello: function () { console.log("Hello, I'm " + this.name + " and I just can't wait to be king!"); }, pounce: function (pouncee) { console.log(this.name + " has pounced on " + pouncee); } } character.pounce(“Zazu”); character[‘pounce’]("Zazu");
In this form, the function is known as a method and calling it is known as the method form. Notice that we can also call this function by specifying the name of the function inside [ ]
.
There are two other forms that I did not use in my examples. The constructor form and the apply/call form. The constructor form is identified by the use of the new keyword.
var func = new Function([optional_arguments], functionBody)
var abs = new Function(['number'], 'if (number < 0) return -number; return number;'); console.log(abs(-8));
What this form should show you is that all functions are objects! All new functions created inherit the properties and methods of Function.protoype — this type of inheritance is known as prototypal inheritance. Although invoking these types of functions looks exactly like the function form, there are some characteristics of calling this form that differ from the function form, these will be covered later in this series.
The apply/call form is particularly important as I believe it can help you to understand the internal [[call]] method that all functions have. The apply/call form is just a very small abstraction around [[call]] thus, understanding this form ensures you understand what’s going on behind the scenes of all the other forms. Just look at all the other forms as syntactic sugar for this form.
According to the ECMAScript 5 spec, a functions call method takes two optional parameters: thisArg
and 0, 1, …, n arguments. The important thing to note about this is that the this
keyword is set to thisArg
, in other words, this
is explicitly set in this form. Think about that for a moment…
Remember I said to think of all other forms of invoking a function as de-sugaring to this? Let’s go through some examples to see what I mean:
character.pounce("Nala"); //Simba has pounced on Nala character.pounce.call(character, "Nala"); //Simba has pounced on Nala console.log(concat("Houston", "Texas", ",")); //Houston, Texas console.log(concat.call(null, "Houston", "Texas", ",")); //Houston, Texas console.log(abs(-16)); //16 console.log(abs.call(null, -16)); //16
All of these return/output the same result. The only difference between the call and non-call methods is the explicit setting of this
, I will not be focusing on the this
keyword and its value in the part, that will be covered when I discuss scope. The apply
method is very similar to the call
method. The difference lies in how the arguments to the method are passed: apply
takes its arguments as an array.
var numbers = [6, 4, 87, 52, 5, 21]; function sum () { var result = 0; var i; for (i = 1; i < arguments.length; i++) { result += arguments[i]; } return result } console.log(sum(6, 4, 87, 52, 5, 21)); //169 console.log(sum.apply(null, numbers)); //169
That is my brain dump on functions for now. I hope it made a bit of sense and you took away something of value. I will be discussing scope in another post and I promise I will try to make it somewhat bearable! For now, you can go create and call functions like a baus!