Polyglot
Languages Web (HTML / CSS / JS) syntax
Web (HTML / CSS / JS) § syntax

Syntax

JavaScript’s syntax is C-family at the surface — curly braces, semicolons (admitted but rarely required since automatic semicolon insertion — ASI — admits omission in most cases), parentheses around if and while conditions. The conventional contemporary form is ES2015+ (also called ES6+) — admits let and const (block-scoped, replacing legacy var), arrow functions (=>), template literals (backticks), destructuring, spread/rest (...), modules (import/export), and classes. The combination — C-family core syntax, the substantial ES2015+ additions, the ASI mechanism, the conventional 'use strict' directive — is the substance of JavaScript’s syntactic identity.

This page covers the surface a working web developer encounters routinely.

A complete script

The classical hello world:

console.log("Hello, world!");

A more substantial example:

import { fetchUser } from "./api.js";

async function greet(userId) {
    try {
        const user = await fetchUser(userId);
        const greeting = `Hello, ${user.name}!`;
        document.getElementById("greeting").textContent = greeting;
    } catch (err) {
        console.error("Failed to greet:", err);
    }
}

document.getElementById("login-button").addEventListener("click", () => {
    const userId = document.getElementById("user-id").value;
    greet(userId);
});

The principal features visible:

  • import { ... } from "..." — ES module import.
  • async function ... { await ... } — async/await.
  • try { ... } catch (err) { ... } — exception handling.
  • const/let — modern variable declarations.
  • Template literal`Hello, ${user.name}!`.
  • Arrow function() => { ... }.

Source character set

JavaScript source is interpreted as UTF-16 by default; modern tooling typically uses UTF-8 source files. Identifiers admit Unicode letters; the conventional code uses ASCII identifiers.

Identifiers and naming conventions

The conventional JavaScript naming follows community style:

FormUseExample
camelCasevariables, functions, methodsuserName, fetchData
PascalCaseclasses, constructorsUser, EventEmitter
UPPER_SNAKE_CASEtrue constantsMAX_RETRIES
_leadingUnderscoreconventional “private” (not enforced)_internalState
#fieldactually private (ES2022+)#balance

Reserved keywords

The principal reserved words:

break       case        catch       class       const
continue    debugger    default     delete      do
else        export      extends     false       finally
for         function    if          import      in
instanceof  new         null        return      super
switch      this        throw       true        try
typeof      var         void        while       with
yield

// Strict mode reserved:
let         static      implements  interface   package
private     protected   public

// Reserved for future:
enum

Comments

Two comment forms:

// A single-line comment, terminated by the end of the line.

/* A block comment.
   Block comments do NOT nest.
   /* This is just a literal */
*/

/**
 * JSDoc — admits substantial documentation tooling.
 *
 * @param {string} name - The user's name.
 * @returns {string} The greeting.
 */
function greet(name) {
    return `Hello, ${name}`;
}

The /** ... */ (JSDoc) form admits substantial documentation processing — many tools (TypeScript inference, IDE tooling, generated docs) consume JSDoc.

Statement terminators

JavaScript admits Automatic Semicolon Insertion (ASI) — newlines may terminate statements:

const x = 5
const y = 10
const z = x + y

// Equivalent:
const x = 5; const y = 10; const z = x + y;

The conventional discipline:

  • With semicolons — explicit, avoids ASI pitfalls.
  • Without semicolons — fewer tokens, leans on ASI; requires substantial care for some pitfalls.

The principal ASI pitfall:

const a = b
[c, d].forEach(...)                                // parsed as: const a = b[c, d].forEach(...)

// With semicolon:
const a = b;
[c, d].forEach(...);                               // safe

// Without semicolons (requires leading semicolon on lines starting with [, (, /, +, -):
const a = b
;[c, d].forEach(...)                               // safe

The conventional contemporary discipline (with linters like Prettier, ESLint) handles this automatically.

var, let, const

Three principal declarations:

var x = 5;                                         // function-scoped (legacy)
let y = 10;                                        // block-scoped, mutable
const z = 15;                                      // block-scoped, immutable binding

The principal differences:

varletconst
ScopeFunctionBlockBlock
HoistingYes (with undefined)TDZTDZ
ReassignableYesYesNo
RedeclarableYesNoNo

The conventional contemporary discipline:

  • Default to const — admit immutable bindings.
  • Use let when reassignment is required.
  • Avoid var — function-scoping admits substantial pitfalls.
// Block scoping with let/const:
{
    const x = 5;
    // x visible here
}
// x NOT visible here

// var hoisting pitfall:
console.log(x);                                    // undefined (hoisted)
var x = 5;

// let TDZ:
console.log(y);                                    // ReferenceError
let y = 5;

const admits immutable binding — the binding cannot be reassigned. Object contents may still mutate:

const obj = { count: 0 };
obj.count = 1;                                     // OK; mutating contents
obj = {};                                          // ERROR; cannot reassign

Block syntax

Control-flow constructs admit braces (or single statement):

if (condition) {
    doSomething();
}

if (condition) doSomething();                      // OK; one-liner

while (!done) {
    advance();
}

for (let i = 0; i < 10; i++) {
    process(i);
}

The conventional discipline always uses braces — admit substantial readability.

Functions

Three principal forms:

// Function declaration:
function add(a, b) {
    return a + b;
}

// Function expression:
const add = function(a, b) {
    return a + b;
};

// Arrow function:
const add = (a, b) => a + b;
const add = (a, b) => {
    return a + b;
};
const square = n => n * n;                         // single param: parens optional
const noArgs = () => 42;                           // no params: parens required

