All skills
ccheney avatar

/modern-javascript

@00695ba

Implement or debug JavaScript language behavior, including promise completion, async iteration, mutation, and runtime compatibility. Use for ECMAScript semantics or polyfill/transform choices; not unrelated framework or TypeScript type design.

Use this Skill: https://skilld.dev/gh/ccheney/robust-skills/modern-javascript

This session only. Nothing lands on disk.

referencesES2022-ES2023.md

≈2.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

ES2022 & ES2023 Features

.at() indexing, Object.hasOwn(), top-level await, private class fields, static blocks, error cause, RegExp match indices, .toSorted(), .toReversed(), .toSpliced(), .with(), .findLast(), .findLastIndex(), hashbang grammar, symbols as WeakMap keys.

Contents

ES2022 (ES13)

Array.prototype.at()

Access elements from the end with negative indices.

const arr = ['a', 'b', 'c', 'd', 'e'];

// Positive indices (same as bracket notation)
arr.at(0);   // 'a'
arr.at(2);   // 'c'

// Negative indices (from end)
arr.at(-1);  // 'e' (last element)
arr.at(-2);  // 'd' (second to last)

// Works on strings too
'hello'.at(-1);  // 'o'

// Works on TypedArrays
new Uint8Array([1, 2, 3]).at(-1);  // 3

Migration pattern:

// ❌ Before
const last = arr[arr.length - 1];
const secondLast = arr[arr.length - 2];
// ✅ After
const last = arr.at(-1);
const secondLast = arr.at(-2);

Object.hasOwn()

Safe alternative to hasOwnProperty.

const obj = { name: 'Alice' };

// ✅ Safe and concise
Object.hasOwn(obj, 'name');      // true
Object.hasOwn(obj, 'toString');  // false (inherited)

// Works with objects that don't have hasOwnProperty
const nullProto = Object.create(null);
nullProto.key = 'value';
Object.hasOwn(nullProto, 'key');  // true
// nullProto.hasOwnProperty('key')  // TypeError!

Why not hasOwnProperty:

// hasOwnProperty can be overwritten
const malicious = {
  hasOwnProperty: () => false,
  secret: 'data'
};
malicious.hasOwnProperty('secret');  // false (wrong!)
Object.hasOwn(malicious, 'secret');  // true (correct!)

Top-Level Await

Use await at module top level without async wrapper.

// config.js
const response = await fetch('/api/config');
export const config = await response.json();
// db.js
import { config } from './config.js';
export const db = await connectDatabase(config.dbUrl);
// app.js
import { db } from './db.js';  // Waits for db to be ready

Use cases:

  • Loading configuration
  • Initializing database connections
  • Dynamic module loading
  • Conditional imports

Class Private Fields and Methods

True encapsulation with # prefix.

class BankAccount {
  #balance = 0;  // Private field
  #transactionHistory = [];

  constructor(initial) {
    this.#balance = initial;
  }

  // Private method
  #logTransaction(type, amount) {
    this.#transactionHistory.push({ type, amount, date: new Date() });
  }

  deposit(amount) {
    this.#balance += amount;
    this.#logTransaction('deposit', amount);
  }

  withdraw(amount) {
    if (amount > this.#balance) throw new Error('Insufficient funds');
    this.#balance -= amount;
    this.#logTransaction('withdrawal', amount);
  }

  get balance() {
    return this.#balance;
  }

  // Private field check
  static isAccount(obj) {
    return #balance in obj;
  }
}

const account = new BankAccount(100);
account.deposit(50);
// account.#balance  // SyntaxError: Private field
// account.#logTransaction()  // SyntaxError: Private method

Static Class Fields and Blocks

class Config {
  static #instance;
  static version = '1.0.0';
  static features = [];

  // Static initialization block
  static {
    console.log('Initializing Config class');
    this.features = ['auth', 'logging'];

    if (process.env.NODE_ENV === 'development') {
      this.features.push('debug');
    }
  }

  static getInstance() {
    if (!this.#instance) {
      this.#instance = new Config();
    }
    return this.#instance;
  }
}

Error Cause

Chain errors with context.

async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error('Failed to fetch user', {
        cause: { status: response.status, userId }
      });
    }
    return await response.json();
  } catch (error) {
    throw new Error('User data unavailable', { cause: error });
  }
}

try {
  await fetchUserData(123);
} catch (error) {
  console.error(error.message);        // 'User data unavailable'
  console.error(error.cause.message);  // 'Failed to fetch user'
  console.error(error.cause.cause);    // { status: 404, userId: 123 }
}

RegExp Match Indices

Get start/end positions with /d flag.

const str = 'Hello World';
const regex = /(?<greeting>Hello) (?<subject>World)/d;
const match = str.match(regex);

console.log(match.indices);
// [[0, 11], [0, 5], [6, 11]]
// [fullMatch, greeting, subject]

