๐ Ordered data
Learning Objectives
Let’s imagine we’re writing a program that involves information about a user’s profile. We could store some user’s profile details in an array:
const profileData = ["Franceso", "Leoni", 33, "Manchester"];
At the moment, we could visualise profileData
in a table like this:
index | value |
---|---|
0 | “Francesco” |
1 | “Leoni” |
2 | 33 |
3 | “Manchester” |
Inside profileData
we access items using an index
. However, with an ordered list of items we can’t tell what each item in the list represents. We only know the position of data in the array. We could access the item at index 3 to get "Manchester"
: however, we don’t know what "Manchester"
tells us about the user. "Manchester"
could be the city they currently live in, it could be their city of birth, a place where they studied in the past etc. We need to know the values but also what these values represent about the user.
We might think we can just remember (and maybe write in a comment) “index 0 is the person’s first name”, but this has problems. What if we need to introduce a new piece of data? We may need to change every piece of code that uses the array. What if some of the data is optional (e.g. a middle name)? It’s also really hard for someone new to come read our code.
Keys not indexes
However, instead of ordering data with indexes, we can label data with keys.
key | value |
---|---|
firstName | “Francesco” |
lastName | “Leoni” |
age | 33 |
cityOfResidence | “Manchester” |
We can look up values in this table by the key. With data stored like this, we can see what values like "Manchester"
actually mean - in this case, it refers to a city of residence for the user.
In JavaScript, we can use an
We can declare an object like this.
const profileData = {
firstName: "Franceso",
lastName: "Leoni"
age: 33,
cityOfResidence: "Manchester",
};
๐๏ธ Key-value pairs
Learning Objectives
The profileData
object is made up of properties.
Each property is an association between a key and a value.
{
firstName: "Francesco",
lastName: "Leoni",
age: 33,
cityOfResidence: "Manchester"
};
In the
firstName
and "Francesco"
. firstName
is the key, "Francesco"
is the value associated with the key firstName
.
In object literals, each key-value pair is separated by a comma.
๐ Note
Defining properties in JavaScript object literals looks a lot like defining properties in a CSS rule:
p {
color: red;
background-color: blue;
}
๐ช Accessing properties
Learning Objectives
We’ve already accessed object property values. console
is an object:
Welcome to Node.js v16.19.1.
Type ".help" for more information.
> console
Object [console] {
log: [Function: log],
warn: [Function: warn],
dir: [Function: dir],
time: [Function: time],
timeEnd: [Function: timeEnd],
.
.
.
}
We use dot notation to access the property value associated with a key.
When we write console.log
- think of this as saying:
“access the value associated with key of
"log"
, inside theconsole
object”
Similarly we can use dot notation to access property values stored in objects we have defined:
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
console.log(profileData.firstName); // logs "Francesco"
Objects also allow looking up property values using square brackets, similar to arrays. Instead of an index, we use a string of the key inside the square brackets:
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
console.log(profileData["firstName"]); // logs "Francesco"
Using dot notation or square brackets both work the same way.
Mutation
Objects are mutable data structures. We can use the assignment operator =
to update the value associated with a particular key.
|
|
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
const twinData = profileData;
twinData.firstName = "Emilia";
console.log(profileData === twinData);
console.log(profileData.firstName);
Predict and explain what the console output be when we run the code above runs.
Properties are optional
It’s possible to add properties to an object that already exists. Objects don’t always have the same properties.
exercise
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
console.log(profileData.cityOfResidence);
profileData.cityOfResidence = "Manchester";
console.log(profileData.cityOfResidence);
Predict and explain what the console output will be when the code above runs.
Object literals vs objects
What’s the difference between an object, and an object literal?
An object is the thing we’re making, which maps keys to values.
An object literal is how we can write one out specifying all of its key-value pairs in one statement.
These two blocks of code construct equivalent objects:
const object1 = {
firstName: "Francesco",
lastName: "Leoni",
};
const object2 = {};
object2.firstName = "Francesco";
object2.lastName = "Leoni";
object1
is all constructed in one object literal.
object2
starts off with an empty object literal, and then adds some properties to it.
Note: This same terminology is used for other types:
"abc"
is a string literal, "a" + "b" + "c"
makes the same string, but by concatenating three string literals together.
โ๐ชข Query strings
Learning Objectives
Letโs define a problem.
Websites have addresses called URLs like this: “https://example.com/widgets". URLs often have
https://example.com/widgets?colour=blue&sort=newest
For this URL, the query string is "colour=blue&sort=newest"
. Query strings consist of query parameters, separated by an ampersand character &
. colour=blue
is a query parameter: we say that colour
is the key and blue
is the value.
URLs must always be strings. However, a string isn’t a useful data type for accessing query parameters. Given a key like colour
, accessing the value from a query string stored as a string is not straightforward. However, objects are ideal for looking up values with keys.
We’re going to implement a function parseQueryString
to extract the query parameters from a query string and store them in an object:
Given a query string and a function parseQueryString
,
When we call parseQueryString
with a query string,
Then it should return an object with the key-value pairs.
E.g.
parseQueryString("colour=blue&sort=newest");
// should return { colour: "blue", sort: "newest" }`
Youtube: Step-through-prep workshop ๐
โ No parameters
Let’s look at the case where the query string is an empty string.
In this case, we need to think of an output that makes sense.
We saw before that we can try to look up a property on an object which the object doesn’t actually have - this will evaluate to undefined
.
When we parse the empty query string, we want to return something where any time we ask it for the value of a key, we get back undefined
.
An empty object behaves this way, so it makes sense to return an empty object.
Let’s create a test to explore this idea. In your prep
dir, touch parse-query-string.js && touch parse-query-string.test.js
. Write the following test in the parse-query-string.test.js
file.
test("given a query string with no query parameters, returns an empty object", function() {
const input = "";
const currentOutput = parseQueryString(input);
const targetOutput = {};
expect(currentOutput).toEqual(targetOutput);
});
We can pass this test just by returning an empty object for now. We can define a function parseQueryString
as follows:
function parseQueryString() {
return {};
}
We run our tests and see them pass. Great news.
โ Parsing a single key-value pair
Learning Objectives
Let’s consider another test case: when the query string contains a single key-value pair.
Write a test in the parse-query-string.test.js
file:
test("given a query string with one pair of query params, returns them in object form", function() {
const input = "fruit=banana";
const currentOutput = parseQueryString(input);
const targetOutput = { fruit: "banana" };
expect(currentOutput).toEqual(targetOutput);
});
๐งญ Strategy
We first need to separate out the "fruit=banana"
string so we can access "fruit"
and "banana"
separately. We can do this by splitting up the string by the =
character. We can split the string into an array consisting of ['fruit', 'banana']
. Then we can grab the array’s contents and assign the elements meaningful names:
function parseQueryString(queryString) {
const queryParams = {};
const keyValuePair = queryString.split("=");
const key = keyValuePair[0]; // key will hold 'fruit'
const value = keyValuePair[1]; // value will hold 'banana'
queryParams.key = value;
return queryParams;
}
An equivalent, but more concise way to do this uses array destructuring to create new variables and assign them values, based on values in an array.
This code does the same thing as the previous code:
function parseQueryString(queryString) {
const queryParams = {};
const [key, value] = queryString.split("="); // key will hold 'fruit', value will hold 'banana'
queryParams.key = value;
return queryParams;
}
๐ฎ Play computer with the implementation of parseQueryString
above to see why it isn’t working properly.
[ ] Access with variables
Learning Objectives
We can mutate an object using .
dot notation. However, if we look at the return value in the previous implementation we get {key: "banana"}
.
Let’s take another look at our current implementation of parseQueryString
:
|
|
On line 4, we’re declaring an identifier called key
. When parseQueryString
is called with "fruit=banana"
then key
will be assigned the value of "fruit"
.
We want to add a property name to the object that is the value of the
key
variable and not the string"key"
. We can do this with square bracket notation:
|
|
We can’t use dot syntax if we don’t know what the name of the key is going to be. Square bracket notation is more powerful than dot notation, because it lets us use any expression as a key.
We’ve currently got the following test suite:
describe("parseQueryString()", () => {
test("given a queryString with no query parameters, returns an empty object", function() {
const input = "";
const currentOutput = parseQueryString(input);
const targetOutput = {};
expect(currentOutput).toEqual(targetOutput);
});
test("given a queryString with one pair of query params, returns them in object form", function() {
const input = "fruit=banana";
const currentOutput = parseQueryString(input);
const targetOutput = { fruit: "banana" };
expect(currentOutput).toEqual(targetOutput);
});
});
We’ve currently got the following test suite:
Sometimes when we’re solving a problem, it can be useful to work out different cases (like empty query strings, or non-empty query strings) and work out how to solve them separately, then come back when we think we understand the cases and work out how to put the solutions together into one function. This often is useful when there are really different cases to consider.
Most of the time, though, it’s useful to try to keep all of our existing tests passing as we cover more cases. If we wanted to do that here, we could make our function be something like:
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
}
const [key, value] = queryString.split("="); // will hold ['fruit', 'banana']
queryParams[key] = value; // will set the property name with the value of the key variable
return queryParams;
}
Here, we only add a key to the object if there was actually something to add - we return early if there’s no extra work to do.
โโโ Parsing multiple parameters
Learning Objectives
Let’s consider the case when there are multiple query parameters in the query string.
๐ก tip
&
.Write this test in the parse-query-string.test.js
file.
test("given a query string with multiple key-value pairs, returns them in object form", function() {
const input = "sort=lowest&colour=yellow";
const currentOutput = parseQueryString(input);
const targetOutput = { sort: "lowest", colour: "yellow" };
expect(currentOutput).toEqual(targetOutput);
});
๐งญ Strategy
We’ve already worked out how to update the query params object given a single key-value pair in the query string.
To work out our strategy, let’s consider what we already know how to do. We already know how to take a key-value pair as a string, and add it to our object.
๐ก Key insight: If we can do it for one pair, we can try doing it for a list of pairs too.
So we’re missing a step - breaking up the string of multiple key-value pairs into an array where each element is a single key-value pair. If we do this, then we can iterate over the array, and do what we already know how to do on each key-value pair.
Our strategy will be to break the query string apart into an array of key-value pairs. Once we’ve got an array we can try iterating through it and storing each key value pair inside the queryParams
object.
Let’s start with the first sub-goal.
๐ฏ Sub-goal 1: split the query string into an array of key-value pairs
Query strings with multiple key-value pairs use &
as a separator e.g. sort=lowest&colour=yellow
. We want to split sort=lowest&colour=yellow
into ["sort=lowest", "colour=yellow"]
. We can achieve this by calling split
with the "&"
separator.
|
|
๐ฏ Sub-goal 2: add each key-value pair in the array to the query params object
Once we’ve got an array we can iterate through the key-value pairs and update the queryParams
object each time (like we did when we just had one key-value pair).
|
|
Play computer with the implementation of parseQueryString
above and pay attention to how the queryParams
object is updated.
Now that we’ve worked out how to solve this problem in the case of multiple query parameters, let’s integrate that solution into our previous implementation, to make sure it works for all cases.
We can keep our if (queryString.length === 0) {
check from before. We still need it because split
on an empty string still returns an empty string. If we don’t have this special case, we’ll try to parse the empty string, probably incorrectly.
We don’t need to do anything special for the one-value case, as an array containing one element gets iterated the same as an array of multiple elements:
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
}
const keyValuePairs = queryString.split("&");
for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
}
return queryParams;
}
When we’re solving problems involving several values, often we need slightly differently handling for the cases when there are 0, 1, or more than 1 values. In our example here, we need to treat 0 values specially (if the query string is empty, we return early), but we can handle 1 and more than 1 the same way.
When you’re breaking down problems, think to yourself: What are special cases we may need to handle differently?
Prep Conflict Resolution
๐ค๐ฝ FeedbackLearning Objectives
Introduction
People at work will inevitably disagree with each other at some time. Most people want to do whatโs best for them and those around them. Often, conflicts arise from misunderstandings or different attitudes to general problem-solving.
These exercises will ensure you understand the theory and have reflected on this theme, so you can do the in-class exercises more effectively.
How to deal with workplace conflicts
๐ฏ Goal: Understand tips that will help you to handle conflicts on day-to-day basis. (10 minutes)
Watch the video โHow to deal with workplace conflicts - Develop your personality and business skillsโ.Conflict Resolution Curve
๐ฏ Goal: Understand what is meant by Conflict Resolution (30 minutes)
Read about the Conflict Resolution Curve on WikipediaProtective Factors
๐ฏ Goal: Understand and reflect on your protective factors (20 minutes)
- Read this text: โProtective Factorsโ
- Answer the reflective questions.
Reflect on your own conflict experience
๐ฏ Goal: Reflect on your conflict experience (30 minutes)
Think of a time you have had a conflict with a work colleague.
Answer these questions in a Google Doc:
- Was there a misunderstanding of the facts?
- Have you learned different lessons from your experience which affected your views?
- Did either of you have a hidden agenda directly opposed to the other person?
- How did either of you try to resolve the conflict?
- What was the outcome?