Skip to main content

Pre Lab 3: DOM & Javascript

due Monday, Feb 8 10:00AM ET

Setup

Before starting this prelab, ensure that you have:

Introduction

The Language of the Internet

JavaScript, also known as ECMAScript, is one of the world's most popular and misunderstood programming languages. This prelab introduces you to some of the tools you'll need to begin programming in JavaScript!

Warning: This prelab is a bit on the longer side, but incredibly important to complete especially if you haven’t used JavaScript before. We're going to be giving you a lot of sample code in this lab, but we recommend typing it in instead of copy/pasting; it'll help you learn the syntax.

What you'll get to know

  • JavaScript and its ~fun quirks~
  • The Document Object Model (DOM)
  • How to dynamically alter a page through a hands on experience

What is JavaScript?

From the Mozilla Developer Network (a great site for help with JS):

JavaScript (JS) is a lightweight interpreted or JIT-compiled programming language with first-class functions. While it is most well-known as the scripting language for Web pages, many non-browser environments also use it...

JavaScript is basically the only language you can use in the browser to control the web page (e.g. make pop-ups, create an image slider, build infinite scroll). To control the web page, you use the DOM API, which is a platform and language-neutral interface that will allow programs and scripts to dynamically access and update the content, structure and style of documents.

Using the console

