Functions in JavaScript encapsulate functionality. We've already how functions work in some of the built-in objects in JavaScript, like a method within a String:
let myString = 'brian'
myString.toUpperCase() // => 'BRIAN'
In this example, all of the logic that goes into turning a String into all upper-case letters is encapsulated into the toUpperCase() function. Code that uses this function doesn't need to know anything about its actual implementation, only that using it does the job it's expected to do, and returns its expected result. We've seen this already with the built-in functions we've used.
Before we get to how to create our own functions, let's first discuss why we'd want to. Organizing our code into functions makes our code more reusable, readable, and far more maintainable than code written in a linear fashion, especially as our applications become more complex.
Practically speaking, a function is a like a little mini-program within our application. It usually takes input and usually produces output. Emphasis on usually, because there are certainly cases where a function takes no input and/or produces no output. We'll get to that later.
function yell(words) {
return `${words.toUpperCase()}!!!!!`
}
yell('tacos') // => 'TACOS!!!!!'
Let's break this down:
In short, we've taken the input of the function, performed some processing/transformation/action, and returned an output to the code that's using the function. Let's go deeper:
function yell(words) {
return `${words.toUpperCase()}!!!!!`
}
function makeFullName(firstName, lastName) {
return `${firstName} ${lastName}`
}
console.log(yell(makeFullName('steph', 'curry'))) // => writes 'STEPH CURRY!!!!!' to the console
console.log(yell(makeFullName('james', 'harden'))) // => writes 'JAMES HARDEN!!!!!' to the console
Now we're using two of our own functions, and one other function that's built-in to JavaScript. And we're calling them all by chaining them together – that is, we're using the return value of makeFullName as the input parameter of yell, and using the return value of yell as the input parameter of console.log.
To illustrate the need for functions, let's consider how we might write the above example without using functions:
let firstName = 'steph'
let lastName = 'curry'
let fullName = `${firstName} ${lastName}`
console.log(`${fullName.toUpperCase()}!!!!!`)
firstName = 'james'
lastName = 'harden'
fullName = `${firstName} ${lastName}`
console.log(`${fullName.toUpperCase()}!!!!!`)
We can immediately see that very clear problems have emerged:
So far, we've seen functions simply declared in the body of our code, using the function keyword. There are couple of other ways that functions can be declared in our code, each serving their own purpose.
First, we can hold the value of a function in a variable, for later use in our code.
let yell = function(words) {
return `${words.toUpperCase()}!!!!!`
}
yell('tacos') // => TACOS!!!!!
This is essentially the same as declaring the function as before, using the function keyword. So what's the difference? This is where things get crazy, so hold on tight...
let yell = function(words) {
return `${words.toUpperCase()}!!!!!`
}
let whisper = function(words) {
return `shhhh... ${words.toLowerCase()}`
}
let transformPlayerName = function(firstName, lastName, transformation) {
let fullName = `${firstName} ${lastName}`
return transformation(fullName)
}
transformPlayerName('steph', 'curry', yell)
transformPlayerName('JAMES', 'HARDEN', whisper)
There's a lot going on here, so let's break it down:
Passing a function as one of the input parameters allows us to dynamically change the function's behavior based on which function is passed in. What's more, we can use a previously defined function, like we've done, or we can pass a function we define on the fly. These functions don't have names (like ones we declare using the function keyword or ones assigned to variable names do), so they're known as anonymous functions.
Let's look at a practical use-case using the example we've already built. Suppose we have the transformPlayerName function, but we don't want to use yell or whisper as the transformation. Instead, we want to define our own, custom transformation. Anonymous functions to the rescue:
let transformPlayerName = function(firstName, lastName, transformation) {
let fullName = `${firstName} ${lastName}`
return transformation(fullName)
}
transformPlayerName('candy', 'man', function(words) {
return `${words} ${words} ${words}`
}) // => 'candy man candy man candy man'
This turns out to be a pretty commonly used pattern in JavaScript, especially when dealing with the web browser. We'll be looking at this quite a bit in the next unit of the course, but here's a quick intro to how we can apply these concepts to programming for the web.
Client-side web development is mostly centered around this basic workflow:
Let's look at some example code, and dissect it further from there:
window.addEventListener('DOMContentLoaded', function() {
for (let i = 0; i < 5; i++) {
let outputElement = document.querySelector('.output')
outputElement.insertAdjacentHTML('beforeend', `
<div class="text-3xl my-8">This is the way.</div>
`)
}
})
This code looks daunting at first, especially if we've never seen anything like it before. But it's really a matter of following the pattern as described above, in code:
We'll notice that we've written the function that reacts to the page load event as an anonymous function. Alternatively, we can also store this function in a variable – let's call it handlePageLoad.
let handlePageLoad = function() {
for (let i = 0; i < 5; i++) {
let outputElement = document.querySelector('.output')
outputElement.insertAdjacentHTML('beforeend', `
<div class="text-3xl my-8">This is the way.</div>
`)
}
}
window.addEventListener('DOMContentLoaded', handlePageLoad)
Some developers prefer the first style, as all code for handling a single event is in one block of code, whereas other developers find the second style more readable. It 100% comes down to personal preference.