๐ฅ JS1
What even is JavaScript?
- JS1
- โ๏ธ โ๏ธ Making a choice
- โป๏ธ Reusing the function
- โ๏ธ Comparing current and target output
- โ โ Interpreting feedback
- โ๏ธ Writing an assertion
- โ Errors
- โ Conditionally executing code
- โก๏ธ Arrow functions
- ๐ Installing Jest
- ๐ Starting a project
- ๐ฎ Playing computer
- ๐ฌ Declarations and statements
- ๐ผ First test case
- ๐ Running scripts
- ๐ค Returning from a function
- ๐ฆ Using packages
- ๐ Reusing variable names
- ๐ Interpreting errors
- ๐ Testing a sub-goal
- ๐ฌ Identifying missing tests
- ๐ญ Scope
- ๐ 12 vs 24 hour clock
- Interacting with computers
- ๐๏ธ Jest's Application Programming Interface
- ๐๏ธ Assembling the parts
- ๐๏ธ Sub-goal #2
- ๐ท๏ธ Anonymous functions
- ๐ท๏ธ Parameterising a function
- ๐ท๏ธ Saving expressions
- ๐น๏ธ Using an interface
- ๐ฅ๏ธ Terminal interface
- ๐จ๏ธ Logging
- ๐ผ๏ธ Testing frameworks
- ๐๏ธ Classifying data
- ๐๏ธ Generalising further
- ๐๏ธ Generalising further
- ๐ง Forming sub-goals
- ๐ง Interpreting this error
- ๐งฉ Percentages
- ๐งฎ Ordinal numbers
- ๐งฐ Install a UNIX based operating system
- ๐งฐ Install Node with nvm
- ๐งถ Accessing strings
- ๐งน Improving the code
- ๐งน Refactoring repetition
- ๐งพ Evaluating expressions
- ๐ช Declaring functions
- ๐ช Functions
JS1 block viewer
This block viewer lets you flick through all the existing blocks in the JS1 folder so you can choose what parts to add to your pages and what parts you might want to create, revise, or leave out.
It's literally just an alphabetical list of whatever is in this folder.
โ๏ธ โ๏ธ Making a choice
Learning Objectives
Our function works for morning inputs like "08:00"
. In this case, the function returns the target output of "08:00 am"
as required. However, at the moment, the output of formatAs12HourClock("23:00")
is "23:00 am"
.
๐ก We need to execute some different logic when the time is beyond midday
We can interpret this behaviour as a question:
We need to make two changes to our code.
- We need to do something different depending on whether the time is before midday. This is called running code conditionally.
- And then we need to know what we do if the time is after midday.
We don’t need to solve the whole problem at once. First let’s work out how to do something different depending on the time. We can worry about what we need to do differently once we’ve solved this problem.
๐ก tip
It’s easier to search for the solutions to smaller parts of problems than the whole problem.
Programming is all about breaking down problems into smaller pieces which we can solve.
โป๏ธ 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%
โ๏ธ Comparing current and target output
Learning Objectives
Let’s start with a function declaration of formatAs12HourClock
:
|
|
Whenever we call formatAs12HourClock
we expect it to return a particular value, which we call the ๐ฏ target output. For example, we expect formatAs12HourClock("08:00")
to return "08:00 am"
. "08:00 am"
is the ๐ฏ target output.
However, the current output is what the function actually returns right now.
๐๐ผ Work through the exercises
Let’s start with formatAs12HourClock
defined like this:
|
|
Suppose we now call the function like this:
formatAs12HourClock("05:30");
For the call above, what is the
a) the current output? b) the ๐ฏ target output?
๐๐ผ Keep going
Let’s continue with formatAs12HourClock
defined like this:
|
|
Suppose we call the function like this:
formatAs12HourClock("20:10");
For the call above, what is the
a) the current output? b) the ๐ฏ target output?
๐งฉ Comparing values
We have learned how to log values to the console. We can also compare two values. We check that a function produces some target output with a comparison.
We compare the current output of formatAs12HourClock("08:00")
with the target output of "08:00 am"
and ask: are these two values the same? We use a comparison operator to compare two expressions and check if they evaluate to the same value. We use the strict equality operator ===
to check if two values are the same.
Left equals Right
formatAs12HourClock("08:00") === "8:00 am";
===
checks if the values on the left and right of the operator are the same. We can think of formatAs12HourClock("08:00") === "8:00 am"
as the question: “Is the return value of formatAs12HourClock("08:00")
equal to "8:00 am"
?” This leads to the question:
What will the expression
formatAs12HourClock("08:00") === "8:00 am"
evaluate to?
โ โ Boolean values
Does 1 equal 0? Yes or No? True or False?
Some values are best represented as strings: any piece of text, a name, address, etc will most likely be stored using the string data type. The number data type can store numbers we use in calculations.
If we’re comparing two things, there are only two different states: true or false. This leads us to the boolean datatype, which only has true or false values. Whenever we compare two values with a comparison operator, we end up with a boolean value: true
or false
. It’s one or the other. It’s boolean.
// using the strict equality comparison expression
console.log(42 === 10 + 32);
// logs true
console.log(10 * 5 === 60);
// logs false
๐๐พ Now work through the exercises
Look at the code below and predict what each expression will evaluate to. Write your prediction down. Use the Node REPL to check your answers. Check out some relevant documentation if you’ve not seen a particular operator before.
|
|
๐๐ฟ Keep Going
Look at the code below and predict what each expression will evaluate to. Write your prediction down. Use the Node REPL to check your answers. Check out some relevant documentation if you’ve not seen a particular operator before
|
|
๐๐ฟ Keep Going
Checking misconceptions ๐ค
Look at the code below and then predict what the expression will evaluate to. Be sure to explain your answer too. Use the Node Repl to check your answer. Does the REPL output make sense?
Try using the Node Repl to check your answer. Does the REPL output make sense?
|
|
โ โ Interpreting feedback
Learning Objectives
We currently have a project structure like this:
week-4-test-example
โโโ get-ordinal-number.test.js
โโโ package.json
โโโ package-lock.json
โโโ node_modules
1 directory, 3 files
And get-ordinal-number.test.js
looks like this
test("converts 1 to an ordinal number", function () {
expect(getOrdinalNumber(1)).toEqual("1st");
});
After running the test above, we should get feedback indicating whether or not the test has passed.
Predict and explain
๐ข Defining the function
At the moment, our test feedback gives the following:
Just like we saw when the test
function wasn’t defined, the test code is throwing a
This means that we haven’t defined a function named getOrdinalNumber
, but we’re trying to use it.
To fix this, we can declare getOrdinalNumber
.
function getOrdinalNumber() {}
test("converts 1 to an ordinal number", function () {
expect(getOrdinalNumber(1)).toEqual("1st");
});
Now we can run the tests again and check the test feedback.
Assertion errors
We now get the following feedback:
Jest tells us 3 main things:
- The test case that failed
- The target output and the current output
- The line number where error occurred
Jest defines Expected and Received in the test feedback:
- Expected: “1st”
- Received:
undefined
exercise
What are the values of Expected and Received in the test output?
How do Received and Expected match up with the target output and expected output ?
What line number did the test case fail on?
Avoiding repetition
When we wrote console.assert
tests before, we ended up extracting variables because we were re-using values.
Without Jest, this assertion would probably have looked more like:
const input = 1;
const targetOutput = "1st";
const currentOutput = getOrdinalNumber(input);
console.assert(targetOutput === currentOutput, `Expected ${targetOutput} but got ${currentOutput}`);
Because Jest makes a useful error message for us telling us what the target and current outputs are, we could write this all in one line. We didn’t need a variable so we could pass "1st"
both to getOrdinalNumber
and into the message.
Jest helped us to avoid writing more repetitive code.
Passing getOrdinalNumber
We can now pass the test by implementing functionality for the first test case. We could write the following:
get-ordinal-number.test.js
:
|
|
โ๏ธ Writing an assertion
Learning Objectives
We now have 2 new concepts: booleans and comparisons. From these concepts, we can now write an assertion about the function formatAs12HourClock
.
๐ An assertion is a check that our code behaves in a particular way: this check can either succeed or fail.
So far we’ve used the log function console.log
. We can write assertions using another function: console.assert
. The documentation says that console.assert
writes an error message to the console if the assertion is false. If the assertion is true, nothing happens. As 10 + 32 === 42
evaluates to true, no message will be written to the console.
const calculation = 10 + 32;
const result = 42;
console.assert(calculation === result);
Experiment with assertions
const calculation = 10 + 32;
const result = 42;
console.assert(calculation === result);
Change the value assigned to the result
variable to make the assertion fail. Check the output you get in the console.
๐๐ฟ Keep Going
Let’s look at an example using formatAs12HourClock
:
function formatAs12HourClock() {}
console.assert(formatAs12HourClock("08:00") === "08:00 am");
Predict and explain if the assertion will succeed or fail. Pay particular attention to the return value of formatAs12HourClock
.
Clarity with arguments
It would be useful to have more information as to why this assertion failed. We can pass an additional argument to console.assert
:
function formatAs12HourClock() {}
console.assert(
formatAs12HourClock("08:00") === "08:00 am",
`current output: ${formatAs12HourClock("08:00")}, target output: 08:00 am`
);
Let’s break down these arguments to make sense of what’s going on:
- first argument -
formatAs12HourClock("08:00") === "08:00 am"
- the condition we’re checking - second argument -
`current output: ${formatAs12HourClock("08:00")}, target output: 08:00 am`
- a message string that will be logged to the console if the condition is false.
๐งน Refactor
We can tidy up the assertion even further. As weโre reusing the same expressions, we can store their result in variables with meaningful names so we can reuse them:
|
|
Execute this code; we now get a log in the console:
Assertion failed: current output: undefined, target output: 08:00 am
๐งฐ Implementing the functionality
On line 3, the function is being passed a single argument "08:00"
. But our function ignores it: it doesn’t declare any parameters. We can parameterise the function and label the input as time
:
function formatAs12HourClock(time) {}
According to our assertion, when we call our function with an input of "08:00"
we need to create an output of "08:00 am"
. If we add "am"
to the time
, we’ll get the target output. We can update our function with a template literal, set the return value and then re-run our code including our assertion to check the function is returning the correct value.
๐ We can and should continually check our assertions to see if our functionโs current output meets our target output.
|
|
โ Nothing is printed to the console, so this assertion is passing ๐
Try yourself
๐ผ Checking different cases
So far weโve only created assertions that check the functionโs behaviour for times between midnight and midday. In these cases, there is a pattern: take the current time and add " am"
to the end.
But this isn’t the pattern we need to follow for all times. To make sure our function works for all times, we need to write more assertions.
We need to assert that the function behaves correctly when the time is later than midday.
Before we think about any code, we should think about our problem. Separating problem and code lets us focus better. First we can focus on the data. Then we can focus on the code.
First, let’s think of an example time in 24 hour clock - we’ll pick 23:00
.
Next, let’s work out what we expect our example time to be in 12 hour clock: 11:00 pm
.
Now that we’ve thought about the problem, we can write the code. Let’s create an assertion for our function when passed an input of "23:00"
:
|
|
Save this code to a file. Before you run it in Node, write down what you think will happen. Then run it with Node - compare what you saw with what you predicted.
โ 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.
Predict, Explain
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.
Predict, Run, Observe
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.โ Conditionally executing code
Learning Objectives
Recall: JavaScript programs are built up from sequences of declarations and statements.
In programming, we can use an if
statement to execute some code when a given condition is true
. In JavaScript, we can write an if
statement as follows:
if (condition) {
// do some code in here
}
The if
statement consists of:
if
keyword: this is the start of theif
statementcondition
: condition is an expression that evaluates to true or false. The condition must be in a pair of parentheses:()
{}
: a block statement: any code we want to execute if the condition is true goes inside the curly braces here
We can represent this with a diagram too:
๐น๏ธ Playing computer
function checkDivisibility(a, b) {
if (a % b === 0) {
return `${a} is divisible by ${b}`;
}
return `${a} is not divisible by ${b}`;
}
console.log(checkDivisibility(10, 2));
console.log(checkDivisibility(50, 3));
Play computer with the example above, step through each line in the code and work out happens each time the function is called.
- What is the return value each time the function is called?
- What value is assigned to the parameters
a
andb
each time the function is called?
If you’ve not encountered any syntax before, remember to look it up in some documentation.
๐น๏ธ Playing computer
function getCountryCode(phoneNumber) {
if (phoneNumber.startsWith("+44")) {
return "UK";
}
}
getCountryCode("+447831620328");
getCountryCode("+989871783972");
Play computer with the example above, step through each line in the code and work out happens each time the function is called.
- What is the return value each time the function is called?
- What value is assigned to the
phoneNumber
parameter each time the function is called? - Try describing the purpose of this function - what should it do?
โก๏ธ Arrow functions
Learning Objectives
As we write more code, we are going to write lots and lots of
JavaScript has even shorter ways of writing an anonymous function. These four functions all do the same thing:
function convertToPercentage(decimalNumber) {
return `${decimalNumber * 100}%`;
}
// We can skip the name of the function if we don't need it to have a name.
function (decimalNumber) {
return `${decimalNumber * 100}%`;
}
// We can also skip the keyword 'function'.
// If we do this, we need an arrow between our parameters and the function body.
(decimalNumber) => {
return `${decimalNumber * 100}%`;
}
// If our function just returns a single value,
// without needing any other statements in our function,
// we can even skip the return keyword.
(decimalNumber) => `${decimalNumber * 100}%`;
This can make it easier and quicker to write functions. It also reduces the number of things we need to read in a function.
Applying all of these techniques, we can rewrite our Jest test with fewer words:
test("works for any number ending in 1", () => {
expect(getOrdinalNumber(1)).toBe("1st");
expect(getOrdinalNumber(11)).toBe("11th");
expect(getOrdinalNumber(21)).toBe("21st");
});
It doesn’t matter whether you use arrow functions or use the function
keyword - they work the same.
Not all arrow functions are anonymous - you can assign them to a variable too:
const convertToPercentage = (decimalNumber) => `${decimalNumber * 100}%`;
Anonymous vs named refers to whether the function is bound to a name, not whether it was defined with the function
keyword or an =>
.
๐ Installing Jest
Learning Objectives
Jest is a package used to help us to write and run test cases in JavaScript. Our next step will be to figure out how to install the Jest package on our machine, so that we can use it in our project.
We can find out more about the Jest framework from the documentation online.
In the Getting started section of the documentation, Jest gives us the following command:
npm install jest --save-dev
Let’s break down the different parts of this command.
npm
-npm
is the package management tool we are using, so we need to run it.install
-npm
has a subcommand calledinstall
. We use it to download a package from the npm registry onto our machine and install it.jest
- this is the name of the package we want to install on our machine.--save-dev
- this means the package is needed for development but not needed in production. Our ordinal app doesn’t needjest
to run, but we need it to help us develop it.
So overall we can think of this command as saying: “Please go to the npm database, find the Jest package and install it on my local machine”
Let’s execute this command in the same directory as the package.json
.
To double check we’re in the correct directory, we can run pwd
:
$ pwd
.../The Docs/ordinal-testing-example
pwd
is telling us we’re in the ordinal-testing-example
directory.
We need to double check the package.json
is also there too.
$ ls
package.json
Now we can execute the command
npm install --save-dev jest
Our project structure will now look as follows:
ordinal-testing-example
โโโ node_modules
โโโ package-lock.json
โโโ package.json
1 directory, 3 files
After running the command, we now have a directory called node_modules
in our project too.
The
node_modules
directory contains all the code from thedependencies ๐งถ we installed in our project. You won’t need to look inside the๐งถ dependencies A dependency is a package that your project depends upon. node_modules
directory - you just need to know it contains the code for Jest and any other dependencies we install in our project.
Running the npm
command also updated our package.json
file for us:
{
"name": "week-4-test-example",
"description": "An example application showing how to write tests using the jest framework",
"devDependencies": {
"jest": "^29.5.0"
}
}
We’ve now got some additional information inside the package.json
:
"devDependencies": {
"jest": "^29.5.0"
}
๐น๏ธ Follow along
Install Jest on your local machine. Double check you’ve got the correct files and folders written to your local machine.
๐ Starting a project
Learning Objectives
Let’s start a brand new project in a directory called ordinal-testing-example
and create a file called package.json
in our project.
- Open your terminal and ensure you’re inside the
<span class="c-our-name">The Docs</span>
directory you created earlier in the course. - Make a new directory on your local machine called
ordinal-testing-example
. - Change directory into
ordinal-testing-example
and double-check your current working directory.
% pwd
.../<span class="c-our-name">The Docs</span>/ordinal-testing-example
๐๐ฝ Now create a package.json
file
๐ก Package
package.json
stores useful information about our project, like the name, description, and version. It is written in the JSON format.- Create a
package.json
inordinal-testing-example
. - Make sure it contains a name and description.
๐๐ฝ Need help? Follow step by step instructions
- Create a
package.json
file in your project directory:
touch package.json
- Add a name to it like this:
{
"name": "ordinal-testing-example"
}
- Add a description:
{
"name": "ordinal-testing-example",
"description": "An example application showing how to write tests using the jest framework"
}
We can continue adding more information about our project as the project grows. For now, double-check we only have a package.json
in our project:
% ls
package.json
๐ฎ 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.
๐ฃ Step through
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
๐ก recall
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.
๐ฌ 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.
๐ผ First test case
Learning Objectives
๐ฏ Goal: Write a test for the case below, using Jest:
Case 1 ๐ผ
Our first case is that the ordinal number for 1
should equal "1st"
.
We can create a file called get-ordinal-number.test.js
and write our first test there.
We can use documentation to work out how to write our first test using Jest.
get-ordinal-number.test.js
:
test("converts 1 to an ordinal number", function() {});
Let’s break down this syntax.
The test
function is part of the Jest API, a function we use to perform a particular task.
In particular, we’re using test
to create a test case.
Before, we could use Math.round
and console.log
because Math
and console
are provided for us by Node.
test
isn’t provided by Node, but when we ask Jest to run our tests, it will make sure the test
function exists and that our code can use it.
Let’s break down the arguments we’re passing to test
:
- 1st argument:
"converts 1 to an ordinal number"
, a string which describes the behaviour we’re testing for - 2nd argument:
function() {}
, we will write some assertions in thisfunction() {}
to check the behaviour
โ๏ธ Creating assertions
We need to write an assertion inside the body of function() {}
inside get-ordinal-number.test.js
get-ordinal-number.test.js
:
test("converts 1 to an ordinal number", function() {});
๐ก Recall
In this example, we want to check that the following is true:
We expect getOrdinalNumber(1)
to be "1st"
An assertion in Jest looks like this:
expect(currentOutput).toEqual(targetOutput);
The function toEqual
is used to check that the current output of getOrdinalNumber(1)
and the target output of "1st"
are equal to each other.
toEqual
is just one example of a function called a matcher.
A matcher is a function we use to compare values in Jest.
So the whole test looks like this:
test("converts 1 to an ordinal number", function() {
expect(getOrdinalNumber(1)).toEqual("1st");
});
๐ Running tests
We can try running the file get-ordinal-number.test.js
with node in the following way:
node get-ordinal-number.test.js
but we get an error:
ReferenceError: test is not defined
Googling “ReferenceError JavaScript”, MDN tells us this is because we’re referring to a variable that doesn’t exist. This is because test
isnโt defined anywhere in the file.
We need to execute this file so that the Jest API is available in our file. We can do this by running the test file using Jest: we do this using an npm script.
The “scripts” section of the package.json
is where we can write useful commands we’ll use in our project. We can add a “scripts” section to the package.json
so that it reads as follows:
|
|
Finally, we’ll need to run our tests.
Now we can run the command npm test
.
When we execute the command, npm test
, we will run npm
, and npm
will look inside the “scripts” section of the package.json
and look up the command for “test” - in this case, “jest”. npm
will then run “jest”.
We can’t ourselves just run jest
on the command line, because it isn’t installed in a place our terminal knows about. But when npm
runs a script, it will make sure all dependencies installed for the project are available.
๐ 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?”
๐ค 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%
๐ฆ Using packages
Learning Objectives
When writing software, we continually make use of software written by other developers. We can call these
We use packages so that we don’t have to solve every problem ourselves. Other people have often solved some things we need to do really well. Using other people’s solutions to parts of a problem means we can focus our time and effort on what’s special about our problem.
Imagine we wanted to work out what the time is in a user’s city. Instead of writing code to work out the time for every city’s time zone (and when they change!), we can use a package some “city time” experts have written, and which they keep up to date.
Different programming languages give developers different ways of accessing packages for use in their code. We will use
npm
, downloads and manages useful packages of code from the npm registry.
๐ Reusing variable names
Learning Objectives
We saw this error:
SyntaxError: Identifier 'currentOutput' has already been declared
Now that we understand it, let’s fix it.
We’re not allowed to declare a new variable with the same name as an old one. Both lines 5 and 12 here try to declare a new variable named currentOutput
:
|
|
remember
Make sure you understand the error message before you try to fix the code.
If you don’t understand what’s wrong, you probably won’t be able to fix it!
Fixing the error
We want to do multiple assertions. And we’re using variables in our assertions. But we’re not allowed to use the same name twice. The simplest way we can fix this problem is by changing the name of the second variable. Remember to also change where we use the variable, not just where we declare it!
|
|
Trying again
After making this change, let’s try running our code again. We get this output:
% node clock-example.js
/Users/dwh/CYF/clock-example.js:13
const targetOutput = "11:00 pm";
^
SyntaxError: Identifier 'targetOutput' has already been declared
at wrapSafe (node:internal/modules/cjs/loader:1383:18)
at Module._compile (node:internal/modules/cjs/loader:1412:20)
at Module._extensions..js (node:internal/modules/cjs/loader:1551:10)
at Module.load (node:internal/modules/cjs/loader:1282:32)
at Module._load (node:internal/modules/cjs/loader:1098:12)
at TracingChannel.traceSync (node:diagnostics_channel:315:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:215:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:5)
at node:internal/main/run_main_module:30:49
Node.js v22.4.1
Fortunately, we’ve seen this kind of error before. It’s exactly the same as the last one, but about the targetOutput
variable on line 13, instead of the currentOutput
variable on line 12.
exercise
Now the second assertion fails with the following message:
% node clock-example.js
Assertion failed: current output: 23:00 am, target output: 11:00 pm
We expect this. Our function just adds " am" to the end of the time, which only works for times in the morning. We have proven our code isn’t complete yet. Next we can fix it so that this test passes.
Exercise 1
formatAs12HourClock
returns the target output when passed another time input in the afternoon.๐ Interpreting errors
Learning Objectives
An error is thrown
When we run the file with Node, we get an error in the console:
% node clock-example.js
/Users/dwh/CYF/clock-example.js:12
const currentOutput = formatAs12HourClock("23:00");
^
SyntaxError: Identifier 'currentOutput' has already been declared
at wrapSafe (node:internal/modules/cjs/loader:1383:18)
at Module._compile (node:internal/modules/cjs/loader:1412:20)
at Module._extensions..js (node:internal/modules/cjs/loader:1551:10)
at Module.load (node:internal/modules/cjs/loader:1282:32)
at Module._load (node:internal/modules/cjs/loader:1098:12)
at TracingChannel.traceSync (node:diagnostics_channel:315:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:215:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:5)
at node:internal/main/run_main_module:30:49
Node.js v22.4.1
When an error is thrown, the program stops and an error report is sent to the user.
As programmers, we will see a lot of errors. It’s useful for us to be able to read them.
Interpreting the output
Each line of output here tells us something useful.
The first line is:
/Users/dwh/CYF/clock-example.js:12
Your output was probably different. But it will have the same parts. Some text, then a colon (:
), then a number.
Exercise
Work out what the parts of this line mean.
Why are they different on my computer than yours?
How can we use both pieces of information?
Often, looking at one line of a file is enough to understand what’s wrong. So the message also shows us a copy of the line that caused the problem:
const currentOutput = formatAs12HourClock("23:00");
Then the output tells us the error message:
SyntaxError: Identifier 'currentOutput' has already been declared
We may not know what this means yet, but it’s something we can learn about.
Exercise
Each line starting with “at” is showing us a “Stack trace”. We’ll skip over this for now. In the future we’ll see how it can be useful to us.
Finally, we have this line:
Node.js v22.4.1
Exercise
What does this line mean? Why might it be useful to know this information?
Add your answer to your spaced repetition calendar. Your understanding of this will grow over time. Answer the question again in the future, and compare it to your previous answer.
๐ Testing a sub-goal
Learning Objectives
Earlier we defined a sub-goal to find a value for the hours from the time
input. We’ve found that Number(time.slice(0,2))
is an expression that evaluates to the hours from time
. So we can write an if
statement using this expression:
if (Number(time.slice(0, 2)) > 12) {
}
If the time is "23:00"
then the expression Number(time.slice(0, 2)) > 12
will evaluate to true
and the body of the if
statement will be executed.
This if
statement is implementing the following part of the diagram from earlier:
Before we worry about how we handle times in the afternoon, we can check that we’ve solved this sub-goal.
We can check that we are correctly identifying times in the afternoon by adding in our if
statement (which we think is correct), with a placeholder body:
|
|
If we run our code, we expect the current output of the 23:00
test-case to have changed. It won’t be correct - the assertion will still fail. But if it hasn’t changed, we know our if
statement is wrong.
% node clock-example.js
Assertion failed: current output: Don't know how to handle times in the afternoon yet, target output: 11:00 pm
Even though we know the code on line 3 is incorrect, this was a useful step. It allowed us to run our code more often, and check that we’re on the right track.
remember
Run your code as often as you can.
Make small changes and run your code.
Sometimes we write code we know we will delete because it lets us get feedback sooner.
Now we can focus on just one problem: how to handle times after midday (i.e. fixing line 3). We don’t need to worry about both detecting the time and handling it.
If the output of this assert still printed "23:00 am"
we would have stopped here and debugged that. Again, we could focus on just one problem.
๐ฌ Identifying missing tests
Learning Objectives
We started off writing one test for our code - checking that it correctly handled the input 08:00
. We wrote an implementation that passed all our (1) tests!
Then we realised there was a bug - it didn’t handle times after midday correctly. So we wrote another test - for the input 23:00
. We saw our implementation failed that test. And we fixed it. And we had an implementation that passed all our (2) tests!
When will we be happy that our implementation works for all possible inputs? When do we have enough tests?
Groups of input
One way to approach this is to think about what groups of input our problem may have.
We’ve already identified two examples of groups of input to the problem of converting 24 hour clocks to 12 hour clocks: Times before midday and times after midday.
One way to find extra cases to consider (and extra tests to write) is to try to think of different groups of input.
For example, some times are exactly on the hour (end in :00
) and other times have a non-zero number of minutes.
exercise
Set a timer for 5 minutes. Write down as many groups of input to this problem as you can. Write an example assertion for each one.
If you find any bugs in the implementation, go fix them!
Edge cases
Another way to consider this question is to think about what edge cases there are in the problem.
Definition: edge case
An edge case is an unusual value which may need special treatment.
Some examples are: the minimum value, the maximum value, and the boundary between two groups of input.
- Some example edge cases for this problem are:
00:00
- The minimum time, which is
12:00 am
in 12 hour clock.
This is also the only hour that is bigger in 12 hour clock than 24 hour clock. 24:00
- The maximum time.
12:00
- Where time changes from am to pm. The edge between morning times and afternoon times.
Often these edge cases are where bugs happen.
exercise
Set a timer for 5 minutes. Write down as many edge cases of input to this problem as you can. Write an example assertion for each one.
If you find any bugs in the implementation, go fix them!
๐ญ 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
.
๐ 12 vs 24 hour clock
Learning Objectives
We usually write the time in one of two ways: the analogue 12 hour clock or the digital 24 hour clock. The 12 hour clock counts up to 12: it resets at midday. The 24 hour clock counts up to 24: it resets at midnight.
2๏ธโฃ4๏ธโฃ hour time | ๐ 12 hour time |
---|---|
09:00 | 09:00 am |
10:00 | 10:00 am |
11:30 | 11:30 am |
12:00 | 12:00 pm |
13:43 | 1:43 pm |
14:00 | 2:00 pm |
We use the notation “HH:MM”. HH is our stand-in for the hours value. MM is our stand-in for the minutes value.
๐งฉ Stating the problem
Let’s pose a problem: given any time in 24 hour clock, we want to format it as a 12 hour clock time. To achieve this goal, we’re going to implement a function formatAs12HourClock
.
Given a time in 24 hour clock
When we call formatAs12HourClock
Then we get back a string representing the same time in 12 hour clock.
๐งช Our tests:
I expect formatAs12HourClock("09:00")
to be "09:00 am"
I expect formatAs12HourClock("14:19")
to be "2:19 pm"
Interacting with computers
Learning Objectives
Modern computers are complicated: it would be too difficult and time-consuming to list all the components that make up a modern computer. So to build our mental model, we will use this simple definition of a computer:
A computer is a device used to store and perform operations on data.
๐๏ธ Jest's Application Programming Interface
Learning Objectives
With Jest installed, we need to figure out how to use the Jest framework to write tests.
This means we need to look at
API stands for
- Application
- Programming
- Interface.
We’ve encountered the word interface already.
But we can break down each word in this acronym to understand it altogether.
An application is a program or piece of software designed to serve some purpose.
Programming refers to the process of writing code or software.
An ๐น๏ธinterface is a shared boundary between two or more systems.
We’ve encountered several functions like console.log
and Math.round
already.
console.log
and Math.round
are APIs.
console.log
is actually implemented in a different language (C++), but that doesn’t matter - its functionality is exposed to us when we write JavaScript, and we don’t need to care how it’s actually implemented or how it works.
Jest provides an API so we can write tests. So we have to find out about the Jest API to start writing tests with Jest.
๐๏ธ Assembling the parts
Learning Objectives
Earlier we defined a sub-goal to find a value for the hours from the time
input. We’ve found that Number(time.slice(0,2))
is an expression that evaluates to the hours from time
. So we can write an if
statement using this expression:
if (Number(time.slice(0, 2)) > 12) {
}
If the time is "23:00"
then the expression Number(time.slice(0, 2)) > 12
will evaluate to true
and the body of the if
statement will be executed.
This if
statement is implementing the following part of the diagram from earlier:
Now we can format the string using our approach from earlier:
weโll need to append "pm"
to the string expression and subtract 12 from the hours. So we get the following:
if (Number(time.slice(0, 2)) > 12) {
return `${time.slice(0, 2) - 12}:00 pm`;
}
The return
statement above implements the following steps we set out earlier:
Now we can re-run our assertions from earlier to check our function behaves as target.
๐๏ธ Sub-goal #2
Learning Objectives
Now we can think about what we do when we’ve identified a time is after midday.
Earlier, we observed what to do when the time goes beyond midday: subtract 12 from the hours time to get the new hours for the 12 hour clock time.
Before writing code, we can define our approach in steps:
Starting with an input like "23:00"
:
Now we can format the string using our approach from earlier:
weโll need to append "pm"
to the string expression and subtract 12 from the hours. So we get the following:
if (Number(time.slice(0, 2)) > 12) {
return `${Number(time.slice(0, 2)) - 12}:00 pm`;
}
The return
statement above implements the following steps we set out earlier:
Now we can re-run our assertions from earlier to check our function behaves as target.
๐ท๏ธ Anonymous functions
Learning Objectives
We have seen functions written like this:
function convertToPercentage(decimalNumber) {
return `${decimalNumber * 100}%`;
}
In our Jest test, we wrote a function differently:
function() {
expect(getOrdinalNumber(1)).toBe("1st");
expect(getOrdinalNumber(11)).toBe("11th");
expect(getOrdinalNumber(21)).toBe("21st");
}
๐ Spot the difference
We didn’t give a name to the function in our Jest test.
This is ok, because we don’t need it to have a name. We don’t call the function by name. We passed the function as an
test
function. The test
function takes the function as a
We can imagine the test
function is defined like this:
function test(name, testFunction) {
// Call the passed test function
testFunction();
}
Inside test
our function is labelled with the name testFunction
. It would be labelled this whatever we named it before. Even if we didn’t label it ourselves at all, it is still labelled with the name testFunction
inside test
.
Because it doesn’t matter what we named the function (because we never call it by name), we didn’t give it a name.
Otherwise, these two functions act the same. The only difference between them is whether we created a variable name for the function in the scope where we defined it.
๐ท๏ธ 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.
๐ท๏ธ 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";
๐ string literal
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.
๐น๏ธ Using an interface
Learning Objectives
We want to use computers without understanding exactly how they are built. Every day we ask machines to do things, and usually we have no idea how these machines work. We could not use modern technology if we had to understand it completely before we could use it; it would take too long! Instead we use
Think about a cash machine (ATM). We go to a hole in the wall with a screen and a keypad. The screen and the keypad are the user interface. We press the buttons and ask the machine to do things - like giving our balance, or withdrawing some money from an account. We don’t need to understand how the information it tells us comes on the screen.
Exercise
Define the user interface for these devices:
- a calculator
- a microwave
- a desktop lamp
- Alexa
- ChatGPT
๐ฅ๏ธ Terminal interface
Learning Objectives
Programmers need interfaces to ask computers to do things. A computer terminal is an interface where programmers can issue commands to a computer. Because users enter text instructions and receive text output, we say that the terminal is a text-based interface.
Interface via the terminal
We can input a command into the prompt and hit enter. The terminal then passes this command to the computer to execute. Find your own terminal and input the ls
command:
ls
๐๏ธ Writing computer instructions
We can issue commands to the computer using the terminal. These commands are instructions that the computer knows how to interpret.
The computer knows ls
means “list the files and directories in the current directory”.
During the execution of a computer program, a computer will store and modify
ls
is a shell command. Shell is a programming language we use to interact with the files and folders on our computer. You already know at least two more programming languages. Can you name them?
Definition: programming language
๐จ๏ธ Logging
Learning Objectives
Printing to the terminal
To look at values when our program runs, we can use a function called console.log
.
๐ก console.log
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.
๐ผ๏ธ Testing frameworks
Learning Objectives
To help us think about the requirements of getOrdinalNumber
, let’s consider one case:
๐ผ Case 1
const input = 1;
const currentOutput = getOrdinalNumber(input);
const targetOutput = "1st";
Case 1 states that when getOrdinalNumber
is called with an input of 1, it has a target output of “1st”. Our first step is to check that getOrdinalNumber
works as we have stated.
We have used console.assert
to write assertions to write tests for our code before. console.assert
is a useful building block, but it is limited. Now we will write tests using a
๐ A test is any piece of code that runs an assertion on the code we’re testing
We want our tests to:
- be easy to write
- be easy to read
- give clear feedback on what the current output is
- give clear feedback on what the target output is
- allows us to easily write multiple test cases
A test framework will help us build test cases like this.
๐ง๐ฝ๐ง๐ฟ Dialogue
We can use a short dialogue to think about why we want to use a testing framework:
- ๐ง๐ฝ Bรผลra
- Ali, looks like I need to implement a function.
- ๐ง๐ฟ Ali
- Cool. How are you going to check it works?
- ๐ง๐ฝ Bรผลra
- I’m going to use tests to check that the function gives the target output as described in the specification
- ๐ง๐ฟ Ali
- Seems wise. How are you going to write a lot of tests efficiently?
- ๐ง๐ฝ Bรผลra
- I’m going to use a testing framework to write test cases quickly. The framework will make sure that the tests give fast, reliable feedback.
๐๏ธ Classifying data
Learning Objectives
Weโre going to focus on the JavaScript programming language.
A programming language organises data with rules so we understand what we can and cannot do with it. Languages split data up into different categories called
Number data type
10
is an example of the number data type.
3.14
is also part of the number data type; both integers (whole numbers) and non-integers are types of number.
-15
is also part of the number data type. Positive and negative numbers, as well as 0
, are all types of number.
String data type
A string is a sequence of characters demarcated by quotes.
"Code Your Future"
๐งฎ Creating expressions
Think of the numbers 10
and 32
. We could ask questions about these numbers, like: What is the sum of 10 and 32?
Another way to say this is what do 10 and 32 add up to? In English we can say this in many ways, but in JavaScript we can say this using numbers and an operator. Just like in mathematics, โthe sum of 10 and 32โ can be written as 10 + 32
:
10 + 32
In JavaScript, +
is an
+
represents the operation โmake the sum of the numbersโ. It symbolises addition.
The combination of symbols 10 + 32
is an
10 + 32
evaluates to the value 42
.
10
is also an expression. It evaluates to the value 10
.
"Code Your Future"
and "Code Your " + "Future"
are also both expressions - both evaluate to the value "Code Your Future"
.
๐๏ธ Generalising further
Learning Objectives
In English, ordinal numbers mostly follow the same pattern.
Numbers ending in 1 will generally have an ordinal number ending in “st”.
Here are some examples of this pattern,
1st, 11th, 21st, 31st, 41st,…
All the numbers ending in 1 will continue to end in "st"
, with the exception of 11.
11 is slightly different and ends with a "th"
.
We can now update our test case to check that getOrdinalNumber
works for lots of different numbers ending in 1.
get-ordinal-number.test.js
:
|
|
We’ve also updated the test description because we’re adding more assertions and checking slightly different functionality.
๐ง Implement
getOrdinalNumber
so it passes the test case above.๐๏ธ Generalising further
Learning Objectives
๐งฐ Handling outliers
We can now implement functionality for getOrdinalNumber
.
Our strategy might be something like this:
Most of the time we just need to return the number with “st” on the end.
However, 11 is an outlier: it doesn’t conform to this pattern.
So our current strategy for this test case will be to check if the number is 11 first and do something differently ( return "11th"
): otherwise we return the default value of num with "st"
on the end.
Here’s the implementation:
|
|
ย ๐งญ Future strategies
Now, we’ve handled any numerical inputs ending in 1. We can try to build up functionality for any number ending in 2.
We can start by adding a test case that only asserts something about the input of 2.
We cannot add this assertion to the first test case. The first test case checks for inputs that end in a 1. To check the case when the input ends in 2, we need to introduce a new test case.
|
|
Check the test output
Here’s the test feedback for the test above:
Play computer with getOrdinalNumber
when it is called with an input of 2
Double check you agree with the test feedback in this case.
Before coding, outline a strategy for handling the second test case.
Further assertions</span>
Further assertions</span>
getOrdinalNumber
works for other numerical inputs ending in 2.We know that this doesn’t solve all cases (e.g. it will give the wrong answer for getOrdinalNumber(2)), but it’s a start, and we have a test-case showing that it works in one case.
This points out a limitation of tests. They only test the cases we wrote tests for. Right now, all our tests are passing, but we know our solution doesn’t work for all inputs!
In order to generalise our solution (to make it work “in general” rather than just for one specific case), It’s important to think about what different groups of inputs we may expect.
๐ง Forming sub-goals
Learning Objectives
For formatAs12HourClock
our strategy for inputs like "23:00"
involves checking if the hours
value is less than 12. For this purpose, we can use the greater than comparison operator >
.
>
will check if the value on the operator’s left side is less than the value on the operator’s right side.
So 3 > 12
evaluates to false
, as 3
is not greater than 12
.
So provided we have an expression for hours, we can write an if
statement as follows:
if (/* here goes an expression here that evaluates to the hours */ < 12) {
// do code to format the 12 hours
}
To complete the logic, we can form a
Any time we’re solving a problem, we can define a goal - a thing we need to achieve to consider the problem solved. We can break a problem into smaller problems, each with its own sub-goal. The problem-solving process involves continually breaking down problems into smaller manageable problems, each with its own sub-goal.
For the implementation of formatAs12HourClock
, we can form a sub-goal as follows:
๐ฏ Sub-goal: Find the hours value from the
time
input
๐ง Interpreting this error
Learning Objectives
We saw this error - let’s try to understand it:
SyntaxError: Identifier 'currentOutput' has already been declared
Knowing what we changed
It can be useful to remember when our code last worked, and what we changed since then.
๐ก tip
Source control can help here.
If you commit your code every time you make something work, you can use git to easily see what changed since your last commit.
When we just had the first 10 lines of code here, everything worked. When we added the rest, we got this error:
|
|
๐ก Tip
Run your code very often.
If we changed one thing since our code last worked, we know what change is the problem. If we have written 100 lines of code since we last saw it work, we have 100 possible problems.
Interpreting the error message
The error message tries to tell us useful information:
SyntaxError: Identifier 'currentOutput' has already been declared
When we get an error, we should make sure we understand all of the words in the error message. If we don’t, we should look them up or ask someone.
Exercise
For each word in this error message, write down what it means.
If you don’t know a word, look it up.
Make sure you understand each word. Make sure you could explain the word to someone without reading the answer to them.
Expand for example definitions - only expand this after you have written yours down. Compare your answers with these.
- SyntaxError - If we Google “JavaScript SyntaxError”, MDN tells us this is “an error when trying to interpret syntactically invalid code”. So - we wrote some code which isn’t allowed.
- Identifier - If we Google “JavaScript Identifier”, MDN tells us: this is “a sequence of characters in the code that identifies a variable, function, or property”. On line 12, the identifier is the variable name:
currentOutput
. - currentOutput - This is the variable name we used in our code. This is the identifier that the error is about.
- has, already, and been are all standard English words with no special meaning.
- declared - We learnt about this already in this course - a declaration is where we make a new name (e.g. a new variable) in JavaScript.
Reading that back, we can rephrase this error message:
We wrote some code which isn’t allowed. We tried to declare a new variable named currentOutput
. But we had already declared something named currentOutput
.
๐งฉ 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.
๐งฎ Ordinal numbers
Learning Objectives
๐ข Let’s imagine you’re working in a 10 storey office building. There are 10 different levels. We need a way to describe each level of the building. We start on the ground floor of the building - level with the ground. We use an ordinal number to describe the other levels in the building.
To form the ordinal number we take a number and add the correct
โ๐ฟ Up from the ground floor, we are then on the 1st floor (first floor) โ๐ฝ Up from the 1st floor, we are on the 2nd floor (second floor)
number | + suffix | = ordinal number |
---|---|---|
1 | st | 1st |
2 | nd | 2nd |
What will the ordinal number be for:
a) 21?
b) 40?
c) 49?
d) 13?
Use ordinal numbers to write the days of the month for the following events:
a) Tomorrow b) A week from now c) Easter Sunday 2024 d) When is Eid expected to occur in 2024
- 1st
- 2nd
- 3rd
- 4th
- 5th
- 6th
- 7th
- 8th
- 9th
- 10th
๐ Specification
Let’s consider a function called getOrdinalNumber
that needs to work like this:
- it takes one argument - a whole number, like 1, 2, 3, etc
- it returns a string that represents the ordinal number
getOrdinalNumber(1); // returns "1st";
getOrdinalNumber(2); // returns "2nd";
getOrdinalNumber(6); // returns "6th";
The requirements above form a
getOrdinalNumber
to behave when it is called with different inputs.
๐งฐ Install a UNIX based operating system
Learning Objectives
If you get stuck on any of the below or above instructions, please post in your class channel on Slack.
You probably already have this if you have done previous modules.
โ ๏ธ warning
Code Your Future does not support Windows.
We have found that we don’t reliably have volunteers at class who can debug Windows problems, and they frequently come up, stopping trainees from making progress.
We do not support Windows so as to not waste lots of trainee and volunteer time. We have made this decision even though Windows is a popular operating system and has features like WSL which help to get experience with Unix.
You must use a Unix-based operating system at Code Your Future.
If you have a Mac or Linux machine already, you already have a UNIX based operating system. All CYF-supplied laptops run Mac OS or Linux. If you have your own machine and it runs Windows, you should already have set up a Linux partition.
If you have still not done this, you must do it now. We cannot support learners using Windows. It takes too much time from everybody else. If you need help doing this, post in Slack, or bring your laptop to a CYF co-working space to get support. It’s normal to need help with this process.
๐งฐ Install Node with nvm
Learning Objectives
If you get stuck on any of the below or above instructions, please post in your class channel on Slack.
๐ก tip
node -v
in a terminal. The command should return a version number. If it does, you can skip the next steps.๐ง On Ubuntu
- Install nvm by running the following commands in your terminal:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
- After the installation is complete, you’ll need to source the nvm script by running:
source ~/.bashrc
- Install the latest LTS version of Node.js by running:
nvm install --lts
- Check that you have successfully installed Node.js by running:
node -v
You should see a version number like v20.16.0
.
- Check that you have successfully installed npm by running:
npm -v
You should see a version number like 10.5.0
.
๏ฃฟ On Mac
- Install nvm by running the following command in your terminal:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
- After the installation is complete, you’ll need to source the nvm script by running:
source ~/.zshrc
- Install the latest LTS version of Node.js by running:
nvm install --lts
- Check that you have successfully installed Node.js by running:
node -v
You should see a version number like v20.16.0
.
- Check that you have successfully installed npm by running:
npm -v
You should see a version number like 10.5.0
.
๐ก Protip
๐งถ Accessing strings
Learning Objectives
Given a time
string we need to access the first 2 characters of the string which represent the hours.
Strings are zero-indexed.
Index means position, so zero-indexed means we start counting character positions from 0
onwards.
Here are the positions/indexes for "23:00"
index | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
character | "2" | "3" | ":" | "0" | "0" |
In JavaScript, we can use square bracket notation to access specific characters in the string using the index.
time[0]; // evaluates to "2"
time[1]; // evaluates to "3"
time[2]; // evaluates to ":"
// etc
Square bracket access will only give a single character. We must use another method to extract multiple characters from the given string.
โ๏ธ Extracting a slice
To extract 1 or more characters from a string, we can use a function called
slice
is a function that can take 2 arguments: a start index and an end index. slice
will return a section of the string from the start index up to but not including the end index.
time; // holds the value "23:00"
time.slice(0, 2); // will access the characters below
index | 0 | 1 |
---|---|---|
character | "2" | "3" |
So time.slice(0,2)
will evaluate to "23"
when the time
is "23:00"
.
Finally we must convert "23"
to the number 23
, otherwise we can’t compare this value properly.
exercise
Think about the numbers 100 and 23. Which one is bigger?
Write code which compares the strings "100"
and "23"
and tells you which one is bigger.
Does this match your expectation? If not, what do you think is happening?
JavaScript uses different ways to compare values depending on their types. If you compare two strings (which may contain numbers), it will do something different than if you compare two numbers.
We can use the Number
function to convert the string into a number.
exercise
Work out how you can convert the strings “100” and “23” to numbers.
Write code which compares the numbers 100
and 23
and tells you which one is bigger.
๐งน Improving the code
Learning Objectives
Refactoring
Now the assertions pass: in other words, our functionโs current output matches with the target output described in the assertions.
In addition to implementing functionality, we also need to continually improve the code quality. Other developers will continue to read our code so it’s vital our code is readable by other humans.
๐ก Definition: refactoring
Let’s consider our working implementation so far:
Currently, weโre using the same expression twice: Number(time.slice(0, 2))
. This means weโre calling the functions Number
and slice
twice.
Additionally, expressions embedded inside curly braces and parentheses can often be difficult to read. In this situation it makes sense to label the recurring expression so we can reuse it wherever we need to in our code.
Letโs create a variable called hours
and assign to it our expression’s result.
function formatAs12HourClock(time) {
const hours = Number(time.slice(0, 2));
if (hours > 12) {
return `${hours - 12}:00 pm`;
}
return `${time} am`;
}
Note that the function’s behavior hasn’t changed: it still returns the same outputs from the given inputs. We’ve just improved the implementation without changing the underlying behaviour.
๐ Fixing bugs
Here is our current implementation of formatAs12HourClock
:
function formatAs12HourClock(time) {
const hours = Number(time.slice(0, 2));
if (hours > 12) {
return `${hours - 12}:00 pm`;
}
return `${time} am`;
}
However, formatAs12HourClock
currently has a
function formatAs12HourClock(time) {
const hours = Number(time.slice(0, 2));
if (hours > 12) {
return `${hours - 12}:00 pm`;
}
return `${time} am`;
}
a) Write an assertion to check the output of formatAs12HourClock
when it is called with an input "17:42"
b) Check the assertion output and try to explain what the bug is
formatAs12HourClock
.formatAs12HourClock
, re-run all the assertions to check you’ve not broken any previous functionality.๐งน Refactoring repetition
Learning Objectives
Let’s look at our code, which passes all of the tests we’ve written:
|
|
Inside the formatAs12HourClock
function we do exactly the same thing twice.
Exercise
formatAs12HourClock
that do exactly the same thing.There are a few reasons this isn’t ideal.
- It’s not clear what this value represents. You can read it and work it out, but that takes some time.
- Doing the same thing twice is slower than doing it once.
- In the future if we need to change this code’s implementation, we would need to change it twice.
Right now our code assumes the hours in a time are always two digits (like05:00
). What if we wanted to support single-digit hours, like5:00
? We would need to make the same change to both lines. It would be easy to change one line and forget the other, which would lead to a bug.
Refactor
Once your code passes your test, look for ways you could make your code better. This doesn’t mean changing what it does - the code works. It means changing how it’s written.
This is called
We can refactor our code to remove this duplication by introducing a variable:
|
|
This code does exactly the same thing as the previous code. But it is better in a few ways:
- We can now tell more easily what this expression represents. The variable name conveys: it’s the hours from the time.
- We only compute the hours once, not twice, which will be a little bit faster.
- If we need to change how we identify the hours (e.g. to support single-digit hours), we only need to update one bit of code. Both lines 3 and 4 will automatically use the same value, because they’re referring to the same variable.
๐ก Code Quality
Whenever you finish implementing a sub-goal, or a goal, read your code and look for ways to refactor it.
This will make your code easier to continue working on.
๐งพ Evaluating expressions
Learning Objectives
๐ก Tip
Computers work by storing and performing operations on data.
Computer programs are built from many expressions. We must understand how expressions are evaluated to understand how computer programs are executed.
We can take an expression like 36 * 45
and ask what it evaluates to. If we know what the *
operator represents (multiplication) and if we understand the arithmetic rules represented by the operation we can evaluate this expression ourselves.
Happily, computers can evaluate expressions for us.
NodeJS is an application that runs JavaScript programs. In other words, NodeJS can understand and execute programs written in JavaScript. One feature of Node is the REPL.
๐ Note
REPL is a special type of program that stands for:
- Read - Users enter some code that Node will read
- Evaluate - Node will then evaluate this code
- Print - Node will print the result to the terminal
- Loop - Node will loop back to the beginning and prompt users to input some more code
With a REPL we can run pieces of code and look at what happens.
{title=“Definition: REPL” AlertType=“definition”}
We input JavaScript instructions that are then executed by NodeJS. The REPL replies with, or prints out, the result of this execution.
Type each of the following expressions into the REPL one at a time and then press enter to check the result.
10 + 32
32 / 10
๐ Note
In this activity, you’ll check you’re ready to use the Node REPL on your machine.
- Open the terminal on your computer
- Check you’ve got Node installed on your computer
- Start the Node REPL in your terminal
- Enter the expressions and evaluate them using the Node REPL {title=“Activity” AlertType=“activity”}
If you don’t know how to do any of the steps above, then try searching for an appropriate command online. Searching for things when you’re stuck is super important part of being a developer!
๐ Note
Create your own expressions and enter them into the Node REPL.
๐ง Before you type in the expressions, predict what the REPL output will be. Write your prediction down and compare it to the outcome. {title=“Activity” AlertType=“activity”}
๐ช Declaring functions
Learning Objectives
๐ก Recall
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.
๐ช 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.
๐ Calling a function
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"
.