We're going to learn about JavaScript syntax straight from the browser. Modern browsers have built in developer tools that allow us to look at the code of any website. To do this:

  1. Create an index.html file (with a general HTML template) and drag the file into your browser to open it, or use index.html file provided in the stencil for this prelab. We recommend using Google Chrome, but you can also use FireFox or Safari if you prefer.
  2. In order to include JavaScript in your HTML page you need to use a script tag, <script>...</script>. You can type any Javascript code inside the tag and it will be executed while the page is loading. Alternatively, you can write JavaScript in a separate file and include it externally with <script src="path/to/index.js"></script>. In most cases, just as you should avoid using inline CSS styling, it is also highly advised to use external imports if possible by writing all your JavaScript in a separate file. For the purposes of this prelab it’s okay to follow along by including all your JavaScript in a script tag, but we also encourage you to play around with using imports.
  3. Right click anywhere on the page, and click on "inspect". This should pull up the developer tools. You should see all of the HTML code of this page. Moving your mouse over the different tags will show you its corresponding elements on the page. Right now, we don't have anything in our webpage, but once elements get added, they will start to show up.
  4. Notice in the developer tools there is another tab called "console."
  5. The console is like a JavaScript terminal and is a great debugging tool for your later projects. You can type in any JavaScript code, and it will evaluate it line by line, just like an interpreter will. (You can read Google's documentation on Chrome Console)
  6. Type in console.log("i love noodles"); into the console and hit enter (the semicolon at the end is optional and can be skipped). You'll notice that the message is printed onto the console. This is the equivalent of "printing" to the terminal. (By the way, semicolons are completely optional in JavaScript, you can choose whether to use them or not, but consistency is recommended)
  7. The console can also evaluate arithmetic expressions. Try typing in 3 + 4 and hitting enter.

IMPORTANT: We highly recommend getting familiar with the various developer tools in the console, such as the Chrome Debugger for debugging JavaScript. You can use the following brief guide to learn how to set breakpoints and step through JavaScript code to inspect values: https://developers.google.com/web/tools/chrome-devtools/javascript/.

Printing to the console

The Console is also a very helpful debugging tool. You can make use of this by printing out to the console directly in your JavaScript code, using the function console.log(). If you have a line in your JavaScript that says console.log("Hello World!"), then when that line is evaluated "Hello World!" will be written to the console. You can console.log() any value in JavaScript, for instance an object or result of a function call, to see if the value is as you expect it to be. You can think of it as similar to printf() or print() in other languages.

JavaScript syntax

JavaScript is a pretty wild language, at least because it supports procedural, object-oriented, and functional programming. Here's a little cheat-sheet.

Variables

There are three ways to declare a variable with the advent of ES6 (the JavaScript version we are using): var, let, and const. It's a good practice to avoid using var. The current most popular opinion is to always use const when you know the value will not be re-assigned and let if you expect to re-assign the value of the variable (e.g. in a for loop). But be careful! Just because a variable is declared with const does NOT mean that JavaScript will enforce it to be immutable, only that it can not be re-assigned to. For example, a const array can still be appended to, and the values of any keys of a const object can still be modified. You can read more about variable declaration in ES6 here.

JavaScript has totally dynamic typing, meaning that you don't need to declare what type a variable is. JavaScript can just figure out the correct type of a variable based on how it's used.

To see this in action, type in the following lines into the JavaScript console (no need to include the comments)

> const lol = "laughing out loud" // strings can be single or double quoted
< undefined
> typeof lol
< "string"

> let numberOfPenguins = 4
< undefined
> typeof numberOfPenguins // should print "number"
< "number"
> numberOfPenguins = 5.0 // we can reassign the value because it is declared using "let"
> typeof numberOfPenguins // all number is JS are type "number" (floating point number)
< "number" 

As a side note, if you’re curious about why your console returns undefined when you define variables, see here: Why does JavaScript variable declaration at console results in "undefined" being printed?

Arrays

Arrays can be of mixed type and are declared through a simple literal syntax that's similar to Python: let mixedTypeArray = ["noodles", {class: "cs1320"}, true, function() {alert("I'm a function")}];

Working with arrays:

> mixedTypeArray[0] // access an element, JS array is 0-indexed
< "noodles"
> mixedTypeArray[1] = "CSCI1320" // reassign element value
< "CSCI1320"
> myArray.length // use length to get a total count of elements
< 4
> mixedTypeArray[100] = "Remember to fill out Collab Policy" // add an element at given index
< "Remember to fill out Collab Policy"

Since arrays are among the most widely used data structures, it may be a good idea to review the following array methods (we have highlighted the ones that we think will be especially important to know): push, pop, shift, unshift, indexOf, includes, splice, sort, and filter. It will also be useful to know the various expressions and operators that can be used with arrays including the spread syntax introduced in ES6, as well as the various array iteration protocols such as basic for loops, for...of loops, and iteration methods such as forEach, and map. You can find all the documentation for arrays on MDN.

Objects

An object in JavaScript is a collection of properties where a property is a mapping from a key (aka name, index...) to a value. Similar to JS Arrays, Objects can also have mixed types.

let captain = {
    name: 'Rockhopper',
    nickname: 'RH',
    location: { // you can have nested objects!
        ship: 'Migrator',
        area: 'Beach'
    }
};

Write a similar object for yourself in the <script> tags in your index.html. Now open up your page in the browser and check out the JavaScript console. Now type the name of the variable you just made into the console. You should see the name come up with a little arrow to let you inspect the object. You can see all the properties you just defined

There are two ways to access a property on an object:

captain['name'] // 'Rockhopper'

or

captain.name // 'Rockhopper'

Dot notation is often used for static attribute while bracket notation could accept an attribute name that is dynamically generated. Note that dot notation only works with object properties that are valid identifiers (they must start with a letter and not contain spaces, hyphens, or be keywords like for).

If you’re interested, you can also check out some other standard built-in objects that you may find useful, including map and set. ES6 also added some useful ways of using objects, including shorthand for object properties and dynamic property names.

Equality (== vs. ===)

One of the most common bugs in JavaScript comes from using == rather than ===. They're both equality operators, but with a catch: JavaScript does type coercion. == will coerce its two operands to the same type before comparing them, so 1 == "1" is true even though they are not of the same type.

=== on the other hand only returns true if its two operands are equivalent and of the same type. So 1 === "1" is false, since 1 is a number and "1" is a string. It's a best practice to always use === so you don't get any surprises, although there might be situations in which you want to compare two things regardless of type, in which case == is the move.

For negative equality, != is equivalent to not ==, and !==is equivalent to not ===.

You can read more about equality comparisons in this article.

Types of false

In JavaScript, variables are "falsy" or "truthy", meaning that you can do if (myNonBooleanVariable)... You can look at definition for truthy and falsy on MDN. Here are the main falsy values.

  • null: Usually used to indicate a nonexistent object, null is actually of type object, which leads to some confusing things like this:
    if (typeof null === 'object') {
        alert("JavaScript is weird!");
    }
    What happens if you put that in your tags. Is that what you'd expect?
  • undefined: When you try to access a property on an object or a variable that isn't defined, you'll get the value undefined. Say you have this:
    function sayHi(name) {
        if (name !== undefined) {
            alert("Hello " + name);
        }
    }
    
    sayHi() // no output since name is undefined
    sayHi("CS Student"); // name is "CS Student", you'll get an alert
    If you're accessing a variable and you're not sure if it'll be there (because, for example, all function arguments are optional in JavaScript), you should check for undefined.
    let myObj = {}; // empty object
    if (myObj.name === undefined) {
        console.log("no name!"); // this will always be called
    }
  • false: false is useful for values that are definitely known (unlike undefined or null) and definitely false. For example, false may be assigned to boolean variables (boolean is a data type that has two possible values: true or false). A trivial example:
    const pigsFly = false;
    if (pigsFly) {
        console.log("Club Penguin will shut down"); // this will never happen :'(
    }

Surprisingly truthy

Empty arrays and objects always behave like true when treated as booleans.

let info = {};
if (info) {
    console.log("true!");
}

Summary table of truthy and falsy values in JavaScript

TRUTHY (evaluates to true) FALSE (evaluates to false)
true false
non-zero numbers 0
"strings" ""
objects undefined
arrays null
functions NaN (not a number)

Functions

Functions are objects. You can define properties on them, and they're capable of inheritance. Here's some advanced information about JavaScript functions. The gist is:

Declaring functions

There are two standard ways to declare functions:

function myFunction(arg1, arg2, arg3, ...) { ... };

// Example
function add(a, b) {
    return a + b;
};
const myFunction = function(arg1, arg2, arg3, ...) { ... };

// Example
const add = function(a, b) {
    return a + b;
};

The first is a normal function or named function, in this case called myFunction. The second is an anonymous function, in which you're assigning to a variable called myFunction. They can almost be used interchangeably, except that the anonymous function is created at run time when the variable is declared, whereas the normal function is run before any other code. In general, it’s a good idea to use anonymous functions when using functions as arguments (think functional programming). You could also instantiate a function object with new Function(), but there aren't many reasons to.

As of ES6 syntax, you can also declare functions with the arrow (=>) operator:

const myFunction = (arg1, arg2, arg3, ...) => { ... };
const myFunction = (arg1, arg2, arg3, ...) => { ... };

// Example
const add = (a, b) => a + b; // When the function body can be written in just one line, you can omit the braces { } and return statement. This is purely syntactic sugar.

This is now one of the most popular ways to declare functions in JavaScript because of two main reasons: 1) it tends to be much more concise to write (see above example) and 2) it resolves some issues relating to the special this variable (to be discussed further in the next section). Although we won’t require the use of any specific form of function declaration, we would recommend using the latest ES6 arrow syntax when possible because it is currently the most popular choice among web devs. Read more about arrow functions on MDN or this feature article: ES6 In Depth: Arrow functions.