console.log(match.indices.groups);
// { greeting: [0, 5], subject: [6, 11] }

ES2023 (ES14)

Array Change-by-Copy Methods

New methods that return modified copies without mutating the original.

toSorted()
const numbers = [3, 1, 4, 1, 5];

// ❌ Mutates original
const sorted = numbers.sort((a, b) => a - b);
console.log(numbers);  // [1, 1, 3, 4, 5] - mutated!

// ✅ Returns new array
const numbers2 = [3, 1, 4, 1, 5];
const sorted2 = numbers2.toSorted((a, b) => a - b);
console.log(numbers2);  // [3, 1, 4, 1, 5] - unchanged
console.log(sorted2);   // [1, 1, 3, 4, 5] - new array
toReversed()
const arr = [1, 2, 3, 4, 5];

// ❌ Mutates original
const reversed = arr.reverse();

// ✅ Returns new array
const arr2 = [1, 2, 3, 4, 5];
const reversed2 = arr2.toReversed();
console.log(arr2);       // [1, 2, 3, 4, 5] - unchanged
console.log(reversed2);  // [5, 4, 3, 2, 1] - new array
toSpliced()
const months = ['Jan', 'Mar', 'Apr'];

// ❌ Mutates original
months.splice(1, 0, 'Feb');

// ✅ Returns new array
const months2 = ['Jan', 'Mar', 'Apr'];
const withFeb = months2.toSpliced(1, 0, 'Feb');
console.log(months2);  // ['Jan', 'Mar', 'Apr'] - unchanged
console.log(withFeb);  // ['Jan', 'Feb', 'Mar', 'Apr'] - new array

// Remove elements
const withoutMar = months2.toSpliced(1, 1);
// ['Jan', 'Apr']
with()

Replace element at index.

const colors = ['red', 'green', 'blue'];

// ❌ Mutates original
colors[1] = 'yellow';

// ✅ Returns new array
const colors2 = ['red', 'green', 'blue'];
const updated = colors2.with(1, 'yellow');
console.log(colors2);  // ['red', 'green', 'blue'] - unchanged
console.log(updated);  // ['red', 'yellow', 'blue'] - new array

// Supports negative indices
const last = colors2.with(-1, 'purple');
// ['red', 'green', 'purple']

findLast() and findLastIndex()

Search from the end of the array.

const numbers = [1, 2, 3, 2, 1];

// Find last occurrence
const lastTwo = numbers.findLast(n => n === 2);      // 2
const lastTwoIdx = numbers.findLastIndex(n => n === 2);  // 3

// Practical example
const transactions = [
  { id: 1, type: 'credit' },
  { id: 2, type: 'debit' },
  { id: 3, type: 'credit' },
  { id: 4, type: 'debit' }
];

const lastCredit = transactions.findLast(t => t.type === 'credit');
// { id: 3, type: 'credit' }

const lastCreditIdx = transactions.findLastIndex(t => t.type === 'credit');
// 2

Hashbang Grammar

Executable scripts with shebang.

#!/usr/bin/env node
// This is now valid JavaScript syntax
console.log('Hello from CLI!');

Symbols as WeakMap Keys

Use symbols (except registered) as WeakMap keys.

const privateData = new WeakMap();
const key = Symbol('private');

const obj = {};
privateData.set(key, { secret: 'data' });

console.log(privateData.get(key));  // { secret: 'data' }

// Registered symbols still not allowed
const registered = Symbol.for('global');
// privateData.set(registered, 'value');  // TypeError

Practical Migrations

Sorting Collections

// Before: Clone then sort
const sortedUsers = [...users].sort((a, b) => a.name.localeCompare(b.name));
// After: Single method
const sortedUsers = users.toSorted((a, b) => a.name.localeCompare(b.name));

State Updates (React/Redux style)

// Before
function updateItem(items, index, newValue) {
  const copy = [...items];
  copy[index] = newValue;
  return copy;
}
// After
function updateItem(items, index, newValue) {
  return items.with(index, newValue);
}

Finding Last Match

// Before
const logs = getLogEntries();
let lastError = null;
for (let i = logs.length - 1; i >= 0; i--) {
  if (logs[i].level === 'error') {
    lastError = logs[i];
    break;
  }
}
// After
const lastError = logs.findLast(entry => entry.level === 'error');

Source: SKILL.md on GitHub

No alerts14d4 checks · Risk SAFE
  • Gen Agent Trust Hub14d

    The skill provides a technical reference for modern JavaScript features and toolchains. It includes an indirect prompt injection surface by directing the agent to read and analyze project files, such as lockfiles and build configurations, which may contain untrusted data that could influence the agent's code generation logic.

  • Socket14d

    No alerts

  • Snyk14d

    Risk: LOW · No issues

  • Runlayer7mo

    12 files scanned · No issues

Signed by skilld at 00695ba. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 3 weeks ago

README badge

README badge for ccheney/robust-skills/modern-javascript