JavaScript ES6+ Basics

Variables

let, const, and why not var
const name = "Evan";    // immutable binding (preferred)
let count = 0;          // mutable binding (when reassignment needed)
// var legacy = true;   // function-scoped, hoisted — avoid

// const prevents reassignment, not mutation
const arr = [1, 2, 3];
arr.push(4);           // OK — array is mutated, binding unchanged
// arr = [5, 6];       // TypeError: Assignment to constant

const is the default. Use let only when you need to reassign. var has function scope and hoisting — it creates subtle bugs.

Destructuring

Extract values from arrays and objects
// Object destructuring
const host = { name: "sw01", ip: "10.50.1.10", port: 22 };
const { name, ip, port } = host;

// Rename and defaults
const { name: hostname, vlan = 10 } = host;

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// Swap without temp variable
let a = 1, b = 2;
[a, b] = [b, a];

// Function parameter destructuring
function connect({ host, port = 443, timeout = 5000 }) {
    console.log(`${host}:${port} (timeout: ${timeout}ms)`);
}
connect({ host: "example.com" });

Destructuring is one of the most-used ES6 features. It eliminates repetitive property access and creates clear function signatures.

Arrow Functions

Concise function syntax
// Traditional
function add(a, b) { return a + b; }

// Arrow — expression body (implicit return)
const add = (a, b) => a + b;

// Arrow — block body (explicit return needed)
const process = (data) => {
    const result = data.filter(x => x > 0);
    return result;
};

// Single parameter — no parentheses needed
const double = x => x * 2;

// No arguments
const now = () => Date.now();

Arrow functions do not have their own this — they inherit from the enclosing scope. This is usually what you want. Use traditional function when you need dynamic this (event handlers, object methods).

Template Literals

String interpolation and multi-line strings
const host = "sw01";
const port = 22;

// Interpolation
const msg = `Connecting to ${host}:${port}`;

// Multi-line
const config = `
[server]
host = ${host}
port = ${port}
`;

// Tagged template literals — custom processing
function sql(strings, ...values) {
    // sanitize values here
    return strings.reduce((acc, str, i) =>
        acc + str + (values[i] ?? ''), '');
}

Spread & Rest

Expand and collect elements
// Spread — expand array/object
const a = [1, 2, 3];
const b = [...a, 4, 5];  // [1, 2, 3, 4, 5]

const defaults = { port: 80, timeout: 5000 };
const config = { ...defaults, port: 443 };  // override port

// Rest — collect remaining arguments
function log(level, ...messages) {
    console.log(`[${level}]`, ...messages);
}
log("INFO", "server started", "port 8080");

// Shallow clone
const clone = [...original];
const objClone = { ...original };

Spread creates shallow copies. For nested objects, use structuredClone() (modern) or JSON.parse(JSON.stringify(obj)) (legacy).

Optional Chaining & Nullish Coalescing

Safe property access and defaults
const user = { profile: { name: "Evan" } };

// Optional chaining — returns undefined instead of throwing
const city = user?.address?.city;  // undefined (no error)
const first = arr?.[0];            // optional array access
const result = obj?.method?.();    // optional method call

// Nullish coalescing — default only for null/undefined
const port = config.port ?? 8080;  // uses 8080 if port is null/undefined
// Different from ||, which also triggers on 0, "", false
const count = data.count ?? 0;     // preserves 0 as a valid value

?. and ?? were added in ES2020. They eliminate entire classes of "Cannot read property of undefined" errors.

Modules

import/export (ESM)
// Named exports — multiple per file
export const PORT = 8080;
export function connect(host) { ... }

// Default export — one per file
export default class Scanner { ... }

// Named imports
import { PORT, connect } from './config.js';

// Default import
import Scanner from './scanner.js';

// Rename on import
import { connect as tcpConnect } from './network.js';

// Dynamic import — lazy loading
const module = await import('./heavy-module.js');

ESM (import/export) is the standard. CommonJS (require/module.exports) is Node.js legacy.