🧑🏾💻
prep
Prerequisites: a terminal, basic arithmetic.
🏷️ Saving expressions
Learning Objectives
In programming we often want to reuse our work. Consider the string: "Hello there"
Suppose we want to create different greetings for different people, like: "Hello there, Alicia"
"Hello there, Barny"
We can use a variable to store this string and reuse it. How can we create a
We can create a variable in our program by writing a variable
const greeting = "Hello there";
Break down the different syntactic elements of this variable declaration:
const
is a keyword used to indicate we’re creating a variable.greeting
is the identifier - it can be used to refer to a variable after it has been declared.=
is the assignment operator. It means assign to the labelgreeting
the value of the expression on the right hand side."Hello there"
- this is the expression whose value we’re assigning to the labelgreeting
.
Type this variable declaration into the REPL:
const greeting = "Hello there";
Now refer to the label greeting
in the REPL:
`${greeting}, Alicia`
Our greeting
variable is stored in memory. We can reuse it to build more expressions:
`${greeting}, Barny`
We just used backticks to create a template literal.
`A template literal places ${expressions} inside strings;
With template literals, we can insert expressions into strings to produce new strings. Any time we want to reference a variable inside a template literal we use a dollar sign $
and a set of curly braces {}
. We can put any expression (e.g. a variable name) inside the curly braces. The value that expression evaluates to is then placed inside the string.
When an operation uses an expression, that expression is immediately evaluated, and how it was written is forgotten about. That means that the greetAlicia
variable is the same in all three of these cases:
const greetAlicia = "Hello there, Alicia";
📝 note
In this example, we don’t use a variable or a template to create a string. Instead we write a string "Hello there, Alicia"
.
A sequence of characters enclosed in quotation marks is called a string literal. "Hello there, Alicia"
is a string literal.
Similarly, 10
is a number literal.
const name = "Alicia";
const greetAlicia = `Hello there, ${name}`;
const greeting = "Hello there";
const name = "Alicia";
const greetAlicia = `${greeting}, ${name}`;
The greetAlicia
variable doesn’t remember whether you used variables to make it or not - in all three cases, greetAlicia
contains the string "Hello there, Alicia"
. Once a value is made, it doesn’t matter how it was made.
💬 Declarations and statements
Learning Objectives
A variable declaration is an example of a
let versionNumber = "2.0.0"; // declaration
versionNumber = "2.0.1"; // statement
The code above has one variable declaration and one statement.
- The first line is a declaration - creating a variable
versionNumber
with a value of"2.0.0"
- The second line is a statement -
reassignment 🧶 of the value of🧶 reassignment Reassignment means changing the value associated with an identifier. versionNumber
to"2.0.1"
In this example, we’ve used the let
keyword to declare a new variable.
The let
keyword allows us to create new variables like the const
keyword.
However, we can reassign the value of a variable that is declared with the let
keyword.
If we’d used const
to declare versionNumber
, we wouldn’t be allowed to reassign it a new value.
In JavaScript, we build up programs by combining declarations and statements.
🪄 Functions
Learning Objectives
Now, instead of adding or multiplying numbers, we’ll consider 10.3
.
🤔 “What is the nearest whole number to
10.3
?”
The process of finding the nearest whole number to a decimal number is called rounding. So we could rephrase our question as:
🤔 “What does the number
10.3
round to?”
♻️ Reusing instructions
There is no operator for rounding the number 10.3
in JavaScript. But we will want to round numbers again and again. We should use a
Math.round
is a function. Because a function is a reusable set of instructions, Math.round
rounds any number.
Functions usually take inputs and then apply their set of instructions to the inputs to produce an output.
- Write
Math.round
in the Node REPL - Hit enter to evaluate our expression
The REPL output [Function: round]
is telling us Math.round
is a function.
📲 Calling a function
For our function to work, we need Node to read the instructions and
Math.round(10.3);
Notice the (
and )
brackets after the name of the function and a number inside the brackets. These brackets mean we are calling the function. The number inside the brackets is the input we’re passing to the function.
📝 note
Math.round(10.3)
is a call expression; read this as:
“apply the set of instructions for Math.round
to the number 10.3
.”
If we type Math.round(10.3)
then we get the result 10
. So we say that Math.round(10.3)
returns 10
.
A call expression is an expression which evaluates to the value returned by the function when it is called. So the expression Math.round(10.3)
evaluates to the value 10
.
If we assign that expression to a variable, or use it in a string, we’ll get the value 10
. So we can write:
const roundedValue = Math.round(10.3);
or we can write:
const roundedValueInAString = `10.3 rounds to ${Math.round(10.3)}`;
Both of these instructions evaluate the call expression Math.round(10.3)
to the returned value 10
as soon as the call expression appears. The variable roundedValue
will have a numeric value 10
(just like if we’d written const roundedValue = 10;
), and the variable roundedValueInAString
will have a string value "10.3 rounds to 10"
.
📁 Running scripts
Learning Objectives
So far we’ve seen how expressions can be evaluated using the Node REPL. The Node REPL is useful for evaluating expressions quickly.
But usually, our programs have many instructions, and we want to keep and re-run them instead of typing them out each time. So we save our instructions in files. Node can also execute instructions written in a file.
We use the node
command to run a JavaScript file in the terminal. A JavaScript file ends with .js
- this is the “file extension”.
Let’s suppose we have a file age.js
. We run the command node age.js
. This terminal command is an instruction to execute the program written inside age.js
. Our program has five lines.
So the computer will read and execute the program one line at a time:
const yearOfBirth = 1990; // declaration
const currentYear = 2023; // declaration
currentYear++; // statement
`I am ${currentYear - yearOfBirth} years old`; // statement
activity
Check you can run a file with Node:
- In your terminal, create a new file called
example.js
. - Try writing a few lines in the file.
- Get Node to run this file. (Don’t use the REPL now - you should run a command to execute the whole file.)
Once the computer executes these statements, the execution of the program is complete. But we’re left with a problem. With the REPL, when the user inputs an expression statement or declaration, the computer reads and executes the line and immediately prints feedback to the terminal. With a file, the computer will execute each line sequentially until completion without printing the values of each expression it evaluates.
So this new problem can be expressed as a question:
❓ Problem
“How can we check what the values evaluated to in our program during execution?”
🖨️ Logging
Learning Objectives
Printing to the terminal
To look at values when our program runs, we can use a function called console.log
.
💡 tip
console usually means a text interface like a terminal. A log is a written record of something that happened.
So console.log
will record something that happens in our program and print it to a text based interface.
console.log
logs the result of expressions while our program is executing.
This is very useful for complex programs when we need to check what values expressions evaluate to at specific moments of our program execution.
Let’s see how to use console.log
. In a file called example.js
, write the name of the function console.log
.
console.log;
If we run this file with Node, we won’t be able to see anything in the terminal. As with Math.round
we need to use the syntax for calling a function. Add brackets after the function name:
console.log("hello there!");
We should see the string "hello there!"
logged out in the terminal.
❌ Errors
Learning Objectives
🗣️ Recall: A programming language is a set of rules for writing computer instructions.
So we need to understand what happens when we break those rules.
Let’s take an example:
|
|
On line 1, we have a variable declaration, but the string has a missing "
We’re not obeying the syntactic rules for JavaScript: the rules for writing expressions, statements and other parts of the language.
When we execute the code above, we get this:
const firstName = "Francesco;
^^^^^^^^^^^
Uncaught SyntaxError: Invalid or unexpected token
We get a SyntaxError message. This error message is telling us that we’ve broken the rules of the language.
activity
Each block of code in this activity is broken. Before you run each block of code:
- Predict the error.
- Explain why the error happened.
const volunteer = "Shadi";
const volunteer = "Abdi";
const volunteer = "Shadi";
volunteer = "Hinde";
console.log(Math.round(10.3);
Saving return values
We can store the return value of a function in a variable. Function calls are also expressions. This means their value can also be stored in variables, just like with operations on numbers or strings.
Suppose we have a file arithmetic.js
containing this code:
const result = Math.round(10.3);
When this program is executed, it creates a variable called result
and assigns to it the return value of the function, in this case the rounded number.
So result
will have a value of 10
.
🔭 Logging and returning
Most functions return values we can use in our program.
Math.round
takes a single input, does a calculation and then returns a value that we can use when our program is running.
Some functions don’t produce useful return values in our running program; but they can still cause effects.
exercise
const result = console.log("hello world");
- Predict what
result
will evaluate to when the code above runs. - Execute this line in the Node REPL.
- Evaluate the value of the
result
variable to observe what happens.
When this program runs, the variable result
will evaluate to undefined
. undefined
is a data type in JavaScript which usually means no value has been assigned. Unlike the number
data type, which contains many possible values (1
, 2
, 10.3
, etc), the undefined
data type has exactly one value, undefined
.
This can feel confusing as console.log
is a function with a set of instructions. console.log
does have an effect: it logs values to the console. However, console.log
doesn’t produce an output that we can use inside the rest of our running program.
💡 tip
console.log
is used to print values to the terminal. It doesn’t produce an output in the running program.🧩 Percentages
Learning Objectives
Let’s begin with this problem:
Given a decimal number I want to convert it into a percentage format.
For example, given the decimal number 0.5
we return the string "50%"
. Given the decimal number 0.231
we return the string "23.1%"
.
Restating the problem
Our function must convert any decimal to a percentage. We have used functions already. Here are some functions we’ve used:
|
|
All these expressions are function calls: we’re passing input ("hello world"
or 3.141
) to the functions (console.log
or Math.round
) to use their functionality. Math.round
and console.log
are functions that the JavaScript language designers have written and stored inside the language, because everyone needs them all the time.
No such pre-built function converts any number to a percentage, so we must write our own. We’re going to create a function called convertToPercentage
with the following requirements:
Given a number input
When we call convertToPercentage
with the number input
Then we get back a string representing the percentage equivalent of that number.
Here are some examples:
|
|
|
|
Useful expressions
It is often helpful to solve a problem in one specific instance before doing it for all cases.
We’re not going to define our function yet. Instead we will work out what our function should do. Then we’ll define a function which does the same thing.
In programming, we always try the simplest thing first. Let’s consider how to convert just one number to a percentage. Look at this variable declaration:
|
|
We want to create an expression for the percentage using the value of decimalNumber
. To convert to a percentage, we will multiply the number by 100 and then add a "%"
sign on the end.
|
|
Recalling template literals, the expression in the curly braces will be evaluated first and then inserted into the string, giving us the percentage string.
Now that we’ve solved the problem of converting a single decimal number to a percentage, let’s practice solving other similar problems using expressions.
Create a new JavaScript file so that you can try running the code for yourself.
Calculating the area and perimeter of a rectangle
In one of these new files, let’s make two variables that describe the dimensions of a rectangle:
const height = 10; // 10 is just an example of a value here - your code should still work if you change this to another value.
const width = 30; // Also just an example - your code should still work if this changes.
Using these variables, let’s calculate the area and perimeter of the rectangle.
We can calculate the area and perimeter by creating expressions that use the height
and width
variables we just created. Hint: read the links above if you don’t know how to calculate area and perimeter of a rectangle.
Finally, we’ll create two more variables: area
and perimeter
to store the result of the calculations.
const area = FILL_ME_IN;
const perimeter = FILL_ME_IN;
Now, if we change the numbers assigned to height
and width
, are the area
and perimeter
values still correct? Try using console.log
to print out the value of the variables and then run the script using Node to view the output.
Remember to create a new JavaScript file to run the code for yourself.
Converting pence to pounds
Like the rectangle example, we’ll start by creating a variable to store a price in pence:
const price = 130; // Just an example value. Try changing this value to 0, 10, or 1521, and make sure you still get the right answer from your code.
Now, you should write an expression that calculates the price in pounds. The price in pounds should be shown with 2 decimal places and start with “£”.
Try using console.log
to print out the value of price in pounds and then run the script using Node to view the output.
🪄 Declaring functions
Learning Objectives
💡 tip
To create a function, we can use a function declaration. A function declaration looks like this:
|
|
The function
declaration consists of the following syntactic elements:
function
keyword, begins the function declarationconvertToPercentage
- names the function()
- any input to the function will go between these round braces (our function above doesn’t take any input (yet), but it still needs the()
s){}
- the body of the function is written inside the curly braces (our function above doesn’t do anything yet, but it still needs the{}
s)
We can create a function declaration by wrapping up the percentage
variable and the expression for the percentage inside the function.
|
|
At the moment decimalNumber
is not wrapped up inside the body of the function. In the following sections, we will explore what happens when this is the case.
🎮 Playing computer
Learning Objectives
To understand how convertToPercentage
works we must build a mental model of how the computer executes our code. To build this model, we use a method called
We will use an interactive code visualiser to play computer.
activity
In a JavaScript program, each line is an instruction that will have some effect. For example, a line of code with a variable declaration means “store a new variable with this value in memory”. In the interactive widget, arrows are used to show which line just executed and which line is next to be executed.
Click next to see what happens when the computer executes the following program. Pay particular attention to what happens when the function convertToPercentage
is called.
🖼️ Global frame
As we step through the program, we keep track of two things: memory and the line that is being currently executed. We keep track of this information using a
The global frame is always the first frame that gets created when our program starts executing. It is like the starting point for our program, the place where code gets executed first. When we run the code above, decimalNumber
and convertToPercentage
are both stored in the global frame.
🖼️ Local frame
💡 tip
Whenever we call a function a new frame is created for executing the code inside that function. In the example above, we call the function convertToPercentage
on line 7 and then a new frame is created for convertToPercentage
. Inside the convertToPercentage
frame, the computer executes the instructions inside convertToPercentage
, storing new variables in memory and keeping track of the current line that is being executed.
🔭 Scope
Learning Objectives
The function convertToPercentage
will only be useful if we can access the percentage
string that it creates. Otherwise, we won’t be able to use the result of convertToPercentage
in other parts of our code. We can try accessing the percentage
variable outside the function body like this:
|
|
However if we run the code above, we get an error:
ReferenceError: percentage is not defined
We get an error because of
convertToPercentage
we also define a local scope - the region of code enclosed inside convertToPercentage
’s function body. This region is convertToPercentage
’s local scope. This means any variables we declare inside convertToPercentage
’s local scope can only be accessed within this region. If we attempt to reference a variable outside the scope where it was declared, then get a ReferenceError
.
📤 Returning from a function
Learning Objectives
We need a way to access the percentage string that is created inside convertToPercentage
. To access values created inside functions, we write
We can add a return statement to convertToPercentage
like this:
|
|
If we want, we could also remove the variable percentage
, since we can return the value of the expression directly:
|
|
🔎 Checking the output
We can store a function’s return value in a variable.
const result = Math.round(10.3);
console.log(result); // logs 10 to the console
We call Math.round
which takes the input 10.3
and then returns the rounded number 10
. So result
stores a value of 10
.
Math.round
is a function implemented by other developers and convertToPercentage
is a function we’re implementing, but calling convertToPercentage
is just like calling Math.round
.
Now we want to call the function convertToPercentage
and store the return value in a variable.
We can store the return value in a variable in exactly the same way:
|
|
Log out the value of result
to the console using console.log
.
|
|
This will now print the following when run:
50%
♻️ Reusing the function
Learning Objectives
Our goal is for convertToPercentage
to be reusable for any number. To check this goal, let’s call convertToPercentage
with different arguments and check the return value each time:
|
|
When we execute this code we want to log the target output for each input: 0.5
and 0.231
:
50%
23.1%
However, given the function’s current implementation, we get the following logs:
50%
50%
🌍 Global scope
At the moment, decimalNumber
is in the
Play computer and step through the code to check why we get the output below:
50%
50%
🏷️ Parameterising a function
Learning Objectives
At the moment, decimalNumber
is a variable in the global scope of our program:
const decimalNumber = 0.5; // defined in the global scope of our program
function convertToPercentage() {
const percentage = `${decimalNumber * 100}%`;
return percentage;
}
const output1 = convertToPercentage(0.5);
const output2 = convertToPercentage(0.231);
So long as decimalNumber
is always in the global scope, convertToPercentage
will always go to the global scope to get the value of decimalNumber
.
However, we want
convertToPercentage
to work for any input we pass to it.
To make a function work for any number, we need to handle inputs. We do this using a
decimalNumber
is still a variable - but as a parameter we don’t assign decimalNumber
a value inside the function’s body. It is a placeholder. When we call the function, we pass an input to the function, and the value of that input is assigned to the decimalNumber
parameter when the function is called. This happens automatically.
We can add a parameter decimalNumber
to our function:
|
|
In the example above, we’re calling convertToPercentage
twice: first with an input of 0.5
and second with an input of 0.231
. In JavaScript instead of input we use the word
()
. An argument means an input.
We’re calling convertToPercentage
twice: first with an argument of 0.5
and next with an argument of 0.231
.
Think of a function as a box. We put data in and then act on it using the rules in the box; at the end, the box gives us new data back. In programming we say that we pass arguments into a function, the function’s code is executed and we get a return value after the function has finished executing. Here’s a diagram:
Here’s a diagram of what happens when convertToPercentage
is passed a specific argument:
In this interactive widget we have defined a parameter decimalNumber
in the function declaration inside parentheses after the function name convertToPercentage
. In our mental model, a function call means going to convertToPercentage
and running the code inside the function.
Use the interactive widget to see what happens when the code above is executed. Pay close attention to what happens inside the convertToPercentage
frame.