Do Something
Imagine that JavaScript is a language--not a computer language, filled with unfamiliar concepts, but a natural human language like English or Spanish. It has nouns, by way of the primitive and object values. It has adjectives, via object properties (see: array.length). It has verbs, in functions, and adverbs, in the values that we may write between the parentheses of a function to change how that verb acts. Libraries like jQuery even strive to make this comparison more true: a line of jQuery or well-written JavaScript, when read aloud, should be easy to translate into your native language (assuming it follows a similar subject-verb-object progression).
But while we have been able to create our own nouns, adjectives, and adverbs by putting together variables, we haven't been able to define our own verbs yet. To torture the metaphor just a bit more, this is something we should want to do for brevity, if nothing else. After all, we don't say that someone "lifted their foot, moved it forward, placed it on the ground, and shifted their weight" to the grocery store. We say that they walked. In JavaScript, by defining our own functions (in addition to those that are built-in), we can wrap complex operations up into simple commands, which makes it much easier to reason at a high level about what our program is doing.
But before we worry about those kinds of architectural concerns, let's talk about the anatomy of a function definition. At first glance, it won't look too different from other control structures, such as loops or conditional statements.
var greeting = function(name) {
var combined = "Hello, " + name;
console.log(combined);
}
greeting("Thomas"); // logs "Hello, Thomas"
The first part of this code should look familiar, since it's just declaring a variable and setting it equal to a value. Functions are first-class values in JavaScript, which means that just like strings and numbers, we can store them and pass them around in variables. The next part is the function literal: also like numbers and strings, functions have a "literal" form, meaning their value as written out (and not accessed through a variable). It begins with the function keyword to let JavaScript know what we're creating, followed by parentheses containing zero or more inputs, and a block of code that will be run when the function is called.
Let's talk about those inputs for a second. Where the condition goes in an if statement, functions have a list of inputs to the function, which we call its arguments. When you call a function, you provide those inputs by putting them between the parentheses of the function expression. Arguments act like variable definitions. In this case, we are declaring that inside the function, we'll have an input variable called name. If we provide a value (above, we provide the string "Thomas"), the argument starts the function with that value. If we don't, the argument is undefined, just like any variable that's declared but not assigned a value. You can have as many arguments as you like, just by separating them with commas:
var manyArgs = function(one, two, three, four, five, andSoOn, andSoForth) {
//so many arguments!
}
Inside of a function, you can also declare new variables, or do any of the other things that we've done in JavaScript so far. You can also use variables that were declared outside the function. Any variable that you declare inside, however, is only available inside that function. Running the function again gives you a new version of the variable, unrelated to the old one. Being in a function is like being in one of those exploratory submarines with the robot arms attached for taking samples. You can interact with the outside world, and there's a whole bunch of gadgets inside the submarine, but the fish can't reach in to fiddle with the controls, and when the trip is over all of the gauges and settings are put back in their default positions.
Along with inputs, functions can also have output (but just one). The output of a function is the value that's used when it is evaluated in an expression, and is called the return value. Here's a simple function that adds two numbers together and returns their sum:
var adder = function(a, b) {
return a + b;
}
var x = adder(1, 2) + 3; //becomes 3 + 3, and so x equals 6
When a function reaches a return statement, it immediately exits. You can write return; without a value to immediately bail out of a function without executing the remainder, but we usually use it because we want to get access to the result of the function's execution. In this way, we can treat functions as essentially black boxes: write them once, and then all we have to care about is the inputs and outputs. If the function doesn't return a value, or doesn't reach a return statement at all, its value in an expression is undefined.
template()
For this chapter, we're going to use templating as an example of building a function. Templates make it much easier to manage and build HTML from JavaScript, in part because JavaScript lacks multi-line strings. Another reason it's not a good idea to mix text and JavaScript is that it mixes up your content and your page behavior. By keeping them separate, you make maintenance much easier, because all of your code is in one place, and all of your content in another. It also makes it much easier to build a translated version of your page. All in all, templates are one of the most important parts of writing modern websites, both on the server and in the browser.
In the interactive section below, you can see each revision on our way from a simple block of code all the way up to a completed template function, with comments on each step in the sidebar. You should feel free to load the final version of the code (or any of the intermediate versions) into a script tag to try it out.
Our template function is pretty good, and serves as a great example of a useful function. Now, instead of writing code for each JavaScript object that we want to represent as HTML, we can just feed the template string and the data to our function, and we get back a complete HTML string that we can (in turn) place on the page using jQuery's html() function.
But you may have noticed that we're still keeping our text inside of JavaScript, and we're still forced to deal with JavaScript's lack of support for multiline strings. Building bigger chunks of HTML this way would be painful. How do we get around this problem? By hiding our templates in plain sight.
You may remember, way back when we first learned about <script> tags, that I recommended leaving the type attribute off, because it doesn't make any difference. It turns out that was a half-truth: if the attribute is left off, the browser will indeed assume that the script is written in JavaScript. But if you specify a type that is radically different from the old-school "text/javascript", such as "text/html", the browser will skip the tag entirely. It'll still be a part of the document, but it isn't interpreted as code, and it's still completely hidden from readers. This trick lets us hide big blocks of text in our document, and then pull them out with jQuery.
<script type="text/html">
This will be ignored by the browser, but is still present in the page.
</script>
<script>
//here is our actual script code
var script = $('script[type="text/html"]'); //find the above tag
var contents = script.html(); //get the text inside
console.log(contents); //log out its content
</script>
With this in mind, here's the final version of our template function. Instead of taking a text string as the first argument, we'll ask for an element ID, and find a hidden script tag with the template inside.
var template = function(id, data) {
//find our script tag by its ID
var scriptTag = $('#' + id);
//retrieve its contents as a multiline string
var code = scriptTag.html();
//loop through the data
for (var key in data) {
//get the value for each key
var value = data[key]
//replace the matching tag with that value
code = code.replace('{{' + key + '}}', value);
}
//output our filled-in template
return code;
}
this Gets Confusing
One more thing about functions: since they're just values, like numbers or strings, it follows that they too can be placed in arrays or as the properties of objects just like those primitive values. A function that's attached to an object is called a method, and it has a special ability: when called as an object property, it can access the parent object through a special variable named this.
var cat = {
noise: 'meow',
speak: function() {
console.log(this.noise);
}
}
cat.speak(); //logs "meow"
Even if you assign a function to many objects, so that it's shared between them, the value of this will always be the object on which it was called--the one before the dot operator (or square brackets, if you're using array-style property notation in a loop). It's a dynamic value, determined at run-time. this can be confusing (and hard to write about), but it's extremely useful for writing objects that have their own verbs, and affect their own internal values.
Although you haven't been aware of how it works, you've been using an object that works exactly this way: jQuery objects make extensive use of this in order to let you affect the elements they've found on the page. Although it's extremely simplified, jQuery's CSS setter function might look internally a bit like this:
var $jquery = {
element: DivElement, //reference to a <div> on the page
css: function(name, value) {
//look up the CSS property by name in the element style list
//then assign it the value passed in
this.element.style[name] = value;
}
}
$jquery.css('background-color', 'black');
jQuery also returns this from most of its methods, which means that in expressions, they evaluate to their parent object. If you console.log() the result of a jQuery change function, you'll see the jQuery object that you changed. The practical upshot of this is that since it keeps returning itself, you can keep calling methods on the return value, chaining them into one long expression.
var paragraphs = $('p');
//getters obviously return the value you're getting
paragraphs.css('background-color'); //"", since no style is set on this page
//setters, though, return themselves
paragraphs.css('background-color', 'black');
//returns paragraphs, so we can call more of its methods
//ultimately, as long as all you do is call setters, you can do this:
paragraphs.css('color', 'green').html('hello').addClass('chained');
Just because you can chain jQuery methods this way doesn't mean that you should. When abused, it can be confusing and hard to update. But if you need to change one or two simple things about an element, chaining function calls on the object can be a fluent interface for doing so without needing an entirely new line for each change.