A photograph of the author, Alex Kharouk

Alex Kharouk

Book Club: Eloquent JavaScript - Chapter 3

# Chapter 3, the World of Functions

## Quick update

It's been almost a month since I published the first part of the Eloquent JavaScript Book Club series. I enjoyed the feedback I received from the Dev.to community and was happy to see folks wanting to join the book club and read along. However, as it has been almost a month, I'm sure many of you continued and finished the book without me. It's cool; I get it. Personally, a lot has happened in my life.

I quit my current job and am very happy to have accepted an excellent offer at a great company. I received an unconditional offer for a Masters in Computer Science & Artificial Intelligence, where I'll be studying part-time for the next two years. I learned a heck load of data structures, algorithms, systems design, and everything in between.

It's been a wild month, but I'm ready to sit back a bit. Drink a nice cold brew. Open up the dusty textbook and get into some eloquent JavaScript. Before we start, I want to quickly mention that I also completed the Just JavaScript book/workshop/mini-course by Dan Abramov. I've already seen some parallels between eloquent JS and that course. I would wholeheartedly recommend getting into both. It helped solidify my mental model of how things like variables (and functions) work. There should be a blog post to analyse the two texts.

Right, functions.

People think that computer science is the art of geniuses, but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones.

Without functions, our code wouldn't function well. It will still do its job. Expressions and statements will continue to execute whilst our bindings (variables) will continue latching onto data. But without some order or a way of keeping related code together, it'd be complicated to manage.

We can create functions with a function expression. It's similar to how we have defined variables.

