JavaScript¶
While HTML and CSS are focused on structure and presentation, JavaScript exists to enable custom interactions on the page.
In many ways JavaScript is a programming language in the traditional sense: variables, conditionals, loops, functions, etc.
Teaching programming in this form is beyond the scope of this guide. Instead we'll focus on the things that make JavaScript unique.
Background¶
To understand JavaScript it is helpful to start with where it came from, and why it is important today.
JavaScript is a programming language introduced in 1995 as a feature of the dominant web browser of the era, Netscape.
At the time, a webpage was typically a straightforward document or form. Web pages were written entirely in static HTML, meaning that whatever loaded when you visited a URL was what you got— the arrangement of words & images on the page didn't change after the page was loaded from the server. Allowing programmers to write code that ran in the browser would mean that pages could have interactive elements; someone could dynamically alter the contents of a page, creating more interactive experiences like games and spreadsheets.
The original version of the language was written by one person, and it took 10 days. That is both an impressive feat and also an indication of the level of planning that went into that first version. In 1995 nobody foresaw what the web would become and the role that JavaScript would play. That is important because it results in a lot of the shortcomings we're left with over 30 years later.
JavaScript vs. Java
The name JavaScript was essentially a marketing term. The language has no formal relationship to Java, which at the time was emerging as a popular and important language. Java is closer to C++ or C# in semantics and purpose, a compiled language used for building standalone applications. Java is a by-the-book object oriented language, and JavaScript is, as we will see, definitely not.
JavaScript borrowed heavily from more obscure origins, Self and Scheme. It is from these that it gets two of its most unique characteristics: prototypal inheritance, and a wealth of functional programming features.
Soon after Netscape introduced JavaScript, other browsers like Internet Explorer had their own versions (JScript). Without cooperation each web browser developed a unique implementation of the language. These implementations were more-or-less compatible, but each had its own quirks. While there was some core agreement, each browser would add custom methods and attributes to their DOM implementations and browser APIs.
Developing for the web requires making a choice:
- stick to the standardized subset of the language that mostly works across all browsers
- use features specific to a single browser, limiting your audience as a result
- use libraries (sometimes called polyfills) that add unsupported features to browsers that lack them
This was particularly challenging in the early "Web 2.0" era when Chrome, Firefox, Safari, and Internet Explorer all had significant market share and rapidly evolving implementations which often diverged.
The situation is much better today; JavaScript has been standardized as ECMA-262, or ECMAScript1.
ECMAScript is now often abbreviated to ES.
A large portion of the JavaScript community prefers ES as a name, and quite a few tools use it as a prefix (e.g. eslint, esbuild).
Despite this standardization, Can I Use? is an important site for any developer building anything of complexity with newer JavaScript (or CSS) features.
Backwards Compatibility¶
JavaScript, like all web languages, cannot break backwards compatibility without breaking sites on the web. As a result, instead of old features (and even bugs) being removed, they are often left in, sometimes deprecated, but available nonetheless. On the other hand, JavaScript changes rapidly and best practices continue to evolve.
This means JavaScript is a language that has more than one way of doing most things.
You may discover that reading JavaScript code from Stack Overflow or generated by Claude varies from what is presented here. There is a lot of old and not-very-good JavaScript out there. This guide will focus on the currently accepted best practice, and only briefly touch upon alternatives.
Common Pitfalls¶
In part due to the inability to break backwards compatibility, the language has more pitfalls than most. Many of these are due to its laxness on things other languages are strict about.
Silent Errors¶
As we saw with HTML and CSS, a design principle of the early web was that pages shouldn't show errors, ignoring or silently correcting broken code.
This means that many things that you'd expect to raise an exception in JavaScript will instead fail silently, such as referencing an undefined property, or using types incorrectly. This can be frustrating, especially as you are learning.
Tip
If you are using type="module" on your script tags, JavaScript will execute in strict mode which means fewer mistakes passing without error.
When errors do occur, they will be visible in the browser's developer console, not on the page itself.
Equality¶
Another place that this laxness can be a real pain is in how JavaScript handles type conversions.
In many languages you get a type error if you try to take an illegal action between two types, such as dividing a string by an integer.
In JavaScript "abc"/3 results in a value called NaN.
Unlike Python where a TypeError would occur, almost any operation between two types will give some result. When a Number is involved, that's often a NaN.
But other types will do surprising things. These expressions are true:
[]*2 === 0[4]*2 === 8[4, 5]*2is aNaN{} * {}is aNaN{} + {} === '[object Object][object Object]'Array(16).join("wat" - 1) + " Batman" === "NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman"
Yeah... it is a mess.
These type coercion rules are learnable, and I've seen JavaScript interviews that ask people to solve the last one.
But as a best practice, we want to avoid triggering any of these.
Instead of relying on this behavior we'll need to be very careful about types, and know that if we're getting a strange bug like output that includes [object Object], we probably used a type incorrectly.
=== is not a typo: it is actually a key part of avoiding type coercion.
== will do coercion, so some strange things are equal to one another.
1 == "1"0 == []"" == falsefalse == ["0"]
We never want this behavior.
Most JavaScript style guides ban == as a result.
Use === instead.
=== does not compare within objects or arrays
On objects and arrays, equality is defined as the two being the same object, not having the same values.
In JS these are both false:
[1, 2] === [1, 2]{x: 1, y: 2} === {x: 1, y: 2}
Formatters & Linters¶
The many quirks of the language are partly mitigated today by a strong culture of using tools that detect & correct common mistakes:
- eslint is a linter similar to
ruff checkorpylintin Python. - prettier is an autoformatter similar to
ruff formatorblackin Python.
Your editor likely supports these too.
JavaScript in the Browser¶
Unlike a standalone program, JavaScript in the browser is being run in tandem with the browser's own code.
When viewing a website, the browser is running code that is live-updating the user interface defined by the HTML and CSS. When things happen: a window is resized, a link is clicked, or any number of "user generated events" occur, the browser is primarily responsible for doing the right thing. The JavaScript that you write exists within this context. It primarily will be called by the browser in response to events: a button is clicked, a slider value is changed, an external API has returned results. It is also possible to ask JavaScript to call your code after a timer, a common technique used in animation to update the page on regular intervals.
Most of the challenge for developers who have written in languages like Python but are new to JavaScript is in understanding how programming within the browser differs from CLI/TUI/GUI programs they're more accustomed to. This article will discuss some of the peculiarities of working in this environment.
<script> tags¶
<script> tags typically live in the <head>, or occasionally at the very end of the <body> (helping ensure the entire page has loaded prior to their execution).
A <script> tag can either contain inline JavaScript:
<script>
function f() {
// writes to the developer console
console.log("Hello World");
}
f();
</script>
Or can reference a remote JS file:
</script>
Script is not a void or self-closing element!
In both forms, </script> must be present!
Regardless of whether the code is inline or external, there are two ways the browser can be told to interpret each separate <script>:
- In the examples above, the JavaScript on a page is added to a global context. Similarly to how CSS works, the various JS files are appended together in the order they appear. This means two blocks on the page share variables:
<head>
<script src="d3.min.js"></script>
<script>
const TOKEN = "abc123";
function exampleFunc(s) {
alert("Hello " + s + "!");
}
</script>
</head>
<body>
...
<script>
// a single shared context, even if this were in separate .js file
exampleFunc(TOKEN);
// d3 is available on the page, no import needed
d3.select("body").style("background", "tomato");
</script>
</body>
It is also possible to add type="module", which changes the semantics of the <script> tag. Each of these will have its own scope, similar to different .py files. It also enables the import/export statements and some other newer ES features:
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
</script>
<script type="module" src="main.js"></script>
This is ECMAScript Module mode, or ESM.
While there are valid reasons to prefer either approach, it is recommended you don't mix the two styles on the same page as interoperability is complex and error prone.
Sandboxing¶
HTML is typically downloaded via HTTP.
That means a web server sends your browser a chunk of HTML and your browser turns that into a webpage you can read & interact with.
HTML alone poses a limited threat.
It can only present content, but now with JS executable code is being sent over the network and your computer is expected to execute it without even showing it to you first.
What if someone sends the equivalent of os.system("rm -rf /")?
This should set off alarm bells!
To cut down on the risks here, JavaScript in the browser runs "sandboxed", meaning it can't access key parts of your operating system: writing to your local disk takes special permissions. The browser can store things, and JavaScript may request to read or write to these, but in a much more limited way than being able to open any file on your drive the way the default Python runtime can.
DOM Manipulation¶
Instead of printing to a terminal, JavaScript programs manipulate the structure and content of the HTML on the page. This simple idea opens up a ton of possibilities, and the key innovation that drives it is referred to as the document object model or the DOM.
In the context of the browser, JavaScript code can always rely upon the existence of a global variable named document that represents the root of the tree.
It exists as a global variable each JavaScript program can access and manipulate.
mindmap
root((<html>))
<body>
<div class=sidebar>
<ul>
<li>
<li>
<div class=content>
<p>
<img>
<p>
<head>
<title>
<meta>
<script>
<link type=text/css>
HTML parses into a tree. (If you've done web scraping, you've navigated this tree via XPath or CSS selectors.)
The original DOM implementation did not support querying by CSS selectors. Instead you would use:
// to get a collection of all div tags on the page
document.getElementsByTagName("div");
// navigate by position in tree
document.documentElement.firstChild.nextSibling.nextSibling;
The purpose of JavaScript in the browser is to modify what is on the page.
This means your code won't use print statements. Instead, you will typically be manipulating the HTML via the DOM.
The best way to select elements is to use
querySelector and
querySelectorAll, which accepts CSS selectors and returns either a single element or a NodeList, an ordered collection of elements.
Example
Element¶
You'll find yourself working a lot with the Element type, which has the commonly used attributes:
elem.classList- Array-like object of classes on this element. (Already split, soclass="one two"becomesclassList=["one", "two"]).elem.innerHTML- Directly access/modify HTML between start & end tag for this element. Should only be used with care since it can be used to inject scripts into the page. (XSS)elem.textContent- Directly access/modify text content between start & end tag for this element.elem.attributes- An object mapping attributes to their values.
See MDN: Element for the full list.
Event Handling¶
Any JavaScript that is outside a function in a bare <script> tag or .js will be executed as the relevant element loads.
This code is typically limited to defining functions and setting up callbacks.
Callbacks are functions we ask the browser to call when certain things happen. These functions receive an event object with details on the event: where the mouse was when it was clicked, what key was pressed, etc. The type and properties of this object vary depending on the kind of event.
A few common events:
- click event - fires when a mouse is clicked on an element
- focus event - fires when an element gains focus (or
blurwhen focus is lost) - scroll event - fires when the page is scrolled
Example
Loading Data¶
In the early web, the only way to change the data on a page was to follow a link, reloading the entire page.
In 1999, Microsoft introduced an unofficial API known as XMLHttpRequest which allowed web pages to make additional HTTP requests from within a page without reloading.
This additional data could then be used to update a small region of the page, instead of a full page reload.
This was a relatively simple idea that revolutionized the web and laid the technical foundation for what we consider "Web 2.0".
You may come across the term AJAX. This is a (now somewhat dated) term for asynchronous JavaScript and XML that was coined in the mid 2000s to describe this approach.
Modern web applications like GMail or Slack rely heavily upon this idea, to present something that feels more like an application than a web page.
A traditional HTTP request:
sequenceDiagram
autonumber
browser->>web server: GET /index.html HTTP/1.1
web server->>browser: ... 200 OK ...
browser->browser: render
browser->>web server: POST /login HTTP/1.1
web server->>browser: ... 200 OK ...
browser->browser: re-render
Each request generates a new response, and the browser reloads the page each time.
Instead, with AJAX:
sequenceDiagram
autonumber
browser->>web server: GET /index.html HTTP/1.1
web server->>browser: ...200 OK ...
browser->browser: render
browser->>web server: fetch(...)
web server->>browser: json | data={"success": ...}
browser->browser: handleResponse(data)
While they achieve similar results, at step 4 things diverge. The request is sent via JS, in the background, instead of the browser URL bar, avoiding a page reload.
At step 5, JSON is returned, containing just the information that the site needs to update.
At step 6, the page is updated via a JS function handleResponse, instead of reloading the entire page.
fetch¶
Instead of waiting to execute like a call to requests.get in Python, the call returns immediately, returning something called a promise. This means if you want to use the data you need to ask JavaScript to call a callback function once the data is loaded:
// short example, does not include error handling
fetch("example.csv")
.then((response) => response.text())
.then((text) => {
// this arrow function runs once the content has been loaded
// your code goes here
const data = csvParse(text);
doSomethingWith(data);
});
If you are using type=module, you can also use the await keyword at the top level (not merely inside functions) to have your code wait until a result is available before executing the next line:
const response = await fetch("example.csv");
const text = await response.text();
// now we can use `text` as we did above
const data = csvParse(text);
doSomethingWith(data);
For more, see MDN: Using Fetch.
Animation¶
GUI-based programs like browsers are constantly redrawing the window. A typical monitor has a 60hz refresh rate, which means that the screen is redrawn 60 times per second.
To avoid visible stutter and feeling laggy, draw() should be called 30-60 times per second. That means that update() can only take ~1/100th of a second to leave room for drawing/updating each frame.
The library p5.js is a good way to experiment with this concept:
Example
and with interaction:
Example
Tip
P5 is based on one of the oldest data visualization tools: https://processing.org. It is a great way to get up & running quickly with animations, art, or even simple games in JS.
CSS Transitions¶
P5 gave us a direct look at what it takes to animate every single frame.
In practice, if you are styling your HTML or SVG with CSS rules, the easiest path is often to change the rule and let the browser calculate the intermediate steps.
The transition property allows delaying the application of CSS rules. In the example below, the background-color and font-size do not immediately take effect when the relevant properties are changed:
Example
setInterval & requestAnimationFrame¶
Animations often need continuous updates to calculate intermediate values during transitions or while simulations play out.
If you find that you need to call a function regularly without an event trigger, you can use setInterval, which calls a callback function using a timer instead of events.
Example
If this is being used purely for animation, you can ask the browser to call your function every time it is about to draw the screen (likely 30, 60, or 120Hz) using setAnimationFrame.
Example
<canvas>¶
Whereas svg is made up of additional elements comprising a vector image, canvas allows drawing raster images using JavaScript.
Like SVG, you typically begin with a single <canvas> element.
Unlike SVG, which can be written inline, the only way to get content inside this element is to call special JS functions on the element:
Example
This can be combined with animation to provide fluid graphics in the browser that would otherwise be limited by the speed of updating the DOM.
-
ECMA is a standards organization: https://ecma-international.org ↩