The principal differences (function vs arrow):

  • this binding — functions have own this; arrows inherit lexical this.
  • arguments — functions have arguments; arrows do not.
  • new — functions are constructible; arrows are not.
  • Hoisting — function declarations hoisted; arrows not.

The conventional contemporary discipline:

  • Arrow functions for short callbacks and methods that need lexical this.
  • Function declarations for top-level named functions.

Treated in Functions and closures.

Object literals

const point = {
    x: 10,
    y: 20,
    distance() {
        return Math.sqrt(this.x ** 2 + this.y ** 2);
    }
};

// Shorthand properties:
const x = 10, y = 20;
const point = { x, y };                            // equivalent to { x: x, y: y }

// Computed keys:
const key = "name";
const obj = { [key]: "Alice" };                    // { name: "Alice" }

// Spread:
const base = { a: 1, b: 2 };
const extended = { ...base, c: 3 };                // { a: 1, b: 2, c: 3 }

Arrays

const arr = [1, 2, 3, 4, 5];

// Spread:
const more = [...arr, 6, 7];                       // [1, 2, 3, 4, 5, 6, 7]
const combined = [...arr1, ...arr2];

// Rest in destructuring:
const [first, ...rest] = arr;                      // first=1, rest=[2,3,4,5]

// Trailing comma admitted:
const items = [
    "apple",
    "banana",
    "cherry",                                      // trailing comma OK
];

Destructuring

Substantial in modern JavaScript:

// Object destructuring:
const { name, age } = user;
const { name: userName, age: userAge } = user;     // rename
const { name = "anonymous", age = 0 } = user;      // defaults

// Array destructuring:
const [first, second] = arr;
const [head, ...tail] = arr;
const [, , third] = arr;                           // skip

// Nested:
const { user: { profile: { name } } } = data;

// In function params:
function greet({ name, age }) {
    console.log(`${name} is ${age}`);
}

// With defaults:
function fetch(url, { method = "GET", headers = {} } = {}) {
    // ...
}

Template literals

Backtick-delimited strings admit interpolation and multi-line:

const name = "Alice";
const age = 30;

const greeting = `Hello, ${name}!`;
const description = `${name} is ${age} years old`;

const multiline = `
    First line
    Second line
    Third line
`;

// Tagged templates:
const html = String.raw`<div>${name}</div>`;

Treated in Strings.

Spread and rest

The ... admits spread (in calls and literals) and rest (in destructuring):

// Spread:
const arr = [1, 2, 3];
console.log(Math.max(...arr));                     // 3 (spread as args)
const more = [...arr, 4];                          // array spread
const obj2 = { ...obj1, x: 10 };                   // object spread

// Rest:
function sum(...nums) {
    return nums.reduce((a, b) => a + b, 0);
}

const [first, ...rest] = arr;
const { name, ...others } = user;

'use strict'

The directive enables stricter parsing and error handling:

'use strict';

x = 5;                                             // ERROR: x is not declared

ES modules and class bodies are strict by default. The conventional contemporary discipline uses ES modules — admit substantial implicit strict mode.

Modules

ES modules (the conventional contemporary form):

// math.js
export function add(a, b) { return a + b; }
export const PI = 3.14159;
export default function multiply(a, b) { return a * b; }

// main.js
import multiply, { add, PI } from "./math.js";

console.log(add(2, 3));
console.log(multiply(2, 3));

In HTML:

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

Treated in Scope and modules.

Classes

ES2015+ class syntax (sugar over prototypes):

class Point {
    #x;                                            // private field
    #y;

    constructor(x, y) {
        this.#x = x;
        this.#y = y;
    }

    get x() { return this.#x; }
    get y() { return this.#y; }

    distance(other) {
        return Math.sqrt((this.#x - other.x) ** 2 + (this.#y - other.y) ** 2);
    }

    static origin() {
        return new Point(0, 0);
    }
}

class ColoredPoint extends Point {
    constructor(x, y, color) {
        super(x, y);
        this.color = color;
    }
}

Treated in Classes and prototypes.

A note on what JavaScript admits

Several distinguishing features:

  • Dynamic typing — types attach to values, not variables.
  • Prototype-based inheritance — every object admits a hidden link to another.
  • First-class functions — functions are values.
  • Closures — substantial, ubiquitous.
  • Garbage collection — automatic memory management.
  • Single-threaded — event-loop concurrency model.
  • Async/await — admits substantial sequential-style async code.
  • Object literal — substantial, with substantial shorthand syntax.
  • Truthy/falsy coercion0, "", null, undefined, NaN, false are falsy.
  • Two equality operators== (with coercion) and === (strict).
  • No integer type (until BigInt) — all numbers were Number (IEEE 754 double).

A note on what is not in JavaScript

  • No static types (until TypeScript) — admit substantial runtime errors.
  • No threading in the language — Web Workers admit substantial parallelism.
  • No native print()console.log is the conventional substitute.
  • No standard file I/O in browsers — File API and Fetch are the substitutes.
  • No traditional namespaces — modules and objects are the conventional substitute.
  • No native immutabilityObject.freeze admits shallow freeze.

The combination — dynamic typing, prototype-based objects, first-class functions, single-threaded event-loop concurrency, ES2015+ syntactic richness, the substantial Web platform integration — is the substance of JavaScript’s identity. The discipline of writing modern JavaScript is largely about using the contemporary ES2015+ features (const, arrow functions, destructuring, modules, classes, async/await) and avoiding the legacy pitfalls (var, ==, accidental globals).