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.
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 withconstcan 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.