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.

referencesES2018-ES2019.md

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

ES2018 & ES2019 Features

Object rest/spread, for-await-of, Promise.finally(), RegExp named capture groups, lookbehind assertions, dotAll flag, Unicode property escapes, Array.flat(), Array.flatMap(), Object.fromEntries(), String.trimStart(), String.trimEnd(), optional catch binding, Symbol.description, stable sort.

Contents

ES2018 (ES9)

Rest/Spread Properties for Objects

// Object spread (copying)
const defaults = { theme: 'dark', lang: 'en' };
const userPrefs = { theme: 'light' };
const settings = { ...defaults, ...userPrefs };
// { theme: 'light', lang: 'en' }

// Object rest (destructuring)
const { name, ...rest } = { name: 'Alice', age: 30, city: 'NYC' };
// name = 'Alice'
// rest = { age: 30, city: 'NYC' }

// Function parameters
function update({ id, ...changes }) {
  return { id, ...changes, updatedAt: new Date() };
}

Asynchronous Iteration

// Async generator
async function* fetchPages(url) {
  let page = 1;
  while (true) {
    const res = await fetch(`${url}?page=${page}`);
    const data = await res.json();
    if (data.length === 0) break;
    yield data;
    page++;
  }
}

// for-await-of
for await (const page of fetchPages('/api/items')) {
  processPage(page);
}

// Async iterables
const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      async next() {
        if (i >= 3) return { done: true };
        await delay(100);
        return { value: i++, done: false };
      }
    };
  }
};

Promise.prototype.finally()

Run cleanup code regardless of Promise outcome.

showSpinner();

fetch('/api/data')
  .then(res => res.json())
  .then(data => process(data))
  .catch(err => showError(err))
  .finally(() => {
    hideSpinner();  // Always runs
  });

// With async/await
async function fetchData() {
  showSpinner();
  try {
    const res = await fetch('/api/data');
    return await res.json();
  } catch (err) {
    showError(err);
  } finally {
    hideSpinner();  // Always runs
  }
}

RegExp Named Capture Groups

// ❌ Before: numbered groups
const oldRegex = /(\d{4})-(\d{2})-(\d{2})/;
const oldMatch = '2024-03-15'.match(oldRegex);
const y = oldMatch[1];   // Which is which?
const m = oldMatch[2];
const d = oldMatch[3];

// ✅ Named groups
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2024-03-15'.match(dateRegex);
const { year, month, day } = match.groups;
// year = '2024', month = '03', day = '15'

// In replace()
const result = '2024-03-15'.replace(
  /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
  '$<month>/$<day>/$<year>'
);
// '03/15/2024'

RegExp Lookbehind Assertions

// Lookahead (existed before): (?=...) and (?!...)
// Lookbehind (new): (?<=...) and (?<!...)

// Positive lookbehind: match if preceded by pattern
const priceRegex = /(?<=\$)\d+(\.\d{2})?/;
'$19.99'.match(priceRegex);  // ['19.99']
'€19.99'.match(priceRegex);  // null

// Negative lookbehind: match if NOT preceded by pattern
const notEuroRegex = /(?<![€\d.])\d+(?:\.\d{2})?(?![\d.])/;
'€19.99'.match(notEuroRegex);  // null
'$19.99'.match(notEuroRegex);  // ['19.99']

RegExp dotAll Flag (s)

Make . match newlines too.

// ❌ Before: dot doesn't match newlines
/foo.bar/.test('foo\nbar');  // false

// ✅ With s flag
/foo.bar/s.test('foo\nbar');  // true

// Check flag
/foo.bar/s.dotAll;  // true

RegExp Unicode Property Escapes

Match characters by Unicode property.

// Match Greek letters
/\p{Script=Greek}/u.test('π');  // true
/\p{Script=Greek}/u.test('p');  // false

// Match any letter
/\p{Letter}/u.test('Ω');  // true
/\p{Letter}/u.test('5');  // false

// Match emoji
/\p{Emoji}/u.test('😀');  // true

// General category properties
/\p{Lowercase}/u;           // Lowercase letters
/\p{Uppercase}/u;           // Uppercase letters
/\p{Alphabetic}/u;          // Alphabetic characters
/\p{Number}/u;              // Any number (Nd, Nl, No)
/\p{Decimal_Number}/u;      // Decimal digits (0-9 in any script)
/\p{Punctuation}/u;         // Punctuation marks
/\p{White_Space}/u;         // Whitespace characters
/\p{Math}/u;                // Mathematical symbols