Calling functions and this

JavaScript has some cool ways to call functions. Of course, you can call a function by name:

foo();

or, if it's the property of an object:

const obj = {
    foo: function(){alert('hello, world!');}
};

// by name
obj.foo();

// dynamically: you could assign fn at runtime
const fn = "foo";
obj[fn]();

Functions have a special variable called this in their scope which can be used to refer to the object that called the function. When a function is called plain (e.g. foo()), this is either the window or the object the function is being called on (which could be an instance of the function object). If the function is called as the property of another object (e.g. obj.foo()) this refers to the object before the . (in this case it would be obj). this can be tricky to understand, and if you find yourself stuck or confused when using it then come to TA hours and/or try checking out this Stack Overflow answer: How does the "this" keyword work?

One important takeaway from this is that arrow functions do not have their own this binding! Instead, the value of this in an arrow function refers to the same this in the context that the arrow function was defined in. The introduction of the arrow function removes the need for a lot of hacky fixes related to this when programming in JavaScript. You can find a lot of examples about this online, for example here.

In a special case where the function is called with the new keyword, this is bound to an object, and that object is returned. This is the normal way of declaring and instantiating an object type ("class") in JavaScript:

function Cyborg(name) {
    // assign the name property of the `this` object
    this.name = name;
}

const doctor = new Cyborg('Doctor Marbles');
console.log('The name of the doctor is ' + doctor.name); // 'The name of the doctor is Doctor Marbles'

If you are familiar with classes in other object-oriented languages, this syntax might confuse you. On one hand, it is used very similarly to a traditional class from other languages, but on the other hand, it's declared as a function. You can think of this like just the constructor of a class. Here, the Cyborg() is a function that's essentially just a constructor for a class called Cyborg. We'll see how to define methods and class methods on this class later on.

If you want, you can call a function with a different context, meaning that this can be set to whatever object you want. You do that with call and apply:

foo.apply(otherObj, [ arg1, arg2, arg3 ]); // call foo where this === otherObj
foo.call(otherObj, arg1, arg2, arg3); // these two lines are equivalent

The difference is how you supply arguments to the function you're calling. apply takes an array, call takes individual arguments.