const addTwo = function (num) {
return num + 2

The num is a parameter, whilst the curly braces encapsulate the body of the function. The code above creates a constant called addTwo and binds it to a function that takes in a number and adds two to it.

Some functions have a return statement. Others return nothing at all. Yet just because it seems like it returns nothing in the code, in reality, all operations with no explicit return statement return undefined. Another example is to open your browser's console, and type in console.log('hello'). You'll see hello being printed, but you'll also get the type undefined returned. That's because the .log() is a function that doesn't return anything. It just runs a side effect, which is the printed message.

### Understanding Scope

A crucial advantage that a function has is being able to have it's own scope. It's a mechanism that allows a function to deal with its internal state and prevent other functions from manipulating state. It creates separation of scope, where you have the global scope (outside the function), and the inner scope. Global scope is like setting some variables at the top of your file.

let time = 9
let closingTime = 11

Functions have the ability to read those variables, and even manipulate them (we will discuss why this is not necessarily good). However, we can't reach into functions and control the variables.

const personalSchedule = function () {
let doctorsAppointment = 12
// doctorsAppointment is not defined

These variables are known as local variables (or local bindings). They only exist for a limited amount of time, when the function is called. Then, once the the function has finished executing, they cease to exist. It's quite melancholic.

A key thing to note is that variables declared with let or const are local to the block they are called in, and therefore can not be called outside the block, unlike var. A great example is a for loop:

for (let i = 0; i < 5; i++) {
// execute code
console.log(i) // undefined
for (var i = 0; i < 5; i++) {
// execute code
console.log(i) // 5

Another thing to note is that whilst we can't look inside a function to get its variables, we can look outside the scope of the function.

const x = 10
const halve = function () {
const divided = x / 2
const print = function () {
console.log(x) // 10
console.log(divided) // 5

The print function inside halve can interact with both the x variable in the global scope, as well as the divided variable within the scope of the halve function. This is also known as lexical scoping, where each local scope can also see all the local scopes that contain it. On top of that, all scopes can see the global scope.

## Declaring Functions

We've seen functions declared as an expression. We can also assign them in a shorter way through what is known as function declarations.

function booDeclare(name) {
console.log(`BOO! Did I scare you ${name}?`)
// how we would write it before
const boo = function (name) {
// ...

There is a difference between the two, and it's primarily due to something called hoisting (we won't get into this right now). If you were to call booDeclare before it was declared, you would see that it still works. However, we can't say the same for the other function. This is due to function declarations being hoisted up to the top of the conceptual page, and thus is able to be used anywhere in the code.

This kind of makes sense, as the second function is more like how we declare a variable, and that we are unable to know what the variable binds to before it is declared.

console.log('I am walking through a haunted house', booDeclare('Alex')) // works
function booDeclare(name) {
return `BOO! Did I scare you ${name}?`
console.log(boo) // Cannot access 'boo' before initialization
const boo = function (name) {
return `BOO! Did I scare you ${name}?`
console.log(ghost) // Cannot access 'ghost' before initialization
const ghost = 'nice ghost'

### Arrow functions

You might be familiar with arrow functions as well. They are newer syntax, and they provide us a way of writing small function expressions in a (my opinion) cleaner manner.

const owedMoney = (sum) => {
return sum
// can be written as
const owedMoney = (sum) => sum

The code is less verbose, as it now implicitly returns the value that sum is bound to, and there are no curly braces. There is another difference between the arrow function and a function expression, and that is regarding the keyword this. We will talk about it more once we get to Chapter 6 (can't wait).

### Optional Arguments

The beauty of JavaScript is that it's quite lenient in what you can do (compared to other languages).

function ages() {
console.log('I have no args')
ages(11, 23, 52, 59, 32, 53, 99, 29, 48, 75, 49, 23) // I have no args

No errors! What happens here is that JavaScript will ignore all these arguments if they're not being used. Simple. Even if you specified the arguments, and didn't provide any parameters, JavaScript will still not error out.

function ages(person1, person2, person3) {
console.log(person1, person2, person3)
ages(19) // 19 undefined undefined

JavaScript assigns missing parameters to undefined (similar to when you declare let x;). It also dismisses any parameters provided if there's no explicit use for them. As you can tell, this is not so beautiful. The downside here is that you can accidentally pass the wrong number of arguments, or none at all, and you might not realise that you have a bug.

One way to assign a value to an argument even when it's not passed is to use optional arguments.

function ages(person1 = 23, person2 = 99) {
console.log(person1, person2)
ages(22) // 22 99

Again, this is not the ultimate solution as it will only assign the parameters in order. So if you don't pass anything in the second argument, person2 will always default to 99. That's why it's common to see code like this (albeit this is very contrived).

function fetchPosts(url, method = 'GET') {
const data = fetch(url, { method })

### Functions and Side Effects

As we've seen, functions can be split into two types. Functions that execute other functions or side effects, and functions that have return values. At times, you will have functions that do both. Each have their own use cases, and their own advantages. Functions with return values will almost always be called more often, since we rely on the values returned to execute more code.

There are pure functions, that have the pleasure of always being reliable. The purity comes from:

  • relying on global variables whose values might change
  • always returning/producing the same value
  • can easily be replaced with a simple value:
const return5 = () => 5
let total = 5 + return5() // 10
total = 5 + 5 // 10

They are easily testable, making unit tests a breeze to write. They usually are quick to understand, as you don't need to scour other parts of the codebase to see what's being called. In essence, they're great. Yet, that ease comes with a bit of difficulty. Whilst you can write primarily pure functions, you'll realise quickly that some side effects are needed. So unless you're a total purist who despises side effects, I'd say it's fine to have a mixture of both. Like the author says:

## Summary

So, functions. A brilliant addition to our JavaScript toolbelt that allows us to manage multiple kinds of scope, separating code logic, not repeating ourselves, and understanding side effects. The chapter gave us a lot of information, and I think it's an important fundamental to really grasp. The author also brings up concepts like the Call Stack, and Recursion. I decided not to include that in this chapter as I felt it deserved a separate snack-esque post. You can read more about it here.

Thanks for reading! The next chapter will be about some rather essential data structures, Objects and Arrays.

If you'd like to attempt the exercises for the chapter, you can find them at the bottom of the chapter. Let me know how you get on. I definitely recommend going through them, to help solidify your knowledge.