Lab 3
JS

MIT Interactive Visualization & Society course • Slides by Lea Verou

What is JS?

A fully-fledged programming language that runs in the browser (but can also run on the server, in native applications, etc.)
- The world’s [most popular](https://survey.stackoverflow.co/2023/#technology-most-popular-technologies) programming language! - JS is short for "JavaScript", as its full name is. This was a marketing ploy, as it was created during a time that Java was very popular. Its similarities with Java are entirely superficial.

What can JS do?

Behavior
When I click on the close button, close the dialog box
Automation & Templating
Add a navigation menu on every page, and give a class of ‘current’ to the current page Not the best way to do templating
APIs
When a message arrives, display an OS-level notification
Web APIs
Display my 9 latest Instagram posts in the sidebar

Adding JS to your page

Linking

From HTML


			<script type="module" src="hello.js"></script>
		

From JS


			import "./hello.js";
			import * as util from "./util.js";
		

Embedding


			<script type="module">
				alert("Hello world!");
			</script>
		

Inline event handlers avoid


			<button onclick="alert('Hello world!')">
				Click me
			</button>
		

The Console

JS gives you superpowers, that you can use not just when developing websites, but also when browsing the Web! The Console in the browser devtools allows you to run JS on *any* website you visit, not just those you develop. You can use this to extract data from websites into a convenient format, remove undesirable elements en masse, and many other things. Tip: Press the up arrow to cycle through previous commands. The console is also a powerful debugging tool: First, it will show you any errors that occur in your JS code, so it's the first place to look when stuff is not working. Second, you can log to it from your own code by using [`console.log()`](https://developer.mozilla.org/en-US/docs/Web/API/console/log_static), which is the first line of defense for debugging, as you can use it to print out the current values of variables and see if they are what you expect.
$$( "*" ) .length
- Identifiers in JS can contain not only letters, numbers, and underscores, but also dollar signs. - This means that `$` and `$$` are valid names for things! - The console has a special function called `$$()` that takes a CSS selector and returns all the elements that match it (there’s also `$()` which returns the first). It is a convenience over the much longer regular JS version that we will see later. - We type the selector in single or double quotes — that is called a _string_ in programming and is how we write text. - If we only type `$$("*")`, we will see a (very long) list of all the elements on the page. To actually get their number we need to get the `length` _property_ of that list. We will learn about properties later, but for now, just know that it’s a way to get some information about a value. **Try it now:** visit your favorite website, open the console and type `$$("*").length` to see how many elements it has! Who can find the website with the most and least elements?

Reactivity

CSS is reactive


			<style>
				a:hover {
					background: gold;
				}
			</style>
			<a href="#">Come here</a>
		

JS is not reactive


			<a href="#"
			   onmouseover="this.style.background = 'gold';"
			>Come here</a>
		

JS is not reactive


			<a href="#"
			   onmouseover="this.style.background = 'gold';"
			   onmouseout="this.style.background = '';"
			>Come here</a>
		
Data flow in CSS is reactive, just like spreadsheet formulas. When something changes, everything updates to match and you don’t have to lift a finger to make that happen. JS instead operates on a different model, called [_imperative_](https://en.wikipedia.org/wiki/Imperative_programming). Imperative programs are a series of steps. Each step is executed once, unless explicitly revisited. Hover over these links to see the difference. Remember pseudo-classes? As a part of CSS they are reactive: not only does the background here become `gold` when the link is hovered, but it also returns to its previous state automatically, when the link stops being hovered. However, when we use JS to do the same thing, we have to manually set the background back to its original value ourselves.

Data in JS

Simple values

🧮 Numbers

2 .5 -11.8 1_000_000_000 Infinity -Infinity NaN

🧵 Strings (text)

"Hello" 'This is some text' `x is ${ x }
and y is ${ y }`

❓Booleans (yes/no)

true false

🫗 Empty

null undefined
- Numbers - Numbers can be integers or floating point numbers, finite or infinite, and can also be `NaN` (Not a Number) which is used for invalid values (e.g. try doing `5 - "yolo"`). - For readability, you can use underscores in numbers, which are ignored by the interpreter. - Strings - Strings can use either single or double quotes - Backticks are used for [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) which are more powerful than regular strings: they can have line breaks, and embed variables and expressions. - There are two kinds of empty values, `null` and `undefined`, used in different situations. - These kinds of simple values are called "primitives" to distinguish them from more complex values like objects and functions.

Arrays

Lists of values

✅ Defining


				let numbers = [1, 2, 3, 4, 5];
				let names = ["Lea", "David", "Jen"];
			

👀 Reading


				console.log(numbers.length); // 5
				console.log(names[0]); // "Lea"
			

📝 Modifying


				names[2] = "Jo";
				names.splice(1, 1);
				console.log(names); // ["Lea", "Jo"]
			

➕ Adding


				numbers.push(6);
				numbers.unshift(0);
				console.log(numbers); // [0, 1, 2, 3, 4, 5, 6]
			
- Arrays are a way to group values together and simplify repetitive code, since we can write code once that operates on all of them in one fell swoop. - [MDN First Steps Tutorial on Arrays](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Arrays)

Objects

Key → value pairs

String → String


				let pages = {
					"Home": "index.html",
					About: "about.html",
					Contact: "contact.html",
				};
			

String → Object


				let pages2 = {
					home: {
						title : "Home",
						url: "index.html"
					},
					about: {
						title : "About me",
						url: "about.html"
					},
					contact: {
						title : "Contact",
						url: "contact.html"
					},
				};
			
- Objects are a more complex type of value, in the sense that they contain other values - Objects are a set of key-value pairs. The keys are always strings (called _properties_), but the values can be anything, including other objects or functions. - If you need to map objects to values, look at [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)). - [Object literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) are plain objects, called "dictionaries" in other languages (e.g. Python). - One of the hands-on exercises of this lab is to generate the navigation menu of your website automatically, via JS. For that, we need a data structure that holds both the titles and the URLs of our pages. There are many options for that. One would be to map urls to titles or titles to URLs via an object like the first one. Another options would be to use identifiers for the pages and map those to objects that contain the title and the URL, like the second one. - The keys can be written without quotes if they don’t contain characters that would be invalid in variable names (e.g. `home` instead of `"home"`).

Object Properties

Reading & Writing

All of the following return "contact.html":

pages.Contact pages["Contact"] pages["Con" + "tact"] pages2.contact.url

				let title = "Contact";
				pages[title];
			

				let id = "contact";
				pages2[id].url;
			
- The dot operator is more convenient, but it only works for properties that are valid variable names (letters, numbers, `_`, `$`). - The brackets can be used for any expression, so can be very useful when the property name is in a variable. - Both can be chained, and combined with each other - Both can be used for writing as well as reading - We can write any property, or even create new ones, but if `obj.foo` does not exist, `obj.foo.bar` will produce an error (you can use [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) to avoid that when reading values, but you cannot write values this way). - Does the syntax seem familiar? We’ve seen `array.length` and `array[0]` before, haven’t we? That is not a coincidence, these are *also* properties. Arrays are just a special type of object. - Because objects and arrays hold other values, they are called _"data structures"_. - Read more: [MDN: Property accessors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors)

JSON

A data format based on JS syntax

You may have heard of JSON. It is simply a subset of JS data with stricter syntax.

Execution flow

JS programs are a series of steps


				let x = 1;
				let y = 2;
				let z = x + y;
				x = 2;
				console.log(z);
			
JS programs are a series of steps, separated by semicolons. Each step is executed once, unless explicitly told to execute again. They can be triggered by user actions, like clicking a button, or by other events, like the page loading or a message arriving.

What can change that?

Conditionals


				let x = 1, y = 2;
				let min;

				if (x < y) {
					min = x;
				}
				else {
					min = y;
				}
				console.log(min);
			
Conditionals are a way to execute different code depending on a condition. [MDN: Making decisions in your code — conditionals](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/conditionals)

Loops


				let numbers = [2, -1, 3, 0, 5];
				let min = Infinity;

				for (let n of numbers) {
					if (n < min) {
						min = n;
					}
				}

				console.log(min);
			
- Programming languages are very useful for rapidly completing repetitive tasks. Loops are a way to execute the same code multiple times, with different data each time. - Here we see a [`for .. of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) loop which is for running code over each item in an array and other types of lists (_iterating_). There are many other types of loops, such as: - [`for .. in`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in) for iterating over object keys - [`for`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for) for arbitrary conditions - [`while`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while) for running code for as long as a condition is true (dangerous! if the loop does not take care of making that condition false, it will run forever, or until the browser crashes) - Read more: - [MDN: Looping code](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code) - [MDN: Loops and iteration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration)

Functions


				let min = (numbers) => {
					let result = Infinity;
					for (let n of numbers) {
						if (n < result) {
							result = n;
						}
					}
					return result;
				};

				let minimum = min([2, -1, 3, 0, 5]);
				console.log(minimum);
			
But what if we frequently needed to calculate the minumum of different lists of numbers? We could write a *function* to do that! - Functions help us reuse chunks of logic (or actions). - They take zero or more inputs, called _parameters_ (here `numbers`), and return a single output, called the _return value_ (even if a function does not explictly return anything, the special value `undefined` is returned). - Aside from inputs and outputs, the rest of the code sees the function as a black box. In that sense, it helps us *modularize* our code, and break down complex logic into manageable chunks. Notice that we assigned our function to a variable? That’s because in JS, **functions are just another type of value, like numbers or strings**. We can have arrays of them, object properties that are functions, we can pass them to other functions as parameters, we could even have functions that return functions! 🤯

Methods

Object properties whose values are functions


			let pages2 = {
				home: {
					title : "Home",
					url: "index.html",
					getAbsoluteURL: function (base) {
						return `${base}/${this.url}`;
					},
				},
				about: {
					title : "About me",
					url: "about.html",
					getAbsoluteURL: function (base) {
						return `${base}/${this.url}`;
					},
				},
				contact: {
					title : "Contact",
					url: "contact.html",
					getAbsoluteURL: function (base) {
						return `${base}/${this.url}`;
					}
				},
			};
		
We’ve seen objects whose values are [primitives](#primitives), other [objects](#objects), or [arrays](#arrays). But they can also be [functions](#functions)! These functions are called _methods_ because they typically have an implicit parameter: the object they are called on. We can access that parameter with the special `this` keyword, as long as we’re using a regular function (not an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) like in the previous slide). Notice that the function here is identical for all three objects. Can we reduce this repetition?

Classes

Blueprints for objects

Class definition

The blueprint


				class Page {
					constructor(title, url) {
						this.title = title;
						this.url = url;
					}

					getAbsoluteURL(base) {
						return `${base}/${this.url}`;
					}
				}
			

Usage

“Creating instances”


				let pages = {
					home: new Page("Home", "index.html"),
					about: new Page("About me", "about.html"),
					contact: new Page("Contact", "contact.html"),
				};
			
Classes are a way to create objects that share the same structure and behavior. They are a way to create _blueprints_ for objects. - The `constructor` method is called when a new object is created from the class. - The `new` keyword is used to create a new object from a class. - The `this` keyword is used to refer to the object that is being created. - Any methods defined on the class (e.g. the `getAbsoluteURL()` method here) is shared by all objects created from the class (_instances_). - Read more: [MDN: Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)

Operators

Math

Math JS
x + y x + y
x - y x - y
x × y x * y
xy x / y
xy x ** y
x mod y x % y

Comparison

Math JS
x < y x < y
x > y x > y
x y x <= y
x y x >= y
x y x !== y
x = y x === y

Logic

Math JS
x y (x AND y) x && y
x y (x OR y) x || y
¬x (NOT x) !x
- One of the mistakes in the design of JS is that `+` serves a double purpose: number addition and string concatenation. This means that e.g. `"2" + 3` is not the number `5` but the string `"23"`.

Elements

Getting them into a JS variable

Predefined

Element JS
<html> document.documentElement
<body> document.body
<head> document.head

Via ids avoid


			<button id="test_button">
				Click me
			</button>
		

			console.log(test_button);
		

Via selector


			// See all links on a page
			let allLinks = document.querySelectorAll('a');
			console.log(allLinks);
		

			// Get closest ancestor with a class
			let container = element.closest("select, article");
		

Creating 🆕 elements


				let p = document.createElement("p");
				p.textContent = "Hello";
				document.body.append(p);
			

				document.body.insertAdjacentHTML("beforeend", `
					<p>Hello</p>
				`);
			
Every element in our HTML corresponds to an object in JS, which we can manipulate. Before we can do any exciting things with elements, we need to get _a reference_ to them, i.e. to grab this object and put it in a variable that we can use. There are multiple ways to do that: - For the three elements that are guaranteed to exist on every page, there are predefined variables for them. - Every time you assign an id to an element, you can also use that in your JS. This can be convenient for experimentation, learning, and prototyping, but it’s not good as a sustained practice as it is quite fragile. - The most flexible way is to use the [`document.querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) and [`document.querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) methods, which allow us to select elements using CSS selectors. The console even offers nice shortcuts for them: `$()` and `$$()` respectively. - If we already have a reference to an element, we can use `element.closest(selector)` to get its closest ancestor that matches a selector. and `element.querySelectorAll(selector)` to get its descendants that match a selector. - We can also create new elements from scratch and add them to the page. Note that until we actually use a method like [`element.append()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/append) to add them to the document, **we will not see them on the page**!

Elements

Manipulating them (the fun part!)

Style

Classes


				a.classList.add("current");
			

CSS


				el.style.setProperty("--value", value);
			

Metadata

Attributes


				h1.id = slugify(h1.textContent);
				a.setAttribute("target", "_blank");
			

Properties


				checkbox.indeterminate = true;
			

DOM Tree

Position


				code.before(a);
				a.append(code);
			

Content


				h1.textContent = "Hi there";
				p.innerHTML = "Yo <em>sup?</em>";
			
Now that we have a reference to the element(s) we are interested it, here comes the fun part: manipulating it dynamically! We can change almost everything about an element (except its type, but we can replace it with another element of a different type!): its attributes, its content, its style, and even move it to another position in the DOM tree. - Every attribute corresponds to a JS property, though in rare cases the name is different (e.g. `for` is `htmlFor`) and the values can be slightly different (e.g. in an `<a>` element `element.href` is a full absolute link, even if the `href` attribute is a relative URL). You can also use [`element.getAttribute(name)`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute) and [`element.setAttribute(name, value)`](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute) to access attributes with their original name and format. - For certain attributes that take lists of values, there are more convenient ways to manipulate their individual values: - For class names use `element.classList` to add (`element.classList.add()`) or remove (`element.classList.remove()`) individual classes - For inline styles (`style` attribute), `element.style` allows us to manipulate individual properties (`element.style.getPropertyValue(name)` and `element.style.setProperty(name, value)`).

Events

“When X happens, do Y”

Basic


			<button id="button">Click me</button>
		

			button.addEventListener("click", function () {
				alert("You clicked me! 🥹 Thank you!");
			});
		

Event object


			document.addEventListener("mousemove", (event) => {
				document.body.textContent = `${event.x} ${event.y}`;
			});
		

⚠️ Common caveat


				<button id="button">Click me</button>
			

				button.addEventListener("click", document.body.style.background = "pink");
			

				<button id="button">Click me</button>
			

				button.addEventListener("click", () => {
					document.body.style.background = "pink"
				});
			

Local URLs Part 2

Ports and Local servers

http:// localhost:4000 /labs/1/hello.html
So far we’ve been working with local URLs that start with the `file:` protocol. Since these are absolute paths starting from the root of your filesystem, This means that a malicious website could potentially access any file on your computer, which is why the `file:` protocol is very locked down today as it’s considered unsafe. Instead of the `file:` protocol, when developing websites locally, we usually run one or more *local servers*, which we can access via the special domain name `localhost`. To allow the same domain name to be used as the host for multiple websites, we have the concept of *ports*. Every URL has a port, but it’s usually omitted when it’s the default port for the protocol (80 for HTTP and 443 for HTTPS). Note that now the path starts from the root of the website, not the root of the filesystem. This is because I’m running the local server at the root of the website. This also means that files outside the root of the website are not accessible, which is a security feature.

Deleted scenes

Variable scope

Variables are scoped to closest {}


			let x = 1, y = 2;

			if (x < y) {
				let min = y;
			}
			else {
				let min = x;
			}

			console.log(min);
		
Uncaught ReferenceError: min is not defined

Functions

Reuse chunks of logic (and actions)

Math JS
f(x,y) = x2 + y2

						let f = function (x, y) {
							return x**2 + y**2;
						}
					

						let f = (x, y) => x**2 + y**2;
					
f(1,2) = 5

						let result = f(1, 2);
						console.log(result); // 5
					
You have certainly used functions in Math. They are a way to encapsulate a calculation you want to reuse. They consist of a name, parameters in parentheses, and a body. JS functions are similar, but more powerful. They can take any number of parameters, of any type (not just numbers) and can return any value (not just numbers). Math functions are _side effect free_: their purpose is to compute an output from one or more inputs, their output depends entirely on their inputs, and they do not affect anything else when called. On the contrary, JS functions can be used to compute things, do things (e.g. send a notification, change the page), or both. There are two main ways to define functions in JS: [function expressions](https://developer.mozilla.org/en-US/docs/web/JavaScript/Reference/Operators/function), and [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) which are more compact and more "lightweight". Note that we are assigning the function to a variable, which is a bit mind-bending at first. Functions in JS are just another type of value, like numbers or strings. In fact, as we’ll see, it’s common to have functions whose parameters include other functions, and there are even functions that create and return new functions!

Functions

Math JS
f ( x ) = { 1 x if  x [ -1 , 1 ] x if  x [ -1 , 1 ]

							let f = function (x) {
								if (x < -1 || x > 1) {
									return 1 / x;
								}
								return x;
							}
						

							let f = (x) => {
								if (x < -1 || x > 1) {
									return 1 / x;
								}
								return x;
							}
						

						let f = (x) => x < -1 || x > 1 ? 1 / x : x;
					
Shorter ≠ better!
Functions are not limited to a single statement, they can include any number of statements. This applies to both types of functions, arrow functions just become a lot less compact. But do not fall into the trap of trying to write very compact code at the expense of readability. Take a look at the last example: is that easier to read? Always remember that **code is written once, but read many times**.