// Script properties
/\p{Script=Latin}/u;        // Latin script
/\p{Script=Greek}/u;        // Greek script
/\p{Script=Cyrillic}/u;     // Cyrillic script
/\p{Script=Han}/u;          // Chinese characters
/\p{Script=Hiragana}/u;     // Japanese Hiragana
/\p{Script=Katakana}/u;     // Japanese Katakana
/\p{Script=Arabic}/u;       // Arabic script
/\p{Script=Hebrew}/u;       // Hebrew script

Template Literal Revision

Allow invalid escape sequences in tagged templates.

// Before ES2018, this would throw SyntaxError
function latex(strings) {
  return strings.raw[0];
}
latex`\unicode`;  // Now works, returns '\\unicode'

// Cooked value is undefined for invalid escapes
function tag(strings) {
  console.log(strings[0]);      // undefined
  console.log(strings.raw[0]);  // '\\unicode'
}
tag`\unicode`;

ES2019 (ES10)

Array.prototype.flat()

Flatten nested arrays.

// Default: flatten one level
[1, [2, [3, [4]]]].flat();     // [1, 2, [3, [4]]]

// Specify depth
[1, [2, [3, [4]]]].flat(2);    // [1, 2, 3, [4]]

// Flatten completely
[1, [2, [3, [4]]]].flat(Infinity);  // [1, 2, 3, 4]

// Removes holes
[1, , 3, , 5].flat();  // [1, 3, 5]

Array.prototype.flatMap()

Map then flatten (one level).

// Equivalent to .map().flat()
const sentences = ['Hello world', 'How are you'];

// ❌ map returns nested arrays
sentences.map(s => s.split(' '));
// [['Hello', 'world'], ['How', 'are', 'you']]

// ✅ flatMap flattens
sentences.flatMap(s => s.split(' '));
// ['Hello', 'world', 'How', 'are', 'you']

// Can also filter by returning empty array
const nums = [1, 2, 3, 4];
nums.flatMap(n => n % 2 ? [n] : []);  // [1, 3]

Object.fromEntries()

Create object from key-value pairs (inverse of Object.entries).

// From entries array
Object.fromEntries([['a', 1], ['b', 2]]);
// { a: 1, b: 2 }

// From Map
const map = new Map([['name', 'Alice'], ['age', 30]]);
Object.fromEntries(map);
// { name: 'Alice', age: 30 }

// Transform object
const prices = { apple: 1, banana: 2 };
const doubled = Object.fromEntries(
  Object.entries(prices).map(([k, v]) => [k, v * 2])
);
// { apple: 2, banana: 4 }

// Filter object
const filtered = Object.fromEntries(
  Object.entries(obj).filter(([k, v]) => v > 0)
);

// URL search params to object
const params = new URLSearchParams('name=Alice&age=30');
Object.fromEntries(params);
// { name: 'Alice', age: '30' }

String.prototype.trimStart() and trimEnd()

const str = '  Hello World  ';

str.trimStart();  // 'Hello World  '
str.trimEnd();    // '  Hello World'
str.trim();       // 'Hello World'

// Aliases (for compatibility)
str.trimLeft();   // Same as trimStart()
str.trimRight();  // Same as trimEnd()

Optional Catch Binding

Omit the error parameter if not needed.

// ❌ Before: always needed parameter
function parseOld(input) {
  try {
    return JSON.parse(input);
  } catch (e) {  // 'e' is unused
    return null;
  }
}

// ✅ Now optional
function parseNew(input) {
  try {
    return JSON.parse(input);
  } catch {
    return null;
  }
}

Symbol.prototype.description

Access symbol description directly.

const sym = Symbol('mySymbol');

// ❌ Before: had to parse toString()
sym.toString();  // 'Symbol(mySymbol)'

// ✅ Now: direct access
sym.description;  // 'mySymbol'

Symbol().description;  // undefined
Symbol('').description;  // ''

Stable Array.prototype.sort()

Sort is now guaranteed to be stable (equal elements maintain order).

const items = [
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 25 },
];

// Sort by age - Alice and Bob maintain relative order
// Note: Use .toSorted() in ES2023+ to avoid mutation
const sorted = items.toSorted((a, b) => a.age - b.age);
// [
//   { name: 'Charlie', age: 25 },
//   { name: 'Alice', age: 30 },    <- Alice before Bob (stable)
//   { name: 'Bob', age: 30 },
// ]

Well-formed JSON.stringify()

Properly encode lone surrogates as escape sequences.

// Before: could produce invalid Unicode
JSON.stringify('\uD800');  // '"\uD800"' (invalid)

// After: escape sequences for lone surrogates
JSON.stringify('\uD800');  // '"\\ud800"' (valid)

Function.prototype.toString() Revision

Returns exact source code including whitespace and comments.

function /* comment */ foo() { }

foo.toString();
// 'function /* comment */ foo() { }'
// (Before, comments/whitespace might be stripped)

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