JavaScript is the scripting language that runs natively in every modern web browser. It controls page behaviour after the HTML and CSS have been parsed — responding to user input, manipulating the DOM tree, and communicating with remote servers. This article covers the language fundamentals required before moving on to frameworks or server-side environments.

JavaScript logo

Variable declarations

JavaScript provides three keywords for declaring variables, each with different scoping and mutability rules:

  • var — function-scoped, hoisted. Largely superseded by the two below.
  • let — block-scoped. The standard choice for values that change.
  • const — block-scoped. Required when a binding will not be reassigned. Objects and arrays declared with const can still be mutated.
const siteTitle = 'Nodexmiex';
let pageCount = 0;
pageCount += 1;

const config = { debug: false };
config.debug = true;

Functions and scope

Functions in JavaScript are first-class values — they can be assigned to variables, passed as arguments, and returned from other functions. There are two common syntaxes:

Function declarations

function greet(name) {
  return 'Hello, ' + name;
}

Arrow functions (ES2015+)

const greet = (name) => 'Hello, ' + name;

const add = (a, b) => {
  const result = a + b;
  return result;
};

Arrow functions do not have their own this binding, which makes them predictable inside callbacks and event handlers.

Closures

A closure is a function that retains access to the variables in its enclosing lexical scope, even after the outer function has returned:

function makeCounter() {
  let count = 0;
  return function increment() {
    count += 1;
    return count;
  };
}

const counter = makeCounter();
counter(); // 1
counter(); // 2

This pattern is used extensively in module patterns, event handlers, and factory functions. The MDN closures guide covers the memory implications in detail.

The DOM

When a browser parses an HTML document it creates the Document Object Model — a tree of node objects representing each element, attribute, and text node. JavaScript interacts with this tree through the document global:

Selecting elements

const heading = document.querySelector('h1');
const buttons = document.querySelectorAll('button');
const form = document.getElementById('contact-form');

Modifying elements

heading.textContent = 'Updated title';
heading.style.color = '#26251e';
heading.classList.add('highlighted');

Creating and inserting elements

const para = document.createElement('p');
para.textContent = 'Inserted paragraph.';
document.querySelector('main').appendChild(para);

Events

Browser interactions — clicks, key presses, form submissions, scroll position changes — are exposed as events. The standard pattern is to attach listeners using addEventListener:

const btn = document.querySelector('#submit-btn');

btn.addEventListener('click', function (event) {
  event.preventDefault();
  console.log('Button clicked');
});

Event listeners are removed with removeEventListener, which requires a reference to the exact same function passed to addEventListener.

Asynchronous JavaScript

JavaScript runs on a single thread but handles asynchronous operations — network requests, timers, file I/O in Node.js — through a non-blocking event loop. Modern code uses async/await built on top of Promises:

Promises

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

delay(1000).then(() => console.log('One second passed'));

Async/await

async function fetchData(url) {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Request failed:', error);
  }
}

The MDN async function reference describes error propagation and return values.

Modules (ES2015+)

JavaScript modules allow code to be split across files with explicit exports and imports, replacing the older pattern of relying on global variables:

export function formatDate(date) {
  return date.toISOString().split('T')[0];
}

import { formatDate } from './utils.js';

In browsers, modules are loaded via <script type="module">. Node.js supports them natively in files with the .mjs extension or when "type": "module" is set in package.json.

Further reading