JavaScript ES6+ Basics
Variables
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
// 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
// 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
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
// 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
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
// 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.