You can define methods on your JavaScript objects using the object's prototype, which is another object that defines properties and methods for the object. You can also define methods inside class function as well.

Put this in your <script> tag:

// this is the constructor for a Friend object
function Friend (name, age, location) {
    this.name = name;
    this.age = age;
    this.location = location;
    this.test = function (){
    return this.name + " " + this.age;
    };
}

// let's define a method for Friend; the prototype is just an object
Friend.prototype = {
    info : function () {
    return this.name + "/" + this.age + "/" + this.location;
    }
};

const penguin = new Friend("Gary", 5, "Pizza Parlor");
console.log(penguin.info());

JavaScript doesn't do inheritance the same way C/Java/Python/Ruby does (classically). It uses prototypical inheritance. It looks a little bit confusing (considering there's no inherits or extends keyword. It's actually a really simple concept: when you call a method on an object, JavaScript goes up that object's prototype chain (its parents' prototypes) looking for a method with that name. While that sounds a lot like classical inheritance, it's actually a lot simpler, since it doesn't require any classes or interfaces.

You don't really need multiple inheritance since you can inherit from any list of prototypes in any order you want; they don't have to implement the same methods! Try this out:

So what if we want to create an object that inherits from Friend, say, a PenguinFriend? Since we're working with Functions, calling the super-object is as easy as calling a function with the context (this) set to our new object:

function PenguinFriend (name, age, location, hobby) {
    Friend.call(this, name, age, location); // call the parent (like super() in Java)
    this.hobby = hobby;
}

Now we want to set PenguinFriend's prototype to an instance of Friend's prototype. We don't want to use Friend's prototype directly because then we'd be messing with Friend. So we create an empty function with a blank constructor so we can instantiate a new Friend prototype:

function emptyFriend() {}; // blank function
emptyFriend.prototype = Friend.prototype;

PenguinFriend.prototype = new emptyFriend(); // a new instance of the prototype
PenguinFriend.prototype.constructor = PenguinFriend; // that's the function that the new keyword calls

Nice! Now we've got a PenguinFriend using its constructor but Friend's prototype. How do we add new methods? Just define them on the prototype object:

PenguinFriend.prototype.profile = function () {
    return this.info() + "/" + this.hobby;
}

Now if you've been following along, you should be able to do this:

const matt = new PenguinFriend("Matt", 11, "Ohio", "Skateboarding");
console.log(matt.profile());

This can get pretty confusing and might be worth reading twice. Evan Wallace also has a section on the same topic in JavaScript for Coders.


As part of ES6, standard class declarations have been introduced to JavaScript. The class syntax is not introducing a new object-oriented inheritance model to JavaScript, it is syntactic-sugar for prototypical inheritance. JavaScript classes provide a simpler and clearer syntax to create objects. For more information on classes checkout this information from Mozilla.

class TA{
    constructor(height, hairColor){
        this.height = height;
        this.hairColor = hairColor;
    }

    gradeHomework(hwk) {
        // code not shown ...
    }
}

Function and class declarations are different because function declarations are hoisted and class declarations are not. You first need to declare your class and then access it; otherwise an error will be thrown.

Scoping

Unlike C/Java/Python and other languages you are probably familiar with, Javascript has function scoping rather than block scoping. So if statements, while loops, etc. do not create a scope, and variables defined with var are hoisted to be declared at the beginning of their scope. This is also a reason why let and const are better practice than var, since they don't exhibit this sort of behavior. Scoping is the source of many errors when using JavaScript. For more information, check out this great StackOverflow answer about Variable Scope in JavaScript.

JavaScript functions can access variables accessible but not declared within their scope at any time.

function counter(){
    let count = 0;
    return function(){
        // [count] here is the [count] from above
        count++;
        return count;
    }
}

let c = counter();
c() // 1
c() // 2
c() // 3
// ...

This type of setup is useful especially when variables shouldn't be accessible -- there's no way for external code to change count (on the other hand, if it were the property of an object anything could read and write it).

Another place this becomes useful is maintaining a reference to an object in event handlers and the like:

//This defines a single Timer object (like a singleton class,
//only one exists and you can't instantiate it).
let Timer = {
    count: 0,
    render: function () {
        console.log(this.count);
    },
    initialize: function () {
        //this puts a reference to [this] in this scope
        //which won't be overridden by the [this] in the scope
        //of functions defined later.
        let manager = this;

        //setInterval calls [argument1] every [argument2] milliseconds.
        setInterval(function () {
            //this reference to manager is maintained, while [this] now
            //references the function as passed to setInterval in this
            //scope.
            manager.count++;
            manager.render();
        }, 1000);
    }
};

Other syntax

Conditionals

JavaScript conditionals are a lot like C. There is if, else if, and else. We already talked about truthy and falsy values. You can convert a variable to a boolean type with !!(not not), so !!1 === true. This is a good way to determine if a value is "truthy" or "falsy" (just do console.log(!!myValue);. You can also use the Boolean() function for the same effect.

JavaScript also has a switch statement:

function legal(age) {
    let ableTo = [];

    switch (age) {
        case 18:
            ableTo.push('vote', 'fight', 'get real estate license');
            break;
        case 21:
            ableTo.push('drink', 'gamble');
            break;
        case 65:
            ableTo.push('retire');
            break;
        default:
            ableTo.push('be free');
    }

    return ableTo;
}

Loops

Similar deal here. It looks just like C:

for (let i=0; i < someArray.length; i++){
    console.log(someArray[i]);
}

One difference is that you can also iterate over an object or array:

for (let key in object) {
    console.log("key: " + key + " value: " + object[key]);
}
for (let index in array) {
    console.log("index: " + index + " value: " + array[index]);
}

Note that when used for the object case, this syntax gives you all the keys defined on the object including keys of its prototypes. That can lead to some unpleasant surprises, so in this case one would either use hasOwnProperty, which returns true if and only if the property name is defined for that object directly, or use something like Object.entries which only gets the object’s own keys. You DO NOT need to use this sort of syntax with arrays, because key would just be the index in the array, and this type of loop is slower.

for (let key in object) {
    if (!object.hasOwnProperty(key)){continue;}
    console.log("key: " + key + " value: " + object[key]);
}
// or using Object.entries() - this tends to be the popular choice
for (let [key, value] of Object.entries(object)) {
    console.log("key: " + key + " value: " + value);
}

JavaScript also has while loops just like C/Java, and you can use ++/-- unary operators on values. JavaScript also has a break keyword for getting out of a loop and a continue statement for going to the next iteration.

Events

Try clicking me!

If you haven't already, try clicking the box above. Wow! We were able to add this interactive functionality to this webpage using JavaScript to listen for a click event on the above div. But what are events? The DOM makes a ton of use of the delegate event model, meaning that when something happens in the browser, it emits events, which can be picked up (and modified) by any code that's listening. This makes it easy for programmers to add additional code to a page without worrying about other scripts (e.g. plugins, bookmarklets, frames), and allows for really decoupled design. Let's see another example:

Here is some syntax for adding event listeners to an object. It's nice because you can add and remove multiple listeners for the same event: Add this HTML between your body tags: <div id="display">press a key</div>. We're going to set up event listeners for keyup, so you can see when the user presses a key. Add the following code between the <script> tags in index.html:

const display = document.getElementById('display');
// Here we're going to make use of an anonymous function.
// Since functions are like any other object (number, string, etc.)
// in JavaScript, we can pass a function as an argument to another function
// without issue!
document.addEventListener('keyup', function (event) {
    // the event object can tell us which key is pressed
    display.textContent = "you pressed key #" + event.keyCode;
}, false);

Reload the page, and try pressing any key. The text should change and indicate the key that was pressed.

Keyboard keys in javascript are identified by their keys and codes. So to only do something when the user hits "enter":

document.addEventListener('keyup', function (event) {
    if (event.key === "Enter") {
        console.log("you hit enter!");
    }
}, false);

Notice that we're adding the event listener to document. The document is an element like any other, referring to everything inside the <html> tags. We're just adding the listener there because we want to call it on a keypress anywhere on the page. Events bubble up the DOM tree: if you click a button, you can listen to that event on any parent element of the button (which can sometimes be tricky if you weren't expecting to get that event there).

As another example, here is the code we used to create the click listener for the "You clicked me!" div above:

<div id="onclick-div" style="background-color: #FEBB14; padding: 15px; border: 4px solid #D64D37; margin-bottom: 20px; width: 300px; user-select: none">Try clicking me!</div>
<script>
    let counter = 0;
    const onclickDiv = document.getElementById("onclick-div");
    onclickDiv.addEventListener("click", function(event) {
        counter++;
        if (counter < 5) {
            onclickDiv.innerHTML = "You clicked me! (" + counter + ")";
        } else {
            onclickDiv.innerHTML = "Ok, please stop clicking me now ;-; (" + counter + ")";
        }
    });
</script>

innerHTML is a property of an element which represents its contents as HTML. Here we use it just to change the text inside the div, but we could also add more elements inside the div (so assigning onclickDiv.innerHTML = "<em>content</em>" will show a div with the word "content" inside of it italicized).

This is opposed to textContent which also represents the contents of an element, but as text. Any tags inside it are ignored, and any tags put into it are represented literally as text. (So assigning onclickDiv.textContent = "<em>content</em>" will show a div with the words "<em>content</em>", not italicized).

preventDefault and stopPropagation

Events have default behaviors depending on what triggered the event. For example, on a text input, the default behavior of a key press event is to add the key that was pressed to the value of the input. However, there are times when you might not always want the default behavior to happen and that is where preventDefault becomes useful. For example, if we wanted our text input to only accept lowercase letters, we could attach the following key press handler to our text input:

<input type="text" id="lowercase-input">
<script>
    const lowercaseInput = document.getElementById("lowercase-input");
    lowercaseInput.addEventListener("keypress", function(event) {
        const key = event.charCode
        if (key !== 0 && (key < 97 || key > 122)) {
            event.preventDefault();
        }
    });
</script>

stopPropagation is useful when you want to prevent further propagation of the current event in the capturing and bubbling phases. Event bubbling in JavaScript describes the order in which event handlers are called when one element is nested inside a second element, and both elements have registered a listener for the same event (such as a click). For example:

<div id="parent">
    <button id="child">click me</button>
</div>
<script>
    const child = document.getElementById("child");
    child.addEventListener("click", function(event) {
        console.log("child clicked");
    });
    const parent = document.getElementById("parent");
    parent.addEventListener("click", function(event) {
        console.log("parent clicked");
    });
</script>

Clicking on the button would result in the console printing “child clicked” then “parent clicked”. However if you don’t want the click event to propagate to the parent and want to prevent the parent from printing, you can change the child’s click listener to the following:

child.addEventListener("click", function(event) {
event.stopPropagation();
    console.log("child clicked");
});

What is the DOM?

From the W3C (the people who design web standards):

The Document Object Model (DOM) is an application programming interface (API) for valid HTML and well-formed XML documents. It defines the logical structure of documents and the way a document is accessed and manipulated. In the DOM specification, the term "document" is used in the broad sense.

The DOM represents elements (i.e. <a>, <div>, <p>) as nodes in a tree. These nodes are available as objects in JavaScript. You may find that you will often want to manipulate the document (we have already seen this, e.g. when we used getElementById() or innerHTML). If you want, you can read more about manipulating objects here.

A quick note on asynchronous programming in JavaScript

This is a fairly advanced but essential part of programming in JavaScript that we won’t go into great depth for the purposes of this prelab. After you’ve had a chance to process and digest the other information, we recommend doing a little independent research on this topic. If you don’t already know what asynchronous programming means, we recommend checking out this introduction. In short, although the JavaScript language is synchronous and single threaded by default, many things that JavaScript is used for needs an asynchronous approach. For example, one of JavaScript’s main jobs is to respond to user actions such as onClick, onMouseOver, onChange, onSubmit, which require an asynchronous approach. The JavaScript environment (browser) provides a set of API functionality that can handle this asynchronous functionality. However, this can result in problems when using variables that we expect to have a certain value but end up not being defined because the event (asynchronous) hasn’t completed yet.

The classic fix for this was the use of a callback function, which is passed as an argument to another function and only has its code executed when the event happens. As of ES6, Promises (highly recommended to look into!) were introduced as the new method of asynchronous programming and can be used to avoid a bunch of the problems with using callbacks. Promises can also be used with async functions and the await keyword introduced in 2017, and this is now arguably the best way to program asynchronously in JavaScript. You may find the following tutorials helpful down the road:

Promises in JavaScript with ES6 / ES2015
Exploring Async/Await Functions in JavaScript

Looking ahead: Using JSDoc + ESLint:

In addition, there are two widely used JavaScript add-ons that you should be aware of which we would like you to use in your assignments: JSDoc and ESLint. JSDoc allows you to add annotations to your JavaScript code, making it easier for you and other programmers to read your code. ESLint is used to help find bugs and errors in your code by statically analyzing it. It can be installed as a plugin with VS Code or configured as part of your Node.js project using npm.

Tasks:

Open the stencil

This is the end of the prelab! It is a lot to digest, so if you're confused about anything, feel free to come to TA Hours.

Handin Instructions